This commit is contained in:
louiscklaw
2025-01-31 19:15:17 +08:00
parent 09adae8c8e
commit 6c60a73f30
1546 changed files with 286918 additions and 0 deletions

View File

@@ -0,0 +1,178 @@
package com.game.tictacteo;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import com.game.tictacteo.engine.Game;
import com.game.tictacteo.engine.GameBoard;
import com.game.tictacteo.engine.GameBoardPlacedException;
import com.game.tictacteo.engine.GameStatus;
import java.util.HashMap;
public class GameActivity extends AppCompatActivity implements View.OnClickListener {
private HashMap<Integer, LocateButton> gameBoardMap;
private Game game;
private ImageView imGameMes;
private final static byte USER_PLAYER = 1;
private final static byte AI_PLAYER = 2;
//private ArrayList<Integer> lockedButton;
private ImageView imDisPlayer;
private ImageView imDisAI;
private Button btnContinue;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
//this.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
imGameMes = (ImageView) findViewById(R.id.txtGameMes);
imDisPlayer = (ImageView) findViewById(R.id.imDisplayPlayer);
imDisAI = (ImageView) findViewById(R.id.imDisplayAI);
btnContinue = (Button) findViewById(R.id.btnContinue);
btnContinue.setVisibility(View.GONE);
// Close button
btnContinue.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
startActivity(getIntent());
}
});
// Button Map to reduce the runtime
gameBoardMap = new HashMap<Integer, LocateButton>();
for(int i = 0; i < 3; i++){
for (int j = 0; j < 3; j++){
String buttonID = "btn_"+i+"_"+j;
int resourceID = getResources().getIdentifier(buttonID, "id", getPackageName());
Button button = (Button) findViewById(resourceID);
button.setOnClickListener(this);
Log.i("gameAc-onCreate", "refesh");
gameBoardMap.put(resourceID, new LocateButton(button, new int[]{i, j}));
}
}
Log.i("gameAc", "start");
game = new Game(GameActivity.this);
//lockedButton = new ArrayList<Integer>();
}
// refesh the game board
public void refesh(byte[][] board){
Log.i("gameAc", "refesh");
for(int i = 0; i < board.length; i++){
for(int j = 0; j < board[i].length; j++){
String mapKey = "btn_"+i+"_"+j;
int resourceID = getResources().getIdentifier(mapKey, "id", getPackageName());
// Get button from hashmap
Button button = gameBoardMap.get(resourceID).button;
// set button image and disable clicked button
if(board[i][j] == USER_PLAYER){
button.setBackgroundResource(R.drawable.ic_o);
button.setClickable(false);
}else if (board[i][j] == AI_PLAYER){
button.setBackgroundResource(R.drawable.ic_x);
button.setClickable(false);
}else{
button.setBackgroundResource(R.drawable.game_board_pos);
button.setClickable(true);
}
}
}
}
// disable all button
public void lockAllButton(){
for(LocateButton lb: gameBoardMap.values())
lb.button.setClickable(false);
}
// public void unlockAllButton(){
// for(Integer key: gameBoardMap.keySet()){
// Button button = gameBoardMap.get(key).button;
// if(lockedButton.contains(key))
// button.setClickable(false);
// else
// button.setClickable(true);
//
// }
// }
@Override
public void onClick(View view) {
Log.i("gameAc", view.getId() +"");
if (gameBoardMap.containsKey(view.getId())) {
LocateButton lb = gameBoardMap.get(view.getId());
// Player place
lockAllButton();
// set clicked button UI setting
lb.button.setBackgroundResource(R.drawable.ic_o);
imGameMes.setImageResource(R.drawable.game_mes_2);
imDisPlayer.setBackgroundResource(0);
imDisAI.setBackgroundResource(R.drawable.btnranking);
//lockedButton.add(view.getId());
try {
// Place the index
GameStatus gs = game.player1Place(lb.locate[0], lb.locate[1]);
this.refesh(gs.board);
imGameMes.setImageResource(R.drawable.game_mes_1);
imDisAI.setBackgroundResource(0);
// Check returned game statue and update the UI
if(gs.status == GameBoard.Status.CONTINUE) {
imDisPlayer.setBackgroundResource(R.drawable.btnranking);
//unlockAllButton();
}else{
if (gs.status == GameBoard.Status.DRAW){
imDisPlayer.setBackgroundResource(R.drawable.btnranking);
imGameMes.setImageResource(R.drawable.game_dis_draw);
}else if (gs.status == GameBoard.Status.PLAYER1_WIN){
imGameMes.setImageResource(R.drawable.game_dis_win);
}else if(gs.status == GameBoard.Status.PLAYER2_WIN) {
imDisPlayer.setBackgroundResource(0);
imDisAI.setBackgroundResource(R.drawable.btnranking);
imGameMes.setImageResource(R.drawable.game_dis_lose);
}
btnContinue.setVisibility(View.VISIBLE);
}
} catch (GameBoardPlacedException e) {
e.printStackTrace();
}
}
}
}
class LocateButton {
Button button;
int[] locate; // row, col index
LocateButton(Button button, int[]locate){
this.button = button;
this.locate = locate;
}
}

