Skip to content

Commit

Permalink
Add snake
Browse files Browse the repository at this point in the history
  • Loading branch information
shiro committed Apr 2, 2024
1 parent 2f3aea2 commit 5c8a18b
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 47 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"prettier-plugin-tailwindcss": "0.5.13",
"remark-frontmatter": "5.0.0",
"remark-gfm": "4.0.0",
"solid-devtools": "0.29.3",
"solid-devtools": "0.30.0",
"ts-node": "10.9.2",
"vite-plugin-solid-svg": "0.8.1"
}
Expand Down
54 changes: 23 additions & 31 deletions src/about/AboutSite.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { css } from "@linaria/core";
import cn from "classnames";
import { Component, JSX } from "solid-js";
import LabeledBox from "~/about/LabeledBox";
import SnakeGame from "~/about/SnakeGame";
import StatsBar from "~/about/StatsBar";
import StatusButton from "~/about/StatusButton";
import Icon from "~/components/Icon";
Expand Down Expand Up @@ -44,28 +46,25 @@ const AboutSite: Component<Props> = (props) => {
class="flex flex-col gap-2 s:flex-row"
style={{ "grid-area": "title" }}>
<div class="text-nowrap text-h3">Level 21</div>
<div class="xs:text-h3 text-nowrap text-h3">
<div class="text-nowrap text-h3 xs:text-h3">
Legendary Software Engineer
</div>
</div>
<LabeledBox
label="general"
class="flex flex-col justify-stretch"
style={{ "grid-area": "general" }}>
<ul class="flex flex-col gap-1">
<li class="flex gap-2">
<LabeledBox label="general" style={{ "grid-area": "general" }}>
<ul class="flex min-h-full flex-col justify-center gap-1">
<li class="flex gap-2 text-nowrap xs:text-h3 s:text-body">
<IconText icon="github" />
<a href="https://github.com/shiro">github.com/shiro</a>
</li>
<li class="flex gap-2">
<li class="flex gap-2 text-nowrap xs:text-h3 s:text-body">
<IconText icon="email" />
<a href="https://github.com/shiro">[email protected]</a>
</li>
<li class="flex gap-2">
<li class="flex gap-2 text-nowrap xs:text-h3 s:text-body">
<IconText icon="globe" />
<a href="https://github.com/shiro">usagi.io</a>
</li>
<li class="flex gap-2">
<li class="flex gap-2 text-nowrap xs:text-h3 s:text-body">
<IconText icon="house" />
<span>Tokyo</span>
</li>
Expand All @@ -85,20 +84,10 @@ const AboutSite: Component<Props> = (props) => {
along the way.
</p>
</LabeledBox>
</div>
);
};

const LabeledBox: Component<any> = (props: any) => {
const { style, children, label } = $destructure(props);
return (
<div class="relative mt-[14px]" style={style}>
<span class="absolute left-2 top-[-14px] bg-colors-special-bg pl-2 pr-2 text-colors-text-300a">
{label}
</span>
<div class="min-h-full rounded-md border-2 border-colors-text-100a p-8">
{children}
</div>
<SnakeGame
style={{ "grid-area": "snake" }}
class="justify-self-stretch"
/>
</div>
);
};
Expand All @@ -110,24 +99,27 @@ const _AboutSite = css`
"picture title title emtpy" auto
"picture general about about" auto
"stats general about about" auto
"snake snake snake snake" auto
/ auto auto auto auto;
${breakpointUntil("m")} {
grid-template:
"picture name " auto
"picture title " auto
"picture general " auto
"stats general " auto
"about about " auto
/ auto 1fr auto auto;
"picture name name " auto
"picture title title" auto
"picture general snake" auto
"stats general snake" auto
"about about about" auto
/ auto auto 1fr;
}
${breakpointUntil("s")} {
justify-items: stretch;
grid-template:
"name name" auto
"picture title" auto
"picture general" auto
"stats general" auto
"about about " auto
/ auto 1fr auto auto;
"snake snake " auto
/ auto 1fr;
}
`;

Expand Down
25 changes: 25 additions & 0 deletions src/about/LabeledBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Component, JSX } from "solid-js";
import cn from "classnames";

interface Props {
children?: JSX.Element;
style?: JSX.CSSProperties;
label: string;
class?: string;
}

const LabeledBox: Component<Props> = (props) => {
const { style, children, label, class: $class } = $destructure(props);
return (
<div class={cn("relative mt-[14px] flex", $class)} style={style}>
<span class="absolute left-2 top-[-14px] bg-colors-special-bg pl-2 pr-2 text-colors-text-300a">
{label}
</span>
<div class="min-w-full overflow-hidden rounded-md border-2 border-colors-text-100a p-8">
{children}
</div>
</div>
);
};

