## Conway's Game of Life ## Rule 1 - Any live cell with fewer than two live neighbours dies (underpopulation) ## Rule 2 - Any live cell with two or three live neighbours lives ## Rule 3 - Any live cell with more than three live neighbours dies (overpopulation) ## Rule 4 - Any dead cell with exactly three live neighbours becomes a live cell (reproduction) extends Node2D enum CellStates { DEAD, ALIVE, } ## UI @onready var background_ui: Control = $UI/Background @onready var debug_ui: MarginContainer = $UI/Debug @onready var debug_generation_counter: Label = $UI/Debug/VBoxContainer/GenerationCounter @onready var debug_living_cells_counter: Label = $UI/Debug/VBoxContainer/LivingCellsCounter @onready var debug_world_seed: Label = $UI/Debug/VBoxContainer/WorldSeed @onready var generation_ui: MarginContainer = $UI/WorldGeneration @onready var generation_seed: LineEdit = $UI/WorldGeneration/VBoxContainer/Seed/Input @onready var generation_world_size_x: LineEdit = $UI/WorldGeneration/VBoxContainer/WorldSize/Input_x @onready var generation_world_size_y: LineEdit = $UI/WorldGeneration/VBoxContainer/WorldSize/Input_y @onready var gneration_cell_size_x: LineEdit = $UI/WorldGeneration/VBoxContainer/CellSize/Input_x @onready var gneration_cell_size_y: LineEdit = $UI/WorldGeneration/VBoxContainer/CellSize/Input_y ## Other @onready var camera: Camera2D = $Camera2D @onready var generation_timer: Timer = $GenerationTimer @export var world_seed: int @export var cell_size: Vector2 = Vector2(16, 16) @export var cell_texture: Texture2D @export var world_size: Vector2 = Vector2(32, 32) var cell_instance var generation: int = 1 var is_paused: int = false var total_living: int = 0 var world: Array = [] func _ready() -> void: update_generation_ui() debug_ui.visible = false background_ui.visible = true generation_ui.visible = true func _input(event: InputEvent) -> void: if event.is_action_pressed("Pause"): is_paused = !is_paused background_ui.visible = is_paused if event.is_action_pressed("Menu"): is_paused = true background_ui.visible = true generation_ui.visible = true update_generation_ui() # # UI # ## Update the Gneration UI with the current settings func update_generation_ui() -> void: if world_seed: generation_seed.text = str(world_seed) else: generation_seed.text = str(randi()) generation_world_size_x.text = str(world_size.x) generation_world_size_y.text = str(world_size.y) gneration_cell_size_x.text = str(cell_size.x) gneration_cell_size_x.text = str(cell_size.y) # # Conway Specific # func start_conway() -> void: debug_world_seed.text = "World Seeed: %s" % world_seed is_paused = false generate_world() generation_timer.start() # Center the camera on the world camera.position.x = world_size.x * cell_size.x / 2 camera.position.y = world_size.y * cell_size.y / 2 ## Check a cell against the Conway rules and return True if cell shoudl be alive ## The logic in this could be cleaned up pretty easily. Only verbose for understanding. func cell_is_alive(pos: Vector2) -> bool: var neighbors := count_living_neighbors(pos) var currently_alive = world[pos.x][pos.y] is RID # Rule 1 - Any live cell with fewer than two live neighbours dies (underpopulation) if currently_alive and neighbors < 2: return false # Rule 2 - Any live cell with two or three live neighbours lives elif currently_alive and (neighbors == 2 or neighbors == 3): return true # Rule 3 - Any live cell with more than three live neighbours dies (overpopulation) elif currently_alive and neighbors > 3: return false # Rule 4 - Any dead cell with exactly three live neighbours becomes a live cell (reproduction) elif not currently_alive and neighbors == 3: return true return false ## Count all neighbors surrounding a cell func count_living_neighbors(pos: Vector2) -> int: var count := 0 @warning_ignore("incompatible_ternary") var x_min = 0 if pos.x - 1 < 0 else pos.x - 1 @warning_ignore("incompatible_ternary") var y_min = 0 if pos.y - 1 < 0 else pos.y - 1 var x_max = world_size.x if pos.x + 2 > world_size.x else pos.x + 2 var y_max = world_size.y if pos.y + 2 > world_size.y else pos.y + 2 for x in range(x_min, x_max): for y in range(y_min, y_max): if x == pos.x and y == pos.y: continue # Current cell - Don't count if world[x][y] is RID: count += 1 return count ## Loop through the world and create or kill cells depending on rules func process_generation() -> void: if is_paused: return generation += 1 total_living = 0 var new_world: Array= [] for x in range(world_size.x): new_world.append([]) new_world[x].resize(world_size.x) for y in range(world_size.y): var pos = Vector2(x, y) if cell_is_alive(pos): total_living += 1 new_world[x][y] = create_cell(pos) else: new_world[x][y] = kill_cell(pos) world = new_world ## Create the cell using the rendering server func create_cell(pos: Vector2) -> RID: if world[pos.x][pos.y] is RID: return world[pos.x][pos.y] var rs = RenderingServer cell_instance = rs.canvas_item_create() rs.canvas_item_set_parent(cell_instance, get_canvas_item()) var rect: Rect2 = Rect2(-cell_size.x/2, -cell_size.y/2, cell_size.x, cell_size.y) # Centered with cell size rs.canvas_item_add_texture_rect(cell_instance, rect, cell_texture) var pos_fixed = Vector2(pos.x * cell_size.x, pos.y * cell_size.y) var trans = Transform2D(0, pos_fixed) rs.canvas_item_set_transform(cell_instance, trans) return cell_instance ## Remove the cell from the RenderingServer, if it exists func kill_cell(pos: Vector2) -> CellStates: if world[pos.x][pos.y] is RID: RenderingServer.free_rid(world[pos.x][pos.y]) return CellStates.DEAD ## Generate the world with cells in random states (alive or dead) func generate_world() -> void: for x in range(world_size.x): world.append([]) world[x].resize(world_size.x) for y in range(world_size.y): if randi_range(0, 1): total_living += 1 world[x][y] = create_cell(Vector2(x, y)) else: world[x][y] = CellStates.DEAD func _on_generation_timer_timeout() -> void: process_generation() debug_generation_counter.text = "Generation: %s" % generation debug_living_cells_counter.text = "Living Cells: %s" % total_living generation_timer.start() func _on_quit_button_pressed() -> void: get_tree().quit() func _on_run_button_pressed() -> void: if not generation_seed.text.strip_edges(): world_seed = randi() else: world_seed = int(generation_seed.text) world_size = Vector2(int(generation_world_size_x.text), int(generation_world_size_y.text)) cell_size = Vector2(int(gneration_cell_size_x.text), int(gneration_cell_size_x.text)) debug_ui.visible = true generation_ui.visible = false background_ui.visible = false seed(world_seed) start_conway() func _on_world_seed_generate_pressed() -> void: generation_seed.text = str(randi())