Euler golf #1
@ -4,16 +4,24 @@ body {
|
|||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
font-family: Lucida Console, Lucida Sans Typewriter, monaco,
|
font-family: Lucida Console, Lucida Sans Typewriter, monaco,
|
||||||
Bitstream Vera Sans Mono, monospace;
|
Bitstream Vera Sans Mono, monospace;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: rgb(238, 174, 202);
|
||||||
|
background: radial-gradient(
|
||||||
|
circle,
|
||||||
|
rgba(238, 174, 202, 1) 0%,
|
||||||
|
rgba(148, 187, 233, 1) 100%
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.canvas-holder canvas {
|
.canvas {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
display: block;
|
display: block;
|
||||||
height: 70vh;
|
|
||||||
width: auto;
|
|
||||||
max-width: 100%;
|
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
|
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
@ -22,3 +30,15 @@ body {
|
|||||||
padding: 5px;
|
padding: 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
padding: 12px;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
|
border: 1px solid white;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-right: 6px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
@ -5,8 +5,10 @@
|
|||||||
<link rel="stylesheet" type="text/css" href="css/styles.css" />
|
<link rel="stylesheet" type="text/css" href="css/styles.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="canvas-holder">
|
<canvas id="canvas"></canvas>
|
||||||
<canvas id="canvas" width="1000" height="1000"></canvas>
|
|
||||||
|
<div class="controls">
|
||||||
|
<span id="iteration-count"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="js/cx.js"></script>
|
<script src="js/cx.js"></script>
|
||||||
|
@ -1,31 +1,23 @@
|
|||||||
const CANVAS_ID = "myCanvas";
|
|
||||||
const DEFAULTS = {
|
const DEFAULTS = {
|
||||||
rows: 15,
|
max_rows: 80,
|
||||||
cols: 15,
|
max_cols: 80,
|
||||||
grid_padding: 10,
|
min_gap: 40,
|
||||||
|
angle_multiplier: 0.0005,
|
||||||
};
|
};
|
||||||
|
const CANVAS = document.getElementById("canvas");
|
||||||
|
|
||||||
let state = {
|
let state = {
|
||||||
...DEFAULTS,
|
grid_padding: 10,
|
||||||
canvas: document.getElementById("canvas"),
|
canvas: CANVAS,
|
||||||
ctx: document.getElementById("canvas").getContext("2d"),
|
ctx: CANVAS.getContext("2d"),
|
||||||
last_render: 0,
|
last_render: 0,
|
||||||
changes: {
|
path: [new cx(0, 0), new cx(1, 0)],
|
||||||
width: document.body.clientWidth,
|
angle: new cx(0, 0),
|
||||||
height: document.body.clientHeight,
|
keys: {},
|
||||||
},
|
changes: {},
|
||||||
};
|
|
||||||
|
|
||||||
const rand_between = (min, max) =>
|
|
||||||
Math.floor(Math.random() * (max - min + 1)) + min;
|
|
||||||
|
|
||||||
const rand_target = (cols, rows) => {
|
|
||||||
const res = new cx(rand_between(0, cols), rand_between(0, rows));
|
|
||||||
if (res.re % 2 || res.im % 2) return rand_target(cols, rows);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Rendering
|
||||||
CanvasRenderingContext2D.prototype.circle = function (x, y, r, color) {
|
CanvasRenderingContext2D.prototype.circle = function (x, y, r, color) {
|
||||||
this.beginPath();
|
this.beginPath();
|
||||||
this.arc(x, y, r, 0, Math.PI * 2);
|
this.arc(x, y, r, 0, Math.PI * 2);
|
||||||
@ -35,22 +27,35 @@ CanvasRenderingContext2D.prototype.circle = function (x, y, r, color) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
CanvasRenderingContext2D.prototype.line = function (
|
CanvasRenderingContext2D.prototype.line = function (
|
||||||
{ x: x1, y: y1 },
|
{ x_pos: x1, y_pos: y1 },
|
||||||
{ x: x2, y: y2 },
|
{ x_pos: x2, y_pos: y2 },
|
||||||
width,
|
width,
|
||||||
color,
|
color,
|
||||||
cap = "round"
|
cap = "round"
|
||||||
) {
|
) {
|
||||||
this.beginPath();
|
|
||||||
this.moveTo(x1, y1);
|
|
||||||
this.lineTo(x2, y2);
|
|
||||||
this.lineWidth = width;
|
this.lineWidth = width;
|
||||||
this.strokeStyle = color;
|
this.strokeStyle = color;
|
||||||
this.lineCap = cap;
|
this.lineCap = cap;
|
||||||
|
|
||||||
|
this.beginPath();
|
||||||
|
this.moveTo(x1, y1);
|
||||||
|
this.lineTo(x2, y2);
|
||||||
this.stroke();
|
this.stroke();
|
||||||
this.closePath();
|
this.closePath();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
CanvasRenderingContext2D.prototype.draw_cartesian_path = function (
|
||||||
|
grid_spec,
|
||||||
|
cartesian_path,
|
||||||
|
width = 2,
|
||||||
|
color = "#000"
|
||||||
|
) {
|
||||||
|
const path = cartesian_path.map((coord) => grid_to_canvas(coord, grid_spec));
|
||||||
|
path.slice(1).forEach((coord, i) => {
|
||||||
|
this.line(path[i], coord, width, color);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
CanvasRenderingContext2D.prototype.do_grid = function (
|
CanvasRenderingContext2D.prototype.do_grid = function (
|
||||||
rows,
|
rows,
|
||||||
cols,
|
cols,
|
||||||
@ -66,33 +71,92 @@ CanvasRenderingContext2D.prototype.do_grid = function (
|
|||||||
CanvasRenderingContext2D.prototype.cartesian_grid = function (
|
CanvasRenderingContext2D.prototype.cartesian_grid = function (
|
||||||
rows,
|
rows,
|
||||||
cols,
|
cols,
|
||||||
width,
|
grid_spec,
|
||||||
height,
|
circle_spec_at_coords = (_x, _y) => ({ radius: 5, color: "#000" })
|
||||||
grid_padding,
|
|
||||||
circle_spec_at_coords = (x, y) => ({ radius: 5, color: "#000" })
|
|
||||||
) {
|
) {
|
||||||
const center = { x: Math.floor(cols / 2), y: Math.floor(rows / 2) };
|
|
||||||
|
|
||||||
const dx = (width - 2 * grid_padding) / cols;
|
|
||||||
const dy = (height - 2 * grid_padding) / rows;
|
|
||||||
|
|
||||||
const start_x = grid_padding + dx / 2;
|
|
||||||
const start_y = grid_padding + dy / 2;
|
|
||||||
|
|
||||||
this.do_grid(rows, cols, (ctx, x, y) => {
|
this.do_grid(rows, cols, (ctx, x, y) => {
|
||||||
const x_pos = x * dx + start_x;
|
const { x_pos, y_pos } = grid_to_canvas({ x, y }, grid_spec);
|
||||||
const y_pos = y * dy + start_y;
|
|
||||||
const { radius, color } = circle_spec_at_coords(x, y);
|
const { radius, color } = circle_spec_at_coords(x, y);
|
||||||
|
|
||||||
ctx.circle(x_pos, y_pos, radius, color);
|
ctx.circle(x_pos, y_pos, radius, color);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const render = ({ canvas, ctx, rows, cols, grid_padding }) => {
|
// Utilities
|
||||||
const { width, height } = canvas;
|
const move = (prev, curr, c) => cx.add(prev, cx.mult(c, cx.sub(curr, prev)));
|
||||||
|
|
||||||
|
const rand_between = (min, max) =>
|
||||||
|
Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
|
||||||
|
const rand_target = (cols, rows) => {
|
||||||
|
const res = new cx(rand_between(0, cols), rand_between(0, rows));
|
||||||
|
if (res.re % 2 || res.im % 2) return rand_target(cols, rows);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculate_grid_spec = ({ rows, cols, width, height, grid_padding }) => {
|
||||||
|
const dx = (width - 2 * grid_padding) / cols;
|
||||||
|
const dy = (height - 2 * grid_padding) / rows;
|
||||||
|
|
||||||
|
return {
|
||||||
|
dx,
|
||||||
|
dy,
|
||||||
|
start_x: grid_padding + dx / 2,
|
||||||
|
start_y: grid_padding + dy / 2,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const grid_to_canvas = ({ x, y }, { dx, dy, start_x, start_y }) => ({
|
||||||
|
x_pos: x * dx + start_x,
|
||||||
|
y_pos: y * dy + start_y,
|
||||||
|
});
|
||||||
|
|
||||||
|
const complex_to_grid = (c, rows, cols) => {
|
||||||
|
const { re, im } = c;
|
||||||
|
return {
|
||||||
|
x: re + Math.floor(cols / 2),
|
||||||
|
y: Math.floor(rows / 2) - im,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Game loop
|
||||||
|
const handle_input = (state, dt) => {
|
||||||
|
if (state.keys.ArrowLeft) {
|
||||||
|
state.angle.im += DEFAULTS.angle_multiplier * dt;
|
||||||
|
} else if (state.keys.ArrowRight) {
|
||||||
|
state.angle.im -= DEFAULTS.angle_multiplier * dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.angle.im <= -1 || state.angle.im >= 1) {
|
||||||
|
state.angle.im = state.angle.im <= -1 ? -1 : 1;
|
||||||
|
state.path.push(move(state.path.at(-2), state.path.at(-1), state.angle));
|
||||||
|
state.angle = new cx(0, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const render = ({ width, height, ctx, rows, cols } = state) => {
|
||||||
ctx.clearRect(0, 0, width, height);
|
ctx.clearRect(0, 0, width, height);
|
||||||
ctx.cartesian_grid(rows, cols, width, height, grid_padding, (x, y) => {
|
ctx.fillStyle = "rgba(255, 255, 255, 0)";
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
|
const grid_spec = calculate_grid_spec(state);
|
||||||
|
|
||||||
|
const curr = state.path.at(-1);
|
||||||
|
const prev = state.path.at(-2);
|
||||||
|
|
||||||
|
const v_diff = cx.sub(curr, prev);
|
||||||
|
const theta = (state.angle.im * Math.PI) / 2;
|
||||||
|
|
||||||
|
const angle_re = Math.cos(theta) * v_diff.re - Math.sin(theta) * v_diff.im;
|
||||||
|
const angle_im = Math.sin(theta) * v_diff.re + Math.cos(theta) * v_diff.im;
|
||||||
|
|
||||||
|
ctx.draw_cartesian_path(grid_spec, [
|
||||||
|
...state.path.map((c) => complex_to_grid(c, rows, cols)),
|
||||||
|
complex_to_grid(cx.add(new cx(angle_re, angle_im), prev), rows, cols),
|
||||||
|
]);
|
||||||
|
|
||||||
|
ctx.cartesian_grid(rows, cols, grid_spec, (x, y) => {
|
||||||
if (x == Math.floor(cols / 2) && y == Math.floor(rows / 2)) {
|
if (x == Math.floor(cols / 2) && y == Math.floor(rows / 2)) {
|
||||||
return {
|
return {
|
||||||
radius: 7,
|
radius: 7,
|
||||||
@ -101,7 +165,7 @@ const render = ({ canvas, ctx, rows, cols, grid_padding }) => {
|
|||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
radius: 3,
|
radius: 3,
|
||||||
color: "#000",
|
color: `rgb(${255 * (x / cols)}, 100, 100)`, // todo: animate with last_render
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -110,14 +174,48 @@ const render = ({ canvas, ctx, rows, cols, grid_padding }) => {
|
|||||||
const loop = (now) => {
|
const loop = (now) => {
|
||||||
const dt = now - state.last_render;
|
const dt = now - state.last_render;
|
||||||
state.changes.last_render = now;
|
state.changes.last_render = now;
|
||||||
|
|
||||||
if (Object.keys(state.changes).length > 0) {
|
if (Object.keys(state.changes).length > 0) {
|
||||||
|
if (state.changes.width || state.changes.height) {
|
||||||
|
state.changes.rows = Math.min(
|
||||||
|
DEFAULTS.max_rows,
|
||||||
|
state.changes.height / DEFAULTS.min_gap
|
||||||
|
);
|
||||||
|
state.changes.cols = Math.min(
|
||||||
|
DEFAULTS.max_cols,
|
||||||
|
state.changes.width / DEFAULTS.min_gap
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
state = { ...state, ...state.changes };
|
state = { ...state, ...state.changes };
|
||||||
state.changes = {};
|
state.changes = {};
|
||||||
|
|
||||||
render(state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handle_input(state, dt);
|
||||||
|
render(state);
|
||||||
requestAnimationFrame(loop);
|
requestAnimationFrame(loop);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// DOM
|
||||||
|
const on_resize = () => {
|
||||||
|
CANVAS.width = document.body.clientWidth;
|
||||||
|
CANVAS.height = document.body.clientHeight;
|
||||||
|
state.changes.width = CANVAS.width;
|
||||||
|
state.changes.height = CANVAS.height;
|
||||||
|
};
|
||||||
|
|
||||||
|
const on_keyup = (e) => {
|
||||||
|
delete state.keys[e.key];
|
||||||
|
};
|
||||||
|
|
||||||
|
const on_keydown = (e) => {
|
||||||
|
state.keys[e.key] = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("resize", on_resize);
|
||||||
|
window.addEventListener("keydown", on_keydown);
|
||||||
|
window.addEventListener("keyup", on_keyup);
|
||||||
|
|
||||||
|
// main
|
||||||
|
on_resize();
|
||||||
requestAnimationFrame(loop);
|
requestAnimationFrame(loop);
|
||||||
|
@ -5,8 +5,6 @@ const DIRECTION = {
|
|||||||
1: new cx(0, -1),
|
1: new cx(0, -1),
|
||||||
};
|
};
|
||||||
|
|
||||||
const move = (prev, curr, c) => cx.add(prev, cx.mult(c, cx.sub(curr, prev)));
|
|
||||||
|
|
||||||
const construct_moves = (curr, prev) =>
|
const construct_moves = (curr, prev) =>
|
||||||
Object.keys(DIRECTION).map((x) => move(curr, prev, DIRECTION[x]));
|
Object.keys(DIRECTION).map((x) => move(curr, prev, DIRECTION[x]));
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user