sorry for my english.
I have a problem and what is the next:
Example, i have a map:
var map =
[[0,1,1,0,0,0,0,0,0,0],
[0,1,0,1,0,1,1,0,0,0],
[0,1,0,0,1,0,0,1,0,0],
[0,1,0,0,0,0,0,0,1,0],
[0,0,1,0,0,0,0,1,0,0],
[0,0,0,1,0,0,0,1,1,0],
[0,0,1,0,0,0,1,0,0,0],
[0,1,0,0,0,0,0,1,0,0],
[1,0,0,1,1,1,0,1,0,0],
[0,1,1,0,0,1,1,1,0,0]];
Which contains a series of numbers 0 and 1 (For example). I need to fill in all the closed boxes that are on this map, for example using the number 2.
Example:
var map =
[[0,1,1,0,0,0,0,0,0,0],
[0,1,2,1,0,1,1,0,0,0],
[0,1,2,2,1,2,2,1,0,0],
[0,1,2,2,2,2,2,2,1,0],
[0,0,1,2,2,2,2,1,0,0],
[0,0,0,1,2,2,2,1,1,0],
[0,0,1,2,2,2,1,0,0,0],
[0,1,2,2,2,2,2,1,0,0],
[1,2,2,1,1,1,2,1,0,0],
[0,1,1,0,0,1,1,1,0,0]];
Taking into consideration that:
Just as in this example there is only one closed figure, there can be several closed figures
The sides of the map will not be taken into consideration
If it is of any use, the numbers 1 (which would be the solid), will
be generated as time passes, so the map will be constantly changing
(like strokes in an array)
I found a method called "Flood Fill" but however it depends on a starting point, in this case it has no starting point. The idea is that the code is in charge of finding the closed areas and filling them automatically.
If you don't have starting coordinates, one method to identify every 0 to be filled is to identify every 0 on the edges. Each of these zeros should not be filled, and each 0 eventually adjacent to these 0s should also not be filled. So, if you take the edge 0s as the "starting point" and iterate through all of their recursive neighbors, you'll have identified each coordinate which is a 0 but should not be filled in.
Then, it's simple: just iterate over the input, and for every 0, check to see if the current coordinate is in that set of coordinates that shouldn't be filled. If the coordinate is not in that set, replace with a 2.
var map =
[[0,1,1,0,0,0,0,0,0,0],
[0,1,2,1,0,1,1,0,0,0],
[0,1,2,2,1,2,2,1,0,0],
[0,1,2,2,2,2,2,2,1,0],
[0,0,1,2,2,2,2,1,0,0],
[0,0,0,1,2,2,2,1,1,0],
[0,0,1,2,2,2,1,0,0,0],
[0,1,2,2,2,2,2,1,0,0],
[1,2,2,1,1,1,2,1,0,0],
[0,1,1,0,0,1,1,1,0,0]];
const height = map.length;
const width = map[0].length;
const edgeZerosCoords = new Set();
map.forEach((arr, row) => {
arr.forEach((num, col) => {
if (num === 0 && (row === 0 || col === 0 || row === width - 1 || col === height - 1)) {
edgeZerosCoords.add(`${row}_${col}`);
}
})
});
const doNotFillCoords = new Set();
const visited = new Set();
const checkCoord = (row, col) => {
// Verify valid coord:
if (row < 0 || col < 0 || row === width || col === height) return;
const str = `${row}_${col}`;
if (doNotFillCoords.has(str) || visited.has(str)) return;
visited.add(str);
const num = map[row][col];
if (num !== 0) return;
doNotFillCoords.add(str);
checkCoord(row + 1, col);
checkCoord(row - 1, col);
checkCoord(row, col + 1);
checkCoord(row, col - 1);
};
for (const str of edgeZerosCoords) {
const [row, col] = str.split('_').map(Number);
checkCoord(row, col)
}
map.forEach((arr, row) => {
arr.forEach((num, col) => {
const str = `${row}_${col}`;
if (num === 0 && !doNotFillCoords.has(str)) {
map[row][col] = 2;
}
})
});
console.log(JSON.stringify(map));
Result:
[
[0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 2, 1, 0, 1, 1, 0, 0, 0],
[0, 1, 2, 2, 1, 2, 2, 1, 0, 0],
[0, 1, 2, 2, 2, 2, 2, 2, 1, 0],
[0, 0, 1, 2, 2, 2, 2, 1, 0, 0],
[0, 0, 0, 1, 2, 2, 2, 1, 1, 0],
[0, 0, 1, 2, 2, 2, 1, 0, 0, 0],
[0, 1, 2, 2, 2, 2, 2, 1, 0, 0],
[1, 2, 2, 1, 1, 1, 2, 1, 0, 0],
[0, 1, 1, 0, 0, 1, 1, 1, 0, 0]
]
Related
let's say I have a grid i.e. 2d array
const grid = [
[0, 0, A, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, B, 0],
[D, E, 0, C, F],
[0, 0, 0, 0, 0],
]
if some cell in the grid can visit all adjacent cells 4-diredtionally, for example, C is at [3, 3] so it can visit [3, 3 + 1], [3 - 1, 3],[3 +1, 3]``[3, 3 - 1], so normally I would have to hard code this like
// 👇 hard-coded directions
const dirs = [
[1, 0],
[-1, 0],
[0, 1],
[0, -1],
]
const possibleMoves = []
for (const [dx, dy] of dirs) {
possibleMoves.push([dx + x, dy +y])
}
then if it can move 8-directionally then you have to hard code more directions
const dirs = [[1, 0], [-1, 0] , [0,1], [0,-1], [1,1], [-1,1], [-1,-1], [1,-1]]
Is there a smarter way to generate the dirs array for the next moves?
Yes!
First: any time you're doing grid-logic, start by checking what Amit Patel has to say.
Honestly, that link has everything you could ever need.
The short version is: if you know the grid width and cell layout, you can easily calculate coordinate offsets of any cell neighbor for any definition of "neighbor."
That logic can be implemented as a pure function that requires both the grid dimensions and the coordinates of the cell whose neighbors you want (aka the "target" cell):
let myNeighbors = getCellNeighbors(
{ x: 2, y: 2 }, // coords of target cell
{ width: 10, height: 10 } // grid dimensions, in cells
)
Or you can create a stateful thing that takes the grid dimensions at creation and calculates the offsets once, to be re-used for all getNeighbors calls:
let myGrid = new Grid(10, 10)
let myNeighbors = myGrid.getNeighbors(2, 5)
let myBiggerGrid = new Grid(25, 25)
let otherNeighbors = myBiggerGrid(2, 5)
I have to calculate the minimum price of a cart after apply discounts.
A customer can buy books of a series. This series has 5 different books. Each book cost 8€. But if you buy two different bookies in the series, you've got a 5% discount. If you buy 3 different books in the series, you've got a 10% discount. If you buy 4 different books in the series, you've got 20% discount. And if you buy all books of the series you've got a 25% discount.
An example of how much does this basket of books cost?
2 copies of the first book
2 copies of the second book
2 copies of the third book
1 copy of the fourth book
1 copy of the fifth book
The correct answer is: 51.20€
To get this final price you have to group the books in two blocks of 4 different books. So, 8 * 4 = 32 - 20% = 25.60€ * 2 = 51.20€
I have try to resolve with backtracking but I'm not able to find a correct solution.
How can I write the correct algorithm in JavaScript?
Edit I:
Following the wise advice of #visleck I have developed a possible solution who works fine always except in one case. At least, I have no found more cases where it doesn't work.
First of all, a function initializing all the options with his cost after discount applied:
const PRICE = 8.00;
export const initArray = (cart) => {
var dp = [];
var basket = [];
basket[0] = typeof cart.get(1) !== "undefined" ? cart.get(1) : 0;
basket[1] = typeof cart.get(2) !== "undefined" ? cart.get(2) : 0;
basket[2] = typeof cart.get(3) !== "undefined" ? cart.get(3) : 0;
basket[3] = typeof cart.get(4) !== "undefined" ? cart.get(4) : 0;
basket[4] = typeof cart.get(5) !== "undefined" ? cart.get(5) : 0;
//If we buy zero books
var aux = [[0, 0, 0, 0, 0], parseFloat(0).toFixed(2)];
dp.push(aux);
//If we buy only one book
aux = [[0, 0, 0, 0, 1], parseFloat(PRICE).toFixed(2)];
dp.push(aux);
aux = [[0, 0, 0, 1, 0], parseFloat(PRICE).toFixed(2)];
dp.push(aux);
aux = [[0, 0, 1, 0, 0], parseFloat(PRICE).toFixed(2)];
dp.push(aux);
aux = [[0, 1, 0, 0, 0], parseFloat(PRICE).toFixed(2)];
dp.push(aux);
aux = [[1, 0, 0, 0, 0], parseFloat(PRICE).toFixed(2)];
dp.push(aux);
//If we buy two books
aux = [[1, 1, 0, 0, 0], parseFloat(PRICE * 2 * 0.95).toFixed(2)];
dp.push(aux);
aux = [[1, 0, 1, 0, 0], parseFloat(PRICE * 2 * 0.95).toFixed(2)];
dp.push(aux);
aux = [[1, 0, 0, 1, 0], parseFloat(PRICE * 2 * 0.95).toFixed(2)];
dp.push(aux);
aux = [[1, 0, 0, 0, 1], parseFloat(PRICE * 2 * 0.95).toFixed(2)];
dp.push(aux);
aux = [[0, 1, 1, 0, 0], parseFloat(PRICE * 2 * 0.95).toFixed(2)];
dp.push(aux);
aux = [[0, 1, 0, 1, 0], parseFloat(PRICE * 2 * 0.95).toFixed(2)];
dp.push(aux);
aux = [[0, 1, 0, 0, 1], parseFloat(PRICE * 2 * 0.95).toFixed(2)];
dp.push(aux);
aux = [[0, 0, 1, 1, 0], parseFloat(PRICE * 2 * 0.95).toFixed(2)];
dp.push(aux);
aux = [[0, 0, 1, 0, 1], parseFloat(PRICE * 2 * 0.95).toFixed(2)];
dp.push(aux);
aux = [[0, 0, 0, 1, 1], parseFloat(PRICE * 2 * 0.95).toFixed(2)];
dp.push(aux);
//If we buy three books
aux = [[1, 1, 1, 0, 0], parseFloat(PRICE * 3 * 0.90).toFixed(2)];
dp.push(aux);
aux = [[1, 1, 0, 1, 0], parseFloat(PRICE * 3 * 0.90).toFixed(2)];
dp.push(aux);
aux = [[1, 1, 0, 0, 1], parseFloat(PRICE * 3 * 0.90).toFixed(2)];
dp.push(aux);
aux = [[1, 0, 0, 1, 1], parseFloat(PRICE * 3 * 0.90).toFixed(2)];
dp.push(aux);
aux = [[1, 0, 1, 0, 1], parseFloat(PRICE * 3 * 0.90).toFixed(2)];
dp.push(aux);
aux = [[1, 0, 1, 1, 0], parseFloat(PRICE * 3 * 0.90).toFixed(2)];
dp.push(aux);
aux = [[0, 1, 1, 1, 0], parseFloat(PRICE * 3 * 0.90).toFixed(2)];
dp.push(aux);
aux = [[0, 1, 1, 0, 1], parseFloat(PRICE * 3 * 0.90).toFixed(2)];
dp.push(aux);
aux = [[0, 1, 0, 1, 1], parseFloat(PRICE * 3 * 0.90).toFixed(2)];
dp.push(aux);
aux = [[0, 0, 1, 1, 1], parseFloat(PRICE * 3 * 0.90).toFixed(2)];
dp.push(aux);
//If we buy four books
aux = [[1, 1, 1, 1, 0], parseFloat(PRICE * 4 * 0.80).toFixed(2)];
dp.push(aux);
aux = [[1, 1, 1, 0, 1], parseFloat(PRICE * 4 * 0.80).toFixed(2)];
dp.push(aux);
aux = [[1, 1, 0, 1, 1], parseFloat(PRICE * 4 * 0.80).toFixed(2)];
dp.push(aux);
aux = [[1, 0, 1, 1, 1], parseFloat(PRICE * 4 * 0.80).toFixed(2)];
dp.push(aux);
aux = [[0, 1, 1, 1, 1], parseFloat(PRICE * 4 * 0.80).toFixed(2)];
dp.push(aux);
//If we buy five books
aux = [[1, 1, 1, 1, 1], parseFloat(PRICE * 5 * 0.75).toFixed(2)];
dp.push(aux);
return [basket, dp];
}
basket is the shopping cart and dp is an array of 32 position with all the posibilities.
Now, the function who try to find the best price called *checkOut*. This function, call to other two: *solution* and *validSolution*
/**
* Returns true if aux can be belongs to a possible finale solution, otherwise returns false
*
* #param { array } basket
* #param { array } si
* #param { array } aux
*/
export const solution = (basket, si, sol) => {
var ps = [...sol];
if (si.length > 0){
for (var i = 0; i < si.length; i++){
for (var j = 0; j < ps.length; j++){
ps[j] = parseInt(si[i][j] + ps[j]);
}
}
}
for (var i = 0; i < basket.length; i++){
if (parseInt(basket[i] - ps[i]) < 0)
return false;
}
return true;
}
export const validSolution = (basket, si) => {
var aux = [...basket];
for (var i = 0; i < si.length; i++){
var sol = si[i];
for (var j = 0; j < aux.length; j++){
aux[j] = parseInt(aux[j] - sol[j]);
}
}
var distinctZero = aux.filter(item => item > 0);
return distinctZero.length == 0 ? true : false;
}
export const checkOut = (cart) => {
const [basket, dp] = initArray(cart);
var voa = Number.POSITIVE_INFINITY;
var value = 0;
var soa = [];
var si = [];
var level = 1;
var i = 1;
while (level < Math.pow(2, 5)){
var aux = dp[i][0];
if (solution(basket, si, aux)){
si.push(aux);
value = (value * 10 + dp[i][1] * 10)/10;
i = level;
}else if (i < (Math.pow(2, 5) - 1)){
i++
}else{
level++;
i = level;
si = [];
value = 0;
}
if (validSolution(basket, si)){
if (value < voa){
voa = value;
}
soa.push([si, value]);
value = 0;
si = [];
level++;
i = level;
}
}
console.log(voa);
// //console.log(soa);
// console.log(soa[8][0][1]);
// for (var i = 0; i < soa.length; i++){
// console.log(soa[i])
// }
return voa;
}
Whith these code, If I pass this tests all works fine:
var cart = new Map();
cart.set(1, 2);
cart.set(2, 2);
cart.set(3, 0);
cart.set(4, 0);
cart.set(5, 0);
assert.ok(parseFloat(30.40).toFixed(2) === parseFloat(checkOut(cart)).toFixed(2));
cart.set(1, 2);
cart.set(2, 2);
cart.set(3, 2);
cart.set(4, 1);
cart.set(5, 1);
assert.ok(parseFloat(51.20).toFixed(2) === parseFloat(checkOut(cart)).toFixed(2));
cart.set(1, 2);
cart.set(2, 0);
cart.set(3, 2);
cart.set(4, 0);
cart.set(5, 0);
assert.ok(parseFloat(30.40).toFixed(2) === parseFloat(checkOut(cart)).toFixed(2));
cart.set(1, 0);
cart.set(2, 0);
cart.set(3, 2);
cart.set(4, 0);
cart.set(5, 0);
assert.ok(parseFloat(16.00).toFixed(2) === parseFloat(checkOut(cart)).toFixed(2));
cart.set(1, 0);
cart.set(2, 0);
cart.set(3, 0);
cart.set(4, 1);
cart.set(5, 1);
assert.ok(parseFloat(15.20).toFixed(2) === parseFloat(checkOut(cart)).toFixed(2));
cart.set(1, 1);
cart.set(2, 1);
cart.set(3, 1);
cart.set(4, 1);
cart.set(5, 1);
assert.ok(parseFloat(30.00).toFixed(2) === parseFloat(checkOut(cart)).toFixed(2));
But with this test, fails:
cart.set(1, 2);
cart.set(2, 1);
cart.set(3, 1);
cart.set(4, 1);
cart.set(5, 1);
assert.ok(parseFloat(38.00).toFixed(2) === parseFloat(checkOut(cart)).toFixed(2));
The value returned is 43.20 and not 38.00 which is the correct value.
If I change this piece of code:
if (solution(basket, si, aux)){
si.push(aux);
value = (value * 10 + dp[i][1] * 10)/10;
i = 1;
Making i = 1 and not i = level, then it works fine, but the previous cases doesn't work.
How can I improve my code so that it passes all the tests?
You can try to solve it using dynamic programming.
Here I am assuming that enough memory is available for use.
You can use a 5-dimensional array for this. Lets call it dp[a][b][c][d][e], where a represents max no of books of type 1, 'b' represents max no of books of type 2, and so on.
Let's initialize each state with some max value as flag which denotes that this value has not been calculated yet. So any state (lets say dp[a1][b1][c1][d1][e1]) stores the optimal way to buy 'a1' books of type 1, 'b1' books of type 2 and so on.
So at each state, lets say dp[a1][b1][c1][d1][e1], we have 32 ways to go to other state, which is as given below.
When selecting only one book at a time.
dp[a][b][c][d][e] = 8.0 + dp[a-1][b][c][d][e]
dp[a][b][c][d][e] = 8.0 + dp[a][b-1][c][d][e]
dp[a][b][c][d][e] = 8.0 + dp[a][b][c-1][d][e]
dp[a][b][c][d][e] = 8.0 + dp[a][b][c][d-1][e]
dp[a][b][c][d][e] = 8.0 + dp[a][b][c][d][e-1]
Selecting two books at a time.
dp[a][b][c][d][e] = 16*0.95 + dp[a-1][b-1][c][d][e]
dp[a][b][c][d][e] = 16*0.95 + dp[a-1][b][c-1][d][e]
and 8 more cases
Selecting three books at a time
dp[a][b][c][d][e] = 24*0.9 + dp[a-1][b-1][c-1][d][e]
dp[a][b][c][d][e] = 24*0.9 + dp[a-1][b-1][c][d-1][e]
and 8 more cases.
Selecting four books at a time
dp[a][b][c][d][e] = 32*0.8 + dp[a-1][b-1][c-1][d-1][e]
dp[a][b][c][d][e] = 32*0.8 + dp[a-1][b-1][c-1][d][e-1]
and 3 more cases.
Selecting five books at a time
dp[a][b][c][d][e] = 40*0.75 + dp[a-1][b-1][c-1][d-1][e-1]
Doing this keeping in mind about the boundary conditions that no books in any state can be less than 0 or can go more than the max number of its type.
So the pseudo code is as given below
A[max_a][max_b][max_c][max_d][max_e]=max_value
func(a,b,c,d,e)
if(a>0 && b>0 && c>0 && d>0 && e>0)
if(A[a][b][c][d][e]!=max_value)
return A[a][b][c][d][e]
endif
recursive calls to all 32 cases mentioned above
endif
return max_value //this will be returned when boundary condition fails
endfunc
This is a top down approach. Hence call this function using func(a,b,c,d,e) where a,b,c,d and e represnts the number of books of type 1,2,3,4 and 5 respectively
I have a feeling you're over thinking this. I also have a strong suspicion we're all doing the same.
Let's take this one step at a time.
Your discount structure is:
1 book: 0%
2 books: 5%
3 books: 10%
4 books: 20%
5 books: 25%
...
Your non-linear discount is making this less of a math problem and more of a logic problem. There's still plenty of math, but the gap between 3 books and 4 books adds complexity that straight math won't cover. How do we fix that? We use a lookup table/array so we can use the amount of books in the series to determine the discount by index.
var discountsAvailable = [0, 0.05, 0.1, 0.2, 0.25, ...];
Actually, it would be easier math later on to build in the subtraction from 100%, so it becomes:
var discountsAvailable = [1, 0.95, 0.9, 0.8, 0.75, ...];
Then we just use the amount of books purchased in the series as the index.
var qty1 = 3;
var qty2 = 5;
discountsAvailable[qty1] == 0.9
discountsAvailable[qty2] == 0.75
We do this for each series independently to get the discount, as you would guess. In the case where someone buys 2 different "sets" of the same series, we can iterate over them multiple times to get different options and then choose which we want to use.
var tempTotal = [];
var average = Math.floor((qty1 + qty2 + ...)/numberOfSeries);
tempTotal[0] = discountsAvailable[qty1] * price * qty1;
tempTotal[0] += discountsAvailable[qty2] * price * qty2;
...
tempTotal[1] = tempTotal[average] * price * average;
...
Then do a loop to find the lowest value in tempTotal. You could probably loop through the calculations for tempTotal, too.
This is a basic example of of your {2x, 2x, 2x, 1x, 1x} example, so more logic and more calculations would be needed to determine {3x, 2x, 4x, 1x, 2x} as well as if there are more than 5 books in the series. Unfortunately, it gets much more convoluted when gets that complicated
Using your example:
average = (3 + 5) / 2; // 4
tempTotal[0] = 0.9 * 8 * 3; // 21.6
tempTotal[0] += 0.75 * 8 * 5; // 30
tempTotal[0] == 51.6;
tempTotal[1] = 0.8 * 8 * 4; // 51.2
The tempTotal[0] is 3 books out of 5 {1, 1, 1, 0, 0} plus 5 out of 5 books{1, 1, 1, 1, 1}, while tempTotal[1] is 2x 4 books out of 5 {1, 1, 1, 1, 0} plus {1, 1, 1, 0, 1}.
So your answer is tempTotal[1] at 51.2.
Here's a memoized recursion where the search space depends on the number of states possible for the book quantities.
JavaScript code:
function f(state, ds, price, memo={}){
if (!state.some(x => x > 0))
return 0
if (memo.hasOwnProperty(state))
return memo[state]
let best = Infinity
function g(st, count, i){
if (i == st.length)
return
if (st[i] > 0){
let _st = st.slice()
_st[i]--
best = Math.min(
best,
count * price * ds[count] + f(_st, ds, price, memo)
)
g(_st, count + 1, i + 1)
}
g(st, count, i + 1)
}
g(state, 1, 0)
return memo[state] = best
}
var price = 8
var ds = [1, 1, 0.95, 0.9, 0.8, 0.75]
var states = [
[2, 2, 0, 0, 0], // 30.40
[2, 2, 2, 1, 1], // 51.20
[2, 0, 2, 0, 0], // 30.40
[0, 0, 2, 0, 0], // 16
[0, 0, 0, 1, 1], // 15.20
[1, 1, 1, 1, 1], // 30
[2, 1, 1, 1, 1] // 38
]
for (let state of states)
console.log(`${ state }: ${ f(state, ds, price) }`)
Note that for anything other than trivial input, trying all the possibilities results in an exponential runtime that is impractical. While we can't theoretically reduce the runtime, we can practically do much better by identifying the search paths that can be pruned.
For this problem, it only matters how many different types of books are there. Instead of operating on the input array, we are going to be working with a frequency array. So, [1, 1, 2] becomes [0, 2, 1, 0, 0, 0]. The array is of size 6 for convenience, so, that we can use the books directly as indices without having to subtract 1.
A naive caching/memoization approach would use the frequency array directly as the key, but note that [0, 1, 2, 0, 0, 0] and [0, 2, 1, 0, 0, 0] cost exactly the same. Thus, we will use a sorted, non-zero, frequency array as the cache key. In the above example, it is [1, 2].
Lastly, note that [1, 1] (zeros removed) will produce three combinations, [[1], [1], [1, 1]], of which the first two are identical. Thus, we will deduplicate the combinations before iterating on them.
I don't have JavaScript code but I solved the same problem on Exercism Rust track, which is where I assume you got it from. You can see my solution here.
I would like to fill until a specific Y value in PlotlyJS. This is as far as I got from the PlotlyJS docs: Fiddle
{
"x": [
"2016-01-31T00:03:57.000Z",
"2016-02-12T04:35:26.000Z"
],
"y": [
100,
100
],
"fill": "tonexty",
"fillcolor": "#8adcb3"
}
In the documentation, there seems to be two options:
tonexty - Fills as below. The problem is 'tonexty' is a bit limiting - The use case is 'filling until the line', so shading ONLY above 110. Example:
tozeroy - Fills till zero:
Also, do you need to introduce a new trace in order to create a fill?
This means that if I have a chart as follows (with only one trace but a threshold line as a shape): I need to introduce another trace just to create a fill. Maybe there's something I missed in the docs, or this is the wrong approach altogether.
So, how do you fill an area in a trace above a specific Y value in PlotlyJS?
A solution is to use multiple traces.
Split all your traces between ones which are above 0 and ones which are not.
When you are done you can fill them (or not) with the 'tozeroy' value.
The following jsfiddle shows a working example.
The code is as following :
HTML:
<div id="myDiv" style="width:600px;height:250px;"></div>
JS:
var data = [
{
x: ['A', 'B', 'C', 'D'],
y: [1, 3, 6, 0],
fill: 'tozeroy',
fillcolor: '#8adcb3'
},
{
x: ['D', 'F', 'G', 'I'],
y: [0, -3, -2, 0],
fill: 'toself'
},
{
x: ['I', 'J', 'K'],
y: [0, 5, 7],
fill: 'tozeroy',
fillcolor: '#0adcb3'
}
];
Plotly.newPlot('myDiv', data);
The result looks as following :
Here is another solution exploiting Plotly's fill: "toself". The idea is to create a closed line trace which encloses the area above the threshold and the markers of the main line. Works for threshold values above zero and for numerical x-values.
The helper traces have their legend hidden and are grouped with the main trace, thereby preventing ugly artifacts when toggling the legend.
The function checks for each x-y-pair if the y-value is above the threshold, if yes
check if there is already a segment above the threshold and use this one OR create a new sgement
the segement starts from the y-value of the threshold and the intermediate x-value from the point above the threshold and the one before.
each segment is terminated with an y-value which is equal to the threshol and the x-value which the mean of the last point in the segment and the next one
The function itself can be surely written in a nicer way but it's just a proof-of-concept.
function dataToTraces(data, threshold) {
var fillers = [];
var emptyFiller = {
x: [],
y: [],
fill: "toself",
mode: "lines",
line: {
width: 0
},
opacity: 0.5,
fillcolor: "#8adcb3",
showlegend: false,
legendgroup: "main"
}
fillers.push(emptyFiller);
for (var i = 0; i < data.y.length; i += 1) {
if (data.y[i] >= threshold) {
if (i !== 0 && data.y[i - 1] < threshold) {
fillers[fillers.length - 1].x.push(data.x[i - 1] + (threshold - data.y[i - 1]) / (data.y[i] - data.y[i - 1]));
fillers[fillers.length - 1].y.push(threshold);
}
fillers[fillers.length - 1].x.push(data.x[i]);
fillers[fillers.length - 1].y.push(data.y[i]);
} else if (fillers[fillers.length - 1].x.length > 0) {
if (i !== 0 && data.y[i - 1] !== threshold) {
fillers[fillers.length - 1].x.push(data.x[i - 1] + (threshold - data.y[i - 1]) / (data.y[i] - data.y[i - 1]));
fillers[fillers.length - 1].y.push(threshold);
}
fillers.push(emptyFiller);
}
}
return fillers;
}
var data = [{
x: [0, 1, 2, 3, 4, 5, 6, 7, 8],
y: [1, 3, 6, 2, -1, 5, 1, 3, 0],
name: "main",
legendgroup: "main"
}];
var fillers = dataToTraces(data[0], 2);
Plotly.newPlot("myDiv", data.concat(fillers));
<div id="myDiv"></div>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
I am working on my first full program with two weeks of programming under my belt, and have run into a road block I can't seem to figure out. I am making a connect 4 game, and have started by building the logic in JavaScript before pushing to the DOM. I have started to make it with cell objects made by a constructor, that are then pushed into a game object in the form of a 2D array. I have managed to create a function that makes the play each time, and changes the value of the cell at the lowest point of that column with a 2 day array. However, I am not sure how to get my check for wins function to operate.
So far my logic is that, for each point in the 2D array, you can check by row, by column, and by diagonals. I understand the logic of how to check for win, but I don't understand how to traverse through the arrays by row and column. In the example below, this.cellsArray is an array of cell objects in the Board Constructor. The array has 7 column arrays, with 6 rows each, as I flipped the typical row column logic to account for Connect Four's column based nature. However I can't access the array like this.cellsArray[col][row], as col and row aren't defined, and I'm not sure how to define an index value? Any help would be appreciated!
Connect 4
Example:
//array location is equal to an instance of this.cellsArray[col][row]
Board.prototype.checkRowRight = function (arrayLocation) {
if ((arrayLocation[i+1][i].value === arrayLocation.value) && (arrayLocation[i+2][i]=== arrayLocation.value) && (arrayLocation[i+3][i].value === arraylocation.value)){
this.winner = this.currentPlayer;
this.winnerFound = true;
console.log('Winner has been found!')
}
};
Referencing back to my logic found here and refactoring out the winning line detection code, this can easily be converted into Javascript as follows:
function chkLine(a,b,c,d) {
// Check first cell non-zero and all cells match
return ((a != 0) && (a ==b) && (a == c) && (a == d));
}
function chkWinner(bd) {
// Check down
for (r = 0; r < 3; r++)
for (c = 0; c < 7; c++)
if (chkLine(bd[r][c], bd[r+1][c], bd[r+2][c], bd[r+3][c]))
return bd[r][c];
// Check right
for (r = 0; r < 6; r++)
for (c = 0; c < 4; c++)
if (chkLine(bd[r][c], bd[r][c+1], bd[r][c+2], bd[r][c+3]))
return bd[r][c];
// Check down-right
for (r = 0; r < 3; r++)
for (c = 0; c < 4; c++)
if (chkLine(bd[r][c], bd[r+1][c+1], bd[r+2][c+2], bd[r+3][c+3]))
return bd[r][c];
// Check down-left
for (r = 3; r < 6; r++)
for (c = 0; c < 4; c++)
if (chkLine(bd[r][c], bd[r-1][c+1], bd[r-2][c+2], bd[r-3][c+3]))
return bd[r][c];
return 0;
}
And a test call:
x =[ [0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 0, 0],
[0, 0, 0, 1, 1, 0, 0],
[0, 0, 1, 2, 2, 2, 0],
[0, 1, 2, 2, 1, 2, 0] ];
alert(chkWinner(x));
The chkWinner function will, when called with the board, return the first (and only, assuming each move changes only one cell and you're checking after every move) winning player.
The idea is to basically limit the checks to those that make sense. For example, when checking cells to the right (see the second loop), you only need to check each row 0-6 starting in each of the leftmost four columns 0-3.
That's because starting anywhere else would run off the right hand side of the board before finding a possible win. In other words, column sets {0,1,2,3}, {1,2,3,4}, {2,3,4,5} and {3,4,5,6} would be valid but {4,5,6,7} would not (the seven valid columns are 0-6).
This is an old thread but i'll throw my solution into the mix since this shows up as a top search result for "how to calculate connect4 win javascript"
I tackled this problem by using matrix addition.
Assume your game board is stored in memory as a 2D array like this:
[ [0, 0, 0, 0, 0, 0, 0],
[0, 0, Y, 0, 0, 0, 0],
[0, 0, Y, 0, 0, 0, 0],
[0, 0, R, 0, 0, 0, 0],
[0, 0, Y, 0, 0, 0, 0],
[0, 0, R, R, R, 0, 0] ];
On each "Coin Drop" you should call a function passing the x/y position of the coin.
THIS is where you calculate weather the user has won the game
let directionsMatrix = {
vertical: { south: [1, 0], north: [-1, 0] },
horizontal: { east: [0, 1], west: [0, -1] },
backward: { southEast: [1, 1], northWest: [-1, -1] },
forward: { southWest: [1, -1], northEast: [-1, 1] },
};
NOTE: "South" in matrix notation is [1,0], meaning "Down 1 cell, Right 0 cells"
Now we can loop through each Axis/Direction to check if there is 4 in a row.
const playerHasWon = (colnum, rowNum, playerColor, newGrid) => {
//For each [North/South, East/West, NorthEast/Northwest, SouthEast/Southwest]
for (let axis in directionsMatrix) {
// We difine this variable here so that "East" and "West" share the same count,
// This allows a coin to be dropped in a middle cell
let numMatches = 1;
// For each [North, South]
for (let direction in directionsMatrix[axis]) {
// Get X/Y co-ordinates of our dropped coin
let cellReference = [rowNum, colnum];
// Add co-ordinates of 1 cell in test direction (eg "North")
let testCell = newGrid[cellReference[0]][cellReference[1]];
// Count how many matching color cells are in that direction
while (testCell == playerColor) {
try {
// Add co-ordinates of 1 cell in test direction (eg "North")
cellReference[0] += directionsMatrix[axis][direction][0];
cellReference[1] += directionsMatrix[axis][direction][1];
testCell = newGrid[cellReference[0]][cellReference[1]];
// Test if cell is matching color
if (testCell == playerColor) {
numMatches += 1;
// If our count reaches 4, the player has won the game
if (numMatches >= 4) {
return true;
}
}
} catch (error) {
// Exceptions are to be expected here.
// We wrap this in a try/catch to ignore the array overflow exceptions
// console.error(error);
break;
}
}
// console.log(`direction: ${direction}, numMatches: ${numMatches}`);
// If our count reaches 4, the player has won the game
if (numMatches >= 4) {
return true;
}
}
}
// If we reach this statement: they have NOT won the game
return false;
};
Here's a link to the github repo if you wish to see the full code.
Here's a link to a live demo
I'm using http://craftyjs.com/ for the Github Game Off, and I'm having some trouble with animation. The first time I start the animation (when I initialize the player entity in the main scene) it works. But when I set the animation via my CustomControls component, it only plays the first frame of the animation.
I believe the problem is in the CustomControls component, so here's the code for that:
https://gist.github.com/3992392
Here's all the code if you want to clone it and test: https://github.com/RylandAlmanza/dragons-suck
If anyone knows what the problem might be, let me know. Thanks!
EDIT: Here's a minimal jsfiddle example with everything stripped out except for movement and what should be animation: http://jsfiddle.net/x7FDF/
var SPRITE_SIZE = 32;
Crafty.init();
Crafty.canvas.init();
Crafty.sprite(SPRITE_SIZE, "http://i.imgur.com/9sN9V.png", {
player_east_1: [0, 0],
player_east_2: [1, 0],
player_east_3: [2, 0],
player_west_1: [0, 1],
player_west_2: [1, 1],
player_west_3: [2, 1],
player_south_1: [0, 2],
player_south_2: [1, 2],
player_south_3: [2, 2],
player_north_1: [0, 3],
player_north_2: [1, 3],
player_north_3: [2, 3]
});
Crafty.scene("loading", function() {
Crafty.background("#000");
Crafty.e("2D, DOM, Text")
.attr({
w: 100,
h: 20,
x: 150,
y: 120
})
.text("Loading")
.css({"text-align": "center"});
Crafty.load(["http://i.imgur.com/9sN9V.png"], function() {
Crafty.scene("main");
});
});
Crafty.scene("main", function() {
Crafty.background("#000");
var player = Crafty.e("2D, DOM, SpriteAnimation, player_east_1, Collision, TileCollision, CustomControls")
.attr({
x: 0,
y: 0,
x_velocity: 0,
y_velocity: 0,
w: SPRITE_SIZE,
h: SPRITE_SIZE
})
.animate("stand_east", 0, 0, 0)
.animate("stand_west", 0, 1, 0)
.animate("stand_south", 0, 2, 0)
.animate("stand_north", 0, 3, 0)
.animate("walk_east", [[0, 0], [1, 0], [0, 0], [2, 0]])
.animate("walk_west", [[0, 1], [1, 1], [0, 1], [2, 1]])
.animate("walk_south", [[0, 2], [1, 2], [0, 2], [2, 2]])
.animate("walk_north", [[0, 3], [1, 3], [0, 3], [2, 3]])
.animate("stand_east", 45, -1)
.CustomControls();
});
Crafty.c("CustomControls", {
CustomControls: function() {
this.bind("EnterFrame", function() {
var up = Crafty.keydown[Crafty.keys.UP_ARROW];
var down = Crafty.keydown[Crafty.keys.DOWN_ARROW];
var left = Crafty.keydown[Crafty.keys.LEFT_ARROW];
var right = Crafty.keydown[Crafty.keys.RIGHT_ARROW];
if (up) {
this.y_velocity = -2;
if (!this.isPlaying("walk_north")) {
this.stop().animate("walk_north", 45, -1);
}
}
if (down) {
this.y_velocity = 2;
if (!this.isPlaying("walk_south")) {
this.stop().animate("walk_south", 45, -1);
}
}
if (left) {
this.x_velocity = -2;
if (!this.isPlaying("walk_west")) {
this.stop().animate("walk_west", 45, -1);
}
}
if (right) {
this.x_velocity = 2;
if (!this.isPlaying("walk_east")) {
this.stop().animate("walk_east", 45, -1);
}
}
if (!left && !right) {
this.x_velocity = 0;
this.stop();
}
if (!up && !down) {
this.y_velocity = 0;
this.stop();
}
this.x += this.x_velocity;
this.y += this.y_velocity;
});
}
});
Crafty.scene("loading");​
In the end of "enterFrame" you have:
if (!left && !right) {
this.x_velocity = 0;
this.stop();
}
if (!up && !down) {
this.y_velocity = 0;
this.stop();
}
Therefore, the animation is always stopped and then restarted from the beginning in the next frame. For example, when you're player is moving down, (!left && !right) evaluates true and the animations is stopped.
One solution is to stop the animation only if all 4 directions are false.
if (!left && !right) {
this.x_velocity = 0;
}
if (!up && !down) {
this.y_velocity = 0;
}
if (!(up | down | left | right)) {
this.stop();
}