How do I build a 2-player Othello game with HTML, CSS, and JavaScript?

I want to create a browser-based Othello (Reversi) game for two human players using plain HTML, CSS, and JavaScript (no frameworks). The game should include: - An 8x8 game board with the standard starting position (2 white and 2 black pieces in the center) - Click-to-place mechanics for each player's turn - Valid move detection (pieces must outflank opponent pieces) - Automatic flipping of captured pieces - Turn switching between black and white players - Display of current player's turn - Game-over detection (when neither player can move) and score display Please provide complete, working code I can use directly in a single HTML file.

0
·Google·41.1s·7:16 AM·#1

Of course! Here is a complete, single-file implementation of a 2-player Othello game using plain HTML, CSS, and JavaScript. You can copy and paste this code into an .html file and open it in your browser to play.

How the Code Works

  1. HTML (<body>): Sets up the basic structure of the page. It includes a title, a status area to show whose turn it is and the score, the game board container, and a "Restart Game" button.
  2. CSS (<style>): Defines the visual appearance. It uses CSS Grid to create the 8x8 board, styles the squares, and creates the circular black and white game pieces. It also includes a subtle style for hinting at valid moves.
  3. JavaScript (<script>): Contains all the game logic.
    • State Management: The board is represented by an 8x8 2D array where 0 is empty, 1 is black, and 2 is white. currentPlayer tracks whose turn it is.
    • Initialization (initGame): Sets up the initial board state with the four center pieces and renders it.
    • Rendering (renderBoard): Clears the board and redraws it based on the current state of the board array. It also finds and displays valid move hints for the current player.
    • Move Validation (getFlippablePieces): This is the core logic. When a player clicks a square, this function checks all 8 directions from that square to see if placing a piece there would "outflank" and capture any opponent pieces. It returns an array of pieces that would be flipped. If the array is empty, the move is invalid.
    • Game Flow (handleSquareClick, makeMove, switchPlayer): These functions handle player input, update the board array, flip the captured pieces, switch turns, and check for game-over conditions.
    • Game Over: The game ends when neither player has a valid move. The script detects this, declares a winner, and displays the final score.

