21
\$\begingroup\$

I'm not sure if this is the right place to ask, but I found this similar question so I'll go ahead.

I'm very new to code golfing, so keep your smothered laughs and throw-up emoji's to yourselves please ;). Anyway, someone recently posted a challenge (that was shortly deleted) to draw a 16x16 pixel image of the Old Man from the original Legend of Zelda, who gives you the wooden sword.

This guy:

Here's my code (643 characters):

from PIL import Image,ImageColor
g=ImageColor.getrgb
r=range
l=[list('0000033333300000000023233232000000003313313300000000331331330000000042333324000000042222222240000044220000224400044224222242244034424422224424433444442222444443344404422440444304440444444044400444044444404440004044444444040000004444444400000004433443344000'[i*16:i*16+16])for i in r(16)]
i=Image.new('RGB',(16,16),color='black')
for j in r(16):
    for k in r(16):
        t=k,j
        v=l[j][k]
        if v=='2':
            i.putpixel(t,g('white'))
        if v=='3':
            i.putpixel(t,g('#FFC864'))
        if v=='4':
            i.putpixel(t,g('red'))
i.show()

It's bad, I know. Anyway, anyone care to share some tips on shortening it?

\$\endgroup\$
11
  • \$\begingroup\$ You can definitely remove some whitespace, single spaces or tabs should work \$\endgroup\$ May 13 at 0:54
  • \$\begingroup\$ @RedwolfPrograms isn't that incorrect syntax? \$\endgroup\$ May 13 at 0:55
  • \$\begingroup\$ Actually it might be, I haven't used python in a while... \$\endgroup\$ May 13 at 0:58
  • \$\begingroup\$ What is the 1 in your encoding supposed to represent? \$\endgroup\$
    – hyper-neutrino
    May 13 at 1:00
  • 1
    \$\begingroup\$ @MawgsaysreinstateMonica I considered posting there, but in the end decided to post here :) \$\endgroup\$ May 16 at 2:39
12
\$\begingroup\$

I'm proposing a slightly new strategy. Instead of Image.putpixel, we use Image.frombytes, which creates an image directly from an encoded string of RGB values. The logic is similar to the putpixel version, but when implemented, it turns out to be considerably shorter:

406 bytes

from PIL import Image
x=b'\0\0\0',b'\xff\xff\xff',b'\xff\xc8\x64',b'\xff\0\0'
D='0000022222200000000012122121000000002202202200000000220220220000000031222213000000031111111130000033110000113300033113111131133023313311113313322333331111333332233303311330333203330333333033300333033333303330003033333333030000003333333300000003322332233000'
Image.frombytes('RGB',(16,16),b''.join(x[int(c)]for c in D)).show()

However, the compressed string D takes up almost half of the program! We can use @ovs's idea to "compress" it by storing it in a higher base (such as base 36). Now the code is almost half as long:

267 bytes

from PIL import Image
x=b'\0\0\0',b'\xff\xff\xff',b'\xff\xc8\x64',b'\xff\0\0'
D=int('MG8FMXO3ZANJ48JRXWYBSK3DF0HDWGJ34QP4XXAYS0EMXW3OJUIPDTNGOJGZIQZEKKSTFNJB1XQIAVDYAS8S06VLJVICO4STFK',36)
Image.frombytes('RGB',(16,16),b''.join(x[D>>2*c&3]for c in range(256))).show()

Finally, notice that the image is vertically symmetric. Knowing this, we can cut the size of our compressed string in half, with some overhead from the added logic. Along with some general golfing tricks, we arrive at a messy 223-byte solution:

222 bytes

