Java Snake Bug: It happens when change direction quickly - java

In my Snake code, if the snake is moving LEFT currently, and I then press either UP or DOWN, and then very quickly press RIGHT, the game is over. I knew the reason why it happened, but I don't know how to fix it. Maybe I should announce a new variable for last direction and then compare it with the new direction to decide if the snake should turn to the new direction? I don't know how to fix it. Can somebody help me to solve the problem ? :-)
Here is the relevant code.
//Judge the keyboard input
public void changeDirection(Direction newDirection) {
if (snakeDirection.compatibleWith(newDirection)) {
snakeDirection = newDirection;
}
Direction.java
public enum Direction {
UP(0),
RIGHT(1),
DOWN(2),
LEFT(3);
private final int directionCode;
Direction(int directionCode) {
this.directionCode = directionCode;
}
private int directionCode() {
return directionCode;
}
//
public boolean compatibleWith(Direction direction) {
return (this.directionCode() + direction.directionCode()) % 2 == 1;
}
}
GameController.java
public class GameController implements Runnable, KeyListener {
private final Grid grid;
private final GameView gameView;
boolean running;
public GameController(Grid grid, GameView gameView) {
this.grid = grid;
this.gameView = gameView;
this.running = true;
}
#Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
switch (keyCode) {
case KeyEvent.VK_UP:
grid.changeDirection(Direction.UP);
break;
case KeyEvent.VK_DOWN:
grid.changeDirection(Direction.DOWN);
break;
case KeyEvent.VK_LEFT:
grid.changeDirection(Direction.LEFT);
break;
case KeyEvent.VK_RIGHT:
grid.changeDirection(Direction.RIGHT);
break;
case KeyEvent.VK_ENTER:
if (running == false) {
grid.init();
running = true;
new Thread(this).start();
}
break;
case KeyEvent.VK_SPACE:
if (running) {
running = false;
} else {
running = true;
new Thread(this).start();
}
break;
default:
break;
}
}
// the Thread
public void run() {
while (running) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
break;
}
// your code here
if (grid.nextRound()) {
gameView.draw();
} else {
gameView.showGameOverMessage();
break;
}
}
running = false;
}

You really need a new variable. Your snakeDirection is the direction you want to use in the next time step and comparing it with newDirection only helps when it has been used. You need a lastUsedDirection set after a step was done.
Some comments
You can replace directionCode by ordinal().
You should start only a single thread and let it skip the action when running=false. Use the constructor allowing to start it with deamon=true, so that you can terminate the game without it terminating first. Don't let it terminate. You current code risks having multiple threads running when you press SPACE multiple times fast enough.
Be careful with you variable used in multiple threads. As long as one thread only reads the variable and the other one only writes it, it's enough to use volatile. You probably don't even do this and then it can happen that one thread never sees the writes made by another one (this is no theory; this really happens, maybe not in your case).

Just check the x and y positon of the head and the "neck".
What I mean is:
neck
|
| xxxx
| x <--- The snake
HNxx
|
|
head
If the position is the same in one axis, you should not turn.
Here's an actual example (it's C++, but Java has similar syntax, so this shouldn't be a problem).
void Snake::setDirection(char dir) {
switch (direction) {
case Snake::NORTH:
if (dir == Snake::SOUTH) return;
if (body.size() > 1 && body[0]->y == body[1]->y) return;
break;
case Snake::SOUTH:
if (dir == Snake::NORTH) return;
if (body.size() > 1 && body[0]->y == body[1]->y) return;
break;
case Snake::EAST:
if (dir == Snake::WEST) return;
if (body.size() > 1 && body[0]->x == body[1]->x) return;
break;
case Snake::WEST:
if (dir == Snake::EAST) return;
if (body.size() > 1 && body[0]->x == body[1]->x) return;
break;
}
direction = dir;
}
body is a std::vector that holds all the snake segments and direction is a Snake class variable.

Related

Android 2 finger touch triggering 1 finger touch as well as 2 finger touch

I have an issue in my app where I need to perform a different action based on if one fingers or two fingers are pressed down.
Currently I have the follow code:
#Override
public boolean onTouchEvent(MotionEvent event){
int touchX = (int)event.getX();
int touchY = (int)event.getY();
int action = event.getActionMasked();
//Multitouch
if(event.getPointerCount() > 1 && action == MotionEvent.ACTION_POINTER_DOWN){
System.out.println("2 Finger press");
return true;
}
else if(action == MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_POINTER_DOWN){
if(event.getPointerCount() == 1) {
System.out.println("single finger down");
invalidate();
return true;
}
}
return false;
}
The issue I am having is when I press with 2 fingers, the multitouch part registers, and after that so does the single press.
I did some googling around on ways to fix that and that would be why I am checking on the single touch conditional that action != MotionEvent.ACTION_POINTER_DOWN which I thought would fix the issue. That didn't fix the issue hence I decided to check that event.getPointerCount() == 1, but unfortunately that still causes both lines to print when I press with 2 fingers.
To summarize I need to only call the 2 finger part when 2 fingers are pressed down and not both.
refer to the documentation on "Handling Multi-Touch Gestures". it details exactly what you're looking for.
in essence, each active pointer is assigned an ID when it makes contact with the screen. your solution will should take into account which IDs come into play and are removed by observing various action states.
something a little bit along these lines (but beware compatibility calls for methods like getActionIndex()):
UPDATED CODE SAMPLE
public class MotionActivity extends AppCompatActivity {
private int primaryPointerId = -1;
private int secondaryPointerId = -1;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new FrameLayout(this));
}
#Override
public boolean onTouchEvent(MotionEvent event){
int action = event.getActionMasked();
switch(action) {
case MotionEvent.ACTION_DOWN:
primaryPointerId = event.getPointerId(0);
Log.d(getClass().getName(), "Primary pointer ID == [" + primaryPointerId + "]");
break;
case MotionEvent.ACTION_POINTER_DOWN:
secondaryPointerId = event.getPointerId(event.getActionIndex());
Log.d(getClass().getName(), "Secondary pointer ID == [" + secondaryPointerId + "]");
break;
case MotionEvent.ACTION_MOVE:
if(primaryPointerId > -1 && secondaryPointerId > -1) {
Log.d(getClass().getName(), "Two-point touch...");
} else {
Log.d(getClass().getName(), "One-point touch...");
}
break;
case MotionEvent.ACTION_POINTER_UP:
if(event.getPointerId(event.getActionIndex()) == primaryPointerId) {
primaryPointerId = secondaryPointerId;
}
secondaryPointerId = -1;
break;
case MotionEvent.ACTION_UP:
primaryPointerId = -1;
break;
}
return true;
}
}

When using KeyListener - 2 input happens

I am programing 2D rpg game and I have weird problem. When I use KeyPressed method it happens to call my move method twice which exits to rewrite player position from [10,10] to [10,12] insted of [10,11] and move my player 2 fields. I was looking around the internet and found nothing useful.
For displaying I use Canvas and Runnable with JFrame. A also use Thread
When I used debugger to search what's wrong I found that Keylistener use method
KeyPressed
public void keyPressed(KeyEvent e)
{
((KeyListener)a).keyPressed(e);
((KeyListener)b).keyPressed(e);
}
I believe that KeyListener a and b do the problem but I dont know hot to fix it.
My move method which change Point (x,y)
public void move() {
Point startPlayerPosition = player.getPosition();
Point newPoint = startPlayerPosition;
switch (direction) {
case Direction.NORTH:
newPoint = new Point(startPlayerPosition.x, startPlayerPosition.y - 1);
break;
case Direction.SOUTH:
newPoint = new Point(startPlayerPosition.x, startPlayerPosition.y + 1);
break;
case Direction.WEST:
newPoint = new Point(startPlayerPosition.x - 1, startPlayerPosition.y);
break;
case Direction.EAST:
newPoint = new Point(startPlayerPosition.x + 1, startPlayerPosition.y);
break;
}
playerPos = newPoint;
player.setPosition(newPoint);}
This is my KeyPressed method. I wait for pressing W,A,S,D to to change direction and then I call move() according to direction.
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_W:
playerWalk = true;
direction = Direction.NORTH;
move();
break;
case KeyEvent.VK_S:
playerWalk = true;
direction = Direction.SOUTH;
move();
break;
case KeyEvent.VK_D:
playerWalk = true;
direction = Direction.EAST;
move();
break;
case KeyEvent.VK_A:
playerWalk = true;
direction = Direction.WEST;
move();
break;
}
}
Thanks for Help

How to handle multi touch?

