tool extends Control class_name Taquin var Piece = preload("res://src/Piece.tscn") export var rows: int = 4 export var columns: int = 4 export var width: int = 512 export var height: int = 512 export var difficulty: int = 10 enum Direction { UP, DOWN, LEFT, RIGHT } var interpiece: int = 4 var min_padding = 15 var padding = Vector2(min_padding, min_padding) var pieces: Array = [] var missing_piece: Vector2 var rng = RandomNumberGenerator.new() var current_animation_path: String = "AnimationPlayer/MockPiece:position" var swipe = Vector2(0, 0) var is_sliding = false var current_sliding_piece: Piece = null var current_origin = Vector2.ZERO var current_axis = Vector2.ZERO var current_slide = Vector2.ZERO var local_min_position = Vector2.ZERO var local_max_position = Vector2.ZERO func position_for_index(index: Vector2, size: int) -> Vector2: return padding + Vector2(index.x * (size + interpiece), index.y * (size + interpiece)) func compute_piece_size() -> int: var w_size: int = (width - (2 * min_padding) - ((columns - 1) * interpiece)) / columns var h_size: int = (height - (2 * min_padding) - ((rows - 1) * interpiece)) / rows return int(min(w_size, h_size)) func compute_padding(piece_size: int) -> Vector2: var padding = Vector2(0, 0) padding.x = width - columns * piece_size - (columns - 1) * interpiece padding.y = height - rows * piece_size - (rows - 1) * interpiece padding = padding / Vector2(2, 2) return padding func _ready() -> void: $AnimationPlayer/MockPiece.visible = false $Particles2D.emitting = false $Background.rect_size.x = width $Background.rect_size.y = height rng.randomize() var piece_size: int = compute_piece_size() padding = compute_padding(piece_size) print("piece size: ", piece_size) print("padding: ", padding) for c in range(columns): var pieces_row: Array = [] for r in range(rows): var piece = Piece.instance() piece.size = piece_size piece.position = position_for_index(Vector2(c, r), piece.size) piece.number = 1 + c + r * columns piece.piece_scale = Vector2((float(piece_size) / width), (float(piece_size) / height)) piece.taquin_position = Vector2(float(piece.position.x) / width, float(piece.position.y) / height) if r == rows - 1 && c == columns - 1: piece.visible = false missing_piece.x = c missing_piece.y = r $Background.add_child(piece) pieces_row.append(piece) pieces.append(pieces_row) shuffle(difficulty) func _input(event): if $AnimationPlayer.is_playing(): # Disable input during animation return var game_state = get_node("/root/Main/GameState") as GameState if game_state != null: match game_state.current_state: # If we are in the winning animation, fast-forward to game over screen GameState.State.WINNING: game_state.transition_to(GameState.State.GAME_OVER) return GameState.State.GAME_OVER: return # # Handle keyboard input # if event.is_action_pressed("ui_up"): move_piece(Direction.DOWN) if event.is_action_pressed("ui_down"): move_piece(Direction.UP) if event.is_action_pressed("ui_left"): move_piece(Direction.RIGHT) if event.is_action_pressed("ui_right"): move_piece(Direction.LEFT) # # Handle touch input # if event is InputEventScreenDrag: # print("screen drag") swipe = event.relative if not is_sliding: is_sliding = true current_slide = Vector2(0, 0) var angle = swipe.angle() var direction = direction_for_angle(angle) debug_print_direction(direction) current_sliding_piece = sliding_piece_for_direction(direction) if current_sliding_piece != null: current_origin = current_sliding_piece.position current_axis = axis_for_direction(direction) var local_end_position = current_axis * (current_sliding_piece.size + interpiece) local_min_position = Vector2(min(0, local_end_position.x), min(0, local_end_position.y)) local_max_position = Vector2(max(0, local_end_position.x), max(0, local_end_position.y)) current_slide += swipe if current_sliding_piece != null: var delta = current_slide.project(current_axis) delta.x = clamp(delta.x, local_min_position.x, local_max_position.x) delta.y = clamp(delta.y, local_min_position.y, local_max_position.y) current_sliding_piece.position = current_origin + delta if event is InputEventScreenTouch: # print("screen touch") if not event.pressed: # Touch released is_sliding = false # var angle = swipe.angle() # if angle < PI / 4 and angle >= - PI / 4: # move_piece(Direction.LEFT) # if angle >= PI / 4 and angle < PI - PI / 4: # move_piece(Direction.UP) # if angle >= - PI + PI / 4 and angle < - PI / 4: # move_piece(Direction.DOWN) # if angle >= PI - PI / 4 or angle < -PI + PI / 4: # move_piece(Direction.RIGHT) func debug_print_direction(direction: int): match direction: Direction.UP: print("Direction UP") Direction.DOWN: print("Direction DOWN") Direction.LEFT: print("Direction LEFT") Direction.RIGHT: print("Direction RIGHT") _: assert(false) func axis_for_direction(direction: int) -> Vector2: match direction: Direction.UP: return Vector2.UP Direction.DOWN: return Vector2.DOWN Direction.LEFT: return Vector2.LEFT Direction.RIGHT: return Vector2.RIGHT _: assert(false) return Vector2.ZERO func direction_for_angle(angle: float) -> int: if angle < PI / 4 and angle >= - PI / 4: return Direction.RIGHT if angle >= PI / 4 and angle < PI - PI / 4: return Direction.DOWN if angle >= - PI + PI / 4 and angle < - PI / 4: return Direction.UP if angle >= PI - PI / 4 or angle < -PI + PI / 4: return Direction.LEFT assert(false) return Direction.DOWN func sliding_piece_for_direction(direction) -> Piece: var destination: Vector2 = missing_piece match direction: Direction.UP: destination.y += 1 Direction.DOWN: destination.y -= 1 Direction.LEFT: destination.x += 1 Direction.RIGHT: destination.x -= 1 if (destination.x < 0 || destination.x >= columns || destination.y < 0 || destination.y >= rows): print("impossible move") return null return pieces[destination.x][destination.y] func move_piece(direction) -> bool: var destination: Vector2 = missing_piece match direction: Direction.UP: destination.y -= 1 Direction.DOWN: destination.y += 1 Direction.LEFT: destination.x -= 1 Direction.RIGHT: destination.x += 1 if (destination.x < 0 || destination.x >= columns || destination.y < 0 || destination.y >= rows): print("impossible move") return false var moving_piece: Piece = pieces[destination.x][destination.y] var moving_piece_animation: Animation = $AnimationPlayer.get_animation("MovingPiece") assert(moving_piece_animation != null) assert(moving_piece_animation.get_track_count() > 0) var moving_piece_track_index: int = moving_piece_animation.find_track(current_animation_path) assert(moving_piece_track_index != -1) var new_animation_path: String = str($AnimationPlayer.get_parent().get_path_to(moving_piece), ":position") moving_piece_animation.track_set_path(moving_piece_track_index, new_animation_path) current_animation_path = new_animation_path moving_piece_animation.track_set_key_value(moving_piece_track_index, 0, position_for_index(destination, moving_piece.size)) moving_piece_animation.track_set_key_value(moving_piece_track_index, 1, position_for_index(missing_piece, moving_piece.size)) $AnimationPlayer.play("MovingPiece") swap(missing_piece, destination) missing_piece = destination update() if check_solved(): var game_state = get_node("/root/Main/GameState") as GameState if game_state != null: game_state.transition_to(GameState.State.WINNING) return true func swap(src: Vector2, dst: Vector2) -> void: var tmp: Piece = pieces[src.x][src.y] pieces[src.x][src.y] = pieces[dst.x][dst.y] pieces[dst.x][dst.y] = tmp # pieces[src.x][src.y].position = position_for_index(src, tmp.size) # pieces[dst.x][dst.y].position = position_for_index(dst, tmp.size) func shuffle(count: int) -> void: while count > 0: count -= 1 var direction = rng.randi_range(Direction.UP, Direction.RIGHT) move_piece(direction) func check_solved() -> bool: for c in range(columns): for r in range(rows): if pieces[c][r].number != 1 + c + r * columns: return false return true func _on_GameState_state_changed(previous, current): match current: GameState.State.WINNING: $Particles2D.emitting = true $Timer.start(-1) GameState.State.GAME_OVER: $Particles2D.emitting = false $Timer.stop() func _on_Timer_timeout(): var game_state = get_node("/root/Main/GameState") as GameState if game_state != null: game_state.transition_to(GameState.State.GAME_OVER)