GhalaDev commited on
Commit
fbd50df
·
verified ·
1 Parent(s): bf7596d

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +20 -8
  2. app.py +116 -16
  3. requirements.txt +3 -0
README.md CHANGED
@@ -5,28 +5,40 @@ colorTo: purple
5
  sdk: gradio
6
  app_file: app.py
7
  pinned: false
8
- sdk_version: 6.20.0
9
  ---
10
 
11
  # AI BoardGame Forge
12
 
13
- A web app that turns any theme into a mini card game with AI-generated artwork.
 
14
 
15
  Enter a theme, such as "Space Cats" or "Ancient Pirates", or pick one of the
16
  example themes provided, and the app generates a game title, description,
17
- rules, and 4 unique cards with AI-generated artwork.
18
 
19
  ## How it works
20
 
21
- - Game text (title, description, rules, cards) is generated locally using a
22
- procedural generator that combines the theme with pre-built word banks and
23
- templates.
 
 
 
24
  - Card artwork is generated using Pollinations.ai, a free image generation
25
- service that requires no API key.
 
26
  - Each card is composed into a trading-card layout using Pillow (PIL).
27
  - The interface is built with Gradio.
28
 
29
  ## Files
30
 
31
  - `app.py`: the full application code
32
- - `requirements.txt`: the Python packages needed to run it
 
 
 
 
 
 
 
 
 
5
  sdk: gradio
6
  app_file: app.py
7
  pinned: false
 
8
  ---
9
 
10
  # AI BoardGame Forge
11
 
12
+ A web app that turns any theme into a mini card game with AI-generated text
13
+ and AI-generated artwork.
14
 
15
  Enter a theme, such as "Space Cats" or "Ancient Pirates", or pick one of the
16
  example themes provided, and the app generates a game title, description,
17
+ rules, and 4 unique cards, each with its own AI-generated artwork.
18
 
19
  ## How it works
20
 
21
+ - Game text (title, description, rules, cards) is generated by
22
+ Qwen2.5-0.5B-Instruct, a small, free, open-source language model that
23
+ runs locally through the Hugging Face `transformers` library. No API key
24
+ or account is needed.
25
+ - If the model's reply cannot be parsed as valid JSON with exactly 4 cards,
26
+ the app falls back to a local template generator so it never breaks.
27
  - Card artwork is generated using Pollinations.ai, a free image generation
28
+ service that also requires no API key. If an image request fails, a
29
+ themed placeholder image is used instead.
30
  - Each card is composed into a trading-card layout using Pillow (PIL).
31
  - The interface is built with Gradio.
32
 
33
  ## Files
34
 
35
  - `app.py`: the full application code
36
+ - `requirements.txt`: the Python packages needed to run it
37
+
38
+ ## Notes on hardware
39
+
40
+ This Space loads a small language model in addition to making image
41
+ requests, so the free CPU tier works but the first response after startup
42
+ may take a bit longer while the model loads into memory. Generating a full
43
+ game typically takes somewhere between 20 and 90 seconds depending on
44
+ available compute.
app.py CHANGED
@@ -1,22 +1,114 @@
1
  """
2
  AI BoardGame Forge
3
  A Gradio web app that turns any theme into a mini card game with
4
- AI-generated artwork. Built for Hugging Face Spaces.
5
  """
6
 
 
7
  import random
8
  import urllib.parse
9
  from io import BytesIO
10
 
11
  import requests
12
  import gradio as gr
 
13
  from PIL import Image, ImageDraw, ImageFont
 
14
 
15
 
16
  # ------------------------------------------------------------
