Picross 2D

I recently had the inkling to revisit Picross 3D, a most entertaining title for the Nintendo DS system. Then I got the urge to experiment with SVG syntax, tinkering with the possible ways to visualize the solved puzzles.

Level

Let’s build on solid foundations, with a string of characters describing a completed puzzle.

const level = `xxxoo
xxxox
xxxox
xxoox
oooox
oooox
oxxox
oxxox`;

The most immediate way to show the level is directly through the xs and os — plot the string in a <pre> element to preserve whitespace.

<pre>
	{@html level}
</pre>

The result doesn’t really improve from the snippet, but it gives a basis.

xxxoo
xxxox
xxxox
xxxox
xxoox
oooox
oooox
oxxox
oxxox

Grid

Being an article devoted to vector graphics, we want to eventually draw within the boundaries of an <svg> element.

<svg>
	<!-- ... -->
</svg>

A 2D array seems like a fitting data structure. Arrays to describe the rows, nested arrays to describe the individual cells. You loop through the collection to draw something when the cell has a specific character.

Break down the string into a first array, for the rows, considering the new line character \n.

const grid = level.split('\n');

For the nested construct, loop through the rows, these are again strings of characters, and separate the individual letters.

const grid = level.split('\n').map((row) => row.split(''));

There you have it. A 2D array hiding the letters in a comforting grid.

/*
[
	['x', 'x', 'x', 'o', 'o'],
	['x', 'x', 'x', 'o', 'x'],
	...
]
*/

Enough logic, however. In terms of SVG we want to set up a canvas as wide and tall as needed for the level.

The grid first describes the rows, meaning the length of the larger array gives us the height.

const height = grid.length;

The nested arrays lead us instead to the width, the number of characters in a row.

const width = grid[0].length;

Thankfully, the rows have all the same length. You can pick any line and rest assured the value prompts the desired canvas.

<svg viewBox="0 0 {width} {height}">
	<!-- ... -->
</svg>

By adding the values directly in the viewBox you end up with a canvas where 1 unit is enough to describe a cell. A rectangle 1 wide, 1 tall will be enough to show the filled value.

But enough reflection. In the body of the <svg> element, loop through the grid to get the rows.

<g fill="currentColor">
	{#each grid as row, y}
		<!--  -->
	{/each}
</g>

The #each block blesses us with two values, the items in the array, but also the index. The value maps directly to the y coordinate to draw at increments of 1: 0, 1, 2, 3…

Loop again, through the row, to find the cells and the horizontal offset.

{#each grid as row, y}
	{#each row as cell, x}
		<!--  -->
	{/each}
{/each}

Finally, draw a rectangle, but only if the cell includes the arbitrary o character.

{#if cell === 'o'}
	<rect x={x} y={y} width="1" height="1" />
{/if}

A definite improvement, wouldn’t you agree?

An upstanding giraffe

It may all look like magic, but take the attributes in sequence and it makes a world of sense. The first o character is tucked in the first row, y equal to 0, and fourth cell, x equal to 3. This means you draw the unitary rectangle in the matching coordinates.

<rect x="3" y="0" width="1" height="1" />

If you enjoy being concise, Svelte has even a shorthand up its sleeve. Since the x, y variables match the attributes in name , inject the value directly between curly brackets.

<!-- sorcery! -->
<rect {x} {y} width="1" height="1" />

Coordinates

Personally, I get fidgety with the logic in the markup. Let’s revisit the logic to keep things separate and isolated to the <script> block.

The grid helps us tremendously, separating the characters and describing the width and height of the canvas. We can however create a separate data structure, just for the characters we need to draw, just for the os.

// grid = level.split(...)
const coordinates = [];

I tend to indulge in rather cryptic syntax, leaning on the reduce function, but there’s the good-honest for loop might illustrate the approach more clearly. And do not worry, I’ll drop the wizarding bit of logic later on.

Loop through the rows. Once again the incrementing index relates the y coordinate.

for (let y = 0; y < grid.length; y++) {
	// ...
}

Loop through the cells. This time, the index describes the offset in the opposite dimension.

for (let y = 0; y < grid.length; y++) {
	for (let x = 0; x < grid[0].length; x++) {
		// ...
	}
}

If the cell contains the character, add the coordinates to the collection.

if (grid[y][x] === 'o') {
	coordinates.push({ x, y });
}

As you populate the one dimensional array, you need only to loop through the collection to complete the rendering.

{#each coordinates as {x, y}}
	<rect {x} {y} width="1" height="1" />
{/each}

The result would produce the same upstanding animal, so let me offer a different level for your troubles. Here’s the string if you fancy a guess.

const level = `xooxoox
oooxooo
oooxooo
xxxoxxx
oooxooo
oooxooo
xooxoox`;

And here’s the most ready result.

A good luck clover

Care to test your luck with a reduce method or two? Here’s a version I created in a few steps.

const coordinates = grid.reduce((acc, curr, y) => {
	return [
		...acc,
		...curr.reduce((a, c, x) => {
			return c === 'o' ? [...a, { x, y }] : a;
		}, [])
	];
}, []);

Consider it a homework of sorts to understand it all. Perhaps remove the return keyword if you enjoy being really, wickedly short.

And consider this final level as your eventual reward.

A savory apple