rock, paper, scissors game using OOP in javascript. better way? - javascript

I'm wondering if i'm on the correct way to not overkill a simple 2 player rock, paper and scissors game. so far, I have only one class that I'm using in Expressjs.
import { IRuleSet, ruleSet } from './../shared/constants/ruleSet';
export class Game {
_player1Score: number;
_player2Score: number;
ruleSet: IRuleSet[];
constructor() {
this._player1Score = 0;
this._player2Score = 0;
this.ruleSet = ruleSet;
}
restartGame(): void {
this._player1Score = 0;
this._player2Score = 0;
}
whoWins(player1Choice: string, player2Choice: string): void {
ruleSet.forEach((rule: IRuleSet, index: number) => {
if (player1Choice == ruleSet[index].choice) {
if (rule.losesTo.includes(player2Choice)) {
console.log('player2Score', this._player2Score);
return {
player1Score: this._player1Score,
player2Score: ++this._player2Score,
message: 'player2 Wins',
};
} else if (rule.beats.includes(player2Choice)) {
return {
player1Score: ++this._player1Score,
player2Score: this._player2Score,
message: 'player1 Wins',
};
}
}
});
}
}
public play(req: Request, res: Response) {
const { player1Move, player2Move } = req.body;
const game = new Game();
const { player1Score, player2Score } = game.whoWins(
player1Move,
player2Move
);
res.render('game', { data: req.body, player1Score: player1Score, player2Score: player2Score, message: message });
}
I am however facing a blocker in terms of restarting the game, see when both players input their move, I get to pick find the winner using whoWins() but then if both players play again, their scores will be set to 0 because a new Game class is being instantiated which brings both scores to 0. I'm curious as to how would you change this into maybe two classes, Game and Player ? ideally, the game would be having 3 rounds after which the user could restart the game which would reset both scores.

Related

JHipster Blueprint | Yeoman Generator - Using a user's answer to a prompt for templating with EJS

I am developping a JHipster blueprint and I need to use EJS to template the files I want to generate. Since this is my first time using EJS, all I am trying to do for now is use an answer from one of the generated question and create a java interface with its name.
This is the template I got:
public interface <%= databaseURL %> {
}
prompts.js:
function askForDatabaseURL(meta) {
const applicationType = this.applicationType;
const prompts = [
{
type: 'string',
name: 'databaseURL',
message:
'Quel est l\'URL de votre base de données ?',
default: 'URL'
}
];
if (meta) return PROMPTS;
const done = this.async();
this.prompt(prompts).then(prompt => {
this.log(this.databaseURL);
this.databaseURL = prompt.databaseURL;
this.log(this.databaseURL);
done();
});
}
module.exports = {
askForDatabaseURL
};
index.js:
const chalk = require('chalk');
const AppGenerator = require('generator-jhipster/generators/app');
const prompts = require('./prompts');
module.exports = class extends AppGenerator {
constructor(args, opts) {
super(args, { fromBlueprint: true, ...opts }); // fromBlueprint variable is important
this.databaseURL = "Hello";
}
get initializing() {
return super._initializing();
}
_prompting() {
return {
askForDatabaseURL: prompts.askForDatabaseURL
}
}
get prompting() {
const defaultPhaseFromJHipster = super._prompting();
const myPrompting = this._prompting();
return Object.assign(defaultPhaseFromJHipster, myPrompting);
}
get configuring() {
return super._configuring();
}
get default() {
return super._default();
}
_writing() {
this.fs.copyTpl(
this.templatePath(`src/main/java/package/repository/JOOQRepository.java.ejs`),
this.destinationPath(`${this.databaseURL}.java`),
{ databaseURL : this.databaseURL}
)
}
get writing() {
const defaultPhaseFromJHipster = super._writing();
const myWriting = this._writing()
return Object.assign(defaultPhaseFromJHipster, myWriting);
}
get install() {
return super._install();
}
get end() {
return super._end();
}
};
The problem is, after the prompting phase, this.databaseURL always has a value of "Hello" which is the default value in the constructor, meaning the file generated is always Hello.java.
I tried to add this.log(this.databaseURL); before and after this.databaseURL = prompt.databaseURL so I'd get an idea if this line does what it's supposed to and it does:
I am fairly new to JavaScript so I might have missed something very basic, but I don't understand why this.databaseURL returns "Hello" after assigning it the user's answer to it.
Any help is welcomed!

Executing class instance method that is in an array (javascript)

EDIT: Solved by renaming the this.powerOn declaration in all of the class constructors.
I have a function that pulls data from a database and stores it in the appropriate array(s). I have another function that iterates through said array(s) and instantiates a new instance of a class based on a targeted property & these instances are stored in a separate array. I am having trouble triggering methods of said instances that, as far as I can tell, should be apart of them.
Currently this is how I am attempting to handle this:
const SourceModel = require('../models/Source');
const DisplayModel = require('../models/Display');
const Roku = require('../_data/sdk/Roku');
const Sony = require('../_data/sdk/Sony');
const driversArray = [];
const liveDriverInstances = [];
// Returns new class instance based on DriverModel
class instantiatedDriverClass {
constructor(DriverModel, DriverIPAddress, DriverPort) {
let driverClasses = {
Roku,
Sony
};
return new driverClasses[DriverModel](DriverIPAddress, DriverPort)
}
}
// Pull sources, displays, ..., from DB
const getDevicesFromDB = async () => {
sourcesArray = await SourceModel.find();
displaysArray = await DisplayModel.find();
};
// Create new array from sources, displays, ..., arrays & iterate to instantiate matching driver class
const loadDeviceDriversToRuntime = async () => {
await getDevicesFromDB();
sourcesArray.forEach((source) => driversArray.push(source));
displaysArray.forEach((display) => driversArray.push(display));
driversArray.forEach((driver) => {
liveDriverInstances.push(new instantiatedDriverClass(driver.driver.driverModel, driver.ipaddress, driver.port));
});
};
// Executed by server after connection to DB is established
const importDrivers = () => {
loadDeviceDriversToRuntime();
}
module.exports = importDrivers, driversArray;
The two classes (so far) that I am trying to execute methods on are Roku and Sony. Roku extends MediaPlayer and Sony extends Display. MediaPlayer and Display extends Commands. Code for Roku class:
const MediaPlayer = require('./MediaPlayer');
class Roku extends MediaPlayer {
constructor(ipaddress, port, powerOnDelay, powerOffDelay) {
super();
let url = `https://${ipaddress}:${port}`
this.powerOn = `${url}/powerOn`;
this.powerOff = `${url}/powerOff`;
this.up = `${url}/up;`
this.down = `${url}/down`;
this.left = `${url}/left`;
this.right = `${url}/right`;
this.enter = `${url}/enter`;
this.select = `${url}/select`;
this.back = `${url}/back`;
this.backspace = `${url}/backspace`;
this.exit = `${url}/exit`;
this.guide = `${url}/guide`;
this.menu = `${url}/menu`;
}
powerOn() {
super.powerOn(this.powerOn);
}
powerOff() {
super.powerOff(this.powerOff);
}
}
module.exports = Roku;
Code for MediaPlayer class:
const Commands = require('./Commands');
class MediaPlayer extends Commands {
constructor(powerOn, powerOff, up, down, left, right, enter, select, back, backspace, exit, guide, menu) {
super();
this.powerOn = powerOn;
this.powerOff = powerOff;
this.up = up;
this.down = down;
this.left = left;
this.right = right;
this.enter = enter;
this.select = select;
this.back = back;
this.backspace = backspace;
this.exit = exit;
this.guide = guide;
this.menu = menu;
}
powerOn() {
super.powerOn(this.powerOn);
}
powerOff() {
super.powerOff(this.powerOff);
}
}
module.exports = MediaPlayer;
Code for Commands class:
class Commands {
constructor(command) {
this.command = command;
}
powerOn(command) {
console.log("Something")
}
powerOff(command) {
console.log("Something")
}
up(command) {
console.log("Something")
}
down(command) {
console.log("Something")
}
left(command) {
console.log("Something")
}
right(command) {
console.log("Something")
}
enter(command) {
console.log("Something")
}
play(command) {
console.log("Something")
}
pause(command) {
console.log("Something")
}
select(command) {
console.log("Something")
}
guide(command) {
console.log("Something")
}
menu(command) {
console.log("Something")
}
back(command) {
console.log("Something")
}
delete(command) {
console.log("Something")
}
speed1(command) {
console.log("Something")
}
speed2(command) {
console.log("Something")
}
speed3(command) {
console.log("Something")
}
HDMI1(command) {
console.log("Something")
}
HDMI2(command) {
console.log("Something")
}
HDMI3(command) {
console.log("Something")
}
}
module.exports = Commands;
As far as I understand, the methods powerOn() and powerOff() should be accessible when an instance of Roku or Sony is created. If, however, I try to do something like liveDriverInstances[0].powerOn() I get an error liveDriverInstances[0].powerOn is not a function. When I run console.log(liveDriverInstances[0]) I get this response:
Roku {
command: undefined,
powerOn: 'https://192.168.1.205:8060/powerOn',
powerOff: 'https://192.168.1.205:8060/powerOff',
up: 'https://192.168.1.205:8060/up;',
down: 'https://192.168.1.205:8060/down',
left: 'https://192.168.1.205:8060/left',
right: 'https://192.168.1.205:8060/right',
enter: 'https://192.168.1.205:8060/enter',
select: 'https://192.168.1.205:8060/select',
back: 'https://192.168.1.205:8060/back',
backspace: 'https://192.168.1.205:8060/backspace',
exit: 'https://192.168.1.205:8060/exit',
guide: 'https://192.168.1.205:8060/guide',
menu: 'https://192.168.1.205:8060/menu'
}
So the data is being passed down from the Roku instance inheriting from MediaPlayer inheriting from Commands, but I don't have the methods. Looks like it is just getting the constructor method, but nothing more. Have I defined something incorrectly here?
On the constructor of Roku class. you are initializing the powerOn as a String . This will replace the function powerOn() declared.
constructor(ipaddress, port, powerOnDelay, powerOffDelay) {
...
this.powerOn = `${url}/powerOn`;
...
}
You can make this work by renaming the url fields
constructor(ipaddress, port, powerOnDelay, powerOffDelay) {
...
this.powerOnUrl = `${url}/powerOn`;
...
}
powerOn() {
super.powerOn(this.powerOnUrl);
}

