1
0

cgame.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. #!/usr/bin/env python
  2. """
  3. Class defenition for Carcassonne score keeping system.
  4. Copyright 2018 George C. Privon
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. """
  16. import re as _re
  17. import sys as _sys
  18. from datetime import datetime as _datetime
  19. import sqlite3 as _sqlite3
  20. import numpy as _np
  21. class cgame:
  22. """
  23. Carcassonne game object
  24. """
  25. def __init__(self):
  26. """
  27. Initialize some variables and set up a game
  28. """
  29. self.commands = [('n', 'next round'),
  30. ('r', 'record score and advance turn'),
  31. ('t', 'advance turn, no score'),
  32. ('b', 'additional turn for a player due to a builder'),
  33. ('e', 'end game (or play if already in postgame scoring'),
  34. ('s', '(current) score and game status'),
  35. ('?', 'print help')]
  36. self.conn = _sqlite3.connect('CarcassonneScore.db')
  37. self.cur = self.conn.cursor()
  38. self.setupGame()
  39. def showCommands(self):
  40. """
  41. Print out a list of valid commands for in-game play.
  42. """
  43. _sys.stderr.write('Possible commands:\n')
  44. for entry in self.commands:
  45. _sys.stderr.write('\t' + entry[0] + ': ' + entry[1] + '\n')
  46. def setupGame(self):
  47. """
  48. Initialize a game
  49. """
  50. # 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:
  51. # generate a new game ID and enter a start time
  52. # get a list of players (from database list)
  53. # have user input which expansions are being used
  54. time = _datetime.utcnow().strftime("%Y-%m-%dT%H:%M")
  55. # insert this into the database
  56. # get players for this game
  57. self.getPlayers()
  58. # general information
  59. self.gameID = 0
  60. # get expansions used for this game
  61. self.getExpansions()
  62. # game state information
  63. self.state = 0 # 0 for main game, 1 for postgame, 2 for ended game
  64. self.ntile = 1 # number of tiles played
  65. self.nbuilder = 0 # number of tiles placed due to builders
  66. def getPlayers(self):
  67. """
  68. Get a list of possible players from the database
  69. """
  70. self.players = []
  71. dbplayers = self.cur.execute('''SELECT * FROM players''').fetchall()
  72. if len(dbplayers):
  73. for dbplayer in dbplayers:
  74. print("{0:d}) ".format(dbplayer[0]) + dbplayer[1])
  75. playerinput = input("Please list the IDs for the players in this game (in order of play): ")
  76. playerIDs = [int(x) for x in playerinput.split()]
  77. for playerID in playerIDs:
  78. matched = False
  79. for dbplayer in dbplayers:
  80. if playerID == dbplayer[0]:
  81. self.players.append((playerID, dbplayer[1]))
  82. matched = True
  83. continue
  84. if not matched:
  85. _sys.stderr.write("Error: player ID {0:d} does not match an option from the list.\n".format(playerID))
  86. else:
  87. _sys.stderr.write("Error: players table empty. Exiting.\n")
  88. _sys.exit(-1)
  89. def getExpansions(self):
  90. """
  91. Get a list of playable expansions
  92. """
  93. self.expansionIDs = []
  94. for minisel in range(0, 2):
  95. if minisel:
  96. exptype = "mini"
  97. else:
  98. exptype = "large"
  99. dbexpans = self.cur.execute('''SELECT expansionID,name FROM expansions WHERE active==1 and mini=={0:d}'''.format(minisel)).fetchall()
  100. if len(dbexpans):
  101. for dbexpan in dbexpans:
  102. print("{0:d}) ".format(dbexpan[0]) + dbexpan[1])
  103. expaninput = input("Please list the numbers for the " + exptype + " used in this game: ")
  104. expanIDs = [int(x) for x in expaninput.split()]
  105. for expanID in expanIDs:
  106. for dbexpan in dbexpans:
  107. if expanID == dbexpan[0]:
  108. self.expansionIDs.append(expanID)
  109. else:
  110. sys.stdout.write("No active " + exptype + " expansions found. Continuing.\n")
  111. def recordScore(gameID, playerIDs, expansionIDs, cround, state):
  112. """
  113. Record a score event in the game
  114. """
  115. if state:
  116. ingame = 0
  117. pname = getPlayerNames(playerIDs)
  118. # for i, pname in enumerate(pname):
  119. pID = input("")
  120. # if the builder was used
  121. BUILDERUSED = True
  122. advanceTurn(builder=BUILDERUSED)
  123. return 0
  124. def advanceTurn(self, builder=False):
  125. """
  126. Make a new entry in the turns table
  127. """
  128. command = '''INSERT INTO turns VALUES ({0:d}, {1:d}, '''.format(self.gameID, self.ntile)
  129. command = command + cmdtime
  130. if builder:
  131. bID = 1
  132. else:
  133. bID = 0
  134. # compute playerID based on the turn number minus nbuilders / number of players
  135. playerID = playerIDs[(self.ntile- self.nbuilder) / len(playerIDs)]
  136. command = command + ', {0:d}, {1:d})'.format(bID, playerID)
  137. c.execute(command)
  138. self.ntile += 1
  139. if builder:
  140. self.nbuilder += 1
  141. def runGame(self):
  142. """
  143. Main routine for entering games
  144. """
  145. # here wait for input for scores, advancing to next round, or completion of game
  146. # for each step of entry, present a series of options, based on the list
  147. # of playerIDs and expansions
  148. while self.state < 2:
  149. # set up prompt based on current round
  150. if self.state:
  151. prompt = "postgame > "
  152. else:
  153. prompt = "round: {0:d}, turn: {1:d} > ".format(int(1 + _np.floor((self.ntile-self.nbuilder) / len(self.players))),
  154. self.ntile-self.nbuilder)
  155. try:
  156. cmd = input(prompt)
  157. except (EOFError, KeyboardInterrupt):
  158. _sys.stderr.write('Improper input. Please retry\n')
  159. showCommands()
  160. if _re.match('e', cmd, _re.IGNORECASE):
  161. advanceState()
  162. elif _re.match('s', cmd, _re.IGNORECASE):
  163. printStatus(tilestats=True)
  164. elif _re.match('n', cmd, _re.IGNORECASE):
  165. advanceTurn()
  166. elif _re.match('r', cmd, _re.IGNORECASE):
  167. recordScore()
  168. elif _re.match('t', cmd, _re.IGNORECASE):
  169. advanceTurn(builder=False)
  170. elif _re.match('b', cmd, _re.IGNORECASE):
  171. advanceTurn(builder=True)
  172. elif _re.match('?', cmd, _re.IGNORECASE):
  173. showCommands()
  174. else:
  175. _sys.stderr.write('Command not understood. Please try again.\n')
  176. showCommands()
  177. if state == 2:
  178. #game is over. write end time to the games table
  179. time = _datetime.utcnow().strftime("%Y-%m-%dT%H:%M")
  180. c.execute('''UPDATE games SET endtime = "''' + time + '''" WHERE gameID = ''' + str(gameID))
  181. conn.commit()
  182. printStatus(tilestats=False)
  183. #### Is there a way to capture "ineffective" uses? For example,
  184. #### meeples that don't score points because they end up in a meadow that's
  185. #### controled by someone else?
  186. return 0
  187. def printStatus(self, tilestats=False):
  188. """
  189. Print the total score (current or final) for the specified gameID
  190. """
  191. for playerID in self.playerIDs:
  192. pname = c.execute('SELECT name FROM players WHERE playerID={0:d}'.format(playerID[0])).fetchall()[0]
  193. a = c.execute('SELECT points FROM scores WHER gameID={0:d} and playerID={1:d}'.format(self.gameID, playerID[0]))
  194. res = a.fetchall()
  195. score = _np.sum(res)
  196. print(pname + ': {0:d}'.format(score))
  197. print("{0:d} tiles played out of {1:d} total ({2:d} remaining).".format(self.ntiles,
  198. self.totaltiles,
  199. self.totaltiles - self.ntiles))