Friday, 3 November 2017

p5 Pacman.js

I've been playing with p5js on and off for a while in order to generate some resources for use with Code Club and I've made (so far) Frogger, Snake and I've helped with Pong. Now I'm working with Pacman.

The thing about Pacman though, is that his jaws open and close as he's moving along and me and maths have an interesting relationship thanks to a somewhat spotchy formal education. Anyway, p5js has an arc so I was sorted. Sorted until I clocked that by default it uses radians rather than degrees, so much head scratching followed, as well as visits to the Khan Academy. And I got it working.

But p5js knows that people have issues with radians so instead of using radians I set angleMode(DEGREES);, neat eh?

The other thing about Pacman is that he moves and moves along 4 axes so the chomping needs to go in the direction of travel. Again I was stuck with radians until I realised that degrees would work (despite them being an arbitrary measurement). So I just had to rotate the start and stop angles of the arc by 90-degree increments to get this working - except that when Pacman was going right or up he flashed when his mouth was closed, so then I had to replace the arc with a circle if the gap was zero.

This is what I came up with:

if(this.jawWidth === 0){
    ellipse(this.x, this.y, this.r * 2, this.r * 2);
}else{
    let topJaw = this.jawWidth;
    let bottomJaw = -Math.abs(this.jawWidth);
    if(this.speed.every((e, i) => e === [0,-1][i])){
        topJaw += 270;
        bottomJaw += 270;
    }
    if(this.speed.every((e, i) => e === [0,1][i])){
        topJaw += 90;
        bottomJaw += 90;
    }
    if(this.speed.every((e, i) => e === [-1,0][i])){
        topJaw += 180;
        bottomJaw += 180;
    }
    arc(
      this.x, 
      this.y, 
      this.r * 2, 
      this.r * 2, 
      topJaw, 
      bottomJaw, 
      PIE);
}

