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