HomePoker: MVP
This commit is contained in:
parent
c77cf64b4e
commit
3767502db8
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<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>
|
<title>$TITLE$</title>
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
|
</>;
|
||||||
|
};
|
||||||
|
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue