Tetgame mark
Tetgame deep-code room

The Diamond Mine

Inspect Tetgame Rabbot behavior files and prepare the future safe editor for strategy tuning.

Welcome guest — sign in through the Trigame front door when you are ready.
Log in via Trigame

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/morrigan_black.js

/*
 * Tetgame Rabbot: Morrigan Black
 *
 * Morrigan v7 — opportunist shadow tactician with corner-pair priorities, first-mark opening discipline, and late outer-vine denial.
 * 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/morrigan_black.json.
 */
(function(global){
  'use strict';

  const registry = global.TetgameRabbotBrains = global.TetgameRabbotBrains || {};

  function cloneMove(move, fallbackReason, scoreBoost){
    if (!move) return null;
    const copy = Object.assign({}, move);
    if (fallbackReason) copy.reason = fallbackReason;
    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 action 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 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 isOuterVineSocket(move, ctx){
    const id = tetId(move);
    const tetCount = Number(ctx && ctx.tetCount || 0);
    return id >= 3 && id >= Math.max(3, tetCount - 1);
  }

  function isOuterDenialEdge(move){
    const idx = socketIndex(move);
    return idx === 6 || idx === 8 || idx === 9 || idx === 5;
  }

  function isPrimarySocket(move){
    return isCenterSocket(move) || isCornerSocket(move);
  }

  function socketReason(move){
    if (!move || !move.socket || !move.socket.metadata) return '';
    return 'socket ' + move.socket.metadata.tetId + ':' + move.socket.metadata.socketIndex;
  }

  function tokenValue(move, ctx){
    if (!move) return -999;
    const idx = socketIndex(move);
    const raw = scoreOf(move);
    let v = raw * 1.12;

    // Morrigan v6 is deliberately corner-pair first. Edge-mid sockets can
    // usually be gained automatically after the two neighboring corners are
    // owned, so an edge-mid must be a huge immediate payoff before she takes
    // it directly. This affects every token choice, not only shadow-blocks.
    if (idx === 10) v += 2.05;             // center still matters for subs/tets
    if (idx >= 0 && idx <= 3) v += 5.40;   // corners create/deny auto mids
    if (idx >= 4 && idx <= 9) {
      v -= 4.85;                           // direct mid claims are usually bait
      if (raw < 8.5) v -= 2.10;
      if (raw >= 10.5) v += 1.10;           // only a real windfall can tempt her
    }

    // On a second action, she values conversion, but not enough to forget
    // the corner-pair rule.
    if (ctx && Number(ctx.turnActionCount || 0) > 0) v += 0.85;

    // Late-board sockets matter more because they are harder to replace.
    const activeCount = Math.max(2, Number(ctx && ctx.activeCount || 2));
    const tokenCount = Number(ctx && ctx.tokenCount || 0);
    const tetCount = Number(ctx && ctx.tetCount || 0);
    const boardLocked = !!(ctx && ctx.boardLocked);
    const id = tetId(move);
    if (tokenCount > activeCount * 2) v += 0.45;
    if (tokenCount > activeCount * 3) v += 0.65;

    // v5: once the board has locked and an outer vine exists, Morrigan must
    // not leave all of the outer edge paths to Aquila.  The hard corner rule
    // won the root fight, but the previous pass was losing by half a point
    // when Morrigan started because Aquila harvested the last outer mid-edge
    // sockets.  This is a *late* denial rule only; early mids are still bait.
    if (boardLocked && tetCount >= 3 && id > 1 && tokenCount >= activeCount * 5) {
      if (isCenterSocket(move)) v += 1.0;
      if (isCornerSocket(move)) v += 1.1;
      if (isEdgeSocket(move)) {
        v += 3.15;
        if (scoreOf(move) >= 5.0) v += 1.45;
        if (scoreOf(move) >= 7.5) v += 1.35;
        if (id >= tetCount) v += 0.9;
      }
    }

    // v6: the v5 record still split 10-10 because Morrigan won when
    // Aquila started, but lost by a half point when Morrigan started.
    // In those narrow losses Aquila harvested the final outer-vine edge
    // sockets after Morrigan had already won the root-corner fight.
    // So, in the *late* locked phase only, outer edge denial becomes more
    // important than another quiet root mark.
    if (boardLocked && tetCount >= 3 && tokenCount >= activeCount * 6) {
      if (isOuterVineSocket(move, ctx)) {
        v += 2.2;
        if (isEdgeSocket(move)) v += 3.9;
        if (isOuterDenialEdge(move)) v += 2.6;
        if (scoreOf(move) >= 2.5) v += 0.9;
      }
      if (id === 1 && isEdgeSocket(move) && tokenCount >= activeCount * 7) {
        v -= 1.8;
      }
    }

    return v;
  }

  function bestToken(ctx, limit){
    const list = Array.isArray(ctx && ctx.tokenMoves) ? ctx.tokenMoves : [];
    let bestAny = null;
    let bestAnyScore = -999;
    let bestPrimary = null;
    let bestPrimaryScore = -999;

    // Do not look only at the first few score-sorted moves; low-immediate
    // corner moves may be hidden below high-immediate edge-mid baits.
    list.forEach(move => {
      const v = tokenValue(move, ctx);
      if (v > bestAnyScore) {
        bestAnyScore = v;
        bestAny = move;
      }
      if (isPrimarySocket(move) && v > bestPrimaryScore) {
        bestPrimaryScore = v;
        bestPrimary = move;
      }
    });

    // If a center/corner is even remotely competitive, take it.  The only
    // time she should choose a direct edge-mid is when every useful corner
    // is gone or the edge-mid is a dramatic immediate score swing.
    if (bestPrimary && (!bestAny || !isEdgeSocket(bestAny) || bestPrimaryScore >= bestAnyScore - 3.4)) {
      return bestPrimary;
    }
    if (bestAny && isEdgeSocket(bestAny) && scoreOf(bestAny) < 10.5 && bestPrimary) {
      return bestPrimary;
    }
    return bestAny;
  }

  function bestTokenAtLeast(ctx, threshold, limit){
    const move = bestToken(ctx, limit || 10);
    return move && scoreOf(move) >= Number(threshold || 0) ? move : null;
  }

  function bestFirstMarkToken(ctx){
    const list = Array.isArray(ctx && ctx.tokenMoves) ? ctx.tokenMoves : [];
    let best = null;
    let bestScore = -999;

    list.forEach(move => {
      const idx = socketIndex(move);
      const id = tetId(move);
      let v = scoreOf(move) * 1.05;

      // v7: when Morrigan herself opens the center tet in a two-player
      // duel, do not immediately hand the first socket tempo to Aquila by
      // expanding as the second action.  Plant a strong root mark first;
      // she can still spread the shadow line on a later turn.
      if (id === 1) v += 2.0;
      if (idx === 10) v += 8.2;
      if (idx >= 0 && idx <= 3) v += 5.8;
      if (idx >= 4 && idx <= 9) v -= 3.8;

      if (v > bestScore) {
        bestScore = v;
        best = move;
      }
    });

    return best;
  }

  function sabotageValue(move, ctx){
    if (!move) return -999;

    const idx = socketIndex(move);
    const id = tetId(move);
    const tetCount = Number(ctx && ctx.tetCount || 0);
    const tokenCount = Number(ctx && ctx.tokenCount || 0);
    const activeCount = Math.max(2, Number(ctx && ctx.activeCount || 2));
    const firstAction = Number(ctx && ctx.turnActionCount || 0) === 0;
    let v = tokenValue(move, ctx);

    // When the vine has just opened to a second or third tet, Aquila tends
    // to turn that fresh outer tet into a huge auto-token / sub / full-tet
    // harvest.  Morrigan's shadow counter is to step onto that future engine
    // before the harvest starts, even if the immediate score is quiet.
    if (tetCount >= 3 && id > 1 && tokenCount <= activeCount * 5) {
      v += 2.2;
      if (isCenterSocket(move)) v += 7.8;
      if (isCornerSocket(move)) v += 9.0;
      if (isEdgeSocket(move)) v -= 5.2;
      if (!!(ctx && ctx.boardLocked) && tokenCount >= activeCount * 5 && id > 1 && isEdgeSocket(move)) v += 3.2;
      if (id >= tetCount) v += 1.2;
      if (tokenCount <= activeCount * 2) v += 1.2;
      if (!firstAction) v += 0.8;
    }

    // In the common one-root/one-branch opening, Aquila often wins by
    // completing a root-sub pattern.  These sockets are deliberately valued
    // as blocking shadows, not merely as immediate path scores.
    if (tetCount <= 2 && isRootSocket(move) && tokenCount >= 3 && tokenCount <= activeCount * 5) {
      if (idx >= 0 && idx <= 3) v += 8.6;     // prefer corners over edge mids
      if (idx === 10) v += 2.6;                // center can still be disruptive
      if (idx >= 4 && idx <= 9) v -= 5.0;      // do not spend sabotage on mids first
      if (scoreOf(move) >= 10.5) v += 0.9;      // unless the mid is a true payoff
      if (!firstAction) v += 0.7;
    }

    return v;
  }

  function bestSabotageToken(ctx){
    const list = Array.isArray(ctx && ctx.tokenMoves) ? ctx.tokenMoves : [];
    let bestCorner = null;
    let bestCornerScore = -999;
    let bestAny = null;
    let bestAnyScore = -999;

    list.forEach(move => {
      const v = sabotageValue(move, ctx);
      if (v > bestAnyScore) {
        bestAnyScore = v;
        bestAny = move;
      }
      if (isCenterSocket(move) || isCornerSocket(move)) {
        if (v > bestCornerScore) {
          bestCornerScore = v;
          bestCorner = move;
        }
      }
    });

    // Hard corner-first rule:
    // Morrigan should not spend a shadow-block on an edge midpoint while a
    // reasonable corner/center block is available.  Corners can trigger or
    // deny the automatic mid-edge captures; taking the midpoint directly is
    // usually a weaker shadow.
    if (bestCorner && bestCornerScore >= 4.2) return bestCorner;

    // Only fall back to an edge midpoint if it is clearly a real tactical
    // payoff and not merely a quiet block.
    return bestAny && bestAnyScore >= 12.0 && scoreOf(bestAny) >= 10.5 ? bestAny : null;
  }

  function bestLateOuterDenialToken(ctx){
    if (!ctx || !ctx.boardLocked) return null;
    const activeCount = Math.max(2, Number(ctx.activeCount || 2));
    const tetCount = Number(ctx.tetCount || 0);
    const tokenCount = Number(ctx.tokenCount || 0);
    if (tetCount < 3 || tokenCount < activeCount * 6) return null;

    const list = Array.isArray(ctx.tokenMoves) ? ctx.tokenMoves : [];
    let best = null;
    let bestScore = -999;

    list.forEach(move => {
      if (!isOuterVineSocket(move, ctx)) return;
      const idx = socketIndex(move);
      let v = scoreOf(move) * 1.15;

      // In the narrow 20½-20 losses, these outer edge sockets were where
      // Aquila found the half-point swing.  Morrigan should deny them before
      // taking a decorative root edge.
      if (isEdgeSocket(move)) v += 6.2;
      if (isOuterDenialEdge(move)) v += 3.0;
      if (idx === 10) v += 3.8;
      if (isCornerSocket(move)) v += 2.6;
      if (tetId(move) >= tetCount) v += 1.3;
      if (tokenCount >= activeCount * 8) v += 1.2;

      if (v > bestScore) {
        bestScore = v;
        best = move;
      }
    });

    return best && bestScore >= 7.0 ? best : null;
  }

  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 || 6)).forEach(move => {
      let v = scoreOf(move);
      if (ctx && Number(ctx.turnActionCount || 0) === 0) v += 0.65;
      const tetCount = Number(ctx && ctx.tetCount || 0);
      if (tetCount <= 2) v += 1.0;
      if (tetCount >= Math.max(4, Number(ctx && ctx.activeCount || 2))) v -= 0.7;
      if (v > bestScore) {
        bestScore = v;
        best = move;
      }
    });
    return best;
  }

  function openingLimit(ctx){
    const active = Number(ctx && ctx.activeCount || 2);
    return Math.max(3, Math.min(5, active));
  }

  registry.morrigan_black = {
    key:'morrigan_black',
    id:'morrigan_black',
    name:'Morrigan Black',
    gender:'female',
    level:7,
    style:'opportunist shadow tactician / corner-pair, first-mark opening, and late outer-vine denial specialist',
    temperament:'predatory, patient, and fond of stolen openings',

    chooseMove(ctx){
      if (!ctx || ctx.matchEnded || ctx.tournamentEnded) return null;
      if (ctx.tetCount <= 0) return {type:'start_tet', score:1000, reason:'open the first shadow'};

      const boardLocked = !!ctx.boardLocked;
      const firstAction = Number(ctx.turnActionCount || 0) === 0;
      const secondAction = !firstAction;
      const tetCount = Number(ctx.tetCount || 0);
      const tokenCount = Number(ctx.tokenCount || 0);
      const activeCount = Math.max(2, Number(ctx.activeCount || 2));
      const highToken = bestTokenAtLeast(ctx, 7.7, 99);
      const goodToken = bestTokenAtLeast(ctx, 5.2, 99);
      const quietToken = bestToken(ctx, 99);
      const tet = !boardLocked ? bestTet(ctx, 8) : null;
      const sabotageToken = bestSabotageToken(ctx);
      const lateOuterToken = bestLateOuterDenialToken(ctx);

      // If she starts a two-player duel, her second action should no longer
      // hand the first socket tempo to Aquila by expanding immediately.  v6
      // still split 10-10 because Morrigan won when Aquila started, then lost
      // by half a point whenever Morrigan opened with [] : [face].  In that
      // exact no-token opening state, plant a strong root mark first.
      if (secondAction && tetCount === 1 && tet) {
        const firstMarkToken = (activeCount <= 2 && tokenCount === 0) ? bestFirstMarkToken(ctx) : null;
        if (firstMarkToken) {
          return cloneMove(firstMarkToken, 'plant the first shadow mark ' + socketReason(firstMarkToken), 0.95);
        }
        if (!highToken || scoreOf(highToken) < 8.6) {
          return cloneMove(tet, 'open a shadow lane from the first mark', 0.8);
        }
      }

      // v6 narrow-match nudge: after Morrigan has started and the board is
      // locked with an outer vine, deny the outer edge harvest before Aquila
      // collects the repeated half-point swing.
      if (lateOuterToken && (!highToken || tokenValue(lateOuterToken, ctx) >= tokenValue(highToken, ctx) - 1.6)) {
        return cloneMove(lateOuterToken, 'seal the outer shadow ' + socketReason(lateOuterToken), 1.1);
      }

      // New in v4/v5/v6: if the opponent is building a dangerous outer-tet engine
      // or a root-sub trap, step into the shadow-block before chasing a
      // merely obvious path score.
      if (sabotageToken && (!highToken || sabotageValue(sabotageToken, ctx) >= tokenValue(highToken, ctx) - 0.4)) {
        return cloneMove(sabotageToken, 'shadow-block ' + socketReason(sabotageToken), 1.2);
      }

      // The heart of Morrigan: take strong exposed token opportunities early.
      if (highToken) return cloneMove(highToken, 'ambush ' + socketReason(highToken), 0.9);

      // Early first actions: expand only if the available token is not already
      // good enough to steal value. This keeps her from wandering, but still
      // lets her create dark corridors for later turns.
      if (firstAction && tet && tetCount < openingLimit(ctx) && (!goodToken || scoreOf(goodToken) < 6.4)) {
        return cloneMove(tet, 'spread the shadow line', 0.4);
      }

      // Second actions should almost always cash in a tactical mark.
      if (secondAction && goodToken) return cloneMove(goodToken, 'take the exposed opening', 0.6);
      if (secondAction && quietToken) return cloneMove(quietToken, 'claim the quiet consequence', 0.25);

      // First action after the opening: prefer a good tactical token over a
      // routine expansion. This is the main performance bump.
      if (goodToken) return cloneMove(goodToken, 'steal ' + socketReason(goodToken), 0.6);

      if (!boardLocked && tet) return cloneMove(tet, 'expand where the board loosens', 0.2);
      if (quietToken) return cloneMove(quietToken, 'keep the useful shadow', 0.1);
      return pass('no shadow worth taking');
    }
  };
})(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.