Introduction

The Atari ST was a rather popular personal computer from the mid 80's to early 90's era, powered by a Motorola 68000 microprocessor. On this machine, the default behavior of the operating system for uncaught CPU exceptions was to display a row of bombs on the screen, as shown in the following picture:

The number of bombs depends on the exception vector, the most common ones being:

($008) Bus Error : 2 bombs

($00c) Address Error : 3 bombs

($010) Illegal Instruction : 4 bombs

Goal

Your goal is to write a program or function that prints or outputs an ASCII art of such Atari ST bombs.

Input

An integer representing the number of bombs to display. Your code must support the most common values: 2, 3 and 4. Supporting less and/or more bombs is fine, but it is neither required nor subject to a bonus.

Output

The original bomb consists of a 16x16 pixel tile, represented here in both ASCII and binary:

In this challenge, each ASCII bomb must be stretched to twice its original width for a better rendering. Therefore, it will consist of 16 rows of 32 characters, using ## for 'ON' pixels and two spaces for 'OFF' pixels. All bomb tiles must be put side by side. Leading spaces are forbidden. Trailing spaces are also forbidden, except the ones that are actually part of the bomb tile (i.e. the 31st and 32nd columns) which must be present. You may include no more than one leading line-break and no more than one trailing line-break.

Example

Below is the reference output for two bombs, where mandatory line-breaks are marked as \n and tolerated extra line-breaks are marked as (\n):

\$\begingroup\$@PeterCordes - That's right, it must be ASCII. However, you'd be allowed to read the bomb graphics from the ST ROM since there's no rule that prevents you from doing it. (Just mention the TOS version this is supposed to work on.)\$\endgroup\$
– ArnauldOct 5 '16 at 11:48

1

\$\begingroup\$Oh WOW, that brings back memories. My first computer was an Atari ST. I remember those bombs with dread.\$\endgroup\$
– RodOct 6 '16 at 15:27

\$\begingroup\$Aside: "The number of bombs depends on the exception vector" - say what?! Any reason why they couldn't output the actual code/error? (Never had an ST, I'm from the Amiga camp... "Guru Meditation" and all that.)\$\endgroup\$
– MrWhiteOct 6 '16 at 21:02

31 Answers
31

How?

Preparation was to compress the data as a run-length encoding of the original image:

Count the length of each run of 1s (space) or 0s (hash) in the image, ignoring new lines - yields a list: [4,2,11,1,1,...];

Subtract one from each number - this gives a range of [0,15];

Treat this as a base-16 number (enumerate the values, v, with index i in reverse and sum up 16**i*v = 19468823747267181273462257760938030726282593096816512166437);

Convert this to base-250: [5,119,249,42,...];

Map into Jelly's code page as indexes: ¥vẏ)X;ndĊɓ¡ẹ3ċi}Ịɲ¡P

Now the code evaluates this number, maps the 1s and 0s to space and hash characters*, doubles each, splits into lines and repeats each the appropriate number of times.
* actually the implementation is performed modulo 2 to save bytes, so spaces are odd and hashes are even:

As the output image only consists of 2 characters we can represent it as a binary number.
We can ignore newlines as every line has the same length.
We can ignore the last char of each row as it the same for all rows.
We use the thinner image as it takes up less space and we can easily duplicate each character later.

Using 1 to represent space and 0 to represent # we get the binary number:

How it works

Since each row of the raw data can be represented as a 16-bit number, we can store the entire file in a 16-char string. The compression algorithm takes each binary row, flips it and reverses it (since every row in the original ends in a 0, every row in the modified version now starts with a 1), then turns it into a char, and concatenates the resulting characters.

To decompress it, we need to extract the charcode and turn its binary representation into a string of hashes and spaces. This can be done with a recursive function, like so:

(f=q=>q?(q&1?" ":"##")+f(q>>1):"")(x.charCodeAt())

f repeatedly takes the last bit of q, selecting two spaces if it's 1 or two hashes if it's 0, then concatenates that with the result of running f in the rest of q. This is run on x.charCodeAt(), turning the char-code into the correct string of spaces and hashes.

(There was a lot more drama here before, but the 4-byte-saving technique erased all of that.)

After that, we can just repeat the string n times and add a newline. This is the shortest decompression method I have found, but feel free to suggest any possibly shorter methods.

\$\begingroup\$Nice. We're still lacking a 68000 answer, but this one is getting closer. :-)\$\endgroup\$
– ArnauldOct 4 '16 at 19:49

\$\begingroup\$objdump -dw output is a good way to show the raw binary, since you see which bytes are which instruction. I did that for gcd and adler32 answers. (As well as including commented source code for people to try themselves.)\$\endgroup\$
– Peter CordesOct 5 '16 at 2:53

Second approach:

Rather than creating a list of strings on-the-fly, there is a hard-coded hexadecimal string which is indexed and converted to binary; then each binary digit is turned into either ' ' or '#', which is duplicated and joined together ... etc.

This contains a hard-coded list of the strings of each line (not including trailing spaces) created by duplicating either ' ' or '##' a number of times. For each of these strings, they are padded with spaces until 32 characters in length, duplicated n times, then joined with newlines.

\$\begingroup\$You can save a byte by switching to printing an unrolled generator, instead of joining on '\n'. So, lambda n:print(*(''.join(2*' #'[int(d)]for d in bin(int('0c0052000100908023e003e00ff81ffc1ffc3ffe3fde1fdc1fbc0ff807f001c0'[i:i+4],16))[2:].zfill(16))*n for i in range(0,64,4))). Also, you don't have to count the bytes needed to assign the lambda a name. So your score can be 176.\$\endgroup\$
– Morgan ThrappOct 5 '16 at 17:38

Try it online!(note that there are some unprintable characters in the code.)

The bomb in encoded in as a number in binary using 1 for spaces (the leading space as 1 ensures we don't have to pad the binary representations), transposed, and then converted to string in base-136 (which produced the shortest string without wide-characters). These steps can be played with here.

This answer then reverses the encoding, the main trick being to repeat the bomb before transposing, effectively concatenating each line of the bomb at once. The characters in each line can be then be doubled up with newlines inserted for the final output.

\$\begingroup\$You can save 2 bytes by using a leading line break rather than a trailing one and thus not having the space after the echo and by using an actual line break rather than the \n.\$\endgroup\$
– user59178Oct 4 '16 at 8:13

\$\begingroup\$@user59178 I know. not so verbose please.\$\endgroup\$
– TitusOct 4 '16 at 11:02

Explanation

The code uses a pre-compressed version of the 16×16 binary matrix. The pre-compressing (not part of the program) used two steps:

Run-length encoding of the matrix read in row-major order (first across, then down).

The resulting run-lengths range from 1 to 16, so the vector of run-lengths minus 1 was converted from base 16 to base 94 (to use all printable ASCII codes except single quote, which is not used because it would need escaping).

The compressed string,

',cxJr(v9hW&waHB`U=NL%)C9[$aoAN'

is decompressed from base 94 to base 16:

F16Za

The obtained vector of run-lenghts plus 1 is multipled by 2:

QE

to perform the horizontal stretching.

The vector of run-lenghts contains 49 values. The original numbers to be repeated with those lengths should be [0 1 0 1 ... 0] (49 entries). But instead of that, it's shorter to use the vector [1 2 ... 49], which will be equally valid thanks to modular indexing. So the run-length decoding is

49: Y"

The generated vector containis the runs of 1, 2, ...49, for a total of 512 entries. This is reshaped into a 16×32 matrix:

32e!

and used as modular indices into the string ' #' to produce a single bomb:

' #'w)

Finally, horizontal repetition by a factor given by the input produces the desired result:

Python 2: 143 bytes

(I realised that direct encoding of the original bomb in base 36 made for shorter code in Python.)

The string was formed by treating spaces as 1s and hashes as 0s, and then converting to base 36. The program then converts back to binary and slices into lengths of 16 (with an offset of 2 for the '0b' at the front of Python's binary string) , converts to double spaces and double hashes, joins them up, repeats the string n times and prints.

Python 2.7, 144 141 bytes

x=input()
for i in range(16):print"".join(" #"[b<"1"]*2for b in bin(int("5ZCAZKAVTP6J04W4VZJ2BQDH5DASIKRS524V8SWRSIVWZEWC8V",36))[2+i::16]*x)

The bomb is written in binary with 1 for space, the leading 1 removes the need to pad binary representations. The bomb is transposed (much like in my CJam answer) and stored in base 36.

The program decodes the bomb to binary and iterates bits by a step of 16 effectively following the transposition (which is saves bytes over slicing a given line). The resultant line is concatenated, bits are replaced with doubled or #, and joined into a singe string.

Note: the line set s= ends in 5 spaces. Accepts the count as a command-line parameter. Simply loops through each line of the bomb (compressed very slightly by removing runs of 5 identical characters) then repeats the bomb as many times as desired before finally duplicating each character.

Python 2, 206205203199191188186184 160 bytes

z=input()
for y in 3072,20992,256,36992,9184,992,4088,8188,8188,16382,16350,8156,8124,4088,2032,448:print"".join(" #"[e>0]*2for e in map(int,bin(y+8**6)[5:]))*z

Looked at Hex for the number list but it didn't seem to save enough to make it worth the effort. Was hoping to be able to golf the code down but seem to have got as far as I can with this approach. Any additional hints gratefully received.

EDIT

-1 by changing e==1 to e>0. I always forget that one.

-2 by ignoring the length of the binary string, prepending 7 0's and taking only the last 16 elements. Works as there are never more than 7 leading 0's.

-4 because now that I have lost the second reference to the variable b I can use bin(y)[2:] directly in the map function taking it below the magic 200 :-)

-8 by using slice assignment on the second list. Learned something new this evening.

-3 with thanks to @Jonathan

-2 by using c=d=([0]*7+map(int,bin(y)[2:]))[-16:] instead of having c=d;

Explanation

'n' = # Associate the input with "n"
=> # Push a new function to the stack.
64 -B # Take the value from the top of the stack, convert it to an integer from Base64
2 B # Take the value from the top of the stack, convert it from an integer to binary
] # Duplicate the top of the stack.
1 16 sub # Push the substring from the top of the stack between 1 and 16, 1 indexed.
n # Push the input.
rep # Repeat, this gives us 'n' bombs, essentially.
\ # Flip the values such that REPEATEDBOMB_ROW TEXT
17 32 sub # Push the substring between 17 and 32.
n # Push the input
rep # Repeat
' # Push a new line
' # RProgN doesn't actually have escapes, so the raw newline is represented as a newline between qoutes.
\ . . # Flip the values so we have LINE1 NEWLINE LINE2, then concatenate them all into one string.
'1' ' ' replace # Replace all the 1's in the binary string with two spaces.
'0' '##' replace # Replace all the 1's with two '#'s
} a'' = # Associate the function with 'a'
D4D/4/'' a # Bottom two lines,
DgQ/AH'' a # Next two up,
DAIeAj'' a # Etc...
DgA8AB'' a # Once This is done, the memory stack is implicitly printed from top to bottom.
DwB+AD'' a # As such, the bomb is upside down.
DcH/wf'' a # Each of these numbers represents two lines, which is why we do the substringing to split it.
D+/29/'' a # This works out saving a few bytes.
Dz/63/'' a # Implicitly printed output. Yay.

Pretty long, The expanding, printing and such of the compressed string is 105 of the bytes.
Might be a bit more golfable, but atleast it works.

Explanation

This doesn't do any binary stuff and doesn't require an external file.

Every 16 bit number is reversed, then encoded as a base-36 number, padded with a leading 0 if necessary, so every 16 bits result in 3 bytes. Concatenating those results in 01c02203k07d1j81j46b4cmwcmwpa4ohobugc8o6b434w0ow. The code reverses the process so the bombs are printed correctly N times.

Tweaks

Saved 4 bytes by using only a single for-loop

Saved a byte by printing a single char for each iteration and using string index instead of ternary

Saved a byte by getting resetting $j to zero on line boundaries with %=. This gets rid of parentheses

Explanation

First a conversion of the hex representation of bombs
[f3 ff ad ff fe ff 6f 7f dc 1f fc 1f f0 07 e0 03 e0 03 c0 01 c0 21 c0 43 e0 43 f0 07 f8 0f fe 3f] to UTF-8 (in the UTF-8 version the compiler stores the string as Wide Char Array - 2 or 4 bytes for each character at runtime but this is academic). Whereas UTF-8 characters would be stored as 2-4 bytes, these values are all within ISO-8859-1 (ASCII) and thus only require 1 byte. Also it is safe to be stored as ISO-8859-x (there are no 0x8_ or 0x9_ values). Therefore the text consumes 32 bytes in ISO-8859 and the routine consumes 135 bytes in total.

(NB wide chars are stored as a 16 bit integer in windows and 32bit in linux but again this is irrelevant to the task at hand)

UTF-8

Here is the version closer to the original posting with UTF-8 encoded source.
This consumes 173 bytes. The string itself being 50 bytes of the source.
The routine is also longer as the ASCII bytes are now stored with padding 0's for the 16bit/32bit Wide Chars and have to be shifted instead of casted to uint16_t as above. I have kept this up as it can be verified with ideone which uses UTF-8 encoding.

