PKPo+S(games/industriallogic/people/Player.javapackage games.industriallogic.people; import games.industriallogic.pieces.*; import java.util.*; import java.awt.*; public class Player { private final int id; private PlayerArea playArea; private Vector winnings = new Vector(); private Hand hand; public Player(int id) { this.id = id; playArea = new PlayerArea(id); } public Hand getHand() { return hand; } public int getId() { return id; } public PlayerArea getPlayArea() { return playArea; } public String getScreenId() { return String.valueOf(id + 1); } public Vector getWinnings() { return winnings; } public boolean has(Component card) { return playArea.contains(card); } public void setHand(Hand newHand) { hand = newHand; } }PKPo+lqxx/games/industriallogic/pieces/test/CardTest.javapackage games.industriallogic.pieces.test; import junit.framework.*; import games.industriallogic.pieces.*; public class CardTest extends TestCase { private Card card; public CardTest(String s) { super(s); } protected void setUp() { card = new Card("Problem", "System Is Buggy"); } public void testConstructor() { assertEquals("Card Type", "Problem", card.getType()); assertEquals("Card Description", "System Is Buggy", card.getDescription()); } public void testEquality() { Card card2 = new Card("Problem", "System Is Buggy"); assertEquals("Card Equality", card2, card); Card card3 = new Card("Problem", "ABCD"); Card card4 = new Card("ABCD", "System Is Buggy"); Card card5 = new Card("Pblem", "System Is Buggy"); assert("Card Inequality (card3)", !card3.equals(card)); assert("Card Inequality (card4)", !card4.equals(card)); assert("Card Inequality (card5)", !card5.equals(card)); } public void testGetClone() { Card card2 = card.getClone(); assertEquals("Card Cloning", card2, card); } public void testToString() { String s = card.toString(); assertEquals("Conversion to string", "Problem\tSystem Is Buggy", s); } public static TestSuite suite() { TestSuite suite = new TestSuite(CardTest.class); return suite; } public void testGetType() { assertEquals("Card Type", "Problem", card.getType()); } }PKPo+Zq/games/industriallogic/pieces/test/DeckTest.javapackage games.industriallogic.pieces.test; import junit.framework.*; import java.util.*; import games.industriallogic.pieces.*; public class DeckTest extends TestCase { private Deck deck; public DeckTest(String s) { super(s); } protected void setUp() { deck = new Deck(); } public void testShuffleStrategy() { deck.shuffle(1); assertEquals("Number of Cards after shuffling", new Integer(99), new Integer(deck.getNumCards())); } public static TestSuite suite() { TestSuite suite = new TestSuite(DeckTest.class); return suite; } public void testNoNullsInDeck() { for (int i = 0; i < deck.getNumCards(); i++) assertNotNull(deck.getCardAt(i)); deck.shuffle(2); for (int i = 0; i < deck.getNumCards(); i++) assertNotNull(deck.getCardAt(i)); } public void testRandomNumbers() { Random r = new Random(); for (int i = 0; i < 600; i++) { int ran = deck.obtainRandomIndex(r); assert(ran < 100); } } }PKPo+ ޅ  /games/industriallogic/pieces/test/HandTest.javapackage games.industriallogic.pieces.test; import junit.framework.*; import java.util.*; import games.industriallogic.*; import games.industriallogic.pieces.*; import games.industriallogic.people.*; public class HandTest extends TestCase { public HandTest(String s) { super(s); } public static TestSuite suite() { TestSuite suite = new TestSuite(HandTest.class); return suite; } public void testCardRemovedFromHand() { Player player = explanations.getPlayer(0); CardComponent card = explanations.getCardAt(player.getPlayArea().insideUpperLeft()); Hand hand = (Hand) explanations.getCurrentPlayer().getHand(); PlayerArea area = explanations.getPlayerArea(0); explanations.moveCard(card, area.upperLeft()); assertEquals("hand contains card", explanations.MAX_CARDS_IN_HAND, hand.size()); explanations.moveCard(card, CardComponent.WIDTH + 10, CardComponent.HEIGHT + 10); assertEquals("hand does not contain card", explanations.MAX_CARDS_IN_HAND - 1, hand.size()); explanations.moveCard(card, area.upperLeft()); assertEquals("hand contains card", explanations.MAX_CARDS_IN_HAND, hand.size()); } public void testDraw() { Hand hand = explanations.getCurrentPlayer().getHand(); Card card = (Card) hand.elements().nextElement(); CardComponent c = new CardComponent(card, explanations); PlayerArea area = explanations.getPlayerArea(0); explanations.moveCard(c, area.upperLeft()); explanations.moveCard(c, CardComponent.WIDTH + 10, CardComponent.HEIGHT + 10); explanations.drawCards(); assertEquals("hand contains card", explanations.MAX_CARDS_IN_HAND, hand.size()); } public void testMoveTwoDrawTwo() { Player player = explanations.getPlayer(0); PlayerArea area = explanations.getPlayerArea(0); CardComponent card1 = explanations.getCardAt(area.insideUpperLeft()); CardComponent card2 = explanations.getCardAt(area.insideLowerRight()); Hand hand = explanations.getCurrentPlayer().getHand(); explanations.moveCard(card1, CardComponent.WIDTH + 10, CardComponent.HEIGHT + 10); explanations.moveCard(card2, CardComponent.WIDTH + 10, CardComponent.HEIGHT + 10); assertEquals("hand size", explanations.MAX_CARDS_IN_HAND - 2, hand.size()); explanations.drawCards(); assertEquals("hand contains card", explanations.MAX_CARDS_IN_HAND, hand.size()); } private Explanations explanations; public void setUp() throws Exception { super.setUp(); explanations = new Explanations(); int numPlayers = 4; explanations.initializeGame(numPlayers); } }PKPo+5games/industriallogic/pieces/test/PlayerAreaTest.javapackage games.industriallogic.pieces.test; import junit.framework.*; import java.awt.*; import games.industriallogic.*; import games.industriallogic.pieces.*; import games.industriallogic.people.*; public class PlayerAreaTest extends TestCase { public PlayerAreaTest(String name) { super(name); } public void testAreaMostlyContainsCard() { Player player = explanations.getPlayer(0); CardComponent card = explanations.getCardAt(player.getPlayArea().insideUpperLeft()); Hand hand = (Hand) explanations.getCurrentPlayer().getHand(); PlayerArea area = explanations.getPlayerArea(0); explanations.moveCard(card, area.upperLeft()); assertEquals("area contains card", true, area.contains(card)); explanations.moveCard(card, -5, CardComponent.HEIGHT + 10); assertEquals("area mostly contains card", true, area.contains(card)); } public void testCardInPlayAreaOne() { Player player = explanations.getPlayer(0); CardComponent card = explanations.getCardAt(player.getPlayArea().insideUpperLeft()); Hand hand = (Hand) explanations.getCurrentPlayer().getHand(); PlayerArea area = explanations.getPlayerArea(0); explanations.moveCard(card, area.upperLeft()); assertEquals("area contains card", true, area.contains(card)); explanations.moveCard(card, CardComponent.WIDTH + 10, CardComponent.HEIGHT + 10); assertEquals("area does not contain card", false, area.contains(card)); explanations.moveCard(card, area.upperLeft()); assertEquals("area contains card", true, area.contains(card)); } public void testCardInPlayAreaTwo() { Player player = explanations.getPlayer(0); CardComponent card = explanations.getCardAt(player.getPlayArea().insideUpperLeft()); Hand hand = (Hand) explanations.getCurrentPlayer().getHand(); PlayerArea area = explanations.getPlayerArea(0); explanations.moveCard(card, area.upperLeft()); assertEquals("area contains card", true, area.contains(card)); explanations.moveCard( card, CardComponent.WIDTH + CardComponent.WIDTH + 10, CardComponent.HEIGHT + 10); assertEquals("area does not contain card", false, area.contains(card)); } public void testCardSlightlyOutOfPlayAreaOne() { Player player = explanations.getPlayer(0); CardComponent card = explanations.getCardAt(player.getPlayArea().insideUpperLeft()); Hand hand = (Hand) explanations.getCurrentPlayer().getHand(); PlayerArea area = explanations.getPlayerArea(0); explanations.moveCard(card, area.upperLeft()); assertEquals("area contains card", true, area.contains(card)); explanations.moveCard(card, CardComponent.WIDTH + 10, CardComponent.HEIGHT + 10); assertEquals("area does not contain card", false, area.contains(card)); explanations.moveCard(card, area.upperLeft()); assertEquals("area contains card", true, area.contains(card)); } public void testPlayerAreaLocations() { PlayerArea one = explanations.getPlayerArea(0); Point point = new Point(0, CardComponent.HEIGHT); assertEquals("left top", point, one.upperLeft()); PlayerArea two = explanations.getPlayerArea(1); point = new Point(CardComponent.WIDTH, 0); assertEquals("left top", point, two.upperLeft()); } private Explanations explanations; public void setUp() throws Exception { super.setUp(); explanations = new Explanations(); int numPlayers = 4; explanations.initializeGame(numPlayers); } public static TestSuite suite() { TestSuite suite = new TestSuite(PlayerAreaTest.class); return suite; } public void testPlayerAreasNotNull() { for (int i = 0; i < explanations.getNumPlayers(); i++) { PlayerArea area = explanations.getCurrentPlayer().getPlayArea(); assertNotNull("area isn't null", area); } } }PKPo+ _;QQ&games/industriallogic/pieces/Card.javapackage games.industriallogic.pieces; public class Card { protected String type; protected String desc; public Card(String type, String desc) { this.type = type; this.desc = desc; } public boolean equals(Object obj) { Card card = (Card) obj; return card.getType().equals(getType()) && card.getDescription().equals(getDescription()); } public Card getClone() { return new Card(type, desc); } public String getDescription() { return desc; } public String getType() { return type; } public String toString() { return type + "\t" + desc; } }PKPo+%9/games/industriallogic/pieces/CardComponent.javapackage games.industriallogic.pieces; import java.util.*; import java.awt.*; import java.awt.event.*; import games.industriallogic.*; import games.industriallogic.pieces.*; public class CardComponent extends Container { private Card card; private Point currPos; private boolean mouseDragged = false; private Point dragPos = new Point(); public static final Font timesNewRomanBold = new Font("Times New Roman", Font.BOLD, 13); private Explanations explanations; public static int HEIGHT = 90; public static int WIDTH = 128; private Vector wrapStrings; private boolean highlight = false; public CardComponent(Card card, Explanations explanations) { super(); this.card = card; this.explanations = explanations; setSize(getMinimumSize()); wrapStrings = wrap(new StringBuffer(card.getDescription())); } public Dimension getMinimumSize() { return new Dimension(WIDTH, HEIGHT); } public Dimension getPreferredSize() { return new Dimension(WIDTH, HEIGHT); } public void paint(Graphics g) { paintCardImage(g); paintCardHighlight(g); paintCardText(g); } private void paintCardText(Graphics g) { g.setColor(Color.black); g.drawRect(0, 0, getSize().width - 1, getSize().height - 1); g.setFont(timesNewRomanBold); int i = 0; for (Enumeration e = wrapStrings.elements(); e.hasMoreElements();) { String line = (String) e.nextElement(); g.drawString(line, 6, 20 + i * 20); i++; } } public void update(Graphics g) { paint(g); } public Card getCard() { return card; } private static int MAX_LINE_WIDTH = 16; private static void addCompleteWordTo(StringBuffer phrase, StringBuffer word) { phrase.append(word); //word.delete(0, word.length()); can't use this in JDK 1.1! String fullText = word.toString(); String remainingText = fullText.substring(word.length(), fullText.length()); word.setLength(0); word.append(remainingText); } private static void addRemainingWordTo(StringBuffer phrase, StringBuffer word) { if (phrase.length() == 0) { phrase.append(word); phrase.append("-"); } } public static boolean containsPoint(Component card, int x, int y) { int cardX = card.getLocation().x; int cardY = card.getLocation().y; return x >= cardX && y >= cardY && x <= cardX + CardComponent.WIDTH && y <= cardY + CardComponent.HEIGHT; } private static String extractPhraseFrom(StringBuffer cardText) { StringBuffer phrase = new StringBuffer(""); StringBuffer word = new StringBuffer(""); final int MAXCHARS = Math.min(MAX_LINE_WIDTH, cardText.length()); for (int i = 0; i < MAXCHARS; i++) { addCharacterTo(word, cardText.charAt(i)); if (isCompleteWord(word, cardText)) addCompleteWordTo(phrase, word); } addRemainingWordTo(phrase, word); removePhraseFrom(cardText, phrase); return phrase.toString(); } private static boolean isCompleteWord(StringBuffer word, StringBuffer cardText) { return ( word.charAt(word.length() - 1) == ' ' || word.charAt(word.length() - 1) == '-' || cardText.toString().endsWith(word.toString())); } private void paintCardHighlight(Graphics g) { if (!canMove() && !highlight) return; if (isPlayedCard() && !highlight) return; if (canMove() && !highlight) g.setColor(Color.red); else g.setColor(Color.black); g.fillRect(0, 0, getSize().width, 3); g.fillRect(0, 0, 3, getSize().height); g.fillRect(0, getSize().height - 3, getSize().width, getSize().height); g.fillRect(getSize().width - 3, 0, getSize().width, getSize().height); } private void paintCardImage(Graphics g) { Image image = null; if (card.getType().equals("Problem")) { image = explanations.getGameUI().problem; } else if (card.getType().equals("Solution")) { image = explanations.getGameUI().solution; } else if (card.getType().equals("Value")) { image = explanations.getGameUI().value; } g.drawImage(image, 0, 0, explanations.getGameUI()); } public static Vector wrap(StringBuffer cardText) { Vector wrapLines = new Vector(); while (cardText.length() > 0) wrapLines.addElement(extractPhraseFrom(cardText)); return wrapLines; } private boolean canBeMoved = false; private static void addCharacterTo(StringBuffer word, char character) { word.append(character); } public boolean canMove() { return canBeMoved; } private MouseAdapter createMouseAdapter() { return new MouseAdapter() { public void mouseEntered(MouseEvent e) { if (!isPlayedCard()) return; highlight = explanations.inPickupWinningsMode(); if (highlight) repaint(); } public void mouseExited(MouseEvent e) { if (highlight == true) { highlight = false; repaint(); } } public void mousePressed(MouseEvent e) { if (explanations.inPickupWinningsMode() && !isPlayedCard()) explanations.turnPickupWinningsModeOff(); currPos = new Point(e.getX(), e.getY()); Container parent = getParent(); parent.remove(CardComponent.this); parent.add(CardComponent.this, 0); repaint(); } public void mouseReleased(MouseEvent e) { int newX = getLocation().x + e.getX() - currPos.x; int newY = getLocation().y + e.getY() - currPos.y; if (explanations.inPickupWinningsMode() && isPlayedCard()) { explanations.winCard(CardComponent.this); explanations.removeWonCardFromBoard(newX, newY); return; } explanations.moveCard(CardComponent.this, newX, newY); repaint(); } }; } private MouseMotionAdapter createMouseMotionAdapter() { return new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) { e.consume(); dragPos.x = e.getX(); dragPos.y = e.getY(); setLocation(getLocation().x + e.getX() - currPos.x, getLocation().y + e.getY() - currPos.y); repaint(); } }; } public Vector getWrapStrings() { return wrapStrings; } public boolean isPlayedCard() { return !explanations.getCurrentPlayer().has(this); } public void listenForMouseEvents() { addMouseListener(createMouseAdapter()); addMouseMotionListener(createMouseMotionAdapter()); canBeMoved = true; } private static void removePhraseFrom(StringBuffer cardText, StringBuffer phrase) { //cardText.delete(0, phrase.length()); // not supported by JDK 1.1 urrrg! String fullText = cardText.toString(); String remainingText = fullText.substring(phrase.length(), fullText.length()); cardText.setLength(0); cardText.append(remainingText); } }PKPo+ mcc&games/industriallogic/pieces/Deck.javapackage games.industriallogic.pieces; import java.util.*; public class Deck { private Card[] cards; public Deck() { assembleDeck(); } public void addCard(Card card) { int nextCardIndex = cards.length - 1; if (nextCardIndex < 0) return; cards[nextCardIndex] = card; } public int getNumCards() { return cards.length; } public Card getCardAt(int index) { return cards[index]; } public void shuffle(int numTimes) { Random r = new Random(); for (int i = 0; i < numTimes; i++) for (int j = 0; j < getNumCards(); j++) swap(obtainRandomIndex(r), obtainRandomIndex(r)); } public String toString() { String str = "Applicability\tType\tDescription\n" + "-------------\t----\t-----------\n"; for (int i = 0; i < getNumCards(); i++) str += getCardAt(i) + "\n"; return str; } private int NUM_CARDS = problems.length + solutions.length + values.length; private static String[] problems = new String[] { "System Is Buggy", "Stories Are Too Big And/Or Complex", "Code Isn't Testable", "Team Lacks Essential Database/Environment Rights", "Acceptance Tests Hard To Write", "Code Is Bloated And Fragile", "Team Can't Get Open Workspace", "Programmers Don't Rotate Off Specialty Tasks", "Tasks Incomplete At Iteration End", "Team Velocity Too Slow", "Programmers Don't Have Enough Story Details", "QA Department Wants Detailed Requirements", "Customers Have No Time To Test System", "Programmers Have Little Contact With Customer", "Programmers Have Little Contact With Customer", "Standup Meetings Take Too Long", "Customer Doesn't Help Produce Acceptance Tests", "Team Doesn't Confront Known Problems", "Management Wants Detailed Documentation", "Customer Wants Delivery By Certain Date", "Coach Solves Hardest Technical Tasks", "Proxy Customers Don't Know Customer Needs", "Team Is Told To Produce Way More Than They Can", "Tests Run Too Slowly", "Discovered Complexity Impedes Development", "A Programming Pair Expresses Confusion", "System Has Duplicate Code", "Diverse Coding Conventions Cause Constant Refactorings", "Programmers Are Ignorant Of Best Practices", "Integration Takes Too Long", "Integration Takes Too Long", "Sections Of Code Are Hard To Understand", "Programmers Are Exhausted", "Team Completes Tasks But Doesn't Refactor", "Team Completes Tasks But Doesn't Refactor", "Programming Pairs Don't Get Along", "Solo Programmers Produce Poor/Buggy Code", "Tests Not Run Before Integration", "Complexity Obscures Simple Solutions", "Team Doesn't Write Tests", "Uncoordinated Dependencies Cause Delays", "Team Encounters Risky Technical Challenges", "Acceptance Tests Performed Manually", "Cowboy Coders Ignore Team's Process", "Code Is Released With Failing Tests", "Meeting Delivery Date Is Unlikely", "Programmers Abandon Practices Under Pressure"}; private static String[] solutions = new String[] { "Story Deferred To Later Iteration", "Programmers Discuss Stories with Customers", "Split a Story", "40-Hour Week", "Temporarily Compromise Practices To Deliver Ontime", "Produce Acceptance Tests To Document Requirements", "Manually Test Where Automation Is Too Costly", "Automate Acceptance Testing", "Customers & Developers Work In Same Room", "Customers & Developers Work In Same Room", "Simplify Stories", "Confront Technical Risks Early", "Intention-Revealing Code", "Include Technical Task Time In Estimates", "Coach Lets Team Face Challenges", "Consider The Simplest Thing That Could Possibly Work", "Reform Cowboy Coders Or Fire Them", "Coding Standard", "Include QA People On XP Team", "Create/Update Release Plan", "Small Releases", "Simple Design", "Every Test Passes Before Code Is Released", "Once And Only Once", "Evolve User-Friendly Acceptance Test Tools", "Optimize Test Code", "Refactor Mercilessly", "Refactor Mercilessly", "Reduce Team Velocity", "Continuous Integration", "Continuous Learning", "Quick Design Session", "Quick, Daily Standup Meetings", "Programmers Help Pairs Within Earshot", "Write Tests To Demonstrate Bugs & Fix Them", "Metaphor", "Pair Program And Change Pairs Frequently", "Test-First Design", "Test-First Design", "Spike Solution", "You Aren't Gonna Need It", "Obtain/Create Team's Open Workspace", "Obtain A Complete Local Development Environment", "Collective Code Ownership", "Improve Estimates", "Test Anything That Could Possibly Break", "The Planning Game", "The Planning Game"}; private Stack stack = new Stack(); private static String[] values = new String[] {"Communication", "Simplicity", "Feedback", "Courage"}; public Deck(int timesToShuffle) { assembleDeck(); shuffle(timesToShuffle); } private void assembleDeck() { cards = new Card[NUM_CARDS]; assembleDeckSolutions(); assembleDeckProblems(); assembleDeckValues(); shuffle(1); for (int i = 0; i < NUM_CARDS; i++) stack.push(cards[i]); } private void assembleDeckProblems() { int i = solutions.length; for (int j = 0; j < problems.length; j++) cards[i++] = new Card("Problem", problems[j]); } private void assembleDeckSolutions() { for (int j = 0; j < solutions.length; j++) cards[j] = new Card("Solution", solutions[j]); } private void assembleDeckValues() { int i = problems.length + solutions.length; for (int j = 0; j < values.length; j++) cards[i++] = new Card("Value", values[j]); } public Card getCard() { if (stack.isEmpty()) return null; stack.pop(); if (stack.isEmpty()) return null; return (Card) stack.peek(); } public int obtainRandomIndex(Random r) { return Math.abs(r.nextInt()) % NUM_CARDS; } private void swap(int index1, int index2) { Card temp = cards[index1]; cards[index1] = cards[index2]; cards[index2] = temp; } }PKPo+&games/industriallogic/pieces/Hand.javapackage games.industriallogic.pieces; import java.util.*; public class Hand { private Deck deck; private Vector currentHand = new Vector(); public Hand(Deck deck) { this.deck = deck; } public void addCard(Card card) { if (!currentHand.contains(card)) currentHand.addElement(card); } public void clearHand() { currentHand = new Vector(); } public Enumeration elements() { return currentHand.elements(); } public boolean removeCard(Card card) { return currentHand.removeElement(card); } public int size() { int i = 0; Enumeration e = currentHand.elements(); while (e.hasMoreElements()) { i++; e.nextElement(); } return i; } public boolean draw(int numCards) { boolean successfulDraw; for (int i = 0; i < numCards; i++) { Card card = deck.getCard(); successfulDraw = (card != null); if (!successfulDraw) return false; currentHand.addElement(card); } return true; } }PKPo+,games/industriallogic/pieces/PlayerArea.javapackage games.industriallogic.pieces; import java.awt.*; import games.industriallogic.pieces.*; import games.industriallogic.ui.*; public class PlayerArea { private static final int HEIGHT = CardComponent.HEIGHT; private static final int[][] REGIONS = {LEFT, TOP, RIGHT, BOTTOM}; private static final int WIDTH = CardComponent.WIDTH; private static final int[] LEFT = {0, HEIGHT, WIDTH, ExplanationsUI.HEIGHT - HEIGHT}; private static final int[] RIGHT = {ExplanationsUI.WIDTH - WIDTH, HEIGHT, ExplanationsUI.WIDTH, ExplanationsUI.HEIGHT - HEIGHT}; private static final int[] TOP = {WIDTH, 0, ExplanationsUI.WIDTH - WIDTH, HEIGHT}; private static final int[] BOTTOM = {WIDTH, ExplanationsUI.HEIGHT - HEIGHT, ExplanationsUI.WIDTH - WIDTH, ExplanationsUI.HEIGHT}; private final int[] coords; public PlayerArea(int areaNumber) { coords = REGIONS[areaNumber]; } public boolean contains(Component c) { if (!(c instanceof CardComponent)) return false; Point p = c.getLocation(); return containsEntireCard(p) || containsLeftCorner(p) || containsRightCorner(p); } public Point insideUpperLeft() { return new Point(coords[0] + 1, coords[1] + 1); } public Point upperLeft() { return new Point(coords[0], coords[1]); } private boolean containsEntireCard(Point p) { return (p.x >= coords[0] && p.y >= coords[1] && (p.x + CardComponent.WIDTH) <= coords[2]) && (p.y + CardComponent.HEIGHT) <= coords[3]; } private boolean containsLeftCorner(Point p) { return (p.x > coords[0] && p.y > coords[1] && (p.x < coords[2]) && (p.y < coords[3])); } private boolean containsRightCorner(Point p) { return ( (p.x + CardComponent.WIDTH) > coords[0] && (p.y + CardComponent.HEIGHT) > coords[1] && (p.x + CardComponent.WIDTH) < coords[2] && (p.y + CardComponent.HEIGHT) < coords[3]); } public Point insideLowerRight() { return new Point(coords[2] - 1, coords[3] - 1); } }PKPo+/games/industriallogic/tests/AcceptanceTest.javapackage games.industriallogic.tests; import java.util.*; import games.industriallogic.*; import games.industriallogic.pieces.*; import junit.framework.*; import java.awt.*; import games.industriallogic.people.*; public class AcceptanceTest extends TestCase { public AcceptanceTest(String name) { super(name); } public static TestSuite suite() { return new TestSuite(AcceptanceTest.class); } private Explanations explanations; private boolean gameOverCalled; private int numPlayers; private CardComponent getFirstCardFromPlayerOne(Explanations explanations) { return explanations.getCardAt(1, CardComponent.HEIGHT + 1); } private void playACardForPlayer(int playerNumber) { Player player = explanations.getPlayer(playerNumber); CardComponent card = explanations.getCardAt(player.getPlayArea().insideUpperLeft()); explanations.moveCard(card, CardComponent.WIDTH + 10, CardComponent.HEIGHT + 10); explanations.drawCards(); } public void setUp() { explanations = new Explanations(); numPlayers = 4; explanations.initializeGame(numPlayers); } public void testGameOver() { gameOverCalled = false; GameOverListener listener = new GameOverListener() { public void gameOver() { gameOverCalled = true; } }; explanations.addGameOverListener(listener); //play 20 cards for each player -- gives us 96 played cards for (int i = 0; i < 20; i++) for (int j = 0; j < 4; j++) playACardForPlayer(j); playACardForPlayer(0); // 97th card playACardForPlayer(1); // 98th card assertEquals("game over called", false, gameOverCalled); playACardForPlayer(2); // 99th card assertEquals("game over called", true, gameOverCalled); } public void testHandCountsAfterPlays() { for (int i = 0; i < numPlayers; i++) { playACardForPlayer(i); int handSize = explanations.getCurrentPlayer().getHand().size(); assertEquals("hand size", explanations.MAX_CARDS_IN_HAND, handSize); } } public void testMovingNonPlayableCards() { playACardForPlayer(0); PlayerArea area = explanations.getPlayerArea(0); CardComponent card = explanations.getCardAt(area.insideUpperLeft()); int xLocation = card.getX(); int yLocation = card.getY(); explanations.moveCard(card, CardComponent.WIDTH + 10, CardComponent.HEIGHT + 10); assertEquals("old card location", new Point(xLocation, yLocation), card.getLocation()); } public void testPlayerRotationAfterPlays() { playACardForPlayer(0); assertEquals("it's now player 1's turn", 1, explanations.getCurrentPlayer().getId()); playACardForPlayer(1); assertEquals("it's now player 2's turn", 2, explanations.getCurrentPlayer().getId()); playACardForPlayer(2); assertEquals("it's now player 3's turn", 3, explanations.getCurrentPlayer().getId()); playACardForPlayer(3); assertEquals("it's now player 0's turn", 0, explanations.getCurrentPlayer().getId()); } public void testTotalCardsInDeck() { int totalCardsInDeck = explanations.getDeck().getNumCards(); assertEquals("99 cards in deck", 99, totalCardsInDeck); } public void testWinningACard() { CardComponent card = getFirstCardFromPlayerOne(explanations); explanations.moveCard(card, CardComponent.WIDTH + 10, CardComponent.HEIGHT + 10); explanations.pickupWinnings(); explanations.winCard(card); assertEquals("check number of cards on table", 15, explanations.getCardsOnTable().size()); assertEquals( "check that player 1 has 1 card in winnings", 1, explanations.getPlayer(0).getWinnings().size()); CardComponent cardInWinningsPile = (CardComponent) explanations.getPlayer(0).getWinnings().get(0); assertEquals( "same card in winnings pile", card.getCard().getDescription(), cardInWinningsPile.getCard().getDescription()); } }PKPo+9AA)games/industriallogic/tests/AllTests.javapackage games.industriallogic.tests; import junit.framework.*; import games.industriallogic.ui.test.*; import games.industriallogic.pieces.test.*; public class AllTests extends TestCase { public AllTests(String s) { super(s); } public static TestSuite suite() { TestSuite suite = new TestSuite(); suite.addTest(CardTest.suite()); suite.addTest(DeckTest.suite()); suite.addTest(HandTest.suite()); suite.addTest(PlayerAreaTest.suite()); suite.addTest(CardComponentTest.suite()); suite.addTest(AcceptanceTest.suite()); return suite; } }PKQo+q==4games/industriallogic/ui/test/CardComponentTest.javapackage games.industriallogic.ui.test; import junit.framework.*; import java.util.*; import games.industriallogic.pieces.*; import games.industriallogic.ui.*; public class CardComponentTest extends TestCase { public CardComponentTest(String name) { super(name); } public static TestSuite suite() { return new TestSuite(CardComponentTest.class); } private String[] result = new String[3]; public void accumulateResult(StringBuffer testString) { int i = 0; for (Enumeration e = CardComponent.wrap(testString).elements(); e.hasMoreElements();) result[i++] = (String) e.nextElement(); } public void testWrap() { accumulateResult(new StringBuffer("Developers Misunderstand Requirements")); assertEquals("First line", "Developers ", result[0]); assertEquals("Second line", "Misunderstand ", result[1]); assertEquals("Third line", "Requirements", result[2]); } public void testWrap2() { accumulateResult(new StringBuffer("Stories Are Too Complex")); assertEquals("First line", "Stories Are Too ", result[0]); assertEquals("Second line", "Complex", result[1]); } public void testWrap3() { accumulateResult(new StringBuffer("Intention-Revealing Code")); assertEquals("First line", "Intention-", result[0]); assertEquals("Second line", "Revealing Code", result[1]); } }PKPo+e,games/industriallogic/ui/ExplanationsUI.javapackage games.industriallogic.ui; import java.applet.*; import java.awt.*; import java.awt.event.*; import java.util.*; import games.industriallogic.*; import games.industriallogic.pieces.*; import games.industriallogic.people.*; public class ExplanationsUI extends Applet implements GameOverListener { private ScoreBoard winningsPanel; private Image logo; public String getAppletInfo() { return "eXPlanations\n" + "\n" + "by Industrial Logic, Inc.\n" + "Creation date: (6/25/2001 1:44:01 AM)\n" + "@authors: Joshua Kerievsky and Somik Raha\n" + ""; } public void init() { super.init(); explanations = new Explanations(); loadImages(); createScoreBoard(); createPlayersTurnPanel(); createPlayingBoard(); setLayout(new BorderLayout()); explanations.initializeGame(DEFAULT_NUM_PLAYERS); explanations.setGameUI(this); explanations.addGameOverListener(this); showHands(); add(playingBoard, BorderLayout.CENTER); playingBoard.initializeDoubleBuffer(getSize().width, getSize().height); } public static int HEIGHT = 540; public static int WIDTH = 768; private Button createDrawCardsButton() { Button drawCardsButton = new Button("Draw Card"); drawCardsButton.setSize(CardComponent.WIDTH, CardComponent.HEIGHT); drawCardsButton.setLocation(0, HEIGHT - CardComponent.HEIGHT); drawCardsButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { explanations.drawCards(); showPreviousHand(); } }); return drawCardsButton; } private void redrawBoard() { scoreBoard.repaint(); playersTurnPanel.repaint(); playingBoard.repaint(); } private void showHands() { for (Enumeration e = explanations.getCardsOnTable().elements(); e.hasMoreElements();) { CardComponent card = (CardComponent) e.nextElement(); playingBoard.add(card, 0); } redrawBoard(); } private void showPreviousHand() { removePreviousHandFromBoard(); Player prevPlayer = explanations.getPlayer(explanations.previousPlayerNumber()); for (Enumeration e = explanations.getCardsOnTable().elements(); e.hasMoreElements();) { CardComponent card = (CardComponent) e.nextElement(); if (prevPlayer.has(card)) playingBoard.add(card, 0); } redrawBoard(); } private PlayingBoard playingBoard = null; private ScoreBoard scoreBoard; private Button createAddToWinningsToggleButton() { Button toggleWinnings = new Button("Add To Winnings"); toggleWinnings.setSize(CardComponent.WIDTH, CardComponent.HEIGHT); toggleWinnings.setLocation( ExplanationsUI.WIDTH - CardComponent.WIDTH, HEIGHT - CardComponent.HEIGHT); toggleWinnings.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { explanations.pickupWinnings(); } }); return toggleWinnings; } private ScoreBoard createScoreBoard() { scoreBoard = null; scoreBoard = new ScoreBoard(this); scoreBoard.setSize(CardComponent.WIDTH, CardComponent.HEIGHT); scoreBoard.setLocation(WIDTH - CardComponent.WIDTH, 0); return scoreBoard; } private void removePreviousHandFromBoard() { Player player = explanations.getPlayer(explanations.previousPlayerNumber()); Component[] component = playingBoard.getComponents(); for (int i = 0; i < component.length; i++) if (player.has(component[i])) playingBoard.remove(component[i]); } public void removeWonCardFromBoard(int x, int y) { Component[] component = playingBoard.getComponents(); for (int i = 0; i < component.length; i++) if (CardComponent.containsPoint(component[i], x, y)) { playingBoard.remove(component[i]); scoreBoard.updatePlayString(); redrawBoard(); return; } } private PlayersTurnPanel playersTurnPanel; private PlayersTurnPanel createPlayersTurnPanel() { playersTurnPanel = null; playersTurnPanel = new PlayersTurnPanel(this); playersTurnPanel.setLocation(0, 0); playersTurnPanel.setSize(CardComponent.WIDTH, CardComponent.HEIGHT); return playersTurnPanel; } public static Color backgroundColor = new Color(214, 214, 214); private Explanations explanations; private static int DEFAULT_NUM_PLAYERS = 4; public Image problem; public Image solution; public Image value; private GameOverPanel createGameOverPanel() { GameOverPanel panel = new GameOverPanel(this); panel.setLocation(0, 0); panel.setSize(CardComponent.WIDTH, CardComponent.HEIGHT); return panel; } private void createPlayingBoard() { playingBoard = null; playingBoard = new PlayingBoard(this, logo); playingBoard.setLayout(null); playingBoard.add(createDrawCardsButton()); playingBoard.add(createAddToWinningsToggleButton()); playingBoard.add(scoreBoard); playingBoard.add(playersTurnPanel); } public void gameOver() { removePlayComponents(); playingBoard.add(createGameOverPanel()); } public Explanations getGame() { return explanations; } private Image loadImage(String fileName) { Image imageToLoad = getImage(getCodeBase(), fileName); int tries = 0; while (!prepareImage(imageToLoad, this) && (tries++) < 1000) try { Thread.sleep(100); } catch (InterruptedException e) { } return imageToLoad; } private void loadImages() { logo = loadImage(getParameter("LOGO_FILE")); problem = loadImage("problem.jpg"); solution = loadImage("solution.jpg"); value = loadImage("value.jpg"); } private void removePlayComponents() { playingBoard.remove(playersTurnPanel); Component[] component = playingBoard.getComponents(); for (int i = 0; i < component.length; i++) if (component[i] instanceof Button) playingBoard.remove(component[i]); } }PKPo+5\~~+games/industriallogic/ui/GameOverPanel.javapackage games.industriallogic.ui; import java.util.*; import java.awt.*; import games.industriallogic.*; import games.industriallogic.pieces.*; public class GameOverPanel extends Panel { public void paint(Graphics g) { int width = CardComponent.WIDTH; int height = CardComponent.HEIGHT; g.setColor(gameUI.backgroundColor); g.fillRect(0, 0, width, height); g.setColor(Color.black); paintBorderRectangle(g, height, width); paintGameOverMessage(g); } public void update(Graphics g) { paint(g); } private ExplanationsUI gameUI; public static final Font timesNewRoman = new Font("Times New Roman", Font.BOLD, 12); public GameOverPanel(ExplanationsUI explanationsUI) { super(); gameUI = explanationsUI; setLayout(null); } private void paintBorderRectangle(Graphics g, int height, int width) { g.setColor(Color.black); g.drawLine(0, 0, width, 0); g.drawLine(0, 0, 0, height); g.drawLine(width, 0, width, height); g.drawLine(0, height, width / 2, height); } private void paintGameOverMessage(Graphics g) { g.setFont(timesNewRoman); g.drawString("Game Over", 30, 50); } }PKPo+LĻ.games/industriallogic/ui/PlayersTurnPanel.javapackage games.industriallogic.ui; import java.util.*; import java.awt.*; import games.industriallogic.*; import games.industriallogic.pieces.*; public class PlayersTurnPanel extends Panel { public void paint(Graphics g) { int width = CardComponent.WIDTH; int height = CardComponent.HEIGHT; g.setColor(gameUI.backgroundColor); g.fillRect(0, 0, width, height); g.setColor(Color.black); paintBorderRectangle(g, height, width); paintCurrentPlayerNumber(g); } public void update(Graphics g) { paint(g); } public static final Font timesNewRomanBold = new Font("Times New Roman", Font.PLAIN, 40); private ExplanationsUI gameUI; public PlayersTurnPanel(ExplanationsUI explanationsUI) { super(); gameUI = explanationsUI; setLayout(null); } private void paintBorderRectangle(Graphics g, int height, int width) { g.setColor(Color.black); g.drawLine(0, 0, width, 0); g.drawLine(0, 0, 0, height); g.drawLine(width, 0, width, height); g.drawLine(0, height, width / 2, height); } private void paintCurrentPlayerNumber(Graphics g) { g.setFont(timesNewRomanBold); g.drawString(gameUI.getGame().getCurrentPlayer().getScreenId(), 52, 60); } }PKQo+)qnn*games/industriallogic/ui/PlayingBoard.javapackage games.industriallogic.ui; import java.awt.*; import games.industriallogic.pieces.*; public class PlayingBoard extends Panel { private Image image; private Graphics offG; private Font timesNewRomanBold; public java.awt.Font getTimesNewRomanBold() { return timesNewRomanBold; } public void initializeDoubleBuffer(int width, int height) { image = createImage(width, height); if (image == null) { System.out.println("offscreen image was null!"); return; } else offG = image.getGraphics(); } public void paint(Graphics g) { int width = getSize().width - 1; int height = getSize().height - 1; drawBoardColor(); drawCenterGrid(height, width); drawOuterBorder(height, width); drawCenterImage(); super.paint(offG); g.drawImage(image, 0, 0, null); } public void setTimesNewRomanBold(java.awt.Font newTimesNewRomanBold) { timesNewRomanBold = newTimesNewRomanBold; } public void update(Graphics g) { paint(g); } private ExplanationsUI gameUI; private Image logo; public PlayingBoard(ExplanationsUI gameUI, Image logo) { super(); this.logo = logo; this.gameUI = gameUI; timesNewRomanBold = new Font("Times New Roman", Font.BOLD, 14); } private void drawBoardColor() { offG.setColor(gameUI.backgroundColor); offG.fillRect(0, 0, getSize().width, getSize().height); } private void drawCenterGrid(int height, int width) { offG.setColor(Color.black); offG.drawLine(0, CardComponent.HEIGHT, width, CardComponent.HEIGHT); // top offG.drawLine(0, height - CardComponent.HEIGHT, width, height - CardComponent.HEIGHT); // bottom offG.drawLine(CardComponent.WIDTH, 0, CardComponent.WIDTH, height); // left offG.drawLine(width - CardComponent.WIDTH, 0, width - CardComponent.WIDTH, height); // right } private void drawCenterImage() { offG.drawImage(logo, 312, 190, null); } private void drawOuterBorder(int height, int width) { offG.setColor(Color.black); offG.drawLine(0, 0, width, 0); offG.drawLine(0, 0, 0, height); offG.drawLine(width, 0, width, height); offG.drawLine(width / 2, height, width, height); } }PKPo+hו(games/industriallogic/ui/ScoreBoard.javapackage games.industriallogic.ui; import java.util.*; import java.awt.*; import games.industriallogic.pieces.*; public class ScoreBoard extends Panel { private Vector playStrings = new Vector(); public static final Font timesNewRoman = new Font("Times New Roman", Font.PLAIN, 12); public static final Font timesNewRomanBold = new Font("Times New Roman", Font.BOLD, 13); public void paint(Graphics g) { int width = CardComponent.WIDTH; int height = CardComponent.HEIGHT; g.setColor(gameUI.backgroundColor); g.fillRect(0, 0, width, height); g.setColor(Color.black); paintBorderRectangle(g, height, width); paintRows(g); } public void paintRows(Graphics g) { g.setFont(timesNewRomanBold); g.drawString(" Team | Winnings", 11, 15); g.setColor(Color.black); g.drawRect(0, 0, getSize().width - 1, getSize().height - 1); g.setFont(timesNewRoman); for (int i = 0; i < gameUI.getGame().getNumPlayers(); i++) { g.drawString(new String("" + (i + 1)), 30, 33 + i * 16); g.drawString(new String("" + gameUI.getGame().getPlayer(i).getWinnings().size()), 88, 33 + i * 16); } } public void update(Graphics g) { paint(g); } public void updatePlayString() { playStrings.removeAllElements(); for (int i = 0; i < gameUI.getGame().getNumPlayers(); i++) { StringBuffer buf = new StringBuffer(); buf.append(i + 1); buf.append(" | "); buf.append(gameUI.getGame().getPlayer(i).getWinnings().size()); playStrings.addElement(buf.toString()); } } private ExplanationsUI gameUI; public ScoreBoard(ExplanationsUI explanationsUI) { super(); this.gameUI = explanationsUI; setLayout(null); } private void paintBorderRectangle(Graphics g, int height, int width) { g.setColor(Color.black); g.drawLine(0, 0, width, 0); g.drawLine(0, 0, 0, height); g.drawLine(width, 0, width, height); g.drawLine(0, height, width / 2, height); } }PKPo+2'games/industriallogic/Explanations.javapackage games.industriallogic; import java.awt.*; import java.util.*; import games.industriallogic.pieces.*; import games.industriallogic.ui.*; import games.industriallogic.people.*; public class Explanations { private int numPlayers; private Deck deck; private int currentPlayer = 0; public void drawCards() { int numCardsToDraw = MAX_CARDS_IN_HAND - getCurrentPlayer().getHand().size(); Hand hand = getCurrentPlayer().getHand(); if (!hand.draw(numCardsToDraw)) { gameOver(); return; } removeCardComponents(getCurrentPlayer()); createCardComponents(getCurrentPlayer()); advanceToNextPlayer(); enableCurrentHand(); } private void removeCardFromCurrentHand(Card card) { getCurrentPlayer().getHand().removeCard(card); } private Vector cardsOnTable = new Vector(); public static int MAX_CARDS_IN_HAND = 4; private boolean pickupWinningsMode = false; private void addCardToCurrentHand(Card card) { getCurrentPlayer().getHand().addCard(card); } private void advanceToNextPlayer() { currentPlayer++; if (currentPlayer > (numPlayers - 1)) currentPlayer = 0; } private void createDeck() { int SHUFFLE_TIMES = 3; deck = new Deck(SHUFFLE_TIMES); } public CardComponent getCardAt(int x, int y) { for (Enumeration e = cardsOnTable.elements(); e.hasMoreElements();) { Component card = (Component) e.nextElement(); if (CardComponent.containsPoint(card, x, y)) return (CardComponent) card; } return null; } public CardComponent getCardAt(Point p) { return getCardAt(p.x, p.y); } public Vector getCardsOnTable() { return cardsOnTable; } public PlayerArea getPlayerArea(int areaNumber) { return players[areaNumber].getPlayArea(); } public void initializeGame(int newNumPlayers) { createDeck(); createPlayers(newNumPlayers); createHands(); enableCurrentHand(); } public void moveCard(CardComponent card, int x, int y) { moveCard(card, new Point(x, y)); } public void moveCard(CardComponent card, Point newLocation) { if (!card.canMove()) return; card.setLocation(newLocation); if (getCurrentPlayer().has(card)) addCardToCurrentHand(card.getCard()); else removeCardFromCurrentHand(card.getCard()); } public void pickupWinnings() { if (pickupWinningsMode == false) pickupWinningsMode = true; else pickupWinningsMode = false; } public int previousPlayerNumber() { if (currentPlayer == 0) return numPlayers - 1; return currentPlayer - 1; } public int getNumPlayers() { return numPlayers; } public boolean inPickupWinningsMode() { return pickupWinningsMode; } public void removeWonCardFromBoard(int x, int y) { if (gameUI == null) return; gameUI.removeWonCardFromBoard(x, y); } public void winCard(Component card) { if (!pickupWinningsMode) return; getCurrentPlayer().getWinnings().addElement(card); cardsOnTable.removeElement(card); } private GameOverListener gameOverListener; private ExplanationsUI gameUI; private Player[] players; public void addGameOverListener(GameOverListener listener) { gameOverListener = listener; } private void createCardComponents(Player player) { Hand hand = player.getHand(); Point p = player.getPlayArea().upperLeft(); int locY = new Double(p.y).intValue(); int locX = new Double(p.x).intValue(); for (Enumeration e = hand.elements(); e.hasMoreElements();) { Card card = (Card) e.nextElement(); CardComponent c = new CardComponent(card, this); cardsOnTable.addElement(c); c.setLocation(locX, locY); if (player.getId() == 0 || player.getId() == 2) locY += CardComponent.HEIGHT; else locX += CardComponent.WIDTH; } } private void createHands() { for (int i = 0; i < numPlayers; i++) { Hand hand = new Hand(deck); hand.draw(MAX_CARDS_IN_HAND); players[i].setHand(hand); createCardComponents(players[i]); } } private void createPlayers(int numPlayers) { this.numPlayers = numPlayers; players = new Player[numPlayers]; for (int i = 0; i < numPlayers; i++) players[i] = new Player(i); } private void enableCurrentHand() { Player player = getCurrentPlayer(); Enumeration e = ((Vector) cardsOnTable.clone()).elements(); while (e.hasMoreElements()) { CardComponent card = (CardComponent) e.nextElement(); if (player.has(card)) card.listenForMouseEvents(); } } public void gameOver() { updateGameOverListeners(); } public Player getCurrentPlayer() { return players[currentPlayer]; } public Deck getDeck() { return deck; } public ExplanationsUI getGameUI() { return gameUI; } public Player getPlayer(int number) { return players[number]; } private void removeCardComponents(Player player) { Enumeration e = ((Vector) cardsOnTable.clone()).elements(); while (e.hasMoreElements()) { Component card = (Component) e.nextElement(); if (player.has(card)) cardsOnTable.removeElement(card); } } public void setGameUI(ExplanationsUI newGameUI) { gameUI = newGameUI; } public void turnPickupWinningsModeOff() { pickupWinningsMode = false; } private void updateGameOverListeners() { if (gameOverListener == null) return; gameOverListener.gameOver(); } }PKPo+0h` )TSj `>:ak5Åƍ1`TY0j\+Ai<4ְtScD0dC/\f}iV ¼e*Fj8p `ǏV%A|pM[l@FIC<h`XJa 09p@PP0؇_'Qe(jbkMV՘SJAEރf-4S$@Epy.Y%G(eTVY%G59@B/<c (\;ț0$]v)r v6Kaw_W~%P6`'  h7H' UfSifЂEWV@i:'JKg 2t᠈;h0zjqI:c=AX@W "u%' gn c*PPjwNHSqm~ !k Wأ( J0e;h <'X&'hALD Y֘eu}t9M(&\kfFC ':Ljj2|\ Q˥ ,E|+92ڥ |Z}Q PB !%l W@yPCNL`砇 * ^z  @ ^ {N -O* n;)܇@ nWj B1Ldb4to?hx3^U$,MD=4=f3#'4Y( r2& ,F-Zra y0PD( U n>"l288h pl&m)NqBT= Jy \t#k:`+F͏G '4B%Tv u}!kҹi3]z6*42ۧ^ _S, E8yꖽuE,C>*ɖy5H;JgY){AЖt?ʼngB @i]*[h57Wg8r?@%5\l92+D KB1œ]Jj e+ISڼj8eg5 *X5@n@j3Dk$SA&Or*G" gc d& t3UG);D%Yl"z%L(WҀt*k(c 7Rn^ A@ԁЀi4ՠ$'$@ ZVx7-ʕ-XZ@6ꥠ H`5ԍ %n=@ { x:ح w(gCAkz#oO3AWTO'0xٛ_.)7@ lW!NtƒZzX{ }-P_ $4!/$-(%d6&55m"eLGG(00Ș  \O.ّ~0:) r\I)&ڤ@^Pj(E LM4|M3VERW5Xq.m A3# ]KF l004~vP!_Ͼ '@m[rp_%80l/1lм}h]@禔#/Ad6~Kg=mK;i4Qtd.s1UigA=BIjS55\#JHX̽eP`Z=eT=d4vɚц`m_'B( ,fFjAH n0RUqL,hAPRf{Y@bAzpb\ǚ6LQJ|T\;T4fmYX>Dek6P ՎtRTJ,#CQdYNyaeT}_1vL1% 9eRRH0$.!#/dpPM"}5`WDZ11GV2E{dDǁPR>HG74cu~/_x%I8}4g1TBXD/9>ogOR0nb1.tySW;be3POEh0PAe>0z8KU'hPt-Kr%3j\/^!V'cn2'MeVJ/'059+<|$-ă(W%}Rz5NsU"Z25@'hT6$6k*xLx.X#l!4!5L$Ha5/h2d#yk4PdqRWrԌ /y&+5J32u. (f"_ 7Č!u!'5Z(RESFms 2?4Cg2YVeCzN858h29 5i5 [89@!-|!|2%݇7p+1BX+3%: NK7‡Au4S\4cdɥl@qW7 ja|٘i5;#`!;;;;ř<`<)_(;& _$'p|r<`vI;+#$p(^Y;9_!a);s <ٹ;#`+p;“ɝԩ^af:=%鹞aB9`8576px4p*a6PEQD!s Y@aGz39f>8*,6#\M&/>eeu1f11#!W$%Ď%= v,+b;5CwjIV(iiђ&wFfR?ZfuQbo`mx&.J]!g1GR a)DB[zhl!6NHA!Y*.NZEs qsv֌jHntjG7fCm4rⓐd9w6o<tm ڎMh3Y :`H6ZfmYF2k"(7טjX&3DJm:S3`j(z!Jf K4f4jwפQofgmi4CmsEx,7U,GU088]# S"WAQ.;W6jX/H rr"}},Rt3fbxC6 " 6df5}=X􈎸85e=-T(CUGfA+0BLT%֝0%0eӮ1"8#6xO^{A8`} R|h/ yBg13܈f"4 ebRR׻; Vlc/",sM `&RW&x=@K2((P U!vƒQA.5Eba,@3!\4^95+895$|Sg@+4421|1 /2 5ZP(36LքDږ.Ͳ5j1@NI՘[xm4n-PµCAWAg*Gi+`LUn\4Ŭ9+ +V6P@]BA8V"#V+4&p{9! N|RL1?9?t^+ S[E/9[ +zQ ($LH%CA2D cX(ȃ)Ud C-Uz Y rEMwpCfH38Ӗ/H4B̦dGFDfOxXA1bnjS|^ݙ6j=`}Ǝ'{3'@_`LBzQ@8{d[gdkW @}UF>aO-^>X %mXEr9sz ~CA|_M# 5QOv3s# 44ԺRPOR~?ºၔUaNn]G  h(h?/?/#(Կ|ϗ۴|G  h(h?/?/"zuq<~CY7' ~CA|_M# 5~Xra9s:i#УU \G  h(h?/UK4-o_kO 铼rc0C./I|W`iJ+iNrZI|)`+K_ &EiDUߨQEDψ?&A> ?g׻Cq>3ym q!Fj(AUxLd͸5X n3 (F4F'+$ f3%_n?Y˥P1$Ÿ b %mX|(*o(gUZ h>c??Vq4P U1]z*ZThĹ)7ͮ|V4Z%AɮxڌP[ݔl4{R$v E9ngJ)G (@K?_&Ek$ f .ƽC_ &Ek1$Ÿ b!P*9MV}hIʳܡ8_Weiʸ*m/\~=,Ȓ(4((K?_&Ek$ f .ƽC_ &Ek} ľ#Ѭ Qª*< _e:_*k%S y7s+OJSC.TWu]%~)Y?+O KQgtWŸ]g?2?/Gyu_ WuˬtU>}E|) _e:_*JSC.T{o ]%~)Y?+O KQgu|gK_ WuZ|Gq^Aq Zh$e`G y[GTw?PK`o+X solution.jpgJFIFHHC  !"$"$CZ" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?> ^[EKIf7y%Ik'x5S "ELf&TJow$7 t_M/&J+UoНEN kKU(֔iJ˖ 'x4o?}bCĂ1被˲rnf`*$zpI" >;.НE]٩կ ǣг*9dRHНEN kGMqzs:8WS{<ʴgF\Vg7N h'x5QUds/&_ u]WDK+т@5|lC\]8 !-XQEsG[6yC_oS\$I5~ ֱF)C,Ĉw]44 Dxspv6b^2W9mЌSk֭Vߝ87EJn&44GZ;wi{慽tCSZC"#'VRE]Ԍ38B7>`+$ſf>6!o.?Y?/aOIou|C/[]}(`1OnN>Y} sܰ XKF62Zgq5ZoYcȠA+++`AAcܵyܸ|z}zP@aƢTٜ0t*QG_emv1Ὼ\:+RACM?QxWM:3sUtQEny!\$>-E5!Iqti~cH|%`+C "EQEs֒5>nm[ W#z+c9\5 t=dO:ٿ$xaҪϡ[>LNNy 305,0e"5wF8u6svg7w]@ &%Q޿frHpS\]#J䏦k*6;eLEu"d\|z QT%}vtQEzGąr?,_\$>-E5ӂy%> !-XH|%`+SOQElȐ]DZZ$EC+ k4IcbOI|y*ȩ 2} 6H䍶!amsek,\^N1,W6!o.?YH|[`kOK|C/[]}~~h'-guhaT;TW-W W^vyڊ'ͭ|Umkyj+K|UC6T[~@ـazۓGꂾ _3k_/G%*Z??&g?fxR*({ +ͭ|Umk`97> _3k_/G%*Z?IO%*Z?-W QO? }\$>-E5%*Zķ:-!զHY^9/de`G pEok^fyPK`o+^  value.jpgJFIFHHC  !"$"$CZ" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?>xC— |9wym6Ylgv( $Otx/- "j%^U+XTĜ3_ ZE7Rп/&K<2G!z ^&nʣψsx/- "h)h_SzQ˟![{L_{<] )h_G KBӦi˜85b3gR_{=*Ug=7Rп/&AB5mFfQ^&Nʤk(9AB4 /s=O(e?/~?o. /oХ_Mi4]U^g{= >'Þ7H_ ZE3_gKO O;,vQ#8 k$? ueCNGD$wIWo ;$ uu͘6)L*ju_UHWsH8>=EIq;Qצϋ\dt6ў?QZs#"6Ϣ*RpeuN?SZpd0^ZJ2*Kh=O?Jn.RQ]M]:?.sվcV( Q^d3pP]O_JS`W\/%^)Uk.|(hJ-`WW\%^U+1{)~l'0+;XwMg~m|d}kpm֏%ne ڰ̵Cp ŭ N^#𧈍qeUy+-޿jZZ?SYnVmW/` >^S[V+FŕQDQEqN|bU\>1*O?]w/GECAwIWo ;$ utf?u/̈́&QEqt"V@9SKGO.pj+3NnR] :oEPI$rOZ(IGRno=~mʂ8ڪ:D{biOV8J\UyIt{B(OH+$? uu|bU\2_抇ă *@)wIWo _ L($)6Cц)QMYV*zIW5HojJgWTp}Af 9$R`D^1 DʛNEԅq*1N^( ((+$? uu|bU\2_抇ă *@Z&}'X?ƏJ|OCr%O>ࢾ1' Oc?6OW/>gW=cd?)? z4%ǬlG%>'X?ƏcCpQ_S h1'{3 +J|OC=cd?{c}\/%^)UkJ|OCS