2.4 테트리스로 따라가는 컴퓨터의 동작
지금까지 컴퓨터가 프로그램에 따라 데이터를 가져오고, 연산하고, 그 결과를 다시 기억하는 과정을 살펴보았다. 2.1절에서 이런 단순한 동작이 어떻게 생생한 게임 화면으로 이어지는지 쉽게 상상되지 않을 수 있다고 했었는데, 이제 그 질문을 테트리스 게임으로 이해해보려고 한다. 테트리스는 비교적 단순한 게임이지만, 그 안에서 입력, 연산, 기억, 출력이 함께 일하는 모습을 모두 볼 수 있다.

위 그림은 고전 컴퓨터 게임으로 알려져 있는 테트리스 게임이다. 요즘 나오는 화려한 3D 그래픽 게임에 비하면 단순한 화면과 동작을 보여주지만, 테트리스 게임의 기본 동작 방식과 화려한 3D 그래픽 게임의 동작 방식의 그 근본 원리는 다르지 않다. 어떤 원리로 테트리스 게임은 컴퓨터에서 동작할 수 있을까? 질문을 바꾸어, 독자 여러분이 테트리스 게임 프로그램을 컴퓨터에서 동작시키려고 한다면 어떤 식으로 프로그램을 작성해야 할까?
테트리스를 비롯한 게임은 빠른 장면 전환을 기본 원리로 한다. 조금씩 다른 장면을 빠르게 이어 보여주면, 우리 눈은 한 장 한 장의 그림이 아니라 움직이는 화면으로 느낀다. 이때 한 장의 장면을 프레임(frame)이라고 부른다. 어렸을 때 공책 모서리에 공이 조금씩 튀어 오르는 그림을 여러 장 그려 놓고, 책장을 빠르게 넘겨 본 적이 있을 것이다. 한 장씩 보면 멈춰 있는 그림이지만, 빠르게 넘기면 공이 통통 튀는 것처럼 보인다. 모든 동영상과 영화도 같은 원리다.
이것을 컴퓨터의 동작과 연결해 보자. 먼저 컴퓨터는 첫 번째 장면을 데이터로 만들어 메모리에 담고, 이 데이터를 바탕으로 모니터에 장면을 보여준다. 이어서 블록이 한 칸 내려온 두 번째 장면에 해당되는 데이터를 만들어 메모리에 담고, 그 데이터를 바탕으로 장면을 만들어 모니터에 보여준다. 그다음에는 세 번째 장면을, 또 그다음에는 네 번째 장면을 같은 방법으로 만들어 보여준다. 이때 사용자가 왼쪽 방향키를 눌렀다면, 다음 장면의 데이터에는 블록이 한 칸 왼쪽으로 옮겨진 모습을 담으면 된다. 그 장면이 모니터에 보이면, 우리는 블록이 왼쪽으로 움직였다고 느낀다. 모니터에 보여주는 과정은 매번 같고, 중요한 것은 연산을 통해 어떤 장면을 만들어 낼지 정하는 일이다. 〈그림 2-9〉는 이렇게 조금씩 다른 장면이 차례로 이어지며 움직임이 만들어지는 모습을 보여준다.

그렇다면 컴퓨터는 다음 장면에 무엇을 그려야 하는지 어떻게 알까? 이제 테트리스의 한 장면을 어떤 데이터로 표현하고, 그 데이터를 어떻게 바꾸는지 좀 더 구체적으로 살펴보자.
〈그림 2-10〉의 왼쪽은 테트리스 게임의 한 장면을 나타내는 데이터이고, 오른쪽은 이 데이터가 실제 화면에 나타난 모습이다. 데이터의 형태는 여러 가지일 수 있지만, 여기서는 5×7 크기의 표에 칸마다 숫자를 하나씩 담는 방식을 썼다. 이렇게 값을 칸마다 차례로 담아 둔 것을 배열이라고 부른다. 각 칸의 숫자는 색을 뜻한다고 약속하자. 0은 빈칸, 1은 빨강, 2는 주황, 3은 노랑, 4는 초록이다. 그러면 숫자가 적힌 칸들의 모양으로 블록을 나타낼 수 있다. 왼쪽 배열에서 1이 적힌 칸들은 빨간색 네모 블록(a)을, 2가 적힌 칸들은 주황색 ㅗ 모양 블록(b)을 나타낸다. 같은 방법으로 다른 모양의 블록도 어렵지 않게 표현할 수 있다. 이 배열 데이터는 메모리에 담겨 있다. 배열 데이터가 있다면, 우리는 이제 화면에 그려질 특정 색과 모양의 블록, 그리고 위치까지 결정할 수 있다.
여기서는 게임의 흐름을 쉽게 따라갈 수 있도록, 여러 작은 동작을 묶어 하나의 명령처럼 표현하겠다. 예를 들어 ‘화면을 그려라’는 일을 하나로 표현하지만, 실제로는 더 작은 명령어들이 모여 그 일을 수행한다.
이제 이 데이터를 바탕으로 화면을 그려 달라는 명령어가 있다고 하자(여기서는 paint라고 부르자). 컴퓨터가 paint를 수행하면, 메모리에 있는 배열 데이터를 읽어 숫자가 적힌 자리마다 그 숫자가 뜻하는 색의 블록을 화면에 그린다. 그렇게 그려진 화면이 바로 〈그림 2-10〉의 오른쪽이다. 이렇게 메모리에 담긴 데이터와, 그 데이터를 화면에 그리라는 명령어가 함께 테트리스 게임의 한 장면을 만들어 냈다.

테트리스 게임의 한 장면을 만들었으니, 이제 게임이 동작하게 만들어 볼 차례다. 앞에서 본 것처럼 게임의 동작은 장면을 만들고, 보여주고, 다시 장면을 만들고, 보여주는 일의 반복이다. 그리고 각 장면은 메모리에 있는 데이터를 바탕으로 만들어진다. 그러니 데이터를 조금씩 바꾸면서 그때마다 화면을 그려 달라고 명령어로 요청하면, 모니터에서 블록이 움직이는 테트리스 게임을 볼 수 있다. 〈그림 2-11〉은 이렇게 데이터를 바꿔 가며 만든 여러 장면을 보여준다.

이제 이런 기본 지식을 바탕으로, 우리는 컴퓨터에게 지시서, 즉 프로그램(명령어의 집합과 그 명령어가 다룰 데이터)을 작성하여 테트리스 게임이 동작하는 화면을 만들 수 있게 된다. 다음 〈그림 2-12〉는 테트리스 게임 명령어 집합 일부를 보여 준다.

프로그램을 시작할 때에는 〈그림 2-10〉에서 보았던 빨간색 네모 블록을 의미하는 데이터가 메모리에 저장되어 있다. 그리고 첫 번째 명령어인 get_key로 사용자의 키 입력이 있는지 확인한다. 이때 입력을 마냥 기다리지는 않는다. 기다리기만 하면 그동안 블록이 내려오지 못하기 때문이다. 짧은 순간 확인해서 입력이 있으면 그 키 입력 값을 메모리에 잠시 저장해 두고, 없으면 입력이 없었다고 기록한 뒤 다음 명령어로 넘어간다. 이제, get_key 명령어가 수행될 때, 왼쪽 방향키 입력이 있었다고 가정해보자.
프로그램의 순서에 따라 다음 단계로, 컴퓨터는 두 번째 명령어로 horizontal_move 명령어를 실행한다. 이 명령어는 저장된 키 값을 꺼내 왼쪽인지 오른쪽인지 확인하고, 그에 맞게 움직이는 블록을 한 칸 옮기도록 데이터를 바꾼다. 블록이 있던 칸은 빈칸을 뜻하는 0으로, 새로 옮겨 갈 칸은 블록의 색을 뜻하는 숫자로 바꾸는 식이다. 화면에서는 블록이 움직이는 것이지만, 컴퓨터 안에서는 새 위치를 계산하고 메모리의 숫자를 바꾸는 연산이다. 이 게임에서 우리는 입력받은 키 값이 왼쪽이기에, 〈그림 2-13〉에서 (b) 데이터로 변경될 예정이며, 만약 키 값이 오른쪽이라면 〈그림 2-13〉에서 (c) 형태로 데이터가 변경될 것이다. 만약, 이전 명령어에서 아무 키 값도 받지 못했다면, (a) 상태의 데이터가 유지될 것이다.

