-#!/usr/bin/python
-#
-# A Python engine for Risk-like games
+"""A Python engine for Risk-like games
+"""
import random
+from .log import Logger
VERSION='0.1'
raise ValueError('%s shared by %s and %s'
% (t.short_name, ts[t.short_name], t))
def production(self, player):
- ts = list(player.territories(world))
+ ts = list(player.territories(self))
production = max(3, len(ts) / 3)
continents = set([t.continent.name for t in ts])
for c_name in continents:
ID_CmpMixin.__init__(self)
self.alive = True
self.hand = Hand()
+ self._message_index = 0
def territories(self, world):
for t in world.territories():
if t.player == self:
if neighbor.player != self:
yield t
break
+ def phase_report(self, world, log):
+ print 'Reporting for %s:\n %s' \
+ % (self, '\n '.join(log[self._message_index:]))
+ self._message_index = len(log)
def phase_select_territory(self, world):
"""Return the selected territory
"""
self.hand.extend(cards)
class Engine (ID_CmpMixin):
- def __init__(self, world, players, deck_class=Deck):
+ def __init__(self, world, players, deck_class=Deck, logger_class=Logger):
ID_CmpMixin.__init__(self)
self.world = world
self.deck = deck_class(world.territories())
+ self.log = logger_class()
self.players = players
def __str__(self):
return '<engine %s %s>' % (self.world, self.players)
def run(self):
self.setup()
self.play()
+ self.log('Game over.')
+ for p in self.players:
+ p.phase_report(self.world, self.log)
def setup(self):
for p in self.players:
p.alive = True
- random.shuffle(players)
+ random.shuffle(self.players)
self.select_territories()
self.place_initial_armies()
self.deal()
active_player = (active_player + 1) % living
turn += 1
def play_turn(self, player):
- print "%s's turn (territory score: %s)" \
- % (player, [(p,len(list(p.territories(self.world))))
- for p in self.players])
+ self.log("%s's turn (territory score: %s)"
+ % (player, [(p,len(list(p.territories(self.world))))
+ for p in self.players]))
+ player.phase_report(self.world, self.log)
self.play_cards_and_place_armies(player)
captures = self.attack_phase(player)
if captures > 0 and len(self.deck) > 0 and len(self.living_players()) > 1:
t.player = None
for i in range(len(list(self.world.territories()))):
p = self.players[i % len(self.players)]
+ p.phase_report(self.world, self.log)
t = p.phase_select_territory(self.world)
if t.player != None:
raise PlayerError('Cannot select %s owned by %s'
% (t, t.player))
- print '%s selects %s' % (p, t)
+ self.log('%s selects %s' % (p, t))
t.player = p
t.armies = 1
def place_initial_armies(self):
assert min(s) == max(s)-1, 'Min %d, max %d' % (min(s), max(s))
for p,placed in zip(self.players, already_placed):
if placed == min(s):
+ p.phase_report(self.world, self.log)
self.player_place_armies(p, remaining, 1)
remaining = self.world.initial_armies[len(self.players)] - max(s)
while remaining > 0:
for p in self.players:
+ p.phase_report(self.world, self.log)
self.player_place_armies(p, remaining, 1)
remaining -= 1
def player_place_armies(self, player, remaining=1, this_round=1):
if sum(placements.values()) != this_round:
raise PlayerError('Placing more than %d armies' % this_round)
for ter_name,armies in placements.items():
- t = world.territory_by_name(ter_name)
+ t = self.world.territory_by_name(ter_name)
if t.player != player:
raise PlayerError('Placing armies in %s owned by %s'
% (t, t.player))
if armies < 0:
raise PlayerError('Placing a negative number of armies (%d) in %s'
% (armies, t))
- print '%s places %s' % (player, placements)
+ self.log('%s places %s' % (player, placements))
for ter_name,armies in placements.items():
- t = world.territory_by_name(ter_name)
+ t = self.world.territory_by_name(ter_name)
t.armies += armies
def deal(self):
for p in self.players:
for i in range(3):
cards.append(self.deck.pop())
p.phase_draw(cards)
- print 'Initial hands dealt'
+ self.log('Initial hands dealt')
def play_cards_and_place_armies(self, player, additional_armies=0):
cards_required = len(player.hand) >= 5
cards = player.phase_play_cards(
raise PlayerError('You have %d >= 5 cards in your hand, you must play'
% len(player.hand))
w_prod,w_terr_prod = self.world.production(player)
- print '%s earned %d armies from territories' % (player, w_prod)
+ self.log('%s earned %d armies from territories' % (player, w_prod))
c_prod,c_terr_prod = self.deck.production(player, cards)
if c_prod > 0:
- print '%s played %s, earning %d armies' \
- % (player, cards, c_prod+sum(c_terr_prod.values()))
+ self.log('%s played %s, earning %d armies'
+ % (player, cards, c_prod+sum(c_terr_prod.values())))
if cards != None:
for c in cards:
player.hand.remove(c)
w_terr_prod[terr] = prod
self.world.place_territory_production(w_terr_prod)
if len(w_terr_prod) > 0:
- print '%s was required to place %s' % (player, w_terr_prod)
+ self.log('%s was required to place %s' % (player, w_terr_prod))
armies = w_prod + c_prod
self.player_place_armies(player, armies, armies)
def attack_phase(self, player):
target.armies -= t_dead
if target.armies == 0:
self.takeover(source, target, remaining_attackers=armies-a_dead)
- print '%s conquered %s from %s with %d:%d. Deaths %d:%d. Remaining %d:%d' \
- % (source.player, target, source, armies, len(t_dice),
- a_dead, t_dead, source.armies, target.armies)
+ self.log('%s conquered %s from %s with %d:%d. Deaths %d:%d. Remaining %d:%d'
+ % (source.player, target, source, armies, len(t_dice),
+ a_dead, t_dead, source.armies, target.armies))
assert target.armies > 0, target
return True
- print '%s attacked %s from %s with %d:%d. Deaths %d:%d. Remaining %d:%d' \
- % (source.player, target, source, armies, len(t_dice),
- a_dead, t_dead, source.armies, target.armies)
+ self.log('%s attacked %s from %s with %d:%d. Deaths %d:%d. Remaining %d:%d' \
+ % (source.player, target, source, armies, len(t_dice),
+ a_dead, t_dead, source.armies, target.armies))
assert target.armies > 0, target
return False
def takeover(self, source, target, remaining_attackers):
if len(self.living_players()) > 1:
while len(killer.hand) > 5:
self.play_cards_and_place_armies(killer)
- print '%s killed by %s' % (player, killer)
+ self.log('%s killed by %s' % (player, killer))
+ if len(self.living_players()) > 1:
+ player.phase_report(self.world, self.log)
+ # else the game is over, and killed will hear about this then.
def living_players(self):
return [p for p in self.players if p.alive == True]
w._resolve_link_names()
return w
-if __name__ == '__main__':
+def test():
import doctest
- import sys
-
failures,tests = doctest.testmod()
- if failures > 0:
- sys.exit(1)
+ return failures
+def random_game():
world = generate_earth()
- players = [Player('A'), Player('B')]
+ players = [Player('Alice'), Player('Bob'), Player('Charlie')]
e = Engine(world, players)
e.run()
+
+if __name__ == '__main__':
+ import sys
+ failures = self.test()
+ if failures > 0:
+ sys.exit(1)
+ self.random_game()