View File

@@ -0,0 +1,32 @@
package com.game.tictacteo;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.widget.TextView;
public class InitActivity extends AppCompatActivity {
private TextView tvLoadingMes;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_init);
// hidden Action bar
// getSupportActionBar().hide();
tvLoadingMes = (TextView) findViewById(R.id.tvLoadingMes);
// For Some setup things
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Intent intent = new Intent(InitActivity.this, MainActivity.class);
startActivity(intent);
finish();
}
}, 2000);
}
}

View File

@@ -0,0 +1,40 @@
package com.game.tictacteo;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.widget.TextView;
public class LoadingDialog {
Context context;
Dialog dialog;
TextView tvLoadingDailogMes;
public LoadingDialog(Context context){
this.context = context;
dialog = new Dialog(context);
dialog.setContentView(R.layout.activity_loading_dialog);
tvLoadingDailogMes = (TextView) dialog.findViewById(R.id.tvLoadingDialogMes);
}
public void show(String message){
dialog.getWindow().setBackgroundDrawable(new ColorDrawable((Color.TRANSPARENT)));
// Disable click outside then close the loading dialog
dialog.setCanceledOnTouchOutside(false);
// set Header
tvLoadingDailogMes.setText(message);
dialog.create();
dialog.show();
}
public void hide(){
dialog.dismiss();
}
}

View File

@@ -0,0 +1,67 @@
package com.game.tictacteo;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
Button btnStartGame;
Button btnRanking;
Button btnRecords;
Button btnClose;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnStartGame = (Button) findViewById(R.id.btnPlay);
btnRanking = (Button) findViewById(R.id.btnRanking);
btnRecords = (Button) findViewById(R.id.btnRec);
btnClose = (Button) findViewById(R.id.btnClose);
// Start game
btnStartGame.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent( MainActivity.this, GameActivity.class);
startActivity(intent);
}
});
// Game Ranking
btnRanking.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent( MainActivity.this, RankingActivity.class);
startActivity(intent);
}
});
// Your Records
btnRecords.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent( MainActivity.this, RecordActivity.class);
startActivity(intent);
}
});
// Close
btnClose.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
System.out.println("click");
finish();
}
});
}
}

View File

