From 9116227d704d7c2769ff914a9c6e9f2cfc65cbe6 Mon Sep 17 00:00:00 2001 From: Luka Vandervelden Date: Sun, 4 Nov 2018 00:32:03 +0900 Subject: [PATCH] Updates, various. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Naka has been split to a separate file for better readability. - Naka::Noise2D added. Basically Perlin noise, but it adds a few APIs to sum stacks of octaves and such. Generation from seed is still missing even though it’s critical. - An example map is generated using several Naka::Noise2D. - Some assets added, and used to display an example map. Objects that are higher than one tile are displayed, but widths can’t overflow at the moment. - Maps are saved to disk. This is still minimal, however, and loading them from save files is not implemented at the moment. - Some Naka::Event were added at some point, although they’re still very much works in progress, and probably incomplete. - Maps are displayed in a very crude way. Transitions between terrain types are unsupported, for example. --- assets/cactus.png | Bin 0 -> 5577 bytes assets/dirt.png | Bin 0 -> 5162 bytes assets/flowers.png | Bin 0 -> 5842 bytes assets/grass.png | Bin 0 -> 5174 bytes assets/haunted.png | Bin 0 -> 5377 bytes assets/rocks.png | Bin 0 -> 5380 bytes assets/sand.png | Bin 0 -> 5216 bytes assets/stone.png | Bin 0 -> 5171 bytes assets/tiles.png | Bin 0 -> 5218 bytes assets/tree.png | Bin 0 -> 6002 bytes assets/tree_pine.png | Bin 0 -> 6066 bytes assets/water.png | Bin 0 -> 5070 bytes src/main.cr | 351 +++++++++++++++++++++++++++---------------- src/naka.cr | 203 +++++++++++++++++++++++++ src/noise.cr | 105 +++++++++++++ 15 files changed, 529 insertions(+), 130 deletions(-) create mode 100644 assets/cactus.png create mode 100644 assets/dirt.png create mode 100644 assets/flowers.png create mode 100644 assets/grass.png create mode 100644 assets/haunted.png create mode 100644 assets/rocks.png create mode 100644 assets/sand.png create mode 100644 assets/stone.png create mode 100644 assets/tiles.png create mode 100644 assets/tree.png create mode 100644 assets/tree_pine.png create mode 100644 assets/water.png create mode 100644 src/naka.cr create mode 100644 src/noise.cr diff --git a/assets/cactus.png b/assets/cactus.png new file mode 100644 index 0000000000000000000000000000000000000000..b53bcbb76cffa957b9623041c9757ad19379322b GIT binary patch literal 5577 zcmeHLd010d77t}pRy!`>g2n(UI?2nvk;oR7Ai=QMs-?x3nvVeB5TE)6x zkuHLxSQji`L0fbv0v23BTt{iu0uCyUR67c-LPzV|ga|l;wtnCAKjBN@p8Gq$bIgIo*3)xG0B5OB6&;WB+20C7~5-<8+f1A`xqvXToSCQ3yEadL^fckTAPKC35x+C zBWA6dL^5AK_4*qh28MV&yg4q%B`9d>@lvy-#ru9=u_#TKbNfJB=;VDz38&dbam?T} z<>ih;HT|A`h3jJ2lC8CWs}7QOu8pYrVX=78rw_#YK5kgoR^sbFV^#YXd(bOkhbz({ zmrM7rwdx};z0>I<(Ok8By|2d~Jyh*1cq{udUU)xeQN@9;9#`Mq8`}3M?UfT{Ys~At zd9kNEe>^_hXH*(7(bU)`GqR)8_(AT=d|lFht1Gj8eZkb^*q=TN zu3Alpi)<}S z=}|f3255)bL0B7g1OOaCZtE^2N$J%U9+aAvfeF4DdX_o zj@vmyR*aS&{b=!zC-(nXL)@ufH8rQZKUG(5EuYjD@}TbRss)qRH2vh6{&~Zc#$cCq z*Cead^0h0snf*P;rIrkJVE$dXK0`hBvss%;C*2ye`@G!CBR;S-ad&ol^V0I-r2aVG zvRx$;&p#S{qoqJU=fOd}Z)&;a*X`9IXUiHJ_80@)Czmi|A0D}`K|h4i>LyrOK{ zN8itLMUc5}d)jW^vJ!%{{<(kccmB%^AKZ1v#RG=vP4jpA`;}Su=B~OM7V9!9D1PzL zGX2n+8B63XQ?tFCZ}~pdWWEJ>jSl~VQ)a@r|(-ui_g~Y8jTq9wij0h?Y#ad z?f4nYWqaPC(zPk&HG#p?w%2u>DQ~aEMlqlR*5(V=$ow-}&%QOaS?&d1TMD>)O5?ky zY;~*Xvo2gF`T69j-0Jr<;dP5{oLykW(3y!fsy*^2D4v-j7fO;uD?-!{6hynb}ggmH5pb;oYIaAqZbZ*%LYbci{&XZraKd7C>jLbEsT zIObPm4mBS~R#xR!?y!V#ZWNIfkG@istga~Za&PLHoRKRj2}?nRNy*cSs~r znArd;GZBKUKr~vtnDVcKhbUyNN=$j3BcsaX zDb!RYA7SyBJSLmLgojbg$F!gy;H;5O!eR=zEC!uJr7PGlm913MseBmlsSqwgSMzvu zHE0uC_1VM}M#!aN3X?`3vdFVwLXBcNF(pB(H=2hwQnflHg@A2BF*x8r944R50D*W6 z-Vo?D1j9k8+c+6?8rQy|QVL@LAq*-`tAjNNWYBBu8$di^6o$YAilw4xmY8BY64~Zy z?;o`M{!Jn;W8rDUKc{qN%$f)27oV1x<9GUsFS z6>lR-;`uxyfd`qA!C=D)%p(1br2Y#h8mPoL;EQ6GVewOtcLr7i(pt4~w`uGfif+`Sj zxO}FX$yG82s&$YZN7aN0#*hdN=o@g)pl$8%x7fkLzWi7p1!ar1^f)0Xvpqpmfvz84jb@}LJbx$Gct(zCjE26j{qEC zNYE+~J&r!_=))lc*EQ_n2hKcT0}oa3jD`ju(vETkm;Xn8IojZl3_&Kp4Dv$yzLe{w zTrZ@+3xQu&*GsuxNP!muzpSqRn_M14AM+7C_yg1gKE&4-=sUqb0NfPwVkM-|R0P9o6)Z7(PD&aiA?bSI>;X!lM}XK!fy%Af{kVCs}gB2xJ$cYfI5u{?at z`uA$|3mQ~ay-n(T!nnYTaHf8>$JC?%f0pb4r*Q-ejlIA#NUfD!GwyE#Y zFI622lHR+<-QOI0U6E9kMh#!Us>xq^uA}_V!lW%TotDm=t$Ck+O~i5!y?28370XrY k+jwI4sRONlawg?E`3sA_6!BjV2HBFN(TS3Z$R*4F15||_@c;k- literal 0 HcmV?d00001 diff --git a/assets/dirt.png b/assets/dirt.png new file mode 100644 index 0000000000000000000000000000000000000000..525d9ef17fbe57fb3e8855f91d0b86aae1821d53 GIT binary patch literal 5162 zcmeHLYg7|w8V*zuQ7W#A+FD|Nk8sYNT;b#>R)?N*DRE#B}bb?wD^Y}s#;2%(F%p0oW2oCM~b z=Y78KeZKd3hp;9+by{RZOoTunh%`>uXTqOR{3|>RzK?#`feQpL#e1`JxJ=xQ&E}yck53MJkWaoN%5HIuNiH_ zk~_oBe&Z}&IlkqM_p+ooe4MdFAk>Meb}Z7Uu&eu@;F6J+*jDalR0 zx_t4tjFQ&U%d5*8`^5Jxf45Kb)MeSn%J1fmjShM3!B?efLsr$sLD(BZmks~@hs!3U zguER!?DU#S9cSy(N{9Y6Ve(8}(eAK= zm)G`>XfG zd==|>vU&@Y8F0Me|=lTmuq%?doOH$#?c~t z@sRKToqD$8gR<+x`u({5y_2v1P5ahsOZU%$u8Ja|lX6TmH3VfB;}*&c#2z~hohA@y zr+8?bC;%K{26?1ICu~1{QizZio$z-G6KbN9Kt4I$%YZCzYBu35Ak-G&l=uj(M*|7$ zfWr}w-R5959-Yv~tAW4sVTlm&LAU~)FvpaRBvA}NLvyWL{9OiVF(5=^aDOHipqDiuM5h%Iz*xJTq*$MO_@4n1HAhNL-? zav(e>Zl;`^PAG);kpMZoOC)<>Ft7lB?W1Qoi^K>o$;=ySNCPpQ8d$$ahOLldw*JB2o4F{Se6R?z%AwaSVIJmLFi?UoM=+fjD zP(XM$0@~(egE#GD0Uz=eZnIb=uqxlygBP7AFhsubZWwNwOv%xWf3 zxdoWxf+*4$68ZtR^|X^Xs}+S#fL#(NCq#0EQX#@ExI%=*Nv(uTu0-RcsE>*lQ1@IpLVdj)+dO-*OT~oKG0!I4N(`T~+fp{$c3i0fo;)20`oY9or z?~4JUgH(MdOuYFRhBA!$glyG0qUqz2kGC%m{ouo?V`}z?P|ZtEUE8(xx?-xnY+cQO z9QtyJNv6a~PA^+IGH=kw`TBLE)}IzswUkHx**Lij2x{NT36Gtyp=RQ@p#n+xu$#y0 SX54|~0%LNj{y<`G@qYpSZdQE& literal 0 HcmV?d00001 diff --git a/assets/flowers.png b/assets/flowers.png new file mode 100644 index 0000000000000000000000000000000000000000..57efbec1bfc264ed2e4732eded8f988e807ab48b GIT binary patch literal 5842 zcmeHLX;>528jgsF2rhN4h^7IHirEuFVj=|+Aca6K;DV?ynM@W4nJ_aXfwqcPtQ5g} zML`g>Vo@(D2wEsow2Dv#tB6+Is2f%(DB@C!OX(y;mV2qK&%OO;@?<&RdB69Z^M1?B zGbz#_KL@)hb_4>!LF_LK!GFh?KDIXa_w+~I0D)lbml(DP3jqv79Re%V8iJ8O1?UTFP#-rBOwnyV=0Y6-gO7|Qccr`p1b8U{Rsh#8fO|Wg~^Nwpl z-Pe`eVGr}^UyiX~KCes8v^(2=O z%6+cn`k*CiD4AJa97Wm8z02d?MfAmYo&>4W>VJ#owp~lj`@Zy^2?&eGA?NfPIKAuR?oxP%%5FC>le}KaK>SX4Kn)Rub^Rt%Bpp_OS6-O8@Ev^NB=b3Fh%*xYX>xE#A`r5w||RTAYo_2TZY z>g=7If0Y}xdF76_``7LZbkgLGOd5A@vm>}s8R@1k_KhB58+UY8gG<(s`j5O`>|Vci z`CLQatoH7@X>-L!Sf+rtKBf{W+cRE!xoj3l&@%W4OU6JRd`x5C#o%2pa*~l(1 z*R~OplFB~KPHC@lYO!Hv_#S#^Wb~Y&LASl^tv>Whe$X%%_WJqcT9YOSVJ>QvE7{A5=SO{(RFd(YmgOvq)x^IFfXToLOZ{`847{hudJ zXlPlkP5gjHm$a7WL-#y(z5lIaJG-dTIjXRsvZ*V?S=Qz7-QBG8sijfp)=WH*Rxv(z zJmW)sK*0>c**&>rXpfP;Cw*zrB}+cbI`|ZEJ2Z6CiaU=+FWt$cYVQ?I{4(R6o1bP6OK@J) zxovT{?St?e>&eLHkDc-#TeZ5`j4o{4u{Sppt#jYoKv}TW`gje$s^-^WvS;t!f^&t; zrI)A6^xvnp@LwbxE?ziN)ZX@@u?{R<4Blx-{S>>sy({hV}>sY<21)|Izio9IXog@h0@5Ohl;{H^5RpY;QD|h5QLSf?=GhVXh+M%75&Bvv z@R5L|!Z00=N;Mb^6a$k2BT6cr%jHsO3@U>`#t~#RL5l%KvKDnVQS@*KArwT^I!p~~ zi6%}!2FGIp5(%FtTFCKPP-Mi7mIX{>Gd+sQsbYMGjs478P+lsYXCYrBi6s*w_IcC??YPtv9F#8isdvY6yhF@dyZs^pF;F?!PFCg+P6p z`~?aeK7c@{iAVcyIv#~gCiB5Has?GXm3iy^7xfcMq`f|-I4ISzIWwSj0jB3Aa$>YB`U__278B3t2uQ zUm=|?VtKG=KHdxljm>3xu)Rd|e(qu|iUC>>dgZRh-RU$rS0R3PtQFU6Sc>))Y=Yv211|wlGtPzk*TOyh~ z&Gkd%TPlMW2!nvBg3Mq@ZYnNId3gg$s(Hw#z7G6fOra{+p#5t+eb8POF9b8dNK7yi zEQ^Lf?Daf@fqR)k@WzH>NP_rJ4)q^6eorR)M zXi`x?LIFJ_H-`Y%v1bZY0a_)5w^mD8yt1nY%T|yklPlOPGQbA$Y<1^&kO2>_J6QpG zFgaWfO{SoG3}i=P1!e#c$V-XW4IXE_Y|U{d&NL_4tbu5Q3c@$(M&t5mG*VA^E#dxq z&f_j)(djG>gREf57-SaYftL(}E+;G8x$Xdcm>o92m4l^hzc z(`nQYGB5#O733e__NZ+?|y_M^Y6nG=>+w6KP z*BdGDM&P&E^?#GguJ5r0q{VNj4fsQely^n8_yY|aS%9CA&~18uQMD@rw~WyFhob}n z#o6>3Vl42~9wSiZ-iy-ORJN&Z#raD6<~yjQL#ydoN(o~?vDHAKP1k* z<2kk&x%*q!4ntctl7FnKGGbF*V0YfPvx1*=UJUco)p0l6ol+ROZ0|4ov;XBvkB|9o zL|tHL`NXl!0R`$LS<_L4Z}Z}u*~y=cu&tsm5P`dEicpuKL%{T(Ns0ct5l{B#KCt?r zJvFVw?gPQRO@}*VOv44zLANd1T09ixxLd1e)vBN?tahbUE31g&gZe72=&G!;YIW}<5n>l@J!ktLa1yxR z{e8cCzu)&5!XMJ7Bt?Xc2os4!5yoVFCipo>c=df1ypOs21Q&^3Nh!?E;WKeJ!cvcLt8{5&zZIln3WI5_g}J~Tc(|4BWx>mQ1ZM*+Q&8NQ4R6(sn%@zel1)n zn|na@PJest?PC|TkKS9({&DPP?~!QxZu830aI9wLmgS#Hjy^0r9fzqOIBRk%duK|E zT5~Riw?EMQ^}7?dc0I^-HQlC`PN}_H(&BAzjG4V;F$^LIzP?GA4^BGjq~oFFRH(ulKNG4SboNs$)(!<&BZCD4X>|l zDgXYgZTL6a>cY$s3ipl?;|E8c{aCF0?LS&StjaVjEgq;!zIp_8Ne`9&bMfm*sRrHgVXZ z=H0}Q1?jUjCrd9h9h`d3HZ8YLZ`a1AZ3n8_h7R6)!fOBf<%z>Kz1{yTmA~o>N))}2 zF(8#Umx`f`lpVvjzc#jLKwO{V?fUZx3$nk=_&ztXM0D8OacESz=KVjxb4E!-8%pXH zL{0AXmtnv9H}P$T&R`(YPky>_%k;@5LsoBGGjWA{^M^T#FG!1{Y1#6mX=#p|>#ND5 z3nNpxBfqY?_R-{b8$ufTY!6RdQ*~E5ui2Hhx2@({b@rOH6;ty!zg2y8#t1ci`msbA z{>|HtD`l@#@O8Q({&Y)HTuRbsuGVe&Y3KIbZ>~4mGpZ{R-Iv#Al^g7XmUAD?&s=oo zMs4pEm-M0XL6?#t$0AYK+EDq8Ys0*&Mt;5X>(fJylqF&p6(j22L`K)0X(jI)Z$|Gb zyPeeB@jDhTn-}^ea$Ld|HP@7#I`Sy7xL@9lgT)OdY=A2D&cRuGine<19d2wmMs~E- z#V2`6 zBC5BppD=vZ^u`HyiZ(nsbA0;z`%~}hm#FQJXOAqbJoT^4rW&ZWe(>m>ZEg1=7;NSG zk%h^1ySx{*k4CjOqWfB79>fp7-hscne?!Y`?l&b#5ZW*kbl4yobXATi6(bnC6ek%o zCH2^ypzB1U*m#c=chf9=pxKVIHm6$BTjA1v4y$d=NfgE6y>cLpp|~Aca&RMI;6fU4V+? z!k}1|v|yR~!~g|YX~lUw@5Er(?RHDuaw)@FVVOpwfe{o&Q3*guIIn}pJrV~ODNy)1 z^b|+1w3DY92PAOfX2!*9#bU4z1<1iJVekNB-~eImqvv=MHiCr$!~*~@iXbr(1eKr~ zIM^TTnoOPE4lbY~&=dCHPFN;IV7vVp4~{n!be-3&2bT?o8=OgTjEf~GLjmRBBZCj+ z_)Mxxlb=BW;Li{^Z7weO(k_k?OuoQvqy+|1`K}&(Xp+&C-svOc!AjemJ`VvMj3kLp zoYTeHd>E2|DH~-6k#K-n_6#2QcRF+%jE~%>G^S?=m%w7wYsJ7KC20~vG%@N)D!oEy zNYu+@21Sel(M>{8M5&R-C=(2_Aa|pK<8cQ;J$0vnyBZ-?W~Ev#K~=a+qM#^LqCr8X zEo!9_L1ef}Md3jdX)F!;0k?H`6H-MeRVZqfsFi9$q9Dx{i3V3764Gq8XjEo}N^T*2 zRDzgTLb_2aMx{uXCEbSe7KXKJ#i_Kz<>}hUrtMS~j|)P{lnR+lE!U`H5RFWxk#~XK zqF4@;y1*$zq$=MANnnY95C;`U+i@!eI~`Wv1`rR^u@ufTY&OH#v|`~(kl^X-A1F4^ z8CWVq;6exaz!WJoSD?Kn;a1qUjD=qW{vW2SJjU(#c|2XvPL>3gcQb5$8k=UGLlOLo zdAb94GG&6n#__Dz_!B|>2p8+G#AM*hu->5av#7cLm4Eu!XrELNVh%5$ z{6zqC^luS)xWh_;(Hdxrr*^uVYE>wes9H^^B#0VJJ_VxENX%vxDxnCRLKTD@QJMXj z?_}o~3-88RD!~f+2ILvEtuN2ecwdo4`^upc?argXB_|*n3_-;H^g^+)Fg>4!7b}bh zlL-qJAfSn!VrdXvBFor)vHJ!UMn1o1(HHhCkOxdItUDP3JuiE%f1omjv4$Ci%Fl8+ zoi>_cpH0})0)7rIw#y!9mY;GDhWWj9jMEEdMjoH$cD(TTZh(G;EQKSl7pV$^rfWb%PJ_OHa3TfPVnIYMz{=7d;Wa%c?i629|!#7L@4`a{DzMmxi|c_Y!>CWlj;SYZXw45kc{k)h-J4^}KI?U1|L+f9 zEMMNDT(Ele>ay@0XVWs1TqRppyWqW1)`2JT^hKjfYDFJhD2~`@oVbb-RsA`qZ*qSRNi4=xFC=N1;$0(rPAr4ng+ zl!}@#HHBjO@lYX*ch7Rj>Ay2E<1e1hS*>v_Zbr8FSxww+p4RK9vW@j2j`NmzKCtaP z|E&J!qGz?op+LLDw{K3JUcSb^AWMSRr99}Y{dU*59`^qH3y-tswlzFyWp@Q1o?Biu zesV$P!NU9|^ewu7rdx60jN&>|M=5&Z)L(Bs3`sjPr{w~Ev7`}Aop#t~oImID+Ttne zuI)DNuWLE(F}5W0Xwf2Lv(wBy*R$&*X{!x^x9+X`)AWbuyjWw{};1)uiCoP1NV$ba_J&667(maM2aQ_wJ> zAV#ud>CK#Z%enTI*C2gsZBfsa=yF^32}Vxr))gg{ZHmgq@iX(jU+6H^ZRy%xyF_;- zgtT-B!j^1s6$x_6Sb6u`OxS~<-gn<|eCYFT;gwQubZ&4!6E#nwz4|X}>ecAbl$%Dc zz9>PLYm8Ht@WSSAj$GgO-LbOF?Q~x#qkWrw+p3*MFBXj1YFjhu=obg94xWgGD!DF5 zfV*q&L@C4Pgw5C;vsZ4*UfJWKU+3`iXNV|8cEhc-rnXY$pQc|a;PW~mc^5EKxlPh_@=5G3R3gp(_ELml9 zK-M*r!(ryyH* zWyr7g+aGMw$9kz!^Yof3?xvf4Q(b1~CZ+KUHep%6pxw=F8;=LdGo9UvD#T8TJ9{?0 zSr$4?Y7{TD-t~z0s54J=%yfWRSBXEa;is2>RMquf&!h~&r4Jdw?;pL>^KZG%uKhCY z?uiRYIqrYYUc9$BO#F0qUf6r*`j_3u9?{kN=5Izbqh@@_?(3+|ztGfFk=_2*t#j33 zS1&#F+jYN&UVgdd<7WKprzg+NOd^6WZPAFu8@kfdZ_im$C+nLPwD?rgvR=h5m%enH zC3o7zVYzD`k7>oUy9W=`sF+Gji{>j>3cWv?fCZ)FXk>b5 z6q25VKq{KAkDbUQ1O&+_0aH!M8m&QS64T7QLhw5oX40r;2$3YF#VEq5{yH3`@)$e@ zi!L)^sT`V*9aV&@)WQg9fQ16wiD?N0p%*flMx&8o7bo3L&P^z&O=H4n`4~2?Q+@koRVK1EFHd!3_`W z2LPBH7R#H?;?Ow|b9g=&RVW6twFZldKu@L#)-%}*7Be~dg$x5BOC6eT#0*0eXsXNz z)Syej5mc6nY6*|wlNyK!bV!pwKmp({5a=~2hT)4&F`#6~ym5_6%>=tLFMar=fpSIo zU=Dc>@mR9noI#?8BUQ*CPM?Bn%or8IL^WtK*ogr!vtPi2{K13~H)AF@D=n1jkQ90jl*CKMZz^Oq1NHaVp<5MO)(7(L}AHj zBmt8`vH3hUhtGj{Tt17(gS>}8^HAIXN}c3nvlw3H0Tm()0E958I4l{CN11wUymGX9i9}1hba?Lgm^X2NZ>)H2+-IJ1fC}UolX4)CmN{4AdsuW(}v9- zi7pzr8|Xe7%q$g^YIeL3Mo1OKGy|N9s?0|KbQ~B$5@2mS3Rs6^Ep#znTzc07tzK9 z6fANM3lg$ew1MNLikM{ge7-#q*&Y-M;cx*gnn+O`1G@{rb;;(vn;Vr-^>WX0Ug=pN z59nONkz}xVp8e8%OJ$O6O(-WTKQcwH*I+3A;(*idq=F+cv|VJbFB+$?(Xh6 z%zxIzCCr`jUB}qZYFGREB3tcMKCX%UD=!KwfN literal 0 HcmV?d00001 diff --git a/assets/rocks.png b/assets/rocks.png new file mode 100644 index 0000000000000000000000000000000000000000..e4d7d4e188baf3d6a637d86b36c60b9e4c3813fe GIT binary patch literal 5380 zcmeHLd010d7LTGhY@&5TMl?haTwXS^yhH*85|&B;fhtr-e0g~Z53>34LV`+NK&ur* z)DN^S^|PoQl?qB3WceIKtfh#>rPhU+78Fqt1+`j6hq+kv-H}3}xI~1DW5N3v^Ytft@H*{rFG8W%wJwiOAY&0DRYzzQxC*0^ z={k&xnQ#S#V!Hld!J_>8zRoYM&QdspZ@5Y@bM>906~D$UZSm?%6E=&I^k=<77i4)I z&XT39>b?795}kFlkS_kbieJvbw>o=%_VT)tCwzFn=4O_U$RqIYMI}2P{JZ_e@eAKq z{%h^Nd8b78XKugkLl0XC?=A4i`=qL)g^*)cA1^;x`U95nV`I+;iJF4Pk5AaIp6~G@ z(G$-vs%g&kojCc&WWUbr$`Z#fSnGGUHGjO>!=uHaDny7VyD#2Q`ks4^{S@s6i7L1K zV=8j1;X2fP$<=$RXTZ{{D{`v;mSId3ZRsxM8E({XUs^t_-oqt#>?6aL+9?jxWt&E3 ze^`7H8Q*edc`$d@o}={lvym3(n08l}^v8xmjN8n59eFQL*vi zlqWI#wlxV(_RU!WS>`oW<)-sp)ny&GuAOe2?0sUI%V>juBHr*|i}T?{*Gy%bMZ2i) z?O2132%Nmrss3X-yN=!Q2S;m;KBfB9Z^+@UIaCB+yOIo1PbX^azN`rvmUwvd({Y^r z>yMv^`c`n~FS;i0g6gOD_B;!C-t+}g9F*x4kToX6QE>gsCdssrY*yWFuf$Sj!Ai&N zvJYk|N1WH~DG}Dv?nrr+ciVWU)b2^NqOTaqp@vCcWBh9x?PJ| zVVM=HG6n9Jp|*hTUmQ}o7gmj*>)?J!w=_i+hGx#s-{9KQbwSxvl-cX?QaR+s1v!vyrUxPpQ_Kmf7 zb~b)`Oxal`9a}VUPiZ41Uwn+JX3uY6cC_Bc=d8Ood_KD%reN>EcDU}yt$StjyqhKz zUQ~{Df4SmT)2_lt-ZCfmifvCSceg(X{`tt_U*eaP#s3x^aqZgV)Gz+f~d^VdIG^zzcriy3@(im!pmcXbSI)~1LBqltKO$%_O`V(@6 zFjgFDqW~ijEtw>BLI%TVG}4WJbS`XQx|zbtA;$D5 zf$K=>r$l zld)L8CclFMz+WTKsSNsoK^yd#*<{(cO0HmlU0FgOxM)~}G^WqTd=5%nt+RNT(F2ik zv=65<5Go5sjxsP6rUpCF17_B1c;MgXFz9A1Zia^6!v4o0>uD|XaWcIfT#wGgac!KTs{wj_%O_aI55nFWH4V2$x#-M&1LaH zKs*bTSxjM2OoWKWrZf93F)D;qXbH857Kv*Nrv8a|T#dz%h*>BWm&0On`Fsus=CWay zpdYjVBlIBE&73SI-MWw*6@~&r1Y{hpMwA#sr%_rwfOx_X0z*hG5wF#%L^N|qRI{h0 zeyIMo$`D3sQN&z97BD6^7niNPf)ORdGW2J>4g5b$amiYv=J)aRL;F~Q2-2t}Qlp7z zSqg@dZ|4~d+{Y9P8k?RZ(j$IjQ~!nYw`O8E@YNFO1J;kjmRU#E?xVsjQcTiJ3R%H;9n5R0$iLL5E^fe@JE2Vp#f z%R{+bxg4`5d>^}Bt00XCfdwf+-GFljWotQSs;?!j(8>^r zqnJjoeOu@QA=cv>aPtE(TW#Q`3hvR2zMHfyUCkB!2R>|N@CU8{sD~DLCw+(H8j|as z6nH1_P<9Q;^-c=B6L=`Q{%>+Q_W#VsG~f%U5&VcBwr|))@CSgsY+jg{(rbS1t3I?1 zSR8cWiFyi!KHYo`GaU#_1IAHggj6!B#mUaqVFaVGtP+^kM~H*s;qTBFy}f~U-m5pR z31*6&eN&U4C;jmKYHqPx*GO9AwoOO=YzH2-dz&lWD%{=O@~#yBbjkVB2G`;-t?QnB z9+0P`1mq;tQbhZ*&qFceow~b_EIX%wz;Ug^XArw{J7Q;$Qf9VOd3gm3Ts1BT&AYyM lazk*+MF~GE?_!5DW$M=OGx@rf`ye7pgd|G*Rq)ca{{a;}!S?_F literal 0 HcmV?d00001 diff --git a/assets/sand.png b/assets/sand.png new file mode 100644 index 0000000000000000000000000000000000000000..464f638ade11f9120436dab3c801a177956a0299 GIT binary patch literal 5216 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 literal 0 HcmV?d00001 diff --git a/assets/tiles.png b/assets/tiles.png new file mode 100644 index 0000000000000000000000000000000000000000..081fc9b2caf6fa781cc0bafff6500fc9ccebba6c GIT binary patch literal 5218 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 Idd7V7A6|g+Hvj+t literal 0 HcmV?d00001 diff --git a/assets/tree_pine.png b/assets/tree_pine.png new file mode 100644 index 0000000000000000000000000000000000000000..239edb92076eb454d73a15d84b9459aac7eb5499 GIT binary patch literal 6066 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 XmU49G&g#psjtH8Im-8{l(2f5Aj&=`v literal 0 HcmV?d00001 diff --git a/assets/water.png b/assets/water.png new file mode 100644 index 0000000000000000000000000000000000000000..2a296bf797a8dfea7f193601ed8950fd4ca7bf42 GIT binary patch literal 5070 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 literal 0 HcmV?d00001 diff --git a/src/main.cr b/src/main.cr index ca0e4a8..a9db35f 100644 --- a/src/main.cr +++ b/src/main.cr @@ -1,167 +1,211 @@ -require "sdl" -require "sdl/image" +require "json" -## Helper code comes here +require "./naka.cr" -class Naka - def self.init - SDL.init SDL::Init::VIDEO - SDL::IMG.init SDL::IMG::Init::PNG +class Map + @save_directory : String + getter altitude_maps : Naka::Noise2D + getter humidity_maps : Naka::Noise2D + getter evil_maps : Naka::Noise2D + getter flowers_map : Naka::Noise2D + def initialize(@save_directory) + Dir.mkdir_p @save_directory - at_exit { SDL.quit } - end -end + @altitude_maps = Naka::Noise2D.new.tap do |a| + a.add_octave(32, 1) + a.add_octave(16, 0.5) + a.add_octave(8, 0.25) + a.add_octave(4, 0.125) + a.add_octave(2, 1/16) # wtf, too smol + end -class Naka::Renderer < SDL::Renderer -end + @humidity_maps = Naka::Noise2D.new.tap do |a| + a.add_octave(64, 1) + a.add_octave(32, 0.5) + a.add_octave(16, 0.25) + a.add_octave(8, 0.20) + a.add_octave(4, 0.15) + a.add_octave(2, 0.10) + end -class Naka::Window - alias Flags = LibSDL::WindowFlags - alias Position = LibSDL::WindowPosition + @evil_maps = Naka::Noise2D.new.tap do |a| + a.add_octave(48, 0.6) + a.add_octave(32, 0.6) + a.add_octave(16, 0.3) + end - getter renderer - - def initialize(title, width, height, - x : Position = Position::UNDEFINED, - y : Position = Position::UNDEFINED, - flags : Flags = Flags::SHOWN) - - @window = SDL::Window.new(title, width, height, x, y, flags) - @renderer = Naka::Renderer.new @window + @flowers_map = Naka::Noise2D.new(1, 1) end - def newImage(file_path) : SDL::Texture - SDL::IMG.load file_path, @renderer - end + class Object + getter type : String + def initialize(@type) + end - # FIXME: We’ll probably want options for scaling, rotations, and so on. - def draw(texture : SDL::Texture, x : Int32, y : Int32) - @renderer.copy texture, - dstrect: SDL::Rect[x, y, texture.width, texture.height] - end -end - -class Naka::Timer - getter start - getter last_update - - def initialize - @start = Time.now - @last_update = @start - end - - def step - now = Time.now - - dt = now.epoch_ms - @last_update.epoch_ms - - @last_update = now - - dt - end -end - -class Naka::Event - class Quit - end - - class Draw - getter window : Naka::Window - - def initialize(@window) + def to_json(builder) + builder.object do + builder.field "type", @type + end end end - class Update - getter dt : Int64 + class Tile < Array(Object) + def initialize(element) + initialize - def initialize(@dt) + self << element end end - # FIXME: Split in KeyUp and KeyDown. - class KeyUp - getter key : LibSDL::Keycode - getter scan_code : LibSDL::Scancode + CHUNK_SIZE = 32 + class Chunk + property tiles : Array(Array(Tile)) + getter x : Int32 + getter y : Int32 - def initialize(@key, @scan_code) - end - end - class KeyDown - getter key : LibSDL::Keycode - getter scan_code : LibSDL::Scancode - getter repeat : Bool + def initialize(@x, @y, map) + p "Creating Map::Chunk" - def initialize(@key, @scan_code, @repeat) - end - end + @tiles = Array.new(CHUNK_SIZE) do |x| + x += @x * CHUNK_SIZE - # FIXME: THIS MAIN LOOP IS **NOT** READY FOR PRODUCTION - # Update events are still missing, and both Draw and Update should - # be time-based. - # Why having a `window` parameter instead of making a Window#loop method? - # Because we may want to have a `windows` parameter in the future, although - # unlikely. But still possible. - def self.loop(window, &block) - max_fps = 60 - max_dt = 1000. / 60 + Array.new(CHUNK_SIZE) do |y| + y += @y * CHUNK_SIZE - timer = Naka::Timer.new + Tile.new(Object.new "stone").tap do |i| + altitude = map.altitude_maps.get(x, y) + 0.5 - renderer = window.renderer + humidity = map.humidity_maps.get(x, y) + 0.5 - ::loop do - exit_requested = false + evil = map.evil_maps.get(x, y) - while event = SDL::Event.poll - r_value = yield case event - when SDL::Event::Quit - Naka::Event::Quit.new - when SDL::Event::Keyboard - keysym = event.keysym - if event.type == LibSDL::EventType::KEYUP - Naka::Event::KeyUp.new keysym.sym, keysym.scancode - elsif event.type == LibSDL::EventType::KEYDOWN - Naka::Event::KeyDown.new keysym.sym, keysym.scancode, event.repeat != 0 + if altitude < 0.25 + i << Object.new "sand" + + if altitude < 0.175 + i << Object.new "water" + else + if humidity < 0.33 + i << Object.new "sand" + else + i << Object.new "dirt" + end + end + elsif altitude < 0.75 + i << Object.new "dirt" + + if evil > 0.5 + i << Object.new "haunted" + else + i << Object.new "grass" + end + + if altitude > 0.35 && altitude < 0.65 + if humidity > 0.4 + if humidity > 0.8 + i << Object.new "tree" + elsif humidity < 0.6 + i << Object.new "pine" + else + if Random.rand(2) == 0 + i << Object.new "tree" + else + i << Object.new "pine" + end + end + else + if Random.rand(10) == 0 + i << Object.new "flowers" + end + end + end + elsif altitude > 0.85 + i << Object.new "rocks" + end end - else # FIXME: Most event types may need proper Naka:: classes. - event - end - - if r_value == Naka::Event::Quit - exit_requested = true - break end end - - break if exit_requested - - dt = timer.step - - r_value = yield Naka::Event::Update.new dt - - break if r_value == Naka::Event::Quit - - renderer.draw_color = SDL::Color[255, 255, 255, 255] - renderer.clear - - yield Naka::Event::Draw.new window - - renderer.present - - sleep 0.001 end + + def to_json + { + :x => @x, + :y => @y, + :tiles => @tiles + }.to_json + end + + def get(x, y) + @tiles[x][y] + end + end + + @chunks = Hash(Int32, Hash(Int32, Chunk)).new + + def get_chunk(x, y) + chunks_list = @chunks[x]? + if chunks_list.nil? + chunks_list = Hash(Int32, Chunk).new + @chunks[x] = chunks_list + end + + chunk = chunks_list[y]? + if chunk.nil? + chunk = Chunk.new x, y, self + chunks_list[y] = chunk + + save chunk + end + + chunk + end + + def save(chunk : Chunk) + File.write "#{@save_directory}/chunk-#{chunk.x}-#{chunk.y}.json", chunk.to_json + end + + def get(x, y) + get_chunk(x / CHUNK_SIZE, y / CHUNK_SIZE).get(x % CHUNK_SIZE, y % CHUNK_SIZE) end end -## Actual application code comes here +class ImagesLoader + getter grass : SDL::Texture + getter stone : SDL::Texture + getter dirt : SDL::Texture + getter tree : SDL::Texture + getter pine : SDL::Texture + getter flowers : SDL::Texture + getter sand : SDL::Texture + getter water : SDL::Texture + getter haunted : SDL::Texture + getter rocks : SDL::Texture + + def initialize(window) + @grass = window.newImage "assets/grass.png" + @stone = window.newImage "assets/stone.png" + @dirt = window.newImage "assets/dirt.png" + @tree = window.newImage "assets/tree.png" + @flowers = window.newImage "assets/flowers.png" + @sand = window.newImage "assets/sand.png" + @water = window.newImage "assets/water.png" + @haunted = window.newImage "assets/haunted.png" + @pine = window.newImage "assets/tree_pine.png" + @rocks = window.newImage "assets/rocks.png" + end +end Naka.init -window = Naka::Window.new "Test", 640, 480 +window = Naka::Window.new "Test", 16*128, 16*128, flags: LibSDL::WindowFlags::RESIZABLE | LibSDL::WindowFlags::SHOWN | LibSDL::WindowFlags::OPENGL -naka = window.newImage "naka.png" +images = ImagesLoader.new window + +connection_font = window.newFont "Connection.otf", 30.5 +window.set_font connection_font + +map = Map.new "map-test-01" Naka::Event.loop window do |event| case event @@ -169,7 +213,54 @@ Naka::Event.loop window do |event| next Naka::Event::Quit when Naka::Event::Update when Naka::Event::Draw - event.window.draw naka, 20, 20 + zoom_level = 1.0 + + 128.times do |x| + 128.times do |y| + tile = map.get x, y + + # FIXME: + # - Please don’t draw more than one layer of ground… + # - Transitions. (eg. between dirt and grass) + tile.each do |object| + image = case object.type + when "grass" + images.grass + when "dirt" + images.dirt + when "stone" + images.stone + when "tree" + images.tree + when "pine" + images.pine + when "flowers" + images.flowers + when "water" + images.water + when "haunted" + images.haunted + when "sand" + images.sand + when "rocks" + images.rocks + else + images.stone + end + + window.draw( + image, + x: (x * 16 * zoom_level).to_i, + y: (y * 16 * zoom_level - (image.height - 16) * zoom_level).to_i, + scale_x: zoom_level, + scale_y: zoom_level + ) + end + end + end + + # Urgh. This shit eats CPU. Fonts need goddamn fixing. + #window.print "The quick brown fox jumps over the lazy dog.", 50, 50 when Naka::Event::KeyUp, Naka::Event::KeyDown pp! event end diff --git a/src/naka.cr b/src/naka.cr new file mode 100644 index 0000000..febfb97 --- /dev/null +++ b/src/naka.cr @@ -0,0 +1,203 @@ + +require "sdl" +require "sdl/image" +require "sdl/ttf" + +class Naka +end + +require "./noise.cr" + +## Helper code comes here + +class Naka + def self.init + SDL.init SDL::Init::VIDEO + SDL::IMG.init SDL::IMG::Init::PNG + SDL::TTF.init + + at_exit { + SDL.quit + SDL::TTF.quit + } + end +end + +class Naka::Renderer < SDL::Renderer +end + +class Naka::Window + alias Flags = LibSDL::WindowFlags + alias Position = LibSDL::WindowPosition + + getter renderer + + @current_font : SDL::TTF::Font? = nil + + def initialize(title, width, height, + x : Position = Position::UNDEFINED, + y : Position = Position::UNDEFINED, + flags : Flags = Flags::SHOWN) + + @window = SDL::Window.new(title, width, height, x, y, flags) + @renderer = Naka::Renderer.new @window + + @renderer.draw_blend_mode = SDL::BlendMode::BLEND + end + + def newImage(file_path) : SDL::Texture + SDL::IMG.load(file_path, @renderer).tap do |texture| + texture.blend_mode = SDL::BlendMode::BLEND + end + end + + def newFont(file_path, font_size = 12) : SDL::TTF::Font + SDL::TTF::Font.new file_path, font_size + end + + # FIXME: We’ll probably want options for scaling, rotations, and so on. + def draw(texture : SDL::Texture, x : Int32, y : Int32, scale_x = 1, scale_y = 1) + @renderer.copy texture, + dstrect: SDL::Rect[x, y, (texture.width * scale_x).to_i, (texture.height * scale_y).to_i] + end + + def draw(surface : SDL::Surface, x : Int32, y : Int32) + @renderer.copy surface, + dstrect: SDL::Rect[x, y, surface.width, surface.height] + end + + def set_font(@current_font) + end + + # FIXME: + # - This only prints properly “solid” text. Shaded text (depixelated) + # would need some additional OpenGL manipulations. + # - This is very inefficient resources-wise. If we are willing to drop + # ligatures support (is it even supported in SDL::TTF?), we should + # build a textures atlas of all glyphs and use that to render text. + # - Alternatively, it may be worth it to let users generate a texture of + # some text and draw it, manually. + def print(text, x, y) + font = @current_font + + return if font.nil? + + surface = font.render_solid(text, SDL::Color[0, 0, 0, 255], @renderer.draw_color) + + draw surface, x, y + end +end + +class Naka::Timer + getter start + getter last_update + + def initialize + @start = Time.now + @last_update = @start + end + + def step + now = Time.now + + dt = now.epoch_ms - @last_update.epoch_ms + + @last_update = now + + dt + end +end + +class Naka::Event + class Quit + end + + class Draw + getter window : Naka::Window + + def initialize(@window) + end + end + + class Update + getter dt : Int64 + + def initialize(@dt) + end + end + + # FIXME: Split in KeyUp and KeyDown. + class KeyUp + getter key : LibSDL::Keycode + getter scan_code : LibSDL::Scancode + + def initialize(@key, @scan_code) + end + end + class KeyDown + getter key : LibSDL::Keycode + getter scan_code : LibSDL::Scancode + getter repeat : Bool + + def initialize(@key, @scan_code, @repeat) + end + end + + # FIXME: THIS MAIN LOOP IS **NOT** READY FOR PRODUCTION + # Update events are still missing, and both Draw and Update should + # be time-based. + # Why having a `window` parameter instead of making a Window#loop method? + # Because we may want to have a `windows` parameter in the future, although + # unlikely. But still possible. + def self.loop(window, &block) + max_fps = 60 + max_dt = 1000. / 60 + + timer = Naka::Timer.new + + renderer = window.renderer + + ::loop do + exit_requested = false + + while event = SDL::Event.poll + r_value = yield case event + when SDL::Event::Quit + Naka::Event::Quit.new + when SDL::Event::Keyboard + keysym = event.keysym + if event.type == LibSDL::EventType::KEYUP + Naka::Event::KeyUp.new keysym.sym, keysym.scancode + elsif event.type == LibSDL::EventType::KEYDOWN + Naka::Event::KeyDown.new keysym.sym, keysym.scancode, event.repeat != 0 + end + else # FIXME: Most event types may need proper Naka:: classes. + event + end + + if r_value == Naka::Event::Quit + exit_requested = true + break + end + end + + break if exit_requested + + dt = timer.step + + r_value = yield Naka::Event::Update.new dt + + break if r_value == Naka::Event::Quit + + renderer.draw_color = SDL::Color[255, 255, 255, 255] + renderer.clear + + yield Naka::Event::Draw.new window + + renderer.present + + sleep 0.001 + end + end +end + diff --git a/src/noise.cr b/src/noise.cr new file mode 100644 index 0000000..9bebbd4 --- /dev/null +++ b/src/noise.cr @@ -0,0 +1,105 @@ + +# FIXME: We may want to generate noise of other numbers of dimensions. +class Naka::Noise2D + class Octave + record Vec2, x : Float64, y : Float64 + + private def lerp(a, b, v) + a * (1.0 - v) + b * v + end + + private def smooth(v) + v * v * (3.0 - 2.0 * v) + end + + private def gradient(orig, grad, p) + sp = Vec2.new(p.x - orig.x, p.y - orig.y) + grad.x * sp.x + grad.y * sp.y + end + + private def random_gradient + v = rand * Math::PI * 2.0 + Vec2.new(Math.cos(v), Math.sin(v)) + end + + @frequency : Float64 | Int32 + @amplitude : Float64 | Int32 + + def amplitude + @amplitude.to_f + end + def frequency + @frequency.to_f + end + + def initialize(@frequency, @amplitude) + @rgradients = StaticArray(Vec2, 256).new { random_gradient } + @permutations = StaticArray(Int32, 256).new { |i| i } + @permutations.shuffle! + end + + def get_gradient(x, y) + idx = @permutations[x & 255] + @permutations[y & 255] + @rgradients[idx & 255] + end + + def get_gradients(x, y) + x0f = x.floor + y0f = y.floor + x0 = x0f.to_i + y0 = y0f.to_i + x1 = x0 + 1 + y1 = y0 + 1 + + { + { + get_gradient(x0, y0), + get_gradient(x1, y0), + get_gradient(x0, y1), + get_gradient(x1, y1), + }, + { + Vec2.new(x0f + 0.0, y0f + 0.0), + Vec2.new(x0f + 1.0, y0f + 0.0), + Vec2.new(x0f + 0.0, y0f + 1.0), + Vec2.new(x0f + 1.0, y0f + 1.0), + }, + } + end + + def get(x, y) + x = x.to_f / @frequency + y = y.to_f / @frequency + + p = Vec2.new(x, y) + gradients, origins = get_gradients(x, y) + v0 = gradient(origins[0], gradients[0], p) + v1 = gradient(origins[1], gradients[1], p) + v2 = gradient(origins[2], gradients[2], p) + v3 = gradient(origins[3], gradients[3], p) + fx = smooth(x - origins[0].x) + vx0 = lerp(v0, v1, fx) + vx1 = lerp(v2, v3, fx) + fy = smooth(y - origins[0].y) + + lerp(vx0, vx1, fy) * @amplitude.to_f + end + end + + + @storage = Array(Octave).new + def initialize + end + def initialize(a, f) + add_octave a, f + end + + def add_octave(a, f) + @storage << Octave.new a, f + end + + def get(x, y) + @storage.reduce 0 { |a, e| a + e.get(x, y) } + end +end +