游戏添加

This commit is contained in:
zhx 2026-06-11 23:09:39 +08:00
parent d4e41b1423
commit f2eb343cf7
10 changed files with 1760 additions and 190 deletions

View File

@ -5,6 +5,7 @@
var scriptCache = {}; var scriptCache = {};
var pagInitCache = {}; var pagInitCache = {};
var videoLayoutCache = {}; var videoLayoutCache = {};
var svgaVideoItemCache = {};
var MIN_SPLIT_MATCH_FRAMES = 3; var MIN_SPLIT_MATCH_FRAMES = 3;
var MIN_USEFUL_FRAME_BRIGHTNESS = 0.035; var MIN_USEFUL_FRAME_BRIGHTNESS = 0.035;
var MIN_SPLIT_SATURATION_GAP = 0.08; var MIN_SPLIT_SATURATION_GAP = 0.08;
@ -41,6 +42,23 @@
return scriptCache[url]; return scriptCache[url];
} }
function loadSVGAItemOnce(sourceURL, canvas, options) {
if (svgaVideoItemCache[sourceURL]) return svgaVideoItemCache[sourceURL];
svgaVideoItemCache[sourceURL] = loadScriptOnce(options.svgaScript)
.then(function () {
if (!window.SVGA) throw new Error('svga_unavailable');
return new Promise(function (resolve, reject) {
var parser = new window.SVGA.Parser(canvas);
parser.load(sourceURL, resolve, reject);
});
})
.catch(function (error) {
delete svgaVideoItemCache[sourceURL];
throw error;
});
return svgaVideoItemCache[sourceURL];
}
function waitForWindowLoad() { function waitForWindowLoad() {
if (document.readyState === 'complete') return Promise.resolve(); if (document.readyState === 'complete') return Promise.resolve();
return new Promise(function (resolve) { return new Promise(function (resolve) {
@ -49,7 +67,9 @@
} }
function extensionFromURL(url) { function extensionFromURL(url) {
var clean = String(url || '').split('?')[0].split('#')[0]; var clean = String(url || '')
.split('?')[0]
.split('#')[0];
var match = clean.match(/\.([a-z0-9]+)$/i); var match = clean.match(/\.([a-z0-9]+)$/i);
return match ? match[1].toLowerCase() : ''; return match ? match[1].toLowerCase() : '';
} }
@ -87,10 +107,7 @@
var separator = base.indexOf('?') >= 0 ? '&' : '?'; var separator = base.indexOf('?') >= 0 ? '&' : '?';
return base + separator + 'url=' + encodeURIComponent(url); return base + separator + 'url=' + encodeURIComponent(url);
} }
if ( if (window.HyAppAPI && typeof window.HyAppAPI.buildURL === 'function') {
window.HyAppAPI &&
typeof window.HyAppAPI.buildURL === 'function'
) {
return window.HyAppAPI.buildURL('/api/v1/media/effects/proxy', { return window.HyAppAPI.buildURL('/api/v1/media/effects/proxy', {
url: url, url: url,
}); });
@ -456,8 +473,7 @@
) { ) {
return; return;
} }
var alphaStats = var alphaStats = first.saturation < second.saturation ? first : second;
first.saturation < second.saturation ? first : second;
var saturationGap = Math.abs(second.saturation - first.saturation); var saturationGap = Math.abs(second.saturation - first.saturation);
if ( if (
saturationGap < MIN_SPLIT_SATURATION_GAP || saturationGap < MIN_SPLIT_SATURATION_GAP ||
@ -505,9 +521,7 @@
var width = Math.max(1, video.videoWidth || 1); var width = Math.max(1, video.videoWidth || 1);
var height = Math.max(1, video.videoHeight || 1); var height = Math.max(1, video.videoHeight || 1);
return { return {
kind: isHorizontal kind: isHorizontal ? 'splitAlphaHorizontal' : 'splitAlphaVertical',
? 'splitAlphaHorizontal'
: 'splitAlphaVertical',
hasAlphaMask: true, hasAlphaMask: true,
hasNativeAlpha: false, hasNativeAlpha: false,
contentAspectRatio: isHorizontal contentAspectRatio: isHorizontal
@ -584,14 +598,14 @@
} }
return ( return (
detectPackedAlphaTopRight(packedFrames) || detectPackedAlphaTopRight(packedFrames) ||
sampleVideoFrames(video, 'split', maxWidth).then(function ( sampleVideoFrames(video, 'split', maxWidth).then(
splitFrames function (splitFrames) {
) { return (
return ( detectSplitAlpha(splitFrames, video) ||
detectSplitAlpha(splitFrames, video) || fullFrameLayout(video, 'normal', false)
fullFrameLayout(video, 'normal', false) );
); }
}) )
); );
}) })
.then(function (layout) { .then(function (layout) {
@ -603,7 +617,11 @@
function rectToPixels(rect, width, height) { function rectToPixels(rect, width, height) {
var x = clamp(Math.round(rect.x * width), 0, width - 1); var x = clamp(Math.round(rect.x * width), 0, width - 1);
var y = clamp(Math.round(rect.y * height), 0, height - 1); var y = clamp(Math.round(rect.y * height), 0, height - 1);
var right = clamp(Math.round((rect.x + rect.width) * width), x + 1, width); var right = clamp(
Math.round((rect.x + rect.width) * width),
x + 1,
width
);
var bottom = clamp( var bottom = clamp(
Math.round((rect.y + rect.height) * height), Math.round((rect.y + rect.height) * height),
y + 1, y + 1,
@ -793,7 +811,12 @@
return video.play().catch(function () {}); return video.play().catch(function () {});
}; };
EffectPlayer.prototype._playCanvasVideo = function (url, options, video, layout) { EffectPlayer.prototype._playCanvasVideo = function (
url,
options,
video,
layout
) {
var self = this; var self = this;
if (!this._isCurrent(url)) return Promise.resolve(); if (!this._isCurrent(url)) return Promise.resolve();
var size = layoutCanvasSize(video, layout, options); var size = layoutCanvasSize(video, layout, options);
@ -831,10 +854,9 @@
video.load(); video.load();
}; };
draw(); draw();
return seekVideo(video, 0) return seekVideo(video, 0).then(function () {
.then(function () { return video.play().catch(function () {});
return video.play().catch(function () {}); });
});
}; };
EffectPlayer.prototype._playVideo = function (url, options) { EffectPlayer.prototype._playVideo = function (url, options) {
@ -860,39 +882,27 @@
EffectPlayer.prototype._playSVGA = function (url, options) { EffectPlayer.prototype._playSVGA = function (url, options) {
var self = this; var self = this;
return loadScriptOnce(options.svgaScript).then(function () { var sourceURL = proxyBinaryURL(url, 'svga', options);
if (!self._isCurrent(url)) return undefined; var size = mediaSize(self.container, options);
if (!window.SVGA) throw new Error('svga_unavailable'); var canvas = document.createElement('canvas');
var sourceURL = proxyBinaryURL(url, 'svga', options); canvas.width = size.width;
var size = mediaSize(self.container, options); canvas.height = size.height;
var canvas = document.createElement('canvas'); styleNode(canvas, options);
canvas.width = size.width; self._mount(canvas);
canvas.height = size.height; return loadSVGAItemOnce(sourceURL, canvas, options).then(
styleNode(canvas, options); function (videoItem) {
self._mount(canvas); if (!self._isCurrent(url)) return undefined;
var player = new window.SVGA.Player(canvas); if (!window.SVGA) throw new Error('svga_unavailable');
var parser = new window.SVGA.Parser(canvas); var player = new window.SVGA.Player(canvas);
self.cleanup = function () { self.cleanup = function () {
if (player.stopAnimation) player.stopAnimation(); if (player.stopAnimation) player.stopAnimation();
if (player.clear) player.clear(); if (player.clear) player.clear();
}; };
return new Promise(function (resolve, reject) { player.setVideoItem(videoItem);
parser.load( player.startAnimation();
sourceURL, return undefined;
function (videoItem) { }
if (!self._isCurrent(url)) { );
if (player.stopAnimation) player.stopAnimation();
resolve();
return;
}
player.setVideoItem(videoItem);
player.startAnimation();
resolve();
},
reject
);
});
});
}; };
EffectPlayer.prototype._playPAG = function (url, options) { EffectPlayer.prototype._playPAG = function (url, options) {
@ -991,6 +1001,20 @@
create: function (target, options) { create: function (target, options) {
return new EffectPlayer(target, options); return new EffectPlayer(target, options);
}, },
preload: function (url, options) {
var normalized = extend(
{
svgaScript: DEFAULT_SVGA_SCRIPT,
proxy: true,
},
options || {}
);
var type = inferType(url, normalized.type);
if (type !== 'svga') return Promise.resolve();
var canvas = document.createElement('canvas');
var sourceURL = proxyBinaryURL(url, 'svga', normalized);
return loadSVGAItemOnce(sourceURL, canvas, normalized);
},
play: function (target, url, options) { play: function (target, url, options) {
var player = new EffectPlayer(target, options); var player = new EffectPlayer(target, options);
return player.play(url).then(function () { return player.play(url).then(function () {

255
common/game-preloader.js Normal file
View File

@ -0,0 +1,255 @@
(function () {
var imageCache = {};
var binaryCache = {};
var imagePattern = /\.(png|jpe?g|gif|webp|svg|avif)(\?|#|$)/i;
var svgaPattern = /\.svga(\?|#|$)/i;
function each(nodes, iteratee) {
Array.prototype.forEach.call(nodes || [], iteratee);
}
function normalizeURL(url, baseURL) {
var value = String(url || '').trim();
if (
!value ||
value.indexOf('data:') === 0 ||
value.indexOf('blob:') === 0
) {
return '';
}
try {
return new URL(value, baseURL || window.location.href).href;
} catch (_) {
return '';
}
}
function addURL(list, seen, url, baseURL) {
var normalized = normalizeURL(url, baseURL);
if (!normalized || seen[normalized]) return;
seen[normalized] = true;
list.push(normalized);
}
function collectFromCSSText(list, seen, text) {
var pattern = /url\((['"]?)(.*?)\1\)/g;
var match;
while ((match = pattern.exec(text || ''))) {
addURL(list, seen, match[2]);
}
}
function collectSrcSet(list, seen, value) {
String(value || '')
.split(',')
.forEach(function (item) {
addURL(list, seen, item.trim().split(/\s+/)[0]);
});
}
function collectDocumentURLs(list, seen) {
each(document.querySelectorAll('img[src]'), function (img) {
addURL(list, seen, img.getAttribute('src'));
});
each(document.querySelectorAll('source[srcset]'), function (source) {
collectSrcSet(list, seen, source.getAttribute('srcset'));
});
each(document.querySelectorAll('style'), function (node) {
collectFromCSSText(list, seen, node.textContent || '');
});
each(document.querySelectorAll('[style]'), function (node) {
collectFromCSSText(list, seen, node.getAttribute('style') || '');
});
each(document.styleSheets, function (sheet) {
try {
each(sheet.cssRules || [], function (rule) {
collectFromCSSText(list, seen, rule.cssText || '');
});
} catch (_) {}
});
}
function collectResourceURLs(assetURLs) {
var list = [];
var seen = {};
(assetURLs || []).forEach(function (url) {
addURL(list, seen, url);
});
collectDocumentURLs(list, seen);
return list;
}
function isImageURL(url) {
return imagePattern.test(String(url || ''));
}
function isSVGAURL(url) {
return svgaPattern.test(String(url || ''));
}
function preloadImage(url) {
var normalized = normalizeURL(url);
if (!normalized) return Promise.resolve(false);
if (imageCache[normalized]) return imageCache[normalized];
imageCache[normalized] = new Promise(function (resolve) {
var image = new Image();
image.onload = function () {
resolve(true);
};
image.onerror = function () {
resolve(false);
};
image.decoding = 'async';
image.src = normalized;
if (image.complete) resolve(true);
});
return imageCache[normalized];
}
function preloadBinary(url) {
var normalized = normalizeURL(url);
if (!normalized) return Promise.resolve(false);
if (binaryCache[normalized]) return binaryCache[normalized];
binaryCache[normalized] = window
.fetch(normalized, { cache: 'force-cache' })
.then(function (response) {
return response.ok;
})
.catch(function () {
return false;
});
return binaryCache[normalized];
}
function preloadSVGA(url, options) {
options = options || {};
if (
window.HyAppEffectPlayer &&
typeof window.HyAppEffectPlayer.preload === 'function'
) {
return window.HyAppEffectPlayer.preload(url, {
type: 'svga',
svgaScript: options.svgaScript,
waitForWindowLoad: options.waitForWindowLoad === true,
})
.then(function () {
return true;
})
.catch(function () {
return preloadBinary(url);
});
}
return preloadBinary(url);
}
function preloadResource(url, options) {
if (isSVGAURL(url)) return preloadSVGA(url, options && options.svga);
if (isImageURL(url)) return preloadImage(url);
return preloadBinary(url);
}
function setProgress(progress) {
var options =
typeof progress === 'number'
? { progress: progress }
: progress || {};
var root = options.root || document.documentElement;
var safeProgress = Math.min(
Math.max(Number(options.progress) || 0, 0),
1
);
var progressNode =
options.progressNode ||
document.querySelector('[data-role="loading-progress"]');
root.style.setProperty('--loading-progress', safeProgress.toFixed(4));
if (progressNode) {
progressNode.setAttribute(
'aria-valuenow',
String(Math.round(safeProgress * 100))
);
}
}
function runTasks(tasks, onProgress) {
var completed = 0;
var total = Math.max((tasks && tasks.length) || 0, 1);
var update =
typeof onProgress === 'function'
? onProgress
: function (value) {
setProgress(value);
};
update(0);
if (!tasks || !tasks.length) {
update(1);
return Promise.resolve([]);
}
// 资源预加载是体验优化,单个资源失败不应该卡死大厅;失败项会返回 false进度仍然推进。
return Promise.all(
tasks.map(function (task) {
return Promise.resolve()
.then(task)
.catch(function () {
return false;
})
.then(function (value) {
completed += 1;
update(completed / total);
return value;
});
})
);
}
function complete(options) {
options = options || {};
var loadingScreen =
options.loadingScreen ||
document.querySelector('[data-role="loading-screen"]');
setProgress({
root: options.root,
progress: 1,
progressNode: options.progressNode,
});
if (loadingScreen) {
loadingScreen.classList.add('is-complete');
loadingScreen.setAttribute('aria-hidden', 'true');
}
if (options.frame) {
options.frame.classList.remove('is-preloading');
}
}
function once(task) {
var promise = null;
return function () {
if (!promise) {
promise = Promise.resolve().then(task);
}
return promise;
};
}
function delay(ms) {
return new Promise(function (resolve) {
window.setTimeout(resolve, ms);
});
}
window.HyAppGamePreloader = {
addURL: addURL,
collectResourceURLs: collectResourceURLs,
complete: complete,
delay: delay,
isImageURL: isImageURL,
isSVGAURL: isSVGAURL,
normalizeURL: normalizeURL,
once: once,
preloadBinary: preloadBinary,
preloadImage: preloadImage,
preloadResource: preloadResource,
preloadSVGA: preloadSVGA,
runTasks: runTasks,
setProgress: setProgress,
};
})();

View File

@ -24,11 +24,13 @@
"rockGame.start": "ابدأ", "rockGame.start": "ابدأ",
"rockGame.matching": "جارٍ البحث...", "rockGame.matching": "جارٍ البحث...",
"rockGame.matched": "تم العثور على خصم", "rockGame.matched": "تم العثور على خصم",
"rockGame.chooseGesture": "اختر الإشارة التي تريد لعبها",
"rockGame.winnerReceives": "الفائز يحصل على", "rockGame.winnerReceives": "الفائز يحصل على",
"rockGame.rock": "حجر", "rockGame.rock": "حجر",
"rockGame.paper": "ورقة", "rockGame.paper": "ورقة",
"rockGame.scissors": "مقص", "rockGame.scissors": "مقص",
"rockGame.win": "فوز", "rockGame.win": "فوز",
"rockGame.youWin": "فزت",
"rockGame.lose": "خسارة", "rockGame.lose": "خسارة",
"rockGame.draw": "تعادل", "rockGame.draw": "تعادل",
"rockGame.coins": "عملات", "rockGame.coins": "عملات",
@ -36,6 +38,7 @@
"rockGame.playAgain": "العب مرة أخرى", "rockGame.playAgain": "العب مرة أخرى",
"rockGame.changeLanguage": "تغيير اللغة", "rockGame.changeLanguage": "تغيير اللغة",
"rockGame.selectStake": "اختر قيمة الرهان", "rockGame.selectStake": "اختر قيمة الرهان",
"rockGame.configFeeNote": "الرسوم {fee}%",
"rockGame.apiUnavailable": "واجهة RPS غير متاحة", "rockGame.apiUnavailable": "واجهة RPS غير متاحة",
"rockGame.refundNote": "إذا لم ينضم أحد إلى لعبة حجر ورقة مقص، سيتم رد العملات خلال 10 دقائق.", "rockGame.refundNote": "إذا لم ينضم أحد إلى لعبة حجر ورقة مقص، سيتم رد العملات خلال 10 دقائق.",
"roomReward.pageTitle": "فعالية مكافآت الغرفة", "roomReward.pageTitle": "فعالية مكافآت الغرفة",

View File

@ -24,11 +24,13 @@
"rockGame.start": "START", "rockGame.start": "START",
"rockGame.matching": "Matching...", "rockGame.matching": "Matching...",
"rockGame.matched": "Matched", "rockGame.matched": "Matched",
"rockGame.chooseGesture": "Choose your gesture",
"rockGame.winnerReceives": "The winner receives", "rockGame.winnerReceives": "The winner receives",
"rockGame.rock": "Rock", "rockGame.rock": "Rock",
"rockGame.paper": "Paper", "rockGame.paper": "Paper",
"rockGame.scissors": "Scissors", "rockGame.scissors": "Scissors",
"rockGame.win": "Win", "rockGame.win": "Win",
"rockGame.youWin": "YOU WIN",
"rockGame.lose": "Lose", "rockGame.lose": "Lose",
"rockGame.draw": "Draw", "rockGame.draw": "Draw",
"rockGame.coins": "Coins", "rockGame.coins": "Coins",
@ -36,6 +38,7 @@
"rockGame.playAgain": "Play again", "rockGame.playAgain": "Play again",
"rockGame.changeLanguage": "Change language", "rockGame.changeLanguage": "Change language",
"rockGame.selectStake": "Select a stake", "rockGame.selectStake": "Select a stake",
"rockGame.configFeeNote": "Fee {fee}%",
"rockGame.apiUnavailable": "RPS API unavailable", "rockGame.apiUnavailable": "RPS API unavailable",
"rockGame.refundNote": "If no one joins the rock-paper-scissors game, the coins will be refunded in 10 minutes.", "rockGame.refundNote": "If no one joins the rock-paper-scissors game, the coins will be refunded in 10 minutes.",
"roomReward.pageTitle": "Room Reward Event", "roomReward.pageTitle": "Room Reward Event",

View File

@ -24,11 +24,13 @@
"rockGame.start": "INICIAR", "rockGame.start": "INICIAR",
"rockGame.matching": "Emparejando...", "rockGame.matching": "Emparejando...",
"rockGame.matched": "Emparejado", "rockGame.matched": "Emparejado",
"rockGame.chooseGesture": "Elige tu gesto",
"rockGame.winnerReceives": "El ganador recibe", "rockGame.winnerReceives": "El ganador recibe",
"rockGame.rock": "Piedra", "rockGame.rock": "Piedra",
"rockGame.paper": "Papel", "rockGame.paper": "Papel",
"rockGame.scissors": "Tijeras", "rockGame.scissors": "Tijeras",
"rockGame.win": "Victoria", "rockGame.win": "Victoria",
"rockGame.youWin": "GANASTE",
"rockGame.lose": "Derrota", "rockGame.lose": "Derrota",
"rockGame.draw": "Empate", "rockGame.draw": "Empate",
"rockGame.coins": "Monedas", "rockGame.coins": "Monedas",
@ -36,6 +38,7 @@
"rockGame.playAgain": "Jugar otra vez", "rockGame.playAgain": "Jugar otra vez",
"rockGame.changeLanguage": "Cambiar idioma", "rockGame.changeLanguage": "Cambiar idioma",
"rockGame.selectStake": "Selecciona una apuesta", "rockGame.selectStake": "Selecciona una apuesta",
"rockGame.configFeeNote": "Comisión {fee}%",
"rockGame.apiUnavailable": "API de RPS no disponible", "rockGame.apiUnavailable": "API de RPS no disponible",
"rockGame.refundNote": "Si nadie se une al juego de piedra, papel o tijeras, las monedas se reembolsarán en 10 minutos.", "rockGame.refundNote": "Si nadie se une al juego de piedra, papel o tijeras, las monedas se reembolsarán en 10 minutos.",
"roomReward.pageTitle": "Evento de recompensas de sala", "roomReward.pageTitle": "Evento de recompensas de sala",

View File

@ -0,0 +1,69 @@
.loading-screen {
position: absolute;
inset: 0;
z-index: 200;
overflow: hidden;
background: #030623;
opacity: 1;
pointer-events: auto;
transition:
opacity 320ms ease,
visibility 0s linear 0s;
visibility: visible;
}
.loading-screen.is-complete {
opacity: 0;
pointer-events: none;
transition:
opacity 320ms ease,
visibility 0s linear 320ms;
visibility: hidden;
}
.loading-bg {
position: absolute;
inset: 0;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center center;
pointer-events: none;
user-select: none;
}
.loading-progress {
position: absolute;
top: var(--loading-progress-top, 692px);
left: var(--loading-progress-left, 61px);
width: var(--loading-progress-width, 253px);
height: var(--loading-progress-height, 8px);
overflow: hidden;
border: 1px solid rgba(255, 213, 139, 0.76);
border-radius: 999px;
background:
linear-gradient(
180deg,
rgba(12, 10, 32, 0.8) 0%,
rgba(2, 4, 18, 0.92) 100%
),
rgba(5, 7, 25, 0.86);
box-shadow:
0 0 12px rgba(255, 164, 58, 0.32),
inset 0 0 7px rgba(0, 0, 0, 0.78);
}
.loading-progress-bar {
display: block;
width: 100%;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #ff612f 0%, #ffc957 52%, #3ca4ff 100%);
box-shadow:
0 0 12px rgba(255, 214, 87, 0.84),
inset 0 1px 1px rgba(255, 255, 255, 0.45);
transform: scaleX(var(--loading-progress, 0));
transform-origin: left center;
transition: transform 180ms ease;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

View File

@ -9,6 +9,7 @@
<title data-i18n="diceMatch.pageTitle">Dice Match</title> <title data-i18n="diceMatch.pageTitle">Dice Match</title>
<link rel="stylesheet" href="../../common/theme.css" /> <link rel="stylesheet" href="../../common/theme.css" />
<link rel="stylesheet" href="../common/game-match.css" /> <link rel="stylesheet" href="../common/game-match.css" />
<link rel="stylesheet" href="../common/game-loading.css" />
<style> <style>
:root { :root {
color-scheme: dark; color-scheme: dark;
@ -623,14 +624,16 @@
.stake-coin { .stake-coin {
position: relative; position: relative;
z-index: 1; z-index: 1;
width: 16px; width: 14px;
height: 16px; height: 14px;
flex: 0 0 16px; flex: 0 0 14px;
} }
.stake-value { .stake-value {
position: relative; position: relative;
z-index: 1; z-index: 1;
font-size: 12px;
line-height: 12px;
} }
.match-button { .match-button {
@ -1708,7 +1711,37 @@
aria-label="Dice match lobby" aria-label="Dice match lobby"
data-i18n-aria="diceMatch.pageLabel" data-i18n-aria="diceMatch.pageLabel"
> >
<main class="dice-frame" data-node-id="466:61" data-name="dice"> <main
class="dice-frame is-preloading"
data-node-id="466:61"
data-name="dice"
>
<div
class="loading-screen"
data-role="loading-screen"
aria-label="Loading"
role="status"
>
<img
class="loading-bg"
src="assets/figma/loading-bg.jpg"
alt=""
/>
<div
class="loading-progress"
data-role="loading-progress"
role="progressbar"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="0"
>
<span
class="loading-progress-bar"
data-role="loading-progress-bar"
></span>
</div>
</div>
<div <div
class="layer" class="layer"
style="left: -47px; top: 0; width: 470px; height: 834px" style="left: -47px; top: 0; width: 470px; height: 834px"
@ -2500,7 +2533,8 @@
window.HyAppI18nSupported = ['en', 'ar', 'es']; window.HyAppI18nSupported = ['en', 'ar', 'es'];
</script> </script>
<script src="../../common/i18n.js?v=dice-lang-20260611"></script> <script src="../../common/i18n.js?v=dice-lang-20260611"></script>
<script src="../../common/effect-player.js"></script> <script src="../../common/effect-player.js?v=dice-preload-20260611"></script>
<script src="../../common/game-preloader.js?v=game-preloader-20260611"></script>
<script> <script>
(function () { (function () {
var DESIGN_WIDTH = 375; var DESIGN_WIDTH = 375;
@ -2508,6 +2542,64 @@
var MAX_STAGE_WIDTH = 768; var MAX_STAGE_WIDTH = 768;
var SETTLEMENT_AUTO_CLOSE_MS = 30000; var SETTLEMENT_AUTO_CLOSE_MS = 30000;
var DICE_ROLL_EFFECT_URL = 'assets/figma/dice-roll.svga'; var DICE_ROLL_EFFECT_URL = 'assets/figma/dice-roll.svga';
var DICE_PRELOAD_ASSET_URLS = [
'assets/figma/loading-bg.jpg',
'assets/figma/amount-fill.png',
'assets/figma/back-button.png',
'assets/figma/badge-help.png',
'assets/figma/bg-arena.png',
'assets/figma/blue-dice.png',
'assets/figma/blue-flare.png',
'assets/figma/blue-ring.png',
'assets/figma/coin.png',
'assets/figma/dice-blue-1.png',
'assets/figma/dice-blue-2.png',
'assets/figma/dice-blue-3.png',
'assets/figma/dice-blue-4.png',
'assets/figma/dice-blue-5.png',
'assets/figma/dice-blue-6.png',
'assets/figma/dice-red-1.png',
'assets/figma/dice-red-2.png',
'assets/figma/dice-red-3.png',
'assets/figma/dice-red-4.png',
'assets/figma/dice-red-5.png',
'assets/figma/dice-red-6.png',
DICE_ROLL_EFFECT_URL,
'assets/figma/game-icon.png',
'assets/figma/home-indicator.svg',
'assets/figma/mask-bg.svg',
'assets/figma/match-button.png',
'assets/figma/matching-center-bg.svg',
'assets/figma/matching-center.svg',
'assets/figma/matching-left.svg',
'assets/figma/matching-right.svg',
'assets/figma/opponent-avatar-glow.svg',
'assets/figma/opponent-card.png',
'assets/figma/opponent-question.png',
'assets/figma/player-avatar-glow.png',
'assets/figma/player-card.png',
'assets/figma/prize-panel.svg',
'assets/figma/red-dice.png',
'assets/figma/red-flare.png',
'assets/figma/red-ring.png',
'assets/figma/search-icon.svg',
'assets/figma/stake-fill.png',
'assets/figma/stake-mask-left.svg',
'assets/figma/stake-mask-right.svg',
'assets/figma/stake-outline.svg',
'assets/figma/status-battery.svg',
'assets/figma/status-cellular.svg',
'assets/figma/status-frame.svg',
'assets/figma/status-wifi.svg',
'assets/figma/vs.png',
'assets/figma/win-avatar-glow.png',
'assets/figma/win-play-again-button.png',
'assets/figma/win-player-card.png',
'assets/figma/win-rematch-button.png',
'assets/figma/win-reward-panel.png',
'assets/figma/win-victory-light.png',
'assets/figma/winner-text-fill.png',
];
var root = document.documentElement; var root = document.documentElement;
var transparentPixel = var transparentPixel =
'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%221%22 height=%221%22%2F%3E'; 'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%221%22 height=%221%22%2F%3E';
@ -2523,6 +2615,7 @@
opponent: null, opponent: null,
}; };
var state = { var state = {
preloading: true,
profile: {}, profile: {},
appearance: {}, appearance: {},
balanceCoin: 0, balanceCoin: 0,
@ -2586,6 +2679,88 @@
postBridge('recharge', payload); postBridge('recharge', payload);
} }
function isInsufficientBalanceError(error) {
var code = String(
(error && error.code) || ''
).toUpperCase();
var message = String(
(error && error.message) || ''
).toLowerCase();
return (
code === 'INSUFFICIENT_BALANCE' ||
message === 'insufficient balance'
);
}
function handleInsufficientBalance() {
// 余额不足需要马上让用户补币:先给固定英文提示,再拉起 Flutter 支付页桥接。
showMessage('insufficient balance');
openRecharge();
}
function setLoadingProgress(progress) {
window.HyAppGamePreloader.setProgress({
root: root,
progress: progress,
});
}
function completeLoadingScreen() {
state.preloading = false;
setMatchButtonBusy(false, matchText());
window.HyAppGamePreloader.complete({
root: root,
frame: frame,
loadingScreen: role('loading-screen'),
progressNode: role('loading-progress'),
});
}
function collectDicePreloadURLs() {
return window.HyAppGamePreloader.collectResourceURLs(
DICE_PRELOAD_ASSET_URLS
);
}
function preloadResourceOnce(url) {
return window.HyAppGamePreloader.preloadResource(url, {
svga: {
svgaScript: '../../common/vendor/svga.min.js',
waitForWindowLoad: false,
},
});
}
function preloadLobbyDataImages(lobbyData) {
var urls = [];
var seen = {};
var profile = (lobbyData && lobbyData.profile) || {};
var appearance = (lobbyData && lobbyData.appearance) || {};
window.HyAppGamePreloader.addURL(
urls,
seen,
avatarURL(profile)
);
window.HyAppGamePreloader.addURL(
urls,
seen,
frameURL(appearance.avatar_frame || {})
);
if (!urls.length) return Promise.resolve();
return Promise.all(
urls.map(function (url) {
return window.HyAppGamePreloader.preloadImage(url);
})
);
}
function runPreloadTasks(tasks) {
return window.HyAppGamePreloader.runTasks(
tasks,
setLoadingProgress
);
}
function each(nodes, iteratee) { function each(nodes, iteratee) {
Array.prototype.forEach.call(nodes, iteratee); Array.prototype.forEach.call(nodes, iteratee);
} }
@ -3607,6 +3782,11 @@
}) })
.catch(function (error) { .catch(function (error) {
stopRollEffects(); stopRollEffects();
if (isInsufficientBalanceError(error)) {
resetToLobby(false);
handleInsufficientBalance(error);
return;
}
showMessage(error.message); showMessage(error.message);
resetToLobby(false); resetToLobby(false);
}); });
@ -3785,6 +3965,11 @@
success: false, success: false,
error_code: error && error.message, error_code: error && error.message,
}); });
if (isInsufficientBalanceError(error)) {
resetToLobby(true);
handleInsufficientBalance(error);
return;
}
showMessage(error.message); showMessage(error.message);
resetToLobby(true); resetToLobby(true);
}); });
@ -3858,13 +4043,33 @@
setMatchButtonBusy(true, matchingText()); setMatchButtonBusy(true, matchingText());
return; return;
} }
if (state.preloading) {
setMatchButtonBusy(
true,
i18nText('diceMatch.loading', 'Loading')
);
return;
}
if (!state.busy) { if (!state.busy) {
setActionText(matchingText()); setActionText(matchingText());
setMatchButtonBusy(false, matchText()); setMatchButtonBusy(false, matchText());
} }
} }
function loadLobby() { function requestLobbyData() {
var lobbyData = {
profile: {},
appearance: {},
balance: null,
configResponse: null,
};
function onceTask(task) {
var promise = null;
return function () {
if (!promise) promise = task();
return promise;
};
}
if (!window.HyAppAPI || !diceAPI()) { if (!window.HyAppAPI || !diceAPI()) {
showMessage( showMessage(
i18nText( i18nText(
@ -3872,32 +4077,61 @@
'Dice API unavailable' 'Dice API unavailable'
) )
); );
return; return {
lobbyData: lobbyData,
tasks: [
function () {
return Promise.resolve();
},
],
};
} }
setMatchButtonBusy( setMatchButtonBusy(
true, true,
i18nText('diceMatch.loading', 'Loading') i18nText('diceMatch.loading', 'Loading')
); );
var configStartedAt = performance.now(); var configStartedAt = performance.now();
Promise.all([ var profileTask = function () {
api() return api()
.user.me() .user.me()
.then(function (data) {
lobbyData.profile = data || {};
return data;
})
.catch(function () { .catch(function () {
lobbyData.profile = {};
return null; return null;
}), });
api() };
var appearanceTask = function () {
return api()
.user.appearance() .user.appearance()
.then(function (data) {
lobbyData.appearance = data || {};
return data;
})
.catch(function () { .catch(function () {
lobbyData.appearance = {};
return null; return null;
}), });
api() };
var balanceTask = function () {
return api()
.wallet.balances('COIN') .wallet.balances('COIN')
.then(function (data) {
lobbyData.balance = data || null;
return data;
})
.catch(function () { .catch(function () {
lobbyData.balance = null;
return null; return null;
}), });
diceAPI() };
var configTask = function () {
return diceAPI()
.config('dice') .config('dice')
.then(function (data) { .then(function (data) {
lobbyData.configResponse = data || null;
trackSelfGameEvent('config_load_success', { trackSelfGameEvent('config_load_success', {
duration_ms: Math.round( duration_ms: Math.round(
performance.now() - configStartedAt performance.now() - configStartedAt
@ -3906,6 +4140,7 @@
return data; return data;
}) })
.catch(function () { .catch(function () {
lobbyData.configResponse = null;
trackSelfGameEvent('config_load_fail', { trackSelfGameEvent('config_load_fail', {
duration_ms: Math.round( duration_ms: Math.round(
performance.now() - configStartedAt performance.now() - configStartedAt
@ -3914,20 +4149,87 @@
error_code: 'config_failed', error_code: 'config_failed',
}); });
return null; return null;
}), });
]).then(function (results) { };
state.profile = results[0] || {}; return {
state.appearance = results[1] || {}; lobbyData: lobbyData,
state.config = tasks: [
(results[3] && results[3].config) || null; onceTask(profileTask),
if (results[2]) onceTask(appearanceTask),
setCoin(balanceFromResponse(results[2])); onceTask(balanceTask),
renderSelfProfile(); onceTask(configTask),
renderStakeOptions(state.config); ],
setMatchButtonBusy(false, matchText()); };
}
function applyLobbyData(lobbyData) {
lobbyData = lobbyData || {};
state.profile = lobbyData.profile || {};
state.appearance = lobbyData.appearance || {};
state.config =
(lobbyData.configResponse &&
lobbyData.configResponse.config) ||
null;
if (lobbyData.balance) {
setCoin(balanceFromResponse(lobbyData.balance));
}
renderSelfProfile();
renderStakeOptions(state.config);
setMatchButtonBusy(
state.preloading,
state.preloading
? i18nText('diceMatch.loading', 'Loading')
: matchText()
);
}
function loadLobby() {
var lobby = requestLobbyData();
return runPreloadTasks(lobby.tasks).then(function () {
return preloadLobbyDataImages(lobby.lobbyData).then(
function () {
applyLobbyData(lobby.lobbyData);
return lobby.lobbyData;
}
);
}); });
} }
function loadStartup() {
var lobby = requestLobbyData();
var tasks = lobby.tasks.slice();
collectDicePreloadURLs().forEach(function (url) {
tasks.push(function () {
return preloadResourceOnce(url);
});
});
tasks.push(function () {
return Promise.all([
lobby.tasks[0](),
lobby.tasks[1](),
]).then(function () {
return preloadLobbyDataImages(lobby.lobbyData);
});
});
// 启动期必须等用户、余额、配置、骰子 SVGA、六面骰子和页面图片都进入缓存后才允许用户进入大厅操作。
return runPreloadTasks(tasks)
.then(function () {
applyLobbyData(lobby.lobbyData);
return new Promise(function (resolve) {
window.setTimeout(resolve, 180);
});
})
.then(function () {
completeLoadingScreen();
trackSelfGameEvent('preload_complete', {
duration_ms: Math.round(
performance.now() - pageStartedAt
),
asset_count: tasks.length,
});
});
}
function initActions() { function initActions() {
document.addEventListener('click', function (event) { document.addEventListener('click', function (event) {
var target = event.target.closest('[data-action]'); var target = event.target.closest('[data-action]');
@ -3943,6 +4245,7 @@
openRecharge(); openRecharge();
return; return;
} }
if (state.preloading) return;
if (action === 'stake') { if (action === 'stake') {
if (state.busy) return; if (state.busy) return;
selectStake(target); selectStake(target);
@ -3996,7 +4299,11 @@
'hyapp:i18n-ready', 'hyapp:i18n-ready',
refreshLocalizedRuntimeText refreshLocalizedRuntimeText
); );
loadLobby(); loadStartup().catch(function (error) {
showMessage(error && error.message);
completeLoadingScreen();
setMatchButtonBusy(false, matchText());
});
window.addEventListener('resize', updateScale); window.addEventListener('resize', updateScale);
window.addEventListener('orientationchange', updateScale); window.addEventListener('orientationchange', updateScale);
if (window.HyAppBridge && window.HyAppBridge.ready) { if (window.HyAppBridge && window.HyAppBridge.ready) {