TensorflowJS model fails to learn - javascript

I built a TensorflowJS model that learns how to play a snake game that I made. I've followed everything I found online, but I can get my model to improve even after hundreds of games.
Can anyone show me what I'm doing wrong? Here's my code:
async function main(currentTime) {
window.requestAnimationFrame(main)
aIModelChoice(snakeBody, applePosition)
updateGameState()
draw(gameBoard)
checkIfLost()
if (gameOver) {
await updateAIModel(snakeBody, applePosition, -1, gameOver);
console.log("UPDATING MODEL WITH REWARD -1")
} else if (appleEaten){
await updateAIModel(snakeBody, applePosition, +1, gameOver);
console.log("UPDATING MODEL WITH REWARD +1")
} else {
await updateAIModel(snakeBody, applePosition, -0.1, gameOver);
console.log("UPDATING MODEL WITH REWARD 0")
}
appleEaten = false
if (gameOver) {
reset()
}
}
export function initiateAIModel() {
window.model = tf.sequential();
window.model.add(tf.layers.dense({ units: 64, inputShape: [100], activation: "relu" }));
window.model.add(tf.layers.dense({ units: 32, activation: "relu" }));
window.model.add(tf.layers.dense({ units: 4, activation: "softmax" }));
window.model.compile({
optimizer: tf.train.adam(),
loss: "categoricalCrossentropy",
metrics: ["accuracy"]
});
window.gamma = 0.9;
window.epsilon = 1.0;
let done = false;
console.log("Model initiated");
}
function getState(snakeBody, applePosition) {
const state = [];
let xValues = [];
let yValues = [];
for (let i = 0; i < snakeBody.length; i++) {
xValues.push(snakeBody[i].x);
yValues.push(snakeBody[i].y);
}
for (let i = 0; i < window.gridsize; i++) {
for (let j = 0; j < window.gridsize; j++) {
if (xValues.includes(j) && yValues.includes(i)) {
state.push(1);
} else if (applePosition.x === j && applePosition.y === i) {
state.push(2);
} else {
state.push(0);
}
}
}
return state;
}
export function aIModelChoice(snakeBody, applePosition) {
window.state = getState(snakeBody, applePosition);
let selectedAction
if (Math.random() < window.epsilon) {
// Choose a random action
selectedAction = ["UP", "DOWN", "LEFT", "RIGHT"][Math.floor(Math.random() * 4)];
} else {
// Choose the action with the highest predicted reward
const actionProbs = window.model.predict(tf.tensor2d([window.state])).dataSync();
selectedAction = ["UP", "DOWN", "LEFT", "RIGHT"][tf.argMax(actionProbs).dataSync()[0]];
}
if (selectedAction == "UP" && window.inputDirection.x !== 0) {
window.inputDirection = { x: 0, y: 1 }
} else if (selectedAction == "DOWN" && window.inputDirection.x !== 0) {
window.inputDirection = { x: 0, y: -1 }
} else if (selectedAction == "LEFT" && window.inputDirection.y !== 0) {
window.inputDirection = { x: -1, y: 0 }
} else if (selectedAction == "RIGHT" && window.inputDirection.y !== 0) {
window.inputDirection = { x: 1, y: 0 }
}
}
export async function updateAIModel(snakeBody, applePosition, reward, done) {
const nextState = getState(snakeBody, applePosition);
let target = reward;
if (!done) {
// Predict the future discounted reward
const futureReward = tf.max(window.model.predict(tf.tensor2d([window.state]))).dataSync()[0];
target = reward + window.gamma * futureReward;
}
// Update the model
const targetVec = window.model.predict(tf.tensor2d([window.state])).dataSync();
targetVec[["UP", "DOWN", "LEFT", "RIGHT"].indexOf(window.inputDirection)] = target;
await window.model.fit(tf.tensor2d([window.state]), tf.tensor2d([targetVec]));
if (!done) {
window.state = nextState;
}
window.epsilon *= 0.995;
}

You aren't updating which grid space it can go to, you're updating which direction it turns. This makes it spin in circles instead of checking different places for the apple.

Related

phaser 3 reset the whole scene

