I put my code below to ask you for a review and ask you if this can be considered a good and clean code?
The game works just fine, I have encountered one issue though - the Food condition to not to create new food on current snake position is not working, and to be honest with you - I have been trying to solve it, but forgot about it and now when I am posting this I have this in my mind one more time.
I have added some buttons to stop/pause the game (can be done by pressing Space as well) and choose speed. Score is related to speed value (score for food is the same as the number of speed).
What's the most interesting I guess is the fact, that almost all snake tutorials around the internet allow you (if you are fast enough) to bug the game with those simple steps: Assume, that the snake moves to the Right, timer is set to some amount of time, and if you manage to press Up or Down arrow and then very fast Left (when the timer hasn't gone off), the snake will move backwards and finally eat himself. Most of the games doesn't allow you to change from Right to Left (or Up to Down etc.) in one step, but this is allowed. I prevent players from doing that by implementing temporary direction, that reads the current direction at the begining of a time period (or refresh rate - it is the same here).
I would like to hear your comments, because I am not quite sure if I get (and if I can implement that) all the Object-oriented programming right. Thank you!
package com.RGuSnake;
public class Main {
public static void main(String[] args) {
Snake.getInstance().createBoard();
}
}
package com.RGuSnake;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
public class Snake extends JPanel implements ActionListener {
private int sizeWidth;
private int sizeHeight;
private int offsetWidth;
private int offsetHeight;
private int scale;
private ArrayList<Point> snakeLocation;
private static Point food;
private String direction = "RIGHT";
private String tmpDirection = "RIGHT";
private static final Snake snake = new Snake();
private Integer delay;
private Boolean isPaused = false;
private Boolean isAlive = false;
private Timer timer;
private Board board;
private Buttons buttons;
private JFrame frame;
private Integer score=0;
private int speed=5;
private Snake() {
}
public static Snake getInstance() {
return snake;
}
public void createBoard() {
frame = new JFrame("Typical Snake Game");
snakeLocation = new ArrayList<>();
snakeLocation.add(new Point(-100,-100));
food=new Point(-100,-100);
board = new Board();
sizeWidth=board.getSizeWidth();
sizeHeight=board.getSizeHeight();
offsetHeight=board.getOffsetHeight();
offsetWidth=board.getOffsetWidth();
scale=board.getScale();
buttons = new Buttons();
frame.getContentPane().add(BorderLayout.CENTER, board);
frame.getContentPane().add(BorderLayout.SOUTH, buttons);
frame.setPreferredSize(new Dimension(sizeWidth + 2 * offsetWidth, sizeHeight + 2 * offsetHeight + 50));
frame.setResizable(false);
frame.setVisible(true);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setFocusable(true);
frame.requestFocus();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void startGame() {
delay=100+(5-speed)*15;
System.out.println(delay);
timer = new Timer(delay, this);
System.out.println(delay);
if(frame==null){
snake.createBoard();
}
score=0;
direction="RIGHT";
snakeLocation.clear();
for(int i=0;i<6;i++){
snakeLocation.add(new Point(Math.round((sizeWidth+offsetWidth)/(2*10))*10-i*10, Math.round((sizeHeight+offsetHeight)/(2*10))*10));
}
newFood();
buttons.blockButtons();
isAlive = true;
isPaused = false;
timer.start();
}
public ArrayList<Point> getSnakeLocation() {
return snakeLocation;
}
public Point getFoodLocation() {
return food;
}
public Boolean getIsAlive() { return isAlive;}
public void setDirection(String dir) {
snake.direction = dir;
}
public String getDirection() {
return snake.direction;
}
public String getTmpDirection(){
return snake.tmpDirection;
}
public void spacePressed(){
if(!isAlive) {
snake.startGame();
}
else {
isPaused^=true;
}
}
public Boolean getPause(){
return isPaused;
}
public void move() {
if (direction.equals("RIGHT")) {
snakeLocation.add(0, new Point(snakeLocation.get(0).x + 10, snakeLocation.get(0).y + 0));
} else if (direction.equals("LEFT")) {
snakeLocation.add(0, new Point(snakeLocation.get(0).x - 10, snakeLocation.get(0).y + 0));
} else if (direction.equals("UP")) {
snakeLocation.add(0, new Point(snakeLocation.get(0).x, snakeLocation.get(0).y - 10));
} else if (direction.equals("DOWN")) {
snakeLocation.add(0, new Point(snakeLocation.get(0).x, snakeLocation.get(0).y + 10));
}
}
public void actionPerformed(ActionEvent arg0) {
if(!isPaused && isAlive) {
tmpDirection = direction;
snake.move();
snake.checkPosition();
//refresh();
board.repaint();
} else if(!isAlive) {
timer.stop();
buttons.enableButtons();
}
}
public void newFood() {
Random random = new Random();
Point point;
point = new Point(random.nextInt(sizeWidth / scale) * scale + offsetWidth, random.nextInt(sizeHeight / scale) * scale + offsetHeight);
while (Arrays.asList(getSnakeLocation()).contains(point)) {
point = new Point(random.nextInt(sizeWidth / scale) * scale + offsetWidth, random.nextInt(sizeHeight / scale) * scale + offsetHeight);
}
food = point;
}
public void increaseScore() {
score=score+speed;
}
public int getScore(){
return score;
}
public void increaseSpeed(){
if(speed<10) {
speed += 1;
}
}
public void decreaseSpeed(){
if(speed>1) {
speed -= 1;
}
}
public int getSpeed(){
return speed;
}
public void refresh(){
board.repaint();
}
public void checkPosition(){
for (int j = 1; j < snakeLocation.size()-1; j++) {
if (snakeLocation.get(0).equals(snakeLocation.get(j))) {
isAlive = false;
}
}
if (snakeLocation.get(0).x==offsetWidth-scale || snakeLocation.get(0).x==sizeWidth+offsetWidth ||snakeLocation.get(0).y==offsetHeight-scale || snakeLocation.get(0).y==sizeHeight+offsetHeight) {
isAlive = false;
}
if (snakeLocation.get(0).equals(food)) {
newFood();
increaseScore();
}
else {
snakeLocation.remove(snakeLocation.size() - 1);
}
}
}
package com.RGuSnake;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
public class Board extends JPanel {
private int sizeWidth = 300;
private int sizeHeight = 300;
private int offsetWidth = 30;
private int offsetHeight = 30;
private int scale = 10;
Snake snake=Snake.getInstance();
public void paintComponent(Graphics g) {
super.paintComponent(g);
//super.update(g);
ArrayList<Point> points = Snake.getInstance().getSnakeLocation();
g.setColor(Color.BLACK);
g.fillRect(offsetWidth - scale, offsetHeight - scale, sizeWidth + 2 * scale, sizeHeight + 2 * scale);
g.setColor(Color.LIGHT_GRAY);
g.fillRect(offsetWidth, offsetHeight, sizeWidth, sizeHeight);
g.setColor(Color.BLACK);
for (int i = 1; i < points.size(); i++) {
g.fillRect(snake.getSnakeLocation().get(i).x, snake.getSnakeLocation().get(i).y, scale, scale);
}
g.setColor(Color.RED);
g.fillRect(snake.getSnakeLocation().get(0).x, snake.getSnakeLocation().get(0).y, scale, scale);
g.setColor(Color.BLUE);
g.fillRect(snake.getFoodLocation().x, snake.getFoodLocation().y, scale, scale);
g.setColor(Color.RED);
Font font = new Font("Verdana", Font.BOLD, 12);
g.setFont(font);
FontMetrics fm = g.getFontMetrics();
String score = "Score: " + snake.getScore() + " Speed: " + snake.getSpeed();
g.drawString(score, (offsetWidth * 2 + sizeWidth) / 2 - fm.stringWidth(score) / 2, offsetHeight / 2);
if (!snake.getIsAlive()) {
font = new Font("Verdana", Font.BOLD, 12);
g.setFont(font);
String gameOver1 = "CHOOSE THE SPEED";
String gameOver2 = "BEFORE STARTING NEW GAME";
fm = g.getFontMetrics();
g.setColor(Color.red);
g.drawString(gameOver1, (offsetWidth * 2 + sizeWidth) / 2 - fm.stringWidth(gameOver1) / 2, (offsetHeight + sizeHeight) / 2);
g.drawString(gameOver2, (offsetWidth * 2 + sizeWidth) / 2 - fm.stringWidth(gameOver2) / 2, (offsetHeight + sizeHeight + 40) / 2);
//String speed = "Game speed: " + snake.getSpeed();
//g.drawString(speed,(offsetWidth*2+sizeWidth)/2-fm.stringWidth(speed)/2,(offsetHeight+sizeHeight)/2+70);
}
}
public int getSizeWidth() {
return sizeWidth;
}
public int getOffsetWidth() {
return offsetWidth;
}
public int getSizeHeight(){
return sizeHeight;
}
public int getOffsetHeight() {
return offsetHeight;
}
public int getScale(){
return scale;
}
}
package com.RGuSnake;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
public class Buttons extends JPanel {
Snake snake=Snake.getInstance();
Board board=new Board();
JButton startGame;
JButton speedDown;
JButton speedUp;
public Buttons(){
JPanel buttonsPanel = new JPanel();
buttonsPanel.setPreferredSize(new Dimension(board.getSizeWidth()+board.getOffsetWidth(), 20));
startGame = new JButton("START!");
startGame.setBackground(Color.red);
speedDown = new JButton("Speed Down");
speedUp = new JButton("Speed Up");
startGame.addActionListener(new startGame());
speedUp.addActionListener(new SpeedUp());
speedDown.addActionListener(new SpeedDown());
startGame.setFocusPainted(false);
startGame.setFocusable(false);
speedDown.setFocusPainted(false);
speedDown.setFocusable(false);
speedUp.setFocusPainted(false);
speedUp.setFocusable(false);
buttonsPanel.setLayout(new GridLayout());
buttonsPanel.add(startGame);
buttonsPanel.add(speedDown);
buttonsPanel.add(speedUp);
InputMap im = buttonsPanel.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap am = buttonsPanel.getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "RightArrow");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "LeftArrow");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "UpArrow");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "DownArrow");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "Space");
am.put("RightArrow", new ArrowAction("RightArrow"));
am.put("LeftArrow", new ArrowAction("LeftArrow"));
am.put("UpArrow", new ArrowAction("UpArrow"));
am.put("DownArrow", new ArrowAction("DownArrow"));
am.put("Space", new ArrowAction("Space"));
add(buttonsPanel);
}
public void blockButtons() {
startGame.setText("PAUSE");
speedUp.setEnabled(false);
speedDown.setEnabled(false);
}
public void enableButtons() {
startGame.setText("START!");
speedUp.setEnabled(true);
speedDown.setEnabled(true);
}
private class startGame implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
snake.spacePressed();
}
}
private class SpeedUp implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
snake.increaseSpeed();
snake.refresh();
}
}
private class SpeedDown implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
snake.decreaseSpeed();
snake.refresh();
}
}
}
package com.RGuSnake;
import javax.swing.*;
import java.awt.event.ActionEvent;
public class ArrowAction extends AbstractAction {
Snake snake=Snake.getInstance();
private String cmd;
public ArrowAction(String cmd) {
this.cmd = cmd;
}
@Override
public void actionPerformed(ActionEvent e) {
if ((cmd.equalsIgnoreCase("LeftArrow")) && !snake.getDirection().equals("RIGHT") && !snake.getTmpDirection().equals("RIGHT") && !snake.getPause()) {
snake.setDirection("LEFT");
} else if (cmd.equalsIgnoreCase("RightArrow") && !snake.getDirection().equals("LEFT") && !snake.getTmpDirection().equals("LEFT")&& !snake.getPause()) {
snake.setDirection("RIGHT");
} else if (cmd.equalsIgnoreCase("UpArrow")&& !snake.getDirection().equals("DOWN") && !snake.getTmpDirection().equals("DOWN")&& !snake.getPause()) {
snake.setDirection("UP");
} else if (cmd.equalsIgnoreCase("DownArrow")&& !snake.getDirection().equals("UP") && !snake.getTmpDirection().equals("UP")&& !snake.getPause()) {
snake.setDirection("DOWN");
} else if (cmd.equalsIgnoreCase("Space")) {
snake.spacePressed();
}
}
}