diff --git a/scenes/main.gd b/scenes/main.gd index 27f2f19..d7961b0 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -141,10 +141,14 @@ func connection_verify(expected_players: Array): func setup_action_buttons(): move_button.pressed.connect(func(): set_action_state(ActionState.MOVING)) grab_button.pressed.connect(func(): set_action_state(ActionState.GRABBING)) + #put_button.pressed.connect(func(): + #if local_player_character: + #local_player_character.handle_put_action() + #set_action_state(ActionState.PUTTING) + #) put_button.pressed.connect(func(): if local_player_character: - local_player_character.handle_put_action() - set_action_state(ActionState.PUTTING) + local_player_character.auto_put_item() ) randomize_button.pressed.connect(func(): set_action_state(ActionState.RANDOMIZING)) arrange_button.pressed.connect(func(): @@ -210,12 +214,15 @@ func set_action_state(new_state): local_player_character.highlight_movement_range() ActionState.GRABBING: local_player_character.highlight_adjacent_cells() - if local_player_character.has_item_at_current_position(): - local_player_character.highlighted_cells.append(local_player_character.current_position) - local_player_character.enhanced_gridmap.set_cell_item( - Vector3i(local_player_character.current_position.x, 0, local_player_character.current_position.y), - local_player_character.enhanced_gridmap.hover_item - ) + + # Deactivated since, using Auto Grabber + + #if local_player_character.has_item_at_current_position(): + #local_player_character.highlighted_cells.append(local_player_character.current_position) + #local_player_character.enhanced_gridmap.set_cell_item( + #Vector3i(local_player_character.current_position.x, 0, local_player_character.current_position.y), + #local_player_character.enhanced_gridmap.hover_item + #) ActionState.PUTTING: local_player_character.highlight_occupied_playerboard_slots() # Make sure this is client-friendly @@ -318,10 +325,13 @@ func _on_playerboard_slot_clicked(event, slot_index): match current_action_state: ActionState.ARRANGING: local_player_character.arrange_playerboard_item(slot_index) - ActionState.GRABBING: - local_player_character.handle_playerboard_slot_selected(slot_index) - ActionState.PUTTING: - local_player_character.handle_put_slot_selected(slot_index) + + # Deactivated since we're using auto grab + #ActionState.GRABBING: + #local_player_character.handle_playerboard_slot_selected(slot_index) + # Deactivated since we're using auto put + #ActionState.PUTTING: + #local_player_character.handle_put_slot_selected(slot_index) func update_playerboard_ui(): if not local_player_character: diff --git a/scenes/player.gd b/scenes/player.gd index 45f5eb0..3fdff88 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -524,9 +524,9 @@ func handle_grid_click(grid_position: Vector2i): main.ActionState.GRABBING: if grid_position in highlighted_cells or grid_position == current_position: grab_item(grid_position) - main.ActionState.PUTTING: - if grid_position in highlighted_cells and selected_playerboard_slot != -1: - put_item(grid_position) + #main.ActionState.PUTTING: + #if grid_position in highlighted_cells and selected_playerboard_slot != -1: + #put_item(grid_position) main.ActionState.RANDOMIZING: if grid_position in highlighted_cells: main.randomize_item_at_position(grid_position) @@ -933,16 +933,63 @@ func bot_try_grab_item() -> bool: return false +#func grab_item(grid_position: Vector2i = current_position) -> bool: + #if is_bot: + #return bot_try_grab_item() + # + #if not enhanced_gridmap or action_points <= 0: + #return false + # + #var cell = Vector3i(grid_position.x, 1, grid_position.y) + #var item = enhanced_gridmap.get_cell_item(cell) + # + #if grid_position != current_position: + #var neighbors = enhanced_gridmap.get_neighbors(current_position, 0) + #var is_adjacent = false + #for neighbor in neighbors: + #if neighbor.position == grid_position: + #is_adjacent = true + #break + #if not is_adjacent: + #return false + # + #if item == -1: + #return false + # + ## Bot-specific grab logic moved to bot_grab_item RPC + #if is_in_group("Bots") or is_bot: + #var empty_slot = playerboard.find(-1) + #if empty_slot == -1: + #return false + # + #if is_multiplayer_authority(): + #rpc("bot_grab_item", grid_position, empty_slot, cell.x, cell.y, cell.z) + #return true + # + #var main = get_tree().get_root().get_node_or_null("Main") + #if main: + #selected_gridmap_position = grid_position + #clear_highlights() + #clear_playerboard_highlights() + # + #for i in range(playerboard.size()): + #if playerboard[i] == -1: + #var slot = main.playerboard_ui.get_child(i) + #if slot.get_child_count() > 0: + #slot.get_child(0).show() + #highlighted_cells.append(i) + #return true +# + #return false + func grab_item(grid_position: Vector2i = current_position) -> bool: - if is_bot: - return bot_try_grab_item() - if not enhanced_gridmap or action_points <= 0: return false - + var cell = Vector3i(grid_position.x, 1, grid_position.y) var item = enhanced_gridmap.get_cell_item(cell) - + + # Validate adjacency (unless it's current position) if grid_position != current_position: var neighbors = enhanced_gridmap.get_neighbors(current_position, 0) var is_adjacent = false @@ -952,86 +999,137 @@ func grab_item(grid_position: Vector2i = current_position) -> bool: break if not is_adjacent: return false - + if item == -1: return false - - # Bot-specific grab logic moved to bot_grab_item RPC - if is_in_group("Bots") or is_bot: - var empty_slot = playerboard.find(-1) - if empty_slot == -1: - return false - - if is_multiplayer_authority(): - rpc("bot_grab_item", grid_position, empty_slot, cell.x, cell.y, cell.z) - return true - - var main = get_tree().get_root().get_node_or_null("Main") - if main: - selected_gridmap_position = grid_position - clear_highlights() - clear_playerboard_highlights() - - for i in range(playerboard.size()): - if playerboard[i] == -1: - var slot = main.playerboard_ui.get_child(i) - if slot.get_child_count() > 0: - slot.get_child(0).show() - highlighted_cells.append(i) - return true - return false + # === AUTO-ARRANGE LOGIC === + var target_slot = find_best_goal_slot_for_item(item) + if target_slot == -1: + return false # no space -func put_item(grid_position: Vector2i = current_position) -> bool: - if not enhanced_gridmap or action_points <= 0 or selected_playerboard_slot == -1: - return false - - var cell = Vector3i(grid_position.x, 1, grid_position.y) - if enhanced_gridmap.get_cell_item(cell) != -1: - return false - - # Check if position is adjacent or current position - if grid_position != current_position: - var is_adjacent = false - var neighbors = enhanced_gridmap.get_neighbors(current_position, 0) - for neighbor in neighbors: - if neighbor.position == grid_position: - is_adjacent = true - break - if not is_adjacent: - return false - - # Get the item to place first - var item = playerboard[selected_playerboard_slot] - - # For clients, we need to RPC to the server first, then let the server RPC back - if is_multiplayer_authority() and not multiplayer.is_server(): - # Client requests server to perform the put operation - rpc_id(1, "request_server_put", grid_position, selected_playerboard_slot, cell.x, cell.y, cell.z, item) - - # We'll return true and let the server handle the actual operation - # The server will RPC back to update our state if successful - return true - elif is_multiplayer_authority() and multiplayer.is_server(): - # Server directly implements the change - rpc("sync_grid_item", cell.x, cell.y, cell.z, item) - playerboard[selected_playerboard_slot] = -1 + # Perform the grab and auto-place + if is_multiplayer_authority(): + # Update gridmap: remove item + rpc("sync_grid_item", cell.x, cell.y, cell.z, -1) + # Update playerboard + playerboard[target_slot] = item rpc("sync_playerboard", playerboard) - + # Consume action has_performed_action = true consume_action_points(1) - if not is_bot == true: - clear_highlights() - clear_playerboard_highlights() - selected_playerboard_slot = -1 - + + # Optional: visual feedback + var main = get_tree().get_root().get_node_or_null("Main") + if main: + main.update_playerboard_ui() + main.set_action_state(main.ActionState.NONE) + + return true + +# Auto-put: no manual selection needed +# Automatically puts a goal-matching tile into an adjacent (or current) empty grid cell +func auto_put_item() -> bool: + if not enhanced_gridmap or action_points <= 0 or is_bot or is_in_group("Bots"): + return false + + # Step 1: Find empty adjacent (or current) grid cells + var valid_put_positions = [] + # Check current position + var current_cell_3d = Vector3i(current_position.x, 1, current_position.y) + if enhanced_gridmap.get_cell_item(current_cell_3d) == -1: + valid_put_positions.append(current_position) + # Check neighbors + var neighbors = enhanced_gridmap.get_neighbors(current_position, 0) + for neighbor in neighbors: + if neighbor.is_walkable: + var pos = neighbor.position + var cell_3d = Vector3i(pos.x, 1, pos.y) + if enhanced_gridmap.get_cell_item(cell_3d) == -1 and not is_position_occupied(pos): + valid_put_positions.append(pos) + + if valid_put_positions.is_empty(): + return false + + # Step 2: Find a playerboard tile that matches any goal + var put_slot = -1 + for i in range(playerboard.size()): + if playerboard[i] in goals: + put_slot = i + break + + if put_slot == -1: + return false + + # Step 3: Perform the put + var target_pos = valid_put_positions[0] # pick first valid + var item = playerboard[put_slot] + var cell = Vector3i(target_pos.x, 1, target_pos.y) + + if is_multiplayer_authority(): + rpc("sync_grid_item", cell.x, cell.y, cell.z, item) + playerboard[put_slot] = -1 + rpc("sync_playerboard", playerboard) + has_performed_action = true + consume_action_points(1) + var main = get_tree().get_root().get_node_or_null("Main") if main: main.set_action_state(main.ActionState.NONE) - _after_action_completed() - return true - - return false + + return true + +# Disabled logic, due the auto put +#func put_item(grid_position: Vector2i = current_position) -> bool: + #if not enhanced_gridmap or action_points <= 0 or selected_playerboard_slot == -1: + #return false +# + #var cell = Vector3i(grid_position.x, 1, grid_position.y) + #if enhanced_gridmap.get_cell_item(cell) != -1: + #return false +# + ## Check if position is adjacent or current position + #if grid_position != current_position: + #var is_adjacent = false + #var neighbors = enhanced_gridmap.get_neighbors(current_position, 0) + #for neighbor in neighbors: + #if neighbor.position == grid_position: + #is_adjacent = true + #break + #if not is_adjacent: + #return false +# + ## Get the item to place first + #var item = playerboard[selected_playerboard_slot] + # + ## For clients, we need to RPC to the server first, then let the server RPC back + #if is_multiplayer_authority() and not multiplayer.is_server(): + ## Client requests server to perform the put operation + #rpc_id(1, "request_server_put", grid_position, selected_playerboard_slot, cell.x, cell.y, cell.z, item) + # + ## We'll return true and let the server handle the actual operation + ## The server will RPC back to update our state if successful + #return true + #elif is_multiplayer_authority() and multiplayer.is_server(): + ## Server directly implements the change + #rpc("sync_grid_item", cell.x, cell.y, cell.z, item) + #playerboard[selected_playerboard_slot] = -1 + #rpc("sync_playerboard", playerboard) + # + #has_performed_action = true + #consume_action_points(1) + #if not is_bot == true: + #clear_highlights() + #clear_playerboard_highlights() + #selected_playerboard_slot = -1 + # + #var main = get_tree().get_root().get_node_or_null("Main") + #if main: + #main.set_action_state(main.ActionState.NONE) + #_after_action_completed() + #return true + # + #return false @rpc("any_peer", "reliable") func request_server_put(grid_position: Vector2i, slot_index: int, x: int, y: int, z: int, item: int): @@ -1092,24 +1190,23 @@ func notify_spawn_selected(spawn_pos: Vector2i): enhanced_gridmap.normal_items[0] ) - - -func handle_put_action(): - var main = get_tree().get_root().get_node_or_null("Main") - if not main or action_points < 1: - return - - if not is_bot == true: - clear_highlights() - clear_playerboard_highlights() - - # Highlight non-empty slots in playerboard - for i in range(playerboard.size()): - if playerboard[i] != -1: # Highlight occupied slots - var slot = main.playerboard_ui.get_child(i) - if slot.get_child_count() > 0: - slot.get_child(0).show() # Show highlight for occupied slots - highlighted_cells.append(i) +# Disabled, auto put activated +#func handle_put_action(): + #var main = get_tree().get_root().get_node_or_null("Main") + #if not main or action_points < 1: + #return + # + #if not is_bot == true: + #clear_highlights() + #clear_playerboard_highlights() + # + ## Highlight non-empty slots in playerboard + #for i in range(playerboard.size()): + #if playerboard[i] != -1: # Highlight occupied slots + #var slot = main.playerboard_ui.get_child(i) + #if slot.get_child_count() > 0: + #slot.get_child(0).show() # Show highlight for occupied slots + #highlighted_cells.append(i) func handle_playerboard_slot_selected(slot_index: int): var main = get_tree().get_root().get_node_or_null("Main") @@ -1247,6 +1344,121 @@ func is_valid_arrangement_slot(from_slot: int, to_slot: int) -> bool: return (row_diff == 1 and col_diff == 0) or (row_diff == 0 and col_diff == 1) +# Returns { slot_index: int, grid_position: Vector2i } or null if no valid put +func find_best_put_candidate() -> Dictionary: + # Convert goals to 2D (3x3) + var goals_2d = [] + for i in range(3): + var row = [] + for j in range(3): + row.append(goals[i * 3 + j]) + goals_2d.append(row) + + # Convert playerboard to 2D (5x5) + var board_2d = [] + for i in range(5): + var row = [] + for j in range(5): + row.append(playerboard[i * 5 + j]) + board_2d.append(row) + + # Step 1: Find misplaced or extra goal-matching items + var candidate_items = [] + for board_i in range(5): + for board_j in range(5): + var item = board_2d[board_i][board_j] + if item == -1: + continue + var board_idx = board_i * 5 + board_j + + # Is this item part of the goals? + if item not in goals: + continue + + # Is it already in the correct central position? + var is_in_correct_central_spot = false + if board_i in [1,2,3] and board_j in [1,2,3]: + var goal_i = board_i - 1 + var goal_j = board_j - 1 + if goals_2d[goal_i][goal_j] == item: + is_in_correct_central_spot = true + + if not is_in_correct_central_spot: + candidate_items.append({ + "slot": board_idx, + "item": item + }) + + # Step 2: Find valid adjacent empty grid cells + var valid_cells = [] + # Check current position + var current_cell_3d = Vector3i(current_position.x, 1, current_position.y) + if enhanced_gridmap.get_cell_item(current_cell_3d) == -1: + valid_cells.append(current_position) + # Check neighbors + var neighbors = enhanced_gridmap.get_neighbors(current_position, 0) + for neighbor in neighbors: + if neighbor.is_walkable: + var pos = neighbor.position + var cell_3d = Vector3i(pos.x, 1, pos.y) + if enhanced_gridmap.get_cell_item(cell_3d) == -1 and not is_position_occupied(pos): + valid_cells.append(pos) + + if valid_cells.is_empty() or candidate_items.is_empty(): + return {} + + # Step 3: Prefer to put an item that *completes* a missing goal + for goal_i in range(3): + for goal_j in range(3): + var needed_item = goals_2d[goal_i][goal_j] + if needed_item == -1: + continue + # Check if central spot is empty + var board_i = goal_i + 1 + var board_j = goal_j + 1 + var central_slot = board_i * 5 + board_j + if playerboard[central_slot] == -1: + # Look for this item in candidate_items + for cand in candidate_items: + if cand.item == needed_item: + if not valid_cells.is_empty(): + return { + "slot_index": cand.slot, + "grid_position": valid_cells[0] # pick first valid cell + } + + # Fallback: just put any candidate item + return { + "slot_index": candidate_items[0].slot, + "grid_position": valid_cells[0] + } + +# Finds the best slot in the playerboard for a given item based on goals +func find_best_goal_slot_for_item(item: int) -> int: + if item == -1: + return -1 + + # Convert goals to 2D (3x3) + var goals_2d = [] + for i in range(3): + var row = [] + for j in range(3): + row.append(goals[i * 3 + j]) + goals_2d.append(row) + + # Search for where this item should go in the central 3x3 (mapped to 5x5 board) + for i in range(3): + for j in range(3): + if goals_2d[i][j] == item: + var board_row = i + 1 # offset to center in 5x5 + var board_col = j + 1 + var slot_index = board_row * 5 + board_col + if playerboard[slot_index] == -1: # only if empty + return slot_index + + # No ideal slot? Return any empty slot + return playerboard.find(-1) + func get_adjacent_playerboard_slots(slot_index) -> Array: var adjacent = [] var row = slot_index / 5 diff --git a/scenes/tile_grabber.gd b/scenes/tile_grabber.gd new file mode 100644 index 0000000..1edb927 --- /dev/null +++ b/scenes/tile_grabber.gd @@ -0,0 +1,172 @@ +# tekton-enet/scripts/tile_grabber.gd +extends Node + +class_name TileGrabber + +signal tile_grabbed(tile_id: int, position: Vector3i) +signal tiles_arranged(arranged_tiles: Array) + +@export var enhanced_gridmap_path: NodePath +@export var player_board_path: NodePath + +var enhanced_gridmap: EnhancedGridMap +var player_board: Node +var grabbed_tile: int = -1 +var grabbed_position: Vector3i + +# Goal combinations - define what patterns score points +var goal_combinations = [ + { + "name": "Line of 3", + "pattern": [ + [1, 1, 1], + [0, 0, 0], + [0, 0, 0] + ], + "score": 10 + }, + { + "name": "L-Shape", + "pattern": [ + [1, 0, 0], + [1, 0, 0], + [1, 1, 0] + ], + "score": 15 + }, + { + "name": "Square", + "pattern": [ + [1, 1, 0], + [1, 1, 0], + [0, 0, 0] + ], + "score": 20 + } +] + +func _ready(): + if enhanced_gridmap_path: + enhanced_gridmap = get_node(enhanced_gridmap_path) + if player_board_path: + player_board = get_node(player_board_path) + +func grab_tile(grid_position: Vector3i) -> bool: + if not enhanced_gridmap: + print("Error: EnhancedGridMap not found") + return false + + # Get the tile at the specified position + var tile_id = enhanced_gridmap.get_cell_item(grid_position) + if tile_id == -1: + print("No tile at position: ", grid_position) + return false + + # Store the grabbed tile and its position + grabbed_tile = tile_id + grabbed_position = grid_position + + # Remove the tile from the grid + enhanced_gridmap.set_cell_item(grid_position, -1) + + # Emit signal that a tile was grabbed + tile_grabbed.emit(tile_id, grid_position) + + print("Grabbed tile ", tile_id, " from position ", grid_position) + return true + +func auto_arrange_tile() -> bool: + if grabbed_tile == -1: + print("No tile to arrange") + return false + + # Find the best position for the grabbed tile based on goal combinations + var best_position = find_best_position_for_tile(grabbed_tile) + + if best_position == Vector3i(-1, -1, -1): + print("No valid position found for tile") + return false + + # Place the tile at the best position + enhanced_gridmap.set_cell_item(best_position, grabbed_tile) + + # Reset grabbed tile + grabbed_tile = -1 + grabbed_position = Vector3i(-1, -1, -1) + + # Emit signal that tiles were arranged + tiles_arranged.emit([best_position]) + + print("Auto-arranged tile at position: ", best_position) + return true + +func find_best_position_for_tile(tile_id: int) -> Vector3i: + var best_position = Vector3i(-1, -1, -1) + var best_score = -1 + + # Check all possible positions on the grid + for x in range(enhanced_gridmap.columns): + for z in range(enhanced_gridmap.rows): + var position = Vector3i(x, 0, z) + + # Skip if position is already occupied + if enhanced_gridmap.get_cell_item(position) != -1: + continue + + # Temporarily place the tile to check for goal combinations + enhanced_gridmap.set_cell_item(position, tile_id) + + # Calculate score for this position + var score = calculate_position_score(position) + + # Remove the temporary tile + enhanced_gridmap.set_cell_item(position, -1) + + # Update best position if this one scores higher + if score > best_score: + best_score = score + best_position = position + + return best_position + +func calculate_position_score(position: Vector3i) -> int: + var total_score = 0 + + # Check each goal combination + for goal in goal_combinations: + var matches = check_goal_combination(position, goal.pattern) + if matches: + total_score += goal.score + + return total_score + +func check_goal_combination(center_position: Vector3i, pattern: Array) -> bool: + # Get the pattern dimensions + var pattern_width = pattern[0].size() + var pattern_height = pattern.size() + + # Calculate the top-left position of the pattern + var start_x = center_position.x - (pattern_width / 2) + var start_z = center_position.z - (pattern_height / 2) + + # Check if the pattern fits within the grid + if start_x < 0 or start_z < 0 or start_x + pattern_width > enhanced_gridmap.columns or start_z + pattern_height > enhanced_gridmap.rows: + return false + + # Check each cell in the pattern + for z in range(pattern_height): + for x in range(pattern_width): + var pattern_value = pattern[z][x] + var grid_position = Vector3i(start_x + x, 0, start_z + z) + var grid_value = enhanced_gridmap.get_cell_item(grid_position) + + # If pattern expects a tile (1) but grid is empty (-1), no match + if pattern_value == 1 and grid_value == -1: + return false + + # If pattern expects empty (0) but grid has a tile, no match + if pattern_value == 0 and grid_value != -1: + return false + + # All cells match the pattern + return true diff --git a/scenes/tile_grabber.gd.uid b/scenes/tile_grabber.gd.uid new file mode 100644 index 0000000..f212b64 --- /dev/null +++ b/scenes/tile_grabber.gd.uid @@ -0,0 +1 @@ +uid://dc1cw21830bfp