.
морской бой
Сообщений 1 страница 15 из 15
Поделиться22025-11-07 19:21:50
[html]
<script src="https://cdn.jsdelivr.net/npm/@quadrosystems/mybb-sdk"></script>
<script>
(async function(){
const mybb = new MyBBSDK({
forumUrl: "https://lalamo.rusff.me",
apiKey: "testtest0"
});
const GAME_KEY = "battleship-field"; // ключ хранения поля в теме
const container = document.getElementById("battleship");
const resetBtn = document.getElementById("reset-field");
const user = mybb.user.username || "Guest";
// Настройки
const GRID_SIZE = 10;
const SHIPS = [4,3,3,2,2,2,1,1,1,1]; // длины кораблей
const FIRE_COOLDOWN = 2*60*1000; // 2 минуты
// Проверка админа
const isAdmin = mybb.user.uid == 1; // можно менять на нужного пользователя
if(isAdmin) resetBtn.style.display="inline-block";
// Создать поле
let field = Array(GRID_SIZE).fill(0).map(()=>Array(GRID_SIZE).fill(0));
let lastFire = {};
// --- функции для игры ---
function renderField() {
container.innerHTML = "";
const table = document.createElement("table");
table.style.borderCollapse="collapse";
for(let y=0;y<GRID_SIZE;y++){
const tr=document.createElement("tr");
for(let x=0;x<GRID_SIZE;x++){
const td=document.createElement("td");
td.style.width="32px";
td.style.height="32px";
td.style.border="1px solid #000";
td.style.textAlign="center";
td.style.verticalAlign="middle";
td.style.cursor="pointer";
td.dataset.x=x;
td.dataset.y=y;
// покраска клеток
if(field[y][x]==1) td.style.background="#555"; // корабль (скрыт)
else if(field[y][x]==2) td.style.background="red"; // попадание
else if(field[y][x]==3) td.style.background="lightblue"; // промах
tr.appendChild(td);
}
table.appendChild(tr);
}
container.appendChild(table);
}
function placeShipsRandom() {
field = Array(GRID_SIZE).fill(0).map(()=>Array(GRID_SIZE).fill(0));
for(const len of SHIPS){
let placed=false;
while(!placed){
const dir = Math.random()<0.5?"H":"V";
const x=Math.floor(Math.random()*(GRID_SIZE-(dir=="H"?len:0)));
const y=Math.floor(Math.random()*(GRID_SIZE-(dir=="V"?len:0)));
let canPlace=true;
for(let i=0;i<len;i++){
if(dir=="H" && field[y][x+i]==1) canPlace=false;
if(dir=="V" && field[y+i][x]==1) canPlace=false;
}
if(canPlace){
for(let i=0;i<len;i++){
if(dir=="H") field[y][x+i]=1;
if(dir=="V") field[y+i][x]=1;
}
placed=true;
}
}
}
}
async function saveField() {
try {
await mybb.topic.setCustomField(GAME_KEY, JSON.stringify(field));
} catch(e){ console.error("Ошибка сохранения:", e);}
}
async function loadField() {
try {
const saved = await mybb.topic.getCustomField(GAME_KEY);
if(saved){
field = JSON.parse(saved);
} else {
if(isAdmin) { placeShipsRandom(); await saveField(); }
}
} catch(e){ console.error("Ошибка загрузки:", e);}
}
container.addEventListener("click", async (e)=>{
if(!e.target.dataset.x) return;
const x=+e.target.dataset.x;
const y=+e.target.dataset.y;
const now=Date.now();
if(lastFire[user] && now-lastFire[user]<FIRE_COOLDOWN) {
alert("Можно стрелять раз в 2 минуты");
return;
}
if(field[y][x]==0){ field[y][x]=3; alert("Промах!"); }
else if(field[y][x]==1){ field[y][x]=2; alert("Попадание!"); }
else{ return; }
lastFire[user]=now;
await saveField();
renderField();
// Автоматически отправляем сообщение в тему
try{
const msg = `${user} стрелял по (${x+1},${y+1}) и ${field[y][x]==2?"попал!":"промах!"}`;
await mybb.topic.postReply(msg);
}catch(e){ console.error("Ошибка отправки сообщения:", e);}
});
resetBtn.addEventListener("click", async ()=>{
if(!isAdmin) return;
placeShipsRandom();
await saveField();
renderField();
});
await loadField();
renderField();
})();
</script>
<!-- Морской бой для MyBB -->
<div id="battleship-container" style="margin:20px auto; text-align:center;">
<h3>Морской бой</h3>
<div id="battleship"></div>
<button id="reset-field" style="margin-top:10px; display:none;">Сбросить поле (только админ)</button>
</div>
[/html]
Поделиться32025-11-07 19:56:37
[html]
<div id="battlefield-container" style="text-align:center;">
<h2>⚓ Морской бой</h2>
<p>Сделайте выстрел! (1 раз в 2 минуты)</p>
<div id="battlefield"></div>
<div id="status" style="margin:10px; font-size:16px;"></div>
</div>
[/html]
Поделиться42025-11-07 20:34:05
[html]<!-- === Морской бой для Rusff — вставь в сообщение темы === -->
<style>
/* Минимальные стили поля — вписаны в твой стиль (.lot-table) */
.battle-wrap { max-width:520px; margin:10px auto; text-align:center; font-family: Arial, sans-serif; }
.battle-grid { display:inline-block; border-collapse:collapse; margin:10px 0; }
.battle-grid td { width:48px; height:48px; border:1px solid #222; box-sizing:border-box;
vertical-align:middle; text-align:center; font-weight:700; font-size:14px; cursor:pointer;
background: rgba(255,255,255,0.03); background-size:70% auto; background-position:center; }
.battle-grid td.header { background:transparent; cursor:default; font-weight:700; font-size:13px; }
.battle-grid td.coord { background:transparent; cursor:default; font-weight:600; font-size:13px; color:#000; opacity:0.7; }
.battle-grid td.miss { background-image: none; color:#2b6ea3; opacity:0.9; }
.battle-grid td.hit { background-image: none; color:#fff; background: #cc3a3a; }
.battle-grid td.ship { /* показывать корабль при reveal */
background: url('https://forumstatic.ru/files/0014/cc/0a/39265.png') no-repeat center;
background-size:60%;
}
.battle-controls { margin:6px 0; }
.battle-controls button { margin:0 6px; padding:6px 10px; cursor:pointer; }
.battle-legend { font-size:13px; margin-top:6px; opacity:0.95; }
.battle-info { margin-top:8px; font-size:13px; opacity:0.9; }
</style>
<div id="battleship" class="battle-wrap">
<h3 style="margin:6px 0;">⚓ Морской бой — поле 8×8</h3>
<div class="battle-controls">
<button id="bs-reset">Сброс и новая расстановка</button>
<button id="bs-toggle-reveal">Показать корабли</button>
<button id="bs-export">Экспорт состояния (json)</button>
</div>
<div id="bs-board-container" style="overflow:auto;">
<table id="bs-board" class="battle-grid lot-table" style="table-layout:fixed;">
<!-- генерируется скриптом -->
</table>
</div>
<div class="battle-info">
<span id="bs-status">Попаданий: <b id="bs-hits">0</b> / <span id="bs-total-cells">0</span></span>
</div>
<div class="battle-legend">
<span style="display:inline-block;margin-right:12px;">🔴 — попадание</span>
<span style="display:inline-block;margin-right:12px;">🔵 — промах</span>
<span style="display:inline-block;">🟩 — корабль (только при показе)</span>
</div>
</div>
<script type="text/javascript">
/* Battleship для Rusff — автономный, сохраняет состояние в localStorage.
Ключ сохранения: 'battleship_topic_' + (FORUM.topic.id || hash(location.href))
Автор: адаптация под твой шаблон.
*/
(function(){
// безопасные имена в глобальных пространствах от твоих скриптов
if (typeof FORUM === 'undefined') window.FORUM = window.FORUM || {};
FORUM.battleship = FORUM.battleship || {};
// Конфигурация
var ROWS = 8, COLS = 8;
var LETTERS = ['A','B','C','D','E','F','G','H'];
var FLEET = [4,3,3,2,2,2,1,1,1,1]; // клетки каждого корабля (1×4,2×3,3×2,4×1)
var storageKey = (function(){
try {
if (window.FORUM && FORUM.topic && FORUM.topic.id) return 'battleship_topic_' + FORUM.topic.id;
} catch(e){}
// fallback: hash URL
function simpleHash(s){
var h=0; for(var i=0;i<s.length;i++){h=(h<<5)-h + s.charCodeAt(i); h|=0;}
return Math.abs(h);
}
return 'battleship_topic_' + simpleHash(location.href);
})();
// state: {ships: [[{r,c},...],...], cells: { "r_c": "miss"|"hit" }, revealed:bool}
var state = null;
// DOM refs
var boardEl = null, hitsEl=null, totalCellsEl=null, btnReset=null, btnReveal=null, btnExport=null;
// helpers
function saveState(){
try { localStorage.setItem(storageKey, JSON.stringify(state)); } catch(e){ console.warn(e); }
}
function loadState(){
try {
var s = localStorage.getItem(storageKey);
if (s) return JSON.parse(s);
} catch(e){}
return null;
}
function clearState(){
state = null;
localStorage.removeItem(storageKey);
}
// попытка разместить флот случайно без пересечений и в пределах поля
function placeFleetRandom(){
var ships = [];
var occupied = {}; // key "r_c"
function tryPlace(len){
var dir = Math.random() < 0.5 ? 'h' : 'v';
var r = Math.floor(Math.random()*ROWS);
var c = Math.floor(Math.random()*COLS);
var cells = [];
for (var k=0;k<len;k++){
var rr = dir==='h' ? r : r+k;
var cc = dir==='h' ? c+k : c;
if (rr<0 || rr>=ROWS || cc<0 || cc>=COLS) return null;
if (occupied[rr+'_'+cc]) return null;
cells.push({r:rr,c:cc});
}
// also prevent adjacent placement (optional rule) — keep gap 1 cell around ship
for (var i=0;i<cells.length;i++){
for(var dr=-1;dr<=1;dr++){
for(var dc=-1;dc<=1;dc++){
var nr=cells[i].r+dr, nc=cells[i].c+dc;
if (nr>=0 && nr<ROWS && nc>=0 && nc<COLS && occupied[nr+'_'+nc]) return null;
}
}
}
return cells;
}
for (var i=0;i<FLEET.length;i++){
var len = FLEET[i];
var attempts = 0, placed = null;
while(attempts < 200){
placed = tryPlace(len);
if (placed) break;
attempts++;
}
if (!placed){
// если не смогли разместить (очень маловероятно), начинаем заново
return placeFleetRandom();
}
ships.push(placed);
for (var j=0;j<placed.length;j++){
occupied[placed[j].r+'_'+placed[j].c] = true;
}
}
return ships;
}
// init state (создать или загрузить)
function initState(){
var s = loadState();
if (s && s.ships && s.cells) {
state = s;
} else {
state = { ships: placeFleetRandom(), cells: {}, revealed: false };
saveState();
}
}
// отрисовка таблицы (с буквами/цифрами)
function renderBoard(){
if (!boardEl) return;
boardEl.innerHTML = '';
var tbody = document.createElement('tbody');
// первая строка: пустая клетка + заголовки столбцов A..H
var trHead = document.createElement('tr');
trHead.appendChild(td('', 'header')); // пустая
for (var c=0;c<COLS;c++){
trHead.appendChild(td(LETTERS[c], 'header'));
}
tbody.appendChild(trHead);
for (var r=0;r<ROWS;r++){
var tr = document.createElement('tr');
// номер строки слева
tr.appendChild(td(String(r+1), 'coord'));
for (var c=0;c<COLS;c++){
var cell = td('', '');
cell.dataset.r = r; cell.dataset.c = c;
var key = r+'_'+c;
// determine css based on state.cells
if (state.cells[key] === 'miss') {
cell.classList.add('miss');
cell.textContent = '•';
cell.title = 'Промах';
} else if (state.cells[key] === 'hit') {
cell.classList.add('hit');
cell.textContent = '✖';
cell.title = 'Попадание';
} else {
cell.textContent = '';
}
// show ship if revealed or (optionally) if hit
if (state.revealed && isShipAt(r,c)) {
cell.classList.add('ship');
cell.title = cell.title || 'Корабль';
}
// bind click handler only for untouched cells
(function(rr,cc,el){
el.addEventListener('click', function(e){
handleCellClick(rr,cc,el);
});
})(r,c,cell);
tr.appendChild(cell);
}
tbody.appendChild(tr);
}
boardEl.appendChild(tbody);
// update counters
var totalShipCells = countTotalShipCells();
totalCellsEl.textContent = totalShipCells;
hitsEl.textContent = countHits();
}
function td(text, cls){
var el = document.createElement('td');
if (cls) el.className = cls;
el.textContent = text;
return el;
}
function isShipAt(r,c){
for (var i=0;i<state.ships.length;i++){
for (var j=0;j<state.ships[i].length;j++){
if (state.ships[i][j].r===r && state.ships[i][j].c===c) return true;
}
}
return false;
}
function countTotalShipCells(){
var s=0;
for (var i=0;i<state.ships.length;i++) s += state.ships[i].length;
return s;
}
function countHits(){
var cnt=0;
for (var k in state.cells) if (state.cells[k]==='hit') cnt++;
return cnt;
}
// клик по клетке
function handleCellClick(r,c,el){
var key = r+'_'+c;
if (state.cells[key] === 'hit' || state.cells[key] === 'miss') {
// уже кликали — ничего не делаем
return;
}
if (isShipAt(r,c)){
state.cells[key] = 'hit';
el.classList.add('hit');
el.textContent = '✖';
el.title = 'Попадание';
} else {
state.cells[key] = 'miss';
el.classList.add('miss');
el.textContent = '•';
el.title = 'Промах';
}
saveState();
hitsEl.textContent = countHits();
// optional: если все потоплены — показать уведомление
if (countHits() >= countTotalShipCells()){
setTimeout(function(){
alert('Поздравляем! Все корабли потоплены.');
}, 50);
}
}
// UI: кнопки
function attachUI(){
btnReset = document.getElementById('bs-reset');
btnReveal = document.getElementById('bs-toggle-reveal');
btnExport = document.getElementById('bs-export');
hitsEl = document.getElementById('bs-hits');
totalCellsEl = document.getElementById('bs-total-cells');
btnReset && btnReset.addEventListener('click', function(){
if (!confirm('Сбросить поле и заново расставить корабли?')) return;
state = { ships: placeFleetRandom(), cells: {}, revealed: false };
saveState();
renderBoard();
btnReveal.textContent = 'Показать корабли';
});
btnReveal && btnReveal.addEventListener('click', function(){
state.revealed = !state.revealed;
saveState();
renderBoard();
btnReveal.textContent = state.revealed ? 'Скрыть корабли' : 'Показать корабли';
});
btnExport && btnExport.addEventListener('click', function(){
var data = JSON.stringify(state);
// простое окно с json — можно скопировать
var w = window.open('', '_blank', 'width=700,height=400');
w.document.write('<pre style="white-space:pre-wrap;word-wrap:break-word;font-family:monospace;">' + escapeHtml(data) + '</pre>');
w.document.title = 'battleship state export';
});
}
function escapeHtml(s){ return (s+'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
// init everything
function init(){
boardEl = document.getElementById('bs-board');
if (!boardEl) return;
attachUI();
initState();
renderBoard();
// ensure reveal button label correct
var rb = document.getElementById('bs-toggle-reveal');
if (rb) rb.textContent = state.revealed ? 'Скрыть корабли' : 'Показать корабли';
}
// run on DOM ready
if (document.readyState === 'loading'){
document.addEventListener('DOMContentLoaded', init);
} else init();
})();
</script>[/html]
Поделиться52025-11-07 20:43:02
[html]
<div id="battlefield-container" style="text-align:center;">
<h2>⚓ Морской бой</h2>
<p>Сделайте выстрел! (1 раз в 2 минуты)</p>
<div id="battlefield"></div>
<div id="status" style="margin:10px; font-size:16px;"></div>
</div>
<style>
#battlefield {
display: grid;
grid-template-columns: repeat(8, 50px);
grid-template-rows: repeat(8, 50px);
gap: 4px;
justify-content: center;
margin: 20px auto;
}
.cell {
width: 50px; height: 50px;
background: #9bb7d4;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
color: white;
font-weight: bold;
user-select: none;
}
.cell.hit { background: #e63946; }
.cell.miss { background: #457b9d; }
</style>
<script>
(async function(){
if (!window.SeaBattle) {
document.getElementById("battlefield-container").innerHTML =
"<p style='color:red;'>Ошибка: нет подключения к API.</p>";
return;
}
const { sdk, topicId, gridSize } = window.SeaBattle;
const field = document.getElementById("battlefield");
const status = document.getElementById("status");
let cells = [];
let gameState = await loadGame();
// создаём поле
renderGrid();
function renderGrid() {
field.innerHTML = "";
for (let y = 0; y < gridSize; y++) {
for (let x = 0; x < gridSize; x++) {
const div = document.createElement("div");
div.className = "cell";
div.dataset.x = x;
div.dataset.y = y;
const key = x + "_" + y;
if (gameState[key] === "hit") div.classList.add("hit");
else if (gameState[key] === "miss") div.classList.add("miss");
div.addEventListener("click", () => shoot(x, y, div));
field.appendChild(div);
cells.push(div);
}
}
}
async function shoot(x, y, div) {
const key = x + "_" + y;
if (gameState[key]) return;
const hit = Math.random() < 0.3; // вероятность попадания
gameState[key] = hit ? "hit" : "miss";
div.classList.add(hit ? "hit" : "miss");
div.textContent = hit ? "✖" : "•";
await saveGame();
}
async function loadGame() {
try {
const data = await sdk.getVariable(topicId, "sea_battle_state");
return data ? JSON.parse(data) : {};
} catch (e) {
console.warn(e);
return {};
}
}
async function saveGame() {
try {
await sdk.setVariable(topicId, "sea_battle_state", JSON.stringify(gameState));
} catch (e) {
console.error("Ошибка сохранения:", e);
}
}
})();
</script>
[/html]
Поделиться62025-11-07 22:30:11
[html]<div id="battle-container" style="text-align:center;">
<h3>⚓ Морской бой — 8Ч8</h3>
<table id="battle-grid" style="margin:10px auto; border-collapse:collapse;"></table>
<div style="margin-top:6px;">
<button id="bs-reveal">Показать/Скрыть корабли</button>
<button id="bs-reset">Сбросить (только автор темы)</button>
</div>
<div id="battle-status" style="margin-top:8px;">Попаданий: <span id="hits">0</span></div>
</div>
<script>
(async function(){
if (!window.FORUM || !FORUM.seaBattle) {
document.getElementById('battle-container').innerHTML = '<p style="color:red;">Ошибка: нет подключения к API.</p>';
return;
}
const { mybb, storageKey, ROWS, COLS, topicId, loadState, placeFleetRandom } = FORUM.seaBattle;
const table = document.getElementById('battle-grid');
const hitsEl = document.getElementById('hits');
const btnReveal = document.getElementById('bs-reveal');
const btnReset = document.getElementById('bs-reset');
let state = await loadState();
function render() {
table.innerHTML='';
const tbody = document.createElement('tbody');
for (let r=0;r<ROWS;r++){
const tr=document.createElement('tr');
for (let c=0;c<COLS;c++){
const td=document.createElement('td');
td.style.width='48px';
td.style.height='48px';
td.style.border='1px solid #222';
td.style.textAlign='center';
td.style.cursor='pointer';
const key=r+'_'+c;
if(state.cells[key]==='hit'){ td.style.background='#cc3a3a'; td.textContent='✖'; }
else if(state.cells[key]==='miss'){ td.style.color='#2b6ea3'; td.textContent='•'; }
else td.textContent='';
if(state.revealed && state.ships.some(ship=>ship.some(s=>s.r===r&&s.c===c))){
td.style.background='green';
}
td.addEventListener('click',()=>handleClick(r,c));
tr.appendChild(td);
}
tbody.appendChild(tr);
}
table.appendChild(tbody);
hitsEl.textContent=Object.values(state.cells).filter(v=>v==='hit').length;
}
async function handleClick(r,c){
const key=r+'_'+c;
if(state.cells[key]) return; // уже кликнули
const isShip=state.ships.some(ship=>ship.some(s=>s.r===r&&s.c===c));
state.cells[key]=isShip?'hit':'miss';
await mybb.storage.set(storageKey,state);
state=await loadState();
render();
}
btnReveal.addEventListener('click',async()=>{
state.revealed=!state.revealed;
await mybb.storage.set(storageKey,state);
render();
});
btnReset.addEventListener('click',async()=>{
const isAuthor=(FORUM.user.id===FORUM.topic.userId);
if(!isAuthor){ alert('Только автор темы может сбросить поле'); return; }
if(!confirm('Сбросить поле и расставить заново?')) return;
state={ships:placeFleetRandom(),cells:{},revealed:false};
await mybb.storage.set(storageKey,state);
render();
});
render();
// автообновление каждые 10 сек, чтобы видеть выстрелы других игроков
setInterval(async()=>{ state=await loadState(); render(); },10000);
})();
</script>[/html]
Поделиться72025-11-07 23:01:33
[html]<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Локальный Морской Бой</title>
<!-- Подключение Tailwind CSS для стилей -->
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* Стиль для сетки, чтобы обеспечить квадратные ячейки */
.grid-container {
display: grid;
grid-template-columns: repeat(11, 1fr);
gap: 2px;
aspect-ratio: 1 / 1;
}
.cell {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
user-select: none;
transition: all 0.1s;
border-radius: 4px;
}
.header-cell {
background-color: #e5e7eb; /* gray-200 */
font-weight: 600;
color: #4b5563; /* gray-600 */
}
</style>
</head>
<body class="bg-gray-100 p-4 font-sans antialiased min-h-screen flex flex-col items-center">
<div id="game-container" class="w-full max-w-4xl bg-white shadow-xl rounded-2xl p-6 md:p-8">
<header class="text-center mb-6">
<h1 class="text-3xl font-extrabold text-indigo-700">Морской Бой (Локальный режим)</h1>
<p id="message" class="mt-2 text-lg font-semibold p-2 rounded-lg bg-yellow-100 text-yellow-800 transition duration-300">
Нажмите "Начать Новую Игру", чтобы начать.
</p>
<button id="startButton" class="mt-4 px-6 py-3 bg-green-600 text-white font-bold rounded-xl shadow-lg hover:bg-green-700 transition transform hover:scale-105">
Начать Новую Игру
</button>
</header>
<div id="gameboards" class="grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-10 mt-8">
<!-- Поле Игрока 1 -->
<div id="player1BoardContainer" class="p-4 border-2 border-indigo-200 rounded-lg bg-indigo-50">
<h2 class="text-xl font-bold text-center mb-4 text-indigo-700">Игрок 1: Ваше Поле</h2>
<div id="player1Grid" class="grid-container w-full max-w-md mx-auto"></div>
</div>
<!-- Поле Игрока 2 -->
<div id="player2BoardContainer" class="p-4 border-2 border-red-200 rounded-lg bg-red-50">
<h2 class="text-xl font-bold text-center mb-4 text-red-700">Игрок 2: Ваше Поле</h2>
<div id="player2Grid" class="grid-container w-full max-w-md mx-auto"></div>
</div>
</div>
<div id="turn-overlay" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center hidden z-10">
<div class="bg-white p-8 rounded-xl shadow-2xl text-center">
<h3 id="overlay-text" class="text-2xl font-bold mb-4">Начинает Игрок 1!</h3>
<p class="text-gray-600 mb-6">Игрок <span id="current-player-name" class="font-extrabold text-3xl text-indigo-600">1</span>, отвернитесь, пока <span id="other-player-name">Игрок 2</span> совершает ход!</p>
<button id="hideButton" class="px-6 py-3 bg-blue-600 text-white font-bold rounded-lg hover:bg-blue-700 transition">
Продолжить
</button>
</div>
</div>
</div>
<script>
// --- КОНСТАНТЫ И ИНИЦИАЛИЗАЦИЯ ---
const GRID_SIZE = 10;
const INITIAL_SHIPS = [
{ size: 4, count: 1 }, // Линкор
{ size: 3, count: 2 }, // Крейсер
{ size: 2, count: 3 }, // Эсминец
{ size: 1, count: 4 } // Катер
];
const TOTAL_SHIP_CELLS = INITIAL_SHIPS.reduce((sum, s) => sum + s.size * s.count, 0);
let grid1 = []; // 0: пустая, 1: корабль
let grid2 = [];
let shots1 = []; // Попадания Игрока 1 по Игроку 2 (состояние поля 2 для P1)
let shots2 = []; // Попадания Игрока 2 по Игроку 1 (состояние поля 1 для P2)
let currentTurn = 1; // 1 или 2
let hits1 = 0; // Попадания Игрока 1 (по полю 2)
let hits2 = 0; // Попадания Игрока 2 (по полю 1)
let gameActive = false;
const messageEl = document.getElementById('message');
const startButton = document.getElementById('startButton');
const board1Container = document.getElementById('player1Grid');
const board2Container = document.getElementById('player2Grid');
const overlay = document.getElementById('turn-overlay');
// --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ---
// Создание пустой сетки
const createEmptyGrid = () => Array(GRID_SIZE).fill(0).map(() => Array(GRID_SIZE).fill(0));
// Генерация случайной сетки (УПРОЩЕННО, просто расставляет 20 случайных кораблей)
const placeShipsRandomly = () => {
const grid = createEmptyGrid();
let placedCellsCount = 0;
// Расчет общего количества ячеек, которые нужно заполнить (как в оригинальном React-коде)
const cellsToPlace = INITIAL_SHIPS.reduce((sum, s) => sum + s.count, 0) * 2;
while (placedCellsCount < cellsToPlace) {
const r = Math.floor(Math.random() * GRID_SIZE);
const c = Math.floor(Math.random() * GRID_SIZE);
if (grid[r][c] === 0) {
grid[r][c] = 1; // 1 = Корабль
placedCellsCount++;
}
}
return grid;
};
// Конвертация координат (0, 0) -> A1
const coordsToLabel = (r, c) => `${String.fromCharCode(65 + c)}${r + 1}`;
// --- ИНИЦИАЛИЗАЦИЯ ИГРЫ ---
const initializeGame = () => {
grid1 = placeShipsRandomly();
grid2 = placeShipsRandomly();
shots1 = createEmptyGrid(); // P1 shots on P2 grid
shots2 = createEmptyGrid(); // P2 shots on P1 grid
currentTurn = 1;
hits1 = 0;
hits2 = 0;
gameActive = true;
renderBoards();
updateMessage(`Игра началась! Ход Игрока 1 (синий).`);
startButton.textContent = 'Новая Игра (Сброс)';
// Показываем оверлей для начала игры
showOverlay(1, 2);
};
// --- ОСНОВНАЯ ЛОГИКА ХОДА ---
const handleShot = (r, c, targetPlayer) => {
if (!gameActive || targetPlayer !== currentTurn) {
// Если стреляет не тот игрок или игра неактивна
return;
}
const shooterShots = targetPlayer === 1 ? shots1 : shots2;
const targetGrid = targetPlayer === 1 ? grid2 : grid1;
const targetHits = targetPlayer === 1 ? hits1 : hits2;
if (shooterShots[r][c] !== 0) {
updateMessage(`Игрок ${currentTurn}: Вы уже стреляли в клетку ${coordsToLabel(r, c)}!`);
return;
}
const isHit = targetGrid[r][c] === 1;
if (isHit) {
shooterShots[r][c] = 2; // 2 = Попадание
if (targetPlayer === 1) { hits1++; } else { hits2++; }
updateMessage(`Игрок ${currentTurn}: 🎉 ПОПАДАНИЕ! Продолжайте стрелять.`);
renderBoards();
checkWinCondition(targetPlayer);
} else {
shooterShots[r][c] = 3; // 3 = Промах
updateMessage(`Игрок ${currentTurn}: 🌊 ПРОМАХ! Ход переходит к Игроку ${targetPlayer === 1 ? 2 : 1}.`);
renderBoards();
// Передача хода
setTimeout(() => {
showOverlay(targetPlayer === 1 ? 2 : 1, targetPlayer);
}, 1500);
}
};
const checkWinCondition = (shooter) => {
const currentHits = shooter === 1 ? hits1 : hits2;
const winnerName = `Игрок ${shooter}`;
if (currentHits >= TOTAL_SHIP_CELLS) {
gameActive = false;
updateMessage(`🏆 ${winnerName} Победил! Все корабли противника потоплены! 🏆`);
startButton.textContent = 'Начать Снова';
document.getElementById('turn-overlay').classList.add('hidden'); // Убрать оверлей
}
};
// --- РЕНДЕРИНГ UI ---
const getCellColorAndContent = (r, c, isOwnBoard) => {
const grid = isOwnBoard ? (currentTurn === 1 ? grid1 : grid2) : (currentTurn === 1 ? grid2 : grid1);
const shots = isOwnBoard ? (currentTurn === 1 ? shots2 : shots1) : (currentTurn === 1 ? shots1 : shots2);
const cellValue = grid[r][c];
const shotStatus = shots[r][c];
let bgColor = 'bg-gray-200 hover:bg-gray-300';
let content = '';
let cursor = isOwnBoard ? 'cursor-default' : 'cursor-pointer';
let handler = null;
if (isOwnBoard) {
// Отрисовка своего поля (показываем корабли)
if (cellValue === 1) {
bgColor = 'bg-gray-700'; // Корабль
if (shotStatus === 2) {
bgColor = 'bg-red-700'; // Корабль подбит
content = '💥';
} else if (shotStatus === 3) {
bgColor = 'bg-blue-300'; // Промах по нам
content = '💧';
}
} else if (shotStatus === 3) {
bgColor = 'bg-blue-300'; // Промах по нам
content = '💧';
}
} else {
// Отрисовка поля противника (для стрельбы)
if (shotStatus === 2) {
bgColor = 'bg-red-500'; // Попадание
content = '🔥';
cursor = 'cursor-default';
} else if (shotStatus === 3) {
bgColor = 'bg-blue-300'; // Промах
content = '💧';
cursor = 'cursor-default';
} else {
// Клетка, по которой можно стрелять
bgColor = 'bg-cyan-100 hover:bg-cyan-300';
cursor = 'cursor-pointer';
handler = () => handleShot(r, c, currentTurn);
}
}
return { bgColor, content, cursor, handler };
};
const renderGrid = (container, isOwnBoard) => {
container.innerHTML = '';
// 1. Заголовки столбцов (A-J)
const emptyCell = document.createElement('div');
emptyCell.className = 'cell header-cell';
container.appendChild(emptyCell);
for (let c = 0; c < GRID_SIZE; c++) {
const header = document.createElement('div');
header.className = 'cell header-cell';
header.textContent = String.fromCharCode(65 + c);
container.appendChild(header);
}
// 2. Ячейки с данными
for (let r = 0; r < GRID_SIZE; r++) {
// Заголовок строки (1-10)
const header = document.createElement('div');
header.className = 'cell header-cell';
header.textContent = r + 1;
container.appendChild(header);
for (let c = 0; c < GRID_SIZE; c++) {
const { bgColor, content, cursor, handler } = getCellColorAndContent(r, c, isOwnBoard);
const cell = document.createElement('div');
cell.className = `cell text-lg font-bold ${bgColor} ${cursor}`;
cell.textContent = content;
cell.title = coordsToLabel(r, c);
if (handler) {
cell.addEventListener('click', handler);
}
container.appendChild(cell);
}
}
};
const renderBoards = () => {
const otherPlayer = currentTurn === 1 ? 2 : 1;
// Рендерим поле текущего игрока (isOwnBoard = true)
document.getElementById(`player${currentTurn}BoardContainer`).style.display = 'block';
renderGrid(document.getElementById(`player${currentTurn}Grid`), true);
// Рендерим поле противника (isOwnBoard = false)
document.getElementById(`player${otherPlayer}BoardContainer`).style.display = 'block';
renderGrid(document.getElementById(`player${otherPlayer}Grid`), false);
// Прячем поле противника и показываем только активное поле и поле, по которому стреляют
document.getElementById(`player${currentTurn}BoardContainer`).style.borderColor = '#4f46e5'; /* indigo-600 */
document.getElementById(`player${otherPlayer}BoardContainer`).style.borderColor = '#10b981'; /* emerald-500 */
document.getElementById(`player${currentTurn}BoardContainer`).classList.add('order-2');
document.getElementById(`player${otherPlayer}BoardContainer`).classList.remove('order-2');
document.getElementById(`player${currentTurn}BoardContainer`).querySelector('h2').textContent = `Ваше Поле (Попаданий по Вам: ${currentTurn === 1 ? hits2 : hits1}/${TOTAL_SHIP_CELLS})`;
document.getElementById(`player${otherPlayer}BoardContainer`).querySelector('h2').textContent = `Поле Противника (Ваших попаданий: ${currentTurn === 1 ? hits1 : hits2}/${TOTAL_SHIP_CELLS})`;
};
const updateMessage = (text) => {
messageEl.textContent = text;
messageEl.className = `mt-2 text-lg font-semibold p-2 rounded-lg transition duration-300 ${gameActive ? 'bg-green-100 text-green-800' : 'bg-yellow-200 text-red-700'}`;
};
// --- ЛОГИКА ОВЕРЛЕЯ (СМЕНА ИГРОКОВ) ---
const showOverlay = (nextPlayer, previousPlayer) => {
currentTurn = nextPlayer;
document.getElementById('overlay-text').textContent = `Ход переходит к Игроку ${nextPlayer}!`;
document.getElementById('current-player-name').textContent = nextPlayer;
document.getElementById('other-player-name').textContent = `Игрок ${previousPlayer}`;
overlay.classList.remove('hidden');
document.getElementById('gameboards').classList.add('hidden');
// Чтобы изменить цвет кнопки оверлея
const hideButton = document.getElementById('hideButton');
hideButton.textContent = `Игрок ${nextPlayer}, нажмите, чтобы начать свой ход`;
hideButton.style.backgroundColor = nextPlayer === 1 ? '#4f46e5' : '#ef4444'; // Indigo vs Red
hideButton.onclick = () => {
overlay.classList.add('hidden');
document.getElementById('gameboards').classList.remove('hidden');
renderBoards();
updateMessage(`Ход Игрока ${currentTurn}. Стреляйте по полю противника.`);
};
};
// --- ОБРАБОТЧИК КНОПКИ СТАРТА ---
startButton.addEventListener('click', initializeGame);
// Инициализация при загрузке страницы
window.onload = initializeGame;
</script>
</body>
</html>[/html]
Поделиться82025-11-07 23:48:54
[html]<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Морской Бой - Пошаговая версия</title>
<script src="https://cdn.jsdelivr.net/npm/@quadrosystems/mybb-sdk@0.9.7/lib/index.min.js"></script>
<style>
/* Стиль взят из предыдущего автономного примера */
body { font-family: Arial, sans-serif; background-color: #f7f9fb; padding: 15px; }
.container { max-width: 450px; margin: 0 auto; background: white; padding: 20px; border-radius: 10px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); }
h1 { text-align: center; color: #3b82f6; margin-bottom: 20px; font-size: 1.8rem; }
.bs-grid-container {
display: grid;
grid-template-columns: repeat(11, 1fr);
gap: 1px;
max-width: 400px;
margin: 10px auto;
border: 3px solid #3b82f6;
border-radius: 8px;
overflow: hidden;
}
.bs-cell {
height: 35px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.9rem;
cursor: pointer;
background-color: #dbeafe;
transition: background-color 0.2s, transform 0.1s;
font-weight: bold;
color: #1f2937;
}
.bs-header { background-color: #e5e7eb; cursor: default; color: #4b5563; font-size: 0.8rem; }
.bs-target-cell:hover { background-color: #93c5fd; transform: scale(1.05); }
.bs-hit { background-color: #f87171 !important; color: white !important; font-size: 1.5rem !important; cursor: default; }
.bs-miss { background-color: #60a5fa !important; color: white !important; font-size: 1.5rem !important; cursor: default; }
#message-box { text-align: center; padding: 10px; margin: 15px auto; border-radius: 6px; font-weight: bold; background-color: #fef3c7; color: #92400e; }
#refresh-button, #reset-button {
display: block;
width: 80%;
margin: 10px auto 0;
padding: 10px;
background-color: #10b981;
color: white;
border: none;
border-radius: 20px;
font-size: 1rem;
cursor: pointer;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.2);
transition: background-color 0.2s;
}
#refresh-button { background-color: #f97316; } /* orange-500 */
</style>
</head>
<body>
<div class="container">
<h1>Морской Бой (Пошаговый)</h1>
<div id="status-info" class="text-sm text-center mb-2">
<p>ID Игры: <span id="game-id">LOADING...</span></p>
<p>Ваш ID: <span id="user-id">LOADING...</span></p>
</div>
<div id="message-box">Ожидание загрузки SDK...</div>
<div id="battleship-container" class="bs-grid-container">
<!-- Сетка будет сгенерирована здесь -->
</div>
<button id="refresh-button">Обновить Ход Соперника</button>
<button id="reset-button">Начать Новую Игру</button>
</div>
<script>
// Инициализация MyBB SDK (предполагаем, что оно успешно загружено)
const mybb = window.MyBB;
console.log("MyBB SDK загружен:", !!mybb);
// --- ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ И НАСТРОЙКИ ---
const GRID_SIZE = 10;
const MAX_HITS = 20;
const GAME_ID_PLACEHOLDER = 'battleship_match_123'; // Должно быть уникальным для каждой игры
let gameState = null;
let currentUserId = 'User_Unknown'; // Будет заменено на ID из SDK
let isMyTurn = false;
// --- ФУНКЦИИ ИГРОВОЙ ЛОГИКИ ---
function createEmptyGrid() { return Array(GRID_SIZE).fill(0).map(() => Array(GRID_SIZE).fill(0)); }
function placeShipsRandomly() {
const grid = createEmptyGrid();
let placedCellsCount = 0;
while (placedCellsCount < MAX_HITS) {
const r = Math.floor(Math.random() * GRID_SIZE);
const c = Math.floor(Math.random() * GRID_SIZE);
if (grid[r][c] === 0) {
grid[r][c] = 1; // 1 = Корабль
placedCellsCount++;
}
}
return grid;
}
function coordsToLabel(r, c) { return `${String.fromCharCode(65 + c)}${r + 1}`; }
function getInitialState(p1Id, p2Id) {
return {
gameId: GAME_ID_PLACEHOLDER,
players: [
{ id: p1Id, name: "Игрок 1", hits: 0 },
{ id: p2Id, name: "Игрок 2", hits: 0 }
],
// Храним только сетку ходов (Grid of Moves) - 0: не стрелял, 1: промах, 2: попадание
// Нам нужно 2 сетки: одна для P1 (для P2) и одна для P2 (для P1)
p1Moves: createEmptyGrid(),
p2Moves: createEmptyGrid(),
// Это сетка, где расположены корабли (должна быть скрыта от противника)
hiddenShipsGrid: placeShipsRandomly(),
currentTurnId: p1Id,
status: 'active',
message: 'Игра началась. Ход Игрока 1.'
};
}
// --- ФУНКЦИИ SDK (ЗАГЛУШКИ) ---
async function loadUserId() {
// Здесь должен быть реальный вызов SDK для получения ID пользователя
if (mybb && mybb.user) {
// ПРИМЕР: Предполагаем, что SDK имеет объект user с полем uid
currentUserId = mybb.user.uid || 'User_' + Math.random().toString(36).substring(2, 9);
} else {
// Имитация ID, если SDK не работает или не предоставляет его
currentUserId = 'User_' + Math.random().toString(36).substring(2, 9);
}
document.getElementById('user-id').textContent = currentUserId.substring(0, 10) + '...';
return currentUserId;
}
async function saveGameStateToForum(state) {
const stateJson = JSON.stringify(state);
console.log("Сохранение состояния (JSON) в MyBB:", stateJson);
// !!! ВАЖНО !!!
// Здесь должен быть реальный вызов mybb-sdk для сохранения JSON
// Пример (НЕ ПРОВЕРЕНО): await mybb.post.updateMeta(POST_ID, { gameData: stateJson });
document.getElementById('message-box').textContent = "СОХРАНЕНО. Попросите соперника обновить страницу!";
return true;
}
async function loadGameStateFromForum() {
console.log("Чтение состояния (JSON) из MyBB...");
// !!! ВАЖНО !!!
// Здесь должен быть реальный вызов mybb-sdk для чтения JSON
// Пример (НЕ ПРОВЕРЕНО): const meta = await mybb.post.getMeta(POST_ID);
// let stateJson = meta.gameData;
// Заглушка:
let stateJson = localStorage.getItem(GAME_ID_PLACEHOLDER);
if (stateJson) {
const newState = JSON.parse(stateJson);
gameState = newState;
updateGameUI();
return true;
} else {
console.warn("Состояние игры не найдено. Начните новую игру.");
return false;
}
}
// --- ОСНОВНАЯ ЛОГИКА ИГРЫ ---
function handleShot(r, c, cellElement) {
if (!gameState || gameState.status !== 'active') return;
if (gameState.currentTurnId !== currentUserId) {
document.getElementById('message-box').textContent = "Сейчас ход вашего соперника. Нажмите 'Обновить Ход Соперника'.";
return;
}
const myIndex = gameState.players[0].id === currentUserId ? 0 : 1;
const opponentIndex = 1 - myIndex;
const opponentMoves = myIndex === 0 ? gameState.p1Moves : gameState.p2Moves;
if (opponentMoves[r][c] !== 0) {
document.getElementById('message-box').textContent = `Вы уже стреляли в ${coordsToLabel(r, c)}!`;
return;
}
// Логика выстрела: всегда стреляем в скрытую сетку соперника
const isHit = gameState.hiddenShipsGrid[r][c] === 1; // Упрощенно: оба игрока стреляют в одну и ту же сетку
const result = isHit ? 2 : 1; // 1: промах, 2: попадание
// Обновляем сетку ходов
opponentMoves[r][c] = result;
if (isHit) {
gameState.players[myIndex].hits++;
gameState.message = `🎉 ПОПАДАНИЕ в ${coordsToLabel(r, c)}! Ваш ход продолжается.`;
} else {
gameState.currentTurnId = gameState.players[opponentIndex].id; // Передаем ход
gameState.message = `🌊 ПРОМАХ в ${coordsToLabel(r, c)}. Ход переходит к ${gameState.players[opponentIndex].name}.`;
}
if (gameState.players[myIndex].hits >= MAX_HITS) {
gameState.status = 'finished';
gameState.message = `🏆 ПОБЕДА! ${gameState.players[myIndex].name} выиграл! 🏆`;
}
// Сохраняем и обновляем UI
saveGameStateToForum(gameState);
updateGameUI();
}
// --- РЕНДЕРИНГ ИНТЕРФЕЙСА ---
function updateGameUI() {
if (!gameState) return;
const myIndex = gameState.players[0].id === currentUserId ? 0 : 1;
const opponentIndex = 1 - myIndex;
const myMoves = myIndex === 0 ? gameState.p2Moves : gameState.p1Moves;
const turnPlayer = gameState.players.find(p => p.id === gameState.currentTurnId);
isMyTurn = gameState.currentTurnId === currentUserId;
document.getElementById('message-box').textContent = gameState.message;
const container = document.getElementById('battleship-container');
container.innerHTML = '';
container.appendChild(createCell('bs-header', ''));
// Заголовки столбцов
for (let c = 0; c < GRID_SIZE; c++) {
container.appendChild(createCell('bs-header', coordsToLabel(0, c)[0]));
}
// Ячейки
for (let r = 0; r < GRID_SIZE; r++) {
container.appendChild(createCell('bs-header', r + 1));
for (let c = 0; c < GRID_SIZE; c++) {
const cell = createCell('bs-cell', '');
const moveStatus = myMoves[r][c];
let isTargetable = gameState.status === 'active' && isMyTurn && moveStatus === 0;
if (moveStatus === 2) {
cell.textContent = '💥';
cell.classList.add('bs-hit');
} else if (moveStatus === 1) {
cell.textContent = '💧';
cell.classList.add('bs-miss');
} else if (isTargetable) {
cell.textContent = coordsToLabel(r, c);
cell.classList.add('bs-target-cell');
cell.onclick = () => handleShot(r, c, cell);
} else {
cell.textContent = ' ';
cell.style.cursor = 'default';
}
container.appendChild(cell);
}
}
}
function createCell(className, content) {
const cell = document.createElement('div');
cell.className = 'bs-cell ' + className;
cell.textContent = content;
return cell;
}
async function initializeGame() {
document.getElementById('game-id').textContent = GAME_ID_PLACEHOLDER;
await loadUserId();
// 1. Попытка загрузить существующую игру
const loaded = await loadGameStateFromForum();
// 2. Если игра не загружена или нет P2, начать новую
if (!loaded || !gameState.players.some(p => p.id === currentUserId && p.id !== 'WAITING_FOR_PLAYER_2')) {
const p1Id = currentUserId;
const p2Id = 'WAITING_FOR_PLAYER_2';
gameState = getInitialState(p1Id, p2Id);
document.getElementById('message-box').textContent = "Новая игра создана. Ждем соперника (с таким же GAME ID).";
// Заглушка для сохранения:
localStorage.setItem(GAME_ID_PLACEHOLDER, JSON.stringify(gameState));
}
updateGameUI();
}
// --- СЛУШАТЕЛИ СОБЫТИЙ ---
document.getElementById('reset-button').onclick = async () => {
if (confirm("Вы уверены, что хотите начать новую игру?")) {
const p1Id = currentUserId;
const p2Id = 'WAITING_FOR_PLAYER_2';
gameState = getInitialState(p1Id, p2Id);
// Заглушка для сохранения:
localStorage.setItem(GAME_ID_PLACEHOLDER, JSON.stringify(gameState));
await saveGameStateToForum(gameState);
updateGameUI();
}
};
document.getElementById('refresh-button').onclick = async () => {
await loadGameStateFromForum();
updateGameUI();
if (isMyTurn) {
document.getElementById('message-box').textContent = "Ваш ход!";
} else {
document.getElementById('message-box').textContent = "Ход соперника. Обновлено.";
}
};
window.onload = initializeGame;
</script>
</body>
</html>[/html]
Поделиться92025-11-07 23:51:55
[html]<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Морской Бой - Пошаговая версия</title>
<style>
/* Общий стиль контейнера, чтобы избежать конфликта с body форума */
.game-wrapper {
font-family: 'Inter', sans-serif;
background-color: #f7f9fb;
padding: 20px; /* Увеличил отступы */
margin: 0 auto;
max-width: 500px;
box-sizing: border-box;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); /* Более сильная тень */
}
.container {
max-width: 450px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 10px;
}
h1 { text-align: center; color: #3b82f6; margin-bottom: 20px; font-size: 1.8rem; }
.bs-grid-container {
display: grid;
grid-template-columns: repeat(11, 1fr);
gap: 1px;
max-width: 400px;
margin: 10px auto;
border: 3px solid #3b82f6;
border-radius: 8px;
overflow: hidden;
box-sizing: content-box;
}
.bs-cell {
height: 35px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.9rem;
cursor: pointer;
background-color: #dbeafe;
transition: background-color 0.2s, transform 0.1s;
font-weight: bold;
color: #1f2937;
}
.bs-header {
background-color: #e5e7eb;
cursor: default;
color: #4b5563;
font-size: 0.8rem;
}
.bs-target-cell:hover { background-color: #93c5fd; transform: scale(1.05); }
.bs-hit { background-color: #f87171 !important; color: white !important; font-size: 1.5rem !important; cursor: default; }
.bs-miss { background-color: #60a5fa !important; color: white !important; font-size: 1.5rem !important; cursor: default; }
#message-box {
text-align: center;
padding: 10px;
margin: 15px auto;
border-radius: 6px;
font-weight: bold;
background-color: #fef3c7;
color: #92400e;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.game-button {
display: block;
width: 80%;
margin: 10px auto 0;
padding: 12px;
color: white;
border: none;
border-radius: 25px;
font-size: 1rem;
cursor: pointer;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
transition: background-color 0.2s, transform 0.1s;
}
.game-button:active { transform: translateY(1px); }
#refresh-button { background-color: #f97316; }
#reset-button { background-color: #10b981; }
</style>
</head>
<body>
<div class="game-wrapper">
<div class="container">
<h1>Морской Бой (Пошаговый)</h1>
<div id="status-info" class="text-sm text-center mb-2">
<p>ID Игры: <span id="game-id">Инициализация...</span></p>
<p>Ваш ID: <span id="user-id">Инициализация...</span></p>
</div>
<div id="message-box">Запуск игры...</div>
<div id="battleship-container" class="bs-grid-container">
<!-- Сетка будет сгенерирована здесь -->
</div>
<button id="refresh-button" class="game-button">Обновить Ход Соперника</button>
<button id="reset-button" class="game-button">Начать Новую Игру</button>
</div>
</div>
<script>
// --- ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ И НАСТРОЙКИ ---
const GRID_SIZE = 10;
const MAX_HITS = 20;
const GAME_ID_PLACEHOLDER = 'battleship_match_123';
let gameState = null;
let currentUserId = 'User_Unknown';
let isMyTurn = false;
/**
* Вычисляет или загружает ID пользователя синхронно, используя sessionStorage.
* Это гарантирует, что ID будет установлен немедленно.
*/
function setupUserIdSynchronously() {
// Убеждаемся, что window.sessionStorage доступен
if (typeof window.sessionStorage === 'undefined') {
currentUserId = 'Fallback_' + Math.random().toString(36).substring(2, 6);
console.error("SessionStorage недоступен. Использование временного ID.");
} else {
const storedId = sessionStorage.getItem('bsUserId');
if (storedId) {
currentUserId = storedId;
} else {
// Генерируем новый ID, если он не найден
currentUserId = 'User_' + Math.random().toString(36).substring(2, 9);
sessionStorage.setItem('bsUserId', currentUserId);
}
}
// Немедленно обновляем UI
const userIdElement = document.getElementById('user-id');
if(userIdElement) {
// Показываем первые 10 символов и троеточие для краткости
userIdElement.textContent = currentUserId.substring(0, 10) + '...';
}
console.log(" ID пользователя установлен синхронно:", currentUserId);
}
// --- ФУНКЦИИ ИГРОВОЙ ЛОГИКИ ---
function createEmptyGrid() { return Array(GRID_SIZE).fill(0).map(() => Array(GRID_SIZE).fill(0)); }
function placeShipsRandomly() {
const grid = createEmptyGrid();
let placedCellsCount = 0;
while (placedCellsCount < MAX_HITS) {
const r = Math.floor(Math.random() * GRID_SIZE);
const c = Math.floor(Math.random() * GRID_SIZE);
if (grid[r][c] === 0) {
grid[r][c] = 1; // 1 = Корабль
placedCellsCount++;
}
}
return grid;
}
function coordsToLabel(r, c) { return `${String.fromCharCode(65 + c)}${r + 1}`; }
function getInitialState(p1Id, p2Id) {
return {
gameId: GAME_ID_PLACEHOLDER,
players: [
{ id: p1Id, name: "Игрок 1", hits: 0 },
{ id: p2Id, name: "Игрок 2", hits: 0 }
],
// Сетки ходов
p1Moves: createEmptyGrid(),
p2Moves: createEmptyGrid(),
// Сетка расположения кораблей (цель)
hiddenShipsGrid: placeShipsRandomly(),
currentTurnId: p1Id,
status: 'active',
message: 'Игра началась. Ход Игрока 1.'
};
}
// --- ФУНКЦИИ ХРАНЕНИЯ (LocalStorage Заглушка) ---
async function saveGameStateToForum(state) {
// Проверяем, что LocalStorage доступен
if (typeof window.localStorage === 'undefined') {
console.error("LocalStorage недоступен. Состояние игры не будет сохранено.");
return false;
}
const stateJson = JSON.stringify(state);
localStorage.setItem(GAME_ID_PLACEHOLDER, stateJson);
document.getElementById('message-box').textContent = "СОХРАНЕНО. Ход передан. Нажмите 'Обновить Ход Соперника' для проверки.";
return true;
}
async function loadGameStateFromForum() {
// Проверяем, что LocalStorage доступен
if (typeof window.localStorage === 'undefined') {
console.error("LocalStorage недоступен. Невозможно загрузить состояние.");
return false;
}
let stateJson = localStorage.getItem(GAME_ID_PLACEHOLDER);
if (stateJson) {
const newState = JSON.parse(stateJson);
// Логика присоединения второго игрока
if (newState.players[1].id === 'WAITING_FOR_PLAYER_2' && currentUserId !== newState.players[0].id) {
newState.players[1].id = currentUserId;
newState.players[1].name = "Игрок 2";
await saveGameStateToForum(newState); // Сохраняем изменение
}
gameState = newState;
updateGameUI();
return true;
} else {
console.warn("Состояние игры не найдено. Начните новую игру.");
return false;
}
}
// --- ОСНОВНАЯ ЛОГИКА ИГРЫ ---
function handleShot(r, c) {
if (!gameState || gameState.status !== 'active') {
document.getElementById('message-box').textContent = "Игра не активна.";
return;
}
if (gameState.currentTurnId !== currentUserId) {
document.getElementById('message-box').textContent = "Сейчас ход вашего соперника. Нажмите 'Обновить Ход Соперника'.";
return;
}
const myIndex = gameState.players.findIndex(p => p.id === currentUserId);
if (myIndex === -1) {
document.getElementById('message-box').textContent = "Ошибка: Ваш ID не найден в игре.";
return;
}
const opponentIndex = 1 - myIndex;
const myMovesGrid = myIndex === 0 ? gameState.p1Moves : gameState.p2Moves;
if (myMovesGrid[r][c] !== 0) {
document.getElementById('message-box').textContent = `Вы уже стреляли в ${coordsToLabel(r, c)}!`;
return;
}
const isHit = gameState.hiddenShipsGrid[r][c] === 1;
const result = isHit ? 2 : 1; // 1: промах, 2: попадание
myMovesGrid[r][c] = result;
if (isHit) {
gameState.players[myIndex].hits++;
gameState.message = ` ПОПАДАНИЕ в ${coordsToLabel(r, c)}! Ваш ход продолжается.`;
} else {
// Передаем ход
gameState.currentTurnId = gameState.players[opponentIndex].id;
gameState.message = ` ПРОМАХ в ${coordsToLabel(r, c)}. Ход переходит к ${gameState.players[opponentIndex].name}.`;
}
if (gameState.players[myIndex].hits >= MAX_HITS) {
gameState.status = 'finished';
gameState.message = ` ПОБЕДА! ${gameState.players[myIndex].name} выиграл! `;
}
// Сохраняем и обновляем UI
saveGameStateToForum(gameState);
updateGameUI();
}
// --- РЕНДЕРИНГ ИНТЕРФЕЙСА ---
function updateGameUI() {
if (!gameState) return;
const myIndex = gameState.players.findIndex(p => p.id === currentUserId);
const myMovesGrid = (myIndex === -1) ? createEmptyGrid() : ((myIndex === 0) ? gameState.p1Moves : gameState.p2Moves);
isMyTurn = gameState.currentTurnId === currentUserId;
document.getElementById('message-box').textContent = gameState.message;
const container = document.getElementById('battleship-container');
container.innerHTML = '';
container.appendChild(createCell('bs-header', ''));
// Заголовки столбцов (A, B, C...)
for (let c = 0; c < GRID_SIZE; c++) {
container.appendChild(createCell('bs-header', coordsToLabel(0, c)[0]));
}
// Ячейки
for (let r = 0; r < GRID_SIZE; r++) {
container.appendChild(createCell('bs-header', r + 1)); // Заголовок строки (1, 2, 3...)
for (let c = 0; c < GRID_SIZE; c++) {
const cell = createCell('bs-cell', '');
const moveStatus = myMovesGrid[r][c];
let isTargetable = gameState.status === 'active' && isMyTurn && moveStatus === 0;
if (moveStatus === 2) {
cell.textContent = '';
cell.classList.add('bs-hit');
} else if (moveStatus === 1) {
cell.textContent = '';
cell.classList.add('bs-miss');
} else if (isTargetable) {
cell.textContent = '';
cell.classList.add('bs-target-cell');
cell.onclick = () => handleShot(r, c, cell);
} else {
cell.textContent = ' ';
cell.style.cursor = 'default';
}
container.appendChild(cell);
}
}
}
function createCell(className, content) {
const cell = document.createElement('div');
cell.className = 'bs-cell ' + className;
cell.textContent = content;
return cell;
}
async function initializeGame() {
console.log("Шаг 1: Начало инициализации. Установка ID пользователя.");
// 1. Устанавливаем ID игры и пользователя немедленно
setupUserIdSynchronously(); // СИНХРОННЫЙ ВЫЗОВ
const gameIdElement = document.getElementById('game-id');
if(gameIdElement) {
gameIdElement.textContent = GAME_ID_PLACEHOLDER;
}
document.getElementById('message-box').textContent = "Шаг 2: Поиск существующей игры...";
// Заменяем window.confirm на простой console.log и прямое выполнение
const customConfirm = (message) => {
console.warn(`[ATTENTION]: Требуется подтверждение: ${message}. Возвращено true.`);
return true;
};
try {
// 2. Попытка загрузить существующую игру
const loaded = await loadGameStateFromForum();
// 3. Если игра не загружена, начать новую
if (!loaded) {
const p1Id = currentUserId;
const p2Id = 'WAITING_FOR_PLAYER_2';
gameState = getInitialState(p1Id, p2Id);
// Сохраняем начальное состояние
await saveGameStateToForum(gameState);
document.getElementById('message-box').textContent = "Шаг 3: Новая игра создана. Ждем соперника. Поделитесь этой страницей!";
} else if (gameState && gameState.players[1].id === 'WAITING_FOR_PLAYER_2' && gameState.players[0].id !== currentUserId) {
document.getElementById('message-box').textContent = "Вы присоединились! Ожидайте первого хода Игрока 1.";
}
updateGameUI();
} catch (e) {
console.error("Критическая ошибка при инициализации игры:", e);
document.getElementById('message-box').textContent = `КРИТИЧЕСКАЯ ОШИБКА! Игра не запустилась: ${e.message}. Проверьте консоль браузера.`;
}
}
// --- СЛУШАТЕЛИ СОБЫТИЙ ---
document.getElementById('reset-button').onclick = async () => {
// Убрал confirm(), так как он заблокирован. Просто перезапускаем.
const p1Id = currentUserId;
const p2Id = 'WAITING_FOR_PLAYER_2';
gameState = getInitialState(p1Id, p2Id);
await saveGameStateToForum(gameState);
updateGameUI();
document.getElementById('message-box').textContent = "Новая игра создана. Сделайте первый выстрел!";
};
document.getElementById('refresh-button').onclick = async () => {
await loadGameStateFromForum();
if (gameState) {
if (isMyTurn) {
document.getElementById('message-box').textContent = "Ваш ход! Стреляйте!";
} else {
document.getElementById('message-box').textContent = "Ход соперника. Обновлено. Ожидайте.";
}
}
};
window.onload = initializeGame;
</script>
</body>
</html>[/html]
Поделиться102025-11-08 00:12:25
[html]
<script src="https://cdn.jsdelivr.net/npm/@quadrosystems/mybb-sdk@0.9.7/lib/index.min.js"></script>
<div id="slotMachine" style="width:90%; max-width:600px; background:#1c1b1b; color:#fff; border-radius:10px; padding:2%; text-align:center; font-family:Georgia; margin:2% auto; box-shadow:0 0 10px rgba(0,0,0,0.6); position:relative;">
<div style="position:absolute; top:8px; left:8px; font-size:0.9em; color:#ffdd8a; text-align:left;">
�� <span id="userName">Гость</span><br>
�� <span id="userCoins">0</span>
</div>
<b style="font-size:1.3em;">�� Слот-машина</b>
<div style="margin-top:1%; font-size:0.9em; color:#d9c7a1;">
⏳ Новый спин через: <span id="timer" style="font-weight:bold; color:#ffdd8a;"></span>
</div>
<div style="display:flex; justify-content:center; gap:2%; margin:3% 0;">
<div class="slotCell" id="slot1" style="flex:1; aspect-ratio:1; background:#000; border:0.3% solid #7c653f; border-radius:8px; display:flex; align-items:center; justify-content:center;"></div>
<div class="slotCell" id="slot2" style="flex:1; aspect-ratio:1; background:#000; border:0.3% solid #7c653f; border-radius:8px; display:flex; align-items:center; justify-content:center;"></div>
<div class="slotCell" id="slot3" style="flex:1; aspect-ratio:1; background:#000; border:0.3% solid #7c653f; border-radius:8px; display:flex; align-items:center; justify-content:center;"></div>
</div>
<button id="spinBtn" style="padding:1% 3%; cursor:pointer; background:#b8863b; color:#fff; border:0.3% solid #000; font-weight:bold; border-radius:6px; box-shadow:0 0 5px rgba(0,0,0,0.6);">
Крутить ��
</button>
<div id="winDisplay" style="margin-top:12px; font-size:1.1em; font-weight:bold; color:#ffdd00; min-height:2.2em;"></div>
</div>
<style>
.winGlow { animation: glowPulse 1s infinite alternate; }
@keyframes glowPulse {
from { text-shadow: 0 0 6px #ffd700; color:#fff2b8; }
to { text-shadow: 0 0 18px #ffd700, 0 0 36px #ffb700; color:#fff9db; }
}
</style>
<script>
/* --- НАСТРОЙКИ --- */
/**
* @const {number} COOLDOWN_MINUTES - Время кулдауна в минутах.
* Установите 0 для отключения.
*/
const COOLDOWN_MINUTES = 0; // <-- УСТАНОВИТЬ ВРУЧНУЮ
const COOLDOWN_MS = COOLDOWN_MINUTES * 60000;
/* Символы (Картинки) - ВСЕГДА HTTPS */
const symbols = [
"https://upforme.ru/uploads/001c/84/76/2/793146.png", // ��
"https://upforme.ru/uploads/001c/84/76/2/635838.png", // ��
"https://upforme.ru/uploads/001c/84/76/2/604915.png", // ��
"https://upforme.ru/uploads/001c/84/76/2/703897.png" // ❤️
];
/* ----------------- */
const ids = ["slot1", "slot2", "slot3"];
const timer = document.getElementById("timer");
const spinBtn = document.getElementById("spinBtn");
const winDisplay = document.getElementById("winDisplay");
let threadID = null;
let userName = localStorage.getItem("slotUserName");
let userCoins = parseInt(localStorage.getItem("slotCoins") || "0");
// Функция для установки изображения в слот
const displayResult = (id, url) => {
document.getElementById(id).innerHTML = `<img src="${url}" style="max-width:80%;max-height:80%;">`;
};
// Асинхронная инициализация пользователя и получение ID темы
async function initialize() {
if (typeof MYBB !== 'undefined' && MYBB.user && MYBB.thread) {
try {
// Получаем ник из SDK
const userDetails = await MYBB.user.getDetails();
userName = userDetails.username || userName || "Игрок";
// Получаем ID темы
const threadDetails = await MYBB.thread.getDetails();
threadID = threadDetails.tid;
console.log("Thread ID установлен:", threadID); // <--- ДОБАВИТЬ ЭТУ СТРОКУ
} catch (e) {
console.warn("MyBB SDK: Не удалось получить детали пользователя/темы.", e);
if (!userName) userName = "Игрок"; // Fallback, если SDK не сработал
}
}
// Запрашиваем ник у гостя, если не удалось получить его с форума
if (!userName || userName === "Гость") {
userName = prompt("Введите ваш ник:") || "Игрок";
}
localStorage.setItem("slotUserName", userName);
document.getElementById("userName").textContent = userName;
document.getElementById("userCoins").textContent = userCoins;
// Инициализируем таймер
updateTimer();
}
/* --- Функции Кулдауна --- */
const canSpin = () => {
if (COOLDOWN_MINUTES === 0) return true;
return Date.now() - (localStorage.getItem("slotLastSpin") || 0) >= COOLDOWN_MS;
};
const updateTimer = () => {
let last = localStorage.getItem("slotLastSpin") || 0;
let diff = COOLDOWN_MS - (Date.now() - last);
if (diff <= 0 || COOLDOWN_MINUTES === 0) {
timer.textContent = "готово ✅";
spinBtn.disabled = false;
return;
}
let minutes = Math.floor(diff/60000);
let seconds = Math.floor((diff%60000)/1000);
timer.textContent = `${minutes}м ${seconds}с`;
spinBtn.disabled = true;
};
setInterval(updateTimer, 1000);
/* --- Функция Спина --- */
spinBtn.onclick = async function() {
if(!canSpin()){
alert("Подождите, ваш следующий ход будет через: " + timer.textContent);
return;
}
// 1. Рандомизация
let results = ids.map(() => symbols[Math.floor(Math.random() * symbols.length)]);
ids.forEach((id, i) => displayResult(id, results[i]));
// 2. Установка кулдауна
localStorage.setItem("slotLastSpin", Date.now());
updateTimer();
// 3. Анализ и логика выигрыша
let counts = {};
results.forEach(s => counts[s] = (counts[s] || 0) + 1);
let reward = 0;
let winText = "Попробуй еще";
let color = "#7c653f";
let winDescription = "";
const [s0, s1, s2, s3] = symbols; // Сокращения для удобства
if (Object.values(counts).includes(3)) {
// 3 ОДИНАКОВЫХ
reward = 500;
winText = "�� ДЖЕКПОТ! Три одинаковых символа!";
color = "#ffd700";
winDescription = `�� ДЖЕКПОТ! Тройка! Выпало: -- -- -- — ВЫИГРЫШ: ${reward}!`;
} else if (Object.values(counts).includes(2)) {
// ПАРА
reward = 100;
winText = "✨ Пара! Получи бонус!";
color = "#c0a060";
winDescription = `�� Пара! Выпало: -- -- -- — ВЫИГРЫШ: ${reward}.`;
} else if (results.includes(s0) && results.includes(s1) && results.includes(s3)) {
// КОМБО: Кристалл (0), Корона (1), Сердце (3) - нет Черепа
reward = 300;
winText = "�� КОМБО! Кристалл, Корона, Сердце!";
color = "#ff6a9a";
winDescription = `�� Комбо (0, 1, 3)! Выпало: -- -- -- — ВЫИГРЫШ: ${reward}.`;
} else if (!results.includes(s2)) {
// КОМБО: НЕТ ЧЕРЕПА (2)
reward = 50;
winText = "�� Удача! Ни одного Черепа!";
color = "#32cd32";
winDescription = `�� Удача! Выпало: -- -- -- — ВЫИГРЫШ: ${reward}.`;
}
// 4. Обновление баланса и интерфейса
userCoins += reward;
localStorage.setItem("slotCoins", userCoins);
document.getElementById("userCoins").textContent = userCoins;
winDisplay.innerHTML = reward > 0 ? `<span class="winGlow">Победа!</span><br>${winText}` : winText;
// 5. Автоматическая отправка сообщения на форум при выигрыше
if (reward > 0 && threadID && typeof MYBB !== 'undefined' && MYBB.thread) {
// Используем и [img] для BB-кода, чтобы пост был красивым
const postContent = `
[center][b]Я только что крутил Слот-Машину и выиграл ${reward} монет!
${winDescription}
Мой текущий баланс: ${userCoins}
[/center]
`;
try {
// Отправка сообщения
await MYBB.thread.reply(threadID, postContent);
} catch (error) {
console.error("Ошибка при отправке сообщения:", error);
// Если не удалось отправить, сообщаем пользователю
alert(`Выигрыш ${reward} получен, но не удалось отправить сообщение на форум. Проверьте, авторизованы ли вы.`);
}
}
};
// Запуск инициализации
initialize();
</script>
[/html]
Поделиться112025-11-08 00:29:31
[html]
<script src="https://cdn.jsdelivr.net/npm/@quadrosystems/mybb-sdk@0.9.7/lib/index.min.js"></script>
<div id="slotMachine" style="width:90%; max-width:600px; background:#1c1b1b; color:#fff; border-radius:10px; padding:2%; text-align:center; font-family:Georgia; margin:2% auto; box-shadow:0 0 10px rgba(0,0,0,0.6); position:relative;">
<div style="position:absolute; top:8px; left:8px; font-size:0.9em; color:#ffdd8a; text-align:left;">
👤 <span id="userName">Гость</span><br>
💰 <span id="userCoins">0</span>
</div>
<b style="font-size:1.3em;">🎰 Слот-машина</b>
<div style="margin-top:1%; font-size:0.9em; color:#d9c7a1;">
⏳ Новый спин через: <span id="timer" style="font-weight:bold; color:#ffdd8a;"></span>
</div>
<div style="display:flex; justify-content:center; gap:2%; margin:3% 0;">
<div class="slotCell" id="slot1" style="flex:1; aspect-ratio:1; background:#000; border:0.3% solid #7c653f; border-radius:8px; display:flex; align-items:center; justify-content:center;"></div>
<div class="slotCell" id="slot2" style="flex:1; aspect-ratio:1; background:#000; border:0.3% solid #7c653f; border-radius:8px; display:flex; align-items:center; justify-content:center;"></div>
<div class="slotCell" id="slot3" style="flex:1; aspect-ratio:1; background:#000; border:0.3% solid #7c653f; border-radius:8px; display:flex; align-items:center; justify-content:center;"></div>
</div>
<button id="spinBtn" style="padding:1% 3%; cursor:pointer; background:#b8863b; color:#fff; border:0.3% solid #000; font-weight:bold; border-radius:6px; box-shadow:0 0 5px rgba(0,0,0,0.6);">
Крутить 🎲
</button>
<div id="winDisplay" style="margin-top:12px; font-size:1.1em; font-weight:bold; color:#ffdd00; min-height:2.2em;"></div>
</div>
<style>
.winGlow { animation: glowPulse 1s infinite alternate; }
@keyframes glowPulse {
from { text-shadow: 0 0 6px #ffd700; color:#fff2b8; }
to { text-shadow: 0 0 18px #ffd700, 0 0 36px #ffb700; color:#fff9db; }
}
</style>
<script>
/* --- НАСТРОЙКИ --- */
const COOLDOWN_MINUTES = 0; // <-- УСТАНОВИТЬ ВРУЧНУЮ
const COOLDOWN_MS = COOLDOWN_MINUTES * 60000;
/* Символы */
const symbols = [
"https://upforme.ru/uploads/001c/84/76/2/793146.png", // 💎
"https://upforme.ru/uploads/001c/84/76/2/635838.png", // 👑
"https://upforme.ru/uploads/001c/84/76/2/604915.png", // 💀
"https://upforme.ru/uploads/001c/84/76/2/703897.png" // ❤️
];
/* ----------------- */
const ids = ["slot1", "slot2", "slot3"];
const timer = document.getElementById("timer");
const spinBtn = document.getElementById("spinBtn");
const winDisplay = document.getElementById("winDisplay");
let threadID = null;
let isUserLoggedIn = false; // Новый флаг
let userName = localStorage.getItem("slotUserName");
let userCoins = parseInt(localStorage.getItem("slotCoins") || "0");
const displayResult = (id, url) => {
document.getElementById(id).innerHTML = `<img src="${url}" style="max-width:80%;max-height:80%;">`;
};
// Асинхронная инициализация пользователя и получение ID темы
async function initialize() {
if (typeof MYBB !== 'undefined' && MYBB.user && MYBB.thread) {
try {
const userDetails = await MYBB.user.getDetails();
// Проверяем, авторизован ли пользователь (uid > 0)
isUserLoggedIn = userDetails.uid && userDetails.uid > 0;
// Устанавливаем ник
userName = userDetails.username || userName || (isUserLoggedIn ? "Игрок" : "Гость");
// Получаем ID темы
const threadDetails = await MYBB.thread.getDetails();
threadID = threadDetails.tid;
console.log(`[СЛОТ] User logged in: ${isUserLoggedIn}, Thread ID: ${threadID}`);
} catch (e) {
console.warn("[СЛОТ] MyBB SDK: Не удалось получить детали пользователя/темы.", e);
}
}
// Запрашиваем ник у гостя, если не удалось получить его с форума и он не был сохранен
if (!userName || userName === "Гость") {
userName = prompt("Введите ваш ник:") || "Игрок";
}
localStorage.setItem("slotUserName", userName);
document.getElementById("userName").textContent = userName;
document.getElementById("userCoins").textContent = userCoins;
updateTimer();
}
/* --- Функции Кулдауна (без изменений) --- */
const canSpin = () => {
if (COOLDOWN_MINUTES === 0) return true;
return Date.now() - (localStorage.getItem("slotLastSpin") || 0) >= COOLDOWN_MS;
};
const updateTimer = () => {
let last = localStorage.getItem("slotLastSpin") || 0;
let diff = COOLDOWN_MS - (Date.now() - last);
if (diff <= 0 || COOLDOWN_MINUTES === 0) {
timer.textContent = "готово ✅";
spinBtn.disabled = false;
return;
}
let minutes = Math.floor(diff/60000);
let seconds = Math.floor((diff%60000)/1000);
timer.textContent = `${minutes}м ${seconds}с`;
spinBtn.disabled = true;
};
setInterval(updateTimer, 1000);
/* --- Функция Спина --- */
spinBtn.onclick = async function() {
if(!canSpin()){
alert("Подождите, ваш следующий ход будет через: " + timer.textContent);
return;
}
// 1. Рандомизация
let results = ids.map(() => symbols[Math.floor(Math.random() * symbols.length)]);
ids.forEach((id, i) => displayResult(id, results[i]));
// 2. Установка кулдауна
localStorage.setItem("slotLastSpin", Date.now());
updateTimer();
// 3. Анализ и логика выигрыша
let counts = {};
results.forEach(s => counts[s] = (counts[s] || 0) + 1);
let reward = 0;
let winText = "Попробуй еще";
let color = "#7c653f";
let winDescription = "";
const [s0, s1, s2, s3] = symbols;
// (Логика выигрыша - сохранена без изменений)
if (Object.values(counts).includes(3)) {
reward = 500;
winText = "🎉 ДЖЕКПОТ! Три одинаковых символа!";
color = "#ffd700";
winDescription = `🎰 ДЖЕКПОТ! Тройка! Выпало: -- -- -- — ВЫИГРЫШ: ${reward}!`;
} else if (Object.values(counts).includes(2)) {
reward = 100;
winText = "✨ Пара! Получи бонус!";
color = "#c0a060";
winDescription = `🎰 Пара! Выпало: -- -- -- — ВЫИГРЫШ: ${reward}.`;
} else if (results.includes(s0) && results.includes(s1) && results.includes(s3)) {
reward = 300;
winText = "🔥 КОМБО! Кристалл, Корона, Сердце!";
color = "#ff6a9a";
winDescription = `🎰 Комбо (0, 1, 3)! Выпало: -- -- -- — ВЫИГРЫШ: ${reward}.`;
} else if (!results.includes(s2)) {
reward = 50;
winText = "🍀 Удача! Ни одного Черепа!";
color = "#32cd32";
winDescription = `🎰 Удача! Выпало: -- -- -- — ВЫИГРЫШ: ${reward}.`;
}
// 4. Обновление баланса и интерфейса
userCoins += reward;
localStorage.setItem("slotCoins", userCoins);
document.getElementById("userCoins").textContent = userCoins;
winDisplay.innerHTML = reward > 0 ? `<span class="winGlow">Победа!</span><br>${winText}` : winText;
// 5. Автоматическая отправка сообщения на форум при выигрыше
if (reward > 0) {
if (!isUserLoggedIn) {
winDisplay.innerHTML += `<br><span style="color:#f00; font-size:0.8em;">(Для постинга на форуме нужно авторизоваться!)</span>`;
console.warn("[СЛОТ] Постинг невозможен: Пользователь не авторизован.");
return;
}
if (!threadID) {
winDisplay.innerHTML += `<br><span style="color:#f00; font-size:0.8em;">(Ошибка ID темы. Постинг невозможен.)</span>`;
console.error("[СЛОТ] Постинг невозможен: Не удалось получить ID темы.");
return;
}
const postContent = `
[center]Я только что крутил Слот-Машину и выиграл ${reward} монет!
${winDescription}
Мой текущий баланс: ${userCoins}
[/center]
`;
try {
// ПРОВЕРКА: Здесь происходит попытка отправки
await MYBB.thread.reply(threadID, postContent);
winDisplay.innerHTML += `<br><span style="color:#0f0; font-size:0.8em;">Сообщение успешно отправлено!</span>`;
} catch (error) {
console.error("[СЛОТ] Критическая ошибка при отправке сообщения:", error);
winDisplay.innerHTML += `<br><span style="color:#f00; font-size:0.8em;">(Сбой постинга. Проверьте консоль F12!)</span>`;
}
}
};
// Запуск инициализации
initialize();
</script>
[/html]
Поделиться122025-11-08 00:52:24
[html]
<script>
/**
* Skipped minification because the original files appears to be already minified.
* Original file: /npm/@quadrosystems/mybb-sdk@0.9.7/lib/index.js
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("cross-fetch")):"function"==typeof define&&define.amd?define(["cross-fetch"],e):"object"==typeof exports?exports.MybbSDK=e(require("cross-fetch")):t.MybbSDK=e(t.fetch)}(this,(function(t){return(()=>{"use strict";var e={607:function(t,e,o){var r=this&&this.__awaiter||function(t,e,o,r){return new(o||(o=Promise))((function(n,i){function s(t){try{a(r.next(t))}catch(t){i(t)}}function u(t){try{a(r.throw(t))}catch(t){i(t)}}function a(t){var e;t.done?n(t.value)
e=t.value,e instanceof o?e:new o((function(t){t(e)}))).then(s,u)}a((r=r.apply(t,e||[])).next())}))},n=this&&this.__generator||function(t,e){var o,r,n,i,s={label:0,sent:function(){if(1&n[0])throw n[1];return n[1]},trys:[],ops:[]};return i={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function u(i){return function(u){return function(i){if(o)throw new TypeError("Generator is already executing.");for(;s;)try{if(o=1,r&&(n=2&i[0]?r.return:i[0]?r.throw||((n=r.return)&&n.call(r),0):r.next)&&!(n=n.call(r,i[1])).done)return n;switch(r=0,n&&(i=[2&i[0],n.value]),i[0]){case 0:case 1:n=i;break;case 4:return s.label++,{value:i[1],done:!1};case 5:s.label++,r=i[1],i=[0];continue;case 7:i=s.ops.pop(),s.trys.pop();continue;default:if(!((n=(n=s.trys).length>0&&n[n.length-1])||6!==i[0]&&2!==i[0])){s=0;continue}if(3===i[0]&&(!n||i[1]>n[0]&&i[1]<n[3])){s.label=i[1];break}if(6===i[0]&&s.label<n[1]){s.label=n[1],n=i;break}if(n&&s.label<n[2]){s.label=n[2],s.ops.push(i);break}n[2]&&s.ops.pop(),s.trys.pop();continue}i=e.call(t,s)}catch(t){i=[6,t],r=0}finally{o=n=0}if(5&i[0])throw i[1];return{value:i[0]?i[1]:void 0,done:!0}}([i,u])}}},i=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0});var s=i(o(831)),u=function(){function t(t,e){this.originalHost=t,!t&&void 0!==typeof window&&document.location.origin&&(this.originalHost=document.location.origin),this.path="/api.php",this.format=e&&e.format||"json",this.charset=e&&e.charset||"utf-8",this.init()}return t.prototype.init=function(){this.parseUrl(this.originalHost)},t.prototype.parseUrl=function(t){if(!t)throw new Error("Hostname not specified and should be a string");var e=/^(((([^:\/#\?]+
?(?
\/\/)((?
([^:@\/#\?]+)(?:\
[^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\
[0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/.exec(t);if(this.hostname=e[11],this.protocol=e[4],!this.hostname)throw new Error("Cannot get hostname from arguments")},t.prototype.setToken=function(t){return this.token=t,this},t.prototype.withHash=function(t){return this.hash=t,this},t.prototype.getBoard=function(t){return this.call("board.get",t)},t.prototype.getForums=function(t){return this.call("board.getForums",t)},t.prototype.getFunds=function(){return this.call("board.getFunds")},t.prototype.getSubscriptions=function(t){return this.call("board.getSubscriptions",t)},t.prototype.getFileMetadata=function(t){return this.call("board.getFileMetadata",t)},t.prototype.auth=function(t,e){return this.call("board.auth",{login:t,password:e})},t.prototype.getUsers=function(t){return this.call("users.get",t)},t.prototype.getOnlineUsers=function(t){return this.call("users.getOnline",t)},t.prototype.getRespect=function(t){return this.call("users.getRespect",t)},t.prototype.getTopics=function(t){return this.call("topic.get",t)},t.prototype.getRecent=function(t){return this.call("topic.getRecent",t)},t.prototype.getPosts=function(t){return this.call("post.get",t)},t.prototype.getPostVotes=function(t){return this.call("post.getVotes",t)},t.prototype.storageSet=function(t,e,o){return this.call("storage.set",{token:this.token,key:t,value:e,action:o})},t.prototype.storageGet=function(t){return this.call("storage.get",{token:this.token,key:t})},t.prototype.storageKeys=function(){return this.call("storage.keys")},t.prototype.storageDelete=function(t){return this.call("storage.delete",{token:this.token,key:t})},t.prototype.storageFlush=function(){return this.call("storage.flush",{token:this.token})},t.prototype.call=function(t,e,o,i){return r(this,void 0,void 0,(function(){var r,s;return n(this,(function(n){switch(n.label){case 0:return r={host:this.hostname,port:"http:"===this.protocol?80:443,path:this.path,apiMethod:t,method:"GET"},[4,this.request(r,e)];case 1:if((s=n.sent()).response)return"function"==typeof o&&o(s.response),[2,s.response];if(s.error){if(console.error(s),"function"!=typeof i)throw new Error(s.error);i(s.error)}return[2]}}))}))},t.prototype.request=function(t,e){return r(this,void 0,void 0,(function(){var o,r,i,u,a=this;return n(this,(function(n){for(r in(o=[]).push("method="+t.apiMethod),o.push("format="+this.format),o.push("charset="+this.charset),e)if(e.hasOwnProperty(r)){if(void 0===e[r])continue;i=Array.isArray(e[r])?e[r].map(encodeURIComponent).join(","):encodeURIComponent(e[r]),o.push(r+"="+i)}return u=[t.protocol,"//",t.host,t.path,"?",o.join("&")].join(""),[2,(0,s.default)(u,{method:t.method||"GET",headers:{Cookie:this.hash?"mybb_ru="+this.hash:void 0},mode:"cors",credentials:"same-origin"}).then((function(t){return"json"===a.format?t.json():t.text()}))]}))}))},t}();e.default=u},831:e=>{e.exports=t}},o={},r=function t(r){var n=o[r];if(void 0!==n)return n.exports;var i=o[r]={exports:{}};return e[r].call(i.exports,i,i.exports,t),i.exports}(607);return r.default})()}));
</script>
<div style="margin: 20px; padding: 15px; border: 2px dashed #00bfff; background: #f0f8ff; text-align: center;">
<p style="font-weight: bold;">Тест Функции Постинга</p>
<button id="testPostButton" style="padding: 10px 20px; background: #00bfff; color: white; border: none; cursor: pointer; border-radius: 5px;" disabled>
Нажать для Отправки Тестового Сообщения
</button>
<p id="testStatus" style="margin-top: 10px; min-height: 20px; color: #333;">Инициализация...</p>
</div>
<script>
window.onload = function() {
let testThreadID = null;
const statusDisplay = document.getElementById("testStatus");
const testButton = document.getElementById("testPostButton");
// Функция инициализации, которая теперь гарантированно видит MYBB
async function initializeTest() {
if (typeof MYBB === 'undefined' || typeof MYBB.thread === 'undefined') {
statusDisplay.textContent = "Критическая ошибка: MYBB не определен после внедрения.";
return; // Сбой, если внедрение не сработало
}
try {
const threadDetails = await MYBB.thread.getDetails();
testThreadID = threadDetails.tid;
if (testThreadID) {
statusDisplay.textContent = `Готово к тесту! ID Темы: ${testThreadID}.`;
testButton.disabled = false;
} else {
statusDisplay.textContent = "Ошибка: Не удалось получить ID темы.";
}
} catch (e) {
statusDisplay.textContent = "Ошибка SDK: Проверьте, авторизованы ли вы.";
console.error("SDK Error:", e);
}
}
// Функция отправки сообщения (без изменений)
testButton.onclick = async function() {
if (!testThreadID) {
statusDisplay.textContent = "Ошибка: ID темы не установлен.";
return;
}
testButton.disabled = true;
statusDisplay.textContent = "Отправка сообщения...";
const testContent = `
[center]✅ АВТОМАТИЧЕСКИЙ ТЕСТ ПОСТИНГА УСПЕШЕН!
Сообщение отправлено скриптом в ${new Date().toLocaleTimeString()}
[/center]
`;
try {
await MYBB.thread.reply(testThreadID, testContent);
statusDisplay.style.color = 'green';
statusDisplay.textContent = "УСПЕХ! Тестовое сообщение отправлено на форум.";
} catch (error) {
statusDisplay.style.color = 'red';
statusDisplay.textContent = "СБОЙ! Проверьте, авторизованы ли вы и консоль F12.";
console.error("Постинг СБОЙ:", error);
} finally {
testButton.disabled = false;
}
};
initializeTest();
};
</script>
[/html]
После прямого внедрения, вы должны увидеть либо **"Готово к тесту!"** (и кнопка станет активной), либо новую ошибку (например, `403 Forbidden` при попытке постинга), что будет означать, что вы наконец-то установили связь с SDK.
Поделиться132025-11-08 01:28:41
[html]
<div id="slotMachine" style="width:90%; max-width:600px; background:#1c1b1b; color:#fff; border-radius:10px; padding:2%; text-align:center; font-family:Georgia; margin:2% auto; box-shadow:0 0 10px rgba(0,0,0,0.6); position:relative;">
<div style="position:absolute; top:8px; left:8px; font-size:0.9em; color:#ffdd8a; text-align:left;">
�� <span id="userName">Гость</span><br>
�� <span id="userCoins">0</span>
</div>
<b style="font-size:1.3em;">�� Слот-машина</b>
<div style="margin-top:1%; font-size:0.9em; color:#d9c7a1;">
⏳ Новый спин через: <span id="timer" style="font-weight:bold; color:#ffdd8a;"></span>
</div>
<div style="display:flex; justify-content:center; gap:2%; margin:3% 0;">
<div class="slotCell" id="slot1" style="flex:1; aspect-ratio:1; background:#000; border:0.3% solid #7c653f; border-radius:8px; display:flex; align-items:center; justify-content:center;"></div>
<div class="slotCell" id="slot2" style="flex:1; aspect-ratio:1; background:#000; border:0.3% solid #7c653f; border-radius:8px; display:flex; align-items:center; justify-content:center;"></div>
<div class="slotCell" id="slot3" style="flex:1; aspect-ratio:1; background:#000; border:0.3% solid #7c653f; border-radius:8px; display:flex; align-items:center; justify-content:center;"></div>
</div>
<button id="spinBtn" style="padding:1% 3%; cursor:pointer; background:#b8863b; color:#fff; border:0.3% solid #000; font-weight:bold; border-radius:6px; box-shadow:0 0 5px rgba(0,0,0,0.6);">
Крутить ��
</button>
<div id="winDisplay" style="margin-top:12px; font-size:1.1em; font-weight:bold; color:#ffdd00; min-height:2.2em;"></div>
</div>
<style>
.winGlow { animation: glowPulse 1s infinite alternate; }
@keyframes glowPulse {
from { text-shadow: 0 0 6px #ffd700; color:#fff2b8; }
to { text-shadow: 0 0 18px #ffd700, 0 0 36px #ffb700; color:#fff9db; }
}
</style>
<script>
/* --- НАСТРОЙКИ --- */
const COOLDOWN_MINUTES = 0; // <-- УСТАНОВИТЬ ВРУЧНУЮ
const COOLDOWN_MS = COOLDOWN_MINUTES * 60000;
/* Символы */
const symbols = [
"https://upforme.ru/uploads/001c/84/76/2/793146.png", //
"https://upforme.ru/uploads/001c/84/76/2/635838.png", //
"https://upforme.ru/uploads/001c/84/76/2/604915.png", //
"https://upforme.ru/uploads/001c/84/76/2/703897.png" // ❤️
];
/* ----------------- */
const ids = ["slot1", "slot2", "slot3"];
const timer = document.getElementById("timer");
const spinBtn = document.getElementById("spinBtn");
const winDisplay = document.getElementById("winDisplay");
let threadID = null;
let isUserLoggedIn = false; // Новый флаг
let userName = localStorage.getItem("slotUserName");
let userCoins = parseInt(localStorage.getItem("slotCoins") || "0");
const displayResult = (id, url) => {
document.getElementById(id).innerHTML = `<img src="${url}" style="max-width:80%;max-height:80%;">`;
};
// Асинхронная инициализация пользователя и получение ID темы
async function initialize() {
if (typeof MYBB !== 'undefined' && MYBB.user && MYBB.thread) {
try {
const userDetails = await MYBB.user.getDetails();
// Проверяем, авторизован ли пользователь (uid > 0)
isUserLoggedIn = userDetails.uid && userDetails.uid > 0;
// Устанавливаем ник
userName = userDetails.username || userName || (isUserLoggedIn ? "Игрок" : "Гость");
// Получаем ID темы
const threadDetails = await MYBB.thread.getDetails();
threadID = threadDetails.tid;
console.log(`[СЛОТ] User logged in: ${isUserLoggedIn}, Thread ID: ${threadID}`);
} catch (e) {
console.warn("[СЛОТ] MyBB SDK: Не удалось получить детали пользователя/темы.", e);
}
}
// Запрашиваем ник у гостя, если не удалось получить его с форума и он не был сохранен
if (!userName || userName === "Гость") {
userName = prompt("Введите ваш ник:") || "Игрок";
}
localStorage.setItem("slotUserName", userName);
document.getElementById("userName").textContent = userName;
document.getElementById("userCoins").textContent = userCoins;
updateTimer();
}
/* --- Функции Кулдауна (без изменений) --- */
const canSpin = () => {
if (COOLDOWN_MINUTES === 0) return true;
return Date.now() - (localStorage.getItem("slotLastSpin") || 0) >= COOLDOWN_MS;
};
const updateTimer = () => {
let last = localStorage.getItem("slotLastSpin") || 0;
let diff = COOLDOWN_MS - (Date.now() - last);
if (diff <= 0 || COOLDOWN_MINUTES === 0) {
timer.textContent = "готово ✅";
spinBtn.disabled = false;
return;
}
let minutes = Math.floor(diff/60000);
let seconds = Math.floor((diff%60000)/1000);
timer.textContent = `${minutes}м ${seconds}с`;
spinBtn.disabled = true;
};
setInterval(updateTimer, 1000);
/* --- Функция Спина --- */
spinBtn.onclick = async function() {
if(!canSpin()){
alert("Подождите, ваш следующий ход будет через: " + timer.textContent);
return;
}
// 1. Рандомизация
let results = ids.map(() => symbols[Math.floor(Math.random() * symbols.length)]);
ids.forEach((id, i) => displayResult(id, results[i]));
// 2. Установка кулдауна
localStorage.setItem("slotLastSpin", Date.now());
updateTimer();
// 3. Анализ и логика выигрыша
let counts = {};
results.forEach(s => counts[s] = (counts[s] || 0) + 1);
let reward = 0;
let winText = "Попробуй еще";
let color = "#7c653f";
let winDescription = "";
const [s0, s1, s2, s3] = symbols;
// (Логика выигрыша - сохранена без изменений)
if (Object.values(counts).includes(3)) {
reward = 500;
winText = " ДЖЕКПОТ! Три одинаковых символа!";
color = "#ffd700";
winDescription = ` ДЖЕКПОТ! Тройка! Выпало: -- -- -- — ВЫИГРЫШ: ${reward}!`;
} else if (Object.values(counts).includes(2)) {
reward = 100;
winText = "✨ Пара! Получи бонус!";
color = "#c0a060";
winDescription = ` Пара! Выпало: -- -- -- — ВЫИГРЫШ: ${reward}.`;
} else if (results.includes(s0) && results.includes(s1) && results.includes(s3)) {
reward = 300;
winText = " КОМБО! Кристалл, Корона, Сердце!";
color = "#ff6a9a";
winDescription = ` Комбо (0, 1, 3)! Выпало: -- -- -- — ВЫИГРЫШ: ${reward}.`;
} else if (!results.includes(s2)) {
reward = 50;
winText = " Удача! Ни одного Черепа!";
color = "#32cd32";
winDescription = ` Удача! Выпало: -- -- -- — ВЫИГРЫШ: ${reward}.`;
}
// 4. Обновление баланса и интерфейса
userCoins += reward;
localStorage.setItem("slotCoins", userCoins);
document.getElementById("userCoins").textContent = userCoins;
winDisplay.innerHTML = reward > 0 ? `<span class="winGlow">Победа!</span><br>${winText}` : winText;
// 5. Автоматическая отправка сообщения на форум при выигрыше
if (reward > 0) {
if (!isUserLoggedIn) {
winDisplay.innerHTML += `<br><span style="color:#f00; font-size:0.8em;">(Для постинга на форуме нужно авторизоваться!)</span>`;
console.warn("[СЛОТ] Постинг невозможен: Пользователь не авторизован.");
return;
}
if (!threadID) {
winDisplay.innerHTML += `<br><span style="color:#f00; font-size:0.8em;">(Ошибка ID темы. Постинг невозможен.)</span>`;
console.error("[СЛОТ] Постинг невозможен: Не удалось получить ID темы.");
return;
}
const postContent = `
[center]Я только что крутил Слот-Машину и выиграл ${reward} монет!
${winDescription}
Мой текущий баланс: ${userCoins}
[/center]
`;
try {
// ПРОВЕРКА: Здесь происходит попытка отправки
await MYBB.thread.reply(threadID, postContent);
winDisplay.innerHTML += `<br><span style="color:#0f0; font-size:0.8em;">Сообщение успешно отправлено!</span>`;
} catch (error) {
console.error("[СЛОТ] Критическая ошибка при отправке сообщения:", error);
winDisplay.innerHTML += `<br><span style="color:#f00; font-size:0.8em;">(Сбой постинга. Проверьте консоль F12!)</span>`;
}
}
};
// Запуск инициализации
initialize();
</script>
[/html]
После этих шагов ваш код слота должен наконец-то увидеть объект `MYBB` и выполнить функцию отправки сообщения без конфликтов.
Поделиться142025-11-08 02:06:05
[html]
<div id="lotteryMachine" style="width:90%; max-width:350px; background:#1c1b1b; color:#fff; border-radius:10px; padding:2%; text-align:center; font-family:Georgia; margin:2% auto; box-shadow:0 0 10px rgba(0,0,0,0.6);">
<div style="font-size:0.9em; color:#ffdd8a; text-align:right;">
�� <span id="userName">Гость</span> | �� <span id="userCoins">0</span>
</div>
<b style="font-size:1.3em;">�� Лотерейные Ячейки</b>
<div style="margin-top:10px; font-size:0.9em; color:#d9c7a1;">
⏳ Новый ход через: <span id="timer" style="font-weight:bold; color:#ffdd8a;"></span>
</div>
<table id="lotteryGrid" style="width:100%; border-collapse:collapse; margin:15px 0;">
<tbody>
<tr>
<td data-id="1" class="lot-cell">1</td>
<td data-id="2" class="lot-cell">2</td>
<td data-id="3" class="lot-cell">3</td>
</tr>
<tr>
<td data-id="4" class="lot-cell">4</td>
<td data-id="5" class="lot-cell">5</td>
<td data-id="6" class="lot-cell">6</td>
</tr>
<tr>
<td data-id="7" class="lot-cell">7</td>
<td data-id="8" class="lot-cell">8</td>
<td data-id="9" class="lot-cell">9</td>
</tr>
</tbody>
</table>
<div id="resultDisplay" style="margin-top:12px; font-size:1.1em; font-weight:bold; color:#ffdd00; min-height:2.2em;">Нажмите на ячейку!</div>
</div>
<style>
#lotteryGrid td {
width: 33.33%;
aspect-ratio: 1; /* Делает ячейки квадратными */
border: 3px solid #7c653f;
background: #282828;
color: #fff;
font-size: 1.5em;
font-weight: bold;
cursor: pointer;
transition: background 0.2s, transform 0.1s;
user-select: none;
}
#lotteryGrid td:hover:not(.disabled) {
background: #3a3a3a;
}
#lotteryGrid td.disabled {
cursor: not-allowed;
opacity: 0.5;
}
.winGlow { animation: glowPulse 1s infinite alternate; }
@keyframes glowPulse {
from { text-shadow: 0 0 6px #ffd700; color:#fff2b8; }
to { text-shadow: 0 0 18px #ffd700, 0 0 36px #ffb700; color:#fff9db; }
}
</style>
<script>
/* --- НАСТРОЙКИ --- */
const COOLDOWN_MINUTES = 0;
const COOLDOWN_MS = COOLDOWN_MINUTES * 60000;
const REWARD_AMOUNT = 500;
/* ----------------- */
const timer = document.getElementById("timer");
const resultDisplay = document.getElementById("resultDisplay");
const lotteryGrid = document.getElementById("lotteryGrid");
const cells = document.querySelectorAll('#lotteryGrid td');
let threadID = null;
let isUserLoggedIn = false;
let userName = localStorage.getItem("slotUserName");
let userCoins = parseInt(localStorage.getItem("slotCoins") || "0");
let winningCellID = 0; // Новая переменная для хранения выигрышной ячейки
// Функция для установки класса "disabled"
const setCellsDisabled = (isDisabled) => {
cells.forEach(cell => {
if (isDisabled) {
cell.classList.add('disabled');
cell.onclick = null;
} else {
cell.classList.remove('disabled');
cell.onclick = () => handleLotteryClick(parseInt(cell.dataset.id));
}
});
};
} else if (isWinner) {
resultDisplay.innerHTML += `<br><span style="color:#f00; font-size:0.8em;">(Войдите, чтобы запостить результат!)</span>`;
}
// Асинхронная инициализация пользователя и получение ID темы
async function initialize() {
// ... (весь ваш код инициализации из прошлого ответа) ...
if (typeof MYBB !== 'undefined' && MYBB.user && MYBB.thread) {
try {
const userDetails = await MYBB.user.getDetails();
isUserLoggedIn = userDetails.uid && userDetails.uid > 0;
userName = userDetails.username || userName || (isUserLoggedIn ? "Игрок" : "Гость");
const threadDetails = await MYBB.thread.getDetails();
threadID = threadDetails.tid;
} catch (e) {
console.warn("[ЛОТЕРЕЯ] MyBB SDK: Не удалось получить детали пользователя/темы.", e);
}
}
if (!userName || userName === "Гость") {
userName = prompt("Введите ваш ник:") || "Игрок";
}
localStorage.setItem("slotUserName", userName);
document.getElementById("userName").textContent = userName;
document.getElementById("userCoins").textContent = userCoins;
// Определяем выигрышную ячейку (только один раз за сессию/ход)
winningCellID = Math.floor(Math.random() * 9) + 1;
console.log("Winning Cell ID:", winningCellID); // Для проверки в консоли
updateTimer();
}
/* --- Функции Кулдауна --- */
const canClick = () => {
if (COOLDOWN_MINUTES === 0) return true;
return Date.now() - (localStorage.getItem("slotLastSpin") || 0) >= COOLDOWN_MS;
};
const updateTimer = () => {
let last = localStorage.getItem("slotLastSpin") || 0;
let diff = COOLDOWN_MS - (Date.now() - last);
if (diff <= 0 || COOLDOWN_MINUTES === 0) {
timer.textContent = "готово ✅";
setCellsDisabled(false); // Активируем ячейки
return;
}
let minutes = Math.floor(diff/60000);
let seconds = Math.floor((diff%60000)/1000);
timer.textContent = `${minutes}м ${seconds}с`;
setCellsDisabled(true); // Отключаем ячейки
};
setInterval(updateTimer, 1000);
/* --- Функция обработки клика --- */
async function handleLotteryClick(cellId) {
if (!canClick()) {
alert("Подождите, ваш следующий ход будет через: " + timer.textContent);
return;
}
setCellsDisabled(true); // Блокируем сразу после клика
const isWinner = cellId === winningCellID;
let reward = isWinner ? REWARD_AMOUNT : 0;
// Визуальное отображение результата
cells.forEach(cell => {
cell.style.background = '#404040'; // Закрашиваем все
});
const clickedCell = document.querySelector(`#lotteryGrid td[data-id="${cellId}"]`);
clickedCell.style.background = isWinner ? '#0f0' : '#f00'; // Выделяем результат
clickedCell.innerHTML = isWinner ? '��' : '❌';
resultDisplay.innerHTML = isWinner ? `<span class="winGlow">ПОБЕДА! +${reward}</span>` : 'Провал! Попробуйте еще.';
// Обновление баланса и кулдауна
userCoins += reward;
localStorage.setItem("slotCoins", userCoins);
localStorage.setItem("slotLastSpin", Date.now());
document.getElementById("userCoins").textContent = userCoins;
updateTimer(); // Запускаем кулдаун
// 5. Постинг на форум
if (isUserLoggedIn && threadID) {
const statusText = isWinner ? `�� ВЫИГРЫШ! Я нашел сокровище в ячейке №${cellId} и выиграл ${reward} монет!` : `�� ПРОИГРЫШ. Я выбрал ячейку №${cellId}, но ничего не нашел.`;
const postContent = `
[center]Я только что испытал удачу в лотерее!
${statusText}
Мой текущий баланс: ${userCoins}
[/center]
`;
try {
await MYBB.thread.reply(threadID, postContent);
resultDisplay.innerHTML += `<br><span style="color:#0f0; font-size:0.8em;">(Сообщение отправлено!)</span>`;
} catch (error) {
console.error("[ЛОТЕРЕЯ] Ошибка постинга:", error);
resultDisplay.innerHTML += `<br><span style="color:#f00; font-size:0.8em;">(Сбой постинга. Проверьте F12.)</span>`;
}
} else if (isWinner) {
resultDisplay.innerHTML += `<br><span style="color:#f00; font-size:0.8em;">(Войдите, чтобы запостить результат!)</span>`;
}
}
// Запуск инициализации
initialize();
</script>
[/html]



















































