488 lines
9.9 KiB
Go
488 lines
9.9 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"runtime/pprof"
|
|
"strings"
|
|
|
|
"github.com/gdamore/tcell/v2"
|
|
"code.rocketnine.space/tslocum/cbind"
|
|
"code.rocketnine.space/tslocum/netris/pkg/event"
|
|
"code.rocketnine.space/tslocum/netris/pkg/game"
|
|
)
|
|
|
|
type Keybinding struct {
|
|
k tcell.Key
|
|
r rune
|
|
m tcell.ModMask
|
|
|
|
a event.GameAction
|
|
}
|
|
|
|
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 {
|
|
scroll = (showLogLines - 2) * direction
|
|
} else {
|
|
scroll = showLogLines * direction
|
|
}
|
|
|
|
r, _ := recent.GetScrollOffset()
|
|
r += scroll
|
|
if r < 0 {
|
|
r = 0
|
|
}
|
|
recent.ScrollTo(r, 0)
|
|
|
|
draw <- event.DrawAll
|
|
}
|
|
|
|
// Render functions called here don't need to be queued (Draw is called when nil is returned)
|
|
func handleKeypress(ev *tcell.EventKey) *tcell.EventKey {
|
|
k := ev.Key()
|
|
r := ev.Rune()
|
|
|
|
if capturingKeybind {
|
|
capturingKeybind = false
|
|
if k == tcell.KeyEscape {
|
|
draftKeybindings = nil
|
|
|
|
app.SetRoot(gameSettingsContainerGrid, true)
|
|
updateTitle()
|
|
|
|
return nil
|
|
}
|
|
|
|
for i, bind := range draftKeybindings {
|
|
if (bind.k != 0 && bind.k != k) || (bind.r != 0 && bind.r != r) || (bind.m != 0 && bind.m != ev.Modifiers()) {
|
|
continue
|
|
}
|
|
|
|
draftKeybindings = append(draftKeybindings[:i], draftKeybindings[i+1:]...)
|
|
break
|
|
}
|
|
|
|
var action event.GameAction
|
|
switch currentSelection {
|
|
case 1:
|
|
action = event.ActionRotateCCW
|
|
case 2:
|
|
action = event.ActionRotateCW
|
|
case 3:
|
|
action = event.ActionMoveLeft
|
|
case 4:
|
|
action = event.ActionMoveRight
|
|
case 5:
|
|
action = event.ActionSoftDrop
|
|
case 6:
|
|
action = event.ActionHardDrop
|
|
default:
|
|
log.Fatal("setting keybind for unknown action")
|
|
}
|
|
|
|
draftKeybindings = append(draftKeybindings, &Keybinding{k: k, r: r, m: ev.Modifiers(), a: action})
|
|
|
|
app.SetRoot(gameSettingsContainerGrid, true)
|
|
updateTitle()
|
|
return nil
|
|
} else if titleVisible {
|
|
if currentScreen > 1 {
|
|
switch k {
|
|
case tcell.KeyEscape:
|
|
if currentScreen == screenNewGame {
|
|
currentScreen = screenGames
|
|
gameListSelected = 0
|
|
currentSelection = 0
|
|
app.SetRoot(gameListContainerGrid, true)
|
|
renderGameList()
|
|
updateTitle()
|
|
return nil
|
|
} else if currentScreen == screenGames {
|
|
currentScreen = screenTitle
|
|
} else {
|
|
currentScreen = screenSettings
|
|
}
|
|
currentSelection = 0
|
|
|
|
app.SetRoot(titleContainerGrid, true)
|
|
updateTitle()
|
|
return nil
|
|
}
|
|
|
|
if currentScreen == screenPlayerSettings {
|
|
switch k {
|
|
case tcell.KeyTab:
|
|
currentSelection++
|
|
if currentSelection > 2 {
|
|
currentSelection = 2
|
|
}
|
|
|
|
updateTitle()
|
|
return nil
|
|
case tcell.KeyBacktab:
|
|
currentSelection--
|
|
if currentSelection < 0 {
|
|
currentSelection = 0
|
|
}
|
|
|
|
updateTitle()
|
|
return nil
|
|
case tcell.KeyEnter:
|
|
selectTitleButton()
|
|
return nil
|
|
}
|
|
} else if currentScreen == screenGameSettings {
|
|
switch k {
|
|
case tcell.KeyTab:
|
|
currentSelection++
|
|
if currentSelection > 8 {
|
|
currentSelection = 8
|
|
}
|
|
|
|
updateTitle()
|
|
return nil
|
|
case tcell.KeyBacktab:
|
|
currentSelection--
|
|
if currentSelection < 0 {
|
|
currentSelection = 0
|
|
}
|
|
|
|
updateTitle()
|
|
return nil
|
|
case tcell.KeyEnter:
|
|
selectTitleButton()
|
|
return nil
|
|
}
|
|
} else if currentScreen == screenGames {
|
|
switch k {
|
|
case tcell.KeyUp:
|
|
if currentSelection == 0 {
|
|
if gameListSelected > 0 {
|
|
gameListSelected--
|
|
}
|
|
renderGameList()
|
|
}
|
|
return nil
|
|
case tcell.KeyBacktab:
|
|
previousTitleButton()
|
|
updateTitle()
|
|
renderGameList()
|
|
return nil
|
|
case tcell.KeyDown:
|
|
if currentSelection == 0 {
|
|
if gameListSelected < len(gameList)-1 {
|
|
gameListSelected++
|
|
}
|
|
renderGameList()
|
|
}
|
|
return nil
|
|
case tcell.KeyTab:
|
|
nextTitleButton()
|
|
updateTitle()
|
|
renderGameList()
|
|
return nil
|
|
case tcell.KeyEnter:
|
|
selectTitleButton()
|
|
return nil
|
|
default:
|
|
if currentSelection == 0 {
|
|
switch r {
|
|
case 'j', 'J':
|
|
if gameListSelected < len(gameList)-1 {
|
|
gameListSelected++
|
|
}
|
|
renderGameList()
|
|
return nil
|
|
case 'k', 'K':
|
|
if gameListSelected > 0 {
|
|
gameListSelected--
|
|
}
|
|
renderGameList()
|
|
return nil
|
|
case 'r', 'R':
|
|
refreshGameList()
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
} else if currentScreen == screenNewGame {
|
|
switch k {
|
|
case tcell.KeyBacktab:
|
|
previousTitleButton()
|
|
updateTitle()
|
|
return nil
|
|
case tcell.KeyTab:
|
|
nextTitleButton()
|
|
updateTitle()
|
|
return nil
|
|
case tcell.KeyEnter:
|
|
selectTitleButton()
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return ev
|
|
}
|
|
|
|
switch k {
|
|
case tcell.KeyEnter:
|
|
selectTitleButton()
|
|
return nil
|
|
case tcell.KeyUp, tcell.KeyBacktab:
|
|
previousTitleButton()
|
|
updateTitle()
|
|
return nil
|
|
case tcell.KeyDown, tcell.KeyTab:
|
|
nextTitleButton()
|
|
updateTitle()
|
|
return nil
|
|
case tcell.KeyEscape:
|
|
if currentScreen == screenSettings {
|
|
currentScreen = screenTitle
|
|
currentSelection = 0
|
|
updateTitle()
|
|
} else if joinedGame {
|
|
setTitleVisible(false)
|
|
} else {
|
|
done <- true
|
|
}
|
|
return nil
|
|
default:
|
|
switch r {
|
|
case 'k', 'K':
|
|
previousTitleButton()
|
|
updateTitle()
|
|
return nil
|
|
case 'j', 'J':
|
|
nextTitleButton()
|
|
updateTitle()
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return ev
|
|
}
|
|
|
|
if inputActive {
|
|
switch k {
|
|
case tcell.KeyEnter:
|
|
defer setInputStatus(false)
|
|
|
|
msg := inputView.GetText()
|
|
if strings.TrimSpace(msg) == "" {
|
|
return nil
|
|
}
|
|
|
|
msgl := strings.ToLower(msg)
|
|
switch {
|
|
case strings.HasPrefix(msgl, "/nick"):
|
|
if activeGame != nil && len(msg) > 6 {
|
|
var oldnick string
|
|
activeGame.Lock()
|
|
if p, ok := activeGame.Players[activeGame.LocalPlayer]; ok {
|
|
oldnick = p.Name
|
|
p.Name = game.Nickname(msg[6:])
|
|
} else {
|
|
return nil
|
|
}
|
|
activeGame.ProcessActionL(event.ActionNick)
|
|
if p, ok := activeGame.Players[activeGame.LocalPlayer]; ok {
|
|
p.Name = oldnick
|
|
}
|
|
activeGame.Unlock()
|
|
}
|
|
case strings.HasPrefix(msgl, "/ping"):
|
|
if activeGame != nil {
|
|
activeGame.ProcessAction(event.ActionPing)
|
|
}
|
|
case strings.HasPrefix(msgl, "/stats"):
|
|
if activeGame != nil {
|
|
activeGame.ProcessAction(event.ActionStats)
|
|
}
|
|
case strings.HasPrefix(msgl, "/version"):
|
|
v := game.Version
|
|
if v == "" {
|
|
v = "unknown"
|
|
}
|
|
|
|
logMessage(fmt.Sprintf("netris version %s", v))
|
|
case strings.HasPrefix(msgl, "/cpu"):
|
|
if profileCPU == nil {
|
|
if len(msg) < 5 {
|
|
logMessage("Profile name must be specified")
|
|
} else {
|
|
profileName := strings.TrimSpace(msg[5:])
|
|
|
|
var err error
|
|
profileCPU, err = os.Create(profileName)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
err = pprof.StartCPUProfile(profileCPU)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
logMessage(fmt.Sprintf("Started profiling CPU usage as %s", profileName))
|
|
}
|
|
} else {
|
|
pprof.StopCPUProfile()
|
|
profileCPU.Close()
|
|
profileCPU = nil
|
|
|
|
logMessage("Stopped profiling CPU usage")
|
|
}
|
|
default:
|
|
if activeGame != nil {
|
|
activeGame.Event <- &event.MessageEvent{Message: msg}
|
|
} else {
|
|
logMessage("Message not sent - not currently connected to any game")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
case tcell.KeyPgUp:
|
|
scrollMessages(-1)
|
|
return nil
|
|
case tcell.KeyPgDn:
|
|
scrollMessages(1)
|
|
return nil
|
|
case tcell.KeyEscape:
|
|
setInputStatus(false)
|
|
return nil
|
|
}
|
|
|
|
return ev
|
|
}
|
|
|
|
switch k {
|
|
case tcell.KeyEnter:
|
|
setInputStatus(!inputActive)
|
|
return nil
|
|
case tcell.KeyTab:
|
|
setShowDetails(!showDetails)
|
|
return nil
|
|
case tcell.KeyPgUp:
|
|
scrollMessages(-1)
|
|
return nil
|
|
case tcell.KeyPgDn:
|
|
scrollMessages(1)
|
|
return nil
|
|
case tcell.KeyEscape:
|
|
setTitleVisible(true)
|
|
return nil
|
|
}
|
|
|
|
return inputConfig.Capture(ev)
|
|
}
|
|
|
|
func rotateCCW(ev *tcell.EventKey) *tcell.EventKey {
|
|
if activeGame == nil {
|
|
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
|
|
}
|