Skip to content

Commit

Permalink
Home Menu, Levels List, and High Scores (#4)
Browse files Browse the repository at this point in the history
* Set up home menu

* Set up CNY2022 home menu

* After victory message, open the home menu

* Add high score system

* Fix comments

* Add new levels
  • Loading branch information
shaunanoordin authored Jan 29, 2022
1 parent 4c98794 commit bd5b5eb
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 13 deletions.
44 changes: 44 additions & 0 deletions app/main.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/main.css.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions app/main.js

Large diffs are not rendered by default.

Binary file added assets/cny2022.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 45 additions & 4 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,51 @@ <h1>Chinese New Year 2022 - Year of the Tiger</h1>
<div id="interaction-menu"></div>
<div id="home-menu">
<div>
<h2>Adventure Menu</h2>
<p>
Created by <a href="https://shaunanoordin.com" target="_blank">Shaun A. Noordin</a>
</p>
<div id="banner">
<img id="card" src="assets/cny2022.png">
</div>
<ul id="levels-list">
<li>
<button>Level 1</button>
<span>Score: 0</span>
</li>
<li>
<button>Level 2</button>
<span>New level</span>
</li>
<li>
<button>Level 3</button>
<span>New level</span>
</li>
<li>
<button>Level 4</button>
<span>New level</span>
</li>
<li>
<button>Level 5</button>
<span>New level</span>
</li>
<li>
<button>Level 6</button>
<span>New level</span>
</li>
<li>
<button>Level 7</button>
<span>New level</span>
</li>
<li>
<button>Level 8</button>
<span>New level</span>
</li>
<li>
<button>Level 9</button>
<span>New level</span>
</li>
<li>
<button>Level 10</button>
<span>New level</span>
</li>
</ul>
</div>
</div>
<button id="button-home" class="ui-button"><img src="./assets/icon-menu-white.svg" alt="Home Menu"></button>
Expand Down
36 changes: 35 additions & 1 deletion src/avo/avo.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ export default class AvO {
// Let's go!
this.initialised = true
this.showUI()
this.levels.load(STARTING_LEVEL)
this.setHomeMenu(true)
// this.levels.load(STARTING_LEVEL)
}
}

Expand Down Expand Up @@ -440,13 +441,44 @@ export default class AvO {
if (homeMenu) {
this.html.homeMenu.style.visibility = 'visible'
this.html.buttonReload.style.visibility = 'hidden'
this.updateCNY2022HomeMenu()
} else {
this.html.homeMenu.style.visibility = 'hidden'
this.html.buttonReload.style.visibility = 'visible'
this.html.main.focus()
}
}

/*
Set up the level list on the home menu.
*/
updateCNY2022HomeMenu () {
const levelsList = document.getElementById('levels-list')
while (levelsList.firstChild) { levelsList.removeChild(levelsList.firstChild) } // Clear

for (let levelIndex = 0 ; levelIndex < this.levels.cny2022LevelGenerators.length; levelIndex ++) {
const levelNumber = levelIndex + 1
const li = document.createElement('li')
const button = document.createElement('button')
const span = document.createElement('span')

button.innerText = `Level ${levelNumber}`
button.addEventListener('click', () => {
this.levels.load(levelIndex)
this.setHomeMenu(false)
})

const score = this.levels.cny2022HighScores[levelIndex]
span.innerText = (Number.isInteger(score))
? `Score: ${score}`
: 'New!'

li.appendChild(button)
li.appendChild(span)
levelsList.appendChild(li)
}
}

