Compare commits
15 Commits
50ba6e3500
...
f847d57827
Author | SHA1 | Date |
---|---|---|
HyperOnion | f847d57827 | |
HyperOnion | e214888ed5 | |
HyperOnion | da9db456d8 | |
HyperOnion | 2335ffd48e | |
HyperOnion | d80c8b399c | |
HyperOnion | c4d6e854a5 | |
HyperOnion | e912451423 | |
HyperOnion | 1be1a39ca0 | |
HyperOnion | b80842ac46 | |
HyperOnion | 34970bc205 | |
HyperOnion | 5de18d1a57 | |
HyperOnion | c3eceaefcb | |
HyperOnion | b4deea63ce | |
HyperOnion | d81d906ed8 | |
HyperOnion | 17e25281cd |
|
@ -0,0 +1,18 @@
|
|||
# overview
|
||||
|
||||
the player character is currently the dark green square. creating new tiles creates them where you're standing, so you have to move out of the way to see them.
|
||||
|
||||
moving onto boxes makes you enter them. right now, this doesn't change your position; in the future entering a box from e.g. the left side will make you move to its left edge.
|
||||
|
||||
moving out of bounds makes you exit the box, if you're in one. this also doesn't change your position yet. if you exit at a position where there's another box in the one you exit into, you'll immediately enter that box.
|
||||
|
||||
# controls
|
||||
|
||||
- arrow keys: move around.
|
||||
- QWEASD: paint with RGBYCM colors, respectively.
|
||||
- space: create box.
|
||||
- backspace: delete/clear/empty tiles to your left and right, and where you're standing.
|
||||
|
||||
# how to play
|
||||
|
||||
run a server in the root directory and open `index.html` in a web browser.
|
|
@ -10,5 +10,5 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
<canvas id="canvas" width="576" height="576"></canvas>
|
||||
<canvas id="canvas" width="729" height="729"></canvas>
|
||||
</body>
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import * as world from "./world.mjs";
|
||||
const BOX_SIZE = world.BOX_SIZE;
|
||||
|
||||
|
||||
|
||||
const entities = [];
|
||||
|
||||
export function create (box, x = 0, y = 0, color = "#fff") {
|
||||
const entity = {
|
||||
id: entities.length,
|
||||
box,
|
||||
x,
|
||||
y,
|
||||
color,
|
||||
};
|
||||
entities.push(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
export function destroy (id) {
|
||||
// returns the destroyed entity, idk if that makes sense.
|
||||
return entities.splice(id, 1)[0];
|
||||
}
|
||||
|
||||
export function for_each (callback) {
|
||||
entities.forEach(entity => {
|
||||
callback(entity, entity.id);
|
||||
});
|
||||
}
|
||||
|
||||
function clamp_pos (entity) {
|
||||
if (entity.x < 0) entity.x = 0;
|
||||
if (entity.x >= BOX_SIZE) entity.x = BOX_SIZE - 1;
|
||||
if (entity.y < 0) entity.y = 0;
|
||||
if (entity.y >= BOX_SIZE) entity.y = BOX_SIZE - 1;
|
||||
return entity;
|
||||
}
|
||||
|
||||
export function set_pos (entity, x, y) {
|
||||
entity.x = x;
|
||||
entity.y = y;
|
||||
|
||||
if (out_of_bounds(entity)) {
|
||||
if (entity.box.parent) {
|
||||
entity.box = entity.box.parent;
|
||||
// TODO: exit from side of box.
|
||||
}
|
||||
clamp_pos(entity);
|
||||
}
|
||||
|
||||
if (world.get_tile(entity.box, entity.x, entity.y)?.type === "box") {
|
||||
entity.box = world.get_tile(entity.box, entity.x, entity.y).box;
|
||||
// TODO: enter at edge of box.
|
||||
}
|
||||
}
|
||||
|
||||
export function move (entity, d_x, d_y) {
|
||||
set_pos(entity, entity.x + d_x, entity.y + d_y);
|
||||
}
|
||||
|
||||
function out_of_bounds (entity) {
|
||||
return world.out_of_bounds(entity.x, entity.y);
|
||||
}
|
|
@ -1,25 +1,70 @@
|
|||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
import * as graphics from "./graphics_core.mjs";
|
||||
export * from "./graphics_core.mjs";
|
||||
|
||||
export function set_size (w, h) {
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
import { range, iter_2d } from "./utils/range.mjs";
|
||||
import * as entity from "./entity.mjs";
|
||||
import * as world from "./world.mjs";
|
||||
|
||||
|
||||
|
||||
const canvas = document.getElementById("canvas");
|
||||
const canvas_context = canvas.getContext("2d");
|
||||
graphics.set_size(canvas.width, canvas.height);
|
||||
|
||||
const BASE_SCALE = 81;
|
||||
|
||||
export function draw_tile (x, y, size = BASE_SCALE) {
|
||||
graphics.fill_rect(x, y, size, size);
|
||||
}
|
||||
|
||||
export function set_color (color) {
|
||||
ctx.fillStyle = color;
|
||||
ctx.strokeStyle = color;
|
||||
function checkerboard (x, y) {
|
||||
return ((x ^ y) & 1) === 0;
|
||||
}
|
||||
|
||||
export function clear (color = "#000") {
|
||||
set_color(color);
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
export function draw_floor (start_x, start_y, scale, invert = false) {
|
||||
iter_2d(range(0, world.BOX_SIZE - 1), (x, y) => {
|
||||
if (checkerboard(x, y) !== invert) {
|
||||
graphics.set_color("#aaa");
|
||||
} else {
|
||||
graphics.set_color("#888");
|
||||
}
|
||||
const [visual_x, visual_y] = project_pos(x, y, scale);
|
||||
draw_tile(start_x + visual_x, start_y + visual_y, scale);
|
||||
});
|
||||
}
|
||||
|
||||
export function fill_rect (x, y, w, h) {
|
||||
ctx.fillRect(x, y, w, h);
|
||||
export function draw_world (box, start_x = 0, start_y = 0, scale = BASE_SCALE) {
|
||||
draw_floor(start_x, start_y, scale, !checkerboard(start_x, start_y));
|
||||
|
||||
iter_2d(range(0, world.BOX_SIZE - 1), (x, y) => {
|
||||
const tile = world.get_tile(box, x, y);
|
||||
const [visual_x, visual_y] = project_pos(x, y, scale);
|
||||
|
||||
switch (tile.type) {
|
||||
case "box": {
|
||||
draw_world(tile.box, start_x + visual_x, start_y + visual_y, scale / world.BOX_SIZE);
|
||||
break;
|
||||
}
|
||||
case "paint": {
|
||||
graphics.set_color(tile.color);
|
||||
draw_tile(start_x + visual_x, start_y + visual_y, scale);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
entity.for_each((entity, id) => {
|
||||
if (entity.box !== box) return;
|
||||
graphics.set_color(entity.color);
|
||||
const [visual_x, visual_y] = project_pos(entity.x, entity.y, scale);
|
||||
draw_tile(start_x + visual_x, start_y + visual_y, scale);
|
||||
});
|
||||
}
|
||||
|
||||
export function render (target_canvas) {
|
||||
target_canvas.drawImage(canvas, 0, 0);
|
||||
function project_pos (x, y, scale) {
|
||||
return [x, y].map(a => a * scale);
|
||||
}
|
||||
|
||||
export function render () {
|
||||
graphics.render(canvas_context);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
export function set_size (w, h) {
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
}
|
||||
|
||||
export function set_color (color) {
|
||||
ctx.fillStyle = color;
|
||||
ctx.strokeStyle = color;
|
||||
}
|
||||
|
||||
export function clear (color = "#000") {
|
||||
set_color(color);
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
|
||||
export function fill_rect (x, y, w, h) {
|
||||
ctx.fillRect(x, y, w, h);
|
||||
}
|
||||
|
||||
export function render (target_canvas) {
|
||||
target_canvas.drawImage(canvas, 0, 0);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
export function on_press (key, callback) {
|
||||
document.body.addEventListener("keydown", e => {
|
||||
if (e.key === key) callback(key);
|
||||
});
|
||||
}
|
||||
|
||||
export function on_release (key, callback) {
|
||||
document.body.addEventListener("keyup", e => {
|
||||
if (e.key === key) callback(key);
|
||||
});
|
||||
}
|
30
js/main.mjs
30
js/main.mjs
|
@ -1,27 +1,15 @@
|
|||
import * as graphics from "./graphics.mjs";
|
||||
import { range } from "./utils/range.mjs";
|
||||
import { BOX_SIZE, get_tile } from "./world.mjs";
|
||||
import { get_player_box } from "./player.mjs";
|
||||
|
||||
const canvas = document.getElementById("canvas");
|
||||
const canvas_context = canvas.getContext("2d");
|
||||
graphics.set_size(canvas.width, canvas.height);
|
||||
|
||||
const BOX_SIZE = 9;
|
||||
const SCALE = 64;
|
||||
|
||||
function draw_tile (x, y) {
|
||||
graphics.fill_rect(x * SCALE, y * SCALE, SCALE, SCALE);
|
||||
function render (timestamp) {
|
||||
graphics.clear();
|
||||
graphics.draw_world(get_player_box());
|
||||
graphics.render();
|
||||
|
||||
requestAnimationFrame(render);
|
||||
}
|
||||
|
||||
function draw_box_floor () {
|
||||
graphics.clear("#888");
|
||||
graphics.set_color("#aaa");
|
||||
for (const x in range(0, BOX_SIZE)) {
|
||||
for (const y in range(0, BOX_SIZE)) {
|
||||
if (((x ^ y) & 1) === 0)
|
||||
draw_tile(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw_box_floor();
|
||||
graphics.render(canvas_context);
|
||||
requestAnimationFrame(render);
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import * as world from "./world.mjs";
|
||||
import { on_press } from "./input.mjs";
|
||||
import * as entity from "./entity.mjs";
|
||||
|
||||
|
||||
|
||||
const player = entity.create(world.get_root(), 2, 1, "#080");
|
||||
|
||||
|
||||
|
||||
export function move (d_x, d_y) {
|
||||
entity.move(player, d_x, d_y);
|
||||
}
|
||||
|
||||
on_press("ArrowLeft", _ => {
|
||||
move(-1, 0);
|
||||
});
|
||||
on_press("ArrowRight", _ => {
|
||||
move(1, 0);
|
||||
});
|
||||
on_press("ArrowUp", _ => {
|
||||
move(0, -1);
|
||||
});
|
||||
on_press("ArrowDown", _ => {
|
||||
move(0, 1);
|
||||
});
|
||||
|
||||
on_press(" ", _ => {
|
||||
world.set_tile(player.box, player.x, player.y, {
|
||||
type: "box",
|
||||
box: world.create_box(player.box),
|
||||
});
|
||||
});
|
||||
|
||||
function clear_tile (x, y) {
|
||||
world.set_tile(player.box, x, y, {
|
||||
type: "empty",
|
||||
});
|
||||
}
|
||||
|
||||
on_press("Backspace", _ => {
|
||||
clear_tile(player.x, player.y);
|
||||
clear_tile(player.x + 1, player.y);
|
||||
clear_tile(player.x - 1, player.y);
|
||||
});
|
||||
|
||||
export function get_player_box () {
|
||||
return player.box;
|
||||
}
|
||||
|
||||
function set_painting_key (key, color) {
|
||||
on_press(key, _ => {
|
||||
world.set_tile(player.box, player.x, player.y, {
|
||||
type: "paint",
|
||||
color,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
set_painting_key("q", "#f00");
|
||||
set_painting_key("w", "#0f0");
|
||||
set_painting_key("e", "#00f");
|
||||
set_painting_key("a", "#ff0");
|
||||
set_painting_key("s", "#0ff");
|
||||
set_painting_key("d", "#f0f");
|
|
@ -1,16 +1,8 @@
|
|||
export {
|
||||
is_in_range,
|
||||
clamp,
|
||||
range,
|
||||
};
|
||||
|
||||
|
||||
|
||||
function is_in_range (number, min, max) {
|
||||
export function is_in_range (number, min, max) {
|
||||
return number >= min && number <= max;
|
||||
}
|
||||
|
||||
function clamp (number, min, max) {
|
||||
export function clamp (number, min, max) {
|
||||
return number < min
|
||||
? min
|
||||
: number > max
|
||||
|
@ -18,10 +10,18 @@ function clamp (number, min, max) {
|
|||
: number;
|
||||
}
|
||||
|
||||
function range (start, end) {
|
||||
export function range (start, end) {
|
||||
const result = [];
|
||||
for (let n = start; n <= end; n++) {
|
||||
result.push(n);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function iter_2d (range_1d, callback) {
|
||||
for (const y in range_1d) {
|
||||
for (const x in range_1d) {
|
||||
callback(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
export const BOX_SIZE = 9;
|
||||
export const CENTER = Math.floor(BOX_SIZE / 2);
|
||||
|
||||
|
||||
|
||||
function create_world (size) {
|
||||
return {
|
||||
tiles: Array(BOX_SIZE).fill(0).map(_ => Array(BOX_SIZE).fill(0)),
|
||||
};
|
||||
}
|
||||
const world = create_world(BOX_SIZE);
|
||||
|
||||
export function set_tile (world, x, y, tile) {
|
||||
if (out_of_bounds(x, y)) return;
|
||||
world.tiles[x][y] = tile;
|
||||
}
|
||||
|
||||
export function get_tile (world, x, y) {
|
||||
if (out_of_bounds(x, y)) return { type: "out_of_bounds" };
|
||||
return world.tiles[x][y];
|
||||
}
|
||||
|
||||
export function create_box (parent) {
|
||||
return {
|
||||
...create_world(BOX_SIZE),
|
||||
parent,
|
||||
};
|
||||
}
|
||||
|
||||
export function get_root () {
|
||||
return world;
|
||||
}
|
||||
|
||||
export function out_of_bounds (x, y) {
|
||||
return x < 0 || x >= BOX_SIZE ||
|
||||
y < 0 || y >= BOX_SIZE
|
||||
}
|
Loading…
Reference in New Issue