Following the phaser documentation, the scene restart is not working.
What I'm doing is the following, there are three scenes (start, play and end.). When it gets to the end, when clicking on the screen it gives this.scene.start('play'). I tried several ways, researched a lot and tried several things but the ones that worked in my case, the game for some events and some settings are not reset.
For example, in this game you will create enemies and some packs. When it is restarted or started again, these builds are no longer executed.
Start
import Phaser from "phaser";
class Start extends Phaser.Scene{
constructor ()
{
super({ key: 'start', active: true });
}
init(){
console.log("- ./node_modules/babel-loader/lib??ref--2-0!./node_modules/vue-loader/lib??vue-loader-options!./pages/game/index.vue?vue&type=script&lang=js&")
console.log("initiated 0418971")
console.log("Unexpected console statement no-console")
console.log("bundle 'client' has 1 warnings")
console.log("Caching create all")
this.cameras.main.setBackgroundColor("#24252A");
}
preload(){
}
create() {
this.helloWorld = this.add.text(
this.cameras.main.centerX,
this.cameras.main.centerY,
"Clique para jogar",
{ font: "40px Arial", fill: "#ffffff" }
);
this.helloWorld.setOrigin(0.5);
this.input.on('pointerup', function (pointer) {
this.scene.start('play');
}, this);
}
update() {
}
}
export default Start;
Play
import Phaser from "phaser";
// import Pack from '~/assets/game/Components/Pack.js'
class Play extends Phaser.Scene {
constructor() {
super({
key: 'play',
active: false
});
}
index = 0;
packs = Phaser.GameObjects.Group;
enemys = Phaser.GameObjects.Group;
spriteLife = [];
config = {
speed: 0,
speedVelocity: 0.3,
maxSpeed: 15,
pack: {
created: 0,
limitcreate: 1,
value: 4.5,
},
enemy: {
speed: 5,
created: 0,
limitcreate: 1,
x: 0,
},
engine: {
temperature: 0,
}
};
init() {
}
preload() {
// Images
this.load.image('grama', require('~/assets/game/assets/grama.jpg'));
this.load.image('road', require('~/assets/game/assets/road-2.png'))
this.load.image('pack', require('~/assets/game/assets/pack.png'));
this.load.image('life', require('~/assets/game/assets/life.png'));
// Sprite sheet
this.load.spritesheet('player', require('~/assets/game/assets/car-sprite.png'), {
frameWidth: 404,
frameHeight: 1009
})
}
createGram() {
let a = 0;
let b = 0;
const totalA = 8;
const totalB = 4;
for (a = 0; a <= totalA; a++) {
for (b = 0; b <= totalB; b++) {
const grama = this.add.sprite(0, 0, 'grama').setScale(0.2);
grama.setPosition(grama.displayWidth * b, grama.displayHeight * a);
}
}
}
createScore() {
this.score = 0;
this.scoreText = this.add.text(15, 45,
`SCORE: 0`, {
font: "20px Arial",
fill: "#ffffff"
}
);
}
createLife() {
this.numberLifes = 3;
this.add.text(15, 15,
`LIFE:`, {
font: "20px Arial",
fill: "#ffffff"
}
);
for (let i = 0; i < this.numberLifes; i++) {
this.spriteLife.push(this.add.image(80 + 30 * i, 25, 'life').setScale(0.09))
}
}
createPlayer() {
this.player = this.physics.add.sprite(0, 0, 'player');
this.player.setOrigin(0.5)
this.player.setScale(0.2);
this.player.setPosition(this.cameras.main.centerX, this.cameras.main.displayHeight - 120);
this.player.setCollideWorldBounds(true);
}
createPack() {
if(this.config.pack.created < this.config.pack.limitcreate){
this.packs.create(Phaser.Math.Between(60, 330), 0, 'pack').setScale(.15).setOrigin(.5);
this.config.pack.created += 1;
}
}
createEnemy() {
if(this.config.enemy.created < this.config.enemy.limitcreate){
this.enemy = this.enemys.create(Phaser.Math.Between(70, 320), -200, 'player').setScale(.2).setOrigin(.5);
this.enemy.anims.play('default', true);
this.config.enemy.created += 1;
this.config.enemy.x = this.enemys.children.entries[0].x;
}
}
createEngineTemperatureBar() {
this.engineTemperatureBar = this.add.graphics();
this.engineTemperatureBar.fillStyle(0xffffff, 1);
this.engineTemperatureBar.fillRect(0, 0, 130, 30);
// Position
this.engineTemperatureBar.x = 350;
this.engineTemperatureBar.y = 830;
this.engineTemperatureBar.angle = -90;
this.engineTemperatureBar.scaleX = 0;
}
myAnims() {
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('player', {
start: 4,
end: 4
}),
frameRate: 3,
repeat: 0
})
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('player', {
start: 0,
end: 1
}),
frameRate: 3,
repeat: 0
})
this.anims.create({
key: 'default',
frames: [{
key: 'player',
frame: 2
}],
frameRate: 20
});
}
addPhysics(){
// Add
this.packs = this.physics.add.group();
this.enemys = this.physics.add.group();
// Collider
this.physics.add.collider(this.player, this.packs, this.collectPack, null, this);
this.physics.add.collider(this.player, this.enemys, this.collideEnemy, null, this, this.enemys);
}
create() {
// Camera
this.cameras.main.setBackgroundColor("#24252A");
// Gram
this.createGram();
// Roads
this.roads = this.add.tileSprite((this.game.canvas.clientWidth / 2), 0, 500, 1210, 'road').setOrigin(0.5, 0).setScale(0.7);
// Functions
this.createScore();
this.createLife();
this.createPlayer();
this.myAnims();
this.addPhysics();
this.createEngineTemperatureBar();
// Body
this.packs.enableBody = true;
this.enemys.enableBody = true;
// Cursors
this.cursors = this.input.keyboard.createCursorKeys();
}
update(time) {
this.roads.tilePositionY -= this.config.speed;
this.movePlayerManager();
this.createPack();
this.createEnemy();
this.movePacks();
this.moveEnemy()
this.updateEngineTemperatureBar()
}
movePlayerManager() {
// Left
if (this.cursors.left.isDown) {
this.player.x -= 5;
this.player.anims.play('left', false);
}
// Right
else if (this.cursors.right.isDown) {
this.player.x += 5;
this.player.anims.play('right', false);
}
else {
this.player.anims.play('default', true);
}
// Up
if (this.cursors.up.isDown) {
// Increase speed
if (this.config.speed <= this.config.maxSpeed){
this.config.speed += this.config.speedVelocity;
}
}
if (this.cursors.up.isUp) {
if (this.config.speed > 0){
this.config.speed -= 0.5;
}
else
this.config.speed = 0
}
}
movePacks() {
this.packs.children.each((e) => {
e.y += this.config.speed;
if(e.y > this.game.config.height){
e.destroy();
this.config.pack.created -= 1;
}
})
}
moveEnemy() {
this.enemys.children.each((e) => {
if(this.cursors.up.isDown){
if(this.config.speed < this.config.maxSpeed-1){
e.y -= this.config.maxSpeed;
return
};
e.y += this.config.enemy.speed;
// Move left or right
if(this.config.enemy.x < 150 && e.x < 290){
e.x += 1;
this.enemy.anims.play('right', true);
}
else if(e.x > 90){
e.x -= 1;
this.enemy.anims.play('left', true);
}else{
this.enemy.anims.play('default', true);
}
}else{
if(e.y < -1000) return;
e.y -= this.config.maxSpeed;
}
if(e.y - 200 > this.game.config.height){
e.destroy();
this.config.enemy.created -= 1;
}
})
}
updateEngineTemperatureBar(){
if(this.cursors.up.isDown && this.config.engine.temperature <= 100){
this.config.engine.temperature += 0.1;
}
else if(this.cursors.up.isUp && this.config.engine.temperature < 100){
this.config.engine.temperature += 0.2;
}else{
this.scene.start('end', { motiveText: "O motor do seu carro explodiu." });
}
// change the size of the bar
this.engineTemperatureBar.scaleX = this.config.engine.temperature/100;
}
updateLifes(){
if(this.numberLifes <= 1){
this.scene.start('end', { motiveText: "Suas vidas acabaram." });
}else{
this.spriteLife[this.numberLifes-1].destroy();
this.numberLifes = this.numberLifes -1;
}
}
collectPack(player, pack){
// Destroy pack
pack.disableBody(true, true);
pack.destroy();
// Config
this.config.pack.created -= 1;
// Score
this.score++;
this.scoreText.setText("SCORE: " + this.score);
// Engien temperature bar
if(this.config.engine.temperature > 1)
this.config.engine.temperature -= this.config.pack.value;
}
collideEnemy(player, enemy){
// Life
this.updateLifes();
// Destroy
enemy.destroy();
// Config
this.config.speed = 1;
this.config.enemy.created -= 1;
}
}
export default Play;
End
import Phaser from "phaser";
class End extends Phaser.Scene{
constructor ()
{
super({ key: 'end', active: false });
}
init(data){
this.motiveText = data.motiveText;
}
preload(){
}
create() {
this.helloWorld = this.add.text(
this.cameras.main.centerX,
this.cameras.main.centerY,
"Perdeu!",
{ font: "40px Arial", fill: "#ffffff" }
);
this.helloWorld.setOrigin(0.5);
this.helloWorld = this.add.text(
this.cameras.main.centerX,
this.cameras.main.centerY + 40,
this.motiveText,
{ font: "20px Arial", fill: "#ffffff" }
);
this.helloWorld.setOrigin(0.5);
this.helloWorld = this.add.text(
this.cameras.main.centerX,
this.cameras.main.centerY + 70,
"Clique para jogar novamente.",
{ font: "20px Arial", fill: "#ffffff" }
);
this.helloWorld.setOrigin(0.5);
this.input.on('pointerup', function (pointer) {
this.scene.start("start");
}, this);
}
update() {
}
}
export default End;
Again, the problem is that he doesn't completely rest. Even using the 'restart' methods, destroying the events and restarting them.
The issue is, that your are setting properties on the Scenes classes, like the property config on the Play class. Since these properties are not set in the create or the init function, they will never be reset, on start/
restart.
When the Scenes are restarted, scenes are not recreated, so properties are not reset and the constructor is also not called.
If you need to define / set properties on the Scenes, the best solution would be to always (re)set them in the init or create function, since these functions are called on start or restart.
I would recomend, simply moving the properties from the Play class into the init method like this:
class Play extends Phaser.Scene {
...
init() {
this.index = 0;
this.packs = Phaser.GameObjects.Group;
this.enemys = Phaser.GameObjects.Group;
this.spriteLife = [];
this.config = {
speed: 0,
speedVelocity: 0.3,
maxSpeed: 15,
pack: {
...
},
enemy: {
...
},
engine: {
temperature: 0,
}
};
}
...
}

How can set pointerDown for each of rectangle on Phaser 3

I’m trying to set up the Conway's Game of Life using Phaser.
My question is this: how can I make a Rectangular of the Phaser.geom class contain a click event?
Class Dots:
import 'phaser';
const COLOUR_ALIVE = 0xffffff;
const COLOUR_DEAD = 0x00000;
export class Dots extends Phaser.Geom.Rectangle {
public alive: number;
public fillColor: number;
public id: string;
constructor(scene, x, y, width, height, alive, id?) {
super(x, y, width, height);
this.alive = alive;
if(this.alive == 1){
this.fillColor = COLOUR_ALIVE;
} else {
this.fillColor = COLOUR_DEAD;
}
this.id = id;
console.log(id);
}
public isAlive():boolean{
return (this.alive == 1);
}
public returnAliveValue():number{
return this.alive;
}
public getFillColor(): number{
return this.fillColor;
}
public dead(){
this.alive = 0;
this.fillColor = COLOUR_DEAD;
}
public setAlive(){
this.alive = 1;
this.fillColor = COLOUR_ALIVE;
}
public click(pointer, gameobject){
console.log(pointer, gameobject);
}
}
Class Game:
import 'phaser';
import {Dots} from './classes/Dots'
const square_size = 10;
const pixel_height = 600;
const pixel_width = 800;
const DOTS = 100;
export default class Demo extends Phaser.Scene
{
private alives: Dots[] = [];
private graphics:Phaser.GameObjects.Graphics = null;
public timeElapsed: number;
public maxTime: number;
public cont = 0;
constructor ()
{
super('demo');
this.timeElapsed = 0;
this.maxTime = 1;
}
preload ()
{
}
draw () {
this.graphics = this.add.graphics();
//Afegim les fitxes vives
let pointer = this.input.activePointer;
this.alives.forEach((rectangle:Dots) => {
this.graphics.fillStyle(rectangle.getFillColor(), 1);
this.graphics.fillRectShape(rectangle);
this.graphics.setInteractive({
hitArea: new Phaser.Geom.Rectangle(0, 22, 27, 29),
hitAreaCallback: Phaser.Geom.Rectangle.Contains,
useHandCursor: true
}, (evt, geom) => {
if(pointer.isDown){
console.log(evt, geom);
}
});
});
}
destroy(obj:Phaser.GameObjects.Graphics) {
obj.destroy();
}
intersects(object1:Dots, object2:Dots){
let x = object1.x;
let y = object1.y;
let intersects = false;
if(object2.x == x - square_size && y == object2.y){
//Bloque izquierda
intersects = true;
} else if( object2.x == x + square_size && y == object2.y){
//Bloque derecha
intersects = true;
}
if(object2.y == y - square_size && x == object2.x){
//Bloque superior
intersects = true;
} else if(object2.y == y + square_size && x == object2.x){
//Bloque inferior
intersects = true;
}
if(object2.x == x - square_size && object2.y == y - square_size){
// Bloque izquierda superior
intersects = true;
} else if (object2.x == x - square_size && object2.y == y + square_size){
// Bloque izquierda inferior
intersects = true;
}
if(object2.x == x + square_size && object2.y == y - square_size){
// Bloque derecha superior
intersects = true;
} else if (object2.x == x + square_size && object2.y == y + square_size){
// Bloque derecha inferior
intersects = true;
}
return intersects;
}
searchArrIntersect(){
this.alives.forEach((x) => {
if(x.alive == 1){
let intersections = 0;
this.alives.forEach((y) => {
if(x != y){
let intersects = this.intersects(x, y);
if(intersects){
intersections ++;
}
}
});
if(intersections == 2 || intersections == 3){
x.isAlive();
}
if(intersections >= 3){
x.dead();
}
} else{
//fichas muertas
let intersections = 0;
this.alives.forEach((y) => {
if(x != y){
let intersects = this.intersects(x, y);
if(intersects){
intersections ++;
}
}
});
if(intersections == 3){
x.isAlive();
}
}
});
}
castObjectIntersects(object_search:Dots):Dots{
let dot_intersect = null;
this.alives.forEach((x, index) => {
if(x.x == object_search.x && x.y == object_search.y){
dot_intersect = x;
}
});
return dot_intersect;
}
create ()
{
let positions = [
//cross
// {x: pixel_width/2 - square_size, y: pixel_height/2, alive:1, id:'left'},
// {x: pixel_width/2 + square_size, y: pixel_height/2, alive:1, id:'right'},
// {x: pixel_width/2, y: pixel_height/2, alive:1, id:'center'},
// {x: pixel_width/2, y: pixel_height/2 - square_size, alive:1, id:'up'},
// {x: pixel_width/2, y: pixel_height/2 + square_size, alive:1, id:'down'},
// //borders
// //left
// {x: pixel_width/2 - square_size, y: pixel_height/2 - square_size, alive: 1, id:'left_up'},
// {x: pixel_width/2 - square_size, y: pixel_height/2 + square_size, alive: 1, id:'left_down'},
// //right
// {x: pixel_width/2 + square_size, y: pixel_height/2 - square_size, alive:0, id:'right_up'},
// {x: pixel_width/2 + square_size, y: pixel_height/2 + square_size, alive:0, id:'right_down'},
];
for(let i = 0; i < pixel_width; i+=10){
for(let j = 0; j < pixel_height; j+=10){
positions.push({x: i, y: j, alive:0, id:`${i}-${j}`});
}
}
positions.forEach((obj) => {
this.alives.push(new Dots(this, obj.x, obj.y, square_size, square_size, obj.alive, obj.id));
});
for(let i = 0; i <= DOTS; i++){
let random_length = Math.floor(Math.random() * (this.alives.length - 1 + 1) + 1);
let dot = this.alives[random_length];
dot.setAlive();
}
this.draw();
}
update(time: number, delta: number): void {
let deltaInSecond = delta/1000; // convert it to second
this.timeElapsed = this.timeElapsed + deltaInSecond;
if(this.timeElapsed >= this.maxTime) // if the time elapsed already more than 1 second
{
this.searchArrIntersect();
this.destroy(this.graphics);
this.draw();
// this.maxTime = 1200;
this.timeElapsed = 0;
}
}
}
const config = {
type: Phaser.AUTO,
backgroundColor: '#000000',
width: pixel_width,
height: pixel_height,
render: {
pixelArt: true
},
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH
},
scene: Demo
};
const game = new Phaser.Game(config);
You are setting interactive on the graphics serveral time, it the forEach-loop. I think this can be done only once, so you are overriding it, but I'm no expert.
I would set the interactivity once, for the whole region:
this.graphics.setInteractive({ useHandCursor: true,
hitArea: new Phaser.Geom.Rectangle(0, 0, pixel_width, pixel_height),
hitAreaCallback: Phaser.Geom.Rectangle.Contains,
})
And than in the "click event" select the Rectangle/Dot
this.graphics.on( 'pointerdown', function(){
// ...
});
To get the Rectangle/Dot clicked, there are many ways, here is one:
this.graphics.on( 'pointerdown', function(pointer){
let selected = this.alives.find( point => Phaser.Geom.Rectangle.Contains( new
Phaser.Geom.Rectangle(point.x, point.y, point.width, point.height), pointer.worldX, pointer.worldY
));
console.log('pointerover', pointer, selected);
}, this);
btw.:
I would add the graphics once in the create method:
create () {
// ...
this.graphics = this.add.graphics();
this.graphics.setInteractive({
// ...
});
this.graphics.on('pointerdown', () => {
// ...
});
this.draw();
}
and in the draw method just clear the graphics-object.
draw () {
this.graphics.clear();
// ...
}

