shachar commited on
Commit
c86bbfa
·
1 Parent(s): 9433768

initial commit

Browse files
Files changed (3) hide show
  1. README.md +5 -2
  2. app.py +234 -0
  3. requirements.txt +4 -0
README.md CHANGED
@@ -1,13 +1,16 @@
1
  ---
2
  title: Practice Chess Openings
3
- emoji: 👀
4
  colorFrom: yellow
5
- colorTo: blue
6
  sdk: streamlit
7
  sdk_version: 1.38.0
8
  app_file: app.py
9
  pinned: false
10
  license: cc0-1.0
 
 
 
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
  title: Practice Chess Openings
3
+ emoji:
4
  colorFrom: yellow
5
+ colorTo: pink
6
  sdk: streamlit
7
  sdk_version: 1.38.0
8
  app_file: app.py
9
  pinned: false
10
  license: cc0-1.0
11
+ datasets:
12
+ - Lichess/chess-openings
13
+ suggested_hardware: cpu-basic
14
  ---
15
 
16
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import random
3
+
4
+ import chess
5
+ import chess.pgn
6
+ import chess.svg
7
+ import streamlit as st
8
+ from datasets import load_dataset
9
+
10
+ # Set page config and custom CSS
11
+ st.set_page_config(page_title="Chess Openings Trainer", page_icon="♖")
12
+
13
+
14
+ @st.cache_data
15
+ def load_data():
16
+ ds = load_dataset("Lichess/chess-openings", split="train")
17
+ df = ds.to_pandas()
18
+ return df
19
+
20
+
21
+ # Initialize session state variables
22
+ if "move_index" not in st.session_state:
23
+ st.session_state.move_index = 0
24
+ if "user_move" not in st.session_state:
25
+ st.session_state.user_move = ""
26
+ if "current_opening" not in st.session_state:
27
+ st.session_state.current_opening = None
28
+ if "board" not in st.session_state:
29
+ st.session_state.board = chess.Board()
30
+ if "moves" not in st.session_state:
31
+ st.session_state.moves = []
32
+
33
+ data = load_data()
34
+
35
+ if data.empty:
36
+ st.error("No data available. Failed to load from Hugging Face dataset.")
37
+ st.stop()
38
+
39
+ # App layout
40
+ st.title("Chess Openings Trainer")
41
+
42
+ with st.sidebar:
43
+ st.header("Settings")
44
+
45
+ # Move range slider
46
+ min_moves, max_moves = st.slider(
47
+ "Select range of moves:", min_value=1, max_value=18, value=(1, 18), step=1
48
+ )
49
+
50
+ # Hide next moves checkbox
51
+ hide_next_moves = st.checkbox("Hide next moves", value=True)
52
+
53
+ # Filter the data based on the min and max number of moves
54
+ filtered_data = data[
55
+ (data["pgn"].str.count(".") >= min_moves)
56
+ & (data["pgn"].str.count(".") <= max_moves)
57
+ ]
58
+
59
+ if filtered_data.empty:
60
+ st.error(
61
+ "No openings found with the specified number of moves. Please adjust your selection."
62
+ )
63
+ st.stop()
64
+
65
+ opening = st.selectbox(
66
+ label="Select an opening",
67
+ options=list(filtered_data["name"].unique()),
68
+ index=None,
69
+ key="opening_selector",
70
+ placeholder="Select an opening",
71
+ )
72
+
73
+ if opening and opening != st.session_state.current_opening:
74
+ st.session_state.current_opening = opening
75
+ st.session_state.move_index = 0
76
+ st.session_state.user_move = ""
77
+ st.session_state.board = chess.Board()
78
+
79
+ # Get PGN for the selected opening
80
+ selected_opening = filtered_data[filtered_data["name"] == opening].iloc[0]
81
+ pgn = selected_opening["pgn"]
82
+
83
+ # Parse PGN
84
+ game = chess.pgn.read_game(io.StringIO(pgn))
85
+ st.session_state.moves = list(game.mainline_moves())
86
+
87
+ with st.expander("Instructions"):
88
+ st.write("Entering moves:")
89
+ st.write("Use standard algebraic notation (SAN)")
90
+ st.write("Examples: e4, Nf3, O-O (castling), exd5 (pawn capture)")
91
+ st.write("Specify the piece (except for pawns) + destination square")
92
+ st.write("Use 'x' for captures, '+' for check, '#' for checkmate")
93
+
94
+ st.write("\nPiece symbols:")
95
+ col1, col2, col3 = st.columns(3)
96
+ with col1:
97
+ st.write("♔ King (K)")
98
+ st.write("♕ Queen (Q)")
99
+ with col2:
100
+ st.write("♖ Rook (R)")
101
+ st.write("♗ Bishop (B)")
102
+ with col3:
103
+ st.write("♘ Knight (N)")
104
+ st.write("♙ Pawn (no letter)")
105
+
106
+ st.write(
107
+ "See full notation [here](https://en.wikipedia.org/wiki/Algebraic_notation_(chess))"
108
+ )
109
+ st.write(
110
+ "This app is using the [Lichess](https://lichess.org/) openings dataset via [HuggingFace](https://huggingface.co/datasets/Lichess/chess-openings)"
111
+ )
112
+
113
+
114
+ def update_board():
115
+ st.session_state.board = chess.Board()
116
+ for move in st.session_state.moves[: st.session_state.move_index]:
117
+ st.session_state.board.push(move)
118
+
119
+
120
+ def update_next_move():
121
+ if st.session_state.move_index < len(st.session_state.moves):
122
+ st.session_state.move_index += 1
123
+ update_board()
124
+
125
+
126
+ def update_prev_move():
127
+ if st.session_state.move_index > 0:
128
+ st.session_state.move_index -= 1
129
+ update_board()
130
+
131
+
132
+ # Create two columns: one for the board and buttons, one for the move list
133
+ col1, col2 = st.columns([3, 1])
134
+
135
+ with col1:
136
+ if st.session_state.current_opening:
137
+ st.header(f":blue[{st.session_state.current_opening}]")
138
+
139
+ col_prev, col_next, right_col = st.columns([1, 1, 1])
140
+
141
+ with col_prev:
142
+ st.button(
143
+ "⬅️ Previous",
144
+ disabled=st.session_state.move_index == 0,
145
+ on_click=update_prev_move,
146
+ )
147
+ with col_next:
148
+ st.button(
149
+ "➡️ Next",
150
+ disabled=st.session_state.move_index >= len(st.session_state.moves),
151
+ on_click=update_next_move,
152
+ )
153
+
154
+ board_container = st.empty()
155
+ board_container.image(chess.svg.board(board=st.session_state.board, size=450))
156
+
157
+ # User input for next move
158
+ if hide_next_moves and st.session_state.move_index < len(
159
+ st.session_state.moves
160
+ ):
161
+ col_input, col_submit, col_right = st.columns([2, 1, 1])
162
+
163
+ def submit_move():
164
+ if st.session_state.user_move.strip() == "":
165
+ return
166
+ user_move = st.session_state.user_move
167
+ try:
168
+ user_chess_move = st.session_state.board.parse_san(user_move)
169
+ correct_move = st.session_state.moves[st.session_state.move_index]
170
+ if user_chess_move == correct_move:
171
+ st.session_state.success_message = (
172
+ "Correct move! Moving to the next one."
173
+ )
174
+ st.session_state.move_index += 1
175
+ st.session_state.user_move = ""
176
+ update_board()
177
+ else:
178
+ st.session_state.error_message = "Incorrect move. Try again!"
179
+ except ValueError as e:
180
+ error_message = str(e).lower()
181
+ if (
182
+ "invalid san" in error_message
183
+ or "unexpected" in error_message
184
+ or "unterminated" in error_message
185
+ ):
186
+ st.session_state.error_message = "Invalid move format. Please use standard SAN notation (e.g., e4 or Nf3)."
187
+ else:
188
+ st.session_state.error_message = "Invalid move. This move is not allowed in the current position."
189
+
190
+ with col_input:
191
+ user_move = st.text_input(
192
+ label="Enter your move",
193
+ placeholder="Enter your move (e.g., e4 or Nf3)",
194
+ key="user_move",
195
+ value=st.session_state.user_move,
196
+ label_visibility="hidden",
197
+ on_change=submit_move,
198
+ )
199
+ if "error_message" in st.session_state:
200
+ st.error(st.session_state.error_message)
201
+ del st.session_state.error_message
202
+ elif "success_message" in st.session_state:
203
+ st.success(st.session_state.success_message)
204
+ del st.session_state.success_message
205
+
206
+ with col_submit:
207
+ st.markdown("<br>", unsafe_allow_html=True)
208
+ submit_button = st.button(
209
+ "Submit",
210
+ on_click=submit_move,
211
+ )
212
+ else:
213
+ st.info("Please select an opening from the sidebar to begin.")
214
+
215
+ with col2:
216
+ if st.session_state.current_opening:
217
+ st.header("Moves", divider="green")
218
+ move_text = ""
219
+ current_node = chess.pgn.Game()
220
+ for i, move in enumerate(st.session_state.moves):
221
+ if i % 2 == 0:
222
+ move_number = i // 2 + 1
223
+ move_text += f"{move_number}. "
224
+ san_move = current_node.board().san(move)
225
+ if i < st.session_state.move_index:
226
+ move_text += f"**{san_move}** "
227
+ elif hide_next_moves:
228
+ move_text += "... "
229
+ else:
230
+ move_text += f"{san_move} "
231
+ if i % 2 == 1 or i == len(st.session_state.moves) - 1:
232
+ move_text += "\n"
233
+ current_node = current_node.add_variation(move)
234
+ st.markdown(move_text)
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ pandas>=2.2.3
2
+ python-chess>=1.2.0
3
+ datasets>=3.0.0
4
+