diff --git a/cmd/client/main.go b/cmd/client/main.go index 11b1bda..6bd00ac 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -1,95 +1,107 @@ package main import ( - "bufio" "fmt" + "io" + "log" "os" + "sshpong/internal/client" "sshpong/internal/netwrk" - "strings" + + "google.golang.org/protobuf/proto" ) +var exit chan bool + func main() { - - lobbyChan := make(chan netwrk.LobbyPlayerStatus) - interrupter := make(chan netwrk.Interrupter) - messageOutput := make(chan *netwrk.LobbyMessage) - inputChan := make(chan string) - fmt.Println("Welcome to sshpong!") fmt.Println("Please enter your username") - go func() { - scanner := bufio.NewScanner(os.Stdin) - for scanner.Scan() { - text := scanner.Text() - inputChan <- text - } - }() - - reader := bufio.NewReader(os.Stdin) - username, err := reader.ReadString('\n') - if err != nil { - fmt.Println("Error reading from your shit bro...") - } - - go netwrk.ConnectToLobby(username, messageOutput, lobbyChan, interrupter) + egress := make(chan *netwrk.LobbyMessage) + ingress := make(chan *netwrk.LobbyMessage) buf := make([]byte, 1024) + n, err := os.Stdin.Read(buf) + if err != nil { + log.Panic("Bro your input is no good...") + } + username := string(buf[:n]) - for { - select { - case msg := <-interrupter: - fmt.Println(msg.Message) - default: - n, err := os.Stdin.Read(buf) - if err != nil { - fmt.Println("Error reading from stdin") - return - } - - input := string(buf[:n]) - args := strings.Fields(input) - switch args[0] { - case "invite": - if args[1] != "" { - messageOutput <- &netwrk.LobbyMessage{ - PlayerId: username, - Type: "invite", - Content: args[1], - } - } else { - fmt.Println("Please provide a player to invite ") - } - case "chat": - if args[1] != "" { - messageOutput <- &netwrk.LobbyMessage{ - PlayerId: username, - Type: "chat", - Content: strings.Join(args[1:], " "), - } - } - case "/": - if args[1] != "" { - messageOutput <- &netwrk.LobbyMessage{ - PlayerId: username, - Type: "chat", - Content: strings.Join(args[1:], " "), - } - } - case "quit": - return - case "q": - return - case "help": - fmt.Println("use invite to invite a player\nchat or / to send a message to the lobby\nq or quit to leave the game") - case "h": - fmt.Println("use invite to invite a player\nchat or / to send a message to the lobby\nq or quit to leave the game") - default: - fmt.Println("use invite to invite a player\nchat or / to send a message to the lobby\nq or quit to leave the game") - } - - } - + conn, err := netwrk.ConnectToLobby(username) + if err != nil { + log.Panic(err) } + // User input handler + go func(egress chan *netwrk.LobbyMessage) { + buf := make([]byte, 1024) + for { + + n, err := os.Stdin.Read(buf) + if err != nil { + log.Panic("Bro your input wack as fuck") + } + + userMessage, err := client.HandleUserInput(buf[:n]) + if err != nil { + fmt.Println(err) + continue + } + + userMessage.PlayerId = username + egress <- userMessage + } + }(egress) + + // Ingress Handler + go func(oc chan *netwrk.LobbyMessage) { + for { + msg := <-ingress + + client.HandleServerMessage(msg) + } + + }(ingress) + + // Network writer + go func(userMessages chan *netwrk.LobbyMessage) { + for { + msg := <-userMessages + bytes, err := proto.Marshal(msg) + if err != nil { + log.Panic("Malformed proto message", err) + } + _, err = conn.Write(bytes) + if err == io.EOF { + log.Panic("Server disconnected sorry...") + } else if err != nil { + log.Panic("Error reading from server connection...") + } + } + }(egress) + + // Network reader + go func(serverMessages chan *netwrk.LobbyMessage) { + buf := make([]byte, 1024) + for { + n, err := conn.Read(buf) + if err == io.EOF { + log.Panic("Server disconnected sorry...") + } else if err != nil { + log.Panic("Error reading from server connection...") + } + + message := &netwrk.LobbyMessage{} + + err = proto.Unmarshal(buf[:n], message) + if err != nil { + log.Panic("Error reading message from server") + } + + serverMessages <- message + + } + }(ingress) + + _ = <-exit } diff --git a/cmd/server/main.go b/cmd/server/main.go index dbbcf8b..272af25 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -5,9 +5,12 @@ import ( "sshpong/internal/netwrk" ) +var exit chan bool + func main() { fmt.Println("Starting sshpong server!") netwrk.Listen() + _ = <-exit } diff --git a/internal/client/client_utils.go b/internal/client/client_utils.go new file mode 100644 index 0000000..a05c920 --- /dev/null +++ b/internal/client/client_utils.go @@ -0,0 +1,71 @@ +package client + +import ( + "fmt" + "io" + "log" + "sshpong/internal/netwrk" + "strings" +) + +func HandleUserInput(buf []byte) (*netwrk.LobbyMessage, error) { + input := string(buf) + args := strings.Fields(input) + switch args[0] { + case "invite": + if args[1] != "" { + return &netwrk.LobbyMessage{ + Type: "invite", + Content: args[1], + }, nil + } else { + fmt.Println("Please provide a player to invite ") + } + case "chat": + if args[1] != "" { + return &netwrk.LobbyMessage{ + Type: "chat", + Content: strings.Join(args[1:], " "), + }, nil + } + case "/": + if args[1] != "" { + return &netwrk.LobbyMessage{ + Type: "chat", + Content: strings.Join(args[1:], " "), + }, nil + } + case "quit": + return nil, io.EOF + case "q": + return nil, io.EOF + case "help": + return nil, fmt.Errorf("use invite to invite a player\nchat or / to send a message to the lobby\nq or quit to leave the game") + case "h": + return nil, fmt.Errorf("use invite to invite a player\nchat or / to send a message to the lobby\nq or quit to leave the game") + default: + return nil, fmt.Errorf("use invite to invite a player\nchat or / to send a message to the lobby\nq or quit to leave the game") + } + return nil, nil +} + +func HandleServerMessage(message *netwrk.LobbyMessage) { + switch message.Type { + case "invite": + log.Println(message.PlayerId, "is inviting you to a game.", message.Content) + case "accepted": + log.Println(message.PlayerId, "accepted your invite.", message.Content) + case "text": + log.Println(message.PlayerId, ":", message.Content) + case "decline_game": + log.Println("Invite was declined:", message.Content) + case "disconnect": + log.Println("Got disconnect for player:", message.Content) + case "connect": + log.Println("Got connect for player:", message.Content) + case "pong": + log.Println("Received", message.Content) + default: + log.Println("Received", message.Content) + } +} diff --git a/internal/netwrk/client.go b/internal/netwrk/client.go index be14b89..562110f 100644 --- a/internal/netwrk/client.go +++ b/internal/netwrk/client.go @@ -2,152 +2,26 @@ package netwrk import ( "fmt" - "log" "net" - "strings" "google.golang.org/protobuf/proto" ) -type LobbyPlayerStatus struct { - Username string - Status string -} - -type Interrupter struct { - MessageType string - Message string - ReplyChan chan string -} - -var username string -var lobby chan LobbyPlayerStatus -var interruptChan chan Interrupter - -func ConnectToLobby(playerUsername string, messageOutputChan chan *LobbyMessage, lobbyMessageChan chan LobbyPlayerStatus, interruptChannel chan Interrupter) { - username = playerUsername - lobby = lobbyMessageChan - interruptChan = interruptChannel - +func ConnectToLobby(username string) (net.Conn, error) { conn, err := net.Dial("tcp", "127.0.0.1:12345") if err != nil { - fmt.Println("Sorry, failed to connect to server...") - return + return nil, fmt.Errorf("Sorry, failed to connect to server...") } loginMsg, err := proto.Marshal(&LobbyMessage{Type: "name", Content: username}) if err != nil { - fmt.Println("Sorry bro but your username is wack AF...") + return nil, fmt.Errorf("Sorry bro but your username is wack AF...") } _, err = conn.Write(loginMsg) if err != nil { - fmt.Println("Sorry, could not communicate with server...") + return nil, fmt.Errorf("Sorry, could not communicate with server...") } - fmt.Println("Starting client loop") - messageInputChan := make(chan *LobbyMessage) - go func() { - for { - messageBytes := make([]byte, 1024) - - n, err := conn.Read(messageBytes) - if err != nil { - fmt.Println("Sorry, failed to read message from server...", err) - } - - message := &LobbyMessage{} - - err = proto.Unmarshal(messageBytes[:n], message) - if err != nil { - fmt.Println("Sorry, the server sent something weird back...") - } - - messageInputChan <- message - } - }() - for { - select { - case msg := <-messageInputChan: - if isDone, gameID := handleLobbyMessage(conn, msg); isDone { - if gameID != "" { - interruptChan <- Interrupter{ - MessageType: "game", - Message: gameID, - ReplyChan: make(chan string), - } - return - } else { - return - } - } - case msg := <-messageOutputChan: - fmt.Println("Sending message out", msg) - err := SendMessageToServer(conn, msg) - if err != nil { - fmt.Println("Error!", err) - } - } - } -} - -func handleLobbyMessage(serverConn net.Conn, message *LobbyMessage) (bool, string) { - switch message.Type { - case "text": - fmt.Println(message.Content) - return false, "" - case "error": - fmt.Println("Error:", message.Content) - return false, "" - case "invite": - fmt.Println("GOT INVITE!") - replyChan := make(chan string) - interruptChan <- Interrupter{ - MessageType: "invite", - Message: fmt.Sprintf("Invite from player %s\nAccept: Y Decline: N", message.Content), - ReplyChan: replyChan, - } - input := <-replyChan - if strings.ToLower(input) == "yes" || strings.ToLower(input) == "y" { - SendMessageToServer(serverConn, &LobbyMessage{Type: "accept_game", Content: username}) - SendMessageToServer(serverConn, &LobbyMessage{Type: "quit", Content: username}) - return true, message.Content - } else { - SendMessageToServer(serverConn, &LobbyMessage{Type: "decline_game", Content: username}) - } - return false, "" - case "accept": - fmt.Println(message.Content, "accepted your invite. Game starting...") - SendMessageToServer(serverConn, &LobbyMessage{Type: "quit", Content: username}) - return true, message.Content - case "decline_game": - fmt.Println("Sorry,", message.Content, "declined your game invite...") - return false, "" - case "connect": - lobby <- LobbyPlayerStatus{Username: message.Content, Status: "connected"} - fmt.Println(message.Content, "connected!") - return false, "" - case "disconnect": - lobby <- LobbyPlayerStatus{Username: message.Content, Status: "disconnected"} - fmt.Println(message.Content, "disconnected!") - return false, "" - case "pong": - log.Println("PoNg!") - return false, "" - default: - log.Println("Got message", message) - } - return false, "" -} - -func SendMessageToServer(connection net.Conn, message *LobbyMessage) error { - bytes, err := proto.Marshal(message) - if err != nil { - return fmt.Errorf("Error marshalling message. Your protobuf is wack yo.") - } - _, err = connection.Write(bytes) - if err != nil { - return fmt.Errorf("Error writing to client connection") - } - return nil + return conn, nil } diff --git a/internal/netwrk/lobby.go b/internal/netwrk/lobby.go index e2d610d..938caa3 100644 --- a/internal/netwrk/lobby.go +++ b/internal/netwrk/lobby.go @@ -1,7 +1,6 @@ package netwrk import ( - "fmt" "io" "log" "net" @@ -14,15 +13,16 @@ func handleLobbyConnection(conn net.Conn) { messageBytes := make([]byte, 4096) - recvMessageChan := make(chan *LobbyMessage) + ingress := make(chan *LobbyMessage) + egress := make(chan *LobbyMessage) + + // Network Reader go func() { for { - fmt.Println("READING!") n, err := conn.Read(messageBytes) if err == io.EOF { return } - fmt.Println("READ something!") if err != nil { log.Printf("Error reading message %v", err) return @@ -32,106 +32,103 @@ func handleLobbyConnection(conn net.Conn) { err = proto.Unmarshal(messageBytes[:n], &message) if err != nil { - log.Println("Invalid message received from client") + log.Println("Invalid message received from client", err) } - recvMessageChan <- &message + ingress <- &message } }() - for { - - select { - case msg := <-recvMessageChan: - if isDone, err := handleClientLobbyMessage(conn, msg); err != nil || isDone { - log.Println(err) - return + // Network Writer + go func() { + for { + msg := <-egress + bytes, err := proto.Marshal(msg) + if err != nil { + log.Println("Error marshalling message to send to user...", err) + } + _, err = conn.Write(bytes) + if err == io.EOF { + log.Println("User has disconnected", err) + ingress <- &LobbyMessage{Type: "disconnect"} + } + if err != nil { + log.Println("Error writing to user...", err) } - fmt.Println("Handled message") } - } + }() + + // Client message handler + go func() { + for { + msg := <-ingress + serverMsg, err := handleClientLobbyMessage(msg) + if err != nil { + log.Println("Error handling client lobby message...", err) + } + if serverMsg != nil { + egress <- serverMsg + } + } + }() } // Returns a bool of whether the player has disconnected from the lobby and an error -func handleClientLobbyMessage(playerConnection net.Conn, message *LobbyMessage) (bool, error) { +func handleClientLobbyMessage(message *LobbyMessage) (*LobbyMessage, error) { switch message.Type { case "name": - _, ok := clientPool.clients[message.Content] + _, ok := lobbyMembers.Load(message.Content) if ok { - SendMessageToClient(playerConnection, &LobbyMessage{Type: "error", Content: "Sorry, that name is already taken"}) - return false, nil - } - playerID := message.Content - clientPool.clients[playerID] = Client{ - name: playerID, - conn: playerConnection, - ready: false, - } - for _, player := range clientPool.clients { - err := SendMessageToClient(playerConnection, &LobbyMessage{PlayerId: player.name, Type: "connect", Content: player.name}) - if err != nil { - log.Println("There was an error sending the list of lobby players to client", message.Content) - } + return &LobbyMessage{Type: "name_error", Content: "Sorry, that name is already taken, please try a different name"}, nil } + username := message.Content + + // Send all client messages + lobbyMembers.Range(func(lobbyUsername string, client Client) bool { + externalMessageChan <- ExternalMessage{Target: username, Message: &LobbyMessage{Type: "connect", Content: lobbyUsername}} + return true + }) log.Println("Broadcasting new player", message.Content) - broadcastToLobby(&LobbyMessage{PlayerId: "", Type: "connect", Content: playerID}) + broadcastToLobby(&LobbyMessage{PlayerId: "", Type: "connect", Content: username}) - return false, SendMessageToClient(playerConnection, &LobbyMessage{PlayerId: playerID, Type: "name", Content: playerID}) + return &LobbyMessage{PlayerId: username, Type: "name", Content: username}, nil case "invite": log.Println("Got invite for player:", message.Content) - invitee, ok := clientPool.clients[message.Content] + invitee, ok := lobbyMembers[message.Content] if !ok { - SendMessageToClient(playerConnection, &LobbyMessage{Type: "text", Content: "Sorry, that player is not available..."}) - return false, nil + return &LobbyMessage{Type: "text", Content: "Sorry, that player is not available..."}, nil } - SendMessageToClient(invitee.conn, &LobbyMessage{Type: "invite", Content: message.PlayerId}) - return false, nil + return &LobbyMessage{Type: "invite", Content: message.PlayerId}, nil case "accept_game": - player := clientPool.clients[message.Content] + player := lobbyMembers[message.Content] - if err := SendMessageToClient(player.conn, &LobbyMessage{Type: "accept", Content: ""}); err != nil { - SendMessageToClient(playerConnection, &LobbyMessage{Type: "error", Content: "Sorry that game is no longer available..."}) - return false, nil - } + return &LobbyMessage{Type: "accept", Content: ""}, nil - return true, nil case "chat": broadcastToLobby(&LobbyMessage{PlayerId: message.PlayerId, Type: "text", Content: message.Content}) - return false, nil + return nil, nil case "decline_game": - inviter := clientPool.clients[message.Content] - SendMessageToClient(inviter.conn, &LobbyMessage{Type: "decline_game", Content: message.PlayerId}) - return false, nil + inviter := lobbyMembers[message.Content] + return &LobbyMessage{Type: "decline_game", Content: message.PlayerId}, nil case "quit": - delete(clientPool.clients, message.PlayerId) + delete(lobbyMembers, message.PlayerId) broadcastToLobby(&LobbyMessage{Type: "disconnect", Content: message.PlayerId}) - return true, nil + return nil, nil case "ping": - SendMessageToClient(playerConnection, &LobbyMessage{Type: "pong", Content: "pong"}) - return false, nil + return &LobbyMessage{Type: "pong", Content: "pong"}, nil default: - SendMessageToClient(playerConnection, &LobbyMessage{Type: "pong", Content: "pong"}) - return false, nil + return &LobbyMessage{Type: "pong", Content: "pong"}, nil } } -func SendMessageToClient(connection net.Conn, message *LobbyMessage) error { - bytes, err := proto.Marshal(message) - if err != nil { - return fmt.Errorf("Error marshalling message. Your protobuf is wack yo.") - } - _, err = connection.Write(bytes) - if err != nil { - return fmt.Errorf("Error writing to client connection") - } - fmt.Println("Sent message to client") - return nil -} - func broadcastToLobby(message *LobbyMessage) { - for _, player := range clientPool.clients { - err := SendMessageToClient(player.conn, message) + for _, player := range lobbyMembers { + bytes, err := proto.Marshal(message) + if err != nil { + log.Println("Error marshalling broadcast message", err) + } + _, err = player.Conn.Write(bytes) if err != nil { log.Println("Error broadcasting to clients...", err) } diff --git a/internal/netwrk/netwrk.go b/internal/netwrk/netwrk.go index 7e5a5c9..468ae7c 100644 --- a/internal/netwrk/netwrk.go +++ b/internal/netwrk/netwrk.go @@ -3,36 +3,40 @@ package netwrk import ( "log" "net" + sync "sync" ) type Client struct { - name string - conn net.Conn - ready bool + Username string + Conn net.Conn } -type ClientPool struct { - clients map[string]Client +type LobbyPlayersMessage struct { + Type string + Username string + IsAvailable chan bool } -type GameClients struct { - client1 chan GameMessage - client2 chan GameMessage +type ExternalMessage struct { + Target string + Message *LobbyMessage } -type GameChans struct { - games map[string]GameClients -} +var lobbyListener chan LobbyPlayersMessage +var externalMessageChan chan ExternalMessage -var clientPool *ClientPool -var gameChans *GameChans +var lobbyMembers sync.Map + +func init() { + lobbyListener = make(chan LobbyPlayersMessage) + externalMessageChan = make(chan ExternalMessage) + + lobbyMembers = sync.Map{} +} // Starts listening on port 12345 for TCP connections // Also creates client pool and game connection singletons -func Listen() { - clientPool = &ClientPool{ - clients: map[string]Client{}, - } +func LobbyListen() { listener, err := net.Listen("tcp", "127.0.0.1:12345") if err != nil { @@ -41,21 +45,18 @@ func Listen() { defer listener.Close() - go func() { - for { - conn, err := listener.Accept() - log.Println("got a connection!") - if err != nil { - log.Println(err) - continue - } - go handleLobbyConnection(conn) + for { + conn, err := listener.Accept() + log.Println("got a connection!") + if err != nil { + log.Println(err) + continue } - }() - - gameChans = &GameChans{ - games: map[string]GameClients{}, + go handleLobbyConnection(conn) } +} + +func GamesListen() { gameListener, err := net.Listen("tcp", "127.0.0.1:42069") if err != nil {