Typescript Sudoku problem outputting incomplete boards - javascript

I've managed to convert a js sudoku generator into a ts generator for some practice and the only problem I'm having is getting it to output only complete boards. Right now, it's outputting boards regardless if they're complete and I have to refresh until one board is correct.
I'm not sure how to write the following function so that it only outputs full boards:
function fillBoard(puzzleArray: number[][]): number[][] {
if (nextEmptyCell(puzzleArray).colIndex === -1) return puzzleArray;
let emptyCell = nextEmptyCell(puzzleArray);
for (var num in shuffle(numArray)) {
if (safeToPlace(puzzleArray, emptyCell, numArray[num])) {
puzzleArray[emptyCell.rowIndex][emptyCell.colIndex] = numArray[num];
fillBoard(puzzleArray);
}
}
return puzzleArray;
}
Here is all my code:
import { Box } from "./Box";
export function Board() {
let BLANK_BOARD = [
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
];
let NEW_BOARD = [
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
];
let counter: number = 0;
let check: number[];
const numArray: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
function rowSafe(
puzzleArray: number[][],
emptyCell: { rowIndex: number; colIndex: number },
num: number
): boolean {
return puzzleArray[emptyCell.rowIndex].indexOf(num) == -1;
}
function colSafe(
puzzleArray: number[][],
emptyCell: { rowIndex: number; colIndex: number },
num: number
): boolean {
let test = puzzleArray.flat();
for (let i = emptyCell.colIndex; i < test.length; i += 9) {
if (test[i] === num) {
return false;
}
}
return true;
}
function regionSafe(
puzzleArray: number[][],
emptyCell: { rowIndex: number; colIndex: number },
num: number
): boolean {
const rowStart: number = emptyCell.rowIndex - (emptyCell.rowIndex % 3);
const colStart: number = emptyCell.colIndex - (emptyCell.colIndex % 3);
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (puzzleArray[rowStart + i][colStart + j] === num) {
return false;
}
}
}
return true;
}
console.log(rowSafe(BLANK_BOARD, { rowIndex: 4, colIndex: 6 }, 5));
console.log(colSafe(BLANK_BOARD, { rowIndex: 2, colIndex: 3 }, 4));
console.log(regionSafe(BLANK_BOARD, { rowIndex: 5, colIndex: 6 }, 5));
function safeToPlace(
puzzleArray: number[][],
emptyCell: { rowIndex: number; colIndex: number },
num: number
): boolean {
return (
regionSafe(puzzleArray, emptyCell, num) &&
rowSafe(puzzleArray, emptyCell, num) &&
colSafe(puzzleArray, emptyCell, num)
);
}
console.log(safeToPlace(BLANK_BOARD, { rowIndex: 5, colIndex: 6 }, 5));
function nextEmptyCell(puzzleArray: number[][]): {
colIndex: number;
rowIndex: number;
} {
let emptyCell = { rowIndex: -1, colIndex: -1 };
for (let i = 0; i < 9; i++) {
for (let j = 0; j < 9; j++) {
if (puzzleArray[i][j] === 0) {
return { rowIndex: i, colIndex: j };
}
}
}
return emptyCell;
}
function shuffle(array: number[]): number[] {
// using Array sort and Math.random
let shuffledArr = array.sort(() => 0.5 - Math.random());
return shuffledArr;
}
function fillBoard(puzzleArray: number[][]): number[][] {
if (nextEmptyCell(puzzleArray).colIndex === -1) return puzzleArray;
let emptyCell = nextEmptyCell(puzzleArray);
for (var num in shuffle(numArray)) {
if (safeToPlace(puzzleArray, emptyCell, numArray[num])) {
puzzleArray[emptyCell.rowIndex][emptyCell.colIndex] = numArray[num];
fillBoard(puzzleArray);
} else {
puzzleArray[emptyCell.rowIndex][emptyCell.colIndex] = 0;
}
}
return puzzleArray;
}
console.log(nextEmptyCell(BLANK_BOARD));
NEW_BOARD = fillBoard(BLANK_BOARD);
function fullBoard(puzzleArray: number[][]): boolean {
return puzzleArray.every((row) => row.every((col) => col !== 0));
}
return (
<div
style={{
height: "450px",
width: "450px",
display: "inline-grid",
gap: "10px",
gridTemplateColumns: "repeat(9,50px)",
gridTemplateRows: "repeat(9,50px)",
position: "absolute",
top: "30px",
left: "0px",
right: "0px",
marginLeft: "auto",
marginRight: "auto",
}}
>
{NEW_BOARD.flat().map((item) => (
<Box i={item} />
))}
</div>
);
}

The function will return an incomplete board when it turns out there is no way to add a digit in a free cell that will be valid.
To solve that, your function should:
implement proper backtracking, i.e. taking back a move that didn't work out
Have the function return a boolean (for success/failure) -- there is no need for it to return puzzleArray, since that array is mutated inplace, so the caller has access to the changes.
This also means that NEW_BOARD = fillBoard(BLANK_BOARD); is having as side effect that NEW_BOARD and BLANK_BOARD reference the same board, and it is not blank any more (so the name is misleading).
break/return out of the loop when there is success.
Here is the adapted implementation:
function fillBoard(puzzleArray: number[][]): boolean {
if (nextEmptyCell(puzzleArray).colIndex === -1) return true; // boolean
let emptyCell = nextEmptyCell(puzzleArray);
for (var num in shuffle(numArray)) {
if (safeToPlace(puzzleArray, emptyCell, numArray[num])) {
puzzleArray[emptyCell.rowIndex][emptyCell.colIndex] = numArray[num];
if fillBoard(puzzleArray) return true; // exit upon success
}
}
puzzleArray[emptyCell.rowIndex][emptyCell.colIndex] = 0; // undo
return false; // boolean - no success
}
The caller should check the return value, but if you start with a blank board, it is guaranteed to get true as return value. So you could do:
const puzzleArray = BLANK_BOARD.map(row => [...row]); // deep copy
const success = fillBoard(puzzleArray); // returns a boolean
// ... do something with puzzleArray ...

Related

Adding images by combining cells with Javascript canvas

