broadfield-dev commited on
Commit
d869459
·
verified ·
1 Parent(s): 7b41d95

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +298 -0
app.py ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ from PIL import Image, ImageDraw, ImageFont
4
+
5
+ # Game Constants
6
+ GRID_SIZE = 10
7
+ TILE_SIZE = 50
8
+ EMPTY = 0
9
+ PLOWED = 1
10
+ PLANTED = 2
11
+ GROWING = 3
12
+ READY = 4
13
+ WATERED = 5
14
+
15
+ CROPS = {
16
+ 'wheat': {'grow_time': 3, 'value': 10, 'color': '#F4D03F'},
17
+ 'carrot': {'grow_time': 5, 'value': 25, 'color': '#E67E22'},
18
+ 'corn': {'grow_time': 7, 'value': 50, 'color': '#F1C40F'},
19
+ 'tomato': {'grow_time': 6, 'value': 40, 'color': '#E74C3C'}
20
+ }
21
+
22
+ class FarmingGame:
23
+ def __init__(self):
24
+ self.reset()
25
+
26
+ def reset(self):
27
+ self.grid = [[EMPTY for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
28
+ self.crop_types = [[None for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
29
+ self.crop_days = [[0 for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
30
+ self.money = 100
31
+ self.day = 1
32
+ self.selected_crop = 'wheat'
33
+ self.message = "Welcome! Use the grid buttons or coordinates below to farm."
34
+
35
+ def render(self):
36
+ img = Image.new('RGB', (GRID_SIZE * TILE_SIZE + 200, GRID_SIZE * TILE_SIZE + 100), '#8BC34A')
37
+ draw = ImageDraw.Draw(img)
38
+
39
+ # Draw title
40
+ try:
41
+ font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20)
42
+ small_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 14)
43
+ except:
44
+ font = ImageFont.load_default()
45
+ small_font = ImageFont.load_default()
46
+
47
+ draw.text((10, 10), f"TopDown Farming Game - Day {self.day}", fill='#2E7D32', font=font)
48
+ draw.text((10, 40), f"Money: ${self.money} | Selected: {self.selected_crop.title()}", fill='#1B5E20', font=small_font)
49
+
50
+ # Draw game grid
51
+ offset_y = 80
52
+ for y in range(GRID_SIZE):
53
+ for x in range(GRID_SIZE):
54
+ px = x * TILE_SIZE + 10
55
+ py = y * TILE_SIZE + offset_y
56
+
57
+ cell = self.grid[y][x]
58
+
59
+ # Base tile
60
+ if cell == EMPTY:
61
+ color = '#7CB342' # Grass
62
+ draw.rectangle([px, py, px + TILE_SIZE - 2, py + TILE_SIZE - 2], fill=color, outline='#558B2F')
63
+ elif cell == PLOWED:
64
+ color = '#8D6E63' # Soil
65
+ draw.rectangle([px, py, px + TILE_SIZE - 2, py + TILE_SIZE - 2], fill=color, outline='#5D4037')
66
+ draw.line([px+5, py+10, px+TILE_SIZE-7, py+10], fill='#5D4037', width=2)
67
+ draw.line([px+5, py+25, px+TILE_SIZE-7, py+25], fill='#5D4037', width=2)
68
+ elif cell == PLANTED:
69
+ crop = self.crop_types[y][x]
70
+ if crop:
71
+ draw.rectangle([px, py, px + TILE_SIZE - 2, py + TILE_SIZE - 2], fill='#8D6E63', outline='#5D4037')
72
+ draw.ellipse([px+18, py+20, px+30, py+32], fill=CROPS[crop]['color'])
73
+ draw.line([px+24, py+20, px+24, py+15], fill='#4CAF50', width=3)
74
+ elif cell == GROWING:
75
+ crop = self.crop_types[y][x]
76
+ if crop:
77
+ draw.rectangle([px, py, px + TILE_SIZE - 2, py + TILE_SIZE - 2], fill='#8D6E63', outline='#5D4037')
78
+ draw.rectangle([px+15, py+10, px+35, py+35], fill=CROPS[crop]['color'])
79
+ draw.polygon([px+25, py+5, px+15, py+25, px+35, py+25], fill='#66BB6A')
80
+ elif cell == READY:
81
+ crop = self.crop_types[y][x]
82
+ if crop:
83
+ draw.rectangle([px, py, px + TILE_SIZE - 2, py + TILE_SIZE - 2], fill='#8D6E63', outline='#5D4037')
84
+ crop_color = CROPS[crop]['color']
85
+ draw.ellipse([px+10, py+5, px+40, py+40], fill=crop_color, outline='#BF360C')
86
+ # Add some shine dots
87
+ draw.ellipse([px+15, py+10, px+18, py+13], fill='white')
88
+ draw.ellipse([px+35, py+15, px+38, py+18], fill='white')
89
+ elif cell == WATERED:
90
+ draw.rectangle([px, py, px + TILE_SIZE - 2, py + TILE_SIZE - 2], fill='#5D4037', outline='#3E2723')
91
+ for i in range(4):
92
+ dx = px + 8 + i * 10
93
+ dy = py + 15 + (i % 2) * 10
94
+ draw.ellipse([dx, dy, dx+4, dy+4], fill='#29B6F6')
95
+
96
+ # Draw control panel
97
+ panel_x = GRID_SIZE * TILE_SIZE + 30
98
+ draw.rectangle([panel_x, offset_y, panel_x + 170, offset_y + 400], fill='#F1F8E9', outline='#558B2F')
99
+ draw.text((panel_x + 10, offset_y + 10), "Legend:", fill='#33691E', font=font)
100
+
101
+ legend = [
102
+ "Green = Grass",
103
+ "Brown = Soil",
104
+ "O/Y/R Circle = Crop",
105
+ "Blue dots = Watered",
106
+ "Sparkle = Ready!"
107
+ ]
108
+ for i, item in enumerate(legend):
109
+ draw.text((panel_x + 10, offset_y + 45 + i * 25), item, fill='#424242', font=small_font)
110
+
111
+ # Draw message area
112
+ msg_y = offset_y + 220
113
+ draw.rectangle([10, msg_y, GRID_SIZE * TILE_SIZE + 10, msg_y + 60], fill='#FFF8E1', outline='#F57F17')
114
+
115
+ words = self.message.split()
116
+ lines = []
117
+ current_line = []
118
+ for word in words:
119
+ current_line.append(word)
120
+ test_line = ' '.join(current_line)
121
+ try:
122
+ bbox = draw.textbbox((0, 0), test_line, font=small_font)
123
+ if bbox[2] - bbox[0] > GRID_SIZE * TILE_SIZE - 20:
124
+ lines.append(' '.join(current_line[:-1]))
125
+ current_line = [current_line[-1]]
126
+ except:
127
+ pass
128
+ if current_line:
129
+ lines.append(' '.join(current_line))
130
+
131
+ for i, line in enumerate(lines[:3]):
132
+ draw.text((15, msg_y + 5 + i * 18), line, fill='#E65100', font=small_font)
133
+
134
+ return img
135
+
136
+ def next_day(self):
137
+ self.day += 1
138
+ self.message = f"Day {self.day} begins!"
139
+
140
+ for y in range(GRID_SIZE):
141
+ for x in range(GRID_SIZE):
142
+ if self.grid[y][x] == WATERED:
143
+ current = self.crop_days[y][x]
144
+ crop = self.crop_types[y][x]
145
+
146
+ if crop and current >= CROPS[crop]['grow_time']:
147
+ self.grid[y][x] = READY
148
+ self.message = f"A {crop} is ready to harvest at ({x+1}, {y+1})!"
149
+ elif crop and current >= CROPS[crop]['grow_time'] // 2:
150
+ self.grid[y][x] = GROWING
151
+ self.crop_days[y][x] += 1
152
+ else:
153
+ self.grid[y][x] = PLANTED
154
+ self.crop_days[y][x] += 1
155
+
156
+ return self.render()
157
+
158
+ # Create game instance
159
+ game = FarmingGame()
160
+
161
+ def reset_game():
162
+ game.reset()
163
+ return game.render(), f"Day: {game.day} | Money: ${game.money} | Message: {game.message}"
164
+
165
+ def select_crop(crop):
166
+ game.selected_crop = crop
167
+ game.message = f"Selected {crop.title()}. Cost: $5, Value: ${CROPS[crop]['value']}"
168
+ return game.render(), f"Day: {game.day} | Money: ${game.money} | Message: {game.message}"
169
+
170
+ def manual_action(x, y, action):
171
+ if x is None or y is None:
172
+ game.message = "Please enter coordinates!"
173
+ return game.render(), f"Day: {game.day} | Money: ${game.money} | Message: {game.message}"
174
+
175
+ x = int(x)
176
+ y = int(y)
177
+
178
+ if not (0 <= x < GRID_SIZE and 0 <= y < GRID_SIZE):
179
+ game.message = f"Invalid coordinates! Use 0-9 for both X and Y."
180
+ return game.render(), f"Day: {game.day} | Money: ${game.money} | Message: {game.message}"
181
+
182
+ cell = game.grid[y][x]
183
+
184
+ if action == "Plow" and cell == EMPTY:
185
+ game.grid[y][x] = PLOWED
186
+ game.message = f"Plowed soil at ({x+1}, {y+1})"
187
+ elif action == "Plant" and cell == PLOWED:
188
+ if game.money >= 5:
189
+ game.grid[y][x] = PLANTED
190
+ game.crop_types[y][x] = game.selected_crop
191
+ game.crop_days[y][x] = 0
192
+ game.money -= 5
193
+ game.message = f"Planted {game.selected_crop} at ({x+1}, {y+1})"
194
+ else:
195
+ game.message = "Not enough money for seeds! ($5 needed)"
196
+ elif action == "Water":
197
+ if cell == PLANTED or cell == GROWING:
198
+ game.grid[y][x] = WATERED
199
+ game.crop_days[y][x] += 1
200
+ game.message = f"Watered crop at ({x+1}, {y+1})"
201
+ elif cell == WATERED:
202
+ game.message = "Already watered! Wait for next day."
203
+ else:
204
+ game.message = "Nothing to water here!"
205
+ elif action == "Harvest" and cell == READY:
206
+ crop = game.crop_types[y][x]
207
+ value = CROPS[crop]['value']
208
+ game.money += value
209
+ game.grid[y][x] = EMPTY
210
+ game.crop_types[y][x] = None
211
+ game.crop_days[y][x] = 0
212
+ game.message = f"Harvested {crop} for ${value}! Total: ${game.money}"
213
+ else:
214
+ game.message = f"Cannot {action.lower()} here! Cell is at state {cell}"
215
+
216
+ return game.render(), f"Day: {game.day} | Money: ${game.money} | Message: {game.message}"
217
+
218
+ def next_day():
219
+ game.next_day()
220
+ return game.render(), f"Day: {game.day} | Money: ${game.money} | Message: {game.message}"
221
+
222
+ # Create Gradio interface
223
+ with gr.Blocks(css="""
224
+ .game-container { text-align: center; }
225
+ .info-box { background: #FFF8E1; padding: 15px; border-radius: 8px; margin: 10px; border: 2px solid #F57F17; }
226
+ .coord-input { max-width: 80px; }
227
+ """) as demo:
228
+ gr.Markdown("# TopDown Farming Game")
229
+ gr.Markdown("Enter coordinates (0-9 for X and Y) and select an action to play!")
230
+
231
+ with gr.Row():
232
+ with gr.Column(scale=2):
233
+ game_image = gr.Image(value=game.render(), label="Your Farm", interactive=False)
234
+
235
+ with gr.Column(scale=1):
236
+ gr.Markdown("### Crop Types")
237
+ with gr.Row():
238
+ wheat_btn = gr.Button("Wheat ($5)", variant="secondary")
239
+ carrot_btn = gr.Button("Carrot ($5)", variant="secondary")
240
+ with gr.Row():
241
+ corn_btn = gr.Button("Corn ($5)", variant="secondary")
242
+ tomato_btn = gr.Button("Tomato ($5)", variant="secondary")
243
+
244
+ gr.Markdown("### Coordinates & Action")
245
+ with gr.Row():
246
+ x_coord = gr.Number(label="X (0-9)", value=0, minimum=0, maximum=9, step=1, elem_classes=["coord-input"])
247
+ y_coord = gr.Number(label="Y (0-9)", value=0, minimum=0, maximum=9, step=1, elem_classes=["coord-input"])
248
+
249
+ action_dropdown = gr.Dropdown(
250
+ choices=["Plow", "Plant", "Water", "Harvest"],
251
+ value="Plow",
252
+ label="Action"
253
+ )
254
+
255
+ apply_btn = gr.Button("Apply Action", variant="primary")
256
+ next_day_btn = gr.Button("Next Day", variant="primary")
257
+ reset_btn = gr.Button("Reset Game", variant="secondary")
258
+
259
+ # Status display
260
+ status_text = gr.Textbox(
261
+ value=f"Day: {game.day} | Money: ${game.money} | Message: {game.message}",
262
+ label="Game Status",
263
+ interactive=False,
264
+ elem_classes=["info-box"]
265
+ )
266
+
267
+ gr.Markdown("""
268
+ ### How to Play:
269
+ 1. **Plow**: Select Plow action, enter X,Y coordinates, click Apply
270
+ 2. **Plant**: Select a crop type above, then Plant action on plowed soil (costs $5)
271
+ 3. **Water**: Select Water action on planted crops
272
+ 4. **Next Day**: Click to advance time and grow crops
273
+ 5. **Harvest**: Select Harvest action on mature crops for profit!
274
+
275
+ | Crop | Grow Time | Sell Value |
276
+ |------|-----------|------------|
277
+ | Wheat | 3 days | $10 |
278
+ | Carrot | 5 days | $25 |
279
+ | Corn | 7 days | $50 |
280
+ | Tomato | 6 days | $40 |
281
+ """)
282
+
283
+ # Event handlers
284
+ wheat_btn.click(fn=lambda: select_crop('wheat'), outputs=[game_image, status_text])
285
+ carrot_btn.click(fn=lambda: select_crop('carrot'), outputs=[game_image, status_text])
286
+ corn_btn.click(fn=lambda: select_crop('corn'), outputs=[game_image, status_text])
287
+ tomato_btn.click(fn=lambda: select_crop('tomato'), outputs=[game_image, status_text])
288
+
289
+ apply_btn.click(
290
+ fn=manual_action,
291
+ inputs=[x_coord, y_coord, action_dropdown],
292
+ outputs=[game_image, status_text]
293
+ )
294
+
295
+ next_day_btn.click(fn=next_day, outputs=[game_image, status_text])
296
+ reset_btn.click(fn=reset_game, outputs=[game_image, status_text])
297
+
298
+ demo.launch()