-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathChessGame.py
More file actions
1448 lines (1344 loc) · 81.2 KB
/
Copy pathChessGame.py
File metadata and controls
1448 lines (1344 loc) · 81.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
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
236
237
238
239
240
241
242
243
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
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#Chess Game - Agathiyan Bragadeesh
#https://commons.wikimedia.org/wiki/Category:PNG_chess_pieces/Standard_transparent
#Source of piece skins
#Import modules
import os
import json
import pygame
from PIL import Image, ImageTk
import math
import tkinter
import time
from datetime import datetime
import random
import numpy
##### BOARD REPRESENTATION #####
###Board Class
class Board:
def __init__(self,turn):
#Array of the board and will contain the pieces on it
self.board = [[None,None,None,None,None,None,None,None],
[None,None,None,None,None,None,None,None],
[None,None,None,None,None,None,None,None],
[None,None,None,None,None,None,None,None],
[None,None,None,None,None,None,None,None],
[None,None,None,None,None,None,None,None],
[None,None,None,None,None,None,None,None],
[None,None,None,None,None,None,None,None]]
self.square_identifiers = [[letter + number for number in ['8','7','6','5','4','3','2','1']] for letter in ['a','b','c','d','e','f','g','h']]
#Loads the image of the board to be displayed
self.image = pygame.image.load("Textures/Board.png")
#List of all the pieces on the board
#Initialising black pieces
bp0 = Pawn(-1,0,1,0,self)
bp1 = Pawn(-1,1,1,1,self)
bp2 = Pawn(-1,2,1,2,self)
bp3 = Pawn(-1,3,1,3,self)
bp4 = Pawn(-1,4,1,4,self)
bp5 = Pawn(-1,5,1,5,self)
bp6 = Pawn(-1,6,1,6,self)
bp7 = Pawn(-1,7,1,7,self)
bq = Queen(-1,3,0,8,self)
bb1 = Bishop(-1,2,0,9,self)
bb2 = Bishop(-1,5,0,10,self)
br1 = Rook(-1,0,0,11,self)
br2 = Rook(-1,7,0,12,self)
bn1 = Knight(-1,1,0,13,self)
bn2 = Knight(-1,6,0,14,self)
bk = King(-1,4,0,15,self)
#Initialising white pieces
wp0 = Pawn(1,0,6,16,self)
wp1 = Pawn(1,1,6,17,self)
wp2 = Pawn(1,2,6,18,self)
wp3 = Pawn(1,3,6,19,self)
wp4 = Pawn(1,4,6,20,self)
wp5 = Pawn(1,5,6,21,self)
wp6 = Pawn(1,6,6,22,self)
wp7 = Pawn(1,7,6,23,self)
wq = Queen(1,3,7,24,self)
wb1 = Bishop(1,2,7,25,self)
wb2 = Bishop(1,5,7,26,self)
wr1 = Rook(1,0,7,27,self)
wr2 = Rook(1,7,7,28,self)
wn1 = Knight(1,1,7,29,self)
wn2 = Knight(1,6,7,30,self)
wk = King(1,4,7,31,self)
#List of all pieces so they can be updated
self.pieces = [bp0,bp1,bp2,bp3,bp4,bp5,bp6,bp7,bq,bb1,bb2,br1,br2,bn1,bn2,bk,wp0,wp1,wp2,wp3,wp4,wp5,wp6,wp7,wq,wb1,wb2,wr1,wr2,wn1,wn2,wk]
self.white_king = wk
self.black_king = bk
self.initial_turn = turn #If board is created by black AI, board starts with black turn
self.turn = turn
self.checkspaces_white = set()
self.checkspaces_black = set()
self.list_of_moves = [] #String list of move in notation
self.record_of_boards = [] #List of boards (not list of moves as list of boards allows for immediate traversal and reversing moves)
self.move_buffer = str()
self.in_check = False
self.endstate = 0 #0 if still in play, -1 if black wins, 1 if white wins, 2 if stalemate
self.current_index = 0
self.record_board(self.current_index)
def increment_turn(self):
if not self.check_check(): #If in a check position after turn is made, invalid move
self.current_index += 1
self.record_board(self.current_index)
self.turn = self.initial_turn * ((-1)**(self.current_index))
self.in_check = self.check_check() #If player whose turn it is is in check
self.endstate = self.check_endstate(self.turn * -1)
self.list_of_moves = self.list_of_moves[0:self.current_index-1]
self.list_of_moves.append(self.move_buffer)
self.move_buffer = str()
if self.endstate == -1 or self.endstate == 1: #Checkmate
self.list_of_moves[-1] += '#' #Adds checkmate symbol
elif self.in_check:
self.list_of_moves[-1] += '+' #Check symbol
for piece in self.pieces:
if piece.colour == self.turn and type(piece) == Pawn:
piece.double_move = False
return True
else:
self.move_buffer = str()
self.revert_board(self.current_index)
return False
def piece(self,x,y):
return self.board[x][y]
def square_id(self,x,y):
return self.square_identifiers[x][y]
def piece_set(self,x,y,piece):
#Sets a piece on board at a specific position
self.board[x][y] = piece
def promote(self,piece,promote_id):
self.pieces[piece.identifier] = None
#promote_id describes the piece to promote to
if promote_id == 1:
new_piece = Queen(piece.colour,piece.posx,piece.posy,piece.identifier,self)
elif promote_id == 2:
new_piece = Rook(piece.colour,piece.posx,piece.posy,piece.identifier,self)
elif promote_id == 3:
new_piece = Bishop(piece.colour,piece.posx,piece.posy,piece.identifier,self)
elif promote_id == 4:
new_piece = Knight(piece.colour,piece.posx,piece.posy,piece.identifier,self)
#0 is only used when reverting board to demoted state
elif promote_id == 0:
new_piece = Pawn(piece.colour,piece.posx,piece.posy,piece.identifier,self)
new_piece.original_pos = False
new_piece.promoted = promote_id
piece_to_delete = self.pieces[new_piece.identifier]
self.pieces[new_piece.identifier] = new_piece
del piece_to_delete #Deletes old instance of the piece
def check_checkspaces(self):
#An array of coordinates on the board which the king cannot occupy as that would result in it being in check.
#checkspaces_white are the spaces the black king can't move to, and checkspaces_black are the spaces the white king can't move to
self.checkspaces_white = set()
self.checkspaces_black = set()
#Following code groups together all the spaces under attack for each colour
for piece in self.pieces:
if piece.alive:
coords = piece.possible_moves() #moves that can be done, if a move can be done to the square the king is on, it must be a capture (i.e. is in check)
for coord in coords:
if piece.colour == -1:
self.checkspaces_white.add((coord[0],coord[1]))
elif piece.colour == 1:
self.checkspaces_black.add((coord[0],coord[1]))
def check_check(self):
#Updates checkspaces lists
self.check_checkspaces()
#Depending on whose turn it is, either the white king is looked at or the black king is
if self.turn == 1:
return (self.white_king.posx,self.white_king.posy) in self.checkspaces_white
else:
return (self.black_king.posx,self.black_king.posy) in self.checkspaces_black
def record_board(self,index):
Record = {}
#When recording a board, all boards after the index being saved are removed
self.record_of_boards = self.record_of_boards[0:index]
for piece in self.pieces:
piece_type = type(piece)
Record[str(piece.identifier)] = {}
Record[str(piece.identifier)]["posx"] = piece.posx
Record[str(piece.identifier)]["posy"] = piece.posy
Record[str(piece.identifier)]["alive"] = piece.alive
#Conditional statements used to assign values to fields specific to certain classes
if piece_type == Pawn:
Record[str(piece.identifier)]["original_pos"] = piece.original_pos
Record[str(piece.identifier)]["double_move"] = piece.double_move
else:
Record[str(piece.identifier)]["double_move"] = None
if piece_type == Rook or piece_type == King:
Record[str(piece.identifier)]["original_pos"] = piece.original_pos
else:
Record[str(piece.identifier)]["original_pos"] = None
Record[str(piece.identifier)]["promoted"] = piece.promoted
#Record is added to the index
self.record_of_boards.append(Record)
def revert_board(self,index):
self.turn = self.initial_turn * ((-1)**(index)) #Since each increment in index is a change in turn, repeated multiplication of the index will get turn
self.current_index = index
#Clears every item on the board, and then reverts the attributes of each piece, and sets the piece back in its old position
for a in range(0,8):
for b in range(0,8):
self.piece_set(a,b,None)
#Gets record from record_of_boards to get data from
record = self.record_of_boards[index]
pieces_buffer = self.pieces
for piece in pieces_buffer:
#Updates attribute values of objects
piece_info = record[str(piece.identifier)]
piece.posx = piece_info["posx"]
piece.posy = piece_info["posy"]
piece.alive = piece_info["alive"]
#Only sets piece on the board if alive
if piece.alive:
self.piece_set(piece.posx,piece.posy,piece)
if piece.promoted != piece_info["promoted"]:
self.promote(piece,piece_info["promoted"])
if type(piece) == Rook or type(piece) == King:
piece.original_pos = piece_info["original_pos"]
elif type(piece) == Pawn:
piece.double_move = piece_info["double_move"]
piece.original_pos = piece_info["original_pos"]
self.in_check = self.check_check() #If player whose turn it is is in check
def check_endstate(self,potential_winner): #0 for none, 1 is white wins, -1 if black wins, 2 if stalematem 3 if insufficient material
game_state = self.check_checkmate(potential_winner)
if game_state == 0:
if self.check_insufficient_material():
return 3
else:
return 0
else:
return game_state
def check_insufficient_material(self):
bishop_insufficient = {(Pawn,-1):0,(Knight,-1):0,(Bishop,-1):1,(Rook,-1):0,(Queen,-1):0,(King,-1):1,(Pawn,1):0,(Knight,1):0,(Bishop,1):1,(Rook,1):0,(Queen,1):0,(King,1):1}
#Tuples represent piece and colour of peice
material = {(Pawn,-1):0,(Knight,-1):0,(Bishop,-1):0,(Rook,-1):0,(Queen,-1):0,(King,-1):0,(Pawn,1):0,(Knight,1):0,(Bishop,1):0,(Rook,1):0,(Queen,1):0,(King,1):0}
for piece in self.pieces:
if piece.alive:
material[(type(piece),piece.colour)] += 1
if material == bishop_insufficient:
#We now know there are only two bishops of opposite colours on the board (and two kings), we can use a logical xor on both their square_colour_black attribute to return if insufficient
insufficient = True
#Variable needs to be declared before loop, True is used as (False XOR A) = NOT A
for piece in self.pieces:
if piece.alive:
if type(piece) == Bishop:
insufficient ^= piece.square_colour_black
#True if insufficient material
return insufficient
else:
#Checks if all of the following pieces have no instances on the board (if the total sum of counts is 0, each individual count is 0)
total = 0
for piece_tuple in [(Pawn,-1),(Rook,-1),(Queen,-1),(Pawn,1),(Rook,1),(Queen,1)]:
total += material[piece_tuple]
if total == 0:
#If at least one of following: (Knight,-1) (Bishop,-1) (Knight,1) (Bishop,1), else insufficient
total = 0
for piece_tuple in [(Knight,-1),(Bishop,-1),(Knight,1),(Bishop,1)]:
total += material[piece_tuple]
#Returns whether or not there is a total of less than one of listed pieces
return total <= 1
else:
return False
def check_checkmate(self,potential_winner):
#GameWin is 0 when the game is in play, and the condition of the while loop that keeps the game playing. GameWin will change to either -1 or 1 on checkmate, indicating which colour has won (black or white respectively)
endstate = potential_winner
#Goes through every move for each piece
move_list = []
for piece in self.pieces:
if piece.colour == potential_winner * -1 and piece.alive:
for move in piece.possible_moves():
move_list.append(MoveObject(piece.identifier,piece.posx,piece.posy,move))
#Checks every move for every piece (important note, if GameWin remains not equal to 0, this means there are no legal moves, but it doesn't necessarily mean there is currently a check, i.e. a stalemate. This is checked after the loop is broken
for move in move_list:
move_success = move.execute(self,True)
self.check_checkspaces()
#Checks if after the move, the king is still in check. If not, GameWin is changed back to 0 as there is a valid move remaining.
if endstate == -1:
if (not ((self.white_king.posx,self.white_king.posy) in self.checkspaces_white)) and move_success:
self.revert_board(self.current_index)
return 0
elif endstate == 1:
if (not ((self.black_king.posx,self.black_king.posy) in self.checkspaces_black)) and move_success:
self.revert_board(self.current_index)
return 0
#If the move was successful, the board is reverted so the next move can be checked
if move_success:
self.revert_board(self.current_index)
if not((self.black_king.posx,self.black_king.posy) in self.checkspaces_black) and not((self.white_king.posx,self.white_king.posy) in self.checkspaces_white):
return 2
return endstate
def save_game(self,game_name): #game_name is the name for the game as chosen by the user
if not os.path.exists("Games"): #Checks if game folder exists, if not the directory is made
os.mkdir("Games")
file_name = game_name
while os.path.isfile("Games/" + file_name + ".json"): #Checks if game has already been saved with same name, else adds a "-" character at the end until file name doesnt exist
file_name += "-"
filepath = "Games/" + file_name + ".json"
game = {}
game["game_name"] = game_name
game["list_of_moves"] = self.list_of_moves
game["record_of_boards"] = self.record_of_boards
game["datetimeplayedUNIX"] = str(time.time()) #Used to order display of games by datetime
game["datetimeplayedstring"] = datetime.now().strftime("%d-%m-%Y %H:%M")
with open(filepath,"w") as savefile:
json.dump(game,savefile)
def load_game_to_board(self,file_name):
with open("Games/" + file_name,"r") as loadfile:
game = json.load(loadfile)
self.list_of_moves = game["list_of_moves"]
self.record_of_boards = game["record_of_boards"]
###Piece Classes
#General class, of which all pieces are subclasses of
class ChessPiece:
def __init__(self,colour,posx,posy,piece,num,board,notation):
#The pawn's x and y positions, along with a temporary cache for reversion
self.posx = posx
self.posy = posy
self.identifier = num
self.alive = True
#Black is -1, White is 1
if colour == -1:
self.image = pygame.image.load("Textures/" + piece + "Black.png")
elif colour == 1:
self.image = pygame.image.load("Textures/" + piece + "White.png")
self.colour = colour
self.promoted = 0 #0 is not promoted, 1 is to a Queen, 2 is to a Rook, 3 is to a Bishop, 4 is to a Rook
self.board = board
self.notation = notation
#Sets piece on the board
self.board.piece_set(posx,posy,self)
def take(self):
#Removes itself from the board and the game
self.board.piece_set(self.posx,self.posy,None)
self.alive = False
def piece_move(self,x,y):
#In the case there is a piece and it is not of the same colour, the knight takes the piece else if it is an empty space, it'll just move there.
if self.board.piece(x,y) != None:
self.board.piece(x,y).take() #move has already been checked, so we can be sure it's valid
#Removes the piece from original position on the board
self.board.piece_set(self.posx,self.posy,None)
#Sets new x and y positional values
self.posx = x
self.posy = y
#Sets piece in new position
self.board.piece_set(x,y,self)
return True
def move(self,x,y,is_player_move = True):
if (x,y) in self.possible_moves():
#If the move is being run by the check_checkmate() method, adding notation isn't needed
if is_player_move:
#Piece notation varies between capture and no capture
if self.board.piece(x,y) != None:
self.board.move_buffer = self.notation + self.board.square_id(self.posx,self.posy) + 'x' + self.board.square_id(x,y)
else:
self.board.move_buffer = self.notation + self.board.square_id(self.posx,self.posy) + '-' + self.board.square_id(x,y)
#Moves the piece
return self.piece_move(x,y)
return False
class Pawn(ChessPiece):
def __init__(self,colour,posx,posy,num,board):
ChessPiece.__init__(self,colour,posx,posy,"Pawn",num,board,'')
self.original_pos = True
#The double move variable is used to track if the last move on the baord was a double move by this pawn. Used in the en passant.
self.double_move = False
def move(self,x,y,is_player_move = True):
if (x,y) in self.possible_moves():
if self.posx != x: #A capture (move must be diagonal and capture if in a possible move)
if is_player_move:
self.board.move_buffer = self.board.square_id(self.posx,self.posy) + 'x' + self.board.square_id(x,y)
if self.board.piece(x,y) == None: #This means a diagonal move, no piece at destination square and a valid move means its an en passant
self.board.piece(x,self.posy).take()
self.original_pos = False
return self.piece_move(x,y)
else: #Not a capture
if is_player_move:
self.board.move_buffer = self.board.square_id(self.posx,self.posy) + '-' + self.board.square_id(x,y)
if abs(self.posy-y) == 2: #Double moves and en passants are mutually exclusive, elif can therefore be used
self.double_move = True #Updates to allow check for en passant
self.original_pos = False
return self.piece_move(x,y)
return False
def possible_moves(self):
possible_moves_list = []
#Single move
if 0<=self.posy-self.colour and 7>=self.posy-self.colour:
if self.board.piece(self.posx,self.posy-self.colour) == None:
possible_moves_list.append((self.posx,self.posy-self.colour))
#Double move
if self.original_pos:
if 0<=self.posy-(2*self.colour) and 7>=self.posy-(2*self.colour):
if self.board.piece(self.posx,self.posy-(2*self.colour)) == None:
possible_moves_list.append((self.posx,self.posy-(2*self.colour)))
#Capture 1
if self.posx <= 6:
if self.board.piece(self.posx+1,self.posy-self.colour) != None:
if self.board.piece(self.posx+1,self.posy-self.colour).colour != self.colour:
possible_moves_list.append((self.posx+1,self.posy-self.colour))
#En passant 1
elif self.board.piece(self.posx+1,self.posy) != None and self.board.piece(self.posx+1,self.posy).colour != self.colour and type(self.board.piece(self.posx+1,self.posy)) == Pawn:
if self.board.piece(self.posx+1,self.posy).double_move:
possible_moves_list.append((self.posx+1,self.posy-self.colour))
#Capture 2
if self.posx >= 1:
if self.board.piece(self.posx-1,self.posy-self.colour) != None:
if self.board.piece(self.posx-1,self.posy-self.colour).colour != self.colour:
possible_moves_list.append((self.posx-1,self.posy-self.colour))
#En passant 2
elif self.board.piece(self.posx-1,self.posy) != None and self.board.piece(self.posx-1,self.posy).colour != self.colour and type(self.board.piece(self.posx-1,self.posy)) == Pawn:
if self.board.piece(self.posx-1,self.posy).double_move:
possible_moves_list.append((self.posx-1,self.posy-self.colour))
return possible_moves_list
class Knight(ChessPiece):
def __init__(self,colour,posx,posy,num,board):
ChessPiece.__init__(self,colour,posx,posy,"Knight",num,board,'N')
def possible_moves(self):
possible_moves_list = []
#All possible unordered pairs of movement in the x and y direction
for vector in [(1,2),(-1,2),(1,-2),(-1,-2),(2,1),(-2,1),(2,-1),(-2,-1)]:
#Finds vectors that the knight can move to, first taking the first item as the x change and the second item as the y change of the move, then vice versa
if 0<=self.posx+vector[0] and 7>=self.posx+vector[0] and 0<=self.posy+vector[1] and 7>=self.posy+vector[1]:
if self.board.piece(self.posx+vector[0],self.posy+vector[1]) != None:
if self.board.piece(self.posx+vector[0],self.posy+vector[1]).colour != self.colour:
possible_moves_list.append((self.posx+vector[0],self.posy+vector[1]))
else:
possible_moves_list.append((self.posx+vector[0],self.posy+vector[1]))
return possible_moves_list
class Bishop(ChessPiece):
def __init__(self,colour,posx,posy,num,board):
ChessPiece.__init__(self,colour,posx,posy,"Bishop",num,board,'B')
self.square_colour_black = ((posx - posy) % 2 == 0) #Used for insufficient material check
def possible_moves(self):
possible_moves_list = []
for (dx,dy) in [(-1,-1),(1,-1),(-1,1),(1,1)]:
for multiple in range(1,8): #max multiple is 7 if moving across the whole board
#If out of range break
if (self.posx + (multiple*dx))>7 or (self.posx + (multiple*dx))<0 or (self.posy + (multiple*dy))>7 or (self.posy + (multiple*dy))<0:
break
#If there is a piece at space, and if it on the same side or opposite side as itself
elif (self.board.piece(self.posx + (multiple*dx),self.posy + (multiple*dy)) != None):
if self.board.piece(self.posx + (multiple*dx),self.posy + (multiple*dy)).alive:
if self.board.piece(self.posx + (multiple*dx),self.posy + (multiple*dy)).colour != self.colour:
possible_moves_list.append((self.posx + (multiple*dx),self.posy + (multiple*dy)))
break
possible_moves_list.append((self.posx + (multiple*dx),self.posy + (multiple*dy)))
return possible_moves_list
#Similar possible_moves method is used for rook and queen, difference being for loop of (dx,dy) is slightly different
class Rook(ChessPiece):
def __init__(self,colour,posx,posy,num,board):
ChessPiece.__init__(self,colour,posx,posy,"Rook",num,board,'R')
#Initialisation, the original_pos attribute is used for castling; castling will be considered an invalid move is original_pos is False, the King has this attribute as well for the same reason.
self.original_pos = True
def possible_moves(self):
possible_moves_list = []
for (dx,dy) in [(-1,0),(1,0),(0,1),(0,-1)]:
for multiple in range(1,8):
if (self.posx + (multiple*dx))>7 or (self.posx + (multiple*dx))<0 or (self.posy + (multiple*dy))>7 or (self.posy + (multiple*dy))<0:
break
if (self.board.piece(self.posx + (multiple*dx),self.posy + (multiple*dy)) != None):
if self.board.piece(self.posx + (multiple*dx),self.posy + (multiple*dy)).alive:
if self.board.piece(self.posx + (multiple*dx),self.posy + (multiple*dy)).colour != self.colour:
possible_moves_list.append((self.posx + (multiple*dx),self.posy + (multiple*dy)))
break
possible_moves_list.append((self.posx + (multiple*dx),self.posy + (multiple*dy)))
return possible_moves_list
def move(self,x,y,is_player_move = True):
if (x,y) in self.possible_moves():
if is_player_move:
if self.board.piece(x,y) != None:
self.board.move_buffer = self.notation + self.board.square_id(self.posx,self.posy) + 'x' + self.board.square_id(x,y)
else:
self.board.move_buffer = self.notation + self.board.square_id(self.posx,self.posy) + '-' + self.board.square_id(x,y)
self.original_pos = False
return self.piece_move(x,y)
return False
class Queen(ChessPiece):
def __init__(self,colour,posx,posy,num,board):
ChessPiece.__init__(self,colour,posx,posy,"Queen",num,board,'Q')
def possible_moves(self):
possible_moves_list = []
for (dx,dy) in [(-1,0),(1,0),(0,1),(0,-1),(-1,-1),(1,-1),(-1,1),(1,1)]:
for multiple in range(1,8):
if (self.posx + (multiple*dx))>7 or (self.posx + (multiple*dx))<0 or (self.posy + (multiple*dy))>7 or (self.posy + (multiple*dy))<0:
break
if (self.board.piece(self.posx + (multiple*dx),self.posy + (multiple*dy)) != None):
if self.board.piece(self.posx + (multiple*dx),self.posy + (multiple*dy)).alive:
if self.board.piece(self.posx + (multiple*dx),self.posy + (multiple*dy)).colour != self.colour:
possible_moves_list.append((self.posx + (multiple*dx),self.posy + (multiple*dy)))
break
possible_moves_list.append((self.posx + (multiple*dx),self.posy + (multiple*dy)))
return possible_moves_list
class King(ChessPiece):
def __init__(self,colour,posx,posy,num,board):
ChessPiece.__init__(self,colour,posx,posy,"King",num,board,'K')
#Initialisation, along with the original_pos attribute to ensure the King hasn't moved before attempting to castle
self.original_pos = True
def move(self,x,y,is_player_move = True):
if abs(self.posx-x) != 2: #If abs(self.posx-x) i.e. a move 2 in the x direction either side, it's a castling move
if (x,y) in self.possible_moves():
if is_player_move:
if self.board.piece(x,y) != None:
self.board.move_buffer = 'K' + self.board.square_id(self.posx,self.posy) + 'x' + self.board.square_id(x,y)
else:
self.board.move_buffer = 'K' + self.board.square_id(self.posx,self.posy) + '-' + self.board.square_id(x,y)
self.original_pos = False
return self.piece_move(x,y)
return False
else:
#If equal to 2 in x, its a castle
if (x,y) in self.castling_moves():
if x < self.posx: #Queenside
self.board.move_buffer = '0-0-0'
self.board.piece(0,y).piece_move(self.posx-1,y)
else: #Kingside
self.board.move_buffer = '0-0'
self.board.piece(7,y).piece_move(self.posx+1,y)
self.original_pos = False
return self.piece_move(x,y)
return False
def possible_moves(self):
possible_moves_list = []
for vector in [(1,1),(1,0),(1,-1),(0,1),(0,-1),(-1,1),(-1,0),(-1,-1)]:
if 0<=self.posx+vector[0] and 7>=self.posx+vector[0] and 0<=self.posy+vector[1] and 7>=self.posy+vector[1]:
if self.board.piece(self.posx+vector[0],self.posy+vector[1]) != None:
if self.board.piece(self.posx+vector[0],self.posy+vector[1]).colour != self.colour:
possible_moves_list.append((self.posx+vector[0],self.posy+vector[1]))
else:
possible_moves_list.append((self.posx+vector[0],self.posy+vector[1]))
return possible_moves_list
def castling_moves(self):
possible_moves_list = []
self.board.check_checkspaces()
if self.original_pos and not((self.colour == 1 and (self.posx,self.posy) in self.board.checkspaces_white) or (self.colour == -1 and (self.posx,self.posy) in self.board.checkspaces_black)):
#Kingside Castling
if (self.board.piece(self.posx+1,self.posy) == None) and not((self.colour == 1 and (self.posx+1,self.posy) in self.board.checkspaces_white) or (self.colour == -1 and (self.posx+1,self.posy) in self.board.checkspaces_black)):
if (self.board.piece(self.posx+2,self.posy) == None) and not((self.colour == 1 and (self.posx+2,self.posy) in self.board.checkspaces_white) or (self.colour == -1 and (self.posx+2,self.posy) in self.board.checkspaces_black)):
if type(self.board.piece(7,self.posy)) == Rook:
if self.board.piece(7,self.posy).original_pos:
possible_moves_list.append((self.posx+2,self.posy))
#Queenside Castling
if (self.board.piece(self.posx-1,self.posy) == None) and not((self.colour == 1 and (self.posx-1,self.posy) in self.board.checkspaces_white) or (self.colour == -1 and (self.posx-1,self.posy) in self.board.checkspaces_black)):
if (self.board.piece(self.posx-2,self.posy) == None) and not((self.colour == 1 and (self.posx-2,self.posy) in self.board.checkspaces_white) or (self.colour == -1 and (self.posx-2,self.posy) in self.board.checkspaces_black)):
if (self.board.piece(self.posx-3,self.posy) == None) and not((self.colour == 1 and (self.posx-3,self.posy) in self.board.checkspaces_white) or (self.colour == -1 and (self.posx-3,self.posy) in self.board.checkspaces_black)):
if type(self.board.piece(0,self.posy)) == Rook:
if self.board.piece(0,self.posy).original_pos:
possible_moves_list.append((self.posx-2,self.posy))
return possible_moves_list
#Used by check_checkmate() method and AI class to describe moves which can then be run
class MoveObject:
def __init__(self,identifier,posx,posy,to_tuple,promotion = 0,score = 0):
self.identifier = identifier
self.from_x = posx
self.from_y = posy
self.to_x = to_tuple[0]
self.to_y = to_tuple[1]
self.promotion = promotion #Only not zero when used by AI
def execute(self,board,is_checkmate_search):
move_success = (board.pieces[self.identifier]).move(self.to_x,self.to_y,not is_checkmate_search) #If its a checkmate search, it's not a player move
if move_success and self.promotion != 0:
board.promote(board.pieces[self.identifier],self.promotion)
return move_success
##### AI ######
#Zobrist hash initialisation
zobrist_table = [[[[random.randint(1,2**64 - 1) for i in range(0,8)] for j in range(0,8)] for k in range(0,6)] for l in range(0,2)]
zobrist_list= [random.randint(1,2**64 - 1) for a in range(0,13)]
#Subclass of board is used, as the turn_increment method of the typical board representation contains redundancies for an AI
class AIboard(Board):
def increment_turn(self):
if not self.check_check(): #If in a check position after turn is made, invalid move
self.current_index += 1
self.record_board(self.current_index)
self.revert_board(self.current_index)
for piece in self.pieces:
if piece.colour == self.turn and type(piece) == Pawn:
piece.double_move = False
return True
else:
self.revert_board(self.current_index)
return False
class AI:
def __init__(self,depth,colour):
self.max_depth = depth
self.colour = colour
#Piece-square tables are taken from https://www.chessprogramming.org/Simplified_Evaluation_Function
self.pawn_piece_table = [[100, 100, 100, 100, 100, 100, 100, 100],
[150, 150, 150, 150, 150, 150, 150, 150],
[110, 110, 120, 130, 130, 120, 110, 110],
[105, 105, 110, 125, 125, 110, 105, 105],
[100, 100, 100, 120, 120, 100, 100, 100],
[105, 95, 90, 100, 100, 90, 95, 105],
[105, 110, 110, 80, 80, 110, 110, 105],
[100, 100, 100, 100, 100, 100, 100, 100]]
self.knight_piece_table = [[270, 280, 290, 290, 290, 290, 280, 270],
[280, 300, 320, 320, 320, 320, 300, 280],
[290, 320, 330, 335, 335, 330, 320, 290],
[290, 325, 335, 340, 340, 335, 325, 290],
[290, 320, 335, 340, 340, 335, 320, 290],
[290, 325, 330, 335, 335, 330, 325, 290],
[280, 300, 320, 325, 325, 320, 300, 280],
[270, 280, 290, 290, 290, 290, 280, 270]]
self.bishop_piece_table = [[310, 320, 320, 320, 320, 320, 320, 310],
[320, 330, 330, 330, 330, 330, 330, 320],
[320, 330, 335, 340, 340, 335, 330, 320],
[320, 335, 335, 340, 340, 335, 335, 320],
[320, 330, 340, 340, 340, 340, 330, 320],
[320, 340, 340, 340, 340, 340, 340, 320],
[320, 335, 330, 330, 330, 330, 335, 320],
[310, 320, 320, 320, 320, 320, 320, 310]]
self.rook_piece_table = [[500, 500, 500, 500, 500, 500, 500, 500],
[505, 510, 510, 510, 510, 510, 510, 505],
[495, 500, 500, 500, 500, 500, 500, 495],
[495, 500, 500, 500, 500, 500, 500, 495],
[495, 500, 500, 500, 500, 500, 500, 495],
[495, 500, 500, 500, 500, 500, 500, 495],
[495, 500, 500, 500, 500, 500, 500, 495],
[500, 500, 500, 505, 505, 500, 500, 500]]
self.queen_piece_table = [[880, 890, 890, 895, 895, 890, 890, 880],
[890, 900, 900, 900, 900, 900, 900, 890],
[890, 900, 905, 905, 905, 905, 900, 890],
[895, 900, 905, 905, 905, 905, 900, 895],
[900, 900, 905, 905, 905, 905, 900, 895],
[890, 905, 905, 905, 905, 905, 900, 890],
[890, 900, 905, 900, 900, 900, 900, 890],
[880, 890, 890, 895, 895, 890, 890, 880]]
self.piece_square_tables = {Pawn:self.pawn_piece_table,Knight:self.knight_piece_table,Bishop:self.bishop_piece_table,Rook:self.rook_piece_table,Queen:self.queen_piece_table}
self.king_mid_piece_table = [[19970, 19960, 19960, 19950, 19950, 19960, 19960, 19970],
[19970, 19960, 19960, 19950, 19950, 19960, 19960, 19970],
[19970, 19960, 19960, 19950, 19950, 19960, 19960, 19970],
[19970, 19960, 19960, 19950, 19950, 19960, 19960, 19970],
[19980, 19970, 19970, 19960, 19960, 19970, 19970, 19980],
[19990, 19980, 19980, 19980, 19980, 19980, 19980, 19990],
[20020, 20020, 20000, 20000, 20000, 20000, 20020, 20020],
[20020, 20030, 20010, 20000, 20000, 20010, 20030, 20020]]
self.king_end_piece_table = [[19950, 19960, 19970, 19980, 19980, 19970, 19960, 19950],
[19970, 19980, 19990, 20000, 20000, 19990, 19980, 19970],
[19970, 19990, 20020, 20030, 20030, 20020, 19990, 19970],
[19970, 19990, 20030, 20040, 20040, 20030, 19990, 19970],
[19970, 19990, 20030, 20040, 20040, 20030, 19990, 19970],
[19970, 19990, 20020, 20030, 20030, 20020, 19990, 19970],
[19970, 19970, 20000, 20000, 20000, 20000, 19970, 19970],
[19950, 19970, 19970, 19970, 19970, 19970, 19970, 19950]]
self.hash_table = {}
def zobrist_hash(self):
hashval = 0
#Piece type to the index in the zobrist table
piece_to_index = {Pawn:0,Knight:1,Bishop:2,Rook:3,Queen:4,King:5}
#If turn is white
if self.ai_board.turn == 1:
hashval ^= zobrist_list[0]
#Cycles through pieces
for piece in self.ai_board.pieces:
if piece.alive:
if piece.colour == 1:
hashval ^= zobrist_table[0][piece_to_index[type(piece)]][piece.posx][piece.posy]
else:
hashval ^= zobrist_table[1][piece_to_index[type(piece)]][piece.posx][piece.posy]
if type(piece) == Pawn:
#For en passant ranks
if piece.double_move:
hashval ^= zobrist_list[5+piece.posx]
#Castling rights
if self.ai_board.white_king.original_pos:
if type(self.ai_board.piece(7,7)) == Rook:
if self.ai_board.piece(7,7).original_pos:
hashval ^= zobrist_list[1]
elif type(self.ai_board.piece(0,7)) == Rook:
if self.ai_board.piece(0,7).original_pos:
hashval ^= zobrist_list[2]
if self.ai_board.black_king.original_pos:
if type(self.ai_board.piece(7,0)) == Rook:
if self.ai_board.piece(7,0).original_pos:
hashval ^= zobrist_list[3]
elif type(self.ai_board.piece(0,0)) == Rook:
if self.ai_board.piece(0,0).original_pos:
hashval ^= zobrist_list[4]
return hashval
def generate_move_list(self,colour): #Used to check for checkmate and used in AI
moves = []
for piece in self.ai_board.pieces:
if piece.colour == colour and piece.alive:
if type(piece) == Pawn: #For pawns moving to the final rank, a promotion index is needed
for move in piece.possible_moves():
if piece.colour == -1 and move[1] == 7:
for index in range(1,5):
moves.append(MoveObject(piece.identifier,piece.posx,piece.posy,move,promotion = index))
elif piece.colour == 1 and move[1] == 0:
for index in range(1,5):
moves.append(MoveObject(piece.identifier,piece.posx,piece.posy,move,promotion = index))
else:
moves.append(MoveObject(piece.identifier,piece.posx,piece.posy,move))
else:
for move in piece.possible_moves():
moves.append(MoveObject(piece.identifier,piece.posx,piece.posy,move))
if type(piece) == King: #Castling moves needs to be added as isn't part of possible_moves()
for move in piece.castling_moves():
moves.append(MoveObject(piece.identifier,piece.posx,piece.posy,move))
return moves
def minimax_root(self,boardclass):
s = time.time()
depth = 0
#set alpha and beta for pruning
alpha = -20000
beta = 20000
best_value = -20001
#initialises AI copy of board
self.ai_board = AIboard(self.colour)
self.ai_board.record_of_boards = [boardclass.record_of_boards[-1]]
self.ai_board.turn = self.colour
self.ai_board.revert_board(0)
#Generates a list of moves to iterate through
moves = self.generate_move_list(self.ai_board.turn)
ordered_moves = []
for move in moves:
move.execute(self.ai_board,False)
if self.ai_board.increment_turn():
returned_value = self.evaluate_board()
#Insert move into ordered_moves based on score
for index,item in enumerate(ordered_moves):
if item[1] < returned_value:
ordered_moves.insert(index+1,(move,returned_value))
break
if not (move,returned_value) in ordered_moves:
ordered_moves.append((move,returned_value))
self.ai_board.revert_board(depth)
moves = []
for move_score_tuple in ordered_moves:
moves.append(move_score_tuple[0])
#Cycles through moves to search through move tree
for move in moves:
move.execute(self.ai_board,False)
if self.ai_board.increment_turn():
returned_value = self.minimax_node(depth+1,alpha,beta,False)
if alpha < returned_value: #New move was better
optimal_move = move
alpha = returned_value
self.ai_board.revert_board(0)
#Saves information to transposition table in case board state is searched
hashval = self.zobrist_hash()
self.hash_table[hashval] = (alpha,self.max_depth - depth)
return optimal_move
def minimax_node(self,depth,alpha,beta,is_maximising_node):
hashval = self.zobrist_hash()
if hashval in self.hash_table: #Checks if current board has already been evaluated in a previous search
table_contents = self.hash_table[hashval]
depth_of_val = table_contents[1]
table_heuristic_val = table_contents[0]
if depth_of_val >= self.max_depth - depth: #If stored value is propagated from required depth or deeper
return table_heuristic_val
if depth < self.max_depth:
moves = self.generate_move_list(self.ai_board.turn)
#Shallow search move ordering
if self.max_depth - depth > 1: #If remaining depth is one, shallow search depth is the same as required depth
ordered_moves = []
for move in moves:
move.execute(self.ai_board,False)
if self.ai_board.increment_turn():
returned_value = self.evaluate_board()
#Insert move into ordered_moves based on score
for index,item in enumerate(ordered_moves):
if item[1] < returned_value:
ordered_moves.insert(index+1,(move,returned_value))
break
if not (move,returned_value) in ordered_moves:
ordered_moves.append((move,returned_value))
self.ai_board.revert_board(depth)
moves = []
for move_score_tuple in ordered_moves:
moves.append(move_score_tuple[0])
if is_maximising_node:
alpha = -20001
#Searches through the ordered moves list using minimax
for move in moves:
move_done = move.execute(self.ai_board,False)
if self.ai_board.increment_turn():
returned_value = self.minimax_node(depth+1,alpha,beta,not is_maximising_node)
alpha = max(returned_value,alpha)
if alpha >= beta:
return alpha
self.ai_board.revert_board(depth)
self.hash_table[hashval] = (alpha,self.max_depth - depth)
return alpha
else:
moves.reverse() #Lowest to highest score, since at a minimising node
beta = 20001
for move in moves:
move_done = move.execute(self.ai_board,False)
if self.ai_board.increment_turn():
returned_value = self.minimax_node(depth+1,alpha,beta,not is_maximising_node)
beta = min(returned_value,beta)
if alpha >= beta:
return beta
self.ai_board.revert_board(depth)
self.hash_table[hashval] = (beta,self.max_depth - depth)
return beta
else:
heuristic_val = self.evaluate_board()
self.hash_table[hashval] = (heuristic_val,0)
return heuristic_val
def evaluate_board(self):
score = 0
is_endgame = self.check_endgame()
#Cycles through all the pieces and retrieves values from the tables.
for piece in self.ai_board.pieces:
if piece.alive:
piece_type = type(piece)
if piece.colour == 1:
posx = piece.posx
posy = piece.posy
else:
posx = piece.posx
posy = 7 - piece.posy
if piece_type != King:
piece_score = (self.piece_square_tables[piece_type][posy][posx])
else:
#Endgame is checked as king utilisation in attacking increases in the endgame.
if is_endgame:
piece_score = (self.king_end_piece_table[posy][posx])
else:
piece_score = (self.king_mid_piece_table[posy][posx])
#Accumulates the score
if piece.colour == self.colour:
score += piece_score
else:
score -= piece_score
return score
def check_endgame(self):
#Coutns material in terms of piece and colour
material = {(Pawn,-1):0,(Knight,-1):0,(Bishop,-1):0,(Rook,-1):0,(Queen,-1):0,(King,-1):0,(Pawn,1):0,(Knight,1):0,(Bishop,1):0,(Rook,1):0,(Queen,1):0,(King,1):0}
for piece in self.ai_board.pieces:
if piece.alive:
material[(type(piece),piece.colour)] += 1
black_minor_piece_count = material[(Knight,-1)] + material[(Bishop,-1)] + material[(Rook,-1)]
white_minor_piece_count = material[(Knight,1)] + material[(Bishop,1)] + material[(Rook,1)]
#No queen or a queen and at most one minor piece for each side
return (material[(Queen,-1)] == 0 or black_minor_piece_count <= 1) and (material[(Queen,1)] or white_minor_piece_count <= 1)
##### INTERFACE #####
### Game display classes
class PygameButton:
def __init__(self,display,position,imagepath):
self.image = pygame.image.load(imagepath)
self.rect = pygame.Rect(position,self.image.get_size())
self.position = position
self.display = display
def draw(self):
self.display.blit(self.image,self.position)
def check_clicked(self,event):
#Checks for event_type == pygame.MOUSEBUTTONDOWN already done before this function will be called
if self.rect.collidepoint(event.pos):
self.click_function()
class Arrow(PygameButton):
def __init__(self,interface,position,direction,increment):
imagepath = "Textures/Arrow" + direction + ".png"
PygameButton.__init__(self,interface.game_display,position,imagepath)
self.direction = direction
self.interface = interface #Class assignement, so reference is saved. Any changes to the "local" copy affects the interface copy
self.increment = increment #increment of two is used against an AI
def click_function(self):
if self.direction == "Left":
if self.interface.chess_board.current_index-self.increment >= 0: #Checks if user is not going back further than possible
self.interface.chess_board.revert_board(self.interface.chess_board.current_index-self.increment)
self.interface.update_displayed_pieces()
self.interface.update_move_list()
return True
else:
return False
elif self.direction == "Right": #Checks if user is not going past the newest board state
if self.interface.chess_board.current_index+self.increment <= len(self.interface.chess_board.record_of_boards)-1:
self.interface.chess_board.revert_board(self.interface.chess_board.current_index+self.increment)
self.interface.update_displayed_pieces()
self.interface.update_move_list()
return True
else:
return False
class ExitButton(PygameButton):
def __init__(self,interface,position):
imagepath = "Textures/ExitButton.png"
PygameButton.__init__(self,interface.game_display,position,imagepath)
self.interface = interface
def click_function(self):
#Ask to be sure
self.interface.exit_button_pressed = True
self.interface.exit_game = True
class GameInterface:
def __init__(self,is_playable,AI_player = False,AI_colour = -1,game_file = None): #game_file with .json extension
pygame.init()
#Sets display up and variables needed for display
self.chess_board = Board(1)
self.game_display = pygame.display.set_mode((900,600))
pygame.display.set_caption("Chess")
self.is_playable = is_playable #Is False when looking at historical game
self.selected_piece = None
if not self.is_playable: #If not playable, the application is loading a game
self.chess_board.load_game_to_board(game_file)
self.displayed_pieces = []
self.moves_to_display = list() #Moves of the score sheet that are displayed on the interface peripherals
self.update_displayed_pieces()
self.orientation = AI_colour * -1 #Facing against the AI always, whether black or white. Default, white POV first
self.exit_game = False #Used to determine to exit a game, whether by end game state or user exit
self.exit_button_pressed = False #Whether game has been ended using the exit button
self.buttons = []
if AI_player:
self.game_AI = AI(3,AI_colour)
self.buttons.append(Arrow(self,(615,20),"Left",2))
self.buttons.append(Arrow(self,(690,20),"Right",2))
else:
self.game_AI = None
self.buttons.append(Arrow(self,(615,20),"Left",1))
self.buttons.append(Arrow(self,(690,20),"Right",1))
self.buttons.append(ExitButton(self,(770,20)))
self.move_font = pygame.font.SysFont('couriernew',16)
self.prompt_font = pygame.font.SysFont('arial',20)
self.prompt = None
self.square_width = 75 #Constant used to determine the position of the mouse on the board
def update_display(self):
###Updates display
#Clears the board
self.game_display.fill((60,60,60))
#Displays the chess_board image and buttons
self.game_display.blit(self.chess_board.image,(0,0))
for button in self.buttons:
button.draw()
#Displays prompt
self.display_prompt()
#Displays move list
self.display_move_list()
#Displays each individual piece, while considering the self.orientation
for piece in self.displayed_pieces:
if self.orientation == 1:
self.game_display.blit(piece.image,(piece.posx*self.square_width+7,piece.posy*self.square_width+7))
else:
self.game_display.blit(piece.image,(532-(piece.posx*self.square_width),532-(piece.posy*self.square_width)))
#Displays the self.selected_piece to be directly on the mouse
if self.selected_piece != None:
x,y = pygame.mouse.get_pos()
self.game_display.blit(self.selected_piece.image,(x-30,y-27))
#The image created using the algorithm above is displayed on the screen
pygame.display.update()
def display_prompt(self):
if self.prompt != None:
text = self.prompt_font.render(self.prompt,True,(0,0,0),(255,0,0))
text_rect = text.get_rect()
text_rect.top = 160
text_rect.left = 630
self.game_display.blit(text,text_rect)
def update_move_list(self):
move_display_count = 20 #Max moves that can be displayed at once
if self.chess_board.current_index == 0: #Reverted to original board state
self.moves_to_display = self.chess_board.list_of_moves[0:move_display_count]
self.first_displayed_move_index = 0
elif len(self.chess_board.list_of_moves) > move_display_count: #more than can be displayed at once
rows_for_all_moves = (len(self.chess_board.list_of_moves)+1)//2 #Number of rows an extensive score sheet of the game would need
row_of_current_move = (self.chess_board.current_index+1)//2 #Row of move previous to the current board state being displayed
if row_of_current_move <= rows_for_all_moves - round(move_display_count/2): #If current move is not recent enough
if (self.chess_board.current_index - 1) % 2 == 0: #White turn, as white move must be displayed in the top left
self.moves_to_display = self.chess_board.list_of_moves[self.chess_board.current_index - 1:self.chess_board.current_index + move_display_count - 1]