Do you like my tutorials?

Then consider supporting me on Ko-fi

Talking about Golf Dash game, Game development, HTML5, Javascript, Phaser and TypeScript.

Golf Dash has a curious story. Initially developed using OpenFL by Osylisor, it has been converted for Commodore 64 by Docster and it’s a really fun game.

Now it’s time to port it back again to HTML5 using Phaser and a TypeScript class with no dependencies, so you can use it in your own projects and frameworks.

This will be the 4th game in my 101 Games Challenge so I am showing you just a prototype because the full source code will be available next week when the game will be released.

Look at the game:

Move the ball with ARROW or WASD keys, the ball will keep on moving until it hits a wall.

There are only a few levels in this prototype, but in the final game there will be 31 levels, pushable crates and switchable blocks.

Look at the commented source code which consists in one HTML file, one CSS file and six TypeScript files.

The code isn’t that commented at the moment, but I’ll add more comments In later examples.

index.html

The web page which hosts the game, to be run inside thegame element.

HTML
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script type="module" src="/src/main.ts"></script>
  </head>
  <body>
    <div id="thegame"></div>
  </body>
</html>

style.css

The cascading style sheets of the main web page.

CSS
/* remove margin and padding from all elements */
* {
  padding : 0;
  margin : 0;
}

/* set body background color */
body {
  background-color : #000000;    
}

/* Disable browser handling of all panning and zooming gestures. */
canvas {
  touch-action : none;
}

gameOptions.ts

Configurable game options.

It’s a good practice to place all configurable game options, if possible, in a single and separate file, for a quick tuning of the game.

TypeScript
// CONFIGURABLE GAME OPTIONS
// changing these values will affect gameplay

export const GameOptions : any = {

    tileSize                : 32,
    ballSpeed               : 30
    
}

levels.ts

Array with all game levels.

