Skip to main content
GameDev.net gamedev.net
🔒 Locked

C++ OO problems when trying to make AI

Started by algumacoisaqualquer Jan 11, 2007 at 4:42 PM 6 replies 1.6k views
Original Post
algumacoisaqualquer
algumacoisaqualquer
Basically, the AI of my game is made by an AI class and a possibility class. Each possibility has a map built-in, as well as an std::vector of possibilities. So, when my AI class calls the first possibility, it will build all the possibilities tree (default is 3 iterations). The problem is that, the first time I call the AI, it will work fine (I don't know if it's clever, but at least it wont crash). However, the second time I call the AI.GetBestMove(), the program will crash. Now, I believe that the problem is that the first time I call it, the entire tree of possibilities is created normally (I have an std::cout inside my possibility construction function). But the second time, the constructor isn't called, and when I try i < one_possibility.possibilities.size(), I crash. I supose that the AI class isn't being properly cleaned up, but that class is a local variable inside the function that is calling it, so that should delete the ai class at every frame. Plus, one_possibility is also a local variable inside c_ai.GetBestMovement(); - it should be deleted after every function call. There are no new or alloc()s inside these classes, only std::vectors, shouldn't they be automaticaly cleaned up? Anyone knows what's going on?? Thanks!
Palidine
Palidine
Quote:
Original post by algumacoisaqualquer
Anyone knows what's going on??


You'll need to post the relevant code. Post the exact line number where it is crashing and the code around that point (i.e. when it crashes in the debugger post the code relevant to that section).

-me

algumacoisaqualquer
algumacoisaqualquer
Ok, so declarations are done as:
class c_possibility{    c_map map;    int iteration;    int looses; int victories;bool win;bool loose;int start;int end;//These shouldn't matter right now    void calculate_board_value(); int board_value;//also shouldn't matter    std::vector <c_possibility> possibilities;    friend class c_AI;    public:    c_possibility(c_map *p_map, int p_iteration);    ~c_possibility();//this does nothing    void calculate_possibility();};class c_AI{    std::vector<c_possibility> possibilities;//this is actually never used    c_map *map;    public:    void GetBestMovement(int &start, int &end);    c_AI(c_map *p_map);};


The actual code is quite long. Here's the complete version:
include "ai.h"#include <iostream>c_possibility::c_possibility(c_map *p_map, int p_iteration){    looses = 0;    victories = 0;    board_value = 0;    win = false;    loose = false;    start = 0;    end = 0;    iteration = p_iteration;    map.copy(p_map);    calculate_possibility();}void c_possibility::calculate_possibility(){    if(iteration == 1) //at one point, we need to stop building the tree    {    calculate_board_value();    return;    }    //if the game is allready ended, no need to calculate it all    if(map.GetGameState() == victory1) //if player1 won...    {        if(map.player1_turn){win=true;return;} //we mark win if it is his turn        else{loose=true;return;} // or loose if it's not    }    if(map.GetGameState() == victory2) //same thing with player 2    {        if(!map.player1_turn){win=true;return;}        else{loose=true;return;}    }    std::cout << start << " " << end << " e";    //These four nested loops are for getting all the movement possibilities.    // i,j are the coordinates of the starting of the movement, i.e: the piece selected.    //then, k,l is were that piece is supposed to move.    for (int i = 0; i < 6 ; i++)    {    	for (int j = 0; j < 6; j++)    	{    	    start = i*10+j;    		if((map.pieces[j]->player != 0)&  //if it's not player 0, and it's the correct player    		((map.pieces[j]->player==2&map.player1_turn!=true)|    		 (map.pieces[j]->player==1&map.player1_turn!=false)))    		{                for(int k = 0; k < 6; k++)//and for all ending possibilities                {                    for(int l = 0; l < 6; l++)                    {                        end = k*10+l;                         if(map.IsMovementLegal(start,end) == true)                         {                            std::cout << "00040 ";                            c_map new_map;                            new_map.copy(↦);                            new_map.MovePieces(start,end);                            c_possibility new_possibility(&new_map, iteration-1);                            new_possibility.start = start;                            new_possibility.end = end;                            std::cout << "00040 ";                            possibilities.push_back(new_possibility);                         }                    }                }            }        }    }    /////////////////    // Here we have finished evaluating all possibilities    std::cout << "00000 ";    for(int i = 0; i < possibilities.size(); i++)    {        //if at least one of the possibilities ends up in the other player victory,        //then this possibility will end in this player loosing.        if(possibilities.win == true){loose=true;return;}        //if at least one of the possibilities ends up in the other player loosing,        //we mark this as an interesting possibility.        if(possibilities.loose == true){victories++;}    }    if(victories==possibilities.size())    {win = true;return;}//if all posibilities are victories, then this movement counts as winning    board_value = victories;    std::cout << "00001 ";}c_possibility::~c_possibility(){    possibilities.empty();//i guess this isn't working}void c_possibility::calculate_board_value(){    board_value = 0;}void c_AI::GetBestMovement(int &start, int &end){    std::cout << "one...";//horrible, horrible debuging    c_possibility one_possibility(map, 3);    //one_possibility.push_back(c_possibility(map. 3));//this was allready commented out    std::cout << "two...";    c_possibility *best_possibility;    best_possibility = &one_possibility.possibilities[0];//we need to start at somewere    int g = one_possibility.possibilities.size();//this doesen't give me an error    std::cout << "three...";//the second time, it will get this far...    for (int i = 0;i < one_possibility.possibilities.size();i++)    {std::cout << "four..."; //but never makes until here (the second time)    	if(one_possibility.possibilities.board_value > best_possibility->board_value)    	{best_possibility = &(one_possibility.possibilities);}    }    start = best_possibility->start;    end = best_possibility->end;}c_AI::c_AI(c_map *p_map){    map = p_map;}


And here is the short version (hopefully only the important stuff, and none cut off):
void c_AI::GetBestMovement(int &start, int &end){    std::cout << "one...";//horrible, horrible debuging    c_possibility one_possibility(map, 3);    //one_possibility.push_back(c_possibility(map. 3));//this was allready commented out    std::cout << "two...";    c_possibility *best_possibility;    best_possibility = &one_possibility.possibilities[0];//we need to start at somewere    int g = one_possibility.possibilities.size();//this doesen't give me an error    std::cout << "three...";//the second time, it will get this far...    for (int i = 0;i < one_possibility.possibilities.size();i++)    {std::cout << "four..."; //but never makes until here (the second time)    	if(one_possibility.possibilities.board_value > best_possibility->board_value)    	{best_possibility = &(one_possibility.possibilities);}    }    start = best_possibility->start;    end = best_possibility->end;}c_AI::c_AI(c_map *p_map){    map = p_map;}


Edit: Well the debugger gave me an "Program received signal SIGSEGV, Segmentation fault." Means it's trying to read memory from something that doesen't exist. I believe this is because there isn't a one_possibility.possibilities[0].board_value, as one_possibility.possibilities has zero values. However, the program is crashing at the line "for (int i = 0;i < one_possibility.possibilities.size();i++)" (this information is from my std::couts, not the debuger).

[Edited by - algumacoisaqualquer on January 11, 2007 5:55:30 PM]
MaulingMonkey
MaulingMonkey
one_possibility.possibilities.size() == 0, or "four" is being reached but not flushed, and the crash occurs later. Instead of using cout, use a debugger to find the actual line of the crash. See Superpig's article.

A decent implementation of the standard library, in debug mode, should've given you a descriptive error immediately upon using possibilties[0] if .size()==0. Upgrade to one that does and/or assert( .size() == 0 ); in the future to avoid similar mishap.
algumacoisaqualquer
algumacoisaqualquer
Quote:
Original post by MaulingMonkey
one_possibility.possibilities.size() == 0, or "four" is being reached but not flushed, and the crash occurs later. Instead of using cout, use a debugger to find the actual line of the crash. See Superpig's article.

A decent implementation of the standard library, in debug mode, should've given you a descriptive error immediately upon using possibilties[0] if .size()==0. Upgrade to one that does and/or assert( .size() == 0 ); in the future to avoid similar mishap.


It does looks like "one_possibility.possibilities.size() == 0", I made a check and it returned true. However, I don't understand what you mean by "Upgrade to one that does and/or assert( .size() == 0 );"

Anyway, I'll finish reading Superpig's article, truth is I never understood how the debuger worked (I'm using MingW in CodeBlocks).
Thanks!

MaulingMonkey
MaulingMonkey
Original post by algumacoisaqualquer
Quote:
Original post by MaulingMonkey
one_possibility.possibilities.size() == 0, or "four" is being reached but not flushed, and the crash occurs later. Instead of using cout, use a debugger to find the actual line of the crash. See Superpig's article.

A decent implementation of the standard library, in debug mode, should've given you a descriptive error immediately upon using possibilties[0] if .size()==0. Upgrade to one that does and/or assert( .size() == 0 ); in the future to avoid similar mishap.


It does looks like "one_possibility.possibilities.size() == 0", I made a check and it returned true. However, I don't understand what you mean by "Upgrade to one that does and/or assert( .size() == 0 );"

"Upgrade to one that does" indicates I think it'd be benifitial to upgrade either:
1) Your C++ toolset (Compiler, Linker, and Standard library, which usually come together, often along with an IDE). Visual Studio 2005, for example (the express version of which is available for free download).
2) The Standard Library's implementation, independantly. Not all implementations of the standard library will work on all compilers, but it may be worth investigating the alternatives. That said, #1 is typically easier.

"assert( .size() == 0 )" is refering to assertions as mentioned in Superpig's article:

Quote:
From GameDev.net -- Introduction to Debugging -- Miscellany (page 7):

How do you put assertions into your code, and have them tested? The fastest approach is to use the assert() function in the C Runtime Library, which will give you a generic error dialogue reporting the thing you were asserting. However, you can often get a lot more information by writing your own assertion macro and handler; you can include state about your game, you can have the handler write a log message instead of throwing up a dialogue, you can provide the option to ignore the assertion and continue on anyway, etc.


Technically, assert() is a macro (in most/all implementations), but regardless, it can be found in the header file for C++.

I'm suggesting I would've used it like so:

void c_AI::GetBestMovement(int &start, int &end){    c_possibility one_possibility(map, 3);    assert(!one_possibility.empty());    /* possible rationale for why one_possibility should never be empty,     * or reference to what piece of code should be enforcing this     * guarantee.  A one line comment on the actual assert line will     * probably suffice, I've just used a multi line break due to the length     * of this explaintory text.     */    c_possibility *best_possibility;    best_possibility = &one_possibility.possibilities[0];    int g = one_possibility.possibilities.size();    for (int i = 0;i < one_possibility.possibilities.size();i++)    {    	if(one_possibility.possibilities.board_value > best_possibility->board_value)    	{best_possibility = &(one_possibility.possibilities);}    }    start = best_possibility->start;    end = best_possibility->end;}


Then, even if my implementation of the standard library did not make this check for me, the program would (in debug mode) halt and inform me of the assert() failure. Typically, this includes file and line number (for quick location of the problematic area of code in question), as well as check performed.

This allows me to skip the entire diagnosis of crash cause step, which was as follows:
1) one_possibiltity.possibilities.size() being on the stack (and not crashing) eliminates the likelyhood of one_possibiliity or it's submember .possibilities refering to invalid data. (In a release build, given that you do nothing with the result, optimization would eliminate .size()'s not crashing as a reliable indicator of anything, given that it doesn't need to actually be called until the loop).
2) Given the above, there are two main "forks" of code execution:

