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).
Related
I need an array to be filled with random integers
Those integers should be very distinct from each other i.e. must at least be 20 units of separation between each items
This is what i have tried so far :
var all = [];
var i = 0;
randomDiff();
function randomDiff() {
var num1 = randomNumber(10, 290); //chose a first random num in the range...
all[0] = num1; //...put it in first index of array
do // until you have 12 items...
{
var temp = randomNumber(10, 290); //...you pick a temporary num
var j;
for (j = 0; j < all.length; j++) // for each item already in the array
{
if ((temp < all[i] - 10) || (temp > all[i] + 10)) // if the temporary num is different enough from others members...
{
all.push(temp); //then you can store it
i++; //increment until....
console.log(all[i]);
}
}
}
while (i < 11) // ...it is filled with 12 items in array
}
////////////Radom in int range function///////////////////////////////////////
function randomNumber(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
but always unsuccessful, including infinite loops...
Have a look on something like this:
function randomNumber(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
const LIST_SIZE = 20;
const DISTANCE = 10;
const STOP_AFTER_ATTEMPT = 2000;
const randomList = [];
let attempt = 0;
while(randomList.length < LIST_SIZE && attempt < STOP_AFTER_ATTEMPT) {
const num = randomNumber(10, 290);
const numberExistsWithSmallerDistance = randomList.some(r => Math.abs(r - num) < DISTANCE)
if (!numberExistsWithSmallerDistance) {
randomList.push(num);
}
attempt++;
}
if (randomList.length === LIST_SIZE) {
console.log(randomList);
} else {
console.log("Failed to create array with distnct values after ", attempt, " tries");
}
Here's a solution that will always work, as long as you allow enough room in the range/separation/count you choose. And it's way more efficient than a while loop. It doesn't just keep trying until it gets it right, it actually does the math to make sure it's right the first time.
This comes at the cost of tending to lean towards certain numbers more than others (like from + (i * separation)), so take note of that.
function getSeparatedRadomInts(from, through, separation, count) {
if(through < from) return getSeparatedRadomInts(through, from, separation, count);
if(count == 0) return [];
if(separation == 0) return !!console.log("Please allow enough room in the range/separation/count you choose.");
//pick values from pool of numbers evenly stepped apart by units of separation... adding 1 to from and through if from is 0 so we can divide properly
var smallFrom = Math.ceil((from || 1) / separation);
var smallThrough = Math.floor((through + (from == 0)) / separation);
var picks = randoSequence(smallFrom, smallThrough).slice(-count).sort((a, b) => a - b);
if(picks.length < count) return !!console.log("Please allow enough room in the range/separation/count you choose.");
for (var i = 0; i < picks.length; i++) picks[i] *= separation;
//go through each pick and randomize with any wiggle room between the numbers above/below it... adding 1 to from and through if from is 0
for (var i = 0; i < picks.length; i++) {
var lowerBound = picks[i - 1] + separation || from || 1;
var upperBound = picks[i + 1] - separation || (through + (from == 0));
picks[i] = rando(lowerBound, upperBound);
}
//subtract 1 from all picks in cases where from is 0 to compensate for adding 1 earlier
for (var i = 0; i < picks.length; i++) if(from == 0) picks[i] = picks[i] - 1;
return picks;
}
console.log(getSeparatedRadomInts(10, 290, 20, 12));
<script src="https://randojs.com/1.0.0.js"></script>
To be clear, from is the minimum range value, through is the maximum range value, separation is the minimum each number must be apart from each other (a separation of 20 could result in [10, 30, 50, 70], for example), and count is how many values you want to pick.
I used randojs in this code to simplify the randomness and make it easier to read, so if you want to use this code, just remember to paste this in the head of your HTML document:
<script src="https://randojs.com/1.0.0.js"></script>
For example: There is a total number 1000, and how many ways to equal 1000 using 100/50/20/10/5/1. I have found a idea about this. But obviously, that's not good. So does anyone have some other good ideas to cover it?
total = prompt('you can input number 1-1000, but 1000 will take a LOONG time');
count = 0;
t100 = total;
function money() {
for (var i100 = Math.floor(t100 / 100); i100 >= 0; i100--) {
var t50 = t100 - i100 * 100;
for (var i50 = Math.floor(t50 / 50); i50 >= 0; i50--) {
var t20 = t50 - i50 * 50;
for (var i20 = Math.floor(t20 / 20); i20 >= 0; i20--) {
var t10 = t20 - i20 * 20;
for (var i10 = Math.floor(t10 / 10); i10 >= 0; i10--) {
var t5 = t10 - i10 * 10;
for (var i5 = Math.floor(t5 / 5); i5 >= 0; i5--) {
var t1 = t5 - i5 * 5;
count++;
console.log(i100 + ' 100' + i50 + ' 50' + i20 + ' 20' + i10 + ' 10' + i5 + ' 5' + t1 + ' 1');
}
}
}
}
}
alert('The total number ' + total + ' is ' + count);
}
money()
No idea if it's correct but a little idea that popped to my head:
You know that with the bills/coins given, you can always fill up to the amount you need (value). If at the same time, you assume that you will always chose the highest possible bill / coin for the remainder I think you could do this:
If you use 100 bills you have floor(value / 100) possibilities (You can use 1 x 100, 2 x 100... )
If you don't use 100 but 50 you have floor(value / 50) possibilities
If you don't use 50 but 20 you have floor(value /20) possibilities
and so on. Add those up and you have a total of possibilities.
Let me know if I'm missing something.
I don't know about coding in js but you could try an algorithm like this one, which should be much more performant timely speaking. I'll try to make it as clear as possible :
availableCoins = [100, 50, 20, 10, 5] // all available coins exept 1
numberCoins = availableCoins.Length
mapResults = new Map[(int,int),int]
money(int total, int indexCoin){
if ( indexCoin == numberCoins ) {
return 1 // only coin 1 is available, there is only one solution
}
elseif ( mapResults.containsKey((total,indexCoin)) ) {
return mapResults((total,indexCoin)) // if the result has already been computed
}
else {
int count = 0
int currentCoin = availableCoin[indexCoin]
int upperbound = floor(total / currentCoin) // compute it before to avoid useless computation
for (int i = 0; i <= upperbound; i++) {
count += money(total - i * currentCoin, indexCoin + 1) // we assume only i of the current coin will be use
}
mapResults((total,indexCoin)) = count
return count
}
}
money(1000, 0)
Let me know if I have missed something or if it is not clear.
I let you adapt it to js.
I have got a expression from my colleague, and here it is:
//n is the total number
Change(n,m) {
if(n===0) return 0;
if(n<0 || m === 0) return 0;
var dimes = [1, 5, 10, 20, 50, 100];
return (change(n, m-1)+change(n-dimes[m-1], m));
}
*******
* *
* * *
* *
*******
It should look like the above arrangement of asterisks. I'm new to programming. So far I have written:
<!DOCTYPE html>
<html>
<meta charset="UTF-8">
<body>
<script>
var maxCount = 7;
var iterationCount = 0;
while (iterationCount < maxCount) {
iterationCount = iterationCount + 1;
document.write('*');
}
I have a strong feeling so far that's incorrect, and even if it's correct, I don't know how to go on from there.
I know I have to use for and nested loops, but I'm extremely confused.
Try
var width = 7,
height = 7, // for example
boxElem = document.getElementById("box"); // this is the element which will hold your box
for(var i = 0; i < height; i++) {
for(var j = 0; j < width; j++) {
if(i === 0 || i === height - 1 || j === 0 || j === width - 1 || (i === Math.floor(height / 2) && j === Math.floor(width / 2))) {
// check if it's the first or last row or column, or if it's the centre of the box
boxElem.innerHTML += "*";
} else {
boxElem.innerHTML += " "; // otherwise, don't add an asterisk but an empty space
}
}
boxElem.innerHTML += "\n"; // new line
}
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.
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