Spaces:
Running
Running
Upload 59 files
Browse files- api.js +16 -14
- handlers/carrom.js +10 -9
- handlers/omi.js +2 -1
- handlers/webCards.js +2 -1
- webapp/cards.html +21 -3
- webapp/carrom.html +2 -1
- webapp/omi.html +19 -1
api.js
CHANGED
|
@@ -2,6 +2,7 @@ const express = require('express');
|
|
| 2 |
const crypto = require('crypto');
|
| 3 |
const path = require('path');
|
| 4 |
const User = require('./models/User');
|
|
|
|
| 5 |
|
| 6 |
const app = express();
|
| 7 |
app.use(express.json());
|
|
@@ -141,6 +142,15 @@ app.get('/me', auth, async (req, res) => {
|
|
| 141 |
} catch (err) { res.status(500).json({ error: 'Error' }); }
|
| 142 |
});
|
| 143 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
// ── Carrom API ──
|
| 145 |
app.post('/carrom/mode', auth, (req, res) => {
|
| 146 |
try {
|
|
@@ -223,11 +233,7 @@ app.post('/carrom/shoot', auth, async (req, res) => {
|
|
| 223 |
await leveling.addXP(w.userId, 200);
|
| 224 |
await u.save();
|
| 225 |
}
|
| 226 |
-
|
| 227 |
-
try {
|
| 228 |
-
await global._tgClient.sendMessage(game.chatId, { message: `🏆 <b>CARROM OVER!</b> ${winners.map(w => w.name).join(' & ')} win <b>$${pot}</b>!` });
|
| 229 |
-
} catch {}
|
| 230 |
-
}
|
| 231 |
setTimeout(() => sessions.delete(req.body.gameId), 5000);
|
| 232 |
}
|
| 233 |
res.json({ ok: true, state: game.getState(req.tgUser.id.toString()) });
|
|
@@ -259,11 +265,7 @@ app.post('/carrom/forfeit', auth, async (req, res) => {
|
|
| 259 |
await leveling.addXP(w.userId, 100);
|
| 260 |
await u.save();
|
| 261 |
}
|
| 262 |
-
|
| 263 |
-
try {
|
| 264 |
-
await global._tgClient.sendMessage(game.chatId, { message: `🏳️ <b>${quitter}</b> gave up! ${winners.map(w => w.name).join(' & ')} win <b>$${pot}</b>!` });
|
| 265 |
-
} catch {}
|
| 266 |
-
}
|
| 267 |
}
|
| 268 |
setTimeout(() => sessions.delete(gameId), 5000);
|
| 269 |
res.json({ ok: true, state: game.getState(userId) });
|
|
@@ -310,7 +312,7 @@ app.post('/wcards/play', auth, async (req, res) => {
|
|
| 310 |
const pot = game.bet * game.players.length;
|
| 311 |
const share = Math.floor(pot / winners.length);
|
| 312 |
for (const w of winners) { let u = await User.findOne({userId:w.userId})||await User.create({userId:w.userId}); u.wallet+=share; await leveling.addXP(w.userId,150); await u.save(); }
|
| 313 |
-
|
| 314 |
setTimeout(() => sessions.delete(req.body.gameId), 5000);
|
| 315 |
}
|
| 316 |
res.json({ ok: true, state: game.getState(req.tgUser.id.toString()) });
|
|
@@ -343,7 +345,7 @@ app.post('/wcards/forfeit', auth, async (req, res) => {
|
|
| 343 |
const pot = game.bet * game.players.length;
|
| 344 |
const share = winners.length ? Math.floor(pot / winners.length) : 0;
|
| 345 |
for (const w of winners) { let u = await User.findOne({userId:w.userId})||await User.create({userId:w.userId}); u.wallet+=share; await leveling.addXP(w.userId,75); await u.save(); }
|
| 346 |
-
|
| 347 |
}
|
| 348 |
setTimeout(() => sessions.delete(req.body.gameId), 4000);
|
| 349 |
res.json({ ok: true, state: game.getState(userId) });
|
|
@@ -398,7 +400,7 @@ app.post('/omi/play', auth, async (req, res) => {
|
|
| 398 |
const pot = game.bet * game.players.length;
|
| 399 |
const share = Math.floor(pot / winners.length);
|
| 400 |
for (const w of winners) { let u = await User.findOne({userId:w.userId})||await User.create({userId:w.userId}); u.wallet+=share; await leveling.addXP(w.userId,150); await u.save(); }
|
| 401 |
-
|
| 402 |
setTimeout(() => sessions.delete(req.body.gameId), 5000);
|
| 403 |
}
|
| 404 |
res.json({ ok: true, state: game.getState(req.tgUser.id.toString()) });
|
|
@@ -421,7 +423,7 @@ app.post('/omi/forfeit', auth, async (req, res) => {
|
|
| 421 |
const pot = game.bet * game.players.length;
|
| 422 |
const share = winners.length ? Math.floor(pot / winners.length) : 0;
|
| 423 |
for (const w of winners) { let u = await User.findOne({userId:w.userId})||await User.create({userId:w.userId}); u.wallet+=share; await leveling.addXP(w.userId,75); await u.save(); }
|
| 424 |
-
|
| 425 |
}
|
| 426 |
setTimeout(() => sessions.delete(req.body.gameId), 4000);
|
| 427 |
res.json({ ok: true, state: game.getState(userId) });
|
|
|
|
| 2 |
const crypto = require('crypto');
|
| 3 |
const path = require('path');
|
| 4 |
const User = require('./models/User');
|
| 5 |
+
const { editMsg } = require('./utils/editMsg');
|
| 6 |
|
| 7 |
const app = express();
|
| 8 |
app.use(express.json());
|
|
|
|
| 142 |
} catch (err) { res.status(500).json({ error: 'Error' }); }
|
| 143 |
});
|
| 144 |
|
| 145 |
+
async function editGameChatResult(game, text) {
|
| 146 |
+
if (!global._tgClient || !game?.chatId || !game?.chatMsgId) return;
|
| 147 |
+
try {
|
| 148 |
+
await editMsg(global._tgClient, game.chatId, game.chatMsgId, text, null);
|
| 149 |
+
} catch (e) {
|
| 150 |
+
console.error('[API] editGameChatResult:', e.message);
|
| 151 |
+
}
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
// ── Carrom API ──
|
| 155 |
app.post('/carrom/mode', auth, (req, res) => {
|
| 156 |
try {
|
|
|
|
| 233 |
await leveling.addXP(w.userId, 200);
|
| 234 |
await u.save();
|
| 235 |
}
|
| 236 |
+
await editGameChatResult(game, `🏆 <b>CARROM OVER!</b>\n${winners.map(w => w.name).join(' & ')} win <b>$${pot}</b>!`);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
setTimeout(() => sessions.delete(req.body.gameId), 5000);
|
| 238 |
}
|
| 239 |
res.json({ ok: true, state: game.getState(req.tgUser.id.toString()) });
|
|
|
|
| 265 |
await leveling.addXP(w.userId, 100);
|
| 266 |
await u.save();
|
| 267 |
}
|
| 268 |
+
await editGameChatResult(game, `🏳️ <b>${quitter}</b> gave up!\n${winners.map(w => w.name).join(' & ')} win <b>$${pot}</b>!`);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
}
|
| 270 |
setTimeout(() => sessions.delete(gameId), 5000);
|
| 271 |
res.json({ ok: true, state: game.getState(userId) });
|
|
|
|
| 312 |
const pot = game.bet * game.players.length;
|
| 313 |
const share = Math.floor(pot / winners.length);
|
| 314 |
for (const w of winners) { let u = await User.findOne({userId:w.userId})||await User.create({userId:w.userId}); u.wallet+=share; await leveling.addXP(w.userId,150); await u.save(); }
|
| 315 |
+
await editGameChatResult(game, `🃏 <b>CARDS OVER!</b>\n${winners.map(w=>w.name).join(' & ')} win <b>$${pot}</b>!`);
|
| 316 |
setTimeout(() => sessions.delete(req.body.gameId), 5000);
|
| 317 |
}
|
| 318 |
res.json({ ok: true, state: game.getState(req.tgUser.id.toString()) });
|
|
|
|
| 345 |
const pot = game.bet * game.players.length;
|
| 346 |
const share = winners.length ? Math.floor(pot / winners.length) : 0;
|
| 347 |
for (const w of winners) { let u = await User.findOne({userId:w.userId})||await User.create({userId:w.userId}); u.wallet+=share; await leveling.addXP(w.userId,75); await u.save(); }
|
| 348 |
+
await editGameChatResult(game, `🏳️ <b>${quitter}</b> gave up!\n${winners.map(w=>w.name).join(' & ')} win <b>$${pot}</b>!`);
|
| 349 |
}
|
| 350 |
setTimeout(() => sessions.delete(req.body.gameId), 4000);
|
| 351 |
res.json({ ok: true, state: game.getState(userId) });
|
|
|
|
| 400 |
const pot = game.bet * game.players.length;
|
| 401 |
const share = Math.floor(pot / winners.length);
|
| 402 |
for (const w of winners) { let u = await User.findOne({userId:w.userId})||await User.create({userId:w.userId}); u.wallet+=share; await leveling.addXP(w.userId,150); await u.save(); }
|
| 403 |
+
await editGameChatResult(game, `🃏 <b>OMI OVER!</b>\n${winners.map(w=>w.name).join(' & ')} win <b>$${pot}</b>!`);
|
| 404 |
setTimeout(() => sessions.delete(req.body.gameId), 5000);
|
| 405 |
}
|
| 406 |
res.json({ ok: true, state: game.getState(req.tgUser.id.toString()) });
|
|
|
|
| 423 |
const pot = game.bet * game.players.length;
|
| 424 |
const share = winners.length ? Math.floor(pot / winners.length) : 0;
|
| 425 |
for (const w of winners) { let u = await User.findOne({userId:w.userId})||await User.create({userId:w.userId}); u.wallet+=share; await leveling.addXP(w.userId,75); await u.save(); }
|
| 426 |
+
await editGameChatResult(game, `🏳️ <b>${quitter}</b> gave up!\n${winners.map(w=>w.name).join(' & ')} win <b>$${pot}</b>!`);
|
| 427 |
}
|
| 428 |
setTimeout(() => sessions.delete(req.body.gameId), 4000);
|
| 429 |
res.json({ ok: true, state: game.getState(userId) });
|
handlers/carrom.js
CHANGED
|
@@ -105,15 +105,16 @@ const startCarrom = async (client, event) => {
|
|
| 105 |
|
| 106 |
const gameId = `crm:${event.chatId}:${crypto.randomBytes(4).toString('hex')}`;
|
| 107 |
const name = await getName(client, userId);
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
|
|
|
| 117 |
|
| 118 |
game._timer = setTimeout(async () => {
|
| 119 |
const g = sessions.get(gameId);
|
|
|
|
| 105 |
|
| 106 |
const gameId = `crm:${event.chatId}:${crypto.randomBytes(4).toString('hex')}`;
|
| 107 |
const name = await getName(client, userId);
|
| 108 |
+
const game = new CarromGame(bet, 4);
|
| 109 |
+
game.addPlayer(userId, name);
|
| 110 |
+
game.chatId = event.chatId.toString();
|
| 111 |
+
sessions.set(gameId, game);
|
| 112 |
+
|
| 113 |
+
const sent = await client.sendMessage(event.chatId, {
|
| 114 |
+
message: lobbyMsg(game),
|
| 115 |
+
buttons: lobbyButtons(game, gameId),
|
| 116 |
+
});
|
| 117 |
+
game.chatMsgId = sent.id;
|
| 118 |
|
| 119 |
game._timer = setTimeout(async () => {
|
| 120 |
const g = sessions.get(gameId);
|
handlers/omi.js
CHANGED
|
@@ -23,7 +23,7 @@ const startOmi = async (client, event) => {
|
|
| 23 |
game.chatId = event.chatId.toString();
|
| 24 |
sessions.set(gameId, game);
|
| 25 |
|
| 26 |
-
await client.sendMessage(event.chatId, {
|
| 27 |
message: `🃏 <b>OMI</b>\nBet: $${bet} each | 2-4 players\n\n👤 ${name} (host)\n⏳ Waiting...`,
|
| 28 |
buttons: new Api.ReplyInlineMarkup({ rows: [
|
| 29 |
new Api.KeyboardButtonRow({ buttons: [
|
|
@@ -34,6 +34,7 @@ const startOmi = async (client, event) => {
|
|
| 34 |
] })
|
| 35 |
] })
|
| 36 |
});
|
|
|
|
| 37 |
|
| 38 |
game._timer = setTimeout(async () => {
|
| 39 |
const g = sessions.get(gameId);
|
|
|
|
| 23 |
game.chatId = event.chatId.toString();
|
| 24 |
sessions.set(gameId, game);
|
| 25 |
|
| 26 |
+
const sent = await client.sendMessage(event.chatId, {
|
| 27 |
message: `🃏 <b>OMI</b>\nBet: $${bet} each | 2-4 players\n\n👤 ${name} (host)\n⏳ Waiting...`,
|
| 28 |
buttons: new Api.ReplyInlineMarkup({ rows: [
|
| 29 |
new Api.KeyboardButtonRow({ buttons: [
|
|
|
|
| 34 |
] })
|
| 35 |
] })
|
| 36 |
});
|
| 37 |
+
game.chatMsgId = sent.id;
|
| 38 |
|
| 39 |
game._timer = setTimeout(async () => {
|
| 40 |
const g = sessions.get(gameId);
|
handlers/webCards.js
CHANGED
|
@@ -23,7 +23,7 @@ const startWebCards = async (client, event) => {
|
|
| 23 |
game.chatId = event.chatId.toString();
|
| 24 |
sessions.set(gameId, game);
|
| 25 |
|
| 26 |
-
await client.sendMessage(event.chatId, {
|
| 27 |
message: `🃏 <b>CARD GAME</b>\nBet: $${bet} each\n\n👤 ${name} (host)\n⏳ Waiting for players (2-6)...\n\n<i>Open the Mini App to see your cards!</i>`,
|
| 28 |
buttons: new Api.ReplyInlineMarkup({ rows: [
|
| 29 |
new Api.KeyboardButtonRow({ buttons: [
|
|
@@ -34,6 +34,7 @@ const startWebCards = async (client, event) => {
|
|
| 34 |
] })
|
| 35 |
] })
|
| 36 |
});
|
|
|
|
| 37 |
|
| 38 |
game._timer = setTimeout(async () => {
|
| 39 |
const g = sessions.get(gameId);
|
|
|
|
| 23 |
game.chatId = event.chatId.toString();
|
| 24 |
sessions.set(gameId, game);
|
| 25 |
|
| 26 |
+
const sent = await client.sendMessage(event.chatId, {
|
| 27 |
message: `🃏 <b>CARD GAME</b>\nBet: $${bet} each\n\n👤 ${name} (host)\n⏳ Waiting for players (2-6)...\n\n<i>Open the Mini App to see your cards!</i>`,
|
| 28 |
buttons: new Api.ReplyInlineMarkup({ rows: [
|
| 29 |
new Api.KeyboardButtonRow({ buttons: [
|
|
|
|
| 34 |
] })
|
| 35 |
] })
|
| 36 |
});
|
| 37 |
+
game.chatMsgId = sent.id;
|
| 38 |
|
| 39 |
game._timer = setTimeout(async () => {
|
| 40 |
const g = sessions.get(gameId);
|
webapp/cards.html
CHANGED
|
@@ -27,14 +27,14 @@ body{background:#0a1628;color:#fff;font-family:-apple-system,sans-serif;min-heig
|
|
| 27 |
.tcard{width:65px;height:95px;border-radius:8px;display:flex;flex-direction:column;align-items:center;justify-content:center;font-weight:700;position:relative;transition:transform .3s,box-shadow .3s;}
|
| 28 |
.tcard.facedown{background:linear-gradient(135deg,#1e40af,#1e3a8a);border:2px solid #3b82f6;box-shadow:0 4px 12px rgba(59,130,246,.2);}
|
| 29 |
.tcard.facedown::after{content:'🂠';font-size:32px;}
|
| 30 |
-
.tcard.faceup{background:#fff;color:#1a1a1a;border:2px solid #ddd;box-shadow:0 4px 15px rgba(0,0,0,.3);
|
| 31 |
.tcard.faceup .tsuit{font-size:24px;}
|
| 32 |
.tcard.faceup .tlabel{font-size:18px;font-weight:800;}
|
| 33 |
.tcard.faceup.red{color:#dc2626;}
|
| 34 |
.tcard.faceup.winner{border-color:#f59e0b;box-shadow:0 0 20px rgba(245,158,11,.4);}
|
| 35 |
.tcard-name{font-size:9px;color:#888;text-align:center;margin-top:2px;max-width:65px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
| 36 |
/* CardMeister card wrappers (table) */
|
| 37 |
-
.tcard-wrap{display:inline-block;border-radius:8px;box-shadow:0 4px 15px rgba(0,0,0,.3);transition:transform .3s,box-shadow .3s;
|
| 38 |
.tcard-wrap svg,.tcard-wrap playing-card,.tcard-wrap playing-card img{display:block;border-radius:8px;}
|
| 39 |
.tcard-wrap playing-card img{width:100%;height:100%;}
|
| 40 |
.tcard-wrap.winner{box-shadow:0 0 20px rgba(245,158,11,.5);}
|
|
@@ -71,6 +71,11 @@ body{background:#0a1628;color:#fff;font-family:-apple-system,sans-serif;min-heig
|
|
| 71 |
.lplayers{margin:16px 0;font-size:14px;line-height:1.8;}
|
| 72 |
.lstbtn{padding:12px 36px;border:none;border-radius:12px;font-size:15px;font-weight:700;cursor:pointer;color:#fff;background:linear-gradient(135deg,#22c55e,#16a34a);margin:6px;}
|
| 73 |
.lstbtn:disabled{opacity:.3;cursor:not-allowed;}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
</style>
|
| 75 |
</head>
|
| 76 |
<body>
|
|
@@ -84,6 +89,7 @@ body{background:#0a1628;color:#fff;font-family:-apple-system,sans-serif;min-heig
|
|
| 84 |
<div id="actions"></div>
|
| 85 |
<div id="hand-area"><div id="hand-label">Your Cards</div><div id="hand"></div></div>
|
| 86 |
</div>
|
|
|
|
| 87 |
<script>
|
| 88 |
const tg=window.Telegram?.WebApp;if(tg){tg.expand();tg.ready();}
|
| 89 |
if(!tg?.initData||!tg.initDataUnsafe?.user?.id){document.getElementById('blocked').style.display='flex';throw new Error('no auth');}
|
|
@@ -93,7 +99,9 @@ const sp=new URLSearchParams(location.search).get('tgWebAppStartParam')||tg.init
|
|
| 93 |
const GID=sp.replace('wcards_','').replace(/_0_/g,':');
|
| 94 |
async function rpc(m,p,b){try{const o={method:m,headers:{'X-Init-Data':tg.initData,'Content-Type':'application/json'}};if(b)o.body=JSON.stringify(b);const r=await fetch(API+p,o);return JSON.parse(await r.text());}catch{return{ok:false,error:'Network error'};}}
|
| 95 |
|
| 96 |
-
let S=null,selectedCard=null,pollId=null;
|
|
|
|
|
|
|
| 97 |
|
| 98 |
// Give Up: confirm popup then forfeit
|
| 99 |
GiveUp.bind({
|
|
@@ -225,10 +233,20 @@ async function hostStart(){
|
|
| 225 |
else{document.getElementById('lwait').textContent=r.error;document.getElementById('lstbtn').disabled=false;}
|
| 226 |
}
|
| 227 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
function render(){
|
| 229 |
if(!S)return;
|
| 230 |
if(S.status==='lobby')renderLobby();
|
| 231 |
else renderGame();
|
|
|
|
|
|
|
| 232 |
}
|
| 233 |
|
| 234 |
let pollFails=0;
|
|
|
|
| 27 |
.tcard{width:65px;height:95px;border-radius:8px;display:flex;flex-direction:column;align-items:center;justify-content:center;font-weight:700;position:relative;transition:transform .3s,box-shadow .3s;}
|
| 28 |
.tcard.facedown{background:linear-gradient(135deg,#1e40af,#1e3a8a);border:2px solid #3b82f6;box-shadow:0 4px 12px rgba(59,130,246,.2);}
|
| 29 |
.tcard.facedown::after{content:'🂠';font-size:32px;}
|
| 30 |
+
.tcard.faceup{background:#fff;color:#1a1a1a;border:2px solid #ddd;box-shadow:0 4px 15px rgba(0,0,0,.3);}
|
| 31 |
.tcard.faceup .tsuit{font-size:24px;}
|
| 32 |
.tcard.faceup .tlabel{font-size:18px;font-weight:800;}
|
| 33 |
.tcard.faceup.red{color:#dc2626;}
|
| 34 |
.tcard.faceup.winner{border-color:#f59e0b;box-shadow:0 0 20px rgba(245,158,11,.4);}
|
| 35 |
.tcard-name{font-size:9px;color:#888;text-align:center;margin-top:2px;max-width:65px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
| 36 |
/* CardMeister card wrappers (table) */
|
| 37 |
+
.tcard-wrap{display:inline-block;border-radius:8px;box-shadow:0 4px 15px rgba(0,0,0,.3);transition:transform .3s,box-shadow .3s;line-height:0;overflow:hidden;}
|
| 38 |
.tcard-wrap svg,.tcard-wrap playing-card,.tcard-wrap playing-card img{display:block;border-radius:8px;}
|
| 39 |
.tcard-wrap playing-card img{width:100%;height:100%;}
|
| 40 |
.tcard-wrap.winner{box-shadow:0 0 20px rgba(245,158,11,.5);}
|
|
|
|
| 71 |
.lplayers{margin:16px 0;font-size:14px;line-height:1.8;}
|
| 72 |
.lstbtn{padding:12px 36px;border:none;border-radius:12px;font-size:15px;font-weight:700;cursor:pointer;color:#fff;background:linear-gradient(135deg,#22c55e,#16a34a);margin:6px;}
|
| 73 |
.lstbtn:disabled{opacity:.3;cursor:not-allowed;}
|
| 74 |
+
#endModal{position:fixed;inset:0;background:rgba(0,0,0,.72);display:none;align-items:center;justify-content:center;padding:20px;z-index:50;}
|
| 75 |
+
#endModal.show{display:flex;}
|
| 76 |
+
#endCard{background:#111827;border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:22px;max-width:360px;width:100%;text-align:center;}
|
| 77 |
+
#endCard h2{margin-bottom:10px;color:#f59e0b;}
|
| 78 |
+
#endBody{font-size:14px;line-height:1.5;margin-bottom:12px;}
|
| 79 |
</style>
|
| 80 |
</head>
|
| 81 |
<body>
|
|
|
|
| 89 |
<div id="actions"></div>
|
| 90 |
<div id="hand-area"><div id="hand-label">Your Cards</div><div id="hand"></div></div>
|
| 91 |
</div>
|
| 92 |
+
<div id="endModal"><div id="endCard"><h2 id="endTitle">Game Over</h2><div id="endBody"></div><button class="abtn green" id="endOkBtn">OK</button></div></div>
|
| 93 |
<script>
|
| 94 |
const tg=window.Telegram?.WebApp;if(tg){tg.expand();tg.ready();}
|
| 95 |
if(!tg?.initData||!tg.initDataUnsafe?.user?.id){document.getElementById('blocked').style.display='flex';throw new Error('no auth');}
|
|
|
|
| 99 |
const GID=sp.replace('wcards_','').replace(/_0_/g,':');
|
| 100 |
async function rpc(m,p,b){try{const o={method:m,headers:{'X-Init-Data':tg.initData,'Content-Type':'application/json'}};if(b)o.body=JSON.stringify(b);const r=await fetch(API+p,o);return JSON.parse(await r.text());}catch{return{ok:false,error:'Network error'};}}
|
| 101 |
|
| 102 |
+
let S=null,selectedCard=null,pollId=null,endShown=false;
|
| 103 |
+
function closeMiniApp(){try{tg.close();}catch(e){try{window.close();}catch(_){}}}
|
| 104 |
+
document.getElementById('endOkBtn').onclick=closeMiniApp;
|
| 105 |
|
| 106 |
// Give Up: confirm popup then forfeit
|
| 107 |
GiveUp.bind({
|
|
|
|
| 233 |
else{document.getElementById('lwait').textContent=r.error;document.getElementById('lstbtn').disabled=false;}
|
| 234 |
}
|
| 235 |
|
| 236 |
+
function showEndModal(){
|
| 237 |
+
if(!S||S.status!=='ended')return;
|
| 238 |
+
const gu=S.forfeitedBy?`<b>${S.forfeitedBy}</b> gave up.<br><br>`:'';
|
| 239 |
+
document.getElementById('endTitle').textContent='🏆 Winners';
|
| 240 |
+
document.getElementById('endBody').innerHTML=`${gu}<b>${(S.winner||[]).join(' & ')}</b> win!`;
|
| 241 |
+
document.getElementById('endModal').classList.add('show');
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
function render(){
|
| 245 |
if(!S)return;
|
| 246 |
if(S.status==='lobby')renderLobby();
|
| 247 |
else renderGame();
|
| 248 |
+
if(S.status==='ended'&&!endShown){endShown=true;showEndModal();}
|
| 249 |
+
if(S.status!=='ended')endShown=false;
|
| 250 |
}
|
| 251 |
|
| 252 |
let pollFails=0;
|
webapp/carrom.html
CHANGED
|
@@ -253,6 +253,7 @@
|
|
| 253 |
'use strict';
|
| 254 |
const tg = window.Telegram?.WebApp;
|
| 255 |
if (tg) { try { tg.expand(); tg.ready(); } catch {} }
|
|
|
|
| 256 |
|
| 257 |
const startParam = new URLSearchParams(location.search).get('tgWebAppStartParam') || tg?.initDataUnsafe?.start_param || '';
|
| 258 |
const GAME_ID = startParam.replace('carrom_', '').replace(/_0_/g, ':');
|
|
@@ -674,7 +675,7 @@
|
|
| 674 |
$('rulesBtnTop').onclick = () => rulesModal.classList.add('show');
|
| 675 |
$('closeRules').onclick = () => rulesModal.classList.remove('show');
|
| 676 |
$('closeBoard').onclick = () => boardModal.classList.remove('show');
|
| 677 |
-
$('closeMatch').onclick = () =>
|
| 678 |
|
| 679 |
document.querySelectorAll('#modeModal button[data-mode]').forEach(btn => {
|
| 680 |
btn.onclick = async () => {
|
|
|
|
| 253 |
'use strict';
|
| 254 |
const tg = window.Telegram?.WebApp;
|
| 255 |
if (tg) { try { tg.expand(); tg.ready(); } catch {} }
|
| 256 |
+
function closeMiniApp(){ try{ tg?.close(); } catch(e){ try{ window.close(); } catch(_){} } }
|
| 257 |
|
| 258 |
const startParam = new URLSearchParams(location.search).get('tgWebAppStartParam') || tg?.initDataUnsafe?.start_param || '';
|
| 259 |
const GAME_ID = startParam.replace('carrom_', '').replace(/_0_/g, ':');
|
|
|
|
| 675 |
$('rulesBtnTop').onclick = () => rulesModal.classList.add('show');
|
| 676 |
$('closeRules').onclick = () => rulesModal.classList.remove('show');
|
| 677 |
$('closeBoard').onclick = () => boardModal.classList.remove('show');
|
| 678 |
+
$('closeMatch').onclick = () => closeMiniApp();
|
| 679 |
|
| 680 |
document.querySelectorAll('#modeModal button[data-mode]').forEach(btn => {
|
| 681 |
btn.onclick = async () => {
|
webapp/omi.html
CHANGED
|
@@ -45,6 +45,11 @@ body{background:#0d1b2a;color:#fff;font-family:-apple-system,sans-serif;min-heig
|
|
| 45 |
#lobby{display:none;flex:1;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:30px;}
|
| 46 |
.lbtn{padding:12px 30px;border:none;border-radius:10px;font-size:14px;font-weight:700;cursor:pointer;color:#fff;background:linear-gradient(135deg,#22c55e,#16a34a);margin:6px;}
|
| 47 |
.lbtn:disabled{opacity:.3;}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
</style>
|
| 49 |
</head>
|
| 50 |
<body>
|
|
@@ -59,6 +64,7 @@ body{background:#0d1b2a;color:#fff;font-family:-apple-system,sans-serif;min-heig
|
|
| 59 |
<div id="actions"></div>
|
| 60 |
<div id="hand-area"><div id="hand-label">Your Cards</div><div id="hand"></div></div>
|
| 61 |
</div>
|
|
|
|
| 62 |
<script>
|
| 63 |
const tg=window.Telegram?.WebApp;if(tg){tg.expand();tg.ready();}
|
| 64 |
if(!tg?.initData||!tg.initDataUnsafe?.user?.id){document.getElementById('blocked').style.display='flex';throw new Error('no auth');}
|
|
@@ -68,7 +74,9 @@ const sp=new URLSearchParams(location.search).get('tgWebAppStartParam')||tg.init
|
|
| 68 |
const GID=sp.replace('omi_','').replace(/_0_/g,':');
|
| 69 |
async function rpc(m,p,b){try{const o={method:m,headers:{'X-Init-Data':tg.initData,'Content-Type':'application/json'}};if(b)o.body=JSON.stringify(b);const r=await fetch(API+p,o);return JSON.parse(await r.text());}catch{return{ok:false,error:'Network'};}}
|
| 70 |
|
| 71 |
-
let S=null,sel=null,pollId=null;
|
|
|
|
|
|
|
| 72 |
|
| 73 |
// Give Up: confirm popup then forfeit
|
| 74 |
GiveUp.bind({
|
|
@@ -203,11 +211,21 @@ async function hostStart(){
|
|
| 203 |
else{document.getElementById('lwait').textContent=r.error;document.getElementById('lbtn').disabled=false;}
|
| 204 |
}
|
| 205 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
function render(){
|
| 207 |
if(!S)return;
|
| 208 |
if(S.status==='lobby')renderLobby();
|
| 209 |
else if(S.status==='chooseTrump')renderTrumpChoice();
|
| 210 |
else renderGame();
|
|
|
|
|
|
|
| 211 |
}
|
| 212 |
|
| 213 |
let pollFails=0;
|
|
|
|
| 45 |
#lobby{display:none;flex:1;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:30px;}
|
| 46 |
.lbtn{padding:12px 30px;border:none;border-radius:10px;font-size:14px;font-weight:700;cursor:pointer;color:#fff;background:linear-gradient(135deg,#22c55e,#16a34a);margin:6px;}
|
| 47 |
.lbtn:disabled{opacity:.3;}
|
| 48 |
+
#endModal{position:fixed;inset:0;background:rgba(0,0,0,.72);display:none;align-items:center;justify-content:center;padding:20px;z-index:50;}
|
| 49 |
+
#endModal.show{display:flex;}
|
| 50 |
+
#endCard{background:#111827;border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:22px;max-width:360px;width:100%;text-align:center;}
|
| 51 |
+
#endCard h2{margin-bottom:10px;color:#f59e0b;}
|
| 52 |
+
#endBody{font-size:14px;line-height:1.5;margin-bottom:12px;}
|
| 53 |
</style>
|
| 54 |
</head>
|
| 55 |
<body>
|
|
|
|
| 64 |
<div id="actions"></div>
|
| 65 |
<div id="hand-area"><div id="hand-label">Your Cards</div><div id="hand"></div></div>
|
| 66 |
</div>
|
| 67 |
+
<div id="endModal"><div id="endCard"><h2 id="endTitle">Game Over</h2><div id="endBody"></div><button class="abtn g" id="endOkBtn">OK</button></div></div>
|
| 68 |
<script>
|
| 69 |
const tg=window.Telegram?.WebApp;if(tg){tg.expand();tg.ready();}
|
| 70 |
if(!tg?.initData||!tg.initDataUnsafe?.user?.id){document.getElementById('blocked').style.display='flex';throw new Error('no auth');}
|
|
|
|
| 74 |
const GID=sp.replace('omi_','').replace(/_0_/g,':');
|
| 75 |
async function rpc(m,p,b){try{const o={method:m,headers:{'X-Init-Data':tg.initData,'Content-Type':'application/json'}};if(b)o.body=JSON.stringify(b);const r=await fetch(API+p,o);return JSON.parse(await r.text());}catch{return{ok:false,error:'Network'};}}
|
| 76 |
|
| 77 |
+
let S=null,sel=null,pollId=null,endShown=false;
|
| 78 |
+
function closeMiniApp(){try{tg.close();}catch(e){try{window.close();}catch(_){}}}
|
| 79 |
+
document.getElementById('endOkBtn').onclick=closeMiniApp;
|
| 80 |
|
| 81 |
// Give Up: confirm popup then forfeit
|
| 82 |
GiveUp.bind({
|
|
|
|
| 211 |
else{document.getElementById('lwait').textContent=r.error;document.getElementById('lbtn').disabled=false;}
|
| 212 |
}
|
| 213 |
|
| 214 |
+
function showEndModal(){
|
| 215 |
+
if(!S||S.status!=='ended')return;
|
| 216 |
+
const gu=S.forfeitedBy?`<b>${S.forfeitedBy}</b> gave up.<br><br>`:'';
|
| 217 |
+
document.getElementById('endTitle').textContent='🏆 Winners';
|
| 218 |
+
document.getElementById('endBody').innerHTML=`${gu}<b>${(S.winner||[]).join(' & ')}</b> win!`;
|
| 219 |
+
document.getElementById('endModal').classList.add('show');
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
function render(){
|
| 223 |
if(!S)return;
|
| 224 |
if(S.status==='lobby')renderLobby();
|
| 225 |
else if(S.status==='chooseTrump')renderTrumpChoice();
|
| 226 |
else renderGame();
|
| 227 |
+
if(S.status==='ended'&&!endShown){endShown=true;showEndModal();}
|
| 228 |
+
if(S.status!=='ended')endShown=false;
|
| 229 |
}
|
| 230 |
|
| 231 |
let pollFails=0;
|