CO2301- Games Development 1: Weeks 8+ Implementing Pathfinding

Introduction

You have no assignments due in during the course of the next 4 weeks. My expectation is that you should be able to complete this worksheet over the next couple of weeks, hopefully during the course of this week. You are expected to put in 3 hours independent work per week and, given that you haven't got any assignments to do, I think this is doable.

The assignment is available. What I'd like you to is to then use the remaining practicals to start work on the assignment. This will mean that I can give you help with the assignment during the practicals.

I will check up at the beginning of practical to see how far you have got, so be prepared to show me.

Pathfinding

There are 3 elements to pathfinding:  Algorithms  Lists  Data Structures

Algorithms are the instructions for accomplishing some task.  You must use the algorithms given.  You must follow the algorithms exactly.

Lists are used a lot within AI. You need to learn some terminology. When an item is added to a list it is said to have been "pushed" from the list. When an item is removed from a list it is said to have been "popped" from the list. An essential part of any implementation of a list therefore is "push" and "pop".

Data Structures are important within AI. The use of the correct data structure will make the algorithm more efficient. However, data structures have an even greater importance than efficiency. Use of the right data structure will make the implementation of the algorithm easier, even trivial. The use of the wrong data structure can make the algorithm impossible to implement.

You need to understand what a First-In, First-Out (FIFO) list, Last-In, First-Out (LIFO) list and a Priority Queue is before continuing with this worksheet.

 A FIFO list is implemented by: o pushing on to the back of the list o popping from the front of the list.  A LIFO list is implemented by: o pushing on to the front of the list o popping from the front of the list.

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 1  A priority queue is an ordered list. Each item on the list has an associated score or priority. The list is ordered (or sorted) according to priority. A priority queue is implemented by: o pushing (inserting) to the appropriate location within the list based on priority. o popping from the front of the list.

It is possible to build a FIFO list and a priority queue from scratch in C++. This would be done using linked lists. The creation of a linked list is an extremely good way to explore and master pointers and I would strongly suggest that you take the time to create a linked list. However, in order to build a FIFO list and a priority queue you are going to use the Standard Template Library (abbreviated to STL).

Moving Data Around: Pointers and Dynamic Memory Management

Pointers are a special type of variable. Pointers do not store values. A pointer stores the memory location of another variable. In other words, pointers "point at" variables.

Pointers allow C++ programmers to access dynamic memory management. Memory management is the ability to request memory from the operating system in real-time, and to release it back to the OS when you have finished with it.

In order to employ the algorithms efficiently you are going to have to use pointer and dynamic memory management. One fundamental operation is moving data from the OpenList to the ClosedList. It would be inefficient to simply copy the data. Instead we can use pointers and simply move the pointers around.

You have been introduced to pointers and dynamic memory management in Advanced C+ +.

If you studied the Games Concepts module then you have also made extensive use of pointers and dynamic memory management throughout that module. Dynamic memory management is used within the TL-Engine. However, it is hidden away inside the IModel class.

Exercise 1: Pointers and Dynamic Memory Management

 The first thing I want you to do is create a new project. Set up a win32 application: File -> New -> Win32 -> Win32 Console Application  In "Applications Settings" the radio button for "Console Application" should be selected and "Empty project" box under "Additional options:" should be checked. Click "Finish".  You need to add a file to your project. In "Solution Explorer" right click on "Source Files", navigate to "Add" and then "New Item". Add a .cpp file. Call it "exercise 1.cpp"

You will be working (for the time being) with a structure called "coords". This structure merely contains the x and y coordinates of cells within the grid.

struct coords { int x; int y; };

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 2 You will be using C++ smart pointers. Smart pointers will look after memory management for you.

You need to include the library /

#include

A smart pointer variable is declared using unique_ptr. (There are other types of smart pointers, but for our purposes this is enough). You must supply the data type being pointed at. This is done using angle brackets. Dynamic Memory Allocation allows the program to request memory from the Operating System whilst the program is running. The following line of code:  declares a coords pointer named ptr:  requests memory from the operating system  points ptr at the new block of memory

unique_ptr ptr(new coords);

The new command allocates enough memory to store the specified data type. It will also return the address of the allocated memory. It is now possible to use the variable to store data.

Pointers do not store directly store data. Rather, they point at the location in which data is stored. In order to access the location to which a pointer points you have to use the arrow operator.

 Create a coords structure. Remember to declare it globally.  Declare a coords pointer called ptr and use the new command to allocate memory and set your pointer to point at the new memory block.  Assign a value of 4 to the x coordinate of ptr and a value of 7 to the y coordinates of ptr. Remember that you are using a pointer so you have to use the -> syntax for the structure selection operator. You should be very used to this syntax. Remember that "Intellisense" will work with the structure selection operator and will show the structure fields (x and y) if your syntax is correct.  Use cout and ptr to output the x and y values from within the structure pointed to by ptr.

You do not have to manually relinquish memory back to the operating system. A smart will do this automatically for you. The memory will be deleted automatically when it goes out of scope.

You will most likely have come across games which have problems attributed to a "memory leak". Typical symptoms would be a general slow down of the game and ultimately it locking up completely. A game may use thousands of models or other forms of dynamic memory allocation. As you can imagine, it is possible to loose track of memory allocation. Memory is allocated but never released. Over time the computer will simply run out of memory. It is safest to test whether a pointer is already being used before you use it to allocate any new memory.

The use of smart pointers will mean that memory leaks will not occur.

Exercise 2: Allocating and reallocating Pointers

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 3 A pointer points at a block of memory. It is not the block of memory itself.

One important characteristic of smart pointers in C++ is ownership. Who currently owns the block of memory being pointed to by a pointer? When you declare a unique_ptr, it actually creates a raw pointer and then wraps it around with extra code. This code will automatically manage the pointer for you. One consequence is that unique_ptr prevents copying of its contained pointer. It means that only one thing can own the pointer at a time and you have to explicitly transfer ownership to another pointer. The transferral of ownership is done using the keyword move.

 Create a coords structure.  Declare a coords unique pointer called ptr and allocate memory for it.  Assign a value of (3, 9) to ptr1.  Declare a unique pointer called ptr2  Try to assign the ptr1 to ptr2. What happens? Only one pointer can own the object. Two unique pointers cannot point at the same object.

You can transfer ownership of an object. This is done by using the move command:

ptr2 = move( ptr );

 Only the second pointer is now pointing at the memory. Test this by outputting the value or x and y for first ptr1 and then ptr2. Trying to use ptr1 in this manner will cause an error.

The Standard Template Library

You are now ready to start work on using the STL to implement pathfinding. For this module, you will implement lists using the deque and list container classes from the Standard Template Library (STL).

The STL includes libraries for certain programming data structures. For the purposes of this module we're particularly interested in the "container" classes: the stack, queue and linked list classes. They are called container classes because they are used to hold data. A container class is like a stack or list of objects.

The STL container classes use a common interface. You can think of a container class as being a special kind of array. In many ways it looks like an array and the syntax is similar to an array, e.g. the square brackets can be used to select an item for some of the container classes.

An index is used to move through the items of a container class in much the same way as using an index within an array. This approach will look much the same. However, you must a special kind of index with the container classes called an "iterator". An iterator is actually a generalisation of a pointer. The iterator points at a container. The iterator be can be made to point at the beginning of the container. The iterator can be advanced through the list of container using the increment (++) operator.

The container classes are far more flexible than arrays. They are dynamic: they can be shrunk and expanded in real time. It is possible to add and remove items from the middle

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 4 of the list as well as from the front and the back. It is possible to remove a range of items from the list. Lastly it the STL uses common operators such as ++, --, == and !=.

There are three types of sequence containers: vectors, deques and lists.

Vectors are similar to arrays but they are dynamic: they can expand or contact during run- time. Insertion or deletion at the end of the vector is efficient. Insertion or deletion elsewhere is much less efficient. There is a good argument to use vectors in place of arrays.

Deque stands for "double-ended queue" and is pronounced D-Q or deck. Insertion or deletion at the end or beginning of the deque is efficient. Insertion or deletion elsewhere is relatively slow. Deques are good for FIFO lists.

The list container class is implemented as a doubly linked list. Random access is not possible with a list, e.g. selecting an element by position in the way we do with an element of an array. However, rearranging (e.g. sorting) is more efficient with a list than with the other sequence container classes, especially if the data we're storing are pointers. Insertion and deletion operations have the same efficiency throughout the list.

You need to be able to implement the following basic operations:  Create a list  Push data on to the list. Initially you need to push on to the back or end of the list.  Pop data from the front of the list.  Check to see whether the list is empty.

