418 lines
12 KiB
Python
418 lines
12 KiB
Python
#
|
|
# Chess interface for Thomas Ahle's sunfish engine
|
|
# Code by Alexander Pruss under the MIT license
|
|
#
|
|
#
|
|
# To work, this needs sunfish.py:
|
|
# https://raw.githubusercontent.com/thomasahle/sunfish/master/sunfish.py
|
|
#
|
|
|
|
from collections import OrderedDict
|
|
from mc import *
|
|
from vehicle import *
|
|
from text import *
|
|
from fonts import *
|
|
import drawing
|
|
import time
|
|
import sys
|
|
|
|
LABEL_BLOCK = REDSTONE_BLOCK
|
|
|
|
try:
|
|
import _sunfish as sunfish
|
|
except:
|
|
try:
|
|
import urllib2
|
|
import os.path
|
|
content = urllib2.urlopen("https://raw.githubusercontent.com/thomasahle/sunfish/master/sunfish.py").read()
|
|
f=open(os.path.join(os.path.dirname(sys.argv[0]),"_sunfish.py"),"w")
|
|
f.write("# From: https://raw.githubusercontent.com/thomasahle/sunfish/master/sunfish.py\n")
|
|
f.write("# Covered by the GPL 2 license\n")
|
|
f.write(content)
|
|
f.close()
|
|
import _sunfish as sunfish
|
|
except:
|
|
print "Failed download: You need sunfish.py for this script."
|
|
|
|
def getCoords(row,col):
|
|
return (corner.x+8*row+4,corner.y,corner.z+8*col+4)
|
|
|
|
def toRowCol(n, black):
|
|
row = 7 - ((n - 20) / 10)
|
|
col = n % 10 - 1
|
|
if black:
|
|
col = 7 - col
|
|
row = 7 - row
|
|
return row,col
|
|
|
|
def toRowColMove(m, black):
|
|
return toRowCol(m[0],black),toRowCol(m[1],black)
|
|
|
|
def toNumeric(rowCol,black):
|
|
if black:
|
|
col = 7 - rowCol[1]
|
|
row = 7 - rowCol[0]
|
|
else:
|
|
row, col = rowCol
|
|
return 20 + 10 * (7-row) + 1 + col
|
|
|
|
def toNumericMove(rowColMove,black):
|
|
return toNumeric(rowColMove[0],black),toNumeric(rowColMove[1],black)
|
|
|
|
def toAlgebraicMove(rowColMove):
|
|
(r0,c0),(r1,c1) = rowColMove
|
|
return 'abcdefgh'[c0]+str(r0+1)+'abcdefgh'[c1]+str(r1+1)
|
|
|
|
def drawSquare(row,col):
|
|
block = OBSIDIAN if (col + row) % 2 == 0 else QUARTZ_BLOCK
|
|
mc.setBlocks(corner.x+row*8,corner.y-1,corner.z+col*8,corner.x+row*8+7,corner.y-1,corner.z+col*8+7,block)
|
|
|
|
def highlightSquare(row,col):
|
|
mc.setBlocks(corner.x+row*8,corner.y-1,corner.z+col*8,
|
|
corner.x+row*8+7,corner.y-1,corner.z+col*8,REDSTONE_BLOCK)
|
|
mc.setBlocks(corner.x+row*8,corner.y-1,corner.z+col*8,
|
|
corner.x+row*8,corner.y-1,corner.z+col*8+7,REDSTONE_BLOCK)
|
|
mc.setBlocks(corner.x+row*8+7,corner.y-1,corner.z+col*8,
|
|
corner.x+row*8+7,corner.y-1,corner.z+col*8+7,REDSTONE_BLOCK)
|
|
mc.setBlocks(corner.x+row*8,corner.y-1,corner.z+col*8+7,
|
|
corner.x+row*8+7,corner.y-1,corner.z+col*8+7,REDSTONE_BLOCK)
|
|
|
|
def drawEmptyBoard():
|
|
mc.setBlocks(corner.x,corner.y,corner.z,corner.x+63,corner.y+MAXHEIGHT,corner.z+63,0)
|
|
for row in range(8):
|
|
for col in range(8):
|
|
drawSquare(row,col)
|
|
for col in range(8):
|
|
c = getCoords(-1,col)
|
|
drawText(mc,FONTS['8x8'],Vec3(c[0]-4,corner.y-1,c[2]-4),Vec3(0,0,1),Vec3(1,0,0),"ABCDEFGH"[col],LABEL_BLOCK)
|
|
c = getCoords(8,col)
|
|
drawText(mc,FONTS['8x8'],Vec3(c[0]+4,corner.y-1,c[2]+4),Vec3(0,0,-1),Vec3(-1,0,0),"ABCDEFGH"[col],LABEL_BLOCK)
|
|
for row in range(8):
|
|
c = getCoords(row,-1)
|
|
drawText(mc,FONTS['8x8'],Vec3(c[0]-4,corner.y-1,c[2]-4),Vec3(0,0,1),Vec3(1,0,0),str(row+1),LABEL_BLOCK)
|
|
c = getCoords(row,8)
|
|
drawText(mc,FONTS['8x8'],Vec3(c[0]+4,corner.y-1,c[2]+4),Vec3(0,0,-1),Vec3(-1,0,0),str(row+1),LABEL_BLOCK)
|
|
|
|
PAWN = (
|
|
(".xx.",
|
|
"xxxx",
|
|
".xx.",
|
|
".xx.",
|
|
"xxxx"),
|
|
(".xx.",
|
|
"xxxx",
|
|
".xx.",
|
|
".xx.",
|
|
"xxxx"))
|
|
|
|
KNIGHT = (
|
|
("...xx.",
|
|
"..xxxx",
|
|
".xxxxx",
|
|
"xxxx.x",
|
|
".xxx..",
|
|
".xxxx.",
|
|
"xxxxxx"),
|
|
("...xx.",
|
|
"..xxxx",
|
|
".xxxxx",
|
|
"xxxx.x",
|
|
".xxx..",
|
|
".xxxx.",
|
|
"xxxxxx")
|
|
)
|
|
|
|
BISHOP = (
|
|
(".xx.",
|
|
"xx.x",
|
|
"x.xx",
|
|
"xxxx",
|
|
".xx.",
|
|
".xx.",
|
|
"xxxx"),
|
|
(".xx.",
|
|
"xx.x",
|
|
"x.xx",
|
|
"xxxx",
|
|
".xx.",
|
|
".xx.",
|
|
"xxxx"))
|
|
|
|
ROOK = (
|
|
("x.xx.x",
|
|
"x.xx.x",
|
|
"xxxxxx",
|
|
".xxxx.",
|
|
".xxxx.",
|
|
"xxxxxx"),
|
|
("x.xx.x",
|
|
"x.xx.x",
|
|
"xxxxxx",
|
|
".xxxx.",
|
|
".xxxx.",
|
|
"xxxxxx"))
|
|
|
|
QUEEN = (
|
|
("..xx..",
|
|
"x.xx.x",
|
|
"x.xx.x",
|
|
"xxxxxx",
|
|
"xxxxxx",
|
|
"xxxxxx",
|
|
"xxxxxx"),
|
|
("..xx..",
|
|
"x.xx.x",
|
|
"x.xx.x",
|
|
"xxxxxx",
|
|
"xxxxxx",
|
|
"xxxxxx",
|
|
"xxxxxx"))
|
|
|
|
KING = (
|
|
("..xx..",
|
|
"xxxxxx",
|
|
"..xx..",
|
|
"xxxxxx",
|
|
"xxxxxx",
|
|
".xxxx.",
|
|
"xxxxxx"),
|
|
("..xx..",
|
|
"xxxxxx",
|
|
"..xx..",
|
|
"xxxxxx",
|
|
"xxxxxx",
|
|
".xxxx.",
|
|
"xxxxxx"))
|
|
|
|
BLACK = WOOL_GRAY
|
|
WHITE = WOOL_WHITE
|
|
|
|
pieceBitmaps = {
|
|
'P':PAWN,
|
|
'N':KNIGHT,
|
|
'B':BISHOP,
|
|
'R':ROOK,
|
|
'Q':QUEEN,
|
|
'K':KING
|
|
}
|
|
|
|
MAXHEIGHT = 8
|
|
|
|
def toVehicle(bitmaps,block,piece):
|
|
dict = {}
|
|
depth = len(bitmaps)
|
|
height = len(bitmaps[0])
|
|
width = len(bitmaps[0][0])
|
|
for plane in range(depth):
|
|
x = plane-depth/2
|
|
for row in range(height):
|
|
y = row
|
|
for col in range(width):
|
|
z = col-width/2
|
|
if bitmaps[plane][height-1-row][col] == 'x':
|
|
dict[(x,y,z)] = block
|
|
v = Vehicle(mc,True)
|
|
v.setVehicle(dict)
|
|
v.name = piece
|
|
return v
|
|
|
|
def animateMovePiece(start,stop):
|
|
a = getCoords(start[0],start[1])
|
|
b = getCoords(stop[0],stop[1])
|
|
piece = pieces[start]
|
|
if not fast:
|
|
line = drawing.getLine(a[0],a[1],a[2],b[0],b[1],b[2])
|
|
for point in line[1:]:
|
|
piece.moveTo(point[0],point[1],point[2])
|
|
time.sleep(0.1)
|
|
piece.moveTo(b[0],b[1],b[2])
|
|
del pieces[start]
|
|
pieces[stop] = piece
|
|
|
|
def parse(message):
|
|
try:
|
|
if len(message) != 4:
|
|
raise ValueError
|
|
col0 = ord(message[0].lower()) - ord('a')
|
|
if col0 < 0 or col0 > 7:
|
|
raise ValueError
|
|
row0 = ord(message[1]) - ord('1')
|
|
if row0 < 0 or row0 > 7:
|
|
raise ValueError
|
|
col1 = ord(message[2].lower()) - ord('a')
|
|
if col1 < 0 or col1 > 7:
|
|
raise ValueError
|
|
row1 = ord(message[3]) - ord('1')
|
|
if row1 < 0 or row1 > 7:
|
|
raise ValueError
|
|
return (row0,col0),(row1,col1)
|
|
except:
|
|
raise ValueError
|
|
|
|
def getPiece(row,col):
|
|
try:
|
|
return pieces[(row,col)].name
|
|
except KeyError:
|
|
return None
|
|
|
|
def inputMove():
|
|
moves = []
|
|
mc.events.clearAll()
|
|
while len(moves) < 2:
|
|
try:
|
|
chats = mc.events.pollChatPosts()
|
|
move = parse(chats[0].message)
|
|
for m in moves:
|
|
drawSquare(m[0],m[1])
|
|
return move
|
|
except:
|
|
pass
|
|
hits = mc.events.pollBlockHits()
|
|
if len(hits) > 0:
|
|
c = hits[0].pos
|
|
if ( corner.x <= c.x and corner.y -1 <= c.y and corner.z <= c.z and
|
|
c.x < corner.x + 64 and c.y < corner.y + MAXHEIGHT and c.z < corner.z + 64 ):
|
|
m = (c.x - corner.x) / 8, (c.z - corner.z) /8
|
|
if len(moves) == 0 or m[0] != moves[0][0] or m[1] != moves[0][1]:
|
|
highlightSquare(m[0],m[1])
|
|
moves.append(m)
|
|
time.sleep(0.2)
|
|
mc.events.clearAll() # debounce
|
|
continue
|
|
for m in moves:
|
|
drawSquare(m[0],m[1])
|
|
moves = []
|
|
mc.postToChat('Canceled. Enter another move.')
|
|
time.sleep(0.2)
|
|
mc.events.clearAll() # debounce
|
|
time.sleep(0.2)
|
|
for m in moves:
|
|
drawSquare(m[0],m[1])
|
|
return tuple(moves)
|
|
|
|
def animateMove(rowColMove):
|
|
pos1 = rowColMove[0]
|
|
pos2 = rowColMove[1]
|
|
highlightSquare(pos1[0],pos1[1])
|
|
piece = getPiece(pos1[0],pos1[1])
|
|
if piece.upper() == 'K' and abs(pos1[1]-pos2[1]) > 1:
|
|
# castling
|
|
animateMovePiece(pos1,pos2)
|
|
if pos2[1] > pos1[1]:
|
|
animateMovePiece((pos1[0],7),(pos1[0],pos2[1]-1))
|
|
else:
|
|
animateMovePiece((pos1[0],0),(pos1[0],pos2[1]+1))
|
|
elif piece.upper() == 'P' and abs(pos2[0]==7):
|
|
# promote to queen (all that's supported by the engine)
|
|
animateMovePiece(pos1,pos2)
|
|
piece = pieces[pos2]
|
|
piece.erase()
|
|
if pos.board[move].islower():
|
|
v = toVehicle(QUEEN, BLACK, 'q')
|
|
else:
|
|
v = toVehicle(QUEEN, WHITE, 'Q')
|
|
pieces[pos2] = v
|
|
c = getCoords(pos2[0],pos2[1])
|
|
v.draw(c[0],c[1],c[2])
|
|
v.blankBehind()
|
|
return
|
|
else:
|
|
victim = None
|
|
redrawPiece = False
|
|
if piece.upper() == 'P' and pos1[1] != pos2[1] and pos2 not in pieces:
|
|
# en Passant
|
|
if pos2[1] > pos1[1]:
|
|
victim = pieces[(pos2[0],pos2[1]-1)]
|
|
else:
|
|
victim = pieces[(pos2[0],pos2[1]+1)]
|
|
elif pos2 in pieces:
|
|
victim = pieces[pos2]
|
|
redrawPiece = True
|
|
animateMovePiece(pos1,pos2)
|
|
if victim is not None:
|
|
victim.erase()
|
|
if redrawPiece:
|
|
piece = pieces[pos2]
|
|
piece.draw(piece.curLocation[0],piece.curLocation[1],piece.curLocation[2])
|
|
piece.blankBehind()
|
|
drawSquare(pos1[0],pos1[1])
|
|
drawSquare(pos2[0],pos2[1])
|
|
|
|
mc = Minecraft()
|
|
options = ''.join(sys.argv[1:])
|
|
black = 'b' in options
|
|
demo = 'd' in options
|
|
fast = 'f' in options
|
|
mc.postToChat("Please wait: setting up board.")
|
|
corner = mc.player.getTilePos()
|
|
corner.x -= 32
|
|
corner.z -= 32
|
|
drawEmptyBoard()
|
|
|
|
def myGetBlockWithData(pos):
|
|
"""
|
|
On RaspberryJuice, this is a lot faster than querying the server.
|
|
"""
|
|
for boardPos in pieces:
|
|
if pos in pieces[boardPos].curVehicle:
|
|
return pieces[boardPos].curVehicle[pos]
|
|
return AIR
|
|
|
|
# z coordinate is cols
|
|
# x coordinate is rows
|
|
pieces = {}
|
|
pos = sunfish.Position(sunfish.initial, 0, (True,True), (True,True), 0, 0)
|
|
for row in range(8):
|
|
for col in range(8):
|
|
piece = pos.board[toNumeric((row,col),False)]
|
|
if piece in pieceBitmaps:
|
|
v = toVehicle(pieceBitmaps[piece], WHITE, piece)
|
|
elif piece.capitalize() in pieceBitmaps:
|
|
v = toVehicle(pieceBitmaps[piece.capitalize()], BLACK, piece)
|
|
else:
|
|
continue
|
|
#uncomment the following line to optimize speed
|
|
v.getBlockWithData = myGetBlockWithData
|
|
pieces[(row,col)] = v
|
|
c = getCoords(row,col)
|
|
v.draw(c[0],c[1],c[2])
|
|
v.blankBehind()
|
|
|
|
playerMovesNext = not black
|
|
|
|
while True:
|
|
if playerMovesNext:
|
|
if black:
|
|
mc.postToChat("Black to move.")
|
|
else:
|
|
mc.postToChat("White to move.")
|
|
if demo:
|
|
sunfish.tp = OrderedDict()
|
|
move,score = sunfish.search(pos)
|
|
else:
|
|
moves = tuple(pos.genMoves())
|
|
move = None
|
|
while move not in moves:
|
|
if move is not None:
|
|
mc.postToChat("Illegal move.")
|
|
mc.postToChat("Right-click the start and end points with a sword.")
|
|
move = toNumericMove(inputMove(),black)
|
|
rowColMove = toRowColMove(move,black)
|
|
mc.postToChat("Player: "+ toAlgebraicMove(rowColMove))
|
|
animateMove(rowColMove)
|
|
pos = pos.move(move)
|
|
mc.postToChat("Thinking...")
|
|
if demo: sunfish.tp = OrderedDict()
|
|
move,score = sunfish.search(pos)
|
|
if score <= -sunfish.MATE_VALUE:
|
|
mc.postToChat("I resign. You won the game.")
|
|
break
|
|
rowColMove = toRowColMove(move,not black)
|
|
mc.postToChat("Computer: "+toAlgebraicMove(rowColMove))
|
|
animateMove(rowColMove)
|
|
if sunfish.MATE_VALUE <= score:
|
|
mc.postToChat("You lost the game.")
|
|
break
|
|
pos = pos.move(move)
|
|
playerMovesNext = True
|