Category Archives: Toys

Update: Wolfram rules

While updating my website, another project that needed a little attention was the Wolfram rules (Live page) project. Apart from looking a little poor in its presentation, it was an experiment in DOM manipulation where a huhe HTML table was used to display the resulting algorithm. While it was nice to see that the DOM and CSS worked as they should, this project was better suited to the canvas, so I moved over to the canvas and added some colour to improve the presentation.

Before and after

Wolfram rules before the update
Wolfram rules before the update
Wolfram rules after the update
Wolfram rules after the update

Update: Conway’s game of life

As part of my website’s facelift I decided to update the Conway’s game of life (Live page) project. The content was rearranged to make more sense to the first time user, a the controls organised in a way that made them larger and easier to navigate. I also changed the links to different shapes to be gallery objects (similar to what I had for the Mandelbrot project.) This is also the first page to get the “You might also like…” feature at the bottom, which will soon appear on most other pages when I get time to add them.

Before and after

Conway before the update
Conway before the update
Conway after the update
Conway after the update
Conway after the update
Conway after the update

Work in progress: Skyline

One of the features I want on my webpage is the silhouette of a skyline to use as background image. This project is aimed at allowing the user to draw a complex skyline, with the possibilty for animation.

Links

Live page
GitHub repository

Overview

The user can create individual “buildings” with many layers, and then arrange the buildings into a skyline to generate an image. If possible, the user can animate the image (for example adding lights at night, allowing the sky to change to match the time of day.)

The design would take place on the canvas, with a suite of tools to allow the user to correctly determine the size of buildings, add arches, spires etc. A second layer would allow the user to add windows, and a third layer would allow them to add other features. These would then be saved to file and a script would read the file to render the images on screen. The images would be rendered on a canvas that sits behind the main content of the page. This project is largely a problem of geometry.

Challenges

Challenge: Initially I wanted to set the canvas as the background of the page.
Solution: There are methds of taking the content of a canvas and using it to draw images that can be used with the DOM. I tried to use Javascript to change the source of the background image to match the canvas, but this lead to “flickering” effects, even when to canvases were used. As a result this was not a viable option. (Abandoned)
Challenge: The user needs a simple and intuitive interface.
Solution: This project allows the user to draw wire frames for the buildings. They can draw straight lines, circle arcs, and quadratic curves. The interface is not perfect, but it is easy enough to quickly make buildings. A grid is also provided so the user can keep track of sizes. (Resolved)
Challenge: This projects needs to be able to calculate interections and unions of polygons.
Solution: One of the hardest problems to solve is the interserction and union of two shapes, so that the user can make complex shapes. This is a non-trivial problems of geometry and finding solutions online was not east. I honest cannot remember if I completely solved this problem or not. (Resolved, to be revisited)
Challenge: This project would ideally respond to the client’s time.
Solution: Animation and time dependence has not been implemented yet, but eventually the colour of the sky and weather would chance, lights would turn on and off, and vehicles would move. This should be relatively simple to implement, once I find the time. (To do)

Sample output

Sample skyline.
Sample skyline.

LHC Driver

I wanted to make a “steady hand” game and realised I could make one based on the beams of the LHC. In this game the player(s) have to control magnets in the \(x\) and \(y\) directions to keep the beams on target. This eventually lead on to the Science Shift Simulator games.

Links

Live page
GitHub repository

Overview

Both beams have a random walk that moves them around the canvas. The player(s) can affect the forces that act on the beam(s) in the \(x\) and \(y\) directions and have to keep the beams within the valid range of the canvas. If the beams are close to each other then the instantaneous luminosity increases, and the integrated luminosity is a measure of the score. When the beams get dumped there is the customary toilet flush!

Challenges

Challenge: I had to make sliders to control the forces.
Solution: This is the first time I made some sliders (that I would later reuse in the Mandelbrot project) and it was trickier than I thought it would be. The event listeners need to keep track of all mouse and keyboard actions to use the slides properly and intuitively. (Resolved.)
Challenge: This was the first two player game I made.
Solution: Making a game that two players can play is a bit tricky, since their controls cannot be allowed to interfere with each other. This was achieved by moving both with the keyboard. (Resolved.)

Screenshots

Here is a screenshot of the game in its current form:

Screenshot of the LHC Driver game
Screenshot of the LHC Driver game

I don’t usually show screenshots of previous stages of development, but here is the first version of the game:

Screenshot of the previous version of the LHC Driver game
Screenshot of the previous version of the LHC Driver game

Dr Who

I had had an idea for a very long time, which was to animate a song using the canvas and a YouTube video. Someone sent a gif image of some Dr Who actors dancing badly, and I saw this as an opportunity to test out my idea in the simplest way possible. So I put the gif together with the YouTube song and button to play/stop the music and animation.

Links

Live page
GitHub repository

Overview

The YouTube video is hidden from view, so this needed to use the YouTube Javascript API (which I already mastered for the Marble Hornet project) and there is a single button which controls the music and animation. If anyone watches to the end of the song they probably need to get some help.

Challenges

Challenge: This is a very simple prototype.
Solution: This project fell far short of what I wanted it to be a prototype for. It was just a joke to show some friends, but it showed that I could synchronise the YouTube player and DOM elements quite well, so the first stage is complete. The next stage is to make some animation on the canvas. (Resolved)

Screenshot

So uncool
So uncool

Code update: Tetris

The first major Javascript project I took on was a clone of Tetris. Every time I look back at it I’m actually surprised an impressed at how many features it has. For a first project it has DOM manipulation, httpxml requests, timeout functions, cookies, audio, and a MySQL backend. After making the Tetris clone I then tried to make Pentris, which is the same game but with five blocks per piece. In going from Tetris to Pentris I made some interesting optimisations.

The Tetris clone
The Tetris clone

For example, here is the function tryRotate:

