Center GUI text, improve matrix test

This commit is contained in:
Trevor Slocum 2019-10-11 22:10:33 -07:00
parent e7a430dbd6
commit 24cce86c0f
14 changed files with 383 additions and 229 deletions

55
CONFIGURATION.md Normal file
View File

@ -0,0 +1,55 @@
# Client
```
Usage of ./netris:
-connect string
connect to server address or socket path
-debug
enable debug logging
-debug-address string
address to serve debug info
-matrix string
pre-fill matrix with pieces
-nick string
nickname (default "Anonymous")
-scale int
UI scale
-verbose
enable verbose logging
```
### -scale
Defaults to 0, automatically scaling up to 3x standard size based on the
terminal window's dimensions.
Specify an integer to only use that UI scale.
### -connect
A TCP address in the form of address:port or socket path may be supplied.
# Server
```
Usage of ./netris-server:
-debug
enable debug logging
-debug-address string
address to serve debug info
-listen-socket string
host server on socket path
-listen-ssh string
host SSH server on network address
-listen-tcp string
host server on network address
-netris string
path to netris client
-verbose
enable verbose logging
```
### -listen-ssh and -netris
The netris client will be launched to serve incoming SSH connections. Update
client and server binaries together.

View File

@ -24,6 +24,10 @@ go get -u git.sr.ht/~tslocum/netris/cmd/netris
go get -u git.sr.ht/~tslocum/netris/cmd/netris-server
```
## Configure
See [CONFIGURATION.md](https://man.sr.ht/~tslocum/netris/CONFIGURATION.md)
## Support
Please share suggestions/issues [here](https://todo.sr.ht/~tslocum/netris).

View File

@ -20,7 +20,11 @@ var (
listenAddressSSH string
netrisBinary string
debugAddress string
done = make(chan bool)
logDebug bool
logVerbose bool
done = make(chan bool)
)
const (
@ -34,7 +38,9 @@ func init() {
flag.StringVar(&listenAddressSocket, "listen-socket", "", "host server on socket path")
flag.StringVar(&listenAddressSSH, "listen-ssh", "", "host SSH server on network address")
flag.StringVar(&netrisBinary, "netris", "", "path to netris client")
flag.StringVar(&debugAddress, "debug", "", "address to serve debug info")
flag.StringVar(&debugAddress, "debug-address", "", "address to serve debug info")
flag.BoolVar(&logDebug, "debug", false, "enable debug logging")
flag.BoolVar(&logVerbose, "verbose", false, "enable verbose logging")
}
func main() {

View File

@ -5,6 +5,7 @@ import (
"fmt"
"log"
"sort"
"strconv"
"strings"
"sync"
"time"
@ -175,6 +176,9 @@ func handleResize(screen tcell.Screen) bool {
grid.SetRows(2+(20*blockSize), -1).SetColumns(1, 4+(10*blockSize), 10, -1)
logMutex.Lock()
renderLogMessages = true
logMutex.Unlock()
draw <- event.DrawAll
return true
}
@ -254,15 +258,6 @@ func setInputStatus(active bool) {
inputView.SetText("")
lowerPages = lowerPages.SwitchToPage("input")
} else {
msg := inputView.GetText()
if msg != "" {
if activeGame != nil {
activeGame.Event <- &event.MessageEvent{Message: msg}
} else {
// TODO: Print warning
}
}
lowerPages = lowerPages.SwitchToPage("recent")
}
}
@ -299,7 +294,20 @@ func renderPreviewMatrix() {
side.Clear()
side.Write(renderMatrix(g.Players[g.LocalPlayer].Preview))
m.Lock()
fmt.Fprint(side, fmt.Sprintf("\n\nTime\n\n%.0f\n\nCombo\n\n%d\n\nPending\n\n%d\n\nSpeed\n\n%d", comboTime, combo, m.PendingGarbage, m.Speed))
renderLock.Lock()
renderBuffer.Reset()
if m.Speed < 100 {
renderBuffer.WriteRune(' ')
}
renderBuffer.WriteString(strconv.Itoa(m.Speed))
if blockSize > 1 {
fmt.Fprint(side, 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, renderBuffer.Bytes()))
} else {
fmt.Fprint(side, 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, renderBuffer.Bytes()))
}
renderLock.Unlock()
m.Unlock()
}
@ -371,17 +379,32 @@ func renderMatrix(m *mino.Matrix) []byte {
m.DrawPiecesL()
if m.Preview && blockSize > 2 {
blockSize = 2
// Draw preview matrix at block size 2 max
bs := blockSize
if m.Preview {
if bs > 2 {
bs = 2
}
if bs > 1 {
renderBuffer.WriteRune('\n')
}
}
for y := m.H - 1; y >= 0; y-- {
for j := 0; j < blockSize; j++ {
for j := 0; j < bs; j++ {
if !m.Preview {
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 < blockSize; k++ {
for k := 0; k < bs; k++ {
renderBuffer.Write(renderBlock[m.Block(x, y)])
}
}
@ -399,17 +422,17 @@ func renderMatrix(m *mino.Matrix) []byte {
}
renderBuffer.Write(renderLLCorner)
for x := 0; x < m.W*blockSize; x++ {
for x := 0; x < m.W*bs; x++ {
renderBuffer.Write(renderHLine)
}
renderBuffer.Write(renderLRCorner)
renderBuffer.WriteRune('\n')
name := m.PlayerName
if len(name) > m.W*blockSize {
name = name[:m.W*blockSize]
if len(name) > m.W*bs {
name = name[:m.W*bs]
}
padName := ((m.W*blockSize - len(name)) / 2) + 1
padName := ((m.W*bs - len(name)) / 2) + 1
for i := 0; i < padName; i++ {
renderBuffer.WriteRune(' ')
}
@ -506,6 +529,13 @@ func renderMatrixes(mx []*mino.Matrix) []byte {
return renderBuffer.Bytes()
}
func logMessage(message string) {
logMutex.Lock()
logMessages = append(logMessages, message)
renderLogMessages = true
logMutex.Unlock()
}
func renderRecentMessages() {
logMutex.Lock()
if !renderLogMessages {

View File

@ -1,85 +1,95 @@
package main
import "github.com/gdamore/tcell"
import (
"git.sr.ht/~tslocum/netris/pkg/event"
"github.com/gdamore/tcell"
)
func handleKeypress(event *tcell.EventKey) *tcell.EventKey {
func handleKeypress(ev *tcell.EventKey) *tcell.EventKey {
if inputActive {
if event.Key() == tcell.KeyEnter {
// TODO: Process
if ev.Key() == tcell.KeyEnter {
msg := inputView.GetText()
if msg != "" {
if activeGame != nil {
activeGame.Event <- &event.MessageEvent{Message: msg}
} else {
logMessage("Message not sent - not currently connected to any game")
}
}
setInputStatus(false)
} else if event.Key() == tcell.KeyEscape {
} else if ev.Key() == tcell.KeyEscape {
setInputStatus(false)
}
return event
return ev
}
if event.Key() == tcell.KeyUp {
if ev.Key() == tcell.KeyUp {
if activeGame == nil {
return event
return ev
}
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.HardDropPiece(0)
activeGame.Players[activeGame.LocalPlayer].Matrix.HardDropPiece()
activeGame.Unlock()
return nil
} else if event.Key() == tcell.KeyDown {
} else if ev.Key() == tcell.KeyDown {
if activeGame == nil {
return event
return ev
}
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.MovePiece(0, 0, -1)
activeGame.Players[activeGame.LocalPlayer].Matrix.MovePiece(0, -1)
activeGame.Unlock()
return nil
} else if event.Key() == tcell.KeyLeft {
} else if ev.Key() == tcell.KeyLeft {
if activeGame == nil {
return event
return ev
}
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.MovePiece(0, -1, 0)
activeGame.Players[activeGame.LocalPlayer].Matrix.MovePiece(-1, 0)
activeGame.Unlock()
return nil
} else if event.Key() == tcell.KeyRight {
} else if ev.Key() == tcell.KeyRight {
if activeGame == nil {
return event
return ev
}
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.MovePiece(0, 1, 0)
activeGame.Players[activeGame.LocalPlayer].Matrix.MovePiece(1, 0)
activeGame.Unlock()
return nil
} else if event.Rune() == 'z' || event.Rune() == 'Z' {
} else if ev.Rune() == 'z' || ev.Rune() == 'Z' {
if activeGame == nil {
return event
return ev
}
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.RotatePiece(0, 1, 1)
activeGame.Players[activeGame.LocalPlayer].Matrix.RotatePiece(1, 1)
activeGame.Unlock()
return nil
} else if event.Rune() == 'x' || event.Rune() == 'X' {
} else if ev.Rune() == 'x' || ev.Rune() == 'X' {
if activeGame == nil {
return event
return ev
}
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.RotatePiece(0, 1, 0)
activeGame.Players[activeGame.LocalPlayer].Matrix.RotatePiece(1, 0)
activeGame.Unlock()
return nil
} else if event.Key() == tcell.KeyEnter {
} else if ev.Key() == tcell.KeyEnter {
setInputStatus(!inputActive)
} else if event.Key() == tcell.KeyEscape {
} else if ev.Key() == tcell.KeyEscape {
done <- true
}
return event
return ev
}

View File

@ -61,5 +61,3 @@ func BenchmarkRenderLargeMatrix(b *testing.B) {
blockSize = 1
}
// TODO Check cpu profile of testrendermatrix

View File

@ -53,16 +53,15 @@ func main() {
defer func() {
if r := recover(); r != nil {
closeGUI()
time.Sleep(time.Second)
log.Println()
log.Println()
log.Println()
log.Println()
debug.PrintStack()
log.Println()
log.Println()
log.Fatalf("panic: %+v", r)
os.Exit(0)
}
}()
@ -80,7 +79,6 @@ func main() {
log.Fatal("failed to start netris: non-interactive terminals are not supported")
}
// TODO Document
if blockSize > 3 {
blockSize = 3
}
@ -117,10 +115,7 @@ func main() {
logger := make(chan string, game.LogQueueSize)
go func() {
for msg := range logger {
logMutex.Lock()
logMessages = append(logMessages, time.Now().Format(LogTimeFormat)+" "+msg)
renderLogMessages = true
logMutex.Unlock()
logMessage(time.Now().Format(LogTimeFormat) + " " + msg)
}
}()
@ -158,10 +153,7 @@ func main() {
if logDebug || logVerbose {
go func() {
for msg := range server.Logger {
logMutex.Lock()
logMessages = append(logMessages, time.Now().Format(LogTimeFormat)+" Local server: "+msg)
renderLogMessages = true
logMutex.Unlock()
logMessage(time.Now().Format(LogTimeFormat) + " Local server: " + msg)
}
}()
} else {

View File

@ -12,7 +12,7 @@ import (
"git.sr.ht/~tslocum/netris/pkg/mino"
)
const UpdateDuration = 750 * time.Millisecond
const UpdateDuration = 850 * time.Millisecond
const (
LogStandard = iota
@ -365,10 +365,6 @@ func (g *Game) handleDistributeMatrixes() {
return
}
// TODO: Check for inactive players and disconnect them
// Assign unique player ID and use maps instead of slices?
// Assign matrix, etc to player instead, and use only one slice of players?
remainingPlayer := -1
remainingPlayers := 0
@ -444,8 +440,10 @@ func (g *Game) HandleReadCommands(in chan GameCommandInterface) {
for e = range in {
g.Lock()
c := e.Command()
logLevel := LogDebug
if e.Command() == CommandUpdateMatrix {
if c == CommandPing || c == CommandPong || c == CommandUpdateMatrix {
logLevel = LogVerbose
}
g.Log(logLevel, "LOCAL handle ", e.Command(), " from ", e.Source(), " ", e)
@ -471,23 +469,7 @@ func (g *Game) HandleReadCommands(in chan GameCommandInterface) {
}
case CommandUpdateGame:
if p, ok := e.(*GameCommandUpdateGame); ok {
for playerID, playerName := range p.Players {
if existingPlayer, ok := g.Players[playerID]; ok {
existingPlayer.Name = playerName
} else {
pl := NewPlayer(playerName, nil)
pl.Player = playerID
g.AddPlayerL(pl)
}
}
for playerID := range g.Players {
if _, ok := p.Players[playerID]; !ok {
g.RemovePlayerL(playerID)
}
}
g.draw <- event.DrawMultiplayerMatrixes
g.processUpdateGameL(p)
}
case CommandStartGame:
if p, ok := e.(*GameCommandStartGame); ok {
@ -619,3 +601,30 @@ func (g *Game) handleLowerPiece() {
g.Unlock()
}
}
func (g *Game) processUpdateGame(gc *GameCommandUpdateGame) {
g.Lock()
defer g.Unlock()
g.processUpdateGameL(gc)
}
func (g *Game) processUpdateGameL(gc *GameCommandUpdateGame) {
for playerID, playerName := range gc.Players {
if existingPlayer, ok := g.Players[playerID]; ok {
existingPlayer.Name = playerName
} else {
pl := NewPlayer(playerName, nil)
pl.Player = playerID
g.AddPlayerL(pl)
}
}
for playerID := range g.Players {
if _, ok := gc.Players[playerID]; !ok {
g.RemovePlayerL(playerID)
}
}
g.draw <- event.DrawMultiplayerMatrixes
}

View File

@ -143,6 +143,8 @@ func (c Command) String() string {
const (
CommandUnknown Command = iota
CommandDisconnect
CommandPing
CommandPong
CommandNickname
CommandMessage
CommandNewGame
@ -182,6 +184,24 @@ type GameCommandInterface interface {
SetSource(int)
}
type GameCommandPing struct {
GameCommand
Message string
}
func (gc GameCommandPing) Command() Command {
return CommandPing
}
type GameCommandPong struct {
GameCommand
Message string
}
func (gc GameCommandPong) Command() Command {
return CommandPong
}
type GameCommandMessage struct {
GameCommand
Player int

View File

@ -83,9 +83,6 @@ func (s *Server) NewGame() (*Game, error) {
return nil, err
}
// TODO
//g.LogLevel = LogDebug
s.Games[gameID] = g
return g, nil
@ -206,6 +203,8 @@ func (s *Server) handleJoinGame(pl *Player) {
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)
@ -237,13 +236,14 @@ 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 {
if e.Command() != CommandUpdateMatrix || g.LogLevel >= LogVerbose {
c := e.Command()
if (c != CommandPing && c != CommandPong && c != CommandUpdateMatrix) || g.LogLevel >= LogVerbose {
s.Log("REMOTE handle game command ", e.Command(), " from ", e.Source(), e)
}
g.Lock()
switch e.Command() {
switch c {
case CommandMessage:
if p, ok := e.(*GameCommandMessage); ok {
if player, ok := g.Players[p.SourcePlayer]; ok {

View File

@ -27,6 +27,8 @@ type ServerConn struct {
out chan GameCommandInterface
ForwardOut chan GameCommandInterface
LastTransfer time.Time
Terminated bool
*sync.WaitGroup
@ -39,6 +41,8 @@ func NewServerConn(conn net.Conn, forwardOut chan GameCommandInterface) *ServerC
s.out = make(chan GameCommandInterface, CommandQueueSize)
s.ForwardOut = forwardOut
s.LastTransfer = time.Now()
if conn == nil {
// Local instance
@ -46,6 +50,7 @@ func NewServerConn(conn net.Conn, forwardOut chan GameCommandInterface) *ServerC
} else {
go s.handleRead()
go s.handleWrite()
go s.handleSendKeepAlive()
}
return &s
@ -81,6 +86,21 @@ func Connect(address string) *ServerConn {
}
}
func (s *ServerConn) handleSendKeepAlive() {
t := time.NewTicker(7 * time.Second)
for {
<-t.C
if s.Terminated {
t.Stop()
return
}
// TODO: Only send when necessary
s.Write(&GameCommandPing{})
}
}
func (s *ServerConn) Write(gc GameCommandInterface) {
if s == nil || s.Terminated {
return
@ -116,17 +136,40 @@ func (s *ServerConn) handleRead() {
}
var (
msg GameCommandTransport
gc GameCommandInterface
msg GameCommandTransport
gc GameCommandInterface
processed bool
)
scanner := bufio.NewScanner(s.Conn)
for scanner.Scan() {
processed = false
err := json.Unmarshal(scanner.Bytes(), &msg)
if err != nil {
panic(err)
}
if msg.Command == CommandMessage {
s.LastTransfer = time.Now()
if msg.Command == CommandPing {
var gameCommand GameCommandPing
err := json.Unmarshal(msg.Data, &gameCommand)
if err != nil {
panic(err)
}
s.Write(&GameCommandPong{Message: gameCommand.Message})
processed = true
} else if msg.Command == CommandPong {
var gameCommand GameCommandPong
err := json.Unmarshal(msg.Data, &gameCommand)
if err != nil {
panic(err)
}
// TODO: Verify
processed = true
} else if msg.Command == CommandMessage {
var gameCommand GameCommandMessage
err := json.Unmarshal(msg.Data, &gameCommand)
if err != nil {
@ -203,8 +246,10 @@ func (s *ServerConn) handleRead() {
continue
}
s.addSourceID(gc)
s.In <- gc
if !processed {
s.addSourceID(gc)
s.In <- gc
}
err = s.Conn.SetReadDeadline(time.Now().Add(ConnTimeout))
if err != nil {
@ -212,6 +257,8 @@ func (s *ServerConn) handleRead() {
return
}
}
s.Close()
}
func (s *ServerConn) handleWrite() {
@ -234,7 +281,17 @@ func (s *ServerConn) handleWrite() {
}
msg = GameCommandTransport{Command: e.Command()}
if p, ok := e.(*GameCommandMessage); ok {
if p, ok := e.(*GameCommandPing); ok {
msg.Data, err = json.Marshal(p)
if err != nil {
panic(err)
}
} else if p, ok := e.(*GameCommandPong); ok {
msg.Data, err = json.Marshal(p)
if err != nil {
panic(err)
}
} else if p, ok := e.(*GameCommandMessage); ok {
msg.Data, err = json.Marshal(p)
if err != nil {
panic(err)
@ -300,6 +357,8 @@ func (s *ServerConn) handleWrite() {
s.Close()
}
s.LastTransfer = time.Now()
err = s.Conn.SetWriteDeadline(time.Time{})
s.Done()
@ -334,18 +393,19 @@ func (s *ServerConn) JoinGame(name string, gameID int, logger chan string, draw
switch e.Command() {
case CommandMessage:
if p, ok := e.(*GameCommandMessage); ok {
if g != nil {
prefix := "* "
if p.Player > 0 {
name := "Anonymous"
if player, ok := g.Players[p.Player]; ok {
name = player.Name
}
prefix = "<" + name + "> "
prefix := "* "
if p.Player > 0 {
name := "Anonymous"
if player, ok := g.Players[p.Player]; ok {
name = player.Name
}
prefix = "<" + name + "> "
}
if g != nil {
g.Log(LogStandard, prefix+p.Message)
} else {
logger <- p.Message
logger <- prefix + p.Message
draw <- event.DrawMessages
}
}
@ -366,22 +426,7 @@ func (s *ServerConn) JoinGame(name string, gameID int, logger chan string, draw
}
if p, ok := e.(*GameCommandUpdateGame); ok {
// TODO Unify with JoinGame player update
g.Lock()
for playerID, playerName := range p.Players {
if existingPlayer, ok := g.Players[playerID]; ok {
existingPlayer.Name = playerName
} else {
pl := NewPlayer(playerName, nil)
pl.Player = playerID
g.AddPlayerL(pl)
}
}
g.Unlock()
} else {
log.Println(e.Command(), " - ", e)
panic("unknown payload")
g.processUpdateGame(p)
}
case CommandStartGame:
if p, ok := e.(*GameCommandStartGame); ok {
@ -397,10 +442,6 @@ func (s *ServerConn) JoinGame(name string, gameID int, logger chan string, draw
return g, nil
}
}
case CommandUpdateMatrix:
// Do nothing (missed join game command)
default:
log.Println("unnknown joingame command", e.Command(), e)
}
}

View File

@ -11,16 +11,18 @@ import (
"git.sr.ht/~tslocum/netris/pkg/event"
)
const GarbageDelayTicks = 3
const GarbageDelay = 1500 * time.Millisecond
type Matrix struct {
W, H, B int // TODO: Implement buffer zone
W int `json:"-"` // Width
H int `json:"-"` // Height
B int `json:"-"` // Buffer height
M map[int]Block // Matrix
O map[int]Block // Overlay
Bag *Bag `json:"-"`
P []*Piece // Pieces
Bag *Bag `json:"-"`
P *Piece
PlayerName string
Preview bool
@ -29,12 +31,12 @@ type Matrix struct {
Move chan int `json:"-"`
draw chan event.DrawObject `json:"-"`
Combo int
ComboStart time.Time
ComboEnd time.Time
PendingGarbage int
PendingGarbageTicks int
Speed int
Combo int
ComboStart time.Time `json:"-"`
ComboEnd time.Time `json:"-"`
PendingGarbage int `json:"-"`
PendingGarbageTime time.Time `json:"-"`
Speed int
GameOver bool
@ -111,11 +113,7 @@ func (m *Matrix) takePiece() bool {
p.Point = pieceStart
if m.P != nil {
m.P[0] = p
} else {
m.P = append(m.P, p)
}
m.P = p
return true
}
@ -285,7 +283,7 @@ func (m *Matrix) AddPendingGarbage(lines int) {
defer m.Unlock()
if m.PendingGarbage == 0 {
m.PendingGarbageTicks = GarbageDelayTicks
m.PendingGarbageTime = time.Now().Add(GarbageDelay)
}
m.PendingGarbage += lines
@ -297,8 +295,7 @@ func (m *Matrix) ReceiveGarbage() {
if m.PendingGarbage == 0 || m.GameOver {
return
} else if m.PendingGarbageTicks > 0 {
m.PendingGarbageTicks--
} else if time.Since(m.PendingGarbageTime) < 0 {
return
}
@ -309,10 +306,6 @@ func (m *Matrix) ReceiveGarbage() {
}
func (m *Matrix) addGarbage(lines int) bool {
if len(m.P) == 0 {
return false
}
for my := m.H + m.B; my >= 0; my-- {
for mx := 0; mx < m.W; mx++ {
if my >= m.H+m.B-lines && m.M[I(mx, my, m.W)] != BlockNone {
@ -334,18 +327,18 @@ func (m *Matrix) addGarbage(lines int) bool {
}
}
y := m.P[0].Y
y := m.P.Y
for {
if y == m.H+m.B {
return false
} else if m.canAddAt(m.P[0], Point{m.P[0].X, y}) {
} else if m.canAddAt(m.P, Point{m.P.X, y}) {
break
}
y++
}
m.P[0].Y = y
m.P.Y = y
m.Draw()
@ -385,7 +378,7 @@ func (m *Matrix) Reset() {
m.lands = nil
m.Speed = 0
m.PendingGarbage = 0
m.PendingGarbageTicks = 0
m.PendingGarbageTime = time.Time{}
m.Unlock()
m.Clear()
@ -415,11 +408,11 @@ func (m *Matrix) DrawPieces() {
func (m *Matrix) DrawPiecesL() {
m.clearOverlay()
if m.GameOver || len(m.P) < 1 {
if m.GameOver {
return
}
p := m.P[0]
p := m.P
if p == nil {
return
}
@ -527,15 +520,15 @@ func (m *Matrix) SetBlock(x int, y int, block Block, overlay bool) bool {
return true
}
func (m *Matrix) RotatePiece(player int, rotations int, direction int) bool {
func (m *Matrix) RotatePiece(rotations int, direction int) bool {
m.Lock()
defer m.Unlock()
if m.GameOver || rotations == 0 || len(m.P) == 0 {
if m.GameOver || rotations == 0 {
return false
}
p := m.P[player]
p := m.P
originalMino := make(Mino, len(p.Mino))
copy(originalMino, p.Mino)
@ -607,32 +600,32 @@ func (m *Matrix) LowerPiece() {
m.Lock()
defer m.Unlock()
if m.GameOver || len(m.P) == 0 {
if m.GameOver {
return
} else if m.canAddAt(m.P[0], Point{m.P[0].X, m.P[0].Y - 1}) {
m.movePiece(0, 0, -1)
} else if m.canAddAt(m.P, Point{m.P.X, m.P.Y - 1}) {
m.movePiece(0, -1)
} else {
m.landPiece()
}
}
func (m *Matrix) finishLandingPiece() {
if m.GameOver || len(m.P) == 0 || m.P[0].Landed {
if m.GameOver || m.P.Landed {
return
}
m.P[0].Landed = true
m.P.Landed = true
dropped := false
LANDPIECE:
for y := m.P[0].Y; y >= 0; y-- {
if y == 0 || !m.canAddAt(m.P[0], Point{m.P[0].X, y - 1}) {
for y := m.P.Y; y >= 0; y-- {
if y == 0 || !m.canAddAt(m.P, Point{m.P.X, y - 1}) {
for dropY := y - 1; dropY < m.H+m.B; dropY++ {
if !m.canAddAt(m.P[0], Point{m.P[0].X, dropY}) {
if !m.canAddAt(m.P, Point{m.P.X, dropY}) {
continue
}
err := m.add(m.P[0], m.P[0].Solid, Point{m.P[0].X, dropY}, false)
err := m.add(m.P, m.P.Solid, Point{m.P.X, dropY}, false)
if err != nil {
log.Fatalf("failed to add piece when landing piece: %+v", err)
}
@ -695,12 +688,12 @@ LANDPIECE:
remainingGarbage := sendGarbage
if m.PendingGarbage > 0 {
m.PendingGarbage -= sendGarbage
remainingGarbage = 0
if m.PendingGarbage <= 0 {
if m.PendingGarbage < 0 {
remainingGarbage = m.PendingGarbage * -1
m.PendingGarbage = 0
m.PendingGarbageTicks = 0
} else {
remainingGarbage = 0
}
}
@ -772,11 +765,7 @@ func (m *Matrix) CalculateBonusGarbage() int {
}
func (m *Matrix) landPiece() {
if len(m.P) == 0 {
return
}
p := m.P[0]
p := m.P
p.Lock()
if p.Landing || p.Landed || m.GameOver {
p.Unlock()
@ -824,29 +813,29 @@ func (m *Matrix) landPiece() {
}()
}
func (m *Matrix) MovePiece(player int, x int, y int) bool {
func (m *Matrix) MovePiece(x int, y int) bool {
m.Lock()
defer m.Unlock()
return m.movePiece(player, x, y)
return m.movePiece(x, y)
}
func (m *Matrix) movePiece(player int, x int, y int) bool {
if m.GameOver || len(m.P) == 0 || (x == 0 && y == 0) {
func (m *Matrix) movePiece(x int, y int) bool {
if m.GameOver || (x == 0 && y == 0) {
return false
}
px := m.P[player].X + x
py := m.P[player].Y + y
px := m.P.X + x
py := m.P.Y + y
if !m.canAddAt(m.P[player], Point{px, py}) {
if !m.canAddAt(m.P, Point{px, py}) {
return false
}
m.P[0].ApplyReset()
m.P[0].SetLocation(px, py)
m.P.ApplyReset()
m.P.SetLocation(px, py)
if !m.canAddAt(m.P[0], Point{m.P[0].X, m.P[0].Y - 1}) {
if !m.canAddAt(m.P, Point{m.P.X, m.P.Y - 1}) {
m.landPiece()
}
@ -867,7 +856,7 @@ func (m *Matrix) Moved() {
m.Move <- 0
}
func (m *Matrix) HardDropPiece(player int) {
func (m *Matrix) HardDropPiece() {
m.Lock()
defer m.Unlock()
@ -880,28 +869,9 @@ func (m *Matrix) Replace(newmtx *Matrix) {
m.W, m.H, m.B = newmtx.W, newmtx.H, newmtx.B
m.M = newmtx.M
/*for i := range newmtx.P {
// TODO
if newmtx.P[i].Mutex == nil {
if i < len(m.P) {
m.P[i].Lock()
newmtx.P[i].Mutex = m.P[i].Mutex
} else {
newmtx.P[i].Mutex = new(sync.Mutex)
newmtx.P[i].Lock()
}
}
}*/
m.P = newmtx.P
/*for i := range m.P {
m.P[i].Unlock()
}*/
m.Preview = newmtx.Preview
m.Speed = newmtx.Speed
// TODO: Show opponents pending garbage
}
func fibonacci(value int) int {

View File

@ -10,29 +10,68 @@ func TestMatrix(t *testing.T) {
t.Error(err)
}
err = m.Add(m.P[0], BlockSolidBlue, Point{3, 3}, false)
m.AddTestBlocks()
ok := m.SetBlock(9, 0, BlockSolidMagenta, false)
if !ok {
t.Error("failed to set final block after test blocks")
}
ok = m.SetBlock(9, 1, BlockSolidMagenta, false)
if !ok {
t.Error("failed to set final block after test blocks")
}
ok = m.SetBlock(9, 3, BlockSolidMagenta, false)
if !ok {
t.Error("failed to set final block after test blocks")
}
cleared := m.clearFilled()
if cleared != 3 {
t.Errorf("failed to clear lines, wanted 3 got %d", cleared)
}
m.Clear()
err = m.Add(m.P, BlockSolidBlue, Point{3, 3}, false)
if err != nil {
t.Errorf("failed to add initial mino to matrix: %s", err)
}
err = m.Add(m.P[0], BlockSolidBlue, Point{3, 3}, false)
err = m.Add(m.P, BlockSolidBlue, Point{3, 3}, false)
if err == nil {
t.Error("failed to detect collision when adding second mino to matrix")
}
err = m.Add(m.P[0], BlockSolidBlue, Point{9, 9}, false)
err = m.Add(m.P, BlockSolidBlue, Point{9, 9}, false)
if err == nil {
t.Error("failed to detect out of bounds when adding third mino to matrix")
}
m.Clear()
for i := 0; i < 8; i++ {
ok := m.RotatePiece(0, 1, 0)
for i := 0; i < 11; i++ {
ok := m.RotatePiece(1, 0)
if !ok {
t.Errorf("failed to rotate piece on iteration %d", i)
}
}
// TODO: Add rotate and wall kick tests
for i := 0; i < 4; i++ {
ok := m.movePiece(1, 0)
if !ok {
t.Errorf("failed to move piece on iteration %d", i)
}
}
ok = m.RotatePiece(1, 0)
if !ok {
t.Errorf("failed to rotate piece for right wall kick")
}
for i := 0; i < 7; i++ {
ok := m.movePiece(-1, 0)
if !ok {
t.Errorf("failed to move piece on iteration %d", i)
}
}
}

View File

@ -1,20 +0,0 @@
package mino
import (
"testing"
)
func TestPiece(t *testing.T) {
minos, err := Generate(4)
if err != nil {
t.Errorf("failed to generate minos for rank %d: %s", 4, err)
}
if len(minos) != 7 {
t.Errorf("failed to generate minos for rank %d: unexpected number of minos generated", 4)
}
p := NewPiece(minos[2], Point{0, 0})
// TODO
_ = p
}