@@ -0,0 +1,97 @@
package com.game.tictacteo;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.game.tictacteo.model.UserRanking;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
public class RankingActivity extends AppCompatActivity {
private Button btnBack;
private final static String API_ENDPOINT = "http://192.168.76.3/ranking_api.php";
private LoadingDialog ld;
private ArrayList<UserRanking> users;
private RecyclerView rvRankingListView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ranking);
// Load Dailog
ld = new LoadingDialog(this);
users = new ArrayList<UserRanking>();
btnBack = (Button) findViewById(R.id.btnBack);
rvRankingListView = (RecyclerView) findViewById(R.id.rvRankingList);
btnBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
// show loading dialog
ld.show("fetching...");
new Thread(() -> {
try{
// fetch API
URL url = new URL(API_ENDPOINT);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
InputStream inputStream = connection.getInputStream();
BufferedReader buffer = new BufferedReader(new InputStreamReader(inputStream));
String data = buffer.readLine();
StringBuffer json = new StringBuffer();
while(data != null){
json.append(data);
data = buffer.readLine();
}
// JSON List to ArrayList
JSONArray jsonArray = new JSONArray(String.valueOf(json));
for(int i = 0; i < jsonArray.length(); i++){
JSONObject jsonObj = jsonArray.getJSONObject(i);
String name = jsonObj.getString("Name");
int duration = jsonObj.getInt("Duration");
users.add(new UserRanking(name, duration));
Log.i("ranking", "add" + name);
}
runOnUiThread(() -> {
ld.hide();
UserRankingAdapter rankingAdapter = new UserRankingAdapter(this, users);
rvRankingListView.setAdapter(rankingAdapter);
rvRankingListView.setLayoutManager(new LinearLayoutManager(RankingActivity.this));
});
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
}
).start();
}
}

View File

@@ -0,0 +1,148 @@
package com.game.tictacteo;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import com.game.tictacteo.localDB.GameLogDB;
import com.game.tictacteo.model.GameLog;
import java.util.ArrayList;
public class RecordActivity extends AppCompatActivity {
private Button btnRecordBack;
private SurfaceView winStatePieChartView;
private SurfaceHolder holder;
private RecyclerView rvGameLogList;
private ArrayList<GameLog> logs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_record);
logs = GameLogDB.getInstance(this).readAllData();
winStatePieChartView = (SurfaceView) findViewById(R.id.sfwinStatePieChartView);
// For get Canvas in SurfaceView
holder = winStatePieChartView.getHolder();
if(logs.size() > 0){
// If not callback will throw error due to SurfaceView.canvas is not created in onCreate()
holder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
float[] perc = calPercentage(logs);
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setStrokeWidth(4);
Canvas canvas = holder.lockCanvas();
RectF rectF = new RectF(225,50,winStatePieChartView.getWidth()-225,winStatePieChartView.getHeight()-50);
// draw the lose pie
paint.setColor(Color.RED);
canvas.drawArc(rectF, 0, 360*perc[0], true, paint);
// draw the win pie
paint.setColor(Color.GREEN);
canvas.drawArc(rectF, 360*perc[0], 360*perc[1], true, paint);
// draw the draw pie
paint.setColor(Color.YELLOW);
canvas.drawArc(rectF, 360*perc[0]+360*perc[1], 360*perc[2], true, paint);
// setup text paint
paint.setStrokeWidth(0);
paint.setTextSize(30);
// write Draw Text
canvas.drawRect(winStatePieChartView.getWidth()-160, winStatePieChartView.getHeight()-200, winStatePieChartView.getWidth()-135, winStatePieChartView.getHeight()-175, paint);
canvas.drawText("DRAW", winStatePieChartView.getWidth()-125, winStatePieChartView.getHeight()-177, paint);
// write Lose Text
paint.setColor(Color.RED);
canvas.drawRect(winStatePieChartView.getWidth()-160, winStatePieChartView.getHeight()-150, winStatePieChartView.getWidth()-135, winStatePieChartView.getHeight()-125, paint);
canvas.drawText("LOSE", winStatePieChartView.getWidth()-125, winStatePieChartView.getHeight()-127, paint);
// write Win Text
paint.setColor(Color.GREEN);
canvas.drawRect(winStatePieChartView.getWidth()-160, winStatePieChartView.getHeight()-100, winStatePieChartView.getWidth()-135, winStatePieChartView.getHeight()-75, paint);
canvas.drawText("WIN", winStatePieChartView.getWidth()-125, winStatePieChartView.getHeight()-77, paint);
holder.unlockCanvasAndPost(canvas);
//holder.lockCanvas(new Rect(0, 0, 0, 0));
//holder.unlockCanvasAndPost(canvas);
}
private float[] calPercentage(ArrayList<GameLog> data){
int[] count = new int[3]; // 0 -> lose, 1 -> win, 2 -> draw
int total = 0;
for(GameLog gl: data ) {
count[gl.getWinningStatus()]++;
total++;
}
Log.i("prc1", count[0]*1.0f/total+"");
Log.i("prc2", count[1]*1.0f/total+"");
Log.i("prc3", count[2]*1.0f/total+"");
return new float[]{count[0]*1.0f/total, count[1]*1.0f/total, count[2]*1.0f/total};
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
}
});
}
rvGameLogList = (RecyclerView) findViewById(R.id.rvGameLogList);
btnRecordBack = (Button) findViewById(R.id.btnRecordBack);
btnRecordBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
// create adapter for attached the log list
UserRecordAdapter rankingAdapter = new UserRecordAdapter(this, logs);
rvGameLogList.setAdapter(rankingAdapter);
rvGameLogList.setLayoutManager(new LinearLayoutManager(this));
}
}

