UNPKG

puzzlescript

Version:

Play PuzzleScript games in your terminal!

467 lines (379 loc) 9.42 kB
/* eslint-env jasmine */ import { lookupColorPalette } from '../colors' import { SimpleRuleGroup } from '../models/rule' import Parser from './parser' function checkGrammar(code: string) { // check that it does not throw an Error const ast = Parser.parseToAST(code) expect(ast).toMatchSnapshot() } function checkParse(code: string) { return Parser.parse(code) } function checkParseRule(code: string, varNames: string[]) { // Now check if the semantics parsed const legendItems = varNames.map((varName) => { return `${varName} = testObject` }) return checkParse(` title checkParseRule === OBJECTS === testObject transparent === LEGEND === ${legendItems.join('\n')} === COLLISIONLAYERS === testObject ==== RULES ==== ${code} `) } function parseRule(code: string, varNames: string[]) { // Add a header checkGrammar(` title checkGrammar === RULES === ${code} `) return checkParseRule(code, varNames) } describe('rules', () => { it('parses a simple rule', () => { parseRule('[ z ] -> [ ]', ['z']) }) it('parses a simple rule 2', () => { parseRule('[ z ] -> [ z ]', ['z']) }) it('parses a simple rule without whitespace', () => { parseRule('[z]->[z]', ['z']) }) it('parses a rule with multiple cells', () => { parseRule('[ z | x ] -> [ | ]', ['z', 'x']) }) it('parses a rule with multiple layers', () => { parseRule('[ z x ] -> [ ]', ['z', 'x']) }) it('parses a rule with ellpisis', () => { parseRule('[ z | ... | x | z ] -> [ RANDOM z | ... | x | ]', ['z', 'x']) }) it('parses a rule with a period for a variable name', () => { parseRule('[.] -> []', ['.']) }) it('parses a rule with a _ for a variable name', () => { parseRule('[z_x] -> []', ['z_x']) }) describe('Cell Modifiers', () => { it('parses a rule with directions', () => { parseRule('[ACTION z ] -> [ ]', ['z']) parseRule('[^ z ] -> [ ]', ['z']) parseRule('[v z ] -> [ ]', ['z']) // This needs to be the down arrow, not a variable parseRule('[> z ] -> [ ]', ['z']) parseRule('[< z ] -> [ ]', ['z']) parseRule('[LEFT z ] -> [ ]', ['z']) parseRule('[RIGHT z ] -> [ ]', ['z']) parseRule('[UP z ] -> [ ]', ['z']) parseRule('[DOWN z ] -> [ ]', ['z']) parseRule('[STATIONARY z ] -> [ ]', ['z']) parseRule('[MOVING z ] -> [ ]', ['z']) parseRule('[VERTICAL z ] -> [ ]', ['z']) parseRule('[HORIZONTAL z ] -> [ ]', ['z']) parseRule('[PERPENDICULAR z ] -> [ ]', ['z']) parseRule('[ORTHOGONAL z ] -> [ ]', ['z']) parseRule('[RANDOMDIR z ] -> [ ]', ['z']) }) it('parses a rule with modifiers', () => { parseRule('[ NO z ] -> [ ]', ['z']) }) it('parses a rule with a variable that begins with the name of a modifier', () => { parseRule('[ stationaryz ] -> [ stationaryz ]', ['stationaryz']) }) it('parses a rule with modifiers 2', () => { parseRule('[ STATIONARY z ] -> [ z ]', ['z']) }) }) describe('Commands', () => { it('parses a rule with a command inside the brackets (these should be moved up to the Action Commands)', () => { parseRule('[z]->[SFX0]', ['z']) parseRule('[z]->[z SFX1]', ['z']) parseRule('[z]->[z winter]', ['z', 'winter']) parseRule('[z|] -> [|z AGAIN]', ['z']) }) }) describe('General Formatting', () => { it('parses an almost-empty file', () => { Parser.parseToAST(`title foo`) }) it('parses when there are empty blocks', () => { Parser.parseToAST(` title foo === objects === === legend === === rules === === levels === `) }) it('parses object shortcut characters', () => { Parser.parseToAST(` title foo === OBJECTS === player Z transparent === RULES === [P]->[] === LEVELS === P`) }) it('Correctly parses weird variable names', () => { checkGrammar(` title foo === LEGEND === Bush? = Player . = Player ==== RULES ==== [Bush?] -> [Bush?] [.] -> [.] `) }) it('Looks up color palettes using a string or an index', () => { const { data: data1 } = checkParse(` title foo color_palette gameboycolour === OBJECTS === player yellow `) expect(data1.objects[0].getPixels(1, 1)[0][0].toHex().toLowerCase()).toBe(lookupColorPalette('gameboycolour', 'yellow').toLowerCase()) const { data: data2 } = checkParse(` title foo color_palette 2 === OBJECTS === player yellow `) expect(data2.objects[0].getPixels(1, 1)[0][0].toHex().toLowerCase()).toBe(lookupColorPalette('gameboycolour', 'yellow').toLowerCase()) }) it('Supports characters that would be invalid in one scope but are valid in another scope', () => { checkParse(` title foo === OBJECTS === hello . transparent hello2 ] transparent hello3 ♡ transparent ======= LEGEND ======= [ = hello , = hello2 д = hello3 sfx11 = hello3 (this is a valid variable name since there are only 10 SFX) ======= COLLISIONLAYERS ======= hello,hello2 hello3 ======= RULES ======= [.]->[.] [♡]->[♡] `) }) it('Supports objects named using only numbers (mirror-isles)', () => { checkParse(` title foo === OBJECTS === Table Yellow Red White (12121 21212 12121 21212 0...0) ..... .121. .212. .121. .0.0. ChairRightLoose Yellow Red `) checkParse(` title foo === OBJECTS === player yellow 00000 00000 00000 00000 00000 00 yellow 01 yellow `) }) }) it('Converts an invalid color to a Transparent one', () => { const { data } = checkParse(` title foo === OBJECTS === player someInvalidColorName === COLLISIONLAYERS === player `) expect(data.objects[0].getPixels(5, 5)[0][0].isTransparent()).toBe(true) // expect(message).toBe('Invalid color name. "someinvalidcolorname" is not a valid color. Using "transparent" instead') // expect(gameNode.__getSourceLineAndColumn()).toBeTruthy() }) it('Sets the collision layer for nested sprites', () => { const { data } = checkParse(` title foo === OBJECTS === player TRANSPARENT === LEGEND === x = player y = x === COLLISIONLAYERS === y `) // just make sure it doesn't throw an exception const player = data._getSpriteByName('player') expect(player && player.getCollisionLayer().id).toBeGreaterThan(0) }) it('Denotes if a rule is late, rigid, or again', () => { const { data } = checkParse(` title foo === OBJECTS === player someInvalidColorName === COLLISIONLAYERS === player === RULES === LATE [ Player ] -> [] RIGID [ Player ] -> [] [ Player ] -> [] AGAIN LATE RIGID [ Player ] -> [] AGAIN [ Player ] -> [] [ Player ] -> [ Player again] (from "Rose") `) function expector(rule: SimpleRuleGroup, late: boolean, rigid: boolean) { expect(rule.getChildRules()[0].isLate()).toBe(late) expect(rule.hasRigid()).toBe(rigid) } expector(data.rules[0], true, false) expector(data.rules[1], false, true) expector(data.rules[2], false, false) expector(data.rules[3], true, true) expector(data.rules[4], false, false) expector(data.rules[5], false, false) }) describe('RANDOM keyword propagation', () => { it('marks a rule as being RANDOM', () => { const { data } = parseRule('RANDOM [.] -> []', ['.']) expect(data.rules[0].isRandom).toBe(true) }) it('marks a rule Group as being RANDOM', () => { const { data } = parseRule(` RANDOM [.] -> [] + [Player] -> []`, ['.', 'Player']) expect(data.rules[0].isRandom).toBe(true) }) it('does not mark a rule Loop as being RANDOM', () => { const { data } = parseRule(` STARTLOOP RANDOM [.] -> [] + [Player] -> [] ENDLOOP `, ['.', 'Player']) expect(data.rules[0].isRandom).toBe(false) }) it('properly groups loops and groups', () => { const { data } = parseRule(` RIGHT [ ] -> [ ] STARTLOOP (1st rulegroup) RIGHT [ ] -> [ ] + RIGHT [ ] -> [ ] + RIGHT [ ] -> [ ] + RIGHT [ ] -> [ ] ( 2nd rulegroup) RIGHT [ ] -> [ ] + RIGHT [ ] -> [ ] RIGHT [ ] -> [ ] ENDLOOP RIGHT [ ] -> [ ] `, ['.', 'Player']) expect(data.rules.length).toBe(3) // startloop expect(data.rules[1].getChildRules().length).toBe(3) // 1st rulegroup expect(data.rules[1].getChildRules()[0].getChildRules().length).toBe(4) // 2nd rulegroup expect(data.rules[1].getChildRules()[1].getChildRules().length).toBe(2) }) }) describe('Expected Failures', () => { // Something using collisionLayers. Like A and B are in the same layer and we try to run either: `[ A B ] -> []` or `[A] -> [A B]` // Check that the magic objects `Background` and `Player` are set to something }) })