Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||
|
||||
|
||||
51
host.d/README.md
Normal file
51
host.d/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# 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
|
||||
rustup target add x86_64-pc-windows-gnu
|
||||
echo windows
|
||||
cargo build --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`
|
||||
|
||||
6
host.d/config.d/mayhem-party.d/env.env
Normal file
6
host.d/config.d/mayhem-party.d/env.env
Normal file
@@ -0,0 +1,6 @@
|
||||
export DEBUG=1
|
||||
export RAW_UDP=17070
|
||||
export WRAP_REFRESH_ON_SIGUSR1=true
|
||||
export WRAP_REMAP_FILE=./config.d/mayhem-party.d/remap.d/live.yaml
|
||||
export MAIN_INTERVAL_DURATION=5ms
|
||||
export OUTPUT_KEYBOARD=false
|
||||
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_0.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
|
||||
26
host.d/config.d/rusty-pipe.d/1.yaml
Normal file
26
host.d/config.d/rusty-pipe.d/1.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
streams:
|
||||
input:
|
||||
debug: false
|
||||
engine:
|
||||
name: gui
|
||||
gui:
|
||||
press: {prefix: "", suffix: ""}
|
||||
release: {prefix: "!", suffix: ""}
|
||||
buttons:
|
||||
up: '1'
|
||||
down: '2'
|
||||
left: '3'
|
||||
right: '4'
|
||||
l: '5'
|
||||
r: '6'
|
||||
a: '7'
|
||||
b: '8'
|
||||
x: '9'
|
||||
y: '0'
|
||||
output:
|
||||
debug: false
|
||||
engine:
|
||||
name: udp
|
||||
udp:
|
||||
host: mayhem-party.home.blapointe.com
|
||||
port: 17070
|
||||
26
host.d/config.d/rusty-pipe.d/2.yaml
Normal file
26
host.d/config.d/rusty-pipe.d/2.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
streams:
|
||||
input:
|
||||
debug: false
|
||||
engine:
|
||||
name: gui
|
||||
gui:
|
||||
press: {prefix: "", suffix: ""}
|
||||
release: {prefix: "!", suffix: ""}
|
||||
buttons:
|
||||
up: 'q'
|
||||
down: 'w'
|
||||
left: 'e'
|
||||
right: 'r'
|
||||
l: 't'
|
||||
r: 'y'
|
||||
a: 'u'
|
||||
b: 'i'
|
||||
x: 'o'
|
||||
y: 'p'
|
||||
output:
|
||||
debug: false
|
||||
engine:
|
||||
name: udp
|
||||
udp:
|
||||
host: mayhem-party.home.blapointe.com
|
||||
port: 17070
|
||||
26
host.d/config.d/rusty-pipe.d/3.yaml
Normal file
26
host.d/config.d/rusty-pipe.d/3.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
streams:
|
||||
input:
|
||||
debug: false
|
||||
engine:
|
||||
name: gui
|
||||
gui:
|
||||
press: {prefix: "", suffix: ""}
|
||||
release: {prefix: "!", suffix: ""}
|
||||
buttons:
|
||||
up: 'a'
|
||||
down: 's'
|
||||
left: 'd'
|
||||
right: 'f'
|
||||
l: 'g'
|
||||
r: 'h'
|
||||
a: 'j'
|
||||
b: 'k'
|
||||
x: 'l'
|
||||
y: ';'
|
||||
output:
|
||||
debug: false
|
||||
engine:
|
||||
name: udp
|
||||
udp:
|
||||
host: mayhem-party.home.blapointe.com
|
||||
port: 17070
|
||||
26
host.d/config.d/rusty-pipe.d/4.yaml
Normal file
26
host.d/config.d/rusty-pipe.d/4.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
streams:
|
||||
input:
|
||||
debug: false
|
||||
engine:
|
||||
name: gui
|
||||
gui:
|
||||
press: {prefix: "", suffix: ""}
|
||||
release: {prefix: "!", suffix: ""}
|
||||
buttons:
|
||||
up: 'z'
|
||||
down: 'x'
|
||||
left: 'c'
|
||||
right: 'v'
|
||||
l: 'b'
|
||||
r: 'n'
|
||||
a: 'm'
|
||||
b: ','
|
||||
x: '-'
|
||||
y: '/'
|
||||
output:
|
||||
debug: false
|
||||
engine:
|
||||
name: udp
|
||||
udp:
|
||||
host: mayhem-party.home.blapointe.com
|
||||
port: 17070
|
||||
26
host.d/config.d/rusty-pipe.d/5.yaml
Normal file
26
host.d/config.d/rusty-pipe.d/5.yaml
Normal file
@@ -0,0 +1,26 @@
|
||||
streams:
|
||||
input:
|
||||
debug: false
|
||||
engine:
|
||||
name: gui
|
||||
gui:
|
||||
press: {prefix: "", suffix: ""}
|
||||
release: {prefix: "!", suffix: ""}
|
||||
buttons:
|
||||
up: '!'
|
||||
down: '@'
|
||||
left: '#'
|
||||
right: '$'
|
||||
l: '%'
|
||||
r: '^'
|
||||
a: '&'
|
||||
b: '*'
|
||||
x: '('
|
||||
y: ')'
|
||||
output:
|
||||
debug: false
|
||||
engine:
|
||||
name: udp
|
||||
udp:
|
||||
host: mayhem-party.home.blapointe.com
|
||||
port: 17070
|
||||
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
|
||||
}
|
||||
19
src/device/input/button/parser.go
Normal file
19
src/device/input/button/parser.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package button
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayhem-party/src/device/input/raw"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Parser interface {
|
||||
Read() []Button
|
||||
Close()
|
||||
}
|
||||
|
||||
func New(ctx context.Context, src raw.Raw) Parser {
|
||||
if os.Getenv("BUTTON_PARSER_V1") == "true" {
|
||||
return NewV1(src)
|
||||
}
|
||||
return NewPlaintext(src)
|
||||
}
|
||||
11
src/device/input/button/parser_test.go
Normal file
11
src/device/input/button/parser_test.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package button_test
|
||||
|
||||
import (
|
||||
"mayhem-party/src/device/input/button"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
var _ button.Parser = button.Plaintext{}
|
||||
var _ button.Parser = button.V1{}
|
||||
}
|
||||
41
src/device/input/button/plaintext.go
Normal file
41
src/device/input/button/plaintext.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package button
|
||||
|
||||
import (
|
||||
"mayhem-party/src/device/input/raw"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Plaintext struct {
|
||||
src raw.Raw
|
||||
release byte
|
||||
}
|
||||
|
||||
func NewPlaintext(src raw.Raw) Plaintext {
|
||||
releaseChar := byte('!')
|
||||
if v := os.Getenv("BUTTON_PLAINTEXT_RELEASE"); v != "" {
|
||||
releaseChar = byte(v[0])
|
||||
}
|
||||
return Plaintext{
|
||||
src: src,
|
||||
release: releaseChar,
|
||||
}
|
||||
}
|
||||
|
||||
func (p Plaintext) Close() { p.src.Close() }
|
||||
|
||||
func (p Plaintext) Read() []Button {
|
||||
b := p.src.Read()
|
||||
buttons := make([]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{Char: b[i], Down: down})
|
||||
}
|
||||
down = true
|
||||
}
|
||||
}
|
||||
return buttons
|
||||
}
|
||||
29
src/device/input/button/plaintext_test.go
Normal file
29
src/device/input/button/plaintext_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package button_test
|
||||
|
||||
import (
|
||||
"mayhem-party/src/device/input/button"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPlaintext(t *testing.T) {
|
||||
src := constSrc("c!b")
|
||||
p := button.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)
|
||||
}
|
||||
55
src/device/input/button/v1.go
Normal file
55
src/device/input/button/v1.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package button
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"mayhem-party/src/device/input/raw"
|
||||
"os"
|
||||
)
|
||||
|
||||
var debugging = os.Getenv("DEBUG") == "true"
|
||||
|
||||
type (
|
||||
V1 struct {
|
||||
src raw.Raw
|
||||
}
|
||||
v1Msg struct {
|
||||
T int64
|
||||
U string
|
||||
Y string
|
||||
N string
|
||||
}
|
||||
)
|
||||
|
||||
func NewV1(src raw.Raw) V1 {
|
||||
return V1{
|
||||
src: src,
|
||||
}
|
||||
}
|
||||
|
||||
func (v1 V1) Close() {
|
||||
v1.src.Close()
|
||||
}
|
||||
|
||||
func (v1 V1) Read() []Button {
|
||||
line := v1.src.Read()
|
||||
var msg v1Msg
|
||||
if err := json.Unmarshal(line, &msg); err != nil {
|
||||
log.Printf("%v: %s", err, line)
|
||||
}
|
||||
return msg.buttons()
|
||||
}
|
||||
|
||||
func (msg v1Msg) buttons() []Button {
|
||||
buttons := make([]Button, len(msg.Y)+len(msg.N))
|
||||
for i := range msg.Y {
|
||||
buttons[i] = Button{Char: msg.Y[i], Down: true}
|
||||
}
|
||||
for i := range msg.N {
|
||||
buttons[len(msg.Y)+i] = Button{Char: msg.N[i], Down: false}
|
||||
}
|
||||
if debugging {
|
||||
log.Printf("%+v", msg)
|
||||
}
|
||||
return buttons
|
||||
}
|
||||
29
src/device/input/button/v1_test.go
Normal file
29
src/device/input/button/v1_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package button_test
|
||||
|
||||
import (
|
||||
"mayhem-party/src/device/input/button"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestV1(t *testing.T) {
|
||||
src := constSrc(`{"T":1,"U":"bel","Y":"abc","N":"cde"}`)
|
||||
t.Logf("(%v) %s", len(src), src.Read())
|
||||
v1 := button.NewV1(src)
|
||||
got := v1.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])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,28 +2,19 @@ package input
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"mayhem-party/src/device/input/button"
|
||||
"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() button.Parser {
|
||||
return button.New(ctx, src)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -30,11 +30,11 @@ func TestNewRemapped(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
os.Setenv("INPUT_REMAP_FILE", remap)
|
||||
os.Setenv("INPUT_RANDOM_WEIGHT_FILE", rand)
|
||||
os.Setenv("WRAP_REMAP_FILE", remap)
|
||||
os.Setenv("RAW_RANDOM_WEIGHT_FILE", rand)
|
||||
t.Cleanup(func() {
|
||||
os.Unsetenv("INPUT_REMAP_FILE")
|
||||
os.Unsetenv("INPUT_RANDOM_WEIGHT_FILE")
|
||||
os.Unsetenv("WRAP_REMAP_FILE")
|
||||
os.Unsetenv("RAW_RANDOM_WEIGHT_FILE")
|
||||
})
|
||||
|
||||
r := input.New(context.Background())
|
||||
@@ -50,9 +50,9 @@ func TestNewRemapped(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewBuffered(t *testing.T) {
|
||||
os.Setenv("INPUT_BUFFERED", "true")
|
||||
os.Setenv("WRAP_BUFFERED", "true")
|
||||
t.Cleanup(func() {
|
||||
os.Unsetenv("INPUT_BUFFERED")
|
||||
os.Unsetenv("WRAP_BUFFERED")
|
||||
})
|
||||
|
||||
r := input.New(context.Background())
|
||||
@@ -71,9 +71,9 @@ func TestNewRandomWeightFile(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
os.Setenv("INPUT_RANDOM_WEIGHT_FILE", p)
|
||||
os.Setenv("RAW_RANDOM_WEIGHT_FILE", p)
|
||||
t.Cleanup(func() {
|
||||
os.Unsetenv("INPUT_RANDOM_WEIGHT_FILE")
|
||||
os.Unsetenv("RAW_RANDOM_WEIGHT_FILE")
|
||||
})
|
||||
|
||||
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{}
|
||||
}
|
||||
@@ -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 os.Getenv("DEBUG") == "true" {
|
||||
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 {
|
||||
26
src/device/input/raw/raw.go
Normal file
26
src/device/input/raw/raw.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package raw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Raw interface {
|
||||
Read() []byte
|
||||
Close()
|
||||
}
|
||||
|
||||
func New(ctx context.Context) Raw {
|
||||
if os.Getenv("RAW_KEYBOARD") == "true" {
|
||||
return NewKeyboard()
|
||||
}
|
||||
if port, _ := strconv.Atoi(os.Getenv("RAW_UDP")); port != 0 {
|
||||
return NewUDP(ctx, port)
|
||||
}
|
||||
generator := randomCharFromRange('a', 'g')
|
||||
if p, ok := os.LookupEnv("RAW_RANDOM_WEIGHT_FILE"); ok && len(p) > 0 {
|
||||
generator = randomCharFromWeightFile(p)
|
||||
}
|
||||
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{}
|
||||
}
|
||||
60
src/device/input/raw/udp.go
Normal file
60
src/device/input/raw/udp.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package raw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
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() {
|
||||
debugging := os.Getenv("DEBUG") == "true"
|
||||
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 debugging {
|
||||
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,7 +1,9 @@
|
||||
package input
|
||||
package wrap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayhem-party/src/device/input/button"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -11,21 +13,25 @@ type Buffered struct {
|
||||
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(os.Getenv("WRAP_BUFFERED_STICKY_DURATION")); 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 +45,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()
|
||||
@@ -54,7 +62,7 @@ func (b *Buffered) 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 +73,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
|
||||
24
src/device/input/wrap/protocol.go
Normal file
24
src/device/input/wrap/protocol.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package wrap
|
||||
|
||||
import (
|
||||
"mayhem-party/src/device/input/button"
|
||||
"mayhem-party/src/device/input/raw"
|
||||
)
|
||||
|
||||
type Protocol struct {
|
||||
src raw.Raw
|
||||
}
|
||||
|
||||
func NewProtocol(src raw.Raw) Protocol {
|
||||
return Protocol{
|
||||
src: src,
|
||||
}
|
||||
}
|
||||
|
||||
func (p Protocol) Close() {
|
||||
p.src.Close()
|
||||
}
|
||||
|
||||
func (p Protocol) Read() []button.Button {
|
||||
panic(nil)
|
||||
}
|
||||
50
src/device/input/wrap/refresh.go
Normal file
50
src/device/input/wrap/refresh.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package wrap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"mayhem-party/src/device/input/button"
|
||||
"os"
|
||||
"os/signal"
|
||||
)
|
||||
|
||||
type Refresh struct {
|
||||
can context.CancelFunc
|
||||
input Wrap
|
||||
}
|
||||
|
||||
func NewRefreshCh(sig os.Signal) <-chan os.Signal {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, sig)
|
||||
return c
|
||||
}
|
||||
|
||||
func NewRefresh(newWrap func() Wrap, ch <-chan os.Signal) *Refresh {
|
||||
ctx, can := context.WithCancel(context.Background())
|
||||
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 = newWrap()
|
||||
}
|
||||
}
|
||||
}()
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *Refresh) Read() []button.Button {
|
||||
return r.input.Read()
|
||||
}
|
||||
|
||||
func (r *Refresh) Close() {
|
||||
r.can()
|
||||
r.input.Close()
|
||||
}
|
||||
44
src/device/input/wrap/refresh_test.go
Normal file
44
src/device/input/wrap/refresh_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package wrap
|
||||
|
||||
import (
|
||||
"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 := NewRefresh(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'))
|
||||
})
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
package input
|
||||
package wrap
|
||||
|
||||
import (
|
||||
"mayhem-party/src/device/input/button"
|
||||
"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,7 +31,7 @@ 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,
|
||||
@@ -41,7 +42,7 @@ 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 {
|
||||
39
src/device/input/wrap/wrap.go
Normal file
39
src/device/input/wrap/wrap.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package wrap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mayhem-party/src/device/input/button"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type Wrap interface {
|
||||
Read() []button.Button
|
||||
Close()
|
||||
}
|
||||
|
||||
func New(ctx context.Context, srcFunc func() button.Parser) Wrap {
|
||||
maker := func() Wrap {
|
||||
return srcFunc()
|
||||
}
|
||||
if os.Getenv("WRAP_BUFFERED") == "true" {
|
||||
oldMaker := maker
|
||||
maker = func() Wrap {
|
||||
return NewBuffered(ctx, oldMaker())
|
||||
}
|
||||
}
|
||||
if p := os.Getenv("WRAP_REMAP_FILE"); p != "" {
|
||||
oldMaker := maker
|
||||
maker = func() Wrap {
|
||||
return NewRemapFromFile(oldMaker(), p)
|
||||
}
|
||||
}
|
||||
if os.Getenv("WRAP_REFRESH_ON_SIGUSR1") != "" {
|
||||
oldMaker := maker
|
||||
c := NewRefreshCh(syscall.SIGUSR1)
|
||||
maker = func() Wrap {
|
||||
return NewRefresh(oldMaker, c)
|
||||
}
|
||||
}
|
||||
return maker()
|
||||
}
|
||||
21
src/device/input/wrap/wrap_test.go
Normal file
21
src/device/input/wrap/wrap_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package wrap
|
||||
|
||||
import (
|
||||
"mayhem-party/src/device/input/button"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWrap(t *testing.T) {
|
||||
var _ Wrap = dummyParser{}
|
||||
var _ Wrap = &Refresh{}
|
||||
var _ Wrap = &Buffered{}
|
||||
var _ Wrap = &Remap{}
|
||||
var _ Wrap = Protocol{}
|
||||
}
|
||||
|
||||
type dummyParser button.Button
|
||||
|
||||
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"
|
||||
@@ -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
|
||||
|
||||
44
todo.yaml
Executable file
44
todo.yaml
Executable file
@@ -0,0 +1,44 @@
|
||||
todo:
|
||||
- change from 'a','b','c' from rust to just 11,21,31,41 so playerName is known implicitly
|
||||
- lag via UDP formatted inputs as space-delimited TS PID buttonIdx buttonIdx buttonIdx
|
||||
- input.MayhemParty as a logical wrapper from mod10 but then gotta translate back
|
||||
to char for keyboard things somewhere; space delimited?
|
||||
- todo: rusty configs have "name" for each client
|
||||
details: |
|
||||
'if name == server_broadcasted_name { debug_print_in_gui(server_broadcasted_message) }'
|
||||
- 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
|
||||
- we have 7 players oooooof
|
||||
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
|
||||
Reference in New Issue
Block a user