Compare commits

...

3 Commits

Author SHA1 Message Date
917c97766d Change websocket handling
With this commit, we stop waiting for the websocket connection to be
established before the game starts.
Now, the Connection type is responsible for waiting for the websocket
connection before writing.
2023-11-26 21:31:44 +01:00
f20c4ae586 Add .gitignore. Ignore binary mchess_server 2023-11-26 21:31:44 +01:00
9803ecad6f Introduce Init and Prepare states 2023-11-25 15:45:21 +01:00
6 changed files with 98 additions and 80 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
mchess_server

View File

@ -2,9 +2,9 @@ package chess
import (
"log"
"math/rand"
"mchess_server/api"
"mchess_server/types"
"time"
"github.com/google/uuid"
)
@ -18,7 +18,9 @@ type Game struct {
}
const (
PlayerToMove = iota
Init = iota
Prepare
PlayerToMove
CheckMove
CheckPlayerChange
)
@ -27,7 +29,7 @@ func NewGame() *Game {
var game = Game{
id: uuid.New(),
board: newBoard(),
gameState: PlayerToMove,
gameState: Init,
}
game.board.Init()
@ -42,17 +44,18 @@ func (game Game) GetPlayer1() *Player {
return game.players[0]
}
func (game Game) GetRandomPlayer() *Player {
index := rand.Int() % 2
return game.players[index]
}
func (game Game) GetPlayer2() *Player {
return game.players[1]
}
func (game *Game) prepare() {
game.currentTurnPlayer = game.GetPlayer1()
ok := game.waitForWebsocketConnections()
if !ok {
return
}
game.players[0].color = types.White
game.players[1].color = types.Black
err := game.notifyPlayersAboutGameStart()
if err != nil {
@ -60,18 +63,32 @@ func (game *Game) prepare() {
}
}
//CHANGES:
/*
Do not wait for the players websocket connection before the game.
Let Connection do it.
Then here in game handler, just read from Connection (aka messagebuffer) and when data arrives, we read it
Question: how do we handle connection losses, should game handler observe state of connection and send a notification to the other player when one player is disconnected
Question: How would reconnect work in this scenario?
*/
func (game *Game) Handle() {
defer game.killGame()
game.prepare()
var receivedMove types.Move
var err error
for {
switch game.gameState {
case Init:
game.currentTurnPlayer = game.GetPlayer1()
game.gameState = Prepare
case Prepare:
game.prepare()
game.gameState = PlayerToMove
case PlayerToMove:
log.Println("with player ", game.currentTurnPlayer, " to move")
log.Println("with ", game.currentTurnPlayer.GetPlayerColor(), " to move")
receivedMove, err = game.currentTurnPlayer.ReadMove()
if err != nil {
log.Println("Error while reading message:", err)
@ -121,25 +138,6 @@ func (game *Game) killGame() {
log.Println("Game should be killed")
}
func (game *Game) waitForWebsocketConnections() bool {
timer := time.NewTimer(5 * time.Second)
numberOfConnections := 0
waitingForPlayers := make(chan bool)
go game.GetPlayer1().WaitForWebsocketConnection(waitingForPlayers)
go game.GetPlayer2().WaitForWebsocketConnection(waitingForPlayers)
for numberOfConnections < 2 {
select {
case <-waitingForPlayers:
numberOfConnections++
case <-timer.C:
return false
}
}
return true
}
func (game Game) notifyPlayersAboutGameStart() error {
colorDeterminedPlayer1, err := api.GetColorDeterminedMessage(types.White)
if err != nil {

View File

@ -8,7 +8,6 @@ import (
"mchess_server/api"
conn "mchess_server/connection"
"mchess_server/types"
"time"
"github.com/google/uuid"
"nhooyr.io/websocket"
@ -18,24 +17,23 @@ type Player struct {
Uuid uuid.UUID
Conn *conn.Connection
InGame bool
wsConnEstablished chan bool
context context.Context
color types.ChessColor
}
func NewPlayer(uuid uuid.UUID) *Player {
return &Player{
Uuid: uuid,
Conn: nil,
Conn: conn.NewConnection(conn.WithContext(context.Background())),
InGame: false,
wsConnEstablished: make(chan bool),
context: context.Background(),
}
}
func (p *Player) SetConnection(ctx context.Context, ws *websocket.Conn) {
p.Conn = conn.NewConnection(conn.WithWebsocket(ws), conn.WithContext(p.context))
p.context = ctx
p.wsConnEstablished <- true
func (p Player) HasWebsocketConnection() bool {
return p.Conn.HasWebsocketConnection()
}
func (p *Player) SetWebsocketConnection(ctx context.Context, ws *websocket.Conn) {
p.Conn.SetWebsocketConnection(ws)
}
func (p *Player) SendMoveAndPosition(move types.Move, boardPosition string) error {
@ -58,9 +56,7 @@ func (p *Player) SendMoveAndPosition(move types.Move, boardPosition string) erro
}
func (p *Player) writeMessage(msg []byte) error {
log.Printf("Writing message: %s to player %s", string(msg), p.Uuid.String())
return p.Conn.Write(p.context, msg)
return p.Conn.Write(msg)
}
func (p *Player) ReadMove() (types.Move, error) {
@ -83,19 +79,12 @@ func (p *Player) ReadMove() (types.Move, error) {
}
func (p *Player) readMessage() ([]byte, error) {
msg, err := p.Conn.Read(p.context)
msg, err := p.Conn.Read()
log.Printf("Reading message: %s from player %s", string(msg), p.Uuid.String())
return msg, err
}
func (p *Player) WaitForWebsocketConnection(c chan bool) {
timer := time.NewTimer(5 * time.Second)
select {
case <-p.wsConnEstablished:
c <- true
case <-timer.C:
return
}
func (p Player) GetPlayerColor() string {
return string(p.color)
}

View File

@ -18,8 +18,7 @@ type message struct {
}
func newMessageBuffer(size int) *MessageBuffer {
mutex := &sync.Mutex{}
cond := sync.NewCond(mutex)
cond := sync.NewCond(&sync.Mutex{})
return &MessageBuffer{
messages: make([]message, size),

View File

@ -2,34 +2,30 @@ package connection
import (
"context"
"log"
"nhooyr.io/websocket"
)
type Connection struct {
ws *websocket.Conn
wsConnectionEstablished chan bool
ctx context.Context
buffer MessageBuffer
}
var dbg_index int
func NewConnection(options ...func(*Connection)) *Connection {
connection := Connection{
buffer: *newMessageBuffer(100),
wsConnectionEstablished: make(chan bool),
}
for _, option := range options {
option(&connection)
}
if connection.ws != nil {
go func() {
for {
_, msg, _ := connection.ws.Read(connection.ctx)
connection.buffer.Insert(string(msg))
}
}()
}
return &connection
}
@ -45,13 +41,43 @@ func WithContext(ctx context.Context) func(*Connection) {
}
}
func (conn *Connection) Write(ctx context.Context, msg []byte) error {
return conn.ws.Write(ctx, websocket.MessageText, msg)
func (conn *Connection) HasWebsocketConnection() bool {
return conn.ws != nil
}
func (conn *Connection) Read(ctx context.Context) ([]byte, error) {
func (conn *Connection) SetWebsocketConnection(ws *websocket.Conn) {
if ws == nil {
return
}
conn.ws = ws
select {
case conn.wsConnectionEstablished <- true:
default:
}
go func() {
for {
_, msg, _ := conn.ws.Read(conn.ctx)
conn.buffer.Insert(string(msg))
}
}()
}
func (conn *Connection) Write(msg []byte) error {
if conn.ws == nil { //if ws is not yet set, we wait for it
<-conn.wsConnectionEstablished
}
log.Printf("Writing message: %s", string(msg))
return conn.ws.Write(conn.ctx, websocket.MessageText, msg)
}
func (conn *Connection) Read() ([]byte, error) {
msg, err := conn.buffer.Get()
if err != nil {
conn.ws = nil
return nil, err // Tell game-handler that connection was lost
}
@ -60,4 +86,5 @@ func (conn *Connection) Read(ctx context.Context) ([]byte, error) {
func (conn *Connection) Close(msg string) {
conn.ws.Close(websocket.StatusCode(400), msg)
conn.ws = nil
}

10
main.go
View File

@ -137,7 +137,7 @@ func waitForAndHandlePlayerID(ctx context.Context, conn *websocket.Conn) {
return
}
log.Println("read from websocket: ", msgType, string(msg), err)
log.Println("read from websocket endpoint: ", msgType, string(msg), err)
var info api.PlayerInfo
err = json.Unmarshal(msg, &info)
@ -149,14 +149,18 @@ func waitForAndHandlePlayerID(ctx context.Context, conn *websocket.Conn) {
}
lobby := lobbies.GetLobbyRegistry().GetLobbyByUUID(*info.LobbyID)
if lobby == nil {
conn.Close(websocket.StatusCode(400), "lobby not found")
}
player, found := lobby.GetPlayerByUUID(*info.PlayerID)
if !found {
conn.Close(websocket.StatusCode(400), "player not found")
return
}
if player.Conn != nil {
if player.Conn.HasWebsocketConnection() {
player.Conn.Close("closing existing connection")
}
player.SetConnection(ctx, conn)
player.SetWebsocketConnection(ctx, conn)
log.Println("player after setting connection: ", player)
}