class_name Board extends Node3D var dims: Vector2i func _ready(): for piece in pieces(): dims.x = max(dims.x, piece.lpos.x) dims.y = max(dims.y, piece.lpos.y) # for zooming out to see the whole puzzle func top_left_aabb() -> AABB: return AABB(position-Vector3(0,0,1), Vector3.ONE*0.1) func bottom_right_aabb() -> AABB: return AABB(position + Vector3(dims.x, 0, dims.y) + Vector3(1,0,2), Vector3.ONE*0.1) func pieces() -> Array[Piece]: var sorted: Array[Piece] = [] for child in get_children(): if child is Piece: sorted.push_back(child) # important for consistency. # everything involving pieces is done top to bottom, then left to right # expensive? yes. wasteful? definitely. worth it? i think so. sorted.sort_custom(func(a, b): return a.lpos.y < b.lpos.y or a.lpos.x < b.lpos.x) return sorted func pieces_at(pos: Vector2i) -> Array[Piece]: var out: Array[Piece] = [] for piece in pieces(): if piece.lpos == pos: out.push_back(piece) return out func find_piece_at(pos: Vector2i, filter: Callable) -> Piece: var at := pieces_at(pos) var idx := at.find_custom(filter) if idx < 0: return return at[idx] func any_at(pos: Vector2i, filter: Callable) -> bool: return pieces_at(pos).any(filter) func type_at(pos: Vector2i, type: Piece.Type) -> Piece: return find_piece_at(pos, func(p): return p.type == type) # TODO: ball collisions func solid_at(pos: Vector2i) -> bool: return any_at(pos, func(p): return p.type == Piece.Type.Wall or p.type == Piece.Type.Ball or p.type == Piece.Type.Player) func passable_at(pos: Vector2i, for_player := false) -> bool: return not solid_at(pos) and not (for_player and type_at(pos, Piece.Type.PlayerBarrier)) func remove_piece(piece: Piece): remove_child(piece) func add_piece(piece: Piece): add_child(piece) func add_pieces(piece: Array[Piece]): for p in piece: add_piece(p) var last_tween: Tween = null # here are the phases: # cardinal velocity # diagonal velocity # uhh that's it lol? # well, subphases: higher lvel magnitude moves first within a phase # TODO: maybe show these phases with the sun func do_step(): var pieces_moving := pieces().filter(func(piece): return piece.lvel != Vector2i.ZERO) var pieces_cardinal := pieces_moving.filter(func(piece): return piece.lvel.x == 0 or piece.lvel.y == 0) var pieces_diagonal := pieces_moving.filter(func(piece): return piece.lvel.x != 0 and piece.lvel.y != 0) var magnitude_sort := func(a: Piece, b: Piece): return a.lvel.length() < b.lvel.length() pieces_cardinal.sort_custom(magnitude_sort) pieces_diagonal.sort_custom(magnitude_sort) if last_tween: last_tween.custom_step(413) last_tween.kill() var tween := get_tree().create_tween() last_tween = tween for piece in pieces_cardinal: piece.do_step(self, tween) for piece in pieces_diagonal: piece.do_step(self, tween) if pieces_moving.is_empty(): # no tweens -> invalid -> annoying error message tween.stop() func undo_step() -> Callable: var undos: Array[Callable] = [] for piece in pieces(): undos.push_back(piece.undo_step()) return func(): for undo in undos: undo.call()