How to create a data model for a boat? - javascript

How would one create a model for a boat in javascript that exists as a grid reference in a cartesian plane?
I would like to learn javascript by creating clone of the popular game Battleship!
To this end I need assistance in my quest to start programming boats!

Here's something to get you started:
function Boat(name, length) {
this.name = name
this.pegs = new Array(length)
this.sunk = false
}
Boat.prototype.place = function (x, y, orientation) {
// Before calling this method you'd need to confirm
// that the position is legal (on the board and not
// conflicting with the placement of existing ships).
// `x` and `y` should reflect the coordinates of the
// upper-leftmost peg position.
for (var idx = 0, len = this.pegs.length; idx < len; idx++) {
this.pegs[idx] = {x: x, y: y, hit: false}
if (orientation == 'horizontal') x += 1
else y += 1
}
}
Boat.prototype.hit = function (x, y) {
var sunk = true
var idx = this.pegs.length
while (idx--) {
var peg = this.pegs[idx]
if (peg.x == x && peg.y == y) peg.hit = true
// If a peg has not been hit, the boat is not yet sunk!
if (!peg.hit) sunk = false
}
return this.sunk = sunk // this is assignment, not comparison
}
Usage:
var submarine = new Boat('submarine', 3)
submarine.place(2, 6, 'horizontal')
submarine.hit(2, 6) // false
submarine.hit(3, 6) // false
submarine.hit(4, 6) // true
Storing pegs as objects with x, y, and hit keys is not necessarily the best approach. If you wanted to be clever you could, for example, store the upper-leftmost coordinates on the object along with the orientation. Then, the hits could be stored in an array. Something like:
name: 'submarine'
x: 2
y: 6
orientation: 'horizontal'
pegs: [0, 0, 0]
After a hit at (2, 6), the boat's properties would be:
name: 'submarine'
x: 2
y: 6
orientation: 'horizontal'
pegs: [1, 0, 0]

I'd start off by creating an array (or two, one for each side) to hold the boats. This can be pretty simple, and just use the boat number as the array entry for "filled" positions.
My boat model would have a length (n "pegs"), a position (x, y), an orientation (vertical or horizontal), and a hit counter. Another option would be to just store each array position the boat occupies, which would make some stuff a little easier.

Related

Unexpected indexOf behavior with multidimensional array

I am attempting to make the classic snake game in p5.js. I have a snake object and I'm storing the locations of its body in a 2d array, this.data, where each element stores an x value and a y value (at index 0 and 1 respectively). As the snake moves, I push new positions into the array.
I ran into a problem when I tried to detect whether or not the snake had run into itself. What I tried to do was test whether its current position was already in the array using indexOf, reasoning that if the new location was open, it would only occur once in the array, at index one less than the array's length. Otherwise, if the location already existed elsewhere in the array (indicating the snake had run into itself), it would return a value less than the length minus one.
However, this doesn't seem to be happening.
function Snake()
{
this.x; //x-coordinate of head
this.y; //y-coordinate of head
this.dx; //velocity in x-direction
this.dy; //velocity in y-direction
this.length; //length of snake
this.data; //stores all locations snake occupies
this.alive = 1; //is the snake alive?
this.update = function(board)
{
if (this.alive)//update head position
{
this.x += this.dx;
this.y += this.dy;
let tempCoords = [this.x,this.y];
this.data.push(tempCoords);
while (this.data.length > this.length) //janky
{
this.data = this.data.slice(1);
}
if (this.data.indexOf(tempCoords) + 1 != this.data.length) //make sure snake hasn't hit itself
{
this.alive = 0;
}
}
}
}
The final if statement always evaluates false even when the snake intersects itself. From the testing I've done, this seems to be an issue with using indexOf on multidimensional arrays. What solutions are there to this problem?
Essentially, you have the following data and you want to see if the data in head is equal to any of the elements in points
var points = [[1,1],[1,2],[1,3]]
var head = [1,2]
You can check for any matches in an array using Array.some() like this:
var overlap = points.some(p => p[0] === head[0] && p[1] === head[1])
var points = [[1,1],[1,2],[1,3]]
var head = [1,2]
var overlap = points.some(p => p[0] === head[0] && p[1] === head[1])
console.log(overlap)
indexOf uses an equality check to find the index, and
[0, 0] === [0, 0]
is false, as objects (and arrays are objects), are compared by reference (and you do have two different arrays). To compare them by their inner values you have to manually check the x and ys against each other:
const collides = this.data.some(coords => coords[0] === this.x && coords[1] === this.y);

