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/octavius_indigo.js
/*
* Tetgame Rabbot: Octavius Indigo
*
* Octavius v7 — eight-arm anti-lock brood hunter.
* Tetgame supplies legal moves in ctx.tokenMoves and ctx.tetMoves; this file
* only chooses among those legal moves.
*
* v7 notes:
* - v7 adds an anti-lock survival rule: while the board is unlocked,
* Octavius keeps placing tets until Fang can no longer close every frontier, before spending moves on
* quiet midpoint tokens.
* - The earlier versions still let Fang lock the board after only two or
* three tets. v7 treats that as an emergency and prefers legal tet moves
* over small token nibbles until a real brood exists.
* - Harvesting still matters, but early harvests must be large enough to be
* worth giving up another arm.
* - Token raw scores come from the generic socket scorer. A one-path bite is
* already near 9 points there, so v7 uses path-scale thresholds instead of
* tiny 2.5/3.0 thresholds that made Octavius waste moves on midpoint nibbles.
*
* Voice lines live separately in js/voices/octavius_indigo.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 openingLimit(ctx, extra){
const active = activeCount(ctx);
return Math.max(3, Math.min(8, active + Number(extra || 3)));
}
function moveTetId(move){
return Number(move && move.socket && move.socket.metadata ? move.socket.metadata.tetId : -1);
}
function socketIndex(move){
return Number(move && move.socket && move.socket.metadata ? move.socket.metadata.socketIndex : -1);
}
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){
return idx >= 0 && idx <= 3;
}
function isEdgeMidSocket(idx){
return idx >= 4 && idx <= 9;
}
function isCenterSocket(idx){
return idx === 10;
}
// The engine's move.score is the generic Aquila socket score, not the final
// scoreboard value. Because one uncaptured friendly path adds about 7.5, a
// raw score around 9 is only a one-path nibble. Octavius needs to ignore
// those while the board is still open and save token moves for real bites.
const RAW_ONE_PATH = 8.5;
const RAW_TWO_PATHS = 16.0;
const RAW_THREE_PATHS= 23.0;
const RAW_FOUR_PATHS = 30.0;
const RAW_HUGE_BITE = 44.0;
function estimatedPathBites(move){
const raw = scoreOf(move);
if (raw >= RAW_HUGE_BITE) return 6;
if (raw >= RAW_FOUR_PATHS) return 4;
if (raw >= RAW_THREE_PATHS) return 3;
if (raw >= RAW_TWO_PATHS) return 2;
if (raw >= RAW_ONE_PATH) return 1;
return 0;
}
function isSmallMidpointNibble(move){
return isEdgeMidSocket(socketIndex(move)) && estimatedPathBites(move) <= 1;
}
function newestTokenTetId(ctx){
const moves = Array.isArray(ctx && ctx.tokenMoves) ? ctx.tokenMoves : [];
let newest = Math.max(1, Number(ctx && ctx.tetCount || 1));
moves.forEach(move => {
const id = moveTetId(move);
if (id > newest) newest = id;
});
return newest;
}
function exactSocketMove(moves, tetId, socketOrder, minRawScore, reasonPrefix){
if (!Array.isArray(moves) || tetId < 1) return null;
const bySocket = Object.create(null);
moves.forEach(move => {
if (moveTetId(move) !== tetId) return;
const idx = socketIndex(move);
if (idx < 0) return;
if (!bySocket[idx] || scoreOf(move) > scoreOf(bySocket[idx])) bySocket[idx] = move;
});
for (let i = 0; i < socketOrder.length; i++) {
const idx = socketOrder[i];
const move = bySocket[idx];
if (!move) continue;
if (typeof minRawScore === 'number' && scoreOf(move) < minRawScore) continue;
return cloneMove(move, reasonPrefix + ' socket ' + tetId + ':' + idx);
}
return null;
}
function exactTetFaceMove(moves, sourceTetId, faceOrder, reasonPrefix){
if (!Array.isArray(moves) || !moves.length) return null;
const byFace = Object.create(null);
moves.forEach(move => {
const faceIdx = tetFaceIndex(move);
if (faceIdx < 0) return;
const faceTet = tetFaceTetId(move);
if (sourceTetId >= 1 && faceTet >= 1 && faceTet !== sourceTetId) return;
if (!byFace[faceIdx] || scoreOf(move) > scoreOf(byFace[faceIdx])) byFace[faceIdx] = move;
});
for (let i = 0; i < faceOrder.length; i++) {
const faceIdx = faceOrder[i];
const move = byFace[faceIdx];
if (move) return cloneMove(move, reasonPrefix + ' face ' + sourceTetId + ':' + faceIdx);
}
// Older tetgame builds may not expose the source tet id on face metadata.
// Fall back to face index only.
for (let i = 0; i < faceOrder.length; i++) {
const faceIdx = faceOrder[i];
const move = moves.find(m => tetFaceIndex(m) === faceIdx);
if (move) return cloneMove(move, reasonPrefix + ' face ' + sourceTetId + ':' + faceIdx);
}
return null;
}
// Socket 10 is center. Sockets 0-3 are corners. Sockets 4-9 are edge midpoints.
const ROOT_OPEN_CENTER = [10, 1, 3, 0, 2, 8, 6, 4, 5, 7, 9];
const ROOT_SHARED_SUCKER = [1, 0, 2, 3, 10, 6, 5, 9, 8, 4, 7];
const ROOT_RADIAL_DENIAL = [0, 2, 3, 1, 10, 6, 5, 9, 8, 4, 7];
const ROOT_INK_CLOUD = [2, 3, 0, 1, 10, 9, 6, 5, 8, 4, 7];
const ROOT_RING_CLOSE = [6, 5, 9, 8, 4, 7, 0, 2, 3, 1, 10];
const OUTER_ARM_ANCHOR = [10, 3, 6, 8, 9, 0, 2, 1, 4, 5, 7];
const OUTER_ARM_SWEEP = [3, 6, 8, 9, 10, 0, 2, 1, 4, 5, 7];
const OUTER_ARM_FINISH = [6, 8, 9, 10, 3, 0, 2, 1, 4, 5, 7];
const NEW_ARM_ANCHOR = [10, 3, 0, 2, 1, 6, 8, 9, 5, 4, 7];
// Face maps. These are deliberately not just "first legal face". Octavius
// wants the board to sprout arms instead of becoming Fang's one-tet bite pit.
const FACE_FIRST_ARM = [1, 2, 0, 3];
const FACE_COUNTER_ARM = [1, 0, 2, 3];
const FACE_SIDE_ARM = [2, 3, 1, 0];
const FACE_ROOT_RADIAL = [1, 2, 3, 0];
const FACE_ROOT_RADIAL_ALT = [2, 3, 0, 1];
const FACE_ROOT_LAST_OPEN = [3, 0, 2, 1];
const FACE_INK_SPIRAL = [3, 1, 0, 2];
const FACE_LONG_LATTICE = [1, 2, 3, 0];
const FACE_BROKEN_SYMMETRY = [2, 0, 3, 1];
// v3: poison Fang's whole-tet meals. These are deliberately hinge-heavy:
// grab corners that create/deny auto-midpoints, then use the midpoints to
// stitch a longer corner-mid-corner chain through the lattice.
const ROOT_POISON_HINGE = [0, 2, 3, 1, 6, 5, 9, 8, 4, 7, 10];
const SECOND_TET_POISON = [3, 6, 8, 9, 10, 0, 2, 1, 4, 5, 7];
const THIRD_TET_POISON = [3, 6, 8, 9, 10, 0, 2, 1, 4, 5, 7];
const LONG_CHAIN_CORNERS = [3, 0, 2, 1, 10, 6, 8, 9, 5, 4, 7];
const LONG_CHAIN_MIDS = [6, 8, 9, 5, 4, 7, 3, 0, 2, 1, 10];
const ENDPOINT_POISON = [10, 0, 1, 2, 3];
const MIDPOINT_BITE_ONLY = [6, 8, 9, 5, 4, 7];
// Frank's first-match hint against Fang: build the brood first, then harvest
// entire tets. These orders favor endpoints/centers that tend to finish
// several sub-territories at once rather than isolated midpoint nibbles.
const BROOD_OPEN_FACES = [2, 1, 3, 0];
const BROOD_ROOT_FACES = [3, 2, 1, 0];
const BROOD_SIDE_FACES = [1, 3, 2, 0];
const WHOLE_TET_CLAWS = [10, 3, 0, 2, 1, 8, 6, 9, 5, 4, 7];
const WHOLE_TET_OUTER_CLAWS = [10, 3, 6, 8, 9, 0, 2, 1, 5, 4, 7];
function inkLatticeLimit(ctx){
// In a duel, give the octopus room to actually become an octopus.
// v6 raises the ceiling: Octavius should not stop at a pair of arms if
// legal placements still exist.
const active = activeCount(ctx);
return Math.max(6, Math.min(12, active + 10));
}
function chooseAntiLockExpansion(ctx){
const tetMoves = Array.isArray(ctx && ctx.tetMoves) ? ctx.tetMoves : [];
if (!tetMoves.length || !!(ctx && ctx.boardLocked)) return null;
const tetCount = Number(ctx && ctx.tetCount || 0);
const tokenCount = Number(ctx && ctx.tokenCount || 0);
const firstAction = Number(ctx && ctx.turnActionCount || 0) === 0;
const limit = inkLatticeLimit(ctx);
// Direct answer to the lock race: while the brood is small, a legal tet
// is usually better than any one-path or two-path token. Make Fang need
// more than one reply to poison every remaining frontier.
if (tetCount >= 1 && tetCount < Math.min(8, limit) && tokenCount <= Math.max(14, tetCount + 8)) {
return exactTetFaceMove(tetMoves, 1, firstAction ? FACE_ROOT_RADIAL : FACE_ROOT_RADIAL_ALT, 'keep the board alive with another root arm') ||
exactTetFaceMove(tetMoves, newestTokenTetId(ctx), firstAction ? FACE_LONG_LATTICE : FACE_BROKEN_SYMMETRY, 'keep the board alive with an outer arm') ||
cloneMove(chooseEightArmTet(ctx), 'keep the board alive before Fang locks every face');
}
// If several legal faces still exist, continue spreading unless a truly
// enormous token shot is visible elsewhere. The big Octavius payoff only
// appears after the board has enough sockets for corner-mid-corner chains.
if (tetCount >= 8 && tetCount < limit && tetMoves.length >= 2 && tokenCount <= Math.max(18, tetCount + 10)) {
return exactTetFaceMove(tetMoves, newestTokenTetId(ctx), firstAction ? FACE_LONG_LATTICE : FACE_BROKEN_SYMMETRY, 'add one more arm before harvesting') ||
cloneMove(chooseEightArmTet(ctx), 'add one more arm before harvesting');
}
return null;
}
function chooseEightArmTet(ctx){
const moves = Array.isArray(ctx && ctx.tetMoves) ? ctx.tetMoves : [];
if (!moves.length) return null;
let best = moves[0];
let bestValue = -999999;
const lookCount = Math.min(moves.length, 8); // eight arms: look wider than the first easy branch.
const tokenCount = Number(ctx && ctx.tokenCount || 0);
const tetCount = Number(ctx && ctx.tetCount || 0);
for (let i = 0; i < lookCount; i++) {
const move = moves[i];
if (!move) continue;
const faceIdx = tetFaceIndex(move);
const sourceTet = tetFaceTetId(move);
let value = scoreOf(move);
// Early v6: make a many-armed crown around the root before crawling
// down one long hallway. Later, outer arms become valuable again.
if (tetCount <= 4 && sourceTet === 1) value += 3.25;
if (tetCount <= 4 && sourceTet > 1) value -= 0.65;
if (tetCount > 4 && sourceTet > 1) value += 1.25;
if (isCornerSocket(faceIdx) || isCenterSocket(faceIdx)) value += 0.90;
if (isEdgeMidSocket(faceIdx)) value -= 1.35;
// Make the choice feel less linear: as the token count changes, a
// different legal branch can become the favored arm.
value += ((i + tokenCount) % 4) * 0.22;
if (i >= 2) value += 0.55;
if (i >= 5) value += 0.30;
value -= i * 0.025;
if (value > bestValue) {
bestValue = value;
best = move;
}
}
return best;
}
function bestIndigoToken(moves){
if (!Array.isArray(moves) || !moves.length) return null;
let best = moves[0];
let bestValue = -999999;
moves.forEach((move, i) => {
const idx = socketIndex(move);
const id = moveTetId(move);
let value = scoreOf(move);
// Octavius values stable suckers. Edge-midpoints are useful only when
// they bite through several paths. A one-path midpoint is Fang bait.
if (isCenterSocket(idx)) value += 1.35;
if (isCornerSocket(idx)) value += 2.35;
if (isEdgeMidSocket(idx) && estimatedPathBites(move) <= 1) value -= 9.50;
if (isEdgeMidSocket(idx) && estimatedPathBites(move) === 2) value -= 2.00;
if (id > 1) value += 0.70;
if (id >= 3) value += 0.35;
if (estimatedPathBites(move) >= 2 && !isEdgeMidSocket(idx)) value += 1.25;
if (value >= RAW_TWO_PATHS) value += 4.10;
if (value >= RAW_THREE_PATHS) value += 10.10;
if (value >= RAW_FOUR_PATHS) value += 24.00;
value -= i * 0.025;
if (value > bestValue) {
bestValue = value;
best = move;
}
});
return best;
}
function chooseLongLineStrike(ctx, minScore, reason){
const moves = Array.isArray(ctx && ctx.tokenMoves) ? ctx.tokenMoves : [];
if (!moves.length) return null;
const boardLocked = !!(ctx && ctx.boardLocked);
const tetCount = Number(ctx && ctx.tetCount || 0);
const tokenCount = Number(ctx && ctx.tokenCount || 0);
const threshold = Number(minScore || RAW_THREE_PATHS);
let best = null;
let bestValue = -999999;
moves.forEach((move, i) => {
const raw = scoreOf(move);
const idx = socketIndex(move);
const id = moveTetId(move);
if (raw < threshold) return;
// Frank's screenshot case: do not spend the move on a small midpoint
// nibble when an endpoint would shoot through a whole line. Midpoints
// are allowed only when they are already a serious bite.
if (isEdgeMidSocket(idx) && raw < Math.max(RAW_THREE_PATHS, threshold + 3.0)) return;
let value = raw * 3.0;
if (isCornerSocket(idx)) value += 5.0;
if (isCenterSocket(idx)) value += 2.0;
if (isEdgeMidSocket(idx)) value -= 1.75;
// Once there are many tets, prefer outward lattice endpoints over the
// cramped root unless the root move is truly enormous.
if (tetCount >= 4 && id > 1) value += Math.min(id, 8) * 0.25;
if (tetCount >= 4 && id === 1 && raw < 6) value -= 2.25;
// This is the long straight-line trigger: four or more paths should beat
// any planned poison or quiet sucker.
if (raw >= RAW_TWO_PATHS) value += 3.0;
if (raw >= RAW_THREE_PATHS) value += 5.0;
if (raw >= RAW_FOUR_PATHS) value += 9.0;
if (boardLocked) value += 1.5;
value += ((tokenCount + id + idx) % 3) * 0.05;
value -= i * 0.02;
if (value > bestValue) {
bestValue = value;
best = move;
}
});
if (!best) return null;
return cloneMove(best, (reason || 'fire the long ink line through') + ' ' + moveTetId(best) + ':' + socketIndex(best));
}
function chooseWholeTetHarvest(ctx, minScore, reason){
const moves = Array.isArray(ctx && ctx.tokenMoves) ? ctx.tokenMoves : [];
if (!moves.length) return null;
const tetCount = Number(ctx && ctx.tetCount || 0);
const tokenCount = Number(ctx && ctx.tokenCount || 0);
const boardLocked = !!(ctx && ctx.boardLocked);
const threshold = Number(minScore || RAW_THREE_PATHS);
let best = null;
let bestValue = -999999;
moves.forEach((move, i) => {
const raw = scoreOf(move);
if (raw < threshold) return;
const idx = socketIndex(move);
const id = moveTetId(move);
let value = raw * 6.0;
// Whole-tet harvests usually show up as a large immediate score. Do not
// let a planned branch or a small poison move outrank that meal.
if (raw >= RAW_ONE_PATH) value += 10.0;
if (raw >= RAW_TWO_PATHS) value += 20.0;
if (raw >= RAW_THREE_PATHS) value += 35.0;
if (raw >= RAW_FOUR_PATHS) value += 60.0;
// Centers and corners are the likely endpoints for the big Frank-style
// tet captures. Midpoints are allowed only when the score says they are
// genuinely part of the chain.
if (isCenterSocket(idx)) value += 8.0;
if (isCornerSocket(idx)) value += 7.0;
if (isEdgeMidSocket(idx) && raw < RAW_THREE_PATHS) value -= 11.0;
if (isEdgeMidSocket(idx) && raw >= RAW_FOUR_PATHS) value += 2.0;
// Prefer harvesting the brood, not the cramped root, once the octopus has
// created a real field of tets.
if (tetCount >= 5 && id > 1) value += Math.min(id, 10) * 0.55;
if (tetCount >= 7 && id >= 4) value += 3.0;
if (tetCount >= 5 && id === 1 && raw < RAW_THREE_PATHS) value -= 4.5;
// Late board: cash now. Fang's danger is that waiting one turn can hand
// him the same whole-tet meal.
if (boardLocked) value += 8.0;
if (tokenCount >= 10) value += 2.0;
if (tokenCount >= 20) value += 4.0;
value -= i * 0.015;
if (value > bestValue) {
bestValue = value;
best = move;
}
});
if (!best) return null;
return cloneMove(best, (reason || 'harvest the whole-tet brood at') + ' ' + moveTetId(best) + ':' + socketIndex(best));
}
function chooseRadialBroodMandate(ctx){
const tetMoves = Array.isArray(ctx && ctx.tetMoves) ? ctx.tetMoves : [];
if (!tetMoves.length || !!(ctx && ctx.boardLocked)) return null;
const firstAction = Number(ctx && ctx.turnActionCount || 0) === 0;
const tetCount = Number(ctx && ctx.tetCount || 0);
const tokenCount = Number(ctx && ctx.tokenCount || 0);
const limit = inkLatticeLimit(ctx);
const newest = newestTokenTetId(ctx);
// The Fang problem: a token on the wrong root/common socket can close the
// board before Octavius has eight arms. Until at least six tets exist,
// a legal tet placement is normally worth more than a quiet token.
if (tetCount >= 1 && tetCount < Math.min(6, limit) && tokenCount <= 8) {
return exactTetFaceMove(
tetMoves,
1,
firstAction ? FACE_ROOT_RADIAL : FACE_ROOT_RADIAL_ALT,
'spread another root arm before Fang locks the board'
) ||
exactTetFaceMove(
tetMoves,
newest,
firstAction ? FACE_BROKEN_SYMMETRY : FACE_LONG_LATTICE,
'fork a live outer arm before Fang locks the board'
) ||
cloneMove(chooseEightArmTet(ctx), 'force another living arm before the teeth close');
}
// After the first crown exists, continue expanding while the token count is
// still low enough that the board is soft. This is the condition meant to
// create the eventual corner-mid-corner-mid-corner chains.
if (tetCount >= 6 && tetCount < limit && tokenCount <= Math.max(10, tetCount + 3)) {
const immediateMeal = chooseWholeTetHarvest(ctx, RAW_FOUR_PATHS, 'take the rare huge meal before growing at');
if (immediateMeal) return immediateMeal;
return exactTetFaceMove(tetMoves, 1, FACE_ROOT_LAST_OPEN, 'open the last central arm at') ||
exactTetFaceMove(tetMoves, newest, firstAction ? FACE_LONG_LATTICE : FACE_BROKEN_SYMMETRY, 'extend the radial brood into a long chain') ||
cloneMove(chooseEightArmTet(ctx), 'extend any available ink arm');
}
return null;
}
function chooseBroodOpeningTet(ctx){
const tetMoves = Array.isArray(ctx && ctx.tetMoves) ? ctx.tetMoves : [];
if (!tetMoves.length || !!(ctx && ctx.boardLocked)) return null;
const firstAction = Number(ctx && ctx.turnActionCount || 0) === 0;
const tetCount = Number(ctx && ctx.tetCount || 0);
const tokenCount = Number(ctx && ctx.tokenCount || 0);
const newest = newestTokenTetId(ctx);
const limit = Math.max(5, Math.min(10, activeCount(ctx) + 8));
// Starter correction: the first turn should look like Frank's winning
// opening, center tet plus one neighbor, not center tet plus a weak token.
if (!firstAction && tetCount === 1 && tokenCount === 0) {
return exactTetFaceMove(tetMoves, 1, BROOD_OPEN_FACES, 'hatch the second tet before the first sucker') ||
cloneMove(chooseEightArmTet(ctx), 'hatch the second tet before the first sucker');
}
// In the very early brood phase, two tet placements in a turn are allowed.
// That is how long corner-mid-corner chains become possible later.
if (tetCount >= 2 && tetCount < limit && tokenCount <= 5) {
return exactTetFaceMove(tetMoves, 1, firstAction ? FACE_ROOT_RADIAL : FACE_ROOT_RADIAL_ALT, 'grow a root-side brood before biting through') ||
exactTetFaceMove(tetMoves, newest, firstAction ? BROOD_ROOT_FACES : BROOD_SIDE_FACES, 'grow the brood before biting through') ||
cloneMove(chooseEightArmTet(ctx), 'grow another arm of the brood');
}
// Midgame: keep building only if no clear harvest is already available.
// This prevents endless beautiful arms that Fang simply eats.
if (tetCount >= 5 && tetCount < limit && tokenCount <= 13) {
const harvest = chooseWholeTetHarvest(ctx, RAW_THREE_PATHS, 'cash the brood before overbuilding at');
if (harvest) return null;
return exactTetFaceMove(tetMoves, newest, FACE_LONG_LATTICE, 'extend the ink brood one more tet through') ||
cloneMove(chooseEightArmTet(ctx), 'extend the ink brood one more tet');
}
return null;
}
function endpointPoisonMove(moves, tetId, reasonPrefix){
if (!Array.isArray(moves) || tetId < 1) return null;
return exactSocketMove(moves, tetId, ENDPOINT_POISON, -999, reasonPrefix) ||
exactSocketMove(moves, tetId, MIDPOINT_BITE_ONLY, RAW_TWO_PATHS, reasonPrefix + ' only if it already bites twice');
}
function chooseTetFirstArm(ctx){
const tetMoves = Array.isArray(ctx && ctx.tetMoves) ? ctx.tetMoves : [];
if (!tetMoves.length || !!(ctx && ctx.boardLocked)) return null;
const firstAction = Number(ctx && ctx.turnActionCount || 0) === 0;
const tetCount = Number(ctx && ctx.tetCount || 0);
const tokenCount = Number(ctx && ctx.tokenCount || 0);
const limit = inkLatticeLimit(ctx);
// The key fix: when the opponent has only one root token, place a tet
// first so the follow-up token can be a shared-corner advantage.
if (firstAction && tetCount === 1 && tokenCount === 1) {
return exactTetFaceMove(tetMoves, 1, FACE_FIRST_ARM, 'unfold the first arm before touching') ||
cloneMove(chooseEightArmTet(ctx), 'unfold the first arm before touching');
}
// Octavius should not stop at two tets if a third branch is legal. This is
// the "eight arms" behavior: branch, anchor, branch again.
if (firstAction && tetCount === 2 && tokenCount <= 4 && tetCount < limit) {
return exactTetFaceMove(tetMoves, 1, FACE_ROOT_RADIAL_ALT, 'split a new root arm into a third') ||
exactTetFaceMove(tetMoves, newestTokenTetId(ctx), FACE_COUNTER_ARM, 'split the second arm into a third') ||
exactTetFaceMove(tetMoves, 2, FACE_COUNTER_ARM, 'split the second arm into a third') ||
cloneMove(chooseEightArmTet(ctx), 'split the second arm into a third');
}
// If the board is still loose, look wider than the obvious first branch.
// This gives Octavius a more alien, radial opening against non-Fang styles.
if (firstAction && tetCount >= 2 && tetCount < limit && tokenCount <= 12) {
return exactTetFaceMove(tetMoves, newestTokenTetId(ctx), FACE_SIDE_ARM, 'throw another arm sideways') ||
exactTetFaceMove(tetMoves, 1, FACE_INK_SPIRAL, 'throw another arm through the ink') ||
cloneMove(chooseEightArmTet(ctx), 'throw another arm where the current opens');
}
return null;
}
function chooseSecondTetArm(ctx){
const tetMoves = Array.isArray(ctx && ctx.tetMoves) ? ctx.tetMoves : [];
if (!tetMoves.length || !!(ctx && ctx.boardLocked)) return null;
const firstAction = Number(ctx && ctx.turnActionCount || 0) === 0;
const tetCount = Number(ctx && ctx.tetCount || 0);
const tokenCount = Number(ctx && ctx.tokenCount || 0);
const limit = inkLatticeLimit(ctx);
// New v3 behavior: use the second action to place another tet while the
// board is still soft. This is what lets Octavius later make long paths
// instead of one-tet nibbles.
if (!firstAction && tetCount >= 2 && tetCount < limit && tokenCount <= 7) {
return exactTetFaceMove(tetMoves, 1, tetCount < 5 ? FACE_ROOT_RADIAL_ALT : FACE_ROOT_LAST_OPEN, 'lay a second-action root arm before the teeth close') ||
exactTetFaceMove(tetMoves, newestTokenTetId(ctx), FACE_LONG_LATTICE, 'lay another ink-lattice tet through') ||
exactTetFaceMove(tetMoves, tetCount, FACE_LONG_LATTICE, 'lay another ink-lattice tet through') ||
exactTetFaceMove(tetMoves, 2, FACE_BROKEN_SYMMETRY, 'branch a second arm before the poison') ||
cloneMove(chooseEightArmTet(ctx), 'place a second-action tet before the teeth close');
}
return null;
}
function choosePoisonLatticeToken(ctx){
const moves = Array.isArray(ctx && ctx.tokenMoves) ? ctx.tokenMoves : [];
if (!moves.length) return null;
const tetCount = Number(ctx && ctx.tetCount || 0);
const tokenCount = Number(ctx && ctx.tokenCount || 0);
const boardLocked = !!(ctx && ctx.boardLocked);
const firstAction = Number(ctx && ctx.turnActionCount || 0) === 0;
const newest = newestTokenTetId(ctx);
if (tetCount < 2) return null;
// Once the board is locked, stop nibbling. Look for the long straight
// endpoint shot before any scripted poison pattern.
if (boardLocked) {
const longShot = chooseLongLineStrike(ctx, tetCount >= 5 ? RAW_THREE_PATHS : RAW_FOUR_PATHS, 'fire the long straight ink line through');
if (longShot) return longShot;
}
// Early poison: Fang's huge scores are coming from whole-tet meals on the
// root/second tet. Take one of those hinge sockets before the teeth close.
if (tetCount >= 3 && tokenCount <= 12) {
const second = exactSocketMove(
moves,
2,
SECOND_TET_POISON,
-999,
"poison Fang\'s second-tet meal at"
);
if (second) return second;
const root = exactSocketMove(
moves,
1,
ROOT_POISON_HINGE,
-999,
'ink-poison the root hinge at'
);
if (root) return root;
}
// Once the lattice exists, walk the chain outward. This favors a visible
// corner-mid-corner-mid rhythm across tets instead of isolated local grabs.
if (tetCount >= 4) {
const lineShot = chooseLongLineStrike(ctx, boardLocked ? RAW_TWO_PATHS : RAW_THREE_PATHS, 'snap the corner-to-corner ink line through');
if (lineShot) return lineShot;
const tetOrder = [];
for (let id = newest; id >= 2; id--) tetOrder.push(id);
tetOrder.push(1);
for (let i = 0; i < tetOrder.length; i++) {
const id = tetOrder[i];
const order = ((tokenCount + i) % 2 === 0) ? LONG_CHAIN_CORNERS : LONG_CHAIN_MIDS;
const chain = exactSocketMove(
moves,
id,
order,
boardLocked ? RAW_TWO_PATHS : RAW_THREE_PATHS,
'walk the long ink chain through'
);
if (chain) return chain;
}
}
// If there are only three tets, make tet 3 poisonous too so Fang cannot
// simply move the meal one tet outward.
if (tetCount >= 3 && tokenCount <= 18) {
const third = endpointPoisonMove(moves, 3, 'poison the next whole-tet bite at');
if (third) return third;
}
return null;
}
function chooseAnchorAfterArm(ctx){
const moves = Array.isArray(ctx && ctx.tokenMoves) ? ctx.tokenMoves : [];
if (!moves.length) return null;
const firstAction = Number(ctx && ctx.turnActionCount || 0) === 0;
const tetCount = Number(ctx && ctx.tetCount || 0);
const tokenCount = Number(ctx && ctx.tokenCount || 0);
const newest = newestTokenTetId(ctx);
// After placing the first branch, grab the root common corner rather than
// an isolated edge. This is the objective correction Frank spotted.
if (!firstAction && tetCount === 2 && tokenCount <= 2) {
return exactSocketMove(moves, 1, ROOT_SHARED_SUCKER, -999, 'set a sucker on the shared corner at');
}
// After creating the lattice, do not blindly park on the newest center.
// Fang is waiting at the older hinge. Poison that meal first, then anchor
// the fresh arm only if the hinge is already safe.
if (!firstAction && tetCount >= 3 && tokenCount <= 6) {
return endpointPoisonMove(moves, 2, 'poison the hinge before anchoring at') ||
endpointPoisonMove(moves, 1, 'poison the root before anchoring at') ||
exactSocketMove(moves, newest, NEW_ARM_ANCHOR, -999, 'anchor the new arm at') ||
exactSocketMove(moves, 3, NEW_ARM_ANCHOR, -999, 'anchor the new arm at');
}
return null;
}
function chooseRadialTokenMap(ctx){
const moves = Array.isArray(ctx && ctx.tokenMoves) ? ctx.tokenMoves : [];
if (!moves.length) return null;
const tetCount = Number(ctx && ctx.tetCount || 0);
const tokenCount = Number(ctx && ctx.tokenCount || 0);
const boardLocked = !!(ctx && ctx.boardLocked);
const firstAction = Number(ctx && ctx.turnActionCount || 0) === 0;
const newest = newestTokenTetId(ctx);
if (boardLocked && tetCount >= 3) {
const longShot = chooseLongLineStrike(ctx, tetCount >= 5 ? RAW_TWO_PATHS : RAW_THREE_PATHS, 'ignore the small bite and spear the long line at');
if (longShot) return longShot;
}
if (tetCount === 1 && tokenCount <= 0) {
return exactSocketMove(moves, 1, ROOT_OPEN_CENTER, -999, 'open the central eye at');
}
if (tetCount === 1 && tokenCount <= 4) {
return exactSocketMove(moves, 1, ROOT_SHARED_SUCKER, -999, 'place the first sucker at');
}
// Fang wins by turning tet 2 into a throat. Octavius now contests tet 2
// early instead of leaving it as prey.
if (tetCount >= 2 && tokenCount >= 3 && tokenCount <= 13) {
const tet2First = (tokenCount % 2 === 0);
const outerA = exactSocketMove(moves, tet2First ? 2 : newest, OUTER_ARM_SWEEP, boardLocked ? RAW_TWO_PATHS : RAW_THREE_PATHS, 'wrap an outer arm around');
if (outerA) return outerA;
const root = exactSocketMove(moves, 1, boardLocked ? ROOT_INK_CLOUD : ROOT_RADIAL_DENIAL, boardLocked ? RAW_TWO_PATHS : RAW_THREE_PATHS, 'cloud the root line at');
if (root) return root;
const outerB = exactSocketMove(moves, tet2First ? newest : 2, OUTER_ARM_FINISH, RAW_TWO_PATHS, 'finish the outer suction at');
if (outerB) return outerB;
}
if (tetCount >= 3 && tokenCount <= 21) {
const newArm = exactSocketMove(moves, newest, OUTER_ARM_ANCHOR, RAW_TWO_PATHS, 'keep pressure on the far arm at');
if (newArm) return newArm;
const tet2 = exactSocketMove(moves, 2, OUTER_ARM_FINISH, RAW_TWO_PATHS, 'do not release tet two at');
if (tet2) return tet2;
}
if (tetCount === 1 && tokenCount <= 11) {
return exactSocketMove(moves, 1, ROOT_RING_CLOSE, 0.0, 'close the ink ring at');
}
return null;
}
registry.octavius_indigo = {
key:'octavius_indigo',
id:'octavius_indigo',
name:'Octavius Indigo',
level:7,
style:'eight-arm anti-lock radial brood and harvest hunter',
temperament:"hatches a wider radial brood before biting, poisons Fang's center sockets, then cashes sub-territories and full tets",
chooseMove(ctx){
if (!ctx || ctx.matchEnded || ctx.tournamentEnded) return null;
if (canOpen(ctx)) return {type:'start_tet', score:1000, reason:'open the first ink pool'};
const token0 = moveAt(ctx.tokenMoves, 0);
const tet0 = moveAt(ctx.tetMoves, 0);
const boardLocked = !!ctx.boardLocked;
const firstAction = Number(ctx.turnActionCount || 0) === 0;
const tetCount = Number(ctx.tetCount || 0);
const tokenCount = Number(ctx.tokenCount || 0);
// First, never pass up an obvious whole-tet/sub-tet feast. This is the
// lesson from Frank's 105-point win: Fang can out-path you and still lose
// if you cash the C/D columns.
const decisiveHarvest = chooseWholeTetHarvest(
ctx,
boardLocked ? RAW_TWO_PATHS : (tetCount >= 6 ? RAW_THREE_PATHS : RAW_FOUR_PATHS),
'cash the whole-tet brood at'
);
if (decisiveHarvest && (boardLocked || tokenCount >= 8 || scoreOf(decisiveHarvest) >= RAW_FOUR_PATHS)) {
return decisiveHarvest;
}
const radialBrood = chooseRadialBroodMandate(ctx);
if (radialBrood) return radialBrood;
const broodOpening = chooseBroodOpeningTet(ctx);
if (broodOpening) return broodOpening;
const tetFirstArm = chooseTetFirstArm(ctx);
if (tetFirstArm) return tetFirstArm;
const secondTetArm = chooseSecondTetArm(ctx);
if (secondTetArm) return secondTetArm;
const antiLockExpansion = chooseAntiLockExpansion(ctx);
if (antiLockExpansion) return antiLockExpansion;
if (boardLocked && tetCount >= 3) {
const harvest = chooseWholeTetHarvest(ctx, tetCount >= 5 ? RAW_TWO_PATHS : RAW_THREE_PATHS, 'finish the full-tet harvest at');
if (harvest) return harvest;
const longShot = chooseLongLineStrike(ctx, tetCount >= 5 ? RAW_TWO_PATHS : RAW_THREE_PATHS, 'spear the long line instead of nibbling at');
if (longShot) return longShot;
}
const anchorAfterArm = chooseAnchorAfterArm(ctx);
if (anchorAfterArm) return anchorAfterArm;
const poisonLattice = choosePoisonLatticeToken(ctx);
if (poisonLattice) return poisonLattice;
const radialMap = chooseRadialTokenMap(ctx);
if (radialMap) return radialMap;
const shapedToken = bestIndigoToken(ctx.tokenMoves) || token0;
if (shapedToken && (!isSmallMidpointNibble(shapedToken) || boardLocked || tetCount >= 8)) {
return cloneMove(
shapedToken,
scoreOf(shapedToken) >= 8 ? 'pull the scoring arm tight' : 'place the quiet sucker'
);
}
const wideTet = chooseEightArmTet(ctx) || tet0;
if (!boardLocked && wideTet && firstAction && tetCount < inkLatticeLimit(ctx) && tokenCount >= 1) {
return cloneMove(wideTet, 'unfold one more arm');
}
if (!boardLocked && wideTet) return cloneMove(wideTet, 'unfold through the ink');
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.
