Created
July 22, 2025 14:08
-
-
Save mhz-tamb/ba2419e20cd276225375ca420bcca2db to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="ru"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Лотерея: классика и быстрая</title> | |
<style> | |
:root { | |
--bg-light: #eef2f7; | |
--bg-dark: #222; | |
--card-light: #fff; | |
--card-dark: #333; | |
--text-light: #000; | |
--text-dark: #eee; | |
--navbar-light: #007bff; | |
--navbar-dark: #444; | |
--btn-primary-light: #007bff; | |
--btn-primary-dark: #555; | |
--btn-secondary-light: #6c757d; | |
--btn-secondary-dark: #666; | |
} | |
/* Основные стили */ | |
body { | |
margin: 0; | |
padding-top: 80px; | |
font-family: Arial, sans-serif; | |
background-color: var(--bg-light); | |
color: var(--text-light); | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
min-height: 100vh; | |
font-size: 1rem; | |
} | |
body.dark-theme { | |
background-color: var(--bg-dark); | |
color: var(--text-dark); | |
} | |
/* Навбар */ | |
#navbar { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 60px; | |
display: flex; | |
justify-content: space-around; | |
align-items: center; | |
background-color: var(--navbar-light); | |
color: var(--text-light); | |
box-shadow: 0 2px 5px rgba(0,0,0,0.1); | |
z-index: 1000; | |
font-size: 1.1rem; | |
} | |
body.dark-theme #navbar { | |
background-color: var(--navbar-dark); | |
color: var(--text-dark); | |
} | |
#navbar .stat { | |
flex: 1; | |
text-align: center; | |
} | |
#themeToggle { | |
background: none; | |
border: none; | |
font-size: 1.4rem; | |
cursor: pointer; | |
color: inherit; | |
} | |
/* Меню выбора */ | |
#menu { | |
margin-bottom: 20px; | |
font-size: 1.1rem; | |
} | |
#lotterySelect { | |
padding: 8px; | |
font-size: 1rem; | |
border-radius: 4px; | |
border: 1px solid #ccc; | |
margin-left: 8px; | |
} | |
/* Контейнер игры */ | |
.lottery-container { | |
background-color: var(--card-light); | |
border-radius: 12px; | |
box-shadow: 0 6px 18px rgba(0,0,0,0.1); | |
width: 500px; | |
max-width: 90%; | |
padding: 24px; | |
text-align: center; | |
margin-bottom: 40px; | |
} | |
body.dark-theme .lottery-container { | |
background-color: var(--card-dark); | |
} | |
.lottery-container h2 { | |
margin-bottom: 16px; | |
font-size: 1.3rem; | |
} | |
/* Поля ввода: общий стиль */ | |
.numbers { | |
display: grid; | |
grid-template-columns: repeat(3, 1fr); | |
gap: 12px; | |
margin-bottom: 16px; | |
justify-items: stretch; | |
} | |
.numbers input { | |
width: 85%; | |
padding: 14px; | |
font-size: 1.2rem; | |
border: 1px solid #ccc; | |
border-radius: 6px; | |
text-align: center; | |
} | |
/* Метка и bias */ | |
.lottery-container label { | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
margin: 12px 0; | |
font-size: 1.1rem; | |
} | |
.lottery-container label input { | |
width: 80px; | |
margin-left: 8px; | |
padding: 6px; | |
font-size: 1rem; | |
} | |
/* Кнопки */ | |
.btn { | |
width: 100%; | |
padding: 14px; | |
border: none; | |
border-radius: 6px; | |
font-weight: bold; | |
color: #fff; | |
cursor: pointer; | |
transition: filter 0.2s; | |
margin: 12px 0; | |
font-size: 1.1rem; | |
} | |
.btn-primary { | |
background-color: var(--btn-primary-light); | |
} | |
.btn-primary:hover { | |
filter: brightness(90%); | |
} | |
body.dark-theme .btn-primary { | |
background-color: var(--btn-primary-dark); | |
} | |
body.dark-theme .btn-primary:hover { | |
filter: brightness(110%); | |
} | |
.btn-secondary { | |
background-color: var(--btn-secondary-light); | |
} | |
.btn-secondary:hover { | |
filter: brightness(90%); | |
} | |
body.dark-theme .btn-secondary { | |
background-color: var(--btn-secondary-dark); | |
} | |
body.dark-theme .btn-secondary:hover { | |
filter: brightness(110%); | |
} | |
.result { | |
margin-top: 20px; | |
font-size: 1.2rem; | |
line-height: 1.4; | |
} | |
</style> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js"></script> | |
</head> | |
<body> | |
<header id="navbar"> | |
<div class="stat">Игр сыграно: <span id="gamesPlayed">0</span></div> | |
<div class="stat">Выигрышей: <span id="wins">0</span></div> | |
<div class="stat">Баланс: <span id="balance">$100</span></div> | |
<button id="themeToggle" aria-label="Переключить тему">🌙</button> | |
</header> | |
<div id="menu"> | |
Выберите лотерею: | |
<select id="lotterySelect"> | |
<option value="classic">Классическая (6/49)</option> | |
<option value="quick">Быстрая (3/20)</option> | |
</select> | |
</div> | |
<!-- Классическая лотерея --> | |
<div id="classicLottery" class="lottery-container"> | |
<h2>Классическая лотерея</h2> | |
<div id="classicNumbers" class="numbers"> | |
<input type="number" min="1" max="49" id="c1" placeholder="1"> | |
<input type="number" min="1" max="49" id="c2" placeholder="2"> | |
<input type="number" min="1" max="49" id="c3" placeholder="3"> | |
<input type="number" min="1" max="49" id="c4" placeholder="4"> | |
<input type="number" min="1" max="49" id="c5" placeholder="5"> | |
<input type="number" min="1" max="49" id="c6" placeholder="6"> | |
</div> | |
<button id="autoFillClassic" class="btn btn-secondary">Автозаполнить</button> | |
<label>Шанс выпадения (%): <input type="number" id="biasClassic" min="0" max="100"></label> | |
<button id="drawClassic" class="btn btn-primary">Сыграть</button> | |
<div id="resultClassic" class="result"></div> | |
</div> | |
<!-- Быстрая лотерея --> | |
<div id="quickLottery" class="lottery-container" style="display:none;"> | |
<h2>Быстрая лотерея</h2> | |
<div id="quickNumbers" class="numbers"> | |
<input type="number" min="1" max="20" id="q1" placeholder="1"> | |
<input type="number" min="1" max="20" id="q2" placeholder="2"> | |
<input type="number" min="1" max="20" id="q3" placeholder="3"> | |
</div> | |
<button id="autoFillQuick" class="btn btn-secondary">Автозаполнить</button> | |
<label>Шанс выпадения (%): <input type="number" id="biasQuick" min="0" max="100"></label> | |
<button id="drawQuick" class="btn btn-primary">Сыграть</button> | |
<div id="resultQuick" class="result"></div> | |
</div> | |
<script> | |
'use strict'; | |
/** | |
* Lottery Application | |
* Manages classic (6/49) and quick (3/20) lotteries, | |
* persists state to localStorage, and supports theme toggling. | |
*/ | |
document.addEventListener('DOMContentLoaded', () => { | |
// Состояние приложения | |
const state = { | |
gamesPlayed: parseInt(localStorage.getItem('gamesPlayed'), 10) || 0, | |
wins: parseInt(localStorage.getItem('wins'), 10) || 0, | |
balance: parseInt(localStorage.getItem('balance'), 10) || 100, | |
lastLottery: localStorage.getItem('lastLottery') || 'classic', | |
biasClassic: localStorage.getItem('biasClassic') || '70', | |
biasQuick: localStorage.getItem('biasQuick') || '70', | |
theme: localStorage.getItem('theme') || 'light' | |
}; | |
// DOM элементы | |
const dom = { | |
stats: { | |
gamesPlayed: document.getElementById('gamesPlayed'), | |
wins: document.getElementById('wins'), | |
balance: document.getElementById('balance') | |
}, | |
lotterySelect: document.getElementById('lotterySelect'), | |
classicContainer: document.getElementById('classicLottery'), | |
quickContainer: document.getElementById('quickLottery'), | |
biasClassic: document.getElementById('biasClassic'), | |
biasQuick: document.getElementById('biasQuick'), | |
themeToggle: document.getElementById('themeToggle'), | |
autoFillClassic: document.getElementById('autoFillClassic'), | |
drawClassic: document.getElementById('drawClassic'), | |
autoFillQuick: document.getElementById('autoFillQuick'), | |
drawQuick: document.getElementById('drawQuick'), | |
resultClassic: document.getElementById('resultClassic'), | |
resultQuick: document.getElementById('resultQuick') | |
}; | |
/** Обновление статистики и сохранение состояния */ | |
const updateStats = () => { | |
dom.stats.gamesPlayed.textContent = state.gamesPlayed; | |
dom.stats.wins.textContent = state.wins; | |
dom.stats.balance.textContent = `$${state.balance}`; | |
localStorage.setItem('gamesPlayed', state.gamesPlayed); | |
localStorage.setItem('wins', state.wins); | |
localStorage.setItem('balance', state.balance); | |
}; | |
/** Применение темы и сохранение */ | |
const applyTheme = (theme) => { | |
document.body.classList.toggle('dark-theme', theme === 'dark'); | |
dom.themeToggle.textContent = theme === 'dark' ? '☀️' : '🌙'; | |
localStorage.setItem('theme', theme); | |
}; | |
/** Отображение выбранной лотереи и сохранение */ | |
const showLottery = (type) => { | |
dom.classicContainer.style.display = type === 'classic' ? 'block' : 'none'; | |
dom.quickContainer.style.display = type === 'quick' ? 'block' : 'none'; | |
localStorage.setItem('lastLottery', type); | |
}; | |
/** Генерация уникальных случайных чисел */ | |
const getRandomNumbers = (count, min, max) => { | |
const set = new Set(); | |
while (set.size < count) { | |
set.add(Math.floor(Math.random() * (max - min + 1)) + min); | |
} | |
return Array.from(set); | |
}; | |
/** | |
* Основная функция розыгрыша | |
* @param {object} config параметры розыгрыша | |
*/ | |
const playLottery = ({ count, maxNum, inputPrefix, biasDom, resultDom, winReward, lossPenalty, confettiOpts }) => { | |
state.gamesPlayed++; | |
const userNums = []; | |
// Считывание и проверка ввода | |
for (let i = 1; i <= count; i++) { | |
const val = parseInt(document.getElementById(`${inputPrefix}${i}`).value, 10); | |
if (isNaN(val) || val < 1 || val > maxNum) { | |
alert(`Введите ${count} уникальных чисел от 1 до ${maxNum}!`); | |
state.gamesPlayed--; | |
return; | |
} | |
userNums.push(val); | |
} | |
if (new Set(userNums).size !== count) { | |
alert('Числа не должны повторяться!'); | |
state.gamesPlayed--; | |
return; | |
} | |
// Получение bias | |
const biasVal = parseInt(biasDom.value, 10); | |
if (isNaN(biasVal) || biasVal < 0 || biasVal > 100) { | |
alert('Bias должен быть между 0 и 100!'); | |
state.gamesPlayed--; | |
return; | |
} | |
localStorage.setItem(biasDom === dom.biasClassic ? 'biasClassic' : 'biasQuick', biasVal); | |
const bias = biasVal / 100; | |
// Подготовка пула чисел | |
const pool = []; | |
for (let i = 1; i <= maxNum; i++) { | |
if (!userNums.includes(i)) pool.push(i); | |
} | |
// Розыгрыш | |
const drawSet = new Set(); | |
while (drawSet.size < count) { | |
const pick = Math.random() < bias | |
? userNums[Math.floor(Math.random() * userNums.length)] | |
: pool[Math.floor(Math.random() * pool.length)]; | |
drawSet.add(pick); | |
} | |
const draw = Array.from(drawSet).sort((a, b) => a - b); | |
const matches = userNums.filter(n => draw.includes(n)); | |
// Отображение результата | |
resultDom.innerHTML = | |
`<strong>Выпало:</strong> ${draw.join(', ')}<br>` + | |
`<strong>Совпадений:</strong> ${matches.length}` + | |
(matches.length === count ? ' (Полное совпадение!)' : ''); | |
// Обновление баланса и выигрышей | |
if (matches.length === count) { | |
state.wins++; | |
state.balance += winReward; | |
confetti(confettiOpts); | |
} else { | |
state.balance -= lossPenalty; | |
} | |
updateStats(); | |
}; | |
// Инициализация интерфейса | |
dom.lotterySelect.value = state.lastLottery; | |
dom.biasClassic.value = state.biasClassic; | |
dom.biasQuick.value = state.biasQuick; | |
applyTheme(state.theme); | |
updateStats(); | |
showLottery(state.lastLottery); | |
// Обработчики событий | |
dom.themeToggle.addEventListener('click', () => applyTheme(document.body.classList.contains('dark-theme') ? 'light' : 'dark')); | |
dom.lotterySelect.addEventListener('change', (e) => showLottery(e.target.value)); | |
dom.autoFillClassic.addEventListener('click', () => { | |
getRandomNumbers(6, 1, 49) | |
.sort((a, b) => a - b) | |
.forEach((num, idx) => document.getElementById(`c${idx+1}`).value = num); | |
}); | |
dom.drawClassic.addEventListener('click', () => playLottery({ | |
count: 6, | |
maxNum: 49, | |
inputPrefix: 'c', | |
biasDom: dom.biasClassic, | |
resultDom: dom.resultClassic, | |
winReward: 100, | |
lossPenalty: 10, | |
confettiOpts: { particleCount: 200, spread: 100, origin: { y: 0.5 } } | |
})); | |
dom.autoFillQuick.addEventListener('click', () => { | |
getRandomNumbers(3,1,20) | |
.sort((a,b) => a - b) | |
.forEach((num, idx) => document.getElementById(`q${idx+1}`).value = num); | |
}); | |
dom.drawQuick.addEventListener('click', () => playLottery({ | |
count: 3, | |
maxNum: 20, | |
inputPrefix: 'q', | |
biasDom: dom.biasQuick, | |
resultDom: dom.resultQuick, | |
winReward: 50, | |
lossPenalty: 5, | |
confettiOpts: { particleCount: 150, spread: 90, origin: { y: 0.5 } } | |
})); | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Что то этих лотереек развелось... А ни одной нормально работающей оконной лотерейки ещё нету. Видимо слишком сложно.