Author Topic: I wrote Conway's Game of Life in JavaScript  (Read 1693 times)

0 Members and 1 Guest are viewing this topic.

Offline njbair

  • Thread Starter
  • Posts: 2825
  • Location: Cleveland, Ohio
  • I love the Powerglove. It's so bad.
    • nickbair.net
I wrote Conway's Game of Life in JavaScript
« on: Sun, 06 September 2015, 09:13:48 »
I was talking to a 12-year-old protege of mine who is really interested in web programming, but he can't think of anything to program. I suggested he do Conway's Game of Life, since it's got a simple set of rules and serves as a good introduction to many programming concepts.

Then it occurred to me that I had never written my own version of Life from scratch. So I decided to fix that. I wrote this yesterday afternoon in a few hours' time.

http://life.lab.nickbair.net/

It's still rough around the edges--no code optimization has been done, and I want to make a bunch of the options user-configurable, such as grid size, cycle delay, and maybe some common patterns as presets. But for now it's just Life.

Any programming nerds, I'd love your opinions, particularly on the source code structure.

Alpine Winter GB | My Personal TMK Firmware Repo
IBM Rubber Band "Floss" Mod | Click Modding Alps 101 | Flame-Polishing Cherry MX Stems
Review: hasu's USB to USB converter
My boards:
More
AEKII 60% | Alps64 HHKB | Ducky Shine 3, MX Blues | IBM Model M #1391401, Nov. 1990 | IBM SSK #1391472, Nov. 1987, screw modded, rubber-band modded | Noppoo EC108-Pro, 45g | Infinity 60% v2 Hacker, Matias Quiet Pros | Infinity 60% v2 Standard, MX Browns | Cherry G80-1800LPCEU-2, MX Blacks | Cherry G80-1813 (Dolch), MX Blues | Unicomp M-122, ANSI-modded | Unicomp M-122 (Unsaver mod in progress) | 2x Unitek K-258, White Alps | Apple boards (IIGS, AEKII) | Varmilo VA87MR, Gateron Blacks | Filco Zero TKL, Fukka White Alps | Planck, Gateron Browns | Monarch, click-modded Cream Alps

Offline zombimuncha

  • Posts: 331
  • Location: UK
Re: I wrote Conway's Game of Life in JavaScript
« Reply #1 on: Sun, 06 September 2015, 09:47:15 »
for ... in loops are slightly naughty but if you control all the code on the page (no ads, frameworks, plugins etc!) then it'll be fine. Maybe slightly slower than a good old fashioned for loop, but I'm not sure about that.

Your init method is huge! I'd prefer to see it broken up into smaller well-named task methods just for the sake of readability.

Otherwise, very nice!

Offline njbair

  • Thread Starter
  • Posts: 2825
  • Location: Cleveland, Ohio
  • I love the Powerglove. It's so bad.
    • nickbair.net
Re: I wrote Conway's Game of Life in JavaScript
« Reply #2 on: Sun, 06 September 2015, 16:00:48 »
for ... in loops are slightly naughty but if you control all the code on the page (no ads, frameworks, plugins etc!) then it'll be fine. Maybe slightly slower than a good old fashioned for loop, but I'm not sure about that.

Your init method is huge! I'd prefer to see it broken up into smaller well-named task methods just for the sake of readability.

Otherwise, very nice!

Yeah, for...in loops will iterate through all object properties including any prototype properties. I don't know why I was using them there instead of a regular for loop.

After reading your comments I decided to run my JS through jshint. I was surprised it didn't yell at me for the for...in loops. But it did identify two anonymous functions within a loop in my init method, so I took the hint (no pun intended) and refactored my init method, offloading each operation into its own method. Having descriptive method names for each of those blocks of code makes the whole thing much more self-documenting. Plus, now my init method is tiny:

