Python for cubing programs?

I also learned a little Python in college. It's a super fun programming language!

Honestly, as a beginning programmer, you probably won't be able to make anything that someone already hasn't done, but that shouldn't stop you from playing around with things.

Some fun things you could do

Basic:
Make random scramble generators. This should be pretty easy. Try to make it so it won't repeat the same moves back to back

Intermediate:
Make a timer. I have no idea how to mess with time stuff in Python, but it shouldn't be terribly difficult to start and stop the timer, and store the times somewhere, even calculate Ao5, Ao12, and Ao100.

Advanced:
Make an OLL/PLL practice tool. It would need you to be able to submit a list of OLL/PLL algorithms, it would need to reverse them to create the "practice scramble", display the practice scrambles randomly, and add your timer to time the results.

I <3 Python. Also, for intermediate, wouldn't the time module just make it easy?
 
I <3 Python. Also, for intermediate, wouldn't the time module just make it easy?

I also heart Python. And for most problems, there is always a module you can use to "make it easy." But I'm not so sure timing is one of them. Python's time module is questionable. Some people say it's supposed to be accurate, others think it's not so great.

Some time ago, I wrote a megaminx simulator in Python, but after testing its time module against whatever qqTimer uses, I found that my timer slowly became farther and farther off. It could be because of all the work it was doing simultaneously to render the megaminx, or whatever, but depending on what you're writing (and the duration you want to measure) I would not say that it's that simple of a problem.
 
Sounds like you're doing it wrong. Typically you build the GUI, register actions that will be called when something happens (like a button click), and then call the GUI's mainloop, i.e., giving up control, not still running your own loop. What GUI package are you using?

I'm using Tkinter for the GUI. I'm sure there's a way to use it, I just never learned it.

Basically, here's what I want:

While x is lower than (Total Pairs to Memo):
Randomly select and display a pair
(pause and wait for feedback)
Either they can add a hint, or simply press enter to continue (so enter triggers the next pair, the hint won't trigger it, I can do that part easily).


I don't know HOW I can force the loop to wait for the enter.

Right now, I have it functioning in a non-GUI loop. I may have to build it into the GUI mainloop instead?
 
I may have to build it into the GUI mainloop instead?

I guess you could say it that way. I don't think about the mainloop, though. I just think of sitting there are reacting to events. Like, when I'm told that the button got clicked, I change my data and the gui accordingly. Then wait for the next event. Did you go through some tkinter tutorial(s) yet?
 
The problem: Python measures in seconds. So inacurrate.

It uses seconds as unit, but with floating point values. Here's a little test:

Code:
Output:

    1157322 different times recorded in a 1 second interval
    The last ten:
    0.99999237 seconds
    0.99999309 seconds
    0.99999404 seconds
    0.99999475 seconds
    0.99999547 seconds
    0.99999642 seconds
    0.99999714 seconds
    0.99999809 seconds
    0.99999881 seconds
    0.99999976 seconds

Program:

    from time import time
    times = [time()]
    while True:
        t = time()
        if t > times[-1]:
            if t - times[0] > 1:
                break
            times.append(t)
    print(len(times), 'different times recorded in a 1 second interval')
    print('The last ten:')
    for t in times[-10:]:
        print('%.8f seconds' % (t - times[0]))
 
Last edited:
I'm using Tkinter for the GUI.

I have a little experience with Tkinter, but I really don't like it. I much prefer PyQt for GUI stuff these days because I find it's both easier to use and more powerful.

Anyway, since you're already using Tkinter, what you need to do is this. Put all the stuff to do into a function, (I'll call it someFunction here, but please give it a better name). Then somewhere in your program before you call root.mainloop(), add the following line:
Code:
root.bind('<Return>', lambda event: someFunction())

Then whenever the user presses Enter, the program will call someFunction. The lambda bit is just there because bind wants to send event information to the function, but your function won't use that information, so the lambda bit just causes the event information to be left from the function call.

I hope that helps you.
 
Timing update: Sweet, I might have found my clock speed, looks like 4 Mhz. I recorded a million times, and there were duplicates, indicating my new program is faster than the clock. After removing the duplicates, I saw that the times are 1/4194304 of a second apart, and that's *exactly* 2[sup]-22[/sup].

