middle of the re-work
This commit is contained in:
parent
e31dbef6ef
commit
f2d488ef51
|
@ -4,41 +4,45 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"log/slog"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sshpong/internal/client"
|
"sshpong/internal/client"
|
||||||
"sshpong/internal/netwrk"
|
"sshpong/internal/lobby"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var exit chan bool
|
var username string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
|
slog.Debug("Debug logs active...")
|
||||||
|
|
||||||
fmt.Println("Welcome to sshpong!")
|
fmt.Println("Welcome to sshpong!")
|
||||||
fmt.Println("Please enter your username")
|
fmt.Println("Please enter your username")
|
||||||
|
|
||||||
egress := make(chan *netwrk.LobbyMessage)
|
egress := make(chan lobby.LobbyMessage)
|
||||||
ingress := make(chan *netwrk.LobbyMessage)
|
ingress := make(chan lobby.LobbyMessage)
|
||||||
interrupter := make(chan client.InterrupterMessage, 100)
|
interrupter := make(chan client.InterrupterMessage, 100)
|
||||||
|
exit := make(chan string)
|
||||||
|
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
n, err := os.Stdin.Read(buf)
|
n, err := os.Stdin.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic("Bro your input is no good...")
|
log.Panic("Bro your input is no good...")
|
||||||
}
|
}
|
||||||
username := string(buf[:n-1])
|
username = string(buf[:n-1])
|
||||||
|
|
||||||
conn, err := netwrk.ConnectToLobby(username)
|
fmt.Println("username is...", username)
|
||||||
|
conn, err := ConnectToLobby(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// User input handler
|
// User input handler
|
||||||
go func(egress chan *netwrk.LobbyMessage) {
|
go func(egress chan lobby.LobbyMessage) {
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
for {
|
for {
|
||||||
|
|
||||||
n, err := os.Stdin.Read(buf)
|
n, err := os.Stdin.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic("Bro your input wack as fuck")
|
log.Panic("Bro your input wack as fuck")
|
||||||
|
@ -47,42 +51,57 @@ func main() {
|
||||||
input := string(buf[:n-1])
|
input := string(buf[:n-1])
|
||||||
args := strings.Fields(input)
|
args := strings.Fields(input)
|
||||||
|
|
||||||
userMessage := &netwrk.LobbyMessage{}
|
userMessage := lobby.LobbyMessage{}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case msg := <-interrupter:
|
case msg := <-interrupter:
|
||||||
userMessage, err := client.HandleInterruptInput(msg, args)
|
userMessage, err := client.HandleInterruptInput(msg, args, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
userMessage, err = client.HandleUserInput(args)
|
userMessage, err = client.HandleUserInput(args, username)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
exit <- true
|
exit <- ""
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
userMessage.PlayerId = username
|
|
||||||
egress <- userMessage
|
egress <- userMessage
|
||||||
|
if userMessage.MessageType == "accept" || userMessage.MessageType == "disconect" {
|
||||||
|
slog.Debug("Closing input handler with accept or disconnect message", slog.Any("message content", userMessage.Message))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userMessage.MessageType == "start_game" {
|
||||||
|
slog.Debug("closing input handler with start_game message and sending exit signal")
|
||||||
|
|
||||||
|
// TODO: This is a wierd one...
|
||||||
|
sg, ok := userMessage.Message.(lobby.StartGame)
|
||||||
|
if !ok {
|
||||||
|
slog.Debug("Start game interrupt message was improperly formatted... Could be indicative of an error in the HandleinterruptInput method")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
exit <- sg.GameID
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
userMessage, err = client.HandleUserInput(args)
|
userMessage, err = client.HandleUserInput(args, username)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
exit <- true
|
exit <- ""
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
userMessage.PlayerId = username
|
|
||||||
egress <- userMessage
|
egress <- userMessage
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}(egress)
|
}(egress)
|
||||||
|
|
||||||
// Ingress Handler
|
// Ingress Handler
|
||||||
go func(oc chan *netwrk.LobbyMessage) {
|
go func(oc chan lobby.LobbyMessage) {
|
||||||
for {
|
for {
|
||||||
msg := <-ingress
|
msg := <-ingress
|
||||||
|
|
||||||
|
@ -98,10 +117,10 @@ func main() {
|
||||||
}(ingress)
|
}(ingress)
|
||||||
|
|
||||||
// Network writer
|
// Network writer
|
||||||
go func(userMessages chan *netwrk.LobbyMessage) {
|
go func(userMessages chan lobby.LobbyMessage) {
|
||||||
for {
|
for {
|
||||||
msg := <-userMessages
|
msg := <-userMessages
|
||||||
bytes, err := proto.Marshal(msg)
|
bytes, err := lobby.Marshal(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic("Malformed proto message", err)
|
log.Panic("Malformed proto message", err)
|
||||||
}
|
}
|
||||||
|
@ -111,31 +130,79 @@ func main() {
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
log.Panic("Error reading from server connection...")
|
log.Panic("Error reading from server connection...")
|
||||||
}
|
}
|
||||||
|
if msg.MessageType == "start_game" || msg.MessageType == "disconnect" {
|
||||||
|
slog.Debug("closing network writer ")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}(egress)
|
}(egress)
|
||||||
|
|
||||||
// Network reader
|
// Network reader
|
||||||
go func(serverMessages chan *netwrk.LobbyMessage) {
|
go func(serverMessages chan lobby.LobbyMessage) {
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
for {
|
for {
|
||||||
n, err := conn.Read(buf)
|
n, err := conn.Read(buf)
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
log.Panic("Server disconnected sorry...")
|
fmt.Println("disconnected from lobby")
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
log.Panic("Error reading from server connection...", err)
|
log.Panic("Error reading from server connection...", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
message := &netwrk.LobbyMessage{}
|
message, err := lobby.Unmarshal(buf[:n])
|
||||||
|
|
||||||
err = proto.Unmarshal(buf[:n], message)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic("Error reading message from server")
|
log.Panic("Error reading message from server", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
serverMessages <- message
|
serverMessages <- message
|
||||||
|
|
||||||
}
|
}
|
||||||
}(ingress)
|
}(ingress)
|
||||||
|
|
||||||
_ = <-exit
|
fmt.Println("Waiting for an exit message")
|
||||||
|
isStartGame := <-exit
|
||||||
|
if isStartGame != "" {
|
||||||
|
fmt.Println("Connecting to game", isStartGame)
|
||||||
|
gameConn, err := ConnectToGame(username, isStartGame)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Panic("Failed to connect to game server...", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Game(gameConn)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConnectToLobby(username string) (net.Conn, error) {
|
||||||
|
conn, err := net.Dial("tcp", "127.0.0.1:12345")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Sorry, failed to connect to server...")
|
||||||
|
}
|
||||||
|
|
||||||
|
loginMsg, err := lobby.Marshal(lobby.LobbyMessage{MessageType: "name", Message: lobby.Name{Name: username}})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Sorry bro but your username is wack AF...")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Write(loginMsg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Sorry, could not communicate with server...")
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConnectToGame(username, gameID string) (net.Conn, error) {
|
||||||
|
conn, err := net.Dial("tcp", "127.0.0.1:42069")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Write([]byte(fmt.Sprintf("%s:%s", gameID, username)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,106 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sshpong/internal/netwrk"
|
"log"
|
||||||
|
"log/slog"
|
||||||
|
"net"
|
||||||
|
"sshpong/internal/lobby"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
var exit chan bool
|
var exit chan bool
|
||||||
|
var games sync.Map
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println("Starting sshpong server!")
|
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
|
fmt.Println("Starting sshpong lobby...")
|
||||||
|
go LobbyListen()
|
||||||
|
fmt.Println("Lobby started")
|
||||||
|
|
||||||
netwrk.LobbyListen()
|
// fmt.Println("Starting game listener...")
|
||||||
netwrk.GamesListen()
|
// go GamesListen()
|
||||||
|
// fmt.Println("Game listener started")
|
||||||
|
|
||||||
_ = <-exit
|
_ = <-exit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Starts listening on port 12345 for TCP connections
|
||||||
|
// Also creates client pool and game connection singletons
|
||||||
|
func LobbyListen() {
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", "127.0.0.1:12345")
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Error setting up listener for lobby. Exiting...")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer listener.Close()
|
||||||
|
|
||||||
|
l := lobby.CreateLobby()
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go l.HandleLobbyConnection(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func GamesListen() {
|
||||||
|
//
|
||||||
|
// slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||||
|
// slog.Debug("Debug level logs are active")
|
||||||
|
//
|
||||||
|
// gameListener, err := net.Listen("tcp", "127.0.0.1:42069")
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for {
|
||||||
|
// defer gameListener.Close()
|
||||||
|
// conn, err := gameListener.Accept()
|
||||||
|
// if err != nil {
|
||||||
|
// log.Println(err)
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// slog.Debug("Received game connection")
|
||||||
|
//
|
||||||
|
// go func(conn net.Conn) {
|
||||||
|
// messageBytes := make([]byte, 126)
|
||||||
|
//
|
||||||
|
// n, err := conn.Read(messageBytes)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Printf("Error reading game ID on connection %s", err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// gInfo := strings.SplitAfter(string(messageBytes[:n]), ":")
|
||||||
|
// if err != nil {
|
||||||
|
// log.Printf("Game id was not a string? %s", err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// slog.Debug("Game request data", slog.Any("game info", gInfo))
|
||||||
|
//
|
||||||
|
// game, ok := games.Load(gInfo[0])
|
||||||
|
// if !ok {
|
||||||
|
// games.Store(gInfo[0], GameClients{Client1: Client{
|
||||||
|
// Username: gInfo[1],
|
||||||
|
// Conn: conn,
|
||||||
|
// }, Client2: Client{}})
|
||||||
|
// } else {
|
||||||
|
// gameclients, _ := game.(GameClients)
|
||||||
|
// client2 := Client{
|
||||||
|
// Username: gInfo[1],
|
||||||
|
// Conn: conn,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// games.Store(gInfo[0], GameClients{
|
||||||
|
// Client1: gameclients.Client1,
|
||||||
|
// Client2: client2})
|
||||||
|
//
|
||||||
|
// go pong.StartGame(gameclients.Client1.Conn, client2.Conn, gameclients.Client1.Username, client2.Username)
|
||||||
|
// }
|
||||||
|
// }(conn)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sshpong/internal/netwrk"
|
"log/slog"
|
||||||
|
"sshpong/internal/lobby"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,113 +16,197 @@ type InterrupterMessage struct {
|
||||||
|
|
||||||
var help = fmt.Errorf("use invite <player name> to invite a player\nchat or / to send a message to the lobby\nq or quit to leave the game")
|
var help = fmt.Errorf("use invite <player name> to invite a player\nchat or / to send a message to the lobby\nq or quit to leave the game")
|
||||||
|
|
||||||
func HandleUserInput(args []string) (*netwrk.LobbyMessage, error) {
|
var red = "\x1b[31m"
|
||||||
|
var normal = "\033[0m"
|
||||||
|
|
||||||
|
func HandleUserInput(args []string, username string) (lobby.LobbyMessage, error) {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return nil, help
|
return lobby.LobbyMessage{}, help
|
||||||
}
|
}
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "invite":
|
case "invite":
|
||||||
if args[1] != "" {
|
if args[1] != "" {
|
||||||
return &netwrk.LobbyMessage{
|
return lobby.LobbyMessage{
|
||||||
Type: "invite",
|
MessageType: "invite",
|
||||||
Content: args[1],
|
Message: lobby.Invite{From: username, To: args[1]}}, nil
|
||||||
}, nil
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Please provide a player to invite ")
|
fmt.Println("Please provide a player to invite ")
|
||||||
}
|
}
|
||||||
case "chat":
|
case "chat":
|
||||||
if args[1] != "" {
|
if args[1] != "" {
|
||||||
return &netwrk.LobbyMessage{
|
return lobby.LobbyMessage{
|
||||||
Type: "chat",
|
MessageType: "chat",
|
||||||
Content: strings.Join(args[1:], " "),
|
Message: lobby.Chat{
|
||||||
|
From: username,
|
||||||
|
Message: args[1],
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
case "/":
|
case "/":
|
||||||
if args[1] != "" {
|
if args[1] != "" {
|
||||||
return &netwrk.LobbyMessage{
|
return lobby.LobbyMessage{
|
||||||
Type: "chat",
|
MessageType: "chat",
|
||||||
Content: strings.Join(args[1:], " "),
|
Message: lobby.Chat{
|
||||||
|
From: username,
|
||||||
|
Message: args[1],
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
case "quit":
|
case "quit":
|
||||||
return nil, io.EOF
|
return lobby.LobbyMessage{}, io.EOF
|
||||||
case "q":
|
case "q":
|
||||||
return nil, io.EOF
|
return lobby.LobbyMessage{}, io.EOF
|
||||||
case "help":
|
case "help":
|
||||||
return nil, help
|
return lobby.LobbyMessage{}, help
|
||||||
case "h":
|
case "h":
|
||||||
return nil, help
|
return lobby.LobbyMessage{}, help
|
||||||
default:
|
default:
|
||||||
if strings.Index(args[0], "/") == 0 {
|
if strings.Index(args[0], "/") == 0 {
|
||||||
return &netwrk.LobbyMessage{
|
return lobby.LobbyMessage{
|
||||||
Type: "chat",
|
MessageType: "chat",
|
||||||
Content: strings.Join(args, " ")[1:],
|
Message: lobby.Chat{
|
||||||
|
From: username,
|
||||||
|
Message: args[1],
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return nil, help
|
return lobby.LobbyMessage{}, help
|
||||||
}
|
}
|
||||||
return nil, nil
|
return lobby.LobbyMessage{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleInterruptInput(incoming InterrupterMessage, args []string) (*netwrk.LobbyMessage, error) {
|
func HandleInterruptInput(incoming InterrupterMessage, args []string, username string) (lobby.LobbyMessage, error) {
|
||||||
|
|
||||||
switch incoming.InterruptType {
|
switch incoming.InterruptType {
|
||||||
case "invite":
|
case "invite":
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return &netwrk.LobbyMessage{
|
return lobby.LobbyMessage{
|
||||||
Type: "decline",
|
MessageType: "decline",
|
||||||
Content: incoming.Content,
|
Message: lobby.Decline{
|
||||||
|
From: username,
|
||||||
|
To: incoming.Content,
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
} else {
|
} else {
|
||||||
if strings.ToLower(args[0]) == "y" || strings.ToLower(args[0]) == "yes" {
|
if strings.ToLower(args[0]) == "y" || strings.ToLower(args[0]) == "yes" {
|
||||||
return &netwrk.LobbyMessage{Type: "accept", Content: incoming.Content}, nil
|
return lobby.LobbyMessage{MessageType: "accept", Message: lobby.Accept{
|
||||||
|
From: username,
|
||||||
|
To: incoming.Content,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel waiting for invite?
|
// // Cancel waiting for invite? we aren't doing this I guess.
|
||||||
case "decline":
|
// case "decline":
|
||||||
|
// return nil,
|
||||||
// Disconnect and connect to game
|
// Disconnect and connect to game
|
||||||
case "accepted":
|
case "accepted":
|
||||||
return &netwrk.LobbyMessage{
|
return lobby.LobbyMessage{
|
||||||
Type: "disconnect",
|
MessageType: "disconnect",
|
||||||
Content: "",
|
Message: lobby.Disconnect{
|
||||||
|
From: incoming.Content,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
case "start_game":
|
||||||
|
return lobby.LobbyMessage{
|
||||||
|
MessageType: "start_game",
|
||||||
|
Message: lobby.StartGame{GameID: incoming.Content},
|
||||||
}, nil
|
}, nil
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("received a interrupt message that could not be handled %v", incoming)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return lobby.LobbyMessage{}, fmt.Errorf("received a interrupt message that could not be handled %v", incoming)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleServerMessage(message *netwrk.LobbyMessage) (InterrupterMessage, error) {
|
func HandleServerMessage(message lobby.LobbyMessage) (InterrupterMessage, error) {
|
||||||
switch message.Type {
|
|
||||||
|
msg := message.Message
|
||||||
|
switch message.MessageType {
|
||||||
case "name":
|
case "name":
|
||||||
fmt.Printf("Current Players\n%s\n", message.Content)
|
|
||||||
|
nmsg, ok := msg.(lobby.Name)
|
||||||
|
if !ok {
|
||||||
|
return InterrupterMessage{}, errors.New("Not a properly formatted name message")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Current Players\n%s\n", nmsg)
|
||||||
case "invite":
|
case "invite":
|
||||||
fmt.Println(message.PlayerId, "is inviting you to a game\nType y to accept...")
|
|
||||||
|
imsg, ok := msg.(lobby.Invite)
|
||||||
|
if !ok {
|
||||||
|
return InterrupterMessage{}, errors.New("Not a propertly formatted invite message")
|
||||||
|
}
|
||||||
|
fmt.Println(imsg.From, "is inviting you to a game\nType y to accept...")
|
||||||
return InterrupterMessage{
|
return InterrupterMessage{
|
||||||
InterruptType: "invite",
|
InterruptType: "invite",
|
||||||
Content: message.PlayerId,
|
Content: imsg.From,
|
||||||
}, nil
|
}, nil
|
||||||
case "pending_invite":
|
case "pending_invite":
|
||||||
fmt.Println("Invite sent to", message.Content, "\nWaiting for response...")
|
|
||||||
|
pimsg, ok := msg.(lobby.PendingInvite)
|
||||||
|
if !ok {
|
||||||
|
return InterrupterMessage{}, errors.New("Not a properly formatted pending invite message")
|
||||||
|
}
|
||||||
|
fmt.Println("Invite sent to", pimsg.Recipient, "\nWaiting for response...")
|
||||||
case "accepted":
|
case "accepted":
|
||||||
fmt.Println(message.PlayerId, "accepted your invite.\n", "Starting game...")
|
|
||||||
case "game_start":
|
amsg, ok := msg.(lobby.Accepted)
|
||||||
fmt.Println("Invited accepted\n", "Starting game...")
|
if !ok {
|
||||||
case "text":
|
return InterrupterMessage{}, errors.New("Not a properly formatted accepted message")
|
||||||
fmt.Println(message.PlayerId, ":", message.Content)
|
}
|
||||||
case "decline_game":
|
fmt.Println(amsg.Accepter, "accepted your invite.", "Press Enter to connect to game...")
|
||||||
fmt.Println(message.Content, "declined your game invite")
|
return InterrupterMessage{
|
||||||
|
InterruptType: "start_game",
|
||||||
|
Content: amsg.GameID,
|
||||||
|
}, nil
|
||||||
|
case "start_game":
|
||||||
|
|
||||||
|
sgmsg, ok := msg.(lobby.StartGame)
|
||||||
|
if !ok {
|
||||||
|
return InterrupterMessage{}, errors.New("Not a properly formatted start game message")
|
||||||
|
}
|
||||||
|
return InterrupterMessage{
|
||||||
|
InterruptType: "start_game",
|
||||||
|
Content: sgmsg.GameID,
|
||||||
|
}, nil
|
||||||
|
case "chat":
|
||||||
|
cmsg, ok := msg.(lobby.Chat)
|
||||||
|
if !ok {
|
||||||
|
return InterrupterMessage{}, errors.New("Not a properly formatted chat message")
|
||||||
|
}
|
||||||
|
fmt.Println(cmsg.From, ":", cmsg.Message)
|
||||||
|
case "decline":
|
||||||
|
dmsg, ok := msg.(lobby.Decline)
|
||||||
|
if !ok {
|
||||||
|
return InterrupterMessage{}, errors.New("Not a properly formatted decline message")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(dmsg.From, "declined your game invite")
|
||||||
case "disconnect":
|
case "disconnect":
|
||||||
fmt.Println(message.Content, "has disconnected")
|
|
||||||
|
dmsg, ok := msg.(lobby.Disconnect)
|
||||||
|
if !ok {
|
||||||
|
return InterrupterMessage{}, errors.New("Not a properly formatted disconnect message")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(dmsg.From, "has disconnected")
|
||||||
case "connect":
|
case "connect":
|
||||||
fmt.Println(message.Content, "has connected")
|
|
||||||
|
cmsg, ok := msg.(lobby.Connect)
|
||||||
|
if !ok {
|
||||||
|
return InterrupterMessage{}, errors.New("Not a properly formated connect message")
|
||||||
|
}
|
||||||
|
fmt.Println(cmsg.From, "has connected")
|
||||||
case "pong":
|
case "pong":
|
||||||
fmt.Println("Received", message.Content)
|
fmt.Println("Received pong")
|
||||||
|
case "error":
|
||||||
|
em, ok := msg.(lobby.Error)
|
||||||
|
if !ok {
|
||||||
|
slog.Debug("Received an indecipherable error message...", slog.Any("msg", msg))
|
||||||
|
}
|
||||||
|
fmt.Println(red, em.Message, normal)
|
||||||
default:
|
default:
|
||||||
fmt.Println("Received", message.Content)
|
fmt.Println("Received", message.MessageType, message.Message)
|
||||||
}
|
}
|
||||||
return InterrupterMessage{}, nil
|
return InterrupterMessage{}, nil
|
||||||
}
|
}
|
||||||
|
|
316
internal/client/game.go
Normal file
316
internal/client/game.go
Normal file
|
@ -0,0 +1,316 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"sshpong/internal/ansii"
|
||||||
|
"sshpong/internal/pong"
|
||||||
|
"sshpong/internal/renderer"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var state pong.GameState
|
||||||
|
var quit chan int
|
||||||
|
var egress chan pong.StateUpdate
|
||||||
|
var isPlayer1 bool = false
|
||||||
|
|
||||||
|
func Game(conn net.Conn) {
|
||||||
|
fmt.Println("Connected to game!")
|
||||||
|
|
||||||
|
egress = make(chan pong.StateUpdate)
|
||||||
|
quit = make(chan int)
|
||||||
|
|
||||||
|
// Network reader
|
||||||
|
go func() {
|
||||||
|
bytes := make([]byte, 512)
|
||||||
|
for {
|
||||||
|
n, err := conn.Read(bytes)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("failed to read from game connection...")
|
||||||
|
quit <- 1
|
||||||
|
}
|
||||||
|
stateUpdateHandler(bytes[:n])
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Network writer
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
update := <-egress
|
||||||
|
bytes, err := json.Marshal(update)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("failed to unmarhal game update message from server")
|
||||||
|
}
|
||||||
|
_, err = conn.Write(bytes)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("failed to write to game connection...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
prev, err := ansii.MakeTermRaw()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to make terminal raw")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer ansii.RestoreTerm(prev)
|
||||||
|
|
||||||
|
os.Stdout.WriteString(string(ansii.Screen.HideCursor))
|
||||||
|
defer os.Stdout.WriteString(string(ansii.Screen.ShowCursor))
|
||||||
|
|
||||||
|
// Input handler
|
||||||
|
go func() {
|
||||||
|
buf := make([]byte, 3)
|
||||||
|
for {
|
||||||
|
|
||||||
|
n, err := os.Stdin.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error reading from stdin", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handleGameInput(buf[:n])
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-quit
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func stateUpdateHandler(bytes []byte) {
|
||||||
|
update := pong.StateUpdate{}
|
||||||
|
err := json.Unmarshal(bytes, &update)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error unmarshalling server json", slog.Any("Unmarshal error", err))
|
||||||
|
update.FieldPath = "Message"
|
||||||
|
update.Value = []byte("An error has occured ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := strings.Split(update.FieldPath, ".")
|
||||||
|
|
||||||
|
// type GameState struct {
|
||||||
|
// Message string
|
||||||
|
// Winner string
|
||||||
|
// Score map[string]int
|
||||||
|
// Player1 Player
|
||||||
|
// Player2 Player
|
||||||
|
// Ball Ball
|
||||||
|
// }
|
||||||
|
|
||||||
|
// For now let's just send the whole field from a top level
|
||||||
|
// of the state. If things are slow we can optimize that later
|
||||||
|
val := update.Value
|
||||||
|
|
||||||
|
switch fields[0] {
|
||||||
|
case "All":
|
||||||
|
|
||||||
|
ns := pong.GameState{}
|
||||||
|
err = json.Unmarshal(val, &ns)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error unmarshalling whole state update")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state = ns
|
||||||
|
case "Message":
|
||||||
|
state.Message = string(update.Value)
|
||||||
|
case "Winner":
|
||||||
|
state.Winner = string(update.Value)
|
||||||
|
case "Score":
|
||||||
|
|
||||||
|
sc := map[string]int{}
|
||||||
|
err = json.Unmarshal(val, &sc)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error unmarshalling score update")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state.Score = sc
|
||||||
|
case "Player1":
|
||||||
|
p1 := pong.Player{}
|
||||||
|
err = json.Unmarshal(val, &p1)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error unmarshalling player1 update")
|
||||||
|
}
|
||||||
|
state.Player1 = p1
|
||||||
|
case "Player2":
|
||||||
|
p2 := pong.Player{}
|
||||||
|
err = json.Unmarshal(val, &p2)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error unmarshalling player2 update")
|
||||||
|
}
|
||||||
|
state.Player2 = p2
|
||||||
|
case "Ball":
|
||||||
|
|
||||||
|
b := pong.Ball{}
|
||||||
|
err = json.Unmarshal(val, &b)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error unmarshalling ball update")
|
||||||
|
}
|
||||||
|
state.Ball = b
|
||||||
|
// Special update message that determines if the client is player1 or player2
|
||||||
|
case "isPlayer1":
|
||||||
|
if update.Value[0] != 0 {
|
||||||
|
isPlayer1 = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.Render(state)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGameInput(bytes []byte) {
|
||||||
|
switch bytes[0] {
|
||||||
|
// Up
|
||||||
|
case 'w':
|
||||||
|
if isPlayer1 {
|
||||||
|
state.Player1.Pos.Y = state.Player1.Pos.Y + 1
|
||||||
|
v, err := json.Marshal(pong.Vector{
|
||||||
|
X: 0, Y: state.Player1.Pos.Y,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||||
|
}
|
||||||
|
update := pong.StateUpdate{
|
||||||
|
FieldPath: "Player1.Pos",
|
||||||
|
Value: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
egress <- update
|
||||||
|
} else {
|
||||||
|
state.Player2.Pos.Y = state.Player2.Pos.Y + 1
|
||||||
|
v, err := json.Marshal(pong.Vector{
|
||||||
|
X: 0, Y: state.Player2.Pos.Y,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error marshalling Player2 movement", slog.Any("error", err))
|
||||||
|
}
|
||||||
|
update := pong.StateUpdate{
|
||||||
|
FieldPath: "Player2.Pos",
|
||||||
|
Value: v,
|
||||||
|
}
|
||||||
|
egress <- update
|
||||||
|
}
|
||||||
|
return
|
||||||
|
// Down
|
||||||
|
case 's':
|
||||||
|
if isPlayer1 {
|
||||||
|
state.Player1.Pos.Y = state.Player1.Pos.Y - 1
|
||||||
|
v, err := json.Marshal(pong.Vector{
|
||||||
|
X: 0, Y: state.Player1.Pos.Y,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||||
|
}
|
||||||
|
update := pong.StateUpdate{
|
||||||
|
FieldPath: "Player1.Pos",
|
||||||
|
Value: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
egress <- update
|
||||||
|
} else {
|
||||||
|
state.Player2.Pos.Y = state.Player2.Pos.Y - 1
|
||||||
|
v, err := json.Marshal(pong.Vector{
|
||||||
|
X: 0, Y: state.Player2.Pos.Y,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||||
|
}
|
||||||
|
update := pong.StateUpdate{
|
||||||
|
FieldPath: "Player2.Pos",
|
||||||
|
Value: v,
|
||||||
|
}
|
||||||
|
egress <- update
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
// Quit
|
||||||
|
case 'q':
|
||||||
|
// Acts as a forfeit. Other player wins
|
||||||
|
if isPlayer1 {
|
||||||
|
update := pong.StateUpdate{
|
||||||
|
FieldPath: "Winner",
|
||||||
|
Value: []byte("Player2"),
|
||||||
|
}
|
||||||
|
egress <- update
|
||||||
|
} else {
|
||||||
|
update := pong.StateUpdate{
|
||||||
|
FieldPath: "Winner",
|
||||||
|
Value: []byte("Player1"),
|
||||||
|
}
|
||||||
|
egress <- update
|
||||||
|
}
|
||||||
|
quit <- 1
|
||||||
|
return
|
||||||
|
|
||||||
|
// Esc char
|
||||||
|
case 27:
|
||||||
|
// Arrow Keys
|
||||||
|
switch bytes[1] {
|
||||||
|
// Up
|
||||||
|
case 65:
|
||||||
|
if isPlayer1 {
|
||||||
|
state.Player1.Pos.Y = state.Player1.Pos.Y + 1
|
||||||
|
v, err := json.Marshal(pong.Vector{
|
||||||
|
X: 0, Y: state.Player1.Pos.Y,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||||
|
}
|
||||||
|
update := pong.StateUpdate{
|
||||||
|
FieldPath: "Player1.Pos",
|
||||||
|
Value: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
egress <- update
|
||||||
|
} else {
|
||||||
|
state.Player2.Pos.Y = state.Player2.Pos.Y + 1
|
||||||
|
v, err := json.Marshal(pong.Vector{
|
||||||
|
X: 0, Y: state.Player2.Pos.Y,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error marshalling Player2 movement", slog.Any("error", err))
|
||||||
|
}
|
||||||
|
update := pong.StateUpdate{
|
||||||
|
FieldPath: "Player2.Pos",
|
||||||
|
Value: v,
|
||||||
|
}
|
||||||
|
egress <- update
|
||||||
|
}
|
||||||
|
return
|
||||||
|
// Down
|
||||||
|
case 66:
|
||||||
|
if isPlayer1 {
|
||||||
|
state.Player1.Pos.Y = state.Player1.Pos.Y - 1
|
||||||
|
v, err := json.Marshal(pong.Vector{
|
||||||
|
X: 0, Y: state.Player1.Pos.Y,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||||
|
}
|
||||||
|
update := pong.StateUpdate{
|
||||||
|
FieldPath: "Player1.Pos",
|
||||||
|
Value: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
egress <- update
|
||||||
|
} else {
|
||||||
|
state.Player2.Pos.Y = state.Player2.Pos.Y - 1
|
||||||
|
v, err := json.Marshal(pong.Vector{
|
||||||
|
X: 0, Y: state.Player2.Pos.Y,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||||
|
}
|
||||||
|
update := pong.StateUpdate{
|
||||||
|
FieldPath: "Player2.Pos",
|
||||||
|
Value: v,
|
||||||
|
}
|
||||||
|
egress <- update
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
290
internal/lobby/lobby.go
Normal file
290
internal/lobby/lobby.go
Normal file
|
@ -0,0 +1,290 @@
|
||||||
|
package lobby
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"log/slog"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Lobby struct {
|
||||||
|
lobbyMembers sync.Map
|
||||||
|
ExternalMessageChannel chan ExternalMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
Username string
|
||||||
|
Conn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExternalMessage struct {
|
||||||
|
From string
|
||||||
|
Target string
|
||||||
|
Message LobbyMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateLobby() *Lobby {
|
||||||
|
externalMessageChan := make(chan ExternalMessage)
|
||||||
|
|
||||||
|
l := Lobby{
|
||||||
|
lobbyMembers: sync.Map{},
|
||||||
|
ExternalMessageChannel: externalMessageChan,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(lm *sync.Map) {
|
||||||
|
for {
|
||||||
|
msg := <-externalMessageChan
|
||||||
|
|
||||||
|
tc, ok := lm.Load(msg.Target)
|
||||||
|
if !ok {
|
||||||
|
slog.Debug("Target not found in lobby map")
|
||||||
|
sc, ok := lm.Load(msg.From)
|
||||||
|
if !ok {
|
||||||
|
slog.Debug("Sender was also not found in lobby map and cannot send error message")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c, ok := sc.(Client)
|
||||||
|
if !ok {
|
||||||
|
slog.Debug("Item that was not a client found in the lobby map...", slog.Any("key", msg.From))
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
em := LobbyMessage{
|
||||||
|
MessageType: "error",
|
||||||
|
Message: Error{
|
||||||
|
Message: fmt.Sprintf("Sorry, player %s is not available...", msg.Target),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b, err := Marshal(em)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("Could not marshall error message for missing player", slog.Any("error", err))
|
||||||
|
}
|
||||||
|
c.Conn.Write(b)
|
||||||
|
}()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c, ok := tc.(Client)
|
||||||
|
if !ok {
|
||||||
|
|
||||||
|
slog.Debug("Item that was not a client found in the lobby map...", slog.Any("key", msg.From))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
b, err := Marshal(msg.Message)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("Could not marshal external message...", slog.Any("error", err))
|
||||||
|
}
|
||||||
|
c.Conn.Write(b)
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
|
}(&l.lobbyMembers)
|
||||||
|
|
||||||
|
return &l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lobby) HandleLobbyConnection(conn net.Conn) {
|
||||||
|
messageBytes := make([]byte, 4096)
|
||||||
|
|
||||||
|
ingress := make(chan LobbyMessage)
|
||||||
|
egress := make(chan LobbyMessage)
|
||||||
|
|
||||||
|
// Network Reader
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
n, err := conn.Read(messageBytes)
|
||||||
|
if err == io.EOF {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
log.Printf("Error reading message %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
message := LobbyMessage{}
|
||||||
|
|
||||||
|
message, err = Unmarshal(messageBytes[:n])
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Invalid message received from client", err)
|
||||||
|
}
|
||||||
|
ingress <- message
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Network Writer
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
msg := <-egress
|
||||||
|
bytes, err := Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error marshalling message to send to user...", err)
|
||||||
|
}
|
||||||
|
_, err = conn.Write(bytes)
|
||||||
|
if err == io.EOF {
|
||||||
|
conn.Close()
|
||||||
|
log.Println("User has disconnected", err)
|
||||||
|
|
||||||
|
// TODO: write message for disconnect to everyone?
|
||||||
|
slog.Debug("Sending bad disconnect message")
|
||||||
|
ingress <- LobbyMessage{MessageType: "disconnect", Message: Disconnect{}}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error writing to user...", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Client message handler
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
msg := <-ingress
|
||||||
|
serverMsg, err := l.handleClientLobbyMessage(&msg, conn)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error handling client lobby message...", err)
|
||||||
|
}
|
||||||
|
if serverMsg.MessageType != "" {
|
||||||
|
egress <- serverMsg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a bool of whether the player has disconnected from the lobby and an error
|
||||||
|
func (l *Lobby) handleClientLobbyMessage(message *LobbyMessage, conn net.Conn) (LobbyMessage, error) {
|
||||||
|
switch message.MessageType {
|
||||||
|
// Handle an name/login message from a player
|
||||||
|
// Store the new player in the l.lobbyMembers
|
||||||
|
// Send a connection message for each of the l.lobbyMembers to the new player
|
||||||
|
// Send a connection message to all members in the lobby
|
||||||
|
case "name":
|
||||||
|
_, ok := l.lobbyMembers.Load(message.Message)
|
||||||
|
if ok {
|
||||||
|
return LobbyMessage{MessageType: "error", Message: Error{Message: "Sorry, that name is already taken, please try a different name"}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
nm, ok := message.Message.(Name)
|
||||||
|
if !ok {
|
||||||
|
return LobbyMessage{MessageType: "error", Message: Error{Message: "Sorry the message value and type were not matching for name"}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
l.lobbyMembers.Store(nm.Name, Client{Username: nm.Name, Conn: conn})
|
||||||
|
|
||||||
|
// Build current lobby list
|
||||||
|
var lobby []string
|
||||||
|
l.lobbyMembers.Range(func(lobbyUsername any, client any) bool {
|
||||||
|
usernameString, _ := lobbyUsername.(string)
|
||||||
|
lobby = append(lobby, usernameString)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
l.broadcastToLobby(LobbyMessage{MessageType: "connect", Message: Name{Name: nm.Name}})
|
||||||
|
|
||||||
|
return LobbyMessage{MessageType: "name", Message: Name{
|
||||||
|
Name: nm.Name,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
// Handle an invite message by sending a message to the target player
|
||||||
|
// Send an invite message to the invitee: message.Content
|
||||||
|
// Send an ack message to the inviter: message.PlayerId
|
||||||
|
case "invite":
|
||||||
|
|
||||||
|
i, ok := message.Message.(Invite)
|
||||||
|
if !ok {
|
||||||
|
return LobbyMessage{MessageType: "error", Message: Error{Message: "Sorry the message value and type were not matching for invite"}}, nil
|
||||||
|
}
|
||||||
|
// TODO: figure out this shit
|
||||||
|
l.ExternalMessageChannel <- ExternalMessage{
|
||||||
|
From: i.From,
|
||||||
|
Target: i.To,
|
||||||
|
Message: LobbyMessage{},
|
||||||
|
}
|
||||||
|
|
||||||
|
return LobbyMessage{MessageType: "pending_invite", Message: PendingInvite{
|
||||||
|
Recipient: i.To,
|
||||||
|
}}, nil
|
||||||
|
|
||||||
|
// Handle a accept message from a player that was invited
|
||||||
|
// Send a game_start message back to the player: message.Content
|
||||||
|
// Send an accepted message back to the inviter: message.PlayerId
|
||||||
|
case "accept":
|
||||||
|
gameID := uuid.NewString()
|
||||||
|
|
||||||
|
am, ok := message.Message.(Accept)
|
||||||
|
if !ok {
|
||||||
|
return LobbyMessage{MessageType: "error", Message: Error{Message: "Sorry the message value and type were not matching for accept"}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Debug("incoming accept message", slog.Any("From", am.From), slog.Any("To", am.To))
|
||||||
|
l.ExternalMessageChannel <- ExternalMessage{
|
||||||
|
Target: am.To,
|
||||||
|
Message: LobbyMessage{MessageType: "accepted", Message: Accepted{
|
||||||
|
Accepter: am.From,
|
||||||
|
GameID: gameID,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
return LobbyMessage{MessageType: "start_game", Message: StartGame{To: am.From, GameID: gameID}}, nil
|
||||||
|
// Handle a chat message from a player with PlayerId
|
||||||
|
case "chat":
|
||||||
|
c, ok := message.Message.(Chat)
|
||||||
|
if !ok {
|
||||||
|
return LobbyMessage{MessageType: "error", Message: Error{Message: "Sorry the message value and type were not matching for chat"}}, nil
|
||||||
|
}
|
||||||
|
l.broadcastToLobby(LobbyMessage{MessageType: "text", Message: Chat{
|
||||||
|
From: c.From,
|
||||||
|
Message: c.Message,
|
||||||
|
}})
|
||||||
|
return LobbyMessage{}, nil
|
||||||
|
|
||||||
|
// Handle a quit message from a player that was connected
|
||||||
|
// broadcast the player quit to the lobby
|
||||||
|
case "quit":
|
||||||
|
q, ok := message.Message.(Disconnect)
|
||||||
|
if !ok {
|
||||||
|
return LobbyMessage{MessageType: "error", Message: Error{Message: "Sorry the message value and type were not matching for quit"}}, nil
|
||||||
|
}
|
||||||
|
l.lobbyMembers.Delete(q.From)
|
||||||
|
l.broadcastToLobby(LobbyMessage{MessageType: "disconnect", Message: Disconnect{
|
||||||
|
From: q.From,
|
||||||
|
}})
|
||||||
|
return LobbyMessage{}, nil
|
||||||
|
|
||||||
|
// Ping and pong
|
||||||
|
case "ping":
|
||||||
|
return LobbyMessage{MessageType: "pong", Message: "pong"}, nil
|
||||||
|
|
||||||
|
// Ping and pong
|
||||||
|
default:
|
||||||
|
return LobbyMessage{MessageType: "pong", Message: "pong"}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Lobby) broadcastToLobby(message LobbyMessage) {
|
||||||
|
var disconnectedUsers []string
|
||||||
|
l.lobbyMembers.Range(func(playerId, player interface{}) bool {
|
||||||
|
bytes, err := Marshal(message)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error marshalling broadcast message", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := player.(Client)
|
||||||
|
_, err = client.Conn.Write(bytes)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error broadcasting to clients...", err)
|
||||||
|
disconnectedUsers = append(disconnectedUsers, playerId.(string))
|
||||||
|
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, player := range disconnectedUsers {
|
||||||
|
l.lobbyMembers.Delete(player)
|
||||||
|
}
|
||||||
|
}
|
244
internal/lobby/lobby_messages.go
Normal file
244
internal/lobby/lobby_messages.go
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
package lobby
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LobbyMessage struct {
|
||||||
|
MessageType string `json:"message_type"`
|
||||||
|
Message any `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Name struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Chat struct {
|
||||||
|
From string `json:"from"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Invite struct {
|
||||||
|
From string `json:"from"`
|
||||||
|
To string `json:"to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PendingInvite struct {
|
||||||
|
Recipient string `json:"recipient"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Accept struct {
|
||||||
|
From string `json:"from"`
|
||||||
|
To string `json:"to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Accepted struct {
|
||||||
|
Accepter string `json:"accepter"`
|
||||||
|
GameID string `json:"game_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StartGame struct {
|
||||||
|
To string `json:"to"`
|
||||||
|
GameID string `json:"game_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Decline struct {
|
||||||
|
From string `json:"from"`
|
||||||
|
To string `json:"to"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Disconnect struct {
|
||||||
|
From string `json:"from"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Connect struct {
|
||||||
|
From string `json:"from"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Marshal(a LobbyMessage) ([]byte, error) {
|
||||||
|
slog.Debug("Marshalling message", slog.Any("message type", a.MessageType))
|
||||||
|
bm, err := json.Marshal(a.Message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Message = bm
|
||||||
|
return json.Marshal(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use this to get the appropriate message type into the message field then assert the
|
||||||
|
// right struct accordingly to safely access the fields you need.
|
||||||
|
func Unmarshal(b []byte) (LobbyMessage, error) {
|
||||||
|
lm := LobbyMessage{}
|
||||||
|
err := json.Unmarshal(b, &lm)
|
||||||
|
if err != nil {
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
|
||||||
|
smsg, ok := lm.Message.(string)
|
||||||
|
if !ok {
|
||||||
|
slog.Debug("error asserting message to string")
|
||||||
|
}
|
||||||
|
slog.Debug("type of message", slog.Any("type of message", reflect.TypeOf(smsg)), slog.String("message", smsg))
|
||||||
|
|
||||||
|
jsonBytes, err := base64.StdEncoding.DecodeString(smsg)
|
||||||
|
lm.Message = jsonBytes
|
||||||
|
|
||||||
|
switch strings.ToLower(lm.MessageType) {
|
||||||
|
case "name":
|
||||||
|
n := Name{}
|
||||||
|
bs, ok := lm.Message.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(bs, &n)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("Error", slog.Any("error", err))
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
lm.Message = n
|
||||||
|
return lm, nil
|
||||||
|
case "chat":
|
||||||
|
c := Chat{}
|
||||||
|
bs, ok := lm.Message.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(bs, &c)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("chat", slog.Any("error", err))
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
lm.Message = c
|
||||||
|
return lm, nil
|
||||||
|
case "invite":
|
||||||
|
i := Invite{}
|
||||||
|
bs, ok := lm.Message.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(bs, &i)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("invite", slog.Any("error", err))
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
lm.Message = i
|
||||||
|
return lm, nil
|
||||||
|
case "pending_invite":
|
||||||
|
pi := PendingInvite{}
|
||||||
|
bs, ok := lm.Message.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(bs, &pi)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("pending_invite", slog.Any("error", err))
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
lm.Message = pi
|
||||||
|
case "accept":
|
||||||
|
a := Accept{}
|
||||||
|
bs, ok := lm.Message.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(bs, &a)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("accept", slog.Any("error", err))
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
lm.Message = a
|
||||||
|
return lm, nil
|
||||||
|
case "accepted":
|
||||||
|
a := Accepted{}
|
||||||
|
bs, ok := lm.Message.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(bs, &a)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("accepted", slog.Any("error", err))
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
lm.Message = a
|
||||||
|
return lm, nil
|
||||||
|
case "start_game":
|
||||||
|
sg := StartGame{}
|
||||||
|
bs, ok := lm.Message.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(bs, &sg)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("start_game", slog.Any("error", err))
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
lm.Message = sg
|
||||||
|
return lm, nil
|
||||||
|
case "decline":
|
||||||
|
d := Decline{}
|
||||||
|
bs, ok := lm.Message.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(bs, &d)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("decline", slog.Any("error", err))
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
lm.Message = d
|
||||||
|
return lm, nil
|
||||||
|
case "disconnect":
|
||||||
|
di := Disconnect{}
|
||||||
|
bs, ok := lm.Message.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(bs, &di)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("disconnect", slog.Any("error", err))
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
lm.Message = di
|
||||||
|
return lm, nil
|
||||||
|
case "connect":
|
||||||
|
co := Connect{}
|
||||||
|
bs, ok := lm.Message.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(bs, &co)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("connect", slog.Any("error", err))
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
lm.Message = co
|
||||||
|
return lm, nil
|
||||||
|
case "error":
|
||||||
|
e := Error{}
|
||||||
|
bs, ok := lm.Message.([]byte)
|
||||||
|
if !ok {
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(bs, &e)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error", slog.Any("error", err))
|
||||||
|
return lm, err
|
||||||
|
}
|
||||||
|
lm.Message = e
|
||||||
|
return lm, nil
|
||||||
|
default:
|
||||||
|
return lm, errors.New("unknown message type")
|
||||||
|
}
|
||||||
|
return lm, errors.New("unknown message type")
|
||||||
|
}
|
|
@ -1,27 +0,0 @@
|
||||||
package netwrk
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ConnectToLobby(username string) (net.Conn, error) {
|
|
||||||
conn, err := net.Dial("tcp", "127.0.0.1:12345")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Sorry, failed to connect to server...")
|
|
||||||
}
|
|
||||||
|
|
||||||
loginMsg, err := proto.Marshal(&LobbyMessage{Type: "name", Content: username})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Sorry bro but your username is wack AF...")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = conn.Write(loginMsg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Sorry, could not communicate with server...")
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
|
@ -1,185 +0,0 @@
|
||||||
package netwrk
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func handleLobbyConnection(conn net.Conn) {
|
|
||||||
messageBytes := make([]byte, 4096)
|
|
||||||
|
|
||||||
ingress := make(chan *LobbyMessage)
|
|
||||||
egress := make(chan *LobbyMessage)
|
|
||||||
|
|
||||||
// Network Reader
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
n, err := conn.Read(messageBytes)
|
|
||||||
if err == io.EOF {
|
|
||||||
conn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
conn.Close()
|
|
||||||
log.Printf("Error reading message %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
message := LobbyMessage{}
|
|
||||||
|
|
||||||
err = proto.Unmarshal(messageBytes[:n], &message)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Invalid message received from client", err)
|
|
||||||
}
|
|
||||||
ingress <- &message
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
conn.Close()
|
|
||||||
log.Println("User has disconnected", err)
|
|
||||||
ingress <- &LobbyMessage{
|
|
||||||
Type: "disconnect",
|
|
||||||
Content: msg.PlayerId,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error writing to user...", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Client message handler
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
msg := <-ingress
|
|
||||||
serverMsg, err := handleClientLobbyMessage(msg, conn)
|
|
||||||
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(message *LobbyMessage, conn net.Conn) (*LobbyMessage, error) {
|
|
||||||
switch message.Type {
|
|
||||||
|
|
||||||
// Handle an name/login message from a player
|
|
||||||
// Store the new player in the lobbyMembers
|
|
||||||
// Send a connection message for each of the lobbyMembers to the new player
|
|
||||||
// Send a connection message to all members in the lobby
|
|
||||||
case "name":
|
|
||||||
_, ok := lobbyMembers.Load(message.Content)
|
|
||||||
if ok {
|
|
||||||
return &LobbyMessage{Type: "name_error", Content: "Sorry, that name is already taken, please try a different name"}, nil
|
|
||||||
}
|
|
||||||
username := message.Content
|
|
||||||
|
|
||||||
lobbyMembers.Store(username, Client{Username: username, Conn: conn})
|
|
||||||
|
|
||||||
// Build current lobby list
|
|
||||||
var lobby []string
|
|
||||||
lobbyMembers.Range(func(lobbyUsername any, client any) bool {
|
|
||||||
usernameString, _ := lobbyUsername.(string)
|
|
||||||
lobby = append(lobby, usernameString)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
broadcastToLobby(&LobbyMessage{PlayerId: "", Type: "connect", Content: username})
|
|
||||||
|
|
||||||
return &LobbyMessage{PlayerId: username, Type: "name", Content: strings.Join(lobby, "\n")}, nil
|
|
||||||
|
|
||||||
// Handle an invite message by sending a message to the target player
|
|
||||||
// Send an invite message to the invitee: message.Content
|
|
||||||
// Send an ack message to the inviter: message.PlayerId
|
|
||||||
case "invite":
|
|
||||||
externalMessageChan <- ExternalMessage{
|
|
||||||
Target: message.Content,
|
|
||||||
Message: message,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &LobbyMessage{Type: "pending_invite", Content: message.Content}, nil
|
|
||||||
|
|
||||||
// Handle a accept message from a player that was invited
|
|
||||||
// Send a game_start message back to the player: message.PlayerId
|
|
||||||
// Send an accepted message back to the inviter: message.Content
|
|
||||||
case "accept":
|
|
||||||
externalMessageChan <- ExternalMessage{
|
|
||||||
Target: message.Content,
|
|
||||||
Message: &LobbyMessage{Type: "game_start", Content: ""},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &LobbyMessage{PlayerId: message.PlayerId, Type: "accepted", Content: ""}, nil
|
|
||||||
|
|
||||||
// Handle a chat message from a player with PlayerId
|
|
||||||
case "chat":
|
|
||||||
broadcastToLobby(&LobbyMessage{PlayerId: message.PlayerId, Type: "text", Content: message.Content})
|
|
||||||
return nil, nil
|
|
||||||
|
|
||||||
// Handle a decline_game message from a player that was invited
|
|
||||||
// Send an ack message back to the invitee: message.PlayerId
|
|
||||||
// Send an ack message to the inviter: message.Content
|
|
||||||
case "decline_game":
|
|
||||||
externalMessageChan <- ExternalMessage{
|
|
||||||
Target: message.Content,
|
|
||||||
Message: &LobbyMessage{Type: "decline", Content: ""},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &LobbyMessage{Type: "decline_game", Content: message.PlayerId}, nil
|
|
||||||
|
|
||||||
// Handle a quit message from a player that was connected
|
|
||||||
// broadcast the player quit to the lobby
|
|
||||||
case "quit":
|
|
||||||
lobbyMembers.Delete(message.PlayerId)
|
|
||||||
broadcastToLobby(&LobbyMessage{Type: "disconnect", Content: message.PlayerId})
|
|
||||||
return nil, nil
|
|
||||||
|
|
||||||
// Ping and pong
|
|
||||||
case "ping":
|
|
||||||
return &LobbyMessage{Type: "pong", Content: "pong"}, nil
|
|
||||||
|
|
||||||
// Ping and pong
|
|
||||||
default:
|
|
||||||
return &LobbyMessage{Type: "pong", Content: "pong"}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func broadcastToLobby(message *LobbyMessage) {
|
|
||||||
var disconnectedUsers []string
|
|
||||||
lobbyMembers.Range(func(playerId, player interface{}) bool {
|
|
||||||
bytes, err := proto.Marshal(message)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error marshalling broadcast message", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
client := player.(Client)
|
|
||||||
_, err = client.Conn.Write(bytes)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error broadcasting to clients...", err)
|
|
||||||
disconnectedUsers = append(disconnectedUsers, playerId.(string))
|
|
||||||
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, player := range disconnectedUsers {
|
|
||||||
lobbyMembers.Delete(player)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,162 +0,0 @@
|
||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// protoc-gen-go v1.34.2
|
|
||||||
// protoc v5.27.3
|
|
||||||
// source: proto/lobby_messages.proto
|
|
||||||
|
|
||||||
package netwrk
|
|
||||||
|
|
||||||
import (
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
|
||||||
reflect "reflect"
|
|
||||||
sync "sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Verify that this generated code is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
|
||||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
|
||||||
)
|
|
||||||
|
|
||||||
type LobbyMessage struct {
|
|
||||||
state protoimpl.MessageState
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
|
|
||||||
PlayerId string `protobuf:"bytes,1,opt,name=player_id,json=playerId,proto3" json:"player_id,omitempty"`
|
|
||||||
Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"`
|
|
||||||
Content string `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *LobbyMessage) Reset() {
|
|
||||||
*x = LobbyMessage{}
|
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_proto_lobby_messages_proto_msgTypes[0]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *LobbyMessage) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*LobbyMessage) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *LobbyMessage) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_proto_lobby_messages_proto_msgTypes[0]
|
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use LobbyMessage.ProtoReflect.Descriptor instead.
|
|
||||||
func (*LobbyMessage) Descriptor() ([]byte, []int) {
|
|
||||||
return file_proto_lobby_messages_proto_rawDescGZIP(), []int{0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *LobbyMessage) GetPlayerId() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.PlayerId
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *LobbyMessage) GetType() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Type
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *LobbyMessage) GetContent() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Content
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
var File_proto_lobby_messages_proto protoreflect.FileDescriptor
|
|
||||||
|
|
||||||
var file_proto_lobby_messages_proto_rawDesc = []byte{
|
|
||||||
0x0a, 0x1a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6c, 0x6f, 0x62, 0x62, 0x79, 0x5f, 0x6d, 0x65,
|
|
||||||
0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, 0x6e, 0x65,
|
|
||||||
0x74, 0x77, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x22, 0x59, 0x0a, 0x0c, 0x4c, 0x6f, 0x62, 0x62, 0x79,
|
|
||||||
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6c, 0x61, 0x79, 0x65,
|
|
||||||
0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x79,
|
|
||||||
0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01,
|
|
||||||
0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74,
|
|
||||||
0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65,
|
|
||||||
0x6e, 0x74, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x2f, 0x6e, 0x65, 0x74, 0x77, 0x72, 0x6b, 0x62, 0x06,
|
|
||||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
file_proto_lobby_messages_proto_rawDescOnce sync.Once
|
|
||||||
file_proto_lobby_messages_proto_rawDescData = file_proto_lobby_messages_proto_rawDesc
|
|
||||||
)
|
|
||||||
|
|
||||||
func file_proto_lobby_messages_proto_rawDescGZIP() []byte {
|
|
||||||
file_proto_lobby_messages_proto_rawDescOnce.Do(func() {
|
|
||||||
file_proto_lobby_messages_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_lobby_messages_proto_rawDescData)
|
|
||||||
})
|
|
||||||
return file_proto_lobby_messages_proto_rawDescData
|
|
||||||
}
|
|
||||||
|
|
||||||
var file_proto_lobby_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
|
||||||
var file_proto_lobby_messages_proto_goTypes = []any{
|
|
||||||
(*LobbyMessage)(nil), // 0: netwrk.v1.LobbyMessage
|
|
||||||
}
|
|
||||||
var file_proto_lobby_messages_proto_depIdxs = []int32{
|
|
||||||
0, // [0:0] is the sub-list for method output_type
|
|
||||||
0, // [0:0] is the sub-list for method input_type
|
|
||||||
0, // [0:0] is the sub-list for extension type_name
|
|
||||||
0, // [0:0] is the sub-list for extension extendee
|
|
||||||
0, // [0:0] is the sub-list for field type_name
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { file_proto_lobby_messages_proto_init() }
|
|
||||||
func file_proto_lobby_messages_proto_init() {
|
|
||||||
if File_proto_lobby_messages_proto != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !protoimpl.UnsafeEnabled {
|
|
||||||
file_proto_lobby_messages_proto_msgTypes[0].Exporter = func(v any, i int) any {
|
|
||||||
switch v := v.(*LobbyMessage); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type x struct{}
|
|
||||||
out := protoimpl.TypeBuilder{
|
|
||||||
File: protoimpl.DescBuilder{
|
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
|
||||||
RawDescriptor: file_proto_lobby_messages_proto_rawDesc,
|
|
||||||
NumEnums: 0,
|
|
||||||
NumMessages: 1,
|
|
||||||
NumExtensions: 0,
|
|
||||||
NumServices: 0,
|
|
||||||
},
|
|
||||||
GoTypes: file_proto_lobby_messages_proto_goTypes,
|
|
||||||
DependencyIndexes: file_proto_lobby_messages_proto_depIdxs,
|
|
||||||
MessageInfos: file_proto_lobby_messages_proto_msgTypes,
|
|
||||||
}.Build()
|
|
||||||
File_proto_lobby_messages_proto = out.File
|
|
||||||
file_proto_lobby_messages_proto_rawDesc = nil
|
|
||||||
file_proto_lobby_messages_proto_goTypes = nil
|
|
||||||
file_proto_lobby_messages_proto_depIdxs = nil
|
|
||||||
}
|
|
|
@ -1,127 +0,0 @@
|
||||||
package netwrk
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"sshpong/internal/pong"
|
|
||||||
"strings"
|
|
||||||
sync "sync"
|
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
Username string
|
|
||||||
Conn net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExternalMessage struct {
|
|
||||||
Target string
|
|
||||||
Message *LobbyMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
type GameClients struct {
|
|
||||||
Client1 Client
|
|
||||||
Client2 Client
|
|
||||||
}
|
|
||||||
|
|
||||||
var externalMessageChan chan ExternalMessage
|
|
||||||
|
|
||||||
var lobbyMembers sync.Map
|
|
||||||
var games sync.Map
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
externalMessageChan = make(chan ExternalMessage)
|
|
||||||
|
|
||||||
lobbyMembers = sync.Map{}
|
|
||||||
games = sync.Map{}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
msg := <-externalMessageChan
|
|
||||||
player, ok := lobbyMembers.Load(msg.Target)
|
|
||||||
if !ok {
|
|
||||||
log.Println("failed to send to target", msg.Target)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
client, _ := player.(Client)
|
|
||||||
bytes, _ := proto.Marshal(msg.Message)
|
|
||||||
_, err := client.Conn.Write(bytes)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Could not write to target", msg.Target, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starts listening on port 12345 for TCP connections
|
|
||||||
// Also creates client pool and game connection singletons
|
|
||||||
func LobbyListen() {
|
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", "127.0.0.1:12345")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer listener.Close()
|
|
||||||
|
|
||||||
for {
|
|
||||||
conn, err := listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
go handleLobbyConnection(conn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GamesListen() {
|
|
||||||
gameListener, err := net.Listen("tcp", "127.0.0.1:42069")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer gameListener.Close()
|
|
||||||
for {
|
|
||||||
conn, err := gameListener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
go func(conn net.Conn) {
|
|
||||||
messageBytes := make([]byte, 126)
|
|
||||||
|
|
||||||
n, err := conn.Read(messageBytes)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error reading game ID on connection %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
gInfo := strings.SplitAfter(string(messageBytes[:n]), ":")
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Game id was not a string? %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
game, ok := games.Load(gInfo[0])
|
|
||||||
if !ok {
|
|
||||||
games.Store(gInfo[0], GameClients{Client1: Client{
|
|
||||||
Username: gInfo[1],
|
|
||||||
Conn: conn,
|
|
||||||
}, Client2: Client{}})
|
|
||||||
} else {
|
|
||||||
gameclients, _ := game.(GameClients)
|
|
||||||
client2 := Client{
|
|
||||||
Username: gInfo[1],
|
|
||||||
Conn: conn,
|
|
||||||
}
|
|
||||||
|
|
||||||
games.Store(gInfo[0], GameClients{
|
|
||||||
Client1: gameclients.Client1,
|
|
||||||
Client2: client2})
|
|
||||||
|
|
||||||
go pong.StartGame(gameclients.Client1.Conn, client2.Conn, gameclients.Client1.Username, client2.Username)
|
|
||||||
}
|
|
||||||
}(conn)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +1,15 @@
|
||||||
package pong
|
package pong
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/exp/rand"
|
"golang.org/x/exp/rand"
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type GameClient struct {
|
type GameClient struct {
|
||||||
|
@ -20,38 +20,71 @@ type GameClient struct {
|
||||||
var player1 GameClient
|
var player1 GameClient
|
||||||
var player2 GameClient
|
var player2 GameClient
|
||||||
|
|
||||||
var ingress chan *ClientUpdateRequest
|
var ingress chan StateUpdate
|
||||||
var egress chan *ServerUpdateMessage
|
var egress chan StateUpdate
|
||||||
|
|
||||||
const posXBound = 50
|
const posXBound = 100
|
||||||
const negXBound = posXBound * -1
|
const negXBound = posXBound * -1
|
||||||
const posYBound = 50
|
const posYBound = 50
|
||||||
const negYBound = posYBound * -1
|
const negYBound = posYBound * -1
|
||||||
|
|
||||||
func StartGame(conn1, conn2 net.Conn, username1, username2 string) {
|
func StartGame(conn1, conn2 net.Conn, username1, username2 string) {
|
||||||
|
|
||||||
|
egress = make(chan StateUpdate)
|
||||||
|
ingress = make(chan StateUpdate)
|
||||||
|
|
||||||
player1 = GameClient{
|
player1 = GameClient{
|
||||||
Username: username1,
|
Username: username1,
|
||||||
Conn: conn1,
|
Conn: conn1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p1msg := StateUpdate{
|
||||||
|
FieldPath: "isPlayer1",
|
||||||
|
Value: []byte{1},
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(p1msg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = player1.Conn.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error writing player1 msg to player1")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
player2 = GameClient{
|
player2 = GameClient{
|
||||||
Username: username2,
|
Username: username2,
|
||||||
Conn: conn2,
|
Conn: conn2,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p2msg := StateUpdate{
|
||||||
|
FieldPath: "isPlayer1",
|
||||||
|
Value: []byte{0},
|
||||||
|
}
|
||||||
|
b, err = json.Marshal(p2msg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = player2.Conn.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error writing player1 msg to player2")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
broadcastUpdate(&ServerUpdateMessage{
|
broadcastUpdate(StateUpdate{
|
||||||
Type: "message",
|
FieldPath: "Message",
|
||||||
Value: "Ready...",
|
Value: []byte("Ready..."),
|
||||||
})
|
})
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
broadcastUpdate(&ServerUpdateMessage{
|
broadcastUpdate(StateUpdate{
|
||||||
Type: "message",
|
FieldPath: "Message",
|
||||||
Value: "Set...",
|
Value: []byte("Set..."),
|
||||||
})
|
})
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
broadcastUpdate(&ServerUpdateMessage{
|
broadcastUpdate(StateUpdate{
|
||||||
Type: "message",
|
FieldPath: "Message",
|
||||||
Value: "Go!",
|
Value: []byte("Go!"),
|
||||||
})
|
})
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
bv := float32(rand.Intn(2)*2 - 1)
|
bv := float32(rand.Intn(2)*2 - 1)
|
||||||
|
@ -68,7 +101,6 @@ func StartGame(conn1, conn2 net.Conn, username1, username2 string) {
|
||||||
X: 1,
|
X: 1,
|
||||||
Y: 10,
|
Y: 10,
|
||||||
},
|
},
|
||||||
Speed: 0,
|
|
||||||
},
|
},
|
||||||
Player2: Player{
|
Player2: Player{
|
||||||
client: player2,
|
client: player2,
|
||||||
|
@ -80,7 +112,6 @@ func StartGame(conn1, conn2 net.Conn, username1, username2 string) {
|
||||||
X: 1,
|
X: 1,
|
||||||
Y: 10,
|
Y: 10,
|
||||||
},
|
},
|
||||||
Speed: 0,
|
|
||||||
},
|
},
|
||||||
Ball: Ball{
|
Ball: Ball{
|
||||||
Pos: Vector{
|
Pos: Vector{
|
||||||
|
@ -93,23 +124,22 @@ func StartGame(conn1, conn2 net.Conn, username1, username2 string) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
go gameLoop(state)
|
go gameLoop(&state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func gameLoop(state GameState) {
|
func gameLoop(state *GameState) {
|
||||||
// Player 1 read loop
|
// Player 1 read loop
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
|
|
||||||
bytes := make([]byte, 512)
|
bytes := make([]byte, 512)
|
||||||
n, err := state.Player1.client.Conn.Read(bytes)
|
n, err := state.Player1.client.Conn.Read(bytes)
|
||||||
msg := &ClientUpdateRequest{}
|
msg := StateUpdate{}
|
||||||
err = proto.Unmarshal(bytes[:n], msg)
|
err = json.Unmarshal(bytes[:n], &msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("error reading player 1's update request:", err)
|
log.Println("error reading player 1's update request:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
msg.Player = 1
|
|
||||||
ingress <- msg
|
ingress <- msg
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -120,13 +150,12 @@ func gameLoop(state GameState) {
|
||||||
|
|
||||||
bytes := make([]byte, 512)
|
bytes := make([]byte, 512)
|
||||||
n, err := state.Player2.client.Conn.Read(bytes)
|
n, err := state.Player2.client.Conn.Read(bytes)
|
||||||
msg := &ClientUpdateRequest{}
|
msg := StateUpdate{}
|
||||||
err = proto.Unmarshal(bytes[:n], msg)
|
err = json.Unmarshal(bytes[:n], &msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("error reading player 2's update request:", err)
|
log.Println("error reading player 2's update request:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
msg.Player = 2
|
|
||||||
ingress <- msg
|
ingress <- msg
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -143,20 +172,38 @@ func gameLoop(state GameState) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case msg := <-ingress:
|
case msg := <-ingress:
|
||||||
err := handlePlayerRequest(&state, msg)
|
err := handlePlayerRequest(msg, state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("FUCK!~", err)
|
fmt.Println("FUCK!~", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
case _ = <-ticker.C:
|
case _ = <-ticker.C:
|
||||||
update := process(&state)
|
update := process(state)
|
||||||
egress <- &update
|
egress <- update
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func process(state *GameState) ServerUpdateMessage {
|
func process(state *GameState) StateUpdate {
|
||||||
|
|
||||||
|
// Move players
|
||||||
|
// Check if player edge is out of bounds
|
||||||
|
// If out of bounds reset velocity to zero and position to edge
|
||||||
|
|
||||||
|
if state.Player1.Pos.Y+state.Player1.Size.Y/2 > posYBound {
|
||||||
|
state.Player1.Pos.Y = posYBound - state.Player1.Size.Y/2 - 1
|
||||||
|
}
|
||||||
|
if state.Player1.Pos.Y-state.Player1.Size.Y/2 < negYBound {
|
||||||
|
state.Player1.Pos.Y = negYBound + state.Player1.Size.Y/2 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.Player2.Pos.Y+state.Player2.Size.Y/2 > posYBound {
|
||||||
|
state.Player2.Pos.Y = posYBound - state.Player2.Size.Y/2 - 1
|
||||||
|
}
|
||||||
|
if state.Player2.Pos.Y-state.Player2.Size.Y/2 < negYBound {
|
||||||
|
state.Player2.Pos.Y = negYBound + state.Player2.Size.Y/2 + 1
|
||||||
|
}
|
||||||
|
|
||||||
// Move ball
|
// Move ball
|
||||||
// Check if ball is out of bounds
|
// Check if ball is out of bounds
|
||||||
|
@ -192,9 +239,9 @@ func process(state *GameState) ServerUpdateMessage {
|
||||||
state.Ball.Vel.X = 1
|
state.Ball.Vel.X = 1
|
||||||
state.Ball.Vel.Y = 0
|
state.Ball.Vel.Y = 0
|
||||||
if state.Score[player2.Username] >= 9 {
|
if state.Score[player2.Username] >= 9 {
|
||||||
return ServerUpdateMessage{
|
return StateUpdate{
|
||||||
Type: "gameover",
|
FieldPath: "Winner",
|
||||||
Value: player2.Username,
|
Value: []byte(player2.Username),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.Score[player2.Username] = state.Score[player2.Username] + 1
|
state.Score[player2.Username] = state.Score[player2.Username] + 1
|
||||||
|
@ -215,49 +262,79 @@ func process(state *GameState) ServerUpdateMessage {
|
||||||
state.Ball.Vel.X = -1
|
state.Ball.Vel.X = -1
|
||||||
state.Ball.Vel.Y = 0
|
state.Ball.Vel.Y = 0
|
||||||
if state.Score[player1.Username] >= 9 {
|
if state.Score[player1.Username] >= 9 {
|
||||||
return ServerUpdateMessage{
|
return StateUpdate{
|
||||||
Type: "gameover",
|
FieldPath: "Winner",
|
||||||
Value: player1.Username,
|
Value: []byte(player1.Username),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.Score[player1.Username] = state.Score[player1.Username] + 1
|
state.Score[player1.Username] = state.Score[player1.Username] + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ServerUpdateMessage{}
|
ns, err := json.Marshal(state)
|
||||||
}
|
if err != nil {
|
||||||
|
slog.Debug("error marshalling entire state update", slog.Any("error", err))
|
||||||
func handlePlayerRequest(state *GameState, msg *ClientUpdateRequest) error {
|
|
||||||
|
|
||||||
switch msg.Type {
|
|
||||||
case "player_pos":
|
|
||||||
if msg.Player == 1 {
|
|
||||||
pos := strings.Split(msg.Value, " ")
|
|
||||||
x, err := strconv.ParseFloat(pos[0], 32)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Got weird position update for x", err)
|
|
||||||
}
|
|
||||||
y, err := strconv.ParseFloat(pos[1], 32)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Got weird position update for y", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
state.Player1.Pos = Vector{
|
|
||||||
X: float32(x),
|
|
||||||
Y: float32(y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
fmt.Println("Got unhandled update", msg.Type)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return StateUpdate{
|
||||||
|
FieldPath: "All",
|
||||||
|
Value: ns,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlePlayerRequest(update StateUpdate, state *GameState) error {
|
||||||
|
|
||||||
|
fields := strings.Split(update.FieldPath, ".")
|
||||||
|
|
||||||
|
// type GameState struct {
|
||||||
|
// Message string
|
||||||
|
// Winner string
|
||||||
|
// Score map[string]int
|
||||||
|
// Player1 Player
|
||||||
|
// Player2 Player
|
||||||
|
// Ball Ball
|
||||||
|
// }
|
||||||
|
|
||||||
|
switch fields[0] {
|
||||||
|
case "Message":
|
||||||
|
state.Message = string(update.Value)
|
||||||
|
case "Winner":
|
||||||
|
state.Winner = string(update.Value)
|
||||||
|
case "Player1":
|
||||||
|
switch fields[1] {
|
||||||
|
case "Pos":
|
||||||
|
v1 := Vector{}
|
||||||
|
err := json.Unmarshal(update.Value, &v1)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error unmarshalling player1 update")
|
||||||
|
}
|
||||||
|
state.Player1.Pos = v1
|
||||||
|
}
|
||||||
|
case "Player2":
|
||||||
|
switch fields[1] {
|
||||||
|
case "Pos":
|
||||||
|
v2 := Vector{}
|
||||||
|
err := json.Unmarshal(update.Value, &v2)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error unmarshalling player2 update")
|
||||||
|
}
|
||||||
|
state.Player2.Pos = v2
|
||||||
|
}
|
||||||
|
case "Ball":
|
||||||
|
b := Ball{}
|
||||||
|
err := json.Unmarshal(update.Value, &b)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("error unmarshalling ball update")
|
||||||
|
}
|
||||||
|
state.Ball = b
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func broadcastUpdate(update *ServerUpdateMessage) error {
|
func broadcastUpdate(update StateUpdate) error {
|
||||||
msg, err := proto.Marshal(update)
|
msg, err := json.Marshal(update)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("malformed server update message %v", err)
|
return err
|
||||||
}
|
}
|
||||||
_, err = player1.Conn.Write(msg)
|
_, err = player1.Conn.Write(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package pong
|
package pong
|
||||||
|
|
||||||
type GameState struct {
|
type GameState struct {
|
||||||
|
Message string
|
||||||
|
Winner string
|
||||||
Score map[string]int
|
Score map[string]int
|
||||||
Player1 Player
|
Player1 Player
|
||||||
Player2 Player
|
Player2 Player
|
||||||
|
@ -16,10 +18,16 @@ type Player struct {
|
||||||
client GameClient
|
client GameClient
|
||||||
Pos Vector
|
Pos Vector
|
||||||
Size Vector
|
Size Vector
|
||||||
Speed float32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ball struct {
|
type Ball struct {
|
||||||
Pos Vector
|
Pos Vector
|
||||||
Vel Vector
|
Vel Vector
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type StateUpdate struct {
|
||||||
|
// The field to update on the state object dot separated
|
||||||
|
// I.e Player1.Speed = the speed field on Player1
|
||||||
|
FieldPath string
|
||||||
|
Value []byte
|
||||||
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sshpong/internal/ansii"
|
"sshpong/internal/ansii"
|
||||||
|
"sshpong/internal/pong"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -19,17 +19,11 @@ var (
|
||||||
playerY int = 10
|
playerY int = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
func Render() {
|
func Render(state pong.GameState) {
|
||||||
now := time.Now()
|
// drawScreen(state)
|
||||||
err := doFpsTest()
|
fmt.Print("\033c")
|
||||||
if err != nil {
|
fmt.Println("Player 1", state.Player1.Pos.X, state.Player1.Pos.Y)
|
||||||
fmt.Println("Error ", err)
|
fmt.Println("Player 2", state.Player2.Pos.X, state.Player2.Pos.Y)
|
||||||
return
|
|
||||||
}
|
|
||||||
then := time.Now()
|
|
||||||
|
|
||||||
total := float64(float64(then.UnixMicro()-now.UnixMicro()) / 1000.0)
|
|
||||||
fmt.Printf("\n\nTook %.3f milliseconds\n", total)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeCheckerBoard(height int, width int, builder *strings.Builder) {
|
func writeCheckerBoard(height int, width int, builder *strings.Builder) {
|
||||||
|
@ -53,25 +47,22 @@ func writeCheckerBoard(height int, width int, builder *strings.Builder) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawScreen(frameNum int, startMs float64) (frameTimeMs float64) {
|
func drawScreen(state pong.GameState) {
|
||||||
_ = frameNum
|
// width := 100
|
||||||
_, height := ansii.GetTermSize()
|
// height := 50
|
||||||
var builder = strings.Builder{}
|
var builder = strings.Builder{}
|
||||||
builder.WriteString(string(ansii.Screen.ClearScreen))
|
builder.WriteString(string(ansii.Screen.ClearScreen))
|
||||||
// writeCheckerBoard(height, width, &builder)
|
ansii.DrawBox(&builder, ansii.Offset{X: int(state.Player1.Pos.X), Y: int(state.Player1.Pos.Y)}, 5, 1, ansii.Colors.Cyan)
|
||||||
// var xOffset = frameNum % width
|
ansii.DrawPixelStyle(&builder, ansii.Offset{X: int(state.Player1.Pos.X), Y: int(state.Player1.Pos.Y)}, ansii.Colors.Purple)
|
||||||
// ansii.DrawBox(&builder, ansii.Offset{X: 0, Y: 0}, 5, 8, ansii.Colors.Purple)
|
ansii.DrawPixelStyle(&builder, ansii.Offset{X: int(state.Player1.Pos.X), Y: int(state.Player1.Pos.Y) + 5}, ansii.Colors.Purple)
|
||||||
ansii.DrawBox(&builder, ansii.Offset{X: playerX, Y: playerY}, 5, 1, ansii.Colors.Cyan)
|
ansii.DrawBox(&builder, ansii.Offset{X: int(state.Player2.Pos.X), Y: int(state.Player2.Pos.Y)}, 5, 1, ansii.Colors.Cyan)
|
||||||
ansii.DrawPixelStyle(&builder, ansii.Offset{X: playerX, Y: playerY}, ansii.Colors.Purple)
|
ansii.DrawPixelStyle(&builder, ansii.Offset{X: int(state.Player2.Pos.X), Y: int(state.Player2.Pos.Y)}, ansii.Colors.Purple)
|
||||||
ansii.DrawPixelStyle(&builder, ansii.Offset{X: playerX, Y: playerY + 5}, ansii.Colors.Purple)
|
ansii.DrawPixelStyle(&builder, ansii.Offset{X: int(state.Player2.Pos.X), Y: int(state.Player2.Pos.Y) + 5}, ansii.Colors.Purple)
|
||||||
// Quit instructions
|
// Quit instructions
|
||||||
builder.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: 0, Y: height})))
|
// builder.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: 0, Y: height})))
|
||||||
builder.WriteString("q to quit")
|
// builder.WriteString("q to quit")
|
||||||
|
|
||||||
os.Stdout.WriteString(builder.String())
|
os.Stdout.WriteString(builder.String())
|
||||||
var frameTimeMilli = (float64(time.Now().UnixNano()) / 1_000_000.0) - startMs
|
|
||||||
// builder.WriteString(string(ansii.Screen.Coordinate(0, 0)))
|
|
||||||
return frameTimeMilli
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawFrameStats(frameNum int, frameTimeMs float64) {
|
func drawFrameStats(frameNum int, frameTimeMs float64) {
|
||||||
|
@ -117,79 +108,3 @@ func waitForFpsLock(startMs float64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func doFpsTest() error {
|
|
||||||
prev, err := ansii.MakeTermRaw()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer ansii.RestoreTerm(prev)
|
|
||||||
quit = make(chan bool, 1)
|
|
||||||
userInput = make(chan rune, 1)
|
|
||||||
|
|
||||||
// User input loop
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
buf := make([]byte, 3)
|
|
||||||
n, err := os.Stdin.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error reading from stdin: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if n > 0 {
|
|
||||||
if buf[0] == 0x1b { // ESC
|
|
||||||
if n > 1 && buf[1] == '[' { // ESC [
|
|
||||||
switch buf[2] {
|
|
||||||
case 'A':
|
|
||||||
userInput <- '↑' // Up arrow
|
|
||||||
case 'B':
|
|
||||||
userInput <- '↓' // Down arrow
|
|
||||||
case 'C':
|
|
||||||
userInput <- '→' // Right arrow
|
|
||||||
case 'D':
|
|
||||||
userInput <- '←' // Left arrow
|
|
||||||
default:
|
|
||||||
userInput <- '?'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
userInput <- '?'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
r, _ := utf8.DecodeRune(buf)
|
|
||||||
userInput <- r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
// Rendering loop
|
|
||||||
go func() {
|
|
||||||
for i := 0; i <= 10_000; i++ {
|
|
||||||
startMs := float64(time.Now().UnixNano()) / 1_000_000.0
|
|
||||||
select {
|
|
||||||
case <-quit:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
frameTimeMs := drawScreen(i, startMs)
|
|
||||||
drawFrameStats(i, frameTimeMs)
|
|
||||||
waitForFpsLock(startMs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(quit)
|
|
||||||
}()
|
|
||||||
|
|
||||||
os.Stdout.WriteString(string(ansii.Screen.HideCursor))
|
|
||||||
defer os.Stdout.WriteString(string(ansii.Screen.ShowCursor))
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-quit:
|
|
||||||
fmt.Println("Exiting")
|
|
||||||
return nil
|
|
||||||
// case ui := <-userInput:
|
|
||||||
case input := <-userInput:
|
|
||||||
handleInput(input)
|
|
||||||
// fmt.Println(ui)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user