This is a snake game I made,
Note: at this point, I would like to hear any thoughts/ reviews about it.
Thank you
Game class:
package snake;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;
public class Game extends Canvas implements Runnable{
public static final int WIDTH = 720;
public static final int HEIGHT = 720;
public static final int BLOCK_SIZE = 30; //Do not change - size of the food and snake body part
//as well as their images
private Thread thread;
private boolean running;
private Snake snake;
private Food food;
public Game(){
initializeWindow();
snake = new Snake(this);
food = new Food();
food.generateLocation(snake.getCopyOfEmptySpaces());
initializeKeyAdapter();
start();
}
private synchronized void start() {
thread = new Thread(this);
running = true;
thread.start();
this.requestFocus();
}
public void run() {
double amountOfTicks = 10d; //ticks amount per second
double nsBetweenTicks = 1000000000 / amountOfTicks;
double delta = 0;
long lastTime = System.nanoTime();
while(running) {
long now = System.nanoTime();
delta += (now - lastTime) / nsBetweenTicks;
lastTime = now;
while (delta >= 1) {
tick();
delta--;
}
render();
}
}
public void tick() {
if (snake.isDead()) {
running = false;
}
else {
if (isEating()) {
food.generateLocation(snake.getCopyOfEmptySpaces());
}
snake.tick();
}
}
public void render() {
if (running) {
BufferStrategy bs = this.getBufferStrategy();
if (bs == null) {
this.createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
g.setColor(Color.black);
g.fillRect(0, 0, Game.WIDTH, Game.HEIGHT);
food.render(g);
snake.render(g);
if (snake.isDead()) {
g.setColor(Color.white);
g.setFont(new Font("Tahoma", Font.BOLD, 75));
g.drawString("Game Over", Game.WIDTH / 2 - 200 , Game.HEIGHT / 2);
}
g.dispose();
bs.show();
}
}
public boolean isEating() {
return snake.getHeadCoor().equals(food.getCoor());
}
private JFrame initializeWindow() {
JFrame frame = new JFrame("Snake Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(this);
this.setPreferredSize(new Dimension(Game.WIDTH, Game.HEIGHT));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
return frame;
}
private void initializeKeyAdapter() {
//this is how to game gets keyboard input
//the controls are wasd keys
class MyKeyAdapter extends KeyAdapter{
private int velocity = Snake.DEFAULT_SPEED; //move a whole block at a time
@Override
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if (key == KeyEvent.VK_ESCAPE) {
System.exit(0);
}
//after a key has been pressed we check if the snake goes the opposite way
//if so, we ignore the press
if (key == KeyEvent.VK_S) {
if (snake.getVelY() != -velocity) {
snake.setVel(0, velocity);
}
}
else if (key == KeyEvent.VK_W) {
if (snake.getVelY() != velocity) {
snake.setVel(0, -velocity);
}
}
else if (key == KeyEvent.VK_D) {
if (snake.getVelX() != -velocity) {
snake.setVel(velocity, 0);
}
}
else if (key == KeyEvent.VK_A) {
if (snake.getVelX() != velocity) {
snake.setVel(-velocity, 0);
}
}
}
}
this.addKeyListener(new MyKeyAdapter()); //adding it to the game
}
public static void main(String[] args) {
Game g = new Game();
}
}
Snake class:
package snake;
import java.awt.Graphics;
import java.awt.Image;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import javax.swing.ImageIcon;
public class Snake {
public static final int DEFAULT_SPEED = Game.BLOCK_SIZE;
private Game game;
private int velX;
private int velY;
private LinkedList<Coor> body; //snake's body
private Set<Coor> emptySpaces; //valid spots for food- spots without snake parts
private boolean dead;
private Image img; //img of other body parts
/*
* @pre: Game.HEIGHT / Game.BLOCK_SIZE == 0 && Game.WIDTH / Game.BLOCK_SIZE == 0
* @pre: Game.HEIGHT % 2 == 0
* @pre: Game.WIDTH > 3 * Game.BLOCK_SIZE
* @post: the snake starts at the middle of the screen
*/
Snake(Game game){
this.game = game;
body = new LinkedList<Coor>();
//starting snake
int halfScreenHeight = Game.HEIGHT / 2;
body.add(new Coor(2 * Game.BLOCK_SIZE, halfScreenHeight)); //head block
body.add(new Coor(Game.BLOCK_SIZE, halfScreenHeight)); //middle block
body.add(new Coor(0, halfScreenHeight)); //last block
velX = DEFAULT_SPEED;
initializeEmptySpaces();
initializeImage();
}
public void tick() { //updating the body and checking for death
/* Updating body:
* Explanation: the Coor of the n-th body part is the Coor of the head n ticks ago
* Execution: adding the current head Coor to the body, and pushing all other
* Coors one place. If the snake hasn't eat this turn than we will remove
* the last Coor in the body. Oterwise, it has eat and needs to grow,
* in that case we'll keep it
* Result: the body will be: [Coor now, before 1 tick, before 2 ticks, ...]
*/
int prevHeadX = body.getFirst().getX();
int prevHeadY = body.getFirst().getY();
body.push(new Coor(prevHeadX + velX, prevHeadY + velY)); //new head Coor
if (!game.isEating()) {
Coor lastCoor = body.getLast();
body.removeLast();
emptySpaces.add(lastCoor); //now there is no body part on it
}
emptySpaces.remove(getHeadCoor());
checkDeath();
}
public void render(Graphics g) {
for (Coor curr : body) {
g.drawImage(img, curr.getX(), curr.getY(), null);
}
}
private void checkDeath() {
Coor h = getHeadCoor();
if (h.getX() < 0 || h.getX() > Game.WIDTH - Game.BLOCK_SIZE) { //invalid X
dead = true;
}
else if (h.getY() < 0 || h.getY() > Game.HEIGHT - Game.BLOCK_SIZE) { //invalid Y
dead = true;
}
else {
dead = false;
for (int i = 1; i < body.size(); i++) { //compare every non-head body part's coor with head's corr
if (getHeadCoor().equals(body.get(i))) { //head touched a body part
dead = true;
}
}
}
}
public void setVel(int velX, int velY) {
this.velX = velX;
this.velY = velY;
}
public int getVelX() {
return velX;
}
public int getVelY() {
return velY;
}
public boolean isDead() {
return dead;
}
public Set<Coor> getCopyOfEmptySpaces() {
return new HashSet<Coor>(emptySpaces);
}
private void initializeEmptySpaces() {
emptySpaces = new HashSet<Coor>();
for (int i = 0; i * Game.BLOCK_SIZE < Game.WIDTH; i++) {
for (int j = 0; j * Game.BLOCK_SIZE < Game.HEIGHT; j++) {
emptySpaces.add(new Coor(i * Game.BLOCK_SIZE, j * Game.BLOCK_SIZE));
}
}
emptySpaces.removeAll(body); //remove the starting snake parts
}
private void initializeImage() {
ImageIcon icon = new ImageIcon("src/res/snake.png");
img = icon.getImage();
}
public Coor getHeadCoor() {
return body.getFirst();
}
}
Food class:
package snake;
import java.awt.Graphics;
import java.awt.Image;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
import javax.swing.ImageIcon;
public class Food {
private Image img;
private Coor coor;
Food(){
initializeImages();
}
public void render(Graphics g) {
g.drawImage(img, coor.getX(), coor.getY(), null);
}
public void generateLocation(Set<Coor> set) { //picking a random coordinate for the food
int size = set.size();
Random rnd = new Random();
int rndPick = rnd.nextInt(size);
Iterator<Coor> iter = set.iterator();
for (int i = 0; i < rndPick; i++) {
iter.next();
}
Coor chosenCoor = iter.next();
coor = chosenCoor;
}
private void initializeImages() {
ImageIcon icon = new ImageIcon("src/res/food.png");
img = icon.getImage();
}
public Coor getCoor() {
return coor;
}
}
Coor class:
package snake;
public class Coor { //coordinates
//we divide the screen to rows and columns, distance
//between two rows or two columns is Game.BLOCK_SIZE
private int x;
private int y;
Coor(int x, int y){
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public String toString() {
return "(" + x + ", " + y + ")";
}
@Override
public int hashCode() {
return x * Game.WIDTH + y;
}
@Override
public boolean equals(Object o) {
Coor c = (Coor) o;
if (x == c.getX() && y == c.getY()) {
return true;
}
return false;
}
}

equalsandhashCodemethods in the Coor class. normallyinstanceofis present inequalsmethod, have the system (ide, etc.) generated them ? \$\endgroup\$