|
1259 | 1259 | ] |
1260 | 1260 | }, |
1261 | 1261 | { |
1262 | | - "description": "The player runs to the ledge holding the coins, jumps onto it, and collects every coin it touches.", |
| 1262 | + "description": "The player drops off its starting platform onto the row of coins on the ground, then runs right and jumps onto the ledge to collect the rest.", |
1263 | 1263 | "name": "Collecting the coins by running into them", |
1264 | 1264 | "type": "gameplay", |
1265 | 1265 | "source": [ |
1266 | | - "// Coins are picked up by touching them. They sit on a ledge above the ground", |
1267 | | - "// the player starts on: it has to run there and jump onto it.", |
| 1266 | + "// Coins are picked up by touching them. They come in two rows: one on the", |
| 1267 | + "// ground below the platform the player starts on, and one on a ledge above", |
| 1268 | + "// the ground, further right.", |
1268 | 1269 | "await harness.goToScene('Game Scene');", |
1269 | 1270 | "harness.watch('Coins');", |
1270 | 1271 | "", |
|
1290 | 1291 | " 'Standing still collects no coin.'", |
1291 | 1292 | ");", |
1292 | 1293 | "", |
1293 | | - "// Run right, jumping while the coins ahead are out of reach above.", |
| 1294 | + "// The two rows are told apart by their height: the ground row is well below", |
| 1295 | + "// the player's starting platform, the ledge row only a little (the ledge is", |
| 1296 | + "// lower than the platform).", |
| 1297 | + "const groundCoinIds = harness", |
| 1298 | + " .getObjects('Coins')", |
| 1299 | + " .filter((coin) => coin.centerY > startY + 60)", |
| 1300 | + " .map((coin) => coin.id);", |
| 1301 | + "const groundRowIsCollected = () => {", |
| 1302 | + " const remaining = coinIds();", |
| 1303 | + " return groundCoinIds.every((id) => !remaining.includes(id));", |
| 1304 | + "};", |
| 1305 | + "harness.assert(", |
| 1306 | + " groundCoinIds.length > 0,", |
| 1307 | + " 'There is a row of coins on the ground below the starting platform.'", |
| 1308 | + ");", |
| 1309 | + "", |
| 1310 | + "// The player cannot reach the ground row from up there: it first drops off", |
| 1311 | + "// the right edge of the platform and runs back left over the coins.", |
| 1312 | + "harness.setKeyPressed('Right', true);", |
| 1313 | + "const fell = await harness.stepUntil(() => !isOnFloor(), { maxFrames: 120 });", |
| 1314 | + "harness.assert(fell, 'Running right takes the player off the edge of the platform.');", |
| 1315 | + "const landedBelow = await harness.stepUntil(isOnFloor, { maxFrames: 120 });", |
| 1316 | + "harness.releaseAllInputs();", |
| 1317 | + "await harness.stepFrames(5);", |
| 1318 | + "harness.assert(landedBelow, 'The player lands on the ground below.');", |
| 1319 | + "", |
| 1320 | + "// Run back left, over the row of coins on the ground. Leaving the floor", |
| 1321 | + "// would mean the player missed them and ran off the level, so stop there.", |
| 1322 | + "harness.setKeyPressed('Left', true);", |
| 1323 | + "await harness.stepUntil(() => groundRowIsCollected() || !isOnFloor(), {", |
| 1324 | + " maxFrames: 240,", |
| 1325 | + "});", |
| 1326 | + "// Brake with the opposite key: the level ends right after the last coin of", |
| 1327 | + "// the row, and just letting go would let the player slide off it.", |
| 1328 | + "harness.setKeyPressed('Left', false);", |
| 1329 | + "harness.setKeyPressed('Right', true);", |
| 1330 | + "await harness.stepFrames(10);", |
| 1331 | + "harness.releaseAllInputs();", |
| 1332 | + "", |
| 1333 | + "const playerAfterGroundRow = getPlayer();", |
| 1334 | + "console.log(", |
| 1335 | + " 'afterGroundRow playerX=' + Math.round(playerAfterGroundRow.centerX) +", |
| 1336 | + " ' playerY=' + Math.round(playerAfterGroundRow.centerY) +", |
| 1337 | + " ' coinsLeft=' + coinIds().length", |
| 1338 | + ");", |
| 1339 | + "harness.assert(", |
| 1340 | + " groundRowIsCollected(),", |
| 1341 | + " 'Running over the row of coins on the ground collects every one of them (' +", |
| 1342 | + " coinIds().filter((id) => groundCoinIds.includes(id)).length +", |
| 1343 | + " ' were left behind, the player ended at x=' +", |
| 1344 | + " Math.round(playerAfterGroundRow.centerX) + ').'", |
| 1345 | + ");", |
| 1346 | + "", |
| 1347 | + "// Run right toward the remaining coins. They sit on a ledge overhanging the", |
| 1348 | + "// end of the ground, so the player jumps while running well before reaching", |
| 1349 | + "// it, rises above it, and stops moving forward once past the start of the", |
| 1350 | + "// row of coins: it comes down onto the first coin of the row instead of", |
| 1351 | + "// flying over the whole ledge. Falling well below the ground would mean the", |
| 1352 | + "// player ran out of the level, so stop there.", |
| 1353 | + "const rowStartX = Math.min(", |
| 1354 | + " ...harness.getObjects('Coins').map((coin) => coin.centerX)", |
| 1355 | + ");", |
1294 | 1356 | "let jumpFramesLeft = 0;", |
| 1357 | + "let hasJumped = false;", |
1295 | 1358 | "harness.setKeyPressed('Right', true);", |
1296 | 1359 | "const collectedThemAll = await harness.stepUntil(", |
1297 | | - " () => coinIds().length === 0,", |
| 1360 | + " () => coinIds().length === 0 || getPlayer().centerY > startY + 300,", |
1298 | 1361 | " {", |
1299 | 1362 | " maxFrames: 400,", |
1300 | 1363 | " onFrame: () => {", |
1301 | 1364 | " const player = getPlayer();", |
1302 | 1365 | " const onFloor =", |
1303 | 1366 | " player.behaviors.PlatformerObject.state.IsOnFloor === true;", |
1304 | | - " const nextCoin = harness.getNearby('Coins', 'Player', 5000)[0];", |
1305 | 1367 | "", |
1306 | 1368 | " if (jumpFramesLeft > 0) {", |
1307 | 1369 | " jumpFramesLeft--;", |
1308 | 1370 | " if (jumpFramesLeft === 0) harness.setKeyPressed('Space', false);", |
1309 | | - " return;", |
1310 | 1371 | " }", |
1311 | | - " if (!onFloor || !nextCoin) return;", |
1312 | | - " const isAhead = nextCoin.centerX > player.centerX;", |
1313 | | - " const isAbove = player.centerY - nextCoin.centerY > 30;", |
1314 | | - " if (isAhead && isAbove) {", |
1315 | | - " harness.setKeyPressed('Space', true);", |
1316 | | - " jumpFramesLeft = 14;", |
| 1372 | + " if (onFloor) {", |
| 1373 | + " harness.setKeyPressed('Right', true);", |
| 1374 | + " if (!hasJumped && rowStartX - player.centerX <= 100) {", |
| 1375 | + " harness.setKeyPressed('Space', true);", |
| 1376 | + " jumpFramesLeft = 20;", |
| 1377 | + " hasJumped = true;", |
| 1378 | + " }", |
| 1379 | + " } else if (player.centerX >= rowStartX - 16) {", |
| 1380 | + " harness.setKeyPressed('Right', false);", |
1317 | 1381 | " }", |
1318 | 1382 | " },", |
1319 | 1383 | " }", |
1320 | 1384 | ");", |
| 1385 | + "// Brake with the opposite key here too: the ledge ends right after the last", |
| 1386 | + "// coin of its row.", |
| 1387 | + "harness.setKeyPressed('Right', false);", |
| 1388 | + "harness.setKeyPressed('Left', true);", |
| 1389 | + "await harness.stepFrames(10);", |
1321 | 1390 | "harness.releaseAllInputs();", |
1322 | 1391 | "await harness.stepFrames(5);", |
1323 | 1392 | "", |
|
1334 | 1403 | " Math.round(player.centerY) + ', it started at y=' + Math.round(startY) + ').'", |
1335 | 1404 | ");", |
1336 | 1405 | "harness.assert(", |
1337 | | - " collectedThemAll,", |
| 1406 | + " collectedThemAll && coinIds().length === 0,", |
1338 | 1407 | " 'Running and jumping to the coins collects every one of them (' +", |
1339 | 1408 | " coinIds().length + ' were left behind, the player ended at x=' +", |
1340 | 1409 | " Math.round(player.centerX) + ').'", |
1341 | | - ");" |
| 1410 | + ");", |
| 1411 | + "", |
| 1412 | + "// A picture of what the game looks like once this has happened, kept with", |
| 1413 | + "// the test results.", |
| 1414 | + "await harness.takeScreenshot('the level once every coin has been collected');" |
1342 | 1415 | ] |
1343 | 1416 | } |
1344 | 1417 | ], |
|
0 commit comments