Electron Adventures: Episode 71: CoffeeScript Phaser Game
Tags: javascript, electron, phaser, coffeescript
Published: 2021-10-03 15:20:27

Now that we have CoffeeScript 2 setup, let's create a simple game with Phaser 3.

### `js2.coffee`

This is my first time writing new CoffeeScript in years, and I quickly discovered how painful lack of working [js2.coffee is](http://js2.coffee/). The existing converter only handles pre-ES6 JavaScript, and even that often doesn't generate great code. Being able to convert between JavaScript and CoffeeScript easily was a huge part of CoffeeScript's appeal at the time, and it's now completely gone.

Not that there's anything too complicated about converting JavaScript to CoffeeScript manually, but it's pointless tedium in a language whose primary appeal is cutting on pointless tedium.

### Asset files

I emptied `preload.coffee` as we won't need it.

I added `star.png` and `coin.mp3` to `public/`. There's a lot of free assets on the Internet which you can use in your games.

We'll also need to `npm install phaser`

### `public/index.html`

Here's the updated `index.html` file, just loading Phaser, and adding a placeholder div for `game` `canvas` to be placed at:

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="app.css">
  </head>
  <body>
    <div id="game"></div>
    <script src="../node_modules/phaser/dist/phaser.js"></script>
    <script src="./build/app.js"></script>
  </body>
</html>
```

### `public/app.css`

To keep things simple I decided to just center the game canvas in the browser window, without any special styling:

```css
body {
  background-color: #444;
  color: #fff;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
}

#game {
}
```

### Game source

Let's go through the game code. It's something I wrote a while ago, and just slightly adapted and converted to CoffeeScript for this episode.

### Getters and setters

When CoffeeScript adapted to ES6, a few features were really difficult to add due to syntactic issues.

Dropping some features made sense, like the whole `var/let/const` mess. JavaScript would do just fine having one way to define variables - namely `let`. You might have noticed by now that I never use `const` - if variables declared `const`s were actually immutable, I might change my mind, but I find it both pointless extra thing to think about, and also intentionally misleading. Declaring mutable state with const, as is the standard React Hooks way (`const [counter, setCounter] = useState(0)`), looks like a vile abomination to me. So CoffeeScript never bothering with three variable types makes perfect sense.

Much more questionable is not having getters and setters. They can be emulated with calls to `Object.defineProperty`, but these are ugly and are in wrong place - in constructor instead of being part of class definition. Well, we'll just use what we have, so here's the getter helper:

```coffee
get = (self, name, getter) ->
  Object.defineProperty self, name, {get: getter}
```

### Start game

We define constant size box and create a game using `MainScene` class.

```coffee
size_x = 800
size_y = 600
game = new Phaser.Game
  backgroundColor: "#AAF"
  width: size_x
  height: size_y
  scene: MainScene
```

### `StarEmitter`

When a ball hits a brick, we want to do some fancy effects. An easy effect is bursting some stars, and it's so common Phaser already contains particle emitter system. Here's a class that sets up such emitter with some settings how those stars should fly.

```coffee
class StarEmitter
  constructor: (scene) ->
    @particles = scene.add.particles("star")
    @emitter = @particles.createEmitter
      gravityY: -50
      on: false
      lifespan: 2000
      speedX: {min: -50, max: 50}
      speedY: {min: -50, max: 50}
      alpha: 0.2
      rotate: {min: -1000, max: 1000}

  burst_at: (x, y) ->
    @emitter.emitParticle(40, x, y)
```

### `Brick`

```coffee
class Brick
  constructor: (scene, x, y) ->
    colors_by_row = {
      2: 0xFF0000
      3: 0xFF0080
      4: 0xFF00FF
      5: 0xFF80FF
      6: 0x8080FF
      7: 0x80FFFF
    }
    @destroyed = false
    @brick_x_size = size_x/18
    @brick_y_size = size_y/30
    @brick = scene.add.graphics()
    @brick.x = x*size_x/12
    @brick.y = y*size_y/20
    @brick.fillStyle(colors_by_row[y])
    @brick.fillRect(
      -@brick_x_size/2, -@brick_y_size/2,
      @brick_x_size, @brick_y_size
    )
    get @, "x",-> @brick.x
    get @, "y",-> @brick.y

  destroy: ->
    @brick.destroy()
    @destroyed = true
```

`Brick` is a straightforward class wrapping Phaser `brick` object. You can see how one can do getters in CoffeeScript. It works, but it's a bit awkward.

The only method Brick has is `destroy`.

### `Ball`

```coffee
class Ball
  constructor: (scene) ->
    @ball = scene.add.graphics()
    @ball.x = 0.5*size_x
    @ball.y = 0.8*size_y
    @ball.fillStyle(0x000000)
    @ball.fillRect(-10,-10,20,20)
    @dx = 300
    @dy = -300
    get @, "x", -> @ball.x
    get @, "y", -> @ball.y

  update: (dt) ->
    @ball.x += @dx*dt
    @ball.y += @dy*dt
    if @ball.x <= 10 && @dx < 0
      @dx = - @dx
    if @ball.x >= size_x-10 && @dx > 0
      @dx = - @dx
    if @ball.y <= 10 && @dy < 0
      @dy = - @dy
```

The `Ball` has similar messy getter. The only method is `update` which is passed how much time passed since the last update, and it's responsible for ball bouncing off the walls, but not bouncing off paddle or bricks.

### `Paddle`

```coffee
class Paddle
  constructor: (scene) ->
    @paddle = scene.add.graphics()
    @paddle.x = 0.5*size_x
    @paddle.y = size_y-20
    @paddle.fillStyle(0x0000FF)
    @paddle.fillRect(-50, -10, 100, 20)
    get @, "x", -> @paddle.x

  update: (dt, direction) ->
    @paddle.x += dt * direction * 500
    @paddle.x = Phaser.Math.Clamp(@paddle.x, 55, size_x-55)
```

`Paddle` follows the same pattern. Its `direction` is sent to the `update` method depending on which keys are pressed, and it moves left or right. `Phaser.Math.Clamp` prevents it from going outside the canvas.

### `MainScene`

```coffee
class MainScene extends Phaser.Scene
  preload: () ->
    @load.image("star", "star.png")
    @load.audio("coin", "coin.mp3")

  create: () ->
    @active = true
    @paddle = new Paddle(@)
    @ball = new Ball(@)
    @bricks = []
    for x from [1..11]
      for y from [2..7]
        @bricks.push(new Brick(@, x, y))
    @emitter = new StarEmitter(@)
    @coin = @sound.add("coin")
    @coin.volume = 0.2

  handle_brick_colission: (brick) ->
    return if brick.destroyed
    distance_x = Math.abs((brick.x - @ball.x) / (10 + brick.brick_x_size/2))
    distance_y = Math.abs((brick.y - @ball.y) / (10 + brick.brick_y_size/2))
    if distance_x <= 1.0 && distance_y <= 1.0
      brick.destroy()
      @emitter.burst_at(@ball.x, @ball.y)
      @coin.play()
      if distance_x < distance_y
        @ball_bounce_y = true
      else
        @ball_bounce_x = true

  is_game_won: () ->
    @bricks.every((b) => b.destroyed)

  update: (_, dts) ->
    return unless @active
    dt = dts / 1000.0
    @ball.update(dt)
    if @input.keyboard.addKey("RIGHT").isDown
      @paddle.update(dt, 1)
    else if @input.keyboard.addKey("LEFT").isDown
      @paddle.update(dt, -1)
    @ball_bounce_x = false
    @ball_bounce_y = false
    for brick from @bricks
      @handle_brick_colission(brick)
    @ball.dx = -@ball.dx if @ball_bounce_x
    @ball.dy = -@ball.dy if @ball_bounce_y

    paddle_distance = Math.abs(@paddle.x - @ball.x)
    bottom_distance = size_y - @ball.y

    if @ball.dy > 0
      if bottom_distance <= 30 && paddle_distance <= 60
        @ball.dy = -300
        @ball.dx = 7 * (@ball.x - @paddle.x)
      else if bottom_distance <= 10 && paddle_distance >= 60
        @cameras.main.setBackgroundColor("#FAA")
        @active = false
    if @is_game_won()
      @cameras.main.setBackgroundColor("#FFF")
      @active = false
```

And finally the `MainScene`. `preload`, `create`, and `update` are Phaser methods. Everything else we just created ourselves.

I think everything should be fairly readable, as long as you remember that `@foo` means `this.foo`, so it's used for both instance variables and instance methods.

### Is CoffeeScript dead?

While I feel nostalgia for it, the unfortunate answer is yes. I mentioned some historical background in previous episode, but ES6 adopted most of the features people used CoffeeScript for, and available tooling did not keep up with the times.

That's not to say the idea is dead. In particular [Imba](https://imba.io/) is a CoffeeScript-inspired language and framework that's absolutely worth checking out. It comes with an extremely expressive and performant framework. For some less extreme cases, Svelte, Vue, React, and so on all come with their own extended versions of JavaScript, so nobody really writes app in plain JavaScript anymore.

### Results

Here's the results:

![Episode 71 Screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qavzad6pqpgcnat7le96.png)

It's time to say goodbye to CoffeeScript, in the next episode we start another small project.

As usual, [all the code for the episode is here](https://github.com/taw/electron-adventures/tree/master/episode-71-coffeescript-phaser-game).