// Case.java // // storage of argument cases (stacks of cards on table) // ---------------------------------------------------------------------- // part of ArgumentGame (simulation of a rational argument) // game written for/designed by Prof. Ron Loui, loui@ai.cs.wustl.edu // based on the paper "An Argument Game" by Ronald Loui and Wiliam Chen // based on the game "LMNOP" // (see Loui-Norman-Stiefvater-Merrill-Olson-Costello [92] // code by Nina Kang, nkang@husc.harvard.edu, 8/96 import java.awt.*; // CASE -- a pile of cards on the table // // ArgumentGame section 2.3 // * Cases are stacked neatly in the case area. // * The set of visible cards among the cases are the _decisions_. // * A case is said to be for its decision; // When cards face down are later displayed, these are said to be // displayed _facts_of_the_case_; // A case with any displayed facts is _opened_; // A case with all cards displayed is _exhausted_. // public class Case { // booleans -- explain the status of the case boolean live; // updated in draw_new_card() boolean opened; // has this case been explored at all boolean exhausted; // has this case been mined dry int decision; // value of the top-facing card of the case int index; // position in Case[] cases or Case[] evidence Support[] optional; // optional facts-of-the-case int numOptional; Support[] excluding; // excluding facts-of-the-case int numExcluding; static Case[][] black; // case lookup-vectors static Case[][] red; // sorted by color static Support thePros; // pros for top-level claim static Support theCons; // cons for top-level claim static ArgumentGame game; static CardDeck deck; static Case[] cases; // card piles static Case[] evidence; // case analogs of evidence, for convenience' sake // --------------- Global-scope functions --------------------------- // CONSTRUCTOR // never use directly-- called by init() Case (int ix, int cl, int opt, int excl) { index = ix; decision = cl; optional = new Support[opt]; numOptional = 0; if (excl > 0) excluding = new Support[excl]; numExcluding = 0; live = true; opened = false; exhausted = false; } // ADD TO LOOKUP // add this case to the lookup vectors void add_to_lookup() { int i = deck.getNumber (decision); if (deck.getColor(decision) == deck.RED) { if (red[i] == null) { red[i] = new Case[1]; } else { Case[] temp = red[i]; red[i] = new Case[temp.length + 1]; System.arraycopy (temp, 0, red[i], 0, temp.length); } red[i][red[i].length-1] = this; } else { if (black[i] == null) { black[i] = new Case[1]; } else { Case[] temp = black[i]; black[i] = new Case[temp.length + 1]; System.arraycopy (temp, 0, black[i], 1, temp.length); } black[i][0] = this; } } // INIT // initialize the class, static variables, etc. public static Case[] init (boolean hasExcluding, ArgumentGame g) { game = g; deck = g.deck; black = new Case[13][]; red = new Case[13][]; cases = new Case[20]; int cl, opt, excl; deck.reset_avoidances(); for (int i = 0; i < game.contract.evidence.length; i++) { deck.set_avoidances (game.contract.evidence[i], true); } for (int i = 0; i < cases.length; i++) { cl = deck.deal(); opt = (int) (Math.random() * 4); excl = 0; if (i < 10) opt += 2; else opt += 6; // if the case is short, there is about a 1/3 chance of having an // excluding card if ((hasExcluding) && ((opt == 2) || (opt == 6)) && (Math.random() > 0.3)) { excl = 1; } // if the case is long, add a couple excluding cards if ((hasExcluding) && ((opt == 5) || (opt == 9))) { excl = (int) (Math.random() * 2); opt -= excl + 1; } // if it's the last case, put in a hack to ensure top-level // disagreement if (i == cases.length-1) { int x = hack(); if (x >= 0) cl = x; } cases[i] = new Case (i, cl, opt, excl); cases[i].add_to_lookup(); } evidence = new Case[game.contract.evidence.length]; for (int i = 0; i < evidence.length; i++) { evidence[i] = new Case (i, game.contract.evidence[i], 0, 0); } Support.init(game); return (cases); } // HACK *sigh* // make sure that there is top-level disagreement private static int hack () { // if there's any top-level disagreement at all, return for (int i = 0; i < red.length; i++) { if ((red[i] != null) && (black[i] != null)) return (-1); } // otherwise, change the decision to make sure there is return (CardDeck.getOpposite (cases[cases.length-2].decision)); } // FORMULATE SUPPORT // initialize the pros/cons for top-level claim public static void formulateSupport () { thePros = Support.NewSupport (game.contract.establish); theCons = Support.NewSupport ((26+game.contract.establish)%52); } // GET CARD BID // suggest a bid for the Contract class to give to user public static int getCardBid() { int r; // number of red supporting cards int b; // number of black supporting cards int total = 0; int index = 0; for (int i = 0; i < 13; i++) { r = 0; b = 0; if (red[i] != null) for (int j = 0; j < red[i].length; j++) { r += red[i][j].optional.length; if (red[i][j].excluding != null) r -= 2 * red[i][j].excluding.length; } if (black[i] != null) for (int j = 0; j < black[i].length; j++) { b += black[i][j].optional.length; if (black[i][j].excluding != null) b -= 2 * black[i][j].excluding.length; } if ((r > 0) && (b > 0) && ((r + b) > total)) { total = r + b; if (r > b) index = -i -1; else index = i + 1; } } if (index > 0) return (black[index-1][0].decision); else if (index < 0) return (red[-index-1][0].decision); else return (-1); } // ------------------------ Personal functions ------------------------ public Image decisionPic () { return (game.deck.getPicture (decision)); } public int getOptionalSupport (int j) { if (optional[j] != null) { return (optional[j].decision); } else { return (-1); } } public int getExcludingSupport (int j) { if (excluding[j] != null) { return (excluding[j].decision); } else { return (-1); } } public Image optionalPic (int j) { if ((optional[j] != null) && (optional[j].live)) return (game.deck.getCorner (getOptionalSupport(j))); else return (game.deck.getDeadCorner (getOptionalSupport(j))); } public Image excludingPic (int j) { if ((excluding[j] != null) && (excluding[j].live)) return (game.deck.getCorner (getExcludingSupport(j))); else return (game.deck.getDeadCorner (getExcludingSupport(j))); } public int excludingLength () { return (excluding.length); } public int optionalLength() { if (optional != null) return (optional.length); return (0); } public boolean isEvidenceOfCase (int card) { for (int i = 0; i < numOptional; i++) { if (CardDeck.equivalent (optional[i].decision, card)) return true; } return false; } // --------------------- CARD-DEALING FUNCTIONS ----------------------- // DRAW NEW CARD // // handles the drawing of new cards, plus a lot of status-checking // (liveness, etc) public int drawNewCard (String str) { Support[] s; int numSupports; // is the new card to be drawn from the optional or excluding pile? if ("Optional".equals(str)) { s = optional; numSupports = numOptional; } else if ("Excluding".equals(str)) { s = excluding; numSupports = numExcluding; } else return (-1); // make sure pile is valid and not exhausted if ((game == null) || (s == null) || (numSupports == -1) || (numSupports >= s.length)) return (-1); else { // set opened opened = true; // deal new card int card; game.deck.reset_avoidances(); game.deck.set_avoidances(game.contract.establish, true); game.deck.set_avoidances(decision, true); for (int j = 0; j < numOptional; j++) { game.deck.set_avoidances(optional[j].decision, true); } if (excluding != null) for (int j = 0; j < numExcluding; j++) { game.deck.set_avoidances(excluding[j].decision, true); } card = game.deck.deal(); s[numSupports] = Support.NewSupport (card); if ("Optional".equals(str)) { numOptional++; } else { numExcluding++; } changeInLiveness(); // check for a change in liveness return (s[numSupports].decision); } } // CHANGE IN LIVENESS // returns true with a change in liveness, updates all supports, etc boolean changeInLiveness () { Support sup; if (live != (checkOptionalLiveness() && checkExcludingLiveness())) { live = !live; // check for a change in the liveness of its support vector sup = Support.NewSupport(decision); if (sup.live != sup.checkLiveness()) { // if there *was* a change, check the liveness of all cases for (int i= 0; i < cases.length; i++) { if (cases[i].live != (cases[i].checkOptionalLiveness() && cases[i].checkExcludingLiveness())) { cases[i].live = !cases[i].live; Support.NewSupport (cases[i].decision).checkLiveness(); } } } // make sure that the decision's support vector is still live Support.checkEstablishLiveness(); return true; } else { return false; } } // CHECK OPTIONAL LIVENESS // checks if the optional part of the deck is live boolean checkOptionalLiveness() { // if cards remain, then the optional part is true if (numOptional < optional.length) return (true); // if an optional support is live, then it's true for (int i = 0; i < numOptional; i++) { if (optional[i].live) return (true); } return (false); } // CHECK EXCLUDING LIVENESS // if any of the excluding supports are live, the whole thing is not live. boolean checkExcludingLiveness() { if (excluding == null) return (true); for (int i = 0; i < numExcluding; i++) { if (excluding[i].live) return (false); } return (true); } // LAST CARD LIVE // checks if the last card clicked on was live // called by Brain.handleCard() boolean lastCardLive (String partOfStack) { if ("Excluding".equals(partOfStack)) { if (numExcluding > 0) { return (excluding[numExcluding-1].live); } else return false; // no excluding cards } else if ("Optional".equals(partOfStack)) { if (numOptional > 0) { return (optional[numOptional-1].live); } else return false; // no optional cards } else throw new IllegalArgumentException (); } // ALL CASES FULL // checks if there are any cards left to uncover-- if not, end game // called by Brain.handleDone(), I think static boolean allCasesExhausted () { for (int i = 0; i < red.length; i++) if (red[i] != null) for (int j = 0; j < red[i].length; j++) if (!red[i][j].exhausted) return false; for (int i = 0; i < black.length; i++) if (black[i] != null) for (int j = 0; j < black[i].length; j++) if (!black[i][j].exhausted) return false; // all cases ARE exhausted-- update variable, etc. game.cardsGone.setValue (true); return true; } } // ------------- CLASS: SUPPORT --------------------------------------- // // SUPPORT-- every card in a case that gets turned over // each "support" holds all the cases that support a certain decision class Support { // the support's individual data structures int decision; // the decision that the support supports boolean live; // are any of its cases live boolean evidence; // is it a fact (evidence) boolean marked; // the support has already been drawn by ArgumentPanel Case[] pros; // all cases that support its decision // the game and some of its components, for convenience' sake. static ArgumentGame game; static CardDeck deck; static Case[] cases; // global support information static Support[] allSupports; // there are only 26 supports // INIT // creates all supports (never use the constructor function) static void init (ArgumentGame g) { game = g; deck = g.deck; cases = g.cases; int decision; allSupports = new Support[26]; for (int i = 0; i < allSupports.length; i++) { decision = i % 13 + 26 * (i / 13); allSupports[i] = new Support (decision); } } // NEW SUPPORT // wrapper to mimic use of constructor function static Support NewSupport (int cl) { return (allSupports[getIndex(cl)]); } // CONSTRUCTOR // always hidden Support (int cl) { decision = cl; marked = false; pros = null; evidence = false; // if this decision is evidence or its opposite, we're set for (int i = 0; i < game.contract.evidence.length; i++) { if (deck.equivalent (game.contract.evidence[i], decision)) { evidence = true; live = true; } else if (deck.opposite (game.contract.evidence[i], decision)) { evidence = true; live = false; } } if (!evidence) { // otherwise, search case collection for supporting cases int index = deck.getNumber (decision); if (deck.getColor(decision) == deck.RED) pros = Case.red[index]; else pros = Case.black[index]; live = (pros != null); } } // --------------------- SHORTHAND FUNCTIONS ------------------------------- // check whether a certain decision has live supports static boolean isLive (int card) { return (allSupports[getIndex(card)].checkLiveness()); } // check whether a decision is evidence static boolean isEvidence (int card) { return (allSupports[getIndex(card)].evidence); } // get the index, in terms of support // (change from 52-card deck index to 26-value theoretical decision index) static int getIndex (int card) { int index = deck.getNumber (card); if (deck.getColor (card) == deck.RED) index += 13; return index; } // ------------------------ MARKING FUNCTIONS ---------------------------- void mark () { marked = true; } static void unmarkAll () { for (int i = 0; i < allSupports.length; i++) { if (allSupports[i] != null) allSupports[i].marked = false; } } // checks whether a case has opposition public boolean hasCon () { int i = (getIndex (decision) + 13) % 26; if ((allSupports[i] != null) && (allSupports[i].marked)) return true; return false; } // get all unmarked cases with marked opposites // called by ArgumentPanel.paint() in PanelCollection.java static Support[] getUnmarkedCons () { int x = 0; Support[] arr = new Support[0]; Support[] temp; for (int i = 0; i < allSupports.length; i++) { if ((allSupports[i] != null) && (!allSupports[i].marked) && (allSupports[i].hasCon())) { temp = arr; arr = new Support[++x]; System.arraycopy (temp, 0, arr, 0, temp.length); arr[x-1] = allSupports[i]; } } return arr; } // ---------------------- LIVENESS CHECKING --------------------------- public boolean checkLiveness () { if ((!live) && (evidence)) return false; // it is the opposite of evidence else if (evidence) return true; // if it's evidence else if (pros == null) return (live = false); // if NO cases support it // all else is defeasible: if it has a live case, it's live. for (int i = 0; i < pros.length; i++) if (pros[i].live) return (live = true); return (live = false); } // ensures that the decision is, mostly, still arguable. public static boolean checkEstablishLiveness() { if (game.contract.establish < 0) return true; Support s = allSupports[getIndex(game.contract.establish)]; if (s.live) return true; // the case may be technically dead, but excluded by something kill-able. Case[] excluders; for (int i = 0; i < s.pros.length; i++) { if (s.pros[i].checkOptionalLiveness()) { for (int j = 0; j < s.pros[i].numExcluding; j++) { if (s.pros[i].excluding[j].live) { excluders = s.pros[i].excluding[j].pros; for (int k = 0; k < excluders.length; k++) { if (excluders[k].optional == null) return true; } } } } } // if we get to this point, the case is definitively dead. game.deadCase(); return false; } // UNEXPLORED -- called in ArgumentPanel.paint() in PanelCollection.java // checks whether any of its cases have been opened public boolean unexplored () { if (pros == null) return true; for (int i = 0; i < pros.length; i++) if (pros[i].opened) return false; return true; } // CREDIBILITY -- calculates the amount of support for a decision public static double credibility (int decision) { double answer = 0; // the answer int opt; // number of optionals in a given case int excl; // number of excluders in a given case Support s = NewSupport(decision); if (s.pros != null) { for (int i = 0; i < s.pros.length; i++) { if (s.pros[i].optional != null) opt = s.pros[i].optional.length; else opt = 0; if (s.pros[i].excluding != null) excl = s.pros[i].excluding.length; else excl = 0; answer += (opt / (excl + 1)); } } return (answer); } }