A) .size() != 0. I considered this first. Unless the internal pointer itself was unreferenceable (very unlikely given that the internal size variable was), we're left with:
A.1) Validly initializing the pointer
A.2) Using said pointer and valid indexes < .size() in the loop
A.3) Dereferencing a local, valid pointer (given 1 and 2) in ->start/->end to copy the values, which std::vector guarantees to work (in a well behaved program, i.e. barring memory corruption that has damaged std::vector's contents).
A.4) Returns to the executing function, which hasn't been provided.

Excepting A.4, that left the other fork of execution to possibly blame:

B) .size() == 0:
B.1) Invalidly initializing the pointer (With container[0], index < size == false (0 < 0 == false)). I note that a bounds checking implementation should catch this.
B.2) A loop which never executes.
B.3) First use of dereferenced value of best_position during assignment to ->start/->end, which in an unchecked implementation will most likely cause a segfault (*nix) or access violation (windows). Delayed result of problem at B.1.
B.4) Returns to the executing function, which hasn't been provided.
algumacoisaqualquer
algumacoisaqualquer
Quote:
Original post by MaulingMonkey
Excepting A.4, that left the other fork of execution to possibly blame:

B) .size() == 0:
B.1) Invalidly initializing the pointer (With container[0], index < size == false (0 < 0 == false)). I note that a bounds checking implementation should catch this.
B.2) A loop which never executes.
B.3) First use of dereferenced value of best_position during assignment to ->start/->end, which in an unchecked implementation will most likely cause a segfault (*nix) or access violation (windows). Delayed result of problem at B.1.
B.4) Returns to the executing function, which hasn't been provided.


Well, I have moved on to Visual-C++, and tried the assert thing. Indeed, the possibilities vector has 0 elements inside, so this is giving me the error. However, The reason why this is happening remains a mystery. I'm suging the debuger now, and while I'm still learning to use it, I'm allready cathing several things I had no idea that were happening.

The most weird one was cought on accident - basically my map class has the two players as elements, and they in turn have a piece vector each. They were suposed to have 12 pieces, but the wacth shows me that, sometimes, they will have 36 or 60 pieces each (and the board has only 36 squares on total). Basically, the crash (or one of the crashes) is happening at c_possibility::calculate_possibility(), when I try to access the (c_map map) element of c_possibility. What I don't understand, however, is why it isn't crashing at all at the first time I build up the possibility tree - it is actually working as expected (only the first time, tough).

So, basically, all this part is working fine - the problem is somewere else. I'll have to revise almost everything now, but if I figure out what's going on, or at least get any more concrete thing to ask, I'll come back.

Anyway, thank you very much for the helping, this is the first time I get to actually make a debuger work - and the assert thing was also very usefull.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.