Related
//Editable Vars
let cols = 35;
let rows = 35;
let fps = 5;
//Declarations
let canvas;
let ctx;
let background;
let grid = new Array(cols);
let w;
let h;
let pathfinder;
let target;
let timer;
let renderQueue = [];
let canPathfind = true;
//Space Class
class Space{
constructor(x,y,c='lightgrey'){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.c = c;
}
draw(){
ctx.fillStyle = this.c;
ctx.strokeStyle = 'black';
ctx.fillRect(this.x * w, this.y * h, this.w, this.h);
ctx.rect(this.x * w, this.y * h, this.w, this.h);
ctx.stroke();
}
move(x,y){
if(x < 0 || x > cols-1 || y < 0|| y > rows-1){
return;
}
grid[this.x][this.y] = new Space(this.x,this.y);
renderQueue.push(grid[this.x][this.y]);
this.x = x;
this.y = y;
grid[this.x][this.y] = this;
renderQueue.push(grid[this.x][this.y]);
}
}
//Game-Run code
function gameStart(){
canvas = document.getElementById('gameCanvas');
ctx = canvas.getContext('2d');
w = canvas.width / cols;
h = canvas.height / rows;
createGrid();
pathfinder = new Space(randomInt(cols-1),randomInt(rows-1),'green');
grid[pathfinder.x][pathfinder.y] = pathfinder;
target = new Space(randomInt(cols-1),randomInt(rows-1),'red');
grid[target.x][target.y] = target;
drawGrid();
timer = setInterval(updateScreen, 1000/fps);
}
function restartGame(){
clearInterval(timer);
gameStart();
}
//Starts loading process on windows load
window.onload = gameStart();
//Checks all 8 possible move directions and calls pathfinder.move for best move
function pathfind(pathfinder, target){
if(!canPathfind) return;
let p = {x: pathfinder.x, y: pathfinder.y};
let t = {x: target.x, y: target.y};
let move = {x : 0, y : 0};
// 0,0 9,9
//if(p.x == t.x && p.y == t.y){
//restartGame();
//}
if(t.x - p.x >= 1){
move.x = 1;
}else if(t.x - p.x <= -1){
move.x = -1;
}else{
move.x = 0;
}
if(t.y - p.y >= 1){
move.y = 1;
}else if(t.y - p.y <= -1){
move.y = -1;
}else{
move.y = 0;
}
pathfinder.move(pathfinder.x + move.x, pathfinder.y + move.y);
}
function updateScreen(){
pathfind(pathfinder,target);
drawUpdatedSpaces();
}
function drawUpdatedSpaces(){
for(let i = 0; i < renderQueue.length; i++){
renderQueue[i].draw();
}
renderQueue = [];
}
function drawGrid(){
for(let i = 0; i < grid.length; i++){
for(let j = 0; j < grid[i].length; j++){
grid[i][j].draw();
}
}
}
//Creates grid and instantiates Space in every cell
function createGrid(){
for(let i = 0; i < grid.length; i++){
grid[i] = new Array(rows);
}
for(let i = 0; i < grid.length; i++){
for(let j = 0; j < grid[i].length; j++){
grid[i][j] = new Space(i,j);
}
}
}
// Returns distance to target from specified coords
function distanceFromTarget(x, y) {
return (Math.sqrt(Math.pow(Math.abs(x - target.x), 2) + (Math.pow(Math.abs(y - target.y), 2))));
}
// Returns random Integer between 0 and Max
function randomInt(max) {
return Math.floor(Math.random() * max);
}
It runs as expected which is great, but performance is super slow. That may be because I'm using jsfiddle to work on this while away from my personal PC setup, but is there a way to make this perform better? As of right now I can't move the grid size to >50 without it running extremely slow. I would love to eventually move to a 4k x 4k grid and create an auto-generating maze for the 'pathfinder' to pathfind through.
Thoughts/things I'm considering for performance:
Using HTML grid and updating via CSS instead of HTML5 Canvas
Only re-drawing cells that have changed (Implemented in the Space.move() function with array renderQueue)
literally re-doing everything in python :D
Algorithm "NoObtuse" in javascript.
I have to implement this algo below in a canvas.
The user put a set of points and click on the button to call the function "noObtuse" and I have to draw the graph (see image).
How can I do it ?
No obtuse algoritm
EDIT:
I change the code with the informations of "MBo" but I don't get what I need, the nextPoint is not the right one (Neither in CW nor in CCW).
My code:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
this.col = 'red';
}
drawDot() {
fill(this.col);
ellipse(this.x, this.y, 8, 8);
}
tagNotExtreme() {
this.col = 'blue';
}
setColor(color) {
this.col = color;
}
}
var points;
var vertices = [];
var rightMost= null;
var tableau;
var angle1;
var angle2;
var a ;
var b;
var p1 ;
var indice;
var pointSize= 0;
var canvas;
var button_CH;
var button_clear;
var isEnter;
var times_clicked = 0;
function setup() {
canvas = createCanvas(500, 500);
background(255);
pointSize = 8;
points = [];
tableau = [];
rightMost = {x: 0, y:0};
a = {x: 0, y:0};
b = {x: 0, y:0};
p1 = {x: 0, y:0};
indice = 2;
isEnter = -1;
angle1 = 0;
angle2 = 0;
canvas.parent('candiv');
}
function draw() {
}
function contient(point, tableau){
var oui = -1;
for (var i = 0; i < tableau.length;i++){
if (point == tableau[i]){
oui = 1;
}
}
return oui;
}
function NoObtuse(){
p1 = rightMost;
angle1 = 0;
angle2 = 0;
a = points[0];
for (var k=0; k < points.length; k++){
angle = Math.atan2(points[k].y - rightMost.y, points[k].x - rightMost.x);
if (angle < angle1){
angle1 = angle;
a = points[k];
}
}
tableau[0] = p1;
tableau[1] = a;
var flag = -1;
var n = points.length-2;
var i;
for (i = 0; i <= 0; i++){
if (contient(points[i],tableau) == -1){
b = nextPoint(points[i], a, flag);
if (Math.degrees(find_angle(points[i],a,b)) <= 90 ){
points[i+1] = a;
a = b;
} else {
points[i+1] = b;
flag = -flag;
}
tableau[indice] = points[i];
indice = indice+1;
// ...
} else{
if(i == 0){
strokeWeight(2);
line(p1.x, p1.y, a.x, a.y);
}
}
}
points[points.length - 1] = a;
}
Math.degrees = function(radians) {
return radians * 180 / Math.PI;
};
function find_angle(A,B,C) {
var AB = Math.sqrt(Math.pow(B.x-A.x,2)+ Math.pow(B.y-A.y,2));
var BC = Math.sqrt(Math.pow(B.x-C.x,2)+ Math.pow(B.y-C.y,2));
var AC = Math.sqrt(Math.pow(C.x-A.x,2)+ Math.pow(C.y-A.y,2));
return Math.acos((BC*BC+AB*AB-AC*AC)/(2*BC*AB));
}
function nextPoint(pi, a, flag){
/* HELP : Let ρ be a ray attached to a and continuing the directed segment pia*/
fill('blue');
strokeWeight(2);
line(pi.x, pi.y, a.x, a.y);
b = a; /* HELP : Let b be the first point encountered by ρ when rotating it around a in the direction indicated by flag*/
return b;
}
function mousePressed() {
if(isEnter == -1){
newPointN = new Point(mouseX,mouseY);
if (rightMost && (rightMost.x < newPointN.x)) {
rightMost = newPointN;
}
points.push(newPointN);
newPointN.drawDot();
}else{
setup();
}
}
function keyPressed() {
if (keyCode == 82) {
setup();
} else if (keyCode == ENTER){
NoObtuse();
isEnter = 1;
} else if (keyCode == 77){
rightMost.setColor('green');
rightMost.drawDot();
}
}
To get the first point from the rightmost, choose one with the smallest value of
angle = Math.atan2(P[i].Y - rightmost.Y, P[i].X - rightmost.X)
To get the next point that goes after current A and B points in CCW order, you can compare angles A-B-C, calculated through atan2, cross-product and dot product of vectors.
angle = Math.atan2(cross(AB, BC), dot(AB, BC))
At my school I am learning how to code in JS using a site called codehs.com. After a while I learned about graphics with JS. There was this one point where I had to create a circle:
var circle = new Circle(50);
circle.setPosition(100,100);
add(circle);
After a few days I came across another website that was teaching students how code using JS. The website was called khanacademy.org I was interested and saw that the first lesson was making drawings. I looked at the video provided and it had a different code to make a circle.
ellipse(203, 197, 300, 350);
I am confused on how to make a circle using JS since I just started.
I'm one of the founders of CodeHS. CodeHS uses a custom JavaScript library on top of regular JavaScript. Khan Academy uses Processing JS, which is a different library (You can use Processing on CodeHS as well if you like).
You can see the documentation for everything in the CodeHS JS library at https://codehs.com/docs and learn how to use it in the Intro CS in JavaScript course.
We have designed this library to be great for learning -- it gives you experience using Object Oriented Programming while making it simple to create and manipulate shapes for programs like a Helicopter Game.
Additionally, you can include the library on an HTML page that runs JavaScript by adding this script tag to your page.
<script type="text/javascript" src="https://static.codehs.com/gulp/3d065bc81d3b7edf21e928ce2c764374a03c5cd6/chs-js-lib/chs.js"></script>
Here's an example of a full HTML page that runs JavaScript and uses the CodeHS library on it to draw a circle.
<html>
<head>
<title>Circle Example</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript" src="https://static.codehs.com/gulp/3d065bc81d3b7edf21e928ce2c764374a03c5cd6/chs-js-lib/chs.js"></script>
<style>
canvas {
border: 1px solid black;
display: inline-block;
vertical-align: top;
}
pre {
border: 1px solid black;
display: inline-block;
width: 400px;
height: 500px;
background-color: #F5F5F5;
}
</style>
</head>
<body>
<h1>Circle Example</h1>
<canvas
width="400"
height="500"
class="codehs-editor-canvas"></canvas>
<script>
window.onload = function() {
var circle = new Circle(50);
circle.setPosition(100,100);
add(circle);
};
</script>
</body>
</html>
Looks like KHAN ACADEMY uses ProcessingJS to draw the circle
I was unable to check what is the library CodeHS uses to draw a circle, but has to be a different one. But the fact is that there are so many good libraries developed in javascript to make whatever you can imagine. They're generally different one from another but their goal is to make our life easier.
JavaScript library | Wikipedia
What's a JS library? | KHAN ACADEMY
I tried using the processing platform for CodeHs, just copying and pasting this code:
/**
* This program finds the shortest path through a series of obstacles using A* search,
* smooths the path, then uses PID control to drive a robot to the goal.
**/
// You can play around with these constants---
var NUM_BLOCKS = 20;
var OBSTACLE_PROBABILITY = 0.2;
var MOVE_NOISE = 0.1;
var STEERING_NOISE = 0.1;
var ROBOT_SPEED = 0.5;
var MAX_STEERING_ANGLE = Math.PI/4.0;
var tP = 2.0;
var tI = 0.0001;
var tD = 16.0;
var weightData = 0.1;
var weightSmooth = 0.1;
// Search types
var A_STAR = 0;
var GREEDY = 1;
var BREADTH_FIRST = 2;
var DEPTH_FIRST = 3;
var SEARCH_MODE = A_STAR;
// --------------------------------------------
var BLOCK_SIZE = width/NUM_BLOCKS;
var START = 2;
var GOAL_X = NUM_BLOCKS-1;
var GOAL_Y = NUM_BLOCKS-1;
var GOAL = 3;
var START_COLOR = color(23, 33, 176);
var GOAL_COLOR = color(199, 188, 68);
var FRONTIER_COLOR = color(105, 179, 105);
var EXPLORED_COLOR = color(117, 117, 117, 100);
var PATH_COLOR = color(166, 53, 53);
var SMOOTH_PATH_COLOR = color(53, 68, 166);
var PLAN = 0;
var SMOOTH = 1;
var CALIBRATE = 2;
var NAVIGATE = 3;
var DELTA = [[-1, 0], [1, 0], [0, -1], [0, 1]];
var frontier = [[0, 0, 0, 0]];
var explored = [];
var predecessors = [];
var path = [];
var smoothPath = [];
var mode = PLAN;
var index = 0;
var cte = 0;
var sigma = 0;
angleMode = "radians";
frameRate(60);
// Initialize world
var world = [];
for (var i = 0; i < NUM_BLOCKS; i++) {
var row = [];
for (var j = 0; j < NUM_BLOCKS; j++) {
var r = random();
if (r < OBSTACLE_PROBABILITY) {
row.push(1);
}
else {
row.push(0);
}
}
world.push(row);
}
world[0][0] = START;
world[GOAL_Y][GOAL_X] = GOAL;
for (var i = 0; i < NUM_BLOCKS; i++) {
var row = [];
for (var j = 0; j < NUM_BLOCKS; j++) {
row.push(false);
}
explored.push(row);
}
explored[0][0] = true;
for (var i = 0; i < NUM_BLOCKS; i++) {
var row = [];
for (var j = 0; j < NUM_BLOCKS; j++) {
row.push(null);
}
predecessors.push(row);
}
var Robot = function(x, y, size) {
this.x = x;
this.y = y;
this.vx = 0;
this.vy = 0;
this.orientation = 0;
this.size = size;
// Thanks to Sebastian Thrun for this code:
// https://www.udacity.com/course/viewer#!/c-cs373/l-48696626/e-48403941/m-48729137
// My code for this movement can be found at:
// https://www.khanacademy.org/computer-programming/bicycle-model/5496951953031168
this.move = function(d, theta) {
var generator = new Random(millis());
var moveDistance = (generator.nextGaussian() * MOVE_NOISE) + d;
var turnAngle = (generator.nextGaussian() * STEERING_NOISE) + theta;
turnAngle = min(turnAngle, MAX_STEERING_ANGLE);
turnAngle = max(turnAngle, -MAX_STEERING_ANGLE);
var turn = tan(turnAngle)*moveDistance/this.size;
// Approximately straight motion
if (abs(turn) < 0.001) {
this.x += moveDistance*cos(this.orientation);
this.y += moveDistance*sin(this.orientation);
this.orientation = (this.orientation+turn)%(2.0*Math.PI);
}
// Move using the bicyle model
else {
var radius = moveDistance/turn;
var cx = this.x-(sin(this.orientation)*radius);
var cy = this.y+(cos(this.orientation)*radius);
this.orientation = (this.orientation+turn)%(2.0*Math.PI);
this.x = cx + (sin(this.orientation)*radius);
this.y = cy - (cos(this.orientation)*radius);
}
};
this.draw = function() {
pushMatrix();
translate(this.x, this.y);
rotate(this.orientation);
fill(128, 27, 27);
stroke(255, 0, 0);
rect(-this.size/2, -(this.size*0.75*0.5), this.size, this.size*0.75);
popMatrix();
};
};
var robot;
var addToFrontier = function(node) {
// Insert the node into the frontier
// Order by lowest cost
var i = frontier.length-1;
if (SEARCH_MODE === A_STAR) {
while (i > 0 && node[2]+node[3] < frontier[i][2]+frontier[i][3]) {
i--;
}
}
else if (SEARCH_MODE === GREEDY) {
while (i > 0 && node[3] < frontier[i][3]) {
i--;
}
}
else if (SEARCH_MODE === BREADTH_FIRST) {
frontier.push(node);
}
else if (SEARCH_MODE === DEPTH_FIRST) {
frontier.splice(0, 0, node);
}
frontier.splice(i+1, 0, node);
};
var distance = function(x1, y1, x2, y2) {
return sqrt(pow(x1-x2, 2) + pow(y1-y2, 2));
};
var manhattanDistance = function(x1, y1, x2, y2) {
return abs(x1-x2) + abs(y1-y2);
};
var drawWorld = function() {
background(255, 255, 255);
for (var i = 0; i < world.length; i++) {
for (var j = 0; j < world[0].length; j++) {
if (world[i][j] === 1) {
stroke(0, 0, 0);
fill(0, 0, 0);
}
else if (world[i][j] === START) {
fill(START_COLOR);
stroke(START_COLOR);
}
else if (world[i][j] === GOAL) {
fill(GOAL_COLOR);
stroke(GOAL_COLOR);
}
else {
fill(255, 255, 255);
noStroke();
}
rect(j*BLOCK_SIZE, i*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
}
};
var simulate = function(steps) {
var error = 0;
var crosstrackError = 0;
var r = new Robot(0, 1, BLOCK_SIZE/2);
var sigma = 0;
var diff = 0;
for (var i = 0; i < steps*2; i++) {
// Compute cte
diff = r.y-crosstrackError;
crosstrackError = r.y;
sigma += crosstrackError;
if (i > steps) {
error += pow(crosstrackError, 2);
}
// Update robot
r.move(ROBOT_SPEED, -tP*crosstrackError - tD*diff - tI*sigma);
}
return error;
};
draw = function() {
drawWorld();
// Use A* to find a path to the goal
if (mode === PLAN) {
// Draw explored
fill(EXPLORED_COLOR);
noStroke();
for (var i = 0; i < explored.length; i++) {
for (var j = 0; j < explored[0].length; j++) {
if (explored[i][j]) {
rect(j*BLOCK_SIZE, i*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
}
}
// Draw frontier
fill(FRONTIER_COLOR);
noStroke();
for (var i = 0; i < frontier.length; i++) {
rect(frontier[i][0]*BLOCK_SIZE, frontier[i][1]*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
// A*
if (frontier.length > 0) {
// Remove a node from the frontier
var x = frontier[0][0];
var y = frontier[0][1];
var cost = frontier[0][2];
//println(cost + ", " + frontier[0][3]);
frontier.splice(0, 1);
// Goal check
if (world[y][x] === GOAL) {
mode = SMOOTH;
}
else {
// Add all adjacent unexplored nodes
for (var i = 0; i < DELTA.length; i++) {
// If the new position is in the world
var x2 = x + DELTA[i][0];
var y2 = y + DELTA[i][1];
if (x2 >= 0 && x2 < world[0].length && y2 >= 0 && y2 < world.length) {
// If the position is unexplored
if (!explored[y2][x2] && world[y2][x2] !== 1) {
explored[y2][x2] = true;
predecessors[y2][x2] = [x, y];
addToFrontier([x2, y2, cost+1, dist(x2, y2, GOAL_X, GOAL_Y)]);
}
}
}
}
}
else {
mode = -1;
println("No possible path to goal.");
}
}
// Smooth the path
else if (mode === SMOOTH) {
// Build path
if (path.length === 0) {
var x = GOAL_X;
var y = GOAL_Y;
path.splice(0, 0, [x, y]);
smoothPath.splice(0, 0, [x, y]);
while (x !== 0 || y !== 0) {
var newX = predecessors[y][x][0];
var newY = predecessors[y][x][1];
x = newX;
y = newY;
path.splice(0, 0, [x, y]);
smoothPath.splice(0, 0, [x, y]);
}
}
// Draw the original path
stroke(PATH_COLOR);
strokeWeight(5);
for (var i = 0; i < path.length; i++) {
point(BLOCK_SIZE*path[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*path[i][1]+BLOCK_SIZE/2);
}
strokeWeight(1);
for (var i = 0; i < path.length-1; i++) {
line(BLOCK_SIZE*path[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*path[i][1]+BLOCK_SIZE/2, BLOCK_SIZE*path[i+1][0]+BLOCK_SIZE/2, BLOCK_SIZE*path[i+1][1]+BLOCK_SIZE/2);
}
// Draw the new path
stroke(SMOOTH_PATH_COLOR);
strokeWeight(5);
for (var i = 0; i < smoothPath.length; i++) {
point(BLOCK_SIZE*smoothPath[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i][1]+BLOCK_SIZE/2);
}
strokeWeight(1);
for (var i = 0; i < smoothPath.length-1; i++) {
line(BLOCK_SIZE*smoothPath[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i][1]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i+1][0]+BLOCK_SIZE/2,
BLOCK_SIZE*smoothPath[i+1][1]+BLOCK_SIZE/2);
}
// Perform gradient descent
var update;
var diff = 0;
for (var i = 1; i < smoothPath.length-1; i++) {
update = [0, 0];
for (var j = 0; j < smoothPath[0].length; j++) {
update[j] += weightData * (path[i][j] - smoothPath[i][j]);
update[j] += weightSmooth * (smoothPath[(i+1)%smoothPath.length][j] + smoothPath[(i-1+smoothPath.length)%smoothPath.length][j] - 2*smoothPath[i][j]);
}
// Simulataneous update
for (var j = 0; j < smoothPath[0].length; j++) {
smoothPath[i][j] += update[j];
diff += abs(update[j]);
}
}
if (diff < 0.000001) {
robot = new Robot(BLOCK_SIZE/2, BLOCK_SIZE/2, BLOCK_SIZE/2);
mode = NAVIGATE;
}
}
else if (mode === CALIBRATE) {
var steps = 100;
var error = simulate(steps);
var dp = [1.0, 1.0, 1.0];
var params = [0, 0, 0];
if (error/steps > 0.04) {
for (var i = 0; i < dp.length; i++) {
params[i] += dp[i];
var newError = simulate(steps);
if (newError < error) {
error = newError;
dp[i] *= 1.1;
}
else {
params[i] -= 2*dp[i];
newError = simulate(steps);
if (newError < error) {
error = newError;
dp[i] *= -1.1;
}
else {
params[i] += dp[i];
dp[i] *= 0.9;
}
}
}
tP = params[0];
tD = params[1];
tI = params[2];
println(error/steps);
}
else {
println(params);
tP = params[0];
tD = params[1];
tI = params[2];
mode = NAVIGATE;
}
}
// Use PID control to follow the path
else if (mode === NAVIGATE) {
// Draw path
stroke(SMOOTH_PATH_COLOR);
strokeWeight(5);
for (var i = 0; i < smoothPath.length; i++) {
point(BLOCK_SIZE*smoothPath[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i][1]+BLOCK_SIZE/2);
}
strokeWeight(1);
for (var i = 0; i < smoothPath.length-1; i++) {
line(BLOCK_SIZE*smoothPath[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i][1]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i+1][0]+BLOCK_SIZE/2,
BLOCK_SIZE*smoothPath[i+1][1]+BLOCK_SIZE/2);
}
// Draw robot
robot.draw();
// Compute cte
var diff = -cte;
var x1 = smoothPath[index][0]*BLOCK_SIZE+BLOCK_SIZE/2;
var y1 = smoothPath[index][1]*BLOCK_SIZE+BLOCK_SIZE/2;
var x2 = smoothPath[index+1][0]*BLOCK_SIZE+BLOCK_SIZE/2;
var y2 = smoothPath[index+1][1]*BLOCK_SIZE+BLOCK_SIZE/2;
var dx = x2-x1;
var dy = y2-y1;
var d = sqrt(pow(dx, 2) + pow(dy, 2));
var u = ((robot.x-x1)*dx + (robot.y-y1)*dy)/(pow(dx, 2) + pow(dy, 2));
var Px = x1 + d*u*cos(atan2(dy, dx));
var Py = y1 + d*u*sin(atan2(dy, dx));
cte = ((robot.y-y1)*dx-(robot.x-x1)*dy)/(pow(dx, 2) + pow(dy, 2));
sigma += cte;
diff += cte;
if (u > 1) {
index++;
index = min(index, smoothPath.length-2);
}
// Update robot
robot.move(ROBOT_SPEED, -tP*cte - tD*diff - tI*sigma);
//println(index);
}
};
but all that happens is that a grey screen shows up
I'm working on getting all the components for my final together using the P5.play engine and while I have made some progress with setting up aspects of my mini game I'm having a hard time with the collision. It should be easy but for whatever reason when I set up my two objects (the fish and the garbage) they do not collide. I am trying to set it up so that when the garbage collides with the fish the fish either get removed or are reset to a place where they can continue to move on the screen while tallying the score. I managed to get the player sprite to collect the garbage and add to the score using overlapPoint and placing the condition in the update for the garbage object. But when I attempt the same technique for the fish on the garbage object an error occurs and everything disappears on the screen. I commented out the portion that is I have tried multiple ways including the collide() function on the objects with the proper conditionals but nothing seems to work. A bit frustrating. I have tried various other ways. So I'm asking for expert advice. Any assistance is appreciated. this is the code that i have thus far:
var bg;
var player;
var player_stand_sprites;
var player_stand;
var fish_swim_sprites;
var fish_swim;
var fish = [];
var garbage_drop_sprites;
var garbage_drop;
var garbage = [];
var score = 0;
function preload() {
bg = loadImage("final-bg.png");
player_stand_sprites = loadSpriteSheet("player2.png", 100, 100, 1);
player_stand = loadAnimation(player_stand_sprites);
fish_swim_sprites = loadSpriteSheet("fish.png", 75, 75, 1);
fish_swim = loadAnimation(fish_swim_sprites);
garbage_drop_sprites = loadSpriteSheet("metal.png", 41, 75, 1);
garbage_drop = loadAnimation(garbage_drop_sprites);
}
function setup() {
createCanvas(720, 360);
player = createSprite(100, 0);
player.addAnimation("stand", player_stand);
player.setCollider("circle", 0, 0, 32, 32);
player.depth = 10;
//player.debug = true;
//to make fish
for (var i = 0; i < 10; i++){
fish.push( new Fish(random(0,width), random(height/2, height)) );
for (var i = 0; i < fish.length; i++) {
fish[i].init();
}
}
//to make garbage
for (var a = 0; a < 5; a++){
garbage.push( new Garbage(random(0,width), random(width/2, width)));
}
}
function draw() {
background(bg);
player.position.x = mouseX;
player.position.y = mouseY;
for (var i = 0; i < fish.length; i++) {
fish[i].update();
}
for (var a = 0; a < garbage.length; a++) {
garbage[a].update();
}
/**for (var b = 0; b < fish.length; b++) {
if(fish[b].collide(garbage[b])){
fish[b].remove;
}
}**/
text(score,100,100);
drawSprites();
}
function Garbage(x,y){
this.sprite = createSprite(x, y);
this.sprite.addAnimation("drop", garbage_drop);
this.sprite.setCollider("circle",0,0,32,32);
this.speed = random(1,2);
this.sprite.debug = true;
this.update = function() {
this.sprite.position.y += this.speed;
if(this.sprite.position.y > height){
this.sprite.position.y = 0;
}
if(this.sprite.overlapPoint(player.position.x, player.position.y)){
this.sprite.position.x = random(0,width);
this.sprite.position.y = -75;
score++;
}
}
}
function Fish(x,y) {
this.sprite = createSprite(x, y);
this.sprite.addAnimation("swim", fish_swim);
this.sprite.setCollider("rectangle",0,0,75,32);
this.speed = 0;
this.sprite.debug = true;
this.init = function() {
if (this.sprite.position.x < width/2) {
this.sprite.mirrorX(-1);
this.speed = random(1, 2);
} else {
this.speed = -random(1,2);
}
}
this.update = function() {
this.sprite.position.x += this.speed;
if(this.sprite.position.x > width){
this.sprite.position.x = 0;
}else if(this.sprite.position.x < -width){
this.sprite.position.x = width;
}
}
}
I actually found the answer to my question a while back but am going to post it here.
The initial problem was that the sprites would not collide but with a simple for loop nested within another for loop and adding a .sprite to each of the objects being checked, I was able to get all the elements to collide properly.
Here is the code I revised to make it work seamlessly using the P5.play.js library:
var bg;
var img;
var dead = false;
var deadFish = 0;
var player;
var player_stand_sprites;
var player_stand;
var fish_swim_sprites;
var fish_swim;
var fish = [];
var garbage_drop_sprites;
var garbage_drop;
var garbage = [];
var score = 0;
//////////////////////////////////////////
function preload() {
bg = loadImage("final-bg.png");
img = loadImage("fish.png");
player_stand_sprites = loadSpriteSheet("player2.png", 100, 100, 1);
player_stand = loadAnimation(player_stand_sprites);
fish_swim_sprites = loadSpriteSheet("fish.png", 75, 75, 1);
fish_swim = loadAnimation(fish_swim_sprites);
garbage_drop_sprites = loadSpriteSheet("metal.png", 41, 75, 1);
garbage_drop = loadAnimation(garbage_drop_sprites);
}
//////////////////////////////////////////
function setup() {
createCanvas(720, 360);
player = createSprite(100, 0);
player.addAnimation("stand", player_stand);
player.setCollider("circle", 0, 0, 32, 32);
player.depth = 10;
//player.debug = true;
//to make fish
for (var i = 0; i < 10; i++){
fish.push( new Fish(random(0,width), random(height/2, height)) );
for (var i = 0; i < fish.length; i++) {
fish[i].init();
}
}
//to make garbage
for (var a = 0; a < 5; a++){
garbage.push( new Garbage(random(0,width), random(width/2, width)));
}
}
function draw() {
scene_start();
}
//////////////////////////////////////////
function scene_start(){
push();
background("green");
fill("white");
textAlign(CENTER);
textSize(50);
textStyle(BOLD);
text("SPOT A FISH", width/2,height/3.5);
image(img, width/2.3, height/3);
textSize(15);
text("Rules: dont let the cans touch the fish. 5 fish die and you must start over", width/2, height/1.5);
textSize(30);
text("press up arrow key to start", width/2, height/1.2);
pop();
if (keyCode == UP_ARROW) {
scene_1();
}
}
function scene_1(){
background(bg);
score_card();
if(!dead){
player.position.x = mouseX;
player.position.y = mouseY;
for (var i = 0; i < fish.length; i++) {
fish[i].update();
}
for (var i = 0; i < garbage.length; i++) {
garbage[i].update();
}
for (var a = 0; a < garbage.length; a++) {
for (var b = 0; b < fish.length; b++) {
if(fish[b].sprite.collide(garbage[a].sprite)){
fish[b].sprite.position.x = random(-500, -100);
deadFish ++;
if(deadFish === 5){
dead = true;
}
}
}
}
drawSprites();
}else{
score_card();
textSize(30);
textStyle(BOLD);
textAlign(CENTER);
text("YOU DIED PLEASE TRY AGAIN",width/2,height/2);
text("press any button to start again",width/2,height/1.5);
if(keyIsPressed === true){
deadFish = 0;
dead = false;
}
}
}
function Garbage(x,y){
this.sprite = createSprite(x, y);
this.sprite.addAnimation("drop", garbage_drop);
this.sprite.setCollider("circle",0,0,32,32);
this.speed = random(1,2);
//this.sprite.debug = true;
this.update = function() {
this.sprite.position.y += this.speed;
if(this.sprite.position.y > height){
this.sprite.position.y = 0;
}
if(this.sprite.overlapPoint(player.position.x, player.position.y)){
this.sprite.position.x = random(0,width);
this.sprite.position.y = random(-200,-75);
score++;
}
if(score === 100){
this.speed = random(2,3);
score += 1000;
}else if(score === 1200){
this.speed = random(3,3.5);
score += 1000;
}
}
}
var score_card = function(){
fill("black");
rect(0,0,width,30);
textStyle(BOLD);
fill("white");
text("SCORE: "+score,10,20);
text("DEAD FISH: "+deadFish,width/1.2,20)
}
function Fish(x,y) {
this.sprite = createSprite(x, y);
this.sprite.addAnimation("swim", fish_swim);
this.sprite.setCollider("rectangle",0,0,75,32);
this.speed = 0;
this.sprite.debug = true;
this.init = function() {
if (this.sprite.position.x < width/2) {
this.sprite.mirrorX(-1);
this.speed = random(1, 2);
} else {
this.speed = -random(1,2);
}
}
this.update = function() {
this.sprite.position.x += this.speed;
if(this.sprite.position.x > width){
this.sprite.position.x = 0;
}else if(this.sprite.position.x < -width){
this.sprite.position.x = width;
}
}
}
I'm programming Conway's Game of Life and have produced a working, smooth JS program. What I do in the working version is to check each neighboring coordinate of every coordinate in the grid, and either kill or spawn it based on its number of neighbors. Now I want to make the algorithm more efficient by keeping track of which coordinates are alive, and only process those and their neighbors, instead of the whole grid. I made this alternative program:
var g = 0;
var cellMatrix = new Array();
var height = 68;
var width = 100;
var livingCellIndex = 0;
var livingCells = new Array();
writeBoard();
declareFirstGeneration();
live();
function live() {
processGeneration();
g++;
setTimeout(live, speed);
}
function declareNextGeneration() {
livingCells[g + 1] = new Array();
cellMatrix[g + 1] = new Array();
for (var x = 0; x < width; x++) {
cellMatrix[g + 1][x] = new Array();
for (var y = 0; y < height; y++) {
cellMatrix[g + 1][x][y] = false;
}
}
}
function declareFirstGeneration() {
livingCells[g] = new Array();
cellMatrix[g] = new Array();
for (var x = 0; x < width; x++) {
cellMatrix[g][x] = new Array();
for (var y = 0; y < height; y++) {
cellMatrix[g][x][y] = false;
}
}
}
function processGeneration() {
declareNextGeneration();
livingCellIndex = 0;
var x, y;
for (var i = 0; i < livingCells[g].length; i++) {
x = livingCells[g][i][0];
y = livingCells[g][i][1];
numberOfNeighbors = getLivingNeighbors(x, y);
//console.log("numberOfNeighbors", numberOfNeighbors);
if (numberOfNeighbors == 2 || numberOfNeighbors == 3) {
spawnCell(g + 1, x, y);
} else {
killCell(g + 1, x, y);
}
for (var neighborX = x - 1; neighborX <= x + 1; neighborX++) {
for (var neighborY = y - 1; neighborY <= y + 1; neighborY++) {
if (neighborX < width && neighborX >= 0 && neighborY < height && neighborY >= 0) {
numberOfNeighbors = getLivingNeighbors(neighborX, neighborY);
//console.log(g, neighborX, neighborY, "has ", numberOfNeighbors, " neighbors");
if (numberOfNeighbors == 3) {
spawnCell(g + 1, neighborX, neighborY);
}
}
}
}
}
refreshGenerationDisplay(x,y);
}
function spawnCell(g, x, y) {
cellMatrix[g][x][y] = true;
livingCells[g][livingCellIndex] = new Array(2);
livingCells[g][livingCellIndex][0] = x;
livingCells[g][livingCellIndex][2] = y;
document.getElementById(x + '-' + y).style.background = "green"; // visual grid
livingCellIndex++;
}
function killCell(g, x, y) {
cellMatrix[g][x][y] = false;
document.getElementById(x + '-' + y).style.background = "none"; // visual grid
}
But I find that it is a lot slower than my first program. It seems like the computational cost of calculating each generation seems to increase with each generation. This surprises me because I though less data is handled in this alternative algorithm. I'm not sure if it is of interest, but here is the first version:
var g = 0;
var cellMatrix = new Array();
var height = 68;
var width = 100;
declareThisGeneration();
function live() {
g++;
processGeneration();
setTimeout(live, speed);
}
function processGeneration() {
if (oscillation) adjustGForOscillation();
if (g > 0) {
processNormalGeneration();
} else {
processFirstGeneration();
}
}
function processFirstGeneration() {
for (var x = 0; x < width; x++) {
for (var y = 0; y < height; y++) {
processFirstGenCell(g, x, y);
}
}
}
function processFirstGenCell(g, x, y) {
if (cellMatrix[g][x][y]) { //if alive
spawnCell(g, x, y);
} else { //if dead
killCell(g, x, y);
}
}
function processNormalGeneration() {
for (var x = 0; x < width; x++) {
for (var y = 0; y < height; y++) {
processCell(g, x, y);
}
}
}
function processCell(g, x, y) {
var livingNeighbors = getLivingNeighbors(g - 1, x, y);
if (cellMatrix[g - 1][x][y]) { //if alive
if (livingNeighbors != 2 && livingNeighbors != 3) {
killCell(g, x, y);
} else {
spawnCell(g, x, y);
}
} else { //if dead
if (livingNeighbors == 3) {
spawnCell(g, x, y);
} else {
killCell(g, x, y);
}
}
}
function spawnCell(g, x, y) {
cellMatrix[g][x][y] = true;
document.getElementById(x + '-' + y).className = 'alive';
}
function killCell(g, x, y) {
cellMatrix[g][x][y] = false;
document.getElementById(x + '-' + y).className = ''
}
My question is, what makes the "improved" algorithm so slow, and how can I reduce its cost?
Version 1 // first, fastest
Version 2 // new, slower
You keep allocating new Arrays for each generation in the new version; in the old version, you keep re-using the same grid. In addition to being slower, if not a REASON for it being slower, is that your memory footprint keeps growing.
One possible explanation is that if you keep track on too many things, the computer will need to allocate more memory to store them, and that takes time.