瀏覽代碼

0.4.0 release

George C. Privon 7 年之前
父節點
當前提交
0e1b91f18d
共有 9 個文件被更改,包括 144 次插入7 次删除
  1. 1 0
      .gitignore
  2. 10 0
      CHANGELOG.md
  3. 2 0
      CarcassonneScore.conf.example
  4. 3 1
      CarcassonneScore.py
  5. 5 1
      README.md
  6. 27 5
      cgame.py
  7. 26 0
      manage_database.py
  8. 6 0
      utilities/README.md
  9. 64 0
      utilities/merge_games.py

+ 1 - 0
.gitignore

@@ -104,4 +104,5 @@ venv.bak/
 .mypy_cache/
 
 CarcassonneScore.db
+CarcassonneScore.conf
 *.swp

+ 10 - 0
CHANGELOG.md

@@ -1,5 +1,15 @@
 # Changelog
 
+## 0.4 Series
+
+### 0.4.0
+
+#### Enhancements
+
+* add a configuration file specifying the database location
+* add a utilities directory for scripts which should not be needed for normal operation but may be useful for working around issues.
+* add scoring capability for Trade tokens (Traders & Builders Expanion). Token allocation is not automatically tracked (this can be tracked manually using the scoring comments). Trade token scores are entered during the post-game scoring period.
+
 ## 0.3 Series
 
 ### 0.3.4 (28 February 2018)

+ 2 - 0
CarcassonneScore.conf.example

@@ -0,0 +1,2 @@
+[CarcassonneScore]
+DBNAME = CarcassonneScore.db

+ 3 - 1
CarcassonneScore.py

@@ -31,6 +31,8 @@ def getargs():
 
     parser = argparse.ArgumentParser(description="Carcassonne score keeping \
 system.")
+    parser.add_argument('-c', '--config', default='CarcassonneScore.conf',
+                        help='Location of the configuration file.')
 
     return parser.parse_args()
 
@@ -42,7 +44,7 @@ def main():
 
     args = getargs()
 
-    mygame = cgame.cgame()
+    mygame = cgame.cgame(config=args.config)
 
     mygame.runGame()
 

+ 5 - 1
README.md

@@ -39,4 +39,8 @@ Use `python manage_database.py -h` to see the full list of options.
 
 ### Analysis
 
-Some jupyter notebooks with sample analysis and a sample sqlite database containing one game is available in the `analysis/` directory.
+Some jupyter notebooks with sample analysis and a sample sqlite database containing one game are available in the `analysis/` directory.
+
+### Miscellaneous Utilities
+
+The `utilities/` directory contains scripts which may be useful for correcting issues, but these should not be needed for normal operations.

+ 27 - 5
cgame.py

@@ -18,8 +18,10 @@ You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
 
+import os as _os
 import re as _re
 import sys as _sys
+import configparser as _configparser
 from datetime import datetime as _datetime
 import sqlite3 as _sqlite3
 import numpy as _np
@@ -30,7 +32,7 @@ class cgame:
     Carcassonne game object
     """
 
-    def __init__(self):
+    def __init__(self, config='CarcassonneScore.db'):
         """
         Initialize some variables and set up a game
         """
@@ -42,13 +44,28 @@ class cgame:
                          ('q', 'quit (will be removed for real gameplay'),
                          ('?', 'print help')]
 
-        self.conn = _sqlite3.connect('CarcassonneScore.db')
+        self.loadConfig(config)
+
+        self.conn = _sqlite3.connect(self.config.get('CarcassonneScore', 'DBNAME'))
         self.cur = self.conn.cursor()
 
         self.timefmt = "%Y-%m-%dT%H:%M:%S"
         self.setupGame()
 
 
+    def loadConfig(self, cfile):
+        """
+        Load configuration file
+        """
+
+        if not _os.path.isfile(cfile):
+            _sys.stderr.write("Error: could not find configuration file '" + cfile + "'\n")
+            _sys.exit()
+
+        self.config = _configparser.RawConfigParser()
+        self.config.read(cfile)
+
+
     def showCommands(self):
         """
         Print out a list of valid commands for in-game play.
@@ -153,7 +170,9 @@ class cgame:
 
     def getExpansions(self):
         """
-        Get a list of playable expansions
+        Get a list of playable expansions.
+        Ask the user which ones are active.
+        Based on the list, add token, tile, and score types to the basic list.
         """
 
         self.expansionIDs = []
@@ -278,7 +297,9 @@ class cgame:
 
         # see which token scored
         # really this should be expanded to allow multiple token types for one score
-        if len(self.tokens) > 1:
+        if score['scoretype'] == 'Trade token':
+            score['tokens'] = 'none'
+        elif len(self.tokens) > 1:
             VALID = False
             while not VALID:
                 for i, token in enumerate(self.tokens):
@@ -427,8 +448,9 @@ class cgame:
             self.commands = [('r', 'record score'),
                              ('e', 'end game (or end play if already in postgame scoring)'),
                              ('s', '(current) score and game status')]
+            # add trade token scoring to the game scoring options
             if 2 in self.expansionIDs:
-                self.commands.append(('t', 'score trade goods'))
+                self.scoretypes.append('Trade token')
             self.commands.append(('?', 'print help'))
 
             _sys.stdout.write("At the end of regulation... ")

