Contents
Outline
This app, “FiveInARow”, lets users enjoy the famous strategy game either with their devices ( realized by greedy algorithm ) or with their friends ( realized by socket ). Also, there are two difficulty levels for the players to choose if they want to play against their devices. This is my postgraduate project from 15/04/2019 to 15/05/2019.
Demo
All app functions have been demonstrated in Demo video
App Features
Login
- Recognize the old users and check the password or create a new account for new users.
![](https://img-blog.csdnimg.cn/20200722171559484.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20200722171149162.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20200722171252117.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20200722172652812.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
- Mode selection: The user can choose to play against either his device or his friend.
![](https://img-blog.csdnimg.cn/20200722171405678.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
- Tips: Guide user on how to play
![](https://img-blog.csdnimg.cn/20200722171650391.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
Human VS Device(stand-alone)
- Easy level and difficult level (Greedy algorithm).
![](https://img-blog.csdnimg.cn/20200722171954683.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
- Users can view the HISTORY of all users who played this game before.
![](https://img-blog.csdnimg.cn/20200722174016748.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
- Users can CLEAN all the history.
- Prompt information about WIN and TIE
Win: when computer wins or human wins:
![](https://img-blog.csdnimg.cn/20200722172226283.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20200722172245663.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
Tie: when the board is full:
![](https://img-blog.csdnimg.cn/2020072217210377.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20200722172131999.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
Human VS Human(double-player)
- Use SOCKET connection: server (left) VS client
![](https://img-blog.csdnimg.cn/20200722171738845.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20200722171821603.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
- Every time the user places the piece on the board, they can REGRET and place the piece again.
- When they decide where to put ultimately, they can press the “SEND” button.
![](https://img-blog.csdnimg.cn/20200722175330763.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
Design Summary
Human VS Device
- The first thing we need to do is to define some usable variables.
private Paint paint = new Paint();
//the width of board should be the same as its height
private int BoardWidth;
int[][] board = new int[LINES][LINES];
/*the width of grid should be the same as its height
a board consists of 9*9 grids*/
private int start_X, end_X;
private float GridWidth;
//a board has 10 lines horizontally(as well as vertically)
static public int LINES = 10;
//change the width of pieces to fit the board
private float ratio = 4 * 1.0f / 5;
private Bitmap white, black;
//check whether the current piece is white piece
private boolean checkIsWhite = true;
private boolean checkIsGameOver = false;
private boolean checkIsDraw = false;
private boolean checkIsTie = false;
//arraylist to storage white or black pieces
private ArrayList<Point> whites = new ArrayList<>();
private ArrayList<Point> blacks = new ArrayList<>();
// IWin and CWin, for each group of five pieces in a line indexed by the number allocated to the Winstate to count the sores for the player and the computer.
int[] IWin = new int[2 * LINES * LINES];
int[] CWin = new int[2 * LINES * LINES];
int[][][] wins = new int[LINES][LINES][2 * LINES * LINES];
int flag;// the difficult level flag
int u = 0, v = 0;//the position where the black piece will be put
- onDraw(Canvas canvas) function: When the ArrayList-- whites and blacks have elements, the board will be repainted and pieces will be located.
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawBoard(canvas);
drawPieces(canvas);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
BoardWidth = w;
GridWidth = BoardWidth * 1.0f / LINES;
//eachpiece should change their width to fit the board
int pieceWidth = (int) (GridWidth * ratio);
white = Bitmap.createScaledBitmap(white, pieceWidth, pieceWidth, false);
black = Bitmap.createScaledBitmap(black, pieceWidth, pieceWidth, false);
}
//draw Board which consists 9*9 grids
private void drawBoard(Canvas canvas)//draw board
{
start_X = (int) (GridWidth / 2);
end_X = (int) (BoardWidth - GridWidth / 2);
for (int i = 0; i < LINES; i++) {
int y = (int) ((0.5 + i) * GridWidth);
//draw horizontal lines
canvas.drawLine(start_X, y, end_X, y, paint);
//draw vertical lines
canvas.drawLine(y, start_X, y, end_X, paint);
}
}
private void drawPieces(Canvas canvas) {
for (int i = 0, n = whites.size(); i < n; i++) {//place white pieces on board
Point wPoint = whites.get(i);
float left = (wPoint.x + (1 - ratio) / 2) * GridWidth;
float top = (wPoint.y + (1 - ratio) / 2) * GridWidth;
canvas.drawBitmap(white, left, top, null);
}
for (int i = 0, n = blacks.size(); i < n; i++) {//place black pieces on the board
Point bPoint = blacks.get(i);
float left = (bPoint.x + (1 - ratio) / 2) * GridWidth;
float top = (bPoint.y + (1 - ratio) / 2) * GridWidth;
canvas.drawBitmap(black, left, top, null);
}
}
- Create a play board with 9*9 grids and initialize the winstate( will be introduced in detail )
//Broadview constructor
public Boardview(Context context, AttributeSet attrs) {
super(context, attrs);
InitWinState();
InitBoard();
}
//initialize Board
private void InitBoard() {
paint.setColor(Color.BLACK);
//prevent some lines from being obscure
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
white = BitmapFactory.decodeResource(getResources(), R.mipmap.white);
black = BitmapFactory.decodeResource(getResources(), R.mipmap.black);
}
public void InitWinState() {
int num = 0;
for (int i = 0; i < LINES; i++) { //vertical
for (int j = 0; j <= 5; j++) {
for (int k = 0; k < 5; k++) {//60
wins[i][j + k][num] = 1;
/* num = 0
wins[0][0][0]
wins[0][1][0]
wins[0][2][0]
wins[0][3][0]
wins[0][4][0]
num = 1
wins[0][1][1]
wins[0][2][1]
wins[0][3][1]
wins[0][4][1]
wins[0][5][1]*/
}
num++;
}
}
for (int i = 0; i < LINES; i++) { //horizontal 60
for (int j = 0; j <= 5; j++) {
for (int k = 0; k < 5; k++) {
wins[j + k][i][num] = 1;
}
num++;
}
}
for (int i = 0; i < 6; i++) { //left diagonal 6*6 36
for (int j = 0; j < 6; j++) {
for (int k = 0; k < 5; k++) {
wins[i + k][j + k][num] = 1;
}
num++;
}
}
for (int i = 0; i < 6; i++) { //right diagonal 6*6 36
for (int j = 9; j > 3; j--) {
for (int k = 0; k < 5; k++) {
wins[i + k][j - k][num] = 1;
}
num++;
}
}
total = num;//192
for (int i = 0; i < num; i++) {
IWin[i] = 0;
CWin[i] = 0;
}
}
WinState
We need to collect all the possible win states, five pieces with the same color in one of the four orientations ( vertical, horizontal, right diagonal, and left diagonal).
Generally, I use three variables, int i, j, and k, to form a triple nested for loop to find all the possible states(WinState) in each orientation and add them to a list(wList). And in every WinState, it includes the coordinate, the number of that WinState (aka. one number of all 192 win states), and the value of the WinState.
Vertical
I fix the horizontal ordinate and find all the 6 groups of five pieces in a vertical line for that column and I do the same for all the 10 columns. In the triple nested for loop, I use the 1st for loop’s variable, int i, as horizontal ordinate so that the two for loop inside it can be used to find the vertical ordinates for the points in that column. The 2nd for loop’s variable, int j, is made to fix the start vertical ordinate for that group and the 3rd for loop’s variable, int k, is to fix the increment of the vertical ordinate and I use j+k to fix the vertical ordinates.
For example, as for i=0, j=0 and k growing from 0 to 4, I can find the ordinates of the 1st group of five pieces which are (0,0),(0,1),(0,2),(0,3),(0,4). And the number 0 and the value 1 will be allocated to the five Winstates.
![](https://img-blog.csdnimg.cn/20200719143100392.jpeg?x-oss%20process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
And as int j grows from 0 to 5, I can get all the 6 groups of five pieces in a vertical line for this column(int i=0).
And with int i growing from 0 to 9, I can do the same operation as above to the all 10 columns so that I can find all the WinStates in vertical direction.
Horizontal
Similar to the Vertical one, I form a triple nested for loop but I use j+k as horizontal ordinate and i as vertical ordinate for this time (just swap the horizontal ordinate and the vertical ordinate in the vertical triple for-loop). I fix the vertical ordinate and find all the 6 groups of five pieces in a horizontal line for that row and I do the same for all the 10 rows.
For example, as for i=0, j=0 and k growing from 0 to 4 I can find the ordinates of the 1st group of five pieces in the horizontal direction which are (0,0),(1,0),(2,0),(3,0),(4,0). And a number and the value 1 will be allocated to those five Winstates.
![](https://img-blog.csdnimg.cn/20200719143713520.jpeg?x-oss%20process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
And as int j grows from 0 to 5, I can get all the 6 groups of five pieces in a horizontal line for this row(int i=0). And with int i growing from 0 to 9, I can do the same operation as above to the all 10 rows so that I can find all the WinStates in the horizontal direction.
Right Diagonal
In this part, I want to find all the WinStates in right diagonal direction. I fix the start piece for each group and find the other 4 pieces in that group by adding the same number to the horizontal ordinate and the vertical ordinate of the start piece. And I use the 1st for loop’s variable, int i, as the original horizontal ordinate and the 2nd for loop’s variable, int j, as the original vertical ordinate so that I can find all the 6 original ordinates (the start point for a group) in one column, and I can do the same for from column 1 to column 6 (as j grows from 0 to 5). There are 6 start pieces in every column from column 1 to column 6 and I call 6 groups of 5 pieces like that as a big group.
For example, as for i=0, j=0 and k growing from 0 to 4 I can find the ordinates of the 1st group of five pieces in the right diagonal direction which are (0,0),(1,1),(2,2),(3,3),(4,4). And a number and the value 1 will be allocated to those five Winstates.
![](https://img-blog.csdnimg.cn/20200719143924838.jpeg?x-oss%20process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
And as int j grows from 0 to 5, I can get all the 6 groups of five pieces in a right diagonal line in big group 1 ( the start pieces for all the 6 groups are in column 1 (int i=0) ).
And with int i growing from 1 to 5, I can do the same operation as above to the other 5 big groups of which the start pieces are in from column 2 to column 6. The last big group should be that:
Left Diagonal
In this part, I want to find all the WinStates in left diagonal direction. Similar to the right diagonal part, I use the 1st for loop’s variable int i as original horizontal ordinate and the 2nd for loop’s variable int j as original vertical ordinate. But this time I start from the left bottom of the board as I make the 2nd for loop starts from j=9. And to get each group of five pieces, I should add the corresponding number, int k, to the original horizontal ordinate and subtract the same number from the original vertical ordinate. There are 6 start pieces in every column from column 1 to column 6 and I call 6 groups of 5 pieces like that as a big group.
For example, as for i=0, j=9 and k growing from 0 to 4 I can find the ordinates of the 1st group of five pieces in the left diagonal direction which are (0,9),(1,8),(2,7),(3,6),(4,5). And a number and the value 1 will be allocated to those five Winstates.
![](https://img-blog.csdnimg.cn/20200719144136161.jpeg?x-oss%20process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
And as int j reduces from 9 to 4, I can get all the 6 groups of five pieces in a left diagonal line in the big group 1 (the start pieces for all the 6 groups are in column 1(int i=0)).
And with int i growing from 1 to 5, I can do the same operation as above to the other 5 big groups of which the start pieces are in from column 2 to column 6, so that I can get all the WinStates in left diagonal direction.
And by using those 4 for loops, I can find all the WinStates.
- Override onTouchEvent function
When the user touches the valid position of the screen which means no piece has been placed before, a white piece should be painted and a black piece will be put by computer.
@Override
public boolean onTouchEvent(MotionEvent event) {
if (checkIsTie) return false;
if (checkIsGameOver || !checkIsWhite)
//when game is over, it will show nothing even if you touch the screen
return false;
int act = event.getAction();
if (act == MotionEvent.ACTION_UP) {
int x = (int) event.getX();
int y = (int) event.getY();
Point p = getPoint(x, y);
if (p.y >= LINES) {
return false;
}
// no action will be done if there is a piece in touch position
if (whites.contains(p) || blacks.contains(p)) return false;
if (checkIsWhite) {//it is the turn of the white piece player
whites.add(p);
invalidate();//repaint
board[p.x][p.y] = 1;
for (int f = 0; f < total; f++) {
if (wins[p.x][p.y][f] == 1) {
IWin[f]++;// win state f now has "IWin" pieces
CWin[f] = 6;
if (IWin[f] == 5) {
createAlertDiaglog("You win!");
writeToHistory(false);// write to txt doc
checkIsGameOver = true;
}
}
}
if (!checkIsGameOver) {
checkIsWhite = !checkIsWhite;
computerAI();
}
}
invalidate();//repaint
return true;
}
return true;
}
ComputerAI
According to the difficulty level (easy & difficult), my Score and computer’s Score will be calculated in a different way
private void computerAI() {
// TODO Auto-generated method stub
int max = 0;
//initialize all the scores
int[][] myScore = new int[10][10];
int[][] computerScore = new int[10][10];
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
myScore[i][j] = 0;
computerScore[i][j] = 0;
}
}
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
// if that position is valid
if (board[i][j] == 0) {
for (int k = 0; k < total; k++) {
// search all the win patterns
if (wins[i][j][k] == 1) {
//if position(i,j) belongs to win patterns
if (flag == 1) {//easy
switch (IWin[k]) {
//k win pattern now has "IWin" black pieces
//when the difficulty level is easy, the
//score of computer will be added less than my score
case 1:
myScore[i][j] += 220;
break;
case 2:
myScore[i][j] += 420;
break;
case 3:
myScore[i][j] += 2000;
break;
case 4:
myScore[i][j] += 22000;
break;
}
switch (CWin[k]) {
case 1:
myScore[i][j] += 200;
break;
case 2:
myScore[i][j] += 400;
break;
case 3:
myScore[i][j] += 2000;
break;
case 4:
myScore[i][j] += 10000;
break;
}
} else { //difficult
switch (IWin[k]) {
case 1:
myScore[i][j] += 200;
break;
case 2:
myScore[i][j] += 400;
break;
case 3:
myScore[i][j] += 2000;
break;
case 4:
myScore[i][j] += 10000;
break;
}
switch (CWin[k]) {
case 1:
myScore[i][j] += 220;
break;
case 2:
myScore[i][j] += 420;
break;
case 3:
myScore[i][j] += 2000;
break;
case 4:
myScore[i][j] += 22000;
break;
}
}
}
}
//GREEDY ALGORITHM
// if my score in position (i,j) > max, the black
// piece will be put in this position to intercept,
// else if my score in position (i,j) = max,
// but computer's score is higher,the black piece
// will be put in that position to get a higher score
if (myScore[i][j] > max) {
max = myScore[i][j];
u = i;
v = j;
} else if (myScore[i][j] == max) {
if (computerScore[i][j] >= computerScore[u][v]) {//more black piece positions will be found
u = i;
v = j;
}
}
// if computer's score in position (i,j) > max, the black
// piece will be put in this position to get a higher score,
// else if computer's score in position (i,j) = max,
// but my score is higher,the black piece
// will be put in that position to intercept
if (computerScore[i][j] > max) {
max = computerScore[i][j];
u = i;
v = j;
} else if (computerScore[i][j] == max) {
if (myScore[i][j] > myScore[u][v]) {
u = i;
v = j;
}
}
int exist = 0;//black pieces shouldn't be overlapped
for (int l = 0; l < bp.size(); l++) {
if (bp.get(l).x == u && bp.get(l).y == v)
exist = 1;
}
if (exist == 0) {
bp.add(new Point(u, v));
//System.out.println("u: "+u+"v: "+v);
}
}
}
}
int get = 0;
for (int i = bp.size() - 1; i >= 0; i--) {
// find the 1st blackpiece position which don't overlap with existing black pieces
int same = 0;
for (int j = 0; j < blacks.size(); j++) {
if (blacks.get(j).x == bp.get(i).x && blacks.get(j).y == bp.get(i).y ) {
same = 1;
}
}
if (same == 0 && get == 0) {
u = bp.get(i).x;
v = bp.get(i).y;
get = 1;
board[u][v] = 2;
blacks.add(new Point(u, v));
invalidate();
if((whites.size()+blacks.size()) == 100) {
checkIsTie = true;
createAlertDialog("It is a tie!");
}
for (int f = 0; f < total; f++) {
if (wins[u][v][f] == 1) {
CWin[f]++;
IWin[f] = 6;
if (CWin[f] == 5) {
createAlertDialog("Computer Wins!");
writeToHistory(true);
checkIsGameOver = true;
}
}
}
if (!checkIsGameOver) {
checkIsWhite = !checkIsWhite;
}
}else break;
}
}
- Start function
public void start() {
u = 0;
v = 0;
total = 0;
whites.clear();
blacks.clear();
checkIsWhite = true;
checkIsGameOver = false;
checkIsTie = false;
wins = new int[10][10][200];
board = new int[10][10];
IWin = new int[200];
CWin = new int[200];
InitWinState();
invalidate();
}
Human VS Human
BoardView for Server & Client
CheckGameOver: Detect win pattern in 4 directions.
final static int Horizontal_Tag = 0, Vertical_Tag = 1, Left_diagonal_Tag = 2, Right_diagonal_Tag = 3;
final static int[] Tags = {Horizontal_Tag, Vertical_Tag, Left_diagonal_Tag, Right_diagonal_Tag};
Point point1, point2;
public void checkGameOver() {
boolean whiteWin = checkWinner(whites);
boolean blackWin = checkWinner(blacks);
if (whiteWin || blackWin) {
checkIsGameOver = true;
String text = whiteWin ? "you win!" : "you lose!";
if(getBoardViewListener()!=null){
getBoardViewListener().BvTouch(whites,blacks,true);
}
createAlertDiaglog(text,whiteWin);
}
}
CheckWinner: Check winner by detecting whether there exists five pieces with the same color, in one of these four orientations (horizontal, vertical, left diagonal and right diagonal)
public boolean checkWinner(List<Point> points) {
for (Point point : points) {
int x = point.x;
int y = point.y;
for (int i = 0; i < Tags.length; i++) {
if (check(x, y, points, Tags[i])) {
return true;
}
}
}
return false;
}
Check the pieces in current piece’s left side or right side whatever orientation we check. For each current point, we will create point 1 and point 2 for four times virtually. Then every time we find point1 or point2 exists in pieces Arraylist actually, and we will make count++, if count = 5 ultimately, which means 5 pieces in a line. Let me illustrate this by using graph: (Note: point1 denoted by 1, point2 denoted by 2, current point denoted by C)
a.horizontal check:
1 1 1 1 C 2 2 2 2
b.vertical check: c.left_diagonal check: d.right_diagonal_tag check:
1 1 *********2
1 *1 ********2
1 **1 *******2
1 ***1 ******2
C ****C *****C
2 *****2 ****1
2 ******2 ***1
2 *******2 **1
2 ********2 *1
private boolean check(int x, int y, List<Point> PList, int tag) {
int count = 1;
for (int i = 1; i < 5; i++) {
if (tag == Horizontal_Tag) {
point1 = new Point(x - i, y);//horizontal
point2 = new Point(x + i, y);
} else if (tag == Vertical_Tag) {
point1 = new Point(x, y - i);//vertical
point2 = new Point(x, y + i);
} else if (tag == Left_diagonal_Tag) {
point1 = new Point(x - i, y + i);//left diagonal
point2 = new Point(x + i, y - i);
} else if (tag == Right_diagonal_Tag) {
point1 = new Point(x + i, y + i);//right diagonal
point2 = new Point(x - i, y - i);
}
if ((PList.contains(point1) && !PList.contains(point2)) || (!PList.contains(point1) && PList.contains(point2))) {
count++;
} else if ((PList.contains(point1) && PList.contains(point2))) {
count += 2;
} else break;
}
if (count == 5) {
return true;
}
return false;
}
Socket
I use Java Socket to realize the two-way real-time communication between client and server.
Server
![](https://img-blog.csdnimg.cn/20200722171738845.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
- Instantiate a ServerSocket object with the specified port through which the server can listen for connection requests from the client;
- Call the accept() method to listen for requests from the connection port, which is blocked.
public void receiveData(){//establish the connection with the client
Thread thread = new Thread(){
@Override
public void run() {
super.run();
try {
serverSocket = new ServerSocket(8090);//port
} catch (IOException e) {
e.printStackTrace();
}
//1. get IP address
GetIpAddress.getLocalIpAddress(serverSocket);
Message message_1 = handler.obtainMessage();
message_1.what = 1;// handler case 1 will process on it
message_1.obj = "IP:" + GetIpAddress.getIP() + " PORT: " + GetIpAddress.getPort();
handler.sendMessage(message_1);
//2.accept the request of socket connection from client
while (true){
try {
socket = serverSocket.accept();
inputStream = socket.getInputStream();
StringBuffer stringBuffer = H2HServer.this.stringBuffer;
int len;
byte[] bytes = new byte[20];
boolean isString = false;
//When the input stream is closed, it will be equal to -1,
// which is not the case that after reading the data, it will be equal to -1
//after reading the data.
while ((len = inputStream.read(bytes)) != -1) {
for (int i = 0; i < len; i++) {
if (bytes[i] != '\0') {
stringBuffer.append((char) bytes[i]);
} else {
isString = true;
break;
}
}
if (isString) {
Message message_2 = handler.obtainMessage();
message_2.what = 2;// handler case 2 will process on it
message_2.obj = stringBuffer;
handler.sendMessage(message_2);
//send data to handler, handler will process on it
isString = false;
}
}
//When this exception occurs, the connection on the client side has been disconnected
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
thread.start();
}
-
Do the read and write operations (IO) using the client socket object returned by this method ;
-
Close the Socket.
Handler:
public Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
Text1.setText(msg.obj.toString());//show IP address
break;
case 2:
editText_2.setText(msg.obj.toString());
String tmp = msg.obj.toString();
//receive and parse data(tmp) from the client and repaint the board
int flag1= 0;
for(int i = 0;i < tmp.length();i++)
{
if(tmp.charAt(i) =='(')
{
if(tmp.substring(i+1,i+2).equals("-"))
{
flag1 = i;
}
}
}
for(int i = 0;i < tmp.length();i++)
{
if(tmp.charAt(i) == '(')
{
if(!tmp.substring(i+1,i+2).equals("-"))
{
if(i < flag1) {
apw.add(new Point(tmp.charAt(i + 1)-'0',tmp.charAt(i + 3)-'0'));// add new white piece to the board
}
else{
apb.add(new Point(tmp.charAt(i + 1)-'0',tmp.charAt(i + 3)-'0'));// add new black piece to the board
}
}
}
}
bv.blacks = apb;// refresh the board
bv.whites = apw;
bv.count = 0;
bv.invalidate();
stringBuffer.setLength(0);
break;
}
}
};
bt.setOnClickListener(new View.OnClickListener() {//send data to the client
@Override
public void onClick(View v) {
try {
bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
} catch (IOException e) {
e.printStackTrace();
}
try {
bw.write(editText_2.getText().toString()+"\n");
} catch (IOException e) {
e.printStackTrace();
}
try {
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
});
Data format:( whites.get(i).x , whites.get(i).y )(-,-)( blacks.get(i).x , blacks.get(i).y )
String str = "";
for(int i = 0;i < whites.size();i++)
{
str += "("+whites.get(i).x + ","+whites.get(i).y+"),";
}
str += "(-,-),";
for(int i = 0;i < blacks.size();i++)
{
str += "("+blacks.get(i).x + ","+blacks.get(i).y+"),";
}
editText_2.setText(str);
Client
![](https://img-blog.csdnimg.cn/20200722171821603.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2NvZHlsZWxl,size_16,color_FFFFFF,t_70)
- Do the instantiating with the IP address and port provided by the server side
**getIPAddress:** get server's IP address
```java
public class GetIpAddress {
public static String IP;
public static int PORT;
public static String getIP(){
return IP;
}
public static int getPort(){
return PORT;
}
public static void getLocalIpAddress(ServerSocket serverSocket){
try {
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();){
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses();enumIpAddr.hasMoreElements();){
InetAddress inetAddress = enumIpAddr.nextElement();
String mIP = inetAddress.getHostAddress().substring(0, 3);
if(mIP.equals("192")){
IP = inetAddress.getHostAddress(); //get the local IP
PORT = serverSocket.getLocalPort(); //get the local PORT
Log.e("IP",""+IP);
Log.e("PORT",""+PORT);
}
}
}
} catch (SocketException e) {
e.printStackTrace();
}
}
}
- Call the connect() method to connect to the server
connect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ip = editText_ip.getText().toString();
if (ip.equals("")) {
Toast.makeText(H2HClient.this, "please input Server IP", Toast.LENGTH_SHORT).show();
}
Thread thread = new Thread() {
@Override
public void run() {
super.run();
if (!socketStatus) {
try {
socket = new Socket(ip, 8090);
} catch (Exception e) {
System.out.println("cannot create socket!!!since e" + e);
}
if (socket == null)
System.out.println("connect fail!");
else {
socketStatus = true;
}
}
}
};
thread.start();
}
});
- Obtain the flow on the Socket and encapsulate that into the BufferedReader/PrintWriter instance, for reading and writing;
- Realize the interacting with the server using getInputStream and getOutputStream provided by Socket;
Send data to the server(the data format is the same as the data sent by server)
public void send(View view) {
data = editText_data.getText().toString();
if (data.equals("")) {
Toast.makeText(H2HClient.this, "please input Sending Data", Toast.LENGTH_SHORT).show();
} else {
data = data + '\0';
}
Thread thread = new Thread() {
@Override
public void run() {
super.run();
if (socketStatus) {
try {
outputStream = socket.getOutputStream();
outputStream.write(data.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
thread.start();
}
Receive data from the server
public void receiveData() {
Thread thread = new Thread() {
@Override
public void run() {
super.run();
if (socketStatus) {
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String mess = br.readLine();
int flag1 = 0;
for (int i = 0; i < mess.length(); i++) {
if (mess.charAt(i) == '(') {
if (mess.substring(i + 1, i + 2).equals("-")) {
flag1 = i;
}
}
}
for (int i = 0; i < mess.length(); i++) {
if (mess.charAt(i) == '(') {
if (!mess.substring(i + 1, i + 2).equals("-")) {
if (i < flag1) {
apw.add(new Point(mess.charAt(i + 1) - '0', mess.charAt(i + 3) - '0'));
} else {
apb.add(new Point(mess.charAt(i + 1) - '0', mess.charAt(i + 3) - '0'));
}
}
}
}
bv.blacks = apb;
bv.whites = apw;
bv.count = 0;
bv.invalidate();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
};
thread.start();
}
- Close the Socket.
Conclusion
In this project, two game modes have been achieved: human VS device and human VS human. To achieve the first mode, I concluded all the possible win states both for human and device - once the white piece was placed, the corresponding pattern would have one more piece, and if there exists a pattern that has five-piece, the game is ended. To achieve another mode, the socket between the server and client was established, and after the client sent the connection request to the server and the server accepted it, the place of pieces can be transferred through the socket. At the same time, every time the user places the piece on the board, win state will be checked.