diff --git a/frontend/public/index.html b/frontend/public/index.html
index aa069f2..f9a560d 100644
--- a/frontend/public/index.html
+++ b/frontend/public/index.html
@@ -3,7 +3,8 @@
-
+
{
render();
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
deleted file mode 100644
index 891d1b8..0000000
--- a/frontend/src/App.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import './App.css';
-
-type State = {
-
-};
-
-function App() {
-
- const [state, setState] = useState({});
- const [socket, setSocket] = useState();
-
- useEffect(() => {
- const url = new URL(`api/client`, window.location.href);
- url.protocol = url.protocol.replace("http", "ws");
- const sock = new WebSocket(url.href);
-
- sock.onmessage = (e) => {
- const newState = JSON.parse(e.data) as State;
- setState(newState);
- }
- setSocket(sock);
-
- return () => {
- sock.close();
- setSocket(undefined);
- };
- }, [])
-
- const clickButton = (button: string) => (e: React.MouseEvent) => {
- if (!socket) return;
- socket.send(JSON.stringify({ "button": button }));
- };
-
- return (
-
-
- State:
{JSON.stringify(state)}
-
-
-
- {"up down left right a b l r start select".split(" ").map((b) =>
-
{b}
- )}
-
-
-
-
- );
-}
-
-export default App;
diff --git a/frontend/src/App.css b/frontend/src/Client.css
similarity index 74%
rename from frontend/src/App.css
rename to frontend/src/Client.css
index 8fe7241..d99f46f 100644
--- a/frontend/src/App.css
+++ b/frontend/src/Client.css
@@ -1,12 +1,25 @@
@media (orientation: landscape) {
- body {
- flex-direction: row;
+ .button-start {
+ margin-bottom: 10px;
+ grid-column: 9;
+ grid-row: 7;
+ }
+ .button-select {
+ grid-column: 7;
+ grid-row: 7;
}
}
@media (orientation: portrait) {
- body {
- flex-direction: column;
+ .button-start {
+ margin-bottom: 10px;
+ justify-self: end;
+ grid-column: 13;
+ grid-row: 7;
+ }
+ .button-select {
+ grid-column: 2;
+ grid-row: 7;
}
}
@@ -25,6 +38,9 @@
user-select: none;
}
+.button.pressed {
+ filter: drop-shadow(2px 2px 6px black);
+}
.button-a, .button-b {
--button-height: 50px;
--button-width: 50px;
@@ -81,9 +97,8 @@
width: 100%;
padding: 10px;
display: grid;
- /*grid-template-columns: auto auto auto 1fr auto auto;*/
- grid-template-columns: auto 10px auto auto auto auto 1fr auto auto 10px auto;
- grid-template-rows: auto 10px auto auto auto 1fr auto auto;
+ grid-template-columns: 10px 10px auto auto auto 1fr auto 10px auto 1fr auto auto 10px 10px;
+ grid-template-rows: auto 1fr auto auto auto 1fr auto auto;
}
.button-up {
grid-column: 4;
@@ -102,16 +117,17 @@
grid-row: 5;
}
.button-dummy {
+ pointer-events: none;
grid-column: 4;
grid-row: 4;
}
.button-a {
- grid-column: 9;
+ grid-column: 12;
grid-row: 4;
transform: scale(1.5);
}
.button-b {
- grid-column: 8;
+ grid-column: 11;
grid-row: 4;
transform: scale(1.5) translate(-45%, 45%);
}
@@ -120,15 +136,19 @@
grid-column: 1;
}
.button-r {
+ justify-self: end;
grid-row: 1;
- grid-column: 11;
+ grid-column: 14;
}
+
+/*
.button-start {
margin-bottom: 10px;
- grid-column: 6;
+ grid-column: 9;
grid-row: 7;
}
.button-select {
- grid-column: 6;
- grid-row: 8;
-}
\ No newline at end of file
+ grid-column: 7;
+ grid-row: 7;
+}
+*/
\ No newline at end of file
diff --git a/frontend/src/Client.tsx b/frontend/src/Client.tsx
new file mode 100644
index 0000000..61b12d6
--- /dev/null
+++ b/frontend/src/Client.tsx
@@ -0,0 +1,66 @@
+import { useEffect, useState } from 'react';
+import './Client.css';
+import { ButtonMap, ButtonType, buttonTypeList, defaultButtonMap, mapToBitvector, WGPPState } from './types';
+
+
+
+const Client = () => {
+
+ const [state, setState] = useState>({});
+ const [socket, setSocket] = useState();
+ const [buttonMap, setButtonMap] = useState(defaultButtonMap);
+
+ const updateButtonEvent = (button: ButtonType, state: "down" | "up") =>
+ () => setButtonMap((m) => ({ ...m, [button]: (state === "down") }));
+
+
+ // Send data to server
+ useEffect(() => {
+ if (!socket) return;
+ //socket.send(JSON.stringify(buttonMap));
+ }, [buttonMap, socket]);
+
+ useEffect(() => {
+ const url = new URL(`api/client`, window.location.href);
+ url.protocol = url.protocol.replace("http", "ws");
+ const sock = new WebSocket(url.href);
+
+ sock.onmessage = (e) => {
+ const newState = JSON.parse(e.data) as Partial;
+ // Merge old and new state
+ setState(oldState => ({ ...oldState, ...newState }));
+ }
+ setSocket(sock);
+
+ return () => {
+ sock.close();
+ setSocket(undefined);
+ };
+ }, [])
+
+ return (
+
+
+ State:
{JSON.stringify(state)}
+ ButtonMap:
{mapToBitvector(buttonMap).map(s => s.toString()).join(" ")}
+
+
+
+ {buttonTypeList.map((b, index) =>
+
+ {b}
+
+ )}
+
+
+
+
+ );
+}
+
+export default Client;
diff --git a/frontend/src/TVMode.css b/frontend/src/TVMode.css
new file mode 100644
index 0000000..bda63ad
--- /dev/null
+++ b/frontend/src/TVMode.css
@@ -0,0 +1,16 @@
+body > div {
+ width: 250px;
+ height: 50vh;
+ background-color: #00ff00;
+ padding: 5px;
+ display: flex;
+ flex-direction: column;
+}
+.heading {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+}
+span {
+ font-weight: bold;
+}
\ No newline at end of file
diff --git a/frontend/src/TVMode.tsx b/frontend/src/TVMode.tsx
new file mode 100644
index 0000000..66ed12e
--- /dev/null
+++ b/frontend/src/TVMode.tsx
@@ -0,0 +1,58 @@
+import { useEffect, useState } from 'react';
+import './TVMode.css';
+import { WGPPState } from './types';
+
+const TVMode = () => {
+
+ const [state, setState] = useState>({});
+ const [socket, setSocket] = useState();
+ const [endTime, setEndTime] = useState(0);
+ const [secondsRemaining, setSecondsRemaining] =useState(0);
+
+ useEffect(() => {
+ setEndTime(Date.now() + (state?.timeUntilNextMode??15) * 1000);
+ const interval = setInterval(() => {
+ const remaining = Math.round((endTime - Date.now())/1000);
+ setSecondsRemaining(remaining < 0 ? 0 : remaining);
+ }, 500);
+ return () => clearInterval(interval);
+ }, [endTime, state?.timeUntilNextMode]);
+
+ useEffect(() => {
+ const url = new URL(`api/client`, window.location.href);
+ url.protocol = url.protocol.replace("http", "ws");
+ const sock = new WebSocket(url.href);
+
+ sock.onmessage = (e) => {
+ const newState = JSON.parse(e.data) as Partial;
+ // Merge old and new state
+ setState(oldState => ({ ...oldState , ...newState }));
+ }
+ setSocket(sock);
+
+ return () => {
+ sock.close();
+ setSocket(undefined);
+ };
+ }, [])
+
+ return (state === undefined ?
+ Loading... :
+
+
+
Mode: {state.mode}
+
+ {secondsRemaining}s ⏱️
+
+
+
+ Current state:
+ {JSON.stringify(state)}
+
+
+
+
+ );
+}
+
+export default TVMode;
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
index 032464f..7835648 100644
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -1,15 +1,19 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
+import Client from './Client';
import './index.css';
-import App from './App';
import reportWebVitals from './reportWebVitals';
+import TVMode from './TVMode';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
+const tvUi = document.location.href.endsWith("stream");
root.render(
-
+ {tvUi ?
+ :
+ }
);
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
new file mode 100644
index 0000000..e2930fb
--- /dev/null
+++ b/frontend/src/types.ts
@@ -0,0 +1,25 @@
+
+
+export const defaultButtonMap : ButtonMap = {up: false, down: false, left: false, right: false, a: false, b: false, l: false, r: false, start: false, select: false};
+
+export type GameMode = "democracy" | "anarchy" | "random";
+
+export type WGPPState = {
+ // current game mode
+ mode: GameMode;
+ allowMultitouch?: boolean; // derived from mode
+
+ // stuff for TV
+ nextMode: GameMode;
+ timeUntilNextMode: number;
+ votes: {[button in ButtonType]?: number};
+
+ playerIdle: boolean; // default true, removed on first message
+};
+
+export const buttonTypeList: ButtonType[] = "up down left right a b l r start select".split(" ") as ButtonType[];
+
+export type ButtonType = "up" | "down" | "left" | "right" | "a" | "b" | "l" | "r" | "start" | "select";
+export type ButtonMap = { [key in ButtonType]: boolean };
+
+export const mapToBitvector = (map: ButtonMap): number[] => buttonTypeList.map(b => map[b] ? 1: 0);
\ No newline at end of file