sortingluatabletop-simulator

Algorithm used to prevent a player from getting their same card


I have a game I am coding in Tabletop Simulator where all players (P) is given a card (C). Once memorized all players put the card back into the deck (D), shuffled, and then all players are dealt one of the cards from the same deck (D). I am trying to code the simplest algorithm that prevents a player from getting their own card. Now when it comes to coding this should be simple I assume instead of creating simulations to run until it is successful.


Solution

  • Say you have the following:

    Then the solution is simply

    local card_ids = {}
    for i, card_data in ipairs(deck.getObjects()) do
       table.insert(card_ids, card_data.guid)
    end
    
    for player, seen_card_id in pairs(seen_card_id_by_player) do
       local card_id = table.remove(card_ids)
    
       if card_id == seen_card_id then
          local i = math.random(1, #card_ids)
          card_ids[i], card_id = card_id, card_ids[i]
       end
    
       -- Deal the specific card.
       deck.takeObject({
          guid     = card_ids[i],
          position = player.getHandTransform().position,
          flip     = true,
       })
    end
    

    When we pick the card the player has already seen, it is placed back at a random location among the remaining cards. This ensures that every card has an equal chance of being drawn by the next player. This is the underlying principle of the Fisher-Yates shuffle.


    Full demonstration

    function broadcast_error(msg)
       broadcastToAll(msg, { r=1, g=0, b=0 })
    end
    
    
    function get_cards_seen_by_players()
       local player_ids = Player.getAvailableColors()
    
       local error = false
       local seen_card_by_player = {}
       for i, player_id in ipairs(player_ids) do
          local player = Player[player_id]
          local hand_objs = player.getHandObjects()
          local player_error = false
          if #hand_objs > 1 then
             player_error = true
          elseif #hand_objs == 1 then
             local card = hand_objs[1]
             if card.tag ~= "Card" then
                player_error = true
             else
                seen_card_by_player[player] = card
             end
          end
    
          if player_error then
             broadcast_error(player_id .. " doesn't have a valid hand.")
             error = true
          end
       end
    
       if error then
          return nil
       end
    
       return seen_card_by_player
    end
    
    
    function run()
       local deck = getObjectFromGUID("...")
    
       local seen_card_by_player = get_cards_seen_by_players()
       if seen_card_by_player == nil or next(seen_card_by_player) == nil then
          return
       end
    
       local seen_card_id_by_player = {}
       for player, card in pairs(seen_card_by_player) do
          local card_id = card.guid
          seen_card_id_by_player[player] = card_id
          card.putObject(deck)
       end
    
       deck.randomize()
    
       local card_ids = {}
       for i, card_data in ipairs(deck.getObjects()) do
          table.insert(card_ids, card_data.guid)
       end
    
       for player, seen_card_id in pairs(seen_card_id_by_player) do
          local card_id = table.remove(card_ids)
    
          if card_id == seen_card_id then
             local i = math.random(1, #card_ids)
             card_ids[i], card_id = card_id, card_ids[i]
          end
    
          deck.takeObject({
             guid     = card_ids[i],
             position = player.getHandTransform().position,
             flip     = true,
          })
       end
    end
    

    Create a game with a deck of cards. Place the above code in Global, replacing ... with the deck's GUID. To run the demonstration, deal one card to any number of players, then use /execute Global.call("run") in the chat window.