-```L04e$```uqaY}},m,Ҫmʨ w111133331111333311113333#""2#""2#""2#""2#""2#""233333333111111331133333333#""2#332#02#02#332#""23333333303303303303300333333330000000033333333#""2#332#02#02#332#""233333333133101013311333331""33####""""33333333""""""!332222""33####2222!33""3333112#1211!2#1212#!1!112!1#!1!1#21133333333!#33333333!!!!!3333333311!1!!1213333333302222#""2333333331""2""2""2""2#""23333333333333333333333333333333333333""!3322222222!33""3####33""1322222222############33""3333#""2#""2#""2#""2#""2#""2333333331111333311113333111133331111333311113333333300033333000000033330000000003333300000003333000330000001333300033333000000033330000000003333330300000003000001111111111111111030110111101311111111000000003111113#""2#""2#""2#""2#""2#""2#""2#""2#""2#""2#""2#""2#""2#""2#""2#""2#"20"2#20230302#2"2#""2#""2#""2#""2#""2#""2#""2#""2#"""#"""#"""#"""#"""#"""#"""#"""""0"02"02"0""0""0""0"223333333333333333333333333333333333333333333333333333333333333333#0333033333033333333333"233333333333333333333333333333333333333333333333333333333333333333333333333333330330330330330330330330333333333333333333333000000003000333000000003333000000011111111000303#1110101110110111#""2#""2#""2#""2#""2#""2#""2#""2#0"#20"#2##"##"0#"20#"2#""""""""""""""""""""""""""#"""#""""#2"02"202"23""3""0""2"""#""23333333333333333333333333333#""33333333333333333333333333330"""03333333330333033333333"""3333333333333333333303033303330333303333333333333033333330033333333333333333330333333333333333030330333333333333x ## ## ##""$%&'()*+,-./`abc" """##456789:;<=>?pqrs####DEFGHIJKLMNO "" TUVWXYZ[\]^_""" " #### ##""""" "######"""""" ######"""""" ###### >`` ` `` ` `  ` ` `@ 0333333333333333333333333333333333333333033333303333333300000000000000000000000000000000000000000000000000000000000000000000000000000000000303013030000011011011011111011001110110111011133101111101101011111011300111011011310111110331#11#""2#""2#"2#""2#""""#""2#0""""""#2#""2#""20"2#""2#""""0#""2#20""""""02#""2#""2#2#""2#""""02#""2#2#"""""202#""2#""202#""2#""""02#""2#"#"""""23#""2#""233#""2#""""0"#""2#"0""""""3#""2#""202#""2#""""0"#""2#"20""""""0#""2#""2#2#""2#""""0"2#""2#"2#"""""2#""2#""20"2#""2#""""0"2#""2#""#""""""33333333#"2333333333033#""2#""0""""""3333333303333333333330333333333303333333333333333333333333333033333333333333333333333333033333333333330333333333333333333333333333333333333333033333333333033333333333330333333333333330333333333333033333333333333333333333333330333333333333333333333333333333333333333303333333333333333N-- title: The Game Boy Tetris -- author: kylesubhan1 -- desc: Classic Tetris Game with Game Boy -- script: lua -- input: gamepad music(0) local CELL_H=8 local CELL_W=8 local isFirstTic=true local isSfxOn=true local state=0 local MENU_SCR="Menu" local GAME_SCR="Game" local PAUSE_SCR="Pause" local OVER_SCR="GameOver" local MENU_SFX="MenuSfx" local SEL_MENU_SFX="SelectionMenuSfx" local GAME_OVER_SFX="GameOverSfx" local FELL_SFX="FellSfx" local LINE_SFX="LineSfx" local EMPTY_CELL_SPR=1 local DIE1_CELL_SPR=17 local DIE2_CELL_SPR=16 local DARKBLUE_CELL_SPR=3 local GREEN_CELL_SPR=4 local YELLOW_CELL_SPR=5 local RED_CELL_SPR=6 local BLUE_CELL_SPR=7 local BROWN_CELL_SPR=8 local LIGHTBLUE_CELL_SPR=9 local bestScore=0 local playerScore=0 local screenMng local gameScreen local soundMng local saveSfxOnIdx=0 local saveScoreIdx=1 -- Helpers -- function PrintToCell(text,cx,cy,color) print(text,CELL_W*cx,CELL_H*cy+2,color) end function DrawToCell(sprId,cx,cy) spr(sprId,CELL_W*cx,CELL_H*cy) end -- temporary placeholder function sfx() end local function UpdateBestScore(score) if bestScore s.animTime + s.animDelay then s.animTime=now remove(DIE2_CELL_SPR) s.isAnim = replace(DIE1_CELL_SPR, DIE2_CELL_SPR) end end return s end -- Tetrimino class local function Tetrimino(sprId) local s={ sprId=sprId, width=0, height=0, pos={x=0,y=0}, shape=0, shapes={}, wallkickGap=0, isFell=false } function s.Move(x,y) s.pos.x=x s.pos.y=y end local function isCollision(grid,shape,pos) local sh=s.GetShape(shape) for y=1,s.height do for x=1,s.width do if sh~=nil and sh[y][x]>0 then local posX=x+pos.x local posY=y+pos.y if posX<=0 or posX>grid.width then return true elseif posY>grid.height then return true elseif not (grid.grid[posY][posX]==EMPTY_CELL_SPR) then return true end end end end return false end function s.Create()end function s.Draw(cx,cy) local shape=s.GetShape(s.shape) for y=1,s.height do for x=1,s.width do if shape~=nil and shape[y][x]>0 then local posX=cx+x-1+s.pos.x local posY=cy+y-1+s.pos.y DrawToCell(s.sprId,posX,posY) end end end end function s.GetShape(shape) if shape>0 then return s.shapes[shape] end return nil end function s.GoDown(grid) local p={x=s.pos.x,y=s.pos.y} p.y=p.y+1 if not isCollision(grid,s.shape,p) then s.pos=p else s.isFell=true end end function s.GoLeft(grid) local p={x=s.pos.x,y=s.pos.y} p.x=p.x-1 if not isCollision(grid,s.shape,p) then s.pos=p end end function s.GoRight(grid) local p={x=s.pos.x,y=s.pos.y} p.x=p.x+1 if not isCollision(grid,s.shape,p) then s.pos=p end end local function doWallkick(grid,shape) if shape==2 or shape==4 then return end for gap=1,s.wallkickGap do -- try right local pRight={x=s.pos.x+gap,y=s.pos.y} if not isCollision(grid,shape,pRight) then s.pos = pRight s.shape = shape return end -- try left local pLeft={x=s.pos.x-gap,y=s.pos.y} if not isCollision(grid,shape,pLeft) then s.pos = pLeft s.shape = shape return end end end function s.DoRotation(clockwise,grid) local shape=s.shape; if clockwise then shape=shape+1 else shape=shape-1 end if shape>4 then shape=1 elseif shape<1 then shape=4 end if not isCollision(grid,shape,s.pos) then s.shape=shape else doWallkick(grid,shape) end end function s.CopyToGrid(grid) local sh=s.GetShape(s.shape) for y=1,s.height do for x=1,s.width do if sh~=nil and sh[y][x]>0 then local posX=x+s.pos.x local posY=y+s.pos.y grid.grid[posY][posX]=s.sprId end end end end function s.IsGameOver(grid) return isCollision(grid,s.shape,s.pos) end return s end local function ZTetrimino(sprId) local s=Tetrimino(sprId) function s.Create() s.width=3 s.height=3 s.shape=1 s.wallkickGap=1 s.shapes[1]={ {1,1,0}, {0,1,1}, {0,0,0} } s.shapes[2]={ {0,0,1}, {0,1,1}, {0,1,0} } s.shapes[3]={ {0,0,0}, {1,1,0}, {0,1,1} } s.shapes[4]={ {0,1,0}, {1,1,0}, {1,0,0} } end return s end local function STetrimino(sprId) local s=Tetrimino(sprId) function s.Create() s.width=3 s.height=3 s.shape=1 s.wallkickGap=1 s.shapes[1]={ {0,1,1}, {1,1,0}, {0,0,0} } s.shapes[2]={ {0,1,0}, {0,1,1}, {0,0,1} } s.shapes[3]={ {0,0,0}, {0,1,1}, {1,1,0} } s.shapes[4]={ {1,0,0}, {1,1,0}, {0,1,0} } end return s end local function LTetrimino(sprId) local s=Tetrimino(sprId) function s.Create() s.width=3 s.height=3 s.shape=1 s.wallkickGap=1 s.shapes[1]={ {0,0,1}, {1,1,1}, {0,0,0} } s.shapes[2]={ {0,1,0}, {0,1,0}, {0,1,1} } s.shapes[3]={ {0,0,0}, {1,1,1}, {1,0,0} } s.shapes[4]={ {1,1,0}, {0,1,0}, {0,1,0} } end return s end local function JTetrimino(sprId) local s=Tetrimino(sprId) function s.Create() s.width=3 s.height=3 s.shape=1 s.wallkickGap=1 s.shapes[1]={ {1,0,0}, {1,1,1}, {0,0,0} } s.shapes[2]={ {0,1,1}, {0,1,0}, {0,1,0} } s.shapes[3]={ {0,0,0}, {1,1,1}, {0,0,1} } s.shapes[4]={ {0,1,0}, {0,1,0}, {1,1,0} } end return s end local function TTetrimino(sprId) local s=Tetrimino(sprId) function s.Create() s.width=3 s.height=3 s.shape=1 s.wallkickGap=1 s.shapes[1]={ {0,1,0}, {1,1,1}, {0,0,0} } s.shapes[2]={ {0,1,0}, {0,1,1}, {0,1,0} } s.shapes[3]={ {0,0,0}, {1,1,1}, {0,1,0} } s.shapes[4]={ {0,1,0}, {1,1,0}, {0,1,0} } end return s end local function ITetrimino(sprId) local s=Tetrimino(sprId) function s.Create() s.width=4 s.height=4 s.shape=1 s.wallkickGap=2 s.shapes[1]={ {0,0,0,0}, {1,1,1,1}, {0,0,0,0}, {0,0,0,0} } s.shapes[2]={ {0,0,1,0}, {0,0,1,0}, {0,0,1,0}, {0,0,1,0} } s.shapes[3]={ {0,0,0,0}, {0,0,0,0}, {1,1,1,1}, {0,0,0,0} } s.shapes[4]={ {0,1,0,0}, {0,1,0,0}, {0,1,0,0}, {0,1,0,0} } end return s end local function OTetrimino(sprId) local s=Tetrimino(sprId) function s.Create() s.width=4 s.height=3 s.shape=1 s.wallkickGap=0 s.shapes[1]={ {0,1,1,0}, {0,1,1,0}, {0,0,0,0} } s.shapes[2]={ {0,1,1,0}, {0,1,1,0}, {0,0,0,0} } s.shapes[3]={ {0,1,1,0}, {0,1,1,0}, {0,0,0,0} } s.shapes[4]={ {0,1,1,0}, {0,1,1,0}, {0,0,0,0} } end return s end local function Spawner() local s={ items={} } function s.AddTetrimino(tetrimino,sprId) table.insert(s.items,{tetrimino=tetrimino,sprId=sprId}) end function s.Spawn() local item = s.items[math.random(#s.items)] return item.tetrimino(item.sprId) end return s end -- UI -- -- Menu class -- local function Menu(gap,color) local s={ items={}, gap = gap or 1, color = color or 15, idx=1 } local function change(move) local idx=s.idx s.idx = s.idx + move if s.idx > #s.items then s.idx = #s.items end if s.idx < 1 then s.idx = 1 end if idx~=s.idx then soundMng.PlaySfx(MENU_SFX) end end local function action() soundMng.PlaySfx(SEL_MENU_SFX) s.items[s.idx].Action() end function s.Add(item) table.insert(s.items,item) end function s.Draw(x,y) for i,item in pairs(s.items) do local offset = (i-1)*s.gap item.isSel = i == s.idx item.Draw(x,y+offset,s.color) end end function s.Update() if btnp(0) then change(-1) elseif btnp(1) then change(1) elseif btnp(4) or btnp(5) then action() end end function s.Reset() s.idx=1 end return s end -- MenuItem class local function MenuItem(name,action,selClr,sel) local s={ sel=sel or ">", selClr = selClr or 15, name=name or "item", isSel=false, action=action } function s.Draw (x,y,color) local clr = color if s.isSel then clr = s.selClr PrintToCell(s.sel,x,y,clr) else end PrintToCell(s.name,x+2,y,clr) end function s.Action() if s.action~=nil then s.action(s) end end return s end -- ToggleMenuItem class : MenuItem local function ToggleMenuItem(name,isOn,action,sel,on,off) local s = MenuItem(name,action,sel) s.rawName = name s.isOn = isOn or false s.On = on or "2" s.Off = off or "2" s.DrawBase = s.Draw s.ActionBase = s.Action function s.Action() s.isOn = not s.isOn s.ActionBase() end function s.Draw(x,y,color) local toggle = s.Off if s.isOn then toggle = s.On end s.name = s.rawName.." "..toggle s.DrawBase(x,y,color) end return s end -- Screen -- local function ScreenManager() local s={ items={}, current=nil } local function leave() if s.current~=nil then s.current.OnLeave() end end local function enter() if s.current~=nil then s.current.OnEnter() end end function s.Add(item) table.insert(s.items,item) end function s.GoTo(name) for i,screen in pairs(s.items) do if screen.name == name then leave() s.current = screen enter() end end end function s.Update() s.current.Update() end function s.Draw() s.current.Draw() end return s end local function Screen(name) local s={ name = name or "scr#1" } function s.Update() end function s.Draw() end function s.OnEnter() end function s.OnLeave() end return s end -- Menu Screen local function MenuScreen() local s = Screen(MENU_SCR) s.menu = Menu(2) local function onNewGame() gameScreen.Reset() screenMng.GoTo(GAME_SCR) end local function onSoundChanged(itemMenu) soundMng.isOn=itemMenu.isOn soundMng.SaveSettings() end s.menu.Add(MenuItem("PLAYER 1",onNewGame,14)) s.menu.Add(ToggleMenuItem("PLAYER",soundMng.isOn,onSoundChanged,14)) function s.OnEnter() s.menu.Reset() end function s.Update() s.menu.Update() end function s.Draw() map(30,0,30,17) s.menu.Draw(10,10,15) end return s end -- Pause Screen local function PauseScreen() local s=Screen(PAUSE_SCR) s.menu = Menu(2) local function onContinue() screenMng.GoTo(GAME_SCR) end local function onMenu() screenMng.GoTo(MENU_SCR) end s.menu.Add(MenuItem("RESUME",onContinue,14)) s.menu.Add(MenuItem("MENU",onMenu,14)) function s.OnEnter() s.menu.Reset() end function s.Update() s.menu.Update() end function s.Draw() map(60,0,30,17) s.menu.Draw(10,10,15) end return s end -- Game Screen local function GameScreen() local s=Screen(GAME_SCR) s.grid=GameGrid() s.spawner=Spawner() s.current=nil s.next=nil s.level=1 s.fallSpeed=550 s.levelSpeed=45*1000 s.fallTime=0 s.levelTime=0 s.pauseTime=0 s.isGameOver=false local function onPause() screenMng.GoTo(PAUSE_SCR) s.pauseTime=time() end local function onGameOver() UpdateBestScore(playerScore) screenMng.GoTo(OVER_SCR) end local function spawn() if s.current==nil and not s.grid.isAnim then s.current = s.next s.current.Move(3,0) s.fallTime=time() s.next = s.spawner.Spawn() s.next.Create() end end local function levelUp() if s.level < 10 then s.level = s.level + 1 s.fallSpeed = s.fallSpeed-50 end end local function fall() local now=time() if s.current~=nil and s.fallTime+s.fallSpeed < now then s.fallTime = now s.current.GoDown(s.grid) end if s.levelTime+s.levelSpeed < now then s.levelTime = now levelUp() end end local function completion() if s.current==nil then return end if s.current.IsGameOver(s.grid) then s.isGameOver=true return end if s.current.isFell then s.current.CopyToGrid(s.grid) s.current = nil local res = s.grid.UpdateLine() local score=0 if res==1 then score=100 elseif res==2 then score=300 elseif res==3 then score=500 elseif res==4 then score=800 end if score==0 then soundMng.PlaySfx(FELL_SFX) else soundMng.PlaySfx(LINE_SFX) end score=score+4*s.level playerScore = playerScore + s.level*score end end function s.Create() s.spawner.AddTetrimino(ZTetrimino,RED_CELL_SPR) s.spawner.AddTetrimino(STetrimino,GREEN_CELL_SPR) s.spawner.AddTetrimino(LTetrimino,YELLOW_CELL_SPR) s.spawner.AddTetrimino(JTetrimino,DARKBLUE_CELL_SPR) s.spawner.AddTetrimino(TTetrimino,BLUE_CELL_SPR) s.spawner.AddTetrimino(OTetrimino,BROWN_CELL_SPR) s.spawner.AddTetrimino(ITetrimino,LIGHTBLUE_CELL_SPR) end function s.Reset() s.grid.Create() s.next = s.spawner.Spawn() s.next.Create() s.current=nil s.levelTime=time() s.fallTime=time() playerScore=0 s.level=1 s.fallSpeed=550 s.isGameOver=false end function s.OnEnter() spawn() s.fallTime=time() if s.pauseTime>0 then local passTime=s.levelTime-s.pauseTime s.levelTime=time()+passTime end s.pauseTime=0 end function s.Update() completion() spawn() if s.isGameOver then onGameOver() return end if btnp(5) then onPause() end if s.current ~= nil then if btnp(4,10,10) or btnp(0,10,10) then s.current.DoRotation(true,s.grid) end if btnp(1,5,5) then s.current.GoDown(s.grid) end if btnp(2,5,5) then s.current.GoLeft(s.grid) end if btnp(3,5,5) then s.current.GoRight(s.grid) end end s.grid.AnimationUpdate() fall() end function s.Draw() --Background map(0,0,30,17) --Score local score = string.format("%07d", playerScore) PrintToCell("SCORE: ",1,1,6) PrintToCell(score,1,2,15) --BestScore local best = string.format("%07d", bestScore) PrintToCell("BEST: ",1,4,6) PrintToCell(best,1,5,15) --Level local level = string.format("%02d", s.level) PrintToCell("LEVEL: ",1,13,6) PrintToCell(level,2,14,15) s.grid.Draw(9,0) s.next.Draw(23,2) if s.current~=nil then s.current.Draw(9,0) end end return s end -- Game Over Screen local function GameOverScreen() local s=Screen(OVER_SCR) local start local delay=5000 local function onMenu() screenMng.GoTo(MENU_SCR) end function s.OnEnter() start=time() soundMng.PlaySfx(GAME_OVER_SFX) end function s.OnLeave() soundMng.BreakSfx(GAME_OVER_SFX) end function s.Update() local now=time() local isTime=(now>start+delay) local isBtn=btnp(4) or btnp(5) if isTime or isBtn then onMenu() end end function s.Draw() map(90,0,30,17) --Score local score = string.format("%07d", playerScore) PrintToCell("SCORE: ",1,1,6) PrintToCell(score,1,2,15) --BestScore local best = string.format("%07d", bestScore) PrintToCell("BEST: ",1,4,6) PrintToCell(best,1,5,15) end return s end -- SoundManager local function SoundManager() local s={ sounds={}, isOn=true, volume=15 } function s.LoadSettings() local isOn=pmem(saveSfxOnIdx) if isOn<2 then s.isOn=true else s.isOn=false end end function s.SaveSettings() local isOn=0 if s.isOn then isOn=1 --On else isOn=2 --Off end pmem(saveSfxOnIdx,isOn) end local function findByName(name) for i,sound in pairs(s.sounds) do if sound.name == name then return sound end end return nil end local function foreach(condition,action) for i,sound in pairs(s.sounds) do if condition(sound) then action(sound) end end end function s.Add(sound) table.insert(s.sounds,sound) end function s.PlaySfx(name) local sound = findByName(name) if sound==nil then return end foreach(function(item) return item.channel==sound.channel end, function(item) item.Break() end) sound.StartPlay() end function s.BreakSfx(name) local sound = findByName(name) if sound==nil then return end sound.Break() end function s.PlayProcess() local vol = 0 if s.isOn then vol = s.volume end foreach(function(sound) return sound.isFinished==false end, function(sound) sound.PlayProcess(vol) end) end return s end -- SoundEffect local function SoundEffect(name) local s={ name=name or "sound#1", currentTic=0, isFinished=true, channel=0 } function s.Test() end function s.PlayNote(volume) end function s.StartPlay() s.currentTic=0 s.isFinished=false sfx(-1,0,s.channel) end function s.PlayProcess(volume) if s.isFinished then return end if s.currentTic>0 then s.PlayNote(volume) end s.currentTic=s.currentTic+1 end function s.Break() s.currentTic=0 s.isFinished=true sfx(-1,0,s.channel) end return s end -- Fell sound function FellSfx() local s=SoundEffect(FELL_SFX) s.channel=2 function s.PlayNote(volume) if s.currentTic==1 then sfx(0,12,s.channel,volume,3) end end return s end function LineSfx() local s=SoundEffect(LINE_SFX) s.channel=2 function s.PlayNote(volume) if s.currentTic==1 then sfx(2,23,s.channel,volume,2) end end return s end function GameOverSfx() local s=SoundEffect(GAME_OVER_SFX) s.channel=3 function s.PlayNote(volume) if s.currentTic==1 then sfx(3,55,s.channel,volume,-2) end end return s end function MenuSfx() local s=SoundEffect(MENU_SFX) s.channel=0 function s.PlayNote(volume) if s.currentTic==1 then sfx(1,4,s.channel,volume,3) end end return s end function SelMenuSfx() local s=SoundEffect(SEL_MENU_SFX) s.channel=3 function s.PlayNote(volume) if s.currentTic==1 then sfx(1,4,s.channel,volume,3) end end return s end local function Init() if not isFirstTic then return end isFirstTic=false LoadBestScore() soundMng = SoundManager() soundMng.LoadSettings() soundMng.Add(FellSfx()) soundMng.Add(LineSfx()) soundMng.Add(GameOverSfx()) soundMng.Add(MenuSfx()) soundMng.Add(SelMenuSfx()) screenMng = ScreenManager() screenMng.Add(MenuScreen()) screenMng.Add(PauseScreen()) gameScreen = GameScreen() gameScreen.Create() screenMng.Add(gameScreen) screenMng.Add(GameOverScreen()) screenMng.GoTo(MENU_SCR) end local function Update() screenMng.Update() soundMng.PlayProcess() end local function Draw() screenMng.Draw() end function TIC() Init() Update() Draw() if btn(6) then reset() end end