您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
win at neggsweeper (neo-legal)
// ==UserScript== // @name Neggsweeper solver // @namespace http://tampermonkey.net/ // @version 0.5 // @description win at neggsweeper (neo-legal) // @author You // @match http*://www.neopets.com/games/neggsweeper/neggsweeper.phtml // @grant none // @require http://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js // ==/UserScript== /* Known issues: 1. Inner fringe calculations do not take into account the number of mines left in the game. As a result, sometimes mines that are clear may be marked with a percentage 2. Occasionally, a clear mine simply isn't marked at all, being registered as a 1 or 2 on the grid. Still unsure why this happens */ (function() { // 'use strict'; var NS_KEY = "nsg" var htmlGrid = []; var grid = []; // map size to mine number var modeMap = { 9: 10, 12: 25, 14: 40 } var debugMode = false; var DEBUG_KEY = "debuggrid" function debugLog(s) { debugMode && console.log(s) } // size based on number of rows // TODO: deal with variable columns too function getTotalNumberOfMines(grid) { return modeMap[grid.length] } /* UI Components */ function isUnknown(el) { // tested return $(el).find('[src="http://images.neopets.com/x/gn.gif"]').length || $(el).find('[src="//images.neopets.com/x/gn.gif"]').length || $(el).find('[src="https://images.neopets.com/x/gn.gif"]').length; } function isMarked(el) { // tested return $(el).find('[src="http://images.neopets.com/games/neggsweeper/old/flagnegg.gif"]').length; } function getSurroundingNumber(el) { // tested return +($(el).find('b').html() || 0); } /* turns neopets grid into a 2d array. Knowns are filled as numbers, unknowns filled as null */ function parseHtmlGrid() { // tested var g = [] var rows = $($('[name=grid] tbody')[0]).find('tr'); rows.each(function(r, d) { if (r >= 3) { var htmlRow = []; var row = []; $(d).find('td').each(function(i, el) { htmlRow.push(el); if (isUnknown(el) || isMarked(el)) { row.push(null); } else { row.push(getSurroundingNumber(el)); } }); htmlGrid.push(htmlRow); g.push(row); } }) return g; } function markBad(row, col) { // tested $(htmlGrid[row][col]).css('border', '3px solid red'); } function markGood(row, col) { // tested $(htmlGrid[row][col]).css('border', '3px solid green'); } function markPercentBad(row, col, p, color) { // tested color = color || "red" $(htmlGrid[row][col]).append('<font color="' + color + '">' + Math.round(100 * p) + '%</font>'); } function displayGridMarks() { // tested for (var r = 0; r < grid.length; ++r) { for (var c = 0; c < grid[0].length; ++c) { if (grid[r][c]) { if (grid[r][c] === "x") { markBad(r, c); } else if (grid[r][c] === "o") { markGood(r, c); } else if (grid[r][c] > 0 && grid[r][c] < 1) { markPercentBad(r, c, grid[r][c]); } } } } } function getNumGood() { return +$('form[name="grid"]').find('td')[3].innerHTML.match(/\d+/)[0]; } /* Logic components */ /* Constructor for Coords objects */ Array.prototype.indexOfCoord = function (c) { for (var i = 0; i < this.length; ++i) { if (this[i].row === c.row && this[i].col === c.col) { return i; } } return -1; } function Coords(row, col) { this.row = row; this.col = col; this.isMine = false; this.mineOdds = 0; this.hits = 0; // currently not really used, maybe for isolated cluster solution in the future this.equals = function(c) { return c.row === this.row && c.col === this.col; }; this.deepCopy = function() { var temp = new Coords(this.row, this.col); temp.isMine = this.isMine; temp.mineOdds = this.mineOdds; temp.hits = this.hits; return temp; } } /* Constructor for unsolved tree node * coords: Coords * unsolvedList: [coords<Coords>, ...] * numSurround: 1 (number of surrounding mines) * possibleConfig: [1, 0, 0, 0] (1 being a mine, 0 not) */ function UnsolvedNode(coords, unsolvedNeighbors, numSurround) { this.coords = coords; this.unsolvedNeighbors = unsolvedNeighbors; // source of truth this.numSurround = numSurround; this.fixedIdx = []; this.countMines = function() { return this.unsolvedNeighbors && this.unsolvedNeighbors.map(d => +d.isMine).reduce(function(a,b){ return a + b }) }; this.validate = function() { return this.countMines() <= this.numSurround; } this.generateConfig = function * (config, numMines, idx) { // config = [...config]; if (idx === config.length - 1) { var sumMines = config.reduce((a,b) => a + b); if (this.fixedIdx.indexOf(idx) === -1) { config[idx] = 0; sumMines = config.reduce((a,b) => a + b); if (sumMines === numMines) { yield config; } if (sumMines < numMines) { config[idx] = 1; sumMines = config.reduce((a,b) => a + b); if (sumMines === numMines) { yield config; } } } else { config[idx] = +this.unsolvedNeighbors[idx].isMine; if (sumMines === numMines) { yield config; } } } else { if (this.fixedIdx.indexOf(idx) === -1) { config[idx] = 0; yield * this.generateConfig(config, numMines, idx + 1); if (config.reduce((a,b) => a + b) < numMines) { config[idx] = 1; yield * this.generateConfig(config, numMines, idx + 1); } } else { config[idx] = this.unsolvedNeighbors[idx].isMine ? 1 : 0; yield * this.generateConfig(config, numMines, idx + 1); } } } this.configGenerator = null; this.mapConfigToNodes = function(cfg) { var that = this; if (cfg) { cfg.forEach(function(d, i) { that.unsolvedNeighbors[i].isMine = !!d; }); return this.unsolvedNeighbors; } return null; } this.getNextPossibleConfig = function() { /* config: [1, 0, 0, 0] (1 being a mine, 0 not) */ if (numSurround <= this.unsolvedNeighbors.length) { if (this.configGenerator === null) { this.configGenerator = this.generateConfig(this.unsolvedNeighbors.map(d => d.isMine? 1: 0), this.numSurround, 0); } var val = this.configGenerator.next().value; if (val === null) { this.configGenerator = null; } //while (val && numSurround !== val.reduce((a,b) => a + b)) { // this.configGenerator.next().value //} return this.mapConfigToNodes(val) } else { console.error("UnsolvedNode > incorrect number of mines to possible squares. Is this really unsolved? call this.validate() first"); } } this.reset = function() { this.fixedIdx = []; this.configGenerator = null; }; /* Return a new mc that takes all marked mines of the rhs and marks them for this object */ this.merge = function(otherNode) { var that = this; otherNode.unsolvedNeighbors.forEach(function(d) { var idx = that.unsolvedNeighbors.indexOfCoord(d); if (idx > -1) { if (that.fixedIdx.indexOf(idx) === -1) { that.fixedIdx.push(idx); } } }); return this.validate(); }; this.deepCopy = function() { var temp = new UnsolvedNode(this.coords.deepCopy(), this.unsolvedNeighbors.map(d => d.deepCopy()), this.numSurround); temp.fixedIdx = [...this.fixedIdx]; return temp; } } function Solution(unsolvedNodes) { var that = this; this.nodes = []; //[UnsolvedNode, ...] unsolvedNodes.forEach(function(d) { that.nodes.push(d.deepCopy()); }); this.allNeighbors = []; this.nodes.forEach(function(d) { d.unsolvedNeighbors.forEach(function(n, i, a) { var idx = that.allNeighbors.indexOfCoord(n); if (idx === -1) { that.allNeighbors.push(n); } else { a[i] = that.allNeighbors[idx]; } }) }) } function getNeighbors(r, c, testfn) { // tested // Assume bigger than 1x1 var n = []; testfn = testfn || function (x) { return x === null }; if (r > 0) { if (c > 0) { if (testfn(grid[r - 1][c - 1])) { n.push(new Coords(r - 1, c - 1)); } } if (c < grid[0].length - 1) { if (testfn(grid[r - 1][c + 1])) { n.push(new Coords(r - 1, c + 1)); } } if (testfn(grid[r - 1][c])) { n.push(new Coords(r - 1, c)); } } if (r < grid.length - 1) { if (c > 0) { if (testfn(grid[r + 1][c - 1])) { n.push(new Coords(r + 1, c - 1)); } } if (c < grid[0].length - 1) { if (testfn(grid[r + 1][c + 1])) { n.push(new Coords(r + 1, c + 1)); } } if (testfn(grid[r + 1][c])) { n.push(new Coords(r + 1, c)); } } if (c > 0) { if (testfn(grid[r][c - 1])) { n.push(new Coords(r, c - 1)); } } if (c < grid[0].length - 1) { if (testfn(grid[r][c + 1])) { n.push(new Coords(r, c + 1)); } } return n; } function findAllUnsolvedNodes() { // tested var result = {}; var unsolvedNodes = []; var allCoords = []; grid.forEach(function(r, i) { r.forEach(function(c, j) { if (c >= 1) { var n; if (localStorage.getItem(NS_KEY)) { n = getNeighbors(i, j, function(x) { return x === 'x' }); // identify neighbors who are mines c -= n.length; // did we completely solve the node already? n = getNeighbors(i, j, function(x) { return x === null || (x > 0 && x < 1) }); // find remaining unknowns debugLog(i + ", " + j + " remaining mines: "+ c) debugLog(n.length + " unsolved nodes ") if (c && n && n.length) { unsolvedNodes.push(new UnsolvedNode(new Coords(i, j), n, c)); // consider the number of surrounding mines reduced by our solution } else if (c === 0) { n.forEach(function (d) { d.isMine = false; grid[d.row][d.col] = 'o'; }); } } else { n = getNeighbors(i, j); if (n && n.length) { unsolvedNodes.push(new UnsolvedNode(new Coords(i, j), n, c)); } } } }); }); result.unsolvedNodes = unsolvedNodes; result.allCoords = allCoords; return unsolvedNodes; } function findAllSolutionsHelper(unsolvedNodes, idx, solutions) { if (idx === unsolvedNodes.nodes.length - 1) { unsolvedNodes.nodes[idx].unsolvedNeighbors = unsolvedNodes.nodes[idx].getNextPossibleConfig(); while (unsolvedNodes.nodes[idx].unsolvedNeighbors !== null) { var minesMatch = true; for (var i = 0; i < unsolvedNodes.nodes.length; ++i) { var numMines = unsolvedNodes.nodes[i].countMines(); if (unsolvedNodes.nodes[i].numSurround !== numMines) { minesMatch = false; break; } } if (minesMatch) { solutions.push(new Solution(unsolvedNodes.nodes)); } unsolvedNodes.nodes[idx].unsolvedNeighbors = unsolvedNodes.nodes[idx].getNextPossibleConfig(); } } else if (idx < unsolvedNodes.nodes.length - 1) { var isValid = true; unsolvedNodes.nodes[idx].unsolvedNeighbors = unsolvedNodes.nodes[idx].getNextPossibleConfig(); while (unsolvedNodes.nodes[idx].unsolvedNeighbors !== null) { for (var j = idx + 1; (j < unsolvedNodes.nodes.length) && isValid; ++j) { isValid = unsolvedNodes.nodes[j].merge(unsolvedNodes.nodes[idx]); } //if (isValid) { findAllSolutionsHelper(new Solution(unsolvedNodes.nodes), idx + 1, solutions); for (j = idx + 1; (j < unsolvedNodes.nodes.length) && isValid; ++j) { unsolvedNodes.nodes[j].reset(); } //} unsolvedNodes.nodes[idx].unsolvedNeighbors = unsolvedNodes.nodes[idx].getNextPossibleConfig(); } } /*var tempUnsolvedNodes = []; unsolvedNodes.forEach(function(d) { tempUnsolvedNodes.push( d.deepCopy()); }) var isValid = true; if (idx >= unsolvedNodes.length - 1) { while (tempUnsolvedNodes[idx].unsolvedNeighbors = unsolvedNodes[idx].getNextPossibleConfig()) { console.log(tempUnsolvedNodes[idx]); var minesMatch = true; for (var i = 0; i < tempUnsolvedNodes.length; ++i) { var numMines = tempUnsolvedNodes[idx].countMines(); if (tempUnsolvedNodes[i].numSurround !== numMines) { minesMatch = false; break; } } if (minesMatch) { tempUnsolvedNodes[idx] = tempUnsolvedNodes[idx].deepCopy(); solutions.push(tempUnsolvedNodes); } tempUnsolvedNodes = []; unsolvedNodes.forEach(function(d) { tempUnsolvedNodes.push( d.deepCopy()); }) } } else { var tempConfig = unsolvedNodes[idx].getNextPossibleConfig(); while (tempConfig && tempConfig.length) { for (var j = idx + 1; (j < tempUnsolvedNodes.length) && isValid; ++j) { isValid = tempUnsolvedNodes[j].merge(tempConfig); } if (isValid) { findAllSolutionsHelper(unsolvedNodes, idx + 1, solutions); for (j = idx + 1; (j < tempUnsolvedNodes.length) && isValid; ++j) { isValid = unsolvedNodes[j].reset(); } } tempConfig = unsolvedNodes[idx].getNextPossibleConfig(); } }*/ } function findAllSolutions() { var solutions = []; // [Solution(), ...] var unsolvedNodes = findAllUnsolvedNodes(); debugLog("unsolvedNodes"); debugLog(unsolvedNodes); findAllSolutionsHelper(new Solution(unsolvedNodes), 0, solutions); return solutions; } function combineSolutions() { var solutions = findAllSolutions(); debugLog("solutions", solutions); var coords = []; solutions.forEach(function(s) { s.allNeighbors.forEach(function(coord) { var idx = coords.indexOfCoord(coord); if (idx === -1) { coords.push(coord); coord.mineOdds += +coord.isMine; } else { coords[idx].mineOdds += +coord.isMine; } coord.hits++; }) }) coords.forEach(function(d) { d.mineOdds = d.mineOdds / solutions.length; }); return coords; } function markGrid(solution) { solution.forEach(function(coord){ if (coord.mineOdds === 0) { grid[coord.row][coord.col] = 'o'; } else if (coord.mineOdds === 1) { grid[coord.row][coord.col] = 'x'; } else { grid[coord.row][coord.col] = coord.mineOdds; } }); } function precompute() { var previousGrid = localStorage.getItem(NS_KEY); grid = parseHtmlGrid(); if (previousGrid) { previousGrid = JSON.parse(previousGrid); for (var r = 0; r < grid.length; ++r) { for (var c = 0; c < grid[0].length; ++c) { grid[r][c] = grid[r][c] === null ? previousGrid[r][c] : grid[r][c] } } } } function markOuterFringe() { var numOuter = 0; var numUnknown = 0; var numExpectedMines = 0; var numGood = getNumGood(); for (var r = 0; r < grid.length; ++r) { for (var c = 0; c < grid.length; ++c) { if (grid[r][c] === null) { numOuter++; numUnknown++; } else if (grid[r][c] < 1 && grid[r][c] > 0) { numExpectedMines += grid[r][c]; numUnknown++; } else if (grid[r][c] === 'o') { numUnknown++; } } } if (numOuter > 0) { var probOuter = (Math.max(numUnknown - numGood - numExpectedMines, 0) / numOuter) for (r = 0; r < grid.length; ++r) { for (c = 0; c < grid.length; ++c) { if (grid[r][c] === null) { if (probOuter === 1) { grid[r][c] = 'x'; } else if (probOuter === 0) { grid[r][c] = 'o'; } else { grid[r][c] = probOuter; } } } } debugLog("outer fringe calculation", grid); } } function postcompute() { localStorage.setItem(NS_KEY, JSON.stringify(grid)); } /*function copyGrid(g) { var newGrid = []; for (var r = 0; r < g.length; ++r) { newGrid.push([...g[r]]); } return newGrid; }*/ /* Main */ var ended = /You Lose|You Won/.test($('form[name="grid"]').html()) if (ended) { localStorage.removeItem(NS_KEY); localStorage.removeItem(DEBUG_KEY) } else { precompute() if (debugMode) { var debugData = JSON.parse(localStorage.getItem(DEBUG_KEY)) if (!debugData) { debugData = { 'gridData': [{ 'grid': grid, }] } } else { debugData.gridData.push({"grid": grid}) } localStorage.setItem(DEBUG_KEY, JSON.stringify(debugData)) } debugLog('grid'); debugLog(grid); debugLog('findallunsolvednodes result') debugLog(findAllUnsolvedNodes()) markGrid(combineSolutions()) postcompute() markOuterFringe(); displayGridMarks(); if (debugMode) { debugData.gridData.push({'grid': grid}) debugMode && localStorage.setItem(DEBUG_KEY, JSON.stringify(debugData)) } debugLog('grid'); debugLog(grid); } /* var n = new UnsolvedNode(new Coords(0,0), [new Coords(1,2),new Coords(1,3),new Coords(1,4),new Coords(1,5)],1) var an = new UnsolvedNode(new Coords(0,1), [new Coords(1,2),new Coords(1,6),new Coords(1,7),new Coords(1,8)],1) var s = new Solution([n, an]); console.log(s); s.allNeighbors[0].isMine = true; n = new UnsolvedNode(new Coords(0,0), [new Coords(1,1),new Coords(1,1),new Coords(1,1),new Coords(1,1)],1) n.configGenerator = n.generateConfig([0,0,0,0],1,0) n.fixedIdx=[1]; n.configGenerator.next().value n = new UnsolvedNode(new Coords(0,0), [new Coords(1,1),new Coords(1,1),new Coords(1,1),new Coords(1,1)],1) an = new UnsolvedNode(new Coords(0,0), [new Coords(1,1),new Coords(1,1),new Coords(1,1),new Coords(1,1)],1) an.unsolvedNeighbors[1].isMine = true; n.configGenerator = n.generateConfig([0,1,0,0],1,0) n.configGenerator.next().value n = new UnsolvedNode(new Coords(0,0), [new Coords(1,2),new Coords(1,3),new Coords(1,4),new Coords(1,5)],1) an = new UnsolvedNode(new Coords(0,1), [new Coords(1,2),new Coords(1,6),new Coords(1,7),new Coords(1,8)],1) n.unsolvedNeighbors[0].isMine = true; an.merge(n.unsolvedNeighbors) */ })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址