Friday, 27 September 2019

The Tea Set

The Tea Set recently released their latest album Back in Time for Tea, and I thought I'd have a play with one of their old logos. I spent ages working out the SCSS mixin, but I'm pleased with the result.

This is the mixin and its usage:

$main-duration: 10s;
$base-percentage: 3%;

@mixin animate_movement($initial, $id, $main-duration, $delay, $duration) {
    $animation-name: unique-id() !global;
    @keyframes #{$animation-name} {
    
        0%, 
        #{$delay * $base-percentage} {
            transform: $initial;
        }
    
        #{($delay + $duration) * $base-percentage}, 
        100% {
            transform: translate(0px, 0px);
        }
    }
    ##{$id} {
        transform: $initial;
        animation: $animation-name  $main-duration linear infinite alternate;
    }
}

@keyframes scale-in-top {
    0% {
        transform: scale(0);
        transform-origin: 50% 0%;
        opacity: 1;
    }
    100% {
        transform: scale(3);
        transform-origin: 50% 0%;
        opacity: 1;
    }
}


.icon-holder {
    text-align: center;
    margin-top: 1em;
    svg {
        animation: scale-in-top 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
    }
    @include animate_movement(translateY(-88px), TheTeaSet, $main-duration, 1, 5);
    @include animate_movement(translateX(-16px), tHe, $main-duration, 9, 3);
    @include animate_movement(translateX(-32px), thE, $main-duration, 6, 6);
    @include animate_movement(translateX(-16px), tEa, $main-duration, 15, 3);
    @include animate_movement(translateX(-32px), teA, $main-duration, 12, 6);
    @include animate_movement(translateX(32px), Set, $main-duration, 18, 6);
    @include animate_movement(translateX(16px), sEt, $main-duration, 21, 3);
}

Monday, 23 September 2019

She said, he thought

This has been rattling around in my head for a while so I thought I'd best get it written. It's basically the reason why I shouldn't try relaxation.

She said, “Breath in,” then waited for my huge inhalation to finish and paused for a beat before saying, “breath out”. Again she waited for my chest to shrink, then my belly to implode, before again saying, “Breath in”, waiting while I inhaled and again pausing for a beat before telling me to exhale.

This repeated for about half a dozen times before she said, “Breath normally”. I did as I was told, but noted that my breathing was much deeper and slower than before.

She said, “Empty your mind”. Nice idea but utterly impossible. Or maybe not even a nice idea… where would I be without the whirling maelstrom of chaos rolling around in my head?

She said, “Imagine you’re walking through the grass...” So I did and wondered what the weather was like.

“It’s a warm and sunny day and you’re barefoot” Okay, I can live with that. I can live with it as long as there’s no dog shit on the grass… I guess there isn’t or this wouldn’t be relaxing… trying to avoid stepping in shit and then going for a Burton and landing on my arse would be a pain, especially if I managed to land in the shite.

“You step from the grass onto the sand by the sea.” Ah, bugger. It’s the grass like pampas grass, isn’t it? The stuff on dunes which is sharp as anything and likely to lacerate my feet, still, thankfully I’m in the sand now. Let’s hope there’s no dog shit or broken glass or some slimy seaweed in the sand now. That’d be just my luck, wouldn’t it? Perhaps I could slide on the seaweed and then manage to land in dogshit… and broken glass! When did I have my last Tetanus injection again?

“You step into the warm sea.” Where is this? It’s not local if the sea is warm… are there jellyfish or sea urchins here? Do I need sunscreen? She said it was warm before… I’ll get a burnt head!

“You see a rock standing in the sea.” A rock? How big is the rock? Are we looking at a pebble here or something more substantial? Am I gonna have to avoid it with my bare feet, as well as avoiding the jellyfish and sea urchins? It’s warm though, isn’t it? I’m sure she said it was warm… happen it’s Australia where everything is trying to kill you! There could be sharks or a manta ray - I’ll end up getting stuck on the tail of the manta ray, bleeding and then the sharks will come and I’ll be buggered! All while being covered in dog shit with a broken bottle stuck out my back!

“You climb the roc.k” Ah, it’s not a pebble, it’s a bit bigger! Wonder how big? Am I gonna be climbing for a while? I do hope not: I’m not as young as I once was and I’m not sure my belly will allow for any major climbing elegance… perhaps it’s the South China seas and the rock is like the island where Goldfinger had his super, secret weapon? That was huge though wasn’t it? I’ll be climbing all bloody day!

“You sit on the top of the rock.” OK, guess it’s not that big… something like that lass was marooned on when the sharks started circling… are the sharks here now, did I manage to cut my feet on the razor grass or broken glass, or are they attracted by dog shit in the water? Also, rocks aren’t all that comfortable, this is gonna kill my arse after a while. Am I even gonna be here a while, should I ask if I can put my hat on?

Monday, 29 July 2019

Been a busy boy

I've just had a busy old weekend and managed to get my digital counter working with CSS animations. I have been wanting to do this for ages but haven't had the time to research the technique correctly. It's here, and I'm quite pleased with the result despite it taking a shed load of tweaking to get right.

I've also caught up some on updated p5 implementations from WireFrame, and I've added three projects to the site hosted by GitHub, all to do with mazes. The first uses an algorithm to generate a maze, and I got lost in the C implementation but found a set of videos from The Coding Train that went through the steps required. I've pretty much stolen them but made them a little more ES6y. The second draws the view of a character navigating a maze, and I'm impressed with the cheating in terms of the images used to illustrate the perspective required. The third and final one also cheats by using images to build up a semi-3D illustration of a landscape and was lots of fun (you can navigate using the arrow keys)!

Friday, 31 May 2019

rgba2rgb

I had a lovely little challenge a couple of days ago which, in conjunction with my invite to the First Alpha Testing of Empire of Code, got me excited. It led me to generate the following chunk of code:

const rgba2rgb = rgba => "#" + rgba.match(/.{1,2}/g)
                             .map((c, i, a) => (i !== a.length -1) 
                                 ? parseInt(c, 16) 
                                 : parseInt(c, 16) / 255)
                             .map((c, i, a) => (i !== a.length -1) 
                                 ? ~~((1 - a[3]) * 255 + a[3] * c) 
                                 : null)
                             .reverse()
                             .slice(1)
                             .reverse()
                             .map(c => c.toString(16))
                             .join("");

We needed to convert an RGBA colour to RGB, with the assumption that the original colour was atop a white background. The maths and colour theory are a little confusing, and I went through any number of iterations. The result made me feel a bit smug though as it is teeny - I'm guessing it could be improved though.

Have at it!

EDIT

So I clocked that the above wasn't quite as brilliant as I thought so I've updated it after simplifying it:

const rgba2rgb = function(rgba){ 
 const coloursAsBase16 = rgba.match(/.{1,2}/g);
    console.log("coloursAsBase16", coloursAsBase16);
    const coloursAsBase10 = coloursAsBase16.map(function(c, i, a){ 
     if(i !== a.length - 1){
         return parseInt(c, 16) // Value between 1 - 255
        }else{
         return parseInt(c, 16) / 255 // Value between 0 - 1
        }
    });
    console.log("coloursAsBase10", coloursAsBase10);
    const coloursWithAlpha = coloursAsBase10.reduce(function(t, c, i, a){
     if(i !== a.length - 1){
         return t.concat([parseInt(((1 - a[3]) * 255 + a[3] * c), 10)]);
        }else{
         return t;
        }
    }, [])
    console.log("coloursWithAlpha", coloursWithAlpha);
    const coloursChangedToBase16 = coloursWithAlpha.map(function(c){
     return c.toString(16)
    });
    console.log("coloursChangedToBase16", coloursChangedToBase16);
 return "#" + coloursChangedToBase16.join("");
}

Which led to this one-liner:

const rgba2rgb = rgba => "#" + rgba.match(/.{1,2}/g)
    .map((c, i, a) => (i !== a.length - 1) 
        ? parseInt(c, 16) 
        : parseInt(c, 16) / 255)
    .reduce((t, c, i, a) => (i !== a.length - 1) 
        ? t.concat([parseInt(((1 - a[3]) * 255 + a[3] * c), 10)]) 
        : t, [])
    .map(c => c.toString(16))
    .join("");

