EnemyMemoryComponent - UQcsse3200/2026-studio-3 GitHub Wiki

EnemyMemoryComponent

1. Overview

EnemyMemoryComponent is the central storage component of the Enemy Memory System.

It stores the player's observed combat behaviour during one battle and produces immutable PlayerMemory snapshots for enemy AI.

The component is attached to the player entity, with one instance created for each combat encounter.

PlayerTrackerComponent
        ↓ writes
EnemyMemoryComponent
        ↓ creates
PlayerMemory
        ↓ read by
EnemyBehaviourComponent
        ↓ passed through
EnemyAIContext
        ↓ consumed by
EnemyAI

EnemyMemoryComponent does not observe the player directly. Player behaviour is observed and classified by PlayerTrackerComponent.


2. Component Location

Source file:

source/core/src/main/com/csse3200/game/components/enemy/EnemyMemoryComponent.java

Package:

package com.csse3200.game.components.enemy;

The source directory must match the package declaration so that Java tools and SonarCloud can resolve the class correctly.


3. ECS Ownership

EnemyMemoryComponent is attached to the player entity by PlayerFactory.

flowchart TB
  Factory[PlayerFactory]
  Player[Player Entity]
  Memory[EnemyMemoryComponent]
  Tracker[PlayerTrackerComponent]
  Stats[CombatStatsComponent]
  Energy[EnergyComponent]

  Factory --> Player
  Player --> Memory
  Player --> Tracker
  Player --> Stats
  Player --> Energy

Both PlayerFactory.createPlayer overloads attach the component:

.addComponent(new EnemyMemoryComponent())
.addComponent(new PlayerTrackerComponent())

This ensures that normally created players and players created with restored health receive the same memory infrastructure.


4. Responsibilities

EnemyMemoryComponent is responsible for:

  • Counting attack cards played during the battle
  • Counting skill cards played during the battle
  • Counting cards played during the current turn
  • Saving the number of cards played during the previous turn
  • Tracking whether the player gained positive block during the current turn
  • Tracking consecutive completed turns without block
  • Resetting temporary state after turn settlement
  • Producing immutable PlayerMemory snapshots

It is not responsible for:

  • Listening to battle events
  • Resolving runtime card instances
  • Loading card configurations
  • Inspecting EffectConfig
  • Understanding battle phases
  • Selecting enemy actions
  • Applying enemy intents
  • Persisting memory between battles
flowchart LR
  Input[Classified player behaviour]
  Memory[EnemyMemoryComponent]
  State[Mutable battle memory]
  Snapshot[PlayerMemory]

  Input --> Memory
  Memory --> State
  State --> Snapshot

5. UML Class Diagram

classDiagram
  class Component {
    +create()
    +update()
    +dispose()
  }

  class EnemyMemoryComponent {
    -int attackCardsPlayed
    -int skillCardsPlayed
    -int cardsPlayedThisTurn
    -int cardsPlayedLastTurn
    -int consecutiveTurnsWithoutBlock
    -boolean gainedBlockThisTurn
    +recordCardPlayed(cardType, gainedBlock)
    +settlePlayerTurn()
    +snapshot()
  }

  class PlayerMemory {
    +int attackCardsPlayed
    +int skillCardsPlayed
    +int consecutiveTurnsWithoutBlock
    +int cardsPlayedLastTurn
    +favoursAttack()
    +isUndefended()
    +empty()
  }

  class PlayerTrackerComponent {
    +connect()
    -onCardPlayed()
    -onPhaseChanged()
  }

  class EnemyBehaviourComponent {
    +setEnemyMemory()
    +rollIntent()
  }

  Component <|-- EnemyMemoryComponent
  PlayerTrackerComponent --> EnemyMemoryComponent : writes
  EnemyMemoryComponent --> PlayerMemory : creates
  EnemyBehaviourComponent --> EnemyMemoryComponent : reads

6. Internal State

The component stores six mutable fields:

private int attackCardsPlayed;
private int skillCardsPlayed;

private int cardsPlayedThisTurn;
private int cardsPlayedLastTurn;

private int consecutiveTurnsWithoutBlock;
private boolean gainedBlockThisTurn;

State Categories

flowchart TB
  Memory[EnemyMemoryComponent]

  subgraph BattleTotals
    Attack[attackCardsPlayed]
    Skill[skillCardsPlayed]
  end

  subgraph TurnState
    Current[cardsPlayedThisTurn]
    Block[gainedBlockThisTurn]
  end

  subgraph TurnHistory
    Previous[cardsPlayedLastTurn]
    Streak[consecutiveTurnsWithoutBlock]
  end

  Memory --> Attack
  Memory --> Skill
  Memory --> Current
  Memory --> Block
  Memory --> Previous
  Memory --> Streak

Field Definitions

