parent
16b7383f42
commit
23edcfcdee
8 changed files with 247 additions and 56 deletions
|
@ -1,4 +1,5 @@
|
|||
0.1.6:
|
||||
- Persist settings via configuration file
|
||||
- Draw playfield border as solid blocks
|
||||
|
||||
0.1.5:
|
||||
|
|
65
cmd/netris/config.go
Normal file
65
cmd/netris/config.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"gitlab.com/tslocum/netris/pkg/event"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type appConfig struct {
|
||||
Input map[event.GameAction][]string // Keybinds
|
||||
Name string
|
||||
}
|
||||
|
||||
var config = &appConfig{}
|
||||
|
||||
func defaultConfigPath() string {
|
||||
homedir, err := os.UserHomeDir()
|
||||
if err == nil && homedir != "" {
|
||||
return path.Join(homedir, ".config", "netris", "config.yaml")
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func readConfig(configPath string) error {
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
if configPath != defaultConfigPath() {
|
||||
return fmt.Errorf("failed to read configuration: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
configData, err := ioutil.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read configuration: %s", err)
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(configData, config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse configuration: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveConfig(configPath string) error {
|
||||
config.Name = nickname
|
||||
|
||||
out, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal configuration: %s", err)
|
||||
}
|
||||
|
||||
os.MkdirAll(path.Dir(configPath), 0755) // Ignore error
|
||||
|
||||
err = ioutil.WriteFile(configPath, out, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to %s: %s", configPath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
"gitlab.com/tslocum/cbind"
|
||||
"gitlab.com/tslocum/netris/pkg/event"
|
||||
"gitlab.com/tslocum/netris/pkg/game"
|
||||
)
|
||||
|
@ -20,27 +21,59 @@ type Keybinding struct {
|
|||
a event.GameAction
|
||||
}
|
||||
|
||||
var keybindings = []*Keybinding{
|
||||
{r: 'z', a: event.ActionRotateCCW},
|
||||
{r: 'Z', a: event.ActionRotateCCW},
|
||||
{r: 'x', a: event.ActionRotateCW},
|
||||
{r: 'X', a: event.ActionRotateCW},
|
||||
{k: tcell.KeyLeft, a: event.ActionMoveLeft},
|
||||
{r: 'h', a: event.ActionMoveLeft},
|
||||
{r: 'H', a: event.ActionMoveLeft},
|
||||
{k: tcell.KeyDown, a: event.ActionSoftDrop},
|
||||
{r: 'j', a: event.ActionSoftDrop},
|
||||
{r: 'J', a: event.ActionSoftDrop},
|
||||
{k: tcell.KeyUp, a: event.ActionHardDrop},
|
||||
{r: 'k', a: event.ActionHardDrop},
|
||||
{r: 'K', a: event.ActionHardDrop},
|
||||
{k: tcell.KeyRight, a: event.ActionMoveRight},
|
||||
{r: 'l', a: event.ActionMoveRight},
|
||||
{r: 'L', a: event.ActionMoveRight},
|
||||
var actionHandlers = map[event.GameAction]func(*tcell.EventKey) *tcell.EventKey{
|
||||
event.ActionRotateCCW: rotateCCW,
|
||||
event.ActionRotateCW: rotateCW,
|
||||
event.ActionMoveLeft: moveLeft,
|
||||
event.ActionMoveRight: moveRight,
|
||||
event.ActionSoftDrop: softDrop,
|
||||
event.ActionHardDrop: hardDrop,
|
||||
}
|
||||
|
||||
var inputConfig = cbind.NewConfiguration()
|
||||
|
||||
var draftKeybindings []*Keybinding
|
||||
|
||||
func setKeyBinds() error {
|
||||
if len(config.Input) == 0 {
|
||||
setDefaultKeyBinds()
|
||||
}
|
||||
|
||||
for a, keys := range config.Input {
|
||||
a = event.GameAction(strings.ToLower(string(a)))
|
||||
handler := actionHandlers[a]
|
||||
if handler == nil {
|
||||
return fmt.Errorf("failed to set keybind for %s: unknown action", a)
|
||||
}
|
||||
|
||||
for _, k := range keys {
|
||||
mod, key, ch, err := cbind.Decode(k)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set keybind %s for %s: %s", k, a, err)
|
||||
}
|
||||
|
||||
if key == tcell.KeyRune {
|
||||
inputConfig.SetRune(mod, ch, handler)
|
||||
} else {
|
||||
inputConfig.SetKey(mod, key, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setDefaultKeyBinds() {
|
||||
config.Input = map[event.GameAction][]string{
|
||||
event.ActionRotateCCW: {"z", "Z"},
|
||||
event.ActionRotateCW: {"x", "X"},
|
||||
event.ActionMoveLeft: {"Left", "h", "H"},
|
||||
event.ActionMoveRight: {"Right", "l", "L"},
|
||||
event.ActionSoftDrop: {"Down", "j", "J"},
|
||||
event.ActionHardDrop: {"Up", "k", "K"},
|
||||
}
|
||||
}
|
||||
|
||||
func scrollMessages(direction int) {
|
||||
var scroll int
|
||||
if showLogLines > 3 {
|
||||
|
@ -396,16 +429,59 @@ func handleKeypress(ev *tcell.EventKey) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
for _, bind := range keybindings {
|
||||
if (bind.k != 0 && bind.k != k) || (bind.r != 0 && bind.r != r) || (bind.m != 0 && bind.m != ev.Modifiers()) {
|
||||
continue
|
||||
} else if activeGame == nil {
|
||||
break
|
||||
}
|
||||
return inputConfig.Capture(ev)
|
||||
}
|
||||
|
||||
activeGame.ProcessAction(bind.a)
|
||||
return nil
|
||||
func rotateCCW(ev *tcell.EventKey) *tcell.EventKey {
|
||||
if activeGame == nil {
|
||||
return ev
|
||||
}
|
||||
|
||||
return ev
|
||||
activeGame.ProcessAction(event.ActionRotateCCW)
|
||||
return nil
|
||||
}
|
||||
|
||||
func rotateCW(ev *tcell.EventKey) *tcell.EventKey {
|
||||
if activeGame == nil {
|
||||
return ev
|
||||
}
|
||||
|
||||
activeGame.ProcessAction(event.ActionRotateCW)
|
||||
return nil
|
||||
}
|
||||
|
||||
func moveLeft(ev *tcell.EventKey) *tcell.EventKey {
|
||||
if activeGame == nil {
|
||||
return ev
|
||||
}
|
||||
|
||||
activeGame.ProcessAction(event.ActionMoveLeft)
|
||||
return nil
|
||||
}
|
||||
|
||||
func moveRight(ev *tcell.EventKey) *tcell.EventKey {
|
||||
if activeGame == nil {
|
||||
return ev
|
||||
}
|
||||
|
||||
activeGame.ProcessAction(event.ActionMoveRight)
|
||||
return nil
|
||||
}
|
||||
|
||||
func softDrop(ev *tcell.EventKey) *tcell.EventKey {
|
||||
if activeGame == nil {
|
||||
return ev
|
||||
}
|
||||
|
||||
activeGame.ProcessAction(event.ActionSoftDrop)
|
||||
return nil
|
||||
}
|
||||
|
||||
func hardDrop(ev *tcell.EventKey) *tcell.EventKey {
|
||||
if activeGame == nil {
|
||||
return ev
|
||||
}
|
||||
|
||||
activeGame.ProcessAction(event.ActionHardDrop)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"gitlab.com/tslocum/cbind"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
"gitlab.com/tslocum/cview"
|
||||
"gitlab.com/tslocum/netris/pkg/event"
|
||||
|
@ -121,9 +123,6 @@ func selectTitleButton() {
|
|||
|
||||
drawGhostPieceUnsaved = drawGhostPiece
|
||||
|
||||
draftKeybindings = make([]*Keybinding, len(keybindings))
|
||||
copy(draftKeybindings, keybindings)
|
||||
|
||||
app.SetRoot(gameSettingsContainerGrid, true)
|
||||
updateTitle()
|
||||
case 2:
|
||||
|
@ -160,8 +159,28 @@ func selectTitleButton() {
|
|||
if currentSelection == 8 {
|
||||
drawGhostPiece = drawGhostPieceUnsaved
|
||||
|
||||
keybindings = make([]*Keybinding, len(draftKeybindings))
|
||||
copy(keybindings, draftKeybindings)
|
||||
for _, bind := range draftKeybindings {
|
||||
if bind.k == tcell.KeyRune {
|
||||
inputConfig.SetRune(bind.m, bind.r, actionHandlers[bind.a])
|
||||
} else {
|
||||
inputConfig.SetKey(bind.m, bind.k, actionHandlers[bind.a])
|
||||
}
|
||||
|
||||
encoded, err := cbind.Encode(bind.m, bind.k, bind.r)
|
||||
if err == nil && encoded != "" {
|
||||
// Remove existing keybinds
|
||||
for existingBindAction, existingBinds := range config.Input {
|
||||
for i, existingBind := range existingBinds {
|
||||
if existingBind == encoded {
|
||||
config.Input[existingBindAction] = append(config.Input[existingBindAction][:i], config.Input[existingBindAction][i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set keybind
|
||||
config.Input[bind.a] = append(config.Input[bind.a], encoded)
|
||||
}
|
||||
}
|
||||
}
|
||||
draftKeybindings = nil
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@ var (
|
|||
|
||||
nicknameFlag string
|
||||
|
||||
configPath string
|
||||
|
||||
blockSize = 0
|
||||
fixedBlockSize bool
|
||||
|
||||
|
@ -76,6 +78,7 @@ func main() {
|
|||
flag.StringVar(&connectAddress, "connect", "", "connect to server address or socket path")
|
||||
flag.StringVar(&serverAddress, "server", game.DefaultServer, "server address or socket path")
|
||||
flag.StringVar(&debugAddress, "debug-address", "", "address to serve debug info")
|
||||
flag.StringVar(&configPath, "config", "", "path to configuration file")
|
||||
flag.BoolVar(&logDebug, "debug", false, "enable debug logging")
|
||||
flag.BoolVar(&logVerbose, "verbose", false, "enable verbose logging")
|
||||
flag.Parse()
|
||||
|
@ -100,8 +103,8 @@ func main() {
|
|||
logLevel = game.LogDebug
|
||||
}
|
||||
|
||||
if game.Nickname(nicknameFlag) != "" {
|
||||
nickname = game.Nickname(nicknameFlag)
|
||||
if configPath == "" {
|
||||
configPath = defaultConfigPath()
|
||||
}
|
||||
|
||||
if debugAddress != "" {
|
||||
|
@ -110,6 +113,22 @@ func main() {
|
|||
}()
|
||||
}
|
||||
|
||||
err := readConfig(configPath)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read configuration file: %s", err)
|
||||
}
|
||||
|
||||
err = setKeyBinds()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to set keybinds: %s", err)
|
||||
}
|
||||
|
||||
if nicknameFlag != "" && game.Nickname(nicknameFlag) != "" {
|
||||
nickname = game.Nickname(nicknameFlag)
|
||||
} else if config.Name != "" && game.Nickname(config.Name) != "" {
|
||||
nickname = game.Nickname(config.Name)
|
||||
}
|
||||
|
||||
app, err := initGUI(connectAddress != "")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to initialize GUI: %s", err)
|
||||
|
@ -168,6 +187,11 @@ func main() {
|
|||
|
||||
closeGUI()
|
||||
|
||||
err := saveConfig(configPath)
|
||||
if err != nil {
|
||||
log.Printf("warning: failed to save configuration: %s", err)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
|
|
8
go.mod
8
go.mod
|
@ -9,8 +9,8 @@ require (
|
|||
github.com/gdamore/tcell v1.3.0
|
||||
github.com/gliderlabs/ssh v0.2.2
|
||||
github.com/mattn/go-isatty v0.0.12
|
||||
github.com/mattn/go-runewidth v0.0.8 // indirect
|
||||
gitlab.com/tslocum/cview v1.4.2-0.20200128151041-339db80f666d
|
||||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 // indirect
|
||||
gitlab.com/tslocum/cbind v0.1.1
|
||||
gitlab.com/tslocum/cview v1.4.4-0.20200213233906-2a8ba3160c01
|
||||
golang.org/x/crypto v0.0.0-20200210222208-86ce3cb69678
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
)
|
||||
|
|
22
go.sum
22
go.sum
|
@ -17,25 +17,31 @@ github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
|
|||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
|
||||
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
gitlab.com/tslocum/cview v1.4.2-0.20200128151041-339db80f666d h1:5rPwwmNYGLcOsyawvAw7m/Jtwp5rAuvLoqVW5k09AP0=
|
||||
gitlab.com/tslocum/cview v1.4.2-0.20200128151041-339db80f666d/go.mod h1:QbxliYQa2I32UJH2boP54jq6tnWlgm6yViaFXKGDfuM=
|
||||
gitlab.com/tslocum/cbind v0.1.1 h1:JXXtxMWHgWLvoF+QkrvcNvOQ59juy7OE1RhT7hZfdt0=
|
||||
gitlab.com/tslocum/cbind v0.1.1/go.mod h1:rX7vkl0pUSg/yy427MmD1FZAf99S7WwpUlxF/qTpPqk=
|
||||
gitlab.com/tslocum/cview v1.4.4-0.20200213233906-2a8ba3160c01 h1:YdNvWO1OoGnmtjdAx84+3MWW1DAANWV8WLN9ZXE7PZc=
|
||||
gitlab.com/tslocum/cview v1.4.4-0.20200213233906-2a8ba3160c01/go.mod h1:+bEf1cg6IoWvL16YHJAKwGGpQf5s/nxXAA7YJr+WOHE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
|
||||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200210222208-86ce3cb69678 h1:wCWoJcFExDgyYx2m2hpHgwz8W3+FPdfldvIgzqDIhyg=
|
||||
golang.org/x/crypto v0.0.0-20200210222208-86ce3cb69678/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200103143344-a1369afcdac7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
package event
|
||||
|
||||
type GameAction int
|
||||
type GameAction string
|
||||
|
||||
const (
|
||||
ActionUnknown GameAction = iota
|
||||
ActionRotateCCW
|
||||
ActionRotateCW
|
||||
ActionMoveLeft
|
||||
ActionMoveRight
|
||||
ActionSoftDrop
|
||||
ActionHardDrop
|
||||
ActionPing
|
||||
ActionStats
|
||||
ActionNick
|
||||
ActionUnknown = ""
|
||||
ActionRotateCCW = "rotate-ccw"
|
||||
ActionRotateCW = "rotate-cw"
|
||||
ActionMoveLeft = "move-left"
|
||||
ActionMoveRight = "move-right"
|
||||
ActionSoftDrop = "soft-drop"
|
||||
ActionHardDrop = "hard-drop"
|
||||
ActionPing = "ping"
|
||||
ActionStats = "stats"
|
||||
ActionNick = "nick"
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue