Skip to content

Card Service & Testing

ataipham edited this page Aug 30, 2026 · 1 revision

Introduction

The Card Library is the single source of static card information. It loads card definitions from configs/cards.json, validates them, and lets other teams retrieve them by ID through the CardService API.

A card definition describes what every copy of a card has in common: its name, energy cost, type, rarity, target, effects and artwork. It does not represent a card currently in a deck or hand.

Team 6 owns these definitions and the Card Library. Team 5 owns runtime cards, decks, hands, drawing, targeting, playing and discarding. Team 7 owns the player's energy, health, block/armour and player status effects.

configs/cards.json
  -> CardConfigLoader loads and validates definitions
  -> CardLibrary registers definitions by unique ID
  -> CardService exposes read-only lookup operations
  -> Team 5 creates and manages runtime cards
  -> Team 1 or Team 7 applies the card effects

Example: Creating the Card Library

Create the library once during game or combat setup:

List<CardConfig> configs = CardConfigLoader.loadCards();
CardService cardService = new CardLibrary(configs);
  1. CardConfigLoader.loadCards() reads the default configs/cards.json asset.
  2. The loader validates all required fields, effect values and durations, and rejects duplicate IDs.
  3. CardLibrary registers each valid definition under its unique ID.
  4. Other systems receive the CardService interface instead of depending on the library's internal map.

There is currently no card-specific registration in ServiceLocator. Pass the same CardService instance to consumers through a constructor or setup method until a shared global lifecycle is agreed upon.

Example: Retrieving a Card

To retrieve the definition of the Strike card:

CardConfig strike = cardService.getCard("strike")
    .orElseThrow(() -> new IllegalStateException("Card definition not found"));

int energyCost = strike.cost;
TargetType targetType = strike.target;
EffectConfig[] effects = strike.effects;
  1. We request the card using its stable ID, strike.
  2. getCard() returns an Optional<CardConfig> so a missing ID never silently produces null.
  3. Team 5 reads cost to check whether the player can play the card.
  4. Team 5 reads target to determine how target selection should work.
  5. Team 5 applies each item in effects in array order.

IDs are case-sensitive. Unknown, null, empty and whitespace-only IDs return Optional.empty().

Card Library API

Other systems should depend on com.csse3200.game.cards.CardService:

public interface CardService {
  Optional<CardConfig> getCard(String cardId);

  List<CardConfig> getAllCards();
}

getCard(String cardId)

  • Returns the definition registered under the exact, case-sensitive ID.
  • Returns Optional.empty() for an unknown, null, empty or whitespace-only ID.
  • Does not draw a card or change deck, hand or discard state.

getAllCards()

  • Returns an unmodifiable snapshot of all registered definitions.
  • Does not expose the library's internal collection.
  • Does not guarantee iteration order. Sort the result when a display order is required.

The returned list cannot be structurally modified, but the current libGDX configuration classes use public fields. Consumers must treat every returned CardConfig, EffectConfig and effects array as read-only.

Card Definition Reference

Each object in the root cards array contains:

Field Java type Meaning and validation
id String Unique, non-blank, case-sensitive lookup key with no surrounding whitespace.
name String Non-blank player-facing name.
description String Required player-facing rules text.
cost int Energy required to play; must be zero or greater.
type CardType Broad card category, separate from the effects it performs.
rarity Rarity Rarity used by reward and shop systems.
target TargetType Target-selection mode that Team 5 must use.
effects EffectConfig[] One or more effects, applied in array order.
texturePath String Non-blank path relative to the core assets directory.

Supported Values

Type Values
CardType ATTACK, SKILL, POWER, STATUS, CURSE
Rarity COMMON, UNCOMMON, RARE
TargetType SELF, SINGLE_ENEMY, ALL_ENEMIES
EffectType DAMAGE, BLOCK, HEAL, POISON, VULNERABLE, FEEBLE, STRENGTH

Card type and card behaviour are deliberately separate. For example, a defence card is a SKILL with a BLOCK or HEAL effect. DEFENCE is not a CardType value.

Effect Values and Durations

Every effect has a type, a positive value and a duration:

