Draft: big refactor
This commit is contained in:
3
Makefile
3
Makefile
@@ -3,6 +3,7 @@
|
|||||||
SWAG ?= ~/go/bin/swag
|
SWAG ?= ~/go/bin/swag
|
||||||
|
|
||||||
TARGET := $(shell find . -type f -name '*.go')
|
TARGET := $(shell find . -type f -name '*.go')
|
||||||
|
DEST := server/docs
|
||||||
|
|
||||||
all: swagger docker install
|
all: swagger docker install
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ game: $(TARGET)
|
|||||||
|
|
||||||
swagger:
|
swagger:
|
||||||
$(SWAG) fmt
|
$(SWAG) fmt
|
||||||
$(SWAG) init -o backend/docs -g cmd/serve/backend.go -pdl 1
|
$(SWAG) init -o $(DEST) -g cmd/serve/backend.go -pdl 1
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
-rm -f game
|
-rm -f game
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/backend/middlewares"
|
|
||||||
"github.com/uptrace/bunrouter"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetLobbyRooms
|
|
||||||
//
|
|
||||||
// @Router /api/lobby/rooms [get]
|
|
||||||
func (self *Handlers) GetLobbyRooms(
|
|
||||||
w http.ResponseWriter,
|
|
||||||
req bunrouter.Request,
|
|
||||||
) error {
|
|
||||||
ctx := req.Context()
|
|
||||||
|
|
||||||
rooms, err := self.db.GetRooms(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return middlewares.HTTPError{
|
|
||||||
StatusCode: http.StatusInternalServerError,
|
|
||||||
Message: "failed to get room",
|
|
||||||
OriginError: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bunrouter.JSON(w, rooms)
|
|
||||||
}
|
|
||||||
@@ -21,8 +21,8 @@ var RootCmd = &cobra.Command{
|
|||||||
SetDisableWarn(true)
|
SetDisableWarn(true)
|
||||||
|
|
||||||
queue := []*tea.Program{}
|
queue := []*tea.Program{}
|
||||||
queue = append(queue,
|
queue = append(queue, tea.NewProgram(
|
||||||
tea.NewProgram(plays.NewLanding(plays.NewBase(client))))
|
plays.NewLanding(plays.NewBase(client))))
|
||||||
|
|
||||||
for len(queue) > 0 {
|
for len(queue) > 0 {
|
||||||
program := queue[0]
|
program := queue[0]
|
||||||
@@ -41,4 +41,5 @@ var RootCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
RootCmd.AddCommand(clientCmd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ import (
|
|||||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
_ "gitea.konchin.com/ytshih/inp2025/game/backend/docs"
|
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/backend/handlers/api"
|
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/backend/handlers/auth"
|
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/backend/middlewares"
|
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/implements"
|
"gitea.konchin.com/ytshih/inp2025/game/implements"
|
||||||
|
"gitea.konchin.com/ytshih/inp2025/game/server/backend/api"
|
||||||
|
"gitea.konchin.com/ytshih/inp2025/game/server/backend/auth"
|
||||||
|
_ "gitea.konchin.com/ytshih/inp2025/game/server/docs"
|
||||||
|
"gitea.konchin.com/ytshih/inp2025/game/server/middlewares"
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/tracing"
|
"gitea.konchin.com/ytshih/inp2025/game/tracing"
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/utils"
|
"gitea.konchin.com/ytshih/inp2025/game/utils"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ services:
|
|||||||
backend:
|
backend:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
env_file: ./backend/.env
|
env_file: ./server/.env
|
||||||
ports:
|
ports:
|
||||||
- 8081:8080
|
- 8081:8080
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -44,6 +44,7 @@ require (
|
|||||||
github.com/go-resty/resty/v2 v2.16.5 // indirect
|
github.com/go-resty/resty/v2 v2.16.5 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -67,6 +67,8 @@ github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17k
|
|||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
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=
|
||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
|||||||
@@ -38,16 +38,6 @@ func (self *BunDatabase) GetUserStatuses(
|
|||||||
return ret, err
|
return ret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BunDatabase) GetRooms(
|
|
||||||
ctx context.Context,
|
|
||||||
) ([]models.Room, error) {
|
|
||||||
var ret []models.Room
|
|
||||||
err := self.db.NewSelect().
|
|
||||||
Model(&ret).
|
|
||||||
Scan(ctx)
|
|
||||||
return ret, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *BunDatabase) InsertUser(
|
func (self *BunDatabase) InsertUser(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
user models.User,
|
user models.User,
|
||||||
@@ -81,16 +71,6 @@ func (self *BunDatabase) InsertUserStatus(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BunDatabase) InsertRoom(
|
|
||||||
ctx context.Context,
|
|
||||||
room models.Room,
|
|
||||||
) error {
|
|
||||||
_, err := self.db.NewInsert().
|
|
||||||
Model(&room).
|
|
||||||
Exec(ctx)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *BunDatabase) DeleteUserStatus(
|
func (self *BunDatabase) DeleteUserStatus(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
username string,
|
username string,
|
||||||
@@ -101,14 +81,3 @@ func (self *BunDatabase) DeleteUserStatus(
|
|||||||
Exec(ctx)
|
Exec(ctx)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BunDatabase) DeleteRoom(
|
|
||||||
ctx context.Context,
|
|
||||||
roomId int,
|
|
||||||
) error {
|
|
||||||
_, err := self.db.NewDelete().
|
|
||||||
Model(&models.Room{Id: roomId}).
|
|
||||||
WherePK().
|
|
||||||
Exec(ctx)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ type Database interface {
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) ([]models.UserStatus, error)
|
) ([]models.UserStatus, error)
|
||||||
|
|
||||||
GetRooms(
|
|
||||||
ctx context.Context,
|
|
||||||
) ([]models.Room, error)
|
|
||||||
|
|
||||||
InsertUser(
|
InsertUser(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
user models.User,
|
user models.User,
|
||||||
@@ -30,18 +26,8 @@ type Database interface {
|
|||||||
userStatus models.UserStatus,
|
userStatus models.UserStatus,
|
||||||
) error
|
) error
|
||||||
|
|
||||||
InsertRoom(
|
|
||||||
ctx context.Context,
|
|
||||||
room models.Room,
|
|
||||||
) error
|
|
||||||
|
|
||||||
DeleteUserStatus(
|
DeleteUserStatus(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
username string,
|
username string,
|
||||||
) error
|
) error
|
||||||
|
|
||||||
DeleteRoom(
|
|
||||||
ctx context.Context,
|
|
||||||
roomId int,
|
|
||||||
) error
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/uptrace/bun"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RoomType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
RoomTypeWordle RoomType = iota
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
RoomTypeStr = []string{"Wordle"}
|
|
||||||
)
|
|
||||||
|
|
||||||
type RoomStatus int
|
|
||||||
|
|
||||||
const (
|
|
||||||
RoomStatusWaiting RoomStatus = iota
|
|
||||||
RoomStatusPlaying
|
|
||||||
RoomStatusEnded
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
RoomStatusStr = []string{"Waiting", "Playing", "Ended"}
|
|
||||||
)
|
|
||||||
|
|
||||||
type Room struct {
|
|
||||||
bun.BaseModel `bun:"table:room"`
|
|
||||||
|
|
||||||
Id int `bun:"id,pk,autoincrement"`
|
|
||||||
Creater string `bun:"creater"`
|
|
||||||
Type RoomType `bun:"type"`
|
|
||||||
Status RoomStatus `bun:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self Room) View() string {
|
|
||||||
return fmt.Sprintf("%2d. %10s %10s [%s]",
|
|
||||||
self.Id,
|
|
||||||
self.Creater,
|
|
||||||
RoomTypeStr[self.Type],
|
|
||||||
RoomStatusStr[self.Status],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
77
plays/game.go
Normal file
77
plays/game.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package plays
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gitea.konchin.com/ytshih/inp2025/game/types"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Game struct {
|
||||||
|
*Base
|
||||||
|
state types.WordleState
|
||||||
|
input string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGame(base *Base) *Game {
|
||||||
|
m := Game{
|
||||||
|
Base: base,
|
||||||
|
state: types.NewWordleState(),
|
||||||
|
input: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
return &m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Game) Init() tea.Cmd {
|
||||||
|
return tea.Batch(tea.ClearScreen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Game) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
if _, cmd := m.Base.Update(msg); cmd != nil {
|
||||||
|
return m, cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch msg.Type {
|
||||||
|
case tea.KeyBackspace:
|
||||||
|
if len(m.input) > 0 {
|
||||||
|
m.input = m.input[:len(m.input)-1]
|
||||||
|
}
|
||||||
|
case tea.KeyEnter:
|
||||||
|
if len(m.input) == types.MaxLength {
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
case tea.KeyRunes:
|
||||||
|
if len(m.input) < types.MaxLength {
|
||||||
|
m.input = m.input + string(msg.Runes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Game) View() string {
|
||||||
|
var b strings.Builder
|
||||||
|
|
||||||
|
for _, s := range m.history {
|
||||||
|
b.WriteString(s.View())
|
||||||
|
b.WriteRune('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString(m.input)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Game) Next(queue *[]*tea.Program) error {
|
||||||
|
if len(m.input) == types.MaxLength {
|
||||||
|
resp, err := m.Base.client.R().
|
||||||
|
Post("/api/guess")
|
||||||
|
|
||||||
|
*queue = append(*queue,
|
||||||
|
tea.NewProgram(NewGame(m.Base)))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ type Lobby struct {
|
|||||||
|
|
||||||
updateCh chan struct{}
|
updateCh chan struct{}
|
||||||
users []models.UserStatus
|
users []models.UserStatus
|
||||||
rooms []models.Room
|
rooms []types.Room
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLobby(base *Base) *Lobby {
|
func NewLobby(base *Base) *Lobby {
|
||||||
@@ -58,7 +58,8 @@ func updateLobbyInfo(m *Lobby) error {
|
|||||||
}
|
}
|
||||||
m.users = users
|
m.users = users
|
||||||
|
|
||||||
var rooms []models.Room
|
var rooms []types.Room
|
||||||
|
// TODO: scan rooms
|
||||||
_, err = m.Base.client.R().
|
_, err = m.Base.client.R().
|
||||||
SetResult(&rooms).
|
SetResult(&rooms).
|
||||||
ForceContentType("application/json").
|
ForceContentType("application/json").
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/backend/middlewares"
|
"gitea.konchin.com/ytshih/inp2025/game/server/middlewares"
|
||||||
"github.com/uptrace/bunrouter"
|
"github.com/uptrace/bunrouter"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/backend/middlewares"
|
"gitea.konchin.com/ytshih/inp2025/game/server/middlewares"
|
||||||
"github.com/uptrace/bunrouter"
|
"github.com/uptrace/bunrouter"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -3,8 +3,8 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/backend/middlewares"
|
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/models"
|
"gitea.konchin.com/ytshih/inp2025/game/models"
|
||||||
|
"gitea.konchin.com/ytshih/inp2025/game/server/middlewares"
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/types"
|
"gitea.konchin.com/ytshih/inp2025/game/types"
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/utils"
|
"gitea.konchin.com/ytshih/inp2025/game/utils"
|
||||||
"github.com/uptrace/bunrouter"
|
"github.com/uptrace/bunrouter"
|
||||||
@@ -3,8 +3,8 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/backend/middlewares"
|
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/models"
|
"gitea.konchin.com/ytshih/inp2025/game/models"
|
||||||
|
"gitea.konchin.com/ytshih/inp2025/game/server/middlewares"
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/types"
|
"gitea.konchin.com/ytshih/inp2025/game/types"
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/utils"
|
"gitea.konchin.com/ytshih/inp2025/game/utils"
|
||||||
"github.com/uptrace/bunrouter"
|
"github.com/uptrace/bunrouter"
|
||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/backend/middlewares"
|
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/models"
|
"gitea.konchin.com/ytshih/inp2025/game/models"
|
||||||
|
"gitea.konchin.com/ytshih/inp2025/game/server/middlewares"
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/types"
|
"gitea.konchin.com/ytshih/inp2025/game/types"
|
||||||
"gitea.konchin.com/ytshih/inp2025/game/utils"
|
"gitea.konchin.com/ytshih/inp2025/game/utils"
|
||||||
"github.com/uptrace/bunrouter"
|
"github.com/uptrace/bunrouter"
|
||||||
@@ -18,11 +18,6 @@ const docTemplate = `{
|
|||||||
"host": "{{.Host}}",
|
"host": "{{.Host}}",
|
||||||
"basePath": "{{.BasePath}}",
|
"basePath": "{{.BasePath}}",
|
||||||
"paths": {
|
"paths": {
|
||||||
"/api/lobby/rooms": {
|
|
||||||
"get": {
|
|
||||||
"responses": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/lobby/users": {
|
"/api/lobby/users": {
|
||||||
"get": {
|
"get": {
|
||||||
"responses": {}
|
"responses": {}
|
||||||
@@ -10,11 +10,6 @@
|
|||||||
},
|
},
|
||||||
"basePath": "/",
|
"basePath": "/",
|
||||||
"paths": {
|
"paths": {
|
||||||
"/api/lobby/rooms": {
|
|
||||||
"get": {
|
|
||||||
"responses": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/api/lobby/users": {
|
"/api/lobby/users": {
|
||||||
"get": {
|
"get": {
|
||||||
"responses": {}
|
"responses": {}
|
||||||
@@ -14,9 +14,6 @@ info:
|
|||||||
title: Intro. to Network Programming Game
|
title: Intro. to Network Programming Game
|
||||||
version: 0.0.1-alpha
|
version: 0.0.1-alpha
|
||||||
paths:
|
paths:
|
||||||
/api/lobby/rooms:
|
|
||||||
get:
|
|
||||||
responses: {}
|
|
||||||
/api/lobby/users:
|
/api/lobby/users:
|
||||||
get:
|
get:
|
||||||
responses: {}
|
responses: {}
|
||||||
48
server/wordle/handlers.go
Normal file
48
server/wordle/handlers.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package wordle
|
||||||
|
|
||||||
|
import "gitea.konchin.com/ytshih/inp2025/game/types"
|
||||||
|
|
||||||
|
type OperationType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
OperationTypeGuess OperationType = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
type Operation struct {
|
||||||
|
Type OperationType
|
||||||
|
Username string
|
||||||
|
Guess string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Handlers struct {
|
||||||
|
state WordleState
|
||||||
|
ans string
|
||||||
|
|
||||||
|
opCh chan Operation
|
||||||
|
subs []*chan types.WordleState
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandlers() *Handlers {
|
||||||
|
ret := &Handlers{subs: nil}
|
||||||
|
go ret.GameFlow()
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Handlers) GameFlow() {
|
||||||
|
for op := range self.opCh {
|
||||||
|
switch op.Type {
|
||||||
|
case OperationTypeGuess:
|
||||||
|
self.state.CurrentGuess++
|
||||||
|
if self.state.CurrentGuess >= len(self.Users) {
|
||||||
|
for i := range self.Users {
|
||||||
|
self.Users[i].History = append(self.Users[i].History,
|
||||||
|
types.NewWord(input, ans))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sub := range self.subs {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
server/wordle/wsGetState.go
Normal file
46
server/wordle/wsGetState.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package wordle
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gitea.konchin.com/ytshih/inp2025/game/server/middlewares"
|
||||||
|
"gitea.konchin.com/ytshih/inp2025/game/types"
|
||||||
|
"gitea.konchin.com/ytshih/inp2025/game/utils"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/uptrace/bunrouter"
|
||||||
|
"github.com/vmihailenco/msgpack/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (self *handlers) WSGetState(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
req bunrouter.Request,
|
||||||
|
) error {
|
||||||
|
ctx := req.Context()
|
||||||
|
|
||||||
|
c, err := upgrader.Upgrade(w, req, nil)
|
||||||
|
if err != nil {
|
||||||
|
return middlewares.HTTPError{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Message: "failed to upgrade websocket",
|
||||||
|
OriginError: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
dataCh := make(chan types.WordleState)
|
||||||
|
handlers.subs = append(handlers.subs, &dataCh)
|
||||||
|
|
||||||
|
for data := range dataCh {
|
||||||
|
b, err := msgpack.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return middlewares.HTTPError{
|
||||||
|
StatusCode: http.StatusInternalServerError,
|
||||||
|
Message: "failed to marshal data into msgpack",
|
||||||
|
OriginError: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.WriteMessage(websocket.BinaryMessage, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.Success(w)
|
||||||
|
}
|
||||||
27
types/game.go
Normal file
27
types/game.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type RoomStatus int
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoomStatusWaiting RoomStatus = iota
|
||||||
|
RoomStatusPlaying
|
||||||
|
RoomStatusEnded
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
RoomStatusStr = []string{"Waiting", "Playing", "Ended"}
|
||||||
|
)
|
||||||
|
|
||||||
|
type Room struct {
|
||||||
|
Creater string
|
||||||
|
Status RoomStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self Room) View() string {
|
||||||
|
return fmt.Sprintf("%10s [%s]",
|
||||||
|
self.Creater,
|
||||||
|
RoomStatusStr[self.Status],
|
||||||
|
)
|
||||||
|
}
|
||||||
83
types/wordle.go
Normal file
83
types/wordle.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CharStatus int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CharStatusA CharStatus = iota
|
||||||
|
CharStatusB
|
||||||
|
CharStatusC
|
||||||
|
)
|
||||||
|
|
||||||
|
type Char struct {
|
||||||
|
Char rune `msgpack:"char"`
|
||||||
|
Status CharStatus `msgpack:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChar() Char {
|
||||||
|
return Char{
|
||||||
|
Char: 0x0,
|
||||||
|
Status: CharStatusC,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self Char) View() string {
|
||||||
|
style := lipgloss.NewStyle()
|
||||||
|
|
||||||
|
switch self.Status {
|
||||||
|
case CharStatusA:
|
||||||
|
style = style.Background(lipgloss.Color("#00ff00"))
|
||||||
|
case CharStatusB:
|
||||||
|
style = style.Background(lipgloss.Color("#ffff00"))
|
||||||
|
case CharStatusC:
|
||||||
|
style = style.Background(lipgloss.Color("#808080"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return style.Render(string(self.Char))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Word struct {
|
||||||
|
Chars []Char `msgpack:"chars"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWord() Word {
|
||||||
|
return Word{
|
||||||
|
Chars: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self Word) View() string {
|
||||||
|
var b strings.Builder
|
||||||
|
|
||||||
|
for _, char := range self.Chars {
|
||||||
|
b.WriteString(char.View())
|
||||||
|
b.WriteRune('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
MaxLength = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserState struct {
|
||||||
|
History []Word `msgpack:"history"`
|
||||||
|
Input string
|
||||||
|
}
|
||||||
|
|
||||||
|
type WordleState struct {
|
||||||
|
Users map[string]UserState `msgpack:"users"`
|
||||||
|
CurrentGuess int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWordleState() WordleState {
|
||||||
|
return WordleState{
|
||||||
|
Users: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,5 @@ func InitDB(ctx context.Context, db *bun.DB) error {
|
|||||||
return db.ResetModel(ctx,
|
return db.ResetModel(ctx,
|
||||||
(*models.User)(nil),
|
(*models.User)(nil),
|
||||||
(*models.UserStatus)(nil),
|
(*models.UserStatus)(nil),
|
||||||
(*models.Room)(nil),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
32
workflows/wordleServer.go
Normal file
32
workflows/wordleServer.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package workflows
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gitea.konchin.com/ytshih/inp2025/game/implements"
|
||||||
|
"gitea.konchin.com/ytshih/inp2025/game/server/middlewares"
|
||||||
|
"gitea.konchin.com/ytshih/inp2025/game/server/wordle"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/uptrace/bunrouter"
|
||||||
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func WordleServer(ctx context.Context) {
|
||||||
|
handlers := wordle.NewHandlers()
|
||||||
|
middlewareHandlers := middlewares.NewHandlers(
|
||||||
|
implements.NewBunDatabase(nil))
|
||||||
|
|
||||||
|
router := bunrouter.New()
|
||||||
|
|
||||||
|
apiGroup := router.NewGroup("/api").
|
||||||
|
Use(middlewareHandlers.ErrorHandler)
|
||||||
|
apiGroup.GET("/state",
|
||||||
|
handlers.WSGetState)
|
||||||
|
apiGroup.POST("/guess",
|
||||||
|
handlers.WSPostGuess)
|
||||||
|
|
||||||
|
log.Println(http.ListenAndServe(":"+viper.GetString("port"),
|
||||||
|
otelhttp.NewHandler(router, "")))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user