Any way to make this simple tennis game more OO?

Started by
11 comments, last by tufflax 9 years, 11 months ago

Hey all, I'm a 1st year CS student and I'm making my first pet project in Java, the idea is taken from this tutorial (http://www.edu4java.com/en/game/game6.html) but I didn't want to continue following the tutorial because I wanted to get my hands dirty and dive in trying to work it out myself.

Currently the code has three classes:

- Game which is the main class that sets up the KeyListener, game loop and painting of the screen

- Ball which creates the ball sprite and works out the position on screen when the move() and paint() methods are called

- Racquet which is the same as Ball but a different shape obviously.

My question is, can I separate the Game class and make a Board class that deals with creating the window and then leave the Game class to run the loop only? I've tried doing so but I've discovered circular dependencies between Board, Ball and Racquet if I do that. I can do it using setters in the Board class to reference the Ball and Racquet class but I can't get the KeyListener working in that case and I think there must be a slicker way of doing it The code is below:

Game:


import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
 * Created by HuwF on 24/04/2014.
 */
public class Game extends JPanel
{
    //Create sprite objects
    Ball ball = new Ball(this);
    Racquet racquet = new Racquet(this);

    public Game()
    {
        //Implement listener to move keyboard
        addKeyListener(new KeyListener()
        {
            @Override
            public void keyTyped(KeyEvent e)
            {

            }

            @Override
            public void keyPressed(KeyEvent e)
            {
                //Call racquet to move it
                racquet.keyPressed(e);
            }

            @Override
            public void keyReleased(KeyEvent e)
            {
                //Stops racquet movement
                racquet.keyReleased(e);
            }
        });

        setFocusable(true);
    }

    //Gets objects to update position and movement values
    public void move()
    {
        ball.move();
        racquet.move();
    }

    //Paints screen based on sprite's values
    @Override
    public void paint(Graphics g)
    {
        super.paint(g);
        Graphics2D g2d = (Graphics2D)g;
        ball.paint(g2d);
        racquet.paint(g2d);
    }

    public static void main(String[] args) throws InterruptedException
    {
        //Create and display frame
        JFrame frame = new JFrame("Mini Tennis");
        Game game = new Game();
        frame.add(game);
        frame.setSize(300, 400);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        //Main game loop - I want everything apart from this in the Board class
        while (true)
        {
            game.move();
            game.repaint();
            Thread.sleep(10);
        }
    }
}
Ball:

import java.awt.Graphics2D;
/**
 * Created by HuwF on 24/04/2014.
 */
public class Ball
{
    //Ball coordinates
    int x = 0;
    int y = 0;

    //1 = Right, 0 = Still, -1 = Left
    int xDirection = 1;
    //1 = Up, 0 = Still, -1 = Down
    int yDirection = 1;

    private Game game;

    public Ball(Game game)
    {
        this.game= game;
    }

    void move()
    {
        //If ball is by left edge of screen, direction reverses
        if (x + xDirection < 0)
        {
            xDirection = 1;
        }

        //If ball is by right edge of screen, direction reverses
        if (x + xDirection > this.game.getWidth() - 30)
        {
            xDirection = -1;
        }

        //If ball is by the bottom of screen, direction reverses
        if (y + yDirection < 0)
        {
            yDirection = 1;
        }

        //If ball is by the top of screen, direction reverses
        if (y + yDirection > this.game.getHeight() - 30)
        {
            yDirection = -1;
        }

        //If ball is no where near screen border continue along path
        x = x + xDirection;
        y = y + yDirection;
    }

    public void paint(Graphics2D g)
    {
        //Draw sprite on screen
        g.fillOval(x, y, 30, 30);
    }
}

Racquet:


import java.awt.*;
import java.awt.event.KeyEvent;

/**
 * Created by HuwF on 24/04/2014.
 */
public class Racquet
{
    //Position, racquet can only move left or right
    int x = 0;

    //Direction, -1 = Left, 0 = Still, 1 = Right
    int xDirection = 0;

    private Game game;

    public Racquet(Game game)
    {
        this.game = game;
    }

    public void move()
    {
        //If not near the left or right edge of the screen then move in current direction
        if ((x + xDirection > 0) && (x + xDirection < this.game.getWidth() - 60))
        {
            x = x + xDirection;
        }
    }

    public void paint(Graphics2D g)
    {
        //Draw sprite on screen
        g.fillRect(x, 330, 60, 10);
    }

    //When a key is released the racquet stops moving
    public void keyReleased(KeyEvent e)
    {
        xDirection = 0;
    }

    public void keyPressed(KeyEvent e)
    {
        //If left arrow is pressed direction is changed to left
        if (e.getKeyCode() == KeyEvent.VK_LEFT)
        {
            xDirection = -1;
        }

        //If right arrow is pressed direction is changed to right
        if (e.getKeyCode() == KeyEvent.VK_RIGHT)
        {
            xDirection = 1;
        }
    }
}

Thanks in advance!

Advertisement

What you could do is to try and create a Scene class, this is a pretty common approach. Then you let all game objects extend the same interface (for example an Entity interface). Then you can call update and draw on the scene function from the game class. When you call the update function, the scene class usually loops through all entities attached and calls update on the entities. The draw function does the same. So the scene class just keeps a list of all game entities and provides functionality to update and draw them all at once.

Cool thanks for the response! I'll take a look at modifying it now and see if I can get something working :), is the original code an acceptable form of OOP or would it need refinement?

http://www.gamedev.net/page/resources/_/technical/general-programming/java-games-active-rendering-r2418

http://www.gamedev.net/page/resources/_/technical/general-programming/java-games-keyboard-and-mouse-r2439

These should give you some ideas about how to pull out the game loop stuff.

I think, therefore I am. I think? - "George Carlin"
My Website: Indie Game Programming

My Twitter: https://twitter.com/indieprogram

My Book: http://amzn.com/1305076532

Cool thanks for the response! I'll take a look at modifying it now and see if I can get something working smile.png, is the original code an acceptable form of OOP or would it need refinement?

I would call it appropriate for the given complexity, but not very flexible.

It does depend a lot on what acceptable means .. and that depends on context and the goals.

If you mostly want to use the project to learn proper OO design, investigate related schools of thought and get familiar with best practices, then there are quite a few things I would change.

Almost all of those changes would be overkill for the project itself, though:

  • Keeping the methods even shorter (main could call initFrame(frame) and executeGame()) ... they should do exactly one thing.
  • Freeing the Game class of anything that subsystems should handle (separating concerns and figuring out how to bring them together ... for example with listeners, references or patterns like the visitor pattern)
  • Using interfaces (provide services) and writing default implementations for them

That the game class inherits from JFrame seems shady if you want to play by most of the stricter rules. The class does too many things.

Instead it should have references, for example to a GameWindow object, and use its methods.

Ideally you would also separate rendering and input, which is unforunate, because the events are tied to the window toolkit.

From an OO design perspective you sould artificially separate those concerns, for example with a ModuleFactory service interface with a default implementation (called ModuleFactorySwing!?) that creates a GUIModule object and an InputModule using the same JFrame object under the hood.

Another little thing you might want to change:

  • Get more concrete in classes from top to bottom for better readability (define the methods after the places where they are used - Clean Code is strict there ... don't remember ... I think they are called the "Level Of Abstraction" related rules like "Just one level of abstraction per function" and "Do not mix levels of abstraction" etc.)
Given enough eyeballs, all mysteries are shallow.

MeAndVR

Thanks for all your input guys, this is really helpful I'll take it a step at a time and try and refine it more and more into fitting the OOP paradigm. In general for Game Development how OO should you go? I'm a little new on all of this so any more info (or if you need me to provide more) would be appreciated :)

Thanks once again!

Thanks for all your input guys, this is really helpful I'll take it a step at a time and try and refine it more and more into fitting the OOP paradigm. In general for Game Development how OO should you go? I'm a little new on all of this so any more info (or if you need me to provide more) would be appreciated smile.png

Thanks once again!

IMO, that's the wrong way to think about software architecture. It doesn't matter "how OO" a codebase is. What matters is how easy it is to maintain. Object orientation is just one tool of several that you can use to get the job done. The danger of focusing so much on OO is that you wind up with a rigid, inflexible monster that makes it impossible to make changes without negative consequences (like code breakage, or increased complexity of implementation).

Somewhere there is a balance between procedural spaghetti code and the inflexible wall of rigid OOP. Finding that balance is a matter of experience. The more you read about these techniques and the more code you write, the more you'll get a feel for flexible design. Try writing a language that doesn't have OO built-in (like C) so you can see the other side. You'll find yourself implementing objects in terms of structs and free functions, but you won't have all the extra help like private members, inheritance and such. I think that can help tremendously in understanding where the OO paradigm is useful and where it doesn't matter (or gets in the way).

Somewhere there is a balance between procedural spaghetti code and the inflexible wall of rigid OOP.

I don't really agree with this.

Nothing about OOP is rigid, with the exception of cross-cutting concern implementations which lead to scattering (code duplication) and tangling (classes that have to do things they should not have to worry about).

Not sure if procedural programming helps with those.

The problem with strict OO design is the overhead (the amount of classes and methods) and the time you need to invest in coming up with a design that works, not that it is rigid.

In general for Game Development how OO should you go?

That really depends on the goals and time constraints.

It is a little project and you want to get it to market quickly? Is is a game that 10 programmers work on at the same time and for years?

Is it modular and will it be extended a lot? Is there client/server communication? Is there a server part that needs to be scalable?

The way you develop can make a difference, too. Pure agile works best if you keep things extremely clean (because you will have to do a lot of refactoring), while you can get away with (less strict) common sense OO design if you go for a well planned framework.

Probably some research would be time well invested. Articles about:

  • Separation of Concerns
  • Cross-Cutting Concerns
  • Agile Development / Scrum
  • Clean Code
  • Design Patterns
  • Programming Paradigms

are things that might help with finding the right balance. Maybe test driven development is interesting as well. There is a huge overlap with clean code principles, though.

Given enough eyeballs, all mysteries are shallow.

MeAndVR


I don't really agree with this.
Nothing about OOP is rigid, with the exception of cross-cutting concern implementations which lead to scattering (code duplication) and tangling (classes that have to do things they should not have to worry about).

When keeping a strict focus on an object-oriented design it is extremely easy to get carried away and wind up with a very inflexible architecture that is difficult to maintain. Not all of the traps are inherent to OO, but they are, IMO, easier to fall into. A few major examples off the top of my head are inheritance hierarchies that go too deep, classes at too granular a level, and tight coupling between systems. Focusing on whether or not your code is "OO enough" can easily lead down these paths. I have seen this more times than I can count (particularly when I was in the world of Java web apps a decade or so ago). And it's extremely easy for beginners to fall into this because they don't have the experience behind them to understand the side effects of a deep object hierarchy, or why they probably don't need separate classes for their forks and spoons and every item in the game.

Rather than focusing on whether a codebase is "OO enough", a better way to look at it is to take each module or system in isolation and take the approach that is appropriate for that purpose. There's plenty of solid advice out there on how to do that, such as keeping object hierarchies as flat as possible, programming to interfaces where it makes sense, choosing free functions over member functions when possible, and so on.

So I would suggest to the OP (and anyone else) not to sweat it too much about "how OO" to go, to get as broad an experience as possible in different paradigms through reading and implementation, and that the primary focus of any project should (ideally) be maintainability, not whether or not the code adheres to a particular paradigm. Of course, the definition of maintainability will change depending upon the size and scope of the project, but the end goal is still the same.

I see, thanks for all the help I am getting a bit too carried away early since I haven't even finished the project yet haha!

This topic is closed to new replies.

Advertisement