Skip to content

Commit

Permalink
all: BIG refactor by moving the Towers+Units into Lines
Browse files Browse the repository at this point in the history
This Lines also have an improved logic to alcaulate the path which improves it much
significntly at least 16 times faster.

This change was needed as the AStar was taking up to 900ms now takes 1-2ms and the Graph
behind it now offer other helpers that are much more faster in terms of calculation
  • Loading branch information
xescugc committed Mar 2, 2024
1 parent 4c8f262 commit a901fb1
Show file tree
Hide file tree
Showing 33 changed files with 2,110 additions and 1,142 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ dc-serve: ## Starts the server using docker-compose
serve: wasm ## Starts the server
@go run ./cmd/server

.PHONY: client
client: ## Runs a client
@go run ./cmd/client

.PHONY: wa-build
wa-build: ## Build the wasm Game
@env GOOS=js GOARCH=wasm go build -o ./server/assets/wasm/maze-wars.wasm ./client/wasm
Expand Down
4 changes: 4 additions & 0 deletions Notes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Map

A tail == 16px

The maps are in an offset of 43 tails

Each line 18t wide, 16t of those are usable and the other 2t are the left and right corners

The spawn area is 7tx16t and the end area is 3tx16t the building area is 74tx16t
Expand Down
28 changes: 14 additions & 14 deletions action/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package action

import (
"github.com/xescugc/maze-wars/utils"
"github.com/xescugc/maze-wars/utils/graph"
"nhooyr.io/websocket"
)

