I want to build a spell bee app on Streamlit.
It will play a word when a button is clicked, wait for the word to be spelled in a text box, and then play another word and repeat the process as long as the answers are correct.
I wanted to have a recursive function that will call itself if the answer is correct, but due to Streamlit’s behaviour of rerunning the entire script when a button is pressed, I am having troubles implementing the functionality.
Here is the python script which works perfectly as a CLI app:
import json
import random
import os
from pprint import PrettyPrinter
from PyDictionary import PyDictionary
from pathlib import Path
from gtts import gTTS
from playsound import playsound
pp = PrettyPrinter(compact=True, sort_dicts=True, width=100)
dictionary = PyDictionary()
def play(word: str, slow: bool = False) -> None:
"""Pronounces `word`
Args:
word (str): Word to be pronounced
slow (bool, optional): Pronounces `word` slowly. Defaults to False.
"""
t1 = gTTS(word, slow=slow)
if os.path.exists("audio.mp3"):
os.remove("audio.mp3")
t1.save("audio.mp3")
return playsound("audio.mp3")
# --------- RECURSIVE FUNCTION ---------
def recurse(words: list, score: int = 0) -> None:
"""Recursive scoring function.
Args:
words (list): List of words to choose from
score (int, optional): Intermediate score. Defaults to 0.
"""
word = random.choice(words)
words.remove(word)
play(word)
answer = (
input(
'Enter spelling (type "define" to get the definition, or "repeat" to repeat the pronounciation slowly): '
)
.lower()
.strip()
)
while answer in ["define", "repeat"]:
if answer == "define":
if meaning := dictionary.meaning(word, disable_errors=True):
pp.pprint(meaning)
else:
print("Definition not available")
else:
play(word, slow=True)
answer = (
input('Enter spelling (type "repeat" to repeat slowly): ').lower().strip()
)
if answer == word:
score += 1
print(f"Correct! Score: {score}")
recurse(words, score)
else:
print(f'Wrong answer. The correct answer is "{word}"\nFinal score: {score}')
# ---------- RUN ---------
with open("words.txt", "r") as f:
words = json.load(f)
recurse(words)
Below is a barebones implementation of the above in Streamlit:
import json
import random
import os
from pprint import PrettyPrinter
from pathlib import Path
import sys
from gtts import gTTS
from playsound import playsound
pp = PrettyPrinter(compact=True, sort_dicts=True, width=100)
import streamlit as st
# --- PLAY WORD ----
def play(word: str, slow: bool = False) -> None:
"""Pronounces `word`
Args:
word (str): Word to be pronounced
slow (bool, optional): Pronounces `word` slowly. Defaults to False.
"""
t1 = gTTS(word, slow=slow)
if os.path.exists("audio.mp3"):
os.remove("audio.mp3")
t1.save("audio.mp3")
return playsound("audio.mp3")
# --------- RECURSIVE FUNCTION ---------
def recurse(words: list, score: int = 0) -> None:
"""Recursive scoring function.
Args:
words (list): List of words to choose from
score (int, optional): Intermediate score. Defaults to 0.
"""
word = random.choice(words)
words.remove(word)
play(word)
st.write(word)
if answer := st.text_input("Enter spelling: ").lower().strip():
if answer == word:
score += 1
st.success(f"Correct! Score: {score}")
recurse(words, score)
else:
st.error(
f'Wrong answer. The correct answer is "{word}"\nFinal score: {score}'
)
sys.exit()
# ---------- RUN ---------
import contextlib
with open("words.txt", "r") as f:
words = json.load(f)
with contextlib.suppress(SystemExit):
if st.button("Hear word"):
recurse(words)
As soon as text is entered in the text box, the whole app resets.
Can this even be implemented in Streamlit? I tried using session state to save words already displayed to have an idea of the current state, but no luck.
Thanks in advance!