HomePoker: MVP
This commit is contained in:
parent
c77cf64b4e
commit
3767502db8
|
|
@ -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>
|
||||
* {
|
||||
|
|
|
|||
|
|
@ -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