commit
53f3da48bc
14 changed files with 5024 additions and 0 deletions
@ -0,0 +1,50 @@ |
|||||||
|
import { Phase, PlayerActions } from "./enums"; |
||||||
|
import { assert } from "./utils"; |
||||||
|
|
||||||
|
const valid_actions = Object.values(PlayerActions); |
||||||
|
|
||||||
|
class InputHandler { |
||||||
|
/** |
||||||
|
* Handles input relating to the player's game actions. |
||||||
|
* @class |
||||||
|
* @property {import('./game.js').GameState} game_state - The game state this handler manipulates |
||||||
|
* @property {import('./Time.js').Time} time - The time manager for the current game loop |
||||||
|
*
|
||||||
|
* The InputHandler manages player events (clicks, keystrokes, etc.). |
||||||
|
* It validates actions, updates game state, and interacts with the time system as needed. |
||||||
|
*/ |
||||||
|
game_state; |
||||||
|
time; |
||||||
|
constructor(game_state, time) { |
||||||
|
this.game_state = game_state; |
||||||
|
this.time = time; |
||||||
|
} |
||||||
|
|
||||||
|
setup() { |
||||||
|
document.addEventListener("click", this.handleClickEvent); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Click handler |
||||||
|
* @param {Event} e
|
||||||
|
* @returns
|
||||||
|
*/ |
||||||
|
handleClickEvent(e) { |
||||||
|
if (!e.target) return; |
||||||
|
const el = /** @type {Element} */ (e.target) |
||||||
|
|
||||||
|
const target = /** @type {HTMLElement | null} */ (el.closest("[data-action]")); |
||||||
|
assert(!!target, "there must be a valid target"); |
||||||
|
|
||||||
|
const action = target?.dataset?.action ?? ""; |
||||||
|
switch (action) { |
||||||
|
case PlayerActions.OPEN: |
||||||
|
this.game_state.time.phase = Phase.OPEN; |
||||||
|
break; |
||||||
|
case PlayerActions.CLOSE: |
||||||
|
this.game_state.time.phase = Phase.CLOSE; |
||||||
|
break; |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,25 @@ |
|||||||
|
import { Phase } from "./enums"; |
||||||
|
import { assert } from "./utils"; |
||||||
|
|
||||||
|
export class PlanDayCommand { |
||||||
|
/** |
||||||
|
* Executes the plan day command |
||||||
|
* @param {import(".").GameState} game_state |
||||||
|
* @returns {import(".").GameState} |
||||||
|
*/ |
||||||
|
execute(game_state) { |
||||||
|
assert( |
||||||
|
game_state.time.phase === Phase.CLOSE, |
||||||
|
"store must be closed before you can plan", |
||||||
|
); |
||||||
|
|
||||||
|
return { |
||||||
|
...game_state, |
||||||
|
time: { |
||||||
|
...game_state.time, |
||||||
|
day: game_state.time.day + 1, |
||||||
|
phase: Phase.PLANNING, |
||||||
|
}, |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,50 @@ |
|||||||
|
import { Phase } from "./enums.js"; |
||||||
|
import { PlanDayCommand } from "./StartDayCommand.js"; |
||||||
|
import { assert } from "./utils.js"; |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {import('./game.js').GameState} GameState |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* Manages a single day in the bakery game |
||||||
|
*/ |
||||||
|
export class Time { |
||||||
|
/** @type {GameState} */ |
||||||
|
game_state; |
||||||
|
|
||||||
|
/** @type {import("./game.js").Command[]} */ |
||||||
|
events; |
||||||
|
|
||||||
|
/** |
||||||
|
* @param {GameState} game_state - The current game state |
||||||
|
*/ |
||||||
|
constructor(game_state) { |
||||||
|
this.game_state = game_state; |
||||||
|
this.events = []; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Queues a command for execution |
||||||
|
* @param {import("./game.js").Command} command |
||||||
|
*/ |
||||||
|
queue(command) { |
||||||
|
this.events.push(command); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Updates the day timer |
||||||
|
* @param {number} dt - Delta time in milliseconds |
||||||
|
*/ |
||||||
|
update(dt) { |
||||||
|
this.game_state.time.elapsed_ms += dt; |
||||||
|
for (const command of this.events) { |
||||||
|
this.game_state = command.execute(this.game_state); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** Renders the current day state */ |
||||||
|
render() {} |
||||||
|
} |
||||||
|
|
||||||
|
export { Day }; |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
export const Phase = Object.freeze({ |
||||||
|
PLANNING: "planning", |
||||||
|
OPEN: "open", |
||||||
|
CLOSE: "close", |
||||||
|
}); |
||||||
|
|
||||||
|
export const MenuItemState = Object.freeze({ |
||||||
|
ENABLED: "enabled", |
||||||
|
DISABLED: "disabled", |
||||||
|
}); |
||||||
|
|
||||||
|
export const PlayerActions = Object.freeze({ |
||||||
|
PLAN: "day", |
||||||
|
OPEN: "day", |
||||||
|
CLOSE: "day", |
||||||
|
}); |
||||||
@ -0,0 +1,249 @@ |
|||||||
|
import { MenuItemState } from "./enums.js"; |
||||||
|
import { assert } from "./utils.js"; |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {Object} SuppliesBase |
||||||
|
* @property {number} flour |
||||||
|
* @property {number} eggs |
||||||
|
* @property {number} oil |
||||||
|
* @property {number} butter |
||||||
|
* @property {number} milk |
||||||
|
* @property {number} yeast |
||||||
|
* @property {number} chocolate |
||||||
|
* @property {number} fruit |
||||||
|
* @property {number} sugar |
||||||
|
*/ |
||||||
|
|
||||||
|
/** @typedef {SuppliesBase & {[key: string]: number}} Supplies */ |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {'enabled' | 'disabled'} MenuItemStateValue |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {Object} MenuItem |
||||||
|
* @property {Partial<Supplies>} ingredients |
||||||
|
* @property {number} oven_slots |
||||||
|
* @property {number} base_demand |
||||||
|
* @property {MenuItemStateValue} state |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {'bread' | 'sweet_rolls' | 'vanilla_cake' | 'cookies' | 'fruit_muffins' | 'croissants' | 'chocolate_cookies' | 'chocolate_cake' | 'fruit_tart'} MenuItemName |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {Object} GameState |
||||||
|
* @property {number} player_money |
||||||
|
* @property {{ day: number, phase: string, elapsed_ms: number, day_length_ms: number, tick_ms: number }} time |
||||||
|
* @property {Supplies} supplies |
||||||
|
* @property {Record<MenuItemName, MenuItem>} menu |
||||||
|
* @property {Supplies} store |
||||||
|
* @property {{ capacity: number, used: number, queue: string[] }} oven |
||||||
|
* @property {Record<MenuItemName, number>} prices |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {Object} Command |
||||||
|
* @property {(game_state: GameState) => GameState} execute |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new game state with default values |
||||||
|
* @returns {GameState} |
||||||
|
*/ |
||||||
|
function init_game_state() { |
||||||
|
return { |
||||||
|
player_money: 0, |
||||||
|
time: { |
||||||
|
day: 1, |
||||||
|
phase: "planning", |
||||||
|
elapsed_ms: 0, |
||||||
|
day_length_ms: 30000, |
||||||
|
tick_ms: 250, |
||||||
|
}, |
||||||
|
supplies: { |
||||||
|
flour: 0, |
||||||
|
eggs: 0, |
||||||
|
oil: 0, |
||||||
|
butter: 0, |
||||||
|
milk: 0, |
||||||
|
yeast: 0, |
||||||
|
chocolate: 0, |
||||||
|
fruit: 0, |
||||||
|
sugar: 0, |
||||||
|
}, |
||||||
|
menu: { |
||||||
|
bread: { |
||||||
|
ingredients: { |
||||||
|
flour: 2, |
||||||
|
yeast: 1, |
||||||
|
milk: 1, |
||||||
|
}, |
||||||
|
oven_slots: 1, |
||||||
|
base_demand: 40, |
||||||
|
state: MenuItemState.ENABLED, |
||||||
|
}, |
||||||
|
sweet_rolls: { |
||||||
|
ingredients: { |
||||||
|
flour: 2, |
||||||
|
sugar: 1, |
||||||
|
butter: 1, |
||||||
|
milk: 1, |
||||||
|
}, |
||||||
|
oven_slots: 1, |
||||||
|
base_demand: 35, |
||||||
|
state: MenuItemState.ENABLED, |
||||||
|
}, |
||||||
|
vanilla_cake: { |
||||||
|
ingredients: { |
||||||
|
flour: 2, |
||||||
|
eggs: 2, |
||||||
|
sugar: 2, |
||||||
|
milk: 1, |
||||||
|
oil: 1, |
||||||
|
}, |
||||||
|
oven_slots: 2, |
||||||
|
base_demand: 25, |
||||||
|
state: MenuItemState.ENABLED, |
||||||
|
}, |
||||||
|
cookies: { |
||||||
|
ingredients: { |
||||||
|
flour: 1, |
||||||
|
eggs: 1, |
||||||
|
sugar: 1, |
||||||
|
butter: 1, |
||||||
|
}, |
||||||
|
oven_slots: 2, |
||||||
|
base_demand: 40, |
||||||
|
state: MenuItemState.ENABLED, |
||||||
|
}, |
||||||
|
fruit_muffins: { |
||||||
|
ingredients: { |
||||||
|
flour: 2, |
||||||
|
eggs: 1, |
||||||
|
sugar: 1, |
||||||
|
fruit: 1, |
||||||
|
}, |
||||||
|
oven_slots: 2, |
||||||
|
base_demand: 30, |
||||||
|
state: MenuItemState.ENABLED, |
||||||
|
}, |
||||||
|
croissants: { |
||||||
|
ingredients: { |
||||||
|
flour: 2, |
||||||
|
butter: 2, |
||||||
|
milk: 1, |
||||||
|
yeast: 1, |
||||||
|
}, |
||||||
|
oven_slots: 3, |
||||||
|
base_demand: 20, |
||||||
|
state: MenuItemState.ENABLED, |
||||||
|
}, |
||||||
|
chocolate_cookies: { |
||||||
|
ingredients: { |
||||||
|
flour: 1, |
||||||
|
eggs: 1, |
||||||
|
sugar: 1, |
||||||
|
butter: 1, |
||||||
|
chocolate: 1, |
||||||
|
}, |
||||||
|
oven_slots: 2, |
||||||
|
base_demand: 35, |
||||||
|
state: MenuItemState.ENABLED, |
||||||
|
}, |
||||||
|
chocolate_cake: { |
||||||
|
ingredients: { |
||||||
|
flour: 2, |
||||||
|
eggs: 2, |
||||||
|
sugar: 2, |
||||||
|
butter: 1, |
||||||
|
chocolate: 2, |
||||||
|
milk: 1, |
||||||
|
}, |
||||||
|
oven_slots: 3, |
||||||
|
base_demand: 12, |
||||||
|
state: MenuItemState.ENABLED, |
||||||
|
}, |
||||||
|
fruit_tart: { |
||||||
|
ingredients: { |
||||||
|
flour: 2, |
||||||
|
butter: 2, |
||||||
|
sugar: 1, |
||||||
|
eggs: 1, |
||||||
|
fruit: 2, |
||||||
|
}, |
||||||
|
oven_slots: 4, |
||||||
|
base_demand: 15, |
||||||
|
state: MenuItemState.ENABLED, |
||||||
|
}, |
||||||
|
}, |
||||||
|
store: { |
||||||
|
flour: 0.6, |
||||||
|
sugar: 0.4, |
||||||
|
yeast: 0.3, |
||||||
|
oil: 0.7, |
||||||
|
milk: 0.8, |
||||||
|
eggs: 0.9, |
||||||
|
butter: 1.2, |
||||||
|
fruit: 1.3, |
||||||
|
chocolate: 1.5, |
||||||
|
}, |
||||||
|
oven: { |
||||||
|
capacity: 4, |
||||||
|
used: 0, |
||||||
|
queue: [], |
||||||
|
}, |
||||||
|
prices: { |
||||||
|
bread: 4.0, |
||||||
|
sweet_rolls: 5.0, |
||||||
|
vanilla_cake: 9.0, |
||||||
|
cookies: 6.0, |
||||||
|
fruit_muffins: 6.5, |
||||||
|
croissants: 7.5, |
||||||
|
chocolate_cookies: 7.0, |
||||||
|
chocolate_cake: 14.0, |
||||||
|
fruit_tart: 12.0, |
||||||
|
}, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Bakes an item if supplies and oven capacity allow |
||||||
|
* @param {GameState} game_state - The current game state |
||||||
|
* @param {MenuItemName} item_name - The name of the item to bake |
||||||
|
* @returns {GameState} The updated game state |
||||||
|
*/ |
||||||
|
function bake_items(game_state, item_name) { |
||||||
|
const menu_item = game_state.menu[item_name]; |
||||||
|
const oven_slots = menu_item.oven_slots; |
||||||
|
const canFitInOven = |
||||||
|
game_state.oven.used + oven_slots <= game_state.oven.capacity; |
||||||
|
|
||||||
|
assert(canFitInOven, "oven must have capacity before baking"); |
||||||
|
assert(!!game_state, "game_state must be defined"); |
||||||
|
assert( |
||||||
|
game_state.menu[item_name].state === MenuItemState.ENABLED, |
||||||
|
"item must be bakeable with current supplies", |
||||||
|
); |
||||||
|
|
||||||
|
// supplies update
|
||||||
|
const updated_supplies = { |
||||||
|
...game_state.supplies, |
||||||
|
}; |
||||||
|
|
||||||
|
for (const ingredient in menu_item.ingredients) { |
||||||
|
updated_supplies[ingredient] -= menu_item.ingredients[ingredient] ?? 0; |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
...game_state, |
||||||
|
supplies: updated_supplies, |
||||||
|
oven: { |
||||||
|
...game_state.oven, |
||||||
|
used: game_state.oven.used + oven_slots, |
||||||
|
}, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
export { init_game_state, bake_items }; |
||||||
@ -0,0 +1,89 @@ |
|||||||
|
import { init_game, bake_items, is_menu_item_enabled } from './game.js'; |
||||||
|
|
||||||
|
describe('init_game', () => { |
||||||
|
test('should create a new game state with default values', () => { |
||||||
|
const game = init_game(); |
||||||
|
|
||||||
|
expect(game.player_money).toBe(0); |
||||||
|
expect(game.time.day).toBe(1); |
||||||
|
expect(game.time.phase).toBe('planning'); |
||||||
|
expect(game.oven.capacity).toBe(4); |
||||||
|
expect(game.oven.used).toBe(0); |
||||||
|
}); |
||||||
|
|
||||||
|
test('should start with zero supplies', () => { |
||||||
|
const game = init_game(); |
||||||
|
|
||||||
|
expect(game.supplies.flour).toBe(0); |
||||||
|
expect(game.supplies.eggs).toBe(0); |
||||||
|
expect(game.supplies.sugar).toBe(0); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('is_menu_item_enabled', () => { |
||||||
|
test('should return false when supplies are insufficient', () => { |
||||||
|
const game = init_game(); |
||||||
|
// No supplies, so nothing should be enabled
|
||||||
|
expect(is_menu_item_enabled(game, 'bread')).toBe(false); |
||||||
|
expect(is_menu_item_enabled(game, 'cookies')).toBe(false); |
||||||
|
}); |
||||||
|
|
||||||
|
test('should return true when supplies are sufficient', () => { |
||||||
|
const game = init_game(); |
||||||
|
// Add enough supplies for bread (flour: 2, yeast: 1, milk: 1)
|
||||||
|
game.supplies.flour = 10; |
||||||
|
game.supplies.yeast = 5; |
||||||
|
game.supplies.milk = 5; |
||||||
|
|
||||||
|
expect(is_menu_item_enabled(game, 'bread')).toBe(true); |
||||||
|
}); |
||||||
|
|
||||||
|
test('should return false for non-existent menu item', () => { |
||||||
|
const game = init_game(); |
||||||
|
expect(is_menu_item_enabled(game, 'pizza')).toBe(false); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('bake_items', () => { |
||||||
|
test('should deduct ingredients when baking', () => { |
||||||
|
const game = init_game(); |
||||||
|
// Add supplies for bread
|
||||||
|
game.supplies.flour = 10; |
||||||
|
game.supplies.yeast = 5; |
||||||
|
game.supplies.milk = 5; |
||||||
|
|
||||||
|
const result = bake_items(game, 'bread'); |
||||||
|
|
||||||
|
// Bread uses: flour: 2, yeast: 1, milk: 1
|
||||||
|
expect(result.supplies.flour).toBe(8); |
||||||
|
expect(result.supplies.yeast).toBe(4); |
||||||
|
expect(result.supplies.milk).toBe(4); |
||||||
|
}); |
||||||
|
|
||||||
|
test('should increase oven used slots', () => { |
||||||
|
const game = init_game(); |
||||||
|
game.supplies.flour = 10; |
||||||
|
game.supplies.yeast = 5; |
||||||
|
game.supplies.milk = 5; |
||||||
|
|
||||||
|
const result = bake_items(game, 'bread'); |
||||||
|
|
||||||
|
// Bread uses 1 oven slot
|
||||||
|
expect(result.oven.used).toBe(1); |
||||||
|
}); |
||||||
|
|
||||||
|
test('should not mutate the original game state', () => { |
||||||
|
const game = init_game(); |
||||||
|
game.supplies.flour = 10; |
||||||
|
game.supplies.yeast = 5; |
||||||
|
game.supplies.milk = 5; |
||||||
|
|
||||||
|
bake_items(game, 'bread'); |
||||||
|
|
||||||
|
// Original should be unchanged
|
||||||
|
expect(game.supplies.flour).toBe(10); |
||||||
|
expect(game.oven.used).toBe(0); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,30 @@ |
|||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>Bakery</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<h1>Bake Shop</h1> |
||||||
|
|
||||||
|
<button id="start_day">Start Day</button> |
||||||
|
|
||||||
|
<div class="dashboard"> |
||||||
|
<div class="game_section"> |
||||||
|
<h2>Supplies</h2> |
||||||
|
<ul> |
||||||
|
<li>flour: <span data-binding="supplies.flour"></span></li> |
||||||
|
<li>eggs: <span data-binding="supplies.eggs"></span></li> |
||||||
|
<li>oil: <span data-binding="supplies.oil"></span></li> |
||||||
|
<li>butter: <span data-binding="supplies.butter"></span></li> |
||||||
|
<li>milk: <span data-binding="supplies.milk"></span></li> |
||||||
|
<li>yeast: <span data-binding="supplies.yeast"></span></li> |
||||||
|
<li>chocolate: <span data-binding="supplies.chocolate"></span></li> |
||||||
|
<li>fruit: <span data-binding="supplies.fruit"></span></li> |
||||||
|
<li>sugar: <span data-binding="supplies.sugar"></span></li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
<!-- game_section --> |
||||||
|
</div> |
||||||
|
<!-- dashboard --> |
||||||
|
</body> |
||||||
|
<script src="index.js" type="module"></script> |
||||||
|
</html> |
||||||
@ -0,0 +1,45 @@ |
|||||||
|
import { Time } from "./Time.js"; |
||||||
|
import { init_game_state } from "./game.js"; |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {import('./game.js').GameState} GameState |
||||||
|
*/ |
||||||
|
|
||||||
|
/** @type {GameState} */ |
||||||
|
let game_state = init_game_state(); |
||||||
|
let time = new Time(game_state); |
||||||
|
|
||||||
|
/** @type {number} */ |
||||||
|
let last = performance.now(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Main game loop - runs every frame |
||||||
|
* @param {number} now - Current timestamp from requestAnimationFrame |
||||||
|
*/ |
||||||
|
function loop(now) { |
||||||
|
const dt = now - last; |
||||||
|
last = now; |
||||||
|
|
||||||
|
update(dt); |
||||||
|
|
||||||
|
render(); |
||||||
|
|
||||||
|
requestAnimationFrame(loop); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Updates game state based on elapsed time |
||||||
|
* @param {number} dt - Delta time in milliseconds |
||||||
|
*/ |
||||||
|
function update(dt) { |
||||||
|
time.update(dt); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Renders the current game state |
||||||
|
*/ |
||||||
|
function render() { |
||||||
|
//render stuff here
|
||||||
|
} |
||||||
|
|
||||||
|
requestAnimationFrame(loop); |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
export default { |
||||||
|
// Use the Node.js test environment
|
||||||
|
testEnvironment: 'node', |
||||||
|
|
||||||
|
// Look for test files in these patterns
|
||||||
|
testMatch: [ |
||||||
|
'**/__tests__/**/*.js', |
||||||
|
'**/*.test.js' |
||||||
|
], |
||||||
|
|
||||||
|
// Enable verbose output so you can see each test
|
||||||
|
verbose: true, |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,14 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"checkJs": true, |
||||||
|
"strict": true, |
||||||
|
"noImplicitAny": true, |
||||||
|
"strictNullChecks": true, |
||||||
|
"moduleResolution": "node", |
||||||
|
"module": "ES2022", |
||||||
|
"target": "ES2022" |
||||||
|
}, |
||||||
|
"include": ["*.js"], |
||||||
|
"exclude": ["node_modules", "*.test.js"] |
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,16 @@ |
|||||||
|
{ |
||||||
|
"name": "bakery", |
||||||
|
"version": "1.0.0", |
||||||
|
"type": "module", |
||||||
|
"main": "index.js", |
||||||
|
"scripts": { |
||||||
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js" |
||||||
|
}, |
||||||
|
"keywords": [], |
||||||
|
"author": "", |
||||||
|
"license": "ISC", |
||||||
|
"description": "", |
||||||
|
"devDependencies": { |
||||||
|
"jest": "^30.2.0" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
/** |
||||||
|
* Asserts a condition is true, throws and breaks the game if not |
||||||
|
* @param {boolean} condition - The condition to check |
||||||
|
* @param {string} message - Error message if assertion fails |
||||||
|
* @throws {Error} Throws an error if the condition is false |
||||||
|
*/ |
||||||
|
export function assert(condition, message) { |
||||||
|
if (!condition) { |
||||||
|
const error = new Error(`Assertion failed: ${message}`); |
||||||
|
console.error(error); |
||||||
|
throw error; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
Loading…
Reference in new issue