import enum import pyglet import random import rectangulararray field_width = 60 field_height = 20 max_level = 2 field = rectangulararray.RectangularArray((field_width, field_height), default = int(max_level / 2)) do_update = True run_simulation = True def update(dt): if not do_update or not run_simulation: return deltas = rectangulararray.RectangularArray((field_width, field_height), default = 0) # Calculate deltas for y in range(field_height): for x in range(field_width): # First drop down if (y + 1) in range(field_height) and field[x, y + 1] < max_level: change = min(field[x, y] + deltas [x, y], max_level - field[x, y + 1] - deltas[x, y + 1]) deltas[x, y] -= change deltas[x, y + 1] += change # Then see if any side-neighbour is lower than us left_lower = (x - 1) in range(field_width) and field[x - 1, y] < field[x, y] right_lower = (x + 1) in range(field_width) and field[x + 1, y] < field[x, y] # Move to lower side, or if both have same level, randomly move_left = False move_right = False if left_lower and not right_lower: move_left = True elif not left_lower and right_lower: move_right = True elif left_lower and right_lower: left_value = field[x - 1, y] right_value = field[x + 1, y] move_left = left_value < right_value move_right = left_value > right_value if not move_left and not move_right: move_left = random.randint(0, 1) == 0 move_right = not move_left assert(not (move_left and move_right)) if move_left: change = min(int((field[x, y] + deltas [x, y]) / 2), max_level - field[x - 1, y] - deltas[x - 1, y]) deltas[x, y] -= change deltas[x - 1, y] += change elif move_right: change = min(int((field[x, y] + deltas [x, y]) / 2), max_level - field[x + 1, y] - deltas[x + 1, y]) deltas[x, y] -= change deltas[x + 1, y] += change # Apply deltas for y in range(field_height): for x in range(field_width): field[x, y] += deltas[x, y] window_width = field_width * (max_level * 7 + 1) + 1 window_height = field_height * (max_level * 7 + 1) + 1 window = pyglet.window.Window(window_width, window_height, resizable = True) pyglet.clock.schedule_interval(update, 1/4) def get_cell(x, y): y = window_height - y - 1 cell_x = int((x - 1) * field_width / window_width) cell_y = int((y - 1) * field_height / window_height) return cell_x, cell_y @window.event def on_mouse_press(x, y, button, modifiers): global do_update assert(0 < x <= window_width) assert(0 < y <= window_height) if button == pyglet.window.mouse.LEFT: cell_x, cell_y = get_cell(x, y) field[cell_x, cell_y] = max_level do_update = False elif button == pyglet.window.mouse.RIGHT: cell_x, cell_y = get_cell(x, y) field[cell_x, cell_y] = 0 do_update = False @window.event def on_key_press(symbol, modifiers): global run_simulation if symbol == pyglet.window.key.SPACE: run_simulation = not run_simulation def scale_h(x): return int(x * (window_width - 1) / field_width) def scale_v(y): return int(y * (window_height - 1) / field_height) def draw_cell(coordinates): x1, y1, x2, y2 = coordinates color = (127, 127, 255) # Tranform coordinates from the internal top-left origin to OpenGL's bottom-left origin y1 = window_height - y1 - 1 y2 = window_height - y2 - 1 pyglet.graphics.draw(4, pyglet.gl.GL_QUADS, ('v2i', (x1, y1, x2, y1, x2, y2, x1, y2)), ('c3B', 4*color)) def get_coordinates(x, y, level): assert(0 <= x <= field_width) assert(0 <= y <= field_height) assert(0 <= level <= max_level) screen_x1 = scale_h(x) + 1 screen_y1 = scale_v(y) + 1 screen_y1 += int((max_level - level) / max_level * ((window_height - 1) / field_height - 1)) screen_x2 = scale_h(x + 1) screen_y2 = scale_v(y + 1) return (screen_x1, screen_y1, screen_x2, screen_y2) @window.event def on_draw(): global do_update window.clear() for y in range(field_height): for x in range(field_width): coordinates = get_coordinates(x, y, field[x, y]) draw_cell(coordinates) do_update = True @window.event def on_resize(width, height): global window_width, window_height window_width, window_height = width, height pyglet.app.run()