Skip to content

Instantly share code, notes, and snippets.

@Dan-Q
Last active April 4, 2024 20:31
Show Gist options
  • Star 19 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save Dan-Q/e9bfe5c2ca4b13fae4994c5e84685761 to your computer and use it in GitHub Desktop.
Save Dan-Q/e9bfe5c2ca4b13fae4994c5e84685761 to your computer and use it in GitHub Desktop.
Experimental under-development code to streamline Jigidi solving.

Jigidi Helper (Experimental)

Replaces the image of a jigsaw puzzle with a predictable, (theoretically) easier-to-solve design.

Partially-solved jigsaw, post-tampering.

How to use

  1. Begin a Jigidi puzzle.
  2. Run the code in your browser debug console.
  3. Restart the puzzle by pressing the Restart button in the Jigidi sidebar.
  4. The puzzle will be replaced with a predictable patterned one.

How does this help?

The predictable pattern has a number of features that make it easier to solve that most other jigsaws:

  • Each piece has two numbers printed on it: the first is the row in which it belongs, the second is the column, making it possible to identify the exact location that a given piece belongs in.
  • Each column is a different colour, streamlining presorting.
  • Rows within a column alternate between lighter and darker variants ("striping").
  • Stripes of alternating thickness and a cycle of colours form long horizonal and vertical bands across the image, streamlining identification.
  • No information is sent back to the server to indicate that the puzzle has been tampered with.
window.jColors = ['red', 'blue', 'brown', 'orange', 'yellow', 'pink', 'lightblue', 'lightgreen', 'lightgray'];
window.lColors = ['white', 'black', 'purple', 'darkgray', '#009'];
window.lWidths = [5, 10, 20];
window.jCols = parseInt(document.getElementById('info-creator').innerText.match(/(\d+)×/)[1]);
window.jC = 0;
CanvasRenderingContext2D.prototype.putImageData = function(imageData, dx, dy){
const col = window.jC % window.jCols;
const row = Math.floor(window.jC / window.jCols);
this.fillStyle = window.jColors[col % window.jColors.length];
this.fillRect(-1000,-1000,2000,2000);
if(0 == (row % 2)){ this.fillStyle = '#ffffff33'; this.fillRect(-1000,-1000,2000,2000); }
this.fillStyle = window.lColors[row % window.lColors.length];
this.fillRect(-1000, -35, 2000, window.lWidths[row % window.lWidths.length]);
this.fillStyle = window.lColors[col % window.lColors.length];
this.fillRect(-35, -1000, window.lWidths[col % window.lWidths.length], 2000);
this.font = 'bold 14px sans-serif';
this.fillStyle = 'black';
this.fillText(`${row+1},${col+1}`, -5, 0);
window.jC++;
}
@tintin49
Copy link

tintin49 commented Apr 5, 2023

Hi Dan

Congratulation for this code.

Now, do you think that should be possible to build automatically the puzzle ?

Thanks in advance

Didier

@Dan-Q
Copy link
Author

Dan-Q commented Apr 5, 2023

Starting from here? No. But others have done pretty-well at cracking that problem already, though they've not published it publicly on the open Internet. My aim is to find solutions that simplify the solving of jigsaws, because these approaches seem to survive longer than entirely-automated solutions which quickly get detected and blocked.

(In fact, it's probably that openly-published solutions get blocked that makes people not publish their fully-automated solutions in places where they can be easily found! The approach above isn't even the one I use - I use one which "stripes" one column at a time, turning the rest of the jigsaw white, which is even faster to visually process than striping the entire jigsaw, for example!)

@tintin49
Copy link

tintin49 commented Apr 5, 2023

Hi Dan

Could you provide me your software please ?

Thanks in advance

Kind regards

Didier

@Gabrielhj17
Copy link

Gabrielhj17 commented May 1, 2023

Hello all,

I have added a fork to this repo so that this code can be added as a chrome bookmark and run much more easily.
The fork can be found here: https://gist.github.com/Gabrielhj17/3b442bfed270e6762b819b1478f574ee

I hope this helps,

Gabriel

@rambii
Copy link

rambii commented May 29, 2023

In case the row and col numbers are not showing anymore, I adjusted it like this:

    this.fillText(`${row+1},${col+1}  --  `.repeat(10), 0, 25);
    this.fillText(`${row+1},${col+1}  --  `.repeat(10), 0, 75);

