<!doctype html>
<html lang="th">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Minesweeper - ไรส์สวีปเปอร์ (Playable)</title>
<style>
:root{--bg:#0f1724;--card:#0b1220;--accent:#f59e0b;--text:#e6eef8}
*{box-sizing:border-box}
body{margin:0;font-family:Inter,ui-sans-serif,system-ui,Segoe UI,Roboto,'Noto Sans',Helvetica,Arial;color:var(--text);background:linear-gradient(180deg,#021124 0%, #051026 100%);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.app{width:100%;max-width:920px}
.header{display
:flex
;gap
:12px
;align
-items
:center
;justify
-content
:space
-between
;margin
-bottom
:14px
} .title{font-size:20px;font-weight:700}
.controls{display:flex;gap:8px;align-items:center}
select,input[type=number]{background:transparent;border:1px solid rgba(255,255,255,0.08);padding:6px 8px;border-radius:8px;color:var(--text)}
button{background:var(--card);border:1px solid rgba(255,255,255,0.04);color:var(--text);padding:8px 12px;border-radius:10px;cursor:pointer}
.status{display:flex;gap:8px;align-items:center}
.chip{background:rgba(255,255,255,0.03);padding:6px 10px;border-radius:8px;font-weight:600}
.board-wrap{background:linear-gradient(180deg, rgba(255,255,255,0.02), transparent);padding:16px;border-radius:12px;border:1px solid rgba(255,255,255,0.03)}
.board{display:grid;gap:4px;margin-top:8px}
.cell{width:34px;height:34px;display:flex;align-items:center;justify-content:center;background:linear-gradient(180deg,#0d2236,#08151f);border-radius:6px;user-select:none;font-weight:700;cursor:pointer}
.cell.open{background:linear-gradient(180deg,#cfe8ff,#eaf6ff);color:#0b2233;cursor:default}
.cell.flag{background:linear-gradient(180deg,#2b3a4b,#283240);}
.cell.mine{background:linear-gradient(180deg,#ffccd5,#ffdce5);color:#7b021f}
.row{display:flex}
.controls-legend{display:flex;gap:8px;align-items:center}
.small{font-size:13px;opacity:0.9}
.footer-note{margin-top:10px;font-size:13px;color:rgba(230,238,248,0.8)}
@media
(max
-width
:520px
){.cell
{width
:28px
;height
:28px
;font
-size
:13px
}.header{flex
-direction
:column
;align
-items
:stretch
}.controls
{flex
-wrap
:wrap
}} </style>
</head>
<body>
<div class="app">
<div class="header">
<div>
<div class="title">Minesweeper — ไรส์สวีปเปอร์</div>
<div class="small">คลิกซ้ายเพื่อเปิด, คลิกขวาเพื่อปักธง (บนมือถือ: แตะค้าง)</div>
</div>
<div class="controls">
<label>
ระดับ:
<select id="preset">
<option value="easy">ง่าย (9×9, 10 ระเบิด)</option>
<option value="medium" selected>กลาง (16×16, 40 ระเบิด)</option>
<option value="hard">ยาก (30×16, 99 ระเบิด)</option>
<option value="custom">กำหนดเอง</option>
</select>
</label>
<label id="customInputs" style="display:none">
W
<input id
="w" type
="number" value
="16" min="5" max="60" style
="width:64px" /> H
<input id
="h" type
="number" value
="16" min="5" max="40" style
="width:64px" /> M
<input id
="m" type
="number" value
="40" min="1" max="500" style
="width:64px" /> </label>
<button id="newBtn">เริ่มใหม่</button>
</div>
</div>
<div class="status">
<div class="chip">ระเบิด: <span id="mineCount">40</span></div>
<div class="chip">เวลา: <span id="timer">0</span>s</div>
<div class="chip" id="result">สถานะ: กำลังเล่น</div>
</div>
<div class="board-wrap">
<div id="board" class="board" aria-hidden="false"></div>
<div class="footer-note">ทิป: ดับเบิลคลิกที่ช่องเปิดเพื่อเปิดช่องรอบ ๆ ถ้าจำนวนธงรอบตรงกับตัวเลข</div>
</div>
</div>
<script>
(function(){
// Minesweeper implementation
const boardEl = document.getElementById('board');
const preset = document.getElementById('preset');
const newBtn = document.getElementById('newBtn');
const mineCountEl = document.getElementById('mineCount');
const timerEl = document.getElementById('timer');
const resultEl = document.getElementById('result');
const customInputs = document.getElementById('customInputs');
const wInput = document.getElementById('w');
const hInput = document.getElementById('h');
const mInput = document.getElementById('m');
let W=16,H=16,M=40;
let cells=[], openCount=0, flagged=0, started=false, timer=null, seconds=0, gameOver=false;
function setPreset(p){
if(p==='easy'){W=9;H=9;M=10}
else if(p==='medium'){W=16;H=16;M=40}
else if(p==='hard'){W=30;H=16;M=99}
}
preset.addEventListener('change',()=>{
if(preset.value==='custom'){customInputs.style.display='inline-block'} else {customInputs.style.display='none';setPreset(preset.value);}
});
function clearTimer(){clearInterval(timer);timer=null;seconds=0;timerEl.textContent='0'}
function startTimer(){if(timer) return;timer=setInterval(()=>{seconds++;timerEl.textContent=seconds},1000)}
function buildBoard(){
boardEl.innerHTML='';
boardEl.style.gridTemplateColumns = `repeat(${W}, auto)`;
boardEl.style.gridTemplateRows = `repeat(${H}, auto)`;
cells
= Array(H
).fill
(0).map
(()=>Array(W
).fill
(null)); openCount=0;flagged=0;started=false;gameOver=false;clearTimer();resultEl.textContent='สถานะ: กำลังเล่น';mineCountEl.textContent=M;
for(let r=0;r<H;r++){
for(let c=0;c<W;c++){
const el = document.createElement('div');
el.className='cell';
el.dataset.r=r;el.dataset.c=c;el.dataset.open='0';el.dataset.flag='0';
el.addEventListener('click',onLeft);
el.addEventListener('contextmenu',onRight);
el.addEventListener('mousedown',onDown);
el.addEventListener('touchstart',onTouchStart,{passive:true});
el.addEventListener('touchend',onTouchEnd);
boardEl.appendChild(el);
cells[r][c]={el, mine:false, number:0, r, c, open:false, flag:false};
}
}
}
function placeMines(sr,sc){
// place M mines excluding first click (sr,sc) and neighbours
const forbidden = new Set();
for(let r=sr-1;r<=sr+1;r++) for(let c=sc-1;c<=sc+1;c++) if(inBounds(r,c)) forbidden.add(`${r},${c}`);
let placed=0;
while(placed<M){
const r
= Math
.floor(Math
.random
()*H
); const c
= Math
.floor(Math
.random
()*W
); if(forbidden
.has
(key)) continue; if(cells[r][c].mine) continue;
cells[r][c].mine=true;placed++;
}
// compute numbers
for(let r=0;r<H;r++){
for(let c=0;c<W;c++){
if(cells[r][c].mine) continue;
let cnt=0;
for(let rr=r-1;rr<=r+1;rr++) for(let cc=c-1;cc<=c+1;cc++) if(inBounds(rr,cc) && cells[rr][cc].mine) cnt++;
cells[r][c].number=cnt;
}
}
}
function inBounds(r,c){return r>=0 && r<H && c>=0 && c<W}
function reveal(r,c){
const cell = cells[r][c];
if(!cell || cell.open || cell.flag) return;
cell.open=true;cell.el.dataset.open='1';cell.el.classList.add('open');
openCount++;
if(cell.mine){
cell.el.classList.add('mine');cell.el.textContent='💣';
return;
}
if(cell.number>0){cell.el.textContent=cell.number; setNumberColor(cell.el, cell.number)}
else{ // flood fill
for(let rr=r-1;rr<=r+1;rr++) for(let cc=c-1;cc<=c+1;cc++) if(inBounds(rr,cc)) reveal(rr,cc);
}
}
function setNumberColor(el,num){
const map = {1:'#0b3b6b',2:'#0b6b3b',3:'#6b0b3b',4:'#2b2b6b',5:'#6b2b2b',6:'#2b6b6b',7:'#1a1a1a',8:'#666'};
el.style.color = map[num] || '#0b2233';
}
function onLeft(e){
if(gameOver) return;
const el = e.currentTarget;
const r = +el.dataset.r; const c = +el.dataset.c;
if(!started){placeMines(r,c);started=true;startTimer();}
const cell = cells[r][c];
if(cell.flag || cell.open) return;
if(cell.mine){
// reveal all mines and lose
revealAllMines(r,c);
endGame(false);
return;
}
reveal(r,c);
checkWin();
}
function revealAllMines(hitR,hitC){
for(let r=0;r<H;r++) for(let c=0;c<W;c++){
const cell=cells[r][c];
if(cell.mine){cell.el.classList.add('mine');cell.el.textContent='💣';}
}
if(inBounds(hitR,hitC)) cells[hitR][hitC].el.classList.add('mine');
}
function onRight(e){
e.preventDefault();
if(gameOver) return;
const el = e.currentTarget;
const r = +el.dataset.r; const c = +el.dataset.c;
const cell=cells[r][c];
if(cell.open) return;
toggleFlag(cell);
checkWin();
}
function toggleFlag(cell){
if(cell.flag){cell.flag=false;cell.el.dataset.flag='0';cell.el.classList.remove('flag');cell.el.textContent='';flagged--;}
else{cell.flag=true;cell.el.dataset.flag='1';cell.el.classList.add('flag');cell.el.textContent='🚩';flagged++;}
mineCountEl
.textContent
= Math
.max(0, M
- flagged
); }
// double-click chord: open neighbours if flags equal number
boardEl.addEventListener('dblclick',(ev)=>{
if(gameOver) return;
const el = ev.target.closest('.cell'); if(!el) return;
const r=+el.dataset.r,c=+el.dataset.c; const cell=cells[r][c];
if(!cell.open || cell.number===0) return;
let flags=0; const neighbours=[];
for(let rr=r-1;rr<=r+1;rr++) for(let cc=c-1;cc<=c+1;cc++) if(inBounds(rr,cc) && !(rr===r && cc===c)){
neighbours.push(cells[rr][cc]); if(cells[rr][cc].flag) flags++;
}
if(flags===cell.number){
neighbours.forEach(n=>{ if(!n.flag && !n.open){ if(n.mine){revealAllMines(n.r,n.c); endGame(false); } else reveal(n.r,n.c); }});
checkWin();
}
});
function checkWin(){
if(gameOver) return;
if(openCount === W*H - M){ endGame(true); }
}
function endGame(won){
gameOver=true;clearTimer();resultEl.textContent = won? 'สถานะ: ชนะ 🎉' : 'สถานะ: แพ้ 💥';
if(won){ // reveal flags nicely
for(let r=0;r<H;r++) for(let c=0;c<W;c++){ const cell=cells[r][c]; if(cell.mine) cell.el.textContent='🚩'; }
}
}
// Support for mobile long-press to flag
let touchTimer=null; let touchCell=null;
function onTouchStart(e){
if(gameOver) return;
const touch = e
.target
.closest
('.cell'); if(!touch) return; touchTimer = setTimeout(()=>{ // long press 600ms -> flag
const r=+touchCell.dataset.r, c=+touchCell.dataset.c; const cell=cells[r][c]; if(!cell.open) toggleFlag(cell); touchTimer=null; touchCell=null; },600);
}
function onTouchEnd(e){ if(touchTimer){clearTimeout(touchTimer); touchTimer=null; touchCell=null;} }
// mouse down for potential chord-style interactions (visual)
function onDown(e){ /* placeholder if we want press effects */ }
// keyboard accessibility: space to open, f to flag
document.addEventListener('keydown',(e)=>{
const active = document.activeElement;
if(active && active.classList && active.classList.contains('cell')){
const r=+active.dataset.r,c=+active.dataset.c, cell=cells[r][c];
if(e
.key===' '){e
.preventDefault
(); if(!cell
.open
) onLeft
({currentTarget
:active
});} if(e
.key.toLowerCase
()==='f'){e
.preventDefault
(); if(!cell
.open
) toggleFlag
(cell
);} } });
// build new game with current settings
function newGame(){
if(preset.value==='custom'){
W
= Math
.max(5, Math
.min(60, +wInput
.value
||16)); H
= Math
.max(5, Math
.min(40, +hInput
.value
||16)); M
= Math
.max(1, Math
.min(W
*H
-1, +mInput
.value
||10)); } else setPreset(preset.value);
buildBoard();
}
newBtn.addEventListener('click', ()=>{ newGame(); });
// initial
setPreset('medium'); buildBoard();
// allow clicking cells via keyboard focus for accessibility
boardEl.addEventListener('click', (ev)=>{ const c=ev.target.closest('.cell'); if(c) c.focus(); });
})();
</script>
</body>
</html>