0$,]]ŞH@ò‰]ÿÍuÚe(²LyP)Yª;ŞuÂösï÷ôôôam}DP] 0ÿÿÿÿÿÿÿÿ2Tv˜ºÜşïÍ«‰gE#2Tv˜ºÜş2Tv˜ºÜş_İİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİݪÚİ­Ì©İÊÀœÚšÌ™‰š™™‰ˆˆØİİİİݪİİ­ÌÚİÊÀ¬İšÌ©İ𙙨𙙉ˆˆØİİİİİİİİİ­ªİİšÌÚ­É ¬˜™Ì©˜™™©ˆˆØİİİİİݪİİ­ÌÚİÊ ¬İšÌ©™™©˜™™©ˆˆØİİİİÙİİİİØİØİİİİİİØİİİØÙİİÙİİİİØİİİİİİİİİİİİİİİİİİØÙİİİİİİİİİİİDÔİMÌCİÄÀ<Ô4Ì3#433#-""ÒİİİİİİİİİMDİİ4ÌÔMà L23ÌC233C-""ÒİİİİİİİİİUÕİ]ÌVİÅÀlÕeÌfveffv}ww×İİİİİİİİİ]UİİeÌÕ]Æ \gfÌVgffV}ww×-İ-İ-İ-İíİíİîŞîŞïßîßÿßÿßößÿßffoffÿŞİöïî"öÿŞİÖİİİÿïŞİöïî"öÿŞİÖİİİföffıÿıoıÿıÿıîışíîíîİŞİŞİÒİÒİÒİÒİİİmİíÿo"îşoİíşÿİİİmİíÿo"îşoİíÿfİİİİİİİİÍÜİİÜÍÌÌÜÍİÍÍÜİİİİİİİİİİ}İİ××Ö}m××m}mİİ×}İİ×××m}Ö×mmmİİ×îîàÿÿÿàÿÿÿÿÿààÿİİİİİİİİİİÜİİİÍİÍÌÌÜİİÍİİİÜİİİİİĞĞĞĞ ĞĞĞĞ ĞĞĞĞ ĞĞĞĞ gffgwvwwpwgww`ppp}İİ×Ö×m}×Ö}}}İİÖ}İİ××Ö}}ÖÖ}m}İİÖİİİİİÍİİİÜİİÍÌÌÜİÜİİİÍİİİİİİİİİİ""""""""""""""""""""""""""""""""ffffffffffffffffffffffffffffffffİ}İİİw×İ}gwİwVv×}gwİİw×İİ}İİİİİİİİİİİİİİİØØİ‰‰İ™‰İݘØİİİİİİİİgffgpvwwwfffffwwwwwwvvvvffffpwgfpfg`pppfwgfvwwgvpgv`````wvvvfvpgpfpgpgpgw`vwpwgvffvvwpwgvffvgfffvffwwffvfggfffgw`wffgff`pppgvwwwwffffffffffgfvgvgg`vwfwwfgffgwvwwpwgww`pppgffgwvwwpwgppgffgwvwwwgpfppfffffwwwwwwvv`vpvpvffffpwgfpf``gp``vpppfwgfvwwgvpgv```g`p``vvwvvvwvwfw`vpwwppgpwfwgwgggpwggpp``wwpvwwwgwvffv`vwwpvwwwgwvffvgfffvvffwwffvfggfgfpfgw`wffgffgvgvwwvwpwwpvwgffvgfffpwwgpwwwgpwwggfwfffffffgvvvvwfwwfpwgwfvgvgvgvppİİİİİİİİİİİİİİİİİUUİ]UUÖ]UfÖİmÖİİİİİİİİİİİİİİİİİݪªİ­ªªÙ­ª™ÙİÙÖİİİİİİİİİİİİİİİİİİİİİİİİİmmİmmİÖİİİİİİİİİİİİİİİİİİİİİİİÖmİİÖmİÖÖİİİİİİİİİİİİİİİİİİİİİİİİİÖmİmİmİİİİİİİİİİİÓİİ=4İİİÓİİİÖİmİmİmİmİİİİİİİİİİİİİ©ÙİİİİİmİİİÖİÖİÖİÖ}İ}İmİ}İİ×mİİ×}İ}m}İm}}İmİmİİİİİ}İ}İ}İmİmİ}İ}İgİ}İÖİ}İÖİmİİİİİİİØØİİØØ3Ó†6""ˆ82#ˆ8"#m6"#ˆ3ƒİˆˆØİİ=3""ch2#ƒˆ2"ƒˆ2"cÖ83ˆØˆˆİİİİİİİİİİİİİİİİİİİİİİÖİİmİİÖmmİÖİİİİİİİİİİİİİİİİİİİİİİmİmİİÖmİÖÖ À ÌÌÌÌ À À ÀÀÀÌÌÌÌÀÀ ÀÀÀ™™™™ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ™™™™ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ€ˆˆˆˆˆˆ™™™™ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ€ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ‘oXXYXXYYXYXYYXYXYYXYXYYYYXYXXYXYXXYYYXXYXYYYXXYYXYYYXXYXYYYYYYYYYYWVYYYYYYYYYYIIIIYYYYYYYYYYYYYYYYYYYYXYYYYYYYYYYYYYYYYYYYYYYYYYLYYWghghgggVWgggghghgVWggggVWVYYYYYYYYYYYYWgggggVGghggFWhgghhhhgWhgggghgghhVWbdfbbcbbbeebebbbbbbbbbbfbbVWVWcVHHHHHHIIWddVWhggghhhhgVHHHHHHHWVGb`C@HHHHWWVWSPPPPPPPPPPPPPPPPPPPPPPPPPPWacacecccddbbbffccddVWSYYYYYYYYYGhgg ghW^]VWVGgghgggWV@@G ggWWVWVWSPPQQRQQPRRQQQQPQRPRQPPQW_hhghgVDadC@@@@WhgVW?VGfc bbW?V@@@@YYYYYYY[0SWcaVWVWVIIIQQQTCGV@@WVWcaV@DC@@@WVWgggg0V\] N!0 VWVW?VIIIGgggWFGVW`bbVW^\]VGFW ZXXXXXXXLXXLXXLXXXXW0VWhgVWVWVGgggFG eTFGbbbVWN^]^]VYYY[gh VGFWggghhhhhW0SRWVWZXXXXXXXXXXXYYYYYYYYYWVG`bC@@@[FG^\\DVQQQThVWZXLXXXY[ZXXW!!WV\\\\\\\\\\\\\\\\\\\\\\\\]VWgghhghghghhZWZX@@XXXXWgFGfg_VYYYYYYYILIKVW!!gghhhZWWfVWggggghhhghgVWgZWggVWhghZXWCDa_VWgghgVWgg^\\\\\\\\\\\\\\\\\\\\\\WW e`bb !!ffb]0fa!! @@VWVWhZXXXWZXYYYXXXXYYXXYX[ggZWJH]_SWVWgghhgghggVW WSPPPPQTSRRQGgCDhhVW00VWZWbhghghhWhJKg`e_ZXX[ZW?baZWcc€ceecW!!V                               W VWWNWhhbS@GghhW€eebe^]\]^\\]!!@ @@@WV°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°W!!!VW€bffebbbbbacbdbbffWgWbST\\\\]NNhhGggggFW V°°°°°°°±°°°°°°°²±°°°°°°°°°°°°°²W^DVQQQQQQTSRRRTSRRQRRRRPWccebaddcff`€becceebCW€bbb`SW€bb`bbbfbbWggggeb`bbbb                            W€ffcc`ccfV°°°°°°°Wf _V¡  ¢¡  ¢RRQPRQQQQQRRRQRRQRRQRRRQRQRQQQPQPQRPQQQQQQPQRQQ¡            ¢QRPQPQPRRPQPRPR±°°°°°°°°°°°°°°°°°°°°°°°°°°²PPPPPPPPQRP±°°°°°²QPR¡                      ¢YYYYYYYYYYYYYYYYYXXXXXXXXYYYIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIWggggghghVXXXXXXXXWgghVWgVYYYYYYYYG hhWhhgggggggggghWVWghggVWVIIIIIIIIWbVWgghgV@@@WWbbfb0bbVWVWdb dffVIIIIGaghgg ggTVWfcbebdZYXYYYXYYWWSQQTSQWVPPPPPTVGgggFDbbaC@@@WVRRRQPTghVWebWF¡     ¢W`cVWVGbb`F@@@WVG`bVWcC@@WJHHHIIIIIIIHHHHH\\\]fVWVGC@@WZXYYYYXYYX[SQWc0e0`0eCWggghhhggcEVWVGFYYYXXYYYWghgFGghg?VTEESW`CGVWVGF[hghgFWfbbFGZXYYW__VW`CGVWVGZX[FWSRTFGghW__VW^IIKVWVWhhgZYYYYWVWFG€bb W__VW0gg?VWVWhhVWVWJKbSRRTW__VWYYY[ZY[?ZWVWggbSWbbbW__VWbffbbbb !fb`b`WghhghWVWSWSQPPW__VPPPPPPTSPPPPPPPPPPPQWadfdccbcfdcbfcfcdb`cfdcWVW VWVW__V         RQRRRRRRPQQQPPTSQPPPPQWVWVPPPPPP                     ±°°°°°°°²¡ ¢¡¢¡     ¢±°°°°°²±°°°°°²±°°°°°² ‡ s; 7;==>>>?????????????????????? s=:=>??????????????????????????¶?ÌÌÌ Ì ÌÌ ÌÀ PÌÌÌ ÌpÌpÌ|ppppîîîîîîîîîîîîîîîîîîîîÌÌÌÌ|ÌÌÌ`pÀlp`p`p`p`àÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿÌÌ`ÀÌ`ÌÌ`Ç ````àÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿÌÌ ÌÌ|w ÌÌ ÌÌ|ÌÌwwwwwwwwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿpvwpvwpvwpvwpvwpvwpvwpvwwwgwwwgwwwgwwwgwwwgwwwgwwwgwwwgwààààààààààvffvvffvvffvvffvvffvvffvvffvvffvàÿàÿàÿàÿàÿàÿàÿPàÿàÿàÿÌ ÌÌÜÍÌ}Ì}İÌÌÍÜÍİİÍÌÜÍÌÌİİİİİİİföffvvÌ|ÌmİÌİlÌ}İÍÜÍÜÍİİİİÌÍÜİİİİİİİİıÿıowîîîîîîîîîîÌÌ|ÌÌ×ÌİÜÌmİÍÜÍÌÌİİİÌÜÍÌÜİİİİİİİıÿıÿvvàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿÌ|Ìİ×ÌÌÜÌ}İÍÜÇÜÍİİÍÜİİİÌİİİİİİİıîışvwàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿÌ|ÌÌlÌİ|ÌÌÜÍÜÆÜÍİİÍÌÌÍÌÜİİİİİİİíîíîvwÿÿÿÿPÿÿÿÿÿÿggm}}İm}}İ}İÖİİİİİİİİİİİİİİİİİİŞİŞfw`pwmİmİmİmİmİİİİİİİİİİİİİİİİİİİİÒİÒvpàààààggİİİİİİİİİİİİİİİİİİİİİİİİİİİİİÒİÒwàÿàÿàÿàÿàÿÌÌÀÌÜÌÍÜÌÌÜİİİÌİÌİÍÜİÌÜİİİİİİİİİİİİİvvÀ pÌİÌÌÜÌİİİİÍÌİÌİÌÜÍÜİİİİİİİİİİİİİİwîîîîÀ wÌİÌÌÜÌÌİİİÌÍİİÍÜÜÍÌÜİİİİİİİİİİİİİvvàÿÿÿàÿÿÿÀ wÌİÜÜÜÌİİİİÌÌÜÌÍÌÌÍÜÍİİİİİİİİİİİİİvwàÿÿÿàÿÿÿÀ ÇÌÜÜİÜÌÌÜİİİÍİÌİİÜİÌÜİİİİİİİİİİİİİvwÿÿÿÿggİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİfw`vwvwvwvwvwvwvwvwpwİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİvpààpwgpwgpwgpwgpwgpwgpwgpwgggİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİwàÿàÿvffvvffvvffvvffvvffvvffvvffvvffvwİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİvvpİİİİ}İ}İ}İ}İ}İ}İ}İ}İİİİİ}İ}İ}İ}İppgİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİwîîgİİİİmİ}İ}İmİmİ}İmİ}İİİİİmİ}İmİ}İpwfİİİİİİİİİUÕİİİİİİİİİİİİİİİİİİİvvàÿÿÿpfİİİİİ×mİmİ}İİ×mİİ×mİİİİİİ×mİİ×mİwgİİİİİİİİ]ÌVİİİİİİ©Ùİİ©Ùİİİİİİİİİvwàÿÿÿpgİİİİİ×}İ}İgİİ×}İİ×}İİİİİİ×}İİ×}İwgİİİİİİİİÅÀlÕİİİİİİİİİİİİİİİİİİvwÿÿpgݪªİ}m}İ}İÖİ}m}İ}m}İİİİİ}m}İ}m}İggİİİİİİİİeÌfvİİİİİmİİİmİİİİİİİİİİfw`vwvwvwvwpg­ªªÙm}}İ}İÖİm}}İm}}İİİİİm}}İm}}İpwİÖmİİmmİeffvİÖmİİÖİÖİÖİÖİİİİİİİİvpàpwgpwgpwgpwgw­ª™ÙmİmİmİİİmİmİmİmİİİİİmİmİmİmİggmİmİmmİÖ}ww×mİmİİÖİÖİÖİÖİİİİİİİİwPàÿvffvvffvvffvvffv`İÙÖİİİİİİİİİİİİİİİİİİİİİİİİİİİİgffggffggffggffggffgffffİİİİİİİİvvp}İ}İİİİİ}İ}İ}İ}İffffİİİİİİİİİİİİffffgffggffggffgwvwwwvwwwvwwwvwwwvwwpwgfİİİİİİİİwîîgmİ}İİİİİmİ}İmİ}İwîîîîpwgfİİİİİİİİİİİİfwwwpvwpvwpvwpwgpwgpwgpwgpwgpfİİİİİİİİvvàÿÿÿpfİ×mİİİİİİ×mİİ×mİvàÿÿÿàÿÿÿpfİİİİİİİİİİİİwwwwwwwwwwwwwwwwww``gİİİİİİİİvwàÿÿÿpgİ×}İİİİİİ×}İİ×}İvàÿÿÿàÿÿÿgİİİİİİİİİİİİw`````p``İİİİİİİİvwÿÿpg}m}İİİİİ}m}İ}m}İvÿÿÿÿ`İİİİİİİİݪªİvPPppppppppppvpİİİİİİİİfw`pgm}}İİİİİm}}İm}}İfpİİİİİİİİ­ªªÙvppppppİİİİİİİİvpàwmİmİİİİİmİmİmİmİvààpİmmİİmmİ­ª™Ùvpİİİİİİİİwàÿ`İİİİİİİİİİİİİİİİàÿàÿpmmİÖmmİÖİÙÖvwİİİİİİİİvvpİÕİİ]İİİİİİİİİİİgffgeffggffgîîîîppgİİİİİİİİwgİİİİİİİİİİİİİİİİwîîîîîîpvwpvwpvwàÿÿÿàÿÿÿpwfİİİİİİİİvvpfİİİİİİİİİİİİİİİİvàÿÿÿàÿÿÿàÿÿÿwwwwwwàÿÿÿàÿÿÿwgİİİİİİİİvwpgİİİİİİİİİİİİİİİİvàÿÿÿàÿÿÿàÿÿÿÿÿÿÿwgİİİİİİİİvwpgİİİİİİİİİİİİİUUİvÿÿÿÿÿÿggİİİİİİİİfw`pgİİİİİİİİİİİİ]UUÖfààpwİİİİİİİİvpwİİİİİmmİİmmİ]UfÖvààààÿàÿggİİİİİİİİw`İİİİmmİÖmmİÖİmÖİàÿàÿàÿwİİİİİİİİvvpİİİİffffgffggffgîîîîîîîîîîppgİİİİİİİİwgİİİİfwwwpvwpvwàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿpwfİİİİİİİİvvpfİİİİwwwwwwàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿwgİİİİİİİİvwpgİİİİwÿÿÿÿÿÿÿÿÿÿwgİİİİİİİİvwpgİİİİvggİİİİİİİİfw`pgİİİİvàààààpwİİİİİİİİvpwİİİİvàÿàÿàÿàÿàÿggİİİİİİİİw`İİİİvwİİİİİİİİvvpİİİİîîîîîîppgİİİİİİİİwîîgİİİİw```pp```îîîîîîàÿÿÿàÿÿÿàÿÿÿpwfİİİİİİİİvvàÿÿÿpfİİİİvvvvp`p`vvvàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿwgİİİİİİİİvwàÿÿÿpgİİİİv``àÿÿÿàÿÿÿàÿÿÿÿÿÿÿÿÿwgİİİİİİİİvwÿÿpgİİİİvwwwwwwwwwwwwwwwwÿÿÿÿÿÿggİİİİİİİİfw`pgİİİİfpvwpvwpvwpvwpvwpvwpvwpvwàààpwİİİİİİİİvpàwİİİİvwwgwwwgwwwgwwwgwwwgwwwgwwwgwwwgwààààÿàÿàÿggİİİİİİİİwàÿ`İİİİvffvvffvvffvvffvvffvvffvvffvvffvàÿàÿàÿwİİİİİİİİvvpİİİİg}İ}İ}İ}İİİİİİİİİİİİİİİİİ}İ}İ}İ}İîîppgİİİİİİİİwg]İİİwg}İmİmİ}İİİİİİİİİİİİİİİİİ}İmİmİ}İwîîîîàÿÿÿpwfİİİİİİİİvvpfİİİİvfmİ}İİ×mİİİİİİİİİİİİİİİİİmİuİİ×mİvàÿÿÿàÿÿÿàÿÿÿwgİİİİİİİİvwpgİİİİvgf}İgİİ×}İİİİİİİİİİİİİİİİİ}İgİİ×}İvàÿÿÿàÿÿÿÿÿwgİİİİİİİİvwpgİİİİvpf}İÖİ}m}İİİİİİİİİİİİİİİİİ}İÖİ}m}İvÿÿÿÿggİİİİİİİİfw`pgİİİİfg}İÖİm}}İİİİİİİİİİİİİİİİİ}İÖİm}}İfàpwİİİİİİİİvpwİİİİvw`wfmİİİmİmİİİİİİİİİİİİİİİİİmİİİmİmİvàààÿggİİİİİİİİw`İİİİfgffİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİàÿàÿwİİİİİİİİvvpİİİİggİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİppgİİİİİİİİwgİİİİfpgİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİwpwfİİİİİİİİvvpfİİİİfp`fİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİvwgİİİİİİİİvwpgİİİİfv`gfİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİvwgİİİİİİİİvwpgİİİİvwwpfİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİvggİİİİİİİİfw`pgİİİİfpvwgİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİfpwİİİİİİİİvpwİİİİfwwwwgww`wfİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİvggİİİİİİİİw`İİİİffvfvffvfgffİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİwİİİİİİİİvvwİİİİ}İ}İ}İ}İ}İ}İİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİgPppgİİİİİİİİwppgİİİİ}İmİ}İmİmİ}İİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİf````pwfİİİİİİİİvvpwfİİİİmİ}İmİ}İİ×mİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİfvvvvwgİİİİİİİİvwwgİİİİ}İgİ}İgİİ×}İİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİfvwgİİİİİİİİvwwgİİİİ}İÖİ}İÖİ}m}İİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİÕİvwwwwwwwwggİİİİİİİİfw`ggİİİİ}İÖİ}İÖİm}}İİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİfpvwpvwpvwpvwpwİİİİİİİİvppwİİİİmİİİmİİİmİmİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİfwwwwgwwwgwwwgwwwgwggİİİİİİİİwggİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİffvfvffvvffvvffvvffvwİİİİİİİİvvwİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİ}İ}İİİİİİİİİİİİİ}İ}İvvppgİİİİİİİİwppgİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİ}İmİİİİİİİİİİİİİ}İmİwpwfİİİİİİİİvvpwfİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİmİ}İİİİİİİİİİİİİmİ}İvvwgİİİİİİİİvwwgİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİ}İgİİİİİİİİİİİİİ}İgİvwwgİİİİİİİİvwwgİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİ}İÖİİİİİİİİİİİİİ}İÖİvwggİİİİİİİİfw`ggİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİ}İÖİİİİİİİİİİİİİ}İÖİfw`pwİİİİİİİİvppwİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİmİİİİİİİİİİİİİİİmİİİvpggİİİİİİİİwggİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİwgİİİİİİİİggİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİg```gİİİİİİİİf`gİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİfvvvfİİİİİİİİfvfİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİØØİİİİİİİİİÍÜİİİİİİfgfİİİİİİİİfvgfİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİ݉‰İİİİİİİİİÜÍÌÌİİİİfvwwwwwwpfİİİİİİİİvwwpfİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİ™‰İİİİİİİİİÜÍİÍİİİİvpvwpvwpvwgİİİİİİİİfpvwgİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİݘØİİİİİİİİİÍÜİİİİİİfwwgwwwgwwwgww`wfİİİİİİİİfwwwwgww`wfİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİfwwvffvvffvvffvfgffİİİİİİİİffvfvffvfgffİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİffvfw}İ}İİİİİ}İ}İ}İ}İİİİİİİİİ}İ}İİİİİ}İ}İİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİ}İİ×ppgmİ}İİİİİ}İmİ}İmİİİİİİİİİmİ}İİİİİ}İmİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİ×Ö}mpwfİ×mİİİİİmİ}İmİ}İİİİİİİİİİ×mİİİİİmİ}İİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİ××m}wgİ×}İİİİİ}İgİ}İgİİİİİİİİİİ×}İİİİİ}İgİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİmİİ×wg}m}İİİİİ}İÖİ}İÖİİİİİİİİİ}m}İİİİİ}İÖİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİ}İİ×ggm}}İİİİİ}İÖİ}İÖİİİİİİİİİm}}İİİİİ}İÖİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİ××m}pwmİmİİİİİmİİİmİİİİİİİİİİİmİmİİİİİmİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİÖ×mmggİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİmİİ×wİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİ}İİ×îîppgİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİ×Ö}màÿÿÿpwfİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİݪÚİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİ××m}àÿÿÿwgİİİİİİİİİ©Ùİİİİİİİİİİİİİİİİİİİİ]İ©Ùİİİİİİİİİİİİİİ©Ùİİİİİİ©Ùİİİİİݭɬİİİİİİİİİİİİİİİİİİİİİİİİİ©Ùİİİİİİİİİmİİ×ÿÿwgݪªİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİİšÌÀÚİİİİİİİİİİİİİİİİUUİİİİİİİİİİİİİİİİ}İİ×gg­ªªÙİİİİİmİİİİİİİİİÖİİİÖİİİİİİİÖİmİİİİİİİİİÖİİİİİmİİİİİÖİmİİİİİ֙ɜÚİİİİİİİİİİİİİİİ]UUÖİİİÖİmİİİİİİİİİÖ××m}àpw­ª™ÙİÖmİİÖİÖİÖmİmİİÖmİİÖİmmİmİİÖİÖİÖİÖmİmİİÖİmmİİÖİÖmİİÖİÖİÖmİİÖ™™™Úİİİİİİİİİİİİmmİ]UfÖmİİÖİÖİÖİÖmİmİİÖÖ×mmàÿggİÙÖmİmİİÖİÖmİmİmİÖÖmİÖÖmmİÖmİÖÖİÖİÖmİmİmİÖÖmmİÖİÖİÖmİÖÖİÖİÖmİÖÖmˆˆˆİİİİİİİİİİİİmmİÖİmÖİmİÖÖİÖİÖmİmİmİÖÖmİİ×gffggffggffggffggffggffggffggffggffggffggffggffggffggffggffggffgffffİİİİİİİİİİİİffffgffggffggffggffggffggffgîîîîwvwwwvwwwvwwwvwwwvwwwvwwwvwwwvwwwvwwwvwwwvwwwvwwwvwwwvwwwvwwwvwwpwgfİİİİİİİİİİİİfwwwwvwwwvwwwvwwwvwwwvwwwvwwàÿÿÿàÿÿÿpwgpwgwgpwgwgwgwgwgwgwgpwgpwgpwgpwgpwgpwgpfİİİİİİİİİİİİwwpwgpwgpwgpwgpwgpwgàÿÿÿàÿÿÿwwwwwwwwww``gİİİİİİİİİİİİwvwwwwwwwwÿÿÿÿ``pfppfpfpfpfpfpf`ppp``p``İİİİİİİİİİİİv`p````pppppppppppppppppppppppppppppvpİİİİİİİİİİİİvpppppppppppààppPppppİİİİİİİİİİİİvpppppàÿàÿpİİİİİİİİİİİİv™™™™™™™™™™™™îîîîîîîîîˆˆˆˆˆˆˆˆ¨ˆîîîîîîàÿÿÿàÿÿÿPàÿÿÿàÿÿÿàÿÿÿˆˆ¨ˆŠˆˆˆ¨ˆˆˆàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿàÿÿÿˆˆˆˆˆˆ¨ˆˆˆŠ¨àÿÿÿàÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿŠˆˆˆ¨ˆˆˆˆˆˆˆÿÿÿÿÿÿˆˆ¨ˆˆˆŠ¨ˆˆˆˆPààààà ˆˆˆˆˆˆˆˆˆŠààààÿàÿàÿàÿàÿˆŠ¨ˆˆˆˆŠˆˆàÿàÿàÿ‹p-- title: In the Hollows -- author: Old Platformer Guy -- desc: A 2D platformer set in the hollows filled with deadly spikes and poisonous water. -- license: MIT License -- version: 1.0 -- script: lua -- This game was made by Harald Scheidl in 2024 -- constants Constants = { friction = 0.15, gravity = 0.15, horizontal_acc = 0.4, vertical_acc = 2.35, max_horizontal_speed = 1.2, max_vertical_speed = 2, map_copy_x = 0, map_copy_y = 119, screen_w = 30, screen_h = 17, grid = 8, eps = 0.001, time_limit = 5 * 60, alternation = 60, water_animation = { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, }, freeze_after_init = 30, invalid_frame = -1000, jump_buffer = 10, level_order = { [0] = 9, [1] = 0, [2] = 1, [3] = 8, [4] = 10, [5] = 11, [6] = 2, [7] = 3, [8] = 4, [9] = 5, [10] = 6, [11] = 7 }, max_deaths = 25 } Sounds = { EscapedLevel = 0, Jumped = 1, Died = 2, Key = 3, Won = 4, Landed = 5 } GameMode = { InPlay = 0, Won = 1, Lost = 2, DeathAnimation = 3, WinningAnimation = 4, Statistics = 5, NextLevel = 6, NotYetStarted = 7 } Tiles = { Air = 0, PlayerLeft1 = 1, PlayerLeft2 = 2, PlayerRight1 = 3, PlayerRight2 = 4, PlayerDeadMin = 6, PlayerDeadMax = 7, MrOrangeLeft = 9, MrOrangeRight = 10, MrGreenLeft = 11, MrGreenRight = 12, Block = 16, SpikesUp = 17, SpikesRight = 18, SpikesDown = 19, SpikesLeft = 20, Key = 21, DoorClosedRight = 22, DoorOpenedRight = 24, DoorClosedLeft = 28, DoorOpenedLeft = 29, WaterMin = 176, WaterMax = 178, WaterSurfaceMin = 160, WaterSurfaceMax = 162, BlockInactive = 25, BlockActive = 26, AlternatingBlockPhase1 = 32, AlternatingBlockPhase2 = 33, AirJump = 48, Gold = 27, GrassBlockMin = 64, GrassBlockMax = 95, MushroomMin = 96, MushroomMax = 97, SnailLeft = 112, SnailRight = 113, SnailBorderLeft = 128, SnailBorderRight = 129, PowerUp = 63 } -- state vars State = { game_mode = GameMode.NotYetStarted, game_mode_start_frame = 0, player_x = nil, player_y = nil, player_sx = nil, player_sy = nil, player_sid = nil, level = nil, on_ground = nil, frame = 0, -- frame counting from begin of current level total_frames = 0, -- frame counting from begin of game lives_remaining = nil, num_died = nil, last_on_ground_frame = nil, last_jump_btn_frame = nil, last_died_x = nil, last_died_y = nil, alternating_tiles = nil, air_jump = nil, time_remaining = nil, time_at_game_start = nil, water_tiles = nil, enemies = nil, jump_animation_start_frame = nil, jump_animation_mode = nil, jump_animation_x = nil, jump_animation_y = nil, mr_green_x = nil, mr_green_y = nil, mr_orange_x = nil, mr_orange_y = nil, bubble_text = nil, particles = nil, } function InitState() State.game_mode = GameMode.NotYetStarted State.game_mode_start_frame = 0 State.player_x = 0 State.player_y = 0 State.player_sx = 0 State.player_sy = 0 State.player_sid = 1 State.level = 0 State.on_ground = true State.frame = 0 State.lives_remaining = Constants.max_deaths State.num_died = 0 State.last_on_ground_frame = Constants.invalid_frame State.last_jump_btn_frame = Constants.invalid_frame State.last_died_x = 0 State.last_died_y = 0 State.alternating_tiles = { {}, {} } State.air_jump = false State.time_remaining = 0 State.time_at_game_start = time() State.water_tiles = {} State.enemies = { mushrooms = {}, snails = {} } State.jump_animation_start_frame = -1 State.jump_animation_mode = "" State.jump_animation_x = 0 State.jump_animation_y = 0 State.mr_green_x = -1 State.mr_green_y = -1 State.mr_orange_x = -1 State.mr_orange_y = -1 State.bubble_text = "" -- init particles State.particles = {} for i = 1, 25 do table.insert(State.particles, { math.random() * Constants.screen_w * Constants.grid, math.random() * Constants.screen_h * Constants.grid, (math.random() - 0.5) * 0.1, (math.random() - 0.5) * 0.1 }) end end function ResetLevel() State.player_sx = 0 State.player_sy = 0 State.on_ground = false State.air_jump = false State.last_on_ground_frame = Constants.invalid_frame State.last_jump_btn_frame = Constants.invalid_frame State.frame = 0 State.water_tiles = {} State.enemies.mushrooms = {} State.enemies.snails = {} State.mr_green_x = -1 State.mr_green_y = -1 State.mr_orange_x = -1 State.mr_orange_y = -1 State.bubble_text = "" CopyLevelMap() State.alternating_tiles = { {}, {} } local snail_path_begin = 0 for y = Constants.map_copy_y, Constants.screen_h + Constants.map_copy_y do for x = Constants.map_copy_x, Constants.screen_w + Constants.map_copy_x do -- pixel coordinates, where left top of screen is (0,0) local xl = Constants.grid * (x - Constants.map_copy_x) local yt = Constants.grid * (y - Constants.map_copy_y) local xr = xl + Constants.grid - 1 local yb = yt + Constants.grid - 1 local id = mget(x, y) -- get player position from map if id == Tiles.PlayerLeft1 or id == Tiles.PlayerRight1 or id == Tiles.PlayerLeft2 or id == Tiles.PlayerRight2 then State.player_x = Constants.grid * (x - Constants.map_copy_x) State.player_y = Constants.grid * (y - Constants.map_copy_y) State.player_sid = id mset(x, y, Tiles.Air) end -- load alternating tiles if id == Tiles.AlternatingBlockPhase1 then local len = #State.alternating_tiles[1] State.alternating_tiles[1][len + 1] = { x, y } mset(x, y, Tiles.BlockActive) elseif id == Tiles.AlternatingBlockPhase2 then local len = #State.alternating_tiles[2] State.alternating_tiles[2][len + 1] = { x, y } mset(x, y, Tiles.BlockInactive) end -- load water tiles local is_water = Tiles.WaterMin <= id and id <= Tiles.WaterMax local is_water_surface = Tiles.WaterSurfaceMin <= id and id <= Tiles.WaterSurfaceMax if is_water or is_water_surface then local yt_water = yt if is_water_surface then yt_water = yt + 1 end table.insert(State.water_tiles, { xl, yt_water, xr, yb }) end -- load mushrooms if id >= Tiles.MushroomMin and id <= Tiles.MushroomMax then table.insert(State.enemies.mushrooms, { xl, yt, xr, yb, nil }) end -- load snails if id == Tiles.SnailBorderLeft then snail_path_begin = xl elseif id == Tiles.SnailBorderRight then table.insert(State.enemies.snails, { snail_path_begin, yt, snail_path_begin + Constants.grid, yb, { 1, snail_path_begin, xr } }) end if id == Tiles.MrGreenLeft or id == Tiles.MrGreenRight then State.mr_green_x = xl State.mr_green_y = yt end if id == Tiles.MrOrangeLeft or id == Tiles.MrOrangeRight then State.mr_orange_x = xl State.mr_orange_y = yt -- only in levels before the last level Mr Orange is animated if not IsLastLevel() then mset(x, y, Tiles.Air) end end end end end function HandleAlternatingTiles() if State.frame % (2 * Constants.alternation) == 0 then for i = 1, #State.alternating_tiles[1] do local coord = State.alternating_tiles[1][i] mset(coord[1], coord[2], Tiles.BlockActive) end for i = 1, #State.alternating_tiles[2] do local coord = State.alternating_tiles[2][i] mset(coord[1], coord[2], Tiles.BlockInactive) end end if State.frame % (2 * Constants.alternation) == Constants.alternation then for i = 1, #State.alternating_tiles[2] do local coord = State.alternating_tiles[2][i] mset(coord[1], coord[2], Tiles.BlockActive) end for i = 1, #State.alternating_tiles[1] do local coord = State.alternating_tiles[1][i] mset(coord[1], coord[2], Tiles.BlockInactive) end end end function PlayerEscapedLevel() return State.player_x < Constants.eps or State.player_x + Constants.grid + Constants.eps >= Constants.screen_w * Constants.grid end function CopyLevelMap() local mapped_level = Constants.level_order[State.level] local offset_x = (mapped_level % 8) * Constants.screen_w local offset_y = (mapped_level // 8) * Constants.screen_h for x = 0, Constants.screen_w do for y = 0, Constants.screen_h do mset(x + Constants.map_copy_x, y + Constants.map_copy_y, mget(x + offset_x, y + offset_y)) end end end function UnlockDoor() for x = Constants.map_copy_x, Constants.screen_w + Constants.map_copy_x do for y = Constants.map_copy_y, Constants.screen_h + Constants.map_copy_y do local id = mget(x, y) if id == Tiles.Key then mset(x, y, Tiles.Air) end if id == Tiles.DoorClosedRight then mset(x, y, Tiles.DoorOpenedRight) end if id == Tiles.DoorClosedLeft then mset(x, y, Tiles.DoorOpenedLeft) end end end end function GetTileAtPixel(x, y) return mget(x / Constants.grid + Constants.map_copy_x, y / Constants.grid + Constants.map_copy_y) end function SetTileAtPixel(x, y, val) return mset(x / Constants.grid + Constants.map_copy_x, y / Constants.grid + Constants.map_copy_y, val) end function FloorGrid(val) return math.floor(val / Constants.grid) * Constants.grid end function CeilGrid(val) return math.ceil(val / Constants.grid) * Constants.grid end function MoveX(btn_left, btn_right) local hit_left = TileIsSolid(GetTileAtPixel(State.player_x, State.player_y + Constants.eps)) or TileIsSolid(GetTileAtPixel(State.player_x, State.player_y + Constants.grid - Constants.eps)) local hit_right = TileIsSolid(GetTileAtPixel(State.player_x + Constants.grid, State.player_y + Constants.eps)) or TileIsSolid(GetTileAtPixel(State.player_x + Constants.grid, State.player_y + Constants.grid - Constants.eps)) -- apply inputs if btn_left and not hit_left then State.player_sx = math.max(-Constants.max_horizontal_speed, State.player_sx - Constants.horizontal_acc) end if btn_right and not hit_right then State.player_sx = math.min(Constants.max_horizontal_speed, State.player_sx + Constants.horizontal_acc) end -- move and check if we crossed a solid edge local new_player_x = State.player_x + State.player_sx -- hit left wall local pos1 = { new_player_x + Constants.eps, State.player_y + Constants.eps } local tile1 = GetTileAtPixel(pos1[1], pos1[2]) local pos2 = { new_player_x + Constants.eps, State.player_y + Constants.grid - Constants.eps } local tile2 = GetTileAtPixel(pos2[1], pos2[2]) local prev_tile1 = GetTileAtPixel(State.player_x + Constants.eps, State.player_y + Constants.eps) local prev_tile2 = GetTileAtPixel(State.player_x + Constants.eps, State.player_y + Constants.grid - Constants.eps) if State.player_sx < 0 and ((tile1 ~= prev_tile1 and TileIsSolid(tile1)) or (tile2 ~= prev_tile2 and TileIsSolid(tile2))) then State.player_sx = 0 new_player_x = CeilGrid(new_player_x) end -- hit right wall local pos3 = { new_player_x + Constants.grid - Constants.eps, State.player_y + Constants.eps } local tile3 = GetTileAtPixel(pos3[1], pos3[2]) local pos4 = { new_player_x + Constants.grid - Constants.eps, State.player_y + Constants.grid - Constants.eps } local tile4 = GetTileAtPixel(pos4[1], pos4[2]) local prev_tile3 = GetTileAtPixel(State.player_x + Constants.grid - Constants.eps, State.player_y + Constants.eps) local prev_tile4 = GetTileAtPixel(State.player_x + Constants.grid - Constants.eps, State.player_y + Constants.grid - Constants.eps) if State.player_sx > 0 and ((tile3 ~= prev_tile3 and TileIsSolid(tile3)) or (tile4 ~= prev_tile4 and TileIsSolid(tile4))) then State.player_sx = 0 new_player_x = FloorGrid(new_player_x) end State.player_x = new_player_x -- apply friction if State.player_sx > 0 then State.player_sx = math.max(0, State.player_sx - Constants.friction) end if State.player_sx < 0 then State.player_sx = math.min(0, State.player_sx + Constants.friction) end return { { tile1, tile2, tile3, tile4 }, { pos1, pos2, pos3, pos4 } } end function MoveY(btn_jump) if btn_jump then State.last_jump_btn_frame = State.frame end -- check if on ground State.on_ground = TileIsSolid(GetTileAtPixel(State.player_x + Constants.eps, State.player_y + Constants.grid)) or TileIsSolid(GetTileAtPixel(State.player_x + Constants.grid - Constants.eps, State.player_y + Constants.grid)) if State.on_ground then State.last_on_ground_frame = State.frame State.air_jump = false end -- apply input if (State.frame - State.last_on_ground_frame < Constants.jump_buffer or State.air_jump) and (State.frame - State.last_jump_btn_frame < Constants.jump_buffer) then State.player_sy = -Constants.vertical_acc State.last_on_ground_frame = Constants.invalid_frame State.last_jump_btn_frame = Constants.invalid_frame State.air_jump = false State.jump_animation_start_frame = State.frame State.jump_animation_mode = "jump" State.jump_animation_x = State.player_x State.jump_animation_y = State.player_y sfx(Sounds.Jumped) end -- move and check if we crossed a solid edge local new_player_y = State.player_y + State.player_sy -- hit floor local pos1 = { State.player_x + Constants.eps, new_player_y + Constants.grid - Constants.eps } local tile1 = GetTileAtPixel(pos1[1], pos1[2]) local pos2 = { State.player_x + Constants.grid - Constants.eps, new_player_y + Constants.grid - Constants.eps } local tile2 = GetTileAtPixel(pos2[1], pos2[2]) local prev_tile1 = GetTileAtPixel(State.player_x + Constants.eps, State.player_y + Constants.grid - Constants.eps) local prev_tile2 = GetTileAtPixel(State.player_x + Constants.grid - Constants.eps, State.player_y + Constants.grid - Constants.eps) if State.player_sy > 0 and ((tile1 ~= prev_tile1 and TileIsSolid(tile1)) or (tile2 ~= prev_tile2 and TileIsSolid(tile2))) then State.on_ground = true State.player_sy = 0 new_player_y = FloorGrid(new_player_y) State.jump_animation_start_frame = State.frame State.jump_animation_mode = "land" State.jump_animation_x = State.player_x State.jump_animation_y = new_player_y sfx(Sounds.Landed) end -- hit ceiling local pos3 = { State.player_x + Constants.eps, new_player_y + Constants.eps } local tile3 = GetTileAtPixel(pos3[1], pos3[2]) local pos4 = { State.player_x + Constants.grid - Constants.eps, new_player_y + Constants.eps } local tile4 = GetTileAtPixel(pos4[1], pos4[2]) local prev_tile3 = GetTileAtPixel(State.player_x + Constants.eps, State.player_y + Constants.eps) local prev_tile4 = GetTileAtPixel(State.player_x + Constants.grid - Constants.eps, State.player_y + Constants.eps) if State.player_sy < 0 and ((tile3 ~= prev_tile3 and TileIsSolid(tile3)) or (tile4 ~= prev_tile4 and TileIsSolid(tile4))) then State.player_sy = 0 new_player_y = CeilGrid(new_player_y) end State.player_y = new_player_y -- apply gravity if not State.on_ground then State.player_sy = math.min(Constants.max_vertical_speed, State.player_sy + Constants.gravity) end return { { tile1, tile2, tile3, tile4 }, { pos1, pos2, pos3, pos4 } } end function TileIsDeadly(id, direction) -- only x if direction == "x" then if id == Tiles.SpikesLeft or id == Tiles.SpikesRight then return true end end -- only y if direction == "y" then if id == Tiles.SpikesDown or id == Tiles.SpikesUp then return true end end --all directions if Tiles.WaterSurfaceMin <= id and id <= Tiles.WaterSurfaceMax then return true end return false end function TileIsSolid(id) if id == Tiles.Block or id == Tiles.BlockActive or (id >= Tiles.GrassBlockMin and id <= Tiles.GrassBlockMax) or id == Tiles.SpikesUp or id == Tiles.SpikesDown or id == Tiles.SpikesLeft or id == Tiles.SpikesRight or id == Tiles.DoorClosedLeft or id == Tiles.DoorClosedRight then return true end return false end function HitDeadlyTile(tiles, direction) for i = 1, #tiles do if TileIsDeadly(tiles[i], direction) then return true end end return false end function HitKeyTile(tiles) for i = 1, #tiles do if tiles[i] == Tiles.Key then return true end end return false end function HandleAirJump(tiles, positions) for i = 1, #tiles do if tiles[i] == Tiles.AirJump then SetTileAtPixel(positions[i][1], positions[i][2], Tiles.Air) State.air_jump = true return end end end function HandlePowerUp(tiles, positions) for i = 1, #tiles do if tiles[i] == Tiles.PowerUp then SetTileAtPixel(positions[i][1], positions[i][2], Tiles.Air) State.lives_remaining = State.lives_remaining + 1 sfx(Sounds.Key) return end end end function HandleNextLevel() State.level = State.level + 1 ResetLevel() end function RenderFinalState() local time_needed = Constants.time_limit - State.time_remaining if State.game_mode == GameMode.Won then print(string.format("CONGRATULATIONS :-)\nDIED %d TIMES\nTOOK %s\n[Z]", State.num_died, MakeTimeString(time_needed)), 80, 32, 6) spr(Tiles.MrOrangeRight, 40, 32, 13) spr(Tiles.PlayerLeft1, 56, 32, 13) spr(Tiles.GrassBlockMin + 3, 40, 40, 13) spr(Tiles.GrassBlockMin, 48, 40, 13) spr(Tiles.GrassBlockMin + 4, 56, 40, 13) elseif State.game_mode == GameMode.Lost then print(string.format("GAME OVER :-(\nDIED %d TIMES\nTOOK %s\n[Z]", State.num_died, MakeTimeString(time_needed)), 80, 32, 2) spr(Tiles.PlayerRight1, 48, 32, 13) spr(Tiles.GrassBlockMin + 3, 40, 40, 13) spr(Tiles.GrassBlockMin, 48, 40, 13) spr(Tiles.GrassBlockMin + 4, 56, 40, 13) end end function MakeTimeString(time) return string.format("%d:%02d", time // 60, time % 60) end function RenderState() print("LEVEL " .. State.level + 1, 0, 0, 12) local death_color = 12 if State.lives_remaining < 10 then death_color = 2 end print("HEALTH " .. State.lives_remaining, 0, 8, death_color) local time_color = 12 if State.time_remaining < 60 then time_color = 2 end print("TIME " .. MakeTimeString(State.time_remaining), 0, 16, time_color) end function RenderBubble() if State.bubble_text ~= "" then rect(30, 110, 180, 25, 15) rectb(30, 110, 180, 25, 14) print(State.bubble_text, 35, 115, 12) end end function RenderWater() for i = 1, #State.water_tiles do local xl = State.water_tiles[i][1] local yt = State.water_tiles[i][2] local xr = State.water_tiles[i][3] local yb = State.water_tiles[i][4] for x = xl, xr do for y = yt, yb do local animation_len = #Constants.water_animation local index = State.frame // 10 + y + 5 * x if Constants.water_animation[index % animation_len] == 1 and pix(x, y) ~= 0 then pix(x, y, 10) end end end end end function RenderParticles() for i = 1, #State.particles do local x = State.particles[i][1] local y = State.particles[i][2] pix(x, y, 5) end end function Clamp(x, a, b) if x < a then return a end if x > b then return b end return x end function HandleParticles() for i = 1, #State.particles do State.particles[i][3] = Clamp(State.particles[i][3] + (math.random() - 0.5) * 0.01, -0.05, 0.05) State.particles[i][4] = Clamp(State.particles[i][4] + (math.random() - 0.5) * 0.01, -0.05, 0.05) State.particles[i][1] = State.particles[i][1] + State.particles[i][3] State.particles[i][2] = State.particles[i][2] + State.particles[i][4] if State.particles[i][1] < 0 then State.particles[i][1] = 0 State.particles[i][3] = -State.particles[i][3] end if State.particles[i][1] > Constants.screen_w * Constants.grid then State.particles[i][1] = Constants.screen_w * Constants.grid State.particles[i][3] = -State.particles[i][3] end if State.particles[i][2] < 0 then State.particles[i][2] = 0 State.particles[i][4] = -State.particles[i][4] end if State.particles[i][2] > Constants.screen_h * Constants.grid then State.particles[i][2] = Constants.screen_h * Constants.grid State.particles[i][4] = -State.particles[i][4] end end end function PlayerHitsBox(xl, yt, xr, yb) xl = xl + Constants.eps yt = yt + Constants.eps xr = xr - Constants.eps yb = yb - Constants.eps if xr < State.player_x or (State.player_x + Constants.grid) < xl or yb < State.player_y or (State.player_y + Constants.grid) < yt then return false end return true end function HandleObjects() -- handle mushrooms for i = 1, #State.enemies.mushrooms do local mushroom = State.enemies.mushrooms[i] if PlayerHitsBox(mushroom[1], mushroom[2], mushroom[3], mushroom[4]) then if mushroom[5] ~= nil then if mushroom[5] + 60 < State.frame then return true end else mushroom[5] = State.frame end end end for i = 1, #State.enemies.snails do local snail = State.enemies.snails[i] local speed = 0.2 local direction = snail[5][1] local left_border = snail[5][2] local right_border = snail[5][3] snail[1] = snail[1] + speed * direction snail[3] = snail[1] + Constants.grid if snail[1] <= left_border and direction < 0 then snail[1] = left_border snail[3] = snail[1] + Constants.grid snail[5][1] = 1 end if snail[3] >= right_border and direction > 0 then snail[3] = right_border snail[1] = snail[3] - Constants.grid snail[5][1] = -1 end if PlayerHitsBox(snail[1], snail[2], snail[3], snail[4]) then return true end end -- we only move Mr Orange in levels before the last level if State.mr_orange_x ~= -1 and State.mr_orange_x ~= -1 and not IsLastLevel() then State.mr_orange_x = State.mr_orange_x + Constants.max_horizontal_speed / 10 end return false end function RenderObjects() for i = 1, #State.enemies.mushrooms do local mushroom = State.enemies.mushrooms[i] if mushroom[5] ~= nil then local color = 1 if State.frame > mushroom[5] + 60 then color = 2 end local cx = (mushroom[1] + mushroom[3]) / 2 local cy = (mushroom[2] + mushroom[4]) / 2 for y = mushroom[2], mushroom[4] do for x = mushroom[1], mushroom[3] do local d = (x - cx) ^ 2 + (y - cy) ^ 2 if d < (Constants.grid / 2) ^ 2 and math.random() > 0.9 then pix(x, y, color) end end end end end for i = 1, #State.enemies.snails do local snail = State.enemies.snails[i] local sid = Tiles.SnailLeft if snail[5][1] > 0 then sid = Tiles.SnailRight end spr(sid, snail[1] + .5, snail[2] + .5, 13) end -- we only render Mr Orange in levels before the last level if State.mr_orange_x ~= -1 and State.mr_orange_x ~= -1 and not IsLastLevel() then spr(Tiles.MrOrangeRight, State.mr_orange_x + .5, State.mr_orange_y + .5, 13) end end function IsLastLevel() -- the # operator returns the last numeric key and not the actual length of the table! return State.level == #Constants.level_order end function RenderLastDied() if State.game_mode ~= GameMode.DeathAnimation then return end local num_sprites = Tiles.PlayerDeadMax - Tiles.PlayerDeadMin + 1 local frames_per_sprite = 40 / num_sprites local id = Tiles.PlayerDeadMin + State.frame // frames_per_sprite spr(id, State.last_died_x + .5, State.last_died_y + .5, 13) end function RenderPlayer() spr(State.player_sid, State.player_x + .5, State.player_y + .5, 13) local diff = State.frame - State.jump_animation_start_frame if diff < 0 then return end local dx = 0 if State.jump_animation_mode == "jump" then dx = 3 end local color = 7 for i = 1, 2 do if diff < 5 then pix(State.jump_animation_x + dx - i, State.jump_animation_y + Constants.grid - i, color) pix(State.jump_animation_x + Constants.grid - dx + i, State.jump_animation_y + Constants.grid - i, color) elseif diff < 10 then pix(State.jump_animation_x + dx - i - 1, State.jump_animation_y + Constants.grid - i, color) pix(State.jump_animation_x + Constants.grid - dx + i + 1, State.jump_animation_y + Constants.grid - i, color) end end end function HandleWalkingAnimation(btn_left, btn_right) local walk_duration = 10 if btn_left then if State.frame % (2 * walk_duration) < walk_duration then State.player_sid = Tiles.PlayerLeft1 else State.player_sid = Tiles.PlayerLeft2 end end if btn_right then if State.frame % (2 * walk_duration) < walk_duration then State.player_sid = Tiles.PlayerRight1 else State.player_sid = Tiles.PlayerRight2 end end end function HandleTimeRemaining() local delta = time() - State.time_at_game_start State.time_remaining = math.max(0, Constants.time_limit - math.floor(delta / 1000)) end function HandleState() -- inputs local btn_left = btn(2) -- left local btn_right = btn(3) -- right local btn_jump = btnp(4) -- jump -- time local state_since = State.total_frames - State.game_mode_start_frame if State.game_mode == GameMode.DeathAnimation then HandleAlternatingTiles() HandleTimeRemaining() if state_since >= 40 then State.game_mode = GameMode.InPlay State.game_mode_start_frame = State.total_frames end elseif State.game_mode == GameMode.InPlay then HandleAlternatingTiles() HandleParticles() HandleTimeRemaining() local movement_res = HandleMovement(btn_left, btn_right, btn_jump) if movement_res == "won" then State.game_mode = GameMode.WinningAnimation State.game_mode_start_frame = State.total_frames elseif movement_res == "lost" then State.game_mode = GameMode.Lost State.game_mode_start_frame = State.total_frames elseif movement_res == "died" then State.game_mode = GameMode.DeathAnimation State.game_mode_start_frame = State.total_frames elseif movement_res == "escaped_level" then State.game_mode = GameMode.NextLevel State.game_mode_start_frame = State.total_frames end elseif State.game_mode == GameMode.WinningAnimation then if btn_jump then State.game_mode = GameMode.Won end elseif State.game_mode == GameMode.Lost or State.game_mode == GameMode.Won then if state_since > 60 and btn_jump then State.game_mode = GameMode.NotYetStarted State.game_mode_start_frame = State.total_frames end elseif State.game_mode == GameMode.NextLevel then HandleNextLevel() State.game_mode = GameMode.InPlay elseif State.game_mode == GameMode.NotYetStarted then InitState() ResetLevel() State.game_mode = GameMode.InPlay State.game_mode_start_frame = State.total_frames end end local function TableConcat(t1, t2) local res = {} for _, v in ipairs(t1) do table.insert(res, v) end for _, v in ipairs(t2) do table.insert(res, v) end return res end function HandleMovement(btn_left, btn_right, btn_jump) local res = "" -- move player local tiles_x = {} local tiles_y = {} local all_tiles = {} local positions_x = {} local positions_y = {} local all_positions = {} if State.game_mode == GameMode.InPlay then local res_x = MoveX(btn_left, btn_right) tiles_x = res_x[1] positions_x = res_x[2] local res_y = MoveY(btn_jump) tiles_y = res_y[1] positions_y = res_y[2] all_tiles = TableConcat(tiles_x, tiles_y) all_positions = TableConcat(positions_x, positions_y) end if HitKeyTile(tiles_x) or HitKeyTile(tiles_y) then sfx(Sounds.Key) UnlockDoor() end -- handle jump in the air HandleAirJump(all_tiles, all_positions) -- handle power ups HandlePowerUp(all_tiles, all_positions) -- walking animation HandleWalkingAnimation(btn_left, btn_right) -- check what happened to player while moving local died = false local extend = Constants.grid State.bubble_text = "" if HitDeadlyTile(tiles_x, "x") or HitDeadlyTile(tiles_y, "y") then died = true elseif PlayerEscapedLevel() then res = "escaped_level" sfx(Sounds.EscapedLevel) elseif PlayerHitsBox(State.mr_orange_x - extend, State.mr_orange_y, State.mr_orange_x + Constants.grid + extend, State.mr_orange_y + Constants.grid) then res = "won" sfx(Sounds.Won) State.bubble_text = "Mr Orange: I went astray in the\nhollows. But you found me :-) [Z]" elseif PlayerHitsBox(State.mr_green_x - extend, State.mr_green_y, State.mr_green_x + Constants.grid + extend, State.mr_green_y + Constants.grid) then State.bubble_text = "Mr Green: our orange friend\ngot lost! Help search for him!" end -- check for enemies if not died then died = HandleObjects() end if died then res = "died" State.last_died_x = State.player_x State.last_died_y = State.player_y State.lives_remaining = State.lives_remaining - 1 State.num_died = State.num_died + 1 -- check if game over (deaths) if State.lives_remaining <= 0 then State.lives_remaining = 0 State.game_mode = GameMode.Lost res = "lost" else ResetLevel() end sfx(Sounds.Died) end -- check if game over (time) if State.time_remaining <= 0 then State.game_mode = GameMode.Lost res = "lost" sfx(Sounds.Died) end return res end function TIC() State.frame = State.frame + 1 State.total_frames = State.total_frames + 1 -- handle the state of the game, and execute whatever is needed in current state HandleState() -- render if State.game_mode == GameMode.Won or State.game_mode == GameMode.Lost then cls(0) RenderFinalState() else cls(13) map(Constants.map_copy_x, Constants.map_copy_y, Constants.screen_w, Constants.screen_h, 0, 0) RenderWater() RenderParticles() RenderObjects() RenderLastDied() RenderPlayer() RenderState() RenderBubble() end end