export default LabeledBox;
193 changes: 193 additions & 0 deletions src/about/SnakeGame.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import { JSX, Component } from "solid-js";
import { css } from "@linaria/core";
import cn from "classnames";
import LabeledBox from "~/about/LabeledBox";
import { color } from "~/style/commonStyle";

type Pos = [number, number];
const TICK_SPEED = 90;

var MAX_16BIT_SIGNED = 32767;
const getKey = (x: number, y: number) => {
x += MAX_16BIT_SIGNED;
y += MAX_16BIT_SIGNED;
return (x << 16) | y;
};

function convertPixelsToRem(px: number) {
return (
px /
parseFloat(
getComputedStyle(document.documentElement).fontSize.replace("px", "")
)
);
}

interface Props {
style?: JSX.CSSProperties;
class?: string;
}

var svgns = "http://www.w3.org/2000/svg";

const SnakeGame: Component<Props> = (props) => {
const { style, class: $class } = $destructure(props);

let canvas!: SVGSVGElement;

$mount(() => {
let map: Record<number, number> = {};

const setNode = (x: number, y: number, type: number) => {
var c = document.createElementNS(svgns, "rect");
c.setAttribute(
"class",
type == 1 ? "fill-colors-primary-800" : "fill-colors-secondary-800"
);
c.setAttribute("x", `${x}rem`);
c.setAttribute("y", `${y}rem`);
c.setAttribute("width", "1rem");
c.setAttribute("height", "1rem");
canvas.appendChild(c);
return c;
};

let h = 20;
let w = 20;

const randomPos = (): Pos => [
Math.round(Math.random() * (w - 1)),
Math.round(Math.random() * (h - 1)),
];

const moveFruit = () => {
fruitEl?.remove();
while (map[getKey(...fruitPos)]) {
fruitPos = [
Math.round(Math.random() * (w - 1)),
Math.round(Math.random() * (h - 1)),
];
}
fruitEl = setNode(...fruitPos, 2);
map[getKey(...fruitPos)] = 2;
};

let snake: { el: Element; pos: Pos }[] = [];
let pos = randomPos();
let fruitPos: Pos;
let fruitEl: Element | null = null;

const reset = () => {
canvas.innerHTML = "";
snake = [];
map = {};
pos = randomPos();
snake.push({ el: setNode(...pos, 1), pos });
map[getKey(...pos)] = 1;
fruitPos = randomPos();
moveFruit();
};

reset();
let interval = setInterval(loop, TICK_SPEED);
let resetTimeout: NodeJS.Timeout;
const handleResize = (entries: ResizeObserverEntry[]) => {
const { width, height } = entries[0].contentRect;
const newW = Math.floor(convertPixelsToRem(width) - 1);
const newH = Math.floor(convertPixelsToRem(height) - 1);
if (newW < 1 || newH < 1) return;
if (w == newW && h == newH) return;
w = newW;
h = newH;
clearInterval(interval);
clearTimeout(resetTimeout);
canvas.innerHTML = "";
resetTimeout = setTimeout(() => {
reset();
interval = setInterval(loop, TICK_SPEED);
}, 1000);
};
const resizeObserver = new ResizeObserver(handleResize);
resizeObserver.observe(canvas);

function loop() {
const available = [
["down", 0, 1],
["up", 0, -1],
["right", 1, 0],
["left", -1, 0],
]
.filter(([label, x, y]) => {
const newPos: Pos = [pos[0] + (x as number), pos[1] + (y as number)];
return (
newPos[0] >= 0 &&
newPos[0] < w &&
newPos[1] >= 0 &&
newPos[1] < h &&
map[getKey(...newPos)] != 1
);
})
.reduce(
(acc, [direction, ...pos]) => ({ ...acc, [direction]: pos }),
{}
) as Record<string, Pos>;

if (pos[0] < fruitPos[0] && available["right"]) {
++pos[0];
} else if (pos[0] > fruitPos[0] && available["left"]) {
--pos[0];
} else if (pos[1] < fruitPos[1] && available["down"]) {
++pos[1];
} else if (pos[1] > fruitPos[1] && available["up"]) {
--pos[1];
} else if (Object.keys(available).length) {
var keys = Object.keys(available);
const direction = available[keys[(keys.length * Math.random()) << 0]];

pos[0] += direction[0];
pos[1] += direction[1];
} else {
reset();
return;
}

snake.push({ el: setNode(...pos, 1), pos: [...pos] });
map[getKey(...pos)] = 1;

if (pos[0] == fruitPos[0] && pos[1] == fruitPos[1]) {
moveFruit();
} else {
const tail = snake.splice(0, 1)[0];
delete map[getKey(...tail.pos)];
tail.el.remove();
}
}

$cleanup(() => {
clearInterval(interval);
clearTimeout(resetTimeout);
});
});

return (
<LabeledBox class={cn(_SnakeGame, $class, "")} label="snake" style={style}>
<div class="relative h-full min-h-20 w-full overflow-hidden">
<div class="absolute left-0 top-0 h-full w-full">
<svg class={cn(Board, "h-full w-full")} ref={canvas} />
</div>
</div>
</LabeledBox>
);
};

