Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import pandas as pd | |
| from huggingface_hub import HfApi, HfFolder | |
| import os | |
| import uuid | |
| from pathlib import Path | |
| # --- Constants for the Calculation --- | |
| GRUNDWERTE = {"Karo": 9, "Herz": 10, "Pik": 11, "Kreuz": 12, "Grand": 24} | |
| NULL_SPIELE = {"Null": 23, "Null Hand": 35, "Null Ouvert": 46, "Null Hand Ouvert": 59} | |
| SPIELER = ["Kirsten", "Robert", "Samuel"] | |
| # Added a new game type 'Ramsch' to the list of choices | |
| SPIELARTEN = ['Karo', 'Herz', 'Pik', 'Kreuz', 'Grand', 'Null', 'Null Hand', 'Null Ouvert', 'Null Hand Ouvert', 'Ramsch'] | |
| HISTORY_DF_HEADERS = ["Kirsten", "Robert", "Samuel", "Spielwert", "Spielart", "Alleinspieler", "Details", "Verloren"] | |
| # --- Hugging Face Configuration --- | |
| LOCAL_TOURNAMENT_DIR = Path("saved_tournaments/") | |
| LOCAL_TOURNAMENT_DIR.mkdir(exist_ok=True) | |
| token = os.getenv('dataset_access_token') | |
| # --- Core Functions --- | |
| def calculate_spielwert(spielart, spitzen_str, hand, schneider, schwarz, schneider_angesagt, schwarz_angesagt, offen): | |
| """Calculates the pure game value (Reizwert) based on the game rules.""" | |
| if spielart in NULL_SPIELE: | |
| return NULL_SPIELE[spielart] | |
| if spielart not in GRUNDWERTE or not spitzen_str: | |
| return 0 | |
| spitzen_anzahl = int(spitzen_str.split(" ")[1]) | |
| gewinnstufe = 1 | |
| if hand: gewinnstufe += 1 | |
| if schneider: gewinnstufe += 1 | |
| if schneider_angesagt: gewinnstufe += 1 | |
| if schwarz: gewinnstufe += 1 | |
| if schwarz_angesagt: gewinnstufe += 1 | |
| if offen: gewinnstufe += 1 | |
| grundwert = GRUNDWERTE[spielart] | |
| return (spitzen_anzahl + gewinnstufe) * grundwert | |
| def spiel_hinzufuegen(history_df, spieler, spielart, spitzen, hand, schneider, schwarz, schneider_angesagt, schwarz_angesagt, offen, verloren, ramsch_verlierer=None, ramsch_punkte=None, ramsch_geschoben=None): | |
| """Adds a new game to the history DataFrame and dynamically calculates the total scores.""" | |
| # --- New logic for 'Ramsch' game type --- | |
| if spielart == 'Ramsch': | |
| if not ramsch_verlierer or not isinstance(ramsch_punkte, (int, float)) or ramsch_punkte <= 0: | |
| # Return current state if Ramsch inputs are invalid or no points were made. | |
| total_scores = history_df[SPIELER].sum() if not history_df.empty else {s: 0 for s in SPIELER} | |
| totals_df = pd.DataFrame([total_scores]) | |
| return totals_df, history_df | |
| # Per user request: a negative score for the loser based on their points and how often the skat was passed ("geschoben"). | |
| # A standard rule is that each "schieben" doubles the game's value. | |
| # Final score = loser_points * (2 ** number_of_passes). This is robust and handles the 0-pass case correctly. | |
| spielwert = ramsch_punkte * (2 ** ramsch_geschoben) | |
| neue_punkte = {name: 0 for name in SPIELER} | |
| # Only the loser's score is affected, as specified. | |
| neue_punkte[ramsch_verlierer] = -spielwert | |
| details = f"Augen: {int(ramsch_punkte)}, Geschoben: {ramsch_geschoben} mal" | |
| neues_spiel_eintrag = { | |
| "Kirsten": neue_punkte["Kirsten"], | |
| "Robert": neue_punkte["Robert"], | |
| "Samuel": neue_punkte["Samuel"], | |
| "Spielwert": spielwert, | |
| "Spielart": "Ramsch", | |
| "Alleinspieler": "N/A", # No soloist in Ramsch | |
| "Details": details, | |
| "Verloren": "Ja" # A Ramsch game always has a loser | |
| } | |
| new_row_df = pd.DataFrame([neues_spiel_eintrag]) | |
| updated_history_df = pd.concat([history_df, new_row_df], ignore_index=True) | |
| total_scores = updated_history_df[SPIELER].sum().astype(int) | |
| totals_df = pd.DataFrame([total_scores]) | |
| return totals_df, updated_history_df | |
| # --- Original logic for standard games --- | |
| spielwert = calculate_spielwert(spielart, spitzen, hand, schneider, schwarz, schneider_angesagt, schwarz_angesagt, offen) | |
| neue_punkte = {name: 0 for name in SPIELER} | |
| if spieler: | |
| if not verloren: | |
| neue_punkte[spieler] = 50 + spielwert | |
| for p in SPIELER: | |
| if p != spieler: | |
| neue_punkte[p] = -40 | |
| else: # Game lost | |
| neue_punkte[spieler] = -50 - (2 * spielwert) | |
| for p in SPIELER: | |
| if p != spieler: | |
| neue_punkte[p] = 40 | |
| details = f"Spitzen: {spitzen or 'N/A'}" | |
| if hand: details += ", Hand" | |
| if schneider_angesagt: details += ", Schneider angesagt" | |
| if schwarz_angesagt: details += ", Schwarz angesagt" | |
| if offen: details += ", Offen" | |
| neues_spiel_eintrag = { | |
| "Kirsten": neue_punkte["Kirsten"], | |
| "Robert": neue_punkte["Robert"], | |
| "Samuel": neue_punkte["Samuel"], | |
| "Spielwert": spielwert, | |
| "Spielart": spielart, | |
| "Alleinspieler": spieler or "N/A", | |
| "Details": details, | |
| "Verloren": "Ja" if verloren else "Nein" | |
| } | |
| new_row_df = pd.DataFrame([neues_spiel_eintrag]) | |
| updated_history_df = pd.concat([history_df, new_row_df], ignore_index=True) | |
| total_scores = updated_history_df[SPIELER].sum().astype(int) | |
| totals_df = pd.DataFrame([total_scores]) | |
| return totals_df, updated_history_df | |
| def reset_turnier(): | |
| """Resets the entire score and history.""" | |
| empty_totals = pd.DataFrame([{"Kirsten": 0, "Robert": 0, "Samuel": 0}]) | |
| empty_history = pd.DataFrame(columns=HISTORY_DF_HEADERS) | |
| return empty_totals, empty_history | |
| def save_turnier_to_hf(history_df): | |
| """Saves the current tournament history to a local JSON file and immediately uploads it to the Hugging Face Hub.""" | |
| if HfFolder.get_token() is None: | |
| return "Hugging Face Token nicht gefunden. Bitte via `huggingface-cli login` anmelden." | |
| if history_df.empty: | |
| return "Keine Spieldaten zum Speichern vorhanden." | |
| tournament_filename = f"skat_tournament_{uuid.uuid4()}.jsonl" | |
| local_file_path = LOCAL_TOURNAMENT_DIR / tournament_filename | |
| history_df.to_json(local_file_path, orient="records", lines=True, force_ascii=False) | |
| api = HfApi() | |
| repo_id = "slevis/skat-scores" | |
| try: | |
| api.upload_file( | |
| path_or_fileobj=local_file_path, | |
| path_in_repo=f"data/{tournament_filename}", | |
| repo_id=repo_id, | |
| repo_type="dataset", | |
| commit_message=f"Add new skat tournament data: {tournament_filename}" | |
| ) | |
| return f"Turnier erfolgreich nach '{repo_id}' auf Hugging Face hochgeladen." | |
| except Exception as e: | |
| return f"Fehler beim Upload: {e}" | |
| # --- UI Definition with Gradio --- | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# Skat-Punkte-Tracker") | |
| gr.Markdown("### Gesamtpunktestand") | |
| initial_totals_df, initial_history_df = reset_turnier() | |
| totals_output = gr.Dataframe( | |
| value=initial_totals_df, | |
| interactive=False, | |
| show_row_numbers=False | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### Neues Spiel eingeben") | |
| spielart_input = gr.Dropdown( | |
| choices=SPIELARTEN, | |
| label="Spielart" | |
| ) | |
| # --- Container for Standard Game Inputs (visible by default) --- | |
| with gr.Group(visible=True) as standard_spiel_group: | |
| gr.Markdown("#### Standard-Spiel") | |
| spieler_input = gr.Dropdown(SPIELER, label="Alleinspieler") | |
| spitzen_input = gr.Dropdown( | |
| choices=[f"{m} {i}" for m in ["Mit", "Ohne"] for i in range(1, 5)], | |
| label="Spitzen" | |
| ) | |
| with gr.Row(): | |
| schneider_input = gr.Checkbox(label="Schneider") | |
| schwarz_input = gr.Checkbox(label="Schwarz") | |
| verloren_input = gr.Checkbox(label="Verloren?") | |
| with gr.Accordion("Optionen für Handspiele", open=False): | |
| hand_input = gr.Checkbox(label="Handspiel") | |
| schneider_angesagt_input = gr.Checkbox(label="Schneider angesagt") | |
| schwarz_angesagt_input = gr.Checkbox(label="Schwarz angesagt") | |
| offen_input = gr.Checkbox(label="Offen / Ouvert") | |
| # --- Container for Ramsch Game Inputs (hidden by default) --- | |
| with gr.Group(visible=False) as ramsch_spiel_group: | |
| gr.Markdown("#### Ramsch-Spiel") | |
| ramsch_verlierer_input = gr.Dropdown(SPIELER, label="Verlierer") | |
| ramsch_punkte_input = gr.Number(label="Augen des Verlierers", value=0, precision=0) | |
| ramsch_geschoben_input = gr.Dropdown( | |
| choices=[("Keinmal (Standard)", 0), ("1 mal", 1), ("2 mal", 2), ("3 mal", 3)], | |
| value=0, | |
| label="Wie oft wurde der Skat geschoben?" | |
| ) | |
| with gr.Row(): | |
| speichere_spiel = gr.Button("Speichere Spiel", variant="primary", scale=2) | |
| reset_button = gr.Button("Reset", variant="stop", scale=1) | |
| save_to_hf_button = gr.Button("Turnier auf Hugging Face speichern", variant="secondary") | |
| hf_status_output = gr.Label(value="", label="Upload Status") | |
| with gr.Column(scale=2): | |
| gr.Markdown("### Spielverlauf") | |
| turnier_output = gr.Dataframe( | |
| value=initial_history_df, | |
| headers=HISTORY_DF_HEADERS, | |
| interactive=False, | |
| wrap=True | |
| ) | |
| # --- Function to dynamically show/hide input sections --- | |
| def toggle_spielart_inputs(spielart): | |
| if spielart == "Ramsch": | |
| return { | |
| standard_spiel_group: gr.update(visible=False), | |
| ramsch_spiel_group: gr.update(visible=True) | |
| } | |
| else: | |
| return { | |
| standard_spiel_group: gr.update(visible=True), | |
| ramsch_spiel_group: gr.update(visible=False) | |
| } | |
| # --- Event Handlers --- | |
| # Link the dropdown change to the visibility function | |
| spielart_input.change( | |
| fn=toggle_spielart_inputs, | |
| inputs=spielart_input, | |
| outputs=[standard_spiel_group, ramsch_spiel_group] | |
| ) | |
| # Consolidate all possible inputs for the click handler | |
| all_inputs = [ | |
| turnier_output, | |
| # Standard game inputs | |
| spieler_input, spielart_input, spitzen_input, | |
| hand_input, schneider_input, schwarz_input, | |
| schneider_angesagt_input, schwarz_angesagt_input, offen_input, | |
| verloren_input, | |
| # Ramsch game inputs | |
| ramsch_verlierer_input, ramsch_punkte_input, ramsch_geschoben_input | |
| ] | |
| speichere_spiel.click( | |
| fn=spiel_hinzufuegen, | |
| inputs=all_inputs, | |
| outputs=[totals_output, turnier_output] | |
| ) | |
| reset_button.click( | |
| fn=reset_turnier, | |
| inputs=[], | |
| outputs=[totals_output, turnier_output] | |
| ) | |
| save_to_hf_button.click( | |
| fn=save_turnier_to_hf, | |
| inputs=[turnier_output], | |
| outputs=[hf_status_output] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(pwa=True) |