Project rename and restructure
This commit is contained in:
153
hopper/engine.py
Normal file
153
hopper/engine.py
Normal file
@ -0,0 +1,153 @@
|
||||
import logging
|
||||
|
||||
from hopper.enums import Direction, PlayerMoveResult
|
||||
from hopper.errors import Collision, PositionOutOfBounds
|
||||
from hopper.models.board import (
|
||||
BOARD_DUMP_CHARS,
|
||||
Destination,
|
||||
GameBoard,
|
||||
Layer,
|
||||
LayerObject,
|
||||
ObjectType,
|
||||
create_random_position,
|
||||
)
|
||||
from hopper.models.player import Player, PlayerList, Position
|
||||
from hopper.watchdog import InactivityWatchdog
|
||||
from settings import settings
|
||||
|
||||
|
||||
class GameEngine:
|
||||
def __init__(self, board: GameBoard) -> None:
|
||||
self.board = board
|
||||
self.players = PlayerList()
|
||||
self._inacivity_watchdog = None
|
||||
self.__debug_print_board()
|
||||
|
||||
def dump_board(self) -> list[list[str]]:
|
||||
dump = self.board.dump()
|
||||
|
||||
for player in self.players:
|
||||
dump[player.position.y][player.position.x] = BOARD_DUMP_CHARS[
|
||||
ObjectType.PLAYER
|
||||
]
|
||||
|
||||
return dump
|
||||
|
||||
def __debug_print_board(self):
|
||||
if not (settings.debug and settings.debug.PRINT_BOARD):
|
||||
return
|
||||
for line in self.dump_board():
|
||||
print(" ".join(line))
|
||||
|
||||
def _start_inactivity_watchdog(self) -> None:
|
||||
if not self._inacivity_watchdog:
|
||||
self._inacivity_watchdog = InactivityWatchdog(
|
||||
players=self.players, daemon=True
|
||||
)
|
||||
self._inacivity_watchdog.start()
|
||||
|
||||
def start_game(self, player_name: str) -> Player:
|
||||
self._start_inactivity_watchdog()
|
||||
player = Player(
|
||||
name=player_name,
|
||||
position=Position(0, 0),
|
||||
)
|
||||
self.players.append(player)
|
||||
|
||||
logging.info(f"Starting new game for player: {player}")
|
||||
self.__debug_print_board()
|
||||
|
||||
return player
|
||||
|
||||
def move_player(self, player: Player, direction: Direction) -> PlayerMoveResult:
|
||||
player.reset_timeout()
|
||||
|
||||
new_position = Position(player.position.x, player.position.y)
|
||||
logging.info(f"Player {player} move to {direction}")
|
||||
|
||||
player.move_attempt_count += 1
|
||||
|
||||
if direction == Direction.LEFT:
|
||||
new_position.x -= 1
|
||||
elif direction == Direction.RIGHT:
|
||||
new_position.x += 1
|
||||
elif direction == Direction.UP:
|
||||
new_position.y -= 1
|
||||
elif direction == Direction.DOWN:
|
||||
new_position.y += 1
|
||||
else:
|
||||
raise ValueError(f"Unhandled direction: {direction}")
|
||||
|
||||
if not self.position_in_board_bounds(new_position):
|
||||
raise PositionOutOfBounds()
|
||||
|
||||
if self.colided_with_obstacle(new_position):
|
||||
raise Collision()
|
||||
|
||||
player.position = new_position
|
||||
player.move_count += 1
|
||||
|
||||
if self.is_player_on_destination(player):
|
||||
logging.info(f"Player {player} reached destination!")
|
||||
return PlayerMoveResult.DESTINATION_REACHED
|
||||
|
||||
self.__debug_print_board()
|
||||
return PlayerMoveResult.OK
|
||||
|
||||
def is_player_on_destination(self, player: Player) -> bool:
|
||||
return player.position == self.board.destination.position
|
||||
|
||||
def position_in_board_bounds(self, position: Position) -> bool:
|
||||
return (
|
||||
0 <= position.x < self.board.width and 0 <= position.y < self.board.height
|
||||
)
|
||||
|
||||
def colided_with_obstacle(self, position: Position) -> bool:
|
||||
return self.board.get_object_at_position(position) is not None
|
||||
|
||||
|
||||
class GameEngineFactory:
|
||||
@staticmethod
|
||||
def create(
|
||||
board_width: int,
|
||||
board_height: int,
|
||||
obstacle_count: int = 0,
|
||||
) -> GameEngine:
|
||||
board = GameBoard(
|
||||
width=board_width,
|
||||
height=board_height,
|
||||
destination=Destination(Position(board_height // 2, board_height // 2)),
|
||||
)
|
||||
obstacle_layer = Layer(name="obstacles")
|
||||
for _ in range(obstacle_count):
|
||||
obstacle_layer.objects.append(
|
||||
LayerObject(
|
||||
type_=ObjectType.OBSTACLE,
|
||||
position=create_random_position(board_width, board_height),
|
||||
),
|
||||
)
|
||||
board.layers.append(obstacle_layer)
|
||||
|
||||
game = GameEngine(board=board)
|
||||
GameEngineFactory.__add_test_player(game.players)
|
||||
return game
|
||||
|
||||
@staticmethod
|
||||
def create_default() -> GameEngine:
|
||||
return GameEngineFactory.create(
|
||||
board_width=settings.board.WIDTH,
|
||||
board_height=settings.board.HEIGHT,
|
||||
obstacle_count=settings.board.OBSTACLE_COUNT,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def __add_test_player(players: PlayerList) -> None:
|
||||
if not (settings.debug and settings.debug.CREATE_TEST_PLAYER):
|
||||
return
|
||||
player = Player(
|
||||
name="Pero",
|
||||
uuid="test-player-id",
|
||||
position=Position(2, 2),
|
||||
)
|
||||
players.append(player)
|
||||
logging.info(f"Test player created: {player}")
|
||||
Reference in New Issue
Block a user