Compare commits

..

35 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
42dc4e5af8 go mod tidy 2024-05-11 12:05:41 +02:00
65dc2ebd24 Replace old websocket library with the gorilla one 2024-05-11 12:03:24 +02:00
d40c757776 fix bug that prevented king from moving because server thought it wanted to castling (but it was not a castling move) 2024-05-10 02:29:11 +02:00
a55a46e866 Merge pull request 'get-lobby-from-passphrase' (#11) from get-lobby-from-passphrase into master
Reviewed-on: #11
2024-05-09 20:49:24 +00:00
d7c4f28f3a Fix endpoint for getting lobby id from passphrase
Had to add several helpers (e.g. passphrase ones) to make the endpoint
for getting lobby id work.

Moved all handler functions into handler package.

Added test for getting lobby from passphrase.
2024-05-09 22:29:48 +02:00
11d6e8c98a Introduce handler to get lobby info from passphrase 2024-05-07 23:39:05 +02:00
b2ebf2cc5d Remove more words and remove duplicates 2024-05-02 12:47:18 +02:00
3b77a01dad go get -u 2024-05-01 01:15:32 +02:00
d50c4908c8 Merge pull request 'We introduce a pre-filtered list of (hopefully) clean words. Generating the passphrase with babbler was too slow' (#10) from clean-words into master
Reviewed-on: #10
2024-04-30 23:13:34 +00:00
d7731c08eb We introduce a pre-filtered list of (hopefully) clean words. Generating the passphrase with babbler was too slow 2024-04-30 23:55:27 +02:00
db754f5601 Do not remove a disconnected player from the 'player' slice since that crashes the server 2024-04-23 19:29:32 +02:00
732d759f84 Merge pull request 'Prevent castling in case there is no rook on the castling side' (#8) from fix-castling-without-rook into master
Reviewed-on: #8
2024-04-16 16:20:33 +00:00
25893a5187 Prevent castling in case there is no rook on the castling side 2024-04-16 18:17:28 +02:00
4153f4cc7c Merge pull request 'Fix a bug that allowed castling even if squares were attacked' (#3) from castling-through-check into master
Reviewed-on: #3
2024-04-09 19:55:40 +00:00
77e66aabdd Fix a bug that allowed castling even if squares were attacked 2024-04-09 21:48:27 +02:00
28 changed files with 61563 additions and 663 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"
}
]
}

1
README
View File

@ -1,2 +1,3 @@
Prerequisites:
1. Install a word file: 1. Install a word file:
the server uses babbler that expects a word list at /usr/share/dict/words in order to generate the passphrases for lobbies. the server uses babbler that expects a word list at /usr/share/dict/words in order to generate the passphrases for lobbies.

139
api/handler/handler.go Normal file
View File

@ -0,0 +1,139 @@
package handler
import (
"mchess_server/api"
"mchess_server/chess"
"mchess_server/lobbies"
"mchess_server/utils"
"net/http"
"sync"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"go.uber.org/ratelimit"
)
var mut sync.Mutex
var limiter = ratelimit.New(10)
func HostGameHandler(c *gin.Context) {
limiter.Take()
player := chess.NewPlayer(uuid.New())
u := lobbies.GetUsher()
mut.Lock()
defer mut.Unlock()
lobby := u.CreateNewPrivateLobby(player)
u.AddPlayerToLobbyAndStartGameIfFull(player, lobby)
passphrase := lobby.Passphrase.String()
info := api.PlayerInfo{
PlayerID: &player.Uuid,
Passphrase: &passphrase,
}
c.Header("Access-Control-Allow-Origin", "*")
c.IndentedJSON(http.StatusOK, info)
}
func GetLobbyForPassphraseHandler(c *gin.Context) {
limiter.Take()
reqPassphrase := c.Param("phrase")
if reqPassphrase == "" {
c.IndentedJSON(http.StatusBadRequest, reqPassphrase)
return
}
passPhraseWithSpaces := utils.ConvertToPassphraseWithSpaces(reqPassphrase)
lobby := lobbies.GetLobbyRegistry().GetLobbyByPassphrase(passPhraseWithSpaces)
if lobby == nil {
c.IndentedJSON(http.StatusNotFound, reqPassphrase)
return
}
passphrase := api.Passphrase{
Value: (*string)(&lobby.Passphrase),
}
c.Header("Access-Control-Allow-Origin", "*")
c.IndentedJSON(http.StatusOK, passphrase)
}
func JoinPrivateGame(c *gin.Context) {
limiter.Take()
req := api.PlayerInfo{}
err := c.ShouldBindJSON(&req)
if err != nil || req.Passphrase == nil || *req.Passphrase == "" {
c.IndentedJSON(http.StatusNotFound, req)
return
}
u := lobbies.GetUsher()
if req.PlayerID != nil { //is reconnect
lobby := u.FindExistingPrivateLobby(utils.Passphrase(*req.Passphrase))
var found bool
if lobby != nil {
_, found = lobby.GetPlayerByUUID(*req.PlayerID)
}
if found {
c.Header("Access-Control-Allow-Origin", "*")
c.IndentedJSON(
http.StatusOK,
api.PlayerInfo{
PlayerID: req.PlayerID,
Passphrase: req.Passphrase,
})
return
} else {
c.IndentedJSON(http.StatusNotFound, req)
return
}
}
player := chess.NewPlayer(uuid.New())
mut.Lock()
defer mut.Unlock()
lobby := u.FindExistingPrivateLobby(utils.Passphrase(*req.Passphrase))
if lobby != nil {
u.AddPlayerToLobbyAndStartGameIfFull(player, lobby)
} else {
c.IndentedJSON(http.StatusNotFound, req)
return
}
info := api.PlayerInfo{
PlayerID: &player.Uuid,
Passphrase: req.Passphrase,
}
c.Header("Access-Control-Allow-Origin", "*")
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

@ -0,0 +1,55 @@
package handler
import (
"encoding/json"
"mchess_server/api"
"mchess_server/utils"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func Test_GetLobbyFromPassphraseHandler(t *testing.T) {
var passphraseURLParameter string
var receivedPhrase string
t.Run("host a lobby", func(t *testing.T) {
r1 := httptest.NewRecorder()
ctx1, e1 := gin.CreateTestContext(r1)
e1.GET("/api/hostPrivate", HostGameHandler)
hostGameRequest, _ := http.NewRequest("GET", "/api/hostPrivate", nil)
ctx1.Request = hostGameRequest
e1.ServeHTTP(r1, hostGameRequest)
playerInfo := api.PlayerInfo{}
err := json.Unmarshal(r1.Body.Bytes(), &playerInfo)
assert.NoError(t, err)
receivedPhrase = *playerInfo.Passphrase
passphrase := utils.NewPassphraseFromString(receivedPhrase)
passphraseURLParameter = passphrase.AsURLParam()
})
t.Run("see if the lobby can be fetched by using the passphrase", func(t *testing.T) {
r2 := httptest.NewRecorder()
ctx2, engine := gin.CreateTestContext(r2)
engine.GET("/api/getLobbyForPassphrase/:phrase", GetLobbyForPassphraseHandler)
url := "/api/getLobbyForPassphrase/" + passphraseURLParameter
getLobbyRequest, _ := http.NewRequest("GET", url, nil)
ctx2.Request = getLobbyRequest
engine.ServeHTTP(r2, getLobbyRequest)
passhrase := api.Passphrase{}
err := json.Unmarshal(r2.Body.Bytes(), &passhrase)
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, r2.Code)
})
}

83
api/handler/websocket.go Normal file
View File

@ -0,0 +1,83 @@
package handler
import (
"context"
"encoding/json"
"fmt"
"log"
"mchess_server/api"
"mchess_server/utils"
"net/http"
"mchess_server/lobbies"
"github.com/gin-gonic/gin"
gorillaws "github.com/gorilla/websocket"
)
var upgrader = gorillaws.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func RegisterWebSocketConnection(c *gin.Context) {
limiter.Take()
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Println(err)
return
}
go waitForAndHandlePlayerID(c, conn)
}
func waitForAndHandlePlayerID(ctx context.Context, conn *gorillaws.Conn) {
limiter.Take()
msgType, msg, err := conn.ReadMessage()
if err != nil {
errorMessage := fmt.Sprintf("Reading from websocket connection did not work: %s", err)
log.Println(errorMessage)
conn.Close()
return
}
log.Println("read from websocket endpoint: ", msgType, string(msg), err)
var info api.PlayerInfo
err = json.Unmarshal(msg, &info)
if err != nil {
errorMessage := fmt.Sprintf("Unmarshaling message did not work: %s", err)
log.Println(errorMessage)
conn.WriteMessage(msgType, []byte(errorMessage))
conn.Close()
return
}
lobby := lobbies.GetLobbyRegistry().GetLobbyByPassphrase(utils.NewPassphraseFromString(*info.Passphrase))
if lobby == nil {
conn.WriteMessage(msgType, []byte("lobby not found"))
conn.Close()
return
}
player, found := lobby.GetPlayerByUUID(*info.PlayerID)
if !found {
conn.WriteMessage(msgType, []byte("player not found"))
conn.Close()
return
}
lobby.Game.SetWebsocketConnectionFor(ctx, player, conn)
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()
}

5
api/passphrase.go Normal file
View File

@ -0,0 +1,5 @@
package api
type Passphrase struct {
Value *string `json:"value,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

@ -80,9 +80,9 @@ func (b *Board) CheckAndPlay(move *types.Move) (bool, Violation) {
} }
} }
wasSpecialMove, err := tempBoard.handleSpecialMove(*move) wasSpecialMove, violation := tempBoard.handleSpecialMove(*move)
if err != nil { if violation != "" {
return false, InvalidMove return false, violation
} }
if !wasSpecialMove { if !wasSpecialMove {
@ -188,21 +188,21 @@ func (p Position) getCopyOfPosition() Position {
return position return position
} }
func (b *Board) handleSpecialMove(move types.Move) (bool, error) { func (b *Board) handleSpecialMove(move types.Move) (bool, Violation) {
var was bool var was bool
var err error var violation Violation
var pieceAtStartSquare = b.getPieceAt(move.StartSquare) var pieceAtStartSquare = b.getPieceAt(move.StartSquare)
switch piece := pieceAtStartSquare.(type) { switch piece := pieceAtStartSquare.(type) {
case Pawn: case Pawn:
was, err = piece.HandlePossiblePromotion(b, move) was, violation = piece.HandlePossiblePromotion(b, move)
if !was { if !was {
was, err = piece.HandleEnPassant(b, move, b.getLastMove()) was, violation = piece.HandleEnPassant(b, move, b.getLastMove())
} }
case King: case King:
was, err = piece.HandleCastling(b, move) was, violation = piece.HandleCastling(b, move)
} }
return was, err return was, violation
} }
type GameEndedReason string type GameEndedReason string

View File

@ -0,0 +1,329 @@
package chess
import (
"mchess_server/types"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Board_WhiteCastlesLeft(t *testing.T) {
board := newBoard()
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 1, Row: 1}] = Rook{Color: types.White}
castling := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 1},
EndSquare: types.Coordinate{Col: 3, Row: 1},
ColorMoved: types.White,
}
valid, _ := board.CheckAndPlay(&castling)
assert.True(t, valid)
}
func Test_Board_WhiteCastlesRight(t *testing.T) {
board := newBoard()
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 8, Row: 1}] = Rook{Color: types.White}
castling := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 1},
EndSquare: types.Coordinate{Col: 7, Row: 1},
ColorMoved: types.White,
}
valid, _ := board.CheckAndPlay(&castling)
assert.True(t, valid)
}
func Test_Board_BlackCastlesLeft(t *testing.T) {
board := newBoard()
board.colorToMove = types.Black
board.position[types.Coordinate{Col: 5, Row: 8}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 1, Row: 8}] = Rook{Color: types.Black}
castling := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 3, Row: 8},
ColorMoved: types.Black,
}
valid, _ := board.CheckAndPlay(&castling)
assert.True(t, valid)
}
func Test_Board_BlackCastlesRight(t *testing.T) {
board := newBoard()
board.colorToMove = types.Black
board.position[types.Coordinate{Col: 5, Row: 8}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 8, Row: 8}] = Rook{Color: types.Black}
castling := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 7, Row: 8},
ColorMoved: types.Black,
}
valid, _ := board.CheckAndPlay(&castling)
assert.True(t, valid)
}
func Test_Board_WhiteCastlesLeft_QueenCoversSquareInBetween(t *testing.T) {
board := newBoard()
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 1, Row: 1}] = Rook{Color: types.White}
board.position[types.Coordinate{Col: 4, Row: 5}] = Queen{Color: types.Black}
castling := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 1},
EndSquare: types.Coordinate{Col: 3, Row: 1},
ColorMoved: types.White,
}
valid, _ := board.CheckAndPlay(&castling)
assert.False(t, valid)
}
func Test_Board_WhiteCastlesRight_QueenCoversSquareInBetween(t *testing.T) {
board := newBoard()
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 8, Row: 1}] = Rook{Color: types.White}
board.position[types.Coordinate{Col: 6, Row: 5}] = Queen{Color: types.Black}
castling := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 1},
EndSquare: types.Coordinate{Col: 7, Row: 1},
ColorMoved: types.White,
}
valid, _ := board.CheckAndPlay(&castling)
assert.False(t, valid)
}
func Test_Board_WhiteCastlesLeft_KingInCheck(t *testing.T) {
board := newBoard()
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 1, Row: 1}] = Rook{Color: types.White}
board.position[types.Coordinate{Col: 5, Row: 5}] = Queen{Color: types.Black}
castling := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 1},
EndSquare: types.Coordinate{Col: 3, Row: 1},
ColorMoved: types.White,
}
valid, violation := board.CheckAndPlay(&castling)
assert.False(t, valid)
assert.Equal(t, CastlingWhileKingInCheck, violation)
}
func Test_Board_WhiteCastlesLeft_QueenCoversKingsSquare(t *testing.T) {
board := newBoard()
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 1, Row: 1}] = Rook{Color: types.White}
board.position[types.Coordinate{Col: 3, Row: 5}] = Queen{Color: types.Black}
castling := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 1},
EndSquare: types.Coordinate{Col: 3, Row: 1},
ColorMoved: types.White,
}
valid, _ := board.CheckAndPlay(&castling)
assert.False(t, valid)
}
func Test_Board_BlackCastlesLeft_QueenCoversSquareInBetween(t *testing.T) {
board := newBoard()
board.colorToMove = types.Black
board.position[types.Coordinate{Col: 5, Row: 8}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 1, Row: 8}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 4, Row: 4}] = Queen{Color: types.White}
castling := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 3, Row: 8},
ColorMoved: types.Black,
}
valid, _ := board.CheckAndPlay(&castling)
assert.False(t, valid)
}
func Test_Board_BlackCastlesRight_QueenCoversSquareInBetween(t *testing.T) {
board := newBoard()
board.colorToMove = types.Black
board.position[types.Coordinate{Col: 5, Row: 8}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 8, Row: 8}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 6, Row: 4}] = Queen{Color: types.White}
castling := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 7, Row: 8},
ColorMoved: types.Black,
}
valid, _ := board.CheckAndPlay(&castling)
assert.False(t, valid)
}
func Test_Board_BlackCastlesLeft_KingInCheck(t *testing.T) {
board := newBoard()
board.colorToMove = types.Black
board.position[types.Coordinate{Col: 5, Row: 8}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 1, Row: 8}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 5, Row: 4}] = Queen{Color: types.White}
castling := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 3, Row: 8},
ColorMoved: types.Black,
}
valid, violation := board.CheckAndPlay(&castling)
assert.False(t, valid)
assert.Equal(t, CastlingWhileKingInCheck, violation)
}
func Test_Board_BlackCastlesLeft_QueenCoversKingsSquare(t *testing.T) {
board := newBoard()
board.colorToMove = types.Black
board.position[types.Coordinate{Col: 5, Row: 8}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 1, Row: 8}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 3, Row: 4}] = Queen{Color: types.White}
castling := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 3, Row: 8},
ColorMoved: types.Black,
}
valid, _ := board.CheckAndPlay(&castling)
assert.False(t, valid)
}
func Test_Board_WhiteCastlesLeft_NoRook(t *testing.T) {
board := newBoard()
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
castling := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 1},
EndSquare: types.Coordinate{Col: 3, Row: 1},
ColorMoved: types.White,
}
valid, _ := board.CheckAndPlay(&castling)
assert.False(t, valid)
}
func Test_Board_WhiteCastlesRight_NoRook(t *testing.T) {
board := newBoard()
board.position[types.Coordinate{Col: 5, Row: 1}] = King{Color: types.White}
castling := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 1},
EndSquare: types.Coordinate{Col: 7, Row: 1},
ColorMoved: types.White,
}
valid, _ := board.CheckAndPlay(&castling)
assert.False(t, valid)
}
func Test_Board_BlackCastlesLeft_NoRook(t *testing.T) {
board := newBoard()
board.colorToMove = types.Black
kingSquare := types.Coordinate{Col: 5, Row: 8}
kingDestinationSquare := types.Coordinate{Col: 3, Row: 8}
board.position[kingSquare] = King{Color: types.Black}
castling := types.Move{
StartSquare: kingSquare,
EndSquare: kingDestinationSquare,
ColorMoved: types.Black,
}
valid, _ := board.CheckAndPlay(&castling)
assert.False(t, valid)
assert.Equal(t, nil, board.getPieceAt(kingDestinationSquare))
}
func Test_Board_BlackCastlesRight_NoRook(t *testing.T) {
board := newBoard()
board.colorToMove = types.Black
board.position[types.Coordinate{Col: 5, Row: 8}] = King{Color: types.Black}
castling := types.Move{
StartSquare: types.Coordinate{Col: 5, Row: 8},
EndSquare: types.Coordinate{Col: 7, Row: 8},
ColorMoved: types.Black,
}
valid, _ := board.CheckAndPlay(&castling)
assert.False(t, valid)
}
func Test_Board_fixBugThatPreventsCastling(t *testing.T) {
board := newBoard()
board.Init()
//white move
valid, violation := board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 2}, EndSquare: types.Coordinate{Col: 5, Row: 3}})
assert.True(t, valid)
assert.Empty(t, violation)
//black move
valid, violation = board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 7}, EndSquare: types.Coordinate{Col: 5, Row: 6}})
assert.True(t, valid)
assert.Empty(t, violation)
//white move
valid, violation = board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 4, Row: 2}, EndSquare: types.Coordinate{Col: 4, Row: 3}})
assert.True(t, valid)
assert.Empty(t, violation)
//black move
valid, violation = board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 6, Row: 7}, EndSquare: types.Coordinate{Col: 6, Row: 6}})
assert.True(t, valid)
assert.Empty(t, violation)
//queen moves to check the king
valid, violation = board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 4, Row: 1}, EndSquare: types.Coordinate{Col: 8, Row: 5}})
assert.True(t, valid)
assert.Empty(t, violation)
//this moves should be valid but it was not because of a bug
valid, violation = board.CheckAndPlay(&types.Move{StartSquare: types.Coordinate{Col: 5, Row: 8}, EndSquare: types.Coordinate{Col: 5, Row: 7}})
assert.True(t, valid)
assert.Empty(t, violation)
}

View File

@ -8,8 +8,7 @@ import (
"mchess_server/types" "mchess_server/types"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/samber/lo" gorillaws "github.com/gorilla/websocket"
"nhooyr.io/websocket"
) )
type Game struct { type Game struct {
@ -59,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()
@ -100,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:
@ -118,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
} }
@ -158,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")
} }
@ -174,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
} }
@ -210,13 +218,12 @@ func (game Game) broadcastGameEnd(reason GameEndedReason) error {
} }
func (game *Game) playerDisconnected(p *Player) { func (game *Game) playerDisconnected(p *Player) {
log.Println(string(p.color), " disconnected")
playerStillInGame := lo.Filter(game.players, func(player *Player, _ int) bool {
return player.color != p.color
})
game.players = playerStillInGame
} }
func (game *Game) SetWebsocketConnectionFor(ctx context.Context, p *Player, ws *websocket.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

@ -29,25 +29,26 @@ func (k King) AfterMoveAction(board *Board, fromSquare types.Coordinate) {
} }
} }
func (k King) HandleCastling(board *Board, move types.Move) (bool, error) { func (k King) HandleCastling(board *Board, move types.Move) (bool, Violation) {
if !k.isMoveCastlingMove(board, move) {
return false, ""
}
if k.hadMovedBefore(board) { if k.hadMovedBefore(board) {
return false, nil return false, ""
} }
valid, dir := k.movedOnValidCastlingSquare(board, move) if board.isSquareAttacked(move.StartSquare, k.Color.Opposite()) {
return false, CastlingWhileKingInCheck
}
valid, dir := k.isCastlingAllowed(board, move)
if !valid { if !valid {
return false, nil return false, ""
}
switch k.Color {
case types.White:
board.state.WhiteKingMoved = true
case types.Black:
board.state.BlackKingMoved = true
} }
k.playCastlingOnBoard(board, move, dir) k.playCastlingOnBoard(board, move, dir)
return true, nil return true, ""
} }
func (k King) hadMovedBefore(b *Board) bool { func (k King) hadMovedBefore(b *Board) bool {
@ -64,52 +65,95 @@ func (k King) hadMovedBefore(b *Board) bool {
type CastlingDirection string type CastlingDirection string
const ( const (
NoDirection CastlingDirection = ""
CastlingRight CastlingDirection = "right" CastlingRight CastlingDirection = "right"
CastlingLeft CastlingDirection = "left" CastlingLeft CastlingDirection = "left"
) )
func (k King) movedOnValidCastlingSquare(b *Board, move types.Move) (bool, CastlingDirection) { func (k King) isMoveCastlingMove(b *Board, move types.Move) bool {
var destinationSquareForKingRight types.Coordinate
var destinationSquareForKingLeft types.Coordinate
switch k.Color {
case types.White:
destinationSquareForKingRight = types.Coordinate{Col: 7, Row: 1}
destinationSquareForKingLeft = types.Coordinate{Col: 3, Row: 1}
case types.Black:
destinationSquareForKingRight = types.Coordinate{Col: 7, Row: 8}
destinationSquareForKingLeft = types.Coordinate{Col: 3, Row: 8}
}
if move.EndSquare == destinationSquareForKingRight ||
move.EndSquare == destinationSquareForKingLeft {
return true
}
return false
}
func (k King) isCastlingAllowed(b *Board, move types.Move) (bool, CastlingDirection) {
var valid = false var valid = false
switch k.Color { switch k.Color {
case types.White: case types.White:
castlingSquareRight := types.Coordinate{Col: 7, Row: 1} destinationSquareForKingRight := types.Coordinate{Col: 7, Row: 1}
castlingSquareLeft := types.Coordinate{Col: 3, Row: 1} destinationSquareForKingLeft := types.Coordinate{Col: 3, Row: 1}
if move.EndSquare == castlingSquareRight && if move.EndSquare == destinationSquareForKingRight &&
b.getPieceAt(castlingSquareRight) == nil && b.getPieceAt(destinationSquareForKingRight) == nil &&
b.getPieceAt(*castlingSquareRight.Left(1)) == nil && b.getPieceAt(*destinationSquareForKingRight.Left(1)) == nil &&
!b.isSquareAttacked(destinationSquareForKingRight, types.Black) &&
!b.isSquareAttacked(*destinationSquareForKingRight.Left(1), types.Black) &&
!b.state.WhiteHRookMoved { !b.state.WhiteHRookMoved {
_, ok := b.getPieceAt(types.Coordinate{Col: 8, Row: 1}).(Rook)
if ok {
return true, CastlingRight return true, CastlingRight
} }
}
if move.EndSquare == castlingSquareLeft && if move.EndSquare == destinationSquareForKingLeft &&
b.getPieceAt(castlingSquareLeft) == nil && b.getPieceAt(destinationSquareForKingLeft) == nil &&
b.getPieceAt(*castlingSquareLeft.Right(1)) == nil && b.getPieceAt(*destinationSquareForKingLeft.Right(1)) == nil &&
b.getPieceAt(*castlingSquareLeft.Left(1)) == nil && b.getPieceAt(*destinationSquareForKingLeft.Left(1)) == nil &&
!b.isSquareAttacked(destinationSquareForKingLeft, types.Black) &&
!b.isSquareAttacked(*destinationSquareForKingLeft.Right(1), types.Black) &&
!b.state.WhiteARookMoved { !b.state.WhiteARookMoved {
_, ok := b.getPieceAt(types.Coordinate{Col: 1, Row: 1}).(Rook)
if ok {
return true, CastlingLeft return true, CastlingLeft
} }
}
case types.Black: case types.Black:
castlingSquareRight := types.Coordinate{Col: 7, Row: 8} destinationSquareForKingRight := types.Coordinate{Col: 7, Row: 8}
castlingSquareLeft := types.Coordinate{Col: 3, Row: 8} destinationSquareForKingLeft := types.Coordinate{Col: 3, Row: 8}
if move.EndSquare == castlingSquareRight && if move.EndSquare == destinationSquareForKingRight &&
b.getPieceAt(castlingSquareRight) == nil && b.getPieceAt(destinationSquareForKingRight) == nil &&
b.getPieceAt(*castlingSquareRight.Left(1)) == nil && b.getPieceAt(*destinationSquareForKingRight.Left(1)) == nil &&
!b.isSquareAttacked(destinationSquareForKingRight, types.White) &&
!b.isSquareAttacked(*destinationSquareForKingRight.Left(1), types.White) &&
!b.state.BlackHRookMoved { !b.state.BlackHRookMoved {
_, ok := b.getPieceAt(types.Coordinate{Col: 8, Row: 8}).(Rook)
if ok {
return true, CastlingRight return true, CastlingRight
} }
}
if move.EndSquare == castlingSquareLeft && if move.EndSquare == destinationSquareForKingLeft &&
b.getPieceAt(castlingSquareLeft) == nil && b.getPieceAt(destinationSquareForKingLeft) == nil &&
b.getPieceAt(*castlingSquareLeft.Right(1)) == nil && b.getPieceAt(*destinationSquareForKingLeft.Right(1)) == nil &&
b.getPieceAt(*castlingSquareLeft.Left(1)) == nil && b.getPieceAt(*destinationSquareForKingLeft.Left(1)) == nil &&
!b.isSquareAttacked(destinationSquareForKingLeft, types.White) &&
!b.isSquareAttacked(*destinationSquareForKingLeft.Right(1), types.White) &&
!b.state.BlackARookMoved { !b.state.BlackARookMoved {
_, ok := b.getPieceAt(types.Coordinate{Col: 1, Row: 8}).(Rook)
if ok {
return true, CastlingLeft return true, CastlingLeft
} }
} }
return valid, CastlingRight }
return valid, NoDirection
} }
func (k King) playCastlingOnBoard(board *Board, move types.Move, dir CastlingDirection) { func (k King) playCastlingOnBoard(board *Board, move types.Move, dir CastlingDirection) {

View File

@ -32,7 +32,7 @@ func (p Pawn) GetColor() types.ChessColor {
return p.Color return p.Color
} }
func (p *Pawn) HandlePossiblePromotion(b *Board, move types.Move) (bool, error) { func (p *Pawn) HandlePossiblePromotion(b *Board, move types.Move) (bool, Violation) {
var isPromotionMove bool var isPromotionMove bool
var promotionToPiece types.PieceShortName var promotionToPiece types.PieceShortName
@ -62,14 +62,14 @@ func (p *Pawn) HandlePossiblePromotion(b *Board, move types.Move) (bool, error)
b.position[move.EndSquare] = GetPieceForShortName(promotionToPiece) b.position[move.EndSquare] = GetPieceForShortName(promotionToPiece)
} }
return isPromotionMove, nil return isPromotionMove, ""
} }
func (p *Pawn) HandleEnPassant(b *Board, move, lastMove types.Move) (bool, error) { func (p *Pawn) HandleEnPassant(b *Board, move, lastMove types.Move) (bool, Violation) {
var wasEnPassant bool var wasEnPassant bool
if lastMove.PieceMoved.ToCommon() != types.PawnShortName { if lastMove.PieceMoved.ToCommon() != types.PawnShortName {
return false, nil return false, ""
} }
switch move.ColorMoved { switch move.ColorMoved {
@ -127,7 +127,7 @@ func (p *Pawn) HandleEnPassant(b *Board, move, lastMove types.Move) (bool, error
b.position[move.EndSquare] = GetPieceForShortName(move.PieceMoved) b.position[move.EndSquare] = GetPieceForShortName(move.PieceMoved)
} }
return wasEnPassant, nil return wasEnPassant, ""
} }
func (p Pawn) getAllMoves(fromSquare types.Coordinate) []types.Coordinate { func (p Pawn) getAllMoves(fromSquare types.Coordinate) []types.Coordinate {
theoreticalMoves := make([]types.Coordinate, 0, 4) theoreticalMoves := make([]types.Coordinate, 0, 4)

View File

@ -10,7 +10,7 @@ import (
"mchess_server/types" "mchess_server/types"
"github.com/google/uuid" "github.com/google/uuid"
"nhooyr.io/websocket" gorillaws "github.com/gorilla/websocket"
) )
type Player struct { type Player struct {
@ -34,18 +34,27 @@ func (p Player) hasWebsocketConnection() bool {
return p.Conn.HasWebsocketConnection() return p.Conn.HasWebsocketConnection()
} }
func (p *Player) SetWebsocketConnection(ctx context.Context, ws *websocket.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 *websocket.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

@ -9,6 +9,9 @@ var (
TargetSquareIsOccupied Violation = "target square is occupied" TargetSquareIsOccupied Violation = "target square is occupied"
KingInCheck Violation = "king would be in check after move" KingInCheck Violation = "king would be in check after move"
SomethingWentWrong Violation = "something went wrong" SomethingWentWrong Violation = "something went wrong"
CastlingThroughCheck Violation = "king would move through check"
CastlingWhileKingInCheck Violation = "king cannot castle while in check"
CastlingKingMovedBefore Violation = "king cannot caslte because he moved before"
) )
func (v Violation) String() string { func (v Violation) String() string {

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,22 +3,38 @@ package connection
import ( import (
"context" "context"
"log" "log"
"mchess_server/types"
"sync"
"nhooyr.io/websocket" "github.com/google/uuid"
gorillaws "github.com/gorilla/websocket"
) )
type Connection struct { type Connection struct {
ws *websocket.Conn ID uuid.UUID
wsConnectionEstablished chan bool ws *gorillaws.Conn
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 {
@ -28,7 +44,7 @@ func NewConnection(options ...func(*Connection)) *Connection {
return &connection return &connection
} }
func WithWebsocket(ws *websocket.Conn) func(*Connection) { func WithWebsocket(ws *gorillaws.Conn) func(*Connection) {
return func(c *Connection) { return func(c *Connection) {
c.ws = ws c.ws = ws
} }
@ -48,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
} }
@ -56,53 +76,81 @@ func (conn *Connection) HasWebsocketConnection() bool {
return conn.ws != nil return conn.ws != nil
} }
func (conn *Connection) SetWebsocketConnection(ws *websocket.Conn) { 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) {
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:
}
go func() { defer conn.logConnection("websocket connection set")
for {
_, msg, err := conn.ws.Read(conn.ctx)
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) Write(msg string) {
if conn.ws == nil { //if ws is not yet set, we wait for it conn.logConnection("Writing message: ", string(msg))
<-conn.wsConnectionEstablished conn.txBuffer.Insert(msg)
}
log.Printf("Writing message: %s", string(msg))
return conn.ws.Write(conn.ctx, websocket.MessageText, msg)
} }
func (conn *Connection) Read() ([]byte, error) { func (conn *Connection) Read() []byte {
msg, err := conn.buffer.Get() msg := conn.rxBuffer.Get()
if err != nil {
conn.ws = nil
return nil, err // TODO: Tell game-handler that connection was lost
}
return []byte(msg), err return []byte(msg)
} }
func (conn *Connection) Close(msg string) { func (conn *Connection) Close(msg string) {
conn.ws.Close(websocket.StatusCode(400), msg) /* 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.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())
} }

48
go.mod
View File

@ -1,46 +1,46 @@
module mchess_server module mchess_server
go 1.21.1 go 1.22
require ( require (
github.com/gin-gonic/gin v1.9.1 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.8.4 github.com/stretchr/testify v1.9.0
github.com/tjarratt/babble v0.0.0-20210505082055-cbca2a4833c1
nhooyr.io/websocket v1.8.10
) )
require github.com/benbjohnson/clock v1.3.5 // indirect
require ( require (
github.com/bytedance/sonic v1.10.2 // indirect github.com/bytedance/sonic v1.11.9 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/cloudwego/base64x v0.1.4 // 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.17.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/google/go-cmp v0.5.9 // indirect 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.6 // 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
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/onsi/gomega v1.27.10 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
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
golang.org/x/arch v0.7.0 // indirect go.uber.org/ratelimit v0.3.1
golang.org/x/crypto v0.19.0 // indirect golang.org/x/arch v0.8.0 // indirect
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 // indirect golang.org/x/crypto v0.24.0 // indirect
golang.org/x/net v0.21.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
golang.org/x/sys v0.17.0 // indirect golang.org/x/net v0.26.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/sys v0.21.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect golang.org/x/text v0.16.0 // 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
) )

221
go.sum
View File

@ -1,69 +1,55 @@
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
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.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
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.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74= github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/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=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 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.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.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.6/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.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
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=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@ -73,136 +59,67 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
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=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tjarratt/babble v0.0.0-20210505082055-cbca2a4833c1 h1:j8whCiEmvLCXI3scVn+YnklCU8mwJ9ZJ4/DGAKqQbRE= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/tjarratt/babble v0.0.0-20210505082055-cbca2a4833c1/go.mod h1:O5hBrCGqzfb+8WyY8ico2AyQau7XQwAfEQeEQ5/5V9E= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
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.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
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=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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.6.0 h1:S0JTfE48HbRj80+4tbvZDYsJ3tGv6BUU3XxyZ7CirAc= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.6.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848 h1:+iq7lrkxmFNBM7xx+Rae2W6uyPfhPeDWD+n+JgppptE=
golang.org/x/exp v0.0.0-20231219180239-dc181d75b848/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4=
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFcdWLGIKnZA4IXNFSrvo=
golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.14.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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q=
nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

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)
}

162
main.go
View File

@ -1,30 +1,19 @@
package main package main
import ( import (
"context"
"encoding/json"
"flag" "flag"
"fmt"
"log" "log"
"mchess_server/api" "mchess_server/api/handler"
"mchess_server/chess"
lobbies "mchess_server/lobby_registry"
"mchess_server/usher"
"mchess_server/utils"
"net/http"
"sync"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid"
"nhooyr.io/websocket"
) )
var cert_path = "/etc/letsencrypt/live/chess.sw-gross.de/" var cert_path = "/etc/letsencrypt/live/chess.sw-gross.de/"
var cert_file = cert_path + "fullchain.pem" var cert_file = cert_path + "fullchain.pem"
var key_file = cert_path + "privkey.pem" var key_file = cert_path + "privkey.pem"
var mut sync.Mutex
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")
@ -35,10 +24,13 @@ func main() {
} }
router := gin.Default() router := gin.Default()
router.GET("/api/random", registerForRandomGame) router.GET("/api/hostPrivate", handler.HostGameHandler)
router.GET("/api/hostPrivate", hostPrivateGame) router.POST("/api/joinPrivate", handler.JoinPrivateGame)
router.POST("/api/joinPrivate", joinPrivateGame) router.GET("/api/ws", handler.RegisterWebSocketConnection)
router.GET("/api/ws", registerWebSocketConnection)
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")
@ -50,139 +42,3 @@ func main() {
log.Fatal(router.RunTLS("chess.sw-gross.de:9999", cert_file, key_file)) log.Fatal(router.RunTLS("chess.sw-gross.de:9999", cert_file, key_file))
} }
} }
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.IndentedJSON(http.StatusOK, info)
}
func hostPrivateGame(c *gin.Context) {
player := chess.NewPlayer(uuid.New())
u := usher.GetUsher()
mut.Lock()
defer mut.Unlock()
lobby := u.CreateNewPrivateLobby(player)
u.AddPlayerToLobbyAndStartGameIfFull(player, lobby)
passphrase := lobby.Passphrase.String()
info := api.PlayerInfo{
PlayerID: &player.Uuid,
LobbyID: &lobby.Uuid,
Passphrase: &passphrase,
}
c.Header("Access-Control-Allow-Origin", "*")
c.IndentedJSON(http.StatusOK, info)
}
func joinPrivateGame(c *gin.Context) {
req := api.PlayerInfo{}
log.Println(c.Request.Body)
err := c.ShouldBindJSON(&req)
if err != nil || req.Passphrase == nil || *req.Passphrase == "" {
c.IndentedJSON(http.StatusNotFound, req)
}
u := usher.GetUsher()
if req.Passphrase != nil &&
*req.Passphrase != "" &&
req.PlayerID != nil &&
req.LobbyID != nil { //is reconnect
lobby := u.FindExistingPrivateLobby(utils.Passphrase(*req.Passphrase))
_, found := lobby.GetPlayerByUUID(*req.PlayerID)
if found {
c.IndentedJSON(
http.StatusOK,
api.PlayerInfo{
PlayerID: req.PlayerID,
LobbyID: req.LobbyID,
Passphrase: req.Passphrase,
})
return
} else {
c.IndentedJSON(http.StatusNotFound, req)
}
}
player := chess.NewPlayer(uuid.New())
mut.Lock()
defer mut.Unlock()
lobby := u.FindExistingPrivateLobby(utils.Passphrase(*req.Passphrase))
if lobby != nil {
u.AddPlayerToLobbyAndStartGameIfFull(player, lobby)
} else {
c.IndentedJSON(http.StatusNotFound, req)
return
}
info := api.PlayerInfo{
PlayerID: &player.Uuid,
LobbyID: &lobby.Uuid,
Passphrase: req.Passphrase,
}
c.Header("Access-Control-Allow-Origin", "*")
c.IndentedJSON(http.StatusOK, info)
}
func registerWebSocketConnection(c *gin.Context) {
webSocketConn, err := websocket.Accept(c.Writer, c.Request, &websocket.AcceptOptions{OriginPatterns: []string{"chess.sw-gross.de", "localhost:*"}})
if err != nil {
log.Println(err)
return
}
go waitForAndHandlePlayerID(c, webSocketConn)
}
func waitForAndHandlePlayerID(ctx context.Context, conn *websocket.Conn) {
msgType, msg, err := conn.Read(ctx)
if err != nil {
errorMessage := fmt.Sprintf("Reading from websocket connection did not work: %s", err)
log.Println(errorMessage)
conn.Close(websocket.StatusCode(400), errorMessage)
return
}
log.Println("read from websocket endpoint: ", msgType, string(msg), err)
var info api.PlayerInfo
err = json.Unmarshal(msg, &info)
if err != nil {
errorMessage := fmt.Sprintf("Unmarshaling message did not work: %s", err)
log.Println(errorMessage)
conn.Close(websocket.StatusCode(400), errorMessage)
return
}
lobby := lobbies.GetLobbyRegistry().GetLobbyByUUID(*info.LobbyID)
if lobby == nil {
conn.Close(websocket.StatusCode(400), "lobby not found")
return
}
player, found := lobby.GetPlayerByUUID(*info.PlayerID)
if !found {
conn.Close(websocket.StatusCode(400), "player not found")
return
}
if player.Conn.HasWebsocketConnection() {
player.Conn.Close("closing existing connection")
}
lobby.Game.SetWebsocketConnectionFor(ctx, player, conn)
log.Println("player after setting connection: ", player)
}

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)
}

60458
utils/clean-words.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,101 +2,56 @@ package utils
import ( import (
"strings" "strings"
"unicode"
"github.com/tjarratt/babble"
) )
type Passphrase string type Passphrase string
func NewPassphrase() Passphrase { func NewPassphrase() Passphrase {
var phrase string var phrase string
var retries int
var word string var word string
var words = 2 var words = 3
//TODO make sure passphrases are unique //TODO make sure passphrases are unique
for words > 0 { for words > 0 {
retries = 20
for {
word = getCleanWord() word = getCleanWord()
if isAccecpable(word) {
phrase = phrase + word + " " phrase = phrase + word + " "
break
}
if retries == 1 { //this is our last try, we take any word
phrase = phrase + word + " "
}
retries -= 1
}
words -= 1 words -= 1
} }
return Passphrase(strings.TrimSpace(phrase)) return Passphrase(strings.TrimSpace(phrase))
} }
func NewPassphraseFromString(s string) Passphrase {
return Passphrase(s)
}
func (p Passphrase) AsURLParam() string {
var result string
phraseAsString := p.String()
segments := strings.Split(phraseAsString, " ")
for _, segment := range segments {
runes := []rune(segment)
runes[0] = unicode.ToUpper(runes[0])
result += string(runes)
}
return result
}
func ConvertToPassphraseWithSpaces(s string) Passphrase {
result := ""
for _, rune := range s {
if unicode.IsUpper(rune) {
result += " "
}
result += strings.ToLower(string(rune))
}
return NewPassphraseFromString(strings.Trim(result, " "))
}
func (p Passphrase) String() string { func (p Passphrase) String() string {
return string(p) return string(p)
} }
func isAccecpable(s string) bool {
l := len(s)
if l > 8 || l < 3 {
return false
}
for _, rune := range s {
if !isEnglishLetter(rune) {
return false
}
}
return true
}
func isProfanity(s string) bool {
contains := []string{
"nigg",
"fag",
"ass",
"bitch",
"rape",
"ass",
"scrot",
"rect",
}
startsWith := []string{
"spic",
"chin",
"cunt",
}
for _, word := range contains {
if strings.Contains(s, word) {
return true
}
}
for _, word := range startsWith {
if strings.HasPrefix(s, word) {
return true
}
}
return false
}
func isEnglishLetter(r rune) bool {
if (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') {
return false
}
return true
}
func getCleanWord() string {
var word string
babbler := babble.NewBabbler()
babbler.Count = 1
for {
word = babbler.Babble()
if !isProfanity(word) {
return word
}
}
}

36
utils/passphrase_test.go Normal file
View File

@ -0,0 +1,36 @@
package utils
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_onlyUniqueWords(t *testing.T) {
wordsDistribution := make(map[string]int)
for _, word := range CleanWords {
wordsDistribution[word] += 1
}
for _, v := range wordsDistribution {
assert.Equal(t, 1, v)
}
assert.Equal(t, numberOfCleanWords, len(wordsDistribution))
}
func Test_Passphrase_AsURLParam(t *testing.T) {
phrase := NewPassphraseFromString("this is a Test phrase")
asParams := phrase.AsURLParam()
assert.Equal(t, "ThisIsATestPhrase", asParams)
}
func Test_Passphrase_ConvertToPassphraseWithSpaces(t *testing.T) {
fromURL := "ThisIsATestPhraseWithManyWords"
phrase := ConvertToPassphraseWithSpaces(fromURL)
assert.Equal(t, NewPassphraseFromString("this is a test phrase with many words"), phrase)
}