//
import java.awt.*; // Panel, Button, Label, TextArea, TextField, Color, etc.
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
// Compilable in Java 1.1.8 for compatibility with Internet Explorer.
// The main differences:  java.awt.* for javax.swing.*; no nextInt(N);
// lose the "J" on component types; TextAreas have the scrollbar built-in; 
// Auction.init() uses "this." instead of "this.getContentPane().";

class AuctionApp
{	
   public static void main (String[] args)
   {	Frame game = new Frame ("AUCTION by William C. Jones");
	game.setSize (750, 560);  // larger than actual applet -- allow for edges
	game.addWindowListener (new WindowCloser());
	Auction display = new Auction();
	display.init();
	game.add (display);
	game.setVisible (true);
   }

   private static class WindowCloser extends java.awt.event.WindowAdapter
   {	public void windowClosing (java.awt.event.WindowEvent e)
	{	System.exit (0);
	}
   }
}

/* The MAIN part of the software (the init method):  22 classes plus 2 above 

	The MAIN part creates the various MODEL, VIEW, and CONTROLLER objects
	used by the software.  12 CONTROLLER classes are defined on pp3-7, as
	inner classes of Auction (to give them access to MODEL and VIEW objects).
	The Auction class (pp1-7) uses the 4 VIEW classes Player (pp8-9, Panel), 
	BidPrompter (p10, Panel), RentLabel (p11, Label), Property (p11, Label); 
	plus the 4 MODEL classes GameStatus (p12), TwoPlayers (p13), TotalGames 
	(p14), and PropList (pp14-16). Those 8 classes are independent of each 
	other except TwoPlayers uses Player, Player uses Property, PropList uses 
	Property & RentLabel. PropList also uses the MODEL Bid class (pp16-19). */

public class Auction extends java.applet.Applet // every Applet is a Panel
{
   public static final int UNIT = 5;  // difference between 2 consec. bids
   public static final int BREAK = 5; // separate first 5 from last 4
   public static final String REPEAT = "repeat game";

   // VIEW objects
   private Panel boardPanel = new Panel();
   private Panel biddingPanel = new Panel();
   private TextArea output = new TextArea (11, 93);
   private TextArea area = new TextArea (15, 75);
   private TextArea crutch = null;  // an option changes this to area
   private BidPrompter bidPrompt = new BidPrompter(); // extends Panel
   private Player humanPanel = new Player ("HUMAN");  // extends Panel
   private Player compyPanel = new Player ("COMPY");  // extends Panel
   private TwoPlayers twoGuys = new TwoPlayers (humanPanel, compyPanel);


   // MODEL objects
   private GameStatus status = new GameStatus();
   private TotalGames gameCounter = new TotalGames();  // tracks num games
   private PropList propList = new PropList();	// tracks 20 properties


   public void init()  // only on initial setup, not for additional games
   {	boardPanel.setLayout (new GridLayout (4, 7, 6, 4));  
	boardPanel.setBackground (Color.white);
	addManyComponentsToBiddingPanel();
	Panel bottom = new Panel();
	bottom.setLayout (new GridLayout (1, 2, 40, 0));
	bottom.add (humanPanel);
	bottom.add (compyPanel);
	this.setLayout (new BorderLayout (2, 2));
	this.add (boardPanel, BorderLayout.NORTH);
	this.add (biddingPanel, BorderLayout.CENTER);
	this.add (bottom, BorderLayout.SOUTH);
	startNewGame ("");
   }

   private void addManyComponentsToBiddingPanel() // only called by init()
   {	biddingPanel.setBackground (Color.yellow);
	biddingPanel.add (bidPrompt);     // using FlowLayout
	biddingPanel.add (newButton ("  bid  ", new MakeBidAl()));
	biddingPanel.add (newButton ("pass", new PassBidAL()));
	biddingPanel.add (newButton (UNIT +" higher", new HigherAL()));
	biddingPanel.add (newButton (UNIT +" lower", new LowerAL()));
	biddingPanel.add (newButton ("start new game", new NewGameAL()));
	biddingPanel.add (newButton (REPEAT, new NewGameAL()));
	biddingPanel.add (newButton ("options", new MoreOptionsAL()));
	biddingPanel.add (output);
	output.addKeyListener (new Responder());
	output.setEditable (false);  // I'll echo user's keys myself
	output.setBackground (new Color (240, 255, 240)); // pastel green
	output.append ("THE AUCTION GAME   by William C. Jones, Jr.\n"
			+ "Objective:  At CASH IN, to have more total value "
			+ "(property@$100...$300 plus cash) than compy.\n"
			+ "The hint is the bid that compy thinks the property "
			+ "is worth (though bids must be multiples of 5).\n"
			+ "If you pass, compy takes it for the hint, "
			+ "rounded down to a multiple of 5 if necessary.\n"
			+ "If you bid what is shown after compy's first bid, "
			+ "you get it for that higher bid.\n"
			+ "If you bid what is shown for your first bid, you "
			+ "get it for that or else compy takes it for 5 more;\n"
			+ "     if the hint ends in 3,4,8,or 9, raise your bid " 
			+ "5 higher to be sure to get the property.\n");
   }

