bemuse/src/game/state/player-state.spec.js
import assert from 'assert'
import GameInput from '../input'
import Player from '../player'
import PlayerState from './player-state'
import { notechart } from '../test-helpers'
describe('PlayerState', function () {
it('updates the input', function () {
const state = new PlayerState({
number: 1,
columns: ['wow'],
notechart: notechart(''),
options: { speed: 1 },
})
const input = {
get: (name) => ({ name }),
}
state.update(0, input)
assert(state.input.get('wow').name === 'p1_wow')
})
describe('with player and chart', function () {
let chart
let player
let state
let input
let buttons
function setup(bms, options = { speed: 1 }) {
chart = notechart(bms)
player = new Player(chart, 1, options)
state = new PlayerState(player)
input = new GameInput()
buttons = {}
input.use({ get: () => buttons })
}
function advance(time, b) {
buttons = b
input.update()
state.update(time, input)
}
describe('node judging', function () {
it('judges notes', function () {
setup(`
#BPM 120
#00111:0101
`)
const column = chart.notes[0].column
assert.equal(state.getNoteStatus(chart.notes[0]), 'unjudged')
assert.equal(state.stats.combo, 0)
assert.equal(state.stats.poor, false)
assert.equal(state.stats.totalCombo, 2)
assert.equal(state.notifications.judgments.length, 0)
advance(1.999, {})
assert.equal(state.getNoteStatus(chart.notes[0]), 'unjudged')
advance(2, { p1_1: 1 })
assert.equal(state.getNoteStatus(chart.notes[0]), 'judged')
assert.equal(state.getNoteJudgment(chart.notes[0]), 1)
assert.equal(state.getNoteStatus(chart.notes[1]), 'unjudged')
assert.deepEqual(state.notifications.judgments[0], {
judgment: 1,
combo: 1,
delta: 0,
column,
})
assert(!state.stats.poor)
advance(2.1, { p1_1: 0 })
advance(5, { p1_1: 0 })
assert.equal(state.getNoteStatus(chart.notes[1]), 'judged')
assert.equal(state.getNoteJudgment(chart.notes[1]), -1)
assert.deepEqual(state.notifications.judgments[0], {
judgment: -1,
combo: 0,
delta: 2,
column,
})
assert.equal(state.stats.poor, true)
})
it('judges multiple notes in different column', function () {
setup(`
#BPM 120
#00111:01
#00112:01
`)
advance(2, { p1_1: 1, p1_2: 1 })
assert(state.getNoteStatus(chart.notes[0]) === 'judged')
assert(state.getNoteStatus(chart.notes[1]) === 'judged')
})
it('judges single note from one column at a time', function () {
setup(`
#BPM 480
#00111:01010100000000000000000000000000
`)
advance(0.531, { p1_1: 1 })
assert(state.getNoteStatus(chart.notes[0]) === 'judged')
assert(state.getNoteStatus(chart.notes[1]) === 'unjudged')
assert(state.getNoteStatus(chart.notes[2]) === 'unjudged')
advance(0.531, { p1_1: 0 })
advance(0.531, { p1_1: 1 })
assert(state.getNoteStatus(chart.notes[0]) === 'judged')
assert(state.getNoteStatus(chart.notes[1]) === 'judged')
assert(state.getNoteStatus(chart.notes[2]) === 'unjudged')
advance(0.531, { p1_1: 0 })
advance(0.531, { p1_1: 1 })
assert(state.getNoteStatus(chart.notes[0]) === 'judged')
assert(state.getNoteStatus(chart.notes[1]) === 'judged')
assert(state.getNoteStatus(chart.notes[2]) === 'judged')
assert(state.getNoteJudgment(chart.notes[0]) > 1)
assert(state.getNoteJudgment(chart.notes[1]) === 1)
assert(state.getNoteJudgment(chart.notes[2]) > 1)
})
it('leaves note unjudged when bad and there are closer note', function () {
setup(`
#BPM 120
#00111:01010100000000000000000000000000
`)
advance(2.125, { p1_1: 1 })
assert(state.getNoteStatus(chart.notes[0]) === 'unjudged')
assert(state.getNoteStatus(chart.notes[1]) === 'judged')
assert(state.getNoteStatus(chart.notes[2]) === 'unjudged')
})
it('records delta when pressed', function () {
setup(`
#BPM 120
#00111:01
`)
sinon.spy(state.stats, 'handleDelta')
advance(2.01, { p1_1: 1 })
assert(state.stats.handleDelta.calledWith(2.01 - 2))
})
it('does not record delta when missed', function () {
setup(`
#BPM 120
#00111:01
`)
sinon.spy(state.stats, 'handleDelta')
advance(9, { p1_1: 1 })
assert(state.stats.handleDelta.callCount === 0)
})
describe('with long note', function () {
let note
beforeEach(function () {
setup(`
#BPM 120
#00151:0101
`)
note = chart.notes[0]
})
it('judges long note', function () {
advance(2, { p1_1: 1 })
assert(state.getNoteStatus(note) === 'active')
assert(state.getNoteJudgment(note) === 1)
assert(state.notifications.judgments[0].judgment === 1)
advance(3, { p1_1: 0 })
assert(state.getNoteStatus(note) === 'judged')
assert(state.getNoteJudgment(note) === 1)
assert(state.notifications.judgments[0].judgment === 1)
})
it('gives 2 discrete judgments, one for down and one for up', function () {
advance(2, { p1_1: 1 })
assert(state.stats.numJudgments === 1)
advance(3, { p1_1: 0 })
assert(state.stats.numJudgments === 2)
})
it('records delta once', function () {
sinon.spy(state.stats, 'handleDelta')
advance(2, { p1_1: 1 })
advance(3, { p1_1: 0 })
assert(state.stats.handleDelta.callCount === 1)
})
it('judges missed long note', function () {
advance(2.3, { p1_1: 1 })
assert(state.getNoteStatus(note) === 'judged')
assert(state.getNoteJudgment(note) === -1)
})
it('gives 2 missed judgment for missed longnote', function () {
advance(2.3, { p1_1: 1 })
assert(state.stats.numJudgments === 2)
})
it('judges long note lifted too fast as missed', function () {
advance(2, { p1_1: 1 })
advance(2.01, { p1_1: 0 })
assert(state.getNoteStatus(note) === 'judged')
assert(state.getNoteJudgment(note) === -1)
assert(state.stats.numJudgments === 2)
})
it('does not end automatically', function () {
advance(2, { p1_1: 1 })
advance(3.1, { p1_1: 1 })
assert(state.getNoteStatus(note) === 'active')
assert(state.stats.numJudgments === 1)
})
it('judges long note lifted too slow as missed', function () {
advance(2, { p1_1: 1 })
advance(4, { p1_1: 1 })
assert(state.getNoteStatus(note) === 'judged')
assert(state.getNoteJudgment(note) === -1)
assert.deepEqual(state.notifications.judgments[0], {
judgment: -1,
combo: 0,
delta: 1,
column: note.column,
})
})
})
describe('with long scratch note', function () {
let note
beforeEach(function () {
setup(`
#BPM 120
#00156:0101
`)
note = chart.notes[0]
})
it('ends automatically', function () {
advance(2, { p1_SC: 1 })
assert(state.getNoteStatus(note) === 'active')
assert(state.getNoteJudgment(note) === 1)
assert(state.notifications.judgments[0].judgment === 1)
advance(3.1, { p1_SC: 1 })
assert(state.getNoteStatus(note) === 'judged')
assert(state.getNoteJudgment(note) === 1)
assert(state.notifications.judgments[0].judgment === 1)
})
})
describe('with long scratch note next to each other', function () {
beforeEach(function () {
setup(`
#BPM 120
#00156:0100000000000000000000000000000000000000000000000000000000000001
#00256:0100000000000000000000000000000000000000000000000000000000000001
`)
})
it('should switch to next one on change', function () {
advance(2, { p1_SC: 1 })
advance(4, { p1_SC: -1 })
assert(state.getNoteStatus(chart.notes[0]) === 'judged')
assert(state.getNoteStatus(chart.notes[1]) === 'active')
})
})
describe('sound notifications', function () {
it('notifies of note hit', function () {
setup(`
#BPM 120
#00111:0102
`)
advance(2, { p1_1: 1 })
assert(state.notifications.sounds[0].note === chart.notes[0])
assert(state.notifications.sounds[0].type === 'hit')
})
it('should notify missed notes as break', function () {
setup(`
#BPM 120
#00111:01
`)
advance(5, { p1_1: 0 })
assert(state.notifications.sounds[0].note === chart.notes[0])
assert(state.notifications.sounds[0].type === 'break')
})
it('notifies of free keysound hit', function () {
setup(`
#BPM 60
#00111:01
#00211:02
`)
// hit the first note
advance(4, { p1_1: 1 })
advance(4, { p1_1: 0 })
// hit the blank area
advance(4, { p1_1: 1 })
assert(state.notifications.sounds[0].note === chart.notes[0])
assert(state.notifications.sounds[0].type === 'free')
// release the button
advance(4, { p1_1: 0 })
assert(!state.notifications.sounds.length)
// try again
advance(5, { p1_1: 1 })
assert(state.notifications.sounds[0].note === chart.notes[0])
// release the button
advance(5, { p1_1: 0 })
// wait and try again.
advance(6.5, { p1_1: 1 })
assert(state.notifications.sounds[0].note === chart.notes[0])
advance(6.5, { p1_1: 0 })
// wait and try again. this time keysound should change
advance(7.5, { p1_1: 1 })
assert(state.notifications.sounds[0].note === chart.notes[1])
})
it('suppresses freestyle keysound when column is sandwiched between 2 adjacent notes in 3d mode', function () {
setup(
`
#BPM 60
#00111:01
#00212:02
#00113:02
`,
{ speed: 1, placement: '3d' }
)
advance(1, { p1_2: 1 })
assert.equal(state.notifications.sounds.length, 1)
advance(1, { p1_2: 0 })
advance(4, { p1_2: 1 })
assert.equal(state.notifications.sounds.length, 0)
advance(4, { p1_2: 0 })
})
it('does not suppress freestyle keysound outside 3d mode', function () {
setup(
`
#BPM 60
#00111:01
#00212:02
#00113:02
`,
{ speed: 1, placement: 'left' }
)
advance(1, { p1_2: 1 })
assert.equal(state.notifications.sounds.length, 1)
advance(1, { p1_2: 0 })
advance(4, { p1_2: 1 })
assert.equal(state.notifications.sounds.length, 1)
advance(4, { p1_2: 0 })
})
})
})
describe('speed', function () {
it('infers speed from player', function () {
setup('', { speed: 2 })
assert(state.speed === 2)
})
it('updates speed on dedicated buttons', function () {
setup('', { speed: 2 })
advance(1.0, { p1_speedup: 1 })
assert(state.speed === 2.5)
advance(1.2, { p1_speedup: 0, p1_speeddown: 1 })
assert(state.speed === 2)
})
it('supports fine-grained speed modifications', function () {
setup('', { speed: 2 })
advance(1.0, { p1_speedup: 1, select: 1 })
assert(state.speed === 2.1)
})
it('supports pinching to zoom', function () {
setup('', { speed: 2 })
advance(1.0, { p1_pinch: 300 })
advance(1.2, { p1_pinch: 450 })
assert(state.speed === 3)
})
})
describe('finish', function () {
it('should become true when song is finished', function () {
setup('#00111:0101')
assert(state.finished === false)
advance(4.0, {})
assert(state.finished === false)
advance(16.0, {})
assert(state.finished === true)
})
})
})
})