1
0
Fork 0
mirror of https://github.com/shlomif/PySolFC.git synced 2025-04-05 00:02:29 -04:00

Compare commits

...

8 commits

Author SHA1 Message Date
Shlomi Fish
2d60bdf726 travis / py3 fix - str.replace. 2019-05-05 21:16:07 +03:00
Shlomi Fish
6fbbe82593 Link to PySolFC-Cardsets.
See https://github.com/shlomif/PySolFC/issues/113 .
2019-05-05 18:34:32 +03:00
Shlomi Fish
76804c6d76 flake8 2019-04-30 18:25:01 +03:00
Shlomi Fish
9142b90cb2 Refactoring / code cleanup.
Remove the unused "Comments" class.

See:

* https://en.wikipedia.org/wiki/Code_refactoring

* https://www.refactoring.com/

* https://www.joelonsoftware.com/2002/01/23/rub-a-dub-dub/

Some small optimisations may have slipped in as well.
2019-04-30 17:49:36 +03:00
Shlomi Fish
ea73eb32a3 refactoring 2019-04-30 17:40:18 +03:00
Shlomi Fish
6317008876 traverse the tree for matching modules.
To avoid misses.
2019-04-30 17:35:43 +03:00
Shlomi Fish
33c8f6e375 add tests. 2019-04-30 17:20:47 +03:00
Shlomi Fish
824407b136 Extract a common module/library/header.
This is Refactoring / code cleanup.

See:

* https://refactoring.com/catalog/extractMethod.html

* https://en.wikipedia.org/wiki/Code_refactoring

* https://www.refactoring.com/

* https://www.joelonsoftware.com/2002/01/23/rub-a-dub-dub/

Some small optimisations may have slipped in as well.
2019-04-30 17:14:57 +03:00
6 changed files with 175 additions and 386 deletions

View file

@ -65,7 +65,7 @@ mo:
pretest:
@rm -f tests/individually-importing/*.py # To avoid stray files
python scripts/gen_individual_importing_tests.py
python3 scripts/gen_individual_importing_tests.py
TEST_ENV_PATH = "`pwd`:`pwd`/tests/lib"
TEST_ENV = PYTHONPATH="$$PYTHONPATH:"$(TEST_ENV_PATH) PERL5LIB="$$PERL5LIB:"$(TEST_ENV_PATH)

View file

@ -183,6 +183,7 @@ to its CMake-based build-system:
- [PySol-Sound-Server fork](https://github.com/shlomif/pysol-sound-server)
- [Sources for the PySolFC web site](https://github.com/shlomif/pysolfc-website)
- [PySolFC Announcements Drafts](https://github.com/shlomif/pysolfc-announcements)
- [PySolFC-Cardsets tarballs sources repo](https://github.com/shlomif/PySolFC-Cardsets)
- [Extra mahjongg cardsets for PySolFC - originally for flowersol](https://github.com/shlomif/PySol-Extra-Mahjongg-Cardsets)
- [The old "pysol-music" distribution](https://github.com/shlomif/pysol-music)

View file

@ -22,16 +22,18 @@
# ---------------------------------------------------------------------------##
# imports
import os
import re
import sys
import traceback
from pickle import UnpicklingError
from pysollib.app_stat import GameStat
from pysollib.actions import PysolMenubar
from pysollib.actions import PysolToolbar
from pysollib.app_stat_result import GameStatResult
from pysollib.app_statistics import Statistics
from pysollib.gamedb import GAME_DB, GI, loadGame
from pysollib.help import destroy_help_html, help_about
from pysollib.images import Images, SubsampledImages
from pysollib.mfxutil import Struct, destruct
from pysollib.mfxutil import USE_PIL
@ -65,160 +67,10 @@ else:
from pysollib.pysoltk import destroy_solver_dialog
if TOOLKIT == 'kivy':
import logging
if True: # This prevents from travis 'error' E402.
from pysollib.actions import PysolMenubar
from pysollib.actions import PysolToolbar
from pysollib.help import help_about, destroy_help_html
# ************************************************************************
# * Statistics
# ************************************************************************
_GameStatResult = GameStatResult
class Statistics:
def __init__(self):
self.version_tuple = VERSION_TUPLE
self.saved = 0
# a dictionary of dictionaries of GameStat (keys: player and gameid)
self.games_stats = {}
# a dictionary of lists of tuples (key: player)
self.prev_games = {}
self.all_prev_games = {}
self.session_games = {}
# some simple balance scores (key: gameid)
self.total_balance = {} # a dictionary of integers
self.session_balance = {} # reset per session
self.gameid_balance = 0 # reset when changing the gameid
def new(self):
return Statistics()
#
# player & demo statistics
#
def resetStats(self, player, gameid):
self.__resetPrevGames(player, self.prev_games, gameid)
self.__resetPrevGames(player, self.session_games, gameid)
if player not in self.games_stats:
return
if gameid == 0:
# remove all games
try:
del self.games_stats[player]
except KeyError:
pass
else:
try:
del self.games_stats[player][gameid]
except KeyError:
pass
def __resetPrevGames(self, player, games, gameid):
if player not in games:
return
if gameid == 0:
del games[player]
else:
games[player] = [g for g in games[player] if g[0] != gameid]
def getStats(self, player, gameid):
# returned (won, lost)
return self.getFullStats(player, gameid)[:2]
def getFullStats(self, player, gameid):
# returned (won, lost, playing time, moves)
stats = self.games_stats
if player in stats and gameid in stats[player]:
s = self.games_stats[player][gameid]
return (s.num_won+s.num_perfect,
s.num_lost,
s.time_result.average,
s.moves_result.average,)
return (0, 0, 0, 0)
def getSessionStats(self, player, gameid):
games = self.session_games.get(player, [])
games = [g for g in games if g[0] == gameid]
won = len([g for g in games if g[2] > 0])
lost = len([g for g in games if g[2] == 0])
return won, lost
def updateStats(self, player, game, status):
ret = None
log = (game.id, game.getGameNumber(format=0), status,
game.gstats.start_time, game.gstats.total_elapsed_time,
VERSION_TUPLE, game.getGameScore(), game.getGameScoreCasino(),
game.GAME_VERSION)
# full log
if status >= 0:
if player is None:
# demo
ret = self.updateGameStat(player, game, status)
else:
# player
if player not in self.prev_games:
self.prev_games[player] = []
self.prev_games[player].append(log)
if player not in self.all_prev_games:
self.all_prev_games[player] = []
self.all_prev_games[player].append(log)
ret = self.updateGameStat(player, game, status)
# session log
if player not in self.session_games:
self.session_games[player] = []
self.session_games[player].append(log)
return ret
def updateGameStat(self, player, game, status):
#
if player not in self.games_stats:
self.games_stats[player] = {}
if game.id not in self.games_stats[player]:
game_stat = GameStat(game.id)
self.games_stats[player][game.id] = game_stat
else:
game_stat = self.games_stats[player][game.id]
if 'all' not in self.games_stats[player]:
all_games_stat = GameStat('all')
self.games_stats[player]['all'] = all_games_stat
else:
all_games_stat = self.games_stats[player]['all']
all_games_stat.update(game, status)
return game_stat.update(game, status)
# def __setstate__(self, state): # for backward compatible
# if 'gameid' not in state:
# self.gameid = None
# self.__dict__.update(state)
# ************************************************************************
# * Comments
# ************************************************************************
class Comments:
def __init__(self):
self.version_tuple = VERSION_TUPLE
self.saved = 0
#
self.comments = {}
def new(self):
return Comments()
def setGameComment(self, gameid, text):
player = None
key = (1, gameid, player)
self.comments[key] = str(text)
def getGameComment(self, gameid):
player = None
key = (1, gameid, player)
return self.comments.get(key, "")
# ************************************************************************
# * Application
# * This is the glue between the toplevel window and a Game.
@ -231,7 +83,6 @@ class Application:
self.opt = Options()
self.startup_opt = self.opt.copy()
self.stats = Statistics()
self.comments = Comments()
self.splashscreen = 1
# visual components
self.top = None # the root toplevel window
@ -344,12 +195,6 @@ class Application:
except Exception:
traceback.print_exc()
pass
# try to load comments
try:
self.loadComments()
except Exception:
traceback.print_exc()
pass
# startup information
if self.getGameClass(self.opt.last_gameid):
self.nextgame.id = self.opt.last_gameid
@ -518,12 +363,6 @@ class Application:
except Exception:
traceback.print_exc()
pass
# save comments
try:
self.saveComments()
except Exception:
traceback.print_exc()
pass
# shut down audio
try:
self.audio.destroy()
@ -1006,7 +845,7 @@ Please select a %s type %s.
return cs
#
# load & save options, statistics and comments
# load & save options, and statistics
#
def loadOptions(self):
@ -1035,14 +874,6 @@ Please select a %s type %s.
self.stats.session_balance = {}
self.stats.gameid_balance = 0
def loadComments(self):
if not os.path.exists(self.fn.comments):
return
comments = unpickle(self.fn.comments)
if comments:
# print "loaded:", comments.__dict__
self.comments.__dict__.update(comments.__dict__)
def __saveObject(self, obj, fn):
obj.version_tuple = VERSION_TUPLE
obj.saved += 1
@ -1054,9 +885,6 @@ Please select a %s type %s.
def saveStatistics(self):
self.__saveObject(self.stats, self.fn.stats)
def saveComments(self):
self.__saveObject(self.comments, self.fn.comments)
#
# access games database
#

143
pysollib/app_statistics.py Normal file
View file

@ -0,0 +1,143 @@
#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
# ---------------------------------------------------------------------------##
#
# Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer
# Copyright (C) 2003 Mt. Hood Playing Card Co.
# Copyright (C) 2005-2009 Skomoroh
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# ---------------------------------------------------------------------------##
from pysollib.app_stat import GameStat
from pysollib.settings import VERSION_TUPLE
class Statistics:
def __init__(self):
self.version_tuple = VERSION_TUPLE
self.saved = 0
# a dictionary of dictionaries of GameStat (keys: player and gameid)
self.games_stats = {}
# a dictionary of lists of tuples (key: player)
self.prev_games = {}
self.all_prev_games = {}
self.session_games = {}
# some simple balance scores (key: gameid)
self.total_balance = {} # a dictionary of integers
self.session_balance = {} # reset per session
self.gameid_balance = 0 # reset when changing the gameid
def new(self):
return Statistics()
#
# player & demo statistics
#
def resetStats(self, player, gameid):
self.__resetPrevGames(player, self.prev_games, gameid)
self.__resetPrevGames(player, self.session_games, gameid)
if player not in self.games_stats:
return
if gameid == 0:
# remove all games
try:
del self.games_stats[player]
except KeyError:
pass
else:
try:
del self.games_stats[player][gameid]
except KeyError:
pass
def __resetPrevGames(self, player, games, gameid):
if player not in games:
return
if gameid == 0:
del games[player]
else:
games[player] = [g for g in games[player] if g[0] != gameid]
def getStats(self, player, gameid):
# returned (won, lost)
return self.getFullStats(player, gameid)[:2]
def getFullStats(self, player, gameid):
# returned (won, lost, playing time, moves)
stats = self.games_stats
if player in stats and gameid in stats[player]:
s = self.games_stats[player][gameid]
return (s.num_won+s.num_perfect,
s.num_lost,
s.time_result.average,
s.moves_result.average,)
return (0, 0, 0, 0)
def getSessionStats(self, player, gameid):
games = self.session_games.get(player, [])
games = [g for g in games if g[0] == gameid]
won = len([g for g in games if g[2] > 0])
lost = len([g for g in games if g[2] == 0])
return won, lost
def updateStats(self, player, game, status):
ret = None
log = (game.id, game.getGameNumber(format=0), status,
game.gstats.start_time, game.gstats.total_elapsed_time,
VERSION_TUPLE, game.getGameScore(), game.getGameScoreCasino(),
game.GAME_VERSION)
# full log
if status >= 0:
if player is None:
# demo
ret = self.updateGameStat(player, game, status)
else:
# player
if player not in self.prev_games:
self.prev_games[player] = []
self.prev_games[player].append(log)
if player not in self.all_prev_games:
self.all_prev_games[player] = []
self.all_prev_games[player].append(log)
ret = self.updateGameStat(player, game, status)
# session log
if player not in self.session_games:
self.session_games[player] = []
self.session_games[player].append(log)
return ret
def updateGameStat(self, player, game, status):
#
if player not in self.games_stats:
self.games_stats[player] = {}
if game.id not in self.games_stats[player]:
game_stat = GameStat(game.id)
self.games_stats[player][game.id] = game_stat
else:
game_stat = self.games_stats[player][game.id]
if 'all' not in self.games_stats[player]:
all_games_stat = GameStat('all')
self.games_stats[player]['all'] = all_games_stat
else:
all_games_stat = self.games_stats[player]['all']
all_games_stat.update(game, status)
return game_stat.update(game, status)
# def __setstate__(self, state): # for backward compatible
# if 'gameid' not in state:
# self.gameid = None
# self.__dict__.update(state)

View file

@ -29,10 +29,24 @@ import traceback
from pickle import Pickler, Unpickler, UnpicklingError
from pysollib.gamedb import GI
from pysollib.help import help_about
from pysollib.hint import DefaultHint
from pysollib.mfxutil import Image, ImageTk, USE_PIL
from pysollib.mfxutil import Struct, SubclassResponsibility, destruct
from pysollib.mfxutil import format_time, print_err
from pysollib.mfxutil import uclock, usleep
from pysollib.move import AFlipAllMove
from pysollib.move import AFlipAndMoveMove
from pysollib.move import AFlipMove
from pysollib.move import AMoveMove
from pysollib.move import ANextRoundMove
from pysollib.move import ASaveSeedMove
from pysollib.move import ASaveStateMove
from pysollib.move import AShuffleStackMove
from pysollib.move import ASingleCardMove
from pysollib.move import ASingleFlipMove
from pysollib.move import ATurnStackMove
from pysollib.move import AUpdateStackMove
from pysollib.mygettext import _
from pysollib.mygettext import ungettext
from pysollib.pysolrandom import LCRandom31, PysolRandom, constructRandom, \
@ -57,14 +71,6 @@ if TOOLKIT == 'tk':
else:
from pysollib.pysoltk import reset_solver_dialog
if True: # This prevents from travis 'error' E402.
from pysollib.move import AMoveMove, AFlipMove, AFlipAndMoveMove
from pysollib.move import ASingleFlipMove, ATurnStackMove
from pysollib.move import ANextRoundMove, ASaveSeedMove, AShuffleStackMove
from pysollib.move import AUpdateStackMove, AFlipAllMove, ASaveStateMove
from pysollib.move import ASingleCardMove
from pysollib.hint import DefaultHint
from pysollib.help import help_about
PLAY_TIME_TIMEOUT = 200

View file

@ -9,206 +9,17 @@ from sys import platform
IS_MAC = (platform == "darwin")
PY_VERS = ([] if re.search("\\bSKIP_PY2\\b",
os.getenv('TEST_TAGS', '')) else [2])+[3]
for module_name in \
[
'pysollib.acard',
'pysollib.actions',
'pysollib.app',
'pysollib.app_stat',
'pysollib.app_stat_result',
'pysollib.configobj.configobj',
'pysollib.configobj.validate',
'pysollib.customgame',
'pysollib.game',
'pysollib.gamedb',
'pysollib.games.acesup',
'pysollib.games.algerian',
'pysollib.games.auldlangsyne',
'pysollib.games.bakersdozen',
'pysollib.games.bakersgame',
'pysollib.games.beleagueredcastle',
'pysollib.games.bisley',
'pysollib.games.braid',
'pysollib.games.bristol',
'pysollib.games.buffalobill',
'pysollib.games.calculation',
'pysollib.games.camelot',
'pysollib.games.canfield',
'pysollib.games.capricieuse',
'pysollib.games.curdsandwhey',
'pysollib.games.dieboesesieben',
'pysollib.games.diplomat',
'pysollib.games.doublets',
'pysollib.games.eiffeltower',
'pysollib.games.fan',
'pysollib.games.fortythieves',
'pysollib.games.freecell',
'pysollib.games.glenwood',
'pysollib.games.golf',
'pysollib.games.grandduchess',
'pysollib.games.grandfathersclock',
'pysollib.games.gypsy',
'pysollib.games.harp',
'pysollib.games.headsandtails',
'pysollib.games.katzenschwanz',
'pysollib.games.klondike',
'pysollib.games.labyrinth',
'pysollib.games.larasgame',
'pysollib.games.mahjongg.mahjongg',
'pysollib.games.mahjongg.mahjongg1',
'pysollib.games.mahjongg.mahjongg2',
'pysollib.games.mahjongg.mahjongg3',
'pysollib.games.mahjongg.shisensho',
'pysollib.games.matriarchy',
'pysollib.games.montana',
'pysollib.games.montecarlo',
'pysollib.games.napoleon',
'pysollib.games.needle',
'pysollib.games.numerica',
'pysollib.games.osmosis',
'pysollib.games.parallels',
'pysollib.games.pasdedeux',
'pysollib.games.picturegallery',
'pysollib.games.pileon',
'pysollib.games.pushpin',
'pysollib.games.pyramid',
'pysollib.games.royalcotillion',
'pysollib.games.royaleast',
'pysollib.games.sanibel',
'pysollib.games.siebenbisas',
'pysollib.games.simplex',
'pysollib.games.special.hanoi',
'pysollib.games.special.memory',
'pysollib.games.special.pegged',
'pysollib.games.special.poker',
'pysollib.games.special.tarock',
'pysollib.games.spider',
'pysollib.games.sthelena',
'pysollib.games.sultan',
'pysollib.games.takeaway',
'pysollib.games.terrace',
'pysollib.games.threepeaks',
'pysollib.games.tournament',
'pysollib.games.ultra.dashavatara',
'pysollib.games.ultra.hanafuda',
'pysollib.games.ultra.hanafuda1',
'pysollib.games.ultra.hanafuda_common',
'pysollib.games.ultra.hexadeck',
'pysollib.games.ultra.larasgame',
'pysollib.games.ultra.matrix',
'pysollib.games.ultra.mughal',
'pysollib.games.ultra.tarock',
'pysollib.games.unionsquare',
'pysollib.games.wavemotion',
'pysollib.games.windmill',
'pysollib.games.yukon',
'pysollib.games.zodiac',
'pysollib.help',
'pysollib.hint',
'pysollib.images',
'pysollib.init',
'pysollib.layout',
'pysollib.macosx.appSupport',
'pysollib.main',
'pysollib.mfxutil',
'pysollib.move',
'pysollib.mygettext',
'pysollib.options',
'pysollib.pysolaudio',
'pysollib.pysolgtk.card',
'pysollib.pysolgtk.colorsdialog',
'pysollib.pysolgtk.edittextdialog',
'pysollib.pysolgtk.findcarddialog',
'pysollib.pysolgtk.fontsdialog',
'pysollib.pysolgtk.gameinfodialog',
'pysollib.pysolgtk.menubar',
'pysollib.pysolgtk.playeroptionsdialog',
'pysollib.pysolgtk.progressbar',
'pysollib.pysolgtk.pysoltree',
'pysollib.pysolgtk.selectcardset',
'pysollib.pysolgtk.selectgame',
'pysollib.pysolgtk.selecttile',
'pysollib.pysolgtk.soundoptionsdialog',
'pysollib.pysolgtk.statusbar',
'pysollib.pysolgtk.timeoutsdialog',
'pysollib.pysolgtk.tkcanvas',
'pysollib.pysolgtk.tkconst',
'pysollib.pysolgtk.tkhtml',
'pysollib.pysolgtk.tkstats',
'pysollib.pysolgtk.tkutil',
'pysollib.pysolgtk.tkwidget',
'pysollib.pysolgtk.tkwrap',
'pysollib.pysolgtk.toolbar',
'pysollib.pysolrandom',
'pysollib.pysoltk',
'pysollib.resource',
'pysollib.settings',
'pysollib.stack',
'pysollib.stats',
'pysollib.tile.basetilemfxdialog',
'pysollib.tile.colorsdialog',
'pysollib.tile.edittextdialog',
'pysollib.tile.fontsdialog',
'pysollib.tile.gameinfodialog',
'pysollib.tile.menubar',
'pysollib.tile.playeroptionsdialog',
'pysollib.tile.progressbar',
'pysollib.tile.selectcardset',
'pysollib.tile.selectgame',
'pysollib.tile.selecttile',
'pysollib.tile.selecttree',
'pysollib.tile.solverdialog',
'pysollib.tile.soundoptionsdialog',
'pysollib.tile.statusbar',
'pysollib.tile.timeoutsdialog',
'pysollib.tile.tkhtml',
'pysollib.tile.tkstats',
'pysollib.tile.tktree',
'pysollib.tile.tkwidget',
'pysollib.tile.toolbar',
'pysollib.tile.ttk',
'pysollib.tile.wizarddialog',
'pysollib.tk.colorsdialog',
'pysollib.tk.edittextdialog',
'pysollib.tk.fontsdialog',
'pysollib.tk.gameinfodialog',
'pysollib.tk.menubar',
'pysollib.tk.playeroptionsdialog',
'pysollib.tk.progressbar',
'pysollib.tk.selectcardset',
'pysollib.tk.selectgame',
'pysollib.tk.selecttile',
'pysollib.tk.selecttree',
'pysollib.tk.solverdialog',
'pysollib.tk.soundoptionsdialog',
'pysollib.tk.statusbar',
'pysollib.tk.tabpage',
'pysollib.tk.timeoutsdialog',
'pysollib.tk.tkhtml',
'pysollib.tk.tkstats',
'pysollib.tk.tktree',
'pysollib.tk.tkwidget',
'pysollib.tk.toolbar',
'pysollib.tk.wizarddialog',
'pysollib.ui.tktile.card',
'pysollib.ui.tktile.colorsdialog',
'pysollib.ui.tktile.edittextdialog',
'pysollib.ui.tktile.findcarddialog',
'pysollib.ui.tktile.menubar',
'pysollib.ui.tktile.solverdialog',
'pysollib.ui.tktile.tkcanvas',
'pysollib.ui.tktile.tkconst',
'pysollib.ui.tktile.tkhtml',
'pysollib.ui.tktile.tkutil',
'pysollib.ui.tktile.tkwrap',
'pysollib.util',
'pysollib.winsystems.aqua',
'pysollib.winsystems.common',
'pysollib.winsystems.win32',
'pysollib.winsystems.x11',
'pysollib.wizardpresets',
'pysollib.wizardutil',
]:
module_names = []
for d, _, files in os.walk("pysollib"):
for f in files:
if re.search("\\.py$", f):
module_names.append(
(d + "/" + re.sub("\\.py$", "", f)).replace("/", "."))
module_names.sort()
for module_name in module_names:
if "kivy" in module_name:
continue
is_gtk = ("gtk" in module_name)
for ver in PY_VERS:
if ((not is_gtk) or (ver == 2 and (not IS_MAC))):