# Mind Game: alignment text adventure (inspired by Ender's Game) (`biobitworks/daisy-mind-game`) Actor

A text adventure inspired by Ender's Game's Mind Game, built on fandom wikis: every move is classified on the D\&D alignment grid, your ASCII character evolves, and each chapter jumps to a new sci-fi or fantasy world.

- **URL**: https://apify.com/biobitworks/daisy-mind-game.md
- **Developed by:** [Byron Lee](https://apify.com/biobitworks) (community)
- **Categories:** Games, AI, Open source
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Mind Game: alignment text adventure (inspired by Ender's Game)

A terminal text adventure inspired by the Battle School **Mind Game** from *Ender's Game*. It reads how you play,
classifies every decision on the D\&D alignment grid, and moves you into new sci-fi and fantasy worlds each chapter.

### Quick summary

- **Data:** Apify scrapes the fandom wiki. SeedGraph's sentence diagrammer turns pages into sentences and subject–verb–object facts.
- **Text:** a local Liquid AI LFM (via Ollama on the MagicPro) writes each scene from those facts.
- **Decisions:** every action becomes TypeSafe Jev-style typed questions, answered by the local model now and by Jev when the key arrives:
  - `choice`: which path you meant
  - `score`: aggression, empathy, curiosity, persistence, law↔chaos, good↔evil
  - `noul`: does the action fit the wiki facts?
- **Logic:** SeedGraph's LogicWorkbench treats the scene's facts as premises and your action as the conclusion → ready / degraded / refused.
- **Alignment grid:** after every answer you see the 3×3 grid: move counts per cell, ● = your last move, ◉ = your overall alignment, plus your recent trail.
- **Giant's Drink:** an unwinnable test. Pick a cup and die. Break the frame and reach Fairyland.
- **Chapters:** after 5 moves the game names your alignment and offers 3 worlds. Your pick is scraped live and becomes chapter 2.

### How to run (Mac Terminal)

```
## 1. local model (once)
ollama serve                                   # if not already running
ollama pull hf.co/LiquidAI/LFM2.5-1.2B-Instruct-GGUF
curl localhost:11434/api/tags                  # should list the model

## 2. play
cd ~/projects/active/daisy-mind-game
./run.sh                                       # first run builds data/facts.json with SeedGraph
```

`run.sh` uses `../seedgraph/.venv/bin/python` (spaCy + SeedGraph) when present, otherwise `python3`.
The top line shows which parts are live: narrator, decisions, logic, diagrammer.

**Options**
| Command / env | Effect |
|---|---|
| `./run.sh prep` | Rebuild `data/facts.json` from `data/enderverse.json` |
| `./run.sh 42` | Seeded run (reproducible) |
| `JEV_BACKEND=heuristic ./run.sh` | LFM narrates, keyword rules decide (instant moves) |
| `JEV_BACKEND=typesafe` + `TYPESAFE_API_KEY` in `.env` | Real Jev decisions |
| `OPENJEV_URL=http://...` | OpenJev endpoint (TypeSafe request shape) |
| `MINDGAME_MODEL=...` | Different Ollama model |
| `MINDGAME_GIANT_TURN=4` | Giant's Drink sooner |
| `python3 scripts/apify_scrape.py start` / `poll` | Re-scrape Enderverse with Apify (`APIFY_TOKEN` in `.env`) |

**In game:** `1` `2` `3` or type any action · `/grid` alignment grid · `/profile` traits + grid · `/facts` source
sentences, triples, wiki URL · `/quit` saves the session to `~/.mindgame/`.

**Fallbacks:** no Ollama → the wiki text narrates and heuristics decide. Apify fails → MediaWiki API. It never crashes on stage.

### Pipeline

1. **Apify** (backend): `scripts/apify_scrape.py` runs `apify/website-content-crawler` on 22 Enderverse pages → `data/enderverse.json`
2. **SeedGraph sentence diagramming:** `mindgame/preprocess.py` → `data/facts.json`
3. **Local LFM** writes scenes (`mindgame/game.py`, `mindgame/llm.py`)
4. **Jev-style decisions** (`mindgame/jev.py`): TypeSafe System One request shape; backends ollama / typesafe / openjev / heuristic
5. **SeedGraph logic gate** (`LogicGate` in `game.py`)
6. **Alignment:** law↔chaos + good↔evil per move, D\&D nine-box (easydamus alignment test), grid after every answer
7. **Chapters** (`mindgame/chapters.py`): 3 fandom offers, live Apify scrape, SeedGraph diagramming
8. **Giant's Drink → Fairyland**

Roadmap: `PLAN.md` (ephemeral runs, background prefetch, cellular-automaton chapters and sprites, Jev harness).

### Apify Actor

The same game runs as an Apify Actor with no keyboard. You give it a list of moves, it plays them in order, and it
returns the alignment result as one dataset item. Entry point: `my_actor/main.py`. The game code is unchanged; the
Actor feeds the `actions` list to the game's `input()` prompts and captures what it prints.

**What it does**

- Plays the full game: alignment scoring, the Giant's Drink, and chapter switches. A chapter switch scrapes a new
  wiki world live: free MediaWiki API by default, or the Apify crawler when `useCrawler` is on (billed to your account).
- Decisions run offline by default (heuristic keyword rules). Give a TypeSafe key for real Jev decisions, or an
  Ollama URL to use a model server you run.
- Needs no Ollama on the platform. Without one, the wiki text narrates each scene.

**Input** (`.actor/input_schema.json`)

| Field | Type | Default | Meaning |
|---|---|---|---|
| `actions` | string list | 12 moves (see below) | Lines typed at the prompts, in order. `"1"`–`"3"` pick a numbered choice; other text is a free action. At the Giant's Drink, `"1"`/`"2"` drink a cup and anything else tries to break the frame. At a chapter end, `"1"`–`"3"` loads that world and `"q"` stops. `/grid`, `/profile` are allowed and do not count as moves. Running out of actions ends and saves the game. |
| `seed` | integer | `0` | Random seed. `0` = random. Same seed and actions give the same run (as long as the wiki pages are unchanged). |
| `giantTurn` | integer | `4` | Move number at which the Giant's Drink appears. |
| `typesafeApiKey` | string (secret) | empty | TypeSafe key for real Jev decisions. |
| `ollamaUrl` | string | empty | Ollama server URL with the LFM model pulled. Empty = offline. |
| `useCrawler` | boolean | `false` | Scrape new chapter worlds with `apify/website-content-crawler` (costs credits). Off = free MediaWiki API. |
| `maxChapters` | integer | `2` | Stop after this many chapters. `0` = no limit. |

Default `actions`: `["1", "1", "1", "I refuse both cups and kick the table over", "1", "1", "1", "1", "1", "1", "1", "1"]`.
That is three moves, the Giant's Drink (escaped), two moves, a world pick for chapter 2, and five moves that end chapter 2.

Example input:

```json
{
    "actions": ["1", "1", "I help the cadet", "I refuse both cups and kick the table over", "1", "1", "1", "/grid", "1", "1", "1", "1", "1"],
    "seed": 42,
    "giantTurn": 4
}
```

**Output** (`.actor/dataset_schema.json`): one dataset item per run.

| Field | Type | Meaning |
|---|---|---|
| `alignment` | string | Final D\&D alignment, for example `Chaotic Good`. |
| `law` | number | Law (0) to chaos (4), running average. |
| `moral` | number | Good (0) to evil (4), running average. |
| `profile` | object | `aggression`, `empathy`, `curiosity`, `persistence`, each 0–4. |
| `worlds` | string array | Worlds visited, starting with `enderverse`. |
| `turns` | integer | Turns reached, including the Giant's Drink turn. |
| `alignmentHistory` | array of `[law, moral]` | One pair per judged move. |
| `log` | object array | Per-move record: action, Jev answers, logic verdict, alignment. |
| `sources` | object array | Wiki pages whose text appeared in the run: `url`, `wiki`, `license`. |
| `licenses` | string array | Distinct content licenses of those pages, e.g. `CC BY-SA`. |
| `transcript` | string | Full terminal text of the run, colour codes removed. |

The transcript is also saved to the default key-value store under `TRANSCRIPT` (`text/plain`).

**Local test**

```bash
pip install 'apify>=4,<5'
mkdir -p storage/key_value_stores/default
echo '{"seed": 42}' > storage/key_value_stores/default/INPUT.json   # empty fields use the defaults
APIFY_LOCAL_STORAGE_DIR=./storage python3 -m my_actor
cat storage/datasets/default/*.json | head -c 600                    # one item
```

### Sources

Scene text is adapted from fandom.com wikis, which license their text under CC BY-SA
(Memory Alpha: CC BY-NC). The game credits the wiki at the start of each chapter, `/facts` shows each
scene's source page, and every Actor run lists the pages it used in `sources`.

### License

Code: [Apache License 2.0](LICENSE). Wiki text in `data/` and fetched at run time: CC BY-SA (Memory Alpha: CC BY-NC), as
described in [NOTICE](NOTICE).

# Actor input Schema

## `actions` (type: `array`):

Lines typed at the game's prompts, in order. A number picks that numbered choice ("1", "2", "3"); any other text is a free action the game judges against the world's facts. At the Giant's Drink, "1" or "2" drinks a cup and anything else tries to break the frame. At the end of a chapter, "1"-"3" loads that world and "q" stops playing. Commands like "/grid" and "/profile" are allowed and do not count as moves. When the list runs out, the game ends and the result is saved.

## `seed` (type: `integer`):

Random seed for world selection, the Giant's Drink escape and the wiki page sample. The same seed with the same actions gives the same run, provided the wiki pages are unchanged. 0 = random.

## `giantTurn` (type: `integer`):

The move number at which the Giant's Drink appears. Lower values reach the Giant sooner. Default 4.

## `typesafeApiKey` (type: `string`):

Optional. A TypeSafe key makes the decisions use the real Jev System One API. Leave empty for offline heuristic decisions. Stored as a secret.

## `ollamaUrl` (type: `string`):

Optional. URL of an Ollama server, for example http://203.0.113.5:11434, with the Liquid AI LFM model pulled. It writes the scene text and answers the decisions. Leave empty (the default) for wiki text narration and offline heuristic decisions. Ollama is not available on the Apify platform by default, so this only works if you run your own server that is reachable from Apify.

## `useCrawler` (type: `boolean`):

When a chapter ends and a new world loads, scrape it with apify/website-content-crawler (billed to your account). Off: pages come from the wiki's free MediaWiki API, which is faster.

## `maxChapters` (type: `integer`):

Stop after this many chapters (0 = no limit; the run still ends when the actions run out).

## Actor input object example

```json
{
  "actions": [
    "1",
    "1",
    "1",
    "I refuse both cups and kick the table over",
    "1",
    "1",
    "1",
    "1",
    "1",
    "1",
    "1",
    "1"
  ],
  "seed": 0,
  "giantTurn": 4,
  "ollamaUrl": "",
  "useCrawler": false,
  "maxChapters": 2
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "actions": [
        "1",
        "1",
        "1",
        "I refuse both cups and kick the table over",
        "1",
        "1",
        "1",
        "1",
        "1",
        "1",
        "1",
        "1"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("biobitworks/daisy-mind-game").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "actions": [
        "1",
        "1",
        "1",
        "I refuse both cups and kick the table over",
        "1",
        "1",
        "1",
        "1",
        "1",
        "1",
        "1",
        "1",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("biobitworks/daisy-mind-game").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "actions": [
    "1",
    "1",
    "1",
    "I refuse both cups and kick the table over",
    "1",
    "1",
    "1",
    "1",
    "1",
    "1",
    "1",
    "1"
  ]
}' |
apify call biobitworks/daisy-mind-game --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,biobitworks/daisy-mind-game"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/IgZbd2LFmTEfTV4k4/builds/3yLFzjFErdH9jJQ6O/openapi.json
