diff --git a/src/game/actions.js b/src/game/actions.js index d88b2d54..1e02dd51 100644 --- a/src/game/actions.js +++ b/src/game/actions.js @@ -567,7 +567,7 @@ function move(state, {move}) { draft.player.currentEnergy = 3 draft.player.block = 0 draft.dungeon.graph[move.y][move.x].didVisit = true - draft.dungeon.pathTaken.push({x: move.x, y: move.y}) + draft.dungeon.pathTaken.push([move.x, move.y]) draft.dungeon.x = move.x draft.dungeon.y = move.y // if (number === state.dungeon.rooms.length - 1) { diff --git a/src/game/backend.js b/src/game/backend.js index 83f80c81..544ba88f 100644 --- a/src/game/backend.js +++ b/src/game/backend.js @@ -6,8 +6,20 @@ const apiUrl = 'https://api.slaytheweb.cards/api/runs' /** * @typedef {object} Run * @prop {string} player - user inputted player name + * @prop {win} number + * @prop {number} floor + * @prop {number} floor + * @prop {number} floor * @prop {object} gameState - the final state - * @prop {Array} gamePast - a list of past states + * @prop {PastEntry[]} gamePast - a list of past states + */ + +/** + * A simplified version of the game.past entries + * @typedef {object} PastEntry + * @prop {number} turn + * @prop {object} action + * @prop {object} player */ /** @@ -17,8 +29,6 @@ const apiUrl = 'https://api.slaytheweb.cards/api/runs' * @returns {Promise} */ export async function postRun(game, playerName) { - console.log('postRun', game.past.list) - /** @type {Run} */ const run = { player: playerName || 'Unknown entity', @@ -28,12 +38,16 @@ export async function postRun(game, playerName) { gamePast: game.past.list.map((item) => { return { action: item.action, + // we're not including the entire state, it's too much data + // but we do want to know which turn and the player's state at the time turn: item.state.turn, player: item.state.player, } }), } + console.log('Posting run', run) + return fetch(apiUrl, { method: 'POST', headers: { @@ -52,3 +66,11 @@ export async function getRuns() { const {runs} = await res.json() return runs } + +/** + * @returns {Promise} a single run + */ +export async function getRun(id) { + const res = await fetch(apiUrl + `/${id}`) + return res.json() +} diff --git a/src/game/dungeon.js b/src/game/dungeon.js index 6ca4ebfa..289e6e40 100644 --- a/src/game/dungeon.js +++ b/src/game/dungeon.js @@ -56,9 +56,9 @@ export const defaultOptions = { * @prop {string} id a unique id * @prop {Graph} graph * @prop {Array} paths - * @prop {number} x current x position - * @prop {number} y current y position - * @prop {Array} pathTaken a list of moves we've taken + * @prop {number} x current x position (which path) + * @prop {number} y current y position (where on the path) + * @prop {Array} pathTaken a list of moves we've taken */ /** @@ -87,7 +87,7 @@ export default function Dungeon(options) { paths, x: 0, y: 0, - pathTaken: [{x: 0, y: 0}], + pathTaken: [[0, 0]], } } diff --git a/src/ui/components/dungeon-stats.js b/src/ui/components/dungeon-stats.js index d99929c7..05ddb909 100644 --- a/src/ui/components/dungeon-stats.js +++ b/src/ui/components/dungeon-stats.js @@ -13,16 +13,17 @@ export default function DungeonStats({dungeon}) { ` } -const getEnemiesStats = (dungeon) => { +export const getEnemiesStats = (dungeon) => { const stats = { killed: 0, encountered: 0, maxHealth: 0, finalHealth: 0, } + if (!dungeon.graph) throw new Error('Missing dungeon graph') /* for each path taken (room) in the dungeon, get some stats */ - dungeon.pathTaken.forEach((usedNode) => { - const nodeData = dungeon.graph[usedNode.y][usedNode.x] + dungeon.pathTaken.forEach(([x, y]) => { + const nodeData = dungeon.graph[y][x] /* find some stats about the enemies encountered */ if (nodeData.room?.monsters) { /* how many encountered monsters */ diff --git a/src/ui/components/publish-run.js b/src/ui/components/publish-run.js index 3d5eb860..b9638cab 100644 --- a/src/ui/components/publish-run.js +++ b/src/ui/components/publish-run.js @@ -34,7 +34,7 @@ export function PublishRun({game}) { -

${loading ? 'submitting' : ''}

+

${loading ? 'Submitting…' : ''}

View highscores

` : html`

Thank you.

`} diff --git a/src/ui/pages/stats.astro b/src/ui/pages/stats.astro index cc722350..81c1f88a 100644 --- a/src/ui/pages/stats.astro +++ b/src/ui/pages/stats.astro @@ -19,71 +19,75 @@ const runs = (await getRuns()).reverse()

A chronological list of Slay the Web runs.
- There is quite a bit of statistics that could be gathered from the runs, and isn't yet shown here. Chat on #slaytheweb:matrix.org

+ There is quite a bit of statistics that could be gathered from the runs, and isn't yet shown here. Chat on #slaytheweb:matrix.org

- - - - - - - - - - - - - - { - runs?.length - ? runs.map((run) => { - const state = run.gameState - const date = new Intl.DateTimeFormat('en', { - dateStyle: 'long', - // timeStyle: 'short', - hour12: false, - }).format(new Date(state.createdAt)) + +
PlayerWin?FloorHealthCardsTimeDate
+ + + + + + + + + + + { + runs?.length + ? runs.map((run) => { + const state = run.gameState + const date = new Intl.DateTimeFormat('en', { + dateStyle: 'long', + // timeStyle: 'short', + hour12: false, + }).format(new Date(run.createdAt)) - let duration = 0 - if (state.endedAt) { - const ms = state.endedAt - state.createdAt - const hours = Math.floor(ms / (1000 * 60 * 60)) - const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60)) - const seconds = Math.floor((ms / 1000) % 60) - duration = `${hours > 0 ? hours + 'h ' : ''}${minutes}m ${seconds}s` - } + let duration = 0 + if (run.endedAt) { + const ms = run.endedAt - run.createdAt + const hours = Math.floor(ms / (1000 * 60 * 60)) + const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60)) + const seconds = Math.floor((ms / 1000) % 60) + duration = `${hours > 0 ? hours + 'h ' : ''}${minutes}m ${seconds}s` + } - return ( - - - - - - - - - - ) - }) - : 'Loading...' - } - -
PlayerWin?FloorTimeDate
{run.player}{state.won ? 'WIN' : 'LOSS'}{state.dungeon.y}{state.player.currentHealth}{run.gameState.deck.length}{duration}{date}
-

- If you want your run removed, let me know. -

-
+ return ( + + + + {run.id}. {run.player} + + + {run.won ? 'WIN' : 'LOSS'} + {run.floor} + {duration} + {date} + + ) + }) + : 'Loading...' + } + + +

+ If you want your run removed, let me know. +

- diff --git a/src/ui/pages/stats/[id].astro b/src/ui/pages/stats/[id].astro new file mode 100644 index 00000000..349b57d4 --- /dev/null +++ b/src/ui/pages/stats/[id].astro @@ -0,0 +1,67 @@ +--- +import Layout from '../../layouts/Layout.astro' +import {getRuns, getRun} from '../../../game/backend.js' +import {getEnemiesStats} from '../../components/dungeon-stats.js' +import '../../styles/typography.css' + +export const getStaticPaths = async () => { + const runs = await getRuns() + return runs.map((run) => { + return { + params: {id: run.id}, + } + }) +} + +const {id} = Astro.params +const run = await getRun(id) +const state = run.gameState + +const date = new Intl.DateTimeFormat('en', { + dateStyle: 'long', + hour12: false, +}).format(new Date(state.createdAt)) + +const ms = state.endedAt - state.createdAt +const hours = Math.floor(ms / (1000 * 60 * 60)) +const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60)) +const seconds = Math.floor((ms / 1000) % 60) +const duration = `${hours > 0 ? hours + 'h ' : ''}${minutes}m ${seconds}s` + +// Not all runs have this data in the backend. +let extraStats = false +if (state.dungeon.graph) { + extraStats = getEnemiesStats(state.dungeon) + console.log(extraStats) +} +--- + + +
+

← Back to all runs

+

Slay the Web run no. {run.id}

+
+

+ {run.player} made it to floor {state.dungeon.y} and {state.won ? 'won' : 'lost'} in { + duration + } on {date} with {state.player.currentHealth}/{state.player.maxHealth} health. +

+ { + extraStats && ( +

+ You encountered {extraStats.encountered} monsters. And killed {extraStats.killed} of them. +

+ ) + } +

Final deck had {state.deck.length} cards:

+
    + {state.deck.map((card) =>
  • {card}
  • )} +
+

+ Feel free to inspect the data yourself: api.slaytheweb.cards/runs/{run.id}. +

+
+
+