(September 2015)

Fork me on GitHub For the TL;DR crowd:

If you are are a developer, read on to see how I ported some old code from JavaScript and Canvas (i.e. dynamically-typed, imperative drawing) to TypeScript and React/JSX (statically-typed, functional style, state-driven - with compile-time checks of HTML syntax). The code can be downloaded from here.

And developer or not, you have to play Score4 below... and try to beat my AI :-)

From JS/Canvas, to...

Score4 - Connect4
The Score 4 (Connect 4) game.

Four years ago I implemented the AI Minimax algorithm and used it to play a game called Score4 / ConnectFour.

I did this after discovering the difference of doing things in functional (as opposed to imperative) style, coding it in many languages, and finally porting it to Javascript. The game was now accessible to any machine with a decent browser. Phones, tablets, desktops and laptops could all play it - One Platform to Rule Them All :-)

Years passed...

And as they did, the programming world - as usual - re-discovered some old truths...

...which I wanted to put to the test.

GUIs are hard - meet React

GUIs are very hard to code properly ; but can be created in an easier way, if you represent your user interface as a function, that gets your state as input. Whenever your state changes, you call that function, and its job is to make sure that your new state shows up.

Equally important - this "magic function" can actually do a significant optimization: it can see which parts of your UI are associated with which parts of your state, and only update the displayed parts that were actually impacted by your state changes.

Clearer code **and** faster execution - could it be true?

Return of the Types - meet TypeScript

I love Python ; and appreciate Javascript and Clojure the Powerful, and generally speaking, the clarity and brevity that usually accompanies dynamically typed languages. You don't prefix each and every one of your variables with type specifications, so only the "core" of your logic is left behind. Go the extra mile and avoid loops, using immutable maps and filters and folds, and you're all set :‑)

But let's be honest about something.

The lack of these type specifications means... that the errors that were traditionally caught by compilers... now blow up in your face.

At run-time. With your clients yelling at your support people.

And when you need to refactor... you must be really careful. As in, you have to create big test suites, and make sure they exercise large parts of your code (also known as "be religious about your test suite's code coverage"...). If you don't, code refactoring becomes an interesting exercise.

teething baby biting snake
I love Python

In hindsight, I think we gave up on compile-time checks too easily... what we really "longed for" was in fact cleaner, more expressive syntax (OK - that, and the rapid edit-run cycles). Personally, I've immersed my backend work in PyLint and Pyflakes and PyCharm's inspector... but all those tools still don't compare with what a language with a strong type system provides.

And as fate would have it, in the frontend, TypeScript - an open-source statically typed language that builds on top of Javascript, recently acquired support for a big part of React: JSX syntax.

Time to play

That was all the incentive I needed - I wanted to taste React for quite some time, so I decided to put these two technologies to the test.

To make the test more representative of what a real application would do, I also wanted to try to make my UI as declarative as possible - which means that instead of using imperative Canvas drawing commands, I wanted to switch the code to CSS-based rendering of the game's board.

Step 1: CSS

I started with the easy part - removing all the Canvas drawing code, and all the related artifacts... like mouse-click-to-column-mapping code. This would now be handled by onClick DOM handlers.

I then had to figure out how to create a board using only HTML/CSS. I am not a frontend engineer, so... this required a bit of digging. Eventually I settled on a simple HTML table, with CSS-generated circles as tiles (inside the table cells).

.no_coin {
    width: 50px;
    height: 50px;
}

.red_coin {
    border-radius: 50%;
    width: 50px;
    height: 50px;
    display: inline-block;
    background: red;
}
If a div has a class of red-coin then it will show up in the board's table as a red game tile:
<table class="grid_table">
    <tr>
        <td><div class="red_coin"></td><
        ...
red tile

Step 2: State

Next step: state... What is the state of my game - the one that influences my UI?

Well...

And that's it, really:

class Score4State {
    board: number[][];
    info: string;
    wins: number;
    losses: number;
}

Given an instance of this state, how do we render it with React to the table elements that compose our UI?

Step 3: JSX

Turns out this part was easier than I thought. We begin by telling React to render our Score4 component in our page, at the place where we have a board div:

