story 3 was not completely done

This commit is contained in:
2026-06-14 09:54:07 +01:00
parent f6605bbbfd
commit 350e8073e9
8 changed files with 39135 additions and 4054 deletions
+9 -11
View File
@@ -1,20 +1,18 @@
/**
* CharacterState — immutable record of all character state at a point in time.
*
* Groups the five character properties into a single value object,
* keeping the Character constructor at one parameter (max-params: 4).
* Groups the five character properties into a single value type,
* keeping the Character constructor at one parameter.
*/
import type { Health } from './Health.ts';
import type { Level } from './Level.ts';
import type { Status } from './Status.ts';
import type { Faction } from './Faction.ts';
export class CharacterState {
constructor(
readonly name: string,
readonly health: Health,
readonly status: Status,
readonly level: Level,
readonly factions: ReadonlySet<Faction>,
) {}
}
export type CharacterState = {
readonly name: string;
readonly health: Health;
readonly status: Status;
readonly level: Level;
readonly factions: ReadonlySet<Faction>;
};
+16 -30
View File
@@ -1,24 +1,22 @@
/**
* Healing Object — a Magical Object that gives health to Characters.
*
* Inherits health/status management from MagicalObject.
* Invariants enforced at construction:
* - Health is non-negative
* - Health never exceeds maxHealth
* - CurrentHealth never exceeds maxHealth
*/
import { Character } from './Character.ts';
import { Level } from './Level.ts';
import { MagicalObject } from './MagicalObject.ts';
export type ObjectStatus = { kind: 'alive' } | { kind: 'destroyed' };
export class HealingObject {
readonly #health: number;
readonly #maxHealth: number;
readonly #status: ObjectStatus;
private constructor(health: number, maxHealth: number, status: ObjectStatus) {
this.#health = health;
this.#maxHealth = maxHealth;
this.#status = status;
export class HealingObject extends MagicalObject {
private constructor(
health: number,
maxHealth: number,
status: { readonly kind: 'alive' } | { readonly kind: 'destroyed' },
) {
super(health, maxHealth, status);
}
static create({
@@ -36,29 +34,17 @@ export class HealingObject {
return new HealingObject(currentHealth, maxHealth, status);
}
get health(): number {
return this.#health;
}
get maxHealth(): number {
return this.#maxHealth;
}
get status(): ObjectStatus {
return this.#status;
}
/** Use this object to heal a character. Returns updated object and character. */
heal(character: Character, amount: number): { object: HealingObject; character: Character } {
// Destroyed objects can't heal
if (this.#status.kind === 'destroyed') {
if (this.status.kind === 'destroyed') {
return { object: this, character };
}
// Negative amount is invalid
if (amount < 0) throw new Error('Heal amount must be non-negative');
// Calculate actual heal amount: min of requested, object remaining, character headroom
const objectRemaining = this.#health;
const characterMax = character.level.value >= 6 ? 1500 : 1000;
const objectRemaining = this.health;
const characterMax = Level.maxHealthForLevel(character.level.value);
const characterHeadroom = characterMax - character.health.value;
const actualHeal = Math.min(amount, objectRemaining, characterHeadroom);
// If actualHeal is 0, nothing changes
@@ -66,7 +52,7 @@ export class HealingObject {
return { object: this, character };
}
// Create updated object
const newObjectHealth = this.#health - actualHeal;
const newObjectHealth = this.health - actualHeal;
const newObjectStatus =
newObjectHealth === 0 ? { kind: 'destroyed' as const } : { kind: 'alive' as const };
// Create updated character
@@ -77,7 +63,7 @@ export class HealingObject {
health: newCharacterHealth,
});
return {
object: new HealingObject(newObjectHealth, this.#maxHealth, newObjectStatus),
object: new HealingObject(newObjectHealth, this.maxHealth, newObjectStatus),
character: newCharacter,
};
}
+45
View File
@@ -0,0 +1,45 @@
/**
* MagicalObject — shared base for all magical items in the game.
*
* Invariants enforced at construction:
* - Health is non-negative
* - Health never exceeds maxHealth
* - Status derived from health (0 = destroyed, > 0 = alive)
*/
export type MagicalObjectStatus = { readonly kind: 'alive' } | { readonly kind: 'destroyed' };
export class MagicalObject {
readonly #health: number;
readonly #maxHealth: number;
readonly #status: MagicalObjectStatus;
protected constructor(health: number, maxHealth: number, status: MagicalObjectStatus) {
this.#health = health;
this.#maxHealth = maxHealth;
this.#status = status;
}
get health(): number {
return this.#health;
}
get maxHealth(): number {
return this.#maxHealth;
}
get status(): MagicalObjectStatus {
return this.#status;
}
/** Create a destroyed object (health = 0). */
static createDestroyed(maxHealth: number): MagicalObject {
if (maxHealth < 0) throw new Error('MaxHealth cannot be negative');
return new MagicalObject(0, maxHealth, { kind: 'destroyed' });
}
/** Check if this object is alive. */
isAlive(): boolean {
return this.#status.kind === 'alive';
}
}
+8 -28
View File
@@ -1,32 +1,25 @@
/**
* Magical Weapon — a Magical Object that deals fixed damage.
*
* Inherits health/status management from MagicalObject.
* Invariants enforced at construction:
* - Health is non-negative
* - Health never exceeds maxHealth
* - Damage is non-negative
*/
import { Character } from './Character.ts';
import { MagicalObject } from './MagicalObject.ts';
export type WeaponStatus = { kind: 'alive' } | { kind: 'destroyed' };
export class MagicalWeapon {
readonly #health: number;
readonly #maxHealth: number;
readonly #status: WeaponStatus;
export class MagicalWeapon extends MagicalObject {
readonly #damage: number;
readonly #owner: Character;
private constructor(
health: number,
maxHealth: number,
status: WeaponStatus,
status: { readonly kind: 'alive' } | { readonly kind: 'destroyed' },
damage: number,
owner: Character,
) {
this.#health = health;
this.#maxHealth = maxHealth;
this.#status = status;
super(health, maxHealth, status);
this.#damage = damage;
this.#owner = owner;
}
@@ -45,18 +38,6 @@ export class MagicalWeapon {
return new MagicalWeapon(maxHealth, maxHealth, { kind: 'alive' }, damage, owner);
}
get health(): number {
return this.#health;
}
get maxHealth(): number {
return this.#maxHealth;
}
get status(): WeaponStatus {
return this.#status;
}
get damage(): number {
return this.#damage;
}
@@ -68,7 +49,7 @@ export class MagicalWeapon {
/** Use this weapon to deal damage. Returns updated weapon and target. */
use(target: Character): { weapon: MagicalWeapon; target: Character } {
// Destroyed weapons can't be used
if (this.#status.kind === 'destroyed') {
if (this.status.kind === 'destroyed') {
return { weapon: this, target };
}
// Deal fixed damage
@@ -81,18 +62,17 @@ export class MagicalWeapon {
status: newTargetStatus,
});
// Reduce weapon health by 1
const newWeaponHealth = this.#health - 1;
const newWeaponHealth = this.health - 1;
const newWeaponStatus =
newWeaponHealth === 0 ? { kind: 'destroyed' as const } : { kind: 'alive' as const };
return {
weapon: new MagicalWeapon(
newWeaponHealth,
this.#maxHealth,
this.maxHealth,
newWeaponStatus,
this.#damage,
this.#owner,
),
target: newTarget,
};
}
-8
View File
@@ -7,11 +7,3 @@ export type Status = { readonly kind: 'alive' } | { readonly kind: 'dead' };
export const StatusAlive: Status = { kind: 'alive' };
export const StatusDead: Status = { kind: 'dead' };
export function isAlive(s: Status): s is { kind: 'alive' } {
return s.kind === 'alive';
}
export function isDead(s: Status): s is { kind: 'dead' } {
return s.kind === 'dead';
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long