Complete Othello Game Code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Othello (Reversi)</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
            margin: 0;
            background-color: #f0f0f0;
        }

        h1 {
            color: #333;
        }

        #game-info {
            display: flex;
            justify-content: space-between;
            width: 400px;
            margin-bottom: 20px;
            font-size: 1.2em;
        }

        #game-board {
            display: grid;
            grid-template-columns: repeat(8, 50px);
            grid-template-rows: repeat(8, 50px);
            border: 2px solid #333;
            background-color: #008000; /* Classic green board color */
        }

        .square {
            width: 50px;
            height: 50px;
            box-sizing: border-box;
            border: 1px solid #006400;
            display: flex;
            justify-content: center;
            align-items: center;
            cursor: pointer;
        }

        .piece {
            width: 80%;
            height: 80%;
            border-radius: 50%;
            box-shadow: 0 2px 4px rgba(0,0,0,0.3);
        }

        .black {
            background-color: #111;
        }

        .white {
            background-color: #fff;
        }
        
        /* Hint for valid moves */
        .valid-move-hint {
            width: 30%;
            height: 30%;
            border-radius: 50%;
            background-color: rgba(0, 0, 0, 0.2);
        }

        #restart-button {
            margin-top: 20px;
            padding: 10px 20px;
            font-size: 1em;
            cursor: pointer;
            border: none;
            border-radius: 5px;
            background-color: #007bff;
            color: white;
            transition: background-color 0.2s;
        }

        #restart-button:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>

    <h1>Othello (Reversi)</h1>

    <div id="game-info">
        <div id="turn-display">Current Turn: Black</div>
        <div id="score-display">Black: 2 | White: 2</div>
    </div>

    <div id="game-board"></div>

    <button id="restart-button">Restart Game</button>

    <script>
        document.addEventListener('DOMContentLoaded', () => {
            const BOARD_SIZE = 8;
            const EMPTY = 0;
            const BLACK = 1;
            const WHITE = 2;

            const boardElement = document.getElementById('game-board');
            const turnDisplay = document.getElementById('turn-display');
            const scoreDisplay = document.getElementById('score-display');
            const restartButton = document.getElementById('restart-button');

            let board = [];
            let currentPlayer;
            let gameOver;

            const directions = [
                [-1, -1], [-1, 0], [-1, 1],
                [0, -1],           [0, 1],
                [1, -1], [1, 0], [1, 1]
            ];

            function initGame() {
                board = Array(BOARD_SIZE).fill(0).map(() => Array(BOARD_SIZE).fill(EMPTY));
                
                // Standard starting position
                board[3][3] = WHITE;
                board[3][4] = BLACK;
                board[4][3] = BLACK;
                board[4][4] = WHITE;

                currentPlayer = BLACK;
                gameOver = false;
                
                renderBoard();
                updateStatus();
            }

            function renderBoard() {
                boardElement.innerHTML = '';
                const validMoves = getValidMoves(currentPlayer);

                for (let row = 0; row < BOARD_SIZE; row++) {
                    for (let col = 0; col < BOARD_SIZE; col++) {
                        const square = document.createElement('div');
                        square.className = 'square';
                        square.dataset.row = row;
                        square.dataset.col = col;

                        const pieceValue = board[row][col];
                        if (pieceValue !== EMPTY) {
                            const piece = document.createElement('div');
                            piece.className = `piece ${pieceValue === BLACK ? 'black' : 'white'}`;
                            square.appendChild(piece);
                        } else {
                            // Show hint for valid moves
                            if (validMoves.some(move => move.row === row && move.col === col)) {
                                const hint = document.createElement('div');
                                hint.className = 'valid-move-hint';
                                square.appendChild(hint);
                            }
                        }
                        
                        boardElement.appendChild(square);
                    }
                }
            }
            
            function updateStatus() {
                // Update turn display
                const playerColor = currentPlayer === BLACK ? 'Black' : 'White';
                turnDisplay.textContent = gameOver ? "Game Over" : `Current Turn: ${playerColor}`;

                // Update score
                let blackScore = 0;
                let whiteScore = 0;
                board.forEach(row => {
                    row.forEach(cell => {
                        if (cell === BLACK) blackScore++;
                        if (cell === WHITE) whiteScore++;
                    });
                });
                scoreDisplay.textContent = `Black: ${blackScore} | White: ${whiteScore}`;

                if (gameOver) {
                    let winnerMessage;
                    if (blackScore > whiteScore) winnerMessage = "Black wins!";
                    else if (whiteScore > blackScore) winnerMessage = "White wins!";
                    else winnerMessage = "It's a draw!";
                    turnDisplay.textContent = `Game Over! ${winnerMessage}`;
                }
            }

            function getFlippablePieces(row, col, player) {
                if (board[row][col] !== EMPTY) return [];

                const opponent = player === BLACK ? WHITE : BLACK;
                let flippablePieces = [];

                for (const [dr, dc] of directions) {
                    let piecesInDirection = [];
                    let r = row + dr;
                    let c = col + dc;

                    // Check if there's at least one opponent piece in this direction
                    if (r < 0 || r >= BOARD_SIZE || c < 0 || c >= BOARD_SIZE || board[r][c] !== opponent) {
                        continue;
                    }

                    piecesInDirection.push({ row: r, col: c });
                    r += dr;
                    c += dc;

                    while (r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE) {
                        if (board[r][c] === EMPTY) {
                            piecesInDirection = []; // Not a valid line
                            break;
                        }
                        if (board[r][c] === player) {
                            flippablePieces = flippablePieces.concat(piecesInDirection);
                            break;
                        }
                        piecesInDirection.push({ row: r, col: c });
                        r += dr;
                        c += dc;
                    }
                }
                return flippablePieces;
            }
            
            function getValidMoves(player) {
                const moves = [];
                for (let row = 0; row < BOARD_SIZE; row++) {
                    for (let col = 0; col < BOARD_SIZE; col++) {
                        if (board[row][col] === EMPTY) {
                            if (getFlippablePieces(row, col, player).length > 0) {
                                moves.push({ row, col });
                            }
                        }
                    }
                }
                return moves;
            }

            function makeMove(row, col) {
                const flippablePieces = getFlippablePieces(row, col, currentPlayer);
                if (flippablePieces.length === 0) return;

                board[row][col] = currentPlayer;
                flippablePieces.forEach(piece => {
                    board[piece.row][piece.col] = currentPlayer;
                });

                switchPlayer();
            }

            function switchPlayer() {
                currentPlayer = (currentPlayer === BLACK) ? WHITE : BLACK;
                
                const currentPlayerHasMoves = getValidMoves(currentPlayer).length > 0;
                const opponentPlayer = (currentPlayer === BLACK) ? WHITE : BLACK;
                const opponentPlayerHasMoves = getValidMoves(opponentPlayer).length > 0;

                if (!currentPlayerHasMoves && !opponentPlayerHasMoves) {
                    gameOver = true;
                } else if (!currentPlayerHasMoves) {
                    // Player must pass their turn
                    alert(`${currentPlayer === BLACK ? 'Black' : 'White'} has no valid moves. Turn passes.`);
                    currentPlayer = opponentPlayer;
                }
                
                renderBoard();
                updateStatus();
            }

            boardElement.addEventListener('click', (event) => {
                if (gameOver) return;
                
                const square = event.target.closest('.square');
                if (square) {
                    const row = parseInt(square.dataset.row);
                    const col = parseInt(square.dataset.col);
                    makeMove(row, col);
                }
            });

            restartButton.addEventListener('click', initGame);

            // Start the game
            initGame();
        });
    </script>

</body>
</html>

Comments