Skip to content

Instantly share code, notes, and snippets.

@guoguo12
Last active February 22, 2016 02:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save guoguo12/6967063 to your computer and use it in GitHub Desktop.
Save guoguo12/6967063 to your computer and use it in GitHub Desktop.
Released source code files from Lines 1.0, a puzzle game created by Sparkfire Games. Get the binary at https://github.com/guoguo12/guoguo12.github.io/tree/master/projects/lines.
package sparkfire.lines.game;
import sparkfire.lines.actors.GameNode;
import sparkfire.lines.actors.GameWall;
import com.badlogic.gdx.Gdx;
/**
* Contains conversion functions for locations of in-game objects, such as
* {@link GameNode} and {@link GameWall} objects.
*
* @author Allen Guo
*/
public class CoordinateUtils {
private static final float WIDTH = Gdx.graphics.getWidth();
private static final float HEIGHT = Gdx.graphics.getHeight();
public static float toDrawingCoordinateX(int gameCoordinateX) {
return gameCoordinateX * 20 + ((WIDTH - 800) / 2) + 20;
}
public static float toDrawingCoordinateY(int gameCoordinateY) {
return gameCoordinateY * 20 + ((HEIGHT - 600) / 2) + 20;
}
public static int toGameCoordinateX(float drawingCoordinateX) {
return (int) ((drawingCoordinateX - ((WIDTH - 800) / 2) - 20) / 20);
}
public static int toGameCoordinateY(float drawingCoordinateY) {
return (int) ((drawingCoordinateY- ((HEIGHT - 600) / 2) - 20) / 20);
}
}
package sparkfire.lines.actors;
import sparkfire.lines.game.LinesGame;
import sparkfire.lines.stages.GameStage;
import static sparkfire.lines.game.CoordinateUtils.*;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.Action;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.utils.Array;
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
/**
* Game node.
*
* @author Allen Guo
*/
public class GameNode extends Actor {
private TextureRegion nodeTexture;
private TextureRegion selectionRingTexture;
private Array<GameNode> connectedNodes;
private Action action;
private boolean actionComplete; // Starts as false.
private boolean isSelected; // Starts as false.
private int value; // Used for solver algorithms.
private GameNode previous; // Used for solver algorithms.
/**
* Creates GameNode at given in-game coordinates.
*/
public GameNode(int x, int y, TextureRegion nodeTexture, TextureRegion selectionRingTexture) {
LinesGame.log(this, "Creating node...");
this.nodeTexture = nodeTexture;
this.selectionRingTexture = selectionRingTexture;
connectedNodes = new Array<GameNode>();
value = -1;
setX(toDrawingCoordinateX(x) + Gdx.graphics.getWidth());
setY(toDrawingCoordinateY(y));
addAction(action = moveTo(toDrawingCoordinateX(x), toDrawingCoordinateY(y), GameStage.LEVEL_TRANSITION_TIME));
}
@Override
public void act(float delta) {
if (action.act(delta)) {
actionComplete = true;
}
}
@Override
public void draw (SpriteBatch batch, float parentAlpha) {
if (isSelected) {
batch.draw(selectionRingTexture, getX() - 20, getY() - 20); // 20 is the semi-height and semi-width.
}
batch.draw(nodeTexture, getX() - 20, getY() - 20);
}
/**
* Selects this node.
*/
public void select() {
isSelected = true;
}
/**
* Deselects this node.
*/
public void deselect() {
isSelected = false;
}
/**
* Returns true if transition action is complete.
*/
public boolean actionIsComplete() {
return actionComplete;
}
/**
* Returns true if this node is selected.
*/
public boolean isSelected() {
return isSelected;
}
/**
* Adds give node to {@link Array} of nodes to which this node is connected.
*/
public void addConnectedNode(GameNode node) {
connectedNodes.add(node);
}
/**
* Returns if there is at least one node connected to this node.
*/
public boolean isConnected() {
return connectedNodes.size > 0 ? true : false;
}
/**
* Clears {@link Array} of nodes to which this node is connected
* via a {@link GameConnector}.
*/
public void resetConnectedNodes() {
connectedNodes.clear();
}
/**
* Returns true if this node and the given node are connected by
* a {@link GameConnector}.
*/
public boolean isDirectlyConnectedTo(GameNode node) {
return connectedNodes.contains(node, true);
}
/**
* Returns true if this node and the given node share a horizontal line
* or a vertical line.
*/
public boolean isOrthogonalTo(GameNode node) {
return getCommonAxis(node) == 1 || getCommonAxis(node) == 0;
}
/**
* Returns 1 if the nodes are on the same horizontal line, 0 if they are on the same vertical line,
* and -1 if they do not share a horizontal or vertical line.
*/
public int getCommonAxis(GameNode node) {
if (getX() == node.getX()) {
return 0;
} else if (getY() == node.getY()) {
return 1;
} else {
return -1;
}
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public GameNode getPrevious() {
return previous;
}
public void setPrevious(GameNode previous) {
this.previous = previous;
}
@Override
public String toString() {
if (value == -1) {
return "GameNode at (" + toGameCoordinateX(getX()) + ", " + toGameCoordinateY(getY()) + ")";
} else {
return "GameNode at (" + toGameCoordinateX(getX()) + ", " + toGameCoordinateY(getY()) + ") with value " + value;
}
}
}
package sparkfire.lines.stages;
import java.util.Iterator;
import sparkfire.lines.actors.GameConnector;
import sparkfire.lines.actors.GameNode;
import sparkfire.lines.actors.GameWall;
import sparkfire.lines.game.LinesGame;
import sparkfire.lines.screens.LevelSelectScreen;
import sparkfire.lines.skins.LinesSkin;
import sparkfire.lines.ui.LevelLabel;
import static sparkfire.lines.game.CoordinateUtils.*;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Buttons;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Timer;
import com.badlogic.gdx.utils.Timer.Task;
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
/**
* Main game logic and graphics.
*
* @author Allen Guo
*/
public class GameStage extends Stage {
/**
* Duration of node/wall entrances.
* Also, duration of the pause before level advances.
* Nodes slide in from the right; walls slide in from the left.
*/
public static final float LEVEL_TRANSITION_TIME = 0.75f;
// Default visibility allows for in-package use.
LinesGame game;
LinesSkin skin;
String[] nodeConfigurations;
String[] wallConfigurations;
private TextureRegion[] textures;
private ShapeRenderer connectorRenderer;
private LevelLabel levelLabel;
private Array<GameNode> nodes;
private Array<GameConnector> connectors;
private Array<GameWall> walls;
private Array<Label> helpText;
private GameNode selectedNode; // Starts as null.
private GameNode alphaNode; // Starts as null.
private GameNode omegaNode; // Starts as null.
private int level; // Starts as zero. (The first level is level one.)
private final int levelsAvailable; // Derived from the number of node configurations.
private boolean movementLocked;
/**
* Constructor loads necessary drawing tools, UI components,
* and game logic components, but not the initial level.
* Also sets up {@link InputListener} for processing input events.
*/
public GameStage(final LinesGame game, LinesSkin skin) {
super();
LinesGame.log(this, "Creating main game stage...");
this.game = game;
this.skin = skin;
loadTextures();
connectorRenderer = new ShapeRenderer();
levelLabel = new LevelLabel(game, skin);
nodes = new Array<GameNode>();
connectors = new Array<GameConnector>();
walls = new Array<GameWall>();
helpText = new Array<Label>();
nodeConfigurations = Gdx.files.internal("files/nodes").readString().split("\r\n");
LinesGame.log(this, (nodeConfigurations.length - 1) + " node level configurations found.");
wallConfigurations = Gdx.files.internal("files/walls").readString().split("\r\n");
LinesGame.log(this, (wallConfigurations.length - 1) + " wall level configurations found.");
levelsAvailable = nodeConfigurations.length - 1;
addListener(new InputListener() {
/**
* Stores keyboard input to process debug codes.
*/
private StringBuilder keyboardHistory;
@Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
if (button == Buttons.LEFT) {
// Check all nodes to see if clicked...
for (int i = 0; i < nodes.size; i++) {
GameNode node = nodes.get(i);
// If node is clicked...
if (Math.abs(node.getX() - x) < 10 && Math.abs(node.getY() - y) < 10) {
// If level transition is complete, the nodes are not the same, are not already connected, share an axis, and the path is not blocked...
if (alphaNode.actionIsComplete() && nodeIsConnectable(node) && !movementLocked) {
moveToNode(node);
checkDeadEnd(node);
checkFinalNode(node);
}
break; // No need to test for additional clicks if one node has been clicked.
}
}
}
return false;
}
@Override
public boolean keyUp(InputEvent event, int keycode) {
if (!movementLocked) {
if (keycode == Keys.ESCAPE) {
game.showGameMenu(false);
} else if (keycode == Keys.BACKSPACE) {
resetConnectors();
}
}
return true;
}
@Override
public boolean keyDown(InputEvent event, int keycode) {
if (!movementLocked) {
if (keycode == Keys.LEFT || keycode == Keys.A) {
moveLeft();
} else if (keycode == Keys.RIGHT || keycode == Keys.D) {
moveRight();
} else if (keycode == Keys.UP || keycode == Keys.W) {
moveUp();
} else if (keycode == Keys.DOWN || keycode == Keys.S) {
moveDown();
}
}
return true;
}
@Override
public boolean keyTyped(InputEvent event, char character) {
if (LinesGame.DEVELOPMENT_MODE) {
if (keyboardHistory == null) {
keyboardHistory = new StringBuilder();
}
keyboardHistory.append(character);
boolean codeActivated = true;
if (keyboardHistory.lastIndexOf("~level") != -1) {
advanceLevel();
} else if (keyboardHistory.lastIndexOf("~mute") != -1) {
game.getAssetCache().musicPlayer.stopMusic();
} else if (keyboardHistory.lastIndexOf("~music") != -1) {
game.getAssetCache().musicPlayer.play();
} else if (keyboardHistory.lastIndexOf("~clear") != -1) {
clear();
} else if (keyboardHistory.lastIndexOf("~reset") != -1) {
resetConnectors();
} else if (keyboardHistory.lastIndexOf("~solve") != -1) {
solve();
} else if (keyboardHistory.lastIndexOf("~exit") != -1) {
game.exit();
} else {
codeActivated = false;
}
if (codeActivated) {
keyboardHistory.delete(0, keyboardHistory.length() - 1);
}
return true;
} else {
return false;
}
}
});
}
private boolean nodeIsConnectable(GameNode node) {
return !selectedNode.equals(node) && !selectedNode.isDirectlyConnectedTo(node) && node.isOrthogonalTo(selectedNode) && pathIsFree(node, selectedNode);
}
/**
* Checks if the given node is a dead end.
* If it is, changes the color of the connectors.
*/
private void checkDeadEnd(GameNode currentNode) {
int count = 0;
for (GameNode node : nodes) {
if (nodeIsConnectable(node)) {
count++;
}
}
if (count == 0) {
for (GameConnector connector : connectors) {
connector.setColor(GameConnector.red);
}
}
}
/**
* Checks if the given node is the final node.
* If it is, advances the level.
*/
private void checkFinalNode(GameNode currentNode) {
if (currentNode == omegaNode) {
for (GameConnector connector : connectors) {
connector.setColor(GameConnector.green);
}
movementLocked = true;
Timer.schedule(new Task() {
@Override
public void run() {
advanceLevel();
}
}, LEVEL_TRANSITION_TIME);
}
}
/**
* Starts game at level specified in the {@link LevelSelectScreen}.
*/
public void beginGame() {
level = game.getAssetCache().levelSelectScreen.getSelectedLevel();
LinesGame.log(this, "Beginning game at level " + level + "...");
level -= 1;
advanceLevel();
}
/**
* Resumes game from pause menu.
*/
public void resumeGame() {
// Currently empty, but called on resume nevertheless.
}
/**
* Returns preloaded textures as an array in the following order:
* white node, blue node, green node, selection ring, wall (vertical), wall (horizontal).
*/
private void loadTextures() {
textures = new TextureRegion[6];
Texture atlas = new Texture("images/atlas.png");
atlas.setFilter(TextureFilter.Linear, TextureFilter.Linear);
for (int i = 0; i < textures.length; i++) {
textures[i] = new TextureRegion(atlas, i * 40, 0, 40, 40);
}
}
/**
* Sets up new level if a further level exists.
* Also saves highest level reached.
*/
void advanceLevel() {
level++;
// If all levels completed...
if (level > levelsAvailable) {
LinesGame.log(this, "All levels completed.");
game.getAssetCache().preferences.putBoolean("gameCompleted", true);
game.getAssetCache().preferences.flush();
game.showCredits();
} else {
LinesGame.log(this, "Advancing to level " + level + "...");
if (level > game.getAssetCache().preferences.getInteger("highestLevelReached", 1)) {
game.getAssetCache().preferences.putInteger("highestLevelReached", level);
game.getAssetCache().preferences.flush();
}
clear();
helpText.clear();
setUpNodes(level);
setUpWalls(level);
setUpHelpText(level);
addActor(levelLabel);
levelLabel.showNewLevel(level);
movementLocked = false;
}
}
/**
* Sets up nodes for given level.
*/
void setUpNodes(int level) {
String[] nodes = nodeConfigurations[level].split("\t");
int nodeCount = nodes.length;
LinesGame.log(this, "Setting up " + nodeCount + " nodes...");
addAlphaNode(Integer.parseInt(nodes[0].split("x")[0]), Integer.parseInt(nodes[0].split("x")[1]));
for (int i = 1; i < nodeCount - 1; i++) {
addNode(Integer.parseInt(nodes[i].split("x")[0]), Integer.parseInt(nodes[i].split("x")[1]));
}
addOmegaNode(Integer.parseInt(nodes[nodeCount - 1].split("x")[0]), Integer.parseInt(nodes[nodeCount - 1].split("x")[1]));
}
/**
* Sets up walls for given level.
*/
void setUpWalls(int level) {
if (!wallConfigurations[level].equals("none")) {
String[] walls = wallConfigurations[level].split("\t");
LinesGame.log(this, "Setting up " + walls.length + " walls...");
for (String wall: walls) {
addWall(Integer.parseInt(wall.split("x")[0]), Integer.parseInt(wall.split("x")[1]), Integer.parseInt(wall.split("x")[2]) == 1);
}
} else {
LinesGame.log(this, "No walls to set up for this level.");
}
}
private void setUpHelpText(int level) {
if (level <= 4) {
String text = null;
float x = 0;
float y = 0;
switch(level) {
case 1:
text = "Starting at the blue node, connect the white nodes to reach the green node. You may only move horizontally or vertically.\n\nUse the mouse or WASD keys to travel from node to node.";
x = Gdx.graphics.getWidth() / 2 - 200;
y = Gdx.graphics.getHeight() / 2 - 100;
break;
case 2:
text = "Walls block horizontal or vertical movement, depending on how they are oriented.";
x = Gdx.graphics.getWidth() / 2;
y = Gdx.graphics.getHeight() / 2 + 120;
break;
case 3:
text = "Not every node needs to be used.\n\nIf you get stuck or reach a dead end, reset the lines with the Backspace key.";
x = Gdx.graphics.getWidth() / 2;
y = Gdx.graphics.getHeight() / 2 - 200;
break;
case 4:
text = "This is the end of the Lines tutorial.\n\nGood luck!";
x = Gdx.graphics.getWidth() / 2 - 230;
y = Gdx.graphics.getHeight() / 2 + 60;
break;
}
Label label = new Label(text, skin, "label-small");
label.setWrap(true);
label.setWidth(270);
label.setPosition(x - label.getWidth() / 2, y - label.getHeight() / 2);
label.addAction(sequence(alpha(0), fadeIn(LEVEL_TRANSITION_TIME), forever(sequence(alpha(1), alpha(0.7f, 1.5f), alpha(1, 1.5f)))));
addActor(label);
helpText.add(label);
}
}
/**
* Adds plain (non-alpha and non-omega) node at given game coordinates.
*/
private void addNode(int x, int y) {
GameNode node = new GameNode(x, y, textures[0], textures[3]);
addActor(node);
nodes.add(node);
}
/**
* Adds first node at given game coordinates and selects it.
*/
private void addAlphaNode(int x, int y) {
GameNode node = new GameNode(x, y, textures[1], textures[3]);
addActor(node);
nodes.add(node);
alphaNode = node;
selectNode(node);
}
/**
* Adds at given game coordinates last node.
*/
private void addOmegaNode(int x, int y) {
GameNode node = new GameNode(x, y, textures[2], textures[3]);
addActor(node);
nodes.add(node);
omegaNode = node;
}
/**
* Selects given node. Does not deselect currently selected node.
*/
private void selectNode(GameNode node) {
selectedNode = node;
node.select();
}
/**
* Deselects currently selected node, if not null.
*/
private void deselectNode() {
if (selectedNode != null) {
selectedNode.deselect();
selectedNode = null;
}
}
/**
* Creates and adds connector with given in-game coordinates.
* Creates but does not add connector if one is already present for given points.
*/
private void addConnector(int startX, int startY, int endX, int endY, Color color) {
connectors.add(new GameConnector(startX, startY, endX, endY, color));
}
/**
* Resets connectors and selects alpha node.
*/
public void resetConnectors() {
LinesGame.log(this, "Reseting connectors...");
connectors.clear();
for (int i = 0; i < nodes.size; i++) {
nodes.get(i).resetConnectedNodes();
}
deselectNode();
selectNode(alphaNode);
}
/**
* Creates and adds wall with given in-game coordinates.
*/
private void addWall(int x, int y, boolean isVertical) {
GameWall wall;
if (isVertical) {
wall = new GameWall(x, y, textures[4], true);
} else {
wall = new GameWall(x, y, textures[5], false);
}
addActor(wall);
walls.add(wall);
}
/**
* Removes node or wall at given game coordinates.
* Returns true if a node or wall is removed.
* Untested since switch to fullscreen, but theoretically usable.
*/
@SuppressWarnings("unused")
private boolean remove(int x, int y) {
float drawingX = toDrawingCoordinateX(x);
float drawingY = toDrawingCoordinateY(y);
Iterator<GameNode> nodeIterator = nodes.iterator();
while (nodeIterator.hasNext()) {
GameNode node = nodeIterator.next();
if (node.getX() == drawingX && node.getY() == drawingY) {
nodeIterator.remove();
getActors().removeValue(node, true);
return true;
}
}
Iterator<GameWall> wallIterator = walls.iterator();
while (wallIterator.hasNext()) {
GameWall wall = wallIterator.next();
if (wall.getX() == drawingX && wall.getY() == drawingY) {
wallIterator.remove();
getActors().removeValue(wall, true);
return true;
}
}
return false;
}
/**
* Connects currently selected node and given node, then selects the given node.
* Does not check for move validity.
*/
private void moveToNode(GameNode node) {
LinesGame.log(this, "Moving to " + node + "...");
addConnector(toGameCoordinateX(selectedNode.getX()), toGameCoordinateY(selectedNode.getY()), toGameCoordinateX(node.getX()), toGameCoordinateY(node.getY()), Color.WHITE);
selectedNode.addConnectedNode(node);
node.addConnectedNode(selectedNode);
deselectNode();
selectNode(node);
}
private void moveLeft() {
for (int i = 0; i < nodes.size; i++) {
GameNode node = nodes.get(i);
if (alphaNode.actionIsComplete() && node.getCommonAxis(selectedNode) == 1 && node.getX() < selectedNode.getX() && nodeIsConnectable(node)) {
moveToNode(node);
checkDeadEnd(node);
checkFinalNode(node);
break;
}
}
}
private void moveRight() {
for (int i = 0; i < nodes.size; i++) {
GameNode node = nodes.get(i);
if (alphaNode.actionIsComplete() && node.getCommonAxis(selectedNode) == 1 && node.getX() > selectedNode.getX() && nodeIsConnectable(node)) {
moveToNode(node);
checkDeadEnd(node);
checkFinalNode(node);
break;
}
}
}
private void moveUp() {
for (int i = 0; i < nodes.size; i++) {
GameNode node = nodes.get(i);
if (alphaNode.actionIsComplete() && node.getCommonAxis(selectedNode) == 0 && node.getY() > selectedNode.getY() && nodeIsConnectable(node)) {
moveToNode(node);
checkDeadEnd(node);
checkFinalNode(node);
break;
}
}
}
private void moveDown() {
for (int i = 0; i < nodes.size; i++) {
GameNode node = nodes.get(i);
if (alphaNode.actionIsComplete() && node.getCommonAxis(selectedNode) == 0 && node.getY() < selectedNode.getY() && nodeIsConnectable(node)) {
moveToNode(node);
checkDeadEnd(node);
checkFinalNode(node);
break;
}
}
}
/**
* Moves along the given {@link Array} path, starting
* from the currently selected node. Does not check
* for move validity.
*/
private void traverse(Array<GameNode> order) {
if (order != null && order.size >= 2) {
while (order.size != 0) {
moveToNode(order.first());
order.removeIndex(0);
}
}
}
/**
* Returns true if the given nodes, which share a horizontal or vertical, can be connected by a connector.
* For instance, returns false if the path between the nodes is blocked by a wall.
*/
private boolean pathIsFree(GameNode node1, GameNode node2) {
if (node1.getCommonAxis(node2) == 1) {
for (GameWall wall : walls) {
if (wall.getY() == node1.getY() && wall.isVertical() && ((node1.getX() > wall.getX() && node2.getX() < wall.getX()) || (node2.getX() > wall.getX() && node1.getX() < wall.getX()))) {
return false;
}
}
for (int i = 0; i < nodes.size; i++) {
GameNode node = nodes.get(i);
if (node.getY() == node1.getY() && ((node1.getX() > node.getX() && node2.getX() < node.getX()) || (node2.getX() > node.getX() && node1.getX() < node.getX()))) {
return false;
}
}
} else {
for (GameWall wall : walls) {
if (wall.getX() == node1.getX() && !wall.isVertical() && ((node1.getY() > wall.getY() && node2.getY() < wall.getY()) || (node2.getY() > wall.getY() && node1.getY() < wall.getY()))) {
return false;
}
}
for (int i = 0; i < nodes.size; i++) {
GameNode node = nodes.get(i);
if (node.getX() == node1.getX() && ((node1.getY() > node.getY() && node2.getY() < node.getY()) || (node2.getY() > node.getY() && node1.getY() < node.getY()))) {
return false;
}
}
}
return true;
}
/**
* Returns all nodes directly reachable from the given node using valid moves.
*/
private Array<GameNode> getAdjacentNodes(GameNode startingNode) {
Array<GameNode> adjacentNodes = new Array<GameNode>();
for (int i = 0; i < nodes.size; i++) {
GameNode targetNode = nodes.get(i);
if (startingNode.isOrthogonalTo(targetNode) && pathIsFree(startingNode, targetNode)) {
adjacentNodes.add(targetNode);
}
}
adjacentNodes.removeValue(startingNode, false);
return adjacentNodes;
}
/**
* Returns the {@link GameNode} objects from the {@link Array} given
* with the lowest value.
*/
private Array<GameNode> getLowestValued(Array<GameNode> nodes) {
Array<GameNode> lowestNodes = new Array<GameNode>();
int highestValue = Integer.MAX_VALUE;
for (int i = 0; i < nodes.size; i++) {
GameNode node = nodes.get(i);
if (node.getValue() < highestValue) {
highestValue = node.getValue();
}
}
for (int i = 0; i < nodes.size; i++) {
GameNode node = nodes.get(i);
if (node.getValue() == highestValue) {
lowestNodes.add(node);
}
}
return lowestNodes;
}
/**
* Implementation of Dijkstra's algorithm for solving levels.
* When successful, the solver will connect the alpha and omega nodes
* with the shortest possible path. The solver will stop one step before the
* omega node. The level label will then display the number of steps
* necessary to solve the level and a calculated difficulty rating.
*/
private void solve() {
LinesGame.logDebug(this, "Attempting to solve the current level...");
resetConnectors();
Array<GameNode> unvisited = new Array<GameNode>(nodes);
for (GameNode node : unvisited) {
node.setValue(Integer.MAX_VALUE);
}
selectedNode.setValue(0);
LinesGame.logDebug(this, "Applying Dijkstra's algorithm...");
int breaker = 0;
while (unvisited.size != 0) {
Array<GameNode> lowestNodes = getLowestValued(unvisited);
if (lowestNodes.size == 0 || lowestNodes.contains(omegaNode, false) || lowestNodes.peek().getValue() == Integer.MAX_VALUE) {
break;
}
GameNode node = lowestNodes.peek();
LinesGame.logDebug(this, "Checking " + node + "...");
for (GameNode adjacentNode : getAdjacentNodes(node)) {
LinesGame.logDebug(this, "Processing adjacent node, " + adjacentNode + "...");
int newValue = node.getValue() + 1;
if (newValue < adjacentNode.getValue()) {
LinesGame.logDebug(this, "Setting value to " + newValue + "...");
adjacentNode.setValue(newValue);
adjacentNode.setPrevious(node);
}
}
unvisited.removeValue(node, false);
breaker++;
if (breaker >= 10000) {
LinesGame.logDebug(this, "Loop breaker triggered. Solve operation failed.");
return;
}
}
LinesGame.logDebug(this, "Algorithm complete. Reverse iterating to find order of selection...");
Array<GameNode> ordered = new Array<GameNode>();
GameNode node = omegaNode;
try {
do {
ordered.add(node);
node = node.getPrevious();
} while (!node.equals(alphaNode));
} catch (NullPointerException e) {
LinesGame.logDebug(this, "Reverse iteration unsuccessful. Solve operation failed.");
return;
}
int steps = ordered.size;
LinesGame.logDebug(this, "Reverse iteration successful. Shortest path has " + steps + " steps.");
levelLabel.showText("Steps in shortest path: " + steps + " | Difficulty rating: " + (int) ((float) (steps + nodes.size + walls.size) / 2.5f) + "/100");
ordered.add(alphaNode);
ordered.removeValue(omegaNode, false);
ordered.reverse();
LinesGame.logDebug(this, "Traversing path...");
traverse(ordered);
}
@Override
public void clear() {
LinesGame.log(this, "Clearing entire screen and actor lists...");
super.clear();
nodes.clear();
walls.clear();
connectors.clear();
}
@Override
public void draw () {
drawConnectors();
super.draw();
}
private void drawConnectors() {
connectorRenderer.begin(ShapeType.Line);
for (GameConnector connector: connectors) {
connectorRenderer.setColor(connector.getColor());
connectorRenderer.line(connector.getStartX(), connector.getStartY(), connector.getEndX(), connector.getEndY());
connectorRenderer.line(connector.getStartX() - 1, connector.getStartY(), connector.getEndX() - 1, connector.getEndY());
connectorRenderer.line(connector.getStartX() + 1, connector.getStartY(), connector.getEndX() + 1, connector.getEndY());
connectorRenderer.line(connector.getStartX(), connector.getStartY() - 1, connector.getEndX(), connector.getEndY() - 1);
connectorRenderer.line(connector.getStartX(), connector.getStartY() + 1, connector.getEndX(), connector.getEndY() + 1);
}
connectorRenderer.end();
}
@Override
public void dispose() {
super.dispose();
connectorRenderer.dispose();
}
}
package sparkfire.lines.game;
import java.awt.Dimension;
import java.awt.Toolkit;
import sparkfire.lines.assets.AssetCache;
import sparkfire.lines.screens.SlideshowScreen;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.scenes.scene2d.Action;
/**
* Main application. Handles all screen switching (though not necessarily
* all input processor switches).
*
* @author Allen Guo
*/
public class LinesGame extends Game implements ApplicationListener {
/**
* Name of the game.
*/
public static final String NAME = "Lines";
/**
* Version designation.
* Includes preceding space so NAME + VERSION can be used as the full name and version.
*/
public static final String VERSION = " v1.0";
/**
* If true, certain debugging keyboard commands are active.
*/
public static final boolean DEVELOPMENT_MODE = true; // UNDEBUG Set to false to disable debugging commands.
/**
* If true, the sound is off.
*/
public static final boolean SILENT_MODE = true;
/**
* Game assets, loaded on instantiation of this {@link LinesGame} object.
* Default visibility enables subclass access.
*/
AssetCache assetCache;
@Override
public void create() {
log(this, "<" + NAME + VERSION + ">");
log(this, "<" + System.getProperty("java.runtime.name") + " " + System.getProperty("java.version") + " on " + System.getProperty("os.name") + ">");
log(this, "Log initiated at " + getTimestamp());
log(this, "Creating game...");
assetCache = new AssetCache(this, true);
LinesGame.log(this, "Showing introduction slideshow...");
Gdx.graphics.setVSync(false); // Prevents tearing during introduction.
setScreen(new SlideshowScreen(new String[]{"images/af.png", "images/sparkfire-logo.png", "images/title.png"}, 0.85f, 2, 0.85f, new Action() {
@Override
public boolean act(float delta) {
Gdx.graphics.setVSync(assetCache.preferences.getString("vsync", "false").equals("true"));
showMenuScreen();
return true;
}
}, Keys.ESCAPE));
if (!SILENT_MODE) {
assetCache.musicPlayer.play();
}
}
/**
* Shows main menu screen.
*/
public void showMenuScreen() {
log(this, "Showing menu screen...");
assetCache.mainMenuScreen.update();
setScreen(assetCache.mainMenuScreen);
Gdx.input.setInputProcessor(assetCache.mainMenuScreen.getStage());
}
/**
* Shows level selection screen.
*/
public void showLevelSelectScreen() {
log(this, "Showing level selection screen...");
assetCache.levelSelectScreen.update(Gdx.app.getPreferences("lines-data.xml").getInteger("highestLevelReached", 1));
setScreen(assetCache.levelSelectScreen);
Gdx.input.setInputProcessor(assetCache.levelSelectScreen.getStage());
}
/**
* Shows game screen and starts game.
* Used for starting game from level selection screen.
*/
public void startGame() {
log(this, "Showing game screen...");
setScreen(assetCache.gameScreen);
assetCache.gameScreen.beginGame(); // Update game.
Gdx.input.setInputProcessor(assetCache.gameScreen.getStage());
}
/**
* Shows in-game pause menu. Fades in if desired.
*/
public void showGameMenu(boolean fadeIn) {
log(this, "Showing game menu screen...");
if (fadeIn) {
assetCache.gameMenuScreen.update();
}
setScreen(assetCache.gameMenuScreen);
Gdx.input.setInputProcessor(assetCache.gameMenuScreen.getStage());
}
/**
* Shows game screen and resumes game.
* Used for returning to game from game menu screen.
*/
public void resumeGame() {
log(this, "Showing game screen...");
setScreen(assetCache.gameScreen);
assetCache.gameScreen.resumeGame(); // Update game.
Gdx.input.setInputProcessor(assetCache.gameScreen.getStage());
}
/**
* Shows options screen.
*/
public void showOptions() {
log(this, "Showing options screen...");
assetCache.optionsScreen.update();
setScreen(assetCache.optionsScreen);
Gdx.input.setInputProcessor(assetCache.optionsScreen.getStage());
}
/**
* Shows graphics options screen.
*/
public void showGraphicsOptionsScreen() {
LinesGame.log(this, "Showing graphics options screen...");
assetCache.graphicsOptionsScreen.update();
setScreen(assetCache.graphicsOptionsScreen);
Gdx.input.setInputProcessor(assetCache.graphicsOptionsScreen.getStage());
}
/**
* Shows audio options screen.
*/
public void showAudioOptionsScreen() {
LinesGame.log(this, "Showing audio options screen...");
assetCache.audioOptionsScreen.update();
setScreen(assetCache.audioOptionsScreen);
Gdx.input.setInputProcessor(assetCache.audioOptionsScreen.getStage());
}
/**
* Shows instructions screen.
*/
public void showInstructions() {
log(this, "Showing instructions screen...");
setScreen(new SlideshowScreen(new String[]{"images/instructions.png"}, 1f, 11.5f, 1f, new Action() {
@Override
public boolean act(float delta) {
showMenuScreen();
return true;
}
}, Keys.ESCAPE));
}
/**
* Shows credits screen.
*/
public void showCredits() {
log(this, "Showing credits screen...");
setScreen(new SlideshowScreen(new String[]{"images/title.png", "images/credits-1.png", "images/credits-2.png", "images/credits-3.png", "images/libgdx-logo.png", "images/af.png", "images/sparkfire-logo.png"}, 0.5f, 2.5f, 0.5f, new Action() {
@Override
public boolean act(float delta) {
showMenuScreen();
return true;
}
}, Keys.ESCAPE));
}
/**
* Shows license screen.
*/
public void showLicense() {
log(this, "Showing license screen...");
setScreen(new SlideshowScreen(new String[]{"images/licenses.png"}, 1f, 11.5f, 1f, new Action() {
@Override
public boolean act(float delta) {
showMenuScreen();
return true;
}
}, Keys.ESCAPE));
}
/**
* Ends application.
*/
public void exit() {
log(this, "Running clean exit sequence...");
Gdx.app.exit();
}
/**
* Returns current asset cache.
*/
public AssetCache getAssetCache() {
return assetCache;
}
/**
* Disposes of current asset cache,
* then replaces it with a new instance of {@link AssetCache}.
* Should be accessible via debugging commands only.
*/
public void reloadAssetCache() {
logDebug(this, "Reloading asset cache...");
long time = System.currentTimeMillis();
assetCache.dispose();
assetCache = new AssetCache(this, false);
logDebug(this, "Asset cache reloaded in " + (System.currentTimeMillis() - time) + " milliseconds.");
}
/**
* Changes to windowed mode.
*/
public void windowedMode() {
log(this, "Switching to windowed mode...");
Gdx.graphics.setDisplayMode(800, 600, false);
reloadAssetCache();
showMenuScreen();
if (!SILENT_MODE) {
assetCache.musicPlayer.play();
}
}
/**
* Changes to fullscreen.
*/
public void fullscreenMode() {
log(this, "Switching to fullscreen...");
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
Gdx.graphics.setDisplayMode(screen.width, screen.height, true);
reloadAssetCache();
showMenuScreen();
if (!SILENT_MODE) {
assetCache.musicPlayer.play();
}
}
@Override
public void resize(int width, int height) {
log(this, "Resizing to " + width + " x " + height + "...");
super.resize(width, height);
}
@Override
public void render() {
super.render();
}
@Override
public void pause() {
log(this, "Pausing game...");
super.pause();
}
@Override
public void resume() {
log(this, "Resuming game...");
super.resume();
}
@Override
public void dispose() {
log(this, "Disposing of game...");
assetCache.dispose();
super.dispose();
log(this, "Log annihilated at " + getTimestamp());
log(this, "<" + "END OF LOG" + ">");
}
/**
* Returns current timestamp.
*/
public static String getTimestamp() {
return String.valueOf(System.currentTimeMillis());
}
/**
* Writes record to log.
*/
public static <T> void log(T type, String message) {
Gdx.app.log(type.getClass().getName(), message);
}
/**
* Writes debugging record to log.
*/
public static <T> void logDebug(T type, String message) {
log(type, "[Debug] " + message);
}
}
package sparkfire.lines.game;
import sparkfire.lines.screens.AbstractScreen;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.scenes.scene2d.Action;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;
/**
* {@link Screen} that displays a slideshow based on the given internal {@link Texture} paths.
*
* @author Allen Guo
*/
public class SlideshowScreen extends AbstractScreen {
private Texture[] textures;
/**
* @param texturePaths - array containing the internal paths to the {@link Texture}s to be displayed.
* @param fadeInTime - duration (in seconds) of the fade-in effect.
* @param delayTime - duration (in seconds) between the fade-in and fade-out effects.
* @param fadeOutTime - duration (in seconds) of the fade-out effect.
* @param endAction - {@link Action} to be completed at the end of the last slide.
* @param skipSlideshowKey - keyboard input code corresponding to {@link Keys} that skips to the {@link Action} at the end of the slideshow.
*/
public SlideshowScreen(String[] texturePaths, float fadeInTime, float delayTime, float fadeOutTime, final Action endAction) {
super();
stage = new Stage();
textures = new Texture[texturePaths.length];
for (int i = 0; i < texturePaths.length; i++) {
Texture texture = new Texture(texturePaths[i]);
texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
textures[i] = texture;
Image image = new Image(texture);
image.setPosition((Gdx.graphics.getWidth() / 2) - (texture.getWidth() / 2), (Gdx.graphics.getHeight() / 2) - (texture.getHeight() / 2));
if (i != texturePaths.length - 1) {
image.addAction(sequence(alpha(0f), delay(i * (fadeInTime + delayTime + fadeOutTime)), fadeIn(fadeInTime), delay(delayTime), fadeOut(fadeOutTime)));
} else {
image.addAction(sequence(alpha(0f), delay(i * (fadeInTime + delayTime + fadeOutTime)), fadeIn(fadeInTime), delay(delayTime), fadeOut(fadeOutTime), endAction));
}
stage.addActor(image);
}
stage.addListener(new InputListener() {
@Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
for (Actor actor : stage.getActors()) {
actor.clearActions();
}
stage.getActors().peek().addAction(endAction);
return false;
}
});
Gdx.input.setInputProcessor(getStage());
}
@Override
public void dispose() {
super.dispose();
for (Texture texture : textures) {
texture.dispose();
}
}
@Override
public void finalize() {
dispose();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment