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