<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged weekendchallenge</title>
<link>https://coderlegion.com/tag/weekendchallenge</link>
<description>Powered by Question2Answer</description>
<item>
<title>I built my girlfriend a local Gemma football analyst that won't make up the stats</title>
<link>https://coderlegion.com/32607/i-built-my-girlfriend-a-local-gemma-football-analyst-that-wont-make-up-the-stats</link>
<description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/challenges/hacktoberfest-weekend-2026-10-01&quot;&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-i-built&quot;&gt;What I built&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;MatchMind&lt;/strong&gt; is a local AI analyst for Brazilian football, built for &lt;strong&gt;Bruna Boaventura, my girlfriend&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Bruna had a hard time finding Brazilian clubs' results and historical data. So I built her one place to look things up — and a way to just ask.&lt;/p&gt;
&lt;p&gt;MatchMind puts the numbers together and lets her ask questions in Portuguese to an AI that runs &lt;strong&gt;on her own computer&lt;/strong&gt;. You pick a club and get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;Brasileirão 2026 table&lt;/strong&gt;, computed from the season's results, with all 20 crests;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;recent form&lt;/strong&gt; and the &lt;strong&gt;last three matches&lt;/strong&gt; with possession, shots, corners, fouls, &lt;strong&gt;goal scorers, cards, stadium and referee&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;&quot;Ver escalações&quot;&lt;/strong&gt; button that loads both line-ups: formation, coach, minutes and player ratings;&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;squad&lt;/strong&gt; with appearances, goals, assists and ratings;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;20+ years of Série A history&lt;/strong&gt; (2003–2024): titles, season-by-season position and coach, the club's top scorers, discipline, and head-to-head records with the scorers of the last meetings;&lt;/li&gt;
&lt;li&gt;a chat with &lt;strong&gt;Gemma 3 4B&lt;/strong&gt; running locally through Ollama, which answers only from that data and separates &lt;strong&gt;FATO&lt;/strong&gt; (fact) from &lt;strong&gt;INTERPRETAÇÃO&lt;/strong&gt; (interpretation).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The rule behind everything: &lt;strong&gt;the numbers come from data or from deterministic code, never from the model.&lt;/strong&gt; If something is missing, MatchMind says &quot;Indisponível&quot; instead of guessing.&lt;/p&gt;
&lt;h3 id=&quot;demo&quot;&gt;Demo&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/Darrkkens/matchmind/main/docs/screenshots/landing.png&quot; alt=&quot;Landing page with the Brasileirão table&quot;&gt;&lt;/p&gt;
&lt;p&gt;A real answer generated locally by Gemma 3 4B (CPU only):&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/Darrkkens/matchmind/main/docs/screenshots/chat.png&quot; alt=&quot;Gemma answering where Palmeiras is in the table and how many titles it has&quot;&gt;&lt;/p&gt;
&lt;p&gt;Line-ups for a match, loaded on demand:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/Darrkkens/matchmind/main/docs/screenshots/lineups.png&quot; alt=&quot;Line-ups dialog&quot;&gt;&lt;/p&gt;
&lt;p&gt;Série A history:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://raw.githubusercontent.com/Darrkkens/matchmind/main/docs/screenshots/history.png&quot; alt=&quot;Série A history panel&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;what-bruna-said&quot;&gt;What Bruna said&lt;/h4&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;&quot;Interessante, pois traz dados históricos de 2003 até hoje, contendo gols e estatísticas.&quot;&lt;/em&gt;&lt;br&gt;
(&quot;Interesting, because it brings historical data from 2003 until today, with goals and statistics.&quot;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That was exactly the gap she had: the history is what made it useful for her. (To be precise about coverage: the historical dataset runs from 2003 to 2024, and the current 2026 season comes from a separate live source.)&lt;/p&gt;
&lt;h3 id=&quot;code&quot;&gt;Code&lt;/h3&gt;
&lt;p&gt;{% github Darrkkens/matchmind %}&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Backend:&lt;/strong&gt; Go (standard library + &lt;code&gt;pgx&lt;/code&gt; for an optional PostgreSQL cache).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Frontend:&lt;/strong&gt; Vue 3 + TypeScript, interface in Brazilian Portuguese.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI:&lt;/strong&gt; Gemma 3 4B through Ollama's &lt;code&gt;/api/chat&lt;/code&gt; with structured JSON output.&lt;/li&gt;
&lt;li&gt;MIT license, CI with &lt;code&gt;go vet&lt;/code&gt;, &lt;code&gt;go test -race&lt;/code&gt; and the frontend build, plus contributing guide and issue templates.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Quick start: &lt;code&gt;ollama pull gemma3:4b&lt;/code&gt;, then &lt;code&gt;go run ./cmd/server&lt;/code&gt; in &lt;code&gt;backend/&lt;/code&gt; and &lt;code&gt;npm run dev&lt;/code&gt; in &lt;code&gt;frontend/&lt;/code&gt;. No account and no API key are needed.&lt;/p&gt;
&lt;h3 id=&quot;how-i-built-it&quot;&gt;How I built it&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;OpenFootball (results, CC0)  ──┐
Série A history CSVs (GPL-2.0) ┼─► Go API ─► deterministic stats, table, form
Optional stats API + cache  ───┘      │
                                      ▼
                     compact JSON context for this question
                                      ▼
                        Gemma 3 4B via Ollama (local)
                                      ▼
            Go validates the JSON and adds FATO / INTERPRETAÇÃO
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Data I can trust.&lt;/strong&gt; Results come from &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/openfootball/football.json&quot;&gt;OpenFootball&lt;/a&gt; (CC0). The 2003–2024 history comes from Adão Duque's &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/adaoduque/Brasileirao_Dataset&quot;&gt;Brasileirão dataset&lt;/a&gt;, downloaded at runtime. I computed every season's final table from the results, and the champions match the official list for all 22 seasons; the scorers file matches the final score in 100% of matches since 2015. Where the source has gaps (its 2016 and 2024 statistics are mostly zeros), MatchMind drops those averages instead of showing fake numbers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Linking sources without guessing.&lt;/strong&gt; Per-match statistics, scorers and line-ups come from an optional API. A match only receives that data when round, opponent, home/away side and final score all agree with OpenFootball. Line-ups abbreviate names (&quot;G. Gómez&quot;) while player stats use full names (&quot;Gustavo Gómez&quot;), so I wrote a matcher that understands initials. It links 95% of starters; the rest (nickname vs. legal name) are shown without numbers rather than guessed. Every API response is cached — in PostgreSQL if you run &lt;code&gt;docker compose up&lt;/code&gt; — so a finished match costs one request, ever.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Grounding the model.&lt;/strong&gt; The browser never sends context. For each question, Go rebuilds the club snapshot and sends Gemma a JSON envelope that keeps the data separate from the question and is explicitly treated as untrusted. Gemma returns &lt;code&gt;facts&lt;/code&gt;, &lt;code&gt;interpretation&lt;/code&gt; and &lt;code&gt;sources_used&lt;/code&gt;; Go validates the structure and the allowed source names and adds the labels itself.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What real testing taught me.&lt;/strong&gt; Mocked tests passed, but running the real model exposed two problems:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Too slow.&lt;/strong&gt; As I added history, line-ups and cards, a question on a CPU-only machine went past my 240-second timeout. The fix was to make the context depend on the question: the full table, the season history, the head-to-head records, the squad or the cards are only sent when the question is about them. The context for &quot;where are we in the table and how many titles?&quot; dropped from about 11,400 to 5,400 characters, and the answer came back in about three minutes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Technically true, actually wrong.&lt;/strong&gt; Gemma answered that Palmeiras &quot;has 4 Brazilian titles&quot;. That's the count in the dataset, which only covers 2003–2024; Palmeiras has more. The prompt already said so, but a 4B model doesn't always follow a sentence buried in a prompt. So I renamed the field the model sees to &lt;code&gt;serie_a_titles_2003_2024_only&lt;/code&gt;. The next answer said &quot;4 Série A titles between 2003 and 2024&quot; — the answer you see in the screenshot above. When a rule really matters (like flagging sample data from an API test key), Go appends the warning itself instead of trusting the model.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;why-does-open-matter-&quot;&gt;Why does open matter?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It runs on her machine and costs nothing.&lt;/strong&gt; After &lt;code&gt;ollama pull gemma3:4b&lt;/code&gt;, there's no per-question fee, no AI account and no key to protect. The football data sources used by default are open too.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Her questions stay local.&lt;/strong&gt; With the default settings, the questions and the club data are processed on the same computer. Nothing goes to a model provider.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I control the whole stack.&lt;/strong&gt; When the model was too slow, I didn't wait for a vendor: I changed what the model sees. When it phrased something misleadingly, I changed the data contract. I can swap in &lt;code&gt;gemma3:12b&lt;/code&gt; on a stronger machine with one environment variable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The trade-off is honest.&lt;/strong&gt; A 4B model on a CPU is slow (1.5–3 minutes per answer) and can still omit something that is in its context. That's why the numbers on screen come from code, not from the model, and why every answer says which data it used. A big hosted model would answer faster, but it would cost money per question, need an account, and send everything off the machine — for a personal football tool, those trade-offs aren't worth it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;ai-assistance&quot;&gt;AI assistance&lt;/h3&gt;
&lt;p&gt;I built MatchMind with the help of an AI coding assistant (Claude Code), which I directed, reviewed and tested throughout. All data, screenshots and the model answer shown above come from running the real application.&lt;/p&gt;
&lt;h3 id=&quot;prize-categories&quot;&gt;Prize categories&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Best Use of Gemma&lt;/strong&gt; — Gemma 3 4B runs locally through Ollama as the analyst at the core of MatchMind. The project shapes its context per question, constrains it with structured JSON output, validates every answer in Go, and documents where the model still fails.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32607/i-built-my-girlfriend-a-local-gemma-football-analyst-that-wont-make-up-the-stats</guid>
<pubDate>Tue, 27 Oct 2026 02:20:22 +0000</pubDate>
</item>
<item>
<title>Interview Buddy: Private Interview Practice for a Friend’s First Developer Job</title>
<link>https://coderlegion.com/32468/interview-buddy-private-interview-practice-for-a-friends-first-developer-job</link>
<description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/challenges/hacktoberfest-weekend-2026-10-01&quot;&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-i-built-1&quot;&gt;What I Built&lt;/h3&gt;
&lt;p&gt;I built &lt;strong&gt;Interview Buddy&lt;/strong&gt; for a friend preparing for their first software engineering job. The focus is junior .NET / full stack development, especially practising how to explain technical answers clearly and confidently.&lt;/p&gt;
&lt;p&gt;The app turns a CV and job description into a short practice interview. You choose technical, behavioural, or mixed questions and a difficulty level, then answer one question at a time.&lt;/p&gt;
&lt;p&gt;Each answer receives feedback covering:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What went well.&lt;/li&gt;
&lt;li&gt;Technical mistakes or missing points.&lt;/li&gt;
&lt;li&gt;How to explain the answer more clearly.&lt;/li&gt;
&lt;li&gt;An example improved answer.&lt;/li&gt;
&lt;li&gt;One relevant follow-up question.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can retry a question and finish with a reflection on strengths, topics to revise, and suggested next practice. Behavioural coaching asks the model to use STAR without inventing a personal story. Improved answers must stick to supplied experience or clearly label hypothetical examples.&lt;/p&gt;
&lt;p&gt;Privacy is part of the workflow: &lt;strong&gt;Save this session locally&lt;/strong&gt; is optional and unchecked by default. Temporary sessions disappear on refresh. Saved sessions can be revisited or deleted individually, and there is a &lt;strong&gt;Delete all local data&lt;/strong&gt; action. Users can also flag questionable feedback.&lt;/p&gt;
&lt;p&gt;The interface uses a calm navy, green, and neutral palette, with readable questions, labelled controls, loading states, and a local-AI connection indicator. A clearly labelled synthetic demo previews the workflow without pretending to assess answers using live AI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Actual friend feedback:&lt;/strong&gt; &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What helped: “Practising one question at a time made it easier to focus. The clearer answer examples helped me organise my explanations.”&lt;/li&gt;
&lt;li&gt;What confused them: “I wasn’t sure whether flagging feedback would correct it or just mark it for review.”&lt;/li&gt;
&lt;li&gt;What they would change: “I’d like a hint before answering and a way to practise only the topics I struggled with.”&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;demo-1&quot;&gt;Demo&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Video walkthrough:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/HSC-Logic/InterviewBuddy/blob/main/docs/media/interview-buddy-demo.mp4&quot;&gt;Watch or download Interview Buddy on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://raw.githubusercontent.com/HSC-Logic/InterviewBuddy/main/docs/media/interview-buddy-demo.mp4&quot;&gt;Direct MP4 link&lt;/a&gt; · 74.5 seconds · 1080 × 1920 · silent, with explanatory text.&lt;/p&gt;
&lt;p&gt;Created with Remotion using actual captures of the running app’s synthetic workflow: context, saving consent, answers, feedback, retry, flagging, summary, and restored history. This is an edited browser-capture walkthrough, not continuous native screen recording or live AI inference.&lt;/p&gt;
&lt;p&gt;The app currently runs locally; it has not been publicly deployed. The repository includes startup instructions, synthetic sample inputs, and a short hand-off walkthrough. The synthetic demo can be explored without downloading a model.&lt;/p&gt;
&lt;h3 id=&quot;code-1&quot;&gt;Code&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/HSC-Logic/InterviewBuddy&quot;&gt;Interview Buddy repository&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The repository is public. The Remotion source and synthetic capture assets are included in &lt;code&gt;walkthrough/&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;how-i-built-it-1&quot;&gt;How I Built It&lt;/h3&gt;
&lt;p&gt;The app uses &lt;strong&gt;React and TypeScript&lt;/strong&gt; for the frontend, &lt;strong&gt;Python FastAPI&lt;/strong&gt; for the backend, and &lt;strong&gt;SQLite&lt;/strong&gt; for sessions the user explicitly chooses to save.&lt;/p&gt;
&lt;p&gt;The inference integration uses &lt;strong&gt;Ollama&lt;/strong&gt; with &lt;strong&gt;Qwen2.5 7B Instruct&lt;/strong&gt;, an open-weight model released under &lt;strong&gt;Apache 2.0&lt;/strong&gt;. The model name and local endpoint are configurable. Only the backend sends inference requests to Ollama; there are no cloud AI calls.&lt;/p&gt;
&lt;p&gt;The backend requests structured JSON and validates responses with &lt;strong&gt;Pydantic&lt;/strong&gt;. Invalid output gets one repair attempt before a helpful error. The app handles missing models, unavailable Ollama, timeouts, and cancellation. CVs, job descriptions, and answers are treated as untrusted evidence rather than instructions.&lt;/p&gt;
&lt;p&gt;I kept the architecture small: no agent framework. Docker Compose provides a convenient setup, and the README includes instructions for running without Docker.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Validation:&lt;/strong&gt; six mocked tests passed, covering the API workflow, response validation, persistence and deletion, consent, repair, and failure handling. TypeScript checks, frontend formatting, Python lint, the production build, and Compose configuration validation also passed. The dependency audit reported zero vulnerabilities at the time of checking.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Current limits:&lt;/strong&gt; real inference has not been tested because Ollama was unavailable in the build environment. Docker execution remains unverified. The synthetic workflow was checked in the browser, including retries, flagging, summary, saving, and history restoration. Saved SQLite data is unencrypted, and model feedback can be wrong. The app is intended for single-user localhost use; real friend testing is still pending.&lt;/p&gt;
&lt;p&gt;The main lesson was that a focused workflow needs more than question generation: clear privacy choices, useful failure states, and honest feedback labels make practice easier to trust.&lt;/p&gt;
&lt;h3 id=&quot;why-does-open-innovation-matter-&quot;&gt;Why Does Open Innovation Matter?&lt;/h3&gt;
&lt;p&gt;CVs and interview answers can contain personal information. Open weights and local inference make it possible to practise without sending those details to a hosted AI provider, while retaining the freedom to inspect the integration and change models.&lt;/p&gt;
&lt;p&gt;After dependencies and weights are downloaded, the app is designed to support the core workflow offline. That complete local workflow would not be possible with a hosted-only, closed AI API. It also avoids per-request cloud AI charges, though local hardware, electricity, and inference speed remain tradeoffs.&lt;/p&gt;
&lt;p&gt;For this project, open innovation means control over where personal data goes and how the practice tool works. It makes a small, private app possible without tying its usefulness to a cloud service.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32468/interview-buddy-private-interview-practice-for-a-friends-first-developer-job</guid>
<pubDate>Mon, 26 Oct 2026 04:55:50 +0000</pubDate>
</item>
<item>
<title>I Love Fragrances, So I Built a 6-Game Arcade + Concierge About My Obsession</title>
<link>https://coderlegion.com/27057/i-love-fragrances-so-i-built-a-6-game-arcade-concierge-about-my-obsession</link>
<description>&lt;p&gt;Hi, my name's Ibrahim, I'm a university student, and I have a problem: I love fragrances way more than my bank account loves me for it.&lt;/p&gt;
&lt;p&gt;It started small, the way these things always do. A cheap Middle Eastern attar someone gave me as a gift, the kind that costs less than a coffee but somehow smells like it belongs in a much fancier bottle. Then another. Then I started actually reading about notes, pyramids, accords, sillage, the whole rabbit hole. Fast forward through a lot of saved-up allowance and skipped nights out, and I've now got about 20 bottles on my shelf. Mostly affordable Middle Eastern gems (some of them genuinely punch way above their price), with a small handful of designer pieces I saved up for and treat like trophies.&lt;/p&gt;
&lt;p&gt;If you're a fellow fragrance enthusiast, you already know the feeling: you don't just &quot;wear&quot; a scent, you &lt;em&gt;collect&lt;/em&gt; them, you &lt;em&gt;study&lt;/em&gt; them, you have opinions about whether a note is top, heart, or base and you will absolutely fight someone about it. That obsession is basically the entire reason this project exists.&lt;/p&gt;
&lt;p&gt;So when I saw the DEV Weekend Challenge's &quot;Passion&quot; prompt, there was only one thing I could possibly build.&lt;/p&gt;
&lt;h3 id=&quot;what-i-built-recommendmeafragrance&quot;&gt;What I built: recommendmeafragrance&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;recommendmeafragrance&lt;/strong&gt; is a browser arcade for fragrance nerds: six small daily games built around real perfume data (notes, brands, years, price tiers), plus an AI Concierge you can actually talk to about what you're in the mood for. Every game feeds into a personal &quot;shelf&quot; that tracks which fragrances you've discovered, plus streaks so you have a reason to come back tomorrow.&lt;/p&gt;
&lt;p&gt;Here's the tour.&lt;/p&gt;
&lt;h4 id=&quot;scentle-wordle-but-for-your-nose&quot;&gt;Scentle: Wordle, but for your nose&lt;/h4&gt;
&lt;p&gt;A new fragrance is picked every day (the same one for everyone, worldwide, no matter your timezone). You get 6 guesses, and after each one you get Wordle style feedback: was the brand exact or just the same house family, did the real answer come out earlier or later than your guess, is it pricier or cheaper, same gender, same concentration, how many notes do you actually share. You can even copy a little emoji share-grid at the end, because obviously.&lt;/p&gt;
&lt;p&gt;There's now an &lt;strong&gt;Easy/Hard toggle&lt;/strong&gt;: Easy leans on a famous, recognizable bottle for the day; Hard pulls from the deep catalog for people who want a real challenge.&lt;/p&gt;
&lt;h4 id=&quot;note-detective-reverse-engineer-a-scent-from-its-notes-only&quot;&gt;Note Detective: reverse engineer a scent from its notes only&lt;/h4&gt;
&lt;p&gt;This one's my favorite. You're given nothing but notes, revealed one at a time in rotating order (one top, one heart, one base, cycling through the pyramid), and you have to guess the perfume before your score hits zero. Every note you reveal costs points, but the cost is calculated as a percentage of that specific perfume's total note count, so a fragrance with 6 notes and one with 16 notes both cost the same fair share to fully reveal. As you burn through reveals, letters of the name itself slowly uncover hangman-style, so there's a nice tension between &quot;guess now&quot; and &quot;wait, I need one more hint.&quot;&lt;/p&gt;
&lt;p&gt;Also has an Easy/Hard mode, same famous-vs-deep-catalog split as Scentle.&lt;/p&gt;
&lt;h4 id=&quot;build-a-bottle-invent-your-own-scent-find-out-what-it-actually-is&quot;&gt;Build-a-Bottle — invent your own scent, find out what it actually is&lt;/h4&gt;
&lt;p&gt;Pick as many top, heart, and base notes as you want from a searchable list of hundreds of real notes, hit &quot;invent,&quot; and the app runs a weighted similarity match (base notes count extra, since they carry a scent's identity the most) against the whole catalog to tell you what you basically just invented, plus two runner-up matches. It also treats closely related notes as the same family (so picking &quot;vanilla&quot; still matches perfumes listed under &quot;Madagascar vanilla,&quot; etc.), because with hundreds of real notes in the system, exact-string-only matching would've been brutal.&lt;/p&gt;
&lt;h4 id=&quot;higher-or-lower&quot;&gt;Higher or Lower&lt;/h4&gt;
&lt;p&gt;Two perfumes, one question: which came out first, or which one's pricier? Keep guessing right to build a streak, one miss ends the run. Easy mode keeps the gap obvious and leans on famous bottles; Hard mode pulls from the deep catalog and can come down to a single year or price tier.&lt;/p&gt;
&lt;h4 id=&quot;scent-roulette&quot;&gt;Scent Roulette&lt;/h4&gt;
&lt;p&gt;Set some dials (season, occasion, how much chaos you want), pull the lever, get a fragrance recommendation that actually respects your filters instead of just being random.&lt;/p&gt;
&lt;h4 id=&quot;blind-date&quot;&gt;Blind Date&lt;/h4&gt;
&lt;p&gt;Swipe through fragrances blind, no brand or hype attached, just notes and vibe, and decide &quot;buy&quot; or &quot;skip.&quot; At the end it tells you your &quot;nose score,&quot; how many of your buys were actually the famous, universally-loved ones. Humbling, honestly.&lt;/p&gt;
&lt;h3 id=&quot;meet-the-concierge&quot;&gt;Meet the Concierge&lt;/h3&gt;
&lt;p&gt;Alongside the games there's a chat-style AI Concierge, built to feel like the app's little mascot come to life: warm, encouraging, genuinely curious about what you like, not just a recommendation-spitting bot. Tell it &quot;something warm and cozy for winter nights&quot; and it'll actually ask a follow-up question before throwing fragrances at you, and when it does recommend something, it comes with a real, buyable card attached, not just a name typed in a chat bubble.&lt;/p&gt;
&lt;h4 id=&quot;bringing-google-ai-into-it&quot;&gt;Bringing Google AI into it&lt;/h4&gt;
&lt;p&gt;This is where I'm submitting for the &lt;strong&gt;Best Use of Google AI&lt;/strong&gt; category. The Concierge runs on a shared daily budget by default so everyone gets to try it for free, but I added a &lt;strong&gt;&quot;bring your own Gemini API key&quot;&lt;/strong&gt; option: paste in a free key from Google AI Studio, and the Concierge switches over to running entirely on &lt;strong&gt;Gemini&lt;/strong&gt; for you, with zero daily limit. The key never touches our servers, it lives only in your browser's local storage and gets sent along with your own requests, so you're fully in control of it. It felt like the right way to let people who want unlimited access get it, without me needing to foot an unlimited AI bill as a student running this on personal savings.&lt;/p&gt;
&lt;h3 id=&quot;real-bottles-real-links&quot;&gt;Real bottles, real links&lt;/h3&gt;
&lt;p&gt;None of this matters if it's just a trivia game, so every single fragrance in the catalog (over a thousand of them) is linked to a live, working offer through FragranceShop.com. Win a game, discover something in the Concierge, or just click &quot;buy&quot; on a card, and it adds to your personal shelf and takes you straight to a real product you can actually purchase. It's a small, honest way to make an app about a hobby self-sustaining without shoving ads in your face.&lt;/p&gt;
&lt;h3 id=&quot;try-it&quot;&gt;Try it&lt;/h3&gt;
&lt;p&gt; &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.recommendmeafragrance.com/&quot;&gt;recommendmeafragrance.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No pressure to sign up for anything just to poke around, though creating a free account (Google sign-in, ten seconds) saves your streaks and shelf across devices and unlocks the Concierge chat.&lt;/p&gt;
&lt;h3 id=&quot;closing-thoughts&quot;&gt;Closing thoughts&lt;/h3&gt;
&lt;p&gt;I've genuinely lost track of how many hours went into this between actual classwork, but every time I opened a new bottle from my (very modest, very Middle-Eastern-attar-heavy) collection for &quot;research,&quot; it felt worth it. If you're even a little bit of a fragrance nerd, or you just like small, weird, well-made web games, I'd love for you to come play and tell me what your nose score is.&lt;/p&gt;
&lt;h2 id=&quot;weekendchallenge-googleai-webdev-showdev&quot;&gt;weekendchallenge #googleai #webdev #showdev&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27057/i-love-fragrances-so-i-built-a-6-game-arcade-concierge-about-my-obsession</guid>
<pubDate>Sun, 13 Sep 2026 12:02:18 +0000</pubDate>
</item>
<item>
<title>VOICE IMPLEMENTATION ArokoOro: Preserving Indigenous Languages with Gamification &amp; ElevenLabs</title>
<link>https://coderlegion.com/23616/voice-implementation-arokooro-preserving-indigenous-languages-gamification-elevenlabs</link>
<description>&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/thegamersbaxechief/arokooro-preserving-indigenous-languages-with-gamification-elevenlabs-this-is-a-submission-for-1dl6&quot;&gt;https://dev.to/thegamersbaxechief/arokooro-preserving-indigenous-languages-with-gamification-elevenlabs-this-is-a-submission-for-1dl6&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What I Built&lt;br&gt;
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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Demo&lt;br&gt;
Here is a look at the ArokoOro interface in action!&lt;/p&gt;
&lt;h3 id=&quot;code-2&quot;&gt;Code&lt;/h3&gt;
&lt;p&gt;import os&lt;br&gt;
import threading&lt;br&gt;
import pygame&lt;br&gt;
from tkinter import *&lt;br&gt;
from random import choice, shuffle&lt;br&gt;
from dotenv import load_dotenv&lt;br&gt;
from elevenlabs.client import ElevenLabs&lt;/p&gt;
&lt;h2 id=&quot;----api-amp-audio-setup----&quot;&gt;--- API &amp;amp; Audio Setup ---&lt;/h2&gt;
&lt;p&gt;load_dotenv()&lt;br&gt;
eleven_client = ElevenLabs(api_key=os.getenv(&quot;ELEVENLABS_API_KEY&quot;))&lt;br&gt;
pygame.mixer.init()&lt;/p&gt;
&lt;p&gt;def play_word_audio(spoken_word):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;&quot;&quot;Fetches the audio from ElevenLabs and plays it in the background.&quot;&quot;&quot;
filename = &quot;temp_word.mp3&quot;
try:
    audio_stream = eleven_client.text_to_speech.convert(
        voice_id=&quot;pNInz6obpgDQGcFmaJgB&quot;, 
        output_format=&quot;mp3_44100_128&quot;,   
        text=spoken_word,                       
        model_id=&quot;eleven_multilingual_v2&quot; 
    )

    with open(filename, &quot;wb&quot;) 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&quot;ElevenLabs Error: {e}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;----gui-setup----&quot;&gt;--- GUI Setup ---&lt;/h2&gt;
&lt;p&gt;root = Tk()&lt;br&gt;
root.title('MajasWotdFlip')&lt;br&gt;
root.geometry(&quot;600x400+-1900+100&quot;)&lt;/p&gt;
&lt;p&gt;my_label = Label(root, text=&quot;&quot;, font=(&quot;Helvetica&quot;, 48))&lt;br&gt;
my_label.pack(pady=20)&lt;/p&gt;
&lt;p&gt;def shuffler():&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;def answer():&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if word == entry_answer.get():
    answer_label.config(text=&quot;Correct!!&quot;)
    threading.Thread(target=play_word_audio, args=(word,), daemon=True).start()
else:
    answer_label.config(text=&quot;Incorrect!!&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;global hint_count&lt;br&gt;
hint_count = 0&lt;/p&gt;
&lt;p&gt;def hint(count):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;global hint_count
hint_count = count
word_length = len(word)
if count &amp;lt; word_length:
    hint_label.config(text=f'{hint_label[&quot;text&quot;]} {word[count]}')
    hint_count +=1 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;entry_answer = Entry(root, font=(&quot;Helvetica&quot;, 24))&lt;br&gt;
entry_answer.pack(pady=20)&lt;/p&gt;
&lt;p&gt;button_frame = Frame(root)&lt;br&gt;
button_frame.pack(pady=20)&lt;/p&gt;
&lt;p&gt;answer_button = Button(button_frame, text=&quot;Answer&quot;, command=answer)&lt;br&gt;
answer_button.grid(row=0, column=0, padx=10)&lt;/p&gt;
&lt;p&gt;my_button = Button(button_frame, text=&quot;Pick Another Word&quot;, command=shuffler)&lt;br&gt;
my_button.grid(row=0, column=1, padx=10)&lt;/p&gt;
&lt;p&gt;hint_button = Button(button_frame, text=&quot;Hint&quot;, command=lambda: hint(hint_count))&lt;br&gt;
hint_button.grid(row=0, column=2, padx=10)&lt;/p&gt;
&lt;p&gt;answer_label = Label(root, text='', font=(&quot;Helvetica&quot;, 18))&lt;br&gt;
answer_label.pack(pady=20)&lt;/p&gt;
&lt;p&gt;hint_label = Label(root, text='', font=(&quot;Helvetica&quot;, 18))&lt;br&gt;
hint_label.pack(pady=10)&lt;/p&gt;
&lt;p&gt;shuffler()&lt;br&gt;
root.mainloop() --&amp;gt;&lt;br&gt;
How I Built It&lt;br&gt;
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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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!&lt;/p&gt;
&lt;p&gt;The PEP 668 Guardrail: When I first tried to pip install elevenlabs, Linux hit me with the dreaded externally-managed-environment error.&lt;br&gt;
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.&lt;/p&gt;
&lt;p&gt;python3 -m venv .venv&lt;br&gt;
source .venv/bin/activate&lt;br&gt;
Error Updating ElevenLabs&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The Sneaky 401 Unauthorized Error: Once the environment was set up, the code ran, but the audio failed, spitting out a status_code: 401.&lt;br&gt;
I had written the code to fetch my API key securely using os.getenv(&quot;ELEVENLABS_API_KEY&quot;). 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.&lt;/p&gt;
&lt;p&gt;Audio Input Failure&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Error for bad api input&lt;/p&gt;
&lt;p&gt;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!&lt;/p&gt;
&lt;p&gt;Maximizing ElevenLabs: The Future Roadmap&lt;br&gt;
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:&lt;/p&gt;
&lt;p&gt;Custom Voice Design for Authentic Dialects&lt;br&gt;
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.&lt;/p&gt;
&lt;p&gt;Pronunciation Dictionaries for Tonal Languages&lt;br&gt;
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.&lt;/p&gt;
&lt;p&gt;Direct Audio Streaming for Zero Latency&lt;br&gt;
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 &quot;Answer&quot; button.&lt;/p&gt;
&lt;p&gt;The Program runs with a welcome inBuilt Note &quot; Bawo Ni &quot; meaning &quot;How are you? &quot; Thanks to #ElevenLabs !! Imagine the possiblilties!&lt;/p&gt;
&lt;h2 id=&quot;demo-2&quot;&gt;Demo&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23616/voice-implementation-arokooro-preserving-indigenous-languages-gamification-elevenlabs</guid>
<pubDate>Tue, 28 Jul 2026 01:29:50 +0000</pubDate>
</item>
</channel>
</rss>