您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
picross solver
// ==UserScript== // @name picross // @namespace http://du9l.com/ // @version 0.1 // @description picross solver // @author Du9L // @match http://liouh.com/picross2/ // @grant none // ==/UserScript== (function() { "use strict"; let $ = window.jQuery; const STATE_UNKNOWN = 'UNKNOWN', STATE_ON = 'ON', STATE_OFF = 'OFF'; const TASK_ROW = 'ROW', TASK_COL = 'COL'; let loadPuzzle = function () { let new_puzzle = { rows: 0, cols: 0, row_keys: [], col_keys: [], }; let all_rows = $('#puzzle tbody > tr'); new_puzzle.rows = all_rows.length - 1; // load row keys for (let r = 1; r < all_rows.length; ++r) { let key_cell = $(all_rows[r]).find('.key').first(); let keys = []; $(key_cell).find('strong').each( (index, element) => { keys.push(parseInt($(element).text())); }); new_puzzle.row_keys.push(keys); } let all_cols = $($('#puzzle tbody > tr').first()).find('td'); new_puzzle.cols = all_cols.length - 1; // load col keys for (let c = 1; c < all_cols.length; ++c) { let key_cell = all_cols[c]; let keys = []; $(key_cell).find('strong').each( (index, element) => { keys.push(parseInt($(element).text())); }); new_puzzle.col_keys.push(keys); } return new_puzzle; } // search function: // [valid solution] {kth cell: [possible states]} | [invalid solution] null = // search ( keys since X, cells since Y ) let searchFunction = function (keys_since, cells_since, keys, initial_state) { // edge: no more keys to fulfill, set rest cells to OFF if (keys_since >= keys.length) { let ret = {}; for (let cell = cells_since; cell < initial_state.length; ++cell) { if (initial_state[cell] == STATE_UNKNOWN) { ret[cell] = [STATE_OFF]; } else if (initial_state[cell] == STATE_ON) { return null; } } return ret; } // edge: no more cells to use, return invalid if (cells_since >= initial_state.length) { return null; } // if current cell is OFF, skip to next ON or UNKNOWN cell let state = initial_state[cells_since]; let ret = null; if (state == STATE_OFF || state == STATE_UNKNOWN) { let start_point = cells_since; while (++start_point < initial_state.length) { if (initial_state[start_point] != STATE_OFF) { ret = searchFunction(keys_since, start_point, keys, initial_state); if (ret !== null && state == STATE_UNKNOWN) { ret[cells_since] = [STATE_OFF]; } break; } } } // if current cell is ON, try to fill the rest of current key if (state == STATE_ON || state == STATE_UNKNOWN) { let current_key = keys[keys_since]; if (cells_since + current_key <= initial_state.length) { // check if ON is possible let valid = true; for (let cell = cells_since; cell < cells_since + current_key; ++cell) { if (initial_state[cell] == STATE_OFF) { valid = false; break; } } if (valid && cells_since + current_key < initial_state.length && initial_state[cells_since + current_key] == STATE_ON) { valid = false; } if (valid) { let start_point = cells_since + current_key + 1; if (start_point > initial_state.length) { -- start_point; } let sub_result = searchFunction(keys_since+1, start_point, keys, initial_state); if (sub_result !== null) { if (ret === null) { ret = {}; } // set current key cells for (let cell = cells_since; cell < cells_since + current_key + 1; ++cell) { if (cell >= initial_state.length || initial_state[cell] != STATE_UNKNOWN) { continue; } let new_value = (cell == cells_since + current_key) ? STATE_OFF : STATE_ON; if (cell in ret) { if (!ret[cell].includes(new_value)) { ret[cell].push(new_value) } } else { ret[cell] = [new_value]; } } // merge with the sub-result for (const cell in sub_result) { if (!sub_result.hasOwnProperty(cell)) { continue; } if (cell in ret) { for (const val of sub_result[cell]) { if (!ret[cell].includes(val)) { ret[cell].push(val) } } } else { ret[cell] = sub_result[cell]; } } } } } } return ret; } // end of search function let solvePuzzle = function (puzzle) { // initial state let state = []; for (let r = 0; r < puzzle.rows; ++r) { let row_state = []; for (let c = 0; c < puzzle.cols; ++c) { row_state.push(STATE_UNKNOWN); } state.push(row_state); } // initial queue let task_set = new Set(); let row_tasks = [], col_tasks = []; for (let r = 0; r < puzzle.rows; ++r) { let task = [TASK_ROW, r]; row_tasks.push(task); task_set.add(task); } for (let c = 0; c < puzzle.cols; ++c) { let task = [TASK_COL, c]; col_tasks.push(task); task_set.add(task); } // start searching let solution = []; while (task_set.size != 0) { let new_tasks = new Set(); // run each task for (const task of task_set.values()) { let task_type = task[0], row_or_col = task[1]; console.info('Run task', task_type, row_or_col); // task data let initial_state, keys; if (task_type == TASK_ROW) { initial_state = state[row_or_col].slice(); keys = puzzle.row_keys[row_or_col]; } else { initial_state = []; for (const row_state of state) { initial_state.push(row_state[row_or_col]); } keys = puzzle.col_keys[row_or_col]; } // start searching let ret = searchFunction(0, 0, keys, initial_state); if (ret === null) { console.error("A search returned null!!", task_type, row_or_col); } else { // check for cells with only one solution for (let cell in ret) { if (!ret.hasOwnProperty(cell)) { continue; } const val = ret[cell]; cell = parseInt(cell); if (val.length == 0) { console.error("Search function returned empty array!!", val); } else if (val.length == 1) { let row, col; if (task_type == TASK_ROW) { if (state[row_or_col][cell] != STATE_UNKNOWN) { throw "Search function returned known cell"; } state[row_or_col][cell] = val[0]; new_tasks.add(col_tasks[cell]); row = row_or_col; col = cell; } else { if (state[cell][row_or_col] != STATE_UNKNOWN) { throw "Search function returned known cell"; } state[cell][row_or_col] = val[0]; new_tasks.add(row_tasks[cell]); row = cell; col = row_or_col; } console.info("Found solution!!", row, col, val[0]); solution.push([row, col, val[0]]); } } } } // check if it's all solved let solved = true; for (const row_state of state) { for (const col_value of row_state) { if (col_value == STATE_UNKNOWN) { solved = false; break; } } if (!solved) { break; } } if (solved) { console.info('all solved!!'); break; } // append new tasks console.info('====== next round ======') task_set.clear(); for (const new_task of new_tasks.values()) { console.info('task:', new_task); task_set.add(new_task); } } return solution; } let applySolution = function (solution) { let _clicker = function (step_data) { let obj = $($('#puzzle tbody > tr')[step_data[0] + 1]).find('td')[step_data[1] + 1]; let _which = (step_data[2] == STATE_ON) ? 1 : 3; $(obj).trigger( {type: 'mousedown', target: obj, which: _which} ).trigger( {type: 'mouseup', target: obj, which: _which} ); } for (let step = 0; step < solution.length; step++) { const step_data = solution[step]; setTimeout(_clicker.bind(undefined, step_data), (step + 1) * 100); } } let addSolveButton = function () { let container = $('.controls .control-group').first(); $('<div class="custom" />').appendTo(container); let button = $('<button>Auto solve</button>'); button.mouseup(function() { let puzzle = loadPuzzle(); console.info(puzzle); let solution = solvePuzzle(puzzle); applySolution(solution); let solvedPercent = solution.length * 100 / (puzzle.rows * puzzle.cols) $('<span />').text( 'Solved: ' + (solvedPercent.toFixed(2) + "%") ).appendTo('#solverText').delay(2000).fadeOut(); }); button.appendTo(container); $('<div id="solverText" />').appendTo(container); } window.addEventListener('load', addSolveButton, false); })();