intro server and client

This commit is contained in:
stryan 2021-07-22 18:19:21 -04:00
parent beab95579c
commit 277189f229
8 changed files with 347 additions and 11 deletions

View File

@ -1,4 +1,5 @@
GOFILES=$(wildcard *.go) GAMEFILES=$(wildcard internal/game/*.go)
COORDFILEs=$(wildcard internal/coordinator/*.go)
PROG=snengame PROG=snengame
#set variables #set variables
GIT_COMMIT := $(shell git rev-list -1 HEAD) GIT_COMMIT := $(shell git rev-list -1 HEAD)
@ -8,11 +9,17 @@ endif
$(PROG): $(GOFILES) $(PROG): $(GOFILES)
go build -ldflags "-X main.GitCommit=$(GIT_COMMIT)" go build -ldflags "-X main.GitCommit=$(GIT_COMMIT)"
client: $(GAMEFILES) $(wildcard cmd/client/*.go)
go build -ldflags "-X main.GitCommit=$(GIT_COMMIT)" ./cmd/client
server: $(GAMEFILES) $(wildcard cmd/server/*.go)
go build -ldflags "-X main.GitCommit=$(GIT_COMMIT)" ./cmd/server
engine: $(GAMEFILES)
go build -ldflags "-X main.GitCommit=$(GIT_COMMIT)" ./cmd/engine
clean: clean:
rm -f snengame rm -f client server engine
run: snengame run: engine
./snengame ./engine
install: $(SNENGAME) install: $(SNENGAME)
install -d $(DESTDIR)$(PREFIX)/bin install -d $(DESTDIR)$(PREFIX)/bin
install -m 755 $(PROG) $(DESTDIR)$(PREFIX)/bin install -m 755 $(PROG) $(DESTDIR)$(PREFIX)/bin

129
cmd/client/main.go Normal file
View File

@ -0,0 +1,129 @@
package main
import (
"fmt"
"log"
"os"
"os/signal"
"strings"
"time"
"git.saintnet.tech/stryan/snengame/internal/coordinator"
"github.com/google/uuid"
"github.com/gorilla/websocket"
)
var done chan interface{}
var interrupt chan os.Signal
func receiveHandler(connection *websocket.Conn) {
defer close(done)
for {
var resp coordinator.SessionCommandResult
err := connection.ReadJSON(&resp)
if err != nil {
log.Println("Error in receive:", err)
return
}
log.Printf("Received: %s\n", resp)
}
}
func main() {
done = make(chan interface{}) // Channel to indicate that the receiverHandler is done
interrupt = make(chan os.Signal) // Channel to listen for interrupt signal to terminate gracefully
cmd := make(chan coordinator.SessionCommand)
signal.Notify(interrupt, os.Interrupt) // Notify the interrupt channel for SIGINT
socketUrl := "ws://localhost:7636" + "/ws"
conn, _, err := websocket.DefaultDialer.Dial(socketUrl, nil)
id := uuid.New()
if err != nil {
log.Fatal("Error connecting to Websocket Server:", err)
}
defer conn.Close()
go receiveHandler(conn)
go GetCommand(id, cmd)
// Our main loop for the client
// We send our relevant packets here
for {
var c coordinator.SessionCommand
select {
case c = <-cmd:
// Send an echo packet every second
err := conn.WriteJSON(c)
if err != nil {
log.Println("Error during writing to websocket:", err)
return
}
case <-interrupt:
// We received a SIGINT (Ctrl + C). Terminate gracefully...
log.Println("Received SIGINT interrupt signal. Closing all pending connections")
// Close our websocket connection
err := conn.WriteJSON(coordinator.SessionCommand{
ID: id,
Command: coordinator.SessionCmdLeave,
})
if err != nil {
log.Println("Error during closing websocket:", err)
return
}
select {
case <-done:
log.Println("Receiver Channel Closed! Exiting....")
case <-time.After(time.Duration(1) * time.Second):
log.Println("Timeout in closing receiving channel. Exiting....")
}
return
}
}
}
func GetCommand(uid uuid.UUID, resp chan coordinator.SessionCommand) {
for {
var cmd string
var t int
fmt.Print("> ")
_, err := fmt.Scanf("%d", &t)
if err != nil {
log.Println(err)
}
if t == -1 {
panic("quitting")
}
_, err = fmt.Scanf("%s", &cmd)
if err != nil {
log.Println(err)
}
cmd = strings.TrimSpace(cmd)
switch t {
case 0:
//session
switch coordinator.SessionCmd(cmd) {
case coordinator.SessionCmdQuery:
resp <- coordinator.SessionCommand{
ID: uid,
Command: coordinator.SessionCmdQuery,
}
case coordinator.SessionCmdJoin:
resp <- coordinator.SessionCommand{
ID: uid,
Command: coordinator.SessionCmdJoin,
}
case coordinator.SessionCmdLeave:
resp <- coordinator.SessionCommand{
ID: uid,
Command: coordinator.SessionCmdLeave,
}
default:
break
}
}
}
}

9
cmd/server/main.go Normal file
View File

@ -0,0 +1,9 @@
package main
import "git.saintnet.tech/stryan/snengame/internal/coordinator"
func main() {
c := coordinator.NewCoordinator()
c.Start()
coordinator.Serve(c)
}

7
go.mod
View File

@ -2,4 +2,9 @@ module git.saintnet.tech/stryan/snengame
go 1.16 go 1.16
require github.com/gorilla/websocket v1.4.2 require (
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/websocket v1.4.2
)
replace git.saintnet.tech/stryan/snengame/internal => ./internal

2
go.sum
View File

@ -1,2 +1,4 @@
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=

View File

@ -0,0 +1,83 @@
package coordinator
import (
"fmt"
"log"
"time"
"git.saintnet.tech/stryan/snengame/internal/game"
"github.com/google/uuid"
)
type Coordinator struct {
Match *Session
PlayerQueueChan chan uuid.UUID
CallbackChan map[uuid.UUID]chan bool
}
func NewCoordinator() *Coordinator {
return &Coordinator{
Match: NewSession(),
PlayerQueueChan: make(chan uuid.UUID),
CallbackChan: make(map[uuid.UUID]chan bool),
}
}
func (c *Coordinator) Start() {
go func() {
m := NewSession()
var p1, p2 uuid.UUID
p1 = <-c.PlayerQueueChan
fmt.Println("p1 join")
p2 = <-c.PlayerQueueChan
fmt.Println("p2 join")
c.Match = m
m.Game = game.NewGame()
c.CallbackChan[p1] <- true
c.CallbackChan[p2] <- true
}()
go func() {
time.Sleep(5)
if c.Match.Game == nil {
log.Println("clearing old match")
c.Match = nil
}
}()
}
func (c *Coordinator) Coordinate(cmd *SessionCommand) *SessionCommandResult {
switch cmd.Command {
case SessionCmdQuery:
c.CallbackChan[cmd.ID] = make(chan bool)
c.PlayerQueueChan <- cmd.ID
<-c.CallbackChan[cmd.ID]
return &SessionCommandResult{
ID: cmd.ID,
Result: SessionRespFound,
}
case SessionCmdJoin:
resp := c.Match.Join(cmd.ID)
return &SessionCommandResult{
ID: cmd.ID,
Result: resp,
}
case SessionCmdLeave:
c.Match.Leave(cmd.ID)
return &SessionCommandResult{
ID: cmd.ID,
Result: SessionRespLeft,
}
case SessionCmdPlay:
resp := c.Match.Play(cmd.ID, cmd.GameCommand)
return &SessionCommandResult{
ID: cmd.ID,
Result: SessionRespPlayed,
GameResult: resp,
}
}
return &SessionCommandResult{
ID: cmd.ID,
Result: SessionRespError,
}
}

View File

@ -1,4 +1,4 @@
package engine package coordinator
import ( import (
"log" "log"
@ -9,9 +9,9 @@ import (
var upgrader = websocket.Upgrader{} // use default options var upgrader = websocket.Upgrader{} // use default options
func serve() { func Serve(c *Coordinator) {
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
serveWs(w, r) serveWs(c, w, r)
}) })
err := http.ListenAndServe(":7636", nil) err := http.ListenAndServe(":7636", nil)
if err != nil { if err != nil {
@ -19,7 +19,7 @@ func serve() {
} }
} }
func serveWs(w http.ResponseWriter, r *http.Request) { func serveWs(c *Coordinator, w http.ResponseWriter, r *http.Request) {
// Upgrade our raw HTTP connection to a websocket based one // Upgrade our raw HTTP connection to a websocket based one
conn, err := upgrader.Upgrade(w, r, nil) conn, err := upgrader.Upgrade(w, r, nil)
if err != nil { if err != nil {
@ -30,18 +30,21 @@ func serveWs(w http.ResponseWriter, r *http.Request) {
// The event loop // The event loop
for { for {
var cmd Command var cmd SessionCommand
var resp CommandResult
err := conn.ReadJSON(&cmd) err := conn.ReadJSON(&cmd)
if err != nil { if err != nil {
log.Println("Error during message reading:", err) log.Println("Error during message reading:", err)
break break
} }
log.Printf("Received: %s", cmd) log.Printf("Received: %s", cmd)
resp := c.Coordinate(&cmd)
err = conn.WriteJSON(resp) err = conn.WriteJSON(resp)
if err != nil { if err != nil {
log.Println("Error during message writing:", err) log.Println("Error during message writing:", err)
break break
} }
if resp.Result == SessionRespLeft {
break
}
} }
} }

View File

@ -0,0 +1,98 @@
package coordinator
import (
"fmt"
"git.saintnet.tech/stryan/snengame/internal/game"
"github.com/google/uuid"
)
type SessionCmd string
type SessionResp string
const (
SessionCmdQuery SessionCmd = "query"
SessionCmdJoin = "join"
SessionCmdLeave = "leave"
SessionCmdPlay = "play"
)
const (
SessionRespFound SessionResp = "found"
SessionRespJoined1 = "joined p1"
SessionRespJoined2 = "joined p2"
SessionRespJoinError = "join error"
SessionRespLeft = "left"
SessionRespPlayed = "played"
SessionRespError = "generic error"
)
type Session struct {
p1 uuid.UUID
p2 uuid.UUID
pMap map[uuid.UUID]int
Game *game.Game
}
func NewSession() *Session {
return &Session{
p1: uuid.Nil,
p2: uuid.Nil,
pMap: make(map[uuid.UUID]int),
Game: nil,
}
}
func (s *Session) Join(id uuid.UUID) SessionResp {
if s.p1 == uuid.Nil {
s.p1 = id
s.pMap[id] = game.SentinalID
return SessionRespJoined1
} else if s.p2 == uuid.Nil {
s.p2 = id
s.pMap[id] = game.ScourgeID
return SessionRespJoined2
} else {
return SessionRespJoinError
}
}
func (s *Session) Leave(id uuid.UUID) {
if id == s.p1 {
s.p1 = uuid.Nil
} else if id == s.p2 {
s.p2 = uuid.Nil
}
if s.p1 == uuid.Nil && s.p2 == uuid.Nil {
s.Game = nil
} else if s.Game.Status != game.StatusDraw || s.Game.Status != game.StatusScourgeWin || s.Game.Status != game.StatusSentinalWin {
s.Game.Status = game.StatusStop
}
}
func (s *Session) Play(id uuid.UUID, cmd *game.Command) *game.CommandResult {
if s.pMap[id] != cmd.PlayerID {
return nil
}
return s.Game.Parse(cmd)
}
type SessionCommand struct {
ID uuid.UUID `json:"player_id"`
Command SessionCmd `json:"command"`
GameCommand *game.Command `json:"game_command"`
}
func (s *SessionCommand) String() string {
return fmt.Sprintf("%v %v\n%v\n", s.ID, s.Command, s.GameCommand)
}
type SessionCommandResult struct {
ID uuid.UUID `json:"player_id"`
Result SessionResp `json:"result"`
GameResult *game.CommandResult `json:"game_result"`
}
func (s *SessionCommandResult) String() string {
return fmt.Sprintf("%v %v\n%v\n", s.ID, s.Result, s.GameResult)
}