basic 2d gaming question involving arrays and json javascript

Now that the format of this game is established let's start writing some functionality one small piece at a time. For this part, write a function named "movePlayer" that takes as parameters a JSON string representing a level in the format above and a string representing the first movement of the player. The movement string will be either "w", "a", "s", or "d" based on the direction the player intends to move with "w" for up, "a" for left, "s" for down, and "d" for right. Return the new player location as an array in the format [x, y]. Remember to check for walls, locked doors, and the exit as the player cannot move through these (you can assume the player has no keys and does not have all the crystals). You may assume that the player did not walk into a lava tile. If the player walked into the edge of the map it should be treated as a wall (Ex. walking to the right while already on the right edge of the map shouldn't move the player)
function movePlayer(json_object, f){
var level = JSON.parse(json_object);
var grid = level["grid"];
var pos = level["start"];
var x = pos[0];
var y = pos[1];
if (f=="w"){
y = y - 1;
}
else if (f=="s"){
y = y + 1;
}
else if (f=="a"){
x = x - 1;
}
else if(f=="d"){
x = x + 1;
}
if (grid[y][x] == 1 || grid[y][x] == 3 || grid[y][x] == 7){
pos = level["start"];
}
else if (grid.includes(grid[y][x])){
pos = [x, y];
}
else{
pos = level["start"]
}
return pos;
}
function movePlayer incorrect on input ['{"start": [2, 1], "grid": [[1, 1, 0, 1], [0, 3, 0, 7], [1, 0, 0, 1], [0, 1, 0, 0]]}', 'w']
returned: [2, 1]
expected: [2, 0]
I'm so confused and don't know how to fix this, if anyone could help I'd greatly appreciate it.
You get [2,0], but then fall into checks and last else statement returns you to [2,1], which is your start.
I think you are mistaken in grid.includes(grid[y][x]) condition, it is always false, because grid is array of arrays and you are checking it for exact integer value.

Isometric topological sort issue

