punter.js 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nvmrc +1 -0
- package/LICENSE.MD +21 -0
- package/README.MD +731 -0
- package/games/pong.html +260 -0
- package/images/pong/sprites/ball.png +0 -0
- package/images/pong/sprites/paddle.png +0 -0
- package/images/punterjs.png +0 -0
- package/package.json +33 -0
- package/punter.js +1514 -0
- package/tests/fixtures/index.html +30 -0
- package/tests/fixtures/silence.wav +0 -0
- package/tests/input-spec.js +104 -0
- package/tests/interface-spec.js +200 -0
- package/tests/jasmine/config.json +20 -0
- package/tests/jasmine/reporter.js +25 -0
- package/tests/jasmine/teardown.js +7 -0
- package/tests/jasmine/timeout.js +1 -0
- package/tests/scenes-spec.js +87 -0
- package/tests/setup.js +49 -0
- package/tests/sounds-spec.js +83 -0
- package/tests/sprites-spec.js +259 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var setup = require('./setup');
|
|
4
|
+
|
|
5
|
+
describe('Sprites', function () {
|
|
6
|
+
|
|
7
|
+
var page;
|
|
8
|
+
|
|
9
|
+
beforeAll(async function () {
|
|
10
|
+
page = await setup.newPage();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterAll(async function () {
|
|
14
|
+
await page.close();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(async function () {
|
|
18
|
+
await page.evaluate(function () {
|
|
19
|
+
punter.sprites.forEach(function (s) { s.destroy(); });
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// --- creation ---
|
|
24
|
+
|
|
25
|
+
it('creates a sprite with the given id, x and y', async function () {
|
|
26
|
+
var result = await page.evaluate(function () {
|
|
27
|
+
var s = punter.createSprite({ id: 's1', key: 'hero', x: 10, y: 20 });
|
|
28
|
+
return { id: s.id, x: s.x, y: s.y };
|
|
29
|
+
});
|
|
30
|
+
expect(result.id).toBe('s1');
|
|
31
|
+
expect(result.x).toBe(10);
|
|
32
|
+
expect(result.y).toBe(20);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('defaults width and height from the image dimensions', async function () {
|
|
36
|
+
var result = await page.evaluate(function () {
|
|
37
|
+
var s = punter.createSprite({ id: 's1', key: 'hero', x: 0, y: 0 });
|
|
38
|
+
return { w: s.w, h: s.h };
|
|
39
|
+
});
|
|
40
|
+
// 1x1 pixel image → both default to 1
|
|
41
|
+
expect(result.w).toBe(1);
|
|
42
|
+
expect(result.h).toBe(1);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('throws when creating a sprite with a duplicate id', async function () {
|
|
46
|
+
var threw = await page.evaluate(function () {
|
|
47
|
+
try {
|
|
48
|
+
punter.createSprite({ id: 's1', key: 'hero', x: 0, y: 0 });
|
|
49
|
+
punter.createSprite({ id: 's1', key: 'hero', x: 0, y: 0 });
|
|
50
|
+
return false;
|
|
51
|
+
} catch (e) { return true; }
|
|
52
|
+
});
|
|
53
|
+
expect(threw).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('throws when the image key has not been loaded', async function () {
|
|
57
|
+
var threw = await page.evaluate(function () {
|
|
58
|
+
try {
|
|
59
|
+
punter.createSprite({ id: 's1', key: 'missing', x: 0, y: 0 });
|
|
60
|
+
return false;
|
|
61
|
+
} catch (e) { return true; }
|
|
62
|
+
});
|
|
63
|
+
expect(threw).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('throws when required opts are missing', async function () {
|
|
67
|
+
var result = await page.evaluate(function () {
|
|
68
|
+
var errors = [];
|
|
69
|
+
try { punter.createSprite(); } catch (e) { errors.push('no-opts'); }
|
|
70
|
+
try { punter.createSprite({ id: 's1', key: 'hero' }); } catch (e) { errors.push('no-x'); }
|
|
71
|
+
return errors;
|
|
72
|
+
});
|
|
73
|
+
expect(result).toContain('no-opts');
|
|
74
|
+
expect(result).toContain('no-x');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// --- retrieval ---
|
|
78
|
+
|
|
79
|
+
it('getSprite returns the sprite by id', async function () {
|
|
80
|
+
var result = await page.evaluate(function () {
|
|
81
|
+
punter.createSprite({ id: 's1', key: 'hero', x: 0, y: 0 });
|
|
82
|
+
var found = punter.getSprite('s1');
|
|
83
|
+
return found ? found.id : null;
|
|
84
|
+
});
|
|
85
|
+
expect(result).toBe('s1');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('getSprite returns null for an unknown id', async function () {
|
|
89
|
+
var result = await page.evaluate(function () {
|
|
90
|
+
return punter.getSprite('nope');
|
|
91
|
+
});
|
|
92
|
+
expect(result).toBeNull();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// --- movement ---
|
|
96
|
+
|
|
97
|
+
it('moveX changes x position', async function () {
|
|
98
|
+
var result = await page.evaluate(function () {
|
|
99
|
+
var s = punter.createSprite({ id: 's1', key: 'hero', x: 50, y: 0 });
|
|
100
|
+
s.moveX(10);
|
|
101
|
+
var after1 = s.x;
|
|
102
|
+
s.moveX(-25);
|
|
103
|
+
return { after1: after1, after2: s.x };
|
|
104
|
+
});
|
|
105
|
+
expect(result.after1).toBe(60);
|
|
106
|
+
expect(result.after2).toBe(35);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('moveY changes y position', async function () {
|
|
110
|
+
var result = await page.evaluate(function () {
|
|
111
|
+
var s = punter.createSprite({ id: 's1', key: 'hero', x: 0, y: 50 });
|
|
112
|
+
s.moveY(15);
|
|
113
|
+
return s.y;
|
|
114
|
+
});
|
|
115
|
+
expect(result).toBe(65);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('centerX positions the sprite horizontally in the middle', async function () {
|
|
119
|
+
var result = await page.evaluate(function () {
|
|
120
|
+
var s = punter.createSprite({ id: 's1', key: 'hero', x: 0, y: 0 });
|
|
121
|
+
s.centerX();
|
|
122
|
+
return { x: s.x, expected: Math.floor((punter.width - s.w) / 2) };
|
|
123
|
+
});
|
|
124
|
+
expect(result.x).toBe(result.expected);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('centerY positions the sprite vertically in the middle', async function () {
|
|
128
|
+
var result = await page.evaluate(function () {
|
|
129
|
+
var s = punter.createSprite({ id: 's1', key: 'hero', x: 0, y: 0 });
|
|
130
|
+
s.centerY();
|
|
131
|
+
return { y: s.y, expected: Math.floor((punter.height - s.h) / 2) };
|
|
132
|
+
});
|
|
133
|
+
expect(result.y).toBe(result.expected);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// --- bounce ---
|
|
137
|
+
|
|
138
|
+
it('bounce oscillates the sprite y around its initial position', async function () {
|
|
139
|
+
var result = await page.evaluate(function () {
|
|
140
|
+
var s = punter.createSprite({ id: 's1', key: 'hero', x: 0, y: 100 });
|
|
141
|
+
var initial = s.initialY;
|
|
142
|
+
s.bounce(8, 10);
|
|
143
|
+
var firstY = s.y;
|
|
144
|
+
for (var i = 0; i < 15; i++) s.bounce(8, 10);
|
|
145
|
+
return { initial: initial, firstY: firstY, laterY: s.y };
|
|
146
|
+
});
|
|
147
|
+
expect(result.firstY).toBe(result.initial);
|
|
148
|
+
expect(result.laterY).not.toBe(result.initial);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// --- visibility ---
|
|
152
|
+
|
|
153
|
+
it('visible is true when the sprite is on screen', async function () {
|
|
154
|
+
var result = await page.evaluate(function () {
|
|
155
|
+
var s = punter.createSprite({ id: 's1', key: 'hero', x: 0, y: 0 });
|
|
156
|
+
return s.visible;
|
|
157
|
+
});
|
|
158
|
+
expect(result).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('visible is false when the sprite is fully off the left edge', async function () {
|
|
162
|
+
var result = await page.evaluate(function () {
|
|
163
|
+
var s = punter.createSprite({ id: 's1', key: 'hero', x: 0, y: 0 });
|
|
164
|
+
s.moveX(-(s.w + 1));
|
|
165
|
+
return s.visible;
|
|
166
|
+
});
|
|
167
|
+
expect(result).toBe(false);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// --- destroy ---
|
|
171
|
+
|
|
172
|
+
it('destroy marks the sprite as destroyed and removes it from the registry', async function () {
|
|
173
|
+
var result = await page.evaluate(function () {
|
|
174
|
+
var s = punter.createSprite({ id: 's1', key: 'hero', x: 0, y: 0 });
|
|
175
|
+
s.destroy();
|
|
176
|
+
return { destroyed: s.destroyed, found: punter.getSprite('s1') };
|
|
177
|
+
});
|
|
178
|
+
expect(result.destroyed).toBe(true);
|
|
179
|
+
expect(result.found).toBeNull();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// --- animation ---
|
|
183
|
+
|
|
184
|
+
it('getFrameKey returns the key string for a non-animated sprite', async function () {
|
|
185
|
+
var result = await page.evaluate(function () {
|
|
186
|
+
var s = punter.createSprite({ id: 's1', key: 'hero', x: 0, y: 0 });
|
|
187
|
+
return s.getFrameKey();
|
|
188
|
+
});
|
|
189
|
+
expect(result).toBe('hero');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('getFrameKey cycles through keys for an animated sprite', async function () {
|
|
193
|
+
var result = await page.evaluate(function () {
|
|
194
|
+
var s = punter.createSprite({ id: 's1', key: ['hero', 'hero'], x: 0, y: 0 });
|
|
195
|
+
return s.getFrameKey();
|
|
196
|
+
});
|
|
197
|
+
expect(result).toBe('hero');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('animate advances the frame index after the delay elapses', async function () {
|
|
201
|
+
var result = await page.evaluate(function () {
|
|
202
|
+
var s = punter.createSprite({ id: 's1', key: ['hero', 'hero'], x: 0, y: 0 });
|
|
203
|
+
var initial = s._frameIndex;
|
|
204
|
+
s.animate(0);
|
|
205
|
+
var afterFirst = s._frameIndex;
|
|
206
|
+
s.animate(0);
|
|
207
|
+
return { initial: initial, afterFirst: afterFirst, afterSecond: s._frameIndex };
|
|
208
|
+
});
|
|
209
|
+
expect(result.initial).toBe(0);
|
|
210
|
+
expect(result.afterFirst).toBe(1);
|
|
211
|
+
expect(result.afterSecond).toBe(0);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// --- collision ---
|
|
215
|
+
|
|
216
|
+
it('isCollidingWith returns true when bounding boxes overlap', async function () {
|
|
217
|
+
var result = await page.evaluate(function () {
|
|
218
|
+
var s1 = punter.createSprite({ id: 's1', key: 'hero', x: 0, y: 0 });
|
|
219
|
+
var s2 = punter.createSprite({ id: 's2', key: 'hero', x: 0, y: 0 });
|
|
220
|
+
s1.bounds = { x: 0, y: 0, w: 100, h: 100 };
|
|
221
|
+
s2.bounds = { x: 50, y: 50, w: 100, h: 100 };
|
|
222
|
+
return s1.isCollidingWith(s2);
|
|
223
|
+
});
|
|
224
|
+
expect(result).toBe(true);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('isCollidingWith returns false when bounding boxes do not overlap', async function () {
|
|
228
|
+
var result = await page.evaluate(function () {
|
|
229
|
+
var s1 = punter.createSprite({ id: 's1', key: 'hero', x: 0, y: 0 });
|
|
230
|
+
var s2 = punter.createSprite({ id: 's2', key: 'hero', x: 0, y: 0 });
|
|
231
|
+
s1.bounds = { x: 0, y: 0, w: 100, h: 100 };
|
|
232
|
+
s2.bounds = { x: 200, y: 200, w: 100, h: 100 };
|
|
233
|
+
return s1.isCollidingWith(s2);
|
|
234
|
+
});
|
|
235
|
+
expect(result).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('isCollidingWith returns false when either sprite has no bounds', async function () {
|
|
239
|
+
var result = await page.evaluate(function () {
|
|
240
|
+
var s1 = punter.createSprite({ id: 's1', key: 'hero', x: 0, y: 0 });
|
|
241
|
+
var s2 = punter.createSprite({ id: 's2', key: 'hero', x: 0, y: 0 });
|
|
242
|
+
return s1.isCollidingWith(s2);
|
|
243
|
+
});
|
|
244
|
+
expect(result).toBe(false);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// --- seen flag ---
|
|
248
|
+
|
|
249
|
+
it('seen flag starts as false and can be set', async function () {
|
|
250
|
+
var result = await page.evaluate(function () {
|
|
251
|
+
var s = punter.createSprite({ id: 's1', key: 'hero', x: 0, y: 0 });
|
|
252
|
+
var initial = s.seen;
|
|
253
|
+
s.seen = true;
|
|
254
|
+
return { initial: initial, afterSet: s.seen };
|
|
255
|
+
});
|
|
256
|
+
expect(result.initial).toBe(false);
|
|
257
|
+
expect(result.afterSet).toBe(true);
|
|
258
|
+
});
|
|
259
|
+
});
|