본문 바로가기
자바스크립트와 캔버스

자바스크립트와 캔버스 6, 테트리스를 만들어봐-2

1. HTML 표준 CANVAS 기술 소개 / https://itadventure.tistory.com/130

2. 자바스크립트와 CANVAS 두번째시간. 캔바스에 눈을 내리자 / https://itadventure.tistory.com/131

3. 자바스크립트와 캔버스 3번째 시간, 공튀기기 놀이 / https://itadventure.tistory.com/132

4. 자바스크립트와 캔버스 4번째 시간, 마우스의 파동을 느껴봐! | https://itadventure.tistory.com/133

 

5. 자바스크립트와 캔버스, 테트리스를 만들어봐-1 | https://itadventure.tistory.com/136

◐ 6. 자바스크립트와 캔버스, 테트리스를 만들어봐-2 ◑


지난 시간에는 20행 10열의 블럭박스를 구성하고
블럭 1개를 채운 결과를 캔버스에 표시하는 부분을 다뤄보았었습니다.

오늘은 블럭박스 위에서 떨어지는 테트리스 조각을 다뤄볼텐데요.
여기서 잠깐 퀴~즈입니다.
테트리스 게임에서 퍼즐조각은 몇가지나 있을까요?
변형판이 아닌 초기 테트리스 게임 기준입니다.

:
:

총 7가지입니다. 아래와 같지요.

이 중에서 우선 5가지 정도만 다루어 볼텐데요.

사실 특별한 이유가 있는게 아니라 급하게 미리 준비하다 보니 처음에 5개만 생각이 나서 미리 만들어놓은게 5개라서 그렇습니다 :)

2개는 나중에 생각이 나더라구요 ㅎ..

우선 논리적인 틀을 먼저 구성해보지요.

5개의 블럭조각중 1개가 블럭박스 안에 표시될텐데요.

표시될 때만 블럭박스 안에서 표시가 되고,

변수로는 별도로 정의되어야 관리하기가 편리합니다.

5가지 타입의 블럭박스를 다음과 같이 정의할 겁니다.

4개 상대 좌표 ( +y, +x ) 지점을 위치값으로 갖는 배열을 블럭 하나로 5개의 배열을 정의

이를테면 첫번째 블럭 의 경우,

[ □□□□ ]

(0,0), (0,1), (0,2), (0,3) 이 블럭의 구성좌표가 됩니다.

두번째 블럭의 경우

[ □□□ ]

[ □ ]

(0,0), (0,1), (0,2), (1,1) 이 블럭의 구성좌표가 되는 것이지요.

이와 같이 5개의 블럭조각의 좌표를 초기화하는 코드를 구성합니다.

먼저 전역변수로 블럭조각을 담을 변수를 정의하고,

// 5가지 타입 블록
var tetrix_block;

블럭조각을 초기화 할 함수를 정의한 다음에

// 5가지 블럭 초기화
function tetrix_block_init()
{
	:
}

함수 안에 블럭 조각을 우선 배열로 정의합니다.

tetrix_block=new Array();

그리고 첫번째 조각을 배열로 만들어서 집어넣는 것이지요.

좌표 구성 형식으로 말이죠.

	// 첫번째 블럭
	// □□□□
	tmp=new Array();
	tmp.push(0,0); tmp.push(0,1); tmp.push(0,2); tmp.push(0,3);
	tetrix_block.push(tmp);

하지만 좌표라고 해서 거창한건 아니고 사실 tmp.push(0, 1); 과

tmp.push(0); tmp.push(1); 과는 똑같은 의미입니다.

그냥 배열 요소가 8개가 추가된다고 보시면 됩니다.

이어서 두번째부터 다섯번째 조각까지 초기화하는 함수 전체입니다.

// 5가지 블럭 초기화
function tetrix_block_init()
{
	tetrix_block=new Array();

	// 첫번째 블럭
	// □□□□
	tmp=new Array();
	tmp.push(0,0); tmp.push(0,1); tmp.push(0,2); tmp.push(0,3);
	tetrix_block.push(tmp);

	// 두번째 블럭
	// □□□
	//  □
	tmp=new Array();
	tmp.push(0,0); tmp.push(0,1); tmp.push(0,2); tmp.push(1,1);
	tetrix_block.push(tmp);

	// 세번째 블럭
	// □□
	//  □□
	tmp=new Array();
	tmp.push(0,0); tmp.push(0,1); tmp.push(1,1); tmp.push(1,2);
	tetrix_block.push(tmp);

	// 네번째 블럭
	//  □□
	// □□
	tmp=new Array();
	tmp.push(0,1); tmp.push(0,2); tmp.push(1,0); tmp.push(1,1);
	tetrix_block.push(tmp);

	// 다섯번째 블럭
	// □□
	// □□
	tmp=new Array();
	tmp.push(0,0); tmp.push(0,1); tmp.push(1,0); tmp.push(1,1);
	tetrix_block.push(tmp);
}