View File

@@ -0,0 +1,59 @@
package com.game.tictacteo;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.game.tictacteo.model.UserRanking;
import java.util.ArrayList;
public class UserRankingAdapter extends RecyclerView.Adapter<UserRankingAdapter.Viewholder> {
private Context context;
private ArrayList<UserRanking> users;
// Constructor
public UserRankingAdapter(Context context, ArrayList<UserRanking> users) {
this.context = context;
this.users = users;
}
@NonNull
@Override
public UserRankingAdapter.Viewholder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_ranking_card_row, parent, false);
return new Viewholder(view);
}
// Set Text for each row
@Override
public void onBindViewHolder(@NonNull UserRankingAdapter.Viewholder holder, int position) {
UserRanking user = users.get(position);
holder.tvRankingNo.setText(String.valueOf(position+1));
holder.tvRankingName.setText("Name: " + user.getName());
holder.tvRankingDuration.setText("Duration: " + String.valueOf(user.getDuration()));
}
@Override
public int getItemCount() {
return users.size();
}
// Init find Element ID;
public class Viewholder extends RecyclerView.ViewHolder {
private TextView tvRankingNo, tvRankingName, tvRankingDuration;
public Viewholder(@NonNull View itemView) {
super(itemView);
tvRankingNo = itemView.findViewById(R.id.tvRecordDateTime);
tvRankingName = itemView.findViewById(R.id.tvRecordState);
tvRankingDuration = itemView.findViewById(R.id.tvRecordDuration);
}
}
}

View File

@@ -0,0 +1,70 @@
package com.game.tictacteo;
import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.game.tictacteo.model.GameLog;
import com.game.tictacteo.model.UserRanking;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
public class UserRecordAdapter extends RecyclerView.Adapter<UserRecordAdapter.Viewholder> {
private Context context;
private ArrayList<GameLog> logs;
public UserRecordAdapter(Context context, ArrayList<GameLog> logs) {
this.context = context;
this.logs = logs;
}
@NonNull
@Override
public UserRecordAdapter.Viewholder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_record_card_row, parent, false);
return new Viewholder(view);
}
@Override
public void onBindViewHolder(@NonNull UserRecordAdapter.Viewholder holder, int position) {
GameLog log = logs.get(position);
SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat timeFormat = new SimpleDateFormat("hh:mm aa");
holder.tvRecordDateTime.setText(dateFormat.format(log.getPlayDate()) + " " + timeFormat.format(log.getPlayTime()));
holder.tvRecordState.setText("WinState: " + log.winStateToString());
if(log.getWinningStatus() == 0)
holder.tvRecordState.setTextColor(Color.RED);
else if (log.getWinningStatus() == 1)
holder.tvRecordState.setTextColor(Color.GREEN);
else
holder.tvRecordState.setTextColor(Color.BLACK);
holder.tvRecordDuration.setText("Duration: " + String.valueOf(log.getDuration()) + " sec");
}
@Override
public int getItemCount() {
return logs.size();
}
public class Viewholder extends RecyclerView.ViewHolder {
private TextView tvRecordDateTime, tvRecordState, tvRecordDuration;
public Viewholder(@NonNull View itemView) {
super(itemView);
tvRecordDateTime = itemView.findViewById(R.id.tvRecordDateTime);
tvRecordState = itemView.findViewById(R.id.tvRecordState);
tvRecordDuration = itemView.findViewById(R.id.tvRecordDuration);
}
}
}

