Last challenge (Pixel-art, episode 1: display Super Mario) was just a training... (and you completed it in amazing ways, thanks!)

This time, you have to work a little bit more. You have to display all the first overworld map of Super Mario bros on NES, without enemies, and without Mario.

Your program or function must display all the pixels of the following image OR produce an image file similar to it (BMP, PNG, or GIF).

Your program must not access the internet in any way.

The output can be zoomed if you want, and the pixels can be ASCII or HTML elements if you want, as long as they have the right color.

Here's the model you have to follow:

Sky blue: #5C94FC
Black: #000000
Pink: #FCBCB0 (for the blocks and the castle)
Brown: #C84C0C (for the blocks and the castle)
Orange: #FC9838 (for the "?" block)
Light green: #80D010 (for bushes, mountains, flagpole, warp)
Dark green: #00A800 (for bushes, mountains, flagpole, warp)
White: #FCFCFC (clouds)
Light blue: #3CBCFC (clouds)

Shortest answer wins.

EDIT: There will be two score boards, one where scores are counted in bytes and one where they are counted in characters.

Good luck!

P.S: Here are some notes that could help you optimizing your program:

  • Clouds, bushes and mountains have a repeating pattern (every 48 columns)
  • Flying blocks are only present on lines 4 and 8
  • Each tile or sprite of the map uses at most 4 colors (including blue or transparent, depending on how you see it)
  • Bushes are just "top of clouds" with a diferent color palette
  • Single, double and triple bushes/clouds can easily be formed using the same mini set of 16x16px pieces. This is also true for the single and triple mountains
  • 11
    \$\begingroup\$ Is episode N going to be making playable 1-1? \$\endgroup\$
    – Nick T
    Commented Oct 18, 2014 at 18:34
  • 1
    \$\begingroup\$ @BetaDecay I could but... the frontier is very thin between including an image and producing a similar image programatically. Also, I'm curious to see someone else make another hardcoded solution in less than 4012 chars. Theoretically, the hardcoded image could fit in less than 2400 Unicode characters. \$\endgroup\$
    – xem
    Commented Oct 19, 2014 at 6:39
  • 1
    \$\begingroup\$ I never realised the bushes were the tops of the clouds... Well well well! \$\endgroup\$
    – Beta Decay
    Commented Oct 19, 2014 at 10:13
  • 6
    \$\begingroup\$ @BetaDecay NES developpers were the code-golfers of the 80's ;) All the SMB game (dozens of sprites, palettes, sounds, musics, physics engine, game engine, AI, ...) fits in 32kb. It blows my mind. This challenge is a tribute to it, btw. \$\endgroup\$
    – xem
    Commented Oct 19, 2014 at 10:21
  • 1
    \$\begingroup\$ @xem: Another one: The castle has complete brick blocks (f.e. at (202,10)) that differs from the other brick blocks (f.e. at (168,8)) which have orange lines at the top. Updated tileset: img.ctrlv.in/img/14/10/19/5443f44c7eb78.png \$\endgroup\$
    – schnaader
    Commented Oct 19, 2014 at 17:26

8 Answers 8


x86 Machine Code, 1729 1619 1468 1382 Bytes

How it works: Tiles are generated by a combination of RLE compression, 2bit images, and procedural code. Once the tiles are generated in memory, the program then creates a matrix of tile indexes. This is first loaded with the repeating background. After that, the pipes, floating blocks, pyramids, and the flag pole are drawn procedurally. The pipes, hills, bushes, and pyramids can extend below the ground, but are covered up when the rock tiles get written next. Finally, the castle tile values are simply copied into the correct location. To generate the image file, the BMP header and palette is stored in the file as data, and is written first. The program then runs through the matrix, writing the appropriate row from the corresponding tile for each position.

Usage: Run mario.com, it will generate "m.bmp", a standard BMP image file. The file is created as a hidden file, since that ended up being less bytes.

Download a ZIP file containing the source code and binary, plus output.

enter image description here

Assembly code to generate the executable file:

org 100h

;xor    cx,cx
mov    cx,12+14+256*3
mov    dx,filename
mov    ah,0x3c
int    21h
mov    bx,ax
push   ax

inc    dx
inc    dx
mov    ah,0x40
int    21h

mov di,tiles
xor ax,ax
mov ch,48
rep stosw

cwd             ; load the cloud sprite
mov si,cloud
call    DrawSprite
call    DrawHill

mov dx,14*256+12*16
PipeTileLoop:                ;Pipe
mov si,pipe0
call    DrawSprite
sub dh,2
jns PipeTileLoop

mov dx,32+12*16
call    DrawSprite
mov dh,13
;mov     si,pipe2
call    DrawSprite

xor ax,ax                ; move to the tile array
mov cl,6
call    MoveTile
loop    MoveTileLoop
mov word [BITMAPFILEHEADER+5+2],0x607     ; bushes
sub dl,3*16
mov cl,13
jnc MoveTileLoop

mov dx,0x1004
mov si,ball
call    DrawSprite
mov dl,0
call    MoveTile

xor bx,bx       ; pole
mov word[bx+tiles+22*256+7],0x707
add bl,16;
jnc pole

mov word [BITMAPFILEHEADER+5+2],0x805      ; qbrick
mov si,qbrick
call    DrawSprite
call    MoveTile

mov byte[BITMAPFILEHEADER+5+3],4

mov al,1            ; bricks & castle
call    Clear
mov al,23
call    MoveTile        ; black door
mov dx,0x800+64
xor dh,8
mov si,bricks
call    DrawSprite
sub dl,dh
jg  BrickLoop

call    MoveTile         ; left window
call    MoveTile         ; castle brick
mov al,3
mov di,image+15*256+16
mov cl,16
rep stosb
mov al,26
mov dl,16
call    MoveTile         ; reg brick

;mov     dl,32
;mov     si,door
call    DrawSprite
;mov     dl,32
call    MoveTile        ; door top
mov dx,48 +8*256
;mov     si,cren
call    DrawSprite
push    si

mov dh,0
call    MoveTile        ; crenulation 1
mov si,image+48+256  +4
mov di,image+48+256*9+4
mov bl,7
mov cl,7
rep movsb
mov dl,256-7
add si,dx
add di,dx
dec bx
jnz CrenLoop
mov dl,3*16
call    MoveTile        ; crenulation 2

pop si
mov cl,3
jmp skip
;call    MoveTile        ; right window
                 ; block
                 ; rocks
call    DrawSprite
call    MoveTile
loop    LastLoop

xor ax,ax              ; Begin drawing world using the tiles
call    Clear
mov bl,48*4
lea di,[bx+image+2*256]
mov al,10
mov cl,5
inc ax
loop    HillRow0
mov dword [di+256-4],10+11*256+256*256*14
mov byte [di+512-3],15
sub di,256-10
cmp di,image +256
ja  SmallHill

mov si,fluff         ; draw clouds, bushes
add ax,bx
add ax,image
xchg    ax,di
mov cl,ah
;call    DrawFluff;

mov ah,1
inc ax
push    cx
rep stosb
inc ax
pop cx
add di,256-2
sub di,cx
inc ax
dec ah
jns FluffLayer2

