Steampunk: Clacker animation

11

1

In the highly underrated Steampunk novel The Difference Engine, the equivalent of cinema houses delivered a pixelated moving image displayed by tiles which could be flipped mechanically. The control engine for orchestrating the movement of the these tiles was a large noisy machine controlled by a deck of punched cards.

Your task is to emulate such an engine and display a pixelated animation as specified by an input file. The input consists of lines in a fixed-width format, but you may assume whatever is convenient for a line-ending indication. The format is:

SSSSYYxxXXOA
SSSS: 4 digit sequence no. may be padded by blanks or all blank
    YY: the y coordinate affected by this line (descending, top is 0, bottom is m-1)
      xx: the starting x coordinate
        XX: the ending x coordinate
          O: hexadecimal opcode
           A: argument (0 or 1)

The input is explicitly sequenced (if you ever drop your deck of cards on the floor, you'll thank me for this part). That means the program must perform a stable sort of the input lines using the sequence field as a sort key. Lines with the same sequence number must maintain their original relative ordering. (It should work with an unstable sort, if you append the actual line number to the key.) A blank sequence field should be interpreted as lower than any number (ascii collation sequence).

A single statement line can only affect a single y coordinate, but may specify a contiguous range of x values. The ending x value may be left blank or may be identical to the initial value in order to affect a single pixel.

The opcode is a hexadecimal digit which specifies the Universal Binary Function Code which is used as a rasterop. The argument is 0 or 1. The raster operation performed is

pixel = pixel OP argument          infix expression
         --or-- 
        OP(pixel, argument)        function call expression

So the original value of the pixel enters as X in the UBF table, and the argument value from the statement enters as Y. The result of this function is the new value of the pixel. And this operation is performed upon each x,y pair from xx,YY to XX,YY specified in the statement. The range specified by xx and XX includes both end-points. So

0000 0 010F1

should set pixels 0,1,2,3,4,5,6,7,8,9,10 on row 0.

Output dimensions (m x n) should be 20 x 20 at a minimum, but may be larger if desired. But the grain should show, you know? It's supposed to be pixelated. Both graphical and ASCII-art output are acceptable.

If for example, we wanted to make an image of a pixelated figure:

  #   #
   ###
   ##
   ####
    #
#### ####
   # #

   ###
   # #
   # #

If we draw him with a bit-flipping op, like XOR, it can be drawn and erased regardless of whether the screen is black or white.

    00020261
     0 6 661
     1 3 561
     2 3 461
     3 3 661
     4 4 461
     5 0 361
     5 5 861
     6 3 361
     6 5 561
     8 3 561
     9 3 361
     9 5 561
    10 3 361
    10 5 561

Duplicating this sequence will make the figure appear and disappear.

NMM is Not Mickey Mouse

A larger animation can be composed out-of-order, by specifying different "shots" in the sequence field.

   100 016F0
   101 016F0
   102 016F0
   103 016F0
   104 016F0
   105 016F0
   106 016F0
   107 016F0
   108 016F0
   109 016F0
   110 016F0
   111 016F0
   112 016F0
   113 016F0
   114 016F0
   115 016F0
   200020261
   2 0 6 661
   2 1 3 561
   2 2 3 461
   2 3 3 661
   2 4 4 461
   2 5 0 361
   2 5 5 861
   2 6 3 361
   2 6 5 561
   2 8 3 561
   2 9 3 361
   2 9 5 561
   210 3 361
   210 5 561
    00020261
     0 6 661
     1 3 561
     2 3 461
     3 3 661
     4 4 461
     5 0 361
     5 5 861
     6 3 361
     6 5 561
     8 3 561
     9 3 361
     9 5 561
    10 3 361
    10 5 561
   300020261
   3 0 6 661
   3 1 3 561
   3 2 3 461
   3 3 3 661
   3 4 4 461
   3 5 0 361
   3 5 5 861
   3 6 3 361
   3 6 5 561
   3 8 3 561
   3 9 3 361
   3 9 5 561
   310 3 361
   310 5 561
    00020261
     0 6 661
     1 3 561
     2 3 461
     3 3 661
     4 4 461
     5 0 361
     5 5 861
     6 3 361
     6 5 561
     8 3 561
     9 3 361
     9 5 561
    10 3 361
    10 5 561

Producing:

black/white vs white/black

This is so shortest program (by byte-count) wins. Bonus (-50) if the engine makes clickity-clack noises.

luser droog

Posted 2014-05-16T04:14:05.650

Reputation: 4 535

3Normally one requests clarification by posting to the sandbox. Are you trying to shut the sandbox down? – John Dvorak – 2014-05-16T05:02:43.183

5For me personally, the sandboxes are a dead-end. I'm too good at procrastinating to finish them up. Here, live, I cannot ignore the fire under my butt. – luser droog – 2014-05-16T05:04:23.627

1How does the Boolean connector work? Does it only join lines with the same sequence number? If they're mixed, is there some form of operator precedence? Do you have any test cases which rely on Boolean connectors? Why does the test case you posted not have any sequence numbers? Is the ending x coord always inclusive? – Peter Taylor – 2014-05-16T13:10:34.710

Good questions! I've tried to address them all in the update. The Boolean connectors were inspired by the MARKIV mainframe database query language. – luser droog – 2014-05-16T17:18:21.357

5

Here are some clickety clack noises. Do I get a bonus? ;-)

– Digital Trauma – 2014-05-16T18:28:56.123

Are 0, 00, 000 and 0000 the same sequence ID or are they different? – Oberon – 2014-05-17T13:15:31.380

@Oberon They are different. – luser droog – 2014-05-17T16:59:05.493

1

For sound, are you thinking something like train station flipboards? E.g. solari board at gare du nord train station in paris or Split-flap Display - DIY driver circuit. Or are you thinking more mechanical relay sounds?

– Scott Leadley – 2014-05-20T02:10:03.830

I hadn't imagined the sound very precisely. I really like the train station flipboard sound, but the simpler flipping sound would also be acceptable, as would other mechanical sounds so long as they're appropriately evocative. The motors in the novel would be steam-driven as well, so shrieks and whistles and puffs would also work. – luser droog – 2014-05-20T03:28:43.927

I mentioned this in the chatroom, but I'll repeat it here. The sequence field does seem kinda stupid, from our textfile point-of-view, but think of the cards! Think of piping this input from some kind of shellscript, positional sorting is very natural to shells... perhaps the interactive command-line is the closest modern analogue to an editable card. – luser droog – 2014-05-20T05:38:51.533

@Oberon was that clear? They should be sorted as strings rather than integers, and valid input will only contain spaces or digits 0-9 and if spaces are present, they will be only on the left of any digits present. I suppose you could interpret them as numbers if you treat space as the 'digit' -1 in a base-10 encoding, but leaving them as strings should be simpler I think. – luser droog – 2014-05-26T09:30:16.380

s.b. ^0 in a base-11 encoding^. – luser droog – 2014-08-02T08:42:50.400

Answers

3

Mathematica, 306 281 bytes

This expects the input string to be stored in variable i

