◀ 10.2c10.2d10.2e ▶

10.2d — Turn Logic: Selection + Iteration

The starter's other empty method: AI_Turn(). Your opponent's brain, and the last thing between your card game and a game somebody can play alone at home. Your job is to give the game the thing it has never had: a turn that belongs to somebody else.

In this lesson you will be able to answer“how do I make the computer take a turn?”Outline only
Break It Down~10 minOutline only

The starter hands you a card game with an opponent that does not exist. AI_Turn() has an empty body, Update() has an empty body, and nothing in the project says whose turn it is. "How do I make the computer take a turn?" is not one problem. It is six: work out what the AI can actually see, visit all of it, apply a rule that picks one thing out, do that one thing, refuse safely when there is nothing to do, and hand the turn back on a schedule the player can read.

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

  1. Know what the AI can see: a hand that is a list of cardswhat does this part do?Read: the ai_hand field in Assets/Scripts/GameManager.cs, the Draw that finally puts something in it (10.2c), and the copy block at the top of Start() in Assets/Scripts/Card.cs
  2. Visit every card in itwhat does this part do?Read: patterns 1 and 2 of the Lists & Arrays reference, "traverse to total" and "traverse with a filter" — the same loop with a different body
  3. Decide which card to play, by a rule you can say out loudwhat does this part do?Read: pattern 3, "Find the best (the AI score loop)", in the Lists & Arrays reference — the page that already names this lesson as where that loop lands
  4. Do one thing with the winner: move it out of the handwhat does this part do?Read: the RemoveAt and Add columns of the AP-to-C# table in 10.2a, and the two discard-pile fields sitting unused in Assets/Scripts/GameManager.cs
  5. Refuse safely when there is nothing to playwhat does this part do?Read: the empty-deck guard that opens Draw in 10.2c, and "Bug 2 — the bounds" in the Lists & Arrays reference, which is the crash the guard exists to prevent
  6. Hand the turn back: a state that says whose turn it iswhat does this part do?Read: 4.5, where one field said what an enemy was doing and every other behaviour read it, and 3.3, where Time.time stopped something happening every single frame

Five of these six parts are things you have already typed, and that is the whole reason this lesson is an outline rather than a walkthrough. Iteration is 10.2a's foreach; the running best is pattern 3 of the Lists & Arrays reference, printed with the AP version beside it; sequencing is RemoveAt plus Add; the guard is 10.2c's empty-deck guard pointed at a hand; and the pause is 3.3's timestamp. What is genuinely new is the last part — a state field that decides whose turn it is — and it is new because it is about the GAME, not about the AI. Get the split right and the method is short: a loop, a comparison, a move and a way out. Get it wrong and you write a click handler that plays cards for both players.

The same test as every other lesson: can this part be looked up on its own, in one search, right now? "Make the computer take a turn" cannot — it is a data question, a loop, a rule, a list operation, a boundary and a piece of game state wearing one name. "How do I remove an item from a C# List" can, and 10.2a already printed the row that answers it.

Your turn

Pick two of the parts above and say in one line what each one actually does.

Your AI plays its best card every turn, for the whole game, and always wins. Is that a balance problem or an AI problem — and what single number would you change first to find out which one it is?

An AI that picks a card at random plays worse and is harder to test. What does 9.5 say about a claim you cannot repeat — and which of those two opponents could somebody actually write a bug report about?

The turn is one field and the pause before the AI moves is one number. Which of the two is game state and which is presentation — and what does the player lose if you get them the wrong way round?

Concept Lab: What a Turn Is ~15 min

Three layers, three exam words. The method you are about to write is the exam's own algorithm definition — sequencing, selection and iteration — assembled into one method you can point at on the exam as yours. Name the layers before you name any code:

  1. Iteration — visit every card in ai_hand. A foreach, the loop from 10.2a.
  2. Selection — ask a question about each one and keep an answer: "is this one better than the best so far?"
  3. Sequencing — do exactly ONE thing with the winner, in a fixed order, and then stop.

Read your file before you write a line of it. Open Assets/Scripts/GameManager.cs: AI_Turn() is an empty body and yours today, ai_hand and ai_discard_pile are real List<Card> fields, and no turn state exists anywhere — no energy system, and Update() is empty too. Whose turn it is, and what a turn costs, are yours to decide.

If your AI's move needs a random number, the call is UnityEngine.Random.Range(...). GameManager.cs opens with using Unity.Mathematics; AND using UnityEngine;, and this template has com.unity.mathematics installed — transitively, so you will not find it in Packages/manifest.json, but Packages/packages-lock.json pins it. That package defines a class called Random, and so does UnityEngine. A bare Random.Range(...) inside THIS file is CS0104: 'Random' is an ambiguous reference and the script does not build — while the identical line compiles happily in every other script you have written this year. Two legal fixes: qualify the call, or delete the using Unity.Mathematics; line.

Socratic

"AI" in your game is a set of IF statements — no learning, no mystery. Why is that honest design for a card game (what does the player actually want from a jam opponent — cleverness, or READABLE rules)? Where would REAL machine learning belong instead (9.6 — and whose data would it learn from)?

Read the Docs: The API for a Turn ~15 min

This is the level-3 substitute for a walkthrough: the API is laid out below and the method is not. Read the What it costs column before you choose — that is where the bugs in this method live.

APISignatureWhat you getWhat it costs
ai_hand public List<Card> ai_hand — a field on GameManager, filled in the Inspector and by Draw The opponent's hand: a real ordered list of the Card components in front of the AI. It ships EMPTY. The starter's Deal() creates one visual per side and drops the reference, so until 10.2c's Draw runs there is nothing in it at all.
foreach foreach (Card c in ai_hand) Every card, in order, with no index to manage. It never tells you WHERE the card was. If your rule needs the position, this is the wrong loop, and the fix is the index loop below.
List<T>.Count int Count { get; } — the AP's LENGTH(aList) How many items are in the list, in one read, with no traversal. A count is not an index: the last valid slot is Count - 1. Ask for ai_hand[ai_hand.Count] and you get an exception, not the last card.
Card.cost, Card.damage, Card.health Three public int fields on Assets/Scripts/Card.cs The three numbers an AI decision can be made from. They are COPIES. Change the asset while the game runs and the card already on screen keeps the number it copied — the asset is the source of truth, and the component is a snapshot of it.
running best int best = int.MinValue; then if (score > best) best = score; Pattern 3 of the Lists & Arrays reference: score every option, keep the winner. It keeps the best SCORE, not the card that earned it. If you need to remove the winner afterwards, track which card it was as well — or you will find yourself with the winning number and no card to play.
List<T>.RemoveAt void RemoveAt(int index) — ai_hand.RemoveAt(i), the AP's REMOVE(aList, i) Removes the card at that slot and shifts everything after it down one position. Remove while traversing FORWARD and your loop skips the next card entirely. That is "Bug 1 — remove while traversing" in the Lists & Arrays reference, and this method is where it bites.
List<T>.Add void Add(T item) — ai_discard_pile.Add(card), the AP's APPEND(aList, item) Appends to the end of the list. Nothing brings a card back out of a discard pile. A reshuffle would be a feature you write, and until you do, a card in there is gone for the rest of the game.
UnityEngine.Random.Range UnityEngine.Random.Range(int min, int max) A random int. For the int overload the top is EXCLUDED, so UnityEngine.Random.Range(0, ai_hand.Count) picks a real index from 0 to Count - 1. It has to be qualified in THIS file (see the callout above), and randomness buys an opponent you cannot reproduce — a real cost the moment you are trying to test a rule.
the turn gate A bool field you declare, read by the player's input path and flipped at the end of a turn One place that answers "whose turn is it?", so "one action per turn" becomes a property of that field rather than a check scattered through the click handler. It is state, so it survives every frame and it has to be set at the right MOMENT. Flip it early and the player takes a second action while the AI is still resolving the first one.
Build Lab: The AI Turn ~35 min

This one is on you. The three specifications below are the whole brief — the decision, the move and the turn gate. The rest is your file.

Architectural specification: the decision.
  • Which field does the method read, and which field must already be a real list before it can read it? Which earlier lesson is what made it one?
  • Write your rule aloud in one sentence before you write it in C#: "the AI plays the card with the ______". If you cannot say it in one sentence, it is two rules and one of them is hiding a bug.
  • What does the rule compare — damage, cost, health, or a combination? What does your choice say about the opponent a player is about to face, and can you defend it as a design decision rather than an accident?
  • What value does your "best so far" variable start at, and why can it not start at 0? What happens to the first card your loop visits if it does?
  • Two cards tie for best. Which one does your loop keep, is that the one you WANT, and is it the same one every time you press Play? Why does a repeatable choice matter more than a clever one here?
  • AI_Turn() is declared with no parameters at all. A procedure that takes none cannot be pointed at a difficulty level, a card pool, or a second kind of opponent later. If you added one parameter, what would it be — and what does adding it turn this method into?
Architectural specification: the move.
  • Your loop removed an item from the list it was traversing. Did your loop survive that — and if it was a forward loop over an index, which card did it skip? What is the fix, and is your case even affected?
  • Does the card disappear from the screen? The AI's card was instantiated by Deal() and parented to the canvas, so a card removed from ai_hand is still a GameObject in the scene. What is your game supposed to show — and what would you change your field to hold if the screen has to know?
  • Nothing reads damage, cost or health yet — they go from the asset into a text label. Your rule is the first thing in the project that reads one of them as a NUMBER. Which one, and what does that make your AI's rule responsible for that the text labels were not?
Architectural specification: the turn gate.
  • Where does "whose turn is it" live — a field, and where is it declared so it survives from one frame to the next? Which lesson is that same declaration habit from?
  • Who flips it, and at exactly which moment? Write the sequence of one full turn in order, from the player pressing a card to the AI having played, and mark the flip points. Which flip is the one that is easy to put one line too early?
  • The player's click path has to read that field before it does anything. If your field is a bool, what do the two values MEAN — and is there a third state your game will grow into (game over, resolving, the AI thinking) that a bool could not express?
  • The AI playing the instant the player's turn ends feels like a bug even when it is correct. Which earlier lesson's timestamp pattern gives the turn a beat, and which number do you tune — the delay, or the moment the flip happens?

Then prove it, in the Console, before you trust it. One run with a Debug.Log(...) at the top of AI_Turn() printing ai_hand.Count and the name of the card your rule picked answers the guard, the decision and the move in one pass. Take the AI's turn twice in a row without the player moving and watch what the second one does. Then delete the log — a print that survives into the next lesson is noise you will read past.

Close of Day ~5 min

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

  • Implement a greedy AI: traverse, compare, keep the best, act
  • Move a played card out of the hand into ai_discard_pile, in the order that does not lose it
  • Say why UnityEngine.Random has to be spelled out in GameManager.cs
  • Say what one parameter would let AI_Turn do that it cannot do with none

Reference Docs

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

Next: 10.2e — Card Jam: Build + Playtest

0/5 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.