By Robert Laing
Wun of the brilliant ideas in JavaScript is the object literal. It is a pleasant and expressive syntax for clustering information. — Douglas Crockford in How JavaScript Works
Like most programers, I enjoy coding games, and this website is a growing collection of games I’m writing to improve my JavaScript, CSS and HTML skills.
Something I learnt from a free online course General Game Playing is that games are state machines, and an important part of this is to represent the current state so that it can be visually rendered, and that legal moves and next states can be figured out from the code.
JavaScript has a really nice way of representing the current state of a game by using what it calls an object literal.
Object Literals are comma separated key-value pairs between curly brackets which differ from traditional OOP classes in that they don’t require instantiation using the new operator.
const state = {"game": "game-name"};I’ve settled on using const rather than let
so as to standardize the way I do modularization, using
mutators rather than functions.
Get into a rut early: Do the same process the same way. Accumulate idioms. Standardize. The only difference between Shakespeare and you was the size of his idiom list - not the size of his vocabulary. — Alan Perlis’s Perlism No. 10
Something that confused me, and I suspect most JavaScript novices, is
that while using const prevents you from redeclaring
state or whatever you call your object literal, you are still
free to add, overwrite, and delete variables agglutinated to it via dots
or brackets assuming Object.freeze()
hasn’t been used.
Once you have created an object literal, JavaScript places no constraints on what you can put in it, but limiting it to what JSON.stringify(state) can handle has several advantages:
As explained in the Mozilla Developer article linked to above, JSON
is a subset of JavaScript object syntax. Keys have to be double quoted
as in “key”, and there’s no undefined value.
Sturgeon’s Law very much applies to JavaScript which was unfortunately forced to be a “multi-paradigm” language, leading to lots of synonymous ways of doing things. I follow Crockford’s advice to do “class free” programing, ignoring all the crap imported into JavaScript from Java.
A programming language is low level when its programs require attention to the irrelevant. — Alan Perlis’s Perlism No. 8
I like to store the setup values of my game state in a JSON file rather than a JavaScript file:
{
"Attack1": [],
"Attack2": [],
"Attack3": [],
"Attack4": [],
"Damage": [],
"Hand1": "./cards/blank.svg",
"Hand2": "./cards/blank.svg",
"Hand3": "./cards/blank.svg",
"Monster1": [],
"Monster2": [],
"Monster3": [],
"Monster4": [],
"PowerDeck": [],
"game": "clear-the-dungeon-beginner"
}This JSON data gets imported into my game.js module like so:
import START from "../data/start.json" with { type: "json" };Crockfords’ jslint.com used to
barf if one added with { type: "json" } to an import
statement. My request to make it valid was accepted, so hopefully my
style becomes widespread.
My game.js module then reads the key-value pairs into the state object like so:
game.new = function (state) {
const init = structuredClone(START);
Object.keys(init).forEach(function(k) {
state[k] = init[k];
});
};I’ve found the best way to learn JavaScript is via the Lisp-classics Structure and Interpretation of Computer Programs quoted above and How to Design Programs. The Lisp examples translate into JavaScript fairly easily, and JSON is fairly closesly related to Lisp’s sexps.
How to Design Programs tries to get programers into the good habits of writing documentation and tests before diving into the code. A key part of documentation is data definition. The only documentation system I’m aware of for JavaScript is JSDoc which is very Java-influenced.
Though JavaScript doesn’t have namespaces per se, JSDoc has the @namespace. tag which I use to create reference material for a given game’s state object literal:
/**
* The filename of a card image,
* eg "./cards/2_of_spades.svg" or "./cards/jack_of_diamonds2.svg"
* @typedef {string} card
*/
/**
* Game state, an object literal initialised from values in start.json and kept in localStorage
* @namespace {Object} state
* @property {card[]} Attack1 - face-up monster plus cards played against it
* @property {card[]} Attack2 - face-up monster plus cards played against it
* @property {card[]} Attack3 - face-up monster plus cards played against it
* @property {card[]} Attack4 - face-up monster plus cards played against it
* @property {card[]} Damage - Starts empty, defeat if reaches 7
* @property {card} Hand1 - face-up card dealt from PowerDeck
* @property {card} Hand2 - face-up card dealt from PowerDeck
* @property {card} Hand3 - face-up card dealt from PowerDeck
* @property {card[]} Monster1 - Deck of 3 royal cards
* @property {card[]} Monster2 - Deck of 3 royal cards
* @property {card[]} Monster3 - Deck of 3 royal cards
* @property {card[]} Monster4 - Deck of 3 royal cards
* @property {card[]} PowerDeck - Number cards and 2 jokers
* @property {string} game - "clear-the-dungeon-beginner"
*/
const state = {"game": "clear-the-dungeon-beginner"};The running
jsdoc -d doc -r . -R README.mdproduces
Creating variables by tagging them onto an object literal avoids a
lot of the var vs let confusion. Historically,
var x = 3; was syntactic sugar for:
window.x = 3;Where window is the global object if you are
using JavaScript in a browser as opposed to global in
node.js, causing the danger that one of window’s
many existing properties might get garbled by a badly selected variable
name.
Creating a namespace and then holding variables there, assuming they
are not local variables just needed within the scope of a given
function, allowed by let but not var, avoids
possible clashes.
This is a bit more convoluted than my original way of doing it which
was to use let state = {} in my main client script and then
have an initialized version returned to the main script:
game.new = function () {
return structuredClone(START);
};That declaring const state = {} in my main client script
allows me in my game.js module to do whatever I want to
variables tagged to it lets me initialise like so:
In order to model compound objects with changing state, we will design data abstractions to include, in addition to selectors and constructors, operations called mutators, which modify data objects. — Structure and Interpretation of Computer Programs
SICP encourages programers to think of variables as places rather than names.
The trouble here is that substitution is based ultimately on the notion that the symbols in our language are essentially names for values. But as soon as we introduce set! and the idea that the value of a variable can change, a variable can no longer be simply a name. Now a variable somehow refers to a place where a value can be stored, and the value stored at this place can change.
Something C introduced were the unary operators * and &. The asterisk is used to declare a variable contains a place in memory (aka pointer), and prefixing that variable with an ampersand returns what is in that place rather than the memory address. These are typically used for abstract data types, such as what JavaScript calls an object (aka dictionary in Python, associative array in Awk…) and arrays.
Eighties scripting languages, such as JavaScript, let users define abstract data types without needing to use * and &. Instead of helping, this “simplified” syntax created piles of dog poo every novice programer steps in at some point.
My most recent stepping into this dog poo was done writing my
initialisation code without structuredClone like so:
game.new = function (state) {
Object.keys(START).forEach(function(k) {
state[k] = START[k];
});
};The first time the code is run, state.myArr is set to the memory address created when START.myArr is first loaded from start.json. The problem emerges after the first game because the above code lets state.myArr keep its old address, unchanged from START.myArr. A fresh clone of START has to be made to get new addresses for any arrays and objects it holds.
pandoc -f markdown -t html -s -o index.html README.md