Improve character suggestions and serve pools from the API

This commit is contained in:
2026-09-04 19:58:35 -07:00
parent 1dac2141e6
commit 0ae1701b82
11 changed files with 255 additions and 1255 deletions
@@ -7,6 +7,17 @@
export let state;
let suggestionError = '';
async function suggest(type, apply) {
suggestionError = '';
try {
apply(await getSuggestion(type));
} catch (error) {
suggestionError = 'Could not load suggestions. Try again.';
}
}
// Form states
let basicDetails = {
avatar_look: state.player.avatar_look || '',
@@ -252,6 +263,8 @@
</script>
{#if suggestionError}<p role="alert">{suggestionError}</p>{/if}
<div class="character-creation-view">
<div class="view-header text-center">
<h2>Character Sheet Creation</h2>
@@ -281,28 +294,28 @@
<label for="avatar_look">What does your Pi-Rat look like?</label>
<div class="input-row">
<input type="text" id="avatar_look" placeholder="e.g. A blue bandana, scarred snout" bind:value={basicDetails.avatar_look} required class="input-field">
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => basicDetails.avatar_look = getSuggestion('look')}>Suggest</button>{/if}
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => suggest('look', value => basicDetails.avatar_look = value)}>Suggest</button>{/if}
</div>
</div>
<div class="form-group">
<label for="avatar_smell">What does your Pi-Rat smell like?</label>
<div class="input-row">
<input type="text" id="avatar_smell" placeholder="e.g. Damp gunpowder, salty cheese" bind:value={basicDetails.avatar_smell} required class="input-field">
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => basicDetails.avatar_smell = getSuggestion('smell')}>Suggest</button>{/if}
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => suggest('smell', value => basicDetails.avatar_smell = value)}>Suggest</button>{/if}
</div>
</div>
<div class="form-group">
<label for="first_words">What were your first words after transforming?</label>
<div class="input-row">
<input type="text" id="first_words" placeholder="e.g. Where is my gat?!" bind:value={basicDetails.first_words} required class="input-field">
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => basicDetails.first_words = getSuggestion('first_words')}>Suggest</button>{/if}
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => suggest('first_words', value => basicDetails.first_words = value)}>Suggest</button>{/if}
</div>
</div>
<div class="form-group">
<label for="good_at_math">Is your Pi-Rat good at Math?</label>
<div class="input-row">
<input type="text" id="good_at_math" placeholder="e.g. Yes, but only geometry; No, thinks Pi is a dessert" bind:value={basicDetails.good_at_math} required class="input-field">
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => basicDetails.good_at_math = getSuggestion('good_at_math')}>Suggest</button>{/if}
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => suggest('good_at_math', value => basicDetails.good_at_math = value)}>Suggest</button>{/if}
</div>
</div>
<div class="input-row mt-4">
@@ -356,7 +369,7 @@
</label>
<div class="input-row">
<input type="text" class="input-field" bind:value={inboxAnswers[`${p.id}:like`]} placeholder="e.g. They always share their cheese"/>
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => inboxAnswers[`${p.id}:like`] = getSuggestion('like')}>Suggest</button>{/if}
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => suggest('like', value => inboxAnswers[`${p.id}:like`] = value)}>Suggest</button>{/if}
<button class="btn btn-primary" disabled={!(inboxAnswers[`${p.id}:like`] || '').trim()} on:click={() => submitDelegatedAnswer('like', p.id)}>Submit</button>
</div>
</div>
@@ -369,7 +382,7 @@
</label>
<div class="input-row">
<input type="text" class="input-field" bind:value={inboxAnswers[`${p.id}:hate`]} placeholder="e.g. They snore too loudly"/>
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => inboxAnswers[`${p.id}:hate`] = getSuggestion('hate')}>Suggest</button>{/if}
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => suggest('hate', value => inboxAnswers[`${p.id}:hate`] = value)}>Suggest</button>{/if}
<button class="btn btn-primary" disabled={!(inboxAnswers[`${p.id}:hate`] || '').trim()} on:click={() => submitDelegatedAnswer('hate', p.id)}>Submit</button>
</div>
</div>
+19 -6
View File
@@ -6,6 +6,17 @@
export let state;
let suggestionError = '';
async function suggest(type, apply) {
suggestionError = '';
try {
apply(await getSuggestion(type));
} catch (error) {
suggestionError = 'Could not load suggestions. Try again.';
}
}
function getPlayerName(id) {
if (!id) return 'None';
const p = state.players.find(x => x.id === id);
@@ -137,6 +148,8 @@
</script>
{#if suggestionError}<p role="alert">{suggestionError}</p>{/if}
<div class="recruit-phase-view">
<div class="view-header text-center">
<h2>🐀 New Recruits</h2>
@@ -162,28 +175,28 @@
<label for="avatar_look">What does your Pi-Rat look like?</label>
<div class="input-row">
<input type="text" id="avatar_look" placeholder="e.g. A blue bandana, scarred snout" bind:value={basicDetails.avatar_look} required class="input-field">
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => basicDetails.avatar_look = getSuggestion('look')}>Suggest</button>{/if}
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => suggest('look', value => basicDetails.avatar_look = value)}>Suggest</button>{/if}
</div>
</div>
<div class="form-group">
<label for="avatar_smell">What does your Pi-Rat smell like? (Determines name)</label>
<div class="input-row">
<input type="text" id="avatar_smell" placeholder="e.g. Damp gunpowder, salty cheese" bind:value={basicDetails.avatar_smell} required class="input-field">
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => basicDetails.avatar_smell = getSuggestion('smell')}>Suggest</button>{/if}
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => suggest('smell', value => basicDetails.avatar_smell = value)}>Suggest</button>{/if}
</div>
</div>
<div class="form-group">
<label for="first_words">What were your first words after transforming?</label>
<div class="input-row">
<input type="text" id="first_words" placeholder="e.g. Where is my gat?!" bind:value={basicDetails.first_words} required class="input-field">
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => basicDetails.first_words = getSuggestion('first_words')}>Suggest</button>{/if}
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => suggest('first_words', value => basicDetails.first_words = value)}>Suggest</button>{/if}
</div>
</div>
<div class="form-group">
<label for="good_at_math">Is your Pi-Rat good at Math?</label>
<div class="input-row">
<input type="text" id="good_at_math" placeholder="e.g. Thinks Pi is a dessert" bind:value={basicDetails.good_at_math} required class="input-field">
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => basicDetails.good_at_math = getSuggestion('good_at_math')}>Suggest</button>{/if}
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => suggest('good_at_math', value => basicDetails.good_at_math = value)}>Suggest</button>{/if}
</div>
</div>
<div class="input-row mt-4">
@@ -264,7 +277,7 @@
</label>
<div class="input-row">
<input type="text" class="input-field" bind:value={inboxAnswers[`${task.recruit.id}:like`]} placeholder="e.g. They always share their cheese"/>
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => inboxAnswers[`${task.recruit.id}:like`] = getSuggestion('like')}>Suggest</button>{/if}
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => suggest('like', value => inboxAnswers[`${task.recruit.id}:like`] = value)}>Suggest</button>{/if}
<button class="btn btn-primary" disabled={!(inboxAnswers[`${task.recruit.id}:like`] || '').trim()} on:click={() => submitDelegatedAnswer('like', task.recruit.id)}>Submit</button>
</div>
</div>
@@ -277,7 +290,7 @@
</label>
<div class="input-row">
<input type="text" class="input-field" bind:value={inboxAnswers[`${task.recruit.id}:hate`]} placeholder="e.g. They snore too loudly"/>
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => inboxAnswers[`${task.recruit.id}:hate`] = getSuggestion('hate')}>Suggest</button>{/if}
{#if devMode}<button type="button" class="btn btn-secondary btn-small" on:click={() => suggest('hate', value => inboxAnswers[`${task.recruit.id}:hate`] = value)}>Suggest</button>{/if}
<button class="btn btn-primary" disabled={!(inboxAnswers[`${task.recruit.id}:hate`] || '').trim()} on:click={() => submitDelegatedAnswer('hate', task.recruit.id)}>Submit</button>
</div>
</div>
+2 -1
View File
@@ -6,10 +6,11 @@
// - Add a CHANGELOG entry only when a commit changes something players can
// see. Skip refactors, tests, and tooling. Keep wording player-facing.
export const VERSION = 46;
export const VERSION = 47;
// Newest first. Each entry: { version, date: 'YYYY-MM-DD', changes: [string, ...] }.
export const CHANGELOG = [
{ version: 47, date: '2026-09-04', changes: ['Rewrote character suggestions with scrappy pirate looks, distinctive smells, first words, and crew quirks that fit Yeld.'] },
{ version: 46, date: '2026-09-04', changes: ['Added a printable TL;DR rules page, linked at the top of the full rulebook.'] },
{ version: 44, date: '2026-09-04', changes: ['Crew Objectives opens as one connected panel, with the toggle and checklist sharing a border and background.'] },
{ version: 43, date: '2026-09-04', changes: ['The crew roster keeps players in the same order when roles or ranks change, including when someone becomes the Deep.'] },
File diff suppressed because it is too large Load Diff
+11 -11
View File
@@ -113,18 +113,18 @@
async function skipCharacterCreation() {
// Suggested values for everyone's free-text fields; the backend only
// applies them to fields players haven't filled in themselves.
const fills = {};
for (const p of state.players) {
fills[p.id] = {
avatar_look: getSuggestion('look'),
avatar_smell: getSuggestion('smell'),
first_words: getSuggestion('first_words'),
good_at_math: getSuggestion('good_at_math'),
like: getSuggestion('like'),
hate: getSuggestion('hate'),
};
}
try {
const fills = {};
for (const p of state.players) {
fills[p.id] = {
avatar_look: await getSuggestion('look'),
avatar_smell: await getSuggestion('smell'),
first_words: await getSuggestion('first_words'),
good_at_math: await getSuggestion('good_at_math'),
like: await getSuggestion('like'),
hate: await getSuggestion('hate'),
};
}
await apiRequest(`/game/${gameId}/player/${playerId}/skip-character-creation`, 'POST', {
fills: JSON.stringify(fills)
});