@johnmorrisdotca/houseki 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -2
- package/README.md +506 -18
- package/dist/colour-chains/challenge.js +18 -4
- package/dist/colour-chains/content-data.js +19582 -2
- package/dist/colour-chains/content-types.d.ts +21 -0
- package/dist/colour-chains/content.d.ts +9 -1
- package/dist/colour-chains/content.js +13 -0
- package/dist/colour-chains/engine.js +8 -4
- package/dist/colour-chains/persistence.js +1 -1
- package/dist/colour-chains/types.d.ts +6 -2
- package/dist/colour-chains.d.ts +2 -2
- package/dist/colour-chains.js +1 -1
- package/dist/magnetic-blocks/content-data.d.ts +3 -0
- package/dist/magnetic-blocks/content-data.js +11781 -0
- package/dist/magnetic-blocks/content-types.d.ts +66 -0
- package/dist/magnetic-blocks/content-types.js +1 -0
- package/dist/magnetic-blocks/content.d.ts +16 -0
- package/dist/magnetic-blocks/content.js +38 -0
- package/dist/magnetic-blocks/engine.js +4 -2
- package/dist/magnetic-blocks.d.ts +2 -0
- package/dist/magnetic-blocks.js +1 -0
- package/package.json +8 -3
|
@@ -10,11 +10,17 @@ export interface ChainDifficultyMetrics {
|
|
|
10
10
|
readonly forcedPlacementShare: number;
|
|
11
11
|
readonly seededPlayoutSamples: number;
|
|
12
12
|
readonly seededPlayoutQueueDepth: number;
|
|
13
|
+
/** Number of pair placements randomized in each seeded playout. */
|
|
14
|
+
readonly seededPlayoutVariablePrefixLength?: number;
|
|
15
|
+
/** Number of later witnessed placements held fixed in each seeded playout. */
|
|
16
|
+
readonly seededPlayoutFixedSuffixLength?: number;
|
|
13
17
|
readonly seededPlayoutSuccesses: number;
|
|
14
18
|
readonly seededPlayoutSuccessRate: number;
|
|
15
19
|
readonly setupPairsBeforePayoff: number;
|
|
16
20
|
readonly requiredSetupPairs: number;
|
|
17
21
|
readonly verifiedSetupDependencies: number;
|
|
22
|
+
/** Number of pre-payoff steps checked by exhaustive single-step deviations. */
|
|
23
|
+
readonly setupDependencyProbeSteps?: number;
|
|
18
24
|
readonly requiredChainDepth: number;
|
|
19
25
|
readonly requiredRotations: number;
|
|
20
26
|
readonly requiredWallKicks: number;
|
|
@@ -26,6 +32,10 @@ export interface ChainCampaignLevel {
|
|
|
26
32
|
readonly id: string;
|
|
27
33
|
readonly number: number;
|
|
28
34
|
readonly title: LocalizedText;
|
|
35
|
+
readonly objective?: LocalizedText;
|
|
36
|
+
readonly nature?: true;
|
|
37
|
+
readonly weather?: 'frequent';
|
|
38
|
+
readonly magneticQueue?: readonly (readonly [boolean, boolean])[];
|
|
29
39
|
readonly canonicalKeyHash: string;
|
|
30
40
|
readonly width: number;
|
|
31
41
|
readonly height: number;
|
|
@@ -43,6 +53,12 @@ export interface ChainCampaignLevel {
|
|
|
43
53
|
readonly proofStatus: 'engine-witness-verified';
|
|
44
54
|
readonly reviewStatus: 'human-review-pending' | 'human-reviewed';
|
|
45
55
|
}
|
|
56
|
+
export interface NatureChainCampaignLevel extends ChainCampaignLevel {
|
|
57
|
+
readonly objective: LocalizedText;
|
|
58
|
+
readonly nature: true;
|
|
59
|
+
readonly weather?: 'frequent';
|
|
60
|
+
readonly magneticQueue?: readonly (readonly [boolean, boolean])[];
|
|
61
|
+
}
|
|
46
62
|
export interface ChainCampaignManifest {
|
|
47
63
|
readonly count: number;
|
|
48
64
|
readonly candidatePoolCount: number;
|
|
@@ -56,6 +72,9 @@ export interface ChainCampaignManifest {
|
|
|
56
72
|
readonly checksum: string;
|
|
57
73
|
readonly levels: readonly ChainCampaignLevel[];
|
|
58
74
|
}
|
|
75
|
+
export interface NatureChainCampaignManifest extends Omit<ChainCampaignManifest, 'levels'> {
|
|
76
|
+
readonly levels: readonly NatureChainCampaignLevel[];
|
|
77
|
+
}
|
|
59
78
|
export interface ChainTutorialStep {
|
|
60
79
|
readonly instruction: LocalizedText;
|
|
61
80
|
readonly action: string;
|
|
@@ -76,5 +95,7 @@ export interface ChainTutorialDefinition {
|
|
|
76
95
|
export interface ChainContentData {
|
|
77
96
|
readonly campaign: ChainCampaignManifest;
|
|
78
97
|
readonly tutorials: readonly ChainTutorialDefinition[];
|
|
98
|
+
readonly shizen?: NatureChainCampaignManifest;
|
|
99
|
+
readonly arashi?: NatureChainCampaignManifest;
|
|
79
100
|
}
|
|
80
101
|
export type { Colour, Orientation, WitnessStep, ChallengeGoal };
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import type { ChainCampaignLevel, ChainCampaignManifest, ChainTutorialDefinition } from './content-types.js';
|
|
1
|
+
import type { ChainCampaignLevel, ChainCampaignManifest, ChainTutorialDefinition, NatureChainCampaignLevel, NatureChainCampaignManifest } from './content-types.js';
|
|
2
2
|
import type { GameState } from './types.js';
|
|
3
3
|
export declare const campaignManifest: ChainCampaignManifest;
|
|
4
4
|
export declare const levelManifest: readonly ChainCampaignLevel[];
|
|
5
5
|
export declare const tutorialManifest: readonly ChainTutorialDefinition[];
|
|
6
|
+
export declare const shizenCampaignManifest: NatureChainCampaignManifest;
|
|
7
|
+
export declare const shizenLevelManifest: readonly NatureChainCampaignLevel[];
|
|
8
|
+
export declare const arashiCampaignManifest: NatureChainCampaignManifest;
|
|
9
|
+
export declare const arashiLevelManifest: readonly NatureChainCampaignLevel[];
|
|
6
10
|
/** Find a campaign challenge by stable content ID or its current one-based number. */
|
|
7
11
|
export declare function getLevel(id: string | number): ChainCampaignLevel | undefined;
|
|
8
12
|
/** Create a fresh engine-validated challenge from a stable content ID or display number. */
|
|
9
13
|
export declare function createLevel(id: string | number): GameState;
|
|
14
|
+
export declare function getShizenLevel(id: string | number): NatureChainCampaignLevel | undefined;
|
|
15
|
+
export declare function createShizenLevel(id: string | number): GameState;
|
|
16
|
+
export declare function getArashiLevel(id: string | number): NatureChainCampaignLevel | undefined;
|
|
17
|
+
export declare function createArashiLevel(id: string | number): GameState;
|
|
10
18
|
/** Find a persistent interactive lesson by its stable ID. */
|
|
11
19
|
export declare function getTutorial(id: string): ChainTutorialDefinition | undefined;
|
|
@@ -11,6 +11,10 @@ function deepFreeze(value) {
|
|
|
11
11
|
export const campaignManifest = deepFreeze(contentData.campaign);
|
|
12
12
|
export const levelManifest = campaignManifest.levels;
|
|
13
13
|
export const tutorialManifest = deepFreeze(contentData.tutorials);
|
|
14
|
+
export const shizenCampaignManifest = deepFreeze((contentData.shizen ?? { ...contentData.campaign, count: 0, levels: [], checksum: '' }));
|
|
15
|
+
export const shizenLevelManifest = shizenCampaignManifest.levels;
|
|
16
|
+
export const arashiCampaignManifest = deepFreeze((contentData.arashi ?? { ...contentData.campaign, count: 0, levels: [], checksum: '' }));
|
|
17
|
+
export const arashiLevelManifest = arashiCampaignManifest.levels;
|
|
14
18
|
/** Find a campaign challenge by stable content ID or its current one-based number. */
|
|
15
19
|
export function getLevel(id) {
|
|
16
20
|
if (typeof id === 'number')
|
|
@@ -24,5 +28,14 @@ export function createLevel(id) {
|
|
|
24
28
|
throw new RangeError(`Unknown Colour Chains level: ${String(id)}`);
|
|
25
29
|
return createChallenge({ id: level.id, seed: level.seed, width: level.width, height: level.height, colourCount: level.colourCount, board: level.board, queue: level.queue, goal: level.goal, witness: level.witness });
|
|
26
30
|
}
|
|
31
|
+
function createNatureLevel(level) {
|
|
32
|
+
return createChallenge({ id: level.id, seed: level.seed, width: level.width, height: level.height, colourCount: level.colourCount, board: level.board, queue: level.queue, goal: level.goal, witness: level.witness, nature: true, ...(level.weather ? { weather: level.weather } : {}), ...(level.magneticQueue ? { magneticQueue: level.magneticQueue } : {}) });
|
|
33
|
+
}
|
|
34
|
+
export function getShizenLevel(id) { return typeof id === 'number' ? shizenLevelManifest.find(level => level.number === id) : shizenLevelManifest.find(level => level.id === id); }
|
|
35
|
+
export function createShizenLevel(id) { const level = getShizenLevel(id); if (!level)
|
|
36
|
+
throw new RangeError(`Unknown Shizen Colour Chains level: ${String(id)}`); return createNatureLevel(level); }
|
|
37
|
+
export function getArashiLevel(id) { return typeof id === 'number' ? arashiLevelManifest.find(level => level.number === id) : arashiLevelManifest.find(level => level.id === id); }
|
|
38
|
+
export function createArashiLevel(id) { const level = getArashiLevel(id); if (!level)
|
|
39
|
+
throw new RangeError(`Unknown Arashi Colour Chains level: ${String(id)}`); return createNatureLevel(level); }
|
|
27
40
|
/** Find a persistent interactive lesson by its stable ID. */
|
|
28
41
|
export function getTutorial(id) { return tutorialManifest.find(tutorial => tutorial.id === id); }
|
|
@@ -136,6 +136,8 @@ function terminalAfterResolution(state) {
|
|
|
136
136
|
if (movement.length)
|
|
137
137
|
return [{ ...current, phase: 'gravity', active: null, gravityBoard: settled, resolutionTick: 0 }, [...weatherEvents, { type: 'cells-fell', cells: movement, before: current.board, after: settled }]];
|
|
138
138
|
current = { ...current, board: settled };
|
|
139
|
+
if (current.settings.mode === 'challenge' && satisfied(current))
|
|
140
|
+
return [{ ...current, phase: 'won', active: null, reason: 'goal-complete' }, [...weatherEvents, { type: 'run-ended', reason: 'goal-complete', score: current.score }]];
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
143
|
if (current.settings.mode === 'challenge') {
|
|
@@ -144,8 +146,9 @@ function terminalAfterResolution(state) {
|
|
|
144
146
|
return [{ ...current, phase: 'lost', active: null, reason: 'challenge-queue-exhausted' }, [...weatherEvents, { type: 'run-ended', reason: 'challenge-queue-exhausted' }]];
|
|
145
147
|
const queue = current.settings.queue;
|
|
146
148
|
const colours = queue[nextIndex];
|
|
147
|
-
const pair = makePair(current, colours);
|
|
148
|
-
const
|
|
149
|
+
const pair = makePair(current, colours, current.settings.magneticQueue?.[nextIndex]);
|
|
150
|
+
const nextMagnetic = current.settings.nature ? queue.slice(nextIndex + 1, nextIndex + 4).map((_, index) => current.settings.magneticQueue?.[nextIndex + index + 1] ?? [false, false]) : undefined;
|
|
151
|
+
const next = { ...current, active: pair, next: queue.slice(nextIndex + 1, nextIndex + 4), ...(nextMagnetic ? { nextMagnetic: Object.freeze(nextMagnetic) } : {}), nextId: current.nextId + 2 };
|
|
149
152
|
if (!pairFits(next, pair))
|
|
150
153
|
return [{ ...next, active: null, phase: 'lost', reason: 'spawn-collision' }, [...weatherEvents, { type: 'run-ended', reason: 'spawn-collision' }]];
|
|
151
154
|
return [next, [...weatherEvents, { type: 'pair-spawned', ids: pair.gems.map(gem => gem.id), colours }]];
|
|
@@ -469,8 +472,9 @@ export function initialChallengeState(settings, visibleBoard, queue) {
|
|
|
469
472
|
board[i + settings.width * HIDDEN] = visibleBoard[i];
|
|
470
473
|
const maxId = Math.max(0, ...visibleBoard.flatMap(gem => gem ? [gem.id] : []));
|
|
471
474
|
const base = { game: 'colour-chains', rules: RULES, settings, board: Object.freeze(board), phase: 'falling', active: null, next: Object.freeze(queue.slice(1, 4)), bag: [], randomState: seedState(`colour-chains|${RULES}|${settings.seed}`), nextId: maxId + 1, score: 0, maxChain: 0, allClears: 0, completedPairs: 0, resolutionTick: 0, waves: [], clearCells: [], gravityBoard: null, resolutionHadClear: false, elapsedTicks: 0, assisted: false, witnessIndex: 0, recording: [] };
|
|
472
|
-
const active = makePair(base, queue[0]);
|
|
473
|
-
|
|
475
|
+
const active = makePair(base, queue[0], settings.magneticQueue?.[0]);
|
|
476
|
+
const nextMagnetic = settings.nature ? queue.slice(1, 4).map((_, index) => settings.magneticQueue?.[index + 1] ?? [false, false]) : undefined;
|
|
477
|
+
return { ...base, active, ...(nextMagnetic ? { nextMagnetic: Object.freeze(nextMagnetic) } : {}), nextId: maxId + 3 };
|
|
474
478
|
}
|
|
475
479
|
/** Creates a deterministic seeded game with one active pair and three previews. */
|
|
476
480
|
export function createGame(options = {}) {
|
|
@@ -17,7 +17,7 @@ function stable(value, depth = 0) {
|
|
|
17
17
|
function challengeOptions(settings) {
|
|
18
18
|
if (settings.mode !== 'challenge' || !settings.challengeId || !settings.goal || !settings.initialBoard || !settings.queue || !settings.witness)
|
|
19
19
|
throw new TypeError('Challenge settings are incomplete');
|
|
20
|
-
return { id: settings.challengeId, width: settings.width, height: settings.height, colourCount: settings.colourCount, seed: settings.seed, board: settings.initialBoard, queue: settings.queue, goal: settings.goal, witness: settings.witness };
|
|
20
|
+
return { id: settings.challengeId, width: settings.width, height: settings.height, colourCount: settings.colourCount, seed: settings.seed, board: settings.initialBoard, queue: settings.queue, ...(settings.magneticQueue ? { magneticQueue: settings.magneticQueue } : {}), ...(settings.nature ? { nature: true } : {}), ...(settings.weather ? { weather: settings.weather } : {}), goal: settings.goal, witness: settings.witness };
|
|
21
21
|
}
|
|
22
22
|
function createFromSettings(settings) {
|
|
23
23
|
if (!settings || typeof settings !== 'object' || Array.isArray(settings))
|
|
@@ -6,8 +6,8 @@ export interface Gem {
|
|
|
6
6
|
readonly colour: Colour;
|
|
7
7
|
readonly magnetic?: true;
|
|
8
8
|
}
|
|
9
|
-
/** Authored
|
|
10
|
-
export type ChallengeGem =
|
|
9
|
+
/** Authored challenge layouts can include explicit Shizen-marked stones. */
|
|
10
|
+
export type ChallengeGem = Gem;
|
|
11
11
|
/** The pivot-to-satellite direction, in clockwise order. */
|
|
12
12
|
export type Orientation = 'up' | 'right' | 'down' | 'left';
|
|
13
13
|
/** A visible or hidden board coordinate. */
|
|
@@ -57,6 +57,7 @@ export interface Settings {
|
|
|
57
57
|
readonly goal?: ChallengeGoal;
|
|
58
58
|
readonly initialBoard?: readonly (ChallengeGem | null)[];
|
|
59
59
|
readonly queue?: readonly (readonly [Colour, Colour])[];
|
|
60
|
+
readonly magneticQueue?: readonly (readonly [boolean, boolean])[];
|
|
60
61
|
readonly witness?: readonly WitnessStep[];
|
|
61
62
|
}
|
|
62
63
|
/** Challenge objective; target IDs refer to the visible starting board. */
|
|
@@ -81,6 +82,9 @@ export interface ChallengeOptions {
|
|
|
81
82
|
readonly seed?: string;
|
|
82
83
|
readonly board: readonly (ChallengeGem | null)[];
|
|
83
84
|
readonly queue: readonly (readonly [Colour, Colour])[];
|
|
85
|
+
readonly magneticQueue?: readonly (readonly [boolean, boolean])[];
|
|
86
|
+
readonly nature?: boolean;
|
|
87
|
+
readonly weather?: WeatherSchedule;
|
|
84
88
|
readonly goal: ChallengeGoal;
|
|
85
89
|
readonly witness?: readonly WitnessStep[];
|
|
86
90
|
}
|
package/dist/colour-chains.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ export { createGame, applyAction, advanceTicks, legalActions, statusOf, landingC
|
|
|
4
4
|
export { createChallenge } from './colour-chains/challenge.js';
|
|
5
5
|
export { restartGame, encodeGame, decodeGame } from './colour-chains/persistence.js';
|
|
6
6
|
export { createInputScheduler, setHeldInput, queueInputEdge, releaseAllInput, actionsForTick, applyScheduledActions, processInputTick } from './colour-chains/input.js';
|
|
7
|
-
export { campaignManifest, levelManifest, tutorialManifest, getLevel, createLevel, getTutorial } from './colour-chains/content.js';
|
|
8
|
-
export type { LocalizedText, ChainDifficultyMetrics, ChainCampaignLevel, ChainCampaignManifest, ChainTutorialStep, ChainTutorialDefinition, ChainContentData } from './colour-chains/content-types.js';
|
|
7
|
+
export { campaignManifest, levelManifest, tutorialManifest, getLevel, createLevel, getTutorial, shizenCampaignManifest, shizenLevelManifest, getShizenLevel, createShizenLevel, arashiCampaignManifest, arashiLevelManifest, getArashiLevel, createArashiLevel } from './colour-chains/content.js';
|
|
8
|
+
export type { LocalizedText, ChainDifficultyMetrics, ChainCampaignLevel, ChainCampaignManifest, NatureChainCampaignLevel, NatureChainCampaignManifest, ChainTutorialStep, ChainTutorialDefinition, ChainContentData } from './colour-chains/content-types.js';
|
package/dist/colour-chains.js
CHANGED
|
@@ -4,4 +4,4 @@ export { createGame, applyAction, advanceTicks, legalActions, statusOf, landingC
|
|
|
4
4
|
export { createChallenge } from './colour-chains/challenge.js';
|
|
5
5
|
export { restartGame, encodeGame, decodeGame } from './colour-chains/persistence.js';
|
|
6
6
|
export { createInputScheduler, setHeldInput, queueInputEdge, releaseAllInput, actionsForTick, applyScheduledActions, processInputTick } from './colour-chains/input.js';
|
|
7
|
-
export { campaignManifest, levelManifest, tutorialManifest, getLevel, createLevel, getTutorial } from './colour-chains/content.js';
|
|
7
|
+
export { campaignManifest, levelManifest, tutorialManifest, getLevel, createLevel, getTutorial, shizenCampaignManifest, shizenLevelManifest, getShizenLevel, createShizenLevel, arashiCampaignManifest, arashiLevelManifest, getArashiLevel, createArashiLevel } from './colour-chains/content.js';
|