arXiv License: MIT Python Version uv Version EMNLP
Banner (Banner art drafted by Gemini Nano-Banana)
Cardiverse is an LLM-powered framework for rapid card game prototyping. It:
-
Generates π novel game variants via graph-based indexing.
-
Produces π» consistent game code validated by gameplay records.
-
Builds π€ scalable gameplay AI through self-playβoptimized heuristics.
We also present an π LLM Gameplay AI Arena for card games, supporting a diverse set of gameplay AI agents ranging from baseline strategies (random, human) to advanced reasoning agents (CoT, ReAct, Reflexion, our methods).
- Installation
- Game Variants Generation
- Game Code Generation
- GameplayAI Generation
- LLM Gameplay AI Arena
We use uv to manage the virtual environment. To set up the environment, run:
uv sync
Remember to set your OpenAI API key in the environment:
export OPENAI_API_KEY="your_api_key"
We propose an indexing method that represents games as mechanic graphs, providing a global view of existing databases to guide design. This approach enables the creation of novel game variants that are intentionally distinct from prior designs.
Graph-based Game Mechanic Generation
Run these three steps in order:
-
Extract entity graphs from game descriptions into the working database:
python -m GameIdea.indexing.entity_extraction --working-dir outputs/graph --game-desc-folder data/game_ideation/example_test --llm-model gpt-4.1
Optional examples:
# Extract a single file python -m GameIdea.indexing.entity_extraction --working-dir outputs/graph --game-desc-file data/game_ideation/example_test/black_peter.md -
Build embeddings, projections, and group IDs:
python -m GameIdea.indexing.entity_grouping --working-dir outputs/graph --node-type entity
-
Build concept clusters and concept hierarchy:
python -m GameIdea.indexing.concept_build \ --working-dir outputs/graph \ --llm-model gpt-4.1 \ --within-threshold-depth0 4 \ --within-threshold-depth1 4 \ --within-threshold-depth2 5 \
Create game variants for all indexed games:
python -m GameIdea.ideation.game_mutate \ --working-dir outputs/graph \ --source-desc-folder data/game_ideation/example_test \ --source-desc-ext .md \ --llm-model gpt-4.1 \ --core-prompts-path GameIdea/llm_op/mutated_game_prompts.json \ --parallel-llm-call-limit 8
Optional: Variate only one game:
python -m GameIdea.ideation.game_mutate \ --working-dir outputs/graph \ --source-desc-folder data/game_ideation/example_test \ --source-desc-ext .md \ --game-name black_peter
By default, game_mutate skips games that already have outputs/graph/variations/<game_name>. Add --overwrite to regenerate.
Optional: Inspect inspiration prompts for a single game:
python -m GameIdea.ideation.get_inspiration \ --working-dir outputs/graph \ --game-name black_peter \ --top-k 5 \ --report-path outputs/graph/reports/black_peter.md \ --save-prompts outputs/graph/reports/black_peter_prompts.md
Evaluate similarities within generated variations:
python -m GameIdea.evaluation.sim_within \ --working-dir outputs/graph \ --percentiles 0.75 0.5 0.25 0.05
Useful options:
--overwrite-group1-embs: recompute embeddings forcardiversevariations instead of reusing cachedembeddings.pkl.
Evaluate similarity of generated variations against the database descriptions:
python -m GameIdea.evaluation.sim_to_db \ --working-dir outputs/graph \ --db-desc-folder data/game_ideation/example_test \ --quantile 0.5
We employ an LLM-based agent system that generates game code from a given description and iteratively refines it by validating against self-generated gameplay records.
-
Preprocess the example game code library for retrieval-augmented generation:
python -m GameCode.retrieval.prepare_indexing --source data/code_generation/example_lib
Then you should see the files in
data/code_generation/example_lib/indexing:βββ game1.md βββ game1.py βββ game2.md βββ game2.py βββ ... -
Test the game code generation pipeline on a single game description.
python create_game_code.py --configs data/code_generation/configs/recommended.yaml --test_run
Then you should see the logging info such as:
2025εΉ΄09ζ07ζ₯ 21:58:38,862 - Running in test mode, only the first task will be executed. 2025εΉ΄09ζ07ζ₯ 21:58:41,646 - Creating game code for emperor in thread MainThread 2025εΉ΄09ζ07ζ₯ 21:58:41,647 - Using the provided game description string for emperor ... 2025εΉ΄09ζ07ζ₯ 22:07:24,249 - Validation result for emperor-a218169c55524c57b3922a2511be5bff: True 2025εΉ΄09ζ07ζ₯ 22:07:29,377 - Validation result for emperor-a218169c55524c57b3922a2511be5bff: True 2025εΉ΄09ζ07ζ₯ 22:07:29,413 - Successfully generated a working game code for emperor after 3 editsAnd the generated game will be saved in:
βββ your_output_dir/ β βββ process.log # log file for code generation β βββ game/ β β βββ emperor.py # generated game code β βββ temp/ β β βββ emperor_llm_chat.log # LLM chat log β β βββ emperor.md # structurized game description β β βββ ... -
To multi-process the game code generation for all games in the config file, run:
python create_game_code.py --configs data/code_generation/configs/recommended.yaml
-
Prepare your own example game code library for better retrieval-augmented generation.
python -m GameCode.retrieval.prepare_indexing --source data/your_example_library
-
Prepare your configuration file. You can copy from
data/code_generation/configs/recommended.yamland modify it.
Our work generates an ensemble of heuristic code functions as gameplay AI, optimized by self-play. It achieves similar performance as LLM agents with NO test-time LLM costs.
Run the script directly from the command line:
python create_gameplay_ai.py --folder_path data/gameplay_ai_generation/examples --policy_num 4
Arguments:
--folder_path(str, default=data/gameplay_ai_generation/examples) Path to the directory containing game folders. Each game folder should have a.mdand.pyfile.--policy_num(int, default=4) Number of base policies to generate for each game.
When you run the script, youβll see logs like:
INFO:GameplayAI:Creating agent for GinRummy
INFO:GameplayAI:Optimizing weights for GinRummy Round 1
INFO:GameplayAI:Optimizing weights for GinRummy Round 2
INFO:GameplayAI:Time usage for GinRummy: {"propose_and_code": 1200, "optimize": 2500}
After running the script, for each game (e.g., GinRummy), the folder will look like:
your_game_directory/
βββ GinRummy/
β βββ GinRummy.md # Game description (prepared manually or using our work)
β βββ GinRummy.py # Game code (prepared manually or using our work)
β βββ ai/ # Gameplay policy and code
β β βββ policy_text.json
β β βββ ...
β βββ time.json # Runtime stats
β βββ usage.json # Token usage stats
Prepare the game folders. Each game folder should contain a .md file with the game description and a .py file with the game code. You can use our generated games or prepare your own.
Example structure:
your_game_directory/
βββ GinRummy/
β βββ GinRummy.md # Game description (prepared manually or using our work)
β βββ GinRummy.py # Game code (prepared manually or using our work)
βββ Uno/
β βββ Uno.md
β βββ Uno.py
βββ ...
Our arena supports a variety of gameplay AI agents, ranging from simple baselines to advanced LLM-driven strategies. We also support human-in-the-loop gameplay via terminal interaction.
| Gameplay AI Agent | Description |
|---|---|
RandomAgent |
Randomized strategy agent |
HumanAgent |
Human player agent, where you can be a part of the game in terminal |
HEAgent |
Our proposed work (heuristics ensemble agent) |
HEA-NoOpt |
Ablation without optimizing heuristic selection |
HEA-NoEns |
Ablation without heuristic ensemble |
CoTAgent |
Chain-of-Thought reasoning agent |
ReActAgent |
ReAct (reasoning + acting) agent |
ReflexionAgent |
Reflexion-based learning agent |
RuleAgent |
Rule-based strategy; supported only for uno, gin_rummy, leduc_holdem |
In total, 22 card games are supported, spanning Rummy, Casino, Trick-Taking, and Other categories. This mix covers traditional casino games, family games, trick-taking classics, and varied versions that are generated by our work.
| Genre | Game Name | Genre | Game Name |
|---|---|---|---|
| Rummy | Boat House Rum | Trick Taking | California Jack |
| Gin Rummy | Crazy Eights | ||
| Casino | Baccarat | Uno | |
| Bull Poker | Go Boom | ||
| Cincinnati Poker | Hearts | ||
| Cincinnati Liz Poker | Hearts (Varied) | ||
| Fan Tan | Hollywood Eights (Varied) | ||
| Fan Tan (Varied) | Other | Go Fish | |
| In-Between | Linger Longer | ||
| Liberty Fan Tan (Varied) | Go Fish (Varied) | ||
| Leduc Holdβem | I Doubt it (Varied) | ||
Run single or multiple evaluation rounds for card games with interchangeable attack and defense agents (LLM-driven, heuristic, rules, random, or human-in-the-loop). Supports optional game logging, CSV result aggregation, and Reflexion-style training.
Human vs Random in Uno. It means YOU will play the game against computer players by inputting commands in the terminal!
python -m GameplayAI.run_game --game uno --attack_agent HumanAgent --defense_agent RandomAgent
Tip: When --attack_agent HumanAgent is selected, the CLI shows your legal actions and (when available) a "Hint" generated by the heuristic executor (HE) agent.
Random vs Random in Uno, appending final game results to a CSV
python -m GameplayAI.run_game --game uno --attack_agent RandomAgent --defense_agent RandomAgent --output_csv output.csv
HeuristicEnsembleAgent vs ReAct in Leduc Holdβem, saving game log and appending results to CSV, with fixed seed
python -m GameplayAI.run_game --game leduc_holdem --attack_agent ReActAgent --defense_agent HEAgent --log --output_csv output.csv --seed 42
ReflexionAgent vs CoTAgent in Gin Rummy with logs and training. OpenAI API key must be set in the environment for LLM-based agents.
python -m GameplayAI.run_game --game gin_rummy --attack_agent ReflexionAgent --defense_agent CoTAgent --log --training
python -m GameplayAI.run_game [OPTIONS]
Although defaults exist in code, these three are parsed as required.
| Flag | Type | Choices | Description |
|---|---|---|---|
--game |
str |
uno, leduc_holdem, gin_rummy, ... |
Game name (must match a folder and file in --dir). |
--defense_agent |
str |
RandomAgent, CoTAgent, ReActAgent, ReflexionAgent, HEAgent, RuleAgent, HEA-NoOpt, HEA-NoEns |
Defense agent type. |
--attack_agent |
str |
RandomAgent, HumanAgent, HEAgent, HEA-NoOpt, HEA-NoEns, CoTAgent, ReActAgent, ReflexionAgent, RuleAgent |
Attack agent type. |
| Flag | Type | Description |
|---|---|---|
--dir |
str |
Root directory containing game assets. The script expects <dir>/<game>/<game>.py and <dir>/<game>/<game>.txt. |
--llm_model |
str |
Model ID passed to LLMHandler (used by CoT/ReAct/Reflexion and any LLM-backed loaders). Model ID naming follows OpenAI's API documentation. |
--run_num |
int |
Number of rounds to play in this session. |
--seed |
int or None |
Random seed forwarded to the environment factory. |
--log |
flag | If set, write a game log per run. |
--log_path |
str or None |
Path for the log file. If omitted but --log is set, a <game>_play_<timestamp>.log is used. |
--training |
flag | Enable Reflexion post-game reflect(...) updates for any ReflexionAgent in the env. |
--output_csv |
str or None |
Append results per run to a CSV file you specify. Created on first run if missing. |
Generate a batch evaluation script for all games in a directory, excluding specific games (e.g., uno), with specified attack and defense agents, number of runs, output CSV file, and LLM model.
python GameplayAI/utils/batch_generate_eval.py --root_path data/gameplay_ai_generation/examples --exclude_games uno --script_path eval_batch.sh --defense_agents RandomAgent --attack_agents RandomAgent ReActAgent --run_num 100 --output_csv results.csv --llm_model gpt-4o-mini
Then run the generated script:
bash eval_batch.sh
In our paper, we used the game descriptions from a third party (https://bicyclecards.com/how-to-play) as inputs to:
- Evaluate game variant generation pipeline,
- Evaluate game code generation pipeline, and
- Evaluate our gameplay AIs versus prior LLM agents.
However, we are not allowed to redistribute these contents due to Fair Use policies.
To provide an alternative data source, the game descriptions we include in this repository are manually crafted with the assistance of ChatGPT. The game rules in data\code_generation\example_lib and data\gameplay_ai_generation\examples are manually verified to largely align with the game descriptions we used in the paper, but the difference in language may lead to some discrepancies in the generated gameplay AIs and their performance.
To more strictly replicate our results, please manually download the game descriptions from the source. Then you replace the game description markdown files in the following directories with the downloaded files:
data\game_ideation\example_testdata\code_generation\example_libdata\gameplay_ai_generation\examples
You should strictly follow the local laws and regulations when using these materials.
This repository uses code snippets from the following open-source projects:
- RLCard (https://github.com/datamllab/rlcard)
- Agentless (https://github.com/OpenAutoCoder/Agentless?tab=readme-ov-file)
If you find our work useful in your research, please consider citing:
@misc{li2025cardiverse, title={Cardiverse: Harnessing LLMs for Novel Card Game Prototyping}, author={Danrui Li and Sen Zhang and Sam S. Sohn and Kaidong Hu and Muhammad Usman and Mubbasir Kapadia}, year={2025}, eprint={2502.07128}, archivePrefix={arXiv}, primaryClass={cs.CL}, url={https://arxiv.org/abs/2502.07128}, }