I was not overly happy with it, but it worked. But what about just rotating Pacman instead? It turns out that that is even easier but does involve pushing and popping to ensure everything works properly - and that took quite a bit of head-scratching as well (It really is no wonder I'm bald). Anyway, here it is:

push();
translate(this.x, this.y);
if(this.speed.every((e, i) => e === [0,-1][i])){
    rotate(270);
}
if(this.speed.every((e, i) => e === [0,1][i])){
    rotate(90);
}
if(this.speed.every((e, i) => e === [-1,0][i])){
    rotate(180);
}
arc(
  0, 
  0, 
  this.r * 2, 
  this.r * 2, 
  this.jawWidth, 
  -Math.abs(this.jawWidth), 
  PIE);  
pop();

It's much more elegant isn't it?

Monday, 30 October 2017

Chomping through a string from the front.

I am quite keen on functional programming techniques, so I am sort of disappointed in myself today. I am disappointed because I have had to change the state of some data, but for the best of reasons considering the use case. I went so far as to write a class that did what I needed it to do without interfering with the original data, but that just felt wrong, as though I was hiding what I was doing and I am not sure if I should try to hide my guilt. On the upside, I did use recursion!

Anyway, my use case required some fragments of a string to be spat to a backend where they would then be stitched back together again. I could send the whole string in one go, but it could be arbitrarily large, so I need to chomp through it and spit each uniform chunk back to the server.

I spent ages thinking about it then, as is so often the case, I went to JSFiddle and wrote this:

const container = document.getElementById("data");
const ourString = "1234567";
const chunkSize = 2;

const sendChunk = (ourString) => {
    const chunk = ourString.substr(0, chunkSize);
    const div = document.createElement("div");
    div.textContent = `Sending: ${chunk}`;
    container.appendChild(div);
    ourString = ourString.slice(chunkSize);
    if (ourString.length) {
        sendChunk(ourString);
    } else {
        const finalDiv = document.createElement("div");
        finalDiv.textContent = "All Done!";
        container.appendChild(finalDiv);
    }
};

sendChunk(ourString);

Along with this simple bit of HTML:

<div id="data"></div>

Basically it uses substr and slice to extract and then remove a portion of the string. If the string still has a length it then repeats the process, otherwise it tells us we’re all done. Neat eh? I just wish it didn’t feel so dirty.

Wednesday, 30 August 2017

p5 Frogger

We've been playing with p5.js lately in order to generate some of our own resources for Code Club. I'm sort of really keen on using JSFiddle but I'm also conscious that it's not overly kid friendly - it's not unfriendly, but it is tailored to an adult audience who know what they're doing. So I thought I'd try using trinket:

Not sure if it's playable in its embedded form but the arrow keys should move it if it is.

Needless to say, it was mostly stolen from The Coding Train but adapted and converted into ES6 (not sure why I hated classes in Java but I love them in JavaScript).

The colours are fun: The red is a London Bus, the yellow is a DHL Truck, the blue are two Tesco vans and the three green ones are minis in British Racing Green...

Wednesday, 16 August 2017

Handicap curves from Archers Mate

I've been doing a little work on investigating archery handicaps for Witchford Archers, and it's been a fascinating journey into statistics. The Club Secretary, God-bless-him, has passed me all sorts of facts and figures and I've been looking at the code on the Archer's Mate website. We'd also attended a meeting of the CAA and discovered that working out your Handicap was a useful thing to do (especially if you enter competitions - for me, less so).

On the site, there is a simple form which allows a user to enter the round they are shooting, the type of bow they are using, the type of target they are shooting at, their gender and their score.

I was only really interested in the Portsmouth handicaps, so I was a wee bit of a pain and hit the server with lots and lots of requests (I did it during odd hours, so hopefully it didn't impact things too much).

Anyway, I discovered that the handicaps didn't differ between men and women but that the curve varied between the different bows and targets. I guess the main take away was that the user shooting a compound at a full target started off the same as someone shooting a recurve bow at the same target, as their score increased though, they began to follow the handicaps of those shooting a compound bow at a triple target.

Hopefully, the graph above illustrates what I mean. I'm not going to hit the server anymore, but I think I'll do the same to the handicaps published by Archery GB and see if there's any correlation.

There did seem to be a difference in the classifications between men and women though... not sure what that's all about.

Tuesday, 8 August 2017

Complex object creation in JavaScript

In JavaScript you can get a little spoilt with the initialisation of variables. For example I might have something like this:

const start = {
    "x": 20,
    "y": 20
}, end = {
    "x": 40,
    "y": 60
}, width = end.x - start.x, height = end.y - start.y;

Where width and height rely on the previously set variables.

Today, at work, I was asked if there was a way of doing something similar regarding initializing an object. This pseudo code illustrates what I mean:

const psuedoObj = {
    "start": [20, 20],
    "end": [40, 60],
    "width": this.end[0] - this.start[0],
    "height": this.end[1] - this.start[1],
};

console.log(psuedoObj);

The thing is, that doesn't work.

So we got to looking and found this answer on Stack Overflow. To get around the issue we could then do this:

const psuedoObj = {
    "start": [20, 20],
    "end": [40, 60],
    "init": function(){
        this.x = this.start[0];
        this.y = this.start[1];
        this.width = this.end[0] - this.start[0],
        this.height = this.end[1] - this.start[1],
        delete this.init
        return this;
    }
}.init();

console.log(psuedoObj);

Nice ehh? And we even tidy up after ourselves!

Wednesday, 2 August 2017

Some thoughts on pagination

I've written an adaptation of DataTables Pagination for use with MDB before but I've never really had to think about pagination much until, that is, a recent project at work came up. I was laid-up at home with a cold, so I used my time to think about it in some depth. For my use case, I wanted a set of buttons starting with a First and Previous button and ending with Next and Last buttons. I spent some time looking around, and it seemed as though the best arrangement of buttons for individual pages had an odd number.

This, then, was my desired output:

1 2 3 4 5

With the currently active page disabled for it to be distinguishable (and immune to clicking - I'm not sure why that's as important as it is to me, but it always pleases me if I'm unable to link back to the page I'm on from the page I'm on). I guessed that I'd also need to disable the First and Previous buttons when the first page was active and the Next and Last when the last page was active.

This then meant that the as far as possible the active number should be in the centre of the range of numbers. In the example above then when three was active I'd be happy. However, should four be the active page then the range would start at two and end at six. When one is the active page then it is not in the middle of the range; so we don't need to show numbers which are impossible to display (we really can't get to the page with the number minus one).

If we had a range of seven pages of data to display, this is what we should see while paging through the data:

Active: one

1 2 3 4 5

Active: two

1 2 3 4 5

Active: three

1 2 3 4 5

Active: four

2 3 4 5 6

Active: five

3 4 5 6 7

Active: six

3 4 5 6 7

Active: seven

3 4 5 6 7

This, then presented me with some considerable confusion, not least because I had trouble thinking clearly - but after far too many hours of pondering I can up with this spreadsheet and started reasoning about the logic behind calculating what I needed to happen.

After playing with the sheet, I clocked the algorithm required needed to check a number of different figures in order to decide where to start and end the range:

=IF(LTE(A3, CEILING(DIVIDE(D2, 2))),
    1,
    IF(GT(ADD(A3, FLOOR(DIVIDE(D2, 2))), A14),
        ADD(MINUS(A14, D2), 1),
        MINUS(A3, FLOOR(DIVIDE(D2,2)))))

Bonkers ehh?

I wrote the first line and clocked what was going on and decided to cheat after that, so I wrote a JSFiddle that generated the correct Google Sheet code for me, which I could then use to create the finished product. The final bit of code is rather simple, and this is it:

const generateCell = (text, active) => {
    const cell = document.createElement("td");
    cell.textContent = text;
    active && cell.classList.add("active");
    return cell;
}
const generateRow = (f, a, b) => {
    const row = document.createElement("tr");
    row.append(generateCell("", a === 1));
    row.append(generateCell("", a === 1));
    for(let i = f; i < (f + b); i++){
        row.append(generateCell(i, i === a));
    }
    row.append(generateCell("", a === total));
    row.append(generateCell("", a === total));
    return row;
};
const total = 12;
const table = document.getElementById("body");
const buttons = 5;
for(let active = 1; active <= total; active++){
    let first = (active <= Math.ceil(buttons / 2))
        ? 1
        : (active + Math.floor(buttons / 2) > total)
            ? (total - buttons) + 1
            : active - Math.floor(buttons / 2);
    table.append(generateRow(first, active, buttons));
}

I'm sort of ashamed that it took quite as long as it did, but I’m rather pleased with the result.

Friday, 14 July 2017

d3.json and Data URLs

I've been playing with d3 quite a bit lately and I know there's a load of stuff I need to learn more about but I thought I'd share this little trick associated with d3.json.

I've had a couple of occasions where I needed to pre-process the json sent to a d3 script. Each time I've been stumped by ds.json requiring an URL, I'm not going to process the data and then send it back to the server in order to get it again, so I started thinking about Data URLs and base64 encoding (as is my wont) and this sorts it out a treat:

let url = "data:application/json;charset=utf-8;base64,";
url += btoa(JSON.stringify(processedObject));
d3.html(url, function(error, graph) {
    if (error) throw error;
    // do cool things with the data...
});

Anyway, I hope it helps someone (and yes, I do know you don’t need to do it, it’s just nice not to change the original scripts too much!).