I'm making a platfrom game for android and I could use some help with the touch events.
This is my code:
public boolean onTouch(MotionEvent e, int scaledX, int scaledY) {
for (int i = 0; i < object.size(); i++) {
tempObject = object.get(i);
if (tempObject.getId() == ObjectId.Player) {
if (e.getAction() == MotionEvent.ACTION_MOVE) {
if (moveLeft.contains(scaledX, scaledY)) {
tempObject.setMovingLeft(true);
tempObject.setMovingRight(false);
}
if (moveLeftExit.contains(scaledX, scaledY)
&& !moveLeft.contains(scaledX, scaledY)) {
tempObject.setMovingLeft(false);
}
if (moveRight.contains(scaledX, scaledY)) {
tempObject.setMovingRight(true);
tempObject.setMovingLeft(false);
}
if (moveRightExit.contains(scaledX, scaledY)
&& !moveRight.contains(scaledX, scaledY)) {
tempObject.setMovingRight(false);
}
}
if (e.getAction() == MotionEvent.ACTION_UP
|| e.getAction() == MotionEvent.ACTION_OUTSIDE) {
if (moveLeft.contains(scaledX, scaledY)) {
tempObject.setMovingLeft(false);
}
if (moveRight.contains(scaledX, scaledY)) {
tempObject.setMovingRight(false);
}
}
if (e.getAction() == MotionEvent.ACTION_DOWN) {
if (jump.contains(scaledX, scaledY)) {
if(tempObject.getVelY() ==0)
tempObject.setVelY(-15);
}
}
}
}
return true;
}
Everything works great while I use one finger, if I touch the moveRight rectangle the character moves right and when I move my finger away he stopes as expected. The problem is that if I touch a button while touching some other button it wont react to it.
So I guess my question is, how can I modify my code so it will react to multi touch?
Thanks!! :)
Here is a simple code.
public boolean onTouch(View v, MotionEvent event) {
int action = MotionEventCompat.getActionMasked(event);
int pointerIndex = MotionEventCompat.getActionIndex(event);
int x = (int)MotionEventCompat.getX(event,pointerIndex);
int y = (int)MotionEventCompat.getY(event,pointerIndex);
switch(action)
{
case MotionEvent.ACTION_DOWN:
//
// First finger was touched. Set "pointerIndex" to some value (lets say 0 or -1)
// Save "pointerIndex" corresponding to this touched object.
//
break;
case MotionEvent.ACTION_POINTER_DOWN:
//
// More finger touched when first finger is already touched.
// Save "pointerIndex" corresponding to this touched object.
//
break;
case MotionEvent.ACTION_MOVE:
//
// Finger with "pointerIndex" was moved.
//
break;
case MotionEvent.ACTION_UP:
//
// The first touched finger went off.
//
break;
case MotionEvent.ACTION_POINTER_UP:
//
// Finger with "pointerIndex" went off (other than first finger)
//
break;
}
return true;
}
Good Luck. :)

How to make a switch statement loop back to beginning

