create spells package

This commit is contained in:
Sky Johnson 2025-08-08 23:26:16 -05:00
parent a4d4dd9777
commit 53d131a96e
4 changed files with 1065 additions and 0 deletions

View File

@ -0,0 +1,88 @@
package spells
import (
"dk/internal/database"
"fmt"
"zombiezen.com/go/sqlite"
)
// Builder provides a fluent interface for creating spells
type Builder struct {
spell *Spell
db *database.DB
}
// NewBuilder creates a new spell builder
func NewBuilder(db *database.DB) *Builder {
return &Builder{
spell: &Spell{db: db},
db: db,
}
}
// WithName sets the spell name
func (b *Builder) WithName(name string) *Builder {
b.spell.Name = name
return b
}
// WithMP sets the spell's mana point cost
func (b *Builder) WithMP(mp int) *Builder {
b.spell.MP = mp
return b
}
// WithAttribute sets the spell's attribute (power/effectiveness)
func (b *Builder) WithAttribute(attribute int) *Builder {
b.spell.Attribute = attribute
return b
}
// WithType sets the spell type
func (b *Builder) WithType(spellType int) *Builder {
b.spell.Type = spellType
return b
}
// Create saves the spell to the database and returns it
func (b *Builder) Create() (*Spell, error) {
// Use a transaction to ensure we can get the ID
var spell *Spell
err := b.db.Transaction(func(tx *database.Tx) error {
query := `INSERT INTO spells (name, mp, attribute, type)
VALUES (?, ?, ?, ?)`
if err := tx.Exec(query, b.spell.Name, b.spell.MP, b.spell.Attribute, b.spell.Type); err != nil {
return fmt.Errorf("failed to insert spell: %w", err)
}
// Get the last inserted ID within the same transaction
var lastID int
err := tx.Query("SELECT last_insert_rowid()", func(stmt *sqlite.Stmt) error {
lastID = stmt.ColumnInt(0)
return nil
})
if err != nil {
return fmt.Errorf("failed to get last insert ID: %w", err)
}
// Create the spell with the ID
spell = &Spell{
ID: lastID,
Name: b.spell.Name,
MP: b.spell.MP,
Attribute: b.spell.Attribute,
Type: b.spell.Type,
db: b.db,
}
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to create spell: %w", err)
}
return spell, nil
}

267
internal/spells/doc.go Normal file
View File

@ -0,0 +1,267 @@
/*
Package spells is the active record implementation for spells in the game.
# Basic Usage
To retrieve a spell by ID:
spell, err := spells.Find(db, 1)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found spell: %s (MP: %d, Power: %d)\n", spell.Name, spell.MP, spell.Attribute)
To get all spells:
allSpells, err := spells.All(db)
if err != nil {
log.Fatal(err)
}
for _, spell := range allSpells {
fmt.Printf("Spell: %s\n", spell.Name)
}
To find a spell by name:
heal, err := spells.ByName(db, "Heal")
if err != nil {
log.Fatal(err)
}
To filter spells by type:
healingSpells, err := spells.ByType(db, spells.TypeHealing)
if err != nil {
log.Fatal(err)
}
To get spells within MP budget:
affordableSpells, err := spells.ByMaxMP(db, 10)
if err != nil {
log.Fatal(err)
}
To get spells of specific type within MP budget:
cheapHurtSpells, err := spells.ByTypeAndMaxMP(db, spells.TypeHurt, 15)
if err != nil {
log.Fatal(err)
}
# Creating Spells with Builder Pattern
The package provides a fluent builder interface for creating new spells:
spell, err := spells.NewBuilder(db).
WithName("Lightning Bolt").
WithMP(25).
WithAttribute(70).
WithType(spells.TypeHurt).
Create()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Created spell with ID: %d\n", spell.ID)
# Updating Spells
Spells can be modified and saved back to the database:
spell, _ := spells.Find(db, 1)
spell.Name = "Greater Heal"
spell.MP = 8
spell.Attribute = 20
err := spell.Save()
if err != nil {
log.Fatal(err)
}
# Deleting Spells
Spells can be removed from the database:
spell, _ := spells.Find(db, 1)
err := spell.Delete()
if err != nil {
log.Fatal(err)
}
# Spell Types
The package defines spell type constants:
spells.TypeHealing = 1 // Healing spells (Heal, Revive, Life, Breath, Gaia)
spells.TypeHurt = 2 // Hurt spells (Hurt, Pain, Maim, Rend, Chaos)
spells.TypeSleep = 3 // Sleep spells (Sleep, Dream, Nightmare)
spells.TypeAttackBoost = 4 // Attack boost spells (Craze, Rage, Fury)
spells.TypeDefenseBoost = 5 // Defense boost spells (Ward, Fend, Barrier)
Helper methods are available to check spell types:
if spell.IsHealing() {
fmt.Println("This spell heals the caster")
}
if spell.IsHurt() {
fmt.Println("This spell damages enemies")
}
if spell.IsSleep() {
fmt.Println("This spell puts enemies to sleep")
}
if spell.IsAttackBoost() {
fmt.Println("This spell boosts attack power")
}
if spell.IsDefenseBoost() {
fmt.Println("This spell boosts defense")
}
fmt.Printf("Spell type: %s\n", spell.TypeName())
# Database Schema
The spells table has the following structure:
CREATE TABLE spells (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
mp INTEGER NOT NULL DEFAULT 0,
attribute INTEGER NOT NULL DEFAULT 0,
type INTEGER NOT NULL DEFAULT 0
)
Where:
- id: Unique identifier
- name: Display name of the spell
- mp: Mana points required to cast the spell
- attribute: Power/effectiveness of the spell
- type: Spell type (1-5 as defined above)
# Spell Mechanics
## Mana Point System
All spells require MP to cast:
if spell.CanCast(player.CurrentMP) {
fmt.Printf("You can cast %s\n", spell.Name)
} else {
fmt.Printf("Not enough MP to cast %s (need %d, have %d)\n",
spell.Name, spell.MP, player.CurrentMP)
}
## Spell Efficiency
Calculate efficiency as attribute per MP cost:
efficiency := spell.Efficiency()
fmt.Printf("%s efficiency: %.2f power per MP\n", spell.Name, efficiency)
This helps players choose the most cost-effective spells.
## Offensive vs Support Spells
Spells are categorized by their primary purpose:
if spell.IsOffensive() {
// Hurt and Sleep spells - used against enemies
fmt.Println("Offensive spell - targets enemies")
}
if spell.IsSupport() {
// Healing and boost spells - used to help player
fmt.Println("Support spell - helps the caster")
}
# Spell Categories
## Healing Spells (Type 1)
Restore hit points to the caster:
- Heal: Basic healing
- Revive: Moderate healing
- Life: Strong healing
- Breath: Very strong healing
- Gaia: Ultimate healing
## Hurt Spells (Type 2)
Deal damage to enemies:
- Hurt: Basic damage
- Pain: Moderate damage
- Maim: Strong damage
- Rend: Very strong damage
- Chaos: Ultimate damage
Note: Some monsters have immunity to Hurt spells.
## Sleep Spells (Type 3)
Put enemies to sleep, preventing them from attacking:
- Sleep: Basic sleep effect
- Dream: Moderate sleep effect
- Nightmare: Strong sleep effect
Note: Some monsters have immunity to Sleep spells.
## Attack Boost Spells (Type 4)
Temporarily increase the caster's attack power:
- Craze: Basic attack boost
- Rage: Moderate attack boost
- Fury: Strong attack boost
## Defense Boost Spells (Type 5)
Temporarily increase the caster's defense:
- Ward: Basic defense boost
- Fend: Moderate defense boost
- Barrier: Strong defense boost
# Query Patterns
## Finding Castable Spells
Get spells a player can cast with their current MP:
currentMP := 25
castableSpells, err := spells.ByMaxMP(db, currentMP)
// Filter by type and MP budget
castableHurtSpells, err := spells.ByTypeAndMaxMP(db, spells.TypeHurt, currentMP)
## Spell Progression
Players typically learn spells in order of power/cost:
// Get all healing spells ordered by cost
healingSpells, err := spells.ByType(db, spells.TypeHealing)
// Results are ordered by MP cost automatically
## Combat Spell Selection
For combat AI or recommendations:
// Get most efficient hurt spells within budget
hurtSpells, err := spells.ByTypeAndMaxMP(db, spells.TypeHurt, availableMP)
// Find most efficient spell
var bestSpell *spells.Spell
var bestEfficiency float64
for _, spell := range hurtSpells {
if eff := spell.Efficiency(); eff > bestEfficiency {
bestEfficiency = eff
bestSpell = spell
}
}
# Error Handling
All functions return appropriate errors for common failure cases:
- Spell not found (Find/ByName returns error for non-existent spells)
- Database connection issues
- Invalid operations (e.g., saving/deleting spells without IDs)
*/
package spells

265
internal/spells/spells.go Normal file
View File

@ -0,0 +1,265 @@
package spells
import (
"fmt"
"dk/internal/database"
"zombiezen.com/go/sqlite"
)
// Spell represents a spell in the database
type Spell struct {
ID int `json:"id"`
Name string `json:"name"`
MP int `json:"mp"`
Attribute int `json:"attribute"`
Type int `json:"type"`
db *database.DB
}
// SpellType constants for spell types
const (
TypeHealing = 1
TypeHurt = 2
TypeSleep = 3
TypeAttackBoost = 4
TypeDefenseBoost = 5
)
// Find retrieves a spell by ID
func Find(db *database.DB, id int) (*Spell, error) {
spell := &Spell{db: db}
query := "SELECT id, name, mp, attribute, type FROM spells WHERE id = ?"
err := db.Query(query, func(stmt *sqlite.Stmt) error {
spell.ID = stmt.ColumnInt(0)
spell.Name = stmt.ColumnText(1)
spell.MP = stmt.ColumnInt(2)
spell.Attribute = stmt.ColumnInt(3)
spell.Type = stmt.ColumnInt(4)
return nil
}, id)
if err != nil {
return nil, fmt.Errorf("failed to find spell: %w", err)
}
if spell.ID == 0 {
return nil, fmt.Errorf("spell with ID %d not found", id)
}
return spell, nil
}
// All retrieves all spells
func All(db *database.DB) ([]*Spell, error) {
var spells []*Spell
query := "SELECT id, name, mp, attribute, type FROM spells ORDER BY type, mp, id"
err := db.Query(query, func(stmt *sqlite.Stmt) error {
spell := &Spell{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
MP: stmt.ColumnInt(2),
Attribute: stmt.ColumnInt(3),
Type: stmt.ColumnInt(4),
db: db,
}
spells = append(spells, spell)
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to retrieve all spells: %w", err)
}
return spells, nil
}
// ByType retrieves spells by type
func ByType(db *database.DB, spellType int) ([]*Spell, error) {
var spells []*Spell
query := "SELECT id, name, mp, attribute, type FROM spells WHERE type = ? ORDER BY mp, id"
err := db.Query(query, func(stmt *sqlite.Stmt) error {
spell := &Spell{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
MP: stmt.ColumnInt(2),
Attribute: stmt.ColumnInt(3),
Type: stmt.ColumnInt(4),
db: db,
}
spells = append(spells, spell)
return nil
}, spellType)
if err != nil {
return nil, fmt.Errorf("failed to retrieve spells by type: %w", err)
}
return spells, nil
}
// ByMaxMP retrieves spells that cost at most the specified MP
func ByMaxMP(db *database.DB, maxMP int) ([]*Spell, error) {
var spells []*Spell
query := "SELECT id, name, mp, attribute, type FROM spells WHERE mp <= ? ORDER BY type, mp, id"
err := db.Query(query, func(stmt *sqlite.Stmt) error {
spell := &Spell{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
MP: stmt.ColumnInt(2),
Attribute: stmt.ColumnInt(3),
Type: stmt.ColumnInt(4),
db: db,
}
spells = append(spells, spell)
return nil
}, maxMP)
if err != nil {
return nil, fmt.Errorf("failed to retrieve spells by max MP: %w", err)
}
return spells, nil
}
// ByTypeAndMaxMP retrieves spells of a specific type that cost at most the specified MP
func ByTypeAndMaxMP(db *database.DB, spellType, maxMP int) ([]*Spell, error) {
var spells []*Spell
query := "SELECT id, name, mp, attribute, type FROM spells WHERE type = ? AND mp <= ? ORDER BY mp, id"
err := db.Query(query, func(stmt *sqlite.Stmt) error {
spell := &Spell{
ID: stmt.ColumnInt(0),
Name: stmt.ColumnText(1),
MP: stmt.ColumnInt(2),
Attribute: stmt.ColumnInt(3),
Type: stmt.ColumnInt(4),
db: db,
}
spells = append(spells, spell)
return nil
}, spellType, maxMP)
if err != nil {
return nil, fmt.Errorf("failed to retrieve spells by type and max MP: %w", err)
}
return spells, nil
}
// ByName retrieves a spell by name (case-insensitive)
func ByName(db *database.DB, name string) (*Spell, error) {
spell := &Spell{db: db}
query := "SELECT id, name, mp, attribute, type FROM spells WHERE LOWER(name) = LOWER(?) LIMIT 1"
err := db.Query(query, func(stmt *sqlite.Stmt) error {
spell.ID = stmt.ColumnInt(0)
spell.Name = stmt.ColumnText(1)
spell.MP = stmt.ColumnInt(2)
spell.Attribute = stmt.ColumnInt(3)
spell.Type = stmt.ColumnInt(4)
return nil
}, name)
if err != nil {
return nil, fmt.Errorf("failed to find spell by name: %w", err)
}
if spell.ID == 0 {
return nil, fmt.Errorf("spell with name '%s' not found", name)
}
return spell, nil
}
// Save updates an existing spell in the database
func (s *Spell) Save() error {
if s.ID == 0 {
return fmt.Errorf("cannot save spell without ID")
}
query := `UPDATE spells SET name = ?, mp = ?, attribute = ?, type = ? WHERE id = ?`
return s.db.Exec(query, s.Name, s.MP, s.Attribute, s.Type, s.ID)
}
// Delete removes the spell from the database
func (s *Spell) Delete() error {
if s.ID == 0 {
return fmt.Errorf("cannot delete spell without ID")
}
query := "DELETE FROM spells WHERE id = ?"
return s.db.Exec(query, s.ID)
}
// IsHealing returns true if the spell is a healing spell
func (s *Spell) IsHealing() bool {
return s.Type == TypeHealing
}
// IsHurt returns true if the spell is a hurt spell
func (s *Spell) IsHurt() bool {
return s.Type == TypeHurt
}
// IsSleep returns true if the spell is a sleep spell
func (s *Spell) IsSleep() bool {
return s.Type == TypeSleep
}
// IsAttackBoost returns true if the spell boosts attack
func (s *Spell) IsAttackBoost() bool {
return s.Type == TypeAttackBoost
}
// IsDefenseBoost returns true if the spell boosts defense
func (s *Spell) IsDefenseBoost() bool {
return s.Type == TypeDefenseBoost
}
// TypeName returns the string representation of the spell type
func (s *Spell) TypeName() string {
switch s.Type {
case TypeHealing:
return "Healing"
case TypeHurt:
return "Hurt"
case TypeSleep:
return "Sleep"
case TypeAttackBoost:
return "Attack Boost"
case TypeDefenseBoost:
return "Defense Boost"
default:
return "Unknown"
}
}
// CanCast returns true if the spell can be cast with the given MP
func (s *Spell) CanCast(availableMP int) bool {
return availableMP >= s.MP
}
// Efficiency returns the attribute per MP ratio (higher is more efficient)
func (s *Spell) Efficiency() float64 {
if s.MP == 0 {
return 0
}
return float64(s.Attribute) / float64(s.MP)
}
// IsOffensive returns true if the spell is used for attacking
func (s *Spell) IsOffensive() bool {
return s.Type == TypeHurt || s.Type == TypeSleep
}
// IsSupport returns true if the spell is used for support/buffs
func (s *Spell) IsSupport() bool {
return s.Type == TypeHealing || s.Type == TypeAttackBoost || s.Type == TypeDefenseBoost
}

View File

@ -0,0 +1,445 @@
package spells
import (
"os"
"testing"
"dk/internal/database"
)
func setupTestDB(t *testing.T) *database.DB {
testDB := "test_spells.db"
t.Cleanup(func() {
os.Remove(testDB)
})
db, err := database.Open(testDB)
if err != nil {
t.Fatalf("Failed to open test database: %v", err)
}
// Create spells table
createTable := `CREATE TABLE spells (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
mp INTEGER NOT NULL DEFAULT 0,
attribute INTEGER NOT NULL DEFAULT 0,
type INTEGER NOT NULL DEFAULT 0
)`
if err := db.Exec(createTable); err != nil {
t.Fatalf("Failed to create spells table: %v", err)
}
// Insert test data
testSpells := `INSERT INTO spells (name, mp, attribute, type) VALUES
('Heal', 5, 10, 1),
('Revive', 10, 25, 1),
('Hurt', 5, 15, 2),
('Pain', 12, 35, 2),
('Sleep', 10, 5, 3),
('Dream', 30, 9, 3),
('Craze', 10, 10, 4),
('Ward', 10, 10, 5)`
if err := db.Exec(testSpells); err != nil {
t.Fatalf("Failed to insert test spells: %v", err)
}
return db
}
func TestFind(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
// Test finding existing spell
spell, err := Find(db, 1)
if err != nil {
t.Fatalf("Failed to find spell: %v", err)
}
if spell.ID != 1 {
t.Errorf("Expected ID 1, got %d", spell.ID)
}
if spell.Name != "Heal" {
t.Errorf("Expected name 'Heal', got '%s'", spell.Name)
}
if spell.MP != 5 {
t.Errorf("Expected MP 5, got %d", spell.MP)
}
if spell.Attribute != 10 {
t.Errorf("Expected attribute 10, got %d", spell.Attribute)
}
if spell.Type != TypeHealing {
t.Errorf("Expected type %d, got %d", TypeHealing, spell.Type)
}
// Test finding non-existent spell
_, err = Find(db, 999)
if err == nil {
t.Error("Expected error when finding non-existent spell")
}
}
func TestAll(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
spells, err := All(db)
if err != nil {
t.Fatalf("Failed to get all spells: %v", err)
}
if len(spells) != 8 {
t.Errorf("Expected 8 spells, got %d", len(spells))
}
// Check ordering (by type, then MP, then ID)
if spells[0].Type > spells[1].Type {
t.Error("Expected spells to be ordered by type first")
}
}
func TestByType(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
// Test healing spells
healingSpells, err := ByType(db, TypeHealing)
if err != nil {
t.Fatalf("Failed to get healing spells: %v", err)
}
if len(healingSpells) != 2 {
t.Errorf("Expected 2 healing spells, got %d", len(healingSpells))
}
for _, spell := range healingSpells {
if spell.Type != TypeHealing {
t.Errorf("Expected healing spell, got type %d", spell.Type)
}
}
// Test hurt spells
hurtSpells, err := ByType(db, TypeHurt)
if err != nil {
t.Fatalf("Failed to get hurt spells: %v", err)
}
if len(hurtSpells) != 2 {
t.Errorf("Expected 2 hurt spells, got %d", len(hurtSpells))
}
// Verify ordering within type (by MP)
if hurtSpells[0].MP > hurtSpells[1].MP {
t.Error("Expected spells within type to be ordered by MP")
}
}
func TestByMaxMP(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
// Test spells with MP <= 10
lowMPSpells, err := ByMaxMP(db, 10)
if err != nil {
t.Fatalf("Failed to get low MP spells: %v", err)
}
expectedCount := 6 // Heal(5), Hurt(5), Sleep(10), Craze(10), Revive(10), Ward(10)
if len(lowMPSpells) != expectedCount {
t.Errorf("Expected %d spells with MP <= 10, got %d", expectedCount, len(lowMPSpells))
}
// Verify all spells have MP <= 10
for _, spell := range lowMPSpells {
if spell.MP > 10 {
t.Errorf("Spell %s has MP %d, expected <= 10", spell.Name, spell.MP)
}
}
// Test very low MP threshold
veryLowMPSpells, err := ByMaxMP(db, 5)
if err != nil {
t.Fatalf("Failed to get very low MP spells: %v", err)
}
if len(veryLowMPSpells) != 2 { // Only Heal and Hurt
t.Errorf("Expected 2 spells with MP <= 5, got %d", len(veryLowMPSpells))
}
}
func TestByTypeAndMaxMP(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
// Test healing spells with MP <= 10
healingSpells, err := ByTypeAndMaxMP(db, TypeHealing, 10)
if err != nil {
t.Fatalf("Failed to get healing spells with MP <= 10: %v", err)
}
expectedCount := 2 // Heal(5) and Revive(10)
if len(healingSpells) != expectedCount {
t.Errorf("Expected %d healing spells with MP <= 10, got %d", expectedCount, len(healingSpells))
}
// Verify all are healing spells and within MP limit
for _, spell := range healingSpells {
if spell.Type != TypeHealing {
t.Errorf("Expected healing spell, got type %d", spell.Type)
}
if spell.MP > 10 {
t.Errorf("Spell %s has MP %d, expected <= 10", spell.Name, spell.MP)
}
}
// Test hurt spells with very low MP
lowHurtSpells, err := ByTypeAndMaxMP(db, TypeHurt, 5)
if err != nil {
t.Fatalf("Failed to get hurt spells with MP <= 5: %v", err)
}
if len(lowHurtSpells) != 1 { // Only Hurt(5)
t.Errorf("Expected 1 hurt spell with MP <= 5, got %d", len(lowHurtSpells))
}
}
func TestByName(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
// Test finding existing spell by name
spell, err := ByName(db, "Heal")
if err != nil {
t.Fatalf("Failed to find spell by name: %v", err)
}
if spell.Name != "Heal" {
t.Errorf("Expected name 'Heal', got '%s'", spell.Name)
}
if spell.Type != TypeHealing {
t.Errorf("Expected healing spell, got type %d", spell.Type)
}
// Test case insensitivity
spellLower, err := ByName(db, "heal")
if err != nil {
t.Fatalf("Failed to find spell by lowercase name: %v", err)
}
if spellLower.ID != spell.ID {
t.Error("Case insensitive search should return same spell")
}
// Test non-existent spell
_, err = ByName(db, "Fireball")
if err == nil {
t.Error("Expected error when finding non-existent spell by name")
}
}
func TestBuilder(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
// Create new spell using builder
spell, err := NewBuilder(db).
WithName("Lightning").
WithMP(25).
WithAttribute(60).
WithType(TypeHurt).
Create()
if err != nil {
t.Fatalf("Failed to create spell with builder: %v", err)
}
if spell.ID == 0 {
t.Error("Expected non-zero ID after creation")
}
if spell.Name != "Lightning" {
t.Errorf("Expected name 'Lightning', got '%s'", spell.Name)
}
if spell.MP != 25 {
t.Errorf("Expected MP 25, got %d", spell.MP)
}
if spell.Attribute != 60 {
t.Errorf("Expected attribute 60, got %d", spell.Attribute)
}
if spell.Type != TypeHurt {
t.Errorf("Expected type %d, got %d", TypeHurt, spell.Type)
}
// Verify it was saved to database
foundSpell, err := Find(db, spell.ID)
if err != nil {
t.Fatalf("Failed to find created spell: %v", err)
}
if foundSpell.Name != "Lightning" {
t.Errorf("Created spell not found in database")
}
}
func TestSave(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
spell, err := Find(db, 1)
if err != nil {
t.Fatalf("Failed to find spell: %v", err)
}
// Modify spell
spell.Name = "Enhanced Heal"
spell.MP = 7
spell.Attribute = 15
// Save changes
err = spell.Save()
if err != nil {
t.Fatalf("Failed to save spell: %v", err)
}
// Verify changes were saved
updatedSpell, err := Find(db, 1)
if err != nil {
t.Fatalf("Failed to find updated spell: %v", err)
}
if updatedSpell.Name != "Enhanced Heal" {
t.Errorf("Expected updated name 'Enhanced Heal', got '%s'", updatedSpell.Name)
}
if updatedSpell.MP != 7 {
t.Errorf("Expected updated MP 7, got %d", updatedSpell.MP)
}
if updatedSpell.Attribute != 15 {
t.Errorf("Expected updated attribute 15, got %d", updatedSpell.Attribute)
}
}
func TestDelete(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
spell, err := Find(db, 1)
if err != nil {
t.Fatalf("Failed to find spell: %v", err)
}
// Delete spell
err = spell.Delete()
if err != nil {
t.Fatalf("Failed to delete spell: %v", err)
}
// Verify spell was deleted
_, err = Find(db, 1)
if err == nil {
t.Error("Expected error when finding deleted spell")
}
}
func TestSpellTypeMethods(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
heal, _ := Find(db, 1) // Healing
hurt, _ := Find(db, 3) // Hurt
sleep, _ := Find(db, 5) // Sleep
craze, _ := Find(db, 7) // Attack boost
ward, _ := Find(db, 8) // Defense boost
// Test type checking methods
if !heal.IsHealing() {
t.Error("Expected Heal to be healing spell")
}
if heal.IsHurt() {
t.Error("Expected Heal not to be hurt spell")
}
if !hurt.IsHurt() {
t.Error("Expected Hurt to be hurt spell")
}
if hurt.IsHealing() {
t.Error("Expected Hurt not to be healing spell")
}
if !sleep.IsSleep() {
t.Error("Expected Sleep to be sleep spell")
}
if !craze.IsAttackBoost() {
t.Error("Expected Craze to be attack boost spell")
}
if !ward.IsDefenseBoost() {
t.Error("Expected Ward to be defense boost spell")
}
// Test TypeName
if heal.TypeName() != "Healing" {
t.Errorf("Expected Heal type name 'Healing', got '%s'", heal.TypeName())
}
if hurt.TypeName() != "Hurt" {
t.Errorf("Expected Hurt type name 'Hurt', got '%s'", hurt.TypeName())
}
if sleep.TypeName() != "Sleep" {
t.Errorf("Expected Sleep type name 'Sleep', got '%s'", sleep.TypeName())
}
if craze.TypeName() != "Attack Boost" {
t.Errorf("Expected Craze type name 'Attack Boost', got '%s'", craze.TypeName())
}
if ward.TypeName() != "Defense Boost" {
t.Errorf("Expected Ward type name 'Defense Boost', got '%s'", ward.TypeName())
}
}
func TestUtilityMethods(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
heal, _ := Find(db, 1) // MP: 5, Attribute: 10
hurt, _ := Find(db, 3) // MP: 5, Attribute: 15
sleep, _ := Find(db, 5) // MP: 10, Attribute: 5
// Test CanCast
if !heal.CanCast(10) {
t.Error("Expected to be able to cast Heal with 10 MP")
}
if heal.CanCast(3) {
t.Error("Expected not to be able to cast Heal with 3 MP")
}
// Test Efficiency
expectedHealEff := float64(10) / float64(5) // 2.0
if heal.Efficiency() != expectedHealEff {
t.Errorf("Expected Heal efficiency %.2f, got %.2f", expectedHealEff, heal.Efficiency())
}
expectedHurtEff := float64(15) / float64(5) // 3.0
if hurt.Efficiency() != expectedHurtEff {
t.Errorf("Expected Hurt efficiency %.2f, got %.2f", expectedHurtEff, hurt.Efficiency())
}
// Test IsOffensive
if heal.IsOffensive() {
t.Error("Expected Heal not to be offensive")
}
if !hurt.IsOffensive() {
t.Error("Expected Hurt to be offensive")
}
if !sleep.IsOffensive() {
t.Error("Expected Sleep to be offensive")
}
// Test IsSupport
if !heal.IsSupport() {
t.Error("Expected Heal to be support spell")
}
if hurt.IsSupport() {
t.Error("Expected Hurt not to be support spell")
}
}