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