Monday, July 6, 2026

Before dividing into multiple .py files and with color scheme mostly done

import customtkinter as ctk import tkinter as tk from tkinter import ttk, filedialog, messagebox from pathlib import Path import chess import chess.pgn import json import os # ---------------------------- # Main Window # ---------------------------- ctk.set_appearance_mode("dark") ctk.set_default_color_theme("blue") app = ctk.CTk() style = ttk.Style() style.theme_use("alt") print(style.theme_names()) reopen_btn = ctk.CTkLabel( app, text="🮟", font=("Arial", 16, "bold"), text_color = ("#515985") ) reopen_btn.place(x=2, rely=1.0, y=0, anchor="sw") app.configure(fg_color="#172134") app.title("🩒 Chess Catalog 🩒") app.geometry("800x700") #change default width later to be longer # ---------------------------- # Configure Grid # ---------------------------- app.grid_rowconfigure(1, weight=1) app.grid_columnconfigure(1, weight=1) # ---------------------------- # PGN Collection # ---------------------------- imported_files = [] cataloged_files = [] sidebar_visible = True # ---------------------------- # Import PGN # ---------------------------- def show_import_workspace(filename): global add_catalog_button with open(filename, encoding="utf-8") as pgn_file: game = chess.pgn.read_game(pgn_file) if game is None: return game_data_tags = ["Event", "Site", "Date", "Round", "White", "Black", "FEN", "Result"] other_data_tags = [] headers = game.headers # Clear the workspace for widget in workspace.winfo_children(): widget.destroy() # Title title = ctk.CTkLabel( workspace, text=f"{Path(filename).name}", font=("Arial", 24, "bold") ) title.pack( anchor="w", padx=20, pady=(15, 20) ) # Button frame button_frame = ctk.CTkFrame(workspace, fg_color="transparent") button_frame.pack(anchor="w", padx=20, pady=5) # Import, Add to Catalog, Catalog buttons add_catalog_button = ctk.CTkButton( button_frame, text="Add to Catalog", command=catalog_pgns ) add_catalog_button.pack(side="left", padx=(10, 0)) ctk.CTkButton( button_frame, text="Import", command=import_pgn ).pack(side="left", padx=(10, 0)) ctk.CTkButton( button_frame, text="Catalog", command=show_catalog_workspace ).pack(side="left", padx=(10, 0)) # Main container options_frame = ctk.CTkFrame(workspace, fg_color="#283858" )#game_data_panel options_frame.pack(fill="x", padx=20, pady=10) tags_list = list(headers.keys()) # Three columns game_data_frame = ctk.CTkFrame(options_frame, #fg_color="#34426e" fg_color="#00ff00" fg_color="#283858") #column color over game_data_panel_background other_data_frame = ctk.CTkFrame(options_frame, fg_color="#283858") #same depth_rating = ctk.CTkFrame(options_frame, fg_color="#283858") #same # Pack the columns for col in (game_data_frame, other_data_frame, depth_rating): col.pack(side="left", anchor="n", padx=(10, 30), pady=10) # ---------- Column 1 ---------- ctk.CTkLabel( game_data_frame, text="Game Data", font=("Arial", 16, "bold") ).pack(anchor="w", pady=(0, 10)) game_data_vars = {} for tag in game_data_tags: game_data_vars[tag] = tk.BooleanVar(value=False) ctk.CTkCheckBox( game_data_frame, text=tag, variable=game_data_vars[tag] ).pack(anchor="w", pady=4) # ---------- Column 2 ---------- ctk.CTkLabel( other_data_frame, text="", font=("Arial", 16, "bold") ).pack(anchor="w", pady=(0, 10)) other_data_tags = [ "WhiteTitle", "BlackTitle", "WhiteElo", "BlackElo", "WhiteFideId", "BlackFideId", "EventDate", "EventType", ] other_data_vars = {} for tag in other_data_tags: other_data_vars[tag] = tk.BooleanVar(value=False) ctk.CTkCheckBox( other_data_frame, text=tag, variable=other_data_vars[tag] ).pack(anchor="w", pady=4) # ---------- Column 3 ---------- ctk.CTkLabel( depth_rating, text="Depth Rating" ).pack(anchor="w", pady=(35, 0)) depth_menu = ctk.CTkOptionMenu( depth_rating, values=[ "15 Moves", "20 Moves", "25 Moves", "30 Moves", "All Moves" ] ) depth_menu.pack(anchor="w") depth_menu.set("15 Moves") ctk.CTkLabel( depth_rating, text="Rating Filter" ).pack(anchor="w", pady=(20, 0)) rating_filter = ctk.CTkOptionMenu( depth_rating, values=[ "2100+", "2400+", "2600+", "All" ] ) rating_filter.pack(anchor="w") rating_filter.set("2100+") def import_pgn(): filename = filedialog.askopenfilename( title="Import PGN", filetypes=[("PGN Files", "*.pgn")] ) if not filename: return show_import_workspace(filename) # Don't allow the same file twice if filename in imported_files: status.configure( text=f"{Path(filename).name} is already imported." ) return imported_files.append(filename) short_name = Path(filename).name sidebar.insert( pgn_node, "end", text=short_name ) status.configure( text=f"Imported: {short_name}" ) # ---------------------------- # Menu Bar # ---------------------------- menu_bar = tk.Menu(app) app.config(menu=menu_bar) def catalog_pgns(): global add_catalog_button if not imported_files: status.configure( text="No PGNs imported to catalog." ) return add_catalog_button.configure(text="Please Wait...") workspace.update() new_files = [] total_games = 0 if os.path.exists("personal_catalog.json"): with open("personal_catalog.json", "r", encoding="utf-8") as f: catalog = json.load(f) cataloged_files = catalog.get("cataloged_files", []) else: catalog = { "cataloged_files": [], "A": {}, "B": {}, "C": {}, "D": {}, "E": {} } cataloged_files = [] for filename in imported_files: if filename in cataloged_files: continue with open(filename, encoding="utf-8") as pgn: while True: game = chess.pgn.read_game(pgn) if game is None: break total_games += 1 eco = game.headers.get("ECO", "").strip() opening = game.headers.get("Opening", "").strip() variation = game.headers.get("Variation", "").strip() if eco and opening: group = eco[0] if eco not in catalog[group]: catalog[group][eco] = { "opening": opening, "variation": variation, "frequency": 0 } catalog[group][eco]["frequency"] += 1 cataloged_files.append(filename) new_files.append(filename) if not new_files: add_catalog_button.configure(text="Add to Catalog") status.configure( text="PGNs already cataloged. Import new games to catalog more." ) return print(catalog) catalog["cataloged_files"] = cataloged_files with open("personal_catalog.json", "w", encoding="utf-8") as f: json.dump(catalog, f, indent=4, sort_keys=True) print("JSON written.") status.configure( text=f"Cataloged {total_games} game(s) from {len(new_files)} PGN(s)." ) status.configure( text=f"Cataloged {total_games} game(s) from {len(new_files)} PGN(s)." ) add_catalog_button.configure(text="Add to Catalog") def clear_catalog(): answer = messagebox.askyesno( "Clear Catalog", "Delete the entire opening catalog?" ) if not answer: return if os.path.exists("personal_catalog.json"): os.remove("personal_catalog.json") cataloged_files.clear() status.configure( text="Catalog cleared." ) show_catalog_workspace() def show_catalog_workspace(): # Clear the workspace for widget in workspace.winfo_children(): widget.destroy() # Title title = ctk.CTkLabel( workspace, text="Opening Catalog", font=("Arial", 24, "bold") ) title.pack(anchor="w", padx=20, pady=(15, 10)) # ----- Table Frame ----- table_frame = ctk.CTkFrame(workspace) table_frame.pack(fill="both", anchor="w", expand=True, padx=(0, 0), pady=(0, 0)) columns = ( "Select", "Games", "ECO", "Opening", "Variation", ) catalog_table = ttk.Treeview( table_frame, columns=columns, show="headings" ) # Headings catalog_table.heading("Select", anchor="center", text=" ☐") catalog_table.heading("Games", anchor="center", text=" Games") catalog_table.heading("ECO", anchor="w", text=" ECO") catalog_table.heading("Opening", anchor="w", text=" Opening") catalog_table.heading("Variation", anchor="w", text=" Variation") # Column widths catalog_table.column("Select", width=45, anchor="center", stretch=False) catalog_table.column("Games", width=70, anchor="center", stretch=False) catalog_table.column("ECO", width=50, anchor="w", stretch=False) catalog_table.column("Opening", width=150, anchor ="w", stretch=False) catalog_table.column("Variation", width=200, anchor="w", stretch=True) # Scrollbar scrollbar = ttk.Scrollbar( table_frame, orient="vertical", command=catalog_table.yview ) catalog_table.configure(yscrollcommand=scrollbar.set) catalog_table.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # ------------------------- # Load catalog from JSON # ------------------------- if not os.path.exists("personal_catalog.json"): return with open("personal_catalog.json", "r", encoding="utf-8") as f: catalog = json.load(f) rows = [] total_games = 0 for group in catalog: if group == "cataloged_files": continue for eco, entry in catalog[group].items(): opening = entry.get("opening", "") variation = entry.get("variation", "") games = entry.get("frequency", 0) total_games += games rows.append(( games, eco, opening, variation )) # Highest frequency first rows.sort(key=lambda row: row[0], reverse=True) # Insert into table for games, eco, opening, variation in rows: catalog_table.insert( "", "end", values=( "☐", games, eco, opening, variation ) ) # Total games in catalog catalog_table.heading( "Select", text=str(total_games) ) reopen_btn.bind("", lambda event: toggle_sidebar()) def toggle_sidebar(): global sidebar_visible if sidebar_visible: left_frame.grid_remove() sidebar_footer.grid_remove() reopen_btn.configure(text="🮝") else: left_frame.grid() sidebar_footer.grid() reopen_btn.configure(text="🮟") reopen_btn.place(x=2, rely=1.0, y=0, anchor="sw") reopen_btn.lift() sidebar_visible = not sidebar_visible # ---------------------------- # File Menu # ---------------------------- file_menu = tk.Menu(menu_bar, tearoff=0) menu_bar.add_cascade( label="File", menu=file_menu ) file_menu.add_command( label="Import PGN...", command=import_pgn ) file_menu.add_command(label="Export PGN") file_menu.add_separator() file_menu.add_command( label="Clear Catalog", command=clear_catalog ) file_menu.add_separator() file_menu.add_command( label="Exit", command=app.quit ) # ---------------------------- # Edit Menu # ---------------------------- edit_menu = tk.Menu(menu_bar, tearoff=0) menu_bar.add_cascade( label="Edit", menu=edit_menu ) # ---------------------------- # View Menu # ---------------------------- view_menu = tk.Menu(menu_bar, tearoff=0) menu_bar.add_cascade( label="View", menu=view_menu ) view_menu.add_command( label="Show / Hide Sidebar", command=toggle_sidebar ) # ---------------------------- # Tools Menu # ---------------------------- tools_menu = tk.Menu(menu_bar, tearoff=0) menu_bar.add_cascade( label="Tools", menu=tools_menu ) tools_menu.add_command( label="Catalog", command=show_catalog_workspace ) tools_menu.add_command(label="Sort") tools_menu.add_command(label="Analyze") # ---------------------------- # Help Menu # ---------------------------- help_menu = tk.Menu(menu_bar, tearoff=0) menu_bar.add_cascade( label="Help", menu=help_menu ) # ---------------------------- # Left Navigation # ---------------------------- left_frame = ctk.CTkFrame( app, width=250, fg_color="#283858" #sidebar right and bottom padding color ) left_frame.grid( row=1, column=0, sticky="ns" ) left_frame.grid_propagate(False) # ---------------------------- # Tree Style # ---------------------------- style = ttk.Style() style.theme_use("default") # ---- Treeview body (for menu bar)---- style.configure( "Treeview", background="#172134", fieldbackground="#172134", #fix - Not field background, it is the background for the whole main body foreground="#FFFFFF", relief="flat", borderwidth=0, rowheight=24 ) # ---- Treeview headers (for the catalog) ---- style.configure( "Treeview.Heading", relief="solid", borderwidth=0, padding="2", background="#1f6aa5", foreground="#eeeeee" ) style.map( "Treeview", background=[("selected", "#c5c5c5")], foreground=[("selected", "#172134")] ) # ---- Disable ALL header interaction visuals ---- style.map( "Treeview.Heading", background=[("active", "#1f6aa5"), ("pressed", "#1f6aa5")], foreground=[("active", "#eeeeee"), ("pressed", "#eeeeee")] ) sidebar = ttk.Treeview( left_frame, style="Treeview", show="tree" ) sidebar.pack( fill="both", expand=True, padx=(0, 4), pady=(0, 4) ) sidebar_footer = ctk.CTkFrame( app, height=28, fg_color="#172134" #where the collapse area is in the footer ) sidebar_footer.grid( row=2, column=0, sticky="ew" ) # Root Items pgn_node = sidebar.insert( "", "end", text="PGN Collection", open=True ) catalog_node = sidebar.insert( "", "end", text="Catalog", open=True ) notes_node = sidebar.insert( "", "end", text="Notes", open=True ) def save_notes(): global notes_box print("Saving notes...") try: with open("notes.txt", "w") as f: f.write(notes_box.get("1.0", "end-1c")) except Exception as e: print(e) def on_tree_select(event): global notes_box item = sidebar.focus() text = sidebar.item(item, "text") if text != "Notes" and "notes_box" in globals(): save_notes() # Clear the workspace for widget in workspace.winfo_children(): widget.destroy() if text == "Notes": notes_box = ctk.CTkTextbox(workspace) notes_box.pack(fill="both", expand=True, padx=10, pady=10) try: with open("notes.txt", "r") as f: notes_box.insert("1.0", f.read()) except FileNotFoundError: pass elif text == "PGN Collection": label = ctk.CTkLabel( workspace, text="PGN Collection", font=("Arial", 24) ) label.pack(expand=True) elif text == "Catalog": show_catalog_workspace() sidebar.bind("<>", on_tree_select) # ---------------------------- # Workspace # ---------------------------- workspace = ctk.CTkFrame( app, fg_color="#172134" #main_workspace_background ) workspace.grid( row=1, column=1, sticky="nsew", padx=1, pady=1 ) label = ctk.CTkLabel( workspace, text="Workspace", font=("Arial", 24) ) label.pack( expand=True ) # ---------------------------- # Status Bar # ---------------------------- status = ctk.CTkLabel( app, text="Import PGN files ", anchor="e", height=26 ) status.grid( row=2, column=1, sticky="ew", padx=10 ) reopen_btn.lift() app.mainloop()

Monday, June 29, 2026

Importing .pgn files using if, elif, else

class="separator" style="clear: both;"href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaWfoxT25FzareJyLo-Chn9LiSWQMo_ZdZzxcZkU2GjDwoTc4RemK1nuwuPQ85RjMh_DrLfAmcNz5slYuMT1myDwgqVSBaZy_9XCQfVFsjz-9v4Nr681Y_O1K-OwMcJJUyZurY56fAeN2K6W23lfI0MHuiP8If2P8hue5-p83-Td31CJniRVAnDj0Neyy2/s805/Screenshot_2026-06-30_00-15-15ifelifelse.png" style="display: block; padding: 1em 0; text-align: center; "alt="" border="0" height="320" data-original-height="805" data-original-width="621" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjaWfoxT25FzareJyLo-Chn9LiSWQMo_ZdZzxcZkU2GjDwoTc4RemK1nuwuPQ85RjMh_DrLfAmcNz5slYuMT1myDwgqVSBaZy_9XCQfVFsjz-9v4Nr681Y_O1K-OwMcJJUyZurY56fAeN2K6W23lfI0MHuiP8If2P8hue5-p83-Td31CJniRVAnDj0Neyy2/s320/Screenshot_2026-06-30_00-15-15ifelifelse.png"div 6/30/26 (Document and date your shit.) https://bsky.app/profile/marcususa.bsky.social