setInteractionMenu (interactionMenu) {
const div = this.html.interactionMenu

Expand Down Expand Up @@ -503,11 +535,13 @@ export default class AvO {
break

// DEBUG
/*
case 'z':
if (!this.interactionMenu) {
this.setInteractionMenu(new Interaction(this))
}
break
*/
}

// General input
Expand Down
72 changes: 70 additions & 2 deletions src/avo/levels.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,20 @@ import ZeldaControls from '@avo/rule/types/zelda-controls'
import CNY2022Controls from '@avo/rule/types/cny2022-controls'
import CNY2022Victory from '@avo/rule/types/cny2022-victory'

const CNY2022_HIGHSCORE_STORAGE_KEY = 'cny2022.highscores'

export default class Levels {
constructor (app) {
this._app = app
this.current = 0

this.cny2022LevelGenerators = [
this.generate_cny2022_level_1.bind(this),
this.generate_cny2022_level_2.bind(this),
]
this.cny2022HighScores = this.cny2022LevelGenerators.map(() => undefined)

this.loadCNY2022HighScores()
}

reset () {
Expand All @@ -42,14 +52,44 @@ export default class Levels {
this.current = level

this.reset()
// this.generate_cny2022_default()
this.generate_cny2022_level_1()

if (this.cny2022LevelGenerators[level]) {
this.cny2022LevelGenerators[level]()
}
}

reload () {
this.load(this.current)
}

registerCNY2022Score (score) {
const highscore = this.cny2022HighScores[this.current]

if (highscore === undefined || highscore < score) {
this.cny2022HighScores[this.current] = score
}

this.saveCNY2022HighScores()
}

saveCNY2022HighScores () {
const storage = window?.localStorage
if (!storage) return
storage.setItem(CNY2022_HIGHSCORE_STORAGE_KEY, JSON.stringify(this.cny2022HighScores))
}

loadCNY2022HighScores () {
const storage = window?.localStorage
if (!storage) return
try {
const str = storage.getItem(CNY2022_HIGHSCORE_STORAGE_KEY)
this.cny2022HighScores = (str) ? JSON.parse(str) : []
} catch (err) {
this.cny2022HighScores = []
console.error(err)
}
}

/*
Default top-down adventure level.
*/
Expand Down Expand Up @@ -92,6 +132,34 @@ export default class Levels {
generate_cny2022_level_1 () {
const app = this._app

const cat = new Cat(app, 3, (CNY2022_ROWS - 1) / 2)
const laserPointer = new LaserPointer(app, (CNY2022_COLS - 1) / 2, 3)
app.atoms.push(cat)
app.atoms.push(laserPointer)
app.addRule(new CNY2022Controls(app, cat, laserPointer))
app.addRule(new CNY2022Victory(app))

// Layout
app.atoms.push(new GlassWall(app, 11, 6, 18, 1))
app.atoms.push(new GlassWall(app, 11, 1, 1, 5))
app.atoms.push(new GlassWall(app, 28, 1, 1, 5))
app.atoms.push(new Wall(app, 11, 13, 18, 1))
app.atoms.push(new Goal(app, CNY2022_COLS - 3, (CNY2022_ROWS - 1) / 2))

// Coins
app.atoms.push(new Coin(app, 18, 11))
app.atoms.push(new Coin(app, 21, 11))
for (let i = 0 ; i < 4 ; i++) {
app.atoms.push(new Coin(app, 15 + i * 3, 15))
app.atoms.push(new Coin(app, 15 + i * 3, 17))
}

this.createOuterWalls()
}

generate_cny2022_level_2 () {
const app = this._app

const cat = new Cat(app, 3, (CNY2022_ROWS - 1) / 2)
const laserPointer = new LaserPointer(app, (CNY2022_COLS - 1) / 2, CNY2022_ROWS - 3)
app.atoms.push(cat)
Expand Down
28 changes: 26 additions & 2 deletions src/avo/rule/types/cny2022-victory.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { TILE_SIZE } from '@avo/constants'
import { easeOut } from '@avo/misc'

const VICTORY_COUNTER_MAX = 500
const RETURN_TO_HOME_MENU_COUNTER_MAX = 1500

/*
This Rule keeps track of scores and the victory condition.
Expand All @@ -17,20 +18,43 @@ export default class CNY2022Victory extends Rule {
super(app)
this._type = 'cny2022-victory'

// Once the player has finished the level, show the victory message over a
// period of time, and then pause the gameplay.
this.victory = false // bool: has the player finished the level?
this.victoryCounter = 0

// Once the player has seen the victory message, wait a moment before
// opening the home menu. (The home menu should only be opened automatically
// ONCE.)
this.returnToHomeMenuCounter = 0
this.returnedToHomeMenu = false // bool: has the home menu been opened?

this.score = 0
}

play (timeStep) {
const app = this._app
super.play(timeStep)

// If the cat has reached the exit, run the victory phase.
if (this.victory) {
this.victoryCounter = Math.min(this.victoryCounter + timeStep, VICTORY_COUNTER_MAX)

if (this.victoryCounter >= VICTORY_COUNTER_MAX) app.paused = true
// Victory phase part 1: show the victory message over a period of time,
// and after that pause the game.
if (this.victoryCounter >= VICTORY_COUNTER_MAX) {
app.paused = true

this.returnToHomeMenuCounter = Math.min(this.returnToHomeMenuCounter + timeStep, RETURN_TO_HOME_MENU_COUNTER_MAX)

// Victory phase part 2: once the victory message has finished playing,
// wait a moment and then open the home menu again. Note that the home
// menu should only be opened automatically ONCE.
if (this.returnToHomeMenuCounter >= RETURN_TO_HOME_MENU_COUNTER_MAX && !this.returnedToHomeMenu) {
this.returnedToHomeMenu = true
app.setHomeMenu(true)
}
}
}
}

Expand Down Expand Up @@ -93,7 +117,7 @@ export default class CNY2022Victory extends Rule {
triggerVictory () {
if (this.victory) return // Don't trigger more than once

const app = this._app
this._app.levels.registerCNY2022Score(this.score)
this.victory = true
}
}
Loading

0 comments on commit bd5b5eb

Please sign in to comment.