Can't modify variable inside onClick method - java

So, I have created a java class to implement onClickListener and inside this class I have written the onClick public method. Outside of this method, I have created an int object and I want to modify this object inside the onClick method. I have researched a lot by also checking other similar SO questions and I have tried many things, like creating the object as a public int, or making it a private int and have another method to change it and then call this method inside onClick. However, nothing seems to work.
The code shown below has the int object created as a private int and named turn. To change it inside onClick, I have first created a public method named changeTurn that modifies it and then I call this method inside onClick.
public class TicTacToe implements View.OnClickListener {
Button buttons[] = new Button[9];
TextView result;
public TicTacToe(Button[] buttonList, TextView text) {
buttons = buttonList;
result = text;
}
//public void
private int turn = 1; // The object that needs to be modified in onCLick
#Override
public void onClick(View v) {
Button b = (Button) v;
if((((Button) v).getText() != "X") && (((Button) v).getText() != "O")) {
if(this.turn == 1) {
b.setText("X");
changeTurn(); // ***Should change the value of turn***
result.setText("Turn is: " + this.turn);
}
if(this.turn == 2) {
b.setText("O");
changeTurn(); // ***Should change the value of turn***
result.setText("Turn is: " + turn);
}
}
}
public void changeTurn() {
if(this.turn == 1) {
this.turn = 2;
}
if(this.turn == 2) {
this.turn = 1;
}
}
}
From what I've tried, the program goes only inside the first if every time I click any of my 9 buttons, whose setOnClickListeners are connected to this onClick method. Also, the value of turn is always 1 when I print it out, which basically means that its value is not changed by changeTurn inside the onClick method.
General info on the application: I'm trying to make a tic-tac-toe game in a 3x3 grid with 9 buttons. Since there would be 2 players, I'm trying to use this turn integer to keep track of whose turn it is to press a button. If turn is 1, the button's text gets changed to X and if turn is 2, it changes to O. Right now, every time I press a button, it always changes to X.
I would really appreciate any help or ideas.

You're setting the turn to 2 and then immediately setting it back to 1.
// turn == 1
if(this.turn == 1) { // true
this.turn = 2; // turn == 2
}
if(this.turn == 2) { // now true!
this.turn = 1; // turn == 1
}
The easiest thing to do is to only enter the second block if the first is skipped, i.e.:
if(this.turn == 1) {
this.turn = 2;
} else if(this.turn == 2) {
this.turn = 1;
}
Alternatively, if you're expecting to expand the block with more turn numbers, use switch:
switch(this.turn) {
case 1:
this.turn = 2;
break;
case 2:
this.turn = 1;
break;
}
The only trouble with switch is if you forget a break statement you end up with an unpredictable mess.
Finally, a quick bit of advice: if you're trying to create a loop of numbers (1 .. n then back to 1) then you should consider the modulus operator (%) like x = x % n + 1;

try use it like this
final private int[] turn = {0}
Then change code to
if(turn[0] == 1) {
b.setText("X");
turn[0]=2; // ***Should change the value of turn***
result.setText("Turn is: " + turn);
}
if(turn[0] == 2) {
b.setText("O");
turn[0]=1; // ***Should change the value of turn***
result.setText("Turn is: " + turn);
}

Related

Why are variable changes not persisting a method call?

I'm trying to program a bug to move around an array attached to a custom Room object, whilst keeping count of how many times each tile has been stepped on.
The Room object is working properly, as are the movement and the counting. However, the bug's coordinates, bugX and bugY, are somehow reverting to 0 after exiting the nextMove method. Their values only revert when exiting the method; even the last line of code in the nextMove method itself uses their new values.
Relevant portion of the method is attached, but other sections can be added upon request.
if (dirNum == 0 && bugY < length-1) //Move up
bugY++;
else if (dirNum == 1 && bugX < width-1) //Move right
bugX++;
else if (dirNum == 2 && bugY > 0) //Move down
bugY--;
else if (dirNum == 3 && bugX > 0) //Move left
bugX--;
else {
System.out.println("Error: Cannot move " + direction + ".");
canMove = false;
dirNum = generator.nextInt(4);
continue;
}
This is the context for the command itself.
while (endSim == false) {
nextMove(bugX, bugY);
System.out.print(room.printRoom() + "\n\nNext move? (y/n) ");
simSentinel = in.next();
if (simSentinel.charAt(0) == 'n')
endSim = true;
}
The declarations where the starting coordinates are assigned aren't inside any loops, let alone where the variable itself is called.
The problem is the one described by #T.J.Crowder in his answer though applied to java.
Variables passed as parameters in java are passed by value. If the value is changed by the method receiving the parameter, the change only affects the value inside that method. The "outside" value doesn't change.
What you can do is to encapsulate the coords in an object and pass the encapsulating object as a parameter.
Then the method will receive the object by value, and change it's state (instead of the value of the object).
For a deeper understanding see this question
EDIT I:
I cleand up the code a bit. Though it is is missing the declaration of room and simSentinel, if you add that you should have a running example.
public class Bug{
public int x=0;
public int y=0;
}
public class SimpleSim {
private int dirNum = 0;
private int length = 20;
private int width = 20;
private boolean canMove = true;
private Random generator = new Random();
private boolean endSim = false;
public static void main(String [] args) {
SimpleSim simpleSim = new SimpleSim();
simpleSim.start();
}
private void start() {
Bug myBug = new Bug();
// Give the bug some initial x, y values.
myBug.x = 0;
myBug.y = 0;
while (endSim == false) {
nextMove(myBug);
System.out.print(room.printRoom() + "\n\nNext move? (y/n) ");
simSentinel = in.next();
if (simSentinel.charAt(0) == 'n')
endSim = true;
}
}
}
public void nextMove(Bug bug){
if (dirNum == 0 && bug.y < length-1) //Move up
bug.y++;
else if (dirNum == 1 && bug.x < width-1) //Move right
bug.x++;
else if (dirNum == 2 && bug.y > 0) //Move down
bug.y--;
else if (dirNum == 3 && bug.x > 0) //Move left
bug.x--;
else {
System.out.println("Error: Cannot move " + "?" + ".");
canMove = false;
dirNum = generator.nextInt(4);
}
}
}
It seems that you are passing your bugX and bugY parameters by value. In this case, changing their value inside the method won't affect their values outside the method.
You may want to make your nextMove method return the new values for bugX and bugY after they are computed so that you can gather them back into your actual bugX and bugY variables

Creating a day schedule

I just need some help with one part of my code. I have 3 classes, Activity, DaySchedule, and DayScheduleFrontEnd. In the DaySchedule Class a have a method called addActivity:
public void AddActivity(Activity aActivity)
{
//Check if activity array is full
if(activities[activities.length-1] != null)
{
System.out.println("The activities database is full");
return;
}
//Find the first empty space
for(int i=0;i<activities.length;i++)
{
if(activities[i] == null)
{
activities[i] = aActivity;
break;
}
}
this.SortActivitiesByTime();
}
What I need help on is that an activity cannot conflict with another one. An activity conflicts if the start and the end of one activity cannot fall at the same time as another. If this were to occur the user should be notified and the activity is not added. Also this method should finally sort the activities based on their starting time. So for example if I had Activity: BreakFast StartHour: 8 EndHour: 9 and then Activity: Breakfast part 2 StartHour: 8 EndHour: 10 it would print out saying that there is a conflict.
Accessors from Activity Class
getStartHour()
getEndHour()
First of all create a function in Activity class called hasConflict(Activity other). In the function check if there is a time conflict. I do not completely agree with how you are keeping time in your activity. You should keep two Date called start and end and use the getTime() to see if there is a conflict between two activities. It should be a simple if statement.
Second, change your activities array to LinkedList<Activity>. You keep MAX_SIZE of your list and check that before saying that the database is full
Third use Collections.sort() to sort your activities. There are two ways to call that function.
Implement Comparable for your Activity class and compare the provided activity with this activity in the function
Implement Comparator and provide an instance of that to this function
Once you have all of the above, you can write your functions to
First check if there is space in your database by checking list.size() with MAX_SIZE
Iterate through the list and see if the new activity has any conflict with any existing one
If all of above is ok, add the activity to list and sort whenever you want.
There are better ways than sorting as in adding the activity to correct index the first time while you are iterating the activities to check for conflict but this will do for now.
I believe you are not doing it the best possible way. You insert an element at the end of the array and then sort the whole thing. However, you start with an empty array and then activities are added. You should find the place where you can insert the element and shift other elements to the right.
public void AddActivity(Activity aActivity)
{
//Check if activity array is full
if(activities[activities.length-1] != null)
{
System.out.println("The activities database is full");
return;
}
//Find the desired location and the first empty space
int desiredLocation = -1;
int firstEmptySpace = -1
for(int i=0;(i<activities.length) && (firstEmptySpace == -1);i++)
{
if ((desiredLocation == -1) && (aActivity.getEndHour() > activities[i].getStartHour()))
{
if (aActivity.getStartHour() < activities[i].getEndHour())
{
//Intersection :(
return;
}
else
{
desiredLocation = i;
}
}
if(activities[i] == null)
{
firstEmptySpace = i;
}
}
//shift things to the right
for (int i = firstEmptySpace; i > desiredLocation; i--)
{
activities[i] = activities[i - 1];
}
//Override previous value at desired location
activities[desiredLocation] = aActivity;
}
Below, is a version using collections, while keeping the total amount and order and uniqueness of elements by using a TreeSet. I didn't include the get/set methods, but it should be straightforward.
import java.util.Comparator;
import java.util.Objects;
import java.util.TreeSet;
public class Activity {
public long startHour;
public long endHour;
public static final int MAX_SIZE = 10;
public static TreeSet<Activity> activities = new TreeSet<>(new Comparator<Activity>() {
#Override
public int compare(Activity o1, Activity o2) {
return Long.compare(o1.startHour, o2.startHour);
}
});
public boolean intersect(Activity activity) {
return (activity.startHour >= this.startHour && activity.startHour < this.endHour)
|| (this.startHour >= activity.startHour && this.startHour < activity.endHour);
}
#Override
public String toString() {
return "start: " + startHour + ", end: " + endHour;
}
#Override
public int hashCode(){
return Objects.hash(startHour, endHour);
}
#Override
public boolean equals(Object object) {
if (this == object) return true;
Activity other = (Activity) object;
return (startHour == other.startHour) && (endHour == other.endHour);
}
public static void addActivity(Activity aActivity)
{
if (activities.size() >= MAX_SIZE) {
System.out.println("The activities database is full");
return;
}
for (Activity a : activities) {
if (a.intersect(aActivity)) {
System.out.println(String.format("Activity %s intersect with %s", a, aActivity));
return;
}
}
activities.add(aActivity);
}
public static void main(String[] args) {
Activity a1 = new Activity();
a1.startHour = 0;
a1.endHour = 5;
Activity b1 = new Activity();
b1.startHour = 3;
b1.endHour = 7;
Activity b2 = new Activity();
b2.startHour = 5;
b2.endHour = 9;
Activity b3 = new Activity();
b3.startHour = 1;
b3.endHour = 2;
addActivity(a1);
addActivity(b1);
addActivity(b1);
addActivity(b2);
addActivity(b3);
System.out.println(activities);
}
}

(Java) Calling methods in constructor?

