瀏覽器裡的中國象棋: HTML5 canvas, JS, python
阿新 • • 發佈:2021-12-16
介面程式很短。引擎不是我寫的,棋力不是很強——但我寫不出來,正在學GNU chess的原始碼。全部檔案:https://files.cnblogs.com/files/blogs/714801/ccib.zip
引擎是可以換的,如象棋旋風官方網站--中國象棋第一AI智慧引擎 (ccyclone.com)旋風專業版.zip (41.45 MB)
ELEEYE.EXE 87KB,BOOK.DAT 95KB ……
# 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() 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 }