Next you need to extend the program so that you can:  Display the coords of all of the nodes on the list  Check to see whether a new state can be found on the list.

Lastly you need to:  Implement parent so that it is possible to construct the path when the solution has been found.

Constructing the OpenList using the deque class

The container class you will be using is called deque. In order to use the deque class you need to include the deque library.

#include

 Create a list.

It is possible to have a list of pointers. Use the asterisk syntax for the pointers. A list is declared by the name of the container class, the type of data stored on the list and then the name of the new list. The following line of code will create a list using the deque container class. It stores data of type "coords pointer" and the name of the list is "openList":

deque > openList;

 How to push items on to the "openList".

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 5 In order to use an element of the list to store any data, you will have to have allocated memory to the list element. You will have to use a temporary pointer in order to do this.

unique_ptr tmp(new coords);

Data is pushed on to the list by using the push method. In order to construct a FIFO list you need to push the data on to the back of the list. This can be done using the push_back() method. Push_back adds an element onto the end of the list. It is defined as:

void push_back( const Type& _Val );

For example,

unique_ptr tmp(new coords);

tmp->x = 4; // x co-ordinate of the start tmp->y = 4; // y co-ordinate of the start

deque > openList;

openList.push_back( move(tmp) );

Note the way that you have to use move. You are pushing the pointer onto the list. Because you are using a unique_ptr you have to explicitly move it onto the list, i.e. changing ownership of the object. It was owned by tmp. It is now owned by the container on the list.

 How to pop data from the front of the list.

Data is popped by using the pop command. A FIFO list is implemented by popping data from the front of the list. The pop command removes the first element from the list. It does not retrieve the data from the list but it will delete the first element. In order to retrieve the data you need to use front method. This returns the first element from the list. Obviously you will need to assign the retrieve node to 'current'. For example,

// pop the first element from the open list unique_ptr currentNode; currentNode = move( openList.front() ); openList.pop_front();

Again you have to explicitly move the object, i.e. changing ownership of the object. It was owned by the front of the list. It is now owned by currentNode.

Individual elements are accessed using the arrow operator.

int I = 7; currentNode->x = i; cout << currentNode->x << endl;

Pop removes an element from the list. It does not destroy the element.

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 6 A container class can be checked to see whether it is empty or not using the empty method. The method returns true if the list has no elements on it, otherwise it will return false.

if( openList.empty( ) )

Exercise 3: Starting out with the deque class

 Create a coords structure.  Declare a deque of type coords pointer and call it 'openList'.  Declare a pointer of type coords called 'currentNode'.  Allocate the memory to 'currentNode' so that you can store some data.  Assign a value of ( 5, 7 ) to 'currentNode'.  Push 'currentNode' on to the back of 'openList'.  Output the value of x and y for the first item on 'openList'.  Declare a pointer of type coords called 'tmpNode'.  Pop the first item from 'openList' and assign it to 'tmpNode'.  Output the value of x and y for 'tmpNode'.

You can display the current head of the list using the front function:

cout << openList.front()->x << endl;

Reusing a unique_ptr

A pointer can point at different objects. You can reuse the same pointer to create a new object, or a whole series of them. The method reset is used to change To get a unique_ptr to replace the managed object.

deque < unique_ptr < coords > > openList;

unique_ptr tmp( new coords ); tmp->x = 4; tmp->y = 4;

openList.push_back( move(tmp) ); cout << openList.front()->x << " " << openList.front()->y << endl;

tmp.reset( new coords ); tmp->x = 5; tmp->y = 7; openList.push_back(move(tmp));

tmp.reset(new coords); tmp->x = 6; tmp->y = 2; openList.push_back(move(tmp));

It is important to note that reset will overwrite the current object. However, in the code above you have used move to change ownership of the current object (actually 3 successive objects each of which were moved onto the list). Reset hasn’t overwritten anything you need, but it has allowed you to reuse the pointer to allocate a new object.

Iterators and loops

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 7 Displaying all of the data held within the list is the same type of operation for container classes as for arrays. You need to loop through the list from beginning to end. Remember that in the STL you have to use an iterator. The iterator is declared using the data type of the container class, the data type being held on the list, and then the keyword iterator. The following line of code declares an appropriate iterator for the coords pointers called 'p'.

deque >::iterator p;

