package main import ( "bufio" "flag" "fmt" "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, 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:] }