Diamond Mine
The Tetgame answer to the Gold Room.
Read-only starter
The Diamond Mine is intended to expose the deeper machinery of Tetgame Rabbots: behavior files, scoring nudges, strategy filters, voice loaders, backups, and eventually safe editing tools.
Current pass: this page only lists and displays local Tetgame JS/JSON files from
js/rabbots, js/personalities, and js/voices. It does not save changes yet.Behavior / voice file viewer
js/personalities/silver_kahuna.js
/*
* Tetgame Rabbot: Silver Kahuna
*
* v10 — Display-Socket Aware Reef Cage / Corner-First from tested v8 baseline.
* Tetgame supplies legal moves in ctx.tokenMoves and ctx.tetMoves; this file
* only chooses among those legal moves. v10 reads tetgame123 display socket
* fields first so center/corner/edge-mid logic matches the game record.
*
* Silver's personality target:
* - Swim only far enough to make a reef, then stop feeding long runway boards.
* - Keep the wave open longer, but do not let a shooter build the gun platform.
* - Swim twice early unless the rival has a real shot; do not waste wake time on dull zero-point blocks.
* - Use a first-action swim / second-action deadeye denial rhythm once the wake is alive.
* - Watch for the opponent's best shooting lanes and block them at useful times.
* - Seed reef tokens in his own wake so Pistolero-style path sniping cannot freely
* harvest the whole helix.
* - Take center sockets along the swim: centers do not confine the wake like
* corners, and they deny Pistolero the sub/full-tet cascades.
* - When the rival owns or threatens a center, steal one of that tet's corners /
* junctions so he cannot finish the full tet.
* - Prefer corners with several connected paths, but avoid dull corners that
* lock Silver out of further expansion.
* - Branch once in a while after the main wake exists, so the opponent cannot
* counter one perfectly straight line forever.
* - Once the wave is long enough or the board locks, harvest long paths first.
* - Then favor center / high-value socket moves that can cascade through subs and full tets.
*
* Optional future engine hook:
* - If ctx.opponentTokenMoves / ctx.rivalTokenMoves / ctx.enemyTokenMoves exists,
* Silver will use it directly to block the opponent's highest-value socket.
* - Without that hook, he falls back to anti-Pistolero threat heuristics based on
* tet id, socket id, current score, and board phase.
*
* Voice lines live separately in js/voices/silver_kahuna.json.
*/
(function(global){
'use strict';
const registry = global.TetgameRabbotBrains = global.TetgameRabbotBrains || {};
function cloneMove(move, fallbackReason){
if (!move) return null;
const copy = Object.assign({}, move);
if (!copy.reason && fallbackReason) copy.reason = fallbackReason;
return copy;
}
function moveAt(list, index){
return Array.isArray(list) && list.length > index ? list[index] : null;
}
function pass(reason){
return {type:'pass', score:-1, reason:reason || 'no legal action found'};
}
function scoreOf(move){
return Number(move && move.score || 0);
}
function canOpen(ctx){
return ctx && Number(ctx.tetCount || 0) <= 0;
}
function activeCount(ctx){
return Math.max(2, Number(ctx && ctx.activeCount || 2));
}
function socketMeta(move){
return move && move.socket && move.socket.metadata ? move.socket.metadata : {};
}
function internalSocketToDisplayIndex(internalIndex){
// Tetgame's engine still uses internal socket IDs for geometry/rules.
// The game record and rabbot move objects expose display/public socket IDs
// so the brain can reason from the visible gray-tet layout:
// display 0 = center
// display 1,3,5,8 = corners
// display 2,4,6,7,9,10 = edge-mid sockets
// Internal -> display mapping from tetgame123_socket_record.php:
// 10->0, 0->1, 5->2, 2->3, 7->4, 1->5, 4->6, 8->7, 3->8, 6->9, 9->10
const map = {
10:0,
0:1,
5:2,
2:3,
7:4,
1:5,
4:6,
8:7,
3:8,
6:9,
9:10
};
const key = Number(internalIndex);
return Object.prototype.hasOwnProperty.call(map, key) ? map[key] : key;
}
function numberFromCandidate(value){
if (value === undefined || value === null || value === '') return null;
const n = Number(value);
return Number.isFinite(n) ? n : null;
}
function socketIndex(move){
if (!move) return -1;
// IMPORTANT: prefer the display/public socket fields added by tetgame123.
// move.socketIndex and socket.metadata.socketIndex are internal engine IDs,
// which caused v9 to reason about the wrong geometry even while the game
// record printed the correct center/corner/edge-mid labels.
const meta = socketMeta(move);
const displayCandidates = [
move.displaySocketIndex,
move.socketIndexDisplay,
move.publicSocketIndex,
move.socketIndexPublic,
meta.displaySocketIndex,
meta.socketIndexDisplay,
meta.publicSocketIndex,
meta.socketIndexPublic
];
for (let i = 0; i < displayCandidates.length; i++) {
const n = numberFromCandidate(displayCandidates[i]);
if (n !== null) return n;
}
const internalCandidates = [
move.socketIndexInternal,
move.internalSocketIndex,
move.socketIndex,
meta.socketIndexInternal,
meta.internalSocketIndex,
meta.socketIndex
];
for (let i = 0; i < internalCandidates.length; i++) {
const n = numberFromCandidate(internalCandidates[i]);
if (n !== null) return internalSocketToDisplayIndex(n);
}
return -1;
}
function moveTetId(move){
return Number(socketMeta(move).tetId !== undefined ? socketMeta(move).tetId : -1);
}
function moveKey(move){
if (!move) return '';
if (move.key) return String(move.key);
if (move.socket && move.socket.metadata && move.socket.metadata.worldKey) return String(move.socket.metadata.worldKey);
return '';
}
function tetFaceIndex(move){
return Number(move && move.face && move.face.metadata ? move.face.metadata.faceIndex : -1);
}
function tetFaceTetId(move){
return Number(move && move.face && move.face.metadata ? move.face.metadata.tetId : -1);
}
function isCornerSocket(idx){
// Public/game-record socket map from tetgame123: 0 center;
// 1,3,5,8 corners; 2,4,6,7,9,10 edge-mid sockets.
return idx === 1 || idx === 3 || idx === 5 || idx === 8;
}
function isEdgeMidSocket(idx){
return idx === 2 || idx === 4 || idx === 6 || idx === 7 || idx === 9 || idx === 10;
}
function isCenterSocket(idx){
return idx === 0;
}
function isCornerOrJunctionSocket(idx){
return isCornerSocket(idx);
}
function hasNumber(list, n){
if (!Array.isArray(list)) return false;
return list.some(v => Number(v) === Number(n));
}
function rivalCenterOwnsTet(ctx, tetId){
if (!ctx || tetId < 0) return false;
// Optional future hooks. Current Tetgame may not provide these yet, but
// this lets Silver immediately use them if/when the engine exposes them.
if (hasNumber(ctx.opponentCenterTetIds, tetId)) return true;
if (hasNumber(ctx.rivalCenterTetIds, tetId)) return true;
if (hasNumber(ctx.enemyCenterTetIds, tetId)) return true;
const direct = ctx.centerOwnerByTet || ctx.tetCenterOwner || ctx.centerOwners || null;
if (direct && typeof direct === 'object') {
const owner = direct[tetId] || direct[String(tetId)] || '';
if (owner && owner !== ctx.owner) return true;
}
return false;
}
function socketPathDegree(move){
const meta = socketMeta(move);
const keys = ['pathDegree', 'connectedPathCount', 'incidentPathCount', 'openPathCount', 'pathCount'];
for (let i = 0; i < keys.length; i++) {
const value = Number(meta[keys[i]]);
if (Number.isFinite(value) && value > 0) return value;
}
return -1;
}
function expansionLockRisk(move, ctx){
const idx = socketIndex(move);
const id = moveTetId(move);
const raw = scoreOf(move);
const boardLocked = !!(ctx && ctx.boardLocked);
if (boardLocked) return 0;
if (!isCornerOrJunctionSocket(idx)) return 0;
const tetCount = Number(ctx && ctx.tetCount || 0);
const newest = newestKnownTetId(ctx);
let risk = 0;
// Early quiet corners can close faces before Silver has enough ocean.
if (tetCount < minimumWake(ctx) && raw < 3.5) risk += 5.0;
if (tetCount < 8 && raw < 2.5 && id > 3) risk += 4.5;
// A far-tip corner can be a useful anchor; an old quiet corner is often a
// self-made reef wall.
if (id >= newest - 1) risk -= 3.0;
if (id <= 3) risk -= 1.5;
if (id > 3 && id < newest - 5 && raw < 6) risk += 3.0;
return risk;
}
function firstAction(ctx){
return Number(ctx && ctx.turnActionCount || 0) === 0;
}
function tokenMoves(ctx){
return Array.isArray(ctx && ctx.tokenMoves) ? ctx.tokenMoves : [];
}
function tetMoves(ctx){
return Array.isArray(ctx && ctx.tetMoves) ? ctx.tetMoves : [];
}
function opponentTokenMoves(ctx){
if (Array.isArray(ctx && ctx.opponentTokenMoves)) return ctx.opponentTokenMoves;
if (Array.isArray(ctx && ctx.rivalTokenMoves)) return ctx.rivalTokenMoves;
if (Array.isArray(ctx && ctx.enemyTokenMoves)) return ctx.enemyTokenMoves;
return [];
}
function swimLimit(ctx){
// v9 from the tested v8 baseline: Spectrum wins the long 12-13 tet runway.
// Silver now wants a compact reef, then tokens. For two-player games this
// gives a practical cap around 8-9 tets instead of swimming to board lock.
const active = activeCount(ctx);
return Math.max(7, Math.min(10, active + 7));
}
function minimumWake(ctx){
// Begin harvesting before the wake turns into Spectrum's shooting gallery.
// For two-player games this is about 7 tets.
const active = activeCount(ctx);
return Math.max(6, Math.min(8, active + 5));
}
function newestKnownTetId(ctx){
let newest = Math.max(1, Number(ctx && ctx.tetCount || 1));
tetMoves(ctx).forEach(move => {
const id = tetFaceTetId(move);
if (id > newest) newest = id;
});
tokenMoves(ctx).forEach(move => {
const id = moveTetId(move);
if (id > newest) newest = id;
});
return newest;
}
function chooseHelixTet(ctx){
const moves = tetMoves(ctx);
if (!moves.length || !!(ctx && ctx.boardLocked)) return null;
const tetCount = Number(ctx && ctx.tetCount || 0);
const newest = newestKnownTetId(ctx);
const tokCount = Number(ctx && ctx.tokenCount || 0);
const lowSea = tetCount < minimumWake(ctx);
let best = null;
let bestValue = -999999;
moves.forEach((move, i) => {
if (!move) return;
const source = tetFaceTetId(move);
const face = tetFaceIndex(move);
let value = scoreOf(move);
// Swim from the newest / farthest tet, not back near the beach.
if (source === newest) value += 11.0;
else if (source === newest - 1) value += 5.8;
else if (source === newest - 2) value += 2.4;
else if (source > 2) value += 1.1;
else if (tetCount > 3) value -= 4.0;
// v8 overused face 3 and built Spectrum's ideal long runway.
// Prefer side-current faces once the wake has any length.
if (face === 2) value += 5.2;
else if (face === 1) value += 3.1;
else if (face === 0) value += 1.4;
else if (face === 3) value += (tetCount <= 3 ? 3.0 : -2.2);
// Early: keep the shape lean and moving. Late: allow small bends.
if (lowSea && source <= 2 && tetCount >= 4) value -= 2.8;
if (tokCount <= 4 && source === newest) value += 0.85;
// When only a few tet moves remain, use them before the board locks.
if (moves.length <= 2 && source >= newest - 2) value += 5.0;
// Respect the engine's ordering only as a tiny tie-breaker.
value -= i * 0.035;
if (value > bestValue) {
bestValue = value;
best = move;
}
});
return best;
}
function chooseBranchTet(ctx){
const moves = tetMoves(ctx);
if (!moves.length || !!(ctx && ctx.boardLocked)) return null;
const tetCount = Number(ctx && ctx.tetCount || 0);
const newest = newestKnownTetId(ctx);
// Do not branch before the main current exists.
if (tetCount < 7) return null;
let best = null;
let bestValue = -999999;
moves.forEach((move, i) => {
if (!move) return;
const source = tetFaceTetId(move);
const face = tetFaceIndex(move);
// Side-current: not from the newest tip, but not all the way back home.
if (source < newest - 6 || source > newest - 2) return;
let value = scoreOf(move);
if (face === 2) value += 5.0;
else if (face === 1) value += 3.2;
else if (face === 3) value += 1.4;
else value += 0.4;
value += Math.max(0, 6 - Math.abs((newest - 4) - source)) * 0.65;
value -= i * 0.03;
if (value > bestValue) {
bestValue = value;
best = move;
}
});
return best;
}
function chooseCornerCascadeToken(ctx){
const moves = tokenMoves(ctx);
if (!moves.length) return null;
const boardLocked = !!(ctx && ctx.boardLocked);
const tetCount = Number(ctx && ctx.tetCount || 0);
const tokCount = Number(ctx && ctx.tokenCount || 0);
const newest = newestKnownTetId(ctx);
let best = null;
let bestValue = -999999;
moves.forEach((move, i) => {
const idx = socketIndex(move);
if (!isCornerSocket(idx)) return;
const id = moveTetId(move);
const raw = scoreOf(move);
const degree = socketPathDegree(move);
let value = raw * 2.25;
// The v8 record shows Spectrum repeatedly wins by leaving a true corner
// until it becomes a double-path / auto-token trigger. True corners are
// now Silver's tactical priority, especially on the first few tets and in
// the fresh wake.
value += 20.0;
if (raw >= 12) value += 18.0;
else if (raw >= 8) value += 12.0;
else if (raw >= 5) value += 8.0;
else if (raw >= 2) value += 5.0;
if (degree >= 4) value += 12.0;
else if (degree === 3) value += 8.0;
else if (degree === 2) value += 4.0;
// Beach/platform corners were the worst leaks in the v8 record.
if (id <= 1) value += 14.0;
else if (id <= 3) value += 10.0;
else if (id <= 6) value += 6.0;
// Fresh-wake corners are also good, because they become Silver's reef
// anchors instead of Spectrum's runway harvest.
if (id >= newest - 1) value += 9.0;
else if (id >= newest - 3) value += 5.0;
// Early after a swim action, corner is usually better than a one-path edge
// chip because it can spawn the auto-token edge later.
if (!firstAction(ctx)) value += 4.0;
if (tokCount <= 18) value += 2.0;
if (boardLocked) value += 5.0;
value -= i * 0.025;
if (value > bestValue) {
bestValue = value;
best = move;
}
});
if (!best) return null;
const threshold = boardLocked ? 13.0 : (tetCount <= 4 ? 18.0 : 15.0);
return bestValue >= threshold ? best : null;
}
function betterCornerInstead(ctx, selected){
if (!selected) return selected;
const idx = socketIndex(selected);
if (!isEdgeMidSocket(idx)) return selected;
const moves = tokenMoves(ctx);
if (!moves.length) return selected;
const id = moveTetId(selected);
const raw = scoreOf(selected);
const newest = newestKnownTetId(ctx);
const boardLocked = !!(ctx && ctx.boardLocked);
let best = null;
let bestValue = -999999;
moves.forEach((move, i) => {
const cIdx = socketIndex(move);
if (!isCornerSocket(cIdx)) return;
const cId = moveTetId(move);
const cRaw = scoreOf(move);
let value = cRaw * 2.0 + 16.0;
if (cId === id) value += 12.0;
if (cId <= 3) value += 9.0;
if (cId >= newest - 2) value += 5.0;
if (!firstAction(ctx)) value += 3.0;
if (boardLocked) value += 3.5;
value -= i * 0.025;
if (value > bestValue) {
bestValue = value;
best = move;
}
});
if (!best) return selected;
// Keep genuinely huge edge-mid captures, but veto the repeated v8 pattern:
// one small/mid edge path while a true corner remains available.
if (raw <= 8 || moveTetId(best) === id || boardLocked) return best;
return selected;
}
function finalMove(ctx, move, reason){
const chosen = betterCornerInstead(ctx, move);
const finalReason = chosen !== move
? 'take the true corner instead of a small mid-edge chip'
: reason;
return cloneMove(chosen, finalReason);
}
function chooseHarvestToken(ctx){
const moves = tokenMoves(ctx);
if (!moves.length) return null;
const tetCount = Number(ctx && ctx.tetCount || 0);
const newest = newestKnownTetId(ctx);
const boardLocked = !!(ctx && ctx.boardLocked);
let best = null;
let bestValue = -999999;
moves.forEach((move, i) => {
if (!move) return;
const idx = socketIndex(move);
const id = moveTetId(move);
const raw = scoreOf(move);
let value = raw;
// Long-path endpoints should flash brightly in the engine score. Make
// those the first harvest targets once the wake exists.
if (raw >= 14) value += 12.0;
else if (raw >= 10) value += 7.0;
else if (raw >= 7) value += 3.0;
// Corners are usually better end anchors than quiet edge-mid sockets.
if (isCornerSocket(idx)) value += 3.4;
// Center tokens are the late-wave prize: subs, full tets, and free-move
// cascades. Do not let a low-scoring center distract from a long path,
// but heavily favor it when it is actually live.
if (isCenterSocket(idx)) {
value += boardLocked ? 8.0 : 6.5;
if (id >= newest - 3) value += 3.5;
if (raw >= 4) value += 3.0;
if (raw >= 8) value += 6.0;
if (raw >= 12) value += 7.0;
}
// Avoid old bait: taking middle edge sockets just because they exist.
if (isEdgeMidSocket(idx)) {
if (raw < 6) value -= 4.5;
else if (raw < 9) value -= 1.2;
else value += 1.0;
}
// Prefer harvesting in the wake he just created.
if (id === newest) value += 2.0;
else if (id >= newest - 2) value += 1.5;
else if (id <= 2 && tetCount >= 7 && raw < 10) value -= 2.0;
value -= i * 0.025;
if (value > bestValue) {
bestValue = value;
best = move;
}
});
return best;
}
function centerAnchorRhythm(ctx, move){
const tetCount = Number(ctx && ctx.tetCount || 0);
const tokCount = Number(ctx && ctx.tokenCount || 0);
const boardLocked = !!(ctx && ctx.boardLocked);
const idx = socketIndex(move);
const id = moveTetId(move);
const raw = scoreOf(move);
const newest = newestKnownTetId(ctx);
if (!isCenterSocket(idx)) return false;
if (boardLocked) return true;
// First few turns: do not stop swimming immediately, unless the center is
// already useful. After the wake exists, keep dropping center anchors at a
// steady surf rhythm.
if (tetCount < 3) return raw >= 5;
// Directly after a swim action, take a center on the current wake often
// enough to secure the tets without turning Silver into a corner-token bot.
if (!firstAction(ctx)) {
if (id >= newest - 2) return true;
if (id <= 3 && tokCount <= 18) return true;
if (raw >= 3) return true;
return tetCount >= 7 && (tetCount % 3 === 0 || tokCount % 4 === 0);
}
// On first action, only take a center if it is a real denial/cascade lever.
if (raw >= 8) return true;
if (tetCount >= minimumWake(ctx) && (id <= 3 || id >= newest - 2)) return true;
return false;
}
function chooseCenterAnchorToken(ctx){
const moves = tokenMoves(ctx);
if (!moves.length) return null;
const boardLocked = !!(ctx && ctx.boardLocked);
const tetCount = Number(ctx && ctx.tetCount || 0);
const newest = newestKnownTetId(ctx);
let best = null;
let bestValue = -999999;
moves.forEach((move, i) => {
const idx = socketIndex(move);
if (!isCenterSocket(idx)) return;
const id = moveTetId(move);
const raw = scoreOf(move);
let value = raw * 1.35;
// Center sockets are Silver's anti-Pistolero insurance: they don't lock
// the board like corners, but they do reserve the tet for Silver's later
// sub/full-tet cascade.
value += 18.0;
// Centers in the fresh wake are the preferred swim rhythm: place tet,
// claim center, keep swimming. Centers near the beach deny Pistolero's
// early launch platform.
if (id >= newest - 1) value += 9.5;
else if (id >= newest - 3) value += 7.0;
else if (id <= 3) value += 7.5;
else if (id <= 6) value += 4.0;
if (raw >= 12) value += 10.0;
else if (raw >= 8) value += 7.0;
else if (raw >= 4) value += 3.5;
else if (raw >= 1) value += 1.2;
// Take a few centers early, but do not take every quiet old center while
// the wake is still too short. The rhythm gate below controls that.
if (!centerAnchorRhythm(ctx, move)) value -= 24.0;
if (boardLocked) value += 8.0;
if (!boardLocked && tetCount < 5 && raw < 2 && id > 2) value -= 5.0;
// Avoid repeatedly spending turns on a quiet center far behind the swim.
if (!boardLocked && id < newest - 6 && raw < 4 && id > 3) value -= 7.0;
// Tiny deterministic tie-break: newer wake first, then engine order.
value += Math.max(0, id - (newest - 4)) * 0.08;
value -= i * 0.02;
if (value > bestValue) {
bestValue = value;
best = move;
}
});
if (!best) return null;
const threshold = boardLocked ? 14.0 : (tetCount < 5 ? 20.0 : 16.5);
return bestValue >= threshold ? best : null;
}
function urgentHarvestAvailable(ctx){
const moves = tokenMoves(ctx);
if (!moves.length) return null;
const tetCount = Number(ctx && ctx.tetCount || 0);
const minWake = minimumWake(ctx);
const boardLocked = !!(ctx && ctx.boardLocked);
// Do not interrupt the swim for medium shots before the wave is ready.
if (!boardLocked && tetCount < minWake) return null;
let best = null;
let bestValue = -999999;
moves.forEach(move => {
const raw = scoreOf(move);
const idx = socketIndex(move);
let value = raw;
if (raw >= 16) value += 8;
if (isCenterSocket(idx) && raw >= 11) value += 9;
if (isCornerSocket(idx) && raw >= 12) value += 4;
if (value < 16 && !(isCenterSocket(idx) && raw >= 11)) return;
if (value > bestValue) {
bestValue = value;
best = move;
}
});
return best;
}
function chooseDirectOpponentBlock(ctx){
const ours = tokenMoves(ctx);
const theirs = opponentTokenMoves(ctx);
if (!ours.length || !theirs.length) return null;
const byKey = new Map();
ours.forEach(move => {
const key = moveKey(move);
if (key) byKey.set(key, move);
});
let best = null;
let bestValue = -999999;
theirs.forEach((enemyMove, i) => {
const key = moveKey(enemyMove);
if (!key || !byKey.has(key)) return;
const ourMove = byKey.get(key);
const enemyScore = scoreOf(enemyMove);
const ourScore = scoreOf(ourMove);
const idx = socketIndex(ourMove);
const id = moveTetId(ourMove);
// Block only meaningful threats. A weak enemy point should not distract
// Silver from building the big wave.
if (enemyScore < 7 && !(isCenterSocket(idx) && enemyScore >= 5.5)) return;
let value = enemyScore * 1.55 + ourScore * 0.65;
if (isCenterSocket(idx)) value += 4.5;
if (isCornerSocket(idx)) value += 2.5;
if (id <= 3) value += 1.5;
value -= i * 0.08;
if (value > bestValue) {
bestValue = value;
best = ourMove;
}
});
return best;
}
function pistoleroHotSocketValue(move, ctx){
if (!move) return -999999;
const raw = scoreOf(move);
const idx = socketIndex(move);
const id = moveTetId(move);
const tetCount = Number(ctx && ctx.tetCount || 0);
const boardLocked = !!(ctx && ctx.boardLocked);
const newest = newestKnownTetId(ctx);
let value = raw;
// Pistolero lives on bright shared path sockets. If Silver can deny a
// bright shot without stopping his wave too early, he should do it.
if (raw >= 12) value += 9.0;
else if (raw >= 9) value += 5.0;
else if (raw >= 7) value += 2.4;
// Early low-id sockets are where Pistolero tends to build his gun platform.
if (id <= 1) value += 2.5;
else if (id <= 3) value += 1.6;
// Pistolero's common dangerous sockets: centers/corners and the edge-mids
// that complete the small 1/2/3-tet shooting gallery.
if (isCenterSocket(idx)) value += 9.0;
if (isCornerSocket(idx)) value += 2.7;
if ((idx === 6 || idx === 8 || idx === 9) && id <= Math.max(3, newest - 1)) value += 2.1;
// Blocks in the current wake are not merely defensive: they become anchors
// for Silver's later cascade.
if (id >= newest - 2) value += 1.7;
// A low raw edge-mid is still bait. Don't call that a block.
if (isEdgeMidSocket(idx) && raw < 5.5) value -= 5.5;
// If the board is already locked, every denial matters more.
if (boardLocked) value += 2.0;
// Before enough sea exists, only block very real threats.
if (!boardLocked && tetCount < minimumWake(ctx) && value < 13.5) value -= 9.0;
return value;
}
function chooseHeuristicBlock(ctx){
const moves = tokenMoves(ctx);
if (!moves.length) return null;
const tetCount = Number(ctx && ctx.tetCount || 0);
const boardLocked = !!(ctx && ctx.boardLocked);
const minWake = minimumWake(ctx);
let best = null;
let bestValue = -999999;
moves.forEach((move, i) => {
let value = pistoleroHotSocketValue(move, ctx);
value -= i * 0.025;
if (value > bestValue) {
bestValue = value;
best = move;
}
});
// Thresholds: block sooner if locked, later if still swimming.
const threshold = boardLocked ? 9.5 : (tetCount >= minWake ? 12.0 : 15.5);
return bestValue >= threshold ? best : null;
}
function chooseBlockToken(ctx){
return chooseDirectOpponentBlock(ctx) || chooseHeuristicBlock(ctx);
}
function chooseRivalCenterCornerBlockToken(ctx){
const moves = tokenMoves(ctx);
if (!moves.length) return null;
const boardLocked = !!(ctx && ctx.boardLocked);
const tetCount = Number(ctx && ctx.tetCount || 0);
const tokCount = Number(ctx && ctx.tokenCount || 0);
const newest = newestKnownTetId(ctx);
const minWake = minimumWake(ctx);
let best = null;
let bestValue = -999999;
moves.forEach((move, i) => {
const idx = socketIndex(move);
if (!isCornerOrJunctionSocket(idx)) return;
const id = moveTetId(move);
const raw = scoreOf(move);
const degree = socketPathDegree(move);
let value = raw * 1.18;
// Main idea: if Pistolero has or is about to have the center, one silver
// corner/junction on that tet breaks the full-tet finish. True corners are
// best, but high-traffic 6/8/9 junctions often serve the same role in the
// current socket map.
if (isCornerSocket(idx)) value += 12.0;
else value += 8.0;
if (rivalCenterOwnsTet(ctx, id)) value += 18.0;
// Pistolero's direction is visible in the bands he keeps turning red:
// low beach tets early, then the just-behind-the-tip wake. Deny there,
// not at a random quiet corner.
if (id <= 3) value += 8.0;
else if (id <= 6) value += 5.0;
if (id >= newest - 3) value += 7.5;
else if (id >= newest - 6) value += 4.0;
// Corners with several paths connected are worth much more than lonely
// corners. If the engine exposes a degree, use it; otherwise the move's
// own score is our best proxy for connected path pressure.
if (degree >= 4) value += 9.0;
else if (degree === 3) value += 6.0;
else if (degree === 2) value += 3.0;
if (raw >= 12) value += 10.0;
else if (raw >= 8) value += 7.0;
else if (raw >= 5) value += 4.0;
else if (raw >= 2.5) value += 1.5;
// The center/corner pair is the danger sign: if center is unavailable on
// this tet but a corner/junction is available, Silver assumes the rival may
// already own or be racing for that center and raises the block priority.
const hasCenterSameTet = moves.some(m => moveTetId(m) === id && isCenterSocket(socketIndex(m)));
if (!hasCenterSameTet && id <= Math.max(6, newest - 2)) value += 4.0;
// Don't let this new idea turn her into a corner-bot. Quiet corner blocks
// before the swim is long enough must either hit the beach, the fresh wake,
// or a real multi-path corner.
value -= expansionLockRisk(move, ctx);
if (!boardLocked && tetCount < minWake && raw < 2.5 && id > 3 && id < newest - 2) value -= 7.0;
if (!boardLocked && tetCount < 6 && !rivalCenterOwnsTet(ctx, id) && raw < 4.0 && id > 3) value -= 6.0;
// Second action is the natural place for the denial while the first action
// keeps Silver swimming. On first action, require a stronger case.
if (firstAction(ctx) && !boardLocked) value -= 3.0;
if (!firstAction(ctx) && tetCount >= 4) value += 2.5;
if (tokCount <= 12 && id <= 4) value += 1.5;
value -= i * 0.03;
if (value > bestValue) {
bestValue = value;
best = move;
}
});
if (!best) return null;
const threshold = boardLocked ? 11.0 : (tetCount < minWake ? 17.5 : 14.0);
return bestValue >= threshold ? best : null;
}
function socketPatternBonus(idx){
// Repeated Pistolero wins came from leaving these sockets open until they
// became full-tet triggers. The exact score may look small to Silver now,
// but ownership here denies the future pistol shot.
if (idx === 0) return 10.0;
if (idx === 1 || idx === 3 || idx === 5 || idx === 8) return 9.8;
if (idx === 2 || idx === 4 || idx === 6 || idx === 7 || idx === 9 || idx === 10) return 4.4;
return 0;
}
function tetBandBonus(id, newest){
// Shoreline tets are Pistolero's launch platform. The far wake is Silver's
// launch platform. The middle is only useful when the move is otherwise good.
if (id === 1) return 10.0;
if (id === 2 || id === 3) return 9.0;
if (id === 4 || id === 5 || id === 6) return 6.0;
if (id >= newest - 2) return 5.2;
if (id >= newest - 4) return 3.4;
if (id >= 7 && id <= 10) return 2.4;
return 0.6;
}
function chooseDeadeyeDenialToken(ctx){
const moves = tokenMoves(ctx);
if (!moves.length) return null;
const boardLocked = !!(ctx && ctx.boardLocked);
const tetCount = Number(ctx && ctx.tetCount || 0);
const newest = newestKnownTetId(ctx);
let best = null;
let bestValue = -999999;
moves.forEach((move, i) => {
const idx = socketIndex(move);
const id = moveTetId(move);
const raw = scoreOf(move);
let value = raw * 2.15;
// Pistolero's brain takes the first bright target it sees. Treat the
// engine's highest-scored legal token moves as his next likely shot and
// steal them before he can pull the trigger.
if (i === 0) value += 10.0;
else if (i === 1) value += 5.8;
else if (i === 2) value += 3.0;
if (raw >= 12) value += 12.0;
else if (raw >= 9) value += 8.0;
else if (raw >= 6) value += 4.5;
else if (raw >= 4) value += 2.4;
// These sockets repeatedly become Pistolero's small-board gun platform.
if (id <= 3 && (isCenterSocket(idx) || isCornerSocket(idx))) value += 6.8;
if (id <= 6 && isCornerSocket(idx)) value += 4.2;
// Far-wake denial is also Silver's later anchor.
if (id >= newest - 2 && raw >= 4) value += 2.5;
if (boardLocked) value += 2.0;
// Do not call a dead zero a block. v4 spent too many turns placing
// pretty-looking zero/one-point reef tokens while Pistolero harvested the
// actual bright shots.
if (!boardLocked && raw < 3.5) value -= 12.0;
if (!boardLocked && tetCount < 5 && raw < 6.0) value -= 6.0;
value -= i * 0.05;
if (value > bestValue) {
bestValue = value;
best = move;
}
});
const threshold = boardLocked ? 8.0 : (tetCount < 5 ? 13.5 : 11.5);
return bestValue >= threshold ? best : null;
}
function chooseSeawallToken(ctx, mode){
const moves = tokenMoves(ctx);
if (!moves.length) return null;
const boardLocked = !!(ctx && ctx.boardLocked);
const tetCount = Number(ctx && ctx.tetCount || 0);
const tokCount = Number(ctx && ctx.tokenCount || 0);
const newest = newestKnownTetId(ctx);
const early = mode === 'early';
let best = null;
let bestValue = -999999;
moves.forEach((move, i) => {
const idx = socketIndex(move);
const id = moveTetId(move);
const raw = scoreOf(move);
let value = raw * (early ? 0.55 : 0.75);
// v4 was overvaluing theoretical future blocks. A seawall token must
// either score now, block a known shoreline pattern, or anchor the far wake.
if (!boardLocked && early && raw < 2.5 && id > 1) value -= 14.0;
if (!boardLocked && raw < 1.5 && id > 3) value -= 10.0;
value += socketPatternBonus(idx);
value += tetBandBonus(id, newest);
// Prefer actual useful captures when they are present, but do not let the
// engine's current-score estimate hide a future defensive keystone.
if (raw >= 12) value += 8.0;
else if (raw >= 9) value += 5.0;
else if (raw >= 6) value += 2.5;
// In the first few turns, steal the low-numbered shoreline sockets that
// repeatedly became Pistolero full-tet triggers in the test record.
if (tokCount <= 12 && id <= 3) value += 6.5;
if (tokCount <= 18 && id <= 6 && (isCenterSocket(idx) || isCornerSocket(idx))) value += 5.5;
// Once the wake is long, anchor its far end before harvesting everything.
if (tetCount >= 7 && id >= newest - 2 && (isCenterSocket(idx) || isCornerSocket(idx))) value += 5.8;
// Center / hub sockets are now a swim-time priority. They deny subs
// and full-tet cascades without locking the board the way corners do.
if (isCenterSocket(idx)) value += boardLocked ? 8.0 : 8.5;
if (isCornerOrJunctionSocket(idx)) {
value += isCornerSocket(idx) ? 3.4 : 2.2;
if (raw >= 5) value += 2.5;
value -= expansionLockRisk(move, ctx) * 0.6;
}
// If we are still swimming, avoid spending the second action on a dull
// remote edge-mid that neither blocks Pistolero nor anchors Silver.
if (!boardLocked && raw < 3.5 && isEdgeMidSocket(idx) && id > 6 && id < newest - 4) value -= 5.0;
if (!boardLocked && early && firstAction(ctx) && raw < 6) value -= 3.0;
// Deterministic tie-breaks: lower shoreline first early, newer wake first late.
if (early) value -= id * 0.11;
else value += Math.max(0, id - (newest - 4)) * 0.08;
value -= i * 0.02;
if (value > bestValue) {
bestValue = value;
best = move;
}
});
if (!best) return null;
const threshold = boardLocked ? 8.0 : (early ? 13.0 : 11.0);
return bestValue >= threshold ? best : null;
}
function choosePistoleroKillSwitchToken(ctx){
const moves = tokenMoves(ctx);
if (!moves.length) return null;
const boardLocked = !!(ctx && ctx.boardLocked);
const tetCount = Number(ctx && ctx.tetCount || 0);
const newest = newestKnownTetId(ctx);
let best = null;
let bestValue = -999999;
moves.forEach((move, i) => {
const idx = socketIndex(move);
const id = moveTetId(move);
const raw = scoreOf(move);
let value = raw * 1.4;
// Kill-switch lanes are the shoreline / common-corner places that turn
// one Pistolero shot into multiple paths, subs, and a full tet.
if (id <= 1) value += 10.0;
else if (id <= 3) value += 8.0;
else if (id <= 6) value += 4.5;
if (isCornerSocket(idx)) value += 8.5;
if (isCenterSocket(idx)) value += 6.5;
if (raw >= 12) value += 10.0;
else if (raw >= 9) value += 7.0;
else if (raw >= 6) value += 3.5;
if (id >= newest - 2) value += 2.0;
if (boardLocked) value += 2.0;
if (!boardLocked && tetCount < 4 && raw < 5 && id > 3) value -= 6.0;
if (firstAction(ctx) && !boardLocked) value -= 1.5;
value -= expansionLockRisk(move, ctx) * 0.45;
value -= i * 0.035;
if (value > bestValue) {
bestValue = value;
best = move;
}
});
const threshold = boardLocked ? 9.0 : (tetCount < minimumWake(ctx) ? 15.0 : 12.0);
return bestValue >= threshold ? best : null;
}
function chooseFinisherToken(ctx){
const moves = tokenMoves(ctx);
if (!moves.length) return null;
const boardLocked = !!(ctx && ctx.boardLocked);
const tetCount = Number(ctx && ctx.tetCount || 0);
const newest = newestKnownTetId(ctx);
let best = null;
let bestValue = -999999;
moves.forEach((move, i) => {
const idx = socketIndex(move);
const id = moveTetId(move);
const raw = scoreOf(move);
let value = raw * 2.0;
if (raw >= 16) value += 16.0;
else if (raw >= 12) value += 11.0;
else if (raw >= 9) value += 6.0;
else if (raw >= 6) value += 2.5;
if (isCenterSocket(idx)) value += 8.0;
if (isCornerSocket(idx)) value += 4.0;
if (id >= newest - 3) value += 2.5;
if (boardLocked) value += 5.0;
if (!boardLocked && tetCount < minimumWake(ctx) && raw < 12) value -= 12.0;
value -= i * 0.025;
if (value > bestValue) {
bestValue = value;
best = move;
}
});
if (!best) return null;
const threshold = boardLocked ? 15.0 : (tetCount >= minimumWake(ctx) ? 18.0 : 26.0);
return bestValue >= threshold ? best : null;
}
function shouldBranchThisAction(ctx){
if (!firstAction(ctx)) return false;
const tetCount = Number(ctx && ctx.tetCount || 0);
if (tetCount < 8) return false;
// Deterministic rhythm: branch at a few predictable wake lengths. This
// keeps Silver recognizably wave-like, not random, but less one-dimensional.
return tetCount % 4 === 0 || tetCount % 5 === 0 || tetCount % 7 === 0;
}
registry.silver_kahuna = {
key:'silver_kahuna',
id:'silver_kahuna',
name:'Silver Kahuna',
level:10,
style:'compact reef-cage builder, true-corner hunter, runway-denier, and reef-cascade finisher',
temperament:"builds a short reef, takes true corners before edge chips, and refuses to feed Spectrum's long runway",
chooseMove(ctx){
if (!ctx || ctx.matchEnded || ctx.tournamentEnded) return null;
if (canOpen(ctx)) return {type:'start_tet', score:1000, reason:'open the first wave'};
const token0 = moveAt(ctx.tokenMoves, 0);
const tet0 = moveAt(ctx.tetMoves, 0);
const boardLocked = !!ctx.boardLocked;
const tetCount = Number(ctx.tetCount || 0);
const limit = swimLimit(ctx);
const minWake = minimumWake(ctx);
const helixTet = chooseHelixTet(ctx);
const branchTet = shouldBranchThisAction(ctx) ? chooseBranchTet(ctx) : null;
const blockToken = chooseBlockToken(ctx);
const deadeyeToken = chooseDeadeyeDenialToken(ctx);
const killSwitchToken = choosePistoleroKillSwitchToken(ctx);
const cornerBlock = chooseRivalCenterCornerBlockToken(ctx);
const cornerCascade = chooseCornerCascadeToken(ctx);
const centerAnchor = chooseCenterAnchorToken(ctx);
const earlySeawall = chooseSeawallToken(ctx, 'early');
const wakeSeawall = chooseSeawallToken(ctx, 'wake');
// v9: stop the long-runway leak. Once the compact reef exists, use
// legal token moves instead of placing more tets for Spectrum to farm.
if (!boardLocked && tetCount >= limit - 1 && tokenMoves(ctx).length) {
const reefLock = cornerCascade || chooseFinisherToken(ctx) || cornerBlock || centerAnchor || killSwitchToken || deadeyeToken || blockToken || wakeSeawall || chooseHarvestToken(ctx) || token0;
if (reefLock) return finalMove(ctx, reefLock, 'lock the reef instead of feeding the long runway');
}
// Phase 1: swim with a seawall rhythm. First action normally extends the
// wake; second action steals the shoreline socket Pistolero wants before
// it becomes a gun platform.
if (!boardLocked && helixTet && tetCount < minWake) {
const urgent = urgentHarvestAvailable(ctx);
if (urgent && scoreOf(urgent) >= 22) {
return finalMove(ctx, urgent, 'catch the breaking wave before it passes');
}
// v6: real open-ocean swimming. Do not burn the second action on a
// merely interesting token while the wake is still short. Pistolero was
// farming those half-built reefs.
if (tetCount < Math.min(9, minWake)) {
if (!firstAction(ctx) && cornerCascade) {
return finalMove(ctx, cornerCascade, 'take the true corner before the edge chip');
}
if (!firstAction(ctx) && cornerBlock && scoreOf(cornerBlock) >= 3) {
return finalMove(ctx, cornerBlock, 'take the shared corner so Pistolero cannot finish the tet');
}
if (!firstAction(ctx) && centerAnchor && tetCount >= 3) {
return finalMove(ctx, centerAnchor, 'claim the center while swimming so Pistolero cannot own this tet');
}
if (!firstAction(ctx) && killSwitchToken && scoreOf(killSwitchToken) >= 11) {
return finalMove(ctx, killSwitchToken, 'throw the kill-switch before Pistolero can line up the reef');
}
if (!firstAction(ctx) && deadeyeToken && scoreOf(deadeyeToken) >= 10) {
return finalMove(ctx, deadeyeToken, 'outdraw Pistolero at the bright target');
}
if (!firstAction(ctx) && earlySeawall && scoreOf(earlySeawall) >= 4) {
return finalMove(ctx, earlySeawall, 'seed a shoreline reef before Pistolero can use it');
}
return finalMove(ctx, helixTet, 'swim straight out and keep the wave open');
}
if (!firstAction(ctx) && cornerCascade) {
return finalMove(ctx, cornerCascade, 'take the true corner before the edge chip');
}
if (!firstAction(ctx) && cornerBlock) {
return finalMove(ctx, cornerBlock, 'steal the common corner before the tet goes red');
}
if (!firstAction(ctx) && centerAnchor) {
return finalMove(ctx, centerAnchor, 'drop the center token while the wave is still open');
}
if (!firstAction(ctx) && (killSwitchToken || deadeyeToken || blockToken || earlySeawall)) {
const reef = killSwitchToken || deadeyeToken || blockToken || earlySeawall;
return finalMove(ctx, reef, killSwitchToken ? 'close the Pistolero kill-switch lane' : 'deny the rival the easy shot and turn it into silver water');
}
return finalMove(ctx, branchTet || helixTet, branchTet ? 'throw a side-current off the main wave' : 'swim straight out and keep the wave open');
}
// Phase 2: after the minimum wake exists but before the limit, mix branch,
// center ownership, and denial. Keep the board alive unless a real catch exists.
if (!boardLocked && tetCount < limit) {
const finisher = chooseFinisherToken(ctx);
if (cornerCascade) {
return finalMove(ctx, cornerCascade, 'take the true corner before the edge chip');
}
if (finisher && scoreOf(finisher) >= 14) {
return finalMove(ctx, finisher, isCenterSocket(socketIndex(finisher)) ? 'drop into the center and ride the cascade' : 'take the breaking cascade before the rival does');
}
if (!firstAction(ctx) && cornerBlock) {
return finalMove(ctx, cornerBlock, 'break the rival center with a shared corner');
}
if (!firstAction(ctx) && centerAnchor) {
return finalMove(ctx, centerAnchor, 'drop the center token and deny the rival the tet');
}
if (!firstAction(ctx) && (killSwitchToken || deadeyeToken || blockToken || wakeSeawall)) {
const block = killSwitchToken || deadeyeToken || blockToken || wakeSeawall;
return finalMove(ctx, block, killSwitchToken ? 'close the Pistolero kill-switch lane' : (deadeyeToken ? 'outdraw Pistolero at the bright target' : 'deny the rival the easy shot and turn it into silver water'));
}
if (branchTet) return finalMove(ctx, branchTet, 'bend the wave so one counter cannot read it');
if (helixTet) return finalMove(ctx, helixTet, 'keep swimming until the reef is ready');
}
// Phase 3: reef is ready or board is locked. Deny the rival's center/corner
// finishes first, then cash the largest cascade.
const finisher = chooseFinisherToken(ctx);
if (cornerBlock && (boardLocked || scoreOf(cornerBlock) >= 4)) {
return finalMove(ctx, cornerBlock, 'break the rival center with a shared corner');
}
if (centerAnchor && (boardLocked || scoreOf(centerAnchor) >= 4 || isCenterSocket(socketIndex(centerAnchor)))) {
return finalMove(ctx, centerAnchor, 'drop the center token and deny the rival the tet');
}
if (finisher && scoreOf(finisher) >= 12) {
return finalMove(ctx, finisher, isCenterSocket(socketIndex(finisher)) ? 'drop into the center and ride the cascade' : 'take the breaking cascade before the rival does');
}
if (cornerBlock || killSwitchToken || deadeyeToken || blockToken || wakeSeawall) {
const chosenBlock = cornerBlock || killSwitchToken || deadeyeToken || blockToken || wakeSeawall;
return finalMove(ctx, chosenBlock, cornerBlock ? 'steal the common corner and keep the tet from going red' : (killSwitchToken ? 'close the Pistolero kill-switch lane' : (deadeyeToken ? "outdraw Pistolero at the bright target" : 'deny the rival the easy shot and turn it into silver water')));
}
const harvest = finisher || chooseHarvestToken(ctx);
if (harvest) {
const raw = scoreOf(harvest);
const idx = socketIndex(harvest);
const reason = isCenterSocket(idx)
? 'drop into the center and ride the cascade'
: (raw >= 10 ? 'take the far end of the long wave' : 'harvest the cleanest silver path');
return finalMove(ctx, harvest, reason);
}
// Phase 4: if he somehow still has sea room but no token worth taking,
// keep swimming.
if (!boardLocked && helixTet) return finalMove(ctx, helixTet, 'find one more open-water face');
if (token0) return finalMove(ctx, token0, 'take the last foam on the wave');
if (tet0) return finalMove(ctx, tet0, 'paddle to open water');
return pass();
}
};
})(window);
Future safe editor shape
Borrowing the Gold Room idea without mixing Trigame and Tetgame logic.
Read original
Keep an original snapshot before the first edit.
Edit safe sections
Expose selected behavior sections without letting the page damage wrapper code.
Test in Tetgame
Save, then jump back to the Blue Room or board to test the Rabbot live.
