Refactor a itsy bitsy tiny bit and add rook moves.

This commit is contained in:
Marco 2023-06-26 23:58:40 +02:00
parent f79e5be008
commit 3d4fee2631
9 changed files with 381 additions and 137 deletions

View File

@ -183,97 +183,13 @@ func (b *Board) handleSpecialMove(move types.Move) bool {
switch pieceAtStartSquare.(type) {
case Pawn:
was = b.handlePossiblePromotion(move)
pawn := pieceAtStartSquare.(Pawn)
was = pawn.HandlePossiblePromotion(b, move)
if !was {
was = b.handleEnPassant(move, b.getLastMove())
was = pawn.HandleEnPassant(b, move, b.getLastMove())
}
}
return was
}
func (b *Board) handlePossiblePromotion(move types.Move) bool {
var isPromotionMove bool
var promotionToPiece types.PieceShortName
//TODO(m): What if message does not contain a promotion, but should be because a pawn is moved to the end square
messageContainsPromotion := move.IsPromotionMove()
if messageContainsPromotion {
promotionToPiece = *move.PromotionToPiece
}
switch move.ColorMoved {
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.position, move.StartSquare)
b.position[move.EndSquare] = GetPieceForShortName(promotionToPiece, move.ColorMoved)
}
return isPromotionMove
}
func (b *Board) handleEnPassant(move, lastMove types.Move) bool {
var wasEnPassant bool
if lastMove.PieceMoved != types.PawnShortName {
return false
}
switch move.ColorMoved {
case types.White:
if lastMove.StartSquare.Row != 7 || lastMove.EndSquare.Row != 5 {
wasEnPassant = false
}
if move.StartSquare.Row != 5 {
wasEnPassant = false
}
if move.EndSquare.Row != 6 {
wasEnPassant = false
}
if move.StartSquare.Col == lastMove.EndSquare.Col+1 &&
move.EndSquare.Col == lastMove.EndSquare.Col {
wasEnPassant = true
}
if move.StartSquare.Col == lastMove.EndSquare.Col-1 &&
move.EndSquare.Col == lastMove.EndSquare.Col {
wasEnPassant = true
}
case types.Black:
if lastMove.StartSquare.Row != 2 || lastMove.EndSquare.Row != 4 {
wasEnPassant = false
}
if move.StartSquare.Row != 4 {
wasEnPassant = false
}
if move.EndSquare.Row != 3 {
wasEnPassant = false
}
if move.StartSquare.Col == lastMove.EndSquare.Col+1 &&
move.EndSquare.Col == lastMove.EndSquare.Col {
wasEnPassant = true
}
if move.StartSquare.Col == lastMove.EndSquare.Col-1 &&
move.EndSquare.Col == lastMove.EndSquare.Col {
wasEnPassant = true
}
}
if wasEnPassant { //play the move
delete(b.position, lastMove.EndSquare)
b.position[move.EndSquare] = GetPieceForShortName(move.PieceMoved, move.ColorMoved)
}
return wasEnPassant
}

View File

@ -117,10 +117,20 @@ func Test_CheckMove_invalidPawnMoves(t *testing.T) {
assert.Equal(t, boardBeforeMove, board)
})
// t.Run("king of moving color is in check after move", func(t *testing.T) {
// good, _ := board.CheckMove(move)
// assert.False(t, good)
// })
t.Run("king of moving color is in check after move", func(t *testing.T) {
var board = newBoard()
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}
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)
})
}
func Test_CheckMove_validPromotion(t *testing.T) {

41
chess/free_squares.go Normal file
View File

@ -0,0 +1,41 @@
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{}
nonBlocked = append(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)...)
return nonBlocked
}
func (b *Board) getNonBlocked(
squaresToCheck []types.Coordinate,
sourceSquare types.Coordinate,
) []types.Coordinate {
pieceOnSourceSquare := b.getPieceAt(sourceSquare)
nonBlocked := []types.Coordinate{}
for _, square := range squaresToCheck {
piece := b.getPieceAt(square)
if piece != nil {
if piece.GetColor() == pieceOnSourceSquare.GetColor() {
break
}
// if there is an opposite colored piece we append it but
// stop appending the squares behind it
nonBlocked = append(nonBlocked, square)
break
}
nonBlocked = append(nonBlocked, square)
}
return nonBlocked
}

