diff --git a/example/blocks/blocks/field.go b/example/blocks/blocks/field.go index 189c97b80..09f10c36d 100644 --- a/example/blocks/blocks/field.go +++ b/example/blocks/blocks/field.go @@ -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 } diff --git a/example/blocks/blocks/font.go b/example/blocks/blocks/font.go index 2b5c089bd..29f8988a1 100644 --- a/example/blocks/blocks/font.go +++ b/example/blocks/blocks/font.go @@ -36,6 +36,7 @@ const charWidth = 8 const charHeight = 8 func textWidth(str string) int { + // TODO: Take care about '\n' return charWidth * len(str) } diff --git a/example/blocks/blocks/gamescene.go b/example/blocks/blocks/gamescene.go index 58490ca86..d452e8b27 100644 --- a/example/blocks/blocks/gamescene.go +++ b/example/blocks/blocks/gamescene.go @@ -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 { diff --git a/example/blocks/blocks/piece.go b/example/blocks/blocks/piece.go index fdce04a7e..6913f0dfc 100644 --- a/example/blocks/blocks/piece.go +++ b/example/blocks/blocks/piece.go @@ -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{}) }