인물은 이렇게 말했다. 인물은 이렇게 말했다.

2048 게임을 C언어로 구현해볼까요?

글. 컴퓨터공학부 3 심성원 편집. 조선해양공학과 3 서지영
여러분 안녕하세요, 이번에 신설된 코너인 <어때요, 코딩 정말 쉽죠?> 1호 기사를 담당하게 된 컴퓨터공학부 심성원입니다. 여러분들은 2048 게임에 대해서 들어본 적이 있나요? 이 게임은 2018년 즈음에 할 일이 없어 심심했던 많은 이들을 달래준 게임 중 하나인데요. 아직 해본 경험이 없다면 play2048.co에 접속하셔서 한 번 해보시는 것도 좋을 것 같아요! 게임 방법은 방향키를 사용하여 2의 거듭제곱수가 적힌 타일들을 움직여 같은 숫자가 적힌 숫자들을 하나로 합친 후, 합쳐져서 숫자가 더해진 타일 중에서 제일 큰 수가 2048이 되도록 하는 거예요. 참고로 서울대학교 컴퓨터공학부에 입학해서 듣는 첫 전공 수업인 ‘프로그래밍 연습’의 최종 과제가 2048 게임을 업그레이드 버전으로 구현하는 것일 정도로 2048 게임 구현은 기초적인 코딩연습을 할 수 있는 좋은 기회인데요. 그렇기에 오늘은 여러분들과 함께 프로그래밍 언어의 시초라고 할 수 있는 C 언어를 통해서 2048 게임을 구현해볼까 합니다.
그림 1. 라이브러리와 매크로 설정

1. 라이브러리 채택 및 매크로 설정

코딩 경험이 많지 않은 독자 여러분들에게는 다소 낯선 과제일수도 있겠네요. 일단, 언어와 관계없이 프로그램을 작성하기 위해서는 먼저 어떤 기능을 구현할지, 그리고 그 기능을 구현하기 위해서는 어떤 라이브러리와 매크로가 필요한지를 고려할 필요가 있습니다. 여기서 라이브러리란 프로그램 제작의 편의성을 높이기 위해서 사전에 구현해 놓은 함수들을 모아둔 곳을 일컫는데 [그림 1]에서 .h로 끝나는 헤더파일들이 이에 해당됩니다.


여기서는 입출력을 위한 stdio, 시간측정을 위한 time, 콘솔을 위한 conio, 윈도우 API 사용을 위한 windows 헤더파일을 선언해 주었습니다. 매크로는 프로그램의 가독성을 높이기 위해 반복되는 숫자나 기호들을 저희가 인식하기 쉬운 글자나 기호로 대체하는 역할을 수행하는데요, [그림 1] 하단에서의 #define으로 시작하는 문장들이 바로 매크로입니다.

이때, 실제 방향키 조작은 아직 여러분들에게 어려울 수 있기에 ‘w’, ‘a’, ‘s’, ‘d’를 방향키 대신 사용하려고 하는데 각 매크로 문장에 적혀있는 숫자들은 각 알파벳에 대응하는 1아스키코드 값으로 컴퓨터에서 저 알파벳 문자들과 초록색 숫자들을 같은 것으로 인식합니다. 예를 들어 이제 아래 코드에서 LEFT와 같은 기호를 보면 이 모든 기호를 2컴파일러에서 97이라는 숫자로 대체하는 방식으로 매크로가 작용하는 것이죠.

그림 2. 하중이 가해지는 모래-점토-모래 지반

2. 전역변수 및 함수 선언

위에서 라이브러리와 매크로를 모두 설정해주었다면 이제는 본격적으로 코딩을 시작할 차례입니다. 2048 게임에서의 점수(score)와 게임판(board)과 판의 변화 유무(act) 변수들은 프로그램 전체적으로 사용되는 변수이기 때문에 3전역변수로 따로 선언해 주었습니다. 그다음으로는 [그림 2]처럼 방향 이동과 같이 필요 기능들을 미리 고안하여 각 기능의 함수들을 미리 선언해 주어야 합니다.

