From 4bf41dacba58f1d2acc8501b680a20977a004a89 Mon Sep 17 00:00:00 2001 From: Ethan John Date: Tue, 16 Jul 2024 08:08:38 +0800 Subject: [PATCH] Up, Down, Top-down (#1958) * Add files via upload * Add files via upload * Rename to Up_Down_Top-down.js * Rename to Up_Down_Top-down.png * Fixing metadata --------- Co-authored-by: graham --- games/Up_Down_Top-down.js | 1797 ++++++++++++++++++++++++++++++++ games/img/Up_Down_Top-down.png | Bin 0 -> 33052 bytes 2 files changed, 1797 insertions(+) create mode 100644 games/Up_Down_Top-down.js create mode 100644 games/img/Up_Down_Top-down.png diff --git a/games/Up_Down_Top-down.js b/games/Up_Down_Top-down.js new file mode 100644 index 0000000000..5d734fdd8a --- /dev/null +++ b/games/Up_Down_Top-down.js @@ -0,0 +1,1797 @@ +/* +@title: Up, Down, Top-down +@author: Somebud0180 +@tags: ['platformer', 'maze'] +@addedOn: 2024-07-15 +*/ + +// A game with blocks, gravity blocks, flags, and stuff +// A platformer game with a maze, featuring 20 maps. +// Also featuring, a menu, that took probably half the programming time +// Most code can be found with "documentation" at the +// Github repo: https://github.com/Somebud0180/Up-Down-Top-down/ + +// Controls +// They're in the guide, when a item is highlighted, press L to display the description +// L to Continue (Open the guide for more) + +const player = "p"; +const topDownSpawner = "t"; +const arrow = "q"; +const block = "b"; +const magicBlock = "v"; +const flagDown = "o"; +const flagUp = "c"; +const gravityBlockDown = "m"; +const gravityBlockUp = "n"; + +const background = "z"; +const textBackground = "y"; +const leftTextBackground = "x"; +const rightTextBackground = "r"; + +const buttonW = "w"; +const buttonA = "a"; +const buttonS = "s"; +const buttonD = "d"; +const buttonI = "i"; +const buttonK = "k"; +const buttonJ = "j"; +const buttonL = "l"; +const blockActive = "h"; +let buttonActive = "g"; + +let pingError; // Check errorPing() +let isMoving = 0; // Check gravityPull() +let jumpHeight = 0; // Check jumpUp() +let ableToJump = 0; // Check jumpUp() +let currentPointer; // Check pointer functions +let pointerX = 2; // Check pointer functions +let pointerY = 6; // Check pointer functions +let optionCoord; // Check pointer functions +let playerCoord; +let belowTile; // Check jumpUp() +let lowerTile; // Check jumpUp() +let verticalTile; // Check gravityBlockDetection() +let allBlocks; // Check displayBlocksInRange() + +// Explained at updateGameIntervals() +let pointerChangeInterval; +let flagDetectionInterval; +let blockDetectionInterval; +let gravityLoopInterval; +let jumpLoopInterval; + +// Resources +// Active Textures +const buttonLGlyph = bitmap` +................ +................ +................ +................ +.....222222..... +....22000222.... +....22022022.2.. +....22000222.22. +....22022022.22. +....22022022.2.. +....22022022.... +.....222222..... +................ +................ +................ +................`; + +// Inactive Textures +const buttonWInactiveGlyph = bitmap` +................ +.......11....... +......1111...... +................ +.....111111..... +....11011111.... +....11011111.... +....11011111.... +....11011111.... +....11011111.... +....11000011.... +.....111111..... +................ +................ +................ +................`; +const buttonAInactiveGlyph = bitmap` +................ +................ +................ +................ +.....111111..... +....11011111.... +..1.11011111.... +.11.11011111.... +.11.11011111.... +..1.11011111.... +....11000011.... +.....111111..... +................ +................ +................ +................`; +const buttonSInactiveGlyph = bitmap` +................ +................ +................ +................ +.....111111..... +....11011111.... +....11011111.... +....11011111.... +....11011111.... +....11011111.... +....11000011.... +.....111111..... +................ +......1111...... +.......11....... +................`; +const buttonDInactiveGlyph = bitmap` +................ +................ +................ +................ +.....111111..... +....11011111.... +....11011111.1.. +....11011111.11. +....11011111.11. +....11011111.1.. +....11000011.... +.....111111..... +................ +................ +................ +................`; +const buttonIInactiveGlyph = bitmap` +................ +.......11....... +......1111...... +................ +.....111111..... +....11000111.... +....11011011.... +....11000111.... +....11011011.... +....11011011.... +....11011011.... +.....111111..... +................ +................ +................ +................`; +const buttonJInactiveGlyph = bitmap` +................ +................ +................ +................ +.....111111..... +....11000111.... +..1.11011011.... +.11.11000111.... +.11.11011011.... +..1.11011011.... +....11011011.... +.....111111..... +................ +................ +................ +................`; +const buttonKInactiveGlyph = bitmap` +................ +................ +................ +................ +.....111111..... +....11000111.... +....11011011.... +....11000111.... +....11011011.... +....11011011.... +....11011011.... +.....111111..... +................ +......1111...... +.......11....... +................`; +const buttonLInactiveGlyph = bitmap` +................ +................ +................ +................ +.....111111..... +....11000111.... +....11011011.1.. +....11000111.11. +....11011011.11. +....11011011.1.. +....11011011.... +.....111111..... +................ +................ +................ +................`; + +// Highlight +const buttonHighlightTexture = bitmap` +................ +................ +................ +.....222222..... +....21111112.... +...2111111112... +...2111111112... +...2111212112... +...2112121112... +...2111111112... +...2111111112... +....21111112.... +.....222222..... +................ +................ +................`; +const blockHighlightTexture = bitmap` +2222222222222222 +2222222222222222 +................ +................ +................ +................ +................ +................ +................ +................ +................ +................ +................ +................ +................ +................`; + +const playerDown = bitmap` +................ +................ +................ +................ +.......22....... +......2222...... +......2222...... +................ +......2222...... +...2222222222... +....22222222.... +.....222222..... +.....222222..... +......2222...... +.......22....... +................`; +const playerUp = bitmap` +................ +.......22....... +......2222...... +.....222222..... +.....222222..... +....22222222.... +...2222222222... +......2222...... +................ +......2222...... +......2222...... +.......22....... +................ +................ +................ +................`; +const playerTop = bitmap` +................ +................ +................ +................ +.......11....... +......1221...... +..LLL122221LLL.. +.L221222222122L. +.L221222222122L. +..LLL122221LLL.. +......1221...... +.......11....... +................ +................ +................ +................`; +const playerTopSide = bitmap` +................ +.......LL....... +......L22L...... +......L22L...... +......L11L...... +......1221...... +.....122221..... +....12222221.... +....12222221.... +.....122221..... +......1221...... +......L11L...... +......L22L...... +......L22L...... +.......LL....... +................`; + +const topDownSpawnerTexture = bitmap` +................ +................ +................ +...77........... +...77........... +...77........... +.777777......... +..7777.......... +...77........... +.........9...... +...11...99...... +..1221.999999... +..1221.999999... +...11...99...... +.........9...... +................`; +const arrowTexture = bitmap` +........22...... +........222..... +........2222.... +........22222... +.2222222222222.. +.22222222222222. +.22222222222222. +.2222222222222.. +........22222... +........2222.... +........222..... +........22...... +................ +................ +................ +................`; +const magicBlockTexture = bitmap` +LLLLLLLLLLLLLLLL +L00LLLLLLLLLLLLL +L0000000000000LL +LL000000000000LL +LL000000000000LL +LL000077770000LL +LL000777777000LL +LL000775577000LL +LL000775577000LL +LL000777777000LL +LL000077770000LL +LL000000000000LL +LL000000000000LL +LL0000000000000L +LLLLLLLLLLLLL00L +LLLLLLLLLLLLLLLL`; +const blockTexture = bitmap` +LLLLLLLLLLLLLLLL +L00LLLLLLLLLLLLL +L0000000000000LL +LL000000000000LL +LL000000000000LL +LL000077770000LL +LL000777777000LL +LL000777777000LL +LL000777777000LL +LL000777777000LL +LL000077770000LL +LL000000000000LL +LL000000000000LL +LL0000000000000L +LLLLLLLLLLLLL00L +LLLLLLLLLLLLLLLL`; +const gravityBlockDownTexture = bitmap` +................ +................ +.......77....... +.......77....... +.......77....... +.......77....... +....77777777.... +.....777777..... +......7777...... +....7..77..7.... +....7......7.... +..77777..77777.. +...777....777... +....7......7.... +................ +................`; +const gravityBlockUpTexture = bitmap` +................ +................ +....9......9.... +...999....999... +..99999..99999.. +....9......9.... +....9..99..9.... +......9999...... +.....999999..... +....99999999.... +.......99....... +.......99....... +.......99....... +.......99....... +................ +................`; +const flagDownTexture = bitmap` +................ +................ +..CCDDDD...DDD.. +..CCDDDDDDDDDD.. +..CCDDDDDDDDDD.. +..CCDDDDDDDDDD.. +..CCDDDDDDDDDD.. +..CCDDDDDDDDDD.. +..CCDDDDDDDDDD.. +..CC...DDDDD.... +..CC............ +..CC............ +..CC............ +..CC............ +..CC............ +..CC............`; +const flagUpTexture = bitmap` +............CC.. +............CC.. +............CC.. +............CC.. +............CC.. +............CC.. +....DDDDD...CC.. +..DDDDDDDDDDCC.. +..DDDDDDDDDDCC.. +..DDDDDDDDDDCC.. +..DDDDDDDDDDCC.. +..DDDDDDDDDDCC.. +..DDDDDDDDDDCC.. +..DDD...DDDDCC.. +................ +................`; + +const backgroundTexture = bitmap` +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000 +0000000000000000`; +const textBackgroundTexture = bitmap` +................ +3333333333333333 +33LL333LLLL3LLL3 +3LLLL3LLLLL3LLL3 +LLLLLLLLLLLLLLLL +LLLLLLLLLLLLLLLL +LLLLLLLLLLLLLLLL +LLLLLLLLLLLLLLLL +LLLLLLLLLLLLLLLL +LLLLLLLLLLLLLLLL +LLLLLLLLLLLLLLLL +LLLLLLLLLLLLLLLL +LLLLLLLLLLLLLLLL +LLLLLLLLLLLLLLLL +LLLLLLLLLLLLLLLL +LLLLLLLLLLLLLLLL`; +const leftTextBackgroundTexture = bitmap` +................ +.......333333333 +.......33L3LLLLL +.......3LLLLLLLL +.......LLLLLLLLL +.......LLLLLLLLL +.......LLLLLLLLL +.......LLLLLLLLL +.......LLLLLLLLL +.......LLLLLLLLL +.......LLLLLLLLL +.......LLLLLLLLL +.......LLLLLLLLL +.......LLLLLLLLL +.......LLLLLLLLL +.......LLLLLLLLL`; +const rightTextBackgroundTexture = bitmap` +................ +333333333....... +3LLL3LL33....... +LLLL3LLL3....... +LLLLLLLLL....... +LLLLLLLLL....... +LLLLLLLLL....... +LLLLLLLLL....... +LLLLLLLLL....... +LLLLLLLLL....... +LLLLLLLLL....... +LLLLLLLLL....... +LLLLLLLLL....... +LLLLLLLLL....... +LLLLLLLLL....... +LLLLLLLLL.......`; + +const levels = [ + map` +............... +............... +............... +...w.......i... +..a.d.....j.l.. +...s.......k... +..........o..n. +.p..b..v..c..m. +............... +.....l......... +............... +...............`, // 0 Guide + map` +b.vvvvvvvvvvv.b +m.vn.......mv.m +..v.........v.. +..v.........v.. +..vvvvvvvvvvv.. +............... +............... +............... +............... +...........b... +n.bb.b.b.bb...o +b.............b`, // 1 Main Menu || Game Map 1 + map` +............... +............... +..............o +............bbb +.........bb.... +............... +........b...... +.......b....... +.....b......... +..b.b.......... +bb............. +...............`, // 2 Game Map 2 "Steps" + map` +....b...b.bbbbb +....b...b.....c +..b.b...m...... +..m.b.......... +....b.......... +....m.......... +........n...... +........b...... +........b...... +.n..n...b...... +bb..b.n.b...... +....b.b.b......`, // 3 Game Map 3 "Switch" + map` +bbbbbbbbbbbbbbb +b.....tb......o +bbb.bbbb.bb.bbb +b...b..b..b...b +b.b....bbbbb..b +b.bbbb.b...bb.b +b..b.bbb.b..b.b +b.bb...b.bb.b.b +b....b.b......b +bbbb.b.bb.bbb.b +b....b....b...b +bbbbbbbbbbbbbbb`, // 4 Game Map 4 "Pathfinder" + map` +............... +............... +bbb............ +............... +............... +............... +............... +............... +............... +............... +..............o +............bbb`, // 5 Game Map 5 "Slop" + map` +............... +..............o +.bbb..bb.bb.bbb +............... +b.............. +bb..bb..bb..... +...........bbb. +............... +..............b +.............bb +...........b... +bbbbbbbbbb.....`, // 6 Game Map 6 "Simple" + map` +............... +.....b.......o. +........b..bbb. +...b........... +..............b +.bbbbb..b..b.bb +............... +b.............. +bbb..bb..b..bb. +............... +..............b +bb..b..b..b..bb`, // 7 Game Map 7 "Sly" + map` +m....m.m....... +....bmbmbb....o +..mb.m.m..bbbbb +.bm............ +............... +.........n..... +.........b..... +..........b.bb. +............... +....b..b......b +..b.........b.. +b........bbb...`, // 8 Game Map 8 "Thin Line" + map` +bbbbbbbbbbbbbbb +mmmmm.......... +..............o +.bbb.b.b.bb.bbb +.b..........mmm +n...........mm. +bb.b.b..b.b.m.. +............bb. +.............b. +.............b. +...b....b..b..n +bb....b....bbbb`, // 9 Game Map 9 "Up and Down" + map` +bbbbbbbbbbbbbbbbbbb +b............b....b +b.bb.bb.bbb.bb.bb.b +b.b...b.....b.....b +bbb.b...bbb...bbbbb +b...bb.......bb...b +bbb.b..bb.bb..b.bbb +ob..b.bb.t.bb.b...o +bbb.b..bb.bb..b.bbb +b...bb....b..bb...b +bbbbb...bbb.b.b.bbb +b.....b.....b.....b +b.bb.bbbbbb.bb.bb.b +b.......b.........b +bbbbbbbbbbbbbbbbbbb`, // 10 Game Map 10 "Familiar" + map` +bbbbbbbbbbbbbbb +b..b.......b..b +bo.v.......b.ob +bbbbbb...bbbbbb +.......b....... +............... +....b..b..b.... +.b....bbb....b. +............... +b.............b +..b.b..p..b.b.. +....bbbbbbb....`, // 11 Game Map 11 "Face" + map` +..........b..mm +..........b.... +....bn....b...o +.bnnb...n.b.bbb +..bbb.n...b.... +.bmmb.....b.... +....b..n..b.... +bbb.bn....b.... +....b.....b.... +....b.n.n.b.... +...nb....n.nnnn +bbbbbbbbbbbbbbb`, // 12 Game Map 12 "Float" + map` +bbbbbbbbbbbbbbb +t.bbb....bbb.bb +b..b..bb..b...b +bb..b..bb.b.b.b +bbb..b.b..b.b.b +b.bb.b.b.bb.b.b +b......b....b.b +b.bbbbbbbbb.b.b +b..b.....b..b.b +bb.b.b.bbbbbb.b +b....b.bo.....b +bbbbbbbbbbbbbbb`, // 13 Game Map 13 "Bumpy" + map` +b.....bbb.....b +b.....vov.....b +b..bbbbbbbbb..b +bb.b...b...b.bb +b..v..bbb..b..b +b.bbb..b..bbb.b +b..b..bbb..b..b +b......b......b +bb..bbbbbbb..bb +b....b...b....b +b.b..v.p.v..b.b +b...bbbbbbb...b`, // 14 Game Map 14 "Skeleton" + map` +bbbbmbbbbvvvvvm +m......pb...... +.b..m..bb.....o +.b.....b.....bb +.b.n.n.b....... +.b.....b.v.v.v. +.b.....b.v.v.v. +.b.....bmb.b.b. +.b.....b....... +..b...b..b..... +bn..b..b.bnnnnn +.........bbbbbb`, // 15 Game Map 15 "W" + map` +bbbbbbbbbbbbbbb +b.......b...b.b +b.bbbbbbb.b...b +b.....b...bbb.b +b.b.b.bbb.b...b +b.b.b.btb.bbb.b +b.b.b.......b.b +b.bbbbbbbbbbb.b +b.b...b...b...b +b.b.b.bbb.b.b.b +b...b..ob...b.b +bbbbbbbbbbbbbbb`, // 16 Game Map 16 "Lines" + map` +bbbbbbbbbbbbbbb +b.............b +bbb.bbtbbbbbb.b +b.b..bbb....b.b +b.bb.....bb.b.b +b....bbbbb..b.b +b.b.bbbb...bb.b +b.b.b.bb.bbb..b +bbb.b..b.b...bb +b...bb.b.b.bbbo +b.b....b.b..... +bbbbbbbbbbbbbbb`, // 17 Game Map 17 "Lol" + map` +....bm.m.b...bb +....bm...b....m +....bm...b...b. +....b....b.m.b. +....m....b..mb. +.m......nbm..b. +.....n...b...b. +...m.b...b.m.b. +....nb...b...b. +....nb.......b. +..p.nb.......bo +..n.nb.....n.bb`, // 18 Game Map 18 "Tense" + map` +bbbbbbbbbbbbbbbbbbbb +bt....b...b....b...b +b.bbb...b...b.bb.b.b +b...b.bbbb.b.....b.b +b.b......b.bbbbb.b.b +b.bb.b.b.b.....b.b.b +b.b..b.b.b.b.b.b.b.b +b.b.bbbb...b.b...b.b +bb..b....bb....bbb.b +b..bbbbbbb..b.bb.b.b +b.bb.b.b.vb...v....b +b.b..v.b.ob.b.b.bb.b +b.b.bb.b.vb.bbb.b..b +b....b...b........bb +bbbbbbbbbbbbbbbbbbbb`, // 19 Game Map 19 "Masive" + map` +m.........o......mmm +.mmmmmmmmmbbbbbbb... +.b.b..b..bmmbbbmmb.. +.................b.. +..b.b..b...m.b...b.. +..........bm.b...... +...........m.b...... +....b..b...bnb...... +.b...........b.mmmmm +.................... +b...........bmmmmmm. +b..b...b...bb....... +b..b...b...b........ +.............b...... +b..b...b...bb.n....n`, // 20 Game Map 20 "Final Hurdle" + map` +bbbbbbbbbbbbbbb +.n...........m. +............... +............... +.n...........m. +............... +............... +.n...........m. +............... +............... +.n...........m. +bbbbbbbbbbbbbbb`, // 21 Finish Screen +]; + +const menuSFX = tune` +750: C4~750, +23250`; +const errorSFX = tune` +150: D4^150 + C4/150, +150: C4/150 + D4^150, +4500`; +const stepSFX = tune` +428.57142857142856: C4~428.57142857142856 + D4~428.57142857142856, +13285.714285714284`; +const jumpSFX = tune` +428.57142857142856: F4~428.57142857142856 + G4~428.57142857142856, +13285.714285714284`; +const gravityChangeSFX = tune` +300: F4^300, +9300`; +const deathSFX = tune` +75: B4^75, +75: B4~75, +75: B4^75, +75: B4~75, +75: B4^75, +2025`; +const finishSFX = tune` +240: A5~240, +240: B5~240, +240: A5~240, +6960`; +const completeSFX = tune` +150: G4~150, +150: G4~150, +150: A4~150, +150: A4~150, +150: D5~150, +150: D5~150, +150, +150: F5^150, +150: E5^150, +150: F5^150, +150: E5^150, +150, +150: D5~150, +150, +150: D5~150, +150, +150: D5~150, +150, +150: C5~150, +150: E5~150, +150: C5~150, +150: D5~150, +150, +150: A4/150, +150: A4/150, +150: D5/150, +150: D5/150, +150: G5/150, +150: G5/150, +150, +150: G5-150, +150: G5-150`; + +// Texts (Looks more clean here) +let currentLevelText; + +let mainMenuTitle = ` + Up Down + + Top-Down +`; + +let mainMenuOptions = ` + Start Game + ---------- + + Guide + ----- +`; + +let backButton = ` +Back +---- +`; + +let tutorial1 = ` +Double-click +the jump button +to get higher +`; + +let tutorial2 = ` +Get on +top the +Gravity +Block to +activate`; + +let errorSpawn = ` +Error: Couldn't +find empty tile +for player spawn`; + +let deathText = `You died`; + +let finishText = ` +You completed + the game! + + + + Thanks for + playing!`; + +// Guide Texts +let menuGuide = `Press + +to activate`; +// Controls +let upLGuide = `Moves player +upward in +Top-down mode`; +let leftLGuide = `Moves player to +the left`; +let downLGuide = `Moves player +downward in +Top-down mode`; +let rightLGuide = `Moves player to +the right`; +let upRGuide = `Makes player jump`; +let leftRGuide = `Returns to menu +(Level is saved)`; +let downRGuide = `Makes player jump, +also acts as the +back button in +the menu`; +let rightRGuide = `Confirm menu +selection, also +acts as level skip`; +// Blocks +let playerGuide = `It's you!`; +let blockGuide = `A solid platform, +good for standing +on`; +let magicBlockGuide = `???`; +let flagGuide = `Get near it +and you complete +the level`; +let gravityBlockGuide = `Stand on this +block and watch +as you flip +upside down`; + +// Game Default States +// Do not move this up, currentPlayer requires the texture from above +let gameState = 0; // 0 for Main Menu; 1 for In-game; 2 for Death +let menuMode = 1; // 1 for Main Menu; 2 for Guide +let pointerOption = 0; +let backButtonState = "2"; // 1 is Gray (unselected); 2 is White (selected) +let level = 1; // 0 for Guide; 1 for Main Menu; Last map for Finish screen; The in between are the game maps +let lastLevel = 1; // Tracks level before mainMenu to allow accessing the main menu whilst in game +let spawnX = 0; // Automatic +let spawnY = 0; // Automatic +let spawnHeight = 0; +let currentPlayer = playerDown; +let gravity = "down"; +let rotation = "horizontal"; + +// Main Menu starts here +mainMenu(); + +// Controls +onInput("w", () => { + if (gameState == 0) { + pointerUp(); + } else if (gameState == 1) { + if (gravity == "top") { + rotation = "vertical"; + getFirst(player).y -= 1; + playTune(stepSFX); + characterInit(); + } + } +}); + +onInput("s", () => { + if (gameState == 0) { + pointerDown(); + } else if (gameState == 1) { + if (gravity == "top") { + rotation = "vertical"; + getFirst(player).y += 1; + playTune(stepSFX); + characterInit(); + } + } +}); + +onInput("a", () => { + if (gameState == 0) { + pointerUp(); + } else if (gameState == 1) { + rotation = "horizontal"; + getFirst(player).x -= 1; + playTune(stepSFX); + characterInit(); + } +}); + +onInput("d", () => { + if (gameState == 0) { + pointerDown(); + } else if (gameState == 1) { + rotation = "horizontal"; + getFirst(player).x += 1; + playTune(stepSFX); + characterInit(); + } +}); + +onInput("i", () => { + if (gameState == 1) { + jumpUp(); + } +}); + +onInput("k", () => { + if (gameState == 0) { + pointerContinue("k"); + pointerBack(); + } else if (gameState == 1) { + jumpUp(); + } +}); + +onInput("j", () => { + if (gameState == 1 && level != levels.length - 1) { + // Make sure no one opens the menu during end + mainMenu(); + } +}); + +onInput("l", () => { + if (gameState == 0) { + pointerContinue(); + } else if (gameState == 1) { + if (level > 0 && level < levels.length - 4) { + nextLevel(); + } else { + pingError = true; + } + } +}); + +afterInput(() => { + displayBlocksInRange(); +}); + +/// Menu Code +// Sets up the main menu +function mainMenu() { + pointerX = 2; + pointerY = 6; + gameState = 0; + menuMode = 1; + pointerOption = 0; + updateGameIntervals(); + + // Check for current level + if (level > 1 && level < levels.length - 1) { + lastLevel = level; // Remember last level before mainMenu (if Applicable) + } else { + lastLevel = 1; + } + currentLevelText = `Current level: ${lastLevel}`; // Grab level and add to text + clearText(); + setTextures(); + level = 1; + setMap(levels[level]); + setBackground(background); + pointerChange(); // Trigger pointer spawning in advance (Rather than wait for interval) + + addText(mainMenuTitle, { + x: 4, + y: 1, + color: color`2`, + }); + addText(mainMenuOptions, { + x: 3, + y: 7, + color: color`2`, + }); + addText(currentLevelText, { + x: 2, + y: 15, + color: color`1`, + }); +} + +// Sets up the guide +function guideScreen() { + gameState = 0; + menuMode = 2; + updateGameIntervals(); + clearText(); + setTextures(); + level = 0; + setMap(levels[level]); + setBackground(background); + addBack(); + addText(menuGuide, { x: 1, y: 12, color: color`1` }); +} + +function addBack() { + clearText(); + addText(backButton, { + x: 2, + y: 0, + color: backButtonState, + }); +} + +// Handles pointer blinking and spawning +function pointerChange() { + if (menuMode == 1) { + // Point to selected + if (currentPointer == arrow) { + clearTile(pointerX, pointerY); + addSprite(pointerX, pointerY, buttonL); + } else { + clearTile(pointerX, pointerY); + addSprite(pointerX, pointerY, arrow); + } + currentPointer = getTile(pointerX, pointerY)[0].type; + } +} + +// Handles pointer selection in guide on-demand +function pointerUpdate() { + if (pointerOption == 1) { + if (getTile(5, 9) !== undefined) { + clearTile(5, 9); + } + updateGlyph(buttonW); + } else if (pointerOption == 2) { + updateGlyph(buttonA); + } else if (pointerOption == 3) { + updateGlyph(buttonS); + } else if (pointerOption == 4) { + updateGlyph(buttonD); + } else if (pointerOption == 5) { + updateGlyph(buttonI); + } else if (pointerOption == 6) { + updateGlyph(buttonJ); + } else if (pointerOption == 7) { + updateGlyph(buttonK); + } else if (pointerOption == 8) { + updateGlyph(buttonL); + } else if (pointerOption == 9) { + updateGlyph(player); + } else if (pointerOption == 10) { + updateGlyph(block); + } else if (pointerOption == 11) { + updateGlyph(magicBlock); + } else if (pointerOption == 12) { + updateGlyph(flagUp); + } else if (pointerOption == 13) { + updateGlyph(gravityBlockDown); + } else { + pointerOption = 0; + updateGlyph(); + } + // Change back button color + if (pointerOption == 0) { + backButtonState = color`2`; + addBack(); + } else { + backButtonState = color`1`; + addBack(); + } +} + +// Handles pointer movement (downwards) +function pointerDown() { + if (menuMode == 1) { + if (pointerOption == 0) { + clearTile(pointerX, pointerY); + pointerY += 2; + pointerOption++; + pointerChange(); + playTune(menuSFX); + } else { + pingError = true; + } + } else if (menuMode == 2) { + if (pointerOption < 13) { + pointerOption++; + pointerUpdate(); + playTune(menuSFX); + } else { + pingError = true; + } + } +} + +// Handles pointer movement (upwards) +function pointerUp() { + if (menuMode == 1) { + if (pointerOption == 1) { + clearTile(pointerX, pointerY); + pointerY -= 2; + pointerOption--; + pointerChange(); + playTune(menuSFX); + } else { + pingError = true; + } + } else if (menuMode == 2) { + if (pointerOption > 0) { + pointerOption--; + pointerUpdate(); + playTune(menuSFX); + } else { + pingError = true; + } + } +} + +// Allows pointer to jump back to the first option +function pointerBack() { + if (menuMode == 1) { + if (pointerOption == 1) { + clearTile(pointerX, pointerY); + pointerY -= 2; + pointerOption = 0; + pointerChange(); + } else { + pingError = true; + } + } else if (menuMode == 2) { + if (pointerOption > 0) { + pointerOption = 0; + pointerUpdate(); + } + } +} + +// Handles pointer selection and runs/displays them accordingly +function pointerContinue(triggered) { + if (menuMode == 1) { + // Main Menu + if (triggered == "k") { + // Check if triggered by back button + if (pointerOption == 0) { + pingError = true; + } + } else if (pointerOption == 0) { + // Start the Game + initializeGame(); + } else if (pointerOption == 1) { + // Go to Guide + pointerOption = 0; // Return to first option + guideScreen(); + } + } else if (menuMode == 2) { + // Guide + if (triggered == "k") { + // Check if triggered by back button + if (pointerOption == 0) { + mainMenu(); + } + } else if (pointerOption == 0) { + pointerOption = 0; // Return to first option + mainMenu(); + } else if (pointerOption > 0) { + guideText(); + } + } +} + +// Update current selected item texture to highlight in the guid +function updateGlyph(activeOption) { + let blockActiveSprite = getFirst(blockActive); + if (blockActiveSprite) { + blockActiveSprite.remove(); + } + if (pointerOption == 0) { + buttonActive = "g"; // Reset buttonActive and activeOption when switching from buttons to back + } else if (pointerOption > 0 && pointerOption < 9) { + buttonActive = activeOption; + } else if (pointerOption > 8) { + buttonActive = "g"; + optionCoord = getFirst(activeOption); + addSprite(optionCoord.x, 8, blockActive); + } + setTextures(); +} + +function guideText() { + if (pointerOption == 1) { + addBack(); // Clears text and rewrites the back button + addText(upLGuide, { x: 1, y: 12, color: color`2` }); + } else if (pointerOption == 2) { + addBack(); + addText(leftLGuide, { x: 1, y: 12, color: color`2` }); + } else if (pointerOption == 3) { + addBack(); + addText(downLGuide, { x: 1, y: 12, color: color`2` }); + } else if (pointerOption == 4) { + addBack(); + addText(rightLGuide, { x: 1, y: 12, color: color`2` }); + } else if (pointerOption == 5) { + addBack(); + addText(upRGuide, { x: 1, y: 12, color: color`2` }); + } else if (pointerOption == 6) { + addBack(); + addText(leftRGuide, { x: 1, y: 12, color: color`2` }); + } else if (pointerOption == 7) { + addBack(); + addText(downRGuide, { x: 1, y: 11, color: color`2` }); + } else if (pointerOption == 8) { + addBack(); + addText(rightRGuide, { x: 1, y: 12, color: color`2` }); + } else if (pointerOption == 9) { + addBack(); + addText(playerGuide, { x: 1, y: 12, color: color`2` }); + } else if (pointerOption == 10) { + addBack(); + addText(blockGuide, { x: 1, y: 12, color: color`2` }); + } else if (pointerOption == 11) { + addBack(); + addText(magicBlockGuide, { x: 1, y: 12, color: color`2` }); + } else if (pointerOption == 12) { + addBack(); + addText(flagGuide, { x: 1, y: 12, color: color`2` }); + } else if (pointerOption == 13) { + addBack(); + addText(gravityBlockGuide, { x: 1, y: 11, color: color`2` }); + } +} + +/// Game Logic + +// Special Map Check +function mapCheck() { + if (level == 2) { + addText(tutorial1, { x: 1, y: 0, color: color`1` }); + } else if (level == 3) { + addText(tutorial2, { x: 12, y: 10, color: color`1` }); + } +} + +// Setup the game +function initializeGame() { + characterInit(); + setSolids([player, block]); + setBackground(background); + level = lastLevel; // Restore lastLevel if applicable + setMap(levels[level]); + + spawn(); // Start Game +} + +// Spawn Code +function spawn() { + clearText(); // Cleans stuff before it + setMap(levels[level]); + allBlocks = getAll(block); // Used for displayBlocksInRange() + mapCheck(); + spawnFind(); + gameState = 1; + characterInit(); + updateGameIntervals(); + addSprite(spawnX, spawnY, player); + displayBlocksInRange(); +} + +// Reset Code +function reset() { + playTune(deathSFX); + if (level != 20) { + // Level 2l has a unique size that makes the background off centre, skips background there + addSprite(4, 5, leftTextBackground); // Makes the text background centre + addSprite(10, 5, rightTextBackground); // Makes the text background centre + for (let i = 5; i <= 9; i++) { + // Adds background to text to make it readable + addSprite(i, 5, textBackground); + } + } + addText(deathText, { + x: 6, + y: 7, + color: color`2`, + }); + gameState = 2; + updateGameIntervals(); + getFirst(player).remove(); + setTimeout(() => spawn(), 3000); +} + +// End Screen Code +function gameComplete() { + // Loads the final map and displays finishText + level = levels.length - 1; + spawn(); + playTune(completeSFX); + addText(finishText, { x: 4, y: 2, color: color`6` }); + setTimeout(() => { + mainMenu(); + }, 10000); +} + +// Dynamic Spawn Finding Code (Allows manually placed or automatic air finding) +function spawnFind() { + if (getFirst(topDownSpawner)) { + // Check for topDownSpawner and set level accordingly + spawnX = getFirst(topDownSpawner).x; + spawnY = getFirst(topDownSpawner).y; + gravity = "top"; + clearTile(spawnX, spawnY); + } else if (getFirst(player)) { + // Check for player and set accordingly + spawnX = getFirst(player).x; + spawnY = getFirst(player).y; + gravity = "down"; + clearTile(spawnX, spawnY); + } else { + // If there is neither run automatic detection + gravity = "down"; + spawnHeight = height() - 1; + spawnX = 0; + spawnY = spawnHeight; + while (spawnHeight == height() - 1) { + for (spawnY >= 0; spawnY--;) { + // Scan from bottom to top + if ( + getTile(spawnX, spawnY).length == 0 && + getTile(spawnX, spawnY + 1).length != 0 && + spawnY < spawnHeight + ) { + // Check for air block within bounds + spawnHeight = 0; // Reset spawnHeight + break; // Exit the loop if air block is found + } else if (spawnY >= spawnHeight) { + // Check if exceeded bounds + spawnHeight = 0; // Reset + pingError = true; + addText(errorSpawn, { + x: 2, + y: 6, + color: color`3`, + }); + setTimeout(() => { + console.log("Error: Couldn't find empty tile for player spawn"); + if (level > 1) { + level--; + } + spawn(); + }, 5000); + return; // Break the loop if no empty tile is found + } + } + } + } +} + +// Jump Code +function jumpUp() { + playerCoord = getFirst(player); + + // Check if ableToJump is 0 + if (ableToJump < 2) { + if (gravity == "down") { + jumpHeight++; // Tells jumpPull() to make player jump + ableToJump++; // Counts jumps made + } else if (gravity == "up") { + jumpHeight--; // Tells jumpPull() to make player jump + ableToJump++; // Counts jumps made + } + playTune(jumpSFX); + } +} + +// Jump Velocity Code +function jumpPull() { + // Increases player y postion whilst jumpHeight is not 0 + while (jumpHeight < 0) { + getFirst(player).y++; + jumpHeight++; + } + while (jumpHeight > 0) { + getFirst(player).y--; + jumpHeight--; + } +} + +// Gravity Code +function gravityPull() { + playerCoord = getFirst(player); + let downCollision = getTile(playerCoord.x, playerCoord.y + 1); + let upCollision = getTile(playerCoord.x, playerCoord.y - 1); + // Collision check + if ( + gravity == "down" && + downCollision.length != 0 && + downCollision[0].type != magicBlock + ) { + isMoving = 0; + ableToJump = 0; // Resets jump counter + } else if ( + gravity == "up" && + upCollision.length != 0 && + upCollision[0].type != magicBlock + ) { + isMoving = 0; + ableToJump = 0; // Resets jump counter + } else { + isMoving += 1; // Jumping feels better requiring two iterations than longer intervals + } + + // Apply Gravity + if (isMoving > 1) { + if (gravity == "down") { + // If gravity is down and there is no block below, lower the player y + getFirst(player).y++; + if (getFirst(player).y == height() - 1) { + // If player is at the bottom edge of the map, execute death function + reset(); + } + } else if (gravity == "up") { + // If gravity is up and there is no block above, increase the player y + getFirst(player).y--; + if (getFirst(player).y == 0) { + // If player is at the top edge of the map, execute death function + reset(); + } + } + } +} + +// Gravity Block Code +function gravityBlockDetection() { + playerCoord = getFirst(player); + if ( + gravity == "down" && + getTile(playerCoord.x, playerCoord.y + 1).length != 0 + ) { + // Checks current gravity and if there is a exisiting block below player + verticalTile = getTile(playerCoord.x, playerCoord.y + 1)[0]; + } else if ( + gravity == "up" && + getTile(playerCoord.x, playerCoord.y - 1).length != 0 + ) { + // Checks current gravity and if there is a exisiting block above player + verticalTile = getTile(playerCoord.x, playerCoord.y - 1)[0]; + } else { + // Exits function if none is found + return; + } + // Check tiles above and below for gravity blocks + if (verticalTile.type == gravityBlockDown) { + playTune(gravityChangeSFX); + gravity = "down"; + characterInit(); + } else if (verticalTile.type == gravityBlockUp) { + playTune(gravityChangeSFX); + gravity = "up"; + characterInit(); + } +} + +function displayBlocksInRange() { + if (gravity == "top") { + if (getFirst(player)) { + // Get the player's coordinates + playerCoord = getFirst(player); + let playerX = playerCoord.x; + let playerY = playerCoord.y; + + // Define the range around the player (5 grids in each direction) + const range = 3; + + for (let blockSprite of allBlocks) { + let blockX = blockSprite.x; + let blockY = blockSprite.y; + addSprite(blockX, blockY, block); + } + + for (let blockSprite of allBlocks) { + let blockX = blockSprite.x; + let blockY = blockSprite.y; + + // Calculate the distance between the block and the player + const distance = + Math.abs(blockX - playerX) + Math.abs(blockY - playerY); + + // Check if the block is within the specified range around the player + if (distance <= range) { + if (!getTile(blockX, blockY)) { + // If block is within range, add it to the game + addSprite(blockX, blockY, blockSprite.type); + } + } else { + if (getTile(blockX, blockY)) { + // If block exceeds the range, remove it from the game + clearTile(blockX, blockY); + } + } + } + } + } +} + +// Checks for a nearby flag and progress the level +function flagDetection() { + playerCoord = getFirst(player); + let surroundingTiles = [ + getTile(playerCoord.x, playerCoord.y + 1)[0], // Tile below player + getTile(playerCoord.x, playerCoord.y - 1)[0], // Tile above player + getTile(playerCoord.x + 1, playerCoord.y)[0], // Tile to the right of player + getTile(playerCoord.x - 1, playerCoord.y)[0], // Tile to the left of playerd + ]; + + // Checks if surrounding tiles contain a flag and stores them + let flagFound = surroundingTiles.some( + (tile) => tile && (tile.type == flagDown || tile.type == flagUp), + ); + + // If flagFound returns something, run + if (flagFound) { + playTune(finishSFX, 1); + nextLevel(); + } +} + +function nextLevel() { + if (levels.length - 2 == level) { + // Checks if game is complete and loads the end screen + gameComplete(); + return; + } else { + level++; + spawn(); + } +} + +function errorPing() { + if (pingError == true) { + playTune(errorSFX); + pingError = false; + } +} + +// Character Update Code +function characterInit() { + // Checks for character gravity and applies the corresponding texture + if (gravity == "down") { + currentPlayer = playerDown; + } else if (gravity == "up") { + currentPlayer = playerUp; + } else if (gravity == "top") { + if (rotation == "vertical") { + currentPlayer = playerTop; + } else if (rotation == "horizontal") { + currentPlayer = playerTopSide; + } + } + setTextures(); +} + +// Texture Update Code +function setTextures() { + // This function loads the required textures for each gameState and menuMode + if (gameState == 0) { + // Main Menu or Guide check + if (menuMode == 1) { + setLegend( + [player, currentPlayer], + [topDownSpawner, topDownSpawnerTexture], + [arrow, arrowTexture], + [block, blockTexture], + [magicBlock, magicBlockTexture], + [flagDown, flagDownTexture], + [flagUp, flagUpTexture], + [gravityBlockDown, gravityBlockDownTexture], + [gravityBlockUp, gravityBlockUpTexture], + [buttonL, buttonLGlyph], + [blockActive, blockHighlightTexture], + [background, backgroundTexture], + [textBackground, textBackgroundTexture], + [leftTextBackground, leftTextBackgroundTexture], + [rightTextBackground, rightTextBackgroundTexture], + ); + } else if (menuMode == 2) { + setLegend( + [player, currentPlayer], + [arrow, arrowTexture], + [background, backgroundTexture], + [block, blockTexture], + [magicBlock, magicBlockTexture], + [flagDown, flagDownTexture], + [flagUp, flagUpTexture], + [gravityBlockDown, gravityBlockDownTexture], + [gravityBlockUp, gravityBlockUpTexture], + [buttonW, buttonWInactiveGlyph], + [buttonA, buttonAInactiveGlyph], + [buttonS, buttonSInactiveGlyph], + [buttonD, buttonDInactiveGlyph], + [buttonI, buttonIInactiveGlyph], + [buttonJ, buttonJInactiveGlyph], + [buttonK, buttonKInactiveGlyph], + [buttonL, buttonLInactiveGlyph], + [buttonActive, buttonHighlightTexture], + [blockActive, blockHighlightTexture], + ); + } + } else if (gameState == 1) { + setLegend( + [player, currentPlayer], + [topDownSpawner, topDownSpawnerTexture], + [block, blockTexture], + [magicBlock, magicBlockTexture], + [flagDown, flagDownTexture], + [flagUp, flagUpTexture], + [gravityBlockDown, gravityBlockDownTexture], + [gravityBlockUp, gravityBlockUpTexture], + [background, backgroundTexture], + [textBackground, textBackgroundTexture], + [leftTextBackground, leftTextBackgroundTexture], + [rightTextBackground, rightTextBackgroundTexture], + ); + } +} + +// Refreshes gameIntervals based on current gameState and menuMode +function updateGameIntervals() { + errorPingInterval = setInterval(errorPing, 1000); // Set interval for error sound being playe + if (gameState == 1) { + // Clear any existing intervals + clearInterval(pointerChangeInterval); + clearInterval(flagDetectionInterval); + clearInterval(blockDetectionInterval); + clearInterval(gravityLoopInterval); + clearInterval(jumpLoopInterval); + + flagDetectionInterval = setInterval(flagDetection, 500); // Set interval for flag detection + blockDetectionInterval = setInterval(gravityBlockDetection, 300); // Set interval for gravity block detection + gravityLoopInterval = setInterval(gravityPull, 300); // Set interval for gravity calculation + jumpLoopInterval = setInterval(jumpPull, 100); // Set interval for jump calculation + } else if (gameState == 0) { + // Clear any existing intervals + clearInterval(pointerChangeInterval); + clearInterval(flagDetectionInterval); + clearInterval(blockDetectionInterval); + clearInterval(gravityLoopInterval); + clearInterval(jumpLoopInterval); + + pointerChangeInterval = setInterval(pointerChange, 1000); // Set interval for pointer texture swap + } else { + // Clear intervals if game is not active + clearInterval(pointerChangeInterval); + clearInterval(flagDetectionInterval); + clearInterval(blockDetectionInterval); + clearInterval(gravityLoopInterval); + clearInterval(jumpLoopInterval); + } +} diff --git a/games/img/Up_Down_Top-down.png b/games/img/Up_Down_Top-down.png new file mode 100644 index 0000000000000000000000000000000000000000..72339915a561275f7de5023812ec6c9641d21207 GIT binary patch literal 33052 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