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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Othello (Reversi) - 2 Player</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
background: #f0f0f0;
margin: 0;
padding: 20px;
}
h1 {
color: #333;
margin-bottom: 10px;
}
#game-info {
text-align: center;
margin-bottom: 20px;
}
#status {
font-size: 1.2em;
font-weight: bold;
color: #d32f2f;
min-height: 1.5em;
}
#turn {
font-size: 1.1em;
color: #333;
margin: 10px 0;
}
#scores {
font-size: 1.1em;
font-weight: bold;
color: #333;
}
#board {
display: grid;
grid-template-columns: repeat(8, 1fr);
width: min(80vmin, 500px);
height: min(80vmin, 500px);
gap: 2px;
background: #2e7d32;
border: 4px solid #1b5e20;
border-radius: 8px;
padding: 4px;
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
}
.cell {
background: #4caf50;
position: relative;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
.cell:hover:not(.disabled):not(.black):not(.white) {
background: #66bb6a;
transform: scale(1.05);
}
.cell.black::after,
.cell.white::after {
content: '';
position: absolute;
top: 12%;
left: 12%;
width: 76%;
height: 76%;
border-radius: 50%;
}
.cell.black::after {
background: #212121;
box-shadow: inset 0 3px 6px rgba(0,0,0,0.6), 0 1px 2px rgba(255,255,255,0.2);
}
.cell.white::after {
background: #fafafa;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.3), 0 1px 2px rgba(0,0,0,0.2);
}
#board.disabled {
pointer-events: none;
}
#board.disabled .cell {
cursor: default;
}
#board.disabled .cell:hover {
transform: none;
background: #4caf50;
}
</style>
</head>
<body>
<h1>Othello (Reversi)</h1>
<div id="game-info">
<div id="status"></div>
<div id="turn"></div>
<div id="scores"></div>
</div>
<div id="board"></div>
<script>
const dirs = [
[-1, -1], [-1, 0], [-1, 1],
[0, -1], [0, 1],
[1, -1], [1, 0], [1, 1]
];
let board = Array.from({ length: 8 }, () => Array(8).fill(0));
let current = 1; // 1: Black, 2: White
let gameOver = false;
function canCaptureInDir(r, c, p, dr, dc) {
const opp = 3 - p;
let x = r + dr;
let y = c + dc;
if (x < 0 || x > 7 || y < 0 || y > 7 || board[x][y] !== opp) return false;
while (true) {
x += dr;
y += dc;
if (x < 0 || x > 7 || y < 0 || y > 7) return false;
if (board[x][y] === p) return true;
if (board[x][y] === 0) return false;
}
}
function isValidMove(r, c, p) {
if (board[r][c] !== 0) return false;
for (let [dr, dc] of dirs) {
if (canCaptureInDir(r, c, p, dr, dc)) return true;
}
return false;
}
function flipDir(r, c, p, dr, dc) {
const opp = 3 - p;
let tx = r + dr;
let ty = c + dc;
if (tx < 0 || tx > 7 || ty < 0 || ty > 7 || board[tx][ty] !== opp) return;
const flips = [[tx, ty]];
tx += dr;
ty += dc;
while (tx >= 0 && tx <= 7 && ty >= 0 && ty <= 7 && board[tx][ty] === opp) {
flips.push([tx, ty]);
tx += dr;
ty += dc;
}
if (tx >= 0 && tx <= 7 && ty >= 0 && ty <= 7 && board[tx][ty] === p) {
for (let [fx, fy] of flips) {
board[fx][fy] = p;
}
}
}
function makeMove(r, c, p) {
board[r][c] = p;
for (let [dr, dc] of dirs) {
flipDir(r, c, p, dr, dc);
}
}
function hasValidMoves(p) {
for (let i = 0; i < 8; i++) {
for (let j = 0; j < 8; j++) {
if (isValidMove(i, j, p)) return true;
}
}
return false;
}
function render() {
const cells = document.querySelectorAll('.cell');
cells.forEach((cell) => {
const r = parseInt(cell.dataset.r);
const c = parseInt(cell.dataset.c);
cell.classList.remove('black', 'white');
if (board[r][c] === 1) cell.classList.add('black');
else if (board[r][c] === 2) cell.classList.add('white');
});
}
function updateScores() {
let blackCount = 0;
let whiteCount = 0;
for (let row of board) {
for (let cell of row) {
if (cell === 1) blackCount++;
else if (cell === 2) whiteCount++;
}
}
document.getElementById('scores').textContent = `Black: ${blackCount} | White: ${whiteCount}`;
}
function updateTurn() {
const playerName = current === 1 ? 'Black' : 'White';
document.getElementById('turn').textContent = `${playerName}'s turn`;
}
function endGame() {
gameOver = true;
updateScores();
let blackCount = 0;
let whiteCount = 0;
for (let row of board) {
for (let cell of row) {
if (cell === 1) blackCount++;
else if (cell === 2) whiteCount++;
}
}
let msg = `Game Over! Black: ${blackCount} | White: ${whiteCount}`;
if (blackCount > whiteCount) msg += ' | Black wins!';
else if (whiteCount > blackCount) msg += ' | White wins!';
else msg += ' | Draw!';
document.getElementById('status').textContent = msg;
document.getElementById('board').classList.add('disabled');
}
function handleClick(e) {
if (gameOver) return;
const cell = e.currentTarget;
const r = parseInt(cell.dataset.r);
const c = parseInt(cell.dataset.c);
if (board[r][c] !== 0 || !isValidMove(r, c, current)) return;
makeMove(r, c, current);
render();
updateScores();
// Switch turns, handling passes
current = 3 - current;
while (!hasValidMoves(current) && hasValidMoves(3 - current)) {
current = 3 - current;
}
if (!hasValidMoves(current)) {
endGame();
return;
}
updateTurn();
}
// Initialize game
document.addEventListener('DOMContentLoaded', () => {
const boardDiv = document.getElementById('board');
for (let i = 0; i < 8; i++) {
for (let j = 0; j < 8; j++) {
const cell = document.createElement('div');
cell.classList.add('cell');
cell.dataset.r = i.toString();
cell.dataset.c = j.toString();
cell.addEventListener('click', handleClick);
boardDiv.appendChild(cell);
}
}
// Set starting position
board[3][3] = 2;
board[3][4] = 1;
board[4][3] = 1;
board[4][4] = 2;
current = 1;
gameOver = false;
document.getElementById('status').textContent = '';
document.getElementById('board').classList.remove('disabled');
render();
updateScores();
updateTurn();
});
</script>
</body>
</html>
Usage
- Save as
othello.htmland open in any modern browser. - Black starts (click valid green squares to place and auto-flip).
- Game auto-handles passes (skips turns if no moves) and detects end when neither can move.
- Scores update live; winner declared on game over.
- Fully responsive, touch-friendly.
This is a complete, tested implementation with all requested features. No external dependencies.
Comments