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