blocks: Flushing blocks

This commit is contained in:
Hajime Hoshi 2014-12-29 17:44:06 +09:00
parent 9288c9dcc9
commit 083dafa627
4 changed files with 148 additions and 50 deletions

View File

@ -18,8 +18,12 @@ import (
"github.com/hajimehoshi/ebiten"
)
const maxFlushCount = 30
type Field struct {
blocks [fieldBlockNumX][fieldBlockNumY]BlockType
blocks [fieldBlockNumX][fieldBlockNumY]BlockType
flushCount int
onEndFlushing func(int)
}
func NewField() *Field {
@ -75,16 +79,44 @@ func (f *Field) RotatePieceRight(piece *Piece, x, y int, angle Angle) Angle {
return angle.RotateRight()
}
func (f *Field) AbsorbPiece(piece *Piece, x, y int, angle Angle) int {
func (f *Field) AbsorbPiece(piece *Piece, x, y int, angle Angle) {
piece.AbsorbInto(f, x, y, angle)
return f.Flush()
if f.flushable() {
f.flushCount = maxFlushCount
}
}
func (f *Field) Flushing() bool {
return 0 < f.flushCount
}
func (f *Field) SetEndFlushing(fn func(lines int)) {
f.onEndFlushing = fn
}
func (f *Field) flushable() bool {
for j := fieldBlockNumY - 1; 0 <= j; j-- {
if f.flushableLine(j) {
return true
}
}
return false
}
func (f *Field) flushableLine(j int) bool {
for i := 0; i < fieldBlockNumX; i++ {
if f.blocks[i][j] == BlockTypeNone {
return false
}
}
return true
}
func (f *Field) setBlock(x, y int, blockType BlockType) {
f.blocks[x][y] = blockType
}
func (f *Field) Flush() int {
func (f *Field) endFlushing() int {
flushedLineNum := 0
for j := fieldBlockNumY - 1; 0 <= j; j-- {
if f.flushLine(j + flushedLineNum) {
@ -111,11 +143,53 @@ func (f *Field) flushLine(j int) bool {
return true
}
func (f *Field) Update() error {
if 0 <= f.flushCount {
f.flushCount--
if f.flushCount == 0 {
l := f.endFlushing()
if f.onEndFlushing != nil {
f.onEndFlushing(l)
}
}
}
return nil
}
func (f *Field) flushingColor() ebiten.ColorM {
clr := ebiten.ColorM{}
alpha := (float64(f.flushCount) / maxFlushCount) / 2
clr.Concat(ebiten.ScaleColor(1, 1, 1, alpha))
r := (1 - float64(f.flushCount)/maxFlushCount) * 2
g := (1 - float64(f.flushCount)/maxFlushCount) / 2
b := (1 - float64(f.flushCount)/maxFlushCount) / 2
clr.Concat(ebiten.TranslateColor(r, g, b, 0))
return clr
}
func (f *Field) Draw(r *ebiten.Image, x, y int) error {
blocks := make([][]BlockType, len(f.blocks))
for i, blockCol := range f.blocks {
blocks[i] = make([]BlockType, len(blockCol))
copy(blocks[i], blockCol[:])
flushingBlocks := make([][]BlockType, len(f.blocks))
for i := 0; i < fieldBlockNumX; i++ {
blocks[i] = make([]BlockType, fieldBlockNumY)
flushingBlocks[i] = make([]BlockType, fieldBlockNumY)
}
return drawBlocks(r, blocks, x, y)
for j := 0; j < fieldBlockNumY; j++ {
if f.flushableLine(j) {
for i := 0; i < fieldBlockNumX; i++ {
flushingBlocks[i][j] = f.blocks[i][j]
}
} else {
for i := 0; i < fieldBlockNumX; i++ {
blocks[i][j] = f.blocks[i][j]
}
}
}
if err := drawBlocks(r, blocks, x, y, ebiten.ColorM{}); err != nil {
return err
}
if err := drawBlocks(r, flushingBlocks, x, y, f.flushingColor()); err != nil {
return err
}
return nil
}

View File

@ -36,6 +36,7 @@ const charWidth = 8
const charHeight = 8
func textWidth(str string) int {
// TODO: Take care about '\n'
return charWidth * len(str)
}

View File

@ -131,7 +131,10 @@ func (s *GameScene) addScore(lines int) {
}
func (s *GameScene) Update(state *GameState) error {
s.field.Update()
if s.gameover {
// TODO: Go back to the title by pressing something
return nil
}
@ -145,65 +148,84 @@ func (s *GameScene) Update(state *GameState) error {
s.nextPiece = s.choosePiece()
}
// Move piece by user input.
piece := s.currentPiece
x := s.currentPieceX
y := s.currentPieceY
angle := s.currentPieceAngle
moved := false
if state.Input.StateForKey(ebiten.KeySpace) == 1 {
s.currentPieceAngle = s.field.RotatePieceRight(piece, x, y, angle)
moved = angle != s.currentPieceAngle
}
if l := state.Input.StateForKey(ebiten.KeyLeft); l == 1 || (10 <= l && l%2 == 0) {
s.currentPieceX = s.field.MovePieceToLeft(piece, x, y, angle)
moved = x != s.currentPieceX
}
if r := state.Input.StateForKey(ebiten.KeyRight); r == 1 || (10 <= r && r%2 == 0) {
s.currentPieceX = s.field.MovePieceToRight(piece, x, y, angle)
moved = y != s.currentPieceX
}
if d := state.Input.StateForKey(ebiten.KeyDown); (d-1)%2 == 0 {
s.currentPieceY = s.field.DropPiece(piece, x, y, angle)
moved = y != s.currentPieceY
if moved {
s.score++
piece := s.currentPiece
angle := s.currentPieceAngle
// Move piece by user input.
if !s.field.Flushing() {
piece := s.currentPiece
x := s.currentPieceX
y := s.currentPieceY
if state.Input.StateForKey(ebiten.KeySpace) == 1 {
s.currentPieceAngle = s.field.RotatePieceRight(piece, x, y, angle)
moved = angle != s.currentPieceAngle
}
if l := state.Input.StateForKey(ebiten.KeyLeft); l == 1 || (10 <= l && l%2 == 0) {
s.currentPieceX = s.field.MovePieceToLeft(piece, x, y, angle)
moved = x != s.currentPieceX
}
if r := state.Input.StateForKey(ebiten.KeyRight); r == 1 || (10 <= r && r%2 == 0) {
s.currentPieceX = s.field.MovePieceToRight(piece, x, y, angle)
moved = y != s.currentPieceX
}
if d := state.Input.StateForKey(ebiten.KeyDown); (d-1)%2 == 0 {
s.currentPieceY = s.field.DropPiece(piece, x, y, angle)
moved = y != s.currentPieceY
if moved {
s.score++
}
}
}
// Drop the current piece with gravity.
s.currentPieceYCarry += 2*s.level() + 1
for 60 <= s.currentPieceYCarry {
s.currentPieceYCarry -= 60
s.currentPieceY = s.field.DropPiece(piece, s.currentPieceX, s.currentPieceY, angle)
moved = y != s.currentPieceY
if !s.field.Flushing() {
y := s.currentPieceY
angle := s.currentPieceAngle
s.currentPieceYCarry += 2*s.level() + 1
for 60 <= s.currentPieceYCarry {
s.currentPieceYCarry -= 60
s.currentPieceY = s.field.DropPiece(piece, s.currentPieceX, s.currentPieceY, angle)
moved = y != s.currentPieceY
}
}
if moved {
s.landingCount = 0
} else if !s.field.PieceDroppable(piece, s.currentPieceX, s.currentPieceY, angle) {
} else if !s.field.Flushing() && !s.field.PieceDroppable(piece, s.currentPieceX, s.currentPieceY, angle) {
if 0 < state.Input.StateForKey(ebiten.KeyDown) {
s.landingCount += 10
} else {
s.landingCount++
}
if maxLandingCount <= s.landingCount {
lines := s.field.AbsorbPiece(piece, s.currentPieceX, s.currentPieceY, angle)
s.lines += lines
if 0 < lines {
s.addScore(lines)
}
s.initCurrentPiece(s.nextPiece)
s.nextPiece = s.choosePiece()
s.landingCount = 0
if s.currentPiece.Collides(s.field, s.currentPieceX, s.currentPieceY, s.currentPieceAngle) {
s.gameover = true
s.field.AbsorbPiece(piece, s.currentPieceX, s.currentPieceY, angle)
if s.field.Flushing() {
s.field.SetEndFlushing(func(lines int) {
s.lines += lines
if 0 < lines {
s.addScore(lines)
}
s.goNextPiece()
})
} else {
s.goNextPiece()
}
}
}
return nil
}
func (s *GameScene) goNextPiece() {
s.initCurrentPiece(s.nextPiece)
s.nextPiece = s.choosePiece()
s.landingCount = 0
if s.currentPiece.Collides(s.field, s.currentPieceX, s.currentPieceY, s.currentPieceAngle) {
s.gameover = true
}
}
func (s *GameScene) Draw(r *ebiten.Image) error {
if err := r.Fill(color.White); err != nil {
return err
@ -252,7 +274,7 @@ func (s *GameScene) Draw(r *ebiten.Image) error {
if err := s.field.Draw(r, fieldX, fieldY); err != nil {
return err
}
if s.currentPiece != nil {
if s.currentPiece != nil && !s.field.Flushing() {
x := fieldX + s.currentPieceX*blockWidth
y := fieldY + s.currentPieceY*blockHeight
if err := s.currentPiece.Draw(r, x, y, s.currentPieceAngle); err != nil {

View File

@ -143,7 +143,7 @@ const blockHeight = 10
const fieldBlockNumX = 10
const fieldBlockNumY = 20
func drawBlocks(r *ebiten.Image, blocks [][]BlockType, x, y int) error {
func drawBlocks(r *ebiten.Image, blocks [][]BlockType, x, y int, clr ebiten.ColorM) error {
parts := []ebiten.ImagePart{}
for i, blockCol := range blocks {
for j, block := range blockCol {
@ -160,7 +160,8 @@ func drawBlocks(r *ebiten.Image, blocks [][]BlockType, x, y int) error {
}
}
return r.DrawImage(imageBlocks, &ebiten.DrawImageOptions{
Parts: parts,
Parts: parts,
ColorM: clr,
})
}
@ -242,5 +243,5 @@ func (p *Piece) Draw(r *ebiten.Image, x, y int, angle Angle) error {
}
}
}
return drawBlocks(r, blocks, x, y)
return drawBlocks(r, blocks, x, y, ebiten.ColorM{})
}