create monsters package
This commit is contained in:
parent
ace43e1053
commit
a4d4dd9777
207
internal/monsters/doc.go
Normal file
207
internal/monsters/doc.go
Normal file
@ -0,0 +1,207 @@
|
||||
/*
|
||||
Package monsters is the active record implementation for monsters in the game.
|
||||
|
||||
# Basic Usage
|
||||
|
||||
To retrieve a monster by ID:
|
||||
|
||||
monster, err := monsters.Find(db, 1)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Found monster: %s (level: %d, HP: %d)\n", monster.Name, monster.Level, monster.MaxHP)
|
||||
|
||||
To get all monsters:
|
||||
|
||||
allMonsters, err := monsters.All(db)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, monster := range allMonsters {
|
||||
fmt.Printf("Monster: %s\n", monster.Name)
|
||||
}
|
||||
|
||||
To filter monsters by level:
|
||||
|
||||
level5Monsters, err := monsters.ByLevel(db, 5)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
To get monsters within a level range:
|
||||
|
||||
monsters1to10, err := monsters.ByLevelRange(db, 1, 10)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
To filter monsters by immunity type:
|
||||
|
||||
hurtImmune, err := monsters.ByImmunity(db, monsters.ImmuneHurt)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
# Creating Monsters with Builder Pattern
|
||||
|
||||
The package provides a fluent builder interface for creating new monsters:
|
||||
|
||||
monster, err := monsters.NewBuilder(db).
|
||||
WithName("Fire Dragon").
|
||||
WithMaxHP(500).
|
||||
WithMaxDmg(100).
|
||||
WithArmor(50).
|
||||
WithLevel(25).
|
||||
WithMaxExp(1000).
|
||||
WithMaxGold(500).
|
||||
WithImmunity(monsters.ImmuneHurt).
|
||||
Create()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Created monster with ID: %d\n", monster.ID)
|
||||
|
||||
# Updating Monsters
|
||||
|
||||
Monsters can be modified and saved back to the database:
|
||||
|
||||
monster, _ := monsters.Find(db, 1)
|
||||
monster.Name = "Enhanced Blue Slime"
|
||||
monster.MaxHP = 10
|
||||
monster.Level = 2
|
||||
|
||||
err := monster.Save()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
# Deleting Monsters
|
||||
|
||||
Monsters can be removed from the database:
|
||||
|
||||
monster, _ := monsters.Find(db, 1)
|
||||
err := monster.Delete()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
# Monster Immunity Types
|
||||
|
||||
The package defines immunity type constants:
|
||||
|
||||
monsters.ImmuneNone = 0 // No immunity
|
||||
monsters.ImmuneHurt = 1 // Immune to Hurt spells (Pain, Maim, Rend, Chaos)
|
||||
monsters.ImmuneSleep = 2 // Immune to Sleep spells (Sleep, Dream, Nightmare)
|
||||
|
||||
Helper methods are available to check immunity:
|
||||
|
||||
if monster.IsHurtImmune() {
|
||||
fmt.Println("This monster is immune to Hurt spells")
|
||||
}
|
||||
if monster.IsSleepImmune() {
|
||||
fmt.Println("This monster is immune to Sleep spells")
|
||||
}
|
||||
if monster.HasImmunity() {
|
||||
fmt.Printf("Monster has immunity: %s\n", monster.ImmunityName())
|
||||
}
|
||||
|
||||
# Database Schema
|
||||
|
||||
The monsters table has the following structure:
|
||||
|
||||
CREATE TABLE monsters (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
max_hp INTEGER NOT NULL DEFAULT 0,
|
||||
max_dmg INTEGER NOT NULL DEFAULT 0,
|
||||
armor INTEGER NOT NULL DEFAULT 0,
|
||||
level INTEGER NOT NULL DEFAULT 0,
|
||||
max_exp INTEGER NOT NULL DEFAULT 0,
|
||||
max_gold INTEGER NOT NULL DEFAULT 0,
|
||||
immune INTEGER NOT NULL DEFAULT 0
|
||||
)
|
||||
|
||||
Where:
|
||||
- id: Unique identifier
|
||||
- name: Display name of the monster
|
||||
- max_hp: Maximum hit points
|
||||
- max_dmg: Maximum damage per attack
|
||||
- armor: Armor class/defense rating
|
||||
- level: Monster level (affects encounter chances)
|
||||
- max_exp: Maximum experience points awarded when defeated
|
||||
- max_gold: Maximum gold awarded when defeated
|
||||
- immune: Immunity type (0=none, 1=hurt spells, 2=sleep spells)
|
||||
|
||||
# Monster Statistics
|
||||
|
||||
## Combat Stats
|
||||
|
||||
The core combat statistics define the monster's fighting capability:
|
||||
|
||||
fmt.Printf("HP: %d, Damage: %d, Armor: %d\n",
|
||||
monster.MaxHP, monster.MaxDmg, monster.Armor)
|
||||
|
||||
## Rewards
|
||||
|
||||
Monsters provide experience and gold rewards when defeated:
|
||||
|
||||
fmt.Printf("Rewards: %d exp, %d gold\n",
|
||||
monster.MaxExp, monster.MaxGold)
|
||||
|
||||
## Utility Methods
|
||||
|
||||
The package provides several utility methods for analyzing monsters:
|
||||
|
||||
// Difficulty rating based on stats relative to level
|
||||
difficulty := monster.DifficultyRating()
|
||||
fmt.Printf("Difficulty: %.2f\n", difficulty)
|
||||
|
||||
// Experience efficiency (exp per HP)
|
||||
expEfficiency := monster.ExpPerHP()
|
||||
fmt.Printf("Exp per HP: %.2f\n", expEfficiency)
|
||||
|
||||
// Gold efficiency (gold per HP)
|
||||
goldEfficiency := monster.GoldPerHP()
|
||||
fmt.Printf("Gold per HP: %.2f\n", goldEfficiency)
|
||||
|
||||
These methods help with:
|
||||
- Balancing monster difficulty
|
||||
- Identifying efficient farming targets
|
||||
- Analyzing risk/reward ratios
|
||||
|
||||
# Level-Based Queries
|
||||
|
||||
Monsters are commonly filtered by level for encounter generation:
|
||||
|
||||
// Get all monsters at a specific level
|
||||
level10Monsters, err := monsters.ByLevel(db, 10)
|
||||
|
||||
// Get monsters within a level range (inclusive)
|
||||
earlyGameMonsters, err := monsters.ByLevelRange(db, 1, 5)
|
||||
|
||||
// All monsters are ordered by level, then ID for consistent results
|
||||
|
||||
# Immunity System
|
||||
|
||||
The immunity system affects spell combat mechanics:
|
||||
|
||||
// Check specific immunities
|
||||
if monster.IsHurtImmune() {
|
||||
// Hurt spells (Pain, Maim, Rend, Chaos) won't work
|
||||
}
|
||||
if monster.IsSleepImmune() {
|
||||
// Sleep spells (Sleep, Dream, Nightmare) won't work
|
||||
}
|
||||
|
||||
// Get all monsters with specific immunity
|
||||
hurtImmuneMonsters, err := monsters.ByImmunity(db, monsters.ImmuneHurt)
|
||||
|
||||
# Error Handling
|
||||
|
||||
All functions return appropriate errors for common failure cases:
|
||||
- Monster not found (Find returns error for non-existent IDs)
|
||||
- Database connection issues
|
||||
- Invalid operations (e.g., saving/deleting monsters without IDs)
|
||||
*/
|
||||
package monsters
|
359
internal/monsters/monsters.go
Normal file
359
internal/monsters/monsters.go
Normal file
@ -0,0 +1,359 @@
|
||||
package monsters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"dk/internal/database"
|
||||
"zombiezen.com/go/sqlite"
|
||||
)
|
||||
|
||||
// Monster represents a monster in the database
|
||||
type Monster struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
MaxHP int `json:"max_hp"`
|
||||
MaxDmg int `json:"max_dmg"`
|
||||
Armor int `json:"armor"`
|
||||
Level int `json:"level"`
|
||||
MaxExp int `json:"max_exp"`
|
||||
MaxGold int `json:"max_gold"`
|
||||
Immune int `json:"immune"`
|
||||
|
||||
db *database.DB
|
||||
}
|
||||
|
||||
// Immunity constants for monster immunity types
|
||||
const (
|
||||
ImmuneNone = 0
|
||||
ImmuneHurt = 1 // Immune to Hurt spells
|
||||
ImmuneSleep = 2 // Immune to Sleep spells
|
||||
)
|
||||
|
||||
// Find retrieves a monster by ID
|
||||
func Find(db *database.DB, id int) (*Monster, error) {
|
||||
monster := &Monster{db: db}
|
||||
|
||||
query := "SELECT id, name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune FROM monsters WHERE id = ?"
|
||||
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||
monster.ID = stmt.ColumnInt(0)
|
||||
monster.Name = stmt.ColumnText(1)
|
||||
monster.MaxHP = stmt.ColumnInt(2)
|
||||
monster.MaxDmg = stmt.ColumnInt(3)
|
||||
monster.Armor = stmt.ColumnInt(4)
|
||||
monster.Level = stmt.ColumnInt(5)
|
||||
monster.MaxExp = stmt.ColumnInt(6)
|
||||
monster.MaxGold = stmt.ColumnInt(7)
|
||||
monster.Immune = stmt.ColumnInt(8)
|
||||
return nil
|
||||
}, id)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find monster: %w", err)
|
||||
}
|
||||
|
||||
if monster.ID == 0 {
|
||||
return nil, fmt.Errorf("monster with ID %d not found", id)
|
||||
}
|
||||
|
||||
return monster, nil
|
||||
}
|
||||
|
||||
// All retrieves all monsters
|
||||
func All(db *database.DB) ([]*Monster, error) {
|
||||
var monsters []*Monster
|
||||
|
||||
query := "SELECT id, name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune FROM monsters ORDER BY level, id"
|
||||
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||
monster := &Monster{
|
||||
ID: stmt.ColumnInt(0),
|
||||
Name: stmt.ColumnText(1),
|
||||
MaxHP: stmt.ColumnInt(2),
|
||||
MaxDmg: stmt.ColumnInt(3),
|
||||
Armor: stmt.ColumnInt(4),
|
||||
Level: stmt.ColumnInt(5),
|
||||
MaxExp: stmt.ColumnInt(6),
|
||||
MaxGold: stmt.ColumnInt(7),
|
||||
Immune: stmt.ColumnInt(8),
|
||||
db: db,
|
||||
}
|
||||
monsters = append(monsters, monster)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve all monsters: %w", err)
|
||||
}
|
||||
|
||||
return monsters, nil
|
||||
}
|
||||
|
||||
// ByLevel retrieves monsters by level
|
||||
func ByLevel(db *database.DB, level int) ([]*Monster, error) {
|
||||
var monsters []*Monster
|
||||
|
||||
query := "SELECT id, name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune FROM monsters WHERE level = ? ORDER BY id"
|
||||
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||
monster := &Monster{
|
||||
ID: stmt.ColumnInt(0),
|
||||
Name: stmt.ColumnText(1),
|
||||
MaxHP: stmt.ColumnInt(2),
|
||||
MaxDmg: stmt.ColumnInt(3),
|
||||
Armor: stmt.ColumnInt(4),
|
||||
Level: stmt.ColumnInt(5),
|
||||
MaxExp: stmt.ColumnInt(6),
|
||||
MaxGold: stmt.ColumnInt(7),
|
||||
Immune: stmt.ColumnInt(8),
|
||||
db: db,
|
||||
}
|
||||
monsters = append(monsters, monster)
|
||||
return nil
|
||||
}, level)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve monsters by level: %w", err)
|
||||
}
|
||||
|
||||
return monsters, nil
|
||||
}
|
||||
|
||||
// ByLevelRange retrieves monsters within a level range (inclusive)
|
||||
func ByLevelRange(db *database.DB, minLevel, maxLevel int) ([]*Monster, error) {
|
||||
var monsters []*Monster
|
||||
|
||||
query := "SELECT id, name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune FROM monsters WHERE level BETWEEN ? AND ? ORDER BY level, id"
|
||||
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||
monster := &Monster{
|
||||
ID: stmt.ColumnInt(0),
|
||||
Name: stmt.ColumnText(1),
|
||||
MaxHP: stmt.ColumnInt(2),
|
||||
MaxDmg: stmt.ColumnInt(3),
|
||||
Armor: stmt.ColumnInt(4),
|
||||
Level: stmt.ColumnInt(5),
|
||||
MaxExp: stmt.ColumnInt(6),
|
||||
MaxGold: stmt.ColumnInt(7),
|
||||
Immune: stmt.ColumnInt(8),
|
||||
db: db,
|
||||
}
|
||||
monsters = append(monsters, monster)
|
||||
return nil
|
||||
}, minLevel, maxLevel)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve monsters by level range: %w", err)
|
||||
}
|
||||
|
||||
return monsters, nil
|
||||
}
|
||||
|
||||
// ByImmunity retrieves monsters by immunity type
|
||||
func ByImmunity(db *database.DB, immunityType int) ([]*Monster, error) {
|
||||
var monsters []*Monster
|
||||
|
||||
query := "SELECT id, name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune FROM monsters WHERE immune = ? ORDER BY level, id"
|
||||
err := db.Query(query, func(stmt *sqlite.Stmt) error {
|
||||
monster := &Monster{
|
||||
ID: stmt.ColumnInt(0),
|
||||
Name: stmt.ColumnText(1),
|
||||
MaxHP: stmt.ColumnInt(2),
|
||||
MaxDmg: stmt.ColumnInt(3),
|
||||
Armor: stmt.ColumnInt(4),
|
||||
Level: stmt.ColumnInt(5),
|
||||
MaxExp: stmt.ColumnInt(6),
|
||||
MaxGold: stmt.ColumnInt(7),
|
||||
Immune: stmt.ColumnInt(8),
|
||||
db: db,
|
||||
}
|
||||
monsters = append(monsters, monster)
|
||||
return nil
|
||||
}, immunityType)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve monsters by immunity: %w", err)
|
||||
}
|
||||
|
||||
return monsters, nil
|
||||
}
|
||||
|
||||
// Builder provides a fluent interface for creating monsters
|
||||
type Builder struct {
|
||||
monster *Monster
|
||||
db *database.DB
|
||||
}
|
||||
|
||||
// NewBuilder creates a new monster builder
|
||||
func NewBuilder(db *database.DB) *Builder {
|
||||
return &Builder{
|
||||
monster: &Monster{db: db},
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
// WithName sets the monster name
|
||||
func (b *Builder) WithName(name string) *Builder {
|
||||
b.monster.Name = name
|
||||
return b
|
||||
}
|
||||
|
||||
// WithMaxHP sets the monster's maximum hit points
|
||||
func (b *Builder) WithMaxHP(maxHP int) *Builder {
|
||||
b.monster.MaxHP = maxHP
|
||||
return b
|
||||
}
|
||||
|
||||
// WithMaxDmg sets the monster's maximum damage
|
||||
func (b *Builder) WithMaxDmg(maxDmg int) *Builder {
|
||||
b.monster.MaxDmg = maxDmg
|
||||
return b
|
||||
}
|
||||
|
||||
// WithArmor sets the monster's armor value
|
||||
func (b *Builder) WithArmor(armor int) *Builder {
|
||||
b.monster.Armor = armor
|
||||
return b
|
||||
}
|
||||
|
||||
// WithLevel sets the monster's level
|
||||
func (b *Builder) WithLevel(level int) *Builder {
|
||||
b.monster.Level = level
|
||||
return b
|
||||
}
|
||||
|
||||
// WithMaxExp sets the monster's maximum experience reward
|
||||
func (b *Builder) WithMaxExp(maxExp int) *Builder {
|
||||
b.monster.MaxExp = maxExp
|
||||
return b
|
||||
}
|
||||
|
||||
// WithMaxGold sets the monster's maximum gold reward
|
||||
func (b *Builder) WithMaxGold(maxGold int) *Builder {
|
||||
b.monster.MaxGold = maxGold
|
||||
return b
|
||||
}
|
||||
|
||||
// WithImmunity sets the monster's immunity type
|
||||
func (b *Builder) WithImmunity(immunity int) *Builder {
|
||||
b.monster.Immune = immunity
|
||||
return b
|
||||
}
|
||||
|
||||
// Create saves the monster to the database and returns it
|
||||
func (b *Builder) Create() (*Monster, error) {
|
||||
// Use a transaction to ensure we can get the ID
|
||||
var monster *Monster
|
||||
err := b.db.Transaction(func(tx *database.Tx) error {
|
||||
query := `INSERT INTO monsters (name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
|
||||
if err := tx.Exec(query, b.monster.Name, b.monster.MaxHP, b.monster.MaxDmg, b.monster.Armor,
|
||||
b.monster.Level, b.monster.MaxExp, b.monster.MaxGold, b.monster.Immune); err != nil {
|
||||
return fmt.Errorf("failed to insert monster: %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 monster with the ID
|
||||
monster = &Monster{
|
||||
ID: lastID,
|
||||
Name: b.monster.Name,
|
||||
MaxHP: b.monster.MaxHP,
|
||||
MaxDmg: b.monster.MaxDmg,
|
||||
Armor: b.monster.Armor,
|
||||
Level: b.monster.Level,
|
||||
MaxExp: b.monster.MaxExp,
|
||||
MaxGold: b.monster.MaxGold,
|
||||
Immune: b.monster.Immune,
|
||||
db: b.db,
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create monster: %w", err)
|
||||
}
|
||||
|
||||
return monster, nil
|
||||
}
|
||||
|
||||
// Save updates an existing monster in the database
|
||||
func (m *Monster) Save() error {
|
||||
if m.ID == 0 {
|
||||
return fmt.Errorf("cannot save monster without ID")
|
||||
}
|
||||
|
||||
query := `UPDATE monsters SET name = ?, max_hp = ?, max_dmg = ?, armor = ?, level = ?, max_exp = ?, max_gold = ?, immune = ? WHERE id = ?`
|
||||
return m.db.Exec(query, m.Name, m.MaxHP, m.MaxDmg, m.Armor, m.Level, m.MaxExp, m.MaxGold, m.Immune, m.ID)
|
||||
}
|
||||
|
||||
// Delete removes the monster from the database
|
||||
func (m *Monster) Delete() error {
|
||||
if m.ID == 0 {
|
||||
return fmt.Errorf("cannot delete monster without ID")
|
||||
}
|
||||
|
||||
query := "DELETE FROM monsters WHERE id = ?"
|
||||
return m.db.Exec(query, m.ID)
|
||||
}
|
||||
|
||||
// IsHurtImmune returns true if the monster is immune to Hurt spells
|
||||
func (m *Monster) IsHurtImmune() bool {
|
||||
return m.Immune == ImmuneHurt
|
||||
}
|
||||
|
||||
// IsSleepImmune returns true if the monster is immune to Sleep spells
|
||||
func (m *Monster) IsSleepImmune() bool {
|
||||
return m.Immune == ImmuneSleep
|
||||
}
|
||||
|
||||
// HasImmunity returns true if the monster has any immunity
|
||||
func (m *Monster) HasImmunity() bool {
|
||||
return m.Immune != ImmuneNone
|
||||
}
|
||||
|
||||
// ImmunityName returns the string representation of the monster's immunity
|
||||
func (m *Monster) ImmunityName() string {
|
||||
switch m.Immune {
|
||||
case ImmuneNone:
|
||||
return "None"
|
||||
case ImmuneHurt:
|
||||
return "Hurt Spells"
|
||||
case ImmuneSleep:
|
||||
return "Sleep Spells"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// DifficultyRating calculates a simple difficulty rating based on stats
|
||||
func (m *Monster) DifficultyRating() float64 {
|
||||
// Simple formula: (HP + Damage + Armor) / Level
|
||||
// Higher values indicate tougher monsters relative to their level
|
||||
if m.Level == 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(m.MaxHP+m.MaxDmg+m.Armor) / float64(m.Level)
|
||||
}
|
||||
|
||||
// ExpPerHP returns the experience reward per hit point (efficiency metric)
|
||||
func (m *Monster) ExpPerHP() float64 {
|
||||
if m.MaxHP == 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(m.MaxExp) / float64(m.MaxHP)
|
||||
}
|
||||
|
||||
// GoldPerHP returns the gold reward per hit point (efficiency metric)
|
||||
func (m *Monster) GoldPerHP() float64 {
|
||||
if m.MaxHP == 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(m.MaxGold) / float64(m.MaxHP)
|
||||
}
|
408
internal/monsters/monsters_test.go
Normal file
408
internal/monsters/monsters_test.go
Normal file
@ -0,0 +1,408 @@
|
||||
package monsters
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"dk/internal/database"
|
||||
)
|
||||
|
||||
func setupTestDB(t *testing.T) *database.DB {
|
||||
testDB := "test_monsters.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 monsters table
|
||||
createTable := `CREATE TABLE monsters (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
max_hp INTEGER NOT NULL DEFAULT 0,
|
||||
max_dmg INTEGER NOT NULL DEFAULT 0,
|
||||
armor INTEGER NOT NULL DEFAULT 0,
|
||||
level INTEGER NOT NULL DEFAULT 0,
|
||||
max_exp INTEGER NOT NULL DEFAULT 0,
|
||||
max_gold INTEGER NOT NULL DEFAULT 0,
|
||||
immune INTEGER NOT NULL DEFAULT 0
|
||||
)`
|
||||
|
||||
if err := db.Exec(createTable); err != nil {
|
||||
t.Fatalf("Failed to create monsters table: %v", err)
|
||||
}
|
||||
|
||||
// Insert test data
|
||||
testMonsters := `INSERT INTO monsters (name, max_hp, max_dmg, armor, level, max_exp, max_gold, immune) VALUES
|
||||
('Blue Slime', 4, 3, 1, 1, 1, 1, 0),
|
||||
('Red Slime', 6, 5, 1, 1, 2, 1, 0),
|
||||
('Shadow', 10, 9, 3, 2, 6, 2, 1),
|
||||
('Silver Slime', 15, 100, 200, 30, 15, 1000, 2),
|
||||
('Raven', 16, 13, 5, 4, 18, 6, 0)`
|
||||
|
||||
if err := db.Exec(testMonsters); err != nil {
|
||||
t.Fatalf("Failed to insert test monsters: %v", err)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func TestFind(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
// Test finding existing monster
|
||||
monster, err := Find(db, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to find monster: %v", err)
|
||||
}
|
||||
|
||||
if monster.ID != 1 {
|
||||
t.Errorf("Expected ID 1, got %d", monster.ID)
|
||||
}
|
||||
if monster.Name != "Blue Slime" {
|
||||
t.Errorf("Expected name 'Blue Slime', got '%s'", monster.Name)
|
||||
}
|
||||
if monster.MaxHP != 4 {
|
||||
t.Errorf("Expected max_hp 4, got %d", monster.MaxHP)
|
||||
}
|
||||
if monster.MaxDmg != 3 {
|
||||
t.Errorf("Expected max_dmg 3, got %d", monster.MaxDmg)
|
||||
}
|
||||
if monster.Armor != 1 {
|
||||
t.Errorf("Expected armor 1, got %d", monster.Armor)
|
||||
}
|
||||
if monster.Level != 1 {
|
||||
t.Errorf("Expected level 1, got %d", monster.Level)
|
||||
}
|
||||
if monster.MaxExp != 1 {
|
||||
t.Errorf("Expected max_exp 1, got %d", monster.MaxExp)
|
||||
}
|
||||
if monster.MaxGold != 1 {
|
||||
t.Errorf("Expected max_gold 1, got %d", monster.MaxGold)
|
||||
}
|
||||
if monster.Immune != ImmuneNone {
|
||||
t.Errorf("Expected immune %d, got %d", ImmuneNone, monster.Immune)
|
||||
}
|
||||
|
||||
// Test finding non-existent monster
|
||||
_, err = Find(db, 999)
|
||||
if err == nil {
|
||||
t.Error("Expected error when finding non-existent monster")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
monsters, err := All(db)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get all monsters: %v", err)
|
||||
}
|
||||
|
||||
if len(monsters) != 5 {
|
||||
t.Errorf("Expected 5 monsters, got %d", len(monsters))
|
||||
}
|
||||
|
||||
// Check first monster (should be ordered by level, then id)
|
||||
if monsters[0].Name != "Blue Slime" {
|
||||
t.Errorf("Expected first monster to be 'Blue Slime', got '%s'", monsters[0].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestByLevel(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
// Test level 1 monsters
|
||||
level1Monsters, err := ByLevel(db, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get level 1 monsters: %v", err)
|
||||
}
|
||||
|
||||
if len(level1Monsters) != 2 {
|
||||
t.Errorf("Expected 2 level 1 monsters, got %d", len(level1Monsters))
|
||||
}
|
||||
|
||||
for _, monster := range level1Monsters {
|
||||
if monster.Level != 1 {
|
||||
t.Errorf("Expected level 1, got %d for monster %s", monster.Level, monster.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Test level that doesn't exist
|
||||
noMonsters, err := ByLevel(db, 999)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to query non-existent level: %v", err)
|
||||
}
|
||||
|
||||
if len(noMonsters) != 0 {
|
||||
t.Errorf("Expected 0 monsters at level 999, got %d", len(noMonsters))
|
||||
}
|
||||
}
|
||||
|
||||
func TestByLevelRange(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
// Test level range 1-2
|
||||
monsters, err := ByLevelRange(db, 1, 2)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get monsters by level range: %v", err)
|
||||
}
|
||||
|
||||
if len(monsters) != 3 {
|
||||
t.Errorf("Expected 3 monsters in level range 1-2, got %d", len(monsters))
|
||||
}
|
||||
|
||||
// Verify all monsters are within range
|
||||
for _, monster := range monsters {
|
||||
if monster.Level < 1 || monster.Level > 2 {
|
||||
t.Errorf("Monster %s level %d is outside range 1-2", monster.Name, monster.Level)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify ordering (by level, then id)
|
||||
if monsters[0].Level > monsters[len(monsters)-1].Level {
|
||||
t.Error("Expected monsters to be ordered by level")
|
||||
}
|
||||
}
|
||||
|
||||
func TestByImmunity(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
// Test Hurt immune monsters
|
||||
hurtImmune, err := ByImmunity(db, ImmuneHurt)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get Hurt immune monsters: %v", err)
|
||||
}
|
||||
|
||||
if len(hurtImmune) != 1 {
|
||||
t.Errorf("Expected 1 Hurt immune monster, got %d", len(hurtImmune))
|
||||
}
|
||||
|
||||
if len(hurtImmune) > 0 && hurtImmune[0].Name != "Shadow" {
|
||||
t.Errorf("Expected Hurt immune monster to be 'Shadow', got '%s'", hurtImmune[0].Name)
|
||||
}
|
||||
|
||||
// Test Sleep immune monsters
|
||||
sleepImmune, err := ByImmunity(db, ImmuneSleep)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get Sleep immune monsters: %v", err)
|
||||
}
|
||||
|
||||
if len(sleepImmune) != 1 {
|
||||
t.Errorf("Expected 1 Sleep immune monster, got %d", len(sleepImmune))
|
||||
}
|
||||
|
||||
// Test no immunity monsters
|
||||
noImmunity, err := ByImmunity(db, ImmuneNone)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get non-immune monsters: %v", err)
|
||||
}
|
||||
|
||||
if len(noImmunity) != 3 {
|
||||
t.Errorf("Expected 3 non-immune monsters, got %d", len(noImmunity))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuilder(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
// Create new monster using builder
|
||||
monster, err := NewBuilder(db).
|
||||
WithName("Test Dragon").
|
||||
WithMaxHP(100).
|
||||
WithMaxDmg(25).
|
||||
WithArmor(10).
|
||||
WithLevel(15).
|
||||
WithMaxExp(500).
|
||||
WithMaxGold(100).
|
||||
WithImmunity(ImmuneHurt).
|
||||
Create()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create monster with builder: %v", err)
|
||||
}
|
||||
|
||||
if monster.ID == 0 {
|
||||
t.Error("Expected non-zero ID after creation")
|
||||
}
|
||||
if monster.Name != "Test Dragon" {
|
||||
t.Errorf("Expected name 'Test Dragon', got '%s'", monster.Name)
|
||||
}
|
||||
if monster.MaxHP != 100 {
|
||||
t.Errorf("Expected max_hp 100, got %d", monster.MaxHP)
|
||||
}
|
||||
if monster.MaxDmg != 25 {
|
||||
t.Errorf("Expected max_dmg 25, got %d", monster.MaxDmg)
|
||||
}
|
||||
if monster.Armor != 10 {
|
||||
t.Errorf("Expected armor 10, got %d", monster.Armor)
|
||||
}
|
||||
if monster.Level != 15 {
|
||||
t.Errorf("Expected level 15, got %d", monster.Level)
|
||||
}
|
||||
if monster.MaxExp != 500 {
|
||||
t.Errorf("Expected max_exp 500, got %d", monster.MaxExp)
|
||||
}
|
||||
if monster.MaxGold != 100 {
|
||||
t.Errorf("Expected max_gold 100, got %d", monster.MaxGold)
|
||||
}
|
||||
if monster.Immune != ImmuneHurt {
|
||||
t.Errorf("Expected immune %d, got %d", ImmuneHurt, monster.Immune)
|
||||
}
|
||||
|
||||
// Verify it was saved to database
|
||||
foundMonster, err := Find(db, monster.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to find created monster: %v", err)
|
||||
}
|
||||
|
||||
if foundMonster.Name != "Test Dragon" {
|
||||
t.Errorf("Created monster not found in database")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSave(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
monster, err := Find(db, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to find monster: %v", err)
|
||||
}
|
||||
|
||||
// Modify monster
|
||||
monster.Name = "Updated Blue Slime"
|
||||
monster.MaxHP = 8
|
||||
monster.Level = 2
|
||||
|
||||
// Save changes
|
||||
err = monster.Save()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to save monster: %v", err)
|
||||
}
|
||||
|
||||
// Verify changes were saved
|
||||
updatedMonster, err := Find(db, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to find updated monster: %v", err)
|
||||
}
|
||||
|
||||
if updatedMonster.Name != "Updated Blue Slime" {
|
||||
t.Errorf("Expected updated name 'Updated Blue Slime', got '%s'", updatedMonster.Name)
|
||||
}
|
||||
if updatedMonster.MaxHP != 8 {
|
||||
t.Errorf("Expected updated max_hp 8, got %d", updatedMonster.MaxHP)
|
||||
}
|
||||
if updatedMonster.Level != 2 {
|
||||
t.Errorf("Expected updated level 2, got %d", updatedMonster.Level)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
monster, err := Find(db, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to find monster: %v", err)
|
||||
}
|
||||
|
||||
// Delete monster
|
||||
err = monster.Delete()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to delete monster: %v", err)
|
||||
}
|
||||
|
||||
// Verify monster was deleted
|
||||
_, err = Find(db, 1)
|
||||
if err == nil {
|
||||
t.Error("Expected error when finding deleted monster")
|
||||
}
|
||||
}
|
||||
|
||||
func TestImmunityMethods(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
blueSlime, _ := Find(db, 1) // No immunity
|
||||
shadow, _ := Find(db, 3) // Hurt immune
|
||||
silverSlime, _ := Find(db, 4) // Sleep immune
|
||||
|
||||
// Test IsHurtImmune
|
||||
if blueSlime.IsHurtImmune() {
|
||||
t.Error("Expected blue slime not to be Hurt immune")
|
||||
}
|
||||
if !shadow.IsHurtImmune() {
|
||||
t.Error("Expected shadow to be Hurt immune")
|
||||
}
|
||||
if silverSlime.IsHurtImmune() {
|
||||
t.Error("Expected silver slime not to be Hurt immune")
|
||||
}
|
||||
|
||||
// Test IsSleepImmune
|
||||
if blueSlime.IsSleepImmune() {
|
||||
t.Error("Expected blue slime not to be Sleep immune")
|
||||
}
|
||||
if shadow.IsSleepImmune() {
|
||||
t.Error("Expected shadow not to be Sleep immune")
|
||||
}
|
||||
if !silverSlime.IsSleepImmune() {
|
||||
t.Error("Expected silver slime to be Sleep immune")
|
||||
}
|
||||
|
||||
// Test HasImmunity
|
||||
if blueSlime.HasImmunity() {
|
||||
t.Error("Expected blue slime to have no immunity")
|
||||
}
|
||||
if !shadow.HasImmunity() {
|
||||
t.Error("Expected shadow to have immunity")
|
||||
}
|
||||
if !silverSlime.HasImmunity() {
|
||||
t.Error("Expected silver slime to have immunity")
|
||||
}
|
||||
|
||||
// Test ImmunityName
|
||||
if blueSlime.ImmunityName() != "None" {
|
||||
t.Errorf("Expected blue slime immunity name 'None', got '%s'", blueSlime.ImmunityName())
|
||||
}
|
||||
if shadow.ImmunityName() != "Hurt Spells" {
|
||||
t.Errorf("Expected shadow immunity name 'Hurt Spells', got '%s'", shadow.ImmunityName())
|
||||
}
|
||||
if silverSlime.ImmunityName() != "Sleep Spells" {
|
||||
t.Errorf("Expected silver slime immunity name 'Sleep Spells', got '%s'", silverSlime.ImmunityName())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUtilityMethods(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
blueSlime, _ := Find(db, 1)
|
||||
|
||||
// Test DifficultyRating
|
||||
expectedDifficulty := float64(4+3+1) / float64(1) // (HP + Damage + Armor) / Level
|
||||
if blueSlime.DifficultyRating() != expectedDifficulty {
|
||||
t.Errorf("Expected difficulty rating %.2f, got %.2f", expectedDifficulty, blueSlime.DifficultyRating())
|
||||
}
|
||||
|
||||
// Test ExpPerHP
|
||||
expectedExpPerHP := float64(1) / float64(4) // Exp / HP
|
||||
if blueSlime.ExpPerHP() != expectedExpPerHP {
|
||||
t.Errorf("Expected exp per HP %.2f, got %.2f", expectedExpPerHP, blueSlime.ExpPerHP())
|
||||
}
|
||||
|
||||
// Test GoldPerHP
|
||||
expectedGoldPerHP := float64(1) / float64(4) // Gold / HP
|
||||
if blueSlime.GoldPerHP() != expectedGoldPerHP {
|
||||
t.Errorf("Expected gold per HP %.2f, got %.2f", expectedGoldPerHP, blueSlime.GoldPerHP())
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user