만들어볼 게임은 2가지다.


(1) 가위바위보

(2) 숫자 맞히기


둘 다 공통적으로 rand crate를 사용한다.


// Cargo.toml [package] name = "rps-game" # 참고로 여긴 하이픈으로 써도 코드 내에선 언더바로 써야 한다 version = "0.1.0" edition = "2021" [dependencies] rand = "0.8.4"


미리 말해둘 점은 가위바위보든 RPS든

셋 다 123이라고 가정했을 때

1 < 2

2 < 3

3 < 1

이런 관계가 성립한다는 것이다


그러니 선택지를 숫자로 제시하고

해당 숫자의 비교를 하면 될 것이다.


우선 틀을 잡아보겠다


use rand::Rng; // rand crate의 Rng 사용 (Random number generator) fn main() { // startup introduction println!("Welcome to RPS Game."); // game loop loop { // write game logic here } // shutdown message println!("Goodbye."); }


게임 루프에 쓸 내용은 다음과 같다.


1. 123의 선택지를 제시할 것. (q는 종료)

2. 컴퓨터도 숫자를 정할 것.

3. 비교 후 승패를 판별할 것.


ctrl + c로 강제 종료가 되긴 하지만,

그것보단 정상적인 종료 방법을 제공해주는 것이 좋다.


use rand::Rng; // rand crate의 Rng 사용 (Random number generator) use std::io::Write; // flush를 위해 필요 fn main() { // startup introduction println!("Welcome to RPS Game."); // game loop loop { // String for getting user input let mut user_input = String::new(); // show input guide print!("Choose your option: (1) Rock, (2) Paper, (3) Scissors, (q) quit: "); std::io::stdout().flush().unwrap(); // get user input std::io::stdin().read_line(&mut user_input).unwrap(); // remove the newline character user_input = user_input.trim().to_string(); // if user input is q, then quit the game if user_input == "q" { break; } // convert user input to integer let user_input: i32 = match user_input.parse() { Ok(num) => num, Err(_) => { continue; } }; // if user input is not 1, 2, or 3, then continue the game loop if user_input < 1 || user_input > 3 { continue; } // computer input } // shutdown message println!("Goodbye."); }


여기까지가 딱 유저 입력을 받는 부분이다.


flush, read_line은 3편 사용자 입출력 편에서 설명한 적이 있다.


read_line을 하게 되면 마지막에 엔터가 들어가게 된다.

trim은 그 부분을 잘라낼 수 있다.


trim은 &str을 반환하기 때문에 to_string을 써서 String으로 바꿔줘야 한다.


이제 컴퓨터 입력을 받아보겠다.


let computer_input: i32 = rand::thread_rng().gen_range(1..=3);


컴퓨터 입력은 String으로 할 필요 없이 바로 i32로 받으면 된다.

저렇게 하면 1부터 3까지의 숫자가 랜덤으로 나온다.


이제 서로의 선택을 보여준다.

숫자만 띄우면 좀 그러니까, 문자열로 변환을 해준다.


Rock, Paper, Scissors, 이들은 길이가 다르기 때문에

첫 글자를 따서 RPS라고만 짓겠다.


그리고 승패를 미리 판별해 <, =, >, 셋 중 하나를 띄울 것이다.


// computer input let computer_input: i32 = rand::thread_rng().gen_range(1..=3); // convert to play let user_play = match user_input { 1 => "R", 2 => "P", _ => "S", }; let computer_play = match computer_input { 1 => "R", 2 => "P", _ => "S", }; // compare the play let user_win; // 초기화는 바로 하지 않아도 된다. let sign; // 다만 타입은 일치시켜야 한다. if user_input == computer_input { sign = "="; user_win = 0; } else if user_input == 1 && computer_input == 3 || user_input == 2 && computer_input == 1 || user_input == 3 && computer_input == 2 { sign = ">"; user_win = 1; } else { sign = "<"; user_win = -1; } // show each other's choices print!("[You] {} {} {} [CPU] ", user_play, sign, computer_play); // show the result match user_win { 1 => println!("You won."), 0 => println!("Draw."), _ => println!("CPU won."), }


