Created
June 18, 2015 03:15
-
-
Save hdon/68c40e9afc4b403e207a to your computer and use it in GitHub Desktop.
Chess for IRC I guess...
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# coding=utf8 | |
""" | |
willie-chess.py - chess, duh | |
""" | |
# whipped this up at lunch. | |
# clone https://github.com/embolalia/willie | |
# put this in ./willie/modules | |
# create mysql database and table "chessgame" | |
# all moves are validated but en passant and castling aren't implemented | |
# also check and checkmate are not implemented | |
# Oh, I guess that I also forgot the create table statement, but I'm sure | |
# it goes something like this: | |
# | |
# create table chessgame ( | |
# id int primary key auto_increment | |
# , whiteNick char(32) not null | |
# , blackNick char(32) not null | |
# , numMoves int not null | |
# , state enum('white to move', 'black to move', other unimplemented game states...) | |
# , board char(64) not null default 'rbhqkhbrpppppppp you figure out the rest, caps for other color' | |
# , castlingPiecesUnmoved char(12) not null default 'h1h5h8a1a5a8' /* or is it s/5/6 idr; castling isn't even implemented who cares */ | |
# ); | |
# TODO determine why i should care about threading wrt mysql connector | |
from __future__ import unicode_literals | |
import willie, phpserialize | |
from willie.module import commands, example, NOLIMIT | |
from willie.formatting import colors, color | |
import mysql.connector | |
from mysql.connector import errorcode | |
mysqlConfig = { | |
'user': 'mysqluser' | |
, 'password': 'mysqlpassword' | |
, 'host': '127.0.0.1' | |
# 'host': 'web-server' | |
, 'database': 'chess' | |
, 'raise_on_warnings': True | |
} | |
# key=nick, value=gameID | |
# if a command is issued, it can infer game ID from this, the last referred to game | |
activeGameByNick = {} | |
def namePiece(c): | |
c = c.tolower() | |
if c == 'p': return 'pawn' | |
elif c == 'r': return 'rook' | |
elif c == 'h': return 'knight' | |
elif c == 'b': return 'bishop' | |
elif c == 'q': return 'queen' | |
elif c == 'k': return 'king' | |
elif c == ' ': return 'empty square' | |
else: return 'unknown piece "%s"' % c | |
def requireDatabase(func): | |
def thunk(bot, trigger): | |
try: | |
cnx = mysql.connector.connect(**mysqlConfig) | |
return func(bot, trigger, cnx) | |
finally: | |
if cnx: cnx.close() | |
return thunk | |
@commands('chess') | |
@example('.chess challenge <opponent-nick>') # only the first example seems to work | |
@example('.chess show <game-id>') | |
@example('.chess list') | |
@example('.chess move <game-id> <from-space-id> <to-space-id>') | |
@willie.module.thread(False) # Run in main thread? | |
@requireDatabase | |
def chess(bot, trigger, cnx): | |
'''Every chess command''' | |
if trigger.group(2) is None: | |
bot.reply('examples of how to use .chess command:') | |
bot.reply('.chess challenge <opponent-nick>') | |
bot.reply('.chess show <game-id>') | |
bot.reply('.chess list') | |
bot.reply('.chess move <game-id> <from-space-id> <to-space-id>') | |
return | |
args = trigger.group(2).lower().split() | |
activeGameID = activeGameByNick[trigger.nick] if trigger.nick in activeGameByNick else None | |
if args[0] == 'challenge': | |
chesschallenge(bot, trigger, cnx, args) | |
return | |
if args[0] == 'list': | |
chessgames(bot, trigger, cnx, activeGameID) | |
return | |
if args[0] == 'show': | |
if len(args) == 1: | |
args.append(activeGameID) | |
chessshow(bot, trigger, cnx, args) | |
return | |
if args[0] == 'castle': | |
bot.reply('castling not yet supported') | |
else: | |
if len(args) < 3: | |
raise ValueError('not enough arguments') | |
if len(args) == 3: | |
args = [args[0], activeGameID, args[1], args[2]] | |
if len(args) != 4: | |
raise ValueError('Wrong number of arguments') | |
chessmove(bot, trigger, cnx, args) | |
def chesschallenge(bot, trigger, cnx, args): | |
"""Begin a new game of chess with named player.""" | |
whiteNick = args[1] | |
if whiteNick: | |
blackNick = str(trigger.nick).lower() | |
cursor = cnx.cursor() | |
cursor.execute(''' | |
INSERT INTO chessgame ( | |
whiteNick | |
, blackNick | |
, board | |
) VALUES ( | |
%s | |
, %s | |
, 'rhbqkbhrpppppppp PPPPPPPPRHBQKBHR' | |
) | |
''', [whiteNick, blackNick]) | |
cnx.commit() | |
cursor.close() | |
bot.say('%s has been challenged to game #%d by %s' % (whiteNick, cursor.lastrowid, blackNick)) | |
def chessgames(bot, trigger, cnx, activeGameID): | |
"""Enumerate the games you are playing.""" | |
nick = str(trigger.nick).lower() | |
cursor = cnx.cursor() | |
cursor.execute(''' | |
SELECT | |
id | |
, whiteNick | |
, blackNick | |
, state | |
, numMoves | |
FROM chessgame | |
WHERE | |
(whiteNick = %s OR blackNick = %s) | |
AND (state = 'white to move' OR state = 'black to move') | |
''', [nick, nick]) | |
for row in cursor: | |
gameID = row[0] | |
whiteNick = row[1] | |
blackNick = row[2] | |
state = row[3] | |
numMoves = row[4] | |
a = '*' if gameID == activeGameID else ' ' | |
bot.reply('%c % 3d moves | game id: % 3d | %s | %8s (white) vs. %8s (black)' % (a, numMoves, gameID, state, whiteNick, blackNick)) | |
cursor.close() | |
@commands('colortest') | |
def colortest(bot, trigger): | |
for bg in range(0,99): | |
line = '%02d: ' % bg | |
for fg in range(0,99): | |
fgs = '%02d' % fg | |
line += color(fgs + willie.formatting.bold(fgs), bg, fg) | |
bot.reply(line) | |
def chessshow(bot, trigger, cnx, args): | |
print 'args:', args | |
"""See the state of the identified game.""" | |
gameID = int(args[1]) | |
if gameID: | |
cursor = cnx.cursor() | |
cursor.execute(''' | |
SELECT | |
board | |
, whiteNick | |
, blackNick | |
, state | |
, numMoves | |
FROM chessgame | |
WHERE | |
id = %s | |
''', [gameID]) | |
width = 26 | |
for row in cursor: # lel only one result at most | |
bot.reply('Game #%d | %d moves | %s' % (gameID, row[4], row[3])) | |
s = row[2] + ' (black)' | |
bot.reply((width - len(s)) / 2 * ' ' + s) | |
bot.reply(' A B C D E F G H') | |
board = row[0] | |
for i in range(8): | |
line = str(8-i) + ' ' | |
x = -1 | |
for c in row[0][i*8:i*8+8]: | |
x += 1 | |
Cc = c.lower() | |
if Cc == 'h': | |
Cc = 'k' | |
elif Cc == 'k': | |
Cc = '&' | |
line += color(' %s ' % willie.formatting.bold(Cc), colors.MAROON if c.isupper() else 15, colors.BLACK if (x+i)%2 else 8) | |
bot.reply(line) | |
s = row[1] + ' (white)' | |
bot.reply((width - len(s)) / 2 * ' ' + s) | |
cursor.close() | |
def chessmove(bot, trigger, cnx, args): | |
"""Make a move.""" | |
try: | |
discard, gameID, src, dst = args | |
gameID = int(gameID) | |
assert(len(src) == 2 and len(dst) == 2 and 'a' <= src[0] and 'a' <= dst[0] and src[0] <= 'h' and dst[0] <= 'h' and '0' <= src[1] and '0' <= dst[1] and src[1] <= '8' and dst[1] <= '8') | |
except Exception as e: | |
bot.reply('invalid arguments (maybe forgot the game ID?)') | |
return | |
srcFile = ord(src[0]) - ord('a') # source x | |
dstFile = ord(dst[0]) - ord('a') # destination x | |
srcRank = 8 - int(src[1]) # source y | |
dstRank = 8 - int(dst[1]) # destination y | |
bot.reply('trying to move (%d,%d) to (%d,%d)' % (srcFile, srcRank, dstFile, dstRank)) # sx, sy, dx, dy | |
cursor = cnx.cursor() | |
cursor.execute(''' | |
SELECT | |
board | |
, whiteNick | |
, blackNick | |
, state | |
, numMoves | |
, castlingPiecesUnmoved | |
FROM chessgame | |
WHERE | |
id = %s | |
''', [gameID]) | |
moved = False | |
row = cursor.fetchone() | |
if not row: | |
bot.reply('could not find game #%d' % gameID) | |
return | |
activeGameByNick[trigger.nick] = gameID | |
playerMoving = False | |
if (str(trigger.nick).lower() == row[1] and row[3] == 'white to move'): | |
playerMoving = 'w' | |
elif (str(trigger.nick).lower() == row[2] and row[3] == 'black to move'): | |
playerMoving = 'b' | |
if playerMoving: | |
bot.reply('game %s %d moves %s %s (WHITE) vs. %s (black)' % (gameID, row[4], row[3], row[1], row[2])) | |
board = moveChessPiece(row[0], playerMoving, (srcFile, srcRank), (dstFile, dstRank)) | |
castlingPiecesUnmoved = row[5] | |
if src.upper() in row[5]: | |
castlingPiecesUnmoved = castlingPiecesUnmoved.replace(src.upper(),'') | |
sql = ''' | |
UPDATE chessgame | |
SET | |
board = %s | |
, state = if(state = 'white to move', 'black to move', 'white to move') | |
, numMoves = numMoves + 1 | |
, castlingPiecesUnmoved = %s | |
WHERE | |
id = %s | |
''' | |
print sql | |
cursor.execute(sql, [board, castlingPiecesUnmoved, gameID]) | |
else: | |
bot.reply('it is not your turn to move in game #%d' % gameID) | |
return | |
cnx.commit() | |
cursor.close() | |
# args: board, player, source, destination | |
# b = board string | |
# player = 'w' | 'b' | |
# s = (x, y) source square coordinates | |
# d = (x, y) destination square coordinates | |
def moveChessPiece(b, player, s, d): | |
# Validate that there is movement | |
if s == d: | |
return ValueError('Flag is not moving. Wind is not moving. Mind is not moving.') | |
# Split string into list | |
b = list(b) | |
# Extract pieces | |
Pc = b[s[1] * 8 + s[0]] | |
Dpc = b[d[1] * 8 + d[0]] | |
# Validate the piece | |
if Pc == ' ': | |
raise ValueError("If you call this a piece, you deny its present existence.") | |
# Validate piece ownership | |
PcWhite = Pc.isupper() | |
DpcWhite = not Dpc.islower() # lel | |
capturing = (Dpc != ' ') and (PcWhite != DpcWhite) | |
if (player == 'w') != PcWhite: | |
raise ValueError("You cannot move your opponent's pieces!") | |
# Validate the movement | |
pc = Pc.lower() | |
dx = d[0]-s[0] | |
dy = d[1]-s[1] | |
print 'source piece:', Pc | |
print 'destination piece:', Dpc | |
print 'capturing:', capturing | |
if pc == 'k': # king | |
if abs(dx) + abs(dy) != 1: | |
raise ValueError("Piece cannot move that way") | |
elif pc == 'q': # queen | |
if (abs(dx) != abs(dy)) and (dx) and (dy): | |
raise ValueError("Piece cannot move that way") | |
elif pc == 'r': # rook | |
if (dx) and (dy): | |
raise ValueError("Piece cannot move that way") | |
elif pc == 'b': # bishop | |
if abs(dx) != abs(dy): | |
raise ValueError("Piece cannot move that way") | |
elif pc == 'h': # knight | |
if (abs(dx) + abs(dy) != 3) or dx == 0 or dy == 0: | |
raise ValueError("Piece cannot move that way") | |
elif pc == 'p': | |
if (abs(dy) > 2) or ((dy > 0) == PcWhite) or (abs(dx) > 1) or (abs(dx) != capturing) or ((abs(dy) == 2) and (dx or (s[1] != 6 if PcWhite else s[1] != 1))): | |
raise ValueError("Piece cannot move that way") | |
# TODO castling | |
# check for mid-flight collisions | |
if pc != 'h': | |
dxy = dx or dy | |
cx = 1 if dx > 0 else (-1 if dx < 0 else 0) | |
cy = 1 if dy > 0 else (-1 if dy < 0 else 0) | |
print 'collision check:', dxy, cx, cy | |
for i in xrange(1, dxy) if dxy > 0 else xrange(1, -dxy): | |
print ' check', i, s[0] + i * cx, s[1] + i * cy | |
if b[(s[1] + i * cy) * 8 + s[0] + i * cx] != ' ': | |
raise ValueError('There is another piece in the way') | |
print 'move succeeds' | |
b[d[1] * 8 + d[0]] = Pc | |
b[s[1] * 8 + s[0]] = ' ' | |
return ''.join(b) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment