Commit 9fdcbd86 authored by Ari Trachtenberg's avatar Ari Trachtenberg
Browse files

case sensitivity issue

parent 0069ef28
Loading
Loading
Loading
Loading
+0 −521
Original line number Diff line number Diff line
package edu.bu.ec504.spr19.samegame;


import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;

import javax.swing.ButtonGroup;
import javax.swing.JApplet;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;

import edu.bu.ec504.spr19.highScores.highScore;
import edu.bu.ec504.spr19.samegame.Brain.Brain;
import edu.bu.ec504.spr19.samegame.Brain.lazyBrain;
import edu.bu.ec504.spr19.samegame.Brain.simpleBrain;

// Ari's implementation of the "samegnome" game, possibly with a computer-aided solver.

class sg extends GUI implements ActionListener,ItemListener {

	// constants
	static public final int defaultWidth=15, defaultHeight=10; // sizes in terms of numbers of circles
	static public final int defaultWindowWidth=500, defaultWindowHeight=300; // default window size, if run as a standalone application
	static public final int numColors = 3; // the number of colors available for circles
	static public final int displayTime = 1000; // number of milliseconds to display (highlight) a move before actually making it
	static public final String highScoreFile=".highscores.db"; // where high scores are kept
	private static final long serialVersionUID = 1L; // required for serializability

	// fields (all static - only one instance of the game should be running at a time)
	final JLabel regionPoints = new JLabel("0"); // keeps track of the number of selected points
	final JLabel totalPoints = new JLabel("0");  // keeps track of the total number of points so far
	private int width = defaultWidth, height=defaultHeight; // initial width and height of the array of circles
	private SelfAwareCircle circles[][];
	private String highScoreName = null;    // the name to use for the high score

	// ... GUI elements
	private JPanel circlePanel; // the panel containing the circles
	private JPanel textPanel;   // the panel containing scoring information
	// ... ... menu items
	private JMenuItem changeBoard;
	private JMenuItem quitGame;
	private JRadioButtonMenuItem noPlayerMenuItem,simplePlayerMenuItem, lazyPlayerMenuItem, // various automatic player options
		smarterPlayerMenuItem;

	// ... Brain elements
	Brain theBrain;                   // the Brain (automated player) for the game
	Thread brainThread;               // the thread that will run the Brain

	// Public access methods

	/*
	 * Default no-args constructor
	 */
	public sg() {
        super("Ari's samegame");
		try {
			SwingUtilities.invokeAndWait(
					new Runnable() {
						public void run() {
							setupGUI();
						}
					}
			);
		}
		catch (Exception e) { System.out.println("Saw exception "+e); }
	}

	/*
	 * Returns the color of the circle at location [xx][yy], or NONE if the circle has been cleared
	 * @param xx must be between 0 and width
	 * @param yy must be between 0 and height
	 */
	public CircleColor colorAt(int xx, int yy) {
		if (circles[xx][yy].isCleared())
			return CircleColor.NONE;
		else
			return circles[xx][yy].clr;
	}

	/*
	 * Returns the width of the current board
	 */
	public int boardWidth() {
		return width;
	}

	/*
	 * Returns the height of the current board
	 */
	public int boardHeight() {
		return height;
	}

	/*
	 * Returns true iff the game is over
	 * (i.e. every circle is surrounded by cleared circles or circles of a different color)
	 */
	public boolean gameOverQ() {
		for (int xx=0; xx<width; xx++)
			for (int yy=0; yy<height; yy++) {
				if (!circles[xx][yy].isCleared()) {
					CircleColor myColor = circles[xx][yy].getColor();
					// check its neighbors
					if (sameColor(myColor,xx-1,yy) ||
							sameColor(myColor,xx+1,yy) ||
							sameColor(myColor,xx,yy-1) ||
							sameColor(myColor,xx,yy+1))
						return false; // the game has not ended
				}
			}

		return true; // there are no viable moves
	}

	/*
	 * "Clicks" on the circle at location (xx,yy)
	 */
	public synchronized void makeMove(int xx, int yy) {

		circles[xx][yy].mouseEntered(null);  // pretend the mouse was pressed at location (xx,yy)
		try {
			Thread.sleep(displayTime);          // wait a bit for the user to register the move
		} catch (InterruptedException e) { }
		circles[xx][yy].mouseExited(null);
		circles[xx][yy].mousePressed(null);
		circles[xx][yy].mouseReleased(null); // pretend that the mouse button was released at the location
	}