Typescript map function return new object not working

I'm new to typescript
export class Reward {
id: number;
point: number;
status: Status;
constructor(id: number, point: number, status: Status) {
this.id = id;
this.point = point;
this.status = Status.NONE;
}
}
export enum Status {
CLAIMED,
AVAILABLE,
NONE
}
public getRewardsOf(numberOfDay: number): Array<Reward> {
return this.availableRewards.map((reward: Reward, index: number) => {
if (index == (numberOfDay - 1)) {
return new Reward(reward.id, reward.point, Status.AVAILABLE);
} else {
return reward;
}
});
}
the if doesn't work for me. It still returns the same old object (the status value is the same). it's supposed to be different even though I create a new object.
It works when I use
if (index == (numberOfDay - 1)) {
return {
'id': reward.id,
'point': reward.point,
'status': Status.AVAILABLE
};
} else {
return reward;
}
if so I lose the power of typescript
Your constructor is hardcoding the value Status.NONE:
...
this.status = Status.NONE
...
This will effectively discard the status argument passed to the constructor.
If you wish to provide Status.NONE as a default parameter, do:
constructor(id: number, point: number, status = Status.NONE) {
this.id = id;
this.point = point;
this.status = status;
}
You can further reduce this to:
constructor(public id: number, public point: number,
public status = Status.NONE) {
}
Edit: Also, because TypeScript is structurally, not nominally typed (see structural typing), you don't lose any type safety by returning a literal versus a class-constructed instance. Both versions of your code can safely be said to be returning a Reward.
TS Playground
class Reward {
constructor(public id: string) {}
}
// This is OK.
const x: Reward = {id: '12435'};
If Reward starts adding methods, then what you lose is the ability to create a Reward easily with your object literal.

How can I evaluate Python code in the document context from JavaScript in JupyterLab?