I've just implemented a topological sort algorithm on my isometric game using this guide: https://mazebert.com/2013/04/18/isometric-depth-sorting/
The issue
Here's a little example (this is just a drawing to illustrate my problem because as we say, a picture is worth a thousand words), what I'm expecting is in left and the result of the topological sorting algorithm is in right
So in the right image, the problem is that the box is drawn BEFORE the character and I'm expecting it to be drawn AFTER like in the left image.
Code of the topological sorting algorithm (Typescript)
private TopologicalSort2() {
// https://mazebert.com/2013/04/18/isometric-depth-sorting/
for(var i = 0; i < this.Stage.children.length; i++) {
var a = this.Stage.children[i];
var behindIndex = 0;
for(var j = 0; j < this.Stage.children.length; j++) {
if(i == j) {
continue;
}
var b = this.Stage.children[j];
if(!a.isoSpritesBehind) {
a.isoSpritesBehind = [];
}
if(!b.isoSpritesBehind) {
b.isoSpritesBehind = [];
}
if(b.posX < a.posX + a.sizeX && b.posY < a.posY + a.sizeY && b.posZ < a.posZ + a.sizeZ) {
a.isoSpritesBehind[behindIndex++] = b;
}
}
a.isoVisitedFlag = 0;
}
var _sortDepth = 0;
for(var i = 0; i < this.Stage.children.length; ++i) {
visitNode(this.Stage.children[i]);
}
function visitNode(n: PIXI.DisplayObject) {
if(n.isoVisitedFlag == 0) {
n.isoVisitedFlag = 1;
if(!n.isoSpritesBehind) {
return;
}
for(var i = 0; i < n.isoSpritesBehind.length; i++) {
if(n.isoSpritesBehind[i] == null) {
break;
} else {
visitNode(n.isoSpritesBehind[i]);
n.isoSpritesBehind[i] = null;
}
}
n.isoDepth = _sortDepth++;
}
}
this.Stage.children.sort((a, b) => {
if(a.isoDepth - b.isoDepth != 0) {
return a.isoDepth - b.isoDepth;
}
return 0;
});
}
Informations
Player:
posX: [the x coordinate of the player]
posY: [the y coordinate of the player]
posZ: 0
sizeX: 1
sizeY: 1
sizeZ: 1
Box:
posX: [the x coordinate of the box]
posY: [the y coordinate of the box]
posZ: 0
sizeX: 3
sizeY: 1
sizeZ: 1
X and Y axis
Do you have any idea of the source of this problem? and maybe how to solve it?
The way to determine whether one object is before the other requires a bit more linear algebra.
First of all, I would suggest to translate the coordinates from the "world" coordinates to the "view" 2D coordinates, i.e. to the rows and columns of the display.
Note also that the original Z coordinate does not influence the sort order (imagine that an object would be lifted up along the Z axis: we can find a sort order where this move would not have any impact). So the above-mentioned translation could assume all points are at Z=0.
Let's take this set-up, but depicted from "above", so when looking along the Z axis down to the game floor:
In the picture there are 7 objects, numbered from 0 to 6. The line of view in the game would be from the bottom-left of this picture. The coordinate system in which I would suggest to translate some points is depicted with the red row/col axis.
The white diagonals in each object link the two points that would be translated and used in the algorithm. The assumption is that when one object is in front of another, their diagonal lines will not intersect. If they would, it would mean that objects are overlapping each other in the game world, which would mean they are like gasses, not solids :) I will assume this is not the case.
One object A could be in front of another object B when in the new coordinate system, the left-most column coordinate of B falls between the two column coordinates of A (or vice versa). There might not really be such an overlap when their Z coordinates differ enough, but we can ignore that, because when there is no overlap we can do no harm in specifying a certain order anyway.
Now, when the coordinates indicate an overlap, the coordinates of diagonals (of A and B) must be compared with some linear algebra formula, which will determine which one is in front of the other.
Here is your adapted function that does that:
topologicalSort() {
// Exit if sorting is a non-operation
if (this.Stage.children.length < 2) return;
// Add two translated coordinates, where each of the resulting
// coordinates has a row (top to bottom) and column
// (left to right) part. They represent a position in the final
// rendered view (the screen).
// The two pairs of coordinates are translations of the
// points (posX + sizeX, Y, 0) and (posX, posY + sizeY, 0).
// Z is ignored (0), since it does not influence the order.
for (let obj of this.Stage.children) {
obj.leftCol = obj.posY - obj.posX - obj.sizeX;
obj.rightCol = obj.posY - obj.posX + obj.sizeY;
obj.leftRow = obj.posY + obj.posX + obj.sizeX;
obj.rightRow = obj.posY + obj.posX + obj.sizeY;
obj.isoSpritesBehind = [];
}
for(let i = 0; i < this.Stage.children.length; i++) {
let a = this.Stage.children[i];
// Only loop over the next objects
for(let j = i + 1; j < this.Stage.children.length; j++) {
let b = this.Stage.children[j];
// Get the two objects in order of left column:
let c = b.leftCol < a.leftCol ? b : a;
let d = b.leftCol < a.leftCol ? a : b;
// See if they overlap in the view (ignoring Z):
if (d.leftCol < c.rightCol) {
// Determine which is behind: some linear algebra
if (d.leftRow <
(d.leftCol - c.leftCol)/(c.rightCol - c.leftCol)
* (c.rightRow - c.leftRow) + c.leftRow) {
// c is in front of d
c.isoSpritesBehind.push(d);
} else { // d is in front of c
d.isoSpritesBehind.push(c);
}
} // in the else-case it does not matter which one comes first
}
}
// This replaces your visitNode function and call:
this.Stage.children.forEach(function getDepth(obj) {
// If depth was already assigned, this node was already visited
if (!obj.isoDepth) {
// Get depths recursively, and retain the maximum of those.
// Add one more to get the depth for the current object
obj.isoDepth = obj.isoSpritesBehind.length
? 1+Math.max(...obj.isoSpritesBehind.map(getDepth))
: 1; // Depth when there is nothing behind it
}
return obj.isoDepth; // Return it for easier recursion
});
// Sort like you did, but in shorter syntax
this.Stage.children.sort((a, b) => a.isoDepth - b.isoDepth);
}
I add a snippet where I completed the class with a minimum of code, enough to make it run and output the final order in terms of object index numbers (as they were originally inserted):
class Game {
constructor() {
this.Stage = { children: [] };
}
addObject(posX, posY, posZ, sizeX, sizeY, sizeZ) {
this.Stage.children.push({posX, posY, posZ, sizeX, sizeY, sizeZ,
id: this.Stage.children.length}); // add a unique id
}
topologicalSort() {
// Exit if sorting is a non-operation
if (this.Stage.children.length < 2) return;
// Add two translated coordinates, where each of the resulting
// coordinates has a row (top to bottom) and column
// (left to right) part. They represent a position in the final
// rendered view (the screen).
// The two pairs of coordinates are translations of the
// points (posX + sizeX, Y, 0) and (posX, posY + sizeY, 0).
// Z is ignored (0), since it does not influence the order.
for (let obj of this.Stage.children) {
obj.leftCol = obj.posY - obj.posX - obj.sizeX;
obj.rightCol = obj.posY - obj.posX + obj.sizeY;
obj.leftRow = obj.posY + obj.posX + obj.sizeX;
obj.rightRow = obj.posY + obj.posX + obj.sizeY;
obj.isoSpritesBehind = [];
}
for(let i = 0; i < this.Stage.children.length; i++) {
let a = this.Stage.children[i];
// Only loop over the next objects
for(let j = i + 1; j < this.Stage.children.length; j++) {
let b = this.Stage.children[j];
// Get the two objects in order of left column:
let c = b.leftCol < a.leftCol ? b : a;
let d = b.leftCol < a.leftCol ? a : b;
// See if they overlap in the view (ignoring Z):
if (d.leftCol < c.rightCol) {
// Determine which is behind: some linear algebra
if (d.leftRow <
(d.leftCol - c.leftCol)/(c.rightCol - c.leftCol)
* (c.rightRow - c.leftRow) + c.leftRow) {
// c is in front of d
c.isoSpritesBehind.push(d);
} else { // d is in front of c
d.isoSpritesBehind.push(c);
}
} // in the else-case it does not matter which one comes first
}
}
// This replaces your visitNode function and call:
this.Stage.children.forEach(function getDepth(obj) {
// If depth was already assigned, this node was already visited
if (!obj.isoDepth) {
// Get depths recursively, and retain the maximum of those.
// Add one more to get the depth for the current object
obj.isoDepth = obj.isoSpritesBehind.length
? 1+Math.max(...obj.isoSpritesBehind.map(getDepth))
: 1; // Depth when there is nothing behind it
}
return obj.isoDepth; // Return it for easier recursion
});
// Sort like you did, but in shorter syntax
this.Stage.children.sort((a, b) => a.isoDepth - b.isoDepth);
}
toString() { // Just print the ids of the children
return JSON.stringify(this.Stage.children.map( x => x.id ));
}
}
const game = new Game();
game.addObject( 2, 2, 0, 1, 1, 1 );
game.addObject( 1, 3, 0, 3, 1, 1 );
game.addObject( 6, 1, 0, 1, 3, 1 );
game.addObject( 9, 3, 0, 1, 1, 1 );
game.addObject( 5, 3, 0, 1, 3, 1 );
game.addObject( 7, 2, 0, 1, 1, 1 );
game.addObject( 8, 2, 0, 3, 1, 1 );
game.topologicalSort();
console.log(game + '');
The objects in the snippet are the same as in the picture with the same numbers. The output order is [0,1,4,2,5,6,3] which is the valid sequence for drawing the objects.

How to correctly detect neighbour cells

As a learning project, I'm writing a simple logical game where the user has html canvas grid where he needs to connect same-colored squares.
I'm stuck at detecting neighbour cell's properties(its colour) and if condition is matched (neighbour colour is different from mine), the cell shouldn't be filled.
My question are:
1) Is there a better way to check if target square is viable for colour-filling?
2) If there is, how do I handle clicks on new cells correctly?
function checkNeighbourTiles(aX, aY) {
var coords = [
[(aX - 1), (aY - 1)],
[(aX - 1), (aY)],
[(aX - 1), (aY + 1)],
[(aX), (aY - 1)],
[(aX), (aY + 1)],
[(aX + 1), (aY - 1)],
[(aX + 1), (aY - 1)],
[(aX + 1), (aY + 1)]
]
for (i = 0; i < coords.length; i++) {
var x = coords[i][0]
var y = coords[i][1]
var b = cells[x][y]
}
}
My code so far - jsfiddle
It depends on the complexity and performance constraints. As you have done a direct lookup table is about as efficient as can be for a simple grid lookup, though instead of creating the lookup array each time just create an offsets array once.
// an array of offset coordinates in pairs x,y 8 pairs skipping the center
const NEIGHBOURS = [-1, -1, 0, -1, 1, -1, -1, 0, 1, 0, -1, 1, 0, 1, 1, 1];
const GIRD_SIZE = 10; // 10 by ten grid
function checkNeighbourTiles(x,y){
var lx, ly, cellResult;
var i = 0;
while(i < 16){ // check for each offset
lx = x + NEIGHBOURS[i++]; // get the x offset
ly = y + NEIGHBOURS[i++]; // get the y offset
// ensure you are inside the grid
if( ly >= 0 && ly < GRID_SIZE && lx >= 0 && lx < GRID_SIZE ){
cellResult = cell[lx][ly];
// do what is needed with the result;
}
}
}
For the type of 2D array that is about simplest way to do it.
The alternative is a linked array were each cell holds and array of references to the neighbouring cells.
Thus (and with simplicity in mind) just the top left right and bottom. Then each cell would look like
cell = {
top : undefined,
left : undefined,
right : undefined,
bottom : undefined,
... other data
}
Then when you add the cell you set the references to the appropriate cells
// first add all the cells to the array
// then for each cell call this
function AddCell(cell,x,y){
cell.top = cells[x][y-1];
cell.left = cells[x-1][y];
cell.right = cells[x+1][y];
cell.bottom = cells[x][y+1];
// also the cells you just reference should also reference back
// where top refs botton and left refs right and so fourth.
cells.top.bottom = cell;
cells.bottom.top = cell;
cells.left.right = cell;
cells.right.left = cell;
}
Then at any point if you want to find which cell is above
//x and y are the cell
var cellAbove = cell[x][y].top;
This method has many advantages when you start getting complex linking, like dead cells, or skipping cells, or even inserting cells so that you change the topology of the grid.
You can also do complex searches like two left one down
resultCall = cell[x][y].left.left.bottom; // returns the cell two left one down
But it is a pain to maintain the links as there is a lot of extra code involved so for a simple 2D grid your method is the best.
A cleaner way for selecting squares might be using loops:
function (x, y){
var coords = [];
for(i = (x-1); i < (x+2); i++){
for(j = (y-1); j < j (y+2); j++){
coords.push([i, j]);
}
}
//rest of the code
}
If you check the square at [0, 0], this code will search for a square at [-1, -1]. You're gonna need some serious if statements for filtering out the proper squares.
Likewise, if you have a 9x9 grid and you search for [8, 8], the code will look for the [9, 9] square eventually and go out of bounds.

