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 |
--------------------------------------------------------------------------------