File size: 6,255 Bytes
e936283
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# -*- 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)