My function doesn't not return object in a for loop

I created a function which takes the inner of the tag S in a XML string:
'<C><P /><Z><S>[Grounds here]</S><D /><O /></Z></C>'
, containing grounds data, obviously related to a game. Then I consume every ground data in this inner until I reach the number z with i in my for loop and return the data of the first/one remaining ground as a object.
The problem: the function returns undefined instead of a object.
This is the function:
/**
* Get a string between 2 strings.
*/
String.prototype.between = function(left, right) {
var sub = this.substr(this.lastIndexOf(left) + left.length);
return sub.substr(0, sub.indexOf(right));
}
/**
* #param {Number} z The inner position of the ground I want to read, e.g, 1, the first.
*/
function readGround(z) {
// string containing all existent grounds
var groundsData = xml.substr(STG.indexOf('<S>') + 3, STG.lastIndexOf('</S>'));
// Iterate the grounds while z isn't reached
for(var i = 1; i < z; i++) {
// Get the ground inner
var outer = groundsData.substr(groundsData.indexOf('<S') + 3, groundsData.indexOf('/>'));
// Check if i reached z
if(i === z) {
// Get grounds properties
var a = [
outer.between('L="', '"'),
outer.between('H="', '"'),
outer.between('X="', '"'),
outer.between('Y="', '"')
];
return {
L: a[0], H: a[1],
X: a[2], Y: a[3]
};
// Else skip this ground
} else groundsData = groundsData.substr(groundsData.indexOf('/>'), groundsData.length);
}
}
Your loop makes i go from 1 to num-1. But within the loop you have a condition if(i==num). This condition is never true, so the programme never reaches the return statement. If the programme flow inside a function means that no return statement is ever reached, then the function simply returns undefined. (This isn't javascript-specific - similar rules apply in many languages.)
Instead, you can move the return statement outside of the loop.
function readGround(num) {
var grounds = stg.substr(stg.indexOf('<S>') + 3, stg.lastIndexOf('</S>')),
gr;
for (var i = 1; i < num; i++) {
grounds = grounds.substr(grounds.indexOf('/>') + 2, grounds.length);
}
gr = grounds.substr(grounds.indexOf('<S') + 3, grounds.indexOf('/>'));
var a = [stringBetween(gr, 'L="', '"'), stringBetween(gr, 'H="', '"'), stringBetween(gr, 'X="', '"'), stringBetween(gr, 'Y="', '"')];
return {
L: a[0],
H: a[1],
X: a[2],
Y: a[3]
};
}
(A few other things also had to be tweaked to make your code work, such as that a should read from the section of text in gr rather than from the longer string grounds)
jsfiddle

Categories

Resources