+ 26 - 0
manage_database.py

@@ -21,10 +21,32 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import sys
 import os
 import argparse
+import configparser
 import re
 import sqlite3
 
 
+def loadConfig(cfile):
+    """
+    Load configuration file
+    """
+
+    if not os.path.isfile(cfile):
+        sys.stderr.write("Error: could not find configuration file '" + cfile + "'\n")
+        if os.path.isfile('CarcassonneScore.conf.example'):
+            sys.stderr.write("Copying 'CarcassonneScore.conf.example' to 'CarcassonneScore.conf'.\n")
+            import shutil
+            shutil.copyfile('CarcassonneScore.conf.example',
+                            'CarcassonneScore.conf')
+        else:
+            sys.stderr.write("Error: cannot find default configuration file 'CarcassonneScore.conf.example' to copy.\n")
+
+    config = configparser.RawConfigParser()
+    config.read(cfile)
+
+    return config
+
+
 def parseArgs():
     """
     Command line arguments
@@ -32,6 +54,8 @@ def parseArgs():
 
     parser = argparse.ArgumentParser(description="Update the Carcassonne \
 scoring database.")
+    parser.add_argument('-c', '--config', default='CarcassonneScore.conf',
+                        help='Location of the configuration file.')
     cmds = parser.add_mutually_exclusive_group(required=True)
     cmds.add_argument('--init', default=False, action='store_true',
                       help='Create a fresh database.')
@@ -270,6 +294,8 @@ def main():
 
     args = parseArgs()
 
+    copts = loadConfig(args.config)
+
     DBNAME = 'CarcassonneScore.db'
     DBVER = 0 
 

+ 6 - 0
utilities/README.md

@@ -0,0 +1,6 @@
+# Miscellaneous Utilities
+
+Scripts in this directory should not be needed during normal use, but may be helpful if problems are encountered.
+Backing up the `CarcassonneScore.db` file is highly recommended, prior to running any of these.
+
+* `merge_games.py` - A script to merge turns and scores from multiple gameIDs into a single gameID. This is useful if the scoring program crashes during play (as there is currently no way to resume games).

+ 64 - 0
utilities/merge_games.py

@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+"""
+In the event the scoring program is killed or crashes mid-game there is 
+currently no way to resume a game, so a new game must be started.
+This script takes a list of games (provided in the `gameIDs` list) and
+combines them into the first game in the list.
+
+It should work fine, but I'd recommend backing up your CarcassonneScore sqlite
+database, just in case.
+"""
+
+import sqlite3
+
+
+# edit this to say which gameIDs will need to combined into a single game
+# You'll need to look at the actual sqlite database.
+gameIDs = (8, 9, 10)
+
+conn = sqlite3.connect('CarcassonneScore.db')
+cur = conn.cursor()
+
+# update turns and scores
+lastturn = []
+lastscore = []
+for game in gameIDs:
+    lastturn.append(cur.execute('SELECT turnNum FROM turns WHERE gameID={0:1.0f} ORDER BY turnNum DESC LIMIT 1;'.format(game)).fetchall()[0][0])
+    lastscore.append(cur.execute('SELECT scoreID FROM scores WHERE gameID={0:1.0f} ORDER BY scoreID DESC LIMIT 1;'.format(game)).fetchall()[0][0])
+
+curturn = lastturn[0]
+curscore = lastscore[0]
+
+for i, game in enumerate(gameIDs[1:]):
+    print("Fixing game {0:1.0f}".format(game))
+    # get a list of all the turns
+    turns = cur.execute('SELECT gameID,turnNum FROM turns WHERE gameID={0:1.0f}'.format(game)).fetchall()
+    for turn in turns:
+        curturn += 1
+        # update turns database
+        command = 'UPDATE turns SET (gameID,turnNum) = ({0:1.0f},{1:1.0f}) WHERE gameID={2:1.0f} AND turnNum={3:1.0f};'.format(gameIDs[0],curturn,game,turn[1])
+        print(command)
+        cur.execute(command)
+        # update all scores
+        command = 'SELECT gameID,turnNum,scoreID from scores where gameID={0:1.0f} and turnNum={1:1.0f}'.format(game,
+                                                                                                                turn[1])
+
+        turnscores = cur.execute(command).fetchall()
+        for score in turnscores:
+            curscore += 1
+            command = 'UPDATE scores SET (gameID,turnNum,scoreID) = ({0:1.0f},{1:1.0f},{2:1.0f}) WHERE gameID={3:1.0f} AND turnNum={4:1.0f};'.format(gameIDs[0], curturn, curscore, game, turn[1])
+            cur.execute(command)
+
+# fix the end of game scores
+curturn += 1
+command = 'SELECT gameID,turnNum,scoreID from scores where gameID={0:1.0f} and turnNum={1:1.0f}'.format(game,
+                                                                                                        14)
+
+turnscores = cur.execute(command).fetchall()
+for score in turnscores:
+    curscore += 1
+    command = 'UPDATE scores SET (gameID,turnNum,scoreID) = ({0:1.0f},{1:1.0f},{2:1.0f}) WHERE gameID={3:1.0f} AND turnNum={4:1.0f};'.format(gameIDs[0], curturn, curscore, game, 14)
+    cur.execute(command)
+
+conn.commit()
+conn.close()