| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -2,9 +2,11 @@ extends Area2D
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				class_name SnakePart
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				const TILE_SIZE = 20
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				enum PartTypes {HEAD, BODY, TAIL, DEAD}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				enum PartTypes {HEAD, BODY, TAIL}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				enum States {ALIVE, DEAD, OUROBOROS, OLD_OUROBOROS}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				var part_type : PartTypes = PartTypes.HEAD
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				var state : States = States.ALIVE
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				var current_direction : Vector2 = Vector2.RIGHT
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				var inputs : Dictionary[String, Vector2] = {"right": Vector2.RIGHT,
 | 
			
		
		
	
	
		
			
				
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -13,6 +15,7 @@ var inputs : Dictionary[String, Vector2] = {"right": Vector2.RIGHT,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
															"down": Vector2.DOWN}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				@onready
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				var raycast_right : RayCast2D = $RightRayCast2D
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				@onready
 | 
			
		
		
	
	
		
			
				
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -30,9 +33,11 @@ var timer_ref : Timer = $"../Timer"
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				var next_part : SnakePart = null
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				var skip_next_move_propagation : bool = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				var queued_growth : int = 0
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				var queued_growth : int = 21
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				signal do_movement(new_dir)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				signal on_movement(new_dir)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				signal on_death
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				signal on_ouroboros
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func _ready() -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if part_type == PartTypes.HEAD:
 | 
			
		
		
	
	
		
			
				
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -40,13 +45,20 @@ func _ready() -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						# Add to group for easy access from other nodes
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						add_to_group("Head")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						# Attach clock
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						var timer : Node = get_tree().get_first_node_in_group("GameClock")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if timer is Timer:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							timer.timeout.connect(process_movement.bind(Vector2.ZERO))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						# Set up a body part
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						var body = snake_part_obj.instantiate()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						body.part_type = PartTypes.BODY
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						body.position = (position - current_direction * TILE_SIZE).snapped(Vector2.ONE * TILE_SIZE)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						next_part = body
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						get_parent().add_child.call_deferred(body)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						do_movement.connect(body.process_movement)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						on_movement.connect(body.process_movement)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						on_death.connect(body.lose_game)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						on_ouroboros.connect(body.ouroboros)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						# Set up a tail part
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						var tail = snake_part_obj.instantiate()
 | 
			
		
		
	
	
		
			
				
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -54,7 +66,9 @@ func _ready() -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						tail.position = (position - current_direction * TILE_SIZE * 2).snapped(Vector2.ONE * TILE_SIZE)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						body.next_part = tail
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						get_parent().add_child.call_deferred(tail)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						body.do_movement.connect(tail.process_movement)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						body.on_movement.connect(tail.process_movement)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						on_death.connect(tail.lose_game)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						on_ouroboros.connect(tail.ouroboros)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						# Free raycasts if we don't need them (consider only loading them in for the head for performance if needed)
 | 
			
		
		
	
	
		
			
				
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -63,22 +77,81 @@ func _ready() -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						raycast_left.queue_free()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						raycast_right.queue_free()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				# TODO: Optimise this!
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func generate_spawn_grid() -> Array[Vector2]:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var spawn_grid : Array[Vector2] = []
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# Determine the level limits
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var level : Area2D = get_tree().get_first_node_in_group("Level")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var x_bounds : Vector2 = level.position.x * Vector2.ONE + Vector2(-TILE_SIZE, TILE_SIZE) * level.scale.x / 2
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var y_bounds : Vector2 = level.position.y * Vector2.ONE + Vector2(-TILE_SIZE, TILE_SIZE) * level.scale.y / 2
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var possible_x : Array = range(x_bounds.x, x_bounds.y, TILE_SIZE)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var possible_y : Array = range(y_bounds.x, y_bounds.y, TILE_SIZE)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# Flood fill all possibilities until we get an inside to use
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for x in possible_x:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						for y in possible_y:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							var start_position : Vector2 = Vector2(x, y).snapped(Vector2.ONE * TILE_SIZE)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							spawn_grid.clear()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							if !check_position_for_snake(start_position) and flood_fill(spawn_grid, start_position, x_bounds, y_bounds):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
								GridManager.current_allowed_spawns = spawn_grid
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
								return spawn_grid
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					GridManager.current_allowed_spawns = spawn_grid
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return spawn_grid
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func flood_fill(positions : Array[Vector2], new_position : Vector2, x_bounds : Vector2, y_bounds : Vector2) -> bool:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var directions : Array[Vector2] = [Vector2.DOWN, Vector2.LEFT, Vector2.UP, Vector2.RIGHT]
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var possible_x : Array = range(x_bounds.x, x_bounds.y, TILE_SIZE)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var possible_y : Array = range(y_bounds.x, y_bounds.y, TILE_SIZE)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var inside : bool = true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for direction in directions:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						var test_position : Vector2 = (new_position + direction * TILE_SIZE).snapped(Vector2.ONE * TILE_SIZE)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if not int(test_position.x) in possible_x or not int(test_position.y) in possible_y:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							inside = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if not test_position in positions and int(test_position.x) in possible_x and int(test_position.y) in possible_y and !check_position_for_snake(test_position):
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							positions.append(test_position)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							inside = inside and flood_fill(positions, test_position, x_bounds, y_bounds)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
								
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return inside
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func check_position_for_snake(query_position : Vector2) -> bool:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var obstacles : Array[Node] = get_tree().get_nodes_in_group("BlocksSpawn")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for obstacle in obstacles:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if obstacle is SnakePart:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							if obstacle.state == States.OUROBOROS and obstacle.position == query_position:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
								return true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					return false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func process_movement(new_direction : Vector2) -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# Only alive snakes can move
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if state != States.ALIVE:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# The head needs to check if we are about to collide with something
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if part_type == PartTypes.HEAD:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						check_movement()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if queued_growth:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							extend()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# Only alive snakes can move
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if state != States.ALIVE:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# Update the position of this part
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					position = (position + current_direction * TILE_SIZE).snapped(Vector2.ONE * TILE_SIZE)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# Tell the next part to move and give it the direction we are moving
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if !skip_next_move_propagation:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						do_movement.emit(current_direction)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						on_movement.emit(current_direction)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					skip_next_move_propagation = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# Unless we are the head, change our direction to the direction given
 | 
			
		
		
	
	
		
			
				
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -86,6 +159,7 @@ func process_movement(new_direction : Vector2) -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						current_direction = new_direction
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func check_movement() -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# Get the correct raycast for the direction we are moving
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var raycast_to_use : RayCast2D
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if current_direction == Vector2.RIGHT:
 | 
			
		
		
	
	
		
			
				
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -106,28 +180,32 @@ func check_movement() -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# If we found a snake part, then either end the game or perform an ouroboros maneouvre
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if object_in_path is SnakePart:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						match object_in_path.part_type:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							PartTypes.BODY:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
								lose_game()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							PartTypes.DEAD:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
								lose_game()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							PartTypes.TAIL:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
								ouroboros()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if object_in_path.state == States.ALIVE:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							match object_in_path.part_type:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
								PartTypes.BODY:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
									lose_game()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
								PartTypes.TAIL:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
									ouroboros()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						else:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							lose_game()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if object_in_path is Fruit:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						queued_growth += object_in_path.growth_amount
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						object_in_path.respawn()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func extend() -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# Remove old connection to previous next part
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					do_movement.disconnect(next_part.process_movement)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					on_movement.disconnect(next_part.process_movement)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# Set up a body part to be in the chain
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var new_body = snake_part_obj.instantiate()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					new_body.part_type = PartTypes.BODY
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					new_body.position = (position).snapped(Vector2.ONE * TILE_SIZE)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					get_parent().add_child(new_body)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					do_movement.connect(new_body.process_movement)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					new_body.do_movement.connect(next_part.process_movement)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					on_movement.connect(new_body.process_movement)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					new_body.on_movement.connect(next_part.process_movement)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					on_death.connect(new_body.lose_game)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					on_ouroboros.connect(new_body.ouroboros)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					new_body.next_part = next_part
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# Set direction of new part
 | 
			
		
		
	
	
		
			
				
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -144,12 +222,71 @@ func extend() -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func lose_game() -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# You lose!
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					print("Game lost")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					timer_ref.stop()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					state = States.DEAD
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					on_death.emit()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func ouroboros() -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					print("You have achieved the ouroboros!")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if state == States.OUROBOROS:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						state = States.OLD_OUROBOROS
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					elif state == States.ALIVE:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						state = States.OUROBOROS
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					on_ouroboros.emit()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# The head can spawn a new snake
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if part_type == PartTypes.HEAD:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						get_tree().get_first_node_in_group("GameClockPause").start(1)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						get_tree().get_first_node_in_group("GameClock").stop()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						spawn_new_snake()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func spawn_new_snake() -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# Find out where we can spawn a snake
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					#var start = Time.get_ticks_usec()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var all_spawn_positions : Array[Vector2] = generate_spawn_grid().duplicate()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					#var end = Time.get_ticks_usec()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					#var worker_time = (end-start)/1000000.0
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					#print(worker_time)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# We cannot spawn too close to a snake on the left, so add a condition
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var spawn_positions : Array[Vector2] = []
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for pos in all_spawn_positions:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if (pos + Vector2.LEFT * TILE_SIZE).snapped(Vector2.ONE * TILE_SIZE) in all_spawn_positions and \
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							(pos + Vector2.LEFT * TILE_SIZE * 2).snapped(Vector2.ONE * TILE_SIZE) in all_spawn_positions:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							spawn_positions.append(pos)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var slightly_preferred_spawns : Array[Vector2] = []
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for pos in spawn_positions:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if (pos + Vector2.RIGHT * TILE_SIZE).snapped(Vector2.ONE * TILE_SIZE) in all_spawn_positions:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							slightly_preferred_spawns.append(pos)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var most_preferred_spawns : Array[Vector2] = []
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					for pos in slightly_preferred_spawns:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						if (pos + Vector2.UP * TILE_SIZE).snapped(Vector2.ONE * TILE_SIZE) in all_spawn_positions and \
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							(pos + Vector2.DOWN * TILE_SIZE).snapped(Vector2.ONE * TILE_SIZE) in all_spawn_positions:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
							most_preferred_spawns.append(pos)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if !most_preferred_spawns.is_empty():
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						spawn_positions = most_preferred_spawns
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					elif !slightly_preferred_spawns.is_empty():
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						spawn_positions = slightly_preferred_spawns
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# If there is no room to spawn a snake, we lose
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					if spawn_positions.is_empty():
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						lose_game()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
						return
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# Pick a position
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var chosen_position : Vector2 = spawn_positions.pick_random()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					# Spawn in the new snake
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					var new_head = snake_part_obj.instantiate()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					new_head.position = chosen_position.snapped(Vector2.ONE * TILE_SIZE)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					get_parent().add_child(new_head)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					new_head.on_ouroboros.connect(ouroboros)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func _unhandled_input(event: InputEvent) -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					
 | 
			
		
		
	
	
		
			
				
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -161,5 +298,5 @@ func _unhandled_input(event: InputEvent) -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				# If we leave the arena
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func _on_level_area_exited(area: Area2D) -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				func _on_level_area_exited(_area: Area2D) -> void:
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
					lose_game()
 | 
			
		
		
	
	
		
			
				
					| 
						 
							
							
							
						 
					 | 
				
			
			 | 
			 | 
			
				
 
 |