2048 게임 만들기(상)

자바스크립트 2048 게임 스크린샷

<이 화면은 스크린샷 그림입니다>

2048의 목적은 보드위의 블록들을 상, 하, 좌, 우로 움직이며 2048의 숫자(혹은 그 이상)이 적힌 블록을 생성시키는 것입니다. 게임의 규칙은 다음과 같습니다.

  • 게임 시작시 2 또는 4가 적힌 블록 2개가 보드 위의 랜덤한 위치에 생성됩니다.
  • 상, 하, 좌, 우로 블록들을 움직일 수 있으며, 블록들이 동시에 이동하게 됩니다.
  • 블록들이 이동을 한 후 2 또는 4가 적힌 블록 1개가 보드위의 랜덤한 위치에 다시 생성됩니다.
  • 블록이 이동중에 숫자가 다른 블록을 만나게 되면 더이상 이동하지 못합니다.
  • 블록이 이동중에 숫자가 같은 블록을 만나게 되면 그 두개의 블록이 하나로 합쳐지며 숫자가 더해집니다. 기본 블록이 2와 4이므로, 2, 4, 8, 16, 32 ... 이렇게 계속해서 두배씩 증가하게 됩니다.
  • 블록들이 더이상 이동할 수 없게 되는 경우 게임이 종료됩니다.
  • 2048 블록이 등장하면 승리한 것이지만 게임은 계속하여 진행할 수 있습니다.

자바스크립트와 Text Game Maker JS 라이브러리를 사용하여 이 게임을 만들어 봅시다. 게임데이터를 화면에 출력하는 부분과 키입력을 받는 부분에만 해당 라이브러리가 사용되고 나머지 모든 코드들은 네이티브 자바스크립트로 작성합니다. Text Game Maker JS 코드가 사용된 부분들은 이 강의 본문에 간단하게만 설명되니 Text Game Maker JS를 더 자세히 알고 싶은 분들은 Text Game Maker JS 튜토리얼 강의를 읽어 주세요.

상편과 하편으로 나누어서 살펴 볼텐데, 이번 상편에서는 게임에 필요한 변수들과 화면에 텍스트들을 출력하는 함수들을 만들어 보겠습니다.

Text Game Maker JS Starter Program 설치

우선 Text Game Maker JS를 받아서 설치합니다. https://github.com/a-mean-blogger/text-game-maker-js/releases 에 이동하면 text-game-maker-js-starter-program.ziptext-game-maker-1.0.0.min.js.zip 두 개의 압축파일이 있는데, text-game-maker-js-starter-program.zip을 다운받아 적당한 폴더에 압축을 풀어 줍시다. 이 파일은 TM 라이브러리 파일과 간단한 데모 코드가 작성된 압축파일이고, text-game-maker-1.0.0.min.js.zip은 TM 라이브러리 파일만 들어있습니다.

text-game-maker-js-starter-program.zip의 압축을 풀고 index.html을 더블 클릭하면 웹 브라우저에 데모 프로그램이 실행됩니다. main.js의 코드를 변경하여 프로그램을 만들게 됩니다. 데모 프로그램이 문제없이 실행되는 것을 확인하면 main.js의 코드를 전부 지워서 새로운 코드를 작성할 준비를 합시다.

소스 코드

2048 게임 만들기(상)의 소스 코드를 여기를 클릭하여 확인해 주세요. 전체 코드를 복사한 후 위에서 다운 받은 TM 스타터 프로그램의 main.js 파일에 붙여넣기 합니다. 그 다음 index.html을 더블 클릭하면 아래와 같이 웹 브라우저가 나타나게 됩니다.

자바스크립트 2048 게임 브라우저 스크린샷

main.js의 코드를 살펴봅시다.

var screenSetting = {
  // canvasId: 'tm-canvas',
  // frameSpeed: 40,
  // column: 60,
  // row: 20,
  // backgroundColor: '#151617',
  // webFontJsPath: 'https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js',
  // fontColor: '#F5F7FA',
  // fontFamily: 'monospace',
  // fontSource: null,
  // fontSize: 30,
  // zoom: 0.5,
  column: 70,
  row: 23,
  fontFamily: 'Consolas',
};

var debugSetting = {
  // devMode: false,
  // outputDomId: 'tm-debug-output',
  devMode: true,
};

screenSettingdebugSetting은 Text Game Maker JS를 사용하기 위해 설정값을 조절하는 부분입니다. 주석 처리된 부분은 해당 항목이 없을 경우 사용될 기본값을 나타냅니다. 각 항목의 의미는 TM.defaultSettings 문서에서 확인할 수 있습니다.