function tryRotate(){
  switch(color){
    case "red":
    switch(orientation){
      case 0:
      case 2:
        rotatedC[0] = C[0] + 1 ; rotatedR[0] = R[0] + 0 ;
        rotatedC[1] = C[1] + 0 ; rotatedR[1] = R[1] + 1 ;
        rotatedC[2] = C[2] - 1 ; rotatedR[2] = R[2] + 0 ;
        rotatedC[3] = C[3] - 2 ; rotatedR[3] = R[3] + 1 ;
      break ;
      case 1:
      case 3:
        rotatedC[0] = C[0] - 1 ; rotatedR[0] = R[0] + 0 ;
        rotatedC[1] = C[1] + 0 ; rotatedR[1] = R[1] - 1 ;
        rotatedC[2] = C[2] + 1 ; rotatedR[2] = R[2] + 0 ;
        rotatedC[3] = C[3] + 2 ; rotatedR[3] = R[3] - 1 ;
      break ;
    }
    break ;
    
    case "green":
    switch(orientation){
      case 0:
        rotatedC[0] = C[0] + 1 ; rotatedR[0] = R[0] + 1 ;
        rotatedC[1] = C[1] + 1 ; rotatedR[1] = R[1] - 1 ;
        rotatedC[2] = C[2] + 0 ; rotatedR[2] = R[2] + 0 ;
        rotatedC[3] = C[3] - 1 ; rotatedR[3] = R[3] + 1 ;
      break ;
      case 1:
        rotatedC[0] = C[0] - 1 ; rotatedR[0] = R[0] + 1 ;
        rotatedC[1] = C[1] + 1 ; rotatedR[1] = R[1] + 1 ;
        rotatedC[2] = C[2] + 0 ; rotatedR[2] = R[2] + 0 ;
        rotatedC[3] = C[3] - 1 ; rotatedR[3] = R[3] - 1 ;
      break ;
      case 2:
        rotatedC[0] = C[0] - 1 ; rotatedR[0] = R[0] - 1 ;
        rotatedC[1] = C[1] - 1 ; rotatedR[1] = R[1] + 1 ;
        rotatedC[2] = C[2] + 0 ; rotatedR[2] = R[2] + 0 ;
        rotatedC[3] = C[3] + 1 ; rotatedR[3] = R[3] - 1 ;
      break ;
      case 3:
        rotatedC[0] = C[0] + 1 ; rotatedR[0] = R[0] - 1 ;
        rotatedC[1] = C[1] - 1 ; rotatedR[1] = R[1] - 1 ;
        rotatedC[2] = C[2] + 0 ; rotatedR[2] = R[2] + 0 ;
        rotatedC[3] = C[3] + 1 ; rotatedR[3] = R[3] + 1 ;
      break ;
    }
    break ;
    
    case "blue":
    switch(orientation){
      case 0:
        rotatedC[0] = C[0] + 1 ; rotatedR[0] = R[0] + 0 ;
        rotatedC[1] = C[1] + 0 ; rotatedR[1] = R[1] + 1 ;
        rotatedC[2] = C[2] - 1 ; rotatedR[2] = R[2] + 2 ;
        rotatedC[3] = C[3] - 2 ; rotatedR[3] = R[3] + 1 ;
      break ;
      case 1:
        rotatedC[0] = C[0] + 1 ; rotatedR[0] = R[0] + 1 ;
        rotatedC[1] = C[1] + 0 ; rotatedR[1] = R[1] + 0 ;
        rotatedC[2] = C[2] - 1 ; rotatedR[2] = R[2] - 1 ;
        rotatedC[3] = C[3] + 0 ; rotatedR[3] = R[3] - 2 ;
      break ;
      case 2:
        rotatedC[0] = C[0] - 1 ; rotatedR[0] = R[0] + 1 ;
        rotatedC[1] = C[1] + 0 ; rotatedR[1] = R[1] + 0 ;
        rotatedC[2] = C[2] + 1 ; rotatedR[2] = R[2] - 1 ;
        rotatedC[3] = C[3] + 2 ; rotatedR[3] = R[3] + 0 ;
      break ;
      case 3:
        rotatedC[0] = C[0] - 1 ; rotatedR[0] = R[0] - 2 ;
        rotatedC[1] = C[1] + 0 ; rotatedR[1] = R[1] - 1 ;
        rotatedC[2] = C[2] + 1 ; rotatedR[2] = R[2] + 0 ;
        rotatedC[3] = C[3] + 0 ; rotatedR[3] = R[3] + 1 ;
      break ;
    }
    break ;
    
    case "cyan":
    switch(orientation){
      case 0:
        rotatedC[0] = C[0] - 1 ; rotatedR[0] = R[0] + 2 ;
        rotatedC[1] = C[1] + 0 ; rotatedR[1] = R[1] + 1 ;
        rotatedC[2] = C[2] + 1 ; rotatedR[2] = R[2] + 0 ;
        rotatedC[3] = C[3] + 0 ; rotatedR[3] = R[3] - 1 ;
      break ;
      case 1:
        rotatedC[0] = C[0] - 1 ; rotatedR[0] = R[0] - 1 ;
        rotatedC[1] = C[1] + 0 ; rotatedR[1] = R[1] + 0 ;
        rotatedC[2] = C[2] + 1 ; rotatedR[2] = R[2] + 1 ;
        rotatedC[3] = C[3] + 2 ; rotatedR[3] = R[3] + 0 ;
      break ;
      case 2:
        rotatedC[0] = C[0] + 1 ; rotatedR[0] = R[0] - 1 ;
        rotatedC[1] = C[1] + 0 ; rotatedR[1] = R[1] + 0 ;
        rotatedC[2] = C[2] - 1 ; rotatedR[2] = R[2] + 1 ;
        rotatedC[3] = C[3] + 0 ; rotatedR[3] = R[3] + 2 ;
      break ;
      case 3:
        rotatedC[0] = C[0] + 1 ; rotatedR[0] = R[0] + 0 ;
        rotatedC[1] = C[1] + 0 ; rotatedR[1] = R[1] - 1 ;
        rotatedC[2] = C[2] - 1 ; rotatedR[2] = R[2] - 2 ;
        rotatedC[3] = C[3] - 2 ; rotatedR[3] = R[3] - 1 ;
      break ;
    }
    break ;
    
    case "magenta":
    switch(orientation){
      case 0:
      case 2:
        rotatedC[0] = C[0] - 1 ; rotatedR[0] = R[0] + 0 ;
        rotatedC[1] = C[1] + 0 ; rotatedR[1] = R[1] + 1 ;
        rotatedC[2] = C[2] + 1 ; rotatedR[2] = R[2] + 0 ;
        rotatedC[3] = C[3] + 2 ; rotatedR[3] = R[3] + 1 ;
      break ;
      case 1:
      case 3:
        rotatedC[0] = C[0] + 1 ; rotatedR[0] = R[0] + 0 ;
        rotatedC[1] = C[1] + 0 ; rotatedR[1] = R[1] - 1 ;
        rotatedC[2] = C[2] - 1 ; rotatedR[2] = R[2] + 0 ;
        rotatedC[3] = C[3] - 2 ; rotatedR[3] = R[3] - 1 ;
      break ;
    }
    break ;
    
    case "yellow":
    switch(orientation){
      case 0:
      case 1:
      case 2:
      case 3:
        rotatedC[0] = C[0] + 0 ; rotatedR[0] = R[0] + 0 ;
        rotatedC[1] = C[1] + 0 ; rotatedR[1] = R[1] + 0 ;
        rotatedC[2] = C[2] + 0 ; rotatedR[2] = R[2] + 0 ;
        rotatedC[3] = C[3] + 0 ; rotatedR[3] = R[3] + 0 ;
      break ;
    }
    break ;
    
    case "white":
    switch(orientation){
      case 0:
      case 2:
        rotatedC[0] = C[0] + 1 ; rotatedR[0] = R[0] - 1 ;
        rotatedC[1] = C[1] + 0 ; rotatedR[1] = R[1] + 0 ;
        rotatedC[2] = C[2] - 1 ; rotatedR[2] = R[2] + 1 ;
        rotatedC[3] = C[3] - 2 ; rotatedR[3] = R[3] + 2 ;
      break ;
      case 1:
      case 3:
        rotatedC[0] = C[0] - 1 ; rotatedR[0] = R[0] + 1 ;
        rotatedC[1] = C[1] + 0 ; rotatedR[1] = R[1] + 0 ;
        rotatedC[2] = C[2] + 1 ; rotatedR[2] = R[2] - 1 ;
        rotatedC[3] = C[3] + 2 ; rotatedR[3] = R[3] - 2 ;
      break ;
    }
  break ;
  }
  for(var i=0 ; i<4 ; i++){
    if(rotatedR[i]>nRows) return false ;
    if(rotatedR[i]<1)     return false ;
    if(rotatedC[i]>nCols) return false ;
    if(rotatedC[i]<1)     return false ;
    var skip = 0 ;
    for(var j=0 ; j<4 ; j++){ if(rotatedR[i]==R[j] && rotatedC[i]==C[j]) skip = 1 ; }
    if(skip==0){
      var className = getClass(rotatedR[i],rotatedC[i]) ;
      if(className!="empty") return false ;
    }
  }
  for(var i=0 ; i<4 ; i++){
    set(R[i],C[i], "empty") ;
    R[i] = rotatedR[i] ;
    C[i] = rotatedC[i] ;
  }
  for(var i=0 ; i<4 ; i++){
    set(R[i],C[i], color) ;
  }
  orientation++ ;
  if(orientation==4) orientation = 0 ;
}

Compare that to the streamlined version I had for Pentris:

function tryRotate(){
  rotatePiece() ;
  for(var i=0 ; i<n ; i++){
    if(rotatedR[i]>nRows) return false ;
    if(rotatedR[i]<1)     return false ;
    if(rotatedC[i]>nCols) return false ;
    if(rotatedC[i]<1)     return false ;
    var skip = 0 ;
    for(var j=0 ; j<n ; j++){ if(rotatedR[i]==R[j] && rotatedC[i]==C[j]) skip = 1 ; }
    if(skip==0){
      var className = getClass(rotatedR[i],rotatedC[i]) ;
      if(className!="empty") return false ;
    }
  }
  for(var i=0 ; i<n ; i++){
    set(R[i],C[i], "empty") ;
    R[i] = rotatedR[i] ;
    C[i] = rotatedC[i] ;
  }
  for(var i=0 ; i<n ; i++){ set(R[i],C[i], color) ;}
}
function rotatePiece(){
  for(var i=0 ; i<n ; i++){
    rotatedR[i] =  (Y) + C[i] - (X) ;
    rotatedC[i] =  (X) - R[i] + (Y) ;
  }
}

The optimisation here is quite impressive, and very much needed when you consider that the move from Tetris to Pentris means moving from five pieces of four blocks each, to eighteeen pieces of five blocks each, with the space needed for ratotions scaling from sixteen to twenty five.

In the first function the different scenarios are all checked explicitly, whereas in the second function the temporary piece is rotated and then collisions checked, before updating the main piece. The amount of code when from 82 to 27 lines, and in doing so became safer and more easily extendible.

In a similar vein, here's the updatePreview function, first in Tetris:

function updatePreview(){
  for(var i=1 ; i<3 ; i++){ for(var j=1 ; j<5 ; j++){ setPreview(i, j, "empty") ; } }
  switch(nextPiece){
    case 1:
      setPreview(1, 2, "red") ;
      setPreview(1, 3, "red") ;
      setPreview(2, 3, "red") ;
      setPreview(2, 4, "red") ;
      break ;
    case 2:
      setPreview(1, 3, "green") ;
      setPreview(2, 2, "green") ;
      setPreview(2, 3, "green") ;
      setPreview(2, 4, "green") ;
      break ;
    case 3:
      setPreview(1, 1, "blue") ;
      setPreview(1, 2, "blue") ;
      setPreview(1, 3, "blue") ;
      setPreview(2, 3, "blue") ;
      break ;
    case 4:
      setPreview(1, 3, "cyan") ;
      setPreview(1, 2, "cyan") ;
      setPreview(1, 1, "cyan") ;
      setPreview(2, 1, "cyan") ;
      break ;
    case 5:
      setPreview(1, 3, "magenta") ;
      setPreview(1, 2, "magenta") ;
      setPreview(2, 2, "magenta") ;
      setPreview(2, 1, "magenta") ;
      break ;
    case 6:
      setPreview(1, 2, "yellow") ;
      setPreview(1, 3, "yellow") ;
      setPreview(2, 3, "yellow") ;
      setPreview(2, 2, "yellow") ;
      break ;
    case 7:
      setPreview(2, 1, "white") ;
      setPreview(2, 2, "white") ;
      setPreview(2, 3, "white") ;
      setPreview(2, 4, "white") ;
      break ;
  }
}