The iterator needs to be assigned to the beginning of the loop. The beginning of the loop is given by the begin method. Note that begin returns an iterator not a reference to the data held on the list.

p = list.begin();

The end of the list is given by the end method. The iterator is advanced through the list by the standard increment operator.

while( p != list.end() ) { p++; }

The way in which the pointer is advanced looks familiar. However, it is not obvious how to access the data held within the list. The problem is that you first need to access the pointer that the iterator is storing, and then access the data which the pointer is pointing at. Any attempt to directly access the data will not work. The following two lines of code both cause compilation errors.

cout << *p->x; // compilation error! cout << p->x; // compilation error!

You need to "dereference" the iterator in order to access the thing that it is storing. Dereference means to access the data or memory location that the iterator is holding. You need to tell the compiler that you want to access this. The compiler will not automatically understand what you want it to do. Dereferencing is done by placing round brackets around the iterator like so:

(*p)

Because what the iterator is storing is a pointer, you now need to use the arrow operator in order to access the data:

cout << (*p)->x;

Note that the syntax will be much nicer if we use auto:

for (auto p = openList.begin(); p != openList.end(); p++) { cout << (*p)->x << endl; }

Using the list with a function

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 8 It will be useful to use functions. For example, you will find it useful to create a display function, a search function (to find out whether a node already exists on the open list or closed list). You cannot do a call by copy for a list of unique_ptrs, instead you need to use a reference parameter. void Display(deque>& myList) {

} int main() { deque < unique_ptr < coords > > openList;

unique_ptr tmp(new coords); tmp->x = 4; tmp->y = 4; openList.push_back(move(tmp));

tmp.reset(new coords); tmp->x = 5; tmp->y = 5; openList.push_back(move(tmp));

tmp.reset(new coords); tmp->x = 6; tmp->y = 6; openList.push_back(move(tmp));

Display(openList);

system("pause"); return 0; }

You should use an iterator to go through all of the elements of the list. The easiest approach is to use auto to declare the iterator. You also need to dereference the iterator. Two versions of this loop have been given below. The first version of the loop uses an iterator and dereferencing. It is similar to material you have already seen.

The second version of the loop does exactly the same thing as the first version, however it uses the new for syntax introduced in C11. Note the way the syntax for accessing the iterator changes. void Display(deque>& myList) { for (auto it = myList.begin(); it != myList.end(); ++it) { cout << (*it)->x << " " << (*it)-> y << endl; }

for (auto& elt : myList) { cout << elt->x << " " << elt->y << endl; } }

Exercise 4: Doing more with the deque class

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 9  Create a coords structure.  Declare a deque of type coords pointer and call it 'openList'.  Assign 10 new items to 'openList'. I would suggest that you use a for loop and assign the values ( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ) to the x fields of the list and ( 0, 2, 4, 6, 8, 10, 12, 14, 16, 18 ) to the y fields.  You will need to use a pointer to help you do this.  You will need to allocate memory to each new item you want to push onto the list.  The pointer can be used repeatedly- each pass around the loop you use the pointer to allocate a new block of memory, assign the x and y values into the new block of memory and lastly push the memory onto the list using push_back().  Note: you do not need to use the begin() or end() methods for this operation.

 Construct a display function to output the value or x and y for all of the elements. You will need to use the begin() and end() methods for this operation. You can use the code given above. Simply: o Create an iterator o Point it at the front of the list o While not at the end of the list . Display the item the iterator is currently pointing at . Increment the iterator  Construct a find function to check whether a node exists on the openList. Use the following x and y values to check that your function works: values ( 0 , 0 ), ( 3 , 6 ), ( 9 , 18 ) and ( 5 , 5 ). All of the values apart from (5, 5) should be found on the list.

Exercise 5: Allocating and reallocating Pointers within lists (and arrays)

The main reason for using pointers for doing pathfinding is that it allows us to efficiently transfer nodes from the OpenList to the ClosedList (and occasionally vice versa in the case of the A* algorithm).

Pointers just point at data. For example, the memory (data) can be transferred around the pointers of the data or, indeed, passed over from one list to another list (or even to a singleton pointer). However, with unique_ptrs you have to move the block of memory: transferring ownership from one pointer to another.

