Commodore 64용 BASIC으로 만든 던전 크롤러의 맵 생성 방식을 바꿔 모든 방을 오갈 수 있게 했다. 방 사이에 문을 배치하고 게임 승리 조건을 추가했으며, 멀리 있는 고블린의 행동 처리를 건너뛰어 턴 진행도 개선했다.

완성된 part 10은 RGC 온라인 IDE에서 플레이하고 코드를 볼 수 있다. 변경 과정은 세 단계로 나뉜다.

이전 내용은 9편에서 확인할 수 있다.

방을 연결하는 맵

이전 맵은 7×7 격자에 복도, 모퉁이, T자 교차로, 방 같은 ‘메타 타일’을 무작위로 배치했다. 타일의 출구를 이웃 타일과 맞추지 않아 벽이 겹치거나 복도가 막혔고, 문도 빈 바닥 칸 어디에나 놓여 방 한가운데 생길 수 있었다. 새 방식의 목표는 복잡한 미로가 아니라 실제로 이동할 수 있는 다양한 던전을 만드는 것이다.

새 맵도 메타 타일을 쓰지만 5×5 격자로 배치한다. 방 내부는 벽을 제외하고 3×3칸이며, 각 방은 윗벽과 왼쪽 벽만 그린다. 방 사이의 벽은 한 칸 두께로 이어진다. 각 변의 가운데 칸에 출입구를 두므로 문은 양쪽이 열린 공간을 향한다. 오른쪽 끝과 아래쪽 끝의 방은 이웃 방과 벽을 공유하지 않아 MAPBLANK가 해당 경계 두 면을 그린다.

DRAWMT는 커서를 한 번 옮긴 뒤 한 번의 PRINT로 방 전체를 출력한다. NL$에는 다음 줄로 이동하고 왼쪽 벽까지 네 칸 이동한 뒤 #를 출력하는 제어 문자열을 담는다.

NL$="{DOWN}{LEFT}{LEFT}{LEFT}{LEFT}#"

DRAWMT:
ROW=TY : COL=TX : GOSUB CURSORSET
PRINT "{GREY}####";NL$;MT$(MT,0);NL$;MT$(MT,1);NL$;MT$(MT,2);
RETURN

출구 위치는 방 모양에 맡기지 않고 방을 그린 다음 EXITS에서 따로 결정한다. 각 방은 윗벽이나 왼쪽 벽, 또는 두 벽 모두에 출구를 만든다. 맨 위 행은 위쪽에 방이 없으므로 왼쪽으로 열리고, 맨 왼쪽 열은 위쪽으로 열린다. 왼쪽 위 방은 바깥 경계에 있으므로 두 방향 모두 열지 않는다.

ET=0 : EL=0
R=RND(1)
IF R<.35 THEN ET=1
IF R>=.35 AND R<.7 THEN EL=1
IF R>=.7 THEN ET=1 : EL=1
IF RY=0 THEN ET=0 : EL=1
IF RX=0 THEN EL=0 : ET=1
IF RX=0 AND RY=0 THEN ET=0

ET는 윗벽 열기, EL은 왼쪽 벽 열기를 뜻한다. 모든 방에서 위쪽이나 왼쪽으로 한 칸씩 이동하면 결국 왼쪽 위 방에 도달한다. 따라서 모든 방이 그 방에 연결되며, 어떤 두 방 사이에도 경로가 생긴다. 미로 생성 분야에서 ‘이진 트리 알고리즘’이라고 부르는 방식으로, 각 칸에서 두 방향 중 하나를 고른다. 이전 위치를 기억하거나 나중에 연결 여부를 검사할 필요가 없어 인터프리터 방식의 8비트 BASIC에 적합하다고 설명한다.

순수한 이진 트리 미로라면 두 칸 사이의 경로는 하나뿐이지만, 이 맵은 무작위 선택으로 출구를 추가할 수 있어 순환 경로와 우회로도 생긴다. 위쪽 행과 왼쪽 열은 각각 하나의 연결된 길이 된다. 이 방식으로 열쇠와 하트, 성배를 순간이동 없이 모두 찾을 수 있는 던전이 처음 만들어졌다.

출구는 세 번 중 두 번은 한 칸짜리 문이 되고, 나머지 한 번은 벽 세 칸 전체를 없애 두 방을 합친다. 방 사이 모퉁이의 기둥은 그대로 남는다. 한 칸짜리 출입구의 좌표는 DX와 DY 목록에 기록하고, NC가 개수를 센다. 이 목록은 최대 40개까지 커질 수 있다. POPULATE는 이제 무작위 바닥 칸 대신 목록에서 문을 골라 배치한다. 같은 문을 두 번 고르지 않도록 사용한 항목을 목록의 마지막 항목으로 덮어쓴 뒤 개수를 줄인다. 벽 전체가 열린 곳은 목록에 없으므로 문이 생기지 않는다.

EXITUP:
IF RND(1)<.25 THEN ROW=TY:COL=TX+1:GOSUB CURSORSET:PRINT "...";:RETURN
ROW=TY : COL=TX+2 : GOSUB CURSORSET : PRINT ".";
NC=NC+1 : DX(NC)=COL : DY(NC)=ROW
RETURN

EXITLEFT도 같은 방식으로 옆 방향에 출구를 만든다. 문이 전혀 없는 맵에서는 IF ND=0이 문 배치와 열쇠 배치 반복문을 건너뛴다. 이 BASIC은 FOR문의 끝 조건을 반복문 본문이 실행된 뒤 NEXT에서 확인하므로 FOR N=1 TO 0만으로는 본문을 건너뛸 수 없기 때문이다.

