Home | History | Annotate | Download | only in library
      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 
     56         private State(int value) {
     57             mValue = value;
     58         }
     59 
     60         public int getValue() {
     61             return mValue;
     62         }
     63 
     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 {
    112         abstract void onCellSelected();
    113     }
    114 
    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 
    155     public State[] getData() {
    156         return mData;
    157     }
    158 
    159     public void setCell(int cellIndex, State value) {
    160         mData[cellIndex] = value;
    161         invalidate();
    162     }
    163 
    164     public void setCellListener(ICellListener cellListener) {
    165         mCellListener = cellListener;
    166     }
    167 
    168     public int getSelection() {
    169         if (mSelectedValue == mCurrentPlayer) {
    170             return mSelectedCell;
    171         }
    172 
    173         return -1;
    174     }
    175 
    176     public State getCurrentPlayer() {
    177         return mCurrentPlayer;
    178     }
    179 
    180     public void setCurrentPlayer(State player) {
    181         mCurrentPlayer = player;
    182         mSelectedCell = -1;
    183     }
    184 
    185     public State getWinner() {
    186         return mWinner;
    187     }
    188 
    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). */
    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
    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
    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