Related
I'm trying to create a little ascii game where I can run around kill enemies etc. However I'm new to C++ and I would like to do something if the players location is at a certain point.
Below is a simpler version of the code and a picture of the problem:
#include <iostream>
using namespace std;
struct Game
{
bool bGameOver = false;
int iWidth = 20;
int iHeight = 40;
void Draw() {
if (player.x == 5)
{
cout << "Hello"
}
}
};
struct Player
{
bool bGameOver = false;
int x = 0;
int y = 0;
};
void Setup()
{
}
int main()
{
Game game;
Player player;
while (!game.bGameOver)
{
Setup();
}
}
Picture of the error
The variable player is local in function main, so it's not visible where you tried to use it in Game::Draw.
One solution could be to make player a global variable. You'll need to switch the order of the structs:
struct Player
{
bool bGameOver = false;
int x = 0;
int y = 0;
};
Player player;
struct Game
{
bool bGameOver = false;
int iWidth = 20;
int iHeight = 40;
void Draw() {
if (player.x == 5)
{
cout << "Hello"
}
}
};
But I'd prefer to instead model things so a Game "has a" Player. So make Player a member of the Game:
struct Player
{
bool bGameOver = false;
int x = 0;
int y = 0;
};
struct Game
{
Player player;
bool bGameOver = false;
int iWidth = 20;
int iHeight = 40;
void Draw() {
if (player.x == 5)
{
cout << "Hello"
}
}
};
(Aside: You probably don't want two different values called bGameOver, since keeping them in sync would be extra work. It sounds more like a game property than a player property to me.)
I tried to write a simple game and I accidently got errors
"No matching function for call to Monster::Monster()"
and
"No matching function for call to Player::Player()".
Both were for the Game class constructor. Class Game is not related to classes Monster or Player in any way. Here are the codes.
main.cpp
#include <game.h>
#include <iostream>
using namespace std;
int main() {
Game game = Game( );
while(!game.hasFinished( )) {
game.round( );
}
}
game.h
#ifndef GAME
#define GAME
#include <entity.h>
class Game {
private:
bool finished = false;
char area[15][15];
char defaultSign = '*';
int size = 15;
Monster monsters[4];
Player player;
int score = 0;
void cls();
void fill();
void display();
void playerMove();
public:
Game();
void round();
int getScore();
bool hasFinished();
};
#endif
game.cpp
#include <game.h>
#include <iostream>
#include <conio.h>
Game::Game() {
player = Player(7, 7);
monsters[0] = Monster(0, 0);
monsters[1] = Monster(size - 1, size - 1);
monsters[2] = Monster(size - 1, 0);
monsters[3] = Monster(0, size - 1);
}
bool Game::hasFinished() {
return !finished;
}
int Game::getScore() {
return score;
}
void Game::cls() {
for(int i = 0; i < 50; i++) {
std::cout << "\n";
}
}
void Game::fill() {
for(int i = 0; i < size; i++) {
for(int j = 0; j < size; j++) {
area[i][j] = defaultSign;
}
}
for(int i = 0; i < 4; i++) {
area[monsters[i].getX( )][monsters[i].getY( )] = monsters[i].getSign( );
}
area[player.getX( )][player.getY( )] = player.getSign( );
}
void Game::display() {
for(int i = 0; i < size; i++) {
for(int j = 0; j < size; j++) {
std::cout << area[i][j];
}
std::cout << "\n";
}
}
void Game::round() {
cls( );
fill( );
display( );
playerMove( );
for(int i = 0; i < 4; i++) {
monsters[i].moveTowards(player);
}
score++;
}
void Game::playerMove() {
bool moved = false;
while(!moved) {
char c = getch( );
if(c == 'w' || c == 'W') {
player.move(player.getX( ), player.getY( ) + 1);
moved = true;
}
if(c == 'a' || c == 'A') {
player.move(player.getX( ) - 1, player.getY( ));
moved = true;
}
if(c == 's' || c == 'S') {
player.move(player.getX( ), player.getY( ) - 1);
moved = true;
}
if(c == 'd' || c == 'D') {
player.move(player.getX( ) + 1, player.getY( ));
moved = true;
}
}
}
entity.h
#ifndef ENTITY
#define ENTITY
class Entity {
protected:
int x, y;
char sign;
public:
Entity(int xPos, int yPos);
int getX();
int getY();
void move(int xPos, int yPos);
char getSign();
};
class Player : public Entity {
private:
char sign = 'P';
public:
Player(int xPos, int yPos);
};
class Monster : public Entity {
private:
char sign = 'M';
public:
Monster(int xPos, int yPos);
void moveTowards(Entity p);
};
#endif
entity.cpp
#include <entity.h>
Entity::Entity(int xPos, int yPos) {
x = xPos;
y = yPos;
sign = '*';
}
void Entity::move(int xPos, int yPos) {
x = xPos;
y = yPos;
}
int Entity::getX() {
return x;
}
int Entity::getY() {
return y;
}
char Entity::getSign() {
return sign;
}
Player::Player(int xPos, int yPos)
: Entity(xPos, yPos) {
}
Monster::Monster(int xPos, int yPos)
: Entity(xPos, yPos) {
}
void Monster::moveTowards(Entity p) {
int moveArea[2] = {x, y};
if(x > p.getX( )) moveArea[0]--;
if(x < p.getX( )) moveArea[0]++;
if(y > p.getY( )) moveArea[1]--;
if(y < p.getY( )) moveArea[1]++;
move(moveArea[0], moveArea[1]);
}
Please, help!
The problem is
Monster monsters[4]; // ---> Means initiliaze 4 Monsters with a ctor which takes no arguments(eg default Ctor)
Player player; ---> //The initiliaze 1 Player with a ctor which take no arguments
Since you defined Monster(int xPos, int yPos); default Ctors are deleted. You have to define them yourself. Entity has the same problem. I compiled your code , if you do like below it will work.
Add "Entity() = default;" , initiliaze x and y like below,
class Entity {
protected:
int x = 0; --> Critical
int y = 0; --> Critical
char sign;
public:
Entity() = default; --> Critical
Entity(int xPos, int yPos);
int getX();
int getY();
void move(int xPos, int yPos);
char getSign();
};
Do the same for player and monster
class Monster : public Entity {
private:
char sign = 'M';
public:
Monster(int xPos, int yPos);
Monster() = default; --> Critical
void moveTowards(Entity p);
};
class Player : public Entity {
private:
char sign = 'P';
public:
Player() = default; --> Critical
Player(int xPos, int yPos);
};
You should add a default constructor to your Monster class because it's implicitly called when the Monster array is initialized. If you don't want to do that for some reason, you could change the Monster array contain raw or smart pointers to Monster objects.
I'm a bit of a newcomer to programming and C++, and learning how to program games with Allegro 5. One of the projects I've set for myself is to clean up a tutorial source code of Pong that I found here: http://www.cppgameprogramming.com/newforums/viewtopic.php?f=5&t=1991
However, I've run into a problem. The compiler generates an error I don't entirely understand, and none of my research is panning out. The error is:
insufficient contextual information to determine type
The closest I found on the internet was this page: http://bytes.com/topic/c/answers/138897-error-insufficient-contextual-information-determine-type which helped me narrow down that the problem is in the class declarations. However, the solution provided there doesn't exactly apply to me, since the class constructors here take parameters. The question previously asked here doesn't seem to apply to my situation, either, since it used file output and templates, neither of which I'm using.
I've posted a large chunk of my program below, with the error-generating parts marked out with commented stars to make them easy to find, I hope. However, I've left in a lot of the rest of the code on the off-chance that it's somewhere else.
As a heads up: there might be bits of code you don't recognize from allegro 5, such as alObject.paint(255,255,255). That's me consolidating some of the allegro objects and functions into their own class to make it a bit easier on me, the source of which I'm not including here since the compiler doesn't generate errors with them.
#include <allegro5/allegro.h>
#include <allegro5/allegro_primitives.h>
#include "allegro.h"
struct CBox{
CBox(int _x, int _y, int _w, int _h):x(_x),y(_y),w(_w),h(_h){}
CBox(const CBox& other){
x = other.x;
y = other.y;
w = other.w;
h = other.h;
}
bool collides(const CBox& other){
return not(other.x + other.w < x or other.y + other.h < y or other.x > x + y or other.y > y + h);
}
int x;
int y;
int w;
int h;
};
class CPlayer{
private:
int score;
CBox box;
ALLEGRO_COLOR color;
double mov_y;
void testBounds(CBox&);
public:
CPlayer(CBox p, ALLEGRO_COLOR col):box(p),color(col){mov_y = 0.0;}
void setScore(int new_s){score=new_s;}
int getScore(){return score;}
void setBox(const CBox& b){box=b;}
CBox getBox(){return box;}
void setXYMovement(double new_my){mov_y=new_my;}
double getXYMovement(){return mov_y;}
void move(CBox& bounds);
void draw(){
al_draw_filled_rectangle(box.x, box.y, box.x + box.w, box.y + box.h, color);
}
};
class CBall{
private:
CBox box;
ALLEGRO_COLOR color;
double mov_y;
double mov_x;
int last_touch;
void testCollision(CBox&, const CBox&, CPlayer*);
int testBounds(CBox&, const CBox&);
public:
CBall(CBox p, ALLEGRO_COLOR col):box(p),color(col),last_touch(3){}
void setXYMovement(double new_mx, double new_my){
mov_x = new_mx;
mov_y = new_my;
}
void move(const CBox& bounds, CPlayer* plys);
void draw(){
al_draw_filled_circle(box.x + box.w/2, box.y + box.h/2, box.w/2, color);
}
};
class GameLoop{
private:
CBox fieldbox(int, int, int, int);
/************************************************/
/***********ERROR HERE?? ERROR HERE??************/
/************************************************/
CBall ball(const CBox&, ALLEGRO_COLOR);
CPlayer player1(const CBox&, ALLEGRO_COLOR);
CPlayer player2(const CBox&, ALLEGRO_COLOR);
/*************************************************/
/*************************************************/
public:
GameLoop(Allegro&);
void GameStart(Allegro&);
void runTimerChecks(ALLEGRO_EVENT&, Allegro&);
void runExit(ALLEGRO_EVENT&, Allegro&, bool&);
void playerInput(ALLEGRO_EVENT&, bool&);
void endPlayerInput(ALLEGRO_EVENT&);
};
void CPlayer::move(CBox& bounds){
//make sure the player doesn't go off-bounds
testBounds(bounds);
box.y+=(int)mov_y;
//Players can't move horizontally, so no bounds checking in that matter
}
void CPlayer::testBounds(CBox& bounds){
if((mov_y < 0) && (box.y + mov_y < bounds.y)){
box.y = bounds.y;
mov_y = 0;
}
else if((mov_y > 0) && (box.y + box.h > bounds.y + bounds.h)){
box.y = bounds.y + bounds.h - box.h;
mov_y = 0;
}
}
//ghostbox is the precalculated ball's trajectory
void CBall::move(const CBox& bounds, CPlayer* plys){
CBox ghostbox(box.x+(int)mov_y, box.y+(int)mov_y, box.w, box.h);
// test collision for box players
testCollision(ghostbox, bounds, plys);
testBounds(ghostbox, bounds);
}
void CBall::testCollision(CBox& ghostbox, const CBox& bounds, CPlayer* plys){
for(int i = 0; i < 2; i++){
//a player cannot touch the ball twice in a row
if(i != last_touch){
CBox other = plys[i].getBox();
if(ghostbox.collides(other)){
//set the last touch to this player
last_touch = i;
//negate the "ghost movement" in x axis
ghostbox.x -= (int)mov_x;
//bounce horizontally
mov_x = -mov_x;
//bounce vertically to change the ball's trajectory
mov_y = (((box.y+box.h/2.0)-(other.y+other.h/2.0))/other.h)*10;
break;
}
}
}
}
int CBall::testBounds(CBox& ghostbox, const CBox& bounds){
if(ghostbox.y < bounds.y){
ghostbox.y = bounds.y;
mov_y = -mov_y;
}
else if(ghostbox.y + ghostbox.h > bounds.y + bounds.h){
ghostbox.y = (bounds.y + bounds.h - ghostbox.h);
mov_y = -mov_y;
}
if(ghostbox.x + ghostbox.w < bounds.x){
box.x = bounds.x + bounds.w/2 - bounds.w/2;
box.y = bounds.y + bounds.h/2 - bounds.h/2;
return 2;
}
else if(ghostbox.x > bounds.x + bounds.w){
box.x = bounds.x + bounds.w/2 - box.w/2;
box.y = bounds.y + bounds.h/2 - box.h/2;
return 1;
}
box = ghostbox;
return 0;
}
GameLoop::GameLoop(Allegro& alObject){
// This box is our playfield (covers the whole screen)
fieldbox(0,0,alObject.getWidth(), alObject.getHeight());
//we setup the ball at the center of the screen with a white color
ball(CBox(alObject.getWidth()/2-10,alObject.getHeight()/2-10,20,20),alObject.paint(255,255,255));
//red player on the left
player1(CBox(10,alObject.getHeight()/2-80/2,20,80), alObject.paint(255,0,0));
//blue player on the right
player2(CBox(alObject.getWidth()-10-20,alObject.getHeight()/2-80/2,20,80), alObject.paint(0,0,255));
}
void GameLoop::GameStart(Allegro& alObject){
/*
when this variable is set to true the program will quit the main loop
and free the allocated resources before quitting.
*/
bool exit = false;
//we tell the ball to move to the left
/***********************************************/
/***********************************************/
ball.setXYMovement(-5.0,5.0); // GENERATES THE ERROR
/***********************************************/
/***********************************************/
while(!exit){
al_wait_for_event(alObject.eventq, NULL);
while(al_get_next_event(alObject.eventq, &alObject.event)){
if(alObject.event.type == ALLEGRO_EVENT_TIMER){
runTimerChecks(alObject.event, alObject);
}
else if(alObject.event.type == ALLEGRO_EVENT_DISPLAY_CLOSE){
// quit if the user tries to close the window
runExit(alObject.event, alObject, exit);
}
else if(alObject.event.type == ALLEGRO_EVENT_KEY_DOWN){
playerInput(alObject.event, exit);
}
else if(alObject.event.type == ALLEGRO_EVENT_KEY_UP){
endPlayerInput(alObject.event);
}
}
}
}
void GameLoop::runTimerChecks(ALLEGRO_EVENT& event, Allegro& alObject){
if(alObject.event.timer.source == alObject.getTimer()){
//fill the screen with black
al_clear_to_color(alObject.paint(0,0,0));
//move and draw our two players
/**************************************************/
/**************************************************/
player1.move(fieldbox); // GENERATES THE ERROR
player1.draw(); // GENERATES THE ERROR
player2.move(fieldbox); // GENERATES THE ERROR
player2.draw(); // GENERATES THE ERROR
/**************************************************/
/**************************************************/
}
}
void GameLoop::runExit(ALLEGRO_EVENT& event, Allegro& alObject, bool& exit){
if(event.display.source == alObject.getDisplay()){
exit = true;
}
}
void GameLoop::playerInput(ALLEGRO_EVENT& event, bool& exit){}
void GameLoop::endPlayerInput(ALLEGRO_EVENT& event){}
Yes, the error is here:
CBall ball(const CBox&, ALLEGRO_COLOR);
CPlayer player1(const CBox&, ALLEGRO_COLOR);
CPlayer player2(const CBox&, ALLEGRO_COLOR);
This doesn't declare member variables called ball, player1 and player2, as you think it does (according to code like: player1.draw();). Instead, what you've written is a declaration of member functions with these names, taking in argument the parameters you've specified. Instead, you should do:
CBall ball;
CPlayer player1;
CPlayer player2;
Then in the constructor of GameLoop, initialize them with whatever value you want, using initialization lists:
GameLoop::GameLoop(Allegro& alObject) :
ball(/* ... */),
player1(/* ... */),
player2(/* ... */)
{
// ....
}
This is for a poker game and I have class PokerTable defined in PokerTable.h
#include <iostream>
using namespace std;
class PokerTable
{
private:
int numPlayers;
int numPlaying;
int dealerPos;
int bigBlind;
int potSize;
int betSize;
bool flop;
bool turn;
bool river;
public:
//constructors
PokerTable();
PokerTable(int,int,int,int,int,bool,bool,bool);
//getters
int getNumPlayers(){return numPlayers;};
int getDealerPos(){return dealerPos;};
int getBigBlind(){return bigBlind;};
int getNumPlaying(){return numPlaying;};
int getPotSize(){return potSize;};
int getBetSize(){return betSize;};
bool getFlop(){return flop;};
bool getTurn(){return turn;};
bool getRiver(){return river;};
//void buttonShow(int);
//setters
void setBetSize(int inBetSize){betSize = inBetSize;};
void setBigBlind(int inBigBlind){bigBlind = inBigBlind;};
void setNumPlaying(int inNumPlaying){numPlaying = inNumPlaying;};
void setPotSize(int inPotSize){potSize = inPotSize;};
void setFlop(bool inFlop){flop = inFlop;};
void setTurn(bool inTurn){turn = inTurn;};
void setRiver(bool inRiver){river = inRiver;};
void setNumPlayers(int inPlayers){numPlayers = inPlayers;};
void setDealerPos(int inDealerPos){dealerPos = inDealerPos;};
};
PokerTable::PokerTable()
{
numPlayers = 9;
numPlaying = 9;
dealerPos = 1;
bigBlind = 20;
flop = false;
turn = false;
river = false;
}
PokerTable::PokerTable(int playerNum, int playingCount, int posDealer, int blindBig,int inPotSize, bool inFlop,bool inTurn,bool inRiver)
{
numPlayers = playerNum;
numPlaying = playingCount;
dealerPos = posDealer;
potSize = inPotSize;
bigBlind = blindBig;
flop = inFlop;
turn = inTurn;
river = inRiver;
}
In my watch list pokerTable.numPlayers has a random value up to 4 million before I even execute this next line of code.
PokerTable aPokerTable(9,9,1,20,30,false,false,false);
and afterwards here is pokerTable in my watch list:
- aPokerTable { numPlayers=2990892 numPlaying=9 dealerPos=9 ...} PokerTable
betSize 30 int
bigBlind 1 int
dealerPos 9 int
flop false bool
numPlayers 2990892 int
numPlaying 9 int
potSize 20 int
river false bool
turn false bool
Can anyone tell me why all the values are not what I declared them to be??!?!!
And how I can fix this?
This is Form1.h
#pragma once
#include "PokerTable.h"
#include "Card.h"
#include <time.h>
#include "PokerPlayer.h"
#include <fstream>
#include <string>
#include <sstream>
//global variables
//TODO make players start from 0
int firstPlayer;
int deck[52];
int nextCard=0;
PokerTable aPokerTable(9,9,1,20,30,false,false,false);
PokerPlayer players[9]; //however many players
ofstream gameLog;
/*
void setTable()
{
aPokerTable.setNumPlayers(9);
aPokerTable.setNumPlaying(9);
aPokerTable.setDealerPos(1);
aPokerTable.setBigBlind(20);
aPokerTable.setPotSize(30);
aPokerTable.setBetSize(20);
aPokerTable.setFlop(false);
aPokerTable.setTurn(false);
aPokerTable.setRiver(false);
}
*/
string convertInt(int number) //convert to string
{
stringstream ss;//create a stringstream
ss << number;//add number to the stream
return ss.str();//return a string with the contents of the stream
}
void createPlayers()
{
// aPokerTable.setNumPlayers(9);
for(int x=0;x<=(aPokerTable.getNumPlayers()-1);x++)
{
players[x] = *(new PokerPlayer(1000,(aPokerTable.getDealerPos())+1,false,0,1));//1000 chips, position i+1, not folded
}
}
void playRound()
{
int action;
for(int playerTurn = firstPlayer; playerTurn <= aPokerTable.getNumPlayers()+firstPlayer; playerTurn++)
{
if(players[playerTurn].getFold() == false)
{
if(aPokerTable.getNumPlaying() == 1)
{
players[playerTurn].setChipStack(players[playerTurn].getChipStack() + aPokerTable.getPotSize()); //player wins pot
}
else //there is more than one person playing
{
action = players[playerTurn].action(); //0 is check/fold, value is call/bet/raise,
if(action > aPokerTable.getBetSize())
{
aPokerTable.setBetSize(action);
aPokerTable.setPotSize(aPokerTable.getPotSize() + action);
playerTurn = playerTurn - aPokerTable.getNumPlayers();
}
else if (action == aPokerTable.getBetSize()) //call
{
aPokerTable.setPotSize(aPokerTable.getPotSize() + action);
}
else //action < aPokerTable.betSize
{
players[playerTurn].setFold(true);
aPokerTable.setNumPlaying(aPokerTable.getNumPlaying()-1); //removes player from playing tally
}
}
}
}
}
void randomDeck()
{
int random_integer;
int tempCard;
//srand((unsigned)time(0));
for(int j=0;j<=51;j++)
{
deck[j] = j;
}
for(int i=51; i>=1; i--)
{
random_integer = rand()%(i); //a random number between 0 and i
tempCard = deck[i];
deck[i] = deck[random_integer]; //put the random card from unshuffled deck into slot i of the deck
deck[random_integer] = tempCard; //put whatever was at slot i into the random slot
}
}
void dealCards()
{
for(int j=1;j<=aPokerTable.getNumPlayers();j++)
{
players[j].setCard1(deck[nextCard]);
nextCard++;
players[j].setCard2(deck[nextCard]);
nextCard++;
}
}
void playPreFlop()
{
aPokerTable.setBetSize(aPokerTable.getBigBlind());
aPokerTable.setFlop(false); //it is before the flop
aPokerTable.setTurn(false);
aPokerTable.setRiver(false);
randomDeck(); //shuffle cards
dealCards();
firstPlayer = (aPokerTable.getDealerPos() + 3)%(aPokerTable.getNumPlayers()); // first player is left of blinds between 0 and numplayers
playRound();
}
void playFlop()
{
aPokerTable.setFlop(true);
firstPlayer = (aPokerTable.getDealerPos())%aPokerTable.getNumPlayers(); // first player is left of dealer between 0 and numplayers
aPokerTable.setBetSize(0);
playRound();
}
void playTurn()
{
aPokerTable.setTurn(true);
firstPlayer = (aPokerTable.getDealerPos())%aPokerTable.getNumPlayers(); // first player is left of dealer between 0 and numplayers
aPokerTable.setBetSize(0);
playRound();
}
void playRiver()
{
aPokerTable.setRiver(true);
firstPlayer = (aPokerTable.getDealerPos())%(aPokerTable.getNumPlayers()); // first player is left of dealer between 0 and numplayers
aPokerTable.setBetSize(0);
playRound();
if(aPokerTable.getNumPlaying() >=2)
{
//showDown();
}
}
/*
void showDown()
{
}
*/
This is pokerPlayer.h
using namespace std;
class PokerPlayer
{
private:
int chipStack,position;
bool fold;
int card1,card2;
public:
//constructors
PokerPlayer();
PokerPlayer(int,int,bool,int,int);
//getters
int getChipStack() {return chipStack;}
int getPosition() {return position;}
int getCard1(){return card1;}
int getCard2(){return card2;}
bool getFold(){return fold;}
//setters
void setChipStack(int inChips){chipStack = inChips;}
void setPosition(int inPos){position = inPos;}
void setCard1(int inCard1){card1 = inCard1;}
void setCard2(int inCard2){card2 = inCard2;}
void setFold(bool inFold){fold = inFold;}
int action();
};
PokerPlayer::PokerPlayer()
{
chipStack = 1000;
position = 0;
fold=false;
card1 = 0;
card2 = 1;
}
PokerPlayer::PokerPlayer(int inChipStack,int inPos, bool inFold, int inCard1, int inCard2)
{
chipStack = inChipStack;
position = inPos;
fold = inFold;
card1 = inCard1;
card2 = inCard2;
}
int PokerPlayer::action()
{
return 0;
}
aPokerTable { numPlayers=2990892 numPlaying=9 dealerPos=9 ...}
Note that dealerPos got assigned the value 9, that's wrong as well. If you look closely, you'll see that everything is shifted by 4 bytes.
Two possible reasons. The debugger could have picked the wrong address for aPokerTable, the actual address minus 4. That's unlikely. Or there's a mismatch between the definition of the PokerTable class as seen by pokertable.cpp and the other .cpp files that #include the pokertable.h include file. Where pokertable.cpp saw an extra member before the numPlayers member. Maybe you edited the header and deleted that member but ended up not recompiling pokertable.cpp for some mysterious reason. Build + Rebuild to fix. Do panic a bit if this actually works.
It's because in C++ before the constructor is called, variable uses the value that it already contains in its memory location that is a "random" value
I cannot reconstruct it because i dont have the full code. However, a random value near 4 million sounds like a pointer. When you store or retrieve a member variable maybe you did not de-reference the pointer. Please post the rest of the code so we can check if that's the case.
players[x] = *(new PokerPlayer(...));
That is a memory leak. What you probably want is:
players[x] = PokerPlayer(1000,(aPokerTable.getDealerPos())+1,false,0,1);
I obviously do not 'grok' C++.
On this programming assignment, I have hit a dead end. A runtime error occurs at this line of code:
else if (grid[i][j]->getType() == WILDEBEEST) { ...
with the message "Runtime Error - pure virtual function call."
From my understanding, this error occurs if the function reference attempts to call the (virtual) base class while the child class is not currently instantiated. However, I do not see where I have made this mistake.
Relevant Code:
Professor's Code:
const int LION = 1;
const int WILDEBEEST = 2;
//
// .
// .
// .
//
class Animal {
friend class Savanna; // Allow savanna to affect animal
public:
Animal();
Animal(Savanna *, int, int);
~Animal();
virtual void breed() = 0; // Breeding implementation
virtual void move() = 0; // Move the animal, with appropriate behavior
virtual int getType() = 0; // Return if wildebeest or lion
virtual bool starve() = 0; // Determine if animal starves
protected:
int x,y; // Position in the savanna, using the XY coordinate plane
bool moved; // Bool to indicate if moved this turn
int breedTicks; // Number of ticks since breeding
Savanna *savanna;
};
//
// .
// .
// .
//
void Savanna::Display()
{
int i,j;
cout << endl << endl;
for (j=0; j<SAVANNASIZE; j++)
{
for (i=0; i<SAVANNASIZE; i++)
{
if (grid[i][j]==NULL){
setrgb(0);
cout << " ";
}
else if (grid[i][j]->getType()==WILDEBEEST) // RUNTIME ERROR HERE
{
setrgb(7);
cout << "W";
}
else {
setrgb(3);
cout << "L";
}
}
cout << endl;
}
setrgb(0);
}
My Code:
class Wildebeest: public Animal {
friend class Savanna; // Allow the Savanna to affect the animal, as per spec
public:
Wildebeest();
Wildebeest(Savanna *, int, int); // accepts (pointer to a Savanna instance, X Position, Y Position)
void breed(); // Perform breeding, and check breedTick
void move(); // move the animal.
int getType(); // returns WILDEBEEST
bool starve(); // if starving, returns 0. (counterintuitive, I know.)
};
int Wildebeest::getType() {
return WILDEBEEST;
}
I've read The Old New Thing: What is __purecall? and Description of the R6025 run-time error in Visual C++ but I do not fully understand why this is occurring in the above code.
[edit] Full listing of main.c (yes, all one file... part of the assignment requirements.)
//
// This program simulates a 2D world with predators and prey.
// The predators (lions) and prey (wildebeest) inherit from the
// Animal class that keeps track of basic information about each
// animal (time ticks since last bred, position on the savanna).
//
// The 2D world is implemented as a separate class, Savanna,
// that contains a 2D array of pointers to type Animal.
//
// ****************************************************************
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include <time.h>
#include "graphics.h"
using namespace std;
int wrapTo20 (int value) {
if (0 > value) {
value = 19;
} else if (20 == value) {
value = 0;
}
return value;
}
const int SAVANNASIZE = 20;
const int INITIALBEEST = 100;
const int INITIALLIONS = 5;
const int LION = 1;
const int WILDEBEEST = 2;
const int BEESTBREED = 3;
const int LIONBREED = 8;
const int LIONSTARVE = 3;
// Forward declaration of Animal classes so we can reference it
// in the Savanna class
class Animal;
class Lion;
class Wildebeest;
// ==========================================
// The Savana class stores data about the savanna by creating a
// SAVANNASIZE by SAVANNASIZE array of type Animal.
// NULL indicates an empty spot, otherwise a valid object
// indicates an wildebeest or lion. To determine which,
// invoke the virtual function getType of Animal that should return
// WILDEBEEST if the class is of type Wildebeest, and Lion otherwise.
// ==========================================
class Savanna
{
friend class Animal; // Allow Animal to access grid
friend class Lion; // Allow Animal to access grid
friend class Wildebeest; // Allow Animal to access grid
public:
Savanna();
~Savanna();
Animal* getAt(int, int);
void setAt(int, int, Animal *);
void Display();
void SimulateOneStep();
private:
Animal* grid[SAVANNASIZE][SAVANNASIZE];
};
// ==========================================
// Definition for the Animal base class.
// Each animal has a reference back to
// the Savanna object so it can move itself
// about in the savanna.
// ==========================================
class Animal
{
friend class Savanna; // Allow savanna to affect animal
public:
Animal();
Animal(Savanna *, int, int);
~Animal();
virtual void breed() = 0; // Whether or not to breed
virtual void move() = 0; // Rules to move the animal
virtual int getType() = 0; // Return if wildebeest or lion
virtual bool starve() = 0; // Determine if animal starves
protected:
int x,y; // Position in the savanna
bool moved; // Bool to indicate if moved this turn
int breedTicks; // Number of ticks since breeding
Savanna *savanna;
};
// ======================
// Savanna constructor, destructor
// These classes initialize the array and
// releases any classes created when destroyed.
// ======================
Savanna::Savanna()
{
// Initialize savanna to empty spaces
int i,j;
for (i=0; i<SAVANNASIZE; i++)
{
for (j=0; j<SAVANNASIZE; j++)
{
grid[i][j]=NULL;
}
}
}
Savanna::~Savanna()
{
// Release any allocated memory
int i,j;
for (i=0; i<SAVANNASIZE; i++)
{
for (j=0; j<SAVANNASIZE; j++)
{
if (grid[i][j]!=NULL) delete (grid[i][j]);
}
}
}
// ======================
// getAt
// Returns the entry stored in the grid array at x,y
// ======================
Animal* Savanna::getAt(int x, int y)
{
if ((x>=0) && (x<SAVANNASIZE) && (y>=0) && (y<SAVANNASIZE))
return grid[x][y];
return NULL;
}
// ======================
// setAt
// Sets the entry at x,y to the
// value passed in. Assumes that
// someone else is keeping track of
// references in case we overwrite something
// that is not NULL (so we don't have a memory leak)
// ======================
void Savanna::setAt(int x, int y, Animal *anim)
{
if ((x>=0) && (x<SAVANNASIZE) && (y>=0) && (y<SAVANNASIZE))
{
grid[x][y] = anim;
}
}
// ======================
// Display
// Displays the savanna in ASCII. Uses W for wildebeest, L for lion.
// ======================
void Savanna::Display()
{
int i,j;
cout << endl << endl;
for (j=0; j<SAVANNASIZE; j++)
{
for (i=0; i<SAVANNASIZE; i++)
{
if (grid[i][j]==NULL){
setrgb(0);
cout << " ";
}
else if (grid[i][j]->getType()==WILDEBEEST)
{
setrgb(7);
cout << "W";
}
else {
setrgb(3);
cout << "L";
}
}
cout << endl;
}
setrgb(0);
}
// ======================
// SimulateOneStep
// This is the main routine that simulates one turn in the savanna.
// First, a flag for each animal is used to indicate if it has moved.
// This is because we iterate through the grid starting from the top
// looking for an animal to move . If one moves down, we don't want
// to move it again when we reach it.
// First move lions, then wildebeest, and if they are still alive then
// we breed them.
// ======================
void Savanna::SimulateOneStep()
{
int i,j;
// First reset all animals to not moved
for (i=0; i<SAVANNASIZE; i++)
for (j=0; j<SAVANNASIZE; j++)
{
if (grid[i][j]!=NULL) grid[i][j]->moved = false;
}
// Loop through cells in order and move if it's a Lion
for (i=0; i<SAVANNASIZE; i++)
for (j=0; j<SAVANNASIZE; j++)
{
if ((grid[i][j]!=NULL) && (grid[i][j]->getType()==LION))
{
if (grid[i][j]->moved == false)
{
grid[i][j]->moved = true; // Mark as moved
grid[i][j]->move();
}
}
}
// Loop through cells in order and move if it's an Wildebeest
for (i=0; i<SAVANNASIZE; i++)
for (j=0; j<SAVANNASIZE; j++)
{
if ((grid[i][j]!=NULL) && (grid[i][j]->getType()==WILDEBEEST))
{
if (grid[i][j]->moved == false)
{
grid[i][j]->moved = true; // Mark as moved
grid[i][j]->move();
}
}
}
// Loop through cells in order and check if we should breed
for (i=0; i<SAVANNASIZE; i++)
for (j=0; j<SAVANNASIZE; j++)
{
// Kill off any lions that haven't eaten recently
if ((grid[i][j]!=NULL) &&
(grid[i][j]->getType()==LION))
{
if (grid[i][j]->starve())
{
delete (grid[i][j]);
grid[i][j] = NULL;
}
}
}
// Loop through cells in order and check if we should breed
for (i=0; i<SAVANNASIZE; i++)
for (j=0; j<SAVANNASIZE; j++)
{
// Only breed animals that have moved, since
// breeding places new animals on the map we
// don't want to try and breed those
if ((grid[i][j]!=NULL) && (grid[i][j]->moved==true))
{
grid[i][j]->breed();
}
}
}
// ======================
// Animal Constructor
// Sets a reference back to the Savanna object.
// ======================
Animal::Animal()
{
savanna = NULL;
moved = false;
breedTicks = 0;
x=0;
y=0;
}
Animal::Animal(Savanna *savana, int x, int y)
{
this->savanna = savana;
moved = false;
breedTicks = 0;
this->x=x;
this->y=y;
savanna->setAt(x,y,this);
}
// ======================
// Animal destructor
// No need to delete the savanna reference,
// it will be destroyed elsewhere.
// ======================
Animal::~Animal()
{ }
// Start with the Wildebeest class and its required declarations
class Wildebeest: public Animal {
friend class Savanna; // Allow savanna to affect animal
public:
Wildebeest();
Wildebeest(Savanna *, int, int);
void breed(); // Whether or not to breed
void move(); // Rules to move the animal
int getType(); // Return if wildebeest or lion
bool starve();
};
bool Wildebeest::starve() {
return 1;
}
// ======================
// Wildebeest constructors
// ======================
Wildebeest::Wildebeest() {
}
Wildebeest::Wildebeest(Savanna * sav, int x, int y) : Animal(sav, x, y) {
}
// ======================
// Wldebeest Move
// Look for an empty cell up, right, left, or down and
// try to move there.
// ======================
void Wildebeest::move() {
int loc1, loc2, loc3, loc4;
int x1, x2, x3, x4;
int y1, y2, y3, y4;
x1 = wrapTo20(x);
y1 = wrapTo20(y + 1);
x2 = wrapTo20(x + 1);
y2 = wrapTo20(y);
x3 = wrapTo20(x);
y3 = wrapTo20(y - 1);
x4 = wrapTo20(x - 1);
y4 = wrapTo20(y);
loc1 = savanna->getAt(x1, y1)->getType();
loc2 = (int)savanna->getAt(x2, y2)->getType();
loc3 = savanna->getAt(x3, y3)->getType();
loc4 = savanna->getAt(x4, y4)->getType();
while (!moved) {
int x = 1 + (rand() % 4);
switch (x) {
case 1:
if (!loc1) savanna->setAt(x1, y1, this);
break;
case 2:
if (!loc2) savanna->setAt(x2, y2, this);
break;
case 3:
if (!loc3) savanna->setAt(x3, y3, this);
break;
case 4:
if (!loc4) savanna->setAt(x4, y4, this);
break;
default:
break;
}
}
}
// ======================
// Wildebeest getType
// This virtual function is used so we can determine
// what type of animal we are dealing with.
// ======================
int Wildebeest::getType() {
return WILDEBEEST;
}
// ======================
// Wildebeest breed
// Increment the tick count for breeding.
// If it equals our threshold, then clone this wildebeest either
// above, right, left, or below the current one.
// ======================
void Wildebeest::breed() {
breedTicks++;
if (2 == breedTicks) {
breedTicks = 0;
}
}
// *****************************************************
// Now define Lion Class and its required declarations
// *****************************************************
class Lion: public Animal {
friend class Savanna; // Allow savanna to affect animal
public:
Lion();
Lion(Savanna *, int, int);
void breed(); // Whether or not to breed
void move(); // Rules to move the animal
int getType(); // Return if wildebeest or lion
bool starve();
};
// ======================
// Lion constructors
// ======================
Lion::Lion() {
}
Lion::Lion(Savanna * sav, int x, int y) : Animal(sav, x, y) {
}
// ======================
// Lion move
// Look up, down, left or right for a lion. If one is found, move there
// and eat it, resetting the starveTicks counter.
// ======================
void Lion::move() {
int loc1, loc2, loc3, loc4;
int x1, x2, x3, x4;
int y1, y2, y3, y4;
x1 = wrapTo20(x);
y1 = wrapTo20(y + 1);
x2 = wrapTo20(x + 1);
y2 = wrapTo20(y);
x3 = wrapTo20(x);
y3 = wrapTo20(y - 1);
x4 = wrapTo20(x - 1);
y4 = wrapTo20(y);
loc1 = savanna->getAt(x1, y1)->getType();
loc2 = (int)savanna->getAt(x2, y2)->getType();
loc3 = savanna->getAt(x3, y3)->getType();
loc4 = savanna->getAt(x4, y4)->getType();
while (!moved) {
int x = 1 + (rand() % 4);
switch (x) {
case 1:
if (!loc1) savanna->setAt(x1, y1, this);
break;
case 2:
if (!loc2) savanna->setAt(x2, y2, this);
break;
case 3:
if (!loc3) savanna->setAt(x3, y3, this);
break;
case 4:
if (!loc4) savanna->setAt(x4, y4, this);
break;
default:
break;
}
}
}
// ======================
// Lion getType
// This virtual function is used so we can determine
// what type of animal we are dealing with.
// ======================
int Lion::getType() {
return LION;
}
// ======================
// Lion breed
// Creates a new lion adjacent to the current cell
// if the breedTicks meets the threshold.
// ======================
void Lion::breed() {
breedTicks++;
if (2 == breedTicks) {
breedTicks = 0;
}
}
// ======================
// Lion starve
// Returns true or false if a lion should die off
// because it hasn't eaten enough food.
// ======================
bool Lion::starve() {
return 1;
}
// ======================
// main function
// ======================
int main()
{
string s;
srand((int)time(NULL)); // Seed random number generator
Savanna w;
int initialWildebeest=0;
int initialLions=0;
// enter initial number of wildebeest
int beestcount = 0;
while(initialWildebeest <= 0 || initialWildebeest > INITIALBEEST){
cout << "Enter number of initial Wildebeest (greater than 0 and less than " << INITIALBEEST << ") : ";
cin >> initialWildebeest;
}
// Randomly create wildebeests and place them in a randomly choosen empty spot in savanna
int i;
bool placed = 0;
for ( i = 0; i < initialWildebeest; i++) {
while (!placed) {
int x = 1 + (rand() % 20);
int y = 1 + (rand() % 20);
if (!(w.getAt(x, y))){
Wildebeest fred(&w, x, y);
placed = 1;
}
}
placed = 0;
}
// Enter initial number of lions
int lioncount = 0;
while(initialLions <= 0 || initialLions > INITIALLIONS){
cout << "Enter number of initial Lions (greater than 0 and less than " << INITIALLIONS << ") : ";
cin >> initialLions;
}
// Randomly create lions and place them in a randomly choosen empty spot in savanna
placed = 0;
for ( i = 0; i < initialLions; i++) {
while (!placed) {
int x = 1 + (rand() % 20);
int y = 1 + (rand() % 20);
if (!(w.getAt(x, y))){
Lion ronald(&w, x, y);
placed = 1;
}
}
placed = 0;
}
// Run simulation forever, until user cancels
int count=0;
while (true)
{
gotoxy(0,0);
w.Display();
w.SimulateOneStep();
Sleep(500);
count++;
if(count == 20){
count=0;
cout << endl << "Press enter for next step, ctrl-c to quit" << endl;
getline(cin,s);
clearline(23);
}
}
return 0;
}
What is the definition of grid and how are you populating it? I bet you are doing it from the Animal constructor. At this time, the dynamic type of this is Animal, and not the type of the object that is eventually created.
Animal::Animal()
{
grid[i][j] = this; // the type of this is Animal
}
Until the object is fully constructed, you cannot use the this pointer in a dynamic way, this includes calling virtual functions, or using the virtual function table.
To be more specific, you need to postpone using the this pointer, that is to say, storing it in the grid array, until after the object is fully constructed.
Here in the Animal constructor:
Animal::Animal(Savanna *savana, int x, int y)
{
this->savanna = savana;
moved = false;
breedTicks = 0;
this->x=x;
this->y=y;
savanna->setAt(x,y,this);
}
Note that you are calling Savanna::setAt with the this parameter. At this point, the dynamic type of this is Animal, not Wildebeest or some other thing. setAt does this:
void Savanna::setAt(int x, int y, Animal *anim)
{
if ((x>=0) && (x<SAVANNASIZE) && (y>=0) && (y<SAVANNASIZE))
{
grid[x][y] = anim;
}
}
The value of anim is the this pointer from the Animal constructor.
Note a few more things. When you are constructing the list of Wildebeest, you are causing a dangling pointer:
for ( i = 0; i < initialWildebeest; i++) {
while (!placed) {
int x = 1 + (rand() % 20);
int y = 1 + (rand() % 20);
if (!(w.getAt(x, y))){
**** Wildebeest fred(&w, x, y);
placed = 1;
}
}
placed = 0;
}
The WildeBeest named fred will go out of scope on the next line and be destroyed. You need to allocate it dynamically via new:
for ( i = 0; i < initialWildebeest; i++) {
while (!placed) {
int x = 1 + (rand() % 20);
int y = 1 + (rand() % 20);
if (!(w.getAt(x, y))){
Wildebeest *fred = new Wildebeest(&w, x, y);
placed = 1;
}
}
placed = 0;
}
In the destrcuctor of the Savanna, there is a matching call to delete so that we do not leak memory:
Savanna::~Savanna()
{
// Release any allocated memory
int i,j;
for (i=0; i<SAVANNASIZE; i++)
{
for (j=0; j<SAVANNASIZE; j++)
{
**** if (grid[i][j]!=NULL) delete (grid[i][j]);
}
}
}
You will have exactly the same problem with the Lion instances as well.
One of your problems is these lines ...
if (!(w.getAt(x, y))){
Wildebeest fred(&w, x, y);
placed = 1;
}
... create a Wildebeest on the stack, and in the constructor the address of that stack-dwelling Wildebeest is stuffed into w's grid, and then the Wildebeest goes out of scope.
Your Wildebeests and Lions need to live in the heap ...
if (!(w.getAt(x, y))){
// hey maintenance programmer, this looks like I'm leaking the Wildebeest,
// but chillax, the Savannah is going to delete them
Wildebeest *fred = new Wildebeest(&w, x, y);
placed = 1;
}
... and you need the comment because what you're doing is far, far away from idiomatic C++.
Well, I don't see the code that intializes the grid array. Perhaps grid it was created on the stack, or on the heap, and is therefore filled with uninitialized values. Uninitialized values could be anything, but they probably are not NULLs, and are definitely not valid pointers.
The pure virtual error message means that the function being called doesn't have an implementation; it's effectively invoking a null pointer of type pointer to method. (That's why the syntax is =0;.) So, whatever else is going on, the error message is telling you there aren't any Wildebeasts being pointed to there.
I'd be real tempted to add a little code to check whether there's ANYTHING there. You've got a check for null, so the question is what is there?
It's almost certain that array isn't being initialized the right way.
I think your problem is not a call to pure virtual function (compiler can never allow that so it doesn't really cause a runtime error), but that you are calling the function on an invalid memory area that therefore has an invalid virtual table.
I am also willing to bet that this may have something to do with how you construct your beasts, as automatic variables rather than dynamically allocating them. As soon as you get out of those ifs, that memory is recycled.
Not necessarily the cause of your problem (I haven't read your code too deeply), but you are not using virtual destructors, which you should be.