├── .gitignore ├── .idea ├── .gitignore ├── chess_go.iml ├── modules.xml └── vcs.xml ├── Dockerfile ├── LICENSE ├── README.md ├── chess_engine ├── basic_move_picker.go ├── basic_move_picker_test.go ├── bishop.go ├── bishop_test.go ├── board.go ├── board_test.go ├── chess_piece.go ├── console_interface.go ├── game.go ├── game_test.go ├── interfaces.go ├── king.go ├── king_test.go ├── knight.go ├── knight_test.go ├── move.go ├── move_picker.go ├── move_test.go ├── pawn.go ├── pawn_test.go ├── queen.go ├── queen_test.go ├── rook.go ├── rook_test.go ├── user_interface.go ├── utils.go └── utils_test.go ├── chess_web_page ├── chessboardjs │ ├── CHANGELOG.md │ ├── LICENSE.md │ ├── README.md │ ├── css │ │ ├── chessboard-1.0.0.css │ │ └── chessboard-1.0.0.min.css │ ├── img │ │ └── chesspieces │ │ │ └── wikipedia │ │ │ ├── bB.png │ │ │ ├── bK.png │ │ │ ├── bN.png │ │ │ ├── bP.png │ │ │ ├── bQ.png │ │ │ ├── bR.png │ │ │ ├── wB.png │ │ │ ├── wK.png │ │ │ ├── wN.png │ │ │ ├── wP.png │ │ │ ├── wQ.png │ │ │ └── wR.png │ ├── js │ │ ├── chessboard-1.0.0.js │ │ └── chessboard-1.0.0.min.js │ └── package.json ├── img │ └── chesspieces │ │ └── wikipedia │ │ ├── bB.png │ │ ├── bK.png │ │ ├── bN.png │ │ ├── bP.png │ │ ├── bQ.png │ │ ├── bR.png │ │ ├── wB.png │ │ ├── wK.png │ │ ├── wN.png │ │ ├── wP.png │ │ ├── wQ.png │ │ └── wR.png └── index.html ├── chess_web_service ├── chess_web_service.go └── chess_web_service_test.go ├── go.mod ├── go.sum └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | chess_go 24 | 25 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/chess_go.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM golang:1.21 4 | WORKDIR /app 5 | COPY go.mod ./ 6 | RUN go mod download 7 | COPY *.go ./ 8 | WORKDIR /app/chess_engine 9 | COPY chess_engine ./ 10 | WORKDIR /app/chess_web_page 11 | COPY chess_web_page ./ 12 | WORKDIR /app/chess_web_service 13 | COPY chess_web_service ./ 14 | WORKDIR /app 15 | RUN go get chess_go/chess_engine 16 | RUN go get chess_go/chess_web_service 17 | RUN CGO_ENABLED=0 GOOS=linux go build -o /chess_go 18 | EXPOSE 80 19 | CMD ["/chess_go"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chess_go 2 | Chess engine written in Go 3 | 4 | The default for the program is to instantiate a web server on port 80 which servers the pages under the chess_web_page folder and 5 | implements the web service in the chess_web_service folder. If a "-c" command line argument is passed in then it will 6 | run the console interface for playing chess games from the command line. The HTML GUI chess board is from https://chessboardjs.com/. 7 | 8 | The engine is up and running and can be tried out at http://www.chessengine.org. 9 | 10 | # Rest Web Service 11 | Provides an API for creating and playing chess games with the Go chess engine. 12 | 13 | API Calls 14 | - /api/games GET - Gets the list of games. Returns a JSON array of GameInfo objects 15 | i.e. [{'id':'1','status':'UserMove'}] 16 | 17 | - /api/games?color={user_color} POST - Creates a new chess game. Returns a GameInfo object for the new game 18 | i.e. {'id':'1','status':'UserMove'} 19 | - /api/games/{game_id} GET - Returns the GameInfo object for the specified game id or an Error object. 20 | - /api/games/{game_id}/moves GET - Returns a JSON array of MoveInfo objects which are the moves that have been made 21 | in the specified game or an Error object. 22 | - /api/games/{game_id}/moves?start={start_position}&end={end_position} POST - Makes a new move in the game. Passed in start 23 | and end positions in rowcol format (i.e. a1, h3, etc). If no parameters passed in then it should be an engine move. 24 | Returns a MoveInfo with an updated GameInfo object or an Error object. 25 | - /api/games/{game_id}/moves/{move_id} GET - Returns a MoveInfo object for the specified move ID or an Error. 26 | 27 | Building and Deploying the Docker Container 28 | - Build the container: docker build --tag chess-go:latest . 29 | - Run the container: docker run -p 80:80 chess-go:latest 30 | -------------------------------------------------------------------------------- /chess_engine/basic_move_picker.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | ) 7 | 8 | type BasicMovePicker struct { 9 | EngineColor PieceColor 10 | } 11 | 12 | func NewBasicMovePicker(engineColor PieceColor) *BasicMovePicker { 13 | movePicker := &BasicMovePicker{engineColor} 14 | return movePicker 15 | } 16 | 17 | func (picker *BasicMovePicker) GetNextMove(game *Game) (Result, *Move) { 18 | retVal := Result{false, INVALID_MOVE, ""} 19 | validMoves := []*Move{} 20 | var retMove *Move = nil 21 | bestScore := -100 22 | 23 | // Check for engine in checkmate 24 | if !game.IsOver() { 25 | moves := game.Board.GetMoves(game.EngineColor) 26 | 27 | for _, m := range moves { 28 | tempBoard := game.Board.CopyBoard() 29 | tempBoard.MovePiece(m) 30 | if !tempBoard.KingInCheck(game.EngineColor) { 31 | validMoves = append(validMoves, m) 32 | } 33 | } 34 | 35 | movesMap := make(map[string][]*Move) 36 | for _, m := range validMoves { 37 | tempBoard := game.Board.CopyBoard() 38 | tempBoard.MovePiece(m) 39 | // Look for CheckMate first 40 | if tempBoard.CheckMate(game.UserColor) { 41 | retMove = m 42 | break 43 | } 44 | // Get the user moves from this position 45 | userMoves := tempBoard.GetMoves(game.UserColor) 46 | bestUserScore := -100 47 | var bestUserBoard *Board = nil 48 | // Get the move which results in the highest user score 49 | for _, userMove := range userMoves { 50 | tempBoard2 := tempBoard.CopyBoard() 51 | tempBoard2.MovePiece(userMove) 52 | userScore := tempBoard2.GetScore(game.UserColor) 53 | engineScore := tempBoard2.GetScore(game.EngineColor) 54 | currScore := userScore - engineScore 55 | if currScore > bestUserScore { 56 | bestUserScore = currScore 57 | bestUserBoard = tempBoard2 58 | } 59 | } 60 | // Get the diff of the user's best move with this move. See if this engine/user move combo ends up with the 61 | // engine having the biggest score advantage. If so then that's the return move. 62 | engineScore := bestUserBoard.GetScore(game.EngineColor) 63 | userScore := bestUserBoard.GetScore(game.UserColor) 64 | currScore := engineScore - userScore 65 | if currScore >= bestScore { 66 | bestScore = currScore 67 | currScoreStr := fmt.Sprintf("%d", currScore) 68 | if movesMap[currScoreStr] == nil { 69 | movesMap[currScoreStr] = make([]*Move, 1, 1) 70 | movesMap[currScoreStr][0] = m 71 | } else { 72 | movesMap[currScoreStr] = append(movesMap[currScoreStr], m) 73 | } 74 | } 75 | 76 | } 77 | 78 | bestScoreStr := fmt.Sprintf("%d", bestScore) 79 | bestLen := len(movesMap[bestScoreStr]) 80 | if bestLen > 0 { 81 | idx := rand.Int() % bestLen 82 | retMove = movesMap[bestScoreStr][idx] 83 | } 84 | } 85 | 86 | if retMove != nil { 87 | retVal = Result{true, NO_ERROR, ""} 88 | } 89 | 90 | return retVal, retMove 91 | } 92 | -------------------------------------------------------------------------------- /chess_engine/basic_move_picker_test.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | -------------------------------------------------------------------------------- /chess_engine/bishop.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | type Bishop struct { 4 | Type PieceType 5 | Color PieceColor 6 | CurrPosition BoardPosition 7 | PrevPosition BoardPosition 8 | MoveCount int 9 | } 10 | 11 | func NewBishop(pos BoardPosition, color PieceColor) *Bishop { 12 | bishop := &Bishop{BISHOP, color, pos, 13 | INVALID_BOARD_POSITION, 0} 14 | return bishop 15 | } 16 | 17 | func (bishop *Bishop) Copy() ChessPiece { 18 | copyBishop := &Bishop{} 19 | copyBishop.Type = bishop.Type 20 | copyBishop.Color = bishop.Color 21 | copyBishop.CurrPosition = bishop.CurrPosition 22 | copyBishop.PrevPosition = bishop.PrevPosition 23 | copyBishop.MoveCount = bishop.MoveCount 24 | return copyBishop 25 | } 26 | 27 | func (bishop *Bishop) GetMoveCount() int { 28 | return bishop.MoveCount 29 | } 30 | 31 | func (bishop *Bishop) GetMoves(board *Board) []*Move { 32 | moves := []*Move{} 33 | 34 | moves = bishop.addIterMoves(board, moves, 1, 1) 35 | moves = bishop.addIterMoves(board, moves, -1, 1) 36 | moves = bishop.addIterMoves(board, moves, 1, -1) 37 | moves = bishop.addIterMoves(board, moves, -1, -1) 38 | 39 | return moves 40 | } 41 | 42 | func (bishop *Bishop) addIterMoves(board *Board, moves []*Move, rInc int, fInc int) []*Move { 43 | startRank, startFile := GetRankFile(bishop.CurrPosition) 44 | startRank += rInc 45 | startFile += fInc 46 | for (startRank < 8 && startRank >= 0) && (startFile < 8 && startFile >= 0) { 47 | currPos := GetBoardPos(startRank, startFile) 48 | if board.Squares[currPos].Occupied { 49 | if board.Squares[currPos].CurrPiece.GetColor() != bishop.Color { 50 | moves = append(moves, NewMove(bishop.CurrPosition, currPos)) 51 | return moves // Hit another piece so no more moves 52 | } else { 53 | return moves // We've hit a blocking piece along the diagonal. 54 | } 55 | } else { 56 | moves = append(moves, NewMove(bishop.CurrPosition, currPos)) 57 | } 58 | startRank += rInc 59 | startFile += fInc 60 | } 61 | 62 | return moves 63 | } 64 | 65 | func (bishop *Bishop) GetCurrPosition() BoardPosition { 66 | return bishop.CurrPosition 67 | } 68 | 69 | func (bishop *Bishop) GetPrevPosition() BoardPosition { 70 | return bishop.PrevPosition 71 | } 72 | 73 | func (bishop *Bishop) GetColor() PieceColor { 74 | return bishop.Color 75 | } 76 | 77 | func (bishop *Bishop) GetType() PieceType { 78 | return bishop.Type 79 | } 80 | 81 | func (bishop *Bishop) Move(newPos BoardPosition) { 82 | bishop.CurrPosition = newPos 83 | bishop.MoveCount++ 84 | } 85 | 86 | func (bishop *Bishop) GetValue() int { 87 | return 3 88 | } 89 | -------------------------------------------------------------------------------- /chess_engine/bishop_test.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | import "testing" 4 | 5 | func TestNewBishop(t *testing.T) { 6 | bishop := NewBishop(C1, Black) 7 | 8 | if bishop == nil { 9 | t.Errorf("Error! NewBishop function returned nil for bishop!") 10 | } 11 | 12 | if bishop.Type != BISHOP { 13 | t.Errorf("Error! NewBishop function returned wrong Type!") 14 | } 15 | 16 | if bishop.CurrPosition != C1 { 17 | t.Errorf("Error! NewBishop function returned wrong CurrPosition!") 18 | } 19 | 20 | if bishop.Color != Black { 21 | t.Errorf("Error! NewBishop function returned wrong Color!") 22 | } 23 | 24 | if bishop.GetType() != BISHOP { 25 | t.Errorf("Error! NewBishop function returned wrong Type!") 26 | } 27 | 28 | if bishop.GetCurrPosition() != C1 { 29 | t.Errorf("Error! NewBishop function returned wrong CurrPosition!") 30 | } 31 | 32 | if bishop.GetColor() != Black { 33 | t.Errorf("Error! NewBishop function returned wrong Color!") 34 | } 35 | } 36 | 37 | func TestBishopMove(t *testing.T) { 38 | bishop := NewBishop(C1, Black) 39 | bishop.Move(C2) 40 | if bishop.GetCurrPosition() != C2 { 41 | t.Errorf("Error! Bishop.Move function returned wrong CurrPosition!") 42 | } 43 | if bishop.MoveCount != 1 { 44 | t.Errorf("Error! Bishop.MoveCount wrong after move!") 45 | } 46 | } 47 | 48 | func TestWhiteBishopCenterMoves(t *testing.T) { 49 | board := &Board{} 50 | // Test white square Bishop moving from center square to outer corners 51 | board.Squares[E4] = Square{true, NewBishop(E4, White)} 52 | moves := board.Squares[E4].CurrPiece.GetMoves(board) 53 | if len(moves) != 13 { 54 | t.Errorf("Error! Bishop.GetMoves function did not return 4 moves for center square on empty board!") 55 | } 56 | if !MovesContainMove(Move{E4, D3, false, false, false}, moves) && 57 | !MovesContainMove(Move{E4, C2, false, false, false}, moves) && 58 | !MovesContainMove(Move{E4, D1, false, false, false}, moves) && 59 | !MovesContainMove(Move{E4, F5, false, false, false}, moves) && 60 | !MovesContainMove(Move{E4, G6, false, false, false}, moves) && 61 | !MovesContainMove(Move{E4, H7, false, false, false}, moves) && 62 | !MovesContainMove(Move{E4, F3, false, false, false}, moves) && 63 | !MovesContainMove(Move{E4, G2, false, false, false}, moves) && 64 | !MovesContainMove(Move{E4, H1, false, false, false}, moves) && 65 | !MovesContainMove(Move{E4, D5, false, false, false}, moves) && 66 | !MovesContainMove(Move{E4, C6, false, false, false}, moves) && 67 | !MovesContainMove(Move{E4, B7, false, false, false}, moves) && 68 | !MovesContainMove(Move{E4, A8, false, false, false}, moves) { 69 | t.Errorf("Error! Bishop.GetMoves for white square bishop missing moves!") 70 | } 71 | } 72 | 73 | func TestBlackBishopCenterMoves(t *testing.T) { 74 | board := &Board{} 75 | // Test white square Bishop moving from center square to outer corners 76 | board.Squares[D4] = Square{true, NewBishop(D4, White)} 77 | moves := board.Squares[D4].CurrPiece.GetMoves(board) 78 | if len(moves) != 13 { 79 | t.Errorf("Error! Bishop.GetMoves function did not return 4 moves for center square on empty board!") 80 | } 81 | if !MovesContainMove(Move{D4, C3, false, false, false}, moves) && 82 | !MovesContainMove(Move{D4, B2, false, false, false}, moves) && 83 | !MovesContainMove(Move{D4, A1, false, false, false}, moves) && 84 | !MovesContainMove(Move{D4, E5, false, false, false}, moves) && 85 | !MovesContainMove(Move{D4, F6, false, false, false}, moves) && 86 | !MovesContainMove(Move{D4, G7, false, false, false}, moves) && 87 | !MovesContainMove(Move{D4, H8, false, false, false}, moves) && 88 | !MovesContainMove(Move{D4, E3, false, false, false}, moves) && 89 | !MovesContainMove(Move{D4, F2, false, false, false}, moves) && 90 | !MovesContainMove(Move{D4, G1, false, false, false}, moves) && 91 | !MovesContainMove(Move{D4, C5, false, false, false}, moves) && 92 | !MovesContainMove(Move{D4, B6, false, false, false}, moves) && 93 | !MovesContainMove(Move{D4, A7, false, false, false}, moves) { 94 | t.Errorf("Error! Bishop.GetMoves for white square bishop missing moves!") 95 | } 96 | } 97 | 98 | func TestWhiteBishopNoMoves(t *testing.T) { 99 | board := &Board{} 100 | // Test white square Bishop moving from center square to outer corners 101 | board.Squares[D4] = Square{true, NewBishop(D4, White)} 102 | board.Squares[E5] = Square{true, NewPawn(E5, White)} 103 | board.Squares[E3] = Square{true, NewPawn(E3, White)} 104 | board.Squares[C5] = Square{true, NewPawn(C5, White)} 105 | board.Squares[C3] = Square{true, NewPawn(C3, White)} 106 | 107 | moves := board.Squares[D4].CurrPiece.GetMoves(board) 108 | if len(moves) != 0 { 109 | t.Errorf("Error! Bishop.GetMoves function did not return 0 moves for white center square when surrounded by white!") 110 | } 111 | } 112 | 113 | func TestBlackBishopNoMoves(t *testing.T) { 114 | board := &Board{} 115 | // Test white square Bishop moving from center square to outer corners 116 | board.Squares[E4] = Square{true, NewBishop(E4, Black)} 117 | board.Squares[F5] = Square{true, NewPawn(F5, Black)} 118 | board.Squares[F3] = Square{true, NewPawn(F3, Black)} 119 | board.Squares[D5] = Square{true, NewPawn(D5, Black)} 120 | board.Squares[D3] = Square{true, NewPawn(D3, Black)} 121 | 122 | moves := board.Squares[E4].CurrPiece.GetMoves(board) 123 | if len(moves) != 0 { 124 | t.Errorf("Error! Bishop.GetMoves function did not return 0 moves for black center square when surrounded by black!") 125 | } 126 | } 127 | 128 | func TestWhiteBishopAttackMoves(t *testing.T) { 129 | board := &Board{} 130 | // Test white square Bishop moving from center square to outer corners 131 | board.Squares[D4] = Square{true, NewBishop(D4, White)} 132 | board.Squares[E5] = Square{true, NewPawn(E5, Black)} 133 | board.Squares[E3] = Square{true, NewPawn(E3, Black)} 134 | board.Squares[C5] = Square{true, NewPawn(C5, Black)} 135 | board.Squares[C3] = Square{true, NewPawn(C3, Black)} 136 | 137 | moves := board.Squares[D4].CurrPiece.GetMoves(board) 138 | if len(moves) != 4 { 139 | t.Errorf("Error! Bishop.GetMoves function did not return 4 moves for white bishop on center square surrounded by black!") 140 | } 141 | } 142 | 143 | func TestBlackBishopAttackMoves(t *testing.T) { 144 | board := &Board{} 145 | // Test white square Bishop moving from center square to outer corners 146 | board.Squares[E4] = Square{true, NewBishop(E4, Black)} 147 | board.Squares[F5] = Square{true, NewPawn(F5, Black)} 148 | board.Squares[F3] = Square{true, NewPawn(F3, Black)} 149 | board.Squares[D5] = Square{true, NewPawn(D5, Black)} 150 | board.Squares[D3] = Square{true, NewPawn(D3, Black)} 151 | 152 | moves := board.Squares[E4].CurrPiece.GetMoves(board) 153 | if len(moves) != 0 { 154 | t.Errorf("Error! Bishop.GetMoves function did not return 4 moves for black center square when surrounded by white!") 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /chess_engine/board.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | type PieceType uint8 4 | 5 | const ( 6 | NoPiece PieceType = iota 7 | PAWN 8 | ROOK 9 | KNIGHT 10 | BISHOP 11 | QUEEN 12 | KING 13 | ) 14 | 15 | type PieceColor uint8 16 | 17 | const ( 18 | White = 0 19 | Black = 1 20 | ) 21 | 22 | type BoardPosition uint8 23 | 24 | const ( 25 | A1 BoardPosition = iota 26 | A2 27 | A3 28 | A4 29 | A5 30 | A6 31 | A7 32 | A8 33 | B1 34 | B2 35 | B3 36 | B4 37 | B5 38 | B6 39 | B7 40 | B8 41 | C1 42 | C2 43 | C3 44 | C4 45 | C5 46 | C6 47 | C7 48 | C8 49 | D1 50 | D2 51 | D3 52 | D4 53 | D5 54 | D6 55 | D7 56 | D8 57 | E1 58 | E2 59 | E3 60 | E4 61 | E5 62 | E6 63 | E7 64 | E8 65 | F1 66 | F2 67 | F3 68 | F4 69 | F5 70 | F6 71 | F7 72 | F8 73 | G1 74 | G2 75 | G3 76 | G4 77 | G5 78 | G6 79 | G7 80 | G8 81 | H1 82 | H2 83 | H3 84 | H4 85 | H5 86 | H6 87 | H7 88 | H8 89 | INVALID_BOARD_POSITION 90 | ) 91 | 92 | type Square struct { 93 | Occupied bool 94 | CurrPiece ChessPiece 95 | } 96 | 97 | type Board struct { 98 | Squares [64]Square 99 | WhiteInCheck bool 100 | BlackInCheck bool 101 | PrevMove Move 102 | } 103 | 104 | func NewBoard() *Board { 105 | b := Board{} 106 | b.InitBoard() 107 | b.WhiteInCheck = false 108 | b.BlackInCheck = false 109 | b.PrevMove = Move{} 110 | return &b 111 | } 112 | 113 | func (board *Board) InitBoard() { 114 | board.Squares[A1] = Square{Occupied: true, CurrPiece: NewRook(A1, White)} 115 | board.Squares[B1] = Square{Occupied: true, CurrPiece: NewKnight(B1, White)} 116 | board.Squares[C1] = Square{Occupied: true, CurrPiece: NewBishop(C1, White)} 117 | board.Squares[D1] = Square{Occupied: true, CurrPiece: NewQueen(D1, White)} 118 | board.Squares[E1] = Square{Occupied: true, CurrPiece: NewKing(E1, White)} 119 | board.Squares[F1] = Square{Occupied: true, CurrPiece: NewBishop(F1, White)} 120 | board.Squares[G1] = Square{Occupied: true, CurrPiece: NewKnight(G1, White)} 121 | board.Squares[H1] = Square{Occupied: true, CurrPiece: NewRook(H1, White)} 122 | board.Squares[A2] = Square{Occupied: true, CurrPiece: NewPawn(A2, White)} 123 | board.Squares[B2] = Square{Occupied: true, CurrPiece: NewPawn(B2, White)} 124 | board.Squares[C2] = Square{Occupied: true, CurrPiece: NewPawn(C2, White)} 125 | board.Squares[D2] = Square{Occupied: true, CurrPiece: NewPawn(D2, White)} 126 | board.Squares[E2] = Square{Occupied: true, CurrPiece: NewPawn(E2, White)} 127 | board.Squares[F2] = Square{Occupied: true, CurrPiece: NewPawn(F2, White)} 128 | board.Squares[G2] = Square{Occupied: true, CurrPiece: NewPawn(G2, White)} 129 | board.Squares[H2] = Square{Occupied: true, CurrPiece: NewPawn(H2, White)} 130 | board.Squares[A8] = Square{Occupied: true, CurrPiece: NewRook(A8, Black)} 131 | board.Squares[B8] = Square{Occupied: true, CurrPiece: NewKnight(B8, Black)} 132 | board.Squares[C8] = Square{Occupied: true, CurrPiece: NewBishop(C8, Black)} 133 | board.Squares[D8] = Square{Occupied: true, CurrPiece: NewQueen(D8, Black)} 134 | board.Squares[E8] = Square{Occupied: true, CurrPiece: NewKing(E8, Black)} 135 | board.Squares[F8] = Square{Occupied: true, CurrPiece: NewBishop(F8, Black)} 136 | board.Squares[G8] = Square{Occupied: true, CurrPiece: NewKnight(G8, Black)} 137 | board.Squares[H8] = Square{Occupied: true, CurrPiece: NewRook(H8, Black)} 138 | board.Squares[A7] = Square{Occupied: true, CurrPiece: NewPawn(A7, Black)} 139 | board.Squares[B7] = Square{Occupied: true, CurrPiece: NewPawn(B7, Black)} 140 | board.Squares[C7] = Square{Occupied: true, CurrPiece: NewPawn(C7, Black)} 141 | board.Squares[D7] = Square{Occupied: true, CurrPiece: NewPawn(D7, Black)} 142 | board.Squares[E7] = Square{Occupied: true, CurrPiece: NewPawn(E7, Black)} 143 | board.Squares[F7] = Square{Occupied: true, CurrPiece: NewPawn(F7, Black)} 144 | board.Squares[G7] = Square{Occupied: true, CurrPiece: NewPawn(G7, Black)} 145 | board.Squares[H7] = Square{Occupied: true, CurrPiece: NewPawn(H7, Black)} 146 | board.PrevMove = Move{} 147 | } 148 | 149 | func (board *Board) CopyBoard() *Board { 150 | copyBoard := &Board{} 151 | for rank := 0; rank < 8; rank++ { 152 | for file := 0; file < 8; file++ { 153 | currPos := GetBoardPos(rank, file) 154 | if board.Squares[currPos].Occupied { 155 | copyBoard.Squares[currPos] = Square{Occupied: true, CurrPiece: board.Squares[currPos].CurrPiece.Copy()} 156 | } 157 | } 158 | } 159 | copyBoard.PrevMove = board.PrevMove 160 | copyBoard.WhiteInCheck = board.WhiteInCheck 161 | copyBoard.BlackInCheck = board.BlackInCheck 162 | return copyBoard 163 | } 164 | 165 | func (board *Board) GetScore(color PieceColor) int { 166 | score := 0 167 | for rank := 0; rank < 8; rank++ { 168 | for file := 0; file < 8; file++ { 169 | currPos := GetBoardPos(rank, file) 170 | if board.Squares[currPos].Occupied && board.Squares[currPos].CurrPiece.GetColor() == color { 171 | score += board.Squares[currPos].CurrPiece.GetValue() 172 | } 173 | } 174 | } 175 | return score 176 | } 177 | 178 | func (board *Board) GetKingPosition(color PieceColor) BoardPosition { 179 | for rank := 0; rank < 8; rank++ { 180 | for file := 0; file < 8; file++ { 181 | currPos := GetBoardPos(rank, file) 182 | if board.Squares[currPos].Occupied && 183 | board.Squares[currPos].CurrPiece.GetColor() == color && 184 | board.Squares[currPos].CurrPiece.GetType() == KING { 185 | return currPos 186 | } 187 | } 188 | } 189 | return INVALID_BOARD_POSITION 190 | } 191 | 192 | func (board *Board) GetMoves(color PieceColor) []*Move { 193 | moves := []*Move{} 194 | for rank := 0; rank < 8; rank++ { 195 | for file := 0; file < 8; file++ { 196 | currPos := GetBoardPos(rank, file) 197 | if board.Squares[currPos].Occupied && 198 | board.Squares[currPos].CurrPiece.GetColor() == color { 199 | newMoves := board.Squares[currPos].CurrPiece.GetMoves(board) 200 | if newMoves != nil { 201 | moves = append(moves, newMoves...) 202 | } 203 | } 204 | } 205 | } 206 | 207 | return moves 208 | } 209 | 210 | func (board *Board) GetAttackMoves(color PieceColor) []*Move { 211 | moves := []*Move{} 212 | for rank := 0; rank < 8; rank++ { 213 | for file := 0; file < 8; file++ { 214 | currPos := GetBoardPos(rank, file) 215 | if board.Squares[currPos].Occupied && 216 | board.Squares[currPos].CurrPiece.GetColor() == color { 217 | newMoves := []*Move{} 218 | if board.Squares[currPos].CurrPiece.GetType() == KING { 219 | king := board.Squares[currPos].CurrPiece.(*King) 220 | newMoves = king.GetAttackMoves(board) 221 | } else { 222 | newMoves = board.Squares[currPos].CurrPiece.GetMoves(board) 223 | } 224 | 225 | if newMoves != nil { 226 | moves = append(moves, newMoves...) 227 | } 228 | } 229 | } 230 | } 231 | 232 | return moves 233 | } 234 | 235 | func (board *Board) KingInCheck(color PieceColor) bool { 236 | retVal := false 237 | if color == White { 238 | board.WhiteInCheck = false 239 | } else { 240 | board.BlackInCheck = false 241 | } 242 | oppositeColor := OppositeColor(color) 243 | moves := board.GetMoves(oppositeColor) 244 | kingPos := board.GetKingPosition(color) 245 | if kingPos != INVALID_BOARD_POSITION { 246 | for _, move := range moves { 247 | if move.EndPos == kingPos { 248 | retVal = true 249 | if color == White { 250 | board.WhiteInCheck = true 251 | } else { 252 | board.BlackInCheck = true 253 | } 254 | } 255 | } 256 | } 257 | 258 | return retVal 259 | } 260 | 261 | func (board *Board) CheckMate(color PieceColor) bool { 262 | retVal := true 263 | 264 | if board.KingInCheck(color) { // King must be in check in it's current position 265 | kingPos := board.GetKingPosition(color) 266 | moves := board.Squares[kingPos].CurrPiece.GetMoves(board) 267 | for _, m := range moves { 268 | tempBoard := board.CopyBoard() 269 | tempBoard.MovePiece(m) 270 | if !tempBoard.KingInCheck(color) { 271 | retVal = false 272 | } 273 | } 274 | } else { 275 | retVal = false 276 | } 277 | 278 | if retVal { 279 | retVal = true 280 | } 281 | return retVal 282 | } 283 | 284 | func (board *Board) IsStalemate() bool { 285 | retVal := false 286 | whiteMoves := board.GetMoves(White) 287 | blackMoves := board.GetMoves(Black) 288 | 289 | if len(whiteMoves) == 0 && board.KingInCheck(White) { 290 | retVal = true 291 | } else if len(blackMoves) == 0 && board.KingInCheck(Black) { 292 | retVal = true 293 | } 294 | return retVal 295 | } 296 | 297 | func (board *Board) IsDraw() bool { 298 | retVal := false 299 | whitePieceCnt := 0 300 | blackPieceCnt := 0 301 | for rank := 0; rank < 8; rank++ { 302 | for file := 0; file < 8; file++ { 303 | currPos := GetBoardPos(rank, file) 304 | if board.Squares[currPos].Occupied && board.Squares[currPos].CurrPiece.GetColor() == Black { 305 | blackPieceCnt += 1 306 | } else if board.Squares[currPos].Occupied && board.Squares[currPos].CurrPiece.GetColor() == White { 307 | whitePieceCnt += 1 308 | } 309 | } 310 | } 311 | 312 | if blackPieceCnt <= 1 && whitePieceCnt <= 1 { 313 | retVal = true 314 | } 315 | 316 | return retVal 317 | } 318 | 319 | func (board *Board) MovePiece(move *Move) Result { 320 | res := Result{true, NO_ERROR, ""} 321 | if move == nil || 322 | move.StartPos == INVALID_BOARD_POSITION || 323 | move.EndPos == INVALID_BOARD_POSITION { 324 | res.Result = false 325 | res.ResCode = INVALID_MOVE 326 | } else if !board.Squares[move.StartPos].Occupied { 327 | res.Result = false 328 | res.ResCode = INVALID_MOVE 329 | } else { 330 | if (board.Squares[move.EndPos].Occupied) && 331 | (board.Squares[move.StartPos].CurrPiece.GetColor() == board.Squares[move.EndPos].CurrPiece.GetColor()) { 332 | res.Result = false 333 | res.ResCode = INVALID_MOVE 334 | } else { 335 | board.Squares[move.EndPos].CurrPiece = board.Squares[move.StartPos].CurrPiece 336 | board.Squares[move.EndPos].Occupied = true 337 | board.Squares[move.StartPos].CurrPiece = nil 338 | board.Squares[move.StartPos].Occupied = false 339 | board.Squares[move.EndPos].CurrPiece.Move(move.EndPos) 340 | if move.Castle { 341 | res = board.CastleMove(move) 342 | } 343 | board.PrevMove = *move 344 | } 345 | } 346 | return res 347 | } 348 | 349 | func (board *Board) CastleMove(move *Move) Result { 350 | res := Result{true, NO_ERROR, ""} 351 | if (move.StartPos != E1 && (move.EndPos != G1 && move.EndPos != C1)) && 352 | (move.StartPos != E8 && (move.EndPos != G8 && move.EndPos != C8)) { 353 | res = Result{false, INVALID_MOVE, "Invalid castle move"} 354 | } else { 355 | rookStart := H1 356 | rookEnd := F1 357 | if move.StartPos == E1 && move.EndPos == C1 { 358 | rookStart = A1 359 | rookEnd = D1 360 | } else if move.StartPos == E8 && move.EndPos == G8 { 361 | rookStart = H8 362 | rookEnd = F8 363 | } else if move.StartPos == E8 && move.EndPos == C8 { 364 | rookStart = A8 365 | rookEnd = D8 366 | } 367 | board.Squares[rookEnd].CurrPiece = board.Squares[rookStart].CurrPiece 368 | board.Squares[rookEnd].Occupied = true 369 | board.Squares[rookStart].CurrPiece = nil 370 | board.Squares[rookStart].Occupied = false 371 | board.Squares[rookEnd].CurrPiece.Move(rookEnd) 372 | } 373 | 374 | return res 375 | 376 | } 377 | -------------------------------------------------------------------------------- /chess_engine/board_test.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | import "testing" 4 | 5 | func TestNewBoard(t *testing.T) { 6 | b := NewBoard() 7 | if b == nil { 8 | t.Errorf("Error! NewBoard function returned nil!") 9 | } 10 | if (!b.Squares[A1].Occupied) || (b.Squares[A1].CurrPiece.GetType() != ROOK) || 11 | (b.Squares[A1].CurrPiece.GetColor() != White) { 12 | t.Errorf("Error initialing board square A1") 13 | } 14 | if (!b.Squares[B1].Occupied) || (b.Squares[B1].CurrPiece.GetType() != KNIGHT) || 15 | (b.Squares[B1].CurrPiece.GetColor() != White) { 16 | t.Errorf("Error initialing board square B1") 17 | } 18 | if (!b.Squares[C1].Occupied) || (b.Squares[C1].CurrPiece.GetType() != BISHOP) || 19 | (b.Squares[C1].CurrPiece.GetColor() != White) { 20 | t.Errorf("Error initialing board square C1") 21 | } 22 | if (!b.Squares[D1].Occupied) || (b.Squares[D1].CurrPiece.GetType() != QUEEN) || 23 | (b.Squares[D1].CurrPiece.GetColor() != White) { 24 | t.Errorf("Error initialing board square D1") 25 | } 26 | if (!b.Squares[E1].Occupied) || (b.Squares[E1].CurrPiece.GetType() != KING) || 27 | (b.Squares[E1].CurrPiece.GetColor() != White) { 28 | t.Errorf("Error initialing board square E1") 29 | } 30 | if (!b.Squares[F1].Occupied) || (b.Squares[F1].CurrPiece.GetType() != BISHOP) || 31 | (b.Squares[F1].CurrPiece.GetColor() != White) { 32 | t.Errorf("Error initialing board square F1") 33 | } 34 | if (!b.Squares[G1].Occupied) || (b.Squares[G1].CurrPiece.GetType() != KNIGHT) || 35 | (b.Squares[G1].CurrPiece.GetColor() != White) { 36 | t.Errorf("Error initialing board square G1") 37 | } 38 | if (!b.Squares[H1].Occupied) || (b.Squares[H1].CurrPiece.GetType() != ROOK) || 39 | (b.Squares[H1].CurrPiece.GetColor() != White) { 40 | t.Errorf("Error initialing board square H1") 41 | } 42 | if (!b.Squares[A2].Occupied) || (b.Squares[A2].CurrPiece.GetType() != PAWN) || 43 | (b.Squares[A2].CurrPiece.GetColor() != White) { 44 | t.Errorf("Error initialing board square A2") 45 | } 46 | if (!b.Squares[B2].Occupied) || (b.Squares[B2].CurrPiece.GetType() != PAWN) || 47 | (b.Squares[B2].CurrPiece.GetColor() != White) { 48 | t.Errorf("Error initialing board square B2") 49 | } 50 | if (!b.Squares[C2].Occupied) || (b.Squares[C2].CurrPiece.GetType() != PAWN) || 51 | (b.Squares[C2].CurrPiece.GetColor() != White) { 52 | t.Errorf("Error initialing board square C2") 53 | } 54 | if (!b.Squares[D2].Occupied) || (b.Squares[D2].CurrPiece.GetType() != PAWN) || 55 | (b.Squares[D2].CurrPiece.GetColor() != White) { 56 | t.Errorf("Error initialing board square D2") 57 | } 58 | if (!b.Squares[E2].Occupied) || (b.Squares[E2].CurrPiece.GetType() != PAWN) || 59 | (b.Squares[E2].CurrPiece.GetColor() != White) { 60 | t.Errorf("Error initialing board square E2") 61 | } 62 | if (!b.Squares[F2].Occupied) || (b.Squares[F2].CurrPiece.GetType() != PAWN) || 63 | (b.Squares[F2].CurrPiece.GetColor() != White) { 64 | t.Errorf("Error initialing board square F2") 65 | } 66 | if (!b.Squares[G2].Occupied) || (b.Squares[G2].CurrPiece.GetType() != PAWN) || 67 | (b.Squares[G2].CurrPiece.GetColor() != White) { 68 | t.Errorf("Error initialing board square G2") 69 | } 70 | if (!b.Squares[H2].Occupied) || (b.Squares[H2].CurrPiece.GetType() != PAWN) || 71 | (b.Squares[H2].CurrPiece.GetColor() != White) { 72 | t.Errorf("Error initialing board square H2") 73 | } 74 | if b.Squares[A3].Occupied { 75 | t.Errorf("Error initialing board square A3") 76 | } 77 | if b.Squares[B3].Occupied { 78 | t.Errorf("Error initialing board square B3") 79 | } 80 | if b.Squares[C3].Occupied { 81 | t.Errorf("Error initialing board square C3") 82 | } 83 | if b.Squares[D3].Occupied { 84 | t.Errorf("Error initialing board square D3") 85 | } 86 | if b.Squares[E3].Occupied { 87 | t.Errorf("Error initialing board square E3") 88 | } 89 | if b.Squares[F3].Occupied { 90 | t.Errorf("Error initialing board square F3") 91 | } 92 | if b.Squares[G3].Occupied { 93 | t.Errorf("Error initialing board square G3") 94 | } 95 | if b.Squares[H3].Occupied { 96 | t.Errorf("Error initialing board square H3") 97 | } 98 | if b.Squares[A4].Occupied { 99 | t.Errorf("Error initialing board square A4") 100 | } 101 | if b.Squares[B4].Occupied { 102 | t.Errorf("Error initialing board square B4") 103 | } 104 | if b.Squares[C4].Occupied { 105 | t.Errorf("Error initialing board square C4") 106 | } 107 | if b.Squares[D4].Occupied { 108 | t.Errorf("Error initialing board square D4") 109 | } 110 | if b.Squares[E4].Occupied { 111 | t.Errorf("Error initialing board square E4") 112 | } 113 | if b.Squares[F4].Occupied { 114 | t.Errorf("Error initialing board square F4") 115 | } 116 | if b.Squares[G4].Occupied { 117 | t.Errorf("Error initialing board square G4") 118 | } 119 | if b.Squares[H4].Occupied { 120 | t.Errorf("Error initialing board square H4") 121 | } 122 | if b.Squares[A5].Occupied { 123 | t.Errorf("Error initialing board square A5") 124 | } 125 | if b.Squares[B5].Occupied { 126 | t.Errorf("Error initialing board square B5") 127 | } 128 | if b.Squares[C5].Occupied { 129 | t.Errorf("Error initialing board square C5") 130 | } 131 | if b.Squares[D5].Occupied { 132 | t.Errorf("Error initialing board square D5") 133 | } 134 | if b.Squares[E5].Occupied { 135 | t.Errorf("Error initialing board square E5") 136 | } 137 | if b.Squares[F5].Occupied { 138 | t.Errorf("Error initialing board square F5") 139 | } 140 | if b.Squares[G5].Occupied { 141 | t.Errorf("Error initialing board square G5") 142 | } 143 | if b.Squares[H5].Occupied { 144 | t.Errorf("Error initialing board square H5") 145 | } 146 | if b.Squares[A6].Occupied { 147 | t.Errorf("Error initialing board square A6") 148 | } 149 | if b.Squares[B6].Occupied { 150 | t.Errorf("Error initialing board square B6") 151 | } 152 | if b.Squares[C6].Occupied { 153 | t.Errorf("Error initialing board square C6") 154 | } 155 | if b.Squares[D6].Occupied { 156 | t.Errorf("Error initialing board square D6") 157 | } 158 | if b.Squares[E6].Occupied { 159 | t.Errorf("Error initialing board square E6") 160 | } 161 | if b.Squares[F6].Occupied { 162 | t.Errorf("Error initialing board square F6") 163 | } 164 | if b.Squares[G6].Occupied { 165 | t.Errorf("Error initialing board square G6") 166 | } 167 | if b.Squares[H6].Occupied { 168 | t.Errorf("Error initialing board square H6") 169 | } 170 | if (!b.Squares[A7].Occupied) || (b.Squares[A7].CurrPiece.GetType() != PAWN) || 171 | (b.Squares[A7].CurrPiece.GetColor() != Black) { 172 | t.Errorf("Error initialing board square A7") 173 | } 174 | if (!b.Squares[B7].Occupied) || (b.Squares[B7].CurrPiece.GetType() != PAWN) || 175 | (b.Squares[B7].CurrPiece.GetColor() != Black) { 176 | t.Errorf("Error initialing board square B7") 177 | } 178 | if (!b.Squares[C7].Occupied) || (b.Squares[C7].CurrPiece.GetType() != PAWN) || 179 | (b.Squares[C7].CurrPiece.GetColor() != Black) { 180 | t.Errorf("Error initialing board square C7") 181 | } 182 | if (!b.Squares[D7].Occupied) || (b.Squares[D7].CurrPiece.GetType() != PAWN) || 183 | (b.Squares[D7].CurrPiece.GetColor() != Black) { 184 | t.Errorf("Error initialing board square D7") 185 | } 186 | if (!b.Squares[E7].Occupied) || (b.Squares[E7].CurrPiece.GetType() != PAWN) || 187 | (b.Squares[E7].CurrPiece.GetColor() != Black) { 188 | t.Errorf("Error initialing board square E7") 189 | } 190 | if (!b.Squares[F7].Occupied) || (b.Squares[F7].CurrPiece.GetType() != PAWN) || 191 | (b.Squares[F7].CurrPiece.GetColor() != Black) { 192 | t.Errorf("Error initialing board square F7") 193 | } 194 | if (!b.Squares[G7].Occupied) || (b.Squares[G7].CurrPiece.GetType() != PAWN) || 195 | (b.Squares[G7].CurrPiece.GetColor() != Black) { 196 | t.Errorf("Error initialing board square G7") 197 | } 198 | if (!b.Squares[H7].Occupied) || (b.Squares[H7].CurrPiece.GetType() != PAWN) || 199 | (b.Squares[H7].CurrPiece.GetColor() != Black) { 200 | t.Errorf("Error initialing board square H7") 201 | } 202 | if (!b.Squares[A8].Occupied) || (b.Squares[A8].CurrPiece.GetType() != ROOK) || 203 | (b.Squares[A8].CurrPiece.GetColor() != Black) { 204 | t.Errorf("Error initialing board square A8") 205 | } 206 | if (!b.Squares[B8].Occupied) || (b.Squares[B8].CurrPiece.GetType() != KNIGHT) || 207 | (b.Squares[B8].CurrPiece.GetColor() != Black) { 208 | t.Errorf("Error initialing board square B8") 209 | } 210 | if (!b.Squares[C8].Occupied) || (b.Squares[C8].CurrPiece.GetType() != BISHOP) || 211 | (b.Squares[C8].CurrPiece.GetColor() != Black) { 212 | t.Errorf("Error initialing board square C8") 213 | } 214 | if (!b.Squares[D8].Occupied) || (b.Squares[D8].CurrPiece.GetType() != QUEEN) || 215 | (b.Squares[D8].CurrPiece.GetColor() != Black) { 216 | t.Errorf("Error initialing board square D8") 217 | } 218 | if (!b.Squares[E8].Occupied) || (b.Squares[E8].CurrPiece.GetType() != KING) || 219 | (b.Squares[E8].CurrPiece.GetColor() != Black) { 220 | t.Errorf("Error initialing board square E8") 221 | } 222 | if (!b.Squares[F8].Occupied) || (b.Squares[F8].CurrPiece.GetType() != BISHOP) || 223 | (b.Squares[F8].CurrPiece.GetColor() != Black) { 224 | t.Errorf("Error initialing board square F8") 225 | } 226 | if (!b.Squares[G8].Occupied) || (b.Squares[G8].CurrPiece.GetType() != KNIGHT) || 227 | (b.Squares[G8].CurrPiece.GetColor() != Black) { 228 | t.Errorf("Error initialing board square G8") 229 | } 230 | if (!b.Squares[H8].Occupied) || (b.Squares[H8].CurrPiece.GetType() != ROOK) || 231 | (b.Squares[H8].CurrPiece.GetColor() != Black) { 232 | t.Errorf("Error initialing board square H8") 233 | } 234 | } 235 | 236 | func TestBoardScore(t *testing.T) { 237 | board := NewBoard() 238 | score := board.GetScore(White) 239 | if score != 39 { 240 | t.Errorf("Error! Board.GetScore function did not return 39 for white on initial board setup!") 241 | } 242 | score = board.GetScore(Black) 243 | if score != 39 { 244 | t.Errorf("Error! Board.GetScore function did not return 39 for black on initial board setup!") 245 | } 246 | board = &Board{} 247 | board.Squares[D4] = Square{true, NewPawn(D4, White)} 248 | board.Squares[E5] = Square{true, NewRook(E5, White)} 249 | board.Squares[E3] = Square{true, NewKnight(E3, White)} 250 | board.Squares[C5] = Square{true, NewBishop(C5, White)} 251 | board.Squares[C3] = Square{true, NewQueen(C3, White)} 252 | board.Squares[E4] = Square{true, NewKing(E4, White)} 253 | board.Squares[F5] = Square{true, NewPawn(F5, Black)} 254 | board.Squares[F6] = Square{true, NewPawn(F6, Black)} 255 | board.Squares[A1] = Square{true, NewRook(A1, Black)} 256 | board.Squares[A2] = Square{true, NewKnight(A2, Black)} 257 | board.Squares[A3] = Square{true, NewBishop(A3, Black)} 258 | board.Squares[A4] = Square{true, NewQueen(A4, Black)} 259 | board.Squares[A5] = Square{true, NewKing(A5, Black)} 260 | 261 | score = board.GetScore(White) 262 | if score != 21 { 263 | t.Errorf("Error! Board.GetScore function did not return 21 for white on random board setup!") 264 | } 265 | score = board.GetScore(Black) 266 | if score != 22 { 267 | t.Errorf("Error! Board.GetScore function did not return 22 for black on random board setup!") 268 | } 269 | } 270 | 271 | func TestBoardGetKingPos(t *testing.T) { 272 | board := NewBoard() 273 | pos := board.GetKingPosition(White) 274 | if pos != E1 { 275 | t.Errorf("Error! Board.GetKingPosition function did not return E1 for white on initial board setup!") 276 | } 277 | 278 | pos = board.GetKingPosition(Black) 279 | if pos != E8 { 280 | t.Errorf("Error! Board.GetKingPosition function did not return E8 for black on initial board setup!") 281 | } 282 | 283 | board = &Board{} 284 | board.Squares[D4] = Square{true, NewKing(D4, White)} 285 | pos = board.GetKingPosition(White) 286 | if pos != D4 { 287 | t.Errorf("Error! Board.GetKingPosition function did not return D4 for white on random board setup!") 288 | } 289 | 290 | board = &Board{} 291 | board.Squares[D5] = Square{true, NewKing(D5, Black)} 292 | pos = board.GetKingPosition(Black) 293 | if pos != D5 { 294 | t.Errorf("Error! Board.GetKingPosition function did not return D5 for black on random board setup!") 295 | } 296 | } 297 | 298 | func TestBoardGetAllMoves(t *testing.T) { 299 | board := NewBoard() 300 | moves := board.GetMoves(White) 301 | if len(moves) != 20 { 302 | t.Errorf("Error! Board.GetMoves for white function did not return 20 moves on initial board setup!") 303 | } 304 | 305 | moves = board.GetMoves(Black) 306 | if len(moves) != 20 { 307 | t.Errorf("Error! Board.GetMoves for black function did not return 20 moves on initial board setup!") 308 | } 309 | } 310 | 311 | func TestBoardKingInCheck(t *testing.T) { 312 | board := &Board{} 313 | board.Squares[D4] = Square{true, NewKing(D4, White)} 314 | board.Squares[D3] = Square{true, NewQueen(D3, Black)} 315 | 316 | if !board.KingInCheck(White) { 317 | t.Errorf("Error! Board.KingInCheck for white returned false when king IS in check!") 318 | } 319 | } 320 | 321 | func TestBoardKingNotInCheck(t *testing.T) { 322 | board := &Board{} 323 | board.Squares[D4] = Square{true, NewKing(D4, White)} 324 | 325 | if board.KingInCheck(White) { 326 | t.Errorf("Error! Board.KingInCheck for white returned true when king IS NOT in check!") 327 | } 328 | } 329 | 330 | func TestBoardValidMovePiece(t *testing.T) { 331 | board := &Board{} 332 | board.Squares[D4] = Square{true, NewKing(D4, White)} 333 | move := NewMove(D4, D5) 334 | res := board.MovePiece(move) 335 | if !res.Result { 336 | t.Errorf("Error! Board.MovePiece returned error for valid move!") 337 | } 338 | } 339 | 340 | func TestBoardInvalidMovePiece(t *testing.T) { 341 | board := &Board{} 342 | board.Squares[D4] = Square{true, NewKing(D4, White)} 343 | board.Squares[D5] = Square{true, NewPawn(D5, White)} 344 | move := NewMove(D4, D5) 345 | res := board.MovePiece(move) 346 | if res.Result { 347 | t.Errorf("Error! Board.MovePiece returned valid for invalid move!") 348 | } 349 | 350 | move = NewMove(A1, A2) 351 | res = board.MovePiece(move) 352 | if res.Result { 353 | t.Errorf("Error! Board.MovePiece returned valid for invalid move!") 354 | } 355 | } 356 | 357 | func TestBoardCheckMate(t *testing.T) { 358 | board := &Board{} 359 | board.Squares[E4] = Square{true, NewKing(E4, White)} 360 | if board.CheckMate(White) { 361 | t.Errorf("Error! Board.CheckMate for white returned true when king IS NOT in checkmate!") 362 | } 363 | 364 | board = &Board{} 365 | board.Squares[A1] = Square{true, NewKing(A1, White)} 366 | board.Squares[A3] = Square{true, NewQueen(A3, Black)} 367 | if board.CheckMate(White) { 368 | t.Errorf("Error! Board.CheckMate for white returned true when king IS NOT in checkmate!") 369 | } 370 | 371 | board = &Board{} 372 | board.Squares[A1] = Square{true, NewKing(A1, White)} 373 | board.Squares[A3] = Square{true, NewQueen(A3, Black)} 374 | board.Squares[B3] = Square{true, NewQueen(B3, Black)} 375 | if !board.CheckMate(White) { 376 | t.Errorf("Error! Board.CheckMate for white returned false when king IS in checkmate!") 377 | } 378 | } 379 | 380 | func TestBoardKingCastle(t *testing.T) { 381 | board := NewBoard() 382 | board.Squares[F1].Occupied = false 383 | board.Squares[G1].Occupied = false 384 | move := &Move{E1, G1, false, true, false} 385 | res := board.MovePiece(move) 386 | if !res.Result { 387 | t.Errorf("Error! Board.MovePiece for white castling move returned failure!") 388 | } 389 | if !board.Squares[F1].Occupied || board.Squares[F1].CurrPiece.GetType() != ROOK { 390 | t.Errorf("Error! Board.MovePiece for white castling missing rook at F1 after move!") 391 | } 392 | if !board.Squares[G1].Occupied || board.Squares[G1].CurrPiece.GetType() != KING { 393 | t.Errorf("Error! Board.MovePiece for white castling missing king at G1 after move!") 394 | } 395 | 396 | board = NewBoard() 397 | board.Squares[B1].Occupied = false 398 | board.Squares[C1].Occupied = false 399 | board.Squares[D1].Occupied = false 400 | move = &Move{E1, C1, false, true, false} 401 | res = board.MovePiece(move) 402 | if !res.Result { 403 | t.Errorf("Error! Board.MovePiece for white castling move returned failure!") 404 | } 405 | if !board.Squares[D1].Occupied || board.Squares[D1].CurrPiece.GetType() != ROOK { 406 | t.Errorf("Error! Board.MovePiece for white castling missing rook at D1 after move!") 407 | } 408 | if !board.Squares[C1].Occupied || board.Squares[C1].CurrPiece.GetType() != KING { 409 | t.Errorf("Error! Board.MovePiece for white castling missing king at C1 after move!") 410 | } 411 | 412 | board = NewBoard() 413 | board.Squares[B8].Occupied = false 414 | board.Squares[C8].Occupied = false 415 | board.Squares[D8].Occupied = false 416 | move = &Move{E8, C8, false, true, false} 417 | res = board.MovePiece(move) 418 | if !res.Result { 419 | t.Errorf("Error! Board.MovePiece for black castling move returned failure!") 420 | } 421 | if !board.Squares[D8].Occupied || board.Squares[D8].CurrPiece.GetType() != ROOK { 422 | t.Errorf("Error! Board.MovePiece for black castling missing rook at D8 after move!") 423 | } 424 | if !board.Squares[C8].Occupied || board.Squares[C8].CurrPiece.GetType() != KING { 425 | t.Errorf("Error! Board.MovePiece for black castling missing king at C8 after move!") 426 | } 427 | 428 | board = NewBoard() 429 | board.Squares[F8].Occupied = false 430 | board.Squares[G8].Occupied = false 431 | move = &Move{E8, G8, false, true, false} 432 | res = board.MovePiece(move) 433 | if !res.Result { 434 | t.Errorf("Error! Board.MovePiece for black castling move returned failure!") 435 | } 436 | if !board.Squares[F8].Occupied || board.Squares[F8].CurrPiece.GetType() != ROOK { 437 | t.Errorf("Error! Board.MovePiece for black castling missing rook at F8 after move!") 438 | } 439 | if !board.Squares[G8].Occupied || board.Squares[G8].CurrPiece.GetType() != KING { 440 | t.Errorf("Error! Board.MovePiece for black castling missing king at G8 after move!") 441 | } 442 | } 443 | -------------------------------------------------------------------------------- /chess_engine/chess_piece.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | type ChessPiece interface { 4 | GetMoves(board *Board) []*Move 5 | GetCurrPosition() BoardPosition 6 | GetPrevPosition() BoardPosition 7 | GetColor() PieceColor 8 | GetType() PieceType 9 | GetMoveCount() int 10 | Move(position BoardPosition) 11 | GetValue() int 12 | Copy() ChessPiece 13 | } 14 | -------------------------------------------------------------------------------- /chess_engine/console_interface.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | import ( 4 | "fmt" 5 | "github.com/fatih/color" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | type ConsoleInterface struct { 11 | CurrGame *Game 12 | } 13 | 14 | func NewConsoleInterface() *ConsoleInterface { 15 | iface := &ConsoleInterface{} 16 | return iface 17 | } 18 | 19 | func (iface *ConsoleInterface) Start() { 20 | // Main loop 21 | for { 22 | iface.DisplayMainMenu() 23 | iface.ProcessMainUserCmd() 24 | } 25 | } 26 | 27 | func (iface *ConsoleInterface) DisplayMainMenu() { 28 | fmt.Print("1) Start New Game\n") 29 | fmt.Print("2) Exit\n") 30 | fmt.Print("Selection: ") 31 | } 32 | 33 | func (iface *ConsoleInterface) ProcessMainUserCmd() { 34 | var value int 35 | n, err := fmt.Scanln(&value) 36 | if n != 1 { 37 | fmt.Printf("Error reading selection (%s). Please try again.", err) 38 | return 39 | } 40 | if value == 1 { 41 | iface.RunGame() 42 | } else { 43 | os.Exit(0) 44 | } 45 | } 46 | 47 | func (iface *ConsoleInterface) RunGame() { 48 | fmt.Print("What color is the user?\n") 49 | fmt.Print("1) White\n") 50 | fmt.Print("2) Black\n") 51 | fmt.Print("Selection: ") 52 | var value int 53 | n, err := fmt.Scanln(&value) 54 | if n != 1 { 55 | fmt.Printf("Error scanning selection (%s). Please try again.\n", err) 56 | return 57 | } 58 | if value == 1 { 59 | iface.CurrGame = NewGame(White) 60 | } else { 61 | iface.CurrGame = NewGame(Black) 62 | } 63 | 64 | for { 65 | if iface.CurrGame.Status == GAME_STATUS_USER_MOVE { 66 | var startStr, endStr string 67 | fmt.Print("Please enter your move in the format 'Start Square End Square (i.e. A1 A3): ") 68 | fmt.Scanln(&startStr, &endStr) 69 | move := GetMoveFromUserString(startStr, endStr) 70 | 71 | if move == nil { 72 | fmt.Print("Invalid move. Please try again.") 73 | } else { 74 | if iface.CurrGame.Board.Squares[move.StartPos].CurrPiece != nil && 75 | iface.CurrGame.Board.Squares[move.StartPos].CurrPiece.GetType() == PAWN { 76 | _, endFile := GetRankFile(move.EndPos) 77 | if (iface.CurrGame.UserColor == White && endFile == 7) || 78 | (iface.CurrGame.UserColor == Black && endFile == 0) { 79 | fmt.Print("Pawn Promotion! Please enter 1)Rook 2)Knight 3)Bishop 4)Queen: ") 80 | var promotion string 81 | fmt.Scanln(&promotion) 82 | promotion = strings.TrimSpace(promotion) 83 | if promotion == "1" { 84 | iface.CurrGame.Board.Squares[move.StartPos].CurrPiece = NewRook(move.StartPos, iface.CurrGame.UserColor) 85 | } else if promotion == "2" { 86 | iface.CurrGame.Board.Squares[move.StartPos].CurrPiece = NewKnight(move.StartPos, iface.CurrGame.UserColor) 87 | } else if promotion == "3" { 88 | iface.CurrGame.Board.Squares[move.StartPos].CurrPiece = NewBishop(move.StartPos, iface.CurrGame.UserColor) 89 | } else if promotion == "4" { 90 | iface.CurrGame.Board.Squares[move.StartPos].CurrPiece = NewQueen(move.StartPos, iface.CurrGame.UserColor) 91 | } 92 | } 93 | 94 | } 95 | res := iface.CurrGame.UserMove(move) 96 | if res.Result { 97 | iface.PrintBoard() 98 | } else { 99 | fmt.Printf("Inavlid move. %s", res.ResStr) 100 | } 101 | } 102 | } else if iface.CurrGame.Status == GAME_STATUS_ENGINE_MOVE { 103 | res, move := iface.CurrGame.EngineMove() 104 | if res.Result { 105 | iface.PrintEngineMove(move) 106 | iface.PrintBoard() 107 | } else { 108 | fmt.Printf("Error getting engine move: %s", res.ResStr) 109 | } 110 | 111 | } else if iface.CurrGame.Status == GAME_STATUS_ENGINE_CHECKMATE { 112 | fmt.Print("You have checkmated me! You Win! Game Over\n") 113 | return 114 | } else { 115 | fmt.Print("I have checkmated you! You Lose! Game Over\n") 116 | return 117 | } 118 | } 119 | 120 | } 121 | 122 | func (iface *ConsoleInterface) PrintEngineMove(move *Move) { 123 | fmt.Printf("Engine Move: %s to %s\n", GetPositionString(move.StartPos), GetPositionString(move.EndPos)) 124 | } 125 | 126 | func (iface *ConsoleInterface) PrintBoard() { 127 | fmt.Print("\n") 128 | iface.PrintRow(7) 129 | iface.PrintRow(6) 130 | iface.PrintRow(5) 131 | iface.PrintRow(4) 132 | iface.PrintRow(3) 133 | iface.PrintRow(2) 134 | iface.PrintRow(1) 135 | iface.PrintRow(0) 136 | pen := color.New(color.FgHiYellow).Add(color.BgHiBlack).Add(color.Bold) 137 | pen.Printf("\tA\tB\tC\tD\tE\tF\tG\tH\n") 138 | 139 | } 140 | 141 | func (iface *ConsoleInterface) PrintRow(rank int) { 142 | pen := color.New(color.FgHiYellow).Add(color.BgHiBlack).Add(color.Bold) 143 | 144 | pen.Printf("%d\t", rank+1) 145 | iface.PrintBoardPos(rank, 0) 146 | iface.PrintBoardPos(rank, 1) 147 | iface.PrintBoardPos(rank, 2) 148 | iface.PrintBoardPos(rank, 3) 149 | iface.PrintBoardPos(rank, 4) 150 | iface.PrintBoardPos(rank, 5) 151 | iface.PrintBoardPos(rank, 6) 152 | iface.PrintBoardPos(rank, 7) 153 | fmt.Print("\n") 154 | } 155 | 156 | func (iface *ConsoleInterface) PrintBoardPos(rank int, file int) { 157 | pos := GetBoardPos(rank, file) 158 | str := " " 159 | fgColor := color.FgHiWhite 160 | 161 | if iface.CurrGame.Board.Squares[pos].Occupied { 162 | if iface.CurrGame.Board.Squares[pos].CurrPiece.GetType() == BISHOP { 163 | str = "B" 164 | } else if iface.CurrGame.Board.Squares[pos].CurrPiece.GetType() == KING { 165 | str = "K" 166 | } else if iface.CurrGame.Board.Squares[pos].CurrPiece.GetType() == QUEEN { 167 | str = "Q" 168 | } else if iface.CurrGame.Board.Squares[pos].CurrPiece.GetType() == ROOK { 169 | str = "R" 170 | } else if iface.CurrGame.Board.Squares[pos].CurrPiece.GetType() == KNIGHT { 171 | str = "N" 172 | } else if iface.CurrGame.Board.Squares[pos].CurrPiece.GetType() == PAWN { 173 | str = "P" 174 | } 175 | 176 | if iface.CurrGame.Board.Squares[pos].CurrPiece.GetColor() == Black { 177 | fgColor = color.FgHiMagenta 178 | } 179 | } 180 | 181 | bgColor := color.BgHiGreen 182 | if (rank+1)%2 == 0 { 183 | if (file+1)%2 != 0 { 184 | bgColor = color.BgHiCyan 185 | } 186 | } else if (file+1)%2 == 0 { 187 | bgColor = color.BgHiCyan 188 | } 189 | 190 | pen := color.New(fgColor).Add(bgColor).Add(color.Bold) 191 | pen.Printf("%s\t", str) 192 | } 193 | -------------------------------------------------------------------------------- /chess_engine/game.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | type GameStatus uint8 4 | 5 | const ( 6 | GAME_STATUS_USER_MOVE = 0 7 | GAME_STATUS_ENGINE_MOVE = 1 8 | GAME_STATUS_USER_CHECKMATE = 2 9 | GAME_STATUS_ENGINE_CHECKMATE = 3 10 | GAME_STATUS_STALEMATE = 4 11 | GAME_STATUS_DRAW = 5 12 | ) 13 | 14 | type Game struct { 15 | Board *Board 16 | Moves []*Move 17 | UserColor PieceColor 18 | EngineColor PieceColor 19 | Status GameStatus 20 | Picker MovePicker 21 | } 22 | 23 | func NewGame(userColor PieceColor) *Game { 24 | g := &Game{} 25 | g.Board = NewBoard() 26 | g.UserColor = userColor 27 | g.EngineColor = OppositeColor(userColor) 28 | if userColor == White { 29 | g.Status = GAME_STATUS_USER_MOVE 30 | } else { 31 | g.Status = GAME_STATUS_ENGINE_MOVE 32 | } 33 | g.Picker = NewBasicMovePicker(g.EngineColor) 34 | return g 35 | } 36 | 37 | func (game *Game) CheckMate() bool { 38 | return false 39 | } 40 | 41 | func (game *Game) UserMove(move *Move) Result { 42 | retVal := Result{false, INVALID_MOVE, ""} 43 | 44 | if game.Status == GAME_STATUS_USER_CHECKMATE { 45 | retVal = Result{false, USER_CHECK_MATE, "User is in checkmate."} 46 | } else if game.Status == GAME_STATUS_ENGINE_CHECKMATE { 47 | retVal = Result{false, ENGINE_CHECK_MATE, "Engine is in checkmate."} 48 | } else if game.Status != GAME_STATUS_USER_MOVE { 49 | retVal = Result{false, NOT_USER_MOVE, "Not user move"} 50 | } else if game.Board.Squares[move.StartPos].Occupied && 51 | game.Board.Squares[move.StartPos].CurrPiece.GetColor() == game.UserColor { 52 | userMoves := game.Board.GetMoves(game.UserColor) 53 | if MovesContainMove(*move, userMoves) { 54 | move.Castle = game.UserMoveIsCastle(move) 55 | tempBoard := game.Board.CopyBoard() 56 | tempRes := tempBoard.MovePiece(move) 57 | if tempRes.Result { 58 | if !tempBoard.KingInCheck(game.UserColor) { 59 | retVal = game.Board.MovePiece(move) 60 | if retVal.Result { 61 | game.Moves = append(game.Moves, move) 62 | game.UpdateGameStatus() 63 | } else { 64 | retVal.Result = false 65 | } 66 | 67 | } else { 68 | retVal = Result{false, INVALID_MOVE, "Invalid move. Would put King in check."} 69 | } 70 | } else { 71 | retVal = tempRes 72 | } 73 | } else { 74 | retVal = Result{false, INVALID_MOVE, "Invalid move"} 75 | } 76 | } 77 | return retVal 78 | } 79 | 80 | func (game *Game) EngineMove() (Result, *Move) { 81 | retVal := Result{false, INVALID_MOVE, ""} 82 | var retMove *Move = nil 83 | retVal, retMove = game.Picker.GetNextMove(game) 84 | if retVal.Result { 85 | retVal = game.Board.MovePiece(retMove) 86 | if retVal.Result { 87 | game.UpdateGameStatus() 88 | retVal = Result{true, NO_ERROR, ""} 89 | } 90 | } 91 | 92 | return retVal, retMove 93 | } 94 | 95 | func (game *Game) UpdateGameStatus() { 96 | if game.Board.CheckMate(game.UserColor) { 97 | game.Status = GAME_STATUS_USER_CHECKMATE 98 | } else if game.Board.CheckMate(game.EngineColor) { 99 | game.Status = GAME_STATUS_ENGINE_CHECKMATE 100 | } else if game.Board.IsStalemate() { 101 | game.Status = GAME_STATUS_STALEMATE 102 | } else if game.Board.IsDraw() { 103 | game.Status = GAME_STATUS_DRAW 104 | } else if game.Status == GAME_STATUS_USER_MOVE { 105 | game.Status = GAME_STATUS_ENGINE_MOVE 106 | } else { 107 | game.Status = GAME_STATUS_USER_MOVE 108 | } 109 | game.Board.KingInCheck(White) 110 | game.Board.KingInCheck(Black) 111 | } 112 | 113 | func (game *Game) UserMoveIsCastle(move *Move) bool { 114 | retVal := false 115 | if (move.StartPos == E1 && (move.EndPos == G1 || move.EndPos == C1)) || 116 | (move.StartPos == E8 && (move.EndPos == G8 || move.EndPos == C8)) { 117 | retVal = true 118 | } 119 | return retVal 120 | } 121 | 122 | func (game *Game) IsOver() bool { 123 | retVal := true 124 | if game.Status != GAME_STATUS_ENGINE_CHECKMATE && game.Status != GAME_STATUS_USER_CHECKMATE && 125 | game.Status != GAME_STATUS_STALEMATE && game.Status != GAME_STATUS_DRAW { 126 | retVal = false 127 | } 128 | 129 | return retVal 130 | } 131 | -------------------------------------------------------------------------------- /chess_engine/game_test.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | import "testing" 4 | 5 | func TestNewGame(t *testing.T) { 6 | g := NewGame(White) 7 | if g == nil { 8 | t.Errorf("Error! NewGame function returned nil for game!") 9 | } 10 | 11 | if g.Board == nil { 12 | t.Errorf("Error! NewGame function returned nil for board!") 13 | } 14 | 15 | if g.UserColor != White { 16 | t.Errorf("Error! NewGame function returned black for user color!") 17 | } 18 | } 19 | 20 | func TestGameUserMove(t *testing.T) { 21 | g := NewGame(Black) 22 | res := g.UserMove(NewMove(A7, A6)) 23 | if res.Result { 24 | t.Errorf("Error! Game.UserMove function returned success for invalid user move (wrong color moving)!") 25 | } 26 | 27 | g = NewGame(White) 28 | res = g.UserMove(NewMove(A2, A3)) 29 | if !res.Result { 30 | t.Errorf("Error! Game.UserMove function returned failure for valid user move!") 31 | } 32 | if len(g.Moves) != 1 { 33 | t.Errorf("Error! Game.Moves array length not 1 after making first user move!!!") 34 | } 35 | 36 | g = NewGame(White) 37 | res = g.UserMove(NewMove(A2, A8)) 38 | if res.Result { 39 | t.Errorf("Error! Game.UserMove function returned success for invalid user move!") 40 | } 41 | 42 | g = &Game{} 43 | g.Board = &Board{} 44 | g.EngineColor = White 45 | g.UserColor = Black 46 | g.Status = GAME_STATUS_USER_MOVE 47 | g.Board.Squares[A1] = Square{true, NewKing(A1, White)} 48 | g.Board.Squares[A3] = Square{true, NewQueen(A3, Black)} 49 | g.Board.Squares[C3] = Square{true, NewQueen(C3, Black)} 50 | 51 | g.UserMove(NewMove(C3, B3)) 52 | if g.Status != GAME_STATUS_ENGINE_CHECKMATE { 53 | t.Errorf("Error! Game.UserMove function did not set status to Engine Check Mate!") 54 | } 55 | 56 | } 57 | 58 | func TestGameUpdateGameStatus(t *testing.T) { 59 | g := &Game{} 60 | g.Board = &Board{} 61 | g.EngineColor = White 62 | g.UserColor = Black 63 | g.Board.Squares[A1] = Square{true, NewKing(A1, White)} 64 | g.Board.Squares[A3] = Square{true, NewQueen(A3, Black)} 65 | g.Board.Squares[B3] = Square{true, NewQueen(B3, Black)} 66 | g.UpdateGameStatus() 67 | if g.Status != GAME_STATUS_ENGINE_CHECKMATE { 68 | t.Errorf("Error! Game.UpdateGameStatus function did not set status to Engine Check Mate!") 69 | } 70 | 71 | g.EngineColor = Black 72 | g.UserColor = White 73 | g.UpdateGameStatus() 74 | if g.Status != GAME_STATUS_USER_CHECKMATE { 75 | t.Errorf("Error! Game.UpdateGameStatus function did not set status to User Check Mate!") 76 | } 77 | 78 | g = &Game{} 79 | g.Board = &Board{} 80 | g.Status = GAME_STATUS_USER_MOVE 81 | g.EngineColor = White 82 | g.UserColor = Black 83 | g.Board.Squares[A1] = Square{true, NewKing(A1, White)} 84 | g.Board.Squares[A3] = Square{true, NewQueen(A3, Black)} 85 | g.UpdateGameStatus() 86 | if g.Status != GAME_STATUS_ENGINE_MOVE { 87 | t.Errorf("Error! Game.UpdateGameStatus function did not set status to Engine Move!") 88 | } 89 | 90 | g.UpdateGameStatus() 91 | if g.Status != GAME_STATUS_USER_MOVE { 92 | t.Errorf("Error! Game.UpdateGameStatus function did not set status to User Move!") 93 | } 94 | } 95 | 96 | func TestGameEngineMoveOneValidMove(t *testing.T) { 97 | g := &Game{} 98 | g.Board = &Board{} 99 | g.Status = GAME_STATUS_ENGINE_MOVE 100 | g.EngineColor = White 101 | g.UserColor = Black 102 | g.Picker = NewBasicMovePicker(g.EngineColor) 103 | g.Board.Squares[A1] = Square{true, NewKing(A1, White)} 104 | g.Board.Squares[A3] = Square{true, NewQueen(A3, Black)} 105 | res, move := g.EngineMove() 106 | 107 | if !res.Result { 108 | t.Errorf("Error! Game.EngineMove did not successfully make an Engine Move!") 109 | } else { 110 | if move.StartPos != A1 && move.EndPos != B1 { 111 | t.Errorf("Error! Game.EngineMove did not move to the only valid square! (invalid move positions)") 112 | } 113 | if g.Board.Squares[A1].Occupied { 114 | t.Errorf("Error! Game.EngineMove did not move to the only valid square! (A1 still occupied)") 115 | } 116 | if !g.Board.Squares[B1].Occupied || g.Board.Squares[B1].CurrPiece == nil { 117 | t.Errorf("Error! Game.EngineMove did not move to the only valid square! (B1 not occupied)") 118 | } else { 119 | if g.Board.Squares[B1].CurrPiece.GetType() != KING && g.Board.Squares[B1].CurrPiece.GetColor() != Black { 120 | t.Errorf("Error! Game.EngineMove did not move to the only valid square (invalid type and piece)!") 121 | } 122 | } 123 | if g.Status != GAME_STATUS_USER_MOVE { 124 | t.Errorf("Error! Game.UpdateGameStatus function did not set status to Engine Move!") 125 | } 126 | } 127 | } 128 | 129 | func TestGameEngineMoveFirstMoveValid(t *testing.T) { 130 | game := NewGame(Black) 131 | validMoves := game.Board.GetMoves(White) 132 | res, move := game.EngineMove() 133 | if !res.Result { 134 | t.Errorf("Error! Game.EngineMove returned failure on new game first move!") 135 | } 136 | if move == nil { 137 | t.Errorf("Error! Game.EngineMove return nil for move on new game first move!") 138 | } else { 139 | if !MovesContainMove(*move, validMoves) { 140 | t.Errorf("Error! Game.EngineMove return invalid move on new game first move!") 141 | } 142 | } 143 | if game.Status != GAME_STATUS_USER_MOVE { 144 | t.Errorf("Error! Game.UpdateGameStatus function did not set status to Engine Move!") 145 | } 146 | } 147 | 148 | func TestGameEngineMovePicksCheckMate(t *testing.T) { 149 | g := &Game{} 150 | g.Board = &Board{} 151 | g.Status = GAME_STATUS_ENGINE_MOVE 152 | g.EngineColor = Black 153 | g.UserColor = White 154 | g.Picker = NewBasicMovePicker(g.EngineColor) 155 | g.Board.Squares[A1] = Square{true, NewKing(A1, White)} 156 | g.Board.Squares[A3] = Square{true, NewQueen(A3, Black)} 157 | g.Board.Squares[C4] = Square{true, NewKnight(C4, Black)} 158 | res, move := g.EngineMove() 159 | 160 | if !res.Result { 161 | t.Errorf("Error! Game.EngineMove did not successfully make an Engine Move!") 162 | } else { 163 | if move.StartPos != A3 && move.EndPos != B2 { 164 | t.Errorf("Error! Game.EngineMove did not move to the only CheckMate square!") 165 | } 166 | if g.Status != GAME_STATUS_USER_CHECKMATE { 167 | t.Errorf("Error! Game.EngineMove did not update game.Status to GAME_STATUS_USER_CHECKMATE!") 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /chess_engine/interfaces.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | -------------------------------------------------------------------------------- /chess_engine/king.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | type King struct { 4 | Type PieceType 5 | Color PieceColor 6 | CurrPosition BoardPosition 7 | PrevPosition BoardPosition 8 | MoveCount int 9 | GenCastle bool 10 | } 11 | 12 | func NewKing(pos BoardPosition, color PieceColor) *King { 13 | king := &King{KING, color, pos, 14 | INVALID_BOARD_POSITION, 0, true} 15 | return king 16 | } 17 | 18 | func (king *King) Copy() ChessPiece { 19 | copyKing := &King{} 20 | copyKing.Type = king.Type 21 | copyKing.Color = king.Color 22 | copyKing.CurrPosition = king.CurrPosition 23 | copyKing.PrevPosition = king.PrevPosition 24 | copyKing.MoveCount = king.MoveCount 25 | return copyKing 26 | } 27 | 28 | func (king *King) GetMoveCount() int { 29 | return king.MoveCount 30 | } 31 | 32 | func (king *King) GetMoves(board *Board) []*Move { 33 | moves := []*Move{} 34 | rank, file := GetRankFile(king.CurrPosition) 35 | moves = king.CheckSquare(board, moves, rank+1, file+1) 36 | moves = king.CheckSquare(board, moves, rank+1, file) 37 | moves = king.CheckSquare(board, moves, rank+1, file-1) 38 | moves = king.CheckSquare(board, moves, rank, file-1) 39 | moves = king.CheckSquare(board, moves, rank-1, file-1) 40 | moves = king.CheckSquare(board, moves, rank-1, file) 41 | moves = king.CheckSquare(board, moves, rank-1, file+1) 42 | moves = king.CheckSquare(board, moves, rank, file+1) 43 | moves = king.GetCastleMoves(board, moves) 44 | return moves 45 | } 46 | 47 | func (king *King) GetAttackMoves(board *Board) []*Move { 48 | moves := []*Move{} 49 | rank, file := GetRankFile(king.CurrPosition) 50 | moves = king.CheckSquare(board, moves, rank+1, file+1) 51 | moves = king.CheckSquare(board, moves, rank+1, file) 52 | moves = king.CheckSquare(board, moves, rank+1, file-1) 53 | moves = king.CheckSquare(board, moves, rank, file-1) 54 | moves = king.CheckSquare(board, moves, rank-1, file-1) 55 | moves = king.CheckSquare(board, moves, rank-1, file) 56 | moves = king.CheckSquare(board, moves, rank-1, file+1) 57 | moves = king.CheckSquare(board, moves, rank, file+1) 58 | return moves 59 | } 60 | 61 | func (king *King) CheckSquare(board *Board, moves []*Move, rank int, file int) []*Move { 62 | if (rank >= 0 && rank < 8) && (file >= 0 && file < 8) { 63 | currPos := GetBoardPos(rank, file) 64 | if board.Squares[currPos].Occupied { 65 | if board.Squares[currPos].CurrPiece.GetColor() != king.Color { 66 | moves = append(moves, NewMove(king.CurrPosition, currPos)) 67 | } 68 | } else { 69 | moves = append(moves, NewMove(king.CurrPosition, currPos)) 70 | } 71 | } 72 | 73 | return moves 74 | } 75 | 76 | func (king *King) GetCurrPosition() BoardPosition { 77 | return king.CurrPosition 78 | } 79 | 80 | func (king *King) GetPrevPosition() BoardPosition { 81 | return king.PrevPosition 82 | } 83 | 84 | func (king *King) GetColor() PieceColor { 85 | return king.Color 86 | } 87 | 88 | func (king *King) GetType() PieceType { 89 | return king.Type 90 | } 91 | 92 | func (king *King) Move(newPos BoardPosition) { 93 | king.CurrPosition = newPos 94 | king.MoveCount++ 95 | } 96 | 97 | func (king *King) GetValue() int { 98 | return 0 99 | } 100 | 101 | func (king *King) GetCastleMoves(board *Board, moves []*Move) []*Move { 102 | king.GenCastle = false 103 | if king.CurrPosition == E1 && king.Color == White && king.MoveCount == 0 && 104 | !board.Squares[F1].Occupied && !board.Squares[G1].Occupied && !board.WhiteInCheck && 105 | board.Squares[H1].Occupied && board.Squares[H1].CurrPiece != nil && 106 | board.Squares[H1].CurrPiece.GetColor() == White && board.Squares[H1].CurrPiece.GetType() == ROOK { 107 | opMoves := board.GetAttackMoves(Black) 108 | 109 | if !MovesContainEndPos(F1, opMoves) && !MovesContainEndPos(G1, opMoves) { 110 | moves = append(moves, NewCastleMove(king.CurrPosition, G1)) 111 | } 112 | } else if king.CurrPosition == E1 && king.Color == White && king.MoveCount == 0 && 113 | !board.Squares[D1].Occupied && !board.Squares[C1].Occupied && 114 | !board.WhiteInCheck && board.Squares[A1].Occupied && board.Squares[A1].CurrPiece != nil && 115 | board.Squares[A1].CurrPiece.GetColor() == White && board.Squares[A1].CurrPiece.GetType() == ROOK { 116 | opMoves := board.GetAttackMoves(Black) 117 | if !MovesContainEndPos(D1, opMoves) && !MovesContainEndPos(C1, opMoves) { 118 | moves = append(moves, NewCastleMove(king.CurrPosition, C1)) 119 | } 120 | } else if king.CurrPosition == E8 && king.Color == Black && king.MoveCount == 0 && 121 | !board.Squares[F8].Occupied && !board.Squares[G8].Occupied && !board.BlackInCheck && 122 | board.Squares[H8].Occupied && board.Squares[H8].CurrPiece != nil && 123 | board.Squares[H8].CurrPiece.GetColor() == Black && board.Squares[H8].CurrPiece.GetType() == ROOK { 124 | opMoves := board.GetAttackMoves(White) 125 | 126 | if !MovesContainEndPos(F8, opMoves) && !MovesContainEndPos(G8, opMoves) { 127 | moves = append(moves, NewCastleMove(king.CurrPosition, G8)) 128 | } 129 | } else if king.CurrPosition == E8 && king.Color == Black && king.MoveCount == 0 && 130 | !board.Squares[D8].Occupied && !board.Squares[C8].Occupied && board.Squares[A8].Occupied && 131 | board.Squares[A8].CurrPiece != nil && board.Squares[A8].CurrPiece.GetColor() == Black && 132 | board.Squares[A8].CurrPiece.GetType() == ROOK && 133 | !board.BlackInCheck { 134 | opMoves := board.GetAttackMoves(White) 135 | if !MovesContainEndPos(D8, opMoves) && !MovesContainEndPos(C8, opMoves) { 136 | moves = append(moves, NewCastleMove(king.CurrPosition, C8)) 137 | } 138 | } 139 | king.GenCastle = true 140 | return moves 141 | } 142 | -------------------------------------------------------------------------------- /chess_engine/king_test.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | import "testing" 4 | 5 | func TestNewKing(t *testing.T) { 6 | king := NewKing(C1, Black) 7 | 8 | if king == nil { 9 | t.Errorf("Error! NewKing function returned nil!") 10 | } 11 | 12 | if king.Type != KING { 13 | t.Errorf("Error! NewKing function returned wrong Type!") 14 | } 15 | 16 | if king.CurrPosition != C1 { 17 | t.Errorf("Error! NewKing function returned wrong CurrPosition!") 18 | } 19 | 20 | if king.Color != Black { 21 | t.Errorf("Error! NewKing function returned wrong Color!") 22 | } 23 | 24 | if king.GetType() != KING { 25 | t.Errorf("Error! NewKing function returned wrong Type!") 26 | } 27 | 28 | if king.GetCurrPosition() != C1 { 29 | t.Errorf("Error! NewKing function returned wrong CurrPosition!") 30 | } 31 | 32 | if king.GetColor() != Black { 33 | t.Errorf("Error! NewKing function returned wrong Color!") 34 | } 35 | } 36 | 37 | func TestKingMove(t *testing.T) { 38 | king := NewKing(C1, Black) 39 | king.Move(C2) 40 | if king.GetCurrPosition() != C2 { 41 | t.Errorf("Error! King.Move function returned wrong CurrPosition!") 42 | } 43 | if king.MoveCount != 1 { 44 | t.Errorf("Error! King.MoveCount wrong after move!") 45 | } 46 | } 47 | 48 | func TestKingAllMoves(t *testing.T) { 49 | board := &Board{} 50 | // Test white square Bishop moving from center square to outer corners 51 | board.Squares[E4] = Square{true, NewKing(E4, Black)} 52 | 53 | moves := board.Squares[E4].CurrPiece.GetMoves(board) 54 | if len(moves) != 8 { 55 | t.Errorf("Error! King.GetMoves function did not return 8 moves for king on open center square!") 56 | } 57 | if !MovesContainMove(Move{E4, E5, false, false, false}, moves) && 58 | !MovesContainMove(Move{E4, E3, false, false, false}, moves) && 59 | !MovesContainMove(Move{E4, D4, false, false, false}, moves) && 60 | !MovesContainMove(Move{E4, F4, false, false, false}, moves) && 61 | !MovesContainMove(Move{E4, F5, false, false, false}, moves) && 62 | !MovesContainMove(Move{E4, F3, false, false, false}, moves) && 63 | !MovesContainMove(Move{E4, D5, false, false, false}, moves) && 64 | !MovesContainMove(Move{E4, D3, false, false, false}, moves) { 65 | t.Errorf("Error! King.GetMoves for king on open center square missing squares!") 66 | } 67 | } 68 | 69 | func TestKingEdgeMoves(t *testing.T) { 70 | board := &Board{} 71 | // Test white square Bishop moving from center square to outer corners 72 | board.Squares[A4] = Square{true, NewKing(A4, Black)} 73 | 74 | moves := board.Squares[A4].CurrPiece.GetMoves(board) 75 | if len(moves) != 5 { 76 | t.Errorf("Error! King.GetMoves function did not return 4 moves for king on open edge square!") 77 | } 78 | if !MovesContainMove(Move{A4, A5, false, false, false}, moves) && 79 | !MovesContainMove(Move{A4, B5, false, false, false}, moves) && 80 | !MovesContainMove(Move{A4, B4, false, false, false}, moves) && 81 | !MovesContainMove(Move{A4, B3, false, false, false}, moves) && 82 | !MovesContainMove(Move{A4, A3, false, false, false}, moves) { 83 | t.Errorf("Error! King.GetMoves for king on open edge square missing squares!") 84 | } 85 | } 86 | 87 | func TestKingNoMoves(t *testing.T) { 88 | board := &Board{} 89 | // Test white square Bishop moving from center square to outer corners 90 | board.Squares[E4] = Square{true, NewKing(E4, White)} 91 | board.Squares[E5] = Square{true, NewPawn(E5, White)} 92 | board.Squares[F5] = Square{true, NewPawn(F5, White)} 93 | board.Squares[F4] = Square{true, NewPawn(F4, White)} 94 | board.Squares[F3] = Square{true, NewPawn(F3, White)} 95 | board.Squares[E3] = Square{true, NewPawn(E3, White)} 96 | board.Squares[D3] = Square{true, NewPawn(D3, White)} 97 | board.Squares[D4] = Square{true, NewPawn(D4, White)} 98 | board.Squares[D5] = Square{true, NewPawn(D5, White)} 99 | moves := board.Squares[E4].CurrPiece.GetMoves(board) 100 | if len(moves) != 0 { 101 | t.Errorf("Error! King.GetMoves function did not return 0 moves for king on totally blocked center square!") 102 | } 103 | } 104 | 105 | func TestKingAttackMoves(t *testing.T) { 106 | board := &Board{} 107 | // Test white square Bishop moving from center square to outer corners 108 | board.Squares[E4] = Square{true, NewKing(E4, White)} 109 | board.Squares[E5] = Square{true, NewPawn(E5, Black)} 110 | board.Squares[F5] = Square{true, NewPawn(F5, Black)} 111 | board.Squares[F4] = Square{true, NewPawn(F4, Black)} 112 | board.Squares[F3] = Square{true, NewPawn(F3, Black)} 113 | board.Squares[E3] = Square{true, NewPawn(E3, Black)} 114 | board.Squares[D3] = Square{true, NewPawn(D3, Black)} 115 | board.Squares[D4] = Square{true, NewPawn(D4, Black)} 116 | board.Squares[D5] = Square{true, NewPawn(D5, Black)} 117 | moves := board.Squares[E4].CurrPiece.GetMoves(board) 118 | if len(moves) != 8 { 119 | t.Errorf("Error! King.GetMoves function did not return 8 moves for king on center square surrounded by opponent pieces!") 120 | } 121 | } 122 | 123 | func TestKingCastle(t *testing.T) { 124 | board := NewBoard() 125 | board.Squares[F1].Occupied = false 126 | board.Squares[G1].Occupied = false 127 | moves := board.Squares[E1].CurrPiece.GetMoves(board) 128 | if len(moves) != 2 { 129 | t.Errorf("Error! King.GetMoves function did not return 2 moves for king in castling position!") 130 | } else if !moves[0].Castle && !moves[1].Castle { 131 | t.Errorf("Error! King.GetMoves did not return a move flagged Castle when in Castle position!") 132 | } 133 | if !MovesContainMove(Move{E1, F1, false, false, false}, moves) && 134 | !MovesContainMove(Move{E1, H1, false, false, false}, moves) { 135 | t.Errorf("Error! King.GetMoves for king on E1 castling square missing squares!") 136 | } 137 | 138 | board = NewBoard() 139 | board.Squares[B1].Occupied = false 140 | board.Squares[C1].Occupied = false 141 | board.Squares[D1].Occupied = false 142 | moves = board.Squares[E1].CurrPiece.GetMoves(board) 143 | if len(moves) != 2 { 144 | t.Errorf("Error! King.GetMoves function did not return 2 moves for king in castling position!") 145 | } else if !moves[0].Castle && !moves[1].Castle { 146 | t.Errorf("Error! King.GetMoves did not return a move flagged Castle when in Castle position!") 147 | } 148 | if !MovesContainMove(Move{E1, D1, false, false, false}, moves) && 149 | !MovesContainMove(Move{E1, C1, false, false, false}, moves) { 150 | t.Errorf("Error! King.GetMoves for king on E1 castling square missing squares!") 151 | } 152 | 153 | board = NewBoard() 154 | board.Squares[B8].Occupied = false 155 | board.Squares[C8].Occupied = false 156 | board.Squares[D8].Occupied = false 157 | moves = board.Squares[E8].CurrPiece.GetMoves(board) 158 | if len(moves) != 2 { 159 | t.Errorf("Error! King.GetMoves function did not return 2 moves for king in castling position!") 160 | } else if !moves[0].Castle && !moves[1].Castle { 161 | t.Errorf("Error! King.GetMoves did not return a move flagged Castle when in Castle position!") 162 | } 163 | if !MovesContainMove(Move{E8, D8, false, false, false}, moves) && 164 | !MovesContainMove(Move{E8, C8, false, false, false}, moves) { 165 | t.Errorf("Error! King.GetMoves for king on E1 castling square missing squares!") 166 | } 167 | 168 | board = NewBoard() 169 | board.Squares[F8].Occupied = false 170 | board.Squares[G8].Occupied = false 171 | moves = board.Squares[E8].CurrPiece.GetMoves(board) 172 | if len(moves) != 2 { 173 | t.Errorf("Error! King.GetMoves function did not return 2 moves for king in castling position!") 174 | } else if !moves[0].Castle && !moves[1].Castle { 175 | t.Errorf("Error! King.GetMoves did not return a move flagged Castle when in Castle position!") 176 | } 177 | if !MovesContainMove(Move{E8, F8, false, false, false}, moves) && 178 | !MovesContainMove(Move{E8, H8, false, false, false}, moves) { 179 | t.Errorf("Error! King.GetMoves for king on E1 castling square missing squares!") 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /chess_engine/knight.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | type Knight struct { 4 | Type PieceType 5 | Color PieceColor 6 | CurrPosition BoardPosition 7 | PrevPosition BoardPosition 8 | MoveCount int 9 | } 10 | 11 | func NewKnight(pos BoardPosition, color PieceColor) *Knight { 12 | knight := &Knight{KNIGHT, color, pos, 13 | INVALID_BOARD_POSITION, 0} 14 | return knight 15 | } 16 | 17 | func (knight *Knight) Copy() ChessPiece { 18 | copyKnight := &Knight{} 19 | copyKnight.Type = knight.Type 20 | copyKnight.Color = knight.Color 21 | copyKnight.CurrPosition = knight.CurrPosition 22 | copyKnight.PrevPosition = knight.PrevPosition 23 | copyKnight.MoveCount = knight.MoveCount 24 | return copyKnight 25 | } 26 | 27 | func (knight *Knight) GetMoveCount() int { 28 | return knight.MoveCount 29 | } 30 | 31 | func (knight *Knight) GetMoves(board *Board) []*Move { 32 | moves := []*Move{} 33 | rank, file := GetRankFile(knight.CurrPosition) 34 | moves = knight.CheckSquare(board, moves, rank+2, file+1) 35 | moves = knight.CheckSquare(board, moves, rank+1, file+2) 36 | moves = knight.CheckSquare(board, moves, rank+2, file-1) 37 | moves = knight.CheckSquare(board, moves, rank+1, file-2) 38 | moves = knight.CheckSquare(board, moves, rank-2, file+1) 39 | moves = knight.CheckSquare(board, moves, rank-1, file+2) 40 | moves = knight.CheckSquare(board, moves, rank-2, file-1) 41 | moves = knight.CheckSquare(board, moves, rank-1, file-2) 42 | return moves 43 | } 44 | 45 | func (knight *Knight) CheckSquare(board *Board, moves []*Move, rank int, file int) []*Move { 46 | if (rank >= 0 && rank < 8) && (file >= 0 && file < 8) { 47 | currPos := GetBoardPos(rank, file) 48 | if board.Squares[currPos].Occupied { 49 | if board.Squares[currPos].CurrPiece.GetColor() != knight.Color { 50 | moves = append(moves, NewMove(knight.CurrPosition, currPos)) 51 | } 52 | } else { 53 | moves = append(moves, NewMove(knight.CurrPosition, currPos)) 54 | } 55 | } 56 | 57 | return moves 58 | } 59 | 60 | func (knight *Knight) GetCurrPosition() BoardPosition { 61 | return knight.CurrPosition 62 | } 63 | 64 | func (knight *Knight) GetPrevPosition() BoardPosition { 65 | return knight.PrevPosition 66 | } 67 | 68 | func (knight *Knight) GetColor() PieceColor { 69 | return knight.Color 70 | } 71 | 72 | func (knight *Knight) GetType() PieceType { 73 | return knight.Type 74 | } 75 | 76 | func (knight *Knight) Move(newPos BoardPosition) { 77 | knight.CurrPosition = newPos 78 | knight.MoveCount++ 79 | } 80 | 81 | func (knight *Knight) GetValue() int { 82 | return 3 83 | } 84 | -------------------------------------------------------------------------------- /chess_engine/knight_test.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | import "testing" 4 | 5 | func TestNewKnight(t *testing.T) { 6 | knight := NewKnight(B1, Black) 7 | 8 | if knight == nil { 9 | t.Errorf("Error! NewKnight function returned nil!") 10 | } 11 | 12 | if knight.Type != KNIGHT { 13 | t.Errorf("Error! NewKnight function returned wrong Type!") 14 | } 15 | 16 | if knight.CurrPosition != B1 { 17 | t.Errorf("Error! NewKnight function returned wrong CurrPosition!") 18 | } 19 | 20 | if knight.Color != Black { 21 | t.Errorf("Error! NewKnight function returned wrong Color!") 22 | } 23 | 24 | if knight.GetType() != KNIGHT { 25 | t.Errorf("Error! NewKnight function returned wrong Type!") 26 | } 27 | 28 | if knight.GetCurrPosition() != B1 { 29 | t.Errorf("Error! NewKnight function returned wrong CurrPosition!") 30 | } 31 | 32 | if knight.GetColor() != Black { 33 | t.Errorf("Error! NewKnight function returned wrong Color!") 34 | } 35 | } 36 | 37 | func TestKnightMove(t *testing.T) { 38 | knight := NewKnight(C1, Black) 39 | knight.Move(C2) 40 | if knight.GetCurrPosition() != C2 { 41 | t.Errorf("Error! Knight.Move function returned wrong CurrPosition!") 42 | } 43 | if knight.MoveCount != 1 { 44 | t.Errorf("Error! Knight.MoveCount wrong after move!") 45 | } 46 | } 47 | 48 | func TestKnightAllMoves(t *testing.T) { 49 | board := &Board{} 50 | // Test white square Bishop moving from center square to outer corners 51 | board.Squares[E4] = Square{true, NewKnight(E4, White)} 52 | 53 | moves := board.Squares[E4].CurrPiece.GetMoves(board) 54 | if len(moves) != 8 { 55 | t.Errorf("Error! Knight.GetMoves function did not return 8 moves for knight on open center square!") 56 | } 57 | if !MovesContainMove(Move{E4, D6, false, false, false}, moves) && 58 | !MovesContainMove(Move{E4, C5, false, false, false}, moves) && 59 | !MovesContainMove(Move{E4, C3, false, false, false}, moves) && 60 | !MovesContainMove(Move{E4, D2, false, false, false}, moves) && 61 | !MovesContainMove(Move{E4, F2, false, false, false}, moves) && 62 | !MovesContainMove(Move{E4, G3, false, false, false}, moves) && 63 | !MovesContainMove(Move{E4, G5, false, false, false}, moves) && 64 | !MovesContainMove(Move{E4, F6, false, false, false}, moves) { 65 | t.Errorf("Error! Knight.GetMoves for knight on open center square missing squares!") 66 | } 67 | } 68 | 69 | func TestKnightNoMoves(t *testing.T) { 70 | board := &Board{} 71 | // Test white square Bishop moving from center square to outer corners 72 | board.Squares[E4] = Square{true, NewKnight(E4, White)} 73 | board.Squares[D6] = Square{true, NewPawn(D6, White)} 74 | board.Squares[C5] = Square{true, NewPawn(C5, White)} 75 | board.Squares[C3] = Square{true, NewPawn(C3, White)} 76 | board.Squares[D2] = Square{true, NewPawn(D2, White)} 77 | board.Squares[F2] = Square{true, NewPawn(F2, White)} 78 | board.Squares[G3] = Square{true, NewPawn(G3, White)} 79 | board.Squares[G5] = Square{true, NewPawn(G5, White)} 80 | board.Squares[F6] = Square{true, NewPawn(F6, White)} 81 | moves := board.Squares[E4].CurrPiece.GetMoves(board) 82 | if len(moves) != 0 { 83 | t.Errorf("Error! Knight.GetMoves function did not return 0 moves for king on totally blocked center square!") 84 | } 85 | } 86 | 87 | func TestKnightAttackMoves(t *testing.T) { 88 | board := &Board{} 89 | // Test white square Bishop moving from center square to outer corners 90 | board.Squares[E4] = Square{true, NewKnight(E4, Black)} 91 | board.Squares[D6] = Square{true, NewPawn(D6, White)} 92 | board.Squares[C5] = Square{true, NewPawn(C5, White)} 93 | board.Squares[C3] = Square{true, NewPawn(C3, White)} 94 | board.Squares[D2] = Square{true, NewPawn(D2, White)} 95 | board.Squares[F2] = Square{true, NewPawn(F2, White)} 96 | board.Squares[G3] = Square{true, NewPawn(G3, White)} 97 | board.Squares[G5] = Square{true, NewPawn(G5, White)} 98 | board.Squares[F6] = Square{true, NewPawn(F6, White)} 99 | moves := board.Squares[E4].CurrPiece.GetMoves(board) 100 | if len(moves) != 8 { 101 | t.Errorf("Error! Knight.GetMoves function did not return 0 moves for king on totally blocked center square!") 102 | } 103 | if !MovesContainMove(Move{E4, D6, false, false, false}, moves) && 104 | !MovesContainMove(Move{E4, C5, false, false, false}, moves) && 105 | !MovesContainMove(Move{E4, C3, false, false, false}, moves) && 106 | !MovesContainMove(Move{E4, D2, false, false, false}, moves) && 107 | !MovesContainMove(Move{E4, F2, false, false, false}, moves) && 108 | !MovesContainMove(Move{E4, G3, false, false, false}, moves) && 109 | !MovesContainMove(Move{E4, G5, false, false, false}, moves) && 110 | !MovesContainMove(Move{E4, F6, false, false, false}, moves) { 111 | t.Errorf("Error! Knight.GetMoves for knight on center square surrounded by opposing pieces missing squares!") 112 | } 113 | } 114 | 115 | func TestKnightEdgeMoves(t *testing.T) { 116 | board := &Board{} 117 | // Test white square Bishop moving from center square to outer corners 118 | board.Squares[A4] = Square{true, NewKnight(A4, White)} 119 | 120 | moves := board.Squares[A4].CurrPiece.GetMoves(board) 121 | if len(moves) != 4 { 122 | t.Errorf("Error! Knight.GetMoves function did not return 4 moves for knight on open center edge square!") 123 | } 124 | if !MovesContainMove(Move{A4, B6, false, false, false}, moves) && 125 | !MovesContainMove(Move{A4, C5, false, false, false}, moves) && 126 | !MovesContainMove(Move{A4, C3, false, false, false}, moves) && 127 | !MovesContainMove(Move{A4, B2, false, false, false}, moves) { 128 | t.Errorf("Error! Knight.GetMoves for knight on open center edge square missing squares!") 129 | } 130 | 131 | board = &Board{} 132 | // Test white square Bishop moving from center square to outer corners 133 | board.Squares[H4] = Square{true, NewKnight(H4, White)} 134 | 135 | moves = board.Squares[H4].CurrPiece.GetMoves(board) 136 | if len(moves) != 4 { 137 | t.Errorf("Error! Knight.GetMoves function did not return 4 moves for knight on open center edge square!") 138 | } 139 | if !MovesContainMove(Move{H4, G6, false, false, false}, moves) && 140 | !MovesContainMove(Move{H4, F5, false, false, false}, moves) && 141 | !MovesContainMove(Move{H4, F3, false, false, false}, moves) && 142 | !MovesContainMove(Move{H4, G2, false, false, false}, moves) { 143 | t.Errorf("Error! Knight.GetMoves for knight on open center edge square missing squares!") 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /chess_engine/move.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | type Move struct { 4 | StartPos BoardPosition 5 | EndPos BoardPosition 6 | EnPassant bool 7 | Castle bool 8 | Promote bool 9 | } 10 | 11 | func NewMove(start BoardPosition, end BoardPosition) *Move { 12 | m := new(Move) 13 | m.StartPos = start 14 | m.EndPos = end 15 | m.EnPassant = false 16 | m.Castle = false 17 | m.Promote = false 18 | return m 19 | } 20 | 21 | func NewEnPassantMove(start BoardPosition, end BoardPosition) *Move { 22 | m := new(Move) 23 | m.StartPos = start 24 | m.EndPos = end 25 | m.EnPassant = true 26 | return m 27 | } 28 | 29 | func NewCastleMove(start BoardPosition, end BoardPosition) *Move { 30 | m := new(Move) 31 | m.StartPos = start 32 | m.EndPos = end 33 | m.Castle = true 34 | return m 35 | } 36 | 37 | func NewPromoteMove(start BoardPosition, end BoardPosition) *Move { 38 | m := new(Move) 39 | m.StartPos = start 40 | m.EndPos = end 41 | m.Promote = true 42 | return m 43 | } 44 | -------------------------------------------------------------------------------- /chess_engine/move_picker.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | type MovePicker interface { 4 | GetNextMove(game *Game) (Result, *Move) 5 | } 6 | -------------------------------------------------------------------------------- /chess_engine/move_test.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | import "testing" 4 | 5 | func TestNewMove(t *testing.T) { 6 | m := NewMove(A2, A3) 7 | if m == nil { 8 | t.Errorf("Error! NewMove function returned nil for move!") 9 | } 10 | 11 | if m.StartPos != A2 { 12 | t.Errorf("Error! NewMove function returned move with incorrect Start Position!") 13 | } 14 | 15 | if m.EndPos != A3 { 16 | t.Errorf("Error! NewMove function returned move with incorrect End Position!") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chess_engine/pawn.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | type Pawn struct { 4 | Type PieceType 5 | Color PieceColor 6 | CurrPosition BoardPosition 7 | PrevPosition BoardPosition 8 | MoveCount int 9 | } 10 | 11 | func NewPawn(pos BoardPosition, color PieceColor) *Pawn { 12 | pawn := &Pawn{PAWN, color, pos, 13 | INVALID_BOARD_POSITION, 0} 14 | return pawn 15 | } 16 | 17 | func (pawn *Pawn) Copy() ChessPiece { 18 | copyPawn := &Pawn{} 19 | copyPawn.Type = pawn.Type 20 | copyPawn.Color = pawn.Color 21 | copyPawn.CurrPosition = pawn.CurrPosition 22 | copyPawn.PrevPosition = pawn.PrevPosition 23 | copyPawn.MoveCount = pawn.MoveCount 24 | return copyPawn 25 | } 26 | 27 | func (pawn *Pawn) GetMoveCount() int { 28 | return pawn.MoveCount 29 | } 30 | 31 | func (pawn *Pawn) GetMoves(board *Board) []*Move { 32 | moves := []*Move{} 33 | rank, file := GetRankFile(pawn.CurrPosition) 34 | increment := 1 35 | newPos := INVALID_BOARD_POSITION 36 | if pawn.Color == Black { 37 | increment = -1 38 | } 39 | newRank := rank + increment 40 | if newRank >= 0 && newRank < 8 { 41 | newPos = GetBoardPos(newRank, file) 42 | if !board.Squares[newPos].Occupied { 43 | moves = append(moves, NewMove(pawn.CurrPosition, newPos)) 44 | } 45 | } 46 | 47 | // Add the two square first move 48 | if pawn.MoveCount == 0 && !board.Squares[newPos].Occupied { 49 | newRank = newRank + increment 50 | if newRank >= 0 && newRank < 8 { 51 | newPos = GetBoardPos(newRank, file) 52 | if !board.Squares[newPos].Occupied { 53 | moves = append(moves, NewMove(pawn.CurrPosition, newPos)) 54 | } 55 | } 56 | } 57 | 58 | // Check for attack move to left 59 | newRank = rank + increment 60 | newFile := file - 1 61 | if (newRank >= 0 && newRank < 8) && (newFile >= 0 && newFile < 8) { 62 | newPos = GetBoardPos(newRank, newFile) 63 | if board.Squares[newPos].Occupied && 64 | board.Squares[newPos].CurrPiece.GetColor() != pawn.Color { 65 | moves = append(moves, NewMove(pawn.CurrPosition, newPos)) 66 | } 67 | } 68 | 69 | // Check for attack move to right 70 | newRank = rank + increment 71 | newFile = file + 1 72 | if (newRank >= 0 && newRank < 8) && (newFile >= 0 && newFile < 8) { 73 | newPos = GetBoardPos(newRank, newFile) 74 | if board.Squares[newPos].Occupied && 75 | board.Squares[newPos].CurrPiece.GetColor() != pawn.Color { 76 | moves = append(moves, NewMove(pawn.CurrPosition, newPos)) 77 | } 78 | } 79 | 80 | // Check for en Passant with white 81 | if pawn.Color == White && rank == 4 { 82 | if file-1 >= 0 { // Check for enPassant to the left 83 | startPos := GetBoardPos(6, file-1) 84 | attackPos := GetBoardPos(rank, file-1) 85 | movePos := GetBoardPos(5, file-1) 86 | if board.Squares[attackPos].Occupied && 87 | board.Squares[attackPos].CurrPiece.GetType() == PAWN && 88 | board.Squares[attackPos].CurrPiece.GetColor() == Black && 89 | board.Squares[attackPos].CurrPiece.GetMoveCount() == 1 && 90 | board.Squares[attackPos].CurrPiece.GetPrevPosition() == startPos && 91 | board.PrevMove.StartPos == startPos { 92 | moves = append(moves, NewEnPassantMove(pawn.CurrPosition, movePos)) 93 | } 94 | } 95 | if file+1 <= 7 { // Check for enPassant to the right 96 | startPos := GetBoardPos(6, file+1) 97 | attackPos := GetBoardPos(rank, file+1) 98 | movePos := GetBoardPos(5, file+1) 99 | if board.Squares[attackPos].Occupied && 100 | board.Squares[attackPos].CurrPiece.GetType() == PAWN && 101 | board.Squares[attackPos].CurrPiece.GetColor() == Black && 102 | board.Squares[attackPos].CurrPiece.GetMoveCount() == 1 && 103 | board.Squares[attackPos].CurrPiece.GetPrevPosition() == startPos && 104 | board.PrevMove.StartPos == startPos { 105 | moves = append(moves, NewEnPassantMove(pawn.CurrPosition, movePos)) 106 | } 107 | } 108 | } 109 | 110 | if pawn.Color == Black && rank == 3 { 111 | if file-1 >= 0 { // Check for enPassant to the left 112 | startPos := GetBoardPos(1, file-1) 113 | attackPos := GetBoardPos(rank, file-1) 114 | movePos := GetBoardPos(2, file-1) 115 | if board.Squares[attackPos].Occupied && 116 | board.Squares[attackPos].CurrPiece.GetType() == PAWN && 117 | board.Squares[attackPos].CurrPiece.GetColor() == White && 118 | board.Squares[attackPos].CurrPiece.GetMoveCount() == 1 && 119 | board.Squares[attackPos].CurrPiece.GetPrevPosition() == startPos && 120 | board.PrevMove.StartPos == startPos { 121 | moves = append(moves, NewEnPassantMove(pawn.CurrPosition, movePos)) 122 | } 123 | } 124 | if file+1 <= 7 { // Check for enPassant to the right 125 | startPos := GetBoardPos(1, file+1) 126 | attackPos := GetBoardPos(rank, file+1) 127 | movePos := GetBoardPos(2, file+1) 128 | if board.Squares[attackPos].Occupied && 129 | board.Squares[attackPos].CurrPiece.GetType() == PAWN && 130 | board.Squares[attackPos].CurrPiece.GetColor() == White && 131 | board.Squares[attackPos].CurrPiece.GetMoveCount() == 1 && 132 | board.Squares[attackPos].CurrPiece.GetPrevPosition() == startPos && 133 | board.PrevMove.StartPos == startPos { 134 | moves = append(moves, NewEnPassantMove(pawn.CurrPosition, movePos)) 135 | } 136 | } 137 | } 138 | return moves 139 | } 140 | 141 | func (pawn *Pawn) GetCurrPosition() BoardPosition { 142 | return pawn.CurrPosition 143 | } 144 | 145 | func (pawn *Pawn) GetPrevPosition() BoardPosition { 146 | return pawn.PrevPosition 147 | } 148 | 149 | func (pawn *Pawn) GetColor() PieceColor { 150 | return pawn.Color 151 | } 152 | 153 | func (pawn *Pawn) GetType() PieceType { 154 | return pawn.Type 155 | } 156 | 157 | func (pawn *Pawn) Move(newPos BoardPosition) { 158 | pawn.PrevPosition = pawn.CurrPosition 159 | pawn.CurrPosition = newPos 160 | pawn.MoveCount++ 161 | } 162 | 163 | func (pawn *Pawn) GetValue() int { 164 | return 1 165 | } 166 | -------------------------------------------------------------------------------- /chess_engine/pawn_test.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | import "testing" 4 | 5 | func TestNewPawn(t *testing.T) { 6 | pawn := NewPawn(A2, Black) 7 | 8 | if pawn == nil { 9 | t.Errorf("Error! NewPawn function returned nil for pawn!") 10 | } 11 | 12 | if pawn.Type != PAWN { 13 | t.Errorf("Error! NewPawn function returned wrong Type!") 14 | } 15 | 16 | if pawn.CurrPosition != A2 { 17 | t.Errorf("Error! NewPawn function returned pawn with wrong CurrPosition!") 18 | } 19 | 20 | if pawn.Color != Black { 21 | t.Errorf("Error! NewPawn function returned pawn with wrong Color!") 22 | } 23 | 24 | if pawn.MoveCount != 0 { 25 | t.Errorf("Error! NewPawn function returned pawn with non-zero move count!") 26 | } 27 | 28 | if pawn.GetType() != PAWN { 29 | t.Errorf("Error! NewPawn function returned wrong Type!") 30 | } 31 | 32 | if pawn.GetCurrPosition() != A2 { 33 | t.Errorf("Error! NewPawn function returned pawn with wrong CurrPosition!") 34 | } 35 | 36 | if pawn.GetColor() != Black { 37 | t.Errorf("Error! NewPawn function returned pawn with wrong Color!") 38 | } 39 | 40 | } 41 | 42 | func TestPawnMove(t *testing.T) { 43 | pawn := NewPawn(C1, Black) 44 | pawn.Move(C2) 45 | if pawn.GetCurrPosition() != C2 { 46 | t.Errorf("Error! Pawn.Move function returned wrong CurrPosition!") 47 | } 48 | } 49 | 50 | func TestPawnGenNormalMoves(t *testing.T) { 51 | board := NewBoard() 52 | // Test White Pawn basic move (forward one) 53 | board.Squares[D3].CurrPiece = board.Squares[D2].CurrPiece // Move the pawn from D2 to D3 54 | board.Squares[D3].Occupied = true 55 | board.Squares[D3].CurrPiece.Move(D3) 56 | moves := board.Squares[D3].CurrPiece.GetMoves(board) 57 | if len(moves) != 1 { 58 | t.Errorf("Error! Pawn.GetMoves function returned wrong number of moves for normal move test!") 59 | } 60 | if moves[0].StartPos != D3 { 61 | t.Errorf("Error! Pawn.GetMoves start position incorrect!") 62 | } 63 | if moves[0].EndPos != D4 { 64 | t.Errorf("Error! Pawn.GetMoves end position incorrect!") 65 | } 66 | } 67 | 68 | func TestPawnBoardEdgeCases(t *testing.T) { 69 | // White at the end of the board 70 | board := NewBoard() 71 | // Test White Pawn basic move (forward one) 72 | board.Squares[D8].CurrPiece = board.Squares[D2].CurrPiece 73 | board.Squares[D8].Occupied = true 74 | board.Squares[D8].CurrPiece.Move(D8) 75 | moves := board.Squares[D8].CurrPiece.GetMoves(board) 76 | if len(moves) != 0 { 77 | t.Errorf("Error! Pawn.GetMoves function returned moves for white Pawn at the end of the board!") 78 | } 79 | 80 | // Black at the end of the board 81 | board.InitBoard() 82 | // Move the black pawn on the D file to the end of the board 83 | board.Squares[D1].CurrPiece = board.Squares[D7].CurrPiece 84 | board.Squares[D1].Occupied = true 85 | board.Squares[D1].CurrPiece.Move(D1) 86 | moves = board.Squares[D1].CurrPiece.GetMoves(board) 87 | if len(moves) != 0 { 88 | t.Errorf("Error! Pawn.GetMoves function returned moves for black Pawn at the end of the board!") 89 | } 90 | 91 | // White on the left edge 92 | board.InitBoard() 93 | // Move the white pawn on the left edge of the board one square (so it's not the first move) 94 | // and then get the moves 95 | board.Squares[A3].CurrPiece = board.Squares[A2].CurrPiece 96 | board.Squares[A3].Occupied = true 97 | board.Squares[A3].CurrPiece.Move(A3) 98 | moves = board.Squares[A3].CurrPiece.GetMoves(board) 99 | if len(moves) != 1 { 100 | t.Errorf("Error! Pawn.GetMoves function returned invalid number of moves for white Pawn on the left edge of the board!") 101 | } 102 | if moves[0].StartPos != A3 { 103 | t.Errorf("Error! Pawn.GetMoves start position incorrect for white pawn on left edge!") 104 | } 105 | if moves[0].EndPos != A4 { 106 | t.Errorf("Error! Pawn.GetMoves end position incorrect for white pawn on left edge!") 107 | } 108 | 109 | // White on the right edge 110 | board.InitBoard() 111 | // Move the white pawn on the left edge of the board one square (so it's not the first move) 112 | // and then get the moves 113 | board.Squares[H3].CurrPiece = board.Squares[H2].CurrPiece 114 | board.Squares[H3].Occupied = true 115 | board.Squares[H3].CurrPiece.Move(H3) 116 | moves = board.Squares[H3].CurrPiece.GetMoves(board) 117 | if len(moves) != 1 { 118 | t.Errorf("Error! Pawn.GetMoves function returned invalid number of moves for white Pawn on the right edge of the board!") 119 | } 120 | if moves[0].StartPos != H3 { 121 | t.Errorf("Error! Pawn.GetMoves start position incorrect for white pawn on right edge!") 122 | } 123 | if moves[0].EndPos != H4 { 124 | t.Errorf("Error! Pawn.GetMoves end position incorrect for white pawn on right edge!") 125 | } 126 | 127 | // Black on the left edge 128 | board.InitBoard() 129 | // Move the black pawn on the left edge of the board one square (so it's not the first move) 130 | // and then get the moves 131 | board.Squares[A6].CurrPiece = board.Squares[A7].CurrPiece 132 | board.Squares[A6].Occupied = true 133 | board.Squares[A6].CurrPiece.Move(A6) 134 | moves = board.Squares[A6].CurrPiece.GetMoves(board) 135 | if len(moves) != 1 { 136 | t.Errorf("Error! Pawn.GetMoves function returned invalid number of moves for black Pawn on the left edge of the board!") 137 | } 138 | if moves[0].StartPos != A6 { 139 | t.Errorf("Error! Pawn.GetMoves start position incorrect for black pawn on left edge!") 140 | } 141 | if moves[0].EndPos != A5 { 142 | t.Errorf("Error! Pawn.GetMoves end position incorrect for black pawn on left edge!") 143 | } 144 | 145 | // Black on the right edge 146 | board.InitBoard() 147 | // Move the black pawn on the left edge of the board one square (so it's not the first move) 148 | // and then get the moves 149 | board.Squares[H6].CurrPiece = board.Squares[H7].CurrPiece 150 | board.Squares[H6].Occupied = true 151 | board.Squares[H6].CurrPiece.Move(H6) 152 | moves = board.Squares[H6].CurrPiece.GetMoves(board) 153 | if len(moves) != 1 { 154 | t.Errorf("Error! Pawn.GetMoves function returned invalid number of moves for black Pawn on the right edge of the board!") 155 | } 156 | if moves[0].StartPos != H6 { 157 | t.Errorf("Error! Pawn.GetMoves start position incorrect for black pawn on right edge!") 158 | } 159 | if moves[0].EndPos != H5 { 160 | t.Errorf("Error! Pawn.GetMoves end position incorrect for black pawn on right edge!") 161 | } 162 | } 163 | 164 | func TestPawnFirstMove(t *testing.T) { 165 | // White 166 | board := NewBoard() 167 | // Test White Pawn first move (forward one or forward two) 168 | moves := board.Squares[D2].CurrPiece.GetMoves(board) 169 | if len(moves) != 2 { 170 | t.Errorf("Error! Pawn.GetMoves function did not return 2 moves for white's first pawn move!") 171 | } 172 | if (moves[0].StartPos != D2) || (moves[1].StartPos != D2) { 173 | t.Errorf("Error! Pawn.GetMoves start positions incorrect for white's first move!") 174 | } 175 | if (moves[0].EndPos != D3) && (moves[1].EndPos != D3) { 176 | t.Errorf("Error! Pawn.GetMoves end position incorrect for white's first move!") 177 | } 178 | if (moves[0].EndPos != D4) && (moves[1].EndPos != D4) { 179 | t.Errorf("Error! Pawn.GetMoves end position incorrect for white's first move!") 180 | } 181 | 182 | // White - No moves if there is a piece right in front of the pawn 183 | board = NewBoard() 184 | board.Squares[D3] = Square{true, NewPawn(D3, White)} 185 | // Test White Pawn first move (forward one or forward two) 186 | moves = board.Squares[D2].CurrPiece.GetMoves(board) 187 | if len(moves) != 0 { 188 | t.Errorf("Error! Pawn.GetMoves function did not return 0 moves for white's first pawn move which is blocked!") 189 | } 190 | 191 | // Black 192 | board = NewBoard() 193 | // Test Black Pawn first move (forward one or forward two) 194 | moves = board.Squares[D7].CurrPiece.GetMoves(board) 195 | if len(moves) != 2 { 196 | t.Errorf("Error! Pawn.GetMoves function did not return 2 moves for black's first pawn move!") 197 | } 198 | if (moves[0].StartPos != D7) || (moves[1].StartPos != D7) { 199 | t.Errorf("Error! Pawn.GetMoves start positions incorrect for black's first move!") 200 | } 201 | if (moves[0].EndPos != D6) && (moves[1].EndPos != D6) { 202 | t.Errorf("Error! Pawn.GetMoves end position incorrect for black's first move!") 203 | } 204 | if (moves[0].EndPos != D5) && (moves[1].EndPos != D5) { 205 | t.Errorf("Error! Pawn.GetMoves end position incorrect for black's first move!") 206 | } 207 | // Black - no moves with piece in front of the pawn 208 | board = NewBoard() 209 | board.Squares[D6] = Square{true, NewPawn(D6, Black)} 210 | // Test Black Pawn first move (forward one or forward two) 211 | moves = board.Squares[D7].CurrPiece.GetMoves(board) 212 | if len(moves) != 0 { 213 | t.Errorf("Error! Pawn.GetMoves function did not return 0 moves for black's first pawn move with piece in front!") 214 | } 215 | } 216 | 217 | func TestPawnAttack(t *testing.T) { 218 | // White 219 | board := NewBoard() 220 | // Test White Pawn first move (forward one or forward two) 221 | board.Squares[D3] = Square{true, NewPawn(D3, White)} 222 | board.Squares[C3] = Square{true, NewPawn(C3, Black)} 223 | 224 | moves := board.Squares[D2].CurrPiece.GetMoves(board) 225 | if len(moves) != 1 { 226 | t.Errorf("Error! Pawn.GetMoves function did not return 1 move for white attack test!") 227 | } 228 | if moves[0].StartPos != D2 { 229 | t.Errorf("Error! Pawn.GetMoves start positions incorrect for white's first move!") 230 | } 231 | if moves[0].EndPos != C3 { 232 | t.Errorf("Error! Pawn.GetMoves end position incorrect for white's first move!") 233 | } 234 | 235 | // Black 236 | board = NewBoard() 237 | // Test Black Pawn first move (forward one or forward two) 238 | board.Squares[D6] = Square{true, NewPawn(D6, Black)} 239 | board.Squares[C6] = Square{true, NewPawn(C6, White)} 240 | 241 | moves = board.Squares[D7].CurrPiece.GetMoves(board) 242 | if len(moves) != 1 { 243 | t.Errorf("Error! Pawn.GetMoves function did not return 1 move for black attack test") 244 | } 245 | if moves[0].StartPos != D7 { 246 | t.Errorf("Error! Pawn.GetMoves start positions incorrect for black's attack move!") 247 | } 248 | if moves[0].EndPos != C6 { 249 | t.Errorf("Error! Pawn.GetMoves end position incorrect for black's attack move!") 250 | } 251 | } 252 | 253 | func TestPawnWhiteEnPassantAttack(t *testing.T) { 254 | // White 255 | board := NewBoard() 256 | // Test White Pawn first move (forward one or forward two) 257 | whitePawn := NewPawn(D5, White) 258 | blackPawn := NewPawn(C5, Black) 259 | blackPawn.PrevPosition = C7 260 | blackPawn.MoveCount = 1 261 | blackPawn2 := NewPawn(E5, Black) 262 | blackPawn2.PrevPosition = E7 263 | blackPawn2.MoveCount = 1 264 | board.Squares[D5] = Square{true, whitePawn} 265 | board.Squares[D6] = Square{true, whitePawn} 266 | board.Squares[C5] = Square{true, blackPawn} 267 | board.Squares[E5] = Square{true, blackPawn2} 268 | moves := board.Squares[D5].CurrPiece.GetMoves(board) 269 | if len(moves) != 2 { 270 | t.Errorf("Error! Pawn.GetMoves function did not return 1 move for white attack test!") 271 | } else { 272 | if moves[0].StartPos != D5 { 273 | t.Errorf("Error! Pawn.GetMoves start positions incorrect for white's enPassant!") 274 | } 275 | if moves[0].EndPos != C6 { 276 | t.Errorf("Error! Pawn.GetMoves end position incorrect for white's enPassant!") 277 | } 278 | if !moves[0].EnPassant { 279 | t.Errorf("Error! Pawn.GetMoves first move is not en passant!") 280 | } 281 | if moves[1].StartPos != D5 { 282 | t.Errorf("Error! Pawn.GetMoves start positions incorrect for white's enPassant!") 283 | } 284 | if moves[1].EndPos != E6 { 285 | t.Errorf("Error! Pawn.GetMoves end position incorrect for white's enPassant!") 286 | } 287 | if !moves[1].EnPassant { 288 | t.Errorf("Error! Pawn.GetMoves first move is not enPassant!") 289 | } 290 | } 291 | } 292 | 293 | func TestPawnBlackEnPassantAttack(t *testing.T) { 294 | // White 295 | board := NewBoard() 296 | // Test White Pawn first move (forward one or forward two) 297 | blackPawn := NewPawn(D4, Black) 298 | whitePawn := NewPawn(C4, White) 299 | whitePawn.PrevPosition = C2 300 | whitePawn.MoveCount = 1 301 | whitePawn2 := NewPawn(E4, White) 302 | whitePawn2.PrevPosition = E2 303 | whitePawn2.MoveCount = 1 304 | board.Squares[D4] = Square{true, blackPawn} 305 | board.Squares[D3] = Square{true, blackPawn} 306 | board.Squares[C4] = Square{true, whitePawn} 307 | board.Squares[E4] = Square{true, whitePawn2} 308 | moves := board.Squares[D4].CurrPiece.GetMoves(board) 309 | if len(moves) != 2 { 310 | t.Errorf("Error! Pawn.GetMoves function did not return 1 move for black enpassant attack test!") 311 | } else { 312 | if moves[0].StartPos != D4 { 313 | t.Errorf("Error! Pawn.GetMoves start positions incorrect for black's enPassant!") 314 | } 315 | if moves[0].EndPos != C3 { 316 | t.Errorf("Error! Pawn.GetMoves end position incorrect for black's enPassant!") 317 | } 318 | if !moves[0].EnPassant { 319 | t.Errorf("Error! Pawn.GetMoves first move is not en passant!") 320 | } 321 | if moves[1].StartPos != D4 { 322 | t.Errorf("Error! Pawn.GetMoves start positions incorrect for black's enPassant!") 323 | } 324 | if moves[1].EndPos != E3 { 325 | t.Errorf("Error! Pawn.GetMoves end position incorrect for black's enPassant!") 326 | } 327 | if !moves[1].EnPassant { 328 | t.Errorf("Error! Pawn.GetMoves first move is not enPassant!") 329 | } 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /chess_engine/queen.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | type Queen struct { 4 | Type PieceType 5 | Color PieceColor 6 | CurrPosition BoardPosition 7 | PrevPosition BoardPosition 8 | MoveCount int 9 | } 10 | 11 | func NewQueen(pos BoardPosition, color PieceColor) *Queen { 12 | queen := &Queen{QUEEN, color, pos, 13 | INVALID_BOARD_POSITION, 0} 14 | return queen 15 | } 16 | 17 | func (queen *Queen) Copy() ChessPiece { 18 | copyQueen := &Queen{} 19 | copyQueen.Type = queen.Type 20 | copyQueen.Color = queen.Color 21 | copyQueen.CurrPosition = queen.CurrPosition 22 | copyQueen.PrevPosition = queen.PrevPosition 23 | copyQueen.MoveCount = queen.MoveCount 24 | return copyQueen 25 | } 26 | 27 | func (queen *Queen) GetMoveCount() int { 28 | return queen.MoveCount 29 | } 30 | 31 | func (queen *Queen) GetMoves(board *Board) []*Move { 32 | moves := []*Move{} 33 | 34 | moves = queen.addIterMoves(board, moves, 1, 1) 35 | moves = queen.addIterMoves(board, moves, -1, 1) 36 | moves = queen.addIterMoves(board, moves, 1, -1) 37 | moves = queen.addIterMoves(board, moves, -1, -1) 38 | moves = queen.addIterMoves(board, moves, -1, 0) 39 | moves = queen.addIterMoves(board, moves, 1, 0) 40 | moves = queen.addIterMoves(board, moves, 0, 1) 41 | moves = queen.addIterMoves(board, moves, 0, -1) 42 | 43 | return moves 44 | } 45 | 46 | func (queen *Queen) addIterMoves(board *Board, moves []*Move, rInc int, fInc int) []*Move { 47 | startRank, startFile := GetRankFile(queen.CurrPosition) 48 | startRank += rInc 49 | startFile += fInc 50 | for (startRank < 8 && startRank >= 0) && (startFile < 8 && startFile >= 0) { 51 | currPos := GetBoardPos(startRank, startFile) 52 | if board.Squares[currPos].Occupied { 53 | if board.Squares[currPos].CurrPiece.GetColor() != queen.Color { 54 | moves = append(moves, NewMove(queen.CurrPosition, currPos)) 55 | return moves // Hit another piece so no more moves 56 | } else { 57 | return moves // We've hit a blocking piece along the diagonal. 58 | } 59 | } else { 60 | moves = append(moves, NewMove(queen.CurrPosition, currPos)) 61 | } 62 | startRank += rInc 63 | startFile += fInc 64 | } 65 | 66 | return moves 67 | } 68 | 69 | func (queen *Queen) GetCurrPosition() BoardPosition { 70 | return queen.CurrPosition 71 | } 72 | 73 | func (queen *Queen) GetPrevPosition() BoardPosition { 74 | return queen.PrevPosition 75 | } 76 | 77 | func (queen *Queen) GetColor() PieceColor { 78 | return queen.Color 79 | } 80 | 81 | func (queen *Queen) GetType() PieceType { 82 | return queen.Type 83 | } 84 | 85 | func (queen *Queen) Move(newPos BoardPosition) { 86 | queen.CurrPosition = newPos 87 | queen.MoveCount++ 88 | } 89 | 90 | func (queen *Queen) GetValue() int { 91 | return 9 92 | } 93 | -------------------------------------------------------------------------------- /chess_engine/queen_test.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | import "testing" 4 | 5 | func TestNewQueen(t *testing.T) { 6 | queen := NewQueen(B1, Black) 7 | 8 | if queen == nil { 9 | t.Errorf("Error! NewQueen function returned nil!") 10 | } 11 | 12 | if queen.Type != QUEEN { 13 | t.Errorf("Error! NewQueen function returned wrong Type!") 14 | } 15 | 16 | if queen.CurrPosition != B1 { 17 | t.Errorf("Error! NewQueen function returned wrong CurrPosition!") 18 | } 19 | 20 | if queen.Color != Black { 21 | t.Errorf("Error! NewQueen function returned wrong Color!") 22 | } 23 | 24 | if queen.GetType() != QUEEN { 25 | t.Errorf("Error! NewQueen function returned wrong Type!") 26 | } 27 | 28 | if queen.GetCurrPosition() != B1 { 29 | t.Errorf("Error! NewQueen function returned wrong CurrPosition!") 30 | } 31 | 32 | if queen.GetColor() != Black { 33 | t.Errorf("Error! NewQueen function returned wrong Color!") 34 | } 35 | } 36 | 37 | func TestQueenMove(t *testing.T) { 38 | queen := NewQueen(C1, Black) 39 | queen.Move(C2) 40 | if queen.GetCurrPosition() != C2 { 41 | t.Errorf("Error! Queen.Move function returned wrong CurrPosition!") 42 | } 43 | if queen.MoveCount != 1 { 44 | t.Errorf("Error! Queen.MoveCount wrong after move!") 45 | } 46 | } 47 | 48 | func TestQueenAllMoves(t *testing.T) { 49 | board := &Board{} 50 | // Test white square Bishop moving from center square to outer corners 51 | board.Squares[D4] = Square{true, NewQueen(D4, White)} 52 | 53 | moves := board.Squares[D4].CurrPiece.GetMoves(board) 54 | if len(moves) != 27 { 55 | t.Errorf("Error! Queen.GetMoves function did not return 27 moves for queen on open center square!") 56 | } 57 | 58 | if !MovesContainMove(Move{D4, C3, false, false, false}, moves) && 59 | !MovesContainMove(Move{D4, B2, false, false, false}, moves) && 60 | !MovesContainMove(Move{D4, A1, false, false, false}, moves) && 61 | !MovesContainMove(Move{D4, E5, false, false, false}, moves) && 62 | !MovesContainMove(Move{D4, F6, false, false, false}, moves) && 63 | !MovesContainMove(Move{D4, G7, false, false, false}, moves) && 64 | !MovesContainMove(Move{D4, H8, false, false, false}, moves) && 65 | !MovesContainMove(Move{D4, E3, false, false, false}, moves) && 66 | !MovesContainMove(Move{D4, F2, false, false, false}, moves) && 67 | !MovesContainMove(Move{D4, G1, false, false, false}, moves) && 68 | !MovesContainMove(Move{D4, C5, false, false, false}, moves) && 69 | !MovesContainMove(Move{D4, B6, false, false, false}, moves) && 70 | !MovesContainMove(Move{D4, D3, false, false, false}, moves) && 71 | !MovesContainMove(Move{D4, D2, false, false, false}, moves) && 72 | !MovesContainMove(Move{D4, D1, false, false, false}, moves) && 73 | !MovesContainMove(Move{D4, D5, false, false, false}, moves) && 74 | !MovesContainMove(Move{D4, D6, false, false, false}, moves) && 75 | !MovesContainMove(Move{D4, D7, false, false, false}, moves) && 76 | !MovesContainMove(Move{D4, D8, false, false, false}, moves) && 77 | !MovesContainMove(Move{D4, C4, false, false, false}, moves) && 78 | !MovesContainMove(Move{D4, B4, false, false, false}, moves) && 79 | !MovesContainMove(Move{D4, A4, false, false, false}, moves) && 80 | !MovesContainMove(Move{D4, E4, false, false, false}, moves) && 81 | !MovesContainMove(Move{D4, F4, false, false, false}, moves) && 82 | !MovesContainMove(Move{D4, G4, false, false, false}, moves) && 83 | !MovesContainMove(Move{D4, H4, false, false, false}, moves) && 84 | !MovesContainMove(Move{D4, A7, false, false, false}, moves) { 85 | t.Errorf("Error! Queen.GetMoves for white square queen missing moves!") 86 | } 87 | } 88 | 89 | func TestQueenNoMoves(t *testing.T) { 90 | board := &Board{} 91 | // Test white square Bishop moving from center square to outer corners 92 | board.Squares[D4] = Square{true, NewQueen(D4, White)} 93 | board.Squares[E5] = Square{true, NewPawn(E5, White)} 94 | board.Squares[E3] = Square{true, NewPawn(E3, White)} 95 | board.Squares[C5] = Square{true, NewPawn(C5, White)} 96 | board.Squares[C3] = Square{true, NewPawn(C3, White)} 97 | board.Squares[D5] = Square{true, NewPawn(D5, White)} 98 | board.Squares[D3] = Square{true, NewPawn(D3, White)} 99 | board.Squares[C4] = Square{true, NewPawn(C4, White)} 100 | board.Squares[E4] = Square{true, NewPawn(E4, White)} 101 | 102 | moves := board.Squares[D4].CurrPiece.GetMoves(board) 103 | if len(moves) != 0 { 104 | t.Errorf("Error! Queen.GetMoves function did not return 0 moves for queen on surrounded center square!") 105 | } 106 | } 107 | 108 | func TestQueenAttackMoves(t *testing.T) { 109 | board := &Board{} 110 | // Test white square Bishop moving from center square to outer corners 111 | board.Squares[D4] = Square{true, NewQueen(D4, Black)} 112 | board.Squares[E5] = Square{true, NewPawn(E5, White)} 113 | board.Squares[E3] = Square{true, NewPawn(E3, White)} 114 | board.Squares[C5] = Square{true, NewPawn(C5, White)} 115 | board.Squares[C3] = Square{true, NewPawn(C3, White)} 116 | board.Squares[D5] = Square{true, NewPawn(D5, White)} 117 | board.Squares[D3] = Square{true, NewPawn(D3, White)} 118 | board.Squares[C4] = Square{true, NewPawn(C4, White)} 119 | board.Squares[E4] = Square{true, NewPawn(E4, White)} 120 | 121 | moves := board.Squares[D4].CurrPiece.GetMoves(board) 122 | if len(moves) != 8 { 123 | t.Errorf("Error! Queen.GetMoves function did not return 8 moves for queen on open center square!") 124 | } 125 | 126 | if !MovesContainMove(Move{D4, D5, false, false, false}, moves) && 127 | !MovesContainMove(Move{D4, C5, false, false, false}, moves) && 128 | !MovesContainMove(Move{D4, C4, false, false, false}, moves) && 129 | !MovesContainMove(Move{D4, C3, false, false, false}, moves) && 130 | !MovesContainMove(Move{D4, D3, false, false, false}, moves) && 131 | !MovesContainMove(Move{D4, E3, false, false, false}, moves) && 132 | !MovesContainMove(Move{D4, E4, false, false, false}, moves) && 133 | !MovesContainMove(Move{D4, E5, false, false, false}, moves) { 134 | t.Errorf("Error! Queen.GetMoves for white square queen missing moves!") 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /chess_engine/rook.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | type Rook struct { 4 | Type PieceType 5 | Color PieceColor 6 | CurrPosition BoardPosition 7 | PrevPosition BoardPosition 8 | MoveCount int 9 | } 10 | 11 | func NewRook(pos BoardPosition, color PieceColor) *Rook { 12 | rook := &Rook{ROOK, color, pos, 13 | INVALID_BOARD_POSITION, 0} 14 | return rook 15 | } 16 | 17 | func (rook *Rook) Copy() ChessPiece { 18 | copyRook := &Rook{} 19 | copyRook.Type = rook.Type 20 | copyRook.Color = rook.Color 21 | copyRook.CurrPosition = rook.CurrPosition 22 | copyRook.PrevPosition = rook.PrevPosition 23 | copyRook.MoveCount = rook.MoveCount 24 | return copyRook 25 | } 26 | 27 | func (rook *Rook) GetMoveCount() int { 28 | return rook.MoveCount 29 | } 30 | 31 | func (rook *Rook) GetMoves(board *Board) []*Move { 32 | moves := []*Move{} 33 | 34 | moves = rook.addIterMoves(board, moves, -1, 0) 35 | moves = rook.addIterMoves(board, moves, 1, 0) 36 | moves = rook.addIterMoves(board, moves, 0, 1) 37 | moves = rook.addIterMoves(board, moves, 0, -1) 38 | 39 | return moves 40 | } 41 | 42 | func (rook *Rook) addIterMoves(board *Board, moves []*Move, rInc int, fInc int) []*Move { 43 | startRank, startFile := GetRankFile(rook.CurrPosition) 44 | startRank += rInc 45 | startFile += fInc 46 | for (startRank < 8 && startRank >= 0) && (startFile < 8 && startFile >= 0) { 47 | currPos := GetBoardPos(startRank, startFile) 48 | if board.Squares[currPos].Occupied { 49 | if board.Squares[currPos].CurrPiece.GetColor() != rook.Color { 50 | moves = append(moves, NewMove(rook.CurrPosition, currPos)) 51 | return moves // Hit another piece so no more moves 52 | } else { 53 | return moves // We've hit a blocking piece along the diagonal. 54 | } 55 | } else { 56 | moves = append(moves, NewMove(rook.CurrPosition, currPos)) 57 | } 58 | startRank += rInc 59 | startFile += fInc 60 | } 61 | 62 | return moves 63 | } 64 | 65 | func (rook *Rook) GetCurrPosition() BoardPosition { 66 | return rook.CurrPosition 67 | } 68 | 69 | func (rook *Rook) GetPrevPosition() BoardPosition { 70 | return rook.PrevPosition 71 | } 72 | 73 | func (rook *Rook) GetColor() PieceColor { 74 | return rook.Color 75 | } 76 | 77 | func (rook *Rook) GetType() PieceType { 78 | return rook.Type 79 | } 80 | 81 | func (rook *Rook) Move(newPos BoardPosition) { 82 | rook.CurrPosition = newPos 83 | rook.MoveCount++ 84 | } 85 | 86 | func (rook *Rook) GetValue() int { 87 | return 5 88 | } 89 | -------------------------------------------------------------------------------- /chess_engine/rook_test.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | import "testing" 4 | 5 | func TestNewRook(t *testing.T) { 6 | rook := NewRook(B1, Black) 7 | 8 | if rook == nil { 9 | t.Errorf("Error! NewRook function returned nil!") 10 | } 11 | 12 | if rook.Type != ROOK { 13 | t.Errorf("Error! NewRook function returned wrong Type!") 14 | } 15 | 16 | if rook.CurrPosition != B1 { 17 | t.Errorf("Error! NewRook function returned wrong CurrPosition!") 18 | } 19 | 20 | if rook.Color != Black { 21 | t.Errorf("Error! NewRook function returned wrong Color!") 22 | } 23 | 24 | if rook.GetType() != ROOK { 25 | t.Errorf("Error! NewRook function returned wrong Type!") 26 | } 27 | 28 | if rook.GetCurrPosition() != B1 { 29 | t.Errorf("Error! NewRook function returned wrong CurrPosition!") 30 | } 31 | 32 | if rook.GetColor() != Black { 33 | t.Errorf("Error! NewRook function returned wrong Color!") 34 | } 35 | } 36 | 37 | func TestRookMove(t *testing.T) { 38 | rook := NewRook(C1, Black) 39 | rook.Move(C2) 40 | if rook.GetCurrPosition() != C2 { 41 | t.Errorf("Error! Rook.Move function returned wrong CurrPosition!") 42 | } 43 | if rook.MoveCount != 1 { 44 | t.Errorf("Error! Rook.MoveCount wrong after move!") 45 | } 46 | } 47 | 48 | func TestRookAllMoves(t *testing.T) { 49 | board := &Board{} 50 | // Test white square Bishop moving from center square to outer corners 51 | board.Squares[D4] = Square{true, NewRook(D4, White)} 52 | 53 | moves := board.Squares[D4].CurrPiece.GetMoves(board) 54 | if len(moves) != 14 { 55 | t.Errorf("Error! Rook.GetMoves function did not return 14 moves for rook on open center square!") 56 | } 57 | 58 | if !MovesContainMove(Move{D4, D3, false, false, false}, moves) && 59 | !MovesContainMove(Move{D4, D2, false, false, false}, moves) && 60 | !MovesContainMove(Move{D4, D1, false, false, false}, moves) && 61 | !MovesContainMove(Move{D4, D5, false, false, false}, moves) && 62 | !MovesContainMove(Move{D4, D6, false, false, false}, moves) && 63 | !MovesContainMove(Move{D4, D7, false, false, false}, moves) && 64 | !MovesContainMove(Move{D4, D8, false, false, false}, moves) && 65 | !MovesContainMove(Move{D4, C4, false, false, false}, moves) && 66 | !MovesContainMove(Move{D4, B4, false, false, false}, moves) && 67 | !MovesContainMove(Move{D4, A4, false, false, false}, moves) && 68 | !MovesContainMove(Move{D4, E4, false, false, false}, moves) && 69 | !MovesContainMove(Move{D4, F4, false, false, false}, moves) && 70 | !MovesContainMove(Move{D4, G4, false, false, false}, moves) && 71 | !MovesContainMove(Move{D4, H4, false, false, false}, moves) { 72 | t.Errorf("Error! Rook.GetMoves for white square rook missing moves!") 73 | } 74 | } 75 | 76 | func TestRookNoMoves(t *testing.T) { 77 | board := &Board{} 78 | // Test white square Bishop moving from center square to outer corners 79 | board.Squares[D4] = Square{true, NewRook(D4, White)} 80 | board.Squares[D5] = Square{true, NewPawn(D5, White)} 81 | board.Squares[D3] = Square{true, NewPawn(D3, White)} 82 | board.Squares[C4] = Square{true, NewPawn(C4, White)} 83 | board.Squares[E4] = Square{true, NewPawn(E4, White)} 84 | 85 | moves := board.Squares[D4].CurrPiece.GetMoves(board) 86 | if len(moves) != 0 { 87 | t.Errorf("Error! Rook.GetMoves function did not return 0 moves for rook on surrounded center square!") 88 | } 89 | } 90 | 91 | func TestRookAttackMoves(t *testing.T) { 92 | board := &Board{} 93 | // Test white square Bishop moving from center square to outer corners 94 | board.Squares[D4] = Square{true, NewRook(D4, Black)} 95 | board.Squares[D5] = Square{true, NewPawn(D5, White)} 96 | board.Squares[D3] = Square{true, NewPawn(D3, White)} 97 | board.Squares[C4] = Square{true, NewPawn(C4, White)} 98 | board.Squares[E4] = Square{true, NewPawn(E4, White)} 99 | 100 | moves := board.Squares[D4].CurrPiece.GetMoves(board) 101 | if len(moves) != 4 { 102 | t.Errorf("Error! Rook.GetMoves function did not return 4 moves for rook on center square surrounded by opponents!") 103 | } 104 | 105 | if !MovesContainMove(Move{D4, D5, false, false, false}, moves) && 106 | !MovesContainMove(Move{D4, C5, false, false, false}, moves) && 107 | !MovesContainMove(Move{D4, C4, false, false, false}, moves) && 108 | !MovesContainMove(Move{D4, C3, false, false, false}, moves) && 109 | !MovesContainMove(Move{D4, D3, false, false, false}, moves) && 110 | !MovesContainMove(Move{D4, E3, false, false, false}, moves) && 111 | !MovesContainMove(Move{D4, E4, false, false, false}, moves) && 112 | !MovesContainMove(Move{D4, E5, false, false, false}, moves) { 113 | t.Errorf("Error! Rook.GetMoves for white square rook surrounded by opposition missing moves!") 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /chess_engine/user_interface.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | type UserInterface interface { 4 | EngineNextMove() *Move 5 | UserNextMove(move *Move) Result 6 | } 7 | -------------------------------------------------------------------------------- /chess_engine/utils.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | import "fmt" 4 | 5 | type ErrorCode uint8 6 | 7 | const ( 8 | NO_ERROR ErrorCode = iota 9 | INVALID_MOVE 10 | ENGINE_CHECK_MATE 11 | USER_CHECK_MATE 12 | NOT_USER_MOVE 13 | NOT_ENGINE_MOVE 14 | ) 15 | 16 | type Result struct { 17 | Result bool 18 | ResCode ErrorCode 19 | ResStr string 20 | } 21 | 22 | func GetRankFile(pos BoardPosition) (int, int) { 23 | rank := -1 24 | file := -1 25 | switch pos { 26 | case A1: 27 | rank = 0 28 | file = 0 29 | case A2: 30 | rank = 1 31 | file = 0 32 | case A3: 33 | rank = 2 34 | file = 0 35 | case A4: 36 | rank = 3 37 | file = 0 38 | case A5: 39 | rank = 4 40 | file = 0 41 | case A6: 42 | rank = 5 43 | file = 0 44 | case A7: 45 | rank = 6 46 | file = 0 47 | case A8: 48 | rank = 7 49 | file = 0 50 | case B1: 51 | rank = 0 52 | file = 1 53 | case B2: 54 | rank = 1 55 | file = 1 56 | case B3: 57 | rank = 2 58 | file = 1 59 | case B4: 60 | rank = 3 61 | file = 1 62 | case B5: 63 | rank = 4 64 | file = 1 65 | case B6: 66 | rank = 5 67 | file = 1 68 | case B7: 69 | rank = 6 70 | file = 1 71 | case B8: 72 | rank = 7 73 | file = 1 74 | case C1: 75 | rank = 0 76 | file = 2 77 | case C2: 78 | rank = 1 79 | file = 2 80 | case C3: 81 | rank = 2 82 | file = 2 83 | case C4: 84 | rank = 3 85 | file = 2 86 | case C5: 87 | rank = 4 88 | file = 2 89 | case C6: 90 | rank = 5 91 | file = 2 92 | case C7: 93 | rank = 6 94 | file = 2 95 | case C8: 96 | rank = 7 97 | file = 2 98 | case D1: 99 | rank = 0 100 | file = 3 101 | case D2: 102 | rank = 1 103 | file = 3 104 | case D3: 105 | rank = 2 106 | file = 3 107 | case D4: 108 | rank = 3 109 | file = 3 110 | case D5: 111 | rank = 4 112 | file = 3 113 | case D6: 114 | rank = 5 115 | file = 3 116 | case D7: 117 | rank = 6 118 | file = 3 119 | case D8: 120 | rank = 7 121 | file = 3 122 | case E1: 123 | rank = 0 124 | file = 4 125 | case E2: 126 | rank = 1 127 | file = 4 128 | case E3: 129 | rank = 2 130 | file = 4 131 | case E4: 132 | rank = 3 133 | file = 4 134 | case E5: 135 | rank = 4 136 | file = 4 137 | case E6: 138 | rank = 5 139 | file = 4 140 | case E7: 141 | rank = 6 142 | file = 4 143 | case E8: 144 | rank = 7 145 | file = 4 146 | case F1: 147 | rank = 0 148 | file = 5 149 | case F2: 150 | rank = 1 151 | file = 5 152 | case F3: 153 | rank = 2 154 | file = 5 155 | case F4: 156 | rank = 3 157 | file = 5 158 | case F5: 159 | rank = 4 160 | file = 5 161 | case F6: 162 | rank = 5 163 | file = 5 164 | case F7: 165 | rank = 6 166 | file = 5 167 | case F8: 168 | rank = 7 169 | file = 5 170 | case G1: 171 | rank = 0 172 | file = 6 173 | case G2: 174 | rank = 1 175 | file = 6 176 | case G3: 177 | rank = 2 178 | file = 6 179 | case G4: 180 | rank = 3 181 | file = 6 182 | case G5: 183 | rank = 4 184 | file = 6 185 | case G6: 186 | rank = 5 187 | file = 6 188 | case G7: 189 | rank = 6 190 | file = 6 191 | case G8: 192 | rank = 7 193 | file = 6 194 | case H1: 195 | rank = 0 196 | file = 7 197 | case H2: 198 | rank = 1 199 | file = 7 200 | case H3: 201 | rank = 2 202 | file = 7 203 | case H4: 204 | rank = 3 205 | file = 7 206 | case H5: 207 | rank = 4 208 | file = 7 209 | case H6: 210 | rank = 5 211 | file = 7 212 | case H7: 213 | rank = 6 214 | file = 7 215 | case H8: 216 | rank = 7 217 | file = 7 218 | } 219 | 220 | return rank, file 221 | } 222 | 223 | func GetBoardPos(rank int, file int) BoardPosition { 224 | pos := INVALID_BOARD_POSITION 225 | if rank < 0 || rank >= 8 || file < 0 || file >= 8 { 226 | return pos 227 | } 228 | switch rank { 229 | case 0: 230 | switch file { 231 | case 0: 232 | pos = A1 233 | break 234 | case 1: 235 | pos = B1 236 | break 237 | case 2: 238 | pos = C1 239 | break 240 | case 3: 241 | pos = D1 242 | break 243 | case 4: 244 | pos = E1 245 | break 246 | case 5: 247 | pos = F1 248 | break 249 | case 6: 250 | pos = G1 251 | break 252 | case 7: 253 | pos = H1 254 | break 255 | } 256 | break 257 | case 1: 258 | switch file { 259 | case 0: 260 | pos = A2 261 | break 262 | case 1: 263 | pos = B2 264 | break 265 | case 2: 266 | pos = C2 267 | break 268 | case 3: 269 | pos = D2 270 | break 271 | case 4: 272 | pos = E2 273 | break 274 | case 5: 275 | pos = F2 276 | break 277 | case 6: 278 | pos = G2 279 | break 280 | case 7: 281 | pos = H2 282 | break 283 | } 284 | break 285 | case 2: 286 | switch file { 287 | case 0: 288 | pos = A3 289 | break 290 | case 1: 291 | pos = B3 292 | break 293 | case 2: 294 | pos = C3 295 | break 296 | case 3: 297 | pos = D3 298 | break 299 | case 4: 300 | pos = E3 301 | break 302 | case 5: 303 | pos = F3 304 | break 305 | case 6: 306 | pos = G3 307 | break 308 | case 7: 309 | pos = H3 310 | break 311 | } 312 | break 313 | case 3: 314 | switch file { 315 | case 0: 316 | pos = A4 317 | break 318 | case 1: 319 | pos = B4 320 | break 321 | case 2: 322 | pos = C4 323 | break 324 | case 3: 325 | pos = D4 326 | break 327 | case 4: 328 | pos = E4 329 | break 330 | case 5: 331 | pos = F4 332 | break 333 | case 6: 334 | pos = G4 335 | break 336 | case 7: 337 | pos = H4 338 | break 339 | } 340 | break 341 | case 4: 342 | switch file { 343 | case 0: 344 | pos = A5 345 | break 346 | case 1: 347 | pos = B5 348 | break 349 | case 2: 350 | pos = C5 351 | break 352 | case 3: 353 | pos = D5 354 | break 355 | case 4: 356 | pos = E5 357 | break 358 | case 5: 359 | pos = F5 360 | break 361 | case 6: 362 | pos = G5 363 | break 364 | case 7: 365 | pos = H5 366 | break 367 | } 368 | break 369 | case 5: 370 | switch file { 371 | case 0: 372 | pos = A6 373 | break 374 | case 1: 375 | pos = B6 376 | break 377 | case 2: 378 | pos = C6 379 | break 380 | case 3: 381 | pos = D6 382 | break 383 | case 4: 384 | pos = E6 385 | break 386 | case 5: 387 | pos = F6 388 | break 389 | case 6: 390 | pos = G6 391 | break 392 | case 7: 393 | pos = H6 394 | break 395 | } 396 | break 397 | case 6: 398 | switch file { 399 | case 0: 400 | pos = A7 401 | break 402 | case 1: 403 | pos = B7 404 | break 405 | case 2: 406 | pos = C7 407 | break 408 | case 3: 409 | pos = D7 410 | break 411 | case 4: 412 | pos = E7 413 | break 414 | case 5: 415 | pos = F7 416 | break 417 | case 6: 418 | pos = G7 419 | break 420 | case 7: 421 | pos = H7 422 | break 423 | } 424 | break 425 | case 7: 426 | switch file { 427 | case 0: 428 | pos = A8 429 | break 430 | case 1: 431 | pos = B8 432 | break 433 | case 2: 434 | pos = C8 435 | break 436 | case 3: 437 | pos = D8 438 | break 439 | case 4: 440 | pos = E8 441 | break 442 | case 5: 443 | pos = F8 444 | break 445 | case 6: 446 | pos = G8 447 | break 448 | case 7: 449 | pos = H8 450 | break 451 | } 452 | break 453 | } 454 | 455 | return pos 456 | } 457 | 458 | func MovesContainMove(move Move, moves []*Move) bool { 459 | retVal := false 460 | 461 | for _, m := range moves { 462 | if m.StartPos == move.StartPos && 463 | m.EndPos == move.EndPos { 464 | retVal = true 465 | break 466 | } 467 | } 468 | 469 | return retVal 470 | } 471 | 472 | func MovesContainEndPos(endPos BoardPosition, moves []*Move) bool { 473 | retVal := false 474 | 475 | for _, m := range moves { 476 | if m.EndPos == endPos { 477 | retVal = true 478 | break 479 | } 480 | } 481 | 482 | return retVal 483 | } 484 | 485 | func OppositeColor(color PieceColor) PieceColor { 486 | if color == White { 487 | return Black 488 | } 489 | 490 | return White 491 | } 492 | 493 | func GetMoveFromUserString(startStr string, endStr string) *Move { 494 | var move *Move = nil 495 | startPos := GetPositionFromString(startStr) 496 | endPos := GetPositionFromString(endStr) 497 | if startPos != INVALID_BOARD_POSITION && endPos != INVALID_BOARD_POSITION { 498 | move = NewMove(startPos, endPos) 499 | } 500 | 501 | return move 502 | } 503 | 504 | func GetPositionFromString(inStr string) BoardPosition { 505 | var pos BoardPosition = INVALID_BOARD_POSITION 506 | if inStr == "A1" { 507 | pos = A1 508 | } else if inStr == "A2" { 509 | pos = A2 510 | } else if inStr == "A3" { 511 | pos = A3 512 | } else if inStr == "A4" { 513 | pos = A4 514 | } else if inStr == "A5" { 515 | pos = A5 516 | } else if inStr == "A6" { 517 | pos = A6 518 | } else if inStr == "A7" { 519 | pos = A7 520 | } else if inStr == "A8" { 521 | pos = A8 522 | } else if inStr == "B1" { 523 | pos = B1 524 | } else if inStr == "B2" { 525 | pos = B2 526 | } else if inStr == "B3" { 527 | pos = B3 528 | } else if inStr == "B4" { 529 | pos = B4 530 | } else if inStr == "B5" { 531 | pos = B5 532 | } else if inStr == "B6" { 533 | pos = B6 534 | } else if inStr == "B7" { 535 | pos = B7 536 | } else if inStr == "B8" { 537 | pos = B8 538 | } else if inStr == "C1" { 539 | pos = C1 540 | } else if inStr == "C2" { 541 | pos = C2 542 | } else if inStr == "C3" { 543 | pos = C3 544 | } else if inStr == "C4" { 545 | pos = C4 546 | } else if inStr == "C5" { 547 | pos = C5 548 | } else if inStr == "C6" { 549 | pos = C6 550 | } else if inStr == "C7" { 551 | pos = C7 552 | } else if inStr == "C8" { 553 | pos = C8 554 | } else if inStr == "D1" { 555 | pos = D1 556 | } else if inStr == "D2" { 557 | pos = D2 558 | } else if inStr == "D3" { 559 | pos = D3 560 | } else if inStr == "D4" { 561 | pos = D4 562 | } else if inStr == "D5" { 563 | pos = D5 564 | } else if inStr == "D6" { 565 | pos = D6 566 | } else if inStr == "D7" { 567 | pos = D7 568 | } else if inStr == "D8" { 569 | pos = D8 570 | } else if inStr == "E1" { 571 | pos = E1 572 | } else if inStr == "E2" { 573 | pos = E2 574 | } else if inStr == "E3" { 575 | pos = E3 576 | } else if inStr == "E4" { 577 | pos = E4 578 | } else if inStr == "E5" { 579 | pos = E5 580 | } else if inStr == "E6" { 581 | pos = E6 582 | } else if inStr == "E7" { 583 | pos = E7 584 | } else if inStr == "E8" { 585 | pos = E8 586 | } else if inStr == "F1" { 587 | pos = F1 588 | } else if inStr == "F2" { 589 | pos = F2 590 | } else if inStr == "F3" { 591 | pos = F3 592 | } else if inStr == "F4" { 593 | pos = F4 594 | } else if inStr == "F5" { 595 | pos = F5 596 | } else if inStr == "F6" { 597 | pos = F6 598 | } else if inStr == "F7" { 599 | pos = F7 600 | } else if inStr == "F8" { 601 | pos = F8 602 | } else if inStr == "G1" { 603 | pos = G1 604 | } else if inStr == "G2" { 605 | pos = G2 606 | } else if inStr == "G3" { 607 | pos = G3 608 | } else if inStr == "G4" { 609 | pos = G4 610 | } else if inStr == "G5" { 611 | pos = G5 612 | } else if inStr == "G6" { 613 | pos = G6 614 | } else if inStr == "G7" { 615 | pos = G7 616 | } else if inStr == "G8" { 617 | pos = G8 618 | } else if inStr == "H1" { 619 | pos = H1 620 | } else if inStr == "H2" { 621 | pos = H2 622 | } else if inStr == "H3" { 623 | pos = H3 624 | } else if inStr == "H4" { 625 | pos = H4 626 | } else if inStr == "H5" { 627 | pos = H5 628 | } else if inStr == "H6" { 629 | pos = H6 630 | } else if inStr == "H7" { 631 | pos = H7 632 | } else if inStr == "H8" { 633 | pos = H8 634 | } 635 | 636 | return pos 637 | } 638 | 639 | func GetPositionString(pos BoardPosition) string { 640 | var retStr string = "IV" 641 | if pos == A1 { 642 | retStr = "A1" 643 | } else if pos == A2 { 644 | retStr = "A2" 645 | } else if pos == A3 { 646 | retStr = "A3" 647 | } else if pos == A4 { 648 | retStr = "A4" 649 | } else if pos == A5 { 650 | retStr = "A5" 651 | } else if pos == A6 { 652 | retStr = "A6" 653 | } else if pos == A7 { 654 | retStr = "A7" 655 | } else if pos == A8 { 656 | retStr = "A8" 657 | } else if pos == B1 { 658 | retStr = "B1" 659 | } else if pos == B2 { 660 | retStr = "B2" 661 | } else if pos == B3 { 662 | retStr = "B3" 663 | } else if pos == B4 { 664 | retStr = "B4" 665 | } else if pos == B5 { 666 | retStr = "B5" 667 | } else if pos == B6 { 668 | retStr = "B6" 669 | } else if pos == B7 { 670 | retStr = "B7" 671 | } else if pos == B8 { 672 | retStr = "B8" 673 | } else if pos == C1 { 674 | retStr = "C1" 675 | } else if pos == C2 { 676 | retStr = "C2" 677 | } else if pos == C3 { 678 | retStr = "C3" 679 | } else if pos == C4 { 680 | retStr = "C4" 681 | } else if pos == C5 { 682 | retStr = "C5" 683 | } else if pos == C6 { 684 | retStr = "C6" 685 | } else if pos == C7 { 686 | retStr = "C7" 687 | } else if pos == C8 { 688 | retStr = "C8" 689 | } else if pos == D1 { 690 | retStr = "D1" 691 | } else if pos == D2 { 692 | retStr = "D2" 693 | } else if pos == D3 { 694 | retStr = "D3" 695 | } else if pos == D4 { 696 | retStr = "D4" 697 | } else if pos == D5 { 698 | retStr = "D5" 699 | } else if pos == D6 { 700 | retStr = "D6" 701 | } else if pos == D7 { 702 | retStr = "D7" 703 | } else if pos == D8 { 704 | retStr = "D8" 705 | } else if pos == E1 { 706 | retStr = "E1" 707 | } else if pos == E2 { 708 | retStr = "E2" 709 | } else if pos == E3 { 710 | retStr = "E3" 711 | } else if pos == E4 { 712 | retStr = "E4" 713 | } else if pos == E5 { 714 | retStr = "E5" 715 | } else if pos == E6 { 716 | retStr = "E6" 717 | } else if pos == E7 { 718 | retStr = "E7" 719 | } else if pos == E8 { 720 | retStr = "E8" 721 | } else if pos == F1 { 722 | retStr = "F1" 723 | } else if pos == F2 { 724 | retStr = "F2" 725 | } else if pos == F3 { 726 | retStr = "F3" 727 | } else if pos == F4 { 728 | retStr = "F4" 729 | } else if pos == F5 { 730 | retStr = "F5" 731 | } else if pos == F6 { 732 | retStr = "F6" 733 | } else if pos == F7 { 734 | retStr = "F7" 735 | } else if pos == F8 { 736 | retStr = "F8" 737 | } else if pos == G1 { 738 | retStr = "G1" 739 | } else if pos == G2 { 740 | retStr = "G2" 741 | } else if pos == G3 { 742 | retStr = "G3" 743 | } else if pos == G4 { 744 | retStr = "G4" 745 | } else if pos == G5 { 746 | retStr = "G5" 747 | } else if pos == G6 { 748 | retStr = "G6" 749 | } else if pos == G7 { 750 | retStr = "G7" 751 | } else if pos == G8 { 752 | retStr = "G8" 753 | } else if pos == H1 { 754 | retStr = "H1" 755 | } else if pos == H2 { 756 | retStr = "H2" 757 | } else if pos == H3 { 758 | retStr = "H3" 759 | } else if pos == H4 { 760 | retStr = "H4" 761 | } else if pos == H5 { 762 | retStr = "H5" 763 | } else if pos == H6 { 764 | retStr = "H6" 765 | } else if pos == H7 { 766 | retStr = "H7" 767 | } else if pos == H8 { 768 | retStr = "H8" 769 | } 770 | 771 | return retStr 772 | } 773 | 774 | func GetMoveString(move Move) string { 775 | return fmt.Sprintf("%s - %s", GetPositionString(move.StartPos), GetPositionString(move.EndPos)) 776 | } 777 | -------------------------------------------------------------------------------- /chess_engine/utils_test.go: -------------------------------------------------------------------------------- 1 | package chess_engine 2 | 3 | import "testing" 4 | 5 | func TestGetRankAndFileFromPos(t *testing.T) { 6 | rank, file := GetRankFile(A1) 7 | if rank != 0 || file != 0 { 8 | t.Errorf("Error! GetRankFile return invalid values for A1!") 9 | } 10 | 11 | rank, file = GetRankFile(A2) 12 | if rank != 1 || file != 0 { 13 | t.Errorf("Error! GetRankFile return invalid values for A2!") 14 | } 15 | 16 | rank, file = GetRankFile(A3) 17 | if rank != 2 || file != 0 { 18 | t.Errorf("Error! GetRankFile return invalid values for A3!") 19 | } 20 | 21 | rank, file = GetRankFile(A4) 22 | if rank != 3 || file != 0 { 23 | t.Errorf("Error! GetRankFile return invalid values for A4!") 24 | } 25 | 26 | rank, file = GetRankFile(A5) 27 | if rank != 4 || file != 0 { 28 | t.Errorf("Error! GetRankFile return invalid values for A5!") 29 | } 30 | 31 | rank, file = GetRankFile(A6) 32 | if rank != 5 || file != 0 { 33 | t.Errorf("Error! GetRankFile return invalid values for A6!") 34 | } 35 | 36 | rank, file = GetRankFile(A7) 37 | if rank != 6 || file != 0 { 38 | t.Errorf("Error! GetRankFile return invalid values for A7!") 39 | } 40 | 41 | rank, file = GetRankFile(A8) 42 | if rank != 7 || file != 0 { 43 | t.Errorf("Error! GetRankFile return invalid values for A8!") 44 | } 45 | 46 | rank, file = GetRankFile(B1) 47 | if rank != 0 || file != 1 { 48 | t.Errorf("Error! GetRankFile return invalid values for B1!") 49 | } 50 | 51 | rank, file = GetRankFile(B2) 52 | if rank != 1 || file != 1 { 53 | t.Errorf("Error! GetRankFile return invalid values for B2!") 54 | } 55 | 56 | rank, file = GetRankFile(B3) 57 | if rank != 2 || file != 1 { 58 | t.Errorf("Error! GetRankFile return invalid values for B3!") 59 | } 60 | 61 | rank, file = GetRankFile(B4) 62 | if rank != 3 || file != 1 { 63 | t.Errorf("Error! GetRankFile return invalid values for B4!") 64 | } 65 | 66 | rank, file = GetRankFile(B5) 67 | if rank != 4 || file != 1 { 68 | t.Errorf("Error! GetRankFile return invalid values for B5!") 69 | } 70 | 71 | rank, file = GetRankFile(B6) 72 | if rank != 5 || file != 1 { 73 | t.Errorf("Error! GetRankFile return invalid values for B6!") 74 | } 75 | 76 | rank, file = GetRankFile(B7) 77 | if rank != 6 || file != 1 { 78 | t.Errorf("Error! GetRankFile return invalid values for B7!") 79 | } 80 | 81 | rank, file = GetRankFile(B8) 82 | if rank != 7 || file != 1 { 83 | t.Errorf("Error! GetRankFile return invalid values for B8!") 84 | } 85 | 86 | rank, file = GetRankFile(C1) 87 | if rank != 0 || file != 2 { 88 | t.Errorf("Error! GetRankFile return invalid values for C1!") 89 | } 90 | 91 | rank, file = GetRankFile(C2) 92 | if rank != 1 || file != 2 { 93 | t.Errorf("Error! GetRankFile return invalid values for C2!") 94 | } 95 | 96 | rank, file = GetRankFile(C3) 97 | if rank != 2 || file != 2 { 98 | t.Errorf("Error! GetRankFile return invalid values for C3!") 99 | } 100 | 101 | rank, file = GetRankFile(C4) 102 | if rank != 3 || file != 2 { 103 | t.Errorf("Error! GetRankFile return invalid values for C4!") 104 | } 105 | 106 | rank, file = GetRankFile(C5) 107 | if rank != 4 || file != 2 { 108 | t.Errorf("Error! GetRankFile return invalid values for C5!") 109 | } 110 | 111 | rank, file = GetRankFile(C6) 112 | if rank != 5 || file != 2 { 113 | t.Errorf("Error! GetRankFile return invalid values for C6!") 114 | } 115 | 116 | rank, file = GetRankFile(C7) 117 | if rank != 6 || file != 2 { 118 | t.Errorf("Error! GetRankFile return invalid values for C7!") 119 | } 120 | 121 | rank, file = GetRankFile(C8) 122 | if rank != 7 || file != 2 { 123 | t.Errorf("Error! GetRankFile return invalid values for C8!") 124 | } 125 | 126 | rank, file = GetRankFile(D1) 127 | if rank != 0 || file != 3 { 128 | t.Errorf("Error! GetRankFile return invalid values for D1!") 129 | } 130 | 131 | rank, file = GetRankFile(D2) 132 | if rank != 1 || file != 3 { 133 | t.Errorf("Error! GetRankFile return invalid values for D2!") 134 | } 135 | 136 | rank, file = GetRankFile(D3) 137 | if rank != 2 || file != 3 { 138 | t.Errorf("Error! GetRankFile return invalid values for D3!") 139 | } 140 | 141 | rank, file = GetRankFile(D4) 142 | if rank != 3 || file != 3 { 143 | t.Errorf("Error! GetRankFile return invalid values for D4!") 144 | } 145 | 146 | rank, file = GetRankFile(D5) 147 | if rank != 4 || file != 3 { 148 | t.Errorf("Error! GetRankFile return invalid values for D5!") 149 | } 150 | 151 | rank, file = GetRankFile(D6) 152 | if rank != 5 || file != 3 { 153 | t.Errorf("Error! GetRankFile return invalid values for D6!") 154 | } 155 | 156 | rank, file = GetRankFile(D7) 157 | if rank != 6 || file != 3 { 158 | t.Errorf("Error! GetRankFile return invalid values for D7!") 159 | } 160 | 161 | rank, file = GetRankFile(D8) 162 | if rank != 7 || file != 3 { 163 | t.Errorf("Error! GetRankFile return invalid values for D8!") 164 | } 165 | 166 | rank, file = GetRankFile(E1) 167 | if rank != 0 || file != 4 { 168 | t.Errorf("Error! GetRankFile return invalid values for E1!") 169 | } 170 | 171 | rank, file = GetRankFile(E2) 172 | if rank != 1 || file != 4 { 173 | t.Errorf("Error! GetRankFile return invalid values for E2!") 174 | } 175 | 176 | rank, file = GetRankFile(E3) 177 | if rank != 2 || file != 4 { 178 | t.Errorf("Error! GetRankFile return invalid values for E3!") 179 | } 180 | 181 | rank, file = GetRankFile(E4) 182 | if rank != 3 || file != 4 { 183 | t.Errorf("Error! GetRankFile return invalid values for E4!") 184 | } 185 | 186 | rank, file = GetRankFile(E5) 187 | if rank != 4 || file != 4 { 188 | t.Errorf("Error! GetRankFile return invalid values for E5!") 189 | } 190 | 191 | rank, file = GetRankFile(E6) 192 | if rank != 5 || file != 4 { 193 | t.Errorf("Error! GetRankFile return invalid values for E6!") 194 | } 195 | 196 | rank, file = GetRankFile(E7) 197 | if rank != 6 || file != 4 { 198 | t.Errorf("Error! GetRankFile return invalid values for E7!") 199 | } 200 | 201 | rank, file = GetRankFile(E8) 202 | if rank != 7 || file != 4 { 203 | t.Errorf("Error! GetRankFile return invalid values for E8!") 204 | } 205 | 206 | rank, file = GetRankFile(F1) 207 | if rank != 0 || file != 5 { 208 | t.Errorf("Error! GetRankFile return invalid values for F1!") 209 | } 210 | 211 | rank, file = GetRankFile(F2) 212 | if rank != 1 || file != 5 { 213 | t.Errorf("Error! GetRankFile return invalid values for F2!") 214 | } 215 | 216 | rank, file = GetRankFile(F3) 217 | if rank != 2 || file != 5 { 218 | t.Errorf("Error! GetRankFile return invalid values for F3!") 219 | } 220 | 221 | rank, file = GetRankFile(F4) 222 | if rank != 3 || file != 5 { 223 | t.Errorf("Error! GetRankFile return invalid values for F4!") 224 | } 225 | 226 | rank, file = GetRankFile(F5) 227 | if rank != 4 || file != 5 { 228 | t.Errorf("Error! GetRankFile return invalid values for F5!") 229 | } 230 | 231 | rank, file = GetRankFile(F6) 232 | if rank != 5 || file != 5 { 233 | t.Errorf("Error! GetRankFile return invalid values for F6!") 234 | } 235 | 236 | rank, file = GetRankFile(F7) 237 | if rank != 6 || file != 5 { 238 | t.Errorf("Error! GetRankFile return invalid values for F7!") 239 | } 240 | 241 | rank, file = GetRankFile(F8) 242 | if rank != 7 || file != 5 { 243 | t.Errorf("Error! GetRankFile return invalid values for F8!") 244 | } 245 | 246 | rank, file = GetRankFile(G1) 247 | if rank != 0 || file != 6 { 248 | t.Errorf("Error! GetRankFile return invalid values for G1!") 249 | } 250 | 251 | rank, file = GetRankFile(G2) 252 | if rank != 1 || file != 6 { 253 | t.Errorf("Error! GetRankFile return invalid values for G2!") 254 | } 255 | 256 | rank, file = GetRankFile(G3) 257 | if rank != 2 || file != 6 { 258 | t.Errorf("Error! GetRankFile return invalid values for G3!") 259 | } 260 | 261 | rank, file = GetRankFile(G4) 262 | if rank != 3 || file != 6 { 263 | t.Errorf("Error! GetRankFile return invalid values for G4!") 264 | } 265 | 266 | rank, file = GetRankFile(G5) 267 | if rank != 4 || file != 6 { 268 | t.Errorf("Error! GetRankFile return invalid values for G5!") 269 | } 270 | 271 | rank, file = GetRankFile(G6) 272 | if rank != 5 || file != 6 { 273 | t.Errorf("Error! GetRankFile return invalid values for G6!") 274 | } 275 | 276 | rank, file = GetRankFile(G7) 277 | if rank != 6 || file != 6 { 278 | t.Errorf("Error! GetRankFile return invalid values for G7!") 279 | } 280 | 281 | rank, file = GetRankFile(G8) 282 | if rank != 7 || file != 6 { 283 | t.Errorf("Error! GetRankFile return invalid values for G8!") 284 | } 285 | 286 | rank, file = GetRankFile(H1) 287 | if rank != 0 || file != 7 { 288 | t.Errorf("Error! GetRankFile return invalid values for H1!") 289 | } 290 | 291 | rank, file = GetRankFile(H2) 292 | if rank != 1 || file != 7 { 293 | t.Errorf("Error! GetRankFile return invalid values for H2!") 294 | } 295 | 296 | rank, file = GetRankFile(H3) 297 | if rank != 2 || file != 7 { 298 | t.Errorf("Error! GetRankFile return invalid values for H3!") 299 | } 300 | 301 | rank, file = GetRankFile(H4) 302 | if rank != 3 || file != 7 { 303 | t.Errorf("Error! GetRankFile return invalid values for H4!") 304 | } 305 | 306 | rank, file = GetRankFile(H5) 307 | if rank != 4 || file != 7 { 308 | t.Errorf("Error! GetRankFile return invalid values for H5!") 309 | } 310 | 311 | rank, file = GetRankFile(H6) 312 | if rank != 5 || file != 7 { 313 | t.Errorf("Error! GetRankFile return invalid values for H6!") 314 | } 315 | 316 | rank, file = GetRankFile(H7) 317 | if rank != 6 || file != 7 { 318 | t.Errorf("Error! GetRankFile return invalid values for H7!") 319 | } 320 | 321 | rank, file = GetRankFile(H8) 322 | if rank != 7 || file != 7 { 323 | t.Errorf("Error! GetRankFile return invalid values for H8!") 324 | } 325 | 326 | } 327 | -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # chessboard.js Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [1.0.0] - 2019-06-11 6 | - Orientation methods now return current orientation. [Issue #64] 7 | - Drop support for IE8 8 | - Do not check for `window.JSON` (Error #1004) 9 | - Rename `ChessBoard` to `Chessboard` (`ChessBoard` is still supported, however) 10 | - id query selectors are now supported as the first argument to `Chessboard()` 11 | - Remove Error #1002 12 | - Format code according to [StandardJS] 13 | - Bump minimum jQuery version to 1.8.3 14 | - Throttle piece drag functions 15 | 16 | ## [0.3.0] - 2013-08-10 17 | - Added `appearSpeed` animation config property 18 | - Added `onSnapbackEnd` event 19 | - Added `onMoveEnd` event 20 | 21 | ## [0.2.0] - 2013-08-05 22 | - Added `onMouseoverSquare` and `onMouseoutSquare` events 23 | - Added `onSnapEnd` event 24 | - Added square code as CSS class on the squares 25 | - Added [chess.js] integration examples 26 | 27 | ## [0.1.0] - 2013-05-21 28 | - Initial release 29 | 30 | [chess.js]:https://github.com/jhlywa/chess.js 31 | [Issue #64]:https://github.com/oakmac/chessboardjs/issues/64 32 | [StandardJS]:https://standardjs.com/ 33 | -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2019 Chris Oakman 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/README.md: -------------------------------------------------------------------------------- 1 | # chessboard.js 2 | 3 | chessboard.js is a JavaScript chessboard component. It depends on [jQuery]. 4 | 5 | Please see [chessboardjs.com] for documentation and examples. 6 | 7 | ## What is chessboard.js? 8 | 9 | chessboard.js is a JavaScript chessboard component with a flexible "just a 10 | board" API that 11 | 12 | chessboard.js is a standalone JavaScript Chess Board. It is designed to be "just 13 | a board" and expose a powerful API so that it can be used in different ways. 14 | Here's a non-exhaustive list of things you can do with chessboard.js: 15 | 16 | - Use chessboard.js to show game positions alongside your expert commentary. 17 | - Use chessboard.js to have a tactics website where users have to guess the best 18 | move. 19 | - Integrate chessboard.js and [chess.js] with a PGN database and allow people to 20 | search and playback games (see [Example 5000]) 21 | - Build a chess server and have users play their games out using the 22 | chessboard.js board. 23 | 24 | chessboard.js is flexible enough to handle any of these situations with relative 25 | ease. 26 | 27 | ## What can chessboard.js **not** do? 28 | 29 | The scope of chessboard.js is limited to "just a board." This is intentional and 30 | makes chessboard.js flexible for handling a multitude of chess-related problems. 31 | 32 | This is a common source of confusion for new users. [remove?] 33 | 34 | Specifically, chessboard.js does not understand anything about how the game of 35 | chess is played: how a knight moves, who's turn is it, is White in check?, etc. 36 | 37 | Fortunately, the powerful [chess.js] library deals with exactly this sort of 38 | problem domain and plays nicely with chessboard.js's flexible API. Some examples 39 | of chessboard.js combined with chess.js: 5000, 5001, 5002 40 | 41 | Please see the powerful [chess.js] library for an API to deal with these sorts 42 | of questions. 43 | 44 | 45 | This logic is distinct from the logic of the board. Please see the powerful 46 | [chess.js] library for this aspect of your application. 47 | 48 | 49 | 50 | Here is a list of things that chessboard.js is **not**: 51 | 52 | - A chess engine 53 | - A legal move validator 54 | - A PGN parser 55 | 56 | chessboard.js is designed to work well with any of those things, but the idea 57 | behind chessboard.js is that the logic that controls the board should be 58 | independent of those other problems. 59 | 60 | ## Docs and Examples 61 | 62 | - Docs - 63 | - Examples - 64 | 65 | ## Developer Tools 66 | 67 | ```sh 68 | # create a build in the build/ directory 69 | npm run build 70 | 71 | # re-build the website 72 | npm run website 73 | ``` 74 | 75 | ## License 76 | 77 | [MIT License](LICENSE.md) 78 | 79 | [jQuery]:https://jquery.com/ 80 | [chessboardjs.com]:http://chessboardjs.com 81 | [chess.js]:https://github.com/jhlywa/chess.js 82 | [Example 5000]:http://chessboardjs.com/examples#5000 83 | -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/css/chessboard-1.0.0.css: -------------------------------------------------------------------------------- 1 | /*! chessboard.js v1.0.0 | (c) 2019 Chris Oakman | MIT License chessboardjs.com/license */ 2 | 3 | .clearfix-7da63 { 4 | clear: both; 5 | } 6 | 7 | .board-b72b1 { 8 | border: 2px solid #404040; 9 | box-sizing: content-box; 10 | } 11 | 12 | .square-55d63 { 13 | float: left; 14 | position: relative; 15 | 16 | /* disable any native browser highlighting */ 17 | -webkit-touch-callout: none; 18 | -webkit-user-select: none; 19 | -khtml-user-select: none; 20 | -moz-user-select: none; 21 | -ms-user-select: none; 22 | user-select: none; 23 | } 24 | 25 | .white-1e1d7 { 26 | background-color: #f0d9b5; 27 | color: #b58863; 28 | } 29 | 30 | .black-3c85d { 31 | background-color: #b58863; 32 | color: #f0d9b5; 33 | } 34 | 35 | .highlight1-32417, .highlight2-9c5d2 { 36 | box-shadow: inset 0 0 3px 3px yellow; 37 | } 38 | 39 | .notation-322f9 { 40 | cursor: default; 41 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 42 | font-size: 14px; 43 | position: absolute; 44 | } 45 | 46 | .alpha-d2270 { 47 | bottom: 1px; 48 | right: 3px; 49 | } 50 | 51 | .numeric-fc462 { 52 | top: 2px; 53 | left: 2px; 54 | } 55 | -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/css/chessboard-1.0.0.min.css: -------------------------------------------------------------------------------- 1 | /*! chessboard.js v1.0.0 | (c) 2019 Chris Oakman | MIT License chessboardjs.com/license */ 2 | .clearfix-7da63{clear:both}.board-b72b1{border:2px solid #404040;box-sizing:content-box}.square-55d63{float:left;position:relative;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.white-1e1d7{background-color:#f0d9b5;color:#b58863}.black-3c85d{background-color:#b58863;color:#f0d9b5}.highlight1-32417,.highlight2-9c5d2{box-shadow:inset 0 0 3px 3px #ff0}.notation-322f9{cursor:default;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;position:absolute}.alpha-d2270{bottom:1px;right:3px}.numeric-fc462{top:2px;left:2px} -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/img/chesspieces/wikipedia/bB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/chessboardjs/img/chesspieces/wikipedia/bB.png -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/img/chesspieces/wikipedia/bK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/chessboardjs/img/chesspieces/wikipedia/bK.png -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/img/chesspieces/wikipedia/bN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/chessboardjs/img/chesspieces/wikipedia/bN.png -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/img/chesspieces/wikipedia/bP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/chessboardjs/img/chesspieces/wikipedia/bP.png -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/img/chesspieces/wikipedia/bQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/chessboardjs/img/chesspieces/wikipedia/bQ.png -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/img/chesspieces/wikipedia/bR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/chessboardjs/img/chesspieces/wikipedia/bR.png -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/img/chesspieces/wikipedia/wB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/chessboardjs/img/chesspieces/wikipedia/wB.png -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/img/chesspieces/wikipedia/wK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/chessboardjs/img/chesspieces/wikipedia/wK.png -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/img/chesspieces/wikipedia/wN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/chessboardjs/img/chesspieces/wikipedia/wN.png -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/img/chesspieces/wikipedia/wP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/chessboardjs/img/chesspieces/wikipedia/wP.png -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/img/chesspieces/wikipedia/wQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/chessboardjs/img/chesspieces/wikipedia/wQ.png -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/img/chesspieces/wikipedia/wR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/chessboardjs/img/chesspieces/wikipedia/wR.png -------------------------------------------------------------------------------- /chess_web_page/chessboardjs/js/chessboard-1.0.0.min.js: -------------------------------------------------------------------------------- 1 | /*! chessboard.js v1.0.0 | (c) 2019 Chris Oakman | MIT License chessboardjs.com/license */ 2 | !function(){"use strict";var z=window.jQuery,F="abcdefgh".split(""),r=20,A="…",W="1.8.3",e="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR",G=pe(e),n=200,t=200,o=60,a=30,i=100,H={};function V(e,r,n){function t(){o=0,a&&(a=!1,s())}var o=0,a=!1,i=[],s=function(){o=window.setTimeout(t,r),e.apply(n,i)};return function(e){i=arguments,o?a=!0:s()}}function Z(){return"xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx".replace(/x/g,function(e){return(16*Math.random()|0).toString(16)})}function _(e){return JSON.parse(JSON.stringify(e))}function s(e){var r=e.split(".");return{major:parseInt(r[0],10),minor:parseInt(r[1],10),patch:parseInt(r[2],10)}}function ee(e,r){for(var n in r)if(r.hasOwnProperty(n))for(var t="{"+n+"}",o=r[n];-1!==e.indexOf(t);)e=e.replace(t,o);return e}function re(e){return"string"==typeof e}function ne(e){return"function"==typeof e}function p(e){return"number"==typeof e&&isFinite(e)&&Math.floor(e)===e}function c(e){return"fast"===e||"slow"===e||!!p(e)&&0<=e}function te(e){if(!re(e))return!1;var r=e.split("-");return 2===r.length&&(oe(r[0])&&oe(r[1]))}function oe(e){return re(e)&&-1!==e.search(/^[a-h][1-8]$/)}function u(e){return re(e)&&-1!==e.search(/^[bw][KQRNBP]$/)}function ae(e){if(!re(e))return!1;var r=(e=function(e){return e.replace(/8/g,"11111111").replace(/7/g,"1111111").replace(/6/g,"111111").replace(/5/g,"11111").replace(/4/g,"1111").replace(/3/g,"111").replace(/2/g,"11")}(e=e.replace(/ .+$/,""))).split("/");if(8!==r.length)return!1;for(var n=0;n<8;n++)if(8!==r[n].length||-1!==r[n].search(/[^kqrnbpKQRNBP1]/))return!1;return!0}function ie(e){if(!z.isPlainObject(e))return!1;for(var r in e)if(e.hasOwnProperty(r)&&(!oe(r)||!u(e[r])))return!1;return!0}function se(){return typeof window.$&&z.fn&&z.fn.jquery&&function(e,r){e=s(e),r=s(r);var n=1e5*e.major*1e5+1e5*e.minor+e.patch;return 1e5*r.major*1e5+1e5*r.minor+r.patch<=n}(z.fn.jquery,W)}function pe(e){if(!ae(e))return!1;for(var r,n=(e=e.replace(/ .+$/,"")).split("/"),t={},o=8,a=0;a<8;a++){for(var i=n[a].split(""),s=0,p=0;p';for(var i=0;i<8;i++){var s=n[i]+t;r+='
',f.showNotation&&(("white"===e&&1===t||"black"===e&&8===t)&&(r+='
'+n[i]+"
"),0===i&&(r+='
'+t+"
")),r+="
",o="white"===o?"black":"white"}r+='
',o="white"===o?"black":"white","white"===e?t-=1:t+=1}return ee(r,H)}(p,f.showNotation)),T(),f.sparePieces&&("white"===p?(t.html(x("black")),o.html(x("white"))):(t.html(x("white")),o.html(x("black"))))}function k(e){var r=_(c),n=_(e);ce(r)!==ce(n)&&(ne(f.onChange)&&f.onChange(r,n),c=e)}function E(e,r){for(var n in w)if(w.hasOwnProperty(n)){var t=w[n];if(e>=t.left&&e=t.top&&r (http://chrisoakman.com/)", 3 | "name": "@chrisoakman/chessboardjs", 4 | "description": "JavaScript chessboard widget", 5 | "homepage": "https://chessboardjs.com", 6 | "license": "MIT", 7 | "version": "1.0.0", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/oakmac/chessboardjs.git" 11 | }, 12 | "files": ["dist/"], 13 | "dependencies": { 14 | "jquery": ">=3.4.1" 15 | }, 16 | "devDependencies": { 17 | "csso": "3.5.1", 18 | "fs-plus": "3.1.1", 19 | "kidif": "1.1.0", 20 | "mustache": "2.3.0", 21 | "standard": "10.0.2", 22 | "uglify-js": "3.6.0" 23 | }, 24 | "scripts": { 25 | "build": "standard lib/chessboard.js && node scripts/build.js", 26 | "standard": "standard --fix lib/*.js website/js/*.js", 27 | "website": "node scripts/website.js" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chess_web_page/img/chesspieces/wikipedia/bB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/img/chesspieces/wikipedia/bB.png -------------------------------------------------------------------------------- /chess_web_page/img/chesspieces/wikipedia/bK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/img/chesspieces/wikipedia/bK.png -------------------------------------------------------------------------------- /chess_web_page/img/chesspieces/wikipedia/bN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/img/chesspieces/wikipedia/bN.png -------------------------------------------------------------------------------- /chess_web_page/img/chesspieces/wikipedia/bP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/img/chesspieces/wikipedia/bP.png -------------------------------------------------------------------------------- /chess_web_page/img/chesspieces/wikipedia/bQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/img/chesspieces/wikipedia/bQ.png -------------------------------------------------------------------------------- /chess_web_page/img/chesspieces/wikipedia/bR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/img/chesspieces/wikipedia/bR.png -------------------------------------------------------------------------------- /chess_web_page/img/chesspieces/wikipedia/wB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/img/chesspieces/wikipedia/wB.png -------------------------------------------------------------------------------- /chess_web_page/img/chesspieces/wikipedia/wK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/img/chesspieces/wikipedia/wK.png -------------------------------------------------------------------------------- /chess_web_page/img/chesspieces/wikipedia/wN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/img/chesspieces/wikipedia/wN.png -------------------------------------------------------------------------------- /chess_web_page/img/chesspieces/wikipedia/wP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/img/chesspieces/wikipedia/wP.png -------------------------------------------------------------------------------- /chess_web_page/img/chesspieces/wikipedia/wQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/img/chesspieces/wikipedia/wQ.png -------------------------------------------------------------------------------- /chess_web_page/img/chesspieces/wikipedia/wR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbw317/chess_go/f7519d00aa23190bb64ec609a4060ad1df7b1be1/chess_web_page/img/chesspieces/wikipedia/wR.png -------------------------------------------------------------------------------- /chess_web_page/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Chess Go 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |

Chess Go

16 |
17 |
18 |
19 | This is a chess engine written in the Go programming language. Source code is available on github at 20 | https://github.com/rbw317/chess_go. 21 |
22 |
23 |
 
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | Moves 32 |
33 |
34 | 36 |
37 |
38 | 39 |
40 |
41 |
42 |
 
43 |
44 |
45 |
46 | 47 |   48 | 49 |   50 | User Color:  51 | 55 |
56 |
57 |
58 |
 
59 |
60 |
61 | 63 |
64 |
65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 309 | 310 | -------------------------------------------------------------------------------- /chess_web_service/chess_web_service_test.go: -------------------------------------------------------------------------------- 1 | package chess_web_service 2 | 3 | import ( 4 | "chess_go/chess_engine" 5 | "encoding/json" 6 | "io" 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | ) 11 | 12 | func TestGetInstance(t *testing.T) { 13 | ws := GetWebServiceInstance(":8080") 14 | 15 | if ws == nil { 16 | t.Errorf("Error! GetWebServiceInstance function returned nil for ws instance!") 17 | } 18 | 19 | ws2 := GetWebServiceInstance(":8080") 20 | 21 | if ws != ws2 { 22 | t.Errorf("Error! GetWebServiceInstance function returned a different pointer for second call!") 23 | } 24 | } 25 | 26 | func TestGetGames(t *testing.T) { 27 | var result GetGamesResult 28 | ws := GetWebServiceInstance(":8080") // Have to create the web service 29 | ws.Games = make(map[int]*chess_engine.Game) 30 | ws.Games[1] = chess_engine.NewGame(chess_engine.White) 31 | ws.Games[2] = chess_engine.NewGame(chess_engine.Black) 32 | req := httptest.NewRequest(http.MethodGet, "/api/games", nil) 33 | w := httptest.NewRecorder() 34 | ws.Router.ServeHTTP(w, req) 35 | res := w.Result() 36 | defer res.Body.Close() 37 | data, err := io.ReadAll(res.Body) 38 | if err != nil { 39 | t.Errorf("expected error to be nil got %v", err) 40 | } 41 | if http.StatusOK != res.StatusCode { 42 | t.Fatalf("GetGames return status expected a StatusOK, instead got: %d", res.StatusCode) 43 | } 44 | 45 | json.Unmarshal(data, &result) 46 | if !result.Result.Result { 47 | t.Fatalf("GetGames returned failue!") 48 | } 49 | 50 | if len(result.Games) != 2 { 51 | t.Fatalf("Expected GetGames to return 2 games, actually returned %d!", len(result.Games)) 52 | } 53 | 54 | if result.Games[0].ID != 1 { 55 | t.Fatalf("Expected GetGames first id to be 0, actually returned %d!", result.Games[0].ID) 56 | } 57 | 58 | if result.Games[1].ID != 2 { 59 | t.Fatalf("Expected GetGames first id to be 1, actually returned %d!", result.Games[1].ID) 60 | } 61 | 62 | } 63 | 64 | func TestCreateGame(t *testing.T) { 65 | ws := GetWebServiceInstance(":8080") // Have to create the web service 66 | ws.Games = make(map[int]*chess_engine.Game) 67 | req := httptest.NewRequest(http.MethodPost, "/api/games?color=black", nil) 68 | w := httptest.NewRecorder() 69 | ws.Router.ServeHTTP(w, req) 70 | gameLen := len(ws.Games) 71 | res := w.Result() 72 | defer res.Body.Close() 73 | data, err := io.ReadAll(res.Body) 74 | if err != nil { 75 | t.Errorf("expected error to be nil got %v", err) 76 | } 77 | if http.StatusOK != res.StatusCode { 78 | t.Fatalf("CreateGame return status expected a StatusOK, instead got: %d", res.StatusCode) 79 | } 80 | 81 | var m map[string]string 82 | json.Unmarshal(data, &m) 83 | if m["error"] != "" { 84 | t.Errorf("Received error %s from Game POST call", m["error"]) 85 | } 86 | 87 | if gameLen != 1 { 88 | t.Fatalf("After CreateGame expected 1 game in the web service list but have %d", len(ws.Games)) 89 | } 90 | 91 | if ws.Games[1].UserColor != chess_engine.Black { 92 | t.Fatalf("After CreateGame expected user color to be white but is black.") 93 | } 94 | } 95 | 96 | func TestGetGameInfo(t *testing.T) { 97 | var result GetGameInfoResult 98 | ws := GetWebServiceInstance(":8080") // Have to create the web service 99 | ws.Games = make(map[int]*chess_engine.Game) 100 | ws.Games[1] = chess_engine.NewGame(chess_engine.White) 101 | ws.Games[2] = chess_engine.NewGame(chess_engine.Black) 102 | req := httptest.NewRequest(http.MethodGet, "/api/games/2", nil) 103 | w := httptest.NewRecorder() 104 | ws.Router.ServeHTTP(w, req) 105 | res := w.Result() 106 | defer res.Body.Close() 107 | data, err := io.ReadAll(res.Body) 108 | if err != nil { 109 | t.Errorf("expected error to be nil got %v", err) 110 | } 111 | if http.StatusOK != res.StatusCode { 112 | t.Fatalf("GetGameInfo return status expected a StatusOK, instead got: %d", res.StatusCode) 113 | } 114 | 115 | json.Unmarshal(data, &result) 116 | if !result.Result.Result { 117 | t.Fatalf("GetGameInfo returned failue!") 118 | } 119 | 120 | if result.Game == nil { 121 | t.Fatalf("GetGameInfo did not return game info") 122 | } 123 | 124 | if result.Game.ID != 2 { 125 | t.Fatalf("Expected GetGameInfo id to be 1, actually returned %d!", result.Game.ID) 126 | } 127 | 128 | if result.Game.Status != "Engine Move" { 129 | t.Fatalf("Expected GetGameInfo status to be Engine Move, actually returned %s!", result.Game.Status) 130 | } 131 | } 132 | 133 | func TestDeleteGame(t *testing.T) { 134 | var result GetGameInfoResult 135 | ws := GetWebServiceInstance(":8080") // Have to create the web service 136 | ws.Games = make(map[int]*chess_engine.Game) 137 | ws.Games[1] = chess_engine.NewGame(chess_engine.White) 138 | ws.Games[2] = chess_engine.NewGame(chess_engine.Black) 139 | req := httptest.NewRequest(http.MethodDelete, "/api/games/2", nil) 140 | w := httptest.NewRecorder() 141 | ws.Router.ServeHTTP(w, req) 142 | res := w.Result() 143 | defer res.Body.Close() 144 | data, err := io.ReadAll(res.Body) 145 | if err != nil { 146 | t.Errorf("expected error to be nil got %v", err) 147 | } 148 | if http.StatusOK != res.StatusCode { 149 | t.Fatalf("DeleteGame return status expected a StatusOK, instead got: %d", res.StatusCode) 150 | } 151 | 152 | json.Unmarshal(data, &result) 153 | if !result.Result.Result { 154 | t.Fatalf("DeleteGame returned failue!") 155 | } 156 | 157 | if len(ws.Games) != 1 { 158 | t.Fatalf("DeleteGame did not delete the specified game from the web service.") 159 | } 160 | } 161 | 162 | func TestUserMove(t *testing.T) { 163 | var result CreateMoveResult 164 | ws := GetWebServiceInstance(":8080") // Have to create the web service 165 | ws.Games = make(map[int]*chess_engine.Game) 166 | ws.Games[1] = chess_engine.NewGame(chess_engine.White) 167 | req := httptest.NewRequest(http.MethodPost, "/api/games/1/moves?start=a2&end=a3", nil) 168 | w := httptest.NewRecorder() 169 | ws.Router.ServeHTTP(w, req) 170 | res := w.Result() 171 | defer res.Body.Close() 172 | data, err := io.ReadAll(res.Body) 173 | if err != nil { 174 | t.Errorf("expected error to be nil got %v", err) 175 | } 176 | if http.StatusOK != res.StatusCode { 177 | t.Fatalf("CreateMove return status expected a StatusOK, instead got: %d", res.StatusCode) 178 | } 179 | 180 | err = json.Unmarshal(data, &result) 181 | if err != nil { 182 | t.Fatalf("Error parsing response from CreateMove call: %s!", err.Error()) 183 | } 184 | 185 | if !result.Result.Result { 186 | t.Fatalf("CreateMove returned failue!") 187 | } 188 | 189 | if chess_engine.GetPositionFromString(result.Move.StartPos) != chess_engine.A2 || 190 | chess_engine.GetPositionFromString(result.Move.EndPos) != chess_engine.A3 { 191 | t.Fatalf("CreateMove returned wrong positions: %s, %s", result.Move.StartPos, result.Move.EndPos) 192 | } 193 | 194 | if result.Move.Castle { 195 | t.Fatalf("CreateMove indicates Castle for basic pawn move.") 196 | } 197 | 198 | if result.Move.EnPassant { 199 | t.Fatalf("CreateMove indicates EnPassant for basic pawn move.") 200 | } 201 | 202 | if result.Move.Promotion { 203 | t.Fatalf("CreateMove indicates Promotion for basic pawn move.") 204 | } 205 | } 206 | 207 | func TestInvalidUserMove(t *testing.T) { 208 | var result CreateMoveResult 209 | ws := GetWebServiceInstance(":8080") // Have to create the web service 210 | ws.Games = make(map[int]*chess_engine.Game) 211 | ws.Games[1] = chess_engine.NewGame(chess_engine.White) 212 | req := httptest.NewRequest(http.MethodPost, "/api/games/1/moves?start=a2&end=d3", nil) 213 | w := httptest.NewRecorder() 214 | ws.Router.ServeHTTP(w, req) 215 | res := w.Result() 216 | defer res.Body.Close() 217 | data, err := io.ReadAll(res.Body) 218 | if err != nil { 219 | t.Errorf("expected error to be nil got %v", err) 220 | } 221 | if http.StatusBadRequest != res.StatusCode { 222 | t.Fatalf("CreateMove return status expected a StatusOK, instead got: %d", res.StatusCode) 223 | } 224 | 225 | err = json.Unmarshal(data, &result) 226 | if err != nil { 227 | t.Fatalf("Error parsing response from CreateMove call: %s!", err.Error()) 228 | } 229 | 230 | if result.Result.Result { 231 | t.Fatalf("CreateMove returned success for an invalid user move!") 232 | } 233 | } 234 | 235 | func TestEngineMove(t *testing.T) { 236 | var result CreateMoveResult 237 | ws := GetWebServiceInstance(":8080") // Have to create the web service 238 | ws.Games = make(map[int]*chess_engine.Game) 239 | ws.Games[1] = chess_engine.NewGame(chess_engine.White) 240 | req := httptest.NewRequest(http.MethodPost, "/api/games/1/moves?start=a2&end=a3", nil) 241 | w := httptest.NewRecorder() 242 | ws.Router.ServeHTTP(w, req) 243 | res := w.Result() 244 | defer res.Body.Close() 245 | data, err := io.ReadAll(res.Body) 246 | if err != nil { 247 | t.Errorf("expected error to be nil got %v", err) 248 | } 249 | if http.StatusOK != res.StatusCode { 250 | t.Fatalf("CreateMove return status expected a StatusOK, instead got: %d", res.StatusCode) 251 | } 252 | 253 | err = json.Unmarshal(data, &result) 254 | if err != nil { 255 | t.Fatalf("Error parsing response from CreateMove call: %s!", err.Error()) 256 | } 257 | 258 | if !result.Result.Result { 259 | t.Fatalf("CreateMove returned failue!") 260 | } 261 | 262 | if result.Move.Mover != "user" { 263 | t.Fatalf("CreateMove returned %s for the mover when user was expected.", result.Move.Mover) 264 | } 265 | 266 | if chess_engine.GetPositionFromString(result.Move.StartPos) != chess_engine.A2 || 267 | chess_engine.GetPositionFromString(result.Move.EndPos) != chess_engine.A3 { 268 | t.Fatalf("CreateMove returned wrong positions: %s, %s", result.Move.StartPos, result.Move.EndPos) 269 | } 270 | 271 | if result.Move.Castle { 272 | t.Fatalf("CreateMove indicates Castle for basic pawn move.") 273 | } 274 | 275 | if result.Move.EnPassant { 276 | t.Fatalf("CreateMove indicates EnPassant for basic pawn move.") 277 | } 278 | 279 | if result.Move.Promotion { 280 | t.Fatalf("CreateMove indicates Promotion for basic pawn move.") 281 | } 282 | } 283 | 284 | func TestGetMoveInfo(t *testing.T) { 285 | var result GetMoveInfoResult 286 | ws := GetWebServiceInstance(":8080") // Have to create the web service 287 | ws.Games = make(map[int]*chess_engine.Game) 288 | ws.Games[1] = chess_engine.NewGame(chess_engine.White) 289 | // Make a valid move 290 | req := httptest.NewRequest(http.MethodPost, "/api/games/1/moves?start=a2&end=a3", nil) 291 | w := httptest.NewRecorder() 292 | ws.Router.ServeHTTP(w, req) 293 | 294 | req = httptest.NewRequest(http.MethodGet, "/api/games/1/moves/0", nil) 295 | w = httptest.NewRecorder() 296 | ws.Router.ServeHTTP(w, req) 297 | res := w.Result() 298 | defer res.Body.Close() 299 | data, err := io.ReadAll(res.Body) 300 | if err != nil { 301 | t.Errorf("expected error to be nil got %v", err) 302 | } 303 | if http.StatusOK != res.StatusCode { 304 | t.Fatalf("GetMoveInfo return status expected a StatusOK, instead got: %d", res.StatusCode) 305 | } 306 | 307 | json.Unmarshal(data, &result) 308 | if !result.Result.Result { 309 | t.Fatalf("GetMoveInfo returned failue!") 310 | } 311 | 312 | if result.Move == nil { 313 | t.Fatalf("GetMoveInfo did not return game info") 314 | } 315 | 316 | if result.Move.ID != 1 { 317 | t.Fatalf("Expected GetMoveInfo id to be 1, actually returned %d!", result.Move.ID) 318 | } 319 | 320 | if result.Move.StartPos != "A2" { 321 | t.Fatalf("Expected GetMoveInfo.StartPos to be A2, actually returned %s!", result.Move.StartPos) 322 | } 323 | 324 | if result.Move.EndPos != "A3" { 325 | t.Fatalf("Expected GetMoveInfo.EndPos to be A3, actually returned %s!", result.Move.EndPos) 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module chess_go 2 | 3 | go 1.21rc2 4 | 5 | require ( 6 | github.com/fatih/color v1.15.0 // indirect 7 | github.com/gorilla/mux v1.8.0 // indirect 8 | github.com/mattn/go-colorable v0.1.13 // indirect 9 | github.com/mattn/go-isatty v0.0.19 // indirect 10 | golang.org/x/sys v0.11.0 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= 2 | github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= 3 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 4 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 5 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 6 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 7 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 8 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 9 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 10 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 11 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 12 | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= 13 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 14 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "chess_go/chess_engine" 5 | "chess_go/chess_web_service" 6 | "fmt" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | fmt.Printf("Chess Go is starting!") 12 | if len(os.Args) >= 2 && os.Args[1] == "-c" { 13 | console := chess_engine.NewConsoleInterface() 14 | console.Start() 15 | } else { 16 | fmt.Printf("Starting Chess Go Web Service on Port 80.") 17 | ws := chess_web_service.GetWebServiceInstance(":80") 18 | ws.Run() 19 | } 20 | 21 | } 22 | --------------------------------------------------------------------------------