|
|
from flask import Flask, render_template_string, request, jsonify, session |
|
|
import random |
|
|
import time |
|
|
from datetime import datetime |
|
|
|
|
|
app = Flask(__name__) |
|
|
app.secret_key = 'roulette_secret_key_2024' |
|
|
|
|
|
class RouletteGame: |
|
|
def __init__(self): |
|
|
self.numbers = list(range(0, 37)) |
|
|
self.reds = [1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36] |
|
|
self.blacks = [2,4,6,8,10,11,13,15,17,20,22,24,26,28,29,31,33,35] |
|
|
self.wheel_order = [0, 32, 15, 19, 4, 21, 2, 25, 17, 34, 6, 27, 13, 36, 11, 30, 8, 23, |
|
|
10, 5, 24, 16, 33, 1, 20, 14, 31, 9, 22, 18, 29, 7, 28, 12, 35, 3, 26] |
|
|
|
|
|
def spin(self): |
|
|
return random.choice(self.numbers) |
|
|
|
|
|
def check_win(self, number, bet_type, bet_value): |
|
|
if bet_type == 'number': |
|
|
return number == int(bet_value) |
|
|
elif bet_type == 'color': |
|
|
if bet_value == 'red': |
|
|
return number in self.reds |
|
|
elif bet_value == 'black': |
|
|
return number in self.blacks |
|
|
elif bet_type == 'even_odd': |
|
|
if number == 0: |
|
|
return False |
|
|
if bet_value == 'even': |
|
|
return number % 2 == 0 |
|
|
elif bet_value == 'odd': |
|
|
return number % 2 == 1 |
|
|
elif bet_type == 'dozen': |
|
|
if bet_value == 'first': |
|
|
return 1 <= number <= 12 |
|
|
elif bet_value == 'second': |
|
|
return 13 <= number <= 24 |
|
|
elif bet_value == 'third': |
|
|
return 25 <= number <= 36 |
|
|
elif bet_type == 'column': |
|
|
col = int(bet_value) |
|
|
return number != 0 and (number % 3 == col) |
|
|
elif bet_type == 'half': |
|
|
if bet_value == '1-18': |
|
|
return 1 <= number <= 18 |
|
|
elif bet_value == '19-36': |
|
|
return 19 <= number <= 36 |
|
|
return False |
|
|
|
|
|
def get_payout_multiplier(self, bet_type): |
|
|
multipliers = { |
|
|
'number': 35, |
|
|
'color': 2, |
|
|
'even_odd': 2, |
|
|
'dozen': 3, |
|
|
'column': 3, |
|
|
'half': 2 |
|
|
} |
|
|
return multipliers.get(bet_type, 1) |
|
|
|
|
|
def init_session(): |
|
|
"""Инициализация сессии""" |
|
|
if 'balance' not in session: |
|
|
session['balance'] = 1000 |
|
|
session['history'] = [] |
|
|
session['total_games'] = 0 |
|
|
session['total_wins'] = 0 |
|
|
session.modified = True |
|
|
|
|
|
HTML_TEMPLATE = ''' |
|
|
<!DOCTYPE html> |
|
|
<html lang="ru"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Казино Рулетка</title> |
|
|
<style> |
|
|
* { |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
box-sizing: border-box; |
|
|
} |
|
|
|
|
|
body { |
|
|
font-family: 'Arial', sans-serif; |
|
|
background: linear-gradient(135deg, #0f4c75, #1b262c); |
|
|
min-height: 100vh; |
|
|
color: white; |
|
|
overflow-x: hidden; |
|
|
} |
|
|
|
|
|
.container { |
|
|
max-width: 1200px; |
|
|
margin: 0 auto; |
|
|
padding: 20px; |
|
|
} |
|
|
|
|
|
.header { |
|
|
text-align: center; |
|
|
margin-bottom: 30px; |
|
|
background: rgba(0, 0, 0, 0.7); |
|
|
padding: 20px; |
|
|
border-radius: 15px; |
|
|
border: 2px solid #ffd700; |
|
|
} |
|
|
|
|
|
.game-area { |
|
|
display: grid; |
|
|
grid-template-columns: 350px 1fr; |
|
|
gap: 30px; |
|
|
margin-bottom: 30px; |
|
|
} |
|
|
|
|
|
@media (max-width: 900px) { |
|
|
.game-area { |
|
|
grid-template-columns: 1fr; |
|
|
} |
|
|
} |
|
|
|
|
|
.wheel-container { |
|
|
background: rgba(0, 0, 0, 0.8); |
|
|
padding: 20px; |
|
|
border-radius: 15px; |
|
|
border: 3px solid #ffd700; |
|
|
text-align: center; |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.wheel-wrapper { |
|
|
position: relative; |
|
|
width: 300px; |
|
|
height: 300px; |
|
|
margin: 0 auto; |
|
|
} |
|
|
|
|
|
.wheel { |
|
|
position: relative; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
background: #0a5c36; |
|
|
border-radius: 50%; |
|
|
border: 15px solid #8B4513; |
|
|
overflow: hidden; |
|
|
box-shadow: |
|
|
0 0 30px rgba(255, 215, 0, 0.3), |
|
|
inset 0 0 20px rgba(0, 0, 0, 0.5); |
|
|
transition: transform 4s cubic-bezier(0.2, 0.8, 0.3, 1); |
|
|
} |
|
|
|
|
|
.wheel-center { |
|
|
position: absolute; |
|
|
top: 50%; |
|
|
left: 50%; |
|
|
transform: translate(-50%, -50%); |
|
|
width: 60px; |
|
|
height: 60px; |
|
|
background: #ffd700; |
|
|
border-radius: 50%; |
|
|
border: 3px solid #8B4513; |
|
|
z-index: 10; |
|
|
box-shadow: 0 0 10px rgba(255, 215, 0, 0.8); |
|
|
} |
|
|
|
|
|
.wheel-number { |
|
|
position: absolute; |
|
|
width: 40px; |
|
|
height: 40px; |
|
|
border-radius: 50%; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
font-weight: bold; |
|
|
font-size: 14px; |
|
|
color: white; |
|
|
text-shadow: 1px 1px 2px rgba(0,0,0,0.8); |
|
|
transform-origin: center; |
|
|
left: 130px; |
|
|
top: 20px; |
|
|
border: 1px solid rgba(255,255,255,0.3); |
|
|
box-shadow: inset 0 0 5px rgba(0,0,0,0.5); |
|
|
} |
|
|
|
|
|
.wheel-pointer { |
|
|
position: absolute; |
|
|
top: -10px; |
|
|
left: 50%; |
|
|
transform: translateX(-50%); |
|
|
width: 0; |
|
|
height: 0; |
|
|
border-left: 15px solid transparent; |
|
|
border-right: 15px solid transparent; |
|
|
border-top: 30px solid #ff4444; |
|
|
z-index: 20; |
|
|
filter: drop-shadow(0 0 5px rgba(255,0,0,0.7)); |
|
|
} |
|
|
|
|
|
.ball { |
|
|
position: absolute; |
|
|
width: 20px; |
|
|
height: 20px; |
|
|
background: radial-gradient(circle at 5px 5px, #fff, #ccc, #666); |
|
|
border-radius: 50%; |
|
|
top: 50%; |
|
|
left: 50%; |
|
|
transform: translate(-50%, -50%); |
|
|
z-index: 15; |
|
|
box-shadow: |
|
|
0 0 10px rgba(255, 255, 255, 0.8), |
|
|
inset -2px -2px 5px rgba(0,0,0,0.5); |
|
|
transition: all 0.1s ease; |
|
|
} |
|
|
|
|
|
.wheel-rim { |
|
|
position: absolute; |
|
|
top: -5px; |
|
|
left: -5px; |
|
|
right: -5px; |
|
|
bottom: -5px; |
|
|
border: 8px solid #daa520; |
|
|
border-radius: 50%; |
|
|
pointer-events: none; |
|
|
} |
|
|
|
|
|
.table-container { |
|
|
background: rgba(0, 0, 0, 0.8); |
|
|
padding: 20px; |
|
|
border-radius: 15px; |
|
|
border: 3px solid #ffd700; |
|
|
} |
|
|
|
|
|
.roulette-table { |
|
|
display: grid; |
|
|
grid-template-columns: 60px repeat(12, 1fr); |
|
|
gap: 2px; |
|
|
margin-bottom: 20px; |
|
|
background: #0a5c36; |
|
|
padding: 10px; |
|
|
border-radius: 10px; |
|
|
border: 2px solid #8B4513; |
|
|
} |
|
|
|
|
|
.table-cell { |
|
|
padding: 12px 5px; |
|
|
text-align: center; |
|
|
background: #0a5c36; |
|
|
border: 1px solid #daa520; |
|
|
cursor: pointer; |
|
|
font-weight: bold; |
|
|
transition: all 0.3s ease; |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.table-cell::before { |
|
|
content: ''; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
right: 0; |
|
|
bottom: 0; |
|
|
background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.1) 50%, transparent 70%); |
|
|
opacity: 0; |
|
|
transition: opacity 0.3s; |
|
|
} |
|
|
|
|
|
.table-cell:hover::before { |
|
|
opacity: 1; |
|
|
} |
|
|
|
|
|
.table-cell:hover { |
|
|
transform: scale(1.05); |
|
|
box-shadow: 0 0 15px gold; |
|
|
z-index: 2; |
|
|
} |
|
|
|
|
|
.red { |
|
|
background: linear-gradient(135deg, #d00, #f00, #d00); |
|
|
text-shadow: 1px 1px 2px rgba(0,0,0,0.8); |
|
|
} |
|
|
.black { |
|
|
background: linear-gradient(135deg, #111, #333, #111); |
|
|
text-shadow: 1px 1px 2px rgba(255,255,255,0.3); |
|
|
} |
|
|
.green { |
|
|
background: linear-gradient(135deg, #0a5c36, #0c7a4b, #0a5c36); |
|
|
text-shadow: 1px 1px 2px rgba(0,0,0,0.8); |
|
|
} |
|
|
|
|
|
.bet-options { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); |
|
|
gap: 10px; |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
|
|
|
.bet-option { |
|
|
padding: 15px; |
|
|
text-align: center; |
|
|
background: linear-gradient(135deg, #2d3748, #4a5568); |
|
|
border: 2px solid #ffd700; |
|
|
border-radius: 8px; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s ease; |
|
|
font-weight: bold; |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.bet-option::before { |
|
|
content: ''; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: -100%; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); |
|
|
transition: left 0.5s; |
|
|
} |
|
|
|
|
|
.bet-option:hover::before { |
|
|
left: 100%; |
|
|
} |
|
|
|
|
|
.bet-option:hover { |
|
|
background: linear-gradient(135deg, #4a5568, #2d3748); |
|
|
transform: translateY(-3px); |
|
|
box-shadow: 0 5px 15px rgba(255, 215, 0, 0.3); |
|
|
} |
|
|
|
|
|
.bet-option.active { |
|
|
background: linear-gradient(135deg, #e53e3e, #c53030); |
|
|
box-shadow: 0 0 20px #e53e3e; |
|
|
animation: pulse-glow 1.5s infinite; |
|
|
} |
|
|
|
|
|
@keyframes pulse-glow { |
|
|
0% { box-shadow: 0 0 15px #e53e3e; } |
|
|
50% { box-shadow: 0 0 25px #e53e3e; } |
|
|
100% { box-shadow: 0 0 15px #e53e3e; } |
|
|
} |
|
|
|
|
|
.controls { |
|
|
display: flex; |
|
|
gap: 15px; |
|
|
align-items: center; |
|
|
flex-wrap: wrap; |
|
|
background: rgba(0, 0, 0, 0.7); |
|
|
padding: 20px; |
|
|
border-radius: 10px; |
|
|
margin-bottom: 20px; |
|
|
border: 2px solid #ffd700; |
|
|
} |
|
|
|
|
|
.balance-info { |
|
|
font-size: 24px; |
|
|
font-weight: bold; |
|
|
color: #ffd700; |
|
|
text-shadow: 0 0 10px rgba(255, 215, 0, 0.5); |
|
|
} |
|
|
|
|
|
input[type="number"] { |
|
|
padding: 12px; |
|
|
width: 120px; |
|
|
border: 2px solid #ffd700; |
|
|
border-radius: 5px; |
|
|
background: #2d3748; |
|
|
color: white; |
|
|
font-size: 16px; |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
button { |
|
|
padding: 12px 25px; |
|
|
background: linear-gradient(45deg, #ffd700, #ffed4e); |
|
|
border: none; |
|
|
border-radius: 5px; |
|
|
cursor: pointer; |
|
|
font-weight: bold; |
|
|
font-size: 16px; |
|
|
color: #1b262c; |
|
|
transition: all 0.3s ease; |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
button::before { |
|
|
content: ''; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: -100%; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent); |
|
|
transition: left 0.5s; |
|
|
} |
|
|
|
|
|
button:hover::before { |
|
|
left: 100%; |
|
|
} |
|
|
|
|
|
button:hover { |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 5px 15px rgba(255, 215, 0, 0.6); |
|
|
} |
|
|
|
|
|
button:disabled { |
|
|
background: #718096; |
|
|
cursor: not-allowed; |
|
|
transform: none; |
|
|
box-shadow: none; |
|
|
} |
|
|
|
|
|
.result { |
|
|
padding: 20px; |
|
|
background: rgba(0, 0, 0, 0.8); |
|
|
border-radius: 10px; |
|
|
text-align: center; |
|
|
font-size: 18px; |
|
|
margin-bottom: 20px; |
|
|
border: 2px solid; |
|
|
transition: all 0.5s ease; |
|
|
min-height: 80px; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
} |
|
|
|
|
|
.win { |
|
|
border-color: #48bb78; |
|
|
background: rgba(72, 187, 120, 0.2); |
|
|
animation: pulse-win 1s infinite; |
|
|
} |
|
|
|
|
|
.lose { |
|
|
border-color: #e53e3e; |
|
|
background: rgba(229, 62, 62, 0.2); |
|
|
} |
|
|
|
|
|
@keyframes pulse-win { |
|
|
0% { |
|
|
transform: scale(1); |
|
|
box-shadow: 0 0 20px rgba(72, 187, 120, 0.5); |
|
|
} |
|
|
50% { |
|
|
transform: scale(1.02); |
|
|
box-shadow: 0 0 30px rgba(72, 187, 120, 0.8); |
|
|
} |
|
|
100% { |
|
|
transform: scale(1); |
|
|
box-shadow: 0 0 20px rgba(72, 187, 120, 0.5); |
|
|
} |
|
|
} |
|
|
|
|
|
.history { |
|
|
background: rgba(0, 0, 0, 0.8); |
|
|
padding: 20px; |
|
|
border-radius: 10px; |
|
|
border: 2px solid #ffd700; |
|
|
} |
|
|
|
|
|
.history h3 { |
|
|
margin-bottom: 15px; |
|
|
color: #ffd700; |
|
|
text-align: center; |
|
|
} |
|
|
|
|
|
.history-item { |
|
|
padding: 10px; |
|
|
border-bottom: 1px solid #4a5568; |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
transition: background 0.3s; |
|
|
} |
|
|
|
|
|
.history-item:hover { |
|
|
background: rgba(255, 215, 0, 0.1); |
|
|
} |
|
|
|
|
|
.history-item:last-child { |
|
|
border-bottom: none; |
|
|
} |
|
|
|
|
|
.stats { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
|
|
gap: 15px; |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
|
|
|
.stat-card { |
|
|
background: rgba(0, 0, 0, 0.7); |
|
|
padding: 15px; |
|
|
border-radius: 10px; |
|
|
text-align: center; |
|
|
border: 1px solid #ffd700; |
|
|
transition: transform 0.3s; |
|
|
} |
|
|
|
|
|
.stat-card:hover { |
|
|
transform: translateY(-3px); |
|
|
} |
|
|
|
|
|
.stat-value { |
|
|
font-size: 24px; |
|
|
font-weight: bold; |
|
|
color: #ffd700; |
|
|
} |
|
|
|
|
|
.winning-animation { |
|
|
animation: win-spin 2s ease-out; |
|
|
} |
|
|
|
|
|
@keyframes win-spin { |
|
|
0% { transform: rotate(0deg) scale(1); } |
|
|
25% { transform: rotate(90deg) scale(1.1); } |
|
|
50% { transform: rotate(180deg) scale(1.2); } |
|
|
75% { transform: rotate(270deg) scale(1.1); } |
|
|
100% { transform: rotate(360deg) scale(1); } |
|
|
} |
|
|
|
|
|
.ball-track { |
|
|
position: absolute; |
|
|
top: 15px; |
|
|
left: 15px; |
|
|
right: 15px; |
|
|
bottom: 15px; |
|
|
border: 3px dashed rgba(255, 255, 255, 0.2); |
|
|
border-radius: 50%; |
|
|
pointer-events: none; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="container"> |
|
|
<div class="header"> |
|
|
<h1>🎰 КАЗИНО РУЛЕТКА 🎰</h1> |
|
|
<p>Испытайте удачу в классической европейской рулетке</p> |
|
|
</div> |
|
|
|
|
|
<div class="stats"> |
|
|
<div class="stat-card"> |
|
|
<div>Баланс</div> |
|
|
<div class="stat-value" id="balance">1000</div> |
|
|
</div> |
|
|
<div class="stat-card"> |
|
|
<div>Последний выигрыш</div> |
|
|
<div class="stat-value" id="lastWin">0</div> |
|
|
</div> |
|
|
<div class="stat-card"> |
|
|
<div>Всего игр</div> |
|
|
<div class="stat-value" id="totalGames">0</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="game-area"> |
|
|
<div class="wheel-container"> |
|
|
<h3>Колесо Рулетки</h3> |
|
|
<div class="wheel-wrapper"> |
|
|
<div class="wheel-pointer"></div> |
|
|
<div class="wheel" id="wheel"> |
|
|
<!-- Numbers will be generated by JavaScript --> |
|
|
</div> |
|
|
<div class="wheel-center"></div> |
|
|
<div class="ball" id="ball"></div> |
|
|
<div class="wheel-rim"></div> |
|
|
<div class="ball-track"></div> |
|
|
</div> |
|
|
<div style="margin-top: 20px; font-size: 18px; font-weight: bold;"> |
|
|
Выпало: <span id="lastNumber" style="color: #ffd700;">-</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="table-container"> |
|
|
<h3>Игровой стол</h3> |
|
|
<div class="roulette-table" id="rouletteTable"> |
|
|
<!-- Table will be generated by JavaScript --> |
|
|
</div> |
|
|
|
|
|
<div class="bet-options"> |
|
|
<div class="bet-option" data-type="color" data-value="red">Красное (x2)</div> |
|
|
<div class="bet-option" data-type="color" data-value="black">Черное (x2)</div> |
|
|
<div class="bet-option" data-type="even_odd" data-value="even">Четное (x2)</div> |
|
|
<div class="bet-option" data-type="even_odd" data-value="odd">Нечетное (x2)</div> |
|
|
<div class="bet-option" data-type="dozen" data-value="first">1-12 (x3)</div> |
|
|
<div class="bet-option" data-type="dozen" data-value="second">13-24 (x3)</div> |
|
|
<div class="bet-option" data-type="dozen" data-value="third">25-36 (x3)</div> |
|
|
<div class="bet-option" data-type="half" data-value="1-18">1-18 (x2)</div> |
|
|
<div class="bet-option" data-type="half" data-value="19-36">19-36 (x2)</div> |
|
|
<div class="bet-option" data-type="column" data-value="1">1-я колонка (x3)</div> |
|
|
<div class="bet-option" data-type="column" data-value="2">2-я колонка (x3)</div> |
|
|
<div class="bet-option" data-type="column" data-value="0">3-я колонка (x3)</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="controls"> |
|
|
<div class="balance-info">Баланс: <span id="currentBalance">1000</span> ₽</div> |
|
|
<input type="number" id="betAmount" value="10" min="1" max="1000"> |
|
|
<button id="spinButton">🎰 КРУТИТЬ</button> |
|
|
<button id="clearButton">❌ ОЧИСТИТЬ</button> |
|
|
<div>Ставка: <span id="currentBet" style="color: #ffd700;">Не выбрана</span></div> |
|
|
</div> |
|
|
|
|
|
<div class="result" id="result"> |
|
|
Сделайте ставку и нажмите "Крутить" |
|
|
</div> |
|
|
|
|
|
<div class="history"> |
|
|
<h3>История игр</h3> |
|
|
<div id="historyList"> |
|
|
<!-- History will be populated by JavaScript --> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
const gameState = { |
|
|
balance: 1000, |
|
|
currentBet: null, |
|
|
currentBetType: null, |
|
|
currentBetValue: null, |
|
|
history: [], |
|
|
totalGames: 0, |
|
|
totalWins: 0 |
|
|
}; |
|
|
|
|
|
// Wheel configuration |
|
|
const wheelOrder = [0, 32, 15, 19, 4, 21, 2, 25, 17, 34, 6, 27, 13, 36, 11, 30, 8, 23, |
|
|
10, 5, 24, 16, 33, 1, 20, 14, 31, 9, 22, 18, 29, 7, 28, 12, 35, 3, 26]; |
|
|
const redNumbers = [32,19,21,25,34,27,36,30,23,5,16,1,14,9,18,7,12,35,3,26]; |
|
|
|
|
|
// Initialize game |
|
|
function initGame() { |
|
|
createWheelNumbers(); |
|
|
createRouletteTable(); |
|
|
updateDisplay(); |
|
|
} |
|
|
|
|
|
// Create realistic wheel numbers |
|
|
function createWheelNumbers() { |
|
|
const wheel = document.getElementById('wheel'); |
|
|
const totalNumbers = wheelOrder.length; |
|
|
|
|
|
wheelOrder.forEach((num, index) => { |
|
|
const angle = (index / totalNumbers) * 360; |
|
|
const numberEl = document.createElement('div'); |
|
|
|
|
|
// Determine color |
|
|
let colorClass = 'black'; |
|
|
if (num === 0) { |
|
|
colorClass = 'green'; |
|
|
} else if (redNumbers.includes(num)) { |
|
|
colorClass = 'red'; |
|
|
} |
|
|
|
|
|
numberEl.className = `wheel-number ${colorClass}`; |
|
|
numberEl.textContent = num; |
|
|
numberEl.style.transform = `rotate(${angle}deg) translate(0, -110px) rotate(-${angle}deg)`; |
|
|
|
|
|
// Add subtle shadow for depth |
|
|
numberEl.style.boxShadow = 'inset 2px 2px 5px rgba(0,0,0,0.5), inset -2px -2px 5px rgba(255,255,255,0.2)'; |
|
|
|
|
|
wheel.appendChild(numberEl); |
|
|
}); |
|
|
} |
|
|
|
|
|
// Create roulette table |
|
|
function createRouletteTable() { |
|
|
const table = document.getElementById('rouletteTable'); |
|
|
|
|
|
// Create zero cell |
|
|
const zeroCell = document.createElement('div'); |
|
|
zeroCell.className = 'table-cell green'; |
|
|
zeroCell.textContent = '0'; |
|
|
zeroCell.dataset.type = 'number'; |
|
|
zeroCell.dataset.value = '0'; |
|
|
table.appendChild(zeroCell); |
|
|
|
|
|
// Create number cells (3 columns x 12 rows) |
|
|
for (let row = 0; row < 12; row++) { |
|
|
for (let col = 0; col < 3; col++) { |
|
|
const number = 3 * row + col + 1; |
|
|
const numberEl = document.createElement('div'); |
|
|
const isRed = [1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36].includes(number); |
|
|
|
|
|
numberEl.className = `table-cell ${isRed ? 'red' : 'black'}`; |
|
|
numberEl.textContent = number; |
|
|
numberEl.dataset.type = 'number'; |
|
|
numberEl.dataset.value = number; |
|
|
table.appendChild(numberEl); |
|
|
} |
|
|
} |
|
|
|
|
|
// Add event listeners |
|
|
document.querySelectorAll('.table-cell, .bet-option').forEach(el => { |
|
|
el.addEventListener('click', handleBetSelection); |
|
|
}); |
|
|
} |
|
|
|
|
|
// Handle bet selection |
|
|
function handleBetSelection(e) { |
|
|
const el = e.currentTarget; |
|
|
const betType = el.dataset.type; |
|
|
const betValue = el.dataset.value; |
|
|
const betAmount = parseInt(document.getElementById('betAmount').value); |
|
|
|
|
|
if (betAmount > gameState.balance) { |
|
|
showResult('Недостаточно средств!', 'lose'); |
|
|
return; |
|
|
} |
|
|
|
|
|
// Remove active class from all bets |
|
|
document.querySelectorAll('.bet-option, .table-cell').forEach(item => { |
|
|
item.classList.remove('active'); |
|
|
}); |
|
|
|
|
|
// Add active class to current bet |
|
|
el.classList.add('active'); |
|
|
|
|
|
gameState.currentBetType = betType; |
|
|
gameState.currentBetValue = betValue; |
|
|
gameState.currentBet = { |
|
|
type: betType, |
|
|
value: betValue, |
|
|
amount: betAmount, |
|
|
display: el.textContent |
|
|
}; |
|
|
|
|
|
updateDisplay(); |
|
|
} |
|
|
|
|
|
// Spin the wheel with realistic animation |
|
|
async function spinWheel() { |
|
|
if (!gameState.currentBet) { |
|
|
showResult('Сначала сделайте ставку!', 'lose'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const betAmount = parseInt(document.getElementById('betAmount').value); |
|
|
if (betAmount > gameState.balance) { |
|
|
showResult('Недостаточно средств!', 'lose'); |
|
|
return; |
|
|
} |
|
|
|
|
|
// Disable controls during spin |
|
|
document.getElementById('spinButton').disabled = true; |
|
|
document.getElementById('clearButton').disabled = true; |
|
|
|
|
|
// Deduct bet amount |
|
|
gameState.balance -= betAmount; |
|
|
gameState.totalGames++; |
|
|
|
|
|
// Enhanced wheel animation |
|
|
const wheel = document.getElementById('wheel'); |
|
|
const ball = document.getElementById('ball'); |
|
|
|
|
|
// Reset wheel position |
|
|
wheel.style.transition = 'none'; |
|
|
wheel.style.transform = 'rotate(0deg)'; |
|
|
|
|
|
// Ball animation |
|
|
ball.style.transition = 'none'; |
|
|
ball.style.transform = 'translate(-50%, -50%)'; |
|
|
|
|
|
// Force reflow |
|
|
setTimeout(() => { |
|
|
const spins = 4 + Math.random() * 2; // 4-6 spins |
|
|
const winningAngle = Math.random() * 360; |
|
|
const totalRotation = spins * 360 + winningAngle; |
|
|
|
|
|
// Apply animations |
|
|
wheel.style.transition = `transform ${3 + spins}s cubic-bezier(0.1, 0.7, 0.2, 1.0)`; |
|
|
wheel.style.transform = `rotate(${totalRotation}deg)`; |
|
|
|
|
|
// Ball bouncing animation |
|
|
ball.style.transition = `all 0.2s ease-out`; |
|
|
let bounceCount = 0; |
|
|
const bounceInterval = setInterval(() => { |
|
|
if (bounceCount < 15) { |
|
|
const offset = Math.sin(bounceCount * 0.5) * 5; |
|
|
ball.style.transform = `translate(-50%, calc(-50% + ${offset}px))`; |
|
|
bounceCount++; |
|
|
} else { |
|
|
clearInterval(bounceInterval); |
|
|
} |
|
|
}, 100); |
|
|
|
|
|
}, 10); |
|
|
|
|
|
// Send request to server |
|
|
try { |
|
|
const response = await fetch('/spin', { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Content-Type': 'application/json', |
|
|
}, |
|
|
body: JSON.stringify({ |
|
|
betType: gameState.currentBetType, |
|
|
betValue: gameState.currentBetValue, |
|
|
betAmount: betAmount |
|
|
}) |
|
|
}); |
|
|
|
|
|
const data = await response.json(); |
|
|
|
|
|
// Show result after animation |
|
|
setTimeout(() => { |
|
|
handleSpinResult(data, betAmount); |
|
|
}, 4000 + Math.random() * 1000); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Error:', error); |
|
|
showResult('Ошибка соединения!', 'lose'); |
|
|
document.getElementById('spinButton').disabled = false; |
|
|
document.getElementById('clearButton').disabled = false; |
|
|
} |
|
|
} |
|
|
|
|
|
// Handle spin result |
|
|
function handleSpinResult(data, betAmount) { |
|
|
const won = data.win; |
|
|
const payout = data.payout; |
|
|
const winningNumber = data.winningNumber; |
|
|
|
|
|
if (won) { |
|
|
gameState.balance += payout; |
|
|
gameState.totalWins++; |
|
|
showResult(`🎉 ПОБЕДА! Выпало ${winningNumber}. Выигрыш: ${payout} ₽`, 'win'); |
|
|
|
|
|
// Winning animation |
|
|
const wheel = document.getElementById('wheel'); |
|
|
wheel.classList.add('winning-animation'); |
|
|
setTimeout(() => { |
|
|
wheel.classList.remove('winning-animation'); |
|
|
}, 2000); |
|
|
} else { |
|
|
showResult(`❌ Проигрыш. Выпало: ${winningNumber}`, 'lose'); |
|
|
} |
|
|
|
|
|
// Update last number with color |
|
|
const lastNumberEl = document.getElementById('lastNumber'); |
|
|
lastNumberEl.textContent = winningNumber; |
|
|
lastNumberEl.style.color = data.color === 'red' ? '#ff4444' : data.color === 'black' ? '#000' : '#0a5c36'; |
|
|
|
|
|
// Add to history |
|
|
addToHistory({ |
|
|
number: winningNumber, |
|
|
bet: gameState.currentBet.display, |
|
|
amount: betAmount, |
|
|
payout: payout, |
|
|
won: won, |
|
|
timestamp: new Date().toLocaleTimeString() |
|
|
}); |
|
|
|
|
|
// Update display and re-enable controls |
|
|
updateDisplay(); |
|
|
document.getElementById('spinButton').disabled = false; |
|
|
document.getElementById('clearButton').disabled = false; |
|
|
|
|
|
// Clear bet after spin |
|
|
gameState.currentBet = null; |
|
|
gameState.currentBetType = null; |
|
|
gameState.currentBetValue = null; |
|
|
document.querySelectorAll('.bet-option, .table-cell').forEach(item => { |
|
|
item.classList.remove('active'); |
|
|
}); |
|
|
} |
|
|
|
|
|
// Add game to history |
|
|
function addToHistory(game) { |
|
|
gameState.history.unshift(game); |
|
|
if (gameState.history.length > 10) { |
|
|
gameState.history.pop(); |
|
|
} |
|
|
updateHistoryDisplay(); |
|
|
} |
|
|
|
|
|
// Update history display |
|
|
function updateHistoryDisplay() { |
|
|
const historyList = document.getElementById('historyList'); |
|
|
historyList.innerHTML = ''; |
|
|
|
|
|
gameState.history.forEach(game => { |
|
|
const item = document.createElement('div'); |
|
|
item.className = `history-item ${game.won ? 'win' : 'lose'}`; |
|
|
item.innerHTML = ` |
|
|
<span style="color: ${game.won ? '#48bb78' : '#e53e3e'}">${game.timestamp}</span> |
|
|
<span>${game.bet}</span> |
|
|
<span>${game.amount}₽</span> |
|
|
<span style="color: ${getNumberColor(game.number)}">${game.number}</span> |
|
|
<span style="color: ${game.won ? '#48bb78' : '#e53e3e'}">${game.won ? `+${game.payout}₽` : '−'}</span> |
|
|
`; |
|
|
historyList.appendChild(item); |
|
|
}); |
|
|
} |
|
|
|
|
|
// Helper function to get number color |
|
|
function getNumberColor(number) { |
|
|
if (number === 0) return '#0a5c36'; |
|
|
return redNumbers.includes(number) ? '#ff4444' : '#000'; |
|
|
} |
|
|
|
|
|
// Show result message |
|
|
function showResult(message, type) { |
|
|
const result = document.getElementById('result'); |
|
|
result.textContent = message; |
|
|
result.className = 'result'; |
|
|
if (type) { |
|
|
result.classList.add(type); |
|
|
} |
|
|
} |
|
|
|
|
|
// Update display |
|
|
function updateDisplay() { |
|
|
document.getElementById('currentBalance').textContent = gameState.balance; |
|
|
document.getElementById('balance').textContent = gameState.balance; |
|
|
document.getElementById('lastWin').textContent = gameState.history[0]?.payout || 0; |
|
|
document.getElementById('totalGames').textContent = gameState.totalGames; |
|
|
|
|
|
if (gameState.currentBet) { |
|
|
document.getElementById('currentBet').textContent = |
|
|
`${gameState.currentBet.display} (${gameState.currentBet.amount} ₽)`; |
|
|
} else { |
|
|
document.getElementById('currentBet').textContent = 'Не выбрана'; |
|
|
} |
|
|
} |
|
|
|
|
|
// Clear current bet |
|
|
function clearBet() { |
|
|
gameState.currentBet = null; |
|
|
gameState.currentBetType = null; |
|
|
gameState.currentBetValue = null; |
|
|
document.querySelectorAll('.bet-option, .table-cell').forEach(item => { |
|
|
item.classList.remove('active'); |
|
|
}); |
|
|
updateDisplay(); |
|
|
showResult('Ставка очищена', ''); |
|
|
} |
|
|
|
|
|
// Event listeners |
|
|
document.getElementById('spinButton').addEventListener('click', spinWheel); |
|
|
document.getElementById('clearButton').addEventListener('click', clearBet); |
|
|
|
|
|
// Initialize game when page loads |
|
|
window.addEventListener('load', initGame); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
''' |
|
|
|
|
|
game = RouletteGame() |
|
|
|
|
|
@app.route('/') |
|
|
def index(): |
|
|
|
|
|
init_session() |
|
|
return render_template_string(HTML_TEMPLATE) |
|
|
|
|
|
@app.route('/spin', methods=['POST']) |
|
|
def spin(): |
|
|
|
|
|
init_session() |
|
|
|
|
|
data = request.json |
|
|
bet_type = data['betType'] |
|
|
bet_value = data['betValue'] |
|
|
bet_amount = float(data['betAmount']) |
|
|
|
|
|
|
|
|
if bet_amount > session['balance']: |
|
|
return jsonify({'error': 'Недостаточно средств'}), 400 |
|
|
|
|
|
|
|
|
session['balance'] -= bet_amount |
|
|
session['total_games'] = session.get('total_games', 0) + 1 |
|
|
|
|
|
|
|
|
winning_number = game.spin() |
|
|
win = game.check_win(winning_number, bet_type, bet_value) |
|
|
|
|
|
|
|
|
payout = 0 |
|
|
if win: |
|
|
multiplier = game.get_payout_multiplier(bet_type) |
|
|
payout = bet_amount * multiplier |
|
|
session['balance'] += payout |
|
|
session['total_wins'] = session.get('total_wins', 0) + 1 |
|
|
|
|
|
|
|
|
history_item = { |
|
|
'number': winning_number, |
|
|
'bet_type': bet_type, |
|
|
'bet_value': bet_value, |
|
|
'amount': bet_amount, |
|
|
'payout': payout, |
|
|
'won': win, |
|
|
'timestamp': datetime.now().strftime('%H:%M:%S') |
|
|
} |
|
|
|
|
|
session['history'] = session.get('history', []) |
|
|
session['history'].insert(0, history_item) |
|
|
if len(session['history']) > 10: |
|
|
session['history'] = session['history'][:10] |
|
|
|
|
|
session.modified = True |
|
|
|
|
|
return jsonify({ |
|
|
'winningNumber': winning_number, |
|
|
'win': win, |
|
|
'payout': payout, |
|
|
'color': 'red' if winning_number in game.reds else 'black', |
|
|
'newBalance': session['balance'] |
|
|
}) |
|
|
|
|
|
@app.route('/reset', methods=['POST']) |
|
|
def reset(): |
|
|
session['balance'] = 1000 |
|
|
session['history'] = [] |
|
|
session['total_games'] = 0 |
|
|
session['total_wins'] = 0 |
|
|
session.modified = True |
|
|
return jsonify({'message': 'Игра сброшена', 'balance': session['balance']}) |
|
|
|
|
|
@app.before_request |
|
|
def before_request(): |
|
|
"""Ensure session is initialized before each request""" |
|
|
init_session() |
|
|
|
|
|
if __name__ == '__main__': |
|
|
app.run(debug=True, host='0.0.0.0', port=7860) |