995 lines
15 KiB
Go
995 lines
15 KiB
Go
package mino
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"math"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"gitlab.com/tslocum/netris/pkg/event"
|
|
)
|
|
|
|
const (
|
|
GarbageDelay = 1500 * time.Millisecond // 1.5 seconds
|
|
ComboBaseTime = 2.4 // Seconds
|
|
)
|
|
|
|
type MatrixType int
|
|
|
|
const (
|
|
MatrixStandard MatrixType = iota
|
|
MatrixPreview
|
|
MatrixCustom
|
|
)
|
|
|
|
type Matrix struct {
|
|
W int `json:"-"` // Width
|
|
H int `json:"-"` // Height
|
|
B int `json:"-"` // Buffer height
|
|
|
|
M []Block // Matrix
|
|
O []Block `json:"-"` // Overlay
|
|
|
|
Bag *Bag `json:"-"`
|
|
P *Piece
|
|
PlayerName string `json:"pn,omitempty"`
|
|
|
|
Type MatrixType `json:"ty,omitempty"`
|
|
|
|
Event chan<- interface{} `json:"-"`
|
|
Move chan int `json:"-"`
|
|
draw chan event.DrawObject
|
|
|
|
Combo int `json:"mc,omitempty"`
|
|
ComboStart time.Time `json:"-"`
|
|
ComboEnd time.Time `json:"-"`
|
|
PendingGarbage int `json:"-"`
|
|
PendingGarbageTime time.Time `json:"-"`
|
|
|
|
LinesCleared int `json:"lc,omitempty"`
|
|
GarbageSent int `json:"gs,omitempty"`
|
|
GarbageReceived int `json:"gr,omitempty"`
|
|
Speed int `json:"sp,omitempty"`
|
|
|
|
GameOver bool `json:"go,omitempty"`
|
|
|
|
lands []time.Time
|
|
|
|
sync.Mutex `json:"-"`
|
|
}
|
|
|
|
func I(x int, y int, w int) int {
|
|
if x < 0 || x >= w || y < 0 {
|
|
log.Panicf("failed to retrieve index for %d,%d width %d: invalid coordinates", x, y, w)
|
|
}
|
|
|
|
return (y * w) + x
|
|
}
|
|
|
|
func NewMatrix(w int, h int, b int, players int, event chan<- interface{}, draw chan event.DrawObject, t MatrixType) *Matrix {
|
|
m := Matrix{
|
|
Type: t,
|
|
W: w,
|
|
H: h,
|
|
B: b,
|
|
M: make([]Block, w*(h+b)),
|
|
O: make([]Block, w*(h+b)),
|
|
Event: event,
|
|
Move: make(chan int, 10),
|
|
draw: draw,
|
|
}
|
|
|
|
return &m
|
|
}
|
|
|
|
func (m *Matrix) HandleReceiveGarbage() {
|
|
t := time.NewTicker(500 * time.Millisecond)
|
|
for {
|
|
<-t.C
|
|
|
|
m.ReceiveGarbage()
|
|
}
|
|
}
|
|
|
|
func (m *Matrix) AttachBag(bag *Bag) bool {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
m.Bag = bag
|
|
|
|
return true
|
|
}
|
|
|
|
func (m *Matrix) takePiece() bool {
|
|
if m.Type != MatrixStandard {
|
|
return true
|
|
} else if m.GameOver || m.Bag == nil {
|
|
return false
|
|
}
|
|
|
|
p := NewPiece(m.Bag.Take(), Point{0, 0})
|
|
|
|
spawn := m.SpawnLocation(p)
|
|
if spawn.X < 0 || spawn.Y < 0 {
|
|
return false
|
|
}
|
|
p.Point = spawn
|
|
|
|
m.P = p
|
|
|
|
m.lowerPiece()
|
|
|
|
return true
|
|
}
|
|
|
|
func (m *Matrix) TakePiece() bool {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
return m.takePiece()
|
|
}
|
|
|
|
func (m *Matrix) CanAddAt(mn *Piece, loc Point) bool {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
return m.canAddAt(mn, loc)
|
|
}
|
|
|
|
func (m *Matrix) canAddAt(mn *Piece, loc Point) bool {
|
|
if m.GameOver || loc.Y < 0 {
|
|
return false
|
|
}
|
|
|
|
var (
|
|
x, y int
|
|
index int
|
|
)
|
|
|
|
for _, p := range mn.Mino {
|
|
x = p.X + loc.X
|
|
y = p.Y + loc.Y
|
|
|
|
if x < 0 || x >= m.W || y < 0 || y >= m.H+m.B {
|
|
return false
|
|
}
|
|
|
|
index = I(x, y, m.W)
|
|
if m.M[index] != BlockNone {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (m *Matrix) CanAdd(mn *Piece) bool {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
if m.GameOver {
|
|
return false
|
|
}
|
|
|
|
var (
|
|
x, y int
|
|
index int
|
|
)
|
|
|
|
for _, p := range mn.Mino {
|
|
x = p.X + mn.X
|
|
y = p.Y + mn.Y
|
|
|
|
if x < 0 || x >= m.W || y < 0 || y >= m.H+m.B {
|
|
return false
|
|
}
|
|
|
|
index = I(x, y, m.W)
|
|
if m.M[index] != BlockNone {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (m *Matrix) Add(mn *Piece, b Block, loc Point, overlay bool) error {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
return m.add(mn, b, loc, overlay)
|
|
}
|
|
|
|
func (m *Matrix) add(mn *Piece, b Block, loc Point, overlay bool) error {
|
|
if m.GameOver {
|
|
return nil
|
|
}
|
|
|
|
var (
|
|
x, y int
|
|
index int
|
|
|
|
M []Block
|
|
)
|
|
|
|
if overlay {
|
|
M = m.O
|
|
} else {
|
|
M = m.M
|
|
}
|
|
|
|
for _, p := range mn.Mino {
|
|
x = p.X + loc.X
|
|
y = p.Y + loc.Y
|
|
|
|
if x < 0 || x >= m.W || y < 0 || y >= m.H+m.B {
|
|
return fmt.Errorf("failed to add to matrix at %s: point %s out of bounds (%d, %d)", loc, p, x, y)
|
|
}
|
|
|
|
index = I(x, y, m.W)
|
|
if !overlay && M[index] != BlockNone {
|
|
return fmt.Errorf("failed to add to matrix at %s: point %s already contains %s", loc, p, M[index])
|
|
}
|
|
|
|
M[index] = b
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Matrix) Empty(loc Point) bool {
|
|
index := I(loc.X, loc.Y, m.W)
|
|
return m.M[index] == BlockNone
|
|
}
|
|
|
|
func (m *Matrix) LineFilled(y int) bool {
|
|
for x := 0; x < m.W; x++ {
|
|
if m.Empty(Point{x, y}) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (m *Matrix) ClearFilled() int {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
return m.clearFilled()
|
|
}
|
|
|
|
func (m *Matrix) clearFilled() int {
|
|
cleared := 0
|
|
|
|
for y := 0; y < (m.H+m.B)-1; y++ {
|
|
for {
|
|
if m.LineFilled(y) {
|
|
for my := y + 1; my < (m.H+m.B)-1; my++ {
|
|
for mx := 0; mx < m.W; mx++ {
|
|
m.M[I(mx, my-1, m.W)] = m.M[I(mx, my, m.W)]
|
|
}
|
|
}
|
|
|
|
cleared++
|
|
continue
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
return cleared
|
|
}
|
|
|
|
func (m *Matrix) AddPendingGarbage(lines int) {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
if m.PendingGarbage == 0 {
|
|
m.PendingGarbageTime = time.Now().Add(GarbageDelay)
|
|
}
|
|
|
|
m.PendingGarbage += lines
|
|
}
|
|
|
|
func (m *Matrix) ReceiveGarbage() {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
if m.PendingGarbage == 0 || m.GameOver {
|
|
return
|
|
} else if time.Since(m.PendingGarbageTime) < 0 {
|
|
return
|
|
}
|
|
|
|
m.PendingGarbage--
|
|
if !m.addGarbage(1) {
|
|
m.Event <- &event.GameOverEvent{}
|
|
}
|
|
}
|
|
|
|
func (m *Matrix) addGarbage(lines int) bool {
|
|
for my := (m.H + m.B) - 1; my >= 0; my-- {
|
|
for mx := 0; mx < m.W; mx++ {
|
|
if my >= (m.H+m.B-1)-lines {
|
|
if m.M[I(mx, my, m.W)] != BlockNone {
|
|
return false
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
m.M[I(mx, my+lines, m.W)] = m.M[I(mx, my, m.W)]
|
|
}
|
|
}
|
|
|
|
for my := 0; my < lines; my++ {
|
|
hole := m.Bag.GarbageHole()
|
|
for mx := 0; mx < m.W; mx++ {
|
|
if mx == hole {
|
|
m.M[I(mx, my, m.W)] = BlockNone
|
|
} else {
|
|
m.M[I(mx, my, m.W)] = BlockGarbage
|
|
}
|
|
}
|
|
}
|
|
|
|
y := m.P.Y
|
|
for {
|
|
if y == m.H+m.B {
|
|
return false
|
|
} else if m.canAddAt(m.P, Point{m.P.X, y}) {
|
|
break
|
|
}
|
|
|
|
y++
|
|
}
|
|
|
|
m.P.Y = y
|
|
|
|
m.Draw()
|
|
|
|
return true
|
|
}
|
|
|
|
func (m *Matrix) Draw() {
|
|
if m.draw == nil {
|
|
return
|
|
}
|
|
|
|
select {
|
|
case m.draw <- event.DrawPlayerMatrix:
|
|
}
|
|
}
|
|
|
|
func (m *Matrix) ClearOverlay() {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
m.ClearOverlayL()
|
|
}
|
|
|
|
func (m *Matrix) ClearOverlayL() {
|
|
for i, b := range m.O {
|
|
if b == BlockNone {
|
|
continue
|
|
}
|
|
|
|
m.O[i] = BlockNone
|
|
}
|
|
}
|
|
|
|
func (m *Matrix) Reset() {
|
|
m.Lock()
|
|
|
|
m.GameOver = false
|
|
m.P = nil
|
|
m.lands = nil
|
|
m.Speed = 0
|
|
m.PendingGarbage = 0
|
|
m.PendingGarbageTime = time.Time{}
|
|
m.Unlock()
|
|
|
|
m.Clear()
|
|
m.ClearOverlay()
|
|
}
|
|
|
|
func (m *Matrix) Clear() {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
for i, b := range m.M {
|
|
if b == BlockNone {
|
|
continue
|
|
}
|
|
|
|
m.M[i] = BlockNone
|
|
}
|
|
}
|
|
|
|
func (m *Matrix) DrawGhostPieceL() {
|
|
p := m.P
|
|
if m.Type != MatrixStandard || m.GameOver || p == nil {
|
|
return
|
|
}
|
|
|
|
for y := p.Y; y >= 0; y-- {
|
|
if y == 0 || !m.canAddAt(p, Point{p.X, y - 1}) {
|
|
err := m.add(p, p.Ghost, Point{p.X, y}, true)
|
|
if err != nil {
|
|
log.Fatalf("failed to draw ghost piece: %+v", err)
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *Matrix) DrawActivePieceL() {
|
|
p := m.P
|
|
if m.Type != MatrixStandard || m.GameOver || p == nil {
|
|
return
|
|
}
|
|
|
|
err := m.add(p, p.Solid, Point{p.X, p.Y}, true)
|
|
if err != nil {
|
|
log.Fatalf("failed to draw active piece: %+v", err)
|
|
}
|
|
}
|
|
|
|
func (m *Matrix) Block(x int, y int) Block {
|
|
if y >= m.H+m.B {
|
|
log.Panicf("failed to retrieve block at %d,%d: invalid y coordinate", x, y)
|
|
}
|
|
|
|
index := I(x, y, m.W)
|
|
|
|
// Return overlay block when present
|
|
if b := m.O[index]; b != BlockNone {
|
|
return b
|
|
}
|
|
|
|
return m.M[index]
|
|
}
|
|
|
|
func (m *Matrix) SetGameOver() {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
m.setGameOver()
|
|
}
|
|
|
|
func (m *Matrix) setGameOver() {
|
|
if m.GameOver {
|
|
return
|
|
}
|
|
|
|
m.GameOver = true
|
|
m.Combo = 0
|
|
m.ComboStart = time.Time{}
|
|
m.ComboEnd = time.Time{}
|
|
|
|
go func() {
|
|
for y := 0; y < m.H+m.B-1; y++ {
|
|
m.Lock()
|
|
|
|
if !m.GameOver {
|
|
m.Unlock()
|
|
|
|
return
|
|
}
|
|
|
|
for x := 0; x < m.W; x++ {
|
|
i := I(x, y, m.W)
|
|
|
|
switch m.M[i] {
|
|
case BlockSolidJ:
|
|
m.M[i] = BlockGhostJ
|
|
case BlockSolidI:
|
|
m.M[i] = BlockGhostI
|
|
case BlockSolidS:
|
|
m.M[i] = BlockGhostS
|
|
case BlockSolidT:
|
|
m.M[i] = BlockGhostT
|
|
case BlockSolidL:
|
|
m.M[i] = BlockGhostL
|
|
case BlockSolidZ:
|
|
m.M[i] = BlockGhostZ
|
|
case BlockSolidO:
|
|
m.M[i] = BlockGhostO
|
|
}
|
|
}
|
|
|
|
m.Draw()
|
|
|
|
m.Unlock()
|
|
|
|
time.Sleep(7 * time.Millisecond)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (m *Matrix) SetBlock(x int, y int, block Block, overlay bool) bool {
|
|
if x < 0 || x >= m.W || y < 0 || y >= m.H+m.B {
|
|
return false
|
|
}
|
|
|
|
index := I(x, y, m.W)
|
|
|
|
if overlay {
|
|
if m.O[index] != BlockNone {
|
|
return false
|
|
}
|
|
|
|
m.O[index] = block
|
|
} else {
|
|
if m.M[index] != BlockNone {
|
|
return false
|
|
}
|
|
|
|
m.M[index] = block
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (m *Matrix) RotatePiece(rotations int, direction int) bool {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
if m.GameOver || rotations == 0 {
|
|
return false
|
|
}
|
|
|
|
p := m.P
|
|
|
|
originalMino := make(Mino, len(p.Mino))
|
|
copy(originalMino, p.Mino)
|
|
|
|
p.Mino = p.Rotate(rotations, direction)
|
|
|
|
for i := range AllOffsets {
|
|
px := p.X + AllOffsets[i].X
|
|
py := p.Y + AllOffsets[i].Y
|
|
|
|
if m.canAddAt(p, Point{px, py}) {
|
|
p.ApplyReset()
|
|
|
|
if p.X != px || p.Y != py {
|
|
p.SetLocation(px, py)
|
|
}
|
|
|
|
p.ApplyRotation(rotations, direction)
|
|
|
|
m.Draw()
|
|
|
|
return true
|
|
}
|
|
|
|
}
|
|
|
|
p.Mino = originalMino
|
|
return false
|
|
}
|
|
|
|
func (m *Matrix) SpawnLocation(p *Piece) Point {
|
|
if p == nil {
|
|
return Point{-1, -1}
|
|
}
|
|
|
|
w, _ := p.Size()
|
|
x := (m.W / 2) - (w / 2)
|
|
|
|
for y := m.H; y < (m.H+m.B)-1; y++ {
|
|
if m.canAddAt(p, Point{x, y}) {
|
|
return Point{x, y}
|
|
}
|
|
}
|
|
|
|
return Point{-1, -1}
|
|
|
|
}
|
|
|
|
func (m *Matrix) Render() string {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
var b strings.Builder
|
|
|
|
for y := m.H - 1; y >= 0; y-- {
|
|
for x := 0; x < m.W; x++ {
|
|
b.WriteRune(m.Block(x, y).Rune())
|
|
}
|
|
|
|
if y == 0 {
|
|
break
|
|
}
|
|
|
|
b.WriteRune('\n')
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// LowerPiece lowers the active piece by one line when possible, otherwise the
|
|
// piece is landed
|
|
func (m *Matrix) LowerPiece() {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
m.lowerPiece()
|
|
}
|
|
|
|
func (m *Matrix) lowerPiece() {
|
|
if m.GameOver {
|
|
return
|
|
} 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 || m.P.landed {
|
|
return
|
|
}
|
|
|
|
m.P.landed = true
|
|
|
|
dropped := false
|
|
LANDPIECE:
|
|
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)-1; dropY++ {
|
|
if !m.canAddAt(m.P, Point{m.P.X, dropY}) {
|
|
continue
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
dropped = true
|
|
break LANDPIECE
|
|
}
|
|
}
|
|
}
|
|
|
|
if !dropped {
|
|
m.Event <- event.GameOverEvent{}
|
|
|
|
m.Draw()
|
|
return
|
|
}
|
|
|
|
cleared := m.clearFilled()
|
|
|
|
score := 0
|
|
switch cleared {
|
|
case 0:
|
|
// No score
|
|
case 1:
|
|
score = 100
|
|
case 2:
|
|
score = 300
|
|
case 3:
|
|
score = 500
|
|
case 4:
|
|
score = 800
|
|
default:
|
|
score = 1000 + ((cleared - 5) * 200)
|
|
}
|
|
|
|
_ = score
|
|
|
|
m.moved()
|
|
|
|
for i := range m.lands {
|
|
if time.Since(m.lands[i]) > 2*time.Minute {
|
|
continue
|
|
}
|
|
|
|
if i > 0 {
|
|
m.lands = m.lands[i+1:]
|
|
}
|
|
break
|
|
}
|
|
m.lands = append(m.lands, time.Now())
|
|
|
|
numlands := len(m.lands)
|
|
if numlands > 1 {
|
|
m.Speed = int(time.Minute / (time.Since(m.lands[0]) / time.Duration(numlands)))
|
|
}
|
|
|
|
if cleared > 0 {
|
|
sendGarbage := m.addToCombo(cleared)
|
|
if sendGarbage > 0 {
|
|
remainingGarbage := sendGarbage
|
|
if m.PendingGarbage > 0 {
|
|
m.PendingGarbage -= sendGarbage
|
|
|
|
if m.PendingGarbage < 0 {
|
|
remainingGarbage = m.PendingGarbage * -1
|
|
m.PendingGarbage = 0
|
|
} else {
|
|
remainingGarbage = 0
|
|
}
|
|
}
|
|
|
|
if remainingGarbage > 0 {
|
|
m.Event <- &event.SendGarbageEvent{Lines: remainingGarbage}
|
|
}
|
|
}
|
|
}
|
|
|
|
if !m.takePiece() {
|
|
m.Event <- &event.GameOverEvent{}
|
|
}
|
|
|
|
m.Draw()
|
|
}
|
|
|
|
func (m *Matrix) addToCombo(lines int) int {
|
|
if m.GameOver {
|
|
return 0
|
|
}
|
|
|
|
baseTime := ComboBaseTime
|
|
bonusTime := baseTime / 2
|
|
|
|
if m.Combo == 0 || time.Until(m.ComboEnd) <= 0 {
|
|
m.Combo = 0
|
|
m.ComboStart = time.Now()
|
|
m.ComboEnd = m.ComboStart
|
|
}
|
|
|
|
m.Combo++
|
|
|
|
if m.Combo > 1 {
|
|
baseTime /= math.Pow(2, float64(m.Combo-1))
|
|
bonusTime /= math.Pow(2, float64(m.Combo-1))
|
|
}
|
|
|
|
m.ComboEnd = m.ComboEnd.Add(time.Duration((baseTime * float64(time.Second)) + (bonusTime * float64(lines) * float64(time.Second))))
|
|
|
|
baseGarbage := 0
|
|
if lines > 1 {
|
|
baseGarbage = lines - 1
|
|
}
|
|
|
|
bonusGarbage := m.CalculateBonusGarbage()
|
|
|
|
return baseGarbage + bonusGarbage
|
|
}
|
|
|
|
func (m *Matrix) CalculateBonusGarbage() int {
|
|
bonusGarbage := 0
|
|
if m.Combo == 1 {
|
|
// No bonus garbage
|
|
} else if m.Combo < 4 {
|
|
bonusGarbage = 1
|
|
} else {
|
|
scoreCombo := m.Combo - 3
|
|
if scoreCombo > 7 {
|
|
scoreCombo = 7
|
|
}
|
|
|
|
bonusGarbage = fibonacci(scoreCombo)
|
|
}
|
|
|
|
return bonusGarbage
|
|
}
|
|
|
|
func (m *Matrix) landPiece() {
|
|
p := m.P
|
|
p.Lock()
|
|
if p.landing || p.landed || m.GameOver {
|
|
p.Unlock()
|
|
return
|
|
}
|
|
|
|
p.landing = true
|
|
p.Unlock()
|
|
|
|
go func() {
|
|
landStart := time.Now()
|
|
|
|
t := time.NewTicker(100 * time.Millisecond)
|
|
for {
|
|
<-t.C
|
|
|
|
m.Lock()
|
|
p.Lock()
|
|
|
|
if p.landed {
|
|
p.Unlock()
|
|
m.Unlock()
|
|
return
|
|
}
|
|
|
|
if p.resets > 0 && time.Since(p.lastReset) < 500*time.Millisecond {
|
|
p.Unlock()
|
|
m.Unlock()
|
|
continue
|
|
} else if time.Since(landStart) < 500*time.Millisecond {
|
|
p.Unlock()
|
|
m.Unlock()
|
|
continue
|
|
}
|
|
|
|
t.Stop()
|
|
break
|
|
}
|
|
|
|
p.Unlock()
|
|
|
|
m.finishLandingPiece()
|
|
m.Unlock()
|
|
}()
|
|
}
|
|
|
|
func (m *Matrix) MovePiece(x int, y int) bool {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
return m.movePiece(x, y)
|
|
}
|
|
|
|
func (m *Matrix) movePiece(x int, y int) bool {
|
|
if m.GameOver || (x == 0 && y == 0) {
|
|
return false
|
|
}
|
|
|
|
px := m.P.X + x
|
|
py := m.P.Y + y
|
|
|
|
if !m.canAddAt(m.P, Point{px, py}) {
|
|
return false
|
|
}
|
|
|
|
m.P.ApplyReset()
|
|
m.P.SetLocation(px, py)
|
|
|
|
if !m.canAddAt(m.P, Point{m.P.X, m.P.Y - 1}) {
|
|
m.landPiece()
|
|
}
|
|
|
|
if y < 0 {
|
|
m.moved()
|
|
}
|
|
|
|
m.Draw()
|
|
|
|
return true
|
|
}
|
|
|
|
func (m *Matrix) Moved() {
|
|
m.Lock()
|
|
if m.Move == nil {
|
|
m.Unlock()
|
|
return
|
|
}
|
|
|
|
select {
|
|
case m.Move <- 0:
|
|
m.Unlock()
|
|
default:
|
|
m.Unlock()
|
|
m.SetGameOver()
|
|
}
|
|
}
|
|
|
|
func (m *Matrix) moved() {
|
|
if m.Move == nil {
|
|
return
|
|
}
|
|
|
|
select {
|
|
case m.Move <- 0:
|
|
default:
|
|
m.setGameOver()
|
|
}
|
|
}
|
|
|
|
func (m *Matrix) HardDropPiece() {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
m.finishLandingPiece()
|
|
}
|
|
|
|
func (m *Matrix) ValidPoint(x int, y int) bool {
|
|
return x >= 0 && x < m.W && y >= 0 && y < m.H+m.B
|
|
}
|
|
|
|
func (m *Matrix) Replace(newmtx *Matrix) {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
if m.GameOver && !newmtx.GameOver {
|
|
return
|
|
}
|
|
|
|
m.M = newmtx.M
|
|
m.P = newmtx.P
|
|
|
|
m.PlayerName = newmtx.PlayerName
|
|
m.GarbageSent = newmtx.GarbageSent
|
|
m.GarbageReceived = newmtx.GarbageReceived
|
|
m.Speed = newmtx.Speed
|
|
}
|
|
|
|
func fibonacci(value int) int {
|
|
if value == 0 || value == 1 {
|
|
return value
|
|
}
|
|
return fibonacci(value-2) + fibonacci(value-1)
|
|
}
|
|
|
|
func NewTestMatrix() (*Matrix, error) {
|
|
minos, err := Generate(4)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate minos: %s", err)
|
|
}
|
|
|
|
ev := make(chan interface{})
|
|
go func() {
|
|
for range ev {
|
|
}
|
|
}()
|
|
|
|
draw := make(chan event.DrawObject)
|
|
go func() {
|
|
for range draw {
|
|
}
|
|
}()
|
|
|
|
m := NewMatrix(10, 20, 4, 1, ev, draw, MatrixStandard)
|
|
|
|
bag, err := NewBag(1, minos, 10)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate minos: %s", err)
|
|
}
|
|
|
|
m.AttachBag(bag)
|
|
|
|
m.TakePiece()
|
|
|
|
return m, nil
|
|
}
|
|
|
|
func (m *Matrix) AddTestBlocks() {
|
|
var block Block
|
|
for y := 0; y < 7; y++ {
|
|
for x := 0; x < m.W-1; x++ {
|
|
if y > 3 && (x < 2 || x > 7) {
|
|
continue
|
|
}
|
|
|
|
if y == 2 || (y > 4 && x%2 > 0) {
|
|
block = BlockSolidT
|
|
} else {
|
|
block = BlockSolidO
|
|
}
|
|
|
|
m.M[I(x, y, m.W)] = block
|
|
}
|
|
}
|
|
}
|
|
|
|
// Type alias used during marshalling
|
|
type LockedMatrix *Matrix
|
|
|
|
func (m *Matrix) MarshalJSON() ([]byte, error) {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
return json.Marshal(*LockedMatrix(m))
|
|
}
|