Accessing objects from JavaScript factories and modules - javascript

I am writing a Tic Tac Toe app in Javascript, HTML and CSS, where I have to practice placing all my functions inside modules or factories. Currently I am having an issue accessing a factory created object inside other modules.
My complete code can be found here:
https://codepen.io/wulfenn/pen/xxVqGrW
Breakdown of my code:
I have two modules and one factory; gameBoard is a module in charge of anything board related. (reading the board, modifying the board, visual effects, etc)
The game module is in charge of the functionality (starting new rounds, handling player turns, checking for winners, etc)
Lastly, Player is my factory to create new players and has simple functionality, such as retrieving the name, or retrieving the mark assigned to it. (X or O)
The problem I am currently having is when creating a player with const p1 = Player('P1Name', 'X') inside the game module, specifically the soloPlay() function inside it. If I try to access it within said function, I have no problems, but the rest of the code cannot access it.
If I were to create a const p1 = Player('P1Name', 'X') outside of my factory and modules, all the code can access it. But the issue is that I need to be able to create a new player within the soloPlay() function inside the game module.
How can I go about calling soloPlay() and still be able to access P1 and P2 objects along with their functions outside of the code?
Thanks for your help, and I apologize if my code is cluttered or poor, I am still learning.
Relevant code if you don't want to go through all my project:
soloPlay() inside a game module.
// Our second menu for solo play, to set the P1 name, and start the game once done.
const soloPlay = () => {
const enterBtn = document.querySelector('.enter-btn');
enterBtn.addEventListener('click', function () {
const p1 = Player(document.querySelector('.p1').textContent, 'X');
const p2 = Player('AI', 'O');
const soloMenu = document.querySelector('.solo-menu');
soloMenu.style.display = 'none';
const gameMenu = document.querySelector('.menu-bg');
gameMenu.style.display = 'none';
game.setup(); // Sets our grid listeners
game.newRound(); // Starts a new round.
});
}
Player Factory
/* Our player factory in charge of creating new players, and assigning them functions to obtain their names and marks */
const Player = (name, mark) => {
const hasTurn = true;
const getName = () => name;
const getMark = () => mark;
return { getName, getMark, hasTurn };
}

Source: https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Sentencias/const
Declaring a variable const, defines it with a block scope, meaning that nothing outside the function that created it will be able to see it... in the case of your players, you are not changing the Player factory, you are creating an instance of it when you declare
const p1 = Player.... so p1 is created within soloPlay and is not accessible to any other part of the code... if you declare p1 and p2 as global (outside any other scope or inside a scope you choose that makes sense for the code) and not declare them as const your game should work.
//This goes outside 'const gameBoard'
var p1;
var p2;
And not declaring here p1 and p2 as const:
const soloPlay = () => {
const enterBtn = document.querySelector('.enter-btn');
enterBtn.addEventListener('click', function () {
p1 = Player((document.querySelector('#p1').value), 'X');
console.log('Jugador 1: '+p1.getName());
p2 = Player('AI', 'O');
console.log('Jugador 2: '+p2.getName());
const soloMenu = document.querySelector('.solo-menu');
soloMenu.style.display = 'none';
const gameMenu = document.querySelector('.menu-bg');
gameMenu.style.display = 'none';
game.setup(); // Sets our grid listeners
game.newRound(); // Starts a new round.
});
}

Related

How can multiple Imported classes call each others methods?

