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.
Complete Othello/Reversi Game
Here's a fully functional, single-file Othello game with all the features you requested:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Othello Game</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
}
.game-container {
background: white;
border-radius: 15px;
padding: 30px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}
h1 {
text-align: center;
color: #333;
margin-bottom: 20px;
}
.info-panel {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding: 15px;
background: #f5f5f5;
border-radius: 10px;
}
.score {
display: flex;
gap: 30px;
}
.score-item {
display: flex;
align-items: center;
gap: 10px;
font-size: 18px;
font-weight: bold;
}
.score-disc {
width: 30px;
height: 30px;
border-radius: 50%;
border: 2px solid #333;
}
.score-disc.black {
background: #000;
}
.score-disc.white {
background: #fff;
}
.current-turn {
font-size: 18px;
font-weight: bold;
color: #333;
display: flex;
align-items: center;
gap: 10px;
}
.turn-indicator {
width: 25px;
height: 25px;
border-radius: 50%;
border: 2px solid #333;
}
#board {
display: grid;
grid-template-columns: repeat(8, 60px);
grid-template-rows: repeat(8, 60px);
gap: 2px;
background: #2c5f2d;
padding: 2px;
border-radius: 5px;
margin: 0 auto;
}
.cell {
width: 60px;
height: 60px;
background: #4a9d4f;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
position: relative;
transition: background 0.2s;
}
.cell:hover {
background: #5ab55f;
}
.cell.valid-move {
background: #6bc96f;
}
.cell.valid-move::after {
content: '';
width: 15px;
height: 15px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.5);
position: absolute;
}
.disc {
width: 50px;
height: 50px;
border-radius: 50%;
border: 2px solid #333;
transition: transform 0.3s, background 0.3s;
animation: placeDisc 0.3s ease-out;
}
@keyframes placeDisc {
0% {
transform: scale(0);
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
}
}
.disc.black {
background: radial-gradient(circle at 30% 30%, #333, #000);
}
.disc.white {
background: radial-gradient(circle at 30% 30%, #fff, #ddd);
}
.disc.flip {
animation: flipDisc 0.6s ease-in-out;
}
@keyframes flipDisc {
0%, 100% {
transform: rotateY(0deg);
}
50% {
transform: rotateY(90deg);
}
}
.buttons {
margin-top: 20px;
display: flex;
justify-content: center;
gap: 10px;
}
button {
padding: 12px 24px;
font-size: 16px;
font-weight: bold;
border: none;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
background: #667eea;
color: white;
}
button:hover {
background: #5568d3;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.game-over {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 40px;
border-radius: 15px;
box-shadow: 0 10px 50px rgba(0, 0, 0, 0.5);
text-align: center;
z-index: 1000;
display: none;
}
.game-over.show {
display: block;
animation: slideIn 0.5s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translate(-50%, -60%);
}
to {
opacity: 1;
transform: translate(-50%, -50%);
}
}
.game-over h2 {
color: #333;
margin-bottom: 20px;
font-size: 28px;
}
.game-over p {
font-size: 20px;
margin-bottom: 10px;
color: #666;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 999;
display: none;
}
.overlay.show {
display: block;
}
</style>
</head>
<body>
<div class="game-container">
<h1>Othello / Reversi</h1>
<div class="info-panel">
<div class="score">
<div class="score-item">
<div class="score-disc black"></div>
<span id="black-score">2</span>
</div>
<div class="score-item">
<div class="score-disc white"></div>
<span id="white-score">2</span>
</div>
</div>
<div class="current-turn">
<span>Current Turn:</span>
<div class="turn-indicator black" id="turn-indicator"></div>
<span id="turn-text">Black</span>
</div>
</div>
<div id="board"></div>
<div class="buttons">
<button onclick="resetGame()">New Game</button>
</div>
</div>
<div class="overlay" id="overlay"></div>
<div class="game-over" id="game-over">
<h2 id="winner-text">Game Over!</h2>
<p id="final-score"></p>
<button onclick="resetGame()">Play Again</button>
</div>
<script>
const BOARD_SIZE = 8;
const BLACK = 1;
const WHITE = 2;
const EMPTY = 0;
let board = [];
let currentPlayer = BLACK;
let gameOver = false;
// Direction vectors for checking valid moves
const directions = [
[-1, -1], [-1, 0], [-1, 1],
[0, -1], [0, 1],
[1, -1], [1, 0], [1, 1]
];
// Initialize the game
function initGame() {
board = Array(BOARD_SIZE).fill(null).map(() => Array(BOARD_SIZE).fill(EMPTY));
// Set up initial position
const mid = BOARD_SIZE / 2;
board[mid - 1][mid - 1] = WHITE;
board[mid - 1][mid] = BLACK;
board[mid][mid - 1] = BLACK;
board[mid][mid] = WHITE;
currentPlayer = BLACK;
gameOver = false;
renderBoard();
updateScore();
updateTurnDisplay();
}
// Create the board HTML
function renderBoard() {
const boardElement = document.getElementById('board');
boardElement.innerHTML = '';
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 0; col < BOARD_SIZE; col++) {
const cell = document.createElement('div');
cell.className = 'cell';
cell.dataset.row = row;
cell.dataset.col = col;
cell.onclick = () => handleCellClick(row, col);
if (board[row][col] !== EMPTY) {
const disc = document.createElement('div');
disc.className = `disc ${board[row][col] === BLACK ? 'black' : 'white'}`;
cell.appendChild(disc);
} else if (isValidMove(row, col, currentPlayer)) {
cell.classList.add('valid-move');
}
boardElement.appendChild(cell);
}
}
}
// Handle cell click
function handleCellClick(row, col) {
if (gameOver || board[row][col] !== EMPTY) return;
if (isValidMove(row, col, currentPlayer)) {
makeMove(row, col, currentPlayer);
// Switch player
currentPlayer = currentPlayer === BLACK ? WHITE : BLACK;
updateTurnDisplay();
// Check if next player has valid moves
setTimeout(() => {
if (!hasValidMoves(currentPlayer)) {
// Current player has no moves, switch back
currentPlayer = currentPlayer === BLACK ? WHITE : BLACK;
updateTurnDisplay();
if (!hasValidMoves(currentPlayer)) {
// Neither player can move, game over
endGame();
} else {
alert(`${currentPlayer === BLACK ? 'White' : 'Black'} has no valid moves. Turn skipped.`);
}
}
}, 100);
}
}
// Check if a move is valid
function isValidMove(row, col, player) {
if (board[row][col] !== EMPTY) return false;
const opponent = player === BLACK ? WHITE : BLACK;
for (const [dx, dy] of directions) {
let r = row + dx;
let c = col + dy;
let foundOpponent = false;
while (r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE) {
if (board[r][c] === EMPTY) break;
if (board[r][c] === opponent) {
foundOpponent = true;
} else if (board[r][c] === player) {
if (foundOpponent) return true;
break;
}
r += dx;
c += dy;
}
}
return false;
}
// Make a move and flip pieces
function makeMove(row, col, player) {
board[row][col] = player;
const opponent = player === BLACK ? WHITE : BLACK;
const toFlip = [];
for (const [dx, dy] of directions) {
let r = row + dx;
let c = col + dy;
const tempFlip = [];
while (r >= 0 && r < BOARD_SIZE && c >= 0 && c < BOARD_SIZE) {
if (board[r][c] === EMPTY) break;
if (board[r][c] === opponent) {
tempFlip.push([r, c]);
} else if (board[r][c] === player) {
toFlip.push(...tempFlip);
break;
}
r += dx;
c += dy;
}
}
// Flip the pieces
for (const [r, c] of toFlip) {
board[r][c] = player;
}
renderBoard();
updateScore();
}
// Check if a player has valid moves
function hasValidMoves(player) {
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 0; col < BOARD_SIZE; col++) {
if (isValidMove(row, col, player)) {
return true;
}
}
}
return false;
}
// Update score display
function updateScore() {
let blackCount = 0;
let whiteCount = 0;
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 0; col < BOARD_SIZE; col++) {
if (board[row][col] === BLACK) blackCount++;
if (board[row][col] === WHITE) whiteCount++;
}
}
document.getElementById('black-score').textContent = blackCount;
document.getElementById('white-score').textContent = whiteCount;
}
// Update turn display
function updateTurnDisplay() {
const turnIndicator = document.getElementById('turn-indicator');
const turnText = document.getElementById('turn-text');
if (currentPlayer === BLACK) {
turnIndicator.className = 'turn-indicator black';
turnText.textContent = 'Black';
} else {
turnIndicator.className = 'turn-indicator white';
turnText.textContent = 'White';
}
}
// End game and show results
function endGame() {
gameOver = true;
const blackScore = parseInt(document.getElementById('black-score').textContent);
const whiteScore = parseInt(document.getElementById('white-score').textContent);
let winnerText = '';
if (blackScore > whiteScore) {
winnerText = 'Black Wins!';
} else if (whiteScore > blackScore) {
winnerText = 'White Wins!';
} else {
winnerText = "It's a Tie!";
}
document.getElementById('winner-text').textContent = winnerText;
document.getElementById('final-score').textContent = `Black: ${blackScore} - White: ${whiteScore}`;
document.getElementById('overlay').classList.add('show');
document.getElementById('game-over').classList.add('show');
}
// Reset the game
function resetGame() {
document.getElementById('overlay').classList.remove('show');
document.getElementById('game-over').classList.remove('show');
initGame();
}
// Start the game when page loads
initGame();
</script>
</body>
</html>
Features Included:
✅ 8x8 Game Board - Standard Othello grid with green cells
✅ Starting Position - 2 black and 2 white pieces in the center
✅ Click-to-Place - Simply click on valid cells to place pieces
✅ Valid Move Detection - Only allows legal moves that outflank opponent pieces
✅ Automatic Flipping - All captured pieces flip automatically with animation
✅ Turn Switching - Alternates between black and white players
✅ Turn Display - Shows current player with visual indicator
✅ Score Tracking - Live count of each player's pieces
✅ Game-Over Detection - Detects when neither player can move
✅ Final Score Display - Shows winner and final scores in a modal
✅ Visual Feedback - Animations for piece placement and valid moves
✅ New Game Button - Restart anytime
How to Use:
- Save the code as an
.htmlfile - Open it in any modern web browser
- Black plays first - click on any highlighted valid move
- The game automatically handles all Othello rules
- When the game ends, you'll see the final scores and winner
The game is fully self-contained with no external dependencies!
Comments