| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521 | <!DOCTYPE html><html><head>    <meta charset="UTF-8">    <title>Connect 4</title>    <link rel="stylesheet" href="styles.css">    <link href="favicon.ico" rel="icon" type="image/x-icon" /></head><body><div class='text'>    <h1 id="h1">Connect 4</h1>    <br>    <h3 id='h3'>If you have issues, e-mail ryan.satur@protonmail.com for technical support.</h3></div><br><button onclick="start_game();" class='button' id='button'>Start Game</button><script>    function start_game() {        "use strict";        let button = document.getElementById("button");        let h1 = document.getElementById("h1");        let h3 = document.getElementById('h3');        h3.remove()        h1.remove();        button.remove();        // game parameters        const DELAY_COMP = 0.5; // seconds for the computer to take its turn        const GRID_CIRCLE = 0.7; // circle size as a fraction of cell size        const GRID_COLS = 7; // number of game columns        const GRID_ROWS = 6; // number of game rows        const MARGIN = 0.02; // margin as a fraction of the shortest screen dimension        // colour variables        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";        // text variables        const TEXT_COMP = "Computer";        const TEXT_PLAY = "YOU";        const TEXT_TIE = "DRAW";        const TEXT_WIN = "WON!";        // cell class        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 the circle or hole            draw(/** @type {CanvasRenderingContext2D} */ ctx) {                // owner colour                let color = this.owner == null ? COLOR_BACKGROUND : this.owner ? COLOR_PLAY : COLOR_COMP;                // draw the circle                ctx.fillStyle = color;                ctx.beginPath();                ctx.arc(this.cx, this.cy, this.r, 0, Math.PI * 2);                ctx.fill();                // draw highlighting                if (this.winner || this.highlight != null) {                                    // colour                    color = this.winner ? COLOR_WIN : this.highlight ? COLOR_PLAY : COLOR_COMP;                    // draw a circle around the perimeter                    ctx.lineWidth = this.r / 4;                    ctx.strokeStyle = color;                    ctx.beginPath();                    ctx.arc(this.cx, this.cy, this.r, 0, Math.PI * 2);                    ctx.stroke();                }            }        }        // set up the canvas and context        var canv = document.createElement("canvas");        document.body.appendChild(canv);        var ctx = canv.getContext("2d");            // game variables        var gameOver, gameTied, grid = [], playersTurn, timeComp;        // dimensions        var height, width, margin;        setDimensions();        // event listeners        canv.addEventListener("click", click);        canv.addEventListener("mousemove", highlightGrid);        window.addEventListener("resize", setDimensions);        // game loop        var timeDelta, timeLast;        requestAnimationFrame(loop);        function loop(timeNow) {            // initialise timeLast            if (!timeLast) {                timeLast = timeNow;            }            // calculate the time difference            timeDelta = (timeNow - timeLast) / 1000; // seconds            timeLast = timeNow;            // update            goComputer(timeDelta);            // draw            drawBackground();            drawGrid();            drawText();            // call the next frame            requestAnimationFrame(loop);        }        function checkWin(row, col) {                    // get all the cells from each direction            let diagL = [], diagR = [], horiz = [], vert = [];            for (let i = 0; i < GRID_ROWS; i++) {                for (let j = 0; j < GRID_COLS; j++) {                                    // horizontal cells                    if (i == row) {                        horiz.push(grid[i][j]);                    }                    // vertical cells                    if (j == col) {                        vert.push(grid[i][j]);                    }                    // top left to bottom right                    if (i - j == row - col) {                        diagL.push(grid[i][j]);                    }                    // top right to bottom left                    if (i + j == row + col) {                        diagR.push(grid[i][j]);                    }                }            }            // if any have four in a row, return a win!            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++) {                // no owner, reset the count                if (cells[i].owner == null) {                    count = 0;                    winningCells = [];                }                // same owner, add to the count                else if (cells[i].owner == lastOwner) {                    count++;                    winningCells.push(cells[i]);                }                // new owner, new count                else {                    count = 1;                    winningCells = [];                    winningCells.push(cells[i]);                }                // set the lastOwner                lastOwner = cells[i].owner;                // four in a row is a win                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 = [];            // set up cell size and margins            let cell, marginX, marginY;            // portrait            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;            }            // landscape            else {                cell = (height - margin * 2) / GRID_ROWS;                marginX = (width - cell * GRID_COLS) / 2;                marginY = margin;            }            // populate the grid            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() {            // frame and butt            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);            // cells            for (let row of grid) {                for (let cell of row) {                    cell.draw(ctx);                }            }        }        function drawText() {            if (!gameOver) {                return;            }            // set up text parameters            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";            // draw the text            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;            }            // count down till the computer makes its selection            if (timeComp > 0) {                timeComp -= delta;                if (timeComp <= 0) {                    selectCell();                }                return;            }            // set up the options array            let options = [];            options[0] = []; // computer wins            options[1] = []; // block the player from winning            options[2] = []; // no significance            options[3] = []; // give away a win            // loop through each column            let cell;            for (let i = 0; i < GRID_COLS; i++) {                cell = highlightCell(grid[0][i].cx, grid[0][i].cy);                // column full, go to the next column                if (cell == null) {                    continue;                }                // first priority, computer wins                cell.owner = playersTurn;                if (checkWin(cell.row, cell.col)) {                    options[0].push(i);                } else {                                // second priority, block the player                    cell.owner = !playersTurn;                    if (checkWin(cell.row, cell.col)) {                        options[1].push(i);                    } else {                        cell.owner = playersTurn;                        // check the cell above                        if (cell.row > 0) {                            grid[cell.row - 1][cell.col].owner = !playersTurn;                            // last priority, let player win                            if (checkWin(cell.row - 1, cell.col)) {                                options[3].push(i);                            }                            // third priority, no significance                            else {                                options[2].push(i);                            }                            // deselect cell above                            grid[cell.row - 1][cell.col].owner = null;                        }                        // no row above, third priority, no significance                        else {                            options[2].push(i);                        }                    }                }                // cancel highlight and selection                cell.highlight = null;                cell.owner = null;            }            // clear the winning cells            for (let row of grid) {                for (let cell of row) {                    cell.winner = false;                }            }            // randomly select a column in priority order            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)];            }            // highlight the selected cell            highlightCell(grid[0][col].cx, grid[0][col].cy);            // set the delay            timeComp = DELAY_COMP;        }        function highlightCell(x, y) {            let col = null;            for (let row of grid) {                for (let cell of row) {                    // clear existing highlighting                    cell.highlight = null;                    // get the column                    if (cell.contains(x, y)) {                        col = cell.col;                    }                }            }            if (col == null) {                return;            }            // highlight the first unoccupied cell            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;                    }                }            }        // don't allow selection if no highlighting        if (!highlighting) {            return;        }            // check for a tied game            if (!gameOver) {                gameTied = true;                OUTER: for (let row of grid) {                    for (let cell of row) {                        if (cell.owner == null) {                            gameTied = false;                            break OUTER;                        }                    }                }                // set game over                if (gameTied) {                    gameOver = true;                }            }            // switch the player if no game over            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();        }    }</script></body></html>
 |