Lightning Chess Timer

Hi, I’m new here. I’m not a programmer nor will I ever be. I’m an old chess player who has tried to make a gui program to run a Lightning Chess Timer for my local club. It works but not as polished as I’d like and I’ve playing at this on and off for over a year.

‘Lightning Chess’ is nominally 10 seconds per move. It works like this. A buzzer goes off (I use a 3 second foghorn) for 3 seconds and all the White players make their first move during the foghorn; not before and not after. All infringements are an immediate loss as there is no time to argue. For example, you do not announce “check!” If your opponent does not see the check you just take the King and win the game. After the 3 second foghorn there is an adjustable delay. In practice we find 9 seconds delay to be good for most people.

So my gui design is this:
Spinbox to choose the delay seconds, wrapping 2-20.
3 buttons Play, Stop and Quit.

Play and Stop Functions

def play():
    global user_input
    foghorn = subprocess.call(['mpv', '/$path/foghorn.ogg'])
	# Re-schedule this function to run after user_input seconds.
    win.after(user_input, play)
def stop():
    win.after_cancel(win.after(user_input, play))

The play function works great. No problem, loops on itself.
The stop function cancels the after() timer but the foghorn plays over and over with <1 sec between repeats.

I’ve tried using a global interrupt but with no difference. Any idea what I’m doing wrong please. (BTW the Quit button still works after the short gui freeze.) Ill just post the whole code, might be simpler.

#!/usr/bin/env python3

import tkinter as ttk
from tkinter import Button, Scale, Label    
import threading
import subprocess 

# global interrupt; interrupt = False
user_input = 9

def on_change():
	global user_input
	# convert spinbox string to integer then milliseconds
	user_input=int(spinbox.get())*1000

def start_timer():
    global timer_id
    timer_id = win.after(user_input, play)
    
def play():
    global user_input
    foghorn = subprocess.call(['mpv', '/home/moss/Python/Lightning/foghorn.ogg'])
	# Re-schedule this function to run after user_input seconds.
    win.after(user_input, play)
    
def stop():
    win.after_cancel(win.after(user_input, play))
    
win = ttk.Tk()
win.title("Lightning Chess Timer")
win.geometry("180x140")

spinbox_label = ttk.Label(win, text="2 to 20 seconds pause")
spinbox_label.grid(row=1, column=1, columnspan=2, sticky=ttk.W, padx=5, pady=3)
spinbox = ttk.Spinbox(win, from_=2, to=20, increment=1,  font=("Helvetica", 10), wrap=True, 
    state="readonly", width=5, textvariable=user_input, command=on_change, justify=ttk.RIGHT)
spinbox.grid(row=2, column=1, sticky=ttk.W, padx=5, pady=3)

play_button = ttk.Button(win, text="Play", bg="pale green", command=lambda: [start_timer(), play()])
play_button.grid(row=3, column=1, sticky=ttk.W, padx=5, pady=3) 

stop_button = ttk.Button(win, text="Stop", bg="pink", command=stop)
stop_button.grid(row=3, column=2, sticky=ttk.W, padx=5, pady=3)

quit_button = ttk.Button(win, text="Quit", command=win.destroy)
quit_button.grid(row=4, column=1, sticky=ttk.W, padx=5, pady=3) 

# 9 is usually good user_value in practice.
win.mainloop()

BTW I run OpenBSD but I’m the least knowledgeable user on the planet and the python version is 3.11

subprocess.call will wait for the process it called to finish, so it’ll be setting the timer again after that.

subprocess.Popen, on the other hand, won’t wait, so it’ll set the timer again immediately after it has started the subprocess that, 9 seconds, or whatever, after it last went off.

You’re setting user_input to 9 initially. If you hit Play without touching the spinbox, that’s the value that’ll be used: 9 milliseconds.

To stop the timer, you should really be using timer_id to cancel the timer in stop. You’re setting timer_id when you first start it in start, but not in play when you’re setting the timer again.

Incidentally, command=lambda: [start_timer(), play()] is a novel way to do it. Personally, I’d put write a function to call start_timer and play.

2 Likes

this does not help at all but, I wish I knew the amount you did when I started

Thank you so much! Ill have another bash at it tomorrow!

Hahaha. Thanks for the compliment. I hsve learned from scratch during the year. Trial snd error - feedback from that and searching and a little reading

BTW sorry about the paragraph of capital letters. Could not seem to edit it out.

“… capital letters”? If you mean the large letters, that’s because of the line of equals signs on the next line.

This site uses “markup” which controls how the text is displayed. A line of equals signs makes indicates that the preceding line is a heading, so it should be displayed as large text.

Putting a blank line after the line of equals signs will stop that from happening.

Yes, large letters. Thanks for the explanation. I’m not used to markup.

Fixed. Finally cracked it, at least to my liking, this morning. It’s ugly but it is good enough for me. :slight_smile: Thank you for the support folks! (BTW, oddly the user_input did not set until I put it in the window section.)

#!/usr/bin/env python3

import tkinter as ttk
import subprocess 

def play():
    global user_input
    global timer_id
    user_input=int(spinbox.get())*1000
    foghorn = subprocess.Popen(['mpv', '/home/moss/Python/Lightning/foghorn.ogg'])
	# Re-schedule this function to run after user_input seconds.
    timer_id = win.after(user_input, play)
    
def stop():
    global timer_id
    win.after_cancel(timer_id)
    
win = ttk.Tk()
win.title("Lightning Chess Timer")
win.geometry("300x180")

user_input = ttk.StringVar()
user_input.set("9")

spinbox_label = ttk.Label(win, text="5 to 30 second cycle.", font=("Helvetica", 8))
spinbox_label.grid(row=1, column=1, columnspan=2, sticky=ttk.S, padx=5, pady=3)
spinbox = ttk.Spinbox(win, from_=5, to=30, increment=1,  font=("Helvetica", 10), wrap=True, 
    state="readonly", width=5, textvariable=user_input, command=None, justify=ttk.RIGHT)
spinbox.grid(row=2, column=1, sticky=ttk.N, padx=5, pady=3)

play_button = ttk.Button(win, text="Play", font=("Helvetica", 8), bg="pale green", command=play)
play_button.grid(row=3, column=1, sticky=ttk.W, padx=5, pady=3) 

stop_button = ttk.Button(win, text="Stop", font=("Helvetica", 8), bg="pink", command=stop)
stop_button.grid(row=3, column=2, sticky=ttk.W, padx=5, pady=3)

quit_button = ttk.Button(win, text="Quit", font=("Helvetica", 8), command=win.destroy)
quit_button.grid(row=4, column=1, sticky=ttk.W, padx=5, pady=3) 

win.mainloop()

yay you got it to work

Last time chaps, honest! :slight_smile: A brand new friend has got this running with more refinement and pygame and even made a Windows executable for me. Esplendido.

import os
import sys
import pygame
import tkinter as ttk

# Initialize pygame mixer
pygame.mixer.init()

def play():
    global user_input
    global timer_id
    user_input = int(spinbox.get()) * 1000

    # Determine the path where the executable or script is located
    if getattr(sys, 'frozen', False):  # If running as an executable
        app_path = sys._MEIPASS
    else:  # If running from the source code
        app_path = os.path.dirname(os.path.abspath(__file__))

    # Define the path to the MP3 file
    mp3_path = os.path.join(app_path, 'foghorn.mp3')

    # Check if the MP3 file exists
    if os.path.exists(mp3_path):
        pygame.mixer.music.load(mp3_path)
        pygame.mixer.music.play()
    else:
        print("MP3 file not found!")

    # Re-schedule this function to run after user_input seconds
    timer_id = win.after(user_input, play)

def stop():
    global timer_id
    win.after_cancel(timer_id)

win = ttk.Tk()
win.title("Lightning Chess Timer")
win.geometry("300x180")

user_input = ttk.StringVar()
user_input.set("9")

spinbox_label = ttk.Label(win, text="5 to 30 second cycle.", font=("Helvetica", 8))
spinbox_label.grid(row=1, column=1, columnspan=2, sticky=ttk.S, padx=5, pady=3)
spinbox = ttk.Spinbox(win, from_=5, to=30, increment=1, font=("Helvetica", 10), wrap=True, 
    state="readonly", width=5, textvariable=user_input, command=None, justify=ttk.RIGHT)
spinbox.grid(row=2, column=1, sticky=ttk.N, padx=5, pady=3)

play_button = ttk.Button(win, text="Play", font=("Helvetica", 8), bg="pale green", command=play)
play_button.grid(row=3, column=1, sticky=ttk.W, padx=5, pady=3) 

stop_button = ttk.Button(win, text="Stop", font=("Helvetica", 8), bg="pink", command=stop)
stop_button.grid(row=3, column=2, sticky=ttk.W, padx=5, pady=3)

quit_button = ttk.Button(win, text="Quit", font=("Helvetica", 8), command=win.destroy)
quit_button.grid(row=4, column=1, sticky=ttk.W, padx=5, pady=3)

win.mainloop()

are you going to put on github

I have no idea how to do that. Do you really think it worthwhile? I am happy to do so. I’ll speak to my new friend Jack and see if he knows more about it. I literally only met him last week!

I think people would like it I would at least

OK it is up GitHub - manselton/Lightning-Chess-Timer: A simple gui to play a 3 second foghorn every 5-30 seconds, inclusive.

Please try it out and tell me if it works. So far only Jack and myself have played it. (You just can’t get the staff these days :slight_smile: )

1 Like

it works but the .EXE takes a while to load

Tx for that. Youre the first with any feedback. I dont let windows in the house but i installed win10 and win 11 in Virtualbox. Would not run on 10 (exe made on 11) Ugly and clunky on 11. Needs more work … ah well. Give me time lol

1 Like

Well, I’ve given up trying to improve the Windows exe. I’ve just updated the Readme to tell Windows users to do what any Linux user would do and install Python etc.

1 Like