I have a JButton called nextAd that increments my private int i; plus 1 each time it is clicked, which shows different images on each click. After the button is clicked a fourth time, I want my GUI to loop back to when the button was clicked 0 times, showing what was initially on the screen when I first ran the program.
Question: How can I make my switch statement loop back to what was initially on my screen when I first launch my GUI program?
I've omitted the rest of my code except for the button clicked action. If more code is required in order to answer the question, please let me know.
private class buttonListener implements ActionListener
{
#Override
public void actionPerformed(ActionEvent e)
{
{
if(e.getSource() == nextAd)
{
i++;
}
switch (i){
case 1:
appleTitle.setVisible(false);
appleAdPic.setVisible(false);
appleLogo.setVisible(false);
IBMTitle.setVisible(true);
IBMAdPic.setVisible(true);
IBMLogo.setVisible(true);
break;
case 2:
IBMTitle.setVisible(false);
IBMAdPic.setVisible(false);
IBMLogo.setVisible(false);
microsoftTitle.setVisible(true);
microsoftAdPic.setVisible(true);
microsoftLogo.setVisible(true);
break;
case 3:
microsoftTitle.setVisible(false);
microsoftAdPic.setVisible(false);
microsoftLogo.setVisible(false);
samsungTitle.setVisible(true);
samsungAdPic.setVisible(true);
samsungLogo.setVisible(true);
break;
}
}
}
}
}
I think the easiest solution would be just to set the increment value to 0 at the end of actionPerformed() if i == 3;
public void actionPerformed(ActionEvent event) {
// ...
if (i == 3) {
i = 0; // Reset the ad counter to 0, to restart the ad loop
}
}
That way, the next time actionPerformed() is called, it will start at the beginning.
You could change the start of your event handler to:
if(e.getSource() == nextAd)
{
i = (i == 3 ? 1 : i + 1);
}
Can we not have a default case and inside you can set the so-called 0-times scenario ?
For e.g.:
default:
appleTitle.setVisible(false);
appleAdPic.setVisible(false);
appleLogo.setVisible(false);
IBMTitle.setVisible(false);
IBMAdPic.setVisible(false);
IBMLogo.setVisible(false);
break;
Probably the best way to do it would be to put the switch statement into a seperate method of return type void, much like
private class buttonListener implements ActionListener
{
#Override
public void actionPerformed(ActionEvent e)
{
{
if(e.getSource() == nextAd)
{
i++;
}
switching()
}
}
}
public static void switching() {
switch (i){
case 1:
appleTitle.setVisible(false);
appleAdPic.setVisible(false);
appleLogo.setVisible(false);
IBMTitle.setVisible(true);
IBMAdPic.setVisible(true);
IBMLogo.setVisible(true);
break;
case 2:
IBMTitle.setVisible(false);
IBMAdPic.setVisible(false);
IBMLogo.setVisible(false);
microsoftTitle.setVisible(true);
microsoftAdPic.setVisible(true);
microsoftLogo.setVisible(true);
break;
case 3:
microsoftTitle.setVisible(false);
microsoftAdPic.setVisible(false);
microsoftLogo.setVisible(false);
samsungTitle.setVisible(true);
samsungAdPic.setVisible(true);
samsungLogo.setVisible(true);
break;
}
case 4:
i = 0;
switching();
break;
}
Here is what I would do :
if (e.getSource() == nextAd) {
if (i >= 3) {
i = 1;
} else {
i++;
}
}
Here was my solution.
Thanks everyone!
if(e.getSource() == nextAd)
{
i++;
{
if(i == 5)
{
i = 0;
samsungTitle.setVisible(false);
samsungAdPic.setVisible(false);
samsungLogo.setVisible(false);
}
switch (i){
case 1:
appleTitle.setVisible(true);
appleAdPic.setVisible(true);
appleLogo.setVisible(true);
break;
case 2:
appleTitle.setVisible(false);
appleAdPic.setVisible(false);
appleLogo.setVisible(false);
IBMTitle.setVisible(true);
IBMAdPic.setVisible(true);
IBMLogo.setVisible(true);
break;
case 3:
IBMTitle.setVisible(false);
IBMAdPic.setVisible(false);
IBMLogo.setVisible(false);
microsoftTitle.setVisible(true);
microsoftAdPic.setVisible(true);
microsoftLogo.setVisible(true);
break;
case 4:
microsoftTitle.setVisible(false);
microsoftAdPic.setVisible(false);
microsoftLogo.setVisible(false);
samsungTitle.setVisible(true);
samsungAdPic.setVisible(true);
samsungLogo.setVisible(true);
break;
}
}
}
}
}
}
}

Android MultiTouch only working for one finger

I am developing a two player Pong game and wish to control the bats, each with one finger. However, my problem is that it only responds to one finger being on the screen at a time. The entire game is ran in one view. First, I check if a pointer is "active" before checking if it occured on the top or bottom of the screen before moving the corresponding bat. It works normally, except for that only one touch registers. My code for the method is below:
// variables
int _touchY = (int) event.getY();
boolean isTopSide = true;
// test if motionEvent was on the top or bottom of the screen
if (SCREEN_HEIGHT / 2 - _touchY < 0) {
isTopSide = false;
}
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
_activePointer = event.getPointerId(0);
break;
case MotionEvent.ACTION_MOVE:
if(_activePointer != INVALID_POINTER_ID) {
if (isTopSide) { // top bat
_topBat.moveBat(event);
} else { // bottom bat
_botBat.moveBat(event);
}
}
if(_newPointer != INVALID_POINTER_ID){
if (isTopSide) { // top bat
_topBat.moveBat(event);
} else { // bottom bat
_botBat.moveBat(event);
}
}
break;
case MotionEvent.ACTION_UP:
if(_activePointer != INVALID_POINTER_ID){
_activePointer = INVALID_POINTER_ID;
}
break;
case MotionEvent.ACTION_CANCEL:
_activePointer = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:
int pointerIndex = event.getActionIndex();
int pointerId = event.getPointerId(pointerIndex);
if(pointerId == _activePointer){
_activePointer = INVALID_POINTER_ID;
}
case MotionEvent.ACTION_POINTER_DOWN:
int newPointerIndex = event.getActionIndex();
_newPointer = event.getPointerId(newPointerIndex);
break;
}
Relevant global code
// touched
private static final int INVALID_POINTER_ID = -1;
private int _activePointer = INVALID_POINTER_ID;
private int _newPointer = INVALID_POINTER_ID;
The main problem why you're only seeing values for a single pointer is because event.getY() without any parameter gives you the position for the first pointer index, even if the pointer that actually moved was the second pointer.
The MotionEvent class contains the coordinates for all the active pointers.
If you want to get the second pointer's position, you'll want to call event.getY(1).
You'll probably want to loop through all the active points, getting the total count via event.getPointerCount(), and then send the events accordingly.

Categories

Resources