// PanelCollection.java // // unintelligent graphics for the Argument Game // ---------------------------------------------------------------------- // 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.*; import java.util.*; public class PanelCollection { static ArgumentGame game; CasesLayout cases; // drawing the cases (card stacks) ArgumentLayout argument; // drawing the tree of the argument static Font[] fonts; // standard fonts HelpLayout help; PanelCollection (ArgumentGame g) { game = g; cases = new CasesLayout (game); argument = new ArgumentLayout (game); help = new HelpLayout (game); fonts = new Font[3]; fonts[0] = new Font ("Courier", Font.PLAIN, 12); fonts[1] = new Font ("Courier", Font.PLAIN, 10); fonts[2] = new Font ("Courier", Font.PLAIN, 9); } // --------------- CONVENIENCE METHODS for GRAPHICS ------------------- public static void bgRect (Rectangle r, Graphics g, java.awt.Color c) { java.awt.Color oldColor = g.getColor(); g.setColor (Color.black); g.drawRect (r.x, r.y, r.width, r.height); g.setColor (c); g.fillRect (r.x-1, r.y-1, r.width-2, r.height-2); g.setColor (oldColor); } public static MessagePanel makeMessagePanel (String[] msg) { return new MessagePanel (msg); } // help w/ the GridBagLayout layout manager public static void constrain (Container container, Component component, int grid_width, int grid_height, double weight_x, double weight_y, int fill, int top, int left, int bottom, int right) { GridBagConstraints c = new GridBagConstraints(); c.gridwidth = grid_width; c.gridheight = grid_height; c.insets = new Insets (top, left, bottom, right); c.weightx = weight_x; c.weighty = weight_y; c.fill = fill; ((GridBagLayout)container.getLayout()).setConstraints (component, c); container.add (component); } } // ========================= CASES LAYOUT ================================= // draws stacks of cards on a green background // class CasesLayout extends Panel { ArgumentGame game; CardDeck deck; Case[] cases; CasesLayout (ArgumentGame g) { game = g; deck = g.deck; cases = g.cases; } public void paint (Graphics g) { int row, col, x, y, z; char[] alph = new char[1]; game.panels.bgRect (bounds(), g, Color.green); int w = size().width; int h = size().height; g.setFont(game.panels.fonts[1]); for (col=0; col<4; col++) { for (row=0; row<5; row++) { // calculate placement of case x = col_pixel (col); y = row_pixel (row); z = col*5+row; // display decision of case g.drawImage(cases[z].decisionPic(), x+1, y+1, this); // print alphabetical index of case, starting from D g.setColor (Color.black); alph[0] = (char)(z + 'd'); g.drawString(new String (alph), x+5, y+g.getFontMetrics().getHeight()); // draw rectangle around it g.setColor (Color.black); g.drawRect (x, y, deck.PWIDTH+1, deck.HEIGHT+1); x += deck.PWIDTH + 1; // display optional for (int i=0; i < cases[z].optionalLength(); i++) { g.drawImage(cases[z].optionalPic(i), x+1, y+1, this); g.drawRect (x, y, deck.CWIDTH+1, deck.HEIGHT+1); x += deck.CWIDTH+1; } // display excluding if (cases[z].excluding != null) { int elen = cases[z].excludingLength(); g.setColor (Color.darkGray); g.drawRect (x, y-2, deck.CWIDTH*elen+7, deck.HEIGHT+5); g.drawRect (x+1, y-1, deck.CWIDTH*elen+5, deck.HEIGHT+3); g.drawRect (x+2, y, deck.CWIDTH*elen+3, deck.HEIGHT+1); x += 3; g.setColor (Color.black); for (int i=0; i < elen; i++) { g.drawImage(cases[z].excludingPic(i), x+1, y+1, this); g.drawRect (x, y, deck.CWIDTH+1, deck.HEIGHT+1); x += deck.CWIDTH+1; } // draw ugly red lines through the case and call it not live. if (!cases[z].checkExcludingLiveness()) { int a = col_pixel (col); int b = row_pixel (row); int c = cases[z].excludingLength() + cases[z].optionalLength(); for (int i = 0; i < 2; i++) { g.setColor (Color.red); g.drawLine (a, deck.HEIGHT+b, c*deck.CWIDTH+deck.PWIDTH+a, b); a++; b++; } } } // if the case is naturally not-live, draw a black line through it. if (!cases[z].checkOptionalLiveness()) { cases[z].live = false; g.setColor(Color.black); int a = col_pixel (col); int b = row_pixel (row); int c = cases[z].optionalLength(); if (cases[z].excluding != null) c += cases[z].excludingLength(); for (int i = 0; i < 4; i++) { g.drawLine (a, b, c*deck.CWIDTH+deck.PWIDTH+a, deck.HEIGHT+b); a++; b++; } } } } } // calculates the pixel x-coordinate of each column private int col_pixel (int colNumber) { int col_spacing = deck.PWIDTH + (4 * deck.CWIDTH) + 15; int extra_col_space = (4 * deck.CWIDTH) + 4; int returnval = colNumber * col_spacing + 10; if (colNumber > 2) returnval += (colNumber - 2) * extra_col_space; return (returnval); } // calculates the pixel y-coordinate of a row private int row_pixel (int rowNumber) { int row_spacing = deck.HEIGHT + 12; return (rowNumber * row_spacing + 10); } // deals with a mouse click public boolean mouseDown (Event event, int x, int y) { int row; // row of the card int col; // col of the card Case stack; // if the click is not on a card-corner, ignore if (y % 72 <= 10) return false; if ((x < 190) && (x % 95 <= 10)) return false; if ((x >= 190) && ((x - 190) % 139 <= 10)) return false; if (x > 468) return false; // find out what card it is and call the card_click function to deal w/ it row = (int) y / 72; if (x < 190) { col = (int) x / 95; } else { col = 2 + (int) ((x - 190) / 139); } stack = cases[col*5+row]; // if the contract is being made, set the establish card to this: if (game.contractDone.getValue() == false) { game.contract.card.setVal (stack.decision); game.contract.card.repaint(); // if it's a clickable case, and we have enough money, // pick a new card from that case and decrease resources } else { // find out whether this was an excluding case if ((stack.excluding != null) && ((x - col_pixel (col) - deck.PWIDTH - 5 - (stack.optionalLength() * deck.CWIDTH + 1)) > 0)) { if (stack.numExcluding < stack.excluding.length) { game.flow.handleCard (stack, "Excluding"); repaint (col_pixel (col), row_pixel (row), col_pixel (col+1) - col_pixel (col), row_pixel (row+1) - row_pixel (row)); } } else { if (stack.numOptional < stack.optional.length) { game.flow.handleCard (stack, "Optional"); if (stack.live) { x = col_pixel (col) + deck.PWIDTH + (stack.numOptional - 1) * (deck.CWIDTH + 1); y = row_pixel (row); repaint(x, y, deck.CWIDTH + 5, deck.HEIGHT + 2); } else { repaint (col_pixel (col), row_pixel (row), col_pixel (col+1) - col_pixel (col), row_pixel (row+1) - row_pixel (row)); } } } } return (true); } public Dimension minimumSize () { return new Dimension (488, 370); } public Dimension preferredSize () { return minimumSize(); } } // ======================= ARGUMENT LAYOUT ================================== // holds Argument Panel // class ArgumentLayout extends Panel { ArgumentGame game; ArgumentPanel tree; Scrollbar vscroll; BorderLayout theLayout; ArgumentLayout (ArgumentGame g) { game = g; setLayout (theLayout = new BorderLayout()); tree = new ArgumentPanel (game, this); add ("Center", tree); vscroll = new Scrollbar (Scrollbar.VERTICAL, 0, 0, 0, 100); add ("East", vscroll); } public void paint (Graphics g) { game.panels.bgRect (bounds(), g, Color.gray); tree.repaint(); } public boolean handleEvent (Event evt) { if ((evt.target == vscroll) && (tree != null) && (evt.arg instanceof Integer)) { tree.offset_y = ((int)(((Integer)evt.arg).intValue() * 5 / 1)); tree.repaint(); return true; } return super.handleEvent (evt); } } // ARGUMENT PANEL -- calculates and draws all possible arguments class ArgumentPanel extends Panel { ArgumentGame game; ArgumentLayout parent; CardDeck deck; Case[] cases; int FontHeight; int FontWidth; int AWidth; int depth; int xinset; int yinset; int offset_y = 0; boolean small; // toggles small-font mode ArgStack theStack; ArgumentPanel (ArgumentGame g, ArgumentLayout p) { parent = p; game = g; cases = g.cases; deck = g.deck; small = false; theStack = new ArgStack(); FontHeight = 0; FontWidth = 0; } public void paint (Graphics g) { String caption; Rectangle r = bounds(); game.panels.bgRect(r, g, Color.white); g.translate (0, -offset_y); // set the font and fontmetrics if (!small) g.setFont (game.panels.fonts[0]); else g.setFont (game.panels.fonts[1]); FontHeight = g.getFontMetrics().getHeight(); FontWidth = g.getFontMetrics().stringWidth("10"); AWidth = g.getFontMetrics().stringWidth(">"); xinset = 20; yinset = 20 + 4 * FontHeight; // make sure to display nothing until contract done if (game.contractDone.getValue() == false) return; // draw the path of the argument recursively depth = 0; Support.unmarkAll(); g.setColor (Color.black); caption = "Key: '!' = evidence; '?' = unsupported decision;"; g.drawString (caption, xinset, yinset - 4*FontHeight); caption = " '...' = this argument already displayed."; g.drawString (caption, xinset, yinset - 3*FontHeight); // draw all pros drawArray (Case.thePros.pros, g); if (Case.thePros.pros == null) { g.setColor (Color.black); g.drawString ("No argument has been constructed.", 50, 50); } else { g.setColor (Color.red); caption = "Arguments in favor:"; g.drawString (caption, xinset, yinset - FontHeight); g.drawLine (xinset, yinset - FontHeight + 2, xinset + g.getFontMetrics().stringWidth(caption), yinset - FontHeight + 2); } if ((depth > 54) && (!small)) { small = true; repaint(); } // find out a way to RESIZE this screen... // print the cons xinset += r.width / 2; depth = 0; theStack.clear(); drawArray (Case.theCons.pros, g); theStack.clear(); Support[] s = Support.getUnmarkedCons(); if (s != null) { for (int i = 0; i < s.length; i++) { if ((s[i] != null) && (s[i].pros != null)) { for (int j = 0; j < s[i].pros.length; j++) { if ((s[i].pros[j] != null) && (s[i].pros[j].live)) { drawArgument (s[i].pros[j], g); } } } } } g.setColor (Color.red); caption = "Arguments opposing:"; g.drawString (caption, xinset, yinset - FontHeight); g.drawLine (xinset, yinset - FontHeight + 2, xinset + g.getFontMetrics().stringWidth(caption), yinset - FontHeight + 2); if ((depth > 54) && (!small)) { small = true; repaint(); } } // DRAW ARRAY -- draws an array of cases void drawArray (Case[] arr, Graphics g) { if (arr != null) { for (int i = 0; i < arr.length; i++) { drawArgument (arr[i], g); } } } // DRAW SYMBOL--when a symbol is a dead end (evidence, unexplored, repeated) void drawSymbol (Support card, Graphics g) { depth++; int x = theStack.depth() * FontWidth + xinset; int y = depth * FontHeight + yinset; g.setColor (deck.color (card.decision)); if (card.evidence) { g.drawString (deck.toString (card.decision) + "!", x, y); } else if (card.marked) { g.drawString (deck.toString (card.decision) + "...", x, y); } else { // support is unexplored g.drawString (deck.toString (card.decision) + "?", x, y); } ArgLevel al = theStack.peek(); g.setColor (Color.darkGray); if (al != null) { y = y - FontHeight / 2; g.drawLine (al.x, al.y + FontHeight/2, al.x, y); g.drawLine (al.x, y, x - 3, y); } } // DRAW ARGUMENT // recursively draws the argument tree void drawArgument (Case statement, Graphics g) { depth++; g.setColor (Color.black); if (theStack.depth() >= theStack.maxDepth()) { g.drawString ("ERROR: too deep a level", 50, 50); return; } // set the color of the words g.setColor (deck.color (statement.decision)); // calculate the position int x = theStack.depth() * FontWidth + xinset; int y = depth * FontHeight + yinset; // draw the symbol String sym = deck.toString (statement.decision); if (deck.getNumber (statement.decision) == deck.getNumber (game.contract.establish)) { g.drawString (">"+ sym, x - AWidth, y); } else { g.drawString (sym, x, y); } // if statement not live (only happens at top level), say so if (!statement.live) { g.setColor (Color.darkGray); g.drawString ("--not live", x + g.getFontMetrics().stringWidth(sym), y); return; } // draw the angle g.setColor (Color.darkGray); ArgLevel al = theStack.peek(); if (al != null) { g.drawLine (al.x, al.y + FontHeight/2, al.x, y - FontHeight / 2); g.drawLine (al.x, y - FontHeight/2, x - 3, y - FontHeight/2); } // draw the rest of the supporting cards: Support S; Case C; boolean NS = true; // "NS" -- not supported theStack.push (x, y - FontHeight/2, statement); for (int i = 0; i < statement.optional.length; i++) { S = statement.optional[i]; if ((S != null) && (S.live) && (!theStack.hasCase(S))) { if (NS) NS = false; if ((S.evidence) || (S.marked) || (S.unexplored())) { drawSymbol (S, g); } else { S.mark(); for (int j = 0; j < S.pros.length; j++) { C = S.pros[j]; if (C.live) drawArgument (C, g); } } } } if (NS) { x += g.getFontMetrics().stringWidth(deck.toString(statement.decision)); g.setColor (Color.black); g.drawString ("?", x, y); } theStack.pop (); } } // ======================= ARG STACK and ARG LEVEL ========================= // arg stack is a stack that holds arg levels-- // arg levels are basically a wrapper object to hold (x,y) and a Case // used in tree-drawing graphics routines-- ArgumentPanel and ArgumentChoice // class ArgStack { ArgLevel[] theStack; int stackPtr; ArgStack () { theStack = new ArgLevel[20]; stackPtr = 0; for (int i = 0; i < theStack.length; i++) theStack[i] = null; } void clear () { stackPtr = 0; for (int i = 0; i < theStack.length; i++) theStack[i] = null; } void push (int x, int y, Case z) { theStack[stackPtr++] = new ArgLevel (x, y, z); } void push (ArgLevel a) { theStack[stackPtr++] = a; } ArgLevel peek () { if (stackPtr > 0) return (theStack[stackPtr - 1]); else return null; } ArgLevel pop () { if (stackPtr > 0) return (theStack[--stackPtr]); else return null; } // icky hack. returns if red or black, but same number. boolean hasCase (Support s) { for (int i = 0; i < stackPtr; i++) { if ((theStack[i].c != null) && (CardDeck.getNumber (theStack[i].c.decision) == CardDeck.getNumber (s.decision))) return true; } return false; } int depth () { return stackPtr; } int maxDepth () { return theStack.length; } } class ArgLevel { int x; int y; Case c; ArgLevel (int i, int j, Case k) { x = i; y = j; c = k; } } // ====================== HELP LAYOUT =================================== // in theory, will give generic help, depending on what stage it is class HelpLayout extends Panel { ArgumentGame game; HelpLayout (ArgumentGame g) { game = g; } public void paint (Graphics g) { // if the contract is finished, and playing begun if (game.contractDone.getValue()) { if ((game.flow != null) && ((game.flow.brain.currMove == game.flow.brain.V_ARGUE) || (game.flow.brain.currMove == game.flow.brain.E_ARGUE))) { g.setColor(Color.black); g.setFont(game.panels.fonts[0]); int height = g.getFontMetrics().getHeight(); String[] helpMsg = new String[13]; helpMsg[0] = "Typing in argument:"; helpMsg[1] = " Type path from argument root to argument leaf"; helpMsg[2] = " for each leaf in argument tree, and for each tree."; helpMsg[3] = "For example:"; helpMsg[4] = " A B C D"; helpMsg[5] = " A B C E"; helpMsg[6] = " A F G"; helpMsg[7] = " H I"; helpMsg[8] = "These letters are the indices of cases or evidence."; helpMsg[9] = "Upper/lower-case and spaces are ignored."; helpMsg[10]= "Each root must support your argument or be a "+ "counter-argument to your opponent."; helpMsg[11]= "Arguments are judged by specificity--"; helpMsg[12]= " the more specific argument wins."; for (int i = 0; i < helpMsg.length; i++) { g.drawString (helpMsg[i], 10, height * (i+1)); } } } } } // ==================== MESSAGE PANEL =================================== // blank panel to display message // sized to fit top portion of screen // class MessagePanel extends Panel { String[] msg; Font theFont; MessagePanel (String[] s) { msg = s; theFont = new Font ("Courier", Font.PLAIN, 14); } public void paint (Graphics g) { int x, y; int w = size().width; int h = size().height; g.setColor (Color.black); g.drawRect (0, 0, w-1, h-1); g.setColor (Color.white); g.fillRect (1, 1, w-2, h-2); g.setColor (Color.black); g.setFont (theFont); y = g.getFontMetrics().getHeight(); for (int i = 0; i < msg.length; i++) { x = g.getFontMetrics().stringWidth (msg[i]); g.drawString (msg[i], (w-x)/2, (h-(y*msg.length))/2 + i*y); } } public Dimension minimumSize() { return new Dimension (488, 170); } public Dimension preferredSize() { return minimumSize(); } } // OPTIONS LAYOUT -- tiny panel -------------------------------------------- class OptionsLayout extends Panel { ArgumentGame game; Contract contract; OptionsLayout (ArgumentGame g) { game = g; } public void getContract (Contract c) { contract = c; } public void paint (Graphics g) { g.setFont (game.panels.fonts[0]); g.setColor (Color.black); int x = g.getFontMetrics().stringWidth(" "); int y = g.getFontMetrics().getHeight(); g.drawRect (0, 0, 99, y*2+6); if (!contract.OrderedEvidence) { g.drawLine (0, 0, 99, y*2+6); g.drawLine (0, y*2+6, 99, 0); } g.drawString ("Ordered", x, y+2); g.drawString (" Evidence", x, y*2+2); g.drawRect (0, y*2+8, 99, y*2+6); if (!contract.DefeasibleSpecificity) { g.drawLine (0, y*2+8, 99, y*4+14); g.drawLine (0, y*4+14, 99, y*2+8); } g.drawString ("Defeasible", x, y*3+10); g.drawString (" Specificity", x, y*4+10); } public Dimension minimumSize() { return new Dimension (100, 80); } public Dimension preferredSize() { return minimumSize(); } } // RESOURCES LAYOUT -- tiny panel ----------------------------------------- class ResourcesLayout extends Panel { ArgumentGame game; Contract contract; ResourcesLayout (ArgumentGame g) { game = g; } public void getContract (Contract c) { contract = c; } public void paint (Graphics g) { g.setFont (game.panels.fonts[0]); g.setColor (Color.black); int x = g.getFontMetrics().stringWidth(" "); int y = g.getFontMetrics().getHeight(); g.drawRect (0, 0, 79, y*2+6); g.drawRect (0, y*2+8, 79, y*2+6); g.drawString ("Resources: ", x, y+2); g.drawLine (x, y+4, x+g.getFontMetrics().stringWidth("Resources"), y+4); g.drawString (String.valueOf (contract.resources), x, y*2+2); g.drawString ("Burden: ", x, y*3+10); g.drawLine(x, y*3+12, x+g.getFontMetrics().stringWidth("Burden"), y*3+12); g.drawString (game.contract.Vladimir(), x, y*4+10); } public Dimension minimumSize() { return new Dimension (82, 82); } public Dimension preferredSize() { return minimumSize(); } } // FACTS LAYOUT -- tiny panel ---------------------------------------------- class EvidenceLayout extends Panel { ArgumentGame game; Contract contract; CardDeck deck; PanelCollection panels; EvidenceLayout (ArgumentGame g) { game = g; panels = g.panels; deck = g.deck; } public void getContract (Contract c) { contract = c; } public void paint (Graphics g) { g.setFont (panels.fonts[0]); g.setColor (Color.black); int fontWidth = g.getFontMetrics().stringWidth("Evidence:"); int fontHeight = g.getFontMetrics().getHeight(); int x = 5; int y = fontHeight; g.drawString ("Evidence: ", x, y-2); // draw the evidence themselves: char[] ch = new char[1]; g.setFont (new Font ("Helvetica", Font.PLAIN, 10)); for (int i = 0; i < contract.evidence.length; i++) { g.drawRect (x, y, deck.PWIDTH+1, deck.HEIGHT+1); // draw the card g.drawImage (deck.getPicture(contract.evidence[i]), x+1, y+1, this); // draw the symbol ch[0] = (char)(i + 'a'); g.drawString (new String (ch), x+5, y+fontHeight); // move down the screen x += deck.PWIDTH + 5; } } public Dimension minimumSize() { int w = deck.PWIDTH * 3 + 20; int h = deck.HEIGHT + 18; return new Dimension (w,h); } public Dimension preferredSize() { return minimumSize(); } } // CLAIM LAYOUT-- tiny panel --------------------------------------------- class ClaimLayout extends Panel { ArgumentGame game; CardDeck deck; PanelCollection panels; int card; int width; int height; ClaimLayout (ArgumentGame g) { game = g; panels = g.panels; deck = g.deck; card = -1; width = deck.PWIDTH+3; height = deck.HEIGHT+18; } public void getContract (Contract c) { card = c.establish; } public void setVal (int c) { card = c; } public int getVal () { return card; } public void paint (Graphics g) { g.setFont (panels.fonts[0]); g.setColor (Color.black); int fontWidth = g.getFontMetrics().stringWidth("Claim:"); int fontHeight = g.getFontMetrics().getHeight(); int x = (width - fontWidth)/2; int y = fontHeight; if (x < 0) { g.setFont (panels.fonts[2]); x = 0; } g.drawString ("Claim:", x, y-2); g.drawRect (x, y, deck.PWIDTH+1, deck.HEIGHT+1); g.drawImage(deck.getPicture(card), x+1, y+1, this); } public Dimension minimumSize() { return new Dimension (width, height); } public Dimension preferredSize() { return minimumSize(); } }