ListAnimate[ArrayPlot/@FoldList[({n,y,x,X,o,a}=#2;MapAt[IntegerDigits[o,2,4][[-1-FromDigits[{#,a},2]]]&,#,{y+1,x+1;;X+1}])&,Array[0&,{20,20}],ToExpression/@MapAt["16^^"<>#&,StringTrim/@SortBy[i~StringSplit~"\n"~StringCases~RegularExpression@"^....|..(?!.?$)|.",{#[[1]]&}],{;;,5}]]]

And here with some whitespace:

ListAnimate[ArrayPlot /@ FoldList[(
     {n, y, x, X, o, a} = #2;
     MapAt[
      IntegerDigits[o, 2, 4][[-1 - FromDigits[{#, a}, 2]]] &,
      #,
      {y + 1, x + 1 ;; X + 1}
      ]
     ) &,
   Array[0 &, {20, 20}],
   ToExpression /@ 
    MapAt["16^^" <> # &, 
     StringTrim /@ 
      SortBy[i~StringSplit~"\n"~StringCases~
        RegularExpression@"^....|..(?!.?$)|.", {#[[1]] &}], {;; , 5}]
   ]]

This got pretty damn long. This challenge contained a lot of fiddly details, and especially the input parsing takes a lot of code in Mathematica (almost half of it, 137 bytes, are just parsing the input). I ended up switching the language twice before settling on Mathematica (I thought I could save on the input parsing by using Ruby, but then I realised the result needs to be animated, so I went back to Mathematica).

Martin Ender

Posted 2014-05-16T04:14:05.650

Reputation: 184 808

2

Ungolfed Postscript example

This is a "protocol-prolog"-style program, so the data immediately follows in the same source file. Animated gif files can be produced with ImageMagick's convert utility (uses ghostscript): convert clack.ps clack.gif.

%%BoundingBox: 0 0 321 321

/t { token pop exch pop } def
/min { 2 copy gt { exch } if pop } def
/max { 2 copy lt { exch } if pop } def

/m [ 20 { 20 string }repeat ] def
/draw { change {
        m {} forall 20 20 8 [ .0625 0 0 .0625 0 0 ] {} image showpage
    } if } def

%insertion sort from https://groups.google.com/d/topic/comp.lang.postscript/5nDEslzC-vg/discussion
% array greater_function insertionsort array
/insertionsort
{ 1 1 3 index length 1 sub
    { 2 index 1 index get exch % v, j
        { dup 0 eq {exit} if
            3 index 1 index 1 sub get 2 index 4 index exec
            {3 index 1 index 2 copy 1 sub get put 1 sub}
            {exit} ifelse
        } loop
        exch 3 index 3 1 roll put
    } for
    pop
} def

/process {
    x X min 1 x X max { % change? x
        m y get exch  % row-str x_i
        2 copy get  % r x r_x 
        dup         % r x r_x r_x
        0 eq { 0 }{ 1 } ifelse  % r x r_x b(x)
        2 mul a add f exch neg bitshift 1 and   % r x r_x f(x,a)
        0 eq { 0 }{ 255 } ifelse  % r x r_x c(f)
        exch 1 index % r x c(f) r_x c(f)
        ne { /change true def } if
        put
    } for
    draw
} def

{ [ {
     currentfile 15 string
         dup 2 13 getinterval exch 3 1 roll
         readline not{pop pop exit}if
    pop
    [ exch
     /b exch dup 0 1 getinterval exch
     /n exch dup 1 1 getinterval exch
     /seq exch dup 2 4 getinterval exch
     /y exch dup 6 2 getinterval t exch
     /x exch dup 8 2 getinterval t exch
     /X exch dup 10 2 getinterval dup (  ) ne { t exch }{pop 2 index exch} ifelse
     /f exch dup 12 get (16#?) dup 3 4 3 roll put t exch
     /a exch 13 get 48 sub
     /change false def
    >>
}loop ]
dup { /seq get exch /seq get exch gt } insertionsort
true exch
{ begin
    b(A)eq{
        { process } if
    }{
        b(O)eq{
            not { process } if
        }{
            pop
            process
        }ifelse
    }ifelse
    change
    end
} forall
    draw
} exec
   100 016F0
   101 016F0
   102 016F0
   103 016F0
   104 016F0
   105 016F0
   106 016F0
   107 016F0
   108 016F0
   109 016F0
   110 016F0
   111 016F0
   112 016F0
   113 016F0
   114 016F0
   115 016F0
   200020261
   2 0 6 661
   2 1 3 561
   2 2 3 461
   2 3 3 661
   2 4 4 461
   2 5 0 361
   2 5 5 861
   2 6 3 361
   2 6 5 561
   2 8 3 561
   2 9 3 361
   2 9 5 561
   210 3 361
   210 5 561
    00020261
     0 6 661
     1 3 561
     2 3 461
     3 3 661
     4 4 461
     5 0 361
     5 5 861
     6 3 361
     6 5 561
     8 3 561
     9 3 361
     9 5 561
    10 3 361
    10 5 561
   300020261
   3 0 6 661
   3 1 3 561
   3 2 3 461
   3 3 3 661
   3 4 4 461
   3 5 0 361
   3 5 5 861
   3 6 3 361
   3 6 5 561
   3 8 3 561
   3 9 3 361
   3 9 5 561
   310 3 361
   310 5 561
    00020261
     0 6 661
     1 3 561
     2 3 461
     3 3 661
     4 4 461
     5 0 361
     5 5 861
     6 3 361
     6 5 561
     8 3 561
     9 3 361
     9 5 561
    10 3 361
    10 5 561
0000 0 515F1
0000 1 11501
0000 1 115F1

luser droog

Posted 2014-05-16T04:14:05.650

Reputation: 4 535

The bounding box info was discovered by running gs -sDEVICE=bbox clack.ps. – luser droog – 2014-05-26T23:25:17.890