Compare commits
22 Commits
59f6fa80e0
...
0124b69440
| Author | SHA1 | Date | |
|---|---|---|---|
| 0124b69440 | |||
| e4f6e1899d | |||
| e6cee7c2fa | |||
| 7e5bc73b0d | |||
| 9d573b4b7d | |||
| 552c544014 | |||
| 50c94b8e1e | |||
| 24dd8b1e99 | |||
| e3710f7152 | |||
| cac5ebff3a | |||
| 5480e2921b | |||
| 27d47764f8 | |||
| 6edbaa7a68 | |||
| 9bc6cfbf7c | |||
| 600cf1c290 | |||
| 7a5ac26f45 | |||
| 44e7586baf | |||
| 4efc60861b | |||
| 58038a6e86 | |||
| 5fc503778d | |||
| 369fbec9e5 | |||
| ac944e4c87 |
9
.dockerignore
Normal file
9
.dockerignore
Normal file
@@ -0,0 +1,9 @@
|
||||
node_modules
|
||||
Dockerfile
|
||||
Makefile
|
||||
README.md
|
||||
.output
|
||||
.vinxi
|
||||
.git
|
||||
.gitignore
|
||||
.dockerignore
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
assets/** filter=lfs diff=lfs merge=lfs -text
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
.output
|
||||
.vinxi
|
||||
|
||||
# ---> Node
|
||||
# Logs
|
||||
logs
|
||||
|
||||
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM node:22-alpine
|
||||
WORKDIR /app
|
||||
EXPOSE 3000
|
||||
|
||||
COPY package.json ./
|
||||
RUN --mount=type=cache,target=/root/.npm npm install
|
||||
|
||||
COPY . .
|
||||
RUN --mount=type=cache,target=/app/.vinxi npm run build
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
40
Dockerfile.stages
Normal file
40
Dockerfile.stages
Normal file
@@ -0,0 +1,40 @@
|
||||
FROM arm64v8/node:22 AS base
|
||||
ENV SHARP_IGNORE_GLOBAL_CLI_BINARIES=1
|
||||
ENV VIPS_DISABLE_DEPS=1
|
||||
ENV PNPM_SCRIPT_RUNNER_ALLOW_BUILD=true
|
||||
RUN corepack enable
|
||||
WORKDIR /app
|
||||
|
||||
FROM base AS builder
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile --dangerously-allow-all-builds
|
||||
COPY . .
|
||||
# ^ copy source code
|
||||
RUN --mount=type=cache,target=/app/.vinxi pnpm run build
|
||||
# ^ produces .output (build artifact) and .vinxi (build cache)
|
||||
|
||||
|
||||
FROM base AS production_builder
|
||||
RUN apt-get update && apt-get install -y jq
|
||||
COPY --from=builder /app/package.json ./
|
||||
RUN jq 'del(.devDependencies)' package.json > package.prod.json
|
||||
COPY --from=builder /app/pnpm-lock.yaml ./
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile --dangerously-allow-all-builds
|
||||
|
||||
# Prod image
|
||||
FROM arm64v8/node:22-alpine
|
||||
ENV SHARP_IGNORE_GLOBAL_CLI_BINARIES=1
|
||||
ENV VIPS_DISABLE_DEPS=1
|
||||
ENV PNPM_SCRIPT_RUNNER_ALLOW_BUILD=true
|
||||
RUN corepack enable
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=production_builder /app/package.prod.json ./package.json
|
||||
COPY --from=production_builder /app/pnpm-lock.yaml ./pnpm-lock.yaml
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||
|
||||
COPY --from=builder /app/.output ./.output
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
11
Makefile
11
Makefile
@@ -1,8 +1,13 @@
|
||||
SHELL := /bin/bash
|
||||
|
||||
build:
|
||||
pnpm install
|
||||
pnpm run build
|
||||
sudo docker buildx build --load \
|
||||
--cache-from=type=local,src=/var/cache/docker-build \
|
||||
--cache-to=type=local,dest=/var/cache/docker-build \
|
||||
--platform linux/arm64 \
|
||||
--progress=plain \
|
||||
--tag games .
|
||||
|
||||
|
||||
start:
|
||||
pnpm run start
|
||||
sudo docker run -p $(PORT):3000 -t games
|
||||
|
||||
BIN
assets/sources/Card_back_01.svg
LFS
Normal file
BIN
assets/sources/Card_back_01.svg
LFS
Normal file
Binary file not shown.
BIN
assets/sources/Vector-Cards-Version-3.2/AUTHORS.txt
LFS
Normal file
BIN
assets/sources/Vector-Cards-Version-3.2/AUTHORS.txt
LFS
Normal file
Binary file not shown.
BIN
assets/sources/Vector-Cards-Version-3.2/CHANGELOG_(PROJECT_HISTORY).txt
LFS
Normal file
BIN
assets/sources/Vector-Cards-Version-3.2/CHANGELOG_(PROJECT_HISTORY).txt
LFS
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
assets/sources/Vector-Cards-Version-3.2/LGPL 3.0 - LICENSING INFORMATION.txt
LFS
Normal file
BIN
assets/sources/Vector-Cards-Version-3.2/LGPL 3.0 - LICENSING INFORMATION.txt
LFS
Normal file
Binary file not shown.
BIN
assets/sources/Vector-Cards-Version-3.2/QUESTIONS & ANSWERS (Please Read).html
LFS
Normal file
BIN
assets/sources/Vector-Cards-Version-3.2/QUESTIONS & ANSWERS (Please Read).html
LFS
Normal file
Binary file not shown.
1
assets/views/back.svg
Symbolic link
1
assets/views/back.svg
Symbolic link
@@ -0,0 +1 @@
|
||||
../sources/Card_back_01.svg
|
||||
1
assets/views/cards
Symbolic link
1
assets/views/cards
Symbolic link
@@ -0,0 +1 @@
|
||||
../sources/Vector-Cards-Version-3.2/FACES (BORDERED)/STANDARD BORDERED/Single Cards (One Per FIle)/
|
||||
@@ -1,12 +1,14 @@
|
||||
{
|
||||
"name": "games",
|
||||
"type": "module",
|
||||
"version": "0.0.2",
|
||||
"scripts": {
|
||||
"dev": "vinxi dev",
|
||||
"build": "vinxi build",
|
||||
"start": "vinxi start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@solidjs/router": "^0.15.3",
|
||||
"@solidjs/start": "^1.1.0",
|
||||
"solid-js": "^1.9.5",
|
||||
"vinxi": "^0.5.7"
|
||||
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@@ -8,6 +8,9 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@solidjs/router':
|
||||
specifier: ^0.15.3
|
||||
version: 0.15.3(solid-js@1.9.7)
|
||||
'@solidjs/start':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.7(solid-js@1.9.7)(vinxi@0.5.8(@netlify/blobs@9.1.2)(@types/node@24.1.0)(db0@0.3.2)(ioredis@5.6.1)(jiti@2.5.1)(terser@5.43.1))(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.1)(terser@5.43.1))
|
||||
@@ -883,6 +886,11 @@ packages:
|
||||
resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@solidjs/router@0.15.3':
|
||||
resolution: {integrity: sha512-iEbW8UKok2Oio7o6Y4VTzLj+KFCmQPGEpm1fS3xixwFBdclFVBvaQVeibl1jys4cujfAK5Kn6+uG2uBm3lxOMw==}
|
||||
peerDependencies:
|
||||
solid-js: ^1.8.6
|
||||
|
||||
'@solidjs/start@1.1.7':
|
||||
resolution: {integrity: sha512-30nUFzCpCVH7ORtHlO4ZE+VLG3g3EP+x+ceLLJBFRXIVuFQ1p203xZvVCXWqUPydtK78O5w3nIkWA/tLtF0Ybg==}
|
||||
peerDependencies:
|
||||
@@ -3977,6 +3985,10 @@ snapshots:
|
||||
|
||||
'@sindresorhus/merge-streams@2.3.0': {}
|
||||
|
||||
'@solidjs/router@0.15.3(solid-js@1.9.7)':
|
||||
dependencies:
|
||||
solid-js: 1.9.7
|
||||
|
||||
'@solidjs/start@1.1.7(solid-js@1.9.7)(vinxi@0.5.8(@netlify/blobs@9.1.2)(@types/node@24.1.0)(db0@0.3.2)(ioredis@5.6.1)(jiti@2.5.1)(terser@5.43.1))(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.1)(terser@5.43.1))':
|
||||
dependencies:
|
||||
'@tanstack/server-functions-plugin': 1.121.21(vite@6.3.5(@types/node@24.1.0)(jiti@2.5.1)(terser@5.43.1))
|
||||
|
||||
60
src/app.css
60
src/app.css
@@ -0,0 +1,60 @@
|
||||
html {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Trebuchet MS", "Lucida Sans Unicode", "Lucida Grande",
|
||||
"Lucida Sans", Arial, sans-serif;
|
||||
color: white;
|
||||
height: 100%;
|
||||
}
|
||||
body::before {
|
||||
z-index: -1;
|
||||
content: "";
|
||||
font-size: 28px;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
background: radial-gradient(rgb(24, 82, 65), rgb(1, 42, 16));
|
||||
}
|
||||
|
||||
.full {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.free {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.fixed-br {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.clear {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
46
src/app.tsx
46
src/app.tsx
@@ -1,22 +1,36 @@
|
||||
import { createSignal } from "solid-js";
|
||||
import "./app.css";
|
||||
import { Router } from "@solidjs/router";
|
||||
import { FileRoutes } from "@solidjs/start/router";
|
||||
import { Suspense } from "solid-js";
|
||||
import pkg from "~/../package.json";
|
||||
|
||||
const Version = () => (
|
||||
<div class="full free clear">
|
||||
<span
|
||||
style={{
|
||||
margin: "5px",
|
||||
"font-size": "0.8rem",
|
||||
"font-family": "monospace",
|
||||
"pointer-events": "all",
|
||||
}}
|
||||
class="fixed-br"
|
||||
>
|
||||
v{pkg.version}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default function App() {
|
||||
const [count, setCount] = createSignal(0);
|
||||
|
||||
return (
|
||||
<main>
|
||||
<h1>Hello world!</h1>
|
||||
<button class="increment" onClick={() => setCount(count() + 1)} type="button">
|
||||
Clicks: {count()}
|
||||
</button>
|
||||
<p>
|
||||
Visit{" "}
|
||||
<a href="https://start.solidjs.com" target="_blank">
|
||||
start.solidjs.com
|
||||
</a>{" "}
|
||||
to learn how to build SolidStart apps.
|
||||
</p>
|
||||
</main>
|
||||
<Router
|
||||
root={(props) => (
|
||||
<>
|
||||
<Suspense>{props.children}</Suspense>
|
||||
<Version />
|
||||
</>
|
||||
)}
|
||||
>
|
||||
<FileRoutes />
|
||||
</Router>
|
||||
);
|
||||
}
|
||||
|
||||
11
src/cards.ts
11
src/cards.ts
@@ -1,11 +0,0 @@
|
||||
export type Suit = "hearts" | "diamonds" | "spades" | "clubs";
|
||||
|
||||
export type Card = {
|
||||
suit: Suit;
|
||||
value: number;
|
||||
};
|
||||
|
||||
export type Pile = Card[];
|
||||
export type Stack = Card[];
|
||||
export type Hand = Card[];
|
||||
export type Board = Card[];
|
||||
49
src/components/Card.tsx
Normal file
49
src/components/Card.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Component, createResource, JSX, Suspense } from "solid-js";
|
||||
import { Card } from "../types/cards";
|
||||
|
||||
const cardToSvgFilename = (card: Card) => {
|
||||
if (card.kind == "joker") {
|
||||
return `JOKER-${card.color == "black" ? "2" : "3"}`;
|
||||
}
|
||||
|
||||
const value =
|
||||
{ ace: 1, jack: 11, queen: 12, king: 13 }[
|
||||
card.rank as "ace" | "jack" | "queen" | "king" // fuck you typescript
|
||||
] ?? (card.rank as number);
|
||||
return `${card.suit.toUpperCase()}-${value}${
|
||||
value >= 11 ? "-" + (card.rank as string).toUpperCase() : ""
|
||||
}`;
|
||||
};
|
||||
|
||||
export default ((props) => {
|
||||
const [svgPath] = createResource(() =>
|
||||
props.face == "down"
|
||||
? // @ts-ignore
|
||||
import("~/../assets/views/back.svg")
|
||||
: import(
|
||||
`~/../assets/views/cards/${cardToSvgFilename(
|
||||
props.card
|
||||
)}.svg`
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<Suspense>
|
||||
<img
|
||||
draggable={false}
|
||||
class={props.class}
|
||||
style={{
|
||||
"border-radius": "5px",
|
||||
...props.style,
|
||||
}}
|
||||
width="100px"
|
||||
src={svgPath()?.default}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}) satisfies Component<{
|
||||
class?: string;
|
||||
style?: JSX.CSSProperties;
|
||||
card: Card;
|
||||
face?: "up" | "down";
|
||||
}>;
|
||||
40
src/components/Game.tsx
Normal file
40
src/components/Game.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { createContext, JSX } from "solid-js";
|
||||
import Card from "./Card";
|
||||
import Hand from "./Hand";
|
||||
import Pile from "./Pile";
|
||||
import { newDeck, shuffle, Hand as THand } from "../types/cards";
|
||||
import { createStore, produce } from "solid-js/store";
|
||||
|
||||
const GameContext = createContext();
|
||||
|
||||
export default () => {
|
||||
const [gameState, setGameState] = createStore({
|
||||
pile: shuffle(newDeck()),
|
||||
hand: [] as THand,
|
||||
});
|
||||
|
||||
return (
|
||||
<GameContext.Provider value={{ gameState, setGameState }}>
|
||||
<div
|
||||
onClick={() => {}}
|
||||
class="full column"
|
||||
style={{ "row-gap": "20px", "font-size": "32px" }}
|
||||
>
|
||||
<div class="full center">
|
||||
<Pile
|
||||
pile={gameState.pile}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={() =>
|
||||
setGameState(
|
||||
produce((state) => {
|
||||
state.hand.push(state.pile.pop()!);
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Hand hand={gameState.hand} />
|
||||
</div>
|
||||
</GameContext.Provider>
|
||||
);
|
||||
};
|
||||
24
src/components/Hand.tsx
Normal file
24
src/components/Hand.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Component, For } from "solid-js";
|
||||
import Card from "./Card";
|
||||
import { Hand } from "../types/cards";
|
||||
|
||||
export default ((props) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
border: "2px dashed white",
|
||||
"border-radius": "12px",
|
||||
margin: "10px",
|
||||
"margin-bottom": "25px",
|
||||
padding: "10px",
|
||||
height: "200px",
|
||||
overflow: "scroll",
|
||||
"scrollbar-width": "none",
|
||||
display: "flex",
|
||||
gap: "5px",
|
||||
}}
|
||||
>
|
||||
<For each={props.hand}>{(card) => <Card card={card} />}</For>
|
||||
</div>
|
||||
);
|
||||
}) satisfies Component<{ hand: Hand }>;
|
||||
44
src/components/Pile.tsx
Normal file
44
src/components/Pile.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Component, For, JSX } from "solid-js";
|
||||
import Card from "./Card";
|
||||
import { Pile } from "../types/cards";
|
||||
|
||||
export default ((props) => {
|
||||
return (
|
||||
<div
|
||||
class="center"
|
||||
style={{ width: "200px", height: "400px", ...props.style }}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
<For each={props.pile}>
|
||||
{(card, i) => (
|
||||
<Card
|
||||
card={card}
|
||||
face="down"
|
||||
style={{
|
||||
position: "absolute",
|
||||
transform: `translate(${i() * 0.8}px, ${
|
||||
-i() * 0.4
|
||||
}px)`,
|
||||
"z-index": 100 - i(),
|
||||
border: `0.1px solid rgb(${
|
||||
60 - i() + Math.random() * 10
|
||||
}, ${60 - i() + Math.random() * 10}, ${
|
||||
60 - i() + Math.random() * 10
|
||||
});`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
}) satisfies Component<{
|
||||
pile: Pile;
|
||||
style?: JSX.CSSProperties;
|
||||
onClick?:
|
||||
| JSX.EventHandlerUnion<
|
||||
HTMLDivElement,
|
||||
MouseEvent,
|
||||
JSX.EventHandler<HTMLDivElement, MouseEvent>
|
||||
>
|
||||
| undefined;
|
||||
}>;
|
||||
0
src/components/Player.tsx
Normal file
0
src/components/Player.tsx
Normal file
0
src/components/Table.tsx
Normal file
0
src/components/Table.tsx
Normal file
@@ -1,4 +1,4 @@
|
||||
import { Board, Card, Hand, Pile, Stack, Suit } from "./cards";
|
||||
import { Board, Card, Hand, Pile, Stack, Suit } from "./types/cards";
|
||||
import { clone } from "./fn";
|
||||
|
||||
const AGG: Suit = "spades";
|
||||
|
||||
5
src/routes/index.tsx
Normal file
5
src/routes/index.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import Game from "../components/Game";
|
||||
|
||||
export default () => {
|
||||
return <Game />;
|
||||
};
|
||||
57
src/types/cards.ts
Normal file
57
src/types/cards.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
const suits = ["heart", "diamond", "spade", "club"] as const;
|
||||
export type Suit = (typeof suits)[number];
|
||||
|
||||
const ranks = [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
"jack",
|
||||
"queen",
|
||||
"king",
|
||||
"ace",
|
||||
] as const;
|
||||
export type Rank = (typeof ranks)[number];
|
||||
|
||||
export type Card =
|
||||
| {
|
||||
kind: "normal";
|
||||
suit: Suit;
|
||||
rank: Rank;
|
||||
}
|
||||
| { kind: "joker"; color: "red" | "black" };
|
||||
|
||||
export type Pile = Card[];
|
||||
export type Stack = Card[];
|
||||
export type Hand = Card[];
|
||||
export type Board = Card[];
|
||||
|
||||
export const newDeck = (withJokers = false): Pile =>
|
||||
suits
|
||||
.map((suit) =>
|
||||
ranks.map((rank) => ({ kind: "normal", suit, rank } as Card))
|
||||
)
|
||||
.flat()
|
||||
.concat(
|
||||
withJokers
|
||||
? [
|
||||
{ kind: "joker", color: "red" },
|
||||
{ kind: "joker", color: "black" },
|
||||
]
|
||||
: []
|
||||
);
|
||||
|
||||
export const shuffle = (cards: Card[]) => {
|
||||
let i = cards.length;
|
||||
while (i > 0) {
|
||||
const j = Math.floor(Math.random() * i);
|
||||
i--;
|
||||
[cards[i], cards[j]] = [cards[j], cards[i]];
|
||||
}
|
||||
return cards;
|
||||
};
|
||||
Reference in New Issue
Block a user