diff --git a/euler-golf/css/styles.css b/euler-golf/css/styles.css new file mode 100644 index 0000000..f343898 --- /dev/null +++ b/euler-golf/css/styles.css @@ -0,0 +1,24 @@ +body { + margin: 0; + padding: 0; + overflow: scroll; + font-family: Lucida Console, Lucida Sans Typewriter, monaco, + Bitstream Vera Sans Mono, monospace; +} + +.canvas-holder canvas { + padding: 0; + margin: auto; + display: block; + height: 70vh; + width: auto; + max-width: 100%; + border: 1px solid black; +} + +.button { + border: 1px solid black; + border-radius: 5px; + padding: 5px; + cursor: pointer; +} diff --git a/euler-golf/index.html b/euler-golf/index.html index 2682a0c..2717ed5 100644 --- a/euler-golf/index.html +++ b/euler-golf/index.html @@ -2,36 +2,15 @@ Euler Golf 2 - + - +
+ +
+ diff --git a/euler-golf/js/game.js b/euler-golf/js/game.js index 6283cc4..25c1632 100644 --- a/euler-golf/js/game.js +++ b/euler-golf/js/game.js @@ -1,11 +1,123 @@ -const WIDTH = 1000; -const HEIGHT = 1000; - -const draw_grid = (ctx) => {}; - -const main = () => { - const canvas = document.getElementById("canvas"); - const ctx = canvas.getContext("2d"); +const CANVAS_ID = "myCanvas"; +const DEFAULTS = { + rows: 15, + cols: 15, + grid_padding: 10, }; -main(); +let state = { + ...DEFAULTS, + canvas: document.getElementById("canvas"), + ctx: document.getElementById("canvas").getContext("2d"), + last_render: 0, + changes: { + width: document.body.clientWidth, + height: document.body.clientHeight, + }, +}; + +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; +}; + +CanvasRenderingContext2D.prototype.circle = function (x, y, r, color) { + this.beginPath(); + this.arc(x, y, r, 0, Math.PI * 2); + this.fillStyle = color; + this.fill(); + this.closePath(); +}; + +CanvasRenderingContext2D.prototype.line = function ( + { x: x1, y: y1 }, + { x: x2, y: y2 }, + width, + color, + cap = "round" +) { + this.beginPath(); + this.moveTo(x1, y1); + this.lineTo(x2, y2); + this.lineWidth = width; + this.strokeStyle = color; + this.lineCap = cap; + this.stroke(); + this.closePath(); +}; + +CanvasRenderingContext2D.prototype.do_grid = function ( + rows, + cols, + draw_at_grid_pos = (ctx, x, y) => ctx.circle(x, y, 10, "#00ff00") +) { + for (let y = 0; y < rows; y++) { + for (let x = 0; x < cols; x++) { + draw_at_grid_pos(this, x, y); + } + } +}; + +CanvasRenderingContext2D.prototype.cartesian_grid = function ( + rows, + cols, + width, + height, + 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) => { + const x_pos = x * dx + start_x; + const y_pos = y * dy + start_y; + const { radius, color } = circle_spec_at_coords(x, y); + + ctx.circle(x_pos, y_pos, radius, color); + }); +}; + +const render = ({ canvas, ctx, rows, cols, grid_padding }) => { + const { width, height } = canvas; + + ctx.clearRect(0, 0, width, height); + ctx.cartesian_grid(rows, cols, width, height, grid_padding, (x, y) => { + if (x == Math.floor(cols / 2) && y == Math.floor(rows / 2)) { + return { + radius: 7, + color: "#0000ff", + }; + } else { + return { + radius: 3, + color: "#000", + }; + } + }); +}; + +const loop = (now) => { + const dt = now - state.last_render; + state.changes.last_render = now; + if (Object.keys(state.changes).length > 0) { + state = { ...state, ...state.changes }; + state.changes = {}; + + render(state); + } + + requestAnimationFrame(loop); +}; + +requestAnimationFrame(loop); diff --git a/euler-golf/js/json-ds.js b/euler-golf/js/json-ds.js new file mode 100644 index 0000000..24a8729 --- /dev/null +++ b/euler-golf/js/json-ds.js @@ -0,0 +1,17 @@ +class JSONSet { + items = new Set(); + constructor(initial) { + if (Array.isArray(initial)) { + initial.map((x) => this.apply_set_function("add", x)); + } else { + this.apply_set_function("add", initial); + } + + ["add", "has", "remove"].forEach( + (f_name) => (this[f_name] = (x) => this.apply_set_function(f_name, x)) + ); + } + apply_set_function(f_name, x) { + return this.items[f_name](JSON.stringify(x)); + } +} diff --git a/euler-golf/js/sol.js b/euler-golf/js/sol.js index a022c99..a8ce2cf 100644 --- a/euler-golf/js/sol.js +++ b/euler-golf/js/sol.js @@ -1,4 +1,4 @@ -const DEPTH = 7; +const DEPTH = 21; const DIRECTION = { 0: new cx(0, 1), @@ -6,39 +6,36 @@ const DIRECTION = { }; const move = (prev, curr, c) => cx.add(prev, cx.mult(c, cx.sub(curr, prev))); + const construct_moves = (curr, prev) => Object.keys(DIRECTION).map((x) => move(curr, prev, DIRECTION[x])); + const backtrack = (local_index, depth) => local_index .toString(2) .padStart(depth, "0") .split("") - .reverse() - .map((dir) => (dir ? "+" : "-")); + .map((direction) => (Number(direction) ? "-" : "+")); const sol = (target, start_from = new cx(0, 0), start_to = new cx(1, 0)) => { - let moves = construct_moves(start_from, start_to); - let curr_depth = 2; // potential bug: when target is within one move away + let moves = [start_to, ...construct_moves(start_from, start_to)]; + let curr_depth = 2; - do { + while (curr_depth < DEPTH) { for (let i = 0; i < Math.pow(2, curr_depth); i++) { - const current_i = - (i >> 1) + ((1 - Math.pow(2, curr_depth - 1)) / (1 - 2) - 1); - const previous_i = - (i >> 2) + ((1 - Math.pow(2, curr_depth - 2)) / (1 - 2) - 1); + const direction = DIRECTION[Number(i.toString(2).at(-1))]; + // Current element is at i >> 1 + the offset for the previous group (which is + // the sum of the geometric series 2**n until curr_depth - 1) + const current_i = (i >> 1) + (1 - Math.pow(2, curr_depth - 1)) / (1 - 2); + const previous_i = (i >> 2) + (1 - Math.pow(2, curr_depth - 2)) / (1 - 2); - const new_move = move( - previous_i < 0 ? start_from : moves[previous_i], - moves[current_i], - DIRECTION[parseInt(i.toString(2)[0])] - ); - - if (cx.eq(new_move, target)) return backtrack(moves, target); + const new_move = move(moves[previous_i], moves[current_i], direction); moves.push(new_move); + if (cx.eq(new_move, target)) return backtrack(i, curr_depth); } curr_depth++; - } while (curr_depth < DEPTH); - console.log(moves); + } + return null; };