Engine for building memi memory card game instances
Project description
memi-engine
In a world where a language model will answer almost anything in an instant, the part of your mind that recalls — that retrieves what you know on its own — gets little exercise. memi is a small counterweight: a game built on active recall. You look at an image, try to name it before revealing the answer, and follow the know more link to learn more. Each round strengthens the link between what you see and what you know. The answer is always one tap away — the point is to reach for it yourself first.
memi-engine lets you build your own memi game — a
tap-to-reveal flashcard trainer — from a list of names and where to find their
images.
You define categories (countries, animals, monuments, movies…); the engine gives you the responsive web UI, the menu, image fetching from Wikipedia and friends, filters, clue mode, a "know more" link to each item's Wikipedia (or source) page on reveal, theming, and a reporting system.
pip install memi-engine
from memi_engine import CategoryProvider, MemiConfig, create_app, register
class Animals(CategoryProvider):
key = "nature:animals"
items = ["Lion", "Tiger", "Elephant", "Aardvark"]
register(Animals())
app = create_app(MemiConfig(title="My Memi"))
if __name__ == "__main__":
app.run(debug=True)
Open http://localhost:5000, pick nature → animals, and play. Images are resolved from Wikipedia automatically from each item's name.
Concepts
A memi game is just a set of category providers registered with the engine. Each provider declares:
items— the list of names to guess.key— where the category sits in the menu (see below).- how to get an image for an item (default: Wikipedia), and optionally a tag (a subtitle shown on reveal) and a clue.
The engine handles routing, the random game loop (/api/random), filtering,
prefetching, and rendering.
Keys are the menu
A category key is a colon-separated path. The engine splits it to build a
nested menu, and renders each segment verbatim as the on-screen label — so
the key is also your menu copy. This is why localized games keep their keys in
the game's language:
| Key | Menu shown to the player |
|---|---|
"space" |
space |
"nature:animals" |
nature → animals |
"nature:plants:flowers" |
nature → plants → flowers |
"geografia:freguesias" |
geografia → freguesias |
Up to four levels are supported. A child labelled all always sorts first.
CategoryProvider
Subclass it and set at least key and items. Override the methods you need.
class Monuments(CategoryProvider):
key = "culture:monuments"
items = ["Belém Tower", "Eiffel Tower"]
override_name = True # show the item name, not the article title
def get_tag(self, item): # subtitle on the revealed card
return PARISHES.get(item)
Register each provider with register(Monuments()), or use @register as a
class decorator on the definition.
Attributes
| Attribute | Default | Meaning |
|---|---|---|
key |
"" |
Menu path (see above). |
items |
[] |
List of item names. |
filters |
{} |
{filter_name: {value: [items]}} — auto-generates filter UI. |
single_select |
False |
Only one subcategory active at a time. |
light_bg |
False |
Light card background (good for logos). |
override_name |
False |
Use the item key as the display name, not the article title. |
footers |
[] |
Footer IDs (attribution) to show when this category is active. |
tag_style |
None |
"plain", "scientific", or None (auto-detect) — tag styling. |
Methods
| Method | Returns |
|---|---|
get_image(item) |
{"name": ..., "image": ..., "url": ...} or None. Default: Wikipedia. |
get_tag(item) |
A short subtitle for the revealed card, or None. |
get_clue(item) |
A clue shown before reveal, or None. |
The optional url in the get_image result is the item's source page; the
engine turns it into the "know more" link shown on reveal (label set via
MemiConfig.label_more). The built-in image helpers populate it automatically —
e.g. get_wikipedia_image returns the Wikipedia article URL — so Wikipedia-backed
categories get the link for free.
Scientific names
ScientificNameProvider tags each item with its Latin name. It ships a bundled
English database (SCIENTIFIC_NAMES, ~1500 species) used by default; pass your
own mapping for other languages. The tag is shown only when the Latin name
differs from the display name, in italic scientific style.
from memi_engine import ScientificNameProvider, register
class Animals(ScientificNameProvider):
key = "nature:animals"
items = ["Lion", "Tiger"] # → "Panthera leo", "Panthera tigris"
class Plantas(ScientificNameProvider):
key = "natureza:plantas"
items = ["Sobreiro"]
scientific_names = {"Sobreiro": "Quercus suber"}
Filters
A filter maps option values to subsets of items. The engine renders the filter
buttons and applies the choice via a URL parameter.
class Countries(CategoryProvider):
key = "geography:countries"
items = ["France", "Spain", "Japan"]
filters = {
"continent": {"europe": ["France", "Spain"], "asia": ["Japan"]},
}
Images
memi_engine.images resolves item names to image URLs and caches results
in-memory. Providers call these from get_image:
get_wikipedia_image, get_wikipedia_file_image, get_commons_file_image,
get_tmdb_image, get_tmdb_tv_image, get_fandom_image, get_country_shape,
get_album_cover, get_logo_image, and more.
Some sources need configuration via environment variables:
| Variable | Used by | Default |
|---|---|---|
TMDB_API_KEY |
get_tmdb_image (movies / TV) |
(unset — TMDB skipped) |
BONES_API_URL |
anatomy image service | http://127.0.0.1:8081 |
MemiConfig
Passed to create_app. Common fields:
| Field | Default | Purpose |
|---|---|---|
title |
"memi" |
Header title. |
subtitle |
"practise…" |
Header subtitle. |
themes |
8 built-in themes | Available colour themes. |
default_theme |
"light" |
Initial theme. |
sponsor_url |
None |
Sponsor link (hidden if None). |
about_html |
None |
Custom HTML for the about page. |
analytics_html |
None |
Analytics snippet injected on the page. |
favicon_color |
"#b8860b" |
Favicon background colour. |
wikipedia_lang |
"en" |
Wikipedia edition for default images / know more links. |
related_sites |
[] |
Sibling games to link from the about page. |
label_* |
English strings | UI labels (for localization). |
For a non-English game, set wikipedia_lang so the default image lookup and the
"know more" link resolve against that language's Wikipedia (e.g. "pt"). It
can also be set with the MEMI_WIKIPEDIA_LANG environment variable.
All UI strings are label_* fields, so a fully localized game keeps its labels
and about_html in its own language while the code stays English.
Instance static files
To serve your own logo or images, point create_app at a static folder; its
files take precedence over the engine's:
app = create_app(config, instance_static="/path/to/static")
# served at /static/... , falling back to the engine's static files
Deployment
The app is a standard WSGI Flask app. For production, install the server
extra and run under gunicorn:
pip install "memi-engine[server]"
gunicorn "yourgame:app"
Two optional data files are read from the working directory at runtime:
excluded_items.txt (items to hide) and reported_items.log (written when
players report a bad card).
Live examples
Real games built on this engine: memi · memi portugal · memi lisboa · memi slovensko · memi US.
Development
uv sync --extra dev
pytest # run the test suite
ruff check . # lint
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file memi_engine-0.1.1.tar.gz.
File metadata
- Download URL: memi_engine-0.1.1.tar.gz
- Upload date:
- Size: 43.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3d12cbd6939c9ea48bfce730134f4bb2f87c9c12de0aa42b8c7320b2d98b9d36
|
|
| MD5 |
c6e1979b2b395fec06748548036b7c55
|
|
| BLAKE2b-256 |
3b499ef0ed87c48c4e3005d1eb67bde9a3f1229c52d96141babd9aa11e4fd018
|
File details
Details for the file memi_engine-0.1.1-py3-none-any.whl.
File metadata
- Download URL: memi_engine-0.1.1-py3-none-any.whl
- Upload date:
- Size: 47.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98357a3f5124794040485b0c10e7e27900fd930af2e5647a53e4fa0e95422d3c
|
|
| MD5 |
3194c145d6a09562cbb232952d7c2c76
|
|
| BLAKE2b-256 |
e6d265f9e43da0a34767b6960913b40d72803ee87cd3ccc97adfed89f1fe8abc
|