Christian Heilmann

You are currently browsing the archives for the General category.

Archive for the ‘General’ Category

10 print chr$(205.5 + rnd(1));:goto 10 in JavaScript

Friday, January 19th, 2024

Forget about the Vision Pro, or whatever Samsung just brought out, we all know that the pinnacle of computing fun has been released in 1982 in the form of the Commodore 64.

One of the coolest things you could show people when writing BASIC on it was the following one-liner:

10 print chr$(205.5 + rnd(1));:goto 10

Executed, this resulted in a diagonal endless maze:

What the one liner does is print a character with the PETSCII code 205 or 206 (or SHIFT + M and SHIFT + N) which are fat diagonal lines. It does that using the PRINT command and the CHR$() command which turns a number into a character, much like fromCharCode() does in JavaScript. Luckily enough, the CHR$() command doesn’t care if it gets integers or floats. without the CHR$() it would be a list of floats:

When you ended a PRINT command with a semicolon, the computer didn’t add a new line but kept the cursor where it was.

Michel de Bree also has a version of this that is even shorter using Assembler and the D012 functionality, which stores the current scanline of the screen. He also pointed out that there is a whole book about this one liner and others that use faux random designs on the good old breadbox.

Now, let’s try something similar in JavaScript. A classic approach would be nested for loops.

let out = '';
for (let y = 0; y < 10; y++) {
    for (let x = 0; x < 40; x++) {
        out += (Math.random() > 0.5 ? '\\' : '/');
    }
    out = out + '\n';
}
console.log(out);

This works, but doesn’t look good.

Maze generated with slash and backslash

The reason is that slash and backslash have too many pixels around them. UTF-8 has box drawing characters, which allows us to use two diagonals that have less whitespace, ╲ and ╱ respectively, or 2571 and 2572 in unicode.

Using this, and moving from classic nested loops to chained array methods, we can do the following:

console.log(new Array(400).fill().map((_, i) => 
    (i % 40 === 0 ? '\n' : '') + 
    (Math.random() > 0.5 ? '\u2571' : '\u2572')
).join(''));

We create a new array of 400 items, fill it with undefined and map each item. As the item is irrelevant, we use _, but what’s important is the index, so we send this one as i. We then add a linebreak on every 40th character or an empty string. We then use Math.random() and see if it is above 0.5 and add either ╲ or ╱. We join the array to a string and log it out.

This looks better:

Maze generated with unicode characters

However, it doesn’t have the WUT factor the original one liner had. Luckily enough the two unicode characters are also following one another, so we can use fromCharCode with Math.random() to do the same. JS is not as forgiving as BASIC on Commodore64, so we need to round to the next integer and as we use unicode, fromCharCode() also needs a ‘0x’ to work:

console.log(new Array(400).fill().map((_, i) => 
    (i % 40 === 0 ? '\n' : '') + 
    String.fromCharCode('0x' + '257' + 
    Math.round(Math.random() + 1))
).join(''));

Coding is fun. Let’s do more of it without reason.

Want to do it live with a chance to get to the finals of the CODE100 coding competition? We run another edition of it in Amsterdam on the 29th of February. Apply here as a challenger!

Rob Bateman and his journey to resurrect Flash content on today’s web

Thursday, December 21st, 2023

Flash has been a boon to the web and the bane of my work as a web standards advocate. Now defunct, it is great that people work on converting old content. I’ve been talking to Rob Bateman about his work on converting Flash games to run on today’s web in an edition of Coffee With Developers.

I met Rob again at Halfstack in London and if you are interested in more details about the work he is doing, his “An Emulator’s journey” is also available on YouTube:

A santa themed CODE100 puzzle – Hitting the chimney

Tuesday, December 19th, 2023

Illustration of Santa facepalming because he failed to hit the chimney with his presents

I just finished another puzzle for the CODE100 competition and thought I make it Santa themed. Imagine Santa cocking things up by dropping his presents everywhere but inside the chimney. Can you find out which ones hit, which ones landed on the chimney and which ones dropped outside of it?

Ilustration showing points in the circle as grey, points outside the circle as black and points on the circle as green

You get points in a coordinate system that are the drop points of presents and you should sort them into different arrays: those that landed in the chimney, those that landed outside and those that landed on the chimney.

You get the a JSON dataset of a coordinate system with a certain height and with and a circle in its centre with a radius of 75 pixels that is 10 pixels wide.

The `droppoints` array contains the coordinates of the dropped presents, and your task is to sort them into different arrays. Copy all points that are inside the circle into the `innerPoints` array, all the ones outside the circle into the `outerPoints` array and all that landed on the chimney into the `onChimneyPoints` array. Store the size of each of the arrays in `inside`, `outside` and `chimnney` and return all of them as a JSON object.

For example, the original dataset is:

{
    "width": 300,
    "height": 300,
    "chimneyWidth": 10,
    "chimneyRadius": 75,
    "inside": 0,
    "outside": 0,
    "chimney": 0,
    "innerPoints": [],
    "outerPoints": [],
    "onChimneyPoints": [],
    "droppoints": [[127,37],[202,112],[113,84], … ] 
}

Your result should be something like:

{
    "inside": 30,
    "outside": 120,
    "chimney": 4,
    "innerPoints": [[157, 170],[169,170],[131,166], … ],
    "outerPoints": [[90,279], [16,247], [242,140],[72,209], … ],
    "onChimneyPoints": [[208,102] [208,102] … ],
}

