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