1#!/usr/bin/env python 2# -*- mode: python; coding: utf-8; -*- 3# --------------------------------------------------------------------------- 4# 5# Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer 6# Copyright (C) 2003 Mt. Hood Playing Card Co. 7# Copyright (C) 2005-2009 Skomoroh 8# 9# This program is free software: you can redistribute it and/or modify 10# it under the terms of the GNU General Public License as published by 11# the Free Software Foundation, either version 3 of the License, or 12# (at your option) any later version. 13# 14# This program is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU General Public License for more details. 18# 19# You should have received a copy of the GNU General Public License 20# along with this program. If not, see <http://www.gnu.org/licenses/>. 21# 22# --------------------------------------------------------------------------- 23 24import os 25import time 26 27from pysollib.mfxutil import KwStruct, kwdefault 28from pysollib.mfxutil import format_time 29from pysollib.mygettext import _ 30from pysollib.settings import TOP_TITLE 31from pysollib.stats import ProgressionFormatter, PysolStatsFormatter 32from pysollib.ui.tktile.tkutil import bind, loadImage 33 34from six.moves import tkinter 35from six.moves import tkinter_font 36 37from .tkwidget import MfxDialog, MfxMessageDialog 38from .tkwidget import MfxScrolledCanvas 39 40# FIXME - this file is a quick hack and needs a rewrite 41 42 43class SingleGame_StatsDialog(MfxDialog): 44 def __init__(self, parent, title, app, player, gameid, **kw): 45 self.app = app 46 self.selected_game = None 47 kw = self.initKw(kw) 48 MfxDialog.__init__(self, parent, title, kw.resizable, kw.default) 49 top_frame, bottom_frame = self.createFrames(kw) 50 self.top_frame = top_frame 51 self.createBitmaps(top_frame, kw) 52 # 53 self.player = player or _("Demo games") 54 self.top.wm_minsize(200, 200) 55 self.button = kw.default 56 # 57 # createChart = self.create3DBarChart 58 createChart = self.createPieChart 59 # createChart = self.createSimpleChart 60 # if parent.winfo_screenwidth() < 800 or parent.winfo_screenheight() < 61 # 600: 62 # createChart = self.createPieChart 63 # createChart = self.createSimpleChart 64 # 65 self.font = self.app.getFont("default") 66 self.tk_font = tkinter_font.Font(self.top, self.font) 67 self.font_metrics = self.tk_font.metrics() 68 self._calc_tabs() 69 # 70 won, lost = app.stats.getStats(player, gameid) 71 createChart(app, won, lost, _("Total")) 72 won, lost = app.stats.getSessionStats(player, gameid) 73 createChart(app, won, lost, _("Current session")) 74 # 75 focus = self.createButtons(bottom_frame, kw) 76 self.mainloop(focus, kw.timeout) 77 78 # 79 # helpers 80 # 81 82 def _calc_tabs(self): 83 # 84 font = self.tk_font 85 t0 = 160 86 t = '' 87 for i in (_("Won:"), 88 _("Lost:"), 89 _("Total:")): 90 if len(i) > len(t): 91 t = i 92 t1 = font.measure(t) 93 # t1 = max(font.measure(_("Won:")), 94 # font.measure(_("Lost:")), 95 # font.measure(_("Total:"))) 96 t1 += 10 97 # t2 = font.measure('99999')+10 98 t2 = 45 99 # t3 = font.measure('100%')+10 100 t3 = 45 101 tx = (t0, t0+t1+t2, t0+t1+t2+t3) 102 # 103 ls = self.font_metrics['linespace'] 104 ls += 5 105 ls = max(ls, 20) 106 ty = (ls, 2*ls, 3*ls+15, 3*ls+25) 107 # 108 self.tab_x, self.tab_y = tx, ty 109 110 def _getPwon(self, won, lost): 111 pwon, plost = 0.0, 0.0 112 if won + lost > 0: 113 pwon = float(won) / (won + lost) 114 pwon = min(max(pwon, 0.00001), 0.99999) 115 plost = 1.0 - pwon 116 return pwon, plost 117 118 def _createChartInit(self, text): 119 w, h = self.tab_x[-1]+20, self.tab_y[-1]+20 120 c = tkinter.Canvas(self.top_frame, width=w, height=h) 121 c.pack(side='top', fill='both', expand=False, padx=20, pady=10) 122 self.canvas = c 123 # self.fg = c.cget("insertbackground") 124 self.fg = c.option_get('foreground', '') or c.cget("insertbackground") 125 # 126 c.create_rectangle(2, 7, w, h, fill="", outline="#7f7f7f") 127 label = tkinter.Label(c, text=text, font=self.font, bd=0, padx=3, 128 pady=1) 129 dy = int(self.font_metrics['ascent']) - 10 130 dy //= 2 131 c.create_window(20, -dy, window=label, anchor="nw") 132 133 def _createChartTexts(self, tx, ty, won, lost): 134 c, tfont, fg = self.canvas, self.font, self.fg 135 pwon, plost = self._getPwon(won, lost) 136 # 137 x = tx[0] 138 dy = int(self.font_metrics['ascent']) - 10 139 dy //= 2 140 c.create_text( 141 x, ty[0]-dy, text=_("Won:"), anchor="nw", font=tfont, fill=fg) 142 c.create_text( 143 x, ty[1]-dy, text=_("Lost:"), anchor="nw", font=tfont, fill=fg) 144 c.create_text( 145 x, ty[2]-dy, text=_("Total:"), anchor="nw", font=tfont, fill=fg) 146 x = tx[1] - 16 147 c.create_text( 148 x, ty[0]-dy, text="%d" % won, anchor="ne", font=tfont, fill=fg) 149 c.create_text( 150 x, ty[1]-dy, text="%d" % lost, anchor="ne", font=tfont, fill=fg) 151 c.create_text( 152 x, ty[2]-dy, text="%d" % (won + lost), anchor="ne", font=tfont, 153 fill=fg) 154 y = ty[2] - 11 155 c.create_line(tx[0], y, x, y, fill=fg) 156 if won + lost > 0: 157 x = tx[2] 158 pw = int(round(100.0 * pwon)) 159 c.create_text( 160 x, ty[0]-dy, text="%d%%" % pw, anchor="ne", 161 font=tfont, fill=fg) 162 c.create_text( 163 x, ty[1]-dy, text="%d%%" % (100-pw), anchor="ne", font=tfont, 164 fill=fg) 165 166# def _createChart3DBar(self, canvas, perc, x, y, p, col): 167# if perc < 0.005: 168# return 169# # translate and scale 170# p = list(p[:]) 171# for i in (0, 1, 2, 3): 172# p[i] = (x + p[i][0], y + p[i][1]) 173# j = i + 4 174# dx = int(round(p[j][0] * perc)) 175# dy = int(round(p[j][1] * perc)) 176# p[j] = (p[i][0] + dx, p[i][1] + dy) 177# # draw rects 178# def draw_rect(a, b, c, d, col, canvas=canvas, p=p): 179# points = (p[a][0], p[a][1], p[b][0], p[b][1], 180# p[c][0], p[c][1], p[d][0], p[d][1]) 181# canvas.create_polygon(points, fill=col) 182# draw_rect(0, 1, 5, 4, col[0]) 183# draw_rect(1, 2, 6, 5, col[1]) 184# draw_rect(4, 5, 6, 7, col[2]) 185# # draw lines 186# def draw_line(a, b, canvas=canvas, p=p): 187# # print a, b, p[a], p[b] 188# canvas.create_line(p[a][0], p[a][1], p[b][0], p[b][1]) 189# draw_line(0, 1) 190# draw_line(1, 2) 191# draw_line(0, 4) 192# draw_line(1, 5) 193# draw_line(2, 6) 194# # draw_line(3, 7) # test 195# draw_line(4, 5) 196# draw_line(5, 6) 197# draw_line(6, 7) 198# draw_line(7, 4) 199# def createSimpleChart(self, app, won, lost, text): 200# #c, tfont, fg = self._createChartInit(frame, 300, 100, text) 201# self._createChartInit(300, 100, text) 202# c, tfont, fg = self.canvas, self.font, self.fg 203# # 204# tx = (90, 180, 210) 205# ty = (21, 41, 75) 206# self._createChartTexts(tx, ty, won, lost) 207# def create3DBarChart(self, app, won, lost, text): 208# image = app.gimages.stats[0] 209# iw, ih = image.width(), image.height() 210# #c, tfont, fg = self._createChartInit(frame, iw+160, ih, text) 211# self._createChartInit(iw+160, ih, text) 212# c, tfont, fg = self.canvas, self.font, self.fg 213# pwon, plost = self._getPwon(won, lost) 214# # 215# tx = (iw+20, iw+110, iw+140) 216# yy = ih//2 # + 7 217# ty = (yy+21-46, yy+41-46, yy+75-46) 218# # 219# c.create_image(0, 7, image=image, anchor="nw") 220# # 221# p = ((0, 0), (44, 6), (62, -9), (20, -14), 222# (-3, -118), (-1, -120), (-1, -114), (-4, -112)) 223# col = ("#00ff00", "#008200", "#00c300") 224# self._createChart3DBar(c, pwon, 102, 145+7, p, col) 225# p = ((0, 0), (49, 6), (61, -10), (15, -15), 226# (1, -123), (3, -126), (4, -120), (1, -118)) 227# col = ("#ff0000", "#860400", "#c70400") 228# self._createChart3DBar(c, plost, 216, 159+7, p, col) 229# # 230# self._createChartTexts(tx, ty, won, lost) 231# c.create_text(tx[0], ty[0]-48, text=self.player, anchor="nw", 232# font=tfont, fill=fg) 233 234 def createPieChart(self, app, won, lost, text): 235 # c, tfont, fg = self._createChartInit(frame, 300, 100, text) 236 # 237 self._createChartInit(text) 238 c, tfont = self.canvas, self.font 239 pwon, plost = self._getPwon(won, lost) 240 # 241 # tx = (160, 250, 280) 242 # ty = (21, 41, 75) 243 # 244 tx, ty = self.tab_x, self.tab_y 245 if won + lost > 0: 246 # s, ewon, elost = 90.0, -360.0 * pwon, -360.0 * plost 247 s, ewon, elost = 0.0, 360.0 * pwon, 360.0 * plost 248 c.create_arc( 249 20, 25+9, 110, 75+9, fill="#007f00", start=s, extent=ewon) 250 c.create_arc( 251 20, 25+9, 110, 75+9, fill="#7f0000", start=s+ewon, 252 extent=elost) 253 c.create_arc( 254 20, 25, 110, 75, fill="#00ff00", start=s, extent=ewon) 255 c.create_arc( 256 20, 25, 110, 75, fill="#ff0000", start=s+ewon, 257 extent=elost) 258 x, y = tx[0] - 25, ty[0] 259 c.create_rectangle(x, y, x+10, y+10, fill="#00ff00") 260 y = ty[1] 261 c.create_rectangle(x, y, x+10, y+10, fill="#ff0000") 262 else: 263 c.create_oval(20, 25+10, 110, 75+10, fill="#7f7f7f") 264 c.create_oval(20, 25, 110, 75, fill="#f0f0f0") 265 c.create_text( 266 65, 50, text=_("No games"), anchor="center", 267 font=tfont, fill="#bfbfbf") 268 # 269 self._createChartTexts(tx, ty, won, lost) 270 271 # 272 # 273 # 274 275 def initKw(self, kw): 276 kw = KwStruct( 277 kw, 278 strings=(_("&OK"), 279 (_("&All games..."), 102), 280 (TOP_TITLE+"...", 105), 281 (_("&Reset..."), 302)), default=0, 282 image=self.app.gimages.logos[5], 283 padx=10, pady=10, 284 ) 285 return MfxDialog.initKw(self, kw) 286 287 288# ************************************************************************ 289# * 290# ************************************************************************ 291 292class CanvasFormatter(PysolStatsFormatter): 293 def __init__(self, app, canvas, parent_window, font, w, h): 294 self.app = app 295 self.canvas = canvas 296 self.parent_window = parent_window 297 # self.fg = canvas.cget("insertbackground") 298 self.fg = canvas.option_get('foreground', '') or \ 299 canvas.cget("insertbackground") 300 self.font = font 301 self.w = w 302 self.h = h 303 # self.x = self.y = 0 304 self.gameid = None 305 self.gamenumber = None 306 self.canvas.config(yscrollincrement=h) 307 self._tabs = None 308 309 def _addItem(self, id): 310 self.canvas.dialog.nodes[id] = (self.gameid, self.gamenumber) 311 312 def _calc_tabs(self, arg): 313 tw = 15*self.w 314 # tw = 160 315 self._tabs = [tw] 316 font = tkinter_font.Font(self.canvas, self.font) 317 for t in arg[1:]: 318 tw = font.measure(t)+20 319 self._tabs.append(tw) 320 self._tabs.append(10) 321 322 def pstats(self, y, args, gameid=None): 323 x = 1 324 t1, t2, t3, t4, t5, t6, t7 = args 325 self.gameid = gameid 326 if gameid is None: # header 327 self.gameid = 'header' 328 for var, text, anchor, tab in ( 329 ('name', t1, 'nw', self._tabs[0]+self._tabs[1]), 330 ('played', t2, 'ne', self._tabs[2]), 331 ('won', t3, 'ne', self._tabs[3]), 332 ('lost', t4, 'ne', self._tabs[4]), 333 ('time', t5, 'ne', self._tabs[5]), 334 ('moves', t6, 'ne', self._tabs[6]), 335 ('percent', t7, 'ne', self._tabs[7]), 336 ): 337 self.gamenumber = None 338 if gameid is None: # header 339 self.gamenumber = var 340 id = self.canvas.create_text(x, y, text=text, anchor=anchor, 341 font=self.font, fill=self.fg) 342 self._addItem(id) 343 x += tab 344 self.pstats_perc(x, y, t7) 345 346 def pstats_perc(self, x, y, t): 347 if not (t and "0" <= t[0] <= "9"): 348 return 349 perc = int(round(float(str(t)))) 350 if perc < 1: 351 return 352 rx, ry, rw, rh = x, y+1, 2 + 8*10, self.h-5 353 if 1: 354 w = int(round(rw*perc/100.0)) 355 if 1 and w < 1: 356 return 357 if w > 0: 358 w = max(3, w) 359 w = min(rw - 2, w) 360 self.canvas.create_rectangle( 361 rx, ry, rx+w, ry+rh, width=1, 362 fill="#00ff00", outline="#000000") 363 if w < rw: 364 self.canvas.create_rectangle( 365 rx+w, ry, rx+rw, ry+rh, width=1, 366 fill="#ff0000", outline="#000000") 367 return 368 # fill = "#ffffff" 369 # fill = self.canvas["bg"] 370 fill = None 371 self.canvas.create_rectangle(rx, ry, rx+rw, ry+rh, width=1, 372 fill=fill, outline="#808080") 373 if 1: 374 rx, rw = rx + 1, rw - 1 375 ry, rh = ry + 1, rh - 1 376 w = int(round(rw*perc/100.0)) 377 if w > 0: 378 self.canvas.create_rectangle(rx, ry, rx+w, ry+rh, width=0, 379 fill="#00ff00", outline="") 380 if w < rw: 381 self.canvas.create_rectangle( 382 rx+w, ry, rx+rw, ry+rh, width=0, 383 fill="#ff0000", outline="") 384 return 385 p = 1.0 386 ix = rx + 2 387 for i in (1, 11, 21, 31, 41, 51, 61, 71, 81, 91): 388 if perc < i: 389 break 390 # c = "#ff8040" 391 r, g, b = 255, 128*p, 64*p 392 c = "#%02x%02x%02x" % (int(r), int(g), int(b)) 393 self.canvas.create_rectangle(ix, ry+2, ix+6, ry+rh-2, width=0, 394 fill=c, outline=c) 395 ix = ix + 8 396 p = max(0.0, p - 0.1) 397 398 def writeStats(self, player, sort_by='name'): 399 header = self.getStatHeader() 400 y = 0 401 if self._tabs is None: 402 self._calc_tabs(header) 403 self.pstats(y, header) 404 # 405 y += 2*self.h 406 for result in self.getStatResults(player, sort_by): 407 gameid = result.pop() 408 self.pstats(y, result, gameid) 409 y += self.h 410 # 411 y += self.h 412 total, played, won, lost, time_, moves, perc = self.getStatSummary() 413 s = _("Total (%(played)d out of %(total)d games)") % { 414 'played': played, 'total': total} 415 self.pstats(y, (s, won+lost, won, lost, time_, moves, perc)) 416 417 def writeLog(self, player, prev_games): 418 y = 0 419 header = self.getLogHeader() 420 t1, t2, t3, t4 = header 421 s = "%-25s %-20s %-17s %s" % header 422 id = self.canvas.create_text(1, y, text=s, anchor="nw", 423 font=self.font, fill=self.fg) 424 self._addItem(id) 425 y += 2*self.h 426 if not player or not prev_games: 427 return 0 428 for result in self.getLogResults(player, prev_games): 429 s = "%-25s %-20s %-17s %s" % tuple(result[:4]) 430 id = self.canvas.create_text(1, y, text=s, anchor="nw", 431 font=self.font, fill=self.fg) 432 y += self.h 433 return 1 434 435 def writeFullLog(self, player): 436 prev_games = self.app.stats.prev_games.get(player) 437 return self.writeLog(player, prev_games) 438 439 def writeSessionLog(self, player): 440 prev_games = self.app.stats.session_games.get(player) 441 return self.writeLog(player, prev_games) 442 443 444# ************************************************************************ 445# * 446# ************************************************************************ 447 448class AllGames_StatsDialogScrolledCanvas(MfxScrolledCanvas): 449 pass 450 451 452class AllGames_StatsDialog(MfxDialog): 453 454 YVIEW = 0 455 FONT_TYPE = "default" 456 457 def __init__(self, parent, title, app, player, **kw): 458 lines = 25 459 # if parent and parent.winfo_screenheight() < 600: 460 # lines = 20 461 # 462 self.font = app.getFont(self.FONT_TYPE) 463 font = tkinter_font.Font(parent, self.font) 464 self.font_metrics = font.metrics() 465 self.CHAR_H = self.font_metrics['linespace'] 466 self.CHAR_W = font.measure('M') 467 self.app = app 468 # 469 self.player = player 470 self.title = title 471 self.sort_by = 'name' 472 self.selected_game = None 473 # 474 kwdefault(kw, width=self.CHAR_W*64, height=lines*self.CHAR_H) 475 kw = self.initKw(kw) 476 MfxDialog.__init__(self, parent, title, kw.resizable, kw.default) 477 top_frame, bottom_frame = self.createFrames(kw) 478 self.createBitmaps(top_frame, kw) 479 # 480 self.top.wm_minsize(200, 200) 481 self.button = kw.default 482 # 483 self.sc = AllGames_StatsDialogScrolledCanvas( 484 top_frame, width=kw.width, height=kw.height) 485 self.sc.pack(fill='both', expand=True, padx=kw.padx, pady=kw.pady) 486 # 487 self.nodes = {} 488 self.canvas = self.sc.canvas 489 self.canvas.dialog = self 490 from pysollib.options import calcCustomMouseButtonsBinding 491 bind( 492 self.canvas, 493 calcCustomMouseButtonsBinding("<{mouse_button1}>"), 494 self.singleClick 495 ) 496 self.fillCanvas(player, title) 497 bbox = self.canvas.bbox("all") 498 # print bbox 499 # self.canvas.config(scrollregion=bbox) 500 dx, dy = 4, 0 501 self.canvas.config(scrollregion=(-dx, -dy, bbox[2]+dx, bbox[3]+dy)) 502 self.canvas.xview_moveto(-dx) 503 self.canvas.yview_moveto(self.YVIEW) 504 # 505 focus = self.createButtons(bottom_frame, kw) 506 self.mainloop(focus, kw.timeout) 507 508 def initKw(self, kw): 509 kw = KwStruct( 510 kw, 511 strings=(_("&OK"), 512 (_("&Save to file"), 202), 513 (_("&Reset all..."), 301),), 514 default=0, 515 resizable=True, 516 padx=10, pady=10, 517 # width=900, 518 ) 519 return MfxDialog.initKw(self, kw) 520 521 def destroy(self): 522 self.app = None 523 self.canvas.dialog = None 524 self.nodes = {} 525 self.sc.destroy() 526 MfxDialog.destroy(self) 527 528 def rearrange(self, sort_by): 529 if self.sort_by == sort_by: 530 return 531 self.sort_by = sort_by 532 self.fillCanvas(self.player, self.title) 533 534 def singleClick(self, event=None): 535 id = self.canvas.find_withtag('current') 536 if not id: 537 return 538 # print 'singleClick:', id, self.nodes.get(id[0]) 539 gameid, gamenumber = self.nodes.get(id[0], (None, None)) 540 if gameid == 'header': 541 if self.sort_by == gamenumber: 542 return 543 self.sort_by = gamenumber 544 self.fillCanvas(self.player, self.title) 545 return 546 # FIXME / TODO 547 return 548 if gameid and gamenumber: 549 print(gameid, gamenumber) 550 elif gameid: 551 print(gameid) 552 553 # 554 # 555 # 556 557 def fillCanvas(self, player, header): 558 self.canvas.delete('all') 559 self.nodes = {} 560 writer = CanvasFormatter(self.app, self.canvas, self, 561 self.font, self.CHAR_W, self.CHAR_H) 562 writer.writeStats(player, self.sort_by) 563 564 565# ************************************************************************ 566# * 567# ************************************************************************ 568 569class FullLog_StatsDialog(AllGames_StatsDialog): 570 YVIEW = 1 571 FONT_TYPE = "fixed" 572 573 def fillCanvas(self, player, header): 574 writer = CanvasFormatter(self.app, self.canvas, self, 575 self.font, self.CHAR_W, self.CHAR_H) 576 writer.writeFullLog(player) 577 578 def initKw(self, kw): 579 kw = KwStruct(kw, 580 strings=(_("&OK"), (_("Session &log..."), 104), 581 (_("&Save to file"), 203)), default=0, 582 width=76*self.CHAR_W, 583 ) 584 return AllGames_StatsDialog.initKw(self, kw) 585 586 587class SessionLog_StatsDialog(FullLog_StatsDialog): 588 def fillCanvas(self, player, header): 589 PysolStatsFormatter() 590 writer = CanvasFormatter(self.app, self.canvas, self, 591 self.font, self.CHAR_W, self.CHAR_H) 592 writer.writeSessionLog(player) 593 594 def initKw(self, kw): 595 kw = KwStruct( 596 kw, 597 strings=(_("&OK"), (_("&Full log..."), 103), 598 (_("&Save to file"), 204)), 599 default=0,) 600 return FullLog_StatsDialog.initKw(self, kw) 601 602# ************************************************************************ 603# * 604# ************************************************************************ 605 606 607class Status_StatsDialog(MfxMessageDialog): 608 def __init__(self, parent, game): 609 stats, gstats = game.stats, game.gstats 610 w1 = w2 = "" 611 n = 0 612 for s in game.s.foundations: 613 n = n + len(s.cards) 614 w1 = (_("Highlight piles: ") + str(stats.highlight_piles) + "\n" + 615 _("Highlight cards: ") + str(stats.highlight_cards) + "\n" + 616 _("Highlight same rank: ") + str(stats.highlight_samerank) + 617 "\n") 618 if game.s.talon: 619 if game.gameinfo.redeals != 0: 620 w2 = w2 + _("\nRedeals: ") + str(game.s.talon.round - 1) 621 w2 = w2 + _("\nCards in Talon: ") + str(len(game.s.talon.cards)) 622 if game.s.waste and game.s.waste not in game.s.foundations: 623 w2 = w2 + _("\nCards in Waste: ") + str(len(game.s.waste.cards)) 624 if game.s.foundations: 625 w2 = w2 + _("\nCards in Foundations: ") + str(n) 626 # 627 date = time.strftime( 628 "%Y-%m-%d %H:%M", 629 time.localtime(game.gstats.start_time)) 630 MfxMessageDialog.__init__( 631 self, parent, title=_("Game status"), 632 text=game.getTitleName() + "\n" + 633 game.getGameNumber(format=1) + "\n" + 634 _("Playing time: ") + game.getTime() + "\n" + 635 _("Started at: ") + date + "\n\n" + 636 _("Moves: ") + str(game.moves.index) + "\n" + 637 _("Undo moves: ") + str(stats.undo_moves) + "\n" + 638 _("Bookmark moves: ") + str(gstats.goto_bookmark_moves) + "\n" + 639 _("Demo moves: ") + str(stats.demo_moves) + "\n" + 640 _("Total player moves: ") + str(stats.player_moves) + "\n" + 641 _("Total moves in this game: ") + str(stats.total_moves) + "\n" + 642 _("Hints: ") + str(stats.hints) + "\n" + 643 "\n" + 644 w1 + w2, 645 strings=(_("&OK"), 646 (_("&Statistics..."), 101), 647 (TOP_TITLE+"...", 105), ), 648 image=game.app.gimages.logos[3], 649 image_side="left", image_padx=20, 650 padx=20, 651 ) 652 653# ************************************************************************ 654# * 655# ************************************************************************ 656 657 658class _TopDialog(MfxDialog): 659 def __init__(self, parent, title, top, **kw): 660 kw = self.initKw(kw) 661 MfxDialog.__init__(self, parent, title, kw.resizable, kw.default) 662 top_frame, bottom_frame = self.createFrames(kw) 663 self.createBitmaps(top_frame, kw) 664 665 cnf = {'master': top_frame, 666 'highlightthickness': 1, 667 'highlightbackground': 'black', 668 } 669 frame = tkinter.Frame(**cnf) 670 frame.pack(expand=True, fill='both', padx=10, pady=10) 671 frame.columnconfigure(0, weight=1) 672 cnf['master'] = frame 673 cnf['text'] = _('N') 674 label = tkinter.Label(**cnf) 675 label.grid(row=0, column=0, sticky='ew') 676 cnf['text'] = _('Game number') 677 label = tkinter.Label(**cnf) 678 label.grid(row=0, column=1, sticky='ew') 679 cnf['text'] = _('Started at') 680 label = tkinter.Label(**cnf) 681 label.grid(row=0, column=2, sticky='ew') 682 cnf['text'] = _('Result') 683 label = tkinter.Label(**cnf) 684 label.grid(row=0, column=3, sticky='ew') 685 686 row = 1 687 for i in top: 688 # N 689 cnf['text'] = str(row) 690 label = tkinter.Label(**cnf) 691 label.grid(row=row, column=0, sticky='ew') 692 # Game number 693 cnf['text'] = '#'+str(i.game_number) 694 label = tkinter.Label(**cnf) 695 label.grid(row=row, column=1, sticky='ew') 696 # Start time 697 t = time.strftime( 698 '%Y-%m-%d %H:%M', time.localtime(i.game_start_time)) 699 cnf['text'] = t 700 label = tkinter.Label(**cnf) 701 label.grid(row=row, column=2, sticky='ew') 702 # Result 703 if isinstance(i.value, float): 704 # time 705 s = format_time(i.value) 706 else: 707 # moves 708 s = str(i.value) 709 cnf['text'] = s 710 label = tkinter.Label(**cnf) 711 label.grid(row=row, column=3, sticky='ew') 712 row += 1 713 714 focus = self.createButtons(bottom_frame, kw) 715 self.mainloop(focus, kw.timeout) 716 717 def initKw(self, kw): 718 kw = KwStruct(kw, strings=(_('&OK'),), default=0, separator=True) 719 return MfxDialog.initKw(self, kw) 720 721 722class Top_StatsDialog(MfxDialog): 723 def __init__(self, parent, title, app, player, gameid, **kw): 724 self.app = app 725 kw = self.initKw(kw) 726 MfxDialog.__init__(self, parent, title, kw.resizable, kw.default) 727 top_frame, bottom_frame = self.createFrames(kw) 728 self.createBitmaps(top_frame, kw) 729 730 frame = tkinter.Frame(top_frame) 731 frame.pack(expand=True, fill='both', padx=10, pady=10) 732 frame.columnconfigure(0, weight=1) 733 734 if (player in app.stats.games_stats and 735 gameid in app.stats.games_stats[player] and 736 app.stats.games_stats[player][gameid].time_result.top): 737 738 tkinter.Label(frame, text=_('Minimum')).grid(row=0, column=1) 739 tkinter.Label(frame, text=_('Maximum')).grid(row=0, column=2) 740 tkinter.Label(frame, text=_('Average')).grid(row=0, column=3) 741 # tkinter.Label(frame, text=_('Total')).grid(row=0, column=4) 742 743 s = app.stats.games_stats[player][gameid] 744 row = 1 745 ll = [ 746 (_('Playing time:'), 747 format_time(s.time_result.min), 748 format_time(s.time_result.max), 749 format_time(s.time_result.average), 750 format_time(s.time_result.total), 751 s.time_result.top, 752 ), 753 (_('Moves:'), 754 s.moves_result.min, 755 s.moves_result.max, 756 round(s.moves_result.average, 2), 757 s.moves_result.total, 758 s.moves_result.top, 759 ), 760 (_('Total moves:'), 761 s.total_moves_result.min, 762 s.total_moves_result.max, 763 round(s.total_moves_result.average, 2), 764 s.total_moves_result.total, 765 s.total_moves_result.top, 766 ), 767 ] 768 # if s.score_result.min: 769 # ll.append(('Score:', 770 # s.score_result.min, 771 # s.score_result.max, 772 # round(s.score_result.average, 2), 773 # s.score_result.top, 774 # )) 775 # if s.score_casino_result.min: 776 # ll.append(('Casino Score:', 777 # s.score_casino_result.min, 778 # s.score_casino_result.max, 779 # round(s.score_casino_result.average, 2), )) 780 for label, min, max, avr, tot, top in ll: 781 tkinter.Label(frame, text=label).grid(row=row, column=0) 782 tkinter.Label(frame, text=str(min)).grid(row=row, column=1) 783 tkinter.Label(frame, text=str(max)).grid(row=row, column=2) 784 tkinter.Label(frame, text=str(avr)).grid(row=row, column=3) 785 # tkinter.Label(frame, text=str(tot)).grid(row=row, column=4) 786 b = tkinter.Button(frame, text=TOP_TITLE+' ...', width=10, 787 command=lambda top=top: self.showTop(top)) 788 b.grid(row=row, column=5) 789 row += 1 790 else: 791 tkinter.Label(frame, text=_('No TOP for this game')).pack() 792 793 focus = self.createButtons(bottom_frame, kw) 794 self.mainloop(focus, kw.timeout) 795 796 def showTop(self, top): 797 # print top 798 _TopDialog(self.top, TOP_TITLE, top) 799 800 def initKw(self, kw): 801 kw = KwStruct(kw, 802 strings=(_('&OK'),), 803 default=0, 804 image=self.app.gimages.logos[4], 805 separator=True, 806 ) 807 return MfxDialog.initKw(self, kw) 808 809 810# ************************************************************************ 811# * 812# ************************************************************************ 813 814class ProgressionDialog(MfxDialog): 815 def __init__(self, parent, title, app, player, gameid, **kw): 816 817 font_name = app.getFont('default') 818 font = tkinter_font.Font(parent, font_name) 819 tkfont = tkinter_font.Font(parent, font) 820 font_metrics = font.metrics() 821 measure = tkfont.measure 822 self.text_height = font_metrics['linespace'] 823 self.text_width = measure('XX.XX.XX') 824 825 self.items = [] 826 self.formatter = ProgressionFormatter(app, player, gameid) 827 828 kw = self.initKw(kw) 829 MfxDialog.__init__(self, parent, title, kw.resizable, kw.default) 830 top_frame, bottom_frame = self.createFrames(kw) 831 self.createBitmaps(top_frame, kw) 832 833 frame = tkinter.Frame(top_frame) 834 frame.pack(expand=True, fill='both', padx=5, pady=10) 835 frame.columnconfigure(0, weight=1) 836 837 # constants 838 self.canvas_width, self.canvas_height = 600, 250 839 if parent.winfo_screenwidth() < 800 or \ 840 parent.winfo_screenheight() < 600: 841 self.canvas_width, self.canvas_height = 400, 200 842 self.xmargin, self.ymargin = 10, 10 843 self.graph_dx, self.graph_dy = 10, 10 844 self.played_color = '#ff7ee9' 845 self.won_color = '#00dc28' 846 self.percent_color = 'blue' 847 # create canvas 848 self.canvas = canvas = tkinter.Canvas(frame, bg='#dfe8ff', 849 highlightthickness=1, 850 highlightbackground='black', 851 width=self.canvas_width, 852 height=self.canvas_height) 853 canvas.pack(side='left', padx=5) 854 # 855 dir = os.path.join('images', 'stats') 856 try: 857 fn = app.dataloader.findImage('progression', dir) 858 self.bg_image = loadImage(fn) 859 canvas.create_image(0, 0, image=self.bg_image, anchor='nw') 860 except Exception: 861 pass 862 # 863 tw = max(measure(_('Games/day')), 864 measure(_('Games/week')), 865 measure(_('% won'))) 866 self.left_margin = self.xmargin+tw//2 867 self.right_margin = self.xmargin+tw//2 868 self.top_margin = 15+self.text_height 869 self.bottom_margin = 15+self.text_height+10+self.text_height 870 # 871 x0, y0 = self.left_margin, self.canvas_height-self.bottom_margin 872 x1, y1 = self.canvas_width-self.right_margin, self.top_margin 873 canvas.create_rectangle(x0, y0, x1, y1, fill='white') 874 # horizontal axis 875 canvas.create_line(x0, y0, x1, y0, width=3) 876 877 # left vertical axis 878 canvas.create_line(x0, y0, x0, y1, width=3) 879 t = _('Games/day') 880 self.games_text_id = canvas.create_text(x0-4, y1-4, anchor='s', text=t) 881 882 # right vertical axis 883 canvas.create_line(x1, y0, x1, y1, width=3) 884 canvas.create_text(x1+4, y1-4, anchor='s', text=_('% won')) 885 886 # caption 887 d = self.text_height 888 x, y = self.xmargin, self.canvas_height-self.ymargin 889 canvas.create_rectangle(x, y, x+d, y-d, outline='black', 890 fill=self.played_color) 891 x += d+5 892 canvas.create_text(x, y, anchor='sw', text=_('Played')) 893 x += measure(_('Played'))+20 894 canvas.create_rectangle(x, y, x+d, y-d, outline='black', 895 fill=self.won_color) 896 x += d+5 897 canvas.create_text(x, y, anchor='sw', text=_('Won')) 898 x += measure(_('Won'))+20 899 canvas.create_rectangle(x, y, x+d, y-d, outline='black', 900 fill=self.percent_color) 901 x += d+5 902 canvas.create_text(x, y, anchor='sw', text=_('% won')) 903 904 # right frame 905 right_frame = tkinter.Frame(frame) 906 right_frame.pack(side='left', fill='x', padx=5) 907 self.all_games_variable = var = tkinter.StringVar() 908 var.set('all') 909 b = tkinter.Radiobutton(right_frame, text=_('All games'), 910 variable=var, value='all', 911 command=self.updateGraph, 912 justify='left', anchor='w' 913 ) 914 b.pack(fill='x', expand=True, padx=3, pady=1) 915 b = tkinter.Radiobutton(right_frame, text=_('Current game'), 916 variable=var, value='current', 917 command=self.updateGraph, 918 justify='left', anchor='w' 919 ) 920 b.pack(fill='x', expand=True, padx=3, pady=1) 921 label_frame = tkinter.LabelFrame(right_frame, text=_('Statistics for')) 922 label_frame.pack(side='top', fill='x', pady=10) 923 self.variable = var = tkinter.StringVar() 924 var.set('week') 925 for v, t in ( 926 ('week', _('Last 7 days')), 927 ('month', _('Last month')), 928 ('year', _('Last year')), 929 ('all', _('All time')), 930 ): 931 b = tkinter.Radiobutton(label_frame, text=t, variable=var, value=v, 932 command=self.updateGraph, 933 justify='left', anchor='w' 934 ) 935 b.pack(fill='x', expand=True, padx=3, pady=1) 936 label_frame = tkinter.LabelFrame(right_frame, text=_('Show graphs')) 937 label_frame.pack(side='top', fill='x') 938 self.played_graph_var = tkinter.BooleanVar() 939 self.played_graph_var.set(True) 940 b = tkinter.Checkbutton(label_frame, text=_('Played'), 941 command=self.updateGraph, 942 variable=self.played_graph_var, 943 justify='left', anchor='w' 944 ) 945 b.pack(fill='x', expand=True, padx=3, pady=1) 946 self.won_graph_var = tkinter.BooleanVar() 947 self.won_graph_var.set(True) 948 b = tkinter.Checkbutton(label_frame, text=_('Won'), 949 command=self.updateGraph, 950 variable=self.won_graph_var, 951 justify='left', anchor='w' 952 ) 953 b.pack(fill='x', expand=True, padx=3, pady=1) 954 self.percent_graph_var = tkinter.BooleanVar() 955 self.percent_graph_var.set(True) 956 b = tkinter.Checkbutton(label_frame, text=_('% won'), 957 command=self.updateGraph, 958 variable=self.percent_graph_var, 959 justify='left', anchor='w' 960 ) 961 b.pack(fill='x', expand=True, padx=3, pady=1) 962 963 self.updateGraph() 964 965 focus = self.createButtons(bottom_frame, kw) 966 self.mainloop(focus, kw.timeout) 967 968 def initKw(self, kw): 969 kw = KwStruct(kw, strings=(_('&OK'),), default=0, separator=True) 970 return MfxDialog.initKw(self, kw) 971 972 def updateGraph(self, *args): 973 interval = self.variable.get() 974 canvas = self.canvas 975 if self.items: 976 canvas.delete(*self.items) 977 self.items = [] 978 979 all_games = (self.all_games_variable.get() == 'all') 980 result = self.formatter.getResults(interval, all_games) 981 982 if interval in ('week', 'month'): 983 t = _('Games/day') 984 else: 985 t = _('Games/week') 986 canvas.itemconfig(self.games_text_id, text=t) 987 988 graph_width = self.canvas_width-self.left_margin-self.right_margin 989 graph_height = self.canvas_height-self.top_margin-self.bottom_margin 990 dx = (graph_width-2*self.graph_dx)//(len(result)-1) 991 graph_dx = (graph_width-(len(result)-1)*dx)//2 992 dy = (graph_height-self.graph_dy)//5 993 x0, y0 = self.left_margin, self.canvas_height-self.bottom_margin 994 x1, y1 = self.canvas_width-self.right_margin, self.top_margin 995 td = self.text_height//2 996 997 # vertical scale 998 x = x0+graph_dx 999 xx = -100 1000 for res in result: 1001 if res[0] is not None and x > xx+self.text_width+4: 1002 # id = canvas.create_line(x, y0, x, y0-5, width=3) 1003 # self.items.append(id) 1004 id = canvas.create_line(x, y0, x, y1, stipple='gray50') 1005 self.items.append(id) 1006 id = canvas.create_text(x, y0+td, anchor='n', text=res[0]) 1007 self.items.append(id) 1008 xx = x 1009 else: 1010 id = canvas.create_line(x, y0, x, y0-3, width=1) 1011 self.items.append(id) 1012 x += dx 1013 1014 # horizontal scale 1015 max_games = max([i[1] for i in result]) 1016 games_delta = max_games//5+1 1017 percent = 0 1018 games = 0 1019 for y in range(y0, y1, -dy): 1020 if y != y0: 1021 id = canvas.create_line(x0, y, x1, y, stipple='gray50') 1022 self.items.append(id) 1023 id = canvas.create_text(x0-td, y, anchor='e', text=str(games)) 1024 self.items.append(id) 1025 id = canvas.create_text(x1+td, y, anchor='w', text=str(percent)) 1026 self.items.append(id) 1027 games += games_delta 1028 percent += 20 1029 1030 # draw result 1031 games_resolution = float(dy)/games_delta 1032 percent_resolution = float(dy)/20 1033 played_coords = [] 1034 won_coords = [] 1035 percent_coords = [] 1036 x = x0+graph_dx 1037 for res in result: 1038 played, won = res[1], res[2] 1039 y = y0 - int(games_resolution*played) 1040 played_coords += [x, y] 1041 y = y0 - int(games_resolution*won) 1042 won_coords += [x, y] 1043 if played > 0: 1044 percent = int(100.*won/played) 1045 else: 1046 percent = 0 1047 y = y0 - int(percent_resolution*percent) 1048 percent_coords += [x, y] 1049 x += dx 1050 if self.played_graph_var.get(): 1051 id = canvas.create_line(fill=self.played_color, width=3, 1052 *played_coords) 1053 self.items.append(id) 1054 if self.won_graph_var.get(): 1055 id = canvas.create_line(fill=self.won_color, width=3, 1056 *won_coords) 1057 self.items.append(id) 1058 if self.percent_graph_var.get(): 1059 id = canvas.create_line(fill=self.percent_color, width=3, 1060 *percent_coords) 1061 self.items.append(id) 1062