How should I prevent a pick_random() call from choosing the same option twice in a row?

Godot Version

Godot v4.2.2

Question

I am making a game where I have “debris” physics objects that when called in they randomly choose one of many spawn points to originate from, It works but every now and then two will choose the same spot and overlap. My question basically boils down to: What is the best way to prevent two from choosing the same spawn point during any given spawn event?

Its fine if an already chosen spawn is chosen again in a subsequent timer event but I want to prevent it from being chosen twice in the same timer event, if that makes sense.

The way my code is setup is that there is a Level Manager script that has a function for spawning in a debris object, bestowing it an effect, and letting it drop. A timer will call this event.

This is my current setup:

func spawn_debris(num):
	var spawn_x
	var spawn_y
	
	for amount in num:
		var debris = DebrisList[WeightedChoice.pick(DebrisList, "rarity")]["file"].instantiate()
		
		if debris.is_large == true:
			spawn_y = LargeSpawns.pick_random().position.y
			spawn_x = LargeSpawns.pick_random().position.x
		else:
			spawn_y = SmallSpawns.pick_random().position.y
			spawn_x = SmallSpawns.pick_random().position.x
	
		add_child(debris)
		debris.debris_effect.push_back(EffectList[WeightedChoice.pick(EffectList, "rarity")]["file"])
		debris.position.y = spawn_y
		debris.position.x = spawn_x


func _on_spawn_timer_timeout():
	spawn_debris(randi_range(0, 2))

its very messy but here is the spawn point arrays being called

@onready var small_1 = $"Environment/Level/Spawners/Small 1"
@onready var small_2 = $"Environment/Level/Spawners/Small 2"
@onready var small_3 = $"Environment/Level/Spawners/Small 3"
@onready var small_4 = $"Environment/Level/Spawners/Small 4"
@onready var small_5 = $"Environment/Level/Spawners/Small 5"
@onready var small_6 = $"Environment/Level/Spawners/Small 6"
@onready var small_7 = $"Environment/Level/Spawners/Small 7"
@onready var small_8 = $"Environment/Level/Spawners/Small 8"
@onready var small_9 = $"Environment/Level/Spawners/Small 9"
@onready var small_10 = $"Environment/Level/Spawners/Small 10"
@onready var small_11 = $"Environment/Level/Spawners/Small 11"
@onready var small_12 = $"Environment/Level/Spawners/Small 12"

@onready var large_1 = $"Environment/Level/Spawners/Large 1"
@onready var large_2 = $"Environment/Level/Spawners/Large 2"
@onready var large_3 = $"Environment/Level/Spawners/Large 3"
@onready var large_4 = $"Environment/Level/Spawners/Large 4"
@onready var large_5 = $"Environment/Level/Spawners/Large 5"
@onready var large_6 = $"Environment/Level/Spawners/Large 6"

@onready var SmallSpawns: Array = [small_1, small_2, small_3, small_4, small_5, small_6,
	small_7, small_8, small_9, small_12]
@onready var LargeSpawns: Array = [large_1, large_2, large_3, large_4, large_5, large_6]

Any help appreciated!

pick a card from the deck, remove the card, when you run out of cards, reshuffle the deck.

const OPTIONS: Array[Node]

var deck: Array[Node]

func reshuffle() -> void:
    deck = OPTIONS.duplicate()
    deck.shuffle()

func draw_card() -> Node:
    if deck.size() == 0:
        reshuffle()
    return deck.pop_back()

You might need two "deck"s for small and large spawns.

This method can still pick two in a row between re-shuffles, but it is less common and also guarenteed to go through every option per shuffle. If you really need it to just not pick the same option you have to track the last option, a simple approach is to re-roll if the last it picked again; this can hang the system with bad enough luck and a short enough list.

Another option is to keep track of, for example, the last 3, and make sure you don’t select them. Something like this.

var LastFewSmall: Array = []

func _ready():
    SmallSpawns.shuffle()
    for i in 3:
        LastFewSmall.push_back(SmallSpawns.pop_back())

func random_small_spawn():
    var spawn = SmallSpawns.pick_random()
    SmallSpawns.erase(spawn)
    LastFewSmall.push_back(spawn)
    SmallSpawns.push_back(LastFewSmall.pop_front())
    return spawn

Keep in mind when using this, you should only call it once to get a spawn. In your code you were calling pick_random() twice which is (probably) not what you want.

var spawn
for amount in num:
		var debris = DebrisList[WeightedChoice.pick(DebrisList, "rarity")]["file"].instantiate()
		
		if debris.is_large == true:
			spawn = random_large_spawn()
		else:
			spawn = random_small_spawn()
	
		add_child(debris)
		debris.debris_effect.push_back(EffectList[WeightedChoice.pick(EffectList, "rarity")]["file"])
		debris.position = spawn