Skip to content
Michael Murray edited this page Mar 21, 2015 · 9 revisions

Welcome to the cloaca wiki!

Architecture

There are a few classes handling server operation.

  • Game
  • GameState
  • Player
  • Building

The GameState object represents the physical game state, containing a library of Orders cards, zones for the pool, jacks, and sites and a list of Player objects. The Player and Building classes are containers used by GameState. The Game class is a high-level object that runs the server, manipulating the GameState, Player, and Building objects. Note that there is no separate Card class.

Building class

Building is a container representing a building in GtR. There are lists of cards for the site, foundation, added building materials, materials added by the Stairway, and a flag that indicates completion.

Player class

Player is a container for all the zones associated with a player. This includes the hand, camp, stockpile, vault, clientele, buildings, and influence. Member functions are limited because the rules that apply to players go beyond the buildings they have completed, possibly including other player's buildings affected by the Stairway.

GameState class

GameState represents the physical game state, including a list with a Player object for each player. Other members include the deck, Jacks, pool, sites, and flags related to the game state, eg. whose turn it is. The rules of the game are not enforced by the GameState object, but physical limits (eg. trying to draw from an empty deck) are enforced. There are some member functions designed to be make manipulation more convenient, but they generally do not check legality.

In the current incarnation, there are member functions that print the game state to stdout, but will likely change. Since the GameState class contains the stack that controls game flow, saving and loading this object is enough to fully restore a game.

Game class

The Game class should maybe be more properly called Server. Game has a GameState object member which it manipulates while maintaining the flow of the game. After initializing the GameState and the players' names and turn order, Game begins by calling take_turn(first_player). This method starts the series of steps that make up a player's turn and eventually lead to calling take_turn(next_player). A schematic example, showing the member functions of Game that are called at each step.

- take_turn(p1)
  - lead_role_action(p1)                   # asks if/what p1 is leading
  - follow_role_action(p2)                 # asks if p2 is following
    - perform_thinker_action(p2)           # thinking, not leading
  - perform_role_being_led(p1)             # performs role (also clients)
     - perform_craftsman_action(p1)
     - perform_clientele_action(p1, 'Craftsman')
  - perform_role_being_led(p2)
     - perform_clientele_action(p2, 'Craftsman') # p2 still gets clientele
  - end_turn(p1)
    - kids_in_pool()
- take_turn(p2)
  - ...

The control flow in the Game class is handled by a stack. See the section below for more information.

Cards

Cards do not have their own class. Instead, they are passed around as strings corresponding to the building name (or the material name, for sites). The card_manager module handles conversions from the building name to coin value, material, role, etc. This module also imports the cards from a json dictionary of all legal cards, which only includes the I.V. edition.

Player interation : "Dialog" methods

There are several Dialog methods that are used to interact with the player, using the rudimentary Python function raw_input(). There is a Dialog for every possible information request from the player. Here's a non-exhaustive list:

  • ThinkerOrLeadDialog()
  • UseLatrineDialog()
  • UseVomitoriumDialog()
  • ThinkerTypeDialog()
  • LeadRoleDialog()
  • FollowRoleDialog()
  • LaborerDialog()
  • PatronFromPoolDialog()
  • PatronFromDeckDialog()
  • PatronFromHandDialog()

The atomicity of these information requests is somewhat arbitrary, but there are subtleties. For instance, notice that there are separate PatronFromPool, PatronFromDeck, and PatronFromHand Dialogs, reflecting the possibility that the Bath allows any of these patron actions to change relevant information, such as the clientele limit. However, there is a single LaborerDialog() that asks the player to select card from the pool and a card from his hand if he has a Dock, all in one go. This is possible because peforming one of the two modes doesn't affect the other.

These methods are intended as a stop-gap until a proper client can be developed. They are member functions of the Game class, printing the requests directly to stdout and taking input typed at the command line. The upgrade path will be to implement the Dialogs as a self-contained LocalTerminalClient that can register itself with Game. Additional clients, eg. HTMLClient, or GUIClient could then be devloped with the same interface.

Turn State Stack

The game flow is controlled with a stack. Game actions will push new frames on the stack as the last thing they do. A central control loop will process the top frame of the stack in an infinite loop.

In the Game class, this loop is contained in the run() function.

while(self.game_state.stack.stack):
  self.save_game_state('tmp/log_state')
  self.process_stack_frame()

The stack frames (see stack.py) have a member that's the name of a function in gtr.py, and a list of arguments to this function.

The process_stack_frame() function pops the top frame and evaluates it. Upon returning from this function, the loop recycles and the next frame is called. Because of this, the stack frame must push new frames as the last action in its function call. Any further action would occur before the pushed frames, ruining the last-in-first-out order.

The stack allows adjustment of the granularity of certain maintenance tasks, like saving the game or refreshing the screen. If we want to save before a particular game action, we just need to make that game action its own stack frame.

Game Persistence

Another advantage of the game state stack is that the game can easily be saved with finer granularity than ever turn. Before each stack frame is evaluated, the whole GameState is pickled and written to a text file in the current directory. The most recent file can be loaded by calling Game.run(True).

Building Functionality

Because there is a limited set of buildings that is unlikely to change, they are implemented by coding them directly into the game rules. An alternative would be to provide hooks in the turn sequence and player actions, and allow scripts associated with each card's function to hook into these.

These are the buildings that are not yet fully implemented:

  • Forum
  • Prison
  • Wall
  • Bridge
  • Coliseum
  • Palisade

The Legionary-focused buildings are not implemented because Legionary isn't fully implemented.

The Forum needs checks for victory inserted after every game action that could result in a change.

  1. Completion of any building
  2. Addition of a material with stairway
  3. After Prison resolves
  4. Client gained