Optimizations
This commit is contained in:
@ -8,6 +8,7 @@
|
|||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
|
||||||
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
|
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<script src="js/frontend.js"></script>
|
||||||
|
|
||||||
<title>FairHopper Visualisation Client</title>
|
<title>FairHopper Visualisation Client</title>
|
||||||
</head>
|
</head>
|
||||||
@ -42,179 +43,4 @@
|
|||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
<script>
|
|
||||||
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 += `<div class="cell" id="cell-${x}-${y}"> </div>`;
|
|
||||||
}
|
|
||||||
html += `
|
|
||||||
<div class="flex-grid">
|
|
||||||
${colHtml}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
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 `
|
|
||||||
<li class="${onDestination ? "text-success" : ""}">
|
|
||||||
${player.name} (${player.move_count})
|
|
||||||
${onDestination ? "✅" : ""}
|
|
||||||
</li>
|
|
||||||
`;
|
|
||||||
}).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 = `
|
|
||||||
<div class="player-tooltip">${player.name}</div>
|
|
||||||
${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 `
|
|
||||||
<div class="card product">
|
|
||||||
<img src="img/products/${product.name}.jpeg" class="card-img-topx" alt="${product.name}">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">${product.name}</h5>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}).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();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
179
frontend/js/frontend.js
Normal file
179
frontend/js/frontend.js
Normal file
@ -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 += `<div class="cell" id="cell-${x}-${y}"> </div>`;
|
||||||
|
}
|
||||||
|
html += `
|
||||||
|
<div class="flex-grid">
|
||||||
|
${colHtml}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
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 `
|
||||||
|
<li class="${onDestination ? "text-success" : ""}">
|
||||||
|
${player.name} (${player.move_count})
|
||||||
|
${onDestination ? "✅" : ""}
|
||||||
|
</li>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.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 = `
|
||||||
|
<div class="player-tooltip">${player.name}</div>
|
||||||
|
${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 `
|
||||||
|
<div class="card product">
|
||||||
|
<img src="img/products/${product.name}.jpeg" class="card-img-topx" alt="${product.name}">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">${product.name}</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.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();
|
||||||
|
};
|
||||||
@ -22,6 +22,22 @@ from hopper.ws_server import WSServer
|
|||||||
from settings import settings
|
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:
|
class GameEngine:
|
||||||
def __init__(self, board: GameBoard, ws_server: WSServer = None) -> None:
|
def __init__(self, board: GameBoard, ws_server: WSServer = None) -> None:
|
||||||
self.board = board
|
self.board = board
|
||||||
@ -29,7 +45,8 @@ class GameEngine:
|
|||||||
self.players = PlayerList()
|
self.players = PlayerList()
|
||||||
self._inacivity_watchdog = None
|
self._inacivity_watchdog = None
|
||||||
self._purchase_countdown_timer: Optional[CountdownTimer] = 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]]:
|
def dump_board(self) -> list[list[str]]:
|
||||||
dump = self.board.dump()
|
dump = self.board.dump()
|
||||||
@ -61,6 +78,10 @@ class GameEngine:
|
|||||||
)
|
)
|
||||||
self._inacivity_watchdog.start()
|
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:
|
def reset_game(self) -> None:
|
||||||
self.__debug_print_board()
|
self.__debug_print_board()
|
||||||
self.game_state = GameState.RUNNING
|
self.game_state = GameState.RUNNING
|
||||||
@ -69,7 +90,7 @@ class GameEngine:
|
|||||||
self._start_inactivity_watchdog()
|
self._start_inactivity_watchdog()
|
||||||
player = Player(
|
player = Player(
|
||||||
name=player_name,
|
name=player_name,
|
||||||
position=self._create_player_start_position(),
|
position=create_player_start_position(self.board.width, self.board.height),
|
||||||
state=PlayerState.CREATED,
|
state=PlayerState.CREATED,
|
||||||
)
|
)
|
||||||
self.players.append(player)
|
self.players.append(player)
|
||||||
@ -77,32 +98,15 @@ class GameEngine:
|
|||||||
logging.info(f"Starting new game for player: {player}")
|
logging.info(f"Starting new game for player: {player}")
|
||||||
self.__debug_print_board()
|
self.__debug_print_board()
|
||||||
|
|
||||||
if self.ws_server:
|
await self.send_game_dump()
|
||||||
await self.ws_server.send_game_dump()
|
|
||||||
|
|
||||||
#!!!!!!!!!!!!!!!
|
#!!!!!!!!!!!!!!!
|
||||||
await self._player_on_destination(player)
|
await self._player_on_destination(player)
|
||||||
#!!!!!!!!!!!!!!!
|
#!!!!!!!!!!!!!!!
|
||||||
|
|
||||||
await asyncio.sleep(settings.game.MOVE_DELAY)
|
await asyncio.sleep(settings.game.MOVE_DELAY)
|
||||||
|
|
||||||
return player
|
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:
|
def _move_position(self, position: Position, direction: Direction) -> Position:
|
||||||
new_position = Position(position.x, position.y)
|
new_position = Position(position.x, position.y)
|
||||||
if direction == Direction.LEFT:
|
if direction == Direction.LEFT:
|
||||||
@ -149,10 +153,8 @@ class GameEngine:
|
|||||||
await self._player_on_destination(player)
|
await self._player_on_destination(player)
|
||||||
return PlayerMoveResult.DESTINATION_REACHED
|
return PlayerMoveResult.DESTINATION_REACHED
|
||||||
|
|
||||||
if self.ws_server:
|
await self.send_game_dump()
|
||||||
await self.ws_server.send_game_dump()
|
|
||||||
|
|
||||||
self.__debug_print_board()
|
|
||||||
await asyncio.sleep(settings.game.MOVE_DELAY)
|
await asyncio.sleep(settings.game.MOVE_DELAY)
|
||||||
return PlayerMoveResult.OK
|
return PlayerMoveResult.OK
|
||||||
|
|
||||||
@ -171,8 +173,7 @@ class GameEngine:
|
|||||||
logging.info(f"Player {player} reached destination!")
|
logging.info(f"Player {player} reached destination!")
|
||||||
|
|
||||||
self.game_state = GameState.LOCK_FOR_MOVEMENT
|
self.game_state = GameState.LOCK_FOR_MOVEMENT
|
||||||
await self.ws_server.send_game_dump()
|
await self.send_game_dump()
|
||||||
self.__debug_print_board()
|
|
||||||
|
|
||||||
await self.ws_server.send_product_purchase_start_message(
|
await self.ws_server.send_product_purchase_start_message(
|
||||||
player=player, products=settings.products
|
player=player, products=settings.products
|
||||||
@ -183,6 +184,7 @@ class GameEngine:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def on_purchase_timer_tick(time_left) -> None:
|
def on_purchase_timer_tick(time_left) -> None:
|
||||||
|
logging.info(f"Purchase countdown timer tick, time left: {time_left}")
|
||||||
asyncio.run(
|
asyncio.run(
|
||||||
self.ws_server.send_product_purchase_time_left_message(
|
self.ws_server.send_product_purchase_time_left_message(
|
||||||
player=player, time_left=time_left
|
player=player, time_left=time_left
|
||||||
@ -198,6 +200,7 @@ class GameEngine:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.game_state = GameState.RUNNING
|
self.game_state = GameState.RUNNING
|
||||||
|
asyncio.run(self.send_game_dump())
|
||||||
|
|
||||||
self._purchase_countdown_timer = CountdownTimer(
|
self._purchase_countdown_timer = CountdownTimer(
|
||||||
seconds=settings.purchase_timeout,
|
seconds=settings.purchase_timeout,
|
||||||
@ -206,6 +209,12 @@ class GameEngine:
|
|||||||
)
|
)
|
||||||
self._purchase_countdown_timer.start()
|
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:
|
def get_board_layout(self) -> BoardLayout:
|
||||||
return BoardLayout(board=self.board, players=self.players)
|
return BoardLayout(board=self.board, players=self.players)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user