Javascriptでテトリスを作る
Javascriptのサンプルとしてテトリスは定番ですが、そういえば今まで挑戦してなかったの作ってみました。
canvasを使って落下ブロック(テトリミノ)が7種、色が6種のザ定番ぽいものを作ります。
完成品
Chromeでしか動作確認してません。
a : 左に移動
s : 右に移動
d : 下に移動
f : 右に回転
ソースコード
<!DOCTYPE HTML> <html> <head> <meta charset="UTF-8"> </head> <body> <!-- canvas --> <canvas id="tetris" width="300" height="600"></canvas> <script type="text/javascript"> (function() { /* 全体のコンフィグ */ var CONFIG = { 'width' : 0, /* 横幅 */ 'hegith' : 0, /* 縦幅 */ 'xsqs' : 10, /* 横方眼数 */ 'ysqs' : 20, /* 縦方眼数 */ 'color' : "rgb(65,65,65)", /* 盤面の色 */ 'grid_color' : "black", /* 格子の色 */ 'grid_width' : 2, /* 格子の幅 */ }; /* 方眼 */ var SQUARES; var squre = function() { this.color = 0; }; /* 色リスト */ var COLOR = { "default" : CONFIG.color, "red" : "rgb(255,0,0)", "green" : "rgb(0,255,0)", "blue" : "rgb(0,0,255)", "yellow" : "rgb(255,255,0)", "cyan" : "rgb(0,255,255)", "magenta" : "rgb(255,0,255)", }; var COLOR_LIST = [ COLOR.red, COLOR.green, COLOR.blue, COLOR.yellow, COLOR.cyan, COLOR.magenta, ]; /* ブロックの定義 */ var BLOCK; var block = function () { this.color = COLOR_LIST[Math.floor(Math.random() * (COLOR_LIST.length))]; this.shape = BLOCK_LIST[Math.floor(Math.random() * (BLOCK_LIST.length))]; this.x = (CONFIG.xsqs / 2) - 1; this.y = 0; return this; }; var BLOCK_BOX = [ [0,0,0,0], [0,1,1,0], [0,1,1,0], [0,0,0,0] ]; var BLOCK_BAR = [ [1,1,1,1], [0,0,0,0], [0,0,0,0], [0,0,0,0] ]; var BLOCK_LL = [ [1,1,1,0], [1,0,0,0], [0,0,0,0], [0,0,0,0] ]; var BLOCK_LN = [ [1,0,0,0], [1,1,1,0], [0,0,0,0], [0,0,0,0] ]; var BLOCK_NL = [ [1,0,0,0], [1,1,0,0], [0,1,0,0], [0,0,0,0] ]; var BLOCK_NN = [ [0,1,0,0], [1,1,0,0], [1,0,0,0], [0,0,0,0] ]; var BLOCK_T = [ [1,0,0,0], [1,1,0,0], [1,0,0,0], [0,0,0,0] ]; var BLOCK_LIST = [ BLOCK_BOX, BLOCK_BAR, BLOCK_LL, BLOCK_LN, BLOCK_NL, BLOCK_NN, BLOCK_T, ]; /* 描画コンテキスト */ var CTX; /* タイマー */ var fallTimer; /* キー入力 */ var KEY_DOWN = { "a" : 65, /* left */ "s" : 83, /* right */ "d" : 68, /* down */ "f" : 70, /* turn right */ "left" : 37, "right" : 39, "down" : 40, "space" : 32, /* turn right */ }; /* 初期化処理 */ function initialize() { /* canvasの取得 */ CTX = document.getElementById("tetris").getContext('2d'); if (!CTX) { return false; } CONFIG.width = CTX.canvas.width; CONFIG.height = CTX.canvas.height; /* keydown event */ document.onkeydown = moveBlock; /* 盤面の生成 */ SQUARES = createSquares(CONFIG); /* 落下ブロックの生成 */ BLOCK = block(); /* テトリスの起動 */ drawTetris(CTX, CONFIG, SQUARES, BLOCK); fallTimer = setInterval(tetris, 400, CTX, CONFIG, SQUARES, BLOCK); } /* ブロックの移動 */ function moveBlock() { var key = event.keyCode; switch(key) { case KEY_DOWN.a : case KEY_DOWN.left : moveLeftBlock(); break; case KEY_DOWN.s : case KEY_DOWN.right : moveRightBlock(); break; case KEY_DOWN.f : case KEY_DOWN.space : turnRightBlock(); break; case KEY_DOWN.d : case KEY_DOWN.down : moveDownBlock(); break; default: break; } } /* 左に移動 */ function moveLeftBlock() { var isEnabeMove = true; for (var y = 0 ; y < BLOCK.shape[0].length; y++) { for (var x = 0; x < BLOCK.shape.length; x++) { if (BLOCK.shape[x][y] == 0) { continue; } /* これ以上左に行けない */ if (((BLOCK.x + x) <= 0) || ((SQUARES[BLOCK.x + x - 1][BLOCK.y + y].color != CONFIG.color))) { isEnabeMove = false; break; } } } if (isEnabeMove) { BLOCK.x--; } } /* 右に移動 */ function moveRightBlock() { isEnabeMove = true; for (var y = 0 ; y < BLOCK.shape[0].length; y++) { for (var x = 0; x < BLOCK.shape.length; x++) { if (BLOCK.shape[x][y] != 0) { /* これ以上右に行けない */ if (((BLOCK.x + x) >= (CONFIG.xsqs - 1)) || ((SQUARES[BLOCK.x + x + 1][BLOCK.y + y].color != CONFIG.color))) { isEnabeMove = false; break; } } } } if (isEnabeMove) { BLOCK.x++; } }; /* 下に加速 */ function moveDownBlock() { if (!isLanded(CONFIG, SQUARES, BLOCK)) { BLOCK.y++; } }; /* ターンできるか */ function isEnableToTurn(tmp) { for (var x = 0; x < BLOCK.shape.length; x++) { for (var y = 0; y < BLOCK.shape[0].length; y++) { if (tmp[x][y] != 0) { if (((BLOCK.x + x) < 0) || (CONFIG.xsqs <= (BLOCK.x + x)) || ((BLOCK.y + y) < 0) || (CONFIG.ysqs <= (BLOCK.y + y)) || SQUARES[BLOCK.x + x][BLOCK.y + y].color != CONFIG.color) { return false; } } } } return true; } /* ブロックの右回転 */ function turnRightBlock() { var tmp = Array(BLOCK.shape.length); for (var x = 0; x < BLOCK.shape.length; x++) { tmp[x] = Array(BLOCK.shape[0].length); } for (var x = 0; x < BLOCK.shape.length; x++) { for (var y = 0; y < BLOCK.shape[0].length; y++) { tmp[x][y] = BLOCK.shape[y][BLOCK.shape[0].length - 1 - x]; } } if (isEnableToTurn(tmp)) { BLOCK.shape = tmp; } } /* 方眼の作成 */ function createSquares(c) { var sqrs = new Array(c.ysqs); for (var y = 0; y < c.ysqs; y++) { sqrs[y] = new Array(c.xsqs); } for (var x = 0; x < c.xsqs; x++) { for (var y = 0; y < c.ysqs; y++) { sqrs[x][y] = new squre(); sqrs[x][y].color = c.color; } } return sqrs; } /* 盤面の描画 */ function drawTetris(ctx, c, sqrs, blk) { drawBack(ctx, c); drawSquare(ctx, c, sqrs); drawBlock(ctx, c, blk); drawGrid(ctx, c); } /* 背景の描画 */ function drawBack(ctx, c) { ctx.fillStyle = c.color; ctx.fillRect(0, 0, c.width, c.height); ctx.strokeStyle = c.grid_color; ctx.lineWidth = c.grid_width; ctx.beginPath(); ctx.stroke(); } /* 格子(グリッド)の描画 */ function drawGrid(ctx, c) { ctx.beginPath(); /* 縦のグリッド線 */ for(var x = c.width/c.xsqs; x < c.width; x+= c.width/c.xsqs) { ctx.moveTo(x, 0); ctx.lineTo(x, c.height); } /* 横のグリッド線 */ for(var y = c.height/c.ysqs; y < c.height; y+= c.height/c.ysqs) { ctx.moveTo(0, y); ctx.lineTo(c.width, y); } ctx.stroke(); } /* 方眼の描画 */ function drawSquare(ctx, c, blocks) { var x_sp; /* 描画開始座標(x) */ var y_sp; /* 描画開始座標(y) */ var w; /* 横幅 */ var h; /* 縦幅 */ ctx.beginPath(); for (var x = 0; x < c.xsqs; x++) { for (var y = 0; y < c.ysqs; y++) { if (blocks[x][y].color == COLOR.default) { continue; } w = (c.width/c.xsqs); h = (c.height/c.ysqs); x_sp = x * w; y_sp = y * h; ctx.fillStyle = blocks[x][y].color; ctx.fillRect(x_sp, y_sp, w, h); } } ctx.stroke(); }; /* 落下ブロックの描画 */ function drawBlock(ctx, c, blk) { var width = c.width/c.xsqs; var height = c.height/c.ysqs; ctx.fillStyle = blk.color; for (var x = 0; x < blk.shape.length; x++) { for (var y = 0; y < blk.shape[0].length; y++) { if (blk.shape[x][y] != 0) { if (((blk.x + x) >= 0) && ((blk.x + x) <= c.xsqs) && ((blk.y + y) >= 0) && ((blk.y + y) <= c.ysqs)) { ctx.fillRect((blk.x + x) * width, (blk.y + y) * height, width, height); } } } } }; /* ブロックの着地 */ function addBlock2Board(c, squres, blk) { /* 盤面に着色 */ for (var x = 0; x < blk.shape.length; x++) { for (var y = 0; y < blk.shape[0].length; y++) { if (blk.shape[x][y] == 0) { continue; } if (blk.x + x <= c.xsqs && blk.y + y <= c.ysqs) { squres[blk.x + x][blk.y + y].color = blk.color; } } } } /* ブロックの落下 */ function isLanded(c, squres, blk) { /* 着地判定 */ for (var x = 0; x < blk.shape.length; x++) { for (var y = 0; y < blk.shape[0].length; y++) { if (blk.shape[x][y] != 0) { /* 地面と接触 */ if ((blk.y + y) == (c.ysqs - 1)) { return true; } /* 既設ブロックと接触 */ if (squres[blk.x + x][blk.y + y + 1].color != c.color) { return true; } } } } /* Not着地 */ return false; } /* GameOverですか? */ function isGameOver(c, squres) { for (var x = c.xsqs/2 - 2 ; x < c.xsqs/2 + 2; x++) { if (squres[x][0].color != c.color) { return true; } } return false; } /* 行削除 */ function eraseLine(c, squres) { for (var y = 0; y < c.ysqs; y++) { /* is enable to erase ? */ var flag = true; for (var x = 0; x < c.xsqs; x++) { if (squres[x][y].color == c.color) { flag = false; break; } } if(!flag) { continue; } /* do to erase */ for (var x = 0; x < c.xsqs; x++) { squres[x][y].color = c.color; } if (y != 0) { for (var y1 = y; y1 != 0; y1--) { for (var x = 0; x < c.xsqs; x++) { squres[x][y1].color = squres[x][y1-1].color; } } } } } /* main処理 */ function tetris(ctx, c, squres, blk) { if (isLanded(c, squres, blk)) { /* ブロックの着地 */ addBlock2Board(c, squres, blk); /* 削除処理 */ eraseLine(c, squres); /* Game Over ? */ if (isGameOver(c,squres)) { drawTetris(ctx, c, squres, blk); clearInterval(fallTimer); } else { /* 新しいブロックの生成 */ blk = block(); } } else { /* 落下 */ blk.y++; } /* 盤面の描画 */ drawTetris(ctx, c, squres, blk); } window.addEventListener('load', initialize, false); } )(); </script> <div>a : 左に移動</div> <div>s : 右に移動</div> <div>d : 下に移動</div> <div>f : 右に回転</div> </body> </html>
もう少しシンプルに作れたかも。