/**
* Regel-Definitionen für Connect 4 (Standard und 3D).
* Implementiert "elementary" und "advanced" Strategiebäume.
* @fileoverview
* @author Alexander Wolf
*/
const Connect4RulesLibrary = {
// --- UTILS ---
utils: {
/**
* Checks if a move leads to a win for the specified player.
* @param {Connect4Regular|Connect43D} game
* @param {number} move
* @param {number} player
*/
canWin: (game, move, player) => {
const sim = game.clone();
sim.currentPlayer = player;
sim.makeMove(move);
return sim.winner === player;
},
/**
* Checks if opponent can win on their NEXT turn if we make 'myMove'.
* (Lookahead 1)
*/
givesOpponentWin: (game, myMove, me) => {
const sim = game.clone();
sim.currentPlayer = me;
sim.makeMove(myMove);
if (sim.winner !== NONE) return false;
const opp = sim.currentPlayer;
const oppMoves = sim.getAllValidMoves();
for (let om of oppMoves) {
const sim2 = sim.clone();
sim2.makeMove(om);
if (sim2.winner === opp) return true;
}
return false;
},
/**
* Zählt wie viele Gewinndrohungen ein Spieler hat.
* (Spalten, in denen ein Stein zum Sieg führen würde)
*/
countThreats: (game, player) => {
let threats = 0;
for (const m of game.getAllValidMoves()) {
if (Connect4RulesLibrary.utils.canWin(game, m, player)) threats++;
}
return threats;
}
},
// --- ATOMIC RULES (Regular) ---
regular: {
win: new AtomicRule("Siegzug", "Gewinne sofort wenn möglich", (game) => {
for (let m of game.getAllValidMoves()) {
if (Connect4RulesLibrary.utils.canWin(game, m, game.currentPlayer)) return m;
}
return null;
}),
block: new AtomicRule("Blocken", "Verhindere Sieg des Gegners", (game) => {
const opp = game.currentPlayer === PLAYER1 ? PLAYER2 : PLAYER1;
for (let m of game.getAllValidMoves()) {
if (Connect4RulesLibrary.utils.canWin(game, m, opp)) return m;
}
return null;
}),
preferCenter: new AtomicRule("Zentrum", "Spiele in die mittlere Spalte", (game) => {
const center = Math.floor(game.cols / 2);
const moves = game.getAllValidMoves();
if (moves.includes(center)) return center;
return null;
}),
avoidBadMoves: new AtomicRule("Sicher spielen", "Vermeide Züge, die dem Gegner den Sieg schenken", (game) => {
const moves = game.getAllValidMoves();
if (moves.length === 1) return moves[0];
const safeMoves = moves.filter(m => !Connect4RulesLibrary.utils.givesOpponentWin(game, m, game.currentPlayer));
if (safeMoves.length > 0) {
return safeMoves[Math.floor(Math.random() * safeMoves.length)];
}
return null;
}),
setupThreat: new AtomicRule("Aufbau", "Baue eine Drohung auf (3er mit Lücke)", (game) => {
const me = game.currentPlayer;
const moves = game.getAllValidMoves();
// Suche Zug, der uns eine neue Drohung gibt, ohne dem Gegner zu helfen
let bestMove = null;
let bestScore = -Infinity;
for (const m of moves) {
if (Connect4RulesLibrary.utils.givesOpponentWin(game, m, me)) continue;
// Zähle unsere Drohungen nach dem Zug
const sim = game.clone();
sim.currentPlayer = me;
sim.makeMove(m);
const myThreats = Connect4RulesLibrary.utils.countThreats(sim, me);
if (myThreats > bestScore) {
bestScore = myThreats;
bestMove = m;
}
}
return bestScore > 0 ? bestMove : null;
}),
preferNearCenter: new AtomicRule("Nahe Mitte", "Spalten nahe der Mitte bevorzugen", (game) => {
const moves = game.getAllValidMoves();
if (moves.length === 0) return null;
const center = game.cols / 2;
moves.sort((a, b) => Math.abs(a - center) - Math.abs(b - center));
return moves[0];
})
},
// --- ATOMIC RULES (3D) ---
d3: {
win: new AtomicRule("3D Sieg", "Gewinne 3D sofort", (game) => {
for (let m of game.getAllValidMoves()) {
if (Connect4RulesLibrary.utils.canWin(game, m, game.currentPlayer)) return m;
}
return null;
}),
block: new AtomicRule("3D Block", "Verhindere 3D Sieg", (game) => {
const opp = game.currentPlayer === PLAYER1 ? PLAYER2 : PLAYER1;
for (let m of game.getAllValidMoves()) {
if (Connect4RulesLibrary.utils.canWin(game, m, opp)) return m;
}
return null;
}),
preferCenterPole: new AtomicRule("Zentrum-Säulen", "Spiele nahe der Mitte des Boards (XZ)", (game) => {
const moves = game.getAllValidMoves();
if (moves.length === 0) return null;
const s = game.size;
const center = (s-1)/2;
moves.sort((a, b) => {
const ax = a % s, az = Math.floor(a/s);
const bx = b % s, bz = Math.floor(b/s);
const da = Math.abs(ax-center) + Math.abs(az-center);
const db = Math.abs(bx-center) + Math.abs(bz-center);
return da - db;
});
const limit = Math.min(moves.length, 3);
return moves[Math.floor(Math.random() * limit)];
}),
avoidBadMoves3D: new AtomicRule("3D Sicher", "Vermeide 3D-Züge die zum Verlust führen", (game) => {
const moves = game.getAllValidMoves();
if (moves.length <= 1) return moves[0] || null;
const safeMoves = moves.filter(m => !Connect4RulesLibrary.utils.givesOpponentWin(game, m, game.currentPlayer));
return safeMoves.length > 0 ? safeMoves[Math.floor(Math.random() * safeMoves.length)] : null;
})
},
// --- TREE BUILDER ---
/**
* Erstellt Entscheidungsbaum für Connect4.
* @param {string} variant - 'regular' | '3d'
* @param {string} [level='advanced'] - 'elementary' | 'advanced' (Aliases: 'simple'→elementary, 'complex'→advanced)
* @returns {DecisionTree}
*/
createTree: (variant, level) => {
// Alias-Mapping für Rückwärtskompatibilität
const normalizedLevel = (level === 'simple') ? 'elementary'
: (level === 'complex') ? 'advanced'
: (level || 'advanced');
const rules = (variant === '3d') ? Connect4RulesLibrary.d3 : Connect4RulesLibrary.regular;
const randomFallback = new AtomicRule("Zufall", "Fallback", (game) => {
const moves = game.getAllValidMoves();
return moves.length > 0 ? moves[Math.floor(Math.random() * moves.length)] : null;
});
if (normalizedLevel === 'elementary') {
// Elementary: Win → Block → Center → Random (flache Liste)
const root = new RuleGroup("Wurzel", "Einfache Prioritätsliste");
root.add(rules.win.clone());
root.add(rules.block.clone());
root.add((variant === '3d') ? rules.preferCenterPole.clone() : rules.preferCenter.clone());
root.add(randomFallback);
return new DecisionTree(`C4-${variant}-elementary`, root);
}
// ═══ Advanced: ConditionNode-basierte Verzweigungen ═══
const root = new RuleGroup("Wurzel", "Strategischer Entscheidungsbaum");
// 1. Existenz: Win / Block
const survival = new RuleGroup("Existenz");
survival.add(rules.win.clone());
survival.add(rules.block.clone());
root.add(survival);
if (variant === '3d') {
// 3D: Bedingung → Zentrum frei?
const centerCheck = new ConditionNode(
"Zentrum frei?",
"Sind die zentralen Säulen verfügbar?",
(game) => {
const s = game.size;
const center = (s - 1) / 2;
const moves = game.getAllValidMoves();
return moves.some(m => {
const x = m % s, z = Math.floor(m / s);
return Math.abs(x - center) <= 0.5 && Math.abs(z - center) <= 0.5;
});
},
// THEN: Zentrum besetzen
rules.preferCenterPole.clone(),
// ELSE: Sicher spielen
rules.avoidBadMoves3D.clone()
);
root.add(centerCheck);
} else {
// Regular: Bedingung → Unter Druck? (Gegner droht)
const pressureCheck = new ConditionNode(
"Unter Druck?",
"Hat der Gegner aufgebaute Drohungen?",
(game) => {
const opp = game.currentPlayer === PLAYER1 ? PLAYER2 : PLAYER1;
return Connect4RulesLibrary.utils.countThreats(game, opp) >= 1;
},
// THEN: Defensiv — sichere Züge, Gegner-Drohungen neutralisieren
new RuleGroup("🛡️ Defensiv", "", [
rules.avoidBadMoves.clone(),
rules.preferCenter.clone()
]),
// ELSE: Offensiv — eigene Drohungen aufbauen
new RuleGroup("⚔️ Offensiv", "", [
rules.setupThreat.clone(),
rules.preferCenter.clone(),
rules.preferNearCenter.clone()
])
);
root.add(pressureCheck);
}
// Fallback
root.add(randomFallback);
return new DecisionTree(`C4-${variant}-advanced`, root);
}
};