My A* pathfinding function always gets to its intended destination, but it almost always goes a bit out of the way. Here's an example:
[I made a nice image to show my issue, but apparently it won't post until my reputation reaches 10; sorry, I'm new. :P]
Essentially, it pulls left or up as much as possible without actually adding more tiles to the path. It sounds like an issue with calculating the gScore or possibly the part where a tile's parent can be reassigned based on neighboring tiles' gScores, but I just can't figure out where it's going wrong. I've combed over my code for weeks and browsed dozens of online posts, but I'm still stuck. Fyi, the compiler/debugger I have to use doesn't support breakpoints or step-through debugging, so I'm stuck with simple text output. Can anyone spot what I'm doing wrong?
Here's the primary function (Note: this is all in Angelscript. It's based on C++, but there are small differences):
int CARDINAL_COST = 10;
int DIAGONAL_COST = 14;
array<vector2> findPath(vector2 startPosition, vector2 endPosition)
{
//Translate the start and end positions into grid coordinates
startPosition = _level.getTileGridPosition(startPosition);
endPosition = _level.getTileGridPosition(endPosition);
//The path to be returned
array<vector2> path(0);
//Create the closed
array<vector2> closedSet(0);
//Create the open set. These are nodes to be considered.
array<vector2> openSet(0);
//Add the startPosition to the open set.
openSet.insertLast(startPosition);
//Create the cameFrom (path) array. Each entry hods that tile's parent tile.
array<array<vector2>> cameFrom;
cameFrom = array<array<vector2>>(_level.width(), array<vector2>(_level.height()));
//Create the gScore array. gScore is the cost to get from the start to the current tile.
array<array<int>> gScore;
gScore = array<array<int>>(_level.width(), array<int>(_level.height()));
//Set the start position score to 0
gScore[startPosition.x][startPosition.y] = 0;
//Create the fScore array. fScore is the gScore + heuristic cost.
array<array<int>> fScore;
fScore = array<array<int>>(_level.width(), array<int>(_level.height()));
//Set the start position score to the estimated (heuristic) cost.
//gScore for start is 0, so that's not included in the equation.
fScore[startPosition.x][startPosition.y] = getHeuristicCost(startPosition, endPosition);
//Required variables
bool searchComplete = false;
vector2 currentTile = startPosition;
int x = 0;
int y = 0;
string tileType = "";
vector2 nextTile(0,0);
vector2 neighborTile(0,0);
int lowestScore = 0;
int tempScore = 0;
int index = 0;
while(!searchComplete)
{
//Find the tile in the openSet with the lowest fScore.
lowestScore = fScore[openSet[0].x][openSet[0].y];
neighborTile = openSet[0];//May not actually be a "neighbor" in this case, just looking for the lowest fScore.
for(int i = 0; i < openSet.length(); i++)
{
if(fScore[neighborTile.x][neighborTile.y] < lowestScore || i == 0)
{
lowestScore = fScore[neighborTile.x][neighborTile.y];
nextTile.x = neighborTile.x;
nextTile.y = neighborTile.y;
}
}
//Drop the "nextTile" from the openSet and add it to the closedSet
index = openSet.find(nextTile);
openSet.removeAt(openSet.find(nextTile));
closedSet.insertLast(nextTile);
//Set the currentTile
currentTile = nextTile;
//Get the fScore for each neighboring tile
for(x = currentTile.x - 1; x <= currentTile.x + 1; x++)
{
for(y = currentTile.y - 1; y <= currentTile.y + 1; y++)
{
//Safety: make sure x and y aren't out of bounds
if(x < 0)
x = 0;
else if(x > _level.width())
x = _level.width();
if(y < 0)
y = 0;
else if (y > _level.height())
y = _level.height();
//Set this x,y pair to be the neighborTile
neighborTile.x = x;
neighborTile.y = y;
//Get the tile type
if(_level.tileArray()[neighborTile.x][neighborTile.y] != null)
tileType = _level.tileArray()[neighborTile.x][neighborTile.y].GetString("type");
else
tileType = "";
//Make sure we aren't looking at the current tile, the tile is not closed, and the tile is a floor or door.
if(neighborTile != currentTile && closedSet.find(neighborTile) == -1 && (tileType == "floor" || tileType == "door"))
{
//If the neighboring tile is already in the open set, check to see if the currentTile's gScore would be less if that tile was its parent.
//If it is, set the it as the currentTile's parent and reset the fScore and gScore for it.
if(openSet.find(neighborTile) != -1)
{
if(gScore[neighborTile.x][neighborTile.y] < gScore[cameFrom[currentTile.x][currentTile.y].x][cameFrom[currentTile.x][currentTile.y].y])
{
cameFrom[currentTile.x][currentTile.y] = neighborTile;
//If the tile is a diagonal move
if(neighborTile.x - currentTile.x != 0 && neighborTile.y - currentTile.y != 0)
gScore[currentTile.x][currentTile.y] = gScore[neighborTile.x][neighborTile.y] + DIAGONAL_COST;
else//If the tile is a cardinal (N,S,E,W) move
gScore[currentTile.x][currentTile.y] = gScore[neighborTile.x][neighborTile.y] + CARDINAL_COST;
fScore[currentTile.x][currentTile.y] = gScore[currentTile.x][currentTile.y] + getHeuristicCost(currentTile, endPosition);
}
}
else//Add this tile to the open set
{
openSet.insertLast(neighborTile);
//Record this tile's parent
cameFrom[neighborTile.x][neighborTile.y] = currentTile;
//If the tile is a diagonal move
if(neighborTile.x - currentTile.x != 0 && neighborTile.y - currentTile.y != 0)
gScore[neighborTile.x][neighborTile.y] = gScore[currentTile.x][currentTile.y] + DIAGONAL_COST;
else//If the tile is a cardinal (N,S,E,W) move
gScore[neighborTile.x][neighborTile.y] = gScore[currentTile.x][currentTile.y] + CARDINAL_COST;
//Get the fScore for this tile
fScore[neighborTile.x][neighborTile.y] = gScore[neighborTile.x][neighborTile.y] + getHeuristicCost(neighborTile, endPosition);
}
}
}
}
//Check to see if we have arrived at the endTile
if(currentTile == endPosition)
{
searchComplete = true;
path = reconstructPath(cameFrom, startPosition, endPosition);
}
else
{
//Check to see if the openSet is empty
if(openSet.length() == 0)
searchComplete = true;
}
}//while(!searchComplete)
return path;
}
My heuristic uses the Manhattan method:
int getHeuristicCost(vector2 startPosition, vector2 endPosition)
{
//Using Manhattan method:
int x = abs(startPosition.x - endPosition.x)*10;
int y = abs(startPosition.y - endPosition.y)*10;
return x+y;
}
And finally, here's my path reconstructing function:
array<vector2> reconstructPath(array<array<vector2>> &in cameFrom, vector2 &in startPosition, vector2 &in endPosition)
{
//Start by adding in the end position
array<vector2> totalPath(1);
vector2 currentTile = endPosition;
totalPath[0] = endPosition;
int x = endPosition.x;
int y = endPosition.y;
int angle = 0;
while(vector2(x, y) != startPosition)
{
currentTile = cameFrom[x][y];
totalPath.insertAt(0,currentTile);
x = currentTile.x;
y = currentTile.y;
}
return totalPath;
}
for(int i = 0; i < openSet.length(); i++)
{
if(fScore[neighborTile.x][neighborTile.y] < lowestScore || i == 0)
{
lowestScore = fScore[neighborTile.x][neighborTile.y];
nextTile.x = neighborTile.x;
nextTile.y = neighborTile.y;
}
}
This loop just looks at neighborTile over and over. Did you mean to go over the elements of openSet?
Related
I'm building Space Invaders in C++ (using the MBed platform) for a microcontroller. I've used a 2D Vector of object pointers to organise the invaders.
The movement algorithm is below, and runs in the main while loop for the game. Basically, I get the highest/lowest x and y values of invaders in the vector, and use those to set bounds based on screensize (the HEIGHT variable);
I also get the first invader's position, velocity, and width, which I apply changes to based on the bounds above.
Then I iterate through the whole vector again and apply all those changes. It sort of works – the invaders move – but the bounds don't seem to take effect, and so they fly off screen. I feel like I'm missing something really dumb, thanks in advance!
void Army::move_army() {
int maxy = HEIGHT - 20;
int Ymost = 0; // BOTTOM
int Yleast = 100; // TOP
int Xmost = 0; // LEFT
int Xleast = 100; // RIGHT
int first_row = _rows;
int first_column = _columns;
int firstWidth = 0;
Vector2D firstPos;
Vector2D firstVel;
for (int i = 0; i < _rows; i++) {
for (int n = 0; n < _columns; n++) {
bool state = invaders[i][n]->get_death();
if (!state) {
if (i < first_row && n < first_column) {
firstPos = invaders[i][n]->get_pos();
firstVel = invaders[i][n]->get_velocity();
firstWidth = invaders[i][n]->get_width();
}
Vector2D pos = invaders[i][n]->get_pos();
if (pos.y > Ymost) {Ymost = pos.y;} // BOTTOM
else if (pos.y < Yleast) {Yleast = pos.y;} // TOP
else if (pos.x > Xmost) {Xmost = pos.x;} // LEFT
else if (pos.x < Xleast) {Xleast = pos.x;} // RIGHT
}
}
}
firstVel.y = 0;
if (Xmost >= (WIDTH - 8) || Xleast <= 2) {
firstVel.x = -firstVel.x;
firstPos.y += _inc;
// reverse x velocity
// increment y position
}
else if (Ymost > maxy) {
_inc = -_inc;
// reverse increment
}
else if (Yleast < 2) {
_inc = -_inc;
// reverse increment
}
for (int i = 0; i < _rows; i++) {
int setx = firstPos.x;
if (i > 0) {firstPos.y += 9;}
for (int n = 0; n < _columns; n++) {
invaders[i][n]->set_velocity(firstVel);
invaders[i][n]->set_pos(setx,firstPos.y);
setx += firstWidth + 2;
}
}
It looks like you have your assignment cases reversed. Assignment always goes: right <- left, so in the first case you're changing the YMost value, not pos.y. It looks like if you swap those four assignments in your bounds checking it should work. Good luck!
I don't work with tiles but cubes drawn with sf::Vertex. Each cubes have 6 sides with 4 points each.
So i just have to cubes[numCube].sides()[numSide].... to select a side.
I create cubes layer.cpp :
for(int J = 0; J < mapSize; J++)
{
for(int I = 0; I < mapSize; I++)
{
x = (J - I) * (cubeSize/2);
y = (J + I) * (cubeSize/4);
c = new cube(cubeSize, x, y, z, I, J);
cs.push_back(*c);
}
}
In cube.cpp i create sides, then, in sides.cpp, i calcul each points' coordinates like this :
switch(typeSide)
{
case 0://DOWN_SIDE
light = 1;
tmp_x = x + (size/2);
tmp_y = y + (size/2);
p0 = new point(tmp_x, tmp_y, tmp_z);
tmp_x = x + size;
tmp_y = y + (3 * (size/4));
p1 = new point(tmp_x, tmp_y, tmp_z);
tmp_x = x + (size/2);
tmp_y = y + size;
p2 = new point(tmp_x, tmp_y, tmp_z);
tmp_x = x;
tmp_y = y + (3 * (size/4));
p3 = new point(tmp_x, tmp_y, tmp_z);
break;
case 1://BACK_LEFT_SIDE
//ETC. ....
Point.cpp :
/*
* point.cpp
*
* Created on: 21 nov. 2015
* Author: user
*/
#include "point.h"
point::point(float tx, float ty, float tz)
{
coords* dummyVar = new coords(tx, ty, tz);
coordinates = dummyVar;
}
std::vector<float> point::position()//Use : myPoint.getPosition[0] //get the x
{
std::vector<float> dummyVar;
dummyVar.push_back(coordinates->getX());
dummyVar.push_back(coordinates->getY() - coordinates->getZ());
return dummyVar;
}
void point::move(float tx, float ty, float tz)
{
coordinates->setX(tx);
coordinates->setY(ty);
coordinates->setZ(tz);
}
My problem come from the function i use to detect click :
if (event.type == sf::Event::MouseMoved)
{
currentSelectedCube = maps[currentMapID].getCubeIDAt(event.mouseMove.x, event.mouseMove.y, offsetLeft, offsetTop, enableOffset);
}
The function(don't bother with the comments) :
I try to get a cube's entry in my cube vector without 'for loop'.
Why ? to use less CPU when i click.
int map::getCubeIDAt(float x, float y, int offsetLeft, int offsetTop, bool enableOffset)//WIP ! //USED FOR CLICK DETECTION ON CUBES
{
//----------------------------------------------------------------//
int unsigned entry = -1;
int I = 0;
int J = 0;
//----------------------------------------------------------------//
if(currentLayerId() > -1)//If there is any layers
{
//IF CHECK IN MAP BOUDING BOX + ROTATION TO GOT DIAMOND SHAPE AREA(LAYER + OFFSETS)----------------------------------
//{
if(!enableOffset)//With offsets disabled
{
I = (y * 2 - x) / cubeSize;
J = (y * 2 + x) / cubeSize;
}
else //With offsets enabled
{
I = (((y-offsetTop)+(currentLayerId()*(cubeSize/2))) * 2 - (x-offsetLeft)) / cubeSize;
J = (((y-offsetTop)+(currentLayerId()*(cubeSize/2))) * 2 + (x-offsetLeft)) / cubeSize;
}
entry = I + J * size;
if (entry < 0 || entry >= layers()[currentLayerId()].cubes().size())
{
entry = -1;
}
else//DEBUG - DISPLAYING VALUES FOR TEST
{
std::cout << "Entry n°" << entry << " - ";
std::cout << "[" << I << "; " << J << "]" << std::endl;
}
//}
//END IF CHECK IN MAP BOUDING BOX + ROTATION TO GOT DIAMOND SHAPE AREA(LAYER + OFFSETS)----------------------------------
}
return entry;
}
The I-J and entryNumber are OK. i mean, for example, for the cube 0, i have I = 0; J = 0; etc ... This is working.
I don't understand why the coordinate range is like the red part(not accurate at 100%, i'm not a paint genius ha ha) in this picture :
But i should get that(2nd picture - the red part is where i click) :
But after few checks, the I-J and the entry i got are corresponding. This is so weird.
EDIT2:
Offsets and layer number implemented.
Problem left: wrong coordinates range.
Just in case, this is the 'function' handling events :
void GRAPHICS_HANDLER::listenEvents()
{
while (window->pollEvent(event))
{
if (event.type == sf::Event::Closed)
{
window->close();
}
if(event.type == sf::Event::KeyPressed)
{
//DISPLAY/UNDISPLAY GRID -- DEBUG FUNCTION
if(event.key.code == sf::Keyboard::Escape)
{
if(grid)
grid = false;
else
grid = true;
}
//-----------------------------------------------------------------------------------DEBUG---------------------------------------------------------------//
if(event.key.code == sf::Keyboard::B)//ACTIVE BRUSHMODE -- NEED TO BLOCK IT WHEN ACCESS VIOLATION OF CUBES ARRAY(CRASH)
{
if(!brushMode)
{
brushMode = true;
std::cout << "Brush mode enabled" << std::endl;
}
else
{
brushMode = false;
std::cout << "Brush mode disabled" << std::endl;
}
}
if(event.key.code == sf::Keyboard::L)//ADD_LAYER
{
addLayer(getCurrentMapID());
}
if(event.key.code == sf::Keyboard::M)//DELETE_LAYER
{
deleteLayer(currentMapID, maps[currentMapID].currentLayerId());
}
if(event.key.code == sf::Keyboard::S)//ADD_LAYER
{
std::cout << "Select a texture: ";
std::cin >> currentSelectedTexture; std::cout << std::endl;
}
if(event.key.code == sf::Keyboard::Left)//Move in Layer
{
if(maps[currentMapID].currentLayerId() > 0)
{
maps[currentMapID].setCurrentLayerID(maps[currentMapID].currentLayerId()-1);
}
}
if(event.key.code == sf::Keyboard::Right)//Move in Layer
{
if(maps[currentMapID].currentLayerId() < maps[currentMapID].layers().size()-1)
{
maps[currentMapID].setCurrentLayerID(maps[currentMapID].currentLayerId()+1);
}
}
//-----------------------------------------------------------------------------------DEBUG---------------------------------------------------------------//
}
if (event.type == sf::Event::MouseMoved)
{
//--------------------------------------------------------------------------CURSOR-----------------------------------------------------------------------//
currentSelectedCube = maps[currentMapID].getCubeIDAt(event.mouseMove.x, event.mouseMove.y, offsetLeft, offsetTop, enableOffset);
//--------------------------------------------------------------------------CURSOR-----------------------------------------------------------------------//
}
if (event.type == sf::Event::MouseButtonPressed)
{
//--------------------------------------------------------------------------CURSOR-----------------------------------------------------------------------//
currentSelectedCube = maps[currentMapID].getCubeIDAt(event.mouseButton.x, event.mouseButton.y, offsetLeft, offsetTop, enableOffset);
//--------------------------------------------------------------------------CURSOR-----------------------------------------------------------------------//
if (event.mouseButton.button == sf::Mouse::Left)
{
//--------------------------------------------------------------------------CUBE CLICK DETECTION--------------------------------------------------//
if(maps.size() > 0 && maps[currentMapID].layers().size() > 0 && currentSelectedCube > -1)
{
cubeClicked = true;
}
}
if (event.mouseButton.button == sf::Mouse::Right)
{
if(maps.size() > 0 && maps[currentMapID].layers().size() > 0 && currentSelectedCube > -1)
{
maps[currentMapID].layers()[maps[currentMapID].currentLayerId()].cubes()[currentSelectedCube].setTexture(1);
}
}
//--------------------------------------------------------------------------CUBE CLICK DETECTION--------------------------------------------------//
}
}
}
EDIT3: I updated my code to allow me to draw only the down side of the cube, so i can do this(the grass) :
The coordinate range(the red isometric square shown before in the screenshots) change a little when i put flat square(green).
I don't know why, i prefer to precise it, just in case.
You need to store the "heigth" of each element from the tiles plane in order to distinguish which cube are you actually selecting (the closer to the observer):
Same screen coordinates, but different tiles.
It's not clear to me how you modeled your world, so I'll give you a partial algorithm to check what face of what cube is the one clicked. Please, adapt it to your actual code and to the classes you have written to make it work.
// I'll let you to add the offsets for the screen coordinates
I = (y * 2 - x) / cubeSize;
J = (y * 2 + x) / cubeSize;
// find out if it is a left or right triangle
if ( x < (J - I) * (cubeSize/2) ) {
// left triangle
for ( k = max_n_layer; k > -1; --k ) {
// you create the cubes nesting the I loop in the J loop, so to get the index of a cube,
// assuming that you have created all the cubes (even the invisible ones, like it seems from your code)
index = (J+1+k)*mapsize + I+1+k;
// I don't really get how you define the existence or not of a face, but I guess something like this:
if ( index < map.layer[k].cubes.size()
&& map.layer[k].cubes[index].sides[top_side] != 0 ) {
// the face selected is the top side of cube[index] of layer k
// you have to return index and k to select the right face, or simply a pointer to that face
// if this makes any sense with how you have designed your model
return &map.layer[k].cubes[index].sides[top_side];
}
// now check for the side
index = (J+k)*mapsize + I+1+k;
if ( index < map.layer[k].cubes.size()
&& map.layer[k].cubes[index].sides[right_side] != 0 ) {
return &map.layer[k].cubes[index].sides[right_side];
}
index = (J+k)*mapsize + I+k;
if ( index < map.layer[k].cubes.size()
&& map.layer[k].cubes[index].sides[left_side] != 0 ) {
return &map.layer[k].cubes[index].sides[left_side];
}
}
} else {
// right triangle
for ( k = max_n_layer; k > -1; --k ) {
index = (J+1+k)*mapsize + I+1+k;
if ( index < map.layer[k].cubes.size()
&& map.layer[k].cubes[index].sides[top_side] != 0 ) {
return &map.layer[k].cubes[index].sides[top_side];
}
index = (J+1+k)*mapsize + I+k;
if ( index < map.layer[k].cubes.size()
&& map.layer[k].cubes[index].sides[left_side] != 0 ) {
return &map.layer[k].cubes[index].sides[left_side];
}
index = (J+k)*mapsize + I+k;
if ( index < map.layer[k].cubes.size()
&& map.layer[k].cubes[index].sides[right_side] != 0 ) {
return &map.layer[k].cubes[index].sides[right_side];
}
}
}
// well, no match found. As I said is up to you to decide how to do in this case
return nullptr;
Edit
I suggest you to try another way.
Consider the screen as divided not by quadrangular tiles but by the triangles you already depicted. Every 2D tile of your model will be formed by two of those triangles and so all the sides of the cubes you want to draw. For every cube don't draw nor even create the back sides, those will never be drawn.
You can try to implement a sort of specialized z-buffer algorithm by storing for each one of the triangles you have to draw on the screen the index of the side which is closer to the observer.
The coordinates of the vertex of all the triangles are calculated (once) with the code you already have.
(I,J) //For every node (I,J) you have a left and a right triangle
. * .
(I+1,J) * . | . * (I,J+1)
*
(I+1,J+1)
You are creating your cubes layer by layer, I guess, each layer having a different heigth over the base plane. Create every side of the cube using the coordinates calculated earlier. For every face (only the 3 pointing to the observer) consider each one of its 2 triangles. You can easily determine if it is visible or not if you proceed in order, then you only have to update the ID stored in the corresponding triangle.
Once finished this fase, you'll have to draw each triangle once as you already have dropped the hidden ones.
To determine the inverse transformation from screen coordinates to cell indexes, you only have to calculate which triangle is hitted and then look up which ID correspond to that. So transform back x,y to I,J (you already have those equations) and choose the left triangle if x < (J-I)/cubesize the right one otherwise.
Okay so I am a college student and our professor gave us this code to examine, and I was wondering if there was another way to do this but for OS X. My professor is using a HANDLE which I barely understand what that is, the professor was telling me he create the HANDLE as a pointer to the output stream so what would be the equivalent to it for mac since we don't have #include Windows.h obviously. Everything you see in this code is my professor's, including the comments.
//This is an example of a simple platformer made in the console. This
//makes no claims as the best way of doing things as I created this
//live before a class (while taking suggestions from them).
#include <iostream>
#include <string>
#include <vector>
#include <Windows.h>
using namespace std;
const int MAX_ROWS = 20;
const int MAX_COLS = 60;
//this is a reference to cout (we got this when we changed the output color)
//we can use this to setCursorPosition
HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);
//this is the place that we can set the cursor to when we are not using it
COORD zero;
//basic cardinal directions
enum Direction
{
UP = 8,
DOWN = 2,
RIGHT = 6,
LEFT = 4,
NORTH = UP,
SOUTH = DOWN,
EAST = RIGHT,
WEST = LEFT
};
//each place on the gameboard is a tile (tiles in this game are 1 character in length, though they do not have to be)
class Tile
{
public:
char display;
bool isPassible;
COORD pos;
Tile(char d, bool b, int y, int x)
{
display = d;
isPassible = b;
pos.X = x;
pos.Y = y;
}
void Display()
{
SetConsoleCursorPosition(output, pos);
cout << display;
SetConsoleCursorPosition(output, zero);
}
};
class Player
{
public:
COORD pos;
char display;
int JumpAmt;
//player constructor (x and y are starting location)
Player(int x, int y)
{
pos.X = x;
pos.Y = y;
display = 'C';
JumpAmt = 0;
}
//This gets the input and decides how to use it (this should be called in the main game loop)
bool Act(vector<vector<Tile>> GameBoard)
{
bool didMove = false;
COORD oldPos;
oldPos.X = pos.X;
oldPos.Y = pos.Y;
if (GetAsyncKeyState(VK_RIGHT) & 0x8000)
{
//make sure the movement is not off the game board and that there is not a wall in the way
if (pos.X + 1 < MAX_COLS && GameBoard[pos.Y][pos.X + 1].isPassible)
{
//actually move the character
pos.X += 1;
didMove = true;
}
}
if (GetAsyncKeyState(VK_LEFT) & 0x8000)
{
if (pos.X - 1 > 0 && GameBoard[pos.Y][pos.X - 1].isPassible)
{
pos.X -= 1;
didMove = true;
}
}
//You can only jump if you are on the ground
if (pos.Y + 1 < MAX_ROWS && !(GameBoard[pos.Y + 1][pos.X].isPassible))
{
if (GetAsyncKeyState(VK_UP) & 0x8000)
{
if (pos.Y - 1 > 0 && GameBoard[pos.Y - 1][pos.X].isPassible)
{
pos.Y -= 1;
didMove = true;
JumpAmt = 4;
}
}
}
//When you are not jumping fall (gravity)
if (JumpAmt == 0)
{
if (pos.Y + 1 < MAX_ROWS && GameBoard[pos.Y + 1][pos.X].isPassible)
{
pos.Y += 1;
didMove = true;
}
}
//This is what happens during your jump
if (JumpAmt > 0)
{
JumpAmt--;
if (pos.Y - 1 > 0 && GameBoard[pos.Y - 1][pos.X].isPassible)
{
pos.Y -= 1;
didMove = true;
}
}
//If you did move anywhere then update the board
if (didMove)
{
Display(oldPos, GameBoard);
}
return didMove;
}
void Display()
{
//draw myself at my position
SetConsoleCursorPosition(output, pos);
cout << display;
SetConsoleCursorPosition(output, zero);
}
void Display(COORD fix, vector<vector<Tile>> GameBoard)
{
//clear my old position
GameBoard[fix.Y][fix.X].Display();
Display();
}
};
int main()
{
//zero is used after anything is drawn to reset the cursor (this should never be changed after this)
zero.X = 0;
zero.Y = 0;
//this is a 2 dimentional array of tiles
vector<vector<Tile>> GameBoard;
//init all the tiles to blank (we will later add in platforms and stuff over top of these)
for (int row = 0; row < MAX_ROWS; row++)
{
vector<Tile> thisRow;
for (int col = 0; col < MAX_COLS; col++)
{
thisRow.push_back(Tile(' ', true, row, col));
}
GameBoard.push_back(thisRow);
}
//Build the game specific tiles (in a perfect world these would be read in from a file)
GameBoard[4][2] = Tile('-', false,4,2);
GameBoard[4][3] = Tile('-', false, 4,3);
GameBoard[4][4] = Tile('-', false, 4,4);
GameBoard[4][5] = Tile('-', false, 4,5);
GameBoard[4][6] = Tile('-', false, 4,6);
GameBoard[7][9] = Tile('-', false, 7,9);
GameBoard[7][10] = Tile('-', false, 7,10);
GameBoard[5][10] = Tile('-', false, 5,10);
GameBoard[8][14] = Tile('*', false, 8, 14); //this marks the win square
//display the board once
for (int row = 0; row < MAX_ROWS; row++)
{
for (int col = 0; col < MAX_COLS; col++)
{
GameBoard[row][col].Display();
}
}
//Bob is our hero
Player bob = Player(3, 3);
while (true)
{
bob.Act(GameBoard);
bob.Display();
Sleep(50);
//if bob falls down he dies
if (bob.pos.Y > 18)
{
bob.pos.X = 3;
bob.pos.Y = 3;
//bob.display = 65 + rand() % 26;
}
//if bob gets here he wins
if (bob.pos.Y == 7 && bob.pos.X == 14)
{
COORD pos;
pos.Y = 20;
pos.X = 0;
SetConsoleCursorPosition(output, pos);
cout << "You are Awesome";
break;
}
}
COORD pos;
pos.Y = 21;
pos.X = 0;
SetConsoleCursorPosition(output, pos);
system("Pause");
return 0;
}
I'm making a 2D game in c++ and am trying to shoot enemies. When a bullet collides with the first enemy rendered then the enemy is killed fine, and it is removed from the enemys vector. However after the first enemy has been killed, other enemies no longer die.
This is where the check is carried out in the update function.
size = enemys.size();
for (int i = 0; i<size; i++)
{
double x = enemys[i].getEnemyX();
double y = enemys[i].getEnemyY();
bool isShot = enemyShot(x,y);
if(isShot == true){
enemys.erase(enemys.begin()+i);
size = size - 1;
}
}
This is the enemyShot function.
bool GameActivity::enemyShot(double enemyX, double enemyY)
{
int size = bullets.size();
for (int i = 0; i<size; i++)
{
double x = bullets[i].getX();
double y = bullets[i].getY();
if (x >= enemyX-5.0 && x <= enemyX+5.0 && y >= enemyY-5.0 && y <= enemyY + 5.0){
return true;
}
else{
return false;
}
}
}
The problem is that your vector of enemies is updated after each erasure - thus, the current index is no longer correct.
A better way to iterate the enemys vector is to start from the end of the enemys vector. That way, when you erase an element, the index is still correct:
for (size_t i = enemys.end (); i> 0; --i) {
double x = enemys[i].getEnemyX();
double y = enemys[i].getEnemyY();
bool isShot = enemyShot(x,y);
if(isShot == true){
enemys.erase(enemys.begin()+i);
}
}
I need to place numbers within a grid such that it doesn't collide with each other. This number placement should be random and can be horizontal or vertical. The numbers basically indicate the locations of the ships. So the points for the ships should be together and need to be random and should not collide.
I have tried it:
int main()
{
srand(time(NULL));
int Grid[64];
int battleShips;
bool battleShipFilled;
for(int i = 0; i < 64; i++)
Grid[i]=0;
for(int i = 1; i <= 5; i++)
{
battleShips = 1;
while(battleShips != 5)
{
int horizontal = rand()%2;
if(horizontal == 0)
{
battleShipFilled = false;
while(!battleShipFilled)
{
int row = rand()%8;
int column = rand()%8;
while(Grid[(row)*8+(column)] == 1)
{
row = rand()%8;
column = rand()%8;
}
int j = 0;
if(i == 1) j= (i+1);
else j= i;
for(int k = -j/2; k <= j/2; k++)
{
int numberOfCorrectLocation = 0;
while(numberOfCorrectLocation != j)
{
if(row+k> 0 && row+k<8)
{
if(Grid[(row+k)*8+(column)] == 1) break;
numberOfCorrectLocation++;
}
}
if(numberOfCorrectLocation !=i) break;
}
for(int k = -j/2; k <= j/2; k++)
Grid[(row+k)*8+(column)] = 1;
battleShipFilled = true;
}
battleShips++;
}
else
{
battleShipFilled = false;
while(!battleShipFilled)
{
int row = rand()%8;
int column = rand()%8;
while(Grid[(row)*8+(column)] == 1)
{
row = rand()%8;
column = rand()%8;
}
int j = 0;
if(i == 1) j= (i+1);
else j= i;
for(int k = -j/2; k <= j/2; k++)
{
int numberOfCorrectLocation = 0;
while(numberOfCorrectLocation != i)
{
if(row+k> 0 && row+k<8)
{
if(Grid[(row)*8+(column+k)] == 1) break;
numberOfCorrectLocation++;
}
}
if(numberOfCorrectLocation !=i) break;
}
for(int k = -j/2; k <= j/2; k++)
Grid[(row)*8+(column+k)] = 1;
battleShipFilled = true;
}
battleShips++;
}
}
}
}
But the code i have written is not able to generate the numbers randomly in the 8x8 grid.
Need some guidance on how to solve this. If there is any better way of doing it, please tell me...
How it should look:
What My code is doing:
Basically, I am placing 5 ships, each of different size on a grid. For each, I check whether I want to place it horizontally or vertically randomly. After that, I check whether the surrounding is filled up or not. If not, I place them there. Or I repeat the process.
Important Point: I need to use just while, for loops..
You are much better of using recursion for that problem. This will give your algorithm unwind possibility. What I mean is that you can deploy each ship and place next part at random end of the ship, then check the new placed ship part has adjacent tiles empty and progress to the next one. if it happens that its touches another ship it will due to recursive nature it will remove the placed tile and try on the other end. If the position of the ship is not valid it should place the ship in different place and start over.
I have used this solution in a word search game, where the board had to be populated with words to look for. Worked perfect.
This is a code from my word search game:
bool generate ( std::string word, BuzzLevel &level, CCPoint position, std::vector<CCPoint> &placed, CCSize lSize )
{
std::string cPiece;
if ( word.size() == 0 ) return true;
if ( !level.inBounds ( position ) ) return false;
cPiece += level.getPiece(position)->getLetter();
int l = cPiece.size();
if ( (cPiece != " ") && (word[0] != cPiece[0]) ) return false;
if ( pointInVec (position, placed) ) return false;
if ( position.x >= lSize.width || position.y >= lSize.height || position.x < 0 || position.y < 0 ) return false;
placed.push_back(position);
bool used[6];
for ( int t = 0; t < 6; t++ ) used[t] = false;
int adj;
while ( (adj = HexCoord::getRandomAdjacentUnique(used)) != -1 )
{
CCPoint nextPosition = HexCoord::getAdjacentGridPositionInDirection((eDirection) adj, position);
if ( generate ( word.substr(1, word.size()), level, nextPosition, placed, lSize ) ) return true;
}
placed.pop_back();
return false;
}
CCPoint getRandPoint ( CCSize size )
{
return CCPoint ( rand() % (int)size.width, rand() % (int)size.height);
}
void generateWholeLevel ( BuzzLevel &level,
blockInfo* info,
const CCSize &levelSize,
vector<CCLabelBMFont*> wordList
)
{
for ( vector<CCLabelBMFont*>::iterator iter = wordList.begin();
iter != wordList.end(); iter++ )
{
std::string cWord = (*iter)->getString();
// CCLog("Curront word %s", cWord.c_str() );
vector<CCPoint> wordPositions;
int iterations = 0;
while ( true )
{
iterations++;
//CCLog("iteration %i", iterations );
CCPoint cPoint = getRandPoint(levelSize);
if ( generate (cWord, level, cPoint, wordPositions, levelSize ) )
{
//Place pieces here
for ( int t = 0; t < cWord.size(); t++ )
{
level.getPiece(wordPositions[t])->addLetter(cWord[t]);
}
break;
}
if ( iterations > 1500 )
{
level.clear();
generateWholeLevel(level, info, levelSize, wordList);
return;
}
}
}
}
I might add that shaped used in the game was a honeycomb. Letter could wind in any direction, so the code above is way more complex then what you are looking for I guess, but will provide a starting point.
I will provide something more suitable when I get back home as I don't have enough time now.
I can see a potential infinite loop in your code
int j = 0;
if(i == 1) j= (i+1);
else j= i;
for(int k = -j/2; k <= j/2; k++)
{
int numberOfCorrectLocation = 0;
while(numberOfCorrectLocation != i)
{
if(row+k> 0 && row+k<8)
{
if(Grid[(row)*8+(column+k)] == 1) break;
numberOfCorrectLocation++;
}
}
if(numberOfCorrectLocation !=i) break;
}
Here, nothing prevents row from being 0, as it was assignd rand%8 earlier, and k can be assigned a negative value (since j can be positive). Once that happens nothing will end the while loop.
Also, I would recommend re-approaching this problem in a more object oriented way (or at the very least breaking up the code in main() into multiple, shorter functions). Personally I found the code a little difficult to follow.
A very quick and probably buggy example of how you could really clean your solution up and make it more flexible by using some OOP:
enum Orientation {
Horizontal,
Vertical
};
struct Ship {
Ship(unsigned l = 1, bool o = Horizontal) : length(l), orientation(o) {}
unsigned char length;
bool orientation;
};
class Grid {
public:
Grid(const unsigned w = 8, const unsigned h = 8) : _w(w), _h(h) {
grid.resize(w * h);
foreach (Ship * sp, grid) {
sp = nullptr;
}
}
bool addShip(Ship * s, unsigned x, unsigned y) {
if ((x <= _w) && (y <= _h)) { // if in valid range
if (s->orientation == Horizontal) {
if ((x + s->length) <= _w) { // if not too big
int p = 0; //check if occupied
for (int c1 = 0; c1 < s->length; ++c1) if (grid[y * _w + x + p++]) return false;
p = 0; // occupy if not
for (int c1 = 0; c1 < s->length; ++c1) grid[y * _w + x + p++] = s;
return true;
} else return false;
} else {
if ((y + s->length) <= _h) {
int p = 0; // check
for (int c1 = 0; c1 < s->length; ++c1) {
if (grid[y * _w + x + p]) return false;
p += _w;
}
p = 0; // occupy
for (int c1 = 0; c1 < s->length; ++c1) {
grid[y * _w + x + p] = s;
p += _w;
}
return true;
} else return false;
}
} else return false;
}
void drawGrid() {
for (int y = 0; y < _h; ++y) {
for (int x = 0; x < _w; ++x) {
if (grid.at(y * w + x)) cout << "|S";
else cout << "|_";
}
cout << "|" << endl;
}
cout << endl;
}
void hitXY(unsigned x, unsigned y) {
if ((x <= _w) && (y <= _h)) {
if (grid[y * _w + x]) cout << "You sunk my battleship" << endl;
else cout << "Nothing..." << endl;
}
}
private:
QVector<Ship *> grid;
unsigned _w, _h;
};
The basic idea is create a grid of arbitrary size and give it the ability to "load" ships of arbitrary length at arbitrary coordinates. You need to check if the size is not too much and if the tiles aren't already occupied, that's pretty much it, the other thing is orientation - if horizontal then increment is +1, if vertical increment is + width.
This gives flexibility to use the methods to quickly populate the grid with random data:
int main() {
Grid g(20, 20);
g.drawGrid();
unsigned shipCount = 20;
while (shipCount) {
Ship * s = new Ship(qrand() % 8 + 2, qrand() %2);
if (g.addShip(s, qrand() % 20, qrand() % 20)) --shipCount;
else delete s;
}
cout << endl;
g.drawGrid();
for (int i = 0; i < 20; ++i) g.hitXY(qrand() % 20, qrand() % 20);
}
Naturally, you can extend it further, make hit ships sink and disappear from the grid, make it possible to move ships around and flip their orientation. You can even use diagonal orientation. A lot of flexibility and potential to harness by refining an OOP based solution.
Obviously, you will put some limits in production code, as currently you can create grids of 0x0 and ships of length 0. It's just a quick example anyway. I am using Qt and therefore Qt containers, but its just the same with std containers.
I tried to rewrite your program in Java, it works as required. Feel free to ask anything that is not clearly coded. I didn't rechecked it so it may have errors of its own. It can be further optimized and cleaned but as it is past midnight around here, I would rather not do that at the moment :)
public static void main(String[] args) {
Random generator = new Random();
int Grid[][] = new int[8][8];
for (int battleShips = 0; battleShips < 5; battleShips++) {
boolean isHorizontal = generator.nextInt(2) == 0 ? true : false;
boolean battleShipFilled = false;
while (!battleShipFilled) {
// Select a random row and column for trial
int row = generator.nextInt(8);
int column = generator.nextInt(8);
while (Grid[row][column] == 1) {
row = generator.nextInt(8);
column = generator.nextInt(8);
}
int lengthOfBattleship = 0;
if (battleShips == 0) // Smallest ship should be of length 2
lengthOfBattleship = (battleShips + 2);
else // Other 4 ships has the length of 2, 3, 4 & 5
lengthOfBattleship = battleShips + 1;
int numberOfCorrectLocation = 0;
for (int k = 0; k < lengthOfBattleship; k++) {
if (isHorizontal && row + k > 0 && row + k < 8) {
if (Grid[row + k][column] == 1)
break;
} else if (!isHorizontal && column + k > 0 && column + k < 8) {
if (Grid[row][column + k] == 1)
break;
} else {
break;
}
numberOfCorrectLocation++;
}
if (numberOfCorrectLocation == lengthOfBattleship) {
for (int k = 0; k < lengthOfBattleship; k++) {
if (isHorizontal)
Grid[row + k][column] = 1;
else
Grid[row][column + k] = 1;
}
battleShipFilled = true;
}
}
}
}
Some important points.
As #Kindread said in an another answer, the code has an infinite loop condition which must be eliminated.
This algorithm will use too much resources to find a solution, it should be optimized.
Code duplications should be avoided as it will result in more maintenance cost (which might not be a problem for this specific case), and possible bugs.
Hope this answer helps...