How to send data from Node.js(webpack) to pure Node.js(express)?

I am getting data in a local storage of Webpack in JSON format. Node.js of Webpack is not allowing me to create a connection with MySQL. So, I am thinking if there is a way to send data to a backend(pure Node/Express server) like REST API.
I cannot use MySQL directly in Webpack node.js file
1.ref stackoveflow
2.ref GitHub
Below is my Webpack code I want save my localstorage data to database like MySQL, mongoDB.
import twitter from 'twitter-text';
import PDFJSAnnotate from '../';
import initColorPicker from './shared/initColorPicker';
/*import mysql from '../';
var con = mysql.createConnection({
host: "localhost",
user: "ali",
password: "demopassword"
});
con.connect(function(err) {
if (err) throw err;
console.log("Connected!");
});*/
//var hummus = require('HummusJS/hummus');
const { UI } = PDFJSAnnotate;
const documentId = 'question.pdf';
let PAGE_HEIGHT;
let RENDER_OPTIONS = {
documentId,
pdfDocument: null,
scale: parseFloat(localStorage.getItem(`${documentId}/scale`), 10) || 1.33,
rotate: parseInt(localStorage.getItem(`${documentId}/rotate`), 10) || 0
};
PDFJSAnnotate.setStoreAdapter(new PDFJSAnnotate.LocalStoreAdapter());
PDFJS.workerSrc = './shared/pdf.worker.js';
//var annotationz = localStorage.getItem(RENDER_OPTIONS.documentId + '/annotations') || [];
//console.log(annotationz);
// Render stuff
let NUM_PAGES = 0;
document.getElementById('content-wrapper').addEventListener('scroll', function (e) {
let visiblePageNum = Math.round(e.target.scrollTop / PAGE_HEIGHT) + 1;
let visiblePage = document.querySelector(`.page[data-page-number="${visiblePageNum}"][data-loaded="false"]`);
if (visiblePage) {
setTimeout(function () {
UI.renderPage(visiblePageNum, RENDER_OPTIONS);
});
}
});
function render() {
PDFJS.getDocument(RENDER_OPTIONS.documentId).then((pdf) => {
RENDER_OPTIONS.pdfDocument = pdf;
let viewer = document.getElementById('viewer');
viewer.innerHTML = '';
NUM_PAGES = pdf.pdfInfo.numPages;
for (let i=0; i<NUM_PAGES; i++) {
let page = UI.createPage(i+1);
viewer.appendChild(page);
}
UI.renderPage(1, RENDER_OPTIONS).then(([pdfPage, annotations]) => {
let viewport = pdfPage.getViewport(RENDER_OPTIONS.scale, RENDER_OPTIONS.rotate);
PAGE_HEIGHT = viewport.height;
});
});
}
render();
// Text stuff
(function () {
let textSize;
let textColor;
// let fontface;
function initText() {
let size = document.querySelector('.toolbar .text-size');
[8, 9, 10, 11, 12, 14, 18, 24, 30, 36, 48, 60, 72, 96].forEach((s) => {
size.appendChild(new Option (s, s));
});
/*let face = document.querySelector('.toolbar .font-face');
['fontGeorgiaSerif', 'fontPalatinoSerif', 'fontTimesSerif', 'Sans-Serif Fonts', 'fontArialSansSerif', 'fontComicSansMS', 'fontVerdanaSansMS', 'Monospace Fonts', 'fontCourierNew', 'fontLucidaConsole'].forEach((s) => {
face.appendChild(new Option (s, s));
});*/
setText(
localStorage.getItem(`${RENDER_OPTIONS.documentId}/text/size`) || 10,
localStorage.getItem(`${RENDER_OPTIONS.documentId}/text/color`) || '#000000'
//localStorage.getItem(`${RENDER_OPTIONS.documentId}/text/face`) || 'fontGeorgiaSerif'
// console.log(${RENDER_OPTIONS.documentId});
);
initColorPicker(document.querySelector('.text-color'), textColor, function (value) {
setText(textSize, value);
});
}
function setText(size, color) {
let modified = false;
if (textSize !== size) {
modified = true;
textSize = size;
localStorage.setItem(`${RENDER_OPTIONS.documentId}/text/size`, textSize);
document.querySelector('.toolbar .text-size').value = textSize;
}
/* if (fontface !== face) {
modified = true;
fontface = face;
localStorage.setItem(`${RENDER_OPTIONS.documentId}/text/face`, fontface);
document.querySelector('.toolbar .font-face').value = fontface;
}*/
if (textColor !== color) {
modified = true;
textColor = color;
localStorage.setItem(`${RENDER_OPTIONS.documentId}/text/color`, textColor);
let selected = document.querySelector('.toolbar .text-color.color-selected');
if (selected) {
selected.classList.remove('color-selected');
selected.removeAttribute('aria-selected');
}
selected = document.querySelector(`.toolbar .text-color[data-color="${color}"]`);
if (selected) {
selected.classList.add('color-selected');
selected.setAttribute('aria-selected', true);
}
}
if (modified) {
UI.setText(textSize, textColor);
}
}
function handleTextSizeChange(e) {
setText(e.target.value, textColor);
}
document.querySelector('.toolbar .text-size').addEventListener('change', handleTextSizeChange);
initText();
})();
// Pen stuff
(function () {
let penSize;
let penColor;
function initPen() {
let size = document.querySelector('.toolbar .pen-size');
for (let i=0; i<20; i++) {
size.appendChild(new Option(i+1, i+1));
}
setPen(
localStorage.getItem(`${RENDER_OPTIONS.documentId}/pen/size`) || 1,
localStorage.getItem(`${RENDER_OPTIONS.documentId}/pen/color`) || '#000000'
);
initColorPicker(document.querySelector('.pen-color'), penColor, function (value) {
setPen(penSize, value);
});
}
function setPen(size, color) {
let modified = false;
if (penSize !== size) {
modified = true;
penSize = size;
localStorage.setItem(`${RENDER_OPTIONS.documentId}/pen/size`, penSize);
document.querySelector('.toolbar .pen-size').value = penSize;
}
if (penColor !== color) {
modified = true;
penColor = color;
localStorage.setItem(`${RENDER_OPTIONS.documentId}/pen/color`, penColor);
let selected = document.querySelector('.toolbar .pen-color.color-selected');
if (selected) {
selected.classList.remove('color-selected');
selected.removeAttribute('aria-selected');
}
selected = document.querySelector(`.toolbar .pen-color[data-color="${color}"]`);
if (selected) {
selected.classList.add('color-selected');
selected.setAttribute('aria-selected', true);
}
}
if (modified) {
UI.setPen(penSize, penColor);
}
}
function handlePenSizeChange(e) {
setPen(e.target.value, penColor);
}
document.querySelector('.toolbar .pen-size').addEventListener('change', handlePenSizeChange);
initPen();
})();
// Toolbar buttons
(function () {
let tooltype = localStorage.getItem(`${RENDER_OPTIONS.documentId}/tooltype`) || 'cursor';
if (tooltype) {
setActiveToolbarItem(tooltype, document.querySelector(`.toolbar button[data-tooltype=${tooltype}]`));
}
function setActiveToolbarItem(type, button) {
let active = document.querySelector('.toolbar button.active');
if (active) {
active.classList.remove('active');
switch (tooltype) {
case 'cursor':
UI.disableEdit();
break;
case 'draw':
UI.disablePen();
break;
case 'text':
UI.disableText();
break;
case 'point':
UI.disablePoint();
break;
case 'area':
case 'highlight':
case 'strikeout':
UI.disableRect();
break;
}
}
if (button) {
button.classList.add('active');
}
if (tooltype !== type) {
localStorage.setItem(`${RENDER_OPTIONS.documentId}/tooltype`, type);
}
tooltype = type;
switch (type) {
case 'cursor':
UI.enableEdit();
break;
case 'draw':
UI.enablePen();
break;
case 'text':
UI.enableText();
break;
case 'point':
UI.enablePoint();
break;
case 'area':
case 'highlight':
case 'strikeout':
UI.enableRect(type);
break;
}
}
function handleToolbarClick(e) {
if (e.target.nodeName === 'BUTTON') {
setActiveToolbarItem(e.target.getAttribute('data-tooltype'), e.target);
}
}
document.querySelector('.toolbar').addEventListener('click', handleToolbarClick);
})();
// Scale/rotate
(function () {
function setScaleRotate(scale, rotate) {
scale = parseFloat(scale, 10);
rotate = parseInt(rotate, 10);
if (RENDER_OPTIONS.scale !== scale || RENDER_OPTIONS.rotate !== rotate) {
RENDER_OPTIONS.scale = scale;
RENDER_OPTIONS.rotate = rotate;
localStorage.setItem(`${RENDER_OPTIONS.documentId}/scale`, RENDER_OPTIONS.scale);
localStorage.setItem(`${RENDER_OPTIONS.documentId}/rotate`, RENDER_OPTIONS.rotate % 360);
render();
}
}
function handleScaleChange(e) {
setScaleRotate(e.target.value, RENDER_OPTIONS.rotate);
}
function handleRotateCWClick() {
setScaleRotate(RENDER_OPTIONS.scale, RENDER_OPTIONS.rotate + 90);
}
function handleRotateCCWClick() {
setScaleRotate(RENDER_OPTIONS.scale, RENDER_OPTIONS.rotate - 90);
}
document.querySelector('.toolbar select.scale').value = RENDER_OPTIONS.scale;
document.querySelector('.toolbar select.scale').addEventListener('change', handleScaleChange);
document.querySelector('.toolbar .rotate-ccw').addEventListener('click', handleRotateCCWClick);
document.querySelector('.toolbar .rotate-cw').addEventListener('click', handleRotateCWClick);
})();
// Clear toolbar button
(function () {
function handleClearClick(e) {
if (confirm('Are you sure you want to clear annotations?')) {
for (let i=0; i<NUM_PAGES; i++) {
document.querySelector(`div#pageContainer${i+1} svg.annotationLayer`).innerHTML = '';
}
localStorage.removeItem(`${RENDER_OPTIONS.documentId}/annotations`);
}
}
document.querySelector('a.clear').addEventListener('click', handleClearClick);
})();
// Comment stuff
(function (window, document) {
let commentList = document.querySelector('#comment-wrapper .comment-list-container');
let commentForm = document.querySelector('#comment-wrapper .comment-list-form');
let commentText = commentForm.querySelector('input[type="text"]');
function supportsComments(target) {
let type = target.getAttribute('data-pdf-annotate-type');
return ['point', 'highlight', 'area'].indexOf(type) > -1;
}
function insertComment(comment) {
let child = document.createElement('div');
child.className = 'comment-list-item';
child.innerHTML = twitter.autoLink(twitter.htmlEscape(comment.content));
commentList.appendChild(child);
}
function handleAnnotationClick(target) {
if (supportsComments(target)) {
let documentId = target.parentNode.getAttribute('data-pdf-annotate-document');
let annotationId = target.getAttribute('data-pdf-annotate-id');
PDFJSAnnotate.getStoreAdapter().getComments(documentId, annotationId).then((comments) => {
commentList.innerHTML = '';
commentForm.style.display = '';
commentText.focus();
commentForm.onsubmit = function () {
PDFJSAnnotate.getStoreAdapter().addComment(documentId, annotationId, commentText.value.trim())
.then(insertComment)
.then(() => {
commentText.value = '';
commentText.focus();
});
return false;
};
comments.forEach(insertComment);
});
}
}
function handleAnnotationBlur(target) {
if (supportsComments(target)) {
commentList.innerHTML = '';
commentForm.style.display = 'none';
commentForm.onsubmit = null;
insertComment({content: 'No comments'});
}
}
UI.addEventListener('annotation:click', handleAnnotationClick);
UI.addEventListener('annotation:blur', handleAnnotationBlur);
})(window, document);
Simple AJAX was working on the page because Webpack is a module bundler. Its main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging read more...
So in my case Node js was using Webpack module to load javascript in a browser. It is pure javascript. So I just implemented ajax on my javascript code and it is simply working like a REST API.

