▶이전글: C#으로 게임 만들기: ④ 이벤트 핸들러로 마우스 클릭 이벤트 만들기
플레이어가 게임을 클리어한 시간을 기록할 수 있다면 동물 짝 맞추기 게임이 더 재밌어질 것 같지 않나요? 이번에는 플레이어의 실행 시간을 기록할 수 있도록 타이머(timer)를 추가해 보겠습니다.
게임에 재미를 더해 볼까요? 게임 시작과 동시에 타이머가 시작되고 경과 시간을 화면 아래쪽에 표시합니다. 모든 동물의 짝을 맞추면 타이머가 멈춥니다.
타이머는 일정 시간마다 메서드를 반복 호출합니다. 여기서 사용할 타이머는 플레이어가 게임을 시작하면 같이 시작하고, 모든 동물의 짝을 맞추면 멈춰야 합니다.
01 MainWindow.xaml.cs 파일에서 namespace 키워드를 찾아서 다음 using 문을 using System. Windows.Threading; 코드를 다음처럼 추가합니다.
namespace MatchGame
{
using System.Windows.Threading;
02 public partial class MainWindow를 찾아 여는 중괄호 ({) 바로 아랫줄에 타이머 생성 코드를 추가합니다
public partial class MainWindow : Window
{
DispatcherTimer timer = new DispatcherTimer();
int tenthsOfSecondsElapsed;
int matchesFound;
세 번째 ~ 다섯 번째 코드는 새로운 타이머를 만들고, 경과 시간과 플레이어가 맞춘 동물 쌍의 숫자를 저장하는 2개의 필드를 추가합니다.
03 타이머에 어떤 메서드를 얼마나 자주 호출할지를 지정해야 합니다. SetUpGame() 메서드 위에 빈 줄을 추가하고, 다음 이미지처럼 timer로 시작하는 코드를 두 줄 입력합니다. += 연산자를 입력할 때 다음과 같은 팝업 메시지가 출력됩니다.
04 [Tab] 키를 누릅니다. 그러면 자동으로 Timer_Tick() 메서드가 추가됩니다.
05 Timer_Tick() 메서드는 그리드 맨 아래의 행 전체에 걸쳐 있는 TextBlock의 내용을 업데이트합니다. 설정하는 방법을 알아보겠습니다. 디자인 화면으로 이동하세요.
여기까지 하고 나면 XAML 코드는 다음과 같을 것입니다. [XAML 편집기]에 표시된 코드와 비교해 보세요.
<TextBlock x:Name="TimeTextBlock" HorizontalAlignment="Center" Grid.Row="4" TextWrapping="Wrap" Text="Elapsed time" VerticalAlignment="Center" Grid.ColumnSpan="4" FontSize="36" MouseDown="TimeTextBlock_MouseDown"/>
06 MouseDown 이벤트 처리기를 추가하면 이전 TextBlock 컨트롤에서와 마찬가지로 [코드 편집기]에 TimeTextBlock_MouseDown() 메서드가 생성됩니다. 다음 코드를 TimeTextBlock_MouseDown() 메서드 안에 추가합니다.
private void TimeTextBlock_MouseDown(object sender, MouseButtonEventArgs e) {
if (matchesFound == 8) { //이 코드는 8쌍의 동물 이모지를 전부 맞추면 게임을 리셋하는 역할을 합니다.
SetUpGame();
}
}
07 이제 Timer_Tick() 메서드 구현에 필요한 모든 것이 준비됐습니다. Timer_Tick() 메서드를 TextBlock 컨트롤에 경과 시간을 표시하고, 플레이어가 모든 짝을 맞췄을 경우 타이머를 중지하는 코드를 찾아 작성해 보세요.
private void Timer_Tick(object sender, EventArgs e) {
tenthsOfSecondsElapsed++;
TimeTextBlock.Text = (tenthsOfSecondsElapsed / 10F).ToString("0.0s");
if (matchesFound == 8) {
timer.Stop();
TimeTextBlock.Text = TimeTextBlock.Text + " - Play again?";
}
}
프로그램을 실행해 보겠습니다. 이런, 예외가 발생했네요. 우선 비주얼 스튜디오에서 예외 내용과 함께 강조 표시가 된 구문을 살펴볼게요.
버그(bug)라는 단어를 들어본 적이 있나요? 어쩌면 이전에 ‘이 게임은 정말 버그가 심해, 문제가 많아’ 같은 대화를 들은 적이 있을지도 모릅니다. 모든 버그에는 원인이 있죠. 방금 발생한 예외도 마찬가지입니다. 하지만 경우에 따라서는 버그를 추적하는 일이 쉽지 않습니다.
버그가 발생한 이유를 이해하는 것이 버그를 해결하기 위한 첫걸음입니다. 다행히도 비주얼 스튜디오는 버그가 왜 발생했는지 찾아보기에 아주 훌륭한 도구입니다. 버그를 제거하는 도구인 디버거(debugger)가 있기 때문이죠.
01 게임을 몇 번 재시작해 봅니다.
먼저 프로그램이 항상 동일한 예외와 오류 메시지를 발생시키는지 확인해야 합니다.
예외는 C#이 코드가 실행되는 동안 무언가 잘못됐다는 것을 개발자에게 알려 주는 방식입니다. 모든 예외는 타입(type)을 가지며 방금 발생한 예외의 타입은 ArgumentOutOfRangeException입니다. 예외는 무엇이 잘못됐는지 파악하기 위한 오류 메시지도 포함합니다. 이 예외의 오류 메시지는 ‘Index was out of range’입니다. 오류 메시지는 무엇이 잘못됐는지 알려 주는 유용한 정보입니다. 예외가 발생하는 것은 좋은 소식입니다. 문제를 발견했고 고칠 수 있으니까요.
[예외 상자]를 옆으로 치우면 어느 명령어에서 프로그램이 멈추는지 볼 수 있습니다.
이 예외는 재현(reproducible)할 수 있습니다. 프로그램을 실행할 때마다 동일한 예외가 발생하네요. 이런 경우에는 문제가 무엇인지 쉽게 파악할 수 있죠.
02 예외가 있는 코드에 중단점을 추가합니다.
프로그램을 다시 실행해서 예외가 발생한 지점으로 이동합니다. 프로그램을 중지하기 전에 [디버그] ‐ [중단점 설정/해제] 메뉴를 선택합니다. 그러면 명령어가 빨간색으로 강조되면서 왼쪽에 빨간점이 표시됩니다. 앱을 중지해 보세요. 강조 표시와 빨간점이 그대로 남아 있습니다.
방금 명령어에 중단점(breakpoint)을 설정했습니다. 프로그램을 실행하면 이 명령어를 실행하려고 할 때마다 프로그램이 중단됩니다. 한번 시험해 볼게요. 앱을 다시 실행하면 앱이 해당 명령어에서 중단되지만, 이번에는 예외를 표시하지 않습니다. [계속] 버튼을 누르세요. 앱이 또 중단됩니다. [계속] 버튼을 눌러서 예외가 발생할 때까지 진행하세요. 그런 다음 다시 앱을 중지하세요.
03 무엇이 문제를 일으키는지 증거를 수집합니다.
앱을 실행하면서 [로컬] 창에서 재밌는 점을 발견하지 않았나요? 앱을 다시 실행하고 animalEmoji 변수의 값을 눈여겨보세요. 앱이 처음 중단될 때, [로컬] 창에는 다음과 같은 값이 표시됩니다.
[계속] 버튼을 한번 눌러보세요. Count 값이 16에서 15로 1만큼 감소합니다.
이 부분의 코드는 animalEmoji 목록에서 임의의 이모지를 꺼내 TextBlock에 넣은 다음 animalEmoji 목록에서 그 이모지를 제거합니다. 그래서 Count 값이 1씩 감소하는 것이죠. 코드를 계속 실행해 보면, Count 값이 0이 될 때까지는 괜찮은데, 그 다음부터 예외가 발생합니다. 증거를 하나 찾았네요! 또 다른 증거는 이 예외가 foreach 반복문(loop)에서 발생한다는 것과 이 예외가 새 TextBlock을 추가하고 나서 발생했다는 겁니다. 셜록 홈즈가 되어 추리할 시간이군요. 예외에 대한 탐정 수사를 해 볼까요?
04 버그가 생기는 이유가 무엇인지 알아봅시다.
프로그램에 버그가 생기는 이유는 animalEmoji 목록에서 다음 이모지를 꺼내려고 할 때 목록이 비어 있기 때문입니다. 그래서 ArgumentOutOfRange 예외가 발생하는 거구요. 왜 목록이 비어 있을까요?
뭔가를 변경하기 전까지 프로그램은 잘 작동했습니다. 그런데 TextBlock을 추가했더니 갑자기 프로그램이 멈췄네요. 참고로 반복문에서는 모든 TextBlock에 대한 반복 처리를 하는데요. 점점 실마리가 풀리네요..!
앱을 실행하면 UI에 있는 모든 TextBlock마다 예외가 발생한 명령문에서 실행이 중단됩니다. 물론 컬렉션에는 이모지가 16개 들어 있기 때문에 TextBlock 16개까지는 문제가 없습니다.
하지만 현재 UI 아래에는 새로 추가된 TextBlock이 있으므로, 17번째로 프로그램이 실행됐을 때 animalEmoji 목록은 비어 있습니다. animalEmoji 목록에는 16개의 이모지가 들어 있기 때문입니다.
TextBlock을 추가하기 전에는 목록에 TextBlock 16개와 이모지 16개가 들어 있어서 TextBlock 1개당 이모지 1개를 할당할 수 있었습니다. 그러나 지금은 17개의 TextBlock이 있지만 이모지는 16개뿐이어서 더 할당할 이모지가 없기 때문에 예외가 발생합니다.
05 버그를 고칩시다.
반복문에서 TextBlock에 할당할 이모지 수가 부족해져서 예외가 발생했으므로 방금 추가한 TextBlock에는 이모지를 할 당하지 않고 넘어가게 만들면 버그를 고칠 수 있습니다. 조건문을 추가해서 TextBlock의 이름이 새로 추가한 TextBlock 의 이름과 일치하면 할당하지 않고 넘어가도록 코드를 고쳐 봅시다. 그런 다음 중단점을 클릭해서 제거하거나 [디버그]‐[모든 중단점 삭제] 메뉴를 선택해서 중단점을 제거하겠습니다.
아직 할 일이 남았습니다. TimeTextBlock_MouseDown() 메서드는 matchesFound 필드의 값을 체크하지만, 이 필드에 값을 지정하는 명령문이 없습니다. 그러니 SetUpGame() 메서드의 foreach 문이 끝나는 중괄호 다음에 세 줄의 코드를 추가합니다.
그런 다음 TextBlock_MouseDown 메서드의 else/if 문 블록 중간에 다음 명령문을 추가합니다.
마지막으로, 잊지 말고 중단점을 삭제하세요. 이제 게임에 이 타이머는 플레이어가 모든 짝을 맞췄을 때 멈추고, 플레이어는 ‘Play again?’이라고 뜨는 TextBlock을 클릭해 게임을 다시 시작할 수 있습니다. 축하합니다! C#으로 첫 게임을 완성했어요!
https://github.com/head-first-csharp/fourth-edition 에서 게임 프로젝트의 전체 코드를 살펴보거나 다운로드할 수 있습니다.
게임을 완성했으니, 완성된 코드를 Git에 푸시해 봅시다. 비주얼 스튜디오를 사용하면 쉽게 이 작업을 할 수 있습니다. 커밋을 스테이징(staging)하고, 커밋 메시지를 입력한 다음, 이 변경 사항을 원격 저장소와 동기화(sync)하면 됩니다.
01 커밋 메시지에 변경한 내용을 입력합니다.
02 버튼을 눌러 파일을 스테이징합니다. 스테이징은 Git에 파일을 푸시할 준비가 됐다고 알려 주는 역할을 합니다. 파일을 스테이징한 다음에 변경하면 푸시해도 스테이징된 내용만 원격 저장소에 저장됩니다. 이후에 변경한 내용은 원격 저장소에 저장되지 않습니다.
03 스테이징된 항목 커밋]‐[스테이징된 항목 커밋 후 동기화]를 선택합니다. 몇 초가 지나면 동기화가 끝나고 [Git 변경 내용] 탭에서 다음과 같은 푸시 성공 메시지를 볼 수 있습니다.
큰 프로젝트는 여러 개의 작은 프로젝트로 나누어 진행해 보세요. 크고 어려운 문제를 해결할 때 유용한 프로그래밍 전략은 문제를 더 작고 쉬운 문제로 분할하는 것입니다. 큰 프로젝트를 시작할 때 문제 자체에 압도되어 ‘이걸 어디서부터 어떻게 시작해야 하지...’라고 생각하기 쉽습니다.
하지만 문제를 작은 단위로 분할하면 작은 것부터 차근차근 시작할 수 있죠. 작은 부분 하나를 완성한 다음에 또 다른 작은 부분을 해결하며 전체 프로젝트를 하나씩 완성해 나가는 것입니다. 한 문제를 완성할 때마다 그에 따라 프로젝트를 어떻게 완성하는지 배울 수 있습니다.
위 내용은 『헤드퍼스트 C#(4판)』의 내용을 재구성하여 작성되었습니다.
이전 글 : 챗GPT는 어떻게 사람이 쓴 듯한 글을 자동으로 생성해낼까요?
다음 글 : 왜 C#을 배워야 하나요?
최신 콘텐츠