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) -> bool: return not solid_at(pos) 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) func do_step(): for piece in pieces(): piece.do_step(self) 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()