Today’s plan is to read the chapters in Eloquent JavaScript leading up to and including the project of building a simple platform game in the browser (for which I plan to translate the code to TypeScript and solve some of the exercises).

Eloquent JavaScript

Day 7 Code

Chapters 13 - 16

  • Intro to the browser, DOM, etc
  • Project: a Platform Game

Project: Platform Game

Adding type annotations to the basic game setup code from the book took quite a bit of debugging, due to a few issues.

First, TypeScript / ECMA script versions and features took some trial and error. I ended up updating the tsconfig file to have compile flags as follows:

    "compilerOptions": {
        "lib": ["dom", "es2017"],
        "target": "esnext",
        "module": "es2020",
        ...

This silenced most, but not all errors pulled in by flycheck. For example, it still complains about find not being a proeprty of an array, even though, as far as I can tell, it’s part of the language spec (since what version?).

And even though the target is set to esnext, the compiler still shows a few errors when esnext is not set as a command-line option.

tsc game.ts -&& node game.js doesn’t work, but tsc game.ts -t esnext && node game.js does work.

I’ll chalk that up to unfamiliarity with TypeScript configs, but it’s a bit baffling how a new-ish user is supposed to work out the myriad of langauge spec / config / build flag options.

Next, I couldn’t figure out how to get TypeScript to typecheck the lookup used in the board cell constructor.

In the level initialization, the function to create row cells looks up the input character (ch) from the levelChars object, and either returns the resulting string, or uses the resulting class’s method to create an actor (and return the string “empty”).

    ...
        let type = levelChars[ch];
        if (typeof type == "string") return type;
        this.startActors.push(
          type.create(new Vec(x, y), ch));
        return "empty";
    ...

const levelChars = {
  ".": "empty", "#": "wall", "+": "lava",
  "@": Player, "o": Coin,
  "=": Lava, "|": Lava, "v": Lava
};

What is the type of the values of levelChars?

Since Player, Coin, Lava are classes, I thought perhaps string | Person | Coin | Laval, but that doesn’t typecheck. (Perhaps because the class prototype needs to be distinguished somehow from the class’s type?) In any case, since levelChars isn’t used elsewhere, I just pattern matched manually:

if (ch == ".") {
    return "empty";
} else if (ch == "#") {
    return "wall";
} else if (ch == "+") {
    return "lava";
} else {
    const v = new Vec(x, y);
    if (ch == "@") {
        this.startActors.push(Player.create(v));
    } else if (ch == "0") {
        this.startActors.push(Coin.create(v));
    } else if (ch == "=" || ch == "|" || ch == "v") {
        this.startActors.push(Lava.create(v, ch));
    }
    return "empty";
}

.. which is a bit more verbose, but the slightly different shapes of create also woulnd’t have type checked in the original code.

As of this commit, the code compiles with the example board test in the book (before the “Encapsulation as a border” section).

% tsc -t esnext game.ts && node game.js
22 by 9 

Rendering

Hmm… there are so many TypeScript issues with the book code.

The static HTML game map (as a stylized table) looks like this:

Game Home Statuc

I find the pattern of not declaring class methods (because they are class prototype methods) very confusing as a reader of code, and especially so when they are presented out of order in the book.

If I recall correctly from Atencio, assigning to the class prototype avoids instantiating the data or method per class instance, yielding a more efficient runtime. That seems logical, but I’m not sure how to get TypeScript to accept it when compiling strictly.

class A:
    this.foo;

    constructor(foo: Foo) {
        this.Foo = Foo;
    };
}

A.prototype.someMethod = function () {
...
}

TypeScript notes that class A doesn’t have method someMethod. The method can be declared in class A, but TypeScript comaplains that it has no initializer and is not definitely assigned..., which seems fair. The error can be ignored in this case, but there are so many errors that the benefit of having a live, static type checker has been negated!

Interestingly, implenting the prototypal method declarations as interfaces doesn’t type check at all – class A incorrectly implements Interface SomeMethod... property someMethod is missing...

Separately, it took me a while to figure out that HTML elements have type HTMLElement. I’m still not sure what the most specific type is for HTML attribrutes ({ [key: string]: string }?)

Wrapping Up

This was the most frustrating day so far, primarily because I’ve self-imposed the additional complication of TypeScript. It’s possible that it’s better to stick with pure JavaScript and embrace it as such, adding in TypeScript later. Either way, I’ll finish the game project in some form tomorrow, or possibly just move on to m another project given the limited time remaining. Early on in learning a new domain, it’s often better to keep moving. It’s more productive to figure out all the details with a bit more context and experience.

Todo / To Read

  • Eloquent JavaScript has exercises at the end of chapters, which might be worth exploring
  • Figure out how to use outDir(?) to compile .js files to separate subdirectory
  • Compiling with both TypeScript with Babel
  • Kyle Simpson, You Don’t Know JS
  • Composing Software, by Eric Elliott (https:// leanpub.com/composingsoftware)