Saturday, June 27, 2026

First Shell Before Importing and Exporting

import customtkinter as ctk import tkinter as tk from tkinter import ttk # ---------------------------- # Main Window # ---------------------------- ctk.set_appearance_mode("dark") ctk.set_default_color_theme("blue") app = ctk.CTk() app.title("Chess Study") app.geometry("1200x700") # ---------------------------- # Configure Grid # ---------------------------- app.grid_rowconfigure(1, weight=1) app.grid_columnconfigure(1, weight=1) # ---------------------------- # Menu Bar # ---------------------------- # ---------------------------- # Menu Bar # ---------------------------- menu_bar = tk.Menu(app) app.config(menu=menu_bar) # ---------------------------- # File Menu # ---------------------------- file_menu = tk.Menu(menu_bar, tearoff=0) menu_bar.add_cascade( label="File", menu=file_menu ) file_menu.add_command(label="Import PGN") file_menu.add_command(label="Export PGN") file_menu.add_separator() file_menu.add_command( label="Exit", command=app.quit ) # ---------------------------- # Edit Menu # ---------------------------- edit_menu = tk.Menu(menu_bar, tearoff=0) menu_bar.add_cascade( label="Edit", menu=edit_menu ) # ---------------------------- # View Menu # ---------------------------- view_menu = tk.Menu(menu_bar, tearoff=0) menu_bar.add_cascade( label="View", menu=view_menu ) # ---------------------------- # Analysis Menu # ---------------------------- analysis_menu = tk.Menu(menu_bar, tearoff=0) menu_bar.add_cascade( label="Analysis", menu=analysis_menu ) # ---------------------------- # Tools Menu # ---------------------------- tools_menu = tk.Menu(menu_bar, tearoff=0) menu_bar.add_cascade( label="Tools", menu=tools_menu ) # ---------------------------- # Help Menu # ---------------------------- help_menu = tk.Menu(menu_bar, tearoff=0) menu_bar.add_cascade( label="Help", menu=help_menu ) # ---------------------------- # Left Navigation # ---------------------------- left_frame = ctk.CTkFrame( app, width=250 ) left_frame.grid( row=1, column=0, sticky="ns" ) left_frame.grid_propagate(False) # ---------------------------- # Tree Style # ---------------------------- style = ttk.Style() style.theme_use("default") style.configure( "Treeview", background="#111111", fieldbackground="#111111", foreground="#EEEEEE", borderwidth=0, rowheight=24 ) style.map( "Treeview", background=[("selected", "#1F6AA5")], foreground=[("selected", "#FFFFFF")] ) tree = ttk.Treeview( left_frame, style="Treeview", show="tree" ) tree.pack( fill="both", expand=True ) # Root Items collections = tree.insert( "", "end", text="Collections", open=True ) analysis = tree.insert( "", "end", text="Analysis", open=True ) music = tree.insert( "", "end", text="Music Notes", open=True ) # Collections worldcup = tree.insert( collections, "end", text="World Cup" ) olympiad = tree.insert( collections, "end", text="Olympiad" ) highschool = tree.insert( collections, "end", text="High School" ) # Example Children tree.insert( worldcup, "end", text="Round 1" ) tree.insert( worldcup, "end", text="Round 2" ) tree.insert( olympiad, "end", text="Open Section" ) tree.insert( olympiad, "end", text="Women's Section" ) # ---------------------------- # Workspace # ---------------------------- workspace = ctk.CTkFrame(app) workspace.grid( row=1, column=1, sticky="nsew", padx=5, pady=5 ) label = ctk.CTkLabel( workspace, text="Workspace", font=("Arial", 24) ) label.pack( expand=True ) # ---------------------------- # Status Bar # ---------------------------- status = ctk.CTkLabel( app, text="Ready", anchor="w", height=24 ) status.grid( row=2, column=0, columnspan=2, sticky="ew" ) app.mainloop()

