程序洞穴--元胞自动机#

procedural_caves_cellular.py#
1"""
2This example procedurally develops a random cave based on cellular automata.
3
4For more information, see:
5https://gamedevelopment.tutsplus.com/tutorials/generate-random-cave-levels-using-cellular-automata--gamedev-9664
6
7If Python and Arcade are installed, this example can be run from the command line with:
8python -m arcade.examples.procedural_caves_cellular
9"""
10
11import random
12import arcade
13import timeit
14
15# Sprite scaling. Make this larger, like 0.5 to zoom in and add
16# 'mystery' to what you can see. Make it smaller, like 0.1 to see
17# more of the map.
18SPRITE_SCALING = 0.25
19SPRITE_SIZE = 128 * SPRITE_SCALING
20
21# How big the grid is
22GRID_WIDTH = 450
23GRID_HEIGHT = 400
24
25# Parameters for cellular automata
26CHANCE_TO_START_ALIVE = 0.4
27DEATH_LIMIT = 3
28BIRTH_LIMIT = 4
29NUMBER_OF_STEPS = 4
30
31# How fast the player moves
32MOVEMENT_SPEED = 5
33
34# How close the player can get to the edge before we scroll.
35VIEWPORT_MARGIN = 300
36
37# How big the window is
38WINDOW_WIDTH = 800
39WINDOW_HEIGHT = 600
40WINDOW_TITLE = "Procedural Caves Cellular Automata Example"
41
42# How fast the camera pans to the player. 1.0 is instant.
43CAMERA_SPEED = 0.1
44
45
46def create_grid(width, height):
47 """ Create a two-dimensional grid of specified size. """
48 return [[0 for _x in range(width)] for _y in range(height)]
49
50
51def initialize_grid(grid):
52 """ Randomly set grid locations to on/off based on chance. """
53 height = len(grid)
54 width = len(grid[0])
55 for row in range(height):
56 for column in range(width):
57 if random.random() <= CHANCE_TO_START_ALIVE:
58 grid[row][column] = 1
59
60
61def count_alive_neighbors(grid, x, y):
62 """ Count neighbors that are alive. """
63 height = len(grid)
64 width = len(grid[0])
65 alive_count = 0
66 for i in range(-1, 2):
67 for j in range(-1, 2):
68 neighbor_x = x + i
69 neighbor_y = y + j
70 if i == 0 and j == 0:
71 continue
72 elif neighbor_x < 0 or neighbor_y < 0 or neighbor_y >= height or neighbor_x >= width:
73 # Edges are considered alive. Makes map more likely to appear naturally closed.
74 alive_count += 1
75 elif grid[neighbor_y][neighbor_x] == 1:
76 alive_count += 1
77 return alive_count
78
79
80def do_simulation_step(old_grid):
81 """ Run a step of the cellular automaton. """
82 height = len(old_grid)
83 width = len(old_grid[0])
84 new_grid = create_grid(width, height)
85 for x in range(width):
86 for y in range(height):
87 alive_neighbors = count_alive_neighbors(old_grid, x, y)
88 if old_grid[y][x] == 1:
89 if alive_neighbors < DEATH_LIMIT:
90 new_grid[y][x] = 0
91 else:
92 new_grid[y][x] = 1
93 else:
94 if alive_neighbors > BIRTH_LIMIT:
95 new_grid[y][x] = 1
96 else:
97 new_grid[y][x] = 0
98 return new_grid
99
100
101class InstructionView(arcade.View):
102 """ View to show instructions """
103
104 def __init__(self):
105 super().__init__()
106 self.frame_count = 0
107
108 def on_show_view(self):
109 """ This is run once when we switch to this view """
110 self.window.background_color = arcade.csscolor.DARK_SLATE_BLUE
111
112 # Reset the viewport, necessary if we have a scrolling game and we need
113 # to reset the viewport back to the start so we can see what we draw.
114 self.window.default_camera.use()
115
116 def on_draw(self):
117 """ Draw this view """
118 self.clear()
119 arcade.draw_text("Loading...", self.window.width / 2, self.window.height / 2,
120 arcade.color.BLACK, font_size=50, anchor_x="center")
121
122 def on_update(self, dt):
123 if self.frame_count == 0:
124 self.frame_count += 1
125 return
126
127 """ If the user presses the mouse button, start the game. """
128 game_view = GameView()
129 game_view.setup()
130 self.window.show_view(game_view)
131
132
133class GameView(arcade.View):
134 """
135 Main application class.
136 """
137
138 def __init__(self):
139 super().__init__()
140
141 self.grid = None
142 self.wall_list = None
143 self.player_list = None
144 self.player_sprite = None
145 self.draw_time = 0
146 self.processing_time = 0
147 self.physics_engine = None
148
149 # Track the current state of what key is pressed
150 self.left_pressed = False
151 self.right_pressed = False
152 self.up_pressed = False
153 self.down_pressed = False
154
155 # Create the cameras. One for the GUI, one for the sprites.
156 # We scroll the 'sprite world' but not the GUI.
157 self.camera_sprites = arcade.camera.Camera2D()
158 self.camera_gui = arcade.camera.Camera2D()
159
160 self.window.background_color = arcade.color.BLACK
161
162 self.sprite_count_text = None
163 self.draw_time_text = None
164 self.processing_time_text = None
165
166 def setup(self):
167 self.wall_list = arcade.SpriteList(use_spatial_hash=True)
168 self.player_list = arcade.SpriteList()
169
170 # Create cave system using a 2D grid
171 self.grid = create_grid(GRID_WIDTH, GRID_HEIGHT)
172 initialize_grid(self.grid)
173 for step in range(NUMBER_OF_STEPS):
174 self.grid = do_simulation_step(self.grid)
175
176 texture = arcade.load_texture(":resources:images/tiles/grassCenter.png")
177 # Create sprites based on 2D grid
178 # Each grid location is a sprite.
179 for row in range(GRID_HEIGHT):
180 for column in range(GRID_WIDTH):
181 if self.grid[row][column] == 1:
182 wall = arcade.BasicSprite(texture, scale=SPRITE_SCALING)
183 wall.center_x = column * SPRITE_SIZE + SPRITE_SIZE / 2
184 wall.center_y = row * SPRITE_SIZE + SPRITE_SIZE / 2
185 self.wall_list.append(wall)
186
187 # Set up the player
188 self.player_sprite = arcade.Sprite(
189 ":resources:images/animated_characters/female_person/femalePerson_idle.png",
190 scale=SPRITE_SCALING)
191 self.player_list.append(self.player_sprite)
192
193 # Randomly place the player. If we are in a wall, repeat until we aren't.
194 placed = False
195 while not placed:
196
197 # Randomly position
198 max_x = int(GRID_WIDTH * SPRITE_SIZE)
199 max_y = int(GRID_HEIGHT * SPRITE_SIZE)
200 self.player_sprite.center_x = random.randrange(max_x)
201 self.player_sprite.center_y = random.randrange(max_y)
202
203 # Are we in a wall?
204 walls_hit = arcade.check_for_collision_with_list(self.player_sprite, self.wall_list)
205 if len(walls_hit) == 0:
206 # Not in a wall! Success!
207 placed = True
208
209 self.scroll_to_player(1.0)
210
211 self.physics_engine = arcade.PhysicsEngineSimple(self.player_sprite,
212 self.wall_list)
213
214 # Draw info on the screen
215 sprite_count = len(self.wall_list)
216 output = f"Sprite Count: {sprite_count:,}"
217 self.sprite_count_text = arcade.Text(output,
218 20,
219 self.window.height - 20,
220 arcade.color.WHITE, 16)
221
222 output = "Drawing time:"
223 self.draw_time_text = arcade.Text(output,
224 20,
225 self.window.height - 40,
226 arcade.color.WHITE, 16)
227
228 output = "Processing time:"
229 self.processing_time_text = arcade.Text(output,
230 20,
231 self.window.height - 60,
232 arcade.color.WHITE, 16)
233
234 def on_draw(self):
235 """ Render the screen. """
236
237 # Start timing how long this takes
238 draw_start_time = timeit.default_timer()
239
240 # This command should happen before we start drawing. It will clear
241 # the screen to the background color, and erase what we drew last frame.
242 self.clear()
243
244 # Select the camera we'll use to draw all our sprites
245 self.camera_sprites.use()
246
247 # Draw the sprites
248 self.wall_list.draw(pixelated=True)
249 self.player_list.draw()
250
251 # Select the (unscrolled) camera for our GUI
252 self.camera_gui.use()
253
254 self.sprite_count_text.draw()
255 output = f"Drawing time: {self.draw_time:.3f}"
256 self.draw_time_text.text = output
257 self.draw_time_text.draw()
258
259 output = f"Processing time: {self.processing_time:.3f}"
260 self.processing_time_text.text = output
261 self.processing_time_text.draw()
262
263 self.draw_time = timeit.default_timer() - draw_start_time
264
265 def update_player_speed(self):
266
267 # Calculate speed based on the keys pressed
268 self.player_sprite.change_x = 0
269 self.player_sprite.change_y = 0
270
271 if self.up_pressed and not self.down_pressed:
272 self.player_sprite.change_y = MOVEMENT_SPEED
273 elif self.down_pressed and not self.up_pressed:
274 self.player_sprite.change_y = -MOVEMENT_SPEED
275 if self.left_pressed and not self.right_pressed:
276 self.player_sprite.change_x = -MOVEMENT_SPEED
277 elif self.right_pressed and not self.left_pressed:
278 self.player_sprite.change_x = MOVEMENT_SPEED
279
280 def on_key_press(self, key, modifiers):
281 """Called whenever a key is pressed. """
282
283 if key == arcade.key.UP:
284 self.up_pressed = True
285 elif key == arcade.key.DOWN:
286 self.down_pressed = True
287 elif key == arcade.key.LEFT:
288 self.left_pressed = True
289 elif key == arcade.key.RIGHT:
290 self.right_pressed = True
291
292 def on_key_release(self, key, modifiers):
293 """Called when the user releases a key. """
294
295 if key == arcade.key.UP:
296 self.up_pressed = False
297 elif key == arcade.key.DOWN:
298 self.down_pressed = False
299 elif key == arcade.key.LEFT:
300 self.left_pressed = False
301 elif key == arcade.key.RIGHT:
302 self.right_pressed = False
303
304 def scroll_to_player(self, speed=CAMERA_SPEED):
305 """
306 Scroll the window to the player.
307
308 if CAMERA_SPEED is 1, the camera will immediately move to the desired position.
309 Anything between 0 and 1 will have the camera move to the location with a smoother
310 pan.
311 """
312
313 position = (self.player_sprite.center_x, self.player_sprite.center_y)
314 arcade.camera.controllers.simple_follow_2D(speed, position, self.camera_sprites.view_data)
315
316 def on_resize(self, width: int, height: int):
317 """
318 Resize window
319 Handle the user grabbing the edge and resizing the window.
320 """
321 super().on_resize(width, height)
322 self.camera_sprites.match_screen(and_projection=True)
323 self.camera_gui.match_screen(and_projection=True)
324
325 def on_update(self, delta_time):
326 """ Movement and game logic """
327
328 start_time = timeit.default_timer()
329
330 # Call update on all sprites (The sprites don't do much in this
331 # example though.)
332 self.update_player_speed()
333 self.physics_engine.update()
334
335 # Scroll the screen to the player
336 self.scroll_to_player()
337
338 # Save the time it took to do this.
339 self.processing_time = timeit.default_timer() - start_time
340
341
342def main():
343 window = arcade.Window(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE, resizable=True)
344 start_view = InstructionView()
345 window.show_view(start_view)
346 arcade.run()
347
348
349if __name__ == "__main__":
350 main()