6
\$\begingroup\$

I made a single player slither.io game. Here are the features:

  1. You can speed up by holding down the mouse button.
  2. You can eat food and grow both in length and width.
  3. Only 100 food will spawn maximum (eat to generate more)

What suggestions both in game logic and in code structure do you guys have?

(there is only one class)

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;

import javax.swing.JFrame;

public class Controller {

    MyFrame frame;
    ArrayList<Point> listOfDots;
    ArrayList<Point> foods;
    Image OSC;
    PointerInfo a = MouseInfo.getPointerInfo();
    int size = 10;
    int speed = 10;
    Random r;

    public static void main(String[] args) {
        Controller c = new Controller();
        c.startGame();
    }

    public void startGame(){
        frame = new MyFrame("slitherio");
        listOfDots = new ArrayList<Point>();
        foods = new ArrayList<Point>();
        r = new Random();
        listOfDots.add(new Point(100, 100));
        Runner r = new Runner();
        r.start();
    }

    class Runner extends Thread {
        public void run(){
            while(true){
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(foods.size() < 100)
                    foods.add(new Point(r.nextInt(900), r.nextInt(900)));
                a = MouseInfo.getPointerInfo();
                Point p = a.getLocation();
                Point last = listOfDots.get(listOfDots.size() - 1);
                Point n = new Point();
                if(last.distance(p) > 5){
                    n = calcCoor(last, p);
                    listOfDots.add(n);
                    if(listOfDots.size() >= size){
                        for(int i = 0; i < listOfDots.size() - size; i++){
                            listOfDots.remove(i);
                        }
                    }
                    System.out.println(n);
                }
                Iterator<Point> i = foods.iterator();
                while(i.hasNext()){
                    Point food = i.next();
                    if(food.distance(n) < 20){
                        i.remove();
                        size++;
                    }
                }
                frame.repaint();
            }

        }
        public Point calcCoor(Point last, Point mouse){
            double degree = 0;
            if(last.x < mouse.x && last.y < mouse.y){
                degree = 360 - Math.toDegrees(Math.atan((double) (mouse.y - last.y) / (mouse.x - last.x)));
            }else if(last.x > mouse.x && last.y > mouse.y){
                degree = 180 - Math.toDegrees(Math.atan((double) (last.y - mouse.y) / (last.x - mouse.x)));
            }else if(last.y > mouse.y && last.x < mouse.x){
                degree = Math.toDegrees(Math.atan((double) (last.y - mouse.y) / (mouse.x - last.x)));
            }else if(last.y < mouse.y && last.x > mouse.x){
                degree = 180 + Math.toDegrees(Math.atan((double) (mouse.y - last.y) / (last.x - mouse.x)));
            }
            Point p = new Point((int) 
                    (last.x + Math.cos(Math.toRadians(degree)) * speed), (int) 
                    (last.y - Math.sin(Math.toRadians(degree)) * speed));
            return p;
        }
    }

    class MyMouseSensor extends MouseAdapter {
        boolean mouseDown = false;
        public void mousePressed(MouseEvent e){
            speed = 20;
            mouseDown = true;
            size /= 2;
            new Thread(){
                public void run(){
                    while(mouseDown){
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e1) {
                            // TODO Auto-generated catch block
                            e1.printStackTrace();
                        }
                        size--;
                    }
                }
            }.start();

        }
        public void mouseReleased(MouseEvent e){
            speed = 10;
            size *= 2;
            mouseDown = false;
        }
    }

    class MyFrame extends JFrame {
        public MyFrame(String s){
            super(s);

            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            setBounds(0, 0, 900, 900);
            setLayout(new FlowLayout());
            setBackground(Color.white);
            addMouseListener(new MyMouseSensor());

            setVisible(true);
        }
        public void paint(Graphics g) {
            Dimension d = getSize();
            checkOffscreenImage();
            Graphics offG = OSC.getGraphics();
            offG.setColor(Color.white);
            offG.fillRect(0, 0, d.width, d.height);
            paintOffscreen(OSC.getGraphics());
            g.drawImage(OSC, 0, 0, null);
        }

        private void checkOffscreenImage() {
            Dimension d = getSize();
            if (OSC == null || OSC.getWidth(null) != d.width
                    || OSC.getHeight(null) != d.height) {
                OSC = createImage(d.width, d.height);
            }
        }

        public void paintOffscreen(Graphics g) {
            g.clearRect(0, 0, 900, 900);
            Point first = new Point();
            Point last = listOfDots.get(0);
            g.setColor(Color.BLACK);
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setStroke(new BasicStroke(9 + (float) size / 10));
            for(int i = 1; i < listOfDots.size(); i++){
                first = listOfDots.get(i);
                g2.drawLine(first.x, first.y, last.x, last.y);
                last = new Point(first);
            }
            g2.setColor(Color.red);
            for(int i = 0; i < foods.size(); i++){
                g2.fillOval(foods.get(i).x, foods.get(i).y, 10, 10);
            }
        }
    }

}

Here is a photo explaining Slither.io image of Slither.io

The pinkish dots on the lower right are the remains of a dead snake. You die by ramming your head into another snake's body. You turn into food after you die.

\$\endgroup\$
10
\$\begingroup\$

Variables should be private when possible

MyFrame frame;
ArrayList<Point> listOfDots;
ArrayList<Point> foods;
Image OSC;
PointerInfo a = MouseInfo.getPointerInfo();
int size = 10;
int speed = 10;
Random r;

You should make variables private and final whenever possible. There are many benefits to this. The compiler will warn you when you are not using a field in your code, for example. You also want your classes to expose as little information as possible.

Think about a fixed time step for the game loop

class Runner extends Thread {
    public void run(){
        while(true){
            try {
                Thread.sleep(50);

I understand why you are doing this, but if you plan on making more games then I would start learning about a fixed time step rather than relying on Thread.sleep() and multiple threads. I found this that talks about specific ways to implement a game loop with Swing http://www.java-gaming.org/index.php?topic=24220.0

Don't use the type of data structure when naming

ArrayList<Point> listOfDots;

There's no good reason not to just call this dots. If you later change this to a Map or another structure, you won't have to go through your code and change the variable name since it would then be misleading.

Magic Numbers

if(foods.size() < 100)
foods.add(new Point(r.nextInt(900), r.nextInt(900)));
if(last.distance(p) > 5){
if(food.distance(n) < 20){
setBounds(0, 0, 900, 900);
g2.fillOval(foods.get(i).x, foods.get(i).y, 10, 10);

It's not impossible to figure out what these numbers mean when reading through the code, but the code will be more clear and more self documenting if you use explicit variables for each of these magic numbers. So things like screenWidth, screenHeight, maxFood, eatDistance, etc.

Separate game model from rendering

Currently, the logic for the slither game as well as its state are tied together with the way that you render the game and handle input. You should always try to separate your game model from the way it is rendered. You should have a Slither class that contains the information about the player position, food positions, and the rules for how the game plays. Then, each frame the rendering would look at the game model and draw things according to where they are located.

When you do this, it will be very easy for you to port the game to another graphics library, or even port the game logic to another language or game engine.

That's all for now! Nice question.

\$\endgroup\$
6
\$\begingroup\$
    ArrayList<Point> listOfDots;
    ArrayList<Point> foods;

Interfaces usually make better types than implementations.

    List<Point> listOfDots;
    List<Point> foods;

This makes it easier if you want to change implementations in the future.

        Runner r = new Runner();
        r.start();

You already have a field named r. It's somewhat confusing to add a local variable with the same name that does something different. It may be better to rename both to longer, more descriptive names.

                a = MouseInfo.getPointerInfo();

This doesn't need to be an object field. You can just declare it as a local variable here and delete the field.

                PointerInfo a = MouseInfo.getPointerInfo();

As a general rule it's best to declare variables at the smallest scope that covers all their uses.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.