45
\$\begingroup\$

In public-key cryptography, a public key fingerprint is a short sequence of bytes used to identify a longer public key.

In SSH in particular they can be used to verify that a server is in fact the server I'm expecting to communicate with and I'm not targeted by a man-in-the-middle attack.

They are usually represented as a string of hexadecimal digits, so it can be rather boring and tedious to compare it with the fingerprint I would expect:

37:e4:6a:2d:48:38:1a:0a:f3:72:6d:d9:17:6b:bd:5e

To make it a little easier, OpenSSH has introduced a method to visualize fingerprints as ASCII art, that would look like the following:

+-----------------+
|                 |
|                 |
|          .      |
|     .   o       |
|o . o . S +      |
|.+ + = . B .     |
|o + + o B o E    |
| o .   + . o     |
|         .o      |
+-----------------+

With this, I could try to remember the rough shape of the ASCII art and would then (theoretically) recognize it when the fingerprint of the server changed and the image looks different.

How it works

Taken from Dirk Loss, Tobias Limmer, Alexander von Gernler. 2009. The drunken bishop: An analysis of the OpenSSH fingerprint visualization algorithm.

The grid has a width of 17 characters and a height of 9 characters. The "bishop" starts at row 4/column 8 (the center). Each position can be denoted as [x,y], i.e. [8,4] for the starting position of the bishop.

            1111111
  01234567890123456
 +-----------------+
0|                 |
1|                 |
2|                 |
3|                 |
4|        S        |
5|                 |
6|                 |
7|                 |
8|                 |
 +-----------------+

The bishop uses the fingerprint to move around. It reads it byte-wise from left to right and from the least significant bit to the most significant bit:

Fingerprint      37      :       e4      :       6a      :  ...  :       5e
Bits        00 11 01 11  :  11 10 01 00  :  01 10 10 10  :  ...  :  01 01 11 10
             |  |  |  |      |  |  |  |      |  |  |  |              |  |  |  |
Step         4  3  2  1      8  7  6  5     12 11 10  9             64 63 62 61

The bishop will move by the following plan:

Bits   Direction
-----------------
00     Up/Left
01     Up/Right
10     Down/Left
11     Down/Right

Special cases:

  • If the bishop is in a corner and would move into the corner again, he doesn't move at all. i.e: The bishop is at [0,0] and his next step would be 00. He remains at [0,0]
  • If the bishop is in a corner or at a wall and would move into one of the walls, he moves horizontally or vertically only. i.e: The bishop is at [0,5] and his next step would be 01. He cannot go left, so he just moves up, to [0,4].

Each position holds a value of how often the bishop has visited this field:

Value      | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12| 13| 14| 15| 16|
Character  |   | . | o | + | = | * | B | O | X | @ | % | & | # | / | ^ | S | E |

The values 15 (S) and 16 (E) are special in that they mark the start and end position of the bishop respectively and overwrite the real value of the respecting position.

Goal

Create a program, that takes an alphanumeric fingerprint as input and produces its ASCII art representation as shown in the examples.

Examples

Input:
16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48

Output:
+-----------------+
|        .        |
|       + .       |
|      . B .      |
|     o * +       |
|    X * S        |
|   + O o . .     |
|    .   E . o    |
|       . . o     |
|        . .      |
+-----------------+

Input:
b6:dd:b7:1f:bc:25:31:d3:12:f4:92:1c:0b:93:5f:4b

Output:
+-----------------+
|            o.o  |
|            .= E.|
|             .B.o|
|              .= |
|        S     = .|
|       . o .  .= |
|        . . . oo.|
|             . o+|
|              .o.|
+-----------------+

Input:
05:1e:1e:c1:ac:b9:d1:1c:6a:60:ce:0f:77:6c:78:47

Output:
+-----------------+
|       o=.       |
|    o  o++E      |
|   + . Ooo.      |
|    + O B..      |
|     = *S.       |
|      o          |
|                 |
|                 |
|                 |
+-----------------+

Rules

  • This is . The code in the fewest bytes wins.
  • You can not use an existing library that produces the image.
  • Use whichever language you prefer!
  • Your submission has to be a complete program
\$\endgroup\$
  • 4
    \$\begingroup\$ Can we assume that no cell will be visited more than 14 times? \$\endgroup\$ – Martin Ender Oct 5 '15 at 5:21
  • 3
    \$\begingroup\$ There are a few corner cases of minimal coverage that would result in a few fields being visited more than 14 times. 33:33:33:...:33, cc:cc:cc:...:cc would be examples for this. The fingerprint is usually a MD5 hash, so it's highly unlikely you get such a result. I haven't found any reliable sources on how to deal with these, so for now I'd say: Assume no cell will be visited more than 14 times. \$\endgroup\$ – Padarom Oct 5 '15 at 5:51