\$\begingroup\$Impressive. What encoding does this use?\$\endgroup\$
– JamesOct 7 '16 at 18:01

\$\begingroup\$Its compiled on MinGW GCC where a wide character is an uint16. Therefore the encoding is [16-bit type holding UTF-16 Unicode]. However, I think that because the characters are within 0xFF (16bit) they are extended ASCII. + So nothing special\$\endgroup\$
– claydonkeyOct 7 '16 at 18:19

\$\begingroup\$Sorry I have learnt a tad more about the encoding and may have been wrong about it being stored as UTF-16.please refer to the overhauled answer.\$\endgroup\$
– claydonkeyOct 14 '16 at 5:28

\$\begingroup\$@KevinCruijssen Thanks (consider posting this as an own answer). I'm not sooo active here on codegolf, and thus am not sure about the rules, but I think that in most cases, only counting the bytes of the function (and ignoring the imports) is allowed.\$\endgroup\$
– Marco13Oct 4 '17 at 16:18

\$\begingroup\$It's a bit too similar to post as a separated answer, but I can understand if you don't want to edit your answer of over a year ago. :) I'll just leave it in the comment. And I'm afraid imports do indeed count toward the byte-count.\$\endgroup\$
– Kevin CruijssenOct 4 '17 at 17:32

The string following is a base-95 encoded string with an offset of 32, so that all the characters fall into the ASCII range, and thankfully there are no 's that need to be escaped. 3 u: gets the char codes of the string, 32x-~ makes each number an extended number and subtracts 32 from it; 95#. converts to a base-95 number an 2#.inv converts it to a binary digit array. I added a leading 1 to the binary in order to make it a solid number, so I take it off with }.. I shape the array into a 16x16 table with 16 16$ then transpose it using |:. (Possible golf for later: transpose the literal encoded string.) 2# duplicates each character width-wise. We are left with a table of 0s and 1s. ' #'{~ maps 0s to ' ' and 1 to '#'. As thus, we are left with our bomb.

C (Atari TOS 2.06 US), 129 124 117 113 bytes

This uses the bomb bitmap from the TOS ROM, which is slightly different from the one in the question. For other versions of TOS, you'll have to adjust the address pointed to by *a. Note that some emulator roms don't include the bomb bitmap!

If you don't provide a command line argument, several high resolution bitmapped bombs may be displayed :-)

SmileBASIC, 127 bytes

(Screenshot of version without doubled characters)
SB has a square font, so doubling the characters looks bad (and doesn't fit on the screen)
Non-ASCII characters have been replaced by x's.
Hex values: 0008,0002,0610,1F8A,3FC1,7FC1,7FF2,FFF4,FFF8,EFF0,73F0,7FC0,3FC0,1F80,0600
Since SB saves files in UTF-8, some of these count as 2 or 3 bytes.

\$\begingroup\$@Arnauld I don't know much about SmileBASIC, but given that there's a loop FOR K=1TO N with INPUT N, I think it displays the number of bombs given in the input. However, I must say that despite the square font, I believe the characters should still be doubled to keep consistent with the requirements (to avoid an advantage over other answers). You could keep the current one for a nicer-looking solution, but I think you should still add a correct solution.Once you add that, I'll upvote for the creative use of the UTF-8 characters!\$\endgroup\$
– HyperNeutrinoFeb 5 '17 at 23:18

Similar idea as the python version(s):
break the string of hexadecimal encoded bombs into sections of 4 characters, convert to binary, translate 1 to # and 0 to , double every character, and print the resulting array.

Note that puts is used to print the array. This will print the array out one line per element.

Output

Your Answer

If this is an answer to a challenge…

…Be sure to follow the challenge specification. However, please refrain from exploiting obvious loopholes. Answers abusing any of the standard loopholes are considered invalid. If you think a specification is unclear or underspecified, comment on the question instead.

…Try to optimize your score. For instance, answers to code-golf challenges should attempt to be as short as possible. You can always include a readable version of the code in addition to the competitive one.
Explanations of your answer make it more interesting to read and are very much encouraged.

…Include a short header which indicates the language(s) of your code and its score, as defined by the challenge.

More generally…

…Please make sure to answer the question and provide sufficient detail.

…Avoid asking for help, clarification or responding to other answers (use comments instead).

Code Golf Stack Exchange is a site for recreational programming competitions, not general programming questions. Challenges must have an objective scoring criterion, and it is highly recommended to first post proposed challenges in the Sandbox.