puzzlescript
Version:
Play PuzzleScript games in your terminal!
444 lines (384 loc) • 9.72 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-env jasmine */
const colors_1 = require("../colors");
const parser_1 = __importDefault(require("./parser"));
function checkGrammar(code) {
// check that it does not throw an Error
const ast = parser_1.default.parseToAST(code);
expect(ast).toMatchSnapshot();
}
function checkParse(code) {
return parser_1.default.parse(code);
}
function checkParseRule(code, varNames) {
// 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, varNames) {
// 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_1.default.parseToAST(`title foo`);
});
it('parses when there are empty blocks', () => {
parser_1.default.parseToAST(`
title foo
===
objects
===
===
legend
===
===
rules
===
===
levels
===
`);
});
it('parses object shortcut characters', () => {
parser_1.default.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((0, colors_1.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((0, colors_1.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, late, rigid) {
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
});
});
//# sourceMappingURL=parser.spec.js.map