Need advice on assigning unique items from a shared list

Godot Version

4.7

Question

Hi all, thank you for reading my question.

As a beginner Godot user, I am trying to create a system where I can assign X to Y (in my case, a hotel guests to rooms). The part I am struggling with is that system which manages what rooms are available and which are not. New guests are instantiated with an OptionButton, with a list of rooms to choose from: The rooms are in occupied and available groups, which should get updated _on_option_button_item_selected().

. After choosing a room, new guests aren’t offered the option to choose the occupied room, but old yet to be assigned guests are not updated.

I feel like this is not the correct way to approach the system design.

I would appreciate if more experienced users could suggest how to think about the problem, where to start from. I am happy to try design solutions from scratch, I would appreciate small hints and details as well. I am just trying to learn and being stuck on this problem for days has been frustrating.

If more details are needed, I am happy to provide them, just let me know. Thank you for your time.

Assigning X to Y is called mapping. A data structure that’s most suitable to maintain the mapping relationships is called hashmap aka associative array aka Dictionary.

You need a way for the ā€œoldā€ guests to retrieve or be updated with the latest state of the rooms.

There are a number of ways you could approach this:

  1. Whenever a room is occupied, send out a signal with the latest room states, then the guests on _ready() (or whenever) can connect to that signal. (think SignalBus).
  2. Whenever a room is occupied, have whatever is managing the Guests call methods on each one to update the latest room state.
    1. Guests could also be part of group ( Groups — Godot Engine (stable) documentation in English ) and whenever a room state changes, call a method on all the guests to update the room state.

I’m not sure to have understood the issue. As I see it, based on what I took from your description, all rooms are members of one of either the ā€˜Available’ group or the ā€˜Occupied’ group. Any room that has already been assigned a guest would be in the ā€˜Occupied’ group, all the others are ā€˜Available’. As and when a new guest requests a room, it would be chosen from the ā€˜Available’ group, and that room would be removed from that group, and made a member of the ā€˜Occupied’ group instead. Whenever a guest leaves (ā€˜checks out’…), the room would once again become a member of the ā€˜Available’ group, instead of ā€˜Occupied’. All existing guests have been affected to an ā€˜Occupied’ room at their arrival; what ā€˜update’ is required, except to free their room when they leave..?
The only issue I see is to be sure that there are enough rooms in the ā€˜Available’ group to accept a new guest, failing which a ā€˜No Vacancies’ sign should light up..!
Have I missed something..? :blush:

Thank you all for the quick replies, I spent my afternoon learning about the new concepts you guys suggested, and now I have something that I feel like is close: but not quite there. I created a separate project and rewrote every part of this system from scratch, if someone finds time to look over this implementation using a dictionary and signals, I would greatly appreciate advice on it!

main.gd

extends Node2D

var available_rooms : int

# On ready, I set the starting room numbers
func _ready() -> void:
	available_rooms = 4
	populate_dict(available_rooms, false, 0, 0)	
	
# I ask for amount of rooms, and if there is an occupancy change
func populate_dict( length : int, occupancy_change : bool, 
					room_number : int, guest_id : int):
	
	# Reset the OptionsButton
	$Guest/Control/OptionsButton.clear()
	$Guest/Control/OptionsButton.add_item("Select a room")
	$Guest/Control/OptionsButton.select(0)
	
	# If not, just set all the rooms in the dictionary to x
	if !occupancy_change:
		for i in range(length):
			Global.rooms_dict["Room"+str(i+1)] = "x"
			$Guest/Control/OptionsButton.add_item("Room" + str(i+1))
	
	# Else, set the entered room number to the guest's id
	else:
		for i in range(length):
			if i != room_number - 1:
				Global.rooms_dict["Room"+str(i+1)] = "x"
				$Guest/Control/OptionsButton.add_item("Room" + str(i+1))
			else:
				Global.rooms_dict["Room"+str(i+1)] = guest_id

# Receive the signal emmited from OptionsButton
func _on_guest_new_selection(index: int) -> void:
	populate_dict(available_rooms, true, index, $Guest.id)

guest.gd

extends Node2D

signal new_selection(index : int)

# Set an id for the guest
static var id : int = 0

# Forward the signal from OptionsButton to main
func _on_control_new_selection(index: int) -> void:
	new_selection.emit(index)

control.gd

extends Control

signal new_selection(index : int)

# On item selected, send over the item's index
func _on_popup_item_selected(index: int) -> void:
	new_selection.emit(index)

Scene tree:

main.tscn

main (Node 2D)
Ā» guest (Node 2D)

guest.tscn

guest (Node 2D)
Ā» Sprite 2D
Ā» Control
»» OptionsButton

I would appreciate criticism/advice/tips of any sort on any part of this. I just want to learn as much as I can with this project, so even unrelated topics interest me :grinning_face_with_smiling_eyes:

My first advice would be to encapsulate when possible.

Your main script is currently making changes in a child that may or may not be there, and if you have multiple guests then only 1 will get updated.

Here’s a simple autoload script that can be called from your Guest (to be created by you) whenever the button is clicked within the Guest.

room_manager.gd

extends Node

signal room_status_updated(status: Dictionary[int, int])

const UNASSIGNED: int = -1

var max_rooms: int = 4

## {room_id: int, guest_id: int
var room_status: Dictionary[int, int] = {}

func _ready() -> void:		
	for i in range(1, max_rooms + 1):
		# i being the room_id
		# -1 to indicate it is not taken since I don't know how your guest_ids are created
		room_status.set(i, UNASSIGNED)


func has_available_rooms() -> bool:
	# Check the value in the room_status dictionary and if any single room is free, return true
	for guest_id in room_status.values():
		if guest_id == UNASSIGNED:
			return true
	
	return false

func assign_room(guest: Guest) -> bool:
	if not has_available_rooms():
		return false

	var free_room_id := _get_free_room_id()

	if free_room_id != UNASSIGNED:
		room_status.set(free_room_id, guest.id)
		room_status_updated.emit(room_status)
		return true

	return false

func _get_free_room_id() -> int:
	for room_id in room_status:
		if room_status.get(room_id) == UNASSIGNED:
			return room_id

	return UNASSIGNED

As you can see, it has 2 public methods that can be called but to be extra careful, assign_room calls the public method has_available_room too in case the caller did not first check if any rooms are available.

So the idea here is that your Guest script would call the global RoomManager.has_available_rooms() in _ready() to get the initial state, then you can disable or change the button state as needed, as well as connect to the room_status_updated signal, that way whenever it is emitted, your Guest can update it’s state/button state to refect the current status of the rooms.

If there are rooms and you want to assign a guest to the room, then the Guest would call RoomManager.assign_room(self).

Note: My script lacks the removing of the guest from the room but you can figure that out yourself :wink:

Note 2: The Guest class should have an id variable defined on creation.

Thank you all very much. I learned a lot from you guys.