浏览器里的中国象棋: HTML5 canvas, JS, python
界面程序很短。引擎不是我写的,棋力不是很强——但我写不出来,正在学GNU chess的源码。
全部文件: https://files.cnblogs.com/files/blogs/714801/ccib.zip
引擎是可以换的,如 象棋旋风官方网站--中国象棋第一AI智能引擎 (ccyclone.com) 旋风专业版.zip (41.45 MB)
ELEEYE.EXE 87KB, BOOK.DAT 95KB ……
电脑下象棋资源微全 - Fun_with_Words - 博客园 (cnblogs.com)
# Universal Chinese Chess Protocol(UCCI)是象棋界面和引擎间的通信协议。国际象棋有UCI. # 引擎是个.exe,它和界面通过stdin和stdout通信。 # 界面向引擎发送“指令”,引擎向界面发送“反馈”。指令和反馈以“行”为单位(以'\n'结束)。 # 别忘了刷新缓冲区,如fflush(). # 引擎有引导、空闲和思考三种状态。 # 引导状态: 界面用ucci指令让引擎进入空闲状态; 引擎输出ucciok作为初始化结束的标志。 # 空闲状态: 引擎接收思考(go)和退出(quit)指令。 # 思考状态: 引擎收到go指令后进入思考状态,输出bestmove或nobestmove。 # 界面用position指令把局面告诉引擎。如: # ucci # id name ElephantEye 3.1 # option usemillisec type check default true # ucciok # position fen rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1 # go time 3 # info depth 0 score 1473 pv b2e2 # bestmove g3g4 ponder h7g7 from subprocess import Popen, PIPE class EleEye(Popen): def __init__(m): wd = 'ElephantEye/' ; Popen.__init__(m,[wd + 'ELEEYE.EXE' ,],cwd = wd,stdin = PIPE,stdout = PIPE) def send(m, s): m.stdin.write(s.encode() + b '\n' ); m.stdin.flush() def recv(m, p): p = p.encode(); out = b'' while True : s = m.stdout.readline(); print (s.decode(), end = '') out + = s if s.find(p) ! = - 1 : return out print ( 'Staring engine...' ) ee = EleEye() ee.send( 'ucci' ); print (ee.recv( 'ucciok' ).decode()) from http.server import * from threading import * import re import urllib class HTTPReqHandler(SimpleHTTPRequestHandler): def __init__(m, r, c, s): super ().__init__(r, c, s, directory = 'www' ) def do_GET(m): path = m.requestline.split( ' ' )[ 1 ] if not path.startswith( '/ucci' ): return super ().do_GET() param = re.split( '[\?\<]' , urllib.parse.unquote(path)) m.send_response( 200 ) m.send_header( 'Content-type' , 'text/html' ) m.end_headers() ee.send(param[ 2 ]); print (param[ 2 ]) if param[ 1 ] ! = 'none' : m.wfile.write(ee.recv(param[ 1 ])) def do_HEAD(m): super ().do_HEAD() def do_POST(m): super ().do_POST() # ERROR: super没有do_POST(). Try Flask. def httpd_thread(): port = 8000 svr_addr = ('', port) httpd = ThreadingHTTPServer(svr_addr, HTTPReqHandler) print ( 'Listening at' , port) httpd.serve_forever() Thread(daemon = 1 , target = httpd_thread).start() while input (): pass |
HTML:
< html >< meta charset="gbk">< title >CCIB</ title >< style > /* https://www.w3school.com.cn/cssref/css_selectors.asp */ * { font:12pt 'Segoe UI' } #brd_canvas { cursor:hand } h6 { color:green; margin:1em } /* InfrawView里在像素上按住鼠标左键,标题栏显示颜色 */ /* red/black button */ .rb {font:bold 24pt '楷体'; padding:8px; background:#E0C088; cursor:hand; box-shadow:0 5px 8px 0 rgba(0,0,0,0.25) } input, #fenbak { font:10pt mono; width:850px; padding:6px; border:dotted 1px } </ style > < body > < div style="position:absolute; left:80px; top:8px"> < canvas id="brd_canvas" width="407" height="454"></ canvas > </ div > < div style="position:absolute; left: 500px" id="panel"> < h6 >Chinese Chess In Browser< br >引擎:ElephantEye by Morning Yellow</ h6 > < ul > < li >你可以连续走红棋或黑棋。</ li > < li >点击红棋走子或黑棋走子,电脑走。</ li > < li >可通过FEN设置局面。</ li > < li >在棋盘上修改局面:< br >① 无规则、可乱走、能"吃"自己子< br >② 先点空白处再点棋子可拿掉它< br > ③ 先点棋子再点棋盘外可拿掉它< br >④ 复制粘贴FEN可备份</ li > </ ul > < p >< button class="rb" style="color:#AC0000" onclick="move('w')">红棋走子</ button ></ p > < p >< button class="rb" style="color:black" onclick="move('b')">黑棋走子</ button ></ p > </ div > < div style="position:absolute; left:8px; top:480px"> < button style="font:11pt mono" onclick="fromFEN(), draw_all()">应用</ button > FEN:< br > < input type="text" id="fen" value="rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR" style="margin-top:2px"></ input >< br > < textarea id="fenbak" value="FEN备份区" rows="6" style="position:relative; top:-1px"></ textarea > </ div > < div style="position:absolute; left:900px; top:0px; bottom:0px; border-left:solid #efe 1px"> < p id="ucciout" style="font:10pt mono; margin:8px"></ p > </ div > < script src="ccib.js"></ script > </ body ></ html > |
JS:
document.addEventListener( 'mousemove' , function (e){ // mx,my记录鼠标指针位置(块坐标) mx = Math.floor((e.x - 80 - 4) / 41); my = Math.floor((e.y - 8 - 7) / 41) }) sx = sy = -1 // 选择的位置 ctx = brd_canvas.getContext( '2d' ) img_wood = new Image(); img_wood.src = 'img/WOOD.gif' img_wood.onload = function (){ // 得这么干 ctx.drawImage(img_wood, 0, 0) img_brd = get_2d_ary(10, 9) // 存放10x9个棋盘切片图片 for ( let y = 0; y < 10; y++) for ( let x = 0; x < 10; x++) // 直接打开test.html,报The canvas has been tainted by cross-origin data // 打开http://127.0.0.1:8000/test.html则无此问题 img_brd[y][x] = ctx.getImageData(4 + x * 41, 7 + y * 41, 41, 41) img_pieces = {} // 获取棋子图片; 小写表示黑方,大写表示红方 let str = 'rnbakcp' ; for ( let c of str) { var i = new Image; i.src = 'img/B' + c + '.gif' ; img_pieces[c] = i i = new Image; i.src = 'img/R' + c + '.gif' ; img_pieces[c.toUpperCase()] = i } img_sel = new Image; img_sel.src = 'img/OOS.gif' fromFEN() setTimeout(draw_all, 100) // 棋子图片加载完再draw } function draw_all(){ for ( let y=0; y<10; y++) for ( let x=0; x<9; x++) draw(x,y) } function draw(x, y){ var px = 4 + x * 41, py = 7 + y * 41 // pixel var c = brd[y][x] if (c == ' ' ) ctx.putImageData(img_brd[y][x], px, py) else draw_img(img_pieces[c], px, py) if (x == sx && y == sy) draw_img(img_sel, px, py) } function draw_img(img, x, y){ // 得这么干 var i = new Image; i.src = img.src; i.onload = function () { ctx.drawImage(i, x, y); i = null } } document.addEventListener( 'mousedown' , function (e){ if (e.which != 1) return // Not left button var out = mx < 0 || mx >= 9 || my < 0 || my >= 10 if (sx >= 0 && (sx != mx || sy != my)){ if (!out) brd[my][mx] = brd[sy][sx], draw(mx, my) brd[sy][sx]= ' ' ; var X=sx, Y=sy; sx=sy=-1; draw(X, Y) toFEN() } else if (!out) draw(sx = mx, sy = my) }) function fromFEN(){ try { brd = get_2d_ary(10, 9) f = fen.value.split( '/' ) var x, y, i for (y = 0; y < 10; y++){ x = 0 for (i = 0; i < f[y].length; i++){ var c = f[y][i] if (c >= '1' && c <= '9' ) x += c - '0' else brd[y][x++] = c } } } catch (e){} } function toFEN(){ let f = '' for ( let y = 0; y < 10; y++){ let n = 0 for ( let x = 0; x < 9; x++){ let c = brd[y][x] if (c == ' ' ) ++n else { if (n) f += n f += c; n = 0 } } if (n) f += n if (y != 9) f += '/' } return fen.value = f } function ajax(req, cb){ let ax = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject( 'Microsoft.XMLHTTP' ) ax.onreadystatechange = function (){ if (ax.readyState != 4 || ax.status != 200) return cb(ax.responseText) } ax.open( 'GET' , '/ucci?' + req, true ); ax.send() } function move(who){ ajax( 'none<position fen ' + toFEN() + ' ' + who + ' - - 0 1' , function (){ ajax( 'bestmove<go time 5000' , function (s){ ucciout.innerText = s let i = s.indexOf( '\nbestmove' ) if (i == -1){ alert( 'No best move' ); return } let t = 'a0' fx = s.charCodeAt(i+10) - t.charCodeAt(0) tx = s.charCodeAt(i+12) - t.charCodeAt(0) fy = 9 - (s.charCodeAt(i+11) - t.charCodeAt(1)) ty = 9 - (s.charCodeAt(i+13) - t.charCodeAt(1)) console.log(fx, fy, tx, ty) brd[ty][tx] = brd[fy][fx]; brd[fy][fx] = ' ' ; draw_all(); toFEN() }) }) } function get_2d_ary(m, n) { var a = new Array() for (;m>0;m--){ var r = new Array(), i for (i = 0; i < n; i++) r.push(0) a.push(r) } return a } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?