Field Meaning Reset Behaviour
attackCardsPlayed Attack cards played during the battle Reset when the component is destroyed
skillCardsPlayed Skill cards played during the battle Reset when the component is destroyed
cardsPlayedThisTurn All cards played during the active player turn Reset after turn settlement
cardsPlayedLastTurn Cards played during the previous completed turn Replaced after each settlement
consecutiveTurnsWithoutBlock Consecutive completed turns without positive block Reset when block is gained
gainedBlockThisTurn Whether positive block was observed this turn Reset after turn settlement

7. Public API

The component exposes three public operations.

classDiagram
  class EnemyMemoryComponent {
    +recordCardPlayed(CardType cardType, boolean gainedBlock) void
    +settlePlayerTurn() void
    +snapshot() PlayerMemory
  }

7.1 recordCardPlayed

public void recordCardPlayed(
    CardType cardType,
    boolean gainedBlock)

Records one successfully played card.

The caller must already have determined:

  • The card play succeeded
  • The card type
  • Whether the card provided positive block

The method does not inspect a CardConfig.

7.2 settlePlayerTurn

public void settlePlayerTurn()

Finalises the current player turn.

It:

  • Copies the current card count into cardsPlayedLastTurn
  • Updates the no-block streak
  • Clears the current-turn card count
  • Clears the current-turn block flag

7.3 snapshot

public PlayerMemory snapshot()

Creates and returns an immutable snapshot of the current stored information.


8. Card Recording Logic

When recordCardPlayed is called, every valid card increases:

cardsPlayedThisTurn

Attack cards additionally increase:

attackCardsPlayed

Skill cards additionally increase:

skillCardsPlayed

Other card types count towards the current turn but do not increase attack or skill totals.

flowchart TD
  Start[Record successful card]
  Validate{Card type is valid}
  Count[Increment cards this turn]
  Type{Card type}
  Attack[Increment attack total]
  Skill[Increment skill total]
  Other[No category total]
  Block{Positive block}
  Mark[Mark block gained]
  Finish[Recording complete]

  Start --> Validate
  Validate -->|No| Finish
  Validate -->|Yes| Count
  Count --> Type

  Type -->|Attack| Attack
  Type -->|Skill| Skill
  Type -->|Other| Other

  Attack --> Block
  Skill --> Block
  Other --> Block

  Block -->|Yes| Mark
  Block -->|No| Finish
  Mark --> Finish

The implementation uses:

switch (cardType) {
  case ATTACK -> attackCardsPlayed++;
  case SKILL -> skillCardsPlayed++;
  default -> {
    // Other card types still count towards this turn.
  }
}

Supported card categories include:

  • ATTACK
  • SKILL
  • POWER
  • STATUS
  • CURSE

9. Block Tracking

The method receives a boolean indicating whether the card provided positive block:

recordCardPlayed(cardType, gainedBlock)

If any card provides block during the current turn:

gainedBlockThisTurn = true;

Later cards that do not provide block must not reset the value.

flowchart LR
  Start[Turn starts]
  False[Block flag false]
  BlockCard[Block card played]
  True[Block flag true]
  AttackCard[Non-block card played]
  StillTrue[Block flag remains true]
  Settle[Turn settlement]

  Start --> False
  False --> BlockCard
  BlockCard --> True
  True --> AttackCard
  AttackCard --> StillTrue
  StillTrue --> Settle

This means the component records whether block was gained at least once during the turn.

It does not record the amount of block.


10. Turn Settlement Logic

settlePlayerTurn() is called only when the real player turn ends.

flowchart TD
  Start[Settle player turn]
  Copy[Copy current count to last turn]
  Block{Block gained this turn}
  ResetStreak[Reset streak to zero]
  IncreaseStreak[Increase streak by one]
  ClearCount[Clear current turn count]
  ClearFlag[Clear block flag]
  Complete[Settlement complete]

  Start --> Copy
  Copy --> Block

  Block -->|Yes| ResetStreak
  Block -->|No| IncreaseStreak

  ResetStreak --> ClearCount
  IncreaseStreak --> ClearCount
  ClearCount --> ClearFlag
  ClearFlag --> Complete

Equivalent logic:

cardsPlayedLastTurn = cardsPlayedThisTurn;

if (gainedBlockThisTurn) {
  consecutiveTurnsWithoutBlock = 0;
} else {
  consecutiveTurnsWithoutBlock++;
}

cardsPlayedThisTurn = 0;
gainedBlockThisTurn = false;

11. Turn State Transition Diagram

flowchart LR
  Empty[Turn begins]
  Tracking[Cards being recorded]
  Ready[Ready for settlement]
  Settled[Previous turn updated]
  Next[Next turn begins]

  Empty --> Tracking
  Tracking --> Tracking
  Tracking --> Ready
  Empty --> Ready
  Ready --> Settled
  Settled --> Next
  Next --> Tracking

A player may end a turn without playing any cards.

In that case:

cardsPlayedLastTurn = 0

The no-block streak still increases if no positive block was observed.


12. Write Sequence UML

The tracker is the only normal writer to the component.