***I tried searching but I just can't understand what comes up. Sorry.
I am working on a server/client project for school. I am at my wit's end and I am about to give up. I do not have notes from class so I need some help. First I will post the directions I was given, then my current code, then my problem.
SERVER DIRECTIONS:
The Triangle class must have the following instance variables: side_a, side_b, side_c. Create a static variable to keep track of the number of Triangle objects created. Also create a static variable to hold the total of the perimeters of all Triangle objects created.
A three parameter constructor of the Triangle class should assign a trio of input values to the instance variables of the object. If not a valid triangle set all of the sides to 1. Create a separate private method called isValid(). The sum of any two sides of a triangle must be greater than the third in order to represent a valid triangle. No side may be 0 or negative. The constructor should also add 1 to the count and also call a method to calculate and then add the perimeter for that object to an accumulator.
The Triangle class must have the following methods as stated above:
public boolean is_right() public boolean is_isoc()
public boolean is_equil() public boolean is_scal()
public String toString() – returns the values for the 3 sides of the Triangle
You should also add a method to the Triangle class called calc_perim. This method will use the sides of the Triangle object to calculate the perimeter for that object.
addTotalPerim. This method will call calc_perim and add the perimeter for that object to an accumulator.
reduceTotalPerim. This method should subtract the perimeter for that object from the accumulator.
SERVER CODE:
public class Triangle {
private int side_a, side_b, side_c;
private static int count;
**//PROBLEM 1: Java tells me 'perim' is not used.**
private static int perim;
private boolean valid;
public Triangle(int s1, int s2, int s3)
{
side_a = s1; side_b = s2; side_c = s3;
**//PROBLEM 2: Java tells me 'v' is not used.**
boolean v = isValid();
if (v = false)
{side_a = 1; side_b = 1; side_c = 1;}
Triangle.count++;
calc_perim(s1,s2,s3);
addTotalPerim();
reduceTotalPerim();
}
private int calc_perim()
{
int perimeterCalc = side_a + side_b + side_c;
return perimeterCalc;
}
private void addTotalPerim()
{
Triangle.perim += calc_perim();
}
private void reduceTotalPerim()
{
Triangle.perim -= calc_perim();
}
private boolean isValid()
{
boolean valid1;
if (side_a < 1)
{ valid1 = false;}
else if (side_b < 1)
{ valid1 = false;}
else if (side_c < 1)
{ valid1 = false;}
else if ((side_a + side_b) < side_c || (side_a + side_b) == side_c)
{ valid1 = false;}
else
{ valid1 = true;}
return valid1;
}
public boolean is_right()
{
boolean right;
if (((side_a * side_a) + (side_b * side_b)) == (side_c * side_c))
right = true;
else
right = false;
return right;
}
public boolean is_isoc()
{
boolean isoc;
if (side_a == side_b)
isoc = true;
else if (side_a == side_c)
isoc = true;
else if (side_b == side_c)
isoc = true;
else
isoc = false;
return isoc;
}
public boolean is_equil()
{
boolean equil;
if (side_a == side_b && side_a == side_c)
equil = true;
else
equil = false;
return equil;
}
public boolean is_scal()
{
boolean scal;
if (side_a == side_b || side_a == side_c || side_b == side_c)
scal = false;
else
scal = true;
return scal;
}
public String toString()
{
return "Side 1: " + side_a + " Side 2: " + side_b + " Side 3: " + side_c;
}
}
Sorry about formatting but this site has a terrible way of formatting code, unless I'm misunderstanding something...
SERVER PROBLEMS:
What is the correct way to add/subtract the perimeter obtained via method calc_perim to varible perim? The directions say to call the calc_perim method in the constructor but I can't figure out how, so I just made it do its calculations on its own.
In the constructor, after calling method isValid(), why am I told by Java that variable 'v' is not used? Did I call isValid() incorrectly? >>>>> How do I call a method in the constructor? <<<<<
Other than that major issue, the server class works fine.
Suggestions:
Within Triangle class, initialize the static variables.
private static int count = 0;
private static int perim = 0;
Within Triangle constructor, change,
if (v = false)
to
if (v == false)
Change calc_perim as:
private void addTotalPerim()
{
perim += calc_perim();
}
private void reduceTotalPerim()
{
perim -= calc_perim();
}
Why you call reduceTotalPerim() after addTotalPerim(), didn't get this clear.
isValid function should check all combinations like a+b>c, b+c>a, c+a>b, if any one fails should be invalid
The "is not used" message from the Java compiler is technically a warning, not an error, so you could run your program even with the message still in effect if you really wanted to. But your instincts are correct- it's a bad idea to ignore those messages.
In this case, your code has a serious problem. You're calling v = false, which means that you're assigning a value of false to v. Change it to v == false. By using ==, you're doing a comparison, which is what you really want.

JLabel updating problems

