koishi-plugin-chess
Version:
Playing chess games in Koishi
225 lines (224 loc) • 9.74 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.apply = exports.Config = exports.inject = exports.name = void 0;
const koishi_1 = require("koishi");
const board_1 = require("./board");
const go = __importStar(require("./games/go"));
const gomoku = __importStar(require("./games/gomoku"));
const othello = __importStar(require("./games/othello"));
const games = {
go,
gomoku,
othello,
};
const states = {};
__exportStar(require("./board"), exports);
exports.name = 'chess';
exports.inject = {
optional: ['puppeteer', 'database'],
};
exports.Config = koishi_1.Schema.object({});
function apply(ctx) {
ctx.model.extend('channel', {
// do not use shorthand because the initial value is `null`
chess: { type: 'json' },
});
ctx = ctx.guild();
ctx.command('chess [position]', '棋类游戏')
.userFields(['name'])
.channelFields(['chess'])
.alias('落子')
.alias('悔棋', { options: { repent: true } })
.alias('围棋', { options: { size: 19, rule: 'go' } })
.alias('五子棋', { options: { size: 15, rule: 'gomoku' } })
.alias('奥赛罗', { options: { size: 8, rule: 'othello' } })
.alias('黑白棋', { options: { size: 8, rule: 'othello' } })
.alias('停止下棋', { options: { stop: true } })
.alias('跳过回合', { options: { skip: true } })
.alias('查看棋盘', { options: { show: true } })
.option('rule', '<rule> 设置规则,支持的规则有 go, gomoku, othello')
.option('size', '<size:number> 设置大小')
.option('skip', '跳过回合')
.option('repent', '悔棋')
.option('show', '-v, --show, --view 显示棋盘')
.option('stop', '-e, --stop, --end 停止游戏')
.option('imageMode', '-i 使用图片模式', { hidden: () => !ctx.puppeteer })
.option('textMode', '-t 使用文本模式', { hidden: () => !ctx.puppeteer })
.usage([
'输入“五子棋”“黑白棋”“围棋”开始对应的一局游戏。',
'再输入“落子 A1”将棋子落于 A1 点上。',
'目前默认使用图片模式。文本模式速度更快,但是在部分机型上可能无法正常显示,同时无法适应过大的棋盘。',
].join('\n'))
.action(async ({ session, options }, position) => {
const { cid, userId, channel = { chess: null } } = session;
if (!states[cid]) {
if (position || options.stop || options.repent || options.skip) {
return '没有正在进行的游戏。输入“五子棋”“黑白棋”“围棋”开始对应的一局游戏。';
}
if (!(0, koishi_1.isInteger)(options.size) || options.size < 2 || options.size > 20) {
return '棋盘大小应该为不小于 2,不大于 20 的整数。';
}
const rule = games[options.rule];
if (!rule)
return '没有找到对应的规则。';
const state = new board_1.State(options.rule, options.size, rule.placement || 'cross', ctx);
state.p1 = userId;
if (rule.create) {
const result = rule.create.call(state);
if (result)
return result;
}
state.update = rule.update;
states[cid] = state;
state.save();
return state.draw(session, `${session.username} 发起了游戏!`);
}
if (options.stop) {
delete states[cid];
channel.chess = null;
return '游戏已停止。';
}
const state = states[cid];
if (options.show)
return state.draw(session);
if (options.textMode) {
state.imageMode = false;
return state.draw(session, '已切换到文本模式。');
}
else if (options.imageMode) {
state.imageMode = true;
return state.draw(session, '已切换到图片模式。');
}
if (state.p2 && state.p1 !== userId && state.p2 !== userId) {
return '游戏已经开始,无法加入。';
}
if (options.skip) {
if (!state.next)
return '对局尚未开始。';
if (state.next !== userId)
return '当前不是你的回合。';
state.next = state.p1 === userId ? state.p2 : state.p1;
channel.chess = state.serial();
return `${session.username} 选择跳过其回合,下一手轮到 ${koishi_1.segment.at(state.next)}。`;
}
if (options.repent) {
if (!state.next)
return '对局尚未开始。';
const last = state.p1 === state.next ? state.p2 : state.p1;
if (last !== userId)
return '上一手棋不是你所下。';
state.history.pop();
state.refresh();
state.next = last;
channel.chess = state.serial();
return state.draw(session, `${session.username} 进行了悔棋。`);
}
if (!position)
return '请输入坐标。';
let isLetterFirst = false;
if (typeof position !== 'string' || !(isLetterFirst = /^[a-z]\d+$/i.test(position)) && !/^\d+[a-z]$/i.test(position)) {
return '请输入由字母+数字构成的坐标。';
}
if (state.p2 && userId !== state.next)
return '当前不是你的回合。';
const [x, y] = isLetterFirst ? [
position.charCodeAt(0) % 32 - 1,
parseInt(position.slice(1)) - 1,
] : [
position.slice(-1).charCodeAt(0) % 32 - 1,
parseInt(position.slice(0, -1)) - 1,
];
if (x >= state.size || y >= state.size || y < 0) {
return '落子超出边界。';
}
if (state.get(x, y))
return '此处已有落子。';
let message = '';
if (state.next || userId === state.p1) {
message = `${session.username} 落子于 ${position.toUpperCase()},`;
}
else {
if (state.history.length === 1) {
state.p2 = state.p1;
state.p1 = userId;
}
else {
state.p2 = userId;
}
message = `${session.username} 加入了游戏并落子于 ${position.toUpperCase()},`;
}
const value = userId === state.p1 ? 1 : -1;
const result = state.update(x, y, value);
switch (result) {
case board_1.MoveResult.illegal:
state.next = userId;
return '非法落子。';
case board_1.MoveResult.skip:
message += `下一手依然轮到 ${koishi_1.segment.at(userId)}。`;
break;
case board_1.MoveResult.p1Win:
delete states[cid];
channel.chess = null;
return message + `恭喜 ${koishi_1.segment.at(state.p1)} 获胜!`;
case board_1.MoveResult.p2Win:
delete states[cid];
channel.chess = null;
return message + `恭喜 ${koishi_1.segment.at(state.p2)} 获胜!`;
case board_1.MoveResult.draw:
delete states[cid];
channel.chess = null;
return message + '本局游戏平局。';
case undefined:
// eslint-disable-next-line no-cond-assign
if (state.next = userId === state.p1 ? state.p2 : state.p1) {
message += `下一手轮到 ${koishi_1.segment.at(state.next)}。`;
}
else {
message = message.slice(0, -1) + '。';
}
break;
default:
state.next = userId;
return `非法落子(${result})。`;
}
state.save();
channel.chess = state.serial();
return state.draw(session, message, x, y);
});
ctx.using(['database'], async (ctx) => {
const channels = await ctx.database.getAssignedChannels(['id', 'chess']);
for (const { id, chess } of channels) {
if (chess) {
states[id] = board_1.State.from(chess, ctx);
states[id].update = games[chess.rule].update;
}
}
});
}
exports.apply = apply;