2703 lines
98 KiB
HTML
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>
|