黑白棋
2025-11-27
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>黑白棋</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
font-family: 'Arial', 'Helvetica';
}
.game-container {
position: static;
display: block;
margin: 10px auto;
}
.tile-container {
margin: auto;
position: absolute;
border: solid 1px #477757;
}
.tile-background {
background-color: #42b964;
width: 60px;
height: 60px;
border: solid 1px #376747;
position: absolute;
}
.dot {
border-radius: 50%;
width: 10px;
height: 10px;
position: absolute;
background-color: #175727;
}
.black-piece,
.white-piece {
width: 50px;
height: 50px;
margin: 5px;
border-radius: 50%;
border: solid 1px #477757;
}
.white-piece {
background-color: white;
}
.black-piece {
background-color: black;
}
.score-board {
display: inline-block;
margin: 10px auto;
width: 75px;
border: solid 1px #175727;
padding: 5px;
text-align: center;
}
.score-container {
display: flex;
flex-direction: row;
}
.result {
font-size: 20px;
font-weight: bold;
margin: 23px;
}
</style>
</head>
<body>
<div class="game-container">
<div class="score-container">
<div class="score-board">
黑棋
<div class="score-black">2</div>
</div>
<div class="result"></div>
<div class="score-board">
白棋
<div class="score-white">2</div>
</div>
</div>
<div class="tile-container"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script>
var gameArea,
dim = 8,
tileWidth = 62,
game;
$(function () {
gameArea = $('.tile-container');
gameArea.css('width', dim * tileWidth + 'px');
gameArea.css('height', dim * tileWidth + 'px');
$('.game-container').css('width', dim * tileWidth + 'px');
game = new Othello();
});
// constructor sets the board and model
function Othello() {
this.computer = -1;
this.turn = -1;
this.whiteScore = 2;
this.blackScore = 2;
this.state = new Array(dim);
this.badMoves = [
{ i: 1, j: 1 },
{ i: 6, j: 6 },
{ i: 1, j: 6 },
{ i: 6, j: 1 },
];
this.preferedMoves = [
{ i: 0, j: 0 },
{ i: 7, j: 7 },
{ i: 0, j: 7 },
{ i: 7, j: 0 },
];
/* {i: 0, j: 1},
{i: 1, j: 0},
{i: 0, j: 6},
{i: 1, j: 7},
{i: 6, j: 0},
{i: 7, j: 1},
{i: 7, j: 6},
{i: 6, j: 7}*/
for (var i = 0; i < dim; i++) {
this.state[i] = new Array(dim);
for (var j = 0; j < dim; j++) {
this.state[i][j] = new Tile(i, j);
}
}
// Add dots
for (var i = 2; i < 7; i += 4) {
for (var j = 2; j < 7; j += 4) {
var dot = $('<div>', {
class: 'dot',
});
dot.css('top', i * tileWidth - 5 + 'px');
dot.css('left', j * tileWidth - 5 + 'px');
gameArea.append(dot);
}
}
// Add 4 starting pieces
// i = 3, j = 3
this.state[3][3].setTileBlack();
// i = 4, j = 4
this.state[4][4].setTileBlack();
// i = 3, j = 4
this.state[3][4].setTileWhite();
// i = 4, j = 3
this.state[4][3].setTileWhite();
if (this.turn == this.computer) this.computerMove();
}
Othello.prototype.isValidMove = function (i, j, state, turn) {
if (state == undefined) state = this.state;
if (turn == undefined) turn = this.turn;
i = parseInt(i);
j = parseInt(j);
if (state[i][j].value != 0) return false;
var count;
for (var k = -1; k < 2; k++) {
for (var l = -1; l < 2; l++) {
count = 1;
while (withinBounds(i + count * k, j + count * l)) {
if (state[i + count * k][j + count * l].value == -1 * turn) {
count++;
} else break;
}
if (withinBounds(i + count * k, j + count * l)) {
if (
(count > 1) &
(state[i + count * k][j + count * l].value == turn)
)
return true;
}
}
}
return false;
};
function withinBounds(i, j) {
return (i >= 0) & (j >= 0) & (i < dim) & (j < dim);
}
Othello.prototype.makeMove = function (i, j) {
i = parseInt(i);
j = parseInt(j);
var count;
for (var k = -1; k < 2; k++) {
for (var l = -1; l < 2; l++) {
count = 1;
while (withinBounds(i + count * k, j + count * l)) {
if (
this.state[i + count * k][j + count * l].value ==
-1 * this.turn
) {
count++;
} else break;
}
if (withinBounds(i + count * k, j + count * l)) {
if (
(count > 1) &
(this.state[i + count * k][j + count * l].value == this.turn)
) {
count = 1;
while (
this.state[i + count * k][j + count * l].value ==
-1 * this.turn
) {
this.setColor(i + count * k, j + count * l, 1);
count++;
}
}
}
}
}
this.setColor(i, j, 0);
this.turn *= -1;
var moves = this.movesAvailable();
console.log(moves.length);
if (moves.length == 0) {
console.log('no moves');
this.turn *= -1;
moves = this.movesAvailable();
if (moves.length == 0) {
console.log('no moves for opponent');
this.gameOver();
return;
}
}
if (this.turn == this.computer) {
setTimeout(function () {
game.computerMove();
}, 500);
}
};
Othello.prototype.gameOver = function () {
if (this.blackScore > this.whiteScore) $('.result').text('黑棋获胜!');
else if (this.blackScore < this.whiteScore)
$('.result').text('白棋获胜!');
else $('.result').text('平局!');
};
Othello.prototype.movesAvailable = function (state, turn) {
if (state == undefined) state = this.state;
if (turn == undefined) turn = this.turn;
var moves = [];
for (var i = 0; i < dim; i++) {
for (var j = 0; j < dim; j++) {
if (this.isValidMove(i, j, state, turn)) {
moves.push({ i: i, j: j, enemyMoves: -1, disaster: false });
}
}
}
return moves;
};
Othello.prototype.setColor = function (i, j, flip) {
if (this.turn > 0) {
this.state[i][j].setTileWhite();
this.whiteScore++;
if (flip) this.blackScore--;
} else if (this.turn < 0) {
this.state[i][j].setTileBlack();
this.blackScore++;
if (flip) this.whiteScore--;
}
$('.score-black').text(this.blackScore);
$('.score-white').text(this.whiteScore);
};
Othello.prototype.computerMove = function () {
var moves = this.movesAvailable(),
enemy = -1 * this.turn,
model,
count;
// simulate each move and count the moves available to the other person
for (var k = 0; k < moves.length; k++) {
model = copyBoard(this.state);
makeFakeMove(model, this.turn, moves[k].i, moves[k].j);
var opponentMoves = this.movesAvailable(model, enemy);
var modelNext,
movesNext,
minMoves = 64;
// make all possible enemy moves
for (var l = 0; l < opponentMoves.length; l++) {
modelNext = copyBoard(model);
// make the move
makeFakeMove(
modelNext,
enemy,
opponentMoves[l].i,
opponentMoves[l].j
);
// count how many of my own moves I get
movesNext = this.movesAvailable(modelNext, this.turn);
// find who has minimum
minMoves = 64;
count = movesNext.length;
for (var m = 0; m < movesNext.length; m++) {
if (this.checkBadMove(movesNext[m].i, movesNext[m].j))
count = count - 2;
if (this.checkPreferedMove(movesNext[m].i, movesNext[m].j))
count += 10;
}
if (minMoves > count) minMoves = count;
if (this.checkPreferedMove(opponentMoves[l].i, opponentMoves[l].j))
moves[k].disaster = true;
}
moves[k].enemyMoves = minMoves;
}
moves.sort(moveSort);
//console.log(moves);
for (var k = 0; k < moves.length; k++) {
if (this.checkPreferedMove(moves[k].i, moves[k].j)) {
this.makeMove(moves[k].i, moves[k].j);
return;
}
}
// Must check for bad moves
for (count = 0; count < moves.length - 1; count++) {
if (
!moves[count].disaster &
!this.checkBadMove(moves[count].i, moves[count].j)
)
break;
}
this.makeMove(moves[count].i, moves[count].j);
};
Othello.prototype.checkBadMove = function (i, j) {
for (var k = 0; k < this.badMoves.length; k++) {
if ((this.badMoves[k].i == i) & (this.badMoves[k].j == j))
return true;
}
return false;
};
Othello.prototype.checkPreferedMove = function (i, j) {
for (var k = 0; k < this.preferedMoves.length; k++) {
if ((this.preferedMoves[k].i == i) & (this.preferedMoves[k].j == j))
return true;
}
return false;
};
function moveSort(a, b) {
if (a.enemyMoves < b.enemyMoves) return 1;
else if (a.enemyMoves > b.enemyMoves) return -1;
else {
if (a.i < b.i) return -1;
if (a.i > b.i) return 1;
else {
if (a.j < b.j) return -1;
if (a.j > b.j) return 1;
else return 0;
}
}
}
function makeFakeMove(state, turn, i, j) {
var count;
for (var k = -1; k < 2; k++) {
for (var l = -1; l < 2; l++) {
count = 1;
while (withinBounds(i + count * k, j + count * l)) {
if (state[i + count * k][j + count * l].value == -1 * turn) {
count++;
} else break;
}
if (withinBounds(i + count * k, j + count * l)) {
if (
(count > 1) &
(state[i + count * k][j + count * l].value == turn)
) {
count = 1;
while (state[i + count * k][j + count * l].value == -1 * turn) {
state[i + count * k][j + count * l].value = turn;
count++;
}
}
}
}
}
state[i][j].value = turn;
}
function copyBoard(state) {
var model = new Array(dim);
for (var i = 0; i < dim; i++) {
model[i] = new Array(dim);
for (var j = 0; j < dim; j++) {
model[i][j] = { value: state[i][j].value };
}
}
return model;
}
function Tile(i, j) {
this.value = 0;
this.i = i;
this.j = j;
this.view = createTile(i, j);
}
// 1: White
// -1: Black
// 0: Empty
Tile.prototype.setTileBlack = function () {
if (this.value == 1) this.view.removeClass('white-piece');
this.value = -1;
this.view.addClass('black-piece');
};
Tile.prototype.setTileWhite = function () {
if (this.value == -1) this.view.removeClass('black-piece');
this.value = 1;
this.view.addClass('white-piece');
};
function createTile(i, j) {
var tileBg = $('<div>', {
class: 'tile-background',
i: i,
j: j,
});
tileBg.css('top', i * tileWidth + 'px');
tileBg.css('left', j * tileWidth + 'px');
tileBg.on('click', function () {
if (game.isValidMove($(this).attr('i'), $(this).attr('j')))
game.makeMove($(this).attr('i'), $(this).attr('j'));
});
tileBg
.mouseenter(function () {
if (game.isValidMove($(this).attr('i'), $(this).attr('j')))
tileBg.css('background-color', '#62e984');
})
.mouseleave(function () {
tileBg.css('background-color', '#42b964');
});
var tile = $('<div>', {
class: 'tile',
});
tileBg.append(tile);
gameArea.append(tileBg);
return tile;
}
</script>
</body>
</html>
