Last active
January 29, 2023 22:03
-
-
Save dclamage/63cc6241752dbeb1214328f4ae655cf7 to your computer and use it in GitHub Desktop.
Allows setters to specify a solution to the puzzle, which is checked in solve mode when the final digit is entered.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Fpuzzles-Solution | |
// @namespace http://tampermonkey.net/ | |
// @version 2.1 | |
// @description Can input a solution to a puzzle, with a custom message for a correct solve. | |
// @author Rangsk, Charlie | |
// @match https://*.f-puzzles.com/* | |
// @match https://f-puzzles.com/* | |
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== | |
// @grant none | |
// @run-at document-end | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
const numSolutionToolsButtons = 4; | |
const successMessageKey = 'msgcorrect:'; | |
const isValidSuccessMessage = function(message) { | |
return typeof message === 'string' && message.trim().length > 0; | |
} | |
const isSuccessMessageCage = function(cage) { | |
if (cage.value === undefined) return false; | |
return cage.value.startsWith(successMessageKey) | |
} | |
const doShim = function() { | |
// Additional import/export data | |
const origExportPuzzle = window.exportPuzzle; | |
window.exportPuzzle = function(includeCandidates) { | |
const compressed = origExportPuzzle(includeCandidates); | |
const puzzle = JSON.parse(compressor.decompressFromBase64(compressed)); | |
if (puzzle.cage) { | |
puzzle.cage = puzzle.cage.filter((cage) => !isSuccessMessageCage(cage)); | |
} | |
if (isValidSuccessMessage(window.successMessage)) { | |
puzzle.successMessage = window.successMessage; | |
if (puzzle.cage === undefined) puzzle.cage = []; | |
puzzle.cage.push({ | |
value: `${successMessageKey} ${window.successMessage}`, | |
cells: ['R1C1'], | |
outlineC: '#ffffff00', | |
fontC: '#ffffff00', | |
}); | |
} | |
if (window.grid.solution) { | |
puzzle.solution = window.grid.solution; | |
} | |
return compressor.compressToBase64(JSON.stringify(puzzle)); | |
} | |
const origImportPuzzle = window.importPuzzle; | |
window.importPuzzle = function(string, clearHistory) { | |
const puzzle = JSON.parse(compressor.decompressFromBase64(string)); | |
origImportPuzzle(string, clearHistory); | |
if (puzzle.cage !== undefined) { | |
let message = null; | |
let newCages = puzzle.cage.reduce((arr, cage) => { | |
if (isSuccessMessageCage(cage)) { | |
message = cage.value.replace(successMessageKey, '').trim() | |
return arr; | |
} | |
return [...arr, cage]; | |
}, []); | |
if (newCages.length === 0) delete puzzle.cage; | |
else puzzle.cage = newCages; | |
if (window.successMessage === undefined && puzzle.successMessage === undefined && message !== null) { | |
puzzle.successMessage = message; | |
} | |
} | |
if (isValidSuccessMessage(puzzle.successMessage)) { | |
window.successMessage = puzzle.successMessage; | |
} else if (window.successMessage !== undefined) { | |
delete window.successMessage; | |
} | |
if (puzzle.solution) { | |
window.grid.solution = puzzle.solution; | |
} else if (window.grid.solution !== undefined) { | |
delete window.grid.solution; | |
} | |
} | |
popups.successmessage = { | |
w: canvas.width / 2, | |
h: 64 + buttonGap + (canvas.height * 0.3) + 2 * buttonGap + buttonSH, | |
}; | |
const inputEl = document.createElement('textarea'); | |
inputEl.classList.add('textarea'); | |
inputEl.setAttribute('id', 'successMessageInput'); | |
inputEl.style.width = '47%'; | |
inputEl.style.height = '25%'; | |
inputEl.style.left = '50%'; | |
inputEl.style.bottom = '60%'; | |
inputEl.style.transform = 'translate(-50%)'; | |
inputEl.style.top = '40%'; | |
inputEl.style.position = 'fixed'; | |
inputEl.style.backgroundColor = boolSettings['Dark Mode'] ? '#ffffff' : '#000000'; | |
inputEl.style.fontSize = 'min(2.31111vh, 1.3vw)'; | |
inputEl.style.textAlign = 'left'; | |
document.getElementById('everything').appendChild(inputEl); | |
const origCreateSidebars = window.createSidebars; | |
window.createSidebars = function() { | |
origCreateSidebars(); | |
const sidebar = window.sidebars[0]; | |
sidebar.show = function() { | |
if (this.modes.includes(mode)) { | |
ctx.lineWidth = lineWW; | |
ctx.fillStyle = boolSettings['Dark Mode'] ? '#404040' : '#D0D0D0'; | |
ctx.strokeStyle = boolSettings['Dark Mode'] ? '#202020' : '#808080'; | |
ctx.fillRect(this.x - sidebarW / 2, gridY, sidebarW, gridSL + 35); | |
ctx.strokeRect(this.x - sidebarW / 2, gridY, sidebarW, gridSL + 35); | |
for (var a = 0; a < this.sections.length; a++) | |
this.sections[a].show(); | |
for (var a = 0; a < this.buttons.length; a++) | |
this.buttons[a].show(); | |
} | |
} | |
const cosmeticToolsButton = sidebars[0].buttons.find(b => b.id === 'CosmeticTools'); | |
const solutionToolsX = cosmeticToolsButton.x; | |
const solutionToolsY = cosmeticToolsButton.y + buttonSH + buttonGap; | |
const solutionToolsButton = new button(solutionToolsX, solutionToolsY, buttonW, buttonSH, ['Setting'], 'SolutionTools', 'Solution Tools', true, true); | |
sidebar.buttons.push(solutionToolsButton); | |
solutionToolsButton.click = function() { | |
if (!this.hovering() || !this.modes.includes(window.mode)) { | |
return; | |
} | |
togglePopup('SolutionTools'); | |
} | |
const baseX = gridX - (sidebarDist + sidebarW / 2) + sidebarW; | |
const baseY = gridY + gridSL + 35 - ((buttonSH * numSolutionToolsButtons) + (buttonGap * (numSolutionToolsButtons - 1)) + buttonMargin); | |
const successMessageButton = new button(baseX, baseY + (buttonSH + buttonGap) * 0, buttonW, buttonSH, ['SolutionTools'], 'SuccessMessage', 'Success Message'); | |
const saveSolutionButton = new button(baseX, baseY + (buttonSH + buttonGap) * 1, buttonW, buttonSH, ['SolutionTools'], 'SaveSolution', 'Save Solution'); | |
const viewSolutionButton = new button(baseX, baseY + (buttonSH + buttonGap) * 2, buttonW, buttonSH, ['SolutionTools'], 'ViewSolution', 'View Solution'); | |
const clearSolutionButton = new button(baseX, baseY + (buttonSH + buttonGap) * 3, buttonW, buttonSH, ['SolutionTools'], 'ClearSolution', 'Clear Solution'); | |
successMessageButton.click = function() { | |
if (!this.hovering()) { | |
return; | |
} | |
togglePopup('SuccessMessage'); | |
return true; | |
} | |
saveSolutionButton.click = function() { | |
if (!this.hovering()) { | |
return; | |
} | |
let solution = []; | |
for (let i = 0; i < size; i++) { | |
for (let j = 0; j < size; j++) { | |
let value = window.grid[i][j].value; | |
if (value == 0) { | |
value = '.'; | |
} | |
solution.push(value); | |
} | |
} | |
window.grid.solution = solution; | |
return true; | |
} | |
viewSolutionButton.click = function() { | |
if (!this.hovering()) { | |
return; | |
} | |
if (window.grid.solution) { | |
const solution = window.grid.solution; | |
for (let i = 0; i < size; i++) { | |
for (let j = 0; j < size; j++) { | |
let value = solution[i * size + j]; | |
if (value == '.') { | |
value = 0; | |
} | |
window.grid[i][j].value = value; | |
} | |
} | |
} | |
return true; | |
} | |
clearSolutionButton.click = function() { | |
if (!this.hovering()) { | |
return; | |
} | |
delete window.grid.solution; | |
return true; | |
} | |
sidebar.buttons.push(successMessageButton); | |
sidebar.buttons.push(saveSolutionButton); | |
sidebar.buttons.push(viewSolutionButton); | |
sidebar.buttons.push(clearSolutionButton); | |
} | |
const saveSuccessMessageButton = new button(canvas.width / 2, canvas.height / 2 + popups[cID('SuccessMessage')].h / 2 - buttonGap - buttonSH, buttonW, buttonSH, ['SuccessMessage'], 'SaveSuccessMessage', 'Save'); | |
saveSuccessMessageButton.click = function() { | |
if (!this.hovering()) { | |
return; | |
} | |
if (isValidSuccessMessage(inputEl.value)) { | |
window.successMessage = inputEl.value; | |
} | |
closePopups(); | |
return true; | |
} | |
buttons.push(saveSuccessMessageButton); | |
const origTogglePopup = window.togglePopup; | |
window.togglePopup = function(title) { | |
origTogglePopup(title); | |
if (title === 'SuccessMessage') { | |
inputEl.style.display = 'block'; | |
if (window.successMessage) { | |
inputEl.value = window.successMessage; | |
} | |
setTimeout(() => inputEl.focus(), 50); | |
} | |
} | |
const origClosePopups = window.closePopups; | |
window.closePopups = function() { | |
origClosePopups(); | |
inputEl.value = ''; | |
inputEl.style.display = 'none'; | |
} | |
const origDrawPopups = window.drawPopups; | |
window.drawPopups = function(overlapSidebars) { | |
origDrawPopups(overlapSidebars); | |
if (overlapSidebars) { | |
if (popup === 'SuccessMessage') { | |
const box = popups[cID(popup)]; | |
ctx.lineWidth = lineWW; | |
ctx.fillStyle = boolSettings['Dark Mode'] ? '#404040' : '#E0E0E0'; | |
ctx.strokeStyle = '#000000'; | |
ctx.fillRect(canvas.width/2 - box.w/2, canvas.height/2 - box.h/2, box.w, 90); | |
ctx.strokeRect(canvas.width/2 - box.w/2, canvas.height/2 - box.h/2, box.w, 90); | |
ctx.fillStyle = boolSettings['Dark Mode'] ? '#F0F0F0' : '#000000'; | |
ctx.font = '50px Arial'; | |
ctx.fillText('Success Message', canvas.width/2, canvas.height/2 - box.h/2 + 64); | |
} else { | |
return; | |
} | |
} | |
if (popup === "SolutionTools") { | |
ctx.lineWidth = lineWW; | |
ctx.fillStyle = boolSettings['Dark Mode'] ? '#404040' : '#D0D0D0'; | |
ctx.strokeStyle = boolSettings['Dark Mode'] ? '#202020' : '#808080'; | |
ctx.fillRect(gridX - sidebarDist, gridY + gridSL + 35, sidebarW, -((buttonSH * numSolutionToolsButtons) + (buttonGap * (numSolutionToolsButtons - 1)) + (buttonMargin * 2))); | |
ctx.strokeRect(gridX - sidebarDist, gridY + gridSL + 35, sidebarW, -((buttonSH * numSolutionToolsButtons) + (buttonGap * (numSolutionToolsButtons - 1)) + (buttonMargin * 2))); | |
} | |
} | |
const origMouseMove = document.onmousemove; | |
document.onmousemove = function(e) { | |
origMouseMove(e); | |
if (!testPaused() && !disableInputs) { | |
if (sidebars.length) { | |
var hoveredButton = sidebars[sidebars.findIndex(a => a.title === 'Constraints')].buttons[sidebars[sidebars.findIndex(a => a.title === 'Constraints')].buttons.findIndex(a => a.id === 'SolutionTools')]; | |
if (popup === 'SolutionTools' && (mouseX < hoveredButton.x - hoveredButton.w / 2 - buttonMargin || mouseX > hoveredButton.x + hoveredButton.w / 2 + buttonMargin || mouseY < hoveredButton.y - buttonMargin || mouseY > hoveredButton.y + buttonSH + buttonMargin) && | |
(mouseX < gridX - sidebarDist || mouseX > gridX - sidebarDist + sidebarW + (buttonGap + buttonSH) * 2 || mouseY < gridY + gridSL + 35 - ((buttonSH * numSolutionToolsButtons) + (buttonGap * (numSolutionToolsButtons - 1)) + (buttonMargin * 2)) || mouseY > gridY + gridSL + 35)) { | |
closePopups(); | |
} | |
} | |
} | |
} | |
const origCandidatePossibleInCell = window.candidatePossibleInCell; | |
window.candidatePossibleInCell = function(n, cell, options) { | |
if (n < 1 || n > size) | |
return false; | |
if (!options) | |
options = {}; | |
if (!options.bruteForce && cell.value) | |
return cell.value === n; | |
// If there is no solution, then all candidates are possible | |
if (!window.grid.solution) { | |
return origCandidatePossibleInCell(n, cell, options); | |
} | |
// Cells that are set to 0 can be ignored | |
const solutionVal = window.grid.solution[cell.i * size + cell.j]; | |
if (solutionVal === 0) { | |
return origCandidatePossibleInCell(n, cell, options); | |
} | |
// Only use the solution if all cells are filled | |
var allFilled = true; | |
for (var i = 0; i < size; i++) { | |
for (var j = 0; j < size; j++) { | |
if (grid[i][j].value === 0) { | |
allFilled = false; | |
break; | |
} | |
} | |
} | |
if (!allFilled) { | |
return origCandidatePossibleInCell(n, cell, options); | |
} | |
// Check against the solution | |
return solutionVal === n; | |
} | |
if (window.sidebars && window.sidebars.length && window.boolConstraints) { | |
createSidebars(); | |
onInputEnd(); | |
} | |
if (window.boolConstraints) { | |
let prevButtons = buttons.splice(0, buttons.length); | |
window.onload(); | |
buttons.splice(0, buttons.length); | |
for (let i = 0; i < prevButtons.length; i++) { | |
buttons.push(prevButtons[i]); | |
} | |
} | |
} | |
let intervalId = setInterval(() => { | |
if (typeof exportPuzzle === 'undefined' || | |
typeof importPuzzle === 'undefined' || | |
typeof closePopups === 'undefined' || | |
typeof togglePopup === 'undefined' || | |
typeof createSidebars === 'undefined' || | |
typeof drawPopups === 'undefined' || | |
typeof candidatePossibleInCell === 'undefined') { | |
return; | |
} | |
clearInterval(intervalId); | |
doShim(); | |
importPuzzle(location.search.substring(importString.length, location.search.length), true); | |
}, 16); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment