koishi-plugin-chess
Version:
Playing chess games in Koishi
206 lines (205 loc) • 7.32 kB
JavaScript
"use strict";
/* global BigInt */
Object.defineProperty(exports, "__esModule", { value: true });
exports.State = exports.MoveResult = void 0;
const numbers = '①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳';
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var MoveResult;
(function (MoveResult) {
MoveResult[MoveResult["p1Win"] = 1] = "p1Win";
MoveResult[MoveResult["p2Win"] = -1] = "p2Win";
MoveResult[MoveResult["draw"] = -2] = "draw";
MoveResult[MoveResult["skip"] = 2] = "skip";
MoveResult[MoveResult["illegal"] = 3] = "illegal";
})(MoveResult || (exports.MoveResult = MoveResult = {}));
class State {
rule;
size;
placement;
ctx;
p1;
p2;
next;
bBoard = 0n;
wBoard = 0n;
imageMode = true;
history = [];
area;
full;
update;
constructor(rule, size, placement, ctx) {
this.rule = rule;
this.size = size;
this.placement = placement;
this.ctx = ctx;
this.area = BigInt(size * size);
this.full = (1n << this.area) - 1n;
}
get pBoard() {
return this.next === this.p2 ? this.wBoard : this.bBoard;
}
set pBoard(value) {
this.next === this.p2 ? this.wBoard = value : this.bBoard = value;
}
get nBoard() {
return this.next === this.p2 ? this.bBoard : this.wBoard;
}
set nBoard(value) {
this.next === this.p2 ? this.bBoard = value : this.wBoard = value;
}
get isFull() {
return !((this.bBoard | this.wBoard) ^ this.full);
}
bit(x, y) {
return 1n << BigInt(x * this.size + y);
}
inRange(x, y) {
return x >= 0 && y >= 0 && x < this.size && y < this.size;
}
get(x, y) {
if (!this.inRange(x, y))
return 0;
const p = 1n << BigInt(x * this.size + y);
if (p & this.bBoard)
return 1;
if (p & this.wBoard)
return -1;
return 0;
}
drawSvg(x, y) {
const { SVG } = require('koishi-plugin-puppeteer');
const { size, placement } = this;
const viewSize = size + (placement === 'cross' ? 2 : 3);
const svg = new SVG({ viewSize, size: Math.max(512, viewSize * 32) }).fill('white');
const lineGroup = svg.g({
stroke: 'black',
strokeWidth: 0.08,
strokeLinecap: 'round',
});
const textGroup = svg.g({
fontSize: '0.75',
fontWeight: 'normal',
style: 'font-family: Sans; letter-spacing: 0',
});
const topTextGroup = textGroup.g({ textAnchor: 'middle' });
const leftTextGroup = textGroup.g({ textAnchor: 'right' });
const maskGroup = svg.g({ fill: 'white' });
const blackGroup = svg.g({ fill: 'black' });
const whiteGroup = svg.g({
fill: 'white',
stroke: 'black',
strokeWidth: 0.08,
});
const verticalOffset = placement === 'cross' ? 0.3 : 0.8;
const horizontalOffset = placement === 'cross' ? 0 : 0.5;
for (let index = 2; index < viewSize; ++index) {
lineGroup.line(index, 2, index, viewSize - 1);
lineGroup.line(2, index, viewSize - 1, index);
if (index < size + 2) {
topTextGroup.text(String(index - 1), index + horizontalOffset, 1.3);
leftTextGroup.text(String.fromCharCode(index + 63), 0.8, index + verticalOffset);
}
}
for (let i = 0; i < size; i += 1) {
for (let j = 0; j < size; j += 1) {
const value = this.get(i, j);
if (!value) {
if (size >= 13 && size % 2 === 1
&& (i === 3 || i === size - 4 || i * 2 === size - 1)
&& (j === 3 || j === size - 4 || j * 2 === size - 1)) {
lineGroup.circle(j + 2, i + 2, 0.08);
}
continue;
}
let offset = 2.5;
if (placement === 'cross') {
maskGroup.rect(j + 1.48, i + 1.48, j + 2.52, i + 2.52);
offset = 2;
}
const whiteMark = 0.08;
const blackMark = 0.12;
const cx = j + offset;
const cy = i + offset;
if (value === 1) {
blackGroup.circle(cx, cy, 0.36);
if (x === i && y === j) {
blackGroup.rect(cx - blackMark, cy - blackMark, cx + blackMark, cy + blackMark, { fill: 'white' });
}
}
else {
whiteGroup.circle(cx, cy, 0.32);
if (x === i && y === j) {
whiteGroup.rect(cx - whiteMark, cy - whiteMark, cx + whiteMark, cy + whiteMark, { fill: 'black' });
}
}
}
}
return svg;
}
drawText(x, y) {
const max = this.size - 1;
let output = ' ' + numbers.slice(0, this.size);
for (let i = 0; i < this.size; i += 1) {
output += '\n' + alphabet[i];
for (let j = 0; j < this.size; j += 1) {
const value = this.get(i, j);
output += value === 1 ? x === i && y === j ? '▲' : '●'
: value === -1 ? x === i && y === j ? '△' : '○'
: i === 0 ? j === 0 ? '┌' : j === max ? '┐' : '┬'
: i === max ? j === 0 ? '└' : j === max ? '┘' : '┴'
: j === 0 ? '├' : j === max ? '┤' : '┼';
}
}
return output;
}
async draw(session, message = '', x, y) {
if (message)
message += '\n';
if (this.imageMode && session.app.puppeteer) {
message += await this.drawSvg(x, y).render(this.ctx);
}
else {
message += this.drawText(x, y);
}
await session.send(message);
}
set(x, y, value) {
const chess = this.bit(x, y);
let board = 0n;
if (value === 1) {
this.wBoard &= ~chess;
board = this.bBoard |= chess;
}
else if (value === -1) {
this.bBoard &= ~chess;
board = this.wBoard |= chess;
}
else {
this.wBoard &= ~chess;
this.bBoard &= ~chess;
}
return board;
}
save() {
this.history.push((this.wBoard << this.area) + this.bBoard);
}
refresh() {
const board = this.history[this.history.length - 1];
this.wBoard = board >> this.area;
this.bBoard = board & this.full;
}
serial() {
const { rule, size, placement, p1, p2, next, history } = this;
return { rule, size, placement, p1, p2, next, history: history.join(',') };
}
static from(data, ctx) {
const state = new State(data.rule, data.size, data.placement, ctx);
state.p1 = data.p1;
state.p2 = data.p2;
state.next = data.next;
state.history = data.history.split(',').map(BigInt);
state.refresh();
return state;
}
}
exports.State = State;