Conways Game of Life p5js [closed] - javascript

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 4 years ago.
Improve this question
am trying to create conways game of life using p5js. (p5js website)
here is a link to a gif of the simulation: Google Drive Link
I have two 2d arrays, one to hold the current grid and one to hold the updated grid. the arrays hold a value of 0 = empty or 1 = active. I use an image to draw the grid with 1 pixel representing a cell in the grid. Now when I run the program it does something I cant explain (Explosive Growth.. See gif), but I think it has something to do with the rules to decide whether a cell should be active or not. Or it could possibly be something to do with me have multiple grids?
any help would be appreciated to help find whats wrong and why its producing these patterns
heres my logic to decide whether a cell should be active or not
for(var x = 0; x < width; x++){
for(var y = 0; y < height; y++){
//get cells neighbors
var n = getNeighbors(x,y);
//if cell is active
if(grid[x][y] == 1){
// #1 - Any live cell with fewer than two live neighbors dies, as if by underpopulation.
if(n < 2){
newGrid[x][y] = 0;
}else if(n > 3){// #3 -Any live cell with more than three live neighbors dies, as if by overpopulation.
newGrid[x][y] = 0;
}
// #2 - Any live cell with two or three live neighbors lives on to the next generation.
// this doesnt need a statement since if neighbors value is not < 2 or not > 3 then the value can only be 2 or 3
}else{
// #4 - Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
if(n == 3){
newGrid[x][y] = 1;
}
}
}
}
and just for reference here is the whole source with comments(135 lines)
var grid = [];
var newGrid = [];
var img;
function setup() {
createCanvas(400, 400);
frameRate(10);
//populate grid array
for (var i = 0; i < width; i++) {
grid[i] = [];
for (var j = 0; j < height; j++) {
var r = random(0, 1);
if (r > 0.9) {
grid[i][j] = 1;
} else {
grid[i][j] = 0;
}
}
}
//populate newgrid
newGrid = grid;
//create img object
img = createImage(width, height);
//populate img pixels
drawImageUsingGrid();
//draw img
image(img, 0, 0);
}
function draw() {
//loop through grid
for (var x = 0; x < width; x++) {
for (var y = 0; y < height; y++) {
//get cells neighbors
var n = getNeighbors(x, y);
//if cell is active
if (grid[x][y] == 1) {
// #1 - Any live cell with fewer than two live neighbors dies, as if by underpopulation.
if (n < 2) {
newGrid[x][y] = 0;
} else if (n > 3) { // #3 -Any live cell with more than three live neighbors dies, as if by overpopulation.
newGrid[x][y] = 0;
}
// #2 - Any live cell with two or three live neighbors lives on to the next generation.
// this doesnt need a statement since if neighbors value is not < 2 or not > 3 then the value can only be 2 or 3
} else {
// #4 - Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
if (n == 3) {
newGrid[x][y] = 1;
}
}
}
}
//set current grid to the new updated grid
grid = newGrid;
//set img pixels based upon grid
drawImageUsingGrid();
//draw img
image(img, 0, 0);
}
function getNeighbors(x, y) {
//hold # of neighbors
var neighbors = 0;
//check if neighbor exists at index and is active
if (grid[x] && grid[x][y - 1] && grid[x][y - 1] == 1) { //top
neighbors++;
}
if (grid[x + 1] && grid[x + 1][y] && grid[x + 1][y] == 1) { //right
neighbors++;
}
if (grid[x] && grid[x][y + 1] && grid[x][y + 1] == 1) { //bottom
neighbors++;
}
if (grid[x - 1] && grid[x - 1][y] && grid[x - 1][y] == 1) { //left
neighbors++;
}
//diagonal neighbors
if (grid[x - 1] && grid[x - 1][y - 1] && grid[x - 1][y - 1] == 1) { //topleft
neighbors++;
}
if (grid[x + 1] && grid[x + 1][y - 1] && grid[x + 1][y - 1] == 1) { //topright
neighbors++;
}
if (grid[x - 1] && grid[x - 1][y + 1] && grid[x - 1][y + 1] == 1) { //bottomleft
neighbors++;
}
if (grid[x + 1] && grid[x + 1][y + 1] && grid[x + 1][y + 1] == 1) { //bottomright
neighbors++;
}
return neighbors;
}
function drawImageUsingGrid() {
//load img pixels to be edited
img.loadPixels();
//2d for loop
for (var i = 0; i < img.width; i++) {
for (var j = 0; j < img.height; j++) {
if (grid[i][j] == 0) {
//get pixel at x,y
var pix = getPixelIndex(i, j);
//set pixel rgba
img.pixels[pix + 0] = 255;
img.pixels[pix + 1] = 255;
img.pixels[pix + 2] = 255;
img.pixels[pix + 3] = 255;
} else {
//get pixel at x,y
var pix = getPixelIndex(i, j);
//set pixel rgba
img.pixels[pix + 0] = 0;
img.pixels[pix + 1] = 0;
img.pixels[pix + 2] = 0;
img.pixels[pix + 3] = 255;
}
}
}
//update img pixels
img.updatePixels();
}
function getPixelIndex(x, y) {
return (x + y * width) * 4;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.js"></script>

Related

The algorithm problem: Unique Paths III. Using backtracing pattern in javascript and not work

On a 2-dimensional grid, there are 4 types of squares:
1 represents the starting square.  There is exactly one starting square.
2 represents the ending square.  There is exactly one ending square.
0 represents empty squares we can walk over.
-1 represents obstacles that we cannot walk over.
Return the number of 4-directional walks from the starting square to the ending square, that walk over every non-obstacle square exactly once.
source:力扣(LeetCode)
link:https://leetcode-cn.com/problems/unique-paths-iii
i'm trying to use backtrack pattern to solve this problem
here is my code
/**
* #param {number[][]} grid
* #return {number}
*/
var uniquePathsIII = function(grid) {
let m = grid.length,
n = grid[0].length;
let start, targetIndex1,targetIndex2;
let res = 0;
let zero_counts = 0;
for(let i = 0; i < m; i++){
for(let j = 0; j < n; j++){
if(grid[i][j] == 1){
start = [i,j]
}
else if(grid[i][j] == 0){
zero_counts += 1;
}
else if(grid[i][j] == 2){
targetIndex1 = i;
targetIndex2 = j;
}
}
}
const backtrace = (i, j, zero_count) => {
if( i < 0 || i >= m ||
j < 0 || j >= n ||
grid[i][j] == -1 || zero_count < 0)
{
return;
}
if(i == targetIndex1 && j == targetIndex2 ){
if(zero_count == 0)
{
console.log("yes")
res += 1;
}
return
}
grid[i][j] = -1;
backtrace(i+1, j, zero_count - 1)
backtrace(i-1, j, zero_count - 1)
backtrace(i, j+1, zero_count - 1)
backtrace(i, j-1, zero_count - 1)
grid[i][j] = 0;
}
backtrace(start[0], start[1], zero_counts);
return res;
};
test sample:
[[1,0,0,0],[0,0,0,0],[0,0,2,-1]]
expect result:
2
acutal result:
0
Perhaps a simpler solution is to use Depth First Search to solve Unique Paths III, as shown here.
The concept is that you take a point and then traverse in all directions until you hit an obstacle.
The guts are as follows:
int totalPaths = dfs(grid, x+1, y, zero) +
dfs(grid, x, y+1, zero) +
dfs(grid, x-1, y, zero) +
dfs(grid, x, y-1, zero);

How can I code this Cellular Automata project more efficiently?

I am new to programming and I have an interest in cellular automata, so I decided to try to script one using JavaScript both for coding practice and so that I could make a personalised one. The cellular automata project I created is for a simple binary (black and white) 2D table CA which looks at the colours of the 8 nearest neighbors of a cell and the colour of the cell itself and updates its colour depending on the rules given in the 'ruleset' table below the CA table. Only problem is the code that I wrote takes forever to process each iteration, clearly because of all the large loops it needs. In fact as I am writing this I realise that I can reduce the processing power needed by stopping the comparison search between the current neighbour colour configuration and the set of all possible configurations when the if statement finds the correct configuration, but this will probably not reduce the processing power needed by the amount that I would prefer and I am sure that there are more ways to make it faster. If anybody could give me some advice on how to reduce the processing power even more I would really appreciate it. Also, please explain your answers in laymans terms. Thank you! Here is the code:
<!DOCTYPE html>
<html>
<head>
<title></title>
<style>
table {border-collapse: collapse;}
table, td, th {border: 1px solid black;}
td {width:1px; height:1px;}
</style>
</head>
<body>
<button onclick="Toggle()">Toggle</button>
<!-- Toggle runs the Iterate function with a setInterval -->
<button onclick="Iterate()">Iterate</button>
<br>
<script>
document.write("<table>")
for (row=0; row<100; row++) {
document.write("<tr>")
for (col=0; col<100; col++)
{document.write("<td id = 'R" + row + "C" + col + "' style='background-color: white' ondblclick='MouseDown(this)' onmousedown='MouseDown(this)' onmouseover='MouseUp(this)'>" + "</td>")}
document.write("</tr>")}
document.write("</table>")
// This is the cellular automata table
document.write("<br>")
document.write("<table>")
for (row=0; row<16; row++) {
document.write("<tr>")
for (col=0; col<32; col++)
{document.write("<td id = 'id" + (col+32*row) + "' style='background-color: white' ondblclick='MouseDown(this)' onmousedown='MouseDown(this)' onmouseover='MouseUp(this)'>" + "</td>")}
document.write("</tr>")}
document.write("</table>")
// This is the 'ruleset' table
let determiner = 0
function MouseDown(cell) {determiner = 1
if (cell.style.backgroundColor == "white") {cell.style.backgroundColor = "black"}
else {cell.style.backgroundColor = "white"}}
window.addEventListener('mouseup', function(event){determiner = 0})
function MouseUp(cell) {if (determiner == 1) {
if (cell.style.backgroundColor == "white") {cell.style.backgroundColor = "black"}
else {cell.style.backgroundColor = "white"}}}
// This section provides the click & drag cell colour changing functions
for (i=0; i<512; i++) {
if (i % 512 < 256){this["j1"] = "white"} else {this["j1"] = "black"}
if (i % 256 < 128){this["j2"] = "white"} else {this["j2"] = "black"}
if (i % 128 < 64){this["j3"] = "white"} else {this["j3"] = "black"}
if (i % 64 < 32){this["j4"] = "white"} else {this["j4"] = "black"}
if (i % 32 < 16){this["j5"] = "white"} else {this["j5"] = "black"}
if (i % 16 < 8){this["j6"] = "white"} else {this["j6"] = "black"}
if (i % 8 < 4){this["j7"] = "white"} else {this["j7"] = "black"}
if (i % 4 < 2){this["j8"] = "white"} else {this["j8"] = "black"}
if (i % 2 < 1){this["j9"] = "white"} else {this["j9"] = "black"}
this["compare"+i] = {unit00: j1,unit01: j2,unit02: j3,unit10: j4,unit11: j5,unit12: j6,unit20: j7,unit21: j8,unit22: j9}
}
// This creates an object for each possible block of 9 cells to compare with the actual blocks of cells around each cell in the Iterate() function
function Iterate() {
this["groupvec"] = []
for (i=0; i<100; i++) {
for (j=0; j<100; j++) {
if (i !== 0 && i !== 99) {rownum = [i-1, i, i+1]}
else if (i == 0) {rownum = [99, 0, 1]}
else if (i == 99) {rownum = [98, 99, 0]}
if (j !== 0 && j !== 99) {colnum = [j-1, j, j+1]}
else if (j == 0) {colnum = [99, 0, 1]}
else if (j == 99) {colnum = [98, 99, 0]}
this["group"+"R"+i+"C"+j] = {}
for (r in rownum) {
for (c in colnum) {
this["group"+"R"+i+"C"+j]['unit'+r.toString()+c.toString()] = document.getElementById("R" + rownum[r] + "C" + colnum[c]).style.backgroundColor
}}
this["groupvec"].push( JSON.stringify(this["group"+"R"+i+"C"+j]) )
}}
for (i=0; i<100; i++) {
for (j=0; j<100; j++) {
for (k=0; k<512; k++) {
if (groupvec[j+(100*i)] == JSON.stringify(window["compare"+k.toString()])) {
document.getElementById("R"+i+"C"+j).style.backgroundColor = document.getElementById("id"+k).style.backgroundColor
}}}}}
// This function finds the colours of the cells in a block of 9 cells around each cell, compares them with the 'compare' objects and then changes their colour to the colour of the 'ruleset' table with the same index as the 'compare' object.
let toggler = null
function Toggle() {
if (toggler == null) {toggler = setInterval(Iterate.bind(null), 1000)}
else {clearInterval(toggler); toggler = null}
}
// This provides an automated run function for the CA
</script>
</body>
</html>
Your code loops 5 210 000 times for every iteration (100 rows * 100 columns * 3 cells * 3 cells + 100 rows * 100 columns * 512 ruleset cells). That's quite a lot of work for every iteration, and it is exacerbated by your use of an HTML <table>, from and to which you are constantly reading and writing styles.
If you want a more tenable solution, try using a Canvas for displaying your CA's state, and JavaScript arrays for handling the state. You could still perhaps use a table to set the ruleset at the start, and then store it in an array, so that when you check it you are checking an in-memory array of data.
Your code then might look something like this, which loops 80 000 times (although it might be more if you incorporate your rulesets):
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const width = 100;
const height = 100;
const cellWidth = 4;
const cellHeight = 4;
// Access cells with automaton[row][column]
let automaton = Array(height).fill(Array(width).fill(0)));
// Initialise your automaton somehow
// ...
function iterate() {
// Create the next automaton state to write updates to
let nextAutomaton = Array(height).fill(Array(width).fill(0)));
for (let y = 0; y < height; ++y) {
for (let x = 0; x < width; ++x) {
// Get the surrounding 8 cells
// (n + MAX + 1) % MAX will wrap around
// (n + MAX - 1) % MAX will wrap around
const surroundingIndices = [
{ x: x, y: (y + height - 1) % height }, // above
{ x: (x + width + 1) % width, y: (y + height - 1) % height }, // above right
{ x: (x + width + 1) % width, y: y }, // right
{ x: (x + width + 1) % width, y: (y + height + 1) % height }, // below right
{ x: x, y: (y + height + 1) % height }, // below
{ x: (x + width - 1) % width, y: (y + height + 1) % height }, // below left
{ x: (x + width - 1) % width, y: y }, // left
{ x: (x + width - 1) % width, y: (y + height - 1) % height } // above left
];
for (int i = 0; i < 8; ++i) {
const cell = automaton[surroundingIndices.y][surroundingIndices.x];
// Do something based on the value of this surrounding cell
// This could depend on your ruleset
// ...
}
// Set this cell's value in the next state
nextAutomaton[y][x] = 1;
// Render the cell
context.fillStyle = 'red';
context.fillRect(x, y, cellWidth, cellHeight);
}
}
// Overwrite the old automaton state
automaton = nextAutomaton;
}
For animating your automaton, you will want to use window.requestAnimationFrame, which must call itself recursively with the iterate function, and which you can start or stop with your toggle button (see window.cancelAnimationFrame).

