Creating bookmarklets
A bookmarklet in a short piece of Javascript code that you store in your bookmarks menu. When you select the bookmarklet from the menu, the Javascript code is run in the context of the page you have open in your browser.
This page describes how you might write your own bookmarklet that extracts a string of 81 digits from the page and then opens the Sudoku Exchange solver app with those digits loaded in the grid.
Starting simple
The tricky part is building up the string of 81 digits, so for now, we’ll assume you’ve already done that. Here’s some Javascript code that has a digit string in a variable and loads it on SudokuExchange.com:
s = '000001230123008040804007650765000000000000000000000123012300804080400765076500000'; location.href = 'https://sudokuexchange.com/play/?s='+s;
To turn that into a bookmarklet, we need to:
- remove all the newlines and unnecessary spaces
- put
'javascript:'
on the begining to make it into a URL
Which gives us this single long line that we can bookmark: Load a hard-coded digit string
javascript:s='000001230123008040804007650765000000000000000000000123012300804080400765076500000';location.href='https://sudokuexchange.com/play/?s='+s;
Opening a new tab
One problem with our starting example is that it replaces the page you’re viewing with the Sudoku Exchange app. If, instead, we want the app to open in a new tab, we could replace the second line:
s = '000001230123008040804007650765000000000000000000000123012300804080400765076500000'; window.open('https://sudokuexchange.com/play/?s='+s, '_blank');
While that does successfully open the app in a new window,
unfortunately it also replaces the page we were viewing with the value
returned by the last statement. In our code the last statement returns a
Window
object, so the page we were viewing get’s replaced
with this short piece of text: [object Window]
. To make
matters worse, you can’t even use the back button to re-display the
page.
To avoid that unfortunate side-effect, we need to ensure that there is
no value returned by the last statement. One way to achieve that is by
wrapping the statement with void(
... )
:
s = '000001230123008040804007650765000000000000000000000123012300804080400765076500000'; void( window.open('https://sudokuexchange.com/play/?s='+s, '_blank') );
Once again we can reformat our code as a bookmarklet: Load a hard-coded digit string in a new window
javascript:s='000001230123008040804007650765000000000000000000000123012300804080400765076500000';void(window.open('https://sudokuexchange.com/play/?s='+s, '_blank'));
Grabbing selected text
Instead of hard-coding the digit string, a more useful bookmarklet
might use the contents of any selected text in the page. The user could
use their mouse to highlight a string of 81 digits in the page and then
select the bookmarklet from their menu. This code uses the Window.getSelection()
method to grab the current selection object and then the Selection.toString()
method to turn that into a plain text string:
s = window.getSelection().toString(); void( window.open('https://sudokuexchange.com/play/?s='+s, '_blank') );
As a bookmarklet: Load selected text
javascript:s=window.getSelection().toString();void(window.open('https://sudokuexchange.com/play/?s='+s, '_blank'));
Making our code more robust
When the user highlights text with their mouse it is very easy to
accidentally include some whitespace characters too. We can use the String.trim()
method to remove spaces from the beginning and end of the string.
s = window.getSelection().toString().trim();
If the user doesn’t select enough digits, or if they include some
non-digit characters in the selection, it would be nice to catch the
error and display a message rather than opening a broken puzzle in a new
tab. We can use pattern matching with the String.match()
method to check that the selected text contains exactly 81 digits and
that they are not all zeros:
s = window.getSelection().toString().trim(); if(s.match(/^[0-9]{81}$/) && s.match(/[1-9]/)) { alert('Selected text does not look like a Sudoku puzzle'); } else { void( window.open('https://sudokuexchange.com/play/?s='+s, '_blank') ); }
Sometimes sudoku puzzle strings are represented with dots (‘.’)
instead of zeros (‘0’) to represent blank cells. We can use the String.replace()
method to turn each ‘.’ into a ‘0’.
s = window.getSelection().toString().trim().replace(/[.]/g, '0'); if(s.match(/^[0-9]{81}$/) && s.match(/[1-9]/)) { alert('Selected text does not look like a Sudoku puzzle'); } else { void( window.open('https://sudokuexchange.com/play/?s='+s, '_blank') ); }
To make our code a bit shorter, we can replace the if ...
else
block with a ternary
operator:
s = window.getSelection().toString().trim().replace(/[.]/g, '0'); void( (s.match(/^[0-9]{81}$/) && s.match(/[1-9]/)) ? window.open('https://sudokuexchange.com/play/?s='+s, '_blank') : alert('Selected text does not look like a Sudoku puzzle') );
As a bookmarklet: Load 81 digit string into SudokuExchange.com
javascript:s=window.getSelection().toString().trim().replace(/[.]/g, '0');void((s.match(/^[0-9]{81}$/)&&s.match(/[1-9]/))?window.open('https://sudokuexchange.com/play/?s='+s,'_blank'):alert('Selected text does not look like a Sudoku puzzle'));
If you save this final version to to your bookmarks menu, you can try it out on the Patterns Game Results page.
Scraping Sudoku puzzles
In order to scrape a page on a puzzle site, you’ll need to explore the
structure of the page using browser tools like the DOM (document object
model) inspector. Then you might use a DOM method like Document.querySelectorAll()
to loop through all elements with a particular class, or all children of
an element with a particular "id".
As you loop through each element, you would replace blank cells with
‘0’ and cells containing a given digit with ‘1’ through ‘9’. The value for
the given digit might come from the element’s text content, or an attribute, or even the filename in the
src
attribute of an <image>
element.
Refer to the provided bookmarklets for specific code examples. From this point it’s over to you, your creativity and your problem solving skills.