
hamle geri al - ses aç - kapa - oyun kuralları - menü


3 adet zorluk bölümü var kolay -orta - zor

başla diyoruz oyun başlıyor zarlar otomatik atıyor


pul toplama ise çift tıklayarak gerçekleşmekte oyun bitince oyun özetini txt olarak indirebiliyorsunuz

kodlar;
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Türk Tavlası - Büyük Pullar</title>
<style>
:root {
--wood-primary: #8B4513;
--wood-secondary: #DEB887;
--wood-border: #5D4037;
--point-dark: #3E2723;
--point-light: #D7CCC8;
--checker-white: #F5F5F5;
--checker-black: #111;
--highlight-color: rgba(0, 255, 0, 0.5);
--text-color: #fff;
}
* { box-sizing: border-box; user-select: none; -webkit-user-select: none; touch-action: manipulation; }
body {
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #222;
color: var(--text-color);
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* --- UI HEADER --- */
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
background: #1a1a1a;
border-bottom: 2px solid #444;
height: 60px;
z-index: 10;
}
h1 { margin: 0; font-size: 1.2rem; color: var(--wood-secondary); }
.controls { display: flex; gap: 10px; }
button {
background: #444;
border: none;
color: white;
padding: 5px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
transition: background 0.2s;
}
button:hover { background: #666; }
button:disabled { opacity: 0.5; cursor: not-allowed; }
button.primary { background: var(--wood-primary); font-weight: bold; }
/* --- GAME AREA --- */
#game-container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
position: relative;
padding: 5px; /* Mobilde kenar boşluğunu azalttık */
background-color: #2a2a2a;
}
/* --- BOARD --- */
.board {
width: 100%;
max-width: 900px; /* Biraz daha genişlettik */
aspect-ratio: 4/3;
background-color: var(--wood-secondary);
border: 12px solid var(--wood-border);
border-radius: 8px;
display: flex;
position: relative;
box-shadow: 0 10px 30px rgba(0,0,0,0.6);
}
.half-board {
flex: 1;
display: flex;
flex-direction: column;
border: 2px solid rgba(0,0,0,0.2);
}
.half-board:first-child { border-right: 6px solid var(--wood-border); }
.quadrant {
flex: 1;
display: flex;
position: relative;
}
.quadrant.top { border-bottom: 2px solid rgba(0,0,0,0.1); align-items: flex-start; }
.quadrant.bottom { border-top: 2px solid rgba(0,0,0,0.1); align-items: flex-end; }
/* --- POINTS (TRIANGLES) --- */
.point {
flex: 1;
height: 45%;
position: relative;
display: flex;
justify-content: center;
z-index: 1;
}
.point:nth-child(odd) { background-color: var(--point-dark); clip-path: polygon(50% 100%, 0 0, 100% 0); }
.point:nth-child(even) { background-color: var(--point-light); clip-path: polygon(50% 100%, 0 0, 100% 0); }
.bottom .point:nth-child(odd) { clip-path: polygon(50% 0, 0 100%, 100% 100%); }
.bottom .point:nth-child(even) { clip-path: polygon(50% 0, 0 100%, 100% 100%); }
.point.valid-target { cursor: pointer; }
.point.valid-target::after {
content: '';
position: absolute;
top:0; left:0; width:100%; height:100%;
background-color: var(--highlight-color);
z-index: 0;
}
/* --- CHECKERS (PULLAR) - GÜNCELLENDİ --- */
.checker-container {
position: absolute;
width: 100%;
height: 100%;
pointer-events: none;
display: flex;
}
.col {
position: absolute;
width: calc(100% / 6);
height: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
padding: 0; /* Padding kaldırıldı, yer kazanıldı */
pointer-events: auto;
}
.quadrant.bottom .col { justify-content: flex-end; }
/* PUL BOYUTU AYARLARI */
.checker {
width: 160%; /* %80'den %94'e çıkarıldı - Daha büyük */
max-width: 65px; /* Çok büyük ekranlarda devasa olmasın */
aspect-ratio: 1/1;
border-radius: 50%;
margin: 1px 0; /* Dikey boşluk */
box-shadow: inset 0 0 8px rgba(0,0,0,0.6), 0 3px 5px rgba(0,0,0,0.4);
border: 1px solid rgba(0,0,0,0.3);
position: relative;
transition: transform 0.1s, box-shadow 0.2s;
z-index: 10;
}
.checker.white { background: radial-gradient(circle at 35% 35%, #fff, var(--checker-white)); }
.checker.black { background: radial-gradient(circle at 35% 35%, #666, var(--checker-black)); border-color: #000; }
.checker.selected {
box-shadow: 0 0 0 3px gold, 0 0 15px gold;
transform: scale(1.1);
z-index: 20;
}
/* SIKIŞTIRMA (STACK) AYARLARI - Pullar büyük olduğu için daha çok üst üste binmeli */
.col.stacked .checker {
margin-bottom: -45%; /* Bindirme oranı artırıldı */
}
.quadrant.bottom .col.stacked .checker {
margin-bottom: 0;
margin-top: -45%;
}
/* --- BAR (Middle) --- */
.bar-area {
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: 40px; /* Bar biraz daraltıldı */
transform: translateX(-50%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 5;
background: rgba(0,0,0,0.1); /* Hafif gölge */
}
.bar-box {
width: 100%;
height: 40%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.bar-box .checker { margin: -20% 0; width: 85%; } /* Bardaki pullar biraz daha sıkışık */
.bar-box.has-checkers { cursor: pointer; background: rgba(255,0,0,0.15); border-radius: 5px; }
/* --- DICE AREA --- */
.dice-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
gap: 15px;
z-index: 100;
pointer-events: none;
}
.die {
width: 44px;
height: 44px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 4px 10px rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
font-size: 22px;
font-weight: bold;
color: #000;
opacity: 0;
transform: scale(0);
transition: all 0.3s ease-out;
border: 1px solid #999;
}
.die.active { opacity: 1; transform: scale(1); }
/* --- NOTIFICATIONS & MODALS --- */
#notification {
position: absolute;
bottom: 10%;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.85);
color: #FFD700;
padding: 10px 25px;
border-radius: 30px;
font-size: 1.1rem;
font-weight: bold;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s;
white-space: nowrap;
z-index: 200;
box-shadow: 0 5px 15px rgba(0,0,0,0.5);
}
.modal-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.85);
display: none; justify-content: center; align-items: center;
z-index: 1000;
backdrop-filter: blur(3px);
}
.modal {
background: #222; color: white; padding: 25px;
border-radius: 12px; max-width: 400px; width: 90%;
text-align: center; border: 1px solid #444;
box-shadow: 0 10px 25px rgba(0,0,0,0.8);
}
.modal h2 { margin-top: 0; color: var(--wood-secondary); border-bottom: 1px solid #444; padding-bottom: 10px;}
.modal-buttons { margin-top: 20px; display: flex; justify-content: center; gap: 10px; }
select { padding: 8px; border-radius: 4px; border: none; background: #444; color: white; font-size: 1rem;}
#scoreboard {
display: flex; align-items: center; gap: 20px; font-weight: bold;
font-size: 1.1rem; color: #ccc;
}
.turn-indicator {
width: 12px; height: 12px; border-radius: 50%; display: inline-block; margin-right: 6px;
border: 1px solid #666;
}
.turn-white { background: #fff; box-shadow: 0 0 8px #fff; }
.turn-black { background: #333; border-color: #999; }
@media (max-width: 600px) {
header { flex-direction: column; height: auto; gap: 8px; padding: 10px; }
h1 { display: none; } /* Mobilde başlığı gizle yer aç */
.board { border-width: 6px; margin-top: 10px;}
.checker { width: 96%; } /* Mobilde daha da büyük olsun */
}
</style>
</head>
<body>
<header>
<div id="scoreboard">
<div id="p1-score"><span id="indicator-white" class="turn-indicator turn-white"></span>Beyaz: <span id="score-white">0</span></div>
<div id="p2-score"><span id="indicator-black" class="turn-indicator"></span>Siyah: <span id="score-black">0</span></div>
</div>
<h1>TÜRK TAVLASI</h1>
<div class="controls">
<button onclick="game.undo()" title="Geri Al">↩</button>
<button onclick="ui.toggleSound()" id="btn-sound" title="Ses">🔊</button>
<button onclick="ui.showRules()">?</button>
<button class="primary" onclick="ui.showNewGameModal()">Menü</button>
</div>
</header>
<div id="game-container">
<div class="board">
<div class="half-board">
<div class="quadrant top" id="quad-tl"></div> <div class="quadrant bottom" id="quad-bl"></div> </div>
<div class="half-board">
<div class="quadrant top" id="quad-tr"></div> <div class="quadrant bottom" id="quad-br"></div> </div>
<div class="checker-container"></div>
<div class="bar-area">
<div class="bar-box" id="bar-black-area"></div> <div class="bar-box" id="bar-white-area"></div> </div>
<div class="dice-container">
<div class="die" id="die1"></div>
<div class="die" id="die2"></div>
</div>
</div>
</div>
<div id="notification">Bildirim Alanı</div>
<div class="modal-overlay" id="modal-newgame">
<div class="modal">
<h2>Oyun Ayarları</h2>
<p style="margin-bottom: 15px;">
<label>Mod:</label><br>
<select id="game-mode" style="width: 100%; margin-top: 5px;">
<option value="pvp">2 Oyunculu (Aynı Cihaz)</option>
<option value="ai" selected>Yapay Zeka'ya Karşı</option>
</select>
</p>
<p id="ai-diff-container">
<label>Zorluk:</label><br>
<select id="ai-level" style="width: 100%; margin-top: 5px;">
<option value="easy">Kolay (Acemi)</option>
<option value="medium" selected>Orta (Dengeli)</option>
<option value="hard">Zor (Agresif)</option>
</select>
</p>
<div class="modal-buttons">
<button onclick="ui.hideModal('modal-newgame')">Kapat</button>
<button class="primary" onclick="game.startNewGame()">BAŞLA</button>
</div>
</div>
</div>
<div class="modal-overlay" id="modal-rules">
<div class="modal" style="text-align: left;">
<h2>Kurallar</h2>
<ul style="font-size: 0.9rem; line-height: 1.5; padding-left: 20px;">
<li><strong>Beyaz:</strong> Sağ altta toplar (Saat yönü tersi).</li>
<li><strong>Siyah:</strong> Sağ üstte toplar (Saat yönü).</li>
<li><strong>Kırma:</strong> Tek pula vurulabilir. Kırılan pul Bar'a gider.</li>
<li><strong>Giriş:</strong> Bar'daki pul girmeden hamle yapılamaz.</li>
<li><strong>Çift Zar:</strong> 4 kere oynanır (Örn: 6-6 gelirse 4 tane 6).</li>
</ul>
<div class="modal-buttons">
<button class="primary" onclick="ui.hideModal('modal-rules')">Tamam</button>
</div>
</div>
</div>
<div class="modal-overlay" id="modal-gameover">
<div class="modal">
<h2 id="winner-text">Oyun Bitti!</h2>
<p id="game-summary" style="white-space: pre-line; line-height: 1.6;"></p>
<div class="modal-buttons">
<button onclick="game.downloadSummary()">Özeti İndir</button>
<button class="primary" onclick="ui.showNewGameModal()">Yeni Oyun</button>
</div>
</div>
</div>
<script>
const PLAYER_WHITE = 1;
const PLAYER_BLACK = -1;
const BOARD_SIZE = 24;
class BackgammonGame {
constructor() {
this.board = Array(BOARD_SIZE).fill(0);
this.bar = { [PLAYER_WHITE]: 0, [PLAYER_BLACK]: 0 };
this.off = { [PLAYER_WHITE]: 0, [PLAYER_BLACK]: 0 };
this.turn = PLAYER_WHITE;
this.dice = [];
this.movesLeft = [];
this.selectedPoint = null;
this.history = [];
this.gameMode = 'ai';
this.aiLevel = 'medium';
this.isGameOver = false;
this.moveCount = 0;
this.initialSetup();
}
initialSetup() {
this.board.fill(0);
// Standart Dizilim
this.board[23] = 2;
this.board[12] = 5;
this.board[7] = 3;
this.board[5] = 5;
this.board[0] = -2;
this.board[11] = -5;
this.board[16] = -3;
this.board[18] = -5;
this.bar = { [PLAYER_WHITE]: 0, [PLAYER_BLACK]: 0 };
this.off = { [PLAYER_WHITE]: 0, [PLAYER_BLACK]: 0 };
this.turn = PLAYER_WHITE;
this.dice = [];
this.movesLeft = [];
this.history = [];
this.isGameOver = false;
this.moveCount = 0;
this.saveState();
}
startNewGame() {
const modeEl = document.getElementById('game-mode');
const levelEl = document.getElementById('ai-level');
this.gameMode = modeEl.value;
this.aiLevel = levelEl.value;
this.initialSetup();
ui.hideModal('modal-newgame');
ui.hideModal('modal-gameover');
ui.renderBoard();
ui.updateScoreboard();
ui.notify("Oyun Başladı! Bol şans.");
setTimeout(() => this.rollDice(), 800);
}
saveState() {
const state = JSON.stringify({
board: this.board, bar: this.bar, off: this.off,
turn: this.turn, dice: this.dice, movesLeft: this.movesLeft
});
if (this.history.length > 30) this.history.shift();
this.history.push(state);
localStorage.setItem('tavla_save_v2', state);
}
undo() {
if (this.gameMode === 'ai' && this.turn === PLAYER_BLACK) return;
if (this.history.length < 2) {
ui.notify("Geri alınacak hamle yok.");
return;
}
this.history.pop();
const prevState = JSON.parse(this.history[this.history.length - 1]);
this.board = prevState.board;
this.bar = prevState.bar;
this.off = prevState.off;
this.turn = prevState.turn;
this.dice = prevState.dice;
this.movesLeft = prevState.movesLeft;
this.selectedPoint = null;
ui.renderBoard();
ui.renderDice();
ui.highlightValidMoves([]);
}
rollDice() {
if (this.isGameOver) return;
audio.play('roll');
const d1 = Math.floor(Math.random() * 6) + 1;
const d2 = Math.floor(Math.random() * 6) + 1;
this.dice = [d1, d2];
if (d1 === d2) {
this.movesLeft = [d1, d1, d1, d1];
ui.notify("DÜŞEŞ! (" + d1 + "-" + d1 + ")");
} else {
this.movesLeft = [d1, d2];
}
ui.renderDice();
if (!this.hasAnyValidMove()) {
setTimeout(() => {
ui.notify("Hamle yok. Sıra geçiyor.");
this.endTurn();
}, 2000);
} else {
if (this.gameMode === 'ai' && this.turn === PLAYER_BLACK) {
setTimeout(() => ai.playTurn(), 1000);
}
}
}
hasAnyValidMove() {
if (this.bar[this.turn] > 0) {
const entryPoints = [...new Set(this.movesLeft)];
for (let die of entryPoints) {
const target = this.turn === PLAYER_WHITE ? (24 - die) : (die - 1);
if (this.isValidTarget(target)) return true;
}
return false;
}
for (let i = 0; i < BOARD_SIZE; i++) {
if (Math.sign(this.board[i]) === this.turn) {
const moves = [...new Set(this.movesLeft)];
for (let die of moves) {
let target = this.turn === PLAYER_WHITE ? i - die : i + die;
if (this.canBearOff()) {
if (this.isValidBearOffMove(i, die)) return true;
}
if (target >= 0 && target < 24 && this.isValidTarget(target)) return true;
}
}
}
return false;
}
isValidTarget(idx) {
if (idx < 0 || idx >= BOARD_SIZE) return false;
const count = this.board[idx];
if (count === 0) return true;
if (Math.sign(count) === this.turn) return true;
if (Math.sign(count) !== this.turn && Math.abs(count) === 1) return true;
return false;
}
getValidMovesForPoint(pointIdx) {
let validDestinations = [];
let uniqueDice = [...new Set(this.movesLeft)];
if (this.bar[this.turn] > 0) {
if (pointIdx !== 'bar') return [];
uniqueDice.forEach(die => {
let target = this.turn === PLAYER_WHITE ? (24 - die) : (die - 1);
if (this.isValidTarget(target)) {
validDestinations.push({ target: target, die: die, type: 'normal' });
}
});
return validDestinations;
}
if (pointIdx === 'bar' || Math.sign(this.board[pointIdx]) !== this.turn) return [];
uniqueDice.forEach(die => {
let target = this.turn === PLAYER_WHITE ? pointIdx - die : pointIdx + die;
if (target >= 0 && target < BOARD_SIZE) {
if (this.isValidTarget(target)) {
validDestinations.push({ target: target, die: die, type: 'normal' });
}
} else if (this.canBearOff()) {
if (this.isValidBearOffMove(pointIdx, die)) {
validDestinations.push({ target: 'off', die: die, type: 'off' });
}
}
});
return validDestinations;
}
canBearOff() {
let start = this.turn === PLAYER_WHITE ? 6 : 0;
let end = this.turn === PLAYER_WHITE ? 23 : 17;
if (this.bar[this.turn] > 0) return false;
for (let i = start; i <= end; i++) {
if (Math.sign(this.board[i]) === this.turn) return false;
}
return true;
}
isValidBearOffMove(fromIdx, die) {
let dist = this.turn === PLAYER_WHITE ? fromIdx + 1 : 24 - fromIdx;
if (dist === die) return true;
if (die > dist) {
if (this.turn === PLAYER_WHITE) {
for (let i = fromIdx + 1; i <= 5; i++) {
if (this.board[i] > 0) return false;
}
} else {
for (let i = fromIdx - 1; i >= 18; i--) {
if (this.board[i] < 0) return false;
}
}
return true;
}
return false;
}
makeMove(from, to, die) {
this.saveState();
audio.play('move');
this.moveCount++;
if (from === 'bar') {
this.bar[this.turn]--;
} else {
this.board[from] -= this.turn;
}
if (to === 'off') {
this.off[this.turn]++;
audio.play('bearoff');
} else {
if (this.board[to] !== 0 && Math.sign(this.board[to]) !== this.turn) {
this.bar[-this.turn]++;
this.board[to] = 0;
audio.play('hit');
ui.notify("KIRILDI!");
}
this.board[to] += this.turn;
}
const dieIdx = this.movesLeft.indexOf(die);
if (dieIdx > -1) this.movesLeft.splice(dieIdx, 1);
ui.renderBoard();
ui.renderDice();
if (this.checkWin()) {
this.gameOver();
return;
}
if (this.movesLeft.length === 0) {
setTimeout(() => this.endTurn(), 500);
} else if (!this.hasAnyValidMove()) {
setTimeout(() => {
ui.notify("Kalan zarlarla hamle yok.");
this.endTurn();
}, 1500);
} else {
if (this.gameMode === 'ai' && this.turn === PLAYER_BLACK) {
setTimeout(() => ai.playTurn(), 800);
}
}
}
endTurn() {
this.turn = -this.turn;
this.movesLeft = [];
this.dice = [];
ui.renderDice();
ui.updateScoreboard();
ui.selectedPoint = null;
ui.highlightValidMoves([]);
if (this.gameMode === 'ai' && this.turn === PLAYER_BLACK) {
ui.notify("Sıra Siyah'ta (Düşünüyor...)");
setTimeout(() => this.rollDice(), 1000);
} else {
ui.notify(this.turn === PLAYER_WHITE ? "Sıra Beyaz'da" : "Sıra Siyah'ta");
setTimeout(() => this.rollDice(), 500);
}
}
checkWin() { return this.off[this.turn] === 15; }
gameOver() {
this.isGameOver = true;
const winner = this.turn === PLAYER_WHITE ? "BEYAZ" : "SİYAH";
document.getElementById('winner-text').innerText = winner + " KAZANDI!";
let summary = `Toplam Hamle: ${this.moveCount}\n`;
summary += `Beyaz Toplanan: ${this.off[PLAYER_WHITE]}\n`;
summary += `Siyah Toplanan: ${this.off[PLAYER_BLACK]}\n`;
if (this.off[-this.turn] === 0) summary += "\nMARS (2 Kat)!";
document.getElementById('game-summary').innerText = summary;
ui.showModal('modal-gameover');
audio.play('win');
}
downloadSummary() {
const text = document.getElementById('game-summary').innerText;
const blob = new Blob([text], { type: 'text/plain' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'tavla_ozet.txt';
a.click();
}
}
class AI {
playTurn() {
if (game.turn !== PLAYER_BLACK || game.movesLeft.length === 0) return;
let sources = [];
if (game.bar[PLAYER_BLACK] > 0) {
sources = ['bar'];
} else {
for (let i = 0; i < BOARD_SIZE; i++) {
if (game.board[i] < 0) sources.push(i);
}
}
let dieToUse = game.movesLeft[0];
let candidates = [];
sources.forEach(source => {
const moves = game.getValidMovesForPoint(source);
moves.forEach(m => {
if (game.movesLeft.includes(m.die)) {
candidates.push({ source: source, move: m });
}
});
});
if (candidates.length === 0) return;
let bestMove = null;
let bestScore = -9999;
candidates.forEach(cand => {
let score = this.evaluateMove(cand.source, cand.move);
if (score > bestScore) {
bestScore = score;
bestMove = cand;
}
});
if (bestMove) {
game.makeMove(bestMove.source, bestMove.move.target, bestMove.move.die);
}
}
evaluateMove(from, moveData) {
let score = 0;
let target = moveData.target;
if (target === 'off') return 2000;
// Kırma
if (game.board[target] > 0) score += 600;
// Kapı Alma
if (game.board[target] < -1) score += 100;
if (game.board[target] === -1) score += 300;
// Açık Verme Riski
if (game.board[target] === 0) {
score -= 50;
if (target >= 18) score += 40;
}
// Evden kaçış (Siyah için 0-5 arası tehlikeli)
if (from !== 'bar' && from < 6) score += 50;
// Zorluk Varyasyonu
if (game.aiLevel === 'easy') score += Math.random() * 800;
if (game.aiLevel === 'medium') score += Math.random() * 200;
return score;
}
}
class UI {
constructor() {
this.boardEl = document.querySelector('.checker-container');
this.soundEnabled = true;
}
initBoardDOM() {
this.boardEl.innerHTML = '';
document.getElementById('quad-tl').innerHTML = '';
document.getElementById('quad-tr').innerHTML = '';
document.getElementById('quad-bl').innerHTML = '';
document.getElementById('quad-br').innerHTML = '';
const createPoints = (parent, startIdx, endIdx) => {
let indices = [];
if (startIdx < endIdx) {
for (let i=startIdx; i<=endIdx; i++) indices.push(i);
} else {
for (let i=startIdx; i>=endIdx; i--) indices.push(i);
}
indices.forEach(idx => {
let pt = document.createElement('div');
pt.className = 'point';
pt.dataset.index = idx;
pt.onclick = () => this.handlePointClick(idx);
let col = document.createElement('div');
col.className = 'col';
col.id = `col-${idx}`;
pt.appendChild(col);
parent.appendChild(pt);
});
};
createPoints(document.getElementById('quad-tl'), 12, 17);
createPoints(document.getElementById('quad-tr'), 18, 23);
createPoints(document.getElementById('quad-bl'), 11, 6);
createPoints(document.getElementById('quad-br'), 5, 0);
document.getElementById('bar-white-area').onclick = () => this.handleBarClick(PLAYER_WHITE);
document.getElementById('bar-black-area').onclick = () => this.handleBarClick(PLAYER_BLACK);
}
renderBoard() {
for (let i = 0; i < BOARD_SIZE; i++) {
const col = document.getElementById(`col-${i}`);
col.innerHTML = '';
col.classList.remove('stacked');
const count = game.board[i];
const player = Math.sign(count);
const numCheckers = Math.abs(count);
if (numCheckers > 5) col.classList.add('stacked');
for (let k = 0; k < numCheckers; k++) {
const checker = document.createElement('div');
checker.className = `checker ${player === 1 ? 'white' : 'black'}`;
if (game.selectedPoint === i && k === numCheckers - 1) {
checker.classList.add('selected');
}
col.appendChild(checker);
}
}
const renderBar = (pid, elemId) => {
const el = document.getElementById(elemId);
el.innerHTML = '';
const count = game.bar[pid];
el.classList.toggle('has-checkers', count > 0);
for(let k=0; k<count; k++) {
const c = document.createElement('div');
c.className = `checker ${pid === 1 ? 'white' : 'black'}`;
if (game.selectedPoint === 'bar' && game.turn === pid && k === count - 1) {
c.classList.add('selected');
}
el.appendChild(c);
}
};
renderBar(PLAYER_WHITE, 'bar-white-area');
renderBar(PLAYER_BLACK, 'bar-black-area');
this.updateScoreboard();
}
renderDice() {
const d1 = document.getElementById('die1');
const d2 = document.getElementById('die2');
if (game.movesLeft.length === 0 && game.dice.length === 0) {
d1.className = 'die'; d2.className = 'die';
return;
}
d1.innerText = game.dice[0] || '';
d2.innerText = game.dice[1] || '';
d1.classList.add('active');
d2.classList.add('active');
}
updateScoreboard() {
document.getElementById('score-white').innerText = game.off[PLAYER_WHITE];
document.getElementById('score-black').innerText = game.off[PLAYER_BLACK];
document.getElementById('indicator-white').style.opacity = game.turn === PLAYER_WHITE ? 1 : 0.3;
document.getElementById('indicator-black').style.opacity = game.turn === PLAYER_BLACK ? 1 : 0.3;
}
handlePointClick(idx) {
if (this.canBearOffSelection && game.selectedPoint === idx) {
const moves = game.getValidMovesForPoint(idx);
const offMove = moves.find(m => m.target === 'off');
if (offMove) {
game.makeMove(idx, 'off', offMove.die);
this.highlightValidMoves([]);
game.selectedPoint = null;
this.canBearOffSelection = false;
return;
}
}
if (game.gameMode === 'ai' && game.turn === PLAYER_BLACK) return;
const pointEl = document.querySelector(`.point[data-index='${idx}']`);
if (pointEl.classList.contains('valid-target') && game.selectedPoint !== idx) {
const moves = game.getValidMovesForPoint(game.selectedPoint);
const move = moves.find(m => m.target === idx);
if (move) {
game.makeMove(game.selectedPoint, idx, move.die);
this.highlightValidMoves([]);
game.selectedPoint = null;
return;
}
}
if (game.bar[game.turn] > 0) {
this.notify("Bar'da taşınız var! Önce onu oynayın.");
return;
}
if (Math.sign(game.board[idx]) === game.turn) {
if (game.selectedPoint === idx && !this.canBearOffSelection) {
game.selectedPoint = null;
this.highlightValidMoves([]);
this.renderBoard();
} else {
game.selectedPoint = idx;
this.renderBoard();
const moves = game.getValidMovesForPoint(idx);
this.highlightValidMoves(moves);
}
}
}
handleBarClick(pid) {
if (game.gameMode === 'ai' && game.turn === PLAYER_BLACK) return;
if (pid !== game.turn) return;
if (game.bar[pid] === 0) return;
game.selectedPoint = 'bar';
this.renderBoard();
const moves = game.getValidMovesForPoint('bar');
this.highlightValidMoves(moves);
}
highlightValidMoves(moves) {
document.querySelectorAll('.point').forEach(el => {
el.classList.remove('valid-target');
});
moves.forEach(m => {
if (m.type === 'off') {
this.notify("Taşı dışarı atabilirsiniz (Tekrar tıklayın)");
const pt = document.querySelector(`.point[data-index='${game.selectedPoint}']`);
if(pt) pt.classList.add('valid-target');
} else {
const el = document.querySelector(`.point[data-index='${m.target}']`);
if(el) el.classList.add('valid-target');
}
});
if (moves.some(m => m.target === 'off')) {
this.canBearOffSelection = true;
} else {
this.canBearOffSelection = false;
}
}
notify(msg) {
const el = document.getElementById('notification');
el.innerText = msg;
el.style.opacity = 1;
clearTimeout(this.notifyTimer);
this.notifyTimer = setTimeout(() => {
el.style.opacity = 0;
}, 2500);
}
showModal(id) { document.getElementById(id).style.display = 'flex'; }
hideModal(id) { document.getElementById(id).style.display = 'none'; }
showNewGameModal() { this.showModal('modal-newgame'); }
showRules() { this.showModal('modal-rules'); }
toggleSound() {
this.soundEnabled = !this.soundEnabled;
document.getElementById('btn-sound').innerText = this.soundEnabled ? '🔊' : '🔇';
}
}
class AudioManager {
constructor() {
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
}
play(type) {
if (!ui.soundEnabled) return;
if (this.ctx.state === 'suspended') this.ctx.resume();
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.connect(gain);
gain.connect(this.ctx.destination);
const now = this.ctx.currentTime;
if (type === 'roll') {
osc.type = 'square';
osc.frequency.setValueAtTime(150, now);
osc.frequency.exponentialRampToValueAtTime(40, now + 0.1);
gain.gain.setValueAtTime(0.1, now);
gain.gain.exponentialRampToValueAtTime(0.01, now + 0.1);
osc.start(now); osc.stop(now + 0.1);
} else if (type === 'move') {
osc.type = 'sine';
osc.frequency.setValueAtTime(300, now);
gain.gain.setValueAtTime(0.1, now);
gain.gain.exponentialRampToValueAtTime(0.01, now + 0.1);
osc.start(now); osc.stop(now + 0.1);
} else if (type === 'hit') {
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(100, now);
osc.frequency.linearRampToValueAtTime(600, now + 0.1);
gain.gain.setValueAtTime(0.1, now);
gain.gain.linearRampToValueAtTime(0.01, now + 0.1);
osc.start(now); osc.stop(now + 0.1);
} else if (type === 'bearoff') {
osc.type = 'triangle';
osc.frequency.setValueAtTime(600, now);
osc.frequency.exponentialRampToValueAtTime(1000, now + 0.2);
gain.gain.setValueAtTime(0.1, now);
gain.gain.linearRampToValueAtTime(0.01, now + 0.2);
osc.start(now); osc.stop(now + 0.2);
} else if (type === 'win') {
this.playTone(440, 0, 0.2);
this.playTone(554, 0.2, 0.2);
this.playTone(659, 0.4, 0.4);
}
}
playTone(freq, delay, dur) {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.connect(gain);
gain.connect(this.ctx.destination);
const now = this.ctx.currentTime + delay;
osc.frequency.value = freq;
gain.gain.setValueAtTime(0.1, now);
gain.gain.linearRampToValueAtTime(0.01, now + dur);
osc.start(now); osc.stop(now + dur);
}
}
const game = new BackgammonGame();
const ui = new UI();
const ai = new AI();
const audio = new AudioManager();
window.onload = () => {
ui.initBoardDOM();
const saved = localStorage.getItem('tavla_save_v2');
if (saved) {
try {
const s = JSON.parse(saved);
game.board = s.board;
game.bar = s.bar;
game.off = s.off;
game.turn = s.turn;
game.dice = s.dice;
game.movesLeft = s.movesLeft;
ui.renderBoard();
ui.renderDice();
ui.updateScoreboard();
ui.notify("Oyun yüklendi.");
} catch(e) { game.startNewGame(); }
} else {
ui.showNewGameModal();
}
};
</script>
</body>
</html>Kodları metin dosyasına atıp tavla.html olarak kaydedip deneyimleyebilirsiniz gemini 3 ile ilk projem bu şekilde iyi forumlar geliştirmek size kalmış