How would you implement sokoban

I am new to Java game programming. So I started with some simple games such as snake, moon lander, and I am doing sokoban now.

Following the idea of OOP, I created Player.class, Box.class, Grid.class, and some other classes for structure. When it comes to logic, I found that those classes hinder the design.

Then I realizes it would be much easier to implement sokoban just using a 2D int array, in which the integer value presents the player, box and so on.

Perhaps it is because I am a newbie and don’t know how to do things right. But I want to know what is the best way to design.

It doesn’t matter how you make it unless you don’t actually make it.

The 2d is the right way to go, but you dont have to fill it with integers. you can put your box and player objects in it, as well as other objects you want.

to do that you will have to create a super class for the various movable object types.
you have a class Mob{…}. It holds basic information like position, sprite, renderMethod(), etc. You can then make your 2d array as: Mob mobMap[][];

then you make a class for each type of object in the game:
PlayerMob extends Mob{…}
BoxMob extends Mob{…}
etc.
these can have their own additional behaviors, but they will still fit in together in your 2d array.

These concepts are very easy to find more information on, but hopefully it points you in the right direction.

Actually I did quite the same as you suggested. I create the Grid extends Point. But I don’t just extends the Grid. Instead, for player and box, I include the Grid in them. For walls, floors, I extends.

But whatever, these classes don’t help in logic, what they have are just the draw() method. Maybe I’ll let them implements a Drawable interface and then put them into the array. Then I complete the logic in a Game.class, and call the draw() methods from the 2D array.

Only a 2D array is not the way you should go, because you need to be able to place the gems onto the socket tiles.
Also, you might make a mistake in your game code somehow and end up with two players inside your array.

I would create a Grid class / simple 2D int[][], that holds what tiles are walls, what tiles are sockets and what tiles are ground that is walkable. You can simply create constants for them (i.e. [icode]public static final int WALL = 1; public static final int GROUND = 2;[/icode]), no need for classes.
Then you add a Player and a Gem class that have x and y integer fields. They can move around, the 2D int[][] cannot change.

Your propose is even closer to my origin design.

I have the Map.class which contain a Map.Type[][]. And Type is a enum { WALL, FLOOR, TARGET}. final static int is good when you need the int value, while in other situation I prefer enum.

Also, I have the box class. But then I find out I don’t need this class. It will be much easier to just manuplate the 2D array. If I use 2D array, I can easily find out if the player can go where, if there is a box and what if the box is leaning on to the wall. However if the boxes are saved in a Set, I need to iterate through them every time, comparing with x, y, then checking the walls.

The enum is not a bad idea, it doesn’t need to be used, but it doesn’t hurt much anyways.
However, what if the socket tile is saved in Map’s [icode]tile[4][4];[/icode] and you finally moved the box to [icode]tile[4][4];[icode]? Would you overwrite the socket? But then you can’t move the box or you would remove the socket.

For that way of implementation. I would have more in the enum, BOX_ON_TARGET (CRYSTAL_IN_SOCKET).

I am very happy that someone thinks the same with me. I am going this way, to have:

class Grid extends Point {
Type type;
}

enum Type { WALL, FLOOR, BOX, TARGET, BOX_ON_TARGET}

class Player

class Game {
Grid[][] grids;
Player player;
}

If you feel there are better designs, please enlighten me. (or why shouldn’t I do this)

I realize this is somehow getting rediculous now, since it’s only sokoban, but anways, I like thinking about “nearly perfect” design :slight_smile:

Back on topic:

I don’t see a reason why you should make a difference between Player and Boxes. I liked my approach (saving only walls, targets and free spaces in the 2D Array), because I would seperate movable objects from Static ones.

Also, I wouldn’t call it “Grid”, because a Grid encapsulates single Cells. On the first glance, something that is typed “Grid[][]” looks like a 2D array with 2D arrays (Grids) inside.

I would create the following types:


enum Cell {
    WALL(false),
    FLOOR(true),
    TARGET(true),
    OUT_OF_LEVEL(false)

    public boolean canBeMovedOn;

    Cell(boolean canBeMovedOn) {
        this.canBeMovedOn = canBeMovedOn;
    }
}

abstract class Movable {
    private int x;
    private int y;
    
    public Movable(int startx, int starty) {
        this.x = startx;
        this.y = starty;
    }
    
    // Direction is the direction the user has pressed
    public abstract void act(Direction dir, SokobanWorld world);
}

class Box extends Movable {
    public Box(int sx, int sy) {
        super(sx, sy);
    }
    
    // Do nothing. A Box doesn't react to user input directly
    public void act(Direction dir, SokobanWorld world) {}
}

class Player extends Movable {
    public Player(int sx, int sy) {
        super(sx, sy);
    }

    public void act(Direction dir, SokobanWorld world) // To be implemented
}

class SokobanWorld {
    public Cell[][] grid;
    public Map<Vec2i, Movable> movables;
}

But this might all be over-engineered :wink: