Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67e504ced6 | ||
|
|
ad967d5047 | ||
|
|
8fd0067ad1 | ||
|
|
43566be7ae | ||
|
|
cb8b254cbb | ||
|
|
340ca1d2f5 | ||
|
|
02c49852c0 | ||
|
|
02c9dce1b3 | ||
|
|
a3650642ca | ||
|
|
fbded57807 | ||
|
|
44cb05487e | ||
|
|
e1e2ce3eec | ||
|
|
4c7f444887 | ||
|
|
0311fc56a3 | ||
|
|
9902684990 | ||
|
|
967e66bdb3 | ||
|
|
ff21bfb8b3 | ||
|
|
c153636e24 | ||
|
|
efe4adf129 | ||
|
|
802266e500 | ||
|
|
373d8be1a0 | ||
|
|
bd5654128e | ||
|
|
9073658e12 | ||
|
|
7df4d09553 | ||
|
|
1ad60189f4 | ||
|
|
766c77b00a | ||
|
|
bcdf545188 | ||
|
|
3264d9ad55 | ||
|
|
3f35f7f936 | ||
|
|
0cddc33ac6 | ||
|
|
a1a12b1873 | ||
|
|
ae1e32391c | ||
|
|
97cc3ae151 | ||
|
|
2113252e2d | ||
|
|
2cae3c6d28 | ||
|
|
de261ae400 | ||
|
|
3dd0a557d4 | ||
|
|
51ae1b27b4 | ||
|
|
50e89492cf | ||
|
|
3d9ea1296c | ||
|
|
db69f76aa0 | ||
|
|
0ee3a8b6e8 | ||
|
|
b379f1d82c | ||
|
|
c83f9d8700 | ||
|
|
6289222b69 | ||
|
|
607a65e22e | ||
|
|
6bbb297c59 | ||
|
|
95866f7df0 | ||
|
|
aaa949cc2a | ||
|
|
ed2b7b7cb9 | ||
|
|
1ef3afd647 | ||
|
|
2746051a2a | ||
|
|
610aef4f7e | ||
|
|
a9ca58f154 | ||
|
|
7182ab387f | ||
|
|
0e46f6e122 | ||
|
|
01777c8c3e | ||
|
|
aa16b66332 | ||
|
|
2af373aed7 | ||
|
|
896f5e9c92 | ||
|
|
b319ed7e6d | ||
|
|
9990273b19 | ||
|
|
ea7f2d8932 | ||
|
|
38b00e55b0 | ||
|
|
ab673a81f0 | ||
|
|
287b9c7b4e | ||
|
|
126f5ab60a | ||
|
|
3c19f984a9 | ||
|
|
cf3b93464a | ||
|
|
edcea37148 | ||
|
|
b4e4de82ae | ||
|
|
cdfcfe8fd0 | ||
|
|
bf677856a2 | ||
|
|
b9d76d5e8f | ||
|
|
d292a830a1 | ||
|
|
745175210c | ||
|
|
6536daee7f | ||
|
|
9ce50f2622 | ||
|
|
ea0bb5d365 | ||
|
|
20488d2be8 | ||
|
|
7b7486cc93 | ||
|
|
e5a668b691 | ||
|
|
c298bb0dfd | ||
|
|
adabc4eb98 | ||
|
|
6e1bfc177d | ||
|
|
e491cc5cbc | ||
|
|
37d02f0f52 | ||
|
|
e832085fc2 | ||
|
|
8e92c9a6d6 | ||
|
|
1fc6d71db6 | ||
|
|
4f48ee805f | ||
|
|
f9ec874491 | ||
|
|
32c186e1e2 | ||
|
|
17b2891f9a |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/mayhem-party
|
||||
**/*.sw*
|
||||
11
README.md
11
README.md
@@ -9,3 +9,14 @@ Think Dug's Twitch Chat Plays
|
||||
* multiplayer engine
|
||||
* server; https://github.com/LizardByte/Sunshine
|
||||
* client; https://moonlight-stream.org/
|
||||
|
||||
## DONE
|
||||
|
||||
* input
|
||||
* random from weighted file
|
||||
* buffered
|
||||
* remapped from file
|
||||
* output
|
||||
* to keyboard
|
||||
* to stderr
|
||||
|
||||
|
||||
15
go.mod
15
go.mod
@@ -3,6 +3,17 @@ module mayhem-party
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible // indirect
|
||||
github.com/micmonay/keybd_event v1.1.1 // indirect
|
||||
github.com/faiface/beep v1.1.0
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/hajimehoshi/oto v0.7.1 // indirect
|
||||
github.com/micmonay/keybd_event v1.1.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 // indirect
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 // indirect
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 // indirect
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 // indirect
|
||||
)
|
||||
|
||||
40
go.sum
40
go.sum
@@ -1,4 +1,44 @@
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
|
||||
github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c=
|
||||
github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
|
||||
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
|
||||
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
|
||||
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
|
||||
github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
|
||||
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
|
||||
github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk=
|
||||
github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos=
|
||||
github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
|
||||
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
|
||||
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk=
|
||||
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU=
|
||||
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
|
||||
github.com/micmonay/keybd_event v1.1.1 h1:rv7omwXWYL9Lgf3PUq6uBgJI2k1yGkL/GD6dxc6nmSs=
|
||||
github.com/micmonay/keybd_event v1.1.1/go.mod h1:CGMWMDNgsfPljzrAWoybUOSKafQPZpv+rLigt2LzNGI=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc=
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
|
||||
58
host.d/README.md
Normal file
58
host.d/README.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Hosting a Mayhem Party
|
||||
|
||||
## Requirements
|
||||
|
||||
1. [`rusty-pipe`](https://gogs.inhome.blapointe.com/bel/rusty-pipe)
|
||||
1. [`mayhem-party`](https://gogs.inhome.blapointe.com/bel/mayhem-party)
|
||||
1. [`stt`](https://gogs.inhome.blapointe.com/bel/stt)
|
||||
|
||||
# Clients
|
||||
|
||||
## Distribute `rusty-pipe`
|
||||
|
||||
```bash
|
||||
# https://www.reddit.com/r/rust/comments/5k8uab/crosscompiling_from_ubuntu_to_windows_with_rustup/
|
||||
(
|
||||
echo '[target.x86_64-pc-windows-gnu]'
|
||||
echo 'linker = "x86_64-w64-mingw32-gcc"'
|
||||
echo 'ar = "x86_64-w64-mingw32-gcc-ar"'
|
||||
) >> $HOME/.cargo/config
|
||||
sudo apt install mingw-w64
|
||||
rustup target add x86_64-pc-windows-gnu
|
||||
echo windows
|
||||
cargo build --release --target x86_64-pc-windows-gnu && ls target/x86_64-pc-windows-gnu/release/rusty-pipe.exe
|
||||
echo local
|
||||
cargo install --path ./
|
||||
```
|
||||
|
||||
Each client needs 1 executable and 1 env file with a unique set of buttons
|
||||
|
||||
> 10 buttons per player
|
||||
> `go doc key Undef | grep Key | grep -v Undef | wc -l` total (51)
|
||||
|
||||
The server cannot be a client because math. Maybe a VM on the client as a server would work tho.
|
||||
|
||||
See `./config.d/rusty-pipe.d`
|
||||
|
||||
# Server
|
||||
|
||||
## `mayhem-party`
|
||||
|
||||
### Configs
|
||||
|
||||
`cd ./config.d/mayhem-party.d/remap.d; bash ./rotate.sh`
|
||||
|
||||
> rotate anytime via stdin or `pkill -SIGUSR1 -f rotate.sh`
|
||||
|
||||
### Binary
|
||||
|
||||
`bash -c 'true; source ./config.d/mayhem-party.d/env.env; mayhem-party'`
|
||||
|
||||
## Game Playing
|
||||
|
||||
Foreground
|
||||
|
||||
## `stt`
|
||||
|
||||
TODO pipe stt detecting relevant strings -> change the `./config.d/mayhem-party.d/remap.d/live.yaml` link -> `SIGUSR1` to `mayhem-party`
|
||||
|
||||
7
host.d/config.d/mayhem-party.d/env.env
Normal file
7
host.d/config.d/mayhem-party.d/env.env
Normal file
@@ -0,0 +1,7 @@
|
||||
export DEBUG=true
|
||||
export RAW_UDP=17070
|
||||
export BUTTON_V01=true
|
||||
export WRAP_REFRESH_ON_SIGUSR1=true
|
||||
export MAIN_INTERVAL_DURATION=5ms
|
||||
export OUTPUT_KEYBOARD=false
|
||||
export BUTTON_V01_CONFIG=./config.d/mayhem-party.d/v01.yaml
|
||||
31
host.d/config.d/mayhem-party.d/remap.d/.players_offset.py
Normal file
31
host.d/config.d/mayhem-party.d/remap.d/.players_offset.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import yaml
|
||||
from sys import argv
|
||||
from sys import stderr
|
||||
|
||||
def log(*args):
|
||||
print(*args, file=stderr)
|
||||
|
||||
def main(args):
|
||||
players = []
|
||||
for i in range(1, 5+1):
|
||||
with open(f"../../rusty-pipe.d/{i}.yaml", "r") as f:
|
||||
players.append(yaml.safe_load(f)["streams"]["input"]["engine"]["gui"]["buttons"])
|
||||
log(players[-1])
|
||||
for arg in args:
|
||||
offset = int(arg)
|
||||
for i in range(len(players)):
|
||||
j = (i+offset)%len(players)
|
||||
if i == j:
|
||||
break
|
||||
keys = players[i]
|
||||
values = players[j]
|
||||
log(f"player {i+1} plays as player {j+1}")
|
||||
print(f"# player {i+1} controls {j+1}")
|
||||
for k in keys:
|
||||
key = keys[k]
|
||||
value = values[k]
|
||||
print(f"'{key}': '{value}'")
|
||||
|
||||
if __name__ == "__main__":
|
||||
from sys import argv
|
||||
main(argv[1:])
|
||||
1
host.d/config.d/mayhem-party.d/remap.d/live.yaml
Symbolic link
1
host.d/config.d/mayhem-party.d/remap.d/live.yaml
Symbolic link
@@ -0,0 +1 @@
|
||||
players_offset_4.yaml
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
55
host.d/config.d/mayhem-party.d/remap.d/players_offset_1.yaml
Normal file
55
host.d/config.d/mayhem-party.d/remap.d/players_offset_1.yaml
Normal file
@@ -0,0 +1,55 @@
|
||||
# player 1 controls 2
|
||||
'1': 'q'
|
||||
'2': 'w'
|
||||
'3': 'e'
|
||||
'4': 'r'
|
||||
'5': 't'
|
||||
'6': 'y'
|
||||
'7': 'u'
|
||||
'8': 'i'
|
||||
'9': 'o'
|
||||
'0': 'p'
|
||||
# player 2 controls 3
|
||||
'q': 'a'
|
||||
'w': 's'
|
||||
'e': 'd'
|
||||
'r': 'f'
|
||||
't': 'g'
|
||||
'y': 'h'
|
||||
'u': 'j'
|
||||
'i': 'k'
|
||||
'o': 'l'
|
||||
'p': ';'
|
||||
# player 3 controls 4
|
||||
'a': 'z'
|
||||
's': 'x'
|
||||
'd': 'c'
|
||||
'f': 'v'
|
||||
'g': 'b'
|
||||
'h': 'n'
|
||||
'j': 'm'
|
||||
'k': ','
|
||||
'l': '-'
|
||||
';': '/'
|
||||
# player 4 controls 5
|
||||
'z': '!'
|
||||
'x': '@'
|
||||
'c': '#'
|
||||
'v': '$'
|
||||
'b': '%'
|
||||
'n': '^'
|
||||
'm': '&'
|
||||
',': '*'
|
||||
'-': '('
|
||||
'/': ')'
|
||||
# player 5 controls 1
|
||||
'!': '1'
|
||||
'@': '2'
|
||||
'#': '3'
|
||||
'$': '4'
|
||||
'%': '5'
|
||||
'^': '6'
|
||||
'&': '7'
|
||||
'*': '8'
|
||||
'(': '9'
|
||||
')': '0'
|
||||
55
host.d/config.d/mayhem-party.d/remap.d/players_offset_2.yaml
Normal file
55
host.d/config.d/mayhem-party.d/remap.d/players_offset_2.yaml
Normal file
@@ -0,0 +1,55 @@
|
||||
# player 1 controls 3
|
||||
'1': 'a'
|
||||
'2': 's'
|
||||
'3': 'd'
|
||||
'4': 'f'
|
||||
'5': 'g'
|
||||
'6': 'h'
|
||||
'7': 'j'
|
||||
'8': 'k'
|
||||
'9': 'l'
|
||||
'0': ';'
|
||||
# player 2 controls 4
|
||||
'q': 'z'
|
||||
'w': 'x'
|
||||
'e': 'c'
|
||||
'r': 'v'
|
||||
't': 'b'
|
||||
'y': 'n'
|
||||
'u': 'm'
|
||||
'i': ','
|
||||
'o': '-'
|
||||
'p': '/'
|
||||
# player 3 controls 5
|
||||
'a': '!'
|
||||
's': '@'
|
||||
'd': '#'
|
||||
'f': '$'
|
||||
'g': '%'
|
||||
'h': '^'
|
||||
'j': '&'
|
||||
'k': '*'
|
||||
'l': '('
|
||||
';': ')'
|
||||
# player 4 controls 1
|
||||
'z': '1'
|
||||
'x': '2'
|
||||
'c': '3'
|
||||
'v': '4'
|
||||
'b': '5'
|
||||
'n': '6'
|
||||
'm': '7'
|
||||
',': '8'
|
||||
'-': '9'
|
||||
'/': '0'
|
||||
# player 5 controls 2
|
||||
'!': 'q'
|
||||
'@': 'w'
|
||||
'#': 'e'
|
||||
'$': 'r'
|
||||
'%': 't'
|
||||
'^': 'y'
|
||||
'&': 'u'
|
||||
'*': 'i'
|
||||
'(': 'o'
|
||||
')': 'p'
|
||||
55
host.d/config.d/mayhem-party.d/remap.d/players_offset_3.yaml
Normal file
55
host.d/config.d/mayhem-party.d/remap.d/players_offset_3.yaml
Normal file
@@ -0,0 +1,55 @@
|
||||
# player 1 controls 4
|
||||
'1': 'z'
|
||||
'2': 'x'
|
||||
'3': 'c'
|
||||
'4': 'v'
|
||||
'5': 'b'
|
||||
'6': 'n'
|
||||
'7': 'm'
|
||||
'8': ','
|
||||
'9': '-'
|
||||
'0': '/'
|
||||
# player 2 controls 5
|
||||
'q': '!'
|
||||
'w': '@'
|
||||
'e': '#'
|
||||
'r': '$'
|
||||
't': '%'
|
||||
'y': '^'
|
||||
'u': '&'
|
||||
'i': '*'
|
||||
'o': '('
|
||||
'p': ')'
|
||||
# player 3 controls 1
|
||||
'a': '1'
|
||||
's': '2'
|
||||
'd': '3'
|
||||
'f': '4'
|
||||
'g': '5'
|
||||
'h': '6'
|
||||
'j': '7'
|
||||
'k': '8'
|
||||
'l': '9'
|
||||
';': '0'
|
||||
# player 4 controls 2
|
||||
'z': 'q'
|
||||
'x': 'w'
|
||||
'c': 'e'
|
||||
'v': 'r'
|
||||
'b': 't'
|
||||
'n': 'y'
|
||||
'm': 'u'
|
||||
',': 'i'
|
||||
'-': 'o'
|
||||
'/': 'p'
|
||||
# player 5 controls 3
|
||||
'!': 'a'
|
||||
'@': 's'
|
||||
'#': 'd'
|
||||
'$': 'f'
|
||||
'%': 'g'
|
||||
'^': 'h'
|
||||
'&': 'j'
|
||||
'*': 'k'
|
||||
'(': 'l'
|
||||
')': ';'
|
||||
55
host.d/config.d/mayhem-party.d/remap.d/players_offset_4.yaml
Normal file
55
host.d/config.d/mayhem-party.d/remap.d/players_offset_4.yaml
Normal file
@@ -0,0 +1,55 @@
|
||||
# player 1 controls 5
|
||||
'1': '!'
|
||||
'2': '@'
|
||||
'3': '#'
|
||||
'4': '$'
|
||||
'5': '%'
|
||||
'6': '^'
|
||||
'7': '&'
|
||||
'8': '*'
|
||||
'9': '('
|
||||
'0': ')'
|
||||
# player 2 controls 1
|
||||
'q': '1'
|
||||
'w': '2'
|
||||
'e': '3'
|
||||
'r': '4'
|
||||
't': '5'
|
||||
'y': '6'
|
||||
'u': '7'
|
||||
'i': '8'
|
||||
'o': '9'
|
||||
'p': '0'
|
||||
# player 3 controls 2
|
||||
'a': 'q'
|
||||
's': 'w'
|
||||
'd': 'e'
|
||||
'f': 'r'
|
||||
'g': 't'
|
||||
'h': 'y'
|
||||
'j': 'u'
|
||||
'k': 'i'
|
||||
'l': 'o'
|
||||
';': 'p'
|
||||
# player 4 controls 3
|
||||
'z': 'a'
|
||||
'x': 's'
|
||||
'c': 'd'
|
||||
'v': 'f'
|
||||
'b': 'g'
|
||||
'n': 'h'
|
||||
'm': 'j'
|
||||
',': 'k'
|
||||
'-': 'l'
|
||||
'/': ';'
|
||||
# player 5 controls 4
|
||||
'!': 'z'
|
||||
'@': 'x'
|
||||
'#': 'c'
|
||||
'$': 'v'
|
||||
'%': 'b'
|
||||
'^': 'n'
|
||||
'&': 'm'
|
||||
'*': ','
|
||||
'(': '-'
|
||||
')': '/'
|
||||
21
host.d/config.d/mayhem-party.d/remap.d/rotate.sh
Normal file
21
host.d/config.d/mayhem-party.d/remap.d/rotate.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#! /bin/bash
|
||||
|
||||
mayhem_party_rotate() {
|
||||
local currently=$(realpath live.yaml | grep -o '[0-9].yaml$' | grep -o '^[0-9]')
|
||||
local next=${NEXT:-$((RANDOM%5))}
|
||||
while [ -z "$NEXT" ] && [ "$next" == "$currently" ]; do
|
||||
next=$((RANDOM%5))
|
||||
done
|
||||
rm live.yaml
|
||||
ln -s players_offset_$next.yaml live.yaml
|
||||
pkill -SIGUSR1 -f mayhem-party
|
||||
}
|
||||
|
||||
trap mayhem_party_rotate SIGUSR1
|
||||
|
||||
if [ "$0" == "$BASH_SOURCE" ]; then
|
||||
NEXT=0 mayhem_party_rotate
|
||||
while read -p "$(date) > [press enter to rotate]"; do
|
||||
mayhem_party_rotate
|
||||
done
|
||||
fi
|
||||
21
host.d/config.d/mayhem-party.d/v01.yaml
Normal file
21
host.d/config.d/mayhem-party.d/v01.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
feedback:
|
||||
addr: :17071
|
||||
users:
|
||||
bel:
|
||||
player: 2
|
||||
message: its bel
|
||||
broadcast:
|
||||
message: 8
|
||||
players:
|
||||
- {}
|
||||
- transformation:
|
||||
w: t
|
||||
a: f
|
||||
s: g
|
||||
d: h
|
||||
q: r
|
||||
e: y
|
||||
1: 5
|
||||
2: 6
|
||||
3: 7
|
||||
4: 8
|
||||
30
host.d/config.d/rusty-pipe.d/1.yaml
Normal file
30
host.d/config.d/rusty-pipe.d/1.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
streams:
|
||||
input:
|
||||
debug: false
|
||||
engine:
|
||||
name: gui
|
||||
gui:
|
||||
user: bel
|
||||
feedback:
|
||||
url: http://mayhem-party.home.blapointe.com:17071?user=bel
|
||||
press: {prefix: "", suffix: ""}
|
||||
release: {prefix: "", suffix: ""}
|
||||
format: '{"T":{{ms}},"U":"{{user}}","Y":"{{pressed}}","N":"{{released}}"}'
|
||||
buttons:
|
||||
up: 'w'
|
||||
down: 's'
|
||||
left: 'a'
|
||||
right: 'd'
|
||||
l: 'e'
|
||||
r: 'q'
|
||||
a: '1'
|
||||
b: '2'
|
||||
x: '3'
|
||||
y: '4'
|
||||
output:
|
||||
debug: false
|
||||
engine:
|
||||
name: udp
|
||||
udp:
|
||||
host: mayhem-party.home.blapointe.com
|
||||
port: 17070
|
||||
30
host.d/config.d/rusty-pipe.d/2.yaml
Normal file
30
host.d/config.d/rusty-pipe.d/2.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
streams:
|
||||
input:
|
||||
debug: false
|
||||
engine:
|
||||
name: gui
|
||||
gui:
|
||||
user: zach
|
||||
feedback:
|
||||
url: http://mayhem-party.home.blapointe.com:17071?user=zach
|
||||
press: {prefix: "", suffix: ""}
|
||||
release: {prefix: "", suffix: ""}
|
||||
format: '{"T":{{ms}},"U":"{{user}}","Y":"{{pressed}}","N":"{{released}}"}'
|
||||
buttons:
|
||||
up: 'w'
|
||||
down: 's'
|
||||
left: 'a'
|
||||
right: 'd'
|
||||
l: 'e'
|
||||
r: 'q'
|
||||
a: '1'
|
||||
b: '2'
|
||||
x: '3'
|
||||
y: '4'
|
||||
output:
|
||||
debug: false
|
||||
engine:
|
||||
name: udp
|
||||
udp:
|
||||
host: mayhem-party.home.blapointe.com
|
||||
port: 17070
|
||||
30
host.d/config.d/rusty-pipe.d/3.yaml
Normal file
30
host.d/config.d/rusty-pipe.d/3.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
streams:
|
||||
input:
|
||||
debug: false
|
||||
engine:
|
||||
name: gui
|
||||
gui:
|
||||
user: chase
|
||||
feedback:
|
||||
url: http://mayhem-party.home.blapointe.com:17071?user=chase
|
||||
press: {prefix: "", suffix: ""}
|
||||
release: {prefix: "", suffix: ""}
|
||||
format: '{"T":{{ms}},"U":"{{user}}","Y":"{{pressed}}","N":"{{released}}"}'
|
||||
buttons:
|
||||
up: 'w'
|
||||
down: 's'
|
||||
left: 'a'
|
||||
right: 'd'
|
||||
l: 'e'
|
||||
r: 'q'
|
||||
a: '1'
|
||||
b: '2'
|
||||
x: '3'
|
||||
y: '4'
|
||||
output:
|
||||
debug: false
|
||||
engine:
|
||||
name: udp
|
||||
udp:
|
||||
host: mayhem-party.home.blapointe.com
|
||||
port: 17070
|
||||
30
host.d/config.d/rusty-pipe.d/4.yaml
Normal file
30
host.d/config.d/rusty-pipe.d/4.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
streams:
|
||||
input:
|
||||
debug: false
|
||||
engine:
|
||||
name: gui
|
||||
gui:
|
||||
user: mason
|
||||
feedback:
|
||||
url: http://mayhem-party.home.blapointe.com:17071?user=mason
|
||||
press: {prefix: "", suffix: ""}
|
||||
release: {prefix: "", suffix: ""}
|
||||
format: '{"T":{{ms}},"U":"{{user}}","Y":"{{pressed}}","N":"{{released}}"}'
|
||||
buttons:
|
||||
up: 'w'
|
||||
down: 's'
|
||||
left: 'a'
|
||||
right: 'd'
|
||||
l: 'e'
|
||||
r: 'q'
|
||||
a: '1'
|
||||
b: '2'
|
||||
x: '3'
|
||||
y: '4'
|
||||
output:
|
||||
debug: false
|
||||
engine:
|
||||
name: udp
|
||||
udp:
|
||||
host: mayhem-party.home.blapointe.com
|
||||
port: 17070
|
||||
30
host.d/config.d/rusty-pipe.d/5.yaml
Normal file
30
host.d/config.d/rusty-pipe.d/5.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
streams:
|
||||
input:
|
||||
debug: false
|
||||
engine:
|
||||
name: gui
|
||||
gui:
|
||||
user: nat
|
||||
feedback:
|
||||
url: http://mayhem-party.home.blapointe.com:17071?user=nat
|
||||
press: {prefix: "", suffix: ""}
|
||||
release: {prefix: "", suffix: ""}
|
||||
format: '{"T":{{ms}},"U":"{{user}}","Y":"{{pressed}}","N":"{{released}}"}'
|
||||
buttons:
|
||||
up: 'w'
|
||||
down: 's'
|
||||
left: 'a'
|
||||
right: 'd'
|
||||
l: 'e'
|
||||
r: 'q'
|
||||
a: '1'
|
||||
b: '2'
|
||||
x: '3'
|
||||
y: '4'
|
||||
output:
|
||||
debug: false
|
||||
engine:
|
||||
name: udp
|
||||
udp:
|
||||
host: mayhem-party.home.blapointe.com
|
||||
port: 17070
|
||||
30
host.d/config.d/rusty-pipe.d/6.yaml
Normal file
30
host.d/config.d/rusty-pipe.d/6.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
streams:
|
||||
input:
|
||||
debug: false
|
||||
engine:
|
||||
name: gui
|
||||
gui:
|
||||
user: roxy
|
||||
feedback:
|
||||
url: http://mayhem-party.home.blapointe.com:17071?user=roxy
|
||||
press: {prefix: "", suffix: ""}
|
||||
release: {prefix: "", suffix: ""}
|
||||
format: '{"T":{{ms}},"U":"{{user}}","Y":"{{pressed}}","N":"{{released}}"}'
|
||||
buttons:
|
||||
up: 'w'
|
||||
down: 's'
|
||||
left: 'a'
|
||||
right: 'd'
|
||||
l: 'e'
|
||||
r: 'q'
|
||||
a: '1'
|
||||
b: '2'
|
||||
x: '3'
|
||||
y: '4'
|
||||
output:
|
||||
debug: false
|
||||
engine:
|
||||
name: udp
|
||||
udp:
|
||||
host: mayhem-party.home.blapointe.com
|
||||
port: 17070
|
||||
30
host.d/config.d/rusty-pipe.d/7.yaml
Normal file
30
host.d/config.d/rusty-pipe.d/7.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
streams:
|
||||
input:
|
||||
debug: false
|
||||
engine:
|
||||
name: gui
|
||||
gui:
|
||||
user: bill
|
||||
feedback:
|
||||
url: http://mayhem-party.home.blapointe.com:17071?user=bill
|
||||
press: {prefix: "", suffix: ""}
|
||||
release: {prefix: "", suffix: ""}
|
||||
format: '{"T":{{ms}},"U":"{{user}}","Y":"{{pressed}}","N":"{{released}}"}'
|
||||
buttons:
|
||||
up: 'w'
|
||||
down: 's'
|
||||
left: 'a'
|
||||
right: 'd'
|
||||
l: 'e'
|
||||
r: 'q'
|
||||
a: '1'
|
||||
b: '2'
|
||||
x: '3'
|
||||
y: '4'
|
||||
output:
|
||||
debug: false
|
||||
engine:
|
||||
name: udp
|
||||
udp:
|
||||
host: mayhem-party.home.blapointe.com
|
||||
port: 17070
|
||||
1
host.d/config.d/rusty-pipe.d/rusty-pipe.exe
Symbolic link
1
host.d/config.d/rusty-pipe.d/rusty-pipe.exe
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../rusty-pipe.d/target/x86_64-pc-windows-gnu/release/rusty-pipe.exe
|
||||
6
src/device/input/button/button.go
Normal file
6
src/device/input/button/button.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package button
|
||||
|
||||
type Button struct {
|
||||
Char byte
|
||||
Down bool
|
||||
}
|
||||
@@ -2,28 +2,20 @@ package input
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"mayhem-party/src/device/input/button"
|
||||
"mayhem-party/src/device/input/parse"
|
||||
"mayhem-party/src/device/input/raw"
|
||||
"mayhem-party/src/device/input/wrap"
|
||||
)
|
||||
|
||||
type Input interface {
|
||||
Read() []Button
|
||||
Read() []button.Button
|
||||
Close()
|
||||
}
|
||||
|
||||
func New(ctx context.Context) Input {
|
||||
foo := randomCharFromRange('a', 'g')
|
||||
if p, ok := os.LookupEnv("INPUT_RANDOM_WEIGHT_FILE"); ok && len(p) > 0 {
|
||||
foo = randomCharFromWeightFile(p)
|
||||
}
|
||||
var result Input = NewRandom(foo)
|
||||
if os.Getenv("INPUT_KEYBOARD") == "true" {
|
||||
result = NewKeyboard()
|
||||
}
|
||||
if os.Getenv("INPUT_BUFFERED") == "true" {
|
||||
result = NewBuffered(ctx, result)
|
||||
}
|
||||
if p, ok := os.LookupEnv("INPUT_REMAP_FILE"); ok && len(p) > 0 {
|
||||
result = NewRemapFromFile(result, p)
|
||||
}
|
||||
return result
|
||||
src := raw.New(ctx)
|
||||
return wrap.New(ctx, func() wrap.Wrap {
|
||||
return parse.New(ctx, src)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package input_test
|
||||
import (
|
||||
"context"
|
||||
"mayhem-party/src/device/input"
|
||||
"mayhem-party/src/device/input/raw"
|
||||
"mayhem-party/src/device/input/wrap"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
@@ -30,11 +32,11 @@ func TestNewRemapped(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
os.Setenv("INPUT_REMAP_FILE", remap)
|
||||
os.Setenv("INPUT_RANDOM_WEIGHT_FILE", rand)
|
||||
wrap.FlagRemapFile = remap
|
||||
raw.FlagRawRandomWeightFile = rand
|
||||
t.Cleanup(func() {
|
||||
os.Unsetenv("INPUT_REMAP_FILE")
|
||||
os.Unsetenv("INPUT_RANDOM_WEIGHT_FILE")
|
||||
wrap.FlagRemapFile = ""
|
||||
raw.FlagRawRandomWeightFile = ""
|
||||
})
|
||||
|
||||
r := input.New(context.Background())
|
||||
@@ -50,9 +52,9 @@ func TestNewRemapped(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewBuffered(t *testing.T) {
|
||||
os.Setenv("INPUT_BUFFERED", "true")
|
||||
wrap.FlagBuffered = true
|
||||
t.Cleanup(func() {
|
||||
os.Unsetenv("INPUT_BUFFERED")
|
||||
wrap.FlagBuffered = false
|
||||
})
|
||||
|
||||
r := input.New(context.Background())
|
||||
@@ -71,9 +73,9 @@ func TestNewRandomWeightFile(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
os.Setenv("INPUT_RANDOM_WEIGHT_FILE", p)
|
||||
raw.FlagRawRandomWeightFile = p
|
||||
t.Cleanup(func() {
|
||||
os.Unsetenv("INPUT_RANDOM_WEIGHT_FILE")
|
||||
raw.FlagRawRandomWeightFile = ""
|
||||
})
|
||||
|
||||
r := input.New(context.Background())
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package input
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestInput(t *testing.T) {
|
||||
var _ Input = &Random{}
|
||||
var _ Input = Keyboard{}
|
||||
var _ Input = &Buffered{}
|
||||
var _ Input = Remap{}
|
||||
}
|
||||
26
src/device/input/parse/parser.go
Normal file
26
src/device/input/parse/parser.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayhem-party/src/device/input/button"
|
||||
v01 "mayhem-party/src/device/input/parse/v01"
|
||||
"mayhem-party/src/device/input/raw"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
FlagParseV01 = os.Getenv("PARSE_V01") == "true"
|
||||
)
|
||||
|
||||
type Parser interface {
|
||||
Read() []button.Button
|
||||
Close()
|
||||
CloseWrap() raw.Raw
|
||||
}
|
||||
|
||||
func New(ctx context.Context, src raw.Raw) Parser {
|
||||
if FlagParseV01 {
|
||||
return v01.NewV01(ctx, src)
|
||||
}
|
||||
return NewPlaintext(src)
|
||||
}
|
||||
12
src/device/input/parse/parser_test.go
Normal file
12
src/device/input/parse/parser_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package parse_test
|
||||
|
||||
import (
|
||||
"mayhem-party/src/device/input/parse"
|
||||
v01 "mayhem-party/src/device/input/parse/v01"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
var _ parse.Parser = parse.Plaintext{}
|
||||
var _ parse.Parser = &v01.V01{}
|
||||
}
|
||||
48
src/device/input/parse/plaintext.go
Normal file
48
src/device/input/parse/plaintext.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package parse
|
||||
|
||||
import (
|
||||
"mayhem-party/src/device/input/button"
|
||||
"mayhem-party/src/device/input/raw"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
FlagParsePlaintextRelease = os.Getenv("PARSE_PLAINTEXT_RELEASE")
|
||||
)
|
||||
|
||||
type Plaintext struct {
|
||||
src raw.Raw
|
||||
release byte
|
||||
}
|
||||
|
||||
func NewPlaintext(src raw.Raw) Plaintext {
|
||||
releaseChar := byte('!')
|
||||
if FlagParsePlaintextRelease != "" {
|
||||
releaseChar = byte(FlagParsePlaintextRelease[0])
|
||||
}
|
||||
return Plaintext{
|
||||
src: src,
|
||||
release: releaseChar,
|
||||
}
|
||||
}
|
||||
|
||||
func (p Plaintext) Close() { p.src.Close() }
|
||||
|
||||
func (p Plaintext) CloseWrap() raw.Raw { return p.src }
|
||||
|
||||
func (p Plaintext) Read() []button.Button {
|
||||
b := p.src.Read()
|
||||
buttons := make([]button.Button, 0, len(b))
|
||||
down := true
|
||||
for i := range b {
|
||||
if b[i] == p.release {
|
||||
down = false
|
||||
} else {
|
||||
if b[i] != '\n' {
|
||||
buttons = append(buttons, button.Button{Char: b[i], Down: down})
|
||||
}
|
||||
down = true
|
||||
}
|
||||
}
|
||||
return buttons
|
||||
}
|
||||
30
src/device/input/parse/plaintext_test.go
Normal file
30
src/device/input/parse/plaintext_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package parse_test
|
||||
|
||||
import (
|
||||
"mayhem-party/src/device/input/button"
|
||||
"mayhem-party/src/device/input/parse"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPlaintext(t *testing.T) {
|
||||
src := constSrc("c!b")
|
||||
p := parse.NewPlaintext(src)
|
||||
got := p.Read()
|
||||
if len(got) != 2 {
|
||||
t.Fatal(len(got))
|
||||
}
|
||||
if got[0] != (button.Button{Char: 'c', Down: true}) {
|
||||
t.Error(got[0])
|
||||
}
|
||||
if got[1] != (button.Button{Char: 'b', Down: false}) {
|
||||
t.Error(got[1])
|
||||
}
|
||||
}
|
||||
|
||||
type constSrc string
|
||||
|
||||
func (c constSrc) Close() {}
|
||||
|
||||
func (c constSrc) Read() []byte {
|
||||
return []byte(c)
|
||||
}
|
||||
15
src/device/input/parse/v01/config.go
Normal file
15
src/device/input/parse/v01/config.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package v01
|
||||
|
||||
type config struct {
|
||||
Feedback struct {
|
||||
Addr string
|
||||
TTSURL string
|
||||
}
|
||||
Users map[string]struct {
|
||||
Player int
|
||||
Message string
|
||||
}
|
||||
Players []struct {
|
||||
Transformation transformation
|
||||
}
|
||||
}
|
||||
27
src/device/input/parse/v01/message.go
Normal file
27
src/device/input/parse/v01/message.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package v01
|
||||
|
||||
import (
|
||||
"log"
|
||||
"mayhem-party/src/device/input/button"
|
||||
)
|
||||
|
||||
type message struct {
|
||||
T int64
|
||||
U string
|
||||
Y string
|
||||
N string
|
||||
}
|
||||
|
||||
func (msg message) buttons() []button.Button {
|
||||
buttons := make([]button.Button, len(msg.Y)+len(msg.N))
|
||||
for i := range msg.Y {
|
||||
buttons[i] = button.Button{Char: msg.Y[i], Down: true}
|
||||
}
|
||||
for i := range msg.N {
|
||||
buttons[len(msg.Y)+i] = button.Button{Char: msg.N[i], Down: false}
|
||||
}
|
||||
if FlagDebug {
|
||||
log.Printf("%+v", msg)
|
||||
}
|
||||
return buttons
|
||||
}
|
||||
100
src/device/input/parse/v01/server.go
Normal file
100
src/device/input/parse/v01/server.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package v01
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"mayhem-party/src/device/input/wrap"
|
||||
"net/http"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (v01 *V01) listen() {
|
||||
if v01.cfg.Feedback.Addr == "" {
|
||||
return
|
||||
}
|
||||
v01._listen()
|
||||
}
|
||||
|
||||
func (v01 *V01) _listen() {
|
||||
mutex := &sync.RWMutex{}
|
||||
s := &http.Server{
|
||||
Addr: v01.cfg.Feedback.Addr,
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
} else {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
}
|
||||
v01.ServeHTTP(w, r)
|
||||
}),
|
||||
}
|
||||
go func() {
|
||||
<-v01.ctx.Done()
|
||||
log.Println("closing v01 server")
|
||||
s.Close()
|
||||
}()
|
||||
log.Println("starting v01 server")
|
||||
if err := s.ListenAndServe(); err != nil && v01.ctx.Err() == nil {
|
||||
log.Println("err with v01 server", err)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (v01 *V01) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
r = r.WithContext(v01.ctx)
|
||||
v01.serveHTTP(w, r)
|
||||
v01.globalQueries(r)
|
||||
}
|
||||
|
||||
func (v01 *V01) serveHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/":
|
||||
v01.getUserFeedback(w, r)
|
||||
case "/broadcast":
|
||||
v01.putBroadcast(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (v01 *V01) getUserFeedback(w http.ResponseWriter, r *http.Request) {
|
||||
user, ok := v01.cfg.Users[r.URL.Query().Get("user")]
|
||||
if !ok {
|
||||
user = v01.cfg.Users["broadcast"]
|
||||
}
|
||||
w.Write([]byte(user.Message))
|
||||
}
|
||||
|
||||
func (v01 *V01) putBroadcast(w http.ResponseWriter, r *http.Request) {
|
||||
b, _ := io.ReadAll(r.Body)
|
||||
v := v01.cfg.Users["broadcast"]
|
||||
v.Message = string(b)
|
||||
v01.cfg.Users["broadcast"] = v
|
||||
}
|
||||
|
||||
func (v01 *V01) globalQueries(r *http.Request) {
|
||||
v01.globalQuerySay(r)
|
||||
v01.globalQueryRefresh(r)
|
||||
}
|
||||
|
||||
func (v01 *V01) globalQuerySay(r *http.Request) {
|
||||
text := r.URL.Query().Get("say")
|
||||
if text == "" {
|
||||
text = r.Header.Get("say")
|
||||
}
|
||||
if text == "" {
|
||||
return
|
||||
}
|
||||
go v01.tts(text)
|
||||
}
|
||||
|
||||
func (v01 *V01) globalQueryRefresh(r *http.Request) {
|
||||
if _, ok := r.URL.Query()["refresh"]; !ok {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case wrap.ChSigUsr1 <- syscall.SIGUSR1:
|
||||
default:
|
||||
}
|
||||
}
|
||||
19
src/device/input/parse/v01/testdata/v01.yaml
vendored
Normal file
19
src/device/input/parse/v01/testdata/v01.yaml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
feedback:
|
||||
addr: :17071
|
||||
ttsurl: http://localhost:15002
|
||||
users:
|
||||
bel:
|
||||
player: 0
|
||||
message: "hi"
|
||||
players:
|
||||
- buttons:
|
||||
up: "w"
|
||||
down: "s"
|
||||
left: "a"
|
||||
right: "d"
|
||||
l: "q"
|
||||
r: "e"
|
||||
a: "1"
|
||||
b: "2"
|
||||
x: "3"
|
||||
y: "4"
|
||||
14
src/device/input/parse/v01/transform.go
Normal file
14
src/device/input/parse/v01/transform.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package v01
|
||||
|
||||
type (
|
||||
transformation map[string]string
|
||||
)
|
||||
|
||||
func (t transformation) pipe(s string) string {
|
||||
for i := range s {
|
||||
if v := t[s[i:i+1]]; v != "" {
|
||||
s = s[:i] + v[:1] + s[i+1:]
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
73
src/device/input/parse/v01/tts.go
Normal file
73
src/device/input/parse/v01/tts.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package v01
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/faiface/beep"
|
||||
"github.com/faiface/beep/effects"
|
||||
"github.com/faiface/beep/speaker"
|
||||
"github.com/faiface/beep/wav"
|
||||
)
|
||||
|
||||
var (
|
||||
ttsLock = &sync.RWMutex{}
|
||||
)
|
||||
|
||||
func (v01 *V01) tts(text string) {
|
||||
if err := v01._tts(text); err != nil {
|
||||
log.Printf("failed to tts: %s: %v", text, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (v01 *V01) _tts(text string) error {
|
||||
if v01.cfg.Feedback.TTSURL == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
url, err := url.Parse(v01.cfg.Feedback.TTSURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(url.Path) < 2 {
|
||||
url.Path = "/api/tts"
|
||||
}
|
||||
q := url.Query()
|
||||
if q.Get("voice") == "" {
|
||||
q.Set("voice", "en-us/glados-glow_tts")
|
||||
}
|
||||
if q.Get("lengthScale") == "" {
|
||||
q.Set("lengthScale", "1")
|
||||
}
|
||||
q.Set("text", text)
|
||||
url.RawQuery = q.Encode()
|
||||
|
||||
resp, err := http.Get(url.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode != http.StatusOK || resp.Header.Get("Content-Type") != "audio/wav" {
|
||||
return fmt.Errorf("failed to call ttsurl: (%d) %s", resp.StatusCode, b)
|
||||
}
|
||||
|
||||
decoder, format, err := wav.Decode(bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ttsLock.Lock()
|
||||
defer ttsLock.Unlock()
|
||||
speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/30))
|
||||
speaker.Play(&effects.Volume{Streamer: beep.ResampleRatio(4, 1, &beep.Ctrl{Streamer: beep.Loop(1, decoder)})})
|
||||
duration := time.Duration(decoder.Len()) * format.SampleRate.D(1)
|
||||
time.Sleep(duration)
|
||||
return nil
|
||||
}
|
||||
86
src/device/input/parse/v01/v01.go
Normal file
86
src/device/input/parse/v01/v01.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package v01
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mayhem-party/src/device/input/button"
|
||||
"mayhem-party/src/device/input/raw"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
FlagDebug = os.Getenv("DEBUG") == "true"
|
||||
FlagParseV01Config = os.Getenv("PARSE_V01_CONFIG")
|
||||
)
|
||||
|
||||
type (
|
||||
V01 struct {
|
||||
ctx context.Context
|
||||
can context.CancelFunc
|
||||
src raw.Raw
|
||||
cfg config
|
||||
}
|
||||
)
|
||||
|
||||
func NewV01(ctx context.Context, src raw.Raw) *V01 {
|
||||
var cfg config
|
||||
b, _ := ioutil.ReadFile(FlagParseV01Config)
|
||||
yaml.Unmarshal(b, &cfg)
|
||||
ctx, can := context.WithCancel(ctx)
|
||||
result := &V01{
|
||||
ctx: ctx,
|
||||
can: can,
|
||||
src: src,
|
||||
cfg: cfg,
|
||||
}
|
||||
go result.listen()
|
||||
return result
|
||||
}
|
||||
|
||||
func (v01 *V01) CloseWrap() raw.Raw {
|
||||
v01.can()
|
||||
return v01.src
|
||||
}
|
||||
|
||||
func (v01 *V01) Close() {
|
||||
v01.can()
|
||||
v01.src.Close()
|
||||
}
|
||||
|
||||
func (v01 *V01) Read() []button.Button {
|
||||
line := v01.src.Read()
|
||||
var msg message
|
||||
if err := json.Unmarshal(line, &msg); err != nil {
|
||||
log.Printf("%v: %s", err, line)
|
||||
}
|
||||
v01.telemetry(msg)
|
||||
|
||||
return v01.transform(msg).buttons()
|
||||
}
|
||||
|
||||
func (v01 *V01) telemetry(msg message) {
|
||||
if FlagDebug {
|
||||
log.Printf("%s|%dms", msg.U, time.Now().UnixNano()/int64(time.Millisecond)-msg.T)
|
||||
}
|
||||
}
|
||||
|
||||
func (v01 *V01) transform(msg message) message {
|
||||
if len(v01.cfg.Players) == 0 {
|
||||
return msg
|
||||
}
|
||||
user := v01.cfg.Users[msg.U]
|
||||
if user.Player < 1 {
|
||||
msg.Y = ""
|
||||
msg.N = ""
|
||||
return msg
|
||||
}
|
||||
player := v01.cfg.Players[user.Player-1]
|
||||
msg.Y = player.Transformation.pipe(msg.Y)
|
||||
msg.N = player.Transformation.pipe(msg.N)
|
||||
return msg
|
||||
}
|
||||
190
src/device/input/parse/v01/v01_exported_test.go
Normal file
190
src/device/input/parse/v01/v01_exported_test.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package v01_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"mayhem-party/src/device/input/button"
|
||||
v01 "mayhem-party/src/device/input/parse/v01"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestV01(t *testing.T) {
|
||||
src := constSrc(fmt.Sprintf(`{"T":%v,"U":"bel","Y":"abc","N":"cde"}`, time.Now().UnixNano()/int64(time.Millisecond)-50))
|
||||
t.Logf("(%v) %s", len(src), src.Read())
|
||||
v01 := v01.NewV01(context.Background(), src)
|
||||
defer v01.Close()
|
||||
got := v01.Read()
|
||||
want := []button.Button{
|
||||
{Down: true, Char: 'a'},
|
||||
{Down: true, Char: 'b'},
|
||||
{Down: true, Char: 'c'},
|
||||
{Down: false, Char: 'c'},
|
||||
{Down: false, Char: 'd'},
|
||||
{Down: false, Char: 'e'},
|
||||
}
|
||||
if len(got) != len(want) {
|
||||
t.Fatal(len(want), len(got))
|
||||
}
|
||||
for i := range got {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("[%d] want %+v got %+v", i, want[i], got[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestV01WithCfg(t *testing.T) {
|
||||
d := t.TempDir()
|
||||
p := path.Join(d, "cfg.yaml")
|
||||
os.WriteFile(p, []byte(`
|
||||
users:
|
||||
bel:
|
||||
player: 2
|
||||
players:
|
||||
- transformation:
|
||||
w: t
|
||||
- transformation:
|
||||
w: i
|
||||
`), os.ModePerm)
|
||||
v01.FlagParseV01Config = p
|
||||
|
||||
t.Run("unknown user ignored", func(t *testing.T) {
|
||||
v01 := v01.NewV01(context.Background(), constSrc(`{"U":"qt","Y":"w"}`))
|
||||
defer v01.Close()
|
||||
got := v01.Read()
|
||||
if len(got) != 0 {
|
||||
t.Error(got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("player2", func(t *testing.T) {
|
||||
v01 := v01.NewV01(context.Background(), constSrc(`{"U":"bel","Y":"w","N":"w"}`))
|
||||
defer v01.Close()
|
||||
got := v01.Read()
|
||||
if len(got) != 2 {
|
||||
t.Error(got)
|
||||
}
|
||||
if got[0] != (button.Button{Char: 'i', Down: true}) {
|
||||
t.Error(got[0])
|
||||
}
|
||||
if got[1] != (button.Button{Char: 'i', Down: false}) {
|
||||
t.Error(got[1])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestV01Feedback(t *testing.T) {
|
||||
d := t.TempDir()
|
||||
p := path.Join(d, "cfg.yaml")
|
||||
os.WriteFile(p, []byte(`
|
||||
feedback:
|
||||
addr: :27071
|
||||
ttsurl: http://localhost:15002
|
||||
users:
|
||||
bel:
|
||||
player: 2
|
||||
message: to bel
|
||||
broadcast:
|
||||
message: to everyone
|
||||
players:
|
||||
- transformation:
|
||||
w: t
|
||||
- transformation:
|
||||
w: i
|
||||
`), os.ModePerm)
|
||||
v01.FlagParseV01Config = p
|
||||
ctx, can := context.WithCancel(context.Background())
|
||||
defer can()
|
||||
|
||||
v01 := v01.NewV01(ctx, constSrc(`{"U":"qt","Y":"w"}`))
|
||||
defer v01.Close()
|
||||
|
||||
for {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
resp, err := http.Get("http://localhost:27071?user=bel")
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
resp.Body.Close()
|
||||
break
|
||||
}
|
||||
|
||||
t.Run("specific user", func(t *testing.T) {
|
||||
resp, err := http.Get("http://localhost:27071?user=bel")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
if string(b) != "to bel" {
|
||||
t.Error(b)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("broadcast", func(t *testing.T) {
|
||||
resp, err := http.Get("http://localhost:27071")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
if string(b) != "to everyone" {
|
||||
t.Error(b)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("change broadcast", func(t *testing.T) {
|
||||
want := `my new broadcast`
|
||||
r, _ := http.NewRequest(http.MethodPut, "http://localhost:27071/broadcast", strings.NewReader(want))
|
||||
resp, err := http.DefaultClient.Do(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
resp, err = http.Get("http://localhost:27071")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
if string(b) != want {
|
||||
t.Error(string(b))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("tts", func(t *testing.T) {
|
||||
if os.Getenv("INTEGRATION_TTS") != "true" {
|
||||
t.Skip("$INTEGRATION_TTS is not true")
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
resp, err := http.Get("http://localhost:27071/?say=hello%20world")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
time.Sleep(time.Millisecond * 2500)
|
||||
r, _ := http.NewRequest(http.MethodGet, "http://localhost:27071", nil)
|
||||
r.Header.Set("say", "No, HTTP does not define any limit. However most web servers do limit size of headers they accept. For example in Apache default limit is 8KB, in IIS it's 16K. Server will return 413 Entity Too Large error if headers size exceeds that limit.")
|
||||
resp, err := http.DefaultClient.Do(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
time.Sleep(time.Millisecond * 8500)
|
||||
})
|
||||
}
|
||||
|
||||
type constSrc string
|
||||
|
||||
func (c constSrc) Close() {}
|
||||
|
||||
func (c constSrc) Read() []byte {
|
||||
return []byte(c)
|
||||
}
|
||||
51
src/device/input/parse/v01/v01_test.go
Normal file
51
src/device/input/parse/v01/v01_test.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package v01
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestV01TransformationPipe(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
input string
|
||||
xform map[string]string
|
||||
want string
|
||||
}{
|
||||
"empty input": {
|
||||
xform: map[string]string{"a": "bc"},
|
||||
},
|
||||
"empty xform": {
|
||||
input: "aa",
|
||||
want: "aa",
|
||||
},
|
||||
"all": {
|
||||
input: "aa",
|
||||
xform: map[string]string{"a": "cc"},
|
||||
want: "cc",
|
||||
},
|
||||
"last": {
|
||||
input: "ba",
|
||||
xform: map[string]string{"a": "cc"},
|
||||
want: "bc",
|
||||
},
|
||||
"first": {
|
||||
input: "ab",
|
||||
xform: map[string]string{"a": "cc"},
|
||||
want: "cb",
|
||||
},
|
||||
"noop": {
|
||||
input: "bb",
|
||||
xform: map[string]string{"a": "bc"},
|
||||
want: "bb",
|
||||
},
|
||||
}
|
||||
|
||||
for name, d := range cases {
|
||||
c := d
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := transformation(c.xform).pipe(c.input)
|
||||
if got != c.want {
|
||||
t.Errorf("%+v(%s) want %s got %s", c.xform, c.input, c.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
package input
|
||||
package raw
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
@@ -43,16 +44,14 @@ func (kb Keyboard) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (kb Keyboard) Read() []Button {
|
||||
func (kb Keyboard) Read() []byte {
|
||||
b := make([]byte, 5)
|
||||
n, err := os.Stdin.Read(b)
|
||||
if err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
result := make([]Button, n)
|
||||
for i := range result {
|
||||
result[i] = Button{Char: b[i], Down: true}
|
||||
if FlagDebug {
|
||||
log.Printf("raw.Keyboard.Read() %s", b[:n])
|
||||
}
|
||||
return result
|
||||
return b[:n]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package input
|
||||
package raw
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -6,19 +6,15 @@ import (
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/go-yaml/yaml"
|
||||
)
|
||||
|
||||
type Button struct {
|
||||
Char byte
|
||||
Down bool
|
||||
}
|
||||
|
||||
type Random struct {
|
||||
generator func() byte
|
||||
down []Button
|
||||
down []byte
|
||||
}
|
||||
|
||||
func NewRandom(generator func() byte) *Random {
|
||||
@@ -29,19 +25,8 @@ func NewRandom(generator func() byte) *Random {
|
||||
func (r *Random) Close() {
|
||||
}
|
||||
|
||||
func (r *Random) Read() []Button {
|
||||
if len(r.down) > 0 && rand.Int()%2 == 0 {
|
||||
was := r.down
|
||||
for i := range was {
|
||||
was[i].Down = false
|
||||
}
|
||||
r.down = r.down[:0]
|
||||
return was
|
||||
} else {
|
||||
c := Button{Char: r.generator(), Down: true}
|
||||
r.down = append(r.down, c)
|
||||
return []Button{c}
|
||||
}
|
||||
func (r *Random) Read() []byte {
|
||||
return []byte{r.generator()}
|
||||
}
|
||||
|
||||
func randomCharFromRange(start, stop byte) func() byte {
|
||||
@@ -87,11 +72,15 @@ func randomCharFromWeights(m map[byte]int) func() byte {
|
||||
}
|
||||
sum += v
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].i < result[j].i
|
||||
})
|
||||
if sum <= 0 {
|
||||
panic("weights must total nonzero")
|
||||
}
|
||||
return func() byte {
|
||||
n := rand.Int() % sum
|
||||
r := rand.Int()
|
||||
n := r % (sum + 1)
|
||||
for _, v := range result {
|
||||
n -= v.i
|
||||
if n <= 0 {
|
||||
@@ -1,4 +1,4 @@
|
||||
package input
|
||||
package raw
|
||||
|
||||
import (
|
||||
"strings"
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
func TestRandomCharFromWeights(t *testing.T) {
|
||||
weights := map[byte]int{
|
||||
'a': 1,
|
||||
'b': 99,
|
||||
'b': 20,
|
||||
}
|
||||
foo := randomCharFromWeights(weights)
|
||||
for {
|
||||
32
src/device/input/raw/raw.go
Normal file
32
src/device/input/raw/raw.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package raw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
FlagRawKeyboard = os.Getenv("RAW_KEYBOARD") == "true"
|
||||
FlagRawUDP = os.Getenv("RAW_UDP")
|
||||
FlagRawRandomWeightFile = os.Getenv("RAW_RANDOM_WEIGHT_FILE")
|
||||
)
|
||||
|
||||
type Raw interface {
|
||||
Read() []byte
|
||||
Close()
|
||||
}
|
||||
|
||||
func New(ctx context.Context) Raw {
|
||||
if FlagRawKeyboard {
|
||||
return NewKeyboard()
|
||||
}
|
||||
if port, _ := strconv.Atoi(FlagRawUDP); port != 0 {
|
||||
return NewUDP(ctx, port)
|
||||
}
|
||||
generator := randomCharFromRange('a', 'g')
|
||||
if FlagRawRandomWeightFile != "" {
|
||||
generator = randomCharFromWeightFile(FlagRawRandomWeightFile)
|
||||
}
|
||||
return NewRandom(generator)
|
||||
}
|
||||
9
src/device/input/raw/raw_test.go
Normal file
9
src/device/input/raw/raw_test.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package raw
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRaw(t *testing.T) {
|
||||
var _ Raw = &Random{}
|
||||
var _ Raw = UDP{}
|
||||
var _ Raw = Keyboard{}
|
||||
}
|
||||
63
src/device/input/raw/udp.go
Normal file
63
src/device/input/raw/udp.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package raw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
FlagDebug = os.Getenv("DEBUG") == "true"
|
||||
)
|
||||
|
||||
type UDP struct {
|
||||
conn net.PacketConn
|
||||
c chan []byte
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewUDP(ctx context.Context, port int) UDP {
|
||||
conn, err := net.ListenPacket("udp", ":"+strconv.Itoa(port))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
result := UDP{
|
||||
conn: conn,
|
||||
c: make(chan []byte, 8),
|
||||
ctx: ctx,
|
||||
}
|
||||
go result.listen()
|
||||
return result
|
||||
}
|
||||
|
||||
func (udp UDP) listen() {
|
||||
for udp.ctx.Err() == nil {
|
||||
buff := make([]byte, 256)
|
||||
n, _, err := udp.conn.ReadFrom(buff)
|
||||
if err != nil && udp.ctx.Err() == nil {
|
||||
panic(err)
|
||||
}
|
||||
if FlagDebug {
|
||||
log.Printf("raw.UDP.Read() => %s", buff[:n])
|
||||
}
|
||||
select {
|
||||
case udp.c <- buff[:n]:
|
||||
case <-udp.ctx.Done():
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (udp UDP) Read() []byte {
|
||||
select {
|
||||
case v := <-udp.c:
|
||||
return v
|
||||
case <-udp.ctx.Done():
|
||||
return []byte{}
|
||||
}
|
||||
}
|
||||
|
||||
func (udp UDP) Close() {
|
||||
udp.conn.Close()
|
||||
}
|
||||
@@ -1,31 +1,42 @@
|
||||
package input
|
||||
package wrap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayhem-party/src/device/input/button"
|
||||
"mayhem-party/src/device/input/raw"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
FlagBufferedStickyDuration = os.Getenv("WRAP_BUFFERED_STICKY_DURATION")
|
||||
)
|
||||
|
||||
type Buffered struct {
|
||||
ctx context.Context
|
||||
can context.CancelFunc
|
||||
lock sync.Mutex
|
||||
keys map[byte]int64
|
||||
input Input
|
||||
input Wrap
|
||||
listenInterval time.Duration
|
||||
expirationInterval time.Duration
|
||||
}
|
||||
|
||||
func NewBuffered(ctx context.Context, input Input) *Buffered {
|
||||
func NewBuffered(ctx context.Context, input Wrap) *Buffered {
|
||||
ctx, can := context.WithCancel(ctx)
|
||||
expirationInterval := time.Millisecond * 125
|
||||
if d, err := time.ParseDuration(FlagBufferedStickyDuration); err == nil {
|
||||
expirationInterval = d
|
||||
}
|
||||
result := &Buffered{
|
||||
input: input,
|
||||
ctx: ctx,
|
||||
can: can,
|
||||
lock: sync.Mutex{},
|
||||
keys: map[byte]int64{},
|
||||
listenInterval: time.Millisecond * 20,
|
||||
expirationInterval: time.Millisecond * 100,
|
||||
listenInterval: time.Millisecond * 10,
|
||||
expirationInterval: expirationInterval,
|
||||
}
|
||||
go result.listen()
|
||||
return result
|
||||
@@ -39,6 +50,8 @@ func (b *Buffered) listen() {
|
||||
for i := range buttons {
|
||||
if buttons[i].Down {
|
||||
b.keys[buttons[i].Char] = time.Now().UnixNano()
|
||||
} else {
|
||||
b.keys[buttons[i].Char] = 0
|
||||
}
|
||||
}
|
||||
b.lock.Unlock()
|
||||
@@ -49,12 +62,17 @@ func (b *Buffered) listen() {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Buffered) CloseWrap() raw.Raw {
|
||||
b.can()
|
||||
return b.input.CloseWrap()
|
||||
}
|
||||
|
||||
func (b *Buffered) Close() {
|
||||
b.input.Close()
|
||||
b.can()
|
||||
}
|
||||
|
||||
func (b *Buffered) Read() []Button {
|
||||
func (b *Buffered) Read() []button.Button {
|
||||
for b.ctx.Err() == nil {
|
||||
result := b.read()
|
||||
if len(result) > 0 {
|
||||
@@ -65,19 +83,19 @@ func (b *Buffered) Read() []Button {
|
||||
case <-time.After(b.listenInterval):
|
||||
}
|
||||
}
|
||||
return []Button{}
|
||||
return []button.Button{}
|
||||
}
|
||||
|
||||
func (b *Buffered) read() []Button {
|
||||
func (b *Buffered) read() []button.Button {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
result := make([]Button, 0, len(b.keys))
|
||||
result := make([]button.Button, 0, len(b.keys))
|
||||
for k, v := range b.keys {
|
||||
isFresh := v > 0
|
||||
isStale := v < 0 && time.Since(time.Unix(0, -1*v)) > b.expirationInterval
|
||||
if isFresh || isStale {
|
||||
result = append(result, Button{Char: k, Down: isFresh})
|
||||
result = append(result, button.Button{Char: k, Down: isFresh})
|
||||
}
|
||||
if isFresh {
|
||||
b.keys[k] = -1 * v
|
||||
64
src/device/input/wrap/refresh.go
Normal file
64
src/device/input/wrap/refresh.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package wrap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"mayhem-party/src/device/input/button"
|
||||
"mayhem-party/src/device/input/raw"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
ChSigUsr1 = func() chan os.Signal {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGUSR1)
|
||||
return c
|
||||
}()
|
||||
)
|
||||
|
||||
type Refresh struct {
|
||||
can context.CancelFunc
|
||||
input Wrap
|
||||
}
|
||||
|
||||
func NewRefresh(ctx context.Context, newWrap func() Wrap) *Refresh {
|
||||
return NewRefreshWith(ctx, newWrap, ChSigUsr1)
|
||||
}
|
||||
|
||||
func NewRefreshWith(ctx context.Context, newWrap func() Wrap, ch <-chan os.Signal) *Refresh {
|
||||
ctx, can := context.WithCancel(ctx)
|
||||
result := &Refresh{
|
||||
can: can,
|
||||
input: newWrap(),
|
||||
}
|
||||
go func() {
|
||||
defer log.Println("refreshing done")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case sig := <-ch:
|
||||
log.Println("refreshing for", sig)
|
||||
result.input.CloseWrap()
|
||||
result.input = newWrap()
|
||||
}
|
||||
}
|
||||
}()
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *Refresh) CloseWrap() raw.Raw {
|
||||
r.can()
|
||||
return r.input.CloseWrap()
|
||||
}
|
||||
|
||||
func (r *Refresh) Read() []button.Button {
|
||||
return r.input.Read()
|
||||
}
|
||||
|
||||
func (r *Refresh) Close() {
|
||||
r.can()
|
||||
r.input.Close()
|
||||
}
|
||||
101
src/device/input/wrap/refresh_test.go
Normal file
101
src/device/input/wrap/refresh_test.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package wrap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayhem-party/src/device/input/button"
|
||||
"mayhem-party/src/device/input/raw"
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRefresh(t *testing.T) {
|
||||
b := byte('a')
|
||||
generator := func() Wrap {
|
||||
b += byte(1)
|
||||
return dummyParser{Char: b}
|
||||
}
|
||||
ch := make(chan os.Signal, 1)
|
||||
defer close(ch)
|
||||
refresh := NewRefreshWith(context.Background(), generator, ch)
|
||||
defer refresh.Close()
|
||||
|
||||
assertIts := func(t *testing.T, b byte) {
|
||||
for i := 0; i < 10; i++ {
|
||||
some := false
|
||||
for j, button := range refresh.Read() {
|
||||
some = true
|
||||
if button.Char != b {
|
||||
t.Error(i, j, b, button)
|
||||
}
|
||||
}
|
||||
if !some {
|
||||
t.Error("empty read")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("called once on init", func(t *testing.T) {
|
||||
assertIts(t, byte('b'))
|
||||
})
|
||||
ch <- syscall.SIGUSR1
|
||||
time.Sleep(time.Millisecond * 450)
|
||||
t.Run("called once on signal", func(t *testing.T) {
|
||||
assertIts(t, byte('c'))
|
||||
})
|
||||
}
|
||||
|
||||
func TestRefreshDoesntCloseSources(t *testing.T) {
|
||||
src := &telemetrySrc{}
|
||||
newParsers := 0
|
||||
newParser := func() Wrap {
|
||||
newParsers += 1
|
||||
return src
|
||||
}
|
||||
ctx, can := context.WithCancel(context.Background())
|
||||
defer can()
|
||||
refresh := NewRefresh(ctx, newParser)
|
||||
if newParsers != 1 {
|
||||
t.Error(newParsers)
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
refresh.Read()
|
||||
}
|
||||
if want := (telemetrySrc{reads: 5}); *src != want {
|
||||
t.Errorf("%+v", *src)
|
||||
} else if newParsers != 1 {
|
||||
t.Error(newParsers)
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
ChSigUsr1 <- syscall.SIGINT
|
||||
}
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
if want := (telemetrySrc{reads: 5, closeWraps: 5}); *src != want {
|
||||
t.Errorf("want %+v, got %+v", want, *src)
|
||||
} else if newParsers != 6 {
|
||||
t.Error(newParsers)
|
||||
}
|
||||
}
|
||||
|
||||
type telemetrySrc struct {
|
||||
closeWraps int
|
||||
closes int
|
||||
reads int
|
||||
}
|
||||
|
||||
func (src *telemetrySrc) CloseWrap() raw.Raw {
|
||||
src.closeWraps += 1
|
||||
return nil
|
||||
}
|
||||
|
||||
func (src *telemetrySrc) Close() {
|
||||
src.closes += 1
|
||||
}
|
||||
|
||||
func (src *telemetrySrc) Read() []button.Button {
|
||||
src.reads += 1
|
||||
return nil
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
package input
|
||||
package wrap
|
||||
|
||||
import (
|
||||
"mayhem-party/src/device/input/button"
|
||||
"mayhem-party/src/device/input/raw"
|
||||
"os"
|
||||
|
||||
"github.com/go-yaml/yaml"
|
||||
)
|
||||
|
||||
type Remap struct {
|
||||
input Input
|
||||
input Wrap
|
||||
m map[byte]byte
|
||||
}
|
||||
|
||||
func NewRemapFromFile(input Input, p string) Remap {
|
||||
func NewRemapFromFile(input Wrap, p string) Remap {
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -30,18 +32,22 @@ func NewRemapFromFile(input Input, p string) Remap {
|
||||
return NewRemap(input, remap)
|
||||
}
|
||||
|
||||
func NewRemap(input Input, m map[byte]byte) Remap {
|
||||
func NewRemap(input Wrap, m map[byte]byte) Remap {
|
||||
return Remap{
|
||||
input: input,
|
||||
m: m,
|
||||
}
|
||||
}
|
||||
|
||||
func (re Remap) CloseWrap() raw.Raw {
|
||||
return re.input.CloseWrap()
|
||||
}
|
||||
|
||||
func (re Remap) Close() {
|
||||
re.input.Close()
|
||||
}
|
||||
|
||||
func (re Remap) Read() []Button {
|
||||
func (re Remap) Read() []button.Button {
|
||||
result := re.input.Read()
|
||||
for i := range result {
|
||||
if v, ok := re.m[result[i].Char]; ok {
|
||||
45
src/device/input/wrap/wrap.go
Normal file
45
src/device/input/wrap/wrap.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package wrap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayhem-party/src/device/input/button"
|
||||
"mayhem-party/src/device/input/raw"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
FlagBuffered = os.Getenv("WRAP_BUFFERED") == "true"
|
||||
FlagRemapFile = os.Getenv("WRAP_REMAP_FILE")
|
||||
FlagRefreshOnSigUsr1 = os.Getenv("WRAP_REFRESH_ON_SIGUSR1") == "true"
|
||||
)
|
||||
|
||||
type Wrap interface {
|
||||
Read() []button.Button
|
||||
Close()
|
||||
CloseWrap() raw.Raw
|
||||
}
|
||||
|
||||
func New(ctx context.Context, parserFunc func() Wrap) Wrap {
|
||||
maker := func() Wrap {
|
||||
return parserFunc()
|
||||
}
|
||||
if FlagBuffered {
|
||||
oldMaker := maker
|
||||
maker = func() Wrap {
|
||||
return NewBuffered(ctx, oldMaker())
|
||||
}
|
||||
}
|
||||
if FlagRemapFile != "" {
|
||||
oldMaker := maker
|
||||
maker = func() Wrap {
|
||||
return NewRemapFromFile(oldMaker(), FlagRemapFile)
|
||||
}
|
||||
}
|
||||
if FlagRefreshOnSigUsr1 {
|
||||
oldMaker := maker
|
||||
maker = func() Wrap {
|
||||
return NewRefresh(ctx, oldMaker)
|
||||
}
|
||||
}
|
||||
return maker()
|
||||
}
|
||||
22
src/device/input/wrap/wrap_test.go
Normal file
22
src/device/input/wrap/wrap_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package wrap
|
||||
|
||||
import (
|
||||
"mayhem-party/src/device/input/button"
|
||||
"mayhem-party/src/device/input/raw"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWrap(t *testing.T) {
|
||||
var _ Wrap = dummyParser{}
|
||||
var _ Wrap = &Refresh{}
|
||||
var _ Wrap = &Buffered{}
|
||||
var _ Wrap = &Remap{}
|
||||
}
|
||||
|
||||
type dummyParser button.Button
|
||||
|
||||
func (d dummyParser) CloseWrap() raw.Raw { return nil }
|
||||
func (d dummyParser) Close() {}
|
||||
func (d dummyParser) Read() []button.Button {
|
||||
return []button.Button{button.Button(d)}
|
||||
}
|
||||
@@ -28,6 +28,31 @@ var (
|
||||
'x': X,
|
||||
'y': Y,
|
||||
'z': Z,
|
||||
'1': N1,
|
||||
'2': N2,
|
||||
'3': N3,
|
||||
'4': N4,
|
||||
'5': N5,
|
||||
'6': N6,
|
||||
'7': N7,
|
||||
'8': N8,
|
||||
'9': N9,
|
||||
'0': N0,
|
||||
'!': F1,
|
||||
'@': F2,
|
||||
'#': F3,
|
||||
'$': F4,
|
||||
'%': F5,
|
||||
'^': F6,
|
||||
'&': F7,
|
||||
'*': F8,
|
||||
'(': F9,
|
||||
')': F10,
|
||||
',': PComma,
|
||||
'/': PFSlash,
|
||||
';': PSemicolon,
|
||||
'-': PMinus,
|
||||
'=': PEqual,
|
||||
}
|
||||
keyToChar = func() map[Key]byte {
|
||||
result := map[Key]byte{}
|
||||
@@ -47,7 +72,7 @@ func ToChar(k Key) byte {
|
||||
}
|
||||
|
||||
func FromChar(b byte) Key {
|
||||
if b < 'a' {
|
||||
if 'A' <= b && b <= 'Z' {
|
||||
b += 'a' - 'A'
|
||||
}
|
||||
v, ok := charToKey[b]
|
||||
|
||||
@@ -3,13 +3,16 @@ package key
|
||||
import "testing"
|
||||
|
||||
func TestFromChar(t *testing.T) {
|
||||
if got := FromChar('1'); got != N1 {
|
||||
t.Error(got)
|
||||
}
|
||||
if got := FromChar('a'); got != A {
|
||||
t.Error(got)
|
||||
}
|
||||
if got := FromChar('A'); got != A {
|
||||
t.Error(got)
|
||||
}
|
||||
if got := FromChar('!'); got != Undef {
|
||||
if got := FromChar(byte(0)); got != Undef {
|
||||
t.Error(got)
|
||||
}
|
||||
if got := ToChar(A); got != 'a' {
|
||||
|
||||
@@ -5,31 +5,56 @@ import "github.com/micmonay/keybd_event"
|
||||
type Key int
|
||||
|
||||
const (
|
||||
Undef = Key(keybd_event.VK_SP11)
|
||||
A = Key(keybd_event.VK_A)
|
||||
B = Key(keybd_event.VK_B)
|
||||
C = Key(keybd_event.VK_C)
|
||||
D = Key(keybd_event.VK_D)
|
||||
E = Key(keybd_event.VK_E)
|
||||
F = Key(keybd_event.VK_F)
|
||||
G = Key(keybd_event.VK_G)
|
||||
H = Key(keybd_event.VK_H)
|
||||
I = Key(keybd_event.VK_I)
|
||||
J = Key(keybd_event.VK_J)
|
||||
K = Key(keybd_event.VK_K)
|
||||
L = Key(keybd_event.VK_L)
|
||||
M = Key(keybd_event.VK_M)
|
||||
N = Key(keybd_event.VK_N)
|
||||
O = Key(keybd_event.VK_O)
|
||||
P = Key(keybd_event.VK_P)
|
||||
Q = Key(keybd_event.VK_Q)
|
||||
R = Key(keybd_event.VK_R)
|
||||
S = Key(keybd_event.VK_S)
|
||||
T = Key(keybd_event.VK_T)
|
||||
U = Key(keybd_event.VK_U)
|
||||
V = Key(keybd_event.VK_V)
|
||||
W = Key(keybd_event.VK_W)
|
||||
X = Key(keybd_event.VK_X)
|
||||
Y = Key(keybd_event.VK_Y)
|
||||
Z = Key(keybd_event.VK_Z)
|
||||
Undef = Key(keybd_event.VK_SP11)
|
||||
A = Key(keybd_event.VK_A)
|
||||
B = Key(keybd_event.VK_B)
|
||||
C = Key(keybd_event.VK_C)
|
||||
D = Key(keybd_event.VK_D)
|
||||
E = Key(keybd_event.VK_E)
|
||||
F = Key(keybd_event.VK_F)
|
||||
G = Key(keybd_event.VK_G)
|
||||
H = Key(keybd_event.VK_H)
|
||||
I = Key(keybd_event.VK_I)
|
||||
J = Key(keybd_event.VK_J)
|
||||
K = Key(keybd_event.VK_K)
|
||||
L = Key(keybd_event.VK_L)
|
||||
M = Key(keybd_event.VK_M)
|
||||
N = Key(keybd_event.VK_N)
|
||||
O = Key(keybd_event.VK_O)
|
||||
P = Key(keybd_event.VK_P)
|
||||
Q = Key(keybd_event.VK_Q)
|
||||
R = Key(keybd_event.VK_R)
|
||||
S = Key(keybd_event.VK_S)
|
||||
T = Key(keybd_event.VK_T)
|
||||
U = Key(keybd_event.VK_U)
|
||||
V = Key(keybd_event.VK_V)
|
||||
W = Key(keybd_event.VK_W)
|
||||
X = Key(keybd_event.VK_X)
|
||||
Y = Key(keybd_event.VK_Y)
|
||||
Z = Key(keybd_event.VK_Z)
|
||||
N1 = Key(keybd_event.VK_1)
|
||||
N2 = Key(keybd_event.VK_2)
|
||||
N3 = Key(keybd_event.VK_3)
|
||||
N4 = Key(keybd_event.VK_4)
|
||||
N5 = Key(keybd_event.VK_5)
|
||||
N6 = Key(keybd_event.VK_6)
|
||||
N7 = Key(keybd_event.VK_7)
|
||||
N8 = Key(keybd_event.VK_8)
|
||||
N9 = Key(keybd_event.VK_9)
|
||||
N0 = Key(keybd_event.VK_0)
|
||||
F1 = Key(keybd_event.VK_F1)
|
||||
F2 = Key(keybd_event.VK_F2)
|
||||
F3 = Key(keybd_event.VK_F3)
|
||||
F4 = Key(keybd_event.VK_F4)
|
||||
F5 = Key(keybd_event.VK_F5)
|
||||
F6 = Key(keybd_event.VK_F6)
|
||||
F7 = Key(keybd_event.VK_F7)
|
||||
F8 = Key(keybd_event.VK_F8)
|
||||
F9 = Key(keybd_event.VK_F9)
|
||||
F10 = Key(keybd_event.VK_F10)
|
||||
PComma = Key(keybd_event.VK_COMMA)
|
||||
PFSlash = Key(keybd_event.VK_BACKSLASH)
|
||||
PSemicolon = Key(keybd_event.VK_SEMICOLON)
|
||||
PMinus = Key(keybd_event.VK_MINUS)
|
||||
PEqual = Key(keybd_event.VK_EQUAL)
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ package src
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"mayhem-party/src/device/input"
|
||||
"mayhem-party/src/device/output"
|
||||
"mayhem-party/src/device/output/key"
|
||||
@@ -16,7 +17,7 @@ func Main(ctx context.Context) error {
|
||||
defer reader.Close()
|
||||
|
||||
interval := time.Millisecond * 50
|
||||
if intervalS, ok := os.LookupEnv("MAIN_INTERVAL_DURATION"); !ok {
|
||||
if intervalS := os.Getenv("MAIN_INTERVAL_DURATION"); intervalS != "" {
|
||||
} else if v, err := time.ParseDuration(intervalS); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
@@ -37,6 +38,9 @@ func Main(ctx context.Context) error {
|
||||
state := map[key.Key]bool{}
|
||||
for block() {
|
||||
delta := reader.Read()
|
||||
if os.Getenv("DEBUG") == "true" {
|
||||
log.Printf("src.Main.reader.Read(): %+v", delta)
|
||||
}
|
||||
for _, button := range delta {
|
||||
state[key.FromChar(button.Char)] = button.Down
|
||||
}
|
||||
|
||||
6
testdata/INPUT_REMAP_FILE.yaml
vendored
6
testdata/INPUT_REMAP_FILE.yaml
vendored
@@ -2,3 +2,9 @@ w: i
|
||||
a: j
|
||||
s: k
|
||||
d: l
|
||||
q: u
|
||||
e: o
|
||||
1: 0
|
||||
2: 9
|
||||
3: 8
|
||||
4: 7
|
||||
|
||||
4
testdata/keyboard_input.env
vendored
4
testdata/keyboard_input.env
vendored
@@ -1,2 +1,2 @@
|
||||
export INPUT_BUFFERED=true
|
||||
export INPUT_KEYBOARD=true
|
||||
export WRAP_BUFFERED=true
|
||||
export RAW_KEYBOARD=true
|
||||
|
||||
4
testdata/remapped_wasd.env
vendored
4
testdata/remapped_wasd.env
vendored
@@ -1,2 +1,2 @@
|
||||
export INPUT_RANDOM_WEIGHT_FILE=testdata/INPUT_RANDOM_WEIGHT_FILE.yaml
|
||||
export INPUT_REMAP_FILE=testdata/INPUT_REMAP_FILE.yaml
|
||||
export RAW_RANDOM_WEIGHT_FILE=testdata/INPUT_RANDOM_WEIGHT_FILE.yaml
|
||||
export WRAP_REMAP_FILE=testdata/INPUT_REMAP_FILE.yaml
|
||||
|
||||
2
testdata/supermarioplay.com.env
vendored
2
testdata/supermarioplay.com.env
vendored
@@ -1,3 +1,3 @@
|
||||
export OUTPUT_KEYBOARD=true
|
||||
export MAIN_INTERVAL_DURATION=250ms
|
||||
export INPUT_RANDOM_WEIGHT_FILE=testdata/INPUT_RANDOM_WEIGHT_FILE.yaml
|
||||
export RAW_RANDOM_WEIGHT_FILE=testdata/INPUT_RANDOM_WEIGHT_FILE.yaml
|
||||
|
||||
80
todo.yaml
Executable file
80
todo.yaml
Executable file
@@ -0,0 +1,80 @@
|
||||
todo:
|
||||
- https via home.blapointe and rproxy
|
||||
- tts for when someone said the word via larynx docker + http.get + https://pkg.go.dev/github.com/faiface/beep@v1.1.0/wav
|
||||
- endpoint for v01 to start read-only mode so when hotword spoken, players are dcd
|
||||
without losing players; press a hotkey that is bound to dolphin emulator pause
|
||||
- todo: rotation triggers
|
||||
subtasks:
|
||||
- todo: stdin
|
||||
subtasks:
|
||||
- minigame end
|
||||
- todo: voice recognition of hotwords to vote who dun it
|
||||
subtasks:
|
||||
- random word from cur wikipedia page
|
||||
- each person has their own hotword
|
||||
- only spectators have hotwords and must get a player to speak it
|
||||
- tribunal to vote who said it
|
||||
scheduled: []
|
||||
done:
|
||||
- todo: sticky keyboard input mode for enable/disable explicitly
|
||||
ts: Thu Mar 23 20:55:52 MDT 2023
|
||||
- todo: case-sensitive
|
||||
ts: Fri Mar 24 13:39:26 MDT 2023
|
||||
- todo: rusty configs have "name" for each client so "if name == server_broadcasted_name
|
||||
{ debug_print_in_gui(server_broadcasted_message) }
|
||||
ts: Fri Mar 24 16:40:09 MDT 2023
|
||||
- todo: change from 'a','b','c' from rust to just 10,11,12 so playerName is known
|
||||
implicitly but then gotta translate back to char for keyboard things somewhere;
|
||||
space delimited?
|
||||
ts: Fri Mar 24 17:00:55 MDT 2023
|
||||
- todo: '"Button" to interface or strings'
|
||||
ts: Fri Mar 24 17:01:01 MDT 2023
|
||||
- todo: input.UDP as a raw provider
|
||||
ts: Fri Mar 24 19:58:59 MDT 2023
|
||||
- todo: input.MayhemParty as a logical wrapper
|
||||
ts: Fri Mar 24 19:58:59 MDT 2023
|
||||
- todo: change from 'a','b','c' from rust to just 11,21,31,41 so playerName is known
|
||||
implicitly from %10 but then gotta translate back to char for keyboard things
|
||||
somewhere; space delimited?
|
||||
ts: Fri Mar 24 19:58:59 MDT 2023
|
||||
- todo: input."Button" to interface or strings
|
||||
ts: Fri Mar 24 21:16:39 MDT 2023
|
||||
- todo: input.MayhemParty as a logical wrapper from %10 but then gotta translate back
|
||||
to char for keyboard things somewhere; space delimited?
|
||||
ts: Fri Mar 24 21:16:39 MDT 2023
|
||||
- todo: change from 'a','b','c' from rust to just 11,21,31,41 so playerName is known
|
||||
implicitly
|
||||
ts: Sat Mar 25 00:06:21 MDT 2023
|
||||
- todo: lag via UDP formatted inputs as space-delimited TS PID buttonIdx buttonIdx
|
||||
buttonIdx
|
||||
ts: Sat Mar 25 00:13:19 MDT 2023
|
||||
- todo: map keys triggered by user to player idx and their keys
|
||||
ts: Sat Mar 25 00:44:19 MDT 2023
|
||||
- todo: use button.V01Cfg; map keys triggered by user to player idx and their keys
|
||||
ts: Sat Mar 25 09:12:43 MDT 2023
|
||||
- todo: v01cfg includes messages to send per client and exposes tcp server for it
|
||||
ts: Sat Mar 25 10:09:06 MDT 2023
|
||||
- todo: v01cfg includes messages to send per client and exposes http server for it
|
||||
ts: Sat Mar 25 11:28:29 MDT 2023
|
||||
- todo: send clients messages to display
|
||||
ts: Sat Mar 25 11:28:29 MDT 2023
|
||||
- todo: input.MayhemParty as a logical wrapper from mod10 but then gotta translate
|
||||
back to char for keyboard things somewhere; space delimited?
|
||||
ts: Sat Mar 25 11:28:40 MDT 2023
|
||||
- todo: rusty configs have "name" for each client
|
||||
details: |
|
||||
'if name == server_broadcasted_name { debug_print_in_gui(server_broadcasted_message) }'
|
||||
ts: Sat Mar 25 11:28:40 MDT 2023
|
||||
- todo: rotation triggers
|
||||
subtasks:
|
||||
- minigame end
|
||||
- random word from cur wikipedia page
|
||||
- each person has their own hotword
|
||||
- only spectators have hotwords and must get a player to speak it
|
||||
- tribunal to vote who said it
|
||||
ts: Sat Mar 25 11:29:52 MDT 2023
|
||||
- todo: we have 7 players oooooof
|
||||
ts: Sat Mar 25 11:29:52 MDT 2023
|
||||
- todo: endpoint for v01 to start read-only mode so when hotword spoken, players are
|
||||
dcd without losing players
|
||||
ts: Sat Mar 25 23:16:47 MDT 2023
|
||||
Reference in New Issue
Block a user