Friday, June 26, 2026

Tracking down 11, 12, 14, and 13 Python "stuff"

python3.11-tk Python interpreter Yes, that is part of it. 3.11 was from 2025, but this is 2026. Linux Mint Cinnamon 22.3 is using some 12 version. I was trying to get "14" on the PyCharm I had, but due to a "sandbox" issue, I couldn't. I completely removed the flatpak Pycharm I was using which was determined as a sandbox for some reason, and then I put on tar installation and it worked. It automatically recognized the 12 version and I could use customtkinter in PyCharm. Yes, now I remember the sequence much more clearly. Here's what happened: Originally (2025): You had a Flatpak installation of PyCharm. Because Flatpak runs applications in a sandbox, it couldn't properly see the Python installations on your system. You were trying to use Python 3.14, but PyCharm either couldn't find it or couldn't use it correctly because of the sandbox. The fix: You removed the Flatpak version of PyCharm. You installed the tarball version (through JetBrains Toolbox or directly). That version was no longer sandboxed and could access the system Python. On Linux Mint 22.3: PyCharm automatically detected the system Python 3.12 interpreter. You installed customtkinter into that interpreter (or its virtual environment). Everything worked normally, and you decided you were happy using Python 3.12 instead of chasing 3.14. Now you're on PeppermintOS Trixie, which means we need to determine whether you're back in the same situation. I am now on PeppermintOS Trixie. I typed in Pycharm with Python 12 and it gave back nothing. If typing something like: pycharm or searching for "PyCharm" returns nothing, there are a few possibilities. First, let's determine whether PyCharm is installed at all. In a terminal, try these commands one at a time: which pycharm which pycharm-community which idea Then check whether Python 3.12 exists: python3 --version python3.12 --version Also see if the executable is somewhere on your system: find ~ -iname "pycharm*" 2>/dev/null or, if you installed it system-wide: find /opt -iname "pycharm*" 2>/dev/null Since you switched to PeppermintOS Remember that changing distributions does not carry over installed applications unless you reused the same installation. If this is a fresh Peppermint installation, you'll likely need to reinstall: PyCharm your virtual environments packages like python-chess and customtkinter Your project files are only still there if they're on a partition or home directory that you kept. Can you tell me exactly what you typed? When you wrote: "I typed in Pycharm with Python 12 and it gave back nothing." Did you mean: you searched the application menu for "PyCharm"? you typed pycharm in a terminal? or you were searching inside PyCharm for a Python 3.12 interpreter? Also, please post the output of: python3 --version which python3 which pycharm That will tell us where things stand. Thought for a few seconds Yes. From what I can recall, the issue wasn't that Python 3.12 itself was missing—it was that after switching environments, PyCharm wasn't using a Python installation with the components you needed. More specifically: PyCharm ended up using /usr/bin/python. Running your customtkinter test failed because it couldn't find tkinter (ModuleNotFoundError: No module named 'tkinter'). You preferred to run installation commands in the normal terminal, not the PyCharm terminal, and we discussed installing the missing python3.11-tk package there. Now you've switched to PeppermintOS Trixie, so we may be dealing with a different issue. When you said: "I typed in Pycharm with Python 12 and it gave back nothing." it reminds me of another problem we had recently: PyCharm couldn't see or select the Python interpreter you wanted. Can you tell me exactly where you typed "Python 12"? In PyCharm → Add Interpreter? In the application launcher? In a terminal? Somewhere else? That will help determine whether this is the same interpreter-detection problem or something new. python3.11-tk Python interpreter Yes, that is part of it. 3.11 was from 2025, but this is 2026. Linux Mint Cinnamon 22.3 is using some 12 version. I was trying to get "14" on the PyCharm I had, but due to a "sandbox" issue, I couldn't. I completely removed the flatpak Pycharm I was using which was determined as a sandbox for some reason, and then I put on tar installation and it worked. It automatically recognized the 12 version and I could use customtkinter in PyCharm. Yes, now I remember the sequence much more clearly. Here's what happened: Originally (2025): You had a Flatpak installation of PyCharm. Because Flatpak runs applications in a sandbox, it couldn't properly see the Python installations on your system. You were trying to use Python 3.14, but PyCharm either couldn't find it or couldn't use it correctly because of the sandbox. The fix: You removed the Flatpak version of PyCharm. You installed the tarball version (through JetBrains Toolbox or directly). That version was no longer sandboxed and could access the system Python. On Linux Mint 22.3: PyCharm automatically detected the system Python 3.12 interpreter. You installed customtkinter into that interpreter (or its virtual environment). Everything worked normally, and you decided you were happy using Python 3.12 instead of chasing 3.14. Now you're on PeppermintOS Trixie, which means we need to determine whether you're back in the same situation. The first thing I'd like to know is how you installed PyCharm on Peppermint. Was it: Flatpak JetBrains Toolbox Downloaded tar.gz from JetBrains APT package Something else? If it's Flatpak again, I'd immediately suspect the same sandbox problem. Also, could you run these two commands? which pycharm and python3 --version If which pycharm returns nothing, that's okay—it tells us PyCharm isn't on your PATH, which helps narrow down how it was installed. Python 3.13.5 (LOL, there's my 13?) Why did Linux Mint skip from 12 to 14? 😄 That is a good catch. The answer is: Linux Mint didn't actually skip from 3.12 to 3.14. Here's what likely happened: Linux Mint 22.x is based on a version of Ubuntu whose default Python is 3.12. At some point you installed Python 3.14 yourself (or from a PPA or source), which is why you were trying to get PyCharm to recognize it. Now that you're on PeppermintOS Trixie (which is based on a newer Debian release), the default system Python is 3.13.5. So your timeline is probably: Linux Mint 22.3 → Python 3.12.x (system default) You manually installed 3.14 and tried to use it in PyCharm. Flatpak PyCharm couldn't see it because of sandboxing. Tarball PyCharm worked with the system Python 3.12. Now PeppermintOS Trixie ships with Python 3.13.5 as the default. In other words, the operating systems didn't "jump" from 3.12 to 3.14—you introduced 3.14 yourself. The new operating system simply comes with 3.13. You can verify what you have installed with: python3 --version python3.13 --version python3.14 --version ls /usr/bin/python3* I'm actually pleased to see 3.13.5 on your Peppermint system. It means you probably don't need to chase a newer interpreter this time. We can just point PyCharm at Python 3.13 and install python3-tk, python3-venv, python3-pip, python-chess, and customtkinter in that environment. That should give you essentially the same smooth setup you ended up with on Linux Mint.

