Skip to content

Commit

Permalink
server: Made the game playable from the server
Browse files Browse the repository at this point in the history
Now the server has an HTML page (the website) which adds the ability
to play
the game from the website, no need to download anything.

Also migrated the WS lib to one that compiles to WASM as if not I was
not able
to run the game on the browser.
  • Loading branch information
xescugc committed Dec 6, 2023
1 parent 4f5ec61 commit 36c8b79
Show file tree
Hide file tree
Showing 30 changed files with 1,101 additions and 87 deletions.
17 changes: 16 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,20 @@ build:
@go build -v ./...

.PHONY: test
test: ## Run the tests
test: wasm ## Run the tests
@xvfb-run go test ./...

.PHONY: serve
serve: wasm ## Starts the server
@go run . server

.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

.PHONY: wa-copy
wa-copy: ## Copy the 'wasm_exec.js' to execute WebAssembly binary
@cp $$(go env GOROOT)/misc/wasm/wasm_exec.js ./server/assets/js/

.PHONY: wasm
wasm: wa-copy wa-build ## Runs all the WASM related commands to have the code ready to run
26 changes: 14 additions & 12 deletions action/action.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package action

import (
"github.com/gorilla/websocket"
"github.com/xescugc/ltw/utils"
"nhooyr.io/websocket"
)

