@Florian F's point aside, there's an important rule in the site's 0hn0 script source code:
Blue dots can see others in their own row and column
It doesn't say only the numbered dots can see others. There's also an error message to the same effect (it showed up when I intentionally painted an isolated dot blue to test it out):
A blue dot should always see at least one other
A dot completely isolated from all the numbers is technically "safe" to paint either red or blue. If there's a rule to paint such dots always blue, that also provides a unique solution, but that can lead to a dot not seeing any blue dots. If multiple empty blue dots form an isolated "island", there should have been a number or you just paint them all red.
If you have a suspicious dot that's not meant to be surrounded by red dots, its being painted blue would require being isolated with red squares blocking it off, and the last paragraph would be the case anyway.
PS.
https://github.com/Techdojo/0hn0/blob/master/js/tile.js
function isLockedIn() {
if (!isDot()) return false;
for (var dir in Directions) {
if (self.move(dir) && !self.move(dir).isWall())
return false;
}
return true;
}
All the tiles can fit four possible categories: numbers (numbered blue tiles), dots (blue without numbers), walls (red) or unknowns (grey). LockedIn was the name of the error I experienced. If I left a locked-in tile alone, "this one should be easy" (MustBeWall) shows up instead:
Only dots can be examined for the LockedIn status. Then you move to the adjacent tiles and check them. If it's not surrounded by walls from all directions, the original dot isn't locked in. But what if there's a numberless island of otherwise "safe" tiles surrounded by walls? That's how the MustBeWall code works:
https://github.com/Techdojo/0hn0/blob/master/js/grid.js
function solve(silent, hintMode) {
var tryAgain = true;
var attempts = 0;
var hintTile = null;
var pool = tiles;
// for stepByStep solving, randomize the pool
if (hintMode) {
pool = tiles.concat();
Utils.shuffle(pool);
}
while (tryAgain && attempts++ < 99) {
//console.log('solving, attempt ', attempts);
tryAgain = false;
if (isDone()) {
if (silent)
clearTimeout(renderTOH);
return true;
}
// first pass collection
for (var i=0; i<pool.length; i++) {
pool[i].info = pool[i].collect();
}
// second pass collection, now we have full info
for (var i=0; i<pool.length; i++) {
var tile = pool[i],
info = tile.collect(tile.info);
// dots with no empty tiles in its paths can be fixed
if (tile.isDot() && !info.unknownsAround && !hintMode) {
tile.number(info.numberCount, true);
tryAgain = HintType.NumberCanBeEntered;
break;
}
// if a number has unknowns around, perhaps we can fill those unknowns
if (tile.isNumber() && info.unknownsAround) {
// if its number is reached, close its paths by walls
if (info.numberReached) {
if (hintMode)
hintTile = tile;
else
tile.close();
tryAgain = HintType.ValueReached;
break;
}
// if a tile has only one direction to go, fill the first unknown there with a dot and retry
if (info.singlePossibleDirection) {
if (hintMode)
hintTile = tile;
else
tile.closeDirection(info.singlePossibleDirection, true, 1);
tryAgain = HintType.OneDirectionLeft;
break;
}
// if its number CAN be reached by filling out exactly the remaining unknowns, then do so!
//else if (info.canBeCompletedWithUnknowns) {
//console.log(tile.x, tile.y)
//tile.close(true);
//tryAgain = true;
//}
// check if a certain direction would be too much
for (var dir in Directions) {
var curDir = info.direction[dir];
if (curDir.wouldBeTooMuch) {
if (hintMode)
hintTile = tile;
else
tile.closeDirection(dir);
tryAgain = HintType.WouldExceed;
break;
}
// if dotting one unknown tile in this direction is at least required no matter what
else if (curDir.unknownCount && curDir.numberWhenDottingFirstUnknown + curDir.maxPossibleCountInOtherDirections <= tile.value) {
if (hintMode)
hintTile = tile;
else
tile.closeDirection(dir, true, 1);
tryAgain = HintType.OneDirectionRequired;
break;
}
}
// break out the outer for loop too
if (tryAgain)
break;
}
// if a number has its required value around, but still an empty tile somewhere, close it
// (this core regards that situation FROM the empty unknown tile, not from the number itself)
// (but only if there are no tiles around that have a number and already reached it)
if (tile.isUnknown() && !info.unknownsAround && !info.completedNumbersAround) {
if (info.numberCount == 0) {
if (hintMode)
hintTile = tile;
else
tile.wall(true);
tryAgain = HintType.MustBeWall;
break;
}
//else if (info.numberCount > 0) {
//tile.number(info.numberCount);
//tryAgain = 7;
//break;
//}
}
}
if (hintMode) {
hint.mark(hintTile, tryAgain);
//console.log(tryAgain, tile)
tryAgain = false;
return false;
}
}
//console.log(attempts + ' attempts. ' + (tryAgain? 'Not done.' : 'Done.'));
render();
if (silent)
clearTimeout(renderTOH);
return false;
}
Notice that the last commented/documented section always leads to a MustBeWall situation (with "safe" or unsafe empty tiles alike). It works with a numberless empty tile surrounded by walls, so it can work with a possible numberless island too. If you can't prove a tile is blue with all the available information without a doubt, it must be red.