   public Button newButton (String label, ActionListener alis)
   {	Button but = new Button (label);
	but.addActionListener (alis);
	return but;
   }


/* The CONTROL part of the software -- 11 ActionListeners + Keys on 5 pages */

   private class NewGameAL implements ActionListener // click 1 of 2 buttons
   {	public void actionPerformed (ActionEvent ev)
	{	int numSold = propList.numSold();
		if (numSold >= PropList.FIRST_GROUP && numSold < PropList.MAX) 
		{	int score = twoGuys.score();
			int cash = humanPanel.cash() - compyPanel.cash();
			if ((score > 60 && cash > 0) || (score < -60 && cash < 0))
				output.append (gameCounter.updateTotals (score));
		}
		boardPanel.removeAll();
		humanPanel.removeAll();
		compyPanel.removeAll();
		startNewGame (((Button) ev.getSource()).getLabel());
	}
   }

  /** startNewGame resets all variables and displays for the start of a 
	 new game. This may occur in the middle of the current game. 
       Called by init() and also by clicking a NewGameAL button. */

   public void startNewGame (String newGame)
   {	if ( ! newGame.equals (REPEAT))  // no repeat for first game
	{	int id = status.nextGameID();
		propList.shuffleToRandomize (id);
		newGame = "Game " + id;
	}
	twoGuys.makeHumanFirst (true);
	addPropertiesToTopPanel();
	int initial = status.getInitialCash();
	propList.resetIterator (initial);
	humanPanel.addHoldingsToPanel (initial, 0);
	compyPanel.addHoldingsToPanel (initial, status.getLoan());
	int best = propList.chooseBestBid (true, humanPanel.cash(), 
						compyPanel.cash(), 0, crutch);
	bidPrompt.updateForNewBestBid (true, false, false, best);
	int val = best * 100 / ((Property) propList.getNext()).getCost();
	output.append ("\n" + newGame + ": The first " + PropList.FIRST_GROUP
		+ " properties total $" + propList.getTotalOfFirstGroup() 
		+ ".  Compy bids " + val + " on 100s, " + (2 * val) 
		+ " on 200s, and " + (3 * val) + " on 300s.\n");
	biddingPanel.validate();  // re-layout components
   }

   private void addPropertiesToTopPanel()  // only called by startNewGame()
   {	propList.resetIterator (0);
	boardPanel.add (new Label ("Up for bid ==>"));
	for (int k = 0;  k < PropList.TOTAL;  k++)
		boardPanel.add (propList.next());
	boardPanel.add (new Label (""));
	boardPanel.add (new Label (""));  // so 28 labels = 7 x 4
	boardPanel.validate();    // re-layout components
   }



   private class LowerAL implements ActionListener // click "5 lower"
   {	public void actionPerformed (ActionEvent ev)
	{	if (bidPrompt.getDisplayedBid() > 0)
			bidPrompt.changeDisplayedBid (-UNIT);
	}
   }

   private class HigherAL implements ActionListener // click "5 higher"
   {	public void actionPerformed (ActionEvent ev)
	{	int bid = bidPrompt.getDisplayedBid();
		if (bid <= twoGuys.getFirst().cash()
						&& bid < twoGuys.getSecond().cash())
			bidPrompt.changeDisplayedBid (UNIT);
	}
   }

   private class Responder extends java.awt.event.KeyAdapter 
	// respond to certain chars typed inside textarea
   {	public void keyTyped (java.awt.event.KeyEvent ev)
	{	char key = ev.getKeyChar();
		if (key == 'b')
			new MakeBidAl().actionPerformed (null);
		else if (key == 'n')
			new PassBidAL().actionPerformed (null);
		else if (key == 'm')
		{	new HigherAL().actionPerformed (null);
			new MakeBidAl().actionPerformed (null);
		}
		else if (key == 's')
			new NewGameAL().actionPerformed 
				(new ActionEvent (new Button ("start"), 0, null));
		else if (key == 'r')
			new NewGameAL().actionPerformed 
				(new ActionEvent (new Button (REPEAT), 0, null));
	}
   }

   private class PassBidAL implements ActionListener // click "pass"
   {	public void actionPerformed (ActionEvent ev)
	{	if ( ! (propList.getNext() instanceof Property))
			return;
		twoGuys.makeHumanFirst (true);
		output.append (propList.numSold() == BREAK ? ".p" : "p");
		compyPanel.makeSale (propList.next(), boardPanel, 
				twoGuys.bothAreReal() ? bidPrompt.getDisplayedBid() 
					: bidPrompt.getBestBid() / UNIT * UNIT);
		prepareForNextTurn();
	}
   }








   private void prepareForNextTurn() // only called by PassBidAL or MakeBidAl
   {	if ( ! (propList.getNext() instanceof Property))
	{	((RentLabel) propList.getNext()).flashIntermittently();
		if (propList.numSold() >= PropList.MAX) 
		{	output.append (gameCounter.updateTotals (twoGuys.score())); 
			return;
		}
		payOutRentAndShowResults();  // removes the RENT label
	}
	boolean humanFirst = twoGuys.getFirst() == humanPanel;
	int best = propList.chooseBestBid (humanFirst, 
			twoGuys.getFirst().cash(), twoGuys.getSecond().cash(), 
			compyPanel.prop() - humanPanel.prop(), crutch);
	bidPrompt.updateForNewBestBid (humanFirst, humanPanel.cash() <= best, 
						 compyPanel.cash() <= best, best);
	boardPanel.validate();    // re-layout components
   }