Code: [Select]
    init: function() {
        var x,y;
       
        Life.board = document.getElementById(Life.config.boardElementId);
        Life.grid = Life.createGrid();
        Life.board.appendChild(Life.grid.element);
       
        for (y=0; y<Life.grid.rows.length; y++) {
            Life.grid.rows[y] = Life.createRow(y);
            Life.grid.element.appendChild(Life.grid.rows[y].element);
           
            for (x=0; x<Life.grid.rows[y].columns.length; x++) {
                Life.grid.rows[y].columns[x] = Life.createCell(x,y);
                Life.grid.rows[y].element.appendChild(Life.grid.rows[y].columns[x].element);
            }
        }
    },

So thanks for the feedback. The code is much cleaner now.

Alpine Winter GB | My Personal TMK Firmware Repo
IBM Rubber Band "Floss" Mod | Click Modding Alps 101 | Flame-Polishing Cherry MX Stems
Review: hasu's USB to USB converter
My boards:
More
AEKII 60% | Alps64 HHKB | Ducky Shine 3, MX Blues | IBM Model M #1391401, Nov. 1990 | IBM SSK #1391472, Nov. 1987, screw modded, rubber-band modded | Noppoo EC108-Pro, 45g | Infinity 60% v2 Hacker, Matias Quiet Pros | Infinity 60% v2 Standard, MX Browns | Cherry G80-1800LPCEU-2, MX Blacks | Cherry G80-1813 (Dolch), MX Blues | Unicomp M-122, ANSI-modded | Unicomp M-122 (Unsaver mod in progress) | 2x Unitek K-258, White Alps | Apple boards (IIGS, AEKII) | Varmilo VA87MR, Gateron Blacks | Filco Zero TKL, Fukka White Alps | Planck, Gateron Browns | Monarch, click-modded Cream Alps

Offline rowdy

  • HHKB Hapster
  • * Erudite Elder
  • Posts: 21175
  • Location: melbourne.vic.au
  • Missed another sale.
Re: I wrote Conway's Game of Life in JavaScript
« Reply #3 on: Sun, 06 September 2015, 21:18:30 »
I dunno about the code (haven't learned enough JavaScript to consider myself in a position to comment), but a "clear all" button would be nice :)
"Because keyboards are accessories to PC makers, they focus on minimizing the manufacturing costs. But that’s incorrect. It’s in HHKB’s slogan, but when America’s cowboys were in the middle of a trip and their horse died, they would leave the horse there. But even if they were in the middle of a desert, they would take their saddle with them. The horse was a consumable good, but the saddle was an interface that their bodies had gotten used to. In the same vein, PCs are consumable goods, while keyboards are important interfaces." - Eiiti Wada

NEC APC-H4100E | Ducky DK9008 Shine MX blue LED red | Ducky DK9008 Shine MX blue LED green | Link 900243-08 | CM QFR MX black | KeyCool 87 white MX reds | HHKB 2 Pro | Model M 02-Mar-1993 | Model M 29-Nov-1995 | CM Trigger (broken) | CM QFS MX green | Ducky DK9087 Shine 3 TKL Yellow Edition MX black | Lexmark SSK 21-Apr-1994 | IBM SSK 13-Oct-1987 | CODE TKL MX clear | Model M 122 01-Jun-1988

Ị̸͚̯̲́ͤ̃͑̇̑ͯ̊̂͟ͅs̞͚̩͉̝̪̲͗͊ͪ̽̚̚ ̭̦͖͕̑́͌ͬͩ͟t̷̻͔̙̑͟h̹̠̼͋ͤ͋i̤̜̣̦̱̫͈͔̞ͭ͑ͥ̌̔s̬͔͎̍̈ͥͫ̐̾ͣ̔̇͘ͅ ̩̘̼͆̐̕e̞̰͓̲̺̎͐̏ͬ̓̅̾͠͝ͅv̶̰͕̱̞̥̍ͣ̄̕e͕͙͖̬̜͓͎̤̊ͭ͐͝ṇ̰͎̱̤̟̭ͫ͌̌͢͠ͅ ̳̥̦ͮ̐ͤ̎̊ͣ͡͡n̤̜̙̺̪̒͜e̶̻̦̿ͮ̂̀c̝̘̝͖̠̖͐ͨͪ̈̐͌ͩ̀e̷̥͇̋ͦs̢̡̤ͤͤͯ͜s͈̠̉̑͘a̱͕̗͖̳̥̺ͬͦͧ͆̌̑͡r̶̟̖̈͘ỷ̮̦̩͙͔ͫ̾ͬ̔ͬͮ̌?̵̘͇͔͙ͥͪ͞ͅ

Offline njbair

  • Thread Starter
  • Posts: 2825
  • Location: Cleveland, Ohio
  • I love the Powerglove. It's so bad.
    • nickbair.net
Re: I wrote Conway's Game of Life in JavaScript
« Reply #4 on: Sun, 06 September 2015, 22:20:24 »
I dunno about the code (haven't learned enough JavaScript to consider myself in a position to comment), but a "clear all" button would be nice :)

Good idea. I added one but labeled it "reinitialize" because when I eventually add configurable width/height settings, I'll use that button to redraw the grid.

Alpine Winter GB | My Personal TMK Firmware Repo
IBM Rubber Band "Floss" Mod | Click Modding Alps 101 | Flame-Polishing Cherry MX Stems
Review: hasu's USB to USB converter
My boards:
More
AEKII 60% | Alps64 HHKB | Ducky Shine 3, MX Blues | IBM Model M #1391401, Nov. 1990 | IBM SSK #1391472, Nov. 1987, screw modded, rubber-band modded | Noppoo EC108-Pro, 45g | Infinity 60% v2 Hacker, Matias Quiet Pros | Infinity 60% v2 Standard, MX Browns | Cherry G80-1800LPCEU-2, MX Blacks | Cherry G80-1813 (Dolch), MX Blues | Unicomp M-122, ANSI-modded | Unicomp M-122 (Unsaver mod in progress) | 2x Unitek K-258, White Alps | Apple boards (IIGS, AEKII) | Varmilo VA87MR, Gateron Blacks | Filco Zero TKL, Fukka White Alps | Planck, Gateron Browns | Monarch, click-modded Cream Alps

Offline tbc

  • Posts: 2365
Re: I wrote Conway's Game of Life in JavaScript
« Reply #5 on: Mon, 14 September 2015, 21:02:46 »
quick architectural review, I don't actually know wth Life is, so I'm not going into the code.  Everything said, besides not enough comments, it's better than most grads by a significant margin.



1. Radix is set to 0

"Life.config.grid.width = parseInt(Life.controls.widthField.value, 0);"

this is an example of radix being set to 0 when calling parseInt().  There is no benefit to this because it's equivalent to not setting a radix at all.

More usefully, it should be set to 10 because most people will assume something like width will be in base10.

ref:

"parseInt(string, radix);"
"If radix is undefined or 0 (or absent)"

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt

2.  Model is not explicitly attached to window object


"var Life = {...}" is the object/model for the application.  It is declared at the root level of the script file, meaning this will attach the object implicitly to the window object.

A better solution would be to explicitly attach it to the window object - like this:

"window.Life = {...}"

Further improving the code, you should create an IIFE and import a 'namespace':

"
;(function (model) {
   model = {...};  //'model' is equivalent to 'life'
})(window.Life = window.Life || {});
"

3.  'Life' is a data object, not a class

Capitalized names are reserved for classes according to every standard naming convention


4.  Magic strings are frequently used

Examples include:
"hidden"
"gameContainer"

'static' strings, such as "hidden", should be made into a static string

'initialization' strings, such as 'gameContainer', should be injected into the model and saved into a string variable that is used instead of the literal


5.  Driver is included with the core logic/model

The 'load' event callback serves as a driver in this codebase.  It should be externalized into a separate JS file or made inline in the HTML file.  This will force an API to be made with the result being enhanced testability (hopefully).

There are many more details about why your driver should be separate, but it's more detail than I care to dump out at this point.



6.  setInterval is used


I'm not going to go into why this is bad.  It really should only be used after careful planning and analysis.  There ARE situations where it is the best or only option; but those are far rarer than instances where setInterval is used.



EDIT:

also no one tell me that JS doesn't have classes.  if you know what you're talking about, then you know what I mean when i say classes.


also JS2015
« Last Edit: Mon, 14 September 2015, 21:06:57 by tbc »
ALL zombros wanted:  dead or undead or dead-dead.

Offline njbair

  • Thread Starter
  • Posts: 2825
  • Location: Cleveland, Ohio
  • I love the Powerglove. It's so bad.
    • nickbair.net
Re: I wrote Conway's Game of Life in JavaScript
« Reply #6 on: Tue, 15 September 2015, 00:06:06 »
quick architectural review, I don't actually know wth Life is, so I'm not going into the code.  Everything said, besides not enough comments, it's better than most grads by a significant margin.



1. Radix is set to 0

"Life.config.grid.width = parseInt(Life.controls.widthField.value, 0);"

this is an example of radix being set to 0 when calling parseInt().  There is no benefit to this because it's equivalent to not setting a radix at all.

More usefully, it should be set to 10 because most people will assume something like width will be in base10.

ref:

"parseInt(string, radix);"
"If radix is undefined or 0 (or absent)"

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt

2.  Model is not explicitly attached to window object


"var Life = {...}" is the object/model for the application.  It is declared at the root level of the script file, meaning this will attach the object implicitly to the window object.

A better solution would be to explicitly attach it to the window object - like this:

"window.Life = {...}"

Further improving the code, you should create an IIFE and import a 'namespace':

"
;(function (model) {
model = {...};  //'model' is equivalent to 'life'
})(window.Life = window.Life || {});
"

3.  'Life' is a data object, not a class

Capitalized names are reserved for classes according to every standard naming convention


4.  Magic strings are frequently used

Examples include:
"hidden"
"gameContainer"

'static' strings, such as "hidden", should be made into a static string

'initialization' strings, such as 'gameContainer', should be injected into the model and saved into a string variable that is used instead of the literal


5.  Driver is included with the core logic/model

The 'load' event callback serves as a driver in this codebase.  It should be externalized into a separate JS file or made inline in the HTML file.  This will force an API to be made with the result being enhanced testability (hopefully).

There are many more details about why your driver should be separate, but it's more detail than I care to dump out at this point.



6.  setInterval is used


I'm not going to go into why this is bad.  It really should only be used after careful planning and analysis.  There ARE situations where it is the best or only option; but those are far rarer than instances where setInterval is used.



EDIT:

also no one tell me that JS doesn't have classes.  if you know what you're talking about, then you know what I mean when i say classes.


also JS2015
Wow, I feel like I've got a lot to learn from these points. Rest assured I'll be going over these at some point to learn what I can from them and apply the changes. Thanks!

Alpine Winter GB | My Personal TMK Firmware Repo
IBM Rubber Band "Floss" Mod | Click Modding Alps 101 | Flame-Polishing Cherry MX Stems
Review: hasu's USB to USB converter
My boards:
More
AEKII 60% | Alps64 HHKB | Ducky Shine 3, MX Blues | IBM Model M #1391401, Nov. 1990 | IBM SSK #1391472, Nov. 1987, screw modded, rubber-band modded | Noppoo EC108-Pro, 45g | Infinity 60% v2 Hacker, Matias Quiet Pros | Infinity 60% v2 Standard, MX Browns | Cherry G80-1800LPCEU-2, MX Blacks | Cherry G80-1813 (Dolch), MX Blues | Unicomp M-122, ANSI-modded | Unicomp M-122 (Unsaver mod in progress) | 2x Unitek K-258, White Alps | Apple boards (IIGS, AEKII) | Varmilo VA87MR, Gateron Blacks | Filco Zero TKL, Fukka White Alps | Planck, Gateron Browns | Monarch, click-modded Cream Alps