-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmatch.py
2349 lines (1994 loc) · 118 KB
/
match.py
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
import pygame
from dataclasses import dataclass, field
import font, board, pieces
from board import row_of_
from board import (
NORTE, NOR_ESTE, NOR_OESTE,
SUR, SUR_OESTE, SUR_ESTE,
ESTE, OESTE) # piece movement directions
# Game colors -------------------------
LEGAL_MOV_HIGHLIGHT = (100,230,100)
LEGAL_KILL_HIGHLIGHT = (230,100,100)
CASTLING_HIGHLIGHT = (100,100,230)
EMPTY_SQUARE_HIGHLIGHT = (230,230,230)
GRAY_BTN_HOVER = (230,230,230)
# -------------------------------------
@dataclass
class PlayerTeamUnit:
name: str
direct_threatOrigin_type: str # 'single' or 'multiple' or 'none'
single_threat_standpoint: int | None
king_banned_direction: int | None
legal_moves: set[str] # DEFENDER ONLY perspective util
all_effectiveThreat_standpoints: list[int] = field(default_factory=list)
single_directThreatOnEnemy_trace: list[int] = field(default_factory=list)
positions: dict[int, str] = field(default_factory=dict)
pawns_in_origin: list[int] = field(default_factory=list)
all_threat_emissions: dict[str, int] = field(default_factory=dict)
king_legal_moves: list[int] = field(default_factory=list)
castling_enablers: dict[int, str] = field(default_factory=dict)
enPassant_enablers: dict[str, int] = field(default_factory=dict)
def clear(self):
self.all_threat_emissions.clear()
self.king_legal_moves.clear()
self.single_directThreatOnEnemy_trace.clear()
self.direct_threatOrigin_type = 'none'
self.single_threat_standpoint = None
self.king_banned_direction = None
self.legal_moves.clear()
self.all_effectiveThreat_standpoints.clear()
class Match:
def __init__(self, screen, control_input):
self.running = True
self.screen = screen
self.curtain = pygame.Surface(self.screen.get_size(), flags=pygame.SRCALPHA)
self.control_input = control_input
self.mid_screen_coordinates = (self.screen.get_width()/2, self.screen.get_height()/2)
self.mid_screen = pygame.Vector2(self.mid_screen_coordinates)
board.place(self.mid_screen)
self.set_content()
def set_content(self):
# Match initial content -----------------------------------------------
self.black = PlayerTeamUnit(
name = 'black',
direct_threatOrigin_type = 'none',
single_directThreatOnEnemy_trace = [],
positions = pieces.black_positions.copy(),
pawns_in_origin = [bpawn for bpawn in pieces.origins['black']['pawn']],
all_threat_emissions = {piece:[] for piece in pieces.origins['black']},
king_legal_moves = [],
castling_enablers = {0: 'west-rook', 4: 'king', 7: 'east-rook'},
single_threat_standpoint = None,
king_banned_direction = None,
legal_moves = set(),
all_effectiveThreat_standpoints = [],
enPassant_enablers = {'true-pos': None, 'offset-kill-pos': None}
)
self.white = PlayerTeamUnit(
name = 'white',
direct_threatOrigin_type = 'none',
single_directThreatOnEnemy_trace = [],
positions = pieces.white_positions.copy(),
pawns_in_origin = [wpawn for wpawn in pieces.origins['white']['pawn']],
all_threat_emissions = {piece:[] for piece in pieces.origins['white']},
king_legal_moves = [],
castling_enablers = {56: 'west-rook', 60: 'king', 63: 'east-rook'},
single_threat_standpoint = None,
king_banned_direction = None,
legal_moves = set(),
all_effectiveThreat_standpoints = [],
enPassant_enablers = {'true-pos': None, 'offset-kill-pos': None}
)
# menu spawn variables / game halt reasons
self.pause = False
self.player_selecting_gameClockLimit = True # match opening
self.player_deciding_match: bool = False
self.player_deciding_promotion: bool = False
self.winner: bool = False
self.game_halt: bool = False
self.stalemate: bool = False # Ahogado | draw
self.show_switchable_menu = True
# ---------------------------------------
self.curtain_transparency = 255
self.curtain.fill((255,255,255,self.curtain_transparency))
self.showing_openingCurtain = True
self.showing_closingCurtain = False
self.match_restarting = False
# core game variables -------------------
self.move_here: int | None = None
self.match_state: str = '' # HUD info
self.killing: bool = False
self.finish_turn: bool = False # turn halt utility
self.pawn_being_promoted: int | None = None
self.castling: bool = False
self.castling_direction: str = ''
self.pawn_doubleMove: bool = False
self.killing_enPassant: bool = False
# ---------------------------------------
# turn look-ups
self.turn_attacker: PlayerTeamUnit = self.white
self.turn_defender: PlayerTeamUnit = self.black
# board feedback utilities
self.selectedPiece_legalMoves: list[int] = []
self.selectedPiece_killMoves: list[int] = []
self.selectedPiece_castlingMoves: list[int] = []
self.selectedPiece_pawnDoubleMove: list[int] = []
self.selectedPiece_pawnKillingEnPassant: list[int] = []
# turn clocks (defaults)
self.gameClockLimit_minutes: int = 10
self.whitetime_SNAP: int = pygame.time.get_ticks()
self.blacktime_SNAP: int = pygame.time.get_ticks()
self.pausetime_SNAP: int = 0
self.black_turn_time: int = self.gameClockLimit_minutes * 60
self.black_turn_minutes: str = '00'
self.black_turn_seconds: str = '00'
self.white_turn_time: int = self.gameClockLimit_minutes * 60
self.white_turn_minutes: str = '00'
self.white_turn_seconds: str = '00'
# clock + or - remnants
self.white_time_leftover: int = 0
self.black_time_leftover: int = 0
self.pause_time_leftover: int = 0
# ---------------------------------------------------------------
def set_turn_clocks(self, minutes: int):
self.gameClockLimit_minutes = minutes
self.whitetime_SNAP: int = pygame.time.get_ticks()
self.blacktime_SNAP: int = pygame.time.get_ticks()
self.pausetime_SNAP: int = 0
self.black_turn_time: int = self.gameClockLimit_minutes * 60
self.black_turn_minutes: str = str(int(self.black_turn_time/60))
self.black_turn_seconds: str = '00'
self.white_turn_time: int = self.gameClockLimit_minutes * 60
self.white_turn_minutes: str = str(int(self.white_turn_time/60))
self.white_turn_seconds: str = '00'
# clock + or - remnants
self.white_time_leftover: int = 0
self.black_time_leftover: int = 0
self.pause_time_leftover: int = 1200 # default match count-down
def draw_text(
self,text,
color, x, y,
center=False, font_size='large',
x_center=False):
_font = font.large_font if font_size=='large' else font.medium_font
textobj = _font.render(text,1,color)
text_width = textobj.get_width()
text_height = textobj.get_height()
textrect = textobj.get_rect()
if center: textrect.topleft = (x - text_width/2, y - text_height/2) # x&y center
elif x_center: textrect.topleft = (x - text_width/2, y)
else: textrect.topleft = (x,y)
self.screen.blit(textobj,textrect)
def check_pawn_promotion(self):
# Obtener standpoints PAWN de attacker
pawn_standpoints: list[int] = self.get_piece_standpoint(color=self.turn_attacker.name,piece="pawn")
# Revisar si algun pawn llegó a la fila objetivo
if self.turn_attacker.name == 'white':
for _pawn in pawn_standpoints:
if _pawn in row_of_(0): # NORTHMOST ROW
self.pawn_being_promoted = _pawn
self.player_deciding_promotion = True
self.finish_turn = False
# Revisar si algun pawn llegó a la fila objetivo
if self.turn_attacker.name == 'black':
for _pawn in pawn_standpoints:
if _pawn in row_of_(63): # SOUTHMOST ROW
self.pawn_being_promoted = _pawn
self.player_deciding_promotion = True
self.finish_turn = False
# allows turn to finish
if self.pawn_being_promoted == None: self.finish_turn = True
def make_promotion(self, selected_piece: str):
self.turn_attacker.positions.update({self.pawn_being_promoted: selected_piece})
self.pawn_being_promoted = None
self.finish_turn = True
def trace_direction_walk(
self,
defKing_standpoint: int,
mixedDirections_threats: list[int],
attThreat_standpoint: int,
) -> list[int]:
'''Caminamos desde el rey hasta la amenaza devolviendo una traza.
INCLUYE STANDPOINT DEL REY PERO NO DE LA AMENAZA.
Prohibe al rey -actual defensor- dirigirse en la dirección de la amenaza.'''
# direcciones
cardinal_directions = [NORTE,SUR,ESTE,OESTE,NOR_OESTE,NOR_ESTE,SUR_OESTE,SUR_ESTE]
walk_trace: set[int] = set()
for direction in cardinal_directions:
walk_trace.clear()
walk_trace.add(defKing_standpoint)
for mult in range(1,8):
walk = defKing_standpoint+direction*mult
if direction == ESTE or direction == OESTE:
if walk not in row_of_(defKing_standpoint):
break
if direction == NOR_ESTE or direction == NOR_OESTE:
if walk not in row_of_(defKing_standpoint+NORTE*mult):
break
if direction == SUR_ESTE or direction == SUR_OESTE:
if walk not in row_of_(defKing_standpoint+SUR*mult):
break
if 0 <= walk <= 63: # VALID SQUARE
if walk == attThreat_standpoint:
self.turn_defender.king_banned_direction = -direction # requiere dirección inversa
return walk_trace
elif walk in mixedDirections_threats:
walk_trace.add(walk)
return walk_trace # vacía si llega a este punto.
def update_turn_objectives(self):
'''Llama a todas las funciones _objectives() con sus correctas perspectivas-de-turno.
En este punto de la ejecución, el atacante ya hizo su acción.
Antes de ser utilizadas, estas variables (excepto defender.threat_on_enemy que puede contener
información del *jaque actual* y es resultado de transferencia/SWAP ) deben limpiarse para evitar
un solapamiento infinito de posiciones.
Primero debemos actualizar la ofensiva -siendo restringido por la defensiva-, y luego la defensiva,
revisando especialmente si puede salvar a su rey ante la última jugada ofensiva.
direct_threatOrigin_type será siempre y únicamente calculado luego de evaluar la ofensiva,
Si es multiple el rey depende SOLO de su propio movimiento (o será jaque-mate).
Desde perspectiva = 'attacker' es importante:
>> Si defender.direct_threatOrigin_type = 'single' (jaque), solo puedo moverme/atacar si
eso salva a mi rey.
>> Si no existe jaque, debo revisar si "salirme del casillero" -moviendome o matando-
expone a mi rey a un jaque.
-> exposing_movement(standpoint, direction, request_from)
>> Actualizaremos attacker.threat_on_enemy
-> kill-movements aún no hechos pero que "amenazan" CASILLEROS VACÍOS/CASILLEROS CON ALIADOS EN EL.
Desde perspectiva = 'defender' es importante:
>> Verificar y validar LEGAL-MOVES, si resultan ser 0 para el actual defensor, el actual atacante ganó o empató.
>> Los movimientos descartados (ilegales) son aquellos que no puedan realizarse si,
NO ESTANDO EN JAQUE:
- Por bloqueo aliado
- Por ser expositivos-al-rey
ESTANDO EN JAQUE:
- Por bloqueo aliado
- Por ser REexpositivos-al-rey
- Por no *salvar al rey* (matando o bloqueando la amenaza)
'''
self.turn_attacker.clear()
self.turn_defender.clear()
# Attacker ----------------------------------------------------------------------------------------
king_standpoints: list[int] = self.get_piece_standpoint(color=self.turn_attacker.name, piece="king")
if len(king_standpoints) != 0:
_king = king_standpoints.pop()
self.king_objectives(_king, perspective='attacker')
pawn_standpoints: list[int] = self.get_piece_standpoint(color=self.turn_attacker.name,piece="pawn")
for _pawn in pawn_standpoints:
self.pawn_objectives(_pawn, perspective='attacker')
rook_standpoints: list[int] = self.get_piece_standpoint(color=self.turn_attacker.name,piece="rook")
for _rook in rook_standpoints:
self.rook_objectives(_rook, perspective='attacker')
bishop_standpoints: list[int] = self.get_piece_standpoint(color=self.turn_attacker.name,piece="bishop")
for _bishop in bishop_standpoints:
self.bishop_objectives(_bishop, perspective='attacker')
knight_standpoints: list[int] = self.get_piece_standpoint(color=self.turn_attacker.name, piece="knight")
for _knight in knight_standpoints:
self.knight_objectives(_knight, perspective='attacker')
queen_standpoint: list[int] = self.get_piece_standpoint(color=self.turn_attacker.name, piece="queen")
for _queen in queen_standpoint:
self.queen_objectives(_queen, perspective='attacker')
# --------------------------------------------------------------------------------------------------
# Defender -----------------------------------------------------------------------------------------
king_standpoints: list[int] = self.get_piece_standpoint(color=self.turn_defender.name, piece="king")
if len(king_standpoints) != 0:
_king = king_standpoints.pop()
# Revisión del estado de la amenaza del atacante sobre el rey defensor (jaque)
# La posición de orígen de la amenaza estará SIEMPRE en _threats_list[-1].
# attacker.single_directThreatOnEnemy_trace NO INCLUIRÁ STANDPOINT DE LA AMENAZA.
# Si la pieza amenazante es el caballo NO llamar a trace_direction_walk.
for _threats_list in self.turn_attacker.all_threat_emissions.values():
if _king in _threats_list:
self.turn_attacker.all_effectiveThreat_standpoints.append(_threats_list[-1])
if self.turn_attacker.direct_threatOrigin_type == 'single': # caso amenaza múltiple
self.turn_attacker.direct_threatOrigin_type = 'multiple'
self.turn_attacker.single_threat_standpoint = None
self.turn_attacker.single_directThreatOnEnemy_trace.clear()
if self.turn_attacker.direct_threatOrigin_type == 'none':
self.turn_attacker.direct_threatOrigin_type = 'single'
self.turn_attacker.single_threat_standpoint = _threats_list[-1]
knight_walk_exception: str = self.turn_attacker.positions[_threats_list[-1]]
if knight_walk_exception != 'knight':
self.turn_attacker.single_directThreatOnEnemy_trace = self.trace_direction_walk(_king, _threats_list, _threats_list[-1])
else: self.turn_attacker.single_directThreatOnEnemy_trace = []
self.remove_all_attacker_standpoints() # Necesario para que el rey pueda identificar piezas como -quizás matable-.
self.king_objectives(_king, perspective='defender') # genero/reviso defender.king_legal_moves.
if self.turn_attacker.direct_threatOrigin_type != 'multiple':
# defender legal moves update
pawn_standpoints = self.get_piece_standpoint(color=self.turn_defender.name, piece='pawn')
for _pawn in pawn_standpoints:
self.pawn_objectives(_pawn, perspective='defender')
rook_standpoints = self.get_piece_standpoint(color=self.turn_defender.name, piece="rook")
for _rook in rook_standpoints:
self.rook_objectives(_rook, perspective='defender')
bishop_standpoints = self.get_piece_standpoint(color=self.turn_defender.name, piece='bishop')
for _bishop in bishop_standpoints:
self.bishop_objectives(_bishop, perspective='defender')
knight_standpoints = self.get_piece_standpoint(color=self.turn_defender.name, piece="knight")
for _knight in knight_standpoints:
self.knight_objectives(_knight, perspective='defender')
queen_standpoint = self.get_piece_standpoint(color=self.turn_defender.name, piece="queen")
for _queen in queen_standpoint:
self.queen_objectives(_queen, perspective='defender')
# -------------------------------------------------------------------------------------------------
def turn_swap(self):
if self.turn_attacker == self.white:
if self.black_time_leftover < 1000:
self.blacktime_SNAP = pygame.time.get_ticks() - self.black_time_leftover
else:
self.blacktime_SNAP = pygame.time.get_ticks() + self.black_time_leftover
self.turn_attacker = self.black
self.turn_defender = self.white
return
if self.turn_attacker == self.black:
if self.white_time_leftover < 1000:
self.whitetime_SNAP = pygame.time.get_ticks() - self.white_time_leftover
else:
self.whitetime_SNAP = pygame.time.get_ticks() + self.white_time_leftover
self.turn_attacker = self.white
self.turn_defender = self.black
return
def remove_all_attacker_standpoints(self):
'''
Quita TODOS los standpoints de attacker.all_threat_emissions
(y por consecuente su vuelta como def_threatOnAtt).
Los standpoints coinciden SIEMPRE en el último item de la lista de amenazas.
De esta forma aclaramos la visión al rey correspondiente en cuanto a amenazas
matables y no-matables.
'''
for _threats in self.turn_attacker.all_threat_emissions.values():
_threats.pop()
def exposing_direction(self, standpoint: int, intended_move: int, request_from: str) -> bool:
'''Para verificar si un movimiento expone al rey aliado "falsificaremos"
un movimiento contra el conjunto de piezas que corresponda.
>> falsificar defenderMov contra attacker
>> falsificar attackerMov contra defender
Llamaremos a las piezas correspondientes en la :perspectiva: correspondiente:
"fake-attackerMov-toDef"
"fake-defenderMov-toAtt"
E inyectandoles posiciones falsas de "quien consulta".
Cada piece_objective(perspective="fake...") devolverá:
TRUE si encontró al rey en amenaza directa.
FALSE si NO lo hizo.
Crearemos un conjunto de posiciones falsas mediante:
Buscar en el conjunto "consultante" el standpoint y lo reemplazaremos por
fake_move (standpoint + direction)
Esto deja un "hueco" por donde ahora el rey podría ser amenazado.
**IMPORTANTE** Los peones y caballos NUNCA influyen en exposing-movements
**IMPORTANTE** En perspectivas "fake..." NO se toman en cuenta *nuevos* exposing-movements.
**IMPORTANTE** Para evitar superposiciones de piezas que nos den errores, no haremos
las comprobaciones de fake_positions que caigan justo en posición del otro equipo.
la superposición de posiciones nos dictará incorrectamente que estamos exponiendo al rey
cuando en realidad *quizás* mataríamos a la pieza.
'''
fake_move: int = standpoint+intended_move
fake_positions: dict[int, str] = {}
if request_from == 'attacker':
for ap in self.turn_attacker.positions.keys():
if standpoint != ap:
fake_positions.update({ap: self.turn_attacker.positions[ap]})
else:
fake_positions.update({fake_move: self.turn_attacker.positions[ap]})
if self.turn_defender.direct_threatOrigin_type == 'single':
'''Si el ATACANTE está en jaque, debemos filtrar y descartar la pieza
DEFENSORA amenazante (Torre, Alfil o Reina) o nunca obtendremos el resultado
esperado, nos dirá SIEMPRE que estamos exponiendo al rey porque ya está en jaque.'''
# nombre de la pieza que NO debemos considerar
rejected_piece: str = self.turn_defender.positions[self.turn_defender.single_threat_standpoint]
if rejected_piece != 'rook':
rook_standpoints: list[int] = self.get_piece_standpoint(color=self.turn_defender.name,piece="rook")
for _rook in rook_standpoints:
if _rook != fake_move:
if self.rook_objectives(_rook, perspective='fake-attackerMov-toDef', fake_positions=fake_positions):
return True
if rejected_piece != 'bishop':
bishop_standpoints: list[int] = self.get_piece_standpoint(color=self.turn_defender.name,piece="bishop")
for _bishop in bishop_standpoints:
if _bishop != fake_move:
if self.bishop_objectives(_bishop, perspective='fake-attackerMov-toDef', fake_positions=fake_positions):
return True
if rejected_piece != 'queen':
queen_standpoint: list[int] = self.get_piece_standpoint(color=self.turn_defender.name, piece="queen")
for _queen in queen_standpoint:
if _queen != fake_move:
if self.queen_objectives(_queen, perspective='fake-attackerMov-toDef', fake_positions=fake_positions):
return True
return False
elif self.turn_defender.direct_threatOrigin_type == 'none':
rook_standpoints: list[int] = self.get_piece_standpoint(color=self.turn_defender.name,piece="rook")
for _rook in rook_standpoints:
if _rook != fake_move:
if self.rook_objectives(_rook, perspective='fake-attackerMov-toDef', fake_positions=fake_positions):
return True
bishop_standpoints: list[int] = self.get_piece_standpoint(color=self.turn_defender.name,piece="bishop")
for _bishop in bishop_standpoints:
if _bishop != fake_move:
if self.bishop_objectives(_bishop, perspective='fake-attackerMov-toDef', fake_positions=fake_positions):
return True
queen_standpoint: list[int] = self.get_piece_standpoint(color=self.turn_defender.name, piece="queen")
for _queen in queen_standpoint:
if _queen != fake_move:
if self.queen_objectives(_queen, perspective='fake-attackerMov-toDef', fake_positions=fake_positions):
return True
return False
if request_from == 'defender':
for dp in self.turn_defender.positions.keys():
if standpoint != dp:
fake_positions.update({dp: self.turn_defender.positions[dp]})
else:
fake_positions.update({fake_move: self.turn_defender.positions[dp]})
rook_standpoints: list[int] = self.get_piece_standpoint(color=self.turn_attacker.name,piece="rook")
for _rook in rook_standpoints:
if _rook != fake_move:
if self.rook_objectives(_rook, perspective='fake-defenderMov-toAtt', fake_positions=fake_positions):
return True
bishop_standpoints: list[int] = self.get_piece_standpoint(color=self.turn_attacker.name,piece="bishop")
for _bishop in bishop_standpoints:
if _bishop != fake_move:
if self.bishop_objectives(_bishop, perspective='fake-defenderMov-toAtt', fake_positions=fake_positions):
return True
queen_standpoint: list[int] = self.get_piece_standpoint(color=self.turn_attacker.name, piece="queen")
for _queen in queen_standpoint:
if _queen != fake_move:
if self.queen_objectives(_queen, perspective='fake-defenderMov-toAtt', fake_positions=fake_positions):
return True
return False
def pawn_objectives(self, piece_standpoint: int, perspective: str) -> dict[int, pygame.Rect] | None:
'''Movimiento Peón:
NORTE (white)
SUR (black)
1 casillero por vez, excepto que sea su primer movimiento,
lo que hará que pueda moverse 2 casilleros a la vez.
Kill mov. Peón:
Peon NEGRO: SUR_OESTE, SUR_ESTE
Peon BLANCO: NOR_OESTE, NOR_ESTE
'''
# Objectives
_legal_movements: list[int] = [piece_standpoint] # standpoint is always first pos
on_target_kill_positions: list[int] = []
double_movement: list[int] = []
on_target_enPassant_killPositions: list[int] = []
# Pre-objectives
kill_positions: list[int] = []
movement: int
if perspective == 'defender' :
if self.turn_defender.name == 'black': # defiende hacia el SUR
# 1st Movement
movement = piece_standpoint+SUR
if movement <= 63: # board limit
if self.turn_attacker.direct_threatOrigin_type == 'single':
if movement not in self.turn_defender.positions or movement not in self.turn_attacker.positions:
if not self.exposing_direction(piece_standpoint, intended_move=SUR, request_from="defender"):
# 1st Movement -BLOCK saving position-
if movement in self.turn_attacker.single_directThreatOnEnemy_trace:
self.turn_defender.legal_moves.add(f'pawn{piece_standpoint}')
# 2nd Movement
if piece_standpoint in self.black.pawns_in_origin:
if movement+SUR not in self.turn_defender.positions:
# 2nd Movement -BLOCK saving position-
if movement+SUR in self.turn_attacker.single_directThreatOnEnemy_trace:
self.turn_defender.legal_moves.add(f'pawn{piece_standpoint}')
else: pass
# kill saving positions
# board limits check
if piece_standpoint+OESTE not in row_of_(piece_standpoint):
kill_positions.append(piece_standpoint+SUR_ESTE)
if piece_standpoint+ESTE not in row_of_(piece_standpoint):
kill_positions.append(piece_standpoint+SUR_OESTE)
elif len(kill_positions) == 0:
kill_positions.extend([piece_standpoint+SUR_OESTE, piece_standpoint+SUR_ESTE])
for kp in kill_positions:
if kp in self.turn_attacker.positions:
if not self.exposing_direction(piece_standpoint, intended_move=kp-piece_standpoint, request_from="defender"):
if kp == self.turn_attacker.single_threat_standpoint:
self.turn_defender.legal_moves.add(f'pawn{piece_standpoint}')
return
elif self.turn_attacker.direct_threatOrigin_type == 'none':
if movement not in self.turn_defender.positions or movement not in self.turn_attacker.positions:
if not self.exposing_direction(piece_standpoint, intended_move=SUR, request_from="defender"):
self.turn_defender.legal_moves.add(f'pawn{piece_standpoint}')
else: pass
# kill positions
# board limits check
if piece_standpoint+OESTE not in row_of_(piece_standpoint):
kill_positions.append(piece_standpoint+SUR_ESTE)
if piece_standpoint+ESTE not in row_of_(piece_standpoint):
kill_positions.append(piece_standpoint+SUR_OESTE)
elif len(kill_positions) == 0:
kill_positions.extend([piece_standpoint+SUR_OESTE, piece_standpoint+SUR_ESTE])
for kp in kill_positions:
if kp in self.turn_attacker.positions:
if not self.exposing_direction(piece_standpoint, intended_move=kp-piece_standpoint, request_from="defender"):
self.turn_defender.legal_moves.add(f'pawn{piece_standpoint}')
return
return
if self.turn_defender.name == 'white': # defiende hacia el NORTE
# 1st Movement
movement = piece_standpoint+NORTE
if movement <= 63: # board limit
if self.turn_attacker.direct_threatOrigin_type == 'single':
if movement not in self.turn_defender.positions:
if not self.exposing_direction(piece_standpoint, intended_move=NORTE, request_from="defender"):
# 1st Movement -BLOCK saving position-
if movement in self.turn_attacker.single_directThreatOnEnemy_trace:
self.turn_defender.legal_moves.add(f'pawn{piece_standpoint}')
# 2nd Movement
if piece_standpoint in self.white.pawns_in_origin:
if movement+NORTE not in self.turn_defender.positions:
# 2nd Movement -BLOCK saving position-
if movement+NORTE in self.turn_attacker.single_directThreatOnEnemy_trace:
self.turn_defender.legal_moves.add(f'pawn{piece_standpoint}')
else: pass
# kill saving positions
# board limits check
if piece_standpoint+OESTE not in row_of_(piece_standpoint):
kill_positions.append(piece_standpoint+NOR_ESTE)
if piece_standpoint+ESTE not in row_of_(piece_standpoint):
kill_positions.append(piece_standpoint+NOR_OESTE)
elif len(kill_positions) == 0:
kill_positions.extend([piece_standpoint+NOR_OESTE, piece_standpoint+NOR_ESTE])
for kp in kill_positions:
if kp in self.turn_attacker.positions:
if not self.exposing_direction(piece_standpoint, intended_move=kp-piece_standpoint, request_from="defender"):
if kp == self.turn_attacker.single_threat_standpoint:
self.turn_defender.legal_moves.add(f'pawn{piece_standpoint}')
return
elif self.turn_attacker.direct_threatOrigin_type == 'none':
if movement not in self.turn_defender.positions or movement not in self.turn_attacker.positions:
if not self.exposing_direction(piece_standpoint, intended_move=NORTE, request_from="defender"):
self.turn_defender.legal_moves.add(f'pawn{piece_standpoint}')
else: pass
# kill saving positions
# board limits check
if piece_standpoint+OESTE not in row_of_(piece_standpoint):
kill_positions.append(piece_standpoint+NOR_ESTE)
if piece_standpoint+ESTE not in row_of_(piece_standpoint):
kill_positions.append(piece_standpoint+NOR_OESTE)
elif len(kill_positions) == 0:
kill_positions.extend([piece_standpoint+NOR_OESTE, piece_standpoint+NOR_ESTE])
for kp in kill_positions:
if kp in self.turn_attacker.positions:
if not self.exposing_direction(piece_standpoint, intended_move=kp-piece_standpoint, request_from="defender"):
self.turn_defender.legal_moves.add(f'pawn{piece_standpoint}')
return
return
return
if perspective == 'attacker':
if self.turn_attacker.name == 'black': # Ataca hacia el SUR
# 1st Movement
movement = piece_standpoint+SUR
if movement <= 63: # board limit
if self.turn_defender.direct_threatOrigin_type == 'single':
if movement not in self.turn_attacker.positions and movement not in self.turn_defender.positions: # piece block
if not self.exposing_direction(piece_standpoint, intended_move=SUR, request_from="attacker"):
if movement in self.turn_defender.single_directThreatOnEnemy_trace:
# BLOCK saving position
_legal_movements.append(movement)
#probamos con el 2do mov
if piece_standpoint in self.black.pawns_in_origin:
if movement+SUR not in self.turn_attacker.positions and movement+SUR not in self.turn_defender.positions: # piece block
if movement+SUR in self.turn_defender.single_directThreatOnEnemy_trace:
# BLOCK saving position
double_movement.append(movement+SUR)
# kill-movements
# board limits check
if piece_standpoint+OESTE not in row_of_(piece_standpoint):
kill_positions.append(piece_standpoint+SUR_ESTE)
if piece_standpoint+ESTE not in row_of_(piece_standpoint):
kill_positions.append(piece_standpoint+SUR_OESTE)
elif len(kill_positions) == 0:
kill_positions.extend([piece_standpoint+SUR_OESTE, piece_standpoint+SUR_ESTE])
for kp in kill_positions:
if kp not in self.turn_attacker.positions:
if not self.exposing_direction(piece_standpoint, intended_move=kp-piece_standpoint, request_from="attacker"):
if kp == self.turn_defender.single_threat_standpoint:
# KILL saving position
on_target_kill_positions.append(kp)
if kp == self.turn_defender.enPassant_enablers['offset-kill-pos']:
if self.turn_defender.single_threat_standpoint == self.turn_defender.enPassant_enablers['true-pos']:
# KILL saving position
on_target_enPassant_killPositions.append(kp)
return _legal_movements, on_target_kill_positions, double_movement, on_target_enPassant_killPositions
elif self.turn_defender.direct_threatOrigin_type == 'none':
if movement not in self.turn_attacker.positions and movement not in self.turn_defender.positions: # piece block
if not self.exposing_direction(piece_standpoint, intended_move=SUR, request_from="attacker"):
_legal_movements.append(movement) # 1st Movement
if piece_standpoint in self.black.pawns_in_origin:
if movement+SUR <= 63: # board limit check
if movement+SUR not in self.turn_attacker.positions and movement+SUR not in self.turn_defender.positions: # piece block
double_movement.append(movement+SUR)
else: pass
# kill-movements
# board limits check
if piece_standpoint+OESTE not in row_of_(piece_standpoint):
kill_positions.append(piece_standpoint+SUR_ESTE)
if piece_standpoint+ESTE not in row_of_(piece_standpoint):
kill_positions.append(piece_standpoint+SUR_OESTE)
elif len(kill_positions) == 0:
kill_positions.extend([piece_standpoint+SUR_OESTE, piece_standpoint+SUR_ESTE])
for kp in kill_positions:
if kp not in self.turn_attacker.positions:
if not self.exposing_direction(piece_standpoint, intended_move=kp-piece_standpoint, request_from="attacker"):
if kp in self.turn_defender.positions:
on_target_kill_positions.append(kp)
elif kp == self.turn_defender.enPassant_enablers['offset-kill-pos']:
on_target_enPassant_killPositions.append(kp)
# Threat on defender ------------------------
kill_positions.append(piece_standpoint)
self.turn_attacker.all_threat_emissions.update({f'pawn{piece_standpoint}': kill_positions})
return _legal_movements, on_target_kill_positions, double_movement, on_target_enPassant_killPositions
if self.turn_attacker.name == 'white': # Ataca hacia el NORTE
# 1st Movement
movement = piece_standpoint+NORTE
if movement >= 0: # board limit
if self.turn_defender.direct_threatOrigin_type == 'single':
if movement not in self.turn_attacker.positions and movement not in self.turn_defender.positions: # piece block
if not self.exposing_direction(piece_standpoint, intended_move=NORTE, request_from="attacker"):
if movement in self.turn_defender.single_directThreatOnEnemy_trace:
# BLOCK saving position
_legal_movements.append(movement)
#probamos con el 2do mov
if piece_standpoint in self.white.pawns_in_origin:
if movement+NORTE not in self.turn_attacker.positions and movement+NORTE not in self.turn_defender.positions:# piece block
if movement+NORTE in self.turn_defender.single_directThreatOnEnemy_trace:
# BLOCK saving position
double_movement.append(movement+NORTE)
# kill-movements
# board limits check
if piece_standpoint+OESTE not in row_of_(piece_standpoint):
kill_positions.append(piece_standpoint+NOR_ESTE)
if piece_standpoint+ESTE not in row_of_(piece_standpoint):
kill_positions.append(piece_standpoint+NOR_OESTE)
elif len(kill_positions) == 0:
kill_positions.extend([piece_standpoint+NOR_OESTE, piece_standpoint+NOR_ESTE])
for kp in kill_positions:
if kp not in self.turn_attacker.positions:
if not self.exposing_direction(piece_standpoint, intended_move=kp-piece_standpoint, request_from="attacker"):
if kp == self.turn_defender.single_threat_standpoint:
# KILL saving position
on_target_kill_positions.append(kp)
if kp == self.turn_defender.enPassant_enablers['offset-kill-pos']:
if self.turn_defender.single_threat_standpoint == self.turn_defender.enPassant_enablers['true-pos']:
# KILL saving position
on_target_enPassant_killPositions.append(kp)
return _legal_movements, on_target_kill_positions, double_movement, on_target_enPassant_killPositions
elif self.turn_defender.direct_threatOrigin_type == 'none': # no jaque
if movement not in self.turn_attacker.positions and movement not in self.turn_defender.positions: # piece block
if not self.exposing_direction(piece_standpoint, intended_move=NORTE, request_from="attacker"):
_legal_movements.append(movement) # 1st Movement
if piece_standpoint in self.white.pawns_in_origin:
if movement+NORTE >= 0: # board limit check
if movement+NORTE not in self.black.positions and movement+NORTE not in self.white.positions: # piece block
double_movement.append(movement+NORTE)
else: pass
# kill-movements
# board limits check
if piece_standpoint+OESTE not in row_of_(piece_standpoint):
kill_positions.append(piece_standpoint+NOR_ESTE)
if piece_standpoint+ESTE not in row_of_(piece_standpoint):
kill_positions.append(piece_standpoint+NOR_OESTE)
elif len(kill_positions) == 0:
kill_positions.extend([piece_standpoint+NOR_OESTE, piece_standpoint+NOR_ESTE])
for kp in kill_positions:
if kp not in self.turn_attacker.positions:
if not self.exposing_direction(piece_standpoint, intended_move=kp-piece_standpoint, request_from="attacker"):
if kp in self.turn_defender.positions:
on_target_kill_positions.append(kp)
elif kp == self.turn_defender.enPassant_enablers['offset-kill-pos']:
on_target_enPassant_killPositions.append(kp)
# Threat on defender ------------------------
kill_positions.append(piece_standpoint)
self.turn_attacker.all_threat_emissions.update({f'pawn{piece_standpoint}': kill_positions})
return _legal_movements, on_target_kill_positions, double_movement, on_target_enPassant_killPositions
return _legal_movements, on_target_kill_positions, double_movement, on_target_enPassant_killPositions
def rook_objectives(
self,
piece_standpoint: int,
perspective: str,
fake_positions: dict[int, str] | None = None
) -> dict[int, pygame.Rect] | None:
'''Movimiento Torre:
+NORTE
+SUR
+ESTE
+OESTE
La torre mata como se mueve.
'''
# Visual feedback utils
_legal_movements: list[int] = [piece_standpoint] # standpoint is always first pos
on_target_kill_positions: list[int] = []
# Objectives
_threat_emission: list[int] = []
rook_directions = [NORTE,SUR,ESTE,OESTE]
if perspective == 'fake-attackerMov-toDef':
for direction in rook_directions:
for mult in range(1,8): # 1 to board_size
movement = piece_standpoint+direction*mult
if direction == ESTE or direction == OESTE:
if movement not in row_of_(piece_standpoint):
break
if 0 <= movement <= 63: # VALID SQUARE
# bloqueos aliados
if movement in self.turn_defender.positions:
break
elif movement in fake_positions:
if fake_positions[movement] == 'king':
return True
else: break # descartar kill-movements que NO sean al rey
return False
if perspective == 'fake-defenderMov-toAtt':
for direction in rook_directions:
for mult in range(1,8): # 1 to board_size
movement = piece_standpoint+direction*mult
if direction == ESTE or direction == OESTE:
if movement not in row_of_(piece_standpoint):
break
if 0 <= movement <= 63: # VALID SQUARE
# bloqueos aliados
if movement in self.turn_attacker.positions:
break
elif movement in fake_positions:
if fake_positions[movement] == 'king':
return True
else: break # descartar kill-movements que NO sean al rey
return False
if perspective == 'defender':
for direction in rook_directions:
for mult in range(1,8): # 1 to board_size
movement = piece_standpoint+direction*mult
if direction == ESTE or direction == OESTE:
if movement not in row_of_(piece_standpoint):
break
if 0 <= movement <= 63: # VALID SQUARE
if self.exposing_direction(piece_standpoint, intended_move=direction, request_from="defender"):
continue
if movement in self.turn_defender.positions:
break
if self.turn_attacker.direct_threatOrigin_type == 'single':
if movement in self.turn_attacker.single_directThreatOnEnemy_trace:
# block saving position
self.turn_defender.legal_moves.add(f'rook{piece_standpoint}')
elif movement == self.turn_attacker.single_threat_standpoint:
# kill saving position
self.turn_defender.legal_moves.add(f'rook{piece_standpoint}')
elif self.turn_attacker.direct_threatOrigin_type == 'none':
self.turn_defender.legal_moves.add(f'rook{piece_standpoint}')
return
if perspective == 'attacker':
if self.turn_defender.direct_threatOrigin_type == 'single':
for direction in rook_directions:
for mult in range(1,8): # 1 to board_size
movement = piece_standpoint+direction*mult
if direction == ESTE or direction == OESTE:
if movement not in row_of_(piece_standpoint):
break
if 0 <= movement <= 63: # VALID SQUARE
if self.exposing_direction(piece_standpoint, intended_move=direction, request_from='attacker'):
break
if movement not in self.turn_attacker.positions and movement not in self.turn_defender.positions:
if movement in self.turn_defender.single_directThreatOnEnemy_trace:
# BLOCK saving position.
_legal_movements.append(movement)
break
elif movement == self.turn_defender.single_threat_standpoint:
# KILL saving position.
on_target_kill_positions.append(movement)
break
else: break # ally block
return _legal_movements, on_target_kill_positions
elif self.turn_defender.direct_threatOrigin_type == 'none':
for direction in rook_directions:
for mult in range(1,8): # 1 to board_size
movement = piece_standpoint+direction*mult
if direction == ESTE or direction == OESTE:
if movement not in row_of_(piece_standpoint):
break
if 0 <= movement <= 63: # VALID SQUARE
if self.exposing_direction(piece_standpoint, intended_move=direction, request_from='attacker'):
break
if movement not in self.turn_attacker.positions and movement not in self.turn_defender.positions:
_threat_emission.append(movement)
_legal_movements.append(movement)
# Kill-movement
elif movement in self.turn_defender.positions:
_threat_emission.append(movement)
on_target_kill_positions.append(movement)
break
else: # ally block
_threat_emission.append(movement)
break
_threat_emission.append(piece_standpoint)
self.turn_attacker.all_threat_emissions.update({f'rook{piece_standpoint}': _threat_emission})
return _legal_movements, on_target_kill_positions
return _legal_movements, on_target_kill_positions
def knight_objectives(self, piece_standpoint: int, perspective: str) -> dict[int,pygame.Rect] | None:
'''Movimiento Caballo:
doble-norte + este
doble-norte + oeste
doble-sur + este
doble-sur + oeste
doble-este + norte
doble-este + sur
doble-oeste + norte
doble-oeste + sur
El caballo mata como se mueve.
Unica pieza la cual podemos descartar todos sus movimientos si uno solo -expone-.
'''
# Visual feedback utils
_legal_movements: list[int] = [piece_standpoint] # standpoint is always first pos
on_target_kill_positions: list[int] = []
# Objectives
_threat_emission: list[int] = []
knight_pre_movements = []
# ESTE / OESTE LIMITS
if piece_standpoint+ESTE in row_of_(piece_standpoint):
knight_pre_movements.extend([piece_standpoint+NORTE+NOR_ESTE,
piece_standpoint+SUR+SUR_ESTE])
if piece_standpoint+ESTE*2 in row_of_(piece_standpoint):
knight_pre_movements.extend([piece_standpoint+ESTE+NOR_ESTE,