From 846bab0ab1bf1ea3fd8c79970e089a2edeaade9a Mon Sep 17 00:00:00 2001 From: Mat Booth Date: Mon, 24 Sep 2018 10:12:04 +0100 Subject: [PATCH] Settlers of EMF Implement pass-and-play multiplayer, with interstitial screen asking to pass the badge to the next player --- settlers_of_emf/main.py | 158 ++++++++++++++++++++++++++-------------- 1 file changed, 102 insertions(+), 56 deletions(-) diff --git a/settlers_of_emf/main.py b/settlers_of_emf/main.py index c70e75b..520a08a 100644 --- a/settlers_of_emf/main.py +++ b/settlers_of_emf/main.py @@ -1,6 +1,10 @@ """Settlers of EMF -After a long voyage of great deprivation of wifi, your vehicles have finally reached the edge of an uncharted field at the foot of a great castle. The Electromagnetic Field! But you are not the only discoverer. Other fearless voyagers have also arrived at the foot the castle: the race to settle the Electricmagnetic Field has begun! """ +After a long voyage and great deprivation of wifi, your vehicles have finally reached the edge of an uncharted field at the foot of a great castle. The Electromagnetic Field! + +But you are not the only discoverer. Other fearless voyagers have also arrived at the foot the castle; the race to settle the Electricmagnetic Field has begun! + +Settlers of EMF is a pass-and-play game of ruthless strategy for up to 4 players of all ages.""" ___title___ = "Settlers of EMF" ___license___ = "MIT" @@ -97,14 +101,17 @@ class Menu(State): col = c['colour'] if not self.is_choice_enabled(i): col = ugfx.html_color(0x676767) - text = "{} - {} ".format(i + 1, c['name']) - ugfx.text(20, offset + 125, text, col) + if len(self.choices) == 1: + text = "{} ".format(c['name']) + else: + text = "{} - {} ".format(i + 1, c['name']) + ugfx.text(18, offset + 125, text, col) offset = offset + 20 if 'cost' in c: for j in range(len(c['cost'])): cost = c['cost'][j] - ugfx.area((42 * j) + 48, offset + 125, 18, 18, cost['resource']['col']) - ugfx.text((42 * j) + 66, offset + 125, "x{} ".format(cost['amount']), col) + ugfx.area((42 * j) + 46, offset + 125, 18, 18, cost['resource']['col']) + ugfx.text((42 * j) + 64, offset + 125, "x{} ".format(cost['amount']), col) offset = offset + 20 # Set the initial selection @@ -207,8 +214,8 @@ class MainMenu(Menu): ] def __init__(self, disable_continue_option=True): - MainMenu.options[1]['disabled'] = disable_continue_option - super().__init__('Main menu:', MainMenu.options) + MainMenu.options[MainMenu.CONTINUE_GAME]['disabled'] = disable_continue_option + super().__init__('Welcome!', MainMenu.options) class TeamMenu(Menu): @@ -226,52 +233,76 @@ class TeamMenu(Menu): 'colour': ugfx.html_color(0xeaeaea)}, {'name': "Null Sector", 'colour': ugfx.html_color(0x9c27b0)}, + {'name': "Start Game"}, {'name': "Back"}, ] + TEAM_MAX = len(options) - 3 + START_GAME = len(options) - 2 BACK = len(options) - 1 - def __init__(self): - super().__init__('Choose your team:', TeamMenu.options) + def __init__(self, teams): + # Disable team options based on which have already been chosen + for option in TeamMenu.options: + if 'colour' not in option: + continue + option['disabled'] = option['name'] in [team['name'] for team in teams] + TeamMenu.options[TeamMenu.START_GAME]['disabled'] = len(teams) == 0 + super().__init__('Player {}, choose a team:'.format(len(teams) + 1), TeamMenu.options) def get_selected_team(self): return TeamMenu.options[self.selection].copy() -class BuildMenu(Menu): +class ActionMenu(Menu): options = [ - {'name': "Road (0 points)", + {'name': "Build Road (0 points)", 'cost': [{'resource': BRICK, 'amount': 1}, {'resource': WOOD, 'amount': 1}]}, - {'name': "Town (1 point)", + {'name': "Build Town (1 point)", 'cost': [{'resource': BRICK, 'amount': 1}, {'resource': WOOD, 'amount': 1}, {'resource': SHEEP, 'amount': 1}, {'resource': WHEAT, 'amount': 1}]}, - {'name': "City (2 points)", + {'name': "Upgrade to City (2 points)", 'cost': [{'resource': WHEAT, 'amount': 2}, {'resource': ORE, 'amount': 3}]}, + # TODO Implement trading + {'name': "Trade", 'disabled': True}, + {'name': "End Turn"}, {'name': "Back"}, ] + TRADE = len(options) - 3 + END_TURN = len(options) - 2 BACK = len(options) - 1 - def __init__(self, resources): - # Disable options based on whether the player can afford them - for option in BuildMenu.options: - option['disabled'] = False + def __init__(self, resources, dice_roll): + # Disable build options based on whether the player can afford them + for option in ActionMenu.options: if 'cost' not in option: continue + option['disabled'] = False for cost in option['cost']: for resource in resources: if resource.resource == cost['resource']: if resource.quantity < cost['amount']: option['disabled'] = True - super().__init__('Build a thing:', BuildMenu.options) + # Rolling the dice is mandatory, so don't let the turn end unless it happened + ActionMenu.options[ActionMenu.END_TURN]['disabled'] = dice_roll == 0 + super().__init__('Do a thing:', ActionMenu.options) def get_selected_build(self): - return BuildMenu.options[self.selection].copy() + return ActionMenu.options[self.selection].copy() + + +class NextPlayer(Menu): + + START_TURN = 0 + + def __init__(self, team): + super().__init__('Pass the badge to next team:', [team]) class Hex: @@ -627,8 +658,7 @@ class Dice: class GameBoard(State): MAIN_MENU = 0 - BUILD_MENU = 1 - END_TURN = 2 + ACTION_MENU = 1 # List of resources (pre-randomised to combat the not-very random number # generator) that make up the hexes on the game board for 4 players @@ -653,7 +683,7 @@ class GameBoard(State): numbers = [FIVE, TWO, SIX, THREE, EIGHT, TEN, NINE, TWELVE, ELEVEN, FOUR, EIGHT, TEN, NINE, FOUR, FIVE, SIX, THREE, ELEVEN] - def __init__(self, team): + def __init__(self, teams): # Two rings of hexes around the centre radius = 2 @@ -696,7 +726,7 @@ class GameBoard(State): self.robber_mode = False self.robber_hex = self.get_robber_hex() - # Generate lists of unique valid locations for building + # Generate lists of unique valid locations for building settlements and roads self.roads = [] self.settlements = [] for h in self.hexes: @@ -719,17 +749,24 @@ class GameBoard(State): s.hexes.append(h) self.settlements.append(s) - # Give the team starting towns in the two settlements with the highest probability score - # TODO interleave starting town choices for multi-player - self.pick_starting_settlement(team) - self.pick_starting_settlement(team) + # Create a player for each team and give the team starting towns in the two settlements + # with the highest probability score that not already taken + # Each team gets a settlement in player order, then again but in reverse, so the last + # player gets the first pick of the second settlements + for team in teams: + self.pick_starting_settlement(team) + teams.reverse() + for team in teams: + self.pick_starting_settlement(team) + teams.reverse() + self.players = [] + for team in teams: + self.players.append(Player(team, self.roads, self.settlements)) + self.player = self.players[-1] # The dice roller self.dice = Dice() - # The player details - self.player = Player(team, self.roads, self.settlements) - def get_roads_for_settlement(self, settlement): """Return a list of roads that connect to the given settlement""" roads = [] @@ -811,14 +848,9 @@ class GameBoard(State): if btn == Buttons.BTN_Menu: self.selection = GameBoard.MAIN_MENU self.done = True - if btn == Buttons.BTN_B: - self.selection = GameBoard.BUILD_MENU - self.done = True if btn == Buttons.BTN_Star: - # Can end the turn if dice were rolled - if self.dice.total() != 0: - self.selection = GameBoard.END_TURN - self.done = True + self.selection = GameBoard.ACTION_MENU + self.done = True if btn == Buttons.BTN_Hash: # Only roll the dice if not already rolled if self.dice.total() == 0: @@ -830,8 +862,9 @@ class GameBoard(State): h.set_highlight(True) else: h.set_highlight(False) - # Collect resources corresponding with the dice roll - self.player.collect(num) + # All players collect resources corresponding with the dice roll + for p in self.players: + p.collect(num) # Activate the robber on a seven if num == 7: self.robber_mode = True @@ -882,11 +915,19 @@ class GameBoard(State): def next_player(self): """ Call from the state machine to reset the board for the next player""" + for i in range(len(self.players)): + if self.player == self.players[i]: + if i + 1 == len(self.players): + self.player = self.players[0] + else: + self.player = self.players[i + 1] + break self.player.increment_turn() self.dice.reset() for h in self.hexes: h.set_highlight(False) + class Settlers: """A lean mean state machine""" @@ -895,12 +936,13 @@ class Settlers: MAIN_MENU = 1 TEAM_MENU = 2 GAME = 3 - BUILD_MENU = 4 - END_TURN_MENU = 5 + ACTION_MENU = 4 + END_TURN = 5 def __init__(self): self.state = Settlers.MAIN_MENU self.game = None + self.teams = [] def run(self): while self.state != Settlers.EXIT: @@ -909,6 +951,7 @@ class Settlers: menu = MainMenu(self.game is None) x = menu.run() if x == MainMenu.NEW_GAME: + self.teams = [] self.state = Settlers.TEAM_MENU if x == MainMenu.CONTINUE_GAME: self.state = Settlers.GAME @@ -916,12 +959,16 @@ class Settlers: self.state = Settlers.EXIT if self.state == Settlers.TEAM_MENU: - menu = TeamMenu() + menu = TeamMenu(self.teams) x = menu.run() + if x <= TeamMenu.TEAM_MAX: + self.teams.append(menu.get_selected_team()) + if len(self.teams) >= 4: + x = TeamMenu.START_GAME if x == TeamMenu.BACK: self.state = Settlers.MAIN_MENU - else: - self.game = GameBoard(menu.get_selected_team()) + if x == TeamMenu.START_GAME: + self.game = GameBoard(self.teams) self.game.next_player() self.state = Settlers.GAME @@ -929,23 +976,22 @@ class Settlers: x = self.game.run() if x == GameBoard.MAIN_MENU: self.state = Settlers.MAIN_MENU - if x == GameBoard.BUILD_MENU: - self.state = Settlers.BUILD_MENU - if x == GameBoard.END_TURN: - self.state = Settlers.END_TURN_MENU + if x == GameBoard.ACTION_MENU: + self.state = Settlers.ACTION_MENU - if self.state == Settlers.BUILD_MENU: - menu = BuildMenu(self.game.player.resources) + if self.state == Settlers.ACTION_MENU: + menu = ActionMenu(self.game.player.resources, self.game.dice.total()) x = menu.run() - if x == BuildMenu.BACK: - self.state = Settlers.GAME - else: - # TODO initiate building a thing + if x == ActionMenu.BACK: self.state = Settlers.GAME + if x == ActionMenu.END_TURN: + self.state = Settlers.END_TURN + # TODO initiate building a thing - if self.state == Settlers.END_TURN_MENU: + if self.state == Settlers.END_TURN: self.game.next_player() - # TODO: Ask for confirmation + menu = NextPlayer(self.game.player.team) + x = menu.run() self.state = Settlers.GAME # User chose exit, a machine reset is the easiest way :-)