mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 09:31:20 -04:00
better umm.yeah
This commit is contained in:
@@ -258,17 +258,16 @@ function getPublicState(rawState, context = {}) {
|
||||
const participantResults = state.status === 'ended' && state.finalResult
|
||||
? state.finalResult.participants
|
||||
: getParticipantList(state);
|
||||
const scannedObjects = Object.values(state.seenObjects || {})
|
||||
.sort((a, b) => (a.firstScannedAt || 0) - (b.firstScannedAt || 0))
|
||||
.slice(-5)
|
||||
.map((object) => object.label || object.entityId || object.code)
|
||||
.filter(Boolean);
|
||||
|
||||
return {
|
||||
id: GAME_ID,
|
||||
title: 'Most items',
|
||||
status: state.status,
|
||||
headline: state.lastMessage,
|
||||
detail: state.status === 'running'
|
||||
? `${uniqueItems} unique items, ${Math.ceil(remainingMs / 1000)} seconds left`
|
||||
: state.finalResult
|
||||
? `${state.finalResult.uniqueItems} unique items in last round`
|
||||
: 'scan different known objects',
|
||||
uniqueItems,
|
||||
totalObjectScans: state.totalObjectScans,
|
||||
duplicateObjectScans: state.duplicateObjectScans,
|
||||
@@ -304,6 +303,25 @@ function getPublicState(rawState, context = {}) {
|
||||
{ label: 'Object scans', value: state.totalObjectScans },
|
||||
{ label: 'World record', value: worldRecordText },
|
||||
],
|
||||
sections: [
|
||||
{
|
||||
title: 'Progress',
|
||||
// This replaces the older top-level detail text with a display-owned
|
||||
// section so every client renders game explanations from the same
|
||||
// structured field instead of guessing between headline/detail.
|
||||
items: [
|
||||
state.status === 'running'
|
||||
? `${uniqueItems} unique items, ${Math.ceil(remainingMs / 1000)} seconds left`
|
||||
: state.finalResult
|
||||
? `${state.finalResult.uniqueItems} unique items in last round`
|
||||
: 'scan different known objects',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Recent items',
|
||||
items: scannedObjects.length ? scannedObjects : ['no objects scanned yet'],
|
||||
},
|
||||
],
|
||||
results: Object.values(state.seenObjects || {}).slice(-3).map((object) => ({
|
||||
label: 'Item',
|
||||
value: object.label || object.entityId || object.code,
|
||||
|
||||
@@ -273,15 +273,24 @@ function getPublicState(rawState, context = {}) {
|
||||
const currentStep = state.currentQuest?.steps?.[state.progressIndex] || null;
|
||||
const totalSteps = state.currentQuest?.steps?.length || 0;
|
||||
const stepLabel = totalSteps ? `Item ${Math.min(state.progressIndex + 1, totalSteps)} of ${totalSteps}` : 'Find the object';
|
||||
const orderedList = state.currentQuest?.steps?.length
|
||||
? state.currentQuest.steps.map((step, idx) => `${idx + 1}. ${step.label}`).join(' -> ')
|
||||
: 'add object barcodes to the registry';
|
||||
const routeItems = state.currentQuest?.steps?.length
|
||||
? state.currentQuest.steps.map((step, idx) => ({
|
||||
// The item label is already numbered here because the same payload is
|
||||
// shown on both the far-away scanner screen and the compact Activities
|
||||
// panel. Keeping the ordering text server-side prevents every client
|
||||
// from inventing its own route numbering rules.
|
||||
label: `${idx + 1}. ${step.label}`,
|
||||
status: idx < state.progressIndex
|
||||
? 'complete'
|
||||
: idx === state.progressIndex
|
||||
? 'active'
|
||||
: 'pending',
|
||||
}))
|
||||
: [];
|
||||
return {
|
||||
id: GAME_ID,
|
||||
title: 'Scan quest',
|
||||
status: state.status === 'running' && state.currentQuest ? 'running' : state.status,
|
||||
headline: state.lastMessage || formatQuestPrompt(state),
|
||||
detail: orderedList,
|
||||
progress: {
|
||||
current: state.progressIndex,
|
||||
total: state.currentQuest?.steps?.length || 0,
|
||||
@@ -306,6 +315,16 @@ function getPublicState(rawState, context = {}) {
|
||||
{ label: 'Skipped', value: state.skippedItems },
|
||||
{ label: 'Quest points', value: getTopScores(state)[0]?.points || 0 },
|
||||
],
|
||||
sections: [
|
||||
{
|
||||
title: 'Route',
|
||||
// This section is the important part of scan quest: players need to
|
||||
// know the entire ordered route before they drive across the room.
|
||||
// It is intentionally structured instead of folded into secondary
|
||||
// text so clients can show progress and the route at the same time.
|
||||
items: routeItems.length ? routeItems : [{ label: 'add object barcodes to the registry', status: 'pending' }],
|
||||
},
|
||||
],
|
||||
results: state.recentEvents.slice(0, 3).map((event) => ({
|
||||
label: event.kind === 'timeout' ? 'Skipped' : event.kind,
|
||||
value: event.label || event.points || '',
|
||||
|
||||
@@ -270,12 +270,6 @@ function getPublicState(rawState, context = {}) {
|
||||
id: GAME_ID,
|
||||
title: 'Scans per second',
|
||||
status: state.status,
|
||||
headline: state.lastMessage,
|
||||
detail: state.status === 'running'
|
||||
? `${state.scans.length} scans, ${Math.ceil(remainingMs / 1000)} seconds left`
|
||||
: state.finalResult
|
||||
? `${state.finalResult.scanCount} scans in last round`
|
||||
: 'five minute scan challenge',
|
||||
scanCount: state.scans.length,
|
||||
scansPerSecond: Number(currentRate.toFixed(3)),
|
||||
bestRate: Number(bestRate.toFixed(3)),
|
||||
@@ -307,6 +301,21 @@ function getPublicState(rawState, context = {}) {
|
||||
{ label: 'Live scan rate', value: Number(currentRate).toFixed(2) },
|
||||
{ label: 'World record', value: worldRecordText },
|
||||
],
|
||||
sections: [
|
||||
{
|
||||
title: 'Round',
|
||||
// The section carries the richer explanatory text that used to live
|
||||
// in top-level detail. Keeping it under display means clients have a
|
||||
// single place to look for all visible game-status information.
|
||||
items: [
|
||||
state.status === 'running'
|
||||
? `${state.scans.length} scans, ${Math.ceil(remainingMs / 1000)} seconds left`
|
||||
: state.finalResult
|
||||
? `${state.finalResult.scanCount} scans in last round`
|
||||
: 'five minute scan challenge',
|
||||
],
|
||||
},
|
||||
],
|
||||
results: state.recentRounds.slice(0, 3).map((round) => ({
|
||||
label: 'Round',
|
||||
value: `${Number(round.scansPerSecond || 0).toFixed(2)} scans per second`,
|
||||
|
||||
@@ -56,7 +56,13 @@ function formatList(items = [], limit = 5) {
|
||||
|
||||
const visible = values.slice(0, limit);
|
||||
const extraCount = values.length - visible.length;
|
||||
const suffix = extraCount > 0 ? `, and ${extraCount} more` : '';
|
||||
if (extraCount === 1) {
|
||||
// A single hidden item reads badly as "and 1 more" in chat, especially for
|
||||
// result summaries where the omitted item is usually just one normal stat.
|
||||
// Including it is still short and avoids making the bot sound confused.
|
||||
return values.slice(0, limit + 1).join(', ');
|
||||
}
|
||||
const suffix = extraCount > 1 ? `, plus ${extraCount} more` : '';
|
||||
return `${visible.join(', ')}${suffix}`;
|
||||
}
|
||||
|
||||
@@ -166,7 +172,11 @@ function formatResultSummary(display = {}) {
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
return formatList([primary, ...usefulStats], 3);
|
||||
// Result summaries are already compact because games normally publish one
|
||||
// primary line and a few stats. Joining them all avoids vague bot messages
|
||||
// like "and 1 more" while still leaving the heavier participant lists to the
|
||||
// bounded formatList helper above.
|
||||
return [primary, ...usefulStats].filter(Boolean).join('. ');
|
||||
}
|
||||
|
||||
function sendGameStartChat(definition, participants = []) {
|
||||
@@ -906,6 +916,7 @@ function buildLifecycleGameState(store, { now, selectedDefinition, runningDefini
|
||||
secondary: store.resultDisplay?.secondary || 'Vote to start another game',
|
||||
timer: store.resultsUntil ? { label: 'Results clear in', endsAt: store.resultsUntil } : null,
|
||||
stats: store.resultDisplay?.stats || [],
|
||||
sections: store.resultDisplay?.sections || [],
|
||||
results: store.resultDisplay?.results || [],
|
||||
},
|
||||
};
|
||||
@@ -923,6 +934,17 @@ function buildLifecycleGameState(store, { now, selectedDefinition, runningDefini
|
||||
secondary: 'Get ready',
|
||||
timer: store.startsAt ? { label: 'Starts in', endsAt: store.startsAt } : null,
|
||||
stats: [],
|
||||
sections: [
|
||||
{
|
||||
title: 'Players',
|
||||
// Joining and starting are shared lifecycle states, so they expose
|
||||
// their own display sections here instead of asking each game to
|
||||
// explain the same startup flow.
|
||||
items: participants.length
|
||||
? participants.map((participant) => getParticipantName(participant)).filter(Boolean)
|
||||
: ['waiting for counted rovers'],
|
||||
},
|
||||
],
|
||||
results: [],
|
||||
},
|
||||
};
|
||||
@@ -941,6 +963,12 @@ function buildLifecycleGameState(store, { now, selectedDefinition, runningDefini
|
||||
secondary: `${selectedTitle} needs at least one rover`,
|
||||
timer: store.joinEndsAt ? { label: 'Join by', endsAt: store.joinEndsAt } : null,
|
||||
stats: [],
|
||||
sections: [
|
||||
{
|
||||
title: 'How to join',
|
||||
items: ['scan your rover barcode at the station'],
|
||||
},
|
||||
],
|
||||
results: [],
|
||||
},
|
||||
};
|
||||
@@ -962,6 +990,7 @@ function buildLifecycleGameState(store, { now, selectedDefinition, runningDefini
|
||||
label: game.title,
|
||||
value: voteCounts[game.id] || 0,
|
||||
})),
|
||||
sections: [],
|
||||
results: [],
|
||||
},
|
||||
};
|
||||
@@ -978,6 +1007,7 @@ function buildLifecycleGameState(store, { now, selectedDefinition, runningDefini
|
||||
secondary: 'Vote to start the next round',
|
||||
timer: null,
|
||||
stats: [],
|
||||
sections: [],
|
||||
results: [],
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user