jQuery Connect four - help debugging function - javascript
I'm having problems with my jQuery homework (I'm a student and a JS beginner).
Basically the assignment is to create a Connect four game with jQuery using the MV(C) (we don't use the controller) schema.
The playing field is a 2D array and looks like this.
- , - , - , - , - , - , -
- , - , - , - , - , - , -
- , - , - , - , - , - , -
- , - , - , - , - , - , -
- , - , - , - , - , - , -
- , - , - , - , - , - , -
and the players play by pressing the columns (f.ex. Player 1 presses 3)
- , - , - , - , - , - , -
- , - , - , - , - , - , -
- , - , - , - , - , - , -
- , - , - , - , - , - , -
- , - , - , - , - , - , -
- , - , x , - , - , - , -
and the Player 2 presses 4
- , - , - , - , - , - , -
- , - , - , - , - , - , -
- , - , - , - , - , - , -
- , - , - , - , - , - , -
- , - , - , - , - , - , -
- , - , x , o , - , - , -
and so on.
When the game is over the winning four letter should change to uppercase.
I'm stuck and don't really know how to go on so I created a GitHub repository with all the code.
https://github.com/VeronicaLeeds/connectfour
Basically it initializes the playing field and you can press the numbers but then the play() method won't work.
It would be great if anyone could tell me why it doesn't change the value of the array from - to x/o ?
This is the code:
checkAvailableRow(column) {
for (var i = 5 ; i >= 0 ; i--) {
console.log(currentPlayer);
if (playFieldArray[column][i] === "-") {
return i;
if(currentPlayer == "Player 1"){
playFieldArray[column][i] = "x";
} else {
playFieldArray[column][i] = "o";
}
}
}
return -1;
}
And why the checkIfWon() function in the Model.js isn't working.
Would be eternally grateful for any help.
return i;
A return causes the logical process to return out of the function it is in. As such, none of the logic after it will ever happen. Comment this statement and see if anything changes for you (it should).
Or move it to after the if/else that changes the array value.
Here is an example of how to get available index of the y axes when providing a grid and x value:
//function to get available index for y when given grid and x
// it will return false if no y is available
const getY = (grid,index)=>{
const getIndex = (arr,index) => {
if(arr[index]==="-"){
return index;
}
if(index<0){
return false;
}
return getIndex(arr,index-1);
}
return getIndex(grid[index],grid[index].length-1);
}
//Run some tests:
const rotate = grid =>//just to make the grid look normal in console.log
grid[0].map((i,y)=>grid.map((i,x)=>[y,x]))
.map((row)=>row.map(([y,x])=>grid[x][y]));
const format = grid => rotate(grid).map(row=>row.join(" ")).join("\n");
//function to mutate grid setting items for testing
const setItem = (x,grid,value) => y => grid[x][y]=value;
//7 by 6 grid containing -
const grid = Array.from(new Array(7),()=>Array.from(new Array(6),()=>"-"));
//add some values to grid
[0,1,2,3,4,5].forEach(x=>[0,1,2,3,4,5].slice(x).forEach(setItem(x,grid,"O")));
console.log("grid is:");
console.log(format(grid));
//see the values for y when asking for
console.log("Testing....");
[0,1,2,3,4,5,6].forEach(
x=>console.log(`y is ${getY(grid,x)} when x is:${x}`)
);
The following is a function to check if a player wins given a grid and a position.
const isWin = (grid,position) => {
const [x,y] = position;
const player = grid[x][y];
if(player==="-"){//current location in grid is empty, return false
return false;
}
const countConnectedAllDirections = (directions,position,player) => {
const connectCount = (direction,position,player,count=1)=>{
const [x,y]=[position[0]+count*direction[0],position[1]+count*direction[1]];
if((grid[x] && grid[x][y])!==player){
return count;
}
return connectCount(direction,position,player,count+1);
};
return directions.map(
([back,forward])=>[connectCount(back,position,player),forward]
).map(
([countBack,forward])=>[countBack,connectCount(forward,position,player)]
).map(
([countBack,countForward])=>countBack+countForward-1
).reduce(
(highest,item)=>(item>highest)?item:highest,0
);
}
const directions = [[0,-1],[-1,-1],[-1,0],[-1,1]].map(([x,y])=>[[x,y],[x*-1,y*-1]]);
return countConnectedAllDirections(directions,position,player)>=4;
};
//Run some tests:
const rotate = grid =>//just to make the grid look normal in console.log
grid[0].map((i,y)=>grid.map((i,x)=>[y,x]))
.map((row)=>row.map(([y,x])=>grid[x][y]));
const format = grid => rotate(grid).map(row=>row.join(" ")).join("\n");
const test = (message,pass) =>
(pass)
? console.log(`Passed: ${message}`)
: console.error(`Failed: ${message}`)
//function to mutate grid setting items for testing
const setItem = (x,y,value) => grid[x][y]=value;
//7 by 6 grid containing -
const createGrid = () => Array.from(new Array(7),()=>Array.from(new Array(6),()=>"-"));
var grid = createGrid();
test("Should return false when position is empty",isWin(grid,[0,5])===false);
const posReducer = (all,pos)=>all&&isWin(grid,pos);
[[0,5],[0,4],[0,3]].forEach(([x,y])=>setItem(x,y,"X"));
console.log(format(grid));
test(
"Should return false when position is not empty but not connect 4",
[[0,5],[0,4],[0,3]].reduce((all,pos)=>all||isWin(grid,pos),false)===false
);
[[0,5],[0,4],[0,3],[0,2]].forEach(([x,y])=>setItem(x,y,"X"));
console.log(format(grid));
test(
"Should return true when position is not empty and connect 4 vertical",
[[0,5],[0,4],[0,3],[0,2]].reduce(posReducer,true)
);
grid = createGrid();//reset grid
[[1,5],[2,5],[3,5],[4,5]].forEach(([x,y])=>setItem(x,y,"X"));
console.log(format(grid));
test(
"Should return true when position is not empty and connect 4 horizontal",
[[1,5],[2,5],[3,5],[4,5]].reduce(posReducer,true)
);
grid = createGrid();//reset grid
[[1,5],[2,4],[3,3],[4,2]].forEach(([x,y])=>setItem(x,y,"X"));
console.log(format(grid));
test(
"Should return true when position is not empty and connect 4 diagonal bottom left to top right",
[[1,5],[2,4],[3,3],[4,2]].reduce(posReducer,true)
);
grid = createGrid();//reset grid
[[1,2],[2,3],[3,4],[4,5]].forEach(([x,y])=>setItem(x,y,"X"));
console.log(format(grid));
test(
"Should return true when position is not empty and connect 4 diagonal top left to bottom right",
[[1,2],[2,3],[3,4],[4,5]].reduce(posReducer,true)
);
The following is the complete game:
//view part
const [init,paint] = (()=>{
const getStyle = gridValue =>
(gridValue==="-")
? ""
: `style="background-color:${(gridValue==="RED")?"red":"green"}"`;
const createTD = ([x,y,gridValue]) =>
`<td ${getStyle(gridValue)} data-role="turn" data-position="${x},${y}"></td>`;
const createTR = gridRow =>
`<tr>${gridRow.reduce((result,value)=>result+createTD(value),"")}</tr>`;
const createTable = grid =>
`<table>${
grid.reduce((all,row)=>all+createTR(row),"")
}</table>`
;
const createMessage = message => `<div><h1>${message}</h1></div>`;
const createResetButton = () => `<button data-role="reset">Reset</button>`;
const createUndoRedo = () => `
<div>
<button data-role="undo_redo" data-direction="-1">Undo</button>
<button data-role="undo_redo" data-direction="1">Redo</button>
</div>
`;
const paint = state =>
state.container.innerHTML =
createTable(state.grid) +
createMessage(state.message) +
createResetButton(state) +
createUndoRedo();
const init = state => state.container.addEventListener(
"click",
e=>{
const role = e.target.getAttribute("data-role");
if(role==="turn"){
const xy = e.target.getAttribute("data-position");
handleEvent(role,xy.split(",").map(Number));
}
if(role==="reset"){
handleEvent(role,{});
}
if(role==="undo_redo"){
const direction = e.target.getAttribute("data-direction");
handleEvent(role,Number(direction));
}
}
);
return [init,paint];
})();
const handleEvent = (()=>{
const createState = ([x,y])=>({
grid:Array.from(new Array(x),()=>Array.from(new Array(y),()=>"-")),
container:document.querySelector("#content"),
currentPlayer:"RED",
winner:false,
message:`It's RED's turn`
});
var state = createState([7,6]);
const isWin = (grid,position) => {
const [x,y] = position;
const player = grid[x][y];
if(player==="-"){//current location in grid is empty, return false
return false;
}
const countConnectedAllDirections = (directions,position,player) => {
const connectCount = (direction,position,player,count=1)=>{
const [x,y]=[position[0]+count*direction[0],position[1]+count*direction[1]];
if((grid[x] && grid[x][y])!==player){
return count;
}
return connectCount(direction,position,player,count+1);
};
return directions.map(
([back,forward])=>[connectCount(back,position,player),forward]
).map(
([countBack,forward])=>[countBack,connectCount(forward,position,player)]
).map(
([countBack,countForward])=>countBack+countForward-1
).reduce(
(highest,item)=>(item>highest)?item:highest,0
);
}
const directions = [[0,-1],[-1,-1],[-1,0],[-1,1]].map(([x,y])=>[[x,y],[x*-1,y*-1]]);
return countConnectedAllDirections(directions,position,player)>=4;
};
const getAvailableIndex = (grid,x)=>{
const getIndex = (arr,y) => {
if(arr[y]==="-"){
return y;
}
if(y<0){
return false;
}
return getIndex(arr,y-1);
}
return getIndex(grid[x],grid[x].length-1);
}
const play = (player,grid,[posX,posY])=>{
return grid.map((arr,x)=>arr.map(
(val,y)=>(x===posX && y===posY) ? player : val
));
}
const rotateGrid = grid =>
grid[0].map((i,y)=>grid.map((i,x)=>[y,x]))
.map((row)=>row.map(([y,x])=>[x,y,grid[x][y]]));
const paintRotate = state => paint({...state,grid:rotateGrid(state.grid)});
const canMove = grid => grid.reduce(
(result,ignore,index)=>result||(getAvailableIndex(grid,index)!==false),false
)
const handleEvent = ((states,statesIndex) => (eventType,data) =>{
const newState={...states[statesIndex]};
if(eventType==="turn"){
if(newState.winner){
return;
}
if(!canMove(newState.grid)){
newState.message=`It's a draw, press reset to play again.`;
paintRotate(newState);
return;
}
const availableY = getAvailableIndex(newState.grid,data[0]);
if(availableY===false){
newState.message=`Cannot play this move, still ${newState.currentPlayer}'s turn.`
if(states[statesIndex].message!==newState.message){
statesIndex=states.length;
states.push(newState);
paintRotate(newState);
}
return;
}
statesIndex=states.length;
const newGrid = play(newState.currentPlayer,newState.grid,[data[0],availableY]);
newState.grid=newGrid;
newState.winner=isWin(newState.grid,[data[0],availableY]);
if(newState.winner){
newState.message=`Winner is:${newState.currentPlayer}`;
states.push(newState)
paintRotate(newState);
return;
}
if(!canMove(newState.grid)){
newState.message=`It's a draw, press reset to play again.`;
states.push(newState);
paintRotate(newState);
return;
}
newState.currentPlayer=(newState.currentPlayer==="RED")?"GREEN":"RED";
newState.message=`It's ${newState.currentPlayer}'s turn`;
states.push(newState);
paintRotate(newState);
}
if(eventType==="reset"){
state = createState([newState.grid.length,newState.grid[0].length]);
statesIndex=states.length;
states.push(state);
paintRotate(state);
}
if(eventType==="undo_redo"){
const newIndex = statesIndex+data;
if(newIndex<0){
paintRotate({...states[statesIndex],message:"Cannot undo. "+states[statesIndex].message});
return;
}
if(newIndex>=states.length){
paintRotate({...states[statesIndex],message:"Cannot redo. "+states[statesIndex].message});
return;
}
statesIndex=newIndex;
paintRotate(states[statesIndex]);
}
})([state],0);
init(state);
paintRotate(state);
return handleEvent;
})();
td {
width:30px;
height:30px;
border:1px solid gray;
cursor:pointer;
}
<div id="content"></div>
Related
Array Changes When A Function is Called (Debugging)
console.clear(); function hori_resolveOverlaps(lines) { if (lines.length <= 1) return lines; // Sort the lines ascending by start value lines.sort((a, b) => a[0][0] - b[0][0]); let outLines = [lines[0]]; let last = outLines[0]; // Iterate over the lines, skipping the first one lines.slice(1).forEach((line) => { // There's an overlap, so extend the current segment's end if (line[0][0] <= last[1][0]) { last[1][0] = Math.max(last[1][0], line[1][0]); } else { // No overlap, start a new segment outLines.push(line); last = outLines[outLines.length - 1]; } }); return outLines; } const input=[ [[1,4],[40,4]] , [[1,5],[40,5]] , [[4,7],[4,24]] , [[1,9],[4,1]] , [[1,2],[6,4]] , [[4,1],[4,2]] , [[4,35],[4,29]] , [[4,28],[4,35]] , [[30,4],[190,4]] , [[5,3.6],[9,5.2]] , [[1,20],[30,1]] , [[15,10.82758],[20,7.55172]] ]; // a function to get the slope and intercept of the line formed by a pair of points function describeLine([[x1, y1], [x2, y2]]) { if (x1 == x2) { // vertical line return {m: "vertical", x: x1} } const p1 = x1 > x2 ? { x: x1, y: y1 } : { x: x2, y: y2 } const p2 = x1 < x2 ? { x: x1, y: y1 } : { x: x2, y: y2 } const m = (p1.y - p2.y) / (p1.x - p2.x) const y = y1 - m * x1 return { m, y } } const maps = input.reduce((acc, line) => { const desc = describeLine(line) const m = acc[desc.m] || { } if (desc.x) { // vertical line x = m[desc.x] || [] return { ...acc, [desc.m]: { ...m, [desc.x]: [ ...x, line ]}} } else { y = m[desc.y] || [] return { ...acc, [desc.m]: { ...m, [desc.y]: [ ...y, line ]}} } }, {}) const sameLines = Object.values(maps).flatMap(Object.values) console.log(sameLines) console.log(hori_resolveOverlaps(sameLines[0]) ) at this particular line, if hori_resolve_Overlaps is not called which means you can comment with (//) to prevent it from running, sameLines arrays which has length of 7 , at its first index array, the value highlighted below weirdly changes without any reason. May I know how to solve this bug? Why the sameLines array can change when hori_resolve_Overlaps() function is called? Any help will be greatly appreciated :) console.log(sameLines) console.log(hori_resolveOverlaps(sameLines[0]) ) I have tried to change or clear the variable scope at the beginning of hori_resolve_Overlaps() function, but it does not solve this issue.
In this case is correct use while instead for?
This is a function that finds the snow level in an array of walls [5,6,7,12,12,4,11,2]. The array[i] is the height of the wall and the snow is between two walls. I want to know is it correct to use while or should always try to use for const findSnowLevel = (walls) => { if (walls === undefined) return new Error('No data') let snowLevels = new Array(walls.length - 1) const fillSnow = () => { let i = 0 while(true) { const nextTallerWall = walls.findIndex(v => v > walls[i]) if (nextTallerWall === -1) break snowLevels.splice(i, nextTallerWall - i, ...Array(nextTallerWall - i).fill(walls[i])) i = nextTallerWall } const nextSameWall = walls.lastIndexOf(walls[i]) snowLevels.splice(i, nextSameWall - i, ...Array(nextSameWall - i).fill(walls[i])) } //fill the snow since left to right fillSnow() //invert to fill the snow since right to left walls.reverse() snowLevels.reverse() fillSnow() //revert and return return snowLevels.reverse() }
d3 ticks function does not return same number of time ticks as specified in input/argument. (React JS)
I am trying to build a responsive chart in React JS and I am not very familiar with D3. I noticed in my code ticks functions doesn't return same number of ticks as I specified. here is my code: const pixelsPerTick = 90; const xScale = d3.scaleTime() .domain([moment(xDomain[0]).toDate(), moment(xDomain[1]).toDate()]) .range(xRange); const getXOffset = value => xScale(moment(value).toDate()); const getYOffset = value => { const elementIndex = yDomain.findIndex(item => item === value); return (elementIndex + 1) * 50; }; const numberOfTicksTarget = Math.max( 1, Math.floor( (xRange[1] - xRange[0]) / pixelsPerTick ) ); const xTicks = useMemo(() => { return xScale.tickValues(numberOfTicksTarget) .map(value => ({ value, xOffset: xScale(moment(value).toDate()); })); }, [ xDomain.join("-"), xRange.join("-") ]); In addition even though I wrote const pixelsPerTick = 90; the pixel per tick does not seem to be always the same. Also if numberOfTicksTarget is 11, the xTicks returns an array of 11 element.
Functional Programming: Calling a Curried Function
I'm implementing the game Tic Tac Toe/Naughts and Crosses in a functional programming style and have stumbled across a hurdle with curried functions. I have a reoccurring pattern of functions in the form func(width, height, index) which I then wish to curry, binding width and height and leaving curriedFunc(index). However the problem arises when I have functions that expect one of these curried functions to be defined at compile-time. They cannot be defined at compile time, because they need input from the user to then bind the values to the function. Below is some example code of the pattern I've encountered. // Board indexes: // 0 | 1 | 2 // ---+---+--- // 3 | 4 | 5 // ---+---+--- // 6 | 7 | 8 const getRowNumGivenWidth = w => i => Math.floor(i/w); // I want to be able to declare nextIndexInRowGivenWidth() here, outside of main() // but getRowNum() needs to be defined beforehand const main = () => { // User input: const width = 3; // ... const getRowNum = getRowNumGivenWidth(width); const nextIndexInRowGivenWidth = width => currentIndex => { const rowNum = getRowNum(currentIndex); const nextIndex = currentIndex + 1; if (getRowNum(nextIndex) != rowNum) result = nextIndex - width; else result = nextIndex; return result; }; const nextIndexInRow = nextIndexInRowGivenWidth(width); const board = [0, 1, 2, 3, 4, 5, 6, 7, 8]; board.map(x => console.log(x, " -> ", nextIndexInRow(x))); // ... } main(); The only way I can think of solving this is to pass the curried function as an argument (to nextIndexInRowGivenWidth() in this example). However I don't think this is ideal as if a function requires a few similarly curried functions at run-time, it quickly becomes unwieldy to define and curry said function. The ideal solution would be if I could somehow make the binding of the values dynamic, suppose I could put the declaration getRowNum = getRowNumGivenWidth(width); before main(). This way I could call something like getRowNum(someInt) to initialise getRowNum() which I could then use in other functions that are already expecting it to be defined. As this is a reoccurring pattern in my code, I was wondering if there is a design pattern to achieve this.
I think you are looking for const getRowNumGivenWidth = w => i => Math.floor(i/w); const nextIndexInRowGivenWidth = width => { const getRowNum = getRowNumGivenWidth(width); //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ return currentIndex => { const nextIndex = currentIndex + 1; if (getRowNum(nextIndex) != getRowNum(currentIndex)) return nextIndex - width; else return nextIndex; }; }; const main = () => { // User input: const width = 3; const nextIndexInRow = nextIndexInRowGivenWidth(width); // ... } Alternatively, you could define that nextIndexInRowGiven… function not with the width as the first curried parameter, but with getRowNum itself as the parameter: const getRowNumGivenWidth = w => i => Math.floor(i/w); const nextIndexInRowGivenRowNumGetter = getRowNum => currentIndex => { const nextIndex = currentIndex + 1; if (getRowNum(nextIndex) != getRowNum(currentIndex)) return nextIndex - width; else return nextIndex; }; const main = () => { // User input: const width = 3; const nextIndexInRow = nextIndexInRowGivenRowNumGetter(getRowNumGivenWidth(width)); // ... }
pushing an object giving me push not a function
been trying to debug for hours already been searrching but i cant get it . ratings = { "hotel_a": "2.8", }; ///ratings.push ( { hotel_b : 3.2} ); console.log(ratings); // total number of stars const starTotal = 5; for (const rating in ratings) { const starPercentage = (ratings[rating] / starTotal) * 100; const starPercentageRounded = `${(Math.round(starPercentage / 10) * 10)}%`; document.querySelector(`.${rating}.stars - inner`).style.width = starPercentageRounded; } it gives me ratings.push is not a function.
///ratings.push ( { hotel_b : 3.2} ); That wont work because ratings is an object and not array. What you can do is create a new key and value. ratings["hotel_b"] =3.2 or ratings.hotel_b =3.2