Second in Pentris:

function updatePreview(){
  for(var i=1 ; i<4 ; i++){ for(var j=1 ; j<6 ; j++){ setPreview(i, j, "empty") ; } }
  var x = new Array(n) ;
  var y = new Array(n) ;
  var piece = getPiece(nextPiece) ;
  var counter = 0 ;
  for(var i=0 ; i<n ; i++){
    for(var j=0 ; j<n ; j++){
      if(piece[i][j]==1){
        x[counter] = i   ;
        y[counter] = j+1 ;
        counter++ ;
      }
    }
  }
  for(var i=0 ; i<n ; i++){setPreview(x[i],y[i],colors[nextPiece-1]);}
}

Such an economy of code! One of my favourite parts of code is refactoring existing blocks of code into something more elegant and effiienct. The original code, as a first attempt, and a first adventure in serious Javascript was necessarily messy and in need of a good clean, so it's no surprise that so many improvements could be had.

I've been putting off resolving these differences for a very long time, partly because the games work as they are and there's no need to fix something that's not broken, and partly because I wanted to keep some legacy code around to remind myself of how it all started. I've archived the original code and now I'm bringing the Tetris, Pentris, and Tritris into line with each other to make all three games share a single code base.

Image morpher

*.gif images have been around for a long time, but this is something a bit different. It takes an array of images and changes between them randomly with random intervals of time.

Links

Live page
GitHub repository

Overview

The canvas is used to load images and switch between them. The time interval is determined randomly, and images are selected at random, so this differs from the traditional .gif images in a fundamental way.

Screenshot

Static screenshot of the image morpher
Static screenshot of the image morpher

SCP

Here is where I put my content relevant to the SCP Wiki.

Links

Live page
SCP Wiki
GitHub repository

Overview

The SCP Wiki is description of a fictional emergency containment facility. This project collects mostly attempts at writing articles, and anything else relevant to the SCP, including the python script.

Challenges

Challenge: The python script has to make an HTTP request and keep the user entertained while it handles the request.
Solution: This was the first time I used python with HTTP requests, which turned out to be quite easy. The script produces a series of messages to emulate the security steps needed to access the articles. (Resolved)
Challenge: It would be nice to have ASCII images appear.
Solution: Most SCP articles contain small images, and I’ve already worked with ASCII art elsewhere, so it should be a matter of putting the two together, when I get time. (To be done)

python screenshot

SCPython screenshot
SCPython screenshot

pi estimation

This project was put together quickly while waiting for a physics job to finish. It was an exercise to see how quickly I could make a useful page with my current setup on my website. Including time to research, write, upload, and test the page it took around 20 minutes.

Links

Live page
GitHub repository

Overview

This page uses a Monte Carlo method to estimate π. This is a pure method in that it does not rely on knowledge of π to make the estimate, as some methods do. It throws random numbers in a two dimensional area and counts how many lie within the circle of radius \(1\) compared to a square of side \(2\).

Challenges

Challenge: This was all about speed.
Solution: I already knew everything I needed to put this page together, so it was simply a matter of how quickly I did it. I’m quite happy with the end result of 1020 seconds. (Resolved)

Sample output

Sample Monte Carlo output
Sample Monte Carlo output

Daily Fail

This is one of my more puerile projects. My older sister asked me if I could make a website that replaced the word “cancer” with the words “uncontrollable urge to bum farm animals” for Daily Mail articles. Seeing this as a chance to practice page scrubbing I took up the challenge and gave my sister a new toy.

Links

Live page
GitHub repository

Overview

The user supplies a Daily Mail uri and the script fetches the relevant page. It then performs a mildly intelligent search and replace to make the word substituions.

Challenges

Challenge: The script must get the style, scripts, and images correct.
Solution: The script goes through the source code and removes all scripts safely. The uris and images are then remapped to give tne user a seamless experience. Page scrubbing has never been so easy! (Resolved)

Sample output

Daily Fail sample headline
Daily Fail sample headline