문은 이제 실제로 이동을 막으므로 이전보다 게임에서 의미가 생겼다. 고블린은 문을 열지 못해 체력이 낮을 때 문 뒤에 숨을 수 있다. 열쇠가 해당 문 너머에 놓일 수도 있지만 다른 길이 있는 경우가 많다. 다른 길이 없다면 문을 부술 수 있으며, 이때 체력 4를 잃는다.

승리 조건과 고블린 처리

성배를 주우면 이제 게임에서 승리한다. 이전에는 게임을 끝낼 방법이 없어 다시 시작할 수밖에 없었다. 성배 획득은 메인 루프에서 GOSUB KEYS로 호출하는 COLISSION 안에서 처리한다. 여기서 곧바로 승리 화면으로 이동하면 대기 중인 GOSUB가 대응하는 RETURN을 받지 못한다. 이렇게 쌓인 호출이 많아지면 BASIC의 스택을 소모해 ?OUT OF MEMORY ERROR가 발생할 수 있다.

IF CH=40 THEN GL=GL+10:WN=1:MSG$="CHALICE 10 GOLD":GOSUB TAKEITEM

성배를 얻으면 WN=1로 표시하고 정상적으로 돌아간다. 메인 루프 최상위에서 이 값을 확인한 뒤 GOTO로 승리 화면에 간다. 이 검사는 적의 행동 처리인 GOSUB ENEMYLOGIC보다 먼저 실행되므로 승리 직후 고블린이 공격하지 않는다.

IF WN=1 THEN GOTO YOUWIN

YOUWIN은 “YOU WIN!”을 표시한 뒤 게임 종료 화면과 합류한다. 그 화면에는 골드 합계와 “PLAY AGAIN, Y OR N”이 나온다. 새 게임을 시작할 때 INITPLAYER가 WN=0으로 설정한다.

YOUWIN:
GOSUB SHOWHUD
ROW=12 : COL=24 : GOSUB CURSORSET : PRINT "{YELLOW}YOU WIN!";
GOTO ENDPANEL

고블린은 이전까지 맵 어디에 있든 플레이어가 한 번 움직일 때마다 모두 행동했다. 한 마리의 처리에 약 165밀리초가 걸려 여섯 마리가 있으면 한 턴마다 1초가량 기다려야 했다. 시간의 대부분은 화면 출력이 아니라 위치 배열 읽기, 조건 검사 여섯 번, 화면 메모리 확인 등 BASIC의 행동 처리에서 들었다.

맵을 3×3의 아홉 구역으로 나누고, 플레이어와 고블린의 구역을 비교해 먼 곳의 잠든 고블린은 처리하지 않도록 바꿨다. 구역 번호는 칸의 열과 행으로 계산한다.

PA=INT(PX/7)+3*INT((PY-2)/7)

INT(PX/7)은 열에서 7칸 단위의 구역 위치를 구한다. 행에도 같은 계산을 적용하며, 맵이 화면 2행에서 시작하므로 -2를 한다. 가로 위치에 세 배를 곱한 세로 위치를 더해 왼쪽 위 0부터 오른쪽 아래 8까지의 번호를 만든다. PA는 플레이어의 구역으로, 턴마다 고블린 반복문을 시작하기 전에 한 번 계산한다. GA(G)는 고블린의 구역이며 배치할 때와 이동할 때만 계산한다.

잠든 고블린이 플레이어 구역 밖에 있으면 행동을 건너뛴다. 구역 번호 확인은 배열에서 값을 읽고 비교하는 작업 한 번으로 줄어든다. 조건문 두 개를 이어 쓰면 첫 번째 조건이 거짓일 때 BASIC이 두 번째 조건을 확인하지 않는다는 점도 활용한다.

IF GW(G)=0 THEN IF GA(G)<>PA THEN GOTO NEXTENEMY

구역 방식에는 사각지대가 있다. 구역 경계 반대편에 서서 플레이어 바로 옆에 있는 고블린은 움직이거나 반격하지 못한다. 플레이어가 공격하면 고블린이 깨어난다. COMBAT에서 GW(G)=1로 설정하며, 깨어난 고블린은 해당 레벨이 끝날 때까지 구역을 무시하고 맵 전체에서 플레이어를 추적한다. 따라서 경계 너머의 잠든 고블린을 한 번 공격할 수 있고, 이후에는 고블린이 깨어난다. 플레이어 구역 안의 고블린은 이전처럼 행동하며 플레이어가 구역을 떠나면 다시 잠든다.

커서 이동을 빠르게 하는 방법도 먼저 측정했지만 실제 게임에서 턴 속도는 3%만 빨라졌다. 반면 구역 최적화는 같은 게임에서 20턴, 고블린 여섯 마리를 기준으로 고블린 처리 시간이 990ms에서 556ms로 줄었고, 전체 턴 시간은 1.42초에서 0.99초로 줄었다. 추격 중인 적도 계속 최적화 효과를 모두 소모하지 않도록, 고블린은 체력이 10이며 깨어난 뒤 보통 두세 번 맞으면 죽는다. 죽은 고블린은 처리 대상에서 제외한다. 다만 한 마리를 공격하고 떠난 뒤 다른 고블린도 공격하면 여러 마리가 동시에 깨어날 수 있다. 추가 속도 개선은 별도의 후속 작업에서 다룰 가능성이 있다.

다음에는 성배를 얻고 출구로 내려가는 층 진행, 골드 외에 캐릭터를 강화하는 보상, 지금은 보석과 마찬가지로 골드 10만 주는 물약의 역할, 무작위로 움직이는 새 적인 쥐를 추가할 계획이다.