Newer
Older
2021-fuyagd / box.js
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();
}