With Jupyter Notebooks, I could have a cell
%%javascript IPython.notebook.kernel.execute('x = 42')
Then, elsewhere in the document a Python code cell with x would show it bound to 42 as expected.
I'm trying to produce something similar with JupyterLab. I understand I'm supposed to write a plugin rather than using ad-hoc JS, and that's fine, but I'm not finding an interface to the kernel similar to the global IPython from notebooks:
import { JupyerLab, JupyterLabPlugin } from '#jupyterlab/application';
const extension: JupyterLabPlugin<void> = {
// ...
requires: [],
activate: (app: JupyterLab) => {
// can I get to Python evaluation through app?
// by adding another class to `requires` above?
}
}
export default extension;
Here's a hacky attempt that "works". Could still use advice if anyone knows where there is a public promise for the kernel being ready, how to avoid the intermediate class, or any other general improvements:
import { JupyterLab, JupyterLabPlugin } from '#jupyterlab/application';
import { DocumentRegistry } from '#jupyterlab/docregistry';
import { INotebookModel, NotebookPanel } from '#jupyterlab/notebook';
import { IDisposable, DisposableDelegate } from '#phosphor/disposable';
declare global {
interface Window {
'execPython': {
'readyState': string,
'exec': (code: string) => any,
'ready': Promise<void>
} | null
}
}
class ExecWidgetExtension implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel> {
createNew(nb: NotebookPanel, context: DocumentRegistry.IContext<INotebookModel>): IDisposable {
if (window.execPython) {
return;
}
window.execPython = {
'readyState': 'waiting',
'exec': null,
'ready': new Promise((resolve) => {
const wait = setInterval(() => {
if (!context.session.kernel || window.execPython.readyState === 'ready') {
return;
}
clearInterval(wait);
window.execPython.readyState = 'ready';
window.execPython.exec = (code: string) =>
context.session.kernel.requestExecute({ code }, true);
resolve();
}, 50);
})
};
// Usage elsewhere: execPython.ready.then(() => execPython.exec('x = 42').done.then(console.log, console.error))
return new DisposableDelegate(() => {});
}
}
const extension: JupyterLabPlugin<void> = {
'id': 'jupyterlab_foo',
'autoStart': true,
'activate': (app: JupyterLab) => {
app.docRegistry.addWidgetExtension('Notebook', new ExecWidgetExtension())
}
};
export default extension;

Calling a paginated API with promises

I want to call a paginated API with promises. The information how many pages are available is not known at the beginning but is delivered on every response. In detail I am calling a Jira search, the paging information part looks like:
{
"startAt": 0,
"maxResults": 15,
"total": 100000,
...
}
I solved the handling of the pagination with recursion, this is my solution in Typescript:
search(jql: string, fields: string[] = [], maxResults = 15) : Promise<JiraIssue[]> {
// container for the issues
let issues: Array<JiraIssue> = new Array<JiraIssue>();
// define recursive function to collect the issues per page and
// perform a recursive call to fetch the next page
let recursiveFn = (jql: string, fields: string[], startAt: number, maxResults: number) :
Promise<JiraIssue[]> => {
return this
// retrieves one page
.searchPage(jql, fields, startAt, maxResults)
.then((searchResult: JiraSearchResult) => {
// saves the result of the retrieved page
issues.push.apply(issues, searchResult.issues);
if (searchResult.startAt + searchResult.maxResults < searchResult.total) {
// retrieves the next page with a recursive call
return recursiveFn(jql, fields,
searchResult.startAt + searchResult.maxResults,
maxResults);
}
else {
// returns the collected issues
return issues;
}
})
};
// and execute it
return recursiveFn(jql, fields, 0, maxResults);
}
However, I don't like the recursive approach, because this works only well with small result sets (I am afraid of a stack overflow). How would you solve this problem with a not recursive approach?
This is not actual recursion, and there is no stack overflow danger, because the function is being called inside a then handler.
One options is to wrap this in an iterator pattern.
Something like:
interface Searcher {
(jql: string, fields: string[], startAt: number, maxResults: number) => Promise<JiraSearchResult>;
}
class ResultsIterator {
private jql: string;
private fields: string[];
private searcher: Searcher;
private startAt: number;
private maxResults: number;
private currentPromise: Promise<JiraIssue[]>;
private total: number;
constructor(searcher: Searcher, jql: string, fields?: string[], maxResults?: number) {
this.jql = jql;
this.startAt = 0;
this.searcher = searcher;
this.fields = fields || [];
this.maxResults = maxResults || 15;
this.total = -1;
}
hasNext(): boolean {
return this.total < 0 || this.startAt < this.total;
}
next(): Promise<JiraIssue[]> {
if (!this.hasNext()) {
throw new Error("iterator depleted");
}
return this.searcher(this.jql, this.fields, this.startAt, this.maxResults)
.then((searchResult: JiraSearchResult) => {
this.total = searchResult.total;
this.startAt = searchResult.startAt + searchResult.maxResults;
return searchResult.issues;
});
}
}
This code isn't perfect, as I'm not entirely sure what you're doing there (for example what's this.searchPage?), but you should probably get the idea I'm aiming at.
You'll just do:
if (resultIterator.hasNext()) {
resultIterator.next().then(...);
}
Hope this helps.

Categories

Resources