I am creating a hangman game and I was having trouble getting the jlabel that contained each character of the word to update after the right letter button has been clicked. I have been having trouble with this as I am relatively new to working with Java Guis. Below is the action listener for the letter buttons.
private class LetterHandler implements ActionListener{
private char letterVal;
public LetterHandler(char lv){
letterVal = lv;
}
//checks if clicked button has the correct value as word char
public void actionPerformed(ActionEvent e){
for(int x = 1; x <= selectedWord.wordLength; x++){
if(selectedWord.wordValue.charAt(x - 1) == letterVal){
// JLabel letterValLabel = new JLabel(String.valueOf(letterVal));
wordSpacesArray.get(x-1).setName(String.valueOf(letterVal));
wordSpacesArray.get(x-1).revalidate();
continue;
}
else{
continue;
}
}
checkWin();
triesLeft--;
triesLeftLabel.revalidate();
}
//finds if all jlabels are complete or not
public void checkWin(){
for(int x = 1; x <= wordSpacesArray.size(); x++){
String charVal;
charVal = String.valueOf(wordSpacesArray.get(x-1));
if(charVal != "?"){
System.out.println("youWon");
System.exit(0);
}
else{
break;
}
charVal = null;
}
}
}
Any help is appreciated. Let me know if you need for of the programs code Thanks :)
There are some issues with the code. However, I'll first try to focus on your current problem:
I assume that wordSpacesArray is a list that contains the JLabels of individual letters of the word.
When this ActionListener will be notified, you try to update the labels in wordSpacesArray with the letter that corresponds to this button. However, in order to update the text that is shown on a JLabel, you have to call JLabel#setText(String) and not JLabel#setName(String). So the line should be
wordSpacesArray.get(x-1).setText(String.valueOf(letterVal));
// ^ Use setText here!
Now, concerning the other issues:
As pointed out in the comments, you should use 0-based indexing
The calls to revalidate are not necessary
The use of continue in its current for is not necessary
You should not compare strings with ==, but with equals
// if(charVal != "?") { ... } // Don't do this!
if(!charVal.equals("?")){ ... } // Use this instead
But the charVal in this case will be wrong anyhow: It will be the string representation of the label, and not its contents. So you should instead obtain the text from the label like this:
// String charVal = String.valueOf(wordSpacesArray.get(x-1)); // NO!
String charVal = wordSpacesArray.get(x-1).getText(); // Yes!
The triesLeftLabel will not be updated as long as you don't call setText on it
I think the logic of the checkWin method is flawed. You print "You won" when you find the first letter that is not a question mark. I think it should print "You won" when no letter is a question mark.
You should not call System.exit(0). That's a bad practice. Let your application end normally. (In this case, maybe by just disposing the main frame, although that would also be questionable for a game...)
So in summary, the class could probably look like this:
private class LetterHandler implements ActionListener
{
private char letterVal;
public LetterHandler(char lv)
{
letterVal = lv;
}
// checks if clicked button has the correct value as word char
#Override
public void actionPerformed(ActionEvent e)
{
for (int x = 0; x < selectedWord.wordLength; x++)
{
if (selectedWord.wordValue.charAt(x) == letterVal)
{
wordSpacesArray.get(x).setText(String.valueOf(letterVal));
}
}
checkWin();
triesLeft--;
triesLeftLabel.setText(String.valueOf(triesLeft));
}
// finds if all jlabels are complete or not
public void checkWin()
{
for (int x = 0; x < wordSpacesArray.size(); x++)
{
String charVal = wordSpacesArray.get(x).getText();
if (charVal.equals("?"))
{
// There is still an incomplete label
return;
}
}
// When it reaches this line, no incomplete label was found
System.out.println("You won");
}
}

Selecting buttons at random in java