I have this index.js file:
import GameController from './Controllers/GameController'
import LandController from './Controllers/LandController'
const GAME = new GameController();
const LAND = new LandController();
And in this class, I want to call a function every second using setInterval:
export default class GameController {
tickCount = 0;
tick = setInterval( () => {
this.Tick()
}, 1000);
Tick () {
LAND.updateLand();
this.tickCount++;
}
}
But I get this error:
Uncaught ReferenceError: GAME is not defined
I'm new to webpack, I had this working in vanilla javascript but I'm still learning, so any help would be greatly appreciated! Thank you
EDIT:
Updated GameController class based on bravo's answer, however now I get the error:
Uncaught ReferenceError: LAND is not defined
How can I make it so multiple classes can call each others methods?
You started using modules and classes, so you are moving to OOP (Object Oriented Programming). This means that you should forget about scripting, where you declare globals, everything manipulates everything, and start reasoning in a structured, hierarchical, and isolated fashion (where you have objects responsible for specific tasks).
You started properly by understanding what isolation is. You created an autonomous class to control the land. Then you need a game controller, which controls the land (and anything else, but we will come back to this lather).
Follow the reasoning; Game controls Land, in OOP this would translate in either Game receives Land or Game creates Land. Which of two to choose depends on the specific use case.
Game creates Land
// ./Controllers/GameController.js
import LandController from './Controllers/LandController'
export default class GameController {
tickCount = 0;
land = new LandController();
intercal = setInterval(this.onTick.bind(this), 1000);
onTick () {
this.land.updateLand();
this.tickCount++;
}
}
// index.js
import GameController from './Controllers/GameController'
const GAME = new GameController();
Here you can see how Game creates Land, this is useful but reduces your access to the Land constructor (assuming you do not want to change Game to configure land)
Game receives Land
// ./Controllers/GameController.js
export default class GameController {
tickCount = 0;
land;
constructor(land) {
this.land = land;
}
intercal = setInterval(this.onTick.bind(this), 1000);
onTick () {
this.land.updateLand();
this.tickCount++;
}
}
// index.js
import GameController from './Controllers/GameController'
import LandController from './Controllers/LandController'
const LAND = new LandController();
const GAME = new GameController(LAND);
Here you have access to both constructors and your code is more maintainable. Game is not concerned about Land (what is, what it has in or where it came from) as long as it has a method updateLand() that has to be called at an interval.
More than land
The second solution allows you to implement a more versatile Game. Assume your Land has a method called update() rather than updateLand().
// ./Controllers/GameController.js
export default class GameController {
tickCount = 0;
children;
constructor(children) {
this.children = children;
}
intercal = setInterval(this.onTick.bind(this), 1000);
onTick () {
this.children.forEach(c => c.update());
this.tickCount++;
}
}
And then you can go beyond Land, maybe have a Character or more Land, or anything else.
// index.js
import GameController from './Controllers/GameController'
import CharController from './Controllers/CharController'
import LandController from './Controllers/LandController'
const LAND1 = new LandController();
const LAND2 = new LandController();
const CHAR = new CharController();
const GAME = new GameController([ LAND1, LAND2, CHAR ]);
This is how you can start reasoning to structure your OOP software properly; objects that are concerned about tasks and other objects.
When an object is not concerned about another object it should not have access to it (so no globals)
When an object is responsible for another object it should have direct access (receive them from the constructor or a setter) or directly instantiate them (create them).
If you really want to make the instances global, which I would not recommend because there are normally are better methods then using global variables, you should setup the variables in the window object
window.yourNamespace = {};
window.yourNamespace.game = new GameController();
window.yourNamespace.land = new LandController();
Then you can access both objects everywhere you like with
window.yourNamespace.game
window.yourNamespace.land
Answering your question about a non global solution:
I can only guess what your application will do but it sounds like a game. So GameController should be the main class. From this class you should instantiate all other classes like the LandController(). In that case you can access the LandController of course from the GameController. If you need an object in a class you should give the object as an parameter. E.g.
Class GameController()
{
landController = new LandController();
… here all needed methods for the class …
// Example if you need LandController in an other Class
exampleMethod()
{
Let otherClass = new OtherClass(this.landController);
}
}
You can do it this way:
We may use a constructor for that.
export default class GameController {
tickCount = 0;
tick = null;
constructor() {
this.tick = setInterval(() => {
this.Tick()
}, 1000);
}
Tick () {
LAND.updateLand();
this.tickCount++;
}
}

Passing a variable to a (globally?) exported class

I apologize if this question doesn't really make sense - I'm probably lacking a bit of an understanding on how exactly module.export works and scoping between classes, but I will try my best to explain. I currently have a discord bot with a music player that has a MusicPlayer class - this class is exported in this manner:
const ytdl = require('ytdl-core-discord');
const MusicQueue = require('./musicqueue');
const discordUtils = require ('../../../utils/discord-utils');
class MusicPlayer {
constructor() {
this.repeat_playlist = false;
this.queue = new MusicQueue(); // this is the persistent queue for the server
this.dispatcher = null;
this.volume = 1;
this.stopped = true;
this.paused = false;
this.repeat_current_song = false;
this.auto_delete = false;
this.manual_index = false;
this.autodcEnabled = false;
}
....
}
module.exports = new MusicPlayer();
I have various music-related commands that import this music player like so:
const { Command } = require('discord.js-commando');
const discordUtils = require ('../../utils/discord-utils');
let musicplayer = require(`./modules/musicplayer`);
module.exports = class TestCommand extends Command {
constructor(client) {
....
}
...
}
My question is, how would I pass a local variable to the musicplayer constructor since I'm exporting it via module.exports = new MusicPlayer();?
It's my understanding that using module.exports = new MusicPlayer(); would allow me to use the same musicplayer object throughout the entire project (which is what I want); but - and this is where I'm not sure I understand how module.exports works - there's no guarantee when (if at all) the musicplayer will be instantiated.
If you never use a music-related command the musicplayer will never be created because let musicplayer = require('./modules/musicplayer'); will never be run, right?
Either way, now I need to pass something to the musicplayer constructor from within one of the commands, how can I do so in a way that I don't have to repeatedly do something like: let musicplayer = require('./modules/musicplayer')(variable); in every file that requires the music player? Is there a way for me to declare let musicplayer = require('./modules/musicplayer')(variable); one time and have that musicplayer be used throughout the entire codebase?
Let's list out a few requirements:
You want to share a single music player among a bunch of code.
You want the music player to be created upon demand, only when someone actually needs it
You want to initialize the music player with some variable you have in your code
To make this work, you have to centralize the creation of the music player and that has to be coordinated with whoever has the initialization variable that you want to pass to the constructor. There are many ways to do that. I will illustrate two of them.
Let's assume that it's your application initialization code that has this variable that you want to use when creating the music player. So, rather than having everyone load the music player module themselves, we will have them all ask the app for the music player.
// app.js
const MusicPlayer = require(`./modules/musicplayer`); // load module to get constructor
let musicPlayerInitVariable; // your code sets this during startup
const sharedPlayer;
function getMusicPlayer() {
if (!sharedPlayer) {
sharedPlayer = new MusicPlayer(musicPlayerInitVariable);
}
return sharedPlayer;
}
// one of many possible exports from your app
module.exports = { getMusicPlayer };
Then, in any of your application files that want access to the shared music player, then just do this:
const musicPlayer = require('./app.js').getMusicPlayer();
This requires that anyone using the shared musicPlayer knows how to reference app.js.
Another way to do it is to have your app, initialize the music player module itself with some initial state that it can then use when it creates the music player upon demand.
// musicplayer.js
const ytdl = require('ytdl-core-discord');
const MusicQueue = require('./musicqueue');
const discordUtils = require ('../../../utils/discord-utils');
class MusicPlayer {
constructor() {
this.repeat_playlist = false;
this.queue = new MusicQueue(); // this is the persistent queue for the server
this.dispatcher = null;
this.volume = 1;
this.stopped = true;
this.paused = false;
this.repeat_current_song = false;
this.auto_delete = false;
this.manual_index = false;
this.autodcEnabled = false;
}
....
}
// this is the constructor variable
// You can either give it a default value so things will work if the player
// is not configured or if you don't give it a default value, then the code
// will require that configurePlayer() is called first
let musicPlayerInitVariable;
// call this before anyone calls getMusicPlayer()
function configurePlayer(data) {
// save this for later
musicPlayerInitVariable = data;
}
let sharedPlayer;
function getMusicPlayer() {
if (!sharedPlayer) {
if (!musicPlayerInitVariable) {
throw new Error('Music player must be configured with configurePlayer() first')
}
sharedPlayer = new MusicPlayer(musicPlayerInitVariable);
}
return sharedPlayer;
}
module.exports = { getMusicPlayer, configurePlayer };
Then, in your app-startup code somewhere, you do this:
require('./modules/musicplayer').configurePlayer(passMagicVariableHere);
From then on, anyone can just do:
const musicPlayer = require('./modules/musicplayer').getMusicPlayer();

