diff --git a/cmds/client.go b/cmds/client.go index a4bb2bb..cd7448b 100644 --- a/cmds/client.go +++ b/cmds/client.go @@ -3,6 +3,7 @@ package cmds import ( "fmt" "inp2025/tcp" + "inp2025/workflows" "github.com/spf13/cobra" ) @@ -12,7 +13,8 @@ func ping() { for _, msg := range msgs { fmt.Printf("client sending: '%s'\n", msg) - resp, err := tcp.Get(":8080", "/test", map[string]string{"msg": msg}) + resp, err := tcp.Get(":8080", "/test/ping", + map[string]string{"msg": msg}) if err != nil { panic(err) } @@ -22,7 +24,7 @@ func ping() { } func pull() { - socket, err := tcp.Dial(":8080", "/test") + socket, err := tcp.Dial("localhost:8080", "/test") if err != nil { panic(err) } @@ -40,6 +42,12 @@ func pull() { var clientCmd = &cobra.Command{ Use: "client", Run: func(cmd *cobra.Command, args []string) { - ping() + err := workflows.GameClient() + if err != nil { + panic(err) + } }, } + +func init() { +} diff --git a/cmds/game.go b/cmds/game.go new file mode 100644 index 0000000..2854a72 --- /dev/null +++ b/cmds/game.go @@ -0,0 +1,32 @@ +package cmds + +import ( + "inp2025/workflows" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "go.uber.org/zap" +) + +var gameCmd = &cobra.Command{ + Use: "game", + Run: func(cmd *cobra.Command, args []string) { + router := workflows.GameServer() + + err := router.Listen(":" + viper.GetString("port")) + if err != nil { + panic(err) + } + + zap.L().Info("server up", + zap.String("addr", router.ListenAddr())) + if err := router.WaitFor(); err != nil { + panic(err) + } + }, +} + +func init() { + gameCmd.Flags(). + String("port", "12345", "") +} diff --git a/cmds/server.go b/cmds/server.go index 253ebd7..2f44c03 100644 --- a/cmds/server.go +++ b/cmds/server.go @@ -1,45 +1,27 @@ package cmds import ( - "fmt" - "inp2025/middlewares" - "inp2025/tcp" - "io" - "time" + "inp2025/workflows" "github.com/spf13/cobra" "github.com/spf13/viper" "go.uber.org/zap" ) -func pongHandler(w tcp.ResponseWriter, req *tcp.Request) error { - w.WriteHeader(tcp.StatusOK) - _, err := io.WriteString(w, string(req.Params["msg"])) - return err -} - -func tickHandler(w tcp.ResponseWriter, req *tcp.Request) error { - zap.L().Info("Run tickHandler") - for { - w.SocketSendString(fmt.Sprintf("time: %s", time.Now().String())) - time.Sleep(time.Second) - } - return nil -} - var serverCmd = &cobra.Command{ Use: "server", Run: func(cmd *cobra.Command, args []string) { - router := tcp.NewRouter(). - Use(middlewares.ErrorHandler). - Use(middlewares.AccessLog) + router := workflows.LobbyServer() - router.Register(tcp.MethodSOCKET, "/test", - tickHandler) - router.Register(tcp.MethodGET, "/test", - pongHandler) - - router.Listen(":" + viper.GetString("port")) + err := router.Listen(":" + viper.GetString("port")) + if err != nil { + panic(err) + } + zap.L().Info("server up", + zap.String("addr", router.ListenAddr())) + if err := router.WaitFor(); err != nil { + panic(err) + } }, } diff --git a/go.mod b/go.mod index 72164cd..5b79caa 100644 --- a/go.mod +++ b/go.mod @@ -12,18 +12,33 @@ require ( ) require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/bubbletea v1.3.10 // indirect + github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect + github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/x/ansi v0.10.1 // indirect + github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-sqlite3 v1.14.28 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect @@ -33,10 +48,11 @@ require ( github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.uber.org/multierr v1.10.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect - golang.org/x/sys v0.34.0 // indirect + golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.28.0 // indirect modernc.org/libc v1.66.3 // indirect modernc.org/mathutil v1.7.1 // indirect diff --git a/go.sum b/go.sum index 06105a7..fece30f 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,24 @@ +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= +github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= +github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= +github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= +github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= @@ -23,10 +39,22 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= @@ -39,6 +67,9 @@ github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++ github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -73,6 +104,8 @@ github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IU github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= @@ -87,9 +120,12 @@ golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= diff --git a/go.work.sum b/go.work.sum index a7b76be..c0a8bfb 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,3 +1,6 @@ +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= +github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= diff --git a/handlers/game/handlers.go b/handlers/game/handlers.go new file mode 100644 index 0000000..f78bb61 --- /dev/null +++ b/handlers/game/handlers.go @@ -0,0 +1,8 @@ +package game + +type Handlers struct { +} + +func NewHandlers() *Handlers { + return &Handlers{} +} diff --git a/handlers/game/postMove.go b/handlers/game/postMove.go new file mode 100644 index 0000000..9360874 --- /dev/null +++ b/handlers/game/postMove.go @@ -0,0 +1,14 @@ +package game + +import ( + "inp2025/tcp" + "inp2025/utils" +) + +func (self *Handlers) PostMove( + w tcp.ResponseWriter, + req *tcp.Request, +) error { + // TODO + return utils.Success(w) +} diff --git a/handlers/game/postReady.go b/handlers/game/postReady.go new file mode 100644 index 0000000..aa0a766 --- /dev/null +++ b/handlers/game/postReady.go @@ -0,0 +1,14 @@ +package game + +import ( + "inp2025/tcp" + "inp2025/utils" +) + +func (self *Handlers) PostReady( + w tcp.ResponseWriter, + req *tcp.Request, +) error { + // TODO + return utils.Success(w) +} diff --git a/handlers/game/socketState.go b/handlers/game/socketState.go new file mode 100644 index 0000000..0d314d4 --- /dev/null +++ b/handlers/game/socketState.go @@ -0,0 +1,14 @@ +package game + +import ( + "inp2025/tcp" + "inp2025/utils" +) + +func (self *Handlers) SocketState( + w tcp.ResponseWriter, + req *tcp.Request, +) error { + // TODO + return utils.Success(w) +} diff --git a/middlewares/nmsl.go b/middlewares/nmsl.go new file mode 100644 index 0000000..3ccab86 --- /dev/null +++ b/middlewares/nmsl.go @@ -0,0 +1,14 @@ +package middlewares + +import ( + "inp2025/tcp" + + "go.uber.org/zap" +) + +func NMSL(next tcp.Handler) tcp.Handler { + return func(w tcp.ResponseWriter, req *tcp.Request) error { + zap.L().Info("nmsl") + return next(w, req) + } +} diff --git a/models/state.go b/models/state.go new file mode 100644 index 0000000..0269780 --- /dev/null +++ b/models/state.go @@ -0,0 +1,81 @@ +package models + +import "fmt" + +const ( + BlockNull byte = '0' + BlockI byte = 'I' + BlockJ byte = 'J' + BlockL byte = 'L' + BlockO byte = 'O' + BlockS byte = 'S' + BlockT byte = 'T' + BlockZ byte = 'Z' +) + +const ( + RowCnt int = 20 + ColCnt int = 10 +) + +type BlockState struct { + Block byte `json:"b"` + PosX int `json:"x"` + PosY int `json:"y"` +} + +type ActiveBlockState struct { + *BlockState `json:",inline"` + + Direc int `json:"d"` +} + +type CompressedGameState struct { + UserId int + Tick int + Score int + Level int + + RLE string + Active ActiveBlockState +} + +type GameState struct { + UserId int + Tick int + Score int + Level int + + Blocks [RowCnt][ColCnt]BlockState + Active ActiveBlockState +} + +func (self CompressedGameState) Uncompress() (GameState, error) { + var cnt int = 0 + var ch byte = '0' + var blocks [RowCnt][ColCnt]BlockState + for x := range blocks { + for y := range blocks[x] { + if cnt <= 0 { + _, err := fmt.Sscanf(self.RLE, "%d%c", &cnt, &ch) + if err != nil { + return GameState{}, err + } + } + blocks[x][y] = BlockState{ + Block: ch, + PosX: x, + PosY: y, + } + } + } + + return GameState{ + UserId: self.UserId, + Tick: self.Tick, + Score: self.Score, + Level: self.Level, + Blocks: blocks, + Active: self.Active, + }, nil +} diff --git a/stages/game.go b/stages/game.go new file mode 100644 index 0000000..ac9e807 --- /dev/null +++ b/stages/game.go @@ -0,0 +1,85 @@ +package stages + +import ( + "encoding/json" + "fmt" + "inp2025/models" + "inp2025/tcp" + "strings" + + tea "github.com/charmbracelet/bubbletea" +) + +type GameModel struct { + state models.GameState + + conn *tcp.SocketConn + err error +} + +func NewGameModel() *GameModel { + return &GameModel{} +} + +func (m *GameModel) getState() tea.Cmd { + return func() tea.Msg { + if m.conn == nil { + socket, err := tcp.Dial("localhost", "/state") + if err != nil { + m.err = fmt.Errorf("failed to dial socket, %w", err) + return nil + } + m.conn = socket + } + b, err := m.conn.Read() + if err != nil { + m.err = fmt.Errorf("failed to read from socket conn, %w", err) + return nil + } + + var cState models.CompressedGameState + if err := json.Unmarshal(b, &cState); err != nil { + m.err = fmt.Errorf("failed to unmarshal state from json, %w", err) + return nil + } + + state, err := cState.Uncompress() + if err != nil { + m.err = fmt.Errorf("failed to uncompress, %w", err) + return nil + } + + return state + } +} + +func (m *GameModel) Init() tea.Cmd { + return tea.Sequence( + tea.ClearScreen, + m.getState(), + ) +} + +func (m *GameModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmds []tea.Cmd + + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "ctrl+c": + return m, tea.Quit + } + case models.GameState: + m.state = msg + cmds = append(cmds, m.getState()) + } + + return m, tea.Batch(cmds...) +} + +func (m *GameModel) View() string { + var b strings.Builder + fmt.Fprintf(&b, "%+##v\n", *m) + fmt.Fprintf(&b, "%w\n", m.err) + return b.String() +} diff --git a/tcp/router.go b/tcp/router.go index 717a409..3411691 100644 --- a/tcp/router.go +++ b/tcp/router.go @@ -4,6 +4,7 @@ import ( "encoding/json" "io" "net" + "strings" "go.uber.org/zap" ) @@ -14,11 +15,41 @@ type Middleware func(next Handler) Handler type Router struct { middlewares []Middleware routes map[Method]map[string]Handler + + listenAddr string + doneCh chan error } func NewRouter() *Router { return &Router{ routes: make(map[Method]map[string]Handler), + doneCh: make(chan error), + } +} + +type Group struct { + pRouter *Router + pGroup *Group + + path string + middlewares []Middleware +} + +func (self *Router) Group(path string) *Group { + return &Group{ + pRouter: self, + pGroup: nil, + + path: path, + } +} + +func (self *Group) Group(path string) *Group { + return &Group{ + pRouter: nil, + pGroup: self, + + path: path, } } @@ -27,6 +58,11 @@ func (self *Router) Use(middleware Middleware) *Router { return self } +func (self *Group) Use(middleware Middleware) *Group { + self.middlewares = append(self.middlewares, middleware) + return self +} + func (self *Router) Register(method Method, route string, handler Handler) { _, ok := self.routes[method] if !ok { @@ -39,26 +75,59 @@ func (self *Router) Register(method Method, route string, handler Handler) { self.routes[method][route] = handler } +func (self *Group) Register(method Method, route string, handler Handler) { + for _, middleware := range self.middlewares { + handler = middleware(handler) + } + + if self.pRouter != nil { + self.pRouter.Register(method, self.path+route, handler) + } + if self.pGroup != nil { + self.pGroup.Register(method, self.path+route, handler) + } +} + func (self *Router) GET(route string, handler Handler) { self.Register(MethodGET, route, handler) } +func (self *Group) GET(route string, handler Handler) { + self.Register(MethodGET, route, handler) +} + func (self *Router) POST(route string, handler Handler) { self.Register(MethodPOST, route, handler) } +func (self *Group) POST(route string, handler Handler) { + self.Register(MethodPOST, route, handler) +} + func (self *Router) PUT(route string, handler Handler) { self.Register(MethodPUT, route, handler) } +func (self *Group) PUT(route string, handler Handler) { + self.Register(MethodPUT, route, handler) +} + func (self *Router) DELETE(route string, handler Handler) { self.Register(MethodDELETE, route, handler) } +func (self *Group) DELETE(route string, handler Handler) { + self.Register(MethodDELETE, route, handler) +} + func (self *Router) SOCKET(route string, handler Handler) { self.Register(MethodSOCKET, route, handler) } +func (self *Group) SOCKET(route string, handler Handler) { + self.Register(MethodSOCKET, route, handler) +} + func (self *Router) run(conn net.Conn, req *Request) { handler, ok := self.routes[req.Method][req.Route] if !ok { @@ -94,42 +163,64 @@ func (self *Router) run(conn net.Conn, req *Request) { } } -func (self *Router) Listen(addr string) error { - listener, err := net.Listen("tcp", addr) - if err != nil { - return err - } +func (self *Router) listen(listener net.Listener) { defer listener.Close() - for { conn, err := listener.Accept() if err != nil { - return err + self.doneCh <- err + return } rawHeader, err := readFrame(conn) if err != nil { if err == io.EOF { - return nil + self.doneCh <- nil + return } - return err + self.doneCh <- err + return } body, err := readFrame(conn) if err != nil { if err == io.EOF { - return nil + self.doneCh <- nil + return } - return err + self.doneCh <- err + return } var header RequestHeader if err := json.Unmarshal(rawHeader, &header); err != nil { - return err + self.doneCh <- err + return } req := NewRequest(conn, header, body) go self.run(conn, req) } +} +func (self *Router) WaitFor() error { + return <-self.doneCh +} + +func (self *Router) ListenAddr() string { + conn, _ := net.Dial("udp", "8.8.8.8:80") + addr := conn.LocalAddr().(*net.UDPAddr).IP.String() + conn.Close() + ss2 := strings.Split(self.listenAddr, ":") + return addr + ":" + ss2[len(ss2)-1] +} + +func (self *Router) Listen(addr string) error { + listener, err := net.Listen("tcp", addr) + if err != nil { + return err + } + + self.listenAddr = listener.Addr().String() + go self.listen(listener) return nil } diff --git a/workflows/databaseServer.go b/workflows/databaseServer.go new file mode 100644 index 0000000..d693fb6 --- /dev/null +++ b/workflows/databaseServer.go @@ -0,0 +1,14 @@ +package workflows + +import ( + "inp2025/middlewares" + "inp2025/tcp" +) + +func DatabaseServer() *tcp.Router { + router := tcp.NewRouter(). + Use(middlewares.ErrorHandler). + Use(middlewares.AccessLog) + + return router +} diff --git a/workflows/gameClient.go b/workflows/gameClient.go new file mode 100644 index 0000000..b1d940e --- /dev/null +++ b/workflows/gameClient.go @@ -0,0 +1,14 @@ +package workflows + +import ( + "inp2025/stages" + + tea "github.com/charmbracelet/bubbletea" +) + +func GameClient() error { + program := tea.NewProgram( + stages.NewGameModel()) + _, err := program.Run() + return err +} diff --git a/workflows/gameServer.go b/workflows/gameServer.go new file mode 100644 index 0000000..48d97aa --- /dev/null +++ b/workflows/gameServer.go @@ -0,0 +1,24 @@ +package workflows + +import ( + "inp2025/handlers/game" + "inp2025/middlewares" + "inp2025/tcp" +) + +func GameServer() *tcp.Router { + router := tcp.NewRouter(). + Use(middlewares.ErrorHandler). + Use(middlewares.AccessLog) + + handlers := game.NewHandlers() + + // before start + router.POST("/ready", handlers.PostReady) + + // game start + router.POST("/move", handlers.PostMove) + router.SOCKET("/state", handlers.SocketState) + + return router +} diff --git a/workflows/lobbyServer.go b/workflows/lobbyServer.go new file mode 100644 index 0000000..212f90c --- /dev/null +++ b/workflows/lobbyServer.go @@ -0,0 +1,55 @@ +package workflows + +import ( + "fmt" + "inp2025/middlewares" + "inp2025/tcp" + "io" + "time" + + "go.uber.org/zap" +) + +func pongHandler(w tcp.ResponseWriter, req *tcp.Request) error { + w.WriteHeader(tcp.StatusOK) + _, err := io.WriteString(w, string(req.Params["msg"])) + return err +} + +func tickHandler(w tcp.ResponseWriter, req *tcp.Request) error { + zap.L().Info("Run tickHandler") + for { + w.SocketSendString(fmt.Sprintf("time: %s", time.Now().String())) + time.Sleep(time.Second) + } + return nil +} + +func LobbyServer() *tcp.Router { + router := tcp.NewRouter(). + Use(middlewares.ErrorHandler). + Use(middlewares.AccessLog) + + // test + test := router.Group("/test"). + Use(middlewares.NMSL) + test.SOCKET("/tick", tickHandler) + test.GET("/ping", pongHandler) + + /* + auth := router.Group("/auth") + auth.POST("/register") + auth.POST("/login") + auth.POST("/logout") + + api := router.Group("/api") + api.GET("/online-users") + api.GET("/rooms") + api.GET("/invitations") + api.GET("/current-invitation") + api.POST("/invite") + api.POST("/accept-invitition") + */ + + return router +}