chess / chessfenbot /helper_functions_chessbot.py
rosenthal's picture
v1 of chess space
e936283
raw
history blame contribute delete
No virus
6.26 kB
# -*- coding: utf-8 -*-
#
# Helper functions for the reddit chessbot
# Includes functions to parse FEN strings and get pithy quotes
import re
from helper_functions import lengthenFEN
from message_template import *
#########################################################
# ChessBot Message Generation Functions
def isPotentialChessboardTopic(sub):
"""if url is imgur link, or url ends in .png/.jpg/.gif"""
if sub.url == None:
return False
return ('imgur' in sub.url
or any([sub.url.lower().endswith(ending) for ending in ['.png', '.jpg', 'jpeg', '.gif']]))
def invert(fen):
return ''.join(reversed(fen))
def generateMessage(fen, certainty, side, visualize_link):
"""Generate response message using FEN, certainty and side for flipping link order"""
vals = {} # Holds template responses
# Things that don't rely on black/white to play
# FEN image link is aligned with screenshot, not side to play
if fen == '8/8/8/8/8/8/8/8':
# Empty chessboard link, fen-to-image doesn't correctly identify those
vals['unaligned_fen_img_link'] = 'http://i.stack.imgur.com/YxP53.gif'
else:
vals['unaligned_fen_img_link'] = 'http://www.fen-to-image.com/image/60/%s.png' % fen
vals['certainty'] = certainty*100.0 # to percentage
vals['pithy_message'] = getPithyMessage(certainty)
if side == 'b':
# Flip FEN if black to play, assumes image is flipped
fen = invert(fen)
inverted_fen = invert(fen)
# Get castling status based on pieces being in initial positions or not
castle_status = getCastlingStatus(fen)
inverted_castle_status = getCastlingStatus(inverted_fen)
# Fill out template and return
vals['fen_w'] = "%s w %s -" % (fen, castle_status)
vals['fen_b'] = "%s b %s -" % (fen, castle_status)
vals['inverted_fen_w'] = "%s w %s -" % (inverted_fen, inverted_castle_status)
vals['inverted_fen_b'] = "%s b %s -" % (inverted_fen, inverted_castle_status)
vals['lichess_analysis_w'] = 'https://www.lichess.org/analysis/%s_w_%s' % (fen, castle_status)
vals['lichess_analysis_b'] = 'https://www.lichess.org/analysis/%s_b_%s' % (fen, castle_status)
vals['lichess_editor_w'] = 'https://www.lichess.org/editor/%s_w_%s' % (fen, castle_status)
vals['lichess_editor_b'] = 'https://www.lichess.org/editor/%s_b_%s' % (fen, castle_status)
vals['inverted_lichess_analysis_w'] = 'https://www.lichess.org/analysis/%s_w_%s' % (inverted_fen, inverted_castle_status)
vals['inverted_lichess_analysis_b'] = 'https://www.lichess.org/analysis/%s_b_%s' % (inverted_fen, inverted_castle_status)
vals['inverted_lichess_editor_w'] = 'https://www.lichess.org/editor/%s_w_%s' % (inverted_fen, inverted_castle_status)
vals['inverted_lichess_editor_b'] = 'https://www.lichess.org/editor/%s_b_%s' % (inverted_fen, inverted_castle_status)
vals['visualize_link'] = visualize_link
return MESSAGE_TEMPLATE.format(**vals)
# Add a little message based on certainty of response
def getPithyMessage(certainty):
pithy_messages = [
'*[\[ ◕ _ ◕\]^*> ... \[⌐■ _ ■\]^*](http://i.imgur.com/yaVftzT.jpg)*',
'A+ ✓',
'✓',
'[Close.](http://i.imgur.com/SwKKZlD.jpg)',
'[WAI](http://gfycat.com/RightHalfIndianglassfish)',
'[:(](http://i.imgur.com/BNwca4R.gifv)',
'[I tried.](http://i.imgur.com/kmmp0lc.png)',
'[Wow.](http://i.imgur.com/67fZDh9.webm)']
pithy_messages_cutoffs = [0.999995, 0.99, 0.9, 0.8, 0.7, 0.5, 0.2, 0.0]
for cuttoff, pithy_message in zip(pithy_messages_cutoffs, pithy_messages):
if certainty >= cuttoff:
return pithy_message
return ""
def getSideToPlay(title, fen):
"""Based on post title return 'w', 'b', or predict from FEN"""
title = title.lower()
# Return if 'black' in title unless 'white to' is, and vice versa, or predict if neither
if 'black' in title:
if 'white to' in title:
return 'w'
return 'b'
elif 'white' in title:
if 'black to' in title:
return 'b'
return 'w'
else:
# Predict side from fen (always returns 'w' or 'b', default 'w')
return predictSideFromFEN(fen)
def predictSideFromFEN(fen):
"""Returns which side it thinks FEN is looking from.
Checks number of white and black pieces on either side to determine
i.e if more black pieces are on 1-4th ranks, then black to play"""
# remove spaces values (numbers) from fen
fen = re.sub('\d','',fen)
#split fen to top half and bottom half (top half first)
parts = fen.split('/')
top = list(''.join(parts[:4]))
bottom = list(''.join(parts[4:]))
# If screenshot is aligned from POV of white to play, we'd expect
# top to be mostly black pieces (lowercase)
# and bottom to be mostly white pieces (uppercase), so lets count
top_count_white = sum(list(map(lambda x: ord(x) <= ord('Z'), top)))
bottom_count_white = sum(list(map(lambda x: ord(x) <= ord('Z'), bottom)))
top_count_black = sum(list(map(lambda x: ord(x) >= ord('a'), top)))
bottom_count_black = sum(list(map(lambda x: ord(x) >= ord('a'), bottom)))
# If more white pieces on top side, or more black pieces on bottom side, black to play
if (top_count_white > bottom_count_white or top_count_black < bottom_count_black):
return 'b'
# Otherwise white
return 'w'
def getCastlingStatus(fen):
"""Check FEN to see if castling is allowed based on initial positions.
Returns 'KQkq' variants or '-' if no castling."""
fen = lengthenFEN(fen) # 71-char long fen
# rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR : initial position
# 01234567 01234567 +63
status = ['','','',''] # KQkq
# Check if black king can castle
if fen[4] == 'k':
# long (q)
if fen[0] == 'r':
status[3] = 'q'
if fen[7] == 'r':
status[2] = 'k'
# Check if white king can castle
if fen[63+4] == 'K':
# long (Q)
if fen[63+0] == 'R':
status[1] = 'Q'
if fen[63+7] == 'R':
status[0] = 'K'
status = ''.join(status)
return status if status else '-'
def getFENtileLetter(fen,letter,number):
"""Given a fen string and a rank (number) and file (letter), return piece letter"""
l2i = lambda l: ord(l)-ord('A') # letter to index
piece_letter = fen[(8-number)*8+(8-number) + l2i(letter)]
return ' KQRBNPkqrbnp'.find(piece_letter)