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) { document.getElementById("players-content").innerHTML = players .filter((player) => player.active) .map((player) => { const onDestination = player.state === "ON_DESTINATION"; return `
  • ${player.name} (${player.move_count}) ${onDestination ? "✅" : ""}
  • `; }) .join(""); } 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) { cell.innerHTML = `
    ${player.name}
    ${playerIcon} `; } }); } 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) { closePurchaseWindow(); 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"); contentElement.innerHTML = products .map((product) => { return `
    ${product.name}
    ${product.name}
    `; }) .join(""); containerElement.classList.remove("d-none"); purchaseTimeoutElement.innerText = purchaseTimeout; } function productPurchaseTimerTick(timeLeft) { const purchaseTimeoutElement = document.getElementById("purchase-countdown"); purchaseTimeoutElement.innerText = timeLeft; } function closePurchaseWindow() { const container = document.getElementById("purchase-container"); container.classList.add("d-none"); } function productPurchaseDone(product) { const cardContainer = document.getElementById(`product-${product.id}`); cardContainer.classList.add("selected"); } function wsConnect() { console.log("Attempting to connect to", FAIRHOPPER_WS_SERVER); let ws = new WebSocket(FAIRHOPPER_WS_SERVER); 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_purchase_done": productPurchaseDone(wsMessage.data.product); break; default: console.error("Unknown message:", wsMessage); } }; ws.onclose = (e) => { setTimeout(() => { wsConnect(); }, 1000); }; ws.onerror = (err) => { console.error("Socket encountered error:", err.message, "Closing socket"); ws.close(); }; } window.onload = () => { wsConnect(); };