META-INF/MANIFEST.MF
ca/uottawa/eecs/puzzler/Board.class
ca/uottawa/eecs/puzzler/Board.java
ca/uottawa/eecs/puzzler/Cell.class
ca/uottawa/eecs/puzzler/Cell.java
ca/uottawa/eecs/puzzler/Puzzler.class
ca/uottawa/eecs/puzzler/Puzzler.java
data/ball-0.png
data/ball-1.png
data/ball-2.png
data/ball-3.png
data/ball-4.png
data/ball-5.png
data/ball-6.png
manifest.mf
Manifest-Version: 1.0
Created-By: 1.6.0_29 (Apple Inc.)
Main-Class: ca.uottawa.eecs.puzzler.Puzzler
Board
package ca.uottawa.eecs.puzzler;
public synchronized class Board extends javax.swing.JPanel implements java.awt.event.ActionListener {
private static final long serialVersionUID = 1;
public static final int NUMBER_OF_ROWS = 7;
public static final int NUMBER_OF_COLUMNS = 7;
private Cell[][] board;
private boolean allowsClicks;
public void Board();
public void reset();
public boolean allowsClicks();
public void setAllowsClicks(boolean);
public void deselectAllCells();
public boolean hasIdenticalNeighbours(int, int);
public void selectCellAndContiguousCells(int, int);
public void removeCellAndContiguousCells(int, int);
public void fallDown();
public void fallRight();
public boolean solved();
public void actionPerformed(java.awt.event.ActionEvent);
}
ca/uottawa/eecs/puzzler/Board.java
ca/uottawa/eecs/puzzler/Board.javapackage ca.uottawa.eecs.puzzler;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
/**
* In this application, Board is a specialized type of JPanel that
* holds cells (balls). The board also holds the higher level logic of the game
* for selecting and removing cells.
*
* @author Marcel Turcotte, University of Ottawa
*
*/
public class Board extends JPanel implements ActionListener {
private static final long serialVersionUID = 1L;
/**
* Defines the total number of rows.
*/
public static final int NUMBER_OF_ROWS = 7;
/**
* Defines the total number of columns.
*/
public static final int NUMBER_OF_COLUMNS = 7;
/**
* A two dimensional array to keep references to all the cells of the board.
*/
private Cell[][] board;
/**
* Used by the logic to avoid processing multiple clicks.
*/
private boolean allowsClicks = false;
/**
* Displays all the cells on a two dimensional grid.
*/
public Board() {
setBackground(Color.WHITE);
setLayout(new GridLayout(NUMBER_OF_ROWS, NUMBER_OF_COLUMNS));
setBorder(BorderFactory.createEmptyBorder(20, 20, 0, 20));
board = new Cell[NUMBER_OF_ROWS][NUMBER_OF_COLUMNS];
for (int row = 0; row < NUMBER_OF_ROWS; row++) {
for (int column = 0; column < NUMBER_OF_COLUMNS; column++) {
board[row][column] = new Cell(this, row, column);
add(board[row][column]);
}
}
}
/**
* Re-initializes all the cells of the grid.
*/
public void reset() {
for (int row = 0; row < NUMBER_OF_ROWS; row++) {
for (int column = 0; column < NUMBER_OF_COLUMNS; column++) {
board[row][column].reset();
}
}
}
/**
* Returns true if clicks are allowed, and false otherwise.
*
* @return true if clicks are allowed
*/
public boolean allowsClicks() {
return allowsClicks;
}
/**
* A setter for the attribute allowsClick.
*
* @param allowClicks
* the allowClicks to set
*/
public void setAllowsClicks(boolean allowClicks) {
this.allowsClicks = allowClicks;
}
/**
* Sets the attribute selected to false for all the cells of
* the grid.
*/
public void deselectAllCells() {
for (int row = 0; row < NUMBER_OF_ROWS; row++) {
for (int column = 0; column < NUMBER_OF_COLUMNS; column++) {
board[row][column].setSelected(false);
}
}
}
/**
* Returns true if the cell found at row, column has at
* least one neighbor of the same type, and false otherwise.
*
* @param row
* the specified row
* @param column
* the specified column
* @return true if the specified cell has at least one neighbor of the same
* type
*/
public boolean hasIdenticalNeighbours(int row, int column) {
Cell cell = board[row][column];
boolean result = false;
if ((row – 1 >= 0 && cell.sameType(board[row – 1][column]))
|| (row + 1 < NUMBER_OF_ROWS && cell
.sameType(board[row + 1][column]))
|| (column - 1 >= 0 && cell.sameType(board[row][column – 1]))
|| (column + 1 < NUMBER_OF_COLUMNS && cell
.sameType(board[row][column + 1]))) {
result = true;
}
return result;
}
/**
* This method is called after a call to hasIdenticalNeighbours in
* order to select (set the attribute selected to true) all the adjacent
* cells of the same type.
*
* @param row
* the specified row
* @param column
* the specified column
*/
public void selectCellAndContiguousCells(int row, int column) {
Cell cell = board[row][column];
cell.setSelected(true);
if (column – 1 >= 0 && cell.sameType(board[row][column – 1])
&& (!board[row][column – 1].selected())) {
selectCellAndContiguousCells(row, column – 1);
}
if (column + 1 < NUMBER_OF_ROWS
&& cell.sameType(board[row][column + 1])
&& (!board[row][column + 1].selected())) {
selectCellAndContiguousCells(row, column + 1);
}
if (row - 1 >= 0 && cell.sameType(board[row – 1][column])
&& (!board[row – 1][column].selected())) {
selectCellAndContiguousCells(row – 1, column);
}
if (row + 1 < NUMBER_OF_COLUMNS
&& cell.sameType(board[row + 1][column])
&& (!board[row + 1][column].selected())) {
selectCellAndContiguousCells(row + 1, column);
}
}
/**
* The selected cells are removed. Actually, this simply means setting the
* type of the selected cells to Empty. As the method proceeds it
* unselects cells. This is important to avoid looping infinitely.
*
* @param row
* the specified row
* @param column
* the specified column
*/
public void removeCellAndContiguousCells(int row, int column) {
Cell cell = board[row][column];
cell.setSelected(false);
if (column – 1 >= 0 && board[row][column – 1].selected()) {
removeCellAndContiguousCells(row, column – 1);
}
if (column + 1 < NUMBER_OF_ROWS && board[row][column + 1].selected()) {
removeCellAndContiguousCells(row, column + 1);
}
if (row - 1 >= 0 && board[row – 1][column].selected()) {
removeCellAndContiguousCells(row – 1, column);
}
if (row + 1 < NUMBER_OF_COLUMNS && board[row + 1][column].selected()) {
removeCellAndContiguousCells(row + 1, column);
}
cell.setType(Cell.EMPTY);
}
/**
* Detects vertical gaps and collapse those cells.
*/
public void fallDown() {
for (int column = NUMBER_OF_COLUMNS - 1; column >= 0; column–) {
boolean foundGap = false;
int fallTo = -1;
for (int row = NUMBER_OF_ROWS – 1; row >= 0; row–) {
if (board[row][column].isEmpty()) {
if (!foundGap) {
foundGap = true;
fallTo = row;
}
} else {
if (foundGap) {
board[fallTo][column].setType(board[row][column]
.getType());
board[row][column].setType(Cell.EMPTY);
fallTo–;
}
}
}
}
}
/**
* Detects horizontal gaps and collapse those cells.
*/
public void fallRight() {
for (int row = NUMBER_OF_ROWS – 1; row >= 0; row–) {
boolean foundGap = false;
int fallTo = -1;
for (int column = NUMBER_OF_COLUMNS – 1; column >= 0; column–) {
if (board[row][column].isEmpty()) {
if (!foundGap) {
foundGap = true;
fallTo = column;
}
} else {
if (foundGap) {
board[row][fallTo]
.setType(board[row][column].getType());
board[row][column].setType(Cell.EMPTY);
fallTo–;
}
}
}
}
}
/**
* Returns true if and only if all the cells are Empty.
*
* @return true if and only if all the cells are Empty
*/
public boolean solved() {
boolean flag = false;
for (int row = 0; row < NUMBER_OF_ROWS && !flag; row++) {
for (int column = 0; column < NUMBER_OF_COLUMNS && !flag; column++) {
if (!board[row][column].isEmpty()) {
flag = true;
}
}
}
return !flag;
}
/**
* This method must be implemented as part of the contract specified by
* ActionListener. This method will be called each time the user clicks a
* button. Upon a first click, if there are adjacent cells of the same type,
* the cell and the adjacent cells of the same type are selected. Upon a
* second click, the cell and the adjacent cells of the same type are
* removed from the Board. This causes other cells to fall down, and
* right.
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
public void actionPerformed(ActionEvent e) {
if (e.getSource() instanceof Cell) {
Cell src = (Cell) e.getSource();
if (src.getType() != Cell.EMPTY && allowsClicks()) {
setAllowsClicks(false);
int row = src.getRow(), column = src.getColumn();
if (!src.selected()) {
deselectAllCells();
if (hasIdenticalNeighbours(row, column)) {
selectCellAndContiguousCells(row, column);
}
} else {
removeCellAndContiguousCells(row, column);
fallDown();
fallRight();
if (solved()) {
System.out.println(“Solved!”);
reset();
}
src.setSelected(false);
}
setAllowsClicks(true);
}
}
}
}
Cell
package ca.uottawa.eecs.puzzler;
public synchronized class Cell extends javax.swing.JButton {
private static final long serialVersionUID = 1;
private static java.util.Random generator;
public static final int NUM_COLOURS = 5;
public static final int EMPTY = 0;
private int type;
private boolean selected;
private int row;
private int column;
private static final javax.swing.ImageIcon[] icons;
public void Cell(Board, int, int);
public void Cell(Board, int, int, int);
private int newRandomCellType();
private javax.swing.ImageIcon getImageIcon();
public void reset();
public int getType();
public boolean sameType(Cell);
public void setType(int);
public boolean isEmpty();
public boolean selected();
public void setSelected(boolean);
public int getRow();
public int getColumn();
static void
}
ca/uottawa/eecs/puzzler/Cell.java
ca/uottawa/eecs/puzzler/Cell.javapackage ca.uottawa.eecs.puzzler;
import java.util.Random;
import java.awt.Color;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.BorderFactory;
import javax.swing.border.Border;
/**
* In the application Puzzler, a Cell is a specialized type of
* JButton that represents a ball in the game. Upon a first click, if
* there are adjacent cells of the same type, this cell and the adjacent cells
* of the same type are selected. Upon a second click, this cell and the
* adjacent cells of the same type are removed from the Board. This
* causes other cells to fall down, and right.
*
* @author Marcel Turcotte, University of Ottawa
*/
public class Cell extends JButton {
private static final long serialVersionUID = 1L;
/**
* A random number generator is used to generate the cell type, when a new
* board is created, or the user clicks reset. The same number generator is
* shared by all the cells.
*/
private static Random generator = new Random();
/**
* Number of colours
*/
public static final int NUM_COLOURS = 5;
/**
* Symbolic constant that represents an empty cell
*/
public static final int EMPTY = 0;
/**
* The cell type. Valid values are Empty, Blue, Green, etc.
*/
private int type;
/**
* This instance variable is true if the cell is selected.
*/
private boolean selected = false;
/**
* The coordinate of this cell on the Board.
*/
private int row, column;
/**
* A an array is used to cache all the images. Since the images are not
* modified. All the cells that display the same image reuse the same
* ImageIcon object. Notice the use of the keyword static.
*/
private static final ImageIcon[] icons = new ImageIcon[NUM_COLOURS + 2];
/**
* Constructor used for initializing a cell of an unspecified type. The
* actual type is randomly generated. A randomly generated cell is never
* Empty.
*
* @param board
* the grid that holds all the cells
* @param row
* the row of this Cell
* @param column
* the column of this Cell
*/
public Cell(Board board, int row, int column) {
this(board, row, column, generator.nextInt(NUM_COLOURS) + 1);
}
/**
* Constructor used for initializing a cell of a specified type.
*
* @param board
* the grid that holds all the cells
* @param row
* the row of this Cell
* @param column
* the column of this Cell
* @param type
* specifies the type of this cell
*/
public Cell(Board board, int row, int column, int type) {
this.row = row;
this.column = column;
this.type = type;
setBackground(Color.WHITE);
setIcon(getImageIcon());
Border emptyBorder = BorderFactory.createEmptyBorder(0, 0, 0, 0);
setBorder(emptyBorder);
setBorderPainted(false);
addActionListener(board);
}
/**
* A helper method that randomly generates a non-empty cell type.
*
* @return randomly generated non-empty cell type
*/
private int newRandomCellType() {
return generator.nextInt(NUM_COLOURS) + 1;
}
/**
* Determine the image to use based on the cell type. Uses
* getResource to locate the image file, either on the file system or
* the .jar file. Implements a caching mechanism.
*
* @return the image to be displayed by the button
*/
private ImageIcon getImageIcon() {
int id;
if (selected) {
id = NUM_COLOURS + 1;
} else {
id = type;
}
if (icons[id] == null) {
String strId = Integer.toString(id);
icons[id] = new ImageIcon(Cell.class.getResource(“/data/ball-”
+ strId + “.png”));
}
return icons[id];
}
/**
* This method is called when the used clicks the reset button. A new cell
* type is generated. Its image is updated. The cell is unselected.
*/
public void reset() {
type = newRandomCellType();
setIcon(getImageIcon());
selected = false;
}
/**
* Returns the cell type of this cell.
*
* @return the type
*/
public int getType() {
return type;
}
/**
* Returns true if this and the other cell have the same type.
*
* @param other
* other cell used for the comparison
* @return true if both cells have the same type
*/
public boolean sameType(Cell other) {
return type == other.type;
}
/**
* Changes the cell type of this cell. The image is updated accordingly.
*
* @param type
* the type to set
*/
public void setType(int type) {
this.type = type;
setIcon(getImageIcon());
}
/**
* Returns true if this cell is empty, i.e. its type is
* CellType.Empty.
*
* @return true is this cell is empty
*/
public boolean isEmpty() {
return type == EMPTY;
}
/**
* Returns true is this cell is selected. Note: isSelected would have
* been a better name. However, this would overwrite the parent method
* isSelected.
*
* @return true is this cell is selected.
*/
public boolean selected() {
return selected;
}
/**
* Sets the value of the attribute selected.
*
* @param selected
* the new value for the attribute selected
*/
public void setSelected(boolean selected) {
this.selected = selected;
setIcon(getImageIcon());
}
/**
* Getter method for the attribute row.
*
* @return the value of the attribute row
*/
public int getRow() {
return row;
}
/**
* Getter method for the attribute column.
*
* @return the value of the attribute column
*/
public int getColumn() {
return column;
}
}
Puzzler
package ca.uottawa.eecs.puzzler;
public synchronized class Puzzler extends javax.swing.JFrame implements java.awt.event.ActionListener {
private static final long serialVersionUID = 1;
private Board board;
public void Puzzler();
public void actionPerformed(java.awt.event.ActionEvent);
public static void main(String[]);
}
ca/uottawa/eecs/puzzler/Puzzler.java
ca/uottawa/eecs/puzzler/Puzzler.javapackage ca.uottawa.eecs.puzzler;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
/**
* This is the main window of the application. A Board object is placed
* in the center of the frame. The reset button is placed at the bottom.
*
* Based on Puzzler by Apple.
*
* @author Marcel Turcotte, University of Ottawa
*/
public class Puzzler extends JFrame implements ActionListener {
private static final long serialVersionUID = 1L;
/**
* Keeps a reference to the object board in order to call the method
* reset whenever the user clicks the reset button.
*/
private Board board;
/**
* Creates the layout of the application.
*/
public Puzzler() {
super(“Puzzler”);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBackground(Color.WHITE);
board = new Board();
add(board, BorderLayout.CENTER);
JButton button = new JButton(“Reset”);
button.setFocusPainted(false);
button.addActionListener(this);
JPanel control = new JPanel();
control.setBackground(Color.WHITE);
control.add(button);
add(control, BorderLayout.SOUTH);
pack();
setResizable(false);
setVisible(true);
board.setAllowsClicks(true);
}
/**
* This method must be implemented as part of the contract specified by
* ActionListener. When the user clicks the reset button, it calls the
* method reset of the object designated by board.
*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
public void actionPerformed(ActionEvent e) {
if (board.allowsClicks()) {
board.setAllowsClicks(false);
board.reset();
board.setAllowsClicks(true);
}
}
/**
* Java programs start by executing the main method. Here, this main method
* creates the main window of the application.
*
* @param args
* the command line arguments
*/
public static void main(String[] args) {
new Puzzler();
}
}
Main-Class: ca.uottawa.eecs.puzzler.Puzzler