Binary Converter

I’m trying to implement a binary converter in one of my passages, where it would convert binary to text, but nothing seems to work. Here’s what I have so far:

<div id="mach-box">
<center>
<<textbox "$output" "">>


<<textbox "$input" "">>

<<button "Translate">>

var output = $('#textbox-output')[0];
var input = $('#textbox-input')[0];
output.value = parseInt(input, 2);

<</button>>
</center>
</div>

<<script>>

$(document).ready(function(){
	$("#textbox-output").prop("readonly", true);
});

<</script>>

I got the binary conversion code bit from here.

Try the following:

<div id="mach-box">
<center>
<input type="text" id="output" readonly>
<<textbox "_input" "">>

<<button "Translate">>
	<<run $('#output').prop('value', Number.parseInt(_input, 2))>>
<</button>>
</center>
</div>

Notes:

  1. Changed the output <<textbox>> macro into a <input type="text"> markup so it could be made read-only from the start, which also allowed dropping the final <<script>>, and also because you really do not need or want to use the <<textbox>> macro just to display something. Strictly speaking, you could do this with a regular HTML markup tag—e.g., a <div> or <span>—but then you’d need to style it to look like an <input>, assuming that was important to you, so I kept the <input> in my example to keep things simple.
  2. Changed the translate code to use the variable associated with the input <<textbox>> because you really do not need or want to use the <<textbox>> macro just to ignore the associated variable.
  3. Changed the inner <<script>> macro to <<run>> macro so TwineScript could be used—for native story/temporary access.
  4. Unless you need one or both values to live beyond the passage, then you really should use temporary variables.

If you do need both values to be stored within story variables, so you may access them in later passages, then you could do the following:

<div id="mach-box">
<center>
<input type="text" id="output" readonly>
<<textbox "$input" "">>

<<button "Translate">>
	<<script>>
	var sv = State.variables;
	sv.output = Number.parseInt(sv.input, 2);
	$('#output').prop('value', sv.output);
	<</script>>
<</button>>
</center>
</div>

With that, both the input and output values may be accessed in other passages as the story variables $input and $output, respectively. Ideally, however, you should only store values you need to persist within story variables.

This works for me, but since it converts the binary to decimal, I added String.fromCharCode() to your <<run>> code to convert it to ASCII characters, which somewhat did the job. It only converts one byte, or one letter. If I wanted to convert several binary bytes to a word, it just showes the first letter.

<<run $('#output').prop('value', String.fromCharCode(Number.parseInt(_input, 2)))>>

Am I doing this the wrong way?

It converts a number to its binary form, actually. And you’re going to have to explain what you’re actually attempting to do if that’s not what you wanted, because that’s what the code you found does.

Sorry for not being clear. I’m trying to have a simple binary converter that converts binary to text, like here. When I put the original <<run>> code, it converts them to decimal (eg typing “00010000” would result in “16”). So I wrapped the Number.parseInt() bit with a String.fromCharCode so that instead it would output a letter (eg typing “01000001” would result in “A”, since its ASCII code is 65). It works but it only outputs one letter. If I wanted to type several binary bytes it would only output one letter instead of a whole word. I was wondering if my approach was wrong, and if I have to do something else to get it to output a full word. Hope I cleared things up.

This might help, if I understand what you’re trying to do.

First you could start with converting a string to the binary representation of its character codes, then work backwards from the output to make sure you get the same input. Try something like this in the console:

for (c of 'banana') console.log(c, c.charCodeAt(0).toString(2).padStart(8, '0'))

That should output this:

b 01100010 
a 01100001 
n 01101110 
a 01100001 
n 01101110 
a 01100001

So, you want to take ‘011000100110000101101110’ and output ‘ban’, right?

I’d probably do something like this (test in console):

var binary = '011000100110000101101110'
binary.replace(/.{8}/g, b => String.fromCharCode(parseInt(b, 2)))

Then just wrap that guy up in a function and you should be good to go. Make sure to validate or sanitize the input first – either strip out anything that’s not a 0 or 1, then zero-pad so it has a multiple of 8 characters, or throw it out if it doesn’t fit that pattern.

This is assuming you only care about ASCII characters that fit in 8 bits, of course – characters as bytes, not characters as unicode codepoints.

What you want to do is certainly doable, but what you need to do to achieve it depends on whether you intend to limit your conversions to the Latin-1 subset or fully support Unicode. Basically, how many digits would be required per “character”, 8 for the Latin-1 subset or 16 for full Unicode.

See the following Gist I threw together binary-to-unicode.js—goes in your JavaScript section.

Usage example:

!!Example: setup.latin1FromBinary
<center>
<input type="text" id="output-latin1" readonly>
<<textbox "_latin1" "">>

<<button "Translate">>
	<<run $('#output-latin1').prop('value', setup.latin1FromBinary(_latin1))>>
<</button>>
</center>

!!Example: setup.unicodeFromBinary
<center>
<input type="text" id="output-unicode" readonly>
<<textbox "_unicode" "">>

<<button "Translate">>
	<<run $('#output-unicode').prop('value', setup.unicodeFromBinary(_unicode))>>
<</button>>
</center>

Try the following binary string with the Latin-1 version (yields: Hello, World!):

01001000011001010110110001101100011011110010110000100000010101110110111101110010011011000110010000100001

And the following binary string with the full Unicode version (yields: :see_no_evil::hear_no_evil::speak_no_evil:):

110110000011110111011110010010001101100000111101110111100100100111011000001111011101111001001010

I don’t think I need special characters like emojis, so Latin-1 is what I need. I tried your code and it’s great! Thanks so much!

Huh, wouldn’t it be easier to use TextEncoder and TextDecoder for that?

I mean something like this to encode it:

var binary = Array.from(new TextEncoder().encode('🙈🙉🙊'))
    .map(b => b.toString(2).padStart(8, '0'))
    .join('')

And this to decode:

new TextDecoder('utf-8').decode(
    Uint8Array.from(Array.from(binary.matchAll(/.{8}/g))
    .map(m => parseInt(m, 2))))

Are those not widely supported yet?

I’m getting a different value for the monkeys though, not sure what’s up there.

The Encoding API will never be supported on IE11 and is not yet supported on Edge (ca. Oct, 2019) at all. Other browsers, depending on the browser and version in question, may contain buggy and/or obsolete implementations—mostly older versions. The API is still a WHATWG draft and generally considered “experimental”—the decoder, at least, is still very much a work in progress.

I’m not saying don’t use it if it works for you, but I’m not going to recommend it yet due to both its status and because I generally write examples to be as broadly usable as possible. The latter because players, generally, aren’t going to care why a story/game isn’t working in their browser, they’re still going to complain, and because the author may not be tech savvy enough to be able to say “use a different browser” when issues invariably do pop up—not to mention that people can be stubborn about their choice of browser anyway; i.e., “There’s nothing wrong with my browser, fix your game!”.

The Encoding API encodes to UTF-8, rather than JavaScript’s native UTF-16—well, UCS-2+UTF-16 surrogate pairs for legacy reasons, but close enough. Thus, you’re seeing the UTF-8 encoding, rather than the UTF-16 encoding I posted.

Ahh, didn’t realize those were utf-16. But if you want to keep it in utf-16, can’t you just do something like this?

// encode
var binary = '🙈🙉🙊'.split('')
    .map(c => c.charCodeAt(0).toString(2).padStart(16, '0'))
    .join('')

// decode
var text = String.fromCharCode(
    ...Array.from(binary.matchAll(/.{16}/g)).map(b => parseInt(b, 2)))

Apparently IE doesn’t have padStart either, but that’d be simple to shim.