Edit page details, start integrating socket

This commit is contained in:
snen 2021-09-24 00:47:45 -04:00
parent 4bcb0e947d
commit 6d3343d064
6 changed files with 104 additions and 42 deletions

View File

@ -1,6 +1,6 @@
import React, { useEffect } from 'react'
import useWebSocket from 'react-use-websocket'
import useSocket from './useSocket.ts'
import {CardInstance} from './types.ts'
interface GameActionsContextValue {}
@ -39,19 +39,13 @@ export default function Game(props: GameProps): JSX.Element {
const [state, setState] = React.useState()
// ensure this is stable wrt state so that onMessage does not have to be constantly reattached
const onMessage = React.useCallback(() => {}, [])
// const onMessage = React.useCallback(() => {}, [])
const WS_URL = `ws://localhost:7636/ws`
const socket = useWebSocket(WS_URL, {onMessage})
useEffect(() => {
setInterval(() => console.log(socket.getWebSocket()), 3000)
}, [socket])
const gameActions = React.useMemo<GameActionsContextValue>(() => ({}), [socket.sendJsonMessage])
// const handle = useSocket()
return (
<GameActionsContext.Provider value={gameActions}>
<div />
<GameActionsContext.Provider value={{}}>
<div>Hello world!</div>
</GameActionsContext.Provider>
)
}

View File

@ -15,17 +15,50 @@ type AsyncHandle<T> =
| { status: 'connecting' }
| { status: 'connected'; handle: T}
interface SocketAPI {}
interface SessionCommandAPI {
query: () => void
join: () => void
leave: () => void
play: () => void
poll: () => void
}
interface GameCommandAPI {
act: () => void,
getState: () => void,
debug: () => void,
}
// TODO
type GameCommandEnum = 'a' | 's' | 'd'
interface SocketMessage {
playerId?: string
matchId?: string
command: keyof SessionCommandAPI
gameCommand?: {
playerId: string
type: GameCommandEnum,
cmd: unknown
}
}
type State =
| { status: 'not-connected' }
| { status: 'connecting' }
| { status: 'connected'}
| { status: 'finding-game' }
| { status: 'in-game'; playerId: string; matchId: string }
type Action =
| { type: 'open' }
| { type: 'close' }
| { type: 'error' }
| { type: 'game-found'; matchId: string; playerId: string }
type Action = 'open' | 'close' | 'error'
function reducer(_state: State, action: Action): State {
switch (action) {
case 'open': return { status: 'connected' }
switch (action.type) {
case 'open': return { status: 'finding-game' }
case 'game-found': return { status: 'in-game', matchId: action.matchId, playerId: action.playerId }
case 'close': return { status: 'not-connected' }
case 'error': return { status: 'connecting' }
default: return assertNever(action)
@ -34,8 +67,8 @@ function reducer(_state: State, action: Action): State {
const initialState: State = {status: 'not-connected'}
export default function useSocket(): AsyncHandle<SocketAPI> {
const _user = useUser()
export default function useSocket(): AsyncHandle<GameCommandAPI> {
const profile = useUser()
const [state, dispatch] = React.useReducer(reducer, initialState)
const onMessage = React.useCallback((message: WebSocketEventMap['message']) => {
@ -45,36 +78,53 @@ export default function useSocket(): AsyncHandle<SocketAPI> {
const onOpen = React.useCallback(() => {
console.log('socket opened')
dispatch('open')
dispatch({type: 'open'})
}, [])
const onError = React.useCallback((event: WebSocketEventMap['error']) => {
console.error(event)
dispatch('error')
dispatch({type: 'error'})
}, [])
const onClose = React.useCallback((_event: WebSocketEventMap['close']) => {
console.log('socket closed')
dispatch('close')
dispatch({type: 'close'})
}, [])
const url = React.useMemo(() =>
`ws://${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/api/ws?name=${name}`,
[],
const url = React.useMemo(
// () => `ws://arcade.saintnet.tech:7636/ws?name=${profile.displayName}`,
() => `ws://arcade.saintnet.tech:7636/ws`,
[profile],
)
const socket = useWebSocket(
url,
{onMessage, onOpen, onError, onClose, shouldReconnect},
)
const sendJson = React.useCallback((message: SocketMessage) => {
socket.send(JSON.stringify(message))
}, [socket])
const handle = React.useMemo(() => ({
sendJson: (value: {}) => socket.send(JSON.stringify(value)),
}), [socket])
// session commands
query: () => {},
join: () => {},
leave: () => {},
play: () => {},
poll: () => {},
// game commands
act: () => {},
getState: () => {},
debug: () => {},
}), [sendJson])
switch (state.status) {
case 'connected': {
case 'in-game': {
return {status: 'connected', handle}
}
case 'finding-game': {
return {status: 'connecting'}
}
case 'connecting':
case 'not-connected':
return state

View File

@ -1,13 +1,33 @@
import React from 'react'
import assertNever from '~/common/assertNever.ts'
import IntroPage from '../components/IntroPage.tsx'
import Page from '../components/Page.tsx'
import Game from '../game/Game.tsx'
type MenuState = 'menu' | 'play'
export default function AppPage() {
return (
<IntroPage>
<div>
<p>Hello world!</p>
</div>
</IntroPage>
)
const [menuState, setMenuState] = React.useState<MenuState>('menu')
switch (menuState) {
case 'menu': {
return (
<IntroPage>
<button onClick={() => setMenuState('play')}>
Play
</button>
</IntroPage>
)
}
case 'play': {
return (
<Page>
<Game />
</Page>
)
}
default: return assertNever(menuState)
}
}

View File

@ -98,10 +98,10 @@ h2 {
.header {
height: 90px;
flex: 0 0 auto;
background-color: #4a6670;
background-color: #3454d1;
padding-left: 2em;
padding-right: 2em;
box-shadow: 0 1em 1em rgba(0, 0, 0, 0.25);
box-shadow: 2px 8px 4px 1px rgba(0, 0, 0, 0.25);
display: flex;
align-items: center;
@ -109,9 +109,8 @@ h2 {
}
.header-text {
font-size: 32px;
font-family: sans-serif;
color: #18212b;
color: #ddf6fd;
}
.main {
@ -119,7 +118,6 @@ h2 {
display: flex;
align-items: center;
justify-content: center;
background-color: #e6eaef;
}
.frame {

View File

@ -1,3 +1,3 @@
export interface UserProfile {
displayName: string
}

View File

@ -40,8 +40,8 @@ const html = `
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Deno React Web Starter</title>
<meta name="description" content="Template for SSR React apps with Deno, Oak, and Firebase." />
<title>Tomecraft</title>
<meta name="description" content="Tactical CCG" />
<link href="styles.css" rel="stylesheet" type="text/css">
<style>
body {