[html](function(){
const topicId = 4; // ID темы Rusff
const gameMaster = 'admin'; // Автор поста
const size = 10;
const letters = 'ABCDEFGHIJ'.split('');
const turnDelay = 2601000; // 2 минуты
const wrap = document.getElementById('battleship');
if(!wrap) return;
wrap.innerHTML = <style> #battleship table {border-collapse: collapse; margin:0 auto;} #battleship td {width:42px;height:42px;border:1px solid #444;text-align:center;vertical-align:middle;cursor:pointer;background:#b0d4ff;font-weight:bold;user-select:none;} #battleship td.hit {background:#f00 url(https://forumstatic.ru/files/0014/cc/0a/39265.png) no-repeat center/70%;} #battleship td.miss {background:#9bb;opacity:0.9;} #battleship th {background:#e0f0ff;padding:5px;} #reset-game {margin-top:10px;display:none;} </style> <h2>⚓ Морской бой</h2> <div id="battle-field"></div> <button id="reset-game" type="button">Сбросить поле</button> <p id="game-status" style="margin-top:10px;font-weight:bold;"></p> ;
// ---------------- Создаем поле ----------------
function createField(){
let html = '<table><tr><th></th>';
for(let i=1;i<=size;i++) html+='<th>'+i+'</th>';
html+='</tr>';
for(let r=0;r<size;r++){ html+="<tr><th>" th>';
for(let c=1;c<=size;c++){
html+='<td data-cell="'+letters[r]+c+'"></td>';
}
html+='';
}
html+='</table>';
const bf = document.getElementById('battle-field');
bf.innerHTML = html;
}
// ---------------- Расставляем корабли случайно ----------------
function placeShips(){
const shipsLen = [4,3,3,2,2,2,1,1,1,1];
const taken = new Set();
function randomDir(){return Math.random()<0.5?'h':'v';}
function canPlace(r,c,len,dir){
for(let i=0;i<len;i++){ let rr="r+(dir==='v'?i:0)," cc="c+(dir==='h'?i:0);">=size||cc>=size) return false;
const key=rr+''+cc;
if(taken.has(key)) return false;
for(let dr=-1;dr<=1;dr++)
for(let dc=-1;dc<=1;dc++)
if(taken.has((rr+dr)+''+(cc+dc))) return false;
}
return true;
}
const ships=[];
shipsLen.forEach(len=>{
let attempts=0;
let placed=false;
while(!placed && attempts<1000){
attempts++;
let r=Math.floor(Math.random()*size);
let c=Math.floor(Math.random()*size);
let dir=randomDir();
if(canPlace(r,c,len,dir)){
for(let i=0;i<len;i++){ let rr="r+(dir==='v'?i:0)," cc="c+(dir==='h'?i:0);" taken.add(rr+''+cc); ships.push(rr+''+cc); } placed="true;" if(!placed) console.warn('не удалось разместить корабль длины', len); }); return ships; ---------------- состояние игры state="{" ships: [], клетки кораблей (формат 'r_c') shots: {}, все выстрелы по ключу 'a1'="hit" |'miss' lastshot:{} время последнего выстрела игроков }; конвертируем ячейку function convertcell(cell){ const row="letters.indexOf(cell[0]);" col="parseInt(cell.slice(1))-1;" row+'_'+col; отправляем сообщение в тему posttotopic(msg, hit){ if(typeof ="=="′undefined′)return;="==" 'undefined') return;="=="′undefined′)return;.post(' misc.php?item="ajax_lottery'," { a:'message', id: topicid, msg: '[b]'+msg+'[ b]', color: hit?'red':'blue' }).fail(()>console.warn('Не удалось отправить пост в тему'));
const status = document.getElementById('game-status');
if(status) status.textContent = msg;
}
// ---------------- Рендер ----------------
function render(){
const cells = document.querySelectorAll('#battle-field td[data-cell]');
cells.forEach(td=>{
const cell=td.dataset.cell;
td.classList.remove('hit','miss');
if(state.shots[cell]==='miss') td.classList.add('miss');
else if(state.shots[cell]==='hit') td.classList.add('hit');
});
}
// ---------------- Обработка кликов ----------------
function activate(){
const bf = document.getElementById('battle-field');
if(!bf) return;
bf.addEventListener('click', function(e){
const td = e.target.closest('td[data-cell]');
if(!td) return;
// Безопасно получить имя пользователя
const user = (window.FORUM && FORUM.user && FORUM.user.name) ? FORUM.user.name : 'guest';
const now = Date.now();
if(state.lastShot[user] && now - state.lastShot[user]<turndelay){ 2 alert('можно стрелять раз в минуты.'); return; } const cell="td.dataset.cell;" if(state.shots[cell]) hit="state.ships.indexOf(convertCell(cell))" !="=" -1; state.shots[cell]="hit" ? 'hit':'miss'; state.lastshot[user]="now;" render(); savestate(); posttotopic('игрок '+user+' выстрелил '+cell+' — '+(hit?'попал!':'мимо.'), hit); }); показать кнопку сброса если пользователь мастер игры btn="document.getElementById('reset-game');" if(btn){ currentuser="(window.FORUM" && forum.user forum.user.name) forum.user.name : null; if(currentuser btn.style.display="inline-block" ; else { btn.addeventlistener('click', function(){ if(!confirm('сбросить поле и расставить корабли заново?')) state.ships="[];" state.shots="{};" state.lastshot="{};" posttotopic('⚓ сброшено администратором!', false); ---------------- сохраняем загружаем function savestate(){ if(typeof ="=="′undefined′)="==" 'undefined')="=="′undefined′).post(' misc.php?item="ajax_lottery'," a:'save', id: topicid, data: json.stringify(state) }).fail(()>console.warn('Не удалось сохранить состояние'));
}
function loadState(cb){
if(typeof $ === 'undefined'){ // если нет jQuery, инициализируем без загрузки
if(!state.ships.length) state.ships = placeShips();
if(cb) cb();
return;
}
$.get('/misc.php?item=ajax_lottery&id='+topicId+'&action=get').done(function(res){
try {
const parsed = typeof res === 'string' ? JSON.parse(res) : res;
if(parsed && parsed.ships) state = parsed;
} catch(e){
state = {ships:[],shots:{},lastShot:{}};
}
if(!state.ships || !state.ships.length) state.ships = placeShips();
if(cb) cb();
}).fail(function(){
state = {ships:[],shots:{},lastShot:{}};
state.ships = placeShips();
if(cb) cb();
});
}
// ---------------- Инициализация ----------------
createField();
loadState(function(){
render();
activate();
});
})();[/html]
бой
Сообщений 1 страница 2 из 2
Поделиться12025-10-15 19:16:22
Поделиться22025-10-15 19:35:44
[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]