Support custom game creation
This commit is contained in:
parent
8196f84ad9
commit
4aa44246f2
18 changed files with 1210 additions and 570 deletions
|
@ -1,4 +1,6 @@
|
|||
0.1.1:
|
||||
- Add game browser and support custom game creation
|
||||
- Add website and version to title screen
|
||||
- Kick inactive players
|
||||
|
||||
0.1.0:
|
||||
|
|
8
NETWORKING.md
Normal file
8
NETWORKING.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
*This is a draft specification of a future networking protocol.*
|
||||
|
||||
# Overview
|
||||
|
||||
Gameplay is recorded and buffered for 1-2 seconds before being sent to the server, then distributed among all players.
|
||||
|
||||
Each
|
||||
|
10
README.md
10
README.md
|
@ -5,14 +5,16 @@
|
|||
|
||||
Multiplayer Tetris clone
|
||||
|
||||
![](https://netris.rocketnine.space/static/screenshot4.png)
|
||||
## Play Without Installing
|
||||
|
||||
## Demo
|
||||
|
||||
To play netris without installing:
|
||||
To play netris without installing, connect via [SSH](https://en.wikipedia.org/wiki/Secure_Shell):
|
||||
|
||||
```ssh netris.rocketnine.space```
|
||||
|
||||
## Screenshot
|
||||
|
||||
![](https://netris.rocketnine.space/static/screenshot4.png)
|
||||
|
||||
## Install
|
||||
|
||||
Choose one of the following methods:
|
||||
|
|
|
@ -33,8 +33,8 @@ var (
|
|||
|
||||
joinedGame bool
|
||||
|
||||
draw = make(chan event.DrawObject, game.CommandQueueSize)
|
||||
selectMode = make(chan event.GameMode, game.CommandQueueSize)
|
||||
draw = make(chan event.DrawObject, game.CommandQueueSize)
|
||||
joinGame = make(chan int, game.CommandQueueSize)
|
||||
|
||||
renderLock = new(sync.Mutex)
|
||||
renderBuffer bytes.Buffer
|
||||
|
@ -60,6 +60,9 @@ var (
|
|||
buttonKeybindHardDrop *tview.Button
|
||||
buttonKeybindCancel *tview.Button
|
||||
buttonKeybindSave *tview.Button
|
||||
|
||||
buttonCancel *tview.Button
|
||||
buttonStart *tview.Button
|
||||
)
|
||||
|
||||
const DefaultStatusText = "Press Enter to chat, Z/X to rotate, arrow keys or HJKL to move/drop"
|
||||
|
@ -87,284 +90,14 @@ var renderBlock = map[mino.Block][]byte{
|
|||
var (
|
||||
renderHLine = []byte(string(tcell.RuneHLine))
|
||||
renderVLine = []byte(string(tcell.RuneVLine))
|
||||
renderLTee = []byte(string(tcell.RuneLTee))
|
||||
renderRTee = []byte(string(tcell.RuneRTee))
|
||||
renderULCorner = []byte(string(tcell.RuneULCorner))
|
||||
renderURCorner = []byte(string(tcell.RuneURCorner))
|
||||
renderLLCorner = []byte(string(tcell.RuneLLCorner))
|
||||
renderLRCorner = []byte(string(tcell.RuneLRCorner))
|
||||
)
|
||||
|
||||
func initGUI(skipTitle bool) (*tview.Application, error) {
|
||||
app = tview.NewApplication()
|
||||
|
||||
app.SetAfterResizeFunc(handleResize)
|
||||
|
||||
inputView = tview.NewInputField().
|
||||
SetText(DefaultStatusText).
|
||||
SetLabel("> ").
|
||||
SetFieldWidth(0).
|
||||
SetFieldBackgroundColor(tcell.ColorDefault).
|
||||
SetFieldTextColor(tcell.ColorWhite)
|
||||
|
||||
inputView.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if !inputActive {
|
||||
return nil
|
||||
}
|
||||
|
||||
return event
|
||||
})
|
||||
|
||||
gameGrid = tview.NewGrid().
|
||||
SetBorders(false).
|
||||
SetRows(2+(20*blockSize)+extraScreenPadding, -1)
|
||||
|
||||
mtx = tview.NewTextView().
|
||||
SetScrollable(false).
|
||||
SetTextAlign(tview.AlignLeft).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false)
|
||||
|
||||
mtx.SetDynamicColors(true)
|
||||
|
||||
side = tview.NewTextView().
|
||||
SetScrollable(false).
|
||||
SetTextAlign(tview.AlignLeft).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false)
|
||||
|
||||
side.SetDynamicColors(true)
|
||||
|
||||
buffer = tview.NewTextView().
|
||||
SetScrollable(false).
|
||||
SetTextAlign(tview.AlignLeft).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false)
|
||||
|
||||
buffer.SetDynamicColors(true)
|
||||
|
||||
spacer := tview.NewBox()
|
||||
|
||||
recent = tview.NewTextView().
|
||||
SetScrollable(true).
|
||||
SetTextAlign(tview.AlignLeft).
|
||||
SetWrap(true).
|
||||
SetWordWrap(true)
|
||||
|
||||
gameGrid.SetColumns(1, 4+(10*blockSize), 10, -1).
|
||||
AddItem(spacer, 0, 0, 2, 1, 0, 0, false).
|
||||
AddItem(mtx, 0, 1, 1, 1, 0, 0, false).
|
||||
AddItem(side, 0, 2, 1, 1, 0, 0, false).
|
||||
AddItem(buffer, 0, 3, 1, 1, 0, 0, false).
|
||||
AddItem(inputView, 1, 1, 1, 3, 0, 0, true).
|
||||
AddItem(recent, 2, 1, 1, 3, 0, 0, true)
|
||||
|
||||
// Set up title screen
|
||||
|
||||
titleVisible = !skipTitle
|
||||
|
||||
minos, err := mino.Generate(4)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to render title: failed to generate minos: %s", err)
|
||||
}
|
||||
|
||||
var (
|
||||
piece *mino.Piece
|
||||
addToRight bool
|
||||
i int
|
||||
)
|
||||
for y := 0; y < 6; y++ {
|
||||
for x := 0; x < 4; x++ {
|
||||
piece = mino.NewPiece(minos[i], mino.Point{x * 5, (y * 5)})
|
||||
|
||||
i++
|
||||
if i == len(minos) {
|
||||
i = 0
|
||||
}
|
||||
|
||||
if addToRight {
|
||||
titlePiecesR = append(titlePiecesR, piece)
|
||||
} else {
|
||||
titlePiecesL = append(titlePiecesL, piece)
|
||||
}
|
||||
|
||||
addToRight = !addToRight
|
||||
}
|
||||
}
|
||||
|
||||
titleName = tview.NewTextView().
|
||||
SetScrollable(false).
|
||||
SetTextAlign(tview.AlignLeft).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetDynamicColors(true)
|
||||
|
||||
titleL = tview.NewTextView().
|
||||
SetScrollable(false).
|
||||
SetTextAlign(tview.AlignLeft).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetDynamicColors(true)
|
||||
|
||||
titleR = tview.NewTextView().
|
||||
SetScrollable(false).
|
||||
SetTextAlign(tview.AlignLeft).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetDynamicColors(true)
|
||||
|
||||
go handleTitle()
|
||||
|
||||
buttonA = tview.NewButton("A")
|
||||
buttonLabelA = tview.NewTextView().SetTextAlign(tview.AlignCenter)
|
||||
|
||||
buttonB = tview.NewButton("B")
|
||||
buttonLabelB = tview.NewTextView().SetTextAlign(tview.AlignCenter)
|
||||
|
||||
buttonC = tview.NewButton("C")
|
||||
buttonLabelC = tview.NewTextView().SetTextAlign(tview.AlignCenter)
|
||||
|
||||
titleNameGrid := tview.NewGrid().SetRows(5).
|
||||
AddItem(titleName, 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView().SetText(SubTitle+game.Version), 1, 0, 1, 1, 0, 0, false)
|
||||
|
||||
titleGrid = tview.NewGrid().
|
||||
SetRows(7, 3, 3, 3, 3, 3, 2).
|
||||
SetColumns(-1, 38, -1).
|
||||
AddItem(titleL, 0, 0, 7, 1, 0, 0, false).
|
||||
AddItem(titleNameGrid, 0, 1, 1, 1, 0, 0, false).
|
||||
AddItem(titleR, 0, 2, 7, 1, 0, 0, false).
|
||||
AddItem(buttonA, 1, 1, 1, 1, 0, 0, false).
|
||||
AddItem(buttonLabelA, 2, 1, 1, 1, 0, 0, false).
|
||||
AddItem(buttonB, 3, 1, 1, 1, 0, 0, false).
|
||||
AddItem(buttonLabelB, 4, 1, 1, 1, 0, 0, false).
|
||||
AddItem(buttonC, 5, 1, 1, 1, 0, 0, false).
|
||||
AddItem(buttonLabelC, 6, 1, 1, 1, 0, 0, false)
|
||||
|
||||
playerSettingsTitle := tview.NewTextView().
|
||||
SetTextAlign(tview.AlignCenter).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetText("Player Settings")
|
||||
|
||||
playerSettingsForm = tview.NewForm().SetButtonsAlign(tview.AlignCenter)
|
||||
|
||||
playerSettingsGrid = tview.NewGrid().
|
||||
SetRows(7, 2, -1, 1).
|
||||
SetColumns(-1, 38, -1).
|
||||
AddItem(titleL, 0, 0, 3, 1, 0, 0, false).
|
||||
AddItem(titleNameGrid, 0, 1, 1, 1, 0, 0, false).
|
||||
AddItem(titleR, 0, 2, 3, 1, 0, 0, false).
|
||||
AddItem(playerSettingsTitle, 1, 1, 1, 1, 0, 0, true).
|
||||
AddItem(playerSettingsForm, 2, 1, 1, 1, 0, 0, true).
|
||||
AddItem(tview.NewTextView().
|
||||
SetTextAlign(tview.AlignCenter).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetText("Press Tab to move between fields"), 3, 1, 1, 1, 0, 0, true)
|
||||
|
||||
gameSettingsTitle := tview.NewTextView().
|
||||
SetTextAlign(tview.AlignCenter).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetText("Game Settings")
|
||||
|
||||
buttonKeybindRotateCCW = tview.NewButton("Set")
|
||||
buttonKeybindRotateCW = tview.NewButton("Set")
|
||||
buttonKeybindMoveLeft = tview.NewButton("Set")
|
||||
buttonKeybindMoveRight = tview.NewButton("Set")
|
||||
buttonKeybindSoftDrop = tview.NewButton("Set")
|
||||
buttonKeybindHardDrop = tview.NewButton("Set")
|
||||
buttonKeybindCancel = tview.NewButton("Cancel")
|
||||
buttonKeybindSave = tview.NewButton("Save")
|
||||
|
||||
rotateCCWGrid := tview.NewGrid().
|
||||
AddItem(tview.NewTextView().SetText("Rotate CCW"), 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(buttonKeybindRotateCCW, 0, 1, 1, 1, 0, 0, false)
|
||||
|
||||
rotateCWGrid := tview.NewGrid().
|
||||
AddItem(tview.NewTextView().SetText("Rotate CW"), 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(buttonKeybindRotateCW, 0, 1, 1, 1, 0, 0, false)
|
||||
|
||||
moveLeftGrid := tview.NewGrid().
|
||||
AddItem(tview.NewTextView().SetText("Move Left"), 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(buttonKeybindMoveLeft, 0, 1, 1, 1, 0, 0, false)
|
||||
|
||||
moveRightGrid := tview.NewGrid().
|
||||
AddItem(tview.NewTextView().SetText("Move Right"), 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(buttonKeybindMoveRight, 0, 1, 1, 1, 0, 0, false)
|
||||
|
||||
softDropGrid := tview.NewGrid().
|
||||
AddItem(tview.NewTextView().SetText("Soft Drop"), 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(buttonKeybindSoftDrop, 0, 1, 1, 1, 0, 0, false)
|
||||
|
||||
hardDropGrid := tview.NewGrid().
|
||||
AddItem(tview.NewTextView().SetText("Hard Drop"), 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(buttonKeybindHardDrop, 0, 1, 1, 1, 0, 0, false)
|
||||
|
||||
gameSettingsSubmitGrid := tview.NewGrid().
|
||||
SetColumns(-1, 10, 1, 10, -1).
|
||||
AddItem(tview.NewTextView(), 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(buttonKeybindCancel, 0, 1, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView(), 0, 2, 1, 1, 0, 0, false).
|
||||
AddItem(buttonKeybindSave, 0, 3, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView(), 0, 4, 1, 1, 0, 0, false)
|
||||
|
||||
gameSettingsGrid = tview.NewGrid().
|
||||
SetRows(7, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1).
|
||||
SetColumns(-1, 38, -1).
|
||||
AddItem(titleL, 0, 0, 16, 1, 0, 0, false).
|
||||
AddItem(titleNameGrid, 0, 1, 1, 1, 0, 0, false).
|
||||
AddItem(titleR, 0, 2, 16, 1, 0, 0, false).
|
||||
AddItem(gameSettingsTitle, 1, 1, 1, 1, 0, 0, false).
|
||||
AddItem(rotateCCWGrid, 2, 1, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView(), 3, 1, 1, 1, 0, 0, false).
|
||||
AddItem(rotateCWGrid, 4, 1, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView(), 5, 1, 1, 1, 0, 0, false).
|
||||
AddItem(moveLeftGrid, 6, 1, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView(), 7, 1, 1, 1, 0, 0, false).
|
||||
AddItem(moveRightGrid, 8, 1, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView(), 9, 1, 1, 1, 0, 0, false).
|
||||
AddItem(softDropGrid, 10, 1, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView(), 11, 1, 1, 1, 0, 0, false).
|
||||
AddItem(hardDropGrid, 12, 1, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView(), 13, 1, 1, 1, 0, 0, false).
|
||||
AddItem(gameSettingsSubmitGrid, 14, 1, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView().
|
||||
SetTextAlign(tview.AlignCenter).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetText("\nPress Tab to move between fields"), 15, 1, 1, 1, 0, 0, false)
|
||||
|
||||
titleContainerGrid = tview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
|
||||
AddItem(tview.NewTextView(), 0, 0, 1, 3, 0, 0, false).
|
||||
AddItem(tview.NewTextView(), 1, 0, 1, 1, 0, 0, false).
|
||||
AddItem(titleGrid, 1, 1, 1, 1, 0, 0, true).
|
||||
AddItem(tview.NewTextView(), 1, 2, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView(), 0, 0, 1, 3, 0, 0, false)
|
||||
|
||||
playerSettingsContainerGrid = tview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
|
||||
AddItem(tview.NewTextView(), 0, 0, 1, 3, 0, 0, false).
|
||||
AddItem(tview.NewTextView(), 1, 0, 1, 1, 0, 0, false).
|
||||
AddItem(playerSettingsGrid, 1, 1, 1, 1, 0, 0, true).
|
||||
AddItem(tview.NewTextView(), 1, 2, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView(), 0, 0, 1, 3, 0, 0, false)
|
||||
|
||||
gameSettingsContainerGrid = tview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
|
||||
AddItem(tview.NewTextView(), 0, 0, 1, 3, 0, 0, false).
|
||||
AddItem(tview.NewTextView(), 1, 0, 1, 1, 0, 0, false).
|
||||
AddItem(gameSettingsGrid, 1, 1, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView(), 1, 2, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView(), 0, 0, 1, 3, 0, 0, false)
|
||||
|
||||
app = app.SetInputCapture(handleKeypress)
|
||||
|
||||
if !skipTitle {
|
||||
app.SetRoot(titleContainerGrid, true)
|
||||
|
||||
updateTitle()
|
||||
} else {
|
||||
app.SetRoot(gameGrid, true)
|
||||
|
||||
app.SetFocus(nil)
|
||||
}
|
||||
|
||||
go handleDraw()
|
||||
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func resetPlayerSettingsForm() {
|
||||
playerSettingsForm.Clear(true).AddInputField("Name", nickname, 0, nil, func(text string) {
|
||||
nicknameDraft = text
|
||||
|
@ -498,14 +231,14 @@ func setInputStatus(active bool) {
|
|||
|
||||
inputActive = active
|
||||
|
||||
inputView.SetText("")
|
||||
if inputActive {
|
||||
app.SetFocus(inputView)
|
||||
} else {
|
||||
app.SetFocus(nil)
|
||||
}
|
||||
|
||||
app.Draw()
|
||||
app.QueueUpdateDraw(func() {
|
||||
inputView.SetText("")
|
||||
if inputActive {
|
||||
app.SetFocus(inputView)
|
||||
} else {
|
||||
app.SetFocus(nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func setShowDetails(active bool) {
|
||||
|
|
503
cmd/netris/gui_init.go
Normal file
503
cmd/netris/gui_init.go
Normal file
|
@ -0,0 +1,503 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"unicode"
|
||||
|
||||
"git.sr.ht/~tslocum/netris/pkg/event"
|
||||
"git.sr.ht/~tslocum/netris/pkg/game"
|
||||
"git.sr.ht/~tslocum/netris/pkg/mino"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/tslocum/tview"
|
||||
)
|
||||
|
||||
func initGUI(skipTitle bool) (*tview.Application, error) {
|
||||
app = tview.NewApplication()
|
||||
|
||||
app.SetAfterResizeFunc(handleResize)
|
||||
|
||||
inputView = tview.NewInputField().
|
||||
SetText(DefaultStatusText).
|
||||
SetLabel("> ").
|
||||
SetFieldWidth(0).
|
||||
SetFieldBackgroundColor(tcell.ColorDefault).
|
||||
SetFieldTextColor(tcell.ColorWhite)
|
||||
|
||||
inputView.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if !inputActive {
|
||||
return nil
|
||||
}
|
||||
|
||||
return event
|
||||
})
|
||||
|
||||
gameGrid = tview.NewGrid().
|
||||
SetBorders(false).
|
||||
SetRows(2+(20*blockSize)+extraScreenPadding, -1)
|
||||
|
||||
mtx = tview.NewTextView().
|
||||
SetScrollable(false).
|
||||
SetTextAlign(tview.AlignLeft).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false)
|
||||
|
||||
mtx.SetDynamicColors(true)
|
||||
|
||||
side = tview.NewTextView().
|
||||
SetScrollable(false).
|
||||
SetTextAlign(tview.AlignLeft).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false)
|
||||
|
||||
side.SetDynamicColors(true)
|
||||
|
||||
buffer = tview.NewTextView().
|
||||
SetScrollable(false).
|
||||
SetTextAlign(tview.AlignLeft).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false)
|
||||
|
||||
buffer.SetDynamicColors(true)
|
||||
|
||||
pad := tview.NewBox()
|
||||
|
||||
recent = tview.NewTextView().
|
||||
SetScrollable(true).
|
||||
SetTextAlign(tview.AlignLeft).
|
||||
SetWrap(true).
|
||||
SetWordWrap(true)
|
||||
|
||||
gameGrid.SetColumns(1, 4+(10*blockSize), 10, -1).
|
||||
AddItem(pad, 0, 0, 2, 1, 0, 0, false).
|
||||
AddItem(mtx, 0, 1, 1, 1, 0, 0, false).
|
||||
AddItem(side, 0, 2, 1, 1, 0, 0, false).
|
||||
AddItem(buffer, 0, 3, 1, 1, 0, 0, false).
|
||||
AddItem(inputView, 1, 1, 1, 3, 0, 0, true).
|
||||
AddItem(recent, 2, 1, 1, 3, 0, 0, true)
|
||||
|
||||
// Set up title screen
|
||||
|
||||
titleVisible = !skipTitle
|
||||
|
||||
minos, err := mino.Generate(4)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to render title: failed to generate minos: %s", err)
|
||||
}
|
||||
|
||||
var (
|
||||
piece *mino.Piece
|
||||
addToRight bool
|
||||
i int
|
||||
)
|
||||
for y := 0; y < 6; y++ {
|
||||
for x := 0; x < 4; x++ {
|
||||
piece = mino.NewPiece(minos[i], mino.Point{x * 5, (y * 5)})
|
||||
|
||||
i++
|
||||
if i == len(minos) {
|
||||
i = 0
|
||||
}
|
||||
|
||||
if addToRight {
|
||||
titlePiecesR = append(titlePiecesR, piece)
|
||||
} else {
|
||||
titlePiecesL = append(titlePiecesL, piece)
|
||||
}
|
||||
|
||||
addToRight = !addToRight
|
||||
}
|
||||
}
|
||||
|
||||
titleName = tview.NewTextView().
|
||||
SetScrollable(false).
|
||||
SetTextAlign(tview.AlignLeft).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetDynamicColors(true)
|
||||
|
||||
titleL = tview.NewTextView().
|
||||
SetScrollable(false).
|
||||
SetTextAlign(tview.AlignLeft).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetDynamicColors(true)
|
||||
|
||||
titleR = tview.NewTextView().
|
||||
SetScrollable(false).
|
||||
SetTextAlign(tview.AlignLeft).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetDynamicColors(true)
|
||||
|
||||
go handleTitle()
|
||||
|
||||
buttonA = tview.NewButton("A")
|
||||
buttonLabelA = tview.NewTextView().SetTextAlign(tview.AlignCenter)
|
||||
|
||||
buttonB = tview.NewButton("B")
|
||||
buttonLabelB = tview.NewTextView().SetTextAlign(tview.AlignCenter)
|
||||
|
||||
buttonC = tview.NewButton("C")
|
||||
buttonLabelC = tview.NewTextView().SetTextAlign(tview.AlignCenter)
|
||||
|
||||
titleNameGrid := tview.NewGrid().SetRows(5).
|
||||
AddItem(titleName, 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView().SetText(SubTitle+game.Version), 1, 0, 1, 1, 0, 0, false)
|
||||
|
||||
titleGrid = tview.NewGrid().
|
||||
SetRows(7, 3, 3, 3, 3, 3, 2).
|
||||
SetColumns(-1, 38, -1).
|
||||
AddItem(titleL, 0, 0, 7, 1, 0, 0, false).
|
||||
AddItem(titleNameGrid, 0, 1, 1, 1, 0, 0, false).
|
||||
AddItem(titleR, 0, 2, 7, 1, 0, 0, false).
|
||||
AddItem(buttonA, 1, 1, 1, 1, 0, 0, false).
|
||||
AddItem(buttonLabelA, 2, 1, 1, 1, 0, 0, false).
|
||||
AddItem(buttonB, 3, 1, 1, 1, 0, 0, false).
|
||||
AddItem(buttonLabelB, 4, 1, 1, 1, 0, 0, false).
|
||||
AddItem(buttonC, 5, 1, 1, 1, 0, 0, false).
|
||||
AddItem(buttonLabelC, 6, 1, 1, 1, 0, 0, false)
|
||||
|
||||
gameListView = tview.NewTextView().SetDynamicColors(true)
|
||||
|
||||
gameListButtonsGrid := tview.NewGrid().
|
||||
SetColumns(-1, 1, -1, 1, -1).
|
||||
AddItem(buttonA, 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 0, 1, 1, 1, 0, 0, false).
|
||||
AddItem(buttonB, 0, 2, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 0, 3, 1, 1, 0, 0, false).
|
||||
AddItem(buttonC, 0, 4, 1, 1, 0, 0, false)
|
||||
|
||||
gameListHeader = tview.NewTextView().SetTextAlign(tview.AlignCenter)
|
||||
|
||||
gameListGrid = tview.NewGrid().
|
||||
SetRows(7, 1, -1, 1, 3).
|
||||
SetColumns(-1, 38, -1).
|
||||
AddItem(titleL, 0, 0, 5, 1, 0, 0, false).
|
||||
AddItem(titleNameGrid, 0, 1, 1, 1, 0, 0, false).
|
||||
AddItem(titleR, 0, 2, 5, 1, 0, 0, false).
|
||||
AddItem(gameListHeader, 1, 1, 1, 1, 0, 0, true).
|
||||
AddItem(gameListView, 2, 1, 1, 1, 0, 0, true).
|
||||
AddItem(gameListButtonsGrid, 3, 1, 1, 1, 0, 0, true).
|
||||
AddItem(tview.NewTextView().
|
||||
SetTextAlign(tview.AlignCenter).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetText("\nRefresh: R\nPrevious: Shift+Tab - Next: Tab"), 4, 1, 1, 1, 0, 0, true)
|
||||
|
||||
buttonCancel = tview.NewButton("Cancel")
|
||||
buttonStart = tview.NewButton("Start")
|
||||
|
||||
newGameSubmitGrid := tview.NewGrid().
|
||||
SetColumns(-1, 10, 1, 10, -1).
|
||||
AddItem(pad, 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(buttonCancel, 0, 1, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 0, 2, 1, 1, 0, 0, false).
|
||||
AddItem(buttonStart, 0, 3, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 0, 4, 1, 1, 0, 0, false)
|
||||
|
||||
newGameNameInput = tview.NewInputField().SetText("netris")
|
||||
newGameMaxPlayersInput = tview.NewInputField().SetFieldWidth(3).SetAcceptanceFunc(func(textToCheck string, lastChar rune) bool {
|
||||
return unicode.IsDigit(lastChar) && len(textToCheck) <= 3
|
||||
})
|
||||
newGameSpeedLimitInput = tview.NewInputField().SetFieldWidth(3).SetAcceptanceFunc(func(textToCheck string, lastChar rune) bool {
|
||||
return unicode.IsDigit(lastChar) && len(textToCheck) <= 3
|
||||
})
|
||||
|
||||
resetNewGameInputs()
|
||||
|
||||
newGameNameGrid := tview.NewGrid().
|
||||
AddItem(tview.NewTextView().SetText("Name"), 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(newGameNameInput, 0, 1, 1, 1, 0, 0, false)
|
||||
|
||||
newGameMaxPlayersGrid := tview.NewGrid().
|
||||
AddItem(tview.NewTextView().SetText("Player Limit"), 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(newGameMaxPlayersInput, 0, 1, 1, 1, 0, 0, false)
|
||||
|
||||
newGameSpeedLimitGrid := tview.NewGrid().
|
||||
AddItem(tview.NewTextView().SetText("Speed Limit"), 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(newGameSpeedLimitInput, 0, 1, 1, 1, 0, 0, false)
|
||||
|
||||
newGameHeader := tview.NewTextView().
|
||||
SetTextAlign(tview.AlignCenter).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetText("New Game")
|
||||
|
||||
newGameGrid = tview.NewGrid().
|
||||
SetRows(7, 2, 1, 1, 1, 1, 1, 1, 1, -1, 3).
|
||||
SetColumns(-1, 38, -1).
|
||||
AddItem(titleL, 0, 0, 11, 1, 0, 0, false).
|
||||
AddItem(titleNameGrid, 0, 1, 1, 1, 0, 0, false).
|
||||
AddItem(titleR, 0, 2, 11, 1, 0, 0, false).
|
||||
AddItem(newGameHeader, 1, 1, 1, 1, 0, 0, false).
|
||||
AddItem(newGameNameGrid, 2, 1, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 3, 1, 1, 1, 0, 0, false).
|
||||
AddItem(newGameMaxPlayersGrid, 4, 1, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 5, 1, 1, 1, 0, 0, false).
|
||||
AddItem(newGameSpeedLimitGrid, 6, 1, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 7, 1, 1, 1, 0, 0, false).
|
||||
AddItem(newGameSubmitGrid, 8, 1, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 9, 1, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView().
|
||||
SetTextAlign(tview.AlignCenter).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetText("\nLimits set to zero are disabled\nPrevious: Shift+Tab - Next: Tab"), 10, 1, 1, 1, 0, 0, false)
|
||||
|
||||
playerSettingsTitle := tview.NewTextView().
|
||||
SetTextAlign(tview.AlignCenter).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetText("Player Settings")
|
||||
|
||||
playerSettingsForm = tview.NewForm().SetButtonsAlign(tview.AlignCenter)
|
||||
|
||||
playerSettingsGrid = tview.NewGrid().
|
||||
SetRows(7, 2, -1, 1).
|
||||
SetColumns(-1, 38, -1).
|
||||
AddItem(titleL, 0, 0, 4, 1, 0, 0, false).
|
||||
AddItem(titleNameGrid, 0, 1, 1, 1, 0, 0, false).
|
||||
AddItem(titleR, 0, 2, 4, 1, 0, 0, false).
|
||||
AddItem(playerSettingsTitle, 1, 1, 1, 1, 0, 0, true).
|
||||
AddItem(playerSettingsForm, 2, 1, 1, 1, 0, 0, true).
|
||||
AddItem(tview.NewTextView().
|
||||
SetTextAlign(tview.AlignCenter).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetText("Previous: Shift+Tab - Next: Tab"), 3, 1, 1, 1, 0, 0, true)
|
||||
|
||||
gameSettingsTitle := tview.NewTextView().
|
||||
SetTextAlign(tview.AlignCenter).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetText("Game Settings")
|
||||
|
||||
buttonKeybindRotateCCW = tview.NewButton("Set")
|
||||
buttonKeybindRotateCW = tview.NewButton("Set")
|
||||
buttonKeybindMoveLeft = tview.NewButton("Set")
|
||||
buttonKeybindMoveRight = tview.NewButton("Set")
|
||||
buttonKeybindSoftDrop = tview.NewButton("Set")
|
||||
buttonKeybindHardDrop = tview.NewButton("Set")
|
||||
buttonKeybindCancel = tview.NewButton("Cancel")
|
||||
buttonKeybindSave = tview.NewButton("Save")
|
||||
|
||||
rotateCCWGrid := tview.NewGrid().
|
||||
AddItem(tview.NewTextView().SetText("Rotate CCW"), 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(buttonKeybindRotateCCW, 0, 1, 1, 1, 0, 0, false)
|
||||
|
||||
rotateCWGrid := tview.NewGrid().
|
||||
AddItem(tview.NewTextView().SetText("Rotate CW"), 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(buttonKeybindRotateCW, 0, 1, 1, 1, 0, 0, false)
|
||||
|
||||
moveLeftGrid := tview.NewGrid().
|
||||
AddItem(tview.NewTextView().SetText("Move Left"), 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(buttonKeybindMoveLeft, 0, 1, 1, 1, 0, 0, false)
|
||||
|
||||
moveRightGrid := tview.NewGrid().
|
||||
AddItem(tview.NewTextView().SetText("Move Right"), 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(buttonKeybindMoveRight, 0, 1, 1, 1, 0, 0, false)
|
||||
|
||||
softDropGrid := tview.NewGrid().
|
||||
AddItem(tview.NewTextView().SetText("Soft Drop"), 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(buttonKeybindSoftDrop, 0, 1, 1, 1, 0, 0, false)
|
||||
|
||||
hardDropGrid := tview.NewGrid().
|
||||
AddItem(tview.NewTextView().SetText("Hard Drop"), 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(buttonKeybindHardDrop, 0, 1, 1, 1, 0, 0, false)
|
||||
|
||||
gameSettingsSubmitGrid := tview.NewGrid().
|
||||
SetColumns(-1, 10, 1, 10, -1).
|
||||
AddItem(pad, 0, 0, 1, 1, 0, 0, false).
|
||||
AddItem(buttonKeybindCancel, 0, 1, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 0, 2, 1, 1, 0, 0, false).
|
||||
AddItem(buttonKeybindSave, 0, 3, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 0, 4, 1, 1, 0, 0, false)
|
||||
|
||||
gameSettingsGrid = tview.NewGrid().
|
||||
SetRows(7, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1).
|
||||
SetColumns(-1, 38, -1).
|
||||
AddItem(titleL, 0, 0, 16, 1, 0, 0, false).
|
||||
AddItem(titleNameGrid, 0, 1, 1, 1, 0, 0, false).
|
||||
AddItem(titleR, 0, 2, 16, 1, 0, 0, false).
|
||||
AddItem(gameSettingsTitle, 1, 1, 1, 1, 0, 0, false).
|
||||
AddItem(rotateCCWGrid, 2, 1, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 3, 1, 1, 1, 0, 0, false).
|
||||
AddItem(rotateCWGrid, 4, 1, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 5, 1, 1, 1, 0, 0, false).
|
||||
AddItem(moveLeftGrid, 6, 1, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 7, 1, 1, 1, 0, 0, false).
|
||||
AddItem(moveRightGrid, 8, 1, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 9, 1, 1, 1, 0, 0, false).
|
||||
AddItem(softDropGrid, 10, 1, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 11, 1, 1, 1, 0, 0, false).
|
||||
AddItem(hardDropGrid, 12, 1, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 13, 1, 1, 1, 0, 0, false).
|
||||
AddItem(gameSettingsSubmitGrid, 14, 1, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView().
|
||||
SetTextAlign(tview.AlignCenter).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetText("\nPrevious: Shift+Tab - Next: Tab"), 15, 1, 1, 1, 0, 0, false)
|
||||
|
||||
titleContainerGrid = tview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
|
||||
AddItem(pad, 0, 0, 1, 3, 0, 0, false).
|
||||
AddItem(pad, 1, 0, 1, 1, 0, 0, false).
|
||||
AddItem(titleGrid, 1, 1, 1, 1, 0, 0, true).
|
||||
AddItem(pad, 1, 2, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 0, 0, 1, 3, 0, 0, false)
|
||||
|
||||
gameListContainerGrid = tview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
|
||||
AddItem(pad, 0, 0, 1, 3, 0, 0, false).
|
||||
AddItem(pad, 1, 0, 1, 1, 0, 0, false).
|
||||
AddItem(gameListGrid, 1, 1, 1, 1, 0, 0, true).
|
||||
AddItem(pad, 1, 2, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 0, 0, 1, 3, 0, 0, false)
|
||||
|
||||
newGameContainerGrid = tview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
|
||||
AddItem(pad, 0, 0, 1, 3, 0, 0, false).
|
||||
AddItem(pad, 1, 0, 1, 1, 0, 0, false).
|
||||
AddItem(newGameGrid, 1, 1, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 1, 2, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 0, 0, 1, 3, 0, 0, false)
|
||||
|
||||
playerSettingsContainerGrid = tview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
|
||||
AddItem(pad, 0, 0, 1, 3, 0, 0, false).
|
||||
AddItem(pad, 1, 0, 1, 1, 0, 0, false).
|
||||
AddItem(playerSettingsGrid, 1, 1, 1, 1, 0, 0, true).
|
||||
AddItem(pad, 1, 2, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 0, 0, 1, 3, 0, 0, false)
|
||||
|
||||
gameSettingsContainerGrid = tview.NewGrid().SetColumns(-1, 80, -1).SetRows(-1, 24, -1).
|
||||
AddItem(pad, 0, 0, 1, 3, 0, 0, false).
|
||||
AddItem(pad, 1, 0, 1, 1, 0, 0, false).
|
||||
AddItem(gameSettingsGrid, 1, 1, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 1, 2, 1, 1, 0, 0, false).
|
||||
AddItem(pad, 0, 0, 1, 3, 0, 0, false)
|
||||
|
||||
app = app.SetInputCapture(handleKeypress)
|
||||
|
||||
if !skipTitle {
|
||||
app.SetRoot(titleContainerGrid, true)
|
||||
|
||||
updateTitle()
|
||||
} else {
|
||||
app.SetRoot(gameGrid, true)
|
||||
|
||||
app.SetFocus(nil)
|
||||
}
|
||||
|
||||
go handleDraw()
|
||||
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func newTitleMatrixSide() *mino.Matrix {
|
||||
ev := make(chan interface{})
|
||||
go func() {
|
||||
for range ev {
|
||||
}
|
||||
}()
|
||||
|
||||
draw := make(chan event.DrawObject)
|
||||
go func() {
|
||||
for range draw {
|
||||
}
|
||||
}()
|
||||
|
||||
m := mino.NewMatrix(21, 24, 0, 1, ev, draw, mino.MatrixCustom)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func newTitleMatrixName() *mino.Matrix {
|
||||
ev := make(chan interface{})
|
||||
go func() {
|
||||
for range ev {
|
||||
}
|
||||
}()
|
||||
|
||||
draw := make(chan event.DrawObject)
|
||||
go func() {
|
||||
for range draw {
|
||||
}
|
||||
}()
|
||||
|
||||
m := mino.NewMatrix(36, 7, 0, 1, ev, draw, mino.MatrixCustom)
|
||||
|
||||
centerStart := (m.W / 2) - 17
|
||||
|
||||
var titleBlocks = []struct {
|
||||
mino.Point
|
||||
mino.Block
|
||||
}{
|
||||
// N
|
||||
{mino.Point{0, 0}, mino.BlockSolidRed},
|
||||
{mino.Point{0, 1}, mino.BlockSolidRed},
|
||||
{mino.Point{0, 2}, mino.BlockSolidRed},
|
||||
{mino.Point{0, 3}, mino.BlockSolidRed},
|
||||
{mino.Point{0, 4}, mino.BlockSolidRed},
|
||||
{mino.Point{1, 3}, mino.BlockSolidRed},
|
||||
{mino.Point{2, 2}, mino.BlockSolidRed},
|
||||
{mino.Point{3, 1}, mino.BlockSolidRed},
|
||||
{mino.Point{4, 0}, mino.BlockSolidRed},
|
||||
{mino.Point{4, 1}, mino.BlockSolidRed},
|
||||
{mino.Point{4, 2}, mino.BlockSolidRed},
|
||||
{mino.Point{4, 3}, mino.BlockSolidRed},
|
||||
{mino.Point{4, 4}, mino.BlockSolidRed},
|
||||
|
||||
// E
|
||||
{mino.Point{7, 0}, mino.BlockSolidYellow},
|
||||
{mino.Point{7, 1}, mino.BlockSolidYellow},
|
||||
{mino.Point{7, 2}, mino.BlockSolidYellow},
|
||||
{mino.Point{7, 3}, mino.BlockSolidYellow},
|
||||
{mino.Point{7, 4}, mino.BlockSolidYellow},
|
||||
{mino.Point{8, 0}, mino.BlockSolidYellow},
|
||||
{mino.Point{9, 0}, mino.BlockSolidYellow},
|
||||
{mino.Point{8, 2}, mino.BlockSolidYellow},
|
||||
{mino.Point{9, 2}, mino.BlockSolidYellow},
|
||||
{mino.Point{8, 4}, mino.BlockSolidYellow},
|
||||
{mino.Point{9, 4}, mino.BlockSolidYellow},
|
||||
|
||||
// T
|
||||
{mino.Point{12, 4}, mino.BlockSolidGreen},
|
||||
{mino.Point{13, 4}, mino.BlockSolidGreen},
|
||||
{mino.Point{14, 0}, mino.BlockSolidGreen},
|
||||
{mino.Point{14, 1}, mino.BlockSolidGreen},
|
||||
{mino.Point{14, 2}, mino.BlockSolidGreen},
|
||||
{mino.Point{14, 3}, mino.BlockSolidGreen},
|
||||
{mino.Point{14, 4}, mino.BlockSolidGreen},
|
||||
{mino.Point{15, 4}, mino.BlockSolidGreen},
|
||||
{mino.Point{16, 4}, mino.BlockSolidGreen},
|
||||
|
||||
// R
|
||||
{mino.Point{19, 0}, mino.BlockSolidCyan},
|
||||
{mino.Point{19, 1}, mino.BlockSolidCyan},
|
||||
{mino.Point{19, 2}, mino.BlockSolidCyan},
|
||||
{mino.Point{19, 3}, mino.BlockSolidCyan},
|
||||
{mino.Point{19, 4}, mino.BlockSolidCyan},
|
||||
{mino.Point{20, 2}, mino.BlockSolidCyan},
|
||||
{mino.Point{20, 4}, mino.BlockSolidCyan},
|
||||
{mino.Point{21, 2}, mino.BlockSolidCyan},
|
||||
{mino.Point{21, 4}, mino.BlockSolidCyan},
|
||||
{mino.Point{22, 0}, mino.BlockSolidCyan},
|
||||
{mino.Point{22, 1}, mino.BlockSolidCyan},
|
||||
{mino.Point{22, 3}, mino.BlockSolidCyan},
|
||||
|
||||
// I
|
||||
{mino.Point{25, 0}, mino.BlockSolidBlue},
|
||||
{mino.Point{25, 1}, mino.BlockSolidBlue},
|
||||
{mino.Point{25, 2}, mino.BlockSolidBlue},
|
||||
{mino.Point{25, 3}, mino.BlockSolidBlue},
|
||||
{mino.Point{25, 4}, mino.BlockSolidBlue},
|
||||
|
||||
// S
|
||||
{mino.Point{28, 0}, mino.BlockSolidMagenta},
|
||||
{mino.Point{29, 0}, mino.BlockSolidMagenta},
|
||||
{mino.Point{30, 0}, mino.BlockSolidMagenta},
|
||||
{mino.Point{31, 1}, mino.BlockSolidMagenta},
|
||||
{mino.Point{29, 2}, mino.BlockSolidMagenta},
|
||||
{mino.Point{30, 2}, mino.BlockSolidMagenta},
|
||||
{mino.Point{28, 3}, mino.BlockSolidMagenta},
|
||||
{mino.Point{29, 4}, mino.BlockSolidMagenta},
|
||||
{mino.Point{30, 4}, mino.BlockSolidMagenta},
|
||||
{mino.Point{31, 4}, mino.BlockSolidMagenta},
|
||||
}
|
||||
|
||||
for _, titleBlock := range titleBlocks {
|
||||
if !m.SetBlock(centerStart+titleBlock.X, titleBlock.Y, titleBlock.Block, false) {
|
||||
log.Fatalf("failed to set title block %s", titleBlock.Point)
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
|
@ -60,6 +60,7 @@ func scrollMessages(direction int) {
|
|||
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()
|
||||
|
@ -111,7 +112,19 @@ func handleKeypress(ev *tcell.EventKey) *tcell.EventKey {
|
|||
if titleScreen > 1 {
|
||||
switch k {
|
||||
case tcell.KeyEscape:
|
||||
titleScreen = 1
|
||||
if titleScreen == 5 {
|
||||
titleScreen = 4
|
||||
gameListSelected = 0
|
||||
titleSelectedButton = 0
|
||||
app.SetRoot(gameListContainerGrid, true)
|
||||
renderGameList()
|
||||
updateTitle()
|
||||
return nil
|
||||
} else if titleScreen == 4 {
|
||||
titleScreen = 0
|
||||
} else {
|
||||
titleScreen = 1
|
||||
}
|
||||
titleSelectedButton = 0
|
||||
|
||||
app.SetRoot(titleContainerGrid, true)
|
||||
|
@ -157,6 +170,106 @@ func handleKeypress(ev *tcell.EventKey) *tcell.EventKey {
|
|||
app.SetRoot(modal, true)
|
||||
|
||||
capturingKeybind = true
|
||||
return nil
|
||||
}
|
||||
} else if titleScreen == 4 {
|
||||
switch k {
|
||||
case tcell.KeyUp:
|
||||
if titleSelectedButton == 0 {
|
||||
if gameListSelected > 0 {
|
||||
gameListSelected--
|
||||
}
|
||||
renderGameList()
|
||||
}
|
||||
return nil
|
||||
case tcell.KeyBacktab:
|
||||
previousTitleButton()
|
||||
updateTitle()
|
||||
renderGameList()
|
||||
return nil
|
||||
case tcell.KeyDown:
|
||||
if titleSelectedButton == 0 {
|
||||
if gameListSelected < len(gameList)-1 {
|
||||
gameListSelected++
|
||||
}
|
||||
renderGameList()
|
||||
}
|
||||
return nil
|
||||
case tcell.KeyTab:
|
||||
nextTitleButton()
|
||||
updateTitle()
|
||||
renderGameList()
|
||||
return nil
|
||||
case tcell.KeyEnter:
|
||||
if titleSelectedButton == 0 {
|
||||
if gameListSelected >= 0 && gameListSelected < len(gameList) {
|
||||
joinGame <- gameList[gameListSelected].ID
|
||||
}
|
||||
} else if titleSelectedButton == 1 {
|
||||
titleScreen = 5
|
||||
titleSelectedButton = 0
|
||||
|
||||
resetNewGameInputs()
|
||||
app.SetRoot(newGameContainerGrid, true).SetFocus(nil)
|
||||
updateTitle()
|
||||
} else if titleSelectedButton == 2 {
|
||||
titleScreen = 5
|
||||
titleSelectedButton = 0
|
||||
|
||||
modal := tview.NewModal().SetText("Joining another server by IP via GUI is not yet implemented.\nPlease re-launch netris with the --connect argument instead.\n\nPress Escape to go back").ClearButtons()
|
||||
app.SetRoot(modal, true)
|
||||
} else if titleSelectedButton == 3 {
|
||||
titleScreen = 0
|
||||
titleSelectedButton = 0
|
||||
|
||||
app.SetRoot(titleContainerGrid, true)
|
||||
updateTitle()
|
||||
}
|
||||
|
||||
return nil
|
||||
default:
|
||||
if titleSelectedButton == 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 titleScreen == 5 {
|
||||
switch k {
|
||||
case tcell.KeyBacktab:
|
||||
previousTitleButton()
|
||||
updateTitle()
|
||||
return nil
|
||||
case tcell.KeyTab:
|
||||
nextTitleButton()
|
||||
updateTitle()
|
||||
return nil
|
||||
case tcell.KeyEnter:
|
||||
if titleSelectedButton == 3 {
|
||||
titleScreen = 4
|
||||
gameListSelected = 0
|
||||
titleSelectedButton = 0
|
||||
app.SetRoot(gameListContainerGrid, true)
|
||||
renderGameList()
|
||||
updateTitle()
|
||||
} else if titleSelectedButton == 4 {
|
||||
joinGame <- event.GameIDNewCustom
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -174,9 +287,7 @@ func handleKeypress(ev *tcell.EventKey) *tcell.EventKey {
|
|||
titleScreen = 2
|
||||
titleSelectedButton = 0
|
||||
|
||||
app.SetRoot(playerSettingsContainerGrid, true)
|
||||
app.SetFocus(playerSettingsForm)
|
||||
app.Draw()
|
||||
app.SetRoot(playerSettingsContainerGrid, true).SetFocus(playerSettingsForm)
|
||||
return nil
|
||||
case 1:
|
||||
titleScreen = 3
|
||||
|
@ -186,9 +297,7 @@ func handleKeypress(ev *tcell.EventKey) *tcell.EventKey {
|
|||
draftKeybindings = make([]*Keybinding, len(keybindings))
|
||||
copy(draftKeybindings, keybindings)
|
||||
|
||||
app.SetRoot(gameSettingsContainerGrid, true)
|
||||
app.SetFocus(buttonKeybindRotateCCW)
|
||||
app.Draw()
|
||||
app.SetRoot(gameSettingsContainerGrid, true).SetFocus(buttonKeybindRotateCCW)
|
||||
return nil
|
||||
case 2:
|
||||
titleScreen = 0
|
||||
|
@ -216,10 +325,18 @@ func handleKeypress(ev *tcell.EventKey) *tcell.EventKey {
|
|||
} else {
|
||||
switch titleSelectedButton {
|
||||
case 0:
|
||||
selectMode <- event.ModePlayOnline
|
||||
titleScreen = 4
|
||||
titleSelectedButton = 0
|
||||
gameListSelected = 0
|
||||
|
||||
refreshGameList()
|
||||
renderGameList()
|
||||
|
||||
app.SetRoot(gameListContainerGrid, true).SetFocus(nil)
|
||||
updateTitle()
|
||||
return nil
|
||||
case 1:
|
||||
selectMode <- event.ModePractice
|
||||
joinGame <- event.GameIDNewLocal
|
||||
return nil
|
||||
case 2:
|
||||
titleScreen = 1
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.sr.ht/~tslocum/netris/pkg/event"
|
||||
"git.sr.ht/~tslocum/netris/pkg/game"
|
||||
"git.sr.ht/~tslocum/netris/pkg/mino"
|
||||
"github.com/tslocum/tview"
|
||||
|
@ -25,10 +25,24 @@ var (
|
|||
titleGrid *tview.Grid
|
||||
titleContainerGrid *tview.Grid
|
||||
|
||||
gameListSelected int
|
||||
|
||||
newGameGrid *tview.Grid
|
||||
newGameNameInput *tview.InputField
|
||||
newGameMaxPlayersInput *tview.InputField
|
||||
newGameSpeedLimitInput *tview.InputField
|
||||
|
||||
playerSettingsForm *tview.Form
|
||||
playerSettingsGrid *tview.Grid
|
||||
playerSettingsContainerGrid *tview.Grid
|
||||
|
||||
gameList []*game.ListedGame
|
||||
gameListHeader *tview.TextView
|
||||
gameListView *tview.TextView
|
||||
gameListGrid *tview.Grid
|
||||
gameListContainerGrid *tview.Grid
|
||||
newGameContainerGrid *tview.Grid
|
||||
|
||||
gameSettingsGrid *tview.Grid
|
||||
gameSettingsContainerGrid *tview.Grid
|
||||
gameGrid *tview.Grid
|
||||
|
@ -61,7 +75,13 @@ func previousTitleButton() {
|
|||
}
|
||||
|
||||
func nextTitleButton() {
|
||||
if titleSelectedButton == 2 {
|
||||
maxButton := 2
|
||||
if titleScreen == 4 {
|
||||
maxButton = 3
|
||||
} else if titleScreen == 5 {
|
||||
maxButton = 4
|
||||
}
|
||||
if titleSelectedButton >= maxButton {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -122,6 +142,12 @@ func updateTitle() {
|
|||
|
||||
buttonC.SetLabel("Return")
|
||||
buttonLabelC.SetText("\nReturn to the last screen")
|
||||
} else if titleScreen == 4 {
|
||||
buttonA.SetLabel("New Game")
|
||||
|
||||
buttonB.SetLabel("Join by IP")
|
||||
|
||||
buttonC.SetLabel("Return")
|
||||
} else {
|
||||
if joinedGame {
|
||||
buttonA.SetLabel("Resume")
|
||||
|
@ -144,7 +170,35 @@ func updateTitle() {
|
|||
}
|
||||
}
|
||||
|
||||
if titleScreen > 1 {
|
||||
if titleScreen == 4 {
|
||||
switch titleSelectedButton {
|
||||
case 2:
|
||||
app.SetFocus(buttonB)
|
||||
case 3:
|
||||
app.SetFocus(buttonC)
|
||||
case 1:
|
||||
app.SetFocus(buttonA)
|
||||
default:
|
||||
app.SetFocus(nil)
|
||||
}
|
||||
|
||||
return
|
||||
} else if titleScreen == 5 {
|
||||
switch titleSelectedButton {
|
||||
case 1:
|
||||
app.SetFocus(newGameMaxPlayersInput)
|
||||
case 2:
|
||||
app.SetFocus(newGameSpeedLimitInput)
|
||||
case 3:
|
||||
app.SetFocus(buttonCancel)
|
||||
case 4:
|
||||
app.SetFocus(buttonStart)
|
||||
default:
|
||||
app.SetFocus(newGameNameInput)
|
||||
}
|
||||
|
||||
return
|
||||
} else if titleScreen > 1 {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -261,123 +315,129 @@ func renderTitle() {
|
|||
renderLock.Unlock()
|
||||
}
|
||||
|
||||
func newTitleMatrixSide() *mino.Matrix {
|
||||
ev := make(chan interface{})
|
||||
go func() {
|
||||
for range ev {
|
||||
func renderGameList() {
|
||||
w := 36
|
||||
|
||||
gameListView.Clear()
|
||||
gameListView.Write(renderULCorner)
|
||||
for i := 0; i < w; i++ {
|
||||
gameListView.Write(renderHLine)
|
||||
}
|
||||
gameListView.Write(renderURCorner)
|
||||
gameListView.Write([]byte("\n"))
|
||||
|
||||
gameListView.Write(renderVLine)
|
||||
gameListView.Write([]byte(fmt.Sprintf("%-29s%s", "Game", "Players")))
|
||||
gameListView.Write(renderVLine)
|
||||
gameListView.Write([]byte("\n"))
|
||||
|
||||
gameListView.Write(renderLTee)
|
||||
for i := 0; i < w; i++ {
|
||||
gameListView.Write(renderHLine)
|
||||
}
|
||||
gameListView.Write(renderRTee)
|
||||
gameListView.Write([]byte("\n"))
|
||||
|
||||
h := 8
|
||||
|
||||
for i, g := range gameList {
|
||||
p := strconv.Itoa(g.Players)
|
||||
if g.MaxPlayers > 0 {
|
||||
p += "/" + strconv.Itoa(g.MaxPlayers)
|
||||
}
|
||||
}()
|
||||
|
||||
draw := make(chan event.DrawObject)
|
||||
go func() {
|
||||
for range draw {
|
||||
gameListView.Write(renderVLine)
|
||||
if titleSelectedButton == 0 && gameListSelected == i {
|
||||
gameListView.Write([]byte("[#000000:#FFFFFF]"))
|
||||
}
|
||||
}()
|
||||
|
||||
m := mino.NewMatrix(21, 24, 0, 1, ev, draw, mino.MatrixCustom)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func newTitleMatrixName() *mino.Matrix {
|
||||
ev := make(chan interface{})
|
||||
go func() {
|
||||
for range ev {
|
||||
gameListView.Write([]byte(fmt.Sprintf("%-29s%7s", g.Name, p)))
|
||||
if titleSelectedButton == 0 && gameListSelected == i {
|
||||
gameListView.Write([]byte("[-:-]"))
|
||||
}
|
||||
}()
|
||||
gameListView.Write(renderVLine)
|
||||
gameListView.Write([]byte("\n"))
|
||||
|
||||
draw := make(chan event.DrawObject)
|
||||
go func() {
|
||||
for range draw {
|
||||
}
|
||||
}()
|
||||
|
||||
m := mino.NewMatrix(36, 7, 0, 1, ev, draw, mino.MatrixCustom)
|
||||
|
||||
centerStart := (m.W / 2) - 17
|
||||
|
||||
var titleBlocks = []struct {
|
||||
mino.Point
|
||||
mino.Block
|
||||
}{
|
||||
// N
|
||||
{mino.Point{0, 0}, mino.BlockSolidRed},
|
||||
{mino.Point{0, 1}, mino.BlockSolidRed},
|
||||
{mino.Point{0, 2}, mino.BlockSolidRed},
|
||||
{mino.Point{0, 3}, mino.BlockSolidRed},
|
||||
{mino.Point{0, 4}, mino.BlockSolidRed},
|
||||
{mino.Point{1, 3}, mino.BlockSolidRed},
|
||||
{mino.Point{2, 2}, mino.BlockSolidRed},
|
||||
{mino.Point{3, 1}, mino.BlockSolidRed},
|
||||
{mino.Point{4, 0}, mino.BlockSolidRed},
|
||||
{mino.Point{4, 1}, mino.BlockSolidRed},
|
||||
{mino.Point{4, 2}, mino.BlockSolidRed},
|
||||
{mino.Point{4, 3}, mino.BlockSolidRed},
|
||||
{mino.Point{4, 4}, mino.BlockSolidRed},
|
||||
|
||||
// E
|
||||
{mino.Point{7, 0}, mino.BlockSolidYellow},
|
||||
{mino.Point{7, 1}, mino.BlockSolidYellow},
|
||||
{mino.Point{7, 2}, mino.BlockSolidYellow},
|
||||
{mino.Point{7, 3}, mino.BlockSolidYellow},
|
||||
{mino.Point{7, 4}, mino.BlockSolidYellow},
|
||||
{mino.Point{8, 0}, mino.BlockSolidYellow},
|
||||
{mino.Point{9, 0}, mino.BlockSolidYellow},
|
||||
{mino.Point{8, 2}, mino.BlockSolidYellow},
|
||||
{mino.Point{9, 2}, mino.BlockSolidYellow},
|
||||
{mino.Point{8, 4}, mino.BlockSolidYellow},
|
||||
{mino.Point{9, 4}, mino.BlockSolidYellow},
|
||||
|
||||
// T
|
||||
{mino.Point{12, 4}, mino.BlockSolidGreen},
|
||||
{mino.Point{13, 4}, mino.BlockSolidGreen},
|
||||
{mino.Point{14, 0}, mino.BlockSolidGreen},
|
||||
{mino.Point{14, 1}, mino.BlockSolidGreen},
|
||||
{mino.Point{14, 2}, mino.BlockSolidGreen},
|
||||
{mino.Point{14, 3}, mino.BlockSolidGreen},
|
||||
{mino.Point{14, 4}, mino.BlockSolidGreen},
|
||||
{mino.Point{15, 4}, mino.BlockSolidGreen},
|
||||
{mino.Point{16, 4}, mino.BlockSolidGreen},
|
||||
|
||||
// R
|
||||
{mino.Point{19, 0}, mino.BlockSolidCyan},
|
||||
{mino.Point{19, 1}, mino.BlockSolidCyan},
|
||||
{mino.Point{19, 2}, mino.BlockSolidCyan},
|
||||
{mino.Point{19, 3}, mino.BlockSolidCyan},
|
||||
{mino.Point{19, 4}, mino.BlockSolidCyan},
|
||||
{mino.Point{20, 2}, mino.BlockSolidCyan},
|
||||
{mino.Point{20, 4}, mino.BlockSolidCyan},
|
||||
{mino.Point{21, 2}, mino.BlockSolidCyan},
|
||||
{mino.Point{21, 4}, mino.BlockSolidCyan},
|
||||
{mino.Point{22, 0}, mino.BlockSolidCyan},
|
||||
{mino.Point{22, 1}, mino.BlockSolidCyan},
|
||||
{mino.Point{22, 3}, mino.BlockSolidCyan},
|
||||
|
||||
// I
|
||||
{mino.Point{25, 0}, mino.BlockSolidBlue},
|
||||
{mino.Point{25, 1}, mino.BlockSolidBlue},
|
||||
{mino.Point{25, 2}, mino.BlockSolidBlue},
|
||||
{mino.Point{25, 3}, mino.BlockSolidBlue},
|
||||
{mino.Point{25, 4}, mino.BlockSolidBlue},
|
||||
|
||||
// S
|
||||
{mino.Point{28, 0}, mino.BlockSolidMagenta},
|
||||
{mino.Point{29, 0}, mino.BlockSolidMagenta},
|
||||
{mino.Point{30, 0}, mino.BlockSolidMagenta},
|
||||
{mino.Point{31, 1}, mino.BlockSolidMagenta},
|
||||
{mino.Point{29, 2}, mino.BlockSolidMagenta},
|
||||
{mino.Point{30, 2}, mino.BlockSolidMagenta},
|
||||
{mino.Point{28, 3}, mino.BlockSolidMagenta},
|
||||
{mino.Point{29, 4}, mino.BlockSolidMagenta},
|
||||
{mino.Point{30, 4}, mino.BlockSolidMagenta},
|
||||
{mino.Point{31, 4}, mino.BlockSolidMagenta},
|
||||
h--
|
||||
}
|
||||
|
||||
for _, titleBlock := range titleBlocks {
|
||||
if !m.SetBlock(centerStart+titleBlock.X, titleBlock.Y, titleBlock.Block, false) {
|
||||
log.Fatalf("failed to set title block %s", titleBlock.Point)
|
||||
if h > 0 {
|
||||
for i := 0; i < h; i++ {
|
||||
gameListView.Write(renderVLine)
|
||||
for i := 0; i < w; i++ {
|
||||
gameListView.Write([]byte(" "))
|
||||
}
|
||||
gameListView.Write(renderVLine)
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
gameListView.Write(renderLLCorner)
|
||||
for i := 0; i < w; i++ {
|
||||
gameListView.Write(renderHLine)
|
||||
}
|
||||
gameListView.Write(renderLRCorner)
|
||||
}
|
||||