First of all, I have difficulty in explaining because my English is not very good. But I will try to explain as best I can.
I add random photos to the canvasta cells fields with Javascript.
Each plot is equivalent to 20pixels. What I want to do is:
if i and y in the map data are equal to 4;
I want to add the photo I want to a 4x4 area.
In other words, while adding a photo to 20 pixels;
I want to add a photo to a 320pixel area when it is 4x4.
As in the sample photo.
Check Photo
var map = [
[1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1],
[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0],
[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0],
[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0],
[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0],
[1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1],
[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1],
[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1],
[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1],
[1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1],
[4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
];
window.onload = function() {
const canvas = document.getElementById("main");
const ctx = canvas.getContext("2d");
ctx.strokeStyle = "transparent";
ctx.lineWidth = 0;
//draw grid
for (let i = 0; i <= 300; i++) {
const x = i*20;
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
const y = i*20;
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
//draw images
const p = ctx.lineWidth / 1; //padding
for (let xCell = 0; xCell < map.length; xCell++) {
for (let yCell = 0; yCell < map[xCell].length; yCell++) {
const x = xCell * 20;
const y = yCell * 20;
const img = new Image();
if (map[xCell][yCell] === 1) {
img.onload = function() {
ctx.drawImage(img, y+p, x+p, 20-p*2, 20-p*2);
};
//TODO: set img.src to your api url instead of the dummyimage url.
img.src = `https://picsum.photos/id/${Math.floor(Math.random() * 10)}${Math.floor(Math.random() * 10)}/200/300`;
}else if(map[xCell][yCell] == 4){
img.onload = function() {
ctx.drawImage(img, y+p, x+p, 20-p*2, 20-p*2);
};
//TODO: set img.src to your api url instead of the dummyimage url.
img.src = `https://picsum.photos/id/${Math.floor(Math.random() * 12)}${Math.floor(Math.random() * 12)}/200/300`;
}
}
}
};
<canvas id="main" width="600" height="630"></canvas>
</canvas>
I solved the problem as below. There are positional errors but I will completely fix them.
Thanks to Lajos Arpad for trying to help.
var defaultMap = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1],
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
[0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1],
[0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1],
[1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1],
[1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1],
[1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1],
[1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1],
];
var images = [
{
name: "player 1",
position: [
{
x: [0,4],
y: [4,4]
}
]
},
{
name: "player 2",
position: [
{
x: [5,7],
y: [5,2]
}
]
}
];
window.onload = function() {
const canvas = document.getElementById("main");
const ctx = canvas.getContext("2d");
ctx.strokeStyle = "transparent";
ctx.lineWidth = 0;
//draw grid
for (let i = 0; i <= 300; i++) {
const x = i*40;
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
const y = i*40;
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
const p = ctx.lineWidth / 1;
for (let xCell = 0; xCell < defaultMap.length; xCell++) {
for (let yCell = 0; yCell < defaultMap[xCell].length; yCell++) {
const x = xCell * 20;
const y = yCell * 20;
const img = new Image();
if (defaultMap[xCell][yCell] === 1) {
img.onload = function() {
ctx.drawImage(img, y+p, x+p, 20-p*2, 20-p*2);
};
img.src = `https://besthqwallpapers.com/Uploads/5-6-2020/134999/thumb-black-ground-texture-macro-grunge-textures-black-backgrounds-ground-textures.jpg`;
}
}
}
for (let i = 0; i < images.length; i++) {
var width = 1, height = 1, x = images[i].position[0].x[0], y = images[i].position[0].y[0];
console.log(images[i].name + " x => " + images[i].position[0].x[0])
for(let w = images[i].position[0].x[0]; w < images[i].position[0].x[1]; w++){
width++
}
for(let h = 1; h < images[i].position[0].y[1]; h++){
height++
}
const img = new Image();
img.src = `https://picsum.photos/id/${Math.floor(Math.random() * 12)}${Math.floor(Math.random() * 12)}/200/300`;
img.onload = function() {
console.log(20*images[i].position[0].x[0]+p)
/* ctx.rect(20*i, 20*i, 20*width-p*2, 20*height-p*2)
ctx.fillStyle = "blue";
ctx.fill(); */
ctx.drawImage(img, 20*images[i].position[0].y[0]+p, 20*images[i].position[0].x[0]+p, 20*width-p*2, 20*height-p*2);
};
}
};
<canvas id="main" width="600" height="630"></canvas>
</canvas>
You can compute the value of a multiplier and use that to determine the picture size.
var map = [
[1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1],
[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0],
[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0],
[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0],
[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0],
[1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1],
[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1],
[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1],
[1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1],
[1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1],
[4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
];
window.onload = function() {
const canvas = document.getElementById("main");
const ctx = canvas.getContext("2d");
ctx.strokeStyle = "transparent";
ctx.lineWidth = 0;
//draw grid
for (let i = 0; i <= 300; i++) {
const x = i*20;
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
const y = i*20;
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
//draw images
const p = ctx.lineWidth / 1; //padding
for (let xCell = 0; xCell < map.length; xCell++) {
for (let yCell = 0; yCell < map[xCell].length; yCell++) {
let multiplier = ((map[xCell][yCell] % 4) ? 1 : 4);
const x = xCell * 20;
const y = yCell * 20;
const img = new Image();
if (map[xCell][yCell] === 1) {
img.onload = function() {
ctx.drawImage(img, y+p, x+p, multiplier * 20-p*2, multiplier * 20-p*2);
};
//TODO: set img.src to your api url instead of the dummyimage url.
img.src = `https://picsum.photos/id/${Math.floor(Math.random() * 10)}${Math.floor(Math.random() * 10)}/200/300`;
}else if(map[xCell][yCell] == 4){
img.onload = function() {
ctx.drawImage(img, y+p, x+p, multiplier * 20-p*2, multiplier * 20-p*2);
};
//TODO: set img.src to your api url instead of the dummyimage url.
img.src = `https://picsum.photos/id/${Math.floor(Math.random() * 12)}${Math.floor(Math.random() * 12)}/200/300`;
}
}
}
};
<canvas id="main" width="600" height="630"></canvas>
</canvas>

How to determine if chunks of a value could fit into an array

Given an input array of 1s and 0s of arbitrary length, such as:
[0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0]
How can I (most efficiently) calculate a new array detailing if chunks of size n 0s which can fit into the input?
Examples
Where output now means
1 == 'Yes a zero chunk that size could go here'
0 == 'Couldn't fit a chunk that size there'
Chunk size = 1 ([0]): [1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1]
Chunk size = 2 ([0,0]): [0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1]
Chunk size = 3 ([0,0,0]): [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0]
Chunk size = 4 ([0,0,0,0]): [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
I'm using ES6, so any language features are fine.
EDIT:
The output shouldn't just be a 'yes'/'no' a chunk of size 'n' can fit in this array. More specifically, it needs to be an array of the same length, where a '1' / 'true' array value represents either:
Yes, a chunk of size 'n' could start and fit here, or
Yes, this slot could contain a chunk of size 'n' that started before it
On that second point, this would mean for chunk size 3:
input = [1, 0, 0, 0, 0, 1];
output = [0, 1, 1, 1, 1, 0];
Edit 2:
This is the function I came up with but it seems very inefficient:
const calculateOutput = (input, chunkSize) => {
const output = input.map((value, index) => {
let chunkFitsHere = false;
const start = (index - (chunkSize) >= 0) ? index - (chunkSize) : 0;
const possibleValues = input.slice(start, index + chunkSize);
possibleValues.forEach((pValue, pIndex) => {
let consecutives = 0;
for (let i = 0; i < possibleValues.length - 1; i += 1) {
if (consecutives === chunkSize) {
break;
}
if (possibleValues[i+1] === 0) {
consecutives += 1;
} else {
consecutives = 0;
}
}
if (consecutives === chunkSize) {
chunkFitsHere = true;
}
});
return chunkFitsHere ? 1 : 0;
});
return output;
};
You could count the connected free places by reversing the array and take an flag for the last return value for mapping.
Array before final mapping and after, depending on n
1 0 4 3 2 1 0 0 2 1 0 0 0 3 2 1 0 2 1 array with counter
1 0 4 4 4 4 0 0 2 2 0 0 0 3 3 3 0 2 2 array same counter
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ------------------
1 0 1 1 1 1 0 0 1 1 0 0 0 1 1 1 0 1 1 n = 1
0 0 1 1 1 1 0 0 1 1 0 0 0 1 1 1 0 1 1 n = 2
0 0 1 1 1 1 0 0 0 0 0 0 0 1 1 1 0 0 0 n = 3
0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 n = 4
function chunk(array, n) {
return array
.slice()
.reverse()
.map((c => v => v ? c = 0 : ++c)(0))
.reverse()
.map((l => v => l = v && (l || v))(0))
.map(v => +(v >= n));
}
var array = [0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0];
console.log(chunk(array, 1).join(' '));
console.log(chunk(array, 2).join(' '));
console.log(chunk(array, 3).join(' '));
console.log(chunk(array, 4).join(' '));
If you like only one mapping at the end, remove the last two map and use
.map((l => v => l = +(v && (v >= n || l)))(0));
for final mapping.
You can traverse array once, calculating length of the series of zeros. If it is long enough, fill output with series of 1 of the same length.
Note that you can fill output for different chunk lengths simultaneously (filling chunks in rows of 2d array with row index not exceeding zerolen)
Python code:
def zerochunks(a, n):
l = len(a)
result = [0] * l #list of l zeros
zerolen = 0
for i in range(l + 1):
### Short circuit evaluation to shorten code
if (i==l) or (a[i] != 0):
if (zerolen >= n): #series of zeros is finished here
for j in range(i - zerolen, i):
result[j] = 1
zerolen = 0
else:
zerolen += 1
return result
print(zerochunks([0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0], 1))
print(zerochunks([0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0], 2))
print(zerochunks([0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0], 3))
print(zerochunks([0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0], 4))
print(zerochunks([0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0], 5))
>>>
[1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1]
[0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1]
[0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0]
[0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
And function for getting all arrays with chunks in maxn range:
def zerochunksall(a, maxn):
l = len(a)
result = [[0] * l for i in range(maxn)]
zerolen = 0
for i in range(l + 1):
if (i==l) or (a[i] != 0):
for k in range(0, zerolen):
for j in range(i - zerolen, i):
result[k][j] = 1
zerolen = 0
else:
zerolen += 1
return result
print(zerochunksall([0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0], 5))
>>
[[1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1],
[0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1],
[0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
function fit(input, n) {
var output = [];
for(var i = 0; i < input.length; i++) {
var fit = true;
for(var j = i; j < i + n; j++) {
if(j >= input.length || input[j]) { // either over the array size or occupied
fit = false;
break;
}
}
output.push(fit);
}
return output;
}
var input = [0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0];
console.log(fit(input, 1).map(v => +v));
console.log(fit(input, 2).map(v => +v));
console.log(fit(input, 3).map(v => +v));
console.log(fit(input, 4).map(v => +v));
outputs
[ 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1 ]
[ 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0 ]
[ 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 ]
[ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
These don't seem to exactly match your expected output though, but that may be because I'm assuming the flags in the array should mark the start of the chunk (i.e. can you fit N truthy values in the array starting from this position).
(See below, where e = your expected result, n = the output of my algorithm.)
input = [ 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0 ]
e = 2 = [ 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1 ]
n = 2 = [ 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0 ]
==== ==== ==== ====
You can use array.prototype.some to check if the chunk can fit starting to some index of the input array. To check if the chunk with it's actual length can fit, you can use array.prototype.every:
var input = [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0];
var chunk = [0,0,0];
var res = input.some((e, i) => chunk.every((c, j) => input[i + j] === c));
console.log(res);
var input = [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0];
var chunk = [0, 0, 0, 0, 0, 0, 0, 0];
var res = input.some((e, i) => chunk.every((c, j) => input[i + j] === c));
console.log(res);
You could use some method and then if the current element is 0 you can slice part of the array from current index and check if its all zeros.
const data = [0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0]
function check(arr, n) {
let chunk = Array(n).fill(0).join('');
return arr.some((e, i) => e === 0 && arr.slice(i, i + n).join('') == chunk)
}
console.log(check(data, 3))
console.log(check(data, 4))
console.log(check(data, 5))

detecting for off bounded squares on grid

I'm going through a tutorial, and apparently I'm suppose to be getting this. But I can't find wrong in the coding part, I'm going step by step as well. I'm geting nothing, but before I had a block falling from above. Apparently I'm suppose to be getting something like
var WIDTH = 300,
HEIGHT = 400,
c = document.getElementById('canvas'),
ctx = c.getContext('2d');
setInterval(function() {
clearCanvas();
generateNextSquare();
updatePosition();
drawOnCanvas();
}, 1000 / 50);
var clearCanvas = function() {
ctx.fillStyle = 'White';
ctx.beginPath();
ctx.rect(0, 0, WIDTH, HEIGHT);
ctx.closePath();
ctx.fill();
}
var drawLine = function() {
ctx.beginPath();
ctx.moveTo(200, 0);
ctx.lineTo(200, 400);
ctx.stroke();
}
var updatePosition = function() {
_square.fall();
}
var drawOnCanvas = function() {
drawLine();
_square.draw();
}
var squareLength = 20;
var speedLevels = [20, 16, 12, 10, 8],
currSpeed = speedLevels[0];
var gameGrid = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
];
var Square = function(speed) {
var self = this;
self.color = "Black";
self.vPosition = 0;
self.hPosition = 4;
self.speed = speed;
self.current = 0 ;
self.temp = 0;
self.fall = function() {
if (self.counter >= self.speed) {
if (self.checkFalling()) {
self.vPosition++;
} else {
gameGrid[self.vPosition][self.hPosition] = 1;
self.active = false;
}
self.counter = 0;
}
self.counter++;
}
self.checkFalling = function() {
if (gameGrid[self.vPosition + 1][self.hPosition] == 1)
return false;
else
return true;
}
}
return self;
}
var drawFixedSquares = function() {
for (var i = 0; i < 20; i++) {
for (var j = 0; j < 10; j++) {
if (gameGrid[i][j] == 1) {
ctx.fillStyle = "Black";
ctx.fillRect(j * squareLength, i * squareLength, squareLength, squareLength);
}
}
}
}
var generateNextSquare = function() {
if (!_square.active)
_square = new Square(currSpeed);
}
var _square = new Square(currSpeed);

How can I load these images

Though I have been able to create and use animations for small demos in the past using the CreateJS library, i'm currently stumped in trying to understand why Nothing is displaying to the canvas even though I have verified that my animation tiles are all being added. I'm going senile from stepping through the debugger and seeing everything work properly, but getting a completely blank page on reloads.
Any ideas are much appreciated!
var stage, loader;
function tick() {
stage.update();
}
function generateMap(rekeyed_array, spriteSheet, row_length, col_length, tilesize) {
for (var x = 0; x < col_length; x++) {
for (var y = 0; y < row_length; y++) {
// z is a onedimentional value mapped from x and y iterators
spriteInstance = new createjs.Sprite(spriteSheet, rekeyed_array[z]);
var z = x * row_length + y;
spriteInstance.x = tilesize * x;
spriteInstance.y = tilesize * y;
spriteInstance.gotoAndPlay(rekeyed_array[z]);
spriteInstance = spriteInstance.clone();
stage.addChild(spriteInstance);
}
}
console.groupEnd();
stage.update();
}
//Replace Tiled's map data numbers with the actual Game Object's Names
function rekeyTiledMapData(spritemappings, array_to_reindex, rows, cols) {
var reindexedArray = new Array();
for (var y = 0; y < cols; y++) {
for (var x = 0; x < rows; x++) {
// z is a onedimentional value mapped from x and y iterators
var z = y * rows + x;
var currentItem = array_to_reindex[z];
if (typeof spritemappings[currentItem] === "string") {
reindexedArray.push(spritemappings[currentItem]);
} else {
reindexedArray.push(currentItem);
}
}
}
return reindexedArray;
}
function getSpriteData(loadedimg) {
var data = {
framerate: 60,
images: [loadedimg],
frames: [
/*middlearth*/
[592, 654, 70, 70, 0, 0, 0],
/*water*/
[562, 434, 70, 70, 0, 0, 0],
/*doormid*/
[146, 290, 70, 70, 0, 0, 0],
/*doortop*/
[218, 290, 70, 70, 0, 0, 0],
/*grass*/
[736, 362, 70, 70, 0, 0, 0]
],
animations: {
"sand": {
frames: 0,
next: "sand",
speed: 1
},
"water": {
frames: 1,
next: "water",
speed: 1
},
"doormid": [2, 2, "doormid", 1],
"doortop": [3, 3, "doortop", 1],
"grass": [4, 4, "grass", 1],
}
};
return data;
}
var mapdata = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 7, 7, 7, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5];
var spritemappings = {
'0': "water",
'8': "water",
'7': "water",
'5': "sand",
'6': "grass",
'10': "doortop",
'11': "doormid"
};
function loadLevel(event) {
var tileimage = loader.getResult("tiles");
levelanimations = rekeyTiledMapData(spritemappings, mapdata, 16, 10);
var spritesheet = new createjs.SpriteSheet(getSpriteData(tileimage));
generateMap(levelanimations, spritesheet, 16, 10, 70);
}
function init() {
stage = new createjs.Stage("gameCanvas");
createjs.Ticker.on("tick", tick);
createjs.Ticker.setFPS(60);
loader = new createjs.LoadQueue(false);
loader.addEventListener("complete", loadLevel)
loader.loadManifest({
id: "tiles",
src: "assets/images/level_tiles.png"
});
}
init();
</script>
</head>
<body>
<canvas id="gameCanvas" width="1600" height="900"></canvas>
</body>
If that script is a copy and paste, the script in the head is executed before the body is being created. Thats why you are seeing that it is executing correctly, but nothing is actually happening.
I would copy and paste the script tag in the header down below the canvas element.

Multidimensional array fill

I'm trying to fill an area in a multidimensional array and not sure on the approach.
For example I have the following array:
var map = [
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 2, 2, 2, 2, 2, 2, 0, 0],
[0, 2, 0, 0, 0, 0, 2, 0, 0],
[0, 2, 0, 2, 0, 0, 2, 0, 0],
[0, 2, 0, 0, 2, 0, 2, 0, 0],
[0, 0, 2, 0, 0, 0, 2, 0, 0],
[0, 0, 0, 2, 2, 2, 2, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]
];
And then I am trying to get the number from X and Y position and fill all those numbers (which is 0) with a number given such as 1, which will result in the following array:
var map = [
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 2, 2, 2, 2, 2, 2, 0, 0],
[0, 2, 1, 1, 1, 1, 2, 0, 0],
[0, 2, 1, 2, 1, 1, 2, 0, 0],
[0, 2, 1, 1, 2, 1, 2, 0, 0],
[0, 0, 2, 1, 1, 1, 2, 0, 0],
[0, 0, 0, 2, 2, 2, 2, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]
];
Basically just replacing all numbers next to each other (0) with (1) within that area.
What is the correct way to do this with JavaScript?
Assuming you're given a starting position and you want to then fill all neighboring values up/down, left/right that contain the same value, you can do something like this:
var map = [
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 2, 2, 2, 2, 2, 2, 0, 0],
[0, 2, 0, 0, 0, 0, 2, 0, 0],
[0, 2, 0, 2, 0, 0, 2, 0, 0],
[0, 2, 0, 0, 2, 0, 2, 0, 0],
[0, 0, 2, 0, 0, 0, 2, 0, 0],
[0, 0, 0, 2, 2, 2, 2, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]
];
function fill(data, x, y, newValue) {
// get target value
var target = data[x][y];
function flow(x,y) {
// bounds check what we were passed
if (x >= 0 && x < data.length && y >= 0 && y < data[x].length) {
if (data[x][y] === target) {
data[x][y] = newValue;
flow(x-1, y); // check up
flow(x+1, y); // check down
flow(x, y-1); // check left
flow(x, y+1); // check right
}
}
}
flow(x,y);
}
fill(map, 2, 2, 1);
Working demo: http://jsfiddle.net/jfriend00/C83AT/
Here's a version that doesn't use recursion and appears to work with large data sets. Your large test data set wasn't a very interesting test pattern so I wouldn't say this is tested conclusively, but it seems to work on both the small and large data set:
Large data example: http://jsfiddle.net/jfriend00/8mrhN/
Small data example: http://jsfiddle.net/jfriend00/BFTub/ (easier to see the result)
function fill(data, x, y, newValue) {
// get target value
var target = data[x][y];
// maintain list of cells to process
// put the starting cell in the queue
var queue = [{x:x, y:y}], item;
while (queue.length) {
item = queue.shift();
x = item.x;
y = item.y;
if (data[x][y] === target) {
data[x][y] = newValue;
// up
if (x > 0) {
queue.push({x:x-1, y:y})
}
// down
if (x + 1 < data.length) {
queue.push({x:x+1, y:y})
}
// left
if (y > 0) {
queue.push({x:x, y:y-1});
}
// right
if (y + 1 < data[x].length) {
queue.push({x:x, y:y+1});
}
}
}
}
This could be optimized further for performance by testing the value before putting it in the queue and by following a given direction until you find a non-matching value, if required.
This is an alternative implementation (queue-based) roughly translated, no optimisations performed. There are also others.
Javascript
var map = [
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 2, 2, 2, 2, 2, 2, 0, 0],
[0, 2, 0, 0, 0, 0, 2, 0, 0],
[0, 2, 0, 2, 0, 0, 2, 0, 0],
[0, 2, 0, 0, 2, 0, 2, 0, 0],
[0, 0, 2, 0, 0, 0, 2, 0, 0],
[0, 0, 0, 2, 2, 2, 2, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]
];
/*
1. Set Q to the empty queue.
2. If the color of node is not equal to target-color, return.
3. Add node to Q.
4. For each element N of Q:
5. If the color of N is equal to target-color:
6. Set w and e equal to N.
7. Move w to the west until the color of the node to the west of w no longer matches target-color.
8. Move e to the east until the color of the node to the east of e no longer matches target-color.
9. For each node n between w and e:
10. Set the color of n to replacement-color.
11. If the color of the node to the north of n is target-color, add that node to Q.
12. If the color of the node to the south of n is target-color, add that node to Q.
13. Continue looping until Q is exhausted.
14. Return.
*/
function floodFill(data, node, targetValue, replacementValue) {
var Q;
if (data[node[0]][node[1]] === targetValue) {
Q = [node];
while (Q.length) {
var N = Q.shift(),
value,
index,
n,
e,
s,
w;
if (data.hasOwnProperty([N[0]]) && data[N[0]][N[1]] === targetValue) {
w = e = N[0];
do {
w -= 1;
} while (data.hasOwnProperty(w) && data[w][N[1]] === targetValue);
do {
e += 1;
} while (data.hasOwnProperty(e) && data[e][N[1]] === targetValue);
n = N[1] - 1;
s = N[1] + 1;
for (index = w + 1; index < e; index += 1) {
data[index][N[1]] = replacementValue;
if (data[index].hasOwnProperty(n) && data[index][n] === targetValue) {
Q.push([index, n]);
}
if (data[index].hasOwnProperty(s) && data[index][s] === targetValue) {
Q.push([index, s]);
}
}
}
}
}
}
floodFill(map, [2, 2], 0, 1);
map.forEach(function (m) {
console.log(JSON.stringify(m));
});
Output
[0,0,0,0,0,0,0,0,0]
[0,2,2,2,2,2,2,0,0]
[0,2,1,1,1,1,2,0,0]
[0,2,1,2,1,1,2,0,0]
[0,2,1,1,2,1,2,0,0]
[0,0,2,1,1,1,2,0,0]
[0,0,0,2,2,2,2,0,0]
[0,0,0,0,0,0,0,0,0]
On jsFiddle

Categories

Resources