How can I randomly select a java button, I am trying to create a tic-tac-toe game where the user plays the cpu or another player. I have it working fine for 2 players but am stuck on 1 player game, I dont know if it can be done but my idea is that I just randomly select a button for the cpu check to see if its been selected previously and then assign the appropiate x or o to the selected square .
public void buttonSelected(ActionEvent click) {
Object source = click.getSource();
// loop through to see which button has been selected
if(onePlayer){
// User Vs CPU
/*if((turn % 2 == 0)){// CPU Turn
int selected;
do {
selected = new Random().nextInt(btnEmpty.length );
if (chosen[selected -1] == false){
chosen[selected -1] = true;
}
}while (chosen[selected -1] == true);
source = Integer.valueOf(selected);
for(int i=1; i<= btnNotSelected.length; i++) {
if(source == btnNotSelected[i] && turn < 10) {
btnClicked = true; // user has selected a button
// Check which user selected button and insert appropriate x or y
btnNotSelected[i].setText("X");
btnNotSelected[i].setEnabled(false); // disable selected button
pnlPlayingField.requestFocus(); // highlight selected panel
}
}
}
else{ //User Turn
for(int i=1; i<=9; i++) {
if(source == btnNotSelected[i] && turn < 10) {
btnClicked = true; // user has selected a button
// Check which user selected button and insert O
btnNotSelected[i].setText("O");
btnNotSelected[i].setEnabled(false);
chosen[i] = true;// disable selected button
pnlPlayingField.requestFocus(); // highlight selected panel
}
}
} */
turn++;
}
else if(twoPlayer){
for(int i=1; i<=9; i++) {
if(source == btnNotSelected[i] && turn < 10) {
btnClicked = true; // user has selected a button
// Check which user selected button and insert appropriate x or y
if(turn % 2 == 0){
btnNotSelected[i].setText("X");
}
else{
btnNotSelected[i].setText("O");
}
btnNotSelected[i].setEnabled(false); // disable selected button
pnlPlayingField.requestFocus(); // highlight selected panel
turn++;
}`
A one player tic-tac-toe game can certainly be done, and your strategy of selecting at random is fine. The first specific error in your commented out one player code is an infinite do-while loop. The condition on the loop always evaluates to true since chosen[selected - 1] is always true (if it is false, you set it to true right before the condition check), and therefore loops again.
Your do-while should look like this:
do {
selected = new Random().nextInt(btnEmpty.length);
} while (chosen[selected - 1] == true);
chosen[selected - 1] = true;
That way, you are setting the chosen flag after the while loop condition.
A couple of additional issues I see with the onePlayer block:
in the CPU turn block, the comparison between source (here an Integer) and btnNotSelected[i] (assuming a java button based on the working code in the twoPlayer block) will not work as you expect it to
this method is called in response to a user input, which is clicking one of the buttons. The computer will not provide any such input, so you should have another trigger that calls running the code for the computer's turn. The easiest one is just to run it after the user's turn
Without making any drastic changes to your overall coding style and strategy, I'll attempt to translate the onePlayer portion into something more functional:
public void buttonSelected(ActionEvent click) {
Object source = click.getSource();
if (onePlayer) {
// User's turn first
source.setText("O");
source.setEnabled(false);
pnlPlayingField.requestFocus();
// Mark that button as chosen
for (int i = 0; i < btnNotSelected.length; i++) {
if (source == btnNotSelected[i]) {
chosen[i] = true;
break;
}
}
// Increment turn counter
turn++;
// Check if game is over
if (turn > 9) return;
// CPU turn
int selected;
do {
selected = new Random().nextInt(btnNotSelected.length);
} while (chosen[selected]);
chosen[selected] = true;
btnNotSelected[selected].setText("X");
btnNotSelected[selected].setEnabled(false);
pnlPlayingField.requestFocus();
turn++;
} else if (twoPlayer) {
/* your preexisting twoPlayer code */
}
}
int selected;
do {
selected = new Random().nextInt(btnEmpty.length );
if (chosen[selected -1] == false){
chosen[selected -1] = true;
}
}while (chosen[selected -1] == true);
code above is an endless loop, change it to:
int selected;
do {
selected = new Random().nextInt(btnEmpty.length);
}while (chosen[selected] == true);
chosen[selected] == true;
remove -1, because nextInt(n) will give you a number "between 0 (inclusive) and n (exclusive)"
I, personally, would start out with a List of the buttons, removing each on as it become selected, this would make easier to determine what has being selected and what hasn't, but lets work with what we've got...
List<Integer> free = new ArrayList<Integer>(chosen.length);
for (int index = 0; index < chosen.length; index++) {
if (!chosen[index]) {
free.add(index);
}
}
if (!free.isEmpty()) {
Collections.shuffle(free);
int selected = free.get(0);
// Continue with game logic
}
Basically, this places an Integer in a List which represents the "free" slots. We then use Collections.shuffle to randomise the list, then we grab the first element (for want of something to grab) and continue with the game logic...
This eliminates the possibility of a infinite loop trying to find free slots that don't exist...

Categories