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/echo_echo.js
/*
* Tetgame Rabbot: Echo Echo
*
* Echo v5 — echo-chamber counterpuncher with root-reversal booster.
*
* Booster goal:
* - keep Echo's mirroring personality,
* - stop handing Grim a free private tomb,
* - answer a lone opening mark by raising and claiming her own echo chamber,
* - invade an opponent's private chamber early instead of finishing the old root fight too late,
* - answer Grim's single-root royal seal by raising a counter-chamber instead of freezing the root,
* - when Grim raises a private tomb, mirror it with a new chamber before invading,
* - when Grim starts and Echo answers with a chamber, return to the root tet fast
* enough to steal the remaining corner keys before Grim closes his tomb.
*
* Tetgame supplies legal moves in ctx.tokenMoves and ctx.tetMoves;
* this file only chooses among those legal moves.
* Voice lines live separately in js/voices/echo_echo.json.
*/
(function(global){
'use strict';
const registry = global.TetgameRabbotBrains = global.TetgameRabbotBrains || {};
function cloneMove(move, reason, scoreBoost){
if (!move) return null;
const copy = Object.assign({}, move);
if (reason) copy.reason = reason;
if (typeof scoreBoost === 'number') copy.score = Number(copy.score || 0) + scoreBoost;
return copy;
}
function pass(reason){
return {type:'pass', score:-1, reason:reason || 'no legal echo found'};
}
function scoreOf(move){
return Number(move && move.score || 0);
}
function socketIndex(move){
return Number(move && move.socket && move.socket.metadata ? move.socket.metadata.socketIndex : -1);
}
function tetId(move){
return Number(move && move.socket && move.socket.metadata ? move.socket.metadata.tetId : -1);
}
function socketReason(move){
if (!move || !move.socket || !move.socket.metadata) return 'the open point';
return 'socket ' + move.socket.metadata.tetId + ':' + move.socket.metadata.socketIndex;
}
function isCenterSocket(move){
return socketIndex(move) === 10;
}
function isCornerSocket(move){
const idx = socketIndex(move);
return idx >= 0 && idx <= 3;
}
function isEdgeSocket(move){
const idx = socketIndex(move);
return idx >= 4 && idx <= 9;
}
function isRootSocket(move){
return tetId(move) === 1;
}
function canOpen(ctx){
return ctx && Number(ctx.tetCount || 0) <= 0;
}
function activeCount(ctx){
return Math.max(2, Number(ctx && ctx.activeCount || 2));
}
function firstAction(ctx){
return Number(ctx && ctx.turnActionCount || 0) === 0;
}
function secondAction(ctx){
return !firstAction(ctx);
}
function newestTetId(ctx){
const list = Array.isArray(ctx && ctx.tokenMoves) ? ctx.tokenMoves : [];
let max = Math.max(1, Number(ctx && ctx.tetCount || 1));
list.forEach(move => {
const id = tetId(move);
if (id > max) max = id;
});
return max;
}
function captureEstimate(move){
const s = scoreOf(move);
if (s >= 23) return 3;
if (s >= 15) return 2;
if (s >= 7.6) return 1;
return 0;
}
function openingLimit(ctx, extra){
const active = activeCount(ctx);
return Math.max(2, Math.min(5, active + Number(extra || 1)));
}
function reasonForToken(move, prefix){
const captures = captureEstimate(move);
const socket = socketReason(move);
if (captures >= 3) return 'repeat the whole chorus at ' + socket;
if (captures >= 2) return 'double the echo through ' + socket;
if (captures >= 1) return 'catch the answering echo at ' + socket;
return (prefix || 'mark the echo at') + ' ' + socket;
}
function tokenValue(move, ctx){
if (!move) return -999;
const idx = socketIndex(move);
const id = tetId(move);
const raw = scoreOf(move);
const captures = captureEstimate(move);
const boardLocked = !!(ctx && ctx.boardLocked);
const tokens = Number(ctx && ctx.tokenCount || 0);
const tets = Number(ctx && ctx.tetCount || 0);
const active = activeCount(ctx);
const newest = newestTetId(ctx);
let v = raw * 1.18;
// Echo's general style: she likes strong captures, but she also likes
// center/corner anchors because they make automatic replies possible.
v += captures * 2.10;
if (captures >= 2) v += 2.85;
if (captures >= 3) v += 4.15;
if (idx === 10) v += 3.10;
if (idx >= 0 && idx <= 3) v += 4.85;
if (idx >= 4 && idx <= 9) {
v -= 1.55;
if (raw >= 9.5) v += 1.85;
if (captures >= 2) v += 1.75;
}
// Booster: Grim's private-tomb line wins because the opponent keeps
// playing on the old root stone while he harvests the new chamber.
// Echo now hears that second chamber and answers it immediately.
if (active <= 2 && tets === 2 && id === newest && tokens <= active * 4) {
v += 6.40;
if (idx === 10) v += 4.80;
if (idx >= 0 && idx <= 3) v += 5.60;
if (idx >= 4 && idx <= 9 && raw < 10.5) v -= 1.80;
}
// If Echo herself raised the new chamber, the second action should claim
// the chamber rather than wandering back to the old root tet.
if (active <= 2 && secondAction(ctx) && tets === 2 && tokens === 1 && id === newest) {
v += 8.50;
if (idx === 10) v += 7.50;
if (idx >= 0 && idx <= 3) v += 4.50;
if (idx >= 4 && idx <= 9) v -= 3.50;
}
// Root fight: do not over-love edge mids early. Let corners produce mids.
if (active <= 2 && tets <= 2 && tokens <= active * 3 && isRootSocket(move)) {
if (idx === 10) v += 2.10;
if (idx >= 0 && idx <= 3) v += 3.30;
if (idx >= 4 && idx <= 9 && raw < 11.0) v -= 2.60;
}
// V3 booster: when Grim opens with his center royal seal, Echo was
// answering with one corner plus one tempting edge. That lets Grim take
// the remaining corners and roll into auto-tokens plus a sub. On a lone
// root tet, Echo now treats corner pairs as the real melody and waits on
// edge sockets until the corner race is mostly decided.
if (active <= 2 && tets <= 1 && isRootSocket(move) && tokens >= 1 && tokens <= 6) {
if (idx >= 0 && idx <= 3) v += 10.75;
if (idx >= 4 && idx <= 9 && raw < 15.0) v -= 7.80;
if (idx >= 4 && idx <= 9 && captures <= 1) v -= 2.25;
}
// Late locked boards: finish value wherever it actually appears.
if (boardLocked) {
v += 1.25;
if (tokens >= active * 5) v += 0.80;
if (tokens >= active * 6 && captures >= 1) v += 1.10;
if (id > 1 && captures >= 1) v += 0.95;
}
return v;
}
function bestToken(ctx, limit){
const list = Array.isArray(ctx && ctx.tokenMoves) ? ctx.tokenMoves : [];
let best = null;
let bestScore = -999;
list.slice(0, Math.max(1, limit || 24)).forEach(move => {
const v = tokenValue(move, ctx);
if (v > bestScore) {
bestScore = v;
best = move;
}
});
return best;
}
function bestTokenAtLeast(ctx, threshold, limit){
const move = bestToken(ctx, limit || 24);
return move && scoreOf(move) >= Number(threshold || 0) ? move : null;
}
function echoChamberTokenValue(move, ctx){
if (!move) return -999;
const id = tetId(move);
const idx = socketIndex(move);
const newest = newestTetId(ctx);
if (id !== newest || id < 2) return -999;
let v = scoreOf(move) * 1.14;
const captures = captureEstimate(move);
if (idx === 10) v += 12.20;
if (idx >= 0 && idx <= 3) v += 8.90;
if (idx >= 4 && idx <= 9) {
v -= 3.70;
if (captures >= 1) v += 1.20;
if (scoreOf(move) >= 10.5) v += 2.10;
}
v += captures * 1.55;
return v;
}
function bestEchoChamberToken(ctx){
const list = Array.isArray(ctx && ctx.tokenMoves) ? ctx.tokenMoves : [];
let best = null;
let bestScore = -999;
list.forEach(move => {
const v = echoChamberTokenValue(move, ctx);
if (v > bestScore) {
bestScore = v;
best = move;
}
});
return best && bestScore > -100 ? best : null;
}
function chamberContestValue(move, ctx){
if (!move) return -999;
const id = tetId(move);
const idx = socketIndex(move);
const newest = newestTetId(ctx);
if (id !== newest || id < 2) return -999;
let v = tokenValue(move, ctx) + 4.00;
if (idx === 10) v += 3.00;
if (idx >= 0 && idx <= 3) v += 4.70;
if (idx >= 4 && idx <= 9) {
v -= 1.50;
if (scoreOf(move) >= 9.5) v += 2.00;
}
return v;
}
function bestChamberContestToken(ctx){
const list = Array.isArray(ctx && ctx.tokenMoves) ? ctx.tokenMoves : [];
let best = null;
let bestScore = -999;
list.forEach(move => {
const v = chamberContestValue(move, ctx);
if (v > bestScore) {
bestScore = v;
best = move;
}
});
return best && bestScore > -100 ? best : null;
}
function rootDuetSealValue(move, ctx){
if (!move) return -999;
if (activeCount(ctx) > 2) return -999;
if (!!(ctx && ctx.boardLocked)) return -999;
const tets = Number(ctx && ctx.tetCount || 0);
const tokens = Number(ctx && ctx.tokenCount || 0);
const idx = socketIndex(move);
const id = tetId(move);
// Exactly the Grim-start problem: one root tet, one royal mark already
// on it, and Echo gets two actions. Take two corners before any edge bait.
if (tets > 1 || tokens < 1 || tokens > 2 || id !== 1) return -999;
if (idx < 0 || idx > 3) return -999;
let v = scoreOf(move) * 0.75;
v += 24.0;
// Keep choices stable but not frozen; lower corners tend to interrupt
// the royal-center pattern earlier, while score still breaks close cases.
if (idx === 0) v += 2.40;
if (idx === 1) v += 2.10;
if (idx === 2) v += 1.70;
if (idx === 3) v += 1.40;
if (secondAction(ctx)) v += 3.50;
return v;
}
function bestRootDuetSealToken(ctx){
const list = Array.isArray(ctx && ctx.tokenMoves) ? ctx.tokenMoves : [];
let best = null;
let bestScore = -999;
list.forEach(move => {
const v = rootDuetSealValue(move, ctx);
if (v > bestScore) {
bestScore = v;
best = move;
}
});
return best && bestScore > -100 ? best : null;
}
function rootReversalValue(move, ctx){
if (!move) return -999;
if (activeCount(ctx) > 2) return -999;
const tets = Number(ctx && ctx.tetCount || 0);
const tokens = Number(ctx && ctx.tokenCount || 0);
const idx = socketIndex(move);
const id = tetId(move);
const raw = scoreOf(move);
const captures = captureEstimate(move);
// This is Echo's answer to Grim's best counterpunch:
// Grim starts with the royal center, Echo raises and claims a chamber,
// then Grim harvests two root keys. If Echo keeps playing in her new
// chamber, Grim closes the old root tet for the match. Echo must snap
// back to tet 1 and take the remaining corner/key sockets first.
if (tets !== 2 || tokens < 4 || tokens > 11 || id !== 1) return -999;
let v = raw * 1.08 + captures * 2.75;
if (idx >= 0 && idx <= 3) {
v += 24.00;
// In the royal-seal pattern, sockets 1 and 3 are often the two keys
// Grim wants next; make Echo prefer them when legal.
if (idx === 3) v += 4.20;
if (idx === 1) v += 3.90;
if (idx === 0) v += 2.40;
if (idx === 2) v += 2.20;
}
if (idx >= 4 && idx <= 9) {
// Edges are only worth choosing early if they are actually paying.
v -= 5.80;
if (captures >= 2) v += 9.40;
if (captures >= 3) v += 4.60;
if (raw >= 11.0) v += 4.25;
// These sockets are frequent tomb-closing/swing points once the
// corners are gone, so let them break ties after the key corners.
if (idx === 9) v += 2.80;
if (idx === 8) v += 2.25;
if (idx === 6) v += 1.80;
if (idx === 5) v += 1.45;
}
if (idx === 10) v -= 2.50;
if (secondAction(ctx)) v += 2.25;
if (ctx && ctx.boardLocked) v += 1.20;
return v;
}
function bestRootReversalToken(ctx){
const list = Array.isArray(ctx && ctx.tokenMoves) ? ctx.tokenMoves : [];
let best = null;
let bestScore = -999;
list.forEach(move => {
const v = rootReversalValue(move, ctx);
if (v > bestScore) {
bestScore = v;
best = move;
}
});
return best && bestScore > -100 ? best : null;
}
function shouldRootReversalAfterEchoChamber(ctx){
if (!ctx) return false;
if (activeCount(ctx) > 2) return false;
const tets = Number(ctx.tetCount || 0);
const tokens = Number(ctx.tokenCount || 0);
// The important Grim-start window arrives just after Echo has answered
// with her own chamber and Grim has begun harvesting the original root.
// Keep applying this for a few moves so Echo can take both root keys if
// a bonus/auto-token changes the count mid-turn.
return tets === 2 && tokens >= 4 && tokens <= 11;
}
function shouldPlayRootDuetSeal(ctx){
if (!ctx || ctx.boardLocked) return false;
if (activeCount(ctx) > 2) return false;
const tets = Number(ctx.tetCount || 0);
const tokens = Number(ctx.tokenCount || 0);
return tets <= 1 && tokens >= 1 && tokens <= 2;
}
function tetValue(move, ctx){
if (!move) return -999;
const raw = scoreOf(move);
const tokens = Number(ctx && ctx.tokenCount || 0);
const tets = Number(ctx && ctx.tetCount || 0);
const active = activeCount(ctx);
let v = raw;
// Echo may build a twin chamber, but she should not become a reckless
// builder. Construction is mainly an opening answer, not a late habit.
if (firstAction(ctx)) v += 0.90;
if (secondAction(ctx)) v -= 3.50;
if (tets <= 1) v += 2.40;
if (tets === 2) v -= 0.35;
if (tets >= openingLimit(ctx, 1)) v -= 4.10;
if (tokens >= active * 2) v -= 2.10;
if (tokens >= active * 3) v -= 3.40;
return v;
}
function bestTet(ctx, limit){
const list = Array.isArray(ctx && ctx.tetMoves) ? ctx.tetMoves : [];
let best = null;
let bestScore = -999;
list.slice(0, Math.max(1, limit || 8)).forEach(move => {
const v = tetValue(move, ctx);
if (v > bestScore) {
bestScore = v;
best = move;
}
});
return best;
}
function shouldRaiseEchoChamber(ctx, tet){
if (!ctx || !tet || ctx.boardLocked) return false;
if (activeCount(ctx) > 2) return false;
// v4 correction: the root-duet answer froze the board too early and let
// Grim win the small one-tet fight. Against a single royal seal, Echo
// now answers like a true echo: build the second chamber first, then claim
// it on action two.
return firstAction(ctx) && Number(ctx.tetCount || 0) === 1 && Number(ctx.tokenCount || 0) === 1;
}
function shouldClaimEchoChamberAfterRaise(ctx){
if (!ctx || ctx.boardLocked) return false;
if (activeCount(ctx) > 2) return false;
return secondAction(ctx) && Number(ctx.tetCount || 0) === 2 && Number(ctx.tokenCount || 0) === 1;
}
function shouldMirrorPrivateTomb(ctx, tet){
if (!ctx || !tet || ctx.boardLocked) return false;
if (activeCount(ctx) > 2) return false;
// If Echo opened with a mark and Grim answered by raising and claiming a
// private tomb, simple invasion loses: Grim closes the old root and his
// chamber together. Echo's better personality move is to repeat the tomb
// one layer louder, keeping the board open and stealing tempo back.
return firstAction(ctx) && Number(ctx.tetCount || 0) === 2 && Number(ctx.tokenCount || 0) === 2;
}
function shouldContestPrivateChamber(ctx){
if (!ctx) return false;
if (activeCount(ctx) > 2) return false;
const tets = Number(ctx.tetCount || 0);
const tokens = Number(ctx.tokenCount || 0);
// Echo started with a mark, then Grim-like opponents build and claim a
// private chamber. On Echo's next turn, the game has two tets and two
// tokens. Go into the chamber immediately.
return tets === 2 && tokens >= 2 && tokens <= activeCount(ctx) * 4;
}
registry.echo_echo = {
key:'echo_echo',
id:'echo_echo',
name:'Echo Echo',
gender:'female',
level:11,
style:'echo-chamber counterpuncher / tomb-mirror / root-reversal booster',
temperament:'mirrors the enemy plan, then repeats it one chamber louder',
chooseMove(ctx){
if (!ctx || ctx.matchEnded || ctx.tournamentEnded) return null;
if (canOpen(ctx)) return {type:'start_tet', score:1000, reason:'sing the first note'};
const boardLocked = !!ctx.boardLocked;
const tokens = Number(ctx.tokenCount || 0);
const tets = Number(ctx.tetCount || 0);
const active = activeCount(ctx);
const royalToken = bestTokenAtLeast(ctx, 22.0, 24);
const doubleToken = bestTokenAtLeast(ctx, 14.4, 24);
const singleToken = bestTokenAtLeast(ctx, 7.30, 24);
const usefulToken = bestTokenAtLeast(ctx, 3.00, 24);
const quietToken = bestToken(ctx, 24);
const tet = !boardLocked ? bestTet(ctx, 8) : null;
const echoChamberToken = bestEchoChamberToken(ctx);
const contestToken = bestChamberContestToken(ctx);
const rootDuetSealToken = bestRootDuetSealToken(ctx);
const rootReversalToken = bestRootReversalToken(ctx);
if (shouldRaiseEchoChamber(ctx, tet)) {
return cloneMove(tet, 'answer the royal seal with an echo chamber', 0.95);
}
if (shouldClaimEchoChamberAfterRaise(ctx) && echoChamberToken) {
return cloneMove(echoChamberToken, 'claim the echo chamber at ' + socketReason(echoChamberToken), 1.35);
}
if (shouldRootReversalAfterEchoChamber(ctx) && rootReversalToken) {
return cloneMove(rootReversalToken, "turn Grim\'s tomb back at " + socketReason(rootReversalToken), 1.45);
}
if (shouldMirrorPrivateTomb(ctx, tet)) {
return cloneMove(tet, 'mirror the private tomb with a louder chamber', 0.90);
}
if (shouldPlayRootDuetSeal(ctx) && rootDuetSealToken) {
return cloneMove(rootDuetSealToken, 'counter-sing a root duet at ' + socketReason(rootDuetSealToken), 1.15);
}
if (shouldContestPrivateChamber(ctx) && contestToken) {
return cloneMove(contestToken, 'invade the private chamber at ' + socketReason(contestToken), 1.10);
}
// Second actions should normally convert sound into score, not wander.
if (secondAction(ctx)) {
if (royalToken) return cloneMove(royalToken, reasonForToken(royalToken), 1.20);
if (doubleToken) return cloneMove(doubleToken, reasonForToken(doubleToken), 0.95);
if (singleToken) return cloneMove(singleToken, reasonForToken(singleToken), 0.65);
if (usefulToken) return cloneMove(usefulToken, reasonForToken(usefulToken), 0.35);
if (quietToken) return cloneMove(quietToken, 'place the answering note at ' + socketReason(quietToken), 0.15);
if (tet) return cloneMove(tet, 'repeat the board at an angle', -0.25);
}
if (royalToken) return cloneMove(royalToken, reasonForToken(royalToken), 1.20);
if (doubleToken) return cloneMove(doubleToken, reasonForToken(doubleToken), 0.95);
if (boardLocked && singleToken) return cloneMove(singleToken, reasonForToken(singleToken), 0.75);
// Early, Echo can still build, but only when the board is quiet enough
// that a new chamber is likely to become her reply rather than a gift.
if (!boardLocked && firstAction(ctx) && tet && tets < openingLimit(ctx, 1) && tokens < active && (!usefulToken || scoreOf(usefulToken) < 3.6)) {
return cloneMove(tet, 'repeat the board at an angle', 0.25);
}
if (singleToken) return cloneMove(singleToken, reasonForToken(singleToken), 0.65);
if (usefulToken) return cloneMove(usefulToken, reasonForToken(usefulToken), 0.35);
if (!boardLocked && firstAction(ctx) && tet && tets <= 2 && (!quietToken || scoreOf(quietToken) < 2.5)) {
return cloneMove(tet, 'open a second echo', 0.05);
}
if (quietToken) return cloneMove(quietToken, 'answer the nearest echo at ' + socketReason(quietToken), 0.10);
if (!boardLocked && tet) return cloneMove(tet, 'echo the empty space', -0.10);
return pass('the room is too quiet');
}
};
})(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.
