many many changes again.

This commit is contained in:
Marco 2023-06-27 22:32:24 +02:00
parent 3d4fee2631
commit f733d9bb08
13 changed files with 269 additions and 160 deletions

View File

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

View File

@ -1,6 +1,7 @@
package chess
import (
"errors"
"mchess_server/types"
"github.com/samber/lo"
@ -78,7 +79,7 @@ func (b *Board) CheckAndPlay(move types.Move) (bool, string) {
if !wasSpecialMove {
// 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)
allMovesExceptBlocked := pieceAtStartSquare.GetAllNonBlockedMoves(tempBoard, move.StartSquare)
legal := lo.Contains(allMovesExceptBlocked, move.EndSquare)
if !legal {
return false, "not a legal square"
@ -89,16 +90,12 @@ func (b *Board) CheckAndPlay(move types.Move) (bool, string) {
tempBoard.position[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 := tempBoard.getSquareOfPiece(King{Color: move.ColorMoved})
if ownKingCoordinate == nil {
return false, string(move.ColorMoved) + " king not found"
kingAttacked, err := tempBoard.isKingOfMovingColorInCheck(move.ColorMoved)
if err != nil {
return false, "something went wrong"
}
kingIsAttacked := tempBoard.isSquareAttacked(*ownKingCoordinate, move.ColorMoved.Opposite())
if kingIsAttacked {
return false, "king is attacked after move"
if kingAttacked {
return false, "king would be in check after move"
}
//Check for checkmat&e
@ -118,10 +115,24 @@ func (b *Board) CheckAndPlay(move types.Move) (bool, string) {
b.position = tempBoard.position
b.history = tempBoard.history
b.appendMoveToHistory(move)
return true, ""
}
func (b Board) isKingOfMovingColorInCheck(color types.ChessColor) (bool, error) {
//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: color})
if ownKingCoordinate == nil {
return false, errors.New("no king found")
}
kingIsAttacked := b.isSquareAttacked(*ownKingCoordinate, color.Opposite())
if kingIsAttacked {
return true, nil
}
return false, nil
}
func (b Board) getSquareOfPiece(piece Piece) *types.Coordinate {
for k, v := range b.position {
if v == piece {
@ -135,9 +146,10 @@ func (b Board) isSquareAttacked(square types.Coordinate, byColor types.ChessColo
var attackedSquares []types.Coordinate
for square, piece := range b.position {
attackedSquares = append(attackedSquares, piece.GetAllMovesButBlocked(b, square)...)
if piece.GetColor() == byColor {
attackedSquares = append(attackedSquares, piece.GetAllAttackedSquares(b, square)...)
}
}
return lo.Contains(attackedSquares, square)
}
@ -146,7 +158,6 @@ func (b Board) getPieceAt(coord types.Coordinate) Piece {
if !found {
return nil
}
return piece
}
@ -191,5 +202,3 @@ func (b *Board) handleSpecialMove(move types.Move) bool {
}
return was
}

View File

@ -122,14 +122,18 @@ func Test_CheckMove_invalidPawnMoves(t *testing.T) {
board.position[types.Coordinate{Col: 1, Row: 6}] = King{Color: types.White}
board.position[types.Coordinate{Col: 2, Row: 6}] = Pawn{Color: types.White}
board.position[types.Coordinate{Col: 8, Row: 6}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 7, Row: 6}] = Rook{Color: types.Black}
board.position[types.Coordinate{Col: 8, Row: 8}] = King{Color: types.Black}
boardBeforeMove := board
move := types.Move{
StartSquare: types.Coordinate{Col: 2, Row: 6},
EndSquare: types.Coordinate{Col: 2, Row: 7},
}
good, _ := board.CheckAndPlay(move)
assert.False(t, good)
assert.Equal(t, boardBeforeMove, board)
})
}
@ -139,7 +143,7 @@ func Test_CheckMove_validPromotion(t *testing.T) {
board.position[types.Coordinate{Col: 1, Row: 7}] = Pawn{Color: types.White}
board.position[types.Coordinate{Col: 1, Row: 1}] = King{Color: types.White}
board.position[types.Coordinate{Col: 8, Row: 7}] = King{Color: types.Black}
board.position[types.Coordinate{Col: 7, Row: 7}] = King{Color: types.Black}
shortName := types.QueenShortName
move := types.Move{

View File

@ -3,13 +3,12 @@ package chess
import "mchess_server/types"
func (b *Board) GetNonBlockedRowAndColumn(fromSquare types.Coordinate) []types.Coordinate {
squaresLeft := fromSquare.GetAllSquaresLeft()
squaresRight := fromSquare.GetAllSquaresRight()
squaresAbove := fromSquare.GetAllSquaresAbove()
squaresBelow := fromSquare.GetAllSquaresBelow()
nonBlocked := []types.Coordinate{}
squaresLeft := fromSquare.GetStraightInDirection(types.Coordinate.Left)
squaresRight := fromSquare.GetStraightInDirection(types.Coordinate.Right)
squaresAbove := fromSquare.GetStraightInDirection(types.Coordinate.Up)
squaresBelow := fromSquare.GetStraightInDirection(types.Coordinate.Down)
nonBlocked = append(nonBlocked, b.getNonBlocked(squaresLeft, fromSquare)...)
nonBlocked := b.getNonBlocked(squaresLeft, fromSquare)
nonBlocked = append(nonBlocked, b.getNonBlocked(squaresRight, fromSquare)...)
nonBlocked = append(nonBlocked, b.getNonBlocked(squaresAbove, fromSquare)...)
nonBlocked = append(nonBlocked, b.getNonBlocked(squaresBelow, fromSquare)...)
@ -17,6 +16,25 @@ func (b *Board) GetNonBlockedRowAndColumn(fromSquare types.Coordinate) []types.C
return nonBlocked
}
func (b *Board) GetNonBlockedDiagonals(fromSquare types.Coordinate) []types.Coordinate {
rightUp := fromSquare.GetDiagonalInDirection(types.Coordinate.Right, types.Coordinate.Up)
leftUp := fromSquare.GetDiagonalInDirection(types.Coordinate.Left, types.Coordinate.Up)
leftDown := fromSquare.GetDiagonalInDirection(types.Coordinate.Left, types.Coordinate.Down)
rightDown := fromSquare.GetDiagonalInDirection(types.Coordinate.Right, types.Coordinate.Down)
nonBlocked := b.getNonBlocked(rightUp, fromSquare)
nonBlocked = append(nonBlocked, b.getNonBlocked(leftUp, fromSquare)...)
nonBlocked = append(nonBlocked, b.getNonBlocked(leftDown, fromSquare)...)
nonBlocked = append(nonBlocked, b.getNonBlocked(rightDown, fromSquare)...)
return nonBlocked
}
func (b *Board) GetNonBlockedKnightMoves(fromSquare types.Coordinate) []types.Coordinate {
allKnightMoves := fromSquare.GetAllKnightMoves()
return b.getNonBlocked(allKnightMoves, fromSquare)
}
func (b *Board) getNonBlocked(
squaresToCheck []types.Coordinate,
sourceSquare types.Coordinate,

View File

@ -1,15 +1,21 @@
package chess
import "mchess_server/types"
import (
"mchess_server/types"
)
type King struct {
Color types.ChessColor
}
func (k King) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
return k.GetAllNonBlockedMoves(board, fromSquare)
}
func (k King) GetColor() types.ChessColor {
return k.Color
}
func (k King) GetAllMovesButBlocked(board Board, fromSquare types.Coordinate) []types.Coordinate {
func (k King) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate {
return []types.Coordinate{}
}

View File

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

View File

@ -10,7 +10,18 @@ type Pawn struct {
Color types.ChessColor
}
func (p Pawn) GetAllMovesButBlocked(board Board, fromSquare types.Coordinate) []types.Coordinate {
func (p Pawn) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
attackingMoves := make([]types.Coordinate,0,2)
allMoves := p.GetAllNonBlockedMoves(board, fromSquare)
for _,move := range allMoves {
if move.Col != fromSquare.Col {
attackingMoves = append(attackingMoves, move)
}
}
return attackingMoves
}
func (p Pawn) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate {
theoreticalSquares := p.getAllMoves(fromSquare)
legalSquares := p.filterBlockedSquares(board, fromSquare, theoreticalSquares)
@ -176,4 +187,3 @@ func (p Pawn) filterBlockedSquares(board Board, fromSquare types.Coordinate, squ
}
return lo.Intersect(nonBlockedSquares, squaresToBeFiltered)
}

View File

@ -5,7 +5,8 @@ import (
)
type Piece interface {
GetAllMovesButBlocked(board Board, fromSquare types.Coordinate) []types.Coordinate
GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate
GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate
GetColor() types.ChessColor
}

View File

@ -1,15 +1,23 @@
package chess
import "mchess_server/types"
import (
"mchess_server/types"
)
type Queen struct {
Color types.ChessColor
}
func (q Queen) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
return q.GetAllNonBlockedMoves(board, fromSquare)
}
func (q Queen) GetColor() types.ChessColor {
return q.Color
}
func (q Queen) GetAllMovesButBlocked(board Board, fromSquare types.Coordinate) []types.Coordinate {
return []types.Coordinate{}
func (q Queen) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate {
squares := board.GetNonBlockedRowAndColumn(fromSquare)
squares = append(squares, board.GetNonBlockedDiagonals(fromSquare)...)
return squares
}

View File

@ -1,11 +1,17 @@
package chess
import "mchess_server/types"
import (
"mchess_server/types"
)
type Rook struct {
Color types.ChessColor
}
func (r Rook) GetAllAttackedSquares(board Board, fromSquare types.Coordinate) []types.Coordinate {
return r.GetAllNonBlockedMoves(board, fromSquare)
}
func (r Rook) AfterMoveAction() {
}
@ -13,6 +19,6 @@ func (r Rook) GetColor() types.ChessColor {
return r.Color
}
func (r Rook) GetAllMovesButBlocked(board Board, fromSquare types.Coordinate) []types.Coordinate {
func (r Rook) GetAllNonBlockedMoves(board Board, fromSquare types.Coordinate) []types.Coordinate {
return board.GetNonBlockedRowAndColumn(fromSquare)
}

View File

@ -14,9 +14,8 @@ func Test_Rook_GetNonBlockedSquares(t *testing.T) {
rook := Rook{Color: types.Black}
board.position[types.Coordinate{Col: 5, Row: 5}] = rook
squares := rook.GetAllMovesButBlocked(board, types.Coordinate{Col: 5, Row: 5})
squares := rook.GetAllNonBlockedMoves(board, types.Coordinate{Col: 5, Row: 5})
assert.Len(t, squares, 14)
assert.Equal(t, types.Coordinate{Col: 4, Row: 5}, squares[0])
})
t.Run("free row and column", func(t *testing.T) {
@ -29,7 +28,7 @@ func Test_Rook_GetNonBlockedSquares(t *testing.T) {
board.position[types.Coordinate{Col: 5, Row: 6}] = Pawn{Color: types.White}
board.position[types.Coordinate{Col: 6, Row: 5}] = Pawn{Color: types.Black}
squares := rook.GetAllMovesButBlocked(board, types.Coordinate{Col: 5, Row: 5})
squares := rook.GetAllNonBlockedMoves(board, rookCoordinate)
squaresOnLeft := lo.Filter(squares, func(square types.Coordinate, _ int) bool {
return square.Row == rookCoordinate.Row && square.Col < rookCoordinate.Col

View File

@ -1,5 +1,7 @@
package types
import "github.com/samber/lo"
// coordinates starting at 1:1 and end at 8:8
type Coordinate struct {
Col int `json:"col"`
@ -14,6 +16,65 @@ const (
RangeLowerInvalid = 0
)
type CoordinateBuilder struct {
coordinate Coordinate
}
func (b *CoordinateBuilder) Up(number int) *CoordinateBuilder {
b.coordinate.Row += number
return b
}
func (b *CoordinateBuilder) Down(number int) *CoordinateBuilder {
b.coordinate.Row -= number
return b
}
func (b *CoordinateBuilder) Left(number int) *CoordinateBuilder {
b.coordinate.Col -= number
return b
}
func (b *CoordinateBuilder) Right(number int) *CoordinateBuilder {
b.coordinate.Col += number
return b
}
func (b *CoordinateBuilder) Resolve() *Coordinate {
c := b.coordinate
if c.Row <= RangeLastValid &&
c.Row >= RangeFirstValid &&
c.Col <= RangeLastValid &&
c.Col >= RangeLastValid {
return nil
}
return &c
}
func (c *Coordinate) GetAllKnightMoves() []Coordinate {
unfilteredMoves := make([]*Coordinate, 0, 8)
builder := CoordinateBuilder{coordinate: *c}
unfilteredMoves = append(unfilteredMoves, builder.Up(2).Right(1).Resolve())
unfilteredMoves = append(unfilteredMoves, builder.Up(1).Right(2).Resolve())
unfilteredMoves = append(unfilteredMoves, builder.Down(1).Right(2).Resolve())
unfilteredMoves = append(unfilteredMoves, builder.Down(2).Right(1).Resolve())
unfilteredMoves = append(unfilteredMoves, builder.Down(2).Left(1).Resolve())
unfilteredMoves = append(unfilteredMoves, builder.Down(1).Left(2).Resolve())
unfilteredMoves = append(unfilteredMoves, builder.Up(1).Left(2).Resolve())
unfilteredMoves = append(unfilteredMoves, builder.Up(2).Left(1).Resolve())
return lo.FilterMap(unfilteredMoves, func(unfilteredMove *Coordinate, _ int) (Coordinate, bool) {
if unfilteredMove != nil {
return *unfilteredMove, true
}
return Coordinate{}, false
})
}
func (c Coordinate) Up(number int) *Coordinate {
check := c.Row + number
if check <= RangeLastValid {
@ -45,11 +106,27 @@ func (c Coordinate) Left(number int) *Coordinate {
return nil
}
func (c Coordinate) GetAllSquaresLeft() []Coordinate {
func (c Coordinate) GetAllSquaresOnStraights() []Coordinate {
squares := c.GetStraightInDirection(Coordinate.Up)
squares = append(squares, c.GetStraightInDirection(Coordinate.Down)...)
squares = append(squares, c.GetStraightInDirection(Coordinate.Left)...)
squares = append(squares, c.GetStraightInDirection(Coordinate.Right)...)
return squares
}
func (c Coordinate) GetAllSquaresOnDiagonals() []Coordinate {
squares := c.GetDiagonalInDirection(Coordinate.Up, Coordinate.Right)
squares = append(squares, c.GetDiagonalInDirection(Coordinate.Down, Coordinate.Right)...)
squares = append(squares, c.GetDiagonalInDirection(Coordinate.Down, Coordinate.Left)...)
squares = append(squares, c.GetDiagonalInDirection(Coordinate.Up, Coordinate.Left)...)
return squares
}
func (c Coordinate) GetStraightInDirection(dirFn func(Coordinate, int) *Coordinate) []Coordinate {
squares := []Coordinate{}
i := 1
for {
if square := c.Left(i); square != nil {
if square := dirFn(c, i); square != nil {
squares = append(squares, *square)
i += 1
} else {
@ -58,70 +135,20 @@ func (c Coordinate) GetAllSquaresLeft() []Coordinate {
}
}
func (c Coordinate) GetAllSquaresRight() []Coordinate {
func (c Coordinate) GetDiagonalInDirection(dirFn1, dirFn2 func(Coordinate, int) *Coordinate) []Coordinate {
squares := []Coordinate{}
i := 1
for {
if square := c.Right(i); square != nil {
if squareRight := dirFn1(c, i); squareRight != nil {
if square := dirFn2(*squareRight, i); square != nil {
squares = append(squares, *square)
i += 1
} else {
return squares
}
}
}
func (c Coordinate) GetAllSquaresAbove() []Coordinate {
squares := []Coordinate{}
i := 1
for {
if square := c.Up(i); square != nil {
squares = append(squares, *square)
i += 1
} else {
return squares
}
}
}
func (c Coordinate) GetAllSquaresBelow() []Coordinate {
squares := []Coordinate{}
i := 1
for {
if square := c.Down(i); square != nil {
squares = append(squares, *square)
i += 1
} else {
return squares
}
}
}
func (c Coordinate) GetSquaresOnRow() []Coordinate {
rowToReturn := []Coordinate{}
leftMostSquareOnRow := Coordinate{Col: RangeFirstValid, Row: c.Row}
index := 0
for {
squareToAppend := leftMostSquareOnRow.Right(index)
if squareToAppend == nil {
break
}
rowToReturn = append(rowToReturn, *squareToAppend)
index = index + 1
}
return rowToReturn
}
func (c Coordinate) GetSquaresOnColumn() []Coordinate {
columnToReturn := []Coordinate{}
bottomSquareOnColumn := Coordinate{Col: c.Col, Row: RangeFirstValid}
index := 0
for {
squareToAppend := bottomSquareOnColumn.Up(index)
if squareToAppend == nil {
} else {
break
}
columnToReturn = append(columnToReturn, *squareToAppend)
index = index + 1
}
return columnToReturn
return squares
}

View File

@ -6,20 +6,33 @@ import (
"github.com/stretchr/testify/assert"
)
func Test_Coordinate_GetSquaresOfRow(t *testing.T) {
func Test_Coordinate_GetSquaresOfRowExcludingStartSquare(t *testing.T) {
t.Run("get row for a coordinate", func(t *testing.T) {
startSquare := Coordinate{Col: 4, Row: 2}
row := startSquare.GetSquaresOnRow()
assert.Len(t, row, 8)
assert.Equal(t,row[0].Col, 1)
assert.Equal(t,row[7].Col, 8)
row := startSquare.GetStraightInDirection(Coordinate.Left)
row = append(row, startSquare.GetStraightInDirection(Coordinate.Right)...)
assert.Len(t, row, 7)
assert.Equal(t, row[0], Coordinate{Col: 3, Row: 2})
assert.Equal(t, row[1], Coordinate{Col: 2, Row: 2})
})
t.Run("get column for a coordinate", func(t *testing.T) {
startSquare := Coordinate{Col: 4, Row: 2}
column := startSquare.GetSquaresOnColumn()
assert.Len(t, column, 8)
assert.Equal(t,column[0].Row, 1)
assert.Equal(t,column[7].Row, 8)
column := startSquare.GetStraightInDirection(Coordinate.Up)
column = append(column, startSquare.GetStraightInDirection(Coordinate.Down)...)
assert.Len(t, column, 7)
assert.Equal(t, column[0], Coordinate{Col: 4, Row: 3})
})
}
func Test_GetDiagonals(t *testing.T) {
square := Coordinate{Col: 5, Row: 5}
diagonal := square.GetDiagonalInDirection(Coordinate.Right, Coordinate.Up)
assert.Len(t, diagonal, 3)
assert.Equal(t, []Coordinate{
{Col: 6, Row: 6},
{Col: 7, Row: 7},
{Col: 8, Row: 8},
}, diagonal)
}