@rambii
Copy link

rambii commented May 30, 2023

Some adjustments I found useful:

window.jColors = ['red', 'seagreen', 'gold', 'mediumvioletred', 'lime', 'blue', 'dodgerblue'];
window.lColors = ['white', 'black', 'purple', 'darkgray', '#009'];
window.lWidths = [3, 8, 13];
window.jCols = parseInt(document.getElementById('info-creator').innerText.match(/(\d+)×/)[1]);
window.jC = 0;
CanvasRenderingContext2D.prototype.putImageData = function (imageData, dx, dy) {
    const col = window.jC % window.jCols;
    const row = Math.floor(window.jC / window.jCols);
    this.fillStyle = window.jColors[col % window.jColors.length];
    this.fillRect(-1000, -1000, 2000, 2000);
    if (0 == (row % 2)) { this.fillStyle = '#ffffff33'; this.fillRect(-1000, -1000, 2000, 2000); }	
    this.fillStyle = window.lColors[row % window.lColors.length];
    // adjust position of vertical line - won't be aligned but still help differentiate columns with the same color
    this.fillRect(-1000, 35, 2000, window.lWidths[row % window.lWidths.length]);
    this.fillStyle = window.lColors[col % window.lColors.length];
    this.fillRect(35, -1000, window.lWidths[col % window.lWidths.length], 2000);
    // little smaller font so that the numbers are less likely to be cut of on the edge of the tile
    this.font = 'bold 11px sans-serif';

    // print the col first since i sort the cols first by color and it makes it more readable
    // print it a few times since not sure where the center of the tile is 
    // print it in black and white for better contrast with darker and lighter colors (and simplicity ;) )
    this.fillStyle = 'black';
    this.fillText(`${col + 1},${row + 1}  --  `.repeat(10), 0, 25);
    this.fillText(`${col + 1},${row + 1}  --  `.repeat(10), 0, 50);
    this.fillText(`${col + 1},${row + 1}  --  `.repeat(10), 0, 75);

    this.fillStyle = 'white';
    this.fillText(`${col + 1},${row + 1}  --  `.repeat(10), 0, 38);
    this.fillText(`${col + 1},${row + 1}  --  `.repeat(10), 0, 63);
    this.fillText(`${col + 1},${row + 1}  --  `.repeat(10), 0, 88);

    window.jC++;	
}

@letsjigidi
Copy link

Is it possible to make it a Tampermonkey script please?

@Dan-Q
Copy link
Author

Dan-Q commented Jun 1, 2023

@letsjigidi this alternative tool I also made already is a userscript, if that helps. @tintin49 it's the one you're asking for, too, if that matters!

@letsjigidi
Copy link

letsjigidi commented Jun 1, 2023 via email

@Dan-Q
Copy link
Author

Dan-Q commented Jun 1, 2023

@letsjigidi I'm the original author of the blog posts about this tool. I have no interest in Jigidi except where I have to solve them in order to find geocache coordinates. You won't hear any solution from me that doesn't produce the popup at the end!

@rteitelman
Copy link

Dan Q's 20 lines of code stopped working for me a few days ago, so something changed. I just tried rambii's code posted 4 days ago and it works! Nice. I would love it if the numbers could be made larger so I can read them, but I suspect that's not possible or it would have been done already.

@rambii
Copy link

rambii commented Jun 9, 2023

@rteitelman
You can make the numbers larger. Change this line:
this.font = 'bold 11px sans-serif'; to this.font = 'bold 18px sans-serif'; or something that works for you.

But I found larger numbers are more likely to be cut off and be unreadable. That's why I chose a smaller font size.

@medovy
Copy link

medovy commented Aug 8, 2023

// ==UserScript==
// @name Jigidi Magic Stripes
// @namespace me.danq.com.jigidi.magicstripes
// @match https://www.jigidi.com/solve/*
// @grant GM_getValue
// @grant GM_setValue
// @Version 1.0
// @author Dan Q https://danq.me
// @description 23/03/2023, 14:32:30
// ==/UserScript==

/*
RainbowVis-JS ()Eclipse Public License - v 1.0) | https://github.com/anomal/RainbowVis-JS/blob/master/rainbowvis.js
*/