For example, in the diagram above the green pointer is used to create an object. The pointer owns this object. The list is a list of pointers. When you push the object onto the

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 10 list, you will be pushing a pointer onto the list (the red pointer in the diagram) and allocating ownership of the block of memory to it. See page 6 for the code to do this. The block of memory has stayed the same, but two different pointers have pointed at it, one after the other.

 Create a coords structure.  Declare a deque list of type coords pointer.  Using a temporary pointer: o Allocate the memory. o Assign a value of ( 5, 7 ) to the block of memory.  Using the temporary pointer push the data onto the list. The first element of the list is pointing at the recently allocated data.  Output the value of x and y for the front of the list.  Output the value of x and y for the back of the list.

 You can prove that these are the same block of data by generating an error.  Pop the data from the front of the list.

// pop the first element from the open list unique_ptr temp; currentNode = move( openList.front() ); openList.pop_front();

 The first element on the list has been popped. Pop removes an element from the list. It does not destroy the element. However, the front function has been used to point the temp pointer at the data that was at the front of the list.

 If you now attempt to output the data supposedly pointed to by the front of the list you will see garbage or an error message. The reason for this is that you have transferred ownership of the object to currentNode.

cout << openList.front()->x << " " << openList.front()->y << endl;

Extend the reassignment of pointers is to work with two lists.

 Create a coords structure.  Declare a deque of type coords pointer and call it 'openList'.  Assign 10 new items to 'openList'. I would suggest that you use a for loop and assign the values ( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ) to the x fields of the list and ( 0, 2, 4, 6, 8, 10, 12, 14, 16, 18 ) to the y fields. Remember to allocate memory to each element of the list.  Declare a second list using deque of type coords pointer and call it 'closedList'.  Transfer over the data from the first list to the second list. In other words, move the data from the openList to the closedList. You will need an iterator to go through the openList. You do not need to allocate memory during this operation - you are merely making the pointers in the second list point at the same data as the pointers in the original list, i.e. transferring ownership.  Output the value or x and y for all of the elements of the closedList.

 Redo the above exercise but reverse the list when you transfer over the values.

Smart pointers and functions using a raw pointer

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 11 It may be useful to pass a unique_ptr to a function so that you can build your program out of functions and thereby avoid duplicated code.

The first thing I want to show is how to pass the value of a unique_ptr over to function. For example, with a function that just displays the value of an object I do not want to move the unique_ptr over to the function, just its value. You can use a "raw pointer" to do this. You can obtain a raw pointer from a smart pointer using get().

void Raw( coords* t) { cout << t->x << " " << t->y << endl; }

You would invoke Raw using get():

Raw( currentNode.get() );

You can transfer ownership of a unique_ptr to a function using move (as you would expect, since move is how we transfer ownership)

unique_ptr Transfer(unique_ptr t) { cout << t->x << " " << t->y << endl; t->x = 2; t->y = 5; return std::move(t); }

Note that the Transfer() function also uses return and move to transfer ownership of the pointer back again. If you left ownership of the pointer inside the function and didn't transfer it back, then you will have lost the object because it would be deleted as soon as the function completed.

You would invoke Transfer as follows:

currentNode = Transfer( move( currentNode ) );

Exercise 6: Implementing breadth-first using deque

You now know how to use the deque class to  Create a list  Push on to the back of a list  Pop from the front of a list  Check to see whether a list is empty  Go through all of the elements of the list, for example in order to display each one in turn.  Use a list with a function

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 12  Declare a pointer within a structure that will point at other structures of the same data type and use this to implement parent. You have been provided with the algorithm to perform a breadth-first search. Implement it using deque and the methods presented in the previous worksheet. The approach you need to adopt is to simply transcribe the algorithm step-by-step into the equivalent code.  I would strongly suggest that you write a couple of lines and then check for errors by running the compiler. The STL is a great tool but a real pain to debug.  You can treat ClosedList as an unordered list (in practice it will probably be more efficient to implement it as an ordered list, which is ordered by x and y coordinates).

This is a trace from my implementation. The start is (4, 4), goal is (6, 6). current: A 4 4

Open List: B: 4 5 C: 5 4 D: 4 3 E: 3 4

Closed List:

Press any key to continue . . . current: B 4 5

Open List: C: 5 4 D: 4 3 E: 3 4 F: 4 6 G: 5 5 H: 3 5

Closed List: A: 4 4

Press any key to continue . . . current: C 5 4

