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