Android应用实践之数独游戏开发

时间:2021-05-19

数独游戏是一种源自18世纪末的瑞士的游戏,后在美国发展、并在日本得以发扬光大的数学智力拼图游戏。拼图是九宫格(即3格宽×3格高)的正方形状,每一格又细分为一个九宫格。在每一个小九宫格中,分别填上1至9的数字,让整个大九宫格每一列、每一行的数字都不重复。

数独的玩法逻辑简单,数字排列方式千变万化。不少教育者认为数独是锻炼脑筋的好方法,上外语阅读课的时候外教老师就很喜欢带我们玩这个,乐此不疲,老外的教学方式还是很受欢迎的。但是每次玩这个游戏的时候都要发一张数独游戏卡,嫌麻烦,就想着写一个demo放自己手机上,想想那个时候真是好奇心爆棚,碰上很火爆的小游戏都想整一个DIY的Demo,叨叨够了,哈哈,上源码。

一、界面布局

1.主界面

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:background="@color/background" android:layout_height="fill_parent" android:layout_width="fill_parent" android:padding="30dip" android:orientation="horizontal"> <LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginBottom="25dip" android:text="@string/main_title" android:textSize="24sp" /> <Button android:id="@+id/continue_button" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/continue_label" /> <Button android:id="@+id/new_button" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/new_game_label" /> <Button android:id="@+id/about_button" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/about_label" /> <Button android:id="@+id/exit_button" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/exit_label" /> </LinearLayout></LinearLayout>

2.数字键盘布局

<?xml version="1.0" encoding="utf-8"?><TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/keypad" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" android:background="@color/puzzle_background" android:stretchColumns="*" > <TableRow > <Button android:id="@+id/keypad_1" android:text="@string/keypad_1" /> <Button android:id="@+id/keypad_2" android:text="@string/keypad_2" /> <Button android:id="@+id/keypad_3" android:text="@string/keypad_3" /> </TableRow> <TableRow > <Button android:id="@+id/keypad_4" android:text="@string/keypad_4" /> <Button android:id="@+id/keypad_5" android:text="@string/keypad_5" /> <Button android:id="@+id/keypad_6" android:text="@string/keypad_6" /> </TableRow> <TableRow > <Button android:id="@+id/keypad_7" android:text="@string/keypad_7" /> <Button android:id="@+id/keypad_8" android:text="@string/keypad_8" /> <Button android:id="@+id/keypad_9" android:text="@string/keypad_9" /> </TableRow></TableLayout>

3.游戏提示布局

<?xml version="1.0" encoding="utf-8"?><ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:padding="10dip"> <TextView android:id="@+id/about_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/about_text"/></ScrollView>

二、游戏提示类

package com.dw.gamesuduku;import android.app.Activity;import android.os.Bundle;public class About extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.about); }}

三、逻辑实现1