View File

@ -21,6 +21,102 @@ func (p Pawn) GetColor() types.ChessColor {
return p.Color
}
func (p *Pawn) HandlePossiblePromotion(b *Board ,move types.Move) bool {
var isPromotionMove bool
var promotionToPiece types.PieceShortName
//TODO(m): What if message does not contain a promotion, but should be because a pawn is moved to the end square
messageContainsPromotion := move.IsPromotionMove()
if messageContainsPromotion {
promotionToPiece = *move.PromotionToPiece
}
switch move.ColorMoved {
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.position, move.StartSquare)
b.position[move.EndSquare] = GetPieceForShortName(promotionToPiece, move.ColorMoved)
}
return isPromotionMove
}
func (p *Pawn) HandleEnPassant(b *Board, move, lastMove types.Move) bool {
var wasEnPassant bool
if lastMove.PieceMoved != types.PawnShortName {
return false
}
switch move.ColorMoved {
case types.White:
if lastMove.StartSquare.Row != 7 || lastMove.EndSquare.Row != 5 {
wasEnPassant = false
break
}
if move.StartSquare.Row != 5 {
wasEnPassant = false
break
}
if move.EndSquare.Row != 6 {
wasEnPassant = false
break
}
if move.StartSquare.Col == lastMove.EndSquare.Col+1 &&
move.EndSquare.Col == lastMove.EndSquare.Col {
wasEnPassant = true
break
}
if move.StartSquare.Col == lastMove.EndSquare.Col-1 &&
move.EndSquare.Col == lastMove.EndSquare.Col {
wasEnPassant = true
break
}
case types.Black:
if lastMove.StartSquare.Row != 2 || lastMove.EndSquare.Row != 4 {
wasEnPassant = false
break
}
if move.StartSquare.Row != 4 {
wasEnPassant = false
break
}
if move.EndSquare.Row != 3 {
wasEnPassant = false
break
}
if move.StartSquare.Col == lastMove.EndSquare.Col+1 &&
move.EndSquare.Col == lastMove.EndSquare.Col {
wasEnPassant = true
break
}
if move.StartSquare.Col == lastMove.EndSquare.Col-1 &&
move.EndSquare.Col == lastMove.EndSquare.Col {
wasEnPassant = true
break
}
}
if wasEnPassant { //play the move
delete(b.position, lastMove.EndSquare)
b.position[move.EndSquare] = GetPieceForShortName(move.PieceMoved, move.ColorMoved)
}
return wasEnPassant
}
func (p Pawn) getAllMoves(fromSquare types.Coordinate) []types.Coordinate {
theoreticalMoves := make([]types.Coordinate, 0, 4)
@ -80,3 +176,4 @@ func (p Pawn) filterBlockedSquares(board Board, fromSquare types.Coordinate, squ
}
return lo.Intersect(nonBlockedSquares, squaresToBeFiltered)
}

View File

@ -3,17 +3,16 @@ package chess
import "mchess_server/types"
type Rook struct {
Color types.ChessColor
Color types.ChessColor
}
func (r Rook) AfterMoveAction() {
}
// GetColor implements Piece.
func (r Rook) GetColor() types.ChessColor {
return r.Color
}
func (r Rook) GetAllMovesButBlocked(board Board, fromSquare types.Coordinate) []types.Coordinate {
return []types.Coordinate{}
return board.GetNonBlockedRowAndColumn(fromSquare)
}

72
chess/rook_test.go Normal file
View File

