
界面程序很短。引擎不是我写的,棋力不是很强——但我写不出来,正在学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
}
