184 lines
5.2 KiB
JavaScript
184 lines
5.2 KiB
JavaScript
export const standardMap = [
|
|
"--------",
|
|
"--------",
|
|
"--------",
|
|
"---wb---",
|
|
"---bw---",
|
|
"--------",
|
|
"--------",
|
|
"--------"
|
|
];
|
|
export class ReversiEngine {
|
|
map;
|
|
mapWidth;
|
|
mapHeight;
|
|
board;
|
|
turn = true;
|
|
prevColor = null;
|
|
opts;
|
|
constructor(map, opts){
|
|
this.opts = {
|
|
isLlotheo: opts.isLlotheo ?? false,
|
|
canPutEverywhere: opts.canPutEverywhere ?? false,
|
|
loopedBoard: opts.loopedBoard ?? false
|
|
};
|
|
this.mapWidth = map[0]?.length ?? 0;
|
|
this.mapHeight = map.length;
|
|
const data = map.join("");
|
|
this.board = data.split("").map((d)=>d === "-" ? null : d === "b" ? true : d === "w" ? false : undefined);
|
|
this.map = data.split("").map((d)=>d === "-" || d === "b" || d === "w" ? "empty" : "null");
|
|
if (!this.canPutSomewhere(true)) this.turn = this.canPutSomewhere(false) ? false : null;
|
|
}
|
|
get blackCount() {
|
|
return this.board.filter((x)=>x === true).length;
|
|
}
|
|
get whiteCount() {
|
|
return this.board.filter((x)=>x === false).length;
|
|
}
|
|
posToXy(pos) {
|
|
return [
|
|
pos % this.mapWidth,
|
|
Math.floor(pos / this.mapWidth)
|
|
];
|
|
}
|
|
xyToPos(x, y) {
|
|
return x + y * this.mapWidth;
|
|
}
|
|
mapDataGet(pos) {
|
|
const [x, y] = this.posToXy(pos);
|
|
return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? "null" : this.map[pos];
|
|
}
|
|
getPuttablePlaces(color) {
|
|
return Array.from(this.board.keys()).filter((i)=>this.canPut(color, i));
|
|
}
|
|
canPutSomewhere(color) {
|
|
return this.getPuttablePlaces(color).length > 0;
|
|
}
|
|
canPut(color, pos) {
|
|
if (this.board[pos] !== null) return false;
|
|
if (this.opts.canPutEverywhere) return this.mapDataGet(pos) === "empty";
|
|
return this.effects(color, pos).length !== 0;
|
|
}
|
|
effects(color, initPos) {
|
|
const enemyColor = !color;
|
|
const diffVectors = [
|
|
[
|
|
0,
|
|
-1
|
|
],
|
|
[
|
|
1,
|
|
-1
|
|
],
|
|
[
|
|
1,
|
|
0
|
|
],
|
|
[
|
|
1,
|
|
1
|
|
],
|
|
[
|
|
0,
|
|
1
|
|
],
|
|
[
|
|
-1,
|
|
1
|
|
],
|
|
[
|
|
-1,
|
|
0
|
|
],
|
|
[
|
|
-1,
|
|
-1
|
|
]
|
|
];
|
|
return diffVectors.flatMap(([dx, dy])=>{
|
|
const found = [];
|
|
let [x, y] = this.posToXy(initPos);
|
|
while(true){
|
|
x += dx;
|
|
y += dy;
|
|
if (this.opts.loopedBoard && this.xyToPos(x = (x % this.mapWidth + this.mapWidth) % this.mapWidth, y = (y % this.mapHeight + this.mapHeight) % this.mapHeight) === initPos) {
|
|
return found;
|
|
}
|
|
if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) return [];
|
|
const pos = this.xyToPos(x, y);
|
|
if (this.mapDataGet(pos) === "null") return [];
|
|
const stone = this.board[pos];
|
|
if (stone === null) return [];
|
|
if (stone === enemyColor) found.push(pos);
|
|
if (stone === color) return found;
|
|
}
|
|
});
|
|
}
|
|
putStone(pos) {
|
|
const color = this.turn;
|
|
if (color == null) return;
|
|
this.board[pos] = color;
|
|
for (const effect of this.effects(color, pos)){
|
|
this.board[effect] = color;
|
|
}
|
|
this.prevColor = color;
|
|
this.turn = this.canPutSomewhere(!color) ? !color : this.canPutSomewhere(color) ? color : null;
|
|
}
|
|
get isEnded() {
|
|
return this.turn == null;
|
|
}
|
|
get winner() {
|
|
if (!this.isEnded) return null;
|
|
if (this.blackCount === this.whiteCount) return null;
|
|
return this.opts.isLlotheo === this.blackCount > this.whiteCount ? false : true;
|
|
}
|
|
calcCrc32() {
|
|
let hash = 0;
|
|
const text = JSON.stringify({
|
|
board: this.board,
|
|
turn: this.turn
|
|
});
|
|
for(let i = 0; i < text.length; i++){
|
|
hash = hash * 31 + text.charCodeAt(i) | 0;
|
|
}
|
|
return hash.toString();
|
|
}
|
|
}
|
|
export function serializeLogs(logs) {
|
|
const serialized = [];
|
|
for(let i = 0; i < logs.length; i++){
|
|
const log = logs[i];
|
|
const timeDelta = i === 0 ? log.time : log.time - logs[i - 1].time;
|
|
serialized.push([
|
|
timeDelta,
|
|
log.player ? 1 : 0,
|
|
0,
|
|
log.pos
|
|
]);
|
|
}
|
|
return serialized;
|
|
}
|
|
export function deserializeLogs(logs) {
|
|
const deserialized = [];
|
|
let time = 0;
|
|
for (const log of logs){
|
|
time += log[0];
|
|
if (log[2] === 0) {
|
|
deserialized.push({
|
|
time,
|
|
player: log[1] === 1,
|
|
operation: "put",
|
|
pos: log[3]
|
|
});
|
|
}
|
|
}
|
|
return deserialized;
|
|
}
|
|
export function restoreGame(env) {
|
|
const game = new ReversiEngine(env.map, env);
|
|
for (const log of deserializeLogs(env.logs)){
|
|
if (log.operation === "put") game.putStone(log.pos);
|
|
}
|
|
return game;
|
|
}
|