7

To practice my OOP knowledge I'm making a Pong game in javascript (I know, I know, it's like playing Stairway to Heaven in a guitar shop). I've had several functioning versions of the game by implementing several different techniques including prototypal based OOP and functional style. However I'm not doing this to get a functional game I'm doing it to learn.

I'm using the html5 canvas and plain ol' javascript, no frameworks (ok, a little bit of jQuery for the keyboard capture). I had my Pong object which represented my game. Pong had an attribute ctx that contained a reference to the canvas.getContext("2d") context. It also had a player1, player2 and ball attribute for holding you know what. When the ball and two players were instantiated the context was passed in to their constructor so that they too could hold a reference to the context for use in their draw(ctx) methods. Pong had a draw() method that would get called using a setInterval(this.draw, 10). Pong's draw method would call the draw method of the two players and the ball.

It doesn't sit well with me that the two players and ball have the context as an attribute. They don't own the context and therefore it shouldn't be an attribute. However the nature of using javascript and the canvas seems to be that this is the best way. Who or what should own the context in this situation? Ideally I wouldn't want the players and ball objects to have a draw object at all. I feel like they should have attributes that describe their geometry and position and a dedicated object should be tasked with rendering them to the screen. This way, if in the future I decided I wanted to use <div>'s instead of the canvas I could just change the rendering object and everything else would be oblivious.

I know I'm making a javascript Pong game more complicated than it needs to be but I want to practice the techniques and really grok the concept of OOP but every time I think I've cracked it a completely new problem created by my 'solution' presents itself.

EDIT: If it would help if you had a nosy at my code here is an (almost) fully working version:

library.js - http://mikemccabe.me/tests/pong.archive.14.06.11/library.js

pong.js - http://mikemccabe.me/tests/pong.archive.14.06.11/pong.js

Try it - out http://mikemccabe.me/tests/pong.archive.14.06.11/

punkrockbuddyholly
  • 9,675
  • 7
  • 36
  • 69

4 Answers4

1

You are making things more complicated than they're worth.

a dedicated object should be tasked with rendering them to the screen...

When you have a task that needs doing it's weird to say that "an object is tasked" with doing the thing. Functions do things. Objects encapsulate data. A function should be tasked with rendering them to the screen.

Who or what should own the context in this situation?

Nothing "owns" the context. Or, if you like, the canvas owns the context, but I don't know why you would take the context and put it somewhere; I would expect that to be more confusing than just passing it around.

EDIT: Glancing at your code, you've made the Pong closure "own" (have a reference to, that is) the context, which seems reasonable enough. I don't really see what problems you anticipate with that approach. I don't agree, however, that you should pass it into the constructor of Ball etc. I think it's a lot more straightforward to pass it to the various draw() methods.

mqp
  • 70,359
  • 14
  • 95
  • 123
  • Yes, it could make it cleaner to pass the context into the various `draw()` methods. – justis Jun 15 '11 at 19:39
  • I suppose my main problem is that by building the entire game around using the canvas context I can't decide to implement the game using div's instead. If I abstracted the actual rendering from the objects themselves it would produce more re-useable code. I'm not really trying to be practical though, just trying to learn. – punkrockbuddyholly Jun 15 '11 at 19:43
  • The version I am working on at the moment passes the context to the draw methods, however the ball has a `flash` method that creates a bounced effect when the ball hits anything. This flash method needs the context which is where my dilemma started! – punkrockbuddyholly Jun 15 '11 at 19:47
  • Regarding your dilemma, the reason it's confusing is because by putting the context into the `Ball` and `Player` objects you've obscured the fact that `Player.celebrate` and `Ball.move` actually do drawing too, and are implicitly dependent on having a context. In absence of refactoring them, those functions should take the context as a parameter; then they can pass it to `Ball.flash`. – mqp Jun 15 '11 at 20:20
  • By the way, I agree with you that it would be better to have the `draw` functionality outside of the objects, so that you can abstract away the drawing from the data. It would be both more reusable and clearer to read, in my opinion. – mqp Jun 15 '11 at 20:21
1

Not specific to pong, but this online book

http://eloquentjavascript.net/chapter8.html

Has an example of building a small game. It's slightly different, but it does a good job of showing how to use Object Oriented Principles.

You can code things in many ways, and it's easy to go a little far with design patterns. As with anything, use moderation. I've always used Nibbles/Snake as my goto game when working in new language.

MillsJROSS
  • 1,239
  • 7
  • 9
1

This might be a great case in which to apply MVC (model-view-controller) principles.

You can have a model to track the state of each game element. These would be things like Player, Paddle, Ball, etc. Then, you can have renderers (the views) which contain only the logic for drawing the current state. The views could be named something like BallCanvasRenderer and PaddleCanvasRenderer.

Since the view would be well encapsulated, it might make sense to have it keep a reference to the canvas context, but you could also just pass in the appropriate context and model for each time you render.

The controller would be responsible for responding to game events and updating the models.

Note that this pattern also gives you the option of maintaining multiple renderers for any type of model. You could also have renderers that output text, ASCII art, or OpenGL.

justis
  • 504
  • 4
  • 6
1

I don't see a problem with your use of the context. To meet a design goal to allow the rendering to be technology independent, then you should write a general purpose interface to the rendering methods you need, and create an object that uses context to implement this interface instead. Then you could substitute another version of that object, for example, to make it work in Internet Explorer <9.

In Javascript, I think it's often convenient to use scope to allow objects in your application to access shared resources directly. Though this is not strictly good OO design, think of it as a singleton. For example:

var Pong = (function() {
   var Graphics, graphics, Ball, ball1, ball2, play;
   Graphics = function() {
      this.context = ...
   };
   graphics = new Graphics();
   Ball = function() {
     // do something with graphics
   };
   ball1 = new Ball();
   ball2 = new Ball();
   // ball1 and ball2 will both be able to access graphics

   play =function() {
       // play the game!
   };
   return {
       play: play
   }
}());

To generalize this you can just make the Graphics object have general purpose methods instead of providing access to context directly, and have more than one version of it, instantiating the correct one depending on browser. There aren't really any downsides compared to explicitly assigning graphics to both ball1 and ball2 except purism. The upsides become substantial when you are dealing with hundreds of objects (e.g. representing DOM elements) rather than just a few.

Jamie Treworgy
  • 23,934
  • 8
  • 76
  • 119
  • By 'more than one version of' Graphics do you mean, for example, a DomGraphics object and a CanvasGraphics object which use the same interface? – punkrockbuddyholly Jun 15 '11 at 20:11
  • Exactly. You can then just have some logic like (using jquery) `if ($.browser.msie && $.browser.version<9) { graphics = new VmlGraphics() } else {graphics = new CanvasGraphics() }`. In practice in JS its more efficient to just conditionally define the object prototype in the first place so the code that doesn't apply to that browser never gets run at all. – Jamie Treworgy Jun 15 '11 at 20:16
  • An example actually from one of my projects: https://github.com/jamietre/ImageMapster Caveat: that part of it is the oldest, ugliest, inherited, stovepiped part of that code. But it shows a conditional implementation of a graphics interface. If you go looking start at line 1387. – Jamie Treworgy Jun 15 '11 at 20:20