Spaces:
Paused
Paused
| 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() |