Effect Meaning of value duration rule
DAMAGE Damage amount Must be 0; applies immediately.
BLOCK Block/armour amount Must be 0; applies immediately.
HEAL Health restored Must be 0; applies immediately.
POISON Effect magnitude or stacks Must be a positive number of turns.
VULNERABLE Effect magnitude or stacks Must be a positive number of turns.
FEEBLE Effect magnitude Must be a positive number of turns.
STRENGTH Strength amount Must be 0; lasts for the rest of combat.

In Team 6's schema, duration is always measured in turns. Team 7 and the enemy system store, decrement and resolve the statuses that they own.

Example: Multiple Effects

Effects are performed in the order in which they appear. poison_dagger first deals damage and then applies Poison:

{
  "id": "poison_dagger",
  "name": "Poison Dagger",
  "description": "Deal 4 damage. Apply 3 Poison for 3 turns.",
  "cost": 1,
  "type": "ATTACK",
  "rarity": "UNCOMMON",
  "target": "SINGLE_ENEMY",
  "effects": [
    { "type": "DAMAGE", "value": 4, "duration": 0 },
    { "type": "POISON", "value": 3, "duration": 3 }
  ],
  "texturePath": "images/cards/poison_dagger.png"
}

Testing

The Team 6 tests cover the consumer contract at three levels:

  • CardLibraryTest tests registration, duplicate rejection, lookup behaviour and unmodifiable list snapshots.
  • CardConfigLoaderTest.shouldIntegrateWithCardLibrary loads the asset format, constructs a library and retrieves a definition.
  • CardsEndToEndTest loads the real configs/cards.json, validates and registers it, and checks all six Sprint 1 definitions.

Run the card tests from the source directory:

./gradlew core:test --tests "com.csse3200.game.cards.*"

Behind the Scenes

Why Separate Card Definitions from Runtime Cards?

One definition can describe many copies of the same card. If the player has three Strike cards, the game needs three runtime cards but only one definition of Strike's name, artwork, cost and effects.

Keeping these responsibilities separate also prevents the Card Library from depending on Team 5's choice of runtime representation. Team 5 can change its deck implementation or switch between ECS and Scene2D without changing CardConfig or CardService.

Why Use a CardService Interface?

Consumers need only two operations: retrieve one card or list the available cards. Depending on the small interface hides the library's map, prevents other systems from registering definitions accidentally, and makes consumer code easier to test with a stub service.

Why Return Optional<CardConfig>?

A card ID may be missing because of an invalid save file, outdated content or an integration mistake. Optional makes the missing case explicit and avoids returning an unexplained null. Known built-in IDs may use orElseThrow(), while IDs from saved or external data should handle the empty result gracefully.

Cross-Team Decision Record

Decision Agreed result Teams affected
Static definitions versus runtime cards Team 6 supplies reusable definitions; Team 5 owns runtime instances/entities. 5, 6
Retrieval boundary Consumers use the two-method CardService interface. 2, 5, 6
Missing lookup Unknown, null and blank IDs return Optional.empty(). 2, 5, 6
Card categories Use ATTACK, SKILL, POWER, STATUS, CURSE; defence and utility are gameplay groupings. 5, 6
Target modes Sprint 1 supports self, one enemy and all enemies. 5, 6
Multiple effects Supported; consumers execute effects in array order. 1, 5, 6, 7
Energy representation cost is a non-negative integer; Team 7 owns live energy and Team 5 requests spending. 5, 6, 7
Timed effects POISON, VULNERABLE and FEEBLE require a positive duration measured in turns. 1, 5, 6, 7
Combat-long effect STRENGTH uses duration 0 and lasts for the combat. 1, 5, 6, 7
Service access Explicit dependency passing is supported; ServiceLocator registration remains undecided. 5, 6
Listing order getAllCards() does not guarantee an order. 2, 5, 6

Table of Contents

Home

Game Engine

Getting Started

Entities and Components

Service Locator

Loading Resources

Logging

Unit Testing

Debug Terminal

Input Handling

UI

Animations

Audio

AI

Physics

Game Screens and Areas

Terrain

Concurrency & Threading

Settings

Encounters
Tutorials and Contextual Help (Team 2)
Cards (Team 6)
Story (Team 6)
Level design, battle system, turn order, spawn (Team 3)
Cards, Save/Load & Bestiary (Team 5)
Rewards and Battle Settlement (Team 7)

Troubleshooting

MacOS Setup Guide

Clone this wiki locally