Open List: D: 4 3 E: 3 4 F: 4 6 G: 5 5 H: 3 5 I: 6 4 J: 5 3

Closed List: B: 4 5 A: 4 4

Press any key to continue . . . current: D 4 3

Open List: E: 3 4 F: 4 6 G: 5 5 H: 3 5

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 13 I: 6 4 J: 5 3 K: 4 2 L: 3 3

Closed List: C: 5 4 B: 4 5 A: 4 4

Press any key to continue . . .

Exercise 7: Implementing depth-first using deque

The algorithm for depth-first search requires a Last-In, First-Out (LIFO) list. In order to be able to implement a LIFO list you need to be able to push on to the front of the list. Needless to say, this is done with the push_front() method.

openList.push_front( newNode );

 Modify your existing list code so it now works as a LIFO list.  You have been provided with the algorithm to perform a depth-first search. Implement it using deque and the methods presented in the previous worksheet. Once again the approach you need to adopt is to simply transcribe the algorithm step-by-step into the equivalent code.  Think about the algorithm before you implement it. What work do you actually need to do?

This is a trace from my implementation. The start is (4, 4), goal is (6, 6). There's a wall at x = 0, x = 9, y = 0, y = 9. current: A 4 4

Open List: E: 3 4 D: 4 3 C: 5 4 B: 4 5

Closed List:

Press any key to continue . . . current: E 3 4

Open List: H: 2 4 G: 3 3 F: 3 5 D: 4 3 C: 5 4 B: 4 5

Closed List: A: 4 4

Press any key to continue . . . current: H 2 4

Open List:

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 14 K: 1 4 J: 2 3 I: 2 5 G: 3 3 F: 3 5 D: 4 3 C: 5 4 B: 4 5

Closed List: E: 3 4 A: 4 4

Press any key to continue . . . current: K 1 4

Open List: M: 1 3 L: 1 5 J: 2 3 I: 2 5 G: 3 3 F: 3 5 D: 4 3 C: 5 4 B: 4 5

Closed List: H: 2 4 E: 3 4 A: 4 4

Press any key to continue . . .

Exercise 8: Implementing parent

Change the coords structure so that it contains a coords pointer.

struct coords { int x; int y; coords* parent; };

This may look strange however the compiler will understand exactly what this code means. The compiler will create a pointer within the structure that will point at other coords structures. This will allow you to keep a record of the parent nodes within the pathfinding algorithm.

You cannot use unique_ptr in this context because the child class does not own the parent. Instead you will have to use a raw pointer. Remember that you can use the get command in order to obtain the raw pointer from the unique_ptr (see page 12). .  Create a coords structure.  Declare a deque of type coords pointer and call it 'openList'.  Create a coords pointer and call it prior. This should be a raw pointer not a unique_ptr.

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 15  Declare a unique_ptr called tmp. Using tmp add one new item to 'openList' using dynamic memory allocation. It should be assigned the value of ( 0, 0 ).  Assign a value of 0 to the parent pointer of tmp.  Assign tmp to prior. When you do the assignment you will need to convert tmp to a raw pointer. Remember that you can obtain a raw pointer from a smart pointer using get().

 Now use a loop to add 9 more new items to 'openList'. Assign the values ( 1, 2, 3, 4, 5, 6, 7, 8, 9 ) to the x fields of the list and ( 2, 4, 6, 8, 10, 12, 14, 16, 18 ) to the y fields. This is similar to what you did in exercise 5, p. 11.  Remember to allocate memory to each element of the list.  At the end of each iteration of the loop you need to: o Assign prior to the parent pointer of the current item. o Assign the current item to prior. (Remember to use get()).  In this way you will have created a chain of pointers which can be followed in order to show the ancestry of each of the items on the list.  Create a new coords pointer and call it path. path is a raw pointer. Assign path to the last item on openList. (Again, remember to use get()).  Show each successive item using the ancestry chain. The end of the chain will occur when path has a value of 0, e.g.

while ( path != 0 )

 The path pointer can be use to display the values held within each item. The path pointer can be advanced along the ancestry chain by using the parent field, like so:

path = path->parent;

 In effect you will show the openList in reverse.

Priority Queue

Hill-climbing, Best-First, Dijkstra's algorithm and A* all rely on the use of a priority queue for their efficacy implementation. In order to be able to construct a priority you need to sort the elements on the list according to some value, e.g. heuristic score or cost. There are different ways in which a priority list can be implemented. The implementation depends upon the method used to sort the data. Sorting algorithms vary according to their efficiency. One efficient implementation is the use of heap sort. However, you will use the sort() method provided in STL.

To implement the priority queue you will have to add a new field to the coords structure so that the list can be ordered by the score of the nodes.

struct coords { int x; int y; int score; coords* parent; };

Constructing a Priority Queue: sort()

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 16 You need to include a new library: algorithm. This library has implementations of various algorithms for the STL.

#include

Sorting relies on being able to compare two items and state that one has priority over the other. You need to write the comparison function (or method). The comparison function takes two parameters: the two elements which are being compared. The comparison function returns a boolean stating whether the first element has a higher precedence than the second element (or vice versa depending on whether you are implementing an ascending or a descending sort). When the sort() method is invoked it is provided with the name of the comparison function.

I think that this is the first time you've been presented with the idea of passing a function via a parameter list. Functions have an address in memory in the same way as anything else within a program so it is possible to pass a function to another function or method.

// Comparison function for sort. Sort in ascending order, i.e. lowest score at // the front of the list bool CompareCoords(unique_ptr& lhs, unique_ptr& rhs) { return lhs->score < rhs->score; }

Exercise 9: Implementing sort

 Create a deque called openList (If you don’t already have one).  Push 5 items onto the list. Ensure that each of them has different score and that they’re not already sorted.  Implement a comparison function.  Sort the openList:

sort(openList.begin(), openList.end(), CompareCoords);

 Test the sort by displaying the list.  Test you sort by adding one item at a time and then sorting. o add a middling score o add a very low score o add a very high score

Incidentally: lambda function

 You may have come across lambda functions (or will see them at some other point). You can, as an alternative, implement the sort using a lambda function:

sort(openList.begin(), openList.end(), [](unique_ptr& lhs, unique_ptr& rhs) { return lhs->score < rhs->score; });

Deletion from the middle of the list: erase()

Have a look at the A* algorithm. In order to implement A* you have to be able to remove a node from the closed list and put it back on to open if a better route to the node has been

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 17 found. This means that a list element may be pop from inside the list, rather than the front or end. The way to do this is to use the erase() method.

The erase() method needs to supplied with an iterator. The iterator points at the element to be removed. It can also be supplied with two iterator in which case it will erase the range of elements between the two iterators.

deque < unique_ptr >::iterator p; openList.erase(p);

As always it will be easier to write this using auto:

auto p = openList.begin(); // set p to the beginning of the

Note that as with pop_front() and pop_back() the erase() method merely removes the item from the list. You have to retrieve the data yourself before killing the element.

There is an added complication with erase(). A typical way of using the method would be to search through the list until you find the item you are searching for and then erase the item. If you use this approach note that the search will break as soon as you erase the element. This is because increment relies on the items being in the sequence they were in at the beginning of the search: but now there is a missing item and so increment will move onto the wrong item, or catastrophically beyond the end of the list.

One way to solve this would be to use the break command and simply come out of the loop after you have found the item you are looking for and having erased it.

auto p = openList.begin(); // set p to the beginning of the loop while (p != openList.end()) // while not at the end of the loop { if ((*p)->x == 3 && (*p)->y == 3) { cout << "found" << endl; openList.erase(p); break; } p++; // advance through the loop }

The erase() method returns an iterator to the element immediately after the element erased or, if there are no succeeding elements, the end of the list. An alternative approach would be use this facility to cope with missing elements. This would be useful if, for example, you want to find multiple instances on the list.

auto p = openList.begin(); // set p to the beginning of the loop while (p != openList.end()) // while not at the end of the loop { if ((*p)->x == 3 && (*p)->y == 3) { cout << "found" << endl; p = openList.erase(p); } else { p++; // advance through the loop }

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 18 }

Exercise 10: Implementing erase()

 Use your existing code and push 5 items onto the list. Ensure that each of them has different x and y values.  Implement a search and erase function using break. Check that it works in all 3 possible alternatives: o finding and erasing the first element of the list o finding and erasing the last element of the list o finding and erasing a middle element from the list  Modify your search and erase function so that it will also successful get to the end of the list even after an item is erased. Check that it works in all 3 possible alternatives:

CO2301 - Games Development 1 Gareth Bellaby Lab Worksheet 8 Page 19