完整版

This commit is contained in:
Resrate 2025-03-22 13:01:57 +08:00
parent 7631aa1cb7
commit 6f377745d2
6 changed files with 338 additions and 133 deletions

View File

@ -1,13 +1,9 @@
<style scoped>
#table {
background-color: #beb1a0;
width: 350px;
height: 350px;
position: relative;
margin: 10px auto;
padding: 0px 0px;
border-radius: 10px;
border: #857c71 solid 2px;
@media (max-width: 400px) or (max-height: 400px) {
* {
scale: 0.85;
transform-origin: center top;
}
}
#first_line,
#last_line {
@ -15,52 +11,64 @@
width: 350px;
justify-content: space-between;
margin: 0px auto;
padding-top: 10px;
}
#first_line div,
#last_line div{
margin-top: 8px;
#score div {
text-align: center;
}
#score {
background-color: #beb1a0;
width: 170px;
border-radius: 3px;
border: #857c71 solid 1px;
#table {
background-color: #bbada0;
width: 390px;
height: 390px;
position: relative;
margin: 10px auto;
padding: 0px 0px;
border-radius: 10px;
border: #857c71 solid 2px;
}
h1{
h1 {
margin: 0px;
font-weight: bolder;
font-size: 300%;
}
button{
button {
background-color: #beb1a0;
border-radius: 2px;
border: #857c71 solid 1px;
border: #857c71 solid 2px;
color: #736d64;
}
.fonts{
.fonts {
color: #736d64;
}
.font_white{
.font_white {
color: whitesmoke;
}
#num{
font-size: smaller;
#num {
font-size: large;
height: 100%;
}
</style>
<template>
<!-- <check :show="show" :result="result"></check> -->
<check :show="show" :result="result" :score="score" :max="max"></check>
<div id="first_line" class="fonts">
<h1>2048</h1>
<div id="score" class="font_white">&nbsp;</div>
<div id="score" class="font_white">
<shows name="最高数" :value="max"></shows>
<shows name="分数" :value="score"></shows>
</div>
</div>
<div id="last_line" class="fonts">
<div id="num">合并数字达到2048</div>
<button class="font_white">重新开始游戏</button>
<button class="font_white" @click="f5">重新开始游戏</button>
</div>
<div id="table">
<div id="table" ref="tables">
<buttom></buttom>
<move></move>
<move
@change-win="changeWin"
@change-max="changeMax"
@change-score="changeScore"
></move>
</div>
</template>
@ -68,11 +76,37 @@ button{
import buttom from './components/buttom.vue';
import move from './components/move.vue';
import check from './components/check.vue';
import { ref } from 'vue';
const show=ref(false);
const result=ref("")
const chenge=(shows:boolean,results:"胜利"|"失败")=>{
show.value=shows;
result.value=results;
import shows from './components/shows.vue';
import { onMounted, ref } from 'vue';
import gsap from 'gsap';
const f5=()=>{
location.replace(location.href)
}
const show = ref(false); //check
const result = ref(''); //check
const score = ref(0);
const max = ref(0);
const changeWin = (shows: boolean, results: '胜利' | '失败') => {
//move
show.value = shows;
result.value = results;
};
const changeScore = (newScore: number) => {
score.value = newScore;
};
const changeMax = (newMax: number) => {
max.value = newMax;
};
const tables=ref<HTMLElement>()
onMounted(()=>{
gsap.fromTo(tables.value!,{
opacity:0.3,
scale:0.8
},{
ease:"power1.out",
duration:0.8,
opacity:1,
scale:1
})
})
</script>

View File

@ -1,3 +1,5 @@
body{
background-color: #fcf6e3;
background-color: #f3f0e5;
width: 100vw;
height: 100vh;padding: 0;margin: 0;
}

View File

@ -1,61 +1,73 @@
<style scoped>
@media (max-width:400px) or (max-height:400px) {
*{
scale: 0.85;
transform-origin: center top;
}
}
.buttom {
background-color: #cec0b3;
background-color: #cdc1b4;
position: absolute;
padding: 0px 0px;
border: #857c71 solid 2px;
border-radius: 7px;
border-radius: 3px;
z-index: 0;
scale: 0;
width: 75px;
height: 75px;
width: 80px;
height: 80px;
left: 0px;
top: 0px;
scale: 0;
}
</style>
<template>
<transition-group :css="false" name="div" @enter="enter" tag="div" >
<div
v-for="obj in arr"
key="obj"
class="buttom"
v-show="obj.show"
:style="{ top: `${obj.y * 85 + 10}px`, left: `${obj.x * 85 + 10}px` }"
></div>
</transition-group>
<div v-for="obj in arr" ref="block" class="buttom"></div>
</template>
<script lang="ts" setup>
import { useEventListener } from '@vueuse/core';
import gsap from 'gsap';
import { onMounted, ref } from 'vue';
import timmer from '../ts/my_ts_pack';
type GSAPTweenTarget = string | Element | Element[] | object;
const arr = ref(
Array.from(Array(16), (_, index) => ({
show: false,
x: index % 4,
y: Math.floor(index / 4),
}))
);
const enter = (vDom: gsap.TweenTarget, done: () => void) => {
gsap.to(vDom, {
scale:1,
duration:1.2,
ease: "back.out(1.5)",
onComplete:done
});
};
onMounted(() => {
useEventListener(window, 'load', async () => {
for (let i = 0; i <= 7; i++) {
await timmer(120);
for (let j = 0; j <= i; j++) {
const y = j;
const x = i - j;
if (arr.value[y * 4 + x]!=undefined)
arr.value[y * 4 + x].show = true;
const block = ref<HTMLElement[]>([]);
const arr = Array.from({ length: 16 }, (_, index) => ({
id: index,
x: index % 4,
y: Math.floor(index / 4),
show(delay: number) {
const self = block.value[this.id];
const deltaX = this.x <= 1 ? 37 : -37;
const deltaY = this.y <= 1 ? 37 : -37;
gsap.fromTo(
self,
{
x: this.x * 95 + 12 + deltaX,
y: this.y * 95 + 12 + deltaY,
opacity:-0.5,
scale: 0,
},
{
ease: "power1.out",
x: this.x * 95 + 12,
y: this.y * 95 + 12,
opacity:1,
scale: 1,
duration: 0.8,
delay: delay,
}
}
});
});
);
},
}));
const animate = [
[5, 6, 9, 10],
[1, 2, 4, 8, 13, 14, 7, 11],
[0, 3, 12, 15],
];
const main = () => {
animate.forEach((arrs, delay) => {
arrs.forEach((index) => {
arr[index].show(delay * 0.17+0.3);
});
});
};
onMounted(main);
</script>

View File

@ -1,33 +1,44 @@
<style scoped>
@media (max-width: 400px) or (max-height: 400px) {
* {
transform-origin: center top;
}
#white {
scale: 3;
}
}
* {
position: absolute;
text-align: center;
width: 100%;
height: auto;
z-index: 30;
color: rgba(255, 255, 255, 0.65);
font-size: 200%;
}
#white {
width: 100%;
height: 100%;
background-color: rgba(125, 125, 125, 0.6);
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.7);
}
#win {
font-family: '楷体';
font-size: 500%;
top:10%;
top: 10%;
}
#time{
top: 50%;
#time {
top: 55%;
}
#tick{
top:53%
#tick {
top: 63%;
}
</style>
<template>
<Transition @enter="obj.white.animate">
<div id="white" v-show="obj.white.show">&nbsp;</div>
<div id="white" v-if="obj.white.show">&nbsp;</div>
</Transition>
<Transition @enter="obj.win.animate">
<div id="win" v-show="obj.white.show">
@ -35,22 +46,32 @@
</div>
</Transition>
<Transition @enter="obj.time.animate">
<div id="time" v-show="obj.white.show">时间(还未做),分数(还未做)</div>
<div id="time" v-show="obj.white.show">
最高数:{{ Math.floor(max.value) }}, 分数{{ Math.floor(score.value) }}
</div>
</Transition>
<Transition @enter="obj.tick.animate">
<div id="tick" v-show="obj.white.show">单击任意一处即可继续游戏</div>
<div id="tick" v-show="obj.white.show" @click="f5">
单击此处即可重新开始游戏
</div>
</Transition>
</template>
<script setup lang="ts">
import { reactive, ref, Transition, watch } from 'vue';
import { reactive, ref, watch } from 'vue';
import timmer from '../ts/my_ts_pack';
import gsap from 'gsap';
const f5 = () => {
location.replace(location.href);
};
const max = reactive({ value: 0 }),
score = reactive({ value: 0 });
type GSAPTweenTarget = string | Element | Element[] | object;
const props = defineProps<{
result: string;
show: boolean;
score: number;
max: number;
}>();
const obj = reactive({
white: {
@ -70,7 +91,7 @@ const obj = reactive({
opacity: 0,
duration: 2,
y: -1000,
ease: "bounce.out",
ease: 'bounce.out',
onComplete: done,
});
},
@ -81,9 +102,19 @@ const obj = reactive({
gsap.from(vDom, {
opacity: 0,
y: window.innerHeight,
duration: 1,
duration: 0.7,
onComplete: done,
});
gsap.to(max, {
duration: 2.3,
ease: 'power1.out',
value: props.max,
});
gsap.to(score, {
duration: 2.3,
ease: 'power1.out',
value: props.score,
});
},
},
tick: {
@ -92,7 +123,7 @@ const obj = reactive({
gsap.from(vDom, {
opacity: 0,
duration: 4,
ease:"power4.in",
ease: 'power4.in',
onComplete: done,
});
},

View File

@ -1,18 +1,30 @@
<style scoped>
@media (max-width:400px) or (max-height:400px) {
*{
scale: 0.85;
transform-origin: center top;
}
}
.move {
z-index: 10;
color: #736d64;
color: #776e65;
font-weight: bolder;
font-size: large;
font-size: 30px;
text-align: center;
line-height: 74px;
background-color: #efe0c9;
width: 74px;
height: 74px;
line-height: 80px;
background-color: #eee4da;
width: 80px;
height: 80px;
position: absolute;
margin: 0px 0px;
padding: 0px 0px;
border-radius: 7px;
border-radius: 3px;
}
.move[data-length='4'] {
font-size: 15px;
}
.move[data='T'] {
color: #f3f0e5;
}
</style>
<template>
@ -22,16 +34,37 @@
v-for="dom in block"
v-show="dom.value"
:style="{ zIndex: dom.merge === true ? 10 : 0 }"
:data-length="String(dom.value).length"
:data="dom.value >= 8 ? 'T' : 'F'"
>
{{ dom.value }}
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, reactive, ref } from 'vue';
import { computed, onMounted, reactive, ref, watch } from 'vue';
import gsap from 'gsap';
import timmer from '../ts/my_ts_pack';
import { useEventListener } from '@vueuse/core';
const scores=reactive({
max:0,
score:0
})
let score=0
const emits= defineEmits(["changeWin","changeScore","changeMax"])
const blockes = ref<HTMLElement[]>([]);
const colorMap = new Map([
[2, 'rgb(238, 228, 218)'],
[4, 'rgb(237, 224, 200)'],
[8, 'rgb(242, 177, 121)'],
[16, 'rgb(242, 177, 121)'],
[32, 'rgb(246, 124, 95)]'],
[64, 'rgb(246, 94, 59)'],
[128, 'rgb(237, 207, 114)'],
[256, 'rgb(237, 204, 97)'],
[512, 'rgb(237, 200, 80)'],
[1024, 'rgb(237, 197, 63)'],
[2048, 'rgb(237, 194, 46)'],
]);
const color = (value: number) => colorMap.get(value);
class Block {
constructor(
public index: number,
@ -39,21 +72,20 @@ class Block {
public to = { x: -1, y: -1 },
public value = 0,
public digitalValue = 0,
public endValue = 0,
public merge = false as boolean | Block,
public create = false
) {}
get positionForm() {
return {
x: this.from.x * 85 + 12,
y: this.from.y * 85 + 12,
x: this.from.x * 95 + 13,
y: this.from.y * 95 + 13,
};
}
get positionTo() {
return {
x: this.to.x * 85 + 12,
y: this.to.y * 85 + 12,
duration: 0.5, //
x: this.to.x * 95 + 13,
y: this.to.y * 95 + 13,
duration: 0.15, //
};
}
digitalMove(deltaY: number, deltaX: number) {
@ -65,33 +97,42 @@ class Block {
return;
}
if (target.digitalValue == this.digitalValue) {
if (this.merge == false) {
if (this.merge === false && target.merge === false) {
this.merge = target;
this.digitalValue = this.endValue = 0;
this.digitalValue = 0;
target.merge = true;
target.digitalValue *= 2;
target.endValue *= 2;
score+=target.digitalValue;
}
return;
}
}
allAnimation() {
const self = blockes.value[this.index];
const timeLine=gsap.timeline()
gsap.set(self, { backgroundColor: color(this.value) });
const timeLine = gsap.timeline();
timeLine.add(gsap.set(self, { scale: 1 }));
timeLine.add(gsap.fromTo(self, this.positionForm, this.positionTo), '<');
if (this.merge == true) {
this.value *= 2;
const self = blockes.value[this.index];
timeLine.add(gsap.to(self, { scale: 1.1, duration: 0.2 }));
timeLine.add(gsap.to(self, { scale: 1, duration: 0.2 }));
timeLine.add(
gsap.to(self, {
scale: 1.1,
duration: 0.1,
onStart: () => {
this.value = this.digitalValue;
gsap.set(self, { backgroundColor: color(this.value) });
},
})
);
timeLine.add(gsap.to(self, { scale: 1, duration: 0.1 }));
}
if (typeof this.merge == 'object') {
this.merge = false;
}
if (this.create) {
this.create = false;
gsap.from(self, { scale: 0, duration: 0.3 ,delay:0.1});
timeLine.add(gsap.from(self, { scale: 0, duration: 0.2 }));
gsap.to(self, { backgroundColor: color(this.value) });
}
}
syncing(target: 'from&to' | 'merge&xy') {
@ -111,11 +152,30 @@ class Block {
if (table.value[newY][newX] === null) {
this.from.x = this.to.x = newX;
this.from.y = this.to.y = newY;
this.digitalValue = this.value = this.endValue = 2;
this.digitalValue = this.value = 2;
break;
}
}
}
checkWoring() {
if (!this.digitalValue) return false;
const arr = [
[0, -1],
[0, 1],
[-1, 0],
[1, 0],
];
for (const [x, y] of arr) {
const target = table.value[this.to.y + y]?.[this.to.x + x];
if (target?.value == this.value&&target!=undefined)
return false;
}
return true;
}
checkWin(){
if(this.digitalValue) return true;
return false
}
}
const block = reactive(
Array.from({ length: 16 }, (_, index) => new Block(index))
@ -131,7 +191,9 @@ const table = computed<(Block | null)[][]>(() => {
return result;
});
const controller = async (e: KeyboardEvent) => {
let createFlag = true;
let createFlag = true,
woringFlag = true,
winFlag=false;
switch (e.key) {
case 'ArrowUp':
case 'ArrowDown':
@ -139,10 +201,10 @@ const controller = async (e: KeyboardEvent) => {
case 'ArrowRight':
block.forEach((element) => {
element.syncing('from&to');
const result = element.endValue;
element.value = element.digitalValue = result;
element.value = element.digitalValue;
element.create = element.merge = false;
});
break;
}
for (let i = 1; i <= 3; i++) {
switch (e.key) {
@ -170,17 +232,36 @@ const controller = async (e: KeyboardEvent) => {
createFlag = false;
}
}
block.forEach((element) => {
if (!element.value && createFlag) {
element.digitalCreate();
createFlag = false;
element.create=true;
if (createFlag){
let max=0;
block.forEach((element) => {
if (!element.value&&createFlag) {
element.digitalCreate();
createFlag = false;
element.create = true;
}
element.syncing('merge&xy');
element.allAnimation();
element.create = false;
if (!element.checkWoring()) woringFlag = false;
if(element.checkWin()) winFlag =false;
if(element.digitalValue>max) max=element.digitalValue
});
scores.max=max
scores.score=score
if(winFlag){
emits("changeWin",true,"胜利")
}else if(woringFlag){
emits("changeWin",true,"失败")
}
element.syncing('merge&xy');
element.allAnimation();
element.create=false;
});
}
};
watch(()=>scores.score,(newValue)=>{
emits("changeScore",newValue)
})
watch(()=>scores.max,(newValue)=>{
emits("changeMax",newValue)
})
onMounted(() => {
useEventListener(window, 'keydown', controller);
});

45
src/components/shows.vue Normal file
View File

@ -0,0 +1,45 @@
<style scoped>
#sd div {
background-color: #beb1a0;
width: 80px;
height: 50%;
font-size: large;
}
#sd {
margin-top: 10px;
margin-left: 10px;
float: right;
border: #857c71 solid 2px;
border-radius: 3px;
}
</style>
<template>
<div id="sd">
<div>{{ props.name }}</div>
<div ref="value">{{ Math.floor(num.value) }}</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, watch } from 'vue';
import gsap from 'gsap';
const value = ref<HTMLElement>();
const props = defineProps<{
name: string;
value: number;
}>();
let num = reactive({
value:0
})
watch(
() => props.value,
(newValue) => {
gsap.to(num, {
value: newValue,
ease: 'power1.out',
duration: 0.7,
});
}
);
</script>