↩ revert: 还原合并逻辑
All checks were successful
ci / build (push) Successful in 45s

This commit is contained in:
Litrix2 2025-03-01 22:30:17 +08:00
parent 18ec5baee1
commit 292f79e5d3

View File

@ -10,14 +10,15 @@
></div>
</template>
<div
v-for="tile of tiles"
:key="tile.id"
v-for="{ renderState, id } of tiles"
v-show="renderState.show"
:key="id"
class="tile"
:data-tile-id="tile.id"
:style="[getNumberTileStyle(tile.renderState)]"
:data-tile-id="id"
:style="[getNumberTileStyle(renderState)]"
>
{{ tile.renderState.number }}
<div v-if="GAME_2048_DEBUG" class="id">#{{ tile.id }}</div>
{{ renderState.number }}
<div v-if="GAME_2048_DEBUG" class="id">#{{ id }}</div>
</div>
</div>
</template>
@ -59,7 +60,7 @@
<script lang="ts" setup>
import { GAME_2048_DEBUG } from '@/env';
import { useGame2048Store } from '@/stores/2048';
import { waitRef } from '@/utils';
import { chainIterables, waitRef } from '@/utils';
import { arrayIncludes, zip } from '@/utils/array';
import { setDefault } from '@/utils/map';
import { TableView, toPos, type Pos } from '@/utils/table-view';
@ -68,7 +69,7 @@ import { Directions } from '@/views/Game2048PageV2.vue';
import { useEventListener } from '@vueuse/core';
import { sample, shuffle } from 'lodash-es';
import { storeToRefs } from 'pinia';
import { onMounted, reactive, ref, useTemplateRef } from 'vue';
import { nextTick, onMounted, reactive, ref, useTemplateRef } from 'vue';
const game2048Store = useGame2048Store();
const { width, height } = storeToRefs(game2048Store);
const table = new TableView<Tile>([game2048Store.height, game2048Store.width]);
@ -78,9 +79,7 @@ type TileRenderState = {
toPos: Pos;
showPos: Pos;
zIndex: number;
shouldMove: boolean;
shouldHide: boolean;
shouldShow: boolean;
show: boolean;
};
class Tile {
private static lastId = 0;
@ -89,16 +88,13 @@ class Tile {
private _y!: number;
number = 0;
zIndex = 0;
fromOthers = false;
renderState: TileRenderState = {
number: 0,
fromPos: [0, 0],
toPos: [0, 0],
showPos: [0, 0],
zIndex: 0,
shouldMove: false,
shouldHide: false,
shouldShow: false,
show: false,
};
constructor() {
this.setPos([0, 0], 'move', false);
@ -122,8 +118,8 @@ class Tile {
idleTiles.add(this);
}
setPos([y, x]: Pos, type: 'move' | 'show', changeTable: boolean) {
if (changeTable && table.get(this.y, this.x) === this) {
table.delete(this.y, this.x);
if (changeTable) {
this.remove();
}
this._y = y;
this._x = x;
@ -132,7 +128,7 @@ class Tile {
idleTiles.delete(this);
}
if (type === 'move') {
this.renderState.fromPos = this.renderState.toPos;
this.renderState.fromPos = [...this.renderState.toPos];
this.renderState.toPos = [this.y, this.x];
} else {
this.renderState.showPos = [this.y, this.x];
@ -143,49 +139,44 @@ class Tile {
this.el?.getAnimations().forEach((animation) => animation.finish());
return this;
}
async animate() {
this.finish();
const { shouldMove, shouldHide, shouldShow } = this.renderState;
if (shouldMove) {
this.renderState.shouldMove = false;
this.renderState.zIndex = this.zIndex;
this.zIndex = 0;
const ext = { zIndex: this.renderState.zIndex };
await this.el?.animate(
[
Object.assign(getTilePositionStyle(this.renderState.fromPos), ext),
Object.assign(getTilePositionStyle(this.renderState.toPos), ext),
],
createKeyframeOptions({ easing: 'ease-in', duration: tileMoveDuration }),
).finished;
}
if (shouldHide) {
this.renderState.shouldHide = false;
await this.el?.animate({ display: 'none' }, createKeyframeOptions()).finished;
}
if (shouldShow) {
this.renderState.shouldShow = false;
this.renderState.toPos = [...this.renderState.showPos];
this.renderState.number = this.number;
await this.el?.animate({ display: 'block' }, createKeyframeOptions()).finished;
const posStyle = getTilePositionStyle(this.renderState.showPos);
await this.el?.animate(
this.fromOthers
? [
{ transform: `scale(0.5)`, ...posStyle },
{ transform: `scale(1.1)`, offset: 0.8 },
{ transform: `scale(1)`, ...posStyle },
]
: Object.assign(
createShowKeyframeCommon(this.renderState.showPos, { opacity: false }),
posStyle,
),
createKeyframeOptions({
duration: tileShowDuration,
delay: shouldMove ? 0 : tileMoveDuration,
}),
).finished;
}
async move() {
this.renderState.zIndex = this.zIndex;
this.zIndex = 0;
const ext = { zIndex: this.renderState.zIndex };
await this.el?.animate(
[
Object.assign(getTilePositionStyle(this.renderState.fromPos), ext),
Object.assign(getTilePositionStyle(this.renderState.toPos), ext),
],
createKeyframeOptions({ easing: 'ease-in', duration: tileMoveDuration }),
).finished;
return this;
}
hide() {
this.renderState.show = false;
return this;
}
async show(fromOthers: boolean) {
this.renderState.toPos = [...this.renderState.showPos];
this.renderState.number = this.number;
this.renderState.show = true;
const posStyle = getTilePositionStyle(this.renderState.showPos);
await this.el?.animate(
fromOthers
? [
{ transform: `scale(0.5)`, ...posStyle },
{ transform: `scale(1.1)`, offset: 0.8 },
{ transform: `scale(1)`, ...posStyle },
]
: Object.assign(
createShowKeyframeCommon(this.renderState.showPos, { opacity: false }),
posStyle,
),
createKeyframeOptions({
duration: tileShowDuration,
}),
).finished;
return this;
}
toString() {
return `Tile#${this.id}`;
@ -211,7 +202,7 @@ const createKeyframeOptions = ({
...options
}: KeyframeAnimationOptions = {}): KeyframeAnimationOptions => ({
easing: 'ease',
fill: 'both',
fill: 'forwards',
duration: !breaking ? duration : 0,
delay: !breaking ? delay : 0,
...options,
@ -236,8 +227,8 @@ const tileBackgroundColorMapping: Record<number, string | undefined> = {
1024: 'rgb(237, 197, 63)',
2048: 'rgb(237, 194, 46)',
};
const tileMoveDuration = 150,
tileShowDuration = 150;
const tileMoveDuration = 500,
tileShowDuration = 500;
const getTilePositionStyle = ([y, x]: Pos) => ({
left: `${borderWidth + x * (tileWidth + borderWidth)}px`,
top: `${borderWidth + y * (tileWidth + borderWidth)}px`,
@ -257,8 +248,6 @@ const addRandomTiles = (count: number) => {
if (!pos) break;
tile.number = sample([2, 4]);
tile.setPos(pos, 'show', true);
tile.fromOthers = false;
tile.renderState.shouldShow = true;
addedTiles.push(tile);
}
return addedTiles;
@ -282,8 +271,8 @@ onMounted(async () => {
.flat()
.map((a) => a?.finished),
);
fading = false;
await Promise.all(addRandomTiles(2).map((t) => t.animate()));
await Promise.all(addRandomTiles(2).map((t) => t.show(false)));
animating.value = false;
});
const firstIndices = (d: Directions) =>
Array.from(
@ -333,8 +322,7 @@ const secondIndices = (d: Directions) =>
})(),
);
const once = true;
let fading = true;
const animating = ref(false);
const animating = ref(true);
let breaking = false;
const canMove = () => {
if (table.filledSize < width.value * height.value) return true;
@ -356,7 +344,7 @@ const canMove = () => {
return false;
};
const move = async (key: Directions) => {
if (fading || breaking || !canMove()) return;
if (breaking || !canMove()) return;
if (animating.value) {
breaking = true;
tiles.forEach((t) => t.finish());
@ -384,16 +372,14 @@ const move = async (key: Directions) => {
prev = tile;
continue;
}
const prevTile = prev;
if (prevTile.number === tile.number) {
if (prev.number === tile.number) {
finished = false;
changed = true;
prevTile.remove();
prev.remove();
tile.number *= 2;
tile.fromOthers = true;
setDefault(relationMap, tile, []).push(prevTile);
setDefault(relationMap, tile, []).push(prev);
tilesToReshow.add(tile);
tilesToHide.add(prevTile);
tilesToHide.add(prev);
prev = once ? undefined : tile;
} else {
prev = tile;
@ -420,20 +406,24 @@ const move = async (key: Directions) => {
}
}
}
const tilesToAdd: Exposed<Tile>[] = [];
if (tilesToMove.size) {
addRandomTiles(2);
tilesToAdd.push(...addRandomTiles(2));
}
tilesToMove.forEach((t) => {
t.renderState.shouldMove = true;
});
tilesToHide.forEach((t) => {
t.renderState.shouldHide = true;
});
tilesToReshow.forEach((t) => {
t.renderState.shouldShow = true;
});
await Promise.all([tiles.map((t) => t.animate())]);
await Promise.all(Iterator.from(tilesToMove).map((t) => t.move()));
tilesToHide.forEach((t) => t.hide());
await Promise.all(
chainIterables(
Iterator.from(tilesToReshow).map((t) => t.show(true)),
tilesToAdd.map((t) => t.show(false)),
),
);
animating.value = false;
console.log({
move: [...tilesToMove],
hide: [...tilesToHide],
show: [...tilesToReshow, ...tilesToAdd],
});
};
useEventListener(document, 'keydown', async (e) => {
if (!arrayIncludes(Object.values(Directions), e.key)) {