I have a declared variable that the code is not recognizing. Why is this?

I am programming a game, and this is a portion of it. I want the program to cycle through the different pictures of the sprite running I have uploaded. I made a variable to let the program know which picture is being shown on screen of the character running. The program does not recognize it is a variable being declared. Am I just making a simple mistake with Javascript?
I have tried to move the variable declaration around my code, and put different things inside of it. Nothing seems to work.
class Example1 extends Phaser.Scene {
constructor() {
super({key: "Example1"});
}
//variable for the current running stance which does not work
var playerBoard = 1;
preload() {
this.load.image('Background', 'assets/Background.jpg');
this.load.image('4 JUMP_000', 'assets/4 JUMP_000.png');
let run1 = this.load.image('3 RUN_000', 'assets/3 RUN_000.png');
let run2 = this.load.image('3 RUN_001', 'assets/3 RUN_001.png');
let run3 = this.load.image('3 RUN_002', 'assets/3 RUN_002.png');
let run4 = this.load.image('3 RUN_003', 'assets/3 RUN_003.png');
let run5 = this.load.image('3 RUN_004', 'assets/3 RUN_004.png');
}
/*another variable to help return to the first running stance when the sprite stops running*/
let runningStance = run1;
//function that switches the running stances
runningScene(x,y){
if(this.input.keyboard.on("keyDown_D")){
while(this.input.keyboard.on("keyDown_D")) {
if (this.playerBoard = 1) {
this.playerBoard1 = 2;
this.image = this.add.image(this.image.x, this.image.y, '3 RUN_001');
runningStance = run2;
run1.visable = false;
}
Some of the code from the program is not included as it would take up too much space. What is not working is the declaration of the variable, as it does not allow the if function to work properly. I have phaser installed, which you can refer to in the code. The output is supposed to hide the starting picture and show the next in the sequence, which will be run2.
Try to declare playerBoard in constructor:
constructor() {
super({key: "Example1"});
this.playerBoard = 1;
}
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
The same for runningStance, please take a look on the above link to check how classes work
This line:
if (this.playerBoard = 1) {
is bad for two reasons. First, = which obviously meant to be == or ===. Second, playerBoard is a variable, and here you try to access and object property. A few lines earlier you declared var playerBoard = 1;, so just simply read it
if (playerBoard === 1) {

Passing $(this) to a new P5 instance is undefined

In the code below, I'm looping through each "player_visualizer" element and attempting to create a new P5 instance for each element.
If I console.log(context) in the loop I will get the context of that particular element, which is exactly what I need.
$('.player_visualizer').each(function (i) {
context = $(this);
playerVisualizersP5[i] = new p5(playerVisualizer, context);
});
However, The trouble I'm having is passing the context of that particular element to the function that will handle all of the P5 animations.
For example, when I try and pass that context variable to the function below and do console.log(p.context), the context variable is always undefined.
let playerVisualizer = function (p, context) {
p.context = context;
}
I've done a fair amount of research on what I could do about this, but I can't seem to tie it back to my particular situation. I've narrowed down my research to a few resources below.
http://hugoware.net/blog/passing-context-with-javascript
How do I pass the this context to a function?
Any help or guidance is greatly appreciated.
Why do you believe that passing something into the p5 constructor will automatically pass that argument into the playerVisualizer function?
From the P5.js documentation:
One final note: when creating a p5 instance, you can specify a second
argument (HTML element id) which acts the parent for all elements
created by the sketch. For example, let's say you have:
<body>
<div id = "p5sketch">
</div>
<p>Some other HTML</p>
</body>
You can now say:
var myp5 = new p5(s,'p5sketch');
And all elements will be created inside that div.
This means the only valid second argument is a string ID, which gets used by P5.js but isn't passed into the sketch function.
To understand better what's going on, let's look at this example:
var s = function( sketch ) {
sketch.setup = function() {
sketch.createCanvas(200, 200);
};
sketch.draw = function() {
sketch.background(128);
};
};
var myp5 = new p5(s);
In this example sketch, there are a few things to understand:
myp5 is an instance of p5, which contains P5.js functions like setup() and draw() and background().
s is a sketch function, which takes an instance of p5.
sketch is an instance of p5, which s can use to access P5.js functions.
In other words, myp5 and sketch are the same object.
This is useful to you, because if you want to pass data into sketch, you can pass that data into myp5, like this:
var s = function( sketch ) {
sketch.setup = function() {
sketch.createCanvas(200, 200);
};
sketch.draw = function() {
sketch.background(128);
sketch.text(sketch.extraThing, 20, 20);
};
};
var myp5 = new p5(s);
myp5.extraThing = "testing";

NodeJS required module in module is always undefined

I am pretty much new to backend-code. For now I try learning the architecture of a nodeJS-server.
My Problem for now is pretty simple I believe, but I can't run through -.-
server.js (main):
var io = require("socket.io"),
express = require("express"),
expressHbs = require('express3-handlebars'),
Player = require("./models/Player").Player,
Room = require("./models/Room").Room,
Game = require("./models/Game").Game,
dataSet = require('./data/data.json');
//...
Then I have 3 modules:
Player
Room
Game
Each module is the same structure:
Player.js
// importing another module
var Room = require("./Room").Room;
if (!Player.players) {
Player.players = {};
}
function Player(foo) {
this.foo = foo;
Player.players[foo] = this;
}
Player.getAllPlayersAsObject = function () {
return Player.players;
};
// ...
Player.prototype.removePlayer = function () {
if (Player.players.hasOwnProperty(this.id)) {
delete Player.players[this.id];
}
};
// ...
/**
* node export
* #type {Player}
*/
exports.Player = Player;
Room.js
var Player = require("./Player").Player;
if (!Room.rooms) {
Room.rooms = {};
}
function Room(foo) {
this.foo = foo
}
Room.getAllRoomsAsObject = function () {
return Room.rooms;
};
Room.prototype.toString = function () {
return JSON.stringify(this);
};
/**
* node export
* #type {Room}
*/
exports.Room = Room;
My main problem is, that I can use Room, Player, Game from server.js - constructor, prototype and all other functions normally.
In Player.js I can use the imported Room-module normally, too!
Room.js tells me:
path/path/path/gameserver/models/Room.js:222
var currentPlayer = Player.getPlayer(this.players[player].id);
^
TypeError: Cannot read property 'getPlayer' of undefined
I am going crazy. What am I doing wrong? When I do a console.log (debug) of that variable "Player" at the top of the file, I am always getting undefined.
I walked around on google and here at stackoverflow, not finding any solution for my problem.
Thx and best regards,
Michael
You have a cyclic dependency between your modules.
You start by requiring Player. The first thing it does is require Room. Room then requires Player. At this point, Player has not yet completed its loading. Node prevents an infinite loop occurring by returning an incomplete version of Player. At this point, all bets are off as to what will happen.
You need to structure your modules to avoid this direct loop.
Background information of cyclic dependencies here: https://nodejs.org/api/modules.html#modules_cycles

Categories