1import sys 2from math import e, floor 3from random import randint 4 5from gi.repository import Gtk, GObject 6from gi.repository import Gdk 7 8from pychess.System import uistuff, conf 9from pychess.System.prefix import addDataPrefix 10from pychess.Utils.const import WHITE, DRAW, WHITEWON, BLACKWON 11from pychess.Utils.lutils import leval 12 13__title__ = _("Score") 14__icon__ = addDataPrefix("glade/panel_score.svg") 15__desc__ = _("The score panel tries to evaluate the positions and shows you a graph of the game progress") 16 17 18class Sidepanel: 19 def load(self, gmwidg): 20 self.boardview = gmwidg.board.view 21 self.plot = ScorePlot(self.boardview) 22 self.sw = __widget__ = Gtk.ScrolledWindow() 23 __widget__.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) 24 port = Gtk.Viewport() 25 port.add(self.plot) 26 port.set_shadow_type(Gtk.ShadowType.NONE) 27 __widget__.add(port) 28 __widget__.show_all() 29 30 self.plot_cid = self.plot.connect("selected", self.plot_selected) 31 self.cid = self.boardview.connect('shownChanged', self.shownChanged) 32 self.model_cids = [ 33 self.boardview.model.connect_after("game_changed", self.game_changed), 34 self.boardview.model.connect_after("moves_undone", self.moves_undone), 35 self.boardview.model.connect_after("analysis_changed", self.analysis_changed), 36 self.boardview.model.connect_after("game_started", self.game_started), 37 self.boardview.model.connect_after("game_terminated", self.on_game_terminated), 38 ] 39 40 def cb_config_changed(none): 41 self.fetch_chess_conf() 42 self.plot.redraw() 43 self.cids_conf = [ 44 conf.notify_add("scoreLinearScale", cb_config_changed) 45 ] 46 self.fetch_chess_conf() 47 48 uistuff.keepDown(__widget__) 49 50 return __widget__ 51 52 def fetch_chess_conf(self): 53 self.plot.linear_scale = conf.get("scoreLinearScale") 54 55 def on_game_terminated(self, model): 56 self.plot.disconnect(self.plot_cid) 57 self.boardview.disconnect(self.cid) 58 for cid in self.model_cids: 59 self.boardview.model.disconnect(cid) 60 for cid in self.cids_conf: 61 conf.notify_remove(cid) 62 63 def moves_undone(self, model, moves): 64 for i in range(moves): 65 self.plot.undo() 66 67 # As shownChanged will normally be emitted just after game_changed - 68 # if we are viewing the latest position - we can do the selection change 69 # now, and thereby avoid redraw being called twice 70 if self.plot.selected == model.ply - model.lowply: 71 self.plot.select(model.ply - model.lowply - moves) 72 self.plot.redraw() 73 74 def game_changed(self, model, ply): 75 if len(self.plot) + model.lowply > ply: 76 return 77 78 for i in range(len(self.plot) + model.lowply, ply): 79 if i in model.scores: 80 points = model.scores[i][1] 81 points = points * -1 if i % 2 == 1 else points 82 else: 83 points = leval.evaluateComplete( 84 model.getBoardAtPly(i).board, WHITE) 85 self.plot.addScore(points) 86 87 if model.status == DRAW: 88 points = 0 89 elif model.status == WHITEWON: 90 points = sys.maxsize 91 elif model.status == BLACKWON: 92 points = -sys.maxsize 93 else: 94 if ply in model.scores: 95 points = model.scores[ply][1] 96 points = points * -1 if ply % 2 == 1 else points 97 else: 98 try: 99 points = leval.evaluateComplete( 100 model.getBoardAtPly(ply).board, WHITE) 101 except IndexError: 102 return 103 self.plot.addScore(points) 104 105 # As shownChanged will normally be emitted just after game_changed - 106 # if we are viewing the latest position - we can do the selection change 107 # now, and thereby avoid redraw being called twice 108 if self.plot.selected == ply - model.lowply - 1: 109 self.plot.select(ply - model.lowply) 110 self.plot.redraw() 111 112 # Uncomment this to debug eval function 113 # --- 114 # board = model.boards[-1].board 115 # opboard = model.boards[-1].clone().board 116 # opboard.setColor(1 - opboard.color) 117 # material, phase = leval.evalMaterial(board) 118 # if board.color == WHITE: 119 # print("material", -material) 120 # e1 = leval.evalKingTropism(board) 121 # e2 = leval.evalKingTropism(opboard) 122 # print("evaluation: %d + %d = %d " % (e1, e2, e1 + e2)) 123 # p1 = leval.evalPawnStructure(board, phase) 124 # p2 = leval.evalPawnStructure(opboard, phase) 125 # print("pawns: %d + %d = %d " % (p1, p2, p1 + p2)) 126 # print("knights:", -leval.evalKnights(board)) 127 # print("king:", -leval.evalKing(board, phase)) 128 # else: 129 # print("material", material) 130 # print("evaluation:", leval.evalKingTropism(board)) 131 # print("pawns:", leval.evalPawnStructure(board, phase)) 132 # print("pawns2:", leval.evalPawnStructure(opboard, phase)) 133 # print("pawns3:", leval.evalPawnStructure(board, phase) + 134 # leval.evalPawnStructure(opboard, phase)) 135 # print("knights:", leval.evalKnights(board)) 136 # print("king:", leval.evalKing(board, phase)) 137 # print("----------------------") 138 139 def game_started(self, model): 140 if model.lesson_game: 141 return 142 143 self.game_changed(model, model.ply) 144 145 def shownChanged(self, boardview, shown): 146 if not boardview.shownIsMainLine(): 147 return 148 if self.plot.selected != shown: 149 self.plot.select(shown - self.boardview.model.lowply) 150 self.plot.redraw() 151 152 def analysis_changed(self, gamemodel, ply): 153 if self.boardview.animating: 154 return 155 156 if not self.boardview.shownIsMainLine(): 157 return 158 if ply - gamemodel.lowply > len(self.plot.scores) - 1: 159 # analysis line of yet undone position 160 return 161 162 color = (ply - 1) % 2 163 score = gamemodel.scores[ply][1] 164 score = score * -1 if color == WHITE else score 165 self.plot.changeScore(ply - gamemodel.lowply, score) 166 self.plot.redraw() 167 168 def plot_selected(self, plot, selected): 169 try: 170 board = self.boardview.model.boards[selected] 171 except IndexError: 172 return 173 self.boardview.setShownBoard(board) 174 175 176class ScorePlot(Gtk.DrawingArea): 177 178 __gtype_name__ = "ScorePlot" + str(randint(0, sys.maxsize)) 179 __gsignals__ = {"selected": (GObject.SignalFlags.RUN_FIRST, None, (int, ))} 180 181 def __init__(self, boardview): 182 GObject.GObject.__init__(self) 183 self.boardview = boardview 184 self.connect("draw", self.expose) 185 self.connect("button-press-event", self.press) 186 self.props.can_focus = True 187 self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK | 188 Gdk.EventMask.KEY_PRESS_MASK) 189 self.scores = [] 190 self.selected = 0 191 192 def get_move_height(self): 193 c = self.__len__() 194 w = self.get_allocation().width 195 if c != 0: 196 w = int(floor(w / c)) 197 return max(min(w, 24), 1) 198 199 def addScore(self, score): 200 self.scores.append(score) 201 202 def changeScore(self, ply, score): 203 if self.scores: 204 self.scores[ply] = score 205 206 def __len__(self): 207 return len(self.scores) 208 209 def undo(self): 210 del self.scores[-1] 211 212 def select(self, index): 213 self.selected = index 214 215 def clear(self): 216 del self.scores[:] 217 218 def redraw(self): 219 if self.get_window(): 220 a = self.get_allocation() 221 rect = Gdk.Rectangle() 222 rect.x, rect.y, rect.width, rect.height = (0, 0, a.width, a.height) 223 self.get_window().invalidate_rect(rect, True) 224 self.get_window().process_updates(True) 225 226 def press(self, widget, event): 227 self.grab_focus() 228 self.emit('selected', event.x / self.get_move_height()) 229 230 def expose(self, widget, context): 231 a = widget.get_allocation() 232 context.rectangle(0, 0, a.width, a.height) 233 context.clip() 234 self.draw(context) 235 return False 236 237 def draw(self, cr): 238 m = self.boardview.model 239 if m.isPlayingICSGame(): 240 return 241 242 width = self.get_allocation().width 243 height = self.get_allocation().height 244 245 ######################################## 246 # Draw background # 247 ######################################## 248 249 cr.set_source_rgb(1, 1, 1) 250 cr.rectangle(0, 0, width, height) 251 cr.fill() 252 253 ######################################## 254 # Draw the actual plot (dark area) # 255 ######################################## 256 257 def sign(n): 258 return n == 0 and 1 or n / abs(n) 259 260 def mapper(score): 261 if self.linear_scale: 262 return min(abs(score), 800) / 800 * sign(score) # Linear 263 else: 264 return (e ** (5e-4 * abs(score)) - 1) * sign(score) # Exponentially stretched 265 266 if self.scores: 267 cr.set_source_rgb(0, 0, 0) 268 cr.move_to(0, height) 269 cr.line_to(0, (height / 2.) * (1 + mapper(self.scores[0]))) 270 for i, score in enumerate(self.scores): 271 x = (i + 1) * self.get_move_height() 272 y = (height / 2.) * (1 + mapper(score)) 273 y = max(0, min(height, y)) 274 cr.line_to(x, y) 275 cr.line_to(x, height) 276 cr.fill() 277 else: 278 x = 0 279 cr.set_source_rgb(0.9, 0.9, 0.9) 280 cr.rectangle(x, 0, width, height) 281 cr.fill() 282 283 ######################################## 284 # Draw middle line and markers # 285 ######################################## 286 287 cr.set_line_width(0.25) 288 markers = [16, -16, 8, -8, 3, -3, 0] # centipawns 289 for mark in markers: 290 if mark == 0: 291 cr.set_source_rgb(1, 0, 0) 292 else: 293 cr.set_source_rgb(0.85, 0.85, 0.85) 294 y = (height / 2.) * (1 + mapper(100 * mark)) 295 y = max(0, min(height, y)) 296 cr.move_to(0, y) 297 cr.line_to(width, y) 298 cr.stroke() 299 300 ######################################## 301 # Draw selection # 302 ######################################## 303 304 lw = 2 305 cr.set_line_width(lw) 306 s = self.get_move_height() 307 x = self.selected * s 308 cr.rectangle(x - lw / 2, lw / 2, s + lw, height - lw) 309 found, color = self.get_style_context().lookup_color("p_bg_selected") 310 cr.set_source_rgba(color.red, color.green, color.blue, .15) 311 cr.fill_preserve() 312 cr.set_source_rgb(color.red, color.green, color.blue) 313 cr.stroke() 314