var TMS = new TM.ScreenManager(screenSetting),
    TMI = new TM.InputManager(screenSetting.canvasId,debugSetting.devMode),
    TMD = new TM.DebugManager(debugSetting);

screenSettingdebugSetting을 사용하여 게임 제작에 필요한 TM 메니저들을 생성합니다.

  • TMS: 화면 생성및 출력을 담당합니다.
  • TMI: 키입력을 담당합니다. 이번 포스트에는 사용되지 않고 다음 포스트에서 사용됩니다.
  • TMD: 디버그용 데이터 출력을 담당합니다.

각 인스턴스의 함수 및 자세한 설명은 TM.ScreenManager, TM.InputManager, TM.DebugManager 문서에서 확인할 수 있습니다.

var NUM_OF_ROW = 4;
var NUM_OF_COLUMN = 4;
var NUM_OF_INITIAL_BLOCK = 2;
var BOARD_HEIGHT = NUM_OF_ROW*3+2;
var BOARD_WIDTH = NUM_OF_COLUMN*6+2;
var SPEED = 50;
var FRAME_X = 6;
var FRAME_Y = 1;
var COLORSET = {
  BLOCKS: ['#5D9CEC', '#4FC1E9', '#48CFAD', '#A0D468',
         '#FFCE54', '#FC6E51', '#ED5565', '#AC92EC',
         '#EC87C0', '#4A89DC', '#3BAFDA', '#37BC9B',
         '#8CC152', '#F6BB42', '#E9573F', '#DA4453'],
  EMPTY_BLOCK: '#999',
};
var KEYSET = {
  LEFT: 37,
  UP: 38,
  RIGHT: 39,
  DOWN: 40,
  ESC: 27,
};

2048 게임에서 사용될 상수와 변수들입니다. 상수는 프로그램이 실행되는 동안 변하지 않는 값이며 대문자와 _를 사용하여 나타내었습니다. 변수는 프로그램이 실행되는 동안 변할 수 있는 값이며 Camel Case(띄어쓰기 대신 대문자사용)로 나타내었습니다.

  • NUM_OF_ROW: 보드의 세로칸의 수입니다. 이 값을 조절하여 게임에서 사용할 세로칸의 수를 바꿀 수 있습니다.
  • NUM_OF_COLUMN: 보드의 가로칸의 수입니다. 이 값을 조절하여 게임에서 사용할 가로칸의 수를 바꿀 수 있습니다.

**NUM_OF_ROWNUM_OF_COLUMN의 값을 변경하게 되면 게임의 가로x세로 크기가 달라지게 됩니다. 이 경우 screenSettingcolumnrow 값을 조절하여 화면의 크기를 게임의 크기에 맞게 바꿀 수 있습니다.

  • NUM_OF_INITIAL_BLOCK: 게임의 시작시 랜덤하게 나오는 블록의 갯수입니다.
  • BOARD_HEIGHT, BOARD_WIDTH: 블록 하나는 가로 6칸 세로 3칸입니다. NUM_OF_ROW, NUM_OF_COLUMN의 수와 외곽선의 들어갈 칸수를 더해 보드의 전체 가로 세로 길이를 구합니다.
  • SPEED: 키입력을 받거나 블록 이동 애니메이션에 사용되는 속도입니다.
  • FRAME_X, FRAME_Y: 화면에서 게임의 위치를 조절할 수 있는 값입니다.
  • COLORSET: 게임에 사용될 색깔 값들입니다.
  • KEYSET: 게임에 사용될 키 값들입니다. 키값을 찾기위해서는 TMI를 debug용으로 생성하고(TMI = new TM.InputManager(첫번째_인자, 두번째_인자)에서 두번째_인자true가 들어가게 설정) 게임 화면을 마우스로 클릭한 후에 키값을 알고자 하는 키보드 키를 누릅니다. 이렇게 하면 브라우저 콘솔에 해당 키의 값이 표시됩니다.
  • boardData: 2차원 배열(towerData[블록_세로_위치][블록_가로_위치])로 블록의 값들을 저장하는 변수입니다. resetBlockData함수에 의해 초기화 됩니다.
  • score: 점수를 저장하는 변수입니다.
  • biggestNumber: 지금까지 출현한 가장 큰 숫자를 저장하는 변수입니다.
  • moveDir: 현재 이동방향을 저장하는 변수입니다. "LEFT", "RIGHT", "UP", "DOWN"을 가집니다.
  • isGameOver: 현재 게임이 끝난 상태인지 아닌지를 true/false로 저장합니다.
  • isAutoMoving: 현재 자동으로 블록 이동 중인 상태인지 아닌지를 true/false로 저장합니다.

다음으로 함수들을 살펴봅시다.

