I'm trying to implement a functionality to a game of Tic Tac Toe.
const "startModule" - Player should be able to select X or O on the start screen.
Then the value should be assigned to an argument "playerChoice", which I'm trying to pass to "gameModule".
In "gameModule", the "playerChoice" argument is being assigned to "circleTurn".
Based on the "circleTurn value, script should assign css value ("X_CLASS" or "CIRCLE_CLASS") to the "currentClass" argument in the "handleClick" function. This is where things are not working.
I'm still learning and would appreciate any help or pointers :)
edit: I've cut a lot from previous js section and left only substantial info (I hope)
const startModule = (() => {
let xButton = document.getElementById("player-choiceX")
let oButton = document.getElementById("player-choiceO")
let playerChoice;
const xoChoice = () => {
xButton.addEventListener('click', function(e){
playerChoice = false;
document.getElementById("playerChoice").style.display = "none";
console.log(playerChoice);
})
}
return {
xoChoice, playerChoice
};
})();
const gameModule = (() => {
const board = document.getElementById("board");
const X_CLASS = 'x';
const CIRCLE_CLASS = 'circle';
const resetButton = document.getElementById("reset");
const winningMessageElement = document.getElementById("winningMessage");
const cellElements = document.querySelectorAll("[data-cell]");
const winningMessageTextElement = document.querySelector("[data-winning-message-text]");
let circleTurn = playerChoice
const WINNING_COMBINATIONS = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
]
const startGame = () => {
resetButton.addEventListener('click', startGame);
cellElements.forEach(cell => {
cell.classList.remove(X_CLASS)
cell.classList.remove(CIRCLE_CLASS)
cell.removeEventListener('click', handleClick)
cell.addEventListener('click', handleClick, {once: true})
})
setBoardHoverClass()
winningMessageElement.classList.remove('show')
}
const handleClick = (e) => {
const cell = e.target
const currentClass = circleTurn ? CIRCLE_CLASS : X_CLASS
placeMark(cell, currentClass)
if (checkWin(currentClass)){
endGame()
} else if(isDraw()) {
endGame(true)
} else {
swapTurns();
setBoardHoverClass();
}
}
}
return {
startGame, handleClick
}
})();
startModule.xoChoice()
gameModule.startGame()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="styles.css">
<script src="index2.js" defer></script>
<title>TicTacToe</title>
</head>
<body>
<div class="player-choice" id="playerChoice">
<div class="xOrCircle">
<div class="player-choiceX" id="player-choiceX"></div>
<div class="player-choiceO" id="player-choiceO"></div>
</div>
</div>
</div>
<div class="board" id="board">
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
</div>
<div class="winning-message" id="winningMessage">
<div data-winning-message-text></div>
<button id="reset">Reset</button>
</div>
</body>
</html>
So there are a few things about what you have that I want to point out.
For the sake of explaining things, I am going to rewrite your startModule to make it easier to digest
const startModule = (() => {
let playerChoice;
const xoChoice = () => {
playerChoice = false;
};
return {
xoChoice, playerChoice
};
})();
Based on this version of startModule, checkout the outputs from the following code:
startModule.playerChoice; // undefined
startModule.xoChoice();
startModule.playerChoice; // undefined
Ok so what went wrong that playerChoice did not become false? It has to do with something called pass-by-value and pass-by-reference (more on that here)
Basically, because playerChoice was assigned a primitive value (undefined), when it was returned here:
return {
xoChoice, playerChoice
};
The return statement only returned the primitive value undefined, not a reference to say an Object.
Fortunately, there is a work around, by utilizing the pass-by-reference functionality in Javascript.
If instead of storing playerChoice with a primitive value (undefined) and returning it, we could store an Object with the necessary context of the module. This should work because Objects are passed-by-reference, not passed-by-value. The code for this can look something like this:
const startModule = (() => {
let context = {
playerChoice: undefined
}; // This is a JSON Object!
const xoChoice = () => {
context.playerChoice = false;
};
return {
xoChoice, context
};
})();
Now let's look at what is outputted from the following code based on this version of startModule:
startModule.context.playerChoice; // undefined
startModule.xoChoice();
startModule.context.playerChoice; // false
It works! This is the case because since we are returning an Object (context), so we are returning a reference to a place of storage (to my knowledge :) ), instead of returning a primitive value (ex. undefined, false) with no reference to a place in storage. All of this means that the playerChoice that is changed when the xoChoice function is called is interlinked with the playerChoice that is returned when startModule is first called.
Ok so how can you utilize this information to share the value of playerChoice amongst the modules? For the sake of answering this question, I am going to again, simplify gameModule to make things easier to digest.
const startModule = (() => {
// ... (What we previously had)
})();
const gameModule = (() => {
let circleTurn = context.playerChoice;
return {
circleTurn
};
})();
Same as before, let's run some code to see what's happening:
startModule.context.playerChoice; // undefined
gameModule.circleTurn; // undefined
startModule.xoChoice();
startModule.context.playerChoice; // false
startModule.circleTurn; // false
So there you go, using pass-by-reference we we're able to transmit data from one module (startModule), to another (gameModule)!
Feel free to ask any other questions, and if you'd like I can show you another interesting alternative way to coding this.
EDIT: Here is the interesting (not better or worse) alternative way. With the previous way, you ran a function that did all this stuff to return an informative JSON Object to which you can retrieve information like a normal JSON Object. Instead of making startModule the return value of a function, you can simply assign startModule to a JSON Object, with functions that do stuff (the stuff from earlier) as value of the Object. It can look something like this:
const startModule = {
playerChoice: undefined,
xoChoice: function () {
// 'this' references the 'startModule' Object
this.playerChoice = false;
}
}
As you notice, startModule is now a JSON Object, but still with all the same components as before. It's important to read up about the this keyword, as it involves itself heavily with this alternative. (FYI, if you're using arrow functions, this keyword is never assigned, so make sure you use ES5 functions if you want to use this).
Let's look at the following code and its output:
startModule.playerChoice; // undefined;
startModule.xoChoice();
startModule.playerChoice; // false
And everything seems to be working fine. While playerChoice was initially undefined, calling xoChoice successfully mutated the value to false.
Now there is a small issue with this alternative. Say when you create the startModule you want to automatically do some logic (Maybe you want to make an HTTPS request to check the user's Tic Tac Toe win streak, maybe you wanna check if the user is logged in, etc). To do this, it's common practice to assign init to startModule, and assign the value to a function that perform initial logic. It can look something like this:
const startModule = {
playerChoice: undefined,
xoChoice: function () {
// 'this' references the 'startModule' Object
this.playerChoice = false;
},
init: function () {
// do initialization stuff
this.userLoggedIn = true;
}
}
Now obviously, we would need to run startModule.init() as soon as we assign startModule. Here's a more elegant solution to that:
const startModule = {
playerChoice: undefined,
xoChoice: function () {
// 'this' references the 'startModule' Object
this.playerChoice = false;
},
init: function () {
// do initialization stuff
this.userLoggedIn = true;
return this;
}
}.init();
Here, we call init as soon as we create the Object, but because init returns this, we can still assign startModule to this function call.
Now let's look at a possible implementation of gameModule:
const gameModule = {
board: document.getElementById("board"),
X_CLASS: 'x',
CIRCLE_CLASS: 'circle',
resetButton: document.getElementById("reset"),
winningMessageElement: document.getElementById("winningMessage"),
cellElements: document.querySelectorAll("[data-cell]"),
winnningMessageTextElement: document.querySelector("[data-winning-message-text]"),
circleTurn: startModule.playerChoice, // look for the playerChoice value in the startModule Object
WINNING_COMBINATIONS: [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
],
startGame: function () {
// 'this' references the Object this function lives under
this.resetButton.addEventListener('click', this.startGame);
this.cellElements.forEach(cell => {
cell.classList.remove(this.X_CLASS)
cell.classList.remove(this.CIRCLE_CLASS)
cell.removeEventListener('click', this.handleClick)
cell.addEventListener('click', this.handleClick, {once: true})
})
setBoardHoverClass() // idk where this func comes from
this.winningMessageElement.classList.remove('show')
},
handleClick: function (e) {
// etc...
}
}
With this implementation, all our variables are stored as key-value pairs, and we use this to reference the Object they're in and retrieve their values. You can also see in the value to circleTurn that we use startModule.playerChoice to get the playerChoice value from startModule.
I wish I knew the technical name for both these different implementations, but personally I enjoy the alternative I just showed, as it feels simpler to extend upon, and IMO, feels easier to separate logic in a clearer sense. Nonetheless, you do you, both options are perfectly fine and offer their own pros and cons.
Related
I have a function that takes a few discounts and groups them by days, so the final result is an array of 7 representing each day with a few discounts or none in each index, for some reason this computation makes the UI don't feel very snappy, only fully completing when I press the UI and showing an uncompleted result UI before that...
I tried placing a function which gives me the resulting array to the default state of my state hook but I gwt the following error:
getVenueDiscounts is not a function
Before this I was using useEffect with empty array to get my array but as I said it didn't feel very snappy...
Here is my method, maybe the problem is I have not optimized my business logic, is it possible to do this calculations better , maybe with a lodash method?
My method for grouping discounts by week days:
const [ weekDiscounts, setWeekDiscounts ] = useState(getVenueDiscounts());
const getVenueDiscounts = () =>
{
if(discounts.length == 0)
{
var weekDiscountsCopy = [ [], [], [], [], [], [], [] ];
//let weekDiscountsCopy = [...weekDiscounts];
//let response = await venueStore.getVenueDiscounts({ id:1 });
console.log(response.data);
let allDiscounts = _.cloneDeep(venueStore.myDiscounts);
//let allDiscounts = response.data.discounts;
//Alert.alert(response.data.discounts.length+' discounts');
//setDiscounts(discounts);
console.log(response.data.discounts[0].days);
for(var i=0; i<response.data.discounts.length;i++)
{
let discountDays = allDiscounts[i].days;
for(var x=0; x<discountDays.length;x++)
{
if(discountDays[x].id == 1)
{
weekDiscountsCopy[0].push(allDiscounts[i]);
}
else if(discountDays[x].id == 2)
{
weekDiscountsCopy[1].push(allDiscounts[i]);
}
else if(discountDays[x].id == 3)
{
weekDiscountsCopy[2].push(allDiscounts[i]);
}
else if(discountDays[x].id == 4)
{
weekDiscountsCopy[3].push(allDiscounts[i]);
}
else if(discountDays[x].id == 5)
{
weekDiscountsCopy[4].push(allDiscounts[i]);
}
else if(discountDays[x].id == 6)
{
weekDiscountsCopy[5].push(allDiscounts[i]);
}
else if(discountDays[x].id == 7)
{
weekDiscountsCopy[6].push(allDiscounts[i]);
}
}
//setWeekDiscounts(weekDiscountsCopy);
setDiscounts(allDiscounts);
return weekDiscountsCopy;
}
}
};
Thanks in advance!
This is an issue of how and when your code gets interpreted.
Because getVenueDiscounts is a lambda assigned to a const, it gets hoisted to the top of the scope, but remains undefined until that code is actually executed on the second pass -- and the state hook is being initialized with that call before the function has been defined. One solution would be to just move getVenueDiscounts above the hook.
A more detailed description can be found here:
These variable declarations only become initialized when they are evaluated during runtime. The time between these variables being declared and being evaluated is referred to as the temporal dead zone. If you try to access these variables within this dead zone, you will get the reference error above.
Reason of error
Well, you missed on a fundamental concept of programming, that one should not access a variable, function, etc. before initialisation.
MCVE
A minimal, reproducible example of your error would be to try accessing bar before it's initialisation.
const foo = () => {
bar();
^^^^^ // Uncaught ReferenceError: Cannot access 'bar' before initialization
const bar = () => console.log('I worked!');
};
foo();
Solution
There can be two solutions for your problem:
Declare getVenueDiscounts() function before useState() hook.
const getVenueDiscounts = () => {
// logic goes here
}
const [ weekDiscounts, setWeekDiscounts ] = useState(getVenueDiscounts());
Better approach would be to use useMemo() hook which would return a cached value unless the dependencies in the array do not change.
const weekDiscounts = useMemo(() => {
// getVenueDiscounts() logic goes here
},[]); // list of dependencies on which weekDiscounts depends on
I'm just wondering how to figure this strange security/scope question out:
function vector() {
var array = [];
return {
append: function append(v) {
array.push(v);
},
get: function get(i) {
return array[i];
},
store: function store(i,v) {
array[i] = v;
}
};
}
This is the question asked:
Can you spot any security concerns with this approach? Mainly, can we get access to the array outside of vector? Note*: the issue has nothing to do with prototypes and we can assume that global prototypes cannot be altered. Hint*: Think about using this in a method invocation. Can we override a method of vector?
Example
var v = vector();
v.append(1);
v.append(2);
var internalData = exploitVector(v); // [1, 2]
My attempts + thoughts
Pretty sure I'm supposed to use the this keyword somehow as the hint says.
I'm a beginner at javascript so I don't fully understand the context that well. This code is written in a file with other functions on the text editor Atom, not a browser.
function exploitVector(v) {
v.get = function() {
return this.array;
};
console.log(v.get());
return v.get();
}
Also, this is just a fun exercise I saw on a github repo.
Vector.store() can be abused to modify the array methods (e.g. array.push), followed by a v.append() to trigger the modified array.push method. The modified push method can for example either do something like window.visiblearray=this after which, visiblearray can be accessed globally.
Or as in the example below, store this (Array instance) to visiblearray of local scope, and then return it.
function vector() {
var array = [];
return {
append: function append(v) {
array.push(v);
},
get: function get(i) {
return array[i];
},
store: function store(i,v) {
array[i] = v;
}
};
}
var v = vector();
v.append(1);
v.append(2);
var internalData = exploitVector(v); // [1, 2]
function exploitVector(v) {
var visible_array;
v.store('push', function(x){visible_array=this}) // modify array push
v.append(12) // trigger the modified array push
console.log(visible_array);
return visible_array
}
I dont get why this function's console.log returns the value, and the typeof correctly, but its return value is invalid to use in another function
I'm attempting to use bubble event to collect the value of the clicked button.
<DIV class = "keys">
<button class = "key" id = "rock" value = "rock">Rock</button>
<button class = "key" id = "paper" value = "paper">Paper</button>
<button class = "key" id = "scissors" value = "scissors">Scissors</button>
</DIV>
Here is the JS:
const bubbleBar = document.querySelector(".keys");
bubbleBar.addEventListener("click", playerPlay);
bubbleBar.addEventListener("click", display);
function playerPlay(e){
let playerChoice = ""
console.log(e.target.value); //debugs correctly
playerChoice = e.target.value;
console.log(playerChoice, typeof playerChoice); //debugs correctly
return playerChoice; // apparently returns nothing
}
function display(){
console.log(playerPlay) // displays function playerPlay(e), not the result of the first function.
}
As stated in comments, returning a value from an event handler doesn't do anything with that value. The value is used to determine further event processing.
If you want the value you will have to store it somewhere from within the handler.
This code saves the value in a global variable chosenValue and also sets the value as the text within a <span> — what you actually do to store the value will depend on how you plan to make use of the value later.
let chosenValue;
const bubbleBar = document.querySelector(".keys");
bubbleBar.addEventListener("click", playerPlay);
bubbleBar.addEventListener("click", display);
function playerPlay(e) {
e.preventDefault();
let playerChoice = ""
console.log(e.target.value); //debugs correctly
playerChoice = e.target.value;
console.log(playerChoice, typeof playerChoice); //debugs correctly
document.getElementById('playerChoice').innerText = playerChoice;
chosenValue = playerChoice;
console.log(`chosenValue variable value is now ${chosenValue}`);
}
function display(e) {
e.preventDefault();
console.log(e.target.value);
}
<div class="keys">
<button class="key" id="rock" value="rock">Rock</button>
<button class="key" id="paper" value="paper">Paper</button>
<button class="key" id="scissors" value="scissors">Scissors</button>
</div>
<div>
Player's Choice: <span id="playerChoice">none</span>
</div>
A note on scope and return statements
Nested scopes can often be confusing, and where things return to isn't always clear. Here's some examples fo reference:
function simple(){ return 'foo' }
Returns "foo" when simple is called like simple()
(or simple("args", "aren't", "used", "but still are passed") or simple.call())
function nested(){
return function inner(){
return 'foo'
}
}
nested, when called, returns a function, but that function does not run immediately. so to get "foo" you would need to call it like this:
let outerResult = nested() // creates and returns a function
outerResult() // returns "foo"
//or more concisely
nested()() // calls both functions in order.
Here we see that return is always going to return to the function it is "closest" too, the inner-most function. return can't skip out of the function it is in.
function loop(){
let values = [1,2,3]
for (let value of values){
return value
}
}
Returns 1, and the loop does not run another time. So in this case the block does not change the scope of return.
function callbackLoop(){
let values = [1,2,3]
values.forEach(value => { return value })
}
This code has a bug. You might expect that this code would work the same as the regular loop, but it doesn't, and in fact, it can't. There is no way for the return in the function value => { return value } to cause the callbackLoop function to return. It can only return itself, same as the nested example. Sometimes functions that take callbacks will pass along the callback return value, but they don't have to if they don't want to. Since forEach is designed for side effects (functions that don't have return values) it doesn't do this, and neither does addEventListener, because it also is designed for side effects.
An example of a function that takes a "pure function" callback, instead of side effects, is Array.prototype.map:
const result = [1, 4, 9, 16].map(x => {return x * 2});
// `map` returns: [2, 8, 18, 32]
// (it gets the return value of the callback function for
// each item in the array, and uses that)
A "pure function" is a function that operates on only the inputs (x) in this case, and makes no changes to any variables. Multiplication "creates" a new value, and that is what is returned. The original x is not "mutated" (it is left unchanged). In order for pure functions to be useful, they need to return a value, and that value needs to be used in some way.
I am refactoring my https://github.com/perdugames/cacau test library, which I did to learn a bit more about JavaScript and testing. The problem is that I'm not liking how FIXTURE are currently working on it, so I decided to refactor, and I wanted to get the result below, but I still do not know how to make the "addTest" functions have access to "x".
const runner = new Runner();
runner.addSuite(() => {
console.log('Suite 1');
runner.addFixture(() => {
console.log('Suite');
const x = 10;
});
runner.addTest(() => {
console.log('Test 1');
console.log(x);
});
runner.addTest(() => {
console.log('Test 2');
console.log(x);
});
});
runner.run();
Note: Of course without the use of globals, and without adding to the scope of "runner".
Actually, you should be using contexts. Please read more about contexts in javascript. There's plenty of sites.
In your case you can call a context
obj.funcA(() => {
let ctx = this;
console.log('A');
obj.funcA1(() => {
let ctxFncA1 = this // context of previous level... etc.
console.log('A1');
const x = 10;
});
// ... you get the point.
});
In general what you're trying to do is not "ok". If you go build object class, the JavaScript language might allow you to do really anything, but you must not.
Probably you should look into object programmation in JavaScript (book or training website).
Actually you have an example describing pretty much what you desire on : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let (Code is copy/pasted from there.)
var Thing;
{
let privateScope = new WeakMap();
let counter = 0;
Thing = function() {
this.someProperty = 'foo';
privateScope.set(this, {
hidden: ++counter,
});
};
Thing.prototype.showPublic = function() {
return this.someProperty;
};
Thing.prototype.showPrivate = function() {
return privateScope.get(this).hidden;
};
}
console.log(typeof privateScope);
// "undefined"
var thing = new Thing();
console.log(thing);
// Thing {someProperty: "foo"}
thing.showPublic();
// "foo"
thing.showPrivate();
// 1
I do not believe this is physically possible. You would have to pass the the variable to the function in order to access it if you do not want a global variable, or have it accessible in the scope of a class or object.
While this variable should be in your heap, there is no way to communicate its address to the scope of the new function without passing something to it, either a pointer or the value itself.
I found two ways to resolve the issue, the first was:
function test(){
console.log(this.x * 2);
}
function fixture() {
this.x = 5;
}
test.call(new fixture()) // 10
This is the worst way I found, since I will always have to use "this", and I will still have to deal with FIXTURE as a constructor function, otherwise it would be better to use a literal object in this case.
The second way I chose it is simpler, and it already fits in with what I have ready:
const runner = new Runner();
runner.addSuite(() => {
console.log('Suite 1');
var x;
runner.addFixture(() => {
console.log('Suite');
x = 1;
x++;
});
runner.addTest(() => {
console.log('Test 1');
console.log(x); // 2
});
runner.addTest(() => {
console.log('Test 2');
console.log(x); // 2
});
});
I have been looking into partial application and currying over the last few days.
I'm wondering how could I use these concepts with a function that only takes one options object as argument.
const myFunc = options => {
const options.option1 = options.option1 || 'default value';
const options.option2 = options.option2 || 'another default value';
// ... etc, it takes about 5 more options, all of which have a
// default fall-back value if not supplied
return doSometing(options);
}
In that case, I don't feel good changing the myFunc signature and pass every option as a separate argument because it's a pain to remember the order in which the options must be supplied.
I'd like to stick with a functional style and avoid instantiating objects with new ... just to keep state; I have a hunch this can be achieved with partial application. It keeps things simpler when it's time for testing, or to instantiate.
But then, I don't know how to do partial application properly without separate arguments.
How would you handle this refactor?
I would suggest that the equivalent of currying a function taking an option object would be the same as how to handle defaults. Consider this as a partial applier:
myFuncWithMyOptions(myFunc, myOptions) {
return function(options) {
return myFunc(Object.assign({}, myOptions, options));
}
}
If you want the options in myOptions not be be overridden by those in options simply swap the two arguments to Object.assign
Following on Dan D's answer and the comments, this technique would let you partially apply such a function repeatedly until all the required fields are supplied:
const vals = (obj) => Object.keys(obj).map(key => obj[key]);
const addDefaults = (() => {
const req = Symbol();
const addDefaults = (defaults, fn) => (options) => {
const params = Object.assign({}, defaults, options);
return (vals(params).includes(req))
? addDefaults(params, fn)
: fn(params);
};
addDefaults.REQUIRED = req;
return addDefaults;
})();
const order = addDefaults({
color: addDefaults.REQUIRED,
size: addDefaults.REQUIRED,
giftWrap: false,
priority: false
}, (opts) =>
`${opts.size}, ${opts.color}, ${opts.giftWrap ? '' : 'no'} wrap, priority: ${opts.priority}`
);
order({color: 'Red', size: 'Medium'}); // "Medium, Red, no wrap, priority: false"
const orderLarge = order({size: 'Large'}); // Options -> String
orderLarge({color: 'Blue'}); // "Large, Blue, no wrap, priority: false"
I don't think your problem is connected with partial application. What exactly does myFunc do actually?
it sets a couple of optional default values
it invokes another function
This is not much. And yet two problems arise:
the function composition is hard coded and hidden in the body of myFunc
it doesn't get apparent from the function signature which default values are overwritten
Simply put, a "proper" function reveals its functionality by its signature. So let's get rid of the myFunc wrapper:
const options = {
foo: 1,
bar: true,
bat: "",
baz: []
};
// function composition
const comp = (...fs) => x => fs.reduceRight((acc, f) => f(acc), x);
// applicator for partial application with right section
const _$ = (f, y) => x => f(x) (y); // right section
// `Object` assignment
const assign = o => p => Object.assign({}, o, p);
// specific function of your domain
const doSomething = o => (console.log(o), o);
// and run (from right-to-left)
comp(doSomething, _$(assign, {baz: [1, 2, 3], bat: "abc"})) (options);
Now you can exactly see what is going on without having to look into the function bodies. The property order of the options Object doesn't matter either.
A remark on _$. It has this odd name because I prefer a visual name over a textual one in this particular case. Given the function sub = x => y => x - y, _$(sub, 2) simply means x => x - 2. This is called the right section of the partially applied sub function, because the "left" argument is still missing.