Add game components 246-268; data.DrawSlot, Canvas.TextVeil
Health, mana and experience bars, quarter hearts, lives, a spinning coin counter, score board, stage timer, minimap with fog, compass, hotbar, paper-doll equipment, skill tree, skill buttons with cooldown wipes, status effects, a boss bar, damage numbers, quest log and tracker, party frames, character sheet, level-up screen and shop. data exports DrawSlot so every slot looks alike; pixel adds TextVeil for dissolving text. Messages the font cannot draw are fixed and TestMessagesHaveGlyphs holds every message to the font.
This commit is contained in:
42 files changed
+3260
-26
No files matched your search
@@ -0,0 +1,208 @@
|
||||
package game_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/data"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/game"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
|
||||
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
|
||||
)
|
||||
|
||||
var bag = []data.Item{{Sprite: icon.Potion, Name: "Potion", Count: 5}, {Sprite: icon.Sword, Name: "Sword"}, {Sprite: icon.Bolt, Name: "Bolt"}, {}, {Sprite: icon.Key, Name: "Key"}}
|
||||
|
||||
func TestHotbarKeysWheelAndClicks(t *testing.T) {
|
||||
sel, changes, used := 0, 0, -1
|
||||
off := false
|
||||
wd := &world{clk: newClock()}
|
||||
tt := view(400, 100, wd, func(c *ui.Context) {
|
||||
r := game.Hotbar(c, "Hotbar", bag, &sel, game.HotbarOptions{Disabled: off})
|
||||
if r.Changed {
|
||||
changes++
|
||||
}
|
||||
if r.Used >= 0 {
|
||||
used = r.Used
|
||||
}
|
||||
})
|
||||
// Slot i is 22 dots wide, its middle at 22i+11.
|
||||
tt.ClickAt(2*(22*2+11), 22)
|
||||
if sel != 2 || used != 2 || changes != 1 {
|
||||
t.Fatalf("click: slot %d, used %d, %d changes", sel, used, changes)
|
||||
}
|
||||
tt.Key(0, ui.Key5)
|
||||
tt.Key(0, ui.Key9)
|
||||
tt.Key(0, ui.KeyLeft)
|
||||
if sel != 3 || changes != 3 {
|
||||
t.Fatalf("5, 9, Left: slot %d, %d changes", sel, changes)
|
||||
}
|
||||
// A notch is a 16-dot line; the bar wraps.
|
||||
tt.Scroll(22, 22, 0, 32)
|
||||
tt.Scroll(22, 22, 0, 32)
|
||||
tt.Scroll(22, 22, 0, -16)
|
||||
if sel != 0 || changes != 5 {
|
||||
t.Fatalf("two notches and a half: slot %d, %d changes", sel, changes)
|
||||
}
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
if used != 0 {
|
||||
t.Fatalf("Enter used %d", used)
|
||||
}
|
||||
keeps(t, tt.Image())
|
||||
off = true
|
||||
tt.Frame()
|
||||
tt.Key(0, ui.Key3)
|
||||
tt.Scroll(22, 22, 0, 32)
|
||||
if sel != 0 {
|
||||
t.Fatalf("a disabled bar moved to %d", sel)
|
||||
}
|
||||
panics(t, "ten slots", func() {
|
||||
view(400, 100, wd, func(c *ui.Context) {
|
||||
game.Hotbar(c, "x", make([]data.Item, 10), &sel, game.HotbarOptions{})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestInertHotbarIgnoresTheWheel(t *testing.T) {
|
||||
sel := 0
|
||||
wd := &world{clk: newClock(), inert: true}
|
||||
tt := view(400, 100, wd, func(c *ui.Context) { game.Hotbar(c, "Hotbar", bag, &sel, game.HotbarOptions{}) })
|
||||
tt.Scroll(22, 22, 0, 32)
|
||||
tt.ClickAt(2*(22*3+11), 22)
|
||||
if sel != 0 {
|
||||
t.Fatalf("an inert bar moved to %d", sel)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEquipmentWalksTheDoll(t *testing.T) {
|
||||
gear := []data.Item{{Sprite: icon.Crown, Name: "Crown"}, {}, {Sprite: icon.Sword, Name: "Iron sword"}, {}, {}}
|
||||
sel, picked := game.Weapon, -1
|
||||
tt := view(400, 200, &world{clk: newClock()}, func(c *ui.Context) {
|
||||
if r := game.Equipment(c, "Gear", gear, &sel, game.EquipmentOptions{}); r.Picked >= 0 {
|
||||
picked = r.Picked
|
||||
}
|
||||
})
|
||||
r := find(t, tt, "Gear")
|
||||
// The doll is centered under the widest caption.
|
||||
w := 3*26 - 4
|
||||
for _, s := range []string{"HEAD: Crown", "BODY", "WEAPON: Iron sword", "SHIELD", "RING"} {
|
||||
w = max(w, pixel.Measure(s))
|
||||
}
|
||||
left := (w - (3*26 - 4)) / 2
|
||||
click := func(x, y int) { tt.ClickAt(r.X+float32(2*(left+x*26+11)), r.Y+float32(2*(y*26+11))) }
|
||||
click(2, 1)
|
||||
if sel != game.Shield || picked != game.Shield {
|
||||
t.Fatalf("clicking the shield slot chose %d, picked %d", sel, picked)
|
||||
}
|
||||
// Left of the doll, nothing is hit.
|
||||
tt.ClickAt(r.X+float32(2*(left-5)), r.Y+float32(2*(26+11)))
|
||||
if sel != game.Shield {
|
||||
t.Fatalf("a click beside the doll chose %d", sel)
|
||||
}
|
||||
for _, step := range []struct {
|
||||
key ui.Key
|
||||
want int
|
||||
}{{ui.KeyLeft, game.Body}, {ui.KeyUp, game.Head}, {ui.KeyLeft, game.Weapon}, {ui.KeyDown, game.Ring}, {ui.KeyUp, game.Weapon}, {ui.KeyRight, game.Body}, {ui.KeyDown, game.Ring}} {
|
||||
tt.Key(0, step.key)
|
||||
if sel != step.want {
|
||||
t.Fatalf("%v went to %d, not %d", step.key, sel, step.want)
|
||||
}
|
||||
}
|
||||
tt.Key(0, ui.KeySpace)
|
||||
if picked != game.Ring {
|
||||
t.Fatalf("Space picked %d", picked)
|
||||
}
|
||||
keeps(t, tt.Image())
|
||||
panics(t, "four slots", func() {
|
||||
view(400, 200, &world{clk: newClock()}, func(c *ui.Context) {
|
||||
game.Equipment(c, "x", gear[:4], &sel, game.EquipmentOptions{})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSkillTreeLearnsOpenSkillsWithPoints(t *testing.T) {
|
||||
skills := []game.Skill{
|
||||
{Name: "Slash", Sprite: icon.Sword, At: game.Spot{X: 1}, Learned: true},
|
||||
{Name: "Guard", Sprite: icon.Shield, At: game.Spot{Y: 1}, Needs: []int{0}},
|
||||
{Name: "Fire", Sprite: icon.Flame, At: game.Spot{X: 2, Y: 1}, Needs: []int{0}, Learned: true},
|
||||
{Name: "Meteor", Sprite: icon.Star, At: game.Spot{X: 2, Y: 2}, Needs: []int{2, 1}},
|
||||
}
|
||||
cur, points, learned := 0, 1, -1
|
||||
tt := view(400, 200, &world{clk: newClock()}, func(c *ui.Context) {
|
||||
if r := game.SkillTree(c, "Skills", skills, &cur, &points, game.SkillTreeOptions{}); r.Learned >= 0 {
|
||||
learned = r.Learned
|
||||
}
|
||||
})
|
||||
r := find(t, tt, "Skills")
|
||||
// Nodes sit 28 dots apart under a 20-dot header.
|
||||
node := func(x, y int) { tt.ClickAt(r.X+float32(2*(x*28+10)), r.Y+float32(2*(20+y*28+10))) }
|
||||
node(1, 0)
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
if points != 1 {
|
||||
t.Fatal("a learned skill took a point")
|
||||
}
|
||||
node(2, 2)
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
if cur != 3 || skills[3].Learned || points != 1 {
|
||||
t.Fatal("a locked skill was learned")
|
||||
}
|
||||
node(0, 1)
|
||||
if cur != 1 || skills[1].Learned {
|
||||
t.Fatal("the first click on a skill learned it")
|
||||
}
|
||||
node(0, 1)
|
||||
if !skills[1].Learned || points != 0 || learned != 1 {
|
||||
t.Fatalf("the second click: learned %d, %d points", learned, points)
|
||||
}
|
||||
tt.Key(0, ui.KeyRight)
|
||||
tt.Key(0, ui.KeyDown)
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
if cur != 3 || skills[3].Learned {
|
||||
t.Fatal("Meteor was learned with no points")
|
||||
}
|
||||
points = 1
|
||||
tt.Key(0, ui.KeyEnter)
|
||||
if !skills[3].Learned || points != 0 || learned != 3 {
|
||||
t.Fatal("Meteor was not learned with a point")
|
||||
}
|
||||
keeps(t, tt.Image())
|
||||
first := 0
|
||||
panics(t, "skills needing each other", func() {
|
||||
view(400, 200, &world{clk: newClock()}, func(c *ui.Context) {
|
||||
game.SkillTree(c, "x", []game.Skill{{Name: "a", Sprite: icon.Star, Needs: []int{1}}, {Name: "b", Sprite: icon.Star, At: game.Spot{X: 1}, Needs: []int{0}}}, &first, &points, game.SkillTreeOptions{})
|
||||
})
|
||||
})
|
||||
panics(t, "a skill off the grid", func() {
|
||||
view(400, 200, &world{clk: newClock()}, func(c *ui.Context) {
|
||||
game.SkillTree(c, "x", []game.Skill{{Name: "a", Sprite: icon.Star, At: game.Spot{X: -1}}}, &first, &points, game.SkillTreeOptions{})
|
||||
})
|
||||
})
|
||||
panics(t, "a skill needing itself", func() {
|
||||
view(400, 200, &world{clk: newClock()}, func(c *ui.Context) {
|
||||
game.SkillTree(c, "x", []game.Skill{{Name: "a", Sprite: icon.Star, Needs: []int{0}}}, &cur, &points, game.SkillTreeOptions{})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestSkillButtonFiresOnlyWhenReady(t *testing.T) {
|
||||
cool, clicks := 0.5, 0
|
||||
tt := view(200, 100, &world{clk: newClock()}, func(c *ui.Context) {
|
||||
if game.SkillButton(c, "Fireball", icon.Flame, "Q", cool, game.SkillButtonOptions{}).Clicked {
|
||||
clicks++
|
||||
}
|
||||
})
|
||||
// The wipe darkens the left half for half a cooldown.
|
||||
wiped := count(t, tt.Image(), 2, 2, 9, 20, pixel.Edge)
|
||||
if err := tt.Click("Fireball"); err != nil || clicks != 0 {
|
||||
t.Fatalf("a cooling skill fired: %v", err)
|
||||
}
|
||||
cool = 0
|
||||
tt.Frame()
|
||||
if n := count(t, tt.Image(), 2, 2, 9, 20, pixel.Edge); wiped < n+50 {
|
||||
t.Fatalf("wipe dots %d, ready %d", wiped, n)
|
||||
}
|
||||
tt.Click("Fireball")
|
||||
if clicks != 1 {
|
||||
t.Fatal("a ready skill did not fire")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user