	/**
	 * @return the score achieved from clicking on a region of length <b>level</b>
	 */
	final public int score(int level, CircleColor theColor) {
	    int tmp;

        if (level==1)
            return -10;
        else
            tmp = (int) (level * Math.log(level));
        switch (theColor) {
            case Red:
                return -tmp;
            case Green: case Blue:
                return tmp;
            default:
                return 0;
        }
	}


	/*
	 * setupGUI helper
	 *   - adds to circle[ii][jj] a SelfAwareListener of circle[xx][yy] as 0<=xx<width and 0<=yy<height
	 * @param ii = x coordinate of the circle to whom we will be listening
	 * @param jj = y coordinate of the circle to whom we will be listening
	 * @param xx = x coordinate of the listener circle
	 * @param yy = y coordinate of the listener circle
	 */

	private void addNeighbor(int ii, int jj, int xx, int yy) {
		if ((0<=xx && xx<width) &&
				(0<=yy && yy<height))
			circles[ii][jj].addSelfAwareListener(circles[xx][yy]);
	}

	/**
	 * Setup the initial screen items
	 * NOTE:  must perform cleanUp() first if there is already a GUI set up on the screen
	 */
	void setupGUI() {

		// set the layout style for the whole app
		setLayout(new BorderLayout());

		// ... set up the menus
		JMenuBar menuBar = new JMenuBar();

		// ...... File Menu
		JMenu menu = new JMenu("File");

		changeBoard = new JMenuItem("Change board");
		changeBoard.addActionListener(this);
		menu.add(changeBoard);

		quitGame = new JMenuItem("Quit");
		quitGame.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, ActionEvent.META_MASK));
		quitGame.setMnemonic('Q');
		quitGame.addActionListener(this);
		menu.add(quitGame);

		menuBar.add(menu);

		// ...... Automatic play Menu
		menu = new JMenu("Automatic play");
		ButtonGroup group = new ButtonGroup();
		
		noPlayerMenuItem = new JRadioButtonMenuItem("none");
		noPlayerMenuItem.addItemListener(this);
		noPlayerMenuItem.setSelected(true); // i.e. the default item
		group.add(noPlayerMenuItem);
		menu.add(noPlayerMenuItem);
		menu.addSeparator();
		
		simplePlayerMenuItem = new JRadioButtonMenuItem("Simple player");
		simplePlayerMenuItem.addItemListener(this);
		group.add(simplePlayerMenuItem);
		menu.add(simplePlayerMenuItem);

		lazyPlayerMenuItem = new JRadioButtonMenuItem("Lazy player");
		lazyPlayerMenuItem.addItemListener(this);
		group.add(lazyPlayerMenuItem);
		menu.add(lazyPlayerMenuItem);

		smarterPlayerMenuItem = new JRadioButtonMenuItem("Smarter player");
		smarterPlayerMenuItem.setEnabled(false); // disabled for this version
		smarterPlayerMenuItem.addItemListener(this);
		group.add(smarterPlayerMenuItem);
		menu.add(smarterPlayerMenuItem);
		menuBar.add(menu);

		this.setJMenuBar(menuBar);

		// ... set up the circle panel
		circlePanel = new JPanel(new GridLayout(height,width));
		// allocate circles
		circles = new SelfAwareCircle[width][height];

		// ..... set up some circles
		for (int jj=0; jj<height; jj++) 
			for (int ii=0; ii<width; ii++) 
			{
				CircleColor foreColor = CircleColor.randColor(numColors);

				// establish the button
				circles[ii][jj] = new SelfAwareCircle(foreColor,ii,jj,this);
				circlePanel.add(circles[ii][jj]);
			}
		// set up listeners in the area
		for (int xx=0; xx<width; xx++)
			for (int yy=0; yy<height; yy++) {
				// add all neighbors
				addNeighbor(xx,yy,xx,yy-1);
				addNeighbor(xx,yy,xx,yy+1);
				addNeighbor(xx,yy,xx-1,yy);
				addNeighbor(xx,yy,xx+1,yy);
			}

		add(circlePanel,BorderLayout.CENTER);

		// ... set up the text panel
		textPanel = new JPanel( new FlowLayout());
		textPanel.add(new JLabel("Selected len: "));
		textPanel.add(regionPoints);
		textPanel.add(new JLabel("")); // i.e. blank
		textPanel.add(new JLabel("Total pts: "));
		textPanel.add(totalPoints);
		add(textPanel,BorderLayout.SOUTH);

		validate();
		repaint();
	}

	/*
	 * Performs any clean up actions needed before setting up a new GUI
	 */
	void cleanUp() {
		// Removes elements for garbage collection

		// close down the brain
		highScoreName=null;
		if (theBrain != null)
			theBrain.allDone();

		// score panel
		totalPoints.setText("0");
		textPanel.setVisible(false);
		textPanel=null;

		// circles
		for (int ii=0; ii<width; ii++)
			for (int jj=0; jj<height; jj++) {
				circles[ii][jj].removeMouseListener(circles[ii][jj]);
				circles[ii][jj]=null;
			}

		// circle panel
		circlePanel.setVisible(false);
		circlePanel=null;

	}

	/*
	 * Returns true iff circles[xx][yy] has color theColor, is not cleared, AND (xx,yy) is within the range of the board
	 */
	private boolean sameColor(CircleColor theColor, int xx, int yy) {
		if (xx<0 || yy<0 || xx>=width || yy>=height || circles[xx][yy].isCleared())
			return false;
		else
			return circles[xx][yy].getColor().equals(theColor);
	}

	/*
	 * Moves circle c1 into location c2, leaving c1 as a clear circle that
	 * does not receive mouse events
	 */
	private void moveCircle(SelfAwareCircle orig, SelfAwareCircle dest) {
		// copy the immutable, position independent values
		orig.copy(dest);

		// clear the top item
		orig.setClear();
	}

	/*
	 * Called to request a reshifting of the board (as necessary).
	 *    This should happen if some circles are rendered "clear"ed
	 *    @return true if game continues; false otherwise
	 */

	final boolean shiftCircles() {
		/* start at the bottom and move up ... all cleared circles are
		 *    removed, with upper circles falling into their positions;
		 *    if a column is totally empty, then its rightmost columns
		 *    shift into it
		 */

		// 1.  SHIFT VERTICALLY
		for (int xx=0; xx<width; xx++) {
			int firstClr = height-1;  // the lowest cleared entry in the column
			int firstFull = height-1; // the lowest uncleared entry in the column that has not yet been processed
			boolean moveOn = false;      // set to true in order to move on to the next column
			while (!moveOn) {
				// find the lowest clear entry in the column (if it exists)
				try {
					while (!circles[xx][firstClr].isCleared())
						firstClr--;
				} catch (ArrayIndexOutOfBoundsException e) {
					moveOn = true;
					continue;  // i.e. no cleared circle found in this column --- go to the next column
				}

				if (firstFull > firstClr)
					firstFull = firstClr; // only move items "down" the column

				// find the lowest non-cleared entry in the column (if it exists)
				try {
					while (circles[xx][firstFull].isCleared())
						firstFull--;
				} catch (ArrayIndexOutOfBoundsException e) {
					moveOn = true;
					continue;  // i.e. the whole column is clear --- for now, go to the next column
				}

				moveCircle(circles[xx][firstFull],circles[xx][firstClr]);

				firstFull--; firstClr--; // iterate
			}
		}

		// 2.  SHIFT HORIZONTALLY
		// Check to see if any column is now empty
		// ... this could have been done within the loop above, but it would detract from readability of the code
		boolean emptySoFar=true;       // remains true if all columns seen so far have only cleared circles
		for (int xx=width-1; xx>=0; xx--) {
			boolean allCleared=true;   // remains true if all circles in column xx have been cleared
			for (int yy=0; yy<height; yy++) {
				try {
					if (!circles[xx][yy].isCleared())
						throw new InterruptedException();
				} catch (InterruptedException e) { allCleared=false; }
			}

			if (allCleared) {
				if (!emptySoFar) { // i.e. do not do anything with empty columns on the right of the screen
					// move other columns into this empty column
					for (int ii=xx+1; ii<width; ii++)
						for (int jj=0; jj<height; jj++)
							moveCircle(circles[ii][jj],circles[ii-1][jj]);
				}
			}
			else
				emptySoFar=false;
		}

		// 3.  (LAST ITEM) CHECK IF THE GAME HAS ENDED
		// This happens if every circle is surrounded by cleared circles or circles of a different color
		if (!gameOverQ())
			return true; // the game has not ended ... go on

		// if we've gotten here, it means that the game has ended
		int score = new Integer(totalPoints.getText());

		highScore hs = new highScore(highScoreFile);
		hs.loadScores();

		// check the high score
		if (hs.newRecordQ(score)) { // i.e. a new record
			JOptionPane.showMessageDialog(null, "Game Over - You got a new high score of "+score+" points!\n"+
					"Your weighted score for this sized board was "+ highScore.hsComp.weightedScore( new highScore.HSdata("",width,height,score))+".");
			if (highScoreName==null)
				highScoreName = JOptionPane.showInputDialog(null,"You got a new high score!\nPlease enter your name:");
			
			if (highScoreName == null) // i.e. the user is not interested in high scores
				return false;
			// populate the high score item
			highScore.HSdata datum = new highScore.HSdata(highScoreName,width,height,score);
			hs.putScore(datum); // enter the name into the high score list

			hs.saveScores();
		}
		else
			JOptionPane.showMessageDialog(null, "Game Over - You did not make the high score.  You had "+score+" points.\n"+
					"Your weighted score for this sized board was "+ highScore.hsComp.weightedScore( new highScore.HSdata("",width,height,score))+".");


		JOptionPane.showMessageDialog(null, "Current high scores:\n"+hs.display());

		return false;

	}

	public void actionPerformed(ActionEvent e) {
		Object source = e.getSource();

		if (source == quitGame)
			System.exit(0);
		else if (source == changeBoard) {
			// modified from http://www.vbforums.com/showthread.php?t=513699
			JTextField width = new JTextField(); width.setText(""+defaultWidth);
			JTextField height = new JTextField(); height.setText(""+defaultHeight);
			Object[] msg = {"Width:", width, "Height:", height};

			JOptionPane op = new JOptionPane(
					msg,
					JOptionPane.QUESTION_MESSAGE,
					JOptionPane.OK_CANCEL_OPTION,
					null,
					null);

			JDialog dialog = op.createDialog(this, "Enter new board size ...");
			dialog.setVisible(true);

			int result = JOptionPane.OK_OPTION;

			try
			{
				result = (Integer) op.getValue();
			}
			catch(Exception uninitializedValue)
			{}

			if(result == JOptionPane.OK_OPTION) // i.e. effect the change
			{
				// i.e. destroy the old board
				cleanUp(); 
				this.width = new Integer(width.getText());
				this.height = new Integer(height.getText());

				// create the new board
				setupGUI();
			}
		}
	}

	/**
	 * Starts a Brain that makes moves
	 */
	private void startBrain(Brain theBrain) {
		this.theBrain = theBrain;
		highScoreName = theBrain.myName(); // the computer gets credit for any subsequent high score
		brainThread = new Thread(theBrain,"Brain Thread");
		brainThread.start();
	}
	
	// For handling the menu checkBox
	public void itemStateChanged(ItemEvent e) {
		Object source = e.getItemSelectable();

		if (e.getStateChange() == ItemEvent.SELECTED) {
			if (source == noPlayerMenuItem)
				return; // i.e. no players
			else if (source == simplePlayerMenuItem)
				startBrain(new simpleBrain(this));
			else if (source == lazyPlayerMenuItem)
				startBrain(new lazyBrain(this));
/*
 * The smarterBrain is missing from this code :-)
*/
		}
		else {
			// deselected
			if (theBrain!=null)
				theBrain.allDone(); // will ask the Brain to stop thinking
		}
	}

	/**
	 * Runs the application
	 */
	public static void main(String args[]) {
		JFrame myApp = new sg();
		myApp.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
		myApp.setSize(defaultWindowWidth, defaultWindowHeight);
		myApp.setVisible(true);
	}
}
 No newline at end of file