def display_state(game): comment("displaying game info:") comment( _RENDER( game, use_debugboard=use_debugboard, use_colour=use_colour, use_unicode=use_unicode, ), depth=1, )
def __init__(self, name, player_loc, time_limit=None, space_limit=None): self.name = name # create some context managers for resource limiting self.timer = _CountdownTimer(time_limit, self.name) if space_limit is not None: space_limit *= NUM_PLAYERS self.space = _MemoryWatcher(space_limit) # import the Player class from given package player_pkg, player_cls = player_loc comment(f"importing {self.name}'s player class '{player_cls}' " f"from package '{player_pkg}'") self.Player = _load_player_class(player_pkg, player_cls)
def action(self): comment(f"asking {self.name} for next action...") with self.space, self.timer: # ask the real player action = self.player.action() comment(f"{self.name} returned action: {action!r}", depth=1) comment(self.timer.status(), depth=1) comment(self.space.status(), depth=1) # give back the result return action
def update(self, opponent_action, player_action): comment(f"updating {self.name} with actions...") with self.space, self.timer: # forward to the real player self.player.update(opponent_action, player_action) comment(self.timer.status(), depth=1) comment(self.space.status(), depth=1)
def init(self, colour): self.colour = colour self.name += f" ({colour})" player_cls = str(self.Player).strip("<class >") comment(f"initialising {self.colour} player as a {player_cls}") with self.space, self.timer: # construct/initialise the player class self.player = self.Player(colour) comment(self.timer.status(), depth=1) comment(self.space.status(), depth=1)
def main(): # Parse command-line options into a namespace for use throughout this # program options = get_options() # Create a star-log for controlling the format of output from within this # program config(level=options.verbosity, ansi=options.use_colour) comment("all messages printed by the referee after this begin with *") comment("(any other lines of output must be from your Player class).") comment() try: # Import player classes p1 = PlayerWrapper( "player 1", options.player1_loc, time_limit=options.time, space_limit=options.space, ) p2 = PlayerWrapper( "player 2", options.player2_loc, time_limit=options.time, space_limit=options.space, ) # We'll start measuring space usage from now, after all # library imports should be finished: set_space_line() # Play the game! result = play( [p1, p2], delay=options.delay, print_state=(options.verbosity > 1), use_debugboard=(options.verbosity > 2), use_colour=options.use_colour, use_unicode=options.use_unicode, log_filename=options.logfile, ) # Display the final result of the game to the user. comment("game over!", depth=-1) print(result) # In case the game ends in an abnormal way, print a clean error # message for the user (rather than a trace). except KeyboardInterrupt: _print() # (end the line) comment("bye!") except IllegalActionException as e: comment("game error!", depth=-1) print("error: invalid action!") comment(e) except ResourceLimitException as e: comment("game error!", depth=-1) print("error: resource limit exceeded!") comment(e)
def wait(): comment("(press enter to continue)", end="") input()
def play( players, delay=0, print_state=True, use_debugboard=False, use_colour=False, use_unicode=False, log_filename=None, out_function=comment, ): """ Coordinate a game, return a string describing the result. Arguments: * players -- A list of Player wrappers supporting init, action and update methods. * delay -- Time in seconds to wait between turns, or negative to wait for user input. * print_state -- If True, print a picture of the board after each update. * use_debugboard -- If True, use a larger board during updates (if print_state is also True). * use_colour -- Use ANSI colour codes for output. * use_unicode -- Use unicode symbols for output. * log_filename -- If not None, log all game actions to this path. * out_function -- Use this function (instead of default 'comment') for all output messages. """ # Configure behaviour of this function depending on parameters: if delay > 0: def wait(): time.sleep(delay) elif delay < 0: def wait(): comment("(press enter to continue)", end="") input() else: def wait(): pass if print_state: def display_state(game): comment("displaying game info:") comment( _RENDER( game, use_debugboard=use_debugboard, use_colour=use_colour, use_unicode=use_unicode, ), depth=1, ) else: def display_state(game): pass # Set up a new game and initialise the players (constructing the # Player classes including running their .__init__() methods). game = Game(log_filename=log_filename) comment("initialising players", depth=-1) for player, colour in zip(players, COLOURS): # NOTE: `player` here is actually a player wrapper. Your program # should still implement a method called `__init__()`, not one # called `init()`: player.init(colour) # Display the initial state of the game. comment("game start!", depth=-1) display_state(game) # Repeat the following until the game ends # SIMULTANEOUS PLAY VERSION: # all players choose an action, then the board and players get updates: turn = 1 player_1, player_2 = players while not game.over(): comment(f"Turn {turn}", depth=-1) # Ask both players for their next action (calling .action() methods) action_1 = player_1.action() action_2 = player_2.action() # Validate both actions and apply them to the game if they are # allowed. Display the resulting game state game.update(action_1, action_2) display_state(game) # Notify both players of the actions (via .update() methods) player_1.update(opponent_action=action_2, player_action=action_1) player_2.update(opponent_action=action_1, player_action=action_2) # Next turn! turn += 1 wait() # After that loop, the game has ended (one way or another!) result = game.end() return result
def connect_and_play( player, name, channel, host, port, log_filename=None, out_function=None, print_state=True, use_debugboard=False, use_colour=False, use_unicode=False, ): """ Connect to and coordinate a game with a server, return a string describing the result. Parameters: * player -- Your player's wrapped object (supporting 'init', 'update' and 'action' methods). * name -- Your player's name on the server * channel -- The matchmaking channel string * host -- The server address * port -- The server port * log_filename -- If not None, log all game actions to this path. * print_state -- If True, print a picture of the board after each update. * use_debugboard -- If True, use a larger board during updates (if print_state is also True). * use_colour -- Use ANSI colour codes for output. * use_unicode -- Use unicode symbols for output. """ # Configure behaviour of this function depending on parameters: if print_state: def display_state(players_str, game): comment("displaying game info:") comment( _RENDER( game, message=players_str, use_debugboard=use_debugboard, use_colour=use_colour, use_unicode=use_unicode, ), depth=1, ) else: def display_state(players_str, game): pass # Set up a connection with the server comment("connecting to battleground", depth=-1) comment("attempting to connect to the server...") server = Server.from_address(host, port) comment("connection established!") # Wait for some matching players comment("looking for a game", depth=-1) channel_str = f"channel '{channel}'" if channel else "open channel" comment(f"submitting game request as '{name}' in {channel_str}...") server.send(M.PLAY, name=name, channel=channel) server.recv(M.OKAY) comment("game request submitted.") comment(f"waiting for opponents in {channel_str}...") comment("(press ^C to stop waiting)") # (wait through some OKAY-OKAY msg exchanges until a GAME message comes--- # the server is asking if we are still here waiting, or have disconnected) gamemsg = server.recv(M.OKAY | M.GAME) while gamemsg["mtype"] is not M.GAME: server.send(M.OKAY) gamemsg = server.recv(M.OKAY | M.GAME) # when we get a game message, it's time to play! comment("setting up game", depth=-1, clear=True) comment("opponents found!") for colour in COLOURS: comment(f"{colour} player:", gamemsg[colour]) # Initialise the player comment("initialising player", depth=-1) comment("waiting for colour assignment...") initmsg = server.recv(M.INIT) comment("playing as", initmsg["colour"], depth=1) comment("initialising your player class...") player.init(initmsg["colour"]) comment("ready to play!") server.send(M.OKAY) # Set up a new game and display the initial state and players comment("game start", depth=-1) players_str = format_players_str(gamemsg, player.colour) game = Game(log_filename) display_state(players_str, game) # Now wait for messages from the sever and respond accordingly while True: msg = server.recv(M.TURN | M.UPD8 | M.OVER | M.ERRO) if msg["mtype"] is M.TURN: # TODO: For simultaneous play, there's no need to display the # state again at the start of the turn... # comment("your turn!", depth=-1, clear=True) # display_state(players_str, game) # decide on action and submit it to server action = player.action() server.send(M.ACTN, action=action) elif msg["mtype"] is M.UPD8: player_action = msg["player_action"] opponent_action = msg["opponent_action"] comment("receiving update", depth=-1, clear=True) if player.colour == "upper": game.update( upper_action=player_action, lower_action=opponent_action, ) else: game.update( upper_action=opponent_action, lower_action=player_action, ) display_state(players_str, game) player.update( player_action=player_action, opponent_action=opponent_action, ) # then notify server we are ready to continue: server.send(M.OKAY) elif msg["mtype"] is M.OVER: # the game ended! return msg["result"] elif msg["mtype"] is M.ERRO: # seems like the server encountered an error, but not # with our connection raise ServerEncounteredError(msg["reason"])
def main(): # Parse command-line options into a namespace for use throughout this # program options = get_options() # Create a star-log for controlling the format of output from within this # program out = config(level=options.verbosity, ansi=options.use_colour) comment("all messages printed by the client after this begin with a *") comment("(any other lines of output must be from your Player class).") comment() try: # Import player classes player = PlayerWrapper( "your player", options.player_loc, ) # Even though we're not limiting space, the display # may still be useful for some users set_space_line() # Play the game, catching any errors and displaying them to the # user: result = connect_and_play( player=player, name=options.name, channel=options.channel, host=options.host, port=options.port, log_filename=options.logfile, print_state=(options.verbosity > 1), use_debugboard=(options.verbosity > 2), use_colour=options.use_colour, use_unicode=options.use_unicode, ) comment("game over!", depth=-1) print(result) except KeyboardInterrupt: _print() # (end the line) comment("bye!") except ConnectingException as e: print("error connecting to server") comment(e) except DisconnectException as e: print("connection lost", depth=-1) comment(e) except ProtocolException as e: print("protocol error!", depth=-1) comment(e) except ServerEncounteredError as e: print("server encountered error!", depth=-1) comment(e)