Javascript 2D array function

I have a 2D array, which contains either true or false.
I made a function that returns the number of neighbors (in all 8 directions) in that array, which are true.
But for some unknown reason, it does not work corectly (returns wrong number of neighbors).
(Yes, I'm making Conway's Game of Life.)
function neighbors(seq, x, y) {
var cnt = 0;
try {
if (seq[y-1][x]){
cnt++;
}
}
catch(err) {
}
try {
if (seq[y][x-1]){
cnt++;
}
}
catch(err) {
}
try {
if (seq[y][x+1]){
cnt++;
}
}
catch(err) {
}
try {
if (seq[y+1][x]){
cnt++;
}
}
catch(err) {
}
try {
if (seq[y-1][x+1]){
cnt++;
}
}
catch(err) {
}
try {
if (seq[y-1][x-1]){
cnt++;
}
}
catch(err) {
}
try {
if (seq[y+1][x-1]){
cnt++;
}
}
catch(err) {
}
try {
if (seq[y+1][x+1]){
cnt++;
}
}
catch(err) {
}
return cnt;
}
This code was basically translated from my Python code, that works.
You could take an object with all needed directions for counting.
Then check if the index is a key of the array and check the nested level. Add all and return that value.
function neighbors(seq, i, j) {
var directions = [{ x: 0, y: -1 }, { x: -1, y: -1 }, { x: -1, y: 0 }, { x: -1, y: 1 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 1, y: 0 }, { x: 1, y: -1 }];
return directions.reduce((r, { x, y }) => {
x += i;
y += j;
return r + (x in seq && seq[x][y] || 0);
}, 0);
}
You can simplify function like below.
var seq = [[false,false,false,false],
[false,true,false,false],
[false,true,false,false],
[false,true,false,false]]
function neighbors(seq, x, y) {
var cnt = 0;
var controls = [{x:0,y:1},{x:0,y:-1},
{x:1,y:0},{x:1,y:1},{x:1,y:-1},
{x:-1,y:0},{x:-1,y:1},{x:-1,y:-1}]
for (var index in controls) {
var newX = x+controls[index].x;
var newY = y+controls[index].y;
if(newX >= 0 && newY >= 0 && newX < seq.length && newY < seq[newX].length
&& seq[newX][newY]) {
cnt++;
}
}
return cnt;
}
console.log(neighbors(seq, 0,1))
console.log(neighbors(seq, 1,0))
console.log(neighbors(seq, 2,2))

Null Reference after restarting a State

I created a little game with Phaser for presentation purposes. After you've won or lost you can restart the game. This is done with states. When I try to fire a bullet after the game has been restarted, a null reference error occurs and the game freezes. It seems the null reference occurs because the this.game property is not set correctly in the Weapon classes after the state is restarted.
var PhaserGame = function () {
this.background = null;
this.stars = null;
this.player = null;
this.enemies = null;
this.cursors = null;
this.speed = 300;
this.weapons = [];
this.currentWeapon = 0;
this.weaponName = null;
this.score = 0;
};
PhaserGame.prototype = {
init: function () {
this.game.renderer.renderSession.roundPixels = true;
this.physics.startSystem(Phaser.Physics.ARCADE);
},
preload: function () {
this.game.time.advancedTiming = true;
},
create: function () {
this.background = this.add.tileSprite(0, 0, this.game.width, this.game.height, 'background');
this.background.autoScroll(-40, 0);
this.stars = this.add.tileSprite(0, 0, this.game.width, this.game.height, 'stars');
this.stars.autoScroll(-60, 0);
this.weapons.push(new Weapon.SingleBullet(this.game));
//this.weapons.push(new Weapon.FrontAndBack(this.game));
this.weapons.push(new Weapon.ThreeWay(this.game));
//this.weapons.push(new Weapon.EightWay(this.game));
this.weapons.push(new Weapon.ScatterShot(this.game));
this.weapons.push(new Weapon.Beam(this.game));
this.weapons.push(new Weapon.SplitShot(this.game));
//this.weapons.push(new Weapon.Pattern(this.game));
this.weapons.push(new Weapon.Rockets(this.game));
this.weapons.push(new Weapon.ScaleBullet(this.game));
//this.weapons.push(new Weapon.Combo1(this.game));
//this.weapons.push(new Weapon.Combo2(this.game));
this.currentWeapon = 0;
for (var i = 1; i < this.weapons.length; i++)
{
this.weapons[i].visible = false;
}
this.player = this.add.existing(new Spaceship(this.game, 100, 200, 'player'));
this.player.events.onKilled.add(this.toGameOver, this);
this.physics.arcade.enable(this.player);
this.player.body.collideWorldBounds = true;
this.player.animations.add('flame', [0, 1, 2, 3], 10, true);
this.player.animations.play('flame');
//Enemies
this.enemies = this.add.group();
//Enable Physics for Enemies
//this.physics.arcade.enable(this.enemies);
this.enemies.enableBody = true;
for (var i = 0; i < 24; i++) {
//create a star inside the group
var enemy = this.enemies.add(new Enemy(this.game, 1000 + (i * 50), 10 + Math.random() * 300, 'enemy'));
enemy.events.onKilled.add(this.raiseCounter, this);
}
//this.weaponName = this.add.bitmapText(8, 364, 'shmupfont', "ENTER = Next Weapon", 24);
// Cursor keys to fly + space to fire
this.cursors = this.input.keyboard.createCursorKeys();
this.input.keyboard.addKeyCapture([ Phaser.Keyboard.SPACEBAR ]);
var changeKey = this.input.keyboard.addKey(Phaser.Keyboard.ENTER);
changeKey.onDown.add(this.nextWeapon, this);
},
nextWeapon: function () {
// Tidy-up the current weapon
this.weapons[this.currentWeapon].visible = false;
this.weapons[this.currentWeapon].callAll('reset', null, 0, 0);
this.weapons[this.currentWeapon].setAll('exists', false);
// Activate the new one
this.currentWeapon++;
if (this.currentWeapon === this.weapons.length)
{
this.currentWeapon = 0;
}
this.weapons[this.currentWeapon].visible = true;
//this.weaponName.text = this.weapons[this.currentWeapon].name;
},
enemyHit: function (bullet, enemy) {
bullet.kill();
enemy.dealDamage(2);
},
playerHit: function (player, enemy) {
player.dealDamage(10);
enemy.dealDamage(1);
},
raiseCounter: function () {
this.score++;
},
toGameOver: function () {
this.game.state.start('GameOver', true, false, this.score);
},
update: function () {
//Framerate
this.game.debug.text(this.time.fps || '--', 2, 14, "#00ff00");
this.game.debug.text('Health: ' + this.player.health || 'Health: ---', 2, 30, "#00ff00");
this.game.debug.text('Counter: ' + this.score || 'Counter: ---', 2, 44, "#00ff00");
this.game.physics.arcade.overlap(this.weapons[this.currentWeapon], this.enemies, this.enemyHit, null, this);
this.game.physics.arcade.overlap(this.player, this.enemies, this.playerHit, null, this);
this.player.body.velocity.set(0);
this.enemies.setAll('body.velocity.x', -50);
if (this.cursors.left.isDown)
{
this.player.body.velocity.x = -this.speed;
}
else if (this.cursors.right.isDown)
{
this.player.body.velocity.x = this.speed;
}
if (this.cursors.up.isDown)
{
this.player.body.velocity.y = -this.speed;
}
else if (this.cursors.down.isDown)
{
this.player.body.velocity.y = this.speed;
}
if (this.input.keyboard.isDown(Phaser.Keyboard.SPACEBAR))
{
this.weapons[this.currentWeapon].fire(this.player);
}
}
};
The weapon-classes were taken from Phaser-Coding-Tips 7:
Weapon.SingleBullet = function (game) {
console.log(game);
Phaser.Group.call(this, game, game.world, 'Single Bullet', false, true, Phaser.Physics.ARCADE);
this.nextFire = 0;
this.bulletSpeed = 600;
this.fireRate = 200;
for (var i = 0; i < 64; i++)
{
this.add(new Bullet(game, 'bullet5'), true);
}
return this;
};
Weapon.SingleBullet.prototype = Object.create(Phaser.Group.prototype);
Weapon.SingleBullet.prototype.constructor = Weapon.SingleBullet;
Weapon.SingleBullet.prototype.fire = function (source) {
//Here occurs the problem, because this.game is null after restarting the state
if (this.game.time.time < this.nextFire) { return; }
var x = source.x + 50;
var y = source.y + 15;
this.getFirstExists(false).fire(x, y, 0, this.bulletSpeed, 0, 0);
this.nextFire = this.game.time.time + this.fireRate;
};
The problem occurs consistent in all Weapon classes after restarting the state.
In the beginning of the create-method before weapons is filled, i forgot to empty the array. The funny thing is: I tried emptying the array before and it didn't work. When i logged the length of weapons it suddenly started to work as expected. Maybe the cause was a strange optimization made by the javascript-engine.

Categories

Resources