def undo_split(self, parent_markerid, child_markerid): parent = self.markerid_to_legion.get(parent_markerid) if parent is None: return child = self.markerid_to_legion.get(child_markerid) if child is None: return parent_creature_names = parent.creature_names child_creature_names = child.creature_names parent.creatures += child.creatures child.remove_observer(self) del self.markerid_to_legion[child_markerid] self.markerids_left.add(child.markerid) self.selected_markerid = None del child # One action for our player with creature names, and a # different action for other players without. action = Action.UndoSplit(self.game.name, self.name, parent_markerid, child_markerid, parent_creature_names, child_creature_names) self.notify(action, names=[self.name]) action = Action.UndoSplit(self.game.name, self.name, parent_markerid, child_markerid, len(parent_creature_names) * ["Unknown"], len(child_creature_names) * ["Unknown"]) other_playernames = self.game.playernames other_playernames.remove(self.name) self.notify(action, names=other_playernames)
def test_save(): game_name = "game" playername = "player" parent_markerid = "Rd01" child_markerid = "Rd02" history = History.History() assert history.actions == [] assert history.undone == [] assert not history.can_undo(playername) assert not history.can_redo(playername) action1 = Action.MoveLegion(game_name, playername, parent_markerid, 1, 1, False, None, 2) history.update(None, action1, None) assert history.actions == [action1] assert history.undone == [] assert history.can_undo(playername) assert not history.can_undo("") assert not history.can_redo(playername) action2 = Action.MoveLegion(game_name, playername, child_markerid, 2, 3, False, None, 3) history.update(None, action2, None) assert history.actions == [action1, action2] global tmp_path with tempfile.NamedTemporaryFile(prefix="test_history", delete=False) as fil: tmp_path = fil.name history.save(fil) with open(tmp_path) as fil: lines = fil.readlines() assert len(lines) == 2
def test_undo_then_do_different(): game_name = "game" playername = "player" parent_markerid = "Rd01" child_markerid = "Rd02" other_markerid = "Rd03" parent_creature_names = 4 * [None] child_creature_names = 4 * [None] history = History.History() action = Action.SplitLegion(game_name, playername, parent_markerid, child_markerid, parent_creature_names, child_creature_names) history.update(None, action, None) undo_action = Action.UndoSplit(game_name, playername, parent_markerid, child_markerid, parent_creature_names, child_creature_names) history.update(None, undo_action, None) action2 = Action.SplitLegion(game_name, playername, parent_markerid, other_markerid, parent_creature_names, child_creature_names) history.update(None, action2, None) assert history.actions == [action2] assert history.undone == [] assert history.can_undo(playername) assert not history.can_redo(playername)
def split_legion(self, parent_markerid, child_markerid, parent_creature_names, child_creature_names): logging.info("%s %s %s %s", parent_markerid, child_markerid, parent_creature_names, child_creature_names) parent = self.markerid_to_legion.get(parent_markerid) if parent is None: return if child_markerid not in self.markerids_left: raise AssertionError("illegal marker") if (bag(parent.creature_names) != bag(parent_creature_names).union( bag(child_creature_names)) and bag(parent_creature_names).union(bag(child_creature_names)) != bag({"Unknown": len(parent)})): raise AssertionError("wrong creatures", "parent.creature_names", parent.creature_names, "parent_creature_names", parent_creature_names, "child_creature_names", child_creature_names) new_legion1 = Legion.Legion(self, parent_markerid, Creature.n2c(parent_creature_names), parent.hexlabel) new_legion2 = Legion.Legion(self, child_markerid, Creature.n2c(child_creature_names), parent.hexlabel) if not parent.is_legal_split(new_legion1, new_legion2): raise AssertionError("illegal split") del new_legion1 parent.creatures = Creature.n2c(parent_creature_names) for creature in parent.creatures: creature.legion = parent self.take_marker(child_markerid) new_legion2.add_observer(self.game) self.markerid_to_legion[child_markerid] = new_legion2 del parent # One action for our player with creature names action = Action.SplitLegion(self.game.name, self.name, parent_markerid, child_markerid, parent_creature_names, child_creature_names) logging.info(action) self.notify(action, names=[self.name]) # Another action for everyone (including our player, who will # ignore it as a duplicate) without creature names. action = Action.SplitLegion(self.game.name, self.name, parent_markerid, child_markerid, len(parent_creature_names) * ["Unknown"], len(child_creature_names) * ["Unknown"]) logging.info(action) self.notify(action)
def fight(self, playername, game_name, attacker_markerid, defender_markerid): """Fight the current current engagement.""" logging.info("Server.fight %s %s %s %s", playername, game_name, attacker_markerid, defender_markerid) game = self.name_to_game(game_name) if game: attacker_legion = game.find_legion(attacker_markerid) attacker_legion = game.find_legion(attacker_markerid) defender_legion = game.find_legion(defender_markerid) if (not attacker_legion or not defender_legion or playername not in [ attacker_legion.player.name, defender_legion.player.name ]): logging.warning("illegal fight call from %s", playername) return if (defender_legion.can_flee and not game.defender_chose_not_to_flee): logging.warning( "Illegal fight call while defender can still flee") return action = Action.Fight(game.name, attacker_markerid, defender_markerid, attacker_legion.hexlabel) logging.info("Server.fight calling game.update") game.update(self, action, None)
def remove_observer(self, user): Observed.remove_observer(self, user) playername = user.name if playername in self.playernames: self.playernames.remove(playername) action = Action.DelUsername(playername) self.notify(action)
def add_points(self, points, can_acquire_angels): logging.info("Legion.add_points %s %s %s", self, points, can_acquire_angels) ARCHANGEL_POINTS = Creature.Creature("Archangel").acquirable_every ANGEL_POINTS = Creature.Creature("Angel").acquirable_every player = self.player score0 = player.score score1 = score0 + points player.score = score1 logging.info("%s now has score %s", player, player.score) if can_acquire_angels: height = len(self) archangels = 0 if (height < 7 and score1 // ARCHANGEL_POINTS > score0 // ARCHANGEL_POINTS): archangels += 1 score1 -= ANGEL_POINTS logging.info("archangels %d" % archangels) angels = 0 while (height + archangels + angels < 7 and score1 // ANGEL_POINTS > score0 // ANGEL_POINTS): angels += 1 score1 -= ANGEL_POINTS logging.info("angels %d" % angels) self._angels_pending = angels self._archangels_pending = archangels if angels + archangels > 0: action = Action.CanAcquireAngels(self.player.game.name, self.player.name, self.markerid, angels, archangels) self.notify(action)
def done_with_counterstrikes(self): if self.has_forced_strikes: logging.info("Forced strikes remain") return action = Action.StartReinforceBattlePhase(self.game.name, self.name, self.game.battle_turn) self.notify(action)
def join_game(self, playername, game_name, player_class, player_info): """Join an existing game that hasn't started yet. Return True on success, False on failure. """ logging.info("%s %s %s %s", playername, game_name, player_class, player_info) game = self.name_to_game(game_name) if game: try: game.add_player(playername, player_class, player_info) except AssertionError: logging.exception("join_game caught an exception") return False else: action = Action.JoinGame(playername, game.name, player_class, player_info) self.notify(action) set1 = self.game_to_waiting_ais.get(game_name) if set1: set1.discard(playername) if not set1: game = self.name_to_game(game_name) reactor.callLater(1, game.start, game.owner.name) return True return False
def flee(self, playername, game_name, markerid): """Flee from an engagement.""" game = self.name_to_game(game_name) if game: legion = game.find_legion(markerid) if not legion: logging.warning("flee with no legion %s", markerid) return hexlabel = legion.hexlabel for enemy_legion in game.all_legions(hexlabel): if enemy_legion != legion: break # Enemy illegally managed to concede before we could flee. if enemy_legion == legion: logging.warning("illegal concede before flee") return player = game.get_player_by_name(playername) if player == game.active_player: logging.warning("attacker tried to flee") return if legion.player != player: logging.warning("wrong player tried to flee") return if not legion.can_flee: logging.warning("illegal flee attempt") return enemy_markerid = enemy_legion.markerid action = Action.Flee(game.name, markerid, enemy_markerid, legion.hexlabel) game.update(self, action, None)
def load(self, fil): """Load history from a file, which should already be open for read.""" self.actions = [] self.undone = [] for line in fil: line = line.strip() action = Action.fromstring(line) self.actions.append(action)
def do_not_acquire_angels(self): """Do not acquire any angels, and notify observers.""" logging.info("do_not_acquire_angels %s", self) if self.angels_pending or self.archangels_pending: self.reset_angels_pending() action = Action.DoNotAcquireAngels(self.player.game.name, self.player.name, self.markerid) self.notify(action)
def done_with_recruits(self): if self.game.active_player != self or self.game.phase != Phase.MUSTER: logging.info("illegal call to done_with_recruits") return (player, turn) = self.game.next_player_and_turn if player is not None: action = Action.StartSplitPhase(self.game.name, player.name, turn) self.notify(action)
def done_with_moves(self): logging.debug("") if self.can_exit_move_phase: self.recombine() action = Action.StartFightPhase(self.game.name, self.name) self.notify(action) else: logging.warning("%s cannot exit move phase", self)
def test_history_2(): game_name = "game" playername = "player" parent_markerid = "Rd01" child_markerid = "Rd02" history = History.History() assert history.actions == [] assert history.undone == [] assert not history.can_undo(playername) assert not history.can_redo(playername) action1 = Action.MoveLegion(game_name, playername, parent_markerid, 1, 1, False, None, 2) history.update(None, action1, None) assert history.actions == [action1] assert history.undone == [] assert history.can_undo(playername) assert not history.can_undo("") assert not history.can_redo(playername) action2 = Action.MoveLegion(game_name, playername, child_markerid, 2, 3, False, None, 3) history.update(None, action2, None) assert history.actions == [action1, action2] assert history.undone == [] assert history.can_undo(playername) assert not history.can_undo("") assert not history.can_redo(playername) undo_action2 = Action.UndoMoveLegion(game_name, playername, child_markerid, 2, 3, False, None, 3) history.update(None, undo_action2, None) assert history.actions == [action1] assert history.undone == [action2] assert history.can_undo(playername) assert history.can_redo(playername) undo_action1 = Action.UndoMoveLegion(game_name, playername, parent_markerid, 1, 1, False, None, 2) history.update(None, undo_action1, None) assert history.actions == [] assert history.undone == [action2, action1] assert not history.can_undo(playername) assert history.can_redo(playername)
def assign_color(self, color): """Set this player's color""" self.color = color abbrev = self.color_abbrev num_markers = len(markerdata.data[color]) for ii in xrange(num_markers): self.markerids_left.add("%s%02d" % (abbrev, ii + 1)) logging.info(self.markerids_left) action = Action.PickedColor(self.game.name, self.name, color) self.notify(action)
def send_chat_message(self, source, dest, text): """Send a chat message from user source to users in dest. source is a playername. dest is a set of playernames. If dest is None, send to all users """ message = "%s: %s" % (source, text) if dest is not None: dest.add(source) action = Action.ChatMessage(source, message) self.notify(action, names=dest)
def done_with_strikes(self): if self.has_forced_strikes: logging.info("Forced strikes remain") return player = None for legion in self.game.battle_legions: if legion.player != self: player = legion.player action = Action.StartCounterstrikeBattlePhase(self.game.name, player.name) self.notify(action)
def withdraw(self, playername, game_name): """Withdraw a player from the game.""" game = self.name_to_game(game_name) if game: if game.started: game.withdraw(playername) else: try: game.remove_player(playername) except AssertionError: pass else: if len(game.players) == 0: if game in self.games: self.games.remove(game) action = Action.RemoveGame(game.name) self.notify(action) else: action = Action.Withdraw(playername, game.name) self.notify(action)
def test_eq(): obj1 = Action.fromstring( "MoveLegion {'markerid': 'Rd01', \ 'entry_side': 1, 'teleport': False, 'playername': 'player', \ 'teleporting_lord': None, 'game_name': 'game', 'hexlabel': 1, \ 'previous_hexlabel': 2}" ) obj2 = Action.fromstring( "MoveLegion {'markerid': 'Rd01', \ 'entry_side': 1, 'teleport': False, 'playername': 'player', \ 'teleporting_lord': None, 'game_name': 'game', 'hexlabel': 1, \ 'previous_hexlabel': 2}" ) assert obj1 == obj2 obj3 = Action.fromstring( "MoveLegion {'markerid': 'Rd01', \ 'entry_side': 1, 'teleport': True, 'playername': 'player', \ 'teleporting_lord': None, 'game_name': 'game', 'hexlabel': 1, \ 'previous_hexlabel': 2}" ) assert obj1 != obj3
def die(self, scoring_player, check_for_victory): """Die and give half points to scoring_player, except for legions which are engaged with someone else. """ logging.info("Player.die %s %s %s", self, scoring_player, check_for_victory) # First reveal all this player's legions. for legion in self.legions: if (legion.all_known and legion not in self.game.battle_legions): # Only reveal the legion if we're sure about its contents, # to avoid spreading disinformation. # Do not reveal the legion that's currently in battle, because # its contents are in flux. action = Action.RevealLegion(self.game.name, legion.markerid, legion.creature_names) self.notify(action) if scoring_player is None: scoring_player_name = "" else: scoring_player_name = scoring_player.name self.has_titan = False action = Action.EliminatePlayer(self.game.name, scoring_player_name, self.name, check_for_victory) reactor.callLater(0.1, self.notify, action)
def test_fromstring(): obj = Action.fromstring( "MoveLegion {'markerid': 'Rd01', \ 'entry_side': 1, 'teleport': False, 'playername': 'player', \ 'teleporting_lord': None, 'game_name': 'game', 'hexlabel': 1, \ 'previous_hexlabel': 2}" ) assert isinstance(obj, Action.MoveLegion) assert obj.markerid == "Rd01" assert obj.entry_side == 1 assert obj.teleport is False assert obj.playername == "player" assert obj.teleporting_lord is None assert obj.game_name == "game" assert obj.hexlabel == 1 assert obj.previous_hexlabel == 2
def unreinforce(self): """Undo reinforcement, and notify observers.""" # Avoid double undo if not self.recruited: return player = self.player creature = self.creatures.pop() recruiter_names = self.recruiter_names_list.pop() logging.info("%s clearing self.recruited", self) self.recruited = False caretaker = self.player.game.caretaker caretaker.put_one_back(creature.name) action = Action.UnReinforce(player.game.name, player.name, self.markerid, creature.name, recruiter_names) self.notify(action)
def test_undo_nothing(): game_name = "game" playername = "player" parent_markerid = "Rd01" child_markerid = "Rd02" parent_creature_names = 4 * [None] child_creature_names = 4 * [None] history = History.History() undo_action = Action.UndoSplit(game_name, playername, parent_markerid, child_markerid, parent_creature_names, child_creature_names) history.update(None, undo_action, None) assert history.actions == [] assert history.undone == [] assert not history.can_undo(playername) assert not history.can_redo(playername)
def unsummon_angel(self, legion, creature_name): donor = self.last_donor # Avoid doing it twice. if not self.summoned or donor is None or len(donor) >= 7: return # Can be done during battle, so it matters which of this creature. if not legion.creatures or legion.creatures[-1].name != creature_name: return legion.creatures.pop() donor.add_creature_by_name(creature_name) creature = donor.creatures[-1] creature.legion = donor self.summoned = False self.last_donor = None action = Action.UnsummonAngel(self.game.name, self.name, legion.markerid, donor.markerid, creature.name) self.notify(action)
def test_find_last_split(): game_name = "game" playername = "player" parent_markerid = "Rd01" child_markerid = "Rd02" parent_creature_names = 4 * [None] child_creature_names = 4 * [None] history = History.History() action = Action.SplitLegion(game_name, playername, parent_markerid, child_markerid, parent_creature_names, child_creature_names) assert history.find_last_split(playername, parent_markerid, child_markerid) is None history.update(None, action, None) assert history.find_last_split(playername, parent_markerid, child_markerid) == action assert history.find_last_split(playername, "Rd03", "Rd04") is None
def form_game(self, playername, game_name, min_players, max_players, ai_time_limit, player_time_limit, player_class, player_info): """Form a new game. Return None normally, or an error string if there's a problem. """ logging.info("%s %s %s %s %s %s %s %s", playername, game_name, min_players, max_players, ai_time_limit, player_time_limit, player_class, player_info) if not game_name: st = "Games must be named" logging.warning(st) return st game_info_tuples = self.get_game_info_tuples() game_names = set((tup[0] for tup in game_info_tuples)) if game_name in game_names: st = 'The game name "%s" is already in use' % game_name logging.warning(st) return st if min_players > max_players: st = "min_players must be <= max_players" logging.warning(st) return st now = time.time() GAME_START_DELAY = 5 * 60 game = Game.Game(game_name, playername, now, now + GAME_START_DELAY, min_players, max_players, master=True, ai_time_limit=ai_time_limit, player_time_limit=player_time_limit, player_class=player_class, player_info=player_info) self.games.append(game) game.add_observer(self) action = Action.FormGame(playername, game.name, game.create_time, game.start_time, game.min_players, game.max_players, ai_time_limit, player_time_limit, player_class, player_info) self.notify(action)
def recruit_creature(self, playername, game_name, markerid, creature_name, recruiter_names): """Recruit one creature.""" game = self.name_to_game(game_name) if game: player = game.get_player_by_name(playername) if player: legion = player.markerid_to_legion.get(markerid) if legion and not legion.recruited: caretaker = game.caretaker hexlabel = legion.hexlabel masterhex = game.board.hexes[hexlabel] mterrain = masterhex.terrain lst = list(recruiter_names[:]) lst.insert(0, creature_name) tup = tuple(lst) if tup in legion.available_recruits_and_recruiters( mterrain, caretaker): action = Action.RecruitCreature( game.name, player.name, markerid, creature_name, tuple(recruiter_names)) game.update(self, action, None)
def create_starting_legion(self): markerid = self.selected_markerid if markerid is None: raise AssertionError("create_starting_legion without marker") if markerid not in self.markerids_left: raise AssertionError("create_starting_legion with bad marker") if self.markerid_to_legion: raise AssertionError("create_starting_legion but have a legion") creatures = [ Creature.Creature(name) for name in creaturedata.starting_creature_names ] legion = Legion.Legion(self, self.take_marker(markerid), creatures, self.starting_tower) self.markerid_to_legion[markerid] = legion legion.add_observer(self.game) action = Action.CreateStartingLegion(self.game.name, self.name, markerid) caretaker = self.game.caretaker for creature in creatures: caretaker.take_one(creature.name) self.created_starting_legion = True self.notify(action)
def do_not_flee(self, playername, game_name, markerid): """Do not flee from an engagement.""" game = self.name_to_game(game_name) if game: legion = game.find_legion(markerid) hexlabel = legion.hexlabel player = game.get_player_by_name(playername) if player == game.active_player: logging.warning("attacker tried to not flee") return legion = player.markerid_to_legion.get(markerid) if legion is None: logging.warning("no legion") return if legion.player != player: logging.warning("wrong player tried to not flee") return for enemy_legion in game.all_legions(hexlabel): if enemy_legion != legion: break enemy_markerid = enemy_legion.markerid action = Action.DoNotFlee(game.name, markerid, enemy_markerid, hexlabel) game.update(self, action, None)
Legion.find_picname(action.markerid), ", ".join(action.angel_names)) elif isinstance(action, Action.GameOver): if len(action.winner_names) == 1: st = "%s wins!" % action.winner_names[0] else: st = "%s draw" % " and ".join(action.winner_names) if st and st != self.last_st: self.last_st = st label = gtk.Label(st) # left-align the label label.set_alignment(0.0, 0.5) self.vbox2.pack_start(label, expand=False, fill=False) label.show() upper = self.vadjustment.get_upper() self.vadjustment.set_value(upper) if __name__ == "__main__": import time from slugathon.util import guiutils parent = gtk.Window() event_log = EventLog(None, None) event_log.connect("destroy", guiutils.exit) parent.add(event_log) parent.show_all() action = Action.GameOver("a", ["Bob"], time.time()) reactor.callWhenRunning(event_log.update, None, action, None) reactor.run()
def add_observer(self, user): playername = user.name Observed.add_observer(self, user, playername) self.playernames.add(playername) action = Action.AddUsername(playername) self.notify(action)