Search code examples
javascriptalgorithmbit-manipulationbitwise-operators

Algorith problem Decode Hex to set output


I got an algorithm that I need to solve. Unfortunately, I can't even find a clue about a solution. Please help me to understand how to solve this problem.

The problem

An imaginary pen input device sends hex data to the device to display a line with color.

Examples Draw simple Green Line
Set color to green, draw a line from (0,0) to (4000, 4000). Filled circle in this diagram indicates a pen down position, empty circle indicates pen up position.

Input Data: F0A04000417F4000417FC040004000804001C05F205F20804000

Output:
CLR; CO 0 255 0 255;
MV (0, 0);
PEN DOWN;
MV (4000, 4000);
PEN UP;

I got the information about each output. output data

This code is for encoding hex to binary. I guess that the solution would encode the hex to binary, and manipulate it to set correct output.

function hex2bin(hex){
    return (parseInt(hex, 16).toString(2))}

The expected result has the same output as Examaple's with the input data.


Solution

  • First of all, some important information is missing from your question: how the numbers like 4000 (in the result) are encoded in the hex format.

    I think I could derive it from the example though

    The peculiar numeric encoding

    Numbers seem to be encoded with 2 bytes (4 hex characters) each, where the most significant bits of these 2 bytes (bits 7 and 15) do not contribute to the value (they are always zero).

    Furthermore, the remaining 14 bits are encoded in offset binary, where the most significant of those 14 bits is the inverted sign bit.

    This means that "4000" is zero, "0000" is -8192 (the minimum), and "7F7F" is 8191 (the maximum). Note that the one but last character cannot be more than 7, since that would set a bit that is not used in this (custom) encoding.

    This was the hardest part to derive from the little info you provided.

    On the Example

    The input example you provided can be broken down into pieces like this:

    opcode | argument(s)
    -------+----------------------------
     "F0"  |
     "A0"  | "4000" "417F" "4000" "417F"
     "C0"  | "4000" "4000" 
     "80"  | "4001" 
     "C0"  | "5F20" "5F20"
     "80"  | "4000"
    

    Using the numeric conversion discussed above, this would translate to:

    opcode | argument(s)
    -------+------------
      240  |
      160  | 0 255 0 255
      192  | 0 0
      128  | 1
      192  | 4000 4000
      128  | 0
    

    And then it is a matter of following the instructions to turn that into the required output.

    So the algorithm could first decode the input string into commands, where each command consists of an opcode and zero or more numeric arguments.

    And then those commands could be turned into the required output by keeping track of whether the pen is down and what the current coordinates are:

    function decode(hex) {
        let commands = [];
        let command;
        for (let i = 0, len; i < hex.length; i+=len) {
            // Opcodes take 1 byte (i.e. 2 hex characters), and 
            // numbers take 2 bytes (4 characters)
            len = hex[i] >= "8" ? 2 : 4;
            let num = parseInt(hex.slice(i, i+len), 16);
            if (len === 2) { // opcode
                command = []; // start a new command
                commands.push(command);
            } else { // number
                // The encoded format is a custom one. This seems to be it:
                num = ((num & 0x7F00) >> 1) + (num & 0x7F) - 0x2000; 
            }
            command.push(num); // push opcode or argument in current command
        }
        return commands;
    }
    
    function disassemble(hex) {
        let isPenDown = false;
        let x = 0, y = 0;
        let output = "";
        let commands = decode(hex);
        for (let [opcode, ...args] of commands) {
            if (opcode === 0xF0) {
                x = y = 0;
                isPenDown = false;
                output += "CLR;\n";
            } else if (opcode === 0x80) {
                isPenDown = args[0] > 0;
                output += "PEN " + (isPenDown ? "DOWN" : "UP") + ";\n";
            } else if (opcode === 0xA0) {
                output += "CO " + args.join(" ") + ";\n";
            } else if (opcode === 0xC0) {
                let allPos = "", lastPos;
                for (let i = 0; i < args.length; i+=2) {
                    x += args[i];
                    y += args[i+1];
                    lastPos = ` (${x}, ${y})`;
                    if (isPenDown) allPos += lastPos;
                }
                output += "MV" + (allPos || lastPos) + ";\n";
            } // else: ignore unknown commands
        }
        return output;
    }
    
    // Sample:
    console.log(disassemble("F0A04000417F4000417FC040004000804001C05F205F20804000"));

    More to do

    In the screenshot of the problem, near the end, there is also mention of clipping movements to a bounding box. This goes beyond your question about decoding the hex input, so I will leave it at this. For the interested, you could check out Q&A about calculating line segment intersections, such as this one.