mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-24 18:58:54 +01:00
examples/2048: Animate tiles
This commit is contained in:
parent
0ec07420f4
commit
bdf2919ed0
@ -15,14 +15,19 @@
|
||||
package twenty48
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"errors"
|
||||
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
)
|
||||
|
||||
var taskTerminated = errors.New("twenty48: task terminated")
|
||||
|
||||
type task func() error
|
||||
|
||||
type Board struct {
|
||||
size int
|
||||
tiles map[*Tile]struct{}
|
||||
tasks []task
|
||||
}
|
||||
|
||||
func NewBoard(size int) *Board {
|
||||
@ -40,66 +45,79 @@ func (b *Board) tileAt(x, y int) *Tile {
|
||||
return tileAt(b.tiles, x, y)
|
||||
}
|
||||
|
||||
func (b *Board) isAnimating() bool {
|
||||
for t := range b.tiles {
|
||||
if t.isAnimating() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *Board) Update(input *Input) error {
|
||||
if 0 < len(b.tasks) {
|
||||
t := b.tasks[0]
|
||||
err := t()
|
||||
if err == taskTerminated {
|
||||
b.tasks = b.tasks[1:]
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if dir, ok := input.Dir(); ok {
|
||||
if err := b.Move(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for t := range b.tiles {
|
||||
if err := t.Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
nextTiles := map[*Tile]struct{}{}
|
||||
for t := range b.tiles {
|
||||
if t.current.value == 0 {
|
||||
continue
|
||||
}
|
||||
nextTiles[t] = struct{}{}
|
||||
}
|
||||
b.tiles = nextTiles
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Board) Move(dir Dir) error {
|
||||
if moved := MoveTiles(b.tiles, b.size, dir); !moved {
|
||||
if !MoveTiles(b.tiles, b.size, dir) {
|
||||
return nil
|
||||
}
|
||||
if err := addRandomTile(b.tiles, b.size); err != nil {
|
||||
return err
|
||||
}
|
||||
b.tasks = append(b.tasks, func() error {
|
||||
for t := range b.tiles {
|
||||
if err := t.Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for t := range b.tiles {
|
||||
if t.isAnimating() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return taskTerminated
|
||||
})
|
||||
b.tasks = append(b.tasks, func() error {
|
||||
nextTiles := map[*Tile]struct{}{}
|
||||
for t := range b.tiles {
|
||||
if t.next.value != 0 {
|
||||
panic("not reach")
|
||||
}
|
||||
if t.current.value == 0 {
|
||||
continue
|
||||
}
|
||||
nextTiles[t] = struct{}{}
|
||||
}
|
||||
b.tiles = nextTiles
|
||||
if err := addRandomTile(b.tiles, b.size); err != nil {
|
||||
return err
|
||||
}
|
||||
return taskTerminated
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
tileSize = 40
|
||||
tileMargin = 2
|
||||
)
|
||||
|
||||
var (
|
||||
tileImage *ebiten.Image
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
tileImage, err = ebiten.NewImage(tileSize, tileSize, ebiten.FilterNearest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := tileImage.Fill(color.White); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Board) Size() (int, int) {
|
||||
x := b.size*tileSize + (b.size+1)*tileMargin
|
||||
y := x
|
||||
return x, y
|
||||
}
|
||||
|
||||
func (b *Board) Draw(screen *ebiten.Image) error {
|
||||
if err := screen.Fill(frameColor); err != nil {
|
||||
func (b *Board) Draw(boardImage *ebiten.Image) error {
|
||||
if err := boardImage.Fill(frameColor); err != nil {
|
||||
return err
|
||||
}
|
||||
for j := 0; j < b.size; j++ {
|
||||
@ -111,13 +129,27 @@ func (b *Board) Draw(screen *ebiten.Image) error {
|
||||
op.GeoM.Translate(float64(x), float64(y))
|
||||
r, g, b, a := colorToScale(tileBackgroundColor(v))
|
||||
op.ColorM.Scale(r, g, b, a)
|
||||
if err := screen.DrawImage(tileImage, op); err != nil {
|
||||
if err := boardImage.DrawImage(tileImage, op); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
animatingTiles := map[*Tile]struct{}{}
|
||||
nonAnimatingTiles := map[*Tile]struct{}{}
|
||||
for t := range b.tiles {
|
||||
if err := t.Draw(screen); err != nil {
|
||||
if t.isAnimating() {
|
||||
animatingTiles[t] = struct{}{}
|
||||
} else {
|
||||
nonAnimatingTiles[t] = struct{}{}
|
||||
}
|
||||
}
|
||||
for t := range nonAnimatingTiles {
|
||||
if err := t.Draw(boardImage); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for t := range animatingTiles {
|
||||
if err := t.Draw(boardImage); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -32,9 +32,9 @@ type TileData struct {
|
||||
}
|
||||
|
||||
type Tile struct {
|
||||
current TileData
|
||||
next TileData
|
||||
moving bool
|
||||
current TileData
|
||||
next TileData
|
||||
animationCount int
|
||||
}
|
||||
|
||||
func NewTile(value int, x, y int) *Tile {
|
||||
@ -63,6 +63,10 @@ func (t *Tile) NextPos() (int, int) {
|
||||
return t.next.x, t.next.y
|
||||
}
|
||||
|
||||
func (t *Tile) isAnimating() bool {
|
||||
return 0 < t.animationCount
|
||||
}
|
||||
|
||||
func tileAt(tiles map[*Tile]struct{}, x, y int) *Tile {
|
||||
var result *Tile
|
||||
for t := range tiles {
|
||||
@ -91,6 +95,10 @@ func nextTileAt(tiles map[*Tile]struct{}, x, y int) *Tile {
|
||||
return result
|
||||
}
|
||||
|
||||
const (
|
||||
maxAnimationCount = 10
|
||||
)
|
||||
|
||||
func MoveTiles(tiles map[*Tile]struct{}, size int, dir Dir) bool {
|
||||
vx, vy := dir.Vector()
|
||||
tx := []int{}
|
||||
@ -113,6 +121,9 @@ func MoveTiles(tiles map[*Tile]struct{}, size int, dir Dir) bool {
|
||||
if t == nil {
|
||||
continue
|
||||
}
|
||||
if t.animationCount != 0 {
|
||||
panic("not reach")
|
||||
}
|
||||
ii := i
|
||||
jj := j
|
||||
for {
|
||||
@ -140,15 +151,17 @@ func MoveTiles(tiles map[*Tile]struct{}, size int, dir Dir) bool {
|
||||
moved = true
|
||||
break
|
||||
}
|
||||
next := TileData{}
|
||||
next.value = t.current.value
|
||||
if tt := nextTileAt(tiles, ii, jj); tt != t && tt != nil {
|
||||
t.next.value = t.current.value + tt.next.value
|
||||
next.value = t.current.value + tt.next.value
|
||||
tt.next = TileData{}
|
||||
} else {
|
||||
t.next.value = t.current.value
|
||||
tt.animationCount = 1
|
||||
}
|
||||
t.next.x = ii
|
||||
t.next.y = jj
|
||||
t.moving = true
|
||||
next.x = ii
|
||||
next.y = jj
|
||||
t.next = next
|
||||
t.animationCount = maxAnimationCount
|
||||
}
|
||||
}
|
||||
return moved
|
||||
@ -157,12 +170,10 @@ func MoveTiles(tiles map[*Tile]struct{}, size int, dir Dir) bool {
|
||||
func addRandomTile(tiles map[*Tile]struct{}, size int) error {
|
||||
cells := make([]bool, size*size)
|
||||
for t := range tiles {
|
||||
i := 0
|
||||
if t.moving {
|
||||
i = t.next.x + t.next.y*size
|
||||
} else {
|
||||
i = t.current.x + t.current.y*size
|
||||
if t.isAnimating() {
|
||||
panic("not reach")
|
||||
}
|
||||
i := t.current.x + t.current.y*size
|
||||
cells[i] = true
|
||||
}
|
||||
availableCells := []int{}
|
||||
@ -188,12 +199,17 @@ func addRandomTile(tiles map[*Tile]struct{}, size int) error {
|
||||
}
|
||||
|
||||
func (t *Tile) Update() error {
|
||||
if !t.moving {
|
||||
if t.animationCount == 0 {
|
||||
if t.next.value != 0 {
|
||||
panic("not reach")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
t.current = t.next
|
||||
t.next = TileData{}
|
||||
t.moving = false
|
||||
t.animationCount--
|
||||
if t.animationCount == 0 {
|
||||
t.current = t.next
|
||||
t.next = TileData{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -212,12 +228,47 @@ func colorToScale(clr color.Color) (float64, float64, float64, float64) {
|
||||
return rf, gf, bf, af
|
||||
}
|
||||
|
||||
func mean(a, b int, rate float64) int {
|
||||
return int(float64(a)*rate + float64(b)*(1-rate))
|
||||
}
|
||||
|
||||
const (
|
||||
tileSize = 40
|
||||
tileMargin = 2
|
||||
)
|
||||
|
||||
var (
|
||||
tileImage *ebiten.Image
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
tileImage, err = ebiten.NewImage(tileSize, tileSize, ebiten.FilterNearest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := tileImage.Fill(color.White); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tile) Draw(boardImage *ebiten.Image) error {
|
||||
i, j := t.current.x, t.current.y
|
||||
ni, nj := t.next.x, t.next.y
|
||||
v := t.current.value
|
||||
if v == 0 {
|
||||
return nil
|
||||
}
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
x := i*tileSize + (i+1)*tileMargin
|
||||
y := j*tileSize + (j+1)*tileMargin
|
||||
nx := ni*tileSize + (ni+1)*tileMargin
|
||||
ny := nj*tileSize + (nj+1)*tileMargin
|
||||
if 0 < t.animationCount && t.next.value != 0 {
|
||||
rate := float64(t.animationCount) / maxAnimationCount
|
||||
x = mean(x, nx, rate)
|
||||
y = mean(y, ny, rate)
|
||||
}
|
||||
op.GeoM.Translate(float64(x), float64(y))
|
||||
r, g, b, a := colorToScale(tileBackgroundColor(v))
|
||||
op.ColorM.Scale(r, g, b, a)
|
||||
|
@ -103,6 +103,21 @@ func TestMoveTiles(t *testing.T) {
|
||||
0, 0, 0, 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
Dir: DirLeft,
|
||||
Input: []int{
|
||||
0, 2, 2, 2,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
},
|
||||
Want: []int{
|
||||
4, 2, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
Dir: DirRight,
|
||||
Input: []int{
|
||||
|
Loading…
Reference in New Issue
Block a user