from PIL import Image
Image.frombytes('RGB',[16]*2,b''.join(b'\0\xff\xff\xff\0\xff\xc8\0\0\xffd\0'[int('5VJ6J7FF7GD34HSPDCBV1SHKVG80MFVR3VX2RKU7WMRHBW0XZ4',36)>>2*(c//16*8+(c^-(c%16>7))%8)&3::4]for c in range(256))).show()
\$\endgroup\$
18
  • 1
    \$\begingroup\$ It will take some studding before I know what is going on here ;) \$\endgroup\$ May 13 at 4:21
  • 1
    \$\begingroup\$ import PIL ..... PIL.Image.frombytes saves a few chars \$\endgroup\$
    – jez
    May 14 at 16:13
  • 1
    \$\begingroup\$ import PIL does not automatically import PIL.Image. \$\endgroup\$
    – Federico
    May 14 at 18:00
  • 1
    \$\begingroup\$ You can save 5 more bytes by compressing in base 36: tio.run/##RYrBboJAFEX3/… \$\endgroup\$
    – ovs
    May 14 at 18:06
  • 1
    \$\begingroup\$ 44 chars to represent a 32byte code? Could you cat the 32bytes to the end of the script and read it? \$\endgroup\$
    – 5p0ng3b0b
    May 15 at 3:36
11
\$\begingroup\$

Splitting the string into rows of 16 and then using two nested loops to go over each pixel is taking a lot of bytes to do. It would be easier to just loop over the original string and use (index%16,index//16) to point at a specific row and column.

Also, instead of specifying a background color you can just draw every single pixel, which is shorter when using hyper-neutrino's advice to use a list of colors. You can also use #000 and #FFF instead of black and white (1 byte shorter each).

Bonus tip: (16,16) can become [16]*2 (1 byte shorter)

Combining this with knosmos huge save by using data compression and makonede's suggestion to use as imports we can get this down to 353 bytes:

import base64,zlib
from PIL import Image,ImageColor as C
i=Image.new('RGB',[16]*2)
for j in range(256):i.putpixel((j%16,j//16),C.getrgb(['#000','#000','#FFF','#FFC864','red'][int(zlib.decompress(base64.a85decode(b"Garp%d1+#J#QrG=W9sOr.gFe$difq+:dD#Fi'\\kVYS/be9`DBVETlH%2Un>#OH/p!dpQBq?eAD\\o?Ok%[FUW>EJ8;cLZ6ZbI2XaB#r3e")).decode("u8")[j])]))
i.show()
\$\endgroup\$
0
6
\$\begingroup\$

Just removing some whitespace, we can get:

from PIL import Image,ImageColor
g=ImageColor.getrgb
r=range
l=[list('0000033333300000000023233232000000003313313300000000331331330000000042333324000000042222222240000044220000224400044224222242244034424422224424433444442222444443344404422440444304440444444044400444044444404440004044444444040000004444444400000004433443344000'[i*16:i*16+16])for i in r(16)]
i=Image.new('RGB',(16,16),color='black')
for j in r(16):
    for k in r(16):
        t=k,j;v=l[j][k]
        if v=='2':i.putpixel(t,g('white'))
        if v=='3':i.putpixel(t,g('#FFC864'))
        if v=='4':i.putpixel(t,g('red'))
i.show()

(since you can inline the if statements, and putting two statements on one line with ; saves bytes when you're in a block)

Then, you can collapse the repetitive i.putpixel by using a ternary if-else. Alternatively, you can just use a list to select the appropriate value:

from PIL import Image,ImageColor
g=ImageColor.getrgb
r=range
l=[list('0000033333300000000023233232000000003313313300000000331331330000000042333324000000042222222240000044220000224400044224222242244034424422224424433444442222444443344404422440444304440444444044400444044444404440004044444444040000004444444400000004433443344000'[i*16:i*16+16])for i in r(16)]
i=Image.new('RGB',(16,16),color='black')
for j in r(16):
    for k in r(16):
        t=k,j;v=int(l[j][k])
        if v>1:i.putpixel(t,g(['white','#FFC864','red'][v-2]))
i.show()

Basically, for 2, 3, and 4, what this does is it uses a list, ['white', '#FFC864', 'red'] and selects the v-2th element from it.

\$\endgroup\$
2
  • \$\begingroup\$ Ok, what's it doing now? ;) \$\endgroup\$ May 13 at 1:00
  • \$\begingroup\$ @Haveaniceday ['white', '#FFC864', 'red'] is a list, [v-2] is accessing an index. It's like a[x], but here both a and x are expressions. Since v is one of 0, 1, 2, 3, 4. I use if v>1 to only handle if v is 2, 3, or 4. Then, if v is 2, you want white, so ['white', ..., ...][2 - 2] will give you the first element, which is white. For v=4, you want red, so [..., ..., 'red'][4 - 2] will give you the third element, which is red. \$\endgroup\$
    – hyper-neutrino
    May 13 at 1:08
6
\$\begingroup\$

You use the literal 16 8 times. In python 3.8+, you can write c:=16 and then c the other times:

i=Image.new('RGB',(c:=16,c),color='black')
\$\endgroup\$
1
  • \$\begingroup\$ Never forget the little things! \$\endgroup\$
    – MEMark
    May 15 at 6:26
5
\$\begingroup\$

Shave off 97 bytes from hyper-neutrino's answer by encoding the data with base85 and zlib:

import base64,zlib
from PIL import Image,ImageColor
g=ImageColor.getrgb
r=range
l=[list(zlib.decompress(base64.a85decode(b"Garp%d1+#J#QrG=W9sOr.gFe$difq+:dD#Fi'\\kVYS/be9`DBVETlH%2Un>#OH/p!dpQBq?eAD\\o?Ok%[FUW>EJ8;cLZ6ZbI2XaB#r3e")).decode("u8")[i*16:i*16+16])for i in r(16)]
i=Image.new('RGB',(16,16),color='black')
for j in r(16):
 for k in r(16):
  t=k,j;v=int(l[j][k])
  if v>1:i.putpixel(t,g(['white','#FFC864','red'][v-2]))
i.show()
\$\endgroup\$
2
  • 7
    \$\begingroup\$ ah yes, garpdjqrgwsrgfedifqddfikvysbedbvetlhunohpdpqbqeadookfuwejclzzbixabre \$\endgroup\$
    – Makonede
    May 13 at 1:38
  • 2
    \$\begingroup\$ btw you can remove tf- to save 3 more ('u8' is a valid encoding) \$\endgroup\$
    – Makonede
    May 13 at 1:40
3
\$\begingroup\$

EDIT: Turns out Python 2 doesn't have base64.a85decode, so this won't work. But you can still combine a b-string with an r-string, and combine that with Leo's amazing tip, and you've saved \$292\$ bytes.

import base64,zlib
from PIL import Image,ImageColor as C
i=Image.new('RGB',[16]*2)
for j in range(256):i.putpixel((j%16,j//16),C.getrgb(['#000','#000','#FFF','#FFC864','red'][int(zlib.decompress(base64.a85decode(br"Garp%d1+#J#QrG=W9sOr.gFe$difq+:dD#Fi'\kVYS/be9`DBVETlH%2Un>#OH/p!dpQBq?eAD\o?Ok%[FUW>EJ8;cLZ6ZbI2XaB#r3e")).decode("u8")[j])]))
i.show()
\$\endgroup\$
3
  • 1
    \$\begingroup\$ How does this line work from PIL import Image,ImageColor as C? Are Image and ImageColor both mapped to C now? ...Oh, so just ImageColor then. \$\endgroup\$ May 14 at 0:34
  • \$\begingroup\$ @Haveaniceday Yes, just ImageColor. \$\endgroup\$
    – Makonede
    May 14 at 1:20
  • 1
    \$\begingroup\$ Upvoted this answer because it's the first to mention that you can use #000 and #fff for black/white respectively, which saves two characters. Negligible, but a difference nonetheless \$\endgroup\$ May 14 at 7:40
2
\$\begingroup\$

215 210 216 210 216 bytes

By using literal characters in place of some of the Unicode escapes from dingledooper's 222 byte solution, we can save a byte. But by encoding the program in Latin-1, we can save 7!

EDIT: Saved 5 bytes thanks to Jakque!

EDIT: Python by default parses programs as UTF-8, so this won't work. You would need a magic comment #coding:l1 on the first line, so this would actually end up having to add 11 bytes.

EDIT: Just tested on TIO, turns out I was wrong. 6 bytes have been regained!

EDIT: After some more testing with Bash to try and get the raw bytes in, I was in fact correct the first time. Back to 216.

from PIL.Image import*;frombytes('RGB',[16]*2,b''.join(bytes('\0ÿÿÿ\0ÿÈ\0\0ÿd\0','l1')[int('5VJ6J7FF7GD34HSPDCBV1SHKVG80MFVR3VX2RKU7WMRHBW0XZ4',36)>>2*(c//16*8+(c^-(c%16>7))%8)&3::4]for c in range(256))).show()
\$\endgroup\$
5
  • \$\begingroup\$ It's 215 chars, but 221 bytes in TIO. Ty for another answer! \$\endgroup\$ Jun 2 at 20:51
  • \$\begingroup\$ @Haveaniceday Yes, because TIO assumes Python code is in UTF-8. This is in fact 221 UTF-8 bytes but by using the Latin-1 encoding (where all the characters in the code are 1 byte) this is 215 Latin-1 bytes. \$\endgroup\$
    – Makonede
    Jun 2 at 21:23
  • \$\begingroup\$ Oh, ok. Now the shortest answer goes to you then! \$\endgroup\$ Jun 2 at 22:03
  • \$\begingroup\$ you can also change from PIL import Image;Image.frombytes(... by from PIL.Image import*;frombytes(... to gain 5 bytes \$\endgroup\$
    – Jakque
    Jun 2 at 23:02
  • \$\begingroup\$ @Jakque Thanks! \$\endgroup\$
    – Makonede
    Jun 2 at 23:09
1
\$\begingroup\$
import PIL.Image, base64, zlib
s = b'c$}?~T@Jt?2!nfA54&42e`bR~6Cc(PgZQCUfkXDR5YV!&&a9D%ndJ9|Ir)dDC5CqM!KF%g^YH~{$pjovNiD-9`RpnztxNF;#WPO6@aBt?_fv%3QW0r-!$ZTqrT#L(OW41F%c&RU*cng'
i = PIL.Image.frombytes("RGB", (16,16), zlib.decompress(base64.b85decode(s)))
\$\endgroup\$
2
  • \$\begingroup\$ Without whitespace it is 247 bytes. Not quite as low as @dingledooper's ;) \$\endgroup\$ May 14 at 18:50
  • 2
    \$\begingroup\$ s doesn't have to be in a variable \$\endgroup\$ May 15 at 15:05
0
\$\begingroup\$

Instead of making a bunch of zeros, you probably could have made something like 0x8 and then parse it to display as 8 zeros. However I am not sure if that's any shorter.

\$\endgroup\$
7
  • \$\begingroup\$ I'm not too worried about speed, just the number of bytes. Ty for the suggestion! :) \$\endgroup\$ May 15 at 15:00
  • \$\begingroup\$ Sorry, not faster, but shorter. Mistyped that \$\endgroup\$ May 15 at 15:01
  • \$\begingroup\$ Gotcha. If it works, I don't see why it wouldn't be shorter. \$\endgroup\$ May 15 at 15:02
  • \$\begingroup\$ Well, a method to parse zeros might take a lot of space. \$\endgroup\$ May 15 at 15:07
  • \$\begingroup\$ Welcome to Code Golf! As this is a [tips] question, it would be helpful if you could include an example piece of code, to demonstrate your tip to the OP :) \$\endgroup\$ May 15 at 15:10

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.