package gdi1sokoban.game;
import gdi1sokoban.exceptions.InternalFailureException;
import gdi1sokoban.exceptions.LevelHistoryEntryNotFoundException;
import gdi1sokoban.exceptions.ParameterOutOfRangeException;
import gdi1sokoban.exceptions.ParseException;
import gdi1sokoban.globals.Point;
import gdi1sokoban.gui.SokobanWindow;
import gdi1sokoban.highscores.Highscores;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JOptionPane;
/**
* GameEngine class
*
* main class for controlling the Sokoban game
* contains game logic and supplies GUI functions
*/
public class GameEngine {
private static final long serialVersionUID = 8702781401668955536L;
private GameLevel gameLevel; // current loaded level
private SokobanWindow gameGui; // GUI interface
private int currentLevelNr = 0; // current level no.
private String currentCheatInput=
""; // cheat input
private Highscores highscores; // highscore database
private String leveldir
; // current level directory
private String playerName =
""; // player name
private long startTime = 0; // start time of the current level
private boolean deadLock = false; // deadlock situation?
/**
* gets the handle of the highscore engine
* @return highscores
*/
public Highscores getHighscores() {
return highscores;
}
/**
* @return gameLevel
*/
public GameLevel getGameLevel() {
return gameLevel;
}
/**
* returns the level number of the played level
* @return levelNumber
*/
public int getCurrentLevelNr(){
if(currentLevelNr < 0)
currentLevelNr = 0;
return currentLevelNr;
}
/**
* set playerName to specified name
* @param playerName the name of the player
*/
public void setPlayerName
(final String playerName
){
this.playerName = playerName;
}
/**
* gets playerName
* @return current player name
*/
public String getPlayerName
(){
return playerName;
}
/**
*
* @param windowTitle
* @throws InternalFailureException
*/
public GameEngine(final SokobanWindow gameGui) throws InternalFailureException {
try {
if(gameGui != null)
gameGui.setOwner(this);
this.gameGui = gameGui;
final File levelDir =
new File(ClassLoader.
getSystemClassLoader().
getResource("levels").
toURI());
setLevelDir(levelDir);
System.
err.
println("Fehlerhafter Dateiname");
}
}
/**
* checks if the user has typed a cheat
* @param input the currently read char
* @throws ParameterOutOfRangeException
*/
public void checkCheat(final char input) throws ParameterOutOfRangeException{
final Pattern beamCheat =
Pattern.
compile("osj([0-9]+),([0-9]+)#"); // define regular expression
final Pattern solveCheat =
Pattern.
compile("helpmeplease"); // define regular expression
currentCheatInput += input; // add input character to current cheat Code
final Matcher beam = beamCheat.
matcher(currentCheatInput
); // match against input
final Matcher solve = solveCheat.
matcher(currentCheatInput
); // match against input
if(beam.find()){ // beam cheat found
if (gameGui != null)
gameGui.playCheatSound();
final Point playerPos = gameLevel.
getPlayerPos(); // get Player Position
// move player to given position
final int destTop =
Integer.
valueOf(beam.
group(2)).
intValue();
final int destLeft =
Integer.
valueOf(beam.
group(1)).
intValue();
if(!(destTop == playerPos.top && destLeft == playerPos.left)){
gameLevel.setPosition(playerPos.top, playerPos.left, destTop, destLeft);
gameLevel.setNewHistoryStart(); // cheat code cannot be undone
}
currentCheatInput = ""; // reset cheat input
// redraw etc
roundEnd(true);
}
if(solve.find()){ // solve cheat found
// call level solver
if (gameGui != null)
gameGui.playCheatSound();
int solveTime = 20;
if(gameGui != null)
solveTime = gameGui.showSolveTimeDialog();
if(solveTime < 0)
solveTime = 0;
final String moves = solve
(solveTime
);
currentCheatInput = ""; // reset cheat input
//System.out.println(moves);
// execute moves
public void run() {
for(final char m : moves.toCharArray()){
try {
roundEnd(movePlayer(m));
} catch (final ParameterOutOfRangeException e1) {
System.
err.
println("Fehler: Ungltige Levelkoordinaten!");
}
try {
System.
err.
println("Interner Fehler: Thread wurde unterbrochen.");
}
}
}
}.start();
}
}
/**
* sets the level directory
* @param leveldir level directory
* @throws InternalFailureException
*/
public void setLevelDir
(final File leveldir,
final int maxHighscores
) throws InternalFailureException
{
this.leveldir = leveldir.toURI().toString();
highscores = new Highscores(this.leveldir + "/highscore.txt", maxHighscores);
// reset level no.
currentLevelNr = 0;
}
/**
* returns the level directory
* @return level directory
*/
return leveldir;
}
public void setLevelDir
(final File leveldir
) throws InternalFailureException
{
setLevelDir(leveldir, 10);
}
/**
* Parses the level no. from the filename of a given file handle
* @author Victor
* @param level level file handle
* @return level no., -1 if error
*/
private int parseLevelNr
(final File level
) {
final String fileName = level.
toString();
final Pattern p =
Pattern.
compile("(level\\_)([0-9]+)(\\.txt)"); // define regular expression
final Matcher m = p.
matcher(fileName
); // apply regexp
if (m.find() && (m.groupCount() >= 3))
return Integer.
valueOf(m.
group(2)).
intValue(); // return level no.
else
return -1;
}
/**
* Startet das Level neu bzw. startet einen neuen Level
* @throws IOException
* @throws InternalFailureException
* @throws ParameterOutOfRangeException
* @throws URISyntaxException
* @throws ParseException
*/
deadLock = false;
startTime =
System.
currentTimeMillis();
try {
gameLevel = new GameLevel(level);
currentLevelNr = parseLevelNr(level);
if(gameGui != null) {
gameGui.notifyLevelLoaded(gameLevel.getWidth(), gameLevel.getHeight());
// set new window position
gameGui.setLocation((int) ((screenSize.width - gameGui.getWidth()) / 2), (int) ((screenSize.height - gameGui.getHeight()) / 2));
}
if (gameGui != null)
gameGui.showParseError();
newGame();
}
}
/**
* Startet das Standard-Level
* @throws URISyntaxException
* @throws InternalFailureException
* @throws ParameterOutOfRangeException
* @throws IOException
* @throws ParseException
*/
newGame(1);
}
/**
* Starts a specified level
* @param level Das zu ladende Level
* @throws URISyntaxException
* @throws InternalFailureException
* @throws ParameterOutOfRangeException
* @throws IOException
* @throws ParseException
*/
currentLevelNr = level;
if (level < 10)
levelURI = "0" + levelURI;
newGame
(new File(new URI(leveldir +
"/level_" + levelURI +
".txt")));
}
public void roundEnd(final boolean hasMoved) throws ParameterOutOfRangeException {
roundEnd(hasMoved, playerName);
}
public int getPlayingTime(long endTime) {
endTime =
System.
currentTimeMillis();
return (int) ((endTime - startTime)/1000);
}
/**
* a step was made, redraw, check if level is complete and if it is a new highscore
* Start new level if level is complete
* @param hasMoved was the move succesful?
* @param playerName name of player
* @throws ParameterOutOfRangeException
*/
public void roundEnd
(boolean hasMoved,
final String playerName
) throws ParameterOutOfRangeException
{
if (! hasMoved) return; // break if nothing has moved
gameLevel.incSteps(); // increment done steps
if(gameGui != null)
gameGui.roundEnd(hasMoved, playerName);
if (gameLevel.isSolved()) {
if(gameGui != null)
gameGui.setStatusBarInfo("Level kmpl.");
this.playerName = playerName;
final int passedTime = getPlayingTime(0);
final int place = highscores.getPlace(currentLevelNr, gameLevel.getSteps(), passedTime);
if (highscores.isHighscore(currentLevelNr, gameLevel.getSteps(), passedTime)) {
if(gameGui != null)
gameGui.newHighscore(place);
highscores.
addHighscore(currentLevelNr,
this.
playerName,
String.
valueOf(gameLevel.
getSteps()),
Integer.
valueOf(passedTime
).
toString());
try {
highscores.saveHighscores(); // save the highscore list
} catch (final InternalFailureException e) {
System.
err.
println("Fehler: Highscores konnten nicht gespeichert werden.");
}
if(gameGui != null)
gameGui.showHighscores();
}
try {
newGame(currentLevelNr+1);
System.
err.
println("Level-Datei konnte nicht gefunden werden");
} catch (final ParameterOutOfRangeException e) {
e.printStackTrace();
} catch (final InternalFailureException e) {
System.
err.
println("Ein interner Fehler ist aufgetreten");
e.printStackTrace();
System.
err.
println("Fehlerhafter Dateiname");
System.
err.
println("Leveldatei fehlerhaft");
}
}
// notify player if level contains deadlock
if (gameLevel.hasDeadlock()) {
if (! deadLock) {
if (gameGui != null) {
gameGui.playDeadlockSound();
gameGui.showDeadlockMessage();
}
deadLock = true;
}
} else if (deadLock) {
deadLock = false;
if (gameGui != null)
gameGui.playBackgroundSound();
}
}
/**
* method to make the player walk to the given coordinates
* (steps are visible for the player)
* @param top
* @param left
* @throws ParameterOutOfRangeException
*/
public void playerWalk(final int top, final int left) throws ParameterOutOfRangeException{
final Point playerPos = gameLevel.
getPlayerPos();
// if the player only has to walk one step, he can move boxes
// and the normal MovePlayer method is called
final int diffTop = playerPos.top - top;
final int diffLeft = playerPos.left - left;
if((diffTop == 1) && (diffLeft == 0)) // Up
roundEnd(movePlayer('U'));
else if((diffTop == -1) && (diffLeft == 0)) // Down
roundEnd(movePlayer('D'));
else if((diffTop == 0) && (diffLeft == -1)) // Right
roundEnd(movePlayer('R'));
else if((diffTop == 0) && (diffLeft == 1)) // Left
roundEnd(movePlayer('L'));
else
public void run() {
// The method returns a LinkedList of chars (U, R, D, L)
// that have to be executed so that the player reaches his
// goal with the fewest possible moves
try {
lod = gameLevel.getShortestPath(playerPos.top, playerPos.left, top, left);
} catch (final ParameterOutOfRangeException e1) {
System.
err.
println("Fehler: Ungültige Levelkoordinaten!");
return;
}
// for every Direction in the List of directions
// the player has to execute the move and then wait
// for a while
for(final char dir : lod)
try {
// move player in given direction
roundEnd(movePlayer(dir));
// wait 0.1 seconds before executing the next move
System.
err.
println("Interner Thread Fehler");
e.printStackTrace();
} catch (final ParameterOutOfRangeException e) {
System.
err.
println("Fehler: Ungültige Levelkoordinaten!");
return;
}
}
}.start();
}
/**
* Checks if the move is allowed,
* moves player in the given direction,
* updates game level and returns if the level is solved
*
* @param dir Direction ('R', 'U', 'D', 'L')
* @return true if move was successful
* @throws ParameterOutOfRangeException
*/
public boolean movePlayer (final char dir) throws ParameterOutOfRangeException {
final Point playerPos = gameLevel.
getPlayerPos();
final Point newPos = playerPos.
moveDirection(dir
);
final Point newCratePos = newPos.
moveDirection(dir
);
final int top = playerPos.top;
final int left = playerPos.left;
// if crate is in front of player try to move it first
boolean isCrate = false;
try {
isCrate = gameLevel.isCrate(newPos.top, newPos.left);
} catch (final ParameterOutOfRangeException e) {
System.
err.
println("Fehler: Koordinaten liegen ausserhalb des Spielfelds!");
return false;
}
if(isCrate)
gameLevel.setPosition(newPos.top, newPos.left , newCratePos.top, newCratePos.left);
if (gameLevel.setPosition(top, left, newPos.top, newPos.left)) { // move successful?
gameLevel.addMove(dir); // Add move to history
return true;
}
return false; // move unsuccessful
}
/**
* undo the last move
* @throws ParameterOutOfRangeException
*/
public void undo() throws LevelHistoryEntryNotFoundException, ParameterOutOfRangeException{
final int stepCount = gameLevel.getHistorySteps(); // current position in history
if(stepCount == 0)
throw new LevelHistoryEntryNotFoundException("Undo not possible! No moves made yet.");
gameLevel.reset(); // resets level to start
// execute all steps made except the last one
for(int i=0; i< stepCount-1; i++)
movePlayer(history.get(i));
// history may not change
gameLevel.setHistory(history);
}
/**
* redo the last undo
* @throws ParameterOutOfRangeException
*/
public void redo() throws LevelHistoryEntryNotFoundException, ParameterOutOfRangeException{
final int stepCount = gameLevel.getHistorySteps(); // current position in history
if(stepCount == history.size())
throw new LevelHistoryEntryNotFoundException("Redo not possible! No undos made yet.");
movePlayer(history.get(stepCount)); // execute undone move
gameLevel.setHistory(history); // history may not change
}
/**
* load saved game from the given File
* @param GamesSaveFile saved game file
*/
public void loadGame
(final File GamesSaveFile
) {
try {
int historySteps=0;
int stepCount = 0;
if(GamesSaveFile != null){
int lineNum = 0;
while ((line = in.readLine()) != null) { // read SavedGames file line by line
lineNum++;
if(lineNum > 1)
historyStart += line+"\n";
else
saveData = line.split("\t"); // split SavedGames line
}
currentLevelNr =
Integer.
valueOf(saveData
[1]);//Level
if(saveData.length >= 3)
try {
stepCount =
Integer.
valueOf(saveData
[2]).
intValue();
System.
err.
println("Fehler: Savegame fehlerhaft.");
if (gameGui != null)
JOptionPane.
showMessageDialog(null,
"Der Spielstand konnte nicht geladen werden.\nDie Datei ist fehlerhaft.",
"Savegame fehlerhaft",
JOptionPane.
ERROR_MESSAGE);
return;
}
newGame(currentLevelNr);
if(saveData.length >= 4)
steps = saveData[3]; // Schritte in richtiger Reihenfolge
if(saveData.length >= 5)
startTime =
System.
currentTimeMillis() -
Integer.
parseInt(saveData
[4])*1000; // Playing Time
if(saveData.length >= 6)
historySteps =
Integer.
valueOf(saveData
[5]).
intValue(); // History-Schritte
try {
gameLevel.parseString(historyStart);
if (gameGui != null)
gameGui.showParseError();
in.close();
return;
}
gameLevel.setNewHistoryStart();
for(final char step : steps.toCharArray())
stepsSequence.add(step);
gameLevel.setHistory(stepsSequence);
for(int i = 0;i<historySteps; i++)
redo();
roundEnd(true);
gameLevel.setSteps(stepCount);
in.close();
}
System.
err.
println("Ungltiger Dateiname");
System.
err.
println("Datei nicht gefunden!");
e.printStackTrace();
e.printStackTrace();
} catch (final ParameterOutOfRangeException e) {
e.printStackTrace();
} catch (final InternalFailureException e) {
e.printStackTrace();
} catch (final LevelHistoryEntryNotFoundException e) {
System.
err.
println("Fehler beim Einlesen der Schrittfolge");
System.
err.
println("Leveldatei fehlerhaft");
}
}
/**
* save game to given filepath
* @param GamesSaveFile File to save gamestatus to
* @return
*/
public boolean saveGame
(final File GamesSaveFile
) {
for(int i=0;i<getGameLevel().getMoveHistory().size();i++)
stepsSequence += getGameLevel().getMoveHistory().get(i);
try {
out.write(playerName + "\t" + getCurrentLevelNr() + "\t" + getGameLevel().getSteps() + "\t"
+ stepsSequence +
"\t" + getPlayingTime
(System.
currentTimeMillis())
+ "\t" + getGameLevel().getHistorySteps() + "\n" + gameLevel.getHistoryStart());
out.close();
e.printStackTrace();
}
return true;
}
/**
* tries to solve the level in a given amount of time
* @param solutionTime maximum time the solver may run
* @return moves that have to be executed to clear the level or an empty String when no solution could be found
* @throws ParameterOutOfRangeException
*/
private String solveWithQueue
(final int solutionTime
) throws ParameterOutOfRangeException
{
try {
final char[] directions = new char[] { 'U', 'L', 'R', 'D' };
// passed seconds since start of solver
final long startTime =
System.
currentTimeMillis();
int passedTime = 0;
do{
passedTime =
(int) (System.
currentTimeMillis() - startTime
) /
1000;
if(!queue.isEmpty())
currMoves = queue.pop();
// set level to state
for (final char m : currMoves.toCharArray())
movePlayer(m);
// Try if next move completes game
for (final char d : directions)
// if move is valid and level contains no deadlocks and
// field has not been visited yet
if (!gameLevel.hasDeadlock() && movePlayer(d)) {
if (gameLevel.isSolved())
// return made moves
solvedString = currMoves + d;
// do not restore a state of the level in which
// it has been before
if (!levelHistory.contains(gameLevel.toString())) {
queue.add(currMoves + d); // add moves to queue
levelHistory.add(gameLevel.toString());
}
undo(); // undo move
}
// reset level to beginning state
for (int i = 0; i < currMoves.length(); i++)
undo();
if(solvedString != "")
return solvedString;
} while (!queue.isEmpty() && (passedTime < solutionTime));
} catch (final LevelHistoryEntryNotFoundException e) {
System.
err.
println("Interner Fehler: Levelsolver-Fehler");
}
return "";
}
/**
* tries to solve the level
* @param solutionTime max time for searching for a solution (in seconds)
* @return the steps that have to be executed to solve the level in {R,L,U,D}-format
* @throws ParameterOutOfRangeException
*/
public String solve
(final int solutionTime
) throws ParameterOutOfRangeException
{
final long startTime =
System.
currentTimeMillis();
// the solver tries to find the optimal solution
// (i.e. the solution with the smallest number of moves)
long timePassed = 0;
final String solution = solveWithQueue
(solutionTime
);
if(solution != ""){ // solution has be found
timePassed =
(System.
currentTimeMillis() - startTime
)/
1000;
System.
out.
println("Found solution in "+timePassed+
" seconds.");
if(gameGui != null)
gameGui.setStatusBarInfo("Lsung ("+timePassed+"s)");
return solution;
}
// return empty string if no solution could be found
if(gameGui != null)
gameGui.setStatusBarInfo("Keine Ls.");
return "";
}
}