   private void payOutRentAndShowResults() // only called by prepareForNextTurn
   {	boardPanel.remove (propList.next());
	humanPanel.updateForIncomeReceived (-1);
	compyPanel.updateForIncomeReceived (-1);
	output.append (((propList.numSold() > PropList.FIRST_GROUP) 
			? "            " : "") + "   After " + propList.numSold() 
		+ ", human: rent= " + (humanPanel.prop() / 10) + ", cash= " 
		+ humanPanel.cash() + ";  compy: rent= " + (compyPanel.prop() / 10) 
		+ ", cash= " + compyPanel.cash() 
		+ ((status.getLoan() == 0) ? "" : ", loan= " + (-status.getLoan()))
		+ ";   human " + ((twoGuys.score() >= 0)
					? "is ahead by " + twoGuys.score() 
					: "is losing by " + (-twoGuys.score())) + ".\n");
   }

   private class MakeBidAl implements ActionListener // click "bid"
   {	public void actionPerformed (ActionEvent ev)
	{	if ( ! (propList.getNext() instanceof Property))
			return;
		int bid = bidPrompt.getDisplayedBid();
		int price = twoGuys.compyPayment (bid, bidPrompt.getBestBid());
		output.append ((propList.numSold() == BREAK ? "." : "") 
				+ bidPrompt.echo (price >= 0, ! twoGuys.bothAreReal()));
		if (price >= 0) 
			compyPanel.makeSale (propList.next(), boardPanel, price);
		else
			humanPanel.makeSale (propList.next(), boardPanel, bid); 
		twoGuys.makeHumanFirst (price >= 0);
		prepareForNextTurn();
	}
   }








   private class MoreOptionsAL implements ActionListener // click "options"
   {	public void actionPerformed (ActionEvent ev)
	{	final Frame opt = new Frame ("MORE OPTIONS FOR AUCTION");
		opt.setLayout (new FlowLayout());
		opt.setSize (600, 360);
		opt.setBackground (new Color (255, 248, 248));
		opt.add (new Label ("Enter a game ID number or initial cash"));
		TextField field = new TextField (10);
		field.addActionListener (new SetStatusAL());
		opt.add (field);
		opt.add (newButton ("vs person   ", new SwitchOpponentAL()));
		opt.add (newButton ("gotcha!", new GotchaAL()));
		opt.add (new Label ("loan compy interest-free?"));
		opt.add (newButton ("loan $50", new MakeItHarderAL()));
		opt.add (newButton ("use the crutch", new DisplayChoiceTreeAL()));
		opt.add (area);
		opt.setVisible (true);
		field.requestFocus();
		opt.addWindowListener (new java.awt.event.WindowAdapter()
		{	public void windowClosing(java.awt.event.WindowEvent ev)
			{	opt.setVisible (false);
			}
		});
	}
   }

   private class MakeItHarderAL implements ActionListener // click "loan 50"
   {	public void actionPerformed (ActionEvent ev)
	{	status.increaseLoan (50);
		area.append ("Compy has an advantage of " + status.getLoan() +"\n");
	}
   }

   private class DisplayChoiceTreeAL implements ActionListener // click "crutch"
   {	public void actionPerformed (ActionEvent ev)
	{	crutch = area;
		area.append ("For each group of 3 properties between RENTs, you "
			+ "have up to 8 different sequences\n of 3 choices.  We list " 
			+ "here all of your choices and their consequences.\n"
			+ "The listing is 4 columns; an entry such as 200C80 in "
			+ "the first 3 columns\n means that on a $200 property Compy "
			+ "buys it for $80.\n The fourth column tells how much "
			+ "cash the two players (H and C) are left with after that \n"
			+ " sequence of choices. It also tells the net change in "
			+ "your cash and property holdings.\n\n");
	}
   }

   private class SwitchOpponentAL implements ActionListener // click "vs..."
   {	public void actionPerformed (ActionEvent ev)
	{	((Button) ev.getSource()).setLabel ("vs " + twoGuys.opponent());
		twoGuys.toggleOpponent();
		area.append ("You are now playing against a " + twoGuys.opponent()
					+ ".\n");
	}
   }

   private class SetStatusAL implements ActionListener // for options TextField
   {	public void actionPerformed (ActionEvent ev)
	{	TextField input = (TextField) ev.getSource();
		try
		{	int num = Integer.parseInt (input.getText());
			if (num < 100)
				area.append ("INVALID: " + num + "\n");
			else if (num < 1000)  // 3 digits
			{	status.setInitialCash (num);
				area.append ("START = $" + num + "\n");
			}
			else  // 4 or more digits
			{	status.setGameID (num);
				area.append ("NEW GAME ID = " + num + "\n");
			}
		}
		catch (RuntimeException e)
		{ 	int num = Math.abs (input.getText().hashCode());
			status.setGameID (num);
			area.append ("numerically, NEW GAME ID = " + num + "\n");
		}
		input.setText ("");
		input.requestFocus();
	}
   }

