124 lines
3.4 KiB
GDScript
124 lines
3.4 KiB
GDScript
class_name Board
|
|
extends Node3D
|
|
|
|
## squares per second
|
|
@export_range(0.0, 1, 0.01) var anim_time := 0.15
|
|
|
|
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.y == b.lpos.y and 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)
|
|
|
|
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)
|
|
|
|
func player() -> Piece:
|
|
for piece in pieces():
|
|
if piece.type == Piece.Type.Player:
|
|
return piece
|
|
return null
|
|
|
|
var last_tween: Tween = null
|
|
|
|
func finish_tween():
|
|
if last_tween:
|
|
last_tween.custom_step(413)
|
|
last_tween.kill()
|
|
last_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
|
|
|
|
## returns the total time animation *will* take
|
|
func step_anim_time() -> float:
|
|
var pieces_moving := pieces().filter(func(piece): return piece.lvel != Vector2i.ZERO).size()
|
|
return pieces_moving * anim_time
|
|
|
|
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)
|
|
|
|
finish_tween()
|
|
|
|
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.kill()
|
|
|
|
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()
|