1
0
Fork 0

HomePoker: MVP

This commit is contained in:
Pafnooty 2024-12-21 15:59:50 +03:00
parent c77cf64b4e
commit 3767502db8
4 changed files with 297 additions and 1 deletions

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0">
<title>$TITLE$</title>
<style>
* {

View File

@ -0,0 +1,55 @@
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
overflow: hidden;
width: 100dvw;
height: 100dvh;
}
table {
width: 100%;
height: 100%;
border: 1px solid black;
border-collapse: collapse;
tr.first {
border-top: 2px solid black;
}
th, td {
border: 1px solid black;
text-align: center;
padding: 0;
margin: 0;
&.request {
width: 2rem;
}
&.first {
border-left: 2px solid black;
}
}
input {
width: 100%;
height: 100%;
margin: 0;
border: 0;
&.blind {
background-color: #cccccc;
}
}
}
.flex-row {
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
> * {
flex: 1;
}
}

View File

@ -0,0 +1,230 @@
import { range } from "@common/utils";
import { useMemo, useState } from "preact/hooks";
interface Turn {
isBlind: boolean;
request: number | '-' | undefined;
taken: number | '-' | undefined;
}
type RowType = number | 'Т' | 'Б' | 'З';
type Row = Turn[];
const numCards = 54;
export const App = () => {
const [numPlayers, setNumPlayers] = useState(4);
const [players, setPlayers] = useState<string[]>([]);
const [rows, setRows] = useState<Row[]>([]);
const rowTypes = useMemo(() => {
const maxDeal = Math.floor(numCards / numPlayers);
let rowTypes: RowType[] = [];
for (let i = 1; i < maxDeal; i++) {
rowTypes.push(i);
}
for (let p = 0; p < numPlayers; p++) {
rowTypes.push(maxDeal);
}
rowTypes.push('Т');
rowTypes.push('Б');
rowTypes.push('З');
return rowTypes;
}, [numPlayers]);
const mismatches: string[] = useMemo(() => {
let mismatches: number[] = [];
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
const row = rows[rowIndex];
if (!row) continue;
const type = rowTypes[rowIndex];
let maxCount: number;
if (typeof type === 'number') {
maxCount = type;
} else {
maxCount = Math.floor(numCards / numPlayers);
}
let sum = 0;
for (let player = 0; player < numPlayers; player++) {
const turn = row[player];
if (!turn) continue;
let { request } = turn;
if (typeof request !== 'number') {
request = 0;
}
sum += request;
}
mismatches[rowIndex] = maxCount - sum;
}
return mismatches.map(m => {
if (m === 0) return '';
if (m < 0) return '' + m;
return '+' + m;
});
}, [rows, numPlayers]);
const points: number[][] = useMemo(() => {
const points: number[][] = [];
for (let player = 0; player < numPlayers; player++) {
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
const row = rows[rowIndex];
if (!row) continue;
const type = rowTypes[rowIndex];
const turn = row[player];
if (!turn) continue;
let { request, taken, isBlind } = turn;
if (request === '-') {
request = 0;
}
if (taken === '-') {
taken = 0;
}
if (typeof request !== 'number' || typeof taken !== 'number') {
continue;
}
let score = 0;
const diff = taken - request;
if (taken < request) {
score += diff * 10;
} else if (taken > request) {
score += diff;
} else if (taken === 0 && request === 0) {
score += 5;
} else if (taken === request) {
score += 10 * taken;
}
if (isBlind) {
score *= 2;
}
if (type === 'З') {
score *= 2;
}
let pointsRow = points[rowIndex];
let prevScore = points[rowIndex - 1]?.[player] ?? 0;
if (!pointsRow) {
pointsRow = [];
}
pointsRow[player] = prevScore + score;
points[rowIndex] = pointsRow;
}
}
return points;
}, [rows, rowTypes, numPlayers]);
const handleSetPlayer = (i: number) => (e: InputEvent) => {
const newPlayers = [...players];
if (e.target instanceof HTMLInputElement) {
newPlayers[i] = e.target.value;
}
setPlayers(newPlayers);
};
const handleAddPlayers = () => {
setNumPlayers(numPlayers + 1);
};
const handleChangeTakes = (rowIndex: number, player: number, field: 'request' | 'taken') => (e: InputEvent) => {
if (e.target instanceof HTMLInputElement) {
let row = rows[rowIndex];
const type = rowTypes[rowIndex];
let maxCount: number;
if (typeof type === 'number') {
maxCount = type;
} else {
maxCount = Math.floor(numCards / numPlayers);
}
if (!row) {
row = [];
}
let value: string | number = e.target.value;
let isBlind = type === 'Т' || row[player]?.isBlind;
if (type !== 'Т' && value.toLowerCase().includes('b')) {
isBlind = !isBlind;
}
value = value.replace(/[^-0-9]/g, '');
value = Math.abs(parseInt(value));
if (value >= maxCount) {
value = maxCount;
}
if (isNaN(value)) {
value = 0;
}
if (value === 0) {
value = '-';
}
row[player] = {
...row[player],
[field]: value,
isBlind,
}
const newRows = [...rows];
newRows[rowIndex] = row;
setRows(newRows);
}
};
return <>
<table>
<thead>
<th />
{range(numPlayers).map(i => (
<th key={i} colSpan={3} class="first">
<input
type='text'
value={players[i]}
onInput={handleSetPlayer(i)}
autoComplete='off'
/>
</th>
))}
<th class="flex-row">
<button onClick={handleAddPlayers}>+</button>
</th>
</thead>
<tbody>
{rowTypes.map((rt, i) => (
<tr
class={`${(rt === 'Т' || i === 0) ? 'first' : ''}`}
key={i}>
<td>{rt}</td>
{range(numPlayers).map(p => (
<>
<td class="request first">
<input
class={`${rowTypes[i] === 'Т' || rows[i]?.[p]?.isBlind ? 'blind' : ''}`}
value={rows[i]?.[p]?.request ?? ''}
onInput={handleChangeTakes(i, p, 'request')}
size={2}
autoComplete='off'
/>
</td>
<td class="request">
<input
value={rows[i]?.[p]?.taken ?? ''}
onInput={handleChangeTakes(i, p, 'taken')}
size={2}
autoComplete='off'
/>
</td>
<td>
{points[i]?.[p]}
</td>
</>
))}
<td>{mismatches[i]}</td>
</tr>
))}</tbody>
</table>
</>;
};

View File

@ -0,0 +1,11 @@
import { render } from "preact";
import { App } from "./components/App";
import './assets/style.css';
export default function main() {
render(
<App />,
document.body
);
}