Skip to content

Commit 72f1638

Browse files
committed
[clean] pylint
1 parent 8c0e673 commit 72f1638

3 files changed

Lines changed: 98 additions & 185 deletions

File tree

opening_generator.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,13 @@ def __init__(self):
7575
self.puzzle_counter = 1
7676
self.category_mapping = {} # Store mapping from opening name to category
7777

78-
def add_opening_from_pgn(self, name: str, pgn_moves: str, rating: str = "",
79-
popularity: str = "", themes: str = "",
80-
category: str = "") -> None:
78+
def add_opening_from_pgn(self,
79+
name: str,
80+
pgn_moves: str,
81+
rating: str = "",
82+
popularity: str = "",
83+
themes: str = "",
84+
category: str = "") -> None:
8185
"""
8286
Add an opening from PGN notation and generate all moves.
8387
@@ -180,13 +184,13 @@ def _parse_pgn_to_moves(self, pgn_moves: str) -> List[str]:
180184

181185
return moves
182186

183-
except Exception as e:
187+
except (ValueError, EOFError) as e:
184188
print(f"PGN parsing error: {e}")
185189

186190
# Method 2: Fallback - manual parsing
187191
try:
188192
return self._manual_pgn_parse(pgn_moves)
189-
except Exception as e:
193+
except (ValueError, chess.IllegalMoveError) as e:
190194
print(f"Manual parsing error: {e}")
191195

192196
return []
@@ -335,7 +339,10 @@ def _opening_prefixed_tokens(text: str):
335339

336340
# Créer une instance de l'analyseur
337341
output_file='opening_report.txt'
338-
analyzer = opening_report.ChessOpeningAnalyzer(self.moves,self.variants, output_file=output_file)
342+
analyzer = opening_report.ChessOpeningAnalyzer(
343+
self.moves,
344+
self.variants,
345+
output_file=output_file)
339346

340347
# Générer le rapport
341348
report = analyzer.generate_report(report_format='console', include_visuals=True)

opening_report.py

Lines changed: 83 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -38,158 +38,7 @@
3838
import chess
3939
import chess.svg
4040
import numpy as np
41-
42-
class AsciiTable:
43-
"""Helper class for creating perfectly aligned ASCII tables with dynamic column widths"""
44-
45-
@staticmethod
46-
def create(data, headers, alignments=None, padding=1, border_style="none", title=None):
47-
"""
48-
Create a perfectly aligned ASCII table with dynamic column widths
49-
50-
Args:
51-
data: List of rows (each row is a list of cells)
52-
headers: List of header names
53-
alignments: List of alignments ('<', '>', '^') for each column
54-
padding: Number of spaces between columns
55-
border_style: "none", "minimal", "full"
56-
title: Optional table title
57-
58-
Returns:
59-
str: Formatted table
60-
"""
61-
# Déterminer la largeur maximale de chaque colonne (incluant les en-têtes)
62-
all_rows = [headers] + data
63-
col_widths = [
64-
max(len(str(row[i])) for row in all_rows if i < len(row))
65-
66-
for i in range(max(len(row) for row in all_rows))
67-
]
68-
69-
# Appliquer le padding
70-
col_widths = [w + padding*2 for w in col_widths]
71-
72-
# Définir les alignements par défaut (gauche pour le texte, droite pour les nombres)
73-
74-
if not alignments:
75-
alignments = ['<' if i == 0 else '>' for i in range(len(col_widths))]
76-
77-
# Créer les lignes du tableau
78-
lines = []
79-
80-
# Ajouter un titre si spécifié
81-
82-
if title:
83-
lines.append(f"{title}")
84-
lines.append("─" * sum(col_widths + [len(col_widths) - 1]))
85-
86-
# Ligne de séparation supérieure si nécessaire
87-
88-
if border_style == "full":
89-
top_border = '+' + '+'.join(['─' * w for w in col_widths]) + '+'
90-
lines.append(top_border)
91-
92-
# En-têtes
93-
header_line = ''
94-
95-
if border_style in ["minimal", "full"]:
96-
header_line += '|'
97-
98-
for i, header in enumerate(headers):
99-
fmt = f"{{:{alignments[i]}{col_widths[i]}}}"
100-
header_line += fmt.format(header)
101-
102-
if border_style in ["minimal", "full"] or i < len(headers) - 1:
103-
header_line += '|'
104-
lines.append(header_line)
105-
106-
# Ligne de séparation après les en-têtes si nécessaire
107-
108-
if border_style == "full":
109-
separator = '+' + '+'.join(['─' * w for w in col_widths]) + '+'
110-
lines.append(separator)
111-
elif border_style == "minimal":
112-
separator = ':' + ':'.join(['-' * w for w in col_widths]) + ':'
113-
lines.append(separator)
114-
115-
# Données
116-
117-
for row in data:
118-
row_line = ''
119-
120-
if border_style in ["minimal", "full"]:
121-
row_line += '|'
122-
123-
for i, cell in enumerate(row):
124-
if i >= len(col_widths):
125-
continue
126-
fmt = f"{{:{alignments[i]}{col_widths[i]}}}"
127-
row_line += fmt.format(str(cell))
128-
129-
if border_style in ["minimal", "full"] or i < len(row) - 1:
130-
row_line += '|'
131-
lines.append(row_line)
132-
133-
# Ligne de fermeture si nécessaire
134-
135-
if border_style == "full":
136-
bottom_border = '+' + '+'.join(['─' * w for w in col_widths]) + '+'
137-
lines.append(bottom_border)
138-
139-
return "\n".join(lines)
140-
141-
@staticmethod
142-
def create_meter(value, width=20, symbol_filled='█', symbol_empty='░', show_value=True):
143-
"""
144-
Create a visual progress meter
145-
146-
Args:
147-
value: Value between 0 and 1
148-
width: Total width of the meter
149-
symbol_filled: Symbol for filled portion
150-
symbol_empty: Symbol for empty portion
151-
show_value: Whether to show the percentage value
152-
153-
Returns:
154-
str: Formatted meter
155-
"""
156-
level = int(value * width)
157-
meter = symbol_filled * level + symbol_empty * (width - level)
158-
159-
if show_value:
160-
return f"[{meter}] {value*100:.0f}%"
161-
162-
return f"[{meter}]"
163-
164-
@staticmethod
165-
def create_balance_meter(white, black, width=20):
166-
"""
167-
Create a visual balance meter for white/black distribution
168-
169-
Args:
170-
white: Count of white positions
171-
black: Count of black positions
172-
width: Total width of the meter
173-
174-
Returns:
175-
str: Formatted balance meter
176-
"""
177-
total = white + black
178-
179-
if total == 0:
180-
return "[ ] 0.0%"
181-
182-
white_pct = white / total * 100
183-
184-
white_width = int(white_pct * width / 100)
185-
black_width = width - white_width
186-
187-
meter = "♔" * white_width + "♚" * black_width
188-
balance = 1.0 - abs(0.5 - white/total) * 2
189-
status = "(Perfect)" if balance > 0.9 else ""
190-
191-
return f"[{meter}] {balance*100:.1f}% {status}"
192-
41+
from ascii_table import AsciiTable
19342

19443
class ChessOpeningAnalyzer:
19544
"""Professional chess opening deck analyzer with advanced visualization"""
@@ -258,10 +107,25 @@ def _analyze_data(self):
258107
'critical_positions': []
259108
},
260109
'color_breakdown': {
261-
'white': {'mainlines': 0, 'variants': 0, 'families': Counter(), 'moves': []},
262-
'black': {'mainlines': 0, 'variants': 0, 'families': Counter(), 'moves': []}
110+
'white': {
111+
'mainlines': 0,
112+
'variants': 0,
113+
'families': Counter(),
114+
'moves': [],
115+
},
116+
'black': {
117+
'mainlines': 0,
118+
'variants': 0,
119+
'families': Counter(),
120+
'moves': [],
121+
}
263122
},
264-
'family_breakdown': defaultdict(lambda: {'white': 0, 'black': 0, 'mainlines': 0, 'variants': 0}),
123+
'family_breakdown': defaultdict(lambda: {
124+
'white': 0,
125+
'black': 0,
126+
'mainlines': 0,
127+
'variants': 0,
128+
}),
265129
'move_patterns': {
266130
'first_moves': Counter(),
267131
'common_sequences': Counter(),
@@ -346,7 +210,8 @@ def _determine_color(self, move, puzzle_id, themes, color_lookup):
346210
board = chess.Board(fen)
347211

348212
return 'white' if board.turn == chess.WHITE else 'black'
349-
except:
213+
except (AttributeError, ValueError) as e:
214+
print(f"Error determining color: {e}")
350215
pass
351216

352217
# 4. Last resort: position in deck
@@ -394,7 +259,8 @@ def _determine_family(self, move, puzzle_id, themes, category, category_lookup):
394259

395260
if first_move == 'e2e4' and 'c7c5' in [m.uci() for m in board.legal_moves]:
396261
return 'sicilian'
397-
except:
262+
except (AttributeError, ValueError) as e:
263+
print(f"Error analyzing FEN: {e}")
398264
pass
399265

400266
return 'unknown'
@@ -409,7 +275,9 @@ def _analyze_depth(self, move):
409275
move_number = board.fullmove_number
410276

411277
return (move_number - 1) * 2 + (1 if board.turn == chess.WHITE else 0)
412-
except:
278+
except (AttributeError, ValueError) as e:
279+
print(f"Error analyzing depth: {e}")
280+
413281
return 0
414282

415283
return 0
@@ -553,10 +421,18 @@ def _generate_console_report(self, include_visuals):
553421
f" • Total positions: {total}",
554422
f" • Color distribution: White {white} ({white/total*100:.1f}%) | Black {black} ({black/total*100:.1f}%)",
555423
"",
556-
" • Balance level: " + AsciiTable.create_meter(stats['quality_metrics']['balance'], 20),
557-
" • Theoretical completeness: " + AsciiTable.create_meter(stats['quality_metrics']['completeness'], 20),
558-
" • Opening diversity: " + AsciiTable.create_meter(stats['quality_metrics']['diversity'], 20),
559-
" • Theoretical soundness: " + AsciiTable.create_meter(stats['quality_metrics']['theoretical_soundness'], 20),
424+
" • Balance level: " + AsciiTable.create_meter(
425+
stats['quality_metrics']['balance'],
426+
20),
427+
" • Theoretical completeness: " + AsciiTable.create_meter(
428+
stats['quality_metrics']['completeness'],
429+
20),
430+
" • Opening diversity: " + AsciiTable.create_meter(
431+
stats['quality_metrics']['diversity'],
432+
20),
433+
" • Theoretical soundness: " + AsciiTable.create_meter(
434+
stats['quality_metrics']['theoretical_soundness'],
435+
20),
560436
""
561437
])
562438

@@ -796,7 +672,9 @@ def _generate_key_positions_preview(self):
796672
if fen:
797673
board = chess.Board(fen)
798674
key_positions.append((board, move))
799-
except:
675+
except (AttributeError, ValueError) as e:
676+
print(f"Error processing move: {e}")
677+
800678
continue
801679

802680
if not key_positions:
@@ -853,58 +731,86 @@ def _generate_personalized_recommendations(self):
853731
black = stats['metadata']['color_balance']['black']
854732

855733
if white > black * 1.5:
856-
recommendations.append("♔ Strengthen your black defenses - you're too focused on white openings")
734+
recommendations.append(
735+
"♔ Strengthen your black defenses - you're too focused on white openings",
736+
)
857737
elif black > white * 1.5:
858-
recommendations.append("♚ Strengthen your white openings - you lack practice as white")
738+
recommendations.append(
739+
"♚ Strengthen your white openings - you lack practice as white",
740+
)
859741

860742
# Mainline-based recommendations
861743
mainlines = (stats['color_breakdown']['white']['mainlines'] +
862744
stats['color_breakdown']['black']['mainlines'])
863745

864746
if mainlines / total < 0.3:
865-
recommendations.append("📚 Add more mainlines - your deck lacks theoretical foundations")
747+
recommendations.append(
748+
"📚 Add more mainlines - your deck lacks theoretical foundations",
749+
)
866750
elif mainlines / total > 0.7:
867-
recommendations.append("💡 Add more variants - your deck is too theoretical without practical alternatives")
751+
recommendations.append(
752+
"💡 Add more variants - your deck is too theoretical without practical alternatives",
753+
)
868754

869755
# Diversity-based recommendations
870756

871757
if len(stats['metadata']['family_coverage']) < 5:
872-
recommendations.append("🌍 Broaden your repertoire - you're focusing too much on few opening families")
758+
recommendations.append(
759+
"🌍 Broaden your repertoire - you're focusing too much on few opening families",
760+
)
873761
elif len(stats['metadata']['family_coverage']) > 12:
874-
recommendations.append("🎯 Good diversity! Now focus on mastering key families")
762+
recommendations.append(
763+
"🎯 Good diversity! Now focus on mastering key families",
764+
)
875765

876766
# Depth-based recommendations
877767
avg_depth = sum(k*v for k,v in stats['metadata']['depth_distribution'].items()) / total
878768

879769
if avg_depth < 3:
880-
recommendations.append("♟ Develop deeper positions - your deck remains too superficial")
770+
recommendations.append(
771+
"♟ Develop deeper positions - your deck remains too superficial",
772+
)
881773
elif avg_depth > 10:
882-
recommendations.append("🧠 Excellent! Your deck covers well developments beyond the first few moves")
774+
recommendations.append(
775+
"🧠 Excellent! Your deck covers well developments beyond the first few moves",
776+
)
883777

884778
# Personalized recommendation based on first moves
885779

886780
if stats['move_patterns']['first_moves']:
887781
top_move = stats['move_patterns']['first_moves'].most_common(1)[0][0]
888782

889783
if top_move in ['e4', 'd4', 'Nf3', 'c4']:
890-
recommendations.append(f"🚀 You master {top_move} well - now explore less common responses")
784+
recommendations.append(
785+
f"🚀 You master {top_move} well - now explore less common responses",
786+
)
891787
else:
892-
recommendations.append(f"🔍 {top_move} is a good start - ensure you know main responses")
788+
recommendations.append(
789+
f"🔍 {top_move} is a good start - ensure you know main responses",
790+
)
893791

894792
# If no specific recommendations
895793

896794
if not recommendations:
897-
recommendations.append("✅ Excellent deck! Keep practicing regularly")
795+
recommendations.append(
796+
"✅ Excellent deck! Keep practicing regularly",
797+
)
898798

899799
# Add progression recommendation
900800
completeness = stats['quality_metrics']['completeness']
901801

902802
if completeness < 0.3:
903-
recommendations.append("🎯 Short-term goal: Reach 5 main opening families")
803+
recommendations.append(
804+
"🎯 Short-term goal: Reach 5 main opening families",
805+
)
904806
elif completeness < 0.6:
905-
recommendations.append("🏆 Intermediate goal: Master 8 key opening families")
807+
recommendations.append(
808+
"🏆 Intermediate goal: Master 8 key opening families",
809+
)
906810
else:
907-
recommendations.append("🏅 Advanced goal: Explore specialized variants in your favorite families")
811+
recommendations.append(
812+
"🏅 Advanced goal: Explore specialized variants in your favorite families",
813+
)
908814

909815
# Format recommendations with priorities
910816
formatted = []

0 commit comments

Comments
 (0)