15 Answers 15

2
\$\begingroup\$

Pyth, 125 bytes

Jj*17\-"++"JVc9XXsm@"^ .o+=*BOX@%&#/"hdrS+*U9U17K.u.e@S[0b*8hk)1.b+tNyYNYsm_c4.[08jxsM^.HM16 2d2cz\:,4 8 8ieK17\E76\SjN"||")J

Try it online: Demonstration or Test-Suite

I wrote a few days ago, but didn't post it, because I wasn't really happy about it.

Explanation:

The basic idea is the following. I start with the pair (4, 8). In each move (m1,m2) I go from the (x, y) to (x-1+2*m1, y-1+2*m2). To make sure, that these coordinates don't go outside the boarders, I'll make some lists, sort them and return the middle element: (sorted(0,8,newx)[1], sorted(0,16,newy)[1]).

I keep track of all positions. To this list of positions I add a list of all possible positions, sort them and run-length-encode them. Which gives me a number for each position. With this number I can choose the coorect char, and at the end overwrite the chars of the start and end position.

| improve this answer | |
\$\endgroup\$
9
\$\begingroup\$

Dyalog APL (178)

{⎕ML←3⋄F←9 17⍴0⋄5 9{(⍺⌷F)+←1⋄×⍴⍵:(1 1⌈9 17⌊⍺-1 1-2×↑⍵)∇1↓⍵⋄(⍺⌷F)←16⋄F[5;9]←15⋄K⍪(M,' .o+=*BOX@%&#/^SE'[1+F],M←'|')⍪K←'+','+',⍨17⍴'-'}⊃,/{↓⊖4 2⍴⍉(4/2)⊤¯1+⍵⍳⍨⎕D,'abcdef'}¨⍵⊂⍨':'≠⍵}

This is a function that takes the string as its right argument, and returns a character matrix containing the ASCII art representation, e.g.:

      F←{⎕ML←3⋄F←9 17⍴0⋄5 9{(⍺⌷F)+←1⋄×⍴⍵:(1 1⌈9 17⌊⍺-1 1-2×↑⍵)∇1↓⍵⋄(⍺⌷F)←16⋄F[5;9]←15⋄K⍪(M,' .o+=*BOX@%&#/^SE'[1+F],M←'|')⍪K←'+','+',⍨17⍴'-'}⊃,/{↓⊖4 2⍴⍉(4/2)⊤¯1+⍵⍳⍨⎕D,'abcdef'}¨⍵⊂⍨':'≠⍵}


      F '16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48'
+-----------------+
|        .        |
|       + .       |
|      . B .      |
|     o * +       |
|    X * S        |
|   + O o . .     |
|    .   E . o    |
|       . . o     |
|        . .      |
+-----------------+
      F 'b6:dd:b7:1f:bc:25:31:d3:12:f4:92:1c:0b:93:5f:4b'
+-----------------+
|            o.o  |
|            .= E.|
|             .B.o|
|              .= |
|        S     = .|
|       . o .  .= |
|        . . . oo.|
|             . o+|
|              .o.|
+-----------------+

