Compare commits
3 Commits
6aeca0838d
...
18385e4977
| Author | SHA1 | Date | |
|---|---|---|---|
| 18385e4977 | |||
|
|
9522a5b40c | ||
|
|
171e6c516f |
8
bun.lock
8
bun.lock
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"configVersion": 1,
|
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pyre",
|
"name": "pyre",
|
||||||
|
|
@ -11,7 +10,6 @@
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"hono": "*",
|
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -19,13 +17,13 @@
|
||||||
"packages": {
|
"packages": {
|
||||||
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
|
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="],
|
"@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
|
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
|
||||||
|
|
||||||
"hono": ["hono@4.11.4", "", {}, "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA=="],
|
"hono": ["hono@4.11.6", "", {}, "sha512-ofIiiHyl34SV6AuhE3YT2mhO5HRWokce+eUYE82TsP6z0/H3JeJcjVWEMSIAiw2QkjDOEpES/lYsg8eEbsLtdw=="],
|
||||||
|
|
||||||
"hype": ["hype@git+https://git.nose.space/defunkt/hype#33d228c9ac6a01fad570e0ac2ba836a100dde623", { "dependencies": { "hono": "^4.10.4", "kleur": "^4.1.5" }, "peerDependencies": { "typescript": "^5" } }, "33d228c9ac6a01fad570e0ac2ba836a100dde623"],
|
"hype": ["hype@git+https://git.nose.space/defunkt/hype#52086f4eb94cc36ecd9470d9a101ff01002687c8", { "dependencies": { "hono": "^4.10.4", "kleur": "^4.1.5" }, "peerDependencies": { "typescript": "^5" } }, "52086f4eb94cc36ecd9470d9a101ff01002687c8"],
|
||||||
|
|
||||||
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"configVersion": 1,
|
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pyre-example-tictactoe",
|
"name": "pyre-example-tictactoe",
|
||||||
|
|
@ -13,13 +12,13 @@
|
||||||
"packages": {
|
"packages": {
|
||||||
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
|
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="],
|
"@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
|
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
|
||||||
|
|
||||||
"forge": ["forge@git+https://git.nose.space/defunkt/forge#e772e0e7115750bcd11a6a1daae171ebd89a17bb", { "peerDependencies": { "typescript": "^5" } }, "e772e0e7115750bcd11a6a1daae171ebd89a17bb"],
|
"forge": ["forge@git+https://git.nose.space/defunkt/forge#9b6e1e91ec77d7e03589cac256d97fb9cd942184", { "peerDependencies": { "typescript": "^5" } }, "9b6e1e91ec77d7e03589cac256d97fb9cd942184"],
|
||||||
|
|
||||||
"hono": ["hono@4.11.4", "", {}, "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA=="],
|
"hono": ["hono@4.11.6", "", {}, "sha512-ofIiiHyl34SV6AuhE3YT2mhO5HRWokce+eUYE82TsP6z0/H3JeJcjVWEMSIAiw2QkjDOEpES/lYsg8eEbsLtdw=="],
|
||||||
|
|
||||||
"hype": ["hype@git+https://git.nose.space/defunkt/hype#52086f4eb94cc36ecd9470d9a101ff01002687c8", { "dependencies": { "hono": "^4.10.4", "kleur": "^4.1.5" }, "peerDependencies": { "typescript": "^5" } }, "52086f4eb94cc36ecd9470d9a101ff01002687c8"],
|
"hype": ["hype@git+https://git.nose.space/defunkt/hype#52086f4eb94cc36ecd9470d9a101ff01002687c8", { "dependencies": { "hono": "^4.10.4", "kleur": "^4.1.5" }, "peerDependencies": { "typescript": "^5" } }, "52086f4eb94cc36ecd9470d9a101ff01002687c8"],
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// DO NOT MODIFY!
|
// DO NOT MODIFY!
|
||||||
// This file is generated by pyre.
|
// This file is generated by pyre.
|
||||||
// Generated: 2026-01-24 09:09:49.
|
// Generated: 2026-01-26 13:25:18.
|
||||||
|
|
||||||
import { send } from 'pyre/client'
|
import { send } from 'pyre/client'
|
||||||
|
|
||||||
|
|
@ -12,6 +12,14 @@ export const playAgain = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const emojiChange = (player: 'x' | 'o', emoji: string) => {
|
||||||
|
send({
|
||||||
|
type: 'action',
|
||||||
|
action: 'emojiChange',
|
||||||
|
args: [player, emoji]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const nameChange = (name: string) => {
|
export const nameChange = (name: string) => {
|
||||||
send({
|
send({
|
||||||
type: 'action',
|
type: 'action',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { define, createThemes } from 'forge'
|
import { define, createThemes } from 'forge'
|
||||||
import { move, nameChange, playAgain } from './actions'
|
import { move, nameChange, playAgain } from './actions'
|
||||||
|
import { EmojiModal, openEmojiModal } from './emojiModal'
|
||||||
import type { Game } from '../types'
|
import type { Game } from '../types'
|
||||||
import { sessionId } from 'pyre/client'
|
import { sessionId } from 'pyre/client'
|
||||||
|
|
||||||
|
|
@ -33,6 +34,10 @@ const Player = define('Player', {
|
||||||
parts: {
|
parts: {
|
||||||
Symbol: {
|
Symbol: {
|
||||||
fontSize: 32,
|
fontSize: 32,
|
||||||
|
cursor: 'pointer',
|
||||||
|
render({ children }) {
|
||||||
|
return <div onClick={openEmojiModal}>{children}</div>
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Indicator: {
|
Indicator: {
|
||||||
width: 8,
|
width: 8,
|
||||||
|
|
@ -314,14 +319,14 @@ const BoardWrapper = define('BoardWrapper', {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
})
|
})
|
||||||
|
|
||||||
const symbol = (tile: string): string =>
|
const symbol = (game: Game, tile: string): string =>
|
||||||
tile === 'x' ? '❌' : tile === 'o' ? '⭕️' : ''
|
tile === 'x' ? game.emoji.x : tile === 'o' ? game.emoji.o : ''
|
||||||
|
|
||||||
const PlayerO = ({ game, myPlayer }: { game: Game, myPlayer: string }) =>
|
const PlayerO = ({ game, myPlayer }: { game: Game, myPlayer: string }) =>
|
||||||
<Player symbol="⭕️" game={game} name={game.names.o} player="o" active={game.turn === 'o'} editable={myPlayer === 'o'} />
|
<Player symbol={game.emoji.o} game={game} name={game.names.o} player="o" active={game.turn === 'o'} editable={myPlayer === 'o'} />
|
||||||
|
|
||||||
const PlayerX = ({ game, myPlayer }: { game: Game, myPlayer: string }) =>
|
const PlayerX = ({ game, myPlayer }: { game: Game, myPlayer: string }) =>
|
||||||
<Player symbol="❌" game={game} name={game.names.x} player="x" active={game.turn === 'x'} editable={myPlayer === 'x'} />
|
<Player symbol={game.emoji.x} game={game} name={game.names.x} player="x" active={game.turn === 'x'} editable={myPlayer === 'x'} />
|
||||||
|
|
||||||
const tryMove = (game: Game, player: 'x' | 'o', x: number, y: number) => {
|
const tryMove = (game: Game, player: 'x' | 'o', x: number, y: number) => {
|
||||||
return game.turn === player && game.phase === 'play' ? () => move(x, y) : () => { }
|
return game.turn === player && game.phase === 'play' ? () => move(x, y) : () => { }
|
||||||
|
|
@ -335,6 +340,7 @@ export default ({ game }: { game: Game }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Phone>
|
<Phone>
|
||||||
|
<EmojiModal game={game} player={player} />
|
||||||
<GameContainer>
|
<GameContainer>
|
||||||
{game.winLine && (
|
{game.winLine && (
|
||||||
<WinBanner
|
<WinBanner
|
||||||
|
|
@ -346,17 +352,17 @@ export default ({ game }: { game: Game }) => {
|
||||||
{player === 'o' && <PlayerX game={game} myPlayer={player} />}
|
{player === 'o' && <PlayerX game={game} myPlayer={player} />}
|
||||||
<BoardWrapper>
|
<BoardWrapper>
|
||||||
<Board>
|
<Board>
|
||||||
<Tile onClick={tryMove(game, player, 0, 0)} player={game.board[0][0]}>{symbol(game.board[0][0])}</Tile>
|
<Tile onClick={tryMove(game, player, 0, 0)} player={game.board[0][0]}>{symbol(game, game.board[0][0])}</Tile>
|
||||||
<Tile onClick={tryMove(game, player, 0, 1)} player={game.board[0][1]}>{symbol(game.board[0][1])}</Tile>
|
<Tile onClick={tryMove(game, player, 0, 1)} player={game.board[0][1]}>{symbol(game, game.board[0][1])}</Tile>
|
||||||
<Tile onClick={tryMove(game, player, 0, 2)} col="last" player={game.board[0][2]}>{symbol(game.board[0][2])}</Tile>
|
<Tile onClick={tryMove(game, player, 0, 2)} col="last" player={game.board[0][2]}>{symbol(game, game.board[0][2])}</Tile>
|
||||||
|
|
||||||
<Tile onClick={tryMove(game, player, 1, 0)} player={game.board[1][0]}>{symbol(game.board[1][0])}</Tile>
|
<Tile onClick={tryMove(game, player, 1, 0)} player={game.board[1][0]}>{symbol(game, game.board[1][0])}</Tile>
|
||||||
<Tile onClick={tryMove(game, player, 1, 1)} player={game.board[1][1]}>{symbol(game.board[1][1])}</Tile>
|
<Tile onClick={tryMove(game, player, 1, 1)} player={game.board[1][1]}>{symbol(game, game.board[1][1])}</Tile>
|
||||||
<Tile onClick={tryMove(game, player, 1, 2)} col="last" player={game.board[1][2]}>{symbol(game.board[1][2])}</Tile>
|
<Tile onClick={tryMove(game, player, 1, 2)} col="last" player={game.board[1][2]}>{symbol(game, game.board[1][2])}</Tile>
|
||||||
|
|
||||||
<Tile onClick={tryMove(game, player, 2, 0)} row="last" player={game.board[2][0]}>{symbol(game.board[2][0])}</Tile>
|
<Tile onClick={tryMove(game, player, 2, 0)} row="last" player={game.board[2][0]}>{symbol(game, game.board[2][0])}</Tile>
|
||||||
<Tile onClick={tryMove(game, player, 2, 1)} row="last" player={game.board[2][1]}>{symbol(game.board[2][1])}</Tile>
|
<Tile onClick={tryMove(game, player, 2, 1)} row="last" player={game.board[2][1]}>{symbol(game, game.board[2][1])}</Tile>
|
||||||
<Tile onClick={tryMove(game, player, 2, 2)} row="last" col="last" player={game.board[2][2]}>{symbol(game.board[2][2])}</Tile>
|
<Tile onClick={tryMove(game, player, 2, 2)} row="last" col="last" player={game.board[2][2]}>{symbol(game, game.board[2][2])}</Tile>
|
||||||
</Board>
|
</Board>
|
||||||
{game.winLine && (
|
{game.winLine && (
|
||||||
<WinLine
|
<WinLine
|
||||||
|
|
|
||||||
131
example/src/client/emojiModal.tsx
Normal file
131
example/src/client/emojiModal.tsx
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
import { define, createThemes } from 'forge'
|
||||||
|
import { redraw } from 'pyre/client'
|
||||||
|
import type { Game, xo } from '../types'
|
||||||
|
import { emojiChange } from './actions'
|
||||||
|
|
||||||
|
const theme = createThemes({
|
||||||
|
light: {
|
||||||
|
bg: '#fff',
|
||||||
|
hoverBg: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
inputBorder: '#999',
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
bg: '#1a1a1a',
|
||||||
|
hoverBg: 'rgba(255, 255, 255, 0.1)',
|
||||||
|
inputBorder: '#666',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const EMOJI_OPTIONS = ['❌', '⭕', '🔥', '💀', '👑', '⚡', '🌟', '💎', '🎯'] as const
|
||||||
|
type Emoji = (typeof EMOJI_OPTIONS)[number]
|
||||||
|
|
||||||
|
export let emojiModalOpen = false
|
||||||
|
|
||||||
|
export const openEmojiModal = () => {
|
||||||
|
emojiModalOpen = true
|
||||||
|
redraw()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const closeEmojiModal = () => {
|
||||||
|
emojiModalOpen = false
|
||||||
|
redraw()
|
||||||
|
}
|
||||||
|
|
||||||
|
const chooseEmoji = (game: Game, player: xo, emoji: Emoji) => {
|
||||||
|
closeEmojiModal()
|
||||||
|
emojiChange(player, emoji)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalBackdrop = define('ModalBackdrop', {
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
zIndex: 100,
|
||||||
|
|
||||||
|
render({ parts: { Root }, props }) {
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') closeEmojiModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
const onMount = () => {
|
||||||
|
document.addEventListener('keydown', handleKeyDown)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Root ref={onMount} onClick={closeEmojiModal}>
|
||||||
|
{props.children}
|
||||||
|
</Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const ModalContent = define('ModalContent', {
|
||||||
|
background: theme('bg'),
|
||||||
|
borderRadius: 16,
|
||||||
|
padding: 24,
|
||||||
|
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)',
|
||||||
|
|
||||||
|
render({ parts: { Root }, props }) {
|
||||||
|
return (
|
||||||
|
<Root onClick={(e: MouseEvent) => e.stopPropagation()}>
|
||||||
|
{props.children}
|
||||||
|
</Root>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const EmojiGrid = define('EmojiGrid', {
|
||||||
|
display: 'grid',
|
||||||
|
gridTemplateColumns: 'repeat(3, 1fr)',
|
||||||
|
gap: 8,
|
||||||
|
})
|
||||||
|
|
||||||
|
const EmojiCell = define('EmojiCell', {
|
||||||
|
base: 'button',
|
||||||
|
width: 72,
|
||||||
|
height: 72,
|
||||||
|
fontSize: 40,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
background: theme('hoverBg'),
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: 12,
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'transform 0.15s ease, background 0.15s ease',
|
||||||
|
|
||||||
|
states: {
|
||||||
|
hover: {
|
||||||
|
background: theme('inputBorder'),
|
||||||
|
transform: 'scale(1.1)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
type EmojiPickerProps = {
|
||||||
|
game: Game
|
||||||
|
player: xo
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EmojiModal = define('EmojiModal', {
|
||||||
|
render({ props }: { props: EmojiPickerProps }) {
|
||||||
|
const { game, player } = props
|
||||||
|
return (
|
||||||
|
emojiModalOpen &&
|
||||||
|
<ModalBackdrop>
|
||||||
|
<ModalContent>
|
||||||
|
<EmojiGrid>
|
||||||
|
{EMOJI_OPTIONS.map((emoji) => (
|
||||||
|
<EmojiCell onClick={() => chooseEmoji(game, player, emoji)}>{emoji}</EmojiCell>
|
||||||
|
))}
|
||||||
|
</EmojiGrid>
|
||||||
|
</ModalContent>
|
||||||
|
</ModalBackdrop>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
@ -8,6 +8,7 @@ const newGame: Game = {
|
||||||
turn: 'x',
|
turn: 'x',
|
||||||
players: {},
|
players: {},
|
||||||
names: { x: 'Player X', o: 'Player O' },
|
names: { x: 'Player X', o: 'Player O' },
|
||||||
|
emoji: { x: '❌', o: '⭕️' },
|
||||||
board: [
|
board: [
|
||||||
['', '', ''],
|
['', '', ''],
|
||||||
['', '', ''],
|
['', '', ''],
|
||||||
|
|
@ -31,7 +32,7 @@ const action = setup(newGame, {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
action('playAgain', (ctx) => {
|
action('playAgain', ctx => {
|
||||||
const game = ctx.game as Game
|
const game = ctx.game as Game
|
||||||
game.phase = 'play'
|
game.phase = 'play'
|
||||||
game.turn = 'x'
|
game.turn = 'x'
|
||||||
|
|
@ -44,6 +45,12 @@ action('playAgain', (ctx) => {
|
||||||
return game
|
return game
|
||||||
})
|
})
|
||||||
|
|
||||||
|
action('emojiChange', (ctx, player: 'x' | 'o', emoji: string) => {
|
||||||
|
const game = ctx.game as Game
|
||||||
|
if (game.emoji[player]) game.emoji[player] = emoji
|
||||||
|
return game
|
||||||
|
})
|
||||||
|
|
||||||
action('nameChange', (ctx, name: string) => {
|
action('nameChange', (ctx, name: string) => {
|
||||||
const game = ctx.game as Game
|
const game = ctx.game as Game
|
||||||
const player = game.players[ctx.session]
|
const player = game.players[ctx.session]
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ export type Game = {
|
||||||
board: [Row, Row, Row]
|
board: [Row, Row, Row]
|
||||||
players: Record<string, xo>
|
players: Record<string, xo>
|
||||||
names: Record<xo, string>
|
names: Record<xo, string>
|
||||||
|
emoji: Record<xo, string>
|
||||||
winLine?: WinLine
|
winLine?: WinLine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
export { setup } from './setup'
|
export { setup, redraw } from './setup'
|
||||||
export { send, gameId } from './websocket'
|
export { send, gameId } from './websocket'
|
||||||
export { sessionId } from './session'
|
export { sessionId } from './session'
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,21 @@ import { initWebsocket, onUpdate } from './websocket'
|
||||||
|
|
||||||
const root = document.getElementById('root')!
|
const root = document.getElementById('root')!
|
||||||
|
|
||||||
|
export let redraw = () => { }
|
||||||
|
|
||||||
export function setup<G>(render: typeof renderJsx, onRender: (game: G) => Child) {
|
export function setup<G>(render: typeof renderJsx, onRender: (game: G) => Child) {
|
||||||
let game: G | undefined = undefined
|
let game: G | undefined = undefined
|
||||||
|
|
||||||
|
redraw = () =>
|
||||||
|
render(game ? onRender(game) : <Loading />, root)
|
||||||
|
|
||||||
onUpdate((newGame: G) => {
|
onUpdate((newGame: G) => {
|
||||||
game = newGame
|
game = newGame
|
||||||
render(game ? onRender(game) : <Loading />, root)
|
redraw()
|
||||||
})
|
})
|
||||||
|
|
||||||
initWebsocket()
|
initWebsocket()
|
||||||
render(game ? onRender(game) : <Loading />, root)
|
redraw()
|
||||||
}
|
}
|
||||||
|
|
||||||
const Loading = () => <h1>Loading...</h1>
|
const Loading = () => <h1>Loading...</h1>
|
||||||
Loading…
Reference in New Issue
Block a user