function resetBlockData(){
  var blockData = { value: 0, isMerged: false };
  return blockData;
}

블록을 빈칸으로 초기화하는 함수입니다. 블록은 숫자값을 가지는 value 항목과, 다른 블록과 합쳐진 적이 있는지를 가지는 isMerged 항목으로 구성되어 있습니다. isMerged는 다른 블록 합쳐진 적이 있는 블록을 구별하여 한턴에 한번만 값이 합쳐지도록 하는 역할을 합니다.

function resetBoardData(){
  var boardData = [];
  for(var i=0; i<NUM_OF_ROW; i++){
    boardData[i] = [];
    for(var j=0; j<NUM_OF_COLUMN; j++){
      boardData[i][j] = resetBlockData();
    }
  }
  return boardData;
}

boardData를 초기화하는 함수입니다. 바로 위에서 살펴본 resetBlockData함수로 2차원배열인 boardData의 모든 곳에 블록을 생성합니다.

가로길이가 NUM_OF_COLUMN, 세로길이가NUM_OF_ROW 그리고 모든 값이 { value: 0, isMerged: false }인 2차원 배열로 초기화 됩니다.

function putNewBlock(boardData){
  var emptySpots = [];
  for(var i=0; i<NUM_OF_ROW; i++){
    for(var j=0; j<NUM_OF_COLUMN; j++){
      if(boardData[i][j].value === 0){
        emptySpots.push(boardData[i][j]);
      }
    }
  }
  if(emptySpots.length){
    var randomEmptySpot = emptySpots[Math.floor(Math.random()*emptySpots.length)];
    randomEmptySpot.value = Math.ceil(Math.random()*2)*2;
  }
}

boardData에서 값이 없는 블록 위치에 값을 하나 추가하는 함수입니다. 먼저 빈자리들을 찾고, 빈자리들 중 랜덤한 위치에 2혹은 4를 추가합니다.

function getBlockColor(block_value){
  var blockColor;
  var colorOrder;
  for(colorOrder=0; Math.pow(2,colorOrder+1)<=block_value; colorOrder++)
  blockColor = COLORSET.BLOCKS[colorOrder%COLORSET.BLOCKS.length];
  return blockColor;
}

블록의 숫자를 통해 COLORSET.BLOCKS에서 색깔을 가져오는 함수입니다.

function alignTextCenter(length, text){
  var alignedText = '';
  var leftPadding = '';
  var leftPaddingLength = Math.ceil((length-text.length)/2);
  var RightPadding = '';
  var RightPaddingLength = length-leftPaddingLength-text.length;
  for(var i=0; i<leftPaddingLength; i++) leftPadding+=' ';
  for(var j=0; j<RightPaddingLength; j++) RightPadding+=' ';
  alignedText = leftPadding+text+RightPadding;
  return alignedText;
}

주어진 텍스트(text)를 주어진 길이(length)안에 가운데 정렬시키는 함수입니다. 블록 안에 숫자를 가운데 정렬로 출력하기 위해 사용됩니다.

function drawFrame(){
  var x = FRAME_X+2;
  var y = FRAME_Y;

  TMS.cursor.move(x,y);
  TMS.insertText('──┐┌─┐│ │┌─┐\n');
  TMS.insertText('┌─┘│ │└─┤├─┤\n');
  TMS.insertText('└──└─┘  │└─┘\n');

  for(var i=0; i<BOARD_HEIGHT; i++){
    for(var j=0; j<BOARD_WIDTH; j++){
      TMS.cursor.move(FRAME_X+j,FRAME_Y+3+i);
      if(i==0&&j==0) TMS.insertText('┌');
      else if(i==0&&j==BOARD_WIDTH-1) TMS.insertText('┐');
      else if(i==BOARD_HEIGHT-1&&j==0) TMS.insertText('└');
      else if(i==BOARD_HEIGHT-1&&j==BOARD_WIDTH-1) TMS.insertText('┘');
      else if(i==0 || i==BOARD_HEIGHT-1) TMS.insertText('─');
      else if(j==0 || j==BOARD_WIDTH-1) TMS.insertText('|');
    }
  }

  x = FRAME_X+1+BOARD_WIDTH;
  y = FRAME_Y+5;
  TMS.cursor.move(x,y);
  TMS.insertText('Score:');

  x = FRAME_X+1;
  y = FRAME_Y+3+BOARD_HEIGHT;
  TMS.cursor.move(x,y);
  TMS.insertText('◇ ←, →, ↑, ↓ : Move\n');
  TMS.insertText('◇ ESC : Restart\n\n');
  TMS.insertText('www.A-MEAN-Blog.com\n');
}