   private class GotchaAL implements ActionListener // click "gotcha"
   {	public void actionPerformed (ActionEvent ev)
	{	Player seller = twoGuys.getFirst();  // because he lost the bid
		Player buyer  = twoGuys.getSecond();
		int price = buyer.getPriceOfLastBuy();
		if (! twoGuys.bothAreReal() || buyer.cash() < price)
			return;
		Label matching = seller.getMatchFor (buyer.lastPurchaseInitial());
		if (matching != null)
		{	area.append ("Distress sale of " + matching.getText()
						+ " for a price of " + price + ".\n");
			buyer.makeSale (matching, seller, price);
			seller.updateForIncomeReceived (price);
			seller.validate();
		}
	}
   }
} // END OF Auction CLASS
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

   /* Version 1.1 (a) allow words as gameIDs in SetStatusAL (p7); 
	(b) correct toString (p19) to let Human pass e.g. comCash=75, best=79
	(c) correct BidPrompter (p10) to keep width of promptForBid constant
   */






/* The VIEW part of the software:  4 classes on 4 pages: 
		Player (Panel), BidPrompter (Panel), 
		RentLabel (Label), Property (Label) 

	A VIEW object should only have methods that create displayable objects,
	display info on them, and report on the info currently on display.
	The Player class however is not pure VIEW -- in addition to the display 
	info on assets of cashAmt and propAmt, it keeps track of two related
	numbers: loanAmt (a claim against assets) and priceOfLastBuy 
	(the difference between	the previous cashAmt and the current cashAmt).
*/


class Player extends Panel 
{
   private Label cashLabel;	// heading on first column of properties
   private Label propLabel;	// heading on second column of properties
   private String cashTag;	// self-explanatory note on the cashLabel
   private String propTag;	// self-explanatory note on the propLabel
   private int cashAmt = 0;	// amount of cash currently on hand
   private int propAmt = 0;	// value of property currently owned
   private int loanAmt = 0;	// amount to be repaid at the end of the game
   private int priceOfLastBuy = 100000;	// the last property purchase made

   private static final int NUM_BLANKS = 9; // blank labels to fill out grid
   private Label[] blanks = new Label [NUM_BLANKS];	// added as filler
   private int size = NUM_BLANKS;	// number of blanks currently on this panel

   public Player (String whoIsIt) // only called once
   {	cashTag = whoIsIt + " CASH: ";
	propTag = whoIsIt + " PROPERTY: ";
	cashLabel = new Label (cashTag + "000");
	propLabel = new Label (propTag + "0000");
	setLayout (new GridLayout (8, 3, 6, 4));
	for (int k = 0;  k < NUM_BLANKS;  k++)
		blanks[k] = new Label ("");
	setBackground (new Color (240, 240, 255));
   } 

   /** Called once at the beginning of each game. */

   public void addHoldingsToPanel (int initialCash, int loan) 
   {	propAmt = 0;
	cashAmt = initialCash + loan;
	loanAmt = loan;
	priceOfLastBuy = 100000;   // just so it is initially larger than any cash

	propLabel.setText (propTag + propAmt);
	cashLabel.setText (cashTag + cashAmt);
	this.add (propLabel);
	this.add (cashLabel);
	size = NUM_BLANKS;
	for (int k = NUM_BLANKS - 1;  k >= 0;  k--)
		this.add (blanks[k]);
	validate();  // re-layout components
   }
   public int cash()
   {	return cashAmt;
   }

   public int prop()
   {	return propAmt;
   }

   public int assets()
   {	return cashAmt + propAmt - loanAmt;
   }

   public int getPriceOfLastBuy()
   {	return priceOfLastBuy;
   }

   public void makeSale (Label prop, Panel boardPanel, int priceOfProp)
   {	cashAmt -= priceOfProp;
	propAmt += ((Property) prop).getCost();
	priceOfLastBuy = priceOfProp; 

	if (size == 0)
		this.add (prop);
	else
	{	this.add (prop, 2 + NUM_BLANKS - size); // 2,3,...
		boardPanel.add (blanks[--size]);   // also removes from own
	}
	cashLabel.setText (cashTag + cashAmt);
	propLabel.setText (propTag + propAmt);
	this.validate();  // re-layout components
   }

   public void updateForIncomeReceived (int increase) 
   {	cashAmt += (increase >= 0)  ?  increase  :  propAmt / 10;
	cashLabel.setText (cashTag + cashAmt);
   }

   public char lastPurchaseInitial() // only called by GotchaAL
   {	Component[] data = this.getComponents();
	int pos = data.length - 1;
	while (! (data[pos] instanceof Property))//skip blank labels
		pos--;
	return ((Property) data[pos]).getText().charAt (4);
   }

   public Label getMatchFor (char chr) // only called by GotchaAL
   {	Component[] data = this.getComponents();
	for (int pos = data.length - 1;  pos >= 0;  pos--)
	{	if (data[pos] instanceof Property 
				&& ((Property) data[pos]).getText().charAt (4) == chr)
			return (Label) data[pos];
	}
	return null;
   }
}// END OF Player CLASS
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
class BidPrompter extends Panel
{
   public static final String HUM_DOWN = "            You first; bid this?";
   public static final String HUM_UP = "You first; this or more?";
   private Label hintLabel = new Label ("hint=000");
   private Label promptForBid;    // tells human what the bid choices are
   private Label currentBid = new Label ("000");  // current or suggested bid
   private int displayedBid = 0;   // the bid showing in the currentBid label
   private int bestBid = 0;         // best estimate of amount to bid
   private boolean bidHasBeenRaised = false;

   public BidPrompter()
   {	super();
	promptForBid = new Label (HUM_UP, Label.RIGHT);
	add (hintLabel);     // using FlowLayout
	add (promptForBid);
	add (currentBid);
   }

   public void updateForNewBestBid (boolean humanFirst, boolean humanIsBroke, 
					boolean compyIsBroke, int best)
   {	bestBid = best;
	displayedBid = bestBid / Auction.UNIT * Auction.UNIT;
	boolean goUp = bestBid > displayedBid + Auction.UNIT / 2;
	hintLabel.setText ( (goUp && compyIsBroke) ? "CHEAP!"
				: (goUp && humanIsBroke) ? "GONE!"
				: "hint=" + bestBid);
	if (humanFirst)
		promptForBid.setText ((goUp && ! compyIsBroke) ? HUM_UP : HUM_DOWN);
	else
	{	promptForBid.setText ("Compy bids " + displayedBid
				+ ((goUp && ! humanIsBroke) ? "; go up?" : "; you?"));
		displayedBid += Auction.UNIT;
	}
	currentBid.setText ("" + displayedBid);
	bidHasBeenRaised = false;
   }

   public int getBestBid()
   {	return bestBid;
   }

   public int getDisplayedBid()
   {	return displayedBid;
   }
   public void changeDisplayedBid (int change)
   {	displayedBid += change;
	currentBid.setText ("" + displayedBid);
	bidHasBeenRaised = change > 0;
   }

   public String echo (boolean humanLostBid, boolean vsComputer)
   {	return humanLostBid ? "b" : (vsComputer && bidHasBeenRaised) ? "M" : "B";
   }
} // END OF BidPrompter CLASS
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

class RentLabel extends Label  
{
   public RentLabel (String name)
   {	super (name, Label.CENTER);
	setBackground (Color.magenta);
   }

   public void paint (Graphics page)
   {	page.setColor (Color.blue);
	Dimension dim = this.getSize();   // required in Java 1.1, not getHeight()
	int bottom = dim.height;
	int reps = (dim.width - 2) / 8;
	int x = (dim.width - 8 * reps) / 2;
	page.drawLine (x, 1, x, bottom - 1);
	for (;  x < 8 * reps;  x += 8)
	{	page.drawLine (x, 1, x + 4, 5);
		page.drawLine (x + 4, 5, x + 8, 1);
		page.drawLine (x, bottom - 1, x + 4, bottom - 5);
		page.drawLine (x + 4, bottom - 5, x + 8, bottom - 1);
	}
	page.drawLine (x, 1, x, bottom - 1);
   }
   public void flashIntermittently()  // only called from prepareForNextTurn
   {	long later = System.currentTimeMillis() + 1000;  // 1 second
	while (later > System.currentTimeMillis())
	{	this.setBackground (this.getBackground() == Color.white
				? Color.blue : Color.white);
		for (int k = 0;  k < 6000000;  k++)
		{ }
	}
	this.setBackground (Color.magenta);
   }
}
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

class Property extends Label
{
   private int itsCost;

   public Property (int givenCost, String givenName)
   {	super (givenCost + "   " + givenName);
	itsCost = givenCost;
   }

   public void paint (Graphics page)
   {	page.setColor (Color.black);
	Dimension dim = this.getSize();   // required in Java 1.1, not getHeight()
	page.drawRect (1, 1, dim.width - 2, dim.height - 2);
   }
   public int getCost()
   {	return itsCost;
   }
}
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

/* The MODEL part of the software:  5 classes on 8 pages: 
		GameStatus, TotalGames, TwoPlayers, PropList, Bid
	A MODEL object store non-IO info.  They should not have methods that 
	obtain input from the user (input comes via parameters) or produce output 
	directly (instead, return a value or maybe send messages to IO objects).
*/


class GameStatus extends Object // tracks status variables set once each game
{
   private int initialCash = 600 + new java.util.Random().nextInt() % 3 * 50;
		// nextInt()%3 is -2 to 2, so the result is 500/550/600/650/700
   private int compyLoan = 0;
   private int gameID = 0; 
   private boolean haveUsedGameID = true;

   public void increaseLoan (int num)	// only called by MakeItHarderAL
   {	compyLoan += num;
   }

   public int getLoan()
   {	return compyLoan;
   }

   public String showLoan()
   {	return (compyLoan == 0) ? "" : ", loan= " + (-compyLoan);
   }

   public void setGameID (int num)		// only called by SetStatusAL
   {	gameID = num;
	haveUsedGameID = false;
   }

   public int nextGameID()
   {	if (haveUsedGameID)
		gameID = 55000 + new java.util.Random().nextInt() % 45000;
		// so the result is 10001 through 99999
	haveUsedGameID = true;
	return gameID;
   }

   public void setInitialCash (int num)	// only called by SetStatusAL
   {	initialCash = num;
   }

   public int getInitialCash()
   {	return initialCash;
   }
} // END OF GameStatus CLASS
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$






class TwoPlayers  // tracks which of the two has the first bid
{
   private Player first, second;
   private final Player human, compy;
   private boolean twoRealPeople = false;

   public TwoPlayers (Player one, Player two)
   {	human = one;
	compy = two;
	makeHumanFirst (true);
   }

   public boolean bothAreReal()
   {	return twoRealPeople;
   }

   public String opponent()
   {	return twoRealPeople ? "person" : "computer";
   }
   public void toggleOpponent()
   {	twoRealPeople = ! twoRealPeople;
   }

   public Player getFirst()
   {	return first;
   }
   public Player getSecond()
   {	return second;
   }

   public void makeHumanFirst (boolean yes)
   {	if (yes)
	{	first = human;
		second = compy;
	}
	else
	{	first = compy;
		second = human;
	}
   }

   public int score()
   {	return human.assets() - compy.assets();
   }

   public int compyPayment (int bid, int best) // -1 if compy doesn't buy it
   {	int half = Auction.UNIT / 2;  // half is 2 if UNIT is 5
	int low = best / Auction.UNIT * Auction.UNIT;  // rounding down
	int p = (first == human && best > low + half) ? low + Auction.UNIT : low;
	return twoRealPeople ? -1 : (bid > human.cash()) ? p : (bid > best) ? -1
			: (bid < low || first == compy) ? p
			: (bid >= best - half || bid >= compy.cash()) ? -1 : p;
   }
} // END OF TwoPlayers CLASS
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$

class TotalGames  // tracks multiple games and totals
{
   private int itsNumGames = 0;
   private int itsTotalScore = 0;
   private int itsNumWon = 0;

   public String updateTotals (int score)
   {	itsTotalScore += score;
	itsNumGames++;
	if (score > 0)
		itsNumWon++;
	return "  ==> GAME OVER: Your score =  " + score 
		 + (itsNumGames == 1 ? ".\n" 
			: ".   You won " + itsNumWon + " out of " + itsNumGames 
				+ "; your average is " 
				+ (itsTotalScore / itsNumGames) + ".\n");
   }
} // END OF TotalGames CLASS
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$


class PropList
{
   public static final int MAX = 20;   // number of properties
   public static final int TOTAL = MAX + 5;  // allow for 4 rents + 1 cash-in
   public static final int FIRST_GROUP = 9;
   public static final int UNIT = Auction.UNIT;

   private Label[] allData = new Label [TOTAL];
   private Property[] itsProperty;
   private Label[] fixed = {new RentLabel ("COLLECT RENT"),	
	     new RentLabel ("COLLECT RENT"), new RentLabel ("COLLECT RENT"), 
	     new RentLabel ("COLLECT RENT"), new RentLabel ("CASH IN")};
   private int nextAvailable = 0;
   private int totalOfFirstGroup;
   private int valueOfFirstGroup;

   public PropList()
   {	itsProperty = new Property[MAX];
	String[] name = {"St.Louis RR", "SantaFe RR", "B&O RR", "Boston RR",
				"Arizona", "Alabama", "Connecticut", "California", 
				"Illinois", "Indiana", "Maryland", "Mass.", 
				"New York", "New Jersey", "Oregon", "Oklahoma",
				"Tennessee", "Texas", "Virginia", "Vermont"};
	int[] cost = {100, 100, 100, 100, 100, 100, 100, 100, 200, 200, 200, 200, 
			  200, 200, 200, 200, 300, 300, 300, 300};
	final Color RED = new Color (255, 96, 96);
	final Color GRAY = new Color (216, 216, 216);
	for (int k = 0;  k < MAX;  k++) // inefficient but only done once per game
	{	itsProperty[k] = new Property (cost[k], name[k]);
		itsProperty[k].setBackground (k < 4 ? GRAY : k < 8 ? Color.cyan 
				: k < 12 ? Color.orange : k < 16 ? RED : Color.green); 
	}
   }


   public void resetIterator (int initial)
   {	nextAvailable = 0;
	valueOfFirstGroup = Math.min (64 + Math.max (0, (initial - 650) / 18), 
			initial * 200 / (totalOfFirstGroup + 505 + 
			(1500-totalOfFirstGroup) / 100 * (1000-initial) / 20));
   }

   public Label next()
   {	return allData[nextAvailable++];
   }

   public Label getNext()
   {	return allData[nextAvailable];
   }

   public int numSold() // returns 9, 12, 15, 18 on 10, 14, 18, 22
   {	return nextAvailable <= FIRST_GROUP ? nextAvailable
			: (nextAvailable + 2) - (nextAvailable + 2) / 4;
   }


   /** Produce a random ordering of the 20 properties at the beginning of
       a new game.  Also calculate the total value of the first 9. */

