commit e6661a26f42f0af130b852fe52cf5d70a3d78665 Author: Steve Date: Fri Oct 1 12:53:36 2021 -0400 initial migration diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f81b60b --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +#set variables +GIT_COMMIT := $(shell git rev-list -1 HEAD) +ifeq ($(PREFIX),) # PREFIX is environment variable, but if it is not set, then set default value + PREFIX := /usr/local +endif + +all: client client2 + +client: $(wildcard client/*.go) + go build -ldflags "-X main.GitCommit=$(GIT_COMMIT)" ./client +client2: $(wildcard client2/*.go) + go build -ldflags "-X main.GitCommit=$(GIT_COMMIT)" ./client2 + +clean: + rm -f client client2 + diff --git a/client/main.go b/client/main.go new file mode 100644 index 0000000..583b1a9 --- /dev/null +++ b/client/main.go @@ -0,0 +1,244 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "log" + "os" + "os/signal" + "strconv" + "strings" + "time" + "unicode/utf8" + + "github.com/google/uuid" + "github.com/gorilla/websocket" +) + +var done chan interface{} +var interrupt chan os.Signal +var pid int +var matchID uuid.UUID + +func receiveHandler(connection *websocket.Conn, output chan string) { + defer close(done) + for { + var resp coordinator.SessionCommandResult + err := connection.ReadJSON(&resp) + if err != nil { + log.Println("Error in receive:", err) + } + switch resp.Result { + case coordinator.SessionRespJoined1: + pid = game.SentinalID + output <- "joined as sentinal" + case coordinator.SessionRespJoined2: + pid = game.ScourgeID + output <- "joined as scourge" + case coordinator.SessionRespFound: + matchID = resp.MatchID + output <- "game found" + case coordinator.SessionRespJoinError: + output <- "error joining game" + case coordinator.SessionRespPlayed: + if resp.GameResult != nil { + output <- "played succesfully" + switch resp.GameResult.ResultType { + case game.ActCmd: + output <- resp.GameResult.ActionResult.String() + case game.StateCmd: + output <- resp.GameResult.StateResult.String() + case game.DebugCmd: + output <- resp.GameResult.DebugResult.String() + } + } else { + output <- "error playing" + } + case coordinator.SessionRespLeft: + output <- "game left" + break + case coordinator.SessionRespBroadcastSenTurn: + output <- "Sentinal may take their turn" + case coordinator.SessionRespBroadcastScoTrun: + output <- "Scourge may take their turn" + case coordinator.SessionRespBroadcastSenWin: + output <- "Sentinal wins!" + case coordinator.SessionRespBroadcastScoWin: + output <- "Scourge wins!" + case coordinator.SessionRespBroadcastUpdate: + //we don't handle updates + case coordinator.SessionRespBroadcastScoJoin: + output <- "Scourge has joined the game" + case coordinator.SessionRespBroadcastSenJoin: + output <- "Sentinal has joined the game" + case coordinator.SessionRespBroadcastScoLeft: + output <- "Scourge has left the game" + case coordinator.SessionRespBroadcastSenLeft: + output <- "Sentinal has left the game" + case coordinator.SessionRespBroadcastSenReady: + output <- "Sentinal is ready" + case coordinator.SessionRespBroadcastScoReady: + output <- "scourge is ready" + case coordinator.SessionRespBroadcastNone: + + case coordinator.SessionRespError: + output <- "generic error" + + default: + output <- "Received a server response we don't know how to handle" + } + } +} + +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) + output := make(chan string) + + signal.Notify(interrupt, os.Interrupt) // Notify the interrupt channel for SIGINT + hostname := flag.String("host", "localhost", "server hostname to connect to") + port := flag.Int("port", 7636, "port to connect to") + + flag.Parse() + port_s := strconv.Itoa(*port) + socketUrl := "ws://" + *hostname + ":" + port_s + "/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, output) + go GetCommand(id, cmd) + ticker := time.NewTicker(1 * time.Second) + // Our main loop for the client + // We send our relevant packets here + for { + var c coordinator.SessionCommand + var o string + select { + case c = <-cmd: + err := conn.WriteJSON(c) + if err != nil { + log.Println("Error during writing to websocket:", err) + return + } + if c.Command == coordinator.SessionCmdLeave { + break + } + case o = <-output: + fmt.Println(o) + 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 + case <-ticker.C: + if matchID != uuid.Nil { + err := conn.WriteJSON(coordinator.SessionCommand{ + ID: id, + MatchID: matchID, + Command: coordinator.SessionCmdPoll, + }) + if err != nil { + log.Println("Error writing to websocket:", err) + 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") + } + cmd, err = bufio.NewReader(os.Stdin).ReadString('\n') + 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, + MatchID: matchID, + Command: coordinator.SessionCmdJoin, + } + case coordinator.SessionCmdLeave: + resp <- coordinator.SessionCommand{ + ID: uid, + MatchID: matchID, + Command: coordinator.SessionCmdLeave, + } + default: + break + } + case 1: + //state + resp <- coordinator.SessionCommand{ + ID: uid, + MatchID: matchID, + Command: coordinator.SessionCmdPlay, + GameCommand: &game.Command{ + PlayerID: pid, + Type: game.StateCmd, + Cmd: cmd, + }, + } + case 2: + //action + resp <- coordinator.SessionCommand{ + ID: uid, + MatchID: matchID, + Command: coordinator.SessionCmdPlay, + GameCommand: &game.Command{ + PlayerID: pid, + Type: game.ActCmd, + Cmd: cmd, + }, + } + } + + } +} + +func trimFirstRune(s string) string { + _, i := utf8.DecodeRuneInString(s) + return s[i:] +} diff --git a/client2/backend.go b/client2/backend.go new file mode 100644 index 0000000..21b4d88 --- /dev/null +++ b/client2/backend.go @@ -0,0 +1,260 @@ +package main + +import ( + "flag" + "log" + "os" + "os/signal" + "strconv" + "strings" + "time" + "unicode/utf8" + + "git.saintnet.tech/stryan/snengame/internal/coordinator" + "git.saintnet.tech/stryan/snengame/internal/game" + "github.com/google/uuid" + "github.com/gorilla/websocket" +) + +var done chan interface{} +var interrupt chan os.Signal +var pid int +var matchID uuid.UUID + +func receiveHandler(connection *websocket.Conn, container *UIContainer) { + defer close(done) + for { + var resp coordinator.SessionCommandResult + err := connection.ReadJSON(&resp) + if err != nil { + log.Println("Error in receive:", err) + break + } + switch resp.Result { + case coordinator.SessionRespJoined1: + pid = game.SentinalID + container.Output <- "joined as sentinal" + case coordinator.SessionRespJoined2: + pid = game.ScourgeID + container.Output <- "joined as scourge" + case coordinator.SessionRespFound: + matchID = resp.MatchID + container.Output <- "game found" + case coordinator.SessionRespPlayed: + if resp.GameResult != nil { + switch resp.GameResult.ResultType { + case game.ActCmd: + container.Output <- resp.GameResult.ActionResult.String() + case game.StateCmd: + container.State = resp.GameResult.StateResult + container.Updated <- true + case game.DebugCmd: + container.Output <- resp.GameResult.DebugResult.String() + } + } else { + container.Output <- "error playing" + } + case coordinator.SessionRespLeft: + container.Output <- "game left" + matchID = uuid.Nil + return + case coordinator.SessionRespBroadcastSenTurn: + container.Output <- "Sentinal may take their turn" + case coordinator.SessionRespBroadcastScoTrun: + container.Output <- "Scourge may take their turn" + case coordinator.SessionRespBroadcastSenWin: + container.Output <- "Sentinal wins!" + case coordinator.SessionRespBroadcastScoWin: + container.Output <- "Scourge wins!" + case coordinator.SessionRespBroadcastUpdate: + container.GetUpdate <- true + case coordinator.SessionRespBroadcastScoJoin: + container.Output <- "Scourge has joined the game" + case coordinator.SessionRespBroadcastSenJoin: + container.Output <- "Sentinal has joined the game" + case coordinator.SessionRespBroadcastScoLeft: + container.Output <- "Scourge has left the game" + case coordinator.SessionRespBroadcastSenLeft: + container.Output <- "Sentinal has left the game" + case coordinator.SessionRespBroadcastSenReady: + container.Output <- "Sentinal is ready" + case coordinator.SessionRespBroadcastScoReady: + container.Output <- "scourge is ready" + case coordinator.SessionRespBroadcastNone: + + case coordinator.SessionRespError: + container.Output <- "generic error" + + default: + container.Output <- "Received a server response we don't know how to handle" + } + } +} + +func backend(container *UIContainer) { + 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 + hostname := flag.String("host", "localhost", "server hostname to connect to") + port := flag.Int("port", 7636, "port to connect to") + + flag.Parse() + port_s := strconv.Itoa(*port) + socketUrl := "ws://" + *hostname + ":" + port_s + "/ws" + id := uuid.New() + conn, _, err := websocket.DefaultDialer.Dial(socketUrl, nil) + + if err != nil { + log.Fatal("Error connecting to Websocket Server:", err) + } + defer conn.Close() + go receiveHandler(conn, container) + go GetCommand(id, cmd, container) + ticker := time.NewTicker(2 * time.Second) + // Our main loop for the client + // We send our relevant packets here + for { + var c coordinator.SessionCommand + select { + case c = <-cmd: + err := conn.WriteJSON(c) + if err != nil { + log.Println("Error during writing to websocket:", err) + return + } + if c.Command == coordinator.SessionCmdLeave { + break + } + case <-container.GetUpdate: + if container.State != nil { + err = conn.WriteJSON(&coordinator.SessionCommand{ + ID: id, + MatchID: matchID, + Command: coordinator.SessionCmdPlay, + GameCommand: &game.Command{ + PlayerID: pid, + Type: game.StateCmd, + Cmd: "g", + }, + }) + if err != nil { + log.Println("Error during writing to websocker:", err) + return + } + container.Updated <- true + } + + 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 + case <-ticker.C: + select { + case <-done: + return + default: + if matchID != uuid.Nil { + err := conn.WriteJSON(coordinator.SessionCommand{ + ID: id, + MatchID: matchID, + Command: coordinator.SessionCmdPoll, + }) + if err != nil { + log.Println("Error writing to websocket:", err) + return + } + } + } + } + } +} + +func GetCommand(uid uuid.UUID, resp chan coordinator.SessionCommand, container *UIContainer) { + for { + var cmd string + var t int + input := <-container.Input + input_s := strings.Split(input, " ") + t, err := strconv.Atoi(input_s[0]) + if err != nil { + continue + } + cmd = trimFirstRune(input) + + 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, + MatchID: matchID, + Command: coordinator.SessionCmdJoin, + } + case coordinator.SessionCmdLeave: + resp <- coordinator.SessionCommand{ + ID: uid, + MatchID: matchID, + Command: coordinator.SessionCmdLeave, + } + default: + break + } + case 1: + //state + resp <- coordinator.SessionCommand{ + ID: uid, + MatchID: matchID, + Command: coordinator.SessionCmdPlay, + GameCommand: &game.Command{ + PlayerID: pid, + Type: game.StateCmd, + Cmd: cmd, + }, + } + case 2: + //action + resp <- coordinator.SessionCommand{ + ID: uid, + MatchID: matchID, + Command: coordinator.SessionCmdPlay, + GameCommand: &game.Command{ + PlayerID: pid, + Type: game.ActCmd, + Cmd: cmd, + }, + } + } + + } +} + +func trimFirstRune(s string) string { + _, i := utf8.DecodeRuneInString(s) + return s[i:] +} diff --git a/client2/main.go b/client2/main.go new file mode 100644 index 0000000..47934bc --- /dev/null +++ b/client2/main.go @@ -0,0 +1,131 @@ +package main + +import ( + "fmt" + + "code.rocketnine.space/tslocum/cview" + "git.saintnet.tech/stryan/snengame/internal/game" + "github.com/gdamore/tcell/v2" +) + +type UIContainer struct { + State *game.GameView + Updated chan bool + Output chan string + Input chan string + GetUpdate chan bool +} + +func main() { + app := cview.NewApplication() + app.EnableMouse(true) + container := &UIContainer{ + State: nil, + Updated: make(chan bool), + Output: make(chan string), + Input: make(chan string), + GetUpdate: make(chan bool), + } + + grid := cview.NewGrid() + grid.SetRows(3, 0, 0, 3, 3) + grid.SetColumns(30, 0, 30) + grid.SetBorders(true) + + board := cview.NewTable() + board.SetBorders(true) + hand := cview.NewTable() + hand.SetBorders(true) + playerside := cview.NewTextView() + playerside.SetText("Game has not started") + playerside.SetTextAlign(cview.AlignCenter) + commandinput := cview.NewInputField() + commandoutput := cview.NewTextView() + commandinput.SetDoneFunc(func(key tcell.Key) { + container.Input <- commandinput.GetText() + commandinput.SetText("") + }) + commandoutput.SetChangedFunc(func() { + app.Draw() + }) + playerside.SetChangedFunc(func() { + app.Draw() + }) + + go func() { + for { + select { + case <-container.Updated: + playerside.Clear() + var pv string + if container.State != nil && container.State.Player != nil { + ct := "Noone" + if container.State.CurrentTurn == game.SentinalID { + ct = "Sentinal" + } else if container.State.CurrentTurn == game.ScourgeID { + ct = "Scourge" + } + pv = fmt.Sprintf("Your Life: %v\nEnemy Life: %v\nEnemy Hand size: %v\nEnemy Deck Size: %v\n\nCT:%v CD: %v, HD %v, Status: %v\n", container.State.Player.Life, container.State.EnemyLife, container.State.EnemyHandSize, container.State.EnemyDeckSize, ct, container.State.CanDraw, container.State.HasDrawn, container.State.Status) + } else { + pv = fmt.Sprintf("%v", "Awaiting player info") + } + fmt.Fprintf(playerside, "%v", pv) + + for c := 0; c < 4; c++ { + color := tcell.ColorWhite.TrueColor() + be := "_" + if container.State != nil && container.State.Board != nil { + be = container.State.Board.Sentinal[c].String() + } + cell := cview.NewTableCell(be) + cell.SetTextColor(color) + cell.SetAlign(cview.AlignCenter) + board.SetCell(0, c, cell) + } + for c := 0; c < 4; c++ { + color := tcell.ColorWhite.TrueColor() + be := "_" + if container.State != nil && container.State.Board != nil { + be = container.State.Board.Scourge[c].String() + } + cell := cview.NewTableCell(be) + cell.SetTextColor(color) + cell.SetAlign(cview.AlignCenter) + board.SetCell(1, c, cell) + } + cols := 0 + if container.State != nil && container.State.Player != nil && container.State.Player.Hand != nil { + cols = len(container.State.Player.Hand) + } + hand.Clear() + for c := 0; c < cols; c++ { + color := tcell.ColorWhite.TrueColor() + ce := "_" + if container.State != nil && container.State.Player != nil && container.State.Player.Hand != nil { + ce = container.State.Player.Hand[c].String() + } + cell := cview.NewTableCell(ce) + cell.SetTextColor(color) + cell.SetAlign(cview.AlignCenter) + hand.SetCell(0, c, cell) + } + app.Draw() + case o := <-container.Output: + commandoutput.Clear() + fmt.Fprintf(commandoutput, "Result: %v", o) + } + } + }() + + grid.AddItem(playerside, 1, 0, 2, 1, 0, 100, false) + grid.AddItem(board, 1, 1, 1, 1, 0, 100, false) + grid.AddItem(hand, 2, 1, 1, 1, 0, 100, false) + grid.AddItem(commandinput, 3, 0, 1, 3, 0, 0, true) + grid.AddItem(commandoutput, 4, 0, 1, 3, 0, 0, false) + container.Updated <- true + go backend(container) + app.SetRoot(grid, true) + if err := app.Run(); err != nil { + panic(err) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1c7267c --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.saintnet.tech/tome_client + +go 1.16