Implementing a Sokoban Game in Android Studio

1. Project Overview

Sokoban is a very popular puzzle game. The gameplay is simple, but it requires players to have a certain level of logical thinking and spatial perception abilities, which is why it is loved by many players. In the game, players need to push boxes to designated locations, with each level having a specific goal, such as pushing all boxes to designated positions or completing tasks within a set time limit. As the levels progress, the difficulty of the game increases, requiring players to use their brains and strategies more flexibly to complete higher difficulty challenges.

This Sokoban game not only trains players’ logical thinking and spatial imagination but also brings a pleasant gaming experience and programming fun.

2. Development Environment

Implementing a Sokoban Game in Android Studio

3. Detailed Design

MainActivity includes game introduction, game start, and exit. The logic of the menu bar and bottom navigation bar is the same.

@Overridepublic boolean onOptionsItemSelected(MenuItem item) {    switch (item.getItemId()){        case R.id.itm_start:            Intent xgq = new Intent(MainActivity.this, GameLevelActivity.class);            startActivity(xgq);            break;        case R.id.itm_intro:            Intent i = new Intent(MainActivity.this, GameIntroActivity.class);            startActivity(i);            break;        case R.id.itm_exit:            finish();            break;    }    return super.onOptionsItemSelected(item);}

Define the game view GameView, mManRect is the position of the mover, getManRect() sets the position of the mover, goToLevel() selects the level, resetGame() starts a new game, gotoNextLevel() enters the next level, gotoPrvLevel() enters the previous level, onDraw() draws the game area, and draws the icon for the sound switch. drawGameBoard() draws the game grid, drawSoundSwitch() draws the sound switch, drawDoneLabel() draws the completion label, onTouchEvent() controls the mover’s movement based on user touches in the game area.

private void drawGameBoard(Canvas canvas) {    Rect destRect = new Rect();    for (int r = 0; r < mGameData.getBoardRowNum(); r++ )        for (int c = 0; c < mGameData.getBoardColumnNum(); c++){            int topleft_x = (int)(mTopLeft_x + c * mColumnWidth);            int topleft_y = (int)(mTopLeft_y + r * mRowHeight);            destRect.set(topleft_x, topleft_y,(int)(topleft_x + mColumnWidth) + 2, (int)(topleft_y + mRowHeight) + 2);//+2 is to eliminate the gap between walls            if (mGameData.hasFlag(r, c))                canvas.drawBitmap(GameBitmaps.mFlagBitmap, null, destRect, null);            StringBuffer []gameState = mGameData.getGameState();            //Draw according to the state of the grid: box, flag, empty, person, wall, box + flag, person + flag            switch (gameState[r].charAt(c)){                case GameInitialData.BOX:                    canvas.drawBitmap(GameBitmaps.mBoxBitmap, null, destRect, null);                    break;                case GameInitialData.FLAG:                    canvas.drawBitmap(GameBitmaps.mFlagBitmap, null, destRect, null);                    break;                case GameInitialData.NOTHING:                    break;                case GameInitialData.MAN:                    canvas.drawBitmap(GameBitmaps.mManBitmap, null, destRect, null);                    break;                case GameInitialData.WALL://                        destRect.set(destRect.left, destRect.top, destRect.right+2, destRect.bottom + 2);  //+2 is to eliminate the gap between walls                    canvas.drawBitmap(GameBitmaps.mWallBitmap, null, destRect, null);                    break;                case GameInitialData.BOX_FLAG:                    canvas.drawBitmap(GameBitmaps.mFlagBitmap, null, destRect, null);                    canvas.drawBitmap(GameBitmaps.mBoxBitmap, null, destRect, null);                    break;                case GameInitialData.MAN_FLAG:                    canvas.drawBitmap(GameBitmaps.mFlagBitmap, null, destRect, null);                    canvas.drawBitmap(GameBitmaps.mManBitmap, null, destRect, null);                    break;            }        }}

The GameData class contains data for all levels. The game area has no more than 11 columns, with several empty columns on both sides to make up 11 columns. If there are fewer than 11 rows, several empty rows are added to make the wall image look larger. The go() method moves the mover from (srcRow, srcColumn) to (destRow, destColumn), recordMoveInfo() records the move information for easy redrawing, and isGameOver() checks if all boxes have reached their destination.

public boolean undoMove(){    if (mGameSteps.isEmpty())        return false;        GameStepData step = mGameSteps.remove(mGameSteps.size() - 1);    assert(mManPostion.isEqualTo(step.getManCurrentPosition()));    restoreInitialState(step.getManCurrentPosition().row, step.getManCurrentPosition().column);    int manRow = step.getManPrvPosition().row;    int manColumn = step.getManPrvPosition().column;    mManPostion.set(manRow, manColumn);    mGameState[manRow].setCharAt(manColumn, 'M');    TCell boxPrvPos = step.getBoxPrvPosition();    TCell boxCurPos = step.getBoxCurrentPosition();    if (boxPrvPos != null && boxCurPos != null){        //assert mGameState[boxCurPos.row].charAt(boxCurPos.column) == 'B';        restoreInitialState(boxCurPos.row, boxCurPos.column);        mGameState[boxPrvPos.row].setCharAt(boxPrvPos.column, 'B');    }    return true;}

GameBitmaps contains bitmaps for each state in the game, loadBitmaps() loads the bitmaps, releaseBitmaps() releases all bitmap resources.

public static void releaseBitmaps(){    //game board    releaseBmp(mBoxBitmap);    releaseBmp(mManBitmap);    releaseBmp(mWallBitmap);    releaseBmp(mFlagBitmap);    releaseBmp(mDoneBitmap);    releaseBmp(mSoundOpenBitmap);    releaseBmp(mSoundCloseBitmap);}

The GameInitialData class contains the game initialization data, first defining final char static constants to indicate what to place in the game area cells, providing default patterns for the first and second levels. The readConfig() method reads the test_level_list.txt file from the assets folder, which contains the styles for all levels.

Implementing a Sokoban Game in Android Studio

public static final char NOTHING = ' ';         //This cell has nothingpublic static final char BOX = 'B';             //This cell contains a boxpublic static final char FLAG = 'F';            //Red flag, indicates the destination of the boxpublic static final char MAN = 'M';             //Moverpublic static final char WALL = 'W';            //Wallpublic static final char MAN_FLAG = 'R';        //Mover + Red flagpublic static final char BOX_FLAG = 'X';        //Box + Red flag

The GameSound class defines the game’s sound effects, mOneSetpMusicId is the music for moving one step, mMoveBoxMusicId is the music for moving a box, mGameOverMusicId is the sound effect for game over, and mSoundAllowed indicates whether sound effects are allowed.

//Turn off sound public static void releaseSound(){    if (mSoundPool == null) return;    mSoundPool.unload(mOneSetpMusicId);    mSoundPool.unload(mMoveBoxMusicId);    mSoundPool.unload(mGameOverMusicId);    mSoundPool.release();}

The LevelInitialData class contains the cell data for each level, and the TCell class represents cell coordinates. The PrfsManager class stores the difficulty of passing levels.

public static void setPassedLevel(Context context, int level){    mPassedLevels = context.getSharedPreferences(PRFS_PASSED_LEVEL_NAME, Context.MODE_PRIVATE);    SharedPreferences.Editor editor = mPassedLevels.edit();    editor.putBoolean(KEY_PASSED_LEVEL_PREFIX + level, true);    editor.commit();} public static boolean getPassedLevel(Context context, int level){    mPassedLevels = context.getSharedPreferences(PRFS_PASSED_LEVEL_NAME, Context.MODE_PRIVATE);    return  mPassedLevels.getBoolean(KEY_PASSED_LEVEL_PREFIX + level, false);  //false is the default value, returns false if there is no preference value for level}

4. Run Demonstration

5. Source Code Access

Background Reply:Sokoban

Also, you missed the wonderful past issues

Implementing an Accounting Book in Android
Implementing a Course Selection System in Android
Implementing a Multifunctional Diary in Android
Implementing a Literary Reading App in Android

Leave a Comment