1 / 23

Lecture 11 Wrapping Up the Sudoku Demo Graphics and Game Logic

Lecture 11 Wrapping Up the Sudoku Demo Graphics and Game Logic . Running the Gauntlet.

gamba
Download Presentation

Lecture 11 Wrapping Up the Sudoku Demo Graphics and Game Logic

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Lecture 11 Wrapping Up the Sudoku Demo Graphics and Game Logic

  2. Running the Gauntlet In Chapter 4 of Hello Android! the author "wraps up" the graphics and game logic for the Sudoku demo application. Unfortunately this chapter is clearly the result of a slap-dash, rush-to-press leaving the reader scratching their heads and asking, "Will I ever be able to hit the Run button?" While stumbling through this random walk of "oh yeah, you'll also need this and this and this..." a few Android programming techniques are revealed. (1) When you get an XML syntax error in the resources (res), you first need to find and fix the error, Clean the project, and finally attempt to run (i.e. force a build) in order to recover the resources file (R), which disappears whenever an xml syntax error occurs. (2) If you opt to create a Blank Activity when you start a new project, you will need to do a lot more manual code entry than if you choose another option. For other options, Eclipse and the ADT automatically generate some of the necessary code. This can cause confusion when following an app development example such as the Sudoku Game demo. (3) When building your own Android apps, you should use best programming practices. The author seems to prefer dumping a lot of code into the same class that would be better placed in its separate classes. Switch fall-throughs and magic literals are considered sloppy coding and should generally be avoided...don't get me started...

  3. keypad.xml <?xmlversion="1.0"encoding="utf-8"?> <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/keypad" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" android:stretchColumns="*"> <TableRow> <Buttonandroid:id="@+id/keypad_1" android:text="@string/one" > </Button> <Buttonandroid:id="@+id/keypad_2" android:text="@string/two"> </Button> <Buttonandroid:id="@+id/keypad_3" android:text="@string/three"> </Button> </TableRow> <TableRow> <Buttonandroid:id="@+id/keypad_4" android:text="@string/four"> </Button> <Buttonandroid:id="@+id/keypad_5" android:text="@string/five"> </Button> <Buttonandroid:id="@+id/keypad_6" android:text="@string/six"> </Button> </TableRow> <TableRow> <Buttonandroid:id="@+id/keypad_7" android:text="@string/seven"> </Button> <Buttonandroid:id="@+id/keypad_8" android:text="@string/eight"> </Button> <Buttonandroid:id="@+id/keypad_9" android:text="@string/nine"> </Button> </TableRow> </TableLayout> add to strings.xml <stringname="one">1</string> <stringname="two">2</string> <stringname="three">3</string> <stringname="four">4</string> <stringname="five">5</string> <stringname="six">6</string> <stringname="seven">7</string> <stringname="eight">8</string> <stringname="nine">9</string>

  4. AndroidManifest.xml <?xmlversion="1.0"encoding="utf-8"?> <manifestxmlns:android="http://schemas.android.com/apk/res/android" package="org.example.sudoku" android:versionCode="1" android:versionName="1.0"> <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme"> <activity android:name="org.example.sudoku.Sudoku" android:label="@string/app_name"> <intent-filter> <actionandroid:name="android.intent.action.MAIN"/> <categoryandroid:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activityandroid:name=".About" android:label="@string/about_title" android:theme="@android:style/Theme.Dialog"> </activity> <activityandroid:name=".NewGame" android:label="@string/newgame_title"> </activity> <activityandroid:name=".Prefs" android:label="@string/settings_title"> </activity> <activityandroid:name=".Game" android:label="@string/game_title"> </activity> </application> </manifest>

  5. colors.xml <?xmlversion="1.0"encoding="utf-8"?> <resources> <colorname="bkg">#006666</color> <colorname="btn">#66ffff</color> <colorname="puzzle_background">#ffe6f0ff</color> <colorname="puzzle_hilite">#ffffffff</color> <colorname="puzzle_light">#64c6d4ef</color> <colorname="puzzle_dark">#6456648f</color> <colorname="puzzle_foreground">#ff000000</color> <colorname="puzzle_hint_0">#64ff0000</color> <colorname="puzzle_hint_1">#6400ff80</color> <colorname="puzzle_hint_2">#2000ff80</color> <colorname="puzzle_selected">#64ff8000</color> </resources>

  6. The res/anim Folder and Its Contents cycle_7.xml <?xmlversion="1.0"encoding="utf-8"?> <cycleInterpolator xmlns:android="http://schemas.android.com/apk/res/android" android:cycles="7" /> shake.xml <?xmlversion="1.0"encoding="utf-8"?> <translate xmlns:android="http://schemas.android.com/apk/res/android" android:fromXDelta="0" android:toXDelta="10" android:duration="1000" android:interpolator="@anim/cycle_7"/> from PuzzleView.java publicvoid setSelectedTile(int tile) { if (game.setTileIfValid(selX, selY, tile)) { invalidate();// may change hints } else { // Number is not valid for this tile Log.d(TAG, "setSelectedTile: invalid: " + tile); startAnimation(AnimationUtils.loadAnimation(game,R.anim.shake)); } } The manner in which this code is implemented in the text is likely to create xml errors which will require a clean and forced rebuild of the project.

  7. 3 Sample Puzzles Hardwired in Game Class privatefinal String easyPuzzle = "360000000004230800000004200"+ "070460003820000014500013020" + "001900000007048300000000045" ; privatefinal String mediumPuzzle = "650000070000506000014000005" + "007009000002314700000700800" + "500000630000201000030000097" ; privatefinal String hardPuzzle = "009000000080605020501078000" + "000000700706040102004000000" + "000720903090301080000000600" ; medium hard easy

  8. Keypad.java package org.example.sudoku; import android.app.Dialog; import android.content.Context; import android.os.Bundle; import android.view.KeyEvent; import android.view.View; publicclass Keypad extends Dialog { protectedstaticfinal String TAG = "Sudoku" ; privatefinal View keys[] = new View[9]; private View keypad; privatefinalintuseds[]; privatefinal PuzzleView puzzleView; public Keypad(Context context, int useds[], PuzzleView puzzleView) { super(context); this.useds = useds; this.puzzleView = puzzleView; } @Override protectedvoid onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTitle(R.string.keypad_title); setContentView(R.layout.keypad); findViews(); for (int element : useds) { if (element != 0) keys[element - 1].setVisibility(View.INVISIBLE); } setListeners(); }

  9. Keypad.java (cont.) privatevoid findViews() { 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); } privatevoid setListeners() { for (int i = 0; i < keys.length; i++) { finalint t = i + 1; keys[i].setOnClickListener(new View.OnClickListener() { publicvoid onClick(View v) { returnResult(t); } }); } keypad.setOnClickListener(new View.OnClickListener() { publicvoid onClick(View v) { returnResult(0); } }); }

  10. Keypad.java (conc.) @Override publicboolean onKeyDown(int keyCode, KeyEvent event) { 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: returnsuper.onKeyDown(keyCode, event); } if (isValid(tile)) { returnResult(tile); } returntrue; } privateboolean isValid(int tile) { for (int t : useds) { if (tile == t) returnfalse; } returntrue; } privatevoid returnResult(int tile) { puzzleView.setSelectedTile(tile); dismiss(); } }

  11. PuzzleView.java package org.example.sudoku; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.Paint.FontMetrics; import android.graphics.Paint.Style; import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.animation.AnimationUtils; publicclass PuzzleView extends View { privatestaticfinal String TAG = "Sudoku" ; privatefinal Game game; privatefloatwidth; // width of one tile privatefloatheight; // height of one tile privateintselX; // X index of selection privateintselY; // Y index of selection privatefinal Rect selRect = new Rect(); @Override protectedvoid onSizeChanged(int w, int h, int oldw, int oldh) { width = w / 9f; height = h / 9f; getRect(selX, selY, selRect); Log.d(TAG, "onSizeChanged: width " + width + ", height " + height); super.onSizeChanged(w, h, oldw, oldh); }

  12. PuzzleView.java (cont.) privatevoid getRect(int x, int y, Rect rect) { rect.set((int) (x * width), (int) (y * height), (int) (x* width + width), (int) (y * height + height)); } public PuzzleView(Context context) { super(context); this.game = (Game) context; setFocusable(true); setFocusableInTouchMode(true); } @Override protectedvoid onDraw(Canvas canvas) { // Draw the background... Paint background = new Paint(); background.setColor(getResources().getColor(R.color.puzzle_background)); canvas.drawRect(0, 0, getWidth(), getHeight(), background); // Draw the board... // Define colors for the grid lines 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 the 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(), light); canvas.drawLine(i * width + 1, 0, i * width + 1,getHeight(), hilite); }

  13. PuzzleView.java (cont.) // Draw the 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 the numbers... // Define color and style for numbers Paint foreground = new Paint(Paint.ANTI_ALIAS_FLAG); foreground.setColor(getResources().getColor(R.color.puzzle_foreground)); foreground.setStyle(Style.FILL); foreground.setTextSize(height * 0.75f); foreground.setTextScaleX(width / height); foreground.setTextAlign(Paint.Align.CENTER); // Draw the number in the center of the tile FontMetrics fm = foreground.getFontMetrics(); // Centering in X: use alignment (and X at midpoint) float x = width / 2; // Centering in Y: measure ascent/descent first 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.getTileString(i, j), i* width + x, j * height + y, foreground); } }

  14. PuzzleView.java (cont.) // Draw the hints... // Pick a hint color based on #moves left 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); } } } // Draw the selection... Log.d(TAG, "selRect=" + selRect); Paint selected = new Paint(); selected.setColor(getResources().getColor(R.color.puzzle_selected)); canvas.drawRect(selRect, selected); }

  15. PuzzleView.java (cont.) @Override publicboolean onKeyDown(int keyCode, KeyEvent event) { 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); break; default: returnsuper.onKeyDown(keyCode, event); } returntrue; }

  16. PuzzleView.java (conc.) privatevoid select(int x, int y) { invalidate(selRect); selX = Math.min(Math.max(x, 0), 8); selY = Math.min(Math.max(y, 0), 8); getRect(selX, selY, selRect); invalidate(selRect); } @Override publicboolean onTouchEvent(MotionEvent event) { if (event.getAction() != MotionEvent.ACTION_DOWN) returnsuper.onTouchEvent(event); select((int) (event.getX() / width), (int) (event.getY() / height)); game.showKeypadOrError(selX, selY); Log.d(TAG, "onTouchEvent: x " + selX + ", y " + selY); returntrue; } publicvoid setSelectedTile(int tile) { if (game.setTileIfValid(selX, selY, tile)) { invalidate();// may change hints } else { // Number is not valid for this tile Log.d(TAG, "setSelectedTile: invalid: " + tile); startAnimation(AnimationUtils.loadAnimation(game,R.anim.shake)); } } }

  17. Game.java package org.example.sudoku; import android.app.Activity; import android.app.Dialog; import android.os.Bundle; import android.util.Log; import android.view.Gravity; import android.widget.Toast; publicclass Game extends Activity { privatestaticfinal String TAG = "Sudoku" ; privatefinal String easyPuzzle = "360000000004230800000004200" + "070460003820000014500013020" + "001900000007048300000000045" ; privatefinal String mediumPuzzle = "650000070000506000014000005" + "007009000002314700000700800" + "500000630000201000030000097" ; privatefinal String hardPuzzle = "009000000080605020501078000" + "000000700706040102004000000" + "000720903090301080000000600" ; publicstaticfinal String KEY_DIFFICULTY ="org.example.sudoku.difficulty" ; publicstaticfinalintDIFFICULTY_EASY = 0; publicstaticfinalintDIFFICULTY_MEDIUM = 1; publicstaticfinalintDIFFICULTY_HARD = 2; privateintpuzzle[] = newint[9 * 9]; privatefinalintused[][][] = newint[9][9][]; protectedint[] getUsedTiles(int x, int y) { returnused[x][y]; }

  18. Game.java (cont.) private PuzzleView puzzleView; @Override protectedvoid onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate" ); int diff = getIntent().getIntExtra(KEY_DIFFICULTY,DIFFICULTY_EASY); puzzle = getPuzzle(diff); calculateUsedTiles(); puzzleView = new PuzzleView(this); setContentView(puzzleView); puzzleView.requestFocus(); } privateint[] getPuzzle(int diff) { String puz; // TODO: Continue last game switch (diff) { caseDIFFICULTY_HARD: puz = hardPuzzle; break; caseDIFFICULTY_MEDIUM: puz = mediumPuzzle; break; caseDIFFICULTY_EASY: default: puz = easyPuzzle; break; } returnfromPuzzleString(puz); }

  19. Game.java (cont.) protectedvoid showKeypadOrError(int x, int y) { 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.CENTER, 0, 0); toast.show(); } else { Log.d(TAG, "showKeypad: used=" + toPuzzleString(tiles)); Dialog v = new Keypad(this, tiles, puzzleView); v.show(); } } protectedboolean setTileIfValid(int x, int y, int value) { int tiles[] = getUsedTiles(x, y); if (value != 0) { for (int tile : tiles) { if (tile == value) returnfalse; } } setTile(x, y, value); calculateUsedTiles(); returntrue; }

  20. Game.java (cont.) privatevoid calculateUsedTiles() { for (int x = 0; x < 9; x++) { for (int y = 0; y < 9; y++) { used[x][y] = calculateUsedTiles(x, y); // Log.d(TAG, "used[" + x + "][" + y + "] = " // + toPuzzleString(used[x][y])); } } } privateint[] calculateUsedTiles(int x, int y) { int c[] = newint[9]; // horizontal for (int i = 0; i < 9; i++) { if (i == y) continue; int t = getTile(x, i); if (t != 0) c[t - 1] = t; } // vertical for (int i = 0; i < 9; i++) { if (i == x) continue; int t = getTile(i, y); if (t != 0) c[t - 1] = t; }

  21. Game.java (cont.) // 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 = getTile(i, j); if (t != 0) c[t - 1] = t; } } // compress int nused = 0; for (int t : c) { if (t != 0) nused++; } int c1[] = newint[nused]; nused = 0; for (int t : c) { if (t != 0) c1[nused++] = t; } return c1; }

  22. Game.java (conc.) staticprivate String toPuzzleString(int[] puz) { StringBuilder buf = new StringBuilder(); for (int element : puz) { buf.append(element); } return buf.toString(); } staticprotectedint[] fromPuzzleString(String string) { int[] puz = newint[string.length()]; for (int i = 0; i < puz.length; i++) { puz[i] = string.charAt(i) - '0' ; } return puz; } privateint getTile(int x, int y) { returnpuzzle[y * 9 + x]; } privatevoid setTile(int x, int y, int value) { puzzle[y * 9 + x] = value; } protected String getTileString(int x, int y) { int v = getTile(x, y); if (v == 0) return"" ; else return String.valueOf(v); } }

More Related