Public
Edited
Dec 13, 2023
2 forks
7 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
crtWarpDisplacementMap_v5 = {
({
prompt:
"Maybe barrel distortion is not the right term. I want the corners pulled in. So displacements far from the center should be deflected towards the center",
time: 1701296188546,
comment:
"Generate displacement map for CRT warp effect with corners pulled towards the center"
});

const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = canvas.height = crtWarpAdjustment.displacement_size; // Use a power of 2 for better texture performance

// Draw the displacement map
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const warpEffect = crtWarpAdjustment.barrel_warp;
for (let index = 0; index < data.length; index += 4) {
const x = (index / 4) % canvas.width;
const y = index / 4 / canvas.width;
const dy = y / (canvas.height * 1.0) - 0.5;
let dx = x / (canvas.width * 1.0) - 0.5;

const distance2 = dx * dx + dy * dy;
data[index] = 127.5 + dx * distance2 * distance2 * warpEffect; // Red channel for X displacement
data[index + 1] = 127.5 + dy * distance2 * distance2 * warpEffect; // Green channel for Y displacement
data[index + 2] = 127; // Blue channel not used
data[index + 3] = 127; // Alpha channel
}
ctx.putImageData(imageData, 0, 0);

// Convert canvas to an image blob
const warp = await new Promise((resolve) => {
canvas.toBlob((blob) => {
resolve(URL.createObjectURL(blob));
});
});

deps.register("crtWarpDisplacementMap", warp, 4);

return warp;
}
Insert cell
Insert cell
Insert cell
viewof enemy_ship_template_v4 = {
({
prompt:
"OK I like the latest ship, can we make the center elipse spin around 360 with an SVG animation. Do not add or remove controls, or change values, other than than add spin speed UI",
time: 1700598322777,
comment:
"Add spin speed UI and implement SVG animation for the center ellipse of the enemy ship"
});
const enemyShipSettings = view`<div>
<h2>Enemy Ship Settings</h2>
${["scale", Inputs.range([0.05, 5], { value: 0.75, label: "Scale" })]}
${["width", Inputs.range([0.1, 2], { value: 0.5, label: "Width" })]}
${["height", Inputs.range([0.05, 1], { value: 0.1, label: "Height" })]}
${["color", Inputs.color({ value: "#000000", label: "Color" })]}
${[
"stroke_width",
Inputs.range([0, 0.05], { value: 0.018, label: "Stroke width" })
]}
${[
"stroke_color",
Inputs.color({ value: "#46ff2e", label: "Stroke color" })
]}
${[
"center_ellipse_width",
Inputs.range([0, 1], { value: 0.15, label: "Center ellipse width" })
]}
${[
"center_ellipse_height",
Inputs.range([0, 1], { value: 0.2, label: "Center ellipse height" })
]}
${[
"spin_speed",
Inputs.range([0, 10000], {
value: 400,
step: 100,
label: "Spin speed (ms for full rotation)"
})
]}
</div>`;

const updateEnemyShipTemplate = () => {
const settings = enemyShipSettings.value;
const centerEllipse = htl.svg`<ellipse cx="0" cy="0" stroke="${
settings.stroke_color
}" stroke-width="${settings.stroke_width * settings.scale}" rx="${
(settings.center_ellipse_width / 2) * settings.scale
}" ry="${(settings.center_ellipse_height * settings.scale) / 2}" fill="${
settings.color
}">
<animateTransform attributeName="transform" type="rotate" from="0 0 0" to="360 0 0" dur="${
settings.spin_speed
}ms" repeatCount="indefinite"/>
</ellipse>`;
const enemyShip = htl.svg`<ellipse cx="0" cy="0" rx="${
(settings.width / 2) * settings.scale
}" ry="${(settings.height / 2) * settings.scale}" stroke="${
settings.stroke_color
}" stroke-width="${settings.stroke_width * settings.scale}" />
${centerEllipse}`;
assets.set("enemyShip", enemyShip);
return enemyShip;
};

// Preview enemy ship template
const preview = htl.svg`<svg width="100" height="100" viewBox="-0.4 -0.4 .8 .8" style="background: black;">${updateEnemyShipTemplate()}</svg>`;

enemyShipSettings.addEventListener("input", () => {
updateEnemyShipTemplate();
preview.replaceChild(assets.get("enemyShip"), preview.firstChild);
});

return htl.html`${enemyShipSettings}${preview}`;
}
Insert cell
Insert cell
viewof PlayerControl_v6 = {
({
prompt:
"Can we make a new version of keyboard controls that includes a UI for changing the values",
time: 1700398068677,
comment: "New version of PlayerControl with UI for changing control values"
});

const controlSettings = view`<div>
<h2>Player Control Settings</h2>
${[
"rotation_speed",
Inputs.range([0.01, 0.1], {
value: 0.03,
step: 0.01,
label: "Adjust rotation speed"
})
]}
${[
"thrust",
Inputs.range([0.0001, 0.001], {
value: 0.0004,
step: 0.00005,
label: "Adjust thrust"
})
]}
${[
"bullet_speed",
Inputs.range([0, 1], {
value: 0.02,
step: 0.00005,
label: "Bullet speed"
})
]}
${[
"bullet_ttl",
Inputs.range([0, 500], {
value: 80,
step: 1,
label: "Bullet ttl"
})
]}
</div>`;

class PlayerControl extends deps.resolve("BaseSystem") {
constructor() {
super();
}

tick() {
this.controlSettings = controlSettings.value;
const player = entities.get("player");
if (!player) return;
if (keysPressed["ArrowLeft"]) {
player.velocity[2] = -this.controlSettings.rotation_speed;
}
if (keysPressed["ArrowRight"]) {
player.velocity[2] = this.controlSettings.rotation_speed;
}
if (!keysPressed["ArrowRight"] && !keysPressed["ArrowLeft"]) {
player.velocity[2] = 0;
}
if (keysPressed["ArrowUp"]) {
const rotation = player.position[2];
player.velocity[0] += Math.cos(rotation) * this.controlSettings.thrust;
player.velocity[1] += Math.sin(rotation) * this.controlSettings.thrust;
}
if (keysPressed["ArrowDown"]) {
const rotation = player.position[2];
player.velocity[0] -= Math.cos(rotation) * this.controlSettings.thrust;
player.velocity[1] -= Math.sin(rotation) * this.controlSettings.thrust;
}

if (keysPressed[" "]) {
keysPressed[" "] = false;
const rotation = player.position[2];
const bulletVelocity = [
Math.cos(rotation) * this.controlSettings.bullet_speed +
player.velocity[0],
Math.sin(rotation) * this.controlSettings.bullet_speed +
player.velocity[1],
0
];
const projectile = {
id: `p${Date.now()}`,
template: "projectile",
position: [...player.position],
velocity: bulletVelocity,
ttl: this.controlSettings.bullet_ttl
};
entities.set(projectile.id, projectile);
}
}
}

deps.register("PlayerControl", new PlayerControl(), 6);

return controlSettings;
}
Insert cell
Insert cell
RendererSystem_v5 = {
({
prompt:
"Our render system should use the generator and generator args if available",
time: 1700507770128,
comment: "Update RendererSystem to use generator function if available"
});

class RendererSystem extends deps.resolve("BaseSystem") {
constructor(...args) {
super(...args);
this.game_view = deps.resolve("game_view");
this.assetVersions = new Map();
this.boulderGenerator = deps.resolve("boulder_generator");
this.boulderGeneratorArgs = deps.resolve("boulder_generator_args");
}

tick() {
const assets = deps.resolve("Assets");
const unfiltered = this.game_view.querySelector(`#unfiltered`);
const root = this.game_view.querySelector(`#game-root`);
for (let [id, entity] of entities) {
if (!entity.position) return;

// Set attributes or create transform if needed
let transform = this.game_view.querySelector(`g[id=${id}]`);
let generator = deps.resolve(entity.generator);
let template = assets.get(entity.template);
if (
!transform ||
transform.generator !== generator ||
transform.template !== template
) {
let svgElement;
if (generator && entity.generator_args) {
// Use generator function if available
svgElement = generator(...entity.generator_args);
} else if (template) {
svgElement = template.cloneNode(true);
}
transform = transform || htl.svg`<g id=${id}></g>`;
transform.textContent = "";
transform.appendChild(svgElement);
transform.template = template;
transform.generator = generator;

if (entity.unfiltered) {
unfiltered.appendChild(transform);
} else {
root.appendChild(transform);
}
}

// Sync transform to entity's position and rotation
transform.setAttribute(
"transform",
`translate(${entity.position[0]} ${entity.position[1]}) rotate(${
(entity.position[2] * 180) / Math.PI
})`
);
}
}
}

deps.register("RendererSystem", new RendererSystem(), 5);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
GameOverSystem = {
({
prompt:
"Add a system that when lives is 0, the top score is recorded and the player ship is removed",
time: 1700511760135,
comment:
"Create a system to handle game over, record top score, and remove player ship"
});

class GameOverSystem extends deps.resolve("BaseSystem") {
constructor() {
super();
}

tick() {
if (gameState.lives <= 0) {
gameState.state = "Game Over";

// Record top score
const topScore = localStorage.getItem("topScore") || 0;
if (gameState.score > topScore) {
localStorage.setItem("topScore", gameState.score);
}

// Remove player ship
entities.delete("player");
const playerElement = game_view.querySelector("#player");
if (playerElement) playerElement.remove();
}
}
}

deps.register("GameOverSystem", new GameOverSystem(), 1);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
events
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
deps.resolve("generateUIPanel")
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
entities
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
positionView
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
explode
Insert cell
explode
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
RestartGameOnSpaceSystem = {
({
prompt:
"When the game is over, clicking space should restart the game. You can acheive this by dispactching a click to the start game button. Make a system for it",
time: 1701617460949,
comment:
"Create a system to restart the game when space is pressed during Game Over state"
});

class RestartGameOnSpaceSystem extends deps.resolve("BaseSystem") {
constructor() {
super();
}

tick() {
if (gameState.state === "Game Over" && keysPressed[" "]) {
debugger;
// Space key pressed during Game Over state
keysPressed[" "] = false; // Prevent repeated restarts
const startButton = document.querySelector("#startGameButton");
if (startButton) {
startButton.click();
}
}
}
}

deps.register("RestartGameOnSpaceSystem", new RestartGameOnSpaceSystem(), 1);
}
Insert cell
MobileShootOnTapSystem = {
({
prompt:
"Mobile users should be able to at least know the game works. If the user taps the view we should fake a space being pressed to shoot",
time: 1701619598675,
comment:
"System to simulate spacebar press for mobile users when they tap the game view"
});

game_view.addEventListener("touchstart", (event) => {
event.preventDefault();
keysPressed[" "] = true;
});
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
source["crtWarpDisplacementMap"]
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more