View File

@@ -0,0 +1,68 @@
package com.game.tictacteo.engine;
import android.content.Context;
import android.util.Log;
import com.game.tictacteo.localDB.GameLogDB;
import com.game.tictacteo.model.GameLog;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Date;
public class Game {
private Timestamp startTime;
private Timestamp endTime;
private GameBoard board;
private Context ctx;
public Game(Context ctx) {
startTime = new Timestamp(System.currentTimeMillis());
board = new GameBoard();
this.ctx = ctx;
}
// User move
public GameStatus player1Place(int row, int col) throws GameBoardPlacedException {
GameStatus gs = place(row, col, 1);
checkGameStatus(gs); // check status if won not need AI move
if (gs.status == GameBoard.Status.CONTINUE) {
gs = aiPlayerPlace();
checkGameStatus(gs); // check status
}
return gs;
}
private GameStatus place(int row, int col, int player) throws GameBoardPlacedException {
if (player == 1)
return this.board.placePlayer1(row, col);
else if (player == 2)
return this.board.placePlayer2(row, col);
return null;
}
private void checkGameStatus(GameStatus gs) {
// check status if Player1 or Player2 win need to insert the log to db
if (gs.status != GameBoard.Status.CONTINUE) {
endTime = new Timestamp(System.currentTimeMillis());
int duration = (int) ((endTime.getTime() - startTime.getTime()) / 1000);
int winingStatus = ((gs.status == GameBoard.Status.DRAW) ? 2 : (gs.status == GameBoard.Status.PLAYER2_WIN) ? 0 : 1);
GameLogDB.getInstance(this.ctx).addLog(new GameLog(new Date(endTime.getTime()), new Time(endTime.getTime()), duration, winingStatus));
}
}
// AI move
private GameStatus aiPlayerPlace() throws GameBoardPlacedException {
Log.i("game-OBJ-AI", "aiPLayerPlace");
int[] move = GameAI.findBestMove(board.getBoard());
return place(move[0], move[1], 2);
}
}

View File

