Reversi/Othello
Reversi/Othello
Black (You): 2
White (Computer): 2
Your turn (Black)
Computer thinking...
reversi.php
PHP
A fully functional Reversi/Othello game that users can play against the computer.
<?php
/**
* Plugin Name: Reversi / Othello Game
* Plugin URI: https://it-breeze.info/
* Description: A fully functional Reversi/Othello game that users can play against the computer. Use shortcode [reversi] to display the game.
* Version: 2.3.0
* Author: Mike Vahldieck
* Author URI: https://it-breeze.info/
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
<?php
/**
* Plugin Name: Reversi / Othello Game
* Plugin URI: https://it-breeze.info/
* Description: A fully functional Reversi/Othello game that users can play against the computer. Use shortcode [reversi] to display the game.
* Version: 2.3.0
* Author: Mike Vahldieck
* Author URI: https://it-breeze.info/
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: reversi
* Domain Path: /languages
*/
if (!defined('ABSPATH')) {
exit;
}
class ReversiOthelloPlugin {
public function __construct() {
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
add_shortcode('reversi', array($this, 'display_game'));
add_action('admin_menu', array($this, 'add_admin_menu'));
}
public function enqueue_scripts() {
global $post;
if (is_a($post, 'WP_Post') && has_shortcode($post->post_content, 'reversi')) {
add_action('wp_footer', array($this, 'add_inline_assets'));
}
}
public function add_inline_assets() {
?>
<style>
.reversi-game-container {
font-family: Arial, sans-serif;
background: linear-gradient(135deg, #ffffff 0%, #ffffff 100%);
padding: 20px;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
margin: 20px auto;
max-width: 600px;
}
.reversi-game-wrapper {
background: rgba(255, 255, 255, 0.95);
border-radius: 15px;
padding: 20px;
text-align: center;
}
.reversi-title {
color: #333;
margin-bottom: 20px;
font-size: 24px;
}
.reversi-game-info {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
padding: 10px;
background: #f0f0f0;
border-radius: 8px;
}
.reversi-player-info {
font-weight: bold;
font-size: 16px;
}
.reversi-current-turn {
margin-bottom: 15px;
font-size: 18px;
font-weight: bold;
color: #126724;
}
.reversi-board {
display: inline-grid;
grid-template-columns: repeat(8, 50px);
grid-template-rows: repeat(8, 50px);
gap: 2px;
background: #4CAF50;
padding: 10px;
border-radius: 10px;
margin: 20px auto;
}
.reversi-cell {
width: 50px;
height: 50px;
background: #8BC34A;
border: 1px solid #4CAF50;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
border-radius: 4px;
transition: background-color 0.2s;
}
.reversi-cell:hover {
background: #9CCC65;
}
.reversi-cell.reversi-valid {
background: #CDDC39;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.3);
}
.reversi-piece {
width: 40px;
height: 40px;
border-radius: 50%;
border: 2px solid #333;
transition: transform 0.3s ease;
}
.reversi-piece.reversi-black {
background: #333;
}
.reversi-piece.reversi-white {
background: #fff;
}
.reversi-controls {
margin-top: 20px;
}
.reversi-btn {
background: #126724;
color: white;
border: none;
padding: 10px 20px;
font-size: 14px;
border-radius: 5px;
cursor: pointer;
margin: 0 5px;
transition: background-color 0.3s;
}
.reversi-btn:hover {
background: #126724;
}
.reversi-game-over {
background: #FFC107;
padding: 15px;
border-radius: 8px;
margin-top: 15px;
font-size: 18px;
font-weight: bold;
}
.reversi-thinking {
color: #FF9800;
font-style: italic;
margin-top: 10px;
}
@media (max-width: 600px) {
.reversi-board {
grid-template-columns: repeat(8, 40px);
grid-template-rows: repeat(8, 40px);
}
.reversi-cell {
width: 40px;
height: 40px;
}
.reversi-piece {
width: 32px;
height: 32px;
}
.reversi-game-info {
flex-direction: column;
gap: 10px;
}
}
</style>
<script>
(function() {
"use strict";
const REVERSI_EMPTY = 0;
const REVERSI_BLACK = 1;
const REVERSI_WHITE = 2;
let reversiBoard = [];
let reversiCurrentPlayer = REVERSI_BLACK;
let reversiGameEnded = false;
function reversiInitializeGame() {
reversiBoard = [];
for (let i = 0; i < 8; i++) {
reversiBoard[i] = [];
for (let j = 0; j < 8; j++) {
reversiBoard[i][j] = REVERSI_EMPTY;
}
}
reversiBoard[3][3] = REVERSI_WHITE;
reversiBoard[3][4] = REVERSI_BLACK;
reversiBoard[4][3] = REVERSI_BLACK;
reversiBoard[4][4] = REVERSI_WHITE;
reversiCurrentPlayer = REVERSI_BLACK;
reversiGameEnded = false;
reversiUpdateDisplay();
}
function reversiCreateBoard() {
const boardElement = document.getElementById("reversi-game-board");
if (!boardElement) return;
boardElement.innerHTML = "";
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
const cell = document.createElement("div");
cell.className = "reversi-cell";
cell.onclick = function() { reversiHandleCellClick(row, col); };
cell.id = "reversi-cell-" + row + "-" + col;
boardElement.appendChild(cell);
}
}
}
function reversiUpdateDisplay() {
reversiCreateBoard();
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
const cell = document.getElementById("reversi-cell-" + row + "-" + col);
if (!cell) continue;
if (reversiBoard[row][col] !== REVERSI_EMPTY) {
const piece = document.createElement("div");
piece.className = "reversi-piece " + (reversiBoard[row][col] === REVERSI_BLACK ? "reversi-black" : "reversi-white");
cell.appendChild(piece);
} else if (reversiCurrentPlayer === REVERSI_BLACK && reversiIsValidMove(row, col, REVERSI_BLACK)) {
cell.classList.add("reversi-valid");
}
}
}
reversiUpdateScores();
reversiUpdateTurnInfo();
if (reversiIsGameOver()) {
reversiEndGame();
}
}
function reversiIsValidMove(row, col, player) {
if (reversiBoard[row][col] !== REVERSI_EMPTY) return false;
const directions = [
[-1, -1], [-1, 0], [-1, 1],
[0, -1], [0, 1],
[1, -1], [1, 0], [1, 1]
];
for (let i = 0; i < directions.length; i++) {
let dr = directions[i][0];
let dc = directions[i][1];
if (reversiWouldFlipInDirection(row, col, dr, dc, player)) {
return true;
}
}
return false;
}
function reversiWouldFlipInDirection(row, col, dr, dc, player) {
let r = row + dr;
let c = col + dc;
let foundOpponent = false;
while (r >= 0 && r < 8 && c >= 0 && c < 8) {
if (reversiBoard[r][c] === REVERSI_EMPTY) {
return false;
}
if (reversiBoard[r][c] === player) {
return foundOpponent;
}
foundOpponent = true;
r += dr;
c += dc;
}
return false;
}
function reversiMakeMove(row, col, player) {
if (!reversiIsValidMove(row, col, player)) return false;
reversiBoard[row][col] = player;
const directions = [
[-1, -1], [-1, 0], [-1, 1],
[0, -1], [0, 1],
[1, -1], [1, 0], [1, 1]
];
for (let i = 0; i < directions.length; i++) {
let dr = directions[i][0];
let dc = directions[i][1];
if (reversiWouldFlipInDirection(row, col, dr, dc, player)) {
reversiFlipInDirection(row, col, dr, dc, player);
}
}
return true;
}
function reversiFlipInDirection(row, col, dr, dc, player) {
let r = row + dr;
let c = col + dc;
while (r >= 0 && r < 8 && c >= 0 && c < 8 && reversiBoard[r][c] !== player) {
reversiBoard[r][c] = player;
r += dr;
c += dc;
}
}
function reversiHandleCellClick(row, col) {
if (reversiGameEnded || reversiCurrentPlayer !== REVERSI_BLACK) return;
if (reversiMakeMove(row, col, REVERSI_BLACK)) {
reversiCurrentPlayer = REVERSI_WHITE;
reversiUpdateDisplay();
if (!reversiIsGameOver()) {
setTimeout(reversiComputerMove, 1000);
}
}
}
function reversiComputerMove() {
if (reversiGameEnded || reversiCurrentPlayer !== REVERSI_WHITE) return;
const thinkingElement = document.getElementById("reversi-thinking");
if (thinkingElement) {
thinkingElement.style.display = "block";
}
setTimeout(function() {
const validMoves = reversiGetValidMoves(REVERSI_WHITE);
if (validMoves.length > 0) {
const randomMove = validMoves[Math.floor(Math.random() * validMoves.length)];
reversiMakeMove(randomMove.row, randomMove.col, REVERSI_WHITE);
}
reversiCurrentPlayer = REVERSI_BLACK;
if (thinkingElement) {
thinkingElement.style.display = "none";
}
if (reversiGetValidMoves(REVERSI_BLACK).length === 0 && !reversiIsGameOver()) {
reversiCurrentPlayer = REVERSI_WHITE;
setTimeout(reversiComputerMove, 500);
}
reversiUpdateDisplay();
}, 800);
}
function reversiGetValidMoves(player) {
const moves = [];
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
if (reversiIsValidMove(row, col, player)) {
moves.push({row: row, col: col});
}
}
}
return moves;
}
function reversiUpdateScores() {
let blackCount = 0;
let whiteCount = 0;
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
if (reversiBoard[row][col] === REVERSI_BLACK) blackCount++;
if (reversiBoard[row][col] === REVERSI_WHITE) whiteCount++;
}
}
const blackScore = document.getElementById("reversi-black-score");
const whiteScore = document.getElementById("reversi-white-score");
if (blackScore) blackScore.textContent = blackCount;
if (whiteScore) whiteScore.textContent = whiteCount;
}
function reversiUpdateTurnInfo() {
const turnInfo = document.getElementById("reversi-turn-info");
if (!turnInfo) return;
if (reversiCurrentPlayer === REVERSI_BLACK) {
turnInfo.textContent = "Your turn (Black)";
} else {
turnInfo.textContent = "Computer's turn (White)";
}
}
function reversiIsGameOver() {
return reversiGetValidMoves(REVERSI_BLACK).length === 0 && reversiGetValidMoves(REVERSI_WHITE).length === 0;
}
function reversiEndGame() {
reversiGameEnded = true;
let blackCount = 0;
let whiteCount = 0;
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
if (reversiBoard[row][col] === REVERSI_BLACK) blackCount++;
if (reversiBoard[row][col] === REVERSI_WHITE) whiteCount++;
}
}
const resultElement = document.getElementById("reversi-game-result");
if (!resultElement) return;
let message;
if (blackCount > whiteCount) {
message = "You won! " + blackCount + " - " + whiteCount;
} else if (whiteCount > blackCount) {
message = "Computer won! " + whiteCount + " - " + blackCount;
} else {
message = "It's a tie! " + blackCount + " - " + whiteCount;
}
resultElement.textContent = message;
resultElement.style.display = "block";
const turnInfo = document.getElementById("reversi-turn-info");
if (turnInfo) {
turnInfo.textContent = "Game Over";
}
}
window.reversiStartNewGame = function() {
const resultElement = document.getElementById("reversi-game-result");
const thinkingElement = document.getElementById("reversi-thinking");
if (resultElement) resultElement.style.display = "none";
if (thinkingElement) thinkingElement.style.display = "none";
reversiInitializeGame();
};
window.reversiShowHelp = function() {
alert("How to play Reversi/Othello:\n\n1. Players alternate turns placing pieces\n2. You must place a piece that traps opponent pieces between your new piece and an existing piece of yours\n3. All trapped pieces flip to your color\n4. Valid moves are highlighted in light green\n5. If you have no valid moves, your turn is skipped\n6. Game ends when no player can move\n7. Player with most pieces wins!\n\nYou are Black, Computer is White.\nClick on highlighted squares to make your move.");
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
if (document.getElementById("reversi-game-board")) {
reversiInitializeGame();
}
});
} else {
if (document.getElementById("reversi-game-board")) {
reversiInitializeGame();
}
}
})();
</script>
<?php
}
public function display_game($atts) {
$atts = shortcode_atts(array(
'width' => '100%',
'height' => 'auto'
), $atts, 'reversi');
$game_id = 'reversi-game-' . rand(1000, 9999);
ob_start();
?>
<div class="reversi-game-container" style="width: <?php echo esc_attr($atts['width']); ?>; height: <?php echo esc_attr($atts['height']); ?>;">
<div class="reversi-game-wrapper">
<h3 class="reversi-title">Reversi/Othello</h3>
<div class="reversi-game-info">
<div class="reversi-player-info">
<span>Black (You): <span id="reversi-black-score">2</span></span>
</div>
<div class="reversi-player-info">
<span>White (Computer): <span id="reversi-white-score">2</span></span>
</div>
</div>
<div id="reversi-turn-info" class="reversi-current-turn">Your turn (Black)</div>
<div id="reversi-thinking" class="reversi-thinking" style="display: none;">Computer thinking...</div>
<div class="reversi-board" id="reversi-game-board"></div>
<div class="reversi-controls">
<button class="reversi-btn" onclick="reversiStartNewGame()">New Game</button>
<button class="reversi-btn" onclick="reversiShowHelp()">Help</button>
</div>
<div id="reversi-game-result" class="reversi-game-over" style="display: none;"></div>
</div>
</div>
<?php
return ob_get_clean();
}
public function add_admin_menu() {
add_options_page(
'Reversi Game Settings',
'Reversi Game',
'manage_options',
'reversi-settings',
array($this, 'admin_page')
);
}
public function admin_page() {
?>
<div class="wrap">
<h1>Reversi/Othello Game Settings</h1>
<div class="card">
<h2>How to Use</h2>
<p>To display the Reversi/Othello game on any post or page, use the following shortcode:</p>
<code>[reversi]</code>
<h3>Shortcode Options</h3>
<ul>
<li><strong>width</strong> - Set the game container width (default: 100%)</li>
<li><strong>height</strong> - Set the game container height (default: auto)</li>
</ul>
<h3>Examples</h3>
<p><code>[reversi width="600px"]</code></p>
<p><code>[reversi width="100%" height="800px"]</code></p>
</div>
<div class="card">
<h2>Game Rules</h2>
<ol>
<li>Players alternate turns placing pieces on the board</li>
<li>You must place a piece that traps opponent pieces between your new piece and an existing piece</li>
<li>All trapped pieces flip to your color</li>
<li>If you have no valid moves, your turn is skipped</li>
<li>Game ends when no player can move</li>
<li>Player with the most pieces wins!</li>
</ol>
</div>
</div>
<?php
}
}
new ReversiOthelloPlugin();
if (is_admin()) {
add_filter('plugin_action_links_' . plugin_basename(__FILE__), function($links) {
$settings_link = '<a href="' . admin_url('options-general.php?page=reversi-settings') . '">Settings</a>';
array_unshift($links, $settings_link);
return $links;
});
}
if (class_exists('Custom_Plugin_Updater')) {
new Custom_Plugin_Updater(
__FILE__,
'reversi',
'https://it-breeze.cloud/data/reversi/reversi.json'
);
}