Stuck programming Conway's "Game of Life" in JS

We have to program a JavaScript version of Conway's Game of Life for a school project, but we're stuck on looping the edges. The whole thing works fine, but the function that calculates the number of neighbors doesn't work on the cells that are on the edges (because it has to evaluate values outside of the array, which are undefined). We've tried several options, but they all alter the functionality of the rest of the program.
What should we add for it to work on the edges of the grid?
var totalNeighbors = function(x, y) {
var total = 0;
if (x > 0 && cells[(x - 1)][y] == 1) {
total++;
}
if (x < (width - 1) && cells[x + 1][y] == 1) {
total++;
}
if (y > 0 && cells[x][y - 1] == 1) {
total++;
}
if (y < (height - 1) && cells[x][y + 1] == 1) {
total++;
}
if (y > 0 && x > 0 && cells[x - 1][y - 1] == 1) {
total++;
}
if (y > 0 && x < (width - 1) && cells[x + 1][y - 1] == 1) {
total++;
}
if (y < (height - 1) && x > 0 && cells[x - 1][y + 1] == 1) {
total++;
}
if (y < (height - 1) && x < (width - 1) && cells[x + 1][y + 1] == 1) {
total++;
}
return total;
};
Thanks!
I'd go with something more like this:
As you can see, I refactored a little bit.
var isvalid = function(x, y) {
/*
* This returns 1 if cells[x][y] == 1.
* Otherwise, we return 0.
* NOTE: If cells[x, y] is out of bounds, we return 0.
* GLOBALS USED: cells, width, and height.
*/
//This returns true if (index < size && index >= 0)
//Used to check that index is not an invalid index.
var inbounds = function (size, index) {
return (index >= 0 && index < size);
};
//given point is out of bounds
if (!inbounds(width, x) || !inbounds(height, y)) {
return 0;
}
//everything is good
return (cells[x][y] === 1) ? 1 : 0;
};
var totalNeighbors = function(x, y) {
var total = 0;
//cells[x-1][y]
total += isvalid(x-1, y);
//cells[x + 1][y]
total += isvalid(x+1, y);
//cells[x][y - 1]
total += isvalid(x, y-1);
//cells[x][y + 1]
total += isvalid(x, y+1);
//cells[x - 1][y - 1]
total += isvalid(x-1, y-1);
//cells[x + 1][y - 1]
total += isvalid(x+1, y-1);
//cells[x - 1][y + 1]
total += isvalid(x-1, y+1);
//cells[x + 1][y + 1]
total += isvalid(x+1, y+1);
return total;
};
PS: Your original code sample is 37 lines without comments. My code sample is 52 lines with comments and 33 lines without comments.
As near as I can figure, this way is cleaner and shorter. ;)

Conway game of life in javascript( best sol [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
i am writing a code for conway game of life...... i am taking 2> arrays one fr old generation. and one for 2 nd genration.
**rules are : The universe of the Game of Life is an infinite two-dimensional orthogonal grid of square cells, each of which is in one of two possible states, alive or dead. Every cell interacts with its eight neighbours, which are the cells that are horizontally, vertically, or diagonally adjacent. At each step in time, the following transitions occur: **1.Any live cell with fewer than two live neighbours dies, as if caused by under-population.
2.Any live cell with two or three live neighbours lives on to the next generation.
3.Any live cell with more than three live neighbours dies, as if by overcrowding.
4.Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.**
The initial pattern constitutes the seed of the system. The first generation is created by applying the above rules simultaneously to every cell in the seed—births and deaths occur simultaneously, and the discrete moment at which this happens is sometimes called a tick (in other words, each generation is a pure function of the preceding one). The rules continue to be applied repeatedly to create further generations.**
here is the code
I am getting a soln but i guess its not giving me the correct solution becuase its not checking the neighbors of the corners. i have marked that part
**
window.conway =
{
};
window.conway.maingame =
{
};
conway.maingame = function(width, height)
{
window.a = [];
this.width = width;
this.height = height;
this.map = new Array(width);
for( i = 0; i < this.width; i++)
{
this.map[i] = new Array(height);
}
console.log(this.map, "map")
}
conway.maingame.prototype.randomize = function()
{
for( y = 0; y < this.height; y++)
{
//console.log("enter for loop")
for( x = 0; x < this.width; x++)
{
if(Math.random() > .5)
{
i =true;
}
else
{
i = false;
}
//console.log("enter function")
this.set(x, y, i);
}
}
}
conway.maingame.prototype.set = function(x, y, val)
{
x = x % this.width;
y = y % this.height;
this.map[x][y] = val;
console.log(this.map, "map2");
}
conway.maingame.prototype.get = function(x, y)
{
x = x % this.width;
y = y % this.height;
return this.map[x][y];
}
*********************************************************************************
conway.maingame.prototype.neighbors = function(x, y)
{
count = 0;
if(x > 0 && y > 0 && this.get(x + 1, y + 1))
{
console.log(this.get(x + 1, y + 1), "value neighbor");
count++;
console.log(count);
}
if(x > 0 && y > 0 && this.get(x + 1, y))
{
console.log(this.get(x + 1, y), "vallue neighbor");
count++;
console.log(count);
}
if(x > 0 && y > 0 && this.get(x + 1, y - 1))
{
console.log(this.get(x + 1, y - 1), "vallue neighbor");
count++;
console.log(count);
}
if(x > 0 && y >=0 && this.get(x, y - 1))
{
console.log(this.get(x + 1, y - 1), "vallue neighbor");
count++;
console.log(count);
}
if(x > 0 && y > 0 && this.get(x - 1, y - 1))
{
console.log(this.get(x + 1, y - 1), "vallue neighbor");
count++;
console.log(count);
}
if(x > 0 && y > 0 && this.get(x - 1, y))
{
console.log(this.get(x + 1, y - 1), "vallue neighbor");
count++;
console.log(count);
}
if(x > 0 && y > 0 && this.get(x - 1, y + 1))
{
console.log(this.get(x + 1, y - 1), "vallue neighbor");
count++;
console.log(count);
}
if(x > 0 && y > 0 &&this.get(x, y + 1))
{
console.log(this.get(x + 1, y - 1), "vallue neighbor");
count++;
console.log(count);
}
return count;
}***
conway.maingame.prototype.newgeneration = function()
{
var newMap = new Array(this.width);
for( i = 0; i < this.width; i++)
{
newMap[i] = new Array(this.height);
}
for(var y = 0; y < this.height; y++)
{
for(var x = 0; x < this.width; x++)
{
console.log("enter all for");
newMap[x][y] = this.get(x, y);
console.log(newMap, "newarray");
//Rule 1: any live cell with fewer than two live neighbors dies
if(this.get(x, y) == true && this.neighbors(x, y) < 2)
{
newMap[x][y] = false;
console.log("rule1");
}
//Rule 2: Any live cell with two or three live neighbours lives on to the next generation
if(this.get(x, y) == true && this.neighbors(x, y) == 2 || this.neighbors(x, y) == 3)
{
newMap[x][y] = true
console.log("rule2");
}
//Rule 3: any live cell with more than three live neighbors dies
if(this.get(x, y) == true && this.neighbors(x, y) > 3)
{
newMap[x][y] = false;
console.log("rule3");
}
//Rule 4: any dead cell with exactly three live neighbors becomes a live cell
if(this.get(x, y) == false && this.neighbors(x, y) == 3)
{
newMap[x][y] = true;
console.log("rule4");
}
}
}
this.map = newMap;
console.log(this.map,"new generation")
}
**
I trawled through your code in JSHint and fixed all of the issues, wrote some glue to test it in the browser and here's the result: jsfiddle.
It looks like it's working to me, so I think the problem must have been due to one of the dozens of warnings that JSHint flagged up.
Re: your assertion that the issue was due to the corners, that's what these lines are for:
x = x % this.width;
y = y % this.height;
In my test case I'm using a 10 x 10, so when it comes to check the neighbours of (9,9) it looks at (10, 10) and (10 % 10, 10 % 10) is (0, 0), thus avoiding looking outside the array. I believe this is what's known as a toroidal array.
The lesson we learn from this? Keep on top of the small issues, and the big issues will take care of themselves.

algorithm connect four javascript

Hy,
I am trying to implement an Connect Four Game in javascript / jQuery. First off this is no homework or any other duty. I'm just trying to push my abilities.
My "playground" is a simple html table which has 7 rows and 6 columns.
But now I have reached my ken. I'm stuck with the main functionality of checking whether there are 4 same td's around. I am adding a class to determine which color it should represent in the game.
First I thought I could handle this with .nextAll() and .prevAll() but this does not work for me because there is no detection between.
Because I was searching for siblings, when adding a new Item and just looked up the length of siblings which were found and if they matched 4 in the end I supposed this was right, but no its not :D Is there maybe any kind of directNext() which provides all next with a css selector until something different comes up ?
I will put all of my code into this jsfiddle: http://jsfiddle.net/LcUVf/5/
Maybe somebody has ever tried the same or someone comes up with a good idea I'm not asking anybody to do or finish my code. I just want to get hints for implementing such an algorithm or examples how it could be solved !
Thanks in anyway !
DOM traversal is not particularly efficient so, when you can avoid it, I'd recommend doing so. It'd make sense for you to build this as a 2D array to store and update the state of the game. The table would only be a visual representation of the array.
I know that, normally, you would build the array with rows as the first dimension and columns as the second dimension but, for the purposes of being able to add pieces to each column's "stack," I would make the first dimension the columns and the second dimension the rows.
To do the check, take a look at this fiddle I made:
http://jsfiddle.net/Koviko/4dTyw/
There are 4 directions to check: North-South, East-West, Northeast-Southwest, and Southeast-Northwest. This can be represented as objects with the delta defined for X and Y:
directions = [
{ x: 0, y: 1 }, // North-South
{ x: 1, y: 0 }, // East-West
{ x: 1, y: 1 }, // Northeast-Southwest
{ x: 1, y: -1 } // Southeast-Northwest
];
Then, loop through that object and loop through your "table" starting at the farthest bounds that this piece can possibly contribute to a win. So, since you need 4 pieces in a row, the currently placed piece can contribute in a win for up to 3 pieces in any direction.
minX = Math.min(Math.max(placedX - (3 * directions[i].x), 0), pieces.length - 1);
minY = Math.min(Math.max(placedY - (3 * directions[i].y), 0), pieces[0].length - 1);
maxX = Math.max(Math.min(placedX + (3 * directions[i].x), pieces.length - 1), 0);
maxY = Math.max(Math.min(placedY + (3 * directions[i].y), pieces[0].length - 1), 0);
To avoid any issues with less-than and greater-than (which I ran into), calculate the number of steps before looping through your pieces instead of using the calculated bounds as your conditions.
steps = Math.max(Math.abs(maxX - minX), Math.abs(maxY - minY));
Finally, loop through the items keeping a count of consecutive pieces that match the piece that was placed last.
function isVictory(pieces, placedX, placedY) {
var i, j, x, y, maxX, maxY, steps, count = 0,
directions = [
{ x: 0, y: 1 }, // North-South
{ x: 1, y: 0 }, // East-West
{ x: 1, y: 1 }, // Northeast-Southwest
{ x: 1, y: -1 } // Southeast-Northwest
];
// Check all directions
outerloop:
for (i = 0; i < directions.length; i++, count = 0) {
// Set up bounds to go 3 pieces forward and backward
x = Math.min(Math.max(placedX - (3 * directions[i].x), 0), pieces.length - 1);
y = Math.min(Math.max(placedY - (3 * directions[i].y), 0), pieces[0].length - 1);
maxX = Math.max(Math.min(placedX + (3 * directions[i].x), pieces.length - 1), 0);
maxY = Math.max(Math.min(placedY + (3 * directions[i].y), pieces[0].length - 1), 0);
steps = Math.max(Math.abs(maxX - x), Math.abs(maxY - y));
for (j = 0; j < steps; j++, x += directions[i].x, y += directions[i].y) {
if (pieces[x][y] == pieces[placedX][placedY]) {
// Increase count
if (++count >= 4) {
break outerloop;
}
} else {
// Reset count
count = 0;
}
}
}
return count >= 4;
}
I released a fully working version of the game on Github.
It implements an optimised variation on the algorythm Sirko mentioned.
To avoid any unnecessary redunancy, the algorythm directly checks the DOM rather than a JS table. As that algorythm requires a minimum amount of checks, the performance overhead for accessing the DOM is neglectable.
The current player and a flag for keeping track of whether the game has ended are basicly the only statuses stored in the JS itself.
I even used the DOM to store strings. It has no external dependencies and is supported by all versions of IE from IE6 upwards as well as modern browsers.
Code is optimised for filesize and performance. The latest version also includes animation, even though the total JS code of the game is still only 1.216 bytes after minification.
The Code :
Here's the full, un-minified JS code :
(function (doc, win, onclick, gid, classname, content, showMessage) {
var
a, b, c, colorLabel, cid, players, current, finished, newgameLabel, wonLabel, laststart = 1,
cellAt = function (i, j) {
return doc[gid](cid + i + j);
},
isCurrentColor = function (i, j) {
return cellAt(i, j)[classname] === players[current];
},
start = function () {
current = laststart = (laststart + 1) % 2;
finished = 0;
colorLabel[content] = colorLabel[classname] = players[current = (current + 1) % 2];
for (a = 1; a < 7; a++)
for (b = 1; b < 8; b++)
cellAt(a, b)[classname] = '';
},
makeMove = function (i, j, s) {
s > 0 && (cellAt(s, j)[classname] = '');
cellAt(s + 1, j)[classname] = players[current];
s === i - 1 ? function (i, j) {
return function (i, j) {
for (a = j - 1; 0 < a && isCurrentColor(i, a); a--) {
}
for (b = j + 1; 8 > b && isCurrentColor(i, b); b++) {
}
return 4 < b - a;
}(i, j) || function (i, j) {
for (c = i + 1; 7 > c && isCurrentColor(c, j); c++) {
}
return 3 < c - i;
}(i, j) || function (i, j) {
for (a = i - 1, b = j - 1; 0 < a && !(1 > b) && isCurrentColor(a, b); a--)
b--;
for (c = i + 1, b = j + 1; 7 > c && !(7 < b) && isCurrentColor(c, b); c++)
b++;
return 4 < c - a
}(i, j) || function (i, j) {
for (a = i - 1, b = j + 1; 0 < a && !(7 < b) && isCurrentColor(a, b); a--)
b++;
for (c = i + 1, b = j - 1; 7 > c && !(1 > b) && isCurrentColor(c, b); c++)
b--;
return 4 < c - a;
}(i, j);
}(i, j)
? finished = 1 && win[showMessage](doc[gid](wonLabel)[content].replace("%s", players[current].toLowerCase())) && start()
: colorLabel[content] = colorLabel[classname] = players[current = (current + 1) % 2]
: setTimeout(function () {
makeMove(i, j, s + 1)
}, 20);
};
return function (n, w, c, h, p1, p2) {
cid = c;
newgameLabel = n;
wonLabel = w;
colorLabel = doc[gid](c);
players = [doc[gid](p1)[content], doc[gid](p2)[content]];
for (a = 1; a < 7; a++)
for (b = 1; b < 8; b++)
cellAt(a, b)[onclick] = function (b, a) {
return function () {
if (!finished)
for (a = 6; a > 0; a--)
if (!cellAt(a, b)[classname]) {
makeMove(a, b, 0);
break;
}
};
}(b);
;
doc[gid](h)[onclick] = function () {
win[showMessage](doc[gid](newgameLabel)[content]) && start()
};
start();
};
})(document, window, "onclick", "getElementById", "className", "innerHTML", "confirm")("newgame", "won", "color", "restart", "p1", "p2");
A screenshot :
In general a 2dimensional array would be better suited for checking for a line of 4. You could then do something like the following:
function check( lastPiece, playground, player ) {
// check length in each direction
var l = 1,
i = 1;
// top to bottom
while( (playground[ lastPiece.x ][ lastPiece.y - i ] === player) && ((lastPiece.y - i) >= 0) ) { l += 1; i += 1; };
i = 1;
while( (playground[ lastPiece.x ][ lastPiece.y + i ] === player) && ((lastPiece.y + i) <= MAX_Y) ) { l += 1; i += 1; };
if ( l >= 4 ) { return true; }
// left to right
l = 1;
while( (playground[ lastPiece.x - i][ lastPiece.y ] === player) && ((lastPiece.x - i) >= 0) ) { l += 1; i += 1; };
i = 1;
while( (playground[ lastPiece.x + i][ lastPiece.y ] === player) && ((lastPiece.x + i) <= MAX_X) ) { l += 1; i += 1; };
if ( l >= 4 ) { return true; }
// same for top left to bottom right and bottom left to top right
// . . .
// if we got no hit until here, there is no row of 4
return false;
}
EDIT: added checks for borders of the playground

Categories

Resources