@@ -0,0 +1,151 @@
package com.game.tictacteo.engine;
import android.util.Log;
public class GameAI {
static final byte player = 2;
static final byte opponent = 1;
private static Boolean isMovesLeft(byte board[][]) {
for (int i = 0; i < board.length; i++)
for (int j = 0; j < board[i].length; j++)
if (board[i][j] == 0)
return true;
return false;
}
private static int evaluate(byte[][] board) {
// Checking for Rows is player or opponent victory.
for (int row = 0; row < board.length; row++) {
if (board[row][0] == board[row][1] &&
board[row][1] == board[row][2]) {
if (board[row][0] == player)
return +10;
else if (board[row][0] == opponent)
return -10;
}
}
for (int col = 0; col < board[0].length; col++) {
if (board[0][col] == board[1][col] &&
board[1][col] == board[2][col]) {
if (board[0][col] == player)
return +10;
else if (board[0][col] == opponent)
return -10;
}
}
if (board[0][0] == board[1][1] && board[1][1] == board[2][2]) {
if (board[0][0] == player)
return +10;
else if (board[0][0] == opponent)
return -10;
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0]) {
if (board[0][2] == player)
return +10;
else if (board[0][2] == opponent)
return -10;
}
// Else if none of them have won then return 0
return 0;
}
private static int minimax(byte board[][], int depth, Boolean isMax) {
int score = evaluate(board);
// Evaluated score if Maximizer has won the game
if (score == 10)
return score;
if (score == -10)
return score;
// no winner then it is a tie if false
if (isMovesLeft(board) == false)
return 0;
if (isMax) {
int best = -1000;
// Traverse all cells
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
// Check cell is empty
if (board[i][j] == 0) {
// Make the move
board[i][j] = player;
// calculate minimax recursively and choose
best = Math.max(best, minimax(board, depth + 1, !isMax));
// Undo move
board[i][j] = 0;
}
}
}
return best;
}else {
int best = 1000;
// Traverse all cells
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
// Check cell is empty
if (board[i][j] == 0) {
// Make the move
board[i][j] = opponent;
// calculate minimax recursively and choose
best = Math.min(best, minimax(board,
depth + 1, !isMax));
// Undo move
board[i][j] = 0;
}
}
}
return best;
}
}
public static int[] findBestMove(byte board[][]) {
int bestVal = -1000;
int[] bestMove = new int[2];
bestMove[0] = -1;
bestMove[1] = -1;
// evaluate minimax func
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
// Check cell is empty
if (board[i][j] == 0) {
// Make the move
board[i][j] = player;
// compute evaluation function
int moveVal = minimax(board, 0, false);
// Undo the move
board[i][j] = 0;
// If the value of the current move is higher then best value, then update
if (moveVal > bestVal) {
bestMove[0] = i;
bestMove[1] = j;
bestVal = moveVal;
}
}
}
}
return bestMove;
}
}

View File

@@ -0,0 +1,139 @@
package com.game.tictacteo.engine;
import android.util.Log;
public class GameBoard {
private final static int ROW = 3; // Game Board ROW
private final static int COL = 3; // Game Board COL
private byte[][] board;
private short step;
private short winner; // Winner
public enum Status {
CONTINUE,
PLAYER1_WIN,
PLAYER2_WIN,
DRAW
}
// 0 0 0
// 0 0 0
// 0 0 0
GameBoard() {
board = new byte[ROW][COL];
step = 0;
winner = -1;
}
public byte[][] getBoard() {
return board;
}
public GameStatus placePlayer1(int row, int col) throws GameBoardPlacedException {
return place(row, col, 1);
}
public GameStatus placePlayer2(int row, int col) throws GameBoardPlacedException{
return place(row, col, 2);
}
private GameStatus place(int row, int col, int player) throws GameBoardPlacedException {
if(board[row][col] != 0)
throw new GameBoardPlacedException("The position is placed: " + row + " " + col);
board[row][col] = (byte) player;
step++; // count step for game
return genGameStatus();
}
private GameStatus genGameStatus(){
// return current game board status
// step == board.length mean all is placed
if(step == board[0].length * board.length)
return new GameStatus(Status.DRAW, this.board);
// check has winner?
if(winner == -1 && !checkWin())
return new GameStatus(Status.CONTINUE, this.board);
else
return new GameStatus((this.winner == 1) ? Status.PLAYER1_WIN: Status.PLAYER2_WIN, this.board);
}
private boolean checkWin(){
return (checkRows() || checkColumns() || checkDiagonals());
}
private boolean checkRows() {
for (int i = 0; i < board.length; i++) {
int count = 1;
for (int j = 1; j < board[i].length; j++) {
// compare other col is same?
if (board[i][0] != 0 && board[i][0] == board[i][j])
count++;
else
break;
}
if (count == board[i].length){
this.winner = board[i][0];
return true;
}
}
return false;
}
private boolean checkColumns() {
for (int i = 0; i < board[0].length; i++) {
int count = 1;
for (int j = 1; j < board.length; j++) {
// compare other col is same?
if (board[0][i] != 0 && board[0][i] == board[j][i])
count++;
else
break;
}
if (count == board.length) {
this.winner = board[0][i];
return true;
}
}
return false;
}
// checkDiagonals
private boolean checkDiagonals() {
int countX = 1; // for count left-top to right-bottom
int countY = 1; // for count right-top to left-bottom
for (int j = 1; j < board.length; j++) {
// compare other col is same?
if (board[0][0] != 0 && board[0][0] == board[j][j])
countX++;
if (board[0][board[0].length - 1] != 0 && board[0][board[0].length - 1] == board[j][board[0].length - 1 - j])
countY++;
}
if (countX == board.length) {
this.winner = board[0][0];
return true;
}
if (countY == board.length) {
this.winner = board[0][board[0].length-1];
return true;
}
return false;
}
}