이 함수도 지난 블럭박스처럼 Init() 함수에서 한번 호출해주면 됩니다.

// 초기화
function Init()
{
	if(init==false)
	{
             :
		tetrix_blockbox_init();	// 블럭상자 초기화
             :
	}
}

블럭조각 모양도 준비가 되었으니 이제 블럭조각을 블럭박스 안에 표시할 차례입니다.

블럭조각이 위에서 아래로 떨어지려면 블럭조각은 가로, 세로 좌표가 있어야 합니다.

그리고 현재 표시할 블럭조각이 몇번째 블럭인지의 정보도 있어야 겠지요?

블럭조각은 1번으로 하고, (x, y) 좌표는 3, 2 로 정하기로 합시다.

아래와 같이 전역변수를 선언하고 값을 초기화합니다.

// 현재 떨어지는 블록번호와 좌표
var tetrix_block_number=1;
var tetrix_block_x=3;
var tetrix_block_y=0;

그리고 블럭 박스 내에 블럭조각을 표시할텐데요.

Draw 함수 내에서 블럭 박스를 그려주는 for 반복문 내에 이 부분을 추가해주겠습니다.

원래 Draw 함수에서 블럭을 표시해주는 부분은 아래와 같이 블럭박스의 현재 위치에 따라 그려줄 색상을 정의해주는 부분이 전부였습니다만,

	// 블럭 표시
	for(i=0;i<20;++i)
		for(j=0;j<10;++j)
		{
			if(tetrix_blockbox[i][j]==0)
				Context.fillStyle="#ccc";
			else
				Context.fillStyle="green";
             :

그 아래에 이런 코드가 추가됩니다. 현재 위치를 블럭 조각이 지나고 있으면 표시할 색상을 파란색으로 바꿔주는 건데요.

			// 떨어지는 블럭검사 
			var size=tetrix_block[tetrix_block_number].length;
			for(k=0;k<size;k+=2)
			{
				if(tetrix_block_y+tetrix_block[tetrix_block_number][k]==i
				   && tetrix_block_x+tetrix_block[tetrix_block_number][k+1]==j)
					Context.fillStyle="blue"; 
			}
            :

현재 블록에 해당하는 4쌍(8개)의 좌표와 현재 블럭 박스의 좌표 중 겹치는 부분이 있는지를 검사해서 겹치는 경우 표시할 블럭 색상을 파란색으로 바꿔주게 됩니다.

여기까지 반영된 전체 코드입니다.

<!DOCTYPE html>
<html lang="ko">
<head>
	<meta charset="UTF-8">
	<title>캔바스. 테트리스</title>
</head>
<body>
https://blog.naver.com/ephraimdrlee 
/ 크레이의 세컨드라이프 탐구생활 네이버 블로그<br/>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script>
// 기본 초기화
var init=false;
var myCanvas;
var Context;

var tetrix_blockbox_boxsize=25;
var tetrix_blockbox_top=50;
var tetrix_blockbox_left=280;

// tetrix_blockbox[row][col]; 20행 10열
var tetrix_blockbox;

// 테트리스 블럭박스 초기화
function tetrix_blockbox_init()
{
	// 20행 10열의 박스 생성
	tetrix_blockbox=new Array();
	for(i=0;i<20;++i)
	{
		tetrix_blockbox.push(new Array(10));
		// 모두 0으로 채운다
		for(j=0;j<10;++j)tetrix_blockbox[i][j]=0;
	}
}

// 5가지 타입 블록
var tetrix_block;

// 5가지 블럭 초기화
function tetrix_block_init()
{
	tetrix_block=new Array();

	// 첫번째 블럭
	// □□□□
	tmp=new Array();
	tmp.push(0,0); tmp.push(0,1); tmp.push(0,2); tmp.push(0,3);
	tetrix_block.push(tmp);

	// 두번째 블럭
	// □□□
	//  □
	tmp=new Array();
	tmp.push(0,0); tmp.push(0,1); tmp.push(0,2); tmp.push(1,1);
	tetrix_block.push(tmp);

	// 세번째 블럭
	// □□
	//  □□
	tmp=new Array();
	tmp.push(0,0); tmp.push(0,1); tmp.push(1,1); tmp.push(1,2);
	tetrix_block.push(tmp);

	// 네번째 블럭
	//  □□
	// □□
	tmp=new Array();
	tmp.push(0,1); tmp.push(0,2); tmp.push(1,0); tmp.push(1,1);
	tetrix_block.push(tmp);

	// 다섯번째 블럭
	// □□
	// □□
	tmp=new Array();
	tmp.push(0,0); tmp.push(0,1); tmp.push(1,0); tmp.push(1,1);
	tetrix_block.push(tmp);
}

// 현재 떨어지는 블록번호와 좌표
var tetrix_block_number=1;
var tetrix_block_x=3;
var tetrix_block_y=0;

// 초기화
function Init()
{
	if(init==false)
	{
		myCanvas=document.getElementById("MyCanvas");
		Context=myCanvas.getContext("2d");		
		init=true;
		tetrix_block_init();	// 5가지 블럭 모양 초기화
		tetrix_blockbox_init();	// 블럭상자 초기화
	}
}

function Run()
{
	onDraw();
}

// draw 이벤트
function onDraw()
{
	if(init==false)return;
	// 전체 테두리
	Context.strokeStyle="#000";
	Context.lineWidth=1;
	Context.strokeRect(0, 0, myCanvas.width-1, myCanvas.height-1);
	// 블럭 표시
	for(i=0;i<20;++i)
		for(j=0;j<10;++j)
		{
			if(tetrix_blockbox[i][j]==0)
				Context.fillStyle="#ccc";
			else
				Context.fillStyle="green";
			
			// 떨어지는 블럭검사 
			var size=tetrix_block[tetrix_block_number].length;
			for(k=0;k<size;k+=2)
			{
				if(tetrix_block_y+tetrix_block[tetrix_block_number][k]==i
				   && tetrix_block_x+tetrix_block[tetrix_block_number][k+1]==j)
					Context.fillStyle="blue"; 
			}


			x=tetrix_blockbox_left + j*tetrix_blockbox_boxsize;
			y=tetrix_blockbox_top + i*tetrix_blockbox_boxsize;
			Context.fillRect(x, y, tetrix_blockbox_boxsize-2, tetrix_blockbox_boxsize-2);
		}
}

$(document).ready(function(){
	Init();
	setInterval(Run, 500);

});

</script>

<canvas id="MyCanvas" width=800 height=600>
Canvas is not supported.
</canvas>
허공에 존재하는 블럭 1개 표시
<span id=debug></span>
</body>
</html>

이 코드를 실행하면, 블럭박스 안에 블럭조각이 이렇게 떠 있을 겁니다.

소스에서 블럭 조각 번호를 한번 바꿔 볼까요?

tetrix_block_number 라고 된 곳을 찾아서 뒤의 숫자를 바꿔 보세요.

var tetrix_block_number=3;

아래와 같은 모양으로 바뀌는 것을 확인하실 수 있습니다.

블럭번호를 0~4까지 총 5가지니까 실습해보시면 다양한 모양을 확인해보실 수 있습니다.

여기서 끝내면 아쉬우니까 1가지만 더 해보죠.

바로 블럭 조각이 화면 아래로 떨어지는 부분인데요.

Run() 함수 안에

한줄만 추가해주면 됩니다.

function Run()
{
	tetrix_block_y++;
	onDraw();
}

그리고 페이지를 새로 고침하면,

블럭 조각이 위에서 아래로 떨어집니다

이윽고 바닥에 닿았다가

영영 사라져 버리는 것을 보실 수 있습니다.

불행히도 블럭조각은 저 아래로 한 없이 한 없이 하강 행진을 계속하는 것이지요 :)