showComponent() {
    React.render(
        <Score4 />, document.getElementById('board'));
}
<div id="board" align="center"></div>

And our component will traverse the board, emitting the cells based on their state...

To ease the traversal, since poor Javascript lacks a range, I provided one:

var range = (n:number) => {
    var result = [];
    for(var i=0; i<n; i++) 
        result.push(i);
    return result;
};

...and used it to loop vertically and horizontally on my board, generating React elements as I went:

var proclaim = 
    (n:number) => String(n) + " victor" + (n === 1? "y.":"ies.")
;
return (
    <div>
        <span style={{color:"green"}}>
        <b>You</b>:</span> {proclaim(this.state.wins)}
        <div style={{display:'inline-block'}}>
            <table className={"grid_table"}>
            {
                range(Score4_AI.height).map( y => (
                        <tr key={y}>
                        {
                            range(Score4_AI.width).map( 
                                x => cellMaker(y, x)
                            )
                        }
                        </tr>
                    )
                )
            }
            </table>
            <p>{this.state.info}</p>
            <button type="button" onClick={resetAndRepaint}><b>New game</b></button>
        </div>
        <span style={{color:"red"}}>
        <b>CPU</b>:</span> {proclaim(this.state.losses)}
    </div>
);

This looks very PHP-ish... until you realize that everything is checked for correctness at compile-time. Try modifying any part of the HTML in that code, say rename the style attribute to styl, and watch the TypeScript compiler complain...

score4.tsx(148,23): error TS2339: Property 'styl' does not exist on type 'HTMLAttributes'.</pre>

And of course that doesn't just apply to your HTML "template" - try goofing on the code itself, mis-typing yy instead of y, and...

score4.tsx(155,42): error TS2304: Cannot find name 'yy'.</pre>

Complete integration between the two worlds...

Wow.

And you are probably wondering about that cellMaker; why didn't I just emit the <td>s in my inner "loop"?

Well, that was just by choice. In my humble opinion, separating them is cleaner. It also shows that generation of React components can be split up in whatever manner we choose:

var cellMaker = (y:number, x:number) => {
    return (
        <td key={x} onClick={self.handleClick.bind(self, x)}>
            <div
                className={
                    (() => {
                        switch(self.state.board[y][x]) {
                        case  4: return "red_won_coin";
                        case  1: return "red_coin";
                        case -1: return "green_coin";
                        case -4: return "green_won_coin";
                        default: return "no_coin";
                        }
                    })()
                }
            />
        </td>
    );
};

Notice that JSX expects expressions in the JS bodies - and since switch is a statement in TypeScript, I had to enclose it in an anonymous function and immediately call it.

Was it worth it, in the end?

Let me start by saying that the code that renders the board is now something like 40% of the corresponding Canvas code.

But that's just the cherry on top - what really matters, is that inside that handleClick member of my class, I just update the state...

var newBoard = this.dropDisk(this.state.board, column, -1);
...
this.setState({
    board: newBoard,
    info: msg,
    wins: newWins,
    losses: newLosses,
});

That's all there is to it - React will automatically figure out what changed since last time, and will update the page to reflect the changes.

Wow. "Oh my God" kind of wow.

Conclusion

Well, frontend development has certainly changed a lot since the age of jQuery... Even though I only ported a simple game, with a UI layer of 200 lines of code, the difference in the mechanics used is very impressive. I can only imagine what React's impact will be in major UIs with complex state - and how many errors TypeScript will shield you from.

For example, to create the React component, I had to explicitely pass the type of my state (Score4State)...

class Score4 extends React.Component<any, Score4State> {
    brain: Score4_AI;
    ...

...and tsc (the TypeScript compiler) guided me when I misused my this.state.<fieldname> accesses.

Overall, I am very pleased to see how all these technologies have evolved. And if I ever get back into frontend coding again, I will definitely make use of them.

Now go play Score4, above :-)


profile for ttsiodras at Stack Overflow, Q&A for professional and enthusiast programmers
GitHub member ttsiodras
 
Index
 
 
CV
 
 
Updated: Sat Oct 8 11:41:25 2022