function Rainbow(){"use strict";var gradients=null;var minNum=0;var maxNum=100;var colours=['ff0000','ffff00','00ff00','0000ff'];setColours(colours);function setColours(spectrum){if(spectrum.length<2){throw new Error('Rainbow must have two or more colours.')}else{var increment=(maxNum-minNum)/(spectrum.length-1);var firstGradient=new ColourGradient();firstGradient.setGradient(spectrum[0],spectrum[1]);firstGradient.setNumberRange(minNum,minNum+increment);gradients=[firstGradient];for(var i=1;i<spectrum.length-1;i++){var colourGradient=new ColourGradient();colourGradient.setGradient(spectrum[i],spectrum[i+1]);colourGradient.setNumberRange(minNum+incrementi,minNum+increment(i+1));gradients[i]=colourGradient}
colours=spectrum}}
this.setSpectrum=function(){setColours(arguments);return this}
this.setSpectrumByArray=function(array){setColours(array);return this}
this.colourAt=function(number){if(isNaN(number)){throw new TypeError(number+' is not a number')}else if(gradients.length===1){return gradients[0].colourAt(number)}else{var segment=(maxNum-minNum)/(gradients.length);var index=Math.min(Math.floor((Math.max(number,minNum)-minNum)/segment),gradients.length-1);return gradients[index].colourAt(number)}}
this.colorAt=this.colourAt;this.setNumberRange=function(minNumber,maxNumber){if(maxNumber>minNumber){minNum=minNumber;maxNum=maxNumber;setColours(colours)}else{throw new RangeError('maxNumber ('+maxNumber+') is not greater than minNumber ('+minNumber+')')}
return this}}
function ColourGradient(){"use strict";var startColour='ff0000';var endColour='0000ff';var minNum=0;var maxNum=100;this.setGradient=function(colourStart,colourEnd){startColour=getHexColour(colourStart);endColour=getHexColour(colourEnd)}
this.setNumberRange=function(minNumber,maxNumber){if(maxNumber>minNumber){minNum=minNumber;maxNum=maxNumber}else{throw new RangeError('maxNumber ('+maxNumber+') is not greater than minNumber ('+minNumber+')')}}
this.colourAt=function(number){return calcHex(number,startColour.substring(0,2),endColour.substring(0,2))+calcHex(number,startColour.substring(2,4),endColour.substring(2,4))+calcHex(number,startColour.substring(4,6),endColour.substring(4,6))}
function calcHex(number,channelStart_Base16,channelEnd_Base16){var num=number;if(num<minNum){num=minNum}
if(num>maxNum){num=maxNum}
var numRange=maxNum-minNum;var cStart_Base10=parseInt(channelStart_Base16,16);var cEnd_Base10=parseInt(channelEnd_Base16,16);var cPerUnit=(cEnd_Base10-cStart_Base10)/numRange;var c_Base10=Math.round(cPerUnit*(num-minNum)+cStart_Base10);return formatHex(c_Base10.toString(16))}
function formatHex(hex){if(hex.length===1){return'0'+hex}else{return hex}}
function isHexColour(string){var regex=/^#?[0-9a-fA-F]{6}$/i;return regex.test(string)}
function getHexColour(string){if(isHexColour(string)){return string.substring(string.length-6,string.length)}else{var name=string.toLowerCase();if(colourNames.hasOwnProperty(name)){return colourNames[name]}
throw new Error(string+' is not a valid colour.')}}
var colourNames={aliceblue:"F0F8FF",antiquewhite:"FAEBD7",aqua:"00FFFF",aquamarine:"7FFFD4",azure:"F0FFFF",beige:"F5F5DC",bisque:"FFE4C4",black:"000000",blanchedalmond:"FFEBCD",blue:"0000FF",blueviolet:"8A2BE2",brown:"A52A2A",burlywood:"DEB887",cadetblue:"5F9EA0",chartreuse:"7FFF00",chocolate:"D2691E",coral:"FF7F50",cornflowerblue:"6495ED",cornsilk:"FFF8DC",crimson:"DC143C",cyan:"00FFFF",darkblue:"00008B",darkcyan:"008B8B",darkgoldenrod:"B8860B",darkgray:"A9A9A9",darkgreen:"006400",darkgrey:"A9A9A9",darkkhaki:"BDB76B",darkmagenta:"8B008B",darkolivegreen:"556B2F",darkorange:"FF8C00",darkorchid:"9932CC",darkred:"8B0000",darksalmon:"E9967A",darkseagreen:"8FBC8F",darkslateblue:"483D8B",darkslategray:"2F4F4F",darkslategrey:"2F4F4F",darkturquoise:"00CED1",darkviolet:"9400D3",deeppink:"FF1493",deepskyblue:"00BFFF",dimgray:"696969",dimgrey:"696969",dodgerblue:"1E90FF",firebrick:"B22222",floralwhite:"FFFAF0",forestgreen:"228B22",fuchsia:"FF00FF",gainsboro:"DCDCDC",ghostwhite:"F8F8FF",gold:"FFD700",goldenrod:"DAA520",gray:"808080",green:"008000",greenyellow:"ADFF2F",grey:"808080",honeydew:"F0FFF0",hotpink:"FF69B4",indianred:"CD5C5C",indigo:"4B0082",ivory:"FFFFF0",khaki:"F0E68C",lavender:"E6E6FA",lavenderblush:"FFF0F5",lawngreen:"7CFC00",lemonchiffon:"FFFACD",lightblue:"ADD8E6",lightcoral:"F08080",lightcyan:"E0FFFF",lightgoldenrodyellow:"FAFAD2",lightgray:"D3D3D3",lightgreen:"90EE90",lightgrey:"D3D3D3",lightpink:"FFB6C1",lightsalmon:"FFA07A",lightseagreen:"20B2AA",lightskyblue:"87CEFA",lightslategray:"778899",lightslategrey:"778899",lightsteelblue:"B0C4DE",lightyellow:"FFFFE0",lime:"00FF00",limegreen:"32CD32",linen:"FAF0E6",magenta:"FF00FF",maroon:"800000",mediumaquamarine:"66CDAA",mediumblue:"0000CD",mediumorchid:"BA55D3",mediumpurple:"9370DB",mediumseagreen:"3CB371",mediumslateblue:"7B68EE",mediumspringgreen:"00FA9A",mediumturquoise:"48D1CC",mediumvioletred:"C71585",midnightblue:"191970",mintcream:"F5FFFA",mistyrose:"FFE4E1",moccasin:"FFE4B5",navajowhite:"FFDEAD",navy:"000080",oldlace:"FDF5E6",olive:"808000",olivedrab:"6B8E23",orange:"FFA500",orangered:"FF4500",orchid:"DA70D6",palegoldenrod:"EEE8AA",palegreen:"98FB98",paleturquoise:"AFEEEE",palevioletred:"DB7093",papayawhip:"FFEFD5",peachpuff:"FFDAB9",peru:"CD853F",pink:"FFC0CB",plum:"DDA0DD",powderblue:"B0E0E6",purple:"800080",red:"FF0000",rosybrown:"BC8F8F",royalblue:"4169E1",saddlebrown:"8B4513",salmon:"FA8072",sandybrown:"F4A460",seagreen:"2E8B57",seashell:"FFF5EE",sienna:"A0522D",silver:"C0C0C0",skyblue:"87CEEB",slateblue:"6A5ACD",slategray:"708090",slategrey:"708090",snow:"FFFAFA",springgreen:"00FF7F",steelblue:"4682B4",tan:"D2B48C",teal:"008080",thistle:"D8BFD8",tomato:"FF6347",turquoise:"40E0D0",violet:"EE82EE",wheat:"F5DEB3",white:"FFFFFF",whitesmoke:"F5F5F5",yellow:"FFFF00",yellowgreen:"9ACD32"}}

// Are we logged-in?
const loggedIn = !document.querySelector('account-status.guest');
console.log(Magic Stripes: loggedIn=${loggedIn ? 'true' : 'false'});

// Prepare Magic Stripes UI
const jigidiMagicStripes = document.createElement('div');
jigidiMagicStripes.id = 'jigidi-magic-stripes';

// Inject Magic Stripes UI
const creatorElem = document.getElementById('info-creator');
creatorElem.before(jigidiMagicStripes);

if(!loggedIn){
// Magic Stripes UI only works if logged in (we need Jigidi's state saving through page refreshes)
jigidiMagicStripes.innerHTML = '

Magic Stripes
Disabled. Sign in to Jigidi to enable.

';
} else {
// Logged in: jigsaw ID and settings for this jigsaw, enumerate lengths of jigsaw dimensions, and update Magic Stripes UI
const jigsawId = window.location.href.match(/solve/(\w+)//)[1];
const magicStripesSettings = GM_getValue(magicStripesSettings_${jigsawId}, { col: 0 });
const jDimensions = creatorElem.innerText.match(/(\d+)×(\d+)/);
const jCols = parseInt(jDimensions[1]);
const jRows = parseInt(jDimensions[2]);
console.log(Magic Stripes: jCols=${jCols} jRows=${jRows});
jigidiMagicStripes.innerHTML = <p> <strong>Magic Stripes</strong><br> <label for="magicStripesCol">Help with column? (0 to disable)</label><br> <input type="number" id="magicStripesCol" value="${magicStripesSettings.col}" min="0" max="${jCols}" style="width: 4em;"> <button id="magicStripesGo">Go</button> <button id="magicStripesPlusOne">+1 and Go</button> </p>;
const magicStripesCol = document.getElementById('magicStripesCol');
const magicStripesGo = document.getElementById('magicStripesGo');
magicStripesGo.addEventListener('click', ()=>{
magicStripesSettings.col = parseInt(magicStripesCol.value);
GM_setValue(magicStripesSettings_${jigsawId}, magicStripesSettings);
window.location.reload();
});
document.getElementById('magicStripesPlusOne').addEventListener('click', ()=>{
magicStripesCol.value = (parseInt(magicStripesCol.value) + 1) % (jCols + 1);
magicStripesGo.dispatchEvent(new Event('click'));
});

// Generate spectrum to use:
var rainbow = new Rainbow();
let jColors = [];
rainbow.setNumberRange(1, jRows);
rainbow.setSpectrum('red', 'violet');
for(var i = 1; i <= jRows; i++) {
jColors.push(#${rainbow.colourAt(i)});
}
let jC = 0;

const targetCol = parseInt(magicStripesSettings.col);
if(targetCol > 0) {
// Override putImageData with a manipulated version for THIS page load
CanvasRenderingContext2D.prototype.putImageData = function(imageData, dx, dy){
console.log('Magic Stripes: putImageData');
const targetCol = parseInt(magicStripesSettings.col);
const col = jC % jCols;
const row = Math.floor(jC / jCols);
if((col + 1) === targetCol) {
// Target column: color and number
this.fillStyle = jColors[row % jColors.length];
this.fillRect(-1000,-1000,2000,2000);
this.font = 'bold 30px sans-serif';
this.fillStyle = 'black';
this.fillText(${col + 1},${row + 1} -- .repeat(10), 0, 25);
this.fillText(${col + 1},${row + 1} -- .repeat(10), 0, 50);
this.fillText(${col + 1},${row + 1} -- .repeat(10), 0, 75);
} else if ((col + 2) === targetCol) {
// Previous column: lightly color, don't number
this.fillStyle = jColors[row % jColors.length];
this.fillRect(-1000,-1000,2000,2000);
this.fillStyle = '#ffffffbb';
this.fillRect(-1000,-1000,2000,2000);
this.font = 'bold 30px sans-serif';
this.fillStyle = 'black';
this.fillText(${col + 1},${row + 1} -- .repeat(10), 0, 38);
this.fillText(${col + 1},${row + 1} -- .repeat(10), 0, 63);
this.fillText(${col + 1},${row + 1} -- .repeat(10), 0, 88);
} else {
// Other columns: white-out
this.fillStyle = '#ffffffdd';
this.fillRect(-1000,-1000,2000,2000);
}
jC++;
}
}
}

In this code, how do I remove the first number before the decimal point from the puzzles?
So that 2,10 becomes just 10?
2023-08-08_152801

@NeilandJen
Copy link

Hi Guys,
First off Happy New Year - when trying Rambii code I keep getting an error on my console on Google Chrome that says?
VM1242:1 Uncaught ReferenceError: mokole is not defined
at :1:62

Any ideas?

@rambii
Copy link

rambii commented Jan 2, 2024

@NeilandJen
Seems like the hyperlink in the comment I included in the code snippet got interpreted in a wrong way.
I updated the snippet without the comments (the lines starting with // ). You can copy it again and it should work.

@suyrulola
Copy link

suyrulola commented Feb 20, 2024

Hi @Dan-Q, I have already partially completed the puzzle. Is there a way to have the script start from there instead of starting from scratch?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment