Moteur de simulation (plan 1/2) #1

Merged
Mahtan merged 29 commits from feat/sim-core into main 2026-10-08 14:05:53 +02:00
4 changed files with 129 additions and 0 deletions
Showing only changes of commit 9765e75dd2 - Show all commits

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
[
{ "id": "tetris", "title": "Tetris", "console": "GB", "releaseDate": "1990-09-28", "launchPriceFrancs": 199, "publisher": "Nintendo", "genre": "Puzzle", "basePopularity": 95 },
{ "id": "super-mario-land", "title": "Super Mario Land", "console": "GB", "releaseDate": "1990-09-28", "launchPriceFrancs": 249, "publisher": "Nintendo", "genre": "Plateforme", "basePopularity": 95 },
{ "id": "tennis", "title": "Tennis", "console": "GB", "releaseDate": "1990-09-28", "launchPriceFrancs": 199, "publisher": "Nintendo", "genre": "Sport", "basePopularity": 50, "uncertain": true },
{ "id": "alleyway", "title": "Alleyway", "console": "GB", "releaseDate": "1990-09-28", "launchPriceFrancs": 199, "publisher": "Nintendo", "genre": "Casse-briques", "basePopularity": 40, "uncertain": true },
{ "id": "baseball", "title": "Baseball", "console": "GB", "releaseDate": "1990-09-28", "launchPriceFrancs": 199, "publisher": "Nintendo", "genre": "Sport", "basePopularity": 30, "uncertain": true },
{ "id": "golf", "title": "Golf", "console": "GB", "releaseDate": "1990-11-15", "launchPriceFrancs": 199, "publisher": "Nintendo", "genre": "Sport", "basePopularity": 45, "uncertain": true },
{ "id": "kwirk", "title": "Kwirk", "console": "GB", "releaseDate": "1990-10-15", "launchPriceFrancs": 199, "publisher": "Acclaim", "genre": "Puzzle", "basePopularity": 30, "uncertain": true },
{ "id": "bugs-bunny-crazy-castle", "title": "The Bugs Bunny Crazy Castle", "console": "GB", "releaseDate": "1990-10-15", "launchPriceFrancs": 249, "publisher": "Kemco", "genre": "Puzzle", "basePopularity": 45, "uncertain": true },
{ "id": "castlevania-the-adventure", "title": "Castlevania: The Adventure", "console": "GB", "releaseDate": "1990-11-15", "launchPriceFrancs": 249, "publisher": "Konami", "genre": "Action", "basePopularity": 50, "uncertain": true },
{ "id": "batman", "title": "Batman", "console": "GB", "releaseDate": "1990-11-15", "launchPriceFrancs": 249, "publisher": "Sunsoft", "genre": "Action", "basePopularity": 60, "uncertain": true },
{ "id": "solar-striker", "title": "Solar Striker", "console": "GB", "releaseDate": "1990-11-15", "launchPriceFrancs": 199, "publisher": "Nintendo", "genre": "Shoot'em up", "basePopularity": 40, "uncertain": true },
{ "id": "dr-mario", "title": "Dr. Mario", "console": "GB", "releaseDate": "1990-12-01", "launchPriceFrancs": 199, "publisher": "Nintendo", "genre": "Puzzle", "basePopularity": 60, "uncertain": true },
{ "id": "gargoyles-quest", "title": "Gargoyle's Quest", "console": "GB", "releaseDate": "1990-12-01", "launchPriceFrancs": 249, "publisher": "Capcom", "genre": "Action", "basePopularity": 55, "uncertain": true },
{ "id": "double-dragon", "title": "Double Dragon", "console": "GB", "releaseDate": "1990-12-01", "launchPriceFrancs": 249, "publisher": "Tradewest", "genre": "Beat'em all", "basePopularity": 55, "uncertain": true },
{ "id": "turtles-fall-of-the-foot-clan", "title": "Teenage Mutant Hero Turtles: Fall of the Foot Clan", "console": "GB", "releaseDate": "1991-02-15", "launchPriceFrancs": 249, "publisher": "Konami", "genre": "Action", "basePopularity": 75, "uncertain": true },
{ "id": "nemesis", "title": "Nemesis", "console": "GB", "releaseDate": "1991-03-15", "launchPriceFrancs": 249, "publisher": "Konami", "genre": "Shoot'em up", "basePopularity": 50, "uncertain": true },
{ "id": "duck-tales", "title": "La Bande à Picsou", "console": "GB", "releaseDate": "1991-04-15", "launchPriceFrancs": 249, "publisher": "Capcom", "genre": "Plateforme", "basePopularity": 60, "uncertain": true },
{ "id": "qix", "title": "Qix", "console": "GB", "releaseDate": "1991-05-15", "launchPriceFrancs": 199, "publisher": "Nintendo", "genre": "Puzzle", "basePopularity": 35, "uncertain": true },
{ "id": "f1-race", "title": "F-1 Race", "console": "GB", "releaseDate": "1991-06-15", "launchPriceFrancs": 249, "publisher": "Nintendo", "genre": "Course", "basePopularity": 45, "uncertain": true },
{ "id": "bubble-bobble", "title": "Bubble Bobble", "console": "GB", "releaseDate": "1991-07-15", "launchPriceFrancs": 249, "publisher": "Taito", "genre": "Plateforme", "basePopularity": 50, "uncertain": true },
{ "id": "the-amazing-spider-man", "title": "The Amazing Spider-Man", "console": "GB", "releaseDate": "1991-09-15", "launchPriceFrancs": 249, "publisher": "LJN", "genre": "Action", "basePopularity": 50, "uncertain": true },
{ "id": "radar-mission", "title": "Radar Mission", "console": "GB", "releaseDate": "1991-10-15", "launchPriceFrancs": 199, "publisher": "Nintendo", "genre": "Stratégie", "basePopularity": 30, "uncertain": true }
]
+57
View File
@@ -0,0 +1,57 @@
import { compareDates, GameDate, parseISO } from './calendar';
import { francsToCents } from './money';
import rawGames from '../data/games.json';
export type ConsoleId = 'GB' | 'GBC' | 'GBA';
export interface Game {
id: string;
title: string;
console: ConsoleId;
releaseDate: string; // YYYY-MM-DD, sortie FR
launchPriceCents: number;
publisher: string;
genre: string;
basePopularity: number; // 0-100
cover?: string;
uncertain?: boolean;
}
export interface RawGame extends Omit<Game, 'launchPriceCents'> {
launchPriceFrancs: number;
}
export interface Catalog {
games: Game[];
byId: Map<string, Game>;
}
export function createCatalog(games: Game[]): Catalog {
const byId = new Map<string, Game>();
for (const g of games) {
if (byId.has(g.id)) throw new Error(`Duplicate game id: ${g.id}`);
byId.set(g.id, g);
}
return { games, byId };
}
export function loadGames(raw: RawGame[]): Game[] {
return raw.map(({ launchPriceFrancs, ...rest }) => ({
...rest,
launchPriceCents: francsToCents(launchPriceFrancs),
}));
}
export function loadDefaultCatalog(): Catalog {
return createCatalog(loadGames(rawGames as RawGame[]));
}
export function getGame(catalog: Catalog, id: string): Game {
const game = catalog.byId.get(id);
if (!game) throw new Error(`Unknown game: ${id}`);
return game;
}
export function availableGames(catalog: Catalog, date: GameDate): Game[] {
return catalog.games.filter((g) => compareDates(parseISO(g.releaseDate), date) <= 0);
}
+37
View File
@@ -0,0 +1,37 @@
import { describe, it, expect } from 'vitest';
import { availableGames, createCatalog, getGame, loadDefaultCatalog } from '../../src/sim/catalog';
import { START_DATE, parseISO } from '../../src/sim/calendar';
import { TEST_GAMES, testCatalog } from './helpers';
describe('catalog', () => {
it('indexes games by id', () => {
expect(getGame(testCatalog(), 'sml').title).toBe('Super Mario Land');
});
it('throws on unknown id', () => {
expect(() => getGame(testCatalog(), 'nope')).toThrow('Unknown game: nope');
});
it('rejects duplicate ids', () => {
expect(() => createCatalog([TEST_GAMES[0], TEST_GAMES[0]])).toThrow('Duplicate game id: tetris');
});
it('only lists released games', () => {
const ids = availableGames(testCatalog(), START_DATE).map((g) => g.id);
expect(ids).toEqual(['tetris', 'sml']);
const later = availableGames(testCatalog(), parseISO('1991-06-01')).map((g) => g.id);
expect(later).toContain('future');
});
it('loads the default GB catalog with francs converted to cents', () => {
const catalog = loadDefaultCatalog();
expect(catalog.games.length).toBeGreaterThanOrEqual(20);
expect(getGame(catalog, 'super-mario-land').launchPriceCents).toBe(3796);
for (const g of catalog.games) {
expect(g.releaseDate).toMatch(/^\d{4}-\d{2}-\d{2}$/);
expect(g.basePopularity).toBeGreaterThanOrEqual(0);
expect(g.basePopularity).toBeLessThanOrEqual(100);
}
expect(availableGames(catalog, START_DATE).map((g) => g.id)).toContain('tetris');
});
});
+11
View File
@@ -0,0 +1,11 @@
import { createCatalog, Game } from '../../src/sim/catalog';
export const TEST_GAMES: Game[] = [
{ id: 'tetris', title: 'Tetris', console: 'GB', releaseDate: '1990-09-28', launchPriceCents: 3034, publisher: 'Nintendo', genre: 'Puzzle', basePopularity: 90 },
{ id: 'sml', title: 'Super Mario Land', console: 'GB', releaseDate: '1990-09-28', launchPriceCents: 3796, publisher: 'Nintendo', genre: 'Plateforme', basePopularity: 95 },
{ id: 'future', title: 'Future Game', console: 'GB', releaseDate: '1991-06-01', launchPriceCents: 3796, publisher: 'Test', genre: 'Action', basePopularity: 50 },
];
export function testCatalog() {
return createCatalog(TEST_GAMES);
}