My restart button of the game does not work and it multiples when it is clicked. I do not understand Java perfectly I am considering myself good.
Main of the game
package snake_game;
public class snake {
public static void main(String arg[]) {
new GameFrame();
// is exacly the same as frame f = new frame();
// this is shorter and does the same job
}
}
GamePanel
package snake_game;
// import java.awt.event.ActionEvent;
// import java.awt.event.ActionListener;
// import java.awt.Graphics;
// import java.awt.event.KeyAdapter;
// import java.awt.event.KeyEvent;
// or I could write simply
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Random;
import javax.swing.JPanel;
public class GamePanel extends JPanel implements ActionListener {
// panal dimentions
static final int SCREEN_WIDTH = 600;
static final int SCREEN_HEIGHT = 600;
// panal dimentions
// size
static final int UNIT_SIZE = 25;
// size to make 600 * 600 = 1200 px equel between 25 px
static final int GAME_UNITS = (SCREEN_WIDTH * SCREEN_HEIGHT) / UNIT_SIZE;
// size
// delay how fast the game will be
static final int DELAY = 75;
// delay
// dimentions
final int x[] = new int[GAME_UNITS];
final int y[] = new int[GAME_UNITS];
// dimentions
// snake
int bodyParts = 6;
// snake
// apple
int appleEaten;
int appleX;
int appleY;
// apple
char direction = 'R';
boolean running = false;
Timer timer;
Random random;
GamePanel game;
JButton resetButton;
GamePanel() {
random = new Random();
this.setPreferredSize(new Dimension(SCREEN_WIDTH, SCREEN_HEIGHT));
this.setBackground(Color.black);
this.setFocusable(true);
this.addKeyListener(new MyKeyAdapter());
startGame();
// when we want to make the program to continie we must say what the programm
// must execute next
}
public void startGame() {
newApple();
running = true;
timer = new Timer(DELAY, this);
timer.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
public void draw(Graphics g) {
if (running) {
for (int i = 0; i < SCREEN_HEIGHT / UNIT_SIZE; i++) {
g.drawLine(i * UNIT_SIZE, 0, i * UNIT_SIZE, SCREEN_HEIGHT);
g.drawLine(0, i * UNIT_SIZE, i * SCREEN_WIDTH, i * UNIT_SIZE);
}
g.setColor(Color.red);
g.fillOval(appleX, appleY, UNIT_SIZE, UNIT_SIZE);
for (int i = 0; i < bodyParts; i++) {
if (i == 0) {
g.setColor(Color.green);
g.fillRect(x[i], y[i], UNIT_SIZE, UNIT_SIZE);
} else {
g.setColor(new Color(45, 180, 0));
// random color
g.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
// random color
g.fillRect(x[i], y[i], UNIT_SIZE, UNIT_SIZE);
}
}
g.setColor(Color.red);
g.setFont(new Font("Ink Free", Font.BOLD, 30));
FontMetrics metrics = getFontMetrics(g.getFont());
g.drawString("SCORE:" + appleEaten, (SCREEN_WIDTH - metrics.stringWidth("SCORE:" + appleEaten)) / 2,
g.getFont().getSize());
} else {
gameOver(g);
}
}
public void newApple() {
appleX = random.nextInt((int) (SCREEN_WIDTH / UNIT_SIZE)) * UNIT_SIZE;
appleY = random.nextInt((int) (SCREEN_HEIGHT / UNIT_SIZE)) * UNIT_SIZE;
}
public void move() {
for (int i = bodyParts; i > 0; i--) {
x[i] = x[i - 1];
y[i] = y[i - 1];
}
switch (direction) {
case 'U':
y[0] = y[0] - UNIT_SIZE;
break;
case 'D':
y[0] = y[0] + UNIT_SIZE;
break;
case 'L':
x[0] = x[0] - UNIT_SIZE;
break;
case 'R':
x[0] = x[0] + UNIT_SIZE;
break;
}
}
public void checkApple() {
if ((x[0] == appleX) && (y[0] == appleY)) {
bodyParts++;
appleEaten++;
newApple();
}
}
public void checkCollisions() {
// check if head collides with body
for (int i = bodyParts; i > 0; i--) {
if ((x[0] == x[i]) && (y[0] == y[i])) {
running = false;
}
}
// check if head touches left border
if (x[0] < 0) {
running = false;
}
// check if head touches right border
if (x[0] > SCREEN_WIDTH) {
running = false;
}
// check if head touches top border
if (y[0] < 0) {
running = false;
}
// check if head touches bottom border
if (y[0] > SCREEN_HEIGHT) {
running = false;
}
if (!running) {
timer.stop();
}
}
public void gameOver(Graphics g) {
// score
g.setColor(Color.red);
g.setFont(new Font("Ink Free", Font.BOLD, 30));
FontMetrics metrics1 = getFontMetrics(g.getFont());
g.drawString("SCORE:" + appleEaten, (SCREEN_WIDTH - metrics1.stringWidth("SCORE:" + appleEaten)) / 2,
g.getFont().getSize());
// game over text
g.setColor(Color.red);
g.setFont(new Font("Ink Free", Font.BOLD, 75));
FontMetrics metrics2 = getFontMetrics(g.getFont());
g.drawString("Game Over", (SCREEN_WIDTH - metrics2.stringWidth("Game Over")) / 2, SCREEN_HEIGHT / 2);
// restart button
resetButton = new JButton();
resetButton.setText("Restart");
resetButton.setSize(100, 50);
resetButton.setLocation(150, 150);
resetButton.addActionListener(this);
game = new GamePanel();
this.add(resetButton);
this.add(game);
this.setVisible(true);
// restart button
}
#Override
public void actionPerformed(ActionEvent e) {
if (running) {
move();
checkApple();
checkCollisions();
}
repaint();
// restart button
if (e.getSource() == resetButton) {
this.remove(game);
game = new GamePanel();
this.add(game);
resetButton.setVisible(false);
SwingUtilities.updateComponentTreeUI(this);
// restart button
}
}
public class MyKeyAdapter extends KeyAdapter {
#Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_LEFT:
if (direction != 'R') {
direction = 'L';
}
break;
case KeyEvent.VK_RIGHT:
if (direction != 'L') {
direction = 'R';
}
break;
case KeyEvent.VK_UP:
if (direction != 'D') {
direction = 'U';
}
break;
case KeyEvent.VK_DOWN:
if (direction != 'U') {
direction = 'D';
}
break;
}
}
}
}
GameFrame
package snake_game;
import javax.swing.JFrame;
public class GameFrame extends JFrame {
GameFrame() {
GamePanel panel = new GamePanel();
this.add(panel);
this.setTitle("Snake");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
this.pack();
this.setVisible(true);
this.setLocationRelativeTo(null);
this.setUndecorated(false);
}
}
Introduction
I copied your code into my Eclipse IDE and ran it as is. I received the following runtime error.
Exception in thread "main" java.awt.IllegalComponentStateException: The frame is displayable.
at java.desktop/java.awt.Frame.setUndecorated(Frame.java:926)
at com.ggl.testing.SnakeGame$GameFrame.<init>(SnakeGame.java:41)
at com.ggl.testing.SnakeGame.main(SnakeGame.java:24)
Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Learning Swing with the NetBeans IDE section. Pay close attention to the Concurrency in Swing section.
I've been writing Swing code for over 10 years, and I have the Oracle website bookmarked in my browser. I still look up how to use certain components to make sure I'm using them correctly.
The first thing I did was to slow down your snake so I could test the game. I changed the delay of 75 to a delay of 750. Here's a screenshot of your current GUI.
Looking over your code, you extend a JFrame. You don't need to extend a JFrame. You're not changing any JFrame functionality. It's much simpler to use a JFrame. This leads to one of my Java rules.
Don't extend a Swing component, or any Java class, unless you intend
to override one or more of the class methods.
You do extend a JPanel. That's fine because you override the paintComponent method.
Finally, your JPanel class is doing too much work. You also make heavy use of static fields. Even though you'll only create one JPanel, it's a good habit to treat every class as if you will create multiple instances of the class. This creates fewer problems for yourself down the road.
You have the right idea, creating three classes.
So let's rework your code, using some basic patterns and Swing best practices. At this time, I don't know how many classes we'll wind up creating.
Explanation
When I write a Swing GUI, I use the model–view–controller (MVC) pattern. The name implies that you create a model first, then the view, then the controller(s).
An application model consists of one or more plain Java getter/setter classes.
A view consists of a JFrame, one or more JPanels, and any other necessary Swing components.
The controller consists of one or more Actions or ActionListeners. In Swing, there's usually not one controller to "rule them all".
To summarize:
The view reads information from the model
The view does not update the model
The controllers update the model and repaint/revalidate your view.
Model
I created two model classes, SnakeModel and Snake.
The SnakeModel class is a plain Java getter/setter class that holds one Snake instance, the apple eaten count, the apple location, the size of the game area, and a couple of booleans. One boolean indicates whether or not the game loop is running and the other boolean indicates whether or not the game is over.
The game area uses a java.awt.Dimension to hold the width and height of the game area. The width and the height do not have to have the same value. The game area can be rectangular.
The game area is measured in units. In the view, I convert the units into pixels. That's the opposite of what you did. If you want to change the game area, all you have to do is change the dimensions in the SnakeModel class. Everything in the view is based on the game area dimension.
The Snake class holds a java.util.List of java.awt.Point objects and a char direction. A java.awt.Point object holds an X and Y value. Since we're dealing with objects, rather than int values, we have to be careful to clone the object when we want a new Point.
View
All Swing applications must start with a call to the SwingUtilities invokeLater method. This method ensures that the Swing components are created and executed on the Event Dispatch Thread.
I created a JFrame, a drawing JPanel, and a separate button JPanel. Generally, it's not a good idea to add Swing components to a drawing JPanel. By creating a separate button JPanel, I get the added feature of a "Start Game" button at almost no extra cost. The button is disabled while the game is running.
The JFrame methods must be called in a specific order. The setVisible method must be called last.
I made the drawing JPanel more complicated by adding a separate area for the score.
I made the drawing JPanel less complicated by only drawing the state of the game, based on the application model. Period. Nothing else.
I limited the random colors to the white end of the color spectrum to maintain the contrast between the snake and the drawing JPanel background.
I used key bindings instead of a key listener. One advantage is that the drawing JPanel doesn't have to be in focus. Since I have a separate button JPanel, the drawing JPanel doesn't have focus.
Another advantage is that I can add the WASD keys with four lines of additional code.
One disadvantage is that the key bindings code looks more complicated than a key listener. Once you've coded a few key bindings, you'll appreciate the advantages.
Controller
I created three controller classes, ButtonListener, TimerListener, and MovementAction.
The ButtonListener class implements ActionListener. The ButtonListener class initializes the game model and restarts the timer.
The TimerListener class implements ActionListener. The TimerListener class is the game loop. This class moves the snake, checks to see if the apple is eaten, checks to see if the snake has moved outside the game area or touched itself, and repaints the drawing JPanel. I used your code as a model for the code in this class.
The MovementAction class extends AbstractAction. The AbstractAction class implements Action. This class changes the direction of the snake, based on the key presses.
I create four instances of the MovementAction class, one for each direction. This makes the actionPerformed method of the class much simpler.
Images
Here's what the revised GUI looks like when you start the game.
Here's the revised GUI during the game.
Here's the revised GUI when the game is over.
Code
Here's the complete runnable code. I made all the additional classes inner classes so I could post this code as one block.
You should put the separate classes in separate files.
When setting up a Swing GUI project, I create separate packages for the model, view, and controller. This helps me keep the code organized.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class SnakeGame implements Runnable {
public static void main(String arg[]) {
SwingUtilities.invokeLater(new SnakeGame());
}
private final GamePanel gamePanel;
private final JButton restartButton;
private final SnakeModel model;
public SnakeGame() {
this.model = new SnakeModel();
this.restartButton = new JButton("Start Game");
this.gamePanel = new GamePanel(model);
}
#Override
public void run() {
JFrame frame = new JFrame("Snake");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(gamePanel, BorderLayout.CENTER);
frame.add(createButtonPanel(), BorderLayout.SOUTH);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
private JPanel createButtonPanel() {
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
panel.setBackground(Color.black);
restartButton.addActionListener(new ButtonListener(this, model));
panel.add(restartButton);
return panel;
}
public JButton getRestartButton() {
return restartButton;
}
public void repaint() {
gamePanel.repaint();
}
public class GamePanel extends JPanel {
private static final long serialVersionUID = 1L;
private final int margin, scoreAreaHeight, unitSize;
private final Random random;
private final SnakeModel model;
public GamePanel(SnakeModel model) {
this.model = model;
this.margin = 10;
this.unitSize = 25;
this.scoreAreaHeight = 36 + margin;
this.random = new Random();
this.setBackground(Color.black);
Dimension gameArea = model.getGameArea();
int width = gameArea.width * unitSize + 2 * margin;
int height = gameArea.height * unitSize + 2 * margin + scoreAreaHeight;
this.setPreferredSize(new Dimension(width, height));
setKeyBindings();
}
private void setKeyBindings() {
InputMap inputMap = this.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = this.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "up");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "down");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "right");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0), "up");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0), "down");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), "left");
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, 0), "right");
actionMap.put("up", new MovementAction(model, 'U', 'D'));
actionMap.put("down", new MovementAction(model, 'D', 'U'));
actionMap.put("left", new MovementAction(model, 'L', 'R'));
actionMap.put("right", new MovementAction(model, 'R', 'L'));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Dimension gameArea = model.getGameArea();
drawHorizontalGridLines(g, gameArea);
drawVerticalGridLines(g, gameArea);
drawSnake(g);
drawScore(g, gameArea);
if (model.isGameOver) {
drawGameOver(g, gameArea);
} else {
drawApple(g);
}
}
private void drawHorizontalGridLines(Graphics g, Dimension gameArea) {
int y1 = scoreAreaHeight + margin;
int y2 = y1 + gameArea.height * unitSize;
int x = margin;
for (int index = 0; index <= gameArea.width; index++) {
g.drawLine(x, y1, x, y2);
x += unitSize;
}
}
private void drawVerticalGridLines(Graphics g, Dimension gameArea) {
int x1 = margin;
int x2 = x1 + gameArea.width * unitSize;
int y = margin + scoreAreaHeight;
for (int index = 0; index <= gameArea.height; index++) {
g.drawLine(x1, y, x2, y);
y += unitSize;
}
}
private void drawApple(Graphics g) {
// Draw apple
g.setColor(Color.red);
Point point = model.getAppleLocation();
if (point != null) {
int a = point.x * unitSize + margin + 1;
int b = point.y * unitSize + margin + scoreAreaHeight + 1;
g.fillOval(a, b, unitSize - 2, unitSize - 2);
}
}
private void drawScore(Graphics g, Dimension gameArea) {
g.setColor(Color.red);
g.setFont(new Font("Ink Free", Font.BOLD, 36));
FontMetrics metrics = getFontMetrics(g.getFont());
int width = 2 * margin + gameArea.width * unitSize;
String text = "SCORE: " + model.getApplesEaten();
int textWidth = metrics.stringWidth(text);
g.drawString(text, (width - textWidth) / 2, g.getFont().getSize());
}
private void drawSnake(Graphics g) {
// Draw snake
Snake snake = model.getSnake();
List<Point> cells = snake.getCells();
Point cell = cells.get(0);
drawSnakeCell(g, cell, Color.green);
for (int index = 1; index < cells.size(); index++) {
// Color color = new Color(45, 180, 0);
// random color
Color color = new Color(getColorValue(), getColorValue(),
getColorValue());
cell = cells.get(index);
drawSnakeCell(g, cell, color);
}
}
private void drawSnakeCell(Graphics g, Point point, Color color) {
int x = margin + point.x * unitSize;
int y = margin + scoreAreaHeight + point.y * unitSize;
if (point.y >= 0) {
g.setColor(color);
g.fillRect(x, y, unitSize, unitSize);
}
}
private int getColorValue() {
// White has color values of 255
return random.nextInt(64) + 191;
}
private void drawGameOver(Graphics g, Dimension gameArea) {
g.setColor(Color.red);
g.setFont(new Font("Ink Free", Font.BOLD, 72));
FontMetrics metrics = getFontMetrics(g.getFont());
String text = "Game Over";
int textWidth = metrics.stringWidth(text);
g.drawString(text, (getWidth() - textWidth) / 2, getHeight() / 2);
}
}
public class ButtonListener implements ActionListener {
private final int delay;
private final SnakeGame view;
private final SnakeModel model;
private final Timer timer;
public ButtonListener(SnakeGame view, SnakeModel model) {
this.view = view;
this.model = model;
this.delay = 750;
this.timer = new Timer(delay, new TimerListener(view, model));
}
#Override
public void actionPerformed(ActionEvent event) {
JButton button = (JButton) event.getSource();
String text = button.getText();
if (text.equals("Start Game")) {
button.setText("Restart Game");
}
button.setEnabled(false);
model.initialize();
timer.restart();
}
}
public class TimerListener implements ActionListener {
private final SnakeGame view;
private final SnakeModel model;
public TimerListener(SnakeGame view, SnakeModel model) {
this.view = view;
this.model = model;
}
#Override
public void actionPerformed(ActionEvent event) {
moveSnake();
checkApple();
model.checkCollisions();
if (model.isGameOver()) {
Timer timer = (Timer) event.getSource();
timer.stop();
model.setRunning(false);
view.getRestartButton().setEnabled(true);
}
view.repaint();
}
private void moveSnake() {
Snake snake = model.getSnake();
Point head = (Point) snake.getHead().clone();
switch (snake.getDirection()) {
case 'U':
head.y--;
break;
case 'D':
head.y++;
break;
case 'L':
head.x--;
break;
case 'R':
head.x++;
break;
}
snake.removeTail();
snake.addHead(head);
// System.out.println(Arrays.toString(cells.toArray()));
}
private void checkApple() {
Point appleLocation = model.getAppleLocation();
Snake snake = model.getSnake();
Point head = snake.getHead();
Point tail = (Point) snake.getTail().clone();
if (head.x == appleLocation.x && head.y == appleLocation.y) {
model.incrementApplesEaten();
snake.addTail(tail);
model.generateRandomAppleLocation();
}
}
}
public class MovementAction extends AbstractAction {
private static final long serialVersionUID = 1L;
private final char newDirection, oppositeDirection;
private final SnakeModel model;
public MovementAction(SnakeModel model, char newDirection,
char oppositeDirection) {
this.model = model;
this.newDirection = newDirection;
this.oppositeDirection = oppositeDirection;
}
#Override
public void actionPerformed(ActionEvent event) {
if (model.isRunning()) {
Snake snake = model.getSnake();
char direction = snake.getDirection();
if (direction != oppositeDirection && direction != newDirection) {
snake.setDirection(newDirection);
// System.out.println("New direction: " + newDirection);
}
}
}
}
public class SnakeModel {
private boolean isGameOver, isRunning;
private int applesEaten;
private Dimension gameArea;
private Point appleLocation;
private Random random;
private Snake snake;
public SnakeModel() {
this.random = new Random();
this.snake = new Snake();
this.gameArea = new Dimension(24, 24);
}
public void initialize() {
this.isRunning = true;
this.isGameOver = false;
this.snake.initialize();
this.applesEaten = 0;
Point point = generateRandomAppleLocation();
// Make sure first apple isn't under snake
int y = (point.y == 0) ? 1 : point.y;
this.appleLocation = new Point(point.x, y);
}
public void checkCollisions() {
Point head = snake.getHead();
// Check for snake going out of the game area
if (head.x < 0 || head.x > gameArea.width) {
isGameOver = true;
return;
}
if (head.y < 0 || head.y > gameArea.height) {
isGameOver = true;
return;
}
// Check for snake touching itself
List<Point> cells = snake.getCells();
for (int index = 1; index < cells.size(); index++) {
Point cell = cells.get(index);
if (head.x == cell.x && head.y == cell.y) {
isGameOver = true;
return;
}
}
}
public Point generateRandomAppleLocation() {
int x = random.nextInt(gameArea.width);
int y = random.nextInt(gameArea.height);
this.appleLocation = new Point(x, y);
return getAppleLocation();
}
public void incrementApplesEaten() {
this.applesEaten++;
}
public boolean isRunning() {
return isRunning;
}
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
public boolean isGameOver() {
return isGameOver;
}
public void setGameOver(boolean isGameOver) {
this.isGameOver = isGameOver;
}
public Dimension getGameArea() {
return gameArea;
}
public int getApplesEaten() {
return applesEaten;
}
public Point getAppleLocation() {
return appleLocation;
}
public Snake getSnake() {
return snake;
}
}
public class Snake {
private char direction;
private List<Point> cells;
public Snake() {
this.cells = new ArrayList<>();
initialize();
}
public void initialize() {
this.direction = 'R';
cells.clear();
for (int x = 5; x >= 0; x--) {
cells.add(new Point(x, 0));
}
}
public void addHead(Point head) {
cells.add(0, head);
}
public void addTail(Point tail) {
cells.add(tail);
}
public void removeTail() {
cells.remove(cells.size() - 1);
}
public Point getHead() {
return cells.get(0);
}
public Point getTail() {
return cells.get(cells.size() - 1);
}
public char getDirection() {
return direction;
}
public void setDirection(char direction) {
this.direction = direction;
}
public List<Point> getCells() {
return cells;
}
}
}
Related
I am working on homework for class, and its late because I can't seem to understand the material despite all the research that I am doing. I am a beginner and do not know much in the way of java. Also, this is my first post so please be forgiving when you are reading this.
I am building on source code from my textbook, which I updated recently for past homework, but now I am trying to generate a class that draws multiple squares and moves those objects independently and at different speeds. They will all need to rebound off the walls as well. I followed the instructions and created two arrays that will hold the random x and y values between 1 and 10. However, I struggle with arrays and I am sure that I am not doing it correctly. So, I would love some feedback to see if I have it set up correctly.
I have a the jpanel pulling up and drawing, and as long as there is 1 square it is working fine bouncing off the walls, but things change when I draw more than one. The do not move independently and they also share the same speed. Some even disappear from time to time. This has really thrown me off. I appreciate any help!
In short, I am trying to paint new squares that all travel in different directions and at different speeds. Per the instructions we are suppose create and use a two arrays that handle the x and y values.
Here is what I have so far:
public class DotsPanel extends JPanel
{
private int delay = 15;
private final int SIZE = 7, IMAGE_SIZE = 3; // radius of each dot
private Timer timer;
private int x, y, i;
private ArrayList<Point> pointList;
static int [] xarray = new int [1000];
static int [] yarray = new int [1000];
Random rand = new Random();
//-----------------------------------------------------------------
// Constructor: Sets up this panel to listen for mouse events.
//-----------------------------------------------------------------
public DotsPanel()
{
pointList = new ArrayList<Point>();
int [] xarray = new int [1000];
int [] yarray = new int [1000];
timer = new Timer(delay, new ReboundListener());
addMouseListener (new DotsListener());
addMouseMotionListener (new DotsListener());
setBackground(Color.gray);
setPreferredSize(new Dimension(700, 500));
for(int i = 0; i < xarray.length; i++)
{
xarray[i] = rand.nextInt(7);
yarray[i] = rand.nextInt(7);
}
timer.start();
}
//-----------------------------------------------------------------
// Draws all of the dots stored in the list.
//-----------------------------------------------------------------
public void paintComponent(Graphics page)
{
super.paintComponent(page);
page.setColor(Color.BLUE);
for (Point spot : pointList)
{
page.fillRect(spot.x-SIZE, spot.y-SIZE, 25, 25);
page.drawString("Count: " + pointList.size(), 5, 15);
}
}
//*****************************************************************
// Represents the listener for mouse events.
//*****************************************************************
private class DotsListener implements MouseListener, MouseMotionListener
{
//--------------------------------------------------------------
// Adds the current point to the list of points and redraws
// the panel whenever the mouse button is pressed.
//--------------------------------------------------------------
public void mousePressed(MouseEvent event)
{
pointList.add(event.getPoint());
repaint();
}
public void mouseDragged(MouseEvent event)
{
// initially I had two xarray and yarray in here just like in
// mouseClicked
// but it did not change anything when removed
}
//--------------------------------------------------------------
// Provide empty definitions for unused event methods.
//--------------------------------------------------------------
public void mouseClicked(MouseEvent event)
{
xarray[i] = rand.nextInt(7);
yarray[i] = rand.nextInt(7);
}
public void mouseReleased(MouseEvent event) {}
public void mouseEntered(MouseEvent event) {}
public void mouseExited(MouseEvent event) {}
public void mouseMoved(MouseEvent e) {}
}
private class ReboundListener implements ActionListener
{
//--------------------------------------------------------------
// Updates the position of the image and possibly the direction
// of movement whenever the timer fires an action event.
//--------------------------------------------------------------
public void actionPerformed(ActionEvent event)
{
for (Point spot : pointList)
{
spot.x += xarray[i];
spot.y += yarray[i];
if (spot.x <= 0 || spot.x >= 700)
xarray[i] = xarray[i] * -1;
if (spot.y <= 0 || spot.y >= 500)
yarray[i] = yarray[i] * -1;
repaint();
}
}
}
}
However, I struggle with arrays and I am sure that I am not doing it correctly.
I wouldn't use Arrays.
Instead, have a Ball object manage its own state. Then you can have different color, speed, size etc for each Ball. Then when the Timer fires you just calculate the new position and repaint the Ball.
Here is an example to get you started:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
public class BallAnimation4
{
private static void createAndShowUI()
{
BallPanel panel = new BallPanel();
JFrame frame = new JFrame("BallAnimation4");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( panel );
frame.setSize(800, 600);
frame.setLocationRelativeTo( null );
//frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setVisible( true );
panel.addBalls(5);
panel.startAnimation();
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
class BallPanel extends JPanel implements ActionListener
{
private ArrayList<Ball> balls = new ArrayList<Ball>();
public BallPanel()
{
setLayout( null );
setBackground( Color.BLACK );
}
public void addBalls(int ballCount)
{
Random random = new Random();
for (int i = 0; i < ballCount; i++)
{
Ball ball = new Ball();
ball.setRandomColor(true);
ball.setLocation(random.nextInt(getWidth()), random.nextInt(getHeight()));
ball.setMoveRate(32, 32, 1, 1, true);
// ball.setMoveRate(16, 16, 1, 1, true);
ball.setSize(32, 32);
balls.add( ball );
}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
for (Ball ball: balls)
{
ball.draw(g);
}
}
public void startAnimation()
{
Timer timer = new Timer(75, this);
timer.start();
}
public void actionPerformed(ActionEvent e)
{
move();
repaint();
}
private void move()
{
for (Ball ball : balls)
{
ball.move(this);
}
}
class Ball
{
public Color color = Color.BLACK;
public int x = 0;
public int y = 0;
public int width = 1;
public int height = 1;
private int moveX = 1;
private int moveY = 1;
private int directionX = 1;
private int directionY = 1;
private int xScale = moveX;
private int yScale = moveY;
private boolean randomMove = false;
private boolean randomColor = false;
private Random myRand = null;
public Ball()
{
myRand = new Random();
setRandomColor(randomColor);
}
public void move(JPanel parent)
{
int iRight = parent.getSize().width;
int iBottom = parent.getSize().height;
x += 5 + (xScale * directionX);
y += 5 + (yScale * directionY);
if (x <= 0)
{
x = 0;
directionX *= (-1);
xScale = randomMove ? myRand.nextInt(moveX) : moveX;
if (randomColor) setRandomColor(randomColor);
}
if (x >= iRight - width)
{
x = iRight - width;
directionX *= (-1);
xScale = randomMove ? myRand.nextInt(moveX) : moveX;
if (randomColor) setRandomColor(randomColor);
}
if (y <= 0)
{
y = 0;
directionY *= (-1);
yScale = randomMove ? myRand.nextInt(moveY) : moveY;
if (randomColor) setRandomColor(randomColor);
}
if (y >= iBottom - height)
{
y = iBottom - height;
directionY *= (-1);
yScale = randomMove ? myRand.nextInt(moveY) : moveY;
if (randomColor) setRandomColor(randomColor);
}
}
public void draw(Graphics g)
{
g.setColor(color);
g.fillOval(x, y, width, height);
}
public void setColor(Color c)
{
color = c;
}
public void setLocation(int x, int y)
{
this.x = x;
this.y = y;
}
public void setMoveRate(int xMove, int yMove, int xDir, int yDir, boolean randMove)
{
this.moveX = xMove;
this.moveY = yMove;
directionX = xDir;
directionY = yDir;
randomMove = randMove;
}
public void setRandomColor(boolean randomColor)
{
this.randomColor = randomColor;
switch (myRand.nextInt(3))
{
case 0: color = Color.BLUE;
break;
case 1: color = Color.GREEN;
break;
case 2: color = Color.RED;
break;
default: color = Color.BLACK;
break;
}
}
public void setSize(int width, int height)
{
this.width = width;
this.height = height;
}
}
}
Since your Arrays only contain the Point you want to paint you don't have any information about the speed each point should be moved at. The best you could do is create a random amount each point should be moved each time its location is changed. This would give erratic movement as each time you move a point the distance would be random.
If you want more constant speed then you would need to create a second Array to contain the distance each point should move every time.
This starts to get messy creating a new Array every time you want a new property to be unique for the object you want to paint. That is why the approach to create a custom Object with multiple properties is easier to manage.
I created an app that contains a square that bounces every time it touches an edge of the frame.I don't have issues lunching the app,the problem is that i don't know how to create various threads in order to have multiples squares inside the frame.
I tried multiple things but i can't figure out where i should create the threads.
I also noticed that the square is visible only when i add it directly inside the frame and not when i put it inside a JPanel.
Square.java
public class Square extends JComponent implements ActionListener {
int width = 20;
int height = 20;
double y = Math.random() * 360;
double x = Math.random() * 360;
boolean xMax = false;
boolean yMax = false;
boolean xMin = true;
boolean yMin = true;
Rectangle2D.Double square = new Rectangle2D.Double(x, y, width, height);
public Square() {
Timer t = new Timer(2, this);
t.start();
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
super.paintComponent(g);
g2.setColor(Color.BLUE);
g2.fill(square);
x_y_rules();
}
public void x_y_rules() {
if (xMax == true) {
x = x - 0.5;
if (x <= 0) {
xMax = false;
}
} else {
x = x + 0.5;
if (x >= this.getWidth()) {
xMax = true;
}
}
if (yMax == true) {
y = y - 0.5;
if (y <= 0) {
yMax = false;
}
} else {
y = y + 0.5;
if (y >= this.getHeight()) {
yMax = true;
}
}
square.setFrame(x, y, width, height);
}
#Override
public void actionPerformed(ActionEvent arg0) {
repaint();
}
}
App.java
public class App extends JFrame {
public static void main(String[] args) {
JFrame jf = new JFrame();
Square sqr = new Square();
jf.setSize(400, 400);
jf.setVisible(true);
jf.add(sqr);
jf.setDefaultCloseOperation(EXIT_ON_CLOSE);
jf.setLocationRelativeTo(null);
}
}
Is it normal that despite i put a time of 2 inside the Timer,the square moves very slowly?
Issues:
you've got program logic, the x_y_rules() method call, inside of the paintComponent method. Get it out as it does not belong there, and instead put it into the Timer's ActionListener code where it belongs.
you can give each Square its own Swing Timer if you want. This isn't really a threading issue since each Timer's ActionListener will run on the EDT.
Two milliseconds is an unrealistic time slice to expect to use in a Swing Timer and no timer will run that fast. 11 to 13 is about the fastest to expect or hope for.
if you want your sprite to move faster, give it a greater value for delta-x and delta-y in your movement code.
Your JComponent has no preferred size defined which is likely why it's not showing up in the JPanel, since the default FlowLayout will size it then to [0, 0]. Override its getPreferredSize() and have it return a reasonable Dimension value.
you're calling setVisible(true) on your JFrame before adding all components, a no-no.
Ok,i put a getPrefferedSize() inside the square class but i've encountered a problem: the squares are not "together",it's like they're bouncing on separate panels
Then your program structure is broken. You really don't want create separate Swing components, and in fact your Square class shouldn't extend JComponent or JPanel. Rather
Square should be a logical class, one that extends from nothing (other than default Object).
Give it a drawing method, say public void draw(Graphics g) {....}
Create one class that extends JPanel, say called DrawingPanel, and override its paintComponent method.
Give the DrawingPanel class an ArrayList<Square> so that it can hold multiple Square objects.
Give the DrawingPanel class a Swing Timer
In the DrawingPanel class's Timer, have it update the position of all the Squares in the ArrayList, and then call repaint()
In the paintComponent method, iterate through all the Squares in the list, using a for loop, and call each one's draw method.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class DrawingPanel extends JPanel {
private static final int PREF_W = 600;
private static final int PREF_H = PREF_W;
private static final int TIMER_DELAY = 20;
private static final Color[] SQUARE_COLOR = { Color.BLUE, Color.CYAN, Color.DARK_GRAY,
Color.BLACK, Color.GRAY, Color.GREEN, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
Color.PINK, Color.RED, Color.YELLOW };
List<Square> squareList = new ArrayList<>();
public DrawingPanel() {
// create a bunch of squares
for (int i = 0; i < SQUARE_COLOR.length; i++) {
squareList.add(new Square(SQUARE_COLOR[i], PREF_W, PREF_H));
}
setBackground(Color.WHITE);
// create and start the timer
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// simply draw all the squares in the list
for (Square square : squareList) {
square.draw(g);
}
}
// set size of JPanel
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
// simply iterate through list and move all squares
for (Square square : squareList) {
square.move();
}
repaint(); // then repaint the GUI
}
}
private static void createAndShowGui() {
DrawingPanel mainPanel = new DrawingPanel();
JFrame frame = new JFrame("Drawing Panel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
// this class does *not* extend JPanel or JComponent
class Square {
public static final int WIDTH = 20;
// location of Square
private double sqrX;
private double sqrY;
// X and Y speed
private double deltaX;
private double deltaY;
// width and height of DrawingPanel JPanel
private int dpWidth;
private int dpHeight;
// image to draw
private Image image;
public Square(Color color, int dpWidth, int dpHeight) {
this.dpWidth = dpWidth;
this.dpHeight = dpHeight;
// create square at random location with random speed
sqrX = Math.random() * (dpWidth - WIDTH);
sqrY = Math.random() * (dpHeight - WIDTH);
deltaX = Math.random() * 10 - 5;
deltaY = Math.random() * 10 - 5;
// one way to draw it is to create an image and draw it
image = new BufferedImage(WIDTH, WIDTH, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
g.setColor(color);
g.fillRect(0, 0, WIDTH, WIDTH);
g.dispose();
}
public void move() {
// check that we're not hitting boundaries
if (sqrX + deltaX < 0) {
deltaX = Math.abs(deltaX);
}
if (sqrX + deltaX + WIDTH >= dpWidth) {
deltaX = -Math.abs(deltaX);
}
sqrX += deltaX;
// check that we're not hitting boundaries
if (sqrY + deltaY < 0) {
deltaY = Math.abs(deltaY);
}
if (sqrY + deltaY + WIDTH >= dpHeight) {
deltaY = -Math.abs(deltaY);
}
sqrY += deltaY;
}
public void draw(Graphics g) {
int x = (int) sqrX;
int y = (int) sqrY;
g.drawImage(image, x, y, null);
}
}
I'm making an java app (for exercise) where there must be a panel and 2 buttons.
Each time you press the start button a ball must be displayed and it moves based on thread. The user can display all the way up to 10 independent balls.
By pressing the stop button, 1 ball must be removed with each time the stop button is pressed (example, when there is 4 balls, the user must press 4 times stop button to remove all the balls independently)
All the x- and y coordinates of the balls must be stored in a Matrix
When 1 or more ball(s) are colliding with each other, only the colliding balls must change color from red to blue
Ok I'm almost done with it completely ( from point 1 to 4 ), but here comes my problem. When a ball is colliding with another, instead of changing the colors of the colliding balls to blue, my code is changing all the balls color from red to blue. I know that my error lies at the new Balls().setColor(Color.Blue), but I have no idea how to change only the colliding balls.
Below follows a screen shot of the java app and the code.
Can anyone help me with this headache?
Printscreen:
source code:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;`
public class BouncingBalls extends JPanel implements ActionListener {
protected List<Ball> balls = new ArrayList<Ball>(10);
private final Container container;
private final DrawCanvas canvas;
private int canvasWidth;
private int canvasHeight;
public JButton start, stop;
int [][] coords= new int[11][2];
int ammountOfBalls = 0;
static Color clr= Color.RED;
public static int random(int maxRange) {
return (int) Math.round((Math.random() * maxRange));
}
public BouncingBalls(int width, int height) {
setLayout(new FlowLayout());
start= new JButton("start");
start.addActionListener(this);
stop= new JButton("stop");
stop.addActionListener(this);
add(start);
add(stop);
add(new JLabel(""));
container = new Container();
canvasWidth = width;
canvasHeight = height;
canvas = new DrawCanvas();
this.setLayout(new FlowLayout());
this.add(canvas);
start();
}
public void start() {
Thread t = new Thread() {
#Override
public void run() {
while (true) {
update();
getPositions();
collisionDetection();
repaint();
try {
Thread.sleep(30);
} catch (InterruptedException e) {
}
}
}
private void collisionDetection() {
// The algorithm that detects collision
for(int i=0;i<ammountOfBalls;i++){
for(int j=i+1; j<ammountOfBalls; j++){
if(collisionMethod(i,j)){ // my collision method
//HOW DO I CHANGE ONLY THE COLLIDING BALLS COLOR HERE????
new Ball().setColor(Color.BLUE); // this line here changes the color of all the balls on the panel
System.out.println("Its a hit");
}
}
}
}
private void getPositions() {
int row=0;
for (Ball ball : balls) {
int x =ball.getXPosition();
int y =ball.getYPosition();
coords[row][0]=x;
coords[row][1]=y;
row++;
}
}
private boolean collisionMethod(int i, int j) {
float xd = coords[i][0]-coords[j][0];
float yd=coords[i][1]-coords[j][1];
float radius= new Ball().ballRadius;
float sqrRadius= radius * radius;
float distSqr= (xd * xd) + (yd * yd);
if(distSqr <= sqrRadius)
return true;
return false;
}
};
t.start();
}
public void update() {
for (Ball ball : balls) {
ball.move(container);
}
}
#Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == start){
if(ammountOfBalls < 10){
// to limit the ammount of balls to 10
balls.add(new Ball());
ammountOfBalls++;
}
}
else if( e.getSource() == stop){
if(ammountOfBalls > 0){
ammountOfBalls --;
balls.remove(ammountOfBalls);
}
}
}
class DrawCanvas extends JPanel {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
container.draw(g);
for (Ball ball : balls) {
ball.draw(g);
}
}
#Override
public Dimension getPreferredSize() {
return (new Dimension(700, 400));
}
}
public static void main(String[] args) {
JFrame f = new JFrame("Bouncing Balls");
f.setDefaultCloseOperation(f.EXIT_ON_CLOSE);
f.setPreferredSize(new Dimension(800,500));
f.setContentPane(new BouncingBalls(800,800));
f.pack();
f.setVisible(true);
}
public static class Ball {
public int random(int maxRange) {
return (int) Math.round(Math.random() * maxRange);
}
int x = random(700); // method to get random coords for the x-value
int y = random(400); // method to get random coords for the y-value ...... these are used to get random positions for the balls instead of only static
int xMovement = 10;
int yMovement = 10;
int ballRadius = 20;
int i = 0;
public Color getColor(){
return clr;
}
public Color setColor(Color color){
clr=color;
return clr;
}
public void draw(Graphics g) {
g.setColor(getColor());
g.fillOval(x,y,ballRadius,ballRadius);
}
public int getXPosition(){
return x;
}
public int getYPosition(){
return y;
}
public void move(Container container) {
x += xMovement;
y += yMovement;
if (x - ballRadius < 0) {
xMovement = -xMovement;
x = ballRadius;
}
else if (x + ballRadius > 700) {
xMovement = -xMovement;
x = 700 - ballRadius;
}
if (y - ballRadius < 0) {
yMovement = -yMovement;
y = ballRadius;
}
else if (y + ballRadius > 400) {
yMovement = -yMovement;
y = 400 - ballRadius;
}
}
}
public static class Container {
private static final int hightPanel = 800;
private static final int widthPanel= 800;
public void draw(Graphics g) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, widthPanel, hightPanel);
}
}
}
`
You have defined clr as a static field of your application. When your Ball class calls setColor() you are changing the value of clr to blue... and then any Ball that calls getColor() will see that clr is blue.
Solution: Don't make clr an application-wide static field. Define it in the Ball class as a non-static field, so each Ball has its own color.
im fairly new to java and am confused on how to do this. I have a key listener that listens for WASD which indicate movement of my Snake. The key listener changes the x and y positions of my Snakes segments. I have a timer linked to a listener called "Listener" that repaints the movements onto a buffer and onto the screen. My question is, why does the movement indicated by my key listener not make it to the buffer? Also, I know my move function works becuase snek.move(4); works in the timer. Final note, this is a Snake game i've barely begun.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
public class SnekePanel extends JPanel
{
private static final int FRAME1 = 1000;
private static final int FRAME2 = 1000;
private static final Color BACKGROUND = new Color(0, 0, 0);
private BufferedImage myImage;
private Graphics myBuffer;
private Sneke snek;
private Food food;
private Timer t;
private int points = 0;
public SnekePanel()
{
myImage = new BufferedImage(FRAME1, FRAME2, BufferedImage.TYPE_INT_RGB);
myBuffer = myImage.getGraphics();
myBuffer.setColor(BACKGROUND);
myBuffer.fillRect(0, 0, FRAME1,FRAME2);
int xPos = (int)(Math.random()*(FRAME1-100) + 50);
int yPos = (int)(Math.random()*(FRAME2-100)+ 50);
food = new Food(xPos, yPos, 10, Color.RED);
snek = new Sneke(200,200,1,Color.WHITE);
t = new Timer(5, new Listener());
t.start();
addKeyListener(new Key());
setFocusable(true);
}
public void paintComponent(Graphics g)
{
g.drawImage(myImage, 0, 0, getWidth(), getHeight(), null);
}
private class Key extends KeyAdapter
{
public void keyPressed(KeyEvent e)
{
if(e.getKeyCode() == KeyEvent.VK_W)
{
snek.move(1);
}
if(e.getKeyCode() == KeyEvent.VK_A)
{
snek.move(2);
}
if(e.getKeyCode() == KeyEvent.VK_S)
{
snek.move(3);
}
if(e.getKeyCode() == KeyEvent.VK_D)
{
snek.move(4);
}
}
}
private class Listener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
if(snek.checkBlock() != 0)
{
myBuffer.setColor(BACKGROUND);
myBuffer.fillRect(0,0,FRAME1,FRAME2);
snek.move(4);
collide(snek, food);
food.draw(myBuffer);
snek.draw(myBuffer);
myBuffer.setColor(Color.BLACK);
repaint();
}
}
}
private void collide(Sneke b, Food pd)
{
int sx = b.getX(snek.getLength()-1);
int sy = b.getY(snek.getLength()-1);
int fx = pd.getX();
int fy = pd.getY();
if(sx == sy && fx == fy)
{
snek.setLength(snek.getLength()+1);
}
}
}
why does the movement indicated by my key listener not make it to the buffer?
A more important question in my mind is: why do you think that it should make it to the buffer? You're only painting the buffer after calling snek.move(4) and so it appears that only that would make it into the buffer.
Myself, I'd do things differently, including (among other things)
I would create an int field -- or better a Direction enum that encapsulates up, down, left and right.
I'd give my GUI a field of this above, and I'd set it in the KeyListener.
I'd actually prefer using Key Bindings and not a KeyListener since it is much less dodgy when it comes to focus issues, but either could work.
In my Timer, I'd move the sprite based on the state of the value in the field
For example, try running this:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.LinkedList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class SnakePanel extends JPanel {
// size of the GUI
private static final int PREF_W = 1000;
private static final int PREF_H = 800;
// background and snake color
private static final Color BG = Color.BLACK;
private static final Color SNAKE_COLOR = Color.RED;
private static final int SEGMENT_WIDTH = 20;
// distance moved in each timer tick, and time between each tick
private static final int DELTA = 5;
private static final int TIMER_DELAY = 40; // in msecs
// number of segments in the worm
private static final int WORM_LENGTH = 80;
// initial direction
private Direction direction = Direction.RIGHT;
// initial point
private Point point = new Point(PREF_W / 2, PREF_H / 2);
// Snake is little more than a List of Points
private List<Point> snakePointList = new LinkedList<>();
public SnakePanel() {
// set background color
setBackground(BG);
// fill snake list with points
for (int i = 0; i < WORM_LENGTH; i++) {
snakePointList.add(new Point(point));
}
// set key bindings
setKeyBindings();
// create and start Timer
new Timer(TIMER_DELAY, new TimerListener()).start();
}
// set up our key bindings
private void setKeyBindings() {
int condition = WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition);
ActionMap actionMap = getActionMap();
KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0);
setKeyStroke(inputMap, actionMap, keyStroke, Direction.UP);
keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0);
setKeyStroke(inputMap, actionMap, keyStroke, Direction.DOWN);
keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0);
setKeyStroke(inputMap, actionMap, keyStroke, Direction.LEFT);
keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0);
setKeyStroke(inputMap, actionMap, keyStroke, Direction.RIGHT);
}
private void setKeyStroke(InputMap inputMap, ActionMap actionMap, KeyStroke keyStroke,
Direction dir) {
inputMap.put(keyStroke, keyStroke.toString());
actionMap.put(keyStroke.toString(), new MyKeyAction(dir));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// smooth out our graphics
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// draw each oval in the Snake
for (Point pt : snakePointList) {
drawPoint(g2, pt);
}
}
private void drawPoint(Graphics2D g2, Point pt) {
g2.setColor(SNAKE_COLOR);
// The pt is actually the center point
// so we need to draw an oval that is centered on this point
int x = pt.x - SEGMENT_WIDTH / 2;
int y = pt.y - SEGMENT_WIDTH / 2;
g2.drawOval(x, y, SEGMENT_WIDTH, SEGMENT_WIDTH);
}
// set gui's size
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
// Action used by key binding
private class MyKeyAction extends AbstractAction {
private Direction dir;
public MyKeyAction(Direction dir) {
this.dir = dir;
}
public void actionPerformed(ActionEvent e) {
// all it does is set the Direction direction enum field
// for this GUI
// the Timer then uses this field
SnakePanel.this.direction = dir;
};
}
// timer ActionListener
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
// create a new Point whose direction depends
// on the direction field * multiplier, DELTA
int x = point.x + direction.getX() * DELTA;
int y = point.y + direction.getY() * DELTA;
// create new point and add to snakePointList
point = new Point(x, y);
snakePointList.add(point);
// remove last point in list
snakePointList.remove(0);
repaint();
}
}
// Direction enum
enum Direction {
UP(0, -1), DOWN(0, 1), LEFT(-1, 0), RIGHT(1, 0);
private Direction(int x, int y) {
this.x = x;
this.y = y;
}
private int x;
private int y;
public int getX() {
return x;
}
public int getY() {
return y;
}
}
private static void createAndShowGui() {
SnakePanel mainPanel = new SnakePanel();
JFrame frame = new JFrame("SnakePanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
I got this really annoying error. It's really hard to explain, but basically whenever my snake tiles (I'm coding the game "Snake") leaves the screen, I set it so it returns to the same y, but the x as 0, as the x = 0 is the leftmost part of the screen...
So yeah, hard to explain. Here's the full code:
Main Class:
package com.Code0.Snake.Main;
import java.applet.Applet;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.util.LinkedList;
import java.util.Random;
import com.Code0.Snake.Listeners.KeyListenerS;
import com.Code0.Snake.Threads.EventS;
public class MainS extends Applet{
//Defining vars.
public final int BOX_HEIGHT = 15;
public final int BOX_WIDTH = 15;
public final int GRID_HEIGHT = 30;
public final int GRID_WIDTH = 30;
public static final int NORTH = 1;
public static final int SOUTH = 2;
public static final int EAST = 3;
public static final int WEST = 4;
public static final int NONE = 0;
public static int direction;
public LinkedList<Point> snakeParts = new LinkedList<Point>();
public Point itemLocation = new Point();
public Graphics graphics;
int randomX;
int randomY;
int runCount = 0;
EventS events = new EventS(this);
KeyListenerS keylistener = new KeyListenerS(this);
//On Initialization
public void init(){
//Adding "Snakeparts" to the linked list.
snakeParts.add(new Point(10, 10));
snakeParts.add(new Point(10, 11));
snakeParts.add(new Point(10, 12));
this.addKeyListener(keylistener);
}
//Startup paint method.
public void paint(Graphics paramGraphics){
graphics = paramGraphics.create();
this.customInit();
this.setBackground(Color.WHITE);
this.drawAll();
}
public void customInit(){
this.runCount++;
this.setSize(new Dimension(451,451));
if(this.runCount == 2){
Thread eventThread = new Thread(events);
eventThread.start();
}
}
//Used to call all draw methods.
public void drawAll(){
Random random = new Random();
//Calling all draw methods.
this.drawGrid(graphics);
this.drawFruit(graphics,random.nextInt(GRID_WIDTH),random.nextInt(GRID_HEIGHT));
this.drawSnake(graphics);
}
public void drawGrid(Graphics paramGraphics){
paramGraphics.drawRect(0,0,BOX_WIDTH * GRID_WIDTH,BOX_HEIGHT * GRID_HEIGHT);
//Drawing horizontal lines.
for(int x = BOX_WIDTH; x < BOX_WIDTH * GRID_WIDTH; x+=BOX_WIDTH){
paramGraphics.drawLine(x,0,x,BOX_HEIGHT * GRID_HEIGHT);
}
//Drawing vertical lines.
for(int y = BOX_HEIGHT; y < BOX_HEIGHT * GRID_HEIGHT; y+=BOX_HEIGHT){
paramGraphics.drawLine(0, y, GRID_WIDTH * BOX_WIDTH,y);
}
}
public void drawSnake(Graphics paramGraphics){
paramGraphics.setColor(Color.GREEN);
for(Point point : this.snakeParts){
paramGraphics.fillRect(point.x * 15, point.y * 15, BOX_WIDTH, BOX_HEIGHT);
}
paramGraphics.setColor(Color.BLACK);
}
public void drawFruit(Graphics paramGraphics, int paramX, int paramY){
paramGraphics.setColor(Color.RED);
int tempX = paramX * 15;
int tempY = paramY * 15;
paramGraphics.fillOval(tempX, tempY, BOX_WIDTH,BOX_HEIGHT);
paramGraphics.setColor(Color.BLACK);
}
public void drawEmpty(Graphics paramGraphics, int x, int y){
paramGraphics.clearRect(x * 15, y * 15, 15, 15);
this.drawGrid(graphics);
}
}
Event Class (run on seperate thread):
package com.Code0.Snake.Threads;
import java.awt.Point;
import com.Code0.Snake.Main.MainS;
public class EventS implements Runnable{
MainS main;
int CURRENT_DIRECTION;
public EventS(MainS paramMain){
this.main = paramMain;
}
#Override
public void run() {
//Infinite loop checking the game and updating it.
while(true){
main.drawGrid(main.graphics);
Point head;
Point tail;
Point finalPoint = new Point();
switch(MainS.direction){
//If direction = north
case(MainS.NORTH):
head = main.snakeParts.getFirst();
finalPoint = new Point(head.x, head.y - 1);
main.snakeParts.push(finalPoint);
tail = main.snakeParts.getLast();
main.snakeParts.remove(tail);
main.drawSnake(main.graphics);
main.drawEmpty(main.graphics, tail.x, tail.y);
break;
//If direction = south
case(MainS.SOUTH):
head = main.snakeParts.getFirst();
finalPoint = new Point(head.x, head.y + 1);
main.snakeParts.push(finalPoint);
tail = main.snakeParts.getLast();
main.snakeParts.remove(tail);
main.drawSnake(main.graphics);
main.drawEmpty(main.graphics, tail.x , tail.y);
break;
case(MainS.WEST):
head = main.snakeParts.getFirst();
finalPoint = new Point(head.x - 1, head.y);
main.snakeParts.push(finalPoint);
tail = main.snakeParts.getLast();
main.snakeParts.remove(tail);
main.drawSnake(main.graphics);
main.drawEmpty(main.graphics, tail.x , tail.y);
break;
case(MainS.EAST):
head = main.snakeParts.getFirst();
finalPoint = new Point(head.x + 1, head.y);
main.snakeParts.push(finalPoint);
tail = main.snakeParts.getLast();
main.snakeParts.remove(tail);
main.drawSnake(main.graphics);
main.drawEmpty(main.graphics, tail.x , tail.y);
break;
case(MainS.NONE):
break;
}
if(finalPoint.x > main.GRID_WIDTH){
int totalSnakeParts = main.snakeParts.size();
main.snakeParts.clear();
for(int i = totalSnakeParts; i > 0; i--){
Point tempPoint = new Point(i - 2, finalPoint.y);
main.snakeParts.add(tempPoint);
main.drawGrid(main.graphics);
System.out.println(tempPoint);
}
main.drawSnake(main.graphics);
}
try {
Thread.currentThread().sleep((long)100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
KeyListener Class:
package com.Code0.Snake.Listeners;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import com.Code0.Snake.Main.MainS;
public class KeyListenerS implements KeyListener{
MainS main;
public KeyListenerS(MainS paramMain){
this.main = paramMain;
}
#Override
public void keyPressed(KeyEvent event) {
if(event.getKeyCode() == KeyEvent.VK_UP){
if(main.direction == main.SOUTH){
return;
}
main.direction = main.NORTH;
}
if(event.getKeyCode() == KeyEvent.VK_DOWN){
if(main.direction == main.NORTH){
return;
}
main.direction = main.SOUTH;
}
if(event.getKeyCode() == KeyEvent.VK_LEFT){
if(main.direction == main.EAST){
return;
}
main.direction = main.WEST;
}
if(event.getKeyCode() == KeyEvent.VK_RIGHT){
if(main.direction == main.WEST){
return;
}
main.direction = main.EAST;
}
}
#Override
public void keyReleased(KeyEvent arg0) {
}
#Override
public void keyTyped(KeyEvent arg0) {
}
}
EDIT
Here's the link to what it looks like (the bug):
http://s15.postimg.org/8z62dy7az/Bildschirmfoto_2014_06_03_um_16_47_48.png
OK, I'll try to go through your mistakes one-by-one, but really, this is a huge mess.
You save the graphics state and call paint from a separate Thread. Now, I'm not sure about applets and AWT, but in Swing that is a huge no-no. There is a convenient repaint() method available...
You do not have any synchronization on direction. This means the model can ignore the users commands if it feels like it. Try volatile. In fact, having both all variables and your drawing code in one class is fairly bad. MVC is the way to go.
You suffer from a large amount of off-by-1 errors. If you don't know what it is - google will help you. Just to name a few places where you have them: finalPoint.x > main.GRID_WIDTH will fire one cell too late since the grid starts with 0, for(int i = totalSnakeParts; i > 0; i--){Point tempPoint = new Point(i - 2, finalPoint.y); means the first point is in a negative coordinate (when i == 1).
In your border checking logic you reset the position of all the snake points, yet you do not paint empty squares on the previous positions. You do that when processing a move, why aren't you doing it now? That is the residual squares you have left on the right.
Your border checks are doing something strange anyway. What they do is straighten out the snake and teleport it to the left edge completely instead of just moving the head there. It looks the same while the snake is only 3 squares long because of a combination of bugs in number 3, but will be apparent with a longer one (Try it!). A classic approach would be to simply set the x coordinate of the head to 0, instead of resetting everything. That will also make number 4 obsolete, since you won't have to repaint the remainders at all (there won't be any, snake doesn't teleport).