Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/images/commit4-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/components/octo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const OCTO_LANES = [

export const Octo = () => {
const { textures } = useAtlas();
const { lane, lanes, fireLane, gameState } = useGame();
const { lane, lanes, firingLane, gameState } = useGame();

const isActive = gameState !== "off";
const blast = lanes.findIndex((laneState) => {
Expand All @@ -64,7 +64,7 @@ export const Octo = () => {
/>
<pixiSprite
texture={textures[cell.tentacles]}
alpha={index === fireLane ? SPRITE_ALPHA_ACTIVE : SPRITE_ALPHA_DISABLED}
alpha={index === firingLane ? SPRITE_ALPHA_ACTIVE : SPRITE_ALPHA_DISABLED}
/>
<pixiSprite
texture={textures[cell.body]}
Expand Down
84 changes: 54 additions & 30 deletions src/context/game.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createContext, useContext, useState, useRef, useEffect, useCallback, type ReactNode } from "react";
import { useCounter, useEventCallback, useEventListener } from "usehooks-ts";
import { isNil, isNotNil, negate, randomInt } from "es-toolkit";
import { clamp, isNil, isNotNil, negate, randomInt } from "es-toolkit";

import { ACTOR_CELL_IDLE, ACTOR_CELL_OFFSET, ACTOR_CELL_LAST, BUG_EXPLOSION_MS } from "@/utils/const";

Expand All @@ -16,11 +16,14 @@ type LaneState = {

type GameValue = {
gameState: GameState;
fireLane: number | null;
firingLane: number | null;
lane: number;
lanes: LaneState[];
score: number;
handleStart: () => void;
handleFire: () => void;
handleMoveLeft: () => void;
handleMoveRight: () => void;
};

type GameProviderProps = {
Expand Down Expand Up @@ -48,7 +51,7 @@ const GameContext = createContext<GameValue | null>(null);

export const GameProvider = ({ children }: GameProviderProps) => {
const frame = useRef<number | null>(null);
const fireEndsAt = useRef<number | null>(null);
const firingEndsAt = useRef<number | null>(null);
const startedAt = useRef(GAME_TICK_START);
const lastBugTick = useRef(GAME_TICK_START);
const lastBulletTick = useRef(GAME_TICK_START);
Expand All @@ -59,17 +62,22 @@ export const GameProvider = ({ children }: GameProviderProps) => {
const [lanes, setLanes] = useState(GAME_LANES);
const [gameState, setGameState] = useState<GameState>("off");
const [lane, setLane] = useState(OCTO_LANE_FIRST);
const [fireLane, setFireLane] = useState<number | null>(null);
const [firingLane, setFiringLane] = useState<number | null>(null);

const handleStart = () => {
if (gameState === "on") {
setGameState("off");
} else {
setGameState("on");
}

reset();
setFiringLane(null);
setLane(OCTO_LANE_FIRST);
setFireLane(null);
setLanes(GAME_LANES);
setGameState("on");
play("game/keyPress");

fireEndsAt.current = null;
firingEndsAt.current = null;
startedAt.current = performance.now();
lastBugTick.current = GAME_TICK_START;
lastBulletTick.current = GAME_TICK_START;
Expand Down Expand Up @@ -157,12 +165,16 @@ export const GameProvider = ({ children }: GameProviderProps) => {
}, []);

const handleFire = () => {
if (lanes[lane].bullet !== ACTOR_CELL_IDLE || isNotNil(lanes[lane].collision)) {
const isActive = lanes[lane].bullet !== ACTOR_CELL_IDLE;

play("game/keyPress");

if (gameState !== "on" || isActive || isNotNil(lanes[lane].collision)) {
return;
}

setFireLane(lane);
fireEndsAt.current = performance.now() + BUG_TICK_MS;
setFiringLane(lane);
firingEndsAt.current = performance.now() + BUG_TICK_MS;

setLanes((current) => {
return current.map((laneState, index) => {
Expand All @@ -174,6 +186,29 @@ export const GameProvider = ({ children }: GameProviderProps) => {
});
};

const handleMove = (offset: number) => {
play("game/keyPress");

if (gameState !== "on") {
return;
}

setFiringLane(null);
firingEndsAt.current = null;

setLane((current) => {
return clamp(current + offset, OCTO_LANE_FIRST, OCTO_LANE_LAST);
});
};

const handleMoveLeft = () => {
handleMove(-OCTO_LANE_OFFSET);
};

const handleMoveRight = () => {
handleMove(OCTO_LANE_OFFSET);
};

useEventListener("keydown", (event) => {
const isNotControlKey = GAME_CONTROL_KEYS.every((key) => {
return key !== event.code;
Expand All @@ -195,28 +230,14 @@ export const GameProvider = ({ children }: GameProviderProps) => {
if (gameState !== "on") {
return;
}

setFireLane(null);
play("game/keyPress");

fireEndsAt.current = null;

if (event.code === "Space") {
handleFire();
}
// Move the octo one lane to the left.
// Stay on the first lane if there is no further cell.
if (event.code === "ArrowLeft") {
setLane((current) => {
return Math.max(OCTO_LANE_FIRST, current - OCTO_LANE_OFFSET);
});
handleMoveLeft();
}
// Move the octo one lane to the right.
// Stay on the last lane if there is no further cell.
if (event.code === "ArrowRight") {
setLane((current) => {
return Math.min(OCTO_LANE_LAST, current + OCTO_LANE_OFFSET);
});
handleMoveRight();
}
});

Expand All @@ -229,9 +250,9 @@ export const GameProvider = ({ children }: GameProviderProps) => {
const bugTick = Math.floor((now - startedAt.current) / BUG_TICK_MS);
const bulletTick = Math.floor((now - startedAt.current) / BULLET_TICK_MS);

if (isNotNil(fireEndsAt.current) && now >= fireEndsAt.current) {
setFireLane(null);
fireEndsAt.current = null;
if (isNotNil(firingEndsAt.current) && now >= firingEndsAt.current) {
setFiringLane(null);
firingEndsAt.current = null;
}

handleCollisions(now);
Expand Down Expand Up @@ -267,11 +288,14 @@ export const GameProvider = ({ children }: GameProviderProps) => {
<GameContext.Provider
value={{
gameState,
fireLane,
firingLane,
lane,
lanes,
score: count,
handleStart,
handleFire,
handleMoveLeft,
handleMoveRight,
}}
>
{children}
Expand Down
15 changes: 2 additions & 13 deletions src/core/commit4.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { Suspense } from "react";
import { useTranslation } from "react-i18next";
import { useIntersectionObserver } from "usehooks-ts";

import { useGame } from "@/context/game";

import { Scene } from "@/core/scene";
import { Layout } from "@/core/layout";
import { Controls } from "@/ui/controls";

export const Commit4 = () => {
const { t } = useTranslation();
const { ref, entry } = useIntersectionObserver();

const { handleStart } = useGame();

const isCaseClip = Boolean(entry?.rootBounds && entry.boundingClientRect.top > entry.rootBounds.bottom);

return (
Expand All @@ -24,13 +19,7 @@ export const Commit4 = () => {
<Scene />
</Suspense>
</div>

<button
type="button"
aria-label={t("game.start")}
className="absolute top-133 left-47.5 z-10 h-19 w-14 cursor-pointer bg-button opacity-0 outline-none active:opacity-100"
onClick={handleStart}
/>
<Controls />
</div>

<div ref={ref} aria-hidden="true" className="pointer-events-none absolute bottom-0 h-px w-full" />
Expand Down
5 changes: 4 additions & 1 deletion src/data/copy.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
"source": "Star or fork the project on <url2>GitHub</url2>."
},
"game": {
"start": "Start/On"
"start": "Start/On",
"moveLeft": "Move left",
"moveRight": "Move right",
"commit": "Commit"
}
}
Binary file modified src/static/atlas/atlas.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions src/static/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@

--background-image-case: url("../images/bg-1@2x.png");
--background-image-screen: url("../images/bg-2@2x.png");
--background-image-button: url("../images/bg-3@2x.png");
--background-image-lcd: url("../images/bg-4@2x.png");
--background-image-button: url("../images/button-1@2x.png");
--background-image-lcd: url("../images/bg-3@2x.png");
--background-image-button-left: url("../images/button-2@2x.png");
--background-image-button-commit: url("../images/button-3@2x.png");
--background-image-button-right: url("../images/button-4@2x.png");
}
Binary file modified src/static/images/bg-3@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed src/static/images/bg-4@2x.png
Binary file not shown.
Binary file added src/static/images/button-1@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/static/images/button-2@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/static/images/button-3@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/static/images/button-4@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions src/ui/controls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useTranslation } from "react-i18next";

import { useGame } from "@/context/game";

export const Controls = () => {
const { t } = useTranslation();
const { handleStart, handleMoveLeft, handleMoveRight, handleFire } = useGame();

return (
<>
<button
type="button"
aria-label={t("game.moveLeft")}
className="absolute top-128.75 left-17 z-10 h-26.25 w-18 cursor-pointer bg-button-left opacity-0 outline-none active:opacity-100"
onClick={handleMoveLeft}
/>
<button
type="button"
aria-label={t("game.start")}
className="absolute top-133 left-47.5 z-10 h-19 w-14 cursor-pointer bg-button opacity-0 outline-none active:opacity-100"
onClick={handleStart}
/>
<button
type="button"
aria-label={t("game.commit")}
className="absolute top-128.75 left-102.25 z-10 h-26.25 w-18 cursor-pointer bg-button-commit opacity-0 outline-none active:opacity-100"
onClick={handleFire}
/>
<button
type="button"
aria-label={t("game.moveRight")}
className="absolute top-128.75 left-130.75 z-10 h-26.25 w-18 cursor-pointer bg-button-right opacity-0 outline-none active:opacity-100"
onClick={handleMoveRight}
/>
</>
);
};
Binary file modified static/og-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading