Yay - final project done for CS Class

SkyHog

Touchdown! Greaser!
Joined
Feb 23, 2005
Messages
18,431
Location
Castle Rock, CO
Display Name

Display name:
Everything Offends Me
This was a real PITA. It was the first program I had to write in which literally no information was given on how the GUI worked, and we had to write a class that interacted with the GUI without modifying it at all. With the sudoku game, the GUI was well documented so I could figure it out ok. This was not like that.

This is "The Game of Life" the cell birthing game (and I use game lightly). I have officially turned the project in, so I can post it here. Anyone wanna tell me what I did wrong, or could have done better?

I wrote GameOfLife.java and the teacher provided GameOfLifeGUI.java and society.dat (I modified the .dat file to test a few things and everything works fine as long as the rules of the contruct are unchanged. We weren't supposed to test for legit data).

****Turns out I can't attach java or dat files, so here y'all go:

GameOfLife.java
Code:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

/*This class created by Nick Brennan for CS152.
 * A Few notes.  I used Global Variables because I make reference to them throughout the program
 * I know we haven't covered that yet, but its the best way I could think to use it without passing massive
 * amounts of variables between methods.  I created a copy of the BoardPlace array to make modifications without
 * them taking effect right away.  The update method is a bit clunky, but with only a week to work on this with other
 * classes having stuff due also, I was unable to think of a better way to check the surrounding cells.  I hope this is
 * acceptable
 * 
 */
public class GameOfLife  {
	
	/* These are public variables, and I don't think we've done this yet, I hope its ok
	 * 
	 * outputText is a String to hold the data from the board that checks for the presence of an "O"
	 * BoardPlace is a character array that holds the data for the board itself
	 * numCols holds the number of columns in BoardPlace
	 * numRows holds the number of rows in BoardPlace
	 */
	
	public String outputText = "";
	public char[][] BoardPlace;
	public int numCols, numRows;
	
	public GameOfLife (String fileName){
		/* This Method creates the construct for the main GUI to use
		 * 
		 * inputtedFile is only used to store the file name
		 * inputText is the Scanner object
		 */
		File inputtedFile = new File (fileName);
		Scanner inputText = null;
		
		
		try {
			inputText = new Scanner (inputtedFile);
		} catch (FileNotFoundException e) {
			System.err.println("" + fileName + " does not exist");
			System.exit(1);
		}
		
		//Determine the number of columns
		numCols = Integer.parseInt(inputText.next());
		//Determine the number of Rows
		numRows = Integer.parseInt(inputText.next());
		BoardPlace = new char[numCols][numRows];
		
		//Read the rest of the file		
		inputText.nextLine();  //This throws away the carrage return we don't need
		int x = 0; // x here will be the line number we are on
		do{
			String inputLine = inputText.nextLine();
			outputText += inputLine + "\n";
			//split inputLine into chunks for the array
			for (int y = 0; y < numRows; y++){  //y here will be the column we are in
				BoardPlace[x][y] = inputLine.charAt(y);
			}
			x++;
		} while (inputText.hasNext());

	}
	
	public String toString (){
		/* This method takes the data we use in the board and returns it as a string
		 * We could at a later time write this to disk, but for now, it is important
		 * as it helps us test for the presense of "O" on the board, and it gives the
		 * GUI the ability to parse through the data and separate is as necessary
		 */

		outputText = "";
		for (int x = 0; x < numCols; x++){                           //x is the row we are on
			for (int y = 0; y < numRows; y++){                       //y is the column we are on
				outputText += BoardPlace[x][y];
			}
			outputText += "\n";
		}
		return outputText;
	}
	public boolean hasMore(){
		/* This method returns a boolean value to determine whether or not
		 * the letter "O" appears in the board.  Without it, it means the board is
		 * dead
		 * 
		 * tempOutput is a string that holds the data from the board as its sifted through
		 */
		String tempOutput = "";
		for (int x = 0; x < numCols; x++){
			for (int y = 0; y < numRows; y++){
				tempOutput += BoardPlace[x][y];
			}
			tempOutput += "\n";
		}
		if (tempOutput.contains("O")) return true;
		return false;
	}
	public boolean update() {
		/* The meat of the class. This method moves the board to next generation.
		 * We create a temporary board to store the data temporary, and then after
		 * checking all of the rules, we overwrite the main board data.
		 * 
		 * The rules:
		 * If a cell is dead, and 3 of its neighbors are alive, it becomes alive
		 * If a cell is alive, and 2 or 3 of its neighbors are alive, it stays alive
		 * if a cell is alive, and less than 2 or more than 3 of its neighbors are alive, it dies
		 * 
		 * 
		 * tempBoard is a character array used to hold the Board data while we check it
		 * alive is a boolean variable used to determine the life status of each cell
		 * numOfAlive is an integer variable that keeps count of the number of living neighbors
		 * 
		 */
		//create a temporary board to hold the new data before we rewrite it
		char[][] tempBoard = new char[numCols][numRows];
		if (hasMore()){  //determines if there are any cells to check
			
			boolean alive;
			for (int x = 0; x < numCols; x++){                           //x is the column we are on
				for (int y = 0; y < numRows; y++){                       //y is the row we are on
					if (BoardPlace[x][y] == "O".charAt(0)){
						alive = true;
					}else {
						alive = false;
					}
					if (!alive){
						//the cell is dead, are 3 neighbors alive?  REMEMBER TO USE tempBoard
						//also remember to check if it is a border cell
						int numOfAlive = 0;
						
						
						//check cell directly left
						if (y > 0 && BoardPlace[x][y-1] == "O".charAt(0)) {
							numOfAlive++;
						}
						//check the cell directly right
						if (y < (numRows - 1) && BoardPlace[x][y+1] == "O".charAt(0)){
							numOfAlive++;
						}
						//check the cell above and to the left
						if (x > 0 && y > 0 && BoardPlace[x-1][y-1] == "O".charAt(0)){
							numOfAlive++;
						}
						//check the cell directly above
						if (x > 0 && BoardPlace[x-1][y] == "O".charAt(0)){
							numOfAlive++;
						}
						//check the cell above and to the right
						if (x > 0 && y < (numRows -1) && BoardPlace[x-1][y+1]== "O".charAt(0)){
							numOfAlive++;
						}
						//check the cell below and to the left
						if (x < (numCols - 1) && y > 0 && BoardPlace[x+1][y-1] == "O".charAt(0)){
							numOfAlive++;
						}
						//check the cell directly below
						if (x < (numCols - 1) && BoardPlace[x+1][y] == "O".charAt(0)){
							numOfAlive++;
						}
						//check the cell below and to the right
						if (x < (numCols - 1) && y < (numRows- 1) && BoardPlace[x+1][y+1] == "O".charAt(0)){
							numOfAlive++;
						}
						// if numOfAlive is exactly 3, then we make the cell alive
						if (numOfAlive == 3){
							tempBoard[x][y] = "O".charAt(0);
						}else {
							tempBoard[x][y] = ".".charAt(0);
						}
						
					} else {
						//the cell is alive, if 2 or 3 neighbors are alive, we leave it alone
						//CHECK FOR BORDER CELL
						int numOfAlive = 0;
						
						//check the cell directly to the left
						if (y > 0 && BoardPlace[x][y-1] == "O".charAt(0)){
							numOfAlive++;
						}
						
						//check the cell directly to the right
						if (y < (numRows - 1) && BoardPlace[x][y+1] == "O".charAt(0)){
							numOfAlive++;
						}
						
						//check the cell above and left
						if (x > 0 && y > 0 && BoardPlace[x-1][y-1] == "O".charAt(0)){
							numOfAlive++;
						}
						
						//check the cell directly above
						if (x > 0 && BoardPlace[x-1][y] == "O".charAt(0)){
							numOfAlive++;
						}
						
						//check the cell above and right
						if (x > 0 && y < (numRows -1 ) && BoardPlace[x-1][y+1] == "O".charAt(0)){
							numOfAlive++;
						}
						
						//check the cell below and left
						if (x < (numCols - 1) && y > 0 && BoardPlace[x+1][y-1] =="O".charAt(0)){
							numOfAlive++;
						}
						
						//check the cell directly below
						if (x < (numCols - 1) && BoardPlace[x+1][y]=="O".charAt(0)){
							numOfAlive++;
						}
						
						//check the cell below and right
						if (x < (numCols - 1) && y < (numRows - 1) && BoardPlace[x+1][y+1] == "O".charAt(0)){
							numOfAlive++;
						}
						
						//now we check to see if there are exactly 2 or 3 neighbors, and if so, don't change
						if (numOfAlive == 2 || numOfAlive == 3) {
							tempBoard[x][y]= "O".charAt(0);
						}
						
						//the cell is alive, if less than 2 or more than 3 neighbors are alive, kill it
						if (numOfAlive < 2 || numOfAlive > 3){
							tempBoard[x][y] = ".".charAt(0);
						}
						
					}
					
					
				}
			}
			
			
			//Determine if there is a change to the board
			if (tempBoard == BoardPlace) return false;                 
			else {
				BoardPlace = tempBoard;
				return true;
			}
			
		}
		//There are no living cells, so there are no updates available
		return false;
	}
	
	public void changeCell (int row, int col){
		/* This method changes the status of a cell.  If it is dead, we make it alive.
		 * If it is alive, we make it dead.  This is only called when the user
		 * clicks on a box manually to change it
		 * 
		 * There are no local variables.
		 */
		if (BoardPlace[row][col] == "O".charAt(0)){
			BoardPlace[row][col] = ".".charAt(0);
		} else if (BoardPlace[row][col] == ".".charAt(0)){
			BoardPlace[row][col] = "O".charAt(0);
		}
	}
}

GameOfLifeGUI.java, not written by me, but required for the program to work:
Code:
/**
 * Original version by Tom Slattery.
 * Augmented by Andree Jacobson (andree@cs.unm.edu), 12/07/2005
 */

import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class GameOfLifeGUI extends JFrame implements ActionListener {

  // To avoid Xlint problems in Java 5.0
  public static final long serialVersionUID = 1;
  public static final int maxDelay = 1000; // By Andree

  private GameOfLifePanel myWorld;
  private JButton myRunButton, myStopButton;
  private JSlider mySpeedSlider; // By Andree
  private javax.swing.Timer myTimer;

  public GameOfLifeGUI ( String filename ) {
    setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
    setTitle ( "CS 251 Fall 2006 -- Game of Life" ); //Updated by Nick
    setLayout ( new BoxLayout ( getContentPane ( ), BoxLayout.Y_AXIS ) );

    myWorld = new GameOfLifePanel ( filename );
    add ( myWorld );

    myTimer = new javax.swing.Timer ( maxDelay / 2, myWorld );

    JPanel controlPanel = new JPanel ( );
    myRunButton = new JButton ( "Run" );
    myStopButton = new JButton ( "Stop" );
    mySpeedSlider = new JSlider ( JSlider.HORIZONTAL, 0, maxDelay, maxDelay / 2 );
    JButton stepButton = new JButton ( "Step" );
    mySpeedSlider.addChangeListener ( new ChangeListener ( ) {

      public void stateChanged ( ChangeEvent e ) {
	JSlider source = (JSlider) e.getSource ( );
	myTimer.setDelay ( maxDelay - source.getValue ( ) );
      }
    } );

    myRunButton.addActionListener ( this );
    myStopButton.addActionListener ( this );
    stepButton.addActionListener ( myWorld );

    controlPanel.setLayout ( new BoxLayout ( controlPanel, BoxLayout.Y_AXIS ) );
    controlPanel.add ( mySpeedSlider );
    JPanel buttonCtrl = new JPanel ( new FlowLayout ( ) );
    buttonCtrl.add ( stepButton );
    buttonCtrl.add ( myRunButton );
    buttonCtrl.add ( myStopButton );
    controlPanel.add ( buttonCtrl );

    add ( controlPanel );
    pack ( );

    setResizable ( false );
  }

  public void actionPerformed ( ActionEvent e ) {
    if ( e.getSource ( ) == myRunButton )
      myTimer.start ( );
    else if ( e.getSource ( ) == myStopButton )
      myTimer.stop ( );
  }

  public static void main ( String[] args ) {
    new GameOfLifeGUI ( ( args.length == 0 ) ? "./societies/society.dat" : args[0] )
	.setVisible ( true );
  }

  private class GameOfLifePanel extends JPanel implements ActionListener {

    // To avoid Xlint problems in Java 5.0
    public static final long serialVersionUID = 1;

    private GameOfLife mySociety;
    private static final int TILE_SIZE = 12;
    private int myRows, myCols, myGen;

    public GameOfLifePanel ( String filename ) {
      mySociety = new GameOfLife ( filename );
      myGen = 0;
      StringTokenizer sizer = new StringTokenizer ( mySociety.toString ( ) );
      myRows = sizer.countTokens ( );
      myCols = sizer.nextToken ( ).length ( );
      setPreferredSize ( new Dimension ( TILE_SIZE * myCols + myCols - 1,
	  TILE_SIZE * myRows + myRows + 2 ) );
      setBackground ( Color.white );
      addMouseListener ( new ClickListener ( ) );
    }

    private class ClickListener extends MouseAdapter {

      public void mouseReleased ( MouseEvent e ) {
	int row = e.getY ( ) / ( 1 + TILE_SIZE );
	int col = e.getX ( ) / ( 1 + TILE_SIZE );

	mySociety.changeCell ( row, col );
	repaint ( );
      }
    }

    public void actionPerformed ( ActionEvent e ) {
      myGen++;
      boolean ret = mySociety.update ( );
      if ( !ret )
	myTimer.stop ( );
      repaint ( );
    }

    public void paintComponent ( Graphics g ) {
      super.paintComponent ( g );

      g.setColor ( Color.lightGray );

      for ( int r = 1; r <= myRows; r++ )
	g.drawLine ( 0, TILE_SIZE * r + r, getWidth ( ), TILE_SIZE * r + r );

      for ( int c = 1; c <= myCols; c++ )
	g.drawLine ( TILE_SIZE * c + c, 0, TILE_SIZE * c + c, getHeight ( ) );

      StringTokenizer st = new StringTokenizer ( mySociety.toString ( ), " \n" );
      int r = 0;
      while ( st.hasMoreTokens ( ) ) {
	String row = st.nextToken ( );
	for ( int c = 0; c < myCols; c++ )
	  if ( row.charAt ( c ) == 'O' ) {
	    g.setColor ( Color.pink );
	    g.fillRoundRect ( TILE_SIZE * c + c + 1, TILE_SIZE * r + r + 1,
		TILE_SIZE, TILE_SIZE, TILE_SIZE / 2, TILE_SIZE / 2 );
	    g.setColor ( Color.darkGray );
	    g.drawRoundRect ( TILE_SIZE * c + c + 1, TILE_SIZE * r + r + 1,
		TILE_SIZE, TILE_SIZE, TILE_SIZE / 2, TILE_SIZE / 2 );
	  }
	r++;
      }

      g.setColor ( Color.darkGray );
      g.drawString ( "T = " + myGen, (int) ( TILE_SIZE * .5 ), TILE_SIZE - 1 );
    }
  }
}

edit: Oh, and the .dat file too, must be located in a folder called societies, in a file called society.dat :

Code:
12 18
..................
..................
...O......O.O.....
..O..O.......O....
..................
..................
....OO.OO.........
....OO........O...
......O.......O...
.............OOO..
..................
..................
 
Last edited:
I'll take this one function at a time, since time may not let me get to all of them.
Code:
boolean hasMore(){
    /* This method returns a boolean value to determine whether or not
     * the letter "O" appears in the board.  Without it, it means the board is
     * dead
     * 
     * tempOutput is a string that holds the data from the board as its sifted through
     */
    String tempOutput = "";
    for (int x = 0; x < numCols; x++){
        for (int y = 0; y < numRows; y++){
            tempOutput += BoardPlace[x][y];
        }
        tempOutput += "\n";
    }
    if (tempOutput.contains("O")) return true;
    return false;
}

Ok, this will work but it is not terribly efficient, because it continues looping even after it could have found an "O".

A more efficient solution would be something like this
Code:
boolean hasMore()
{
    /* This method returns a boolean value to determine whether or not
     * the letter "O" appears in the board.  Without it, it means the board is
     * dead
     * 
     * tempOutput is a string that holds the data from the board as its sifted through
     */
    String tempOutput = "";
    bool found = false;

    for (int x = 0; x < numCols && !found; x++){
        for (int y = 0; y < numRows && !found; y++){
            found = BoardPlace[x][y] == "O";
        }
    }
    return found;
}
The loop will continue as long as the loop hasn't been ended AND found is false. found = BP[x][y] == "O" is the same as saying:
if(bp[x][y] =="O") found=true else found=false;

So loop 1 begins, found is false. Loop 2 begins, found is still false. Loops continue until a "O" is found. Loop 2 exits early and then loop 1 exits early, and you return the value of found.

That stops you wasting processing time testing 800 "O"s when you only need to find one.
 
I haven't analyzed the meat of the code yet, but I do notice that you're iterating through the entire board and testing each cell within your main loop.

An alternate approach, and one that would be a bit more true to OOP IMO (and OOP purity is subjetive) would be to define a class for each cell (pseudocode)
class cell
private properties:
cell - topRight
cell - right
cell - bottomRight
cell - bottom
etc. (9 total "neighbor" cells)
int state = ? ; // 0 if empty, 1 if alive

function string determineState()
{
int neighborCount = 0;
neighborCount += right.state;
neighborCount += bottomRight.state;
etc... for all 9 neighbors

// now test on neighborcount + state to determine proper action
}

In your process you create an array of these cells and then link them all up, and then your main loop can ask the cell whether or not it should change.

Just another way of doing it. :)
}
 
Thanks Chuck. I knew there had to be a better way to do that part as I was writing it. I thought for a long time and finally just decided to do it the long, crappy way I did it. I really like the way you explained it instead. Live and learn. This is why I don't like having projects with such short deadlines. We literally had one week to write the entire project, and this was with absolutely no documentation other than "These are the rules, make it work."

I suppose that's real life in action, eh?
 
Sadly, that the case far too often. Most of the time my development projects are "we need this yesterday, and we need the other stuff we told you yesterday that we needed the day before yesterday, last week".

Meanwhile I'm trying to use up my extra PTO before year end... *sigh* Why the heck am I wasting time here?? ;)
 
I can't take the time to look at your code just now as we have only 2 weeks to approve a year's worth of code generated by a group of 8 programmers. Probably won't get to it at all. But, as you have seen, time constraints are always too tight. Projects always take much longer than anticipated. Someone else always knows a faster, better, cheaper way to do it.

The really great thing about your work is that you are willing - no, eager - to accept code reviews and suggestions. You will become a far greater programmer than most with that attitude.

To quote, what am I doing here?
 
and once again, Dilbert is SO timely...
 

Attachments

  • untitled.bmp
    1.2 MB · Views: 17
I'm a bit fried from this PHP project I've been working with in PEAR::SOAP... doubt I could write some java code of my own at the moment, let alone debug someone else's :D

But in other news, looks like you had far more interesting programming projects than I did in my intro java course! :D
 
Congrats Nick,

I'll bet real money you passed with a good grade.

Gotta love school where the projects end, unlike real life. Sell a product and support it until you retire (I finally did) then you only have your friends to support.

Joe
 
Nick -
I have a minute while testing timeouts to look at your code. I know that you have completed your work, but thought you might like a general comment.

I think someone else commented on this logic, but I have a style comment to help the code be more sellf-documenting and maintainable.

Code:
 	int x = 0; // x here will be the line number we are on
		do{
			String inputLine = inputText.nextLine();
			outputText += inputLine + "\n";
			//split inputLine into chunks for the array
			for (int y = 0; y < numRows; y++){  //y here will be the column we are in
				BoardPlace[x][y] = inputLine.charAt(y);
			}
			x++;
		} while (inputText.hasNext());

Instead of using single character variables, use meaningful names. Then, you don't need the comment explaining what the heck x and y mean and when it comes time to do a global search and replace, you don't have to weed through every x and y in the code. It also helps make inconsistencies a little more obvious. Is the stuff in red what you wanted?

Code:
inputText.nextLine();  //This throws away the carrage return we don't need
int thisRow = 0; 
do
{
   String inputLine = inputText.nextLine();
   outputText += inputLine + "\n";
               //split inputLine into chunks for the array
   for (int thisColumn = 0; [COLOR=red][B]thisColumn < numRows[/B][/COLOR]; thisColumn++)
   {  
      BoardPlace[thisRow][thisColumn] = inputLine.charAt(thisColumn);
   }
   thisRow++;
} while (inputText.hasNext());
 
Another style suggestion:

Use spaces instead of tabs to indent. People set tabs to different values, usually 4 or 8, but they can often be anywhere from 1 to 8. This causes very ragged code when more than one person edits files with different tab sizes. Many places specify the indent size and specify spaces. You can set your editor to replace tabs with spaces.
 
Good idea. I used Eclipse's default indentations. When using Visual Basic, I use spaces. Just need to figure out how to do it with eclipse.

I agree on the generic variables. I got lazy. heh.

thanks for the pointers!
 
Ok, got the grade back on the final project: 95/100, with a comment saying "Update Method was excessively long - 5."

Now just gotta wait for the final grades to post. I think I did well.
 
Back
Top