Many many changes

This commit is contained in:
Marco 2023-06-25 16:11:29 +02:00
parent 7f206b15fe
commit 9793c37582
20 changed files with 252 additions and 102 deletions

View File

@ -2,7 +2,7 @@ package api
import (
"encoding/json"
"local/m/mchess_server/types"
"mchess_server/types"
)
type WebsocketMessage struct {

View File

@ -1,18 +1,15 @@
package chess
import "local/m/mchess_server/types"
import "mchess_server/types"
type Bishop struct {
Color types.ChessColor
}
func (Bishop) AfterMoveAction() {
}
func (b Bishop) GetColor() types.ChessColor {
return b.Color
}
func (b Bishop) GetAllLegalAndIllegalMoves(board Board, fromSquare types.Coordinate) []types.Coordinate {
func (b Bishop) GetAllMovesButBlocked(board Board, fromSquare types.Coordinate) []types.Coordinate {
return []types.Coordinate{}
}

View File

@ -1,7 +1,7 @@
package chess
import (
"local/m/mchess_server/types"
"mchess_server/types"
"github.com/samber/lo"
)
@ -14,11 +14,11 @@ func (b Board) Init() {
for i := 1; i <= 8; i++ {
coord.Row = 2
coord.Col = i
b[coord] = Pawn{Color: types.White, HasMoved: false}
b[coord] = Pawn{Color: types.White}
coord.Row = 7
coord.Col = i
b[coord] = Pawn{Color: types.Black, HasMoved: false}
b[coord] = Pawn{Color: types.Black}
}
b[types.Coordinate{Row: 1, Col: 1}] = Rook{Color: types.White}
@ -41,39 +41,58 @@ func (b Board) Init() {
}
func (b Board) CheckMove(move types.Move) (bool, string) {
// We make a copy of the original board to play moves on it,
// We can play the move on it and then check if it is invalid
tempBoard := b.getCopyOfBoard()
//Check start square of move
pieceAtStartSquare := b.getPieceAt(move.StartSquare)
if pieceAtStartSquare == nil {
return false, "no piece at start square"
}
movingColor := pieceAtStartSquare.GetColor()
//Check end square of move
pieceAtEndSquare := b.getPieceAt(move.EndSquare)
if pieceAtEndSquare != nil {
if pieceAtEndSquare.GetColor() == pieceAtStartSquare.GetColor() {
return false, "same-coloured piece at end square"
}
}
// At the moment, we do not need to check if the correct color is moving,
//since we are only reading moves from the player whose turn it is.
var wasPromotionMove bool
// var piece types.PieceShortName
switch pieceAtStartSquare.(type) {
case Pawn:
wasPromotionMove, _ = tempBoard.handlePossiblePromotion(move, movingColor)
}
legal := lo.Contains(pieceAtStartSquare.GetAllLegalAndIllegalMoves(b, move.StartSquare), move.EndSquare)
if !legal {
return false, "not a legal square"
if !wasPromotionMove {
// At the moment, we do not need to check if the correct color is moving,
//since we are only reading moves from the player whose turn it is.
allMovesExceptBlocked := pieceAtStartSquare.GetAllMovesButBlocked(tempBoard, move.StartSquare)
legal := lo.Contains(allMovesExceptBlocked, move.EndSquare)
if !legal {
return false, "not a legal square"
}
//We play the move on the temporary board
delete(tempBoard, move.StartSquare)
tempBoard[move.EndSquare] = pieceAtStartSquare
}
//Check if king of moving color is in check -> move not allowed
//Do that by checking if the king is in a square attacked by the other color.
ownKingCoordinate := b.getSquareOfPiece(King{Color: movingColor})
ownKingCoordinate := tempBoard.getSquareOfPiece(King{Color: movingColor})
if ownKingCoordinate == nil {
return false, string(movingColor) + " king not found"
}
kingIsAttacked := b.isSquareAttacked(*ownKingCoordinate, movingColor.Opposite())
kingIsAttacked := tempBoard.isSquareAttacked(*ownKingCoordinate, movingColor.Opposite())
if kingIsAttacked {
return false, "king is attacked after move"
}
//Check for checkmate
//Check for checkmat&e
//Is every square that the king can move to attacked? And can no other
//piece block? -> checkmate
@ -86,11 +105,9 @@ func (b Board) CheckMove(move types.Move) (bool, string) {
// in this scenaria the path are all the squares between queen and king.
// If a piece can be moved into the path, it is no checkmate
//We play the move
delete(b, move.StartSquare)
b[move.EndSquare] = pieceAtStartSquare
//We play the move on the real board
b = tempBoard
pieceAtStartSquare.AfterMoveAction()
return true, ""
}
@ -107,7 +124,7 @@ func (b Board) isSquareAttacked(square types.Coordinate, byColor types.ChessColo
var attackedSquares []types.Coordinate
for square, piece := range b {
attackedSquares = append(attackedSquares, piece.GetAllLegalAndIllegalMoves(b, square)...)
attackedSquares = append(attackedSquares, piece.GetAllMovesButBlocked(b, square)...)
}
return lo.Contains(attackedSquares, square)
@ -115,10 +132,51 @@ func (b Board) isSquareAttacked(square types.Coordinate, byColor types.ChessColo
func (b Board) getPieceAt(coord types.Coordinate) Piece {
piece, found := b[coord]
if !found {
return nil
}
return piece
}
func (b Board) handlePossiblePromotion(move types.Move, color types.ChessColor) (bool, types.PieceShortName) {
var isPromotionMove bool
var promotionToPiece types.PieceShortName
messageContainsPromotion := move.IsPromotionMove()
if messageContainsPromotion {
promotionToPiece = *move.PromotionToPiece
}
switch color {
case types.White:
if move.StartSquare.Row == types.RangeLastValid-1 &&
move.EndSquare.Row == types.RangeLastValid {
isPromotionMove = true
}
case types.Black:
if move.StartSquare.Row == types.RangeFirstValid+1 &&
move.EndSquare.Row == types.RangeFirstValid {
isPromotionMove = true
}
}
if isPromotionMove {
delete(b, move.StartSquare)
b[move.EndSquare] = GetPieceForShortName(promotionToPiece)
}
return isPromotionMove, promotionToPiece
}
func (b Board) getCopyOfBoard() Board {
board := make(map[types.Coordinate]Piece)
for coord, piece := range b {
board[coord] = piece
}
return board
}

71
chess/board_test.go Normal file
View File

@ -0,0 +1,71 @@
package chess
import (
"mchess_server/types"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_CheckMove_validPawnMove(t *testing.T) {
var board = make(Board)
board[types.Coordinate{Col: 1, Row: 1}] = Pawn{Color: types.White}
board[types.Coordinate{Col: 1, Row: 5}] = King{Color: types.White}
board[types.Coordinate{Col: 8, Row: 5}] = King{Color: types.Black}
move := types.Move{
StartSquare: types.Coordinate{Col: 1, Row: 1},
EndSquare: types.Coordinate{Col: 1, Row: 2},
}
good, _ := board.CheckMove(move)
assert.True(t, good)
}
func Test_CheckMove_invalidPawnMoves(t *testing.T) {
var board = make(Board)
board[types.Coordinate{Col: 2, Row: 5}] = Pawn{Color: types.White}
board[types.Coordinate{Col: 1, Row: 5}] = King{Color: types.White}
board[types.Coordinate{Col: 7, Row: 5}] = Queen{Color: types.Black}
board[types.Coordinate{Col: 8, Row: 5}] = King{Color: types.Black}
move := types.Move{
StartSquare: types.Coordinate{Col: 2, Row: 5},
EndSquare: types.Coordinate{Col: 2, Row: 6},
}
t.Run("pawn is blocked", func(t *testing.T) {
testBoard := board.getCopyOfBoard()
testBoard[types.Coordinate{Col: 2, Row: 6}] = Pawn{Color: types.Black}
legalMove, _ := testBoard.CheckMove(move)
assert.False(t, legalMove)
})
t.Run("king of moving color is in check after move", func(t *testing.T) {
good, _ := board.CheckMove(move)
assert.False(t, good)
})
}
func Test_CheckMove_validPromotion(t *testing.T) {
var board Board = make(Board)
board[types.Coordinate{Col: 1, Row: 7}] = Pawn{Color: types.White}
board[types.Coordinate{Col: 1, Row: 1}] = King{Color: types.White}
board[types.Coordinate{Col: 8, Row: 7}] = King{Color: types.Black}
shortName := types.Queen
move := types.Move{
StartSquare: types.Coordinate{Col: 1, Row: 7},
EndSquare: types.Coordinate{Col: 1, Row: 8},
PromotionToPiece: &shortName,
}
good, reason := board.CheckMove(move)
assert.Empty(t, reason)
assert.True(t, good)
}

View File

@ -1,8 +1,8 @@
package chess
import (
"local/m/mchess_server/api"
"local/m/mchess_server/types"
"mchess_server/api"
"mchess_server/types"
"log"
"time"

View File

@ -1,22 +1,15 @@
package chess
import "local/m/mchess_server/types"
import "mchess_server/types"
type King struct {
Color types.ChessColor
HasMoved bool
}
// AfterMoveAction implements Piece.
func (k King) AfterMoveAction() {
k.HasMoved = true
}
// GetColor implements Piece.
func (k King) GetColor() types.ChessColor {
return k.Color
}
func (k King) GetAllLegalAndIllegalMoves(board Board, fromSquare types.Coordinate) []types.Coordinate {
func (k King) GetAllMovesButBlocked(board Board, fromSquare types.Coordinate) []types.Coordinate {
return []types.Coordinate{}
}

View File

@ -1,19 +1,19 @@
package chess
import "local/m/mchess_server/types"
import "mchess_server/types"
type Knight struct {
Color types.ChessColor
}
// AfterMoveAction implements Piece.
func (Knight) AfterMoveAction() {
func (k Knight) AfterMoveAction() {
}
func (k Knight) GetColor() types.ChessColor {
return k.Color
}
func (k Knight) GetAllLegalAndIllegalMoves(board Board, fromSquare types.Coordinate) []types.Coordinate {
func (k Knight) GetAllMovesButBlocked(board Board, fromSquare types.Coordinate) []types.Coordinate {
return []types.Coordinate{}
}

View File

@ -1,21 +1,16 @@
package chess
import (
"local/m/mchess_server/types"
"mchess_server/types"
"github.com/samber/lo"
)
type Pawn struct {
Color types.ChessColor
HasMoved bool
Color types.ChessColor
}
func (p Pawn) AfterMoveAction() {
p.HasMoved = true
}
func (p Pawn) GetAllLegalAndIllegalMoves(board Board, fromSquare types.Coordinate) []types.Coordinate {
func (p Pawn) GetAllMovesButBlocked(board Board, fromSquare types.Coordinate) []types.Coordinate {
theoreticalSquares := p.getAllMoves(fromSquare)
legalSquares := p.filterBlockedSquares(board, fromSquare, theoreticalSquares)
@ -31,34 +26,38 @@ func (p Pawn) getAllMoves(fromSquare types.Coordinate) []types.Coordinate {
switch p.Color {
case types.Black:
if fromSquare.Down(1) != nil {
firstMove := fromSquare.Row == types.RangeLastValid-1
if fromSquare.Down(1) != nil {
theoreticalMoves = append(theoreticalMoves, *fromSquare.Down(1))
}
if !p.HasMoved && fromSquare.Down(2) != nil {
if firstMove && fromSquare.Down(2) != nil {
theoreticalMoves = append(theoreticalMoves, *fromSquare.Down(2))
}
if lowerRight := fromSquare.Down(1).Right(1); lowerRight != nil {
theoreticalMoves = append(theoreticalMoves, *lowerRight)
}
if lowerLeft := fromSquare.Down(1).Left(1); lowerLeft != nil {
theoreticalMoves = append(theoreticalMoves, *lowerLeft)
}
if lowerRight := fromSquare.Down(1).Right(1); lowerRight != nil {
theoreticalMoves = append(theoreticalMoves, *lowerRight)
}
if lowerLeft := fromSquare.Down(1).Left(1); lowerLeft != nil {
theoreticalMoves = append(theoreticalMoves, *lowerLeft)
}
case types.White:
firstMove := fromSquare.Row == types.RangeFirstValid+1
if fromSquare.Up(1) != nil {
theoreticalMoves = append(theoreticalMoves, *fromSquare.Up(1))
}
if !p.HasMoved && fromSquare.Up(2) != nil {
if firstMove && fromSquare.Up(2) != nil {
theoreticalMoves = append(theoreticalMoves, *fromSquare.Up(2))
}
if upperRight := fromSquare.Up(1).Right(1); upperRight != nil {
theoreticalMoves = append(theoreticalMoves, *upperRight)
}
if upperLeft := fromSquare.Up(1).Left(1); upperLeft != nil {
theoreticalMoves = append(theoreticalMoves, *upperLeft)
}
if upperRight := fromSquare.Up(1).Right(1); upperRight != nil {
theoreticalMoves = append(theoreticalMoves, *upperRight)
}
if upperLeft := fromSquare.Up(1).Left(1); upperLeft != nil {
theoreticalMoves = append(theoreticalMoves, *upperLeft)
}
}
return theoreticalMoves

View File

@ -1,11 +1,30 @@
package chess
import (
"local/m/mchess_server/types"
"mchess_server/types"
)
type Piece interface {
GetAllLegalAndIllegalMoves(board Board, fromSquare types.Coordinate) []types.Coordinate
GetAllMovesButBlocked(board Board, fromSquare types.Coordinate) []types.Coordinate
GetColor() types.ChessColor
AfterMoveAction()
}
func GetPieceForShortName(name types.PieceShortName) Piece {
var piece Piece
switch name {
case 'p':
piece = Pawn{}
case 'q':
piece = Queen{}
case 'k':
piece = King{}
case 'b':
piece = Bishop{}
case 'r':
piece = Rook{}
case 'n':
piece = Knight{}
}
return piece
}

View File

@ -4,8 +4,8 @@ import (
"context"
"encoding/json"
"errors"
"local/m/mchess_server/api"
"local/m/mchess_server/types"
"mchess_server/api"
"mchess_server/types"
"log"
"time"

View File

@ -1,18 +1,15 @@
package chess
import "local/m/mchess_server/types"
import "mchess_server/types"
type Queen struct {
Color types.ChessColor
}
func (Queen) AfterMoveAction() {
}
func (q Queen) GetColor() types.ChessColor {
return q.Color
}
func (q Queen) GetAllLegalAndIllegalMoves(board Board, fromSquare types.Coordinate) []types.Coordinate {
func (q Queen) GetAllMovesButBlocked(board Board, fromSquare types.Coordinate) []types.Coordinate {
return []types.Coordinate{}
}

View File

@ -1,13 +1,12 @@
package chess
import "local/m/mchess_server/types"
import "mchess_server/types"
type Rook struct {
Color types.ChessColor
HasMoved bool
}
func (Rook) AfterMoveAction() {
func (r Rook) AfterMoveAction() {
}
// GetColor implements Piece.
@ -15,6 +14,6 @@ func (r Rook) GetColor() types.ChessColor {
return r.Color
}
func (r Rook) GetAllLegalAndIllegalMoves(board Board, fromSquare types.Coordinate) []types.Coordinate {
func (r Rook) GetAllMovesButBlocked(board Board, fromSquare types.Coordinate) []types.Coordinate {
return []types.Coordinate{}
}

6
go.mod
View File

@ -1,4 +1,4 @@
module local/m/mchess_server
module mchess_server
go 1.20
@ -12,6 +12,7 @@ require (
require (
github.com/bytedance/sonic v1.8.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
@ -25,7 +26,10 @@ require (
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/samber/lo v1.38.1 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.9 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect

3
go.sum
View File

@ -74,6 +74,7 @@ github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
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.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -83,6 +84,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/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/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/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=

View File

@ -1,7 +1,7 @@
package lobby_registry
import (
"local/m/mchess_server/chess"
"mchess_server/chess"
"github.com/google/uuid"
)
@ -22,10 +22,10 @@ func NewEmptyLobbyWithUUID(uuid uuid.UUID) *Lobby {
}
}
func (w *Lobby) AddPlayerAndStartGameIfFull(player *chess.Player) {
w.Game.AddPlayersToGame(player)
if w.IsFull() {
go w.Game.Handle()
func (l *Lobby) AddPlayerAndStartGameIfFull(player *chess.Player) {
l.Game.AddPlayersToGame(player)
if l.IsFull() {
go l.Game.Handle()
}
}

View File

@ -4,10 +4,10 @@ import (
"context"
"encoding/json"
"fmt"
"local/m/mchess_server/api"
"local/m/mchess_server/chess"
lobbies "local/m/mchess_server/lobby_registry"
"local/m/mchess_server/usher"
"mchess_server/api"
"mchess_server/chess"
lobbies "mchess_server/lobby_registry"
"mchess_server/usher"
"log"
"net/http"
"os"

View File

@ -45,22 +45,6 @@ func (c Coordinate) Left(number int) *Coordinate {
return nil
}
type Move struct {
StartSquare Coordinate `json:"startSquare"`
EndSquare Coordinate `json:"endSquare"`
}
type PieceClass string
const (
Pawn PieceClass = "pawn"
Rook PieceClass = "rook"
Knight PieceClass = "knight"
Bishop PieceClass = "bishop"
Queen PieceClass = "queen"
King PieceClass = "king"
)
type ChessColor string
const (

14
types/move.go Normal file
View File

@ -0,0 +1,14 @@
package types
type Move struct {
StartSquare Coordinate `json:"startSquare"`
EndSquare Coordinate `json:"endSquare"`
PromotionToPiece *PieceShortName `json:"promotionToPiece,omitempty"`
}
func (m Move) IsPromotionMove() bool {
if m.PromotionToPiece != nil {
return true
}
return false
}

12
types/shortname.go Normal file
View File

@ -0,0 +1,12 @@
package types
type PieceShortName rune
const (
Pawn PieceShortName = 'p'
Rook PieceShortName = 'r'
Knight PieceShortName = 'n'
Bishop PieceShortName = 'b'
Queen PieceShortName = 'q'
King PieceShortName = 'k'
)

View File

@ -1,8 +1,8 @@
package usher
import (
"local/m/mchess_server/chess"
lobbies "local/m/mchess_server/lobby_registry"
"mchess_server/chess"
lobbies "mchess_server/lobby_registry"
)
type Usher struct {