2. 추리 게임
간단 실습 프로젝트
간단한 숫자를 추리하는 게임을 만들어본다.
간단한 디버깅 과정
사실 이 코드는 문자열을 받아서 처리하는 코드이다.
use std:io;
표준 라이브러리를 prelude라고 한다.
원하는 함수가 없다면 use 문을 활용하여 명시적으로 타입을 가져온다.
let mut guess = String::new();
변수는 기본적으로 불변으로 선언된다.
그러나 mut을 넣어 가변적으로 만든다.
그리고 그 변수에는 String의 인스턴스를 넣는다.
::는 new가 String의 연관함수임을 뜻한다.
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line")
stdin()은 Stdin의 인스턴스 반환한다.
read_line을 통해 표준 입력 핸들의 값을 가져온다.
값은 guess에 저장된다.
read_line은 문자열에 추가함.
메서드로 인해 문자열의 내용물을 바꾸기에 가변이어야 함
&를 통해 참조자를 나타낸다.
참조자는 기본적으로 불변이다.
반환 값은 Result이며, Result는 enum, 즉 열거형 타입이다.
- enum
- 여러 개의 가능한 상태 중 하나의 값이 될 수 있는 타입
- 각 값을 variant(배리언트)라고 함
Result 는Ok
,Err
와 같은 에러 처리용 정보를 담음
Result의 메서드 중 expect는 배리언트가 Err
일 때 작동 멈추고 인수로 넘겨진 메시지 출력한다.
Ok
라면 결과값을 돌려준다.
expect가 없을 때는 경고가 출력된다.
println!("{}")
{}는 자리표시자이다.
난수 생성
난수를 만들어주는 rand 크레이트를 가져온다.
크레이트란 러스트 코드 파일들의 모음이다.
자체적으로 실행되지 않고 다른 곳에서 사용되는 것은 라이브러리 크레이트라고 부른다.
Cargo.toml에서 의존성을 추가해준다.
의존성을 추가하면 Crates.io에서 레지스트리를 가져오고, 그로부터 최신 버전의 크레이트를 가져온다.
이때 build를 다시 해주면 해당 의존성을 업데이트하게 된다.
이러한 모든 의존성은 Cargo.lock에 기록된다.
업데이트하고 싶다면 cargo update
를 해준다.
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
}
Rng는 난수 생성기 메서드들이 정의되어 있는 트레잇이다.
각종 메서드 사용을 위해 스코프 내에 있어야만 한다.
thread_rng는 OS에서 시드를 정하고 현재 스레드에서만 사용되는 난수 생성기이다.
gen_range는 Rng 구문을 통해 스코프로 가져와졌다.
start..=end 형식으로 범위를 지정한다.
크레이트에 어떤 기능이 있는지 모를 때는 문서를 참고하면 된다.
cargo doc --open을 통해 현재 추가된 의존성들의 문서를 확인할 수 있다.
숫자 비교
Ordering은 열거형이다.
match는 switch와 비슷하다.
cmp의 결과값이 Ordering이다.
실제로 이렇게 코드를 짜면 에러가 발생한다.
일치하지 않는 타입이 있기 때문이다.
정적 타입을 가지고 있으나 타입 추론도 수행
secret_number는 gen_range의 반환을 가지며 이는 기본적으로 int32의 값을 가진다.
let guess: u32 = guess.trim().parse().expect("Please type a number!");
이 코드를 추가하면 에러가 없어진다.
guess의 값은 섀도잉(shadow)을 통해 새로운 값이 된다.
변수 이름을 재사용하는 것이 가능하다!
String의 trim은 공백을 제거한다.
이는 사용자의 입력 시에 끝에 개행 문자가 들어가기 때문이다.
윈도우의 경우 캐리지 리턴\r
까지 들어가는데 trim은 이것까지 제거한다.
parse는 다른 타입으로 변환해준다.
앞에서 : u32를 명시했기에 parse는 부호 없는 정수로 바뀐다.
parse는 에러가 날 가능성을 가지고 있다.
그래서 expect를 추가해준다.
#question 모든 메서드는 expect를 가질 수 있는가?
반복문 도입
loop {
println!("Please input your guess.");
let mut guess = String::new();
// --생략--
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("You guessed: {guess}");
// --생략--
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => {
println!("You win!");
break;
}
}
}
}
두 가지 수정사항이 있다.
먼저 loop를 씌워 반복문을 만든다.
첫번째는 guess를 정수로 바꿀 때 match문을 넣어서 에러가 날 시에 continue를 시킨다.
두번째는 비교 match에서 성공 시 break를 건다.
_은 모든 값을 매칭시킨다.
최종 코드
조금 내가 원하는 대로 수정해주었다.