블럭조각이 바닥에 쌓이도록 하면 어떻게 해야 할까요?

그건 다음 시간에 살펴보도록 합시다

여기까지 읽어주신 분들께 감사드립니다 :)

오늘 진행된 부분에 대한 예제 페이지는 아래 URL에서 확인하실 수 있습니다.

http://dreamplan7.cafe24.com/canvas/cray06.php

 

캔바스. 테트리스

 

dreamplan7.cafe24.com

시도하시는 모든 분들 성공하시길 바랍니다.
읽어주셔서 감사합니다 :)

 

다음 강좌 보러가기 / https://itadventure.tistory.com/142

 

자바스크립트와 캔버스 7, 테트리스를 만들어봐-3

1. HTML 표준 CANVAS 기술 소개 / https://itadventure.tistory.com/130 2. 자바스크립트와 CANVAS 두번째시간. 캔바스에 눈을 내리자 / https://itadventure.tistory.com/131 3. 자바스크립트와 캔버스 3번째 시..

itadventure.tistory.com

 

반응형
  • 질문드려요 2020.11.13 16:20

    // 떨어지는 블럭검사
    var size=tetrix_block[tetrix_block_number].length;
    for(k=0;k<size;k+=2)
    {
    if(tetrix_block_y+tetrix_block[tetrix_block_number][k]==i
    && tetrix_block_x+tetrix_block[tetrix_block_number][k+1]==j)
    Context.fillStyle="blue";
    }

    요 코드에서 혹시 왜 for문 k 값을 2씩 증가시키는지 여쭤볼 수 있을까요?
    다른건 다 이해가 되는데 저 부분이 이해가 되지 않네요 ㅠㅠ
    제가 코딩 초보라 부탁드립니다

    • Favicon of https://itadventure.tistory.com BlogIcon Dr. CrayFall 2020.11.13 20:07 신고

      첫번째 배열요소는 x좌표 두번째 배열 소요는 y 좌표라서 그렇습니다 :) 실제로는 숫자만 나열된 1차원 배열이거든요. 질문 감사합니다~~

  • 질문드려요 2020.11.15 14:22

    답글을 어떻게 달아야하는지 모르겠지만 이해가 됐어요
    설명해주셔서 감사합니다
    게시글에 코드 하나하나 설명되어있어서 저 같은 초보가 이해하기 쉽네요
    :)

    • Favicon of https://itadventure.tistory.com BlogIcon Dr. CrayFall 2020.11.15 19:16 신고

      잘 이해되셨다니 다행이군요 :)
      보통 생소한 소스코드를 한번에 쭉 읽고 이해가 된다면 천재라고 합니다.
      여러번 읽다 보면 눈에 어느정도 익어서 흐름이 파악되고, 실제로 몇번 보면서 응용하다 보면 익혀지는 것이지요.
      댓글 감사합니다~

  • Sarah 2021.05.16 23:25

    안녕하세요 ^^ 자바스크립트 코드 설명 제가 본 블로그 중 단연 최고네요 !! 덕분에 항상 도움 많이 받고 있습니다!! 혹시 tmp에 블록조각좌표는 (y,x)로 하셨는데 이유가 있는건가요 ? ㅠㅠ보통 좌표는 (x,y) 순이라고 알고 있어 좀 햇갈리네요ㅠ

    • Favicon of https://itadventure.tistory.com BlogIcon Dr. CrayFall 2021.05.17 00:16 신고

      칭찬 감사합니다 :)
      y, x 는 그냥 크레이의 습관같은 겁니다.

      크레이는 C언어부터 컴퓨터 언어를 시작했는데요. C 언어의 2차원 배열에서는 첫번째 배열요소가 아닌 2번째 배열요소들이 메모리에서 한칸씩 순서대로 나열됩니다.

      이를테면, 2줄 3칸 배열의 경우 총 6칸이 메모리가 배분되는데요.

      [2행][3열]로 정의하여,
      행을 배열의 첫 번째 첨자로,
      열을 배열의 두 번째 첨자로 사용하면,
      메모리에 배치되는 순서는 앞에서부터 아래와 같지요.

      [0행][0열]
      [0행][1열]
      [0행][2열]
      [1행][0열]
      [1행][1열]
      [1행][2열]

      그러니까 왼쪽에서 오른쪽으로 블럭들을 주루룩 위치하고 10칸 다음에는 다음줄의 블럭을 또 차례대로 배치하는 겁니다. 이 순서에 익숙해 있다 보니 그런데요.

      직접 비트맵 폰트를 개발하던 예전 시대에는 이러한 순서가 필수일 수 밖에 없었지요.

      크레이는 대괄호가 들어가는 배열의 경우는 y, x 순을 보통 사용하지만
      소괄호가 들어가는 함수의 경우는 x, y 순을 보통 사용합니다.

  • Sarah 2021.05.18 10:07

    우와 자세한 답변 너무 감사해요 크레이님 :) 크레이님처럼 코딩할 수 있는 그 날까지 열공해야겠어요! 자바스크립트 초보라 혹시 질문 하나만 더 드려도 될까용 ㅠㅠ init() 함수에서 init = false 와 true의 역할이 뭔지 알수 있을까요 ? 블록상자와 블럭조각을 초기화 하는 이유도 궁금합니당!!

    • Favicon of https://itadventure.tistory.com BlogIcon Dr. CrayFall 2021.05.19 18:14 신고

      우선 Init() 함수와 init 변수는 자바스크립트 문법으로는 다른 것입니다.
      앞의 i가 한쪽은 대문자이고 한쪽은 소문자이지요.

      Init() 함수는 처음에 한번만 호출되는데요.
      이 떄 호출되면서 갖가지 변수들을 초기화합니다.
      블럭의 배열이라든가 기본 테트리스 블록들의 패턴을 입력하는 등 여러가지 일들을 하는데요.

      이 때 "시간이 만만치 않게 소요"됩니다.
      사람 입장에서는 잠깐이지만 컴퓨터 입장에서 말이지요

      그렇게 때문에 미리 한번만 해놓아도 되는 일들은 사전에 미리 세팅해놓고 시작하게 되는데요. 이 부분은 Init() 함수에서 수행합니다.

      문제는 onDraw() 함수에 있습니다.
      이 함수는 현재의 세팅된 변수들에 따라 테트리스 화면을 갱신하는 기능을 하는데 미처 변수들이 세팅되기도 전에 이 기능이 실행되면 "자바스크립트 오류"가 발생합니다.

      아래와 같은 명령어를 실행하려고 해도
      tetrix_blockbox 변수가 20줄이 모두 초기화되지 않았다면 배열 요소가 참조되지 않아 오류가 나는 것이지요.

      if(tetrix_blockbox[i][j]==0)
      Context.fillStyle="#ccc";
      else Context.fillStyle="green";

      그래서 onDraw() 함수에서는 '준비됐어?' 물어보면서 준비된 경우에만 화면을 그려주는 기능을 실행하는데 그 것이 바로 init 변수입니다.

      onDraw() 함수의 앞 부분은 아래와 같습니다.

      function onDraw()
      {
      if(init==false)return;

      init 변수값이 false 이면 아무 일도 하지 않고 끝내버리지요. 준비가 안됐기 때문입니다.

      그러면 준비가 되었는지는 누가 결정해주는 것일까요?

      바로 Init() 함수가 결정해 줍니다.

      결정적으로 Init() 함수는 $(document).ready() 라는 부분에서 문서 로딩이 끝난 후 단 한번 호출해 주는 데요.

      $(document).ready(function(){
      Init();
      :

      Init() 함수에서는 앞에서 준비과정에 필요한 모든 것을 세팅하고
      init 변수값을 true 값으로 변경해 줌으로써 "나 준비됐어"라고 표현해 주는 것이지요.

      // 초기화
      function Init()
      {
      if(init==false)
      {
      myCanvas=document.getElementById("MyCanvas");
      Context=myCanvas.getContext("2d");
      init=true;
      tetrix_block_init(); // 5가지 블럭 모양 초기화
      tetrix_blockbox_init(); // 블럭상자 초기화
      }
      }

      Init() 함수 내에서 아래 if 부분은 사실 그다지 필요는 없는 부분이지만 혹시나 모를 오류 방지를 위한 것인데요.

      if(init==false)
      {
      :
      init=true;
      :
      }

      페이지가 두번 로딩 완료 액션이 생기지 않을까 해서 넣어놓은 일종의 '보험'과 같습니다.
      ---------
      결론 요약해드리자면,

      대부분의 게임을 비롯,
      테트리스에서도 속도를 높이기 위해,
      한번만 세팅해도 되는 일은 사전에 한번만 진행하는데 이를 '초기화'라고 하며, 이 초기화 작업이 끝났는지 판단을 위해 init 값을 처음에는 false 로 정하였다가
      초기화가 끝나면 true 로 값을 바꿔주어 게임이 진행되도록 하는 것입니다.

      설명이 좀 길었는데 이해에 도움이 되실련가 모르겠군요. 관심 감사합니다~