Newer
Older
zalonzo2
committed
import chess
import io
# run command "pip install stockfish"
from stockfish import Stockfish
skip_camera = None
def init_stockfish(bool1):
global skip_camera
skip_camera = bool1
zalonzo2
committed
# def read_from(file_name):
# with open(r"%s" %file_name, 'r') as File_object: # 'with' autocloses file
# file_data = File_object.read()
zalonzo2
committed
# return file_data
# def write_to(file_name, file_data):
# with open(r"%s" %file_name, 'w') as File_object:
# File_object.write(file_data)
zalonzo2
committed
# return 0
def FEN_check(FEN_string):
'''
DESCRIPTION: Checks if the FEN_string is valid.
INPUT: FEN_string: String in FEN format.
RETURN: is_valid_FEN: Bool that says if the FEN_string was valid.
'''
try:
board = chess.Board(FEN_string)
return True
except ValueError:
return False
def cheat_check(FEN_string, FEN_board_state_before):
zalonzo2
committed
# flag variable
is_cheating = True
# # set board and FEN from state before
# FEN_board_state_before = read_from("saved_FEN")
# Edge case: AI is first to move on start
if (FEN_board_state_before == "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" or FEN_board_state_before == "" and FEN_string == "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"):
is_cheating = False
return is_cheating
if FEN_string == "":
return is_cheating
zalonzo2
committed
board_before = chess.Board(FEN_board_state_before)
# start of weird/custom game, ignore on the first time
if board_before.ply() < 2:
is_cheating = False
return is_cheating
zalonzo2
committed
# possible move set from board state before
move_list = board_before.legal_moves
# iterate all moves to find a move that makes curr board state
for move in move_list:
# make copy of board state before and push moves
board_copy = board_before.copy()
board_copy.push(move)
# if there's a match, it's legal
if (board_copy.fen() == FEN_string):
is_cheating = False
break
return is_cheating
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
def special_move_check(FEN_string, best_move):
'''
DESCRIPTION: Check if the move is capture, castling, promotion, or en passant.
INPUT: FEN_string: String that holds FEN.
best_move: String that holds move
RETURN: special_move_return: List holding flags of special moves and location of affected piece:
[0~4]: Hold 0/1 (False/ True) signifying what type of move.
0: Not a special move
1: En Passant
2: Castling
3: Capture
4: Promotion
[5]: Holds best move.
'''
special_move_return = [0, 0, 0, 0, 0, best_move]
# print("This is best move:", best_move)
board = chess.Board(FEN_string)
move_processed = chess.Move.from_uci(best_move)
# check en passant
if board.is_en_passant(move_processed):
special_move_return[1] = 1
# check castling
if board.is_castling(move_processed):
special_move_return[2] = 1
# check capture
if board.is_capture(move_processed):
special_move_return[3] = 1
# check promotion
is_promotion = chess.square_rank(chess.Move.from_uci(best_move).to_square) in [0, 7] and board.piece_type_at(chess.Move.from_uci(best_move).from_square) == chess.PAWN
if is_promotion:
special_move_return[4] = 1
return special_move_return
zalonzo2
committed
def Stockfish_eval(FEN_string):
'''
DESCRIPTION: Calculates a best move based off Stockfish engine.
INPUT: FEN_string: String in FEN notation.
RETURN: best_move: String of best move (i.e. "e7e6").
'''
# setting up stockfish
if skip_camera:
stockfish = Stockfish( path = "C:\ece445\stockfish\stockfish-windows-x86-64-avx2.exe", # install Stockfish and input path to .exe here
depth = 15, # 20 is good but slower, 15 is fine
parameters={"Skill Level":19}) # range: 0-19? 20 might exist or rounds to 19, https://github.com/vondele/Stockfish/blob/master/src/search.cpp
else:
stockfish = None # FIX FOR RASPI
zalonzo2
committed
# set state of board based off FEN
stockfish.set_fen_position(FEN_string)
# eval best move
best_move = stockfish.get_best_move()
# === TEST: Visualize chess board state ===
# print(stockfish.get_board_visual(True))
zalonzo2
committed
return best_move
def Minimax_eval(FEN_string):
return 0
def Personal_eval(FEN_string):
return 0
def eval_best_move(FEN_string, which_algorithm):
'''
DESCRIPTION: Takes in current game state as FEN and then evaluates a best move.
INPUT: FEN_string: A FEN string.
which_algorithm: Flag that determines best move algorithm:
0: Stockfish
1: Minimax
2: Personal
RETURN: best_move: Tuple that holds:
[0]: From move info.
[1]: To move info.
[2]: FEN string of board.
'''
# calc best move based off flag
best_move = [Stockfish_eval(FEN_string), Minimax_eval(FEN_string), Personal_eval(FEN_string)][which_algorithm]
zalonzo2
committed
# === TEST: See best move ===
# print("Best move is: ", best_move)
return best_move
def prep_info_to_ESP(FEN_string, best_move_processed):
zalonzo2
committed
'''
DESCRIPTION: Preps the info to be sent to the ESP32.
INPUTS: FEN_string: String that holds FEN.
best_move_processed: List that holds flags and best move.
RETURN: ESP_input: List containing info for the ESP32.
[0]: Contains move to go to.
[1]: Contains various strings of secondary piece, if applicable.
"": If reg move or promotion.
"<move>": If En Passant, Castling, or Capture.
[2]: Contains special move flags.
[0]: Reg move
[1]: En Passant
[2]: Castling
[3]: Capture
[4]: Promotion
[3]: Contains FEN before move.
zalonzo2
committed
'''
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# print(best_move_processed)
special_piece_location = ""
# Promotion needs no case since:
# if there's no piece in its way, it's a simple move
# if there's a piece, it will go under Capture
# Capture comes before En Passant since En Passant captures a piece in the process
# Capture case
if best_move_processed[3]:
special_piece_location = [best_move_processed[5][2] + ' ' + best_move_processed[5][3]]
# En Passant case
if best_move_processed[1]:
# left to right
special_piece_location = [best_move_processed[5][2] + ' ' + best_move_processed[5][1]]
# Castling case
if best_move_processed[2]:
# king side castle (e -> g)
# if best_move_processed[5][2] == 'g':
special_piece_location = [('h' if best_move_processed[5][2] == 'g' else 'a') + ' ' + best_move_processed[5][1], ('f' if best_move_processed[5][2] == 'g' else 'd') + ' ' + best_move_processed[5][1]]
# queen side castle (e -> c)
# if best_move_processed[5][2] == 'c':
ESP_input = [[best_move_processed[5][0] + ' ' + best_move_processed[5][1], best_move_processed[5][2] + ' ' + best_move_processed[5][3]],
special_piece_location,
best_move_processed[0:5],
FEN_string]
# print(ESP_input)
# get move's dest and src (separated with space)
# move_from = best_move[0:1] + ' ' + best_move[1:2]
# move_to = best_move[2:3] + ' ' + best_move[3:4]
return ESP_input
# def update_FEN_string(FEN_string, best_move, file_name):
# '''
# DESCRIPTION: Updates the saved FEN with the best_move added to it.
# INPUT: FEN_string: String that hold FEN notation of board.
# best_move: String that holds best move from eval.
# file_name: String that holds the file name.
# RETURN: n/a
# '''
# board = chess.Board(FEN_string)
# board.push(chess.Move.from_uci(best_move))
# write_to(file_name, board.fen())
def chess_AI(FEN_string, FEN_board_state_before, which_alg):
zalonzo2
committed
# check if valid FEN
is_valid_FEN = FEN_check(FEN_string)
zalonzo2
committed
if not is_valid_FEN:
print("FEN format is wrong, please fix")
return -1
else:
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# # check if there's cheating
# is_cheating = cheat_check(FEN_string, FEN_board_state_before)
# if is_cheating:
# print("Illegal move detected, please fix")
# return -1
# else:
# get best move
best_move = eval_best_move(FEN_string, which_alg)
# check move for special characteristics
best_move_processed = special_move_check(FEN_string, best_move)
# prepare info for ESP32
ESP_input = prep_info_to_ESP(FEN_string, best_move_processed)
# # update saved FEN
# file_name = "saved_FEN"
# update_FEN_string(FEN_string, best_move, file_name)
return ESP_input, best_move
# def tests(FEN_string, which_func, which_test):
# '''
# DESCRIPTION: Contains and executes different tests for various functions.
# INPUT: FEN_string: String containing FEN.
# which_func: Int that dictates which functionality to test for.
# 0: FEN
# 1: Eval
# 2: Cheating
# which_test: Int that dictates which test of specific functionality to test for.
# RETURN:
# '''
# message = "FEN Parsing" if which_func == 0 else "Eval" if which_func == 1 else "Cheating" if which_func == 2 else "Special Move" if which_func == 3 else "Processing ESP32 Info" if which_func == 4 else "Chess AI"
# print("You're currently testing: " + message + "'s Test #" + str(which_test))
# # === TESTS: FEN Parsing ===
# if which_func == 0:
# is_valid_FEN = False
# # 0: Valid FEN (Success)
# if which_test == 0:
# FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
# is_valid_FEN = FEN_check(FEN_string)
# # 1: Invalid FEN (Fail)
# if which_test == 1:
# FEN_string = "This should totally fail"
# is_valid_FEN = FEN_check(FEN_string)
# # 2: Invalid FEN by 1 extra char (Fail)
# if which_test == 2:
# FEN_string = "rnbqkbnrr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
# is_valid_FEN = FEN_check(FEN_string)
# message = "Valid" if is_valid_FEN else "Invalid"
# print("This inputted FEN is:", message)
# # === TESTS: Eval ===
# if which_func == 1:
# # TEST: Inputting different moves ===
# if which_test == 0:
# FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
# board = chess.Board(FEN_string)
# move_list = ["g1f3", "c7c5" # "b8a6"
# ]
# for move in move_list:
# board.push(chess.Move.from_uci(move))
# # === TESTS: Cheating ===
# if which_func == 2:
# # save old FEN
# file_name = "saved_FEN"
# saved_FEN = read_from(file_name)
# is_cheating = True
# # 0: Illegal ply/state of board (Fail)
# if which_test == 0:
# write_to(file_name, "r1bqkb1r/pppp1ppp/2n2n2/4p3/2B1P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 1 6") # random board state
# FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
# is_cheating = cheat_check(FEN_string)
# # 1: Valid ply by human (Success)
# if which_test == 1:
# write_to(file_name, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") # base: white starts
# FEN_string = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1" # black's turn, one ply by white since base
# is_cheating = cheat_check(FEN_string)
# # 2: AI is first to move on start & saved_FEN is filled (Success)
# if which_test == 2:
# write_to(file_name, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") # base: white starts
# FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
# is_cheating = cheat_check(FEN_string)
# # 3: AI is first to move on start & saved_FEN is empty (Success)
# if which_test == 3:
# write_to(file_name, "") # base: white starts
# FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
# is_cheating = cheat_check(FEN_string)
# message = "Yes" if is_cheating else "No"
# print("Was there cheating involved?:", message)
# # restore old FEN
# write_to(file_name, saved_FEN)
# # === TESTS: Special Moves ===
# if which_func == 3:
# # 0: Castle
# if which_test == 0:
# is_special_move = special_move_check("r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K2R w KQkq - 0 1", "e1g1")
# # 1: Capture
# if which_test == 1:
# is_special_move = special_move_check("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3", "f3e5")
# # 2: Promotion
# if which_test == 2:
# is_special_move = special_move_check("8/PPPPP1PP/8/8/8/8/8/8 w - - 0 1", "a7a8q")
# # 3: En Passant & Capture
# if which_test == 3:
# is_special_move = special_move_check("8/8/8/4pP2/4P3/8/8/8 w - e6 0 1", "f5e6")
# # print messages based off what it was
# if is_special_move[0]:
# print("The move results in reg move")
# if is_special_move[1]:
# print("The move results in En Passant at location:", is_special_move[5][2:4])
# if is_special_move[2]:
# print("The move results in Castling at location:", is_special_move[5][2:4])
# if is_special_move[3]:
# print("The move results in Capture at location:", is_special_move[5][2:4])
# if is_special_move[4]:
# print("The move results in Promotion at location:", is_special_move[5][2:4])
# # === TESTS: Process ESP32 ===
# if which_func == 4:
# # 0: En Passant
# if which_test == 0:
# FEN_string = "8/8/8/4pP2/4P3/8/8/8 w - e6 0 1"
# best_move_processed = special_move_check("8/8/8/4pP2/4P3/8/8/8 w - e6 0 1", "f5e6")
# ESP_input = prep_info_to_ESP(FEN_string, best_move_processed)
# # 1: Capture
# if which_test == 1:
# FEN_string = "r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3"
# best_move_processed = is_special_move = special_move_check("r1bqkbnr/pppp1ppp/2n5/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3", "f3e5")
# ESP_input = prep_info_to_ESP(FEN_string, best_move_processed)
# # 2: Castling (king side)
# if which_test == 2:
# FEN_string = "r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K2R w KQkq - 0 1"
# best_move_processed = special_move_check("r3k2r/pppppppp/8/8/8/8/PPPPPPPP/R3K2R w KQkq - 0 1", "e1g1")
# ESP_input = prep_info_to_ESP(FEN_string, best_move_processed)
# # 3: Castling (queen side)
# if which_test == 3:
# FEN_string = "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1"
# best_move_processed = special_move_check("r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1", "e1c1")
# ESP_input = prep_info_to_ESP(FEN_string, best_move_processed)
# print(ESP_input)
# # === TESTS: Entire Alg ===
# if which_func == 5:
# file_name = "saved_FEN"
# saved_FEN = read_from(file_name)
# # 0: Base game
# if which_test == 0:
# FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
# if which_test == 1:
# FEN_string = "r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1" # grabbed from ESP32, Test 3
# board = chess.Board(FEN_string)
# print(board)
# which_alg = 0
# ret_val = chess_AI(FEN_string, which_alg)
# print("Return value is:",ret_val)
# string_test = ret_val[0][0] + ret_val[0][1]
# string_test = string_test.replace(" ", "")
# board.push(chess.Move.from_uci(string_test))
# print(board)
# # restore old FEN
# write_to(file_name, saved_FEN)
# if __name__ == '__main__':
# # comment out these when calling with Raspberry PI 4 (make sure when you call chess_AI, you set the parameters)
# FEN_string = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # base: white starts
# # FEN_string = "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1"
# which_alg = 0
# # Chess AI program
# # ret_val = chess_AI(FEN_string, which_alg)
# # print(ret_val)
# # Tests
# which_func = 5
# which_test = 1
# tests(FEN_string, which_func, which_test)