[html]<style>
.shipro-description p a {
color: #b93e3e!important;
}
.shipro-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
margin: 20px auto;
max-width: 900px;
background: #171b1c; /* Цвет темный задника*/
border-radius: 12px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.shipro-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5);
}
.shipro-image {
width: 100%;
max-width: 300px;
aspect-ratio: 1 / 1;
background-size: cover;
background-position: center;
border-radius: 12px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5);
margin: 20px 20px;
transition: transform 0.3s ease;
}
.shipro-card:hover .shipro-image {
transform: scale(1.25);
}
.shipro-info {
text-align: center;
font-family: "american typewriter", sans-serif;
padding: 20px;
}
.shipro-info h5 {
font-size: 24px;
margin: 5px;
color: #b8b2a8; /* Заголовок*/
}
.shipro-info em {
font-size: 14px;
color: #6c6c6c; /* Цвет текста под заголовком*/
margin-bottom: 10px;
display: block;
}
.shipro-description {
font-size: 14px;
color: #949494; /* Главный текст */
text-align: justify;
line-height: 1.5;
text-indent: 30px;
max-height: 2000px;
overflow: hidden;
padding: 0 10px;
transition: max-height 0.3s ease;
}
.shipro-card:hover .shipro-description {
max-height: 900px;
}
.shipro-description p {
margin: 0 !important;
padding: 0 !important;
font-size: 14px !important;
}
</style>
<div class="shipro-card">
<div class="shipro-info">
<h5>СУДОКУ ОНЛАЙН</h5>
<div class="shipro-description">
<p>Если вы не знали, чем заняться в ожидании поста от соигрока, то теперь можно поиграть в Судоку. И не просто поиграть, а еще и получить за это баллы на счёт. Каждая разыгранная партия - 50$. Доказательством присылайте скрины в этой теме ниже.
<p><b>Как играть:</b>
<p> - Игровое поле состоит из 81 клетки (9 строк × 9 столбцов). Каждые 9 клеток образуют небольшой квадрат 3×3.
<p> - Часть клеток уже заполнена цифрами-подсказками.
<p> -Нельзя ставить число, если оно уже есть в ряду, колонке или блоке.
<p> -Нельзя использовать числа вне диапазона 1–9 (например, 0 или 10).
<p> -Нельзя вставлять числа наугад — решение должно быть логически обоснованным на каждом шаге.<br>
<p>"Новая игра" генерирует новое поле. Есть возможность посмотреть подсказки и подсмотреть ответ. Но администрация 1920 верит в наших игроков и знает, что у нас нет места читерству.
<p>Так же есть возможность выбрать сложность игры специальной кнопкой.
</div>
</div>
</div>
<!-- Можете поставить следом ещё одну такую же про другого персонажа -->
[/html]
[html]<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Sudoku</title>
<style>
:root{--cell-size:47px;--gap:4px;--accent:#2b6cb0;--bg:#888888}
body{font-family:Inter,Segoe UI,Roboto,Arial,sans-serif;background:var(--bg);padding:9px}
.sudoku-wrap{display:flex;gap:14px;align-items:flex-start}
.board{display:grid;grid-template-columns:repeat(9,var(--cell-size));grid-template-rows:repeat(9,var(--cell-size));gap:2px;background:#4a4a4b;padding:12px;border-radius:10px;box-shadow:0 6px 18px rgba(20,20,40,0.06)}
.cell{width:var(--cell-size);height:var(--cell-size);display:flex;align-items:center;justify-content:center;font-size:18px;background:#b7b8b8}
.cell input{width:100%;height:100%;border:0;text-align:center;font-weight:600;font-size:14px;background:transparent}
.cell input:focus{outline:2px solid rgba(43,108,176,.15);border-radius:6px}
.given input{font-weight:650;color:#0b2540}
.note{font-size:7px;color:#666}
.thick-right{border-right:3px solid #999}
.thick-bottom{border-bottom:3px solid #999}
.controls{min-width:220px}
button,select{padding:8px 12px;border-radius:8px;border:1px solid #e2e8f0;background:#fff;cursor:pointer}
button:hover{box-shadow:0 6px 12px rgba(16,24,40,0.06)}
.actions{display:flex;flex-direction:column;gap:8px}
.status{margin-top:8px;color:#0b2540;font-weight:600}
.small{font-size:13px;color:#334155}
.hint{background:#fffbeb;border-color:#f6e05e}
.invalid{background:#fff1f2;border-color:#fecaca}
.button-row {
display: flex;
flex-wrap: wrap; /* чтобы на маленьких экранах переносились */
gap: 8px; /* расстояние между кнопками */
}
@media (max-width:960px){.sudoku-wrap{flex-direction:column;align-items:center}.controls{width:100%}}
</style>
</head>
<body>
<h2>Sudoku</h2>
<div class="sudoku-wrap">
<div>
<div id="board" class="board" aria-label="Судоку поле"></div>
</div>
<div class="button-row">
<button id="newBtn">Новая игра</button>
<button id="solveBtn">Показать решение</button>
<button id="checkBtn">Проверить</button>
<button id="hintBtn" class="hint">Подсказка</button>
<button id="resetBtn">Сбросить ходы</button>
<button id="fillNotesBtn">Добавить пометки</button>
</div>
<div class="controls">
<div class="actions">
<label class="small">СЛОЖНОСТЬ
<select id="difficulty">
<option value="35">Лёгкая</option>
<option value="45" selected>Средняя</option>
<option value="55">Сложная</option>
</select>
</label>
</div>
<div class="status" id="status">Готово</div>
<div class="small" style="margin-top:8px"></div>
</div>
</div>
<script>
// Sudoku single-file: генерация + UI + проверка
(() => {
const boardEl = document.getElementById('board');
const newBtn = document.getElementById('newBtn');
const solveBtn = document.getElementById('solveBtn');
const checkBtn = document.getElementById('checkBtn');
const hintBtn = document.getElementById('hintBtn');
const resetBtn = document.getElementById('resetBtn');
const fillNotesBtn = document.getElementById('fillNotesBtn');
const difficultySelect = document.getElementById('difficulty');
const status = document.getElementById('status');
let solution = []; // full solution 9x9
let puzzle = []; // puzzle with zeros for empty
const inputs = []; // DOM inputs
let givens = new Set();
let initialSnapshot = null;
// create board DOM
function createGrid(){
boardEl.innerHTML = '';
inputs.length = 0;
for(let r=0;r<9;r++){
for(let c=0;c<9;c++){
const cell = document.createElement('div');
cell.className = 'cell';
if((c+1)%3===0 && c!==8) cell.classList.add('thick-right');
if((r+1)%3===0 && r!==8) cell.classList.add('thick-bottom');
const inp = document.createElement('input');
inp.setAttribute('inputmode','numeric');
inp.setAttribute('maxlength','1');
inp.dataset.r = r; inp.dataset.c = c;
inp.addEventListener('input', onInput);
inp.addEventListener('keydown', onKeydown);
cell.appendChild(inp);
boardEl.appendChild(cell);
inputs.push(inp);
}
}
}
function onKeydown(e){
// allow arrow navigation
const k = e.key;
const r = +this.dataset.r; const c = +this.dataset.c;
if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight'].includes(k)){
e.preventDefault();
let nr=r,nc=c;
if(k==='ArrowUp') nr = (r+8)%9;
if(k==='ArrowDown') nr = (r+1)%9;
if(k==='ArrowLeft') nc = (c+8)%9;
if(k==='ArrowRight') nc = (c+1)%9;
inputs[nr*9+nc].focus();
}
// allow Backspace to clear
if(k==='Backspace') this.value='';
}
function onInput(e){
const v = this.value.replace(/[^1-9]/g,'').slice(0,1);
this.value = v;
const idx = +this.dataset.r*9 + (+this.dataset.c);
if(givens.has(idx)) return; // don't change given
// validate by quick check: conflict -> mark invalid
validateCell(+this.dataset.r, +this.dataset.c);
}
function validateCell(r,c){
const val = inputs[r*9+c].value;
clearInvalid();
if(!val) return;
// check row/col/box
for(let i=0;i<9;i++){
if(i!==c && inputs[r*9+i].value===val) markInvalid(r*9+c);
if(i!==r && inputs[i*9+c].value===val) markInvalid(r*9+c);
}
const br = Math.floor(r/3)*3, bc = Math.floor(c/3)*3;
for(let dr=0;dr<3;dr++) for(let dc=0;dc<3;dc++){
const rr = br+dr, cc = bc+dc; const idx = rr*9+cc;
if((rr!==r || cc!==c) && inputs[idx].value===val) markInvalid(r*9+c);
}
}
function markInvalid(idx){
inputs[idx].classList.add('invalid');
status.textContent = 'Есть конфликт! Проверьте отмеченные клетки.';
}
function clearInvalid(){
inputs.forEach(i=>i.classList.remove('invalid'));
status.textContent = 'Готово';
}
// Sudoku generator: backtracking to create full board
function generateSolution(){
const grid = Array.from({length:9},()=>Array(9).fill(0));
const rows = Array.from({length:9},()=>new Set());
const cols = Array.from({length:9},()=>new Set());
const boxes = Array.from({length:9},()=>new Set());
const order = [];
for(let r=0;r<9;r++) for(let c=0;c<9;c++) order.push([r,c]);
function shuffle(a){
for(let i=a.length-1;i>0;i--){
const j = Math.floor(Math.random()*(i+1)); [a[i],a[j]]=[a[j],a[i]];
}
}
function tryFill(pos=0){
if(pos===81) return true;
// choose next with minimum candidates to speed up
let best = -1, bestOpts=null, bestIdx=-1;
for(let i=pos;i<81;i++){
const [r,c] = order[i];
if(grid[r][c]!==0) continue;
const opts = getCandidates(grid,r,c,rows,cols,boxes);
if(opts.length===0) return false;
if(best===-1 || opts.length < best){ best = opts.length; bestOpts = opts; bestIdx = i; }
}
// swap chosen to current pos
[order[pos], order[bestIdx]] = [order[bestIdx], order[pos]];
const [r,c] = order[pos];
shuffle(bestOpts);
for(const val of bestOpts){
grid[r][c]=val; rows[r].add(val); cols[c].add(val); boxes[Math.floor(r/3)*3+Math.floor(c/3)].add(val);
if(tryFill(pos+1)) return true;
grid[r][c]=0; rows[r].delete(val); cols[c].delete(val); boxes[Math.floor(r/3)*3+Math.floor(c/3)].delete(val);
}
// restore order? not needed in this run
return false;
}
function getCandidates(grid,r,c,rows,cols,boxes){
const used = new Set([...rows[r],...cols[c],...boxes[Math.floor(r/3)*3+Math.floor(c/3)]]);
const opts = [];
for(let v=1;v<=9;v++) if(!used.has(v)) opts.push(v);
return opts;
}
// init order randomly
shuffle(order);
tryFill(0);
return grid;
}
// remove cells to make puzzle
function makePuzzle(full, empties){
const p = full.map(r=>r.slice());
const indices = Array.from({length:81},(_,i)=>i);
for(let i=indices.length-1;i>0;i--){ const j = Math.floor(Math.random()*(i+1)); [indices[i],indices[j]]=[indices[j],indices[i]] }
let removed = 0; let idx = 0;
while(removed < empties && idx < 81){
const pos = indices[idx++]; const r = Math.floor(pos/9), c = pos%9;
const backup = p[r][c]; p[r][c]=0;
// ensure unique solution (simple check: count solutions <=1)
const copy = p.map(row=>row.slice());
const cnt = countSolutions(copy,2);
if(cnt!==1){ p[r][c]=backup; }
else removed++;
}
return p;
}
// count solutions with backtracking (stop after limit)
function countSolutions(grid,limit){
let count=0;
function findEmpty(){ for(let r=0;r<9;r++) for(let c=0;c<9;c++) if(grid[r][c]===0) return [r,c]; return null; }
function canPlace(r,c,v){
for(let i=0;i<9;i++){ if(grid[r][i]===v || grid[i][c]===v) return false; }
const br=Math.floor(r/3)*3, bc=Math.floor(c/3)*3;
for(let dr=0;dr<3;dr++) for(let dc=0;dc<3;dc++) if(grid[br+dr][bc+dc]===v) return false;
return true;
}
function back(){
if(count>=limit) return;
const pos = findEmpty(); if(!pos){ count++; return; }
const [r,c]=pos;
for(let v=1;v<=9;v++) if(canPlace(r,c,v)){
grid[r][c]=v; back(); grid[r][c]=0; if(count>=limit) return;
}
}
back(); return count;
}
// fill UI from puzzle
function renderPuzzle(p){
givens.clear();
puzzle = p.map(r=>r.slice());
for(let r=0;r<9;r++) for(let c=0;c<9;c++){
const idx = r*9+c; const val = p[r][c]; const inp = inputs[idx];
inp.value = val?String(val):'';
inp.disabled = false; inp.classList.remove('given');
if(val){ inp.disabled = true; inp.classList.add('given'); givens.add(idx); }
}
initialSnapshot = inputs.map(i=>i.value);
clearInvalid();
}
function to2d(arr){ return Array.from({length:9},(_,r)=>arr.slice(r*9,(r+1)*9)); }
// new game
function newGame(){
status.textContent='Генерирую...';
setTimeout(()=>{
solution = generateSolution();
const empties = parseInt(difficultySelect.value,10);
puzzle = makePuzzle(solution, empties);
renderPuzzle(puzzle);
status.textContent='Новая игра готова';
},10);
}
// solve: fill with solution
function showSolution(){
if(!solution.length){ status.textContent='Решение не сгенерировано'; return; }
for(let r=0;r<9;r++) for(let c=0;c<9;c++){
const idx=r*9+c; inputs[idx].value = String(solution[r][c]); inputs[idx].classList.remove('invalid');
}
status.textContent='Показано решение';
}
// check current board
function checkBoard(){
let ok=true; clearInvalid();
for(let r=0;r<9;r++) for(let c=0;c<9;c++){
const idx=r*9+c; const v = inputs[idx].value;
if(!v){ ok=false; inputs[idx].classList.add('invalid'); }
else if(String(solution[r] && solution[r][c]) !== v){ ok=false; inputs[idx].classList.add('invalid'); }
}
status.textContent = ok ? 'Вы выиграли! Все верно.' : 'Есть ошибки или пустые клетки.';
}
// hint: find an empty cell and fill correct value
function hint(){
for(let r=0;r<9;r++) for(let c=0;c<9;c++){
const idx=r*9+c; if(!inputs[idx].value){ inputs[idx].value = solution[r][c]; inputs[idx].classList.add('hint'); setTimeout(()=>inputs[idx].classList.remove('hint'),900); status.textContent='Подсказка поставлена'; return; }
}
status.textContent='Нечего подсказать — поле заполнено';
}
// reset to initial snapshot
function resetToStart(){
if(!initialSnapshot) return;
inputs.forEach((inp,i)=>{ if(!givens.has(i)) inp.value = initialSnapshot[i]||''; inp.classList.remove('invalid'); });
status.textContent='Ходы сброшены';
}
// fill notes small numbers (simple: show 1-9 possible in title attribute)
function fillNotes(){
for(let r=0;r<9;r++) for(let c=0;c<9;c++){
const idx=r*9+c; if(inputs[idx].value) { inputs[idx].title=''; continue; }
const possibles = [];
for(let v=1;v<=9;v++){
// simulate placing v
let ok=true;
for(let i=0;i<9;i++){ if(inputs[r*9+i].value==String(v)) ok=false; if(inputs[i*9+c].value==String(v)) ok=false; }
const br=Math.floor(r/3)*3, bc=Math.floor(c/3)*3;
for(let dr=0;dr<3;dr++) for(let dc=0;dc<3;dc++){ if(inputs[(br+dr)*9+(bc+dc)].value==String(v)) ok=false; }
if(ok) possibles.push(v);
}
inputs[idx].title = 'Возможные: ' + (possibles.length?possibles.join(', '):'-');
}
status.textContent='Пометки добавлены (наведите на клетку)';
}
// utils: read puzzle from UI into 2d
function readCurrent(){ return to2d(inputs.map(i=>i.value?parseInt(i.value,10):0)); }
// initial setup
createGrid();
newBtn.addEventListener('click', newGame);
solveBtn.addEventListener('click', showSolution);
checkBtn.addEventListener('click', checkBoard);
hintBtn.addEventListener('click', hint);
resetBtn.addEventListener('click', resetToStart);
fillNotesBtn.addEventListener('click', fillNotes);
difficultySelect.addEventListener('change', ()=>{});
// generate first game
newGame();
})();
</script>
</body>
</html>[/html]