- potions.c completed: quaff with all 14 potion effects, the PACT standard-fuse table (do_pot), raise_level; the see-invisible fruit- juice message is computed at quaff time as in C - scrolls.c in full: read_scroll with all 18 scroll effects including the magic-mapping map rewrite and identify dispatch, uncurse - options.c in full: the interactive options screen, get_bool/get_str/ get_inv_t/get_sf editors, ROGUEOPTS parsing (prefix matching, no- prefixed booleans, ~ expansion), strucpy - misc.c call_it via the get_str line editor - turn_see gets a DaemonID (C casts it to a fuse callback for the monster-detection potion) Tests: healing/confusion potions with fuse burn-down, enchant armor, hold monster, slow-monster zap, ROGUEOPTS parsing, and a regression test documenting the C wake_monster ISGREED/ISHELD quirk the port keeps faithfully.
171 lines
4.4 KiB
Go
171 lines
4.4 KiB
Go
package game
|
|
|
|
import "testing"
|
|
|
|
// mkGameInput builds a headless game whose input plays the given script.
|
|
func mkGameInput(t *testing.T, seed int32, input string) *RogueGame {
|
|
t.Helper()
|
|
g := NewGame(Config{Seed: seed, Term: &testTerm{input: []byte(input)}})
|
|
g.NewLevel()
|
|
g.Oldpos = g.Player.Pos
|
|
g.Oldrp = g.roomin(g.Player.Pos)
|
|
return g
|
|
}
|
|
|
|
// give puts an object straight into the pack and returns its pack letter.
|
|
func give(g *RogueGame, obj *Object) byte {
|
|
obj.Count = 1
|
|
g.addPack(obj, true)
|
|
return obj.PackCh
|
|
}
|
|
|
|
func TestQuaffHealingPotion(t *testing.T) {
|
|
g := mkGameInput(t, 5, "")
|
|
pot := newObject()
|
|
pot.Type = Potion
|
|
pot.Which = PHealing
|
|
ch := give(g, pot)
|
|
g.scr.term.(*testTerm).input = []byte{ch}
|
|
|
|
g.Player.Stats.HP = 1
|
|
g.quaff()
|
|
if g.Player.Stats.HP <= 1 {
|
|
t.Error("healing potion did not heal")
|
|
}
|
|
if !g.Items.Potions[PHealing].Know {
|
|
t.Error("healing potion not identified after drinking")
|
|
}
|
|
if len(g.Player.Pack) != 5 {
|
|
t.Errorf("potion not consumed: %d items", len(g.Player.Pack))
|
|
}
|
|
}
|
|
|
|
func TestQuaffConfusionSetsFlagAndFuse(t *testing.T) {
|
|
g := mkGameInput(t, 5, "")
|
|
pot := newObject()
|
|
pot.Type = Potion
|
|
pot.Which = PConfuse
|
|
ch := give(g, pot)
|
|
g.scr.term.(*testTerm).input = []byte{ch}
|
|
|
|
g.quaff()
|
|
if !g.Player.On(IsHuh) {
|
|
t.Error("confusion potion did not confuse")
|
|
}
|
|
if g.findSlot(DUnconfuse) == nil {
|
|
t.Error("no unconfuse fuse pending")
|
|
}
|
|
// Let the fuse burn down
|
|
for range 30 {
|
|
g.DoFuses(After)
|
|
}
|
|
if g.Player.On(IsHuh) {
|
|
t.Error("confusion never wore off")
|
|
}
|
|
}
|
|
|
|
func TestReadEnchantArmor(t *testing.T) {
|
|
g := mkGameInput(t, 5, "")
|
|
scr := newObject()
|
|
scr.Type = Scroll
|
|
scr.Which = SArmor
|
|
ch := give(g, scr)
|
|
g.scr.term.(*testTerm).input = []byte{ch}
|
|
|
|
before := g.Player.CurArmor.Arm
|
|
g.readScroll()
|
|
if g.Player.CurArmor.Arm != before-1 {
|
|
t.Errorf("enchant armor: AC %d -> %d, want %d",
|
|
before, g.Player.CurArmor.Arm, before-1)
|
|
}
|
|
}
|
|
|
|
func TestReadHoldMonsterFreezesAdjacent(t *testing.T) {
|
|
// Note: this must not use a greedy monster ('O' orc, ISGREED): the C
|
|
// wake_monster gold-guarding check has no ISHELD guard, so the
|
|
// look(TRUE) at the end of read_scroll immediately re-wakes greedy
|
|
// monsters. The port reproduces that quirk faithfully — see
|
|
// TestHoldScrollGreedyMonsterQuirk.
|
|
g := mkGameInput(t, 5, "")
|
|
tp := spawnAdjacent(g, 'Z')
|
|
tp.Flags.Set(IsRun)
|
|
scr := newObject()
|
|
scr.Type = Scroll
|
|
scr.Which = SHold
|
|
ch := give(g, scr)
|
|
g.scr.term.(*testTerm).input = []byte{ch}
|
|
|
|
g.readScroll()
|
|
t.Logf("after scroll: flags=%o huh=%q", tp.Flags, g.Msgs.Huh)
|
|
if tp.On(IsRun) || !tp.On(IsHeld) {
|
|
t.Error("hold monster scroll did not hold the adjacent monster")
|
|
}
|
|
}
|
|
|
|
// TestHoldScrollGreedyMonsterQuirk documents a C behavior the port keeps:
|
|
// wake_monster's ISGREED branch lacks an ISHELD guard, so a greedy monster
|
|
// (orc) held by a scroll is re-woken by the look(TRUE) that read_scroll
|
|
// performs, ending up both held and running again.
|
|
func TestHoldScrollGreedyMonsterQuirk(t *testing.T) {
|
|
g := mkGameInput(t, 5, "")
|
|
tp := spawnAdjacent(g, 'O')
|
|
tp.Flags.Set(IsRun)
|
|
scr := newObject()
|
|
scr.Type = Scroll
|
|
scr.Which = SHold
|
|
ch := give(g, scr)
|
|
g.scr.term.(*testTerm).input = []byte{ch}
|
|
|
|
g.readScroll()
|
|
t.Logf("orc after scroll: flags=%o (IsRun=%v IsHeld=%v)",
|
|
tp.Flags, tp.On(IsRun), tp.On(IsHeld))
|
|
if !tp.On(IsHeld) {
|
|
t.Error("orc lost IsHeld entirely")
|
|
}
|
|
if !tp.On(IsRun) {
|
|
t.Error("quirk changed: greedy monster stayed held; if this is a " +
|
|
"deliberate fix, update this test and ARCHITECTURE.md")
|
|
}
|
|
}
|
|
|
|
func TestZapSlowMonster(t *testing.T) {
|
|
g := mkGameInput(t, 5, "")
|
|
tp := spawnAdjacent(g, 'Z')
|
|
stick := newObject()
|
|
stick.Type = Stick
|
|
stick.Which = WsSlowM
|
|
g.fixStick(stick)
|
|
ch := give(g, stick)
|
|
g.scr.term.(*testTerm).input = []byte{ch}
|
|
g.Delta = Coord{X: 1, Y: 0} // aim at the monster
|
|
|
|
charges := stick.Charges()
|
|
g.doZap()
|
|
if !tp.On(IsSlow) {
|
|
t.Error("slow monster wand did not slow")
|
|
}
|
|
if stick.Charges() != charges-1 {
|
|
t.Error("zap did not use a charge")
|
|
}
|
|
}
|
|
|
|
func TestParseOpts(t *testing.T) {
|
|
g := NewGame(Config{Seed: 1})
|
|
g.ParseOpts("terse,nojump,name=Conan,fruit=mango,inven=slow")
|
|
if !g.Options.Terse {
|
|
t.Error("terse not set")
|
|
}
|
|
if g.Options.Jump {
|
|
t.Error("nojump not honored")
|
|
}
|
|
if g.Whoami != "Conan" {
|
|
t.Errorf("name = %q", g.Whoami)
|
|
}
|
|
if g.Fruit != "mango" {
|
|
t.Errorf("fruit = %q", g.Fruit)
|
|
}
|
|
if g.Options.InvType != InvSlow {
|
|
t.Errorf("inven = %d", g.Options.InvType)
|
|
}
|
|
}
|