summaryrefslogtreecommitdiffstats
path: root/samples/TicTacToeLib
diff options
context:
space:
mode:
authorRaphael <raphael@google.com>2010-04-02 22:05:14 -0700
committerRaphael <raphael@google.com>2010-04-05 14:19:33 -0700
commitb8a884fbab7d8004211534ec6dd68d8960b41321 (patch)
treedb2c75155567857d0e3ef21fe74e3ddd4b28cf2d /samples/TicTacToeLib
parent718a35143dae79196aa46467eada542cfe49c382 (diff)
downloadandroid_development-b8a884fbab7d8004211534ec6dd68d8960b41321.tar.gz
android_development-b8a884fbab7d8004211534ec6dd68d8960b41321.tar.bz2
android_development-b8a884fbab7d8004211534ec6dd68d8960b41321.zip
Implementation of the tic-tac-toe sample.
Added some README.txt Change-Id: Ie2703ffdfdeba773d0fd27153ec0cdb806b2e2dc
Diffstat (limited to 'samples/TicTacToeLib')
-rwxr-xr-xsamples/TicTacToeLib/AndroidManifest.xml3
-rwxr-xr-xsamples/TicTacToeLib/README.txt7
-rwxr-xr-xsamples/TicTacToeLib/default.properties6
-rwxr-xr-xsamples/TicTacToeLib/res/layout-land/lib_game.xml60
-rwxr-xr-xsamples/TicTacToeLib/res/layout/lib_game.xml12
-rwxr-xr-xsamples/TicTacToeLib/res/values/strings.xml3
-rwxr-xr-xsamples/TicTacToeLib/src/com/example/tictactoe/library/GameActivity.java232
-rwxr-xr-xsamples/TicTacToeLib/src/com/example/tictactoe/library/GameView.java376
8 files changed, 644 insertions, 55 deletions
diff --git a/samples/TicTacToeLib/AndroidManifest.xml b/samples/TicTacToeLib/AndroidManifest.xml
index cc667ee2e..9772b88c5 100755
--- a/samples/TicTacToeLib/AndroidManifest.xml
+++ b/samples/TicTacToeLib/AndroidManifest.xml
@@ -19,7 +19,4 @@
package="com.example.tictactoe.library"
android:versionCode="1"
android:versionName="1.0">
- <application>
- <activity android:name="GameActivity" />
- </application>
</manifest> \ No newline at end of file
diff --git a/samples/TicTacToeLib/README.txt b/samples/TicTacToeLib/README.txt
new file mode 100755
index 000000000..a43b2f156
--- /dev/null
+++ b/samples/TicTacToeLib/README.txt
@@ -0,0 +1,7 @@
+Sample: TicTacToeLib and TicTacToeMain.
+
+These two projects work together. They demonstrate how to use the ability to
+split an APK into multiple projects.
+
+Please see the README.txt file in ../TicTacToeMain for more details.
+
diff --git a/samples/TicTacToeLib/default.properties b/samples/TicTacToeLib/default.properties
index 4ac577ca4..89927d72e 100755
--- a/samples/TicTacToeLib/default.properties
+++ b/samples/TicTacToeLib/default.properties
@@ -16,13 +16,13 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
+#
# This file must be checked in Version Control Systems.
-#
+#
# To customize properties used by the Ant build system use,
# "build.properties", and override values to adapt the script to your
# project structure.
android.library=true
# Project target.
-target=android-7
+target=android-3
diff --git a/samples/TicTacToeLib/res/layout-land/lib_game.xml b/samples/TicTacToeLib/res/layout-land/lib_game.xml
new file mode 100755
index 000000000..c0f5cbab2
--- /dev/null
+++ b/samples/TicTacToeLib/res/layout-land/lib_game.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:gravity="center_vertical|center_horizontal"
+ >
+
+ <com.example.tictactoe.library.GameView
+ android:id="@+id/game_view"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_margin="20dip"
+ />
+
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ >
+
+ <TextView
+ android:id="@+id/info_turn"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:layout_marginBottom="10dip"
+ />
+
+ <Button
+ android:id="@+id/next_turn"
+ android:text="I'm done"
+ android:minEms="10"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="20dip"
+ android:layout_marginRight="20dip"
+ />
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/samples/TicTacToeLib/res/layout/lib_game.xml b/samples/TicTacToeLib/res/layout/lib_game.xml
index c4e674de9..75c284a68 100755
--- a/samples/TicTacToeLib/res/layout/lib_game.xml
+++ b/samples/TicTacToeLib/res/layout/lib_game.xml
@@ -19,13 +19,13 @@
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
- android:weightSum="2"
+ android:gravity="center_horizontal"
>
<com.example.tictactoe.library.GameView
android:id="@+id/game_view"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_margin="20dip"
android:layout_weight="1"
/>
@@ -35,16 +35,16 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
- android:layout_weight="1"
+ android:layout_marginBottom="10dip"
/>
<Button
- android:id="@+id/info_turn"
+ android:id="@+id/next_turn"
+ android:text="I'm done"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dip"
android:layout_marginRight="20dip"
- android:layout_weight="1"
/>
</LinearLayout>
diff --git a/samples/TicTacToeLib/res/values/strings.xml b/samples/TicTacToeLib/res/values/strings.xml
index 5707400cb..9fd841358 100755
--- a/samples/TicTacToeLib/res/values/strings.xml
+++ b/samples/TicTacToeLib/res/values/strings.xml
@@ -16,6 +16,5 @@
-->
<resources>
-
-
+ <!-- TODO externalize strings here. -->
</resources>
diff --git a/samples/TicTacToeLib/src/com/example/tictactoe/library/GameActivity.java b/samples/TicTacToeLib/src/com/example/tictactoe/library/GameActivity.java
index f75665e6c..e1514cdc2 100755
--- a/samples/TicTacToeLib/src/com/example/tictactoe/library/GameActivity.java
+++ b/samples/TicTacToeLib/src/com/example/tictactoe/library/GameActivity.java
@@ -16,15 +16,36 @@
package com.example.tictactoe.library;
+import java.util.Random;
+
import android.app.Activity;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Handler.Callback;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.example.tictactoe.library.GameView.ICellListener;
+import com.example.tictactoe.library.GameView.State;
+
public class GameActivity extends Activity {
- public final static String EXTRA_START_WITH_HUMAN =
- "com.example.tictactoe.library.GameActivity.EXTRA_START_WITH_HUMAN";
+ /** Start player. Must be 1 or 2. Default is 1. */
+ public static final String EXTRA_START_PLAYER =
+ "com.example.tictactoe.library.GameActivity.EXTRA_START_PLAYER";
- private boolean mTurnIsHuman;
+ private static final int MSG_COMPUTER_TURN = 1;
+ private static final long COMPUTER_DELAY_MS = 500;
+
+ private Handler mHandler = new Handler(new MyHandlerCallback());
+ private Random mRnd = new Random();
+ private GameView mGameView;
+ private TextView mInfoView;
+ private Button mButtonNext;
/** Called when the activity is first created. */
@Override
@@ -32,22 +53,207 @@ public class GameActivity extends Activity {
super.onCreate(bundle);
/*
- * IMPORTANT TIP: all resource IDs from this library must be
- * different from the projects that will include it. E.g.
- * if my layout were named "main.xml", I would have to use the ID
- * R.layout.main; however there is already a *different*
- * ID with the same name in the main project and when the library
- * gets merged in the project the wrong ID would end up being used.
+ * IMPORTANT: all resource IDs from this library will eventually be merged
+ * with the resources from the main project that will use the library.
+ *
+ * If the main project and the libraries define the same resource IDs,
+ * the application project will always have priority and override library resources
+ * and IDs defined in multiple libraries are resolved based on the libraries priority
+ * defined in the main project.
+ *
+ * An intentional consequence is that the main project can override some resources
+ * from the library.
+ * (TODO insert example).
*
- * To avoid such potential conflicts, it's probably a good idea
- * to add a prefix to the library resource names.
+ * To avoid potential conflicts, it is suggested to add a prefix to the
+ * library resource names.
*/
setContentView(R.layout.lib_game);
- mTurnIsHuman = getIntent().getBooleanExtra(
- EXTRA_START_WITH_HUMAN, true);
+ mGameView = (GameView) findViewById(R.id.game_view);
+ mInfoView = (TextView) findViewById(R.id.info_turn);
+ mButtonNext = (Button) findViewById(R.id.next_turn);
+
+ mGameView.setFocusable(true);
+ mGameView.setFocusableInTouchMode(true);
+ mGameView.setCellListener(new MyCellListener());
+
+ mButtonNext.setOnClickListener(new MyButtonListener());
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ State player = mGameView.getCurrentPlayer();
+ if (player == State.UNKNOWN) {
+ player = State.fromInt(getIntent().getIntExtra(EXTRA_START_PLAYER, 1));
+ if (!checkGameFinished(player)) {
+ selectTurn(player);
+ }
+ }
+ if (player == State.PLAYER2) {
+ mHandler.sendEmptyMessageDelayed(MSG_COMPUTER_TURN, COMPUTER_DELAY_MS);
+ }
+ if (player == State.WIN) {
+ setWinState(mGameView.getWinner());
+ }
+ }
+
+
+ private State selectTurn(State player) {
+ mGameView.setCurrentPlayer(player);
+ mButtonNext.setEnabled(false);
+
+ if (player == State.PLAYER1) {
+ mInfoView.setText("Player 1's turn -- that's you!");
+ mGameView.setEnabled(true);
+
+ } else if (player == State.PLAYER2) {
+ mInfoView.setText("Player 2's turn (that's the computer)");
+ mGameView.setEnabled(false);
+ }
+
+ return player;
+ }
+
+ private class MyCellListener implements ICellListener {
+ public void onCellSelected() {
+ if (mGameView.getCurrentPlayer() == State.PLAYER1) {
+ int cell = mGameView.getSelection();
+ mButtonNext.setEnabled(cell >= 0);
+ }
+ }
+ }
+
+ private class MyButtonListener implements OnClickListener {
+
+ public void onClick(View v) {
+ State player = mGameView.getCurrentPlayer();
+ if (player == State.WIN) {
+ GameActivity.this.finish();
+ } else if (player == State.PLAYER1) {
+ int cell = mGameView.getSelection();
+ if (cell >= 0) {
+ mGameView.stopBlink();
+ mGameView.setCell(cell, player);
+ finishTurn();
+ }
+ }
+ }
}
+ private class MyHandlerCallback implements Callback {
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MSG_COMPUTER_TURN) {
+
+ // Pick a non-used cell at random. That's about all the AI you need for this game.
+ State[] data = mGameView.getData();
+ int used = 0;
+ while (used != 0x1F) {
+ int index = mRnd.nextInt(9);
+ if (((used >> index) & 1) == 0) {
+ used |= 1 << index;
+ if (data[index] == State.EMPTY) {
+ mGameView.setCell(index, mGameView.getCurrentPlayer());
+ break;
+ }
+ }
+ }
+
+ finishTurn();
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private State getOtherPlayer(State player) {
+ return player == State.PLAYER1 ? State.PLAYER2 : State.PLAYER1;
+ }
+
+ private void finishTurn() {
+ State player = mGameView.getCurrentPlayer();
+ if (!checkGameFinished(player)) {
+ player = selectTurn(getOtherPlayer(player));
+ if (player == State.PLAYER2) {
+ mHandler.sendEmptyMessageDelayed(MSG_COMPUTER_TURN, COMPUTER_DELAY_MS);
+ }
+ }
+ }
+
+ public boolean checkGameFinished(State player) {
+ State[] data = mGameView.getData();
+ boolean full = true;
+
+ int col = -1;
+ int row = -1;
+ int diag = -1;
+
+ // check rows
+ for (int j = 0, k = 0; j < 3; j++, k += 3) {
+ if (data[k] != State.EMPTY && data[k] == data[k+1] && data[k] == data[k+2]) {
+ row = j;
+ }
+ if (full && (data[k] == State.EMPTY ||
+ data[k+1] == State.EMPTY ||
+ data[k+2] == State.EMPTY)) {
+ full = false;
+ }
+ }
+
+ // check columns
+ for (int i = 0; i < 3; i++) {
+ if (data[i] != State.EMPTY && data[i] == data[i+3] && data[i] == data[i+6]) {
+ col = i;
+ }
+ }
+
+ // check diagonals
+ if (data[0] != State.EMPTY && data[0] == data[1+3] && data[0] == data[2+6]) {
+ diag = 0;
+ } else if (data[2] != State.EMPTY && data[2] == data[1+3] && data[2] == data[0+6]) {
+ diag = 1;
+ }
+
+ if (col != -1 || row != -1 || diag != -1) {
+ setFinished(player, col, row, diag);
+ return true;
+ }
+
+ // if we get here, there's no winner but the board is full.
+ if (full) {
+ setFinished(State.EMPTY, -1, -1, -1);
+ return true;
+ }
+ return false;
+ }
+
+ private void setFinished(State player, int col, int row, int diagonal) {
+
+ mGameView.setCurrentPlayer(State.WIN);
+ mGameView.setWinner(player);
+ mGameView.setEnabled(false);
+ mGameView.setFinished(col, row, diagonal);
+
+ setWinState(player);
+ }
+
+ private void setWinState(State player) {
+ mButtonNext.setEnabled(true);
+ mButtonNext.setText("Back");
+
+ String text;
+
+ if (player == State.EMPTY) {
+ text = "This is a tie! No one wins!";
+ } else if (player == State.PLAYER1) {
+ text = "Player 1 (you) wins!";
+ } else {
+ text = "Player 2 (computer) wins!";
+ }
+ mInfoView.setText(text);
+ }
}
diff --git a/samples/TicTacToeLib/src/com/example/tictactoe/library/GameView.java b/samples/TicTacToeLib/src/com/example/tictactoe/library/GameView.java
index 2c27b503e..89f858444 100755
--- a/samples/TicTacToeLib/src/com/example/tictactoe/library/GameView.java
+++ b/samples/TicTacToeLib/src/com/example/tictactoe/library/GameView.java
@@ -16,7 +16,7 @@
package com.example.tictactoe.library;
-import java.lang.reflect.Field;
+import java.util.Random;
import android.content.Context;
import android.content.res.Resources;
@@ -25,10 +25,14 @@ import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
+import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory.Options;
+import android.graphics.Paint.Style;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.Parcelable;
import android.os.Handler.Callback;
import android.util.AttributeSet;
import android.util.Log;
@@ -39,41 +43,166 @@ import android.view.View;
public class GameView extends View {
- private final static String TAG = "GameView";
-
- private static final int MSG_FRAME = 1;
+ private static final String TAG = "GameView";
public static final long FPS_MS = 1000/2;
- public static final int EMPTY = 0;
- public static final int CROSS = 1;
- public static final int CIRCLE = 2;
+ public enum State {
+ UNKNOWN(-3),
+ WIN(-2),
+ EMPTY(0),
+ PLAYER1(1),
+ PLAYER2(2);
+
+ private int mValue;
+
+ private State(int value) {
+ mValue = value;
+ }
+
+ public int getValue() {
+ return mValue;
+ }
+
+ public static State fromInt(int i) {
+ for (State s : values()) {
+ if (s.getValue() == i) {
+ return s;
+ }
+ }
+ return EMPTY;
+ }
+ }
+ private static final int MARGIN = 4;
+ private static final int MSG_BLINK = 1;
- /** Contains one of {@link #EMPTY}, {@link #CROSS} or {@link #CIRCLE} */
- private final int[] mData = new int[9];
+ private final Handler mHandler = new Handler(new MyHandler());
- private final Rect mBgRect = new Rect();
- private final Rect mTempDst = new Rect();
+ private final Rect mSrcRect = new Rect();
+ private final Rect mDstRect = new Rect();
private int mSxy;
private int mOffetX;
private int mOffetY;
+ private Paint mWinPaint;
private Paint mLinePaint;
+ private Paint mBmpPaint;
+ private Bitmap mBmpPlayer1;
+ private Bitmap mBmpPlayer2;
private Drawable mDrawableBg;
+ private ICellListener mCellListener;
+
+ /** Contains one of {@link State#EMPTY}, {@link State#PLAYER1} or {@link State#PLAYER2}. */
+ private final State[] mData = new State[9];
+
+ private int mSelectedCell = -1;
+ private State mSelectedValue = State.EMPTY;
+ private State mCurrentPlayer = State.UNKNOWN;
+ private State mWinner = State.EMPTY;
+
+ private int mWinCol = -1;
+ private int mWinRow = -1;
+ private int mWinDiag = -1;
+
+ private boolean mBlinkDisplayOff;
+ private final Rect mBlinkRect = new Rect();
+
+
+
+ public interface ICellListener {
+ abstract void onCellSelected();
+ }
public GameView(Context context, AttributeSet attrs) {
super(context, attrs);
requestFocus();
mDrawableBg = getResources().getDrawable(R.drawable.lib_bg);
+ setBackgroundDrawable(mDrawableBg);
+
+ mBmpPlayer1 = getResBitmap(R.drawable.lib_cross);
+ mBmpPlayer2 = getResBitmap(R.drawable.lib_circle);
+
+ if (mBmpPlayer1 != null) {
+ mSrcRect.set(0, 0, mBmpPlayer1.getWidth() -1, mBmpPlayer1.getHeight() - 1);
+ }
+
+ mBmpPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLinePaint = new Paint();
mLinePaint.setColor(0xFFFFFFFF);
mLinePaint.setStrokeWidth(5);
+ mLinePaint.setStyle(Style.STROKE);
+
+ mWinPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mWinPaint.setColor(0xFFFF0000);
+ mWinPaint.setStrokeWidth(10);
+ mWinPaint.setStyle(Style.STROKE);
+
+ for (int i = 0; i < mData.length; i++) {
+ mData[i] = State.EMPTY;
+ }
+
+ if (isInEditMode()) {
+ // In edit mode (e.g. in the Eclipse ADT graphical layout editor)
+ // we'll use some random data to display the state.
+ Random rnd = new Random();
+ for (int i = 0; i < mData.length; i++) {
+ mData[i] = State.fromInt(rnd.nextInt(3));
+ }
+ }
+ }
+
+ public State[] getData() {
+ return mData;
+ }
+
+ public void setCell(int cellIndex, State value) {
+ mData[cellIndex] = value;
+ invalidate();
+ }
+
+ public void setCellListener(ICellListener cellListener) {
+ mCellListener = cellListener;
}
+ public int getSelection() {
+ if (mSelectedValue == mCurrentPlayer) {
+ return mSelectedCell;
+ }
+
+ return -1;
+ }
+
+ public State getCurrentPlayer() {
+ return mCurrentPlayer;
+ }
+
+ public void setCurrentPlayer(State player) {
+ mCurrentPlayer = player;
+ mSelectedCell = -1;
+ }
+
+ public State getWinner() {
+ return mWinner;
+ }
+
+ public void setWinner(State winner) {
+ mWinner = winner;
+ }
+
+ /** Sets winning mark on specified column or row (0..2) or diagonal (0..1). */
+ public void setFinished(int col, int row, int diagonal) {
+ mWinCol = col;
+ mWinRow = row;
+ mWinDiag = diagonal;
+ }
+
+ //-----------------------------------------
+
+
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
@@ -83,24 +212,69 @@ public class GameView extends View {
int x7 = mOffetX;
int y7 = mOffetY;
- mDrawableBg.draw(canvas);
-
for (int i = 0, k = sxy; i < 2; i++, k += sxy) {
- canvas.drawLine(x7 , y7 + k, x7 + s3, y7 + k , mLinePaint);
- canvas.drawLine(x7 + k, y7 , x7 + k , y7 + s3, mLinePaint);
+ canvas.drawLine(x7 , y7 + k, x7 + s3 - 1, y7 + k , mLinePaint);
+ canvas.drawLine(x7 + k, y7 , x7 + k , y7 + s3 - 1, mLinePaint);
}
for (int j = 0, k = 0, y = y7; j < 3; j++, y += sxy) {
- for (int i = 0, x = x7; i < 3; i++, x += sxy) {
-
+ for (int i = 0, x = x7; i < 3; i++, k++, x += sxy) {
+ mDstRect.offsetTo(MARGIN+x, MARGIN+y);
+
+ State v;
+ if (mSelectedCell == k) {
+ if (mBlinkDisplayOff) {
+ continue;
+ }
+ v = mSelectedValue;
+ } else {
+ v = mData[k];
+ }
+
+ switch(v) {
+ case PLAYER1:
+ if (mBmpPlayer1 != null) {
+ canvas.drawBitmap(mBmpPlayer1, mSrcRect, mDstRect, mBmpPaint);
+ }
+ break;
+ case PLAYER2:
+ if (mBmpPlayer2 != null) {
+ canvas.drawBitmap(mBmpPlayer2, mSrcRect, mDstRect, mBmpPaint);
+ }
+ break;
+ }
}
}
+
+ if (mWinRow >= 0) {
+ int y = y7 + mWinRow * sxy + sxy / 2;
+ canvas.drawLine(x7 + MARGIN, y, x7 + s3 - 1 - MARGIN, y, mWinPaint);
+
+ } else if (mWinCol >= 0) {
+ int x = x7 + mWinCol * sxy + sxy / 2;
+ canvas.drawLine(x, y7 + MARGIN, x, y7 + s3 - 1 - MARGIN, mWinPaint);
+
+ } else if (mWinDiag == 0) {
+ // diagonal 0 is from (0,0) to (2,2)
+
+ canvas.drawLine(x7 + MARGIN, y7 + MARGIN,
+ x7 + s3 - 1 - MARGIN, y7 + s3 - 1 - MARGIN, mWinPaint);
+
+ } else if (mWinDiag == 1) {
+ // diagonal 1 is from (0,2) to (2,0)
+
+ canvas.drawLine(x7 + MARGIN, y7 + s3 - 1 - MARGIN,
+ x7 + s3 - 1 - MARGIN, y7 + MARGIN, mWinPaint);
+ }
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ // Keep the view squared
+ int w = MeasureSpec.getSize(widthMeasureSpec);
+ int h = MeasureSpec.getSize(heightMeasureSpec);
+ int d = w == 0 ? h : h == 0 ? w : w < h ? w : h;
+ setMeasuredDimension(d, d);
}
@Override
@@ -108,8 +282,8 @@ public class GameView extends View {
super.onSizeChanged(w, h, oldw, oldh);
Log.d(TAG, String.format("onSizeChanged: %dx%d", w, h));
- int sx = w / 3;
- int sy = h / 3;
+ int sx = (w - 2 * MARGIN) / 3;
+ int sy = (h - 2 * MARGIN) / 3;
int size = sx < sy ? sx : sy;
@@ -117,8 +291,7 @@ public class GameView extends View {
mOffetX = (w - 3 * size) / 2;
mOffetY = (h - 3 * size) / 2;
- mDrawableBg.setBounds(mOffetX, mOffetY,
- mOffetX + 3 * size, mOffetY + 3 * size);
+ mDstRect.set(MARGIN, MARGIN, size - MARGIN, size - MARGIN);
}
@Override
@@ -128,23 +301,170 @@ public class GameView extends View {
if (action == MotionEvent.ACTION_DOWN) {
return true;
- } else if (action == MotionEvent.ACTION_DOWN) {
- float x = event.getX();
- float y = event.getY();
+ } else if (action == MotionEvent.ACTION_UP) {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+
+ int sxy = mSxy;
+ x = (x - MARGIN) / sxy;
+ y = (y - MARGIN) / sxy;
+
+ if (isEnabled() && x >= 0 && x < 3 && y >= 0 & y < 3) {
+ int cell = x + 3 * y;
+
+ State state = cell == mSelectedCell ? mSelectedValue : mData[cell];
+ state = state == State.EMPTY ? mCurrentPlayer : State.EMPTY;
+
+ stopBlink();
+
+ mSelectedCell = cell;
+ mSelectedValue = state;
+ mBlinkDisplayOff = false;
+ mBlinkRect.set(MARGIN + x * sxy, MARGIN + y * sxy,
+ MARGIN + (x + 1) * sxy, MARGIN + (y + 1) * sxy);
+
+ if (state != State.EMPTY) {
+ // Start the blinker
+ mHandler.sendEmptyMessageDelayed(MSG_BLINK, FPS_MS);
+ }
+
+ if (mCellListener != null) {
+ mCellListener.onCellSelected();
+ }
+ }
- // TODO
return true;
}
return false;
}
- public Bitmap getResBitmap(int bmpResId) {
+ public void stopBlink() {
+ boolean hadSelection = mSelectedCell != -1 && mSelectedValue != State.EMPTY;
+ mSelectedCell = -1;
+ mSelectedValue = State.EMPTY;
+ if (!mBlinkRect.isEmpty()) {
+ invalidate(mBlinkRect);
+ }
+ mBlinkDisplayOff = false;
+ mBlinkRect.setEmpty();
+ mHandler.removeMessages(MSG_BLINK);
+ if (hadSelection && mCellListener != null) {
+ mCellListener.onCellSelected();
+ }
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Bundle b = new Bundle();
+
+ Parcelable s = super.onSaveInstanceState();
+ b.putParcelable("gv_super_state", s);
+
+ b.putBoolean("gv_en", isEnabled());
+
+ int[] data = new int[mData.length];
+ for (int i = 0; i < data.length; i++) {
+ data[i] = mData[i].getValue();
+ }
+ b.putIntArray("gv_data", data);
+
+ b.putInt("gv_sel_cell", mSelectedCell);
+ b.putInt("gv_sel_val", mSelectedValue.getValue());
+ b.putInt("gv_curr_play", mCurrentPlayer.getValue());
+ b.putInt("gv_winner", mWinner.getValue());
+
+ b.putInt("gv_win_col", mWinCol);
+ b.putInt("gv_win_row", mWinRow);
+ b.putInt("gv_win_diag", mWinDiag);
+
+ b.putBoolean("gv_blink_off", mBlinkDisplayOff);
+ b.putParcelable("gv_blink_rect", mBlinkRect);
+
+ return b;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+
+ if (!(state instanceof Bundle)) {
+ // Not supposed to happen.
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ Bundle b = (Bundle) state;
+ Parcelable superState = b.getParcelable("gv_super_state");
+
+ setEnabled(b.getBoolean("gv_en", true));
+
+ int[] data = b.getIntArray("gv_data");
+ if (data != null && data.length == mData.length) {
+ for (int i = 0; i < data.length; i++) {
+ mData[i] = State.fromInt(data[i]);
+ }
+ }
+
+ mSelectedCell = b.getInt("gv_sel_cell", -1);
+ mSelectedValue = State.fromInt(b.getInt("gv_sel_val", State.EMPTY.getValue()));
+ mCurrentPlayer = State.fromInt(b.getInt("gv_curr_play", State.EMPTY.getValue()));
+ mWinner = State.fromInt(b.getInt("gv_winner", State.EMPTY.getValue()));
+
+ mWinCol = b.getInt("gv_win_col", -1);
+ mWinRow = b.getInt("gv_win_row", -1);
+ mWinDiag = b.getInt("gv_win_diag", -1);
+
+ mBlinkDisplayOff = b.getBoolean("gv_blink_off", false);
+ Rect r = b.getParcelable("gv_blink_rect");
+ if (r != null) {
+ mBlinkRect.set(r);
+ }
+
+ // let the blink handler decide if it should blink or not
+ mHandler.sendEmptyMessage(MSG_BLINK);
+
+ super.onRestoreInstanceState(superState);
+ }
+
+ //-----
+
+ private class MyHandler implements Callback {
+ public boolean handleMessage(Message msg) {
+ if (msg.what == MSG_BLINK) {
+ if (mSelectedCell >= 0 && mSelectedValue != State.EMPTY && mBlinkRect.top != 0) {
+ mBlinkDisplayOff = !mBlinkDisplayOff;
+ invalidate(mBlinkRect);
+
+ if (!mHandler.hasMessages(MSG_BLINK)) {
+ mHandler.sendEmptyMessageDelayed(MSG_BLINK, FPS_MS);
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private Bitmap getResBitmap(int bmpResId) {
Options opts = new Options();
opts.inDither = false;
Resources res = getResources();
Bitmap bmp = BitmapFactory.decodeResource(res, bmpResId, opts);
+
+ if (bmp == null && isInEditMode()) {
+ // BitmapFactory.decodeResource doesn't work from the rendering
+ // library in Eclipse's Graphical Layout Editor. Use this workaround instead.
+
+ Drawable d = res.getDrawable(bmpResId);
+ int w = d.getIntrinsicWidth();
+ int h = d.getIntrinsicHeight();
+ bmp = Bitmap.createBitmap(w, h, Config.ARGB_8888);
+ Canvas c = new Canvas(bmp);
+ d.setBounds(0, 0, w - 1, h - 1);
+ d.draw(c);
+ }
+
return bmp;
}
}