<m-prompt
  id="tb-ic4-prompt"
  message="Play ice Connect Four. Type a column A-G, like DROP D. RESET clears the board."
  placeholder="DROP D"
  prefill="DROP D"
  debug="false"
>
  <m-model
    id="tb-ic4-board-model"
    src="/assets/tb_ice_connect_four_board_downloads.glb"
    x="0"
    y="0.08"
    z="2.95"
    rx="0"
    sx="8"
    sy="8"
    sz="8"
  ></m-model>
<m-cylinder id="tb-ic4-disc-0-0" x="-2.46" y="1.2" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-0-1" x="-1.64" y="1.2" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-0-2" x="-0.82" y="1.2" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-0-3" x="0" y="1.2" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-0-4" x="0.82" y="1.2" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-0-5" x="1.64" y="1.2" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-0-6" x="2.46" y="1.2" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-1-0" x="-2.46" y="1.92" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-1-1" x="-1.64" y="1.92" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-1-2" x="-0.82" y="1.92" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-1-3" x="0" y="1.92" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-1-4" x="0.82" y="1.92" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-1-5" x="1.64" y="1.92" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-1-6" x="2.46" y="1.92" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-2-0" x="-2.46" y="2.64" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-2-1" x="-1.64" y="2.64" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-2-2" x="-0.82" y="2.64" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-2-3" x="0" y="2.64" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-2-4" x="0.82" y="2.64" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-2-5" x="1.64" y="2.64" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-2-6" x="2.46" y="2.64" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-3-0" x="-2.46" y="3.36" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-3-1" x="-1.64" y="3.36" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-3-2" x="-0.82" y="3.36" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-3-3" x="0" y="3.36" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-3-4" x="0.82" y="3.36" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-3-5" x="1.64" y="3.36" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-3-6" x="2.46" y="3.36" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-4-0" x="-2.46" y="4.08" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-4-1" x="-1.64" y="4.08" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-4-2" x="-0.82" y="4.08" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-4-3" x="0" y="4.08" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-4-4" x="0.82" y="4.08" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-4-5" x="1.64" y="4.08" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-4-6" x="2.46" y="4.08" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-5-0" x="-2.46" y="4.8" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-5-1" x="-1.64" y="4.8" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-5-2" x="-0.82" y="4.8" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-5-3" x="0" y="4.8" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-5-4" x="0.82" y="4.8" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-5-5" x="1.64" y="4.8" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
<m-cylinder id="tb-ic4-disc-5-6" x="2.46" y="4.8" z="3.72" rx="90" radius="0.31" height="0.2" color="#f3fbff" opacity="0.96" emissive="0" visible="false"></m-cylinder>
  <m-label id="tb-ic4-notice" x="0" y="5.35" z="3.98" width="7.2" height="0.48" font-size="20" alignment="center" color="#0b2334" font-color="#f5fbff" emissive="0" visible="false" content=""></m-label>
</m-prompt>

<script>