type Action struct {
Expand Down Expand Up @@ -256,22 +256,24 @@ func NewJoinRoom(room, name string) *Action {
}

type AddPlayerPayload struct {
ID string
Name string
LineID int
Websocket *websocket.Conn
Room string
ID string
Name string
LineID int
Websocket *websocket.Conn
RemoteAddr string
Room string
}

func NewAddPlayer(r, id, name string, lid int, ws *websocket.Conn) *Action {
func NewAddPlayer(r, id, name string, lid int, ws *websocket.Conn, ra string) *Action {
return &Action{
Type: AddPlayer,
AddPlayer: &AddPlayerPayload{
ID: id,
Name: name,
LineID: lid,
Websocket: ws,
Room: r,
ID: id,
Name: name,
LineID: lid,
Websocket: ws,
RemoteAddr: ra,
Room: r,
},
}
}
Expand Down
9 changes: 5 additions & 4 deletions client/camera.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const (
zoomScale = 0.5
minZoom = 0
maxZoom = 2
leeway = 50
)

// NewCameraStore creates a new CameraState linked to the Dispatcher d
Expand Down Expand Up @@ -83,15 +84,15 @@ func (cs *CameraStore) Reduce(state, a interface{}) interface{} {
// it means the cursor is moving out of boundaries so we
// increase the camera X/Y at a ratio of the cameraSpeed
// so we move it around on the map
if float64(act.CursorMove.Y) >= cstate.H {
if float64(act.CursorMove.Y) >= (cstate.H - leeway) {
cstate.Y += cs.cameraSpeed
} else if act.CursorMove.Y <= 0 {
} else if act.CursorMove.Y <= (0 + leeway) {
cstate.Y -= cs.cameraSpeed
}

if float64(act.CursorMove.X) >= cstate.W {
if float64(act.CursorMove.X) >= (cstate.W - leeway) {
cstate.X += cs.cameraSpeed
} else if act.CursorMove.X <= 0 {
} else if act.CursorMove.X <= (0 + leeway) {
cstate.X -= cs.cameraSpeed
}

Expand Down
4 changes: 2 additions & 2 deletions client/hud.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ func (hs *HUDStore) Draw(screen *ebiten.Image) {
for _, u := range hst.Units {
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(u.Object.X, u.Object.Y)
if cp.CanSummonUnit(u.Unit.Type.String()) {
if !cp.CanSummonUnit(u.Unit.Type.String()) {
op.ColorM.Scale(2, 0.5, 0.5, 0.9)
}
screen.DrawImage(u.Unit.Faceset.(*ebiten.Image), op)
Expand All @@ -298,7 +298,7 @@ func (hs *HUDStore) Draw(screen *ebiten.Image) {
for _, t := range hst.Towers {
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(t.Object.X, t.Object.Y)
if cp.CanPlaceTower(t.Tower.Type.String()) {
if !cp.CanPlaceTower(t.Tower.Type.String()) {
op.ColorM.Scale(2, 0.5, 0.5, 0.9)
} else if hst.SelectedTower != nil && hst.SelectedTower.Type == t.Tower.Type.String() {
// Once the tower is selected we gray it out
Expand Down
26 changes: 17 additions & 9 deletions client/new.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package client

import (
"context"
"fmt"
"log"
"math/rand"
"net/url"
"time"

"github.com/gorilla/websocket"
"github.com/hajimehoshi/ebiten/v2"
"github.com/xescugc/ltw/action"
"github.com/xescugc/ltw/assets"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
"nhooyr.io/websocket"
"nhooyr.io/websocket/wsjson"
)

var (
Expand Down Expand Up @@ -55,7 +57,7 @@ func init() {
}
}

func New(ad *ActionDispatcher, rs *RouterStore, opt Options) error {
func New(ctx context.Context, ad *ActionDispatcher, rs *RouterStore, opt Options) error {
ebiten.SetWindowTitle("LTW")
ebiten.SetWindowSize(opt.ScreenW*2, opt.ScreenH*2)
ebiten.SetWindowResizingMode(ebiten.WindowResizingModeEnabled)
Expand All @@ -67,18 +69,21 @@ func New(ad *ActionDispatcher, rs *RouterStore, opt Options) error {
u := url.URL{Scheme: "ws", Host: opt.HostURL, Path: "/ws"}

var err error
wsc, _, err = websocket.DefaultDialer.Dial(u.String(), nil)

wsc, _, err = websocket.Dial(ctx, u.String(), nil)
if err != nil {
return fmt.Errorf("failed to dial the server %q: %w", u.String(), err)
}
defer wsc.Close()

go wsHandler()
wsc.SetReadLimit(-1)

err = wsc.WriteJSON(action.NewJoinRoom(opt.Room, opt.Name))
err = wsjson.Write(ctx, wsc, action.NewJoinRoom(opt.Room, opt.Name))
if err != nil {
return fmt.Errorf("failed to write JSON: %w", err)
}
defer wsc.CloseNow()

go wsHandler(ctx)

err = ebiten.RunGame(rs)
if err != nil {
Expand All @@ -88,10 +93,13 @@ func New(ad *ActionDispatcher, rs *RouterStore, opt Options) error {
return nil
}

func wsHandler() {
func run(ctx context.Context, rs *RouterStore, u string, opt Options) {
}

func wsHandler(ctx context.Context) {
for {
var act *action.Action
err := wsc.ReadJSON(&act)
err := wsjson.Read(ctx, wsc, &act)
if err != nil {
// TODO remove from the Room
log.Fatal(err)
Expand All @@ -103,7 +111,7 @@ func wsHandler() {

func wsSend(a *action.Action) {
a.Room = room
err := wsc.WriteJSON(a)
err := wsjson.Write(context.Background(), wsc, a)
if err != nil {
log.Fatal(err)
}
Expand Down
88 changes: 88 additions & 0 deletions client/wasm/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//go:build js && wasm

package main

import (
"context"
"fmt"
"log"
"syscall/js"

"github.com/xescugc/go-flux"
"github.com/xescugc/ltw/client"
"github.com/xescugc/ltw/inputer"
"github.com/xescugc/ltw/store"
)

func main() {
js.Global().Set("new_client", NewClient())
select {}
}

func NewClient() js.Func {
return js.FuncOf(func(this js.Value, args []js.Value) any {
if len(args) != 2 || (args[0].String() == "" || args[1].String() == "") {
return fmt.Errorf("requires 2 parameters: room and name")
}
var (
err error
room = args[0].String()
name = args[1].String()
hostURL = "localhost:5555"
screenW = 288
screenH = 240
)

d := flux.NewDispatcher()
ad := client.NewActionDispatcher(d)

s := store.NewStore(d)

g := &client.Game{
Store: s,
}

i := inputer.NewEbiten()

// TODO: Change this to pass the specific store needed instead of all the game object
cs := client.NewCameraStore(d, s, screenW, screenH)
g.Camera = cs
g.Units, err = client.NewUnits(g)
if err != nil {
return fmt.Errorf("failed to initialize Units: %w", err)
}

g.Towers, err = client.NewTowers(g)
if err != nil {
return fmt.Errorf("failed to initialize Towers: %w", err)
}

g.HUD, err = client.NewHUDStore(d, i, g)
if err != nil {
return fmt.Errorf("failed to initialize HUDStore: %w", err)
}

l, err := client.NewLobbyStore(d, i, s, cs)
if err != nil {
return fmt.Errorf("failed to initialize LobbyStore: %w", err)
}
rs := client.NewRouterStore(d, g, l)

ctx := context.Background()
// We need to run this in a goroutine so when it's compiled to WASM
// it does not block the main thread https://github.com/golang/go/issues/41310
go func() {
err = client.New(ctx, ad, rs, client.Options{
HostURL: hostURL,
Room: room,
Name: name,
ScreenW: screenW,
ScreenH: screenH,
})
if err != nil {
log.Fatal(err)
}
}()
return nil
})
}
4 changes: 3 additions & 1 deletion cmd/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"context"
"fmt"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -56,8 +57,9 @@ var (
return fmt.Errorf("failed to initialize LobbyStore: %w", err)
}
rs := client.NewRouterStore(d, g, l)
ctx := context.Background()

err = client.New(ad, rs, client.Options{
err = client.New(ctx, ad, rs, client.Options{
HostURL: hostURL,
Room: room,
Name: name,
Expand Down
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
module github.com/xescugc/ltw

go 1.17
go 1.21

require (
github.com/davecgh/go-spew v1.1.0
github.com/gofrs/uuid v4.4.0+incompatible
github.com/golang/mock v1.6.0
github.com/gorilla/websocket v1.5.0
github.com/gorilla/handlers v1.5.2
github.com/gorilla/mux v1.8.1
github.com/hajimehoshi/ebiten/v2 v2.5.9
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.7.0
github.com/xescugc/go-flux v1.0.1
golang.org/x/image v0.10.0
nhooyr.io/websocket v1.8.10
)

require (
github.com/ebitengine/purego v0.4.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jezek/xgb v1.1.0 // indirect
Expand Down
Loading

0 comments on commit 36c8b79

Please sign in to comment.