package com.dw.gamesuduku;import android.app.Activity;import android.app.Dialog;import android.os.Bundle;import android.util.Log;import android.view.Gravity;import android.widget.Toast;public class Game extends Activity { private static final String TAG="Sudoku"; private static final String PREF_PUZZLE="puzzle"; protected static final int DIFFICULTY_CONTINUE=-1; public static final String KEY_DIFFICULTY="difficulty"; public static final int DIFFICULTY_EASY=0; public static final int DIFFICULTY_MEDIUM=1; public static final int DIFFICULTY_HARD=2; private int puzzle[]=new int[9*9]; private PuzzleView puzzleView; //三种游戏模式 private static final String easyPuzzle="360000000004230800000004200"+ "070460003820000014500013010"+ "001900000007048300000000045"; private static final String mediumPuzzle="650000070000506000014000005"+ "007009000002314700000700800"+ "500000630000201000030000097"; private static final String hardPuzzle="009000000080605020501078000"+ "000000700706040102004000000"+ "000720903090301080000000600"; private final int used[][][]=new int[9][9][]; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); Log.e(TAG, "onCreate"); int diff=getIntent().getIntExtra(KEY_DIFFICULTY, DIFFICULTY_EASY); puzzle=getPuzzle(diff); calculateUsedTiles(); puzzleView=new PuzzleView(this); setContentView(puzzleView); puzzleView.requestFocus(); //if the activity is restarted ,do a continue next time getIntent().putExtra(KEY_DIFFICULTY, DIFFICULTY_CONTINUE); } @Override protected void onPause() { // TODO Auto-generated method stub super.onPause(); Music.stop(this); //Save the current puzzle getPreferences(MODE_PRIVATE).edit().putString(PREF_PUZZLE, toPuzzleString(puzzle)).commit(); } @Override protected void onResume() { // TODO Auto-generated method stub super.onResume(); Music.play(this, R.raw.game); } protected int[] getUsedTiles(int x,int y){ return used[x][y]; } private void calculateUsedTiles() { // TODO Auto-generated method stub for (int x = 0; x < 9; x++) { for (int y = 0; y < 9; y++) { used[x][y]=calculateUsedTiles(x,y); } } } private int[] calculateUsedTiles(int x, int y) { // TODO Auto-generated method stub int c[]=new int[9]; //horizontal for(int i=0;i<9;i++){ if(i==y) continue; int t=getTitle(x, i); if(t!=0) c[t-1]=t; } //vertical for(int i=0;i<9;i++){ if(i==x) continue; int t=getTitle(i, y); if(t!=0) c[t-1]=t; } //same cell block int startx=(x/3)*3; int starty=(y/3)*3; for(int i=startx;i<startx+3;i++){ for(int j=starty;j<starty+3;j++){ if(i==x&&j==y) continue; int t=getTitle(i, j); if(t!=0) c[t-1]=t; } } //compress int nused=0; for (int t : c) { if(t!=0) nused++; } int c1[]=new int[nused]; nused=0; for (int t : c) { if(t!=0) c1[nused++]=t; } return c1; } //give a difficulty level private int[] getPuzzle(int diff) { // TODO Auto-generated method stub String puz = null; switch (diff) { case DIFFICULTY_CONTINUE: puz=getPreferences(MODE_PRIVATE).getString(PREF_PUZZLE, easyPuzzle); break; case DIFFICULTY_HARD: puz=hardPuzzle; break; case DIFFICULTY_MEDIUM: puz=mediumPuzzle; break; case DIFFICULTY_EASY: puz=easyPuzzle; break; } return fromPuzzleString(puz); } //convert an array into a puzzle string static private String toPuzzleString(int[] puz){ StringBuilder buf=new StringBuilder(); for (int element : puz) { buf.append(element); } return buf.toString(); } //convert a puzzle string to an array static protected int[] fromPuzzleString(String string) { // TODO Auto-generated method stub int[] puz=new int[string.length()]; for (int i = 0; i < puz.length; i++) { puz[i]=string.charAt(i)-'0'; } return puz; } public String getTitleString(int x, int y) { // TODO Auto-generated method stub int v=getTitle(x,y); if(v==0) return ""; else return String.valueOf(v); } private int getTitle(int x, int y) { // TODO Auto-generated method stub return puzzle[y*9+x]; } private void setTitle(int x,int y,int value){ puzzle[y*9+x]=value; } //change the tile only if it's a valid move protected boolean setTileIfValid(int x, int y, int value) { // TODO Auto-generated method stub int tiles[]=getUsedTiles(x, y); if(value!=0){ for (int tile : tiles) { if(tile==value) return false; } } setTitle(x, y, value); calculateUsedTiles(); return true; } //open the keypad if there are any valid moves protected void showKeypadOrError(int x, int y) { // TODO Auto-generated method stub int tiles[]=getUsedTiles(x, y); if(tiles.length==9){ Toast toast=Toast.makeText(this, R.string.no_moves_label, Toast.LENGTH_SHORT); toast.setGravity(Gravity.BOTTOM, 0, 0); toast.show(); }else{ Log.d(TAG, "showKeypad:used="+toPuzzleString(tiles)); Dialog v=new Keypad(this,tiles,puzzleView); v.show(); } }}

四、数字键盘

package com.dw.gamesuduku;import android.app.Dialog;import android.content.Context;import android.os.Bundle;import android.view.KeyEvent;import android.view.View;public class Keypad extends Dialog { protected static final String TAG="Sudoku"; private final View keys[]=new View[9]; private View keypad; private final int useds[]; private PuzzleView puzzleView; public Keypad(Context context,int useds[],PuzzleView puzzleView){ super(context); this.useds=useds; this.puzzleView=puzzleView; } @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.keypad); findViews(); for (int element : useds) { if(element!=0){ keys[element-1].setVisibility(View.INVISIBLE); } setListeners(); } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // TODO Auto-generated method stub int tile=0; switch (keyCode) { case KeyEvent.KEYCODE_0: case KeyEvent.KEYCODE_SPACE:tile=0;break; case KeyEvent.KEYCODE_1:tile=1;break; case KeyEvent.KEYCODE_2:tile=2;break; case KeyEvent.KEYCODE_3:tile=3;break; case KeyEvent.KEYCODE_4:tile=4;break; case KeyEvent.KEYCODE_5:tile=5;break; case KeyEvent.KEYCODE_6:tile=6;break; case KeyEvent.KEYCODE_7:tile=7;break; case KeyEvent.KEYCODE_8:tile=8;break; case KeyEvent.KEYCODE_9:tile=9;break; default: return super.onKeyDown(keyCode, event); } if(isValid(tile)){ returnResult(tile); } return true; } private boolean isValid(int tile) { // TODO Auto-generated method stub for (int t : useds) { if(tile==t) return false; } return true; } private void findViews() { // TODO Auto-generated method stub keypad=findViewById(R.id.keypad); keys[0]=findViewById(R.id.keypad_1); keys[1]=findViewById(R.id.keypad_2); keys[2]=findViewById(R.id.keypad_3); keys[3]=findViewById(R.id.keypad_4); keys[4]=findViewById(R.id.keypad_5); keys[5]=findViewById(R.id.keypad_6); keys[6]=findViewById(R.id.keypad_7); keys[7]=findViewById(R.id.keypad_8); keys[8]=findViewById(R.id.keypad_9); } private void setListeners(){ for(int i=0;i<keys.length;i++){ final int t=i+1; keys[i].setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { // TODO Auto-generated method stub returnResult(t); } }); } keypad.setOnClickListener(new View.OnClickListener() { public void onClick(View arg0) { // TODO Auto-generated method stub returnResult(0); } }); } private void returnResult(int tile) { // TODO Auto-generated method stub puzzleView.setSelectedTile(tile); dismiss(); }}

五、背景音乐

package com.dw.gamesuduku;import android.content.Context;import android.media.MediaPlayer;public class Music { private static MediaPlayer mp=null; //stop old song and start a new song public static void play(Context context,int resource){ stop(context); if(Settings.getMusic(context)){ mp=MediaPlayer.create(context, resource); mp.setLooping(true); mp.start(); } } //stop the music public static void stop(Context context) { // TODO Auto-generated method stub if(mp!=null){ mp.stop(); mp.release(); mp=null; } }}

六、逻辑实现2

package com.dw.gamesuduku;import android.annotation.SuppressLint;import android.content.Context;import android.graphics.Canvas;import android.graphics.Paint;import android.graphics.Paint.FontMetrics;import android.graphics.Paint.Style;import android.graphics.Rect;import android.os.Bundle;import android.os.Parcelable;import android.util.Log;import android.view.KeyEvent;import android.view.MotionEvent;import android.view.View;import android.view.animation.AnimationUtils;@SuppressLint("DrawAllocation")public class PuzzleView extends View { private static final String TAG = "Sudoku"; private final Game game; private float width; private float height; private int selX; private int selY; private final Rect selRect = new Rect(); private static final String SELX="selX"; private static final String SELY="selY"; private static final String VIEW_STATE="viewState"; private static final int ID=42;//any positive int num public PuzzleView(Context context) { super(context); this.game = (Game) context; setFocusable(true); setFocusableInTouchMode(true); setId(ID); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { // TODO Auto-generated method stub width = w / 9f; height = h / 9f; getRect(selX, selY, selRect); Log.d(TAG, "onSizeChanged:width" + width + ",height" + height); super.onSizeChanged(w, h, oldw, oldh); } //实例状态保存在bundle中,保存当前游戏状态 @Override protected Parcelable onSaveInstanceState() { // TODO Auto-generated method stub Parcelable p=super.onSaveInstanceState(); Log.d(TAG, "onSavedInstanceState"); Bundle bundle=new Bundle(); bundle.putInt(SELX, selX); bundle.putInt(SELY, selY); bundle.putParcelable(VIEW_STATE, p); return bundle; } //恢复已经保存的信息 @Override protected void onRestoreInstanceState(Parcelable state) { // TODO Auto-generated method stub Log.d(TAG, "onRestoreInstanceState"); Bundle bundle=(Bundle) state; select(bundle.getInt(SELX),bundle.getInt(SELY)); super.onRestoreInstanceState(bundle.getParcelable(VIEW_STATE)); return; } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub // draw background Paint background = new Paint(); background.setColor(getResources().getColor(R.color.puzzle_background)); canvas.drawRect(0, 0, getWidth(), getHeight(), background); // draw board Paint dark = new Paint(); dark.setColor(getResources().getColor(R.color.puzzle_dark)); Paint hilite = new Paint(); hilite.setColor(getResources().getColor(R.color.puzzle_hilite)); Paint light = new Paint(); light.setColor(getResources().getColor(R.color.puzzle_light)); // draw minor grid lines for (int i = 0; i < 9; i++) { canvas.drawLine(0, i * height, getWidth(), i * height, light); canvas.drawLine(0, i * height + 1, getWidth(), i * height + 1, hilite); canvas.drawLine(i * width, 0, i * width, getHeight(), dark); canvas.drawLine(i * width + 1, 0, i * width + 1, getHeight(), hilite); } // draw major grid lines for (int i = 0; i < 9; i++) { if (i % 3 != 0) continue; canvas.drawLine(0, i * height, getWidth(), i * height, dark); canvas.drawLine(0, i * height + 1, getWidth(), i * height + 1, hilite); canvas.drawLine(i * width, 0, i * width, getHeight(), dark); canvas.drawLine(i * width + 1, 0, i * width + 1, getHeight(), hilite); } // draw numbers Paint foreground = new Paint(Paint.ANTI_ALIAS_FLAG); foreground.setColor(getResources().getColor(R.color.puzzle_foregroud)); foreground.setStyle(Style.FILL); foreground.setTextSize(height * 0.75f); foreground.setTextScaleX(width / height); foreground.setTextAlign(Paint.Align.CENTER); // draw num in the center of the tile FontMetrics fm = foreground.getFontMetrics(); float x = width / 2; float y = height / 2 - (fm.ascent + fm.descent) / 2; for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { canvas.drawText(this.game.getTitleString(i, j), i * width + x, j * height + y, foreground); } } // draw the selection Log.e(TAG, "selRect=" + selRect); Paint selected = new Paint(); selected.setColor(getResources().getColor(R.color.puzzle_selected)); canvas.drawRect(selRect, selected); //draw the hints pick a hint color based on moves left //根据每个单元格可填的数目给出不同颜色的提示 if(Settings.getHints(getContext())){ Paint hint=new Paint(); int c[]={getResources().getColor(R.color.puzzle_hint_0), getResources().getColor(R.color.puzzle_hint_1), getResources().getColor(R.color.puzzle_hint_2),}; Rect r=new Rect(); for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { int movesleft=9-game.getUsedTiles(i, j).length; if(movesleft<c.length){ getRect(i, j, r); hint.setColor(c[movesleft]); canvas.drawRect(r, hint); } } } } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // TODO Auto-generated method stub Log.d(TAG, "onKeyDown:keycode=" + keyCode + ",event=" + event); switch (keyCode) { case KeyEvent.KEYCODE_DPAD_UP: select(selX, selY - 1); break; case KeyEvent.KEYCODE_DPAD_DOWN: select(selX, selY + 1); break; case KeyEvent.KEYCODE_DPAD_LEFT: select(selX - 1, selY); break; case KeyEvent.KEYCODE_DPAD_RIGHT: select(selX + 1, selY); break; case KeyEvent.KEYCODE_0: case KeyEvent.KEYCODE_SPACE: setSelectedTile(0); break; case KeyEvent.KEYCODE_1: setSelectedTile(1); break; case KeyEvent.KEYCODE_2: setSelectedTile(2); break; case KeyEvent.KEYCODE_3: setSelectedTile(3); break; case KeyEvent.KEYCODE_4: setSelectedTile(4); break; case KeyEvent.KEYCODE_5: setSelectedTile(5); break; case KeyEvent.KEYCODE_6: setSelectedTile(6); break; case KeyEvent.KEYCODE_7: setSelectedTile(7); break; case KeyEvent.KEYCODE_8: setSelectedTile(8); break; case KeyEvent.KEYCODE_9: setSelectedTile(9); break; case KeyEvent.KEYCODE_ENTER: case KeyEvent.KEYCODE_DPAD_CENTER: game.showKeypadOrError(selX, selY); default: return super.onKeyDown(keyCode, event); } return true; } //显示软键盘 @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub if(event.getAction()!=MotionEvent.ACTION_DOWN) return super.onTouchEvent(event); select((int)(event.getX()/width),(int)(event.getY()/height)); game.showKeypadOrError(selX, selY); Log.d(TAG, "onTouchEvent:x"+selX+",y"+selY); return true; } public void setSelectedTile(int tile) { // TODO Auto-generated method stub //num is not valid for this tile Log.d(TAG, "selectedTile:invalid:"+tile);// startAnimation(AnimationUtils.loadAnimation(game, R.aims.shake)); if (game.setTileIfValid(selX, selY, tile)) { invalidate(); } else { // num is not invalid for this tile Log.d(TAG, "setSelectedTile:invalid " + tile); } } // 首先计算选定区域的x,y坐标,然后再次调用getRect方法计算新的选择矩形 private void select(int x, int y) { // TODO Auto-generated method stub invalidate(selRect);// 第一次调用通知原选择的区域需要重绘 selX = Math.min(Math.max(x, 0), 8); selY = Math.min(Math.max(y, 0), 8); getRect(selX, selY, selRect); invalidate(selRect);// 第二次调用通知新选择的区域也需要重绘 } private void getRect(int x, int y, Rect rect) { // TODO Auto-generated method stub rect.set((int) (x * width), (int) (y * height), (int) (x * width + width), (int) (y * height + height)); }}

七、游戏设置

package com.dw.gamesuduku;import android.app.Activity;import android.content.Context;import android.os.Bundle;import android.preference.PreferenceFragment;import android.preference.PreferenceManager;public class Settings extends Activity { private static final String OPT_MUSIC="music"; private static final boolean OPT_MUSIC_DEF=true; private static final String OPT_HINTS="hints"; private static final boolean OPT_HINTS_DEF=true; @Override public void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); getFragmentManager().beginTransaction().replace(android.R.id.content, new PrefsFragement()).commit(); } public static class PrefsFragement extends PreferenceFragment{ public void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.settings); } } //get the current music option public static boolean getMusic(Context context){ return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(OPT_MUSIC,OPT_MUSIC_DEF); } //get the current music option public static boolean getHints(Context context){ return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(OPT_HINTS,OPT_HINTS_DEF); }}

八、游戏入口

package com.dw.gamesuduku;import android.app.Activity;import android.app.AlertDialog;import android.content.DialogInterface;import android.content.Intent;import android.os.Bundle;import android.util.Log;import android.view.Menu;import android.view.MenuInflater;import android.view.MenuItem;import android.view.View;import android.view.View.OnClickListener;public class Sudoku extends Activity implements OnClickListener { private static final String TAG = "Sudoku"; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); View continueButton = this.findViewById(R.id.continue_button); continueButton.setOnClickListener(this); View newButton = this.findViewById(R.id.new_button); newButton.setOnClickListener(this); View aboutButton = this.findViewById(R.id.about_button); aboutButton.setOnClickListener(this); View exitButton = this.findViewById(R.id.exit_button); exitButton.setOnClickListener(this); } @Override public void onClick(View v) { // TODO Auto-generated method stub switch (v.getId()) { case R.id.continue_button: startGame(Game.DIFFICULTY_CONTINUE); case R.id.about_button: Intent i = new Intent(this, About.class); startActivity(i); break; case R.id.new_button: openNewGameDialog(); break; case R.id.exit_button: finish(); break; } } @Override protected void onResume() { // TODO Auto-generated method stub super.onResume(); Music.play(this, R.raw.welcome); } @Override protected void onPause() { // TODO Auto-generated method stub super.onPause(); Music.stop(this); } @Override public boolean onCreateOptionsMenu(Menu menu) { // TODO Auto-generated method stub super.onCreateOptionsMenu(menu); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // TODO Auto-generated method stub switch (item.getItemId()) { case R.id.settings: startActivity(new Intent(this, Settings.class)); return true; } return false; } private void openNewGameDialog() { // TODO Auto-generated method stub new AlertDialog.Builder(this).setTitle(R.string.new_game_title) .setItems(R.array.difficulty, new DialogInterface.OnClickListener() { @Override public void onClick( DialogInterface dialoginterface, int i) { // TODO Auto-generated method stub startGame(i); } }).show(); } protected void startGame(int i) { // TODO Auto-generated method stub Log.i(TAG, "clicked on"+i); Intent intent=new Intent(Sudoku.this,Game.class); intent.putExtra(Game.KEY_DIFFICULTY, i); startActivity(intent); }}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

相关文章