Oth1 commited on
Commit
e76647f
·
verified ·
1 Parent(s): 1c26078

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +42 -32
main.py CHANGED
@@ -7,6 +7,7 @@ import time
7
  import json
8
  import aiosqlite
9
  import uvicorn
 
10
  from contextlib import asynccontextmanager
11
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect
12
  from fastapi.middleware.cors import CORSMiddleware
@@ -30,7 +31,7 @@ WEBAPP_URL = f"https://{SPACE_HOST}"
30
 
31
  PORT = int(os.getenv("PORT", 7860))
32
  DB_PATH = "gift_bot.db"
33
- HOUSE_EDGE = 0.03
34
  BUYBACK_RATE = 0.95
35
 
36
  # ==========================================
@@ -124,7 +125,7 @@ async def refresh_prices():
124
  return cached_prices
125
 
126
  # ==========================================
127
- # GAME ENGINE
128
  # ==========================================
129
  class CrashEngine:
130
  def __init__(self):
@@ -141,27 +142,29 @@ class CrashEngine:
141
  self.server_seed = secrets.token_hex(32)
142
  self.server_seed_hash = hashlib.sha256(self.server_seed.encode()).hexdigest()
143
 
144
- def calculate_crash(self, round_id: int) -> float:
145
- h = hashlib.sha256(f"{self.server_seed}:{round_id}".encode()).hexdigest()
146
- h_int = int(h[:13], 16)
147
- E = 2**52
148
- if secrets.randbelow(100) < int(HOUSE_EDGE * 100):
149
- return 1.00
150
- crash = (100 * E - h_int) / (E - h_int) / 100
151
- return max(1.00, round(crash, 2))
 
 
 
152
 
153
  def get_current_multiplier(self) -> float:
154
  if not self.is_running: return 1.0
155
  elapsed = time.time() - self.start_time
156
 
157
- # Slow down the curve so it takes time to reach 2x, 3x, etc.
158
- # Multiplier grows exponentially based on time elapsed.
159
- mult = 1.00 * (1.035 ** (elapsed * 10))
160
  return round(mult, 2)
161
 
162
  async def start_countdown(self):
163
  self.generate_seed()
164
- self.crash_point = self.calculate_crash(self.current_round_id)
165
  self.is_countdown = True
166
  self.is_running = False
167
  self.bets = {}
@@ -193,7 +196,6 @@ class CrashEngine:
193
  asyncio.create_task(self.start_countdown())
194
 
195
  async def place_bet(self, user_id, amount):
196
- # You can only place a bet during countdown or the first second
197
  if not self.is_countdown and not self.is_running:
198
  return False, "Game is not accepting bets."
199
  if user_id in self.bets: return False, "Already bet this round."
@@ -223,7 +225,7 @@ class CrashEngine:
223
  engine = CrashEngine()
224
 
225
  # ==========================================
226
- # TELEGRAM BOT
227
  # ==========================================
228
  bot = Bot(token=BOT_TOKEN) if BOT_TOKEN else None
229
  dp = Dispatcher()
@@ -254,19 +256,21 @@ async def cmd_start(msg: types.Message):
254
  await msg.answer(welcome_text, reply_markup=reply_kb)
255
  await msg.answer("اضغط للدخول:", reply_markup=inline_kb)
256
 
257
- @dp.message(lambda message: message.text == "🎮 ادخل اللعبة")
258
- async def open_game_msg(msg: types.Message):
259
- await msg.answer("جاري فتح اللعبة...")
260
-
261
- async def run_bot():
262
  if not bot:
263
- print("[ERROR] Bot token missing. Bot cannot start.")
264
  return
265
- try:
266
- print("[BOT] Starting Telegram bot polling...")
267
- await dp.start_polling(bot)
268
- except Exception as e:
269
- print(f"[BOT ERROR] {e}")
 
 
 
 
 
 
270
 
271
  # ==========================================
272
  # FASTAPI & WEBSOCKETS
@@ -276,7 +280,9 @@ async def lifespan(app: FastAPI):
276
  await init_db()
277
  await refresh_prices()
278
  asyncio.create_task(engine.start_countdown())
279
- asyncio.create_task(run_bot())
 
 
280
  async def price_loop():
281
  while True:
282
  await asyncio.sleep(300)
