◀ 10.2b10.2c10.2d ▶

10.2c — Decks: Shuffle, Draw & Traverse

The starter's Shuffle() is EMPTY — check your copy, and count the methods while you are there, because there are two of them. That emptiness is a gift: the single most exam-famous algorithm of the year, left in the seat where you sit. Today you implement the shuffle, wire the draw the starter never had, and traverse a hand to render it.

In this lesson you will be able to answer“how do I shuffle a deck and draw from it?”Guided build
Break It Down~10 minGuided build

The starter hands you a card game whose Shuffle is empty twice over, whose deck can never reach a hand, and whose hands are therefore always empty lists. "How do I shuffle a deck and draw from it?" is not one problem. It is five: two methods sharing a name and answering to different list types, an algorithm that has to be exactly right or the game is quietly rigged, a swap written in the one order that does not lose a card, a move between two lists that has to survive an empty deck, and a loop that turns list positions into screen positions.

First, before any code: cut that into parts small enough that you could look each one up on its own.

  1. Notice the same method name is written twice, for two different list typesShuffle has two overloads in the starter — one taking List<Card_data>, the deck of ScriptableObject assets from 10.2b, and one taking List<Card>, the components on screen. C# picks between them from the type of the argument at the call site, and Deal() decides which one it reaches by what it passes.Read: the two Shuffle signatures in Assets/Scripts/GameManager.cs, the two calls to them inside Deal(), and the List<Card_data> / List<Card> split in the field list at the top of that file — the same six Lists you counted in 10.2a.
  2. write this partRead: the RANDOM(a, b) row of the exam reference sheet, which prints the C# translation and warns that Unity is exclusive at the top for ints — and pattern 4, "Swap (the shuffle's spine)", in the Lists & Arrays reference, which names Fisher-Yates and says what makes it fair.
  3. Swap the two slots through a temp, in the order that does not lose a cardThree statements, and the order is the whole of it: copy list[i] into temp first, then write list[r] into list[i], then write temp into list[r]. The AP pseudocode writes exactly the same three lines — temp <- aList[i], aList[i] <- aList[r], aList[r] <- temp.Read: pattern 4, "Swap (the shuffle's spine)", in the Lists & Arrays reference — the AP and C# columns side by side — and the same page's warning about which statement has to come first.
  4. Move the top card out of the deck and into the hand, and refuse when there is noneRead deck[0], hand the data to a card component, Add that component to the hand, and RemoveAt(0) from the deck — in that order, because removing first changes what index 0 even is. The guard in front of all of it is the boundary case: deck[0] on an empty list is the crash 10.2a's socratic asked you to imagine at draw #31.Read: the APPEND(aList, item) and REMOVE(aList, i) rows of the Lists & Arrays reference, that page's "Bug 2 — the bounds", and the socratic in 10.2a that asked what breaks at draw #31 if you never remove.
  5. Traverse the hand by index to place every cardA for loop from 0 to Count - 1, and the loop counter IS the screen position: i multiplies the spacing. The hand literally spreads because of a counter. A foreach visits the same cards and can do nothing with their places, because it never has an i.Read: the FOR i FROM 1 TO LENGTH row of the Lists & Arrays reference, and patterns 1 and 2 on the same page — traverse to total, and traverse with a filter — which are the same loop with a different body.

Three of those five parts are things you have already typed. Reading the file to see which overload a call reaches is the two-minute read that opens every jam day; the three swap statements are pattern 4 from 10.2a's lists; and moving a card from one list to another is RemoveAt plus Add, which is the AP's REMOVE plus APPEND and which 10.2a already made you translate on sight. What is left is the random slot and the empty-deck guard — and the random slot is the reason this is a guided build rather than a full walkthrough. It is one line, and it carries three separate things at once: AP counts list slots from 1 and C# from 0, the exam's RANDOM(a, b) is inclusive at both ends and Unity's int version is not, and the word Random in this template names two different classes, so the call has to say which.

The test: could you look this part up on its own, right now, in one search? "Shuffle a deck" cannot — it is an algorithm, a list operation and a boundary case wearing one name. "How does C# Remove an item from a List" can, and you have already read the row that answers it.

Your turn

The shuffle is the one algorithm in this unit whose bug is invisible in a single play: a silently biased deck deals the same card first again and again and still looks random. How would you test for that — and what did 9.5 teach you about believing a claim about randomness?

Your hand is a List<Card> and your deck is a List<Card_data>. If you build the discard pile the starter already declares a field for, which overload does reshuffling it need, and what does your answer say about where a shuffle method belongs?

Concept Lab ~15 min

Fisher-Yates, in exam language. The AP pseudocode for a shuffle (know BOTH languages):

AP:                                C#:
FOR i FROM LENGTH(list) TO 2     for (int i = list.Count - 1; i > 0; i--)
    r ← RANDOM(1, i)                 int r = UnityEngine.Random.Range(0, i + 1);
    SWAP(list[i], list[r])           Card_data temp = list[i];
                                     list[i] = list[r];
                                     list[r] = temp;

Read it as a story: walk the list from the END; at each position, pick a random EARLIER-or-equal position; swap. Every card gets exactly one chance at every slot — that fairness (uniform distribution) is why this is THE textbook shuffle, not a vibe shuffle.

Two translations, and both bite. AP lists are 1-indexed and C# lists are 0-indexed, so AP's slots LENGTH..2 are C#'s (Count-1)..1 — the same slots, written two ways. And AP's RANDOM(a, b) includes BOTH ends while Unity's Random.Range(a, b) for ints excludes the top, which is why the C# column says i + 1. Write Range(0, i) and the slot at i can never be picked, the last card never moves, and the deck still looks shuffled. The exam reference sheet's RANDOM row prints this same AP-to-C# difference; the Lists & Arrays reference prints the swap as pattern 4.

Why the call says UnityEngine. out loud. This template installs com.unity.mathematics — not directly, but pinned in Packages/packages-lock.json as a dependency of something else — and GameManager.cs has using Unity.Mathematics; at the top. That package defines Unity.Mathematics.Random; UnityEngine defines Random too. So a bare Random.Range(...) in that one file is CS0104: 'Random' is an ambiguous reference and the game will not build. Two legal fixes: fully qualify the call, or delete the using Unity.Mathematics; line. The AP exam never tests namespace resolution. Every real C# codebase does.

Socratic

The lazy shuffle everyone writes first: repeatedly pick a random card and REMOVE it into a new list. It works — why is it worse than Fisher-Yates (cost per removal: the list shifts every element)? And why does the exam phrase "every possible order is equally likely" make the lazy version suspicious?

Build It With MeGuided build

The question this answers“how do I shuffle a deck and draw from it?”

You are editing Assets/Scripts/GameManager.cs. This is the card game template's own script — the one with the two empty Shuffle methods and the working Deal(). You fill both overloads with Fisher-Yates, add the draw that carries a card from the deck to the hand, and add the loop that lays the hand out by index. Deal() gets rewired at the end. Exactly two lines are yours to write, and both of them are the new idea.

This build leaves 2 lines for you to write. Everything around them is given, so you always know the shape of what you are producing.

Step 1Read the caller, then count the methods it can reach

Two minutes of reading, no writing. Deal() is real working code, and it already tells you which list types this file deals in — read it before you touch anything.

    void Deal()
    {
        Shuffle(player_deck);
        Shuffle(ai_deck);
    }

    void Shuffle(List<Card_data> data_hand)
    {
    }
    void Shuffle(List<Card> card_hand)
  1. 1The starter's own method. It is already written, it already runs, and it already calls the method you are about to fill in.
  2. 3The first call. player_deck is declared as List<Card_data> — the ScriptableObject assets from 10.2b — so C# resolves this call to the overload that takes a List<Card_data> and not the other one.C# chooses an overload from the argument's type, at the call site. That is the whole of overloading: one name, several signatures, and the compiler picks by what you pass. It means the two empty methods below are not duplicates — they are two different jobs sharing a word, and only one of them is reachable from this line.
  3. 4The second call, and it is the same overload: ai_deck is a List<Card_data> as well. So Deal() today reaches exactly one of the two empty methods.
  4. 7Overload (a) — the deck, empty in the starter. This is the one Deal() calls.
  5. 10Overload (b) — the hand, also empty. Nothing in the starter calls it yet, because player_hand and ai_hand are List<Card> and nothing shuffles a hand today.Implement one and leave the other, and the other call is a silent no-op. No error, no warning, no message in the Console: the list comes back in exactly the order it went in. C# does not warn about an unused parameter, so an empty method body is a legal method that does nothing. That is the shape of this bug — a Shuffle(player_hand) later in the unit that compiles, runs, and shuffles nothing, while the deck beside it works perfectly.

Step 2Fisher-Yates, walking from the end — and the one line you write

The algorithm the exam prints, in C#. Walk the list from the end; at each slot, pick a random earlier-or-equal slot; swap. One line is yours — the random pick — and it is the line where the AP and C# genuinely disagree.

    void Shuffle(List<Card_data> data_hand)
    {
        for (int i = data_hand.Count - 1; i > 0; i--)
        {
            int r = ______________________________________;

            Card_data temp = data_hand[i];
            data_hand[i] = data_hand[r];
            data_hand[r] = temp;
        }
    }
  1. 1Overload (a) again, now with a body. The parameter is the list the caller passed, so this method never needs to know it is player_deck rather than ai_deck.
  2. 3The loop, and it runs backwards: i starts at the last valid index (Count - 1) and stops when it reaches 1. Read it next to the AP's FOR i FROM LENGTH(list) TO 2 — AP's slot 1 is C#'s slot 0, so AP's range LENGTH..2 is C#'s (Count-1)..1, and the two loops visit the same slots in the same order.Why from the end, and why stop before 0. Working backwards means the slots behind i are still unshuffled and the slots ahead of it are already settled, which is what makes every ordering equally likely. And the loop stops at 1, not 0: when one card is left there is nothing to swap it with, because a single card is already in the only slot it can occupy. Swapping slot 0 with slot 0 would be a no-op — the AP writes that same stopping point as TO 2 in 1-indexed slots.
  3. Your turn5Your line — the whole right-hand side. It picks the slot to swap with: a random index from the start of the list up to and including i. The declaration on the left is fixed — r is the name the three lines below read, so do not rename it — and everything from the = onward is yours to work out.Two numbers and a class name go into it. The first number is the bottom of the range: the first slot in the list, and C# counts slots from 0 while the AP counts from 1. The second is the top, written as i plus one — because the exam's RANDOM(a, b) includes both ends and Unity's int version does not, which the RANDOM(a, b) row of the exam reference sheet spells out. The class name is the one the using lines at the top of GameManager.cs have made ambiguous: Random is defined by both UnityEngine and Unity.Mathematics in this template, so the call has to say which one it means. The RANDOM(a, b) row and the LENGTH(aList) row of the Lists & Arrays reference give you the indexing rule.UnityEngine.Random.Range(0, i + 1) — and both halves of that matter. The i + 1 is the max-exclusive correction: Unity's int overload never returns its upper bound, so Range(0, i) returns 0..i-1 and the slot at i could never be its own partner on this pass. The last card in the list would then never move at all — a shuffle that is wrong in a way that still looks shuffled. The UnityEngine. is the second half. com.unity.mathematics is installed in this template (transitively — it is not in Packages/manifest.json, but Packages/packages-lock.json pins it at depth 2), and it defines Unity.Mathematics.Random; UnityEngine defines Random too. A bare Random.Range(...) in this file is therefore error CS0104: 'Random' is an ambiguous reference and the script does not build. Qualifying the call is the fix that survives somebody adding that using back; deleting using Unity.Mathematics; from the top of the file is the other legal fix, and it is one line you would have to explain to the next person.
  4. 7The swap begins by copying the card at i somewhere safe. This is pattern 4 from the Lists & Arrays reference, in this file's own names — and it is Card_data, because that is what this list holds.The temp is not optional and the order is not optional. Overwrite data_hand[i] before you have saved it and the card that was there is gone from the list entirely — the two statements below then write the same card into both slots.
  5. 8The random slot's card moves into slot i. Read it as the AP's aList[i] <- aList[r].
  6. 9And the saved card goes into the random slot. Read it as aList[r] <- temp. The pair is SWAP(list[i], list[r]) spread over three statements, exactly as the reference sheet prints it.
  7. 11Closes overload (a), which now shuffles. Overload (b) is still an empty body — step 3 fills it in.

Step 3The same six lines for the other overload

Nothing new happens here, and that is the point: overload (b) is the loop and the swap again, with Card in place of Card_data. Type it out rather than copying, and notice that not one of the six lines changes shape.

    void Shuffle(List<Card> card_hand)
    {
        for (int i = card_hand.Count - 1; i > 0; i--)
        {
            int r = UnityEngine.Random.Range(0, i + 1);
            Card temp = card_hand[i];
            card_hand[i] = card_hand[r];
            card_hand[r] = temp;
        }
    }
  1. 1Overload (b) — the hand. List<Card> is the components, not the assets: the thing that is on screen.Both overloads are needed because the starter has two kinds of list, and it is not an accident. The deck is List<Card_data> because a deck is data you have not played yet; the hand is List<Card> because a hand is something you can see and drag. The moment a later lesson reshuffles a hand — or a discard pile back into a deck — it will call this overload, and until you fill it in that call does nothing at all. It will not tell you.
  2. 3The identical loop. The only difference from overload (a) is the parameter name, which is the one thing that has to change: data_hand and card_hand are separate lists of separate types.
  3. 5The line you wrote in step 2, unchanged — and here it is printed, because the shape is worth seeing twice. If your version in step 2 compiles and this one does not, the two are not the same line.A generic Shuffle<T> would remove this duplication, and this is not the lesson for it. Generics are real C# and the exam never asks for one; two short methods are the honest version at this point in the year.
  4. 6The swap again, and the only other word that changes: Card, not Card_data. If you wrote Card_data here the file will not compile, and the error will point at this line rather than at the list it came from.

Step 4Draw: carry the top card from the deck into the hand

The starter has no draw at all — which is why its hands stay empty. Four statements and one guard, and the guard is the second line you write.

    void Draw(List<Card_data> deck, List<Card> hand, Vector3 where)
    {
        if (________________________________) return;

        GameObject new_card = Instantiate(blank_card, where, Quaternion.identity, canvas.transform);
        new_card.GetComponent<Card>().data = deck[0];
        hand.Add(new_card.GetComponent<Card>());
        deck.RemoveAt(0);
    }
  1. 1Three parameters, and every one of them is load-bearing. deck is the List<Card_data> to draw from, hand is the List<Card> to draw into, and where is the screen position the new card appears at. The method takes them as parameters instead of reaching for player_deck and player_hand because the starter has two of each, and a draw that only worked for the player would have to be written twice.
  2. Your turn3Your line — the guard, and the whole condition inside the parentheses. It answers one question: when is there nothing left to draw? The if, the return; and the shape are given; what you write is the condition between the parentheses.Two pieces go inside. The first is the C# half of the LENGTH(aList) row in the Lists & Arrays reference — the property that answers "how many items", read off the deck parameter this method was handed. The second is the question you are asking of it, and the same page's "Bug 2 — the bounds" is about reading an index a list does not have: this line is how you refuse before you get there. = is the wrong operator here and the file will not build with it — one = assigns, and you are asking.deck[0] on an empty list is not a card and not a null — it throws. Which exception it throws is worth reading off your own Console once: the guard clause is the entire difference between a jam build and a jam crash, and the same four lines are why a real card game can survive a player who discards everything and then draws. It also keeps the shuffle honest: the deck is empty exactly when every card has been drawn, which is the state 10.2a asked you to imagine at draw #31.
  3. 5The starter's own Instantiate line, moved here from Deal() verbatim — the blank card prefab, cloned at where, no rotation, parented to the canvas. Deal() no longer needs it, because it will call this method instead.This is why the card has to be created here and not later. Card.Start() reads data.card_name and the rest on the first frame after the object exists, so a visual that is instantiated and left with an empty data throws a NullReferenceException in Card.Start() — from a line in a different file, which is why it is worth knowing about before you see it.
  4. 6Hand the drawn card its data. deck[0] is the top of the deck, and the top of a shuffled deck is a random card — which is the whole reason the shuffle had to be right.The hand holds Card, so the new visual has to be one. A Card_data is an asset on disk; a Card is a component on a GameObject. GetComponent<Card>() is how you get from the clone you just made to the component that will be in the list.
  5. 7The card joins the hand. This is the AP's APPEND(hand, topCard) — and it is the line the starter never had, which is why player_hand.Count is 0 in the template as shipped.
  6. 8And the deck gives it up. This is the AP's REMOVE(deck, 1) — index 1 in exam-speak, because the exam counts list slots from 1 and C# counts from 0.Remove LAST, never first. RemoveAt(0) shifts every remaining card down one slot, so doing it before the three lines above would leave deck[0] pointing at the second card — you would draw the second card and quietly lose the first. The order in this method is not style; it is the difference between a draw and a bug.

Step 5Traverse the hand by index to lay it out

The hand is a list, so showing it means visiting every element — and because the loop counter is also the position, this is the loop that has to be an index loop rather than a foreach.

    void LayoutHand(List<Card> hand, Vector3 origin)
    {
        for (int i = 0; i < hand.Count; i++)
        {
            hand[i].transform.position = origin + new Vector3(i * 160f, 0f, 0f);
        }
    }

        // ... and when you only need each card, not its place:
        int total = 0;
        foreach (Card c in hand)
        {
            total += c.data.cost;
        }
  1. 1A separate method, so that laying the hand out is one named thing that can be called again whenever the hand changes. Same shape as Draw: take the list as a parameter, do not reach for a field.
  2. 3The index loop — the FOR i FROM 1 TO LENGTH row of the Lists & Arrays reference, written the C# way. It runs forward and includes the last card, which is the opposite of the shuffle's loop, and it is safe here because nothing is being removed.Why this one cannot be a foreach. The loop counter i is the only thing that knows which position this card occupies, and the next line needs exactly that. A foreach (Card c in hand) visits the same cards in the same order and never tells you where any of them is — the loop you met in 8.3, and the right tool when the body only cares about the element.
  3. 5Position comes straight out of the counter: card 0 sits at origin, card 1 one step to the right, card 2 two steps. The 160 is a number somebody chose — the card's width plus a gap — which is why it is a literal here and a real knob in a polished game.This is the line that makes the hand spread, and it is the same "position is the index" idea the exam's index-loop questions are really testing. Change 160 and the fan tightens; make it negative and the hand runs off the other way. Nothing about the list changed.
  4. 9The other traversal, for contrast. This is the shape the AP calls FOR EACH item IN aList.
  5. 10An accumulator, seeded at zero — pattern 1 in the Lists & Arrays reference, traverse to total.
  6. 11A foreach over the hand. No index anywhere, because this loop has nothing to say about positions.
  7. 13Notice the .data: c is a Card component, and the cost lives on the Card_data asset it points at. Card.cs also copies the value into its own cost field in Start(), so c.cost would work too — but the asset is the source of truth, and reading it there cannot go stale.This is the traverse-with-a-filter question from the exam, in your own game. "Sum the cost of every card in hand" is this loop with a + in it; "sum the cost of the cards you can afford" is the same loop with an if inside — pattern 2 on that reference page. Write both in AP pseudocode with these names before you write them in C#.

Step 6Rewire Deal() and prove the shuffle is real

The starter's Deal() did its own instantiating and never touched the hands. Now that Draw does that work, Deal() gets shorter — and the game gets a real hand for the first time.

    void Deal()
    {
        Shuffle(player_deck);
        Shuffle(ai_deck);

        Draw(player_deck, player_hand, player_hand_location);
        Draw(ai_deck, ai_hand, ai_hand_location);

        player_hand[0].GetComponent<DraggableUI>().enabled = false;
    }

// Prove it in the Console, then delete the proof:
// Prove the shuffle is not rigged:
  1. 3Unchanged from the starter — and it matters that this runs before the draws. Draw takes deck[0], so whatever card the shuffle left at the top is the card that gets dealt.
  2. 4Unchanged. Both decks are shuffled before either is drawn from, and neither shuffle can reach the other deck, because each call passes one list.
  3. 6One card from the player's deck onto the player's hand, at the player's position. The two Instantiate blocks the starter had here are gone — Draw does that work now, and it does it into the hand instead of into thin air.
  4. 7The same line for the other side. ai_hand_location is computed in Start() as the player's position plus 350 up, and that line is untouched.
  5. 9The starter's own line, and it still works: player_hand[0] is now a Card component, so GetComponent<DraggableUI>() finds the drag behaviour on the same GameObject. The player's drawn card is not draggable — for now.What actually changed here, and it is the biggest thing on this page. The starter's Deal() instantiated a visual, assigned its data, and then dropped the reference — so player_hand and ai_hand stayed empty no matter how many cards were on screen. Every loop over a hand therefore did nothing, silently. Draw puts the card into the list as well as onto the canvas, so from today the hand is a real list you can traverse, count and lay out. That is what makes any of the turn logic possible at all.
  6. 12One run with a Debug.Log(player_hand.Count + " in hand, " + player_deck.Count + " left in deck"); at the end of Deal() should print 1 in hand, 29 left in deck if your deck is thirty cards. Then delete the log — a print that survives into the next lesson is noise you will read past.
  7. 13Play the scene ten times and watch which card is dealt first. Ten identical first cards is not luck — it is the off-by-one in Range(0, i) (see the note on i + 1 in step 2). Ten different ones is a shuffle you can trust — though ten glances is not a count, and a real count is worth writing.
Check your work: the finished filetry every gap first — then open this to compare

Compare against your own file rather than copying it. Three things have to match and nothing else does: both Shuffle overloads are filled in, the random pick is UnityEngine.Random.Range(0, i + 1) with the max-exclusive + 1 and the qualified class name, and Draw reads deck[0] before it removes it, behind the empty-deck guard. Your parameter names, your spacing constant, your deck size and everything Deal() does after the two draws are yours. The gap line is the one that carries the whole lesson: it is the only place the AP pseudocode and C# genuinely disagree, in three ways at once.

// ------------------------------------------------------------------
//  REFERENCE FILE — Assets/Scripts/GameManager.cs
//  Written to be read, not pasted. Build it with the walkthrough
//  above, one step at a time, then open this and compare.
//  If it is sitting in your project unchanged, that is the thing
//  to go and do now — the walkthrough is still above you.
// ------------------------------------------------------------------
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;

public class GameManager : MonoBehaviour
{
    public static GameManager gm;

    // The decks are Card_data (the 10.2b assets); the hands and the piles are
    // Card (the components on screen). Two element types, so two Shuffle overloads.
    public List<Card_data> player_deck = new List<Card_data>();
    public List<Card_data> ai_deck = new List<Card_data>();
    public List<Card> player_hand = new List<Card>();
    public List<Card> ai_hand = new List<Card>();
    public List<Card> player_discard_pile = new List<Card>();
    public List<Card> ai_discard_pile = new List<Card>();

    public Canvas canvas;
    public GameObject blank_card;
    public Vector3 player_hand_location;
    public Vector3 ai_hand_location;

    private void Awake()
    {
        if (gm != null && gm != this)
        {
            Destroy(gameObject);
        }
        else
        {
            gm = this;
            DontDestroyOnLoad(this.gameObject);
        }
    }

    void Start()
    {
        canvas = FindAnyObjectByType<Canvas>();
        ai_hand_location = player_hand_location + new Vector3(0, 350, 0);

        Deal();
    }

    void Deal()
    {
        // Shuffle both decks BEFORE anything is drawn: Draw takes deck[0],
        // so whoever the shuffle left on top is who gets dealt.
        Shuffle(player_deck);
        Shuffle(ai_deck);

        Draw(player_deck, player_hand, player_hand_location);
        Draw(ai_deck, ai_hand, ai_hand_location);

        // The starter's own line, now reading the hand Draw just filled.
        player_hand[0].GetComponent<DraggableUI>().enabled = false;
    }

    // Fisher-Yates, overload (a) — the decks. Walk from the end; at each slot
    // pick a random earlier-or-equal slot; swap through a temp.
    // AP: FOR i FROM LENGTH TO 2 / r <- RANDOM(1, i) / SWAP(list[i], list[r])
    void Shuffle(List<Card_data> data_hand)
    {
        for (int i = data_hand.Count - 1; i > 0; i--)
        {
            // Range is MAX-EXCLUSIVE for ints, so i + 1 makes the range 0..i —
            // and UnityEngine. is required: Unity.Mathematics.Random is also
            // in scope in this file (see the Concept Lab callout on the ambiguity).
            int r = UnityEngine.Random.Range(0, i + 1);

            Card_data temp = data_hand[i];
            data_hand[i] = data_hand[r];
            data_hand[r] = temp;
        }
    }

    // The same six lines, for the hands and piles. Copy, do not guess: an empty
    // body here is a silent no-op, and nothing in C# will warn you about it.
    void Shuffle(List<Card> card_hand)
    {
        for (int i = card_hand.Count - 1; i > 0; i--)
        {
            int r = UnityEngine.Random.Range(0, i + 1);

            Card temp = card_hand[i];
            card_hand[i] = card_hand[r];
            card_hand[r] = temp;
        }
    }

    // Move the top of a deck into a hand, and put it on screen while you do it.
    void Draw(List<Card_data> deck, List<Card> hand, Vector3 where)
    {
        // The boundary: deck[0] on an empty list is a crash, not a null.
        if (deck.Count == 0) return;

        GameObject new_card = Instantiate(blank_card, where, Quaternion.identity, canvas.transform);

        // Read the top FIRST — RemoveAt(0) below would shift a different card
        // into index 0.
        new_card.GetComponent<Card>().data = deck[0];
        hand.Add(new_card.GetComponent<Card>());
        deck.RemoveAt(0);
    }

    // Position IS the index: the hand spreads because of a loop counter.
    // This one cannot be a foreach — a foreach has no i.
    void LayoutHand(List<Card> hand, Vector3 origin)
    {
        for (int i = 0; i < hand.Count; i++)
        {
            hand[i].transform.position = origin + new Vector3(i * 160f, 0f, 0f);
        }
    }

    void AI_Turn()
    {
        // The next lesson's problem.
    }
}

// The two loops side by side, for reference:
//
//   index loop  — you NEED i for positions:
//     for (int i = 0; i < hand.Count; i++) { Layout(hand[i], i); }
//
//   foreach     — you only need each card:
//     int total = 0;
//     foreach (Card c in hand) { total += c.data.cost; }
Build Lab ~35 min

Build it, then break it on purpose. The walkthrough above leaves you a working shuffle, a working draw and a hand that finally holds something. Break each of those three and watch what the Console says — every bug in this method is one somebody in this room will actually write.

Close of Day ~5 min

Checkpoint — before moving on, you must be able to:

  • Implement Fisher-Yates in C# and match it line by line to AP pseudocode
  • Explain why the C# range is (0, i + 1) and not (0, i)
  • Say why UnityEngine.Random has to be spelled out in this file
  • Implement BOTH Shuffle overloads, and say what breaks if you skip one
  • Draw safely with the empty-deck guard, reading the top card before removing it
  • Render a hand by index loop (position = index), and say why a foreach cannot
  • Explain remove-while-traversing and its backward fix

Reference Docs

Need a deeper explanation? These pages cover the same concepts:

Next: 10.2d — Turn Logic: Selection + Iteration

0/3 answered

Answers save on this device as you type. At the end of class, click Download and upload the file to the Canvas assignment.

Saved on this device only — the Dashboard tracks your progress.