Checkers

Front-end practice, part 2.

Introduction

Working on the Tetris game in the last post was good practice for purely front-end coding, but that page literally had no server-side state at all. I don’t really want to set up any kind of persistent storage yet, so as my next project I decided to write a multiplayer Checkers game, caching active games in memory only.

(Side note, apparently Checkers is called “Draughts” everywhere but the US, which was news to me when I checked the canonical rules on Wikipedia. As far as I can tell this is one of those things like the Metric system where we’re clearly wrong, unlike driving on the left side of the road, where the British Isles are clearly wrong.)

Architecture

This project had the advantage of forcing me to get up to speed on ASP.NET Core - the last time I was doing server-side coding in C# (fall 2016) it barely existed. Fortunately it’s just not very complicated - making a set of classes that actually play the game properly was more work.

I also thought it was probably a good idea to start using TypeScript, because I know I won’t be able to put up with weak dynamic typing forever. I’ll skip the traditional religious argument here; apparently this is just one of those things that you get ingrained into you in your first real job, and either you spend years working in Javascript and think it’s totally fine or you spend years working in a static, strongly-typed language and you think Javascript’s type system is like coding in oven mitts while being stabbed at random intervals.

The biggest challenge I ran into with this was that both the client and the server needed some understanding of possible moves in… Draughts. (This is also pronounced “drafts”, to my chagrin, but again, I accept that I am wrong.) I started to write the logic for valid moves in both C# and Typescript and then couldn’t bring myself to finish it, so instead I decided on a pattern where the server would actually send back a list of valid moves for the current situation (in addition to other state updates), and the client-side code would simply allow the user to pick any move on the list.

This pattern seemed pretty nice and I suspect it would work for a lot of board/card games. In fact, I was rather proud of myself for coming up with it… which made me think it’s probably an established pattern in game programming. (I couldn’t find a standard name for it with a quick search, but I see a lot of people before me have done something similar. I can never decide if I should feel more or less proud of myself for inventing something that people have already invented - I clearly lose points for originality, but on the other hand it’s good to know the idea has stood the test of time.)

The initial version of the client code also used the Fetch API to communicate with the server, which forced me to poll for state changes at regular intervals whenever it was the other player’s turn. After I got this working I went back and re-implemented the client code in SignalR, which actually took way less effort - I probably went from “opening my first bit of SignalR documentation” to “the game works again like it did before, but with SignalR” in less than an hour. I do wonder if I’ll find weird edge cases that work poorly with SignalR later, but it was nice break from client-side web development where everything seems to require new and different toolchains (e.g., I used Grunt when I was writing Tetris, but all of the ASP.NET tutorials used Webpack and I didn’t want to focus a lot of time on either of them, so I just switched and dealt with it). I will skip the rest of this rant, but unless you spend no time at all on the Internet reading about front-end dev practices other than this one blog, I’m sure you can fill in the rest in your head.

The server-side code for all of this is pretty simple - games are cached in-memory on the server (and cleaned up if you stop playing them for an hour). As a quick-and-dirty solution I just generate a unique GUID for all games and you can copy it off of the page to send it to somebody else. (Apparently adding a button to copy some text off a page is a surprising chore, but I already said I wasn’t going to do a cliché frontend rant.)

Visuals

I spent some time literally color-sampling pictures of wooden game pieces for the colors, and fiddled with SVGs for a while to give the pieces a sense of depth, which allowed me to represent “kings” in the standard physical way of two stacked pieces. (I mostly did this because I thought it was too much of a pain to reproduce a “crown” decal in SVG, although ironically I had to learn enough about <path> that it probably wouldn’t have been much harder.)

Some Bad Decisions I Made

I felt like this deserved its own section because there were a couple big ones.

First, I initially made this too hard on myself by trying to handle successive jumps by the same piece as “a single move”. For some reason I thought this would “simplify” things by making sure that moves were always taken by alternating players and I never had to store the state of “only this piece can move and only if it’s a jump”, but building the full tree of possible moves was definitely harder (and vaguely reminiscent of a Google interview problem, although probably not a challenging enough one). I think I wasted several hours on this total before accepting that adding two properties to the game DTO (“current player” and “active piece, if any”) was obviously less work.