@@ -302,12 +308,12 @@ async def serve_frontend():
302
  body { background: #0f0f0f; color: #fff; font-family: -apple-system, sans-serif; padding: 16px; min-height: 100vh; }
303
  .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
304
  .balance { background: #1e1e1e; padding: 8px 16px; border-radius: 12px; font-weight: 600; }
305
- .display { text-align: center; padding: 60px 0; background: #1a1a1a; border-radius: 24px; margin-bottom: 16px; position: relative; overflow: hidden; }
306
  .display h1 { font-size: 4rem; font-weight: 800; letter-spacing: -2px; display: flex; align-items: center; justify-content: center; gap: 10px; }
307
  .display.crashed h1 { color: #ff3b30; }
308
  .display.crashed { background: #2a0a0a; }
309
  .display.countdown { background: #1a1a2a; }
310
- .display.countdown h1 { color: #007aff; }
311
  .controls { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; }
312
  .row { display: flex; gap: 10px; }
313
  input { flex: 1; padding: 16px; border-radius: 12px; border: none; background: #1e1e1e; color: #fff; font-size: 1.2rem; text-align: center; }
@@ -374,7 +380,6 @@ async def serve_frontend():
374
  renderGifts(data.inventory);
375
  }
376
  else if (data.type === "countdown") {
377
- // Show countdown state
378
  displayEl.classList.remove("crashed");
379
  displayEl.classList.add("countdown");
380
  multEl.innerText = `Starting in ${data.seconds}...`;
@@ -387,12 +392,15 @@ async def serve_frontend():
387
  cashBtn.disabled = true;
388
  }
389
  else if (data.type === "round_start") {
390
- // Hide countdown, start flying
391
  displayEl.classList.remove("countdown");
392
  displayEl.classList.remove("crashed");
393
  multEl.innerText = "🚀 1.00x";
394
  isCountdown = false;
395
  isRunning = true;
 
 
 
 
396
  }
397
  else if (data.type === "tick") {
398
  if(isRunning) {
@@ -411,7 +419,9 @@ async def serve_frontend():
411
  hasBet = true;
412
  betBtn.innerText = "Bet Placed";
413
  betBtn.disabled = true;
 
414
  if(isRunning) cashBtn.disabled = false;
 
415
  const amt = parseFloat(document.getElementById('betAmount').value);
416
  balanceEl.innerText = (parseFloat(balanceEl.innerText) - amt).toFixed(2);
417
  } else alert(data.msg);
 
7
  import json
8
  import aiosqlite
9
  import uvicorn
10
+ import threading
11
  from contextlib import asynccontextmanager
12
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect
13
  from fastapi.middleware.cors import CORSMiddleware
 
31
 
32
  PORT = int(os.getenv("PORT", 7860))
33
  DB_PATH = "gift_bot.db"
34
+ HOUSE_EDGE = 0.04 # 4% house edge
35
  BUYBACK_RATE = 0.95
36
 
37
  # ==========================================
 
125
  return cached_prices
126
 
127
  # ==========================================
128
+ # GAME ENGINE (FIXED ALGORITHM)
129
  # ==========================================
130
  class CrashEngine:
131
  def __init__(self):
 
142
  self.server_seed = secrets.token_hex(32)
143
  self.server_seed_hash = hashlib.sha256(self.server_seed.encode()).hexdigest()
144
 
145
+ def calculate_crash(self) -> float:
146
+ # Provably fair formula (generates up to 1000x+)
147
+ r = secrets.randbelow(2**32) / (2**32)
148
+ # House edge cut
149
+ crash = (1 - HOUSE_EDGE) / (1 - r)
150
+
151
+ if crash < 1.0:
152
+ crash = 1.0
153
+
154
+ # Cap the max multiplier for sanity
155
+ return min(round(crash, 2), 100.0)
156
 
157
  def get_current_multiplier(self) -> float:
158
  if not self.is_running: return 1.0
159
  elapsed = time.time() - self.start_time
160
 
161
+ # Slower growth curve: 2x takes ~5 seconds, 10x takes ~13 seconds.
162
+ mult = 1.00 * (1.05 ** (elapsed * 2.5))
 
163
  return round(mult, 2)
164
 
165
  async def start_countdown(self):
166
  self.generate_seed()
167
+ self.crash_point = self.calculate_crash()
168
  self.is_countdown = True
169
  self.is_running = False
170
  self.bets = {}
 
196
  asyncio.create_task(self.start_countdown())
197
 
198
  async def place_bet(self, user_id, amount):
 
199
  if not self.is_countdown and not self.is_running:
200
  return False, "Game is not accepting bets."
201
  if user_id in self.bets: return False, "Already bet this round."
 
225
  engine = CrashEngine()
226
 
227
  # ==========================================
228
+ # TELEGRAM BOT (ISOLATED THREAD FIX)
229
  # ==========================================
230
  bot = Bot(token=BOT_TOKEN) if BOT_TOKEN else None
231
  dp = Dispatcher()
 
256
  await msg.answer(welcome_text, reply_markup=reply_kb)
257
  await msg.answer("اضغط للدخول:", reply_markup=inline_kb)
258
 
259
+ def start_bot_in_thread():
 
 
 
 
260
  if not bot:
261
+ print("[ERROR] Bot token missing.")
262
  return
263
+ print("[BOT] Starting Telegram bot polling in background...")
264
+
265
+ async def run():
266
+ try:
267
+ await dp.start_polling(bot)
268
+ except Exception as e:
269
+ print(f"[BOT ERROR] {e}")
270
+
271
+ loop = asyncio.new_event_loop()
272
+ asyncio.set_event_loop(loop)
273
+ loop.run_until_complete(run())
274
 
275
  # ==========================================
276
  # FASTAPI & WEBSOCKETS
 
280
  await init_db()
281
  await refresh_prices()
282
  asyncio.create_task(engine.start_countdown())
283
+ # Start bot in a separate thread to prevent blocking FastAPI startup
284
+ threading.Thread(target=start_bot_in_thread, daemon=True).start()
285
+
286
  async def price_loop():
287
  while True:
288
  await asyncio.sleep(300)
 
308
  body { background: #0f0f0f; color: #fff; font-family: -apple-system, sans-serif; padding: 16px; min-height: 100vh; }
309
  .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
310
  .balance { background: #1e1e1e; padding: 8px 16px; border-radius: 12px; font-weight: 600; }
311
+ .display { text-align: center; padding: 60px 0; background: #1a1a1a; border-radius: 24px; margin-bottom: 16px; position: relative; overflow: hidden; transition: background 0.3s; }
312
  .display h1 { font-size: 4rem; font-weight: 800; letter-spacing: -2px; display: flex; align-items: center; justify-content: center; gap: 10px; }
313
  .display.crashed h1 { color: #ff3b30; }
314
  .display.crashed { background: #2a0a0a; }
315
  .display.countdown { background: #1a1a2a; }
316
+ .display.countdown h1 { color: #007aff; font-size: 2.5rem; }
317
  .controls { display: flex; flex-direction: column; gap: 10px; margin-bottom: 20px; }
318
  .row { display: flex; gap: 10px; }
319
  input { flex: 1; padding: 16px; border-radius: 12px; border: none; background: #1e1e1e; color: #fff; font-size: 1.2rem; text-align: center; }
 
380
  renderGifts(data.inventory);
381
  }
382
  else if (data.type === "countdown") {
 
383
  displayEl.classList.remove("crashed");
384
  displayEl.classList.add("countdown");
385
  multEl.innerText = `Starting in ${data.seconds}...`;
 
392
  cashBtn.disabled = true;
393
  }
394
  else if (data.type === "round_start") {
 
395
  displayEl.classList.remove("countdown");
396
  displayEl.classList.remove("crashed");
397
  multEl.innerText = "🚀 1.00x";
398
  isCountdown = false;
399
  isRunning = true;
400
+ // CRITICAL FIX: If player already bet during countdown, unlock cashout
401
+ if (hasBet) {
402
+ cashBtn.disabled = false;
403
+ }
404
  }
405
  else if (data.type === "tick") {
406
  if(isRunning) {
 
419
  hasBet = true;
420
  betBtn.innerText = "Bet Placed";
421
  betBtn.disabled = true;
422
+ // CRITICAL FIX: If the round is already running, let them cashout immediately
423
  if(isRunning) cashBtn.disabled = false;
424
+
425
  const amt = parseFloat(document.getElementById('betAmount').value);
426
  balanceEl.innerText = (parseFloat(balanceEl.innerText) - amt).toFixed(2);
427
  } else alert(data.msg);