18
\$\begingroup\$

The beautiful pattern drawer

Good morning PPCG !

The other day, when I was trying to help someone on Stack Overflow, a part of his problem gave me an idea for this challenge.

First of all, check the following shape :

enter image description here

Where all black numbers are the index of the points in the shape and all dark-blue numbers are the index of the links between the points.

Now, given an hexadecimal number for 0x00000 to 0xFFFFF, you need to draw a shape in the console using only character space and "■" (using the character "o" is okay too).

Here are some examples where hexadecimal number is input and shape is output :

0xE0C25 :
■ ■ ■ ■ ■ ■ ■ ■ ■ 
■               ■ 
■               ■ 
■               ■ 
■ ■ ■ ■ ■       ■ 
        ■       ■ 
        ■       ■ 
        ■       ■ 
        ■ ■ ■ ■ ■
0xC1043 :
■ ■ ■ ■ ■ ■ ■ ■ ■ 
              ■   
            ■     
          ■       
        ■         
      ■           
    ■             
  ■               
■ ■ ■ ■ ■ ■ ■ ■ ■ 
0xE4F27 :
■ ■ ■ ■ ■ ■ ■ ■ ■ 
■       ■       ■ 
■       ■       ■ 
■       ■       ■ 
■ ■ ■ ■ ■ ■ ■ ■ ■ 
■       ■       ■ 
■       ■       ■ 
■       ■       ■ 
■ ■ ■ ■ ■ ■ ■ ■ ■ 
0xF1957 :
■ ■ ■ ■ ■ ■ ■ ■ ■ 
■ ■           ■ ■ 
■   ■       ■   ■ 
■     ■   ■     ■ 
■       ■       ■ 
■     ■   ■     ■ 
■   ■       ■   ■ 
■ ■           ■ ■ 
■ ■ ■ ■ ■ ■ ■ ■ ■ 
0xD0C67 :
■ ■ ■ ■ ■ ■ ■ ■ ■ 
  ■             ■ 
    ■           ■ 
      ■         ■ 
■ ■ ■ ■ ■       ■ 
      ■ ■       ■ 
    ■   ■       ■ 
  ■     ■       ■ 
■ ■ ■ ■ ■ ■ ■ ■ ■ 
0x95E30 :
■ ■ ■ ■ ■       ■ 
  ■     ■     ■ ■ 
    ■   ■   ■   ■ 
      ■ ■ ■     ■ 
■ ■ ■ ■ ■ ■ ■ ■ ■ 
        ■ ■       
        ■   ■     
        ■     ■   
        ■       ■ 
0x95622 :
■ ■ ■ ■ ■       ■ 
  ■     ■     ■   
    ■   ■   ■     
      ■ ■ ■       
■ ■ ■ ■ ■ ■ ■ ■ ■ 
        ■         
        ■         
        ■         
■ ■ ■ ■ ■         
0xC5463 : 
■ ■ ■ ■ ■ ■ ■ ■ ■ 
        ■     ■   
        ■   ■     
        ■ ■       
■ ■ ■ ■ ■         
      ■ ■         
    ■   ■         
  ■     ■         
■ ■ ■ ■ ■ ■ ■ ■ ■ 
0xE5975 :
■ ■ ■ ■ ■ ■ ■ ■ ■ 
■       ■     ■ ■ 
■       ■   ■   ■ 
■       ■ ■     ■ 
■       ■       ■ 
■     ■ ■ ■     ■ 
■   ■   ■   ■   ■ 
■ ■     ■     ■ ■ 
■       ■ ■ ■ ■ ■ 
0xB5E75 :
■ ■ ■ ■ ■       ■ 
■ ■     ■     ■ ■ 
■   ■   ■   ■   ■ 
■     ■ ■ ■     ■ 
■ ■ ■ ■ ■ ■ ■ ■ ■ 
      ■ ■ ■     ■ 
    ■   ■   ■   ■ 
  ■     ■     ■ ■ 
■       ■ ■ ■ ■ ■ 
0xF4C75 :
■ ■ ■ ■ ■ ■ ■ ■ ■ 
■ ■     ■       ■ 
■   ■   ■       ■ 
■     ■ ■       ■ 
■ ■ ■ ■ ■       ■ 
      ■ ■ ■     ■ 
    ■   ■   ■   ■ 
  ■     ■     ■ ■ 
■       ■ ■ ■ ■ ■
0xF5D75 :
■ ■ ■ ■ ■ ■ ■ ■ ■ 
■ ■     ■     ■ ■ 
■   ■   ■   ■   ■ 
■     ■ ■ ■     ■ 
■ ■ ■ ■ ■       ■ 
■     ■ ■ ■     ■ 
■   ■   ■   ■   ■ 
■ ■     ■     ■ ■ 
■       ■ ■ ■ ■ ■ 

Here are some explanation about how it works :

0xFFFFF(16) = 1111 1111 1111 1111 1111(2)

You here have 20 bits, each bit says whether a link exists or not.

Index of the Most Significant Bit (MSB) is 0 (picture reference) or the Less Significant Bit (LSB) is 19 (picture reference again).

Here's how it works for the first shape given as example :

0xE0C25(16) = 1110 0000 1100 0010 0101(2)

Meaning you'll have the following existing links : 0,1,2,8,9,14,17,19.

If you highlight the lines on the reference picture with those numbers, it will give you this shape :

■ ■ ■ ■ ■ ■ ■ ■ ■ 
■               ■ 
■               ■ 
■               ■ 
■ ■ ■ ■ ■       ■ 
        ■       ■ 
        ■       ■ 
        ■       ■ 
        ■ ■ ■ ■ ■

Here is a simple and ungolfed Python implementation if you need more help :

patterns = [
  0xE0C25, 0xC1043, 0xE4F27, 0xF1957, 
  0xD0C67, 0x95E30, 0x95622, 0xC5463, 
  0xE5975, 0xB5E75, 0xF4C75, 0xF5D75
]

def printIfTrue(condition, text = "■ "):
  if condition:
    print(text, end="")
  else:
    print(" "*len(text), end="")

def orOnList(cube, indexes):
  return (sum([cube[i] for i in indexes]) > 0)

def printPattern(pattern):
  cube = [True if n == "1" else False for n in str(bin(pattern))[2::]]
  for y in range(9):
    if y == 0: printIfTrue(orOnList(cube, [0, 2, 3]))
    if y == 4: printIfTrue(orOnList(cube, [2, 4, 9, 11, 12]))
    if y == 8: printIfTrue(orOnList(cube, [11, 13, 18]))
    if y in [0, 4, 8]:
      printIfTrue(cube[int((y / 4) + (y * 2))], "■ ■ ■ ")
      if y == 0: printIfTrue(orOnList(cube, [0, 1, 4, 5, 6]))
      if y == 4: printIfTrue(orOnList(cube, [3, 5, 7, 9, 10, 13, 14, 15]))
      if y == 8: printIfTrue(orOnList(cube, [12, 14, 16, 18, 19]))
      printIfTrue(cube[int((y / 4) + (y * 2)) + 1], "■ ■ ■ ")
    elif y in [1, 5]:
      for i in range(7):
        if i in [2, 5]:
          print(" ", end=" ")
        printIfTrue(cube[y * 2 + (1 - (y % 5)) + i])
    elif y in [2, 6]:
      for i in range(5):
        if i in [1, 2, 3, 4]:
          print(" ", end=" ")
        if i in [1, 3]:
          if i == 1 and y == 2:
            printIfTrue(orOnList(cube, [3, 4]))
          elif i == 3 and y == 2:
            printIfTrue(orOnList(cube, [6, 7]))
          if i == 1 and y == 6:
            printIfTrue(orOnList(cube, [12, 13]))
          elif i == 3 and y == 6:
            printIfTrue(orOnList(cube, [15, 16]))
        else:
          printIfTrue(cube[(y * 2 - (1 if y == 6 else 2)) + i + int(i / 4 * 2)])
    elif y in [3, 7]:
      for i in range(7):
        if i in [2, 5]:
          print("  ", end="")
        ri, swap = (y * 2 - 2) + (1 - (y % 5)) + i, [[3, 6, 12, 15], [4, 7, 13, 16]]
        if ri in swap[0]: ri = swap[1][swap[0].index(ri)]
        elif ri in swap[1]: ri = swap[0][swap[1].index(ri)]
        printIfTrue(cube[ri])
    if y == 0: printIfTrue(orOnList(cube, [1, 7, 8]))
    if y == 4: printIfTrue(orOnList(cube, [6, 8, 10, 16, 17]))
    if y == 8: printIfTrue(orOnList(cube, [15, 17, 19]))
    print()