sequenceDiagram
  participant BC as BattleController
  participant PT as PlayerTracker
  participant BD as BattleDeck
  participant CS as CardService
  participant EM as EnemyMemory

  BC->>PT: Successful card event
  PT->>BD: Find card instance
  BD-->>PT: CardInstance
  PT->>CS: Find card definition
  CS-->>PT: CardConfig
  PT->>PT: Classify type and block
  PT->>EM: recordCardPlayed

EnemyMemoryComponent receives already-classified information.

It does not know how the card was resolved.


13. Turn Settlement Sequence UML

sequenceDiagram
  participant BC as BattleController
  participant PT as PlayerTracker
  participant EM as EnemyMemory

  BC->>PT: Phase changed
  PT->>PT: Check real turn ending

  alt Real player turn ended
    PT->>EM: settlePlayerTurn
    EM->>EM: Save previous turn count
    EM->>EM: Update no-block streak
    EM->>EM: Reset temporary state
  else Temporary phase transition
    PT->>PT: Ignore transition
  end

The tracker ignores temporary card-resolution transitions:

PLAYER_TURN to CARD_RESOLVING
CARD_RESOLVING to PLAYER_TURN

The memory component does not contain this phase logic.


14. Read Sequence UML

Enemy AI receives memory through EnemyBehaviourComponent.

sequenceDiagram
  participant BC as BattleController
  participant EB as EnemyBehaviour
  participant EM as EnemyMemory
  participant PM as PlayerMemory
  participant EC as EnemyAIContext
  participant AI as EnemyAI

  BC->>EB: rollIntent
  EB->>EM: snapshot
  EM-->>EB: PlayerMemory
  EB->>EC: Create context with memory
  EB->>AI: decide with context
  AI->>EC: getPlayerMemory
  AI-->>EB: EnemyIntent

A new snapshot is requested for every enemy decision.

This ensures the AI receives the latest memory.


15. Immutable Snapshot

The component creates a new PlayerMemory value:

return new PlayerMemory(
    attackCardsPlayed,
    skillCardsPlayed,
    consecutiveTurnsWithoutBlock,
    cardsPlayedLastTurn);
flowchart LR
  Mutable[EnemyMemoryComponent]
  SnapshotOne[PlayerMemory One]
  Update[Memory update]
  SnapshotTwo[PlayerMemory Two]

  Mutable -->|snapshot| SnapshotOne
  Update --> Mutable
  Mutable -->|snapshot| SnapshotTwo

Previously created snapshots remain unchanged after later updates.

This protects enemy AI from mutable shared state.


16. Multiple Enemy Readers

One memory component is shared by every enemy in the current battle.

flowchart TB
  Player[Player Entity]
  Memory[EnemyMemoryComponent]

  BehaviourOne[EnemyBehaviour One]
  BehaviourTwo[EnemyBehaviour Two]
  BehaviourThree[EnemyBehaviour Three]

  AIone[EnemyAI One]
  AItwo[EnemyAI Two]
  AIthree[EnemyAI Three]

  Player --> Memory

  Memory --> BehaviourOne
  Memory --> BehaviourTwo
  Memory --> BehaviourThree

  BehaviourOne --> AIone
  BehaviourTwo --> AItwo
  BehaviourThree --> AIthree

All enemies observe the same player history.

Each enemy still owns:

  • Its own EnemyBehaviourComponent
  • Its own EnemyAI
  • Its own current intent
  • Its own turn state
  • Its own AI-specific counters
  • Its own AI-specific cooldowns

17. Component Lifecycle

The memory exists for one combat encounter.

flowchart LR
  Created[Component created]
  Empty[Empty memory]
  Tracking[Tracking actions]
  Reading[AI reads snapshots]
  Finished[Battle finished]
  Disposed[Component disposed]

  Created --> Empty
  Empty --> Tracking
  Tracking --> Reading
  Reading --> Tracking
  Tracking --> Finished
  Reading --> Finished
  Finished --> Disposed

The lifecycle is:

  1. PlayerFactory creates the player.
  2. EnemyMemoryComponent starts with zero values.
  3. PlayerTrackerComponent records successful actions.
  4. Enemy AI reads snapshots.
  5. The battle ends.
  6. The player entity and memory component are disposed.
  7. The next battle receives a new empty component.

Memory is not currently saved between battles.


18. Example

Assume the player performs the following actions.

First Turn

Attack
Attack
Skill with block

Before settlement:

attackCardsPlayed = 2
skillCardsPlayed = 1
cardsPlayedThisTurn = 3
cardsPlayedLastTurn = 0
consecutiveTurnsWithoutBlock = 0
gainedBlockThisTurn = true

After settlement:

attackCardsPlayed = 2
skillCardsPlayed = 1
cardsPlayedThisTurn = 0
cardsPlayedLastTurn = 3
consecutiveTurnsWithoutBlock = 0
gainedBlockThisTurn = false