M0UNTAIN 0F C0DE

Iron Bloom - Rendering & Serialisation

devlog project

JS Rendering

The initial plan was that the backend would render everything to an image and send it to the browser. It became clear that this isn't going to work when Iain created an awesome animated bonfire sprite. Animated sprites obviously aren't going to work in a JPEG/PNG.

You could maybe do some nonsense by rendering the whole thing to a looping GIF, but that sounds like a whole load of bugs and just no fun.

Instead, we changed to the classic render to a <canvas> with JS approach. This requires that the server has some way of sending the current state to the client. We needed a serialiser.

Serialising Game State

The obvious way to do this would be JSON. Something like:

[
    {
        "definition": "Tile",
        "position": {"x": 1, "y": 2}
    },
    // ...
]

Boooooring πŸ˜† We're building this for fun, let's reinvent a new, better, wheel!

My first attempt was a sort of ASCII rendering of the state:

[W][W][W][W][W][W][W][W]
[W][F][F][F][F][F][F][W]
[W][F][F][F][F][F][F][W]
[W][F][F][F][F][F][F][D]
[W][W][W][W][W][W][W][W]

Each of the three primitives (tile, entity and modifier) were assigned a unique letter. Tiles where wrapped in square brackets, [F] (floor), entities in curly braces, {D} (door) and modifiers were nested inside the relevant tile/entity like this: {D+l} (door with a lock).

Each primitive also needed a unique ID. I used a # to denote an ID: [W#41]. Without the ID it wasn't possible for the client to communicate with the backend about an entity. e.g. pickup item #41.

This totally ruined the ASCII-art aesthetic:

