1 Star 0 Fork 0

jimgo/Three Plus One Game

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
machine animation svg1 reduce.html 21.41 KB
一键复制 编辑 原始数据 按行查看 历史
jimgo 提交于 2024-07-14 16:00 . 多路搞定
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Animation Aemonstrates The Process</title>
</head>
<style>
body {
font-family: Consolas, "Courier New", monospace;
font-size: 24px;
min-width: 1080px;
background-color: #333;
color: #fff;
}
header {
width: 100%;
p {
margin-block-start: 1.2rem;
margin-block-end: 1.2rem;
text-align: center;
font-size: 2rem;
line-height: 2rem;
font-weight: bolder;
input {
width: 36rem;
font-size: 2rem;
line-height: 2rem;
text-align: center;
border-radius: 0.4rem;
letter-spacing: 0.4rem;
}
}
}
footer {
margin: 0 auto;
min-height: 4em;
width: 1080px;
word-break: break-word;
word-wrap: break-word;
}
svg#ANIMATOR {
display: block;
margin: 0 auto;
width: 1080px;
height: 360px;
background-color: #fff;
border-radius: 6px;
.upper {
width: 80px;
height: 80px;
z-index: 1;
stroke: #f33;
stroke-width: 8px;
fill: #fff;
&[val="0"] {
fill: #fff;
}
&[val="1"] {
fill: #666;
}
&[val="-1"] {
fill: #0000;
stroke: #0000;
}
}
.lower {
width: 72px;
height: 64px;
z-index: 2;
stroke: #666;
stroke-width: 4px;
fill: #666;
&[val="0"] {
opacity: 0;
}
&[val="1"] {
opacity: 1;
}
}
.plate {
width: 80px;
height: 12px;
z-index: 1;
stroke: #333;
stroke-width: 2px;
fill: #333;
&[val="0"] {
opacity: 0;
}
&[val="1"] {
opacity: 1;
}
}
}
</style>
<body>
<header>
<p>
<input id="iDec" type="number" placeholder="Enter Decimal Integer" />
</p>
<p>
<input id="iTer" readonly placeholder=" The Ternary Integer " />
</p>
</header>
<main>
<svg
id="ANIMATOR"
viewBox="0 0 1080 360"
preserveAspectRatio="xMidYMid meet"
>
<g id="UPPER_STACK"></g>
<g id="LOWER_STACK"></g>
<g id="PLATE_STACK"></g>
</svg>
</main>
<footer id="ft"></footer>
</body>
<script>
"use strict";
// 隐藏偏移量
const HIDE_LENGTH = 1 << 16;
// 块宽
const TYTE_WITH = 100;
// 消退时长
const CLEAR_DUR = 50;
// 移动时长
const MOVE_DUR = 100;
// 创建SVG元素
const createSVGelm = (type = "rect") =>
document.createElementNS("http://www.w3.org/2000/svg", type);
// 十进制输入框
const iDec = document.getElementById("iDec");
// 三进制输出框
const iTer = document.getElementById("iTer");
// 底部输出
const ft = document.getElementById("ft");
// 全局变量三进制字符串
var intTStr = "";
// 十进制输入监听,转换为三进制,并触发后续操作
iDec.addEventListener("change", async () => {
let dec = parseInt(iDec.value);
if (dec < 0 || isNaN(dec)) {
return alert("Invalid Input");
} else if (dec > 60000) {
return alert("Input too large");
}
intTStr = iTer.value = dec.toString(3);
await Tyte.init();
await Tyte.run();
});
const ANIMATOR = document.getElementById("ANIMATOR");
/**
*
* SVG control element class, used for creating and controlling SVG elements in the animator.
*
*/
class SVGctrlElm {
static stack = ANIMATOR;
/**
* 根据传入的参数获取一个对象实例。
* 此函数旨在实现对象的重用,通过从一个预定义的父节点中获取可用的对象,或者在没有可用对象时创建新对象。
* 这种做法有助于减少内存分配和垃圾收集的开销,特别是在频繁创建和销毁对象的场景中。
*
* @param {Object} param0 函数的参数对象。
* @param {HTMLElement} param0.parentNode - 默认为ANIMATOR,表示对象的父节点。这个父节点用于存储和管理对象实例。
* @param {Function} param0.cls - 默认为SVGctrlElm,表示对象的构造函数。这个构造函数用于创建新对象。
* @param {number} param0.val - 默认为0,表示对象的值。这个值可以被设置到重新使用或新创建的对象上。
* @param {number} param0.idx - 默认为0,表示对象的索引。这个索引可以被设置到重新使用或新创建的对象上。
* @returns {Object} 返回一个SVGctrlElm类型的对象实例。
*/
static getObject({
stack = ANIMATOR,
parentNode = ANIMATOR,
cls = SVGctrlElm,
val = 0,
idx = 0,
}) {
// 检查栈中是否有可用的对象
if (stack?.children?.[0]?.obj instanceof cls) {
// 从栈中获取第一个对象
let obj = stack.children[0].obj;
// 设置对象的值为传入的参数值
obj.val = val;
obj.idx = idx;
obj.elm.setAttribute("opacity", 1);
// 将对象从栈中移除并添加到父节点
stack.removeChild(stack.children[0]);
parentNode.appendChild(obj.elm);
// 返回复用的对象
return obj;
} else {
// 栈为空时,创建新对象
return new cls({ parentNode, val, idx });
}
}
/**
* Constructor, creates an SVG element.
* @param {HTMLElement} parentNode - The parent node where the SVG element will be appended, default is the animator.
* @param {string} type - The type of SVG element to create, default is "rect" (rectangle).
*/
constructor({ parentNode = ANIMATOR, type = "rect", val = 0, idx = 0 }) {
this.parentNode = parentNode;
this.elm = createSVGelm(type);
this.parentNode.appendChild(this.elm);
this.val = val;
this.idx = idx;
this.elm.obj = this;
this.elm.classList.add(this.constructor.name.toLowerCase());
}
get x() {
return parseInt(this.elm.getAttribute("x")) || 0;
}
set x(x) {
if (x === this.x) return null;
this.elm.setAttribute("x", x);
}
get y() {
return parseInt(this.elm.getAttribute("y")) || 0;
}
set y(y) {
if (y === this.y) return null;
this.elm.setAttribute("y", y);
}
get val() {
return this._val;
}
set val(v) {
if (v === this._val) return null;
this._val = v;
this.elm.setAttribute("val", v);
}
get idx() {
return this._idx;
}
set idx(i) {
if (i === this._idx) return null;
this._idx = i;
this.elm.setAttribute("idx", i);
}
/**
* Initiates an animation request.
* @param {Object} options - Animation configuration options.
* @param {Function} options.initFn - The function to execute at the beginning of the animation.
* @param {Function} options.runFn - The function to execute during the animation, receives the animation progress as a parameter.
* @param {Function} options.endFn - The function to execute at the end of the animation.
* @param {number} options.duration - The duration of the animation in milliseconds, default is 100.
* @returns {Promise} Returns a Promise that resolves when the animation ends.
*/
async requestAnimation({
initFn = () => {},
runFn = () => {},
endFn = () => {},
duration = 100,
}) {
if (!this.elm) return Promise.resolve;
return new Promise((rsl, rjt) => {
let startTime;
// parameter: time will auto inject by requestAnimationFrame
const render = (time) => {
if (!startTime) {
startTime = time;
initFn();
}
// calculate running progress
const progress = (time - startTime) / duration;
// request next frame while not reach 100%
if (progress < 1) {
window.requestAnimationFrame(render);
// do animation here
runFn(progress);
} else {
endFn();
// Promise result
rsl(true);
}
};
// animation begin.
window.requestAnimationFrame(render);
});
}
/**
* Clears the SVG element by animating its opacity to 0 and then removing it from the parent node.
* @param {number} duration - The duration of the clear animation in milliseconds, default is 100.
* @returns {Promise} Returns a Promise that resolves after the element is cleared.
*/
async clear(stack = ANIMATOR, duration = CLEAR_DUR) {
return this.requestAnimation({
duration,
runFn: (progress) => {
this.elm.setAttribute("opacity", 1 - progress);
},
endFn: () => {
this.elm.setAttribute("opacity", 0);
this.x = HIDE_LENGTH;
this.y = HIDE_LENGTH;
this.elm.parentNode.removeChild(this.elm);
stack.appendChild(this.elm);
},
});
}
/**
* 将元素移动到指定的位置。
*
* @param {number} bX 目标元素的横坐标。
* @param {number} bY 目标元素的纵坐标。
* @param {number} duration 移动过程的持续时间,单位为毫秒。
*/
async moveTo(bX = 0, bY = 0, duration = MOVE_DUR) {
const [x0, y0] = [this.x, this.y];
// 计算元素需要移动的横向和纵向距离。
const [dX, dY] = [bX - x0, bY - y0];
// 请求动画帧,逐步移动元素到目标位置。
return this.requestAnimation({
duration,
runFn: (progress) => {
// 根据移动进度更新元素的横纵坐标。
this.x = x0 + dX * progress;
this.y = y0 + dY * progress;
},
endFn: () => {
// 动画结束时,确保元素的坐标更新为目标坐标。
this.x = bX;
this.y = bY;
},
});
}
/**
* 将元素相对当前位置移动指定的距离。
*
* @param {number} dX 元素需要增加的横坐标距离。
* @param {number} dY 元素需要增加的纵坐标距离。
* @param {number} duration 移动过程的持续时间,单位为毫秒。
*/
async moveBy(dX = 0, dY = 0, duration = MOVE_DUR) {
const [x0, y0] = [this.x, this.y];
// 获取当前元素的横坐标和纵坐标,如果不存在则默认为0。
// 请求动画帧,逐步移动元素到目标位置。
return this.requestAnimation({
duration,
runFn: (progress) => {
// 根据移动进度更新元素的横纵坐标。
this.x = x0 + dX * progress;
this.y = y0 + dY * progress;
},
endFn: () => {
// 动画结束时,确保元素的坐标相对于初始位置增加了指定的距离。
this.x += dX;
this.y += dY;
},
});
}
}
/**
* 继承自SVGctrlElm,用于创建上方元素SVG控制类。
* 用单例进行控制
*/
class Upper extends SVGctrlElm {
static Y_BIAS = 28;
static X_BIAS = 10;
static array = [];
static stack = document.getElementById("UPPER_STACK");
static getObject({ parentNode = Upper.stack, val = 0, idx = 0 }) {
return SVGctrlElm.getObject({
stack: Upper.stack,
parentNode: parentNode,
cls: Upper,
val: val,
idx: idx,
});
}
constructor({ parentNode = ANIMATOR, val = 0, idx = 0 }) {
super({ parentNode, type: "rect", val, idx });
}
async aline(idx = this._idx, duration = MOVE_DUR) {
return this.moveTo(
idx * TYTE_WITH + Upper.X_BIAS,
Upper.Y_BIAS,
duration
);
}
async clear(duration = CLEAR_DUR) {
return super.clear(Upper.stack, duration);
}
}
/**
* 继承自SVGctrlElm,用于创建下方元素SVG控制类。
*/
class Lower extends SVGctrlElm {
static X_BIAS = 14;
static U_Y = 76;
static A_Y = 136;
static B_Y = 228;
static array = [];
static stack = document.getElementById("LOWER_STACK");
static getObject({
parentNode = Lower.stack,
val = 0,
idx = 0,
pos = 0,
}) {
let obj = SVGctrlElm.getObject({
stack: Lower.stack,
parentNode: parentNode,
cls: Lower,
val: val,
idx: idx,
});
obj.pos = pos;
return obj;
}
constructor({ parentNode = Lower.stack, val = 0, idx = 0, pos = 0 }) {
super({ parentNode, type: "rect", val, idx });
this._pos = pos;
}
get pos() {
return this._pos;
}
set pos(c) {
this._pos = c;
this.elm.setAttribute("pos", this._pos);
}
async aline(idx = this._idx) {
let y = this._pos ? Lower.B_Y : Lower.A_Y;
return this.moveTo(idx * TYTE_WITH + Lower.X_BIAS, y, 0);
}
async clear(duration = CLEAR_DUR) {
return super.clear(Lower.stack, duration);
}
async liftU(duration = MOVE_DUR) {
return this.moveTo(this.x, Lower.U_Y, duration);
}
async fallA(lift = true, duration = MOVE_DUR) {
if (lift) await this.liftU(0);
return this.moveTo(this.x, Lower.A_Y, duration);
}
async fallB(lift = true, duration = MOVE_DUR) {
if (lift) await this.liftU(0);
return this.moveTo(this.x, Lower.B_Y, duration);
}
}
/**
* `Plate`类继承自`SVGctrlElm`,用于创建底盘SVG控制类。
*/
class Plate extends SVGctrlElm {
static X_BIAS = 10;
static Y_BIAS = 312;
static array = [];
static stack = document.getElementById("PLATE_STACK");
static getObject({ parentNode = Plate.stack, val = 0, idx = 0 }) {
return SVGctrlElm.getObject({
stack: Plate.stack,
parentNode: parentNode,
cls: Plate,
val: val,
idx: idx,
});
}
constructor({ parentNode = Plate.stack, val = 0, idx = 0 }) {
super({ parentNode, type: "rect", val, idx });
}
async aline(idx = this._idx) {
return this.moveTo(idx * TYTE_WITH + Plate.X_BIAS, Plate.Y_BIAS, 0);
}
async clear(duration = 100) {
return super.clear(Plate.stack, duration);
}
}
/**
* `Tyte`类用于创建一个Tyte对象,该对象包含一个上、两个下、一个底盘元素。
*/
class Tyte {
static array = [];
static valMap = {
// 0: { upper: 0, lowerA: 0, lowerB: 0, plate: 1 },
// 6: { upper:0, lowerA:0, lowerB:0, plate:0 },
0: [0, 0, 0, 1],
1: [0, 0, 1, 1],
2: [0, 1, 1, 1],
3: [1, 0, 0, 1],
4: [1, 0, 1, 1],
5: [1, 1, 1, 1],
};
static async init() {
ft.textContent = intTStr;
// 清空原队列
await Promise.all(Tyte.array.map(async (t) => await t.clear(10)));
Tyte.array.length = 0;
// 在Tyte.constructor中已添加入队列array
// 无需再标注入队
return await Promise.all(
[...intTStr]
.map((s) => new Tyte(s))
.map(async (t, i) => await t.aline(i))
);
}
static async run() {
if (Tyte.array.length <= 1) return null;
// 当前Upper长
const UL = Upper.array.length;
const TL = Tyte.array.length;
// 当前总偏移量
const BUT = UL - TL;
await Tyte.array.reduce(async (pu, t, i) => {
console.log("pu", await pu);
// 新增队末元素不处理
if (i === TL) return await pu;
// ---------- 每次循环前更新队列索引-----------
t.freshIdx(i);
// ---------- 移除队首的零 ----------
if (i === 0 && t.val === 0) {
await Tyte.array[0].clear();
Tyte.array.shift();
// --------- 全对齐 ---------
await Tyte.alineAll();
// --------- pu后移 ----------
return await pu;
}
const u = await pu;
// --------- upper 恢复显示 ---------
u.val = u.val === -1 ? 0 : u.val;
// --------- 接入前项传入的upper ---------
t.addUpper(u);
// --------- upper对齐 ---------
await u.aline(i);
// --------- 队中计算 ---------
await t.handleLowers();
// 不可简单同步调用,要上锁
// if (Tyte.array.length >= 2) Tyte.run();
// ---------- 队末处理 ----------
if (i === TL - 1) {
// ---------- 按条件插入“2” ----------
if (u.val === 1) {
let tn = new Tyte(2);
await tn.aline(TL);
}
// ---------- 输出 ----------
ft.textContent += ` -> ${Tyte.array
.map((t) => t.lowerA.val + t.lowerB.val)
.join("").replace(/^0+/, "")}`;
// ---------- Upper回栈 ----------
u.clear();
// ---------- 退出处理 ----------
return null;
}
// ---------- 队中处理 ----------
t.removeUpper();
// ---------- 将计算完毕的 upper 后抛传入 ----------
return u;
}, Promise.resolve(Upper.getObject({ parentNode: ANIMATOR, val: -1, idx: 0 })));
// -------- 递归 --------
if (Tyte.array.length >= 2) return Tyte.run();
return null;
}
static async alineAll() {
return await Promise.all(
Tyte.array.map(async (t, i) => {
t.freshIdx(i);
return await t.aline(i);
})
);
}
constructor(val) {
this._val = val;
// Tyte对象必须要进数组,未使用的Tyte对象需要销毁
Tyte.array.push(this);
this._idx = Tyte.array.length - 1;
let pVals = Tyte.valMap[val];
// Upper对象并不要求初始化时就创建
this.upper = null;
this.lowerA = Lower.getObject({
parentNode: ANIMATOR,
val: pVals[1],
idx: this._idx,
pos: 0,
});
this.lowerB = Lower.getObject({
parentNode: ANIMATOR,
val: pVals[2],
idx: this._idx,
pos: 1,
});
// Lower.array.push([this.lowerA, this.lowerB]);
this.plate = Plate.getObject({
parentNode: ANIMATOR,
val: 1,
idx: this._idx,
});
// Plate.array.push(this.plate);
// 方便获取所有部件
this.parts = [this.lowerA, this.lowerB, this.plate];
}
get val() {
this._val =
parseInt(this.lowerA.val) +
parseInt(this.lowerB.val) +
3 * parseInt(this.upper?.val || 0);
return this._val;
}
get idx() {
return this._idx;
}
freshIdx(idx) {
this._idx = idx;
this.parts.forEach((p) => (p.idx = idx));
}
addUpper(upper) {
this.upper = upper;
this.upper.idx = this.idx;
this.parts.push(this.upper);
}
removeUpper() {
this.parts = this.parts.filter((p) => p !== this.upper);
this.upper = null;
}
async aline(idx) {
return await Promise.all(
this.parts.map(async (p) => await p.aline(idx))
);
}
async shiftTyte(width = TYTE_WITH, duration = MOVE_DUR) {
return await Promise.all(
this.parts.map(async (p) => await p.moveBy(-1 * width, 0, duration))
);
}
async clear(duration = CLEAR_DUR) {
return await Promise.all(
this.parts.map(async (p) => await p.clear(duration))
);
}
async handleLowers(duration = MOVE_DUR) {
if (!this.upper) return null;
if (this.upper.val === 0) {
if (this.lowerA.val === 0 && this.lowerB.val === 0) {
return null;
}
if (this.lowerA.val === 0 && this.lowerB.val === 1) {
await this.lowerB.liftU(true, duration);
this.upper.val = 1;
this.lowerB.val = 0;
return null;
}
if (this.lowerA.val === 1 && this.lowerB.val === 1) {
await this.lowerA.fallB(false, duration);
this.lowerA.val = 0;
return null;
}
} else if (this.upper.val === 1) {
if (this.lowerA.val === 0 && this.lowerB.val === 0) {
this.lowerB.val = 1;
await this.lowerB.fallB(true, duration);
return null;
}
if (this.lowerA.val === 0 && this.lowerB.val === 1) {
this.lowerA.val = 1;
await this.lowerA.fallA(true, duration);
this.upper.val = 0;
return null;
}
if (this.lowerA.val === 1 && this.lowerB.val === 1) {
return null;
}
}
}
}
</script>
</html>
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
JavaScript
1
https://gitee.com/jimgo/ThreePlusOneGame.git
git@gitee.com:jimgo/ThreePlusOneGame.git
jimgo
ThreePlusOneGame
Three Plus One Game
master

搜索帮助

0d507c66 1850385 C8b1a773 1850385