1,]']>S}W}p8d%qy)6o;]AsVl0D] `2TvͫgE# ""+""""1  3 ""##2#"# Э Э ```N @MMN@ ``ffff``ffffff```ffff``ffff``ffff``ffff`VeUUeVff``ff``  ""=2# -"" 2#=""  " " "" """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""TTTTEEEETTTTEEEETTTTEEEETTTTEEEESSSS5555SSSS5555SSSS5555SSSS55552222####2222####2222####2222####................ ڻ ڻ ڻ ڻ    03043000303030U430U43030333C03333CU3CU33   @@@@@@@@@@@@ UUPUPUUUppwwpwpwppww     ````` ``ffffffff```ffff``ffff"""B"B """B"B DDD@@@  @@@@@@@@ DDDD@D@@@D@@@@@@DDDDDDDD@D@D@@DDD@@@DDD@@DDD@M@M@M@DDD@@@@@ " " $"$" " " $"$" M@@@@@@@@@@@ @@ @@ @@@@@D@DDDDDDD@D@@D@@@@@@ @@ @@@@@D@DD@DDDD@D@DD@DDDDDDDDD@D@DDDDDDDDDD DDD@@@    @@@@@@@@DDDD@D@D@@DDDDDD@DDDD@DDDDDD@@D@@DD@DDDD@DDD@@@@@DDDD DDDD@@@@@@@@@@@@@@@@@@@@@@DD@@DD@DDDDDDDDDD@DD@DDDDDDDDDD@D@DD eV P` eV " 3 -,,,,00=,,,, -,,, Ve " 3Ve `P - -,,, 0=,,,,-,,, @ DD=4C MDD M=DD @P`P`PP eV P` eV " 3 -,,,,00=,,,, -,,, "   " @`VeVeffffff`VeVeffffff "" 00"" Vf`ffUc`0e002" ""eeeeVVVVeeeeVVVVeeeeVVVVeeeeVVVVސ ުޠ ސ ޺ސ `VeVefff`V```f`P``P`Vf`ffUc`0e002" ""  !!!   ] UU ] UU ] ] UU ] ] UU ] ] UU ] ] UU ] UU ] ] UU ] ] UU ]  ] UU ] UU ] ] UU ``f`ff , "" , "" , "" < 33 , "" , "" 33 < 33 , "" , "" < L DD < L DD L DD < \ UU L DD L DD < UU \ UU L DD L DD < \ ff l ff \ @T@EfEff@dPf@P`VEfTffT`Ff`e``fffwwfffwffvgffwfffwwfgffvpww `fffvgffgvffgvffwwffgvfgffvpwwPfV@ffPf@dEf@Ef@Tef`f``f`F`fTfTE]]]``eeffeeff````VVffVVff````feeffeef``VV`P``Pf`VVP`U`P`PVVPVV`P`eV`PVVPVV`P`ePPeVVV`eVP`PP`eeP`eeP`PVV`P`eeeeP`PVV``PPee`eeP`V`V`eePP``PVV``PPeeVV``PPeeP`VVDDDOO@ODO@OO@DDDDDDDDDDDDDDDDDDD@DDDDDODDDODDDODDDODDDODDDODDDDDD@DDDDO@O@ODO@OO@DDDDD@DDDDODDODDODODODDODDDDD@DDDD@DD@DD@DDDDDODDODDODODODDODDDDD@DDDD@DDDDDDODDDO@DODO@O@DO@DODDDDDD@DD@D@DDDDDODDODDODODDODDODDDDD@DDDD@D@DD@DDDDDDDODDODDODODDODDOD@DDD@DDDD@D@DDDD@DODDODDODDODDODDODDDDD@DD@DD@DD@DD@DD@DDDDDODDODDODDODDODDODDDDD@DD@DD@DD@DD@DD@DDDDDODDODDODDODDODDODDDDD@DD@DD@DD@DD@DD@DDDDDODDODDODDODDODDODDDDD@DD@DDDDDDDDDDDDDODDODDODDODDODDODDDDDDDDDDD@DD@DD@DDDDDOD@ODODDODDODDODDDDDD@DDD@DD@DD@DDDDDODOD@ODDO@DO@DO@DDDD@DDDDD@D@D@DDDODDODDODDOD@ODDODDDDDDDDDDD@DDD@DDDDDDDDO@D@O@D@O@D@O@D@DDDD@@DDDDDDODDODDODODDODDODDD@DDDD@D@DDDD@DDODDODODDODDODDDODDD@DDDD@D@DDDD@D@DDDDODDODDODODODODDDD@DDDD@DD@D@DDDDODDODDODODODDODDDDD@DDDD@DD@DD@DDDDDODOODDOODDODDD@D@DD@@@DD@@@DDDODDDODDDODDDODDOO@DDDDDD@D@DDDDD@DDDODDDODDDODDDODDDOODDDDDDDDDDDDDDD@OOODDODDODDODDDDD@DD@DD@DD@DDDDDOO@D@O@D@O@D@O@D@O@D@DDDDDDDDDDDODDODOD@ODODDODDDD@DDDDDDDDD@DD@DD@DDODODODDODDODDODDDDD@D@D@DD@DD@DD@DDDDDODDODDODODODODDDD@DD@DD@D@D@D@DDDDODDOODDOODDODDDDDD@@@DD@@@DD@DDDDOO@ODODODODDDDDDDDDOOODDDODDDODDDODDDDDD@DDDDDDDDDDDDDDDDDDODDODDODDODDODDODDDDD@DD@DD@DD@DD@DD@DDDDDODDODDODDODDODDODDD@DD@DD@DD@DD@DD@DDDDDODDODDODDODDODDODDD@DD@DD@DD@DD@DDDDDODDODDODDODDODDODDDDD@DD@DD@DD@DD@DDDDDODDDODDDODDDODDDODDDODDDDDDDDDDDDDDDDDDDDDDDDO@DOO@ODO@O@DDDD@DDDDOODDODDODDODDODDDDD@DD@DD@DD@DD@DD@DDDDDOODDODDODDODDODDDDD@DD@DD@DD@DD@DDDDDODDODDODDODDODDODDDDD@DD@DD@DD@DD@DDDDDODDDODDDODDDODDDODDDODDDDDDDDDDDDDDDDDDDDDDDDDDD'00KKKKKKKBBAA0KKKKKKKKCBAAAAA0JJJJJJJB0KKKKKKKKB0K0'''KKKKKKKCCAA'KKKKKKKKBCAAAAA'JJJJJJJC'HIHIHIHIF'''''KKKKKKKBBAA'KKKKKKKKCBAAAAA'JJJJJJJB''''''KKKKKKKCCAA'KKKKKKKKBCAAAAA'JJJJJJJC'''0''KKKKKKKBBAA'KKKKKKKKCBAAAAA'JJJJJJJB''''''KKKKKKKCCAA'IHIHIHIHFCAAAAA'JJJJJJJC''''''KKKKKKKBBAA'BAAAAA'JJJJJJJBEHIHI''''''KKKKKKKCCAA'GHDAAA'JJJJJJJCCJJJJ'EHIH'klmtuvPPPKKKKKKKBBAAPBAAAPJJJJJJJBBJJJJPCKKKPnopMMwxy'''KKKKKKKC56CAA'CAAA'JJJJJJJGHDCJJJJ'BKKK'qrsz{|''0'KKKKKKKBBAA'GHIH'JJJJJJJJJBBJJJJ0IHIHIHIHDCKKK00''()''KKKKKKKCCAA''JJJJJJJJJCCJJJJ'KKKKKKKKCBKKK'''*+,''KKKKKKKBBAA''JJJJJJJJJBBJJJJ'KKKKKKKKBCKKK'''-./''KKKKKKKCCAA''JJJJJJJJJCCJJJJ'KKKKKKKKCBKKK'STU''''HIHIHIHNOHI'0HIHIHIHIHNOHIHI'KKKKKKKKBCKKK'VWXM''''LMML''LMML'KKKKKKKKCOHIH'YZ[' 'N'HIHIHIHNOHI0'HIHIHIHIHNOHIHI'KKKKKKKKBMLB\]^E!"#'''KKKKKKKCCAA''KKKKKKKKKCCAAAA'KKKKKKKKCOHIH''$%&'''KKKKKKKBBAA''KKKKKKKKKB78BAAAASKKKKKKKKBCKKK'''''KKKKKKKCCAA'JKKKKKKKKKCCAAAA'KKKKKKKKCBKKK'''''KKKKKKKBBAAS'KKKKKKKKKB56BAAAA'IHIHIHIHFCKKK'''''KKKKKKKC56CAA''KKKKKKKKKCCAAAA0GHIHTSTU_`a''''KKKKKKKBBAA''KKKKKKKKKBBAAAA''VWXMMbcd'0''KKKKKKKCCAA''KKKKKKKKKCCAAAA'EHIHIHIHIH'YZ[efg''''KKKKKKKBBAA''KKKKKKKKKBBAAAA'78CAAAAAAAAA'\]^^ij''''KKKKKKKCCAA''KKKKKKKEHFGHDAAHBAAAAAAAAA'''''KKKKKKKB BAA''KKKKKKKB '()BAA'CAAAAAAAAA' 'N''KKKKKKKC!"#CAA''KKKKKKKC!"#*+,CAA'BAAAAAAAAA' ''''KKKKKKKB$%&BAA''KKKKKKKB$%&-./BAA056CAAAAAAAAA'STU _`a''''KKKKKKKCCAA''KKKKKKKGHDEHFAA'BAAAAAAAAA'VWXMMbcd''''KKKKKKKBBAA''KKKKKKKKKBBAAAA'CAAAAAAAAA'YZ[efg''''KKKKKKKCCAA''KKKKKKKKKCCAAAA'BAAAAAAAAA'\]^^ij''''HIHIHIHFGHI''KKKKKKKKKBBAAAA'CAAAAAAAAA'''''0'KKKKKKKKKCCAAAA'BAAAAAAAAA'''''''KKKKKKKKKBBAAAANCAAAAAAAAA'''''''KKKKKKKKKCCAAAA0BAAAAAAAAA'''''''KKKKKKKKKBBAAAA'CAAAAAAAAA'''''N'KKKKKKKKKCCAAAA'BAAAAAAAAA'klm''''''KKKKKKKKKBBAAAA'CAAAAAAAAA'nopM''''''KKKKKKKKKCCAAAAHGHIHIHIHIH'qrs'0''''KKKKKKKKKBBAAAA'ML'''''''KKKKKKKKKCCAAAA0EHIHIHIHIH'''''''KKKKKKKKKBBAAAA'CJJJJJJJJJ0''''''KKKKKKKKKCCAAAA'BJJJJJJJJJ''P''''KKKKKKKKKBBAAAA' CJJJJJJJJJ'''''''KKKKKKKKKCCAAAAH!"#BJJJJJJJJJ''''0''KKKKKKKKKB78BAAAA'$%&CJJJJJJJJJ'''''''KKKKKKKKKCCAAAA0BJJJJJJJJJN''''0'HIHIHIHIHFGHIHI'CJJJJJJJJJ'tuv'''''''BJJJJJJJJJ'Mwxy'''E'''CJJJJJJJJJ'z{|''''''HBJJJJJJJJJT'0'''''CJJJJJJJJJ0'''''EHIHIHIH0'BJJJJJJJJJ'''''PIHIHIHIHDCKKKKKKK'0CJJJJJJJJJ''''''JJJJJJJJCBKKKKKKK''BJJJJJJJJJ''E'''JJJJJJJJBCKKKKKKK''CJJJJJJJJJ'''''JJJJJJJJCBKKKKKKK''78BJJJJJJJJJ''''''JJJJJJJJBCKKKKKKK'QCJJJJJJJJJ''''''JJJJJJJJCBKKKKKKK'5656'GHIHIHIHIH''''''JJJJJJJJBCKKKKKKK''M'''''JJJJJJJJCBKKKKKKKI'EHIH''''''JJJJJJJJBCKKKKKKK''CKKK' '''''JJJJJJJJC OHIHIHIH''BKKK'!"#'''''JJJJJJJJB!"#ML''CKKK'$%&0''''JJJJJJJJC$%&OHIHIHIH0'56BKKK'''0''JJJJJJJJBCKKKKKKK''CKKK''''''JJJJJJJJCBKKKKKKK'EHI'BKKK''' ''()'0JJJJJJJJBCKKKKKKK'78CJJ'CKKK'D'!"#'*+,''JJJJJJJJCBKKKKKKK'BJJ'BKKK''0$%&'-./''JJJJJJJJBCKKKKKKK'CJJ'CKKK'0''''JJJJJJJJCBKKKKKKKNBJJ'BKKK'''()'N'JJJJJJJJJBCKKKKKKK'CJJ'CKKK''*+,''''JJJJJJJJCBKKKKKKK'GHI'9:;BKKK''-./''''JJJJJJJJBCKKKKKKK''@CKKK'N''0'JJJJJJJJCBKKKKKKK''OHIH''''''JJJJJJJJBCKKKKKKK''ML''''''JJJJJJJJCBKKKKKKK''OHIH0'''''IHIHIHIHFCKKKKKKK''CKKK''''N'BKKKKKKK0'BKKK''''''CKKKKKKK'56'CKKK''N'''BKKKKKKK''78BKKKTEHIHIH'''''CKKKKKKK''CKKK'IHIHIHDCKKKKK'0'''BKKKKKKK''BKKK'JJJJJJCBKKKKK'''''CKKKKKKKD'CKKK'JJJJJJBCKKKKK'''''GHIHIHIH''HIHIHDBKKK'JJJJJJCBKKKKK'''''''JJJJJCCKKK'JJJJJJBCKKKKK'H'''''JJJJJBBKKK'JJJJJJCBKKKKK'''''''JJJJJCCKKK'JJJJJJB9:;CKKKKK'''''078'HIHIHF9:;BKKK'JJJJJJC@GHIHIH'0'''''LM@CKKK'JJJJJJB'''''''HIHIHDBKKK0JJJJJJCEHIHIHIH''''''0KKKKKCCKKK'JJJJJJBCJJJJJJJ''0''QEHIHIHI'KKKKKBGHIH'JJJJJJCBJJJJJJJ'H'''()''IHIHIHIHD78CJJJJJJ'KKKKKC'JJJJJJBCJJJJJJJ'0'0*+,''AAAAAAAACBJJJJJJ'KKKKKB EHIHIHIRJJJJJJCBJJJJJJJ''''-./''AAAAAAAABCJJJJJJ'KKKKKC!"#CKKKKKK'IHIHIHFCJJJJJJJ''Q''7856'AAAAAAAACBJJJJJJ'HIHIHF$%&78BKKKKKK'BJJJJJJJ'''''EHIHIH'AAAAEHIHFCJJJJJJCCKKKKKK'CJJJJJJJ'E'''CKKKKK'AAAAC OHIHIHI'BKKKKKK'BJJJJJJJ'''''BKKKKK'AAAAB!"#ML'CKKKKKK'56CJJJJJJJ''''0CKKKKK'AAAAC$%&9:;OHIHIHI056GHIHIHINGHIHIHIH'''D'BKKKKK0AAAAGHIHD@CJJJJJJ'EHIHIHIHIHI''''''CKKKKK'AAAAAAAACBJJJJJJ'CAAAAAAAAAA''''''BKKKKK'AAAAAAAABCJJJJJJ'BAAAAAAAAAA'''''NCKKKKK'AAAAAAAACBJJJJJJTCAAAAAAAAAAL'''''BKKKKKCAAAAAAAABCJJJJJJ0BAAAAAAAAAA''''''CKKKKK'AAAAAAAACBJJJJJJ'CAAAAAAAAAA''''''BKKKKK'AAAAAAAABCJJJJJJ'BAAAAAAAAAA''''''CKKKKK'AAAAAAAACBJJJJJJ'CAAAAAAAAAA'HIHIHIHID'0'''GHIHIH'AAAAAAAABCJJJJJJ'BAAAAAAAAAA'AAAAAAAAC'''''QAAAAAAAACBJJJJJJ'CAAAAAAAAAA'AAAAAAAAB''''''AAAAAAAABCJJJJJJNBAAAAAAAAAA'AAAAAAAAC'D0''78'AAAAAAAACGHIHIHI0CAAAAAAAAAA'AAAAAAAAB00'JPP''IHIHIHIHF'BAAAAAAAAAA'AAAAAAAAC''''PP'''CAAAAAAAAAA'AAAAAAAAB''''P''EHIHIH'GHIHIHIHIHI'AAAAAAAAC78''''''CAAAAA'9:;'AAAAAAAABEHIANNI'CIHIHIHIHDBAAAAA'@EHIHI'AAAAAAAACCAA''''''JJJJJJJJCCAAAAA'CKKKK'AAAAAAAABBAA'''''56'JJJJJJJJBBAAAAAPBKKKK'AAAAAAAACCAA''()''() '''78'JJJJJJJJCCAAAAA'CKKKK'AAAAAAAABBAA'*+,'*+,!"#''''IHIHIHIHNBAAAAA'BKKKK'AAAAAAAACCAA'-./'-./$%&''''LMCAAAAA'CKKKK'HIHIHIHHNOHI''''''IHIHIHIHNBAAAAA'BKKKK'LMML'''''AAAAAAAACCAAAAA'CKKKK'HIHIHIHHNOHI0'''''AAAAAAAABBAAAAA'HIHIHIHDBKKKK'KKKKKKKKCCAA''''''AAAAAAAACCAAAAA'JJJJJJJCCKKKK'KKKKKKKKBGHI'''''EHI'AAAAAAAABBAAAAA'JJJJJJJBGHIHI'KKKKKKKKC56'''''CAA'AAAAAAAACCAAAAA'JJJJJJJC'KKKKKKKKBS''''BAA'AAAAAAAABBAAAAA'JJJJJJJB'KKKKKKKKC'''''CAA'AAAAAAAAC56CAAAAA'JJJJJJJC'KKKKKKKKB''''HIHIHIHDBAA'IHIHIHIHFBAAAAA'JJJJJJJB'KKKKKKKKC'''''KKKKKKKCCAA'IHIHIHIHDCAAAAA'JJJJJJJC'KKKKKKKKB'''''KKKKKKKBBAA'KKKKKKKKCBAAAAA'JJJJJJJB'KKKKKKKKC'''''KKKKKKKCCAA'KKKKKKKKBCAAAAA'JJJJJJJC'KKKKKKKKB'           IFEDCCDEGJJIGEDCDFIIHFED D E F H H I------------------------------t                                     %IFEDCC=DEGJJIGEDCDFII%HFEDDEFHHIIFEDCCDEGJJIGEDCDFIIHFEDD E F H H IB B BCC D DDEEEFFFGGGHHIIJKKLLLMNO`PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPK K LLL L OOOOOOOOOOOOOOOOOOOOOOOO                       L @TT0T4AAAA0AGTTTTTTTTTTT0T@HUUUU0ATTTTT0@TTTTTDttttttttttttttttttttKt t t t t t t t t t t t t t t t tttt ``` `V>   ` ` ` ` ` ` `ff``f` VUUfP`f`fVeU``fP UUU`VUVUUUfPP`UeP `ffUUPUVUPUPeVPeU` fwwf UfUfPUeUePeUUPUP ffffff`f`fffwffffff`f`fffffPUUUeVPUPUUUPUP UUUUUUVUVUfvgfUU`Ue`UUPUVUUUUU PU UUUfUeUefwffPPPPUUUUP UfUfUUPUPUfwwfPPPUeUUfUfP UUUUUffUfUgffvPPeV`VUUUUUUP UUUUUUUUpwwPUPUUUUP ``f` f`ff`f`ff`f `````` f`f`f` ff```fff`  ff U`fU UfVUU UUUeef UUfUUU UPUUPU N `ff` @ `````f ```MM ``f`N@ ffffffff ``ff`` UUUUUUUU fUfffffffffffUUffffUUfffffUUUf UUUUUUUUUUUUUUUUUUUUUUUUUU UUUUUffUUUffUUUUfffUUUUUUfUUUffUUUUU UUUUUUUUUUUUUUUUUUUUUUUUUU UUUUUUfUUUUUUUUUUUUUUfUUUUUU UUUUUUUUUUUUUUUUUUUUUUU UfUUUUUfUUUffUUUUUffUUffUUUfUffUUfU UUUUUUUUUUUUUUUUUUUUUUUUUUU UUUUUUUUUUUUUUUUUUUUUUUUUUU        ` ` `` ` `         ` ` ` ` ` ` ` ` ` ` ` `             ` ` ` ` `  `  ` ` ` ` ` ` Р  ` ` `f `f Р   `f  `f   f f         ` ` ` ` ` ` ` ` ` ` ` `               ` ` ` ` ` ` ` Ve ` ` ` ` Ve ` ` ff   ff   ff         ` ` ` ` ` ` ` ` ` ` ` `         -- title: MINIRALLY -- author: Mauricio Fontana (mfontanadev.github.io) -- desc: Simulation of an old real toy called Mini Rally -- script: lua -- saveid: 8_bit_minirally -- tutorial: https://github.com/nesbox/TIC-80 -- version: 1.7.0 -- inspiration: road figther, moto race -- motivation: learn TIC-80, lua and share a game. -- -- WARNING: this file frendermust be kepft under -- 64kB (TIC-80 limit)! -- **************************************** -- FUNNY CODE EXPLANATION -- **************************************** -- Hi there, it a pleasure to me see you here: -- Let me describe a little about the code structure and concepts (I will try to make a tutorial) -- -- CODE STRUCTURE: blocks of code from top to down -- GLOBAL DEFINITIONS: defines variables. -- CLASSES IMPLEMENTATION: implements view classes and helper classes -- GLOBAL FUNCTIONS: extension of primitive functions used globally -- TEST FUNCTIONS: set of functions that used as unit tests -- CONSTRUCTORS: creation of instances for views. -- UNIT TEST: function that call to each one of the unit test functions -- MAIN CYCLE: TIC() function with main cycle logic. -- CODE CONCEPTS: high level definitions (each concept is a world in itself) -- 1-Each screen of the game has his own class called View (ie. PlayView) -- 2-Each View has is own cycle, this means Read Input, Process Logic, Render -- 3-Data shared between views is stored in Game class. -- 4-Navigation (how to change from one screen to another), for this particular -- game I use a transitioner called ViewTransition. -- 5-Scroll class controls the background. -- 6-The background is not a copy from tile map, it is the union of intro -- background plus level info background plus play background an so on. -- 6.1-The background is dumped to a table and rendered character to character. -- 6.2-All those complications just to simulate a continuous up and down scroll. -- **************************************** -- GLOBAL DEFINITIONS -- **************************************** -- used when unit test are executed and want avoid -- game trace messages. runUnitTest = false DEBUG={ -- if false, skeep all debug flags has no effect. DEBUG_MODE = false, -- if true then the screen shows lines with data from variables. SHOW_INFO = true, -- like God mode, the player does not die. SKEEP_CRASH = true, -- like God's car, it does not consume energy. RESTORE_ENERGY = true, -- current playing level START_AT_LEVEL = 4 } -- tuple Tu = {x = 0, y = 8, z = 0} -- settings Sett = { snd = true, mus = true } -- global configuration that not square in any category CFG = { -- velocity of the animation used when the player charges energy. ENERGY_FLOW_SPEED = 100, -- max frames the animation used when the player charges energy. ENERGY_FLOW_MAX_FRAMES = 3, -- text in the intro screen. CREDIT_TITLE = "https://mfontanadev.github.io/", -- scale parameter for the function that draw text. SMALL_FONT_SIZE = 1, -- scale parameter for the function that draw text to get text bigger. BIG_FONT_SIZE = 2, -- number of animations in the win screen. WIN_ANIM_SIZE = 8, -- amount of miliseconds before exit from view level info MI_SLEEP = 5000, -- amount of miliseconds before exit from view end of level EOF_SLEEP = 2000, -- amount of miliseconds before exit from view game over GAME_OVER_SLEEP = 2000, -- amount of miliseconds before exit from view win (five minutes) WIN_SLEEP = 1000*60*5, -- input keys constans IK = { UP = 0, DOWN = 1, LEFT = 2, RIGHT = 3, BUTTON_X = 4 } } -- views, each view is a class that represent one screen with -- his own logic. V = { BOOT = 0, INTRO = 1, -- title screen MI = 2, -- level info PLAY = 3, EOL = 4, -- end of level GAMEOVER = 5, WIN = 6, -- beat entire game TRAN = 7 -- transition } -- texts used in views TXT = { PLAY={ SCORE="SCORE:", SIGN="SIGN" }, EOL={ TITLE_1="Level", TITLE_2="Completed", SCORE="Score=" }, GAMEOVER={ TITLE_1="GAME", TITLE_2="OVER" }, WIN={ TITLE_1="THE END!", TITLE_2="Thanks for playing!", TITLE_3="SCORE" } } -- player PLR = { -- velocity MIN_SPEED = 2, MAX_SPEED = 30, -- Side velocity in pixels. SIDE_SPEED = .5, -- number of scrolls to get one unt of energy decrease. ENERGY_DECREASE = 7, -- amount of energy each time a cell is converted. ENERGY_PER_CELL = 5, -- number of frames to make crash animation CRASH_ANIMATION_FRAMES = 4, -- delay between each frame (60fps/6) CRASH_ANIMATION_SPEED = 10, -- initial x coord cell position PLAYER_INITIAL_POSITION_X = 9, -- right position where horizontal scroll startCrashAnimation RIGHT_POSITION_SCROLL_STARTS = 72, -- default player lives initialization PLAYER_LIVES = 3, -- default cell colected initialization DEFAULT_CELLS_COLLECTED = 0, -- how many is the reward after collect a cell POINTS_X_CELL = 10, -- points to be substracted by each cell collected when is the end of level counting POINTS_X_CELL_PENALTY = 5, -- (in ms) animation speed when player is interchanging cell by energy CONVERTING_CELLS_SPEED = 150 } -- road ROAD = { -- offset index to help mapping from string literal to tile map OFFSET_CHAR = 48, -- lenhtg of the tile map (8*17) LEVEL_ROWS = 136, -- initial x position of the road (xpos=INITIAL_X_OFFSET*8) INITIAL_X_OFFSET = 4, -- number of cols of the section where player runs. FRAME_COLS = 19, -- number of rows of the section where player runs. FRAME_ROWS = 17, -- offsets -- position y (in pixels) where start a section of the road, used to transition scrolling. -- Transition is implemented moving by 8 pixels each time, that wy useg times 8 O = { -- intro screen. MI_SCREEN = 15*8, -- 15+15 (skeep intro and level info) PLAY_SCREEN = 30*8, -- 15+15+136 (skeep intro, level info and play) EOL_SCREEN = 166*8, -- 15+15+136+15 (skeep intro, level info and play) DUAL_SCREEN = 182*8 }, -- lowest position X when stars scrolling to left (px) MARGIN_LEFT = 32, -- highest psotion x when starts scrolling right (px) MARGIN_RIGHT = 72, -- colum in the tile map where is located data for the signal indicator SIGNAL_OFFSET_X = 29, -- how many ceros to the left are shown in the cells panel. CELLS_LEADING_CEROS_QTTY = 2, -- how many pixels skeep the scroll in each step SCROOL_DOWN_SPEED_AFTER_COLLITION = 8, -- how many ceros to the left are shown in the score section. SCORE_LEADING_CEROS_QTTY = 6 } -- panel, it a porcion of the screen, it is useful because -- can be placed sprintes relative to its left-top corner. PL = { -- score panel, section of the screen where score is deisplayed SCORE = {X = (ROAD.FRAME_COLS+3)*8, Y = 8}, -- road panel, section of the screen where player runs ROAD = {X = 8, Y = 8} } -- generic direcction constants: used in scroll DIR = { UP = 1, DOWN = 2, LEFT = 3, RIGHT = 4 } -- scroll direction facility (in px) SCRDIR = { [DIR.UP] = {X = 0, Y = 1}, [DIR.DOWN] = {X = 0, Y = -1}, [DIR.LEFT] = {X = -1, Y = 0}, [DIR.RIGHT] = {X = 1, Y = 0} } -- music tracks BGM = { BOOT = 0, INTRO = 0, MI = 2, PLAY = 0, EOL = 4, GAMEOVER = 6, WIN = 7, TRAN = -1 } -- bgm for each screen BGMM = { [V.BOOT] = BGM.BOOT, [V.INTRO] = BGM.INTRO, [V.MI] = BGM.MI, [V.PLAY] = BGM.PLAY, [V.EOL] = BGM.EOL, [V.GAMEOVER] = BGM.GAMEOVER, [V.WIN] = BGM.WIN, [V.TRAN] = BGM.TRAN } -- sound specs SND = { CRASH = {sfxid = -1, note = "C-1", dur = -1, ch = 1}, CRASH_END = {sfxid = 1, note = "E-4", dur = 30, ch = 0}, COIN = {sfxid = 4, note = "F-4", dur = 10, ch = 0}, COUNT_DOWN = {sfxid = 3, note = "C-5", dur = 10, ch = 0}, COUNT_DOWN_END = {sfxid = 3, note = "G-5", dur = 10, ch = 0}, ENERGY_UP = {sfxid = 5, note = "C-6", dur = 10, ch = 0}, KEY_PRESS = {sfxid = 16, note = "A-6", dur = 25, ch = 0}, GAS_COLLITION = {sfxid = 7, note = "C-5", dur = 40, ch = 0}, TOLL_COLLITION = {sfxid = 7, note = "E-5", dur = 20, ch = 0} } -- Tiles -- 0: empty -- 1-255: solid decorative T = { NOT_SET = 0, HOLE = 1, -- hole in the floor BLUE_CAR = 3, -- blue car EMPTY = 7, -- road floor RADIO_ACTIVE = 6, -- player must collect as much as he can TOLL = 8, -- end of level checkpoint GAS_STOP = 165 -- convert radioactive into energy } -- sprites S = { PLR = { -- player sprites SCROLL = 258, CRASH = 266 }, EN = { -- energy indicator EMPTY = 274, ICON = 325, FLOW_IND = 326 }, TREE = 263, -- used in final animation. CROSS = 271, -- for intro animacion GRAY_CORNER = 284, -- panel's border GRAY_LINE = 285, -- panle's border TICK = 287, -- for intro animation SPEED_EMPTY = 288, -- reset speed indicator SPEED = 305, -- first index of speed indicator WHEEL = 307, -- first index of direction indicator, taken 2x2 BUTTON_A = 309, -- button to start game. LETTER_M = 310, LETTER_I = 311, LETTER_N = 312, LETTER_R = 313, LETTER_A = 314, LETTER_L = 315, LETTER_Y = 316, NUMBERS = {336, 337, 338, 339, 340, 341, 342, 343, 344, 345}, SIGN = {416, 418, 420, 422, 424, 426, 428, 430, 448, 450, 452, 454, 456, 458, 460, 462, 480, 482, 484, 486, 488}, SIGN_EMPTY = 488 } -- intro anim states IAS = { WALKING = 1, CRASH_START = 2, CRASH_END = 3, STOP = 4, WAIT = 5, END = 6 } -- intro animations IANIM = { -- animation definitions -- There are six sub animations that are shown in the intro view. -- The player goes drom down to top until reach the objective icon. -- If the objective is good then the player win. If the objective is -- bad the player dies. -- slotPosX: one of the six colums where animation is happening. -- objectiveSpriteId: id of the sprite objective. -- convertedSpriteId: id of the sprite after reach the objetive. -- movementCounter: y position of the player. -- animState: walking, crash_start, crash_end ... -- crashOnObjective: true=show X on the objective, false=show tick on the objective. -- nextAnimation: pointer (wow, such a word here) to the next animation. DEF = { [0] = { slotPosX = PL.ROAD.X+16, objectiveSpriteId = T.RADIO_ACTIVE, convertedSpriteId = S.TICK, movementCounter = 0, animState = IAS.WALKING, crashOnObjective = false, nextAnimation = nil }, [1] = { slotPosX = PL.ROAD.X+48, objectiveSpriteId = T.GAS_STOP, convertedSpriteId = S.TICK, movementCounter = 0, animState = 0, crashOnObjective = false, nextAnimation = nil }, [2] = { slotPosX = PL.ROAD.X+72, objectiveSpriteId = T.TOLL, convertedSpriteId = S.TICK, movementCounter = 0, animState = 0, crashOnObjective = false, nextAnimation = nil }, [3] = { slotPosX = PL.ROAD.X+80, objectiveSpriteId = T.TOLL, convertedSpriteId = S.TICK, movementCounter = 0, animState = 0, crashOnObjective = false, nextAnimation = nil }, [4] = { slotPosX = PL.ROAD.X+104, objectiveSpriteId = T.HOLE, convertedSpriteId = S.CROSS, movementCounter = 0, animState = 0, crashOnObjective = true, nextAnimation = nil }, [5] = { slotPosX = PL.ROAD.X+136, objectiveSpriteId = T.BLUE_CAR, convertedSpriteId = S.CROSS, movementCounter = 0, animState = 0, crashOnObjective = true, nextAnimation = nil }, [6] = { slotPosX = 0, objectiveSpriteId = 0, convertedSpriteId = 0, movementCounter = 0, animState = 0, crashOnObjective = false, nextAnimation = nil } } } -- info messages index IMI = { CONVERTING = 0, START_IN = 1, EMPTY = 2 } INFO_MSGS = { [IMI.CONVERTING] = { l1 = " CONVERTING", l2 = "" }, [IMI.START_IN] = { l1 = " START IN", l2 = "" }, [IMI.EMPTY] = { l1 = " ", l2 = " " } } -- transition indexes, availables transitions from one view to another. TR = { -- states S = { RESET = 1, TO_MI = 2, START_LEVEL = 3, GAME_OVER = 4, LIVE_LOST = 5, EOL = 6, NEXT_LEVEL = 7, GAME_OVER_TO_INTRO = 8, WIN = 9, WIN_TO_INTRO = 10 }, -- speed of the scroll (ms) SPEED = 100, -- amount of pixel in each scroll movement (px) DETAIL = 8 } -- internal view states VST = { NOT_SET = 0, COUNT_DOWN = 1, GAS_COLLITION = 2, CONVERTING_CELL = 22, PLAYING = 3, CRASHING = 4 } -- Level info. -- There are 8 (colums from 0 to 7) levels in the cart, -- each one goes from bottom to top (17 rows by 8 screen = 136 rows) LVL = { {NAME = "1-1", MUS = BGM.PLAY}, {NAME = "1-2", MUS = BGM.PLAY}, {NAME = "1-3", MUS = BGM.PLAY}, {NAME = "1-4", MUS = BGM.PLAY}, {NAME = "1-5", MUS = BGM.PLAY}, {NAME = "1-6", MUS = BGM.PLAY}, {NAME = "1-7", MUS = BGM.PLAY}, {NAME = "1-8", MUS = BGM.PLAY} } -- game flow definition TRA = { [TR.S.RESET] = { target = nil, indexFrom = 0, indexTo = 0 }, [TR.S.TO_MI] = { target = V.MI, indexFrom = 0, indexTo = ROAD.O.MI_SCREEN }, [TR.S.START_LEVEL] = { target = V.PLAY, indexFrom = ROAD.O.MI_SCREEN, indexTo = ROAD.O.PLAY_SCREEN }, [TR.S.GAME_OVER] = { target = V.GAMEOVER, indexFrom = -1, indexTo = ROAD.O.MI_SCREEN }, [TR.S.LIVE_LOST] = { target = V.PLAY, indexFrom = -1, indexTo = ROAD.O.PLAY_SCREEN }, [TR.S.EOL] = { target = V.EOL, indexFrom = ROAD.O.EOL_SCREEN, indexTo = ROAD.O.EOL_SCREEN }, [TR.S.NEXT_LEVEL] = { target = V.MI, indexFrom = ROAD.O.EOL_SCREEN, indexTo = ROAD.O.DUAL_SCREEN }, [TR.S.GAME_OVER_TO_INTRO] = { target = V.INTRO, indexFrom = ROAD.O.MI_SCREEN, indexTo = 0 }, [TR.S.WIN] = { target = V.WIN, indexFrom = ROAD.O.EOL_SCREEN, indexTo = ROAD.O.DUAL_SCREEN }, [TR.S.WIN_TO_INTRO] = { target = V.INTRO, indexFrom = ROAD.O.MI_SCREEN, indexTo = 0 } } -- game state Game = { -- view v = V.BOOT, -- how good you are saving the planet from toxic waste. score = 0, -- one level represents 8 screens of 17 rows. levelNro = 1, -- avoid user press any key, for example when transition happen. stopKeyPressInput = false, -- player's lives (this is part of Game and not of Player to avoid clear it whne player reset) lives = PLR.PLAYER_LIVES, -- player's cells colelcted (this is part of Game and not of Player to avoid clear it whne player reset) cellsCollected = PLR.DEFAULT_CELLS_COLLECTED, -- internal states, like finite automata, states changes execution flow. viewState = VST.NOT_SET } Game.coinCollected = function(coins, penaltyPoint) Game.cellsCollected = Game.cellsCollected+coins Game.score = Game.score+(PLR.POINTS_X_CELL-penaltyPoint)*math.abs(coins) end Game.resetToStartANewGame = function() Game.score = 0 Game.levelNro = 1 Game.stopKeyPressInput = false Game.lives = PLR.PLAYER_LIVES Game.cellsCollected = PLR.DEFAULT_CELLS_COLLECTED if (validateDebugFlag(DEBUG.DEBUG_MODE)) then Game.levelNro = DEBUG.START_AT_LEVEL end Game.restoreLevel() end Game.nextLevel = function() if (Game.levelNro < #LVL) then Game.levelNro = Game.levelNro+1 Game.restoreLevel() TICF.startTransition(TR.S.NEXT_LEVEL) else TICF.startTransition(TR.S.WIN) end end Game.restoreLevel = function() createLongBackgroundForLevel(Game.levelNro) end -- **************************************** -- CLASSES IMPLEMENTATION -- **************************************** -- Class scroll Scroll = {} Scroll.new = function() local self = {} -- vertical position (in px) of the background used as road. self.bkgOffY = 0 -- horizontal position (int px) of the background used as road. self.bkgOffX = 0 -- background used as road (buffer with data from tile map) self.bkg = {} self.render = function() local offsetXMod8 = self.bkgOffX % 8 local offsetYMod8 = self.bkgOffY % 8 local indexX = self.getOffsetXIndex(self.bkgOffX) local indexY = self.getOffsetYIndex(self.bkgOffY) for y = 0, ROAD.FRAME_ROWS,1 do for x = 0, ROAD.FRAME_COLS+1,1 do sprIndex = scroll.getBackgroundTile_toByte(x+1+indexX, indexY-y, ROAD.OFFSET_CHAR) if (sprIndex > 0) then spr(sprIndex, (x+1)*8-offsetXMod8, (ROAD.FRAME_ROWS-y-2)*8+offsetYMod8) end end end end self.getBkgOffY = function() return self.bkgOffY end self.getBkgOffX = function() return self.bkgOffX end self.setBkgOffY = function(_offsetY) self.bkgOffY = _offsetY end self.setBkgOffX = function(_offsetX) self.bkgOffX = _offsetX end self.getOffsetXIndex = function(_x) return math.floor(_x / 8) end self.getOffsetYIndex = function(_y) local offsetYIndex = math.floor(_y / 8) return #self.bkg-offsetYIndex end self.deleteBackground = function() for k in pairs(self.bkg) do self.bkg[k] = nil end end self.move = function(_dir) self.moveN(_dir,1) end self.moveN = function(_dir, _speed) self.bkgOffX = self.bkgOffX+(SCRDIR[_dir].X*_speed) self.bkgOffY = self.bkgOffY+(SCRDIR[_dir].Y*_speed) end self.plrSpriteIndex = function() return S.PLR.SCROLL+(self.bkgOffY % 2) end self.getSignalIndex = function() indexY = self.getOffsetYIndex(self.bkgOffY) return self.getBackgroundTile_toByte(1+ROAD.SIGNAL_OFFSET_X, indexY-ROAD.FRAME_ROWS, ROAD.OFFSET_CHAR) end self.resetOffsetsTo = function(_offsetY) self.setBkgOffX(ROAD.INITIAL_X_OFFSET*8) self.setBkgOffY(_offsetY) end self.getTile = function(_x, _y, _asciiOffset) self.offsets(_x, _y, Tu) return self.getBackgroundTile_toByte(1+Tu.x, Tu.y, _asciiOffset) end self.getBackgroundTile_toByte = function(x, y, asciiOffset) return string.byte(string.sub(scroll.bkg[y], x, x))-asciiOffset end self.setTile = function(_x, _y, _char) self.offsets(_x, _y, Tu) scroll.bkg[Tu.y] = replace_char(1+Tu.x, scroll.bkg[Tu.y], string.char(_char+ROAD.OFFSET_CHAR)) end self.setBackgroundTile_toByte = function(_x, _y, char) scroll.bkg[_y] = replace_char(1+_x, scroll.bkg[_y], string.char(_char+ROAD.OFFSET_CHAR)) end self.offsets = function(_x, _y, _Tu) _Tu.x = self.getOffsetXIndex(_x+self.bkgOffX) _Tu.y = self.getOffsetYIndex(_y+self.bkgOffY) end self.canScrollToRight = function() return self.getBkgOffX() < ROAD.MARGIN_RIGHT end self.canScrollToLeft = function() return self.getBkgOffX() > 0 end return self end -- Class Sleep Sleep = {} Sleep.new = function(_delayMilis) local self = {} self.delayCounter = 0 self.delayMilis = _delayMilis self.frameCounter = 0 self.frameRange = 0 -- Max number of frames before loop again. self.infiniteLoop = false self.implementLogic = function() if (self.delayCounter > 0) then self.delayCounter = self.delayCounter+1 if (self.delayCounter >= self.delayMilis) then if (self.frameRange > 0) then self.frameCounter = (self.frameCounter+1) % self.frameRange end if (self.infiniteLoop == true) then self.start() else self.delayCounter = -1 end end end end self.hasEnded = function() return self.delayCounter == -1 end self.ended = function() self.delayCounter = -1 end self.start = function() self.delayCounter = 1 end self.reset = function() self.delayCounter = 0 self.frameCounter = 0 end self.setDelayMilis = function(_delayMilis) self.delayMilis = math.floor(60*_delayMilis / 1000) end self.setFrameRange = function(_frameRange) self.frameRange = _frameRange end self.setInfiniteLoop = function(_infiniteLoop) self.infiniteLoop = _infiniteLoop end self.getCurrentFrame = function() return self.frameCounter end self.setDelayMilis(self.delayMilis) return self end -- Class animation Animation = {} Animation.new = function(_id) local self = {} self.id = _id or 0 self.slotPosX = 0 self.objectiveSpriteId = 0 self.movementCounter = 0 self.maxMovement = 0 self.animState = 0 self.afterCrashSpriteId = 0 self.crashCounter = 0 self.crashIndex = 0 self.nextAnimation = nil self.implementLogic = function() if (self.animState == IAS.WALKING) then self.stateWalking() elseif (self.animState == IAS.CRASH_START) then self.stateCrashStart() elseif (self.animState == IAS.CRASH_END) then self.stateCrashEnd() elseif (self.animState == IAS.WAIT) then self.stateWait() end end -- Walking to the objective, when reach it, start crash animation. self.stateWalking = function() if (time() % 100 > 50) then self.movementCounter = self.movementCounter+1 if (self.movementCounter == 8*4) then if (self.crashOnObjective == true) then self.animState = IAS.CRASH_START self.crashIndex = 0 self.crashCounter = 0 snd(SND.CRASH) snd(SND.CRASH_END) else self.animState = IAS.CRASH_END snd(SND.COIN) end end end end -- Crash animation, when reach the end, change state self.stateCrashStart = function() self.crashCounter = self.crashCounter+1 if (self.crashCounter > PLR.CRASH_ANIMATION_SPEED) then self.crashCounter = 0 self.crashIndex = self.crashIndex+1 if (self.crashIndex >= PLR.CRASH_ANIMATION_FRAMES) then self.crashIndex = 0 self.animState = IAS.CRASH_END end end end -- Set current animation stop and trigger the next one. self.stateCrashEnd = function() self.animState = IAS.STOP if (self.nextAnimation.nextAnimation ~= nil) then self.nextAnimation.animState = IAS.WALKING else self.nextAnimation.animState = IAS.WAIT end end -- Special state, it only wait some amount of time. self.stateWait = function() self.movementCounter = self.movementCounter+1 if (self.movementCounter == 60*2) then self.animState = IAS.END end end self.render = function() if (self.animState < IAS.CRASH_END) then spr(self.objectiveSpriteId, self.slotPosX, PL.ROAD.Y+88) end if (self.animState == IAS.WALKING) then spr(S.PLR.SCROLL+(self.movementCounter % 2), self.slotPosX, PL.ROAD.Y+15*8-self.movementCounter,0) elseif (self.animState == IAS.CRASH_START) then spr(S.PLR.CRASH+self.crashIndex, self.slotPosX, (PL.ROAD.Y+15*8)-self.movementCounter,0) elseif (self.animState == IAS.STOP) then spr(self.convertedSpriteId, self.slotPosX, PL.ROAD.Y+88) end end return self end -- Class player Player = {} Player.new = function(_id) local self = {} self.id = _id or 0 self.speed = PLR.MIN_SPEED self.x = PLR.PLAYER_INITIAL_POSITION_X*8 self.y = ROAD.FRAME_ROWS*8-25 self.xLast = 0 self.yLast = 0 self.spriteIndex = 0 self.energy = 100 self.energyCounter = 0 self.speedDirection = 0 self.isCrashAnimation = false self.crashIndex = 0 self.crashCounter = 0 self.cycle = function() self.handleInputs() self.implementLogic() self.render() end self.reset = function() self.speed = PLR.MIN_SPEED self.x = PLR.PLAYER_INITIAL_POSITION_X*8 self.y = ROAD.FRAME_ROWS*8-25 self.xLast = 0 self.yLast = 0 self.spriteIndex = S.PLR.SCROLL self.energy = 100 self.energyCounter = 0 self.speedDirection = 0 self.isCrashAnimation = false self.crashIndex = 0 self.crashCounter = 0 end self.handleInputs = function() end self.implementLogic = function() end self.render = function() spr(self.spriteIndex, self.x+PL.ROAD.X, player.y+PL.ROAD.Y,0) end self.savePosition = function() self.xLast = self.x self.yLast = self.y end self.restorePosition = function() self.x = self.xLast self.y = self.yLast end self.startCrashAnimation = function() self.isCrashAnimation = true end self.endCrashAnimation = function() self.isCrashAnimation = false end self.isCrashing = function() return self.isCrashAnimation == true end self.implementCrashAnimation = function() self.crashCounter = self.crashCounter+1 if (self.crashCounter > PLR.CRASH_ANIMATION_SPEED) then self.crashCounter = 0 self.crashIndex = self.crashIndex+1 if (self.crashIndex >= PLR.CRASH_ANIMATION_FRAMES) then self.crashIndex = 0 self.endCrashAnimation() end end player.spriteIndex = S.PLR.CRASH+player.crashIndex end self.changeSpeed = function(_direction) self.speed = self.speed+_direction if (self.speed > PLR.MAX_SPEED) then self.speed = PLR.MAX_SPEED elseif (self.speed < PLR.MIN_SPEED) then self.speed = PLR.MIN_SPEED end self.speedDirection = _direction end self.getSpeedPercent = function() ret = (self.speed-PLR.MIN_SPEED+1) / (PLR.MAX_SPEED-PLR.MIN_SPEED+1)*100 return math.floor(ret) end self.getLevelDescription = function() return "" .. LVL[Game.levelNro].NAME .. "" end self.stopCar = function() self.energyCounter = 0 self.speed = PLR.MIN_SPEED self.speedDirecton = 0 end self.addEnergyFromCell = function() self.energy = self.energy+PLR.ENERGY_PER_CELL if (self.energy > 100) then self.energy = 100 end end self.moveLeft = function() -- temporal left margin variable local tLM = 0 self.x = self.x-PLR.SIDE_SPEED if (self.x <= tLM+ROAD.MARGIN_LEFT and scroll.canScrollToLeft()) then -- scroll road to left scroll.move(DIR.LEFT) -- reverse x position to slow down scroll speed self.x = self.x+PLR.SIDE_SPEED*2 else -- avoid overload of position x if (self.x < tLM) then self.x = tLM end end end self.moveRight = function() -- temporal right margin variable local tRM = ROAD.FRAME_COLS*8 self.x = self.x+PLR.SIDE_SPEED if (self.x >= tRM-ROAD.MARGIN_LEFT and scroll.canScrollToRight()) then -- scroll road to right scroll.move(DIR.RIGHT) -- reverse x position to slow down scroll speed self.x = self.x-PLR.SIDE_SPEED*2 else -- avoid overload of position x if (self.x > tRM) then self.x = tRM end end end self.convertOneCellToEnergy = function() if (not self.canConvertEnergy()) then return false end snd(SND.ENERGY_UP) Game.cellsCollected = Game.cellsCollected-1 self.addEnergyFromCell() return true end self.canConvertEnergy = function() return (Game.cellsCollected > 0 and self.energy < 100) end return self end -- Class view boot ViewBoot = {} ViewBoot.new = function() local self = {} self.id = V.BOOT self.cycle = function() cls(0) Game.resetToStartANewGame() TICF.changeView(V.INTRO) end return self end -- Class view intro ViewIntro = {} ViewIntro.new = function() local self = {} self.id = V.INTRO self.animations = {} for i = 0, #IANIM.DEF,1 do self.animations[i] = Animation.new(i) end self.cycle = function() self.handleInputs() self.implementLogic() self.render() end self.reset = function() Game.resetToStartANewGame() scroll.setBkgOffX(ROAD.INITIAL_X_OFFSET*8) scroll.setBkgOffY(0) view_tran.resetTransition() for i = 0, #IANIM.DEF,1 do self.animations[i].slotPosX = IANIM.DEF[i].slotPosX self.animations[i].objectiveSpriteId = IANIM.DEF[i].objectiveSpriteId self.animations[i].convertedSpriteId = IANIM.DEF[i].convertedSpriteId self.animations[i].animState = IANIM.DEF[i].animState self.animations[i].crashOnObjective = IANIM.DEF[i].crashOnObjective self.animations[i].movementCounter = IANIM.DEF[i].movementCounter if (i < #IANIM.DEF) then self.animations[i].nextAnimation = self.animations[i+1] end end end self.handleInputs = function() if (Game.stopKeyPressInput == true) then return end if btnp(CFG.IK.BUTTON_X) then snd(SND.KEY_PRESS) TICF.startTransition(TR.S.TO_MI) end end self.implementLogic = function() for i = 0, #IANIM.DEF,1 do self.animations[i].implementLogic() end if (self.animations[#IANIM.DEF].animState == IAS.END) then self.reset() end end self.render = function() scroll.render() if (view_tran.isAnimating() == false) then -- Draw title px = 17 py = 32 print(CFG.CREDIT_TITLE, 35, py+35,14, false,1, true) spr(S.LETTER_M, px, py,-1, 2) spr(S.LETTER_I, px+2*8, py,-1, 2) spr(S.LETTER_N, px+4*8, py,-1, 2) spr(S.LETTER_I, px+6*8, py,-1, 2) spr(S.LETTER_R, px+8*8-3, py,-1, 2) spr(S.LETTER_A, px+10*8, py,-1, 2) spr(S.LETTER_L, px+12*8, py,-1, 2) spr(S.LETTER_L, px+14*8, py,-1, 2) spr(S.LETTER_Y, px+16*8, py,-1, 2) printFontWithShadow("(recicled)", "", 40, py+20,0,0, CFG.BIG_FONT_SIZE) -- Start button if time() % 1000 > 500 then printFontWithShadow("PRESS TO START", "", 44, py-14,0,0, CFG.SMALL_FONT_SIZE) spr(S.BUTTON_A, 76, py-16) end for i = 0, #IANIM.DEF,1 do self.animations[i].render() end self.renderInstructions() end renderFrame() end self.renderInstructions = function() cornerX = PL.SCORE.X cornerY = PL.SCORE.Y+5 printFontWithShadow("Objective:", "", cornerX, cornerY,0,0, CFG.SMALL_FONT_SIZE) print("Save the planet", cornerX, cornerY+10, 6, false,1, true) printFontWithShadow("How:", "", cornerX, cornerY+25,0,0, CFG.SMALL_FONT_SIZE) print("Collect .", cornerX, cornerY+35, 6, false,1, true) spr(T.RADIO_ACTIVE, cornerX+30, cornerY+33) end return self end -- Class level Info ViewLevelnInfo = {} ViewLevelnInfo.new = function() local self = {} self.id = V.MI self.waiting = Sleep.new(CFG.MI_SLEEP) self.cycle = function() self.handleInputs() self.implementLogic() self.render() end self.reset = function() self.waiting.start() scroll.resetOffsetsTo(ROAD.O.MI_SCREEN) end self.handleInputs = function() if (Game.stopKeyPressInput == true) then return end if btnp(CFG.IK.BUTTON_X) then snd(SND.KEY_PRESS) self.waiting.ended() end end self.implementLogic = function() self.waiting.implementLogic() if (self.waiting.hasEnded() == true) then TICF.startTransition(TR.S.START_LEVEL) end end self.render = function() scroll.render() if (self.waiting.hasEnded() == false) then printFontWithShadow("Level: " .. player.getLevelDescription(), "", 40, 52,0,0, CFG.BIG_FONT_SIZE) -- Start button if time() % 1000 > 500 then printFontWithShadow("PRESS TO SKEEP", "", 48, 130-14,0,0, CFG.SMALL_FONT_SIZE) spr(S.BUTTON_A, 80, 130-16) end end renderFrame() end return self end -- Class view Play ViewPlay = {} ViewPlay.new = function() local self = {} self.id = V.PLAY self.scrollCounter = 0 self.messageLine1 = "" self.messageLine2 = "" self.blink = false Game.viewState = VST.NOT_SET self.countDownRemain = 5 self.countDownCounter = 0 self.waitGeneric = nil self.energyFlowAnimation = Sleep.new(CFG.ENERGY_FLOW_SPEED) self.energyFlowAnimation.setInfiniteLoop(true) self.energyFlowAnimation.setFrameRange(CFG.ENERGY_FLOW_MAX_FRAMES) self.signalIndex = 0 self.lastSignal = 0 self.reset = function() Game.restoreLevel() scroll.resetOffsetsTo(ROAD.O.PLAY_SCREEN) player.reset() self.scrollCounter = 0 self.setCountDownState() self.energyFlowAnimation.reset() self.signalIndex = 0 self.lastSignal = 0 end self.cycle = function() self.initFrameLogic() self.handleInputs() self.implementLogic() self.implementCollitionLogic() self.render() end self.initFrameLogic = function() player.changeSpeed(0) end self.handleInputs = function() if (self.handleInputsAllowed() == false) then return end if btn(CFG.IK.UP) then player.changeSpeed(1) end if btn(CFG.IK.DOWN) then player.changeSpeed(-1) end if btn(CFG.IK.LEFT) then player.moveLeft() end if btn(CFG.IK.RIGHT) then player.moveRight() end end self.handleInputsAllowed = function() return (player.isCrashing() == false and Game.viewState == VST.PLAYING) end self.implementLogic = function() if (Game.viewState == VST.COUNT_DOWN) then self.implementCountDownLogic() elseif (Game.viewState == VST.GAS_COLLITION) then self.implementGasCollitionLogic() elseif (Game.viewState == VST.CONVERTING_CELL) then self.implementCellConvertingLogic() elseif (Game.viewState == VST.CRASHING) then self.implementPlayerCrashAnimation() else self.implementScrollLogic() self.implementSignalLogic() end end self.render = function() cls(0) scroll.render() self.renderPlayer() self.renderSpeedIndicator() self.renderScore() self.renderEnergyIndicator() self.renderSignalIndicator() self.renderMessageBox(self.messageLine1, self.messageLine2, self.blink) self.renderScoreIndicator() self.renderLevelDescription() renderFrame() end self.setCountDownState = function() Game.viewState = VST.COUNT_DOWN self.countDownCounter = 0 self.countDownRemain = 3 self.setMessage(INFO_MSGS[IMI.START_IN].l1, INFO_MSGS[IMI.EMPTY].l2 .. self.countDownRemain, true) end self.implementCountDownLogic = function() if (self.countDownRemain > 0) then self.countDownCounter = self.countDownCounter+1 if (self.countDownCounter > 30) then self.countDownCounter = 0 self.countDownRemain = self.countDownRemain-1 self.setMessage(INFO_MSGS[IMI.START_IN].l1, INFO_MSGS[IMI.EMPTY].l2 .. self.countDownRemain, true) if (self.countDownRemain == 0) then snd(SND.COUNT_DOWN_END) else snd(SND.COUNT_DOWN) end end else Game.viewState = VST.PLAYING self.clearMessage() end end self.implementGasCollitionLogic = function() -- Speed down animation if (player.speed > PLR.MIN_SPEED) then if (self.waitGeneric.hasEnded() == true) then self.waitGeneric.start() player.changeSpeed(-1) player.changeSpeed(-1) player.changeSpeed(-1) player.changeSpeed(-1) player.changeSpeed(-1) end else Game.viewState = VST.CONVERTING_CELL self.waitGeneric.setDelayMilis(PLR.CONVERTING_CELLS_SPEED) self.waitGeneric.reset() self.waitGeneric.start() self.energyFlowAnimation.reset() self.energyFlowAnimation.start() end self.waitGeneric.implementLogic() end self.implementCellConvertingLogic = function() if (self.waitGeneric.hasEnded() == true) then self.waitGeneric.start() if (player.convertOneCellToEnergy()) then self.setMessageByIndex(IMI.CONVERTING) else Game.viewState = VST.PLAYING snd(SND.COIN) self.clearMessage() updateMus() end end self.waitGeneric.implementLogic() self.energyFlowAnimation.implementLogic() end self.setMessage = function(_line1, _line2, _blink) self.messageLine1 = _line1 self.messageLine2 = _line2 self.blink = _blink end self.setMessageByIndex = function(_imi) self.setMessage(INFO_MSGS[_imi].l1, INFO_MSGS[_imi].l2, true) end self.clearMessage = function() self.blink = false self.messageLine1 = "" self.messageLine2 = "" end self.implementPlayerCrashAnimation = function() player.implementCrashAnimation() if (player.isCrashing() == false) then if (Game.lives == 0) then TICF.startTransition(TR.S.GAME_OVER) else TICF.startTransition(TR.S.LIVE_LOST) end end end self.implementScrollLogic = function() self.scrollCounter = self.scrollCounter+player.speed if (self.scrollCounter >= 60) then self.scrollCounter = 0 scroll.move(DIR.UP) self.implementEnergyLogic() end player.spriteIndex = scroll.plrSpriteIndex() end self.implementSignalLogic = function() self.signalIndex = scroll.getSignalIndex() end self.implementEnergyLogic = function() player.energyCounter = player.energyCounter+1 if (player.energyCounter > PLR.ENERGY_DECREASE) then player.energy = player.energy-1 if (validateDebugFlag(DEBUG.RESTORE_ENERGY)) then player.energy = player.energy+1 end player.energyCounter = 0 end if (player.energy <= 0) then self.performCrashCollition(0,0) end end self.implementCollitionLogic = function() self.performCollition(player.x+2, 5) self.performCollition(player.x+5, 5) end self.performCollition = function(_x, _y) if (Game.v ~= V.PLAY and Game.view ~= VST.PLAYING) then return end tileIndex = scroll.getTile(_x, _y, ROAD.OFFSET_CHAR) if (tileIndex ~= T.EMPTY) then if (tileIndex == T.RADIO_ACTIVE) then self.performCoinCollition(_x, _y) elseif (tileIndex == T.TOLL) then self.performTollCollition() elseif (tileIndex == T.GAS_STOP) then self.performGasStopCollition(_x, _y) else self.performCrashCollition() end end end self.performCoinCollition = function(_x, _y) Game.coinCollected(1,0) scroll.setTile(_x, _y, T.EMPTY) snd(SND.COIN) end self.performTollCollition = function() snd(SND.TOLL_COLLITION) TICF.startTransition(TR.S.EOL) end self.performCrashCollition = function() if (validateDebugFlag(DEBUG.SKEEP_CRASH)) then return end if (player.isCrashing() == false) then Game.viewState = VST.CRASHING if (Game.lives > 0) then Game.lives = Game.lives-1 end player.startCrashAnimation() self.slowDownCarVelocity() snd(SND.CRASH) snd(SND.CRASH_END) end end self.performGasStopCollition = function(_x, _y) Game.viewState = VST.GAS_COLLITION self.messageLine1 = INFO_MSGS[IMI.CONVERTING].l1 scroll.setTile(_x, _y, T.EMPTY) self.waitGeneric = Sleep.new(100) self.waitGeneric.start() music() snd(SND.GAS_COLLITION) end self.slowDownCarVelocity = function() player.speed = PLR.MIN_SPEED end self.renderScore = function() cornerX = PL.SCORE.X+24 cornerY = PL.SCORE.Y+56+4 -- Title print("STAT", cornerX+3, cornerY+17,12, false,1, true) spr(T.RADIO_ACTIVE, cornerX+2, cornerY+8,-1,1,0,0,1,1) print("x", cornerX+13, cornerY+3+8,12, false,1, true) print( leadingCeros(Game.cellsCollected, ROAD.CELLS_LEADING_CEROS_QTTY), cornerX+18, cornerY+3+8, 12, false, 1, true ) spr(S.PLR.SCROLL, cornerX+2, cornerY-1,-1,1,0,0,1,1) print("x", cornerX+13, cornerY+2,12, false,1, true) print(Game.lives, cornerX+18, cornerY+2,12, false,1, true) end self.renderPlayer = function() player.render() end self.renderSpeedIndicator = function() cornerX = PL.SCORE.X cornerY = PL.SCORE.Y+8*11 scoreIndexes = {0,5,11,1,5,11,2,5,11,3,5,11,4,5,11,4,6,11,4,7,11,4,8,11,4,9,11,4,10,11,4,10,12,4,10,13} spr(S.WHEEL, cornerX, cornerY,-1,1,0,0, 2, 2) -- Direction indicator graph spr(S.SPEED+player.speedDirection, cornerX+4, cornerY+16,-1,1,0,0,1,1) scoreIndexToShow = math.floor((#scoreIndexes / 3)*((player.getSpeedPercent()-1) / 100)) -- RPM indicator spr(S.SPEED_EMPTY+scoreIndexes[scoreIndexToShow*3+1], cornerX+15, cornerY+16,-1,1,0,0,1,1) spr(S.SPEED_EMPTY+scoreIndexes[scoreIndexToShow*3+2], cornerX+15, cornerY+8,-1,1,0,0,1,1) spr(S.SPEED_EMPTY+scoreIndexes[scoreIndexToShow*3+3], cornerX+15, cornerY+0,-1,1,0,0,1,1) -- Title print("RPM", cornerX+8, cornerY+25,12, false,1, true) end self.renderEnergyIndicator = function() fp = ((self.getEnergyPercent(player.energy)-1) / 100) cornerX = PL.SCORE.X+8*3 cornerY = PL.SCORE.Y+8*11 -- Energy indexes local energyIndexes = {9, 6, 2, 9, 6,1, 9, 6,0, 9, 5,0, 9, 4,0, 9, 3,0, 8, 3,0, 7, 3,0} -- Direction indicator graph oneTankIndexes = #energyIndexes/3 indexPercent = oneTankIndexes*4*fp / oneTankIndexes qttyFullTanks = math.floor(indexPercent) mantisa = (indexPercent-qttyFullTanks) indTank = oneTankIndexes-1-math.floor(mantisa*oneTankIndexes) -- ENERGY indicator for iTank = 0, 3,1 do if (iTank < qttyFullTanks) then self.drawEnergyTankEx(energyIndexes,0,cornerX+iTank*8,cornerY) elseif (iTank == qttyFullTanks) then self.drawEnergyTankEx(energyIndexes,indTank*3,cornerX+iTank*8,cornerY) else self.drawEnergyTankEx(energyIndexes,(oneTankIndexes-1)*3,cornerX+iTank*8,cornerY) end end -- Title print("ENER", cornerX+8, cornerY+25,12, false,1, true) end self.drawEnergyTankEx = function(_energyIndexes,_index,_x,_y) spr(S.EN.EMPTY+_energyIndexes[_index+1],_x,_y+18,-1,1,0,0,1,1) spr(S.EN.EMPTY+_energyIndexes[_index+2],_x,_y+10,-1,1,0,0,1,1) spr(S.EN.EMPTY+_energyIndexes[_index+3],_x,_y+2,-1,1,0,0,1,1) end self.renderMessageBox = function(_line1, _line2, _blink) cornerX = PL.SCORE.X cornerY = PL.SCORE.Y+24 spr(S.GRAY_CORNER, cornerX, cornerY,-1,1,0,0,1,1) for x = 1, 5,1 do spr(285, cornerX+8*x, cornerY,-1,1,0,0,1,1) end spr(S.GRAY_CORNER, cornerX+48, cornerY,-1,1,0,1,1,1) spr(S.GRAY_LINE, cornerX, cornerY+8,-1,1,0, 3,1,1) spr(S.GRAY_LINE, cornerX, cornerY+16,-1,1,0, 3,1,1) spr(S.GRAY_LINE, cornerX+48, cornerY+8,-1,1,0,1,1,1) spr(S.GRAY_LINE, cornerX+48, cornerY+16,-1,1,0,1,1,1) spr(S.GRAY_LINE, cornerX, cornerY+24,-1,1,0, 3,1,1) spr(S.GRAY_LINE, cornerX+6*8, cornerY+24,-1,1,0,1,1,1) -- Line 1 print(_line1, cornerX+5, cornerY+5,12, false,1, true) print(_line2, cornerX+5, cornerY+13,12, false,1, true) --- -- Title local footerColor = 12 if (_blink == true) then if (time() % 500 > 250) then footerColor = 12 else footerColor = 2 end end -- Render energy flow indicator if (Game.viewState == VST.CONVERTING_CELL) then drawSprite(T.RADIO_ACTIVE,cornerX+8,cornerY+16) drawSprite(S.EN.FLOW_IND+self.energyFlowAnimation.getCurrentFrame(),cornerX+20,cornerY+16) drawSprite(S.EN.ICON,cornerX+32,cornerY+16) end print("INFO PANEL", cornerX+10, cornerY+27, footerColor, false,1, true) end self.renderScoreIndicator = function() cornerX = PL.SCORE.X cornerY = PL.SCORE.Y print(TXT.PLAY.SCORE .. leadingCeros(Game.score, ROAD.SCORE_LEADING_CEROS_QTTY) .. "",cornerX+5,cornerY+3,12,false,1,true) end self.renderLevelDescription = function(_line1, _line2, _blink) cornerX = PL.SCORE.X cornerY = PL.SCORE.Y print("LEVEL:" .. player.getLevelDescription(), cornerX+5, cornerY+3+8,12, false,1, true) end self.renderSignalIndicator = function() cornerX = PL.SCORE.X cornerY = PL.SCORE.Y+56+4 -- Title print(TXT.PLAY.SIGN, cornerX+3, cornerY+17,12, false,1, true) if (self.signalIndex ~= 39) then if (self.signalIndex ~= 48 and self.signalIndex ~= 0) then self.lastSignal = self.signalIndex-64 else self.lastSignal = 0 end end spr(S.SIGN_EMPTY, cornerX+2, cornerY,-1,1,0,0, 2, 2) if (self.lastSignal ~= 0) and (time() % 500 > 250) then spr(S.SIGN[self.lastSignal], cornerX+2, cornerY,-1,1,0,0, 2, 2) end end self.getEnergyPercent = function(_energy) return player.energy end return self end -- Class view EndOfLevel ViewEndOfLevel = {} ViewEndOfLevel.new = function() local self = {} self.id = V.EOL self.waiting = Sleep.new(CFG.EOF_SLEEP) self.cycle = function() self.handleInputs() self.implementLogic() self.render() end self.reset = function() self.waiting.reset() self.waiting.start() end self.handleInputs = function() if (Game.stopKeyPressInput == true) then return end if btnp(CFG.IK.BUTTON_X) then self.waiting.ended() Game.coinCollected(Game.cellsCollected, PLR.POINTS_X_CELL_PENALTY) Game.cellsCollected = 0 snd(SND.KEY_PRESS) end end self.implementLogic = function() -- Sleep 2 seconds before trigger transition. self.waiting.implementLogic() -- Start transition after 2 seconds waiting. if (self.waiting.hasEnded() == true) then if (Game.cellsCollected > 0) then self.waiting.setDelayMilis(PLR.CONVERTING_CELLS_SPEED) self.waiting.start() Game.coinCollected(-1, PLR.POINTS_X_CELL_PENALTY) snd(SND.ENERGY_UP) else Game.nextLevel() end end end self.render = function() scroll.render() printFontWithShadow(TXT.EOL.TITLE_1,TXT.EOL.TITLE_2, 61, 20, 36, 41, CFG.BIG_FONT_SIZE) cornerX = PL.ROAD.X+56 cornerY = PL.ROAD.Y+56 spr(T.RADIO_ACTIVE, cornerX+15, cornerY+8,-1,1,0,0,1,1) print("x", cornerX+26, cornerY+11,12, false,1, true) print(leadingCeros(Game.cellsCollected, ROAD.CELLS_LEADING_CEROS_QTTY),cornerX+32,cornerY+11,12,false,1,true) print(TXT.PLAY.SCORE .. leadingCeros(Game.score, ROAD.SCORE_LEADING_CEROS_QTTY),cornerX,cornerY+24,12,false,1,true) renderFrame() end return self end -- Class view GameOver ViewGameOver = {} ViewGameOver.new = function() local self = {} self.id = V.GAMEOVER self.waiting = Sleep.new(CFG.GAME_OVER_SLEEP) self.cycle = function() self.handleInputs() self.implementLogic() self.render() end self.reset = function() self.waiting.reset() self.waiting.start() end self.handleInputs = function() end self.implementLogic = function() -- Sleep 2 seconds before trigger transition. self.waiting.implementLogic() if (self.waiting.hasEnded() == true) then TICF.startTransition(TR.S.GAME_OVER_TO_INTRO) end end self.render = function() scroll.render() printFontWithShadow(TXT.GAMEOVER.TITLE_1, TXT.GAMEOVER.TITLE_2, 70, 40, 70, 62, CFG.BIG_FONT_SIZE) renderFrame() end return self end -- Class view win ViewWin = {} ViewWin.new = function() local self = {} self.id = V.WIN self.animations = {} for i = 0, CFG.WIN_ANIM_SIZE,1 do self.animations[i] = Animation.new(i) end self.waiting = Sleep.new(CFG.WIN_SLEEP) self.finalScore = "" self.cycle = function() self.handleInputs() self.implementLogic() self.render() end self.reset = function() self.waiting.reset() self.waiting.start() self.finalScore = leadingCeros(Game.score, ROAD.SCORE_LEADING_CEROS_QTTY) for i = 0, CFG.WIN_ANIM_SIZE,1 do self.animations[i].slotPosX = PL.ROAD.X+32+(i*12) self.animations[i].objectiveSpriteId = T.RADIO_ACTIVE self.animations[i].convertedSpriteId = S.TREE self.animations[i].animState = 0 self.animations[i].crashOnObjective = false self.animations[i].movementCounter = 0 if (i < CFG.WIN_ANIM_SIZE) then self.animations[i].nextAnimation = self.animations[i+1] end if (i > 0 and i <= ROAD.SCORE_LEADING_CEROS_QTTY) then self.setSpriteIndexFromScore(i) end end self.animations[0].animState = IAS.WALKING end self.handleInputs = function() if btnp(CFG.IK.BUTTON_X) then snd(SND.KEY_PRESS) TICF.startTransition(TR.S.GAME_OVER_TO_INTRO) end end self.implementLogic = function() for i = 0, CFG.WIN_ANIM_SIZE,1 do self.animations[i].implementLogic() end if (self.animations[CFG.WIN_ANIM_SIZE].animState == IAS.END) then self.reset() end end self.render = function() scroll.render() printFontWithShadow(TXT.WIN.TITLE_1, "", 70, 40,0,0, CFG.SMALL_FONT_SIZE) print(TXT.WIN.TITLE_2, 38, 56, 6) print(TXT.WIN.TITLE_3, 74, 86, 6) for i = 0, CFG.WIN_ANIM_SIZE-1,1 do self.animations[i].render() end renderFrame() end self.setSpriteIndexFromScore = function(index) numberIndex = string.byte(string.sub(self.finalScore, index, index))-48+1 self.animations[index].convertedSpriteId = S.NUMBERS[numberIndex] end return self end -- Class view GameOver ViewTransition = {} ViewTransition.new = function() local self = {} self.id = V.TRAN self.targetView = nil self.sleep = Sleep.new(TR.SPEED) self.direction = 0 self.fromOffsetY = 0 self.toOffsetY = 0 self.cycle = function() self.handleInputs() self.implementLogic() self.render() end self.reset = function() Game.stopKeyPressInput = true self.resetTransition() end self.handleInputs = function() end self.implementLogic = function() self.sleep.implementLogic() if (self.sleep.hasEnded() == true) then self.sleep.start() if (self.direction > 0) then scroll.moveN(DIR.UP, TR.DETAIL) else scroll.moveN(DIR.DOWN, TR.DETAIL) end end if (self.hasEnded() == true) then Game.stopKeyPressInput = false TICF.changeView(self.targetView) end end self.render = function() scroll.render() renderFrame() end self.resetTransition = function() self.sleep.reset() self.transition(TR.S.RESET) end self.start = function() self.sleep.start() scroll.setBkgOffX(ROAD.INITIAL_X_OFFSET*8) end self.isAnimating = function() return self.direction ~= 0 and self.hasEnded() == false end self.hasEnded = function() if (self.direction > 0 and scroll.getBkgOffY() >= self.toOffsetY) then return true end if (self.direction < 0 and scroll.getBkgOffY() <= self.toOffsetY) then return true end return false end self.transition = function(_transitionId) if (TRA[_transitionId].indexFrom == -1) then self.fromOffsetY = scroll.getBkgOffY() else self.fromOffsetY = TRA[_transitionId].indexFrom end self.toOffsetY = TRA[_transitionId].indexTo self.direction = 0 if (TRA[_transitionId].target ~= nil) then if (self.fromOffsetY < self.toOffsetY) then self.direction = 1 else self.direction = -1 end self.targetView = TRA[_transitionId].target scroll.setBkgOffY(self.fromOffsetY) self.start() end end return self end --****************************** --GLOBAL FUNCTIONS --****************************** function renderFrame() C_CORNER_ID = 272 spr(C_CORNER_ID,0,0) spr(C_CORNER_ID, 29*8,0,-1,1,1) spr(C_CORNER_ID, 29*8,16*8,-1,1, 3) spr(C_CORNER_ID,0,16*8,-1,1, 2) -- Draw rows for x = 1, 28,1 do spr(C_CORNER_ID+1, x*8,0,-1,1, 2,0) spr(C_CORNER_ID+1, x*8,16*8) end -- Draw columns for y = 1,15,1 do spr(C_CORNER_ID+1,0, y*8,-1,1,0,1) spr(C_CORNER_ID+14, ROAD.FRAME_COLS*8+16, y*8,-1,1,0,1) spr(C_CORNER_ID+1, 29*8, y*8,-1,1,0, 3) end end function updateMus() playMus(BGMM[Game.v] or -1) end function snd(spec) if not Sett.snd then return end sfx(spec.sfxid, spec.note, spec.dur, spec.ch, spec.vol or 8, spec.speed or 0) end function playMus(musid) if Sett.mus or musid == -1 then music(musid) end end function print_value(_string, _value, _x, _y) print(_string .. _value, _x, _y, 5) end function replace_char(pos, str, newStr) return str:sub(1, pos-1) .. newStr .. str:sub(pos+1) end function tilemap_to_string(mapX, mapY, strIndex, str, asciiOffset) r = string.char(mget(mapX, mapy)+asciiOffset) result = str:sub(1, strIndex-1) .. r .. str:sub(strIndex+1) return result end -- Create a long background contatenating differents -- sections of backgrounds. -- The idea is concat differents segments to construct -- a long one segment. -- -- +-------------------+ +-------------------+ -- |LEVEL END BKG | |GAME FINISHED | -- +-------------------+ +-------------------+ -- | | -- | | -- |GAME BKG | -- | | -- | | -- index 34 +-------------------+ +-------------------+ -- |LEVEL BKG | |GAME OVER | -- index 17 +-------------------+ +-------------------+ -- |INTRO BKG | -- index 00 +-------------------+ function createLongBackgroundForLevel(_levelNro) scroll.deleteBackground() -- Add level end background insBkL("555555555E F555555555W", 4) insBkg("55555555A @55555555W") insBkg("5555555A @5555555W") insBkg("555555A C555555W") insBkg("55555D B555555W") insBkL("55555B B555555W", 21) insBkg("55555F B555555W") insBkg("555555@ E555555W") insBkg("5555555@ A5555555W") insBkg("55555555@ A55555555W") insBkg("555555555@ A555555555W") -- Add game play background local entireMapLine = "" local levelOffsetX = (_levelNro-1)*30 for y = 0, ROAD.LEVEL_ROWS-1,1 do entireMapLine = "" for x = 0, 29,1 do entireMapLine = entireMapLine .. string.char(mget(x+levelOffsetX, y)+ROAD.OFFSET_CHAR) end insBkg(entireMapLine) end -- Add level info background insBkg("555555555E F555555555W") insBkg("55555555A @55555555W") insBkg("5555555A @5555555W") insBkg("555555A C555555W") insBkg("55555D B555555W") insBkL("55555B B555555W",10) -- Add intro background insBkg("55555E F555555A") insBkg("5555D C55555A") insBkL("5555B B55555A", 6) insBkg("5555F E55555A") insBkg("55555C DGC DC DC DGC D555555A") insBkg("55555B B5B BB BB B5B B555555A") insBkg("55555B B5B B4 4B B5B B555555A") insBkg("55555B B5B BB BB B5B B555555A") insBkg("55555B B5B BB BB B5B B555555A") insBkg(" 55B B5B BB BB B5B B5555 ") end -- Insert to scroll.bkg an entire line always at index 1. function insBkg(line) table.insert(scroll.bkg, #scroll.bkg+1, line) end -- InsBkg loop, call n times to insBkg function insBkL(line, times) for i = 1, times,1 do insBkg(line) end end function leadingCeros(_integer, _qttyCeros) local format = "%0" .. _qttyCeros .. "d" local leadingString = string.format(format, _integer) return leadingString end function printFontWithShadow(firstLine, secondLine, x1, y1, x2, y2, size) print(firstLine, x1, y1, 6, false, size, false) print(firstLine, x1, y1+1, 5, false, size, false) print(secondLine, x2, x2, 6, false, size, false) print(secondLine, x2, x2+1, 5, false, size, false) end function debug() if (validateDebugFlag(DEBUG.SHOW_INFO)) then print("plyX: " .. player.x .. "",1, 1*8,12) print("plyY: " .. player.y .. "",1, 2*8,12) print("energy:" .. player.energy .. "",1, 3*8,12) print("c. collected:" .. Game.cellsCollected .. "",1, 4*8,12) print("backgroundOffsetY:" .. scroll.getBkgOffY() .. "",1, 5*8,12) print("backgroundOffsetX:" .. scroll.getBkgOffX() .. "",1, 6*8,12) end end function drawSprite(_index, _x, _y) spr(_index,_x,_y,-1,1,0,0,1,1) end function validateDebugFlag(_debugFlag) return DEBUG.DEBUG_MODE == true and _debugFlag == true end --****************************** --TEST FUNCTIONS --****************************** function Test_replace_char() trace("\nTest_replace_char") myString = "55555B B5B BB BB B5B B555555A" trace("original String:" .. myString) myString = replace_char(2, myString, string.char(48)) trace("changed char at pos 2 with 0:" .. myString) end function Test_tilemap_to_string() trace("\nTest_tilemap_to_string") myString = "123456" trace("original String:" .. myString) myString = tilemap_to_string(0,0,1, myString,0) trace("changed String:" .. myString) trace("verify:" .. string.byte(string.sub(myString,1,1))) end function Test_createLongBackgroundForLevel() trace("\nTest_createLongBackgroundForLevel") Game.resetToStartANewGame() for i = 1, #scroll.bkg,1 do trace(scroll.bkg[i]) end end function Test_leadingCeros() trace("\nTest_leadingCeros") cls(0) local myInt = 123 local cerosQtty = 6 trace("Add up to " .. cerosQtty .. " ceros to number " .. myInt) trace(leadingCeros(myInt, ROAD.SCORE_LEADING_CEROS_QTTY)) end --****************************** --CONSTRUCTORS --****************************** scroll = {} animation = {} player = {} view_boot = {} view_intro = {} view_play = {} view_level_info = {} view_level_end = {} view_game_over = {} view_tran = {} view_win = {} function constructors() scroll = Scroll.new() player = Player.new() view_boot = ViewBoot.new() view_intro = ViewIntro.new() view_play = ViewPlay.new() view_level_info = ViewLevelnInfo.new() view_level_end = ViewEndOfLevel.new() view_game_over = ViewGameOver.new() view_tran = ViewTransition.new() view_win = ViewWin.new() end constructors() TICF = { [V.BOOT] = view_boot, [V.INTRO] = view_intro, [V.MI] = view_level_info, [V.PLAY] = view_play, [V.EOL] = view_level_end, [V.GAMEOVER] = view_game_over, [V.WIN] = view_win, [V.TRAN] = view_tran } -- Change flow to a new view. TICF.changeView = function(_view) Game.v = _view TICF[Game.v].reset() playMus(BGMM[Game.v] or -1) end -- Start transition, this means, set current view as Transition and then call -- transition function to start transition using predefined transition values. TICF.startTransition = function(_transitionId) TICF.changeView(V.TRAN) view_tran.transition(_transitionId) end --****************************** --UNIT TESTS --****************************** function tests() trace("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n") trace("\n--TEST START-") Test_createLongBackgroundForLevel() Test_replace_char() Test_tilemap_to_string() Test_leadingCeros() trace("\n--TEST END---") trace("\n") end if (runUnitTest==true) then tests() end --****************************** --MAIN CYCLE --****************************** function TIC() cls(0) TICF[Game.v].cycle() debug() end