Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve space invaders #7377

Merged
merged 3 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import React from 'react'
import { Exercise } from '../Exercise'
import { ExecutionContext } from '@/interpreter/executor'
import { random } from 'lodash'
import { d } from '@codemirror/legacy-modes/mode/d'

type GameStatus = 'running' | 'won' | 'lost'
type AlienStatus = 'alive' | 'dead'
class Alien {
public status: AlienStatus
public lastKilledAt?: number

public constructor(
public elem: HTMLElement,
Expand All @@ -16,6 +19,7 @@ class Alien {
this.status = 'alive'
}
}

export default class SpaceInvadersExercise extends Exercise {
private gameStatus: GameStatus = 'running'
private moveDuration = 200
Expand All @@ -30,6 +34,7 @@ export default class SpaceInvadersExercise extends Exercise {
(_, idx) => this.laserStart + idx * this.laserStep
)
private laserPosition = 0
private features = { reanimation: false }

public constructor() {
super('space-invaders')
Expand All @@ -50,6 +55,10 @@ export default class SpaceInvadersExercise extends Exercise {
return { gameStatus: this.gameStatus }
}

public enableReanimation() {
this.features.reanimation = true
}

private addAliens(rows) {
this.aliens = rows.map((row, rowIdx) => {
return row.map((type, colIdx) => {
Expand Down Expand Up @@ -77,6 +86,86 @@ export default class SpaceInvadersExercise extends Exercise {
return new Alien(alien, row, col, type)
}

private killAlien(
executionCtx: ExecutionContext,
alien: Alien,
shot: HTMLElement
) {
const deathTime = executionCtx.getCurrentTime() + this.shotDuration

alien.status = 'dead'
alien.lastKilledAt = deathTime
;[
['tl', -10, -10, -180],
['tr', 10, -10, 180],
['bl', -10, 10, -180],
['br', 10, 10, 180],
].forEach(([pos, x, y, rotate]) => {
this.addAnimation({
targets: `#${this.view.id} #${alien.elem.id} .${pos}`,
duration: 300,
transformations: {
translateX: x,
translateY: y,
rotate: rotate,
opacity: 0,
},
offset: deathTime,
})
})
this.addAnimation({
targets: `#${this.view.id} #${shot.id}`,
duration: 1,
transformations: { opacity: 0 },
offset: deathTime,
})
}

private reanimateRandomAlien(executionCtx: ExecutionContext) {
if (!this.features.reanimation) {
return
}

const deadAliens = this.aliens
.flat()
.filter(
(alien) =>
alien !== null &&
alien.status === 'dead' &&
alien.lastKilledAt &&
alien.lastKilledAt < executionCtx.getCurrentTime() + this.shotDuration
)

// Skip 80% of the time
if (Math.random() > 0.2) {
return
}

// Choose random dead alien from this.aliens
const alien = deadAliens[Math.floor(Math.random() * deadAliens.length)]
if (alien == null) {
return
}

alien.status = 'alive'
const renamationTime = executionCtx.getCurrentTime()

;['tl', 'tr', 'bl', 'br'].forEach((pos) => {
this.addAnimation({
targets: `#${this.view.id} #${alien.elem.id} .${pos}`,
duration: 1,
transformations: { translateX: 0, translateY: 0, rotate: 0 },
offset: renamationTime,
})
this.addAnimation({
targets: `#${this.view.id} #${alien.elem.id} .${pos}`,
duration: 100,
transformations: { opacity: 1 },
offset: renamationTime,
})
})
}

private moveLaser(executionCtx: ExecutionContext) {
this.addAnimation({
targets: `#${this.view.id} .laser`,
Expand Down Expand Up @@ -115,6 +204,13 @@ export default class SpaceInvadersExercise extends Exercise {
}

public shoot(executionCtx: ExecutionContext) {
if (this.lastShotAt > executionCtx.getCurrentTime() - 50) {
executionCtx.logicError(
'Oh no! Your laser canon overheated from shooting too fast! You need to move before you can shoot a second time.'
)
}
this.lastShotAt = executionCtx.getCurrentTime()

let targetRow = null
let targetAlien: Alien | null = null
this.aliens.forEach((row, rowIdx) => {
Expand Down Expand Up @@ -167,42 +263,14 @@ export default class SpaceInvadersExercise extends Exercise {
this.gameStatus = 'lost'
executionCtx.updateState('gameOver', true)
} else {
const alien = targetAlien as Alien
alien.status = 'dead'
;[
['tl', -10, -10, -180],
['tr', 10, -10, 180],
['bl', -10, 10, -180],
['br', 10, 10, 180],
].forEach(([pos, x, y, rotate]) => {
this.addAnimation({
targets: `#${this.view.id} #${alien.elem.id} .${pos}`,
duration: 300,
transformations: {
translateX: x,
translateY: y,
rotate: rotate,
opacity: 0,
},
offset: executionCtx.getCurrentTime() + duration,
})
})
this.addAnimation({
targets: `#${this.view.id} #${shot.id}`,
duration: 1,
transformations: { opacity: 0 },
offset: executionCtx.getCurrentTime() + duration,
})
this.killAlien(executionCtx, targetAlien, shot)
this.reanimateRandomAlien(executionCtx)

// Why do we do this?
executionCtx.fastForward(30)

this.checkForWin(executionCtx)
}

// const target = this.aliens[3][this.laserPosition]
// target = this.aliens[0][0]
// this.laserLeft -= this.moveStep
// if (this.laserLeft < 0) {
// }
}

public moveLeft(executionCtx: ExecutionContext) {
Expand Down
3 changes: 0 additions & 3 deletions app/javascript/interpreter/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ export function describeFrame(
frame: Frame,
externalFunctions: ExternalFunction[]
): string {
try {
console.log(process.env.NODE_ENV)
} catch {}
try {
// These need to come from the exercise.
const functionDescriptions: Record<string, string> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ You can move the laser left and right using the `move_left()` and `move_right()`

As you move, you need to check whether there's an alien above you using the `is_alien_above()` function and then `shoot()` it if so. If you shoot when there's not an alien, you'll lose the game - wasting ammo is not allowed!

The laser canon easily overheats.
You need to move between shoots to keep it cool.

Once all the aliens have been shot down, you win!

### Reference
Expand Down
Loading