function isn't being called in JavaScript graphics - javascript

I was making a tic tac toe game in JavaScript graphics (I think) and I called a function to get the row of the click (the function that isn't working is the getRow function) and there isn't any output from the function
var WIDTH = 400;
var HEIGHT = 400;
setSize(WIDTH, HEIGHT);
var WINNING_LINE_WIDTH = 10;
var WINNING_LINE_COLOR = Color.red;
//high
var width = getWidth() / 3;
//thick
var height = getHeight() / 3;
//x != o
var shape = 0;
//1 2 3, as easy as A B C
var row = 0;
//ready, set, GO!!!!
function start(){
grid();
mouseClickMethod(click);
}
//lines
function grid(){
var line1 = new Line(width, 0, width, getHeight());
var line2 = new Line(width * 2, 0, width * 2, getHeight());
var line3 = new Line(0, height, getWidth(), height);
var line4 = new Line(0, height * 2, getWidth(), height * 2);
add(line1);
add(line2);
add(line3);
add(line4);
}
//notrick
function click(e){
var clicky = e.getY();
getRow(clicky,row);
println(row);
println(clicky);
println(height);
println(height * 2);
println(height * 3);
}
//row row row ur boat
function getRow(clicky, row){
if(clicky < height){
return row + 1;
} if(clicky >= height && clicky <= height * 2 ){
return row + 2;
} else{
return row + 3;
} return row - 1;
println("nothing");
}

This code is working properly for what you wrote, but there are a few things you'll want to change to get your intended functionality.
First, functions' return values are returned through the array call itself. It seems that in your example code you're passing in "clicky" and "row" as arguments, and trying to set the value of those variables in the function. When you create parameters though, you're actually overwriting any other variables in the current scope with the same name and creating local variables instead, and you're just passing in the value of the variables n your function call. So this is what's happening in your program:
var row = 0;
function getRow(row) {
// defining row as a parameter creates a new local variable called "row"
row = 1; // sets the "row" local variable to 1, but doesn't affect the "row" outside of the function
console.log(row); // prints "1" because that's the value of the local var
}
getRow(row);
console.log(row); // prints 0 because that's the value of the global variable "row"
So what you'll want to do is pass in your cursor's y-value on click (which you already are doing! It saves it as the function parameter "clicky") but you do not need to pass in the row too.
Then, you can just return an integer for your row value! (Instead of adding it to the global variable as you are right now) Then all you have to do is actually set row to that value, by setting row equal to the function call.
// variable values for example
var row = -1;
var height = 100 / 3; // each row will be ~33 pixels
function click(e){
var clicky = e.getY();
row = getRow(clicky); // set row to the value returned from getRow()!
}
//row row row ur boat
function getRow(clicky){
if(clicky < height){
return 1;
} if(clicky >= height && clicky <= height * 2 ){
return 2;
} else{
return 3;
} return -1;
println("nothing"); // also note: you can delete this as it will never run, as its after a return (which stops running the function)
}
// testing:
console.log(row); // before click (-1)
click({getY:()=>0}); // test object I made so it can get a y-value (it'll just be your click event in your code)
console.log(row); // after clicking at y = 0 (0)
click({getY:()=>40});
console.log(row); // after clicking at y = 40 (1)
click({getY:()=>90});
console.log(row); // after clicking at y = 90 (2)
Hope this helped! If you need clarification on anything just let me know.

Related

Connect 4 check for win isn't working - how do I fix?

EDIT: The instructions were really confusing for me.
"Step Seven: findSpotForCol and endGame
Right now, the game drops always drops a piece to the top of the column, even if a piece is already there. Fix this function so that it finds the lowest empty spot in the game board and returns the y coordinate (or null if the column is filled)."
This makes me think that findSpotForCol is supposed to put where each piece goes, however step 5 says:
"While not everything will work, you should now be able to click on a column and see a piece appear at the very bottom of that column."
So the piece should already be at the bottom on step 5, not waiting til step 7?
*****
I am doing an assignment creating a connect 4 game. There was some code already, and instructions on how to complete the rest. I, uh, don't think I followed the instructions to a "t" (as in certain code should go inside a certain function), but it all works as intended (mostly).
Every click is a different turn(changing the piece colors). Pieces stack how they're supposed to. If the board is completely full (as in tie), the game ends. Basically everything works in the game except for "ending the game" when 4 of the same pieces match.
The code for checking the win had already been written by my instructor. Is anyone able to check my code and advise how the check for win should be written for the code I have? I've spent quite a few hours on this, and rather not start over to write code that works for the check-for-win function pre-set by my instructor.
HTML
<!doctype html>
<head>
<title>Connect 4</title>
<link href="connect4.css" rel="stylesheet">
</head>
<body>
<div id="game">
<table id="board"></table>
</div>
<script src="connect4.js"></script>
</body>
</html>
CSS
/* game board table */
#board td {
width: 50px;
height: 50px;
border: solid 1px #666;
}
/* pieces are div within game table cells: draw as colored circles */
.piece {
/* TODO: make into circles */
margin: 5px;
width: 80%;
height: 80%;
border-radius: 50%;
}
/* TODO: make pieces red/blue, depending on player 1/2 piece */
.plyr1 {
background-color: blue;
}
.plyr2 {
background-color: red;
}
/* column-top is table row of clickable areas for each column */
#column-top td {
border: dashed 1px lightgray;
}
#column-top td:hover {
background-color: gold;
}
JS
/** Connect Four
*
* Player 1 and 2 alternate turns. On each turn, a piece is dropped down a
* column until a player gets four-in-a-row (horiz, vert, or diag) or until
* board fills (tie)
*/
const WIDTH = 7;
const HEIGHT = 6;
let currPlayer = 1; // active player: 1 or 2
const board = []; // array of rows, each row is array of cells (board[y][x])
/** makeBoard: create in-JS board structure:
* board = array of rows, each row is array of cells (board[y][x])
*/
function makeBoard() {
let board2 = [];
for (let i = 0; i < HEIGHT; i++) {
board2.push(null);
for (let j = 0; j < WIDTH; j++) {
board[i] = board2;
}
}
}
/** makeHtmlBoard: make HTML table and row of column tops. */
function makeHtmlBoard() {
const htmlBoard = document.querySelector('#board'); //selecting the board
const top = document.createElement('tr'); //creating a table row element
top.setAttribute('id', 'column-top'); //setting id of tr just created
top.addEventListener('click', handleClick); // adding an event listener that listens for handleClick( function)
for (let x = 0; x < WIDTH; x++) {
const headCell = document.createElement('td'); //creating a table data element equal to WIDTH
headCell.setAttribute('id', x); //setting id of td just created to x
top.append(headCell); //displaying headingCell right under where top is displayed (nesting the td inside of the tr)
}
htmlBoard.append(top); //displaying top right under where htmlBoard is displayed
for (let y = 0; y < HEIGHT; y++) {
const row = document.createElement('tr'); //creating a table row element * HEIGHT
for (let x = 0; x < WIDTH; x++) {
const cell = document.createElement('td'); //creating a table data element equal to WIDTH
cell.setAttribute('id', `${y}-${x}`); //setting id of td just created
row.append(cell); //displaying cell right under where row is displayed (nesting each td inside of each tr)
}
htmlBoard.append(row); //displaying row right under where htmlBoard is displayed
}
}
/** findSpotForCol: given column x, return top empty y (null if filled) */
function findSpotForCol(x) {
// TODO: write the real version of this, rather than always returning 0
return 0;
}
/** placeInTable: update DOM to place piece into HTML table of board */
function placeInTable(y, x) {
// TODO: make a div and insert into correct table cell
const div = document.createElement('div');
div.classList.add('piece');
const top = document.querySelector(`[id='0-${x}']`);
if (currPlayer === 1 && top.innerHTML === '') {
div.classList.add('plyr1');
currPlayer = 2;
} else if (currPlayer === 2 && top.innerHTML === '') {
div.classList.add('plyr2');
currPlayer = 1;
}
let arrHeight = [];
for (let i = 0; i < HEIGHT; i++) {
arrHeight.push(i);
}
for (i of arrHeight.reverse()) {
if (document.getElementById(`${i}-${x}`).innerHTML === '') {
const selected = document.getElementById(`${i}-${x}`);
const top = document.getElementById(`6 -${x}`);
return selected.append(div);
}
}
}
/** endGame: announce game end */
function endGame(msg) {
// TODO: pop up alert message
alert('Test');
}
/** handleClick: handle click of column top to play piece */
function handleClick(evt) {
// get x from ID of clicked cell
let x = +evt.target.id;
// get next spot in column (if none, ignore click)
let y = findSpotForCol(x);
if (y === null) {
return;
}
// place piece in board and add to HTML table
// TODO: add line to update in-memory board
placeInTable(y, x);
// check for win
if (checkForWin()) {
return endGame(`Player ${currPlayer} won!`);
}
// check for tie
// TODO: check if all cells in board are filled; if so call, call endGame
const tie = document.querySelectorAll('td');
const tieArr = [ ...tie ];
tieArr.reverse();
for (let i = 0; i < WIDTH; i++) {
tieArr.pop();
}
let tie42 = tieArr.filter((v) => {
return v.innerHTML !== '';
});
if (tie42.length === 42) {
setTimeout(() => {
endGame();
}, 1);
}
}
/** checkForWin: check board cell-by-cell for "does a win start here?" */
function checkForWin() {
function _win(cells) {
// Check four cells to see if they're all color of current player
// - cells: list of four (y, x) cells
// - returns true if all are legal coordinates & all match currPlayer
return cells.every(([ y, x ]) => y >= 0 && y < HEIGHT && x >= 0 && x < WIDTH && board[y][x] === currPlayer);
}
// TODO: read and understand this code. Add comments to help you.
for (let y = 0; y < HEIGHT; y++) {
for (let x = 0; x < WIDTH; x++) {
let horiz = [ [ y, x ], [ y, x + 1 ], [ y, x + 2 ], [ y, x + 3 ] ];
let vert = [ [ y, x ], [ y + 1, x ], [ y + 2, x ], [ y + 3, x ] ];
let diagDR = [ [ y, x ], [ y + 1, x + 1 ], [ y + 2, x + 2 ], [ y + 3, x + 3 ] ];
let diagDL = [ [ y, x ], [ y + 1, x - 1 ], [ y + 2, x - 2 ], [ y + 3, x - 3 ] ];
if (_win(horiz) || _win(vert) || _win(diagDR) || _win(diagDL)) {
return true;
}
}
}
}
makeBoard();
makeHtmlBoard();
There are a few essential things you did not code:
// TODO: add line to update in-memory board
// TODO: write the real version of this, rather than always returning 0
Without those implementations you cannot expect the 4-in-a-row detection to work.
But let's take the issues one by one, starting at the top:
(1) The way you create the board is wrong: it only creates one board2 array, and so all the entries of board are referencing the same array. Whatever happens to it will be the same whether you look at it via board[0], board[1], or whichever entry... Here is how you could do it correctly:
function makeBoard() {
for (let i = 0; i < HEIGHT; i++) {
board[i] = Array(WIDTH).fill(null); // create a new array each time!
}
}
(2) The function findSpotForCol always returns 0, which of course is wrong. It is a pity you did not attempt to complete this function. The purpose of such assignments is that you work on them, and learn as you try.
It could look like this:
function findSpotForCol(x) {
for (let y = 0; y < HEIGHT; y++) {
if (board[y][x] === null) return y;
}
return null;
}
(3) placeInTable should not change currPlayer. It is not its job to do so. Changing it here will give it the wrong value at the time you call checkForWin. Instead you should modify currPlayer as the last thing you do in handleClick, and it can be simply this line:
currPlayer = 3 - currPlayer;
(3b) placeInTable is overly complicated. Although it works fine, I would write it like below, making use of the rows and cells methods; no need for HTML id attributes, except for the id of the table element.
function placeInTable(y, x) {
const div = document.createElement('div');
div.classList.add('piece', 'plyr' + currPlayer);
document.querySelector("#board").rows[HEIGHT - y].cells[x].append(div);
}
(4) The endGame shows "Test", which is not its purpose. Again, you should have updated that code. It should alert this:
alert(msg);
(5) In handleClick you did not do anything to modify the board. You should add this line just before the call to placeInTable:
board[y][x] = currPlayer;
(6) The check for a tie should not be based on what is in the document, but on what is in memory. A move counter would make it easier, but let's just do it by scanning board. You should of course pass an argument to endGame:
if (board.every(row => row.every(Boolean))) {
return endGame("It's a draw!");
}
Note the use of Boolean here. It is the callback function given to every, and will return false when it is given null, true when it is given 1 or 2.
With these adaptations it should work.
Just judging from a quick glance on your code: There is an in-memory table called board, which the check for a win is using. However, your placeInTable implementation does not update this table, but directly inserts DOM-elements.
What I would advise you to do instead, is to sepearte the model (the state of the game) from the visualization. When you get the click-event, you update the in-memory table, then you visualize the entire table, e.g. by setting the appropriate css-classes in the already created html-table.
Then you check for a win.
Also, the check for winning compares against a variable called test, which does not seem to be defined anywhere.

