Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion golib/buildclient.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ popd

pushd $GOPATH/src/github.com/jerold/Catan/golib/client/bindings
gopherjs build -m
du -lah bindings.js
du -h bindings.js
mv bindings.js ../../../lib/src
popd
2 changes: 2 additions & 0 deletions golib/catannet/cn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func TestPerformance(t *testing.T) {
}

for i := 0; i < 100; i++ {
b.Pieces[i] = &PieceLocation{}
b.Pieces[i].Location = &Coordinate{
X: 1054,
Y: 4538,
Expand All @@ -23,6 +24,7 @@ func TestPerformance(t *testing.T) {
}
}
for i := 0; i < 50; i++ {
b.Tiles[i] = &Tile{}
b.Tiles[i].Location = &Coordinate{
X: 5354,
Y: 58459,
Expand Down
59 changes: 51 additions & 8 deletions golib/catannet/messages.ng
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,77 @@ struct Heartbeat {
}

struct SaveGameRequest {
ID int32 // Optional ID
ID GameID // Optional ID
Revision int32
Board *GameBoard
Players []*Player
}

struct SaveGameResponse {
ID int32
ID GameID
}

struct LoadGameRequest {
ID int32
ID GameID
}

struct LoadGameResponse {
ID int32
ID GameID
Board *GameBoard
Players []*Player
}

// Listen to a game ID for changes.
struct JoinGame {
ID int32 // ID of game to listen to
PlayerID int32 // Player to take over. -1 means server will assign you next available.
Name string // Name of yourself
}

// Event is sent to all listeners
struct BoardEvent {
ID GameID
Ops []*BoardOperation
}

struct PlayerEvent {
ID GameID
}

// Game State Structs

// Board Operation could be a change to tile, edge, or plot
struct BoardOperation {
OpType OperationType
Value dynamic
}

enum OperationType {
Add = 1
Remove = 2
Update = 3
}

struct GameID {
ID int32
Revision int32
}
struct GameBoard {
Pieces []*PieceLocation
Edges []*EdgePiece
Plots []*PlotPiece
Tiles []*Tile
Thief *Coordinate
}

struct PieceLocation {
Piece *GamePiece
Location *Coordinate
struct EdgePiece {
Piece *GamePiece
Start *Coordinate
End *Coordinate
}

struct PlotPiece {
Piece *GamePiece
Location *Coordinate
}

struct Coordinate {
Expand All @@ -49,6 +91,7 @@ struct Tile {
Location *Coordinate
Type TileType // Land or Water
Product Commodity // Commodity Type
Value int16 // For land this is the roll. For water this is port facing.
}

struct GamePiece {
Expand Down
23 changes: 19 additions & 4 deletions golib/client/bindings/clientjs.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,22 @@ func (c *ClientJS) SaveGame(jso *js.Object) {
c.Outgoing <- catannet.NewPacket(sg)
}

func (c *ClientJS) JoinGame(jso *js.Object) {
sg := catannetjs.JoinGameFromJS(jso)
c.Outgoing <- catannet.NewPacket(sg)
}

type ClientEvents struct {
onLoadGame LoadCallback
onSaveGame SaveCallback
onConnect FieldlessCallback
onLoadGame LoadCallback
onSaveGame SaveCallback
onConnect FieldlessCallback
onNotifyGame GameChangeCallback
}

type FieldlessCallback func()
type LoadCallback func(*catannet.LoadGameResponse)
type SaveCallback func(*catannet.SaveGameResponse)
type GameChangeCallback func(*catannet.BoardEvent)

func (ce *ClientEvents) OnSaveGame(cb SaveCallback) {
ce.onSaveGame = cb
Expand All @@ -61,6 +68,10 @@ func (ce *ClientEvents) OnConnect(cb FieldlessCallback) {
ce.onConnect = cb
}

func (ce *ClientEvents) OnListenEvent(cb GameChangeCallback) {
ce.onNotifyGame = cb
}

func (c *ClientJS) Dial(url string) {
go func() {
if url == "" {
Expand All @@ -70,8 +81,8 @@ func (c *ClientJS) Dial(url string) {
if c.events.onConnect != nil {
c.events.onConnect()
}
go runClient(c)
})
go runClient(c)
}()
}

Expand All @@ -92,6 +103,10 @@ func runClient(c *ClientJS) {
if c.events.onLoadGame != nil {
c.events.onLoadGame(packet.NetMsg.(*catannet.LoadGameResponse))
}
case catannet.BoardEventMsgType:
if (c.events.onNotifyGame) != nil {
c.events.onNotifyGame(packet.NetMsg.(*catannet.BoardEvent))
}
}
}
print("runClient exiting.")
Expand Down
32 changes: 28 additions & 4 deletions golib/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"
"os"
"os/signal"
"sync"

"github.com/jerold/Catan/golib/catannet"
"github.com/jerold/Catan/golib/client"
Expand All @@ -19,9 +20,17 @@ func main() {
func runServer() {
os.Mkdir("storage", 0777)
fmt.Printf("Starting server now.\n")
ss := &ServerState{
sl: &ServerListeners{
listeners: map[int32][]*client.Client{},
m: &sync.Mutex{},
},
gamerevs: map[int32]int32{},
gm: &sync.Mutex{},
}
http.Handle("/ws", websocket.Handler(func(conn *websocket.Conn) {
c := createClient(conn)
runClient(&c)
runClient(&c, ss)
}))

go func() {
Expand Down Expand Up @@ -70,7 +79,7 @@ func createClient(conn *websocket.Conn) client.Client {
}
}

func runClient(c *client.Client) {
func runClient(c *client.Client, ss *ServerState) {
go client.Sender(c)
go client.Reader(c)

Expand All @@ -85,15 +94,26 @@ func runClient(c *client.Client) {
case *catannet.SaveGameRequest:
fmt.Printf(" %s: Requests game to be saved.\n", c.Name)
go func(sg *catannet.SaveGameRequest) {
resp, err := SaveGame(sg)
resp, err := SaveGame(sg, ss)
if err != nil {
fmt.Printf("Failed to save game: %s\n", err)
// TODO: HANDLE ERR HERE.
resp.ID = -1
resp.ID.ID = -1
} else {
fmt.Printf(" %s: Saved game under id: %d\n", c.Name, resp.ID)
}
c.Outgoing <- catannet.NewPacket(resp)
update := catannet.ListenEvent{
ID: resp.ID,
Board: tmsg.Board,
Players: tmsg.Players,
}
eventPacket := catannet.NewPacket(update)
ss.sl.m.Lock()
for _, li := range ss.sl.listeners[update.ID.ID] {
li.Outgoing <- eventPacket
}
ss.sl.m.Unlock()
}(tmsg)
case *catannet.LoadGameRequest:
fmt.Printf(" %s: Requests game %d to be loaded.\n", c.Name, tmsg.ID)
Expand All @@ -105,6 +125,10 @@ func runClient(c *client.Client) {
}
c.Outgoing <- catannet.NewPacket(lgr)
}(tmsg)
case *catannet.ListenSubscribe:
ss.sl.m.Lock()
ss.sl.listeners[tmsg.ID] = append(ss.sl.listeners[tmsg.ID], c)
ss.sl.m.Unlock()
}

}
Expand Down
Binary file modified golib/server/server
Binary file not shown.
81 changes: 70 additions & 11 deletions golib/server/storage.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,94 @@
package main

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"sync/atomic"

"github.com/jerold/Catan/golib/catannet"
"github.com/jerold/Catan/golib/client"
)

var lastID int32
type ServerState struct {
sl *ServerListeners
lastID int32
gamerevs map[int32]int32
gm *sync.Mutex
}

// Listener subscriptions
// TODO: Turn this into a full live game instead of just a listener.
type ServerListeners struct {
listeners map[int32][]*client.Client
m *sync.Mutex
}

func SaveGame(game *catannet.SaveGameRequest) (catannet.SaveGameResponse, error) {
game.ID = atomic.AddInt32(&lastID, 1)
func SaveGame(game *catannet.SaveGameRequest, ss *ServerState) (catannet.SaveGameResponse, error) {
if game.ID.ID == 0 {
game.ID.ID = atomic.AddInt32(&ss.lastID, 1)
}
if game.ID.Revision == 0 {
ss.gm.Lock()
if _, ok := ss.gamerevs[game.ID.ID]; !ok {
ss.gm.Unlock()
r := getSaveRev(game.ID)
ss.gm.Lock()
// If this is a new save, it will be -1
ss.gamerevs[game.ID.ID] = r.Revision
}
ss.gamerevs[game.ID.ID]++
game.ID.Revision = ss.gamerevs[game.ID.ID]
ss.gm.Unlock()
}
data := make([]byte, game.Len())
game.Serialize(data)
// fmt.Printf("game board\n", game.Board.Pieces)
// for _, p := range game.Board.Pieces {
// fmt.Printf(" Piece: %d @ (%d,%d)\n", p.Piece.Type, p.Location.X, p.Location.Y)
// }
err := ioutil.WriteFile(saveFile(game.ID), data, 0655)
return catannet.SaveGameResponse{
ID: game.ID,
}, err
}

func saveFile(id int32) string {
return "storage/" + strconv.Itoa(int(id)) + ".save"
func saveFile(id catannet.GameID) string {
return "storage/" + strconv.Itoa(int(id.ID)) + "." + strconv.Itoa(int(id.Revision)) + ".save"
}

func LoadGame(id int32) (catannet.LoadGameResponse, error) {
data, err := ioutil.ReadFile(saveFile(id))
func getSaveRev(id catannet.GameID) catannet.GameID {
maxrev := -1
err := filepath.Walk("storage/", func(path string, _ os.FileInfo, _ error) error {
ext := filepath.Ext(path)
if ext != "save" {
return nil
}
name := filepath.Base(path)
fnp := strings.Split(name, ".")
rev, err := strconv.Atoi(fnp[1])
if err != nil {
return nil
}
if rev > maxrev {
maxrev = rev
}
return nil
})
if err != nil {
fmt.Printf("Failed to find revision: %s.\n", err)
// Not sure what to do here
return catannet.GameID{ID: -1, Revision: -1}
}
return catannet.GameID{ID: id.ID, Revision: int32(maxrev)}
}

func LoadGame(id catannet.GameID) (catannet.LoadGameResponse, error) {
fn := saveFile(id)
if id.Revision <= 0 {
fn = saveFile(getSaveRev(id))
}
data, err := ioutil.ReadFile(fn)
lgr := catannet.LoadGameResponse{
ID: id,
Board: &catannet.GameBoard{},
Expand Down
6 changes: 3 additions & 3 deletions initgo.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/bin/bash

# Get go deps
go get -u golang.org/x/net/websocket

./golib/install_tools.sh
./golib/buildclient.sh
./golib/buildserver.sh

# Get go deps
go get -u golang.org/x/net/websocket
Loading