package org.squirrelframework.foundation.fsm.snake; import org.squirrelframework.foundation.component.SquirrelProvider; import org.squirrelframework.foundation.fsm.AnonymousCondition; import org.squirrelframework.foundation.fsm.DotVisitor; import org.squirrelframework.foundation.fsm.HistoryType; import org.squirrelframework.foundation.fsm.TransitionType; import org.squirrelframework.foundation.fsm.annotation.*; import org.squirrelframework.foundation.fsm.impl.AbstractUntypedStateMachine; import org.squirrelframework.foundation.fsm.snake.SnakeController.SnakeEvent; import org.squirrelframework.foundation.fsm.snake.SnakeController.SnakeState; import java.awt.*; import java.util.Random; /** * This is an example on how to use state machine to build a game controller. The state machine was defined in declarative manner. * * @author Henry.He * */ @States({ @State(name="NEW"), @State(name="MOVE", historyType=HistoryType.DEEP), @State(parent="MOVE", name="UP", initialState=true), @State(parent="MOVE", name="LEFT"), @State(parent="MOVE", name="RIGHT"), @State(parent="MOVE", name="DOWN"), @State(name="PAUSE"), @State(name="GAMEOVER") }) @Transitions({ @Transit(from="NEW", to="MOVE", on="PRESS_START", callMethod="onStart"), @Transit(from = "GAMEOVER", to = "MOVE", on = "PRESS_START", callMethod = "onStart"), @Transit(from = "MOVE", to = "GAMEOVER", on = "MOVE_AHEAD", callMethod = "onEnd"), @Transit(from = "UP", to = "UP", on = "MOVE_AHEAD", callMethod = "onMove", type = TransitionType.INTERNAL, when = SnakeController.ContinueRunningCondition.class), @Transit(from = "DOWN", to = "DOWN", on = "MOVE_AHEAD", callMethod = "onMove", type = TransitionType.INTERNAL, when = SnakeController.ContinueRunningCondition.class), @Transit(from = "LEFT", to = "LEFT", on = "MOVE_AHEAD", callMethod = "onMove", type = TransitionType.INTERNAL, when = SnakeController.ContinueRunningCondition.class), @Transit(from = "RIGHT", to = "RIGHT", on = "MOVE_AHEAD", callMethod = "onMove", type = TransitionType.INTERNAL, when = SnakeController.ContinueRunningCondition.class), @Transit(from="MOVE", to="PAUSE", on="PRESS_PAUSE", callMethod="onPause"), @Transit(from="PAUSE", to="MOVE", on="PRESS_PAUSE", callMethod="onResume"), @Transit(from="UP", to="LEFT", on="TURN_LEFT", callMethod="onChangeDirection"), @Transit(from="UP", to="RIGHT", on="TURN_RIGHT", callMethod="onChangeDirection"), @Transit(from="DOWN", to="LEFT", on="TURN_LEFT", callMethod="onChangeDirection"), @Transit(from="DOWN", to="RIGHT", on="TURN_RIGHT", callMethod="onChangeDirection"), @Transit(from="LEFT", to="UP", on="TURN_UP", callMethod="onChangeDirection"), @Transit(from="LEFT", to="DOWN", on="TURN_DOWN", callMethod="onChangeDirection"), @Transit(from="RIGHT", to="UP", on="TURN_UP", callMethod="onChangeDirection"), @Transit(from="RIGHT", to="DOWN", on="TURN_DOWN", callMethod="onChangeDirection") }) @StateMachineParameters(stateType=SnakeState.class, eventType=SnakeEvent.class, contextType=SnakeModel.class) public class SnakeController extends AbstractUntypedStateMachine { public enum SnakeState { NEW, UP, LEFT, RIGHT, DOWN, MOVE, PAUSE, GAMEOVER } public enum SnakeEvent { PRESS_START, TURN_UP, TURN_LEFT, TURN_RIGHT, TURN_DOWN, MOVE_AHEAD, PRESS_PAUSE } public static class ContinueRunningCondition extends AnonymousCondition<SnakeModel> { @Override public boolean isSatisfied(SnakeModel context) { Point nextPoint = computeNextPoint(context.peekFirst(), context.getDirection()); boolean insideBorder = nextPoint.x >= 0 && nextPoint.x < GameConfigure.COL_COUNT && nextPoint.y >= 0 && nextPoint.y < GameConfigure.ROW_COUNT; boolean bodyNotCollapsed = context.getSnakePoints().contains(nextPoint)==false; return insideBorder && bodyNotCollapsed; } } private Random random = new Random(); protected void onStart(SnakeState from, SnakeState to, SnakeEvent event, SnakeModel snakeModel) { snakeModel.clear(); Point startPoint = new Point(GameConfigure.COL_COUNT / 2, GameConfigure.ROW_COUNT / 2); snakeModel.push(startPoint); snakeModel.setDirection(snakeModel.getDirection()); // generate random fruit point snakeModel.spawnFruit(getNextFruitIndex(snakeModel)); } private int getNextFruitIndex(SnakeModel snakeModel) { return random.nextInt(GameConfigure.COL_COUNT * GameConfigure.ROW_COUNT - snakeModel.length()); } protected void onMove(SnakeState from, SnakeState to, SnakeEvent event, SnakeModel snakeModel) { Point nextSnakePoint = computeNextPoint(snakeModel.peekFirst(), snakeModel.getDirection()); if (nextSnakePoint.equals(snakeModel.getFruitPos())) { snakeModel.spawnFruit(getNextFruitIndex(snakeModel)); } else if (snakeModel.length()>=GameConfigure.MIN_SNAKE_LENGTH) { snakeModel.removeLast(); } snakeModel.push(nextSnakePoint); } protected void onPause(SnakeState from, SnakeState to, SnakeEvent event, SnakeModel snakeModel) { } protected void onResume(SnakeState from, SnakeState to, SnakeEvent event, SnakeModel snakeModel) { } protected void onChangeDirection(SnakeState from, SnakeState to, SnakeEvent event, SnakeModel snakeModel) { snakeModel.setDirection(getSnakeDirection(to)); } public static SnakeDirection getSnakeDirection(SnakeState state) { switch (state) { case DOWN: return SnakeDirection.DOWN; case LEFT: return SnakeDirection.LEFT; case RIGHT: return SnakeDirection.RIGHT; } return SnakeDirection.UP; } protected void onEnd(SnakeState from, SnakeState to, SnakeEvent event, SnakeModel context) { } public static Point computeNextPoint(Point snakePos, SnakeDirection direction) { Point nextPoint=new Point(snakePos); switch (direction) { case UP: nextPoint.y--; break; case DOWN: nextPoint.y++; break; case LEFT: nextPoint.x--; break; case RIGHT: nextPoint.x++; break; } return nextPoint; } public void export() { // export snake game state machine DotVisitor visitor = SquirrelProvider.getInstance().newInstance(DotVisitor.class); this.accept(visitor); visitor.convertDotFile("SnakeStateMachine"); } }