/**
* @fileoverview Controller für 3D Connect4 (4x4x4 Cube)
*
* Extends BaseGameController mit 3D Connect4-Support und Multi-Panel Rendering.
* Visualisiert 4 unterschiedliche Perspektiven des Spielwürfels.
*
* @class Connect43DController
* @extends BaseGameController
*/
class Connect43DController extends BaseGameController {
constructor() {
super('connect4-3d', 'gameCanvas');
}
createGame() {
return new Connect43D(4);
}
reset() {
this.canvas.width = 800; // Wider for 4 panels
this.canvas.height = 500; // Taller for 2 rows of views
super.reset();
}
drawGame() {
Connect4Renderer.draw3D(this.canvas, this.game);
}
coordsToMove(mx, my) {
const w = this.canvas.width;
const s = this.game.size;
const pad = 10;
const boardW = (w - pad * (s + 1)) / s;
const boardH = boardW;
// Match renderer logic (topY = 40)
const topY = 40;
const secondY = topY + boardH + 60; // Offset for Side View
// Check Top View (Front View)
if (my >= topY && my <= topY + boardH) {
for (let z = 0; z < s; z++) {
const startX = pad + z * (boardW + pad);
if (mx >= startX && mx <= startX + boardW) {
const x = Math.floor((mx - startX) / (boardW / s));
if (x >= 0 && x < s) {
// Move is x + z*size. In Top view we select X and Slice Z (now Y)
// Wait, if "Ebene Y=..." is the slice, then z loop is Y?
// The renderer loop: for (let z = 0; z < s; z++) -> Label "Ebene Y={z+1}"
// So 'z' var is the new 'y' dim.
// But game logic uses x + z*size for a column. Check game logic.
// Standard Connect4 3D: 16 columns (4x4 grid on floor).
// Pieces stack up in Y (Height).
// The input usually selects the column (x, z).
// If we are looking "von vorne" at slices.
// If standard is X (width), Y (height), Z (depth).
// "Ebene Y=1" means Slice at Depth 1? No, usually Y is height.
// If "Ebene Y=1" means Depth=1, then user renamed Z to Y.
// Let's assume the move is still (x, z).
// In Top/Front view: we click on a slice.
// slice index 'z' (loop var).
// grid x 'x'.
// Move = x + z * size.
return x + z * s;
}
}
}
}
// Check Side View
if (my >= secondY && my <= secondY + boardH) {
for (let x = 0; x < s; x++) {
const startX = pad + x * (boardW + pad);
if (mx >= startX && mx <= startX + boardW) {
// In Side View (X Slices), we see Z (Depth, now Y?) vs Y (Height, now Z?)
// If we click here, we are selecting a column?
// A move is defined by picking a vertical column (x, z).
// In Side view of slice X, horizontal axis is Z.
// So we need to compute 'z' from mouse X inside the grid.
const z = Math.floor((mx - startX) / (boardW / s));
if (z >= 0 && z < s) {
// We found x (loop var) and z (calculated).
return x + z * s;
}
}
}
}
return null;
}
createAIAgent(type) {
if (type === 'random') {
return new RandomAgent();
} else if (type === 'rule_simple' || type === 'rule_elementary') {
return new RuleBasedAgent(Connect4RulesLibrary.createTree('3d', 'elementary'));
} else if (type === 'rule_complex' || type === 'rule_advanced') {
return new RuleBasedAgent(Connect4RulesLibrary.createTree('3d', 'advanced'));
} else if (type.startsWith('minimax')) {
const profileMap = { minimax: 'v1_baseline', minimax_positional: 'v2_positional', minimax_aggressive: 'v3_aggressive' };
const profile = profileMap[type] || 'v1_baseline';
const regKey = `connect4:3d:${profile}`;
const heuristicFn = (typeof HeuristicRegistry !== 'undefined' && HeuristicRegistry.has(regKey))
? HeuristicRegistry.get(regKey).evaluate.bind(HeuristicRegistry.get(regKey))
: (typeof HeuristicRegistry !== 'undefined' && HeuristicRegistry.has('connect4', '3d'))
? HeuristicRegistry.get('connect4', '3d').evaluate.bind(HeuristicRegistry.get('connect4', '3d'))
: ((gameState, player) => {
if (gameState.winner === player) return 100000;
if (gameState.winner !== NONE && gameState.winner !== DRAW) return -100000;
return 0;
});
return new MinimaxAgent({
name: `Minimax 3D (${profile})`,
maxDepth: 3,
useAlphaBeta: true,
heuristicFn
});
}
return new RandomAgent();
}
}