Edit page details, start integrating socket
This commit is contained in:
parent
4bcb0e947d
commit
6d3343d064
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
const [menuState, setMenuState] = React.useState<MenuState>('menu')
|
||||
|
||||
switch (menuState) {
|
||||
case 'menu': {
|
||||
return (
|
||||
<IntroPage>
|
||||
<div>
|
||||
<p>Hello world!</p>
|
||||
</div>
|
||||
<button onClick={() => setMenuState('play')}>
|
||||
Play
|
||||
</button>
|
||||
</IntroPage>
|
||||
)
|
||||
}
|
||||
case 'play': {
|
||||
return (
|
||||
<Page>
|
||||
<Game />
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
default: return assertNever(menuState)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -1,3 +1,3 @@
|
||||
export interface UserProfile {
|
||||
displayName: string
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user