|
## 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 epos.xactly three live neighbours becomes a live cell (reproduction)
|
|
extends Node2D
|
|
|
|
|
|
enum CellStates {
|
|
DEAD,
|
|
ALIVE,
|
|
}
|
|
|
|
## UI
|
|
@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
|
|
generation_ui.visible = true
|
|
|
|
func _input(event: InputEvent) -> void:
|
|
if event.is_action_pressed("Pause"):
|
|
is_paused = !is_paused
|
|
if event.is_action_pressed("Menu"):
|
|
is_paused = true
|
|
generation_ui.visible = true
|
|
update_generation_ui()
|
|
|
|
|
|
#
|
|
# UI
|
|
#
|
|
## Update the Gneration UI with the current settings
|
|
func update_generation_ui() -> void:
|
|
generation_seed.text = str(world_seed)
|
|
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
|
|
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 epos.xactly 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_generation_submit_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
|
|
seed(world_seed)
|
|
randomize()
|
|
start_conway()
|