9207d3d5358f0187b6cc004b488de870957d6ee6
[FreeShisen] / src / de / cwde / freeshisen / ShisenShoView.java
1 package de.cwde.freeshisen;
2
3 import java.lang.ref.WeakReference;
4 import java.util.List;
5 import java.util.Locale;
6 import java.util.Timer;
7 import java.util.TimerTask;
8
9 import android.app.Activity;
10 import android.app.AlertDialog;
11 import android.content.Context;
12 import android.content.SharedPreferences;
13 import android.graphics.Bitmap;
14 import android.graphics.BitmapFactory;
15 import android.graphics.Canvas;
16 import android.graphics.Color;
17 import android.graphics.Matrix;
18 import android.graphics.Paint;
19 import android.graphics.Paint.Align;
20 import android.graphics.Paint.Cap;
21 import android.graphics.Paint.Join;
22 import android.graphics.Paint.Style;
23 import android.graphics.Rect;
24 import android.graphics.Typeface;
25 import android.os.Handler;
26 import android.os.Message;
27 import android.preference.PreferenceManager;
28 import android.view.MenuItem;
29 import android.view.MotionEvent;
30 import android.view.SurfaceHolder;
31 import android.view.SurfaceView;
32
33 class ShisenShoView extends SurfaceView implements SurfaceHolder.Callback {
34
35 private static final String INVALID_TIME = "9:99:99";
36 private static final String COLOR_TEXT = "#FFFFFF";
37 private static final String COLOR_TEXT_SHADOW = "#000000";
38 private static final String COLOR_HINT = "#F0C000";
39 private static final String COLOR_SELECTED = "#FF0000";
40
41 private enum StatePlay { UNINITIALIZED, IDLE, SELECTED1, SELECTED2, GAMEOVER };
42 private enum StatePaint { BOARD, SELECTED1, SELECTED2, MATCHED, WIN, LOSE, HINT, TIME };
43
44 private int screenWidth;
45 private int screenHeight;
46 private int tilesetRows;
47 private int tilesetCols;
48 private int tileHeight;
49 private int tileWidth;
50 private Bitmap bg;
51 private Bitmap tile[];
52 private Point selection1 = new Point(0, 0);
53 private Point selection2 = new Point(0, 0);
54 private List<Point> path = null;
55 private List<Line> pairs = null;
56 private long startTime;
57 private long playTime;
58 private long baseTime;
59 private Timer timer;
60
61 static class hHandler extends Handler {
62 private final WeakReference<ShisenShoView> mTarget;
63
64 hHandler(ShisenShoView target) {
65 mTarget = new WeakReference<ShisenShoView>(target);
66 }
67
68 @Override
69 public void handleMessage(Message msg) {
70 ShisenShoView target = mTarget.get();
71 if (target != null)
72 target.onUpdateTime();
73 }
74 }
75
76 private Handler timerHandler = new hHandler(this);
77
78 private boolean timerRegistered = false;
79 private ShisenSho app;
80 private StatePlay cstate;
81 private StatePaint pstate;
82 private Canvas canvas = null;
83 private SurfaceHolder surfaceHolder = null;
84 private String time = INVALID_TIME;
85
86 public ShisenShoView(ShisenSho shishenSho) {
87 super((Context) shishenSho);
88 this.app = shishenSho;
89 cstate = StatePlay.UNINITIALIZED;
90 surfaceHolder = getHolder();
91 surfaceHolder.addCallback(this);
92 }
93
94 public ShisenShoView(Context ctx) {
95 super(ctx);
96 this.app = (ShisenSho) ctx;
97 cstate = StatePlay.UNINITIALIZED;
98 surfaceHolder = getHolder();
99 surfaceHolder.addCallback(this);
100 }
101
102 private void paint(StatePaint pstate) {
103 this.pstate=pstate;
104 repaint();
105 }
106
107 private void control(StatePlay cstate) {
108 this.cstate=cstate;
109 }
110
111 public void loadTileset() {
112 BitmapFactory.Options ops = new BitmapFactory.Options();
113 ops.inScaled = false;
114 Bitmap tileset = BitmapFactory.decodeResource(getResources(), app.tilesetid, ops);
115 tileset.setDensity(Bitmap.DENSITY_NONE);
116
117 // The tile set has 4 rows x 9 columns
118 tilesetRows = 4;
119 tilesetCols = 9;
120 int loadedtileWidth = tileset.getWidth()/tilesetCols;
121 int loadedtileHeight = tileset.getHeight()/tilesetRows;
122 tile = new Bitmap[tilesetRows*tilesetCols];
123
124 // align to screen:
125 // "large" is 16x6, and we want to have a nice border, so we use 17x7 and
126 // choose the lowest scale so everything fits
127 float scalex = ((float) (screenWidth - 2)/17) / loadedtileWidth;
128 float scaley = ((float) (screenHeight - 2)/7) / loadedtileHeight;
129 if (scaley < scalex) {
130 scalex = scaley;
131 } else {
132 scaley = scalex;
133 }
134 Matrix matrix = new Matrix();
135 matrix.setScale(scalex, scaley);
136
137 int k=0;
138 for (int i=0; i<tilesetRows; i++) {
139 for (int j=0; j<tilesetCols; j++) {
140 tile[k] = Bitmap.createBitmap(tileset, j*loadedtileWidth, i*loadedtileHeight,
141 loadedtileWidth, loadedtileHeight, matrix, false);
142 tile[k].setDensity(Bitmap.DENSITY_NONE);
143 k++;
144 }
145 }
146 tileWidth = tile[0].getWidth();
147 tileHeight = tile[0].getHeight();
148 }
149
150 private void loadBackground() {
151 BitmapFactory.Options ops = new BitmapFactory.Options();
152 ops.inScaled = false;
153 bg = BitmapFactory.decodeResource(getResources(), R.drawable.kshisen_bgnd, ops);
154 bg.setDensity(Bitmap.DENSITY_NONE);
155 }
156
157 private void registerTimer() {
158 if (timer != null)
159 return; // Already registered
160 timer = new Timer();
161 timer.scheduleAtFixedRate(new TimerTask() {
162 public void run() {
163 timerHandler.sendEmptyMessage(Activity.RESULT_OK);
164 }
165 }, 0, 1000);
166 timerRegistered = true;
167 }
168
169 private void unregisterTimer() {
170 if (timer == null)
171 return; // Already unregistered
172 timer.cancel();
173 timer = null;
174 timerRegistered = false;
175 }
176
177 public void pauseTime() {
178 updateTime();
179 baseTime = playTime;
180 startTime = System.currentTimeMillis();
181
182 }
183
184 public void resumeTime() {
185 startTime = System.currentTimeMillis();
186 updateTime();
187 }
188
189 private void updateTime() {
190 if (cstate!=StatePlay.GAMEOVER) {
191 playTime = (System.currentTimeMillis()-startTime)/1000+baseTime;
192 }
193 }
194
195 private void initializeGame() {
196 loadBackground();
197 screenWidth=getWidth();
198 screenHeight=getHeight();
199 loadTileset();
200 //undo.sensitive=false;
201 pstate=StatePaint.BOARD;
202 app.newPlay();
203 control(StatePlay.IDLE);
204 startTime=System.currentTimeMillis();
205 playTime=0;
206 baseTime=0;
207 if (!timerRegistered) {
208 registerTimer();
209 }
210 pairs=app.board.getPairs(1);
211 }
212
213 public boolean onOptionsItemSelected(MenuItem item) {
214 // Handle item selection
215 switch (item.getItemId()) {
216 case R.id.hint:
217 this.postDelayed(new Runnable() { public void run() { onHintActivate(); } }, 100);
218 return true;
219 case R.id.undo:
220 this.postDelayed(new Runnable() { public void run() { onUndoActivate(); } }, 100);
221 return true;
222 case R.id.clean:
223 this.postDelayed(new Runnable() { public void run() { reset(); } }, 100);
224 return true;
225 case R.id.options:
226 return true;
227 case R.id.about:
228 return true;
229 default:
230 return false;
231 }
232 }
233
234 public void reset() {
235 control(StatePlay.UNINITIALIZED);
236 paint(StatePaint.BOARD);
237 }
238
239 private void onHintActivate() {
240 if (cstate!=StatePlay.GAMEOVER) {
241 pairs=app.board.getPairs(1);
242 paint(StatePaint.HINT);
243 app.sleep(10);
244 paint(StatePaint.BOARD);
245 control(StatePlay.IDLE);
246 }
247 }
248
249 private void onUndoActivate() {
250 if (app.board.getCanUndo()) {
251 if (cstate==StatePlay.GAMEOVER && !timerRegistered) {
252 // Reprogram the time update that had been
253 // deactivated with the game over status
254 registerTimer();
255 }
256 app.board.undo();
257 paint(StatePaint.BOARD);
258 //undo.sensitive=app.board.getCanUndo();
259 control(StatePlay.IDLE);
260 }
261 }
262
263 public void onTimeCounterActivate() {
264 if (cstate!=StatePlay.GAMEOVER && !timerRegistered) {
265 // Reprogram the time update that had been
266 // deactivated with the time_counter=false
267 registerTimer();
268 }
269 }
270
271 private void onUpdateTime() {
272 paint(pstate);
273 if (cstate==StatePlay.GAMEOVER) {
274 unregisterTimer();
275 }
276 }
277
278 @SuppressWarnings("deprecation")
279 public static void drawMessage(Canvas canvas, int x, int y,
280 boolean centered, String message, float textSize) {
281 Paint paint = new Paint();
282 paint.setLinearText(true);
283 paint.setAntiAlias(true);
284 paint.setTextAlign(centered ? Align.CENTER : Align.LEFT);
285 paint.setTypeface(Typeface.SANS_SERIF);
286 paint.setFakeBoldText(true);
287 paint.setTextSize(textSize);
288 paint.setColor(Color.parseColor(COLOR_TEXT_SHADOW));
289 canvas.drawText(message, x + 1, y + 1, paint);
290 paint.setColor(Color.parseColor(COLOR_TEXT));
291 canvas.drawText(message, x, y, paint);
292 }
293
294 public void repaint() {
295 if (surfaceHolder == null) return;
296 try {
297 if (canvas == null) canvas = surfaceHolder.lockCanvas(null);
298 if (canvas == null) return;
299 if (cstate==StatePlay.UNINITIALIZED) initializeGame();
300 synchronized (surfaceHolder) {
301 doDraw(canvas);
302 }
303 } finally {
304 if (canvas != null) {
305 surfaceHolder.unlockCanvasAndPost(canvas);
306 canvas = null;
307 }
308 }
309 }
310
311 protected void doDraw(Canvas canvas) {
312 try {
313 if (canvas == null) return;
314
315 // Board upper left corner on screen
316 int x0=0;
317 int y0=0;
318
319 if (app!=null && app.board!=null) {
320 x0=(screenWidth-app.board.boardSize[1]*tileWidth)/2;
321 y0=(screenHeight-app.board.boardSize[0]*tileHeight)/2;
322 }
323
324 int selectcolor = Color.parseColor(COLOR_SELECTED);
325 int hintcolor = Color.parseColor(COLOR_HINT);
326
327 // Background & board painting
328 switch (pstate) {
329 case BOARD:
330 case SELECTED1:
331 case SELECTED2:
332 case MATCHED:
333 case WIN:
334 case LOSE:
335 case HINT:
336 case TIME:
337 // Background painting
338 int bgWidth = bg.getWidth();
339 int bgHeight = bg.getHeight();
340 for (int i=0; i<screenHeight/bgHeight+1; i++) {
341 for (int j=0; j<screenWidth/bgWidth+1; j++) {
342 canvas.drawBitmap(bg, j*bgWidth, i*bgHeight, null);
343 }
344 }
345
346 // Board painting
347 // Max visible size: 7x17
348 if (app!=null && app.board!=null) {
349 for (int i=0;i<app.board.boardSize[0];i++) {
350 for (int j=0;j<app.board.boardSize[1];j++) {
351 // Tiles are 56px height, 40px width each
352 char piece=app.board.board[i][j];
353 if (piece!=0) {
354 canvas.drawBitmap(tile[piece], x0+j*tileWidth, y0+i*tileHeight, null);
355 }
356 }
357 }
358 }
359 break;
360 }
361
362 // rectangle for selection 1
363 switch (pstate) {
364 case SELECTED1:
365 case SELECTED2:
366 case MATCHED:
367 highlightTile(canvas, x0, y0, selection1, selectcolor);
368 break;
369 }
370
371 // rectangle for selection 2
372 switch (pstate) {
373 case SELECTED2:
374 case MATCHED:
375 highlightTile(canvas, x0, y0, selection2, selectcolor);
376 break;
377 }
378
379 // Matching path
380 switch (pstate) {
381 case MATCHED:
382 if (path!=null) {
383 Point p0=null;
384 for (Point p1 : path) {
385 if (p0!=null) {
386 drawLine(canvas, x0, y0, p0, p1, selectcolor);
387 }
388 p0=p1;
389 }
390 }
391 break;
392 }
393
394 // hint rectangles
395 switch (pstate) {
396 case HINT:
397 if (pairs != null && pairs.size() > 0) {
398 Line pair = pairs.get(0);
399 Point a = pair.a;
400 Point b = pair.b;
401 path = app.board.getPath(a, b);
402
403 highlightTile(canvas, x0, y0, a, hintcolor);
404
405 if (path != null) {
406 Point p0 = null;
407 for (Point p1 : path) {
408 if (p0 != null) {
409 drawLine(canvas, x0, y0, p0, p1, hintcolor);
410 }
411 p0 = p1;
412 }
413 path = null;
414 }
415
416 highlightTile(canvas, x0, y0, b, hintcolor);
417 }
418 break;
419 }
420
421 // Win & loose notifications
422 switch (pstate) {
423 case WIN:
424 drawMessage(canvas, screenWidth / 2, screenHeight / 2, true,
425 "You Win!", 100);
426 break;
427 case LOSE:
428 drawMessage(canvas, screenWidth / 2, screenHeight / 2, true,
429 "Game Over", 100);
430 break;
431 }
432
433 switch (pstate) {
434 case BOARD:
435 case SELECTED1:
436 case SELECTED2:
437 case MATCHED:
438 case WIN:
439 case LOSE:
440 case HINT:
441 case TIME:
442 updateTime();
443 int hours = (int) (playTime / (60 * 60));
444 int minutes = (int) ((playTime / 60) % 60);
445 int seconds = (int) (playTime % 60);
446 if (hours < 10) {
447 time = String.format(Locale.US, "%01d:%02d:%02d",
448 hours, minutes, seconds);
449 } else {
450 time = INVALID_TIME;
451 }
452
453 int timePosX=screenWidth-120;
454 int timePosY=screenHeight-10;
455
456 if (app.timeCounter) {
457 drawMessage(canvas, timePosX, timePosY, false, time, 30);
458 }
459 break;
460 }
461
462 } catch (Exception e) {
463 e.printStackTrace();
464 }
465
466 }
467
468 private void drawLine(Canvas canvas, int x0, int y0, Point p0, Point p1, int color) {
469 Paint paint = new Paint();
470 paint.setFlags(Paint.ANTI_ALIAS_FLAG);
471 paint.setColor(color);
472 paint.setStyle(Style.STROKE);
473 paint.setStrokeCap(Cap.ROUND);
474 paint.setStrokeJoin(Join.ROUND);
475 paint.setStrokeWidth(3);
476 canvas.drawLine(
477 x0 + p0.j * tileWidth - 2 + (tileWidth / 2),
478 y0 + p0.i * tileHeight - 2 + (tileHeight / 2),
479 x0 + p1.j * tileWidth - 2 + (tileWidth / 2),
480 y0 + p1.i * tileHeight - 2 + (tileHeight / 2), paint);
481 }
482
483 private void highlightTile(Canvas canvas, int x0, int y0, Point p, int color) {
484 Paint paint = new Paint();
485 paint.setFlags(Paint.ANTI_ALIAS_FLAG);
486 paint.setColor(color);
487 paint.setStyle(Style.STROKE);
488 paint.setStrokeCap(Cap.ROUND);
489 paint.setStrokeJoin(Join.ROUND);
490 paint.setStrokeWidth(3);
491 Rect r = new Rect(
492 x0 + p.j * tileWidth - 2,
493 y0 + p.i * tileHeight - 2,
494 x0 + p.j * tileWidth + tileWidth + 2,
495 y0 + p.i * tileHeight + tileHeight + 2);
496 canvas.drawRect(r, paint);
497 }
498
499 @Override
500 public boolean onTouchEvent(MotionEvent event) {
501 if (event.getAction()==MotionEvent.ACTION_DOWN) {
502 onClick(Math.round(event.getX()),Math.round(event.getY()));
503 }
504 return super.onTouchEvent(event);
505 }
506
507 private void onClick(int x, int y) {
508 try {
509 int i=(y-(screenHeight-app.board.boardSize[0]*tileHeight)/2)/tileHeight;
510 int j=(x-(screenWidth-app.board.boardSize[1]*tileWidth)/2)/tileWidth;
511
512 switch (cstate) {
513 case IDLE:
514 if (i >= 0 && i < app.board.boardSize[0] && j >= 0
515 && j < app.board.boardSize[1]
516 && app.board.board[i][j] != 0) {
517 selection1.set(i, j);
518 paint(StatePaint.SELECTED1);
519 control(StatePlay.SELECTED1);
520 }
521 break;
522 case SELECTED1:
523 if (i >= 0 && i < app.board.boardSize[0] && j >= 0
524 && j < app.board.boardSize[1]
525 && app.board.board[i][j] != 0) {
526 if (selection1.equals(i, j)) {
527 paint(StatePaint.BOARD);
528 control(StatePlay.IDLE);
529 } else {
530 selection2.set(i, j);
531 paint(StatePaint.SELECTED2);
532
533 Point a = selection1.copy();
534 Point b = selection2.copy();
535 path = app.board.getPath(a, b);
536 paint(StatePaint.MATCHED);
537 app.sleep(2);
538 paint(StatePaint.BOARD);
539 if (path.size() > 0) {
540 app.board.play(a, b);
541 }
542 path = null;
543 paint(StatePaint.BOARD);
544
545 pairs = app.board.getPairs(1);
546 if (pairs.size() == 0) {
547 if (app.board.getNumPieces() == 0) {
548 paint(StatePaint.WIN);
549 checkforhiscore();
550 } else {
551 paint(StatePaint.LOSE);
552 }
553 control(StatePlay.GAMEOVER);
554 } else {
555 control(StatePlay.IDLE);
556 }
557 //undo.sensitive=app.board.getCanUndo();
558 }
559 }
560 break;
561 case GAMEOVER:
562 reset();
563 paint(StatePaint.BOARD);
564 break;
565 }
566 } catch (Exception e) {
567 e.printStackTrace();
568 }
569 }
570
571 private void checkforhiscore() {
572 if (timerRegistered) {
573 unregisterTimer();
574 }
575 final String[] sizes = { "S", "M", "L" };
576 final String[] diffs = { "E", "H" };
577 String prefname1 = "hiscore_" + diffs[app.difficulty-1] + sizes[app.size-1] + "1";
578 String prefname2 = "hiscore_" + diffs[app.difficulty-1] + sizes[app.size-1] + "2";
579 // get hiscores for current size/difficulty
580 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(app);
581 String besttime1 = sp.getString(prefname1, INVALID_TIME);
582 String besttime2 = sp.getString(prefname2, INVALID_TIME);
583 // did we win something?
584 if (time.compareTo(besttime2) < 0) {
585 // score!
586 new AlertDialog.Builder(app.activity)
587 .setTitle("Hiscore!")
588 .setCancelable(true)
589 .setIcon(R.drawable.icon)
590 .setPositiveButton(app.getString(android.R.string.ok), null)
591 .setMessage("You've made the highscore list!").create() // FIXME: hardcoded string
592 .show();
593
594 SharedPreferences.Editor editor = sp.edit();
595 if (time.compareTo(besttime1) < 0) {
596 editor.putString(prefname1, time);
597 editor.putString(prefname2, besttime1);
598 } else {
599 editor.putString(prefname2, time);
600 }
601 editor.commit();
602 }
603 }
604
605 public void surfaceChanged(SurfaceHolder holder, int format, int width,
606 int height) {
607 surfaceHolder = holder;
608 if (cstate!=StatePlay.GAMEOVER && !timerRegistered) {
609 registerTimer();
610 }
611 repaint();
612 }
613
614 public void surfaceCreated(SurfaceHolder holder) {
615 surfaceHolder = holder;
616 repaint();
617 }
618
619 public void surfaceDestroyed(SurfaceHolder holder) {
620 surfaceHolder = null;
621 if (timerRegistered) {
622 unregisterTimer();
623 }
624 }
625 }
Impressum, Datenschutz