여기서 함수를 미리 선언하는 이유는 프로그램 제작 중에 필수 기능을 자세히 구현하지 않더라도 선언을 미리 하였을 경우 이 함수가 존재한다고 가정하여 큰 오류 없이 프로그램의 큰 틀을 미리 짤 수가 있기 때문이랍니다.

이 게임을 만들면서 제가 필요할 것이라고 예상되어 미리 선언해놓은 함수들은 바로 판을 그리는 함수 draw(), 랜덤넘버 생성 함수 rand_num(), 게임오버인지 클리어인지 확인하는 함수 check_game_over(clear)(), 임시 숫자를 제거해주는 함수 board_clear(), 판이 이동했을 경우 그 이후를 준비해주는 함수 after_act() 그리고 각 방향으로 판을 이동시켜주는 함수들입니다.

11963년 미국 ANSI에서 표준화한 정보교환용 7비트 부호체계이다. 000(0x00)부터 127(0x7F)까지 총 128개의 부호가 사용된다. 이는 영문 키보드로 입력할 수 있는 모든 기호들이 할당되어 있는 부호 체계이다.
2 고급언어로 쓰인 프로그램을 그와 의미적으로 동등하며 컴퓨터에서 즉시 실행될 수 있는 형태의 목적 프로그램으로 바꾸어 주는 번역 프로그램.
3프로그램 내 전 모듈들을 변수 선언의 유효한 영역으로 취하는 변수. 주어진 프로그램 어디에서나 접근이 가능한 변수.

그림 3. 게임실행 함수

3. 게임 진행 함수 제작 및 선언한 함수 구현

위에서 선언한 함수들의 기능이 구현되어있다는 가정하에 제일 중요한 게임을 진행시키는 함수 game_start()를 먼저 구현하는 것입니다.

2048 게임 원칙에 따라 먼저 게임판에 랜덤으로 숫자 2개(2 혹은 4)를 임의로 생성하고 게임화면을 그려줍니다. 그 후 매크로에 따라 특정 키가 입력되면 그 방향으로 이동하는 함수를 불러줍니다.

그림 4. 방향키 입력시 해당 방향으로 판을 이동시키는 함수

이때 switch 문을 활용해서 입력된 방향키에 따라서 실행되는 함수가 다르도록 해야 합니다. [그림 4]를 참고하시면 방향키 입력 후 해당 방향으로 이동시키는 함수가 어떤 원리로 판을 이동시키는지 자세하게 주석으로 설명해 두었습니다. [그림 4]에서는 왼쪽 방향키에 대해서만 나와 있는데 다른 방향 이동에 대한 원리도 동일하기 때문에 이에 대한 설명은 생략하겠습니다.

[그림 4]에서 같은 숫자끼리 더해지는 케이스(else if 문)를 보시면 합쳐진 수에 10000을 더하는 걸 볼 수 있으실 텐데요. 이는 같은 숫자가 연달아 있을 때 한 번씩만 더해지도록 하기 위한 임시의 수입니다. 이 과정이 없으면 ‘2222’처럼 같은 숫자가 연달아 존재할 때 이를 왼쪽이나 오른쪽 방향키를 누를 경우 ‘2’ 4개가 ‘44’가 아닌 ‘8’로 합쳐지는데 이렇게 되면 게임의 난이도가 너무 쉬워지니까요!

그림 5. 판에 임시 수를 제거해주는 함수와 이동이 있었을 경우 새로운 숫자를 생성하고 판을 재차 그려주는 함수

그렇기에 board_clear() 함수를 통해 더해주었던 10000을 판에서 다시 제거해주는 과정을 거쳐야 다음 실행에 영향을 끼치지 않게 된답니다.

그리고 게임판이 움직였다고 판단이 되면(act가 0보다 크면) 게임판에 랜덤한 빈 공간에 새로운 숫자를 생성하고 이때 게임이 끝났거나 클리어되었는지 확인해주는 after_act() 함수를 실행시켜 주면 game_start() 함수의 while 문 내부 전부를 이해한 것이라 보아도 무방합니다.

[그림 5]를 참고하시면 board_clear()와 after_act()의 기능을 이해하는 데 도움이 될 거 같습니다.

그림 6. 게임오버인지 클리어인지 판단해주는 함수들

여기서 exit(0) 함수는 프로그램을 상황과 관계없이 종료시키는 함수입니다. after_act() 함수 내부의 check_game_over() 함수는 0인 칸이 존재하거나 인접한 칸 중에 같은 숫자가 존재할 경우 (더할 수 있는 숫자 쌍이 존재할 경우) 게임을 계속 진행시키고, 위 조건을 모두 만족하지 못할 때 게임을 완료하지 못했다고 판단하는 함수입니다.

check_game_clear() 함수는 판 속에 존재하는 칸 중에서 하나라도 2048 이상의 숫자가 나올 경우 게임을 클리어했다고 판단하는 함수입니다. 각각의 함수 모두 조건이 만족되었을 경우 상황에 맞는 출력문과 함께 1을 반환하여 exit(0)를 실행하도록 합니다.

그림 7. 게임판을 그려주는 함수와 랜덤으로 숫자를 생성해주는 함수

이제 draw() 함수와 rand_num() 함수까지 구현하면 2048 프로그램이 완성됩니다. windows.h 헤더파일에 존재하는 system() 함수를 사용하면 cmd 명령어를 사용할 수 있는데 이때 “cls”를 입력하면 콘솔창을 깔끔하게 클리어 할 수 있습니다.

그 후 게임판에 존재하는 숫자가 ‘0’일 경우 ‘.’으로 나타내고, 숫자가 존재할 경우에는 이를 직접 표시하도록 한 후 하단에는 점수를 나타내도록 draw() 함수를 구현해 보았습니다.

rand_num() 함수는 16개의 칸 중에서 숫자가 존재하지 않는 칸 중 임의로 하나의 칸에 ‘2’ 혹은 ‘4’를 생성하는 함수입니다. 이는 숫자가 존재하지 않는 칸의 주소를 generator(포인터) 배열에 넣은 후 배열 내에 저장되어 있는 여러 포인터(주소) 중 하나를 임의로 택하여 그 포인터(주소) 안에 있는 데이터를 2 혹은 4로 설정해 줍니다.

여기서 임의의 기준은 rand() 함수를 통해 생성된 난수를 특정 값으로 나눈 나머지로 그 확률을 조정하였습니다. [그림 7]을 참고하시면 이해하는 데 도움이 될 것 같습니다.

그림 8. 메인 함수

4. 메인 함수와 실행

이제 마지막으로 메인 함수에서 맨 처음 구현했던 game_start() 함수를 실행시켜주면 코딩이 끝이 납니다. 이 메인 함수가 포함되어 있는 c 파일을 실행시켜주면 [그림 9]와 같이 직접 구현한 2048 게임을 플레이 할 수 있답니다!

이처럼 여러분도 코딩을 할 때 한꺼번에 어려운 알고리즘을 고민하는 것이 아니라 큰 그림을 그린 후에 그 속을 채우는 형식으로 코딩을 해나가면 큰 도움이 될 것입니다!!

그림 9. 실행결과

출처 : 모든 코드 직접 작성

서울시 관악구 관악로1 서울대학교 공과대학 38동 201호 (우 : 08826) /
팩스 : 02-876-7602 / E-mail : eng.magazine@snu.ac.kr
Copyright ⓒ 서울대학교 공과대학. 무단전재 및 재배포 금지
ISSN : 2799-5062
지난호 보기