Skip to content

Instantly share code, notes, and snippets.

@dclamage
Last active January 29, 2023 22:03
Show Gist options
  • Save dclamage/63cc6241752dbeb1214328f4ae655cf7 to your computer and use it in GitHub Desktop.
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.
// ==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