/* Dames New Gen — version hébergement statique (Hostinger) */ /* Pas de build: React, ReactDOM et Tailwind chargés via CDN, JSX transpile via Babel-standalone. */ const { useMemo, useState, useEffect } = React; // --- Moteur du jeu (identique à la version React, sans import/export) --- const BOARD_SIZE = 8; const MAX_TRAPS = 5; const DARK_SQUARE = (r, c) => (r + c) % 2 === 1; const DIRS = { R: [ [1, -1], [1, 1] ], B: [ [-1, -1], [-1, 1] ], }; const ALL_DIRS = [ [1, -1], [1, 1], [-1, -1], [-1, 1] ]; function cloneBoard(board){ return board.map(row => row.map(cell => ({ piece: cell.piece ? { ...cell.piece } : null, trap: cell.trap ? { ...cell.trap } : null, }))); } function initBoard(){ const board = Array.from({length: BOARD_SIZE}, () => Array.from({length: BOARD_SIZE}, () => ({ piece: null, trap: null })) ); for(let r=0; r<3; r++){ for(let c=0; c=0 && r=0 && c0) continue; if(fromPos && (fromPos[0]!==r || fromPos[1]!==c)) continue; const dirs = p.king ? ALL_DIRS : DIRS[player]; const moves = []; let hasCapture = false; for(const [dr,dc] of dirs){ const nr = r + dr, nc = c + dc; if(inside(nr,nc) && !board[nr][nc].piece && DARK_SQUARE(nr,nc)){ if(!forceCaptureOnly) moves.push({ to:[nr,nc], capture:null }); } const jr = r + 2*dr, jc = c + 2*dc; if(inside(jr,jc) && !board[jr][jc].piece && DARK_SQUARE(jr,jc)){ const mid = board[nr][nc]; if(mid?.piece && mid.piece.player !== player){ moves.push({ to:[jr,jc], capture:{r:nr, c:nc} }); hasCapture = true; } } } if(moves.length){ result.set(`${r},${c}`, { moves, hasCapture }); if(hasCapture) anyCapture = true; } } } if(anyCapture && forceCaptureOnly){ for(const [key,val] of result){ const caps = val.moves.filter(m=>!!m.capture); if(caps.length) result.set(key, { moves:caps, hasCapture:true }); else result.delete(key); } } return { movesByPiece: result, anyCapture }; } function hasAnyMove(board, player){ const { movesByPiece } = listAllMoves(board, player, false, null); return movesByPiece.size>0; } function crownIfNeeded(piece, r){ if(piece.king) return piece; if(piece.player==='R' && r===BOARD_SIZE-1) return { ...piece, king:true }; if(piece.player==='B' && r===0) return { ...piece, king:true }; return piece; } function countTraps(board){ let n=0; for(let r=0;r0) p.frozenTurns -= 1; } } return b; } function applyTrapOnLanding(board, r, c){ const b = cloneBoard(board); const cell = b[r][c]; const trap = cell.trap; if(!trap) return { board:b, effect:null, pieceSurvived:true, consumed:false }; const piece = cell.piece; if(!piece) return { board:b, effect:null, pieceSurvived:true, consumed:false }; let effect = null; if(trap.type==='bombe'){ cell.piece = null; effect = '💥 Bombe : la pièce est détruite'; } else if(trap.type==='glace'){ cell.piece = { ...piece, frozenTurns: (piece.frozenTurns||0)+1 }; effect = '🧊 Glace : la pièce est gelée 1 tour'; } else if(trap.type==='teleport'){ const empty = []; for(let rr=0; rrinitBoard()); const [current, setCurrent] = useState('R'); const [selected, setSelected] = useState(null); const [forcedFrom, setForcedFrom] = useState(null); const [mustCapture, setMustCapture] = useState(true); const [turnCount, setTurnCount] = useState(1); const [log, setLog] = useState(['🎮 Nouvelle partie — Rouge commence']); const [settings, setSettings] = useState({ trapTTL:6, trapsPerTurn:1, enabledTraps:['bombe','glace','teleport'] }); const [trapSpawnLocked, setTrapSpawnLocked] = useState(false); const tv = useMemo(()=>{ switch(theme){ case 'dark': return { containerBg: 'bg-gradient-to-b from-slate-950 via-slate-900 to-slate-900', containerText: 'text-slate-100', squareLight: 'bg-slate-600', squareDark: 'bg-slate-800', boardBorder: 'border-slate-700', boardBg: 'bg-slate-900', panelBg: 'bg-white/5 backdrop-blur-lg', panelBorder: 'border border-white/10', moveRing: 'ring-cyan-300', inputBg: 'bg-slate-800 border border-white/10', pieceRed: 'bg-rose-400', pieceGreen: 'bg-emerald-400', pieceRing: 'ring-white/90', footer: 'Mode sombre', }; case 'neon': return { containerBg: 'bg-[#050914]', containerText: 'text-[#e6f3ff]', squareLight: 'bg-[#1a2a4a]', squareDark: 'bg-[#0e1b34]', boardBorder: 'border-[#243253]', boardBg: 'bg-[#0a1228]', panelBg: 'bg-white/5 backdrop-blur-lg', panelBorder: 'border border-[#223559]/60', moveRing: 'ring-[#00e5ff]', inputBg: 'bg-[#13223f] border border-[#223559]/60', pieceRed: 'bg-[#ff3b6b]', pieceGreen: 'bg-[#19d38a]', pieceRing: 'ring-white/90', footer: 'Mode néon', }; default: return { containerBg: 'bg-gradient-to-b from-white to-slate-100', containerText: 'text-slate-900', squareLight: 'bg-white', squareDark: 'bg-slate-600', boardBorder: 'border-slate-300', boardBg: 'bg-slate-50', panelBg: 'bg-white/80 backdrop-blur-md', panelBorder: 'border border-slate-300', moveRing: 'ring-sky-500', inputBg: 'bg-white border border-slate-300', pieceRed: 'bg-red-600', pieceGreen: 'bg-green-600', pieceRing: 'ring-black/20', footer: 'Mode clair', }; } }, [theme]); const baseMoves = useMemo(()=>listAllMoves(board, current, false, forcedFrom), [board, current, forcedFrom]); const { movesByPiece, anyCapture } = useMemo(()=>{ if(!mustCapture || !baseMoves.anyCapture) return baseMoves; const filtered = new Map(); for(const [k,v] of baseMoves.movesByPiece){ const only = v.moves.filter(m=>!!m.capture); if(only.length) filtered.set(k, { moves: only, hasCapture:true }); } return { movesByPiece:filtered, anyCapture:true }; }, [baseMoves, mustCapture]); const gameOver = useMemo(()=>!hasAnyMove(board, current), [board, current]); function reset(){ setBoard(initBoard()); setCurrent('R'); setSelected(null); setForcedFrom(null); setTurnCount(1); setLog(['🎮 Nouvelle partie — Rouge commence']); setTrapSpawnLocked(false); } function onSquareClick(r,c){ if(gameOver) return; const cell = board[r][c]; if(cell.piece && cell.piece.player===current){ if(cell.piece.frozenTurns && cell.piece.frozenTurns>0) return; if(forcedFrom && (forcedFrom[0]!==r || forcedFrom[1]!==c)) return; setSelected([r,c]); return; } if(selected){ const entry = movesByPiece.get(`${selected[0]},${selected[1]}`); if(!entry) return; const move = entry.moves.find(m=>m.to[0]===r && m.to[1]===c); if(!move) return; doMove(selected[0], selected[1], r, c, move.capture); } } function doMove(sr,sc,tr,tc,capture){ let b = cloneBoard(board); const moving = b[sr][sc].piece; if(!moving) return; b[sr][sc].piece = null; if(capture) b[capture.r][capture.c].piece = null; b[tr][tc].piece = crownIfNeeded(moving, tr); let chain = false; if(capture){ const nm = listAllMoves(b, current, true, [tr,tc]); const ent = nm.movesByPiece.get(`${tr},${tc}`); chain = nm.anyCapture && !!ent && ent.moves.some(m=>m.capture); } let trapEffect = null, survived = true, consumedTrap = false; if(b[tr][tc].trap){ const res = applyTrapOnLanding(b, tr, tc); b = res.board; trapEffect = res.effect; survived = res.pieceSurvived; consumedTrap = res.consumed; chain = chain && survived; } if(consumedTrap) setTrapSpawnLocked(false); if(chain){ setBoard(b); setSelected([tr,tc]); setForcedFrom([tr,tc]); if(trapEffect) setLog(l=>[`⚠️ ${trapEffect}`, ...l]); return; } endTurn(b, trapEffect); } function endTurn(boardAfterMove, trapEffect){ let b = boardAfterMove; b = decayTraps(b); const currentCount = countTraps(b); let toAdd = 0; if(!trapSpawnLocked){ const free = Math.max(0, MAX_TRAPS - currentCount); toAdd = Math.min(settings.trapsPerTurn, free); } for(let i=0;i= MAX_TRAPS){ if(!trapSpawnLocked) setTrapSpawnLocked(true); } const next = current==='R' ? 'B' : 'R'; b = reduceFrozenForPlayer(b, next); const msg = trapEffect ? `🧭 Tour ${turnCount} terminé (${current==='R'?'Rouge':'Noir'}) · ${trapEffect}` : `🧭 Tour ${turnCount} terminé (${current==='R'?'Rouge':'Noir'})`; setBoard(b); setCurrent(next); setSelected(null); setForcedFrom(null); setTurnCount(t=>t+1); setLog(l=>[msg, ...l]); } const playableKeys = useMemo(()=>new Set(movesByPiece.keys()), [movesByPiece]); const hint = useMemo(()=>{ if(gameOver) return `Partie terminée — ${(current==='R'?'Rouge':'Noir')} ne peut plus jouer`; const nb = countTraps(board); const lock = (trapSpawnLocked && nb>=MAX_TRAPS) ? " · limite de pièges atteinte (nouveaux pièges bloqués jusqu'à activation)" : ""; return `Au tour de ${(current==='R'?'Rouge':'Noir')}${(mustCapture && anyCapture ? ' · prise obligatoire' : '')} · Pièges: ${nb}/${MAX_TRAPS}${lock}`; }, [board, current, mustCapture, anyCapture, gameOver, trapSpawnLocked]); const squares = useMemo(()=>{ return Array.from({length:BOARD_SIZE}).flatMap((_,r)=> Array.from({length:BOARD_SIZE}).map((_,c)=>{ const dark = DARK_SQUARE(r,c); const cell = board[r][c]; const key = `${r},${c}`; const isSelected = selected && selected[0]===r && selected[1]===c; const entry = movesByPiece.get(selected ? `${selected[0]},${selected[1]}` : ""); const canGo = entry ? entry.moves.some(m=>m.to[0]===r && m.to[1]===c) : false; const isPlayablePiece = cell.piece && cell.piece.player===current && playableKeys.has(key) && (!(cell.piece.frozenTurns>0)); return ( ); }) ); }, [board, movesByPiece, playableKeys, selected, tv]); useEffect(()=>{ try{ const b0 = initBoard(); let rCount=0, bCount=0; for(let r=0;r

Dames New Gen

Version 8×8 • Rouge commence • Limite 5 pièges

{squares}
Légende pièges
💣 Bombe = détruit 🧊 Glace = gel 1 tour 🌀 Téléporteur = déplacement
État
{hint}
Options
Types de pièges actifs
{['bombe','glace','teleport'].map(t => ( ))}
Journal
{log.map((line, idx) => (
{line}
))}
Astuce

Les pièces jouables sont soulignées par un contour ambre. Les cases de destination s'illuminent en bleu. À 5 pièges, le spawn est bloqué jusqu'à ce que quelqu'un tombe dans un piège.

Prototype frontend — sans IA ni multijoueur. {tv.footer}. Optimisé pour desktop.
); } // Démarrage const root = ReactDOM.createRoot(document.getElementById('root')); root.render();