FarmVille / app.py
broadfield-dev's picture
Create app.py
d869459 verified
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()