feat: Nummer „#N“ zählt je Subreddit ab dem ersten Tag dort
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2f12ae4ca1
commit
90150026ab
6 changed files with 47 additions and 15 deletions
8
PLAN.md
8
PLAN.md
|
|
@ -260,3 +260,11 @@ Abgabe: **9. Dezember 2026, 03:00 MEZ**. Eingereicht wird in „Best Daily Game
|
|||
Build gab es zweimal doppelte Versionsnummern und Abbruch. Bei Änderungen an `devvit.json` Playtest stoppen und neu
|
||||
starten.
|
||||
- Folge der 30-Tage-Regel: Freischaltungen gehen nach 30 Tagen ohne Spiel verloren.
|
||||
|
||||
### 2026-11-13 – Nummer je Subreddit
|
||||
|
||||
- „Ninelands #N“ zählt **je Subreddit ab dem ersten Tag dort** (`firstDay` in Redis, beim ersten Aufruf per `nx`
|
||||
gesetzt, enthält keine Nutzerdaten und läuft nicht ab). So beginnt `r/Ninelands` mit #1, auch wenn es erst im
|
||||
Dezember startet. Die Karte hängt weiter nur am Datum (alle spielen am selben Tag dieselbe). `FIRST_DAY` entfällt.
|
||||
- Plan Besitzer/Claude: `r/ninelands_game_dev` bleibt Test-Subreddit; `r/Ninelands` (Name frei) etwa eine Woche vor
|
||||
der Abgabe anlegen (≈ 1. Dez.), öffentlich oder „Restricted“, < 200 Mitglieder bis zur Bewertung.
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
// player:{userId} JSON PlayerStats + lastDay, Aussehen, Kronen-Tag
|
||||
// comments:{day} Hash userId → 1 (Ergebnis schon als Kommentar gepostet)
|
||||
// posts Hash day → postId (verhindert doppelte Tagesposts)
|
||||
// firstDay erster Tag in diesem Subreddit (Nummer „#N“ zählt ab hier; enthält keine Nutzerdaten)
|
||||
//
|
||||
// Devvit-Regel „Enable and respect user deletions“: Für Kontolöschungen gibt es keinen Trigger, deshalb laufen alle
|
||||
// Schlüssel mit User-ID oder Namen nach RETENTION_S ab (Reddit empfiehlt ≤ 30 Tage). Jede Abgabe verlängert die
|
||||
|
|
@ -255,3 +256,9 @@ export async function dbGetPostId(day: string): Promise<string | undefined> {
|
|||
export async function dbSetPostId(day: string, postId: string): Promise<void> {
|
||||
await redis.hSet('posts', {[day]: postId})
|
||||
}
|
||||
|
||||
/** Erster Tag in diesem Subreddit; beim ersten Aufruf auf heute gesetzt (nx: nie überschreiben) */
|
||||
export async function dbFirstDay(today: string): Promise<string> {
|
||||
await redis.set('firstDay', today, {nx: true})
|
||||
return (await redis.get('firstDay')) ?? today
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ const fake: Partial<typeof redis> = {
|
|||
expiries.set(k, seconds)
|
||||
},
|
||||
get: async k => strings.get(k),
|
||||
set: async (k, v) => {
|
||||
set: async (k, v, opts) => {
|
||||
if (opts?.nx && strings.has(k)) return ''
|
||||
strings.set(k, v)
|
||||
return 'OK'
|
||||
},
|
||||
|
|
@ -362,6 +363,12 @@ test('Flair: Streak ab 2 Tagen', async () => {
|
|||
assert.deepEqual(flairs.at(-1)?.text, '🔥 5-day streak')
|
||||
})
|
||||
|
||||
test('Nummer zählt ab dem ersten Tag im Subreddit', async () => {
|
||||
assert.equal((await getDay()).number, 1)
|
||||
strings.set('firstDay', addDays(today, -9))
|
||||
assert.equal((await getDay()).number, 10)
|
||||
})
|
||||
|
||||
test('404', async () => {
|
||||
const rsp = await fetch(serverURL)
|
||||
assert.equal(rsp.status, 404)
|
||||
|
|
|
|||
|
|
@ -23,12 +23,14 @@ import {
|
|||
type DailyMap,
|
||||
dailyMap,
|
||||
dayLabel,
|
||||
dayNumber,
|
||||
isDay,
|
||||
} from '../shared/daily.ts'
|
||||
import {flairText, shareLine} from '../shared/format.ts'
|
||||
import {DEFAULT_LOOK} from '../shared/look.ts'
|
||||
import {
|
||||
dbClaimComment,
|
||||
dbFirstDay,
|
||||
dbGetGoals,
|
||||
dbGetLook,
|
||||
dbGetPlayer,
|
||||
|
|
@ -176,7 +178,7 @@ async function routeGetDay(): Promise<GetDayRsp> {
|
|||
)
|
||||
return {
|
||||
day: map.day,
|
||||
number: map.number,
|
||||
number: dayNumber(await dbFirstDay(today), map.day),
|
||||
mapId: map.mapId,
|
||||
trait: map.trait,
|
||||
goals,
|
||||
|
|
@ -252,7 +254,7 @@ async function routeComment(): Promise<CommentRsp> {
|
|||
if (!(await dbClaimComment(map.day, userId))) return {posted: false}
|
||||
await reddit.submitComment({
|
||||
id: postId,
|
||||
text: `${shareLine(map.number, mine)} · #${mine.rank} today`,
|
||||
text: `${shareLine(dayNumber(await dbFirstDay(map.day), map.day), mine)} · #${mine.rank} today`,
|
||||
runAs: 'USER',
|
||||
})
|
||||
return {posted: true}
|
||||
|
|
@ -304,7 +306,7 @@ async function ensureDailyPost(): Promise<{id: string; created: boolean}> {
|
|||
const existing = await dbGetPostId(map.day)
|
||||
if (existing) return {id: existing, created: false}
|
||||
const post = await reddit.submitCustomPost({
|
||||
title: `Ninelands #${map.number} · ${dayLabel(map.day)}`,
|
||||
title: `Ninelands #${dayNumber(await dbFirstDay(map.day), map.day)} · ${dayLabel(map.day)}`,
|
||||
entry: 'default',
|
||||
postData: {day: map.day},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {Game} from '../engine/core/Game.ts'
|
|||
import {computeGoals} from '../engine/core/goals.ts'
|
||||
import {applyMoves, encodeMoves} from '../engine/core/replay.ts'
|
||||
import {dailySeed, mulberry32} from '../engine/core/rng.ts'
|
||||
import {dailyBoard, dailyMap} from './daily.ts'
|
||||
import {dailyBoard, dailyMap, dayNumber} from './daily.ts'
|
||||
|
||||
test('Tageskarte ist deterministisch und je Tag verschieden', () => {
|
||||
const a = dailyMap(new Date('2026-11-20T00:00:00Z'))
|
||||
|
|
@ -13,8 +13,13 @@ test('Tageskarte ist deterministisch und je Tag verschieden', () => {
|
|||
const c = dailyMap(new Date('2026-11-21T12:00:00Z'))
|
||||
assert.deepEqual(a, b)
|
||||
assert.notEqual(a.seed, c.seed)
|
||||
assert.equal(c.number, a.number + 1)
|
||||
assert.equal(dailyMap(new Date('2026-11-13T05:00:00Z')).number, 1)
|
||||
})
|
||||
|
||||
test('Nummer zählt je Subreddit ab dem ersten Tag dort', () => {
|
||||
assert.equal(dayNumber('2026-12-01', '2026-12-01'), 1)
|
||||
assert.equal(dayNumber('2026-12-01', '2026-12-09'), 9)
|
||||
assert.equal(dayNumber('2026-11-13', '2027-01-01'), 50)
|
||||
assert.equal(dayNumber('2026-12-01', '2026-11-20'), 1) // alte Karte vor dem Start: nie 0 oder negativ
|
||||
})
|
||||
|
||||
test('Reddit-Seed unterscheidet sich von der Website-Tageskarte', () => {
|
||||
|
|
|
|||
|
|
@ -8,14 +8,9 @@ import type {WorldTrait} from '../engine/core/WorldRules.ts'
|
|||
/** Präfix des Tages-Seeds. Nie ändern – sonst ändern sich alle Tageskarten (und Bestenlisten) rückwirkend. */
|
||||
export const DAILY_SEED_PREFIX = 'ninelands-reddit-daily-'
|
||||
|
||||
/** Erster Tag des Reddit-Spiels (UTC); daraus die laufende Nummer „Ninelands #N“. */
|
||||
export const FIRST_DAY = '2026-11-13'
|
||||
|
||||
export interface DailyMap {
|
||||
/** UTC-Tag „JJJJ-MM-TT“ */
|
||||
day: string
|
||||
/** laufende Nummer ab FIRST_DAY (1 = erster Tag) */
|
||||
number: number
|
||||
seed: number
|
||||
mapId: string
|
||||
trait: WorldTrait
|
||||
|
|
@ -24,11 +19,8 @@ export interface DailyMap {
|
|||
export function dailyMap(date: Date): DailyMap {
|
||||
const day = dailyKey(date)
|
||||
const seed = fnv1a(`${DAILY_SEED_PREFIX}${day}`)
|
||||
const number =
|
||||
Math.round((Date.parse(day) - Date.parse(FIRST_DAY)) / 86_400_000) + 1
|
||||
return {
|
||||
day,
|
||||
number,
|
||||
seed,
|
||||
mapId: mapCodeFromSeed(seed),
|
||||
trait: rolledTrait(seed),
|
||||
|
|
@ -40,6 +32,17 @@ export function dailyBoard(map: DailyMap): Board {
|
|||
return generateBoard(map.seed, {trait: map.trait})
|
||||
}
|
||||
|
||||
/**
|
||||
* Laufende Nummer „Ninelands #N“: zählt je Subreddit ab dem ersten Tag dort (1 = erster Tag), damit z. B.
|
||||
* r/Ninelands mit #1 beginnt. Die Karte selbst hängt nur am Datum – alle spielen am selben Tag dieselbe.
|
||||
*/
|
||||
export function dayNumber(firstDay: string, day: string): number {
|
||||
return Math.max(
|
||||
1,
|
||||
Math.round((Date.parse(day) - Date.parse(firstDay)) / 86_400_000) + 1,
|
||||
)
|
||||
}
|
||||
|
||||
/** Gültiger UTC-Tag „JJJJ-MM-TT“ (aus Post-Daten oder Anfragen) */
|
||||
export function isDay(value: unknown): value is string {
|
||||
return (
|
||||
|
|
|
|||
Loading…
Reference in a new issue