Add title screen
This commit is contained in:
parent
4d167068e8
commit
893c5b5ad1
12 changed files with 924 additions and 148 deletions
|
@ -1,3 +1,6 @@
|
|||
This document covers command-line options available when launching netris. Some
|
||||
options (such as keybindings) are only available in-game.
|
||||
|
||||
# Client
|
||||
|
||||
```
|
||||
|
|
|
@ -24,16 +24,29 @@ var (
|
|||
inputActive bool
|
||||
showDetails bool
|
||||
|
||||
app *tview.Application
|
||||
grid *tview.Grid
|
||||
inputView *tview.InputField
|
||||
mtx *tview.TextView
|
||||
side *tview.TextView
|
||||
buffer *tview.TextView
|
||||
recent *tview.TextView
|
||||
lowerPages *tview.Pages
|
||||
app *tview.Application
|
||||
titleGrid *tview.Grid
|
||||
titleContainerGrid *tview.Grid
|
||||
playerSettingsForm *tview.Form
|
||||
playerSettingsGrid *tview.Grid
|
||||
playerSettingsContainerGrid *tview.Grid
|
||||
gameSettingsForm *tview.Form
|
||||
gameSettingsGrid *tview.Grid
|
||||
gameSettingsContainerGrid *tview.Grid
|
||||
gameGrid *tview.Grid
|
||||
titleName *tview.TextView
|
||||
titleL *tview.TextView
|
||||
titleR *tview.TextView
|
||||
inputView *tview.InputField
|
||||
mtx *tview.TextView
|
||||
side *tview.TextView
|
||||
buffer *tview.TextView
|
||||
recent *tview.TextView
|
||||
|
||||
draw = make(chan event.DrawObject, game.CommandQueueSize)
|
||||
joinedGame bool
|
||||
|
||||
draw = make(chan event.DrawObject, game.CommandQueueSize)
|
||||
selectMode = make(chan event.GameMode, game.CommandQueueSize)
|
||||
|
||||
renderLock = new(sync.Mutex)
|
||||
renderBuffer bytes.Buffer
|
||||
|
@ -42,8 +55,15 @@ var (
|
|||
|
||||
screenW, screenH int
|
||||
newScreenW, newScreenH int
|
||||
|
||||
nickname = "Anonymous"
|
||||
nicknameDraft string
|
||||
|
||||
inputHeight, mainHeight, newLogLines int
|
||||
)
|
||||
|
||||
const DefaultStatusText = "Press Enter to chat, Z/X to rotate, arrow keys or HJKL to move/drop"
|
||||
|
||||
// TODO: Darken ghost color?
|
||||
var renderBlock = map[mino.Block][]byte{
|
||||
mino.BlockNone: []byte(" "),
|
||||
|
@ -77,6 +97,7 @@ func initGUI() (*tview.Application, error) {
|
|||
app.SetBeforeDrawFunc(handleResize)
|
||||
|
||||
inputView = tview.NewInputField().
|
||||
SetText(DefaultStatusText).
|
||||
SetLabel("").
|
||||
SetFieldWidth(0).
|
||||
SetFieldBackgroundColor(tcell.ColorDefault).
|
||||
|
@ -90,7 +111,7 @@ func initGUI() (*tview.Application, error) {
|
|||
return event
|
||||
})
|
||||
|
||||
grid = tview.NewGrid().
|
||||
gameGrid = tview.NewGrid().
|
||||
SetBorders(false).
|
||||
SetRows(2+(20*blockSize), -1)
|
||||
|
||||
|
@ -125,57 +146,246 @@ func initGUI() (*tview.Application, error) {
|
|||
SetWrap(true).
|
||||
SetWordWrap(true)
|
||||
|
||||
recent.SetDrawFunc(func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) {
|
||||
logMutex.Lock()
|
||||
showLogLines = height
|
||||
if showLogLines < 1 {
|
||||
showLogLines = 1
|
||||
}
|
||||
logMutex.Unlock()
|
||||
|
||||
return recent.GetInnerRect()
|
||||
})
|
||||
|
||||
lowerPages = tview.NewPages()
|
||||
lowerPages = lowerPages.AddPage("input",
|
||||
inputView,
|
||||
true, false)
|
||||
lowerPages = lowerPages.AddPage("recent",
|
||||
recent,
|
||||
true, true)
|
||||
|
||||
grid = grid.SetColumns(1, 4+(10*blockSize), 10, -1).
|
||||
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(lowerPages, 1, 1, 1, 3, 0, 0, true)
|
||||
AddItem(inputView, 1, 1, 1, 3, 0, 0, true).
|
||||
AddItem(recent, 2, 1, 1, 3, 0, 0, true)
|
||||
|
||||
// Set up title screen
|
||||
|
||||
titleVisible = true
|
||||
|
||||
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().
|
||||
SetTextAlign(tview.AlignLeft).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetDynamicColors(true)
|
||||
|
||||
titleL = tview.NewTextView().
|
||||
SetTextAlign(tview.AlignLeft).
|
||||
SetWrap(false).
|
||||
SetWordWrap(false).SetDynamicColors(true)
|
||||
|
||||
titleR = tview.NewTextView().
|
||||
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)
|
||||
|
||||
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(titleName, 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("\nPlayer 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(titleName, 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("\nGame Settings")
|
||||
|
||||
gameSettingsForm = tview.NewForm().SetButtonsAlign(tview.AlignCenter)
|
||||
|
||||
gameSettingsGrid = tview.NewGrid().
|
||||
SetRows(7, 2, -1, 1).
|
||||
SetColumns(-1, 38, -1).
|
||||
AddItem(titleL, 0, 0, 3, 1, 0, 0, false).
|
||||
AddItem(titleName, 0, 1, 1, 1, 0, 0, false).
|
||||
AddItem(titleR, 0, 2, 3, 1, 0, 0, false).
|
||||
AddItem(gameSettingsTitle, 1, 1, 1, 1, 0, 0, true).
|
||||
AddItem(gameSettingsForm, 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)
|
||||
|
||||
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, true).
|
||||
AddItem(tview.NewTextView(), 1, 2, 1, 1, 0, 0, false).
|
||||
AddItem(tview.NewTextView(), 0, 0, 1, 3, 0, 0, false)
|
||||
|
||||
app = app.SetInputCapture(handleKeypress)
|
||||
|
||||
app = app.SetRoot(grid, true)
|
||||
app.SetRoot(titleContainerGrid, true)
|
||||
|
||||
updateTitle()
|
||||
|
||||
go handleDraw()
|
||||
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func resetPlayerSettingsForm() {
|
||||
playerSettingsForm.Clear(true).AddInputField("Name", nickname, 0, nil, func(text string) {
|
||||
nicknameDraft = text
|
||||
}).AddButton("Cancel", func() {
|
||||
titleScreen = 1
|
||||
titleSelectedButton = 0
|
||||
|
||||
app.SetRoot(titleContainerGrid, true)
|
||||
updateTitle()
|
||||
}).AddButton("Save", func() {
|
||||
if nicknameDraft != "" && game.Nickname(nicknameDraft) != nickname {
|
||||
nickname = game.Nickname(nicknameDraft)
|
||||
|
||||
if activeGame != nil {
|
||||
activeGame.Event <- &event.NicknameEvent{Nickname: nickname}
|
||||
}
|
||||
}
|
||||
|
||||
titleScreen = 1
|
||||
titleSelectedButton = 0
|
||||
|
||||
app.SetRoot(titleContainerGrid, true)
|
||||
updateTitle()
|
||||
})
|
||||
}
|
||||
|
||||
func resetGameSettingsForm() {
|
||||
gameSettingsForm.Clear(true).
|
||||
AddInputField("Custom", "", 0, nil, nil).
|
||||
AddInputField("Keybindings", "", 0, nil, nil).
|
||||
AddInputField("Are", "", 0, nil, nil).
|
||||
AddInputField("Coming", "", 0, nil, nil).
|
||||
AddInputField("Soon", "", 0, nil, nil).
|
||||
AddButton("Cancel", func() {
|
||||
titleScreen = 1
|
||||
titleSelectedButton = 0
|
||||
|
||||
app.SetRoot(titleContainerGrid, true)
|
||||
updateTitle()
|
||||
}).AddButton("Save", func() {
|
||||
if nicknameDraft != "" && game.Nickname(nicknameDraft) != nickname {
|
||||
nickname = game.Nickname(nicknameDraft)
|
||||
|
||||
if activeGame != nil {
|
||||
activeGame.Event <- &event.NicknameEvent{Nickname: nickname}
|
||||
}
|
||||
}
|
||||
|
||||
titleScreen = 1
|
||||
titleSelectedButton = 0
|
||||
|
||||
app.SetRoot(titleContainerGrid, true)
|
||||
updateTitle()
|
||||
})
|
||||
}
|
||||
|
||||
func handleResize(screen tcell.Screen) bool {
|
||||
newScreenW, newScreenH = screen.Size()
|
||||
if newScreenW != screenW || newScreenH != screenH {
|
||||
screenW, screenH = newScreenW, newScreenH
|
||||
|
||||
// TODO Obey initial set blocksize or auto
|
||||
|
||||
if screenW >= 80 && screenH >= 44 {
|
||||
blockSize = 2
|
||||
} else {
|
||||
blockSize = 1
|
||||
if !fixedBlockSize {
|
||||
if screenW >= 80 && screenH >= 44 {
|
||||
blockSize = 2
|
||||
} else {
|
||||
blockSize = 1
|
||||
}
|
||||
}
|
||||
|
||||
multiplayerMatrixSize = (screenW - ((10 * blockSize) + 16)) / ((10 * blockSize) + 4)
|
||||
|
||||
grid.SetRows(2+(20*blockSize), -1).SetColumns(1, 4+(10*blockSize), 10, -1)
|
||||
inputHeight = 1
|
||||
mainHeight = (20 * blockSize) + 2
|
||||
if screenH > mainHeight+5 {
|
||||
mainHeight += 2
|
||||
inputHeight++
|
||||
} else if screenH > mainHeight+2 {
|
||||
mainHeight++
|
||||
}
|
||||
|
||||
newLogLines = (screenH - mainHeight) - inputHeight
|
||||
if newLogLines > 0 {
|
||||
showLogLines = newLogLines
|
||||
} else {
|
||||
showLogLines = 1
|
||||
}
|
||||
|
||||
gameGrid.SetRows(mainHeight, inputHeight, -1).SetColumns(1, 4+(10*blockSize), 10, -1)
|
||||
|
||||
logMutex.Lock()
|
||||
renderLogMessages = true
|
||||
|
@ -255,12 +465,17 @@ func setInputStatus(active bool) {
|
|||
|
||||
inputActive = active
|
||||
|
||||
if active {
|
||||
if inputActive {
|
||||
inputView.SetText("")
|
||||
lowerPages = lowerPages.SwitchToPage("input")
|
||||
inputView.SetLabel("> ")
|
||||
app.SetFocus(inputView)
|
||||
} else {
|
||||
lowerPages = lowerPages.SwitchToPage("recent")
|
||||
inputView.SetText(DefaultStatusText)
|
||||
inputView.SetLabel("")
|
||||
app.SetFocus(nil)
|
||||
}
|
||||
|
||||
app.Draw()
|
||||
}
|
||||
|
||||
func setShowDetails(active bool) {
|
||||
|
@ -395,20 +610,23 @@ func renderMatrix(m *mino.Matrix) []byte {
|
|||
|
||||
m.DrawPiecesL()
|
||||
|
||||
// Draw preview matrix at block size 2 max
|
||||
bs := blockSize
|
||||
if m.Preview {
|
||||
if m.Type == mino.MatrixPreview {
|
||||
// Draw preview matrix at block size 2 max
|
||||
|
||||
if bs > 2 {
|
||||
bs = 2
|
||||
}
|
||||
if bs > 1 {
|
||||
renderBuffer.WriteRune('\n')
|
||||
}
|
||||
} else if m.Type == mino.MatrixCustom {
|
||||
bs = 1
|
||||
}
|
||||
|
||||
for y := m.H - 1; y >= 0; y-- {
|
||||
for j := 0; j < bs; j++ {
|
||||
if !m.Preview {
|
||||
if m.Type == mino.MatrixStandard {
|
||||
renderBuffer.Write(renderVLine)
|
||||
} else {
|
||||
iPieceNext := m.Bag != nil && m.Bag.Next().String() == mino.TetrominoI
|
||||
|
@ -425,15 +643,17 @@ func renderMatrix(m *mino.Matrix) []byte {
|
|||
}
|
||||
}
|
||||
|
||||
if !m.Preview {
|
||||
if m.Type == mino.MatrixStandard {
|
||||
renderBuffer.Write(renderVLine)
|
||||
}
|
||||
|
||||
renderBuffer.WriteRune('\n')
|
||||
if y != 0 || m.Type == mino.MatrixStandard {
|
||||
renderBuffer.WriteRune('\n')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if m.Preview {
|
||||
if m.Type != mino.MatrixStandard {
|
||||
return renderBuffer.Bytes()
|
||||
}
|
||||
|
||||
|
@ -503,7 +723,7 @@ func renderMatrixes(mx []*mino.Matrix) []byte {
|
|||
renderBuffer.WriteString(div)
|
||||
}
|
||||
|
||||
if !m.Preview {
|
||||
if m.Type == mino.MatrixStandard {
|
||||
renderBuffer.Write(renderVLine)
|
||||
}
|
||||
|
||||
|
@ -513,7 +733,7 @@ func renderMatrixes(mx []*mino.Matrix) []byte {
|
|||
}
|
||||
}
|
||||
|
||||
if !m.Preview {
|
||||
if m.Type == mino.MatrixStandard {
|
||||
renderBuffer.Write(renderVLine)
|
||||
}
|
||||
}
|
||||
|
@ -553,7 +773,7 @@ func renderMatrixes(mx []*mino.Matrix) []byte {
|
|||
|
||||
func logMessage(message string) {
|
||||
logMutex.Lock()
|
||||
logMessages = append(logMessages, message)
|
||||
logMessages = append(logMessages, time.Now().Format(LogTimeFormat)+" "+message)
|
||||
renderLogMessages = true
|
||||
logMutex.Unlock()
|
||||
}
|
||||
|
|
|
@ -9,6 +9,103 @@ func handleKeypress(ev *tcell.EventKey) *tcell.EventKey {
|
|||
k := ev.Key()
|
||||
r := ev.Rune()
|
||||
|
||||
if titleVisible {
|
||||
// TODO: During keybind change, record key, rune and modifier
|
||||
if titleScreen > 1 {
|
||||
switch k {
|
||||
case tcell.KeyEscape:
|
||||
titleScreen = 1
|
||||
titleSelectedButton = 0
|
||||
|
||||
app.SetRoot(titleContainerGrid, true)
|
||||
updateTitle()
|
||||
return nil
|
||||
}
|
||||
|
||||
return ev
|
||||
}
|
||||
|
||||
switch k {
|
||||
case tcell.KeyEnter:
|
||||
if titleScreen == 1 {
|
||||
switch titleSelectedButton {
|
||||
case 0:
|
||||
resetPlayerSettingsForm()
|
||||
|
||||
titleScreen = 2
|
||||
titleSelectedButton = 0
|
||||
|
||||
app.SetRoot(playerSettingsContainerGrid, true)
|
||||
app.SetFocus(playerSettingsForm)
|
||||
app.Draw()
|
||||
case 1:
|
||||
resetGameSettingsForm()
|
||||
|
||||
titleScreen = 3
|
||||
titleSelectedButton = 0
|
||||
|
||||
app.SetRoot(gameSettingsContainerGrid, true)
|
||||
app.SetFocus(gameSettingsForm)
|
||||
app.Draw()
|
||||
case 2:
|
||||
titleScreen = 0
|
||||
|
||||
updateTitle()
|
||||
}
|
||||
} else {
|
||||
if joinedGame {
|
||||
switch titleSelectedButton {
|
||||
case 0:
|
||||
setTitleVisible(false)
|
||||
case 1:
|
||||
titleScreen = 1
|
||||
titleSelectedButton = 0
|
||||
|
||||
updateTitle()
|
||||
case 2:
|
||||
done <- true
|
||||
}
|
||||
} else {
|
||||
switch titleSelectedButton {
|
||||
case 0:
|
||||
selectMode <- event.ModePlayOnline
|
||||
case 1:
|
||||
selectMode <- event.ModePractice
|
||||
case 2:
|
||||
titleScreen = 1
|
||||
titleSelectedButton = 0
|
||||
|
||||
updateTitle()
|
||||
}
|
||||
}
|
||||
}
|
||||
case tcell.KeyUp, tcell.KeyBacktab:
|
||||
previousTitleButton()
|
||||
case tcell.KeyDown, tcell.KeyTab:
|
||||
nextTitleButton()
|
||||
case tcell.KeyEscape:
|
||||
if titleScreen == 1 {
|
||||
titleScreen = 0
|
||||
titleSelectedButton = 0
|
||||
} else if joinedGame {
|
||||
setTitleVisible(false)
|
||||
} else {
|
||||
done <- true
|
||||
}
|
||||
default:
|
||||
switch r {
|
||||
case 'k', 'K':
|
||||
previousTitleButton()
|
||||
case 'j', 'J':
|
||||
nextTitleButton()
|
||||
}
|
||||
}
|
||||
|
||||
updateTitle()
|
||||
|
||||
return ev
|
||||
}
|
||||
|
||||
if inputActive {
|
||||
if k == tcell.KeyEnter {
|
||||
msg := inputView.GetText()
|
||||
|
@ -74,7 +171,7 @@ func handleKeypress(ev *tcell.EventKey) *tcell.EventKey {
|
|||
case tcell.KeyTab:
|
||||
setShowDetails(!showDetails)
|
||||
case tcell.KeyEscape:
|
||||
done <- true
|
||||
setTitleVisible(true)
|
||||
}
|
||||
|
||||
switch r {
|
||||
|
|
338
cmd/netris/gui_title.go
Normal file
338
cmd/netris/gui_title.go
Normal file
|
@ -0,0 +1,338 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"git.sr.ht/~tslocum/netris/pkg/event"
|
||||
"git.sr.ht/~tslocum/netris/pkg/game"
|
||||
"git.sr.ht/~tslocum/netris/pkg/mino"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
var (
|
||||
playerForm *tview.Form
|
||||
|
||||
titleVisible bool
|
||||
titleScreen int
|
||||
titleSelectedButton int
|
||||
drawTitle = make(chan struct{}, game.CommandQueueSize)
|
||||
|
||||
buttonA *tview.Button
|
||||
buttonB *tview.Button
|
||||
buttonC *tview.Button
|
||||
|
||||
buttonLabelA *tview.TextView
|
||||
buttonLabelB *tview.TextView
|
||||
buttonLabelC *tview.TextView
|
||||
|
||||
titleMatrixL = newTitleMatrixSide()
|
||||
titleMatrix = newTitleMatrixName()
|
||||
titleMatrixR = newTitleMatrixSide()
|
||||
titlePiecesL []*mino.Piece
|
||||
titlePiecesR []*mino.Piece
|
||||
)
|
||||
|
||||
func previousTitleButton() {
|
||||
if titleSelectedButton == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
titleSelectedButton--
|
||||
}
|
||||
|
||||
func nextTitleButton() {
|
||||
if titleSelectedButton == 2 {
|
||||
return
|
||||
}
|
||||
|
||||
titleSelectedButton++
|
||||
}
|
||||
|
||||
func setTitleVisible(visible bool) {
|
||||
if titleVisible == visible {
|
||||
return
|
||||
}
|
||||
|
||||
titleVisible = visible
|
||||
|
||||
if !titleVisible {
|
||||
app.SetRoot(gameGrid, true)
|
||||
|
||||
app.SetFocus(nil)
|
||||
} else {
|
||||
titleScreen = 0
|
||||
titleSelectedButton = 0
|
||||
|
||||
drawTitle <- struct{}{}
|
||||
|
||||
app.SetRoot(titleContainerGrid, true)
|
||||
|
||||
updateTitle()
|
||||
}
|
||||
}
|
||||
|
||||
func updateTitle() {
|
||||
if titleScreen == 1 {
|
||||
buttonA.SetLabel("Player Settings")
|
||||
buttonLabelA.SetText("\nChange name")
|
||||
|
||||
buttonB.SetLabel("Game Settings")
|
||||
buttonLabelB.SetText("\nChange keybindings")
|
||||
|
||||
buttonC.SetLabel("Return")
|
||||
buttonLabelC.SetText("\nReturn to the last screen")
|
||||
} else {
|
||||
if joinedGame {
|
||||
buttonA.SetLabel("Resume")
|
||||
buttonLabelA.SetText("\nResume game in progress")
|
||||
|
||||
buttonB.SetLabel("Settings")
|
||||
buttonLabelB.SetText("\nChange player name, keybindings, etc")
|
||||
|
||||
buttonC.SetLabel("Quit")
|
||||
buttonLabelC.SetText("\nQuit game")
|
||||
} else {
|
||||
buttonA.SetLabel("Play")
|
||||
buttonLabelA.SetText("\nPlay with others online")
|
||||
|
||||
buttonB.SetLabel("Practice")
|
||||
buttonLabelB.SetText("\nPlay by yourself")
|
||||
|
||||
buttonC.SetLabel("Settings")
|
||||
buttonLabelC.SetText("\nPlayer name, keybindings, etc.")
|
||||
}
|
||||
}
|
||||
|
||||
if titleScreen > 1 {
|
||||
return
|
||||
}
|
||||
|
||||
switch titleSelectedButton {
|
||||
case 1:
|
||||
app.SetFocus(buttonB)
|
||||
case 2:
|
||||
app.SetFocus(buttonC)
|
||||
default:
|
||||
app.SetFocus(buttonA)
|
||||
}
|
||||
}
|
||||
|
||||
func handleTitle() {
|
||||
var t *time.Ticker
|
||||
for {
|
||||
if t == nil {
|
||||
t = time.NewTicker(850 * time.Millisecond)
|
||||
} else {
|
||||
select {
|
||||
case <-t.C:
|
||||
case <-drawTitle:
|
||||
if t != nil {
|
||||
t.Stop()
|
||||
}
|
||||
|
||||
t = time.NewTicker(850 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
if !titleVisible {
|
||||
continue
|
||||
}
|
||||
|
||||
titleMatrixL.ClearOverlay()
|
||||
|
||||
for _, p := range titlePiecesL {
|
||||
p.Y -= 1
|
||||
if p.Y < -3 {
|
||||
p.Y = titleMatrixL.H + 2
|
||||
}
|
||||
if rand.Intn(4) == 0 {
|
||||
p.Mino = p.Rotate(1, 0)
|
||||
p.ApplyRotation(1, 0)
|
||||
}
|
||||
|
||||
for _, m := range p.Mino {
|
||||
titleMatrixL.SetBlock(p.X+m.X, p.Y+m.Y, p.Solid, true)
|
||||
}
|
||||
}
|
||||
|
||||
titleMatrixR.ClearOverlay()
|
||||
|
||||
for _, p := range titlePiecesR {
|
||||
p.Y -= 1
|
||||
if p.Y < -3 {
|
||||
p.Y = titleMatrixL.H + 2
|
||||
}
|
||||
if rand.Intn(4) == 0 {
|
||||
p.Mino = p.Rotate(1, 0)
|
||||
p.ApplyRotation(1, 0)
|
||||
}
|
||||
|
||||
for _, m := range p.Mino {
|
||||
if titleMatrixR.Block(p.X+m.X, p.Y+m.Y) != mino.BlockNone {
|
||||
continue
|
||||
}
|
||||
|
||||
titleMatrixR.SetBlock(p.X+m.X, p.Y+m.Y, p.Solid, true)
|
||||
}
|
||||
}
|
||||
|
||||
app.QueueUpdateDraw(renderTitle)
|
||||
}
|
||||
}
|
||||
|
||||
func renderTitle() {
|
||||
var newBlock mino.Block
|
||||
for i, b := range titleMatrix.M {
|
||||
switch b {
|
||||
case mino.BlockSolidRed:
|
||||
newBlock = mino.BlockSolidMagenta
|
||||
case mino.BlockSolidYellow:
|
||||
newBlock = mino.BlockSolidRed
|
||||
case mino.BlockSolidGreen:
|
||||
newBlock = mino.BlockSolidYellow
|
||||
case mino.BlockSolidCyan:
|
||||
newBlock = mino.BlockSolidGreen
|
||||
case mino.BlockSolidBlue:
|
||||
newBlock = mino.BlockSolidCyan
|
||||
case mino.BlockSolidMagenta:
|
||||
newBlock = mino.BlockSolidBlue
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
titleMatrix.M[i] = newBlock
|
||||
}
|
||||
|
||||
titleName.Clear()
|
||||
titleName.Write(renderMatrix(titleMatrix))
|
||||
|
||||
titleL.Clear()
|
||||
titleL.Write(renderMatrix(titleMatrixL))
|
||||
|
||||
titleR.Clear()
|
||||
titleR.Write(renderMatrix(titleMatrixR))
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
baseStart := 1
|
||||
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, baseStart+titleBlock.Y, titleBlock.Block, false) {
|
||||
log.Fatalf("failed to set title block %s", titleBlock.Point)
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
|
@ -15,6 +15,8 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"git.sr.ht/~tslocum/netris/pkg/event"
|
||||
|
||||
"git.sr.ht/~tslocum/netris/pkg/game"
|
||||
"git.sr.ht/~tslocum/netris/pkg/mino"
|
||||
"github.com/mattn/go-isatty"
|
||||
|
@ -28,10 +30,12 @@ var (
|
|||
|
||||
connectAddress string
|
||||
debugAddress string
|
||||
nickname string
|
||||
startMatrix string
|
||||
|
||||
blockSize = 0
|
||||
nicknameFlag string
|
||||
|
||||
blockSize = 0
|
||||
fixedBlockSize bool
|
||||
|
||||
logDebug bool
|
||||
logVerbose bool
|
||||
|
@ -39,7 +43,7 @@ var (
|
|||
logMessages []string
|
||||
renderLogMessages bool
|
||||
logMutex = new(sync.Mutex)
|
||||
showLogLines = 7 // TODO Set in resize func?
|
||||
showLogLines = 7
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -66,7 +70,7 @@ func main() {
|
|||
}()
|
||||
|
||||
flag.IntVar(&blockSize, "scale", 0, "UI scale")
|
||||
flag.StringVar(&nickname, "nick", "Anonymous", "nickname")
|
||||
flag.StringVar(&nicknameFlag, "nick", "", "nickname")
|
||||
flag.StringVar(&startMatrix, "matrix", "", "pre-fill matrix with pieces")
|
||||
flag.StringVar(&connectAddress, "connect", "", "connect to server address or socket path")
|
||||
flag.StringVar(&debugAddress, "debug-address", "", "address to serve debug info")
|
||||
|
@ -79,8 +83,12 @@ func main() {
|
|||
log.Fatal("failed to start netris: non-interactive terminals are not supported")
|
||||
}
|
||||
|
||||
if blockSize > 3 {
|
||||
blockSize = 3
|
||||
if blockSize > 0 {
|
||||
fixedBlockSize = true
|
||||
|
||||
if blockSize > 3 {
|
||||
blockSize = 3
|
||||
}
|
||||
}
|
||||
|
||||
logLevel := game.LogStandard
|
||||
|
@ -90,6 +98,10 @@ func main() {
|
|||
logLevel = game.LogDebug
|
||||
}
|
||||
|
||||
if game.Nickname(nicknameFlag) != "" {
|
||||
nickname = game.Nickname(nicknameFlag)
|
||||
}
|
||||
|
||||
if debugAddress != "" {
|
||||
go func() {
|
||||
log.Fatal(http.ListenAndServe(debugAddress, nil))
|
||||
|
@ -109,13 +121,10 @@ func main() {
|
|||
done <- true
|
||||
}()
|
||||
|
||||
inputActive = true
|
||||
setInputStatus(false)
|
||||
|
||||
logger := make(chan string, game.LogQueueSize)
|
||||
go func() {
|
||||
for msg := range logger {
|
||||
logMessage(time.Now().Format(LogTimeFormat) + " " + msg)
|
||||
logMessage(msg)
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -129,79 +138,109 @@ func main() {
|
|||
done <- true
|
||||
}()
|
||||
|
||||
// Connect to a game
|
||||
// Connect automatically when an address or path is supplied
|
||||
if connectAddress != "" {
|
||||
s := game.Connect(connectAddress)
|
||||
selectMode <- event.ModePlayOnline
|
||||
}
|
||||
|
||||
activeGame, err = s.JoinGame(nickname, 0, logger, draw)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
activeGame.LogLevel = logLevel
|
||||
var server *game.Server
|
||||
|
||||
go func(server *game.Server) {
|
||||
<-done
|
||||
if server != nil {
|
||||
server.StopListening()
|
||||
}
|
||||
|
||||
closeGUI()
|
||||
return
|
||||
}
|
||||
|
||||
// Host a game
|
||||
server := game.NewServer(nil)
|
||||
os.Exit(0)
|
||||
}(server)
|
||||
|
||||
server.Logger = make(chan string, game.LogQueueSize)
|
||||
if logDebug || logVerbose {
|
||||
go func() {
|
||||
for msg := range server.Logger {
|
||||
logMessage(time.Now().Format(LogTimeFormat) + " Local server: " + msg)
|
||||
for {
|
||||
mode := <-selectMode
|
||||
switch mode {
|
||||
case event.ModePlayOnline:
|
||||
joinedGame = true
|
||||
setTitleVisible(false)
|
||||
|
||||
if connectAddress == "" {
|
||||
connectAddress = game.DefaultServer
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
go func() {
|
||||
for range server.Logger {
|
||||
|
||||
connectNetwork, _ := game.NetworkAndAddress(connectAddress)
|
||||
|
||||
if connectNetwork != "unix" {
|
||||
logMessage(fmt.Sprintf("* Connecting to %s...", connectAddress))
|
||||
draw <- event.DrawMessages
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
localListenAddress := fmt.Sprintf("localhost:%d", game.DefaultPort)
|
||||
s := game.Connect(connectAddress)
|
||||
|
||||
go server.Listen(localListenAddress)
|
||||
|
||||
localServerConn := game.Connect(localListenAddress)
|
||||
|
||||
activeGame, err = localServerConn.JoinGame(nickname, -1, logger, draw)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
activeGame.LogLevel = logLevel
|
||||
|
||||
if startMatrix != "" {
|
||||
activeGame.Players[activeGame.LocalPlayer].Matrix.Lock()
|
||||
startMatrixSplit := strings.Split(startMatrix, ",")
|
||||
startMatrix = ""
|
||||
var (
|
||||
token int
|
||||
x int
|
||||
err error
|
||||
)
|
||||
for i := range startMatrixSplit {
|
||||
token, err = strconv.Atoi(startMatrixSplit[i])
|
||||
activeGame, err = s.JoinGame(nickname, 0, logger, draw)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to parse initial matrix on token #%d", i))
|
||||
log.Fatalf("failed to connect to %s: %s", connectAddress, err)
|
||||
}
|
||||
if i%2 == 1 {
|
||||
activeGame.Players[activeGame.LocalPlayer].Matrix.SetBlock(x, token, mino.BlockGarbage, false)
|
||||
|
||||
if activeGame == nil {
|
||||
log.Fatal("failed to connect to server")
|
||||
}
|
||||
|
||||
activeGame.LogLevel = logLevel
|
||||
case event.ModePractice:
|
||||
joinedGame = true
|
||||
setTitleVisible(false)
|
||||
|
||||
server = game.NewServer(nil)
|
||||
|
||||
server.Logger = make(chan string, game.LogQueueSize)
|
||||
if logDebug || logVerbose {
|
||||
go func() {
|
||||
for msg := range server.Logger {
|
||||
logMessage("Local server: " + msg)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
x = token
|
||||
go func() {
|
||||
for range server.Logger {
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
localListenAddress := fmt.Sprintf("localhost:%d", game.DefaultPort)
|
||||
|
||||
go server.Listen(localListenAddress)
|
||||
|
||||
localServerConn := game.Connect(localListenAddress)
|
||||
|
||||
activeGame, err = localServerConn.JoinGame(nickname, -1, logger, draw)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
activeGame.LogLevel = logLevel
|
||||
|
||||
if startMatrix != "" {
|
||||
activeGame.Players[activeGame.LocalPlayer].Matrix.Lock()
|
||||
startMatrixSplit := strings.Split(startMatrix, ",")
|
||||
startMatrix = ""
|
||||
var (
|
||||
token int
|
||||
x int
|
||||
err error
|
||||
)
|
||||
for i := range startMatrixSplit {
|
||||
token, err = strconv.Atoi(startMatrixSplit[i])
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to parse initial matrix on token #%d", i))
|
||||
}
|
||||
if i%2 == 1 {
|
||||
activeGame.Players[activeGame.LocalPlayer].Matrix.SetBlock(x, token, mino.BlockGarbage, false)
|
||||
} else {
|
||||
x = token
|
||||
}
|
||||
}
|
||||
activeGame.Players[activeGame.LocalPlayer].Matrix.Unlock()
|
||||
}
|
||||
}
|
||||
activeGame.Players[activeGame.LocalPlayer].Matrix.Unlock()
|
||||
}
|
||||
|
||||
<-done
|
||||
|
||||
server.StopListening()
|
||||
|
||||
closeGUI()
|
||||
}
|
||||
|
|
|
@ -10,6 +10,11 @@ type MessageEvent struct {
|
|||
Message string
|
||||
}
|
||||
|
||||
type NicknameEvent struct {
|
||||
Event
|
||||
Nickname string
|
||||
}
|
||||
|
||||
type GameOverEvent struct {
|
||||
Event
|
||||
}
|
||||
|
|
9
pkg/event/game.go
Normal file
9
pkg/event/game.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package event
|
||||
|
||||
type GameMode int
|
||||
|
||||
const (
|
||||
ModeUnknown = iota
|
||||
ModePractice
|
||||
ModePlayOnline
|
||||
)
|
|
@ -20,7 +20,10 @@ const (
|
|||
LogVerbose
|
||||
)
|
||||
|
||||
const DefaultPort = 1984
|
||||
const (
|
||||
DefaultPort = 1984
|
||||
DefaultServer = "netris.rocketnine.space"
|
||||
)
|
||||
|
||||
type Game struct {
|
||||
Rank int
|
||||
|
@ -127,10 +130,10 @@ func (g *Game) AddPlayerL(p *Player) {
|
|||
|
||||
g.Players[p.Player] = p
|
||||
|
||||
p.Preview = mino.NewMatrix(g.Rank, g.Rank-1, 0, 1, g.Event, g.draw, true)
|
||||
p.Preview = mino.NewMatrix(g.Rank, g.Rank-1, 0, 1, g.Event, g.draw, mino.MatrixPreview)
|
||||
p.Preview.PlayerName = p.Name
|
||||
|
||||
p.Matrix = mino.NewMatrix(10, 20, 20, 1, g.Event, g.draw, false)
|
||||
p.Matrix = mino.NewMatrix(10, 20, 20, 1, g.Event, g.draw, mino.MatrixStandard)
|
||||
p.Matrix.PlayerName = p.Name
|
||||
|
||||
if g.Started {
|
||||
|
@ -415,7 +418,7 @@ func (g *Game) handleDistributeMatrixes() {
|
|||
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(5 * time.Second)
|
||||
time.Sleep(7 * time.Second)
|
||||
if g.Terminated {
|
||||
return
|
||||
} else if len(g.Players) > 1 {
|
||||
|
@ -429,6 +432,7 @@ func (g *Game) handleDistributeMatrixes() {
|
|||
|
||||
matrixes = make(map[int]*mino.Matrix)
|
||||
for playerID, player := range g.Players {
|
||||
player.Matrix.PlayerName = player.Name
|
||||
player.Matrix.GarbageSent = player.totalGarbageSent
|
||||
player.Matrix.GarbageReceived = player.totalGarbageReceived
|
||||
|
||||
|
@ -466,6 +470,23 @@ func (g *Game) HandleReadCommands(in chan GameCommandInterface) {
|
|||
}
|
||||
g.Log(LogStandard, prefix+p.Message)
|
||||
}
|
||||
case CommandNickname:
|
||||
if p, ok := e.(*GameCommandNickname); ok {
|
||||
if player, ok := g.Players[p.Player]; ok {
|
||||
newNick := Nickname(p.Nickname)
|
||||
if newNick != "" && newNick != player.Name {
|
||||
oldNick := player.Name
|
||||
|
||||
player.Name = newNick
|
||||
|
||||
if p.Player == g.LocalPlayer {
|
||||
g.Players[g.LocalPlayer].Matrix.PlayerName = newNick
|
||||
}
|
||||
|
||||
g.Logf(LogStandard, "* %s is now known as %s", oldNick, newNick)
|
||||
}
|
||||
}
|
||||
}
|
||||
case CommandJoinGame:
|
||||
g.ResetL()
|
||||
case CommandQuitGame:
|
||||
|
@ -488,6 +509,9 @@ func (g *Game) HandleReadCommands(in chan GameCommandInterface) {
|
|||
if p, ok := e.(*GameCommandUpdateMatrix); ok {
|
||||
for player, m := range p.Matrixes {
|
||||
if player == g.LocalPlayer {
|
||||
g.Players[player].Matrix.GarbageSent = m.GarbageSent
|
||||
g.Players[player].Matrix.GarbageReceived = m.GarbageReceived
|
||||
|
||||
continue
|
||||
} else if _, ok := g.Players[player]; !ok {
|
||||
continue
|
||||
|
@ -557,6 +581,8 @@ func (g *Game) handle() {
|
|||
g.Players[g.LocalPlayer].Matrix.SetGameOver()
|
||||
|
||||
g.out(&GameCommandGameOver{})
|
||||
} else if ev, ok := e.(*event.NicknameEvent); ok {
|
||||
g.out(&GameCommandNickname{Nickname: ev.Nickname})
|
||||
} else if ev, ok := e.(*event.SendGarbageEvent); ok {
|
||||
g.out(&GameCommandSendGarbage{Lines: ev.Lines})
|
||||
} else if ev, ok := e.(*event.ScoreEvent); ok {
|
||||
|
|
|
@ -223,6 +223,17 @@ func (gc GameCommandJoinGame) Command() Command {
|
|||
return CommandJoinGame
|
||||
}
|
||||
|
||||
type GameCommandNickname struct {
|
||||
GameCommand
|
||||
|
||||
Player int
|
||||
Nickname string
|
||||
}
|
||||
|
||||
func (gc GameCommandNickname) Command() Command {
|
||||
return CommandNickname
|
||||
}
|
||||
|
||||
type GameCommandQuitGame struct {
|
||||
GameCommand
|
||||
Player int
|
||||
|
|
|
@ -131,18 +131,6 @@ func (s *Server) FindGame(p *Player, gameID int) *Game {
|
|||
g.Lock()
|
||||
|
||||
g.AddPlayerL(p)
|
||||
if len(g.Players) > 1 {
|
||||
var players []string
|
||||
for playerID, player := range g.Players {
|
||||
if playerID == p.Player {
|
||||
continue
|
||||
}
|
||||
|
||||
players = append(players, player.Name)
|
||||
}
|
||||
|
||||
p.Write(&GameCommandMessage{Message: "Joined game - Players: " + strings.Join(players, " ")})
|
||||
}
|
||||
|
||||
if gameID == -1 {
|
||||
go g.Start(0)
|
||||
|
@ -201,10 +189,6 @@ func (s *Server) handleJoinGame(pl *Player) {
|
|||
if p, ok := e.(*GameCommandJoinGame); ok {
|
||||
pl.Name = Nickname(p.Name)
|
||||
|
||||
s.Log("JOINING GAME", p)
|
||||
|
||||
pl.Write(&GameCommandMessage{Message: "Welcome to netris"})
|
||||
|
||||
g := s.FindGame(pl, p.GameID)
|
||||
|
||||
s.Log("New player added to game", *pl, p.GameID)
|
||||
|
@ -234,7 +218,6 @@ func (s *Server) initiateAutoStart(g *Game) {
|
|||
}
|
||||
|
||||
func (s *Server) handleGameCommands(pl *Player, g *Game) {
|
||||
s.Log("waiting first msg handle game commands")
|
||||
for e := range pl.In {
|
||||
c := e.Command()
|
||||
if (c != CommandPing && c != CommandPong && c != CommandUpdateMatrix) || g.LogLevel >= LogVerbose {
|
||||
|
@ -255,6 +238,19 @@ func (s *Server) handleGameCommands(pl *Player, g *Game) {
|
|||
}
|
||||
}
|
||||
}
|
||||
case CommandNickname:
|
||||
if p, ok := e.(*GameCommandNickname); ok {
|
||||
if player, ok := g.Players[p.SourcePlayer]; ok {
|
||||
newNick := Nickname(p.Nickname)
|
||||
if newNick != "" && newNick != player.Name {
|
||||
oldNick := player.Name
|
||||
player.Name = newNick
|
||||
|
||||
g.Logf(LogStandard, "* %s is now known as %s", oldNick, newNick)
|
||||
g.WriteAllL(&GameCommandNickname{Player: p.SourcePlayer, Nickname: newNick})
|
||||
}
|
||||
}
|
||||
}
|
||||
case CommandUpdateMatrix:
|
||||
if p, ok := e.(*GameCommandUpdateMatrix); ok {
|
||||
for _, m := range p.Matrixes {
|
||||
|
@ -310,7 +306,7 @@ func (s *Server) Listen(address string) {
|
|||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Fatal("Accept error: ", err)
|
||||
continue
|
||||
}
|
||||
|
||||
s.NewPlayers <- &IncomingPlayer{Name: "Anonymous", Conn: NewServerConn(conn, nil)}
|
||||
|
|
|
@ -68,7 +68,7 @@ func Connect(address string) *ServerConn {
|
|||
conn, err = net.DialTimeout(network, address, ConnTimeout)
|
||||
if err != nil {
|
||||
if tries > 25 {
|
||||
log.Fatal("Listen error: ", err)
|
||||
log.Fatalf("failed to connect to %s: %s", address, err)
|
||||
} else {
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
|
||||
|
@ -171,6 +171,14 @@ func (s *ServerConn) handleRead() {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
gc = &gameCommand
|
||||
} else if msg.Command == CommandNickname {
|
||||
var gameCommand GameCommandNickname
|
||||
err := json.Unmarshal(msg.Data, &gameCommand)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
gc = &gameCommand
|
||||
} else if msg.Command == CommandJoinGame {
|
||||
var gameCommand GameCommandJoinGame
|
||||
|
@ -291,6 +299,11 @@ func (s *ServerConn) handleWrite() {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else if p, ok := e.(*GameCommandNickname); ok {
|
||||
msg.Data, err = json.Marshal(p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|