[W#yekr6tB2][W#eNf1i48n][W#DFe8VytZ+ms#5t4KinIp][W#Hkh2TxdL][W#gE780Mc1][W#gF9nYf3j][W#0fiw86f3][W#vPm0zT1x+ms#ETzGJPVa][W#1TDjcbBW]
[W#B63LRZYN+{D#SQzyOcKM+x#hkwBYdOc}][F#49M0nbmc][F#alZ3xQmM][F#uaX1M8L8][F#4HJYn0Hz][F#HFUNR7WW][F#qhVySvDo][F#EcSagTmn][W#aJLGmGYc]
[W#rM1aQatn][F#c1ht8X3H][F#pjNXBDjD][F#qvzLQ9jw][F#vZrSqimK+{@#aQZo93BT}][F#lZXXBEEV][F#xPXwZa0b][F#gKivBapr][W#5RyHcTZj]
[W#hMw31W0o][F#AAoerEgt][F#2POhmpmo][F#7Ua7GsYF][F#35yhsp37][F#JtQ5Zfbk][F#Z627sD3Y][F#WeEwyqJk][W#oDA3r2Ea]
[W#skgFCtn2][F#3sGJOvJw][F#pQQUCkQF][F#NQwtqwEL][F#r5u4HUYV][F#MWolePVq][F#eMs3fjVt+{C#NsQjmQuv+{K#JmM7oabJ}}][F#z5vmG8Ib][W#bHCg65qL]
[W#7nVma53b][F#n32SzzY8][F#CYsWyBI5][F#AcmCuWT1][F#3pX80Tar][F#BeDUTtYl][F#VCqqSpRj][F#iw8oRhBW][W#NX7uc25E+ms#ljAoAHk3]
[W#MhjCnsGw][F#wYsxzbx4][F#yL7T12Wa][F#CzYY3BVa][F#u3PRUV5G][F#KvcG9TP8][F#0xo0tph8][F#KK0fe0P7][W#hPS8dfj6]
[W#KWerlIc8][W#8LONkYnn][W#HCOttHo4][W#qI0p1DMi][W#joiMScex][W#OhaLTFOt][W#fDLqMBky][W#x0oXz1Ox][W#5fHTFyl1]

I could get over that, but the recursive nesting proved to be a PITA to both encode and decode.

I took a break for a while... πŸ˜–

Take II

Returning to the problem, I realised that I'd gotten hung up on encoding the tiles’ positions by where it appeared in the text. It's much simpler to have one line per primitive and for each primitive to be given an explicit location:

[W#wallid(0,0)]

In the case of nested entities, the position is a reference to an ID:

{D#doorid(#wallid)}

This avoided the need for filler 'void' tiles, any coordinate which doesn't have anything in is naturally empty.

Another simplification which fell out of this refactor was that having separate tile and entity representations was redundant. Everything is now an entity or modifier, allowing the brackets to be dropped from the state:

W#wallid(0,0)
D#doorid(#wallid)

We still needed some way to distinguish between entities and modifiers. This was achieved through convention: uppercase letters for entities, lowercase for modifiers.

The Final Schema (For Now)

One primitive per line, in the form:

DEFINITION#IDENTIFIER(LOCATION)<FLAVOUR_TEXT>

Definitions:

Full Example

F#VLY3azY8(0,1)
F#mWWiIpF2(0,2)
F#ExZCMy5F(0,3)
F#8FgNyu3X(0,4)
F#TiF7b29w(1,4)
F#6WP0Nb0N(2,4)
F#fwiK3vft(2,3)
F#UFdDniSG(3,3)
F#l70Db9PD(4,3)
F#kGaSgyMv(5,3)
F#OozTmCxn(5,4)
F#sTz9wIW0(6,4)
F#duN7Cmob(7,4)
F#bpvEHxFZ(8,4)
F#0CcL3p9v(8,5)
F#xO8f2MOc(9,5)
F#Y8zwPQNM(9,6)
F#BUHB8wSl(9,7)
F#NvASc104(8,7)
F#qoaC92ga(7,7)
F#mhnyzdWj(7,6)
F#hgVRvknK(8,6)
F#ElhbQ5bp(8,8)
F#AakqsrlS(8,9)
F#q0W5IZKS(7,9)
F#MKPx2Wm0(7,10)
F#ykgIPne0(8,10)
F#ODPQAkeb(8,11)
F#SGaqmIKE(9,11)
F#2D7TDm5T(9,12)
F#SFD1yzVA(10,12)
F#bqm6gIkO(8,12)
F#F2SZX8qG(10,13)
F#4j5bIYrc(10,14)
F#85I4vXqL(10,15)
F#TXE8l2eg(11,15)
F#NK148IDP(11,14)
F#DF0FLtjJ(10,16)
F#bB8TY9vp(11,16)
F#J4EbXPew(10,17)
F#hbsu6vge(9,14)
F#zK5T9f5t(12,15)
F#bgovxgdK(13,15)
F#OC2sWMPw(13,16)
F#4Eg7E6PR(14,16)
F#nCUgAyT4(14,15)
F#Nrwxvfys(15,15)
F#uFPYdCpQ(15,16)
F#2icZ9PlC(16,16)
F#SJHQ4CuT(15,17)
F#L3J2qkFK(16,17)
F#b89xhCbU(16,18)
F#zLswxy3k(16,19)
F#6RJtTIEC(15,19)
F#ghvO2Huv(14,19)
F#QmgEagqc(13,19)
F#0cANVpvr(12,19)
F#XF0uEmPA(12,20)
F#HOZTtjkL(11,20)
F#AuCS6ocr(11,21)
F#dkDiWJOV(10,21)
F#PIISCJlF(9,21)
F#efxGHqie(9,20)
F#GnYKnQOX(9,19)
F#MIulo6pX(10,19)
F#txnLvjZO(11,19)
F#FlYuwVkr(12,18)
F#ET6WHHCG(11,18)
F#WOYlv9B6(10,18)
F#O4ta4rZs(9,18)
F#Q8vdsLXp(8,18)
F#xCxgoncs(8,19)
F#3s7TnPSj(8,20)
F#OgqKP1uQ(10,20)
F#e5uPn1WZ(11,22)
F#uytjvd0Z(11,23)
F#7R9jQ4tA(12,23)
F#ctuxLaIA(13,23)
F#KhSvbDtr(13,22)
F#FvqxzMeg(14,22)
F#HorAynFE(14,23)
F#QbuoELoZ(15,23)
F#0J9eWwk5(15,24)
F#DrjP4UAD(14,24)
F#Yd2897H9(15,25)
F#pAfGPaq7(15,26)
F#MlS3wXBW(15,27)
F#d38WtnA9(16,27)
F#kJLXyMWn(15,28)
F#96wdCABm(14,28)
F#Wtlwxhlp(13,28)
F#4qqRiXJZ(12,28)
F#ya70CBFi(11,28)
F#Gq9sspeN(10,28)
F#0TcysWCW(11,27)
F#YB4B8BCb(12,27)
F#WZhL3ecP(13,27)
F#VynNMBeO(14,27)
F#xSG62l3Q(13,29)
F#ql4U831v(14,29)
F#FaflyMEB(15,29)
F#bBcjNRsr(15,30)
F#U0rJQ1e1(15,31)
F#EqgVZWdL(16,31)
F#eQ1p7hyV(17,31)
F#NtMW0JWk(17,30)
F#RFxdfrJm(18,30)
F#h1oppjnK(19,30)
F#aarqW0er(18,29)
F#FZwDlzI4(19,29)
F#1Rib3pLo(20,29)
F#QLFNEuYV(20,28)
F#ycJgynUn(20,30)
F#GrhfMQqB(20,31)
F#HZtRZ8B2(21,31)
F#HhKhJEil(19,31)
F#JgeR7PAe(18,31)
F#p21aO7sF(17,29)
F#naqSn79c(20,32)
F#5t4RWKEl(20,33)
F#3pMELbU0(19,33)
F#UQDXIhoL(19,32)
F#X53tDBw2(18,32)
F#Dv3MPdvH(16,32)
F#wC8Vt0vC(17,32)
F#TohbCi4S(18,28)
F#UL7sgkio(16,29)
F#iq3YGRSm(16,28)
F#Dpbf3adD(17,28)
F#Ta52PHRA(17,27)
F#JVr0Bxyu(16,30)
F#oTWDAGRY(14,30)
F#SCEaATN4(14,31)
F#kxEf5W9o(13,30)
F#Y4G0hMBF(13,31)
F#ts1tEeOi(19,28)
F#7ZXs6QpU(21,28)
F#xnlIciqG(21,27)
F#T0VMaozF(21,26)
F#hHdc8Cn3(21,25)
F#BkFZBE2S(21,29)
F#lKBO5Mwx(21,30)
F#Sr0mxoZy(22,31)
F#WAx65Jb6(23,31)
F#HzGreOHM(23,32)
F#a8YNcwkp(24,31)
F#bGsYmpid(25,31)
F#5IjqcjWn(25,30)
F#oI9YXZe6(26,30)
F#K2Ef1eph(26,29)
F#rxeglUGI(26,31)
F#jJVOZG8P(26,32)
F#BJfiE7ik(25,32)
F#F4AtOQ9V(24,32)
F#zepvpwOS(25,33)
F#BaXoEDPo(25,34)
F#8DfoFxdt(25,35)
F#E8s04920(24,35)
F#QuHdGDoY(26,34)
F#2RUkSVQV(26,35)
F#aN1UA3E4(27,35)
F#5wwDY3gN(27,34)
F#GQ1aJD6W(27,33)
F#iivYN3KV(28,33)
F#g1cJHfES(28,34)
F#GJ2fT7yw(29,33)
F#WrKXEB7V(29,32)
F#m2xyWegD(30,33)
F#o6fFZIzF(31,33)
F#4GMXBtWQ(31,32)
F#FvO67m47(31,34)
F#afqHehzc(31,35)
F#bp7Ivz0D(32,33)
F#1FnVc6tj(32,34)
F#V8iTvgDi(30,35)
F#n3so5o9O(29,35)
F#OCmNrL53(30,34)
F#OVnnf7PV(32,35)
F#IKiC9jwf(33,34)
F#M6SbbW4O(34,34)
F#7xwyRG5u(34,35)
F#2MHF4uGd(35,34)
F#FlnIiCLV(35,35)
F#n358JVcz(35,36)
F#E6JbF4oP(35,37)
F#JdCVv1yF(34,37)
F#jKGXVNYI(34,36)
F#S2JXL1zT(33,37)
F#6zoXADft(33,36)
F#B44wQvyt(34,38)
F#LkXFGjfR(35,33)
F#BWr79mH3(36,34)
F#moMJMWUP(36,35)
F#E7SuvACx(34,33)
F#rfy2Ky90(36,33)
F#SbbhfcWZ(36,32)
F#Nk9OscTU(37,32)
F#WOVvWO1U(37,31)
F#D8QJ58Q2(38,31)
F#afUZv3Rm(38,32)
F#DMUcZUw8(36,31)
F#FnbtUoCp(36,30)
F#1dFs2fLZ(36,29)
F#L1Oyf5a4(36,28)
F#phT9Xaep(35,28)
F#KvCYhODM(35,29)
F#T7JCxFvU(37,29)
F#TeGZuthE(37,30)
F#6XxaDQR1(37,28)
F#dafMAnBm(36,27)
F#BKibMjwd(35,30)
F#OlSYkkn1(34,30)
F#ZTGAe26c(34,31)
F#BGye18Yl(34,32)
F#33gcfOPW(35,32)
F#6p0S3AQQ(35,31)
W#kcasa2V7(-1,-1)
W#sUlN8qoC(0,-1)
W#CoNtBs5b(1,-1)
W#DlMnZmOR(-1,0)
W#bEMtMreS(1,0)
W#xfjVXwLT(-1,1)
W#50ULgG9i(1,1)
W#1Ztg4uI3(0,0)
W#VY54rLho(-1,2)
W#HkTe0mr3(1,2)
W#vqT5IwYe(-1,3)
W#h8omRhLj(1,3)
W#nrKaV9lX(-1,4)
W#y3mKEemb(-1,5)
W#w2S34vmN(0,5)
W#KDu6DQP7(1,5)
W#SfSDJrhV(2,5)
W#AMHlJxPH(3,4)
W#WZG5a1Wp(3,5)
W#bU0fZDza(2,2)
W#bU3Hh4eI(3,2)
W#b203T8uf(4,2)
W#8nD3hSep(4,4)
W#DolENpqV(5,2)
W#Rq4qpDZx(6,2)
W#vHzR4fyK(6,3)
W#ewSgg5bP(4,5)
W#5sv1hCwk(5,5)
W#H9LFx7ZV(6,5)
W#APkvLTx3(7,3)
W#1sVMBNWX(7,5)
LP#EgVgUkSS(#1sVMBNWX)
bl#JJuSpi4b(#EgVgUkSS)<10>
W#FbiaSddz(8,3)
W#cY8gzOFW(9,3)
W#Xk4UP5i8(9,4)
W#sfppnHkt(10,4)
W#36W7Ec9v(10,5)
W#Y1aETFe2(10,6)
vn#IhsMXaHb(#Y1aETFe2)
W#caofER0r(10,7)
W#LnO5dvPt(9,8)
W#03qVCQIv(10,8)
W#hWpxQvnR(7,8)
W#KP2RRKMW(6,6)
W#HP2rex4T(6,7)
W#OxOgtPsT(6,8)
W#AddWCd7R(9,9)
W#KEFKimFj(9,10)
W#ZDsFMyDj(6,9)
W#N1n6qDgf(6,10)
W#IZjtX3Kk(6,11)
W#NH1iD9Cu(7,11)
W#qUTImhEG(7,12)
W#Jc6IONin(10,10)
W#xXju7SCS(10,11)
W#BxZQEq6f(8,13)
W#YuLtoT5a(9,13)
W#smvBqSun(11,11)
W#E8RlxhuP(11,12)
W#SRcEqrOH(11,13)
W#HRXKNjz6(7,13)
W#AVXQLEKy(9,15)
W#JItSRHkU(9,16)
W#x8o02tnY(12,14)
W#fNB0zGTb(12,16)
W#k5axwxAv(12,13)
ms#YyqJCWUj(#k5axwxAv)
W#TgVDCMhk(9,17)
W#OTSZU6Cr(11,17)
W#VrpN2sC6(12,17)
W#EId9np5O(8,14)
W#vftjt1oU(8,15)
ms#e7RlD7Sz(#vftjt1oU)
W#Bpxo6Txz(13,14)
W#VxyJg0kn(14,14)
W#vDxwCqLy(13,17)
W#YmgyzGQU(14,17)
W#zNmkgRAU(15,14)
W#4FhDOfZS(16,14)
W#awZVSARd(16,15)
W#AmPrQy9W(17,15)
W#Irdsc2ZD(17,16)
W#Qz3HpefN(17,17)
W#fh1tnLGl(14,18)
W#Ucv2151M(15,18)
W#hW0GprTD(17,18)
W#81WvwT12(17,19)
W#mnboI5se(15,20)
W#v6qhqfml(16,20)
W#LmmGQ0bP(17,20)
ms#A7BDhogX(#LmmGQ0bP)
W#Q1gYYigf(14,20)
W#fx66cJol(13,18)
W#1G2hcfic(13,20)
W#8E82sSM2(12,21)
W#OAhiaWpk(13,21)
W#o63JU8Pd(10,22)
W#qFuOvapC(12,22)
W#s8hGLaji(9,22)
W#8TqNtIXL(8,21)
W#dGGFpRkk(8,22)
vn#86upJ2uZ(#dGGFpRkk)
W#cfv7qY4f(8,17)
W#IRP4HI4X(7,17)
W#uGdI3WfZ(7,18)
W#BJMjdWab(7,19)
W#PHsc36Wf(7,20)
W#WrC6Om4K(7,21)
W#PtjcyPgU(10,23)
W#vr0F7B5j(10,24)
W#Hf3VgJP6(11,24)
W#Hhi8z2oK(12,24)
W#LzUM0ESB(13,24)
W#jInSMfJK(14,21)
W#RNwj0nPA(15,21)
W#3KAasXOW(15,22)
W#I8343CsJ(16,22)
W#jV5zHAgl(16,23)
W#7m0oMUcf(16,24)
W#g3TT4ozV(14,25)
W#cudtWZX2(16,25)
W#laC7LvT1(13,25)
W#bjvVWYka(14,26)
W#M9x5rVYu(16,26)
W#pV3MxU6d(17,26)
W#JaLRIZSX(12,29)
W#c47dNV8a(11,29)
W#UTtrpfIq(10,27)
W#lCstBlf8(10,29)
W#J5Klyp1Q(9,27)
vn#yt2vKZzg(#J5Klyp1Q)
W#gdcJ4k9V(9,28)
W#zZqH9E33(9,29)
vn#Tk79BE5H(#zZqH9E33)
W#0YBfDKkB(10,26)
W#iph2H2fu(11,26)
W#NAR3rX2a(12,26)
W#j0tqvqDO(13,26)
W#WQZv5CD4(12,30)
W#lmaEpoUK(14,32)
W#qGxy4u7s(15,32)
W#LvYYzmuJ(19,27)
W#d7OnOmdS(20,27)
W#gJNZiCO8(21,32)
W#kIsfZX6j(22,30)
W#ZUx0tG25(22,32)
W#D7OfqxgY(21,33)
W#VS2W6UIE(19,34)
W#TlfQrx7G(20,34)
W#cu2AwbGN(21,34)
ms#jW9JzzdN(#cu2AwbGN)
W#pS2tUkEv(18,33)
W#69KxjxMl(18,34)
W#PCpcyQDG(17,33)
W#7bP9B2T1(15,33)
W#qT7T9wJ2(16,33)
W#K53fD2oB(18,27)
W#NIIP9GDC(18,26)
W#aSUdyUu2(13,32)
W#SPuuYT0N(12,31)
W#rEiqUr2R(12,32)
W#ajlwxPwZ(22,27)
W#QiRyVYsv(22,28)
W#sZu6zVa9(22,29)
W#TTkTBdPq(20,26)
W#T8G6udCG(22,26)
W#bQ03byCP(20,25)
W#PT3Hmn90(22,25)
W#AIMVI2On(20,24)
W#Msqcvp5D(21,24)
W#NLCR3XIg(22,24)
W#nJwCk9Ur(23,30)
W#ohpUgXBm(24,30)
W#Dyfc94lT(22,33)
W#qScV7t2I(23,33)
W#x36100Iy(24,33)
W#dBSobHip(24,29)
W#p4XqITPp(25,29)
W#YV6Hwb9X(27,29)
W#IJ1zzYgY(27,30)
W#F4CpLU2G(27,31)
W#dS7uNbdZ(25,28)
W#nYYGMxg7(26,28)
W#pzH2wL59(27,28)
W#v2l8E42F(27,32)
W#9ebqKKao(26,33)
W#IWZc5ziR(24,34)
W#wjcixZN5(24,36)
W#WfduWOm3(25,36)
W#K087Ln3i(26,36)
W#C0pEEiS4(23,34)
W#3Ad5fUjI(23,35)
W#vStqlVLa(23,36)
LP#Mg6h1786(#vStqlVLa)
bl#xoNUW4Hu(#Mg6h1786)<10>
W#AvcugDkO(27,36)
W#VZj9NMez(28,35)
W#vkD4ntZp(28,36)
W#RxiYEbHE(28,32)
W#pYVvnhOk(29,34)
W#uuh4sBKk(30,32)
W#BnWjVwdd(28,31)
vn#ptqnPlq2(#BnWjVwdd)
W#rnFcE6wi(29,31)
W#KwT9Vad4(30,31)
W#BPGeQLWJ(32,32)
W#J6vUwnDT(31,31)
W#aKCGijx4(32,31)
W#Y7ZCbeNA(30,36)
W#08AhZjMV(31,36)
W#B2vvNOeo(32,36)
W#Ds63cdbh(33,32)
W#9Kt8lEKB(33,33)
W#60EiNfhn(33,35)
W#L79EsM2x(29,36)
ms#nP8UyP65(#L79EsM2x)
W#azxlE0NI(36,36)
W#oF3Daog1(36,37)
W#XWHHUCUZ(35,38)
W#EyCBsPBy(36,38)
W#nFKS6Xjb(33,38)
W#goWmIQUL(32,37)
W#e8bG18ul(32,38)
W#4Mn8S3jB(33,39)
W#HE6JmqNU(34,39)
W#0bcVLG9O(35,39)
W#mJEPRjVA(37,33)
W#3kiVRnO1(37,34)
W#hViLXtvC(37,35)
W#KMGRFVRy(37,36)
W#0UjgNwYe(38,33)
W#bnAjJy5m(38,30)
W#IhY7UN84(39,30)
W#psO1W5Mn(39,31)
W#nhUUbSuh(39,32)
W#FGY0ueBb(39,33)
W#V8MreBlR(35,27)
W#hxVD5BMo(37,27)
W#hVRjfrBv(34,27)
W#6BPpHaJo(34,28)
W#DF7eQYWj(34,29)
ms#0moaMqGg(#DF7eQYWj)
W#vPrFps9t(38,28)
W#f1xF3kH2(38,29)
W#65TsKKXW(38,27)
W#LyPqOJz4(35,26)
W#vSaxMkPP(36,26)
W#QEw2NdFk(37,26)
W#3ih1ffR8(33,29)
W#TmcZ4Zmo(33,30)
W#cgzX2nan(33,31)
C#yRdHPqnu(#TXE8l2eg)
K#3g9RobP2(#yRdHPqnu)
D#6TmNQYmd(#HE6JmqNU)
bl#5l8bSEvr(#6TmNQYmd)<15>
x#PInH48Jr(#6TmNQYmd)<It's locked!>
D#7waq5HvI(#vPrFps9t)
bl#BrL4pF3u(#7waq5HvI)<15>
x#I3kCEG0T(#7waq5HvI)<It's locked!>
@#uZXxEkqK(#L1Oyf5a4)
wa#moSXPzwO(#uZXxEkqK)
bl#rrQp9cpa(#uZXxEkqK)<197>
n#QB2pREwG(#uZXxEkqK)<Dr Halyn>
WP#21i30ZRG(#uZXxEkqK)<Stick>
ds#bKHsJDcK(#21i30ZRG)<It's super ineffective.>
bl#k87FL1ln(#L1Oyf5a4)<1>
bl#vhlBxgD5(#dafMAnBm)<1>