Improve playfield display and scaling

This commit is contained in:
Trevor Slocum 2019-10-28 09:57:13 -07:00
parent 4aa44246f2
commit e6a3c0527c
13 changed files with 423 additions and 359 deletions

View file

@ -1,5 +1,6 @@
0.1.1:
- Add game browser and support custom game creation
- Improve playfield display and scaling
- Add website and version to title screen
- Kick inactive players

View file

@ -60,7 +60,14 @@ func main() {
sshServer := &ssh.SSHServer{ListenAddress: listenAddressSSH, NetrisBinary: netrisBinary, NetrisAddress: netrisAddress}
server := game.NewServer([]game.ServerInterface{sshServer})
logLevel := game.LogStandard
if logVerbose {
logLevel = game.LogVerbose
} else if logDebug {
logLevel = game.LogDebug
}
server := game.NewServer([]game.ServerInterface{sshServer}, logLevel)
logger := make(chan string, game.LogQueueSize)
go func() {
@ -91,20 +98,4 @@ func main() {
<-done
server.StopListening()
/*
i, err := strconv.Atoi(flag.Arg(0))
if err != nil {
panic(err)
}
minos, err := mino.Generate(i)
if err != nil {
panic(err)
}
for _, m := range minos {
log.Println(m.Render())
log.Println()
log.Println()
}*/
}

View file

@ -40,7 +40,7 @@ var (
renderBuffer bytes.Buffer
multiplayerMatrixSize int
extraScreenPadding int
screenPadding int
screenW, screenH int
newScreenW, newScreenH int
@ -48,7 +48,7 @@ var (
nickname = "Anonymous"
nicknameDraft string
inputHeight, mainHeight, newLogLines int
inputHeight, mainHeight, previewWidth, newLogLines int
profileCPU *os.File
@ -67,26 +67,6 @@ var (
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(" "),
mino.BlockGhostBlue: []byte("[#2864ff]▓[#ffffff]"), // 1a53ff
mino.BlockSolidBlue: []byte("[#2864ff]█[#ffffff]"),
mino.BlockGhostCyan: []byte("[#00eeee]▓[#ffffff]"),
mino.BlockSolidCyan: []byte("[#00eeee]█[#ffffff]"),
mino.BlockGhostRed: []byte("[#ee0000]▓[#ffffff]"),
mino.BlockSolidRed: []byte("[#ee0000]█[#ffffff]"),
mino.BlockGhostYellow: []byte("[#dddd00]▓[#ffffff]"),
mino.BlockSolidYellow: []byte("[#dddd00]█[#ffffff]"),
mino.BlockGhostMagenta: []byte("[#c000cc]▓[#ffffff]"),
mino.BlockSolidMagenta: []byte("[#c000cc]█[#ffffff]"),
mino.BlockGhostGreen: []byte("[#00e900]▓[#ffffff]"),
mino.BlockSolidGreen: []byte("[#00e900]█[#ffffff]"),
mino.BlockGhostOrange: []byte("[#ff7308]▓[#ffffff]"),
mino.BlockSolidOrange: []byte("[#ff7308]█[#ffffff]"),
mino.BlockGarbage: []byte("[#bbbbbb]█[#ffffff]"),
}
var (
renderHLine = []byte(string(tcell.RuneHLine))
renderVLine = []byte(string(tcell.RuneVLine))
@ -124,6 +104,9 @@ func resetPlayerSettingsForm() {
})
}
// BS 1: 10x10
// BS 2: 20x20
// BS 3: 30x40
func handleResize(screen tcell.Screen) {
newScreenW, newScreenH = screen.Size()
if newScreenW == screenW && newScreenH == screenH {
@ -133,45 +116,68 @@ func handleResize(screen tcell.Screen) {
screenW, screenH = newScreenW, newScreenH
if !fixedBlockSize {
if screenW >= 80 && screenH >= 44 {
if screenW >= 80 && screenH >= 46 {
blockSize = 3
} else if screenW >= 80 && screenH >= 24 {
blockSize = 2
} else {
blockSize = 1
}
}
mainHeight = (20 * blockSize) + 2
xMultiplier := 1
if blockSize == 2 {
xMultiplier = 2
} else if blockSize == 3 {
xMultiplier = 3
}
if blockSize == 1 {
mainHeight = 10 + 3
} else if blockSize == 2 {
mainHeight = 20 + 3
} else {
mainHeight = 40 + 3
}
if screenH > mainHeight+9 {
extraScreenPadding = 3
screenPadding = 2
mainHeight++
inputHeight = 2
} else if screenH > mainHeight+7 {
extraScreenPadding = 2
mainHeight++
inputHeight = 2
} else if screenH > mainHeight+5 {
extraScreenPadding = 1
screenPadding = 2
mainHeight++
inputHeight = 1
} else if screenH > mainHeight+2 {
extraScreenPadding = 0
} else if screenH > mainHeight+5 {
screenPadding = 1
mainHeight++
inputHeight = 1
} else if screenH > mainHeight+3 {
screenPadding = 1
inputHeight = 1
} else {
extraScreenPadding = 0
inputHeight = 1
screenPadding = 0
inputHeight = 0
}
multiplayerMatrixSize = ((screenW - extraScreenPadding) - ((10 * blockSize) + 16)) / ((10 * blockSize) + 4)
if blockSize == 1 {
previewWidth = 9
} else if blockSize == 2 {
previewWidth = 10
} else {
previewWidth = 14
}
newLogLines = ((screenH - mainHeight) - inputHeight) - extraScreenPadding
multiplayerMatrixSize = ((screenW - screenPadding) - ((10 * xMultiplier) + previewWidth + 6)) / ((10 * xMultiplier) + 4)
newLogLines = ((screenH - mainHeight) - inputHeight) - screenPadding
if newLogLines > 0 {
showLogLines = newLogLines
} else {
showLogLines = 1
}
gameGrid.SetRows(mainHeight+extraScreenPadding, inputHeight, -1).SetColumns(1+extraScreenPadding, 4+(10*blockSize), 10, -1)
gameGrid.SetRows(screenPadding, mainHeight, inputHeight, -1).SetColumns(screenPadding+1, 4+(10*xMultiplier), previewWidth, -1)
draw <- event.DrawAll
}
@ -181,9 +187,8 @@ func drawAll() {
return
}
renderPlayerMatrix()
renderPreviewMatrix()
renderMultiplayerMatrix()
renderPlayerGUI()
renderMultiplayerGUI()
}
func drawMessages() {
@ -191,12 +196,11 @@ func drawMessages() {
}
func drawPlayerMatrix() {
renderPlayerMatrix()
renderPreviewMatrix()
renderPlayerGUI()
}
func drawMultiplayerMatrixes() {
renderMultiplayerMatrix()
renderMultiplayerGUI()
}
func handleDraw() {
@ -250,12 +254,18 @@ func setShowDetails(active bool) {
draw <- event.DrawAll
}
func renderPreviewMatrix() {
func renderPlayerGUI() {
g := activeGame
if g == nil || len(g.Players) == 0 || g.Players[g.LocalPlayer].Matrix.Bag == nil {
if g == nil || len(g.Players) == 0 {
return
}
renderLock.Lock()
renderMatrixes([]*mino.Matrix{g.Players[g.LocalPlayer].Matrix})
mtx.Clear()
mtx.Write(renderBuffer.Bytes())
renderLock.Unlock()
player := g.Players[g.LocalPlayer]
m := g.Players[g.LocalPlayer].Matrix
@ -284,12 +294,14 @@ func renderPreviewMatrix() {
}
renderLock.Lock()
renderMatrix(g.Players[g.LocalPlayer].Preview)
renderMatrixes([]*mino.Matrix{g.Players[g.LocalPlayer].Preview})
if blockSize > 1 {
renderBuffer.WriteString(fmt.Sprintf("\n\n\n\n\n Combo\n\n %d\n\n\n\n\n Timer\n\n %.0f\n\n\n\n\nPending\n\n %d\n\n\n\n\n Speed\n\n %s", combo, comboTime, m.PendingGarbage, speed))
} else {
renderBuffer.WriteString(fmt.Sprintf("\n\n Combo\n\n %d\n\n Timer\n\n %.0f\n\nPending\n\n %d\n\n Speed\n\n %s", combo, comboTime, m.PendingGarbage, speed))
if blockSize == 1 {
renderBuffer.WriteString(fmt.Sprintf(" Combo\n %d\n\n Timer\n %.0f\n\nPending\n %d\n\n Speed\n %s", combo, comboTime, m.PendingGarbage, speed))
} else if blockSize == 2 {
renderBuffer.WriteString(fmt.Sprintf("\n Combo\n\n %d\n\n\n Timer\n\n %.0f\n\n\nPending\n\n %d\n\n\n Speed\n\n %s", combo, comboTime, m.PendingGarbage, speed))
} else if blockSize == 3 {
renderBuffer.WriteString(fmt.Sprintf("\n\n\n\n\n Combo\n\n %d\n\n\n\n\n\n Timer\n\n %.0f\n\n\n\n\n\n Pending\n\n %d\n\n\n\n\n\n Speed\n\n %s", combo, comboTime, m.PendingGarbage, speed))
}
side.Clear()
@ -299,20 +311,7 @@ func renderPreviewMatrix() {
m.Unlock()
}
func renderPlayerMatrix() {
g := activeGame
if g == nil || len(g.Players) == 0 {
return
}
renderLock.Lock()
renderMatrix(g.Players[g.LocalPlayer].Matrix)
mtx.Clear()
mtx.Write(renderBuffer.Bytes())
renderLock.Unlock()
}
func renderMultiplayerMatrix() {
func renderMultiplayerGUI() {
g := activeGame
if g == nil {
return
@ -364,87 +363,6 @@ func renderMultiplayerMatrix() {
renderLock.Unlock()
}
func renderMatrix(m *mino.Matrix) {
renderBuffer.Reset()
if m == nil {
return
}
m.Lock()
defer m.Unlock()
m.DrawPiecesL()
bs := blockSize
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 i := 0; i < extraScreenPadding; i++ {
if m.Type == mino.MatrixStandard && i == extraScreenPadding-1 {
renderBuffer.Write(renderULCorner)
for x := 0; x < m.W*bs; x++ {
renderBuffer.Write(renderHLine)
}
renderBuffer.Write(renderURCorner)
}
renderBuffer.WriteRune('\n')
}
for y := m.H - 1; y >= 0; y-- {
for j := 0; j < bs; j++ {
if m.Type == mino.MatrixStandard {
renderBuffer.Write(renderVLine)
} else {
iPieceNext := m.Bag != nil && m.Bag.Next().String() == mino.TetrominoI
if bs == 1 {
renderBuffer.WriteRune(' ')
renderBuffer.WriteRune(' ')
} else if !iPieceNext {
renderBuffer.WriteRune(' ')
}
}
for x := 0; x < m.W; x++ {
for k := 0; k < bs; k++ {
renderBuffer.Write(renderBlock[m.Block(x, y)])
}
}
if m.Type == mino.MatrixStandard {
renderBuffer.Write(renderVLine)
}
if y != 0 || m.Type != mino.MatrixCustom {
renderBuffer.WriteRune('\n')
}
}
}
if m.Type != mino.MatrixStandard {
return
}
renderBuffer.Write(renderLLCorner)
for x := 0; x < m.W*bs; x++ {
renderBuffer.Write(renderHLine)
}
renderBuffer.Write(renderLRCorner)
renderBuffer.WriteRune('\n')
renderPlayerDetails(m, bs)
}
func renderPlayerDetails(m *mino.Matrix, bs int) {
var buf string
if !showDetails {
@ -473,55 +391,74 @@ func renderPlayerDetails(m *mino.Matrix, bs int) {
func renderMatrixes(mx []*mino.Matrix) {
renderBuffer.Reset()
if mx == nil {
if mx == nil || len(mx) == 0 {
return
}
bs := blockSize
mt := mx[0].Type
mh := mx[0].H
div := " "
var nextPieceWidth = 0
if mt == mino.MatrixPreview {
renderBuffer.WriteRune('\n')
if mx[0].Bag != nil {
p := mx[0].Bag.Next()
nextPieceWidth, _ = p.Size()
if nextPieceWidth == 2 && blockSize == 1 {
nextPieceWidth = 3
}
}
} else if mt == mino.MatrixCustom {
bs = 1
}
for i := range mx {
mx[i].Lock()
mx[i].DrawPiecesL()
}
div := " "
height := mx[0].H
for i := 0; i < extraScreenPadding; i++ {
if i == extraScreenPadding-1 {
for i := range mx {
if i > 0 {
renderBuffer.WriteString(div)
}
renderBuffer.Write(renderULCorner)
for x := 0; x < mx[i].W*blockSize; x++ {
renderBuffer.Write(renderHLine)
}
renderBuffer.Write(renderURCorner)
if mt == mino.MatrixStandard {
for i := range mx {
if i > 0 {
renderBuffer.WriteString(div)
}
}
renderBuffer.Write(renderULCorner)
for x := 0; x < mx[i].W*bs; x++ {
renderBuffer.Write(renderHLine)
}
renderBuffer.Write(renderURCorner)
}
renderBuffer.WriteRune('\n')
}
for y := height - 1; y >= 0; y-- {
for j := 0; j < blockSize; j++ {
for i := range mx {
m := mx[i]
if bs == 1 {
for y := mh - 1; y >= 0; y -= 2 {
for i, m := range mx {
if i > 0 {
renderBuffer.WriteString(div)
}
if m.Type == mino.MatrixStandard {
renderBuffer.Write(renderVLine)
} else if m.Type == mino.MatrixPreview {
renderBuffer.WriteRune(' ')
if nextPieceWidth < 4 {
renderBuffer.WriteRune(' ')
}
}
for x := 0; x < m.W; x++ {
for j := 0; j < blockSize; j++ {
renderBuffer.Write(renderBlock[m.Block(x, y)])
}
renderBuffer.WriteRune('[')
renderBuffer.Write(m.Block(x, y-1).Color())
renderBuffer.WriteRune(':')
renderBuffer.Write(m.Block(x, y).Color())
renderBuffer.WriteRune(']')
renderBuffer.WriteRune('▄')
renderBuffer.Write([]byte("[-:-]"))
}
if m.Type == mino.MatrixStandard {
@ -529,30 +466,107 @@ func renderMatrixes(mx []*mino.Matrix) {
}
}
renderBuffer.WriteRune('\n')
if y != 0 || mt != mino.MatrixCustom {
renderBuffer.WriteRune('\n')
}
}
} else if bs == 2 {
for y := mh - 1; y >= 0; y-- {
for i, m := range mx {
if i > 0 {
renderBuffer.WriteString(div)
}
if m.Type == mino.MatrixStandard {
renderBuffer.Write(renderVLine)
} else if m.Type == mino.MatrixPreview {
for pad := 0; pad < 3-nextPieceWidth; pad++ {
renderBuffer.WriteRune(' ')
renderBuffer.WriteRune(' ')
}
}
for x := 0; x < m.W; x++ {
renderBuffer.WriteRune('[')
renderBuffer.Write(m.Block(x, y).Color())
renderBuffer.WriteRune(']')
renderBuffer.WriteRune('█')
renderBuffer.WriteRune('█')
renderBuffer.Write([]byte("[-]"))
}
if m.Type == mino.MatrixStandard {
renderBuffer.Write(renderVLine)
}
}
if y != 0 || mt != mino.MatrixCustom {
renderBuffer.WriteRune('\n')
}
}
} else {
for y := mh - 1; y >= 0; y-- {
for repeat := 0; repeat < 2; repeat++ {
for i, m := range mx {
if i > 0 {
renderBuffer.WriteString(div)
}
if m.Type == mino.MatrixStandard {
renderBuffer.Write(renderVLine)
} else if m.Type == mino.MatrixPreview {
if nextPieceWidth == 2 {
renderBuffer.WriteRune(' ')
renderBuffer.WriteRune(' ')
} else if nextPieceWidth < 4 {
renderBuffer.WriteRune(' ')
}
}
for x := 0; x < m.W; x++ {
renderBuffer.WriteRune('[')
renderBuffer.Write(m.Block(x, y).Color())
renderBuffer.WriteRune(']')
renderBuffer.WriteRune('█')
renderBuffer.WriteRune('█')
renderBuffer.WriteRune('█')
renderBuffer.Write([]byte("[-]"))
}
if m.Type == mino.MatrixStandard {
renderBuffer.Write(renderVLine)
}
}
if y != 0 || mt != mino.MatrixCustom {
renderBuffer.WriteRune('\n')
}
}
}
}
for i := range mx {
if i > 0 {
renderBuffer.WriteString(div)
if mt == mino.MatrixStandard {
for i := range mx {
if i > 0 {
renderBuffer.WriteString(div)
}
renderBuffer.Write(renderLLCorner)
for x := 0; x < mx[i].W*bs; x++ {
renderBuffer.Write(renderHLine)
}
renderBuffer.Write(renderLRCorner)
}
renderBuffer.Write(renderLLCorner)
for x := 0; x < mx[i].W*blockSize; x++ {
renderBuffer.Write(renderHLine)
renderBuffer.WriteRune('\n')
for i, m := range mx {
if i > 0 {
renderBuffer.WriteString(div)
}
renderPlayerDetails(m, bs)
}
renderBuffer.Write(renderLRCorner)
}
renderBuffer.WriteRune('\n')
for i, m := range mx {
if i > 0 {
renderBuffer.WriteString(div)
}
renderPlayerDetails(m, blockSize)
}
for i := range mx {

View file

@ -32,8 +32,7 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
})
gameGrid = tview.NewGrid().
SetBorders(false).
SetRows(2+(20*blockSize)+extraScreenPadding, -1)
SetBorders(false)
mtx = tview.NewTextView().
SetScrollable(false).
@ -67,13 +66,14 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
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)
gameGrid.
AddItem(pad, 0, 0, 4, 1, 0, 0, false).
AddItem(pad, 0, 1, 1, 2, 0, 0, false).
AddItem(mtx, 1, 1, 1, 1, 0, 0, false).
AddItem(side, 1, 2, 1, 1, 0, 0, false).
AddItem(buffer, 1, 3, 1, 1, 0, 0, false).
AddItem(inputView, 2, 1, 1, 3, 0, 0, true).
AddItem(recent, 3, 1, 1, 3, 0, 0, true)
// Set up title screen
@ -89,9 +89,9 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
addToRight bool
i int
)
for y := 0; y < 6; y++ {
for y := 0; y < 11; y++ {
for x := 0; x < 4; x++ {
piece = mino.NewPiece(minos[i], mino.Point{x * 5, (y * 5)})
piece = mino.NewPiece(minos[i], mino.Point{(x * 5) + 2, (y * 5)})
i++
if i == len(minos) {
@ -137,13 +137,13 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
buttonC = tview.NewButton("C")
buttonLabelC = tview.NewTextView().SetTextAlign(tview.AlignCenter)
titleNameGrid := tview.NewGrid().SetRows(5).
titleNameGrid := tview.NewGrid().SetRows(3).
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).
SetRows(5, 3, 3, 3, 3, 3, 4).
SetColumns(-1, 34, -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).
@ -167,8 +167,8 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
gameListHeader = tview.NewTextView().SetTextAlign(tview.AlignCenter)
gameListGrid = tview.NewGrid().
SetRows(7, 1, -1, 1, 3).
SetColumns(-1, 38, -1).
SetRows(5, 1, -1, 1, 3).
SetColumns(-1, 34, -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).
@ -219,8 +219,8 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
SetWordWrap(false).SetText("New Game")
newGameGrid = tview.NewGrid().
SetRows(7, 2, 1, 1, 1, 1, 1, 1, 1, -1, 3).
SetColumns(-1, 38, -1).
SetRows(5, 2, 1, 1, 1, 1, 1, 1, 1, -1, 3).
SetColumns(-1, 34, -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).
@ -246,8 +246,8 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
playerSettingsForm = tview.NewForm().SetButtonsAlign(tview.AlignCenter)
playerSettingsGrid = tview.NewGrid().
SetRows(7, 2, -1, 1).
SetColumns(-1, 38, -1).
SetRows(5, 2, -1, 1).
SetColumns(-1, 34, -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).
@ -305,8 +305,8 @@ func initGUI(skipTitle bool) (*tview.Application, error) {
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).
SetRows(5, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1).
SetColumns(-1, 34, -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).
@ -394,7 +394,7 @@ func newTitleMatrixSide() *mino.Matrix {
}
}()
m := mino.NewMatrix(21, 24, 0, 1, ev, draw, mino.MatrixCustom)
m := mino.NewMatrix(21, 48, 0, 1, ev, draw, mino.MatrixCustom)
return m
}
@ -412,7 +412,7 @@ func newTitleMatrixName() *mino.Matrix {
}
}()
m := mino.NewMatrix(36, 7, 0, 1, ev, draw, mino.MatrixCustom)
m := mino.NewMatrix(36, 5, 0, 1, ev, draw, mino.MatrixCustom)
centerStart := (m.W / 2) - 17

View file

@ -216,7 +216,7 @@ func handleKeypress(ev *tcell.EventKey) *tcell.EventKey {
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()
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 return.").ClearButtons()
app.SetRoot(modal, true)
} else if titleSelectedButton == 3 {
titleScreen = 0

View file

@ -19,7 +19,7 @@ func TestRenderMatrix(t *testing.T) {
m.AddTestBlocks()
renderMatrix(m)
renderPlayerMatrix(m)
}
func BenchmarkRenderStandardMatrix(b *testing.B) {
@ -39,7 +39,7 @@ func BenchmarkRenderStandardMatrix(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
renderMatrix(m)
renderPlayerMatrix(m)
}
}
@ -60,7 +60,7 @@ func BenchmarkRenderLargeMatrix(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
renderMatrix(m)
renderPlayerMatrix(m)
}
blockSize = 1

View file

@ -12,7 +12,7 @@ import (
)
const (
SubTitle = " .rocketnine.space v"
SubTitle = " .rocketnine.space v"
)
var (
@ -300,15 +300,15 @@ func renderTitle() {
renderLock.Lock()
renderMatrix(titleMatrix)
renderMatrixes([]*mino.Matrix{titleMatrix})
titleName.Clear()
titleName.Write(renderBuffer.Bytes())
renderMatrix(titleMatrixL)
renderMatrixes([]*mino.Matrix{titleMatrixL})
titleL.Clear()
titleL.Write(renderBuffer.Bytes())
renderMatrix(titleMatrixR)
renderMatrixes([]*mino.Matrix{titleMatrixR})
titleR.Clear()
titleR.Write(renderBuffer.Bytes())
@ -316,7 +316,7 @@ func renderTitle() {
}
func renderGameList() {
w := 36
w := 32
gameListView.Clear()
gameListView.Write(renderULCorner)
@ -327,7 +327,7 @@ func renderGameList() {
gameListView.Write([]byte("\n"))
gameListView.Write(renderVLine)
gameListView.Write([]byte(fmt.Sprintf("%-29s%s", "Game", "Players")))
gameListView.Write([]byte(fmt.Sprintf("%-25s%s", "Game", "Players")))
gameListView.Write(renderVLine)
gameListView.Write([]byte("\n"))
@ -338,7 +338,7 @@ func renderGameList() {
gameListView.Write(renderRTee)
gameListView.Write([]byte("\n"))
h := 8
h := 9
for i, g := range gameList {
p := strconv.Itoa(g.Players)
@ -350,7 +350,7 @@ func renderGameList() {
if titleSelectedButton == 0 && gameListSelected == i {
gameListView.Write([]byte("[#000000:#FFFFFF]"))
}
gameListView.Write([]byte(fmt.Sprintf("%-29s%7s", g.Name, p)))
gameListView.Write([]byte(fmt.Sprintf("%-25s%7s", g.Name, p)))
if titleSelectedButton == 0 && gameListSelected == i {
gameListView.Write([]byte("[-:-]"))
}

View file

@ -26,7 +26,11 @@ import (
var (
done = make(chan bool)
activeGame *game.Game
activeGame *game.Game
activeGameConn *game.Conn
server *game.Server
localListenDir string
connectAddress string
serverAddress string
@ -62,7 +66,7 @@ func main() {
log.Println()
log.Println()
log.Fatalf("panic: %+v", r)
log.Fatalf("caught panic: %+v", r)
}
}()
@ -143,13 +147,18 @@ func main() {
connectAddress = serverAddress
}
var (
server *game.Server
localListenDir string
)
go func(server *game.Server) {
go func() {
<-done
if activeGameConn != nil {
if server == nil {
activeGameConn.Write(&game.GameCommandDisconnect{})
activeGameConn.Wait()
}
activeGameConn.Close()
}
if server != nil {
server.StopListening()
}
@ -160,7 +169,7 @@ func main() {
closeGUI()
os.Exit(0)
}(server)
}()
for {
gameID := <-joinGame
@ -184,7 +193,7 @@ func main() {
logMessage(fmt.Sprintf("* Connecting to %s...", connectAddress))
}
s, err := game.Connect(connectAddress)
activeGameConn, err = game.Connect(connectAddress)
if err != nil {
log.Fatal(err)
}
@ -206,7 +215,7 @@ func main() {
newGame = &game.ListedGame{Name: game.GameName(newGameNameInput.GetText()), MaxPlayers: maxPlayers, SpeedLimit: speedLimit}
}
activeGame, err = s.JoinGame(nickname, gameID, newGame, logger, draw)
activeGame, err = activeGameConn.JoinGame(nickname, gameID, newGame, logger, draw)
if err != nil {
log.Fatalf("failed to connect to %s: %s", connectAddress, err)
}
@ -222,7 +231,7 @@ func main() {
joinedGame = true
setTitleVisible(false)
server = game.NewServer(nil)
server = game.NewServer(nil, logLevel)
server.Logger = make(chan string, game.LogQueueSize)
if logDebug || logVerbose {
@ -247,14 +256,14 @@ func main() {
go server.Listen(localListenAddress)
localServerConn, err := game.Connect(localListenAddress)
activeGameConn, err = game.Connect(localListenAddress)
if err != nil {
log.Fatal(err)
log.Fatalf("failed to create local game: %s", err)
}
activeGame, err = localServerConn.JoinGame(nickname, event.GameIDNewLocal, nil, logger, draw)
activeGame, err = activeGameConn.JoinGame(nickname, event.GameIDNewLocal, nil, logger, draw)
if err != nil {
panic(err)
log.Fatalf("failed to join local game: %s", err)
}
activeGame.LogLevel = logLevel
@ -271,7 +280,7 @@ func main() {
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))
log.Fatalf("failed to parse custom matrix on token #%d", i)
}
if i%2 == 1 {
activeGame.Players[activeGame.LocalPlayer].Matrix.SetBlock(x, token, mino.BlockGarbage, false)

View file

@ -137,7 +137,7 @@ func (s *Conn) handleRead() {
um = func(mgc interface{}) {
err := json.Unmarshal(msg.Data, mgc)
if err != nil {
panic(err)
s.Close()
}
}
)
@ -147,7 +147,7 @@ func (s *Conn) handleRead() {
err := json.Unmarshal(scanner.Bytes(), &msg)
if err != nil {
panic(err)
break
}
s.LastTransfer = time.Now()
@ -216,7 +216,7 @@ func (s *Conn) handleRead() {
um(&mgc)
gc = &mgc
default:
// TODO Place beind debug log level
// TODO Require at least debug log level
log.Println("unknown serverconn command", scanner.Text())
continue
}
@ -228,8 +228,7 @@ func (s *Conn) handleRead() {
err = s.conn.SetReadDeadline(time.Now().Add(ConnTimeout))
if err != nil {
s.Close()
return
break
}
}
@ -292,8 +291,9 @@ func (s *Conn) Close() {
s.Terminated = true
s.conn.Close()
go func() {
s.conn.Close()
s.Wait()
close(s.In)
close(s.out)

View file

@ -148,7 +148,8 @@ 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, mino.MatrixPreview)
// TODO Verify rank-2 is valid for all playable rank previews
p.Preview = mino.NewMatrix(g.Rank, g.Rank-2, 0, 1, g.Event, g.draw, mino.MatrixPreview)
p.Preview.PlayerName = p.Name
p.Matrix = mino.NewMatrix(10, 20, 4, 1, g.Event, g.draw, mino.MatrixStandard)
@ -258,7 +259,7 @@ func (g *Game) StartL(seed int64) int64 {
g.TimeStarted = time.Now()
if g.LocalPlayer == PlayerUnknown {
panic("Player unknown")
log.Fatal("failed to start game: player unknown")
}
if seed == 0 {
@ -269,7 +270,7 @@ func (g *Game) StartL(seed int64) int64 {
for _, p := range g.Players {
bag, err := mino.NewBag(g.Seed, g.Minos, 10)
if err != nil {
panic(err)
log.Fatalf("failed to start game: failed to create bag: %s", err)
}
p.Preview.AttachBag(bag)
@ -277,9 +278,10 @@ func (g *Game) StartL(seed int64) int64 {
}
// Take piece on host as well to give initial position for start of game
for _, p := range g.Players {
for playerID, p := range g.Players {
if !p.Matrix.TakePiece() {
g.Log(LogStandard, "Failed to take piece while starting game for player ", p.Player)
g.RemovePlayerL(playerID)
}
}
@ -427,14 +429,10 @@ func (g *Game) handleDistributeMatrixes() {
g.setGameOverL(true)
winner := "Tie!"
if remainingPlayer != -1 {
winner = g.Players[remainingPlayer].Name
}
g.WriteAllL(&GameCommandGameOver{Player: 0, Winner: winner})
var otherPlayers string
for i := range g.Players {
if i == remainingPlayer {
winner = g.Players[remainingPlayer].Name
continue
}
if otherPlayers != "" {
@ -444,6 +442,8 @@ func (g *Game) handleDistributeMatrixes() {
otherPlayers += g.Players[i].Name
}
g.WriteAllL(&GameCommandGameOver{Winner: winner})
g.WriteMessage("Game over - winner: " + winner)
g.WriteMessage("Garbage sent/received:")
for _, p := range g.Players {
@ -772,7 +772,10 @@ func (g *Game) handleDropTerminatedPlayers() {
for {
time.Sleep(15 * time.Second)
g.Lock()
if g.Terminated {
g.Unlock()
return
}
@ -781,13 +784,15 @@ func (g *Game) handleDropTerminatedPlayers() {
g.RemovePlayerL(playerID)
}
}
g.Unlock()
}
}
func GameName(name string) string {
name = gameNameRegexp.ReplaceAllString(strings.TrimSpace(name), "")
if len(name) > 28 {
name = name[:28]
if len(name) > 24 {
name = name[:24]
} else if name == "" {
name = "netris"
}

View file

@ -33,6 +33,8 @@ type Server struct {
created time.Time
logLevel int
sync.RWMutex
}
@ -47,11 +49,11 @@ type ServerInterface interface {
Shutdown(reason string)
}
func NewServer(si []ServerInterface) *Server {
func NewServer(si []ServerInterface, logLevel int) *Server {
in := make(chan GameCommandInterface, CommandQueueSize)
out := make(chan GameCommandInterface, CommandQueueSize)
s := &Server{I: si, In: in, Out: out, Games: make(map[int]*Game), created: time.Now()}
s := &Server{I: si, In: in, Out: out, Games: make(map[int]*Game), created: time.Now(), logLevel: logLevel}
var (
g *Game
@ -123,6 +125,7 @@ func (s *Server) NewGame() (*Game, error) {
}
g.ID = gameID
g.LogLevel = s.logLevel
s.Games[gameID] = g
@ -163,7 +166,7 @@ func (s *Server) FindGame(p *Player, gameID int, newGame ListedGame) *Game {
// Create a custom game
g, err = s.NewGame()
if err != nil {
panic(err)
log.Fatalf("failed to create custom game: %s", err)
}
g.Lock()
@ -205,7 +208,7 @@ func (s *Server) FindGame(p *Player, gameID int, newGame ListedGame) *Game {
// Create a local game
g, err = s.NewGame()
if err != nil {
panic(err)
log.Fatalf("failed to create local game: %s", err)
}
g.Local = true
@ -335,94 +338,87 @@ func (s *Server) handleGameCommands(pl *Player, g *Game) {
g.Lock()
switch c {
case CommandMessage:
if p, ok := e.(*GameCommandMessage); ok {
if player, ok := g.Players[p.SourcePlayer]; ok {
s.Logf("<%s> %s", player.Name, p.Message)
switch p := e.(type) {
case *GameCommandDisconnect:
log.Printf("%+v", g.Players)
if _, ok := g.Players[p.SourcePlayer]; ok {
g.RemovePlayerL(p.SourcePlayer)
}
case *GameCommandMessage:
if player, ok := g.Players[p.SourcePlayer]; ok {
s.Logf("<%s> %s", player.Name, p.Message)
msg := strings.ReplaceAll(strings.TrimSpace(p.Message), "\n", "")
if msg != "" {
g.WriteAllL(&GameCommandMessage{Player: p.SourcePlayer, Message: msg})
}
msg := strings.ReplaceAll(strings.TrimSpace(p.Message), "\n", "")
if msg != "" {
g.WriteAllL(&GameCommandMessage{Player: p.SourcePlayer, Message: msg})
}
}
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
case *GameCommandNickname:
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})
}
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 {
if pl, ok := g.Players[p.SourcePlayer]; ok {
for _, m := range p.Matrixes {
pl.Matrix.Replace(m)
case *GameCommandUpdateMatrix:
if pl, ok := g.Players[p.SourcePlayer]; ok {
for _, m := range p.Matrixes {
pl.Matrix.Replace(m)
if g.SpeedLimit > 0 && m.Speed > g.SpeedLimit+5 && time.Since(g.TimeStarted) > 7*time.Second {
pl.Matrix.SetGameOver()
if g.SpeedLimit > 0 && m.Speed > g.SpeedLimit+5 && time.Since(g.TimeStarted) > 7*time.Second {
pl.Matrix.SetGameOver()
g.WriteMessage(fmt.Sprintf("%s went too fast and crashed", pl.Name))
g.WriteAllL(&GameCommandGameOver{Player: p.SourcePlayer})
}
}
m := pl.Matrix
spawn := m.SpawnLocation(m.P)
if m.P != nil && spawn.X >= 0 && spawn.Y >= 0 && m.P.X != spawn.X {
pl.Moved = time.Now()
pl.Idle = 0
}
}
}
case CommandGameOver:
if p, ok := e.(*GameCommandGameOver); ok {
g.Players[p.SourcePlayer].Matrix.SetGameOver()
g.WriteMessage(fmt.Sprintf("%s was knocked out", g.Players[p.SourcePlayer].Name))
g.WriteAllL(&GameCommandGameOver{Player: p.SourcePlayer})
}
case CommandSendGarbage:
if p, ok := e.(*GameCommandSendGarbage); ok {
leastGarbagePlayer := -1
leastGarbage := -1
for playerID, player := range g.Players {
if playerID == p.SourcePlayer || player.Matrix.GameOver {
continue
}
if leastGarbage == -1 || player.totalGarbageReceived < leastGarbage {
leastGarbagePlayer = playerID
leastGarbage = player.totalGarbageReceived
g.WriteMessage(fmt.Sprintf("%s went too fast and crashed", pl.Name))
g.WriteAllL(&GameCommandGameOver{Player: p.SourcePlayer})
}
}
if leastGarbagePlayer != -1 {
g.Players[leastGarbagePlayer].totalGarbageReceived += p.Lines
g.Players[leastGarbagePlayer].pendingGarbage += p.Lines
g.Players[p.SourcePlayer].totalGarbageSent += p.Lines
m := pl.Matrix
spawn := m.SpawnLocation(m.P)
if m.P != nil && spawn.X >= 0 && spawn.Y >= 0 && m.P.X != spawn.X {
pl.Moved = time.Now()
pl.Idle = 0
}
}
case CommandStats:
if p, ok := e.(*GameCommandStats); ok {
players := 0
games := 0
case *GameCommandGameOver:
g.Players[p.SourcePlayer].Matrix.SetGameOver()
for _, g := range s.Games {
players += len(g.Players)
games++
g.WriteMessage(fmt.Sprintf("%s was knocked out", g.Players[p.SourcePlayer].Name))
g.WriteAllL(&GameCommandGameOver{Player: p.SourcePlayer})
case *GameCommandSendGarbage:
leastGarbagePlayer := -1
leastGarbage := -1
for playerID, player := range g.Players {
if playerID == p.SourcePlayer || player.Matrix.GameOver {
continue
}
g.Players[p.SourcePlayer].Write(&GameCommandStats{Created: s.created, Players: players, Games: games})
if leastGarbage == -1 || player.totalGarbageReceived < leastGarbage {
leastGarbagePlayer = playerID
leastGarbage = player.totalGarbageReceived
}
}
if leastGarbagePlayer != -1 {
g.Players[leastGarbagePlayer].totalGarbageReceived += p.Lines
g.Players[leastGarbagePlayer].pendingGarbage += p.Lines
g.Players[p.SourcePlayer].totalGarbageSent += p.Lines
}
case *GameCommandStats:
players := 0
games := 0
for _, g := range s.Games {
players += len(g.Players)
games++
}
g.Players[p.SourcePlayer].Write(&GameCommandStats{Created: s.created, Players: players, Games: games})
}
g.Unlock()

View file

@ -6,6 +6,7 @@ import (
"context"