----------------------------- -- Levak ©2011 -------------- -- http://levak.free.fr/ ---- -- levak92@gmail.com -------- ----------------------------- --- Jim Bauwens Sound Lib TONE_LOW = "\001" TONE_HIGH = "\127" function freq_key(key) --return 440 * math.pow(2, (key-49)/12) return key end function keytone(key) local am = 57600/freq_key(key) return TONE_HIGH:rep(am) .. TONE_LOW:rep(am) end function key_am(key, ammount) return keytone(key):rep(ammount) end function key_time(key, time) return key_am(key, freq_key(key)*time) end --- --- When we close the document, it is important to clean the entire dataBase --- Whitout this, the Nspire begins to slow down function on.destroy() for i=1, #nyanNyan do nyanNyan[i] = {} end nyanNyan = {} nyanNYAN = {} nyanTROLL = {} nyanLOL = {} nyanFU = {} nyanPOKERFACE = {} nyanMEGUSTA = {} nyanYUNO = {} nyanFOREVER = {} nyanCONTENT = {} nyanNyanNyanNyanWhite = {} for i=1, #nyanNyanNyanNyanWhite do nyanNyanNyanNyanWhite[i] = {} end nyanNyanNyanNyanBlack = {} for i=1, #nyanNyanNyanNyanBlack do nyanNyanNyanNyanBlack[i] = {} end nyanBonus = {} for i=1, #nyanBonus do nyanBonus[i] = {} end nuanMalus = {} for i=1, #nuanMalus do nuanMalus[i] = {} end timer.stop() collectgarbage() end --- Restore data when we open the document function on.restore(data) highNyan = data end --- When we close the document, save the highscore in a protected variable function on.save() if nyans and nyans > highNyan then highNyan = nyans end return highNyan end --- Initialize the stuff function on.create() nyanPlayTimes = 0 -- how many times did we play ? if not highNyan then highNyan = 0 -- if there is no higscore because it is the first play end oldnyanTimer = timer.getMilliSecCounter() -- Timer for Nyan sprite nyanAnimationIndex = 1 -- Index for Nyan sprite nyanScoreTimer = timer.getMilliSecCounter() -- Timer to increment score nyanCountDownTimer = timer.getMilliSecCounter() -- Timer for the 3-2-1-0 nyanBoardTimer = timer.getMilliSecCounter() -- Timer for generate elements screen = -1 -- -1=intro, 0=menu, 1=timer, 2=ingame, 3=gameover bgColorIndexNYAN = 0 -- fade intro effect index bgColorIncrement = 1 -- fade intro effect speed if platform.window:height() > 318 then -- We use the fullscreen bug timer.start(0.07) -- that is only present on the nyanOrigin = 28 -- handheld devide. Here is a correction else -- for TI-Nspire Computer Software timer.start(0.01) -- that let's us play without fullscreen nyanOrigin = 0 -- and fix the timer difference end -- because the computer is ... faster ? :D nyanSongIndex = 1 -- current position of the sound "pointer" thisIsSparta = false -- because sound is not madness : Boolean state of the sound. nyanHelp = false end function resetNyan() document.markChanged() -- don't forget to save the high score ! nyanPause = false -- boolean state of the game (you lost) nyans = 0 -- Score dY = 0 -- Difference between old and next Nyan Cat position (smooth effect) nyanX = 20 -- X Position of the Nyan cat nyanY = 100 - nyanOrigin -- and Y nyanLives = 5 -- Lives before dying =( nyanCountDown = 3 -- 3 - 2 - 1 - 0 ! nyanSpeed = 10 -- Speed of the light ! err ... the Nyan Cat !! nyanBoard = {} -- Game element table nyanSmallRainbow = {} -- The little rainbow behind the in-game NyanCat for i=1, nyanX/image.width(nyanNYAN) + 3 do nyanSmallRainbow[i] = nyanY end if not nyanBackground then -- little in-game stars in the background nyanBackground = {} for i=1, 20 do nyanBackground[i] = star(math.random(300, 400), math.random(1, 7)*28 + 15 - nyanOrigin, math.random(10, 30)) end end end --- Iter over each character function nyanipairs(text) local i = 0 return function() i = i + 1 if i <= #text then return i, text:sub(i, i) end end end --- Nyan a given text (multi-random-color) function nyanText(gc, text, x, y) for i, c in nyanipairs(text) do gc:setColorRGB(math.random(0,255), math.random(0,255), math.random(0,255)) x = gc:drawString(c, x, y, "top") end end --- Draw the Nyan's rainbow in the title screen function drawNyanLongRainbow(gc, x, y) for i=0, x + 5, image.width(nyanNYAN) do gc:drawImage(nyanNYAN, i, y + math.sin(i - nyanAnimationIndex) * 1.1) end end --- Draw the Nyan's rainbow in-game function drawNyanSmallRainbow(gc, x, y) local previous = y + math.sin(nyanAnimationIndex) * 5 local w = image.width(nyanNYAN) local n = #nyanSmallRainbow for i, r in ipairs(nyanSmallRainbow) do gc:drawImage(nyanNYAN, (n - i - 1) * w, r ) nyanSmallRainbow[i] = (previous + r) / 2 previous = r end end --- A macgyver-like function to draw a double-border-text function drawDoubleLineText(gc, text, x, y, mode, offset) gc:setColorRGB(0, 0, 0) gc:drawString(text, x - offset, y - offset, mode) gc:drawString(text, x - offset, y, mode) gc:drawString(text, x + offset, y, mode) gc:drawString(text, x - offset, y + offset, mode) gc:drawString(text, x + offset, y - offset, mode) gc:drawString(text, x, y + offset, mode) gc:drawString(text, x, y - offset, mode) gc:drawString(text, x + offset, y + offset, mode) gc:setColorRGB(255, 255, 255) gc:drawString(text, x, y, mode) end function on.paint(gc) gc:begin() -- lets us draw the entire screen (TI-Nspire API bug) local icd = platform.isColorDisplay() if icd then gc:setColorRGB(20, 52, 95) else gc:setColorRGB(255, 255, 255) -- theme for non-CX Nspire end gc:fillRect(0, 0, 320, 240) -- clear the whole screen if nyanHelp then gc:setFont("sansserif", "b", 10) local txt = "Eat Candy !!" drawDoubleLineText(gc, txt, 159 - gc:getStringWidth(txt)/2, 30 - nyanOrigin, "top", 2) for i, bonus in ipairs(nyanBonus) do gc:drawImage(bonus, 10 + i * 30, 50 - nyanOrigin) end txt = "Avoid vegetables !" drawDoubleLineText(gc, txt, 159 - gc:getStringWidth(txt)/2, 100 - nyanOrigin, "top", 2) for i, malus in ipairs(nyanMalus) do gc:drawImage(malus, 70 + i * 30, 120 - nyanOrigin) end txt = "Use Up/Down or 8/2 to move !" drawDoubleLineText(gc, txt, 159 - gc:getStringWidth(txt)/2, 160 - nyanOrigin, "top", 2) txt = "'P' to pause the game !" drawDoubleLineText(gc, txt, 159 - gc:getStringWidth(txt)/2, 180 - nyanOrigin, "top", 2) txt = "'S' to activate sound through serial port" drawDoubleLineText(gc, txt, 159 - gc:getStringWidth(txt)/2, 200 - nyanOrigin, "top", 2) gc:setFont("sansserif", "r", 8) drawDoubleLineText(gc, "by Levak - http://levak.free.fr", 2, 220 - nyanOrigin, "top", 1) elseif screen == -1 then -- Intro local t = bgColorIndexNYAN / 20 if t > 1 then t = 1 elseif t < 0 then t = 0 end if icd then gc:setColorRGB(20*t+20, 52*t+52, 95*t+95) else gc:setColorRGB(-255*t+255, -255*t+255, -255*t+255) end local txt = "Levak" gc:drawString(txt, 159-gc:getStringWidth(txt)/2, 100, "top") txt = "presents" gc:drawString(txt, 159-gc:getStringWidth(txt)/2, 130, "top") bgColorIndexNYAN = bgColorIndexNYAN + bgColorIncrement if bgColorIndexNYAN > 50 then bgColorIncrement = - bgColorIncrement elseif bgColorIndexNYAN < 0 then screen = 0 end elseif screen == 0 then -- Title screen gc:setFont("sansserif", "b", 30) local txt = "NYAN CAT GAME" nyanText(gc, txt, 159 - gc:getStringWidth(txt)/2, 50 - nyanOrigin) gc:setColorRGB(0, 0, 0) gc:setFont("sansserif", "b", 12) gc:drawString("Press Enter", 110, 100 - nyanOrigin, "top") drawNyanLongRainbow(gc, 130, 150 - nyanOrigin) gc:drawImage(nyanNyan[nyanAnimationIndex], 130, 150 - nyanOrigin) gc:setFont("sansserif", "b", 10) if highNyan > 0 then local txt = "High Nyan : "..highNyan nyanText(gc, txt, 159 - gc:getStringWidth(txt)/2, 180 - nyanOrigin, "top") else gc:setColorRGB(255, 255, 255) local txt = "Eat Candy !! Avoid vegetables !" gc:drawString(txt, 159 - gc:getStringWidth(txt)/2, 180 - nyanOrigin, "top") txt = "Use Up/Down or 8/2 to move !" gc:drawString(txt, 159 - gc:getStringWidth(txt)/2, 200 - nyanOrigin, "top") end elseif screen >= 1 then -- Count Down, in-game, higscore gc:setColorRGB(0, 0, 0) gc:setFont("sansserif", "b", 10) drawDoubleLineText(gc, "NYANS : "..nyans, 0, 26 - nyanOrigin, "top", 2) for i=1, nyanLives do gc:drawImage(nyanCONTENT, 180 + i*20, 27 - nyanOrigin) end gc:setColorRGB(255, 255, 255) gc:drawLine(0, 42- nyanOrigin, 320, 42-nyanOrigin) for i, o in ipairs(nyanBackground) do if o.x < 0 then o.x = 300 o.y = math.random(1, 7)*28 + 15 - nyanOrigin o.speed = math.random(10, 30) else o:update() if icd then gc:drawImage(nyanNyanNyanNyanWhite[o.i], o.x, o.y) else gc:drawImage(nyanNyanNyanNyanBlack[o.i], o.x, o.y) end end end drawNyanSmallRainbow(gc, nyanX, nyanY) gc:drawImage(nyanNyan[nyanAnimationIndex], nyanX, nyanY) if highNyan > 0 and nyans > highNyan then gc:drawImage(nyanLOL, nyanX + 15, nyanY - 15) elseif nyans > 2000 then gc:drawImage(nyanTROLL, nyanX + 20, nyanY - 5) end if screen == 1 then gc:setFont("sansserif", "b", 30) drawDoubleLineText(gc, nyanCountDown, 160 - gc:getStringWidth(tostring(nyanCountDown))/2, 116, "middle", 3) elseif screen == 2 then if not nyanPause then if dY ~= 0 then -- if we are moving if dY < 0 and nyanY > 54 -nyanOrigin then dY = dY + 14 nyanY = nyanY - 14 elseif dY > 0 and nyanY < 212 -nyanOrigin then dY = dY - 14 nyanY = nyanY + 14 end end for i, o in ipairs(nyanBoard) do if o.x < 0 then nyanBoard[i] = {} table.remove(nyanBoard, i) collectgarbage() else o.x = o.x - nyanSpeed if o:isInBounds(nyanX, nyanY, 44, 5) then nyans = nyans + o.s if o.s < 0 then nyanLives = nyanLives - 1 if nyanLives < 0 then screen = 3 end end table.remove(nyanBoard, i) else o:paint(gc) end end end else gc:setFont("sansserif", "b", 30) drawDoubleLineText(gc, "PAUSE", 120, 100 - nyanOrigin, "top", 2) end elseif screen == 3 then if nyans > highNyan then gc:setFont("sansserif", "b", 30) drawDoubleLineText(gc, "HIGH SCORE", 105, 40 - nyanOrigin, "top", 2) else if nyans > 0 and nyans + 200 > highNyan then gc:setFont("sansserif", "b", 20) drawDoubleLineText(gc, "So Close ...", 105, 40 - nyanOrigin, "top", 2) gc:drawImage(nyanFU, nyanX + 22, nyanY - 25) elseif nyans < 0 then gc:setFont("sansserif", "b", 10) drawDoubleLineText(gc, "I told you to", 105, 40 - nyanOrigin, "top", 2) drawDoubleLineText(gc, "avoid vegetables !", 105, 60 - nyanOrigin, "top", 2) gc:drawImage(nyanYUNO, 105, 70 - nyanOrigin) gc:drawImage(nyanMEGUSTA, nyanX + 18, nyanY - 22) end end if nyans > 9000 then -- probably impossible ... gc:setFont("sansserif", "b", 20) drawDoubleLineText(gc, "OVER 9000 !!!", 105, 100 - nyanOrigin, "top", 2) gc:setFont("sansserif", "b", 8) drawDoubleLineText(gc, "And ?", nyanX + 18, nyanY - 25, "top", 1) gc:drawImage(nyanPOKERFACE, nyanX + 18, nyanY - 10) end gc:setFont("sansserif", "b", 10) drawDoubleLineText(gc, "Press Enter", 165, 80 - nyanOrigin, "top", 2) if nyanPlayTimes > 8 then gc:drawImage(nyanFOREVER, 220, 175 - nyanOrigin) end end end end function on.deactivate() nyanPause = true end function on.activate() nyanPause = false end function on.charIn(ch) if ch == "8" then on.arrowUp() elseif ch == "2" then on.arrowDown() elseif ch == "p" or ch == "P" then nyanPause = not nyanPause elseif ch == "s" or ch == "S" then thisIsSparta = not thisIsSparta elseif ch == "g" then -- god mode nyanLives = 6 nyanSpeed = 80 end end function on.arrowUp() if screen == 2 then dY = -28 end platform.window:invalidate() end function on.arrowDown() if screen == 2 then dY = 28 end platform.window:invalidate() end function on.enterKey() if nyanHelp then nyanHelp = false elseif screen == -1 then screen = 0 elseif screen == 0 then screen = 1 resetNyan() elseif screen == 3 then if nyans > highNyan then highNyan = nyans end screen = 0 nyanPlayTimes = nyanPlayTimes + 1 end platform.window:invalidate() end function on.escapeKey() nyanHelp = false platform.window:invalidate() end function on.help() nyanHelp = true platform.window:invalidate() end function on.timer() --- This big function lets us handle 4 differents timer with only one. --- Basicly, the base timer speed is 0.01 sec and the other ones are --- fired only if an global var reaches a certain amount of time --- Here we update the sound, the count down, the score, the Nyan's sprite --- and the board generation if thisIsSparta then if nyanSongIndex + 2 <= #nyanSong then nyanSongIndex = nyanSongIndex + 2 print(key_time(nyanSong[nyanSongIndex]*5, nyanSong[nyanSongIndex+1]/10)) else nyanSongIndex = 1 end end if screen == 1 then if nyanCountDownTimer + 1000 < timer.getMilliSecCounter() then if nyanCountDown > 0 then nyanCountDown = nyanCountDown - 1 nyanCountDownTimer = timer.getMilliSecCounter() else screen = 2 end end elseif screen == 2 and not nyanPause and not nyanHelp then if nyanScoreTimer + 100 < timer.getMilliSecCounter() then nyans = nyans + nyanSpeed/10 nyanScoreTimer = timer.getMilliSecCounter() end if nyanBoardTimer + 300 < timer.getMilliSecCounter() then local randBuffer = {} for i = 1, 7 do local rand = math.random(1, 30) if rand >= 1 and rand <= 7 and not randBuffer[rand] then randBuffer[rand] = true end end for line, _ in pairs(randBuffer) do local newObj = math.random(-50, 50) local newImg = newObj > 0 and nyanBonus[math.random(1, #nyanBonus - (nyans < 2000 and 1 or 0))] or nyanMalus[math.random(1, #nyanMalus)] nyanBoard[#nyanBoard + 1] = obj(300, line*28 + 15-nyanOrigin, newObj, newImg) end nyanSpeed = nyanSpeed + 0.1 nyanBoardTimer = timer.getMilliSecCounter() end end if oldnyanTimer + 70 < timer.getMilliSecCounter() then nyanAnimationIndex = nyanAnimationIndex % #nyanNyan + 1 oldnyanTimer = timer.getMilliSecCounter() end platform.window:invalidate() end obj = class() function obj:init(x, y, s, img) self.img = img self.w = image.width(img) self.h = image.height(img) self.x = x + (44 - self.w)/2 self.y = y + (28 - self.h)/2 self.s = s end function obj:isInBounds(x, y, w, h) return x < self.x + self.w and x + w > self.x and y < self.y + self.h and y + h > self.y end function obj:paint(gc) gc:drawImage(self.img, self.x, self.y) end star = class() function star:init(x, y, speed) self.i = 1 self.w = 7 self.h = 7 self.x = x + (44 - self.w)/2 self.y = y + (28 - self.h)/2 self.speed = speed end function star:update() self.i = self.i % #nyanNyanNyanNyanWhite + 1 self.x = self.x - self.speed end