Improve rendering process

This commit is contained in:
Trevor Slocum 2019-10-17 17:14:48 -07:00
parent f09ceab006
commit 92273d1c31
15 changed files with 236 additions and 253 deletions

View File

@ -5,8 +5,6 @@
Multiplayer Tetris clone
This project is not yet stable. Feedback is welcome.
![](https://netris.rocketnine.space/static/screenshot2.png)
## Demo
@ -30,22 +28,6 @@ go get -u git.sr.ht/~tslocum/netris/cmd/netris-server
See [CONFIGURATION.md](https://man.sr.ht/~tslocum/netris/CONFIGURATION.md)
## Play
A single player game may be played by launching without any options.
To play online, connect to the official server:
```netris --nick <name> --connect netris.rocketnine.space```
To host a private game, start a dedicated server:
```netris-server --listen-tcp :1984```
Then, connect with:
```netris --nick <name> --connect ip.or.dns.address:1984```
## Support
Please share suggestions/issues [here](https://todo.sr.ht/~tslocum/netris).

View File

@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"log"
"os"
"sort"
"strconv"
"strings"
@ -60,6 +61,8 @@ var (
nicknameDraft string
inputHeight, mainHeight, newLogLines int
profileCPU *os.File
)
const DefaultStatusText = "Press Enter to chat, Z/X to rotate, arrow keys or HJKL to move/drop"
@ -132,6 +135,7 @@ func initGUI() (*tview.Application, error) {
side.SetDynamicColors(true)
buffer = tview.NewTextView().
SetScrollable(false).
SetTextAlign(tview.AlignLeft).
SetWrap(false).
SetWordWrap(false)
@ -188,16 +192,19 @@ func initGUI() (*tview.Application, error) {
}
titleName = tview.NewTextView().
SetScrollable(false).
SetTextAlign(tview.AlignLeft).
SetWrap(false).
SetWordWrap(false).SetDynamicColors(true)
titleL = tview.NewTextView().
SetScrollable(false).
SetTextAlign(tview.AlignLeft).
SetWrap(false).
SetWordWrap(false).SetDynamicColors(true)
titleR = tview.NewTextView().
SetScrollable(false).
SetTextAlign(tview.AlignLeft).
SetWrap(false).
SetWordWrap(false).SetDynamicColors(true)
@ -435,15 +442,6 @@ func handleDraw() {
case event.DrawMessages:
app.QueueUpdateDraw(renderRecentMessages)
default:
DRAW:
for {
select {
case <-draw:
default:
break DRAW
}
}
app.QueueUpdateDraw(drawAll)
}
}
@ -510,28 +508,28 @@ func renderPreviewMatrix() {
comboTime float64
combo int
)
if m.Combo > 0 && time.Since(m.ComboEnd) < 0 {
comboTime = 1.0 + (float64(m.ComboEnd.Sub(time.Now())) / 1000000000)
if m.Combo > 0 && time.Until(m.ComboEnd) > 0 {
comboTime = 1.0 + (float64(time.Until(m.ComboEnd)) / 1000000000)
combo = m.Combo
}
m.Unlock()
side.Clear()
side.Write(renderMatrix(g.Players[g.LocalPlayer].Preview))
m.Lock()
renderLock.Lock()
renderBuffer.Reset()
var speed = strconv.Itoa(m.Speed)
if m.Speed < 100 {
renderBuffer.WriteRune(' ')
speed = " " + speed
}
renderBuffer.WriteString(strconv.Itoa(m.Speed))
renderLock.Lock()
renderMatrix(g.Players[g.LocalPlayer].Preview)
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()))
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 {
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()))
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))
}
side.Clear()
side.Write(renderBuffer.Bytes())
renderLock.Unlock()
m.Unlock()
}
@ -542,8 +540,11 @@ func renderPlayerMatrix() {
return
}
renderLock.Lock()
renderMatrix(g.Players[g.LocalPlayer].Matrix)
mtx.Clear()
mtx.Write(renderMatrix(g.Players[g.LocalPlayer].Matrix))
mtx.Write(renderBuffer.Bytes())
renderLock.Unlock()
}
func renderMultiplayerMatrix() {
@ -591,23 +592,23 @@ func renderMultiplayerMatrix() {
g.Unlock()
renderLock.Lock()
renderMatrixes(matrixes)
buffer.Clear()
buffer.Write(renderMatrixes(matrixes))
buffer.Write(renderBuffer.Bytes())
renderLock.Unlock()
}
func renderMatrix(m *mino.Matrix) []byte {
if m == nil {
return nil
}
func renderMatrix(m *mino.Matrix) {
renderBuffer.Reset()
renderLock.Lock()
defer renderLock.Unlock()
if m == nil {
return
}
m.Lock()
defer m.Unlock()
renderBuffer.Reset()
m.DrawPiecesL()
bs := blockSize
@ -647,14 +648,14 @@ func renderMatrix(m *mino.Matrix) []byte {
renderBuffer.Write(renderVLine)
}
if y != 0 || m.Type == mino.MatrixStandard {
if y != 0 || m.Type != mino.MatrixCustom {
renderBuffer.WriteRune('\n')
}
}
}
if m.Type != mino.MatrixStandard {
return renderBuffer.Bytes()
return
}
renderBuffer.Write(renderLLCorner)
@ -665,8 +666,6 @@ func renderMatrix(m *mino.Matrix) []byte {
renderBuffer.WriteRune('\n')
renderPlayerDetails(m, bs)
return renderBuffer.Bytes()
}
func renderPlayerDetails(m *mino.Matrix, bs int) {
@ -695,21 +694,18 @@ func renderPlayerDetails(m *mino.Matrix, bs int) {
}
}
func renderMatrixes(mx []*mino.Matrix) []byte {
if mx == nil {
return nil
}
func renderMatrixes(mx []*mino.Matrix) {
renderBuffer.Reset()
renderLock.Lock()
defer renderLock.Unlock()
if mx == nil {
return
}
for i := range mx {
mx[i].Lock()
mx[i].DrawPiecesL()
}
renderBuffer.Reset()
div := " "
height := mx[0].H
@ -767,8 +763,6 @@ func renderMatrixes(mx []*mino.Matrix) []byte {
for i := range mx {
mx[i].Unlock()
}
return renderBuffer.Bytes()
}
func logMessage(message string) {

View File

@ -1,6 +1,12 @@
package main
import (
"fmt"
"log"
"os"
"runtime/pprof"
"strings"
"git.sr.ht/~tslocum/netris/pkg/event"
"github.com/gdamore/tcell"
)
@ -110,10 +116,39 @@ func handleKeypress(ev *tcell.EventKey) *tcell.EventKey {
if k == tcell.KeyEnter {
msg := inputView.GetText()
if msg != "" {
if activeGame != nil {
activeGame.Event <- &event.MessageEvent{Message: msg}
if strings.HasPrefix(msg, "/cpu") {
if profileCPU == nil {
if len(msg) < 5 {
logMessage("Profile name must be specified")
} else {
profileName := strings.TrimSpace(msg[5:])
var err error
profileCPU, err = os.Create(profileName)
if err != nil {
log.Fatal(err)
}
err = pprof.StartCPUProfile(profileCPU)
if err != nil {
log.Fatal(err)
}
logMessage(fmt.Sprintf("Started profiling CPU usage as %s", profileName))
}
} else {
pprof.StopCPUProfile()
profileCPU.Close()
profileCPU = nil
logMessage("Stopped profiling CPU usage")
}
} else {
logMessage("Message not sent - not currently connected to any game")
if activeGame != nil {
activeGame.Event <- &event.MessageEvent{Message: msg}
} else {
logMessage("Message not sent - not currently connected to any game")
}
}
}
@ -172,66 +207,66 @@ func handleKeypress(ev *tcell.EventKey) *tcell.EventKey {
setShowDetails(!showDetails)
case tcell.KeyEscape:
setTitleVisible(true)
}
default:
switch r {
case 'z', 'Z':
if activeGame == nil {
return ev
}
switch r {
case 'z', 'Z':
if activeGame == nil {
return ev
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.RotatePiece(1, 1)
activeGame.Unlock()
return nil
case 'x', 'X':
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.RotatePiece(1, 0)
activeGame.Unlock()
return nil
case 'h', 'H':
if activeGame == nil {
return ev
}
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.MovePiece(-1, 0)
activeGame.Unlock()
return nil
case 'j', 'J':
if activeGame == nil {
return ev
}
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.MovePiece(0, -1)
activeGame.Unlock()
return nil
case 'k', 'K':
if activeGame == nil {
return ev
}
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.HardDropPiece()
activeGame.Unlock()
return nil
case 'l', 'L':
if activeGame == nil {
return ev
}
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.MovePiece(1, 0)
activeGame.Unlock()
return nil
}
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.RotatePiece(1, 1)
activeGame.Unlock()
return nil
case 'x', 'X':
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.RotatePiece(1, 0)
activeGame.Unlock()
return nil
case 'h', 'H':
if activeGame == nil {
return ev
}
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.MovePiece(-1, 0)
activeGame.Unlock()
return nil
case 'j', 'J':
if activeGame == nil {
return ev
}
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.MovePiece(0, -1)
activeGame.Unlock()
return nil
case 'k', 'K':
if activeGame == nil {
return ev
}
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.HardDropPiece()
activeGame.Unlock()
return nil
case 'l', 'L':
if activeGame == nil {
return ev
}
activeGame.Lock()
activeGame.Players[activeGame.LocalPlayer].Matrix.MovePiece(1, 0)
activeGame.Unlock()
return nil
}
return ev

View File

@ -7,6 +7,9 @@ import (
)
func TestRenderMatrix(t *testing.T) {
renderLock.Lock()
defer renderLock.Unlock()
blockSize = 1
m, err := mino.NewTestMatrix()
@ -16,13 +19,13 @@ func TestRenderMatrix(t *testing.T) {
m.AddTestBlocks()
var renderedMatrix []byte
renderedMatrix = renderMatrix(m)
_ = renderedMatrix
renderMatrix(m)
}
func BenchmarkRenderStandardMatrix(b *testing.B) {
renderLock.Lock()
defer renderLock.Unlock()
blockSize = 1
m, err := mino.NewTestMatrix()
@ -32,18 +35,18 @@ func BenchmarkRenderStandardMatrix(b *testing.B) {
m.AddTestBlocks()
var renderedMatrix []byte
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
renderedMatrix = renderMatrix(m)
renderMatrix(m)
}
_ = renderedMatrix
}
func BenchmarkRenderLargeMatrix(b *testing.B) {
renderLock.Lock()
defer renderLock.Unlock()
blockSize = 2
m, err := mino.NewTestMatrix()
@ -53,15 +56,12 @@ func BenchmarkRenderLargeMatrix(b *testing.B) {
m.AddTestBlocks()
var renderedMatrix []byte
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
renderedMatrix = renderMatrix(m)
renderMatrix(m)
}
_ = renderedMatrix
blockSize = 1
}

View File

@ -12,13 +12,17 @@ import (
)
var (
playerForm *tview.Form
titleVisible bool
titleScreen int
titleSelectedButton int
drawTitle = make(chan struct{}, game.CommandQueueSize)
titleMatrixL = newTitleMatrixSide()
titleMatrix = newTitleMatrixName()
titleMatrixR = newTitleMatrixSide()
titlePiecesL []*mino.Piece
titlePiecesR []*mino.Piece
buttonA *tview.Button
buttonB *tview.Button
buttonC *tview.Button
@ -26,12 +30,6 @@ var (
buttonLabelA *tview.TextView
buttonLabelB *tview.TextView
buttonLabelC *tview.TextView
titleMatrixL = newTitleMatrixSide()
titleMatrix = newTitleMatrixName()
titleMatrixR = newTitleMatrixSide()
titlePiecesL []*mino.Piece
titlePiecesR []*mino.Piece
)
func previousTitleButton() {
@ -205,14 +203,21 @@ func renderTitle() {
titleMatrix.M[i] = newBlock
}
renderLock.Lock()
renderMatrix(titleMatrix)
titleName.Clear()
titleName.Write(renderMatrix(titleMatrix))
titleName.Write(renderBuffer.Bytes())
renderMatrix(titleMatrixL)
titleL.Clear()
titleL.Write(renderMatrix(titleMatrixL))
titleL.Write(renderBuffer.Bytes())
renderMatrix(titleMatrixR)
titleR.Clear()
titleR.Write(renderMatrix(titleMatrixR))
titleR.Write(renderBuffer.Bytes())
renderLock.Unlock()
}
func newTitleMatrixSide() *mino.Matrix {

View File

@ -23,8 +23,7 @@ import (
)
var (
ready = make(chan bool)
done = make(chan bool)
done = make(chan bool)
activeGame *game.Game

4
go.mod
View File

@ -9,9 +9,9 @@ require (
github.com/gdamore/tcell v1.3.0
github.com/gliderlabs/ssh v0.2.2
github.com/mattn/go-isatty v0.0.10
github.com/rivo/tview v0.0.0-20190829161255-f8bc69b90341
github.com/rivo/tview v0.0.0-20191017100741-c35e6b2b4c98
github.com/rivo/uniseg v0.1.0 // indirect
golang.org/x/crypto v0.0.0-20191011161858-a950601f39e6
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
golang.org/x/text v0.3.2 // indirect
)

8
go.sum
View File

@ -20,15 +20,15 @@ github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW1
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/rivo/tview v0.0.0-20190829161255-f8bc69b90341 h1:d2Z5U4d3fenPRFFweaMCogbXiRywM5kgYtu20/hol3M=
github.com/rivo/tview v0.0.0-20190829161255-f8bc69b90341/go.mod h1:+rKjP5+h9HMwWRpAfhIkkQ9KE3m3Nz5rwn7YtUpwgqk=
github.com/rivo/tview v0.0.0-20191017100741-c35e6b2b4c98 h1:nPfVK45RSX656/m0SXLl0GfhQbBgIUjojrbVxpOA8Ak=
github.com/rivo/tview v0.0.0-20191017100741-c35e6b2b4c98/go.mod h1:+rKjP5+h9HMwWRpAfhIkkQ9KE3m3Nz5rwn7YtUpwgqk=
github.com/rivo/uniseg v0.0.0-20190513083848-b9f5b9457d44 h1:XKCbzPvK4/BbMXoMJOkYP2ANxiAEO0HM1xn6psSbXxY=
github.com/rivo/uniseg v0.0.0-20190513083848-b9f5b9457d44/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011161858-a950601f39e6 h1:w8KIlm7HkDEQx6EtcrA+tJK38f+NJTDOKKFSM96fwBA=
golang.org/x/crypto v0.0.0-20191011161858-a950601f39e6/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=

View File

@ -145,30 +145,6 @@ func (s *Server) FindGame(p *Player, gameID int) *Game {
return g
}
func (s *Server) joinGame(p *Player, g *Game) {
var notified bool
for {
if p.Terminated {
return
}
g.Lock()
if !g.Started {
break
} else if !notified {
p.Write(&GameCommandMessage{Message: "Game in progress, waiting to join next game.."})
}
g.Unlock()
time.Sleep(500 * time.Millisecond)
}
if !g.Starting {
}
g.Unlock()
}
func (s *Server) accept() {
for {
np := <-s.NewPlayers
@ -182,16 +158,14 @@ func (s *Server) accept() {
}
func (s *Server) handleJoinGame(pl *Player) {
s.Log("waiting first msg handle join game ")
for e := range pl.In {
s.Log("handle join game ", e.Command(), e)
if e.Command() == CommandJoinGame {
if p, ok := e.(*GameCommandJoinGame); ok {
pl.Name = Nickname(p.Name)
g := s.FindGame(pl, p.GameID)
s.Log("New player added to game", *pl, p.GameID)
s.Logf("Adding %s to game %d", pl.Name, p.GameID)
go s.handleGameCommands(pl, g)
return
@ -298,7 +272,7 @@ func (s *Server) Listen(address string) {
listener, err := net.Listen(network, address)
if err != nil {
log.Fatal("Listen error: ", err)
log.Fatalf("failed to listen on %s: %s", address, err)
}
s.listeners = append(s.listeners, listener)

View File

@ -366,9 +366,7 @@ func (s *ServerConn) handleWrite() {
}
s.LastTransfer = time.Now()
err = s.Conn.SetWriteDeadline(time.Time{})
s.Conn.SetWriteDeadline(time.Time{})
s.Done()
}
}

View File

@ -46,37 +46,38 @@ func (s *SSHServer) Host(newPlayers chan<- *game.IncomingPlayer) {
Addr: s.ListenAddress,
IdleTimeout: ServerIdleTimeout,
Handler: func(sshSession ssh.Session) {
ctx := sshSession.Context()
cmdCtx, cancelCmd := context.WithCancel(ctx)
cmd := exec.CommandContext(cmdCtx, s.NetrisBinary, "--nick", game.Nickname(sshSession.User()), "--connect", s.NetrisAddress)
ptyReq, winCh, isPty := sshSession.Pty()
if isPty {
cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", ptyReq.Term))
if !isPty {
io.WriteString(sshSession, "failed to start netris: non-interactive terminals are not supported\n")
f, err := pty.Start(cmd)
if err != nil {
panic(err)
}
defer f.Close()
go func() {
for win := range winCh {
setWinsize(f, win.Width, win.Height)
}
}()
go func() {
io.Copy(f, sshSession)
}()
io.Copy(sshSession, f)
cancelCmd()
cmd.Wait()
} else {
io.WriteString(sshSession, "No PTY requested.\n")
sshSession.Exit(1)
return
}
cmdCtx, cancelCmd := context.WithCancel(sshSession.Context())
cmd := exec.CommandContext(cmdCtx, s.NetrisBinary, "--nick", game.Nickname(sshSession.User()), "--connect", s.NetrisAddress)
cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", ptyReq.Term))
f, err := pty.Start(cmd)
if err != nil {
panic(err)
}
defer f.Close()
go func() {
for win := range winCh {
setWinsize(f, win.Width, win.Height)
}
}()
go func() {
io.Copy(f, sshSession)
}()
io.Copy(sshSession, f)
cancelCmd()
cmd.Wait()
},
PtyCallback: func(ctx ssh.Context, pty ssh.Pty) bool {
// TODO: Compare public key

View File

@ -52,7 +52,7 @@ func (b *Bag) Next() Mino {
func (b *Bag) shuffle() {
if b.Minos == nil {
b.Minos = make([]Mino, len(b.Original), len(b.Original))
b.Minos = make([]Mino, len(b.Original))
}
copy(b.Minos, b.Original)

View File

@ -27,7 +27,7 @@ type Matrix struct {
B int `json:"-"` // Buffer height
M map[int]Block // Matrix
O map[int]Block // Overlay
O map[int]Block `json:"-"` // Overlay
Bag *Bag `json:"-"`
P *Piece
@ -35,9 +35,9 @@ type Matrix struct {
Type MatrixType
Event chan<- interface{} `json:"-"`
Move chan int `json:"-"`
draw chan event.DrawObject `json:"-"`
Event chan<- interface{} `json:"-"`
Move chan int `json:"-"`
draw chan event.DrawObject
Combo int
ComboStart time.Time `json:"-"`
@ -52,7 +52,7 @@ type Matrix struct {
GameOver bool
lands []time.Time `json:"-"`
lands []time.Time
sync.Mutex `json:"-"`
}
@ -630,11 +630,11 @@ func (m *Matrix) LowerPiece() {
}
func (m *Matrix) finishLandingPiece() {
if m.GameOver || m.P.Landed {
if m.GameOver || m.P.landed {
return
}
m.P.Landed = true
m.P.landed = true
dropped := false
LANDPIECE:
@ -738,7 +738,7 @@ func (m *Matrix) addToCombo(lines int) int {
baseTime := 2.4
bonusTime := baseTime / 2
if time.Now().Sub(m.ComboEnd) > 0 {
if time.Until(m.ComboEnd) <= 0 {
m.Combo = 0
}
@ -787,32 +787,31 @@ func (m *Matrix) CalculateBonusGarbage() int {
func (m *Matrix) landPiece() {
p := m.P
p.Lock()
if p.Landing || p.Landed || m.GameOver {
if p.landing || p.landed || m.GameOver {
p.Unlock()
return
}
p.Landing = true
p.landing = true
p.Unlock()
go func() {
landStart := time.Now()
var t *time.Ticker
t = time.NewTicker(100 * time.Millisecond)
t := time.NewTicker(100 * time.Millisecond)
for {
<-t.C
m.Lock()
p.Lock()
if p.Landed {
if p.landed {
p.Unlock()
m.Unlock()
return
}
if p.Resets > 0 && time.Since(p.LastReset) < 500*time.Millisecond {
if p.resets > 0 && time.Since(p.lastReset) < 500*time.Millisecond {
p.Unlock()
m.Unlock()
continue

View File

@ -59,7 +59,7 @@ func TestMatrix(t *testing.T) {
for i := 0; i < 4; i++ {
ok := m.movePiece(1, 0)
if !ok {
t.Errorf("failed to move piece on iteration %d", i)
t.Errorf("failed to Move piece on iteration %d", i)
}
}
@ -71,7 +71,7 @@ func TestMatrix(t *testing.T) {
for i := 0; i < 7; i++ {
ok := m.movePiece(-1, 0)
if !ok {
t.Errorf("failed to move piece on iteration %d", i)
t.Errorf("failed to Move piece on iteration %d", i)
}
}
}

View File

@ -75,21 +75,17 @@ var AllRotationOffsets = map[PieceType][]RotationOffsets{
type Piece struct {
Point
Mino
Original Mino
Ghost Block
Solid Block
Rotation int
Ghost Block
Solid Block
Rotation int
PivotsCW []Point
PivotsCCW []Point
Color int
Landing bool
Resets int
LastReset time.Time
Landed bool
original Mino
pivotsCW []Point
pivotsCCW []Point
resets int
lastReset time.Time
landing bool
landed bool
sync.Mutex `json:"-"`
}
@ -101,7 +97,7 @@ func (p *Piece) String() string {
}
func NewPiece(m Mino, loc Point) *Piece {
p := &Piece{Mino: m, Original: m, Point: loc, Color: 0}
p := &Piece{Mino: m, original: m, Point: loc}
var pieceType PieceType
switch m.Canonical().String() {
@ -138,8 +134,8 @@ func NewPiece(m Mino, loc Point) *Piece {
p.Ghost = BlockGhostYellow
}
p.PivotsCW = AllRotationPivotsCW[pieceType]
p.PivotsCCW = AllRotationPivotsCCW[pieceType]
p.pivotsCW = AllRotationPivotsCW[pieceType]
p.pivotsCCW = AllRotationPivotsCCW[pieceType]
return p
}
@ -179,11 +175,11 @@ func (p *Piece) Rotate(rotations int, direction int) Mino {
}
if (rotationPivot == 3 && direction == 0) || (rotationPivot == 1 && direction == 1) {
newMino = p.Original
newMino = p.original
} else {
pp := p.PivotsCW[rotationPivot%RotationStates]
pp := p.pivotsCW[rotationPivot%RotationStates]
if direction == 1 {
pp = p.PivotsCCW[rotationPivot%RotationStates]
pp = p.pivotsCCW[rotationPivot%RotationStates]
}
px, py := pp.X, pp.Y
@ -207,12 +203,12 @@ func (p *Piece) ApplyReset() {
p.Lock()
defer p.Unlock()
if !p.Landing || p.Resets >= 15 {
if !p.landing || p.resets >= 15 {
return
}
p.Resets++
p.LastReset = time.Now()
p.resets++
p.lastReset = time.Now()
}
func (p *Piece) ApplyRotation(rotations int, direction int) {