Settlers of EMF

Implement pass-and-play multiplayer, with interstitial screen asking
to pass the badge to the next player
master
Mat Booth 2018-09-24 10:12:04 +01:00
parent 01e0b9226d
commit 846bab0ab1
1 changed files with 102 additions and 56 deletions

View File

@ -1,6 +1,10 @@
"""Settlers of EMF """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" ___title___ = "Settlers of EMF"
___license___ = "MIT" ___license___ = "MIT"
@ -97,14 +101,17 @@ class Menu(State):
col = c['colour'] col = c['colour']
if not self.is_choice_enabled(i): if not self.is_choice_enabled(i):
col = ugfx.html_color(0x676767) col = ugfx.html_color(0x676767)
text = "{} - {} ".format(i + 1, c['name']) if len(self.choices) == 1:
ugfx.text(20, offset + 125, text, col) text = "{} ".format(c['name'])
else:
text = "{} - {} ".format(i + 1, c['name'])
ugfx.text(18, offset + 125, text, col)
offset = offset + 20 offset = offset + 20
if 'cost' in c: if 'cost' in c:
for j in range(len(c['cost'])): for j in range(len(c['cost'])):
cost = c['cost'][j] cost = c['cost'][j]
ugfx.area((42 * j) + 48, offset + 125, 18, 18, cost['resource']['col']) ugfx.area((42 * j) + 46, offset + 125, 18, 18, cost['resource']['col'])
ugfx.text((42 * j) + 66, offset + 125, "x{} ".format(cost['amount']), col) ugfx.text((42 * j) + 64, offset + 125, "x{} ".format(cost['amount']), col)
offset = offset + 20 offset = offset + 20
# Set the initial selection # Set the initial selection
@ -207,8 +214,8 @@ class MainMenu(Menu):
] ]
def __init__(self, disable_continue_option=True): def __init__(self, disable_continue_option=True):
MainMenu.options[1]['disabled'] = disable_continue_option MainMenu.options[MainMenu.CONTINUE_GAME]['disabled'] = disable_continue_option
super().__init__('Main menu:', MainMenu.options) super().__init__('Welcome!', MainMenu.options)
class TeamMenu(Menu): class TeamMenu(Menu):
@ -226,52 +233,76 @@ class TeamMenu(Menu):
'colour': ugfx.html_color(0xeaeaea)}, 'colour': ugfx.html_color(0xeaeaea)},
{'name': "Null Sector", {'name': "Null Sector",
'colour': ugfx.html_color(0x9c27b0)}, 'colour': ugfx.html_color(0x9c27b0)},
{'name': "Start Game"},
{'name': "Back"}, {'name': "Back"},
] ]
TEAM_MAX = len(options) - 3
START_GAME = len(options) - 2
BACK = len(options) - 1 BACK = len(options) - 1
def __init__(self): def __init__(self, teams):
super().__init__('Choose your team:', TeamMenu.options) # 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): def get_selected_team(self):
return TeamMenu.options[self.selection].copy() return TeamMenu.options[self.selection].copy()
class BuildMenu(Menu): class ActionMenu(Menu):
options = [ options = [
{'name': "Road (0 points)", {'name': "Build Road (0 points)",
'cost': [{'resource': BRICK, 'amount': 1}, 'cost': [{'resource': BRICK, 'amount': 1},
{'resource': WOOD, 'amount': 1}]}, {'resource': WOOD, 'amount': 1}]},
{'name': "Town (1 point)", {'name': "Build Town (1 point)",
'cost': [{'resource': BRICK, 'amount': 1}, 'cost': [{'resource': BRICK, 'amount': 1},
{'resource': WOOD, 'amount': 1}, {'resource': WOOD, 'amount': 1},
{'resource': SHEEP, 'amount': 1}, {'resource': SHEEP, 'amount': 1},
{'resource': WHEAT, 'amount': 1}]}, {'resource': WHEAT, 'amount': 1}]},
{'name': "City (2 points)", {'name': "Upgrade to City (2 points)",
'cost': [{'resource': WHEAT, 'amount': 2}, 'cost': [{'resource': WHEAT, 'amount': 2},
{'resource': ORE, 'amount': 3}]}, {'resource': ORE, 'amount': 3}]},
# TODO Implement trading
{'name': "Trade", 'disabled': True},
{'name': "End Turn"},
{'name': "Back"}, {'name': "Back"},
] ]
TRADE = len(options) - 3
END_TURN = len(options) - 2
BACK = len(options) - 1 BACK = len(options) - 1
def __init__(self, resources): def __init__(self, resources, dice_roll):
# Disable options based on whether the player can afford them # Disable build options based on whether the player can afford them
for option in BuildMenu.options: for option in ActionMenu.options:
option['disabled'] = False
if 'cost' not in option: if 'cost' not in option:
continue continue
option['disabled'] = False
for cost in option['cost']: for cost in option['cost']:
for resource in resources: for resource in resources:
if resource.resource == cost['resource']: if resource.resource == cost['resource']:
if resource.quantity < cost['amount']: if resource.quantity < cost['amount']:
option['disabled'] = True 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): 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: class Hex:
@ -627,8 +658,7 @@ class Dice:
class GameBoard(State): class GameBoard(State):
MAIN_MENU = 0 MAIN_MENU = 0
BUILD_MENU = 1 ACTION_MENU = 1
END_TURN = 2
# List of resources (pre-randomised to combat the not-very random number # 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 # 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, numbers = [FIVE, TWO, SIX, THREE, EIGHT, TEN, NINE, TWELVE, ELEVEN, FOUR,
EIGHT, TEN, NINE, FOUR, FIVE, SIX, THREE, ELEVEN] EIGHT, TEN, NINE, FOUR, FIVE, SIX, THREE, ELEVEN]
def __init__(self, team): def __init__(self, teams):
# Two rings of hexes around the centre # Two rings of hexes around the centre
radius = 2 radius = 2
@ -696,7 +726,7 @@ class GameBoard(State):
self.robber_mode = False self.robber_mode = False
self.robber_hex = self.get_robber_hex() 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.roads = []
self.settlements = [] self.settlements = []
for h in self.hexes: for h in self.hexes:
@ -719,17 +749,24 @@ class GameBoard(State):
s.hexes.append(h) s.hexes.append(h)
self.settlements.append(s) self.settlements.append(s)
# Give the team starting towns in the two settlements with the highest probability score # Create a player for each team and give the team starting towns in the two settlements
# TODO interleave starting town choices for multi-player # with the highest probability score that not already taken
self.pick_starting_settlement(team) # Each team gets a settlement in player order, then again but in reverse, so the last
self.pick_starting_settlement(team) # 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 # The dice roller
self.dice = Dice() self.dice = Dice()
# The player details
self.player = Player(team, self.roads, self.settlements)
def get_roads_for_settlement(self, settlement): def get_roads_for_settlement(self, settlement):
"""Return a list of roads that connect to the given settlement""" """Return a list of roads that connect to the given settlement"""
roads = [] roads = []
@ -811,14 +848,9 @@ class GameBoard(State):
if btn == Buttons.BTN_Menu: if btn == Buttons.BTN_Menu:
self.selection = GameBoard.MAIN_MENU self.selection = GameBoard.MAIN_MENU
self.done = True self.done = True
if btn == Buttons.BTN_B:
self.selection = GameBoard.BUILD_MENU
self.done = True
if btn == Buttons.BTN_Star: if btn == Buttons.BTN_Star:
# Can end the turn if dice were rolled self.selection = GameBoard.ACTION_MENU
if self.dice.total() != 0: self.done = True
self.selection = GameBoard.END_TURN
self.done = True
if btn == Buttons.BTN_Hash: if btn == Buttons.BTN_Hash:
# Only roll the dice if not already rolled # Only roll the dice if not already rolled
if self.dice.total() == 0: if self.dice.total() == 0:
@ -830,8 +862,9 @@ class GameBoard(State):
h.set_highlight(True) h.set_highlight(True)
else: else:
h.set_highlight(False) h.set_highlight(False)
# Collect resources corresponding with the dice roll # All players collect resources corresponding with the dice roll
self.player.collect(num) for p in self.players:
p.collect(num)
# Activate the robber on a seven # Activate the robber on a seven
if num == 7: if num == 7:
self.robber_mode = True self.robber_mode = True
@ -882,11 +915,19 @@ class GameBoard(State):
def next_player(self): def next_player(self):
""" Call from the state machine to reset the board for the next player""" """ 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.player.increment_turn()
self.dice.reset() self.dice.reset()
for h in self.hexes: for h in self.hexes:
h.set_highlight(False) h.set_highlight(False)
class Settlers: class Settlers:
"""A lean mean state machine""" """A lean mean state machine"""
@ -895,12 +936,13 @@ class Settlers:
MAIN_MENU = 1 MAIN_MENU = 1
TEAM_MENU = 2 TEAM_MENU = 2
GAME = 3 GAME = 3
BUILD_MENU = 4 ACTION_MENU = 4
END_TURN_MENU = 5 END_TURN = 5
def __init__(self): def __init__(self):
self.state = Settlers.MAIN_MENU self.state = Settlers.MAIN_MENU
self.game = None self.game = None
self.teams = []
def run(self): def run(self):
while self.state != Settlers.EXIT: while self.state != Settlers.EXIT:
@ -909,6 +951,7 @@ class Settlers:
menu = MainMenu(self.game is None) menu = MainMenu(self.game is None)
x = menu.run() x = menu.run()
if x == MainMenu.NEW_GAME: if x == MainMenu.NEW_GAME:
self.teams = []
self.state = Settlers.TEAM_MENU self.state = Settlers.TEAM_MENU
if x == MainMenu.CONTINUE_GAME: if x == MainMenu.CONTINUE_GAME:
self.state = Settlers.GAME self.state = Settlers.GAME
@ -916,12 +959,16 @@ class Settlers:
self.state = Settlers.EXIT self.state = Settlers.EXIT
if self.state == Settlers.TEAM_MENU: if self.state == Settlers.TEAM_MENU:
menu = TeamMenu() menu = TeamMenu(self.teams)
x = menu.run() 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: if x == TeamMenu.BACK:
self.state = Settlers.MAIN_MENU self.state = Settlers.MAIN_MENU
else: if x == TeamMenu.START_GAME:
self.game = GameBoard(menu.get_selected_team()) self.game = GameBoard(self.teams)
self.game.next_player() self.game.next_player()
self.state = Settlers.GAME self.state = Settlers.GAME
@ -929,23 +976,22 @@ class Settlers:
x = self.game.run() x = self.game.run()
if x == GameBoard.MAIN_MENU: if x == GameBoard.MAIN_MENU:
self.state = Settlers.MAIN_MENU self.state = Settlers.MAIN_MENU
if x == GameBoard.BUILD_MENU: if x == GameBoard.ACTION_MENU:
self.state = Settlers.BUILD_MENU self.state = Settlers.ACTION_MENU
if x == GameBoard.END_TURN:
self.state = Settlers.END_TURN_MENU
if self.state == Settlers.BUILD_MENU: if self.state == Settlers.ACTION_MENU:
menu = BuildMenu(self.game.player.resources) menu = ActionMenu(self.game.player.resources, self.game.dice.total())
x = menu.run() x = menu.run()
if x == BuildMenu.BACK: if x == ActionMenu.BACK:
self.state = Settlers.GAME
else:
# TODO initiate building a thing
self.state = Settlers.GAME 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() self.game.next_player()
# TODO: Ask for confirmation menu = NextPlayer(self.game.player.team)
x = menu.run()
self.state = Settlers.GAME self.state = Settlers.GAME
# User chose exit, a machine reset is the easiest way :-) # User chose exit, a machine reset is the easiest way :-)