• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1  /*
2   * Copyright (C) 2010 The Android Open Source Project
3   *
4   * Licensed under the Apache License, Version 2.0 (the "License");
5   * you may not use this file except in compliance with the License.
6   * You may obtain a copy of the License at
7   *
8   *      http://www.apache.org/licenses/LICENSE-2.0
9   *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.example.android.tictactoe.library;
18  
19  import java.util.Random;
20  
21  import android.content.Context;
22  import android.content.res.Resources;
23  import android.graphics.Bitmap;
24  import android.graphics.BitmapFactory;
25  import android.graphics.Canvas;
26  import android.graphics.Paint;
27  import android.graphics.Rect;
28  import android.graphics.Bitmap.Config;
29  import android.graphics.BitmapFactory.Options;
30  import android.graphics.Paint.Style;
31  import android.graphics.drawable.Drawable;
32  import android.os.Bundle;
33  import android.os.Handler;
34  import android.os.Message;
35  import android.os.Parcelable;
36  import android.os.Handler.Callback;
37  import android.util.AttributeSet;
38  import android.view.MotionEvent;
39  import android.view.View;
40  
41  //-----------------------------------------------
42  
43  public class GameView extends View {
44  
45      public static final long FPS_MS = 1000/2;
46  
47      public enum State {
48          UNKNOWN(-3),
49          WIN(-2),
50          EMPTY(0),
51          PLAYER1(1),
52          PLAYER2(2);
53  
54          private int mValue;
55  
State(int value)56          private State(int value) {
57              mValue = value;
58          }
59  
getValue()60          public int getValue() {
61              return mValue;
62          }
63  
fromInt(int i)64          public static State fromInt(int i) {
65              for (State s : values()) {
66                  if (s.getValue() == i) {
67                      return s;
68                  }
69              }
70              return EMPTY;
71          }
72      }
73  
74      private static final int MARGIN = 4;
75      private static final int MSG_BLINK = 1;
76  
77      private final Handler mHandler = new Handler(new MyHandler());
78  
79      private final Rect mSrcRect = new Rect();
80      private final Rect mDstRect = new Rect();
81  
82      private int mSxy;
83      private int mOffetX;
84      private int mOffetY;
85      private Paint mWinPaint;
86      private Paint mLinePaint;
87      private Paint mBmpPaint;
88      private Bitmap mBmpPlayer1;
89      private Bitmap mBmpPlayer2;
90      private Drawable mDrawableBg;
91  
92      private ICellListener mCellListener;
93  
94      /** Contains one of {@link State#EMPTY}, {@link State#PLAYER1} or {@link State#PLAYER2}. */
95      private final State[] mData = new State[9];
96  
97      private int mSelectedCell = -1;
98      private State mSelectedValue = State.EMPTY;
99      private State mCurrentPlayer = State.UNKNOWN;
100      private State mWinner = State.EMPTY;
101  
102      private int mWinCol = -1;
103      private int mWinRow = -1;
104      private int mWinDiag = -1;
105  
106      private boolean mBlinkDisplayOff;
107      private final Rect mBlinkRect = new Rect();
108  
109  
110  
111      public interface ICellListener {
onCellSelected()112          abstract void onCellSelected();
113      }
114  
GameView(Context context, AttributeSet attrs)115      public GameView(Context context, AttributeSet attrs) {
116          super(context, attrs);
117          requestFocus();
118  
119          mDrawableBg = getResources().getDrawable(R.drawable.lib_bg);
120          setBackgroundDrawable(mDrawableBg);
121  
122          mBmpPlayer1 = getResBitmap(R.drawable.lib_cross);
123          mBmpPlayer2 = getResBitmap(R.drawable.lib_circle);
124  
125          if (mBmpPlayer1 != null) {
126              mSrcRect.set(0, 0, mBmpPlayer1.getWidth() -1, mBmpPlayer1.getHeight() - 1);
127          }
128  
129          mBmpPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
130  
131          mLinePaint = new Paint();
132          mLinePaint.setColor(0xFFFFFFFF);
133          mLinePaint.setStrokeWidth(5);
134          mLinePaint.setStyle(Style.STROKE);
135  
136          mWinPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
137          mWinPaint.setColor(0xFFFF0000);
138          mWinPaint.setStrokeWidth(10);
139          mWinPaint.setStyle(Style.STROKE);
140  
141          for (int i = 0; i < mData.length; i++) {
142              mData[i] = State.EMPTY;
143          }
144  
145          if (isInEditMode()) {
146              // In edit mode (e.g. in the Eclipse ADT graphical layout editor)
147              // we'll use some random data to display the state.
148              Random rnd = new Random();
149              for (int i = 0; i < mData.length; i++) {
150                  mData[i] = State.fromInt(rnd.nextInt(3));
151              }
152          }
153      }
154  
getData()155      public State[] getData() {
156          return mData;
157      }
158  
setCell(int cellIndex, State value)159      public void setCell(int cellIndex, State value) {
160          mData[cellIndex] = value;
161          invalidate();
162      }
163  
setCellListener(ICellListener cellListener)164      public void setCellListener(ICellListener cellListener) {
165          mCellListener = cellListener;
166      }
167  
getSelection()168      public int getSelection() {
169          if (mSelectedValue == mCurrentPlayer) {
170              return mSelectedCell;
171          }
172  
173          return -1;
174      }
175  
getCurrentPlayer()176      public State getCurrentPlayer() {
177          return mCurrentPlayer;
178      }
179  
setCurrentPlayer(State player)180      public void setCurrentPlayer(State player) {
181          mCurrentPlayer = player;
182          mSelectedCell = -1;
183      }
184  
getWinner()185      public State getWinner() {
186          return mWinner;
187      }
188  
setWinner(State winner)189      public void setWinner(State winner) {
190          mWinner = winner;
191      }
192  
193      /** Sets winning mark on specified column or row (0..2) or diagonal (0..1). */
setFinished(int col, int row, int diagonal)194      public void setFinished(int col, int row, int diagonal) {
195          mWinCol = col;
196          mWinRow = row;
197          mWinDiag = diagonal;
198      }
199  
200      //-----------------------------------------
201  
202  
203      @Override
onDraw(Canvas canvas)204      protected void onDraw(Canvas canvas) {
205          super.onDraw(canvas);
206  
207          int sxy = mSxy;
208          int s3  = sxy * 3;
209          int x7 = mOffetX;
210          int y7 = mOffetY;
211  
212          for (int i = 0, k = sxy; i < 2; i++, k += sxy) {
213              canvas.drawLine(x7    , y7 + k, x7 + s3 - 1, y7 + k     , mLinePaint);
214              canvas.drawLine(x7 + k, y7    , x7 + k     , y7 + s3 - 1, mLinePaint);
215          }
216  
217          for (int j = 0, k = 0, y = y7; j < 3; j++, y += sxy) {
218              for (int i = 0, x = x7; i < 3; i++, k++, x += sxy) {
219                  mDstRect.offsetTo(MARGIN+x, MARGIN+y);
220  
221                  State v;
222                  if (mSelectedCell == k) {
223                      if (mBlinkDisplayOff) {
224                          continue;
225                      }
226                      v = mSelectedValue;
227                  } else {
228                      v = mData[k];
229                  }
230  
231                  switch(v) {
232                  case PLAYER1:
233                      if (mBmpPlayer1 != null) {
234                          canvas.drawBitmap(mBmpPlayer1, mSrcRect, mDstRect, mBmpPaint);
235                      }
236                      break;
237                  case PLAYER2:
238                      if (mBmpPlayer2 != null) {
239                          canvas.drawBitmap(mBmpPlayer2, mSrcRect, mDstRect, mBmpPaint);
240                      }
241                      break;
242                  }
243              }
244          }
245  
246          if (mWinRow >= 0) {
247              int y = y7 + mWinRow * sxy + sxy / 2;
248              canvas.drawLine(x7 + MARGIN, y, x7 + s3 - 1 - MARGIN, y, mWinPaint);
249  
250          } else if (mWinCol >= 0) {
251              int x = x7 + mWinCol * sxy + sxy / 2;
252              canvas.drawLine(x, y7 + MARGIN, x, y7 + s3 - 1 - MARGIN, mWinPaint);
253  
254          } else if (mWinDiag == 0) {
255              // diagonal 0 is from (0,0) to (2,2)
256  
257              canvas.drawLine(x7 + MARGIN, y7 + MARGIN,
258                      x7 + s3 - 1 - MARGIN, y7 + s3 - 1 - MARGIN, mWinPaint);
259  
260          } else if (mWinDiag == 1) {
261              // diagonal 1 is from (0,2) to (2,0)
262  
263              canvas.drawLine(x7 + MARGIN, y7 + s3 - 1 - MARGIN,
264                      x7 + s3 - 1 - MARGIN, y7 + MARGIN, mWinPaint);
265          }
266      }
267  
268      @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)269      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
270          // Keep the view squared
271          int w = MeasureSpec.getSize(widthMeasureSpec);
272          int h = MeasureSpec.getSize(heightMeasureSpec);
273          int d = w == 0 ? h : h == 0 ? w : w < h ? w : h;
274          setMeasuredDimension(d, d);
275      }
276  
277      @Override
278      protected void onSizeChanged(int w, int h, int oldw, int oldh) {
279          super.onSizeChanged(w, h, oldw, oldh);
280  
281          int sx = (w - 2 * MARGIN) / 3;
282          int sy = (h - 2 * MARGIN) / 3;
283  
284          int size = sx < sy ? sx : sy;
285  
286          mSxy = size;
287          mOffetX = (w - 3 * size) / 2;
288          mOffetY = (h - 3 * size) / 2;
289  
290          mDstRect.set(MARGIN, MARGIN, size - MARGIN, size - MARGIN);
291      }
292  
293      @Override
294      public boolean onTouchEvent(MotionEvent event) {
295          int action = event.getAction();
296  
297          if (action == MotionEvent.ACTION_DOWN) {
298              return true;
299  
300          } else if (action == MotionEvent.ACTION_UP) {
301              int x = (int) event.getX();
302              int y = (int) event.getY();
303  
304              int sxy = mSxy;
305              x = (x - MARGIN) / sxy;
306              y = (y - MARGIN) / sxy;
307  
308              if (isEnabled() && x >= 0 && x < 3 && y >= 0 & y < 3) {
309                  int cell = x + 3 * y;
310  
311                  State state = cell == mSelectedCell ? mSelectedValue : mData[cell];
312                  state = state == State.EMPTY ? mCurrentPlayer : State.EMPTY;
313  
314                  stopBlink();
315  
316                  mSelectedCell = cell;
317                  mSelectedValue = state;
318                  mBlinkDisplayOff = false;
319                  mBlinkRect.set(MARGIN + x * sxy, MARGIN + y * sxy,
320                                 MARGIN + (x + 1) * sxy, MARGIN + (y + 1) * sxy);
321  
322                  if (state != State.EMPTY) {
323                      // Start the blinker
324                      mHandler.sendEmptyMessageDelayed(MSG_BLINK, FPS_MS);
325                  }
326  
327                  if (mCellListener != null) {
328                      mCellListener.onCellSelected();
329                  }
330              }
331  
332              return true;
333          }
334  
335          return false;
336      }
337  
338      public void stopBlink() {
339          boolean hadSelection = mSelectedCell != -1 && mSelectedValue != State.EMPTY;
340          mSelectedCell = -1;
341          mSelectedValue = State.EMPTY;
342          if (!mBlinkRect.isEmpty()) {
343              invalidate(mBlinkRect);
344          }
345          mBlinkDisplayOff = false;
346          mBlinkRect.setEmpty();
347          mHandler.removeMessages(MSG_BLINK);
348          if (hadSelection && mCellListener != null) {
349              mCellListener.onCellSelected();
350          }
351      }
352  
353      @Override
354      protected Parcelable onSaveInstanceState() {
355          Bundle b = new Bundle();
356  
357          Parcelable s = super.onSaveInstanceState();
358          b.putParcelable("gv_super_state", s);
359  
360          b.putBoolean("gv_en", isEnabled());
361  
362          int[] data = new int[mData.length];
363          for (int i = 0; i < data.length; i++) {
364              data[i] = mData[i].getValue();
365          }
366          b.putIntArray("gv_data", data);
367  
368          b.putInt("gv_sel_cell", mSelectedCell);
369          b.putInt("gv_sel_val",  mSelectedValue.getValue());
370          b.putInt("gv_curr_play", mCurrentPlayer.getValue());
371          b.putInt("gv_winner", mWinner.getValue());
372  
373          b.putInt("gv_win_col", mWinCol);
374          b.putInt("gv_win_row", mWinRow);
375          b.putInt("gv_win_diag", mWinDiag);
376  
377          b.putBoolean("gv_blink_off", mBlinkDisplayOff);
378          b.putParcelable("gv_blink_rect", mBlinkRect);
379  
380          return b;
381      }
382  
383      @Override
384      protected void onRestoreInstanceState(Parcelable state) {
385  
386          if (!(state instanceof Bundle)) {
387              // Not supposed to happen.
388              super.onRestoreInstanceState(state);
389              return;
390          }
391  
392          Bundle b = (Bundle) state;
393          Parcelable superState = b.getParcelable("gv_super_state");
394  
395          setEnabled(b.getBoolean("gv_en", true));
396  
397          int[] data = b.getIntArray("gv_data");
398          if (data != null && data.length == mData.length) {
399              for (int i = 0; i < data.length; i++) {
400                  mData[i] = State.fromInt(data[i]);
401              }
402          }
403  
404          mSelectedCell = b.getInt("gv_sel_cell", -1);
405          mSelectedValue = State.fromInt(b.getInt("gv_sel_val", State.EMPTY.getValue()));
406          mCurrentPlayer = State.fromInt(b.getInt("gv_curr_play", State.EMPTY.getValue()));
407          mWinner = State.fromInt(b.getInt("gv_winner", State.EMPTY.getValue()));
408  
409          mWinCol = b.getInt("gv_win_col", -1);
410          mWinRow = b.getInt("gv_win_row", -1);
411          mWinDiag = b.getInt("gv_win_diag", -1);
412  
413          mBlinkDisplayOff = b.getBoolean("gv_blink_off", false);
414          Rect r = b.getParcelable("gv_blink_rect");
415          if (r != null) {
416              mBlinkRect.set(r);
417          }
418  
419          // let the blink handler decide if it should blink or not
420          mHandler.sendEmptyMessage(MSG_BLINK);
421  
422          super.onRestoreInstanceState(superState);
423      }
424  
425      //-----
426  
427      private class MyHandler implements Callback {
428          public boolean handleMessage(Message msg) {
429              if (msg.what == MSG_BLINK) {
430                  if (mSelectedCell >= 0 && mSelectedValue != State.EMPTY && mBlinkRect.top != 0) {
431                      mBlinkDisplayOff = !mBlinkDisplayOff;
432                      invalidate(mBlinkRect);
433  
434                      if (!mHandler.hasMessages(MSG_BLINK)) {
435                          mHandler.sendEmptyMessageDelayed(MSG_BLINK, FPS_MS);
436                      }
437                  }
438                  return true;
439              }
440              return false;
441          }
442      }
443  
444      private Bitmap getResBitmap(int bmpResId) {
445          Options opts = new Options();
446          opts.inDither = false;
447  
448          Resources res = getResources();
449          Bitmap bmp = BitmapFactory.decodeResource(res, bmpResId, opts);
450  
451          if (bmp == null && isInEditMode()) {
452              // BitmapFactory.decodeResource doesn't work from the rendering
453              // library in Eclipse's Graphical Layout Editor. Use this workaround instead.
454  
455              Drawable d = res.getDrawable(bmpResId);
456              int w = d.getIntrinsicWidth();
457              int h = d.getIntrinsicHeight();
458              bmp = Bitmap.createBitmap(w, h, Config.ARGB_8888);
459              Canvas c = new Canvas(bmp);
460              d.setBounds(0, 0, w - 1, h - 1);
461              d.draw(c);
462          }
463  
464          return bmp;
465      }
466  }
467  
468  
469