Code:
Output:

    781175 different times recorded in 0.213952 seconds
    The last ten:
    0.21394968 seconds (step: 1/4194304)
    0.21394992 seconds (step: 1/4194304)
    0.21395016 seconds (step: 1/4194304)
    0.21395040 seconds (step: 1/4194304)
    0.21395063 seconds (step: 1/4194304)
    0.21395087 seconds (step: 1/4194304)
    0.21395111 seconds (step: 1/4194304)
    0.21395135 seconds (step: 1/4194304)
    0.21395159 seconds (step: 1/4194304)
    0.21395183 seconds (step: 1/4194304)
    2 ** 22 = 4194304

Program:

    from time import time
    times = sorted(set([time() for _ in range(1000000)]))
    print(len(times), 'different times recorded in %f seconds' % (times[-1] - times[0]))
    print('The last ten:')
    for i in range(-10, 0):
        print('%.8f seconds (step: 1/%d)' % (times[i] - times[0], 1 / (times[i] - times[i-1])))
    print('2 ** 22 =', 2 ** 22)
 
Last edited:
Very funny, Stefan.

The magic number here is 53, as in 53 bits.

Timing update: Sweet, I might have found my clock speed, looks like 4 Mhz. I recorded a million times, and there were duplicates, indicating my new program is faster than the clock. After removing the duplicates, I saw that the times are 1/4194304 of a second apart, and that's *exactly* 2[sup]-22[/sup].
 
Code:
root.bind('<Return>', lambda event: someFunction())

Then whenever the user presses Enter, the program will call someFunction. The lambda bit is just there because bind wants to send event information to the function, but your function won't use that information, so the lambda bit just causes the event information to be left from the function call.

I hope that helps you.

I did get that part, so I can trigger a function when I press enter, but the problem is my current while loop uses an input prompt to "pause" and wait for input (using x = raw_input) This is an easy cheap way to pause a loop. however, it seems I can't do that with stuff that's passive on the GUI (enter key or pressing a button).

The solution I was thinking of was having a global variable that counts up to how many rounds of letter pairs, and rather than using a while loop, just use a function that is triggered when someone presses enter. However, this would have to be nullified when the game isn't running (I suppose counting down to zero would be better, and when the global is zero, don't run the game stuff).

Here's my functioning code without the GUI (Python 3.x)

Code:
import random
import os

#Z = 90
#A = 64

#main function. This should be run automatically at some point
def runGame():
    #checks to see if the letterList text file already exists
    fileCheck = os.path.isfile("letterList.txt")

    #if the file exists, process the file, if not create a new file
    if (fileCheck == True):
        letterList = processFile()
    else:
        letterList = createTable()    
    
    listLength = len(letterList)
    print ("How many pairs would you like to memorize?")
    
    ## TO ADD: ensure that input is a number
    pairCount = int(input())

    #runs the game
    letterList = playIt(pairCount,letterList)
    

#this is the 'game' portion
def playIt(pairCount,letterList):
    ## TO ADD: timer to run
    answer = ""

    #runs the current round until the requested pair count
    for current in range(0, pairCount):
        #random number between 0 and the length of the list
        roundRand = random.randint(0,len(letterList))

        #calls the pair associated with that random number
        pair = letterList[roundRand][0]
        print(pair)

        #displays the hints for the current pair, if they exist
        #TO ADD: Make some settings to make this optional
        for x in range (1, len(letterList[roundRand])):
            print ("Hint " + str(x) + ": " + letterList[roundRand][x])

        #keeps a string containing all of the pairs
        answer += pair
        
        #user can choose to add a hint for future runs, or just proceed
        print ("Type to add a new hint or press Enter to proceed")
        hint = input()
        
        letterList = addHint(letterList, hint, roundRand)
    print ("Type the letter sequence")
    userAnswer = input()
    
    #TO ADD: clear the shell
    os.system('cls' if os.name == 'nt' else 'clear')

    #no longer case sensitive, and takes out spaces
    userAnswer = userAnswer.upper()
    userAnswer = userAnswer.replace(" ", "")

    if (answer == userAnswer):
        print ("Congratulations! You got it right!")
    else:
        print ("Sorry, the answer was " + answer + ", but you typed " + userAnswer + ".")
    return letterList

#function adds a hint element. This can be done an unlimited number of times, so each pair can have multiple hints
def addHint(letterList, hint, roundRand):
    if (hint != ""):
        letterList[roundRand].append(hint)
    writeFile(letterList)
    return letterList
    


def createTable():
    letterList = []
    #shift to the upper case letter index
    shift = 65

    #runs through the first letter
    for firstLet in range(0, 24):
        #nexted for loop to pair the first letter with a second letter
        for secondLet in range(0,24):
            #if statement ensure no double same letter pairs (i.e. "AA")
            if (firstLet != secondLet):
                letPair = (chr(firstLet+shift)+chr(secondLet+shift))
                #appends a list so more information can be added to a letter pair
                letterList.append([letPair])
    writeFile(letterList)
    return letterList


def writeFile(letterList):
    file = open("letterList.txt", "w")
    for line in range(0, len(letterList)):
        for element in range(0, len(letterList[line])):
            file.write(letterList[line][element])
            #adds a ", " if it's not the final element of the list
            if (element+1 < len(letterList[line])):
                file.write(", ")
        file.write("\n")
    file.close()

def processFile():
    letterList = []
    #loads the file into a list of the lines
    with open("letterList.txt") as f:
        lines = f.readlines()
    #runs each line, cutting off the line break, and splitting by the comma space.
    for line in range(0, len(lines)):
        raw = lines[line].rstrip()
        rawList = raw.split(", ")
        letterList.append(rawList)
    return letterList
 
Darn, does that mean 12 years ago I would've gotten twice the resolution I got now? Is my computer getting worse?

You probably wouldn't have 12 years ago, but if you tell your computer it is
12 years ago, you may get twice the resolution now. Let's repeat the
experiment in 23 years and see what happens; maybe you'll get even less
resolution then.
 
You probably wouldn't have 12 years ago, but if you tell your computer it is
12 years ago, you may get twice the resolution now.

Oh right. I just tried it and it kinda worked :) (I say "kinda" because now my program's speed is the bottleneck and it gets the double resolution only occasionally)

Do you know how to find out the real resolution? Or is it just my CPU speed, e.g., 1.6 GHz? I vaguely remember playing with the system clock in assembler in the 1990s and thought it's separate from the CPU, but I have no idea about today and couldn't find anything.

I tried again in Go, as that's faster and offers nanoseconds as integer. I get around 16.8 million distinct times per second and no duplicates, so apparently the program is the bottleneck and the clock is still faster.

Code:
10000000 distinct times in 593.95314ms
16.836345 million distinct times per second
Code:
package main

import (
	"fmt"
	"time"
)

func main() {
	t0 := time.Now()
	t := t0.Nanosecond()
	ctr := 0
	for i := 0; i < 10000000; i++ {
		tmp := time.Now().Nanosecond()
		if tmp != t {
			t = tmp
			ctr++
		}
	}
	elapsed := time.Since(t0)
	fmt.Println(ctr, "distinct times in", elapsed)
	fmt.Printf("%.6f million distinct times per second\n", float64(ctr)/elapsed.Seconds()/1000000)
}
 
Last edited:
I have a little experience with Tkinter, but I really don't like it. I much prefer PyQt for GUI stuff these days because I find it's both easier to use and more powerful.

Anyway, since you're already using Tkinter, what you need to do is this. Put all the stuff to do into a function, (I'll call it someFunction here, but please give it a better name). Then somewhere in your program before you call root.mainloop(), add the following line:
Code:
root.bind('<Return>', lambda event: someFunction())

Then whenever the user presses Enter, the program will call someFunction. The lambda bit is just there because bind wants to send event information to the function, but your function won't use that information, so the lambda bit just causes the event information to be left from the function call.

I hope that helps you.

I'm reading this more, and trying to figure it out. I'm wondering if I could do this in a recursive loop, where it starts with x = how many pairs, runs through the function and every time enter is pressed, it lowers x by 1 until it hits zero where the function will no longer work.
 
Ugh, so I'm trying to call a variable from a different function. I know that's kind of like a "global variable" and those are evil, apprently, but I have one function establishing my GUI, and then I want other functions to be able to modify lables on my GUI. This won't work with calls and returns, because the functions need to be activated by buttons in the GUI, and buttons don't have a return, as far as I know.
 
It's a simple program. Just use the global variables...

Yes, global variables are evil, and you should avoid them in general, but the whole philosophy of python is to keep it simple and not overcomplicate matters. The better approach would be to use OOP and have your functions manipulate the attributes of objects. Then you'd only have to pass objects to functions, and the functions wouldn't need to return anything. But since your program is going to probably be a single file of a couple hundred lines, that's a lot of extra effort and complication just to avoid using global variables.
 
It's a simple program. Just use the global variables...

Yes, global variables are evil, and you should avoid them in general, but the whole philosophy of python is to keep it simple and not overcomplicate matters. The better approach would be to use OOP and have your functions manipulate the attributes of objects. Then you'd only have to pass objects to functions, and the functions wouldn't need to return anything. But since your program is going to probably be a single file of a couple hundred lines, that's a lot of extra effort and complication just to avoid using global variables.

I'm starting to figure out classes, I'm rewriting the program to fit in that environment. It's actually working pretty well. Thanks for your help!
 
I finished! I just need to get the layout looking a bit nicer, but it seems to be fully functional and not too breakable. Feel free to try out my code, and let me know how it works for you. (It creates a notepad in the same folder it's in, so make sure you can keep track of that).

Code:
from tkinter import *
import random
import os

class Application(Frame):

    def __init__(self, master):

        Frame.__init__(self,master)
        self.grid()
        self.create_widgets()
        self.variables()
        self.letterListGrab()
        root.bind('<Return>', self.rounds)
        self.countdown()
        


    def countdown(self):
        if (self.timerStart == True):
            self.label.configure(text= round(self.remaining, 2))
            self.remaining = self.remaining + .01
        self.after(10, self.countdown)
            
    def create_widgets(self):
        self.pair = Label(self, text="Helvetica", font=("Helvetica", 30))
        self.pair["text"] = " "
        self.pair.grid(row=0, column=0, columnspan=3, pady=30, padx=30)

        self.hintLab = Label(self)
        self.hintLab["text"] = ""
        self.hintLab.grid(row=1, columnspan=3)

        self.instr0 = Label(self)
        self.instr0["text"] = "How many pairs would you like to memorize:"
        self.instr0.grid(row=2, column=0, columnspan=3)
     
        self.countEntry = Entry(self)
        self.countEntry["text"] = "5"
        self.countEntry.grid(row=3, column=0, columnspan=2)

        self.start = Button(self)
        self.start["text"] = "Start"
        self.start["command"] = self.runGame
        self.start.grid(row=3, column=2)
        
        self.result = Label(self, wraplength=280)
        self.result["text"] = ""
        self.result.grid(row=4, column=0, columnspan=3)
        
        self.label = Label(self, text="Helvetica", font=("Helvetica", 20))
        self.label["text"] = "0.00"
        self.label.grid(row=5, columnspan=3, pady=30, padx=30)



    def variables(self):
        self.pairCount = 5
        self.tally = 0
        self.answer = ""
        self.remaining = 0.0
        self.timerStart = False

    def runGame(self):
        self.timerStart = True
        
        self.pairCount = int(self.countEntry.get())

        self.instr0.destroy()
        self.start.destroy()
        self.countEntry.destroy()

        self.instr1 = Label(self)
        self.instr1["text"] = "Press Enter to Continue"
        self.instr1.grid(row=2, column=0, columnspan=3)

        self.hintry = Entry(self)
        self.hintry["text"] = ""
        self.hintry.grid(row=3, column=0, columnspan=2)

        self.hintSub = Button(self)
        self.hintSub["text"] = "Add Hint"
        self.hintSub["command"] = self.addHint
        self.hintSub.grid(row=3, column=2)


        self.hintLab["text"] = ""
        self.pairNum = randPair(self.letterList)
        self.curPair = str(self.letterList[self.pairNum][0])
        self.answer += self.curPair
        self.pair["text"] = self.curPair
        for x in range (1, len(self.letterList[self.pairNum])):
            self.hintLab["text"] = "Hint " + str(x) + ": " + self.letterList[self.pairNum][x]
        self.tally += 1

    def rounds(self, event):
        self.hintLab["text"] = ""
        if (self.tally == 0):
            return  
        elif (self.tally < self.pairCount):
            self.pairNum = randPair(self.letterList)
            self.curPair = str(self.letterList[self.pairNum][0])
            self.answer += self.curPair
            self.pair["text"] = self.curPair
            for x in range (1, len(self.letterList[self.pairNum])):
                self.hintLab["text"] += "Hint " + str(x) + ": " + self.letterList[self.pairNum][x] + "\n"
            self.tally += 1
        elif (self.tally == self.pairCount):
            self.checkAnswer()
            self.tally += 1
        else:
            return
        
    def checkAnswer(self):
        self.start.destroy()
        self.pair["text"] = " "
        self.instr1.destroy()
        self.hintry.destroy()
        self.hintSub.destroy()

        self.ansPrompt = Label(self)
        self.ansPrompt["text"] = "Type in the answer:"
        self.ansPrompt.grid(row=2, column=0, columnspan=3)

        self.ansEntry = Entry(self)
        self.ansEntry["text"] = ""
        self.ansEntry.grid(row=3, column=0, columnspan=2)

        self.submit = Button(self)
        self.submit["text"] = "Submit"
        self.submit["command"] = self.compare
        self.submit.grid(row=3, column=2)    
        

    def compare(self):


        
        self.totPairs = int(len(self.answer)/2)
        self.timerStart = False
        self.userAnswer = self.ansEntry.get()
        self.userAnswer = self.userAnswer.upper()
        self.userAnswer = self.userAnswer.replace(" ", "")

        self.ansPrompt.destroy()
        self.ansEntry.destroy()
        self.submit.destroy()
        self.resetBut = Button(self)
        self.resetBut["text"] = "Reset"
        self.resetBut["command"] = self.reset
        self.resetBut.grid(row=3, column=2)
        
        if (self.userAnswer == self.answer):
           self.result["text"] = "Congratulations! You memorized " + str(self.totPairs) + " pairs."
        else:
            self.result["text"] = "Sorry, the answer was " + self.answer + ", but you typed " + self.userAnswer + "."

        self.submit.destroy()

        
    
    def reset(self):
        self.destroy()
        Application(root)
            

    def letterListGrab(self):
        #checks to see if the letterList text file already exists
        fileCheck = os.path.isfile("letterList.txt")

        #if the file exists, process the file, if not create a new file
        if (fileCheck == True):
            self.letterList = processFile()
        else:
            self.letterList = createTable()

    def addHint(self):
        self.addHint = str(self.hintry.get())
        if (self.addHint != ""):
            self.letterList[self.pairNum].append(self.addHint)
            writeFile(self.letterList)       

def randPair(letterList):
    roundRand = random.randint(0,len(letterList))
    return roundRand


def createTable():
    letterList = []
    #shift to the upper case letter index
    shift = 65

    #runs through the first letter
    for firstLet in range(0, 24):
        #nexted for loop to pair the first letter with a second letter
        for secondLet in range(0,24):
            #if statement ensure no double same letter pairs (i.e. "AA")
            if (firstLet != secondLet):
                letPair = (chr(firstLet+shift)+chr(secondLet+shift))
                #appends a list so more information can be added to a letter pair
                letterList.append([letPair])
    writeFile(letterList)
    return letterList


def writeFile(letterList):
    file = open("letterList.txt", "w")
    for line in range(0, len(letterList)):
        for element in range(0, len(letterList[line])):
            file.write(letterList[line][element])
            #adds a ", " if it's not the final element of the list
            if (element+1 < len(letterList[line])):
                file.write(", ")
        file.write("\n")
    file.close()

def processFile():
    letterList = []
    #loads the file into a list of the lines
    with open("letterList.txt") as f:
        lines = f.readlines()
    #runs each line, cutting off the line break, and splitting by the comma space.
    for line in range(0, len(lines)):
        raw = lines[line].rstrip()
        rawList = raw.split(", ")
        letterList.append(rawList)
    return letterList


root = Tk()
root.title("Letter Pair Game")
root.geometry("300x300")

app = Application(root)

root.mainloop()
 
Back
Top