The programming project for the course CCPS 109 is to implement Sokoban, the Japanese modern classic puzzle game. The rules are very simple yet they generate amazing complexity for the player who needs to use lateral thinking to solve these puzzles. For those interested of this game and its colourful history, or for the clarification of its rules, see the Wikipedia Sokoban page.
The instructor provides the level data files and the code needed to read them, and it is your job to implement the actual game as one class Sokoban that is a Swing component with a main method that opens a window that contains one instance of this component.
Each level of Sokoban is a two-dimensional “warehouse” room seen from above. The room is a grid of tiles, each of which is one of the following:
The purpose of the game is to push each box onto some goal tile, of which there are at least as many on each level as there are boxes. The difficulty is that the player, a warehouse keeper, can only push a box, but never pull a box. Furthermore, the player has the strength to push only one box at the time on either an empty space or a goal tile behind the box. In the game, this restriction makes many seemingly innocent moves irreversible: even one misstep can be fatal so that the level can no longer be completed.
Your game must allow the player to move around the level with the cursor keys. Furthermore, to help my testing and debugging, I require that pressing 'N' will immediately move the game to the next level in the game, and pressing 'R' will restart the current level from its initial state.
Since reading and writing data from a file will be covered in CCPS 209, and we should try to keep the size of this project manageable anyway, the instructor provides you with two classes SokoTile.java and LevelReader.java (as part of the 109.jar archive), along with the level data file m1.txt. You should not modify these two classes in any way.
SokoTile is an enumeration that defines the seven possible values that each tile can have, as listed above. LevelReader contains the methods that you need in your game to set up the contents of each level. These methods are
Note that this class does not contain any methods for modifying the contents of each level, but merely provides its initial configuration. Therefore your Sokoban class must maintain the state of the current level in a 2D array field of its own.
Note also that in the coordinate system used in these methods, the x-coordinate is horizontal (column), and the y-coordinate is vertical (row). It doesn't matter if you switch these two, as long as you do so consistently in all of your drawing and movement methods.
Note also that none of the above methods do any kind of error checking to their parameter. It is your responsibility to call these methods so that the parameter values are legal. However, your code can implicitly assume that each level is completely surrounded by solid walls, so there is no chance of the player or the box that is being going out of the array bounds. This way, you don’t need to write your code to check for that possibility.
To implement your game as the class Sokoban, you don't need to follow the roadmap that I have sketched below. However, it will make things a lot easier if you do. Make sure that each stage of the roadmap is complete and works correctly before you even try to move on to the next stage. It is much easier to fix bugs as soon as you create them, and only build more code on top of existing code that is known to be correct, since any problems are then known to be in the newly added code and much easier to find.
The single most important thing about this project is the clarity and brevity in the movement logic. Try to eliminate as much redundancy as you can from the key listener method, but after each modification, be careful to ensure that it still works correctly! As a general rule about code smell, whenever you are doing the same thing twice, or copypasting your own code within the same project, you are doing something wrong, or at least not cleanly enough. You should never in any program have identical branches of code that differ only by some one little thing. The moment you realize that you have created such branches, think of how you could refactor these branches into one branch or a helper method.