View File

@@ -0,0 +1,9 @@
package com.game.tictacteo.engine;
public class GameBoardPlacedException extends Exception{
public GameBoardPlacedException(String mes){
super(mes);
}
}

View File

@@ -0,0 +1,12 @@
package com.game.tictacteo.engine;
public class GameStatus {
public GameBoard.Status status;
public byte[][] board;
public GameStatus(GameBoard.Status status, byte[][] board){
this.status = status;
this.board = board;
}
}

View File

@@ -0,0 +1,106 @@
package com.game.tictacteo.localDB;
import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import com.game.tictacteo.model.GameLog;
import java.sql.Date;
import java.sql.Time;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
public class GameLogDB extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "GameLog.db";
private static final String GAMESLOG_TABLE_NAME = "GamesLog";
private static final int DATABASE_VERSION = 1;
// Singleton pattern
private static GameLogDB instance = null;
public static GameLogDB getInstance(Context ctx) {
if (instance == null)
instance = new GameLogDB(ctx.getApplicationContext());
return instance;
}
public GameLogDB(Context context) {
// applcation_context, db_name, factory, db_version
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
System.out.println("Database creating");
String sql = "CREATE TABLE " + GAMESLOG_TABLE_NAME + " ( " +
"gameID INTEGER PRIMARY KEY AUTOINCREMENT, " +
"playDate TEXT NOT NULL, " +
"playTime TEXT NOT NULL, " +
"duration INTEGER NOT NULL, " +
"winningStatus INTEGER NOT NULL);";
db.execSQL(sql);
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + GAMESLOG_TABLE_NAME + ";");
onCreate(db);
}
// Add log
public void addLog(GameLog log) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat timeFormat = new SimpleDateFormat("hh:mm aa");
values.put("playDate", dateFormat.format(log.getPlayDate()));
values.put("playTime", timeFormat.format(log.getPlayTime()));
values.put("duration", log.getDuration());
values.put("winningStatus", log.getWinningStatus());
db.insert(GAMESLOG_TABLE_NAME, null, values);
db.close();
}
// select all data
public ArrayList<GameLog> readAllData() {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(GAMESLOG_TABLE_NAME, new String[]{"playDate", "playTime", "duration", "winningStatus"}, null, null, null, null, "gameId");
ArrayList<GameLog> data = new ArrayList<GameLog>();
// Time formatter
SimpleDateFormat timeFormat = new SimpleDateFormat("hh:mm aa");
try {
while (cursor.moveToNext()) {
// yyyy-MM-dd striing to date object
Date playDate = Date.valueOf(cursor.getString(0));
Log.i("db", cursor.getString(1));
Time playTime = new Time(timeFormat.parse(cursor.getString(1)).getTime());
int duration = cursor.getInt(2);
short winningStatus = cursor.getShort(3);
data.add(new GameLog(playDate, playTime, duration, winningStatus));
}
} catch (ParseException e) {
e.printStackTrace();
}
db.close();
return data;
}
}