for pattern in patterns:
  printPattern(pattern)

Of course it's not perfect and it's pretty long for what it should do, and that's the exact reason why you're here !

Making this program ridiculously short :)

This is code-golf, so shortest answer wins !

\$\endgroup\$
  • \$\begingroup\$ Can we print a single trailing space on lines? Your examples contain them. \$\endgroup\$ – orlp Jan 27 '17 at 11:48
  • \$\begingroup\$ Yup :) It's allowed \$\endgroup\$ – Sygmei Jan 27 '17 at 12:32
  • 4
    \$\begingroup\$ Is graphical output allowed? \$\endgroup\$ – 12Me21 Jan 27 '17 at 16:11
  • 1
    \$\begingroup\$ Do you require hexadecimal input or is decimal ok? \$\endgroup\$ – Titus Jan 27 '17 at 19:47
  • 1
    \$\begingroup\$ Maybe all the code golf is just getting to me, but that code is painful to read… \$\endgroup\$ – Lynn Jan 27 '17 at 23:06
8
\$\begingroup\$

JavaScript (ES6), 202 188 187 bytes

let f =

n=>`0${x=',16,-54,21,-26,21,21,-26,21,166'}${x},16`.split`,`.map((d,i)=>(k-=d,n)>>i&1&&[..."ooooo"].map(c=>g[p-=(k&3||9)^8]=c,p=k>>2),g=[...(' '.repeat(9)+`
`).repeat(9)],k=356)&&g.join``

console.log(f(0xE0C25))
console.log(f(0xC1043))
console.log(f(0xE4F27))
console.log(f(0xF1957))

How it works

n =>                                                 // given 'n':
  `0${x = ',16,-54,21,-26,21,21,-26,21,166'}${x},16` // build the list of delta values
  .split`,`.map((d, i) =>                            // split the list and iterate
    (k -= d, n) >> i & 1 &&                          // update 'k', test the i-th bit of 'n'
    [..."ooooo"].map(c =>                            // if the bit is set, iterate 5 times:
      g[                                             // 
        p -= (k & 3 || 9) ^ 8                        // compute the direction and update 'p'
      ] = c,                                         // write a 'o' at this position
      p = k >> 2                                     // initial value of 'p'
    ),                                               //
    g = [...(' '.repeat(9) + `\n`).repeat(9)],       // initialization of the 'g' array
    k = 356                                          // initial value of 'k'
  )                                                  //
  && g.join``                                        // yield the final string

We work on a grid g of 9 rows of 10 characters. The grid is initially filled with spaces, with a LineFeed every 10th character.

Each segment is defined by a starting position and a direction.

Directions are encoded as follow:

ID | Dir.| Offset
---|-----|-------
 0 |  W  |  -1        Offset encoding formula:
 1 | NE  |  -9        -((ID || 9) ^ 8)
 2 |  N  |  -10
 3 | NW  |  -11

Each segment is encoded as an integer:

  • the direction is stored in bits #0 and #1
  • the starting position is stored in bits #2 to #8

For instance, segment #3 starts at position 55 and uses the 3rd direction. Therefore, it's encoded as (55 << 2) | 3 == 223.

Below is the resulting list of integers, from segment #19 to segment #0:

356,340,394,373,399,378,357,383,362,196,180,234,213,239,218,197,223,202,36,20

Once delta-encoded, starting at 356, it becomes:

0,16,-54,21,-26,21,21,-26,21,166,16,-54,21,-26,21,21,-26,21,166,16

Which is finally encoded as:

`0${x=',16,-54,21,-26,21,21,-26,21,166'}${x},16`
\$\endgroup\$
  • \$\begingroup\$ Oops... Forgot the spaces in between. Fixing that. \$\endgroup\$ – Arnauld Jan 27 '17 at 16:52
5
\$\begingroup\$

Python 3, 289 bytes

def f(n):
 for r in range(9):print(*(" o"[any(n&1<<ord(c)-97for c in"trq|t|t|t|tspon|s|s|s|sml|r|q||p|o|n||m|l|r||qp||o||nm||l|r|p||q|o|m||n|l|rpkih|k|k|k|qomkjgfe|j|j|j|nljdc|i|h||g|f|e||d|c|i||hg||f||ed||c|i|g||h|f|d||e|c|igb|b|b|b|hfdba|a|a|a|eca".split("|")[r*9+c])]for c in range(9)))

Nothing smart, just hardcoding.

\$\endgroup\$
  • \$\begingroup\$ Couldn't "trq|t...a|eca".split("|") become "tqr t...a eca".split()? \$\endgroup\$ – Loovjo Jan 27 '17 at 12:28
  • \$\begingroup\$ @Loovjo Nope, .split() destroys ||. \$\endgroup\$ – orlp Jan 27 '17 at 12:29
3
\$\begingroup\$

Ruby, 116 bytes

->n{s=[' '*17]*9*$/
20.times{|i|j=i%9
n>>19-i&1>0&&5.times{|k|s[i/9*72+(j>1?~-j/3*8+k*18:j*16)+k*(2--j%3*2)]=?O}}
s}

This relies on a couple of patterns I observed. Firstly, the pattern repeats every 9 lines. Secondly, if the start points of the horizontal lines are chosen appropriately, the x directions cycle continuously through right, left, straight.

Ungolfed in test program

f=->n{
   s=[' '*17]*9*$/                    #Setup a string of 9 newline separated lines of 17 spaces.
   20.times{|i|                       #For each of the 20 bits..
     j=i%9                            #The pattern repeats every 9 bits.
     n>>19-i&1>0&&                    #If the relevant bit is set,
     5.times{|k|                      #draw each of the 5 points on the relevant line.
       s[i/9*72+                      #There are 9 lines starting on each row. Row y=0 starts at 0 in the string, row y=1 at 72, etc.
       (j>1?~-j/3*8+k*18:j*16)+       #~-j=j-1. For j=2..8, the starting x coordinates are (0,0,1,1,1,2,2)*8. For j=0 and 1 starting x coordinates are 0 and 16. 
       k*(2--j%3*2)                   #From the starting points, draw the lines right,left,straight. Down movement if applicable is given by conditional k*18 above.
       ]=?O                           #Having described the correct index to modify, overwrite it with a O character.
     }
   }
s}                                    #Return the string.


[0xE0C25, 0xC1043, 0xE4F27, 0xF1957, 
  0xD0C67, 0x95E30, 0x95622, 0xC5463, 
  0xE5975, 0xB5E75, 0xF4C75, 0xF5D75].map{|m|puts f[m],'---------'}

I believe there is a 112 byte solution using a 20-character string and some decoding to define the parameters of the 20 lines. I will try this later if I have time.

\$\endgroup\$
  • \$\begingroup\$ Nice explanation ! \$\endgroup\$ – Sygmei Jan 30 '17 at 2:12
2
\$\begingroup\$

PHP, 142 150 149 bytes

for($r="";$c=ord(",(NMKJIGFHDjigfecbd`"[$i]);)if(hexdec($argv[1])>>$i++&1)for($p=96^$c&~$k=3;$k++<8;$p+=7+($c&3?:-6))$r[$p]=o;echo chunk_split($r,9);

prints the shape as far as needed; i.e. if the lower part is empty, it will be cut.
Run with php -nr '<code>' <input>. Do not prefix input

Test it online

Add 11 bytes for no cutting: Insert ,$r[80]=" " after $r="".

encoding explained

Every line can be described with a starting point and one of four directions.
Drawing on a 9x9 grid, the starting position ranges from 0,0 to 8,4; or, combined, from 0 to 8*9+4=76. Luckily, all starting points [0,4,8,36,40,44,72,76] are divisible by 4; so the direction code [0..3] can be squeezed into bits 0 and 1 -> no shifting needed at all.

For an easy calculation of the cursor movement, 0 is taken for east (only direction with no vertical movement) and [1,2,3] for south-west, south, south-east, where the offset is 9 (for vertical movement) plus [-1,0,1] -> [8,9,10] -> delta=code?code+7:1.