const _SnakeGame = css``;

const Board = css`
background: repeating-conic-gradient(
${color("colors-primary-100")} 0 90deg,
transparent 0 180deg
)
0 0/32px 32px;
`;

export default SnakeGame;
30 changes: 15 additions & 15 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1586,13 +1586,13 @@
resolved "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz#719df7fb41766bc143369eaa0dd56d8dc87c9958"
integrity sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==

"@solid-devtools/debugger@^0.23.3":
version "0.23.3"
resolved "https://registry.npmjs.org/@solid-devtools/debugger/-/debugger-0.23.3.tgz#f56fa5952460db45a146673eda764b349474381d"
integrity sha512-VrgswTjb2FyHxQJp5y5u7OaJ2k1R14LYlAOX/1rDZrGHWKdGYCaWHGzxI7C8AExtMP+LS+WOxy0uXMPQpoAD2g==
"@solid-devtools/debugger@^0.23.4":
version "0.23.4"
resolved "https://registry.npmjs.org/@solid-devtools/debugger/-/debugger-0.23.4.tgz#07b5e6c9cb08ef961c12efd1e5924a25cdf78d62"
integrity sha512-EfTB1Eo313wztQYGJ4Ec/wE70Ay2d603VCXfT3RlyqO5QfLrQGRHX5NXC07hJpQTJJJ3tbNgzO7+ZKo76MM5uA==
dependencies:
"@nothing-but/utils" "~0.12.0"
"@solid-devtools/shared" "^0.13.1"
"@solid-devtools/shared" "^0.13.2"
"@solid-primitives/bounds" "^0.0.118"
"@solid-primitives/cursor" "^0.0.112"
"@solid-primitives/event-bus" "^1.0.8"
Expand All @@ -1604,10 +1604,10 @@
"@solid-primitives/static-store" "^0.0.5"
"@solid-primitives/utils" "^6.2.1"

"@solid-devtools/shared@^0.13.1":
version "0.13.1"
resolved "https://registry.npmjs.org/@solid-devtools/shared/-/shared-0.13.1.tgz#f672ec4c96d77b6f91e9b0e598dd3ae3abf4a6fb"
integrity sha512-qaAcZF47FFr4alVQSy5ooLy7mMt4MMDxSHw52heY1oCut8yfXDrnLcYDONabfoin2WYIwsQpjYhryHgjtB0uDg==
"@solid-devtools/shared@^0.13.2":
version "0.13.2"
resolved "https://registry.npmjs.org/@solid-devtools/shared/-/shared-0.13.2.tgz#05cebc0c8341ce61a1f2049cf5cb198d4eb00429"
integrity sha512-Y4uaC4EfTVwBR537MZwfaY/eiWAh+hW4mbtnwNuUw/LFmitHSkQhNQTUlLQv/S0chtwrYWQBxvXos1dC7e8R9g==
dependencies:
"@solid-primitives/event-bus" "^1.0.8"
"@solid-primitives/event-listener" "^2.3.0"
Expand Down Expand Up @@ -6646,16 +6646,16 @@ smob@^1.0.0:
resolved "https://registry.npmjs.org/smob/-/smob-1.4.1.tgz#66270e7df6a7527664816c5b577a23f17ba6f5b5"
integrity sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==

solid-devtools@0.29.3:
version "0.29.3"
resolved "https://registry.npmjs.org/solid-devtools/-/solid-devtools-0.29.3.tgz#62c563ab5c340d02168b63017c204f5632aad537"
integrity sha512-9j3VxVbEoC54ML22gAMytR8ZS1nk9xKatsWziKSkI4c/Bcyh4sxQBGESHuXSLs9xaxpyGVTmFl3hknoxEpKzlA==
solid-devtools@0.30.0:
version "0.30.0"
resolved "https://registry.npmjs.org/solid-devtools/-/solid-devtools-0.30.0.tgz#a04869a81952bdd615e85910e0862371c5082050"
integrity sha512-icQr7jEIZ0rgJcIAJOaIIKOzq+BDx9G1Ok+mPJBaxI0RPkeea3xJ7CqrKNQT8oYB5DWXgdu1r8D2SA29+bxGSw==
dependencies:
"@babel/core" "^7.23.3"
"@babel/plugin-syntax-typescript" "^7.23.3"
"@babel/types" "^7.23.3"
"@solid-devtools/debugger" "^0.23.3"
"@solid-devtools/shared" "^0.13.1"
"@solid-devtools/debugger" "^0.23.4"
"@solid-devtools/shared" "^0.13.2"

solid-js@^1.8.16:
version "1.8.16"
Expand Down

0 comments on commit 5c8a18b

Please sign in to comment.