17
- # Game text generator
18
  # ------------------------------------------------------------
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  TITLE_TEMPLATES = [
21
  "{theme} Wars",
22
  "The {theme} Chronicles",
@@ -24,14 +116,11 @@ TITLE_TEMPLATES = [
24
  "{theme}: The Card Game",
25
  "Rise of the {theme}",
26
  "{theme} Showdown",
27
- "Legends of {theme}",
28
- "{theme} Uprising",
29
  ]
30
 
31
  DESCRIPTION_TEMPLATES = [
32
  "In this game, players take on the role of characters caught up in a {theme} scenario. Each player uses cards to outsmart their opponents and be the first to win.",
33
  "Players compete in a world inspired by {theme}, drawing and playing cards to gain the upper hand. Strategy and a little luck decide the winner.",
34
- "Set in a world inspired by {theme}, each player builds a small hand of powerful cards and battles to reach the win condition first.",
35
  ]
36
 
37
  RULES_TEMPLATE = """1. Each player starts with a hand of 4 cards.
@@ -44,7 +133,7 @@ CARD_TYPES = ["Attack", "Defense", "Support", "Trick", "Special", "Boost", "Sabo
44
 
45
  NAME_PARTS = [
46
  "Guardian", "Blade", "Shadow", "Spark", "Wanderer", "Sentinel",
47
- "Rebel", "Trickster", "Champion", "Outcast", "Phantom", "Rogue",
48
  ]
49
 
50
  EFFECT_POOL = [
@@ -53,16 +142,12 @@ EFFECT_POOL = [
53
  "Steal one card from an opponent's hand.",
54
  "Skip your opponent's next turn.",
55
  "Double your score this round.",
56
- "Swap this card with any card in the discard pile.",
57
- "Force all players to reveal their hand.",
58
  "Gain +3 power for the rest of the game.",
59
- "Discard your hand and draw a fresh one.",
60
- "Copy the effect of the last card played.",
61
  ]
62
 
63
 
64
- def generate_game(theme):
65
- """Builds a full mini card game (title, description, rules, 4 cards) from a theme."""
66
  theme_clean = theme.strip().title()
67
 
68
  title = random.choice(TITLE_TEMPLATES).format(theme=theme_clean)
@@ -79,6 +164,18 @@ def generate_game(theme):
79
  return {"title": title, "description": description, "rules": rules, "cards": cards}
80
 
81
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  # ------------------------------------------------------------
83
  # AI artwork generator
84
  # ------------------------------------------------------------
@@ -110,7 +207,8 @@ def get_font(size, bold=True):
110
 
111
 
112
  def make_placeholder_art(card):
113
- """A simple backup image used only if the AI art request fails."""
 
114
  colors = TYPE_COLORS.get(card["type"], {"bg": (100, 100, 100)})
115
  img = Image.new("RGB", (512, 512), colors["bg"])
116
  draw = ImageDraw.Draw(img)
@@ -162,7 +260,9 @@ def wrap_text(text, font, max_width, draw):
162
 
163
 
164
  def draw_card(card, art_image, theme):
165
- """Composes the final card image: background, artwork, title bar, type badge, effect text."""
 
 
166
  width, height = 500, 700
167
  colors = TYPE_COLORS.get(card["type"], {"bg": (60, 60, 60), "border": (255, 255, 255)})
168
 
@@ -205,7 +305,7 @@ def draw_card(card, art_image, theme):
205
  # ------------------------------------------------------------
206
 
207
  def forge_full_game(theme):
208
- """Full pipeline: theme -> game text -> AI artwork -> designed card images."""
209
  print(f"Generating game for theme: {theme}")
210
  game_data = generate_game(theme)
211
  print(f"Game created: {game_data['title']}")
@@ -259,7 +359,7 @@ with gr.Blocks(title="AI BoardGame Forge") as demo:
259
  gr.Markdown("# AI BoardGame Forge")
260
  gr.Markdown(
261
  "Enter a theme, or select one of the example themes below, "
262
- "then click Generate Game to create a mini card game with AI-generated artwork."
263
  )
264
 
265
  theme_input = gr.Textbox(
 
1
  """
2
  AI BoardGame Forge
3
  A Gradio web app that turns any theme into a mini card game with
4
+ AI-generated text and AI-generated artwork. Built for Hugging Face Spaces.
5
  """
6
 
7
+ import json
8
  import random
9
  import urllib.parse
10
  from io import BytesIO
11
 
12
  import requests
13
  import gradio as gr
14
+ import torch
15
  from PIL import Image, ImageDraw, ImageFont
16
+ from transformers import AutoModelForCausalLM, AutoTokenizer
17
 
18
 
19
  # ------------------------------------------------------------
20
+ # Language model setup
21
  # ------------------------------------------------------------
22
 
23
+ MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"
24
+
25
+ print("Loading language model, this can take a minute on first startup...")
26
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
27
+ model = AutoModelForCausalLM.from_pretrained(
28
+ MODEL_NAME,
29
+ torch_dtype="auto",
30
+ device_map="auto",
31
+ )
32
+ print("Model loaded.")
33
+
34
+
35
+ def ask_llm(system_prompt, user_prompt, max_new_tokens=500):
36
+ """Sends a system prompt and a user prompt to the model and returns its text reply."""
37
+ messages = [
38
+ {"role": "system", "content": system_prompt},
39
+ {"role": "user", "content": user_prompt},
40
+ ]
41
+
42
+ prompt_text = tokenizer.apply_chat_template(
43
+ messages, tokenize=False, add_generation_prompt=True
44
+ )
45
+ inputs = tokenizer(prompt_text, return_tensors="pt").to(model.device)
46
+
47
+ output_tokens = model.generate(
48
+ **inputs,
49
+ max_new_tokens=max_new_tokens,
50
+ do_sample=True,
51
+ temperature=0.8,
52
+ pad_token_id=tokenizer.eos_token_id,
53
+ )
54
+
55
+ new_tokens = output_tokens[0][inputs["input_ids"].shape[1]:]
56
+ reply = tokenizer.decode(new_tokens, skip_special_tokens=True)
57
+ return reply.strip()
58
+
59
+
60
+ # ------------------------------------------------------------
61
+ # Game text generator (LLM-based, with a template fallback)
62
+ # ------------------------------------------------------------
63
+
64
+ SYSTEM_PROMPT = """You are a creative board game designer.
65
+ Given a theme, invent a simple, fun mini card game based on it.
66
+
67
+ Reply with ONLY valid JSON, no extra text and no markdown code fences, in exactly this format:
68
+
69
+ {
70
+ "title": "a catchy game title",
71
+ "description": "2 to 3 sentences describing how the game works",
72
+ "rules": "3 to 5 short, simple rules a beginner could follow, written as one string",
73
+ "cards": [
74
+ {"name": "card name", "type": "card type", "effect": "short special ability"},
75
+ {"name": "card name", "type": "card type", "effect": "short special ability"},
76
+ {"name": "card name", "type": "card type", "effect": "short special ability"},
77
+ {"name": "card name", "type": "card type", "effect": "short special ability"}
78
+ ]
79
+ }
80
+
81
+ Keep everything short, simple, and beginner-friendly. Always include exactly 4 cards."""
82
+
83
+
84
+ def extract_json(text):
85
+ """Pulls out the {...} portion of a string, in case the model added extra text around it."""
86
+ start = text.find("{")
87
+ end = text.rfind("}")
88
+ if start == -1 or end == -1:
89
+ raise ValueError("No JSON object found in the model's reply.")
90
+ return text[start:end + 1]
91
+
92
+
93
+ def generate_game_with_llm(theme):
94
+ """Asks the language model to design the game and returns it as a dictionary."""
95
+ user_prompt = f"Theme: {theme}\nDesign the card game now."
96
+ raw_reply = ask_llm(SYSTEM_PROMPT, user_prompt)
97
+
98
+ json_text = extract_json(raw_reply)
99
+ game_data = json.loads(json_text)
100
+
101
+ required_keys = {"title", "description", "rules", "cards"}
102
+ if not required_keys.issubset(game_data.keys()):
103
+ raise ValueError("Model reply is missing required fields.")
104
+ if len(game_data["cards"]) != 4:
105
+ raise ValueError("Model did not return exactly 4 cards.")
106
+
107
+ return game_data
108
+
109
+
110
+ # ---- Fallback generator (used only if the LLM output cannot be used) ----
111
+
112
  TITLE_TEMPLATES = [
113
  "{theme} Wars",
114
  "The {theme} Chronicles",
 
116
  "{theme}: The Card Game",
117
  "Rise of the {theme}",
118
  "{theme} Showdown",
 
 
119
  ]
120
 
121
  DESCRIPTION_TEMPLATES = [
122
  "In this game, players take on the role of characters caught up in a {theme} scenario. Each player uses cards to outsmart their opponents and be the first to win.",
123
  "Players compete in a world inspired by {theme}, drawing and playing cards to gain the upper hand. Strategy and a little luck decide the winner.",
 
124
  ]
125
 
126
  RULES_TEMPLATE = """1. Each player starts with a hand of 4 cards.
 
133
 
134
  NAME_PARTS = [
135
  "Guardian", "Blade", "Shadow", "Spark", "Wanderer", "Sentinel",
136
+ "Rebel", "Trickster", "Champion", "Outcast",
137
  ]
138
 
139
  EFFECT_POOL = [
 
142
  "Steal one card from an opponent's hand.",
143
  "Skip your opponent's next turn.",
144
  "Double your score this round.",
 
 
145
  "Gain +3 power for the rest of the game.",
 
 
146
  ]
147
 
148
 
149
+ def generate_game_template(theme):
150
+ """Local, non-AI backup generator used only if the language model fails."""
151
  theme_clean = theme.strip().title()
152
 
153
  title = random.choice(TITLE_TEMPLATES).format(theme=theme_clean)
 
164
  return {"title": title, "description": description, "rules": rules, "cards": cards}
165
 
166
 
167
+ def generate_game(theme):
168
+ """
169
+ Main entry point. Tries the language model first, and only falls back
170
+ to the template generator if the model's reply could not be used.
171
+ """
172
+ try:
173
+ return generate_game_with_llm(theme)
174
+ except Exception as error:
175
+ print(f"LLM generation could not be used, falling back to templates. Reason: {error}")
176
+ return generate_game_template(theme)
177
+
178
+
179
  # ------------------------------------------------------------
180
  # AI artwork generator
181
  # ------------------------------------------------------------
 
207
 
208
 
209
  def make_placeholder_art(card):
210
+ """A simple backup image used if the AI art request fails, or if the
211
+ LLM invented a card type that has no matching artwork style."""
212
  colors = TYPE_COLORS.get(card["type"], {"bg": (100, 100, 100)})
213
  img = Image.new("RGB", (512, 512), colors["bg"])
214
  draw = ImageDraw.Draw(img)
 
260
 
261
 
262
  def draw_card(card, art_image, theme):
263
+ """Composes the final card image: background, artwork, title bar, type badge, effect text.
264
+ Card type may come from the LLM and not match a preset type; TYPE_COLORS.get()
265
+ below falls back to a neutral gray frame in that case."""
266
  width, height = 500, 700
267
  colors = TYPE_COLORS.get(card["type"], {"bg": (60, 60, 60), "border": (255, 255, 255)})
268
 
 
305
  # ------------------------------------------------------------
306
 
307
  def forge_full_game(theme):
308
+ """Full pipeline: theme -> LLM-generated game text -> AI artwork -> designed card images."""
309
  print(f"Generating game for theme: {theme}")
310
  game_data = generate_game(theme)
311
  print(f"Game created: {game_data['title']}")
 
359
  gr.Markdown("# AI BoardGame Forge")
360
  gr.Markdown(
361
  "Enter a theme, or select one of the example themes below, "
362
+ "then click Generate Game to create a mini card game with AI-generated text and artwork."
363
  )
364
 
365
  theme_input = gr.Textbox(
requirements.txt CHANGED
@@ -1,3 +1,6 @@
1
  gradio
2
  requests
3
  Pillow
 
 
 
 
1
  gradio
2
  requests
3
  Pillow
4
+ transformers
5
+ torch
6
+ accelerate