결과:

Welcome to RPS Game. Choose your option: (1) Rock, (2) Paper, (3) Scissors, (q) quit: 1 [You] R > S [CPU] You won. Choose your option: (1) Rock, (2) Paper, (3) Scissors, (q) quit: 2 [You] P = P [CPU] Draw. Choose your option: (1) Rock, (2) Paper, (3) Scissors, (q) quit: 3 [You] S < R [CPU] CPU won. Choose your option: (1) Rock, (2) Paper, (3) Scissors, (q) quit: 4 Choose your option: (1) Rock, (2) Paper, (3) Scissors, (q) quit: asd Choose your option: (1) Rock, (2) Paper, (3) Scissors, (q) quit: q Goodbye.


컴퓨터 플레이가 랜덤으로 나오고 있고,

또 1~3이나 q가 아닌 선택지도 잘 무시하는 모습이다.


이제 허들을 약간 높여 숫자 추측 게임을 만들어 볼 것이다.


코드를 줄이기 위해 다음 함수가 있다고 가정하겠다.


fn get_string(guide: &str) -> String { // show guide print!("{}", guide); std::io::stdout().flush().unwrap(); // get input let mut input = String::new(); std::io::stdin().read_line(&mut input).unwrap(); return input.trim().to_string(); }


가이드를 인자로 넣으면 print와 flush를 해주고,

문자열을 받은 뒤 그것을 리턴해주는 함수다.


이 부분은 main의 밑에 써도 상관없고, 위에 써도 상관없다.

러스트는 함수를 쓰려고 하면 그 함수가 어디에 있는지 알아서 찾아준다.


선언부와 헤더 파일을 만드느라 고생하지 않아도 된다는 점은 러스트의 큰 장점 중 하나다


이제 루프를 만들 건데, 이중으로 만들어야 한다.


첫번째 루프에서 숫자를 하나 정할거고,

두번째 루프에서 맞힐 때까지 라운드를 반복할 것이다.


fn main() { // startup introduction println!("Welcome to Guessing Game."); // game loop loop { let secret_number = rand::thread_rng().gen_range(1..=100); // round loop loop { } } // shutdown message println!("Goodbye."); }


대략적인 틀은 위와 같다.


여기서 내용을 채우면 다음과 같다.


fn main() { // startup introduction println!("Welcome to Guessing Game."); // game loop 'outer: loop { let secret_number = rand::thread_rng().gen_range(1..=100); // round loop loop { let guess = get_string("Input a number from 1 to 100 (q to quit): "); // check quit if guess == "q" { break 'outer; } // check parse let guess: i32 = match guess.parse() { Ok(num) => num, Err(_) => continue, }; // check range if guess < 1 || guess > 100 { continue; } if guess < secret_number { println!("Too small."); } else if guess > secret_number { println!("Too big."); } else { println!("Correct."); break; } } } // shutdown message println!("Goodbye."); }


q를 입력할 때 바깥 루프를 탈출하기 위해 레이블링을 하였다.


결과:


Welcome to Guessing Game. Input a number from 1 to 100 (q to quit): 50 Too small. Input a number from 1 to 100 (q to quit): 75 Too small. Input a number from 1 to 100 (q to quit): 85 Too big. Input a number from 1 to 100 (q to quit): 80 Too small. Input a number from 1 to 100 (q to quit): 81 Correct. Input a number from 1 to 100 (q to quit): 81 Too big. Input a number from 1 to 100 (q to quit): q Goodbye.


맞히기 전까지 수를 잘 유지하다가,

맞힌 후엔 정답인 수가 제대로 바뀌는 모습이다.

q를 입력하였을 때 정상적인 종료도 가능하다.



사용자 입출력, 난수 생성 등을 이용해 간단한 게임을 2개 만들어보았다.


사용자 입출력을 해 보았으니, 다음 5편 때는 파일 입출력을 해 보겠다.