@ -0,0 +1,72 @@
package chess
import (
"mchess_server/types"
"testing"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
)
func Test_Rook_GetNonBlockedSquares(t *testing.T) {
t.Run("free row and column", func(t *testing.T) {
board := newBoard()
rook := Rook{Color: types.Black}
board.position[types.Coordinate{Col: 5, Row: 5}] = rook
squares := rook.GetAllMovesButBlocked(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) {
board := newBoard()
rook := Rook{Color: types.Black}
rookCoordinate := types.Coordinate{Col: 5, Row: 5}
board.position[rookCoordinate] = rook
board.position[types.Coordinate{Col: 3, Row: 5}] = Pawn{Color: types.White}
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})
squaresOnLeft := lo.Filter(squares, func(square types.Coordinate, _ int) bool {
return square.Row == rookCoordinate.Row && square.Col < rookCoordinate.Col
})
assert.Equal(t,
[]types.Coordinate{
{Col: 4, Row: 5},
{Col: 3, Row: 5},
},
squaresOnLeft)
squaresOnRight := lo.Filter(squares, func(square types.Coordinate, _ int) bool {
return square.Row == rookCoordinate.Row && square.Col > rookCoordinate.Col
})
assert.Equal(t,
[]types.Coordinate{},
squaresOnRight)
squaresAbove := lo.Filter(squares, func(square types.Coordinate, _ int) bool {
return square.Col == rookCoordinate.Col && square.Row > rookCoordinate.Row
})
assert.Equal(t,
[]types.Coordinate{
{Col: 5, Row: 6},
},
squaresAbove)
squaresBelow := lo.Filter(squares, func(square types.Coordinate, _ int) bool {
return square.Col == rookCoordinate.Col && square.Row < rookCoordinate.Row
})
assert.Equal(t,
[]types.Coordinate{
{Col: 5, Row: 4},
{Col: 5, Row: 3},
{Col: 5, Row: 2},
{Col: 5, Row: 1},
},
squaresBelow)
})
}

View File

@ -1,49 +1,6 @@
package types
// coordinates starting at 1:1 and end at 8:8
type Coordinate struct {
Col int `json:"col"`
Row int `json:"row"`
}
const (
RangeLastValid = 8
RangeFirstValid = 1
RangeUpperInvalid = 9
RangeLowerInvalid = 0
)
func (c Coordinate) Up(number int) *Coordinate {
check := c.Row + number
if check <= RangeLastValid {
return &Coordinate{Row: check, Col: c.Col}
}
return nil
}
func (c Coordinate) Down(number int) *Coordinate {
check := c.Row - number
if check >= RangeFirstValid {
return &Coordinate{Row: check, Col: c.Col}
}
return nil
}
// Right and left is seen from a board where row 1 is on the bottom
func (c Coordinate) Right(number int) *Coordinate {
check := c.Col + number
if check >= RangeFirstValid {
return &Coordinate{Row: c.Row, Col: check}
}
return nil
}
func (c Coordinate) Left(number int) *Coordinate {
check := c.Col - number
if check >= RangeFirstValid {
return &Coordinate{Row: c.Row, Col: check}
}
return nil
}
type ChessColor string

127
types/coordinate.go Normal file
View File

@ -0,0 +1,127 @@
package types
// coordinates starting at 1:1 and end at 8:8
type Coordinate struct {
Col int `json:"col"`
Row int `json:"row"`
}
const (
RangeLastValid = 8
RangeFirstValid = 1
RangeUpperInvalid = 9
RangeLowerInvalid = 0
)
func (c Coordinate) Up(number int) *Coordinate {
check := c.Row + number
if check <= RangeLastValid {
return &Coordinate{Row: check, Col: c.Col}
}
return nil
}
func (c Coordinate) Down(number int) *Coordinate {
check := c.Row - number
if check >= RangeFirstValid {
return &Coordinate{Row: check, Col: c.Col}
}
return nil
}
// Right and left is seen from a board where row 1 is on the bottom
func (c Coordinate) Right(number int) *Coordinate {
check := c.Col + number
if check <= RangeLastValid {
return &Coordinate{Row: c.Row, Col: check}
}
return nil
}
func (c Coordinate) Left(number int) *Coordinate {
check := c.Col - number
if check >= RangeFirstValid {
return &Coordinate{Row: c.Row, Col: check}
}
return nil
}
func (c Coordinate) GetAllSquaresLeft() []Coordinate {
squares := []Coordinate{}
i := 1
for {
if square := c.Left(i); square != nil {
squares = append(squares, *square)
i += 1
} else {
return squares
}
}
}
func (c Coordinate) GetAllSquaresRight() []Coordinate {
squares := []Coordinate{}
i := 1
for {
if square := c.Right(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 {
break
}
columnToReturn = append(columnToReturn, *squareToAppend)
index = index + 1
}
return columnToReturn
}

25
types/coordinate_test.go Normal file
View File

@ -0,0 +1,25 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_Coordinate_GetSquaresOfRow(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)
})
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)
})
}