1
1
import csv
2
2
import re
3
3
from pathlib import Path
4
+ from typing import NamedTuple
4
5
5
6
import chess
6
7
from django .core .management import BaseCommand
@@ -55,6 +56,7 @@ def handle(
55
56
min_popularity : int ,
56
57
rating_min_max : tuple [int , int ],
57
58
stop_after : int | None ,
59
+ verbosity : int ,
58
60
** options ,
59
61
):
60
62
rating_min , rating_max = rating_min_max
@@ -63,48 +65,56 @@ def handle(
63
65
Substr ("source" , 9 ), flat = True
64
66
)
65
67
)
66
- print (f"already_imported_ids: { already_imported_ids } " )
68
+ if verbosity >= 2 :
69
+ print (f"already_imported_ids: { already_imported_ids } " )
67
70
68
71
created_count = 0
69
72
current_batch : list [DailyChallenge ] = []
70
73
with csv_file_path .open (newline = "" ) as csv_file :
71
74
reader = csv .DictReader (csv_file )
72
- for row in reader :
75
+ for row in reader : # type: dict[str,str]
73
76
themes : set [str ] = set (row .get ("Themes" , "" ).split ())
74
77
if themes & THEMES_TO_IGNORE :
75
- self .stdout .write ("Skipping too short puzzle" )
78
+ if verbosity >= 2 :
79
+ self .stdout .write ("Skipping puzzle with ignored theme" )
76
80
continue
77
81
if (popularity := int (row ["Popularity" ])) < min_popularity :
78
- self .stdout .write (f"Skipping puzzle with Popularity { popularity } " )
82
+ if verbosity >= 2 :
83
+ self .stdout .write (
84
+ f"Skipping puzzle with Popularity { popularity } "
85
+ )
79
86
continue
80
87
if (rating := int (row ["Rating" ])) < rating_min or rating > rating_max :
81
- self .stdout .write (f"Skipping puzzle with Rating { rating } " )
88
+ if verbosity >= 2 :
89
+ self .stdout .write (f"Skipping puzzle with Rating { rating } " )
82
90
continue
83
91
84
92
puzzle_id = row ["PuzzleId" ]
85
93
if puzzle_id in already_imported_ids :
86
- self .stdout .write (f"Skipping already imported puzzle '{ puzzle_id } '" )
94
+ if verbosity >= 2 :
95
+ self .stdout .write (
96
+ f"Skipping already imported puzzle '{ puzzle_id } '"
97
+ )
87
98
continue
88
- fen = row ["FEN" ]
89
99
90
- self .stdout .write (
91
- f"Creating DailyChallenge for puzzle '{ puzzle_id } ' with Popularity { popularity } , rating { rating } , FEN '{ fen } '"
92
- )
93
-
94
- if " b " in fen : # quick and dirty way to detect if black is to move
95
- self .stdout .write ("Mirroring puzzle with black to move" )
96
- fen = chess .Board (fen ).mirror ().fen ()
100
+ bot_first_move , fen = get_bot_first_move_and_resulting_fen (row )
101
+ if verbosity >= 2 :
102
+ self .stdout .write (
103
+ f"Creating DailyChallenge for puzzle '{ puzzle_id } ' with Popularity { popularity } , rating { rating } ."
104
+ )
97
105
98
106
current_batch .append (
99
107
DailyChallenge (
100
108
source = f"lichess-{ puzzle_id } " ,
101
109
fen = fen ,
110
+ bot_first_move = bot_first_move ,
102
111
)
103
112
)
104
113
105
114
if len (current_batch ) == batch_size :
106
115
DailyChallenge .objects .bulk_create (current_batch )
107
- self .stdout .write (f"Created { batch_size } puzzles." )
116
+ if verbosity >= 2 :
117
+ self .stdout .write (f"Created batch of { batch_size } puzzles." )
108
118
current_batch = []
109
119
110
120
created_count += 1
@@ -114,7 +124,55 @@ def handle(
114
124
115
125
if current_batch :
116
126
DailyChallenge .objects .bulk_create (current_batch )
117
- self .stdout .write (f"Created { len (current_batch )} puzzles." )
127
+ if verbosity >= 2 :
128
+ self .stdout .write (f"Created batch of { batch_size } puzzles." )
129
+
130
+ self .stdout .write (
131
+ f"Imported { self .style .SUCCESS (created_count )} Lichess puzzles."
132
+ )
133
+
134
+
135
+ def get_bot_first_move_and_resulting_fen (
136
+ csv_row : dict ,
137
+ ) -> "BotFirstMoveAndResultingFen" :
138
+ fen_before_bot_first_move = csv_row ["FEN" ]
139
+ bot_first_move_uci = csv_row ["Moves" ][0 :4 ]
140
+
141
+ # The Lichess puzzles FEN is always the one *before* the bot's move,
142
+ # so we're going to have to adapt this to our own models.
143
+ board = chess .Board (fen_before_bot_first_move )
144
+
145
+ # Quick and dirty way to detect if white is to move from the FEN:
146
+ have_to_mirror_board = " w " in fen_before_bot_first_move
147
+
148
+ if have_to_mirror_board :
149
+ # If this is the white to play, we're playing black.
150
+ # (the FEN is always the one before the bot's move)
151
+ # As we always play white in the Zakuchess daily challenge, for simplicity,
152
+ # we have to mirror the board when that's the case.
153
+ board .apply_mirror ()
154
+
155
+ # Ok, let's calculate the FEN after the bot's move:
156
+ if have_to_mirror_board :
157
+ # If we mirrored the board, we have to mirror the move too:
158
+ bot_first_move = chess .Move .from_uci (bot_first_move_uci )
159
+ bot_first_move_uci = "" .join (
160
+ chess .square_name (chess .square_mirror (sq ))
161
+ for sq in (bot_first_move .from_square , bot_first_move .to_square )
162
+ )
163
+
164
+ board .push (chess .Move .from_uci (bot_first_move_uci ))
165
+ starting_fen_after_bot_first_move = board .fen ()
166
+
167
+ return BotFirstMoveAndResultingFen (
168
+ first_move = bot_first_move_uci ,
169
+ fen = starting_fen_after_bot_first_move ,
170
+ )
171
+
172
+
173
+ class BotFirstMoveAndResultingFen (NamedTuple ):
174
+ first_move : str
175
+ fen : str
118
176
119
177
120
178
def existing_path (value : str ) -> Path :
0 commit comments