MVP
This commit is contained in:
parent
15e7f20f1a
commit
067d22f3a9
51
.air_client.toml
Normal file
51
.air_client.toml
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
root = "."
|
||||||
|
testdata_dir = "testdata"
|
||||||
|
tmp_dir = "tmp"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
args_bin = []
|
||||||
|
bin = "./tmp/client/main"
|
||||||
|
cmd = "go build -o ./tmp/client/ ./cmd/client/main.go"
|
||||||
|
delay = 1000
|
||||||
|
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||||
|
exclude_file = []
|
||||||
|
exclude_regex = ["_test.go"]
|
||||||
|
exclude_unchanged = false
|
||||||
|
follow_symlink = false
|
||||||
|
full_bin = ""
|
||||||
|
include_dir = []
|
||||||
|
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||||
|
include_file = []
|
||||||
|
kill_delay = "0s"
|
||||||
|
log = "build-errors.log"
|
||||||
|
poll = false
|
||||||
|
poll_interval = 0
|
||||||
|
post_cmd = []
|
||||||
|
pre_cmd = []
|
||||||
|
rerun = false
|
||||||
|
rerun_delay = 500
|
||||||
|
send_interrupt = false
|
||||||
|
stop_on_error = false
|
||||||
|
|
||||||
|
[color]
|
||||||
|
app = ""
|
||||||
|
build = "yellow"
|
||||||
|
main = "magenta"
|
||||||
|
runner = "green"
|
||||||
|
watcher = "cyan"
|
||||||
|
|
||||||
|
[log]
|
||||||
|
main_only = false
|
||||||
|
time = false
|
||||||
|
|
||||||
|
[misc]
|
||||||
|
clean_on_exit = false
|
||||||
|
|
||||||
|
[proxy]
|
||||||
|
app_port = 0
|
||||||
|
enabled = false
|
||||||
|
proxy_port = 0
|
||||||
|
|
||||||
|
[screen]
|
||||||
|
clear_on_rebuild = false
|
||||||
|
keep_scroll = true
|
51
.air_server.toml
Normal file
51
.air_server.toml
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
root = "."
|
||||||
|
testdata_dir = "testdata"
|
||||||
|
tmp_dir = "tmp"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
args_bin = []
|
||||||
|
bin = "./tmp/server/main"
|
||||||
|
cmd = "go build -o ./tmp/server/ ./cmd/server/main.go"
|
||||||
|
delay = 1000
|
||||||
|
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||||
|
exclude_file = []
|
||||||
|
exclude_regex = ["_test.go"]
|
||||||
|
exclude_unchanged = false
|
||||||
|
follow_symlink = false
|
||||||
|
full_bin = ""
|
||||||
|
include_dir = []
|
||||||
|
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||||
|
include_file = []
|
||||||
|
kill_delay = "0s"
|
||||||
|
log = "build-errors.log"
|
||||||
|
poll = false
|
||||||
|
poll_interval = 0
|
||||||
|
post_cmd = []
|
||||||
|
pre_cmd = []
|
||||||
|
rerun = false
|
||||||
|
rerun_delay = 500
|
||||||
|
send_interrupt = false
|
||||||
|
stop_on_error = false
|
||||||
|
|
||||||
|
[color]
|
||||||
|
app = ""
|
||||||
|
build = "yellow"
|
||||||
|
main = "magenta"
|
||||||
|
runner = "green"
|
||||||
|
watcher = "cyan"
|
||||||
|
|
||||||
|
[log]
|
||||||
|
main_only = false
|
||||||
|
time = false
|
||||||
|
|
||||||
|
[misc]
|
||||||
|
clean_on_exit = false
|
||||||
|
|
||||||
|
[proxy]
|
||||||
|
app_port = 0
|
||||||
|
enabled = false
|
||||||
|
proxy_port = 0
|
||||||
|
|
||||||
|
[screen]
|
||||||
|
clear_on_rebuild = false
|
||||||
|
keep_scroll = true
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sshpong/internal/client"
|
"sshpong/internal/client"
|
||||||
|
"sshpong/internal/config"
|
||||||
"sshpong/internal/lobby"
|
"sshpong/internal/lobby"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -15,25 +16,43 @@ import (
|
||||||
var username string
|
var username string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
if len(os.Args) == 1 {
|
||||||
slog.Debug("Debug logs active...")
|
config.LoadConfig("")
|
||||||
|
} else {
|
||||||
|
config.LoadConfig(os.Args[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.SetLogLoggerLevel(slog.Level(config.Config.LogLevel))
|
||||||
|
|
||||||
fmt.Println("Welcome to sshpong!")
|
fmt.Println("Welcome to sshpong!")
|
||||||
fmt.Println("Please enter your username")
|
|
||||||
|
|
||||||
egress := make(chan []byte)
|
egress := make(chan []byte)
|
||||||
ingress := make(chan []byte)
|
ingress := make(chan []byte)
|
||||||
interrupter := make(chan client.InterrupterMessage, 100)
|
interrupter := make(chan client.InterrupterMessage, 100)
|
||||||
exit := make(chan string)
|
exit := make(chan string)
|
||||||
|
|
||||||
buf := make([]byte, 1024)
|
var usernameOk = false
|
||||||
n, err := os.Stdin.Read(buf)
|
|
||||||
if err != nil {
|
// In the future make a DB call as well?
|
||||||
log.Panic("Bro your input is no good...")
|
isUsernameOk := func(un string) bool {
|
||||||
}
|
if strings.Contains(un, ":") || len(strings.Split(un, " ")) > 1 || len(un) < 1 {
|
||||||
username = string(buf[:n-1])
|
fmt.Println(client.Red, "Sorry, please pick a username that has no special characters or spaces.", client.Normal)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for !usernameOk {
|
||||||
|
fmt.Println("Please enter your username")
|
||||||
|
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-1])
|
||||||
|
usernameOk = isUsernameOk(username)
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println("username is...", username)
|
|
||||||
conn, err := ConnectToLobby(username)
|
conn, err := ConnectToLobby(username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
|
@ -55,6 +74,11 @@ func main() {
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case msg := <-interrupter:
|
case msg := <-interrupter:
|
||||||
|
if msg.InterruptType == "start_game" {
|
||||||
|
slog.Debug("closing input handler with start_game message and sending exit signal")
|
||||||
|
exit <- msg.Content
|
||||||
|
return
|
||||||
|
}
|
||||||
userMessage, err := client.HandleInterruptInput(msg, args, username)
|
userMessage, err := client.HandleInterruptInput(msg, args, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
userMessage, err = client.HandleUserInput(args, username)
|
userMessage, err = client.HandleUserInput(args, username)
|
||||||
|
@ -67,16 +91,6 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
egress <- userMessage
|
egress <- userMessage
|
||||||
if userMessage[0] == lobby.Accept || userMessage[0] == lobby.Disconnect {
|
|
||||||
slog.Debug("Closing input handler with accept or disconnect message")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if userMessage[0] == lobby.StartGame {
|
|
||||||
slog.Debug("closing input handler with start_game message and sending exit signal")
|
|
||||||
|
|
||||||
exit <- msg.Content
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
userMessage, err = client.HandleUserInput(args, username)
|
userMessage, err = client.HandleUserInput(args, username)
|
||||||
|
@ -88,9 +102,7 @@ func main() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
egress <- userMessage
|
egress <- userMessage
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -103,6 +115,9 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic("Error handling server message disconnecting...")
|
log.Panic("Error handling server message disconnecting...")
|
||||||
}
|
}
|
||||||
|
if interrupterMsg.InterruptType == "start_game" {
|
||||||
|
exit <- interrupterMsg.Content
|
||||||
|
}
|
||||||
if interrupterMsg.InterruptType != "" {
|
if interrupterMsg.InterruptType != "" {
|
||||||
interrupter <- interrupterMsg
|
interrupter <- interrupterMsg
|
||||||
}
|
}
|
||||||
|
@ -120,10 +135,6 @@ func main() {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
log.Panic("Server disconnected, sorry...")
|
log.Panic("Server disconnected, sorry...")
|
||||||
}
|
}
|
||||||
if msg[0] == lobby.StartGame || msg[0] == lobby.Disconnect {
|
|
||||||
slog.Debug("closing network writer ")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -139,7 +150,6 @@ func main() {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
fmt.Println("Waiting for an exit message")
|
|
||||||
isStartGame := <-exit
|
isStartGame := <-exit
|
||||||
if isStartGame != "" {
|
if isStartGame != "" {
|
||||||
fmt.Println("Connecting to game", isStartGame)
|
fmt.Println("Connecting to game", isStartGame)
|
||||||
|
@ -157,6 +167,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConnectToLobby(username string) (net.Conn, error) {
|
func ConnectToLobby(username string) (net.Conn, error) {
|
||||||
|
slog.Debug("connecting to server...")
|
||||||
conn, err := net.Dial("tcp", "127.0.0.1:12345")
|
conn, err := net.Dial("tcp", "127.0.0.1:12345")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Sorry, failed to connect to server...")
|
return nil, fmt.Errorf("Sorry, failed to connect to server...")
|
||||||
|
|
|
@ -5,7 +5,11 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
"sshpong/internal/config"
|
||||||
"sshpong/internal/lobby"
|
"sshpong/internal/lobby"
|
||||||
|
"sshpong/internal/pong"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,14 +17,20 @@ var exit chan bool
|
||||||
var games sync.Map
|
var games sync.Map
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
if len(os.Args) == 1 {
|
||||||
|
config.LoadConfig("")
|
||||||
|
} else {
|
||||||
|
config.LoadConfig(os.Args[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.SetLogLoggerLevel(slog.Level(config.Config.LogLevel))
|
||||||
fmt.Println("Starting sshpong lobby...")
|
fmt.Println("Starting sshpong lobby...")
|
||||||
go LobbyListen()
|
go LobbyListen()
|
||||||
fmt.Println("Lobby started")
|
fmt.Println("Lobby started")
|
||||||
|
|
||||||
// fmt.Println("Starting game listener...")
|
fmt.Println("Starting game listener...")
|
||||||
// go GamesListen()
|
go GamesListen()
|
||||||
// fmt.Println("Game listener started")
|
fmt.Println("Game listener started")
|
||||||
|
|
||||||
_ = <-exit
|
_ = <-exit
|
||||||
}
|
}
|
||||||
|
@ -28,10 +38,9 @@ func main() {
|
||||||
// Starts listening on port 12345 for TCP connections
|
// Starts listening on port 12345 for TCP connections
|
||||||
// Also creates client pool and game connection singletons
|
// Also creates client pool and game connection singletons
|
||||||
func LobbyListen() {
|
func LobbyListen() {
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", "127.0.0.1:12345")
|
listener, err := net.Listen("tcp", "127.0.0.1:12345")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Error setting up listener for lobby. Exiting...")
|
slog.Error("Error setting up listener for lobby. Exiting...", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
|
@ -64,60 +73,62 @@ func LobbyListen() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// func GamesListen() {
|
func GamesListen() {
|
||||||
//
|
|
||||||
// slog.SetLogLoggerLevel(slog.LevelDebug)
|
type GameClients struct {
|
||||||
// slog.Debug("Debug level logs are active")
|
Client1 lobby.Client
|
||||||
//
|
Client2 lobby.Client
|
||||||
// gameListener, err := net.Listen("tcp", "127.0.0.1:42069")
|
}
|
||||||
// if err != nil {
|
|
||||||
// log.Fatal(err)
|
gameListener, err := net.Listen("tcp", "127.0.0.1:42069")
|
||||||
// }
|
if err != nil {
|
||||||
//
|
log.Fatal(err)
|
||||||
// for {
|
}
|
||||||
// defer gameListener.Close()
|
|
||||||
// conn, err := gameListener.Accept()
|
for {
|
||||||
// if err != nil {
|
defer gameListener.Close()
|
||||||
// log.Println(err)
|
conn, err := gameListener.Accept()
|
||||||
// continue
|
if err != nil {
|
||||||
// }
|
log.Println(err)
|
||||||
//
|
continue
|
||||||
// slog.Debug("Received game connection")
|
}
|
||||||
//
|
|
||||||
// go func(conn net.Conn) {
|
slog.Debug("Received game connection")
|
||||||
// messageBytes := make([]byte, 126)
|
|
||||||
//
|
go func(conn net.Conn) {
|
||||||
// n, err := conn.Read(messageBytes)
|
messageBytes := make([]byte, 126)
|
||||||
// if err != nil {
|
|
||||||
// log.Printf("Error reading game ID on connection %s", err)
|
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)
|
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])
|
slog.Debug("Game request data", slog.Any("game info", gInfo))
|
||||||
// if !ok {
|
|
||||||
// games.Store(gInfo[0], GameClients{Client1: Client{
|
game, ok := games.Load(gInfo[0])
|
||||||
// Username: gInfo[1],
|
if !ok {
|
||||||
// Conn: conn,
|
games.Store(gInfo[0], GameClients{Client1: lobby.Client{
|
||||||
// }, Client2: Client{}})
|
Username: gInfo[1],
|
||||||
// } else {
|
Conn: conn,
|
||||||
// gameclients, _ := game.(GameClients)
|
}, Client2: lobby.Client{}})
|
||||||
// client2 := Client{
|
} else {
|
||||||
// Username: gInfo[1],
|
gameclients, _ := game.(GameClients)
|
||||||
// Conn: conn,
|
client2 := lobby.Client{
|
||||||
// }
|
Username: gInfo[1],
|
||||||
//
|
Conn: conn,
|
||||||
// games.Store(gInfo[0], GameClients{
|
}
|
||||||
// Client1: gameclients.Client1,
|
|
||||||
// Client2: client2})
|
games.Store(gInfo[0], GameClients{
|
||||||
//
|
Client1: gameclients.Client1,
|
||||||
// go pong.StartGame(gameclients.Client1.Conn, client2.Conn, gameclients.Client1.Username, client2.Username)
|
Client2: client2})
|
||||||
// }
|
|
||||||
// }(conn)
|
go pong.StartGame(gameclients.Client1.Conn, client2.Conn, gameclients.Client1.Username, client2.Username)
|
||||||
// }
|
}
|
||||||
// }
|
}(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
3
config.json
Normal file
3
config.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"logLevel": 1
|
||||||
|
}
|
7
go.mod
7
go.mod
|
@ -1,11 +1,12 @@
|
||||||
module sshpong
|
module sshpong
|
||||||
|
|
||||||
go 1.22.2
|
go 1.23.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948
|
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948
|
||||||
golang.org/x/sys v0.23.0 // indirect
|
golang.org/x/term v0.22.0
|
||||||
golang.org/x/term v0.22.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.34.2
|
google.golang.org/protobuf v1.34.2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.23.0 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -1,3 +1,5 @@
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
|
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
|
||||||
|
|
|
@ -2,6 +2,8 @@ package ansii
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -37,8 +39,8 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Offset struct {
|
type Offset struct {
|
||||||
X int
|
X float32
|
||||||
Y int
|
Y float32
|
||||||
}
|
}
|
||||||
|
|
||||||
type style struct {
|
type style struct {
|
||||||
|
@ -89,8 +91,8 @@ func RestoreTerm(prev *term.State) error {
|
||||||
return term.Restore(fd, prev)
|
return term.Restore(fd, prev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s screen) PlaceCursor(offset Offset) ANSI {
|
func (s screen) PlaceCursor(X, Y int) ANSI {
|
||||||
return ANSI(fmt.Sprintf("\033[%d;%dH", offset.Y, offset.X))
|
return ANSI(fmt.Sprintf("\033[%d;%dH", Y, X))
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -105,31 +107,34 @@ var (
|
||||||
// Blocks that would be placed off screen are clipped.
|
// Blocks that would be placed off screen are clipped.
|
||||||
func DrawBox(builder *strings.Builder, offset Offset, height int, width int, style ANSI) {
|
func DrawBox(builder *strings.Builder, offset Offset, height int, width int, style ANSI) {
|
||||||
builder.WriteString(string(style))
|
builder.WriteString(string(style))
|
||||||
for hIdx := 0; hIdx < height; hIdx++ {
|
for hIdx := range height {
|
||||||
|
|
||||||
if hIdx == 0 || hIdx == height-1 {
|
if hIdx == 0 || hIdx == height-1 {
|
||||||
for wIdx := 0; wIdx < width; wIdx++ {
|
for wIdx := range width {
|
||||||
DrawPixel(builder, Offset{X: offset.X + wIdx, Y: offset.Y + hIdx})
|
drawPixel(builder, offset.X+float32(wIdx), offset.Y+float32(hIdx))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DrawPixel(builder, Offset{X: offset.X, Y: offset.Y + hIdx})
|
drawPixel(builder, offset.X, offset.Y+float32(hIdx))
|
||||||
DrawPixel(builder, Offset{X: offset.X + width - 1, Y: offset.Y + hIdx})
|
drawPixel(builder, offset.X+float32(width-1), offset.Y+float32(hIdx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder.WriteString(string(Styles.Reset))
|
builder.WriteString(string(Styles.Reset))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func DrawPixel(builder *strings.Builder, offset Offset) {
|
func drawPixel(builder *strings.Builder, offsetX, offsetY float32) {
|
||||||
termWidth, termHeight := GetTermSize()
|
termWidth, termHeight := GetTermSize()
|
||||||
if offset.X > termWidth || offset.Y > termHeight || offset.X < 0 || offset.Y < 0 {
|
|
||||||
return
|
scaledX := math.Floor(float64(offsetX * float32(termWidth)))
|
||||||
}
|
scaledY := math.Floor(float64(offsetY * float32(termHeight)))
|
||||||
builder.WriteString(string(Screen.PlaceCursor(offset) + ANSI(Blocks.Block)))
|
|
||||||
|
slog.Debug("positions", slog.Any("x", scaledX), slog.Any("y", scaledY))
|
||||||
|
|
||||||
|
// TODO: Does float to int convertion cause many problems?
|
||||||
|
builder.WriteString(string(Screen.PlaceCursor(int(scaledX), int(scaledY)) + ANSI(Blocks.Block)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func DrawPixelStyle(builder *strings.Builder, offset Offset, style ANSI) {
|
func DrawPixelStyle(builder *strings.Builder, offset Offset, style ANSI) {
|
||||||
builder.WriteString(string(style))
|
builder.WriteString(string(style))
|
||||||
DrawPixel(builder, offset)
|
drawPixel(builder, offset.X, offset.Y)
|
||||||
builder.WriteString(string(Styles.Reset))
|
builder.WriteString(string(Styles.Reset))
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"sshpong/internal/lobby"
|
"sshpong/internal/lobby"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InterrupterMessage struct {
|
type InterrupterMessage struct {
|
||||||
|
@ -16,8 +18,8 @@ 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")
|
||||||
|
|
||||||
var red = "\x1b[31m"
|
var Red = "\x1b[31m"
|
||||||
var normal = "\033[0m"
|
var Normal = "\033[0m"
|
||||||
|
|
||||||
func HandleUserInput(args []string, username string) ([]byte, error) {
|
func HandleUserInput(args []string, username string) ([]byte, error) {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
|
@ -26,11 +28,15 @@ func HandleUserInput(args []string, username string) ([]byte, error) {
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "invite":
|
case "invite":
|
||||||
if args[1] != "" {
|
if args[1] != "" {
|
||||||
msg, err := lobby.Marshal(lobby.InviteData{From: username, To: args[1]}, lobby.Invite)
|
if args[1] == username {
|
||||||
if err != nil {
|
fmt.Println("You cannot invite yourself to a game ;)")
|
||||||
slog.Debug("invite message was not properly marshalled", "error", err)
|
} else {
|
||||||
|
msg, err := lobby.Marshal(lobby.InviteData{From: username, To: args[1]}, lobby.Invite)
|
||||||
|
if err != nil {
|
||||||
|
slog.Debug("invite message was not properly marshalled", "error", err)
|
||||||
|
}
|
||||||
|
return msg, err
|
||||||
}
|
}
|
||||||
return msg, err
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Please provide a player to invite ")
|
fmt.Println("Please provide a player to invite ")
|
||||||
}
|
}
|
||||||
|
@ -84,13 +90,15 @@ func HandleInterruptInput(incoming InterrupterMessage, args []string, username s
|
||||||
switch incoming.InterruptType {
|
switch incoming.InterruptType {
|
||||||
// Respond with yes if you accept game
|
// Respond with yes if you accept game
|
||||||
case "invite":
|
case "invite":
|
||||||
|
slog.Debug("handling invite interrupt")
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return []byte{}, nil
|
return []byte{}, 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" {
|
||||||
msg, err := lobby.Marshal(lobby.AcceptData{
|
msg, err := lobby.Marshal(lobby.AcceptData{
|
||||||
From: username,
|
From: username,
|
||||||
To: incoming.Content,
|
To: incoming.Content,
|
||||||
|
GameID: uuid.NewString(),
|
||||||
}, lobby.Accept)
|
}, lobby.Accept)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Debug("accept message was not properly marshalled", "error", err)
|
slog.Debug("accept message was not properly marshalled", "error", err)
|
||||||
|
@ -99,20 +107,21 @@ func HandleInterruptInput(incoming InterrupterMessage, args []string, username s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect and connect to game
|
// TODO: Do we need this accepted? Disconnect and connect to game
|
||||||
case "accepted":
|
// case "accepted":
|
||||||
msg, err := lobby.Marshal(lobby.DisconnectData{
|
// msg, err := lobby.Marshal(lobby.DisconnectData{
|
||||||
From: incoming.Content,
|
// From: incoming.Content,
|
||||||
}, lobby.Disconnect)
|
// }, lobby.Disconnect)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
slog.Debug("disconnect message was not properly marshalled", "error", err)
|
// slog.Debug("disconnect message was not properly marshalled", "error", err)
|
||||||
}
|
// }
|
||||||
return msg, err
|
// return msg, err
|
||||||
|
|
||||||
case "start_game":
|
case "start_game":
|
||||||
msg, err := lobby.Marshal(lobby.StartGameData{
|
msg, err := lobby.Marshal(lobby.StartGameData{
|
||||||
To: "",
|
To: "",
|
||||||
GameID: incoming.Content,
|
GameID: incoming.Content,
|
||||||
}, lobby.Chat)
|
}, lobby.StartGame)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Debug("start game message was not properly marshalled", "error", err)
|
slog.Debug("start game message was not properly marshalled", "error", err)
|
||||||
}
|
}
|
||||||
|
@ -124,7 +133,6 @@ func HandleInterruptInput(incoming InterrupterMessage, args []string, username s
|
||||||
|
|
||||||
func HandleServerMessage(msg []byte) (InterrupterMessage, error) {
|
func HandleServerMessage(msg []byte) (InterrupterMessage, error) {
|
||||||
header := msg[0]
|
header := msg[0]
|
||||||
|
|
||||||
switch header {
|
switch header {
|
||||||
case lobby.Invite:
|
case lobby.Invite:
|
||||||
imsg, err := lobby.Unmarshal[lobby.InviteData](msg)
|
imsg, err := lobby.Unmarshal[lobby.InviteData](msg)
|
||||||
|
@ -160,6 +168,7 @@ func HandleServerMessage(msg []byte) (InterrupterMessage, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return InterrupterMessage{}, errors.New("Not a properly formatted start game message")
|
return InterrupterMessage{}, errors.New("Not a properly formatted start game message")
|
||||||
}
|
}
|
||||||
|
fmt.Println("Your invite was accepted. Press Enter to join game")
|
||||||
return InterrupterMessage{
|
return InterrupterMessage{
|
||||||
InterruptType: "start_game",
|
InterruptType: "start_game",
|
||||||
Content: sgmsg.GameID,
|
Content: sgmsg.GameID,
|
||||||
|
@ -206,7 +215,7 @@ func HandleServerMessage(msg []byte) (InterrupterMessage, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Debug("Received an indecipherable error message...", slog.Any("msg", msg[1:]))
|
slog.Debug("Received an indecipherable error message...", slog.Any("msg", msg[1:]))
|
||||||
}
|
}
|
||||||
fmt.Println(red, em.Message, normal)
|
fmt.Println(Red, em.Message, Normal)
|
||||||
|
|
||||||
}
|
}
|
||||||
return InterrupterMessage{}, nil
|
return InterrupterMessage{}, nil
|
||||||
|
|
|
@ -166,9 +166,9 @@ func handleGameInput(bytes []byte) {
|
||||||
// Up
|
// Up
|
||||||
case 'w':
|
case 'w':
|
||||||
if isPlayer1 {
|
if isPlayer1 {
|
||||||
state.Player1.Pos.Y = state.Player1.Pos.Y + 1
|
state.Player1.Pos.Y = state.Player1.Pos.Y - 1
|
||||||
v, err := json.Marshal(pong.Vector{
|
v, err := json.Marshal(pong.Vector{
|
||||||
X: 0, Y: state.Player1.Pos.Y,
|
X: -50, Y: state.Player1.Pos.Y,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||||
|
@ -180,9 +180,9 @@ func handleGameInput(bytes []byte) {
|
||||||
|
|
||||||
egress <- update
|
egress <- update
|
||||||
} else {
|
} else {
|
||||||
state.Player2.Pos.Y = state.Player2.Pos.Y + 1
|
state.Player2.Pos.Y = state.Player2.Pos.Y - 1
|
||||||
v, err := json.Marshal(pong.Vector{
|
v, err := json.Marshal(pong.Vector{
|
||||||
X: 0, Y: state.Player2.Pos.Y,
|
X: 50, Y: state.Player2.Pos.Y,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Debug("error marshalling Player2 movement", slog.Any("error", err))
|
slog.Debug("error marshalling Player2 movement", slog.Any("error", err))
|
||||||
|
@ -197,9 +197,9 @@ func handleGameInput(bytes []byte) {
|
||||||
// Down
|
// Down
|
||||||
case 's':
|
case 's':
|
||||||
if isPlayer1 {
|
if isPlayer1 {
|
||||||
state.Player1.Pos.Y = state.Player1.Pos.Y - 1
|
state.Player1.Pos.Y = state.Player1.Pos.Y + 1
|
||||||
v, err := json.Marshal(pong.Vector{
|
v, err := json.Marshal(pong.Vector{
|
||||||
X: 0, Y: state.Player1.Pos.Y,
|
X: -50, Y: state.Player1.Pos.Y,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||||
|
@ -211,9 +211,9 @@ func handleGameInput(bytes []byte) {
|
||||||
|
|
||||||
egress <- update
|
egress <- update
|
||||||
} else {
|
} else {
|
||||||
state.Player2.Pos.Y = state.Player2.Pos.Y - 1
|
state.Player2.Pos.Y = state.Player2.Pos.Y + 1
|
||||||
v, err := json.Marshal(pong.Vector{
|
v, err := json.Marshal(pong.Vector{
|
||||||
X: 0, Y: state.Player2.Pos.Y,
|
X: 50, Y: state.Player2.Pos.Y,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||||
|
@ -252,9 +252,9 @@ func handleGameInput(bytes []byte) {
|
||||||
// Up
|
// Up
|
||||||
case 65:
|
case 65:
|
||||||
if isPlayer1 {
|
if isPlayer1 {
|
||||||
state.Player1.Pos.Y = state.Player1.Pos.Y + 1
|
state.Player1.Pos.Y = state.Player1.Pos.Y - 1
|
||||||
v, err := json.Marshal(pong.Vector{
|
v, err := json.Marshal(pong.Vector{
|
||||||
X: 0, Y: state.Player1.Pos.Y,
|
X: -50, Y: state.Player1.Pos.Y,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||||
|
@ -266,9 +266,9 @@ func handleGameInput(bytes []byte) {
|
||||||
|
|
||||||
egress <- update
|
egress <- update
|
||||||
} else {
|
} else {
|
||||||
state.Player2.Pos.Y = state.Player2.Pos.Y + 1
|
state.Player2.Pos.Y = state.Player2.Pos.Y - 1
|
||||||
v, err := json.Marshal(pong.Vector{
|
v, err := json.Marshal(pong.Vector{
|
||||||
X: 0, Y: state.Player2.Pos.Y,
|
X: 50, Y: state.Player2.Pos.Y,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Debug("error marshalling Player2 movement", slog.Any("error", err))
|
slog.Debug("error marshalling Player2 movement", slog.Any("error", err))
|
||||||
|
@ -283,9 +283,9 @@ func handleGameInput(bytes []byte) {
|
||||||
// Down
|
// Down
|
||||||
case 66:
|
case 66:
|
||||||
if isPlayer1 {
|
if isPlayer1 {
|
||||||
state.Player1.Pos.Y = state.Player1.Pos.Y - 1
|
state.Player1.Pos.Y = state.Player1.Pos.Y + 1
|
||||||
v, err := json.Marshal(pong.Vector{
|
v, err := json.Marshal(pong.Vector{
|
||||||
X: 0, Y: state.Player1.Pos.Y,
|
X: -50, Y: state.Player1.Pos.Y,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||||
|
@ -297,9 +297,9 @@ func handleGameInput(bytes []byte) {
|
||||||
|
|
||||||
egress <- update
|
egress <- update
|
||||||
} else {
|
} else {
|
||||||
state.Player2.Pos.Y = state.Player2.Pos.Y - 1
|
state.Player2.Pos.Y = state.Player2.Pos.Y + 1
|
||||||
v, err := json.Marshal(pong.Vector{
|
v, err := json.Marshal(pong.Vector{
|
||||||
X: 0, Y: state.Player2.Pos.Y,
|
X: 50, Y: state.Player2.Pos.Y,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
slog.Debug("error marshalling player movement", slog.Any("error", err))
|
||||||
|
|
36
internal/config/config.go
Normal file
36
internal/config/config.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Config Configuration
|
||||||
|
|
||||||
|
type Configuration struct {
|
||||||
|
LogLevel int `json:"logLevel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadConfig(path string) {
|
||||||
|
var c = Configuration{}
|
||||||
|
|
||||||
|
var cf []byte
|
||||||
|
var err error
|
||||||
|
if path != "" {
|
||||||
|
cf, err = os.ReadFile(path)
|
||||||
|
} else {
|
||||||
|
cf, err = os.ReadFile("config.json")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
slog.Info("failed to open config at path provided, using default config instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(cf, &c)
|
||||||
|
if err != nil {
|
||||||
|
slog.Info("failed to read configuration, using default config instead...", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Config = c
|
||||||
|
return
|
||||||
|
}
|
|
@ -6,8 +6,6 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Lobby struct {
|
type Lobby struct {
|
||||||
|
@ -37,6 +35,7 @@ func CreateLobby() *Lobby {
|
||||||
go func(lm *sync.Map) {
|
go func(lm *sync.Map) {
|
||||||
for {
|
for {
|
||||||
msg := <-externalMessageChan
|
msg := <-externalMessageChan
|
||||||
|
slog.Debug("forwarding external message")
|
||||||
|
|
||||||
tc, ok := lm.Load(msg.Target)
|
tc, ok := lm.Load(msg.Target)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -170,10 +169,17 @@ func (l *Lobby) handleClientLobbyMessage(msg []byte) ([]byte, error) {
|
||||||
return []byte{}, err
|
return []byte{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
l.ExternalMessageChannel <- ExternalMessage{
|
_, ok := l.lobbyMembers.Load(i.To)
|
||||||
From: i.From,
|
if !ok {
|
||||||
Target: i.To,
|
return Marshal(ErrorData{
|
||||||
Message: msg,
|
Message: fmt.Sprintf("Sorry, player %s is not available.", i.To),
|
||||||
|
}, Error)
|
||||||
|
} else {
|
||||||
|
l.ExternalMessageChannel <- ExternalMessage{
|
||||||
|
From: i.From,
|
||||||
|
Target: i.To,
|
||||||
|
Message: msg,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Marshal(PendingInviteData{
|
return Marshal(PendingInviteData{
|
||||||
|
@ -195,12 +201,11 @@ func (l *Lobby) handleClientLobbyMessage(msg []byte) ([]byte, error) {
|
||||||
return []byte{}, err
|
return []byte{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
gID := uuid.NewString()
|
gID := a.GameID
|
||||||
|
|
||||||
msg, err := Marshal(AcceptedData{
|
msg, err := Marshal(StartGameData{
|
||||||
Accepter: a.From,
|
GameID: gID,
|
||||||
GameID: gID,
|
}, StartGame)
|
||||||
}, Accepted)
|
|
||||||
|
|
||||||
l.ExternalMessageChannel <- ExternalMessage{
|
l.ExternalMessageChannel <- ExternalMessage{
|
||||||
From: a.From,
|
From: a.From,
|
||||||
|
@ -208,23 +213,25 @@ func (l *Lobby) handleClientLobbyMessage(msg []byte) ([]byte, error) {
|
||||||
Message: msg,
|
Message: msg,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
slog.Debug("Sent start game message to inviter")
|
||||||
|
|
||||||
return Marshal(StartGameData{
|
return Marshal(StartGameData{
|
||||||
To: a.From,
|
To: a.From,
|
||||||
|
From: a.To,
|
||||||
GameID: gID,
|
GameID: gID,
|
||||||
}, StartGame)
|
}, StartGame)
|
||||||
|
|
||||||
case Accepted:
|
// TODO: figure out the accepted and start game data situation... To field is a little hard to fill.
|
||||||
a, err := Unmarshal[AcceptedData](msg)
|
// case Accepted:
|
||||||
if err != nil {
|
// a, err := Unmarshal[AcceptedData](msg)
|
||||||
slog.Debug("error unmarshalling accpeted message", "error", err)
|
// if err != nil {
|
||||||
return []byte{}, err
|
// slog.Debug("error unmarshalling accpeted message", "error", err)
|
||||||
}
|
// return []byte{}, err
|
||||||
|
// }
|
||||||
// TODO: figure out the accepted and start game data situation... To field is a little hard to fill.
|
// return Marshal(StartGameData{
|
||||||
return Marshal(StartGameData{
|
// To: "",
|
||||||
To: "",
|
// GameID: a.GameID,
|
||||||
GameID: a.GameID,
|
// }, StartGame)
|
||||||
}, StartGame)
|
|
||||||
|
|
||||||
// TODO: Like pending invite, I think start game is only a client message
|
// TODO: Like pending invite, I think start game is only a client message
|
||||||
// case StartGame:
|
// case StartGame:
|
||||||
|
|
|
@ -36,18 +36,20 @@ type PendingInviteData struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AcceptData struct {
|
type AcceptData struct {
|
||||||
From string `json:"from"`
|
From string `json:"from"`
|
||||||
To string `json:"to"`
|
To string `json:"to"`
|
||||||
|
GameID string `json:"gameID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AcceptedData struct {
|
type AcceptedData struct {
|
||||||
Accepter string `json:"accepter"`
|
Accepter string `json:"accepter"`
|
||||||
GameID string `json:"game_id"`
|
GameID string `json:"gameID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type StartGameData struct {
|
type StartGameData struct {
|
||||||
To string `json:"to"`
|
To string `json:"to"`
|
||||||
GameID string `json:"game_id"`
|
From string `json:"from"`
|
||||||
|
GameID string `json:"gameID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeclineData struct {
|
type DeclineData struct {
|
||||||
|
|
|
@ -23,7 +23,7 @@ var player2 GameClient
|
||||||
var ingress chan StateUpdate
|
var ingress chan StateUpdate
|
||||||
var egress chan StateUpdate
|
var egress chan StateUpdate
|
||||||
|
|
||||||
const posXBound = 100
|
const posXBound = 52
|
||||||
const negXBound = posXBound * -1
|
const negXBound = posXBound * -1
|
||||||
const posYBound = 50
|
const posYBound = 50
|
||||||
const negYBound = posYBound * -1
|
const negYBound = posYBound * -1
|
||||||
|
@ -141,6 +141,9 @@ func gameLoop(state *GameState) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ingress <- msg
|
ingress <- msg
|
||||||
|
if msg.FieldPath == "Winner" {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -157,6 +160,9 @@ func gameLoop(state *GameState) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ingress <- msg
|
ingress <- msg
|
||||||
|
if msg.FieldPath == "Winner" {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -164,6 +170,9 @@ func gameLoop(state *GameState) {
|
||||||
for {
|
for {
|
||||||
msg := <-egress
|
msg := <-egress
|
||||||
broadcastUpdate(msg)
|
broadcastUpdate(msg)
|
||||||
|
if msg.FieldPath == "Winner" {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -176,17 +185,23 @@ func gameLoop(state *GameState) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("FUCK!~", err)
|
fmt.Println("FUCK!~", err)
|
||||||
}
|
}
|
||||||
|
if msg.FieldPath == "Winner" {
|
||||||
|
slog.Debug("Closing game loop on winner message")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
case _ = <-ticker.C:
|
case _ = <-ticker.C:
|
||||||
update := process(state)
|
update := process(state)
|
||||||
egress <- update
|
egress <- update
|
||||||
|
if update.FieldPath == "Winner" {
|
||||||
|
slog.Debug("Closing game loop")
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func process(state *GameState) StateUpdate {
|
func process(state *GameState) StateUpdate {
|
||||||
|
|
||||||
// Move players
|
// Move players
|
||||||
// Check if player edge is out of bounds
|
// Check if player edge is out of bounds
|
||||||
// If out of bounds reset velocity to zero and position to edge
|
// If out of bounds reset velocity to zero and position to edge
|
||||||
|
@ -229,11 +244,13 @@ func process(state *GameState) StateUpdate {
|
||||||
if state.Ball.Pos.X <= negXBound+1 && state.Ball.Vel.X < 0 {
|
if state.Ball.Pos.X <= negXBound+1 && state.Ball.Vel.X < 0 {
|
||||||
// Paddle hit!
|
// Paddle hit!
|
||||||
if state.Ball.Pos.Y <= state.Player1.Pos.Y+state.Player1.Size.Y && state.Ball.Pos.Y >= state.Player1.Pos.Y-state.Player1.Size.Y {
|
if state.Ball.Pos.Y <= state.Player1.Pos.Y+state.Player1.Size.Y && state.Ball.Pos.Y >= state.Player1.Pos.Y-state.Player1.Size.Y {
|
||||||
|
slog.Debug("Player1 paddle hit!")
|
||||||
state.Ball.Pos.X = (negXBound + 1) - (state.Ball.Pos.X - (negXBound + 1))
|
state.Ball.Pos.X = (negXBound + 1) - (state.Ball.Pos.X - (negXBound + 1))
|
||||||
state.Ball.Vel.X = state.Ball.Vel.X * -1.001
|
state.Ball.Vel.X = state.Ball.Vel.X * -1.001
|
||||||
angleTweak := (state.Ball.Pos.Y - state.Player2.Pos.Y) / (state.Player2.Size.Y / 2)
|
angleTweak := (state.Ball.Pos.Y - state.Player1.Pos.Y) / (state.Player1.Size.Y / 2)
|
||||||
state.Ball.Vel.Y = state.Ball.Vel.Y * angleTweak
|
state.Ball.Vel.Y = angleTweak / 10
|
||||||
} else {
|
} else {
|
||||||
|
slog.Debug("Player1 paddle miss...")
|
||||||
state.Ball.Pos.X = 0
|
state.Ball.Pos.X = 0
|
||||||
state.Ball.Pos.Y = 0
|
state.Ball.Pos.Y = 0
|
||||||
state.Ball.Vel.X = 1
|
state.Ball.Vel.X = 1
|
||||||
|
@ -252,11 +269,13 @@ func process(state *GameState) StateUpdate {
|
||||||
if state.Ball.Pos.X > posXBound-1 && state.Ball.Vel.X > 0 {
|
if state.Ball.Pos.X > posXBound-1 && state.Ball.Vel.X > 0 {
|
||||||
// Paddle hit!
|
// Paddle hit!
|
||||||
if state.Ball.Pos.Y <= state.Player2.Pos.Y+state.Player2.Size.Y && state.Ball.Pos.Y >= state.Player2.Pos.Y-state.Player2.Size.Y {
|
if state.Ball.Pos.Y <= state.Player2.Pos.Y+state.Player2.Size.Y && state.Ball.Pos.Y >= state.Player2.Pos.Y-state.Player2.Size.Y {
|
||||||
|
slog.Debug("Player2 paddle hit!")
|
||||||
state.Ball.Pos.X = (posXBound - 1) - (state.Ball.Pos.X - (posXBound - 1))
|
state.Ball.Pos.X = (posXBound - 1) - (state.Ball.Pos.X - (posXBound - 1))
|
||||||
state.Ball.Vel.X = state.Ball.Vel.X * -1.001
|
state.Ball.Vel.X = state.Ball.Vel.X * -1.001
|
||||||
angleTweak := (state.Ball.Pos.Y - state.Player2.Pos.Y) / (state.Player2.Size.Y / 2)
|
angleTweak := (state.Ball.Pos.Y - state.Player2.Pos.Y) / (state.Player2.Size.Y / 2)
|
||||||
state.Ball.Vel.Y = state.Ball.Vel.Y * angleTweak
|
state.Ball.Vel.Y = angleTweak / 10
|
||||||
} else {
|
} else {
|
||||||
|
slog.Debug("Player2 paddle miss...")
|
||||||
state.Ball.Pos.X = 0
|
state.Ball.Pos.X = 0
|
||||||
state.Ball.Pos.Y = 0
|
state.Ball.Pos.Y = 0
|
||||||
state.Ball.Vel.X = -1
|
state.Ball.Vel.X = -1
|
||||||
|
|
|
@ -6,7 +6,8 @@ import (
|
||||||
"sshpong/internal/ansii"
|
"sshpong/internal/ansii"
|
||||||
"sshpong/internal/pong"
|
"sshpong/internal/pong"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -15,96 +16,88 @@ var (
|
||||||
millisecondTimeFrame float64 = float64(1 / targetFpMilli)
|
millisecondTimeFrame float64 = float64(1 / targetFpMilli)
|
||||||
quit chan bool
|
quit chan bool
|
||||||
userInput chan rune
|
userInput chan rune
|
||||||
playerX int = 10
|
)
|
||||||
playerY int = 10
|
|
||||||
|
const (
|
||||||
|
reset string = "\033[0m"
|
||||||
|
plain string = ""
|
||||||
|
bold string = "\033[1m"
|
||||||
|
underline string = "\033[4m"
|
||||||
|
black string = "\033[30m"
|
||||||
|
red string = "\033[31m"
|
||||||
|
green string = "\033[32m"
|
||||||
|
yellow string = "\033[33m"
|
||||||
|
blue string = "\033[34m"
|
||||||
|
purple string = "\033[35m"
|
||||||
|
cyan string = "\033[36m"
|
||||||
|
white string = "\033[37m"
|
||||||
|
blackBg string = "\033[40m"
|
||||||
|
redBg string = "\033[41m"
|
||||||
|
greenBg string = "\033[42m"
|
||||||
|
yellowBg string = "\033[43m"
|
||||||
|
blueBg string = "\033[44m"
|
||||||
|
purpleBg string = "\033[45m"
|
||||||
|
cyanBg string = "\033[46m"
|
||||||
|
whiteBg string = "\033[47m"
|
||||||
|
clearScreen string = "\033[2J"
|
||||||
|
hideCursor string = "\033[?25l"
|
||||||
|
showCursor string = "\033[?25h"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Render(state pong.GameState) {
|
func Render(state pong.GameState) {
|
||||||
// drawScreen(state)
|
// fmt.Println("Player 1", ((state.Player1.Pos.X+50)/100)*width, ((state.Player1.Pos.Y+50)/100)*height)
|
||||||
fmt.Print("\033c")
|
// fmt.Println("Player 2", ((state.Player2.Pos.X+50)/100)*width, ((state.Player2.Pos.Y+50)/100)*height)
|
||||||
fmt.Println("Player 1", state.Player1.Pos.X, state.Player1.Pos.Y)
|
// fmt.Println("Ball", ((state.Ball.Pos.X+50)/100)*width, ((state.Ball.Pos.Y+50)/100)*height)
|
||||||
fmt.Println("Player 2", state.Player2.Pos.X, state.Player2.Pos.Y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeCheckerBoard(height int, width int, builder *strings.Builder) {
|
|
||||||
for i := 0; i < height; i++ {
|
|
||||||
for j := 0; j < width; j++ {
|
|
||||||
if i%2 == 0 {
|
|
||||||
if j%2 == 0 {
|
|
||||||
builder.WriteString("█")
|
|
||||||
} else {
|
|
||||||
|
|
||||||
builder.WriteString(" ")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if j%2 == 0 {
|
|
||||||
builder.WriteString(" ")
|
|
||||||
} else {
|
|
||||||
builder.WriteString("█")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func drawScreen(state pong.GameState) {
|
|
||||||
// width := 100
|
|
||||||
// height := 50
|
|
||||||
var builder = strings.Builder{}
|
var builder = strings.Builder{}
|
||||||
builder.WriteString(string(ansii.Screen.ClearScreen))
|
builder.WriteString(string(ansii.Screen.ClearScreen))
|
||||||
ansii.DrawBox(&builder, ansii.Offset{X: int(state.Player1.Pos.X), Y: int(state.Player1.Pos.Y)}, 5, 1, ansii.Colors.Cyan)
|
x1, y1 := transformToTermPos(state.Player1.Pos)
|
||||||
ansii.DrawPixelStyle(&builder, ansii.Offset{X: int(state.Player1.Pos.X), Y: int(state.Player1.Pos.Y)}, ansii.Colors.Purple)
|
builder.WriteString(renderBox(&builder, x1+2, y1, 2, 10, cyan))
|
||||||
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: int(state.Player2.Pos.X), Y: int(state.Player2.Pos.Y)}, 5, 1, ansii.Colors.Cyan)
|
x2, y2 := transformToTermPos(state.Player2.Pos)
|
||||||
ansii.DrawPixelStyle(&builder, ansii.Offset{X: int(state.Player2.Pos.X), Y: int(state.Player2.Pos.Y)}, ansii.Colors.Purple)
|
builder.WriteString(renderBox(&builder, x2, y2, 2, 10, purple))
|
||||||
ansii.DrawPixelStyle(&builder, ansii.Offset{X: int(state.Player2.Pos.X), Y: int(state.Player2.Pos.Y) + 5}, ansii.Colors.Purple)
|
|
||||||
// Quit instructions
|
xb, yb := transformToTermPos(state.Ball.Pos)
|
||||||
// builder.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: 0, Y: height})))
|
builder.WriteString(renderPixel(&builder, xb, yb, red))
|
||||||
// builder.WriteString("q to quit")
|
|
||||||
|
builder.WriteString(renderMessage(&builder, state.Message))
|
||||||
|
|
||||||
os.Stdout.WriteString(builder.String())
|
os.Stdout.WriteString(builder.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func drawFrameStats(frameNum int, frameTimeMs float64) {
|
func setCursorPos(x, y int) string {
|
||||||
width, height := ansii.GetTermSize()
|
return fmt.Sprintf("\033[%d;%dH", y, x)
|
||||||
var spareTimeMilli = millisecondTimeFrame - frameTimeMs
|
|
||||||
os.Stdout.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: width - 12, Y: height - 2})))
|
|
||||||
os.Stdout.WriteString(fmt.Sprintf("Frame #: %d", frameNum))
|
|
||||||
os.Stdout.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: width - 19, Y: height - 1})))
|
|
||||||
os.Stdout.WriteString(fmt.Sprintf("Frame Time: %.4fms", frameTimeMs))
|
|
||||||
os.Stdout.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: width - 20, Y: height})))
|
|
||||||
os.Stdout.WriteString(fmt.Sprintf("Spare Time: %.4fms", spareTimeMilli))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleInput(rawInput rune) {
|
// Renders a box with center positioned at X,Y with specified width and height
|
||||||
action := ProcessInput(rawInput)
|
func renderBox(builder *strings.Builder, X, Y, width, height int, style string) string {
|
||||||
width, height := ansii.GetTermSize()
|
str := ""
|
||||||
|
for x := X - (width / 2); x < X+(width/2); x++ {
|
||||||
switch action {
|
for y := Y - (height / 2); y < Y+(height/2); y++ {
|
||||||
case Quit:
|
str = str + (setCursorPos(x, y) + style + "█")
|
||||||
fmt.Println("Quitting...")
|
|
||||||
close(quit)
|
|
||||||
case Left, LeftArrow:
|
|
||||||
playerX = max(playerX-1, 0)
|
|
||||||
case Right, RightArrow:
|
|
||||||
playerX = min(playerX+1, width)
|
|
||||||
case Up, UpArrow:
|
|
||||||
playerY = max(playerY-1, 0)
|
|
||||||
case Down, DownArrow:
|
|
||||||
playerY = min(playerY+1, height)
|
|
||||||
case Unknown:
|
|
||||||
default:
|
|
||||||
os.Stdout.WriteString(string(ansii.Screen.PlaceCursor(ansii.Offset{X: 0, Y: height - 2})))
|
|
||||||
os.Stdout.WriteString("Unrecognized Input: " + string(action))
|
|
||||||
close(quit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForFpsLock(startMs float64) {
|
|
||||||
for {
|
|
||||||
var nowMs = float64(time.Now().UnixNano()) / 1_000_000.0
|
|
||||||
if nowMs-startMs >= millisecondTimeFrame {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderPixel(builder *strings.Builder, x, y int, style string) string {
|
||||||
|
return (setCursorPos(x, y) + style + "█")
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderMessage(builder *strings.Builder, message string) string {
|
||||||
|
xm, xy := transformToTermPos(pong.Vector{X: 40, Y: 40})
|
||||||
|
return (setCursorPos(xm, xy) + reset + message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns state x and y positions with center origin and 50 by 50 area
|
||||||
|
// to scaled, top-left origin coordinates for the user's terminal size.
|
||||||
|
func transformToTermPos(vec pong.Vector) (int, int) {
|
||||||
|
iwidth, iheight, _ := term.GetSize(int(os.Stdin.Fd()))
|
||||||
|
width := float32(iwidth)
|
||||||
|
height := float32(iheight)
|
||||||
|
|
||||||
|
ix := int(((vec.X + 50) / 100) * width)
|
||||||
|
iy := int(((vec.Y + 50) / 100) * height)
|
||||||
|
|
||||||
|
return ix, iy
|
||||||
}
|
}
|
||||||
|
|
6
invite_proc.txt
Normal file
6
invite_proc.txt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
1. Inviter invites other player
|
||||||
|
2. Recipent acceptes invite
|
||||||
|
3. Recipent Sends Accept message with generated GameID, receives StartGame message
|
||||||
|
3. Recipent handles StartGame message and connects to game instance
|
||||||
|
4. Inviter recieves StartGame message with GameID
|
||||||
|
5. Inviter connects to game instance
|
11
makefile
11
makefile
|
@ -1,2 +1,13 @@
|
||||||
lint:
|
lint:
|
||||||
golangci-lint run
|
golangci-lint run
|
||||||
|
|
||||||
|
server:
|
||||||
|
air -c .air_server.toml
|
||||||
|
|
||||||
|
client:
|
||||||
|
while true; do \
|
||||||
|
go run cmd/client/main.go;\
|
||||||
|
echo "Client has exited, restarting...";\
|
||||||
|
sleep 2;\
|
||||||
|
done
|
||||||
|
|
||||||
|
|
1
tmp/build-errors.log
Normal file
1
tmp/build-errors.log
Normal file
|
@ -0,0 +1 @@
|
||||||
|
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
|
BIN
tmp/client/main
Executable file
BIN
tmp/client/main
Executable file
Binary file not shown.
BIN
tmp/server/main
Executable file
BIN
tmp/server/main
Executable file
Binary file not shown.
Loading…
Reference in New Issue
Block a user