Expand Down Expand Up @@ -474,8 +475,16 @@ func NewSyncWaitingRoom(tp, s, cd int) *Action {

type SyncStatePayload struct {
Players *SyncStatePlayersPayload
Towers *SyncStateTowersPayload
Units *SyncStateUnitsPayload
Lines *SyncStateLinesPayload
}

type SyncStateLinesPayload struct {
Lines map[int]*SyncStateLinePayload
}

type SyncStateLinePayload struct {
Towers map[string]*SyncStateTowerPayload
Units map[string]*SyncStateUnitPayload
}

type SyncStatePlayersPayload struct {
Expand All @@ -494,10 +503,6 @@ type SyncStatePlayerPayload struct {
Winner bool
}

type SyncStateTowersPayload struct {
Towers map[string]*SyncStateTowerPayload
}

type SyncStateTowerPayload struct {
utils.Object

Expand All @@ -507,10 +512,6 @@ type SyncStateTowerPayload struct {
PlayerID string
}

type SyncStateUnitsPayload struct {
Units map[string]*SyncStateUnitPayload
}

type SyncStateUnitPayload struct {
utils.MovingObject

Expand All @@ -522,18 +523,17 @@ type SyncStateUnitPayload struct {

Health float64

Path []utils.Step
Path []graph.Step
HashPath string
}

// TODO: or make the action.Action separated or make the store.Player separated
func NewSyncState(players *SyncStatePlayersPayload, towers *SyncStateTowersPayload, units *SyncStateUnitsPayload) *Action {
func NewSyncState(players *SyncStatePlayersPayload, lines *SyncStateLinesPayload) *Action {
return &Action{
Type: SyncState,
SyncState: &SyncStatePayload{
Players: players,
Towers: towers,
Units: units,
Lines: lines,
},
}
}
Expand Down
15 changes: 4 additions & 11 deletions client/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,9 @@ func (ac *ActionDispatcher) CameraZoom(d int) {

// PlaceTower places the tower 't' on the position X and Y of the player pid
func (ac *ActionDispatcher) PlaceTower(t, pid string, x, y int) {
units := ac.store.Units.List()
to := utils.Object{X: float64(x), Y: float64(y), H: 32, W: 32}
canPlace := true
for _, u := range units {
if u.IsColliding(to) {
canPlace = false
break
}
}
if canPlace {
// TODO: Add the LineID in the action
p := ac.store.Players.FindByID(pid)
if l := ac.store.Lines.FindByID(p.LineID); l != nil && l.Graph.CanAddTower(x, y, 32, 32) {
pta := action.NewPlaceTower(t, pid, x, y)
wsSend(pta)
}
Expand All @@ -126,7 +119,7 @@ func (ac *ActionDispatcher) SelectedTowerInvalid(i bool) {
ac.Dispatch(sta)
}

// DeelectTower cleans the current selected tower
// DeselectTower cleans the current selected tower
func (ac *ActionDispatcher) DeselectTower(t string) {
dsta := action.NewDeselectTower(t)
ac.Dispatch(dsta)
Expand Down
10 changes: 3 additions & 7 deletions client/game.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ type Game struct {

Camera *CameraStore
HUD *HUDStore

Units *Units
Towers *Towers
Lines *Lines

Map *Map

Expand All @@ -39,8 +37,7 @@ func (g *Game) Update() error {

g.Map.Update()
g.Camera.Update()
g.Units.Update()
g.Towers.Update()
g.Lines.Update()
g.HUD.Update()

actionDispatcher.TPS()
Expand All @@ -55,6 +52,5 @@ func (g *Game) Draw(screen *ebiten.Image) {
g.Map.Draw(screen)
g.Camera.Draw(screen)
g.HUD.Draw(screen)
g.Units.Draw(screen)
g.Towers.Draw(screen)
g.Lines.Draw(screen)
}
101 changes: 11 additions & 90 deletions client/hud.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,14 @@ func (hs *HUDStore) Update() error {
hst := hs.GetState().(HUDState)
x, y := ebiten.CursorPosition()
cp := hs.game.Store.Players.FindCurrent()
tws := hs.game.Store.Towers.List()
cl := hs.game.Store.Lines.FindByID(cp.LineID)
tws := cl.Towers
// Only send a CursorMove when the curso has actually moved
if hst.LastCursorPosition.X != float64(x) || hst.LastCursorPosition.Y != float64(y) {
actionDispatcher.CursorMove(x, y)
}
// If the Current player is dead or has no more lives there are no
// mo actions that can be done
// TODO Be able to move the camera when won or lose
if cp.Lives == 0 || cp.Winner {
return nil
}
Expand All @@ -124,32 +124,7 @@ func (hs *HUDStore) Update() error {
}

if hst.SelectedTower != nil && !hst.SelectedTower.Invalid {
// We double check that placing the tower would not block the path
utws := make([]utils.Object, 0, 0)
for _, t := range tws {
// If the tower does not belong to the current user then we can skip
// as it's outside the Players Building Zone
if t.PlayerID != cp.ID {
continue
}
utws = append(utws, t.Object)
}
var fakex, fakey float64 = hs.game.Store.Map.GetRandomSpawnCoordinatesForLineID(cp.LineID)
utws = append(utws, utils.Object{
X: hst.SelectedTower.X + cs.X,
Y: hst.SelectedTower.Y + cs.Y,
H: hst.SelectedTower.H, W: hst.SelectedTower.W,
})
steps := hs.game.Store.Units.Astar(hs.game.Store.Map, cp.LineID, utils.MovingObject{
Object: utils.Object{
X: fakex,
Y: fakey,
W: 1, H: 1,
},
}, utws)
if len(steps) != 0 {
actionDispatcher.PlaceTower(hst.SelectedTower.Type, cp.ID, int(hst.SelectedTower.X+cs.X), int(hst.SelectedTower.Y+cs.Y))
}
actionDispatcher.PlaceTower(hst.SelectedTower.Type, cp.ID, int(hst.SelectedTower.X+cs.X), int(hst.SelectedTower.Y+cs.Y))
return nil
}
for _, t := range tws {
Expand Down Expand Up @@ -200,69 +175,22 @@ func (hs *HUDStore) Update() error {
actionDispatcher.DeselectTower(hst.SelectedTower.Type)
} else {
var invalid bool
utws := make([]utils.Object, 0, 0)
// TODO: Check why the selected towers can be placed in top of other already palced towers
for _, t := range tws {
// If the tower does not belong to the current user then we can skip
// as it's outside the Players Building Zone
if t.PlayerID != cp.ID {
continue
}
utws = append(utws, t.Object)
// The t.Object has the X and Y relative to the map
// and the hst.SelectedTower has them relative to the
// screen so we need to port the t.Object to the same
// relative values
neo := t.Object
neo.X -= cs.X
neo.Y -= cs.Y
if hst.SelectedTower.IsColliding(neo) {
invalid = true
break
}
}

neo := hst.SelectedTower.Object
neo.X += cs.X
neo.Y += cs.Y
if !hs.game.Store.Map.IsInValidBuildingZone(neo, hst.SelectedTower.LineID) {
invalid = true
}

invalid = !cl.Graph.CanAddTower(int(neo.X), int(neo.Y), int(neo.W), int(neo.H))

if !invalid {
units := hs.game.Store.Units.List()
for _, u := range units {
if u.PlayerID != cp.ID {
continue
}
for _, u := range cl.Units {
if u.IsColliding(neo) {
invalid = true
break
}
}
}

// Only check if the line is blocked when is still valid position and it has not moved.
// TODO: We can improve this by storing this result (if blocking or not) so we only validate
// this once and not when the mouse is static with a selected tower
if !invalid && (hst.LastCursorPosition.X == float64(x) && hst.LastCursorPosition.Y == float64(y) && !hst.CheckedPath) {
//var fakex, fakey float64 = hs.game.Store.Map.GetRandomSpawnCoordinatesForLineID(cp.LineID)
//utws = append(utws, utils.Object{
//X: hst.SelectedTower.X + cs.X,
//Y: hst.SelectedTower.Y + cs.Y,
//H: hst.SelectedTower.H, W: hst.SelectedTower.W,
//})
//steps := hs.game.Store.Units.Astar(hs.game.Store.Map, cp.LineID, utils.MovingObject{
//Object: utils.Object{
//X: fakex,
//Y: fakey,
//W: 1, H: 1,
//},
//}, utws)
//if len(steps) == 0 {
//invalid = true
//}
//actionDispatcher.CheckedPath(true)
}
if invalid != hst.SelectedTower.Invalid {
actionDispatcher.SelectedTowerInvalid(invalid)
}
Expand Down Expand Up @@ -440,24 +368,24 @@ func (hs *HUDStore) Reduce(state, a interface{}) interface{} {
}

func fixPosition(cs CameraState, x, y float64) (float64, float64) {
//cs := hs.game.Camera.GetState().(CameraState)

absnx := x + cs.X
absny := y + cs.Y
// We find the closes multiple in case the cursor moves too fast, between FPS reloads,
// and lands in a position not 'multiple' which means the position of the SelectedTower
// is not updated and the result is the cursor far away from the Drawing of the SelectedTower
// as it has stayed on the previous position
var multiple int = 16
// If it's == 0 means it's exact but as we want to center it we remove 16 (towers are 32)
// If it's !=0 then we find what's the remaning for
if int(absnx)%multiple == 0 {
x -= 16
} else {
x = float64(closestMultiple(int(absnx), multiple)) - 16 - cs.X
x = float64(utils.ClosestMultiple(int(absnx), multiple)) - 16 - cs.X
}
if int(absny)%multiple == 0 {
y -= 16
} else {
y = float64(closestMultiple(int(absny), multiple)) - 16 - cs.Y
y = float64(utils.ClosestMultiple(int(absny), multiple)) - 16 - cs.Y
}

return x, y
Expand Down Expand Up @@ -485,13 +413,6 @@ func sortedTowers() []*tower.Tower {
return ts
}

// closestMultiple finds the coses multiple of 'b' for the number 'a'
func closestMultiple(a, b int) int {
a = a + b/2
a = a - (a % b)
return a
}

func (hs *HUDStore) buildUI() {
topRightContainer := widget.NewContainer(
widget.ContainerOpts.Layout(widget.NewAnchorLayout()),
Expand Down
Loading

0 comments on commit a901fb1

Please sign in to comment.