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
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 :-)