vesicellular/vesicellular.py

143 lines
4.1 KiB
Python

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()