-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #71 from strakam/refactors
refactor: Improve handle events
- Loading branch information
Showing
7 changed files
with
198 additions
and
87 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,77 +1,149 @@ | ||
import pygame | ||
from pygame.event import Event | ||
from abc import abstractmethod | ||
|
||
from .properties import Properties | ||
from generals.core import config as c | ||
|
||
# keybindings # | ||
RIGHT = pygame.K_RIGHT | ||
LEFT = pygame.K_LEFT | ||
SPACE = pygame.K_SPACE | ||
Q = pygame.K_q | ||
R = pygame.K_r | ||
H = pygame.K_h | ||
L = pygame.K_l | ||
|
||
|
||
class Command: | ||
def __init__(self): | ||
self.quit: bool = False | ||
|
||
|
||
class ReplayCommand(Command): | ||
def __init__(self): | ||
super().__init__() | ||
self.frame_change: int = 0 | ||
self.speed_change: float = 1.0 | ||
self.restart: bool = False | ||
self.pause_toggle: bool = False | ||
|
||
|
||
class GameCommand(Command): | ||
def __init__(self): | ||
super().__init__() | ||
raise NotImplementedError | ||
|
||
|
||
class TrainCommand(Command): | ||
def __init__(self): | ||
super().__init__() | ||
|
||
|
||
class EventHandler: | ||
def __init__(self, properties: Properties, from_replay=False): | ||
def __init__(self, properties: Properties): | ||
""" | ||
Initialize the event handler. | ||
Args: | ||
properties: the Properties object | ||
from_replay: bool, whether the game is from a replay | ||
""" | ||
self.properties = properties | ||
self.from_replay = from_replay | ||
self.mode = properties.mode | ||
|
||
def handle_events(self): | ||
def handle_events(self) -> Command: | ||
""" | ||
Handle pygame GUI events | ||
""" | ||
control_events = { | ||
"time_change": 0, | ||
} | ||
for event in pygame.event.get(): | ||
if event.type == pygame.QUIT or ( | ||
event.type == pygame.KEYDOWN and event.key == pygame.K_q | ||
): | ||
pygame.quit() | ||
quit() | ||
|
||
if event.type == pygame.KEYDOWN and self.from_replay: | ||
self.__handle_key_controls(event, control_events) | ||
if event.type == pygame.QUIT: | ||
self.command.quit = True | ||
if event.type == pygame.KEYDOWN: | ||
self.handle_key_event(event) | ||
elif event.type == pygame.MOUSEBUTTONDOWN: | ||
self.__handle_mouse_click() | ||
|
||
return control_events | ||
|
||
self.handle_mouse_event() | ||
return self.command | ||
|
||
def __handle_key_controls(self, event, control_events): | ||
def is_click_on_agents_row(self, x: int, y: int, i: int) -> bool: | ||
""" | ||
Handle key controls for replay mode. | ||
Control game speed, pause, and replay frames. | ||
Check if the click is on an agent's row. | ||
Args: | ||
x: int, x-coordinate of the click | ||
y: int, y-coordinate of the click | ||
i: int, index of the row | ||
""" | ||
match event.key: | ||
# Speed up game right arrow is pressed | ||
case pygame.K_RIGHT: | ||
self.properties.game_speed = max(1 / 128, self.properties.game_speed / 2) | ||
# Slow down game left arrow is pressed | ||
case pygame.K_LEFT: | ||
self.properties.game_speed = min(32.0, self.properties.game_speed * 2) | ||
# Toggle play/pause | ||
case pygame.K_SPACE: | ||
self.properties.paused = not self.properties.paused | ||
case pygame.K_r: | ||
control_events["restart"] = True | ||
# Control replay frames | ||
case pygame.K_h: | ||
control_events["time_change"] = -1 | ||
self.properties.paused = True | ||
case pygame.K_l: | ||
control_events["time_change"] = 1 | ||
self.properties.paused = True | ||
|
||
|
||
def __handle_mouse_click(self): | ||
return ( | ||
x >= self.properties.display_grid_width | ||
and (i + 1) * c.GUI_ROW_HEIGHT <= y < (i + 2) * c.GUI_ROW_HEIGHT | ||
) | ||
|
||
@abstractmethod | ||
def handle_key_event(self, event: Event) -> Command: | ||
raise NotImplementedError | ||
|
||
@abstractmethod | ||
def handle_mouse_event(self): | ||
raise NotImplementedError | ||
|
||
|
||
class ReplayEventHandler(EventHandler): | ||
def __init__(self, properties: Properties): | ||
super().__init__(properties) | ||
self.command = ReplayCommand() | ||
|
||
def handle_key_event(self, event: Event) -> ReplayCommand: | ||
if event.key == Q: | ||
self.command.quit = True | ||
elif event.key == RIGHT: | ||
self.command.speed_change = 2.0 | ||
elif event.key == LEFT: | ||
self.command.speed_change = 0.5 | ||
elif event.key == SPACE: | ||
self.command.pause_toggle = True | ||
elif event.key == R: | ||
self.command.restart = True | ||
elif event.key == H: | ||
self.command.frame_change = -1 | ||
elif event.key == L: | ||
self.command.frame_change = 1 | ||
return self.command | ||
|
||
def handle_mouse_event(self) -> None: | ||
""" | ||
Handle mouse click event. | ||
Handle mouse clicks in replay mode. | ||
""" | ||
agents = self.properties.game.agents | ||
agent_fov = self.properties.agent_fov | ||
|
||
x, y = pygame.mouse.get_pos() | ||
for i, agent in enumerate(agents): | ||
if self.properties.is_click_on_agents_row(x, y, i): | ||
if self.is_click_on_agents_row(x, y, i): | ||
agent_fov[agent] = not agent_fov[agent] | ||
break | ||
|
||
|
||
class GameEventHandler(EventHandler): | ||
def __init__(self, properties: Properties): | ||
super().__init__(properties) | ||
self.command = GameCommand() | ||
|
||
def handle_key_event(self, event: Event) -> GameCommand: | ||
raise NotImplementedError | ||
|
||
def handle_mouse_event(self) -> None: | ||
raise NotImplementedError | ||
|
||
|
||
class TrainEventHandler(EventHandler): | ||
def __init__(self, properties: Properties): | ||
super().__init__(properties) | ||
self.command = TrainCommand() | ||
|
||
def handle_key_event(self, event: Event) -> TrainCommand: | ||
if event.key == Q: | ||
self.command.quit = True | ||
return self.command | ||
|
||
def handle_mouse_event(self) -> None: | ||
raise NotImplementedError |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,50 @@ | ||
from typing import Any | ||
import pygame | ||
from typing import Any, Literal | ||
|
||
from generals.core.game import Game | ||
from .properties import Properties | ||
from .event_handler import EventHandler | ||
from .event_handler import TrainEventHandler, GameEventHandler, ReplayEventHandler | ||
from .rendering import Renderer | ||
|
||
|
||
class GUI: | ||
def __init__( | ||
self, game: Game, agent_data: dict[str, dict[str, Any]], from_replay=False | ||
self, | ||
game: Game, | ||
agent_data: dict[str, dict[str, Any]], | ||
mode: Literal["train", "game", "replay"] = "train", | ||
): | ||
self.properties = Properties(game, agent_data) | ||
self.properties = Properties(game, agent_data, mode) | ||
self.__renderer = Renderer(self.properties) | ||
self.__event_handler = EventHandler(self.properties, from_replay) | ||
self.__event_handler = self.__initialize_event_handler() | ||
|
||
pygame.init() | ||
pygame.display.set_caption("Generals") | ||
|
||
# Handle key repeats | ||
pygame.key.set_repeat(500, 64) | ||
|
||
def __initialize_event_handler(self): | ||
if self.properties.mode == "train": | ||
return TrainEventHandler | ||
elif self.properties.mode == "game": | ||
return GameEventHandler | ||
elif self.properties.mode == "replay": | ||
return ReplayEventHandler | ||
|
||
def tick(self, fps=None): | ||
control_events = self.__event_handler.handle_events() | ||
handler = self.__event_handler(self.properties) | ||
command = handler.handle_events() | ||
if command.quit: | ||
quit() | ||
if self.properties.mode == "replay": | ||
self.properties.update_speed(command.speed_change) | ||
if command.frame_change != 0 or command.restart: | ||
self.properties.paused = True | ||
if command.pause_toggle: | ||
self.properties.paused = not self.properties.paused | ||
self.__renderer.render(fps) | ||
return control_events | ||
return command | ||
|
||
def close(self): | ||
pygame.quit() |
Oops, something went wrong.