The direction for the first and last lines being east, that results in codes ranging from 0 to 76 [0+0,4+0,0+2,0+3,4+1,4+2,4+3,8+1,8+2,...,44+1,44+2,72+0,76+0]; and bitwise xor 96 on each value results in printable and unproblematic ascii codes [96,100,98,99,101,102,103,105,106,68, 72,70,71,73,74,75,77,78,40,44] -> `dbcefgijDHFGIJKMN(,. The code uses the LSB for bit 0, while line 0 corresponds to the MSB, so the string has to be reversed. Finito.

breakdown

for($r="";                  // init result string, loop through line codes
    $c=ord(",(NMKJIGFHDjigfecbd`"[$i]);)
    if(hexdec($argv[1])>>$i++&1)// if bit $i is set, draw line 19-$i:
        for($p=96^$c&~$k=3          // init $k to 3, init cursor to value&~3
            ;$k++<8;                // loop 5 times
            $p+=7+($c&3?:-6)            // 2. map [0,1,2,3] to [1,8,9,10], move cursor
        )
            $r[$p]=o;                   // 1. plot
echo chunk_split($r,9);     // insert a linebreak every 9 characters, print

some golfing explained

  • Since ^96 has no effect on the lower two bits, it can be ignored when extracting the direction; so there is no need to store the value in a variable, which saves 5 bytes on the cursor init.
  • Using ~3 instead of 124 saves one byte and allows the next golfing:
  • Initializing the looping counter $k=3 inside the $p assignment saves two bytes
    and it doesn´t hurt the pre-condition (since the upper value still has one digit).
  • Using a string for the result has the shortest initialization and plotting possible: When a character is set beyond the end of a string, PHP implicitly sets the missing characters to space. And chunk_split is the shortest way to insert the linebreaks.
    I don´t even want to know how much more anything else would take.
  • 7+($c&3?:-6) is one byte shorter than $c&3?$c%4+7:1.
  • Added hexdec() (8 bytes) to satisfy the input restriction.
\$\endgroup\$
2
\$\begingroup\$

JavaScript, 184 183 178 168 167 bytes

f=
n=>[...`<>K[LM]NO\\^k{lm}no|~`].map((e,i)=>n>>i&1&&[0,2,4,6,8].map(i=>a[(e&102)*4+(e&17||15)*i]=`o`,e=~e.charCodeAt()),a=[...(` `.repeat(31)+`
`).repeat(9)])&&a.join``
<input oninput=o.textContent=f(+this.value)><pre id=o>

Was originally 206 bytes but @Arnauld's answer inspired me to investigate a one-dimensional array solution. Edit: Saved 1 byte thanks to @edc65. Saved 5 15 bytes thanks to @Arnauld. Saved a further byte by tweaking the choice of characters.

\$\endgroup\$
  • \$\begingroup\$ [0,1,2,3,4] is shorter \$\endgroup\$ – edc65 Jan 28 '17 at 10:03
  • \$\begingroup\$ I think you can save 4 bytes by using [67,65,52,36,51,50,34,49,48,35,33,20,4,19,18,2,17,16,3,1] and [0,2,4,6,8].map(i=>a[(e&102)*4+(e&17||15)*i]='o') \$\endgroup\$ – Arnauld Jan 28 '17 at 11:04
  • 1
    \$\begingroup\$ Or you can use [..."ecVFUTDSREC6&54$32%#"] and [0,2,4,6,8].map(i=>a[(e&102)*4+(e&17||15)*i]='o',e=e.charCodeAt()-34) to save 10 more bytes. \$\endgroup\$ – Arnauld Jan 28 '17 at 11:13
  • \$\begingroup\$ @Arnauld You seem to have undercounted your saving by 1, and I also managed to golf off an extra byte by using ~ instead of -34 (sadly I fall foul of `\` which is why I don't save 2 bytes). \$\endgroup\$ – Neil Jan 28 '17 at 13:59
  • \$\begingroup\$ I wonder if you could replace this '\' with ASCII character #220. \$\endgroup\$ – Arnauld Jan 28 '17 at 14:26
1
\$\begingroup\$

Batch, 491 bytes

@set n=%1
@for %%i in ("720896 524288 524288 524288 843776 262144 262144 262144 268288" "131072 65536 0 32768 16384 8192 0 4096 2048" "131072 0 98304 0 16384 0 12288 0 2048" "131072 32768 0 65536 16384 4096 0 8192 2048" "165248 1024 1024 1024 89312 512 512 512 10764" "256 128 0 64 32 16 0 8 4" "256 0 192 0 32 0 24 0 4" "256 64 0 128 32 8 0 16 4" "322 2 2 2 171 1 1 1 17")do @set s=&(for %%j in (%%~i)do @set/am=%1^&%%j&call:c)&call echo(%%s%%
:c
@if %m%==0 (set s=%s%  )else set s=%s%o 

Note: The last line ends with a space. Putting an if conditional with a variable inside a for loop is beyond batch so that needs its own subroutine. Since it does nothing visible I fall through into it to exit. The ~ unquotes the strings in the outer loop allowing the inner loop to loop over the numbers. The numbers are simply the bitmasks for all the places where os should be drawn.

\$\endgroup\$
1
\$\begingroup\$

C, 267 262 260 256 characters

Counting escapes as 1 character

void f(int i){char*k="\0\x1\x2\x3\x4\x4\x5\x6\x7\x8\0\x9\x12\x1b\x24\0\xa\x14\x1e\x28\x4\xc\x14\x1c\x2d\x4\xd\x16\x1f\x28\x4\xe\x18\x22\x2c\x8\x10\x18\x20\x28\x8\x11\x1a\x23\x2c\x24\x25\x26\x27\x28\x28\x29\x2a\x2b\x2c\x24\x2d\x36\x3f\x48\x24\x2e\x38\x42\x4c\x28\x30\x38\x40\x48\x28\x31\x3a\x43\x4c\x28\x31\x3a\x43\x4c\x28\x32\x3c\x46\x50\x2c\x35\x3e\x47\x50\x48\x49\x4a\x4b\x4c\x4c\x4d\x4e\x4f\x50";for(int n=0,s,l;n<81;!(++n%9)&&putchar(10))for(s=l=0;s<20;!(++l%5||++s^20)&&putchar(32))if(i<<s&1<<19&&k[l]==n&&putchar(111))break;}

k is a lookup referring to which boxes to put an 'o' in.

Try it online!

\$\endgroup\$
1
\$\begingroup\$

Befunge, 468 bytes

~:85+`!#v_86*-:9`7*-48*%\82**+
3%2:/2\<$v0%2:/2\*g02*!g03%2:/2\*!+4g07%2:/2\*g02*!-8g06%2:/2\*g02*!-4g0
g!*20g*^00>50g*\2/:2%00g8-!*40g*\2/:2%30g8-!*20g*\2/:2%60g66+-!*\2/:2%70
`\5:p00:<g^*!-8g00%2:\-10:\p07-g00:p06+g00:p05`3:p04`\5:p03:<0\p02`3:p01
#o 8`#@_^4>*50g*\2/2%00g!*40g*0\>:#<1#\+_$!1+4g,48*,\1+:8`!#^_55+,$\1+:
g03%2:/2<-^!g00%2:/2\*g01*!g03%2:/2\*g01*!g07%2:/2\*!-4g06%2:/2\*g01*!-4
70g4-!*\^>!*50g*\2/:2%00g4-!*40g*\2/:2%30g8-!*10g*\2/:2%60g8-!*10g*\2/:2%

Try it online!

The first line reads a string from stdin, evaluating it as a hexadecimal number. The rest of the code is essentially just a double loop over the x/y coordinates of the grid, with a massive boolean calculation determining whether an o should be output for each location.

There's basically a separate condition for each of the 20 grid points, for example (the first four):

(y==0) * (x<5) * bit0
(y==0) * (x>3) * bit1
(x==0) * (y<5) * bit2
(x==y) * (y<5) * bit3

And then once we've calculated all 20 of them, we OR the lot together, and if that result is true, we output a o, otherwise we output a space.

Befunge doesn't have anything in the way of bit manipulation operations, so to extract the bits from the input, we're just repeatedly evalating n%2 and then n/=2 as we make our way through the 20 condition calculations.

\$\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.