cmp si,pa
jl  FluffDrawLoop

sub bl,48
jnc BackgroundLoop

;mov     si,pa
mov cl,6
xchg    bx,ax
mov di,image
mov word [bx+di],18+19*256
dec bh
mov word[bx+di],16+17*256
jnz PipeLoop
loop    PipePlace

;mov     si,dba          ; draw the various floating blocks
mov cl,4
xchg    ax,bx
mov ah,bl

mov bl,al
mov byte [bx+di],ah
cmp al,dh
mov dh,al
ja  BlockLoop

dec si
loop    DrawBlockLoop

mov bx,198+256*12
FlagPole:             ; Draw flag pole
mov byte [bx+di],22
dec bh
jg  FlagPole
inc si
;mov     si,pyr            ; pyramid blocks
mov cl,8
shr ah,1
inc dx
jnc NoNeg
neg dx
mov bx,ax
mov byte [bx+di],32
dec bh
jnz PyrDrawLoop
add ax,dx
dec ah
jnz NoNeg
loop    PyrMainLoop

;mov     si,ground        ; ground blocks
;mov     di,image
mov bl,4
push    di
xchg    ax,cx
mov al,33
rep stosb
add di,ax
dec bx
jnz GroundLoop
push    si
inc ch
mov si,di
sub si,cx
rep movsb

pop si
pop di
;push    di
;mov     si,copy         ; Draw Castle, flag ball
mov  dl,6
mov cx,ax
shr cx,12
and ah,15
xchg    bx,ax
mov [bx+di],al
inc bx
loop    CopyRun
dec dx
jnz CopyLoop;

xor ax,ax
xor bx,bx ; image y        ; write the image to file
pop dx
mov bl,0 ; image x
mov ah,[bx+di]
mov bx,dx
add ax,tiles
xchg    ax,dx
mov cl,16
mov ah,0x40
int 21h
inc bx
cmp bl,212
jb  WriteStep
add al,16
jnz WriteRowStep
inc bh
cmp bh,14
jl  WriteTileStep
;pop     bx
;mov     ah,0x3e
;int     21h


DrawSprite: ; dx=x,y, si=data
mov bp,dx
mov di,temp
push    di
lodsw              ; w, h*2+flag
mov dl,al
shr ah,1
mul ah
xchg    bx,ax
jc  BitMapped
mov cl,al
shr cx,2
sub bx,cx
rep stosb
jnz RunLoop
jmp MoveData
mov cl,4
shr ax,2
loop    BitMappedQuad
sub bx,4
jg  BitMappedLoop
mov bx,sp
mov [bx+4],si
pop si
mov bx,di
lea di,[bp+image]
mov dh,-1
mov cl,dl
rep movsb
sub di,dx
cmp si,bx
jl  MoveLoop

MoveTile: ; dx =x,y, bx=y,ax=t-1
inc ax
mov si,dx

xchg    al,ah
xchg    di,ax
mov ax,16
mov cl,16
mov bx,[si+image]
inc si
and bx,3
mov [di+tiles],bl
inc di
loop    TileTrans
add si,256-16
dec ax
jnz TileLoop
add dl,16

Clear: ; al = value
mov di,image
mov ch,48
rep stosb

xor bx,bx
mov al,3
lea di,[bx+image+6*16]
inc byte [di]
inc di
mov cl,16*5-2
sub cl,bl
sub cl,bl
rep stosb
inc byte [di]
inc bx
inc bh
cmp bl,16
jl  HillLoop

mov si,spot
push    si
mov dx,32-7+256*8 + 6*16
call    DrawSprite
mov dl,32+17    + 6*16
pop si
call    DrawSprite
mov dx,5*16      + 6*16
call    DrawSprite

filename: db 'm','.'
BITMAPFILEHEADER: db 66,77,'P',0,12,0,1,2,3,0,26,3,0,0
BITMAPCOREHEADER: db 12,0,0,0,64,13,224,0,1,0,8,0
colors: db 252,148,92,0,0,0,252,252,252,252,188,60,176,188,252,12,76,200,0,168,0,16,208,128,56,152,252

;pal: db 0,1,2,3
cloud: db 88,32,224,5,114,5,228,5,122,5,224,5,122,5,228,9,106,5,6,5,240,102,5,4,5,240,5,86,5,10,5,252,5,78,5,4,5,6,5,252,4,13,66,5,8,5,92,13,16,17,136,5,14,7,42,5,100,9,14,5,4,9,18,9,4,9,120,5,14,11,14,7,18,5,96,5,26,5,34,5,10,9,116,5,26,7,22,5,84,13,18,11,18,15,34,9,108,5,38,5,6,5,84,5,22,27,6,23,38,108,5,34,5,4,5,88,5,10,19,14,19,18,7,34,5,108,9,26,5,92,5,10,7,10,7,26,7,54,5,120,5,18,5,92,5,10,7,46,7,46,5,128,17,56
spot: db 6,17,245,95,255,245,93,87,127,245,87,127,245,223
hilltop: db 16,3*2,0x9, 0x33, 0x9, 0x8, 0xD, 0x1B, 0xD, 0x1C, 0x19, 0x14
ball: db 8,17,80,5,244,31,253,127,253,127,249,127,249,127,228,31,80,5
pipe0: db 64,5,0x90,0xFA,0xAA,0xAE,0xFF,0xFF,0xBB,0x06,0xA9,0xAF,0xAA,0xEB,0xFF,0xFF,0xBB,0x6B,0x90,0xFA,0xAA,0xAE,0xFF,0xFF,0xAE,0x06,0xA9,0xAF,0xAA,0xEB,0xFF,0xFF,0xEF,0x6A
pipe1:db 32,4,8,113,8,129
pipe2:db 32,12,5,23,26,79,9,122,133
qbrick: db 16,33,85,85,85,85,254,255,255,127,222,255,245,119,254,191,246,127,254,191,254,127,254,255,245,127,254,191,246,127,254,191,86,127,254,215,106,127,254,218,107,127,254,218,107,127,254,90,233,127,254,171,250,127,222,255,255,119,254,255,255,127,168,170,170,42
bricks: db 8,16,33,14,5,30,5,30,5,18,33,30,5,30,5,30,5
door: db 16,32,0xFD, 0xFD, 0x89, 0x6, 0x39, 0xA, 0x39, 0x6, 0x41, 0xA, 0x31, 0x6, 0x5, 0xE, 0x29, 0xA, 0x5, 0x16, 0x19, 0x12, 0x5
cren: db 16,17,213,255,255,85,234,0,192,170,234,0,192,170,234,0,192,170,234,0,192,170,234,0,192,170,234,0,192,170,255,0,192,255
block: db 16,32,61,6,7,53,6,5,11,45,6,9,15,37,6,13,19,34,17,19,34,17,19,34,17,19,34,17,19,34,17,19,34,17,19,34,17,19,34,17,15,6,35,17,11,6,43,13,7,6,51,9,6,59,5
rocks: db 16,33,86,149,87,149,171,106,171,90,171,127,171,106,251,85,171,106,95,170,173,106,165,170,173,106,171,170,182,106,171,170,182,106,171,170,182,106,171,170,246,127,171,170,102,149,171,170,118,106,171,170,182,106,171,170,182,106,171,170,182,106,254,255,231,191
fluff: db 8,10,11,     19,11,11,       27,10,31,       36,11,21,    11,1,34,    23,1,14,       41,1,24
pa: db 28,3,38,4,46,5,57,5,163,3,179,3
dba:db 21,5
qb: db 16,21,23,78,106,109,112,170, 21,9
qb2: db 22,94,109,129,130, 27,5
ba: db 20,22,24,77,79,94,100,101,118,129,130,168,169,171  , 27,9
ba2: db 80,81,82,83,84,85,86,87,91,92,93,121,122,123,128,131,0
pyr: db 137,5*2+1,140,5*2,151,5*2+1,152,5*2+1,155,5*2,188,9*2+1,189,9*2+1,198,4
ground: db 69,2,86-69-2,3,153-86-3,2,256-153-2,0
copy: db 202,2+5*16,26,26,24,26,26,   202,3+5*16,26,26,28,26,26,    202,4+5*16,29,30,30,30,29,   203,5+3*16,31,26,25,  203,6+3*16,29,29,29, 198,12+16,20
temp: rb 256*48
tiles: rb 34*16*16
  • \$\begingroup\$ Incredible! There are still people out there writing assembly :D \$\endgroup\$
    – xem
    Commented Oct 30, 2014 at 6:30
  • \$\begingroup\$ +1000 Easily the best entry! So, no standard libraries at all eh? That's really simply leagues ahead smaller than the HLL entries :D \$\endgroup\$
    – Will
    Commented Oct 31, 2014 at 8:34

Javascript minified (*): 1285 1258 1253 1205 1186 1171 characters

(*) Minified using Closure, RegPack and ObfuscaTweet, as suggested by xem

Unicode version is 4549 bytes in size, without ObfuscaTweet (Closure and Regpack only), size is 2251 bytes.


1285 -> 1258: variable A for 48 (thx @hsl), merged some of the for loops, merged mt() and mu(), using tile indices instead of tile strings, optimized png with PNGOUT

1258 -> 1253: merged some more for loops; renamed mt() to r(); removed unnecessary braces; variable B for 16; defining 16 unused CSS sprites (replaces 32 with A); showing 1 unused row (replaces 14 with B); removed function e(); shortened t(), g(), c(); using for(i=0;i<n;)f(i++) instead of for(i=0;i<n;i++)f(i) where possible

1253 -> 1205: moved body style to CSS part instead of <body style=...>; replaced some for loops with f calls; optimized functions r,q; </head><body> seems to be unnecessary, <html><head> too; function t(i) for CSS mapping removed; CSS names b0..b31 instead of a..z,aa..ff

1205 -> 1186: function n renamed to N; new function n that operates on an array with delta coding

1186 -> 1171: hills and warps can be drawn "big" anytime, the lower parts get overdrawn by stone blocks; use d for both clouds and bushes; removed some unnecessary semicolons

This is a procedural attempt. There are patterns everywhere, one of the magic numbers is 48 (tile gap between clouds, bushes and mountains). The tileset is encoded as Base64 data url string and used as a CSS stylesheet. In Javascript, the 212x14 array m is filled with tile indices. See the commented unminified version for more details.

Works in Chrome 38 (Ctrl+T for new tab, Ctrl+Shift+J for javascript console, paste code there) and Firefox 33 (if wrapped with HTML javascript tags). There's a JS bin version, too.

There's still some room for optimizations, I'll post updates and it would be nice if some JS/CSS/HTML people could suggest optimizations/corrections.



Unminified and commented:

// map width
// helper constants
// array containing the map, generated at runtime
function w(s){document.write(s)}
// set a map tile
function N(i,s){m[i]="b"+s.toString()}
function n(i,s){z=0;for($ in i){z+=i[$];N(z,s)}}
// set a row of map tiles (index a..a+b)
function f(a,b,s){for(k=a;k<=a+b;)N(k++,s);}
// set a block of map tiles (index a and b define upper left and lower right corner)
function g(i,j,s){for(x=i%W;x<=j%W;x++)for(y=i/W|0;y<j/W|0;)N(y++*W+x,s)}
// helper for clouds and bushes - j=1..3 sets map tiles to XYZ, XYYZ or XYYYZ
function c(i,j,a){N(i,a);f(i+1,j-1,a+1);N(i+j+1,a+2)}
// clouds/bushes
function d(i,j,a){c(i,j,a);c(i+W,j,a+16)}
// hill
function p(i){N(i+2,8);n([i+213,211],23);n([i+214,211,2],24);n([i+215,213],25);N(i+426,9)}
// warps
function q(i){N(i,10);N(i+1,11);l(i+W,3,26);l(i+213,3,27)}
// stairs - d=1 for upwards, d=0 for downwards
function r(i,d){for(j=0;j<4;j++)f(i+d*(3-j)+j*W,j,0);}
// set a column of map tiles (index i, height j)
function l(i,j,s){for(k=0;k<j;k++)N(i+k*W,s);}
// fill map with sky tiles
// clouds and bushes - some of the bushes will be overdrawn by blocks and the castle, so draw them first
// '?' blocks
// brick blocks and castle
// big and small hills - one of the big hills will be overdrawn by the stairs, the small hills are big hills overdrawn by stone blocks
// warps (often overdrawn by stone blocks), stairs and block below pole
// pole
// lower row of stone blocks
// gaps in stone blocks
// HTML start and CSS spritesheet base (tileset PNG encoded as base64)
// generate CSS classes 'b0'..'b31' for the 32 used tiles
// this actually generates 16 additional unused classes to reuse the constant A and save 1 byte
for(i=0;i<A;i++){j=(i%B)*-B;k=i>15?-B:0;w(".b"+i.toString()+"{background-position:"+j+"px "+k+"px;width:16px;height:16px;float:left;}")}
// some more HTML
// body of HTML contains all tiles as <div> elements arranged in <span> columns
for(x=0;x<W;x++){w("<span class=b0>");for(y=0;y<B;){w("<div class="+m[y++*W+x]+"></div>")}w("</span>")}
  • 1
    \$\begingroup\$ Very impressive for a first draft, and thanks for putting so many efforts into it. Here are a few optimizations you could use: remove <html>, <head>, <body> tags, type="text/javascript", (or text/css) attribute, minify your JS code (use closure-compiler.appspot.com/home), then pack it ( use siorki.github.io/regPack.html) then obfuscate it in Unicode chars (use xem.github.io/obfuscatweet). But please, if you do so, keep the "unminified" version in your answer so thah people can read it. To count your score in number of chars, use mothereff.in/byte-counter. ;) \$\endgroup\$
    – xem
    Commented Oct 19, 2014 at 18:36
  • 1
    \$\begingroup\$ Semicolons are automatically inserted at the end of lines, so you can safely remove those. \$\endgroup\$ Commented Oct 19, 2014 at 19:29
  • 2
    \$\begingroup\$ That's unminified!?!?!?!? \$\endgroup\$ Commented Oct 20, 2014 at 4:23
  • 1
    \$\begingroup\$ @AnubianNoob: Yep :D. It's still golfed, though. Perhaps I'll post more info on what the code does later. \$\endgroup\$
    – schnaader
    Commented Oct 20, 2014 at 5:27
  • 1
    \$\begingroup\$ function l(i,j,s){for(k=0;k<j;k++)n(i+k*W,s);} can be shortened by extracting k++ to the function, I think? So can the next line with j. \$\endgroup\$
    – Compass
    Commented Oct 21, 2014 at 15:15

Javascript, 1069 1072 1024 characters (1957 bytes)

RegPacked and Obfuscatweeted


Unobfuscated code

I hid this code because there's ungolfed code below.

function R(s,t){return new Array(t+1).join(s)}
function b(n){return"<div style='background: url(data:;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAgBAMAAAAPh7vXAAAAG1BMVEVclPwAAAAAqADITAyA0BD8/Pz8vLD8mDg8vPyQWLLUAAACwklEQVR4XrWWS4rjMBCGXcxjrfKje23hA4wRJNtJ8AEmi4JsTUD7phd9gkCOPSWN5OiRttsx8xGJ2CKpj99SJcVOW8bdv+miHd+U5VfxRaQDE/y61jz91FqpHrFSPb/rFRctXEW8KF668E31b+w/LG+BAOIXBOoh4uDX1c4ImCLIVFxcmaEKbSMYc4FXWz8QACKxLFBKyQLS02YCaAkEbAQ4CaBj//GuVJgANESnryRgBLqjZLquyxJAh9rx9GIEtGKcwKh6n8H+Q2G1DwQaYgFYJSCP+SPo0VKZ+HkkAj1WgcDre58IUCO2JLAsoPU4J3DmDLYmgI5MIN8Db68qEAA6X+lKp2cTwBfN4IS/DgSyY/je412g4er0RAL++PrvRYe/nheYTgEgOQSsTQCOUiwLWKUXFTcinAQa8ohGrEygNt1gWUAjCwSGJoG9FwCaOJFYlYAXwE8o9IhMjxfbjII+ED6CHxQgxLoESivgs0WHvy40GioursdIgFsxulYMDQWcko0IiGImgSWBES09FzdjWgh/jBqKiAXQFMCZX0OQUswIuOuq5zHywIQlAXR1paPIP25n/IRpd0aZiiRkighXQVpaOd20z+A4BI3w+2/XBzDB3HskUPPnYpozGc55AKULoGwfCHTdUwLImyiL4GoUrlkCIL2AFE8mgI57sYERIo3gdqXbjR3iCMq7QDufAH7CtDtjgcMhESAufzuzAvMn2wElsomYTSA8AeFJeCzQpQ8BkA34ZQMQaQBt2Zp5JoEVAjbCoxHIMjgR0wT1QXoBaRDPJJDtAWB7JhOAhgQSIQQLZSzQrt8DeR/Aevo/nSgApn/MQSaI7X0ABs8B2H6eUia02/tAPUzUSwIgM8TmPjAEHGBdAEy7tQ/gEFKLdQEwYlsfgCFGrAuAaZME/qMAyIeIbX2gjgQOawNg2rV74C9KQ+xyNDQoswAAAABJRU5ErkJ)-"+(n%16)*16+"px -"+(n/16|0)*16+"px;width:16px;height:16px;float:left}'></div>"}
function M(s){m="";for(_ in s)m+=b(parseInt(s[_],32));return m}
function C(s,a){c="";for($ in a)c+=$%2?M(a[$]):R(s,a[$]);return c}
O="<body style='width:3392px'>";

Ungolfed code

function repeat(s, t) { var S=s; while(t-- > 1) S += s; return S; }
function block(n) { return "<div style='background: url(data:;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAAgBAMAAAAPh7vXAAAAG1BMVEVclPwAAAAAqADITAyA0BD8/Pz8vLD8mDg8vPyQWLLUAAACwklEQVR4XrWWS4rjMBCGXcxjrfKje23hA4wRJNtJ8AEmi4JsTUD7phd9gkCOPSWN5OiRttsx8xGJ2CKpj99SJcVOW8bdv+miHd+U5VfxRaQDE/y61jz91FqpHrFSPb/rFRctXEW8KF668E31b+w/LG+BAOIXBOoh4uDX1c4ImCLIVFxcmaEKbSMYc4FXWz8QACKxLFBKyQLS02YCaAkEbAQ4CaBj//GuVJgANESnryRgBLqjZLquyxJAh9rx9GIEtGKcwKh6n8H+Q2G1DwQaYgFYJSCP+SPo0VKZ+HkkAj1WgcDre58IUCO2JLAsoPU4J3DmDLYmgI5MIN8Db68qEAA6X+lKp2cTwBfN4IS/DgSyY/je412g4er0RAL++PrvRYe/nheYTgEgOQSsTQCOUiwLWKUXFTcinAQa8ohGrEygNt1gWUAjCwSGJoG9FwCaOJFYlYAXwE8o9IhMjxfbjII+ED6CHxQgxLoESivgs0WHvy40GioursdIgFsxulYMDQWcko0IiGImgSWBES09FzdjWgh/jBqKiAXQFMCZX0OQUswIuOuq5zHywIQlAXR1paPIP25n/IRpd0aZiiRkighXQVpaOd20z+A4BI3w+2/XBzDB3HskUPPnYpozGc55AKULoGwfCHTdUwLImyiL4GoUrlkCIL2AFE8mgI57sYERIo3gdqXbjR3iCMq7QDufAH7CtDtjgcMhESAufzuzAvMn2wElsomYTSA8AeFJeCzQpQ8BkA34ZQMQaQBt2Zp5JoEVAjbCoxHIMjgR0wT1QXoBaRDPJJDtAWB7JhOAhgQSIQQLZSzQrt8DeR/Aevo/nSgApn/MQSaI7X0ABs8B2H6eUia02/tAPUzUSwIgM8TmPjAEHGBdAEy7tQ/gEFKLdQEwYlsfgCFGrAuAaZME/qMAyIeIbX2gjgQOawNg2rV74C9KQ+xyNDQoswAAAABJRU5ErkJ)-" + (n%16) * 16 + "px -" + (n/16|0) * 16 + "px; width: 16px; height: 16px; float: left; }'></div>"; }
function blocks(s) { var S=""; for(var i in s) S += block(parseInt(s[i], 32)); return S; }
var output = "<body style=width:3392px>";
output += repeat(block(7), 212);
output += repeat(blocks("777777777777777777745677777777777777455677777777"), 4);
output += blocks("777777c7777777777777");
output += repeat(blocks("7777777745677777777klm77777455567777kllm77777777"), 4);
output += blocks("777777s7456777777777");
output += repeat(blocks("77777777klm7777777777777777klllm7777777777777777"), 4);
output += blocks("777777s7klm777777777");
output += blocks("777777777777777777777737777777777777777777777777777777777777777777777777777777772222222277722237777777777777737777777777722277772332777777777777777777777777777777777777777777777777777777770077777777s7777777777777");
output += blocks("777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777700077777777s7777777777777");
output += blocks("777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777000077777777s7777777777777");
output += blocks("777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777770000077777777s7777ddd777777");
output += blocks("7777777777777777377723232777777777777777777777ab777777777ab7777777777777777772327777777777777727777727777737737737777727777777777227777770770777777777700770777777777777223277777777777700000077777777s7777tgv777777");
output += blocks("77877777777777777777777777777777777777ab777777qr778777777qr7777777777777777777777777777777777777778777777777777777777777777777777777777700770077778777000770077777777777777777777777777000000077778777s777dfffd77777");
output += blocks("7nop777777777777787777777777ab77777777qr777777qr7nop77777qr77777787777777777777777777777777777777nop777777777777787777777777777777777770007700077nop700007700077787ab77777777777777ab700000000777nop77s777ggegg77877");
output += blocks("no9op777777hiiijnop7777hij77qr77777777qr7hiij7qrno9op7777qrhiiijnop7777hij777777777777777hiij777no9op777777hiiijnop7777hij7777777777770000ii0000no9o00000770000jnopqr77hij777777777qr00000000077no9op70777gguggjnop7");
output += repeat( repeat(block(1), 69) + blocks("77") + repeat(block(1), 15) + blocks("777") + repeat(block(1), 64) + blocks("77") + repeat(block(1), 57), 2);

