diff --git a/entity/item.go b/entity/item.go new file mode 100644 index 0000000..837a5a1 --- /dev/null +++ b/entity/item.go @@ -0,0 +1,36 @@ +package entity + +type ItemType int + +const ( + ItemWeapon ItemType = iota + ItemArmor + ItemConsumable +) + +type Item struct { + Name string + Type ItemType + Bonus int + Price int +} + +type RelicEffect int + +const ( + RelicHealOnKill RelicEffect = iota + RelicATKBoost + RelicDEFBoost + RelicGoldBoost +) + +type Relic struct { + Name string + Effect RelicEffect + Value int + Price int +} + +func NewHPPotion() Item { + return Item{Name: "HP Potion", Type: ItemConsumable, Bonus: 30, Price: 20} +} diff --git a/entity/monster.go b/entity/monster.go new file mode 100644 index 0000000..85e0e48 --- /dev/null +++ b/entity/monster.go @@ -0,0 +1,83 @@ +package entity + +import "math" + +type MonsterType int + +const ( + MonsterSlime MonsterType = iota + MonsterSkeleton + MonsterOrc + MonsterDarkKnight + MonsterBoss5 + MonsterBoss10 + MonsterBoss15 + MonsterBoss20 +) + +type monsterBase struct { + Name string + HP, ATK, DEF int + MinFloor int + IsBoss bool +} + +var monsterDefs = map[MonsterType]monsterBase{ + MonsterSlime: {"Slime", 20, 5, 1, 1, false}, + MonsterSkeleton: {"Skeleton", 35, 10, 4, 3, false}, + MonsterOrc: {"Orc", 55, 14, 6, 6, false}, + MonsterDarkKnight: {"Dark Knight", 80, 18, 10, 12, false}, + MonsterBoss5: {"Guardian", 150, 15, 8, 5, true}, + MonsterBoss10: {"Warden", 250, 22, 12, 10, true}, + MonsterBoss15: {"Overlord", 400, 30, 16, 15, true}, + MonsterBoss20: {"Archlich", 600, 40, 20, 20, true}, +} + +type Monster struct { + Name string + Type MonsterType + HP, MaxHP int + ATK, DEF int + IsBoss bool + TauntTarget bool + TauntTurns int +} + +func NewMonster(mt MonsterType, floor int) *Monster { + base := monsterDefs[mt] + scale := 1.0 + if !base.IsBoss && floor > base.MinFloor { + scale = math.Pow(1.15, float64(floor-base.MinFloor)) + } + hp := int(math.Round(float64(base.HP) * scale)) + atk := int(math.Round(float64(base.ATK) * scale)) + return &Monster{ + Name: base.Name, + Type: mt, + HP: hp, + MaxHP: hp, + ATK: atk, + DEF: base.DEF, + IsBoss: base.IsBoss, + } +} + +func (m *Monster) TakeDamage(dmg int) { + m.HP -= dmg + if m.HP < 0 { + m.HP = 0 + } +} + +func (m *Monster) IsDead() bool { + return m.HP <= 0 +} + +func (m *Monster) TickTaunt() { + if m.TauntTurns > 0 { + m.TauntTurns-- + if m.TauntTurns == 0 { + m.TauntTarget = false + } + } +} diff --git a/entity/monster_test.go b/entity/monster_test.go new file mode 100644 index 0000000..a230e15 --- /dev/null +++ b/entity/monster_test.go @@ -0,0 +1,25 @@ +package entity + +import ( + "testing" + "math" +) + +func TestMonsterScaling(t *testing.T) { + slime := NewMonster(MonsterSlime, 1) + if slime.HP != 20 || slime.ATK != 5 { + t.Errorf("Slime floor 1: got HP=%d ATK=%d, want HP=20 ATK=5", slime.HP, slime.ATK) + } + slimeF3 := NewMonster(MonsterSlime, 3) + expectedHP := int(math.Round(20 * math.Pow(1.15, 2))) + if slimeF3.HP != expectedHP { + t.Errorf("Slime floor 3: got HP=%d, want %d", slimeF3.HP, expectedHP) + } +} + +func TestBossStats(t *testing.T) { + boss := NewMonster(MonsterBoss5, 5) + if boss.HP != 150 || boss.ATK != 15 || boss.DEF != 8 { + t.Errorf("Boss5: got HP=%d ATK=%d DEF=%d, want 150/15/8", boss.HP, boss.ATK, boss.DEF) + } +} diff --git a/entity/player.go b/entity/player.go new file mode 100644 index 0000000..ccb2628 --- /dev/null +++ b/entity/player.go @@ -0,0 +1,96 @@ +package entity + +type Class int + +const ( + ClassWarrior Class = iota + ClassMage + ClassHealer + ClassRogue +) + +func (c Class) String() string { + return [...]string{"Warrior", "Mage", "Healer", "Rogue"}[c] +} + +type classStats struct { + HP, ATK, DEF int +} + +var classBaseStats = map[Class]classStats{ + ClassWarrior: {120, 12, 8}, + ClassMage: {70, 20, 3}, + ClassHealer: {90, 8, 5}, + ClassRogue: {85, 15, 4}, +} + +type Player struct { + Name string + Fingerprint string + Class Class + HP, MaxHP int + ATK, DEF int + Gold int + Inventory []Item + Relics []Relic + Dead bool +} + +func NewPlayer(name string, class Class) *Player { + stats := classBaseStats[class] + return &Player{ + Name: name, + Class: class, + HP: stats.HP, + MaxHP: stats.HP, + ATK: stats.ATK, + DEF: stats.DEF, + } +} + +func (p *Player) TakeDamage(dmg int) { + p.HP -= dmg + if p.HP <= 0 { + p.HP = 0 + p.Dead = true + } +} + +func (p *Player) Heal(amount int) { + p.HP += amount + if p.HP > p.MaxHP { + p.HP = p.MaxHP + } +} + +func (p *Player) IsDead() bool { + return p.Dead +} + +func (p *Player) Revive(hpPercent float64) { + p.Dead = false + p.HP = int(float64(p.MaxHP) * hpPercent) + if p.HP < 1 { + p.HP = 1 + } +} + +func (p *Player) EffectiveATK() int { + atk := p.ATK + for _, item := range p.Inventory { + if item.Type == ItemWeapon { + atk += item.Bonus + } + } + return atk +} + +func (p *Player) EffectiveDEF() int { + def := p.DEF + for _, item := range p.Inventory { + if item.Type == ItemArmor { + def += item.Bonus + } + } + return def +} diff --git a/entity/player_test.go b/entity/player_test.go new file mode 100644 index 0000000..7b87460 --- /dev/null +++ b/entity/player_test.go @@ -0,0 +1,53 @@ +package entity + +import "testing" + +func TestNewPlayer(t *testing.T) { + p := NewPlayer("testuser", ClassWarrior) + if p.HP != 120 || p.MaxHP != 120 { + t.Errorf("Warrior HP: got %d, want 120", p.HP) + } + if p.ATK != 12 { + t.Errorf("Warrior ATK: got %d, want 12", p.ATK) + } + if p.DEF != 8 { + t.Errorf("Warrior DEF: got %d, want 8", p.DEF) + } + if p.Gold != 0 { + t.Errorf("Initial gold: got %d, want 0", p.Gold) + } +} + +func TestAllClasses(t *testing.T) { + tests := []struct { + class Class + hp, atk, def int + }{ + {ClassWarrior, 120, 12, 8}, + {ClassMage, 70, 20, 3}, + {ClassHealer, 90, 8, 5}, + {ClassRogue, 85, 15, 4}, + } + for _, tt := range tests { + p := NewPlayer("test", tt.class) + if p.HP != tt.hp || p.ATK != tt.atk || p.DEF != tt.def { + t.Errorf("Class %v: got HP=%d ATK=%d DEF=%d, want HP=%d ATK=%d DEF=%d", + tt.class, p.HP, p.ATK, p.DEF, tt.hp, tt.atk, tt.def) + } + } +} + +func TestPlayerTakeDamage(t *testing.T) { + p := NewPlayer("test", ClassWarrior) + p.TakeDamage(30) + if p.HP != 90 { + t.Errorf("HP after 30 dmg: got %d, want 90", p.HP) + } + p.TakeDamage(200) + if p.HP != 0 { + t.Errorf("HP should not go below 0: got %d", p.HP) + } + if !p.IsDead() { + t.Error("Player should be dead") + } +}