Compare commits
7 Commits
fe6242cbbf
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc55cbbe6b | ||
|
|
58f102af60 | ||
|
|
dfbe9f6603 | ||
|
|
cb9494c89a | ||
|
|
cb5a143623 | ||
|
|
a09b5cc9d1 | ||
|
|
3b459606e4 |
@@ -20,6 +20,8 @@ class GameMode:
|
||||
name: str
|
||||
mode_function: ModeFunction
|
||||
allow_multitouch: bool
|
||||
min_time: int
|
||||
max_time: int
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -62,9 +64,9 @@ class Arbiter:
|
||||
self.modeswitch_time = None
|
||||
self.clients: dict[WebSocket, ClientState] = dict()
|
||||
|
||||
def mode(self, name: str, allow_multitouch: bool = True):
|
||||
def mode(self, name: str, allow_multitouch: bool = True, min_time=10, max_time=90):
|
||||
def inner(f: ModeFunction):
|
||||
self.modes.append(GameMode(name, f, allow_multitouch))
|
||||
self.modes.append(GameMode(name, f, allow_multitouch, min_time, max_time))
|
||||
|
||||
return inner
|
||||
|
||||
@@ -118,7 +120,7 @@ class Arbiter:
|
||||
self.state.mode = self.current_mode.name
|
||||
self.update_next_mode()
|
||||
self.modeswitch_time = datetime.now() + timedelta(
|
||||
seconds=settings.arbiter_mode_switch_cycle
|
||||
seconds=random.randint(self.current_mode.min_time, self.current_mode.max_time)
|
||||
)
|
||||
|
||||
if self.current_mode_task:
|
||||
@@ -151,7 +153,7 @@ class Arbiter:
|
||||
arbiter = Arbiter()
|
||||
|
||||
|
||||
@arbiter.mode("democracy", allow_multitouch=False)
|
||||
@arbiter.mode("democracy", allow_multitouch=False, min_time=5*60, max_time=10*60)
|
||||
async def _(get_input: InputGetter, set_output: OutputSetter):
|
||||
while True:
|
||||
await asyncio.sleep(settings.democracy_vote_cycle)
|
||||
@@ -186,3 +188,38 @@ async def _(get_input: InputGetter, set_output: OutputSetter):
|
||||
output[choice] = True
|
||||
|
||||
set_output(output)
|
||||
|
||||
|
||||
@arbiter.mode("anarchy", allow_multitouch=False, min_time=5*60, max_time=10*60)
|
||||
async def _(get_input: InputGetter, set_output: OutputSetter):
|
||||
while True:
|
||||
await asyncio.sleep(settings.democracy_vote_cycle)
|
||||
|
||||
inputs: list[Input] = await get_input()
|
||||
|
||||
if not inputs:
|
||||
set_output(EMPTY_INPUT)
|
||||
continue
|
||||
|
||||
the_input = random.choice(inputs)
|
||||
|
||||
output = {button: the_input[button] for button in Button}
|
||||
|
||||
set_output(output)
|
||||
|
||||
random_buttons : list[Button]= ["up", "down", "left", "right", "a", "b"];
|
||||
|
||||
@arbiter.mode("random", allow_multitouch=False, min_time=30, max_time=90)
|
||||
async def _(get_input: InputGetter, set_output: OutputSetter):
|
||||
while True:
|
||||
await asyncio.sleep(settings.democracy_vote_cycle)
|
||||
|
||||
the_choice = random.choice([button for button in random_buttons])
|
||||
|
||||
if not the_choice:
|
||||
set_output(EMPTY_INPUT)
|
||||
continue
|
||||
|
||||
output = {button: (button == the_choice) for button in Button}
|
||||
|
||||
set_output(output)
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
--button-color: lightgrey;
|
||||
}
|
||||
|
||||
|
||||
.button {
|
||||
line-height: var(--button-height);
|
||||
height: var(--button-height);
|
||||
@@ -39,7 +38,7 @@
|
||||
}
|
||||
|
||||
.button.pressed {
|
||||
filter: drop-shadow(2px 2px 6px black);
|
||||
filter: drop-shadow(2px 2px 6px white);
|
||||
}
|
||||
.button-a, .button-b {
|
||||
--button-height: 50px;
|
||||
@@ -154,5 +153,7 @@
|
||||
*/
|
||||
|
||||
.client {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background-color: black;
|
||||
}
|
||||
@@ -10,13 +10,15 @@ const Client = () => {
|
||||
const [socket, setSocket] = useState<WebSocket>();
|
||||
const [buttonMap, setButtonMap] = useState<ButtonMap>(defaultButtonMap);
|
||||
|
||||
const updateButtonEvent = (button: ButtonType, state: "down" | "up") =>
|
||||
const updateButtonEvent = (button: ButtonType, buttonState: "down" | "up") =>
|
||||
() => setButtonMap((m) => {
|
||||
const newMap = { ...m, [button]: (state === "down") };
|
||||
if (mapToBitvector(newMap).reduce((a,b) => a+b, 0) > 1) {
|
||||
return m;
|
||||
} else {
|
||||
const newMap = { ...m, [button]: (buttonState === "down") };
|
||||
const multitouch = !!state.allowMultitouch;
|
||||
const multiInput = mapToBitvector(newMap).reduce((a,b) => a+b, 0) > 1;
|
||||
if (!multiInput || multitouch) {
|
||||
return newMap;
|
||||
} else {
|
||||
return m;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@ span {
|
||||
color: white;
|
||||
}
|
||||
.stream > div {
|
||||
width: 300px;
|
||||
height: 1080px;
|
||||
width: 290px;
|
||||
height: 1070px;
|
||||
background-color: black;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.heading {
|
||||
display: flex;
|
||||
@@ -16,6 +17,7 @@ span {
|
||||
}
|
||||
|
||||
.timer {
|
||||
width: 75px;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
@@ -43,21 +45,111 @@ span {
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
justify-content: space-around;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
.vote {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
.vote .bar {
|
||||
color: white;
|
||||
font-size: 24pt;
|
||||
text-transform: uppercase;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 30px;
|
||||
|
||||
border-radius: 5px;
|
||||
background-color: #3a3a3a;
|
||||
max-width: 200px;
|
||||
width: var(--vote-percentage, 0px);
|
||||
}
|
||||
.vote .percentage {
|
||||
font-weight: bold;
|
||||
line-height: 100%;
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
}
|
||||
.vote .bar-wrap {
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.voting {
|
||||
margin: 15%;
|
||||
background: #040404;
|
||||
border-radius: 20px;
|
||||
padding: 4px;
|
||||
gap: 2px;
|
||||
width: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
pre {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.info {
|
||||
height: 35%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.info .heading {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
color: #e8ff37;
|
||||
font-size: 30pt;
|
||||
text-align: center;
|
||||
}
|
||||
.info .link {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
color: #49ff4f;
|
||||
font-size: 24pt;
|
||||
text-align: center;
|
||||
}
|
||||
.info .text {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
font-size: 24pt;
|
||||
text-align: center;
|
||||
}
|
||||
.info hr {
|
||||
width: 100%;
|
||||
}
|
||||
.scoreboard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pokeball {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
position: relative;
|
||||
}
|
||||
.pokeball :first-child {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: red;
|
||||
clip-path: polygon(0% 0%, 0% 50%, 100% 50%, 100% 0%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.pokeball :last-child {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: white;
|
||||
clip-path: polygon(0% 50%, 0% 100%, 100% 100%, 100% 50%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import './Client.css';
|
||||
import { CSSProperties, useEffect, useMemo, useState } from 'react';
|
||||
import './TVMode.css';
|
||||
import { ButtonType, buttonTypeList, WGPPState } from './types';
|
||||
|
||||
@@ -27,31 +26,54 @@ const TVMode = () => {
|
||||
}, [])
|
||||
|
||||
const totalCount = useMemo(() => buttonTypeList.reduce((acc, v) => (state?.votes?.[v] ?? 0) + acc, 0), [state]);
|
||||
const voteList = useMemo(() => buttonTypeList.map(b => [b, state?.votes?.[b] ?? 0] as [ButtonType, number]).sort((a, b) => b[1] - a[1]), [state]);
|
||||
const voteList = useMemo(() =>
|
||||
buttonTypeList
|
||||
.map(b => [b, state?.votes?.[b] ?? 0] as [ButtonType, number])
|
||||
//.sort((a, b) => b[1] - a[1])
|
||||
, [state]);
|
||||
|
||||
return <div className="stream">
|
||||
{state === undefined ?
|
||||
<span>Loading...</span> :
|
||||
<div>
|
||||
<div className="info">
|
||||
<span className="heading">
|
||||
LAN-Party Plays Pokemon
|
||||
</span>
|
||||
<span className="text">
|
||||
Join with your phone on
|
||||
</span>
|
||||
<span className="link">
|
||||
http://pokemon.lan/
|
||||
</span>
|
||||
<span className="text">
|
||||
and become the very best!
|
||||
</span>
|
||||
<hr />
|
||||
</div>
|
||||
<div className="scoreboard">
|
||||
<span className={`mode mode-${state.mode}`}>{state.mode}</span>
|
||||
<div className="row">
|
||||
<span className="subtitle">next: {state.nextMode}</span>
|
||||
<div>
|
||||
<span>
|
||||
next:
|
||||
</span>
|
||||
<span className={`subtitle mode-${state.nextMode}`}>{state.nextMode}</span>
|
||||
</div>
|
||||
<span className="timer">{Math.round(state.timeUntilNextMode ?? 0)}s ⏱️</span>
|
||||
</div>
|
||||
<div className="voting">
|
||||
{voteList.flatMap(([b, numVotes], index) => {
|
||||
const percentage = numVotes === 0 ? "" : Math.round(numVotes / totalCount * 100) + "%";
|
||||
return <div className="vote" key={index}>
|
||||
<span>{b}</span><span>{percentage}</span>
|
||||
const percentage = numVotes === 0 ? 0 : Math.round(numVotes / totalCount * 100);
|
||||
return <div className="vote" style={{ "--vote-percentage": percentage + "%" } as CSSProperties} key={index}>
|
||||
<div className="bar-wrap">
|
||||
<div className="bar" style={{ "--vote-percentage": percentage + "%" } as CSSProperties} >{b}</div>
|
||||
</div>
|
||||
<span className="percentage">{percentage === 0 ? "" : `${percentage}%`}</span>
|
||||
</div>;
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
Current state: <pre>
|
||||
{JSON.stringify(state, null, 4)}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
</div>;
|
||||
|
||||
Reference in New Issue
Block a user