Compare commits

..

20 Commits

Author SHA1 Message Date
464956dd47 hack to fix crashes when a user connects to a game twice using the same browser 2024-06-27 21:30:29 +02:00
88fb5b9fa7 go get -u 2024-05-21 23:59:37 +02:00
8dc8e08f35 Merge pull request 'Fix/enable reconnecting to existing games' (#15) from simplify-connecting into master
Reviewed-on: #15
2024-05-21 21:55:41 +00:00
34143ed3dc remove unused channel 2024-05-21 23:44:37 +02:00
fd2fb3fab6 change behavior of reading/writing from websockets 2024-05-21 23:41:37 +02:00
6f16a5c23b wip 2024-05-20 17:22:06 +02:00
636ce06836 Improve reconnection handling
1. Lobbies are only identified by their passphrases
2. Improve logging
3. Do not close an existing websocket connection for a player but ignore
   the request
2024-05-20 15:36:13 +02:00
b623410aff Merge pull request 'Make games rejoinable again!' (#14) from make-games-rejoinable-again into master
Reviewed-on: #14
2024-05-19 12:44:57 +00:00
75fd0cc400 go mod tidy 2024-05-15 21:17:32 +02:00
cd2ab106a2 go get -u 2024-05-15 21:17:01 +02:00
90dca37d44 fix board state when reconnecting and set access headers 2024-05-15 19:45:35 +02:00
682ce8437b make games rejoinable 2024-05-15 14:28:14 +02:00
b4c82d6c8f remove .vscode 2024-05-13 16:20:07 +02:00
e4ad872849 introduce some simple methods to set/get a player's color 2024-05-12 15:49:27 +02:00
0f71743598 Merge pull request 'Some more changes for the new game handling api and also rate limiting' (#13) from game-handler into master
Reviewed-on: #13
2024-05-12 13:48:22 +00:00
97ad45e505 Rate limit websocket connection 2024-05-12 15:42:40 +02:00
4207a0ed72 forgot files 2024-05-12 15:37:53 +02:00
fc088a04fe More work for introducing a new game handler, also: ratelimiter 2024-05-12 15:36:30 +02:00
58002a1b38 Groundwork for handler that allows reconnecting 2024-05-11 22:57:47 +02:00
829a80d62a Merge pull request 'Replace old websocket library with gorilla's one' (#12) from introduce-gorilla-ws into master
Reviewed-on: #12
2024-05-11 17:17:50 +00:00
19 changed files with 331 additions and 264 deletions

15
.vscode/launch.json vendored
View File

@ -1,15 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/main.go"
}
]
}

View File

@ -1,24 +1,26 @@
package handler package handler
import ( import (
"log"
"mchess_server/api" "mchess_server/api"
"mchess_server/chess" "mchess_server/chess"
lobbies "mchess_server/lobby_registry" "mchess_server/lobbies"
"mchess_server/usher"
"mchess_server/utils" "mchess_server/utils"
"net/http" "net/http"
"sync" "sync"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
"go.uber.org/ratelimit"
) )
var mut sync.Mutex var mut sync.Mutex
var limiter = ratelimit.New(10)
func HostGameHandler(c *gin.Context) {
limiter.Take()
func HostPrivateGameHandler(c *gin.Context) {
player := chess.NewPlayer(uuid.New()) player := chess.NewPlayer(uuid.New())
u := usher.GetUsher() u := lobbies.GetUsher()
mut.Lock() mut.Lock()
defer mut.Unlock() defer mut.Unlock()
@ -28,7 +30,6 @@ func HostPrivateGameHandler(c *gin.Context) {
passphrase := lobby.Passphrase.String() passphrase := lobby.Passphrase.String()
info := api.PlayerInfo{ info := api.PlayerInfo{
PlayerID: &player.Uuid, PlayerID: &player.Uuid,
LobbyID: &lobby.Uuid,
Passphrase: &passphrase, Passphrase: &passphrase,
} }
c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Origin", "*")
@ -36,6 +37,8 @@ func HostPrivateGameHandler(c *gin.Context) {
} }
func GetLobbyForPassphraseHandler(c *gin.Context) { func GetLobbyForPassphraseHandler(c *gin.Context) {
limiter.Take()
reqPassphrase := c.Param("phrase") reqPassphrase := c.Param("phrase")
if reqPassphrase == "" { if reqPassphrase == "" {
c.IndentedJSON(http.StatusBadRequest, reqPassphrase) c.IndentedJSON(http.StatusBadRequest, reqPassphrase)
@ -50,59 +53,44 @@ func GetLobbyForPassphraseHandler(c *gin.Context) {
return return
} }
lobbyInfo := api.LobbyInfo{ passphrase := api.Passphrase{
ID: &lobby.Uuid, Value: (*string)(&lobby.Passphrase),
} }
c.IndentedJSON(http.StatusOK, lobbyInfo)
}
func RegisterForRandomGame(c *gin.Context) {
player := chess.NewPlayer(uuid.New())
usher := usher.GetUsher()
mut.Lock()
defer mut.Unlock()
lobby := usher.WelcomeNewPlayer(player)
usher.AddPlayerToLobbyAndStartGameIfFull(player, lobby)
info := api.PlayerInfo{
PlayerID: &player.Uuid,
LobbyID: &lobby.Uuid,
}
log.Println("responding with info ", info)
c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Origin", "*")
c.IndentedJSON(http.StatusOK, info) c.IndentedJSON(http.StatusOK, passphrase)
} }
func JoinPrivateGame(c *gin.Context) { func JoinPrivateGame(c *gin.Context) {
limiter.Take()
req := api.PlayerInfo{} req := api.PlayerInfo{}
log.Println(c.Request.Body)
err := c.ShouldBindJSON(&req) err := c.ShouldBindJSON(&req)
if err != nil || req.Passphrase == nil || *req.Passphrase == "" { if err != nil || req.Passphrase == nil || *req.Passphrase == "" {
c.IndentedJSON(http.StatusNotFound, req) c.IndentedJSON(http.StatusNotFound, req)
return
} }
u := usher.GetUsher() u := lobbies.GetUsher()
if req.Passphrase != nil && if req.PlayerID != nil { //is reconnect
*req.Passphrase != "" &&
req.PlayerID != nil &&
req.LobbyID != nil { //is reconnect
lobby := u.FindExistingPrivateLobby(utils.Passphrase(*req.Passphrase)) lobby := u.FindExistingPrivateLobby(utils.Passphrase(*req.Passphrase))
_, found := lobby.GetPlayerByUUID(*req.PlayerID) var found bool
if lobby != nil {
_, found = lobby.GetPlayerByUUID(*req.PlayerID)
}
if found { if found {
c.Header("Access-Control-Allow-Origin", "*")
c.IndentedJSON( c.IndentedJSON(
http.StatusOK, http.StatusOK,
api.PlayerInfo{ api.PlayerInfo{
PlayerID: req.PlayerID, PlayerID: req.PlayerID,
LobbyID: req.LobbyID,
Passphrase: req.Passphrase, Passphrase: req.Passphrase,
}) })
return return
} else { } else {
c.IndentedJSON(http.StatusNotFound, req) c.IndentedJSON(http.StatusNotFound, req)
return
} }
} }
@ -120,9 +108,32 @@ func JoinPrivateGame(c *gin.Context) {
info := api.PlayerInfo{ info := api.PlayerInfo{
PlayerID: &player.Uuid, PlayerID: &player.Uuid,
LobbyID: &lobby.Uuid,
Passphrase: req.Passphrase, Passphrase: req.Passphrase,
} }
c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Origin", "*")
c.IndentedJSON(http.StatusOK, info) c.IndentedJSON(http.StatusOK, info)
} }
func JoinGameHandler(c *gin.Context) {
limiter.Take()
passphrase := api.Passphrase{}
err := c.ShouldBindJSON(&passphrase)
if err != nil {
c.IndentedJSON(http.StatusBadRequest, nil)
return
}
u := lobbies.GetUsher()
lobby := u.FindExistingPrivateLobby(utils.Passphrase(*passphrase.Value))
if lobby == nil {
c.IndentedJSON(http.StatusNotFound, nil)
return
}
lobbyInfo := api.Passphrase{
Value: (*string)(&lobby.Passphrase),
}
c.IndentedJSON(http.StatusOK, lobbyInfo)
}

View File

@ -9,18 +9,17 @@ import (
"testing" "testing"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func Test_GetLobbyFromPassphraseHandler(t *testing.T) { func Test_GetLobbyFromPassphraseHandler(t *testing.T) {
var passphraseURLParameter string var passphraseURLParameter string
var hostedLobbyId uuid.UUID var receivedPhrase string
t.Run("host a lobby", func(t *testing.T) { t.Run("host a lobby", func(t *testing.T) {
r1 := httptest.NewRecorder() r1 := httptest.NewRecorder()
ctx1, e1 := gin.CreateTestContext(r1) ctx1, e1 := gin.CreateTestContext(r1)
e1.GET("/api/hostPrivate", HostPrivateGameHandler) e1.GET("/api/hostPrivate", HostGameHandler)
hostGameRequest, _ := http.NewRequest("GET", "/api/hostPrivate", nil) hostGameRequest, _ := http.NewRequest("GET", "/api/hostPrivate", nil)
ctx1.Request = hostGameRequest ctx1.Request = hostGameRequest
@ -30,8 +29,7 @@ func Test_GetLobbyFromPassphraseHandler(t *testing.T) {
err := json.Unmarshal(r1.Body.Bytes(), &playerInfo) err := json.Unmarshal(r1.Body.Bytes(), &playerInfo)
assert.NoError(t, err) assert.NoError(t, err)
receivedPhrase := *playerInfo.Passphrase receivedPhrase = *playerInfo.Passphrase
hostedLobbyId = *playerInfo.LobbyID
passphrase := utils.NewPassphraseFromString(receivedPhrase) passphrase := utils.NewPassphraseFromString(receivedPhrase)
passphraseURLParameter = passphrase.AsURLParam() passphraseURLParameter = passphrase.AsURLParam()
@ -48,10 +46,10 @@ func Test_GetLobbyFromPassphraseHandler(t *testing.T) {
engine.ServeHTTP(r2, getLobbyRequest) engine.ServeHTTP(r2, getLobbyRequest)
lobbyInfo := api.LobbyInfo{} passhrase := api.Passphrase{}
err := json.Unmarshal(r2.Body.Bytes(), &lobbyInfo) err := json.Unmarshal(r2.Body.Bytes(), &passhrase)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, hostedLobbyId, *lobbyInfo.ID) assert.Equal(t, http.StatusOK, r2.Code)
}) })
} }

View File

@ -1,4 +1,4 @@
package websocket package handler
import ( import (
"context" "context"
@ -6,9 +6,10 @@ import (
"fmt" "fmt"
"log" "log"
"mchess_server/api" "mchess_server/api"
"mchess_server/utils"
"net/http" "net/http"
lobbies "mchess_server/lobby_registry" "mchess_server/lobbies"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
gorillaws "github.com/gorilla/websocket" gorillaws "github.com/gorilla/websocket"
@ -21,7 +22,8 @@ var upgrader = gorillaws.Upgrader{
} }
func RegisterWebSocketConnection(c *gin.Context) { func RegisterWebSocketConnection(c *gin.Context) {
log.Println(c.Request) limiter.Take()
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -31,6 +33,8 @@ func RegisterWebSocketConnection(c *gin.Context) {
} }
func waitForAndHandlePlayerID(ctx context.Context, conn *gorillaws.Conn) { func waitForAndHandlePlayerID(ctx context.Context, conn *gorillaws.Conn) {
limiter.Take()
msgType, msg, err := conn.ReadMessage() msgType, msg, err := conn.ReadMessage()
if err != nil { if err != nil {
errorMessage := fmt.Sprintf("Reading from websocket connection did not work: %s", err) errorMessage := fmt.Sprintf("Reading from websocket connection did not work: %s", err)
@ -51,7 +55,7 @@ func waitForAndHandlePlayerID(ctx context.Context, conn *gorillaws.Conn) {
return return
} }
lobby := lobbies.GetLobbyRegistry().GetLobbyByUUID(*info.LobbyID) lobby := lobbies.GetLobbyRegistry().GetLobbyByPassphrase(utils.NewPassphraseFromString(*info.Passphrase))
if lobby == nil { if lobby == nil {
conn.WriteMessage(msgType, []byte("lobby not found")) conn.WriteMessage(msgType, []byte("lobby not found"))
conn.Close() conn.Close()
@ -64,9 +68,16 @@ func waitForAndHandlePlayerID(ctx context.Context, conn *gorillaws.Conn) {
conn.Close() conn.Close()
return return
} }
if player.Conn.HasWebsocketConnection() {
player.Conn.Close("closing existing connection")
}
lobby.Game.SetWebsocketConnectionFor(ctx, player, conn) lobby.Game.SetWebsocketConnectionFor(ctx, player, conn)
log.Println("player after setting connection: ", player)
log.Println("player after setting connection: ")
log.Println("id: ", player.Uuid)
log.Println("color: ", player.GetColor())
log.Println("Connection: ", player.Conn.ID)
}
func ConnectWsForGame(c *gin.Context) {
limiter.Take()
} }

View File

@ -1,7 +0,0 @@
package api
import "github.com/google/uuid"
type LobbyInfo struct {
ID *uuid.UUID `json:"id,omitempty"`
}

View File

@ -4,6 +4,5 @@ import "github.com/google/uuid"
type PlayerInfo struct { type PlayerInfo struct {
PlayerID *uuid.UUID `json:"playerID,omitempty"` PlayerID *uuid.UUID `json:"playerID,omitempty"`
LobbyID *uuid.UUID `json:"lobbyID,omitempty"`
Passphrase *string `json:"passphrase,omitempty"` Passphrase *string `json:"passphrase,omitempty"`
} }

View File

@ -58,8 +58,8 @@ func (game Game) GetPlayer2() *Player {
} }
func (game *Game) prepare() { func (game *Game) prepare() {
game.players[0].color = types.White game.players[0].SetColor(types.White)
game.players[1].color = types.Black game.players[1].SetColor(types.Black)
game.currentTurnPlayer = game.GetPlayer1() game.currentTurnPlayer = game.GetPlayer1()
@ -99,13 +99,13 @@ func (game *Game) Handle() {
game.gameState = PlayerToMove game.gameState = PlayerToMove
case PlayerToMove: case PlayerToMove:
log.Println("with ", game.currentTurnPlayer.GetPlayerColor(), " to move") log.Println("with ", game.currentTurnPlayer.GetColor(), " to move")
receivedMove, err = game.currentTurnPlayer.ReadMove() receivedMove, err = game.currentTurnPlayer.ReadMove()
if err != nil { if err != nil {
log.Println("Error while reading message:", err) log.Println("Error while reading message:", err)
return return
} }
log.Println("Player ", game.currentTurnPlayer, " moved:\n", receivedMove) log.Println("Player ", game.currentTurnPlayer.color.String(), " moved:\n", receivedMove)
game.gameState = CheckMove game.gameState = CheckMove
case CheckMove: case CheckMove:
@ -117,7 +117,7 @@ func (game *Game) Handle() {
log.Println("Error marshalling 'colorDetermined' message for player 1", err) log.Println("Error marshalling 'colorDetermined' message for player 1", err)
return return
} }
game.currentTurnPlayer.writeMessage(invalidMoveMessage) game.currentTurnPlayer.writeMessage(string(invalidMoveMessage))
game.gameState = PlayerToMove game.gameState = PlayerToMove
continue continue
} }
@ -157,6 +157,14 @@ func (game *Game) AddPlayersToGame(player *Player) {
game.players = append(game.players, player) game.players = append(game.players, player)
} }
func (game *Game) AreBothPlayersConnected() bool {
if len(game.GetPlayers()) < 2 {
return false
}
return game.players[0].hasWebsocketConnection() && game.players[1].hasWebsocketConnection()
}
func (game *Game) killGame() { func (game *Game) killGame() {
log.Println("Game should be killed") log.Println("Game should be killed")
} }
@ -173,21 +181,22 @@ func (game Game) notifyPlayersAboutGameStart() error {
return err return err
} }
game.GetPlayer1().writeMessage(colorDeterminedPlayer1) game.GetPlayer1().writeMessage(string(colorDeterminedPlayer1))
game.GetPlayer1().SendBoardState(types.Move{}, game.board.PGN(), types.White) game.GetPlayer1().SendBoardState(types.Move{}, game.board.PGN(), types.White)
game.GetPlayer2().writeMessage(colorDeterminedPlayer2) game.GetPlayer2().writeMessage(string(colorDeterminedPlayer2))
game.GetPlayer2().SendBoardState(types.Move{}, game.board.PGN(), types.White) game.GetPlayer2().SendBoardState(types.Move{}, game.board.PGN(), types.White)
return nil return nil
} }
func (game Game) broadcastMove(move types.Move) error { func (game Game) broadcastMove(move types.Move) error {
err := game.GetPlayer1().SendBoardState(move, game.board.PGN(), game.currentTurnPlayer.color) log.Println("broadcast move")
err := game.GetPlayer1().SendBoardState(move, game.board.PGN(), game.currentTurnPlayer.GetColor())
if err != nil { if err != nil {
return err return err
} }
err = game.GetPlayer2().SendBoardState(move, game.board.PGN(), game.currentTurnPlayer.color) err = game.GetPlayer2().SendBoardState(move, game.board.PGN(), game.currentTurnPlayer.GetColor())
if err != nil { if err != nil {
return err return err
} }
@ -212,5 +221,9 @@ func (game *Game) playerDisconnected(p *Player) {
} }
func (game *Game) SetWebsocketConnectionFor(ctx context.Context, p *Player, ws *gorillaws.Conn) { func (game *Game) SetWebsocketConnectionFor(ctx context.Context, p *Player, ws *gorillaws.Conn) {
p.SetWebsocketConnectionAndSendBoardState(ctx, ws, game.board.PGN(), game.board.colorToMove) p.SetWebsocketConnectionAndSendBoardState(ctx, ws, &game.board)
}
func (game *Game) SendBoardStateTo(p *Player) {
p.SendBoardState(game.board.getLastMove(), game.board.PGN(), game.board.colorToMove)
} }

View File

@ -36,16 +36,25 @@ func (p Player) hasWebsocketConnection() bool {
func (p *Player) SetWebsocketConnection(ctx context.Context, ws *gorillaws.Conn) { func (p *Player) SetWebsocketConnection(ctx context.Context, ws *gorillaws.Conn) {
p.Conn.SetWebsocketConnection(ws) p.Conn.SetWebsocketConnection(ws)
p.Conn.SetForColor(p.color)
} }
func (p *Player) SetWebsocketConnectionAndSendBoardState( func (p *Player) SetWebsocketConnectionAndSendBoardState(
ctx context.Context, ctx context.Context,
ws *gorillaws.Conn, ws *gorillaws.Conn,
boardPosition string, board *Board,
turnColor types.ChessColor,
) { ) {
p.SetWebsocketConnection(ctx, ws) p.SetWebsocketConnection(ctx, ws)
p.SendBoardState(types.Move{}, boardPosition, turnColor) p.SendBoardState(board.getLastMove(), board.PGN(), board.colorToMove)
}
func (p *Player) SetColor(color types.ChessColor) {
p.color = color
p.Conn.SetForColor(p.color)
}
func (p *Player) GetColor() types.ChessColor {
return p.color
} }
func (p *Player) SetDisconnectCallback(cb func(*Player)) { func (p *Player) SetDisconnectCallback(cb func(*Player)) {
@ -63,8 +72,8 @@ func (p *Player) IsInGame() bool {
} }
func (p *Player) SendBoardState(move types.Move, boardPosition string, turnColor types.ChessColor) error { func (p *Player) SendBoardState(move types.Move, boardPosition string, turnColor types.ChessColor) error {
var pColor = p.color var pColor = p.GetColor()
if p.color == "" { // we default to white if we do not know the color yet if p.GetColor() == "" { // we default to white if we do not know the color yet
pColor = types.White pColor = types.White
} }
@ -80,11 +89,8 @@ func (p *Player) SendBoardState(move types.Move, boardPosition string, turnColor
return err return err
} }
err = p.writeMessage(messageToSend) p.writeMessage(string(messageToSend))
if err != nil {
log.Println("Error during message writing:", err)
return err
}
return nil return nil
} }
@ -99,11 +105,8 @@ func (p *Player) SendMoveAndPosition(move types.Move, boardPosition string) erro
return err return err
} }
err = p.writeMessage(messageToSend) p.writeMessage(string(messageToSend))
if err != nil {
log.Println("Error during message writing:", err)
return err
}
return nil return nil
} }
@ -117,26 +120,20 @@ func (p *Player) SendGameEnded(reason GameEndedReason) error {
log.Println("Error while marshalling: ", err) log.Println("Error while marshalling: ", err)
return err return err
} }
err = p.writeMessage(messageToSend) p.writeMessage(string(messageToSend))
if err != nil {
log.Println("Error during message writing:", err)
return err
}
return nil return nil
} }
func (p *Player) writeMessage(msg []byte) error { func (p *Player) writeMessage(msg string) {
return p.Conn.Write(msg) p.Conn.Write(msg)
} }
func (p *Player) ReadMove() (types.Move, error) { func (p *Player) ReadMove() (types.Move, error) {
receivedMessage, err := p.readMessage() receivedMessage := p.readMessage()
if err != nil {
return types.Move{}, err
}
var msg api.WebsocketMessage var msg api.WebsocketMessage
err = json.Unmarshal(receivedMessage, &msg) err := json.Unmarshal(receivedMessage, &msg)
if err != nil { if err != nil {
return types.Move{}, err return types.Move{}, err
} }
@ -148,13 +145,9 @@ func (p *Player) ReadMove() (types.Move, error) {
return *msg.Move, nil return *msg.Move, nil
} }
func (p *Player) readMessage() ([]byte, error) { func (p *Player) readMessage() []byte {
msg, err := p.Conn.Read() msg := p.Conn.Read()
log.Printf("Reading message: %s from player %s", string(msg), p.Uuid.String()) log.Printf("Reading message from %s: %s", p.color.String(), string(msg))
return msg, err return msg
}
func (p Player) GetPlayerColor() string {
return string(p.color)
} }

View File

@ -50,7 +50,7 @@ func (b *MessageBuffer) Insert(msg string) {
b.cond.Broadcast() b.cond.Broadcast()
} }
func (b *MessageBuffer) Get() (string, error) { func (b *MessageBuffer) Get() string {
b.cond.L.Lock() b.cond.L.Lock()
defer b.cond.L.Unlock() defer b.cond.L.Unlock()
@ -69,7 +69,7 @@ func (b *MessageBuffer) Get() (string, error) {
} }
b.getIndex = b.incrementAndWrapIndex(b.getIndex) b.getIndex = b.incrementAndWrapIndex(b.getIndex)
return msg.content, nil return msg.content
} }
func (b MessageBuffer) incrementAndWrapIndex(index int) int { func (b MessageBuffer) incrementAndWrapIndex(index int) int {

View File

@ -66,8 +66,7 @@ func Test_MessageBuffer_GetWaitsForFirstData(t *testing.T) {
buf.Insert("delayed-message") buf.Insert("delayed-message")
}() }()
msg, err := buf.Get() msg := buf.Get()
assert.NoError(t, err)
endTime := time.Now() endTime := time.Now()
@ -79,8 +78,7 @@ func Test_MessageBuffer_GetWaitsForNewData(t *testing.T) {
buf := newMessageBuffer(2) buf := newMessageBuffer(2)
buf.Insert("message-1") buf.Insert("message-1")
msg, err := buf.Get() msg := buf.Get()
assert.NoError(t, err)
assert.Equal(t, "message-1", msg) assert.Equal(t, "message-1", msg)
go func() { go func() {
@ -89,8 +87,7 @@ func Test_MessageBuffer_GetWaitsForNewData(t *testing.T) {
buf.Insert("delayed-message") buf.Insert("delayed-message")
}() }()
msg, err = buf.Get() msg = buf.Get()
assert.NoError(t, err)
assert.Equal(t, "delayed-message", msg) assert.Equal(t, "delayed-message", msg)
} }
@ -117,8 +114,7 @@ func Test_MessageBuffer_IndexesAreCorrectAfterOverwritingOldData(t *testing.T) {
}, },
buf.messages) buf.messages)
msg, err := buf.Get() msg := buf.Get()
assert.NoError(t, err)
assert.Equal(t, "message-2", msg) assert.Equal(t, "message-2", msg)
} }
@ -126,13 +122,11 @@ func Test_MessageBuffer_GetWaitsForNewDataIfOldOneWasAlreadyGotten(t *testing.T)
buf := newMessageBuffer(2) buf := newMessageBuffer(2)
buf.Insert(message1) buf.Insert(message1)
msg, err := buf.Get() msg := buf.Get()
assert.NoError(t, err)
assert.Equal(t, message1, msg) assert.Equal(t, message1, msg)
buf.Insert(message2) buf.Insert(message2)
msg, err = buf.Get() msg = buf.Get()
assert.NoError(t, err)
assert.Equal(t, message2, msg) assert.Equal(t, message2, msg)
go func() { go func() {
@ -140,8 +134,7 @@ func Test_MessageBuffer_GetWaitsForNewDataIfOldOneWasAlreadyGotten(t *testing.T)
buf.Insert(message3) buf.Insert(message3)
}() }()
msg, err = buf.Get() msg = buf.Get()
assert.NoError(t, err)
assert.Equal(t, message3, msg) assert.Equal(t, message3, msg)
} }
@ -157,9 +150,8 @@ func Test_MessageBuffer_InsertCatchesUpWithRead(t *testing.T) {
buf.Insert(message6) buf.Insert(message6)
buf.Insert(message7) buf.Insert(message7)
msg, err := buf.Get() msg := buf.Get()
assert.NoError(t, err)
assert.Equal(t, message3, msg) assert.Equal(t, message3, msg)
} }
@ -172,7 +164,7 @@ func Test_MessageBuffer_FuckShitUp(t *testing.T) {
var readMsg = make([]string, 0) var readMsg = make([]string, 0)
go func() { go func() {
for i := 0; i < size*10; i++ { for i := 0; i < size*10; i++ {
msg, _ := buf.Get() msg := buf.Get()
if msg == "99" { if msg == "99" {
break break
} }

View File

@ -3,24 +3,38 @@ package connection
import ( import (
"context" "context"
"log" "log"
"mchess_server/types"
"sync" "sync"
"github.com/google/uuid"
gorillaws "github.com/gorilla/websocket" gorillaws "github.com/gorilla/websocket"
) )
type Connection struct { type Connection struct {
ID uuid.UUID
ws *gorillaws.Conn ws *gorillaws.Conn
wsConnectionEstablished chan bool
wsWriteLock sync.Mutex
ctx context.Context ctx context.Context
buffer MessageBuffer rxBuffer *MessageBuffer
txBuffer *MessageBuffer
disconnectCallback func() disconnectCallback func()
forColor types.ChessColor
/* this is a hack since a user using the same
browser might get the same object of type Connection in two instances of
the same browser. Then if Close() gets called for both of these instances
the first Close() will set ws=nil, and the seconds one will dereference this
nil pointer. The correct way to fix this, is to make sure, two instances
of the same browser do not get the same connection instance.
At the moment, the usage of localStorage 'identifies' an already connected
player and reuses the old Connection struct instance */
closingMutex sync.Mutex
} }
func NewConnection(options ...func(*Connection)) *Connection { func NewConnection(options ...func(*Connection)) *Connection {
connection := Connection{ connection := Connection{
buffer: *newMessageBuffer(100), ID: uuid.New(),
wsConnectionEstablished: make(chan bool), rxBuffer: newMessageBuffer(100),
txBuffer: newMessageBuffer(100),
} }
for _, option := range options { for _, option := range options {
@ -50,6 +64,10 @@ func WithDisconnectCallback(cb func()) func(*Connection) {
} }
} }
func (conn *Connection) SetForColor(color types.ChessColor) {
conn.forColor = color
}
func (conn *Connection) SetDisconnectCallback(cb func()) { func (conn *Connection) SetDisconnectCallback(cb func()) {
conn.disconnectCallback = cb conn.disconnectCallback = cb
} }
@ -58,57 +76,81 @@ func (conn *Connection) HasWebsocketConnection() bool {
return conn.ws != nil return conn.ws != nil
} }
func (conn *Connection) readFromRxBuffer() {
for {
_, msg, err := conn.ws.ReadMessage()
if err != nil {
conn.logConnection("while reading from websocket: %w", "ERROR:", err.Error())
conn.Close("")
return
}
conn.rxBuffer.Insert(string(msg))
}
}
func (conn *Connection) writeTxBuffer() {
for {
msg := conn.txBuffer.Get()
if conn.ws == nil {
return
}
err := conn.ws.WriteMessage(gorillaws.TextMessage, []byte(msg))
if err != nil {
conn.logConnection("while writing to websocket: %w", "ERROR:", err.Error())
return
}
}
}
func (conn *Connection) SetWebsocketConnection(ws *gorillaws.Conn) { func (conn *Connection) SetWebsocketConnection(ws *gorillaws.Conn) {
if ws == nil { if ws == nil {
conn.logConnection("ERROR: setting ws = null")
return return
} }
conn.ws = ws conn.ws = ws
select { go conn.readFromRxBuffer()
case conn.wsConnectionEstablished <- true: go conn.writeTxBuffer()
default:
defer conn.logConnection("websocket connection set")
} }
go func() { func (conn *Connection) Write(msg string) {
for { conn.logConnection("Writing message: ", string(msg))
_, msg, err := conn.ws.ReadMessage() conn.txBuffer.Insert(msg)
if err != nil {
log.Println("while reading from websocket: %w", err)
if conn.disconnectCallback != nil {
conn.disconnectCallback()
}
return
}
conn.buffer.Insert(string(msg))
}
}()
} }
func (conn *Connection) Write(msg []byte) error { func (conn *Connection) Read() []byte {
conn.wsWriteLock.Lock() msg := conn.rxBuffer.Get()
defer conn.wsWriteLock.Unlock()
if conn.ws == nil { //if ws is not yet set, we wait for it return []byte(msg)
<-conn.wsConnectionEstablished
}
log.Printf("Writing message: %s", string(msg))
return conn.ws.WriteMessage(gorillaws.TextMessage, msg)
}
func (conn *Connection) Read() ([]byte, error) {
msg, err := conn.buffer.Get()
if err != nil {
conn.ws = nil
return nil, err // TODO: Tell game-handler that connection was lost
}
return []byte(msg), err
} }
func (conn *Connection) Close(msg string) { func (conn *Connection) Close(msg string) {
/* This is a hack, do not try this at home.
See more details at the definition of Connection */
conn.closingMutex.Lock()
defer conn.closingMutex.Unlock()
if conn == nil || conn.ws == nil {
return
}
conn.logConnection("closing websocket connection")
conn.ws.WriteMessage(gorillaws.TextMessage, []byte(msg)) conn.ws.WriteMessage(gorillaws.TextMessage, []byte(msg))
conn.ws.Close() conn.ws.Close()
conn.ws = nil conn.ws = nil
conn.txBuffer.Insert("we do this to make txBuffer.Get() return")
}
func (con *Connection) logConnection(v ...string) {
a := ""
for _, s := range v {
a += s + ", "
}
log.Println(a, "on connection ", con.ID, ", for color ", con.forColor.String())
} }

29
go.mod
View File

@ -5,25 +5,27 @@ go 1.22
require ( require (
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/samber/lo v1.39.0 github.com/samber/lo v1.41.0
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
) )
require github.com/benbjohnson/clock v1.3.5 // indirect
require ( require (
github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic v1.11.9 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect github.com/cloudwego/iasm v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/go-playground/validator/v10 v10.22.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.3 // indirect
github.com/gorilla/websocket v1.5.1 github.com/gorilla/websocket v1.5.3
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@ -32,12 +34,13 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/ratelimit v0.3.1
golang.org/x/arch v0.8.0 // indirect golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect golang.org/x/crypto v0.24.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/net v0.25.0 // indirect golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.20.0 // indirect golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.15.0 // indirect golang.org/x/text v0.16.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

34
go.sum
View File

@ -1,5 +1,9 @@
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg=
github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
@ -11,6 +15,8 @@ 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
@ -23,8 +29,10 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 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/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -32,11 +40,15 @@ 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.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
@ -53,6 +65,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/samber/lo v1.41.0 h1:vZEqVQNmX/ykONfla8tiW6cdoLZxb3+LCZ63B8z/2eE=
github.com/samber/lo v1.41.0/go.mod h1:w7R6fO7h2lrnx/s0bWcZ55vXJI89p5UPM6+kyDL373E=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@ -69,23 +83,39 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0=
go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,4 +1,4 @@
package lobby_registry package lobbies
import ( import (
"mchess_server/chess" "mchess_server/chess"
@ -10,7 +10,6 @@ import (
type Lobby struct { type Lobby struct {
Uuid uuid.UUID Uuid uuid.UUID
Game *chess.Game Game *chess.Game
PlayerJoined chan bool
Passphrase utils.Passphrase Passphrase utils.Passphrase
} }
@ -18,7 +17,6 @@ func NewEmptyLobbyWithUUID(uuid uuid.UUID) *Lobby {
return &Lobby{ return &Lobby{
Uuid: uuid, Uuid: uuid,
Game: chess.NewGame(), Game: chess.NewGame(),
PlayerJoined: make(chan bool),
} }
} }
@ -31,12 +29,16 @@ func newEmptyLobbyWithPassphrase() *Lobby {
func (l *Lobby) AddPlayerAndStartGameIfFull(player *chess.Player) { func (l *Lobby) AddPlayerAndStartGameIfFull(player *chess.Player) {
l.Game.AddPlayersToGame(player) l.Game.AddPlayersToGame(player)
if l.IsFull() { if l.ContainsTwoPlayers() {
l.Game.StartHandling() l.Game.StartHandling()
} }
} }
func (w *Lobby) IsFull() bool { func (w *Lobby) AreBothPlayersConnected() bool {
return w.Game.AreBothPlayersConnected()
}
func (w *Lobby) ContainsTwoPlayers() bool {
return len(w.Game.GetPlayers()) == 2 return len(w.Game.GetPlayers()) == 2
} }

View File

@ -1,4 +1,4 @@
package lobby_registry package lobbies
import ( import (
"mchess_server/utils" "mchess_server/utils"
@ -7,7 +7,7 @@ import (
) )
type LobbyRegistry struct { type LobbyRegistry struct {
lobbies map[uuid.UUID]*Lobby lobbies map[utils.Passphrase]*Lobby
} }
var instance *LobbyRegistry var instance *LobbyRegistry
@ -21,7 +21,7 @@ func GetLobbyRegistry() *LobbyRegistry {
} }
func newLobbyRegistry() *LobbyRegistry { func newLobbyRegistry() *LobbyRegistry {
return &LobbyRegistry{lobbies: make(map[uuid.UUID]*Lobby)} return &LobbyRegistry{lobbies: make(map[utils.Passphrase]*Lobby)}
} }
func (r *LobbyRegistry) CreateNewPrivateLobby() *Lobby { func (r *LobbyRegistry) CreateNewPrivateLobby() *Lobby {
@ -32,7 +32,7 @@ func (r *LobbyRegistry) CreateNewPrivateLobby() *Lobby {
func (r *LobbyRegistry) GetLobbyForPlayer() *Lobby { func (r *LobbyRegistry) GetLobbyForPlayer() *Lobby {
for _, lobby := range r.lobbies { for _, lobby := range r.lobbies {
if !lobby.IsFull() { if !lobby.ContainsTwoPlayers() {
return lobby return lobby
} }
} }
@ -42,10 +42,6 @@ func (r *LobbyRegistry) GetLobbyForPlayer() *Lobby {
return newLobby return newLobby
} }
func (r *LobbyRegistry) GetLobbyByUUID(uuid uuid.UUID) *Lobby {
return r.lobbies[uuid]
}
func (r *LobbyRegistry) GetLobbyByPassphrase(p utils.Passphrase) *Lobby { func (r *LobbyRegistry) GetLobbyByPassphrase(p utils.Passphrase) *Lobby {
for _, lobby := range r.lobbies { for _, lobby := range r.lobbies {
if lobby.Passphrase == p { if lobby.Passphrase == p {
@ -56,6 +52,6 @@ func (r *LobbyRegistry) GetLobbyByPassphrase(p utils.Passphrase) *Lobby {
} }
func (r *LobbyRegistry) addNewLobby(lobby *Lobby) uuid.UUID { func (r *LobbyRegistry) addNewLobby(lobby *Lobby) uuid.UUID {
r.lobbies[lobby.Uuid] = lobby r.lobbies[lobby.Passphrase] = lobby
return lobby.Uuid return lobby.Uuid
} }

38
lobbies/usher.go Normal file
View File

@ -0,0 +1,38 @@
package lobbies
import (
"mchess_server/chess"
"mchess_server/utils"
)
type Usher struct {
}
var usherInstance *Usher
func newUsher() *Usher {
return &Usher{}
}
func GetUsher() *Usher {
if usherInstance == nil {
usherInstance = newUsher()
}
return usherInstance
}
func (u *Usher) WelcomeNewPlayer(player *chess.Player) *Lobby {
return GetLobbyRegistry().GetLobbyForPlayer()
}
func (u *Usher) CreateNewPrivateLobby(player *chess.Player) *Lobby {
return GetLobbyRegistry().CreateNewPrivateLobby()
}
func (u *Usher) FindExistingPrivateLobby(p utils.Passphrase) *Lobby {
return GetLobbyRegistry().GetLobbyByPassphrase(p)
}
func (u *Usher) AddPlayerToLobbyAndStartGameIfFull(player *chess.Player, lobby *Lobby) {
lobby.AddPlayerAndStartGameIfFull(player)
}

10
main.go
View File

@ -4,7 +4,6 @@ import (
"flag" "flag"
"log" "log"
"mchess_server/api/handler" "mchess_server/api/handler"
"mchess_server/api/websocket"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -14,6 +13,7 @@ var cert_file = cert_path + "fullchain.pem"
var key_file = cert_path + "privkey.pem" var key_file = cert_path + "privkey.pem"
func main() { func main() {
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
var debugMode bool var debugMode bool
debugModeLong := flag.Bool("debug", false, "activates debug mode") debugModeLong := flag.Bool("debug", false, "activates debug mode")
@ -24,11 +24,13 @@ func main() {
} }
router := gin.Default() router := gin.Default()
router.GET("/api/random", handler.RegisterForRandomGame) router.GET("/api/hostPrivate", handler.HostGameHandler)
router.GET("/api/hostPrivate", handler.HostPrivateGameHandler)
router.POST("/api/joinPrivate", handler.JoinPrivateGame) router.POST("/api/joinPrivate", handler.JoinPrivateGame)
router.GET("/api/ws", websocket.RegisterWebSocketConnection) router.GET("/api/ws", handler.RegisterWebSocketConnection)
router.GET("/api/getLobbyForPassphrase/:phrase", handler.GetLobbyForPassphraseHandler) router.GET("/api/getLobbyForPassphrase/:phrase", handler.GetLobbyForPassphraseHandler)
router.GET("/api/registerWsForGame/:id", handler.ConnectWsForGame)
router.POST("/api/joinGame/:id", handler.JoinGameHandler)
if debugMode { if debugMode {
log.Println("Starting service WITHOUT TLS") log.Println("Starting service WITHOUT TLS")

View File

@ -16,6 +16,10 @@ func (c ChessColor) Opposite() ChessColor {
} }
} }
func (c ChessColor) String() string {
return string(c)
}
type AdditionalState struct { type AdditionalState struct {
BlackKingMoved bool BlackKingMoved bool
WhiteKingMoved bool WhiteKingMoved bool

View File

@ -1,45 +0,0 @@
package usher
import (
"mchess_server/chess"
lobbies "mchess_server/lobby_registry"
"mchess_server/utils"
)
type Usher struct {
}
var instance *Usher
func newUsher() *Usher {
return &Usher{}
}
func GetUsher() *Usher {
if instance == nil {
instance = newUsher()
}
return instance
}
func (u *Usher) WelcomeNewPlayer(player *chess.Player) *lobbies.Lobby {
lobby := lobbies.GetLobbyRegistry().GetLobbyForPlayer()
return lobby
}
func (u *Usher) CreateNewPrivateLobby(player *chess.Player) *lobbies.Lobby {
lobby := lobbies.GetLobbyRegistry().CreateNewPrivateLobby()
return lobby
}
func (u *Usher) FindExistingPrivateLobby(p utils.Passphrase) *lobbies.Lobby {
lobby := lobbies.GetLobbyRegistry().GetLobbyByPassphrase(p)
if lobby == nil || lobby.IsFull() {
return nil
}
return lobby
}
func (u *Usher) AddPlayerToLobbyAndStartGameIfFull(player *chess.Player, lobby *lobbies.Lobby) {
lobby.AddPlayerAndStartGameIfFull(player)
}