generated from mwc/project_game
game
This commit is contained in:
222
class Game source.py
Normal file
222
class Game source.py
Normal file
@@ -0,0 +1,222 @@
|
||||
from collections import defaultdict
|
||||
from signal import signal, SIGWINCH
|
||||
from time import sleep, perf_counter
|
||||
from blessed import Terminal
|
||||
from retro.view import View
|
||||
from retro.validation import (
|
||||
validate_agent,
|
||||
validate_state,
|
||||
validate_agent_name,
|
||||
validate_position,
|
||||
)
|
||||
from retro.errors import (
|
||||
AgentWithNameAlreadyExists,
|
||||
AgentNotFoundByName,
|
||||
IllegalMove,
|
||||
)
|
||||
|
||||
class Game:
|
||||
"""
|
||||
Creates a playable game.
|
||||
You will use Game to create games, but don't need to read or understand how
|
||||
this class works. The main work in creating a
|
||||
|
||||
Arguments:
|
||||
agents (list): A list of agents to add to the game.
|
||||
state (dict): A dict containing the game's initial state.
|
||||
board_size (int, int): (Optional) The two-dimensional size of the game board. D
|
||||
debug (bool): (Optional) Turn on debug mode, showing log messages while playing.
|
||||
framerate (int): (Optional) The target number of frames per second at which the
|
||||
game should run.
|
||||
color (str): (Optional) The game's background color scheme. `Available colors <https://blessed.readthedocs.io/en/latest/colors.html>`_.
|
||||
|
||||
::
|
||||
|
||||
# This example will create a simple game.
|
||||
from retro.game import Game
|
||||
from retro.agent import ArrowKeyAgent
|
||||
|
||||
agents = [ArrowKeyAgent()]
|
||||
state = {}
|
||||
game = Game(agents, state)
|
||||
game.play()
|
||||
|
||||
"""
|
||||
STATE_HEIGHT = 5
|
||||
EXIT_CHARACTERS = ("KEY_ENTER", "KEY_ESCAPE")
|
||||
|
||||
def __init__(self, agents, state, board_size=(64, 32), debug=False, framerate=24,
|
||||
color="white_on_black"):
|
||||
self.log_messages = []
|
||||
self.agents_by_name = {}
|
||||
self.agents = []
|
||||
self.state = validate_state(state)
|
||||
self.board_size = board_size
|
||||
self.debug = debug
|
||||
self.framerate = framerate
|
||||
self.turn_number = 0
|
||||
self.color = color
|
||||
for agent in agents:
|
||||
self.add_agent(agent)
|
||||
|
||||
def play(self):
|
||||
"""Starts the game.
|
||||
"""
|
||||
self.playing = True
|
||||
terminal = Terminal()
|
||||
with terminal.fullscreen(), terminal.hidden_cursor(), terminal.cbreak():
|
||||
view = View(terminal, color=self.color)
|
||||
while self.playing:
|
||||
turn_start_time = perf_counter()
|
||||
self.turn_number += 1
|
||||
self.keys_pressed = self.collect_keystrokes(terminal)
|
||||
if self.debug and self.keys_pressed:
|
||||
self.log("Keys: " + ', '.join(k.name or str(k) for k in self.keys_pressed))
|
||||
for agent in self.agents:
|
||||
if hasattr(agent, 'handle_keystroke'):
|
||||
for key in self.keys_pressed:
|
||||
agent.handle_keystroke(key, self)
|
||||
if hasattr(agent, 'play_turn'):
|
||||
agent.play_turn(self)
|
||||
if getattr(agent, 'display', True):
|
||||
if not self.on_board(agent.position):
|
||||
raise IllegalMove(agent, agent.position)
|
||||
view.render(self)
|
||||
turn_end_time = perf_counter()
|
||||
time_elapsed_in_turn = turn_end_time - turn_start_time
|
||||
time_remaining_in_turn = max(0, 1/self.framerate - time_elapsed_in_turn)
|
||||
sleep(time_remaining_in_turn)
|
||||
while True:
|
||||
if terminal.inkey().name in self.EXIT_CHARACTERS:
|
||||
break
|
||||
|
||||
|
||||
def collect_keystrokes(self, terminal):
|
||||
keys = set()
|
||||
while True:
|
||||
key = terminal.inkey(0.001)
|
||||
if key:
|
||||
keys.add(key)
|
||||
else:
|
||||
break
|
||||
return keys
|
||||
|
||||
def log(self, message):
|
||||
"""Write a log message.
|
||||
Log messages are only shown when debug mode is on.
|
||||
They can be very useful for debugging.
|
||||
|
||||
Arguments:
|
||||
message (str): The message to log.
|
||||
"""
|
||||
self.log_messages.append((self.turn_number, message))
|
||||
|
||||
|
||||
def end(self):
|
||||
"""Ends the game. No more turns will run.
|
||||
"""
|
||||
self.playing = False
|
||||
|
||||
|
||||
def add_agent(self, agent):
|
||||
"""Adds an agent to the game.
|
||||
Whenever you want to add a new agent during the game, you must add it to
|
||||
the game using this method.
|
||||
|
||||
Arguments:
|
||||
agent: An instance of an agent class.
|
||||
"""
|
||||
validate_agent(agent)
|
||||
if getattr(agent, "display", True) and not self.on_board(agent.position):
|
||||
raise IllegalMove(agent, agent.position)
|
||||
if hasattr(agent, "name"):
|
||||
if agent.name in self.agents_by_name:
|
||||
raise AgentWithNameAlreadyExists(agent.name)
|
||||
self.agents_by_name[agent.name] = agent
|
||||
self.agents.append(agent)
|
||||
|
||||
|
||||
def get_agent_by_name(self, name):
|
||||
"""Looks up an agent by name.
|
||||
This is useful when one agent needs to interact with another agent.
|
||||
|
||||
Arguments:
|
||||
name (str): The agent's name. If there is no agent with this name,
|
||||
you will get an error.
|
||||
|
||||
Returns:
|
||||
An agent.
|
||||
"""
|
||||
validate_agent_name(name)
|
||||
if name in self.agents_by_name:
|
||||
return self.agents_by_name[name]
|
||||
else:
|
||||
raise AgentNotFoundByName(name)
|
||||
|
||||
|
||||
def is_empty(self, position):
|
||||
"""Checks whether a position is occupied by any agents.
|
||||
|
||||
Arguments:
|
||||
position (int, int): The position to check.
|
||||
|
||||
Returns:
|
||||
A bool
|
||||
"""
|
||||
return position not in self.get_agents_by_position()
|
||||
|
||||
|
||||
def get_agents_by_position(self):
|
||||
"""Returns a dict where each key is a position (e.g. (10, 20)) and
|
||||
each value is a list containing all the agents at that position.
|
||||
This is useful when an agent needs to find out which other agents are
|
||||
on the same space or nearby.
|
||||
"""
|
||||
positions = defaultdict(list)
|
||||
for agent in self.agents:
|
||||
if getattr(agent, "display", True):
|
||||
validate_position(agent.position)
|
||||
positions[agent.position].append(agent)
|
||||
return positions
|
||||
|
||||
|
||||
def remove_agent(self, agent):
|
||||
"""Removes an agent from the game.
|
||||
|
||||
Arguments:
|
||||
agent (Agent): the agent to remove.
|
||||
"""
|
||||
if agent not in self.agents:
|
||||
raise AgentNotInGame(agent)
|
||||
else:
|
||||
self.agents.remove(agent)
|
||||
if hasattr(agent, "name"):
|
||||
self.agents_by_name.pop(agent.name)
|
||||
|
||||
|
||||
def remove_agent_by_name(self, name):
|
||||
"""Removes an agent from the game.
|
||||
|
||||
Arguments:
|
||||
name (str): the agent's name.
|
||||
"""
|
||||
validate_agent_name(name)
|
||||
if name not in self.agents_by_name:
|
||||
raise AgentNotFoundByName(name)
|
||||
agent = self.agents_by_name.pop(name)
|
||||
self.agents.remove(agent)
|
||||
|
||||
|
||||
def on_board(self, position):
|
||||
"""Checks whether a position is on the game board.
|
||||
|
||||
Arguments:
|
||||
position (int, int): The position to check
|
||||
|
||||
Returns:
|
||||
A bool
|
||||
"""
|
||||
validate_position(position)
|
||||
x, y = position
|
||||
bx, by = self.board_size
|
||||
return x >= 0 and x < bx and y >= 0 and y < by
|
||||
Reference in New Issue
Block a user