feat: New theme + wire price adjustement
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
// Export / import of a save as a JSON or TXT file (or pasted text).
|
||||
// Export / import of the save as a JSON or TXT file (or pasted text).
|
||||
// Works on every device: download via a Blob link, import via file input or paste.
|
||||
// The save is theme-independent: any export can be imported under any theme.
|
||||
|
||||
import { sanitizeState, serializeState } from '../engine/state.js';
|
||||
|
||||
@@ -7,7 +8,7 @@ const GAME_ID = 'idle-clicker';
|
||||
const FORMAT = 1;
|
||||
|
||||
export class ImportError extends Error {
|
||||
/** @param {'empty'|'invalid'|'unknown_theme'|'wrong_game'} code */
|
||||
/** @param {'empty'|'invalid'|'wrong_game'} code */
|
||||
constructor(code) {
|
||||
super(code);
|
||||
this.code = code;
|
||||
@@ -15,12 +16,11 @@ export class ImportError extends Error {
|
||||
}
|
||||
|
||||
/** The text a player downloads, copies or pastes back. */
|
||||
export function buildExport(themeId, state, now = Date.now()) {
|
||||
export function buildExport(state, now = Date.now()) {
|
||||
return JSON.stringify(
|
||||
{
|
||||
game: GAME_ID,
|
||||
format: FORMAT,
|
||||
theme: themeId,
|
||||
exportedAt: new Date(now).toISOString(),
|
||||
state: JSON.parse(serializeState(state)),
|
||||
},
|
||||
@@ -29,8 +29,8 @@ export function buildExport(themeId, state, now = Date.now()) {
|
||||
);
|
||||
}
|
||||
|
||||
/** Validate imported text. Throws ImportError; never trusts the content. */
|
||||
export function parseImport(text, ctx, knownThemeIds) {
|
||||
/** Validate imported text and return a clean state. Throws ImportError; never trusts the content. */
|
||||
export function parseImport(text, ctx) {
|
||||
const trimmed = String(text ?? '').trim().replace(/^/, '');
|
||||
if (!trimmed) throw new ImportError('empty');
|
||||
|
||||
@@ -41,11 +41,10 @@ export function parseImport(text, ctx, knownThemeIds) {
|
||||
throw new ImportError('invalid');
|
||||
}
|
||||
if (data?.game !== GAME_ID || data.format !== FORMAT) throw new ImportError('wrong_game');
|
||||
if (!knownThemeIds.includes(data.theme)) throw new ImportError('unknown_theme');
|
||||
|
||||
const state = sanitizeState(data.state, ctx);
|
||||
if (!state) throw new ImportError('invalid');
|
||||
return { themeId: data.theme, state };
|
||||
return state;
|
||||
}
|
||||
|
||||
export function downloadText(filename, text, mime = 'application/json') {
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
// Persistence in browser cookies.
|
||||
//
|
||||
// A cookie holds ~4 KB, so a save is base64url-encoded and split in chunks:
|
||||
// ic_save_<theme>_n -> number of chunks
|
||||
// ic_save_<theme>_0.. -> the chunks
|
||||
// Each theme has its own save slot. Preferences (theme, language) are stored
|
||||
// in a single small cookie.
|
||||
// ic_game_n -> number of chunks
|
||||
// ic_game_0.. -> the chunks
|
||||
// There is ONE game, shared by every theme (a theme is only a skin).
|
||||
// Preferences (theme, language) are stored in a single small cookie.
|
||||
|
||||
import { readCookie, removeCookie, writeCookie } from './cookies.js';
|
||||
|
||||
const CHUNK_SIZE = 3000;
|
||||
const MAX_CHUNKS = 6;
|
||||
const PREFS_KEY = 'ic_prefs';
|
||||
const saveKey = (themeId) => `ic_save_${themeId}`;
|
||||
const GAME_KEY = 'ic_game';
|
||||
const legacyKey = (themeId) => `ic_save_${themeId}`; // v1: one save per theme
|
||||
|
||||
// ------------------------------------------------------------------ encoding
|
||||
|
||||
@@ -79,16 +80,30 @@ export function cookiesAvailable() {
|
||||
}
|
||||
|
||||
/** Throws if the save does not fit in cookies. */
|
||||
export function saveGameText(themeId, text) {
|
||||
writeChunked(saveKey(themeId), text);
|
||||
export function saveGameText(text) {
|
||||
writeChunked(GAME_KEY, text);
|
||||
}
|
||||
|
||||
export function loadGameText(themeId) {
|
||||
return readChunked(saveKey(themeId));
|
||||
export function loadGameText() {
|
||||
return readChunked(GAME_KEY);
|
||||
}
|
||||
|
||||
export function deleteGame(themeId) {
|
||||
removeChunked(saveKey(themeId));
|
||||
export function deleteGame() {
|
||||
removeChunked(GAME_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves from the first version (one per theme). Returns their texts and
|
||||
* removes those cookies, so the caller can merge them into the shared game.
|
||||
*/
|
||||
export function takeLegacySaves(themeIds) {
|
||||
const texts = [];
|
||||
for (const id of themeIds) {
|
||||
const text = readChunked(legacyKey(id));
|
||||
if (text) texts.push(text);
|
||||
removeChunked(legacyKey(id));
|
||||
}
|
||||
return texts;
|
||||
}
|
||||
|
||||
export function savePrefs(prefs) {
|
||||
|
||||
Reference in New Issue
Block a user