CantStopBoard.java is incomplete and FourDice.java is not given. Both are available in compiled form in the archive.
CantStopGame.java
import java.util.*;
/**
* A game of solitaire Can't Stop. Game = board + dice.
*
* @author Jim Glenn
* @version 0.1 11/18/2008
*/
public class CantStopGame extends Observable
{
/**
* The board.
*/
private CantStopBoard board;
/**
* The dice.
*/
private FourDice dice;
/**
* The current state of the game. The possible states are
* start of turn, selecting move, acknowledging blowing it, deciding
* whether to roll again, or finished.
*/
private int state;
/**
* The 1-based index of the current turn.
*/
private int turn;
/**
* Constants for states.
*/
public static final int START_STATE = 0;
public static final int MOVE_STATE = 1;
public static final int ACK_STATE = 2;
public static final int WON_STATE = 3;
public static final int ROLL_STATE = 4;
/**
* Creates a new game.
*/
public CantStopGame()
{
board = new CantStopBoard();
dice = null;
state = START_STATE;
turn = 1;
}
/**
* Returns a textual representation of the dice in this game. If
* the dice have not been rolled, this method returns the empty
* string.
*
* @return a textual representation of the dice
*/
public String getDice()
{
if (dice != null)
{
return "" + dice.getDie(0) + dice.getDie(1) + dice.getDie(2) + dice.getDie(3);
}
else
{
return "";
}
}
/**
* Rolls this game's dice.
*
* @throws IllegalStateException if this game is not in a state when it
* it legal to roll the dice
*/
public void roll()
{
if (state != ROLL_STATE && state != START_STATE)
throw new IllegalStateException("Not time to roll");
dice = new FourDice();
if (getLegalMoves().size() == 0)
state = ACK_STATE;
else
state = MOVE_STATE;
setChanged();
notifyObservers();
}
/**
* Moves this game from the blown it state to the start of the next
* turn.
*/
public void blowIt()
{
if (state != ACK_STATE)
throw new IllegalStateException("Haven't blown it");
board.endTurnNoProgress();
state = START_STATE;
turn++;
setChanged();
notifyObservers();
}
/**
* Ends the current turn and moves the colored markers to the
* position of the neutral markers on the board.
*/
public void endTurn()
{
if (state != ROLL_STATE)
throw new IllegalStateException("Haven't just moved");
board.endTurnWithProgress();
if (board.isGameOver())
{
state = WON_STATE;
}
else
{
state = START_STATE;
turn++;
}
setChanged();
notifyObservers();
}
/**
* Returns a list of the legal moves from this game's current state.
* The elements of the returned list are themselves lists that
* contain the <CODE>Integer</CODE> labels on the columns the
* move could use.
*
* @return a list of the currently legal moves
*/
public List getLegalMoves()
{
List moves = new LinkedList();
if (dice != null)
{
// nothing clever here -- go over all the possible totals
// and check which ones can be made with the dice...
for (int tot1 = 2; tot1 <= dice.getTotal() / 2; tot1++)
{
if (dice.makeTotal(tot1))
{
int tot2 = dice.getTotal() - tot1;
// ...and then check if we can use both...
if (board.isLegalMove(tot1, tot2))
{
List move = new LinkedList();
move.add(new Integer(tot1));
move.add(new Integer(tot2));
moves.add(move);
}
// ...or just one (yes, this should be
// in an else, but I'd like to test
// the students' code)
if (board.isLegalMove(tot1, dice))
{
List move = new LinkedList();
move.add(new Integer(tot1));
moves.add(move);
}
if (tot2 != tot1 && board.isLegalMove(tot2, dice))
{
List move = new LinkedList();
move.add(new Integer(tot2));
moves.add(move);
}
}
}
}
return moves;
}
/**
* Makes the selected move in this game.
*
* @param which an index in to the list returned by <CODE>getLegalMoves</CODE>
*/
public void makeMove(int which)
{
List move = (List)(getLegalMoves().get(which));
// determine whether the move uses one or two columns
// and invoke the corresponding version of moveNeutralMarkers
if (move.size() == 1)
{
board.moveNeutralMarkers(((Integer)(move.get(0))).intValue());
}
else
{
board.moveNeutralMarkers(((Integer)(move.get(0))).intValue(),
((Integer)(move.get(1))).intValue());
}
state = ROLL_STATE;
setChanged();
notifyObservers();
}
/**
* Returns the current state of this game.
*
* @return the current state
*/
public int getState()
{
return state;
}
/**
* Returns the current board in this game.
*
* @return the board
*/
public CantStopBoard getBoard()
{
return board;
}
/**
* Returns the 1-based index of the current turn.
*
* @return the current turn
*/
public int countTurns()
{
return turn;
}
}
CantStopBoard.java
/**
* A solitaire Can't Stop game board.
*
* This is a skeleton. Complete code will not be posted to protect it
* from the prying eyes of CS201 students.
*
* @author Jim Glenn
* @version SKELETON 11/18/2008
*/
public class CantStopBoard
{
private int[] colored;
private int[] neutral;
public CantStopBoard()
{
}
public CantStopBoard(int[] c, int[] n)
{
colored = c;
neutral = n;
}
public int getColoredMarkerPosition(int col)
{
return 0;
}
public int getNeutralMarkerPosition(int col)
{
return -1;
}
public boolean isLegalMove(int col1, int col2)
{
return true;
}
public boolean isLegalMove(int col, FourDice dice)
{
return true;
}
public void moveNeutralMarkers(int col1, int col2)
{
}
public void moveNeutralMarkers(int col)
{
}
public void endTurnWithProgress()
{
}
public void endTurnNoProgress()
{
}
public int countNeutralMarkersUsed()
{
return 0;
}
public boolean isGameOver()
{
return false;
}
public int getColumnLength(int col)
{
return 13 - 2 * Math.abs(7 - col);
}
/**
* Returns a printable representation of this board.
*
* @return a printable representation of this board
*/
public String toString()
{
// change these if you think it is hard to read as is
final char MARKER = 'O';
final char NEUTRAL = 'o';
final char EMPTY = '.';
// create a buffer for a drawing of each column
StringBuffer[] cols = new StringBuffer[11];
for (int c = 2; c <= 12; c++)
{
int ci = c - 2; // the array index of column c
cols[ci] = new StringBuffer(" "); // 13 spaces
int len = getColumnLength(c);
int bottom = Math.abs(7 - c); // how many spaces below column
for (int s = 1; s <= len; s++) // draw each space in the column
{
if (s == getColoredMarkerPosition(c))
{
cols[ci].setCharAt(s + bottom - 1, MARKER);
}
else if (s == getNeutralMarkerPosition(c))
{
cols[ci].setCharAt(s + bottom - 1, NEUTRAL);
}
else
{
cols[ci].setCharAt(s + bottom - 1, EMPTY);
}
}
}
// transpose the columns to get the final string
StringBuffer result = new StringBuffer();
for (int r = 0; r < 13; r++)
{
for (int c = 2; c <= 12; c++)
{
result.append(cols[c - 2].charAt(12 - r));
}
result.append('\n');
}
return result.toString();
}
}
CantStopPanel.java
import javax.swing.*;
import java.awt.*;
import java.util.*;
/**
* A view of a Can't Stop Game as a <CODE>JPanel</CODE>.
*
* @author Jim Glenn
* @version 0.1 11/18/2008
*/
public class CantStopPanel extends JPanel implements Observer
{
/**
* The model of the game this panel draws.
*/
private CantStopGame model;
/**
* Creates a panel to display the given game.
*/
public CantStopPanel(CantStopGame g)
{
model = g;
setBackground(Color.LIGHT_GRAY);
}
/**
* Paints this panel. The panel will be painted according to
* the state of the model this panel is associated with.
*/
public void paint(Graphics g)
{
g.clearRect(0, 0, getWidth(), getHeight());
// compute the size of the board so we fill 80% of the smaller dimension
int size = Math.min(getWidth(), getHeight()) * 4 / 5;
// compute some important coordinates
int sideLength = (int)(size / (1 + Math.sqrt(2)));
int midX = getWidth() / 2;
int midY = getHeight() / 2;
int topY = midY - size / 2;
int bottomY = topY + size - 1;
int leftX = midX - size / 2;
int rightX = leftX + size - 1;
// draw the board with an outline (should see about drawing a thicker
// outline)
Polygon octagon = new Polygon();
octagon.addPoint(midX - sideLength / 2, topY);
octagon.addPoint(midX + sideLength / 2, topY);
octagon.addPoint(rightX, midY - sideLength / 2);
octagon.addPoint(rightX, midY + sideLength / 2);
octagon.addPoint(midX + sideLength / 2, bottomY);
octagon.addPoint(midX - sideLength / 2, bottomY);
octagon.addPoint(leftX, midY + sideLength / 2);
octagon.addPoint(leftX, midY - sideLength / 2);
g.setColor(Color.RED.darker());
g.fillPolygon(octagon);
g.setColor(Color.RED);
g.drawPolygon(octagon);
// figure out size of the squares from the size of the board
// (we're assuming that since the bounding box is a square everything
// will work out so when we pick a size for the squares based
// on the vertical size of a row that will work horizontally too
int columnSpacing = (int)(size / 11.5); // left edge to left edge
int rowSpacing = size / 16; // top to top
int squareSize = rowSpacing * 4 / 5; // fill 80% of space for tow
CantStopBoard board = model.getBoard();
for (int c = 2; c <= 12; c++)
{
// compute left edge of current column and top edge of
// bottom row in that column
int columnX = midX - squareSize / 2 - (7 - c) * columnSpacing;
int columnY = midY - squareSize / 2 + ((board.getColumnLength(c) + 1) / 2 - 1) * rowSpacing;
// draw all the spaces in the current row
for (int space = 1; space <= board.getColumnLength(c); space++)
{
if (board.getColoredMarkerPosition(c) == space)
g.setColor(Color.GREEN);
else if (board.getNeutralMarkerPosition(c) == space)
g.setColor(Color.WHITE);
else
g.setColor(Color.GRAY);
g.fillRect(columnX, columnY - (space - 1) * rowSpacing,
squareSize, squareSize);
}
g.setColor(Color.WHITE);
g.drawString("" + c, columnX, columnY + rowSpacing * 3 / 2);
}
// draw the dice in the lower left corner
g.setColor(Color.WHITE);
g.fillRect(0, getHeight() - 30, 50, 25);
g.setColor(Color.BLACK);
g.setFont(new Font("SansSerif", Font.BOLD, 20));
g.drawString(model.getDice(), 0, getHeight() - 5);
// draw the turn in the lower right corner
// (maybe use FontMetrics to get this exactly right)
g.drawString("Turn " + model.countTurns(), getWidth() - 150, getHeight() - 5);
}
/**
* Repaints this view.
*/
public void update(Observable obs, Object o)
{
repaint();
}
}
CantStopPanel.java
import javax.swing.*;
import java.awt.*;
import java.util.*;
/**
* A view of a Can't Stop Game as a <CODE>JPanel</CODE>.
*
* @author Jim Glenn
* @version 0.1 11/18/2008
*/
public class CantStopPanel extends JPanel implements Observer
{
/**
* The model of the game this panel draws.
*/
private CantStopGame model;
/**
* Creates a panel to display the given game.
*/
public CantStopPanel(CantStopGame g)
{
model = g;
setBackground(Color.LIGHT_GRAY);
}
/**
* Paints this panel. The panel will be painted according to
* the state of the model this panel is associated with.
*/
public void paint(Graphics g)
{
g.clearRect(0, 0, getWidth(), getHeight());
// compute the size of the board so we fill 80% of the smaller dimension
int size = Math.min(getWidth(), getHeight()) * 4 / 5;
// compute some important coordinates
int sideLength = (int)(size / (1 + Math.sqrt(2)));
int midX = getWidth() / 2;
int midY = getHeight() / 2;
int topY = midY - size / 2;
int bottomY = topY + size - 1;
int leftX = midX - size / 2;
int rightX = leftX + size - 1;
// draw the board with an outline (should see about drawing a thicker
// outline)
Polygon octagon = new Polygon();
octagon.addPoint(midX - sideLength / 2, topY);
octagon.addPoint(midX + sideLength / 2, topY);
octagon.addPoint(rightX, midY - sideLength / 2);
octagon.addPoint(rightX, midY + sideLength / 2);
octagon.addPoint(midX + sideLength / 2, bottomY);
octagon.addPoint(midX - sideLength / 2, bottomY);
octagon.addPoint(leftX, midY + sideLength / 2);
octagon.addPoint(leftX, midY - sideLength / 2);
g.setColor(Color.RED.darker());
g.fillPolygon(octagon);
g.setColor(Color.RED);
g.drawPolygon(octagon);
// figure out size of the squares from the size of the board
// (we're assuming that since the bounding box is a square everything
// will work out so when we pick a size for the squares based
// on the vertical size of a row that will work horizontally too
int columnSpacing = (int)(size / 11.5); // left edge to left edge
int rowSpacing = size / 16; // top to top
int squareSize = rowSpacing * 4 / 5; // fill 80% of space for tow
CantStopBoard board = model.getBoard();
for (int c = 2; c <= 12; c++)
{
// compute left edge of current column and top edge of
// bottom row in that column
int columnX = midX - squareSize / 2 - (7 - c) * columnSpacing;
int columnY = midY - squareSize / 2 + ((board.getColumnLength(c) + 1) / 2 - 1) * rowSpacing;
// draw all the spaces in the current row
for (int space = 1; space <= board.getColumnLength(c); space++)
{
if (board.getColoredMarkerPosition(c) == space)
g.setColor(Color.GREEN);
else if (board.getNeutralMarkerPosition(c) == space)
g.setColor(Color.WHITE);
else
g.setColor(Color.GRAY);
g.fillRect(columnX, columnY - (space - 1) * rowSpacing,
squareSize, squareSize);
}
g.setColor(Color.WHITE);
g.drawString("" + c, columnX, columnY + rowSpacing * 3 / 2);
}
// draw the dice in the lower left corner
g.setColor(Color.WHITE);
g.fillRect(0, getHeight() - 30, 50, 25);
g.setColor(Color.BLACK);
g.setFont(new Font("SansSerif", Font.BOLD, 20));
g.drawString(model.getDice(), 0, getHeight() - 5);
// draw the turn in the lower right corner
// (maybe use FontMetrics to get this exactly right)
g.drawString("Turn " + model.countTurns(), getWidth() - 150, getHeight() - 5);
}
/**
* Repaints this view.
*/
public void update(Observable obs, Object o)
{
repaint();
}
}
CantStopControl.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
/**
* The controller for a Can't Atop GUI.
*
* @author Jim Glenn
* @version 0.1 11/24/2008
*/
public class CantStopControl extends JPanel
{
/**
* The model this controller manipulates.
*/
private CantStopGame model;
/**
* The text at the bottom of the panel that tells the player what
* input is expected.
*/
private JTextField messageField;
/**
* The buttons that allow the player to select a move. The action
* command on these buttons will be set to the index of the
* corresponding move into the model's getLegalMoves list.
*/
private JButton[] moveButtons;
/**
* The "Roll" button.
*/
private JButton rollButton;
/**
* The "Stop" button. When the current roll "blows it" this button
* displays "Blow It" to let the player acknowledge the bad roll.
*/
private JButton stopButton;
/**
* Creates a controller connected to the given model.
*
* @param g a model of Can't Stop
*/
public CantStopControl(CantStopGame g)
{
model = g;
setLayout(new BorderLayout());
// create the message display the the bottom of the panel
messageField = new JTextField();
messageField.setEditable(false);
add(messageField, BorderLayout.SOUTH);
// create a 3x3 array of buttons with the move buttons in the top
// 2 rows
JPanel buttonPanel = new JPanel(new GridLayout(3, 3));
moveButtons = new JButton[6];
for (int i = 0; i < 6; i++)
{
moveButtons[i] = new JButton();
moveButtons[i].setActionCommand("" + i);
moveButtons[i].addActionListener(new MoveListener());
buttonPanel.add(moveButtons[i]);
}
// create the roll and stop buttons and place them in the bottom row
rollButton = new JButton("Roll");
rollButton.addActionListener(new RollListener());
stopButton = new JButton();
stopButton.addActionListener(new StopListener());
buttonPanel.add(rollButton);
buttonPanel.add(new JPanel());
buttonPanel.add(stopButton);
// add all the buttons to the center of the panel
add(buttonPanel, BorderLayout.CENTER);
// set the text on the buttons
setButtons();
}
/**
* Sets the text on the buttons (and the message text) according
* to the current state of the model.
*/
private void setButtons()
{
switch (model.getState())
{
case CantStopGame.START_STATE:
clearMoveButtons();
messageField.setText("Click roll to roll the dice.");
rollButton.setEnabled(true);
stopButton.setText("Stop");
stopButton.setEnabled(false);
break;
case CantStopGame.ROLL_STATE:
clearMoveButtons();
messageField.setText("Roll again or end your turn.");
rollButton.setEnabled(true);
stopButton.setText("Stop");
stopButton.setEnabled(true);
break;
case CantStopGame.ACK_STATE:
clearMoveButtons();
messageField.setText("No moves possible -- click \"Blow it\".");
rollButton.setEnabled(false);
stopButton.setText("Blow it");
stopButton.setEnabled(true);
break;
case CantStopGame.MOVE_STATE:
setMoveButtons();
messageField.setText("Choose your move");
rollButton.setEnabled(false);
stopButton.setText("Stop");
stopButton.setEnabled(false);
break;
case CantStopGame.WON_STATE:
clearMoveButtons();
messageField.setText("You won!");
rollButton.setEnabled(false);
stopButton.setText("Stop");
stopButton.setEnabled(false);
break;
}
}
/**
* Sets all the move buttons to blank and disables them.
* This is intended to be used to set the buttons at the
* start of a turn before a roll has been made.
*/
private void clearMoveButtons()
{
for (int i = 0; i < moveButtons.length; i++)
{
moveButtons[i].setText("");
moveButtons[i].setEnabled(false);
}
}
/**
* Sets the text on the move buttons according to the currently
* level moves and enables them. This is intended to be used
* after a good roll (one that doesn't blow it.
*/
private void setMoveButtons()
{
java.util.List moves = model.getLegalMoves();
for (int i = 0; i < moveButtons.length; i++)
{
if (i < moves.size())
{
java.util.List move = (java.util.List)(moves.get(i));
String label = "";
Iterator j = move.iterator();
while (j.hasNext())
{
label = label + j.next();
if (j.hasNext())
label = label + " - ";
}
moveButtons[i].setText(label);
moveButtons[i].setEnabled(true);
}
else
{
// disable buttons if fewer than 6 legal moves
moveButtons[i].setText("");
moveButtons[i].setEnabled(false);
}
}
}
/**
* Relays clicks on the move buttons to the model.
*/
private class MoveListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
model.makeMove(Integer.parseInt(e.getActionCommand()));
setButtons();
}
}
/**
* Relays clicks on the roll button to the model.
*/
private class RollListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
// tell the model to roll the dice
model.roll();
// reset the buttons
setButtons();
}
}
/**
* Relays clicks on the roll/blow it button to the model.
*/
private class StopListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
// determine if we're stopping or blowing it
if (model.getState() == CantStopGame.ACK_STATE)
{
model.blowIt();
}
else
{
model.endTurn();
}
// reset the buttons accordingly
setButtons();
}
}
}
This code can also be downloaded from the files
CantStopGame.java,
CantStopBoard.java,
CantStopPanel.java,
CantStopPanel.java,
and CantStopControl.java.