(function () {
  const ROWS = 6;
  const COLS = 7;
  const RED = "RED";
  const YELLOW = "YELLOW";
  const PIECE_COLORS = {
RED: "#d9362a",
YELLOW: "#ffd83d"
  };
  const PIECE_IDLE_COLOR = "#f3fbff";
  const PIECE_IDLE_OPACITY = "0.96";
  const PIECE_ACTIVE_OPACITY = "0.98";
  const board = Array.from({ length: ROWS }, function () {
return Array(COLS).fill("");
  });
  const prompt = document.getElementById("tb-ic4-prompt");
  const notice = document.getElementById("tb-ic4-notice");
  let noticeTimer = null;
  let currentPlayer = RED;
  let finished = false;
  let moves = 0;
  let lastPromptKey = "";
  let lastPromptAt = 0;

  function debug(message) {
try {
  if (typeof console !== "undefined" && console && typeof console.log === "function") {
    console.log(String(message));
  }
} catch (error) {
  // Debug output must never break gameplay.
}
  }

  function setText(el, text) {
if (el) el.setAttribute("content", String(text));
  }

  function clearNoticeTimer() {
if (!noticeTimer) return;
const clearTimer =
  typeof clearTimeout === "function"
    ? clearTimeout
    : typeof window !== "undefined" && typeof window.clearTimeout === "function"
      ? window.clearTimeout.bind(window)
      : null;
if (clearTimer) clearTimer(noticeTimer);
noticeTimer = null;
  }

  function showNotice(message, kind) {
if (!notice) return;
clearNoticeTimer();
setText(notice, message);
notice.setAttribute("visible", "true");
notice.setAttribute("emissive", "0");
if (kind === "error") {
  notice.setAttribute("color", "#220711");
  notice.setAttribute("font-color", "#ffb7ce");
} else if (kind === "success") {
  notice.setAttribute("color", "#062317");
  notice.setAttribute("font-color", "#b8ffe5");
} else {
  notice.setAttribute("color", "#0b2334");
  notice.setAttribute("font-color", "#f5fbff");
}
const setTimer =
  typeof setTimeout === "function"
    ? setTimeout
    : typeof window !== "undefined" && typeof window.setTimeout === "function"
      ? window.setTimeout.bind(window)
      : null;
if (setTimer) {
  noticeTimer = setTimer(function () {
    notice.setAttribute("visible", "false");
  }, 3200);
}
  }

  function clean(value) {
return String(value || "").toUpperCase().replace(/[^A-Z0-9 ]/g, " ").replace(/\s+/g, " ").trim();
  }

  function firstString() {
for (let i = 0; i < arguments.length; i += 1) {
  const value = arguments[i];
  if (typeof value === "string" && value.trim()) return value;
  if (typeof value === "number") return String(value);
}
return "";
  }

  function readPromptValue(event) {
const detail = event && event.detail ? event.detail : {};
const value = firstString(
  detail.value,
  detail.text,
  detail.prompt,
  detail.input,
  detail.message,
  detail.result,
  event && event.value,
  event && event.text,
  typeof event === "string" ? event : ""
);
if (value) return value;
if (prompt && typeof prompt.getAttribute === "function") {
  return firstString(prompt.getAttribute("value"), prompt.getAttribute("text"));
}
return "";
  }

  function isDuplicatePrompt(rawValue) {
const key = clean(rawValue);
const now = typeof Date !== "undefined" && Date && typeof Date.now === "function" ? Date.now() : 0;
if (key && key === lastPromptKey && now && lastPromptAt && now - lastPromptAt < 750) {
  return true;
}
lastPromptKey = key;
lastPromptAt = now;
return false;
  }

  function parseColumn(token) {
if (/^[A-G]$/.test(token)) {
  return { col: token.charCodeAt(0) - 65, label: token };
}
if (/^[1-7]$/.test(token)) {
  const col = Number(token) - 1;
  return { col, label: String.fromCharCode(65 + col) };
}
return null;
  }

  function parseMove(rawValue) {
const value = clean(rawValue);
if (!value) return { error: "Type a column: A through G." };
if (value === "RESET" || value === "CLEAR" || value === "NEW") return { reset: true };
for (const token of value.split(" ")) {
  if (token === "DROP" || token === "COLUMN" || token === "COL" || token === "PLAY" || token === "MOVE") continue;
  const column = parseColumn(token);
  if (column) return column;
}
return { error: "Use a column A-G, like DROP D." };
  }

  function pieceName(piece) {
return piece === RED ? "Red" : "Yellow";
  }

  function pieceElement(row, col) {
return document.getElementById("tb-ic4-disc-" + row + "-" + col);
  }

  function rowLabel(row) {
return String(row + 1);
  }

  function slotName(row, col) {
return String.fromCharCode(65 + col) + rowLabel(row);
  }

  function renderSlot(row, col) {
const el = pieceElement(row, col);
if (!el) return;
const piece = board[row][col];
if (!piece) {
  el.setAttribute("visible", "false");
  el.setAttribute("color", PIECE_IDLE_COLOR);
  el.setAttribute("opacity", PIECE_IDLE_OPACITY);
  el.setAttribute("emissive", "0");
  return;
}
el.setAttribute("visible", "true");
el.setAttribute("color", PIECE_COLORS[piece] || PIECE_IDLE_COLOR);
el.setAttribute("opacity", PIECE_ACTIVE_OPACITY);
el.setAttribute("emissive", "0");
  }

  function resetBoard() {
for (let row = 0; row < ROWS; row += 1) {
  for (let col = 0; col < COLS; col += 1) {
    board[row][col] = "";
    renderSlot(row, col);
  }
}
currentPlayer = RED;
finished = false;
moves = 0;
showNotice("Reset. Red starts. Try DROP D.", "info");
debug("ice-connect-four reset");
  }

  function findDropRow(col) {
for (let row = 0; row < ROWS; row += 1) {
  if (!board[row][col]) return row;
}
return -1;
  }

  function inBounds(row, col) {
return row >= 0 && row < ROWS && col >= 0 && col < COLS;
  }

  function countDirection(row, col, dr, dc, piece) {
let count = 0;
let r = row + dr;
let c = col + dc;
while (inBounds(r, c) && board[r][c] === piece) {
  count += 1;
  r += dr;
  c += dc;
}
return count;
  }

  function hasWin(row, col, piece) {
const dirs = [
  [1, 0],
  [0, 1],
  [1, 1],
  [1, -1]
];
return dirs.some(function (dir) {
  return 1 + countDirection(row, col, dir[0], dir[1], piece) + countDirection(row, col, -dir[0], -dir[1], piece) >= 4;
});
  }

  function handlePrompt(event) {
try {
  const rawValue = readPromptValue(event);
  if (isDuplicatePrompt(rawValue)) return;
  const parsed = parseMove(rawValue);
  debug("ice-connect-four prompt: " + String(rawValue || ""));

  if (parsed.reset) {
    resetBoard();
    return;
  }
  if (parsed.error) {
    showNotice(parsed.error, "error");
    debug("ice-connect-four error: " + parsed.error);
    return;
  }
  if (finished) {
    showNotice("Game finished. Type RESET.", "info");
    return;
  }

  const row = findDropRow(parsed.col);
  if (row < 0) {
    showNotice("Column " + parsed.label + " is full.", "error");
    debug("ice-connect-four error: full " + parsed.label);
    return;
  }

  board[row][parsed.col] = currentPlayer;
  renderSlot(row, parsed.col);
  moves += 1;
  const placed = slotName(row, parsed.col);

  if (hasWin(row, parsed.col, currentPlayer)) {
    finished = true;
    showNotice(pieceName(currentPlayer) + " wins at " + placed + ". Type RESET.", "success");
    debug("ice-connect-four win: " + pieceName(currentPlayer) + " " + placed);
    return;
  }

  if (moves >= ROWS * COLS) {
    finished = true;
    showNotice("Draw. Type RESET.", "success");
    debug("ice-connect-four draw");
    return;
  }

  const placedPlayer = currentPlayer;
  currentPlayer = currentPlayer === RED ? YELLOW : RED;
  showNotice(pieceName(placedPlayer) + " drops " + placed + ". " + pieceName(currentPlayer) + " turn.", "info");
  debug("ice-connect-four move: " + placed + " " + pieceName(board[row][parsed.col]));
} catch (error) {
  const message = error && error.message ? error.message : "Prompt handler failed.";
  showNotice("Command error: " + message, "error");
  debug("ice-connect-four exception: " + message);
}
  }

  if (prompt) {
prompt.addEventListener("prompt", handlePrompt);
prompt.addEventListener("submit", handlePrompt);
  }
  showNotice("Red starts. Type DROP D.", "info");
})();

</script>
