import gradio as gr import numpy as np from PIL import Image, ImageDraw, ImageFont # Game Constants GRID_SIZE = 10 TILE_SIZE = 50 EMPTY = 0 PLOWED = 1 PLANTED = 2 GROWING = 3 READY = 4 WATERED = 5 CROPS = { 'wheat': {'grow_time': 3, 'value': 10, 'color': '#F4D03F'}, 'carrot': {'grow_time': 5, 'value': 25, 'color': '#E67E22'}, 'corn': {'grow_time': 7, 'value': 50, 'color': '#F1C40F'}, 'tomato': {'grow_time': 6, 'value': 40, 'color': '#E74C3C'} } class FarmingGame: def __init__(self): self.reset() def reset(self): self.grid = [[EMPTY for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)] self.crop_types = [[None for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)] self.crop_days = [[0 for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)] self.money = 100 self.day = 1 self.selected_crop = 'wheat' self.message = "Welcome! Use the grid buttons or coordinates below to farm." def render(self): img = Image.new('RGB', (GRID_SIZE * TILE_SIZE + 200, GRID_SIZE * TILE_SIZE + 100), '#8BC34A') draw = ImageDraw.Draw(img) # Draw title try: font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20) small_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 14) except: font = ImageFont.load_default() small_font = ImageFont.load_default() draw.text((10, 10), f"TopDown Farming Game - Day {self.day}", fill='#2E7D32', font=font) draw.text((10, 40), f"Money: ${self.money} | Selected: {self.selected_crop.title()}", fill='#1B5E20', font=small_font) # Draw game grid offset_y = 80 for y in range(GRID_SIZE): for x in range(GRID_SIZE): px = x * TILE_SIZE + 10 py = y * TILE_SIZE + offset_y cell = self.grid[y][x] # Base tile if cell == EMPTY: color = '#7CB342' # Grass draw.rectangle([px, py, px + TILE_SIZE - 2, py + TILE_SIZE - 2], fill=color, outline='#558B2F') elif cell == PLOWED: color = '#8D6E63' # Soil draw.rectangle([px, py, px + TILE_SIZE - 2, py + TILE_SIZE - 2], fill=color, outline='#5D4037') draw.line([px+5, py+10, px+TILE_SIZE-7, py+10], fill='#5D4037', width=2) draw.line([px+5, py+25, px+TILE_SIZE-7, py+25], fill='#5D4037', width=2) elif cell == PLANTED: crop = self.crop_types[y][x] if crop: draw.rectangle([px, py, px + TILE_SIZE - 2, py + TILE_SIZE - 2], fill='#8D6E63', outline='#5D4037') draw.ellipse([px+18, py+20, px+30, py+32], fill=CROPS[crop]['color']) draw.line([px+24, py+20, px+24, py+15], fill='#4CAF50', width=3) elif cell == GROWING: crop = self.crop_types[y][x] if crop: draw.rectangle([px, py, px + TILE_SIZE - 2, py + TILE_SIZE - 2], fill='#8D6E63', outline='#5D4037') draw.rectangle([px+15, py+10, px+35, py+35], fill=CROPS[crop]['color']) draw.polygon([px+25, py+5, px+15, py+25, px+35, py+25], fill='#66BB6A') elif cell == READY: crop = self.crop_types[y][x] if crop: draw.rectangle([px, py, px + TILE_SIZE - 2, py + TILE_SIZE - 2], fill='#8D6E63', outline='#5D4037') crop_color = CROPS[crop]['color'] draw.ellipse([px+10, py+5, px+40, py+40], fill=crop_color, outline='#BF360C') # Add some shine dots draw.ellipse([px+15, py+10, px+18, py+13], fill='white') draw.ellipse([px+35, py+15, px+38, py+18], fill='white') elif cell == WATERED: draw.rectangle([px, py, px + TILE_SIZE - 2, py + TILE_SIZE - 2], fill='#5D4037', outline='#3E2723') for i in range(4): dx = px + 8 + i * 10 dy = py + 15 + (i % 2) * 10 draw.ellipse([dx, dy, dx+4, dy+4], fill='#29B6F6') # Draw control panel panel_x = GRID_SIZE * TILE_SIZE + 30 draw.rectangle([panel_x, offset_y, panel_x + 170, offset_y + 400], fill='#F1F8E9', outline='#558B2F') draw.text((panel_x + 10, offset_y + 10), "Legend:", fill='#33691E', font=font) legend = [ "Green = Grass", "Brown = Soil", "O/Y/R Circle = Crop", "Blue dots = Watered", "Sparkle = Ready!" ] for i, item in enumerate(legend): draw.text((panel_x + 10, offset_y + 45 + i * 25), item, fill='#424242', font=small_font) # Draw message area msg_y = offset_y + 220 draw.rectangle([10, msg_y, GRID_SIZE * TILE_SIZE + 10, msg_y + 60], fill='#FFF8E1', outline='#F57F17') words = self.message.split() lines = [] current_line = [] for word in words: current_line.append(word) test_line = ' '.join(current_line) try: bbox = draw.textbbox((0, 0), test_line, font=small_font) if bbox[2] - bbox[0] > GRID_SIZE * TILE_SIZE - 20: lines.append(' '.join(current_line[:-1])) current_line = [current_line[-1]] except: pass if current_line: lines.append(' '.join(current_line)) for i, line in enumerate(lines[:3]): draw.text((15, msg_y + 5 + i * 18), line, fill='#E65100', font=small_font) return img def next_day(self): self.day += 1 self.message = f"Day {self.day} begins!" for y in range(GRID_SIZE): for x in range(GRID_SIZE): if self.grid[y][x] == WATERED: current = self.crop_days[y][x] crop = self.crop_types[y][x] if crop and current >= CROPS[crop]['grow_time']: self.grid[y][x] = READY self.message = f"A {crop} is ready to harvest at ({x+1}, {y+1})!" elif crop and current >= CROPS[crop]['grow_time'] // 2: self.grid[y][x] = GROWING self.crop_days[y][x] += 1 else: self.grid[y][x] = PLANTED self.crop_days[y][x] += 1 return self.render() # Create game instance game = FarmingGame() def reset_game(): game.reset() return game.render(), f"Day: {game.day} | Money: ${game.money} | Message: {game.message}" def select_crop(crop): game.selected_crop = crop game.message = f"Selected {crop.title()}. Cost: $5, Value: ${CROPS[crop]['value']}" return game.render(), f"Day: {game.day} | Money: ${game.money} | Message: {game.message}" def manual_action(x, y, action): if x is None or y is None: game.message = "Please enter coordinates!" return game.render(), f"Day: {game.day} | Money: ${game.money} | Message: {game.message}" x = int(x) y = int(y) if not (0 <= x < GRID_SIZE and 0 <= y < GRID_SIZE): game.message = f"Invalid coordinates! Use 0-9 for both X and Y." return game.render(), f"Day: {game.day} | Money: ${game.money} | Message: {game.message}" cell = game.grid[y][x] if action == "Plow" and cell == EMPTY: game.grid[y][x] = PLOWED game.message = f"Plowed soil at ({x+1}, {y+1})" elif action == "Plant" and cell == PLOWED: if game.money >= 5: game.grid[y][x] = PLANTED game.crop_types[y][x] = game.selected_crop game.crop_days[y][x] = 0 game.money -= 5 game.message = f"Planted {game.selected_crop} at ({x+1}, {y+1})" else: game.message = "Not enough money for seeds! ($5 needed)" elif action == "Water": if cell == PLANTED or cell == GROWING: game.grid[y][x] = WATERED game.crop_days[y][x] += 1 game.message = f"Watered crop at ({x+1}, {y+1})" elif cell == WATERED: game.message = "Already watered! Wait for next day." else: game.message = "Nothing to water here!" elif action == "Harvest" and cell == READY: crop = game.crop_types[y][x] value = CROPS[crop]['value'] game.money += value game.grid[y][x] = EMPTY game.crop_types[y][x] = None game.crop_days[y][x] = 0 game.message = f"Harvested {crop} for ${value}! Total: ${game.money}" else: game.message = f"Cannot {action.lower()} here! Cell is at state {cell}" return game.render(), f"Day: {game.day} | Money: ${game.money} | Message: {game.message}" def next_day(): game.next_day() return game.render(), f"Day: {game.day} | Money: ${game.money} | Message: {game.message}" # Create Gradio interface with gr.Blocks(css=""" .game-container { text-align: center; } .info-box { background: #FFF8E1; padding: 15px; border-radius: 8px; margin: 10px; border: 2px solid #F57F17; } .coord-input { max-width: 80px; } """) as demo: gr.Markdown("# TopDown Farming Game") gr.Markdown("Enter coordinates (0-9 for X and Y) and select an action to play!") with gr.Row(): with gr.Column(scale=2): game_image = gr.Image(value=game.render(), label="Your Farm", interactive=False) with gr.Column(scale=1): gr.Markdown("### Crop Types") with gr.Row(): wheat_btn = gr.Button("Wheat ($5)", variant="secondary") carrot_btn = gr.Button("Carrot ($5)", variant="secondary") with gr.Row(): corn_btn = gr.Button("Corn ($5)", variant="secondary") tomato_btn = gr.Button("Tomato ($5)", variant="secondary") gr.Markdown("### Coordinates & Action") with gr.Row(): x_coord = gr.Number(label="X (0-9)", value=0, minimum=0, maximum=9, step=1, elem_classes=["coord-input"]) y_coord = gr.Number(label="Y (0-9)", value=0, minimum=0, maximum=9, step=1, elem_classes=["coord-input"]) action_dropdown = gr.Dropdown( choices=["Plow", "Plant", "Water", "Harvest"], value="Plow", label="Action" ) apply_btn = gr.Button("Apply Action", variant="primary") next_day_btn = gr.Button("Next Day", variant="primary") reset_btn = gr.Button("Reset Game", variant="secondary") # Status display status_text = gr.Textbox( value=f"Day: {game.day} | Money: ${game.money} | Message: {game.message}", label="Game Status", interactive=False, elem_classes=["info-box"] ) gr.Markdown(""" ### How to Play: 1. **Plow**: Select Plow action, enter X,Y coordinates, click Apply 2. **Plant**: Select a crop type above, then Plant action on plowed soil (costs $5) 3. **Water**: Select Water action on planted crops 4. **Next Day**: Click to advance time and grow crops 5. **Harvest**: Select Harvest action on mature crops for profit! | Crop | Grow Time | Sell Value | |------|-----------|------------| | Wheat | 3 days | $10 | | Carrot | 5 days | $25 | | Corn | 7 days | $50 | | Tomato | 6 days | $40 | """) # Event handlers wheat_btn.click(fn=lambda: select_crop('wheat'), outputs=[game_image, status_text]) carrot_btn.click(fn=lambda: select_crop('carrot'), outputs=[game_image, status_text]) corn_btn.click(fn=lambda: select_crop('corn'), outputs=[game_image, status_text]) tomato_btn.click(fn=lambda: select_crop('tomato'), outputs=[game_image, status_text]) apply_btn.click( fn=manual_action, inputs=[x_coord, y_coord, action_dropdown], outputs=[game_image, status_text] ) next_day_btn.click(fn=next_day, outputs=[game_image, status_text]) reset_btn.click(fn=reset_game, outputs=[game_image, status_text]) demo.launch()