   public void shuffleToRandomize (int givenSeed)
   {	java.util.Random randy = new java.util.Random (givenSeed);
	Property[] copy = new Property[MAX];
	System.arraycopy (itsProperty, 0, copy, 0, MAX);
	for (int k = 0;  k < MAX;  k++)
	{	int spot = k + Math.abs (randy.nextInt()) % (MAX - k);
		Property temp = copy[spot];
		copy[spot] = copy[k];
		copy[k] = temp;
	}

	totalOfFirstGroup = 0;
	for (int k = 0;  k < FIRST_GROUP;  k++)
		totalOfFirstGroup += copy[k].getCost();

	int pos = 0;
	for (int k = 0;  k < MAX;  k++)
	{	allData[pos++] = copy[k];
		if (pos == 9 || pos == 13 || pos == 17 || pos == 21)
			allData[pos++] = fixed[(pos - FIRST_GROUP) / 4];
	}
	allData[pos] = fixed[4];
   }

   public int getTotalOfFirstGroup()
   {	return totalOfFirstGroup;
   }






   public int chooseBestBid (boolean humanFirst, int firstBidder, int other,
					int comPlus, TextArea options)
   {	int cost = ((Property) allData[nextAvailable]).getCost();
	if (nextAvailable < FIRST_GROUP)  // so in the first group of 9
	{	if (nextAvailable == 5 && Math.abs (comPlus) >= 200)
			valueOfFirstGroup -= UNIT * comPlus / Math.abs (comPlus);
		return Math.min (valueOfFirstGroup * cost / 100,
					Math.min (firstBidder, other) + UNIT - 1);
	}
	if (nextAvailable < 22)  // reduce to $75 per 100 except last group
		cost = cost * 3 / 4;
	if ( ! (allData[nextAvailable + 1] instanceof Property)) // one left
		return Bid.bestBid (firstBidder, other, cost).itsBid;
	int b = ((Property) allData[nextAvailable + 1]).getCost() * 3
			/ (nextAvailable < 22 ? 4 : 3);
	if ( ! (allData[nextAvailable + 2] instanceof Property)) // two left
		return Bid.bestBid (firstBidder, other, cost, b).itsBid;
	int c =  ((Property) allData[nextAvailable + 2]).getCost();
	Bid firstOf3 = Bid.bestBid (firstBidder, other, cost, b, c * 3 / 4);
	if (options != null && humanFirst)
		options.append (firstOf3.toString (humanFirst, firstBidder, other));
	else if (options != null)
		options.append (firstOf3.toString (humanFirst, other, firstBidder));
	return firstOf3.itsBid;
   }
}// END OF PropList CLASS
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$



class Bid  // only used by chooseBestBid() in PropList
{
   /** For "me" vs "him":  "me" denotes the one who has first bid. */

   public static final int UNIT = Auction.UNIT;  // for convenience
   private static final Bid EMPTY_BID = new Bid (-101, 10000, 100);

   public final int itsBid;     // always non-negative
   private Bid bestBidIfKept;   // not used by bestBid()
   private Bid bestBidIfLost;   // not used by bestBid()
   private int itsGain;  // gain for whoever is first; thus it may be negative
   private int itsProp;  // always 100 or 200 or 300


   /** A bid on the last property.  Precondition:  itsBid % 5 is 3 or 4 */

   public Bid (int bid, int gain, int prop) // only called by 3-param bestBid()
   {	itsBid = bid; 
	itsProp = prop;
	bestBidIfKept = EMPTY_BID; 
	bestBidIfLost = EMPTY_BID;
	itsGain = gain;
   }



   /** ONE property left.  THREE-parameter bestBid calls THREE-parameter Bid 
	constructor. It is me's bid first. All parameters are non-negative. */

   public static Bid bestBid (int me, int him, int current)
   {	if (me >= current && him >= current)
		return new Bid (current - 1, 0, current);
	else if (him > me) // so me < current
		return new Bid (me + UNIT - 1, me + UNIT - current, current);
	else // so him <= me && him < current
		return new Bid (him + UNIT - 1, current - him, current);
   }

   /** A bid on the next-to-last of two properties before a RENT. 
	Precondition: -5 <= estimate <= minimum of other 3 parameters. */

   public Bid (int estimate, int me, int him, int one, int two)
   {	itsBid = estimate;  // always a multiple of 5
	itsProp = one;
	bestBidIfKept = bestBid (him, me - estimate, two);
	bestBidIfLost = (him <= estimate) ? EMPTY_BID  // gain of infinity
					: bestBid (me, him - estimate - UNIT, two);
	itsGain = Math.min (itsProp - itsBid - bestBidIfKept.itsGain,
			UNIT + itsBid - itsProp + bestBidIfLost.itsGain);
   }

   /** The best bid revised for an estimate 4 higher (to be overbid). */

   public Bid (Bid basis)
   {	this.itsBid = basis.itsBid + 4;
	this.itsProp = basis.itsProp;
	this.bestBidIfKept = basis.bestBidIfKept;
	this.bestBidIfLost = basis.bestBidIfLost;
	this.itsGain = basis.itsGain;
   }

   private int gainIfKept()
   {	return itsProp - itsBid - bestBidIfKept.itsGain;
   }

   private int gainIfLost()  // infinity if opponent cannot overbid 
   {	return itsBid + UNIT - itsProp + bestBidIfLost.itsGain;
   }











   /** TWO properties left.  FOUR-parameter bestBid calls FIVE-parameter Bid 
	constructor, which in turn calls THREE-parameter bestBid. */

