intro server and client
This commit is contained in:
parent
beab95579c
commit
277189f229
15
Makefile
15
Makefile
@ -1,4 +1,5 @@
|
||||
GOFILES=$(wildcard *.go)
|
||||
GAMEFILES=$(wildcard internal/game/*.go)
|
||||
COORDFILEs=$(wildcard internal/coordinator/*.go)
|
||||
PROG=snengame
|
||||
#set variables
|
||||
GIT_COMMIT := $(shell git rev-list -1 HEAD)
|
||||
@ -8,11 +9,17 @@ endif
|
||||
|
||||
$(PROG): $(GOFILES)
|
||||
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:
|
||||
rm -f snengame
|
||||
run: snengame
|
||||
./snengame
|
||||
rm -f client server engine
|
||||
run: engine
|
||||
./engine
|
||||
install: $(SNENGAME)
|
||||
install -d $(DESTDIR)$(PREFIX)/bin
|
||||
install -m 755 $(PROG) $(DESTDIR)$(PREFIX)/bin
|
||||
|
129
cmd/client/main.go
Normal file
129
cmd/client/main.go
Normal 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
9
cmd/server/main.go
Normal 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
7
go.mod
@ -2,4 +2,9 @@ module git.saintnet.tech/stryan/snengame
|
||||
|
||||
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
2
go.sum
@ -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/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
|
83
internal/coordinator/coordinator.go
Normal file
83
internal/coordinator/coordinator.go
Normal 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,
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package engine
|
||||
package coordinator
|
||||
|
||||
import (
|
||||
"log"
|
||||
@ -9,9 +9,9 @@ import (
|
||||
|
||||
var upgrader = websocket.Upgrader{} // use default options
|
||||
|
||||
func serve() {
|
||||
func Serve(c *Coordinator) {
|
||||
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
|
||||
serveWs(w, r)
|
||||
serveWs(c, w, r)
|
||||
})
|
||||
err := http.ListenAndServe(":7636", 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
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
@ -30,18 +30,21 @@ func serveWs(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// The event loop
|
||||
for {
|
||||
var cmd Command
|
||||
var resp CommandResult
|
||||
var cmd SessionCommand
|
||||
err := conn.ReadJSON(&cmd)
|
||||
if err != nil {
|
||||
log.Println("Error during message reading:", err)
|
||||
break
|
||||
}
|
||||
log.Printf("Received: %s", cmd)
|
||||
resp := c.Coordinate(&cmd)
|
||||
err = conn.WriteJSON(resp)
|
||||
if err != nil {
|
||||
log.Println("Error during message writing:", err)
|
||||
break
|
||||
}
|
||||
if resp.Result == SessionRespLeft {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
98
internal/coordinator/session.go
Normal file
98
internal/coordinator/session.go
Normal 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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user