I first made tiles into sprites with @schnaader's minified data URI. 0 ~ v (which is 0 ~ 31 in base 31) represents each tile.

And I converted map to tiles by hand. This data has 212 characters per row.

Then I replaced repeating character (like 7(sky) and 1(ground)) with repeat().

I found 7s and some blocks, and another 7 pattern is repeated. So I made another function to make it compact. You can see it at unobfuscated source code.

Finally I RegPacked, and Obfuscatweeted my 2341 bytes golfed code.

It was very funny challenge. Thanks! And thanks to @xem for more tricks.

  • 1
    \$\begingroup\$ Let's get dirty. Here are some tips to lose a few dozen chars: you can remove the quotes in <body style='width:3392px'> and in <div class='...'>. You can write your <style> element at the end of your <body>. You can omit </style> and </body>. You can remove the leading "image/png" and the trailing "ggg==" in your base64. You can omit the <style> element altogether, and get rid of the longhand notation "background-position" and the div classes, by setting an inline style to each div, like this: <div style='background:url(data:;base64,...)-Mpx -Npx;width:16px;height:16px;float:left'> \$\endgroup\$
    – xem
    Commented Oct 22, 2014 at 12:49
  • 1
    \$\begingroup\$ Good work! I thought my procedural attempt would beat coding the map, now I have something to catch up with ;) \$\endgroup\$
    – schnaader
    Commented Oct 22, 2014 at 14:07

Python3 1638 1576 1616 1513 bytes

581 code + 932 data

enter image description here

↑ This top is the original level. In the middle is the PPM generated by this script. At the bottom is a diff, and this shows that the palette and the map do not agree exactly! I put this down to data glitch and not my script ;)

# 759808 is 212*14 tiles at 16*16 each - the size of the map
import lzma;B,E,R,X=bytearray,759808,range,16
D=B(lzma.decompress(open("i","rb").read())+B(E)) # put canvas on end of data array
i,P=8218,lambda x,y:8622+y*54272+x*X # 8622 is length of decompressed data
def Q(s,n,d): # copies a 16x16 tile from s to d
 # n is the stride for the source s, which is different for put and copy
 for m in R(0,256,X):e=d+m*212;D[e:e+X]=D[s+m*n:s+m*n+X]
for j in R(4): # there are 4 command sections
 j&=1;i+=1+j;r=D[i-1]<<8 # j is now truthy if this is a copy buffer
 for k in R(D[i]): # this many commands
  a,b,w,h,c,d=D[i+1:i+7];q=h>32 # unpack it; put doesn't use all these
  # do the put/copy
  for z in R(((a,h-32)[q],w*h)[j]):Q((D[(i+4+z,i+5)[q]]<<8,P(a+z%w,b+z//w))[j],1+211*j,
  i+=((3+a,5)[q],6)[j&1];r>>=1 # move i to next command
for y in R(2,11):Q(28<<8,1,P(198,y)) # special case flagpole
o=open("o","wb");o.write(b"P6 3392 224 255 ") # header for PPM
for c in D[8622:]:o.write(D[8192+c*3:][:3]) # decompress palette to RGB

(bytes counted without comments, line-unwrapped and second level indent being \t)

Data (base64-encoded to be pasted; decode and save as a file named "i"):


The data file is a custom format I made for this problem and is stored as LZMA.

First all 32 tiles are serialized. There are 9 colours in the palette, and this takes 8219 bytes uncompressed. (I found that trying to compress the tiles at 4-bits-per-pixel didn't help compression at all. I did not try and brute-force the best ordering of the tiles, and I probably lose a few points here.)

There are 212x14=2968 blocks making up the map.

Then the instructions to recreate the map are now encoded.

First comes a section of "put" commands that place some sequence of tiles from the tile-palette on the map at a particular x,y. As there are 32 tiles, I specify a run of the same tile using a number bigger than 32 rather than the tile index.

Then comes a section of "copy" commands that copy some rectangle of the current map to some other place. There is a special bit-mask that marks if the copy should be mirrored.

A short example command buffer:

16, # there are 16 put commands
3, # place 3 tiles
8,2, # dest x, y
4,5,6, # these three tiles
3, # place 3 tiles
8,3, # dest x, y
20,21,22, # these three tiles
5, # place 4 tiles
27,2, # dest x, y
4,5,5,5,6, # these five
8, # bits set if copy reversed (high 8 bits of a uint16 mask)
14, # there are 14 copy commands
8,2, # src x, y
3,2, # w, h to copy
19,1, # dest x, y
1,9, # src x, y
3,2, # w, h to copy
16,10, # dest x, y
13, # there are 13 put commands
7, # bits set if copy reversed (high 8 bits of a uint16 mask)
15, # there are 15 copy commands

(I said short, but that's actually nearly half the total command buffer needed to make the whole map; the vast majority of the bytes in the data are the 32 source tiles themselves)

Then comes a second section of "put" commands and finally another section of "copy" commands.

Because these can overwrite each other, I can build copy parts that I later erase or change.

I can doubtless shave a few more bytes off it by - for example - turning puts into small copies and by doing the eval trick on a gzipped source-code, or playing with PNG (which is DEFLATE with image-specific filters) too. But I like things verbose as they are.

  • \$\begingroup\$ Great work! About the diff, if the images of the OP are wrong (which is totally possible, I made that with paint), can you please provide better images for the map (or better values for the palette, idk). Thanks :) \$\endgroup\$
    – xem
    Commented Oct 22, 2014 at 7:53
  • \$\begingroup\$ Also, all the clouds and bushes appear in the diff. Do you think it's because of colors or because of an offset of 1-2 pixels between the two images? Looks like an offset to me \$\endgroup\$
    – xem
    Commented Oct 22, 2014 at 7:56
  • \$\begingroup\$ @xem I don't think the problem is so bad and I don't think it needs urgent fixing. In your map the tile at 3,11 seems to be missing from the palette. There seems to be some noise in the map around 20,12 and the general outline of trees and clouds may be an off-by-one alignment glitch in the palette? There's only 9 colours, so it can't be any kind of blending artifact. \$\endgroup\$
    – Will
    Commented Oct 22, 2014 at 7:58
  • \$\begingroup\$ the bricks are totally my fault yeah :D. I tried to hide a goomba and made a 1px offset. I didn't touch bushes and clouds at all though. But I think your image is righter than mine, so I take it :p \$\endgroup\$
    – xem
    Commented Oct 22, 2014 at 8:02
  • \$\begingroup\$ I also failed hiding mario and all the other goombas in front of mountains, actually, d'uh. Thanks! \$\endgroup\$
    – xem
    Commented Oct 22, 2014 at 8:04

JavaScript: 3620 (ouch) 3429 3411


  • Update #1: Removed var definitions and put the variable declarations inside brackets of first use. Removed getElementById() as it is also available on load as a varaible by ID. Using cloneNode() instead of createElement('CANVAS'). Renamed main from xMx to M. Removed the scaling feature :(, (still available in the example.)

    Added some comments to the expanded code. (That code is not updated with the removals. The line below, ("Mini code"), is.)

  • Update #2: Removed the main function M() as a whole and let the code run at root. This would require the code be placed inside a load wrapper or at end of document.

  • Update #3: Added statistics.

Mini code:

K=126,f=['#000','#c84c0c','#fc9838','#00a800','#80d010','#fcbcb0','#3cbcfc','#fcfcfc'],X='Å=!15d>KIRiE%>?D^?C^FVNBTJ@M^>>]ANtN?ZCW]@QM4Gb1>EVWEZAEZN@VfMI?MPckVAwNEZWNV>Q]@YT2n6d@%vN4Gb@MABMALT>JTABM>Qz@FCTI`ZEE@L?E@CDD{EE@L_A5G>%>IVEB_DHTCC{PC{HDzlz]A4o5d>KIR5G}LZ?YZIAZI`ZH]LE]BBVL>VBHXLEXBLMLAM3n7y$",2EEKIR&O30y$",2E}?Q6&o9y$",2EbD?Q6&n2pI`ZJNM`BRG~J`M`CRO9dBKIRibPC@NMBNB]6n0dBKIRib>C@CVBCE]6n1dBKIR6n3y*",~1F0G%Ii%1R6Ob")",?-1n4y*",BK0G%1Ri%I6Ob")",In8dEKIRib"*",b1F0G%Ii%1R6O>%CCD8Gb")",An9p@ciuB?LFD?`RC?Ji~F?JRBc6OUdBKIAib1Bciu1>?JFDtDM>DV>DZ>DX>QtQM>QV>a>>PT>PwPr>Pa>s>WT[][M[V[Z[XAI]AIMAICAo1eKIREJ`NGuv`3G~>{AB@MC@JJ@SJES2o2eKIREI`NGBKJ`2GCKAEABBfPT>PwPr>Pa>s>WT[V[Z[XAI]AIM^T^_^r^aA^vATA@VA@ZA@XAJ]AJSO0dHKQFXRC{NzB@D@ZNG}r>>Z>@TBB@Mw>F?C[>>^MJ@VABTACVAE_3GF%Cr>EFMP_[FAO3dHKIFFBFB[FNG}w>FMBHTFZ>HwDFS?HCACFJGFK@T>>>>{ZCCMF@VX>>PtPBE[CBACSO5dH%WBHL[ELBPLGbWwQT@W@S[v@aABDLO4dH%1>?@JB?B4Gb1?]^>v@MJBCLGF%1B]AVvESodHK?EF_@3G}w>FMBEMECMEtZ>>HSndH%I?EF`zCBP|}XSt@a>BX>CaB`M>R>>>YMBREAo3duH?J6o4d~EPFiu`]@FP|bFDVREVN]N[>CEa[aAIV>`CAo6dEK?Cl?E7G}YF9GE%BHWG>K>BRMCF?DHBDDHBN@HBI{LJ@C3o7eKIRi>K?B@?@MB?S>?CATSCMAEV@?V2o8eKIR6O6y(",E%H4&O7y(",bHCF3&O8y(",>%H4&&&#;Ø=Kxt3r@6X>8_J1ZA3TJ6VA8>>@PM2D{5HM7F@L0ES2C@L5BS7zBU>JG>x>>>3a>6a>8rA1rA3_A6_A8T[M3>M5DM7PS0FS2HS5CS7EMU@JG@x>M3PM6PM8HS1HS3ES6ES8BS?T3>T5DT7PBA0FBA2HBA5CBA7ETU@LGBx?M3Hz6DM8E@J1FS3B@J6CS8>zPT2DBB5HT7FBL0EBA2CBL5BBA7{BU>LGu1PM3FM6HM8CS1ES3@S6BS8?MDT2HT5FT7EBA0CBA2BBA5{A7>TU?LGE%1a>3Z>6r>8VA1_A3MA6TA8tDM2HM5FM7ES0CS2BS5@S7>MU?JGF%1^>2L>>4^@5s7^>8jA0lA1jA3lA5lA6l>U`|H%1fB2N>>4f@6kB7f>9kJ0gL2kA3gJ5gA6g>UR|D%1`>>2`>>4N>>6L>>7L>>9fA1^A2^A4kA5jA6sUY|P%^Al>4s6`>>9lA1Lv4`v6^hL>>UsJ]5I]9Qc4Rc9NIG1bL>>5^>9jA4lh`|1>%f>5k>9gA4R>hN|1@%1Y]6`c1Lc6[>UW]@a5?a9DPA4FPhCWG1~kAR>>4g>6N>>9Rv1fA4Nv6khf>Ug>[>4W]9Yc4`c9LIG1uNvg>5f>6Rv0kA1Nv6fhR>>21k>L]5[>9Wc4Yc9`IG1E%IB6P7ALE8WB6N5`B5HI@6P7AJE8W@6N5`@57G1F%1FX2>X2BX7Dm0Fm0Pm1@m7?X2@V9CCA0PCA2PC2G1Hx?X2@X2CX7HX7PX9Cm0?m1Dm2PDJ6DDJ7>X8?CD9>CL2>CL2DCA3>NG1D%13N>N4kN4g`5`>N8^W9gA3`?L4I?L4W?N5`?L8J?Q3FPJ4?PJ5?PL5EPJ8BPY3Hm4?m5>DJ5Em8CDR8EH`8FFN8HEL8DC2G1Pxg>3g>4R>>5lA6Lv7s3Q]4R]5Y]4Fa5HWqb2s3s4l>5gA6Nv8k>3W]4Y]5Q]4Ha5DWq>xQc6Lc7W]3Da4FX5HQq@xWc6Nc8I]3Pa4HX5DQq~19Qc9DPhDDhDHhDFhDEhDChDBhDJqu19mqE%Uf@U`>@UJ?@U`?@Uf@U`>@UCXUBP3qF%U@PEUBH3qH%UNIqD%UNAqP%UBQG3bUEQ&&';'B%?KAG@B@@p"%2_>>AC%]>j>H>G2e%O2O1DAY>I>W>6GA9Q>J>:K:%?A?%P>E1E>A>?>A?F>H1D>P1C>20B>@AF1D19,&GC1@>B1%?@1?17,&,6,5,8,4,3,>12,0,1,'.match(/../g).forEach(function(v){X=X[u='split'](String.fromCharCode(K--))[U='join'](v);if(K==92)K=91});eval(X[u]('*')[U]('f')[u](')')[U]('d')[u]('(')[U]('b')[u]('&')[U](']')[u]('%')[U]('[')[u]('#')[U]('}')[u]('!')[U]('{'));function e(k,d,i){for(s[Y]=k,i=0;i<d[L];)s.rect[G](s,d.slice(i,i+=4))}function _(d,i,q){for(S.width=R,i=0;i<d[L];++i,s.fill(),s.beginPath())F[q=d[++i][0]]?F[q](d[i]):e(f[d[i-1]],d[i])}F={$:function(d){_(Å[d[1]].concat(d.slice(2)))},b:function(d){_(Å[d[1]].slice().map(function(i,x){return(x=d[2].indexOf(i))>-1?d[2][++x]:i}))},f:function(d,i){for(s[Y]=f[d[1]],s.moveTo[G](s,d[2]),i=3;i<d[L];++i)s.lineTo[G](s,d[i])},d:function(d,x,y,g){for(s[Y]=f[d[1]],g=d[2],y=g>0?0:15,x=0;x<R;++x,y+=g)s.rect(x,y,1,1)}};R=16;c.width=3392;c.height=224;;S=c.cloneNode();c=c[G='getContext']('2d');s=S[G]('2d');c[Y='fillStyle']='#5c94fc';c.rect[G='apply'](c,[0,0,3392,224]);c.fill();for(k=0;k<Ø[L='length'];++k)for(_(Å[Ø[k]]),t=Ø[++k],i=0;i<t[L];i+=3)for(j=0;j<t[i+2]*R;j+=R)c.drawImage(S,t[i]*R+j,t[i+1]*R)

Blah blah:

– Demo at end of post.

Using canvas as base for an attempt to solve this. Ended up with about 6000 chars, but after some fiddling (and a custom compression - with fiddling and tweaks on that as well) I'm now at the stated number. Still high, but then again the quality is good ;-).

It also includes a scale option, first argument to the xMx(), aka "main" function. 1 = normal size (as in 16 bit tiles). There is not much room for tweaking, so if one use fractions some of the tiles does not fit best together. At whole numbers it should be OK. [1]

But: warning, going up, quickly eats resources and makes for a huge canvas. (It is all painted in one go.) When the original width is 3392 pixels it quickly becomes huge. [1]

[1] As of update #1 this has been removed. It is present in the demo.


  • Main code           :  870
  • Compression:
    • Main data       : 2176 (17,480 bits) 
    • Key             :  128 ( 1,024 bits) 
    • Code            :  236  
    • Whole she bang : 2540  
    • Decompressed data : 5608 (44,864 bits) 
  • Total   : 3410   [1]

[1]: +1 byte, ";", between the main / data code.

The compressed data is one array, Å, and one "object", Ø. Where Å is holding the tile placement and the Ø holding the data for painting the tiles. The Ø should have been an array as well – but as my code started out with something like this:

var tiles = {
   brick: [
   qmark: [

It ended up as is. I'll perhaps see if I can't fix this. Ideally I would have made it parseable with JSON instead of eval(), but that would not be a point in a contest like this. As it is now it can not be JSON parsed due to missing quotes around the id parts.


Here is a fiddle where one can view the code in action. The code in the fiddle I have expanded somewhat (And added some HTML / CSS styling around the world – but the image of itself is self sustained.):


Expanded code:

Expanded and (a bit) rearranged code:

var D, C, c, S, s,   // Document, canvas and shadow canvas.
    R, F, Z, X, 
    Ø, Å,            // Due to the compression we have a couple of non-ascii
    K,               // variable names.
    L = 'length',
    Y = 'fillStyle',
    u = 'split',
    U = 'join',
    f = [
        '#000',    '#c84c0c', '#fc9838', '#00a800',
        '#80d010', '#fcbcb0', '#3cbcfc', '#fcfcfc'

/* ----------------------- data unpackeer and loader ------------------------ */

K = 126;  // Key pos (char) counting down.
    function (v) {
        X = X[u](String.fromCharCode(K--))[U](v);
        if (K === 92)
            K = 91;

* Initially JSON, but got lost in compression work + a somewhat bad design
* on the obj. to begin with. Is eval, at least for now.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
try {
} catch (e) {

// Scaling function
function z(a) {
    return a.slice().map(
        function (v) {
            return v * Z;
// Tile painter "normal tiles"
function e(k, d, i) {
    for (s[Y] = k, i = 0; i < d[L];)
        s.rect.apply(s, z(d.slice(i, i += 4)));
function _(d, i, q) {
    S.width = R;
    for (i = 0; i < d[L]; ++i) {
        F[q=d[++i][0]] ? F[q](d[i]) : e(f[d[i-1]],d[i]);
F = {
    $ : function (d) {  // Expand a tile (e.g. add border on top
                        // of "normal" bricks as with the "in-air".
    b : function (d) {  // Replace colors. (Used for bushes.)
                return(x = d[2].indexOf(i)) > -1 ? d[2][++x] : i;
    f : function (d, i) { // Free-paint (fill). E.g. "hill-sides"
        s[Y] = f[d[1]];
        s.moveTo.apply(s, z(d[2]));
        for (i = 3; i < d[L]; ++i)
            s.lineTo.apply(s, z(d[i]));
    d : function (d, x, y, g) { // Diagonal lines.
        s[Y] = f[d[1]];
        g = d[2] * Z;
        y = g > 0 ? 0 : 15 * Z;
        for (x = 0; x < 16 * Z; x += Z, y += g)
            s.rect(x, y, Z, Z);
// Main function.
function xMx(h, k, i, j, t) {
    Z = h || 1;
    R = 16 * Z;
    D = document;
    C = D.getElementById('m');              // Doc canvas
    c = C.getContext('2d');
    S = D.createElement('CANVAS');          // Memory canvas
    s = S.getContext('2d');

    C.width  = 3392 * Z;
    C.height =  224 * Z;
    c[Y] = '#5c94fc';
    c.rect.apply(c, z([0, 0, 3392, 224]));  // Blue sky (rest is tiles)

    for (k = 0; k < Ø[L]; ++k) { // Loop coordinate array. This has fixed
        _(Å[Ø[k]]);              // order.
        t = Ø[++k];
        for (i = 0; i < t[L]; i += 3) {
            for (j = 0; j < t[i+2] * R; j += R)
                c.drawImage(S, t[i] * R + j, t[i+1] * R); // Apply tile.

xMx(); // Load main.
  • \$\begingroup\$ woah, That's great! +1 for the nice presentation in the provided link. +1 for the homemade packer. If only you could avoid using non-ASCII chars (like Å,Ø), you could use tools like xem.github.io/obfuscatweet to compress (much) more, because the score is counted in chars. \$\endgroup\$
    – xem
    Commented Oct 21, 2014 at 16:38
  • \$\begingroup\$ You don't need to use var to declare variables; they are automatically declared when you define them, so you should be able to remove var D,C,c,S,s,R,F,Z, from your code. (also you can replace /.{2}/ with /../.) \$\endgroup\$ Commented Oct 21, 2014 at 22:48
  • \$\begingroup\$ @hsl: Yes, thank you. I thought of doing the removal of var but left it as I had to run when I posted it. Done now. The RegExp was a nice one, somehow did not manage to see that ;) \$\endgroup\$
    – Runium
    Commented Oct 22, 2014 at 0:35
  • \$\begingroup\$ @xem: Thank you. There is no problem using the Unicode trick with ÆØÅ as those are within the first set (0x80+0xNN) where 0xNN is the interesting part. I was not sure, however, if that was "legal" as it depends on how one view it. A code point above 0x7f has a length of 2. As such the code would be equal in length (+1 byte if odd - or, actually longer by 45 bytes due to the ev(a|i)l routine). If it is "legal" then OK. \$\endgroup\$
    – Runium
    Commented Oct 22, 2014 at 0:40
  • \$\begingroup\$ You're right, those chars are okay: eval(unescape(escape('𨑬𩑲𭀨𘣆ñ†ƒ…𘠩').replace(/uD./g,'')))works as intended. And yes, your score is counted in characters so you can totally use tricks like Unicode compression and eval.You could have a score near 1750 with that! \$\endgroup\$
    – xem
    Commented Oct 22, 2014 at 7:42

Python 1331 1326 1314 1272 1268 1217 bytes

(code: 219 214 202 186 182, data: 362+750=1112 363+723=1086 346+689=1035)

The idea here is that I first generate a map one 16th the size in each dimension, where each pixel is colored by the index in the tile map. To ensure that the same pallette was used, I first combined the given tile map and the map into a single image. From this and the tile map (both minified pngs) we can now regenerate the original map. (The code for this process can be seen below the submission code). It should be noted that I saved the tile map as a file "t" and the generated map as an image "m"

EDIT: I started exploring how the ordering of the tiles influenced the compression, and for the sake of exploration I generated 10000 random permutations and compressed them with this result: result This was all with the tiles in one line, as that reduced the code quite a bit. I tried similar things with different configurations (2*16, 4*8), but none with a better average result.

Assuming that png would compress better with greater continuous similar regions, I built a tool that would let me move the tiles around, and using what I perceived to be a more continuous image I got the tile image down to 723 from 750b.

EDIT2: Afte rmuch (much) more analysis on how png actually works, and a lot of experimenting (still ongoing), the images have now been compressed even further. Link below updated. I'll write more about this analysis later when it is complete.

New images used are here: https://i.sstatic.net/gofpw.jpg

Here are the other images generated in the process: https://i.sstatic.net/9s1GD.jpg, though some are a bit outdated because of above edit.

import PIL.Image as I
for i in range(2968):y=i/212;x=i%212;v=s*m[x,y];o.paste(t.crop((v,0,v+s,s)),(x*s,y*s))

The script I wrote to reduce the map is pretty crude, but here goes (also might be outdated because of changes above):

from PIL import Image

blockSize = 16
combined = Image.open("combined.png")
tileImage = combined.crop((0,224,256,224+32))
w,h = tileImage.size

tiles = []
for y in range(0,h,16):
    for x in range(0,w,16):

referenceImage = combined.crop((0,0,3392,224))
w,h = referenceImage.size

mapImage = Image.new("RGB", (w/blockSize, h/blockSize))

colorScale = 256/len(tiles)

for y in range(0,h,16):
    for x in range(0,w,16):
        i = tiles.index(list(referenceImage.crop((x,y,x+blockSize,y+blockSize)).getdata()))

  • 1
    \$\begingroup\$ Wow, that's really impressive. And it's even more impressive when we see that you count in bytes and not chars. Congrats! \$\endgroup\$
    – xem
    Commented Oct 24, 2014 at 18:28

Perl 5 + PNG: 689 + 593 = 1282

A failed attempt to replace PNG compressed tile map from Christian's solution with Perl code. In the end it is 65 bytes longer. Takes the same PNG tiles on input and generates a PNG on the output.

Usage: perl mario.pl <tiles.png >world.png

for@z=qw(256@ 1
4<19@MNO14@MNNO8@<6@I 1
5<8@MNO16@M3NO16@< 2
22@X57@8T3@3TX14@X11@3T4@TXXT56@YY 3
203@3U 1
16@X3@TXTXT21@[|9@[|18@TXT14@T5@T5@X2@X2@X5@T10@2T6@Y2@y10@YY2@y12@TTXT31@QPS 1
5<2@H45@< 38@[|162@U3VU 1
5<17@H30@< 28@[|133@[|14@[|21@PPWPP 1
4<11@A3BC7@ABC15@ABBC3@<10@PPRPPC48@2<69Z2@15Z3@64Z2@101Z< 1);
$p=GD::Image;use GD;$f=new$p(3392,224);$k=newFromPng$p('-');

JS: 4012 3921 chars

JS is used to unpack many Unicode characters and write an img tag which src is the image given in the OP, saved as PNG, compressed, encoded in base64 and trimmed.


Demo: http://jsbin.com/fojidejoco/1/

PS: I know it's a little bit cheating (even if the rules allow it), but at least, it gives us a goal: do it in less than 4012 chars.

Tools used:

  • 1
    \$\begingroup\$ I know that this is a non-optimal solution, but why have you answered your own question so soon? \$\endgroup\$
    – Beta Decay
    Commented Oct 18, 2014 at 21:22
  • 1
    \$\begingroup\$ An additional option would be to use a sprite sheet and an array of where the sprites go. o_O \$\endgroup\$
    – Compass
    Commented Oct 18, 2014 at 21:46
  • 7
    \$\begingroup\$ @BetaDecay to get rid of this "hardcoded" approach asap and let people do something more creative, like, use a sprite sheet and an array. :p \$\endgroup\$
    – xem
    Commented Oct 19, 2014 at 6:28
  • 1
    \$\begingroup\$ And I would disagree with that. It's pretty easy to encode binary data as Unicode via simple methods, and thus we'll all end up writing uninteresting extra code in order to pretend binary data is really Unicode. That is the opposite of being creative in my opinion. \$\endgroup\$ Commented Oct 21, 2014 at 16:44
  • 1
    \$\begingroup\$ Then the task really becomes two unrelated tasks - Encode the image data as small as possible, then convert the compressed data to as few Unicode characters as possible. I think the second task hinders being creative on the first, and would be better off as it's own challenge. \$\endgroup\$ Commented Oct 21, 2014 at 17:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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