   public static Bid bestBid (int me, int him, int one, int two)
   {	int estimate = Math.min (him, Math.min (me, one));
	Bid high = new Bid (estimate, me, him, one, two);
	if (estimate < one && high.gainIfLost() < high.gainIfKept()) 
		return new Bid (high);
	estimate -= UNIT;
	Bid lower = new Bid (estimate, me, him, one, two);
	while (lower.gainIfLost() > lower.gainIfKept() && estimate >= 0)
	{	high = lower;
		estimate -= UNIT;
		lower = new Bid (estimate, me, him, one, two);
	}
	return (estimate < 0 || high.gainIfKept() >= lower.gainIfLost()) 
			? high : new Bid (lower);
   }

   /** A bid on the third-to-last of three before a RENT. */

   public Bid (int estimate, int me, int him, int one, int two, int three)
   {	itsBid = estimate;
	itsProp = one;
	bestBidIfKept = bestBid (him, me - estimate, two, three);
	bestBidIfLost = (him <= estimate) ? EMPTY_BID  // gain of infinity
					: bestBid (me, him - estimate - UNIT, two, three);
	itsGain = Math.min (itsProp - itsBid - bestBidIfKept.itsGain,
			UNIT + itsBid - itsProp + bestBidIfLost.itsGain);
   }

   /** THREE properties left.  FIVE-parameter bestBid calls SIX-parameter Bid 
	constructor, which in turn calls FOUR-parameter bestBid. */

   public static Bid bestBid (int me, int him, int one, int two, 
						int three)
   {	int estimate = Math.min (him, Math.min (me, one));
	Bid high = new Bid (estimate, me, him, one, two, three);
	if (estimate < one && high.gainIfLost() < high.gainIfKept()) 
		return new Bid (high);
	estimate -= UNIT;
	Bid lower = new Bid (estimate, me, him, one, two, three);
	while (lower.gainIfLost() > lower.gainIfKept() && estimate >= 0)
	{	high = lower;
		estimate -= UNIT;
		lower = new Bid (estimate, me, him, one, two, three);
	}
	return (estimate < 0 || high.gainIfKept() >= lower.gainIfLost()) 
			? high : new Bid (lower);
   }

   private static final String BLANKS = "                    ";

   public String toString (boolean humanFirst, int humCash, int comCash)
   {	return "human has $" + humCash + "; compy has $" + comCash 
			+ (humanFirst ? "; human" : "; compy") + "'s bid first.  "
			+ "Your choices are...\n"
		+ toString (humanFirst, "", humCash, comCash, 0, 0, 3) + "\n";
   }

   public String toString (boolean humanFirst, String prefix, int humCash, 
					int comCash, int cahead, int pahead, int level)
   {	if (humCash < 0 || comCash < 0)
		return "";  // skip this line, since it is impossible by the rules
	int padding = Math.max (0, 30 - prefix.length());
	if (this == EMPTY_BID)  // only after 3 bids
		return prefix + BLANKS.substring (0, padding)
				+ "  H$" + humCash + ": C$" + comCash + " so cash: "
				+ cahead + ", prop: " + (pahead * 4 / 3) 
				+ ", net= " + (cahead + pahead * 4 / 3) + "\n";

	int loBid = itsBid - itsBid % UNIT;
	boolean goUp = itsBid % UNIT > UNIT / 2;
	int comPaid = humanFirst ? loBid + UNIT : loBid;
	Bid comBid =  humanFirst ? bestBidIfLost : bestBidIfKept;
	int humPaid = humanFirst ? loBid : loBid + UNIT;
	Bid humBid =  humanFirst ? bestBidIfKept : bestBidIfLost;

	// correction to allow for human choosing a subjectively better bid;
	// omit this if-structure to trace the computer's best-bid reasoning.
	if (humanFirst && goUp && comCash > itsBid) // can bid more to get it
	{	humPaid = loBid + UNIT;
		humBid = (level == 1 || humCash < humPaid) ? EMPTY_BID
				 : (level == 2) ?  bestBid (comCash, humCash - humPaid, 
							bestBidIfKept.itsProp)
				 : bestBid (comCash, humCash - humPaid, 
							bestBidIfKept.itsProp, 
							bestBidIfKept.bestBidIfKept.itsProp);
	}
	else if (humanFirst) // can pass to leave compy with it 
	{	comPaid = loBid;
		comBid = (level == 1) ? EMPTY_BID
				 : (level == 2) ?  bestBid (humCash, comCash - comPaid, 
							bestBidIfKept.itsProp)
				 :  bestBid (humCash, comCash - comPaid, 
							bestBidIfKept.itsProp, 
							bestBidIfKept.bestBidIfKept.itsProp);
	}

	char up = (goUp && (humanFirst ? comCash > itsBid : humCash > itsBid)) 
			? '^' : ' ';
	return comBid.toString (true, prefix + (itsProp * 4 / 3) + "C" + comPaid 
			+ up + "  ", humCash, comCash - comPaid, cahead + comPaid,
			pahead - itsProp, level - 1)
		+ humBid.toString (false, prefix + (itsProp * 4 / 3) + "H" + humPaid 
			+ up + "  ", humCash - humPaid, comCash, cahead - humPaid,
			pahead + itsProp, level - 1);
   }
}// END OF Bid CLASS
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
//$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
//