const BLOCK_SIZE = 20; const SCREEN_HEIGHT = 21 * BLOCK_SIZE; const SCREEN_WIDTH = 10 * BLOCK_SIZE; let canvas; let c; window.onload = () => { init(); }; const init = () => { canvas = document.getElementById("game"); canvas.width = SCREEN_WIDTH; canvas.height = SCREEN_HEIGHT; c = canvas.getContext("2d"); }; // フィールドを表す配列 let FIELD = new Array(21) for (let y = 0; y < FIELD.length; y++) { FIELD[y] = new Array(10).fill(0); } // ブロックがないマスの色 const BACK_FILL_COLOR = "#000000"; const BACK_EDGE_COLOR = "#FFFFFF"; // 操作中のブロックがあるマスの色 const ACTBLOCK_FILL_COLOR = "#C80000"; const ACTBLOCK_EDGE_COLOR = "#323324"; // 固定されたブロックがあるマスの色 const FIXBLOCK_FILL_COLOR = "#888888"; const FIXBLOCK_EDGE_COLOR = "#FFFFFF"; const drawBlock = (x, y, fillStyle, strokeStyle) => { let px = x * BLOCK_SIZE; let py = y * BLOCK_SIZE; c.fillStyle = fillStyle; c.fillRect(px, py, BLOCK_SIZE, BLOCK_SIZE); c.strokeStyle = strokeStyle; c.strokeRect(px, py, BLOCK_SIZE, BLOCK_SIZE); } const drawField = () => { // 余白以外の列を読み取るため、yの初期値は1とする。 for (let y = 1; y < FIELD.length; y++) { for (let x = 0; x < FIELD[y].length; x++) { switch (FIELD[y][x]) { // 0はブロックが存在しない case 0: drawBlock(x, y, BACK_FILL_COLOR, BACK_EDGE_COLOR) break; // 1は操作中のブロック case 1: drawBlock(x, y, ACTBLOCK_FILL_COLOR, ACTBLOCK_EDGE_COLOR); break; // 9は固定されたブロック case 9: drawBlock(x, y, FIXBLOCK_FILL_COLOR, FIXBLOCK_EDGE_COLOR) break; } } } } window.onload = () => { init(); newGame(); }; const newGame = () => { drawField(); }; const BLOCK = [ [ [0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0], ], [ [0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0], ], [ [0, 0, 0, 0], [0, 0, 1, 0], [1, 1, 1, 0], [0, 0, 0, 0], ], [ [0, 0, 0, 0], [1, 0, 0, 0], [1, 1, 1, 0], [0, 0, 0, 0], ], [ [0, 0, 0, 0], [1, 1, 0, 0], [0, 1, 1, 0], [0, 0, 0, 0], ], [ [0, 0, 0, 0], [0, 1, 1, 0], [1, 1, 0, 0], [0, 0, 0, 0], ], [ [0, 0, 0, 0], [0, 1, 0, 0], [1, 1, 1, 0], [0, 0, 0, 0], ], ]; // ブロックの左上の座標 let POS = { x: 3, y: 0, }; // ブロックの位置の初期化 const initPos = () => { POS.x = 3; POS.y = 0; } let BLOCK_STOCK = [0, 1, 2, 3, 4, 5, 6]; const putBlock = () => { // ブロックをランダムに選択する。ただし、7回で全てのブロックが出現するようにする。 let idx = Math.floor(Math.random() * BLOCK_STOCK.length); let block = BLOCK[BLOCK_STOCK[idx]]; BLOCK_STOCK = BLOCK_STOCK.filter((block) => block !== BLOCK_STOCK[idx]); if (BLOCK_STOCK.length === 0) { BLOCK_STOCK = [0, 1, 2, 3, 4, 5, 6]; } initPos(); // FIELDに新しいブロックを格納する。 for (let y = 0; y < block.length; y++) { for (let x = 0; x < block[y].length; x++) { FIELD[POS.y + y][POS.x + x] = block[y][x]; } } }; const newGame = () => { putBlock(); drawField(); }; const getActBlock = () => { // 操作中のブロックを格納する配列 let tmpBlock = new Array(4); for (let y = 0; y < tmpBlock.length; y++) { tmpBlock[y] = new Array(4).fill(0); } for (let y = 0; y < 4; y++) { for (let x = 0; x < 4; x++) { // フィールドの内側かつマスの状態が操作中のブロックだった場合、操作中のブロックとして取得する。 if (y + POS.y < FIELD.length && x + POS.x < FIELD[0].length && FIELD[y + POS.y][x + POS.x] === 1) { tmpBlock[y][x] = FIELD[y + POS.y][x + POS.x]; } } } return tmpBlock; }; const moveBlock = (px, py) => { // 操作中のブロックを取得する。 let tmpBlock = getActBlock(); // 動かす前の操作中のブロックがあるマスを、ブロックがない状態にする。 for (let y = 0; y < 4; y++) { for (let x = 0; x < 4; x++) { if (y + POS.y < FIELD.length && x + POS.x < FIELD[0].length && FIELD[POS.y + y][POS.x + x] === 1){ FIELD[POS.y + y][POS.x + x] = 0; } } } // ブロックの位置を動かす。 POS.x += px; POS.y += py; // 動かした位置にブロックを配置する。 for (let y = 0; y < 4; y++) { for (let x = 0; x < 4; x++) { if (y + POS.y < FIELD.length && x + POS.x < FIELD[0].length && tmpBlock[y][x] === 1) { FIELD[y + POS.y][x + POS.x] = tmpBlock[y][x]; } } } } const checkMove = (px, py) => { let tmpBlock = getActBlock(); for (let y = 0; y < tmpBlock.length; y++) { for (let x = 0; x < tmpBlock[0].length; x++) { // ブロックが動いた先がフィールドの範囲外だった場合 if (tmpBlock[y][x] === 1 && ((POS.y + py + y >= FIELD.length || POS.y + py + y < 0) || (POS.x + px + x >= FIELD[0].length || POS.x + px + x < 0))) { return false; } // ブロックが動いた先に固定されたブロックが存在する場合。 if (tmpBlock[y][x] === 1 && POS.y + py + y < FIELD.length && POS.x + px + x < FIELD[0].length && FIELD[POS.y + py + y][POS.x + px + x] === 9 ) { return false; } } } return true; }; let SPEED = 1000; let INTERVAL_ID; const newGame = () => { putBlock(); INTERVAL_ID = setInterval(() => { drawField(); if (checkMove(0, 1)) { moveBlock(0, 1); } else { clearInterval(INTERVAL_ID); } }, SPEED); }; const fixBlock = () => { for (let y = 0; y < 4; y++) { for (let x = 0; x < 4; x++) { // 操作中のブロックを固定されたブロックに変更する。 if (POS.y + y < FIELD.length && POS.x + x < FIELD[0].length && FIELD[POS.y + y][POS.x + x] === 1) { FIELD[POS.y + y][POS.x + x] = 9; } } } }; / 操作中のブロックが固定されているか let FIX_BLOCK_FLAG = true; // 操作中のブロックが固定されるまでのカウンター let FIX_COUNTER = 0; // 操作中のブロックを固定するまでのカウント数 const FIX_COUNT_LIMIT = 5; const newGame = () => { INTERVAL_ID = setInterval(() => { drawField(); // 操作中のブロックが固定された場合、新しいブロックを落とす。 if (FIX_BLOCK_FLAG === true) { putBlock(); drawField(); FIX_BLOCK_FLAG = false; } if (checkMove(0, 1)) { moveBlock(0, 1); } else { // 操作中のブロックが落下できなかった回数をカウントする。 FIX_COUNTER++; // カウントの回数が一定数を超えた時、操作中のブロックを固定する。 if (FIX_COUNTER === FIX_COUNT_LIMIT) { FIX_COUNTER = 0; fixBlock(); FIX_BLOCK_FLAG = true; } } }, SPEED); }; const putBlock = () => { // ブロックをランダムに選択する。ただし、7回で全てのブロックが出現するようにする。 let idx = Math.floor(Math.random() * BLOCK_STOCK.length); let block = BLOCK[BLOCK_STOCK[idx]]; BLOCK_STOCK = BLOCK_STOCK.filter((block) => block !== BLOCK_STOCK[idx]); if (BLOCK_STOCK.length === 0) { BLOCK_STOCK = [0, 1, 2, 3, 4, 5, 6]; } initPos(); for (let y = 0; y < block.length; y++) { for (let x = 0; x < block[y].length; x++) { // 新しいブロックの位置に固定されたブロックが存在する場合、新しいブロックを生成せずにfalseを返す。 if (block[y][x] === 1 && FIELD[POS.y + y][POS.x + x] === 9) { return false; } else if (block[y][x] === 1) { FIELD[POS.y + y][POS.x + x] = block[y][x]; } } } return true; }; // ゲームオーバーか let GAMEOVER_FLAG = false; const newGame = () => { INTERVAL_ID = setInterval(() => { drawField(); // ブロック挿入の判定 if (GAMEOVER_FLAG === false && FIX_BLOCK_FLAG === true) { GAMEOVER_FLAG = !putBlock(); drawField(); FIX_BLOCK_FLAG = false; } // ゲームオーバーの判定 if (GAMEOVER_FLAG === true) { alert("GAMEOVER"); clearInterval(INTERVAL_ID); } if (checkMove(0, 1)) { moveBlock(0, 1); } else { FIX_COUNTER++; if (FIX_COUNTER === FIX_COUNT_LIMIT) { FIX_COUNTER = 0; fixBlock(); FIX_BLOCK_FLAG = true; } } GAMEOVER_FLAG = isGameover(); }, SPEED); }; const init = () => { // ボタンを設定 document.getElementById('up').addEventListener('click', clickUp); document.getElementById('down').addEventListener('click', clickDown); document.getElementById('right').addEventListener('click', clickRight); document.getElementById('left').addEventListener('click', clickLeft); canvas = document.getElementById("game"); canvas.width = SCREEN_WIDTH; canvas.height = SCREEN_HEIGHT; c = canvas.getContext("2d"); }; // ハードドロップ const clickUp = () => { while (checkMove(0, 1)) { moveBlock(0, 1); } fixBlock(); GAMEOVER_FLAG = !putBlock(); drawField(); } // 下に移動 const clickDown = () => { if (checkMove(0, 1)) { moveBlock(0, 1); } drawField(); } // 右に移動 const clickRight = () => { if (checkMove(1, 0)) { moveBlock(1, 0); } drawField(); } // 左に移動 const clickLeft = () => { if (checkMove(-1, 0)) { moveBlock(-1, 0); } drawField(); } // 配列を左回転させる関数 const rotateLeft = array => array[0].map((_, i) => array.map(row => row[i])).reverse(); // 配列を右回転させる関数 const rotateRight = array => array.reverse()[0].map((_, i) => array.map(row => row[i])); // 操作中のブロックを回転させる関数 const rotateBlock = (rotateFunc) => { let rotatedBlock = rotateFunc(getActBlock()); // 回転させる前の操作中のブロックを削除 FIELD.forEach(row => { row.forEach(i => { if (i === 1) { i = 0; } }) }); // 回転させたブロックをフィールドに配置 for (let y = 0; y < rotatedBlock.length; y++) { for (let x = 0; x < rotatedBlock[y].length; x++) { if (FIELD[y + POS.y][x + POS.x] !== 9 && y + POS.y < FIELD.length && x + POS.x < FIELD[0].length) { FIELD[y + POS.y][x + POS.x] = rotatedBlock[y][x]; } } } }; // 回転できるか判定する関数 const checkRotate = (rotateFunc) => { let rotatedBlock = rotateFunc(getActBlock()); for (let y = 0; y < rotatedBlock.length; y++) { for (let x = 0; x < rotatedBlock[0].length; x++) { // ブロックが動いた先が範囲外だった時 if (rotatedBlock[y][x] === 1 && ((POS.y + y >= FIELD.length || POS.y + y < 0) || (POS.x + x >= FIELD[0].length || POS.x + x < 0))) { return false; } // ブロックが動いた先に、固定されたブロックが存在する時 if (FIELD[POS.y + y][POS.x + x] === 9 && rotatedBlock[y][x] === 1) { return false; } } } return true; }; // 左に回転 const clickLeftRotate = () => { if (checkRotate(rotateLeft)) { rotateBlock(rotateLeft); } drawField(); } // 右に回転 const clickRightRotate = () => { if (checkRotate(rotateRight)) { rotateBlock(rotateRight); } drawField(); } const init = () => { // ボタンを設定 document.getElementById('up').addEventListener('click', clickUp); document.getElementById('down').addEventListener('click', clickDown); document.getElementById('right').addEventListener('click', clickRight); document.getElementById('left').addEventListener('click', clickLeft); document.getElementById('rotateright').addEventListener('click', clickRightRotate); // 追加 document.getElementById('rotateleft').addEventListener('click', clickLeftRotate); // 追加 canvas = document.getElementById("game"); canvas.width = SCREEN_WIDTH; canvas.height = SCREEN_HEIGHT; c = canvas.getContext("2d"); }; const deleteLine = () => { for (let i = FIELD.length - 1; i >= 1; i--) { // フィールドの横一列を取得し、全て固定されたブロックの場合は列を削除する。 if (typeof (FIELD[i].find(item => item !== 9)) === "undefined") { for (let j = i; j >= 1; j--) { FIELD[j] = FIELD[j - 1].concat(); } i++; } } }; const newGame = () => { INTERVAL_ID = setInterval(() => { drawField(); // ブロック挿入の判定 if (GAMEOVER_FLAG === false && FIX_BLOCK_FLAG === true) { GAMEOVER_FLAG = !putBlock(); drawField(); FIX_BLOCK_FLAG = false; } // ゲームオーバーの判定 if (GAMEOVER_FLAG === true) { alert("GAMEOVER"); clearInterval(INTERVAL_ID); } if (checkMove(0, 1)) { moveBlock(0, 1); } else { FIX_COUNTER++; if (FIX_COUNTER === FIX_COUNT_LIMIT) { FIX_COUNTER = 0; fixBlock(); deleteLine(); // 追加 FIX_BLOCK_FLAG = true; } } GAMEOVER_FLAG = isGameover(); }, SPEED); }; // ハードドロップ const clickUp = () => { while (checkMove(0, 1)) { moveBlock(0, 1); } fixBlock(); deleteLine(); // 追加 GAMEOVER_FLAG = !putBlock(); drawField(); }