//Javascript Connect 4 Game function start_game() { "use strict"; document.body.innerHTML = ''; const DELAY_COMP = 0.5; const GRID_CIRCLE = 0.7; const GRID_COLS = 7; const GRID_ROWS = 6; const MARGIN = 0.02; const COLOR_BACKGROUND = "mintcream"; const COLOR_COMP = "yellow"; const COLOR_COMP_DRK = "olive"; const COLOR_FRAME = "dodgerblue"; const COLOR_FRAME_BUTT = "royalblue"; const COLOR_PLAY = "red"; const COLOR_PLAY_DRK = "darkred"; const COLOR_TIE = "darkgrey"; const COLOR_TIE_DRK = "black"; const COLOR_WIN = "black"; const TEXT_COMP = "Computer"; const TEXT_PLAY = "YOU"; const TEXT_TIE = "DRAW"; const TEXT_WIN = "WON!"; class Cell { constructor(left, top, w, h, row, col) { this.bot = top + h; this.left = left; this.right = left + w; this.top = top; this.w = w; this.h = h; this.row = row; this.col = col; this.cx = left + w / 2; this.cy = top + h / 2; this.r = w * GRID_CIRCLE / 2; this.highlight = null; this.owner = null; this.winner = false; } contains(x, y) { return x > this.left && x < this.right && y > this.top && y < this.bot; } draw(/** @type {CanvasRenderingContext2D} */ ctx) { let color = this.owner == null ? COLOR_BACKGROUND : this.owner ? COLOR_PLAY : COLOR_COMP; ctx.fillStyle = color; ctx.beginPath(); ctx.arc(this.cx, this.cy, this.r, 0, Math.PI * 2); ctx.fill(); if (this.winner || this.highlight != null) { color = this.winner ? COLOR_WIN : this.highlight ? COLOR_PLAY : COLOR_COMP; ctx.lineWidth = this.r / 4; ctx.strokeStyle = color; ctx.beginPath(); ctx.arc(this.cx, this.cy, this.r, 0, Math.PI * 2); ctx.stroke(); } } } var canv = document.createElement("canvas"); document.body.appendChild(canv); var ctx = canv.getContext("2d"); var gameOver, gameTied, grid = [], playersTurn, timeComp; var height, width, margin; setDimensions(); canv.addEventListener("click", click); canv.addEventListener("mousemove", highlightGrid); window.addEventListener("resize", setDimensions); var timeDelta, timeLast; requestAnimationFrame(loop); function loop(timeNow) { if (!timeLast) { timeLast = timeNow; } timeDelta = (timeNow - timeLast) / 1000; // seconds timeLast = timeNow; goComputer(timeDelta); drawBackground(); drawGrid(); drawText(); requestAnimationFrame(loop); } function checkWin(row, col) { let diagL = [], diagR = [], horiz = [], vert = []; for (let i = 0; i < GRID_ROWS; i++) { for (let j = 0; j < GRID_COLS; j++) { if (i == row) { horiz.push(grid[i][j]); } if (j == col) { vert.push(grid[i][j]); } if (i - j == row - col) { diagL.push(grid[i][j]); } if (i + j == row + col) { diagR.push(grid[i][j]); } } } return connect4(diagL) || connect4(diagR) || connect4(horiz) || connect4(vert); } function connect4(cells = []) { let count = 0, lastOwner = null; let winningCells = []; for (let i = 0; i < cells.length; i++) { if (cells[i].owner == null) { count = 0; winningCells = []; } else if (cells[i].owner == lastOwner) { count++; winningCells.push(cells[i]); } else { count = 1; winningCells = []; winningCells.push(cells[i]); } lastOwner = cells[i].owner; if (count == 4) { for (let cell of winningCells) { cell.winner = true; } return true; } } return false; } function click(ev) { if (gameOver) { newGame(); return; } if (!playersTurn) { return; } selectCell(); } function createGrid() { grid = []; let cell, marginX, marginY; if ((width - margin * 2) * GRID_ROWS / GRID_COLS < height - margin * 2) { cell = (width - margin * 2) / GRID_COLS; marginX = margin; marginY = (height - cell * GRID_ROWS) / 2; } else { cell = (height - margin * 2) / GRID_ROWS; marginX = (width - cell * GRID_COLS) / 2; marginY = margin; } for (let i = 0; i < GRID_ROWS; i++) { grid[i] = []; for (let j = 0; j < GRID_COLS; j++) { let left = marginX + j * cell; let top = marginY + i * cell; grid[i][j] = new Cell(left, top, cell, cell, i, j); } } } function drawBackground() { ctx.fillStyle = COLOR_BACKGROUND; ctx.fillRect(0, 0, width, height); } function drawGrid() { let cell = grid[0][0]; let fh = cell.h * GRID_ROWS; let fw = cell.w * GRID_COLS; ctx.fillStyle = COLOR_FRAME; ctx.fillRect(cell.left, cell.top, fw, fh); ctx.fillStyle = COLOR_FRAME_BUTT; ctx.fillRect(cell.left - margin / 2, cell.top + fh - margin / 2, fw + margin, margin); for (let row of grid) { for (let cell of row) { cell.draw(ctx); } } } function drawText() { if (!gameOver) { return; } let size = grid[0][0].h; ctx.fillStyle = gameTied ? COLOR_TIE : playersTurn ? COLOR_PLAY : COLOR_COMP; ctx.font = size + "px dejavu sans mono"; ctx.lineJoin = "round"; ctx.lineWidth = size / 10; ctx.strokeStyle = gameTied ? COLOR_TIE_DRK : playersTurn ? COLOR_PLAY_DRK : COLOR_COMP_DRK; ctx.textAlign = "center"; ctx.textBaseline = "middle"; let offset = size * 0.55; let text = gameTied ? TEXT_TIE : playersTurn ? TEXT_PLAY : TEXT_COMP; if (gameTied) { ctx.strokeText(text, width / 2, height / 2); ctx.fillText(text, width / 2, height / 2); } else { ctx.strokeText(text, width / 2, height / 2 - offset); ctx.fillText(text, width / 2, height / 2 - offset); ctx.strokeText(TEXT_WIN, width / 2, height / 2 + offset); ctx.fillText(TEXT_WIN, width / 2, height / 2 + offset); } } function goComputer(delta) { if (playersTurn || gameOver) { return; } if (timeComp > 0) { timeComp -= delta; if (timeComp <= 0) { selectCell(); } return; } let options = []; options[0] = []; options[1] = []; options[2] = []; options[3] = []; let cell; for (let i = 0; i < GRID_COLS; i++) { cell = highlightCell(grid[0][i].cx, grid[0][i].cy); if (cell == null) { continue; } cell.owner = playersTurn; if (checkWin(cell.row, cell.col)) { options[0].push(i); } else { cell.owner = !playersTurn; if (checkWin(cell.row, cell.col)) { options[1].push(i); } else { cell.owner = playersTurn; if (cell.row > 0) { grid[cell.row - 1][cell.col].owner = !playersTurn; if (checkWin(cell.row - 1, cell.col)) { options[3].push(i); } else { options[2].push(i); } grid[cell.row - 1][cell.col].owner = null; } else { options[2].push(i); } } } cell.highlight = null; cell.owner = null; } for (let row of grid) { for (let cell of row) { cell.winner = false; } } let col; if (options[0].length > 0) { col = options[0][Math.floor(Math.random() * options[0].length)]; } else if (options[1].length > 0) { col = options[1][Math.floor(Math.random() * options[1].length)]; } else if (options[2].length > 0) { col = options[2][Math.floor(Math.random() * options[2].length)]; } else if (options[3].length > 0) { col = options[3][Math.floor(Math.random() * options[3].length)]; } highlightCell(grid[0][col].cx, grid[0][col].cy); timeComp = DELAY_COMP; } function highlightCell(x, y) { let col = null; for (let row of grid) { for (let cell of row) { cell.highlight = null; if (cell.contains(x, y)) { col = cell.col; } } } if (col == null) { return; } for (let i = GRID_ROWS - 1; i >= 0; i--) { if (grid[i][col].owner == null) { grid[i][col].highlight = playersTurn; return grid[i][col]; } } return null; } function highlightGrid(/** @type {MouseEvent} */ ev) { if (!playersTurn || gameOver) { return; } highlightCell(ev.clientX, ev.clientY); } function newGame() { playersTurn = Math.random() < 0.5; gameOver = false; gameTied = false; createGrid(); } function selectCell() { let highlighting = false; OUTER: for (let row of grid) { for (let cell of row) { if (cell.highlight != null) { highlighting = true; cell.highlight = null; cell.owner = playersTurn; if (checkWin(cell.row, cell.col)) { gameOver = true; } break OUTER; } } } if (!highlighting) { return; } if (!gameOver) { gameTied = true; OUTER: for (let row of grid) { for (let cell of row) { if (cell.owner == null) { gameTied = false; break OUTER; } } } if (gameTied) { gameOver = true; } } if (!gameOver) { playersTurn = !playersTurn; } } function setDimensions() { height = window.innerHeight; width = window.innerWidth; canv.height = height; canv.width = width; margin = MARGIN * Math.min(height, width); newGame(); } }