I'm creating a mobile risk game (client) in Android Studios and a Server in IntelliJ utilizing Java Sockets and ObjectIOStreams sending/receiving Strings to update each clients gamestate when a client finishes their turn.
My current roadblock is when I start my app (currently using a Pixel 2 API R virtual machine) it seems like two Risk games are created, either that or two threads for the Client are being created and the first is sending the correct RiskGame configuration (The one that is displayed in the Apps GUI) and then a second one that is garbage. Here the code for my MainActivity:
package com.example.risk;
import androidx.appcompat.app.AppCompatActivity;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import static java.lang.Thread.sleep;
public class MainActivity extends AppCompatActivity {
RiskGame game; //game instance
Button next; //next button
Button Attack; // attack button
ArrayList<Button> countryButtons; // array list of country buttons
Random rand;
TextView army;
Client client;
@Override
protected void onCreate(Bundle savedInstanceState) {
getSupportActionBar().hide();
super.onCreate(savedInstanceState); //used by android
setContentView(R.layout.activity_main);
army = findViewById(R.id.armyLabel);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); //force landscape mode
rand = new Random(); //random object
countryButtons = new ArrayList<>(); //initialize arrayList
game = new RiskGame(); //new game instance
next = findViewById(R.id.nextButt); //next button
Attack = findViewById(R.id.attackBut); // attack button
int orentation = getResources().getConfiguration().orientation;
if (Configuration.ORIENTATION_LANDSCAPE == orentation) {
for (int i = 0; i < 16; i++) {
String name = "c" + (i + 1);
Button button = findViewById(getResources().getIdentifier(name, "id", getPackageName()));
button.setTag(game.getCountry(i + 1));
countryButtons.add(button);
}
countryButtonSetUp(countryButtons); //sets up button behavior
nextButtonSetup();// sets up next button
Attack.setOnClickListener(v -> { //sets attack button behavior
if (game.getPhase() == 2) {
game = game.attack();
} else if (game.getPhase() == 3) {
game = game.move();
}
updateButtons();
});
for (int i = 0; i < 12; i++) {
next.performClick();
}
} else {
}
}
/**
* Updates a country button's display features
*/
public void updateButtons(){
try {
for (Button button : this.countryButtons) {
Country c = (Country) button.getTag();
button.setTag(game.getCountry(c.getcID()));
button.setText(c.getcID() + ": " + c.getArmiesHeld()); //sets the text to proper number of armies
// System.out.println(c.getPlayerNum() + "Player Num"); // delete
int val = c.getPlayerNum(); //switch statement to set color
switch (val) {
case 1:
button.setBackgroundColor(Color.RED);
break;
case 2:
button.setBackgroundColor(Color.WHITE);
break;
case 3:
button.setBackgroundColor(Color.YELLOW);
break;
case 4:
button.setBackgroundColor(Color.BLUE);
break;
default:
break;
}
if(game.getPhase() == 1){
Attack.setVisibility(View.INVISIBLE);
}else if(game.getPhase() == 2){
Attack.setVisibility(View.VISIBLE);
Attack.setText("Attack");
}else if (game.getPhase() == 3){
Attack.setText("Move");
}
}
army.setText(game.getActivePlayer().getPlaceablearmies() + " ");
}catch (Exception e){
}
//Create the Client object and pass the RiskGame object to it
}
/**
* sets up country button behavior
* @param buttons
*/
public void countryButtonSetUp(ArrayList<Button> buttons){
for(Button button:buttons) { //iterate over each button
Country c = (Country) button.getTag(); //get country data
button.setOnClickListener(v ->{ //set on click behavior
if(game.getPhase() == 1) { //place troop phase of turn
if (game.getActivePlayer().getPlayerNum() == c.getPlayerNum()) { //if active player holds country
Player active = game.getActivePlayer();
if(active.getPlaceablearmies() >0 ) {
this.game = game.addArmy(c.getcID());
button.setTag(game.getCountry(((Country) button.getTag()).getcID())); //update buttons data
active.setPlaceablearmies(active.getPlaceablearmies() -1);
updateButtons(); //update display
}
}
}else if(game.getPhase() ==2){ // attack phase
if(game.getActivePlayer().getPlayerNum() == c.getPlayerNum()){ //player controls country
game.setAttackingCount(c); //sets the country as attacking
}else { //else set to defending
game.setDefendingCount(c);
}
}else if(game.getPhase() == 3){ //move phase
//sets countries that are being moved from and to, if reclicked deselect the country
if(game.getActivePlayer().getPlayerNum() == c.getPlayerNum()){
if(game.getMoveFromcID() == 0){
game.setMoveFromcID(c.getcID());
}else if(game.getMoveTocID() == 0){
game.setMoveTocID(c.getcID());
}else if(game.getMoveTocID() == c.getcID()){
game.setMoveTocID(0);
}else if(game.getMoveFromcID() == c.getcID()){
game.setMoveFromcID(0);
}
}
}
});
// System.out.println(c.getNeighborCIDs() +"");//delete
}
updateButtons(); //update the buttons display
}
public void nextButtonSetup(){//change game phase and player
next.setOnClickListener(v ->{
game.phaseChange();
updateButtons();
});
}
}
The most relevant code here is the onCreate method that calls my RiskGame constructor. The RiskGame constructor is only called one time in the onCreate method. The following is my RiskGame class of which the most relevant where the thread is created is on line 83 in the Constructor:
package com.example.risk;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class RiskGame implements Serializable {
Player activePlayer;
private int activePlayerID;
int phase;
ArrayList<Player> players;
Country attackingCount;
Country defendingCount;
ArrayList<Country> countries;
Random rand;
int moveFromcID;
int moveTocID;
int aDie1;
int aDie2;
int aDie3;
int dDie1;
int dDie2;
public RiskGame() {
players = new ArrayList<Player>();
for (int i = 1; i < 5; ++i) {
players.add(new Player(i));
}
activePlayer = players.get(0);
phase = 1;
attackingCount = new Country();
defendingCount = new Country();
countries = new ArrayList<>();
rand = new Random();
aDie1 =0;
aDie2 =0;
aDie3 =0;
dDie1 =0;
dDie2 =0;
moveFromcID =0;
moveTocID =0;
ArrayList<int []> neighbors = new ArrayList<int[]>(); // array of neighbor arrays
neighbors.add(new int[]{2});
neighbors.add( new int[]{1, 3, 14, 15});
neighbors.add(new int[]{2, 4, 16});
neighbors.add(new int[]{3, 5, 16});
neighbors.add(new int[]{4, 6, 16});
neighbors.add(new int[]{5, 7, 8});
neighbors.add( new int[]{6, 8});
neighbors.add(new int[]{6, 7, 9, 16, 15});
neighbors.add(new int[]{8, 10});
neighbors.add(new int[]{9, 11, 12});
neighbors.add(new int[]{10});
neighbors.add( new int[]{10, 13});
neighbors.add( new int[]{12, 14});
neighbors.add(new int[]{13, 2});
neighbors.add( new int[]{2, 8, 16});
neighbors.add(new int[]{3, 4, 5, 8, 15});
for(int i = 0; i < neighbors.size(); i++) { //sets up random gameboard
int j = 0;
do {
j = rand.nextInt(4) ;
}while (players.get(j).getHeldCountries().size() >= 4); //randomly selects a player that doesn't have 4 held countries
Country c = new Country(j + 1, neighbors.get(i).length+1 , i + 1, neighbors.get(i)); //makes the country
addCountry(c); //adds to the arraylist
players.get(j).addCounty(c); //gives country to player
}
//Add Client Here
ExecutorService worker = Executors.newSingleThreadExecutor();
Client client = null;
try {
client = new Client(this);
} catch (IOException e) {
e.printStackTrace();
}
Client finalClient = client;
worker.submit(() -> {
while (!Thread.interrupted())
finalClient.start();
});
}
public void addCountry(Country country){
countries.add(country);
}
public Player getActivePlayer() {
return activePlayer;
}
public void setActivePlayer(Player activePlayer) {
this.activePlayer = activePlayer;
}
public ArrayList<Player> getPlayers() {
return players;
}
public void setPlayers(ArrayList<Player> players) {
this.players = players;
}
public RiskGame nextPlayer(){
if(activePlayer.getPlayerNum() == 4){
activePlayer = players.get(0);
}else {
activePlayer = players.get(activePlayer.getPlayerNum());
}
return this;
}
public int getPhase() {
return phase;
}
public void setPhase(int phase) {
this.phase = phase;
}
public Country getAttackingCount() {
return attackingCount;
}
public void setAttackingCount(Country attackingCount) {
this.attackingCount = attackingCount;
}
public Country getDefendingCount() {
return defendingCount;
}
public void setDefendingCount(Country defendingCount) {
this.defendingCount = defendingCount;
}
public void setActivePlayerID(int activePlayerID){
this.activePlayerID = activePlayerID;
}
public int getActivePlayerID(){
return this.activePlayerID;
}
public RiskGame attack(){
if(phase == 2){
boolean validAttack = false;
for (int i =0 ; i < attackingCount.getNeighborCIDs().length; i++){
System.out.println(attackingCount.getNeighborCIDs()[i]);
if (attackingCount.getNeighborCIDs()[i] == defendingCount.getcID() && attackingCount.getArmiesHeld() >1){
validAttack = true;
}
}
if (!validAttack){
System.out.println("not vaild attack"); //delete
System.out.println(attackingCount.getcID() + "attack cid" + defendingCount.getcID()); //delete
System.out.println("attacking cids" + Arrays.asList(attackingCount.getNeighborCIDs()).toString() + " cid: "+ attackingCount.getcID());
for (int i =0 ; i < attackingCount.getNeighborCIDs().length; i++){
System.out.println(attackingCount.getNeighborCIDs()[i]);
}
System.out.println("defending cids" + Arrays.asList(defendingCount.getNeighborCIDs()).toString() + " cid: "+ defendingCount.getcID());
attackingCount = new Country();
defendingCount = new Country();
}else {
rollAttack();
rollDefence();
int cId;
if(aDie1 > dDie1 || aDie2 >dDie1 || aDie3 > aDie1){
cId = defendingCount.getcID();
countries.get(cId - 1).removeArmy();
}else if((dDie1 > aDie1 && dDie1 > aDie2 && dDie1 > aDie3)) {
cId = attackingCount.getcID();
countries.get(cId - 1).removeArmy();
}
if (dDie2 != 0 && attackingCount.getArmiesHeld() > 1){
if(aDie1 > dDie2 || aDie2 >dDie2 || aDie3 > aDie2){
cId = defendingCount.getcID();
countries.get(cId - 1).removeArmy();
}else if((dDie2 > aDie1 && dDie2 > aDie2 && dDie2 > aDie3)) {
cId = attackingCount.getcID();
countries.get(cId - 1).removeArmy();
}
}
if(defendingCount.getArmiesHeld() <= 0){
cId = defendingCount.getcID();
countries.get(cId - 1).setPlayerNum(attackingCount.getPlayerNum());
countries.get(cId -1).setArmiesHeld(1);
cId = attackingCount.getcID();
countries.get(cId -1).setArmiesHeld(countries.get(cId -1).getArmiesHeld() - 1);
attackingCount = new Country();
defendingCount = new Country();
}
diceToZero();
System.out.println( attackingCount.getcID() + " is attacking " + defendingCount.getcID()); //delete
}
}
return this;
}
private void diceToZero(){
aDie1 =0;
aDie2 =0;
aDie3 =0;
dDie1 =0;
dDie2 =0;
}
public RiskGame phaseChange(){
if(phase != 3){ //if not phase 3 increment phase
phase+= 1;
}else { //otherwise set phase to one and change active player
phase = 1;
nextPlayer();
activePlayer.setPlaceablearmies(0);
for(Country c: countries) {
if (c.getPlayerNum() == activePlayer.getPlayerNum() ) {
activePlayer.setPlaceablearmies(activePlayer.getPlaceablearmies() + c.getArmyValue());
}
}
moveTocID =0;
moveFromcID =0;
if(activePlayer.getPlaceablearmies() ==0){
phaseChange();
phaseChange();
phaseChange();
}
}
return this;
}
public RiskGame addArmy(int cid){
countries.get(cid - 1).addArmy();
return this;
}
//Rolls the attacking dice
private void rollAttack(){
if(attackingCount.getArmiesHeld() > 3){
aDie1 = rand.nextInt(7 )+1;
aDie2 = rand.nextInt(7 )+1;
aDie3 = rand.nextInt(7 )+1;
}else if(attackingCount.getArmiesHeld() > 2){
aDie1 = rand.nextInt(7 )+1;
aDie2 = rand.nextInt(7 )+1;
}else {
aDie1 = rand.nextInt(7 ) +1;
}
System.out.println(aDie1 +" " + aDie2 +" " +aDie3);
}
//rolls the defence dice
private void rollDefence(){
if(defendingCount.getArmiesHeld() > 1){
dDie1 = rand.nextInt(7 )+1;
dDie2 = rand.nextInt(7 )+1;
}else {
dDie1 = rand.nextInt(7 )+1;
}
System.out.println(dDie1 +" "+ dDie2);
}
public RiskGame move(){
if(phase == 3){
boolean validMove = false;
if(moveTocID != 0 && moveFromcID != 0){
for (int i = 0; i < countries.get(moveFromcID - 1).getNeighborCIDs().length; i++) {
if (countries.get(moveFromcID - 1).getNeighborCIDs()[i] == moveTocID && countries.get(moveFromcID - 1).getArmiesHeld() > 1) {
validMove = true;
}
}
if (moveTocID != 0 && moveTocID != moveFromcID && moveFromcID != 0 && validMove) {
addArmy(moveTocID);
countries.get(moveFromcID - 1).removeArmy();
}
}
}
moveFromcID =0;
moveTocID =0;
return this;
}
public Country getCountry(int cid){
return countries.get(cid - 1);
}
public int getMoveFromcID() {
return moveFromcID;
}
public void setMoveFromcID(int moveFromcID) {
this.moveFromcID = moveFromcID;
}
public int getMoveTocID() {
return moveTocID;
}
public void setMoveTocID(int moveTocID) {
this.moveTocID = moveTocID;
}
}
Below is my "Client" class:
package com.example.risk;
import android.util.Pair;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.LinkedList;
import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
public class Client extends Thread{
RiskGame g;
int ID;
boolean playerExited = false;
ReentrantLock lock = new ReentrantLock();
AtomicBoolean started = new AtomicBoolean(false);
public Client(RiskGame g) throws IOException {
this.g = g;
this.ID = generateID();
}
public String writeGameStateToString(){
String gamestate = "";
gamestate += this.ID + ":";
for(Country countries: g.countries){
gamestate += countries.getcID() + " " + countries.getPlayerNum() + " " + countries.getArmyValue() + ":";
}
return gamestate;
}
@Override
public synchronized void run() {
try {
lock.lock();
this.sendData();
} catch (IOException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public synchronized static int generateID(){
ZonedDateTime nowZoned = ZonedDateTime.now();
Instant midnight = nowZoned.toLocalDate().atStartOfDay(nowZoned.getZone()).toInstant();
Duration duration = Duration.between(midnight, Instant.now());
long seconds = duration.getSeconds();
return (int) (seconds%(65535-4999))+4999;
}
public synchronized void sendData() throws IOException {
if(!started.getAndSet(true)) {
System.out.println(ID);
Socket sendingSocket = new Socket("10.0.2.2", 4999);
OutputStream os;
BufferedOutputStream bos;
ObjectOutputStream oos;
os = sendingSocket.getOutputStream();
bos = new BufferedOutputStream(os);
oos = new ObjectOutputStream(bos);
oos.writeObject(writeGameStateToString());
oos.flush();
while (!this.playerExited) {
//Get gamestate back from server
receiveData();
os = sendingSocket.getOutputStream();
bos = new BufferedOutputStream(os);
oos = new ObjectOutputStream(bos);
oos.writeObject(writeGameStateToString());
oos.flush();
}
sendingSocket.close();
}
}
public synchronized void receiveData() throws IOException {
Socket receivingSocket;
InputStream is;
BufferedInputStream bis;
ObjectInputStream ois;
boolean listening = true;
while(listening){
ServerSocket clientListening = new ServerSocket(this.ID);
receivingSocket = clientListening.accept();
is = receivingSocket.getInputStream();
bis = new BufferedInputStream(is);
ois = new ObjectInputStream(bis);
try {
RiskGame temp = (RiskGame) ois.readObject();
if(temp.getActivePlayerID() == ID) {
listening = false;
this.g = temp;
}else{
System.out.println(temp.getActivePlayer() + ": Updated local gamestate");
this.g = temp;
}
} catch (ClassNotFoundException e) {
System.out.println("Received incompatible Object Type from Server");
}
ois.close();
bis.close();
is.close();
clientListening.close();
}
}
}
Here is an example of the output I am generating Server side where the first block of output is valid and the second is a completely different RiskGame object that is not displayed on the App and I'm not sure how it's being created:
The problem is "User 2 has joined" there should only be one user so far. I know this is a huge code dump but I wanted to make sure that any relevant code would be included. I hope that I made it clear what sections were most relevant and to re-iterate the most relevant code would be in the onCreate method in the first bit of code, the RiskGame constructor at the bottom in the second bit of code and the sendData method of the Client class.
If you need any information or have any questions/suggestions/criticisms;) feel free to drop em below, I could use whatever help I can get. Also apologies the GUI looks like crap I'm no artist. Haha
i think that the problem can be here
ExecutorService worker = Executors.newSingleThreadExecutor();
Client client = null;
try {
client = new Client(this);
} catch (IOException e) {
e.printStackTrace();
}
Client finalClient = client;
worker.submit(() -> {
while (!Thread.interrupted())
finalClient.start();
});
why are u using while loop in submit? if with start method? it can make u more threads. Try it without Thread.interrupted, it will make only one thread with client.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.