I’m sad to report that with this dataset, `20` presents landed on the chimney.

Good luck! You can submit your solution in this Gist on GitHub. If you use JavaScript to solve the puzzle, here’s a CodePen that can get you started.

Cracking a “Developer Tools Killer” script…

Tuesday, November 14th, 2023

The other day I got an email from somebody who took one of my developer tools courses and he said he found a website that cannot be debugged. So I looked, found a nasty script and show you how to work around that one. You can watch the video on YouTube or read on…

I was intrigued and asked if I can see the web site. Turns out it’s one of those, let’s say a website with lots of videos, not necessarily all of it safe for work and not necessarily all of it legal. I went into my private browsing mode, turned on my VPN and took a look in Firefox what’s going on there.

I looked at the sources and I found a script that’s actually pretty good in trying to prevent you from using the developer tools. So let’s take a look at what it does and how we can work around it. I un-minified and documented the script and what it does are some really nasty things. You can check it on GitHub and also try the demo page yourself.

var tryCount = 0;
var minimalUserResponseInMiliseconds = 200;
function check() { 
    console.clear();
    before = new Date().getTime();
    debugger;
    after = new Date().getTime();
    if (after - before > minimalUserResponseInMiliseconds) { 
        document.write(" Dont open Developer Tools. ");
        self.location.replace(
            window.location.protocol + window.location.href.substring(
                window.location.protocol.length
            )
        );  
    } else { 
        before = null;
        after = null;
        delete before;
        delete after;
    }
    setTimeout(check, 100);
}
check();
window.onload = function () { 
    document.addEventListener("contextmenu", function (e) { 
        e.preventDefault();
    }, false);
    document.addEventListener("keydown", function (e) {
        // Ctrl+Shift+I 
        if (e.ctrlKey && e.shiftKey && e.keyCode == 73) { 
            disabledEvent(e);
        }
        // Ctrl+Shift+J 
        if (e.ctrlKey && e.shiftKey && e.keyCode == 74) { 
            disabledEvent(e);
        } 
        // Ctrl+S 
        if (e.keyCode == 83 && (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)) { 
            disabledEvent(e);
        }
        // Ctrl + U 
        if (e.ctrlKey && e.keyCode == 85) { 
            disabledEvent(e);
        }
        // F12
        if (event.keyCode == 123) { 
            disabledEvent(e);
        } 
    }, false);
    function disabledEvent(e) { 
        if (e.stopPropagation) { 
            e.stopPropagation();
        } else if (window.event) { 
            window.event.cancelBubble = true;
        } 
        e.preventDefault();
        return false;
    }
};

The first, and common thing is to block the context menu and all the keyboard shortcuts to open developer tools by adding handlers on the document. Ctrl+Shift+I is blocked, so is Ctrl+Shift+J, Ctrl+U and F12. They also blocked Ctrl+S to prevent saving the website to look at the source code. All these handlers call the disabledElement function which stops the propagation, does a cancel bubble, prevent default and returns false for good measure.

So that means all of the normal ways of opening developer tools should not be available to you when you have that website open in your browser.

The other clever thing they did was actually embed that into the main HTML document instead of having a killer script like we have here. So you can not block the script resource as it would mean you can’t see the page at all.

If you try the script, it seems that it is working in blocking you out. I’m on a Mac and for some reason Option+Command+I still works, which allows me to open developer tools. Then I encountered the next naughty thing that they’re doing here, which is that they have a debugger statement in there. I’ve seen it in several websites – they throw you into an endless loop with a debugger statement. If you try to skip over that one, it will keep going to the same point and stop you there.

The script also reads how long it takes for you to turn the debugger on and off. And then it does a document write and reload of the page if it happens. I don’t know why that’s in there. I never managed to trigger it. But, okay, good luck. Probably there was something else that people tried to do. If you know, tell me about it.

Then they do a set timeout with 100 milliseconds and keep calling that check function, which does a console clear and invokes that debugger. Now how can we work around that? The easiest way is to turn off all breakpoints. When you reload the page, you don’t have that debugger problem any longer.

Deactivating all breakpoints in Chromium Developer Tools

What you still have is that they’re doing another naughty thing which is clearing the console continuously to prevent you from entering anything. One way around that is to turn on preserve log. That way you get a report that the console was cleared but you can still use it.

Preserving log in console

The biggest mistake that the script creators did was not to use a closure (probably because they need the timeout). So, as the check() function is a global one, I can also simply overwrite it with function check(){return true;} and it stops annoying me.

It is fun to see how far people go to stop you from looking into their code. And there are legitimate reasons to turn off debugger tools for certain web sites. For example, when I worked on Microsoft Edge, we considered proposing a standard HTTP header that would disallow developer tools for banking sites and such. Bad actors do use Developer Tools with remote access software to fake for example bank transfers so it would make sense to have a standard way. This hacky script is impressive, but in the end just a nuisance.

Pangram validator in one line

Monday, November 6th, 2023

Source code

For a quiz, I am playing with Pangrams, sentences that have all 26 letters of the alphabet in them. Using Set and some RegEx it is a one-liner in JavaScript to validate them (split into two lines for readability…).

const isPangram = s => new Set(
  s.toLowerCase().replace(/[^a-z]/g, '')
).size === 26;

I’ve also put together a small tool for myself to write pangrams and check them whilst I type. You can see it in action here:

screen recording of the tool validating a sentence

Do you have a shorter solution?