다음으로, 컴퓨터는 세 번째 명령어로 vertical_move 명령어를 실행한다. 이 명령어는 내려오고 있는 블록을 한 행 아래로 옮기도록 데이터를 바꾼다. 방법은 왼쪽이나 오른쪽으로 옮길 때와 같다. 단지 방향이 위에서 아래일 뿐이다. 그 결과 〈그림 2-14〉의 (a)가 (b)로 바뀐다.

컴퓨터가 수행하는 네 번째 명령어는 메모리에 있는 배열 데이터를 바탕으로 화면을 나타내는 명령어이다. 우리 프로그램에서는 paint였다. 사실, 앞의 명령어들은 메모리에 있는 배열 데이터만 바꿨을 뿐, 화면에는 아무것도 그리지 않았다. paint 명령어가 수행되면, 지금 가진 데이터에 따라 화면을 그리고, 그 결과 지금까지의 최종 결과물인 〈그림 2-14〉의 (b)가 화면에 나타난다.
다섯 번째 명령어는 sleep이다. 컴퓨터는 앞의 일들을 눈 깜짝할 사이에 해치우기 때문에, 그대로 두면 블록이 순식간에 바닥까지 떨어져 버리는 것처럼 보인다. 그래서 블록을 내리는 동작 사이에 적당한 시간 간격을 두어야 한다. 가장 간단한 방법은 ‘잠시 아무 일도 하지 말고 기다려라’는 뜻의 sleep 명령어로, 다음 명령어로 넘어가는 것을 잠깐씩 멈추는 것이다. 실제 게임은 이보다 정교한 방법으로 시간을 맞추지만, 블록을 얼마나 자주 내릴지까지 프로그램이 정해 준다는 점은 같다.
여기까지가 한 바퀴다. 이후 프로그램은 다시 첫 번째 get_key 명령어로 돌아가 같은 순서를 처음부터 되풀이한다(명령어의 나열 안에 ‘맨 앞으로 돌아가라’는 명령어를 하나 더 두면 된다). 이렇게 한 바퀴가 계속 반복되면, 블록이 내려오면서 키 입력에 따라 왼쪽이나 오른쪽으로 움직이는 모습을 만들어 낼 수 있다.
같은 방법으로 빨간색 네모 블록 대신 주황색 ㅗ 모양 블록이 내려오게 하는 것도 어렵지 않다. 물론 이것만으로 진짜 테트리스가 되지는 않는다. 블록이 바닥이나 다른 블록에 닿으면 멈추고 쌓여야 하고, 한 줄이 가득 차면 지워져야 한다. 이런 동작을 넣으려면 어떤 데이터를 확인하고, 어떤 명령어가 더 필요할지 한번 상상해보길 바란다.
지금까지 테트리스 게임을 예로, 컴퓨터에게 프로그램으로 일을 시켜 블록이 움직이는 화면을 만들어 보았다. 돌아보면 그 한 바퀴에는 2.1절에서 본 네 가지 기본 동작이 모두 들어 있다. get_key는 입력, horizontal_move와 vertical_move는 연산, 메모리에 놓인 배열은 기억, paint는 출력이다. 게임을 설명했지만, 정말 가져가야 할 것은 그 뒤에 숨은 컴퓨터의 근본 동작 방식이다. 테트리스도 결국 기억 공간에 있는 데이터를, 지시서에 따라, 연산하여 다시 기억하는 것을 빠르게 되풀이하는 일이다. 이 예제를 통해 컴퓨터의 구조와 동작 방식을 조금 더 온전히 이해할 수 있었기를 바란다.
지금까지 살펴본 컴퓨터의 기본적인 동작 방식을 꼭 기억하고, 이제 이러한 동작이 칩 안에서 어떻게 이루어지는지 알아보자.