| @ -1,46 +1,129 @@ | |||||
| ## Conway's Game of Life | ## Conway's Game of Life | ||||
| ## Rules: | |||||
| ## 1. Any cell with >2 neighbors survives | |||||
| ## 2. Any dead cell with 3 live neighbors becomes alive | |||||
| ## 3. All other cells die (all dead cells remaing dead) | |||||
| ## 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 epos.xactly three live neighbours becomes a live cell (reproduction) | |||||
| extends Node2D | extends Node2D | ||||
| enum CellStates { | |||||
| DEAD, | |||||
| ALIVE, | |||||
| } | |||||
| @onready var generation_counter: Label = $CanvasLayer/Debug/VBoxContainer/GenerationCounter | |||||
| @onready var generation_timer: Timer = $GenerationTimer | |||||
| @onready var living_cells_counter: Label = $CanvasLayer/Debug/VBoxContainer/LivingCellsCounter | |||||
| @onready var world_seed_label: Label = $CanvasLayer/Debug/VBoxContainer/WorldSeed | |||||
| @export var world_seed: int | @export var world_seed: int | ||||
| @export var cell_size: Vector2 = Vector2(16, 16) | |||||
| @export var cell_size: Vector2 = Vector2(64, 64) | |||||
| @export var cell_texture: Texture2D | @export var cell_texture: Texture2D | ||||
| @export var world_size: Vector2 = Vector2(128, 128) | |||||
| @export var world_size: Vector2 = Vector2(8, 8) | |||||
| var cell_instance | var cell_instance | ||||
| var generation: int = 1 | var generation: int = 1 | ||||
| var world: Array = [] | var world: Array = [] | ||||
| var total_living: int = 0 | |||||
| func _ready() -> void: | func _ready() -> void: | ||||
| if world_seed: seed(world_seed) | |||||
| if not world_seed: | |||||
| world_seed = randi() | |||||
| seed(world_seed) | |||||
| world_seed_label.text = "World Seeed: %s" % world_seed | |||||
| generate_world() | generate_world() | ||||
| # Create the cell using the rendering server | |||||
| func create_cell(location: Vector2) -> RID: | |||||
| ## Check a cell against the Conway rules | |||||
| func check_cell_rules(pos: Vector2) -> void: | |||||
| 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: | |||||
| kill_cell(pos) | |||||
| return | |||||
| # Rule 2 - Any live cell with two or three live neighbours lives | |||||
| elif currently_alive and (neighbors == 2 or neighbors == 3): | |||||
| total_living += 1 | |||||
| return | |||||
| # Rule 3 - Any live cell with more than three live neighbours dies (overpopulation) | |||||
| elif currently_alive and neighbors > 3: | |||||
| kill_cell(pos) | |||||
| return | |||||
| # Rule 4 - Any dead cell with epos.xactly three live neighbours becomes a live cell (reproduction) | |||||
| elif not currently_alive and neighbors == 3: | |||||
| total_living += 1 | |||||
| world[pos.x][pos.y] = create_cell(pos) | |||||
| return | |||||
| func count_living_neighbors(pos: Vector2) -> int: | |||||
| var count := 0 | |||||
| var x_min = 0 if pos.x - 1 < 0 else pos.x - 1 | |||||
| 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 | |||||
| func process_generation() -> void: | |||||
| generation += 1 | |||||
| total_living = 0 | |||||
| for x in range(world_size.x): | |||||
| for y in range(world_size.y): | |||||
| check_cell_rules(Vector2(x, y)) | |||||
| ## Create the cell using the rendering server | |||||
| func create_cell(pos: Vector2) -> RID: | |||||
| var rs = RenderingServer | var rs = RenderingServer | ||||
| cell_instance = rs.canvas_item_create() | cell_instance = rs.canvas_item_create() | ||||
| rs.canvas_item_set_parent(cell_instance, get_canvas_item()) | 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 | 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) | rs.canvas_item_add_texture_rect(cell_instance, rect, cell_texture) | ||||
| var location_fixed = Vector2(location.x * cell_size.x, location.y * cell_size.y) | |||||
| var trans = Transform2D(0, location_fixed) | |||||
| 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) | rs.canvas_item_set_transform(cell_instance, trans) | ||||
| return cell_instance | return cell_instance | ||||
| func kill_cell(pos: Vector2) -> void: | |||||
| if not world[pos.x][pos.y] is RID: return | |||||
| RenderingServer.free_rid(world[pos.x][pos.y]) | |||||
| world[pos.x][pos.y] = CellStates.DEAD | |||||
| ## Generate the world with the initial cells | ## Generate the world with the initial cells | ||||
| func generate_world() -> void: | func generate_world() -> void: | ||||
| for x in range(world_size.x): | for x in range(world_size.x): | ||||
| world.append([]) | world.append([]) | ||||
| for y in range(world_size.y): | for y in range(world_size.y): | ||||
| if randi_range(0, 1): | if randi_range(0, 1): | ||||
| total_living += 1 | |||||
| world[x].append(create_cell(Vector2(x, y))) | world[x].append(create_cell(Vector2(x, y))) | ||||
| else: | else: | ||||
| world[x].append(0) | |||||
| world[x].append(CellStates.DEAD) | |||||
| func _on_generation_timer_timeout() -> void: | |||||
| process_generation() | |||||
| generation_counter.text = "Generation: %s" % generation | |||||
| living_cells_counter.text = "Living Cells: %s" % total_living | |||||
| generation_timer.start() | |||||