UNPKG

koishi-plugin-chess

Version:
225 lines (224 loc) 9.74 kB
"use strict"; 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;