story 3 was not completely done
This commit is contained in:
parent
f6605bbbfd
commit
350e8073e9
@ -1,20 +1,18 @@
|
|||||||
/**
|
/**
|
||||||
* CharacterState — immutable record of all character state at a point in time.
|
* CharacterState — immutable record of all character state at a point in time.
|
||||||
*
|
*
|
||||||
* Groups the five character properties into a single value object,
|
* Groups the five character properties into a single value type,
|
||||||
* keeping the Character constructor at one parameter (max-params: 4).
|
* keeping the Character constructor at one parameter.
|
||||||
*/
|
*/
|
||||||
import type { Health } from './Health.ts';
|
import type { Health } from './Health.ts';
|
||||||
import type { Level } from './Level.ts';
|
import type { Level } from './Level.ts';
|
||||||
import type { Status } from './Status.ts';
|
import type { Status } from './Status.ts';
|
||||||
import type { Faction } from './Faction.ts';
|
import type { Faction } from './Faction.ts';
|
||||||
|
|
||||||
export class CharacterState {
|
export type CharacterState = {
|
||||||
constructor(
|
readonly name: string;
|
||||||
readonly name: string,
|
readonly health: Health;
|
||||||
readonly health: Health,
|
readonly status: Status;
|
||||||
readonly status: Status,
|
readonly level: Level;
|
||||||
readonly level: Level,
|
readonly factions: ReadonlySet<Faction>;
|
||||||
readonly factions: ReadonlySet<Faction>,
|
};
|
||||||
) {}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,24 +1,22 @@
|
|||||||
/**
|
/**
|
||||||
* Healing Object — a Magical Object that gives health to Characters.
|
* Healing Object — a Magical Object that gives health to Characters.
|
||||||
*
|
*
|
||||||
|
* Inherits health/status management from MagicalObject.
|
||||||
* Invariants enforced at construction:
|
* Invariants enforced at construction:
|
||||||
* - Health is non-negative
|
* - CurrentHealth never exceeds maxHealth
|
||||||
* - Health never exceeds maxHealth
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Character } from './Character.ts';
|
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 extends MagicalObject {
|
||||||
|
private constructor(
|
||||||
export class HealingObject {
|
health: number,
|
||||||
readonly #health: number;
|
maxHealth: number,
|
||||||
readonly #maxHealth: number;
|
status: { readonly kind: 'alive' } | { readonly kind: 'destroyed' },
|
||||||
readonly #status: ObjectStatus;
|
) {
|
||||||
|
super(health, maxHealth, status);
|
||||||
private constructor(health: number, maxHealth: number, status: ObjectStatus) {
|
|
||||||
this.#health = health;
|
|
||||||
this.#maxHealth = maxHealth;
|
|
||||||
this.#status = status;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static create({
|
static create({
|
||||||
@ -36,29 +34,17 @@ export class HealingObject {
|
|||||||
return new HealingObject(currentHealth, maxHealth, status);
|
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. */
|
/** Use this object to heal a character. Returns updated object and character. */
|
||||||
heal(character: Character, amount: number): { object: HealingObject; character: Character } {
|
heal(character: Character, amount: number): { object: HealingObject; character: Character } {
|
||||||
// Destroyed objects can't heal
|
// Destroyed objects can't heal
|
||||||
if (this.#status.kind === 'destroyed') {
|
if (this.status.kind === 'destroyed') {
|
||||||
return { object: this, character };
|
return { object: this, character };
|
||||||
}
|
}
|
||||||
// Negative amount is invalid
|
// Negative amount is invalid
|
||||||
if (amount < 0) throw new Error('Heal amount must be non-negative');
|
if (amount < 0) throw new Error('Heal amount must be non-negative');
|
||||||
// Calculate actual heal amount: min of requested, object remaining, character headroom
|
// Calculate actual heal amount: min of requested, object remaining, character headroom
|
||||||
const objectRemaining = this.#health;
|
const objectRemaining = this.health;
|
||||||
const characterMax = character.level.value >= 6 ? 1500 : 1000;
|
const characterMax = Level.maxHealthForLevel(character.level.value);
|
||||||
const characterHeadroom = characterMax - character.health.value;
|
const characterHeadroom = characterMax - character.health.value;
|
||||||
const actualHeal = Math.min(amount, objectRemaining, characterHeadroom);
|
const actualHeal = Math.min(amount, objectRemaining, characterHeadroom);
|
||||||
// If actualHeal is 0, nothing changes
|
// If actualHeal is 0, nothing changes
|
||||||
@ -66,7 +52,7 @@ export class HealingObject {
|
|||||||
return { object: this, character };
|
return { object: this, character };
|
||||||
}
|
}
|
||||||
// Create updated object
|
// Create updated object
|
||||||
const newObjectHealth = this.#health - actualHeal;
|
const newObjectHealth = this.health - actualHeal;
|
||||||
const newObjectStatus =
|
const newObjectStatus =
|
||||||
newObjectHealth === 0 ? { kind: 'destroyed' as const } : { kind: 'alive' as const };
|
newObjectHealth === 0 ? { kind: 'destroyed' as const } : { kind: 'alive' as const };
|
||||||
// Create updated character
|
// Create updated character
|
||||||
@ -77,7 +63,7 @@ export class HealingObject {
|
|||||||
health: newCharacterHealth,
|
health: newCharacterHealth,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
object: new HealingObject(newObjectHealth, this.#maxHealth, newObjectStatus),
|
object: new HealingObject(newObjectHealth, this.maxHealth, newObjectStatus),
|
||||||
character: newCharacter,
|
character: newCharacter,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
45
src/MagicalObject.ts
Normal file
45
src/MagicalObject.ts
Normal 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';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,32 +1,25 @@
|
|||||||
/**
|
/**
|
||||||
* Magical Weapon — a Magical Object that deals fixed damage.
|
* Magical Weapon — a Magical Object that deals fixed damage.
|
||||||
*
|
*
|
||||||
|
* Inherits health/status management from MagicalObject.
|
||||||
* Invariants enforced at construction:
|
* Invariants enforced at construction:
|
||||||
* - Health is non-negative
|
|
||||||
* - Health never exceeds maxHealth
|
|
||||||
* - Damage is non-negative
|
* - Damage is non-negative
|
||||||
*/
|
*/
|
||||||
import { Character } from './Character.ts';
|
import { Character } from './Character.ts';
|
||||||
|
import { MagicalObject } from './MagicalObject.ts';
|
||||||
|
|
||||||
export type WeaponStatus = { kind: 'alive' } | { kind: 'destroyed' };
|
export class MagicalWeapon extends MagicalObject {
|
||||||
|
|
||||||
export class MagicalWeapon {
|
|
||||||
readonly #health: number;
|
|
||||||
readonly #maxHealth: number;
|
|
||||||
readonly #status: WeaponStatus;
|
|
||||||
readonly #damage: number;
|
readonly #damage: number;
|
||||||
readonly #owner: Character;
|
readonly #owner: Character;
|
||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
health: number,
|
health: number,
|
||||||
maxHealth: number,
|
maxHealth: number,
|
||||||
status: WeaponStatus,
|
status: { readonly kind: 'alive' } | { readonly kind: 'destroyed' },
|
||||||
damage: number,
|
damage: number,
|
||||||
owner: Character,
|
owner: Character,
|
||||||
) {
|
) {
|
||||||
this.#health = health;
|
super(health, maxHealth, status);
|
||||||
this.#maxHealth = maxHealth;
|
|
||||||
this.#status = status;
|
|
||||||
this.#damage = damage;
|
this.#damage = damage;
|
||||||
this.#owner = owner;
|
this.#owner = owner;
|
||||||
}
|
}
|
||||||
@ -45,18 +38,6 @@ export class MagicalWeapon {
|
|||||||
return new MagicalWeapon(maxHealth, maxHealth, { kind: 'alive' }, damage, owner);
|
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 {
|
get damage(): number {
|
||||||
return this.#damage;
|
return this.#damage;
|
||||||
}
|
}
|
||||||
@ -68,7 +49,7 @@ export class MagicalWeapon {
|
|||||||
/** Use this weapon to deal damage. Returns updated weapon and target. */
|
/** Use this weapon to deal damage. Returns updated weapon and target. */
|
||||||
use(target: Character): { weapon: MagicalWeapon; target: Character } {
|
use(target: Character): { weapon: MagicalWeapon; target: Character } {
|
||||||
// Destroyed weapons can't be used
|
// Destroyed weapons can't be used
|
||||||
if (this.#status.kind === 'destroyed') {
|
if (this.status.kind === 'destroyed') {
|
||||||
return { weapon: this, target };
|
return { weapon: this, target };
|
||||||
}
|
}
|
||||||
// Deal fixed damage
|
// Deal fixed damage
|
||||||
@ -81,18 +62,17 @@ export class MagicalWeapon {
|
|||||||
status: newTargetStatus,
|
status: newTargetStatus,
|
||||||
});
|
});
|
||||||
// Reduce weapon health by 1
|
// Reduce weapon health by 1
|
||||||
const newWeaponHealth = this.#health - 1;
|
const newWeaponHealth = this.health - 1;
|
||||||
const newWeaponStatus =
|
const newWeaponStatus =
|
||||||
newWeaponHealth === 0 ? { kind: 'destroyed' as const } : { kind: 'alive' as const };
|
newWeaponHealth === 0 ? { kind: 'destroyed' as const } : { kind: 'alive' as const };
|
||||||
return {
|
return {
|
||||||
weapon: new MagicalWeapon(
|
weapon: new MagicalWeapon(
|
||||||
newWeaponHealth,
|
newWeaponHealth,
|
||||||
this.#maxHealth,
|
this.maxHealth,
|
||||||
newWeaponStatus,
|
newWeaponStatus,
|
||||||
this.#damage,
|
this.#damage,
|
||||||
this.#owner,
|
this.#owner,
|
||||||
),
|
),
|
||||||
|
|
||||||
target: newTarget,
|
target: newTarget,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,11 +7,3 @@ export type Status = { readonly kind: 'alive' } | { readonly kind: 'dead' };
|
|||||||
|
|
||||||
export const StatusAlive: Status = { kind: 'alive' };
|
export const StatusAlive: Status = { kind: 'alive' };
|
||||||
export const StatusDead: Status = { kind: 'dead' };
|
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';
|
|
||||||
}
|
|
||||||
|
|||||||
13112
transcripts/found-out-story-3-is-not-done.html
Normal file
13112
transcripts/found-out-story-3-is-not-done.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
13112
transcripts/story-4-built.html
Normal file
13112
transcripts/story-4-built.html
Normal file
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user