자바스크립트
자바스크립트(JavaScript) 테트리스 (Tetris)
태태딩
2019. 6. 27. 01:41
반응형
자바스크립트 코딩 연습을 하다가 문득 자바에서 테트리스 만들었던 게 생각나 자바스크립트로 만들어 보고자 했다.
먼저 개발에 앞서 알고리즘을 구상해야 했다.
문제는 테트리스 블록정보를 저장하는 것과 각각 다르게 생긴 블록은 랜덤으로 생성하여 색을 다르게 주어야 하는 것이다.
<script>
var canvas = document.getElementById('tatris'); // 캔버스 노드
var ctx = canvas.getContext('2d');
var shapes = [
[0x4640, 0x0E40, 0x4C40, 0x4E00], // 'T'
[0x8C40, 0x6C00, 0x8C40, 0x6C00], // 'S'
[0x4C80, 0xC600, 0x4C80, 0xC600], // 'Z'
[0x4444, 0x0F00, 0x4444, 0x0F00], // 'I'
[0x44C0, 0x8E00, 0xC880, 0xE200], // 'J'
[0x88C0, 0xE800, 0xC440, 0x2E00], // 'L'
[0xCC00, 0xCC00, 0xCC00, 0xCC00] // 'O'
];
var colorList = ['#ff000d','#2600ff','#5eff00','#fff200','#ff00bf','#ff0095','#00ffff'];
var curShapeType = Math.floor(Math.random() * 7);//현재 테트리스 모양을 담을 변수
var curRotation = 0; // 현재 테트리스 회전 모양을 담을 변수 ( 0 , 1 , 2 , 3 )
var curShape = shapes[curShapeType][curRotation]; // 위에 두변수로 가져온 현재 모양 정보
var sPos = {x:3,y:0}; // 현재 도형의 x 와 y 좌표
var gamePanel = [];
var gamePanelColor = [];
블록 정보는 16진수를 사용하여 배열에 미리 모양대로 저장해놓는다.
16진수를 2진수로 표현하면 밑과 같이 4X4 모양으로 T모양의 4가지 방향이 나오는 것을 알 수 있다.
0 | 1 | 0 | 0 |
0 | 1 | 1 | 0 |
0 | 1 | 0 | 0 |
0 | 0 | 0 | 0 |
저장되는 정보는 시프트 연산을 사용하여 0x8000(1000 0000 0000 0000)을 한칸씩 비트 연산하여 채워진 정보를 저장한다.
function gamePanelSave(x,y){
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
if (curShape & (0x8000 >> (i * 4 + j))) {
this.gamePanel[this.sPos.y+i][this.sPos.x+j]=1;
this.gamePanelColor[this.sPos.y+i][this.sPos.x+j] = colorList[curShapeType];
}
}
}
}
이제 이것들을 활용하여 테트리스를 만들면 된다.
대략적으로 몇 가지 기능이 필요한데
보이는 캔버스 밖으로 나가지 못하게 하고, 채워진 정보가 있는지 체크하여 겹쳐지지 않게 하고, 한 줄이 다 채워지면 지워지게 하고, 한 줄을 지울 때마다 점수를 올려주고, 블록이 계속 밑으로 떨어지게 하고, 블록이 쌓여 맨 위에 다다르면 게임을 종료해야 한다.
간단하게 이 정도 기능을 가진 테트리스를 이제 구현하면 된다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
body{
margin: 0;
text-align: center;
}
canvas{
margin-top: 100px;
}
</style>
</head>
<body>
<div>
<canvas style="display: inline-block; border:3px solid black" id='tatris' width="200px" height="400px"></canvas>
<div style="display:inline-block; text-align: left; margin-left: 20px;">
<button id='start'>게임시작</button>
<p>****************************</p>
<h4 id='pointLabel'>score : 0</h4>
<p>조작키</p>
<p>****************************</p>
<p>keyUp : 회전</p>
<p>keyLeft : 왼쪽으로 한칸</p>
<p>keyRight : 오른쪽으로 한칸</p>
<p>spacebar : 블럭 맨 밑으로</p>
<p>****************************</p>
</div>
</div>
<script>
var canvas = document.getElementById('tatris'); // 캔버스 노드
var ctx = canvas.getContext('2d');
var shapes = [
[0x4640, 0x0E40, 0x4C40, 0x4E00], // 'T'
[0x8C40, 0x6C00, 0x8C40, 0x6C00], // 'S'
[0x4C80, 0xC600, 0x4C80, 0xC600], // 'Z'
[0x4444, 0x0F00, 0x4444, 0x0F00], // 'I'
[0x44C0, 0x8E00, 0xC880, 0xE200], // 'J'
[0x88C0, 0xE800, 0xC440, 0x2E00], // 'L'
[0xCC00, 0xCC00, 0xCC00, 0xCC00] // 'O'
];
var colorList = ['#ff000d','#2600ff','#5eff00','#fff200','#ff00bf','#ff0095','#00ffff'];
var curShapeType = Math.floor(Math.random() * 7);//현재 테트리스 모양을 담을 변수
var curRotation = 0; // 현재 테트리스 회전 모양을 담을 변수 ( 0 , 1 , 2 , 3 )
var curShape = shapes[curShapeType][curRotation]; // 위에 두변수로 가져온 현재 모양 정보
var sPos = {x:3,y:0}; // 현재 도형의 x 와 y 좌표
var gamePanel = [];
var gamePanelColor = [];
for(var z = 0; z<20;z++){
gamePanel[z] = [];
gamePanelColor[z] = [];
for(var x = 0;x<10;x++){
gamePanel[z][x]=0;
gamePanelColor[z][x] = 'white';
}
}
function gamePanelSave(x,y){
for (var i = 0; i < 4; i++) {
for (var j = 0; j < 4; j++) {
if (curShape & (0x8000 >> (i * 4 + j))) {
this.gamePanel[this.sPos.y+i][this.sPos.x+j]=1;
this.gamePanelColor[this.sPos.y+i][this.sPos.x+j] = colorList[curShapeType];
}
}
}
}
function gamePanelDraw(){
//배경은 ( 채워지지 않은부분은 흰색 )
ctx.fillStyle = 'white';
// 채워지는 부분은 검은색으로
ctx.fillRect(0,0,200,400);
ctx.fillStyle = 'black';
// 테두리색상 블루로
// ctx.rect(0,0,200,400);
// ctx.strokeStyle="blue";
// ctx.stroke();
// curShape에 맞춰서 검은색으로 칠한다.
for (var y = 0; y < gamePanel.length; y++) {
for (var x = 0; x < gamePanel[y].length; x++) {
if(gamePanel[y][x]){
ctx.fillStyle = gamePanelColor[y][x];
ctx.fillRect(x*20,y*20,19,19);
}
}
}
}
function draw(){
// 확정된 부분은 먼저 블럭은 채우고 시작해야하고
gamePanelDraw();
// curShape에 맞춰서 검은색으로 칠한다.
for (var y = 0; y < 4; y++) {
for (var x = 0; x < 4; x++) {
if (curShape & (0x8000 >> (y * 4 + x))) {
ctx.fillStyle = this.colorList[this.curShapeType];
ctx.fillRect((sPos.x+x) * 20, (sPos.y+y) * 20, 19, 19);
}
}
}
}
function gamefinish(){
clearInterval(intervalHandler);
location.reload();
alert(setpoint + "점 입니다.");
}
// 새로운 도형을 뽑는 함수
function newShape(){
if(gamePanel[0][3]==1 || gamePanel[0][4]==1|| gamePanel[0][5]==1 ||gamePanel[0][6]==1){
gamefinish();
}
var shapeNum = Math.floor(Math.random() * 7);
this.sPos.x = 3;
this.sPos.y = 0;
this.curShapeType = shapeNum;
this.curShape = shapes[curShapeType][curRotation];
draw();
}
function changeAng(){
if(this.curRotation==3){
this.curRotation=0;
}else{
this.curRotation+=1;
}
this.curShape = shapes[curShapeType][curRotation];
draw();
}
$(document).keydown(function (event) {
switch(event.keyCode){
case 37:
if(!rowLeftCheck()){
sPos.x -=1;
}else{
}
draw();
break;
case 39:
if(!rowRightCheck()){
sPos.x +=1;
}else{
}
draw();
break;
case 38:
if(!checkAng(shapes[curShapeType][curRotation+1])){
changeAng();
}else{
}
case 32:
break;
default:
break;
}
});
function rowLeftCheck(){
for(var i = 0; i< 4;i++){
for(var j = 0;j<4;j++){
if(curShape&(0x8000>>(i*4+j))){
if(this.sPos.x+j-1==-1 || gamePanel[this.sPos.y+i][this.sPos.x+j-1]){
return true;
}
}
}
}
return false;
}
function rowRightCheck(){
for(var i = 0; i< 4;i++){
for(var j = 0;j<4;j++){
if(curShape&(0x8000>>(i*4+j))){
if(this.sPos.x+j+1==10 || gamePanel[this.sPos.y+i][this.sPos.x+j+1]){
return true;
}
}
}
}
return false;
}
function checkBlock(x,y){
for (var i = 0; i < 4; i++){
for (var j = 0; j < 4; j++){
if (curShape & (0x8000 >> (i * 4 + j))){
if (y + i >= 19 || gamePanel[y + i + 1][x + j]){
return true;
}
}
}
}
return false;
}
function checkAng(shapes2){
for (var i = 0; i < 4; i++){
for (var j = 0; j < 4; j++){
if (shapes2 & (0x8000 >> (i * 4 + j))){
if(gamePanel[this.sPos.y+i][this.sPos.x+j]){
return true;
}
}
}
}
return false;
}
function checkLastRow(){
var xList = [];
for (var y = gamePanel.length-1; y >=0; y--) {
var ck = 0;
for (var x = gamePanel[y].length-1; x >=0; x--) {
ck+=gamePanel[y][x];
}
if(ck == 10){
xList.push(y);
}
}
dropBlock(xList);
return 1;
}
function dropBlock(xList){
var pointCnt=0;
for(var i = 0; i<xList.length;i++){
for(var j = 0;j<10;j++){
gamePanel[xList[i]][j] = gamePanel[xList[i]-1][j];
gamePanelColor[xList[i]][j] = gamePanelColor[xList[i]-1][j];
gamePanel[xList[i]-1][j]=0;
gamePanelColor[xList[i]-1][j] = 'white';
for(var k = xList[i]-1; k>0;k--){
gamePanel[k][j] =gamePanel[k-1][j];
gamePanelColor[k][j] = gamePanelColor[k-1][j];
}
}
pointCnt+=1;
}
setPoint(pointCnt*10);
}
var setpoint = 0;
function setPoint(point){
setpoint += point;
$('#pointLabel').html('score : '+ setpoint);
}
var intervalHandler;
$().ready(function () {
$('#start').click(function () {
intervalHandler = setInterval(function () {
if (!checkBlock(this.sPos.x, this.sPos.y)) {
this.sPos.y += 1;
draw();
} else {
gamePanelSave(); //저장되고
if(checkLastRow()){ // 저장된거에서
newShape();
}
}
}, 200);
});
});
</script>
</body>
</html>
게임 실행 결과
스크린샷을 위해 빠르게 종료한 것이다. 게임을 못하는 것이 아니다
반응형