hyapp-h5/game/rock/index.html
2026-06-11 18:19:38 +08:00

2703 lines
98 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover"
/>
<title data-i18n="rockGame.pageTitle">Rock Paper Scissors</title>
<link rel="stylesheet" href="../../common/theme.css" />
<link rel="stylesheet" href="../common/game-match.css" />
<style>
:root {
color-scheme: dark;
--dice-topbar-offset: 0px;
}
* {
box-sizing: border-box;
}
html,
body {
width: 100%;
min-width: 100%;
height: 100%;
min-height: 100%;
margin: 0;
overflow: hidden;
background: #030623;
font-family:
'Source Han Sans SC', 'Helvetica Neue', Arial, sans-serif;
-webkit-font-smoothing: antialiased;
text-rendering: geometricprecision;
}
body {
display: flex;
align-items: flex-start;
justify-content: flex-start;
}
button {
padding: 0;
border: 0;
color: inherit;
font: inherit;
background: transparent;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.rps-frame {
--game-screen-scale: var(--game-scale);
}
.rps-bg {
position: absolute;
top: -145px;
left: -53px;
width: 471px;
height: 836px;
object-fit: cover;
pointer-events: none;
user-select: none;
}
.rps-bg-overlay {
mix-blend-mode: multiply;
opacity: 0.9;
}
.rps-status-time {
position: absolute;
top: 13px;
left: 34px;
z-index: 28;
width: 54px;
color: #fff;
font-family: Helvetica, Arial, sans-serif;
font-size: 15px;
line-height: 15px;
text-align: center;
}
.rps-home-indicator {
position: absolute;
left: 120px;
bottom: 9px;
z-index: 50;
width: 135px;
height: 5px;
border-radius: 3px;
background: #fff;
}
.top-bar {
position: absolute;
top: 60px;
left: 16px;
z-index: 30;
display: flex;
align-items: center;
justify-content: space-between;
width: 343px;
height: 36px;
transform: translateY(var(--dice-topbar-offset));
}
.top-back {
position: relative;
width: 46px;
height: 36px;
flex: 0 0 46px;
}
.top-game-icon {
position: absolute;
top: 50%;
left: 50%;
width: 36px;
height: 36px;
overflow: hidden;
border-radius: 6px;
transform: translate(-50%, -50%);
}
.top-coin {
position: relative;
display: flex;
align-items: center;
justify-content: flex-end;
width: max-content;
min-width: 102px;
max-width: 176px;
height: 32px;
flex: 0 1 auto;
gap: 4px;
overflow: visible;
padding: 0 7px;
color: #ffe38e;
font-family: inherit;
font-size: 18px;
font-weight: 900;
line-height: 20px;
margin: 0;
text-align: center;
text-shadow:
0 1px 1px rgba(65, 23, 0, 0.86),
0 0 6px rgba(255, 204, 74, 0.34);
white-space: nowrap;
appearance: none;
-webkit-appearance: none;
border-image: none;
cursor: pointer;
}
.top-coin span {
display: block;
min-width: max-content;
max-width: none;
flex: 0 0 auto;
overflow: visible;
line-height: 20px;
}
.top-coin.is-compact span {
max-width: none;
font-size: 18px;
}
.top-coin img {
display: block;
width: 23px;
height: 23px;
flex: 0 0 23px;
filter: drop-shadow(0 0 5px rgba(255, 197, 49, 0.45));
}
.top-coin-plus {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 5px;
color: #ffd15a;
font-size: 21px;
font-weight: 900;
text-shadow:
0 1px 1px rgba(65, 23, 0, 0.86),
0 0 8px rgba(255, 178, 47, 0.74);
}
.language-switcher {
position: absolute;
top: 41px;
right: 0;
z-index: 20;
width: 102px;
font-family: inherit;
}
.language-button {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 22px;
border: 1px solid rgba(108, 113, 194, 0.68);
border-radius: 4px;
background: rgba(7, 10, 40, 0.72);
color: #ffe38e;
font-size: 12px;
font-weight: 800;
line-height: 14px;
text-shadow: 0 0 5px rgba(255, 204, 74, 0.42);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04);
}
.language-button::after {
margin-left: 4px;
color: #ffd15a;
content: 'v';
font-size: 10px;
line-height: 10px;
}
.language-menu {
position: absolute;
top: 26px;
right: 0;
width: 102px;
padding: 4px;
border: 1px solid rgba(108, 113, 194, 0.78);
border-radius: 6px;
background: rgba(5, 8, 35, 0.94);
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.42);
}
.language-menu[hidden] {
display: none;
}
.language-menu button {
display: block;
width: 100%;
min-height: 24px;
border-radius: 4px;
color: rgba(255, 227, 142, 0.78);
font-size: 12px;
font-weight: 800;
line-height: 14px;
text-align: center;
}
.language-menu button.is-active {
background: rgba(255, 209, 90, 0.16);
color: #ffd15a;
}
.rps-screen {
position: absolute;
inset: 0;
opacity: 0;
pointer-events: none;
transition: opacity 180ms ease;
}
.rps-frame[data-state='lobby'] .rps-lobby,
.rps-frame[data-state='searching'] .rps-matching,
.rps-frame[data-state='vs'] .rps-vs-screen,
.rps-frame[data-state='battle'] .rps-battle,
.rps-frame[data-state='victory'] .rps-victory {
opacity: 1;
pointer-events: auto;
}
.rps-profile {
position: absolute;
top: 116px;
left: 102px;
z-index: 6;
width: 170px;
text-align: center;
}
.rps-avatar-ring {
position: relative;
width: 96px;
height: 96px;
margin: 0 auto;
border: 3px solid #f8bc39;
border-radius: 50%;
background:
radial-gradient(circle, #74421f 0%, #2c1235 66%), #35153d;
box-shadow:
0 0 0 4px rgba(76, 35, 4, 0.9),
0 0 0 7px rgba(255, 205, 61, 0.45),
0 0 22px rgba(255, 175, 40, 0.76);
}
.rps-avatar-ring::before {
position: absolute;
top: -18px;
left: 27px;
width: 42px;
height: 26px;
content: '';
background: linear-gradient(180deg, #ffe777 0%, #c06d12 100%);
clip-path: polygon(
0 100%,
14% 38%,
29% 72%,
50% 0,
71% 72%,
86% 38%,
100% 100%
);
filter: drop-shadow(0 2px 0 rgba(92, 31, 0, 0.72));
}
.rps-profile .rps-avatar-ring {
width: 90px;
height: 98px;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
.rps-profile .rps-avatar-ring::before {
display: none;
}
.rps-lobby-avatar-fallback {
position: absolute;
left: 7px;
top: 15px;
width: 75px;
height: 75px;
border-radius: 50%;
background:
radial-gradient(circle, #74421f 0%, #2c1235 66%), #35153d;
}
.rps-lobby-avatar-frame {
position: absolute;
inset: 0;
width: 90px;
height: 98px;
object-fit: fill;
pointer-events: none;
z-index: 2;
}
.rps-avatar-img {
position: absolute;
inset: 5px;
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
}
.rps-profile .rps-avatar-img {
left: 7px;
top: 15px;
width: 75px;
height: 75px;
z-index: 1;
}
.rps-avatar-img.is-empty {
display: none;
}
.rps-profile-name,
.rps-victory-name {
color: #ffe189;
font-weight: 700;
text-shadow:
0 1px 0 #7a2e09,
0 0 4px #692808;
white-space: nowrap;
}
.rps-profile-name {
margin-top: 13px;
font-size: 16px;
line-height: 16px;
}
.rps-showcase {
position: absolute;
top: 195px;
left: 0;
width: 375px;
height: 236px;
}
.rps-hand-zone {
position: absolute;
width: 258px;
height: 258px;
}
.rps-hand-zone.is-left {
left: -52px;
top: 0;
}
.rps-hand-zone.is-right {
left: 169px;
top: 0;
}
.rps-ring {
position: absolute;
top: 23px;
width: 258px;
height: 258px;
opacity: 0.88;
animation: matching-center-spin 10s linear infinite;
}
.rps-hand-zone.is-left .rps-ring {
left: -7px;
}
.rps-hand-zone.is-right .rps-ring {
right: -7px;
}
.rps-flare {
position: absolute;
top: 0;
width: 244px;
height: 236px;
opacity: 0.95;
}
.rps-hand-zone.is-left .rps-flare {
left: 0;
}
.rps-hand-zone.is-right .rps-flare {
right: 0;
}
.rps-hand {
position: absolute;
object-fit: contain;
filter: drop-shadow(0 16px 11px rgba(0, 0, 0, 0.42));
}
.rps-hand-zone.is-left .rps-hand {
left: 89px;
top: 90px;
width: 78px;
height: 101px;
}
.rps-hand-zone.is-right .rps-hand {
left: 68px;
top: 72px;
width: 121px;
height: 131px;
}
.rps-hand[data-gesture='paper'] {
width: 118px;
height: 132px;
}
.rps-hand[data-gesture='scissors'] {
width: 92px;
height: 132px;
}
.rps-lobby-panel {
position: absolute;
left: 12px;
top: 488px;
width: 350px;
height: 292px;
background: url('assets/figma/lobby-panel.svg') center/100% 100%
no-repeat;
}
.rps-panel-title,
.rps-stake-title {
position: absolute;
left: 0;
width: 100%;
color: #f8edc7;
font-size: 14px;
line-height: 14px;
text-align: center;
text-shadow: 0 0 6px rgba(255, 217, 115, 0.38);
}
.rps-panel-title {
top: 12px;
}
.rps-stake-title {
top: 155px;
}
.rps-help {
position: absolute;
top: 9px;
right: 8px;
width: 20px;
height: 20px;
overflow: hidden;
color: transparent;
background: url('assets/figma/help-badge.png') center/100% 100%
no-repeat;
}
.rps-gesture-row {
position: absolute;
top: 38px;
left: 8px;
display: grid;
grid-template-columns: repeat(3, 105px);
gap: 10px;
}
.rps-gesture {
position: relative;
width: 105px;
height: 103px;
background: url('assets/figma/gesture-card.png') center/100%
100% no-repeat;
}
.rps-gesture[aria-pressed='true'] {
background-image: url('assets/figma/gesture-selected-rock.png');
filter: drop-shadow(0 0 8px rgba(22, 102, 255, 0.95));
}
.rps-gesture img {
position: absolute;
left: 50%;
top: 20px;
width: 40px;
height: 48px;
object-fit: contain;
transform: translateX(-50%);
}
.rps-gesture[data-gesture='paper'] img {
top: 18px;
width: 42px;
height: 50px;
}
.rps-gesture[data-gesture='scissors'] img {
top: 17px;
width: 46px;
height: 52px;
}
.rps-gesture span {
position: absolute;
left: 0;
bottom: 10px;
width: 100%;
color: #ffd982;
font-size: 14px;
font-weight: 800;
line-height: 14px;
text-align: center;
text-shadow:
0 1px 0 #532203,
0 0 5px rgba(255, 216, 105, 0.58);
}
.rps-stake-row {
position: absolute;
top: 181px;
left: 8px;
display: grid;
grid-template-columns: repeat(4, 77px);
gap: 9px;
overflow: visible;
}
.rps-stake {
position: relative;
width: 77px;
height: 28px;
color: #ffe189;
font-size: 16px;
font-weight: 800;
line-height: 28px;
text-align: right;
text-shadow: 0 0 4px rgba(213, 148, 43, 0.82);
overflow: visible;
z-index: 1;
}
.rps-stake::before {
position: absolute;
inset: 0;
content: '';
background: url('assets/figma/stake-outline.svg') center/100%
100% no-repeat;
z-index: -1;
}
.rps-stake[aria-pressed='true'] {
z-index: 3;
filter: drop-shadow(0 0 8px rgba(32, 91, 255, 0.9));
}
.rps-stake[aria-pressed='true']::before {
left: -14px;
width: 105px;
background-image: url('assets/figma/stake-selected.png');
background-size: 105px 28px;
background-position: center;
}
.rps-stake img {
position: absolute;
top: 6px;
left: 8px;
width: 16px;
height: 16px;
z-index: 1;
}
.rps-stake span {
position: relative;
display: block;
padding-right: 7px;
z-index: 1;
}
.rps-start {
position: absolute;
left: 73px;
top: 225px;
width: 201px;
height: 40px;
color: #fcf7cb;
font-size: 18px;
font-weight: 800;
line-height: 40px;
text-align: center;
text-shadow: 0 0 2px #d5942b;
}
.rps-start::before {
position: absolute;
inset: 0;
z-index: -1;
content: '';
background: url('assets/figma/start-button.png') center/100%
100% no-repeat;
filter: brightness(1.85) saturate(1.5)
drop-shadow(0 0 15px rgba(0, 111, 255, 1));
}
.rps-refund-note {
position: absolute;
left: 16px;
bottom: 9px;
width: 318px;
color: #9a9abd;
font-size: 8px;
line-height: 12px;
text-align: center;
}
.rps-duel-card {
position: absolute;
top: 126px;
width: 160px;
height: 80px;
}
.rps-duel-card.is-self {
left: 12px;
}
.rps-duel-card.is-opponent {
left: 203px;
}
.rps-duel-card > img {
position: absolute;
inset: 0;
width: 160px;
height: 80px;
object-fit: fill;
}
.rps-duel-avatar {
position: absolute;
top: 10px;
width: 62px;
height: 62px;
border: 2px solid #f6bc43;
border-radius: 50%;
background: radial-gradient(circle, #5c2d48 0%, #180b32 76%);
box-shadow: 0 0 12px rgba(255, 192, 58, 0.68);
}
.rps-duel-card.is-self .rps-duel-avatar {
left: 6px;
}
.rps-duel-card.is-opponent .rps-duel-avatar {
right: 7px;
border-color: #54bcff;
box-shadow: 0 0 17px rgba(72, 142, 255, 0.92);
}
.rps-duel-avatar img {
position: absolute;
inset: 3px;
width: 52px;
height: 52px;
border-radius: 50%;
object-fit: cover;
}
.rps-duel-avatar img.is-empty {
display: none;
}
.rps-question {
position: absolute;
inset: 0;
display: grid;
place-items: center;
color: #8fd5ff;
font-size: 28px;
font-weight: 900;
text-shadow: 0 0 12px rgba(52, 134, 255, 1);
}
.rps-duel-name {
position: absolute;
top: 29px;
width: 58px;
color: #fff;
font-size: 10px;
line-height: 14px;
}
.rps-duel-card.is-self .rps-duel-name {
left: 79px;
}
.rps-duel-card.is-opponent .rps-duel-name {
left: 21px;
text-align: left;
}
.rps-duel-status {
position: absolute;
top: 45px;
left: 37px;
display: flex;
align-items: center;
gap: 3px;
color: #b9defb;
font-size: 8px;
line-height: 14px;
}
.rps-duel-status img {
width: 12px;
height: 12px;
}
.rps-search-hud {
position: absolute;
top: 134px;
left: 152px;
width: 72px;
height: 72px;
}
.rps-search-hud img:first-child {
width: 72px;
height: 72px;
animation: matching-center-spin 3000ms linear infinite;
}
.rps-search-hud img:last-child {
position: absolute;
top: 29px;
left: 29px;
width: 15px;
height: 15px;
}
.rps-bottom-status {
position: absolute;
left: 74px;
top: 676px;
width: 228px;
color: #c9e8ff;
font-size: 24px;
font-weight: 900;
line-height: 24px;
text-align: center;
text-shadow:
0 0 6px #0039bc,
0 0 16px rgba(70, 157, 255, 0.78);
}
.rps-bottom-status::before,
.rps-bottom-status::after {
position: absolute;
top: 11px;
width: 36px;
height: 1px;
content: '';
background: linear-gradient(
90deg,
transparent,
rgba(180, 224, 255, 0.95)
);
}
.rps-bottom-status::before {
left: 0;
}
.rps-bottom-status::after {
right: 0;
transform: scaleX(-1);
}
.rps-vs-badge {
position: absolute;
top: 112px;
left: 156px;
width: 68px;
height: 112px;
z-index: 12;
}
.rps-battle .rps-showcase {
top: 244px;
}
.rps-battle .rps-hand-zone.is-left .rps-hand {
animation: rps-left-clash 900ms
cubic-bezier(0.21, 0.92, 0.22, 1) 260ms both;
}
.rps-battle .rps-hand-zone.is-right .rps-hand {
animation: rps-right-clash 900ms
cubic-bezier(0.21, 0.92, 0.22, 1) 260ms both;
}
.rps-victory-title {
position: absolute;
top: 90px;
left: 29px;
width: 316px;
height: 124px;
object-fit: contain;
z-index: 1;
}
.rps-victory .rps-avatar-ring {
position: absolute;
top: 246px;
left: 102px;
width: 170px;
height: 186px;
border: 0;
background: transparent;
box-shadow: none;
}
.rps-victory .rps-avatar-ring::before {
display: none;
}
.rps-victory-avatar-bg {
position: absolute;
left: 15px;
top: 28px;
width: 144px;
height: 144px;
border-radius: 50%;
object-fit: cover;
}
.rps-victory-avatar-frame {
position: absolute;
inset: 0;
width: 170px;
height: 186px;
object-fit: fill;
pointer-events: none;
}
.rps-victory .rps-avatar-img {
left: 15px;
top: 28px;
width: 144px;
height: 144px;
z-index: 1;
}
.rps-victory-name {
position: absolute;
top: 448px;
left: 72px;
width: 232px;
overflow: hidden;
font-size: 24px;
line-height: 26px;
text-align: center;
text-overflow: ellipsis;
}
.rps-reward-panel {
position: absolute;
top: 514px;
left: 38px;
width: 300px;
height: 103px;
background: url('assets/figma/victory-reward-frame.png')
center/100% 100% no-repeat;
}
.rps-reward-panel img {
position: absolute;
left: 38px;
top: 16px;
width: 75px;
height: 70px;
object-fit: contain;
}
.rps-reward-copy {
position: absolute;
top: 13px;
left: 145px;
width: 170px;
color: #ffe189;
text-align: center;
text-transform: uppercase;
}
.rps-reward-copy strong {
display: block;
font-size: 18px;
line-height: 20px;
}
.rps-reward-copy b {
display: block;
margin-top: 4px;
font-size: 24px;
line-height: 24px;
}
.rps-reward-copy span {
display: block;
margin-top: 4px;
font-size: 18px;
line-height: 20px;
}
.rps-victory-actions {
position: absolute;
top: 690px;
left: 12px;
width: 351px;
height: 40px;
}
.rps-victory-button {
position: absolute;
left: 0;
top: 0;
width: 168px;
height: 40px;
color: #fcf7cb;
font-size: 18px;
font-weight: 800;
line-height: 40px;
text-align: center;
text-shadow: 0 0 2px #d5942b;
}
.rps-victory-button::before {
position: absolute;
inset: 0;
z-index: -1;
content: '';
background: url('assets/figma/victory-action-blue.png')
center/100% 100% no-repeat;
filter: brightness(1.22) saturate(1.14)
drop-shadow(0 0 10px rgba(0, 111, 255, 0.9));
}
.rps-victory-button.is-secondary {
left: 183px;
}
.rps-victory-button.is-secondary::before {
background-image: url('assets/figma/victory-action-secondary.svg');
filter: drop-shadow(0 0 5px rgba(255, 227, 142, 0.34));
}
.rps-message {
position: absolute;
left: 36px;
top: 636px;
z-index: 35;
width: 303px;
color: #f4f8ff;
font-size: 13px;
line-height: 16px;
text-align: center;
text-shadow: 0 0 8px rgba(31, 130, 255, 0.95);
}
@media (prefers-reduced-motion: reduce) {
.rps-ring,
.rps-search-hud img:first-child,
.rps-battle .rps-hand-zone .rps-hand {
animation: none !important;
}
}
@keyframes rps-left-clash {
0% {
transform: translateX(-34px) scale(0.94);
}
46% {
transform: translateX(42px) scale(1.08) rotate(4deg);
}
62% {
transform: translateX(26px) scale(0.98) rotate(-2deg);
}
100% {
transform: translateX(0) scale(1) rotate(0deg);
}
}
@keyframes rps-right-clash {
0% {
transform: translateX(34px) scale(0.94);
}
46% {
transform: translateX(-42px) scale(1.08) rotate(-4deg);
}
62% {
transform: translateX(-26px) scale(0.98) rotate(2deg);
}
100% {
transform: translateX(0) scale(1) rotate(0deg);
}
}
</style>
</head>
<body>
<div
class="game-stage"
role="application"
aria-label="Rock paper scissors"
data-i18n-aria="rockGame.pageLabel"
>
<main
class="game-frame rps-frame"
data-state="lobby"
data-node-id="491:135"
data-name="rock-paper-scissors"
>
<img class="rps-bg" src="assets/figma/bg-arena.png" alt="" />
<img
class="rps-bg rps-bg-overlay"
src="assets/figma/bg-arena.png"
alt=""
/>
<div class="rps-status-time">9:41</div>
<div class="rps-home-indicator" aria-hidden="true"></div>
<div class="top-bar" data-name="top bar">
<button
class="top-back"
type="button"
aria-label="Back"
data-action="back"
>
<img
class="layer-img cover"
src="assets/figma/back-button.png"
alt=""
/>
</button>
<div class="top-game-icon">
<img
class="layer-img cover"
src="assets/figma/game-icon.png"
alt=""
/>
</div>
<button
class="top-coin"
type="button"
aria-label="Recharge"
data-action="recharge"
>
<img src="assets/figma/coin.png" alt="" />
<span>159</span>
<strong class="top-coin-plus" aria-hidden="true"
>+</strong
>
</button>
<div class="language-switcher">
<button
class="language-button"
type="button"
data-language-toggle
data-i18n-aria="rockGame.changeLanguage"
aria-label="Change language"
aria-expanded="false"
>
<span data-current-lang>EN</span>
</button>
<div class="language-menu" data-language-menu hidden>
<button type="button" data-lang-option="en">
EN
</button>
<button type="button" data-lang-option="ar">
AR
</button>
<button type="button" data-lang-option="es">
ES
</button>
</div>
</div>
</div>
<section class="rps-screen rps-lobby">
<div class="rps-profile">
<div class="rps-avatar-ring">
<div
class="rps-lobby-avatar-fallback"
aria-hidden="true"
></div>
<img
class="rps-avatar-img is-empty"
data-role="self-avatar"
alt=""
/>
<img
class="rps-lobby-avatar-frame"
src="assets/figma/lobby-avatar-frame.png"
alt=""
/>
</div>
<div class="rps-profile-name" data-role="self-name">
NameNameName...
</div>
</div>
<div class="rps-showcase">
<div class="rps-hand-zone is-left">
<img
class="rps-ring"
src="assets/figma/red-ring.png"
alt=""
/>
<img
class="rps-flare"
src="assets/figma/red-flare.png"
alt=""
/>
<img
class="rps-hand"
data-role="self-lobby-hand"
data-gesture="rock"
src="assets/figma/rock.png"
alt=""
/>
</div>
<div class="rps-hand-zone is-right">
<img
class="rps-ring"
src="assets/figma/blue-ring.png"
alt=""
/>
<img
class="rps-flare"
src="assets/figma/blue-flare.png"
alt=""
/>
<img
class="rps-hand"
data-role="preview-opponent-hand"
data-gesture="paper"
src="assets/figma/paper.png"
alt=""
/>
</div>
</div>
<div class="rps-lobby-panel">
<div
class="rps-panel-title"
data-i18n="rockGame.winnerReceives"
>
The winner receives
</div>
<div class="rps-help">?</div>
<div class="rps-gesture-row">
<button
class="rps-gesture"
type="button"
data-action="gesture"
data-gesture="rock"
aria-pressed="true"
>
<img src="assets/figma/rock.png" alt="" />
<span data-i18n="rockGame.rock">Rock</span>
</button>
<button
class="rps-gesture"
type="button"
data-action="gesture"
data-gesture="paper"
aria-pressed="false"
>
<img src="assets/figma/paper.png" alt="" />
<span data-i18n="rockGame.paper">Paper</span>
</button>
<button
class="rps-gesture"
type="button"
data-action="gesture"
data-gesture="scissors"
aria-pressed="false"
>
<img src="assets/figma/scissors.png" alt="" />
<span data-i18n="rockGame.scissors"
>Scissors</span
>
</button>
</div>
<div
class="rps-stake-title"
data-i18n="rockGame.winnerReceives"
>
The winner receives
</div>
<div class="rps-stake-row">
<button
class="rps-stake"
type="button"
data-action="stake"
data-stake="8000"
aria-pressed="false"
>
<img src="assets/figma/coin.png" alt="" />
<span>8,000</span>
</button>
<button
class="rps-stake"
type="button"
data-action="stake"
data-stake="8000"
aria-pressed="true"
>
<img src="assets/figma/coin.png" alt="" />
<span>8,000</span>
</button>
<button
class="rps-stake"
type="button"
data-action="stake"
data-stake="8000"
aria-pressed="false"
>
<img src="assets/figma/coin.png" alt="" />
<span>8,000</span>
</button>
<button
class="rps-stake"
type="button"
data-action="stake"
data-stake="8000"
aria-pressed="false"
>
<img src="assets/figma/coin.png" alt="" />
<span>8,000</span>
</button>
</div>
<button
class="rps-start"
type="button"
data-action="start"
data-i18n="rockGame.start"
>
START
</button>
<div
class="rps-refund-note"
data-i18n="rockGame.refundNote"
>
If no one joins the rock-paper-scissors game, the
coins will be refunded in 10 minutes.
</div>
</div>
</section>
<section class="rps-screen rps-matching">
<div class="rps-duel-card is-self">
<img src="assets/figma/player-card.png" alt="" />
<div class="rps-duel-avatar">
<img
class="is-empty"
data-role="self-card-avatar"
alt=""
/>
</div>
<div class="rps-duel-name" data-role="self-card-name">
<div>NameName</div>
<div>Name...</div>
</div>
</div>
<div class="rps-duel-card is-opponent">
<img src="assets/figma/opponent-card.png" alt="" />
<div class="rps-duel-avatar">
<div class="rps-question">?</div>
</div>
<div class="rps-duel-name">?</div>
<div class="rps-duel-status">
<img src="assets/figma/search-icon.svg" alt="" />
<span data-i18n="rockGame.matching"
>Matching...</span
>
</div>
</div>
<div class="rps-search-hud">
<img src="assets/figma/search-hud.png" alt="" />
<img src="assets/figma/search-icon.svg" alt="" />
</div>
<div
class="rps-bottom-status"
data-role="bottom-status"
data-i18n="rockGame.matching"
>
Matching...
</div>
</section>
<section class="rps-screen rps-vs-screen">
<div class="rps-duel-card is-self">
<img src="assets/figma/player-card.png" alt="" />
<div class="rps-duel-avatar">
<img
class="is-empty"
data-role="self-vs-avatar"
alt=""
/>
</div>
<div class="rps-duel-name" data-role="self-vs-name">
<div>NameName</div>
<div>Name...</div>
</div>
</div>
<div class="rps-duel-card is-opponent">
<img src="assets/figma/opponent-card.png" alt="" />
<div class="rps-duel-avatar">
<div class="rps-question">?</div>
</div>
<div class="rps-duel-name" data-role="opponent-vs-name">
<div>?</div>
<div></div>
</div>
<div class="rps-duel-status">
<img src="assets/figma/search-icon.svg" alt="" />
<span data-i18n="rockGame.matched">Matched</span>
</div>
</div>
<div class="game-vs-badge rps-vs-badge">
<img
class="game-layer-img game-contain"
src="assets/figma/vs.png"
alt=""
/>
</div>
<div class="rps-bottom-status" data-i18n="rockGame.matched">
Matched
</div>
</section>
<section class="rps-screen rps-battle">
<div class="rps-duel-card is-self">
<img src="assets/figma/player-card.png" alt="" />
<div class="rps-duel-avatar">
<img
class="is-empty"
data-role="self-battle-avatar"
alt=""
/>
</div>
<div class="rps-duel-name" data-role="self-battle-name">
<div>NameName</div>
<div>Name...</div>
</div>
</div>
<div class="rps-duel-card is-opponent">
<img src="assets/figma/opponent-card.png" alt="" />
<div class="rps-duel-avatar">
<img
src="assets/figma/opponent-question.png"
alt=""
/>
</div>
<div
class="rps-duel-name"
data-role="opponent-battle-name"
>
<div>NameName</div>
<div>Name...</div>
</div>
</div>
<div class="game-vs-badge rps-vs-badge">
<img
class="game-layer-img game-contain"
src="assets/figma/vs.png"
alt=""
/>
</div>
<div class="rps-showcase">
<div class="rps-hand-zone is-left">
<img
class="rps-ring"
src="assets/figma/red-ring.png"
alt=""
/>
<img
class="rps-flare"
src="assets/figma/red-flare.png"
alt=""
/>
<img
class="rps-hand"
data-role="self-battle-hand"
data-gesture="rock"
src="assets/figma/rock.png"
alt=""
/>
</div>
<div class="rps-hand-zone is-right">
<img
class="rps-ring"
src="assets/figma/blue-ring.png"
alt=""
/>
<img
class="rps-flare"
src="assets/figma/blue-flare.png"
alt=""
/>
<img
class="rps-hand"
data-role="opponent-battle-hand"
data-gesture="paper"
src="assets/figma/paper.png"
alt=""
/>
</div>
</div>
</section>
<section class="rps-screen rps-victory">
<img
class="rps-victory-title"
src="assets/figma/victory-title.svg"
alt=""
/>
<div class="rps-avatar-ring">
<img
class="rps-victory-avatar-bg"
src="assets/figma/victory-avatar-bg.png"
alt=""
/>
<img
class="rps-avatar-img is-empty"
data-role="winner-avatar"
alt=""
/>
<img
class="rps-victory-avatar-frame"
src="assets/figma/victory-avatar-frame.png"
alt=""
/>
</div>
<div class="rps-victory-name" data-role="winner-name">
NameNameName...
</div>
<div class="rps-reward-panel">
<img src="assets/figma/win-coins.png" alt="" />
<div class="rps-reward-copy">
<strong data-role="result-label">Win</strong>
<b data-role="win-amount">1,900,000</b>
<span data-i18n="rockGame.coins">Coins</span>
</div>
</div>
<div class="rps-victory-actions">
<button
class="rps-victory-button"
type="button"
data-action="rematch"
data-i18n="rockGame.rematch"
>
Re-match
</button>
<button
class="rps-victory-button is-secondary"
type="button"
data-action="play-again"
data-i18n="rockGame.playAgain"
>
Play again
</button>
</div>
</section>
<div class="rps-message" data-role="message"></div>
</main>
</div>
<script src="../../common/api.js"></script>
<script src="../../common/params.js"></script>
<script src="../../common/jsbridge.js"></script>
<script>
window.HyAppI18nSupported = ['en', 'ar', 'es'];
</script>
<script src="../../common/i18n.js?v=rock-game-20260611"></script>
<script>
(function () {
var DESIGN_WIDTH = 375;
var DESIGN_HEIGHT = 812;
var GAME_ID = 'rock';
var GAME_CODE = 'game_rock';
var root = document.documentElement;
var frame = document.querySelector('.rps-frame');
var timers = [];
var pollTimer = 0;
var transparentPixel =
'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%221%22 height=%221%22%2F%3E';
var gestures = {
rock: {
labelKey: 'rockGame.rock',
label: 'Rock',
image: 'assets/figma/rock.png',
beats: 'scissors',
},
paper: {
labelKey: 'rockGame.paper',
label: 'Paper',
image: 'assets/figma/paper.png',
beats: 'rock',
},
scissors: {
labelKey: 'rockGame.scissors',
label: 'Scissors',
image: 'assets/figma/scissors.png',
beats: 'paper',
},
};
var state = {
profile: {},
balanceCoin: 159,
selectedGesture: 'rock',
selectedStake: 8000,
opponentGesture: 'paper',
opponent: {
nickname: 'NameName',
display_user_id: 'Name...',
},
config: null,
match: null,
busy: false,
roundID: 0,
serverTimeMS: 0,
};
function updateScale() {
var scale = Math.min(
window.innerWidth / DESIGN_WIDTH,
window.innerHeight / DESIGN_HEIGHT
);
root.style.setProperty(
'--game-scale',
String(Math.max(scale, 0.1))
);
}
function postBridge(action, payload) {
if (window.HyAppBridge && window.HyAppBridge.post) {
window.HyAppBridge.post(action, payload || {});
}
}
function schedule(callback, ms) {
var timer = window.setTimeout(function () {
timers = timers.filter(function (item) {
return item !== timer;
});
callback();
}, ms);
timers.push(timer);
return timer;
}
function clearTimers() {
clearPollTimer();
timers.forEach(function (timer) {
window.clearTimeout(timer);
});
timers = [];
}
function clearPollTimer() {
if (!pollTimer) return;
window.clearTimeout(pollTimer);
pollTimer = 0;
}
function api() {
return window.HyAppAPI || {};
}
function rpsAPI() {
return api().game && api().game.rps;
}
function role(name) {
return document.querySelector('[data-role="' + name + '"]');
}
function each(nodes, iteratee) {
Array.prototype.forEach.call(nodes, iteratee);
}
function i18nText(key, fallback) {
return window.HyAppI18n && window.HyAppI18n.t
? window.HyAppI18n.t(key, fallback)
: fallback;
}
function formatCoin(value) {
var number = Number(value || 0);
if (!Number.isFinite(number)) number = 0;
return Math.trunc(Math.max(number, 0)).toLocaleString(
'en-US'
);
}
function formatTopCoin(value) {
var number = Number(value || 0);
if (!Number.isFinite(number)) number = 0;
number = Math.max(0, Math.trunc(number));
if (number < 1000000) return formatCoin(number);
return Math.floor(number / 1000) + 'K';
}
function currentRoomID() {
var raw = currentParams();
return raw && raw.get
? raw.get('room_id') || raw.get('roomId') || ''
: '';
}
function entryMatchID() {
var raw = currentParams();
return raw && raw.get
? raw.get('match_id') || raw.get('matchId') || ''
: '';
}
function matchRequestMode() {
var raw = currentParams();
var mode =
raw && raw.get
? String(
raw.get('rps_mode') ||
raw.get('match_mode') ||
raw.get('mode') ||
''
)
.trim()
.toLowerCase()
: '';
if (entryMatchID()) return 'join';
if (mode === 'create' || mode === 'waiting') {
return 'create';
}
return 'match';
}
function currentParams() {
var params =
(window.HyAppParams && window.HyAppParams.current) ||
{};
if (params.raw && params.raw.get) return params.raw;
var raw = new URLSearchParams(window.location.search);
var hash = window.location.hash || '';
if (hash.indexOf('?') >= 0) {
new URLSearchParams(
hash.split('?').slice(1).join('?')
).forEach(function (value, key) {
if (!raw.has(key)) raw.set(key, value);
});
}
return raw;
}
function shouldUseMockFlow() {
var params = currentParams();
return (
params.get('mock') === '1' ||
params.get('demo') === '1' ||
params.get('rps_mock') === '1'
);
}
function displayName(user) {
user = user || {};
return (
user.nickname ||
user.username ||
user.name ||
user.display_user_id ||
user.user_id ||
'NameNameName...'
);
}
function displaySubName(user) {
user = user || {};
return user.display_user_id || user.user_id || 'Name...';
}
function avatarURL(user) {
user = user || {};
var url =
user.avatar || user.avatar_url || user.avatarUrl || '';
url = String(url || '').trim();
if (!url) return '';
var normalized = url.toLowerCase();
if (
normalized.indexOf('/avatar/default') !== -1 ||
normalized.indexOf('avatar/default.') !== -1 ||
normalized.indexOf('/default-avatar') !== -1 ||
normalized.indexOf('default_avatar') !== -1
) {
return '';
}
return url;
}
function setText(node, value) {
if (node) node.textContent = value == null ? '' : value;
}
function setImage(node, url) {
if (!node) return;
var next = String(url || '').trim();
if (!next) {
node.removeAttribute('src');
node.classList.add('is-empty');
return;
}
node.onerror = function () {
node.onerror = null;
node.src = transparentPixel;
node.classList.add('is-empty');
};
node.src = next;
node.classList.remove('is-empty');
}
function setNameLines(selector, user) {
var nodes = document.querySelectorAll(selector + ' div');
if (nodes[0]) nodes[0].textContent = displayName(user);
if (nodes[1]) nodes[1].textContent = displaySubName(user);
}
function setHand(node, gesture) {
var meta = gestures[gesture] || gestures.rock;
if (!node) return;
node.src = meta.image;
node.setAttribute('data-gesture', gesture);
node.alt = i18nText(meta.labelKey, meta.label);
}
function showMessage(message) {
setText(role('message'), message || '');
if (!message) return;
schedule(function () {
setText(role('message'), '');
}, 2200);
}
function setScreen(nextState) {
frame.setAttribute('data-state', nextState);
}
function renderCoin(value) {
var number = Number(value || 0);
if (!Number.isFinite(number)) number = 0;
number = Math.max(0, Math.trunc(number));
state.balanceCoin = number;
var fullLabel = formatCoin(number);
var topCoin = document.querySelector('.top-coin');
if (topCoin) {
topCoin.classList.toggle(
'is-compact',
number >= 1000000
);
topCoin.setAttribute('title', fullLabel);
topCoin.setAttribute(
'aria-label',
'Recharge ' + fullLabel
);
}
setText(
document.querySelector('.top-coin span'),
formatTopCoin(number)
);
}
function renderProfile() {
var profile = state.profile || {};
var avatar = avatarURL(profile);
setText(role('self-name'), displayName(profile));
setNameLines('[data-role="self-card-name"]', profile);
setNameLines('[data-role="self-vs-name"]', profile);
setNameLines('[data-role="self-battle-name"]', profile);
setImage(role('self-avatar'), avatar);
setImage(role('self-card-avatar'), avatar);
setImage(role('self-vs-avatar'), avatar);
setImage(role('self-battle-avatar'), avatar);
}
function renderSelection() {
each(
document.querySelectorAll('[data-action="gesture"]'),
function (button) {
var selected =
button.getAttribute('data-gesture') ===
state.selectedGesture;
button.setAttribute(
'aria-pressed',
selected ? 'true' : 'false'
);
}
);
var matchedStake = false;
each(
document.querySelectorAll('[data-action="stake"]'),
function (button) {
var selected =
!matchedStake &&
Number(
button.getAttribute('data-stake') || 0
) === state.selectedStake;
if (selected) matchedStake = true;
button.setAttribute(
'aria-pressed',
selected ? 'true' : 'false'
);
}
);
setHand(role('self-lobby-hand'), state.selectedGesture);
setHand(role('self-battle-hand'), state.selectedGesture);
setHand(
role('preview-opponent-hand'),
counterGestureFor(state.selectedGesture)
);
}
function renderOpponent() {
var opponent = state.opponent || {};
setNameLines('[data-role="opponent-vs-name"]', opponent);
setNameLines(
'[data-role="opponent-battle-name"]',
opponent
);
setHand(
role('opponent-battle-hand'),
state.opponentGesture
);
}
function renderVictory(result, match) {
var self = findSelfParticipant(match);
var winner = findWinnerParticipant(match);
var winnerIsSelf =
result === 'draw' ||
!winner ||
sameParticipant(winner, self);
var winnerUser = winnerIsSelf
? state.profile
: Object.assign(
{},
participantUser(winner),
state.opponent
);
var amount = payoutFromMatch(match, result);
var label =
result === 'draw'
? i18nText('rockGame.draw', 'Draw')
: result === 'lose'
? i18nText('rockGame.lose', 'Lose')
: i18nText('rockGame.win', 'Win');
setImage(role('winner-avatar'), avatarURL(winnerUser));
setText(role('winner-name'), displayName(winnerUser));
setText(role('result-label'), label);
setText(role('win-amount'), formatCoin(amount));
if (self && self.balance_after > 0) {
renderCoin(self.balance_after);
} else if (result === 'win' && !match) {
renderCoin(state.balanceCoin + state.selectedStake);
}
}
function losingGestureFor(gesture) {
return (
(gestures[gesture] && gestures[gesture].beats) ||
'scissors'
);
}
function counterGestureFor(gesture) {
if (gesture === 'rock') return 'paper';
if (gesture === 'paper') return 'scissors';
return 'rock';
}
function resultFor(selfGesture, opponentGesture) {
if (selfGesture === opponentGesture) return 'draw';
return losingGestureFor(selfGesture) === opponentGesture
? 'win'
: 'lose';
}
function chooseOpponentGesture() {
return losingGestureFor(state.selectedGesture);
}
function matchFromData(data) {
return (
(data && (data.match || data.rps_match)) || data || null
);
}
function participantUser(participant) {
participant = participant || {};
return Object.assign(
{},
participant.user || {},
participant.profile || {}
);
}
function readGesture(participant) {
participant = participant || {};
var gesture = String(
participant.gesture ||
participant.rps_gesture ||
participant.choice ||
participant.selected_gesture ||
''
)
.trim()
.toLowerCase();
return gestures[gesture] ? gesture : '';
}
function readResult(participant) {
participant = participant || {};
return String(participant.result || '')
.trim()
.toLowerCase();
}
function selfUserID() {
var profile = state.profile || {};
return String(
profile.user_id ||
profile.userId ||
profile.id ||
profile.display_user_id ||
''
);
}
function sameParticipant(left, right) {
if (!left || !right) return false;
var leftID = String(
left.user_id ||
left.userId ||
left.display_user_id ||
''
);
var rightID = String(
right.user_id ||
right.userId ||
right.display_user_id ||
''
);
if (leftID && rightID) return leftID === rightID;
return left === right;
}
function findSelfParticipant(match) {
var participants = (match && match.participants) || [];
var id = selfUserID();
for (var i = 0; i < participants.length; i += 1) {
var candidateID = String(
participants[i].user_id ||
participants[i].userId ||
participants[i].display_user_id ||
''
);
if (id && candidateID === id) return participants[i];
}
return participants[0] || null;
}
function findOpponentParticipant(match, self) {
var participants = (match && match.participants) || [];
for (var i = 0; i < participants.length; i += 1) {
if (!sameParticipant(participants[i], self)) {
return participants[i];
}
}
return null;
}
function findWinnerParticipant(match) {
var participants = (match && match.participants) || [];
var winnerID = String(
(match &&
(match.winner_user_id || match.winnerUserId)) ||
''
);
for (var i = 0; i < participants.length; i += 1) {
if (
winnerID &&
String(participants[i].user_id || '') === winnerID
) {
return participants[i];
}
if (readResult(participants[i]) === 'win') {
return participants[i];
}
}
return null;
}
function isWaitingMatch(match) {
var status = String(
(match && match.status) || ''
).toLowerCase();
var phase = String(
(match && match.phase) || ''
).toLowerCase();
return (
status === 'waiting' ||
status === 'pending' ||
phase === 'waiting' ||
phase === 'matching'
);
}
function isSettledMatch(match) {
var status = String(
(match && match.status) || ''
).toLowerCase();
var phase = String(
(match && match.phase) || ''
).toLowerCase();
return (
status === 'settled' ||
status === 'finished' ||
phase === 'settled' ||
phase === 'finished'
);
}
function renderMatch(match) {
if (!match) return;
var self = findSelfParticipant(match);
var opponent = findOpponentParticipant(match, self);
if (match.stake_coin > 0) {
state.selectedStake = Number(match.stake_coin);
}
if (readGesture(self)) {
state.selectedGesture = readGesture(self);
}
if (opponent) {
state.opponent = Object.assign(
{},
participantUser(opponent),
{
user_id: opponent.user_id,
display_user_id:
participantUser(opponent).display_user_id ||
opponent.display_user_id ||
opponent.user_id,
}
);
if (readGesture(opponent)) {
state.opponentGesture = readGesture(opponent);
}
}
renderSelection();
renderOpponent();
}
function resultFromMatch(match) {
var self = findSelfParticipant(match);
var explicit = readResult(self);
if (
explicit === 'win' ||
explicit === 'lose' ||
explicit === 'draw'
) {
return explicit;
}
var opponent = findOpponentParticipant(match, self);
var selfGesture =
readGesture(self) || state.selectedGesture;
var opponentGesture =
readGesture(opponent) || state.opponentGesture;
return resultFor(selfGesture, opponentGesture);
}
function payoutFromMatch(match, result) {
var self = findSelfParticipant(match);
if (self && self.payout_coin > 0) return self.payout_coin;
if (result === 'draw') return 0;
return state.selectedStake * 2;
}
function enabledStakeOptions(config) {
var options = (config && config.stake_options) || [];
return options
.filter(function (item) {
return (
item &&
item.enabled !== false &&
Number(item.stake_coin || 0) > 0
);
})
.sort(function (left, right) {
return (
Number(left.sort_order || 0) -
Number(right.sort_order || 0) ||
Number(left.stake_coin || 0) -
Number(right.stake_coin || 0)
);
});
}
function renderStakeOptions(config) {
var options = enabledStakeOptions(config);
var buttons = document.querySelectorAll(
'[data-action="stake"]'
);
if (!options.length) {
renderSelection();
return;
}
if (!state.selectedStake) {
state.selectedStake = Number(
options[0].stake_coin || 0
);
}
var matchedSelected = false;
each(buttons, function (button, index) {
var option = options[index];
if (!option) {
button.style.display = 'none';
return;
}
button.style.display = '';
button.setAttribute('data-stake', option.stake_coin);
button.setAttribute(
'aria-label',
formatCoin(option.stake_coin)
);
setText(
button.querySelector('span'),
formatCoin(option.stake_coin)
);
var selected =
!matchedSelected &&
Number(option.stake_coin) === state.selectedStake;
if (selected) matchedSelected = true;
button.setAttribute(
'aria-pressed',
selected ? 'true' : 'false'
);
});
}
function pollMatch(matchID) {
clearPollTimer();
pollTimer = window.setTimeout(function () {
pollTimer = 0;
if (!state.busy || !matchID || !rpsAPI()) return;
rpsAPI()
.getMatch(matchID)
.then(handleMatchData)
.catch(function (error) {
showMessage(error.message);
pollMatch(matchID);
});
}, 1000);
}
function enterWaiting(match) {
state.match = match;
state.busy = true;
renderMatch(match);
setScreen('searching');
pollMatch(match.match_id || match.matchId);
}
function beginRound(match) {
clearPollTimer();
state.match = match;
state.busy = true;
state.roundID += 1;
var roundID = state.roundID;
renderMatch(match);
showVS(roundID, match);
}
function rollAndAnimate(roundID, match) {
var matchID = match && (match.match_id || match.matchId);
if (!rpsAPI() || !matchID) {
showMessage(
i18nText(
'rockGame.apiUnavailable',
'RPS API unavailable'
)
);
resetToLobby();
return;
}
rpsAPI()
.roll(matchID, {
game_id: GAME_ID,
gesture: state.selectedGesture,
})
.then(function (data) {
var nextMatch = matchFromData(data);
if (roundID !== state.roundID) return;
state.match = nextMatch || match;
renderMatch(state.match);
showBattle(roundID, state.match);
})
.catch(function (error) {
showMessage(error.message);
resetToLobby();
});
}
function handleMatchData(data) {
var match = matchFromData(data);
if (!match || !(match.match_id || match.matchId)) return;
state.serverTimeMS =
(data && data.server_time_ms) || Date.now();
state.match = match;
renderMatch(match);
if (isSettledMatch(match)) {
state.roundID += 1;
showBattle(state.roundID, match);
return;
}
if (isWaitingMatch(match)) {
enterWaiting(match);
return;
}
beginRound(match);
}
function startMatch() {
if (state.busy) return;
if (shouldUseMockFlow()) {
startMockMatch();
return;
}
if (!rpsAPI()) {
showMessage(
i18nText(
'rockGame.apiUnavailable',
'RPS API unavailable'
)
);
return;
}
clearTimers();
state.busy = true;
state.roundID += 1;
state.match = null;
setScreen('searching');
postBridge('gameRPSMatchRequested', {
game_code: GAME_CODE,
game_id: GAME_ID,
room_id: currentRoomID(),
match_id: entryMatchID(),
mode: matchRequestMode(),
stake_coin: state.selectedStake,
gesture: state.selectedGesture,
});
var payload = {
game_id: GAME_ID,
room_id: currentRoomID(),
stake_coin: state.selectedStake,
gesture: state.selectedGesture,
};
var mode = matchRequestMode();
var request;
// H5 默认走快速匹配;分享或指定局入口带 match_id 时只加入该局,后台仍会校验 rock 边界和预选手势。
if (mode === 'join') {
request = rpsAPI().join(entryMatchID(), {
game_id: GAME_ID,
gesture: state.selectedGesture,
});
} else if (mode === 'create') {
request = rpsAPI().create(payload);
} else {
request = rpsAPI().match(payload);
}
request.then(handleMatchData).catch(function (error) {
state.busy = false;
showMessage(error.message);
setScreen('lobby');
});
}
function startMockMatch() {
clearTimers();
state.busy = true;
state.roundID += 1;
var roundID = state.roundID;
state.opponentGesture = chooseOpponentGesture();
state.opponent = {
nickname: 'NameName',
display_user_id: 'Name...',
};
renderOpponent();
setScreen('searching');
postBridge('gameRPSMatchRequested', {
game_code: GAME_CODE,
game_id: GAME_ID,
room_id: currentRoomID(),
stake_coin: state.selectedStake,
gesture: state.selectedGesture,
mock: true,
});
schedule(function () {
if (roundID !== state.roundID) return;
showVS(roundID, null);
}, 1200);
}
function showVS(roundID, match) {
renderOpponent();
frame.classList.remove('is-vs-impact', 'is-screen-impact');
setScreen('vs');
void frame.offsetWidth;
frame.classList.add('is-vs-impact', 'is-screen-impact');
postBridge('gameRPSMatched', {
game_code: GAME_CODE,
game_id: GAME_ID,
room_id: currentRoomID(),
});
schedule(function () {
frame.classList.remove(
'is-vs-impact',
'is-screen-impact'
);
if (roundID !== state.roundID) return;
if (match) {
rollAndAnimate(roundID, match);
} else {
showBattle(roundID, null);
}
}, 1050);
}
function showBattle(roundID, match) {
if (match) renderMatch(match);
else {
renderSelection();
renderOpponent();
}
setScreen('battle');
postBridge('gameRPSRevealing', {
game_code: GAME_CODE,
game_id: GAME_ID,
self_gesture: state.selectedGesture,
opponent_gesture: state.opponentGesture,
});
schedule(function () {
if (roundID !== state.roundID) return;
if (match) {
finishRound(resultFromMatch(match), match);
return;
}
finishRound(
resultFor(
state.selectedGesture,
state.opponentGesture
),
null
);
}, 1800);
}
function finishRound(result, match) {
state.busy = false;
if (match) renderMatch(match);
renderVictory(result, match);
setScreen('victory');
postBridge('gameRPSSettled', {
game_code: GAME_CODE,
game_id: GAME_ID,
match_id: match && (match.match_id || match.matchId),
result: result,
self_gesture: state.selectedGesture,
opponent_gesture: state.opponentGesture,
stake_coin: state.selectedStake,
});
}
function resetToLobby() {
clearTimers();
state.busy = false;
state.roundID += 1;
state.match = null;
frame.classList.remove('is-vs-impact', 'is-screen-impact');
setScreen('lobby');
renderSelection();
renderProfile();
renderOpponent();
}
function cancelMatch() {
var match = state.match;
var matchID = match && (match.match_id || match.matchId);
if (!rpsAPI() || !matchID || isSettledMatch(match)) {
return Promise.resolve();
}
return rpsAPI()
.cancel(matchID)
.catch(function () {});
}
function openRecharge() {
var payload = {
source: 'rock',
game_code: GAME_CODE,
game_id: GAME_ID,
};
if (
window.HyAppBridge &&
typeof window.HyAppBridge.recharge === 'function'
) {
window.HyAppBridge.recharge(payload);
return;
}
postBridge('gameRecharge', payload);
postBridge('recharge', payload);
}
function loadProfile() {
if (shouldUseMockFlow()) {
renderProfile();
return Promise.resolve();
}
if (!api().user || !api().user.me) {
renderProfile();
return Promise.resolve();
}
return api()
.user.me()
.then(function (profile) {
state.profile = profile || {};
renderProfile();
})
.catch(function () {
renderProfile();
});
}
function loadBalance() {
if (shouldUseMockFlow()) {
renderCoin(state.balanceCoin);
return Promise.resolve();
}
if (!api().wallet || !api().wallet.balances) {
renderCoin(state.balanceCoin);
return Promise.resolve();
}
return api()
.wallet.balances('COIN')
.then(function (data) {
var balances = (data && data.balances) || [];
for (var i = 0; i < balances.length; i += 1) {
if (balances[i].asset_type === 'COIN') {
renderCoin(
balances[i].available_amount || 0
);
return;
}
}
renderCoin(state.balanceCoin);
})
.catch(function () {
renderCoin(state.balanceCoin);
});
}
function loadConfig() {
if (!rpsAPI() || shouldUseMockFlow()) {
renderStakeOptions(state.config);
return Promise.resolve();
}
return rpsAPI()
.config(GAME_ID)
.then(function (data) {
state.config =
(data && (data.config || data.rps_config)) ||
data ||
null;
renderStakeOptions(state.config);
})
.catch(function () {
renderStakeOptions(state.config);
});
}
function refreshLocalizedRuntimeText() {
renderSelection();
renderStakeOptions(state.config);
if (
!state.busy &&
frame.getAttribute('data-state') === 'lobby'
) {
setText(role('message'), '');
}
}
function initActions() {
document.addEventListener('click', function (event) {
var target = event.target.closest('[data-action]');
if (!target) return;
var action = target.getAttribute('data-action');
if (action === 'back') {
if (state.busy) {
cancelMatch().then(resetToLobby);
return;
}
postBridge('back');
return;
}
if (action === 'recharge') {
openRecharge();
return;
}
if (action === 'gesture') {
if (state.busy) return;
state.selectedGesture =
target.getAttribute('data-gesture') || 'rock';
renderSelection();
return;
}
if (action === 'stake') {
if (state.busy) return;
state.selectedStake = Number(
target.getAttribute('data-stake') || 0
);
each(
document.querySelectorAll(
'[data-action="stake"]'
),
function (button) {
button.setAttribute(
'aria-pressed',
button === target ? 'true' : 'false'
);
}
);
return;
}
if (action === 'start') {
if (!state.selectedStake) {
showMessage(
i18nText(
'rockGame.selectStake',
'Select a stake'
)
);
return;
}
startMatch();
return;
}
if (action === 'rematch') {
cancelMatch().then(function () {
resetToLobby();
startMatch();
});
return;
}
if (action === 'play-again') {
resetToLobby();
}
});
}
window.HyAppRPSGame = {
reset: resetToLobby,
start: startMatch,
finish: function (payload) {
payload = payload || {};
if (
payload.self_gesture &&
gestures[payload.self_gesture]
) {
state.selectedGesture = payload.self_gesture;
}
if (
payload.opponent_gesture &&
gestures[payload.opponent_gesture]
) {
state.opponentGesture = payload.opponent_gesture;
}
if (payload.opponent) state.opponent = payload.opponent;
renderSelection();
renderOpponent();
finishRound(
payload.result ||
resultFor(
state.selectedGesture,
state.opponentGesture
)
);
},
};
updateScale();
initActions();
window.addEventListener('beforeunload', function () {
cancelMatch();
});
renderCoin(state.balanceCoin);
renderSelection();
renderOpponent();
Promise.all([loadProfile(), loadBalance(), loadConfig()]).then(
function () {
postBridge('gameRPSReady', {
game_code: GAME_CODE,
game_id: GAME_ID,
room_id: currentRoomID(),
});
}
);
window.addEventListener(
'hyapp:i18n-ready',
refreshLocalizedRuntimeText
);
window.addEventListener('resize', updateScale);
window.addEventListener('orientationchange', updateScale);
if (window.HyAppBridge && window.HyAppBridge.ready) {
window.HyAppBridge.ready({
page: 'game/rock',
figma_node_id: '491:135',
});
}
})();
</script>
</body>
</html>