|
|
@@ -0,0 +1,185 @@
|
|
|
+#!/usr/bin/env python
|
|
|
+"""
|
|
|
+Carcassonne score keeping system.
|
|
|
+"""
|
|
|
+
|
|
|
+import re as _re
|
|
|
+import sys as _sys
|
|
|
+from datetime import datetime as _datetime
|
|
|
+import numpy as _np
|
|
|
+
|
|
|
+
|
|
|
+class cgame:
|
|
|
+ """
|
|
|
+ Carcassonne game object
|
|
|
+ """
|
|
|
+
|
|
|
+ def __init__(self):
|
|
|
+ """
|
|
|
+ Initialize some variables and set up a game
|
|
|
+ """
|
|
|
+
|
|
|
+ self.commands = [('n', 'next round'),
|
|
|
+ ('r', 'record score and advance turn'),
|
|
|
+ ('t', 'advance turn, no score'),
|
|
|
+ ('b', 'additional turn for a player due to a builder'),
|
|
|
+ ('e', 'end game (or play if already in postgame scoring'),
|
|
|
+ ('s', '(current) score and game status'),
|
|
|
+ ('?', 'print help')]
|
|
|
+ setupGame()
|
|
|
+
|
|
|
+
|
|
|
+ def showCommands(self):
|
|
|
+ """
|
|
|
+ Print out a list of valid commands for in-game play.
|
|
|
+ """
|
|
|
+
|
|
|
+ _sys.stderr.write('Possible commands:\n')
|
|
|
+ for entry in self.commands:
|
|
|
+ _sys.stderr.write('\t' + entry[0] + ': ' + entry[1] + '\n')
|
|
|
+
|
|
|
+
|
|
|
+ def setupGame(self):
|
|
|
+ """
|
|
|
+ Initialize a game
|
|
|
+ """
|
|
|
+
|
|
|
+ # check to see if there's a game which has not yet been finished (i.e., has a starttime but no endtime). If there is, resume it. Otherwise:
|
|
|
+ # generate a new game ID and enter a start time
|
|
|
+ # get a list of players (from database list)
|
|
|
+ # have user input which expansions are being used
|
|
|
+ time = _datetime.utcnow().strftime("%Y-%m-%dT%H:%M")
|
|
|
+
|
|
|
+ # insert this into the database
|
|
|
+
|
|
|
+ # general information
|
|
|
+ self.gameID =
|
|
|
+ # array of tuples with playerID and name from DB
|
|
|
+ self.players =
|
|
|
+ self.expansionIDs =
|
|
|
+
|
|
|
+ # game state information
|
|
|
+ self.state = 0 # 0 for main game, 1 for postgame, 2 for ended game
|
|
|
+ self.ntile = 1 # number of tiles played
|
|
|
+ self.nbuilder = 0 # number of tiles placed due to builders
|
|
|
+
|
|
|
+
|
|
|
+ def recordScore(gameID, playerIDs, expansionIDs, cround, state):
|
|
|
+ """
|
|
|
+ Record a score event in the game
|
|
|
+ """
|
|
|
+
|
|
|
+ if state:
|
|
|
+ ingame = 0
|
|
|
+
|
|
|
+ pname = getPlayerNames(playerIDs)
|
|
|
+
|
|
|
+
|
|
|
+ # for i, pname in enumerate(pname):
|
|
|
+
|
|
|
+ pID = input("")
|
|
|
+
|
|
|
+ # if the builder was used
|
|
|
+ BUILDERUSED = True
|
|
|
+
|
|
|
+ advanceTurn(cmdtime, gameID, playerID, nturn, builder=BUILDERUSED)
|
|
|
+
|
|
|
+ return 0
|
|
|
+
|
|
|
+
|
|
|
+ def advanceTurn(self, builder=False):
|
|
|
+ """
|
|
|
+ Make a new entry in the turns table
|
|
|
+ """
|
|
|
+
|
|
|
+ command = '''INSERT INTO turns VALUES ({0:d}, {1:d}, '''.format(self.gameID, self.nturn)
|
|
|
+ command = command + cmdtime
|
|
|
+ if builder:
|
|
|
+ bID = 1
|
|
|
+ else:
|
|
|
+ bID = 0
|
|
|
+
|
|
|
+ # compute playerID based on the turn number minus nbuilders / number of players
|
|
|
+ playerID = playerIDs[(nturns - nbuilder) / lnen(playerIDs)]
|
|
|
+ command = command + ', {0:d}, {1:d})'.format(bID, playerID)
|
|
|
+
|
|
|
+ c.execute(command)
|
|
|
+
|
|
|
+ self.nturn += 1
|
|
|
+ if builder:
|
|
|
+ self.nbuilder += 1
|
|
|
+
|
|
|
+
|
|
|
+ def runGame(self):
|
|
|
+ """
|
|
|
+ Main routine for entering games
|
|
|
+ """
|
|
|
+
|
|
|
+ # here wait for input for scores, advancing to next round, or completion of game
|
|
|
+
|
|
|
+ # for each step of entry, present a series of options, based on the list
|
|
|
+ # of playerIDs and expansions
|
|
|
+
|
|
|
+ while self.state < 2:
|
|
|
+ # set up prompt based on current round
|
|
|
+ if self.state:
|
|
|
+ prompt = "postgame > "
|
|
|
+ else:
|
|
|
+ prompt = "round: {0:d}, turn: {1:d} > ".format(1 + _np.floor((self.nturn-self.nbuilder) / len(self.playerIDs)),
|
|
|
+ self.nturn-self.nbuilder)
|
|
|
+
|
|
|
+ try:
|
|
|
+ text = input(prompt)
|
|
|
+ except (EOFError, KeyboardInterrupt):
|
|
|
+ _sys.stderr.write('Improper input. Please retry\n')
|
|
|
+ showCommands()
|
|
|
+
|
|
|
+ if _re.match('e', cmd, _re.IGNORECASE):
|
|
|
+ advanceState()
|
|
|
+ elif _re.match('s', cmd, _re.IGNORECASE):
|
|
|
+ printStatus(tilestats=True)
|
|
|
+ elif _re.match('n', cmd, _re.IGNORECASE):
|
|
|
+ advanceTurn()
|
|
|
+ elif _re.match('r', cmd, _re.IGNORECASE):
|
|
|
+ recordScore()
|
|
|
+ elif _re.match('t', cmd, _re.IGNORECASE):
|
|
|
+ advanceTurn(builder=False)
|
|
|
+ elif _re.match('b', cmd, _re.IGNORECASE):
|
|
|
+ advanceTurn(builder=True)
|
|
|
+ elif _re.match('?'. cmd, _re.IGNORECASE):
|
|
|
+ showCommands()
|
|
|
+ else:
|
|
|
+ _sys.stderr.write('Command not understood. Please try again.\n')
|
|
|
+ showCommands()
|
|
|
+
|
|
|
+ if state == 2:
|
|
|
+ #game is over. write end time to the games table
|
|
|
+ time = _datetime.utcnow().strftime("%Y-%m-%dT%H:%M")
|
|
|
+ c.execute('''UPDATE games SET endtime = "''' + time + '''" WHERE gameID = ''' + str(gameID))
|
|
|
+ conn.commit()
|
|
|
+
|
|
|
+ printStatus(tilestats=False)
|
|
|
+
|
|
|
+ #### Is there a way to capture "ineffective" uses? For example,
|
|
|
+ #### meeples that don't score points because they end up in a meadow that's
|
|
|
+ #### controled by someone else?
|
|
|
+
|
|
|
+ return 0
|
|
|
+
|
|
|
+
|
|
|
+ def printStatus(self, tilestats=False):
|
|
|
+ """
|
|
|
+ Print the total score (current or final) for the specified gameID
|
|
|
+ """
|
|
|
+
|
|
|
+ for playerID in self.playerIDs:
|
|
|
+ pname = c.execute('SELECT name FROM players WHERE playerID={0:d}'.format(playerID[0])).fetchall()[0]
|
|
|
+ a = c.execute('SELECT points FROM scores WHER gameID={0:d} and playerID={1:d}'.format(self.gameID, playerID[0]))
|
|
|
+ res = a.fetchall()
|
|
|
+ score = _np.sum(res)
|
|
|
+
|
|
|
+ print(pname + ': {0:d}'.format(score))
|
|
|
+
|
|
|
+ print("{0:d} tiles played out of {1:d} total ({2:d} remaining).".format(self.ntiles,
|
|
|
+ self.totaltiles,
|
|
|
+ self.totaltiles - self.ntiles)
|