{
  "information": {
    "Title": "ejss_model_knight_moves Knight Moves Hot Seat 2 Player Game for Strategy Thinking, Reach the star to win!",
    "Author": [
      "Shaun Quek",
      "Loo Kang Wee",
      "based on ideas by Theresa Heng",
      "Francisco Esquembre",
      "F\u00e9lix Jes\u00fas Garcia Clemente,"
    ],
    "AuthorLogo": [
      "",
      "",
      "",
      "",
      ""
    ],
    "Password": "",
    "Keywords": "knight moves, chess strategy, spatial reasoning, coordinate geometry, logical thinking, mathematical games, path finding, game strategy, problem solving, enrichment mathematics",
    "Abstract": "This interactive two-player knight-move strategy game helps learners develop spatial reasoning, coordinate thinking, logical planning, and strategic decision-making by comparing legal moves, winning paths, and blocking strategies on an irregular board.",
    "Copyright": "",
    "Level": "Upper Primary, Lower Secondary, Enrichment",
    "Language": "",
    "Logo": [
      "./knight_moves/Screenshot 2021-05-03 at 4.22.37 PM (2).png"
    ],
    "RunAlways": "true",
    "ModelTab": "",
    "ModelTabTitle": "",
    "ModelName": "",
    "FixedNavigationBar": "false",
    "CSSFile": "",
    "DetectedFiles": [
      "./knight_moves/Screenshot 2021-05-03 at 4.22.37 PM (2).png"
    ],
    "AuxiliaryFiles": [
      "./knight_moves/"
    ],
    "HTMLHead": "<script async=\"true\" src=\"https://www.googletagmanager.com/gtag/js?id=UA-3326007-19\"></script>\n<script>\n  window.dataLayer = window.dataLayer || [];\n  function gtag(){dataLayer.push(arguments);}\n  gtag('js', new Date());\n  gtag('config', 'UA-3326007-19');\n</script>\n<script data-ad-client=\"ca-pub-0121577198857509\" async=\"true\" src=\"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></script>",
    "SaveInXMLFormat": "false",
    "IncludeSource": "true",
    "IncludeLibrary": "true",
    "UglifyJS": "false",
    "PreviewFullModel": "false",
    "UseInterpreter": "true",
    "UseDeltaForODE": "false"
  },
  "description": {
    "pages": []
  },
  "model": {
    "variables": {
      "pages": [
        {
          "Name": "Var Table",
          "Active": "true",
          "Internal": "false",
          "Type": "VARIABLE_EDITOR",
          "PageComment": "",
          "Variables": [
            {
              "Name": "board",
              "Value": "",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "numOfRows",
              "Value": "10",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "numOfCols",
              "Value": "11",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "numOfSquares",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "squareX",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "squareY",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "squareLength",
              "Value": "1",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "squareColor",
              "Value": "",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "squareHighlighted",
              "Value": "",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "posToIdx",
              "Value": "",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "idxToPos",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "interact",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "font",
              "Value": "\"normal normal 2vw\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "gameOver",
              "Value": "false",
              "Type": "boolean",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "startX",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "startY",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "basePosToIdx",
              "Value": "",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "starDistance",
              "Value": "",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "currentX",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "currentY",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            }
          ]
        },
        {
          "Name": "lookang",
          "Active": "true",
          "Internal": "false",
          "Type": "VARIABLE_EDITOR",
          "PageComment": "",
          "Variables": [
            {
              "Name": "",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "textXunicodek",
              "Value": "2",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "textYunicodek",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            }
          ]
        },
        {
          "Name": "text",
          "Active": "true",
          "Internal": "false",
          "Type": "VARIABLE_EDITOR",
          "PageComment": "",
          "Variables": [
            {
              "Name": "textStr",
              "Value": "",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "textX",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "textY",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "textXunicode",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "textYunicode",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            }
          ]
        },
        {
          "Name": "colors",
          "Active": "true",
          "Internal": "false",
          "Type": "VARIABLE_EDITOR",
          "PageComment": "",
          "Variables": [
            {
              "Name": "unfilledColor",
              "Value": "\"lightgrey\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "highlightColor",
              "Value": "\"#aed581\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "\"#aed581\"",
              "Domain": "public"
            },
            {
              "Name": "p1Color",
              "Value": "\"#42a5f5\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "p2Color",
              "Value": "\"#ef5350\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "moveCounter",
              "Value": "1",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "",
              "Value": "",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            }
          ]
        },
        {
          "Name": "turn",
          "Active": "true",
          "Internal": "false",
          "Type": "VARIABLE_EDITOR",
          "PageComment": "",
          "Variables": [
            {
              "Name": "turn",
              "Value": "0",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "turnColor",
              "Value": "p1Color",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "turnText",
              "Value": "\"Player 1 to move (Human).\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            }
          ]
        },
        {
          "Name": "computer play",
          "Active": "true",
          "Internal": "false",
          "Type": "VARIABLE_EDITOR",
          "PageComment": "",
          "Variables": [
            {
              "Name": "player1Mode",
              "Value": "\"Human\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "player2Mode",
              "Value": "\"Human\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "showLegalMoves",
              "Value": "true",
              "Type": "boolean",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "strategyText",
              "Value": "\"Choose a green move, or press Auto move to see a strategic demonstration.\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "computerThinking",
              "Value": "false",
              "Type": "boolean",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "computerDelayMs",
              "Value": "2000",
              "Type": "double",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "previewColor",
              "Value": "\"#ffca28\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "controlFont",
              "Value": "\"normal normal 1.25vw\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            },
            {
              "Name": "coachFont",
              "Value": "\"normal normal 1.05vw\"",
              "Type": "String",
              "Dimension": "",
              "Comment": "null",
              "Domain": "public"
            }
          ]
        }
      ]
    },
    "initialization": {
      "pages": [
        {
          "Name": "Init Page",
          "Active": "true",
          "Internal": "false",
          "Type": "CODE_EDITOR",
          "Comment": "",
          "Code": "// INITIALISING BOARD (* means star, S means start, O means a grid square, _ means blank space)\n// bottom to top row\n\nboard =\n[\n  \"*OS_OO__*O_\",\n  \"OOO_OOO__O_\",\n  \"_OO_OOOO_O_\",\n  \"OOO_OOOOOO_\",\n  \"OOOOOOO____\",\n  \"OOOOOOO_O__\",\n  \"_OOOOOOOOOO\",\n  \"OOOOOOOOOOO\",\n  \"_OOOOO_O_O_\",\n  \"__O*_O_____\"\n];\n\nnumOfRows = 10;\nnumOfCols = 11;\n\n// posToIdx is a 2D array which takes the x and y positions of a grid square, and gives the corresponding idx of that square\n// idxToPos is a 1D array which takes the idx of a grid square, and gives the corresponding x and y positions of that square\n// idx == index\n\nsquareX = [], squareY = [], squareColor = [], squareHighlighted = [], idxToPos = [], posToIdx = [], basePosToIdx = [];\ntextX = [], textY = [], textStr = [];\nstarDistance = [];\nnumOfSquares = 0;\n\n// INITIALISING GRID SQUARES\nfor (var i = 0; i < numOfRows; i++) {\n  posToIdx.push([]);\n  basePosToIdx.push([]);\n  for (var j = 0; j < numOfCols; j++) {\n    if (board[i][j] == \"_\") {\n      posToIdx[i].push(-1);\n      basePosToIdx[i].push(-1);\n      continue;\n    }\n    squareX.push(j * squareLength);\n    squareY.push(i * squareLength);\n    squareHighlighted.push(0);\n    squareColor.push(unfilledColor);\n\n    posToIdx[i].push(numOfSquares);\n    basePosToIdx[i].push(numOfSquares);\n    idxToPos.push([j, i]);\n\n    numOfSquares++;\n\n    if (board[i][j] == \"S\" || board[i][j] == \"*\") {\n      textStr.push(board[i][j]);\n      textX.push(j * squareLength);\n      textY.push(i * squareLength);\n    }\n    if (board[i][j] == \"S\") {\n      startX = j;\n      startY = i;\n    }\n  }\n}\n\ncurrentX = startX;\ncurrentY = startY;\ntextXunicodek = startX * squareLength;\ntextYunicodek = startY * squareLength;\nshowLegalMoves = true;\nstrategyText = \"Choose a green move, or press Auto move to see a strategic demonstration.\";\n\ncomputeStarDistances();\nfindPossibleMoves(startX, startY);"
        },
        {
          "Name": "manuallylaystarunicode",
          "Active": "true",
          "Internal": "false",
          "Type": "CODE_EDITOR",
          "Comment": "",
          "Code": "textXunicode = [], textYunicode = []\n\nfor(var i = 0; i < numOfRows; i++){\n  for(var j = 0; j < numOfCols; j++){\n  if (board[i][j]==\"*\"){\n    //board[i][j] = \"a\"\n    textXunicode.push(j * squareLength);\n    textYunicode.push(i * squareLength);\n    //alert(\"*\")\n    }\n    \n    \n    \n    \n}\n}"
        }
      ]
    },
    "evolution": {
      "information": {
        "FPS": "20",
        "SPD": "1",
        "RealTimeVariable": "",
        "Autoplay": "true"
      },
      "pages": []
    },
    "fixed_relations": {
      "pages": []
    },
    "custom": {
      "pages": [
        {
          "Name": "findPossibleMoves",
          "Active": "true",
          "Internal": "false",
          "Type": "LIBRARY_EDITOR",
          "Comment": "",
          "Code": "var computerTimer = null;\nvar previewSquareIdx = -1;\nconst knightDx = [-1, 1, 2, 2, -1, 1, -2, -2];\nconst knightDy = [2, 2, 1, -1, -2, -2, 1, -1];\n\nfunction refreshComputerView() {\n  if (typeof _view !== 'undefined' && _view) {\n    if (typeof _view._update === 'function') _view._update();\n    if (typeof _view._render === 'function') _view._render();\n  }\n}\n\nfunction computeStarDistances() {\n  starDistance = [];\n  for (var i = 0; i < numOfSquares; i++) starDistance.push(999);\n  var queue = [];\n\n  for (var idx = 0; idx < numOfSquares; idx++) {\n    var pos = idxToPos[idx];\n    if (board[pos[1]][pos[0]] == '*') {\n      starDistance[idx] = 0;\n      queue.push(idx);\n    }\n  }\n\n  while (queue.length > 0) {\n    var curIdx = queue.shift();\n    var curPos = idxToPos[curIdx];\n    for (var i = 0; i < 8; i++) {\n      var nextX = curPos[0] + knightDx[i];\n      var nextY = curPos[1] + knightDy[i];\n      if (nextX < 0 || nextX >= numOfCols || nextY < 0 || nextY >= numOfRows) continue;\n      var nextIdx = basePosToIdx[nextY][nextX];\n      if (nextIdx == -1) continue;\n      if (starDistance[nextIdx] > starDistance[curIdx] + 1) {\n        starDistance[nextIdx] = starDistance[curIdx] + 1;\n        queue.push(nextIdx);\n      }\n    }\n  }\n}\n\nfunction getVisitedMask() {\n  var mask = 0n;\n  for (var y = 0; y < numOfRows; y++) {\n    for (var x = 0; x < numOfCols; x++) {\n      var baseIdx = basePosToIdx[y][x];\n      if (baseIdx == -1) continue;\n      if (posToIdx[y][x] == -1) {\n        mask |= (1n << BigInt(baseIdx));\n      }\n    }\n  }\n  return mask;\n}\n\nfunction countVisitedSquares(mask) {\n  var total = 0;\n  while (mask > 0n) {\n    total += Number(mask & 1n);\n    mask >>= 1n;\n  }\n  return total;\n}\n\nfunction getLegalMovesFromState(x, y, visitedMask) {\n  var legalMoves = [];\n  for (var i = 0; i < 8; i++) {\n    var nextX = x + knightDx[i];\n    var nextY = y + knightDy[i];\n    if (nextX < 0 || nextX >= numOfCols || nextY < 0 || nextY >= numOfRows) continue;\n    var nextIdx = basePosToIdx[nextY][nextX];\n    if (nextIdx == -1) continue;\n    if ((visitedMask & (1n << BigInt(nextIdx))) !== 0n) continue;\n    legalMoves.push({\n      x: nextX,\n      y: nextY,\n      idx: nextIdx,\n      isStar: board[nextY][nextX] == '*'\n    });\n  }\n  return legalMoves;\n}\n\nfunction describeSquare(x, y) {\n  return String.fromCharCode(65 + x) + (y + 1);\n}\n\nfunction getPlayerMode(playerIdx) {\n  return playerIdx == 0 ? player1Mode : player2Mode;\n}\n\nfunction updateTurnDisplay() {\n  if (gameOver) return;\n  turnColor = turn == 0 ? p1Color : p2Color;\n  turnText = 'Player ' + (turn + 1) + ' to move (' + getPlayerMode(turn) + ').';\n}\n\nfunction toggleHints() {\n  showLegalMoves = !showLegalMoves;\n  applyHintVisibility();\n}\n\nfunction applyHintVisibility() {\n  for (var i = 0; i < numOfSquares; i++) {\n    if (squareHighlighted[i]) squareColor[i] = showLegalMoves ? highlightColor : unfilledColor;\n  }\n  if (previewSquareIdx >= 0 && previewSquareIdx < numOfSquares && squareHighlighted[previewSquareIdx]) {\n    squareColor[previewSquareIdx] = previewColor;\n  }\n  refreshComputerView();\n}\n\nfunction clearComputerPreview() {\n  if (previewSquareIdx >= 0 && previewSquareIdx < numOfSquares) {\n    if (squareHighlighted[previewSquareIdx]) squareColor[previewSquareIdx] = showLegalMoves ? highlightColor : unfilledColor;\n    else if (squareColor[previewSquareIdx] == previewColor) squareColor[previewSquareIdx] = unfilledColor;\n  }\n  previewSquareIdx = -1;\n}\n\nfunction cancelComputerTimer() {\n  if (computerTimer !== null) {\n    clearTimeout(computerTimer);\n    computerTimer = null;\n  }\n  clearComputerPreview();\n  computerThinking = false;\n}\n\nfunction isComputerTurn() {\n  return !gameOver && ((turn == 0 && player1Mode == 'Computer') || (turn == 1 && player2Mode == 'Computer'));\n}\n\nfunction summariseMove(move, opponentMoves, searchDepth, forcedWin) {\n  if (move.isStar) {\n    return 'Computer chooses ' + describeSquare(move.x, move.y) + ' because it wins immediately by landing on a star.';\n  }\n  if (opponentMoves.length === 0) {\n    return 'Computer chooses ' + describeSquare(move.x, move.y) + ' because it leaves the opponent with no legal move.';\n  }\n  var opponentStarThreats = 0;\n  for (var i = 0; i < opponentMoves.length; i++) {\n    if (opponentMoves[i].isStar) opponentStarThreats++;\n  }\n  if (forcedWin) {\n    return 'Computer chooses ' + describeSquare(move.x, move.y) + ' because it is part of a forced winning line after looking ahead.';\n  }\n  if (opponentStarThreats === 0 && opponentMoves.length <= 2) {\n    return 'Computer chooses ' + describeSquare(move.x, move.y) + ' because it limits the opponent to ' + opponentMoves.length + ' safe reply' + (opponentMoves.length == 1 ? '' : 'ies') + '.';\n  }\n  if (opponentStarThreats > 0) {\n    return 'Computer chooses ' + describeSquare(move.x, move.y) + ' because it avoids giving the opponent an easy star and scores best in the look-ahead search.';\n  }\n  return 'Computer chooses ' + describeSquare(move.x, move.y) + ' because it gives the strongest position after searching about ' + searchDepth + ' half-moves ahead.';\n}\n\nfunction orderMoveDescriptors(legalMoves, visitedMask) {\n  var descriptors = [];\n  for (var i = 0; i < legalMoves.length; i++) {\n    var move = legalMoves[i];\n    var nextVisited = visitedMask | (1n << BigInt(move.idx));\n    var opponentMoves = getLegalMovesFromState(move.x, move.y, nextVisited);\n    var opponentStarThreats = 0;\n    for (var j = 0; j < opponentMoves.length; j++) {\n      if (opponentMoves[j].isStar) opponentStarThreats++;\n    }\n    var distancePenalty = starDistance[move.idx] === 999 ? 6 : starDistance[move.idx];\n    var priority = 0;\n    if (move.isStar) priority += 1000000;\n    if (opponentMoves.length === 0) priority += 500000;\n    priority -= opponentStarThreats * 5000;\n    priority -= opponentMoves.length * 80;\n    priority += distancePenalty * 15;\n    descriptors.push({\n      move: move,\n      nextVisited: nextVisited,\n      opponentMoves: opponentMoves,\n      opponentStarThreats: opponentStarThreats,\n      priority: priority\n    });\n  }\n  descriptors.sort(function(a, b) { return b.priority - a.priority; });\n  return descriptors;\n}\n\nfunction evaluateState(x, y, visitedMask) {\n  var legalMoves = getLegalMovesFromState(x, y, visitedMask);\n  if (!legalMoves.length) return -90000;\n\n  var ordered = orderMoveDescriptors(legalMoves, visitedMask);\n  var bestSafety = -99999;\n  var totalMobility = legalMoves.length;\n\n  for (var i = 0; i < ordered.length; i++) {\n    var desc = ordered[i];\n    if (desc.move.isStar) return 85000;\n    var landingDistance = starDistance[desc.move.idx] === 999 ? 6 : starDistance[desc.move.idx];\n    var safety = landingDistance * 18 - desc.opponentMoves.length * 9 - desc.opponentStarThreats * 70;\n    if (desc.opponentMoves.length === 0) safety += 3000;\n    if (safety > bestSafety) bestSafety = safety;\n  }\n  return totalMobility * 30 + bestSafety;\n}\n\nfunction exactWinningMove(x, y, visitedMask) {\n  var cache = {};\n\n  function solve(curX, curY, mask) {\n    var key = curX + ',' + curY + '|' + mask.toString(16);\n    if (cache[key]) return cache[key];\n\n    var legalMoves = getLegalMovesFromState(curX, curY, mask);\n    if (!legalMoves.length) {\n      cache[key] = { win: false, move: null };\n      return cache[key];\n    }\n\n    var ordered = orderMoveDescriptors(legalMoves, mask);\n    for (var i = 0; i < ordered.length; i++) {\n      var desc = ordered[i];\n      if (desc.move.isStar || desc.opponentMoves.length === 0) {\n        cache[key] = { win: true, move: desc.move };\n        return cache[key];\n      }\n      var reply = solve(desc.move.x, desc.move.y, desc.nextVisited);\n      if (!reply.win) {\n        cache[key] = { win: true, move: desc.move };\n        return cache[key];\n      }\n    }\n\n    cache[key] = { win: false, move: ordered[0].move };\n    return cache[key];\n  }\n\n  return solve(x, y, visitedMask);\n}\n\nfunction negamax(x, y, visitedMask, depth, alpha, beta, deadline, cache) {\n  if (Date.now() > deadline) {\n    return { score: evaluateState(x, y, visitedMask), bestMove: null, forcedWin: false };\n  }\n\n  var legalMoves = getLegalMovesFromState(x, y, visitedMask);\n  if (!legalMoves.length) {\n    return { score: -100000 - depth, bestMove: null, forcedWin: false };\n  }\n\n  var ordered = orderMoveDescriptors(legalMoves, visitedMask);\n  if (ordered[0].move.isStar) {\n    return { score: 100000 + depth, bestMove: ordered[0].move, forcedWin: true };\n  }\n  if (depth <= 0) {\n    return { score: evaluateState(x, y, visitedMask), bestMove: ordered[0].move, forcedWin: false };\n  }\n\n  var curIdx = basePosToIdx[y][x];\n  var key = curIdx + '|' + visitedMask.toString(16) + '|' + depth;\n  if (cache[key]) return cache[key];\n\n  var bestScore = -Infinity;\n  var bestMove = ordered[0].move;\n  var bestForcedWin = false;\n\n  for (var i = 0; i < ordered.length; i++) {\n    var desc = ordered[i];\n    var score;\n    var forcedWin = false;\n\n    if (desc.opponentMoves.length === 0) {\n      score = 95000 + depth;\n      forcedWin = true;\n    }\n    else {\n      var child = negamax(desc.move.x, desc.move.y, desc.nextVisited, depth - 1, -beta, -alpha, deadline, cache);\n      score = -child.score;\n      forcedWin = child.score < -90000;\n    }\n\n    if (score > bestScore) {\n      bestScore = score;\n      bestMove = desc.move;\n      bestForcedWin = forcedWin;\n    }\n\n    if (score > alpha) alpha = score;\n    if (alpha >= beta) break;\n    if (Date.now() > deadline) break;\n  }\n\n  cache[key] = { score: bestScore, bestMove: bestMove, forcedWin: bestForcedWin };\n  return cache[key];\n}\n\nfunction chooseComputerMove() {\n  var visitedMask = getVisitedMask();\n  var legalMoves = getLegalMovesFromState(currentX, currentY, visitedMask);\n  if (!legalMoves.length) return null;\n\n  var ordered = orderMoveDescriptors(legalMoves, visitedMask);\n  var top = ordered[0];\n  if (top.move.isStar || top.opponentMoves.length === 0) {\n    return {\n      move: top.move,\n      explanation: summariseMove(top.move, top.opponentMoves, 1, true),\n      forcedWin: true,\n      depth: 1\n    };\n  }\n\n  var remainingOpenSquares = numOfSquares - countVisitedSquares(visitedMask);\n  if (remainingOpenSquares <= 16) {\n    var exact = exactWinningMove(currentX, currentY, visitedMask);\n    if (exact && exact.move) {\n      var exactVisited = visitedMask | (1n << BigInt(exact.move.idx));\n      var exactOpponentMoves = getLegalMovesFromState(exact.move.x, exact.move.y, exactVisited);\n      return {\n        move: exact.move,\n        explanation: summariseMove(exact.move, exactOpponentMoves, remainingOpenSquares, exact.win),\n        forcedWin: exact.win,\n        depth: remainingOpenSquares\n      };\n    }\n  }\n\n  var deadline = Date.now() + 250;\n  var cache = {};\n  var bestMove = top.move;\n  var bestScore = -Infinity;\n  var bestForcedWin = false;\n  var searchedDepth = 1;\n\n  for (var depth = 1; depth <= 7; depth++) {\n    if (Date.now() > deadline) break;\n    var result = negamax(currentX, currentY, visitedMask, depth, -Infinity, Infinity, deadline, cache);\n    if (result.bestMove) {\n      bestMove = result.bestMove;\n      bestScore = result.score;\n      bestForcedWin = result.forcedWin;\n      searchedDepth = depth;\n    }\n    if (Math.abs(bestScore) > 90000) break;\n  }\n\n  var bestVisited = visitedMask | (1n << BigInt(bestMove.idx));\n  var bestOpponentMoves = getLegalMovesFromState(bestMove.x, bestMove.y, bestVisited);\n  return {\n    move: bestMove,\n    explanation: summariseMove(bestMove, bestOpponentMoves, searchedDepth, bestForcedWin),\n    forcedWin: bestForcedWin,\n    depth: searchedDepth\n  };\n}\n\nfunction scheduleComputerMove(forceMove) {\n  cancelComputerTimer();\n  if (gameOver) return;\n  if (!forceMove && !isComputerTurn()) return;\n\n  computerThinking = true;\n  strategyText = forceMove ? 'Auto move is analysing the position...' : 'Computer is analysing the position...';\n  var forced = forceMove;\n  var choice = chooseComputerMove();\n\n  if (!choice || !choice.move) {\n    computerThinking = false;\n    strategyText = 'No legal move is available from this position.';\n    return;\n  }\n\n  strategyText = choice.explanation;\n  previewSquareIdx = choice.move.idx;\n  if (previewSquareIdx >= 0 && squareHighlighted[previewSquareIdx]) squareColor[previewSquareIdx] = previewColor;\n  refreshComputerView();\n\n  computerTimer = setTimeout(function() {\n    computerTimer = null;\n    if (gameOver) {\n      clearComputerPreview();\n      computerThinking = false;\n      return;\n    }\n    if (!forced && !isComputerTurn()) {\n      clearComputerPreview();\n      computerThinking = false;\n      return;\n    }\n\n    clearComputerPreview();\n    computerThinking = false;\n    onClickSquare(choice.move.x, choice.move.y, true);\n    refreshComputerView();\n  }, Math.max(2000, computerDelayMs));\n}\n\nfunction togglePlayerMode(playerNumber) {\n  cancelComputerTimer();\n  if (playerNumber == 1) player1Mode = player1Mode == 'Human' ? 'Computer' : 'Human';\n  else player2Mode = player2Mode == 'Human' ? 'Computer' : 'Human';\n\n  updateTurnDisplay();\n  if (isComputerTurn()) {\n    strategyText = 'Computer turn detected. A move will be played automatically.';\n    scheduleComputerMove(false);\n  }\n  else {\n    strategyText = 'Player roles updated. It is still the current human player\\'s turn until a move is made.';\n  }\n}\n\nfunction triggerAutoMove() {\n  if (gameOver) {\n    strategyText = 'The game is over. Press Reset to start a new round.';\n    return;\n  }\n  scheduleComputerMove(true);\n}\n\nfunction resetGameKeepModes() {\n  var savedP1 = player1Mode;\n  var savedP2 = player2Mode;\n  var savedHints = showLegalMoves;\n  cancelComputerTimer();\n  _reset();\n  player1Mode = savedP1;\n  player2Mode = savedP2;\n  showLegalMoves = savedHints;\n  updateTurnDisplay();\n  strategyText = 'New game ready. Choose a green move, or let the computer demonstrate one.';\n  applyHintVisibility();\n  if (isComputerTurn()) scheduleComputerMove(false);\n}\n\n// given a particular grid coordinate, determine which squares are available from that square. It returns false if there is no possible moves, and true if there is.\nfunction findPossibleMoves(x, y) {\n  var possible = false;\n  for (var i = 0; i < 8; i++) {\n    var curX = x + knightDx[i], curY = y + knightDy[i];\n    if (curX < 0 || curX >= numOfCols || curY < 0 || curY >= numOfRows || posToIdx[curY][curX] == -1) {\n      continue;\n    }\n    possible = true;\n    var curIdx = posToIdx[curY][curX];\n    squareHighlighted[curIdx] = 1;\n    if (showLegalMoves && squareColor[curIdx] == unfilledColor) squareColor[curIdx] = highlightColor;\n  }\n  return possible;\n}"
        },
        {
          "Name": "onClickSquare",
          "Active": "true",
          "Internal": "false",
          "Type": "LIBRARY_EDITOR",
          "Comment": "",
          "Code": "// on clicking of a square\nfunction onClickSquare(x, y, forceMove) {\n  if (forceMove === undefined) forceMove = false;\n  if (gameOver) return;\n  if (!forceMove && (computerThinking || isComputerTurn())) {\n    strategyText = 'Wait for the computer to move, or switch that player back to Human.';\n    return;\n  }\n\n  clearComputerPreview();\n\n  var idx = posToIdx[y][x];\n  if (idx == -1 || !squareHighlighted[idx]) {\n    _tools.showOkDialog(showLegalMoves ? 'You cannot move to this square! Click a legal move using the green squares.' : 'You cannot move to this square! Turn Hint? on if you want to see the legal moves.');\n    for (var i = 0; i < numOfSquares; i++) {\n      if (squareHighlighted[i]) {\n        squareColor[i] = showLegalMoves ? highlightColor : unfilledColor;\n      }\n    }\n    return;\n  }\n\n  posToIdx[y][x] = -1;\n\n  for (var i = 0; i < numOfSquares; i++) {\n    if (squareColor[i] == highlightColor || squareColor[i] == previewColor) {\n      squareColor[i] = unfilledColor;\n    }\n    if (squareHighlighted[i]) squareHighlighted[i] = 0;\n  }\n\n  if (turn == 0) squareColor[idx] = p1Color;\n  else squareColor[idx] = p2Color;\n\n  textStr.push(moveCounter++);\n  textX.push(x * squareLength);\n  textY.push(y * squareLength);\n  textXunicodek = x * squareLength;\n  textYunicodek = y * squareLength;\n  currentX = x;\n  currentY = y;\n\n  if (board[y][x] == '*') {\n    _tools.showOkDialog('Player ' + (turn + 1) + ' wins!');\n    turnText = 'Player ' + (turn + 1) + ' wins!';\n    strategyText = 'Winning move: Player ' + (turn + 1) + ' reached the star at ' + describeSquare(x, y) + '.';\n    gameOver = true;\n  }\n  else if (!findPossibleMoves(x, y)) {\n    _tools.showOkDialog('Player ' + ((turn + 1) % 2 + 1) + ' has no moves, so Player ' + (turn + 1) + ' wins!');\n    turnText = 'Player ' + (turn + 1) + ' wins!';\n    strategyText = 'Winning move: Player ' + (turn + 1) + ' moved to ' + describeSquare(x, y) + ' and left no legal reply.';\n    gameOver = true;\n  }\n  else {\n    turn = (turn + 1) % 2;\n    updateTurnDisplay();\n    if (!forceMove && getPlayerMode((turn + 1) % 2) == 'Human') {\n      strategyText = 'Now predict the best reply before the next move is played.';\n    }\n    if (isComputerTurn()) scheduleComputerMove(false);\n  }\n}"
        }
      ]
    },
    "elements": {
      "list": []
    }
  },
  "view": {
    "Tree": [
      {
        "Name": "topPanel",
        "Type": "Panel",
        "Expanded": "true",
        "Properties": [
          {
            "name": "Display",
            "value": "\"inline-flex\""
          }
        ],
        "Children": [
          {
            "Name": "turn",
            "Type": "Label",
            "Properties": [
              {
                "name": "Background",
                "value": "turnColor"
              },
              {
                "name": "Text",
                "value": "turnText"
              },
              {
                "name": "Width",
                "value": "\"70vw\""
              },
              {
                "name": "Font",
                "value": "font"
              }
            ]
          },
          {
            "Name": "reset",
            "Type": "Button",
            "Properties": [
              {
                "name": "Text",
                "value": "\"\u21bbReset\""
              },
              {
                "name": "OnClick",
                "value": "resetGameKeepModes();"
              },
              {
                "name": "Width",
                "value": "\"20vw\""
              },
              {
                "name": "Font",
                "value": "font"
              }
            ]
          }
        ]
      },
      {
        "Name": "modePanel",
        "Type": "Panel",
        "Expanded": "false",
        "Properties": [
          {
            "name": "Display",
            "value": "\"inline-flex\""
          }
        ],
        "Children": [
          {
            "Name": "hintToggleButton",
            "Type": "Button",
            "Properties": [
              {
                "name": "Text",
                "value": "\"Hint?\""
              },
              {
                "name": "OnClick",
                "value": "toggleHints();"
              },
              {
                "name": "Width",
                "value": "\"12vw\""
              },
              {
                "name": "Font",
                "value": "controlFont"
              }
            ]
          },
          {
            "Name": "player1ModeButton",
            "Type": "Button",
            "Properties": [
              {
                "name": "Text",
                "value": "\"P1: \" + player1Mode"
              },
              {
                "name": "OnClick",
                "value": "togglePlayerMode(1);"
              },
              {
                "name": "Width",
                "value": "\"26vw\""
              },
              {
                "name": "Font",
                "value": "controlFont"
              }
            ]
          },
          {
            "Name": "player2ModeButton",
            "Type": "Button",
            "Properties": [
              {
                "name": "Text",
                "value": "\"P2: \" + player2Mode"
              },
              {
                "name": "OnClick",
                "value": "togglePlayerMode(2);"
              },
              {
                "name": "Width",
                "value": "\"26vw\""
              },
              {
                "name": "Font",
                "value": "controlFont"
              }
            ]
          },
          {
            "Name": "autoMoveButton",
            "Type": "Button",
            "Properties": [
              {
                "name": "Text",
                "value": "\"Auto move\""
              },
              {
                "name": "OnClick",
                "value": "triggerAutoMove();"
              },
              {
                "name": "Width",
                "value": "\"26vw\""
              },
              {
                "name": "Font",
                "value": "controlFont"
              }
            ]
          }
        ]
      },
      {
        "Name": "strategyPanel",
        "Type": "Panel",
        "Expanded": "false",
        "Children": [
          {
            "Name": "strategyLabel",
            "Type": "Label",
            "Properties": [
              {
                "name": "Text",
                "value": "strategyText"
              },
              {
                "name": "Width",
                "value": "\"95vw\""
              },
              {
                "name": "Background",
                "value": "\"#fff8e1\""
              },
              {
                "name": "Font",
                "value": "coachFont"
              }
            ]
          }
        ]
      },
      {
        "Name": "mainPanel",
        "Type": "Panel",
        "Expanded": "true",
        "Properties": [],
        "Children": [
          {
            "Name": "plottingPanel",
            "Type": "PlottingPanel",
            "Expanded": "true",
            "Properties": [
              {
                "name": "Gutters",
                "value": "[0,0,0,0]"
              },
              {
                "name": "YScalePrecision",
                "value": "0"
              },
              {
                "name": "XFixedTick",
                "value": "-0.5"
              },
              {
                "name": "Enabled",
                "value": "true"
              },
              {
                "name": "SquareAspect",
                "value": "true"
              },
              {
                "name": "YFixedTick",
                "value": "-0.5"
              },
              {
                "name": "YAutoTicks",
                "value": "false"
              },
              {
                "name": "XTickStep",
                "value": "11"
              },
              {
                "name": "YTickStep",
                "value": "12"
              },
              {
                "name": "AutoScaleY",
                "value": "true"
              },
              {
                "name": "AutoScaleX",
                "value": "true"
              },
              {
                "name": "CursorTypeForMove",
                "value": "\"pointer\""
              },
              {
                "name": "XAutoTicks",
                "value": "false"
              },
              {
                "name": "Height",
                "value": "\"90vh\""
              },
              {
                "name": "XScalePrecision",
                "value": "0"
              },
              {
                "name": "Width",
                "value": "\"100%\""
              }
            ],
            "Children": [
              {
                "Name": "shapeSet",
                "Type": "ShapeSet2D",
                "Properties": [
                  {
                    "name": "NumberOfElements",
                    "value": "numOfSquares"
                  },
                  {
                    "name": "FillColor",
                    "value": "squareColor"
                  },
                  {
                    "name": "Sensitivity",
                    "value": "0"
                  },
                  {
                    "name": "SizeX",
                    "value": "squareLength"
                  },
                  {
                    "name": "ShapeType",
                    "value": "\"RECTANGLE\""
                  },
                  {
                    "name": "X",
                    "value": "squareX"
                  },
                  {
                    "name": "Y",
                    "value": "squareY"
                  },
                  {
                    "name": "OnPress",
                    "value": "var x = idxToPos[interact][0], y = idxToPos[interact][1];\n//console.log(interact, x, y);\nonClickSquare(x, y);\n"
                  },
                  {
                    "name": "SizeY",
                    "value": "squareLength"
                  },
                  {
                    "name": "EnabledPosition",
                    "value": "\"ENABLED_NO_MOVE\""
                  },
                  {
                    "name": "ElementInteracted",
                    "value": "interact"
                  }
                ]
              },
              {
                "Name": "textStarthome",
                "Type": "Text2D",
                "Properties": [
                  {
                    "name": "X",
                    "value": "startX"
                  },
                  {
                    "name": "Y",
                    "value": "startY"
                  },
                  {
                    "name": "Text",
                    "value": "\"\ud83c\udfe0\""
                  },
                  {
                    "name": "Font",
                    "value": "\"normal normal 4vw \""
                  }
                ]
              },
              {
                "Name": "textSetuniocdeStar_End",
                "Type": "TextSet2D",
                "Properties": [
                  {
                    "name": "X",
                    "value": "textXunicode"
                  },
                  {
                    "name": "Y",
                    "value": "textYunicode"
                  },
                  {
                    "name": "Text",
                    "value": "\"\u2b50\""
                  },
                  {
                    "name": "FontWeight",
                    "value": "\"bold\""
                  },
                  {
                    "name": "Font",
                    "value": "\"normal normal 4vw \""
                  }
                ]
              },
              {
                "Name": "unicodeknight",
                "Type": "Text2D",
                "Properties": [
                  {
                    "name": "FillColor",
                    "value": "\"Green\""
                  },
                  {
                    "name": "X",
                    "value": "textXunicodek"
                  },
                  {
                    "name": "Y",
                    "value": "textYunicodek"
                  },
                  {
                    "name": "Text",
                    "value": "\"\u265e\""
                  },
                  {
                    "name": "Font",
                    "value": "\"normal normal 5vw \""
                  }
                ]
              },
              {
                "Name": "textSet",
                "Type": "TextSet2D",
                "Properties": [
                  {
                    "name": "X",
                    "value": "textX"
                  },
                  {
                    "name": "Y",
                    "value": "textY"
                  },
                  {
                    "name": "Text",
                    "value": "%textStr%"
                  },
                  {
                    "name": "FontWeight",
                    "value": "\"bold\""
                  },
                  {
                    "name": "Font",
                    "value": "font"
                  }
                ]
              }
            ]
          }
        ]
      },
      {
        "Name": "html",
        "Type": "Panel",
        "Properties": [
          {
            "name": "Html",
            "value": "<div style=\"font-family: Arial, sans-serif; line-height: 1.6; max-width: 920px;\">\n  <h2>About this Simulation</h2>\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/ZwzDE3tXxeY?si=6gVD2c7CLZUtjajH\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen></iframe>\n  <p>This interactive two-player strategy game explores knight moves on an irregular board. Players take turns moving with the chess knight pattern: two squares in one direction and one square perpendicular. A player wins by reaching a golden star or by leaving the opponent with no legal move.</p>\n  <p>Because the game is built around legal-move recognition, target-seeking, and blocking strategies, it helps learners connect spatial reasoning with mathematical thinking. Every move changes not only the current position, but also the set of future possibilities for both players.</p>\n\n  <h2>Pedagogical Use</h2>\n  <p>Use this simulation as a predict-play-explain task for pairs, small groups, or whole-class discussion. Before each move, ask learners to identify every legal knight move, choose the move they think is strongest, and explain why before clicking. After the move, ask them to reflect on whether the choice improved their path toward a star, increased their future options, or reduced the opponent's choices.</p>\n  <p>This works well for mathematical talk because students can justify decisions using positions, coordinates, rows and columns, reachable squares, and future move trees. The game can also be used as an enrichment task in strategy, logical reasoning, and proof-like explanation, because learners must defend why one move is stronger than another.</p>\n\n  <p>You can also switch either player between Human and Computer, or press <strong>Auto move</strong> at any point to let the simulation demonstrate a strong next move from the current position.</p>\n\n  <h2>Expanded Learning Goals</h2>\n  <ul>\n    <li>Recognise and apply the movement rule of a knight accurately on a grid or irregular board.</li>\n    <li>Develop spatial reasoning by visualising legal and illegal destinations from a given square.</li>\n    <li>Compare immediate goals such as reaching a star with longer-term goals such as controlling future options.</li>\n    <li>Use logical reasoning to plan ahead and anticipate an opponent's reply.</li>\n    <li>Describe positions and move sequences clearly using rows, columns, or coordinate notation.</li>\n    <li>Identify patterns in squares that offer many future moves and squares that lead to traps.</li>\n    <li>Build confidence in explaining strategy using mathematical language rather than guesswork alone.</li>\n  </ul>\n\n  <h2>Suggested Learning Sequence</h2>\n  <ol>\n    <li>Warm up by asking learners to point out all legal knight moves from the starting square.</li>\n    <li>Play a short round in pairs and require each player to explain their intended move before clicking.</li>\n    <li>Pause the game at a critical position and discuss which move is best and why.</li>\n    <li>Replay from the same position with a different strategy and compare outcomes.</li>\n    <li>Ask learners to record a winning path or a trapping sequence using coordinates or move numbers.</li>\n  </ol>\n\n  <h2>Suggested Classroom Activities</h2>\n  <ol>\n    <li>Find all the legal moves from a given square and explain why every other square is not allowed.</li>\n    <li>Play one round with the goal of reaching a star as quickly as possible, then a second round with the goal of trapping the opponent.</li>\n    <li>Stop at a turning point in the game and ask learners to vote on the best move before continuing.</li>\n    <li>Challenge learners to invent their own notation for recording the moves, then compare it with coordinate notation.</li>\n    <li>Ask learners to work backwards from a winning square and find possible sequences that could reach it.</li>\n  </ol>\n\n  <h2>Challenge Tasks</h2>\n  <ul>\n    <li>Can you find a guaranteed winning first move from the starting position?</li>\n    <li>Can you create a sequence of moves that forces your opponent into a position with no legal move?</li>\n    <li>Which squares seem most powerful, and what evidence from play supports your claim?</li>\n    <li>Can you compare two different winning strategies and explain which is more efficient?</li>\n  </ul>\n\n  <h2>Thinking Questions</h2>\n  <ul>\n    <li>How do you know whether a move is a legal knight move without trial and error?</li>\n    <li>Why can a move that looks closer to a star still be a poor strategic choice?</li>\n    <li>What patterns do you notice about squares that give many future options?</li>\n    <li>Is it sometimes better to block your opponent than to move toward the nearest star? Why?</li>\n    <li>How can coordinate notation help you explain a winning path clearly?</li>\n    <li>What is the difference between a good move now and a move that creates a stronger position two turns later?</li>\n  </ul>\n\n  <h2>Rules</h2>\n  <p>Each player makes a knight move to an empty square from the place the opponent landed on the preceding turn.</p>\n  <p>When <strong>Hint?</strong> is turned on, allowed knight moves are shown by the light green highlighted squares.</p>\n  <p>A player wins by landing on a golden star or by leaving the opponent with no legal move.</p>\n\n  <h2>More Resources</h2>\n  <ul>\n    <li><a href=\"https://sg.iwant2study.org/ospsg/index.php/interactive-resources/mathematics/1054-knight-intro\" target=\"_blank\" rel=\"noopener noreferrer\">Knight - Intro</a></li>\n    <li><a href=\"https://sg.iwant2study.org/ospsg/index.php/interactive-resources/mathematics/1101-knight-moves-no-hint\" target=\"_blank\" rel=\"noopener noreferrer\">Knight Moves (No Hint)</a></li>\n    <li><a href=\"https://sg.iwant2study.org/ospsg/index.php/interactive-resources/mathematics/1057-knight-moves\" target=\"_blank\" rel=\"noopener noreferrer\">Knight Moves</a></li>\n    <li><a href=\"https://sg.iwant2study.org/ospsg/index.php/interactive-resources/mathematics/1055-knight-star-to-star\" target=\"_blank\" rel=\"noopener noreferrer\">Knight: Star to Star</a></li>\n    <li><a href=\"https://sg.iwant2study.org/ospsg/index.php/interactive-resources/mathematics/1102-knight-star-to-star-no-hint\" target=\"_blank\" rel=\"noopener noreferrer\">Knight: Star to Star (No Hint)</a></li>\n    <li><a href=\"https://sg.iwant2study.org/ospsg/index.php/events/1106-20210830-digital-e2k-applications-knights-move\" target=\"_blank\" rel=\"noopener noreferrer\">Digital E2K Applications: Knight's Move</a></li>\n  </ul>\n</div>"
          }
        ]
      }
    ],
    "RootProperties": []
  },
  "metadata": {
    "APP": "WebEJS",
    "CreatedWith": "WebEJS : The web version of Easy JavaScript Simulations",
    "MoreInfo": "WebEJS 1.1",
    "version": "https://www.um.es/fem/wikis/webejs/"
  }
}