refactor: break circular dependency with DamageDealer and Healer interfaces

- Create src/magical-objects/magical-object-types.ts with DamageDealer
  and Healer interfaces
- MagicalWeapon implements DamageDealer
- HealingObject implements Healer
- Character depends on interfaces instead of concrete classes
This commit is contained in:
Willem van den Ende 2026-06-14 10:49:53 +01:00
parent f29e3c456f
commit 0805623b68
7 changed files with 26272 additions and 28 deletions

View File

@ -75,7 +75,7 @@ After a user-story-conversation produces an Allium spec and fast-check propertie
For each rule/invariant from the spec, decide whether to write a **property-based test** or an **example-based test**. Discuss with the user:
| Signal | Choose | Rationale |
| --------------------------------------------------- | --------------- | -------------------------------------------- |
| --------------------------------------------------- | -------------- | ------------------------------------------- |
| `fc.property` would be trivially short (< 5 lines) | Example-based | Property overhead not worth it |
| Invariant is a simple arithmetic relationship | Example-based | One or two examples cover all cases |
| State transition has a small, finite input space | Example-based | Exhaustive examples are feasible |
@ -168,8 +168,8 @@ Clear description of the change.
## Integration with Other Skills
| Skill | When to run problem-breakdown after |
| ---------------------- | ------------------------------------------------ |
| user-story-conversation| After Allium spec + properties are produced |
| ----------------------- | ------------------------------------------------------------------------ |
| user-story-conversation | After Allium spec + properties are produced |
| distill | After spec extraction, before test generation |
| tend | After spec changes, to plan implementation updates |
| propagate | When propagate produces obligations, to break them into file-level steps |

View File

@ -10,8 +10,8 @@ import type { Status } from './Status.ts';
import { StatusAlive, StatusDead } from './Status.ts';
import type { CharacterState } from './CharacterState.ts';
import type { Faction } from './Faction.ts';
import type { MagicalWeapon } from './MagicalWeapon.ts';
import type { HealingObject } from './HealingObject.ts';
import type { DamageDealer } from './magical-objects/magical-object-types.ts';
import type { Healer } from './magical-objects/magical-object-types.ts';
export interface CharacterCtor {
name: string;
@ -233,10 +233,7 @@ export class Character {
* Dead characters cannot use weapons. Only the owner can use a weapon.
* Returns updated weapon and target.
*/
useWeapon(
weapon: MagicalWeapon,
target: Character,
): { weapon: MagicalWeapon; target: Character } {
useWeapon(weapon: DamageDealer, target: Character): { weapon: DamageDealer; target: Character } {
// Dead characters cannot use weapons
if (this.status.kind === 'dead') return { weapon, target };
// Only the owner can use the weapon
@ -249,10 +246,7 @@ export class Character {
* Dead characters cannot use healing objects.
* Returns updated object and character.
*/
useHealingObject(
object: HealingObject,
amount: number,
): { object: HealingObject; character: Character } {
useHealingObject(object: Healer, amount: number): { object: Healer; character: Character } {
// Dead characters cannot use healing objects
if (this.status.kind === 'dead') return { object, character: this };
return object.heal(this, amount);

View File

@ -9,8 +9,9 @@
import { Character } from './Character.ts';
import { Level } from './Level.ts';
import { MagicalObject } from './MagicalObject.ts';
import type { Healer } from './magical-objects/magical-object-types.ts';
export class HealingObject extends MagicalObject {
export class HealingObject extends MagicalObject implements Healer {
private constructor(
health: number,
maxHealth: number,

View File

@ -7,8 +7,9 @@
*/
import { Character } from './Character.ts';
import { MagicalObject } from './MagicalObject.ts';
import type { DamageDealer } from './magical-objects/magical-object-types.ts';
export class MagicalWeapon extends MagicalObject {
export class MagicalWeapon extends MagicalObject implements DamageDealer {
readonly #damage: number;
readonly #owner: Character;

View File

@ -0,0 +1,24 @@
/**
* Magical object type interfaces break the circular dependency between
* Character.ts and the magical object implementations.
*
* These interfaces describe magical objects from Character's point of view,
* so Character can depend on abstractions rather than concrete classes.
*/
import type { Character } from '../Character.ts';
import type { MagicalObjectStatus } from '../MagicalObject.ts';
/** A magical object that deals damage — from Character's point of view */
export interface DamageDealer {
readonly owner: Character;
readonly health: number;
readonly status: MagicalObjectStatus;
use(target: Character): { weapon: DamageDealer; target: Character };
}
/** A magical object that heals — from Character's point of view */
export interface Healer {
readonly health: number;
readonly status: MagicalObjectStatus;
heal(character: Character, amount: number): { object: Healer; character: Character };
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long