Wednesday, 8 May 2019

lodash _.sortBy

I needed to order an array of objects in an arbitrary fashion, using strings which make sense to the business but aren't necessarily orderable otherwise. Unfortunately I had to support IE11, so couldn't do fancy JS. Thankfully I had access to lodash. Anyway, I came up with this:

var statuses = ["one", "two", "three", "four"];

var complexObj = {
    dataset: [
        {
            label: "two"
        },
        {
            label: "four"
        },
        {
            label: "three"
        },
        {
            label: "one"
        },
    ]
};

console.table(complexObj.dataset)

complexObj.dataset = _.sortBy(complexObj.dataset, function(n) {
  return _.indexOf(statuses, n.label);
});

console.table(complexObj.dataset)

Which worked a treat, once I'd clocked I need to return the sorted array rather than merely sort it.

Sunday, 5 May 2019

Op Art with p5

My Dad and I both love Op art, and he recently forwarded an email from Tumblr with this link. It piqued my interest as I'm playing with p5 an awful lot at the minute in my spare time as you'll see from previous posts.

So I got to it but clocked that there might be an issue in terms of masking the squares which make up the image. A little judicious playing with Gimp meant that I knew the graphic would be made up of multiple squares. The squares either started with a black or yellow background and they changed after a delay. To get around the issue of masking, I opted to use createGraphics() and then embed that graphic in the main body of the image using the image() function. I ended up making a simple error though, but Stack Overflow came to my rescue, and I'm quite pleased with the result:

The code, which is also up on GitHub and as a single file on JSFiddle:

class Tile {

    constructor(p5, x, y, dimension, row, delay) {
        this.p5 = p5;
        this.x = x;
        this.y = y;
        this.dimension = dimension;
        this.delay = delay;
        this.onFirst = row % 2;
        this.on = p5.color(255, 184, 0);
        this.off = p5.color(26, 17, 16);
        this.diameter = Math.sqrt(Math.pow(dimension, 2) * 2)
        this.pg = p5.createGraphics(dimension, dimension)
        this.pg.noStroke();
    }

    update() {
        if (this.delay === 0) {
            if (this.diameter < 0) {
                this.diameter = Math.sqrt(Math.pow(this.dimension, 2) * 2);
                this.onFirst = !this.onFirst;
                this.delay = 120;
            }
            else {
                this.diameter -= 1;
            }
        } else {
            this.delay -= 1;
        }
        return this.draw();
    }

    draw() {
        this.pg.fill(this.onFirst ? this.off : this.on);
        this.pg.rect(0, 0, this.dimension, this.dimension);
        this.pg.fill(this.onFirst ? this.on : this.off);
        this.pg.circle(this.dimension / 2, this.dimension / 2, this.diameter);
        return this.pg;
    }
}

new p5(p5 => {

    const rows = 14;
    const columns = 14;
    const dimension = 40;
    const framerate = 20;
    const tiles = [];
    const delay = 30;

    p5.setup = () => {
        p5.createCanvas(columns * dimension, rows * dimension);
        for (let row = 0; row < rows; row++) {
            for (let column = 0; column < columns; column++) {
                tiles.push(new Tile(
                    p5,
                    column * dimension,
                    row * dimension,
                    dimension,
                    row,
                    column % 2 ? ((rows - row) * 5) + 80 : row * 5
                ));
            }
        }
    };

    p5.draw = () => {
        p5.background(200);
        tiles.forEach((tile) => {
            p5.image(tile.update(), tile.x, tile.y);
        });
    };
});

Wednesday, 24 April 2019

JS Comma Operator

A mate at work asked me a question about checking things in JS. He had a case where if an object had a property he needed to check for 3 conditions, one of which was if the properties value matched a given value. If it didn't have the property then he needed just needed to check 2 conditions. He wasn't overly keen on wrapping the two conditions within a further if statement and asked if I knew of a way of combining them all into one if statement, I must admit I knew that there was one but I couldn't think of it for the life of me...

As in a particularly nasty case of athletes foot, it itched at me overnight, then I started to remember a rather obscure feature of JS which it seems it borrowed from C, the Comma Operator. In order to check that it would do what he needed to do, I wrote this simple JSFiddle:

let a;

if(true && true && a === 1, a){
  alert("First: a === 1");
}

a = 1;

if(true && true && a === 1, a){
  alert("Second: a === 1");
}

It's rather obscure though, so I'm a little concerned about using it with production code, as discussed on StackOverflow.

Saturday, 6 April 2019

p5-INVADER

You might've seen previous posts on my conversion of Python to p5 from Wireframe magazine, and this has been by far the hardest one I've tried as it involved lots and lots of looping through arrays. Arrays not only of the elements which make up the characters, such as shots and shields but also through the matrix which I use to generate the images of the shots and shields. Using a matrix or multi-dimensional array in this way is an approach I'm thinking of adapting for the Digital Counter as it allows a visual representation of the character within an array and so makes tweaking the characters more accessible.

If you check through the history of the repo, you'll see that I went through many different methods of calculating whether or not a shot simply grazed a shield or whether it had a substantial impact, resulting in an explosion. I did this eventually by allowing the shot to have a hit point value which incremented each time it chewed a pixel from the shield; when that hit point reached a critical level, then the shot exploded and eroded a far greater chunk from the shield.

To save resources each shot is removed from an array when it drops too far beneath the stage, and each shield removed when it is empty. I guess I should also close the processing when there are no more shields, but this does pretty much what I wanted and seems to mimic the original Python implementation closely enough. If not you're welcome to branch and tweak it.

Monday, 1 April 2019

grid-area in VueJS

As you can probably tell from recent posts, I'm making extensive use of CSS grid layouts... I'm also having to support IE11 while doing so, oh joy. The thing is I like the grid-area approach to defining where things should go within the layout, and I write it in the following way:

{
  ...
  grid-area: grid-row-start / grid-column-start / grid-row-end / grid-column-end;
  ...
}

Which is cool, but it took me ages to figure out why it wasn't working... even though it was simple enough to get it working in IE11 too where I replaced it with four declarations:

{
  ...
  grid-row: grid-row-start;
  grid-row-span: (grid-row-end - grid-row-start);
  grid-column: grid-column-start;
  grid-column-span: (grid-column-end - grid-column-start);
  ...
}

I was left though with it still not working as expected until I clocked that Vue was replacing grid-row-end and grid-column-end with auto. I then clocked that the JS syntax is slightly different in that the final two values can also use spans like this:

{
  ...
  grid-area: grid-row-start / grid-column-start / span (grid-row-end - grid-row-start) / span (grid-column-end - grid-column-start);
  ...
}


Setting it as four integers was not working, setting it as integers prefixed with span was working in Vue where the final two integers were getting replaced with auto - so I replaced all instances with width and depth spans and updated my script accordingly, the required output is now:

{
  ...
  grid-area: grid-row-start / grid-column-start / span width / span depth;
  ...
}


Combined with the specificity required for IE11 we then have the following:

{
  ...
  grid-row: grid-row-start;
  grid-row-span: width;
  grid-column: grid-column-start;
  grid-column-span: depth;
  grid-area: grid-row-start / grid-column-start / span width / span depth;
  ...
}

Which, when converted to a method function, results in this:

placeElement: function (x, y, width, depth) {
    return {
        gridRow: x,
        gridRowSpan: width,
        gridColumn: y,
        gridColumnSpan: depth,
        gridArea: [x, y, "span " + width, "span " + depth].join(" / "),
    };
}

Not brilliant to look at but Vue will auto prefix the individual values so that gridRow becomes -ms-grid-row etc. for IE11 and won't bother outputting anything other than the grid-area for Chrome et al.

It also seems cleaner in that we have the four arguments and it looks more natural and logical somehow to define how many rows and columns the element should span.

Along with that, returning an object means we can then add to it in subsequent functions.

Sunday, 24 March 2019

Digital Counter

I'm still working with grid layouts and making sure they work properly in IE11... I'm also concerned with petrol prices at the minute at my local BP Garage... so I got to playing with digital readouts in preparation for making a CSS grid calculator and developed this. I'm using lodash merely because it helps with all the nice array functionality which we don't have in IE11.

Saturday, 23 March 2019

p5 TAIL

After my last post, I got to work on another Wireframe conversion from Python to JS using p5, this one is from issue 6:
This is the code, simple eh? I did need a wee bit of help from PaweĊ‚ Dawczak as I'm not totally au fait with the vagaries of Python.
const segment_size = 50;
const angle = 2.5;
const phase_step = 0.3;
const wobble_amount = 0.8;
const speed = 4.0;
const width = 600;
const height = 600;
let seconds = 0;
let tail_pieces = [];
function setup() {
    createCanvas(width, height);
    tail_pieces = Array.from({
        length: 10
    }, (_, i) => ({
        img: loadImage('images/tail_piece.png'),
        x: null,
        y: null
    }));
    tail_pieces.push({
        img: loadImage('images/tail_hook.png'),
        x: null,
        y: null
    });
}
function draw() {
    clear();
    seconds += 0.01;
    let x = width - (segment_size * 2);
    let y = height - (segment_size * 2);
    tail_pieces.forEach(function(p, index, arr) {
        p.x = x;
        p.y = y;
        const temp_angle = angle + wobble_amount * Math.sin(index * phase_step + seconds * speed);
        x += segment_size * Math.cos(temp_angle)
        y -= segment_size * Math.sin(temp_angle)
    });
    tail_pieces.filter((p,index) => index % 2 === 0 && image(p.img, p.x, p.y));
    tail_pieces.filter((p,index) => index % 2 === 1 && image(p.img, p.x, p.y));
}

Wednesday, 20 March 2019

p5-EXPLOSIONS

I love Wireframe magazine, mainly because it seems like a blast from the past where I eagerly bought the latest ZX Spectrum magazine for the game listing... then spent hours upon hours entering the code into the machine to play a game. I know there were often bugs, but the debugging was sometimes the best part. I know I'm not alone in this as at least Ben Collins joins me in an appreciation of debugging.

Where I'm sort of sad though is that most of the smaller listings are in Python and, while I can find my way around Python, I much prefer JS, so I've started converting the listings to JS using p5. I've translated the Parallax example from issue 3, but the one above (from issue 1) was the most challenging yet as I needed to re-architect the program to work with p5. It wasn't all that hard really, but it was fun, especially getting my head around the trigonometry.

This is the JS code, should you want to enter it yourself:

const width = 800;
const height = 400;
const drag = 0.8;
const particles = [];
const framerate = 10;
const speed = 60;

const explode = () => {
    const x = random(width);
    const y = random(height);
    for (var i = 0; i < 100; i++) {
        particles.push({
            x, 
            y, 
            vx: speed * pow(random(1), 0.5) * sin(random(2 * PI)), 
            vy: speed * pow(random(1), 0.5) * cos(random(2 * PI)), 
            age: 0, 
            birth: new Date()
        });
    }
}

function setup() {
    createCanvas(width, height);
    stroke(255, 230, 128);
    frameRate(framerate);
    explode();
}

function draw() {
    if (frameCount % 10 === 0) {
        explode();
    }
    clear();
    background(0);
    particles.forEach(function(p, index, arr) {
        if (p.age <= 2) {
            const age = new Date().getSeconds() - p.birth.getSeconds();
            point(p.x, p.y);
            p.x = p.x += p.vx * p.age;
            p.y = p.y += p.vy * p.age;
            p.vx = p.vx * Math.pow(drag, p.age);
            p.vy = p.vy * Math.pow(drag, p.age);
            p.age = age;
        }else{
            arr.splice(index, 1);
        }
    });
}

Thursday, 14 February 2019

Dom’s PiWars contribution – Arvind

NOTE: Cross-posted from here

Arvind and I met the week before last for a quick chat so I could write this, and it's taken me this long to have time to write up the conversation. I can only blame the vagaries of Internet Explorer 11 messing with my workflow; it's so frustrating to have something that looks and works just as it ought to in every browser under the sun, only for our QA to come along and test it in IE11 and notice that it's borked. It's a shame that a significant proportion of our customers use it, despite the recent advice from Microsoft to avoid using it as the default browser.