TypeScript
export const Levels : number[][][] = [
	[
		[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
		[1, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
		[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1],
		[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 1],
		[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 1],
		[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
		[1, 2, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1],
		[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
	],
	[
		[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
		[1, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
		[1, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1],
		[1, 2, 2, 3, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 4, 2, 1],
		[1, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1],
		[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 2, 1],
		[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 1, 2, 1],
		[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 1, 1, 1, 2, 1],
		[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 1],
		[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 2, 1],
		[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],
	],
	[
		[1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
		[1, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 2, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 4, 2, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 1, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 1, 1, 1, 1, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 1, 2, 2, 1, 2, 1, 1, 1, 1, 1],
		[1, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 1, 1, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 1, 1, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 1, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
		[1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	],
	[
		[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
		[1, 1, 1, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1],
		[1, 1, 1, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2, 1, 1],
		[1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1],
		[1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1],
		[1, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1],
		[1, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1],
		[1, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1],
		[1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1],
		[1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1],
		[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
	],
	[
		[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
		[1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
		[1, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
		[1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
		[1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
		[1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1],
		[1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
		[1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
		[1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
		[1, 2, 1, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2, 2, 1],
		[1, 2, 1, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
		[1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1],
		[1, 2, 1, 2, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
		[1, 2, 1, 2, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
		[1, 2, 1, 3, 1, 1, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
		[1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
		[1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
		[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	],
    [
        [1, 1, 1, 1, 1],
        [1, 3, 1, 4, 1],
        [1, 1, 1, 1, 1]
    ]	
]   

main.ts

This is where the game is created, with all Phaser related options.

TypeScript
import 'phaser';
import { PreloadAssets } from './scenes/preloadAssets'; // preloadAssets scene
import { PlayGame } from './scenes/playGame';           // playGame scene
import './style.css';


 let configObject : Phaser.Types.Core.GameConfig = {
    scale : {
        mode        : Phaser.Scale.FIT,
        autoCenter  : Phaser.Scale.CENTER_BOTH,
        parent      : 'thegame',
        width       : 1440,
        height      : 810
    },
    scene           : [
        PreloadAssets,
        PlayGame
    ]
};
 
new Phaser.Game(configObject);

scenes > preloadAssets.ts

Here we preload all assets to be used in the game.

TypeScript
// CLASS TO PRELOAD ASSETS

// modules to import
import { GameOptions } from "../gameOptions";

// PreloadAssets class extends Phaser.Scene class
export class PreloadAssets extends Phaser.Scene {
  
    // constructor    
    constructor() {
        super({
            key : 'PreloadAssets'
        });
    }
  
    // method to be called during class preloading
    preload() : void {
        
        // load a spritesheet
        this.load.spritesheet('tiles', 'assets/sprites/tiles.png', {
            frameWidth  : GameOptions.tileSize,
            frameHeight : GameOptions.tileSize
        });
    }
  
    // method to be executed when the scene is created
    create() : void {

        // start PlayGame scene
        this.scene.start('PlayGame');
    }
}

scenes > playGame.ts

Main game file, all game logic is stored here.

TypeScript
// THE GAME ITSELF

// modules to import
import { Levels }               from '../levels';
import { GameOptions }          from '../gameOptions';
import { GameBoard, TileType }  from '../gameBoard';

// PlayGame class extends Phaser.Scene class
export class PlayGame extends Phaser.Scene {
   
    constructor() {
        super('PlayGame');
    }

    board           : GameBoard;
    golfTilesGroup  : Phaser.GameObjects.Group;
    
    create() : void {

        this.data.set({
            level : 0,
            moves : 0
        })
        
        this.golfTilesGroup = this.add.group();
        this.board = new GameBoard();
       
        const keyboard : Phaser.Input.Keyboard.KeyboardPlugin = this.input.keyboard as Phaser.Input.Keyboard.KeyboardPlugin; 
        keyboard.on('keydown', (event : any) => {
            switch (event.code) {
                case 'KeyW' :
                case 'ArrowUp' :
                    this.move(-1, 0);
                    break;
                case 'KeyS' :
                case 'ArrowDown' :
                    this.move(1, 0);
                    break;
                case 'KeyA' :
                case 'ArrowLeft' :
                    this.move(0, -1);
                    break;
                case 'KeyD' :
                case 'ArrowRight' :
                    this.move(0, 1);
                    break;
            }
        });

        this.buildLevel(this.data.get('level'));  
    }

    buildLevel(level : number) : void {
        
        this.board.loadLevel(Levels[level]);
        
        const levelWidth : number = this.board.getColumns() * GameOptions.tileSize;
        const levelHeight : number = this.board.getRows() * GameOptions.tileSize;

        const deltaX : number = (this.game.config.width as number - levelWidth + GameOptions.tileSize) / 2;
        const deltaY : number = (this.game.config.height as number - levelHeight + GameOptions.tileSize) / 2;

        for (let i : number = 0; i < this.board.getRows(); i ++) { 
            for (let j : number = 0; j < this.board.getColumns(); j ++) {
                const posX : number = deltaX + j * GameOptions.tileSize;
                const posY : number = deltaY + i * GameOptions.tileSize; 
                switch (this.board.getValueAt(i, j)) {
                    case TileType.WALL :
                        this.addTile(posX, posY, 0, 0);
                        break;
                    case TileType.GRASS :
                        this.addTile(posX, posY, (i + j) % 2 == 0 ? 1 : 2, 0);
                        break;
                    case TileType.BALL :
                        this.addTile(posX, posY, (i + j) % 2 == 0 ? 1 : 2, 0);
                        this.board.ball.data = this.addTile(posX, posY, 3, 2);
                        break;
                    case TileType.HOLE :
                        this.addTile(posX, posY, (i + j) % 2 == 0 ? 1 : 2, 0);
                        this.addTile(posX, posY, 4, 1);   
                        break;                  
                }
            }
        }

        this.tweens.add({
            targets : this.golfTilesGroup.getChildren(),
            scale : 1,
            duration : 500,
            onComplete : () => {
                this.board.ball.canMove = true;
            }    
        });        
    }

    addTile(x : number, y : number, frame : number, depth : number) : Phaser.GameObjects.Sprite {
        return this.golfTilesGroup.get(x, y, 'tiles').setFrame(frame).setActive(true).setScale(0).setVisible(true).setDepth(depth);    
    }

    move(deltaRow : number, deltaColumn : number) : void {
        
        if (!this.board.ball.canMove) {
            return;
        }

        this.board.ball.canMove = false;
        const movement : any = this.board.move(deltaRow, deltaColumn);
        
        if (movement.ballMovement.delta.row != 0 || movement.ballMovement.delta.column != 0) {
            this.data.inc('moves');
        }

        if (movement.ballMovement.delta.row != 0 || movement.ballMovement.delta.column != 0) {
            this.moveBall(movement);   
        }
        else {
            this.board.ball.canMove = true;
        }
    }

    moveBall(movement : any) : void {

        this.tweens.add({
            targets : this.board.ball.data,
            x : this.board.ball.data.x + GameOptions.tileSize * movement.ballMovement.delta.column,
            y : this.board.ball.data.y + GameOptions.tileSize * movement.ballMovement.delta.row,
            duration : GameOptions.ballSpeed * (Math.abs(movement.ballMovement.delta.row) + Math.abs(movement.ballMovement.delta.column)),
            onComplete : () => {
                if (this.board.ball.isInHole) {
                    this.tweens.add({
                        targets : this.board.ball.data,
                        scale : 0,
                        duration : 500,
                        onComplete : () => {
                            this.tweens.add({
                                targets : this.golfTilesGroup.getChildren(),
                                scale : 0,
                                duration : 500,
                                onComplete : () => {
                                    this.golfTilesGroup.getChildren().forEach((child : Phaser.GameObjects.GameObject) => {
                                        this.golfTilesGroup.killAndHide(child);
                                    });
                                    this.data.inc('level');
                                    this.buildLevel(this.data.get('level'));    
                                }  
                            });
                        }
                    });
                }
                else {
                    this.board.ball.canMove = true;    
                }
            } 
        }) 
    }
}

gameBoard.ts

TypeScript class with no dependencies to handle game board.

TypeScript
export enum TileType {
    EMPTY,
    WALL,
    GRASS,
    BALL,
    HOLE
}

export class GameBoard {

    tiles : Tile[][]; 
    ball : Ball;

    constructor() {
        this.ball = new Ball();    
    }

    loadLevel(level : number[][]) {
        this.ball.canMove = false;
        this.ball.isInHole = false;
        this.tiles = [];
        for (let i : number = 0; i < level.length; i ++) {
            this.tiles[i] = [];
            for (let j : number = 0; j < level[i].length; j ++) {
                this.tiles[i][j] = new Tile(level[i][j]);
                switch (level[i][j]) {
                    case TileType.WALL :
                        this.tiles[i][j].isWalkable = false;
                        break;
                    case TileType.BALL :
                        this.ball.row = i;
                        this.ball.column = j;
                        break;
                    case TileType.HOLE :
                        this.tiles[i][j].isHole = true;
                        break;
                }
            }
        }
    }

    isValidMove(deltaRow : number, deltaColumn : number) : boolean {
        return this.tiles[this.ball.row + deltaRow][this.ball.column + deltaColumn].isWalkable;
    }
 
    move(deltaRow : number, deltaColumn : number) : any {
        let boardMovement : any = {
            ballMovement : {
                delta : {
                    row : 0,
                    column : 0
                }
            }
        };   
        if (!this.isValidMove(deltaRow, deltaColumn)) {
            return boardMovement;
        }   
        let movement : number = 0;
        while (this.tiles[this.ball.row + deltaRow][this.ball.column + deltaColumn].isWalkable && !this.ball.isInHole) {
            this.ball.row += deltaRow;
            this.ball.column += deltaColumn;
            movement ++;
            if (this.tiles[this.ball.row][this.ball.column].isHole) {
                this.ball.isInHole = true;
            }
        }
       
        if (movement > 0) {
            boardMovement.ballMovement = {
                delta : {
                    row : movement * deltaRow,
                    column : movement * deltaColumn
                },
                from : {
                    row : this.ball.row - movement * deltaRow,
                    column : this.ball.column - movement * deltaColumn
                },
                to : {
                    row : this.ball.row,
                    column : this.ball.column
                }
            }
        }

        return boardMovement;
    }

    

    getTileAt(row : number, column : number) : Tile {
        return this.tiles[row][column];
    }

    getValueAt(row : number, column : number) : number {
        return this.tiles[row][column].value;
    }

    getRows() : number {
        return this.tiles.length;
    }

    getColumns() : number {
        return this.tiles[0].length;
    }
}

class Tile {

    value       : number;
    isWalkable  : boolean;
    isHole      : boolean;

    constructor (value : number) {
        this.isWalkable = true;
        this.isHole = false;
        this.value = value;
    }
}

class Ball {

    row         : number;
    column      : number;
    data        : any;
    canMove     : boolean;
    isInHole    : boolean;

    constructor() {}
}

With these few lines of code, you can start building your Golf Dash game. Download the full source code along with the Vite project. Don’t know what I am talking about? There’s a free minibook to get you started.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.