게임을 진행하는 동안 바뀌지 않는 텍스트를 출력하는 함수입니다. TMS.cursor.move 함수는 커서 위치를 옮기며, TMS.insertText 함수는 현재 커서 위치에 텍스트를 출력합니다. TMS의 모든 함수들과 자세한 설명은 TM.ScreenManager 문서에서 확인할 수 있습니다.

function drawBoard(){
  for(var i=0; i<boardData.length; i++){
    for(var j=0; j<boardData[i].length; j++){
      TMS.cursor.move(FRAME_X+2+(j*6),FRAME_Y+4+(i*3));
      if(boardData[i][j].value){
        var color = getBlockColor(boardData[i][j].value);
        TMS.insertText('┌───┐\n', color);
        TMS.insertText(alignTextCenter(5,boardData[i][j].value.toString())+'\n', color);
        TMS.insertText('└───┘\n', color);
      }
      else {
        TMS.insertText('     \n',COLORSET.EMPTY_BLOCK);
        TMS.insertText('  +  \n',COLORSET.EMPTY_BLOCK);
        TMS.insertText('     \n',COLORSET.EMPTY_BLOCK);
      }
    }
  }
}

boardData를 화면에 그리는 함수입니다. x, y 위치만 잘 생각해 보면 어려운 것은 없습니다.

function drawScore(){
  var x = FRAME_X+1+BOARD_WIDTH+7;
  var y = FRAME_Y+5;
  TMS.cursor.move(x,y);
  TMS.insertText(score);
}

score의 값을 출력하는 함수입니다. "Score:" 다음에 오도록 x, y값을 맞췄습니다.

function drawGameOver(){
  var x = FRAME_X+1+BOARD_WIDTH;
  var y = FRAME_Y+5;
  TMS.cursor.move(x,y);
  TMS.insertText(' Game Over..  Your score : '+score+' \n',null,'gray');
  TMS.insertText(' Press <ESC> key to start new game ',null,'gray');
}

게임이 종료 텍스트를 화면에 출력하는 함수입니다.

function drawWin(){
  var x = FRAME_X+1+BOARD_WIDTH;
  var y = FRAME_Y+8;
  TMS.cursor.move(x,y);
  TMS.insertText('Congratulations! You Made 2048!\n','#fff');
  TMS.insertText('Keep playing for high score :)','#fff');
}

2048을 완성 텍스트를 화면에 출력하는 함수입니다.

function reset(){
  moveDir = null;
  isAutoMoving = false;
  isGameOver = false;
  biggestNumber = 0;
  score = 0;
  boardData = resetBoardData();
  for(var i=0; i<NUM_OF_INITIAL_BLOCK; i++) putNewBlock(boardData);

  TMS.cursor.hide();
  TMS.clearScreen();
  drawFrame();
  drawBoard();
  drawScore();
}

게임의 화면과 변수들을 초기화 시키는 함수입니다. TMS.cursor.hide는 TM의 깜박이는 커서를 숨기고, TMS.clearScreen 함수는 화면의 모든 값을 지웁니다.

reset();

지금까지는 함수와 변수를 지정하였고, reset함수를 실행하는 부분입니다.

TMD.print('debug-data', {
  moveDir: moveDir,
  isAutoMoving: isAutoMoving,
  isGameOver: isGameOver,
  biggestNumber: biggestNumber,
});

마지막으로 TMD(TM.DebugManager의 인스턴스)으로 디버그용 데이터를 출력합니다. 아래 스크린샷에서 화면 오른쪽의 텍스트 부분(--debug-data--)입니다. 이 TMP.print 함수는 함수가 호출되는 순간의 해당 값들을 출력하게 됩니다. 즉 지금 상태로는 실시간으로 값을 업데이트하지 않습니다. 실시간으로 이 값들을 업데이트하는 코드는 하편에 나옵니다.

자바스크립트 2048 게임 브라우저 스크린샷

예제 코드의 실행

이 화면은 실제 실행중인 프로그램 화면으로 현재 페이지의 브라우저 콘솔을 연 다음 아래의 명령어들을 입력하여 프로그램을 조작할 수 있습니다.

//새로운 블럭을 생성한 후 보드를 업데이트하기
putNewBlock(boardData); drawBoard();
//게임 종료 텍스트 출력해 보기
drawGameOver();
//2048 완성 텍스트 출력해 보기
drawWin();


이어지는 2048 게임 만들기 (하)편에서는 실제 데이터를 조작하는 함수를 만들고 키입력을 받아 게임을 완성해 보겠습니다.

댓글

댓글쓰기

이 글에 댓글을 다시려면 SNS 계정으로 로그인하세요. 자세히 알아보기

UP