mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-02-03 22:44:28 +01:00
blocks: Flushing blocks
This commit is contained in:
parent
9288c9dcc9
commit
083dafa627
@ -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
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ const charWidth = 8
|
||||
const charHeight = 8
|
||||
|
||||
func textWidth(str string) int {
|
||||
// TODO: Take care about '\n'
|
||||
return charWidth * len(str)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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{})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user