This is a submission for Weekend Challenge: Passion Edition
What I Built
For this weekend's challenge, I built ArokoOro a desktop Scrabble-style word game engineered to help users learn, practice, and preserve indigenous African languages (like Yoruba and Fula). The application challenges players to unscramble words, offering hints and scoring mechanics.
Passion is often about preserving where we come from and building tools that make learning engaging. I have a deep interest in gamification strategies and using interactive problem-solving to build engaging software. Indigenous languages carry so much history, but learning them from a textbook can be dry. I wanted to fuse the addictive loop of a word puzzle with cutting-edge Text-to-Speech technology. The passion here is cultural preservation through the lens of modern game development.
Demo
Here is a look at the ArokoOro interface in action!
## Code
import os
import threading
import pygame
from tkinter import *
from random import choice, shuffle
from dotenv import load_dotenv
from elevenlabs.client import ElevenLabs
# --- API & Audio Setup ---
load_dotenv()
eleven_client = ElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY"))
pygame.mixer.init()
def play_word_audio(spoken_word):
"""Fetches the audio from ElevenLabs and plays it in the background."""
filename = "temp_word.mp3"
try:
audio_stream = eleven_client.text_to_speech.convert(
voice_id="pNInz6obpgDQGcFmaJgB",
output_format="mp3_44100_128",
text=spoken_word,
model_id="eleven_multilingual_v2"
)
with open(filename, "wb") as f:
for chunk in audio_stream:
if chunk:
f.write(chunk)
pygame.mixer.music.load(filename)
pygame.mixer.music.play()
except Exception as e:
print(f"ElevenLabs Error: {e}")
# --- GUI Setup ---
root = Tk()
root.title('MajasWotdFlip')
root.geometry("600x400+-1900+100")
my_label = Label(root, text="", font=("Helvetica", 48))
my_label.pack(pady=20)
def shuffler():
hint_label.config(text='')
global hint_count
hint_count = 0
entry_answer.delete(0, END)
answer_label.config(text='')
states = ['Bawo ni?', 'No gonda?', 'Ẹ káàárọ̀ ','Jam tan','Ẹ káàsán', 'Jam wodi', 'Ẹ káalẹ́ ', 'Jam na foti', 'kúùjọkòó', 'No foti', 'Ẹ sé' , 'Ajaraama', 'Jọ̀ọ́ ', 'Jam aada', 'Mo ní fẹ́ ', 'Mi yewi', 'Báwo ni orúkọ rẹ', 'No honto', 'Orúkọ mi ni', 'Mi honto', 'Ọmọ', 'Baajo', 'Bàbá', 'Baaba', 'Màmá', 'Ndewo', 'Ẹ̀gbọ́n', 'Sedyo', 'Àbúrò', 'Gorko', 'Ọkùnrin', 'Naado', 'Obìnrin', 'Debbo', 'Ile', 'Wuro', 'Ilé ìwé', 'Daande', 'Ounje', 'Lewru', 'Òrò', 'Konngi', 'Omi', 'Ndiyam', 'Ẹbọ', 'Seeri', 'Ẹyin', 'Noowo', 'Isu', 'Ndiiw', 'Ẹran', 'Leela', 'Àṣọ', 'Daani', 'Ijò' , 'Daali', 'Igbẹ́ ', 'Seero', 'Ìgbẹ́ ', 'Leesi', 'Ọ̀dọ', 'Lawol', 'Ọ̀yà', 'Jamu', 'Moun', 'Òṣùpá', 'Lewru', 'Òun tímo fẹ́ ', 'Mi yewi', 'Ìròyìn', 'Hareey','Ọ̀rọ̀ tí ó ṣẹlẹ̀', 'Gorko', 'gbẹ́sẹ̀ ', 'Fott', 'Ète', 'Pulaar', 'Orí', 'Honto', 'ẹranko', 'Naado leela', 'Ẹṣin', 'Hiima', 'Iná' , 'Fire', 'Òtútù', 'Ngella', 'Òjò', 'Ruwa', 'Ìrìnàjò', 'Yellagol', 'Ẹ̀gbọ́n', 'Noddle', 'Ilé Ìgbéyàwó', 'Daande', 'marwo', 'Ìrẹ́jẹ', 'Lehru', 'Ẹsẹ́ ', 'Wale', 'Ìsọ́ra', 'Jam', 'Ẹ́lẹ́dàa', 'Mooso', 'Iwọ', 'Aadi', 'Ẹgbọ́n tímo fẹ́', 'Gorko yewi', 'Olúwa', 'Allah', 'Ilé iṣẹ́ ', 'Taare', 'Àṣá', 'Konngi', 'Ìjọba', 'Beegi', 'Ìlú', 'Dekkere', 'Ẹwá',' Deena', 'Oríkì', 'Wale', 'Àkọ́kọ́ ', 'Fettunde', 'Àtijọ', 'Daaɗi', 'Ìjẹ́wẹ̀ ', 'Luudo', 'Ojú', 'Wana', 'Ọgbọ́n', 'Siina', 'Ọgbọ́n tímo ní', 'Mi siina', 'Ìlu', 'Wuro', 'Ìfẹ́ ', 'Habbere', 'Ọ̀rẹ́ ', 'Maro', 'Òun tímo nse', 'Mi noggi', 'Orí ẹ̀yìn', 'Wani laawol', 'Ilé Ìgbéyàwó', 'Daande marwo', 'Igbéyàwo', 'Ndoorti', 'Ìjọba', 'Beegi' , 'Ìlú', 'Wuro', 'Àìní', 'Weeti', 'Ọkùnrin tímo ní', 'Mi naado', 'Obìnrin tímo ní', 'Mi debbo', 'Èda', 'Mooso', 'Òun tímo ní', 'Miɗo', 'Igi', 'Ndow', 'Ìfẹ́ ', 'Habbere', 'Ẹ̀jẹ́ ', 'Dentu', 'Orí', 'Wano', 'Ẹnu', 'Pene', 'Orí', 'Wana', 'Ẹ̀gbọ́n', 'tímo fẹ́ ', 'Gorko yewi', 'Ile', 'Wuro', 'Ọ̀dọ̀ ', 'Lewol', 'Òṣùpá', 'Lewru', 'Iná', 'Leela', 'Ìrìnàjò','Yellagol', 'Ẹ̀ṣẹ́ ', 'Wale', 'Oregon', 'California', 'Ohio', 'Nebraska', 'Colorado', 'Michigan', 'Massachusetts', 'Florida', 'Texas', 'Oklahoma', 'Hawaii', 'Alaska', 'Utah', 'New Mexico', 'North Dakota', 'South Dakota', 'West Virginia', 'Virginia', 'New Jersey', 'Minnesota', 'Illinois', 'Indiana', 'Kentucky', 'Tennessee', 'Georgia', 'Alabama', 'Mississippi', 'North Carolina', 'South Carolina', 'Maine', 'Vermont', 'New Hampshire', 'Connecticut', 'Rhode Island', 'Wyoming', 'Montana', 'Kansas', 'Iowa', 'Pennsylvania', 'Maryland', 'Missouri', 'Arizona', 'Nevada', 'New York', 'Wisconsin', 'Delaware', 'Idaho', 'Arkansas', 'Louisiana']
global word
word = choice(states)
break_apart_word = list(word)
shuffle(break_apart_word)
global shuffled_word
shuffled_word = ''
for letter in break_apart_word:
shuffled_word += letter
my_label.config(text=shuffled_word)
def answer():
if word == entry_answer.get():
answer_label.config(text="Correct!!")
threading.Thread(target=play_word_audio, args=(word,), daemon=True).start()
else:
answer_label.config(text="Incorrect!!")
global hint_count
hint_count = 0
def hint(count):
global hint_count
hint_count = count
word_length = len(word)
if count < word_length:
hint_label.config(text=f'{hint_label["text"]} {word[count]}')
hint_count +=1
entry_answer = Entry(root, font=("Helvetica", 24))
entry_answer.pack(pady=20)
button_frame = Frame(root)
button_frame.pack(pady=20)
answer_button = Button(button_frame, text="Answer", command=answer)
answer_button.grid(row=0, column=0, padx=10)
my_button = Button(button_frame, text="Pick Another Word", command=shuffler)
my_button.grid(row=0, column=1, padx=10)
hint_button = Button(button_frame, text="Hint", command=lambda: hint(hint_count))
hint_button.grid(row=0, column=2, padx=10)
answer_label = Label(root, text='', font=("Helvetica", 18))
answer_label.pack(pady=20)
hint_label = Label(root, text='', font=("Helvetica", 18))
hint_label.pack(pady=10)
shuffler()
root.mainloop() -->
How I Built It
I built the game engine using Python, leveraging Tkinter for the lightweight graphical user interface and Pygame to manage the audio mixer and background thread playback.
To give the game its voice, I implemented the ElevenLabs Python SDK. Specifically, I used their multilingual_v2 model. This was a crucial technical decision because the model handles the subtle inflections of non-English words remarkably well, which is essential when the goal is teaching indigenous language pronunciation.
I love building in Linux environments, but developing this on my Acer Chromebook's Linux container (Crostini) threw some serious curveballs at me this weekend. I wanted to share the raw debugging journey because the errors were just as educational as the code itself!
The PEP 668 Guardrail: When I first tried to pip install elevenlabs, Linux hit me with the dreaded externally-managed-environment error.
The Deep Dive: At first, I thought my Python installation was broken. It turns out, this is a built-in safety feature (PEP 668) adopted by many Linux distributions, including the Debian container running on my ChromeOS. It is designed to prevent pip from accidentally overwriting or breaking core system-level Python tools.
python3 -m venv .venv
source .venv/bin/activate
The Fix: I had to embrace the sandbox. I spun up a virtual environment (python3 -m venv .venv) to isolate my packages and keep my system's global Python installation safe. By activating the .venv, I redirected my shell to use the isolated binaries. After that, installing the ElevenLabs SDK, Pygame, and Python-Dotenv was completely frictionless.
The Sneaky 401 Unauthorized Error: Once the environment was set up, the code ran, but the audio failed, spitting out a status_code: 401.
I had written the code to fetch my API key securely using os.getenv("ELEVENLABS_API_KEY"). However, I had completely forgotten to actually build the vault it was trying to read from! Because the environment variable didn't exist, Python was sending a blank payload to the ElevenLabs server, and their API was rightfully slamming the door in my face.
The Fix: I had hardcoded my environment variable call but forgot to actually build the .env vault! Moving the API key into a dedicated .env file and loading it with python-dotenv instantly fixed the authentication handshake.I created a dedicated .env file at the root of my project to store the key. A major lesson learned here was strict formatting: the .env file requires zero spaces around the equals sign and absolutely no quotation marks around the key. I also immediately added a .gitignore file to ensure my .env vault wouldn't be accidentally pushed to GitHub. Once python-dotenv loaded the hidden file, the authentication handshake succeeded instantly.
The Crostini Network Drop: Just when I thought I was at the finish line, I got hit with [Errno -3] Temporary failure in name resolution.
The Fix: This was a classic Chromebook Linux container quirk. The host device had Wi-Fi, but the container's DNS resolution had temporarily desynced after waking up. A quick container restart and a ping google.com to verify the connection, and the game finally spoke!
Maximizing ElevenLabs: The Future Roadmap
While the current integration is incredibly satisfying, the ElevenLabs API offers several advanced features that I plan to implement next to make this application even more robust for cultural preservation:
Custom Voice Design for Authentic Dialects
Right now, the game uses a default voice profile. Moving forward, I plan to utilize ElevenLabs' Voice Design and Voice Cloning features. By providing high-quality audio samples of native speakers or elders, I can generate a custom voice model that captures the exact regional dialect, cadence, and warmth of the indigenous language, rather than relying on a generic AI tone.Pronunciation Dictionaries for Tonal Languages
Many African languages, such as Yoruba, are highly tonal—meaning a slight change in pitch completely alters the meaning of a word. If the AI slightly mispronounces a specific vowel grouping, I plan to implement ElevenLabs' Pronunciation Dictionaries. This will allow me to feed the API specific phonetic spellings (using the International Phonetic Alphabet - IPA) to force the engine to emphasize the exact tonal markings of complex indigenous words.Direct Audio Streaming for Zero Latency
Currently, the application downloads the generated audio chunk, saves it to a local temp_word.mp3 file, and then triggers Pygame to read that file. To make the UI feel lightning-fast, I want to optimize the architecture to stream the ElevenLabs audio bytes directly into Pygame's memory buffer. This will eliminate the disk read/write step entirely, allowing the audio to play the exact millisecond the player hits the "Answer" button.
The Program runs with a welcome inBuilt Note " Bawo Ni " meaning "How are you? " Thanks to #ElevenLabs !! Imagine the possiblilties!
#Demo
Prize Categories
Best Use of ElevenLabs
Thanks to @chief_desiign for collaboration.




Top comments (0)