Golang and Atari Pong

Golang game development, a DDD oriented approach

Matheus Antunes de Jesus
4 min readNov 30, 2020

--

A quick description of how I structured a simple Atari Pong game using a DDD approach.

Atari Ping Pong screenshot

I am a software engineer mostly focused on back-end and cloud architectures and also a Golang enthusiast. Throughout my journey of spreading the golang word, I have been experimenting with golang in different scenarios.

Today, I’ll show you the result of a Pong development in GO using DDD principles, hope you enjoy it.

As a disclaimer, this is just an experiment and does not reflect the capabilities of Golang when it comes to game development. There are many libraries and engines to support better quality game development. Due to demonstration purposes, this was developed using a very low-level library called SDL

Anyhow, moving to the good part, as in any DDD project, I started by defining what the domain is for our system and what should it control, in terms of entities and value objects:

Domain

A good exercise I always set myself to is to think of the domain as of what the purposes of our system is, technology agnostic-wise.

Our domain for this is our Game, in the most simplistic approach

The pong game is quite simple:

  • A Ball that moves around the screen at a constant speed.
  • Two Paddles
  • A ScoreBoard

These are the pieces of the puzzle that dictate what happens in the game and produce side effects that also have to be analyzed on the game life cycle.

For now, let’s take a deeper dive at our Entities:

type Game struct { 
PaddleLeft Paddle // Left Paddle
PaddleRight Paddle // Right Paddle
Ball Ball // Ball
ScoreBoard ScoreBoard // ScoreBoard
}
// Ball Represents the ping pong ball
type Ball struct {
pos Position // X and Y position
radius float32 // Radius
vel Velocity // X and Y velocity
}
type Paddle struct {
pos Position // X and Y position
size Size // height and width
ySpeed float32 // Y velocity
}
type ScoreBoard struct{
lPos Position // Left Score X and Y Position
rPos Position // Rigth Score X and Y Position
Left int // Current Left Score
Right int // Current Right Score
MaxScore int // Max Score
}

It seems pretty logical to define these properties, and we can even start to picture the effects that can be caused in one another or how a change of state can dictate a different behavior in our domain.

Entity Interactions

  • Ball trajectory has to be altered when colliding with a Paddle.
  • Ball has to be repositioned on the center when colliding to any horizontal end of the screen, or so-called Score.
  • ScoreBoard must increment either left or right when Score.
  • The Ball must be recentered when Score.
  • Game must reset when either left or right of ScoreBoard reaches MaxScore, so-called GameOver.
  • Paddle must move vertically when the player presses Up or Down.

From these interactions, we can define how these are going to take place. For instance, since the Score triggers many events, it makes sense to have each entity listen to an event and act on it based on its own business rules.

We can hide the implementation by exposing a simple Pub/Sub interface, where the Ball entity knows when its position means a Score has to be published so other entities can wait on this event to take action.

A Score at its simplest can be just a boolean value, indicating which side just scored.

type ScoreEvent struct {Left  boolRight bool}type ScoreDispatcher interface {Dispatch(ScoreEvent)AddListener(ScoreListener)}type ScoreListener interface {OnScore(ScoreEvent)}

User Input

For now, we know how the entities will interact amongst themselves, but we are still left with the question of how the Player interacts with its Paddle.

As you can see, the Player hasn’t been defined as an entity in our domain, which makes sense since the player acts as an external interface that can alter the current domain state.

To keep the Player abstract and also allowing our domain library agnostic, we can use the DDD Infrastructure Layer.

The infrastructure layer exposes a simple API with domain understandable information. Okay, this sounds a bit too abstract… Let’s see how we could expose the player inputs to the domain.

// KeyboardEvent Representation of a user input on a keyboardtype KeyboardEvent struct {Keydown uint8Key     Key}type Key intconst (ArrowUp Key = iota // representation of Arrow Up keyArrowDown // representation of Arrow Down key)type KeyboardDispatcher interface {Dispatch(domain.KeyboardEvent)AddListener(domain.KeyBoardListener)}type KeyBoardListener interface {OnKeyBoard(KeyboardEvent)}

Exposing this simple API, the domain does not know who is taking the action nor what library is allowing this to happen. We may even allow multiple entities to act upon the keyboard events.

Let it Run!

Now we know how every part will interchange its information to make the game get to an end state, but how does it MOVE?

Well, the game acts as a movie, it changes its state at a high frame per second rate.

type GameLoop interface {RunFrame(delta float32)
GetStatus() Status
}
type Status int
const (Start Status = iotaPlayingGameOver)

This interface allows our application to let the game be re-rendered over and over again while keeping track of the status to stop the rendering and display the game over screen.

Thank you for getting this far, that’s it for today.

Please leave your feedback or any suggestion and keep being awesome!

Also, check out the whole project on my Github repo.

--

--