Second - and I never fixed this - I initially decided it would be better to make the “move” format on the wire human-readable, by which I for some reason apparently meant “use chess notation” because I guess humans can’t possibly make sense of a pair of integers. This is presumably a really useful feature if any professional chess players stumble across my obscure chdraughts implementation and have enough computer knowledge to debug it but not enough to understand 0-based indices. In the real world it just means there’s an extra confusing layer on everything where I convert to and from chess notation all of the time, which of course I have to implement in both C# and Typescript, and at at least one point I messed it up and inverted the vertical coordinates, because row 8 in chess notation is subarray 0 on the board and of course I would eventually mess that up.

Final Thoughts

At this point I’m going to declare this project “done”, although I know when I come back to post this I’ll probably panic and fix some remaining UI issues. The only major one of these is that I didn’t give the player the ability to stop jumping pieces when they could theoretically continue doing so in a sequence - there must be a valid scenario where you would want to do this, right? I’ll consult Jonathan Schaeffer.

I really meant for that last link to be a quick joke but I do want to take a moment to acknowledge that not only is the “World Checkers/Draughts Championship” a real thing, and not only did a prominent AI researcher set up the same sort of gripping man-vs-machine competition as Garry Kasparov and Deep Blue, but to my surprise those two competitions happened at basically the same time. Deep Blue first beat Kasparov in 1996 and Schaeffer’s Chinook beat then-world-champion Don Lafferty in what I’m sure was an utterly gripping 32-game match (final score: 1 win for Chinook, 31 draws) in 1995. This blows me away as somebody who intuitively can’t shake the sense that this is like building an AI system to beat people at Monopoly or Chutes and Ladders or something. (Before you ask, yes, I realize Chutes and Ladders does not actually contain any decisions.)

I would have expected the computational complexity of them to be a lot further apart, like how Go has so many more valid positions than Chess (and took a lot longer to make competitive AI for), but apparently per Wikipedia Draughts has something like 10^40 valid game positions, Chess has somewhere from 10^43 to 10^50, and Go has more than 10^170, so… my intuition was not very good here.

What I Learned From This

Like I said in the Tetris post, these projects are entirely to build up my skills in various areas, mostly front-end coding - I’m confident there are other, better implementations of multiplayer CheckersDraughts on the web if you’re looking for one.

So, from this I got:

  • An introduction to ASP.NET Core
  • An introduction to SignalR
  • An introduction to TypeScript
  • Practice with both the Fetch API and Promises in JS
  • Basic familiarity with SVG paths
  • The most useless multicultural knowledge ever
  • A vague sense of annoyance at the proliferation of web development toolchains (although to be honest I already had this)
  • A vague sense of irritation at my own stubborn refusal to dig into web development toolchains

Not sure how to fix that last one. Maybe I can come up with some project that I have to build entirely out of CSS postprocessors or something.

Remaining Issues

As noted above, I never went back and got rid of the weird chess notation conversions, which I would like to do.

I also haven’t taken the time to host this code in AWS, although you can get the full source code and run it yourself. I started to do this, but I want to keep myself on a roughly one-post-a-week cadence for the moment and I don’t want to wait another few days while I read through a bunch of documentation and figure out how to properly integrate this blog (which is run as static website out of an S3 bucket) with an EC2 instance, on the same domain, with a proper SSL certificate.

Some of my naming conventions, particularly around the DTOs, are not really well-thought-out, and I would really like it if I didn’t have to write them repeatedly anyway - eventually I should probably figure out a strategy for generating the TypeScript definitions from the C# definitions. I’m sure there’s lots of solutions for this, but it wasn’t worth researching for a project where I could do it by hand in 5 minutes.

References and Further Reading

The SVG path Syntax: An Illustrated Guide Use ASP.NET Core SignalR with TypeScript and Webpack Tutorial: Create a web API with ASP.NET Core How do I copy to the clipboard in JavaScript? [StackOverflow]


Timothy Bond

1823 Words

2020-05-09 08:25 -0700