Explanation:

  • ⎕ML←3: set ⎕ML to 3. This makes more useful for splitting strings.

  • F←9 17⍴0: make a 17-by-9 matrix of zeroes. F represents how many times each position has been visited.

  • ⍵⊂⍨':'≠⍵: split on : characters.

  • {...: for each group:
    • ¯1+⍵⍳⍨⎕D,'abcdef': find the index of each character in the string '01234567890abcdef'. Subtract 1, because APL is 1-indexed by default.
    • (4/2)⊤: convert the values to their 4-bit representations (there should now be 2-by-4 matrix).
    • ↓⊖4 2⍴⍉: rotate the matrix, use the elements to fill a 2-by-4 matrix instead, mirror that matrix horizontally, and then get each line separately. This gives us the 4 2-bit values we need.
  • ⊃,/: join the resulting lists together, giving a list of 2-bit steps.
  • 5 9{...}: given the list of steps, and starting at position [9,5]:
    • (⍺⌷F)+←1: increment the current position in F.
    • ×⍴⍵:: if the list of steps is not empty:
      • ↑⍵: take the first step from the list
      • ⍺-1 1-2×: get the delta for that step, and subtract it from the current position
      • 1 1⌈9 17⌊: restrict movement to within the field
      • (...)∇1↓⍵: continue with the new position and the rest of the steps
    • If it is empty:
      • (⍺⌷F)←16: set F to 16 at the final position
      • F[5;9]←15: set F to 15 at the start position
      • ' .o+=*BOX@%&#/^SE'[1+F]: map each position to the corresponding character
      • K⍪(M,...,M←'|')⍪K←'+','+',⍨17⍴'-': wrap the result in lines
| improve this answer | |
\$\endgroup\$
8
\$\begingroup\$

Perl, 300 + 1 (-n) = 301 bytes

perl -ne 'sub b{$b=$_[0]+$_[1];$_[0]=$b<0?0:$b>$_[2]?$_[2]:$b}$v=pack"(H2)*",/\w\w/g;($x,$y)=(8,4);$a[b($y,($_&2)-1,8)*17+b($x,($_&1)*2-1,16)]++for map{vec$v,$_,2}0..63;@a[76,$y*17+$x]=(15,16);$c=" .o+=*BOX@%&#/^SE";print$d="+".("-"x17)."+\n",(map{+"|",(map{substr$c,$_,1}@a[$_*17..($_+1)*17-1]),"|\n"}0..8),$d'

This answer is disgusting, but it's also the first one for this puzzle, so it'll do for now.

-n to take a line of input on STDIN and fill $_.

# b($v, -1 or 1, max) modifies $v within 0..max
sub b{$b=$_[0]+$_[1];$_[0]=$b<0?0:$b>$_[2]?$_[2]:$b}

# turn $_ into a binary string
$v=pack"(H2)*",/\w\w/g;

# initialize cursor
($x,$y)=(8,4);

# find an element of single-dimensional buffer @a
$a[
    # y += (bitpair & 2) - 1, within 8
    b($y,($_&2)-1,8) * 17
    # x += (bitpair & 1) * 2 - 1, within 17
  + b($x,($_&1)*2-1,16)
# and increment it
]++
# for each bit pair (in the right order!)
  for map{vec$v,$_,2}0..63;

# overwrite the starting and ending positions
@a[76,$y*17+$x]=(15,16);

# ascii art lookup table
$c=" .o+=*BOX@%&#/^SE";

# output
print
  # the top row, saving it for later
  $d="+".("-"x17)."+\n",
  # each of the eight middle rows
  (map{+
    # converting each character in @a in this row as appropriate
    "|",(map{substr$c,$_,1}@a[$_*17..($_+1)*17-1]),"|\n"
  }0..8),
  # the bottom row
  $d
| improve this answer | |
\$\endgroup\$
7
\$\begingroup\$

R, 465 459 410 393 382 357 bytes

f=function(a){s=strsplit;C=matrix(as.integer(sapply(strtoi(el(s(a,":")),16),intToBits)[1:8,]),2);C[!C]=-1;n=c(17,9);R=array(0,n);w=c(9,5);for(i in 1:64){w=w+C[,i];w[w<1]=1;w[w>n]=n[w>n];x=w[1];y=w[2];R[x,y]=R[x,y]+1};R[]=el(s(" .o+=*BOX@%&#/^",""))[R+1];R[9,5]="S";R[x,y]="E";z="+-----------------+\n";cat(z);for(i in 1:9)cat("|",R[,i],"|\n",sep="");cat(z)}

With indentations and newlines:

f=function(a){
    s=strsplit
    C=matrix(as.integer(sapply(strtoi(el(s(a,":")),16),intToBits)[1:8,]),2)
    C[!C]=-1
    n=c(17,9)
    R=array(0,n)
    w=c(9,5)
    for(i in 1:64){
        w=w+C[,i]
        w[w<1]=1
        w[w>n]=n[w>n]
        x=w[1]
        y=w[2]
        R[x,y]=R[x,y]+1
    }
    R[]=el(s(" .o+=*BOX@%&#/^",""))[R+1]
    R[9,5]="S"
    R[x,y]="E"
    z="+-----------------+\n"
    cat(z)
    for(i in 1:9)cat("|",R[,i],"|\n",sep="")
    cat(z)
}

Usage:

> f("16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48")
+-----------------+
|        .        |
|       + .       |
|      . B .      |
|     o * +       |
|    X * S        |
|   + O o . .     |
|    .   E . o    |
|       . . o     |
|        . .      |
+-----------------+
> f("37:e4:6a:2d:48:38:1a:0a:f3:72:6d:d9:17:6b:bd:5e")
+-----------------+
|                 |
|                 |
|          .      |
|     .   o       |
|o . o . S +      |
|.+ + = . B .     |
|o + + o B o E    |
| o .   + . o     |
|         .o      |
+-----------------+
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Cant you golf the 'matrix' function by defining it once as 'm'? Same for 'function'? \$\endgroup\$ – CousinCocaine Oct 5 '15 at 10:18
  • \$\begingroup\$ I think you do not need 'sep=' in the 'cat' function \$\endgroup\$ – CousinCocaine Oct 5 '15 at 10:20
  • \$\begingroup\$ the default for sep is a space, so i need to override it, but indeed I could alias matrix. \$\endgroup\$ – plannapus Oct 5 '15 at 12:00
  • \$\begingroup\$ Additionnaly, as far as I know, you can't alias function. \$\endgroup\$ – plannapus Oct 5 '15 at 12:04
5
\$\begingroup\$

Octave, 277

d=reshape(rot90(dec2bin(hex2dec(strsplit(input('','s'),':'))))>'0',2,[])*2-1;p=[9;5];for m=1:64 p=[max(min(p(:,1)+d(:,m),[17;9]),1) p];end;A=' .o+=*BOX@%&#/^SE';F=A(sparse(p(2,:),p(1,:),1,9,17)+1);F(5,9)='S';F(p(2,1),p(1,1))='E';[a='+-----------------+';b=['|||||||||']' F b;a]

Explanation:

%// convert the input to binary and rearrange it to be
%//   an array of vectors: [x_displacement; y_displacement]
d=reshape(rot90(dec2bin(hex2dec(strsplit(input('','s'),':'))))>'0',2,[])*2-1;

%// start position array with vector for the start position
p=[9;5];
%// for each move, add displacement, clamping to valid values
for m=1:64 p=[max(min(p(:,1)+d(:,m),[17;9]),1) p];end;

%// alphabet for our fingerprint
A=' .o+=*BOX@%&#/^SE';

%// create a sparse matrix, accumulating values for duplicate
%// positions, and replace counts with symbols
F=A(sparse(p(2,:),p(1,:),1,9,17)+1);

%// correct the start and end symbols and construct the final output
F(5,9)='S';F(p(2,1),p(1,1))='E';
[a='+-----------------+';b=['|||||||||']' F b;a]

Sample run:

>> bish
b6:dd:b7:1f:bc:25:31:d3:12:f4:92:1c:0b:93:5f:4b
ans =

+-----------------+
|            o.o  |
|            .= E.|
|             .B.o|
|              .= |
|        S     = .|
|       . o .  .= |
|        . . . oo.|
|             . o+|
|              .o.|
+-----------------+
| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$

Pyth, 145 143 140

Jm*17]09A,4K8FYcz\:V4AmhtS[0^2d+@,HGdtyv@+_.BiY16*7\0+-4dyN),3 4 X@JGHh@@JGH; X@J4K15 X@JGH16
=Y++\+*17\-\+VJ++\|s@L" .o+=*BOX@%&#/^SE"N\|)Y

Try it online.

Pyth isn't really good at challenges with iteration. I'm expecting CJam to beat it easily.

| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$

JavaScript (ES6) 249 208

Edit Added missing border

Test running the snippet below in any EcmaScript 6 compliant browser

B=f=>f.replace(/\w+/g,b=>{for(b=`0x1${b}`;b-1;b>>=2)++g[p=(q=(p=(q=p+~-(b&2)*18)>0&q<162?q:p)+b%2*2-1)%18?q:p]},p=81,z=`+${'-'.repeat(17)}+`,g=Array(162).fill(0))&&g.map((v,q)=>q?q-81?q-p?q%18?' .o+=*BOX@%&#/^'[v]:`|
|`:'E':'S':z+`
|`).join``+`|
`+z

// TEST
console.log=x=>O.innerHTML+=x+'\n'

;['37:e4:6a:2d:48:38:1a:0a:f3:72:6d:d9:17:6b:bd:5e'
,'16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48'
,'b6:dd:b7:1f:bc:25:31:d3:12:f4:92:1c:0b:93:5f:4b'
,'05:1e:1e:c1:ac:b9:d1:1c:6a:60:ce:0f:77:6c:78:47'  
].forEach(t=>console.log(t+'\n'+B(t)+'\n'))


// Less golfed

BB=f=>(
  p = 81,
  g = Array(162).fill(0),
  f.replace(/\w+/g, b => {
    for(b = `0x1${b}`;b != 1; b >>= 2)
      q = p+~-(b&2)*18,
      p = q>0&q<162?q:p,
      p = (q=p+b%2*2-1)%18?q:p,
      ++g[p]
  }),
  g.map((v,q) => q-81?q-p?q%18?' .o+=*BOX@%&#/^'[v]:'\n':'E':'S')
  .join``
)
pre { font-family: menlo,consolas; font-size:13px }
<pre id=O></pre>

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Your golfed code itself should be capable to print the borders too. Currently only your test case prints the upper and lower borders in the forEach, the vertical borders are still missing. \$\endgroup\$ – Padarom Oct 9 '15 at 14:36
  • \$\begingroup\$ @Padarom I misunderstood your "be reasonable" comment, thinking that the border was not requested \$\endgroup\$ – edc65 Oct 9 '15 at 14:49
  • \$\begingroup\$ I meant be reasonable as to which method of input and output you use. My apologies if that was misleading, I'll edit it out \$\endgroup\$ – Padarom Oct 9 '15 at 15:08
3
\$\begingroup\$

Python, 381328

-51 thanks to @JonathanFrech

def h(f):
 s=[f'{int(o,16)>>s&3:02b}'for o in f.split(':')for s in(0,2,4,6)];r=[0]*153;p=76;w=17
 for d in s:r[p]+=1;p+=(-(p%w!=0),p%w!=16)[int(d[1])]+(-w*(p//w!=0),w*(p//w!=8))[int(d[0])]
 r[76]=15;r[p]=16;b='+'+'-'*w+'+';print(b);i=0
 while i<153:print(f"|{''.join(' .o+=*BOX@%&#/^SE'[c]for c in r[i:i+w])}|");i+=w
 print(b)

Slightly ungolfed for the sake of explanation:

def h_(f):
 #Alias 17 because it gets used enough times for this to save bytes
 w=17

 #Input parsing
 s=[f'{int(o,16)>>s&3:02b}'for o in f.split(':')for s in(0,2,4,6)]

 #Room setup
 r=[0]*153
 p=76

 #Apply movements
 for d in s:
  r[p]+=1
  p+=(-(p%w!=0),p%w!=16)[int(d[1])]+(-w*(p//w!=0),w*(p//w!=8))[int(d[0])]
 r[76]=15 #Set start position
 r[p]=16 #Set end position

 #Display result
 b='+'+'-'*w+'+'
 print(b)
 i=0
 while i<153:
  print(f"|{''.join(' .o+=*BOX@%&#/^SE'[c]for c in r[i:i+w])}|")
  i+=w
 print(b)

This mess of a line:

r[p]+=1;p+=(-(p%w!=0),p%w!=16)[int(d[1])]+(-w*(p//w!=0),w*(p//w!=8))[int(d[0])]

​ is functionally equivalent to this:

if int(d[0]): #Down, Y+
  if p//17!=8:
    p+=17
else: #Up, Y-
  if p//17!=0:
    p-=17
​
if int(d[1]): #Right, X+
  if p%17!=16:
    p+=1
else: #Left, X-
  if p%17!=0:
    p-=1

but nests all of the conditionals in this style of golf shortcut: (false_value,true_value)[condition] Hopefully the rest is fairly self-explanatory

Tests

h('16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48')
+-----------------+
|        .        |
|       + .       |
|      . B .      |
|     o * +       |
|    X * S        |
|   + O o . .     |
|    .   E . o    |
|       . . o     |
|        . .      |
+-----------------+

h("b6:dd:b7:1f:bc:25:31:d3:12:f4:92:1c:0b:93:5f:4b")
+-----------------+
|            o.o  |
|            .= E.|
|             .B.o|
|              .= |
|        S     = .|
|       . o .  .= |
|        . . . oo.|
|             . o+|
|              .o.|
+-----------------+

h("05:1e:1e:c1:ac:b9:d1:1c:6a:60:ce:0f:77:6c:78:47")
+-----------------+
|       o=.       |
|    o  o++E      |
|   + . Ooo.      |
|    + O B..      |
|     = *S.       |
|      o          |
|                 |
|                 |
|                 |
+-----------------+
```
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Hello and welcome to PPCG. You can golf your code by using one-letter variable names and putting a single-line-for-loop into one line. (1,0)[p%17==16] is +(p%17!=16), or possibly even p%17!=16. \$\endgroup\$ – Jonathan Frech Feb 21 '19 at 19:02
  • \$\begingroup\$ Furthermore, there is a superfluous space in ] for. \$\endgroup\$ – Jonathan Frech Feb 21 '19 at 20:29
  • \$\begingroup\$ I think fp should be f. \$\endgroup\$ – Jonathan Frech Feb 21 '19 at 20:30
  • \$\begingroup\$ 330 bytes. \$\endgroup\$ – Jonathan Frech Feb 21 '19 at 20:34
  • 2
    \$\begingroup\$ Why did I use ~16? A bit of obfuscation can never hurt your golf! \$\endgroup\$ – Jonathan Frech Feb 21 '19 at 23:43
2
\$\begingroup\$

Ruby 288

->k{w=17
r=[z=?++?-*w+?+]
(0...w*9).each_slice(w).map{|o|r<<?|+o.map{|x|c=76
q=0
k.split(?:).flat_map{|b|(0..7).map{|i|b.to_i(16)[i]}}.each_slice(2){|h,v|v<1?(c>w&&c-=w):c<w*8&&c+=w
c+=h<1?c%w>0?-1:0:c%w<16?1:0
c==x&&q+=1}
x==76?'S':c==x ?'E':' .o+=*BOX@%&#/^'[q]}.join+?|}
(r+[z]).join'
'}

Try it online: http://ideone.com/QOHAnM

The readable version (the one I started golfing from) is here: http://ideone.com/XR64km

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

C 361

Thanks to @ceilingcat for some very nice pieces of golfing - now even shorter

#define H h[i]|=*++p-48-*p/59*39
#define L puts("+-----------------+")
#define F(m,s)for(m=0;m<s;m++)
h[16],m[17][9],i,j,n,x=8,y=4;main(w,v)char**v;{for(char*p=v[1]-1;i<16;p++,i++)H<<4,H;F(j,16)F(n,4)x+=h[j]>>n*2&1?x!=16:-!!x,y+=h[j]>>n*2&2?y!=8:-!!y,m[x][y]++;m[8][4]=15;m[x][y]=L-4;F(i,9){printf("|");F(j,17)printf(L" .o+=*BOX@%&#/^SE"+m[j][i]);puts("|");}L;}

Try it online!

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ @ceilingcat I did the ++p separately because it is UB to do it in an expression when there is more than one p in the expression - however, if it works on TIO I think it is allowed. Thanks! \$\endgroup\$ – Jerry Jeremiah Feb 12 at 4:43
  • \$\begingroup\$ @ceilingcat Nice find, thank you \$\endgroup\$ – Jerry Jeremiah Mar 26 at 9:24
2
\$\begingroup\$

C (gcc), 333 326 314 292 288 bytes

f[17][9],i,x=8,y=4;main(n){for(;i++%4||read(0,&n,3)*sscanf(&n,"%x",&n);n/=4)f[x+=n%2?x<16:-!!x][y+=n&2?y<8:-!!y]+=f[x][y]<14;f[8][4]=15;for(f[x][y]=puts(n="+-----------------+")-4;++i<75;puts("|"))for(x=!printf("|");x<17;)putchar(" .o+=*BOX@%&#/^SE"[y=fmin(f[x++][i-66],16)]);puts(n);}

Try it online!

Golfed from openssh source code key_fingerprint_randomart().

Edit: Now a complete program.

Less golfed:

/* initialize field */
f[17][9],i,x=8,y=4;
main(n){
  /* process raw key */
  /* each byte conveys four 2-bit move commands */
  for(;i%4||read(0,&n,3)+sscanf(&n,"%x",&n);n/=4)
    /* evaluate 2 bit, rest is shifted later */
    /* assure we are still in bounds; augment the field */
    f[x+=n%2?x<16:-!!x][y+=n&2?y<8:-!!y]+=f[x][y]<14;

  /* mark starting point and end point; output upper border */
  f[8][4]=15;
  f[x][y]=puts(n="+-----------------+")-4

  /* output content */
  for(;++i<75;puts("|"))
    for(x=!printf("|");x<17;)
      /*
       * Chars to be used after each other every time the worm
       * intersects with itself.
       */
      putchar(" .o+=*BOX@%&#/^SE"[y=fmin(f[x++][i-66],16)]);

  /* output lower border */
  puts(n);
}
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Your TIO link says it is 311 bytes - its amazing that you were able to make a full program smaller than the function was... \$\endgroup\$ – Jerry Jeremiah Feb 13 at 2:18
1
\$\begingroup\$

Burlesque, 200 bytes

':;;m{b6b28'0P[2cob2^p}m{{-1 1}Jcpj!!}{4 8}+]qr{?+q0cy{>.}Z]{8 16}cy{<.}Z]}pal_g_jf:u[j" .o+=*BOX@%&#/^"XXbcjq!!Z]z[{(D!)_+p^}m[' 17.*9.*)XXje!j'SD!j'ED!"|\n|"IC'|+]'|[+\['+'-17.*_+<-"+\n".+J<-#rCL\[Q

Try it online!

':;;                # Split at colons
m{                  # Map over these
 b6b2               # Read as hex, then to binary
 8'0P[              # Pad to 8 bit
 2co                # Chunks of 2
 b2                 # Read as binary
 ^p}                # Push each
m{                  # Map over these
 {-1 1}Jcp          # Construct direction array {{- -}{- +}{+ -}{+ +}}
 j!!                # Select the block indexed by binary val
}
{4 8}+]             # Push starting pos
qr{                 # Boxed reduce
 ?+                 # Add together
 q0cy{>.}Z]         # If < 0 -> 0
 {8 16}cy{<.}Z]     # If > box -> boxwidth
pa                  # Apply on inits
l_g_j               # Take start and end for later
f:                  # Calculate frequency of everything but head and tail
u[j                 # Unzip to {pos} {counts}
" .o+=*BOX@%&#/^"XXbcj # Magic string
q!!Z]               # Select element based on count
z[                  # Rezip to {pos icon}
{(D!)_+p^}m[        # Add D! (set at pos) to the each
' 17.*9.*)XX        # Create 17x9 array of spaces
je!                 # Evaluate block of ({pos icon D!}, ...)
j'SD!j'ED!          # Set start and end to S & E
"|\n|"IC            # Intercalate surrounding v-bars 
'|+]'|[+            # Add first and last
\[                  # Concatenate
'+'-17.*_+<-"+\n".+ # Create top-bar
J<-                 # Duplicate and reverse (putting newline at the front
#r                  # Rotate stack
CL                  # Collect stack as list
\[                  # Concatenate
Q                   # Pretty print
| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Perl 5, 283 276 266 254 + 1 (-p) = 255 bytes

$x=8;$y=4;for$n(map{hex}split(/:/)){$n&2?$y<8&&$y++:$y&&$y--,$n&1?$x<16&&$x++:$x&&$x--,$a[$x][$y]++,$n>>=2 for 1..4}$a[8][4]=15;$a[$x][$y]=16;map{$y=$_;$s.="|";$s.=substr' .o+=*BOX@%&#/^SE',$a[$_][$y],1 for 0..16;$s.="|
"}0..8;$l='-'x17;$_="+$l+
$s+$l+
"

Uses -p to take a line of input into $_, and to print the (modified) value of $_ at the end.

Try it online!

Ungolfed:

# Implicit from running with perl -p:
# read line from standard input
#while($_ = <STDIN>) {

# Initialise starting coordinates
$x=8;
$y=4;

# Split input line at colons, convert each element (2-digit hex value) to integer,
iterate over the list of integers
for $n (map { hex($_) } split(/:/))
{
    # for each integer, iterate 4 times, looking at 2 of the 8 bits each time
    for(1..4) 
    {
        # if second-last bit is set
        if($n & 2) {
            # move down, unless we are already at the bottom
            $y++ if($y < 8);
        }
        else {
            # move up, unless we are already at the top
            $y-- if($y);
        }

        # if last bit is set
        if($n & 1) {
            # move right, unless already at the right edge
            $x++ if($x<16);
        }
        else {
            # move left, unless already at the left edge
            $x-- if($x);
        }

        # Increment the counter for this position
        $a[$x][$y]++;

        # Shift number right two bits for next iteration
        $n = $n >> 2
    }
}

# Set fixed values (15 and 16) for start and end positions respectively
$a[8][4] = 15;
$a[$x][$y] = 16;


# Iterate over the rows of the grid, building a string containing the output rows
for(0..8)
{
    # save the y-coordinate for use in inner loop below
    $y = $_;

    # append the line start
    $s .= "|";

    # iterate over the columns of the row
    for(0..16)
    {
        # get the value for this position
        $v = $a[$_][$y];

        # map the value to the appropriate character
        $s .= substr(' .o+=*BOX@%&#/^SE', $v, 1)
    }

    # append the line end
    $s .= "|\n"
}

# build the hyphen lines for the top and bottom rows
$l = '-' x 17;

# build the full output string and assign to $_
$_ = "+$l+\n$s+$l+\n"

# Implicit from running under perl -p
#print $_;
#}
| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Java 11, 460 453 452 449 bytes

interface M{static void main(String[]a){var m=new char[9][17];Integer x=4,y=8,i=0,s;for(var p:a[0].split(":"))for(p=x.toString(x.parseInt(p,16),2),i=4;i-->0;m[x-=s<2?x>0?1:0:x/8-1][y-=s%2>0?y/16-1:y>0?1:0]++)s=x.decode(("0".repeat(8-p.length())+p).split("(?<=\\G..)")[i]);String t="+"+"-".repeat(m[x][y]=16)+"-+\n",r=t+"|";for(m[4][8]=15;++i<153;r+=y>15?"|\n"+(x<8?"|":""):"")r+=" .o+=*BOX@%&#/^SE".charAt(m[x=i/17][y=i%17]);System.out.print(r+t);}}

-11 bytes thanks to @ceilingcat.

Try it online.

Explanation:

interface M{              // Class
  static void main(String[]a){
                          //  Mandatory main method
    var m=new char[9][17];//   Create a character-matrix of size 9 by 17
    Integer x=4,y=8,      //   Current position, starting at 4,8
            i=0,s;        //   Temp integers
    for(var p:a[0].split(":"))
                          //   Loop over the argument-String, split by ":"
      for(p=x.toString(x.parseInt(p,16),
                          //    Convert the current part from hexadecimal String to integer
                       2),//    And then from integer to binary String
          i=4;i-->0       //    Inner loop `i` in the range (4,0]:
          ;               //      After every iteration:
           m[x-=          //       Update the `x` position:
                s<2?      //        If `s` is 0 or 1:
                 x>0?     //         If we're not out of bounds yet at the top:
                  1       //          Go up
                 :        //         Else:
                  0       //          Stay at the top border
                :         //        Else (s is 10 or 11):
                 x/8      //         If we're not out of bounds yet at the bottom:
                  -1]     //          Go down
                          //         Else:
                          //          Stay at the bottom border
            [y-=          //       Update the `y` position:
                s%2>0?    //        If `s` is odd (1 or 11):
                 y/16     //         If we're not out of bounds yet at the right side:
                  -1      //          Go right
                          //         Else:
                          //          Stay at the right border
                :         //        Else (s is 0 or 10):
                 y>0?     //         If we're not out of bounds yet at the left side:
                  1       //          Go left
                 :        //         Else:
                  0]      //          Stay at the left border
                    ++)   //       And increase the value at x,y cell by 1
        s=                //     Set `s` to:
          x.decode(("0".repeat(8-p.length())+p)
                          //      Left-pad the binary-String `p` with 0s up to length 8
                   .split("(?<=\\G..)")
                          //      Split it into parts of size 2
                   [i]    //      Get the `i`'th part
                      );  //      And convert it from binary-String to integer
                          //   After the nested loops are done:
    String t="+"+"-".repeat(m[x][y]=16
                          //   Set the value at position x,y to 16
                      )+"-+\n",
                          //   Push a temp String for the top/bottom borders
           r=t+"|";       //   Result-String, starting with the top border + one "|"
    for(m[4][8]=15;       //   Set the value at position 4,8 to 15
        ;++i<153          //   Loop over all cells:
        ;                 //     After every iteration:
         r+=              //      Append the following to the result-String:
            y>15?         //       If we're done with a row:
             "|\n"        //        Append "|" and a newline
             +(x<8?       //        And unless it's the last row:
                "|"       //         Also append a "|"
               :          //        If it is the last row:
                "")       //         Append nothing
            :             //       And if we're not done with the row yet:
             "")          //        Append nothing
      r+=                 //    Append the following to the result-String:
         " .o+=*BOX@%&#/^SE".charAt(m[x=i/17][y=i%17]);
                          //     The `i`'th character of the String " .o+=*BOX@%&#/^SE"
                          //     where `i` is the current value of cell x,y
    System.out.print(r    //   And finally output this result-String,
      +t);}}              //   including its bottom border
| improve this answer | |
\$\endgroup\$
0
\$\begingroup\$

Rust - 509 bytes

fn b(s:&str)->String{let(mut v,mut b)=([[0;11];20],[9,5]);v[19]=[19;11];for i in 0..16{let mut c=usize::from_str_radix(&s[i*3..i*3+2],16).unwrap();for k in 0..4{for j in 0..2{v[j*18][i%9+1]=18;v[i+k][j*10]=[17,3][(i+k+17)%18/17];b[j]=match(if c&(j+1)==j+1{b[j]+1}else{b[j]-1},j,){(0,_)=>1,(18,0)=>17,(10,1)=>9,x@_=>x.0 as usize,}}v[b[0]][b[1]]+=1;c>>=2;}}v[9][5]=15;v[b[0]][b[1]]=16;(0..220).fold("\n".to_string(),|s,i|{format!("{}{}",s," .o+=*BOX@%&#/^SE-|\n".chars().nth(v[i%20][i/20] as usize).unwrap())})}

Large but... almost close to C. As usual there are many bytes used up due to the way Rust does not automagically cast types into each other. But there is also probably room for improvement.... could probably use some ideas from other solutions.

ungolfed version is on the Rust Playground online

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.