Maximum call stack size exceeded (sodoku solver) - javascript

i am triying to create a sodoku solve. but I get the following error:
Maximum call stack size exceeded. Please help me.
let board = [
[5, 3, 0, 0, 7, 0, 0, 0, 0],
[6, 0, 0, 1, 9, 5, 0, 0, 0],
[0, 9, 8, 0, 0, 0, 0, 6, 0],
[8, 0, 0, 0, 6, 0, 0, 0, 3],
[4, 0, 0, 8, 0, 3, 0, 0, 1],
[7, 0, 0, 0, 2, 0, 0, 0, 6],
[0, 6, 0, 0, 0, 0, 2, 8, 0],
[0, 0, 0, 4, 1, 9, 0, 0, 5],
[0, 0, 0, 0, 8, 0, 0, 7, 9],
];
const possible = function (x, y, n, board) {
for (let i = 0; i < 9; i++) {
if (board[i][y] === n || board[x][i] === n) return false;
}
const x0 = Math.floor(x / 3);
const y0 = Math.floor(y / 3);
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (board[x0 + i][y0 + j] === n) return false;
}
}
return true;
};
The possible function check if we can put n in board[x][y] according to sodoku rules
const solve = function (board) {
for (let x = 0; x < 9; x++) {
for (let y = 0; y < 9; y++) {
for (let n = 1; n < 10; n++) {
if (possible(x, y, n, board)) {
board[x][y] = n;
solve(board)
}
}
board[x][y] = 0
}
}
};
solve(board);
The solve function receive a board and solve it using recursion. The board is passed by reference so the board variable at the global scope is modified.

Related

javascript backtracking algorithm with strange bahavior

let puzzle = [
[0, 0, 7, 0, 0, 3, 5, 0, 0],
[6, 0, 5, 4, 0, 8, 3, 0, 2],
[0, 0, 4, 5, 2, 0, 9, 0, 6],
[0, 0, 0, 0, 7, 1, 2, 0, 9],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[8, 0, 9, 2, 3, 0, 0, 0, 0],
[9, 0, 1, 0, 8, 5, 6, 0, 0],
[7, 0, 3, 9, 0, 2, 8, 0, 5],
[0, 0, 8, 7, 0, 0, 1, 0, 0]
];
class Sudoku
{
constructor(puzzle)
{
this.sudoku = puzzle;
}
isPossible(y, x, n)
{
for (let i = 0; i < 9; i++)
{
if (this.sudoku[y][i] == n)
return false;
}
for (let i = 0; i < 9; i++)
{
if (this.sudoku[i][x] == n)
return false;
}
let y0 = (Math.floor(y / 3) * 3);
let x0 = (Math.floor(x / 3) * 3);
for (let i = 0; i < 3; i++)
{
for (let j = 0; j < 3; j++)
{
if (this.sudoku[y0 + i][x0 + j] == n)
return false;
}
}
return true;
}
solve()
{
for (let y = 0; y < 9; y++)
{
for (let x = 0; x < 9; x++)
{
if (this.sudoku[y][x] == 0)
{
for (let n = 1; n <= 9; n++)
{
if (this.isPossible(y, x, n))
{
this.sudoku[y][x] = n;
this.solve();
this.sudoku[y][x] = 0;
}
}
return;
}
}
}
console.table(this.sudoku);
}
}
let s = new Sudoku(puzzle);
s.solve();
This works fine the way it is written. However, debugging shows that after the console.table, the code keeps running and takes the matrix back to its original state. But, the console.table line is never executed again. So, outside of the solve method, this.sudoku is just the original puzzle matrix.
Why is this happening? After the output, what is causing the code to keep running? How come it never goes back to the end (console.table), and how can I stop it once it has actually solved the puzzle?
It is important to see that the console output is reached if and only if there are no if no more open fields in the table (programmatically. matrix elements set to zero).
In any other case the control flow returns from the current function invocation before the output statement is reached
The recursive algorithm dwells on the idea that to solve a given sudoku problem, you pick an open field, pick the first number between 1 and 9 that keeps the tableau consistent with the rules and try to solve this new puzzle by recursively calling the solver. Termination is guaranteed as with each recursive call there is one open field less.
After a recursive call has completed, the choice made immediately before the call is retracted and the remaining possibilities to assign a number to the position are tried, once again ascertaining consistency and recursively calling the solver. This way, all solutions to the original puzzle will be found.
The solver is efficient in the sense that it visits every configuration that does not admit another level of recursion ( ie. which is a solution or a dead end ) only once. There is exactly 1 sequence in which the configuration's positions that are open in the start puzzle will be filled.
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta http-equiv="expires" content="0">
<meta http-equiv="cache-control" content="private">
<meta http-equiv="pragma" content="no-cache">
<title>SO - Bitmap (svg)</title>
<!--
-->
<style type="text/css">
body {
background-color: #eee;
}
.board {
display: table;
border: solid 3px black
}
.board > div {
display: table-row;
}
.cell {
width: 16px;
height: 16px;
display: table-cell;
border: solid 1px lightgrey;
padding: 5px;
text-align: center;
vertical-align: middle;
font-size: 80%;
}
.last-square-column {
border-right: solid 2px black;
}
.last-square-row {
border-bottom: solid 2px black;
}
.preset {
color: blue;
background-color: #ddddff;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', () => {
let puzzle_orig = [ // The original problem
[0, 0, 7, 0, 0, 3, 5, 0, 0],
[6, 0, 5, 4, 0, 8, 3, 0, 2],
[0, 0, 4, 5, 2, 0, 9, 0, 6],
[0, 0, 0, 0, 7, 1, 2, 0, 9],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[8, 0, 9, 2, 3, 0, 0, 0, 0],
[9, 0, 1, 0, 8, 5, 6, 0, 0],
[7, 0, 3, 9, 0, 2, 8, 0, 5],
[0, 0, 8, 7, 0, 0, 1, 0, 0]
]
;
let puzzle = [ // Multiple solutions
[0, 0, 7, 0, 0, 3, 5, 0, 0],
[6, 0, 5, 4, 0, 8, 3, 0, 2],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 7, 1, 2, 0, 9],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[8, 0, 9, 2, 3, 0, 0, 0, 0],
[9, 0, 1, 0, 8, 5, 6, 0, 0],
[7, 0, 3, 9, 0, 2, 8, 0, 5],
[0, 0, 8, 7, 0, 0, 1, 0, 0]
]
;
class Sudoku
{
constructor(puzzle)
{
this.sudoku = puzzle;
this.base = puzzle.map ( a_row => { return a_row.map ( n_cell => n_cell ); });
this.n_solutions = 0;
}
isPossible(y, x, n)
{
for (let i = 0; i < 9; i++)
{
if (this.sudoku[y][i] == n)
return false;
}
for (let i = 0; i < 9; i++)
{
if (this.sudoku[i][x] == n)
return false;
}
let y0 = (Math.floor(y / 3) * 3);
let x0 = (Math.floor(x / 3) * 3);
for (let i = 0; i < 3; i++)
{
for (let j = 0; j < 3; j++)
{
if (this.sudoku[y0 + i][x0 + j] == n)
return false;
}
}
return true;
}
out () {
let e_body = document.querySelector('body')
, e_board = document.createElement('div')
, e_h = document.createElement('h3')
;
e_h.innerText = `Solution #${this.n_solutions++}`;
e_board.setAttribute('class', 'board');
for (let y = 0; y < 9; y++) {
let e_row = document.createElement('div')
;
for (let x = 0; x < 9; x++) {
let e_cell = document.createElement('div')
;
e_cell.innerText = this.sudoku[y][x];
e_cell.setAttribute('class', 'cell');
if (this.base[y][x] !== 0) {
e_cell.classList.add('preset');
}
if ((x === 2) || (x === 5)) {
e_cell.classList.add('last-square-column');
}
if ((y === 2) || (y === 5)) {
e_cell.classList.add('last-square-row');
}
e_row.append(e_cell);
}
e_board.append(e_row);
}
e_body.append(e_h);
e_body.append(e_board);
} // out
solve()
{
for (let y = 0; y < 9; y++)
{
for (let x = 0; x < 9; x++)
{
if (this.sudoku[y][x] == 0)
{
for (let n = 1; n <= 9; n++)
{
if (this.isPossible(y, x, n))
{
this.sudoku[y][x] = n;
this.solve();
this.sudoku[y][x] = 0;
}
}
return;
}
}
}
this.out();
}
}
let s = new Sudoku(puzzle);
s.solve();
});
</script>
</head>
<body>
</body>
</html>
I will do that this way, simply add a solved test and some loop break...
const Sudoku = (()=>
{
let
grid = null
, solved = false
;
const
nums = [1,2,3,4,5,6,7,8,9]
, isPossible = (row, col, num) =>
{
for (let c in grid) if (grid[row][c] === num) return false
for (let r in grid) if (grid[r][col] === num) return false
row -= row %3
col -= col %3
for (let i=0, c=col; i<3; i++,c++)
for (let j=0, r=row; j<3; j++,r++)
if (grid[r][c] === num) return false
return true
}
, solve = () =>
{
for (let row in grid)
{
if (solved) break
for (let col in grid)
{
if (solved) break
if (grid[row][col] === 0)
{
for (let num of nums)
if (isPossible(row, col, num))
{
grid[row][col] = num
solve()
if (solved) break
grid[row][col] = 0
}
return
} } }
solved = true
};
return (puzzle) =>
{
grid = puzzle
solved = false
solve()
return solved
}
})()
const puzzle =
[ [ 0, 0, 7, 0, 0, 3, 5, 0, 0 ]
, [ 6, 0, 5, 4, 0, 8, 3, 0, 2 ]
, [ 0, 0, 4, 5, 2, 0, 9, 0, 6 ]
, [ 0, 0, 0, 0, 7, 1, 2, 0, 9 ]
, [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
, [ 8, 0, 9, 2, 3, 0, 0, 0, 0 ]
, [ 9, 0, 1, 0, 8, 5, 6, 0, 0 ]
, [ 7, 0, 3, 9, 0, 2, 8, 0, 5 ]
, [ 0, 0, 8, 7, 0, 0, 1, 0, 0 ]
]
let resolved = Sudoku(puzzle)
console.log( resolved ? 'resolved !':'not resolved !','\n' )
console.log(JSON.stringify(puzzle).replaceAll('],[',']\n,['))
// console.table( ) doesn't work on snippet
.as-console-wrapper {max-height: 100% !important;top: 0;}
.as-console-row::after {display: none !important;}

Im trying to make a sudoku puzzle in javascript but it wont work:(

let grid = [[2, 5, 0, 0, 3, 0, 9, 0, 1],
[0, 1, 0, 0, 0, 4, 0, 0, 0],
[4, 0, 7, 0, 0, 0, 2, 0, 8],
[0, 0, 5, 2, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 9, 8, 1, 0, 0],
[0, 4, 0, 0, 0, 3, 0, 0, 0],
[0, 0, 0, 3, 6, 0, 0, 7, 2],
[0, 7, 0, 0, 0, 0, 0, 0, 3],
[9, 0, 3, 0, 0, 0, 6, 0, 4]]
function possible(y, x, n){
for (let i = 0; i < 9; i++){
if (grid[i][x] === n){
return false
}
}
for (let i = 0; i < 9; i++){
if (grid[y][i] === n){
return false
}
}
let xx = (Math.floor(x / 3)) * 3
let yy = (Math.floor(y / 3)) * 3
for (let i = 0; i < 3; i++){
for (let j = 0; j < 3; j++){
if (grid[yy + i][xx + j] === n){
return false
}
}
}
return true
}
function solve(){
for (let y = 0; y < 9; y++){
for (let x = 0; x < 9; x++){
if (grid[y][x] === 0){
for (let n = 1; n < 10; n++){
if (possible(y, x, n)){
grid[y][x] = n
solve()
grid[y][x] = 0
}
}
return
}
}
}
console.log(grid)
}
solve()
It'll do recursions but it will return to the original grid. I did code this in python and it worked. I don't know why it wont work in javascript.
The problem is that your code will reset the solution that it finds. Although it prints it correctly, that output is made before the top-level call of the function returns. By the time it returns, all cells have been reverted to 0.
Your code needs to use the return value from the recursive call, and if it is true, it should stop looking further and certainly not put back a 0. It should instead immediately exit, and tell its own caller that the solution is there.
So:
let grid = [[2, 5, 0, 0, 3, 0, 9, 0, 1],
[0, 1, 0, 0, 0, 4, 0, 0, 0],
[4, 0, 7, 0, 0, 0, 2, 0, 8],
[0, 0, 5, 2, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 9, 8, 1, 0, 0],
[0, 4, 0, 0, 0, 3, 0, 0, 0],
[0, 0, 0, 3, 6, 0, 0, 7, 2],
[0, 7, 0, 0, 0, 0, 0, 0, 3],
[9, 0, 3, 0, 0, 0, 6, 0, 4]]
function possible(y, x, n){
for (let i = 0; i < 9; i++){
if (grid[i][x] === n){
return false
}
}
for (let i = 0; i < 9; i++){
if (grid[y][i] === n){
return false
}
}
let xx = (Math.floor(x / 3)) * 3
let yy = (Math.floor(y / 3)) * 3
for (let i = 0; i < 3; i++){
for (let j = 0; j < 3; j++){
if (grid[yy + i][xx + j] === n){
return false
}
}
}
return true
}
function solve(){
for (let y = 0; y < 9; y++){
for (let x = 0; x < 9; x++){
if (grid[y][x] === 0){
for (let n = 1; n < 10; n++){
if (possible(y, x, n)){
grid[y][x] = n;
if (solve()) return true; // <--- success!
grid[y][x] = 0;
}
}
return false; // <-- make it boolean
}
}
}
return true; // grid is complete!
}
solve();
for (let row of grid) console.log(...row);
The next improvement, is to avoid the function from mutating a global variable. Maybe turn the logic into a class, making the grid an instance property.

Find the coordinates of all rectangles in a 2D array

Basically what I'm dealing with is a 2D arrays that make up the pixels on grid. The 2D array consists of the numbers 0 through 10 which represents what color each pixel is. The number 0 represents that a pixel is not filled with a color, while all the numbers 1 though 10 represent that a pixel is filled with a color. What I'm trying to figure out is when these values within the array makes a rectangle and then getting the upper left hand coordinate [x1, y1] and lower right hand coordinate[x2, y2] of each separate rectangle.
A representation of the 2D array with it's values ranging from 0 to 4, and each rectangle color coated.
A representation of the points on each rectangle that I would like to get the coordinates of. With brown representing [x1, y1] and pink representing [x2, y2].
Here is the desired output of coordinates that I would like to get:
Green 1:
1: [x1: 0] [y1: 0] [x2: 4] [y2: 2]
2: [x1: 0] [y1: 3] [x2: 2] [y2: 5]
3: [x1: 3] [y1: 5] [x2: 7] [y2: 5]
4: [x1: 10] [y1: 7] [x2: 12] [y2: 10]
5:[x1: 6] [y1: 13] [x2: 8] [y1: 15]
Red 2:
1: [x1: 5] [y1: 0] [x2: 7] [y2: 2]
2: [x1: 10] [y1: 4] [x2: 15] [y2: 6]
3: [x1: 13 ] [y1: 7] [x2: 15] [y2: 10]
4: [x1: 1] [y1: 10] [x2: 5] [y2: 15]
Blue [3]:
1: [x1: 3] [y1: 3] [x2: 7] [y2: 4]
Purple [4]:
1: [x1: 14] [y1: 0] [x2: 15] [y2: 1]
Code that I use:
Note that this only gets the coordinates of rectangles with a value of one. How can I make this work for the other values as well?
const array = [
[1, 1, 1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, 0, 4, 4], //0
[1, 1, 1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, 0, 4, 4], //1
[1, 1, 1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0], //2
[1, 1, 1, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0], //3
[1, 1, 1, 3, 3, 3, 3, 3, 0, 0, 2, 2, 2, 2, 2, 2], //4
[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 2, 2, 2, 2, 2, 2], //5
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2], //6
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2], //7
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2], //8
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2], //9
[0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2], //10
[0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], //11
[0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], //12
[0, 2, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0], //13
[0, 2, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0], //14
[0, 2, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0] //15
];
const W = array[0].length;
const H = array.length;
// get the area covered by rectangles
let totalRectArea = 0;
for (let i = 0; i < W; ++i) {
for (let j = 0; j < H; ++j) {
totalRectArea += array[j][i] > 0 ? 1 : 0;
}
}
const rects = [];
let rectArea = 0;
// find all rectangle until their area matches the total
while (rectArea < totalRectArea) {
const rect = findNextRect();
rects.push(rect);
markRect(rect);
rectArea += (rect.x2 - rect.x1 + 1) * (rect.y2 - rect.y1 + 1);
}
console.log(rects);
function findNextRect() {
// find top left corner
let foundCorner = false;
const rect = { x1: 0, x2: W - 1, y1: 0, y2: H - 1 };
for (let i = 0; i < W; ++i) {
for (let j = 0; j < H; ++j) {
if (array[j][i] === 1) {
rect.x1 = i;
rect.y1 = j;
foundCorner = true;
break;
}
}
if (foundCorner) break;
}
// find bottom right corner
for (let i = rect.x1; i <= rect.x2; ++i) {
if (array[rect.y1][i] !== 1) {
rect.x2 = i - 1;
return rect;
}
for (let j = rect.y1; j <= rect.y2; ++j) {
if (array[j][i] !== 1) {
rect.y2 = j - 1;
break;
}
}
}
return rect;
}
// mark rectangle so won't be counted again
function markRect({ x1, y1, x2, y2 }) {
for (let i = x1; i <= x2; ++i) {
for (let j = y1; j <= y2; ++j) {
array[j][i] = 2;
}
}
}
Got the code from: Find the coordinates of all rectangles of contiguous 1s in a 2D array in Javascript
If anyone can help me with doing this or supply some code, that would be absolutely amazing!!!
I found out how to do it, I used some of StackSlave's code on top of the code I was already using and had to create a function for every differently colored rectangle.
function test(){
array = [
[1, 1, 1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, 0, 4, 4], //0
[1, 1, 1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, 0, 4, 4], //1
[1, 1, 1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0], //2
[1, 1, 1, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0], //3
[1, 1, 1, 3, 3, 3, 3, 3, 0, 0, 2, 2, 2, 2, 2, 2], //4
[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 2, 2, 2, 2, 2, 2], //5
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2], //6
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2], //7
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2], //8
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2], //9
[0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2], //10
[0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], //11
[0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], //12
[0, 2, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0], //13
[0, 2, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0], //14
[0, 2, 2, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0] //15
];
W = array[0].length;
H = array.length;
green = []
red = []
blue = []
purple = []
// get the area covered by rectangles
greenTotalRectArea = 0;
redTotalRectArea = 0;
blueTotalRectArea = 0;
purpleTotalRectArea = 0;
for (let i = 0, a, l = array.length; i < l; i++) {
a = array[i];
for (let n = 0, q = a.length; n < q; n++) {
switch (a[n]) {
case 1:
green.push(n);
greenTotalRectArea = green.length;
break;
case 2:
red.push(n);
redTotalRectArea = red.length;
break;
case 3:
blue.push(n);
blueTotalRectArea = blue.length;
break;
case 4:
purple.push(n);
purpleTotalRectArea = purple.length;
break;
}
}
}
greenRects = [];
greenRectArea = 0;
while (greenRectArea < greenTotalRectArea) {
const greenRect = findGreenRect();
greenRects.push(greenRect);
markRect(greenRect);
greenRectArea += (greenRect.x2 - greenRect.x1 + 1) * (greenRect.y2 - greenRect.y1 + 1);
}
redRects = [];
redRectArea = 0;
while (redRectArea < redTotalRectArea) {
const redRect = findRedRect();
redRects.push(redRect);
markRect(redRect);
redRectArea += (redRect.x2 - redRect.x1 + 1) * (redRect.y2 - redRect.y1 + 1);
}
blueRects = [];
blueRectArea = 0;
while (blueRectArea < blueTotalRectArea) {
const blueRect = findBlueRect();
blueRects.push(blueRect);
markRect(blueRect);
blueRectArea += (blueRect.x2 - blueRect.x1 + 1) * (blueRect.y2 - blueRect.y1 + 1);
}
purpleRects = [];
purpleRectArea = 0;
while (purpleRectArea < purpleTotalRectArea) {
const purpleRect = findPurpleRect();
purpleRects.push(purpleRect);
markRect(purpleRect);
purpleRectArea += (purpleRect.x2 - purpleRect.x1 + 1) * (purpleRect.y2 - purpleRect.y1 + 1);
}
console.log(greenRects);
console.log(redRects);
console.log(blueRects);
console.log(purpleRects);
}
function findGreenRect() {
// find top left corner
let foundGreenCorner = false;
const greenRect = { x1: 0, x2: W - 1, y1: 0, y2: H - 1 };
for (let i = 0; i < W; ++i) {
for (let j = 0; j < H; ++j) {
if (array[j][i] === 1) {
greenRect.x1 = i;
greenRect.y1 = j;
foundGreenCorner = true;
break;
}
}
if (foundGreenCorner) break;
}
// find bottom right corner
for (let i = greenRect.x1; i <= greenRect.x2; ++i) {
if (array[greenRect.y1][i] !== 1) {
greenRect.x2 = i - 1;
return greenRect;
}
for (let j = greenRect.y1; j <= greenRect.y2; ++j) {
if (array[j][i] !== 1) {
greenRect.y2 = j - 1;
break;
}
}
}
return greenRect;
}
function findRedRect() {
// find top left corner
let foundRedCorner = false;
const redRect = { x1: 0, x2: W - 1, y1: 0, y2: H - 1 };
for (let i = 0; i < W; ++i) {
for (let j = 0; j < H; ++j) {
if (array[j][i] === 2) {
redRect.x1 = i;
redRect.y1 = j;
foundRedCorner = true;
break;
}
}
if (foundRedCorner) break;
}
// find bottom right corner
for (let i = redRect.x1; i <= redRect.x2; ++i) {
if (array[redRect.y1][i] !== 2) {
redRect.x2 = i - 1;
return redRect;
}
for (let j = redRect.y1; j <= redRect.y2; ++j) {
if (array[j][i] !== 2) {
redRect.y2 = j - 1;
break;
}
}
}
return redRect;
}
function findBlueRect() {
// find top left corner
let foundBlueCorner = false;
const blueRect = { x1: 0, x2: W - 1, y1: 0, y2: H - 1 };
for (let i = 0; i < W; ++i) {
for (let j = 0; j < H; ++j) {
if (array[j][i] === 3) {
blueRect.x1 = i;
blueRect.y1 = j;
foundBlueCorner = true;
break;
}
}
if (foundBlueCorner) break;
}
// find bottom right corner
for (let i = blueRect.x1; i <= blueRect.x2; ++i) {
if (array[blueRect.y1][i] !== 3) {
blueRect.x2 = i - 1;
return blueRect;
}
for (let j = blueRect.y1; j <= blueRect.y2; ++j) {
if (array[j][i] !== 3) {
blueRect.y2 = j - 1;
break;
}
}
}
return blueRect;
}
function findPurpleRect() {
// find top left corner
let foundPurpleCorner = false;
const purpleRect = { x1: 0, x2: W - 1, y1: 0, y2: H - 1 };
for (let i = 0; i < W; ++i) {
for (let j = 0; j < H; ++j) {
if (array[j][i] === 4) {
purpleRect.x1 = i;
purpleRect.y1 = j;
foundPurpleCorner = true;
break;
}
}
if (foundPurpleCorner) break;
}
// find bottom right corner
for (let i = purpleRect.x1; i <= purpleRect.x2; ++i) {
if (array[purpleRect.y1][i] !== 4) {
purpleRect.x2 = i - 1;
return purpleRect;
}
for (let j = purpleRect.y1; j <= purpleRect.y2; ++j) {
if (array[j][i] !== 4) {
purpleRect.y2 = j - 1;
break;
}
}
}
return purpleRect;
}
// mark rectangle so won't be counted again
function markRect({ x1, y1, x2, y2 }) {
for (let i = x1; i <= x2; ++i) {
for (let j = y1; j <= y2; ++j) {
array[j][i] = 99;
}
}
}
test();

How can I make a completed tetris row flash before its removed?

So I'm making a Tetris game and so far It's pretty much complete except for I'm not able to track a current score or highs score. And If you complete a row that row will be removed. However, I want to add a nice effect to the row before it gets removed. I want all completed rows to flash black and white for 1s. I've tried to set an interval that would use a for loop to change all the colors in the row. But it doesn't work because the game engine is running in an interval so it's called before the other interval can finish. Any Ideas?
const J = [[[1, 0, 0],
[1, 1, 1],
[0, 0, 0]],
[[0, 1, 1],
[0, 1, 0],
[0, 1, 0]],
[[0, 0, 0],
[1, 1, 1],
[0, 0, 1]],
[[0, 0, 1],
[0, 0, 1],
[0, 1, 1]]]
const L = [[[0, 0, 1],
[1, 1, 1],
[0, 0, 0]],
[[0, 1, 0],
[0, 1, 0],
[0, 1, 1]],
[[0, 0, 0],
[1, 1, 1],
[1, 0, 0]],
[[1, 1, 0],
[0, 1, 0],
[0, 1, 0]]]
const O = [[[1, 1],
[1, 1]],
[[1, 1],
[1, 1]],
[[1, 1],
[1, 1]],
[[1, 1],
[1, 1]]]
const I = [[[0, 0, 0, 0],
[1, 1, 1, 1],
[0, 0, 0, 0],
[0, 0, 0, 0]],
[[0, 0, 1, 0],
[0, 0, 1, 0],
[0, 0, 1, 0],
[0, 0, 1, 0]],
[[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 1, 1, 1]],
[[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0]]]
const S = [[[0, 1, 1],
[1, 1, 0],
[0, 0, 0]],
[[0, 1, 0],
[0, 1, 1],
[0, 0, 1]],
[[0, 0, 0],
[0, 1, 1],
[1, 1, 0]],
[[1, 0, 0],
[1, 1, 0],
[0, 1, 0]]]
const T = [[[0, 1, 0],
[1, 1, 1],
[0, 0, 0]],
[[0, 1, 0],
[0, 1, 1],
[0, 1, 0]],
[[0, 0, 0],
[1, 1, 1],
[0, 1, 0]],
[[0, 1, 0],
[1, 1, 0],
[0, 1, 0]]]
const Z = [[[1, 1, 0],
[0, 1, 1],
[0, 0, 0]],
[[0, 0, 1],
[0, 1, 1],
[0, 1, 0]],
[[0, 0, 0],
[1, 1, 0],
[0, 1, 1]],
[[0, 1, 0],
[1, 1, 0],
[1, 0, 0]]]
const col = 10;
const row = 20;
const vacant = "black";
const cvs = document.querySelector("#canvas");
const ctx = cvs.getContext('2d');
const sq = 35;
//collect blocks from blocks.js and assign a color
const blocks = [[J, 'blue'],
[L, 'orange'],
[O, 'yellow'],
[I, 'cyan'],
[S, 'limegreen'],
[T, 'purple'],
[Z, 'red']]
let board = [];
for(let r = 0; r < row; r++) {
board[r] = [];
for(let c = 0; c < col; c++) {
board[r][c] = vacant;
draw(c, r, board[r][c]);
}
}
//create a blueprint function to draw to the board
function draw(x, y, color) {
//set the drawing specifications
ctx.fillStyle = color;
ctx.fillRect(x * sq, y * sq, sq, sq);
ctx.strokeStyle = "white"; //?????change to color
ctx.strokeRect(x * sq, y * sq, sq, sq);
}
function drawBlock(blockPosition, color) {
for(let r = 0; r < blockPosition.length; r++) {
for(let c = 0; c < blockPosition[r].length; c++) {
if(blockPosition[r][c] == 0) continue;
draw(block.x + c, block.y + r, color);
}
}
}
class Block {
constructor(block, color) {
this.blockSet = block;
this.color = color;
this.index = 0;
this.blockPosition = block[this.index];
this.x = 0;
this.y = -1;
}
moveLeft() {
if(!block.hascollided(-1, 0, this.blockPosition)) {
drawBlock(this.blockPosition, vacant);
this.x--;
drawBlock(this.blockPosition, this.color);
}
}
moveRight() {
if(!block.hascollided(1, 0, this.blockPosition)) {
drawBlock(this.blockPosition, vacant);
this.x++;
drawBlock(this.blockPosition, this.color);
}
}
moveDown() {
if(!block.hascollided(0, 1, this.blockPosition)) {
drawBlock(this.blockPosition, vacant);
this.y++;
drawBlock(this.blockPosition, this.color);
} else {
for(let r = 0; r < this.blockPosition.length; r++) {
for(let c = 0; c < this.blockPosition.length; c++) {
if(this.blockPosition[r][c] == 0) continue;
if(this.y + r <= 0) {
alert("Game Over!");
}
board[this.y + r][this.x + c] = this.color;
checkForPoints();
block = newBlock();
}
}
}
}
rotate() {
let nextPosition = this.blockSet[(this.index + 1) % 4];
let kick = 0;
if(block.hascollided(0, 0, nextPosition)) {
if(block.x < col/2) {
if(this.x == -2) kick = 2;
else kick = 1;
}
if(block.x > col/2) {
if(this.color == "cyan") kick = -2;
else kick = -1;
}
}
if(!block.hascollided(kick, 0, nextPosition))
drawBlock(this.blockPosition, vacant);
this.index = (this.index + 1) % 4;
this.blockPosition = this.blockSet[this.index];
this.x += kick;
drawBlock(this.blockPosition, this.color);
}
hascollided(x, y, block) {
for(let r = 0; r < block.length; r++) {
for(let c = 0; c < block.length; c++) {
if(block[r][c] == 0) continue;
let newX = this.x + c + x;
let newY = this.y + r + y;
if(newX < 0 || newX >= col || newY >= row) return true;
if(board[newY][newX] != vacant) return true;
}
}
return false;
}
}
function newBlock() {
let r = Math.floor(Math.random() * blocks.length);
return new Block(blocks[r][0], blocks[r][1]);
}
function checkForPoints() {
let newRows = [];
for(let r = 0; r < board.length; r++) {
if(board[r].every(squareCheck)) {
for(let c = 0; c < col; c++) {
newRows.unshift(vacant);
}
board.splice(r, 1);
board.unshift(newRows);
redraw();
}
}
function squareCheck(sq) {
if(sq !== vacant) return true;
else return false;
}
function redraw() {
for(let r = 0; r < row; r++) {
for(let c = 0; c < col; c++) {
draw(c, r, board[r][c]);
}
}
}
}
block = newBlock();
document.addEventListener('keydown', (e)=>{
if(e.key == "ArrowLeft") block.moveLeft();
else if(e.key == "ArrowUp") block.rotate();
else if(e.key == "ArrowRight") block.moveRight();
else if(e.key == "ArrowDown") block.moveDown();
})
let dropStart = Date.now();
function drop() {
let now = Date.now();
let delta = now - dropStart;
if(delta > 800) {
dropStart = Date.now();
block.moveDown();
}
requestAnimationFrame(drop);
}
drop();
body, html {
outline: 1px solid red;
margin: 0;
padding: 0;
}
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-color: darkgrey;
}
canvas {
background-color: black;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles.css">
<title>Tetris</title>
</head>
<body>
<canvas id="canvas" width="350" height="700"></canvas>
<script src="js/script.js"></script>
</body>
</html>
You just need to manage your row removal process into 2 steps, after detecting that the row has matched, set the value in the cells so that they are in the highlight state, then only remove the row when it was already in the highlight state.
The obvious injection point is your checkForRowMath() function, but the timing bit is a bit trickier. Using your pattern of time delta for determining when to re-evaluate the state of the board, we can do the same thing to trigger the highlighted row to be removed as a separated async-logic process to the rest of the game logic.
A few pointers, in the absence of documentation or comments, it helps if you name your functions based on the output, not necessarily the process. Specifically squareCheck, if this is renamed to notVacant then the output or result of the function is easier to interpret, but also you can identify points in the logic where we might want to detect other states, in this case the first thing i thought of was isHighlight. The same goes for checkForPoints() this name doesn't really describe the outcome, it describes the process that it uses, when really this could be named something like checkForRowMatch()
So sticking with your current style, do the following:
Define a new constant highlight = "white"
Define a new global variable hlStart that will record the start of the highlight state
In checkForPoints break the collision handling into two steps, first set the cells to highlight and set the time into hlStart
then on the next pass, if all the cells are high lighted, only then do you remove them
In drop (the main game loop) add some logic to call checkForPoints() after a delay to manage the 2nd step in the collision logic.
Drop now looks something like this:
let dropStart = Date.now();
let hlStart = null;
function drop() {
let now = Date.now();
let delta = now - dropStart;
if(delta > 800) {
dropStart = Date.now();
block.moveDown();
}
if(hlStart) {
let hlDelta = Date.now() - hlStart;
if (hlDelta > 600) {
hlStart = null;
checkForPoints();
}
}
requestAnimationFrame(drop);
}
And checkForPoints() has been changed to separately highlight and then later remove the matched row:
In this snip it is possible for multiple rows to be matched at the same time, but the user would never see the result of repainting the board between individual row matches that are detected, so we set a flag to indicate that any change on the board requires a re-draw, then only call redraw once.
function checkForPoints() {
let newRows = [];
let changed = false;
for(let r = 0; r < board.length; r++) {
// if the row has already been highlighted
// then this time we can remove it
if(board[r].every(isHighlight)) {
for(let c = 0; c < col; c++) {
newRows.unshift(vacant);
}
board.splice(r, 1);
board.unshift(newRows);
changed = true;
}
// Only check for notVacant if the row is not highlighted.
else if(board[r].every(notVacant)) {
for(let c = 0; c < col; c++) {
board[r][c] = highlight;
}
// record the start time for the highlight animation
hlStart = Date.now();
changed = true;
}
}
// because multiple rows could be affected at the same time, moved
// redraw out to a single call for each whole board pass
if (changed)
redraw();
function notVacant(sq) {
if(sq !== vacant) return true;
else return false;
}
function isHighlight(sq) {
if(sq === highlight) return true;
else return false;
}
function redraw() {
for(let r = 0; r < row; r++) {
for(let c = 0; c < col; c++) {
draw(c, r, board[r][c]);
}
}
}
}
Putting this together we get the following solution:
const J = [[[1, 0, 0],
[1, 1, 1],
[0, 0, 0]],
[[0, 1, 1],
[0, 1, 0],
[0, 1, 0]],
[[0, 0, 0],
[1, 1, 1],
[0, 0, 1]],
[[0, 0, 1],
[0, 0, 1],
[0, 1, 1]]]
const L = [[[0, 0, 1],
[1, 1, 1],
[0, 0, 0]],
[[0, 1, 0],
[0, 1, 0],
[0, 1, 1]],
[[0, 0, 0],
[1, 1, 1],
[1, 0, 0]],
[[1, 1, 0],
[0, 1, 0],
[0, 1, 0]]]
const O = [[[1, 1],
[1, 1]],
[[1, 1],
[1, 1]],
[[1, 1],
[1, 1]],
[[1, 1],
[1, 1]]]
const I = [[[0, 0, 0, 0],
[1, 1, 1, 1],
[0, 0, 0, 0],
[0, 0, 0, 0]],
[[0, 0, 1, 0],
[0, 0, 1, 0],
[0, 0, 1, 0],
[0, 0, 1, 0]],
[[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[1, 1, 1, 1]],
[[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0],
[0, 1, 0, 0]]]
const S = [[[0, 1, 1],
[1, 1, 0],
[0, 0, 0]],
[[0, 1, 0],
[0, 1, 1],
[0, 0, 1]],
[[0, 0, 0],
[0, 1, 1],
[1, 1, 0]],
[[1, 0, 0],
[1, 1, 0],
[0, 1, 0]]]
const T = [[[0, 1, 0],
[1, 1, 1],
[0, 0, 0]],
[[0, 1, 0],
[0, 1, 1],
[0, 1, 0]],
[[0, 0, 0],
[1, 1, 1],
[0, 1, 0]],
[[0, 1, 0],
[1, 1, 0],
[0, 1, 0]]]
const Z = [[[1, 1, 0],
[0, 1, 1],
[0, 0, 0]],
[[0, 0, 1],
[0, 1, 1],
[0, 1, 0]],
[[0, 0, 0],
[1, 1, 0],
[0, 1, 1]],
[[0, 1, 0],
[1, 1, 0],
[1, 0, 0]]]
const col = 10;
const row = 20;
const vacant = "black";
// 1. define the highlight color / state
const highlight = "white";
const cvs = document.querySelector("#canvas");
const ctx = cvs.getContext('2d');
const sq = 35;
//collect blocks from blocks.js and assign a color
const blocks = [[J, 'blue'],
[L, 'orange'],
[O, 'yellow'],
[I, 'cyan'],
[S, 'limegreen'],
[T, 'purple'],
[Z, 'red']]
let board = [];
for(let r = 0; r < row; r++) {
board[r] = [];
for(let c = 0; c < col; c++) {
board[r][c] = vacant;
draw(c, r, board[r][c]);
}
}
//create a blueprint function to draw to the board
function draw(x, y, color) {
//set the drawing specifications
ctx.fillStyle = color;
ctx.fillRect(x * sq, y * sq, sq, sq);
ctx.strokeStyle = "white"; //?????change to color
ctx.strokeRect(x * sq, y * sq, sq, sq);
}
function drawBlock(blockPosition, color) {
for(let r = 0; r < blockPosition.length; r++) {
for(let c = 0; c < blockPosition[r].length; c++) {
if(blockPosition[r][c] == 0) continue;
draw(block.x + c, block.y + r, color);
}
}
}
class Block {
constructor(block, color) {
this.blockSet = block;
this.color = color;
this.index = 0;
this.blockPosition = block[this.index];
this.x = 0;
this.y = -1;
}
moveLeft() {
if(!block.hascollided(-1, 0, this.blockPosition)) {
drawBlock(this.blockPosition, vacant);
this.x--;
drawBlock(this.blockPosition, this.color);
}
}
moveRight() {
if(!block.hascollided(1, 0, this.blockPosition)) {
drawBlock(this.blockPosition, vacant);
this.x++;
drawBlock(this.blockPosition, this.color);
}
}
moveDown() {
if(!block.hascollided(0, 1, this.blockPosition)) {
drawBlock(this.blockPosition, vacant);
this.y++;
drawBlock(this.blockPosition, this.color);
} else {
for(let r = 0; r < this.blockPosition.length; r++) {
for(let c = 0; c < this.blockPosition.length; c++) {
if(this.blockPosition[r][c] == 0) continue;
if(this.y + r <= 0) {
alert("Game Over!");
}
board[this.y + r][this.x + c] = this.color;
checkForPoints();
block = newBlock();
}
}
}
}
rotate() {
let nextPosition = this.blockSet[(this.index + 1) % 4];
let kick = 0;
if(block.hascollided(0, 0, nextPosition)) {
if(block.x < col/2) {
if(this.x == -2) kick = 2;
else kick = 1;
}
if(block.x > col/2) {
if(this.color == "cyan") kick = -2;
else kick = -1;
}
}
if(!block.hascollided(kick, 0, nextPosition))
drawBlock(this.blockPosition, vacant);
this.index = (this.index + 1) % 4;
this.blockPosition = this.blockSet[this.index];
this.x += kick;
drawBlock(this.blockPosition, this.color);
}
hascollided(x, y, block) {
for(let r = 0; r < block.length; r++) {
for(let c = 0; c < block.length; c++) {
if(block[r][c] == 0) continue;
let newX = this.x + c + x;
let newY = this.y + r + y;
if(newX < 0 || newX >= col || newY >= row) return true;
if(board[newY][newX] != vacant) return true;
}
}
return false;
}
}
function newBlock() {
let r = Math.floor(Math.random() * blocks.length);
return new Block(blocks[r][0], blocks[r][1]);
}
function checkForPoints() {
let newRows = [];
let changed = false;
for(let r = 0; r < board.length; r++) {
// if the row has already been highlighted
// then this time we can remove it
if(board[r].every(isHighlight)) {
for(let c = 0; c < col; c++) {
newRows.unshift(vacant);
}
board.splice(r, 1);
board.unshift(newRows);
changed = true;
}
// Only check for notVacant if the row is not highlighted.
else if(board[r].every(notVacant)) {
for(let c = 0; c < col; c++) {
board[r][c] = highlight;
}
// record the start time for the highlight animation
hlStart = Date.now();
changed = true;
}
}
// because multiple rows could be affected at the same time, moved
// redraw out to a single call for each whole board pass
if (changed)
redraw();
function notVacant(sq) {
if(sq !== vacant) return true;
else return false;
}
function isHighlight(sq) {
if(sq === highlight) return true;
else return false;
}
function redraw() {
for(let r = 0; r < row; r++) {
for(let c = 0; c < col; c++) {
draw(c, r, board[r][c]);
}
}
}
}
block = newBlock();
document.addEventListener('keydown', (e)=>{
if(e.key == "ArrowLeft") block.moveLeft();
else if(e.key == "ArrowUp") block.rotate();
else if(e.key == "ArrowRight") block.moveRight();
else if(e.key == "ArrowDown") block.moveDown();
})
let dropStart = Date.now();
let hlStart = null;
function drop() {
let now = Date.now();
let delta = now - dropStart;
if(delta > 800) {
dropStart = Date.now();
block.moveDown();
}
if(hlStart) {
let hlDelta = Date.now() - hlStart;
if (hlDelta > 600) {
hlStart = null;
checkForPoints();
}
}
requestAnimationFrame(drop);
}
drop();
body, html {
outline: 1px solid red;
margin: 0;
padding: 0;
}
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-color: darkgrey;
}
canvas {
background-color: black;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="styles.css">
<title>Tetris</title>
</head>
<body>
<canvas id="canvas" width="350" height="700"></canvas>
<script src="js/script.js"></script>
</body>
</html>

Passing function as parameter in JavaScript

Have to port a little piece of code from JavaScript to JAVA:
var N = 3;
var w = 8, h = 8;
var matrix = [
[1, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 0],
[1, 1, 1, 1, 1, 1, 1, 1]
];
//[1, 1, 1, 1, 1, 1, 0, 1, 1]
console.log(complexFunction(2, 1));
//first function
function simpleFunction(f_) {
var out = new Array(N * N);
for (var y = 0; y < N; y++) {
for (var x = 0; x < N; x++) {
out[x + y * N] = f_(x, y);
}
}
return out;
};
//second function
function complexFunction(x_, y_) {
return simpleFunction(function (dx, dy) { return matrix[(x + dx) % w][(y + dy) % h]; });
};
Have experimented with Callable without any success.
Ideally, the ported result should have the same structure as JavaScript source.
Unfortunately, I have to use Java 7, which doesn't have lambdas,
so had to make everything as simple as possible:
Integer[] complexFunction(int dx_, int dy_){
Integer[] out = new Integer[N * N];
for (Integer y = 0; y < N; y++) {
for (Integer x = 0; x < N; x++) {
out[x + y * N] = matrix[(x + dx_) % w][(y + dy_) % h];
}
}
return out;
}

Categories

Resources