/** * This class implements a state machine to coordinate gameplay. * * Assignment: MP3 * Class: CS 340, Fall 2005 * TA: Nitin Jindal * System: jdk-1.5.0.4 and Eclipse 3.1 on Windows XP * @author Michael Leonhard (CS account mleonhar) * @version 12 Oct 2005 */ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.Timer; public class State implements ActionListener { // the delay between computer turns (in milliseconds) private static final int COMPUTER_TURN_DELAY = 1000; // State: user's mouse wanders over board, hovering over lines private static final int WANDER_STATE = 0; // State: user's mouse was pressed on the hovered line and not moved off it private static final int PRESSED_STATE = 1; // State: the mouse was clicked and then moved off the hovered line private static final int WAITMOUSEUP_STATE = 2; // State: it is the computer's turn private static final int COMPUTER_STATE = 3; // State: computer made a turn and is waiting to make its next turn (so user // can see which turn it just made) private static final int COMPUTERWAIT_STATE = 4; // State: the game has ended private static final int ENDGAME_STATE = 5; // users's point counter private CounterLabel userCounter; // computer's point counter private CounterLabel computerCounter; // the current state private int currentState; // the line the mouse is near private Line hoveredLine; // the line chosen by the computer private Line chosenLine; // the field whose state we keep private Field field; // timer used to add a delay after computer turns Timer delayTimer; /** * Finds a string representation for the specified state * * @param state the state to look up * @return a string representation of the state */ public static String stateString(int state) { if (state == WANDER_STATE) return "WANDER_STATE"; if (state == PRESSED_STATE) return "PRESSED_STATE"; if (state == WAITMOUSEUP_STATE) return "WAITMOUSEUP_STATE"; if (state == COMPUTER_STATE) return "COMPUTER_STATE"; if (state == COMPUTERWAIT_STATE) return "COMPUTERWAIT_STATE"; if (state == ENDGAME_STATE) return "ENDGAME_STATE"; return "UNKNOWN_STATE!!!"; } /** * Makes a string representation of the state object * * @return a string representation of the object */ public String toString() { return "State[" + State.stateString(this.currentState) + "]"; } /** * Makes new state machine that starts in WANDER state * * @param field the game field * @param computerCounter counter for computer's score * @param userCounter counter for user's score */ public State(Field field, CounterLabel userCount, CounterLabel computerCount) { // save references this.field = field; this.userCounter = userCount; this.computerCounter = computerCount; // make the timer object this.delayTimer = new Timer(COMPUTER_TURN_DELAY, this); // set initial state this.currentState = WANDER_STATE; } /** * Called by field when the mouse button is released * * @param nearestLine the mouse was released near this line */ public void mouseReleased(Line nearestLine) { // System.out.println("State.mouseReleased() " + this + " nearestLine=" // + nearestLine); // PRESSED if (this.currentState == PRESSED_STATE) { // the mouse was released on the same line if (nearestLine == this.hoveredLine) { // draw the line this.setHoveredLine(null); int boxesMade = nearestLine.draw(true); // at least one box was made if (boxesMade > 0) { // add to the user's score this.userCounter.add(boxesMade); // user's gets another turn enterState(WANDER_STATE); } // no box was made, so it is now the computer's turn else enterState(COMPUTER_STATE); } // the mouse was not released on the same line where it was pressed else enterState(WANDER_STATE); } // WAITMOUSEUP else if (this.currentState == WAITMOUSEUP_STATE) enterState(WANDER_STATE); // mouse release event is ignored in all other states } /** * Called by field when mouse button is pressed down * * @param nearestLine the mouse was pressed near this line */ public void mousePressed(Line nearestLine) { // System.out.println("State.mousePressed() " + this + " nearestLine=" // + nearestLine); // WANDER if (this.currentState == WANDER_STATE) { // press was not near any line if (nearestLine == null) enterState(WAITMOUSEUP_STATE); // press was near the line else { // line is drawn and cannot be chosen if (nearestLine.isDrawn()) enterState(WAITMOUSEUP_STATE); // line is not drawn, so choose it else { this.hoveredLine = nearestLine; enterState(PRESSED_STATE); } } } // mouse press events are ignored in all other states } /** * Called by field when mouse moves. * * @param nearestLine the mouse is nearest to this line */ public void mouseMoved(Line nearestLine) { // System.out.println("State.mouseMoved() " + this + " nearestLine=" // + nearestLine); // WANDER if (this.currentState == WANDER_STATE) { // line exists and is drawn, so it cannot be chosen if (nearestLine != null && nearestLine.isDrawn()) this .setHoveredLine(null); // line is not drawn, so it can be chosen (and hovered over) else this.setHoveredLine(nearestLine); } // PRESSED else if (this.currentState == PRESSED_STATE) { // mouse has moved off hoveredLine if (nearestLine != this.hoveredLine) enterState(WAITMOUSEUP_STATE); } // mouse movements are ignored in all other states } /** * Enters the specified state * * @param newState the new state to enter */ private void enterState(int newState) { // System.out.println("State.enterState() " + this + "newState=" // + State.stateString(newState)); // entering WANDER if (newState == WANDER_STATE) { // field contains no undrawn line if (!this.field.hasUndrawnLine()) enterState(ENDGAME_STATE); // field contains an undrawn line else this.currentState = WANDER_STATE; } // entering PRESSED else if (newState == PRESSED_STATE) this.currentState = PRESSED_STATE; // entering WAITMOUSEUP else if (newState == WAITMOUSEUP_STATE) { // hover over no line this.setHoveredLine(null); // enter the state this.currentState = WAITMOUSEUP_STATE; } // entering COMPUTER else if (newState == COMPUTER_STATE) { // field contains no undrawn line if (!this.field.hasUndrawnLine()) enterState(ENDGAME_STATE); // field has an undrawn line else { // choose a line this.chosenLine = this.field.computerChooseLine(); // show the computer chosen line (in red) this.chosenLine.showAsChosen(); // wait a while so the user can see the chosen line enterState(COMPUTERWAIT_STATE); } } // entering COMPUTERWAIT else if (newState == COMPUTERWAIT_STATE) { // start timer (no race condition because Swing callbacks come from // one thread) this.delayTimer.start(); // enter the state this.currentState = COMPUTERWAIT_STATE; } // entering ENDGAME else this.currentState = ENDGAME_STATE; } /** * Called when timer event occurs, used to add delay after computer turn * * @param e information about the event */ public void actionPerformed(ActionEvent e) { // stop timer so it doesn't trigger again this.delayTimer.stop(); // COMPUTERWAIT if (this.currentState == COMPUTERWAIT_STATE) { // draw the chosen line int boxesMade = this.chosenLine.draw(false); // the computer made a box if (boxesMade > 0) { // add to the computer's score this.computerCounter.add(boxesMade); // computer gets another turn enterState(COMPUTER_STATE); } // the computer made no box, so now it is the user's turn // TODO show currently hovered line, don't wait for mouse move else enterState(WANDER_STATE); } // timer event is ignored by all other states } /** * Handles updating the hovered line and redrawing * * @param newHoveredLine the mouse is hovering over this line */ private void setHoveredLine(Line newHoveredLine) { // System.out.println("State.setHoveredLine() " + this.hoveredLine // + " -> " + newHoveredLine); // the line is the same, so do nothing if (newHoveredLine == this.hoveredLine) return; // old line exists, so stop showing it if (this.hoveredLine != null) this.hoveredLine.stopShowing(); // new line exists, so show it if (newHoveredLine != null) newHoveredLine.showAsHovered(); // change to new line this.hoveredLine = newHoveredLine; } }