Svg object modified with setAttributeNS not updating until after while loop

I'm creating a small game in javascript and I'm using svg for the graphics. Right now I'm having a problem with updating the game in the middle of a game tick. If I exit my loop directly after I update the fill attribute with "setAttributeNS", it's redrawn, but if I don't do that, it isn't updated until after "game_tick" is over. Even worse, if I call "game_tick" multiple times in a row, the svg objects aren't updated until after I've run all of the "game_tick"s instead of being updated after each one.
function game_tick(){
num_grid_copy = num_grid.slice();
for (var x = 0; x < num_squares_x; x += 1) {
for (var y = 0; y < num_squares_x; y += 1) {
var n = get_neighbors(x,y);
var isAliveInNextGen = next_gen(n, num_grid[x*num_squares_x+y]);
num_grid_copy[x*num_squares_x+y] = isAliveInNextGen;
if (isAliveInNextGen == 1){
rect_grid[x*num_squares_x+y].setAttributeNS(null, 'fill', '#0099ff');
}
else {
rect_grid[x*num_squares_x+y].setAttributeNS(null, 'fill', '#fff');
}
}
}
num_grid = num_grid_copy;
}
Thanks to valuable input from Robert I realized that javascript execution and page rendering are done in the same thread. I changed the function to the following:
function start() {
var inc = 0,
max = 25;
delay = 100; // 100 milliseconds
var repeat = setInterval(function() {
game_tick();
if (++inc >= max)
clearInterval(repeat);
},
delay);
}
This works fine. I can set the delay and the number of times it repeats.

Why doesn't this function detect overlapping circles?

http://jsfiddle.net/goldrunt/SeAGU/52/
Line 49 checks for "false" on isOnCircle function before creating the new object. Function is on line 32. When creating more object, the function is passing when it should not pass.
if (isOnCanvas(location) && !isOnCircle(location)) {
console.log(location, isOnCanvas(location), isOnCircle(location));
create(location);
In fact I can't get the collision detection to register true no matter what values are passed to it
(Math.pow((a.x - i.x), 2) + Math.pow((a.y - i.y), 2) <= Math.pow((a.radius + i.radius), 2))
here I've fixed and given more descriptive variable names so you can see what's going on.
EDIT: I've noticed you don't always feed a circle but sometimes a point as A, which does not have a .radius property resulting in NaN, which also screws up your comparison.
function circleTest(a,b) {
var DistanceX = a.x - b.x;
var DistanceY = a.y - b.y;
var DistanceCenter = Math.sqrt(DistanceX * DistanceX + DistanceY * DistanceY);
var CollisionDistance = b.radius;
if (a.radius) CollisionDistance += a.radius
return DistanceCenter <= CollisionDistance;
}
I also noticed a problem in your function called "isOnCircle" where you are using i (a number) as if it were a circle object, with the above function this can be fixed like:
function isOnCircle(a) {
for (var i = 0; i < circles.length; i++) {
if (circleTest(a, circles[i])) return true;
}
return false;
}
Two problems:
i is the numerical index you are using to iterate through the circles array but you are using it as if it was a circle object; you need to use circles[i] to get the circle at each iteration.
a is a point and does not have a radius (in the code below I've left a.radius in just in-case you pass in a circle rather than a point and have ORed it with 0 so you get a valid number).
Defining some additional variables (for clarity) then you can replace the isOnCircle function with this:
function isOnCircle(a) {
var i=0,l=circles.length,x,y,d,c;
for (; i < l; ++i) {
c = circles[i];
x = a.x-c.x;
y = a.y-c.y;
d = (a.radius||0)+c.radius;
if (x*x+y*y <= d*d) {
return true;
}
}
return false;
}

Javascript - How do I clear out a previous value in a DIV?

i'm making a game where 2 players are fighting one another.
I have it setup where the document writes the objects hp out (100). However, when I make a event where the player suffers 10 damage, it should write out 90 in the hp bar. Instead it writes out 10090.
How can I get it where the previous value gets updated, rather than it continuing to write beside the previous value?
function player (hp, mana, stamina){
this.hp = hp;
this.mana = mana;
this.stamina = stamina;
}
function npc (hp, mana, stamina) {
this.hp = hp;
this.mana = mana;
this.stamina = stamina;
}
var alistar = new player (100, 50, 30);
var dragon = new npc (100, 50, 30);
document.getElementById("hp").innerHTML += alistar.hp;
if ( 0 < 1) {
alistar.hp = alistar.hp - 10;
document.getElementById("hp").innerHTML += alistar.hp;
}
Doing the = sign works, but it removed any HTML words I had in there. I suppose I can make a 2nd div box for my HTML needs and keep the value seperate.
Just put = symbol instead of +=:
The = operator overwrite the previous value. On the other hand, the += is the union between the + operator and the = operator, and is the "shortway" to achieve the next:
For example, if you want to add a value to a variable you can do this:
var a = 3,
value = 10; /* Your value, i.e. 10 */
a = a + value; /* a values 13 */
BUT, you can get the same result using the += operators:
var a = 3,
value = 10; /* Your value, i.e. 10 */
a = += value; /* This adds the value of a to the value of value variable. */
As you can think, the value of a is 13, too as the below example.
Regarding to your code...
CODE:
if ( 0 < 1) {
alistar.hp = alistar.hp - 10;
document.getElementById("hp").innerHTML = alistar.hp; /* UPDATE */
}
if ( 0 < 1) {
alistar.hp = alistar.hp - 10;
document.getElementById("hp").innerHTML = alistar.hp;
}
+= is concatenating the values and adding them together (seemingly in string format). Instead, you want to use "=" to assign the value of the hp to the hp element, overwriting the existing innerHTML.

How to find selected elements within a javascript marquee selection box without using a loop?

I am writing my own drag and drop file manager. This includes a javascript marquee selection box which when active calculates the elements (files) that are intersected and selects them by adding a class to them.
I currently perform the check during a mousemove handler, loop through an array of element coordinates and determine which ones are intersected by the drag and drop selection box.
The function currently looks like this:
selectItems : function(voidindex){
var self = this;
var coords = self.cache.selectioncoords;
for(var i=0, len = self.cache.items.length; i<len; i++){
var item = self.cache.items[i];
var itemcoords = item.box_pos;
if(coords.topleft.x < (itemcoords.x+201) && coords.topright.x > itemcoords.x && coords.topleft.y < (itemcoords.y+221) && coords.bottomleft.y > itemcoords.y){
if(!item.selected){
item.selected = true;
item.html.addClass('selected').removeClass('activebutton');
self.cache.selecteditems.push(i);
self.setInfo();
}
}
else{
if(item.selected){
item.selected = false;
if(!voidindex || voidindex !== i){
item.html.removeClass('selected');
}
var removeindex = self.cache.selecteditems.indexOf(i);
self.cache.selecteditems.splice(removeindex, 1);
self.setInfo();
}
}
}
},
There is lots of dirty logic in the code above which ensures that the DOM is only manipulated when the selection changes. This is not relevant to the question and can be exluded. The important part is the intersection logic which checks the coordinates of the element versus the coordinates of the marquee selection box.
Also please note that the item dimensions are fixed at 201px width by 221px height.
I have tested this and all works perfectly, however I have the need to support potentially thousands of files which would mean that at some point we will start seeing UI performance decrease.
I would like to know if there is anyway to perform intersection detection without looping through the coordinates of each element.
The coordinates of the marquee box are defined as follows at any given time:
selectioncoords : {
topleft : {
x : 0,
y : 0
},
topright : {
x : 0,
y : 0
},
bottomleft : {
x : 0,
y : 0
},
bottomright : {
x : 0,
y : 0
},
width : 0,
height : 0
}
And the coordinates of each item, stored in the self.cache.items array are defined as follows:
item : {
box_pos : {
x : 0,
y : 0
},
grid_pos : {
row : 1,
column : 1
}
}
So the information available will always be the actual grid position (row/column) as well as the physical item position (left and top offsets in pixels within the grid).
So to summarize, the question is, is there anyway to detect item intersection from a set of marquee selection box coordinates as defined above without looping through the whole array of item coordinates every time the mousemove event fires?
Thanks in advance for any help.
The following depends upon a locked grid with the dimensions as described.
You are comparing a mouse-defined rectangle against a grid with static edge sizes. Thus, given an x coordinate or a y coordinate, you should be able to derive pretty easily which column or row (respectively) the coordinate falls into.
When the user starts the select box, grab that x and y, and find the row/column of the start. When the mouse moves while pulling the select box, you find (and then update) the row/column of the finish. anything that is both within the rows defined by that box and within the columns defined by that box (inclusive) is selected. If you then keep your selectable elements in a two-dimensional array according to rows and columns, you should be able to just grab the ones you want that way.
Mind, how much more (or less) efficient this is depends on the size of your expected selection boxes as compared to the total size, and the degree to which you expect the grid to be populated. Certainly, if the average use case is selecting half or so of the objects at a time, there's not a whole lot you can do to cut down efficiently on the number of objects you have to look at each time.
Also, though it is kludgy, you can have the mousemove handler not fire every time. Letting it pause a bit between updates will reduce the responsiveness of this particular function a fair bit, but it'll cut down significantly on the amount of resources that are used.
There are several ways you could approach this. Here's one. First you need the items in some kind of organized structure that you can look up quickly by row and column. You could use a two-dimensional array, or for simplicity I'm going to use a hash table. You could do this at the same time that you create the self.cache.items, or later, something like this:
var cacheLookup = {};
function initCacheLookup() {
var items = self.cache.items;
for( var i = 0, n = items.length; i < n; i++ ) {
var item = items[i];
var key = [ item.grid_pos.row, item.grid_pos.column ].join(',');
cacheLookup[key] = item;
}
}
Then when you want to get the items intersecting the rectangle, you could do something like this:
var itemWidth = 201, itemHeight = 221;
var tl = selectioncoords.topleft, br = selectioncoords.bottomright;
var left = Math.floor( tl.x / itemWidth ) + 1;
var right = Math.floor( br.x / itemWidth ) + 1;
var top = Math.floor( tl.y / itemHeight ) + 1;
var bottom = Math.floor( br.y / itemHeight ) + 1;
var selecteditems = [];
for( var row = top; row <= bottom; row++ ) {
for( var col = left; col <= right; col++ ) {
var key = [ row, col ].join(',');
var item = cacheLookup[key];
if( item ) {
selecteditems.push( item );
}
}
}
// Now selecteditems has the items intersecting the rectangle
There's probably an off-by-one error or two here, but this should be close.
Well, as I said, that is one way to do it. And it has the possibly interesting property that it doesn't depend on the order of items in the self.cache.items array. But that cacheLookup hash table smells like it might not be the most efficient solution.
Let me take a guess: isn't that array already in the correct order by rows and columns (or vice versa)? For example, if your grid is four wide, then the top row would be array elements 0-3, the second row 4-7, the third row 8-11, etc. Or it could be a similar arrangement going down the columns.
Assuming it's in row-by-row order, then you don't need the hash table at all. That initCacheLookup() function goes away, and instead the search code looks like this:
var nCols = 4/*whatever*/; // defined somewhere else
var itemWidth = 201, itemHeight = 221;
var tl = selectioncoords.topleft, br = selectioncoords.bottomright;
var left = Math.floor( tl.x / itemWidth );
var right = Math.floor( br.x / itemWidth );
var top = Math.floor( tl.y / itemHeight ) * nCols;
var bottom = Math.floor( br.y / itemHeight ) * nCols;
var items = self.cache.items;
var selecteditems = [];
for( var iRow = top; iRow <= bottom; iRow += nCols ) {
for( var col = left; col <= right; col++ ) {
var index = iRow + col;
if( index < items.length ) {
selecteditems.push( items[index] );
}
}
}
// Now selecteditems has the items intersecting the rectangle
This code will be a little faster, and it's simpler too. Also it doesn't depend at all on the item.box_pos and item.grid_pos. You may not need those data fields at all, because they are easily calculated from the item index, grid column count, and item height and width.
Some related notes:
Don't hard code 201 and 221 in the code. Store those in variables once, only, and then use those variables when you need the item height and width.
There is a lot of duplication in your data structures. I recommend that you ruthlessly eliminate all duplicated data unless there is a specific need for it. Specifically:
selectioncoords: {
topleft: {
x: 0,
y: 0
},
topright: {
x: 0,
y: 0
},
bottomleft: {
x: 0,
y: 0
},
bottomright: {
x: 0,
y: 0
},
width: 0,
height: 0
}
More than half the data here is duplicated or can be calculated. This is all you need:
selectioncoords: {
left: 0,
right: 0,
top: 0,
bottom: 0
}
The reason I bring this up is that was a bit confusing when working on the code: "I want the left edge. Do I get that from topleft.x or bottomleft.x? Are they really the same like they seem? How do I pick?"
Also, as mentioned above, the item.box_pos and item.grid_pos may not be needed at all if the items are stored in a sequential array. If they are needed, you could store just one and calculate the other from it, since there's a direct relationship between the two:
box_pos.x === ( grid_pos.column - 1 ) * itemWidth
box_pos.y === ( grid_pos.row - 1 ) * itemHeight
You can limit the scope of your checks by indexing each item in a grid, as often as necessary and no more often. You can use the grid to give you a list of elements near an X, Y coordinate or that might be in an X1, Y2, X1, Y2 range.
To get you started ...
var Grid = function(pixelWidth, pixelHeight, boxSize) {
this.cellsIn = function(x1, y1, x2, y2) {
var rv = [];
for (var x = x1; x < x2; x += boxSize) {
for (var y = y1; y < y2; y += boxSize) {
var gx = Math.ceil(x/boxSize);
var gy = Math.ceil(y/boxSize);
rv.push(this.cells[gx][gy]);
}
}
return rv;
} // cellsIn()
this.add = function(x1, y1, x2, y2, o) {
var cells = this.cellsIn(x1, y1, x2, y2);
for (var i in cells) {
cells[i].push(o);
}
} // add()
this.get = function(x1, y1, x2, y2) {
var rv = [];
var rv_index = {};
var cells = this.cellsIn(x1, y1, x2, y2);
for (var i in cells) {
var cell = cells[i];
for (var oi in cell) {
if (!rv_index[cell[oi]]) {
rv_index[cell[oi]] = 1;
rv.push(cell[oi]);
}
}
}
return rv;
} // get()
this.cells = [];
for (var x = 0; x < Math.ceil(pixelWidth/boxSize); x++) {
this.cells[x] = [];
for (var y = 0; y < Math.ceil(pixelHeight/boxSize); y++) {
this.cells[x][y] = [];
}
}
};
So, rather than iterating through all possible objects, whatever they may be, you iterate over all the objects that are near or potentially in the given coordinates.
This requires that you maintain/re-index the grid as item coordinates change. And you'll likely want to add some functionality to the above (or similar) Grid class to modify/move existing objects. But, to the best of my knowledge, an index of this sort is the best, if not only, way to index objects "in space."
Disclaimer: The code above isn't tested. But, I have similar code that is. See the DemoGrid function class here: http://www.thepointless.com/js/ascii_monsters.js
The functionality of my DemoGrid is similar (as far as I remember, it's been awhile), but accepts x, y, radius as parameters instead. Also notable, my mouse events don't touch the grid every time the event fires. Checks are rate-limited by a game/main loop.
If the system is set up such that
self.cache.items is ordered from left to right and top to bottom
(0,0),(1,0),(2,0),(0,1),(1,1),(1,2),(0,2),(1,2),(2,2)
There is an item in each space
GOOD - (0,0),(1,0),(2,0),(0,1),(1,1),(1,2),(0,2),(1,2),(2,2)
BAD - (0,0),(2,0)(1,2),(1,3),(2,1),(2,3)
We need to know the total number of columns.
So the code to get you started.
// Some 'constants' we'll need.
number_of_columns = 4;
item_width = 201;
item_height = 221;
// First off, we are dealing with a grid system,
// so that means that if given the starting x and y of the marquee,
// we can determine which element in the cache to start where we begin.
top_left_selected_index = Math.floor(selectioncoords.topleft.x / item_width) + (Math.floor(selectioncoords.topright.y / item_height) * number_of_columns );
// Now, because the array is in order, and there are no empty cache points,
// we know that the lower bound of the selected items is `top_left_selected_index`
// so all we have to do is walk the array to grab the other selected.
number_columns_selected = (selectioncoords.bottomright.x - selectioncoords.topleft.x) / item_width;
// if it it doesn't divide exactly it means there is an extra column selected
if((selectioncoords.bottomright.x - selectioncoords.topleft.x) % item_width > 0){
number_columns_selected += 1;
}
// if it it doesn't divide exactly it means there is an extra column selected
number_rows_selected = (selectioncoords.bottomright.y - selectioncoords.topleft.y) / item_height;
if((selectioncoords.bottomright.y - selectioncoords.topleft.y) % item_height > 0){
number_rows_selected += 1;
}
// Outer loop handles the moving the pointer in terms of the row, so it
// increments by the number of columns.
// EX: Given my simple example array, To get from (1,0) to (1,1)
// requires an index increase of 3
for(i=0; i < number_rows_selected; i++){
// Inner loop marches through the the columns, so it is just one at a time.
// Added j < number_of_columns in case your marquee stretches well past your content
for(j=0; j < number_columns_selected && j < number_of_columns; j++){
// Do stuff to the selected items.
self.cache.items[top_left_selected_index + (i * number_of_columns) + j];
}
}

Categories

Resources