From e1e77aba96142b4e7762abe8f9ec906047cffddf Mon Sep 17 00:00:00 2001 From: Eden Kirin Date: Fri, 31 Mar 2023 12:19:48 +0200 Subject: [PATCH] Optimizations --- frontend/index.html | 176 +-------------------------------------- frontend/js/frontend.js | 179 ++++++++++++++++++++++++++++++++++++++++ hopper/engine.py | 59 +++++++------ 3 files changed, 214 insertions(+), 200 deletions(-) create mode 100644 frontend/js/frontend.js diff --git a/frontend/index.html b/frontend/index.html index 53da497..90db0eb 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -8,6 +8,7 @@ + FairHopper Visualisation Client @@ -42,179 +43,4 @@ - - \ No newline at end of file diff --git a/frontend/js/frontend.js b/frontend/js/frontend.js new file mode 100644 index 0000000..000decd --- /dev/null +++ b/frontend/js/frontend.js @@ -0,0 +1,179 @@ +const BOARD_ICONS = { + PLAYER: "😀", + PLAYER_ON_DESTINATION: "😎", + OBSTACLE: "🔥", + DESTINATION: "🏠", +}; + +function createBoard(board) { + let html = ""; + for (let y = 0; y < board.height; y++) { + let colHtml = ""; + for (let x = 0; x < board.width; x++) { + colHtml += `
 
`; + } + html += ` +
+ ${colHtml} +
+ `; + } + document.getElementById("board-content").innerHTML = html; +} + +function findCell(position) { + return document.getElementById(`cell-${position.x}-${position.y}`); +} + +function renderCellContent(position, content) { + const cell = findCell(position); + if (cell) { + cell.innerText = content; + } +} + +function renderPlayerList(players) { + const html = players + .filter((player) => player.active) + .map((player) => { + const onDestination = player.state == "ON_DESTINATION"; + return ` +
  • + ${player.name} (${player.move_count}) + ${onDestination ? "✅" : ""} +
  • + `; + }) + .join(""); + document.getElementById("players-content").innerHTML = html; +} + +function renderPlayers(players) { + players + .filter((player) => player.active) + .forEach((player) => { + const cell = findCell(player.position); + const onDestination = player.state == "ON_DESTINATION"; + const playerIcon = onDestination ? BOARD_ICONS.PLAYER_ON_DESTINATION : BOARD_ICONS.PLAYER; + if (cell) { + const html = ` +
    ${player.name}
    + ${playerIcon} + `; + cell.innerHTML = html; + } + }); +} + +function getLayerObjectsOfType(layers, type) { + let objects = []; + layers.forEach((layer) => { + objects = objects.concat(layer.objects.filter((obj) => obj.type === type)); + }); + return objects; +} + +function renderObstacles(layers) { + const objects = getLayerObjectsOfType(layers, "OBSTACLE"); + objects.forEach((obj) => { + renderCellContent(obj.position, BOARD_ICONS.OBSTACLE); + }); +} + +function renderDestination(position) { + renderCellContent(position, BOARD_ICONS.DESTINATION); +} + +function renderGameDump(data) { + createBoard(data.board); + renderObstacles(data.layers); + renderDestination(data.destination.position); + renderPlayerList(data.players); + renderPlayers(data.players); +} + +function productPurchaseStart(products, purchaseTimeout) { + console.log("productPurchaseStart:", products); + const containerElement = document.getElementById("purchase-container"); + const contentElement = document.getElementById("products-content"); + const purchaseTimeoutElement = document.getElementById("purchase-countdown"); + + const html = products + .map((product) => { + return ` +
    + ${product.name} +
    +
    ${product.name}
    +
    +
    + `; + }) + .join(""); + + contentElement.innerHTML = html; + containerElement.classList.remove("d-none"); + purchaseTimeoutElement.innerText = purchaseTimeout; +} + +function productPurchaseTimerTick(timeLeft) { + const purchaseTimeoutElement = document.getElementById("purchase-countdown"); + purchaseTimeoutElement.innerText = timeLeft; +} + +function productPurchased(product) { + console.log("productPurchased:", product); +} + +function productPurchaseDone() { + console.log("productPurchaseDone"); + const container = document.getElementById("purchase-container"); + container.classList.add("d-none"); +} + +function wsConnect() { + let ws = new WebSocket("ws://localhost:8011"); + ws.onopen = () => { + console.log("WS connected"); + }; + + ws.onmessage = (e) => { + const wsMessage = JSON.parse(e.data); + console.log("WS message received:", wsMessage); + + switch (wsMessage.message) { + case "game_dump": + renderGameDump(wsMessage.data); + break; + case "product_purchase_start": + productPurchaseStart(wsMessage.data.products, wsMessage.data.timeout); + break; + case "product_purchase_timer_tick": + productPurchaseTimerTick(wsMessage.data.time_left); + break; + case "product_purchased": + productPurchased(wsMessage.data); + break; + case "product_purchase_done": + productPurchaseDone(); + break; + default: + console.error("Unknown message:", wsMessage); + } + }; + + ws.onclose = (e) => { + setTimeout(function () { + wsConnect(); + }, 1000); + }; + + ws.onerror = (err) => { + console.error("Socket encountered error:", err.message, "Closing socket"); + ws.close(); + }; +} + +window.onload = () => { + wsConnect(); +}; diff --git a/hopper/engine.py b/hopper/engine.py index 84cccf0..06b21c8 100644 --- a/hopper/engine.py +++ b/hopper/engine.py @@ -22,6 +22,22 @@ from hopper.ws_server import WSServer from settings import settings +def create_player_start_position(board_width: int, board_height: int) -> Position: + """Create random position somewhere on the board border""" + border_len = (board_width + board_height) * 2 + rnd_position = random.randint(0, border_len - 1) + + if rnd_position < board_width * 2: + x = rnd_position % board_width + y = 0 if rnd_position < board_width else board_height - 1 + else: + rnd_position -= 2 * board_width + x = 0 if rnd_position < board_height else board_width - 1 + y = rnd_position % board_height + + return Position(x=x, y=y) + + class GameEngine: def __init__(self, board: GameBoard, ws_server: WSServer = None) -> None: self.board = board @@ -29,7 +45,8 @@ class GameEngine: self.players = PlayerList() self._inacivity_watchdog = None self._purchase_countdown_timer: Optional[CountdownTimer] = None - self.reset_game() + self.game_state = GameState.RUNNING + self.__debug_print_board() def dump_board(self) -> list[list[str]]: dump = self.board.dump() @@ -61,6 +78,10 @@ class GameEngine: ) self._inacivity_watchdog.start() + async def send_game_dump(self): + self.__debug_print_board() + await self.ws_server.send_game_dump() + def reset_game(self) -> None: self.__debug_print_board() self.game_state = GameState.RUNNING @@ -69,7 +90,7 @@ class GameEngine: self._start_inactivity_watchdog() player = Player( name=player_name, - position=self._create_player_start_position(), + position=create_player_start_position(self.board.width, self.board.height), state=PlayerState.CREATED, ) self.players.append(player) @@ -77,32 +98,15 @@ class GameEngine: logging.info(f"Starting new game for player: {player}") self.__debug_print_board() - if self.ws_server: - await self.ws_server.send_game_dump() + await self.send_game_dump() #!!!!!!!!!!!!!!! await self._player_on_destination(player) #!!!!!!!!!!!!!!! await asyncio.sleep(settings.game.MOVE_DELAY) - return player - def _create_player_start_position(self) -> Position: - """Create random position somewhere on the board border""" - border_len = (self.board.width + self.board.height) * 2 - rnd_position = random.randint(0, border_len - 1) - - if rnd_position < self.board.width * 2: - x = rnd_position % self.board.width - y = 0 if rnd_position < self.board.width else self.board.height - 1 - else: - rnd_position -= 2 * self.board.width - x = 0 if rnd_position < self.board.height else self.board.width - 1 - y = rnd_position % self.board.height - - return Position(x=x, y=y) - def _move_position(self, position: Position, direction: Direction) -> Position: new_position = Position(position.x, position.y) if direction == Direction.LEFT: @@ -149,10 +153,8 @@ class GameEngine: await self._player_on_destination(player) return PlayerMoveResult.DESTINATION_REACHED - if self.ws_server: - await self.ws_server.send_game_dump() + await self.send_game_dump() - self.__debug_print_board() await asyncio.sleep(settings.game.MOVE_DELAY) return PlayerMoveResult.OK @@ -171,8 +173,7 @@ class GameEngine: logging.info(f"Player {player} reached destination!") self.game_state = GameState.LOCK_FOR_MOVEMENT - await self.ws_server.send_game_dump() - self.__debug_print_board() + await self.send_game_dump() await self.ws_server.send_product_purchase_start_message( player=player, products=settings.products @@ -183,6 +184,7 @@ class GameEngine: ) def on_purchase_timer_tick(time_left) -> None: + logging.info(f"Purchase countdown timer tick, time left: {time_left}") asyncio.run( self.ws_server.send_product_purchase_time_left_message( player=player, time_left=time_left @@ -198,6 +200,7 @@ class GameEngine: ) ) self.game_state = GameState.RUNNING + asyncio.run(self.send_game_dump()) self._purchase_countdown_timer = CountdownTimer( seconds=settings.purchase_timeout, @@ -206,6 +209,12 @@ class GameEngine: ) self._purchase_countdown_timer.start() + def _reset_player(self, player) -> None: + # move player to start position + player.position = create_player_start_position(self.board.width, self.board.height) + player.state = PlayerState.CREATED + player.last_seen = None + def get_board_layout(self) -> BoardLayout: return BoardLayout(board=self.board, players=self.players)