zh-chess
v3.2.1
Published
A Chinese chess framework that supports browsers and nodejs(一款运行在浏览器和nodejs环境的中国象棋框架)
Maintainers
Readme
ZhChess(中国象棋)
一款JavaScript语言编写的中国象棋游戏框架,支持nodejs,浏览器,vue,react等前端框架。(支持typescript)更多象棋知识
案例网站
更新日志
v3.2.0
- 新增扩展框架(
ChessPlugin/ChessRenderer/ChessVariant):可自定义棋盘与棋子外观、变体规则(如揭棋骨架),默认中国象棋行为不变。 - 新增文字棋盘导出:
exportTextBoard(pieces, options)与实例方法game.exportTextBoard(options),支持chinese/ascii与自定义formatCell。 - 新增
game.use(plugin)/game.unuse(name)/game.getDrawLayout();GameInfo支持plugins/renderer/variant。 - 示例见
example/framework-demo.js(文字导出、方形棋子、揭棋钩子骨架)。
v3.1.0
- 新增
generateMoves(side)批量走法生成 API,作为 AI 搜索入口(内部仅构建一次棋盘位表并交由本方棋子复用,比逐个调用getMovePoints(pl)更高效)。 - 新增
generateLegalMoves(side)合法走法生成 API:对每个伪合法走法做送将过滤(checkGeneralInTrouble增量模拟),返回扁平化Move[]({ from, to, captured }),AI 搜索可直接使用。 - 新增
isLegalMove(side, from, to)单步合法性判定(含送将过滤)。 - 新增
getPiecesOfSide(side)获取指定方存活棋子列表。 - 新增
isGameOver(),旧gameOver()保留为@deprecated委托。 checkGeneralInTrouble由 protected 改为 public,便于引擎侧复用判定逻辑。- 事件 API 由 5 个重载改为泛型签名
on<K extends GameEventName>(e: K, fn: GameEventMap[K]),类型更严格。 - 修正历史类型拼写错误:
PiecePosInfo(原PeicePosInfo)、GamePieceGridDiffX/Y、PENPieceNameCode(原PENPeiceNameCode)、UpdateSuccess(原updateSuccess),旧名保留为@deprecated别名以兼容既有代码。 - 项目工程化:新增 ESLint / Prettier 配置与 GitHub Actions CI(install / lint / format / build / test),清理存量 lint 告警。
- 等价性测试黄金快照化,新增
generateMoves与逐子getMovePoints的一致性断言;新增assertLegalMovesConsistency校验generateLegalMoves/isLegalMove与update逐走法判定的一致性。
v3.0.0
- 重写走法生成与合法性校验,性能与正确性提升。
- 优化将军判定:按精确
constructor判断棋子类型,避免继承链误判。 - 棋盘位表(
Board)在走法生成与将军检测间复用,减少重复构建。 - 等价性回归测试固定为 v2.1.1 行为,保证重构后规则完全一致。
安装使用
nodejs
- 项目新增依赖(需要借助canvas来显示游戏图片)
D:your project>npm i zh-chess canvas -D # or cnpm i zh-chess canvas -D- 直接导入使用
const {
createCanvas
} = require('canvas')
const CTX_WIDTH = 375,
CTX_HEIGHT = 375
const canvas = createCanvas(CTX_WIDTH, CTX_HEIGHT)
const ctx = canvas.getContext('2d')
const ZhChess = require("zh-chess").default
const fs = require('fs')
const out = fs.createWriteStream('./test.jpg') // 创建文件流
const game = new ZhChess({
ctx, // ctx 属性可传 可不传 只要 游戏不需要更新画面 光靠 游戏逻辑来运行完全可以足够
gameWidth: CTX_WIDTH,
gameHeight: CTX_HEIGHT
})
game.gameStart("RED") // 开始游戏
game.moveStr("炮2平5", "RED") // 移动棋子
game.draw(ctx) // 更新画布 需要传入画布
const stream = canvas.createJPEGStream()
stream.pipe(out) // 写入文件
out.on('finish', () => console.log('The JPEG file was created.'))浏览器环境
- 下载源码
git clone https://github.com/kongyijilafumi/zh-chess
# or 国内码云
git clone https://gitee.com/kong_yiji_and_lavmi/zh-chess- 在html的
head部分使用script标签引入./lib/zh-chess.browser.min.js文件
<head>
<script script="./lib/zh-chess.browser.min.js"></script>
</head>- 引入之后的下方,新建
script标签,直接调用api即可。
<head>
<script script="./lib/zh-chess.browser.min.js"></script>
</head>
<body>
<canvas id="canvas" height="375" width="375" style="width:375px;height:375px;"></canvas>
</body>
<script>
const app = document.getElementById("canvas")
const ctx = app.getContext("2d")
const zhchess = new ZhChess.default({
ctx,
gameWidth: 375,
gameHeight: 375
})
// 绑定点击事件
app.addEventListener("click", zhchess.listenClickAsync, false)
</script>react, vue等前端构建项目使用
- 安装依赖
your project>npm i zh-chess -D #or cnpm i zh-chess - 在
vue2.x项目中使用
<template>
<canvas ref="canvas" height="375" width="375" style="width: 375px; height: 375px;" />
</template>
<script>
import ZhChess from "zh-chess";
export default {
data() {
return {
game: null
}
},
mounted() {
const canvas = this.$refs.canvas;
const ctx = canvas.getContext("2d");
this.game = new ZhChess({
ctx,
gameHeight: 375,
gameWidth: 375,
duration: 200, // 默认200 ms单位 值棋子运动时长
});
this.game.gameStart("RED"); // 以红方开始游戏
canvas.addEventListener("click", this.game.listenClickAsync, false);
},
}
</script>- 在
vue3.x项目中使用
<template>
<canvas ref="canvas" height="375" width="375" style="width: 375px; height: 375px;" />
</template>
<script lang="ts" setup>
import ZhChess, {
GameOverCallback,
MoveCallback,
MoveFailCallback,
} from "zh-chess";
import {
onMounted,
reactive,
ref,
getCurrentInstance
} from "vue";
const page = getCurrentInstance();
const CANVAS_WIDTH = 375;
const CANVAS_HEIGHT = 375;
let game = reactive({}
as ZhChess);
const onOver: GameOverCallback = (winnerSide) => {
console.log(`${winnerSide}胜`);
};
// 挂载
onMounted(() => {
const canvas = page?.refs.canvas as HTMLCanvasElement;
game = new ZhChess({
ctx: canvas.getContext("2d") as CanvasRenderingContext2D,
gameHeight: CANVAS_WIDTH,
gameWidth: CANVAS_HEIGHT,
});
game.gameStart("RED");
canvas.addEventListener("click", game.listenClickAsync, false);
game.on("over", onOver);
});
</script>- 在
react项目中使用
import ZhChess from "zh-chess"
import { useEffect, useRef, useState } from "react";
export default function App() {
const [gameInstance, setGame] = useState<ZhChess | null>(null)
const canvas = useRef<HTMLCanvasElement>(null)
useEffect(() => {
if (canvas.current) {
const CTX_WIDTH = 375, CTX_HEIGHT = 375,ctx = canvas.current.getContext('2d') as CanvasRenderingContext2D;
const game = new ZhChess({
ctx,
gameHeight: CTX_HEIGHT,
gameWidth: CTX_WIDTH
})
game.gameStart("RED")
canvas.current.addEventListener("click", game.listenClickAsync, false)
setGame(game)
}
}, [canvas])
return <div className="app">
<canvas ref={canvas} width="375" height="375" style={{width:375,height:375}} />
</div>
} 接口
ZhChess 游戏类
constructor(obj: GameInfo);
GameInfo Properties
- blackPeiceBackground?: string 黑棋子背景色 默认:
#fdec9e - checkerboardBackground?: string 棋盘背景色 默认:
#faebd7 - ctx?: CanvasRenderingContext2D 画布
如果不需要游戏显示出来,可以不需要
画布
- drawMovePoint?: boolean 选中是否绘画可移动的点 默认:
true - duration?: number 棋子运动速度时长 毫秒单位 默认:
200 - gameHeight?: number 游戏窗口高度大小 默认:
800 - gamePadding?: number 游戏内边距大小距离棋盘 默认:
20 - gameWidth?: number 游戏窗口宽度大小 默认:
800 - movePointColor?: string 绘画可移动点的颜色 默认:
#25dd2a - redPeiceBackground?: string 红棋子背景色 默认:
#feeca0 - scaleRatio?: number 画布缩放大小 默认:
1(用于移动端,解决画布模糊问题)
Properties
duration: number
Accessors
棋子运动速度时长 毫秒单位
get currentGameSide(): null | PieceSide
获取游戏方
get currentLivePieceList(): PieceList
获取当前存活的棋子列表
get currentRadius(): number
获取当前象棋绘制半径
get winnerSide(): null | PieceSide
获取赢棋方
gameStart(side: PieceSide): void
选择玩家方并且初始化游戏,side只能是 RED | BLACK .
const game = new ZhChess({
gameHeight: 375,
gameWidth: 375
})
game.gameStart("RED")Methods
changeCurrentPlaySide(side: PieceSide): void
更改当前走棋方
changePlaySide(side: PieceSide): void
更换玩家视角
checkDraw(): void
检查是否有画布 有会根据当前棋子位置状态去更新画布 否则不更新 报出错误
checkGameState(): MoveResult
棋子运动前检查游戏状态是否可以运动
draw(ctx: CanvasRenderingContext2D): void
根据当前棋子状态绘画 棋盘状态 游戏数据 画出布局
isGameOver(): boolean
游戏是否结束
gameOver(): boolean
游戏是否结束(@deprecated 请使用 isGameOver())
gameStart(side: PieceSide): void
初始化选择玩家方 初始化棋盘
generateMoves(side: PieceSide): MovePointList[]
批量生成指定方所有存活棋子的走法列表(AI 搜索入口)。内部仅构建一次棋盘位表并交由本方全部棋子复用,比逐个调用 getMovePoints(pl) 更高效。
game.gameStart("RED")
const moves = game.generateMoves("RED") // MovePointList[]
const redPieces = game.currentLivePieceList.filter(p => p.side === "RED")
// moves[i] 对应 redPieces[i] 的走法列表注意:
generateMoves返回伪合法走法(不包含送将过滤)。需要严格合法走法请使用generateLegalMoves。
generateLegalMoves(side: PieceSide): Move[]
批量生成指定方所有存活棋子的合法走法列表(AI 搜索可直接使用)。与 generateMoves 的区别:对每个伪合法走法执行送将检测(checkGeneralInTrouble),只返回走子后己方将帅不会被攻击、也不会与敌方将帅对脸的走法。内部仅构建一次棋盘位表并复用,送将检测使用增量模拟(双槽位),无额外棋盘分配。
game.gameStart("RED")
const legalMoves = game.generateLegalMoves("RED") // Move[]
// legalMoves[i] = { from: Point, to: Point, captured: ChessOfPeice | null }isLegalMove(side: PieceSide, from: Point, to: Point): boolean
判断指定方棋子从 from 走到 to 是否为合法走法(含送将过滤)。若 from 处没有该方棋子、或 to 不在该棋子可走范围内、或走子后己方将帅不安全,均返回 false。
game.gameStart("RED")
const ok = game.isLegalMove("RED", new Point(8, 9), new Point(8, 8))getPiecesOfSide(side: PieceSide): PieceList
获取指定方的存活棋子列表,顺序与 currentLivePieceList 中该方棋子的顺序一致(可直接与 generateMoves(side) 对齐棋子与走法)。
game.gameStart("RED")
const redPieces = game.getPiecesOfSide("RED") // PieceListgetCurrentPenCode(side: PieceSide): string
获取当前棋盘的 PEN 格式位置代码
exportTextBoard(options?: TextBoardOptions): string
导出当前棋盘文字布局。style 可为 chinese(默认)或 ascii;也可用独立函数 exportTextBoard(pieces, options)。
game.gameStart("RED")
console.log(game.exportTextBoard({ style: "chinese" }))
console.log(game.exportTextBoard({ style: "ascii", showCoords: false }))use(plugin: ChessPlugin) / unuse(name: string)
注册/卸载扩展插件。插件可携带 renderer(自定义棋盘/棋子绘制)、variant(变体规则钩子,如揭棋)、formatCell(文字导出单元格)。
game.use({
name: "my-theme",
renderer: {
drawPiece(ctx, piece, style, layout) {
// 自定义棋子外形,返回 true 跳过默认圆形
return true
}
},
variant: {
name: "jieqi",
getPieceDisplayName(piece) {
return "暗" // 未翻开时显示
},
initBoard(game) { /* 自定义开局 */ },
afterMove(ctx, game) { /* 翻子、揭示等 */ }
}
})GameInfo 也可直接传入 plugins / renderer / variant。更多见 example/framework-demo.js。
getDrawLayout(): DrawLayout
返回当前棋盘绘制布局(格子尺寸、偏移、配色等),供自定义渲染器使用。
listenClick(e: MouseEvent): void
用于dom的点击事件,此方法棋子运动无动画,且为同步执行函数。
const app = document.getElementById("canvas")
const ctx = app.getContext("2d")
const game = new ZhChess.default({
ctx,
gameWidth: 375,
gameHeight: 375
})
// 绑定点击事件
app.addEventListener("click", game.listenClick, false)listenClickAsync(e: MouseEvent): void
用于dom的点击事件,此方法执行棋子运动有动画(根据 duration 来决定运动时长),且为异步执行函数。
const app = document.getElementById("canvas")
const ctx = app.getContext("2d")
const game = new ZhChess.default({
ctx,
gameWidth: 375,
gameHeight: 375,
duration: 500 // 棋子运动时长 500ms
})
// 绑定点击事件
app.addEventListener("click", game.listenClickAsync, false)moveStr(str: string, side: PieceSide): UpdateResult
通过文字的形式根据 红黑方视角 来移动象棋。无移动动画,同步方法,返回移动结果。
10
9
8
7
6
5
4 兵 兵 兵 兵 兵
3 炮 炮
2
1 车 马 相 士 帅 士 相 马 车
1 2 3 4 5 6 7 8 9 已红方或黑方自己视角,靠近对方底线为进,靠近己方底线为退,横着走为平。例如:车1进1是指自己左边的车往前走一步。
game.moveStr("车1进1", "RED") // 红方 车1进1 返回 { flag:true } 或者 { flag:false, message:"xxx" } moveStrAsync(str: string, side: PieceSide, refreshCtx: boolean): Promise < UpdateResult >
跟moveStr方法作用一样,不过是异步的,有动画效果。 refreshCtx 表示 是否每次移动都更新画布。
on(e: K, fn: GameEventMap[K]): void
游戏监听事件(泛型签名,回调参数类型随事件名自动推导)
e为
move时,fn函数的参数有(peice: ChessOfPeice, cp: CheckPoint, enemyhasTrouble: boolean)e为
moveFail时,fn函数的参数有(peice: ChessOfPeice, p: Point, currentSideDanger: boolean, msg: string)e为
log时,fn函数的参数有(str: any)e为
over时,fn函数的参数有(winnerSide: PieceSide)e为
error时,fn函数的参数有(error: any)
removeEvent(e: K, fn: GameEventMap[K]): void
移除游戏的监听函数
setLivePieceList(pl: PieceList): void
设置当前存活棋子列表
setPenCodeList(penCode: string): void
根据pen代码格式来设置当前棋盘
建议参考 文章 博客 https://www.xqbase.com/protocol/cchess_fen.htm
update(pos: Point, mov: null | Point, side: PieceSide, post: boolean): UpdateResult
游戏根据坐标点 移动点来进行更新游戏运行数据。 post :是否由程序自己更新游戏状态。
返回的结果: 如果 post 为 false, 请检查返回的结果 move 是否为 true ,为true表示有返回回调函数cb,只有调用 cb() 游戏状态才会更新。如果 post 为 true,程序会自己更新游戏状态 只需要判断 是否更新成功即可!
updateAsync(pos: Point, mov: null | Point, side: PieceSide, refreshCtx: boolean, moveCallback?: UpdateMoveCallback): Promise < UpdateResult >
游戏根据坐标点 移动点来进行更新游戏运行数据。这是一个返回一个promise结果,也表示 这个方法是异步的。
const ctx = document.getElementById("canvas").getContext("2d")
const game = new ZhChess({})
game.gameStart("RED")
game.updateAsync(pos, mov, side, true, (posPeice, newPoint) => console.log(posPeice, newPoint)) // 每次运动都去绘画一次
game.updateAsync(pos, mov, side, false, (posPeice, newPoint) => game.draw(ctx)) // 每次运动 自己去调用游戏绘画