Thursday, June 25, 2026

MODIFICATION - Add comments, Import chess games, catalog them, list from first variation move and by frequency. / Chess and Music brainstorming

Also see hhttps://1a2b3c4d5e6f7g8h9i10j11k12l13m14n.blogspot.com/2026/06/modification-import-chess-games-catalog.html and https://1a2b3c4d5e6f7g8h9i10j11k12l13m14n.blogspot.com/2026/06/import-chess-games-catalog-them-list.html """ chessopenings3cleanup.py Interactive Opening Catalog + Continuation Analyzer - Loads existing opening catalog (JSON) - Processes ONE PGN at a time - Preserves PGN comments { } - Displays full interactive catalog - Allows manual reordering - Save with comments or without comments Requires: pip install python-chess """ import chess.pgn import os import json import re JSON_FILE = "personal_openings.json" START_PLY = 0 MAX_PLIES = 30 # ---------------------------- # LOAD / SAVE # ---------------------------- def load_data(): if os.path.exists(JSON_FILE): with open(JSON_FILE, "r", encoding="utf-8") as f: return json.load(f) return {} def save_data(data): with open(JSON_FILE, "w", encoding="utf-8") as f: json.dump(data, f, indent=4) def strip_comments(text): return re.sub(r"\s*\{.*?\}", "", text) def remove_comments_from_data(data): clean_data = {} for opening_key, variations in data.items(): clean_data[opening_key] = {} for line, freq in variations.items(): clean_line = strip_comments(line) if clean_line not in clean_data[opening_key]: clean_data[opening_key][clean_line] = freq else: clean_data[opening_key][clean_line] += freq return clean_data # ---------------------------- # FORMAT MOVES # ---------------------------- def format_continuation(san_moves, start_ply): lines = [] i = 0 ply = start_ply while i < len(san_moves) and ply < MAX_PLIES: move_number = (ply // 2) + 1 white = san_moves[i] i += 1 ply += 1 line = f"{move_number}. {white}" if i < len(san_moves) and ply < MAX_PLIES: black = san_moves[i] i += 1 ply += 1 line += f" {black}" lines.append(line) return lines # ---------------------------- # PROCESS PGN # ---------------------------- def process_pgn(filename, data): game_count = 0 with open(filename, "r", encoding="utf-8", errors="ignore") as pgn: while True: game = chess.pgn.read_game(pgn) if game is None: break game_count += 1 eco = game.headers.get("ECO", "").strip() opening = game.headers.get("Opening", "").strip() variation = game.headers.get("Variation", "").strip() white_elo = game.headers.get("WhiteElo", "") black_elo = game.headers.get("BlackElo", "") try: white_elo = int(white_elo) black_elo = int(black_elo) except ValueError: continue if white_elo < 2100 and black_elo < 2100: continue if not opening: opening = "Unknown Opening" key = f"{eco} | {opening}" if variation: key += f" | {variation}" board = game.board() san_moves = [] node = game ply_count = 0 while node.variations and ply_count < MAX_PLIES: next_node = node.variation(0) move_text = board.san(next_node.move) comment = next_node.comment.strip() if comment: move_text += f" {{{comment}}}" san_moves.append(move_text) board.push(next_node.move) node = next_node ply_count += 1 catalog_lines = format_continuation( san_moves, START_PLY ) catalog_str = " ".join(catalog_lines) if key not in data: data[key] = {} if catalog_str not in data[key]: data[key][catalog_str] = 1 else: data[key][catalog_str] += 1 print("\nGames processed:", game_count) # ---------------------------- # DISPLAY # ---------------------------- def display(data): print("\nOPENING CATALOG") print("=" * 100) items = sorted( data.items(), key=lambda item: sum(item[1].values()), reverse=True ) for i, (key, variations) in enumerate(items, start=1): print(f"\n{i:3} {key}") sorted_variations = sorted( variations.items(), key=lambda x: x[1], reverse=True ) for j, (cont, freq) in enumerate( sorted_variations[:5], start=1 ): print(f"\n [{j}] freq={freq}") print(f" {cont}") print("-" * 100) # ---------------------------- # REORDER # ---------------------------- def reorder(data): items = list(data.items()) while True: display(dict(items)) print("\nCommands:") print(" X=Y move item X to position Y") print(" s save WITH comments") print(" n save WITHOUT comments") print(" q quit") cmd = input("\nCommand: ").strip().lower() if cmd == "s": save_data(dict(items)) print("\nSaved with comments.") continue if cmd == "n": clean_data = remove_comments_from_data( dict(items) ) save_data(clean_data) print("\nSaved without comments.") continue if cmd == "q": return dict(items) if "=" in cmd: try: a, b = cmd.split("=") a = int(a) - 1 b = int(b) - 1 item = items.pop(a) items.insert(b, item) except: print("Invalid command") # ---------------------------- # MAIN # ---------------------------- def main(): data = load_data() print("\nChessOpenings3 Cleanup") print("----------------------") filename = input( "\nPGN filename (WITHOUT .pgn): " ).strip() if not filename: return filename += ".pgn" if not os.path.exists(filename): print("File not found:", filename) return process_pgn(filename, data) data = reorder(data) print("\nDone.") if __name__ == "__main__": main()

Tuesday, June 23, 2026

ChatGPT - Brainstorm for different approach (user interaction and sharing), third code preparation

# chessopenings3.py - Project Summary ## Why chessopenings3.py Exists This is a significant departure from chessopenings2.py. The original direction was moving toward creating a prescribed hierarchy of openings and variations. After reviewing another person's opening PDF and discussing user customization, the conclusion was reached that the software should not impose a hierarchy. Instead: * The program creates and maintains an opening database. * The user creates the hierarchy. * Different users can create different hierarchies from the same underlying database. * Users can share or adopt other users' catalogs. Example: * A 1000-rated player may adopt a 1200-rated player's catalog. * A 1200-rated player may adopt a 1500-rated player's catalog. * Users may modify these catalogs over time. The software should support this rather than enforcing a fixed order. --- ## Core Philosophy The software should: * Preserve data. * Preserve annotations. * Preserve PGN information. * Allow user-defined ordering. * Avoid automatic interpretation whenever possible. The user is responsible for deciding: * What matters. * What should be ranked higher. * What should be grouped together. * What annotations should exist. The program should not attempt to merge comments, generate annotations, or determine meaning. --- ## Immediate Changes From chessopenings2.py ### 1. Remove the Move-4 Assumption The current code effectively assumes opening/variation identification begins around moves 4-5. This must change. Some openings become meaningful before move 4. Examples: * Reti Opening * Ruy Lopez * Other systems identified very early The importer should not assume: Opening -> Move 4 -> Variation Instead: Opening -> Next Important Move regardless of move number. The ECO code already present in the PGN can be used to identify the opening. --- ### 2. Remove Custom "|" Formatting The existing pipe-delimited format should be eliminated. The project should stay as close to standard PGN as possible. Goal: PGN -> JSON -> PGN without introducing custom move notation. --- ### 3. Preserve Full Games Do not store only opening fragments. Store: * Full PGN move text * Entire game * Headers * Comments * Variations The full game should remain available. Future projects may need information later in the game. --- ### 4. Preserve Headers At minimum preserve: * Event * Site * Date * White * Black * Result * ECO * Opening * Variation Additional headers may also be preserved. --- ## Annotation Philosophy ### Preserve Existing Annotations PGN comments using: ```pgn {comment text} ``` must be preserved. This is a long-standing PGN standard. Examples: ```pgn {Black asks the bishop a question.} ``` ```pgn {Tal sacrifices material for initiative.} ``` ```pgn {P-K4 was considered best in older books.} ``` The importer should preserve comments exactly. --- ### Preserve Variations Side variations: ```pgn (12...Nf6 13.Re1) ``` should also be preserved. --- ### Preserve Symbolic Annotations If Present Examples: ```pgn ! ? !! ?? !? ?! ``` These are not a priority for Project 1. However: * Do not remove them. * Preserve them if they exist. They may become useful later. --- ### Do Not Interpret Annotations The software should not decide: * Which annotation is correct. * Which annotation is better. * Whether an annotation agrees with an engine. Project 1 is not an engine-analysis project. --- ### Descriptive Notation Is Allowed Comments may contain: * Algebraic notation * Descriptive notation * Historical notation * Mixed notation Examples: ```text P-K4 ``` or ```text 1.e4 ``` The software should simply preserve text. --- ## JSON Philosophy The JSON becomes the master database. The user may no longer possess the original PGN files. Therefore the JSON must contain enough information to reconstruct or export PGN later. The JSON should preserve: * Headers * Moves * Comments * Variations * User-added annotations --- ## Annotation Editing Philosophy No automatic merging. No AI merging. No engine merging. No conflict resolution. Workflow: Current annotation ↓ User edits annotation ↓ Save ↓ Replace previous annotation Exactly like: * YouTube comments * Facebook edits * Editing a text document The user supplies the complete updated text. The software stores the new version. --- ## Updating Existing Games If a game is re-imported later: The goal is NOT: Duplicate Game The goal is: Update Existing Game The same game should not count twice. The program should recognize an existing game and allow updating rather than duplicating. This prevents variation counts from becoming inflated. --- ## Search Functionality Eventually the user should not be forced to sort all variations. Example: Search: Ruy Lopez The user only sees Ruy Lopez variations. Similarly: Search: French Defense Search: Caro-Kann Search: Italian Game This allows focused catalog construction. --- ## Hierarchy Philosophy The software does NOT create the hierarchy. The user creates the hierarchy. Examples: * Global ranking * Opening ranking * Pattern ranking are user-generated concepts. Different users will create different structures. This is intentional. --- ## Pattern Relationships Future possibility: The software preserves ECO classification while allowing users to discover pattern relationships. Examples: French Advance ↔ Caro-Kann Advance Ruy Lopez ↔ Italian These relationships are not required to be hard-coded. The user may discover and organize these patterns naturally. --- ## Project 1 Scope Project 1 is NOT: * Engine analysis * Stockfish integration * Automatic evaluation * Automatic annotation generation Project 1 IS: * PGN import * JSON creation * Annotation preservation * Header preservation * Variation preservation * User-controlled organization * Future PGN export capability --- ## Filename Create as: chessopenings3.py Reason: This is a significant departure from chessopenings2.py. If the design succeeds, continue forward. If it fails, return to chessopenings2.py and attempt a different chessopenings3.py approach. Do not overwrite chessopenings2.py.

Monday, June 22, 2026

MODIFICATION - Import chess games, catalog them, list from first variation move and by frequency. / Chess and Music brainstorming

Check previous post for Chess and Music brainstorming, https://1a2b3c4d5e6f7g8h9i10j11k12l13m14n.blogspot.com/2026/06/import-chess-games-catalog-them-list.html """ chessopenings2.py Interactive Opening Catalog + Continuation Analyzer - Loads existing opening catalog (JSON) - Processes ONE PGN at a time - Extracts continuations (moves AFTER opening) - Displays full interactive catalog in PyCharm - Allows manual reordering like chessopenings1.py Requires: pip install python-chess """ import chess.pgn import os import json JSON_FILE = "personal_openings.json" OPENING_SKIP_PLIES = 6 # skip first ~3 full moves MAX_PLIES = 30 # up to 15 full moves # ---------------------------- # LOAD / SAVE # ---------------------------- def load_data(): if os.path.exists(JSON_FILE): with open(JSON_FILE, "r", encoding="utf-8") as f: return json.load(f) return {} def save_data(data): with open(JSON_FILE, "w", encoding="utf-8") as f: json.dump(data, f, indent=4) # ---------------------------- # FORMAT CONTINUATIONS (MOVE NUMBERED) # ---------------------------- def format_continuation(san_moves, start_ply): lines = [] i = 0 ply = start_ply while i < len(san_moves) and ply < MAX_PLIES: move_number = (ply // 2) + 1 white = san_moves[i] i += 1 ply += 1 line = f"{move_number}. {white}" if i < len(san_moves) and ply < MAX_PLIES: black = san_moves[i] i += 1 ply += 1 line += f" {black}" lines.append(line) return lines # ---------------------------- # PROCESS PGN # ---------------------------- def process_pgn(filename, data): game_count = 0 with open(filename, "r", encoding="utf-8", errors="ignore") as pgn: while True: game = chess.pgn.read_game(pgn) if game is None: break game_count += 1 eco = game.headers.get("ECO", "").strip() opening = game.headers.get("Opening", "").strip() variation = game.headers.get("Variation", "").strip() white_elo = game.headers.get("WhiteElo", "") black_elo = game.headers.get("BlackElo", "") try: white_elo = int(white_elo) black_elo = int(black_elo) except ValueError: continue if white_elo < 2100 and black_elo < 2100: continue if not opening: opening = "Unknown Opening" key = f"{eco} | {opening}" if variation: key += f" | {variation}" board = game.board() san_moves = [] for move in game.mainline_moves(): san_moves.append(board.san(move)) board.push(move) continuation_moves = san_moves[ OPENING_SKIP_PLIES:OPENING_SKIP_PLIES + MAX_PLIES ] start_ply = OPENING_SKIP_PLIES continuation_lines = format_continuation( continuation_moves, start_ply ) continuation_str = " | ".join(continuation_lines) if key not in data: data[key] = {} if continuation_str not in data[key]: data[key][continuation_str] = 1 else: data[key][continuation_str] += 1 print("\nGames processed:", game_count) # ---------------------------- # DISPLAY # ---------------------------- def display(data): print("\nOPENING CATALOG (WITH CONTINUATIONS)") print("=" * 100) items = sorted( data.items(), key=lambda item: sum(item[1].values()), reverse=True ) for i, (key, variations) in enumerate(items, start=1): print(f"\n{i:3} {key}") if not variations: print(" (no continuation data yet)") continue sorted_variations = sorted( variations.items(), key=lambda x: x[1], reverse=True ) for j, (cont, freq) in enumerate(sorted_variations[:5], start=1): print(f"\n [{j}] freq={freq}") print(f" {cont}") print("-" * 100) # ---------------------------- # REORDER SYSTEM # ---------------------------- def reorder(data): items = list(data.items()) while True: display(dict(items)) print("\nCommands:") print(" X=Y move item X to position Y") print(" s save") print(" q quit") cmd = input("\nCommand: ").strip().lower() if cmd == "s": save_data(dict(items)) print("\nSaved.") continue if cmd == "q": return dict(items) if "=" in cmd: try: a, b = cmd.split("=") a = int(a) - 1 b = int(b) - 1 item = items.pop(a) items.insert(b, item) except: print("Invalid command") # ---------------------------- # MAIN # ---------------------------- def main(): data = load_data() print("\nChessOpenings2 - Interactive Continuation System") print("------------------------------------------------") filename = input("\nPGN filename (WITHOUT .pgn): ").strip() if not filename: return filename = filename + ".pgn" if not os.path.exists(filename): print("File not found:", filename) return process_pgn(filename, data) data = reorder(data) save_data(data) print("\nDone.") if __name__ == "__main__": main()