Skip to content

Commit

Permalink
integration: Added integration tests for the game
Browse files Browse the repository at this point in the history
It starts a server + 2 clients and interacts with them to make the
logic run.

It interfaces the client interactions so we can fake clicks on the
client and see
the results later on
  • Loading branch information
xescugc committed Nov 21, 2023
1 parent 4507518 commit 6ee7152
Show file tree
Hide file tree
Showing 26 changed files with 632 additions and 138 deletions.
2 changes: 1 addition & 1 deletion client/action.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package client

import (
"github.com/xescugc/go-flux"
Expand Down
2 changes: 1 addition & 1 deletion client/camera.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package client

import (
"github.com/hajimehoshi/ebiten/v2"
Expand Down
2 changes: 1 addition & 1 deletion client/colors.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package client

import "image/color"

Expand Down
6 changes: 2 additions & 4 deletions client/game.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package client

import (
"image"
Expand All @@ -19,8 +19,6 @@ type Game struct {
Units *Units
Towers *Towers

Map *store.Map

SessionID string
}

Expand All @@ -45,7 +43,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
s := g.Camera.GetState().(CameraState)
op.GeoM.Scale(s.Zoom, s.Zoom)
inverseZoom := maxZoom - s.Zoom + zoomScale
screen.DrawImage(g.Map.GetState().(store.MapState).Image.(*ebiten.Image).SubImage(image.Rect(int(s.X), int(s.Y), int((s.X+s.W)*inverseZoom), int((s.Y+s.H)*inverseZoom))).(*ebiten.Image), op)
screen.DrawImage(g.Store.Map.GetState().(store.MapState).Image.(*ebiten.Image).SubImage(image.Rect(int(s.X), int(s.Y), int((s.X+s.W)*inverseZoom), int((s.Y+s.H)*inverseZoom))).(*ebiten.Image), op)

g.Camera.Draw(screen)
g.HUD.Draw(screen)
Expand Down
27 changes: 14 additions & 13 deletions client/hud.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package client

import (
"bytes"
Expand All @@ -9,11 +9,11 @@ import (
"sort"

"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2/text"
"github.com/xescugc/go-flux"
"github.com/xescugc/ltw/action"
"github.com/xescugc/ltw/assets"
"github.com/xescugc/ltw/inputer"
"github.com/xescugc/ltw/store"
"github.com/xescugc/ltw/tower"
"github.com/xescugc/ltw/unit"
Expand All @@ -30,6 +30,8 @@ type HUDStore struct {
cyclopeFacesetImage image.Image
tilesetHouseImage image.Image
houseIcon image.Image

input inputer.Inputer
}

// HUDState stores the HUD state
Expand All @@ -52,7 +54,7 @@ type SelectedTower struct {
}

// NewHUDStore creates a new HUDStore with the Dispatcher d and the Game g
func NewHUDStore(d *flux.Dispatcher, g *Game) (*HUDStore, error) {
func NewHUDStore(d *flux.Dispatcher, i inputer.Inputer, g *Game) (*HUDStore, error) {
fi, _, err := image.Decode(bytes.NewReader(assets.CyclopeFaceset_png))
if err != nil {
return nil, err
Expand All @@ -74,6 +76,8 @@ func NewHUDStore(d *flux.Dispatcher, g *Game) (*HUDStore, error) {
cyclopeFacesetImage: ebiten.NewImageFromImage(fi),
tilesetHouseImage: ebiten.NewImageFromImage(thi).SubImage(image.Rect(5*16, 17*16, 5*16+16*2, 17*16+16*2)),
houseIcon: ebiten.NewImageFromImage(hi).SubImage(image.Rect(12*16, 0*16, 12*16+16, 0*16+16)),

input: i,
}
cs := g.Camera.GetState().(CameraState)
hs.ReduceStore = flux.NewReduceStore(d, hs.Reduce, HUDState{
Expand Down Expand Up @@ -103,7 +107,7 @@ func NewHUDStore(d *flux.Dispatcher, g *Game) (*HUDStore, error) {
func (hs *HUDStore) Update() error {
cs := hs.game.Camera.GetState().(CameraState)
hst := hs.GetState().(HUDState)
x, y := ebiten.CursorPosition()
x, y := hs.input.CursorPosition()
cp := hs.game.Store.Players.GetCurrentPlayer()
tws := hs.game.Store.Towers.GetTowers()
// Only send a CursorMove when the curso has actually moved
Expand All @@ -116,7 +120,7 @@ func (hs *HUDStore) Update() error {
if cp.Lives == 0 || cp.Winner {
return nil
}
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
if hs.input.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
click := utils.Object{
X: float64(x),
Y: float64(y),
Expand All @@ -129,7 +133,7 @@ func (hs *HUDStore) Update() error {
}
// Check what the user has just clicked
if cp.Gold >= unit.Units[unit.Cyclope.String()].Gold && hst.CyclopeButton.IsColliding(click) {
actionDispatcher.SummonUnit(unit.Cyclope.String(), cp.ID, cp.LineID, hs.game.Map.GetNextLineID(cp.LineID))
actionDispatcher.SummonUnit(unit.Cyclope.String(), cp.ID, cp.LineID, hs.game.Store.Map.GetNextLineID(cp.LineID))
return nil
}
if cp.Gold >= tower.Towers[tower.Soldier.String()].Gold && hst.SoldierButton.IsColliding(click) {
Expand Down Expand Up @@ -193,17 +197,17 @@ func (hs *HUDStore) Update() error {
}
}

if cp.Gold >= tower.Towers[tower.Soldier.String()].Gold && inpututil.IsKeyJustPressed(ebiten.KeyT) {
if cp.Gold >= tower.Towers[tower.Soldier.String()].Gold && hs.input.IsKeyJustPressed(ebiten.KeyT) {
actionDispatcher.SelectTower(tower.Soldier.String(), x, y)
return nil
}
if hst.TowerOpenMenuID != "" {
if inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
if hs.input.IsKeyJustPressed(ebiten.KeyEscape) {
actionDispatcher.CloseTowerMenu()
}
}
if hst.SelectedTower != nil {
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) || inpututil.IsKeyJustPressed(ebiten.KeyEscape) {
if hs.input.IsMouseButtonJustPressed(ebiten.MouseButtonRight) || hs.input.IsKeyJustPressed(ebiten.KeyEscape) {
actionDispatcher.DeselectTower(hst.SelectedTower.Type)
} else {
var invalid bool
Expand All @@ -230,7 +234,7 @@ func (hs *HUDStore) Update() error {
neo := hst.SelectedTower.Object
neo.X += cs.X
neo.Y += cs.Y
if !hs.game.Map.IsInValidBuildingZone(neo, hst.SelectedTower.LineID) {
if !hs.game.Store.Map.IsInValidBuildingZone(neo, hst.SelectedTower.LineID) {
invalid = true
}

Expand Down Expand Up @@ -324,9 +328,6 @@ func (hs *HUDStore) Draw(screen *ebiten.Image) {
text.Draw(screen, fmt.Sprintf("Name: %s, Lives: %d, Gold: %d, Income: %d", p.Name, p.Lives, p.Gold, p.Income), smallFont, 0, 15*pcount, color.White)
pcount++
}
if verbose {
text.Draw(screen, fmt.Sprintf("(X: %d, Y: %d)", int(hst.LastCursorPosition.X+cs.X), int(hst.LastCursorPosition.Y+cs.Y)), smallFont, 0, 15*pcount, color.White)
}
}

func (hs *HUDStore) Reduce(state, a interface{}) interface{} {
Expand Down
91 changes: 65 additions & 26 deletions client/lobby.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package client

import (
"bytes"
Expand All @@ -7,63 +7,75 @@ import (
"sort"

"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
"github.com/hajimehoshi/ebiten/v2/text"
"github.com/xescugc/go-flux"
"github.com/xescugc/ltw/action"
"github.com/xescugc/ltw/assets"
"github.com/xescugc/ltw/inputer"
"github.com/xescugc/ltw/store"
"github.com/xescugc/ltw/utils"
)

type Lobby struct {
type LobbyStore struct {
*flux.ReduceStore

Store *store.Store

Camera *CameraStore
YesBtn image.Image

input inputer.Inputer
}

type LobbyState struct {
YesBtn utils.Object
}

func NewLobby(d *flux.Dispatcher, s *store.Store, c *CameraStore) (*Lobby, error) {
func NewLobbyStore(d *flux.Dispatcher, i inputer.Inputer, s *store.Store, cs *CameraStore) (*LobbyStore, error) {
bi, _, err := image.Decode(bytes.NewReader(assets.YesButton_png))
if err != nil {
return nil, err
}

l := &Lobby{
ls := &LobbyStore{
Store: s,
Camera: c,
Camera: cs,

YesBtn: ebiten.NewImageFromImage(bi),

input: i,
}
return l, nil
cst := cs.GetState().(CameraState)
ls.ReduceStore = flux.NewReduceStore(d, ls.Reduce, LobbyState{
YesBtn: utils.Object{
X: float64(cst.W - float64(ls.YesBtn.Bounds().Dx())),
Y: float64(cst.H - float64(ls.YesBtn.Bounds().Dy())),
W: float64(ls.YesBtn.Bounds().Dx()),
H: float64(ls.YesBtn.Bounds().Dy()),
},
})
return ls, nil
}

func (l *Lobby) Update() error {
l.Camera.Update()
cs := l.Camera.GetState().(CameraState)
x, y := ebiten.CursorPosition()
func (ls *LobbyStore) Update() error {
ls.Camera.Update()
x, y := ls.input.CursorPosition()
lst := ls.GetState().(LobbyState)
// TODO: Fix all this so it's not calculated each time but stored
// the button position
ybtn := utils.Object{
X: float64(cs.W - float64(l.YesBtn.Bounds().Dx())),
Y: float64(cs.H - float64(l.YesBtn.Bounds().Dy())),
W: float64(l.YesBtn.Bounds().Dx()),
H: float64(l.YesBtn.Bounds().Dy()),
}
if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
if ls.input.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
obj := utils.Object{
X: float64(x),
Y: float64(y),
W: 1, H: 1,
}
if ybtn.IsColliding(obj) {
cp := l.Store.Players.GetCurrentPlayer()
if lst.YesBtn.IsColliding(obj) {
cp := ls.Store.Players.GetCurrentPlayer()
actionDispatcher.PlayerReady(cp.ID)
}
}

players := l.Store.Players.GetPlayers()
players := ls.Store.Players.GetPlayers()
if len(players) > 1 {
allReady := true
for _, p := range players {
Expand All @@ -81,9 +93,10 @@ func (l *Lobby) Update() error {
return nil
}

func (l *Lobby) Draw(screen *ebiten.Image) {
cs := l.Camera.GetState().(CameraState)
ps := l.Store.Players.GetPlayers()
func (ls *LobbyStore) Draw(screen *ebiten.Image) {
cs := ls.Camera.GetState().(CameraState)
ps := ls.Store.Players.GetPlayers()
lst := ls.GetState().(LobbyState)
text.Draw(screen, "LOBBY", normalFont, int(cs.W/2), int(cs.H/2), color.White)
var pcount = 1
var sortedPlayers = make([]*store.Player, 0, 0)
Expand All @@ -101,6 +114,32 @@ func (l *Lobby) Draw(screen *ebiten.Image) {
}

ybop := &ebiten.DrawImageOptions{}
ybop.GeoM.Translate(float64(cs.W-float64(l.YesBtn.Bounds().Dx())), float64(cs.H-float64(l.YesBtn.Bounds().Dy())))
screen.DrawImage(l.YesBtn.(*ebiten.Image), ybop)
ybop.GeoM.Translate(lst.YesBtn.X, lst.YesBtn.Y)
screen.DrawImage(ls.YesBtn.(*ebiten.Image), ybop)
}

func (ls *LobbyStore) Reduce(state, a interface{}) interface{} {
act, ok := a.(*action.Action)
if !ok {
return state
}

lstate, ok := state.(LobbyState)
if !ok {
return state
}

switch act.Type {
case action.WindowResizing:
ls.GetDispatcher().WaitFor(ls.Camera.GetDispatcherToken())
cs := ls.Camera.GetState().(CameraState)
lstate.YesBtn = utils.Object{
X: float64(cs.W - float64(ls.YesBtn.Bounds().Dx())),
Y: float64(cs.H - float64(ls.YesBtn.Bounds().Dy())),
W: float64(ls.YesBtn.Bounds().Dx()),
H: float64(ls.YesBtn.Bounds().Dy()),
}
}

return lstate
}
2 changes: 1 addition & 1 deletion client/logger.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package client

import (
"github.com/davecgh/go-spew/spew"
Expand Down
Loading

0 comments on commit 6ee7152

Please sign in to comment.