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
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.
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 |