Ah well, back to talking about Arvind. I should approach describing Arvind by giving some context. In the dim and distant past, I moved from a job as a forensic psychiatric nurse to nursing on nights in an acute psychiatric ward in Cambridge.

Not long after starting there the latest change in nurse education occurred and we who worked nights were asked to mentor student nurses for a compulsory set of nights. I only managed to mentor two before trends changed again and they were no longer required to do some nights before qualifying.

One was the daughter of my friend and boss at the time, and we're still friends though she's no longer a nurse. I don't think she nursed for much longer after finishing her training before becoming a teacher. Here I am kicking myself for not remembering if she was a trained teacher before nurse training or if she went for teacher training afterwards - whatever though: we still keep in touch, so I'm guessing it wasn't working alongside me that put her off nursing.

The other was Sally, Arvind's wife, and I kept abreast of what she was doing but she worked on a different site, and despite us both living afloat on the Cam for a while, we lost touch. My divorce happened, and it turns out that, like many such things in Cambridge, my next wife knew Sally, so I kept up-to-date by proxy.

Then years and a different career happened before someone clocked my geeky t-shirt at the gym and introduced me to Ely Makers and Arvind got in touch and mentioned that his wife knew me. How cool eh?

Anyway, that's the context: on to Arvind. He works as developer like me but is far more adept at hardware type things like 3D printing and electronics whereas not even my Mum asks for my help with technology, and my wife has her own, individual, printer.

I was thinking of how best to describe him and while doing so was reading - for the third time - Dan Simmon's Hyperion (or The Fall of Hyperion) and was struck with something that one of the Pilgrims, Sol Weintraub, said. While describing his daughter, he says something about knowing someone who's sheer goodness makes one strive to be better themselves (having said that I can't find the relevant section now so I might be wrong about the character). I know a few of those people, and despite them making me feel distinctly un-good in comparison, I feel privileged to do so - even more so to be married to one (hey, it's nearly the 14th of February so I can't help myself).

So we know that Arvind is a developer and that I consider him intrinsically good, but what else is there to say about him? Well, one of the things that shaped my consideration was his emphasis on helping others and collaborating. He took exception (at least as much as he seems able to, anyway) at my hesitation to share too many details of our final entry to PiWars this year, for instance.

I thought I'd better show some interest in the actual final product during our conversation and subsequently learnt about the debate about whether to use tracks or wheels and also about stepper motors (which sound fascinating). The thing that does stand out is that while Arvind isn't organising our team, he is one of the many pillars of Ely Makers and without his enthusiasm, we'd be all the poorer.

We, of course, discussed our shared profession, especially in the context of the current divide that seems to be occurring with those who sling HTML, CSS and minimal JS becoming an almost different animal from those who dig deep into whatever the favourite JS Framework is at the minute. This dichotomy is something of a new debate and was brought to my attention by the article by Chris Coyier called The Great Divide, give it a read: it's fun.

Along with the discussion of our jobs we looked at the broader aims of Ely Makers, and there seem to be two principal aims. These seemed to me to be to encourage people to get involved, while also remove the fear which some feel towards technology. I was reminded of the Men's Shed Movement, though the club aims to embrace all genders and abilities. Arvind want to encourage all, so I guess calling it a Men's Shed-like thing is wrong, as all are welcome... let's call it a Shed Movement instead ;-)

I guess even calling it a Shed Movement might be incorrect as Arvind is keen to encourage community and collaboration, not only within Ely Makers but amongst other Makers and the wider community. Thinking about it that seems to be the ethos of all involved with loads of us pitching in to put on events, if not as part of Ely Makers then wider afield (two of us a Code Club volunteers). I guess this is echoed right there on the front page of our website:

Ely Makers is a friendly network of people of all ages with an interest in Science, Technology, Computers and Electronics. The group is based in Ely, Cambridgeshire although anyone is welcome to join.

To sum up, as this post is late and I need to do another over the coming weekend, if you have a chance to meet Arvind or Sally, then please do say hello. Should you be knocking around Ely over the weekend when we're running a session, then please do pop in and check out the stuff we're doing. It's worth keeping a weather eye on our Events & Dates page.