Related
Given an arbitrary matrix, how do I find the co-ordinates that surrounds each city accurately?
E.g. City 1 has surrounding matrix of (0, 0), (0, 3), (1, 0), (1, 3), (2,0), (2, 1), (2, 2), (2, 3).
I have tried using a hardcoded method. Which is loop through each city's co-ordinate, however there are still inaccuracy in this method.
E.g. (0, 1) and from there check all 8 directions, up, down, left, right, upper left, upper right, bottom left, bottom right.
And if the char value is ' ', it is not a city which means it is part of a surrounding.
Is there any way which is much more efficient and more accurate in finding the surrounding?
void surroundings(int x, int y) {
// Initiate the first city struct information
citySummInfo.cityId = cityLocList[0].cityId;
citySummInfo.cityName = cityLocList[0].cityName;
citySummInfoList.push_back(citySummInfo);
// Add unique cityID & cityName into vector
for (size_t i = 0; i < cityLocList.size(); i++) {
for (size_t j = 0; j < citySummInfoList.size(); j++) {
if (cityLocList[i].cityId == citySummInfoList[j].cityId && cityLocList[i].cityName == citySummInfoList[j].cityName) {
break;
}
else if (j == citySummInfoList.size() - 1) {
citySummInfo.cityId = cityLocList[i].cityId;
citySummInfo.cityName = cityLocList[i].cityName;
citySummInfoList.push_back(citySummInfo);
}
}
}
// To populate the entire matrix with city ID
for (int i = 0; i < x; i++) {
for (int j = 0; j < y; j++) {
for (size_t k = 0; k < cityLocList.size(); k++) {
if (cityLocList[k].xGrid == i && cityLocList[k].yGrid == j)
mapPtr[j][i] = cityLocList[k].cityId + '0';
}
}
}
// Main process of getting the surrounding
for (size_t i = 0; i < citySummInfoList.size(); i++) {
for (size_t j = 0; j < cityLocList.size(); j++) {
if (citySummInfoList[i].cityId == cityLocList[j].cityId)
citySummInfoList[i].coOrdinates.push_back(to_string(cityLocList[j].xGrid) + "." + to_string(cityLocList[j].yGrid));
}
}
for (size_t i = 0; i < citySummInfoList.size(); i++) {
vector<string> temp;
for (size_t j = 0; j < citySummInfoList[i].coOrdinates.size(); j++) {
char cityId = citySummInfoList[i].cityId + '0';
char delim[] = { '.' };
vector<string> tempAxis = tokenizer(citySummInfoList[i].coOrdinates[j], delim, 1);
int xAxis = stoi(tempAxis[0]);
int yAxis = stoi(tempAxis[1]);
if (xAxis - 1 < 0 || yAxis - 1 < 0) {
continue;
}
if (mapPtr[xAxis - 1][yAxis + 1] != cityId) {
if (xAxis + 1 == x || yAxis + 1 == y || xAxis - 1 < 0 || yAxis - 1 < 0)
continue;
string coOrd = to_string(xAxis - 1) + "." + to_string(yAxis + 1);
if (find(temp.begin(), temp.end(), coOrd) == temp.end()) {
temp.push_back(coOrd);
}
}
if (mapPtr[xAxis - 1][yAxis] != cityId) {
if (xAxis + 1 == x || yAxis + 1 == y || xAxis - 1 < 0 || yAxis - 1 < 0)
continue;
string coOrd = to_string(xAxis - 1) + "." + to_string(yAxis);
if (find(temp.begin(), temp.end(), coOrd) == temp.end()) {
temp.push_back(coOrd);
}
}
if (mapPtr[xAxis - 1][yAxis - 1] != cityId) {
if (xAxis + 1 == x || yAxis + 1 == y || xAxis - 1 < 0 || yAxis - 1 < 0)
continue;
string coOrd = to_string(xAxis - 1) + "." + to_string(yAxis - 1);
if (find(temp.begin(), temp.end(), coOrd) == temp.end()) {
temp.push_back(coOrd);
}
}
if (mapPtr[xAxis][yAxis + 1] != cityId) {
if (xAxis + 1 == x || yAxis + 1 == y || xAxis - 1 < 0 || yAxis - 1 < 0)
continue;
string coOrd = to_string(xAxis) + "." + to_string(yAxis + 1);
if (find(temp.begin(), temp.end(), coOrd) == temp.end()) {
temp.push_back(coOrd);
}
}
if (mapPtr[xAxis][yAxis - 1] != cityId) {
if (xAxis + 1 == x || yAxis + 1 == y || xAxis - 1 < 0 || yAxis - 1 < 0)
continue;
string coOrd = to_string(xAxis) + "." + to_string(yAxis - 1);
if (find(temp.begin(), temp.end(), coOrd) == temp.end()) {
temp.push_back(coOrd);
}
}
if (mapPtr[xAxis + 1][yAxis + 1] != cityId) {
if (xAxis + 1 == x || yAxis + 1 == y || xAxis - 1 < 0 || yAxis - 1 < 0)
continue;
string coOrd = to_string(xAxis + 1) + "." + to_string(yAxis + 1);
if (find(temp.begin(), temp.end(), coOrd) == temp.end()) {
temp.push_back(coOrd);
}
}
if (mapPtr[xAxis + 1][yAxis] != cityId) {
if (xAxis + 1 == x || yAxis + 1 == y || xAxis - 1 < 0 || yAxis - 1 < 0)
continue;
string coOrd = to_string(xAxis + 1) + "." + to_string(yAxis);
if (find(temp.begin(), temp.end(), coOrd) == temp.end()) {
temp.push_back(coOrd);
}
}
if (mapPtr[xAxis + 1][yAxis - 1] != cityId) {
if (xAxis + 1 == x || yAxis + 1 == y || xAxis - 1 < 0 || yAxis - 1 < 0)
continue;
string coOrd = to_string(xAxis + 1) + "." + to_string(yAxis - 1);
if (find(temp.begin(), temp.end(), coOrd) == temp.end()) {
temp.push_back(coOrd);
}
}
}
citySummInfoList[i].coOrdinates.reserve(temp.size());
citySummInfoList[i].coOrdinates.insert(citySummInfoList[i].coOrdinates.end(), temp.begin(), temp.end());
}
}
Also, is there a possibility that my print function may cause such unreliability?
void print(int x, int y) {
for (int i = 0; i <= x + 2; i++) {
if (i == 0 || i >= x + 1) // Indentation for 1st and last row of non city locations
cout << setw(4) << " ";
for (int j = 0; j <= y + 2; j++) {
if ((i == 0 || i == x + 1) && j <= y + 1) { // Layout for first and last row
cout << "# ";
}
else if ((j == 0 && (i != 0 || i <= x))) { // Numbering for each row
if (x - i >= 0) {
cout << setw(3) << left << x - i << " ";
}
else {
cout << " "; // Indentation for last row of #s
}
}
else if (j == 1 && i < x + 2) { // Layout for first column
cout << "#";
}
else if (j == y + 2 && i != 0 && i < x + 1) { // Layout for last column
cout << " #";
}
else if (i == x + 2 && j < y + 1) { // Numbering for each column
cout << j - 1 << " ";
}
else if ((i != 0 && i != x + 2 && j != y + 2)) {
cout << " " << mapPtr[x - i][j - 2]; // Plot map value
}
}
cout << endl;
}
cout << endl;
}
This is an O(n) answer for your problem. The idea behind it is to find all points that are edges (a point is an edge if it is adjacent to anything which is not its own city).
After finding all edge points, loop through each of them and add all points adjacent to them which are whitespace characters.
#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef pair<int, int> point;
string m[] = {
" ",
" 555 ",
" 555 ",
" 222 555 ",
" 222 ",
" 222 ",
" 222 ",
" ",
"11 33 ",
"11 44",
" 44"
};
//hash function for pairs
struct hash_pair {
template <class T1, class T2>
size_t operator()(const pair<T1, T2>& p) const {
auto hash1 = hash<T1>{}(p.first);
auto hash2 = hash<T2>{}(p.second);
return hash1 ^ hash2;
}
};
bool insideBounds(int x, int y, point &size){
if(x < 0 || x >= size.x || y < 0 || y >= size.y) return false;
return true;
}
bool isEdge(int x, int y, point &size){
for(int i = -1; i < 2; ++i){
for(int j = -1; j < 2; ++j){
if(!insideBounds(x + j, y + i, size)) return true;
if(m[y + i][x + j] == ' ') return true;
}
}
return false;
}
void FindSurrounding(int x, int y){
//size of map
point size = make_pair(11, 11);
//current city id
char city = m[y][x];
//finding a point in edge of the city
point edge = make_pair(x, y);
for(int i = x - 1; i >= 0; --i)
if(m[y][i] == city) edge = make_pair(i, y);
//find all edge points
unordered_set<point, hash_pair> visited;
stack<point> toVisit;
toVisit.push(edge);
while(toVisit.size()){
point current = toVisit.top();
visited.insert(current);
toVisit.pop();
for(int i = -1; i < 2; ++i){
for(int j = -1; j < 2; ++j){
point p = make_pair(current.x + j, current.y + i);
if(!insideBounds(p.x, p.y, size) || m[p.y][p.x] != city) continue;
if(visited.find(p) == visited.end() && isEdge(p.x, p.y, size)){
toVisit.push(p);
}
}
}
}
//find surrounding slots
unordered_set<point, hash_pair> surrounding;
for (const auto& p: visited) {
for(int i = -1; i < 2; ++i){
for(int j = -1; j < 2; ++j){
point curr = make_pair(p.x + j, p.y + i);
if(insideBounds(curr.x, curr.y, size) && m[curr.y][curr.x] == ' '){
surrounding.insert(curr);
}
}
}
}
//print answer
for (const auto& p: surrounding) {
cout<<"("<<p.x<<", "<<p.y<<"), ";
}
}
int main()
{
FindSurrounding(0, 8);
return 0;
}
OUTPUT: (2, 10), (1, 10), (2, 9), (0, 10), (2, 8), (2, 7), (1, 7), (0, 7),
I have a homework where I have to write a C++ program to simulate a disease outbreak using SIR model (Susceptible, Infectious, Recover). The requirement is to use a 2D-array with 7x7 size where user will choose an X and Y coordinate to initialize an infectious person. A Susceptible person (S) will become infected (I) if there is an infected person in adjacent. Then an infected person will recover (R) if there is a Recover person in adjacent. The program will end if all people are recovered.
Example output:
Day 0 Day 1 Day 2
s s s s s s s s s s s s s s s s s s s s s
s s s s s s s s s s s s s s s i i i i i s
s s s s s s s s s i i i s s s i r r r i s
s s s i s s s s s i r i s s s i r r r i s
s s s s s s s s s i i i s s s i r r r i s
s s s s s s s s s s s s s s s i i i i i s
s s s s s s s s s s s s s s s s s s s s s
So far, I can only check the state in position (1,1), (1,7), (7,1), (7,7). If the next three position next to it have an infected person, it will update the state to nextDayState.
Here is my code so far for two functions, SpreadingDisease and RecoverState.
void recoverState(char currentDayState[SIZE][SIZE], char nextDayState[SIZE][SIZE], int sizeOfArray)//It will take in the currentState of Day 0. I also copy the elements in currentState to nextDayState so that it could work.
{
for (int i = 1; i < sizeOfArray + 1; ++i)
{
for (int j = 1; j <= sizeOfArray + 1; ++j)
{
if (currentDayState[i][j] == 'i')//If found any Infected, update it to Recover on the nextDayState array.
{
nextDayState[i][j] == 'r';
}
}
}
for (int i = 1; i < sizeOfArray + 1; ++i)
{
for (int j = 1; j <= sizeOfArray + 1; ++j)
{
currentDayState[i][j] = nextDayState[i][j];
//After all people are recover, update the currentState and output it to terminal.
}
}
}
void spreadDisease(const char currentDayState[SIZE][SIZE], char nextDayState[SIZE][SIZE], int sizeOfArray, int day = 1)
{
for (int i = 1; i < sizeOfArray + 1; ++i)
{
for (int j = 1; j <= sizeOfArray + 1; ++j)
{
if (currentDayState[i][j] == 's')
{
if (i == 1 && j == 1)
{
if (currentDayState[1][2] == 'i' || currentDayState[2][1] == 'i' || currentDayState[2][2] == 'i')
{
nextDayState[1][1] = 'i';
}
}
if (i == 1 && j == 7)
{
if (currentDayState[1][6] == 'i' || currentDayState[2][6] == 'i' || currentDayState[2][7] == 'i')
{
nextDayState[1][7] = 'i';
}
}
if (i == 7 && j == 1)
{
if (currentDayState[6][1] == 'i' || currentDayState[6][2] == 'i' || currentDayState[7][2] == 'i')
{
nextDayState[7][1] = 'i';
}
}
if (i == 7 && j == 7)
{
if (currentDayState[6][6] == 'i' || currentDayState[7][6] == 'i' || currentDayState[6][7] == 'i')
{
nextDayState[7][7] = 'i';
}
}
}
}
}
}
I figure out that If I can somehow get the X and Y coordinate from the user, then I can use that coordinate to update the state of the next day. Unfortunately, I don't know how to assign the X and Y coordinate into the function to start with it.
P/S: Thank you for all of your answers. I very appreciate your kindness. However, I should have mentioned the requirement of my assignment before. Since I only study till the User-Defined Functions part, I am not allowed to use anything else beyond that. So I am limited to use 2D-array, If-else, Looping only to solve this problem. Map and Vector is far beyond my knowledge right now xD.
This assignment remembered me to my days at the University (and that's quite long ago).
It seems like a variant of Conway's Game of Life which I got as assignment when I was first year's student. Hence, I couldn't resist...
Some notes before:
Two dimensional arrays are a bit inconvenient in C++. Either you have to use constant size or resizing them is not possible without using some kind of new[] (or g++'s VAL extension which is not standard conform). The better alternative is usually std::vector. Instead of nesting std::vectors, the two dimensions can be "faked" by appropriate operator overloads. For my luck, I had a minimal working version at hand from another recent answer of mine to Multi-threading benchmarking issues.
Concerning the simulation step i, I came to the following logic:
If patient X is
's': check all neighbours around her/him whether somebody is infected ('i'). If so, infect patient X.
'i' (infected in day before): let her/him recover ('r').
'r' (recovered): do nothing with him i.e. keep her/him recovered ('r').
Please, note that the tests of the different current cases can be done in one iteration of all rows/all columns of the board – no necessity to do this in separate functions.
The most interesting case is 's'. For patient X at [i][j], all neighbours have to be checked. These are the patients at [i + iP][j + jP] with iP in [-1, 1] and jP in [-1, 1]. Iterating over these 9 values will check the patient X itself when iP == 0 and jP == 0. This special case could be checked but I ignored it (as by the above logic) a patient cannot infect itself. This saves an extra check for iP and jP in the most inner loop which is IMHO welcome.
At closer glance, you will realize that [i + iP][j + jP] might result in invalid coordinates if i == 0 or i == number of rows - 1 or j == 0 or j == number of columns - 1. This would require a lot of additional tests to grant valid indices but I use another trick: I make the board respectively larger to provide a border around. I don't use it for writing but this provides me safe read-accesses. All I have to grant is that reading from these border cells will not tamper my simulation logic. I initialize the whole board including border cells with 's'. As border cells are never written (except in initialization) they are never infected what matches my concept.
So, this is my simulation step:
void doSimStep(const Board &board, Board &board1)
{
assert(board.getNumRows() == board1.getNumRows());
assert(board.getNumCols() == board1.getNumCols());
for (size_t i = 1, nRows = board.getNumRows() - 1; i < nRows; ++i) {
for (size_t j = 1, nCols = board.getNumCols() - 1; j < nCols; ++j) {
const char person = board[i][j];
char person1 = person;
switch (person) {
case 's': { // search for infection in neighbourhood
bool infect = false;
for (int iP = -1; !infect && iP <= 1; ++iP) {
for (int jP = -1; !infect && jP <= 1; ++jP) {
infect = board[i + iP][j + jP] == 'i';
}
}
person1 = infect ? 'i' : 's';
} break;
case 'i': // infected -> recover
// fall through
case 'r': // recovered: stable state
person1 = 'r';
break;
default: assert(false); // Wrong cell contents!
}
board1[i][j] = person1;
}
}
}
I don't get why user10522145 believes this cannot be done without recursion. (Btw., I believe the opposite: every recursion can be turned into an iteration which may accumulate or stack intermediate results.) I actually don't know where the recursion would be necessary considering that the OP already planned separate boards for current and new state (which simplifies things much).
Output of a simulation with a 9×9 board:
Init.:
s s s s s s s s s
s s s s s s s s s
s s s s s s s s s
s s s s s s s s s
s s s s s s s s s
s s s s s s s s s
s s s s s s s s s
s s s s s s s s s
s s s s s s s s s
Day 0:
s s s s s s s s s
s s s s s s s s s
s s s s s s s s s
s s s s s s s s s
s s s s i s s s s
s s s s s s s s s
s s s s s s s s s
s s s s s s s s s
s s s s s s s s s
Day 1:
s s s s s s s s s
s s s s s s s s s
s s s s s s s s s
s s s i i i s s s
s s s i r i s s s
s s s i i i s s s
s s s s s s s s s
s s s s s s s s s
s s s s s s s s s
Day 2:
s s s s s s s s s
s s s s s s s s s
s s i i i i i s s
s s i r r r i s s
s s i r r r i s s
s s i r r r i s s
s s i i i i i s s
s s s s s s s s s
s s s s s s s s s
Day 3:
s s s s s s s s s
s i i i i i i i s
s i r r r r r i s
s i r r r r r i s
s i r r r r r i s
s i r r r r r i s
s i r r r r r i s
s i i i i i i i s
s s s s s s s s s
Day 4:
i i i i i i i i i
i r r r r r r r i
i r r r r r r r i
i r r r r r r r i
i r r r r r r r i
i r r r r r r r i
i r r r r r r r i
i r r r r r r r i
i i i i i i i i i
Day 5:
r r r r r r r r r
r r r r r r r r r
r r r r r r r r r
r r r r r r r r r
r r r r r r r r r
r r r r r r r r r
r r r r r r r r r
r r r r r r r r r
r r r r r r r r r
No further progress detected on day 6.
Done.
Live Demo on coliru
And finally (spoiler alert) the complete source code:
#include <cassert>
#include <iomanip>
#include <iostream>
#include <vector>
template <typename VALUE>
class MatrixT; // forward declaration
template <typename VALUE>
void swap(MatrixT<VALUE>&, MatrixT<VALUE>&); // proto
template <typename VALUE>
class MatrixT {
friend void swap<VALUE>(MatrixT<VALUE>&, MatrixT<VALUE>&);
public:
typedef VALUE Value;
private:
size_t _nRows, _nCols;
std::vector<Value> _values;
public:
MatrixT(size_t nRows, size_t nCols, Value value = (Value)0):
_nRows(nRows), _nCols(nCols), _values(_nRows * _nCols, value)
{ }
~MatrixT() = default;
MatrixT(const MatrixT&) = default;
MatrixT& operator=(const MatrixT&) = default;
size_t getNumCols() const { return _nCols; }
size_t getNumRows() const { return _nRows; }
const std::vector<Value>& get() const { return _values; }
Value* operator[](size_t i) { return &_values[0] + i * _nCols; }
const Value* operator[](size_t i) const { return &_values[0] + i * _nCols; }
};
template <typename VALUE>
void swap(MatrixT<VALUE> &mat1, MatrixT<VALUE> &mat2)
{
std::swap(mat1._nRows, mat2._nRows);
std::swap(mat1._nCols, mat2._nCols);
std::swap(mat1._values, mat2._values);
}
typedef MatrixT<char> Board;
bool operator==(const Board &board1, const Board &board2)
{
return board1.getNumRows() == board2.getNumRows()
&& board1.getNumCols() == board2.getNumCols()
&& board1.get() == board2.get();
}
std::ostream& operator<<(std::ostream &out, const Board &board)
{
for (size_t i = 1, nRows = board.getNumRows() - 1; i < nRows; ++i) {
for (size_t j = 1, nCols = board.getNumCols() - 1; j < nCols; ++j) {
out << ' ' << board[i][j];
}
out << '\n';
}
return out;
}
void doSimStep(const Board &board, Board &board1)
{
assert(board.getNumRows() == board1.getNumRows());
assert(board.getNumCols() == board1.getNumCols());
for (size_t i = 1, nRows = board.getNumRows() - 1; i < nRows; ++i) {
for (size_t j = 1, nCols = board.getNumCols() - 1; j < nCols; ++j) {
const char person = board[i][j];
char person1 = person;
switch (person) {
case 's': { // search for infection in neighbourhood
bool infect = false;
for (int iP = -1; !infect && iP <= 1; ++iP) {
for (int jP = -1; !infect && jP <= 1; ++jP) {
infect = board[i + iP][j + jP] == 'i';
}
}
person1 = infect ? 'i' : 's';
} break;
case 'i': // infected -> recover
// fall through
case 'r': // recovered: stable state
person1 = 'r';
break;
default: assert(false); // Wrong cell contents!
}
board1[i][j] = person1;
}
}
}
int main()
{
size_t nRows = 9, nCols = 9;
#if 0 // disabled for demo
std::cout << "N Rows: "; std::cin >> nRows;
std::cout << "N Cols: "; std::cin >> nCols;
/// #todo check nRows, nCols for sufficient values
#endif // 0
// init board
std::cout << "Init.:\n";
Board board(nRows + 2, nCols + 2);
std::fill(board[0], board[nRows + 2], 's');
std::cout << board << '\n';
// infect somebody
size_t i = nRows / 2 + 1, j = nCols / 2 + 1;
#if 0 // disabled for demo
std::cout << "Patient 0:\n";
std::cout << "row: "; std::cin >> i;
std::cout << "col: "; std::cin >> j;
/// #todo check i, j for matching the boundaries
#endif // 0
board[i][j] = 'i';
// simulation loop
for (unsigned day = 0;;) {
std::cout << "Day " << day << ":\n";
std::cout << board << '\n';
// simulate next day
++day;
Board board1(board);
doSimStep(board, board1);
if (board == board1) {
std::cout << "No further progress detected on day "
<< day << ".\n";
break; // exit sim. loop
}
// store data of new day
swap(board, board1);
}
// done
std::cout << "Done.\n";
return 0;
}
You are using C++, so use the standard library to the maximum...
The magically optimized disease simulation function:
/*
*-----------------------
* Key:
* ----------------------
* 0 - Susceptible person
* 1 - Infected person
* 2 - Recovered person
*
* #param init_infect_x Person to infect at x position...
* #param init_infect_y Person to infect at y position...
* #param map_size_x Width of the map...
* #param map_size_y Height of the map...
*/
std::vector<std::vector<std::vector<int>>> disease_simulator(size_t const init_infect_x = 0u,
size_t const init_infect_y = 0u,
size_t const map_size_x = 7u, size_t const map_size_y = 7u)
{
if (map_size_x == 0u || map_size_y == 0u || init_infect_x + 1 > map_size_x || init_infect_x + 1 < 0 || init_infect_y
+ 1 > map_size_y || init_infect_y + 1 < 0) // Well, we can't create a map which is empty...
return std::vector<std::vector<std::vector<int>>>();
std::vector<std::vector<std::vector<int>>> map_list;
std::vector<std::pair<int, int>> spread_pos;
std::vector<std::vector<int>> map(map_size_y, std::vector<int>(map_size_x, 0));
map[init_infect_y][init_infect_x] = 1;
map_list.emplace_back(map);
while (std::adjacent_find(map.begin(), map.end(), std::not_equal_to<>()) != map.end())
{
for (auto i = 0; i < signed(map.size()); i++)
for (auto j = 0; j < signed(map[i].size()); j++)
if (map[i][j] == 1)
{
map[i][j] = 2;
spread_pos.emplace_back(std::make_pair(j, i));
}
for (auto const pos : spread_pos)
{
if (pos.second - 1 >= 0 && map[pos.second - 1][pos.first] == 0) // Up...
map[pos.second - 1][pos.first] = 1;
if (pos.first - 1 >= 0 && map[pos.second][pos.first - 1] == 0) // Left...
map[pos.second][pos.first - 1] = 1;
if (pos.second - 1 >= 0 && pos.first - 1 >= 0 && map[pos.second - 1][pos.first - 1] == 0) // Up left...
map[pos.second - 1][pos.first - 1] = 1;
if (pos.second - 1 >= 0 && pos.first + 2 <= signed(map_size_x) && map[pos.second - 1][pos.first + 1] == 0)
// Up right...
map[pos.second - 1][pos.first + 1] = 1;
if (pos.second + 2 <= signed(map_size_y) && map[pos.second + 1][pos.first] == 0) // Down...
map[pos.second + 1][pos.first] = 1;
if (pos.first + 2 <= signed(map_size_x) && map[pos.second][pos.first + 1] == 0) // Right...
map[pos.second][pos.first + 1] = 1;
if (pos.second + 2 <= signed(map_size_y) && pos.first + 2 <= signed(map_size_x) && map[pos.second + 1][pos.
first + 1] == 0) // Down right...
map[pos.second + 1][pos.first + 1] = 1;
if (pos.second + 2 <= signed(map_size_y) && pos.first - 1 >= 0 && map[pos.second + 1][pos.first - 1] == 0)
// Down left...
map[pos.second + 1][pos.first - 1] = 1;
}
map_list.emplace_back(map);
spread_pos.clear();
}
return map_list;
}
What this function does is that it gives you the map of each day simultaneously, now you can just iterate over them one by one...
Note: Also, don't forget to #include <algorithm> at the beginning for std::adjacent_find()...
Example:
int main()
{
auto days_map = disease_simulator();
for (auto i = 0u; i < days_map.size(); i++)
{
std::cout << "Day " << i << ":" << std::endl;
for (auto elem2 : days_map[i])
{
for (auto elem3 : elem2)
switch (elem3)
{
case 0:
std::cout << "s ";
break;
case 1:
std::cout << "i ";
break;
case 2:
std::cout << "r ";
break;
default:
std::cout << ' ';
break;
}
std::cout << std::endl;
}
std::cout << std::endl;
}
std::cout << "All people have recovered!" << std::endl;
return 0;
}
Edit: Live on coliru (Using 9x9 arrays with center as the infect point)
Well, see if it gives your desired output...
Kind regards,
Ruks.
I guess iteration might not work in this case ill suggest you use recursion with the array boundary values as the condition to stop recursion.
Hope it made sense
This script produces a fibonacci spiral which starts off in the centre and branches outward. Although the output is correct, i get an error message saying:
"Run-Time Check Failure #2 - Stack around the variable 'fibArr' was corrupted."
I've tried reducing fibCount by 1, but this caused the central number to be 0 which I dont want.
int fibFunc(int);
int main() {
//--setting variables
const int rowCount = 5, colCount = 5;
const int fibCount = rowCount * colCount - 1;
static int f;
int i, j;
//--creating grid
int grid[rowCount][colCount] = { 0 };
//Create fibArr
f = fibCount;
int fibArr[fibCount];
while (f >= 0) {
fibArr[f] = fibFunc(f);
f--;
}
//directions settings
enum direction {
DOWN, RIGHT, UP, LEFT
} d = DOWN;
//spiral loop
int R = 0, C = 0; //R for row, C for column
f = fibCount;
while (f >= 0) {
grid[R][C] = fibArr[f];
f--;
if (d == DOWN) {
if (grid[R+1][C] == 0)
R++;
else d = RIGHT;
}
if (d == RIGHT) {
if (grid[R][C+1] == 0)
C++;
else d = UP;
}
if (d == UP) {
if (grid[R-1][C] == 0)
R--;
else d = LEFT;
}
if (d == LEFT) {
if (grid[R][C-1] == 0)
C--;
else {
d = DOWN;
R++;
}
}
}
printFib(fibArr, fibCount);
for (i = 0; i < rowCount; i++) {
for (j = 0; j < colCount; j++) {
cout << setw(7) << grid[i][j];
}
cout << endl;
cout << endl;
}
system("pause>nul");
}
int fibFunc(int n) {
if (n == 0 || n == 1)
return 1;
else
return fibFunc(n - 1) + fibFunc(n - 2);
}
Output
75025 55 89 144 233
46368 34 1 2 377
28657 21 1 3 610
17711 13 8 5 987
10946 6765 4181 2584 1597
As juanchopanza pointed out, array indexing is from 0 to size - 1.
const int rowCount = 5, colCount = 5;
const int fibCount = rowCount * colCount - 1;
This would result in:
fibCount = 24 = ( 5 * 5 ) - 1;
Further down, at your while loop:
int R = 0, C = 0; //R for row, C for column
f = fibCount;
while (f >= 0) {
grid[R][C] = fibArr[f]; // f = 24; size = 24;
...
}
Here you take a value from your array fibArray with the index of 24. Last index would be 23.
Change this to:
const int fibCount = rowCount * colCount;
Then before your while loop:
f = fibCount - 1;
Besides that I would use a single dimensional array like this:
int rows = 5, cols = 5;
char* tst = new char[ rows * cols ];
int select_row = 2;
int select_col = 3;
tst[ select_row * cols + select_col ] = 65;
char c = tst[ select_row * cols + select_col ];
delete[] tst;
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
Firstly, I'm a complete amateur so I may mix up some terminology.
I've been working on a Neural Network to play Connect 4 / Four In A Row.
The current design of the network model is 170 input values, 417 hidden neurons and 1 output neuron. The network is fully connected, i.e. every input is connected to every hidden neuron and every hidden neuron is connected to the output node.
Every connection has an independent weight, and every hidden node, and the single output node, have an additional bias node with a weight.
The input representation of 170 values for the game-state of Connect 4 is:
42 pairs of values (84 input variables) which denote whether a space is occupied by player 1, player 2 or is vacant.
0,0 means it's free
1,0 means it's player 1's position
0,1 means it's player 2's position
1,1 is not possible
Another 42 pairs of values (84 input variables) which denote whether adding a piece here will give player 1 or player 2 a "Connect 4"/"Four In a Row". The combination of values means the same as above.
2 final input variables to denote who's turn it is:
1,0 player 1's turn
0,1 player 2's turn
1,1 and 0,0 are not possible
I measured the average Mean Square Error of 100 games over 10,000 total games of various configurations to arrive at:
417 hidden neurons
Alpha and Beta learning rate of 0.1 at the start and dropping to 0.01 linearly across the total number of epochs
A lambda value of 0.5
90 out of 100 moves are random at the start and drop down to 10 out of every 100 after the first 50% of epochs. So at the midway point 10 out of 100 moves are random
The first 50% of epochs start with a random move
Sigmoid Activation Function used in every node
This image shows the results of the various configurations plotted with a logarithmic scale. This is how I determined which configuration to use.
I calculate this Mean Square Error by comparing the output of a board in a win-state against either -1 for a player 2 win and 1 for a player 1 win. I add these up every 100 games and divide the total by 100 to get 1000 values to plot in the above graph. I.e. the code snippet is:
if(board.InARowConnected(4) == Board<7,6,4>::Player1)
{
totalLoss += NN->BackPropagateFinal({1},previousNN,alpha,beta,lambda);
winState = true;
}
else if(board.InARowConnected(4) == Board<7,6,4>::Player2)
{
totalLoss += NN->BackPropagateFinal({-1},previousNN,alpha,beta,lambda);
winState = true;
}
else if(!board.IsThereAvailableMove())
{
totalLoss += NN->BackPropagateFinal({0},previousNN,alpha,beta,lambda);
winState = true;
}
...
if(gameNumber % 100 == 0 && gameNumber != 0)
{
totalLoss = totalLoss / gamesToOutput;
matchFile << std::fixed << std::setprecision(51) << totalLoss << std::endl;
totalLoss = 0.0;
}
The way I'm training the network is by having it play against itself over and over again. It's a feed-forward network and I'm using TD-Lambda to train it for every move (every move that wasn't randomly chosen).
The Board State that is given to the Neural Network is done through this:
template<std::size_t BoardWidth, std::size_t BoardHeight, std::size_t InARow>
void create_board_state(std::array<double,BoardWidth*BoardHeight*4+2>& gameState, const Board<BoardWidth,BoardHeight,InARow>& board,
const typename Board<BoardWidth,BoardHeight,InARow>::Player player)
{
using BoardType = Board<BoardWidth,BoardHeight,InARow>;
auto bb = board.GetBoard();
std::size_t stateIndex = 0;
for(std::size_t boardIndex = 0; boardIndex < BoardWidth*BoardHeight; ++boardIndex, stateIndex += 2)
{
if(bb[boardIndex] == BoardType::Free)
{
gameState[stateIndex] = 0;
gameState[stateIndex+1] = 0;
}
else if(bb[boardIndex] == BoardType::Player1)
{
gameState[stateIndex] = 1;
gameState[stateIndex+1] = 0;
}
else
{
gameState[stateIndex] = 0;
gameState[stateIndex+1] = 1;
}
}
for(std::size_t x = 0; x < BoardWidth; ++x)
{
for(std::size_t y = 0; y < BoardHeight; ++y)
{
auto testBoard1 = board;
auto testBoard2 = board;
testBoard1.SetBoardChecker(x,y,Board<BoardWidth,BoardHeight,InARow>::Player1);
testBoard2.SetBoardChecker(x,y,Board<BoardWidth,BoardHeight,InARow>::Player2);
// player 1's set
if(testBoard1.InARowConnected(4) == Board<7,6,4>::Player1)
gameState[stateIndex] = 1;
else
gameState[stateIndex] = 0;
// player 2's set
if(testBoard2.InARowConnected(4) == Board<7,6,4>::Player2)
gameState[stateIndex+1] = 1;
else
gameState[stateIndex+1] = 0;
stateIndex += 2;
}
}
if(player == Board<BoardWidth,BoardHeight,InARow>::Player1)
{
gameState[stateIndex] = 1;
gameState[stateIndex+1] = 0;
}
else
{
gameState[stateIndex] = 0;
gameState[stateIndex+1] = 1;
}
}
It's templated to make changing things later easier. I don't believe there's anything wrong in the above.
My Sigmoid activation function:
inline double sigmoid(const double x)
{
// return 1.0 / (1.0 + std::exp(-x));
return x / (1.0 + std::abs(x));
}
My Neuron Class
template<std::size_t NumInputs>
class Neuron
{
public:
Neuron()
{
for(auto& i : m_inputValues)
i = 9;
for(auto& e : m_eligibilityTraces)
e = 9;
for(auto& w : m_weights)
w = 9;
m_biasWeight = 9;
m_biasEligibilityTrace = 9;
m_outputValue = 9;
}
void SetInputValue(const std::size_t index, const double value)
{
m_inputValues[index] = value;
}
void SetWeight(const std::size_t index, const double weight)
{
if(std::isnan(weight))
throw std::runtime_error("Shit! this is a nan bread");
m_weights[index] = weight;
}
void SetBiasWeight(const double weight)
{
m_biasWeight = weight;
}
double GetInputValue(const std::size_t index) const
{
return m_inputValues[index];
}
double GetWeight(const std::size_t index) const
{
return m_weights[index];
}
double GetBiasWeight() const
{
return m_biasWeight;
}
double CalculateOutput()
{
m_outputValue = 0;
for(std::size_t i = 0; i < NumInputs; ++i)
{
m_outputValue += m_inputValues[i] * m_weights[i];
}
m_outputValue += 1.0 * m_biasWeight;
m_outputValue = sigmoid(m_outputValue);
return m_outputValue;
}
double GetOutput() const
{
return m_outputValue;
}
double GetEligibilityTrace(const std::size_t index) const
{
return m_eligibilityTraces[index];
}
void SetEligibilityTrace(const std::size_t index, const double eligibility)
{
m_eligibilityTraces[index] = eligibility;
}
void SetBiasEligibility(const double eligibility)
{
m_biasEligibilityTrace = eligibility;
}
double GetBiasEligibility() const
{
return m_biasEligibilityTrace;
}
void ResetEligibilityTraces()
{
for(auto& e : m_eligibilityTraces)
e = 0;
m_biasEligibilityTrace = 0;
}
private:
std::array<double,NumInputs> m_inputValues;
std::array<double,NumInputs> m_weights;
std::array<double,NumInputs> m_eligibilityTraces;
double m_biasWeight;
double m_biasEligibilityTrace;
double m_outputValue;
};
My Neural Network class
template
class NeuralNetwork
{
public:
void RandomiseWeights()
{
double inputToHiddenRange = 4.0 * std::sqrt(6.0 / (NumInputs+1+NumOutputs));
RandomGenerator inputToHidden(-inputToHiddenRange,inputToHiddenRange);
double hiddenToOutputRange = 4.0 * std::sqrt(6.0 / (NumHidden+1+1));
RandomGenerator hiddenToOutput(-hiddenToOutputRange,hiddenToOutputRange);
for(auto& hiddenNeuron : m_hiddenNeurons)
{
for(std::size_t i = 0; i < NumInputs; ++i)
hiddenNeuron.SetWeight(i, inputToHidden());
hiddenNeuron.SetBiasWeight(inputToHidden());
}
for(auto& outputNeuron : m_outputNeurons)
{
for(std::size_t h = 0; h < NumHidden; ++h)
outputNeuron.SetWeight(h, hiddenToOutput());
outputNeuron.SetBiasWeight(hiddenToOutput());
}
}
double GetOutput(const std::size_t index) const
{
return m_outputNeurons[index].GetOutput();
}
std::array<double,NumOutputs> GetOutputs()
{
std::array<double, NumOutputs> returnValue;
for(std::size_t o = 0; o < NumOutputs; ++o)
returnValue[o] = m_outputNeurons[o].GetOutput();
return returnValue;
}
void SetInputValue(const std::size_t index, const double value)
{
for(auto& hiddenNeuron : m_hiddenNeurons)
hiddenNeuron.SetInputValue(index, value);
}
std::array<double,NumOutputs> Calculate()
{
for(auto& h : m_hiddenNeurons)
h.CalculateOutput();
for(auto& o : m_outputNeurons)
o.CalculateOutput();
return GetOutputs();
}
std::array<double,NumOutputs> FeedForward(const std::array<double,NumInputs>& inputValues)
{
for(std::size_t h = 0; h < NumHidden; ++h)//auto& hiddenNeuron : m_hiddenNeurons)
{
for(std::size_t i = 0; i < NumInputs; ++i)
m_hiddenNeurons[h].SetInputValue(i,inputValues[i]);
m_hiddenNeurons[h].CalculateOutput();
}
std::array<double, NumOutputs> returnValue;
for(std::size_t h = 0; h < NumHidden; ++h)
{
auto hiddenOutput = m_hiddenNeurons[h].GetOutput();
for(std::size_t o = 0; o < NumOutputs; ++o)
m_outputNeurons[o].SetInputValue(h, hiddenOutput);
}
for(std::size_t o = 0; o < NumOutputs; ++o)
{
returnValue[o] = m_outputNeurons[o].CalculateOutput();
}
return returnValue;
}
double BackPropagateFinal(const std::array<double,NumOutputs>& actualValues, const NeuralNetwork<NumInputs,NumHidden,NumOutputs>* NN, const double alpha, const double beta, const double lambda)
{
for(std::size_t iO = 0; iO < NumOutputs; ++iO)
{
auto y = NN->m_outputNeurons[iO].GetOutput();
auto y1 = actualValues[iO];
for(std::size_t iH = 0; iH < NumHidden; ++iH)
{
auto e = NN->m_outputNeurons[iO].GetEligibilityTrace(iH);
auto h = NN->m_hiddenNeurons[iH].GetOutput();
auto w = NN->m_outputNeurons[iO].GetWeight(iH);
double e1 = lambda * e + (y * (1.0 - y) * h);
double w1 = w + beta * (y1 - y) * e1;
m_outputNeurons[iO].SetEligibilityTrace(iH,e1);
m_outputNeurons[iO].SetWeight(iH,w1);
}
auto e = NN->m_outputNeurons[iO].GetBiasEligibility();
auto h = 1.0;
auto w = NN->m_outputNeurons[iO].GetBiasWeight();
double e1 = lambda * e + (y * (1.0 - y) * h);
double w1 = w + beta * (y1 - y) * e1;
m_outputNeurons[iO].SetBiasEligibility(e1);
m_outputNeurons[iO].SetBiasWeight(w1);
}
for(std::size_t iH = 0; iH < NumHidden; ++iH)
{
auto h = NN->m_hiddenNeurons[iH].GetOutput();
for(std::size_t iI = 0; iI < NumInputs; ++iI)
{
auto e = NN->m_hiddenNeurons[iH].GetEligibilityTrace(iI);
auto x = NN->m_hiddenNeurons[iH].GetInputValue(iI);
auto u = NN->m_hiddenNeurons[iH].GetWeight(iI);
double sumError = 0;
for(std::size_t iO = 0; iO < NumOutputs; ++iO)
{
auto w = NN->m_outputNeurons[iO].GetWeight(iH);
auto y = NN->m_outputNeurons[iO].GetOutput();
auto y1 = actualValues[iO];
auto grad = y1 - y;
double e1 = lambda * e + (y * (1.0 - y) * w * h * (1.0 - h) * x);
sumError += grad * e1;
}
double u1 = u + alpha * sumError;
m_hiddenNeurons[iH].SetEligibilityTrace(iI,sumError);
m_hiddenNeurons[iH].SetWeight(iI,u1);
}
auto e = NN->m_hiddenNeurons[iH].GetBiasEligibility();
auto x = 1.0;
auto u = NN->m_hiddenNeurons[iH].GetBiasWeight();
double sumError = 0;
for(std::size_t iO = 0; iO < NumOutputs; ++iO)
{
auto w = NN->m_outputNeurons[iO].GetWeight(iH);
auto y = NN->m_outputNeurons[iO].GetOutput();
auto y1 = actualValues[iO];
auto grad = y1 - y;
double e1 = lambda * e + (y * (1.0 - y) * w * h * (1.0 - h) * x);
sumError += grad * e1;
}
double u1 = u + alpha * sumError;
m_hiddenNeurons[iH].SetBiasEligibility(sumError);
m_hiddenNeurons[iH].SetBiasWeight(u1);
}
double retVal = 0;
for(std::size_t o = 0; o < NumOutputs; ++o)
{
retVal += 0.5 * alpha * std::pow((NN->GetOutput(o) - GetOutput(0)),2);
}
return retVal / NumOutputs;
}
double BackPropagate(const NeuralNetwork<NumInputs,NumHidden,NumOutputs>* NN, const double alpha, const double beta, const double lambda)
{
for(std::size_t iO = 0; iO < NumOutputs; ++iO)
{
auto y = NN->m_outputNeurons[iO].GetOutput();
auto y1 = m_outputNeurons[iO].GetOutput();
for(std::size_t iH = 0; iH < NumHidden; ++iH)
{
auto e = NN->m_outputNeurons[iO].GetEligibilityTrace(iH);
auto h = NN->m_hiddenNeurons[iH].GetOutput();
auto w = NN->m_outputNeurons[iO].GetWeight(iH);
double e1 = lambda * e + (y * (1.0 - y) * h);
double w1 = w + beta * (y1 - y) * e1;
m_outputNeurons[iO].SetEligibilityTrace(iH,e1);
m_outputNeurons[iO].SetWeight(iH,w1);
}
auto e = NN->m_outputNeurons[iO].GetBiasEligibility();
auto h = 1.0;
auto w = NN->m_outputNeurons[iO].GetBiasWeight();
double e1 = lambda * e + (y * (1.0 - y) * h);
double w1 = w + beta * (y1 - y) * e1;
m_outputNeurons[iO].SetBiasEligibility(e1);
m_outputNeurons[iO].SetBiasWeight(w1);
}
for(std::size_t iH = 0; iH < NumHidden; ++iH)
{
auto h = NN->m_hiddenNeurons[iH].GetOutput();
for(std::size_t iI = 0; iI < NumInputs; ++iI)
{
auto e = NN->m_hiddenNeurons[iH].GetEligibilityTrace(iI);
auto x = NN->m_hiddenNeurons[iH].GetInputValue(iI);
auto u = NN->m_hiddenNeurons[iH].GetWeight(iI);
double sumError = 0;
for(std::size_t iO = 0; iO < NumOutputs; ++iO)
{
auto w = NN->m_outputNeurons[iO].GetWeight(iH);
auto y = NN->m_outputNeurons[iO].GetOutput();
auto y1 = m_outputNeurons[iO].GetOutput();
auto grad = y1 - y;
double e1 = lambda * e + (y * (1.0 - y) * w * h * (1.0 - h) * x);
sumError += grad * e1;
}
double u1 = u + alpha * sumError;
m_hiddenNeurons[iH].SetEligibilityTrace(iI,sumError);
m_hiddenNeurons[iH].SetWeight(iI,u1);
}
auto e = NN->m_hiddenNeurons[iH].GetBiasEligibility();
auto x = 1.0;
auto u = NN->m_hiddenNeurons[iH].GetBiasWeight();
double sumError = 0;
for(std::size_t iO = 0; iO < NumOutputs; ++iO)
{
auto w = NN->m_outputNeurons[iO].GetWeight(iH);
auto y = NN->m_outputNeurons[iO].GetOutput();
auto y1 = m_outputNeurons[iO].GetOutput();
auto grad = y1 - y;
double e1 = lambda * e + (y * (1.0 - y) * w * h * (1.0 - h) * x);
sumError += grad * e1;
}
double u1 = u + alpha * sumError;
m_hiddenNeurons[iH].SetBiasEligibility(sumError);
m_hiddenNeurons[iH].SetBiasWeight(u1);
}
double retVal = 0;
for(std::size_t o = 0; o < NumOutputs; ++o)
{
retVal += 0.5 * alpha * std::pow((NN->GetOutput(o) - GetOutput(0)),2);
}
return retVal / NumOutputs;
}
std::array<double,NumInputs*NumHidden+NumHidden+NumHidden*NumOutputs+NumOutputs> GetNetworkWeights() const
{
std::array<double,NumInputs*NumHidden+NumHidden+NumHidden*NumOutputs+NumOutputs> returnVal;
std::size_t weightPos = 0;
for(std::size_t h = 0; h < NumHidden; ++h)
{
for(std::size_t i = 0; i < NumInputs; ++i)
returnVal[weightPos++] = m_hiddenNeurons[h].GetWeight(i);
returnVal[weightPos++] = m_hiddenNeurons[h].GetBiasWeight();
}
for(std::size_t o = 0; o < NumOutputs; ++o)
{
for(std::size_t h = 0; h < NumHidden; ++h)
returnVal[weightPos++] = m_outputNeurons[o].GetWeight(h);
returnVal[weightPos++] = m_outputNeurons[o].GetBiasWeight();
}
return returnVal;
}
static constexpr std::size_t NumWeights = NumInputs*NumHidden+NumHidden+NumHidden*NumOutputs+NumOutputs;
void SetNetworkWeights(const std::array<double,NumInputs*NumHidden+NumHidden+NumHidden*NumOutputs+NumOutputs>& weights)
{
std::size_t weightPos = 0;
for(std::size_t h = 0; h < NumHidden; ++h)
{
for(std::size_t i = 0; i < NumInputs; ++i)
m_hiddenNeurons[h].SetWeight(i, weights[weightPos++]);
m_hiddenNeurons[h].SetBiasWeight(weights[weightPos++]);
}
for(std::size_t o = 0; o < NumOutputs; ++o)
{
for(std::size_t h = 0; h < NumHidden; ++h)
m_outputNeurons[o].SetWeight(h, weights[weightPos++]);
m_outputNeurons[o].SetBiasWeight(weights[weightPos++]);
}
}
void ResetEligibilityTraces()
{
for(auto& h : m_hiddenNeurons)
h.ResetEligibilityTraces();
for(auto& o : m_outputNeurons)
o.ResetEligibilityTraces();
}
private:
std::array<Neuron<NumInputs>,NumHidden> m_hiddenNeurons;
std::array<Neuron<NumHidden>,NumOutputs> m_outputNeurons;
};
I believe one of the places I may have an issue is the BackPropagate and BackPropagateFinal methods in the Neural Network class.
Here's my main function that is training the network:
int main()
{
std::ofstream matchFile("match.txt");
RandomGenerator randomPlayerStart(0,1);
RandomGenerator randomMove(0,100);
Board<7,6,4> board;
auto NN = new NeuralNetwork<7*6*4+2,417,1>();
auto previousNN = new NeuralNetwork<7*6*4+2,417,1>();
NN->RandomiseWeights();
const int numGames = 3000000;
double alpha = 0.1;
double beta = 0.1;
double lambda = 0.5;
double learningRateFloor = 0.01;
double decayRateAlpha = (alpha - learningRateFloor) / numGames;
double decayRateBeta = (beta - learningRateFloor) / numGames;
double randomChance = 90; // out of 100
double randomChangeFloor = 10;
double percentToReduceRandomOver = 0.5;
double randomChangeDecay = (randomChance-randomChangeFloor) / (numGames*percentToReduceRandomOver);
double percentOfGamesToRandomiseStart = 0.5;
int numGamesWonP1 = 0;
int numGamesWonP2 = 0;
int gamesToOutput = 100;
matchFile << "Num Games: " << numGames << "\t\ta,b,l: " << alpha << ", " << beta << ", " << lambda << std::endl;
Board<7,6,4>::Player playerStart = randomPlayerStart() > 0.5 ? Board<7,6,4>::Player1 : Board<7,6,4>::Player2;
double totalLoss = 0.0;
for(int gameNumber = 0; gameNumber < numGames; ++gameNumber)
{
bool winState = false;
Board<7,6,4>::Player playerWhoTurnItIs = playerStart;
playerStart = playerStart == Board<7,6,4>::Player1 ? Board<7,6,4>::Player2 : Board<7,6,4>::Player1;
board.ClearBoard();
int turnNumber = 0;
while(!winState)
{
Board<7,6,4>::Player playerWhoTurnItIsNot = playerWhoTurnItIs == Board<7,6,4>::Player1 ? Board<7,6,4>::Player2 : Board<7,6,4>::Player1;
bool wasRandomMove = false;
std::size_t selectedMove;
bool moveFound = false;
if(board.IsThereAvailableMove())
{
std::vector<std::size_t> availableMoves;
if((gameNumber <= numGames * percentOfGamesToRandomiseStart && turnNumber == 0) || randomMove() > 100.0-randomChance)
wasRandomMove = true;
std::size_t bestMove = 8;
double bestWorstResponse = playerWhoTurnItIs == Board<7,6,4>::Player1 ? std::numeric_limits<double>::min() : std::numeric_limits<double>::max();
for(std::size_t m = 0; m < 7; ++m)
{
Board<7,6,4> testBoard = board; // make a copy of the current board to run our tests
if(testBoard.AvailableMoveInColumn(m))
{
if(wasRandomMove)
{
availableMoves.push_back(m);
}
testBoard.AddChecker(m, playerWhoTurnItIs);
double worstResponse = playerWhoTurnItIs == Board<7,6,4>::Player1 ? std::numeric_limits<double>::max() : std::numeric_limits<double>::min();
std::size_t worstMove = 8;
for(std::size_t m2 = 0; m2 < 7; ++m2)
{
Board<7,6,4> testBoard2 = testBoard;
if(testBoard2.AvailableMoveInColumn(m2))
{
testBoard2.AddChecker(m,playerWhoTurnItIsNot);
StateType state;
create_board_state(state, testBoard2, playerWhoTurnItIs);
auto outputs = NN->FeedForward(state);
if(playerWhoTurnItIs == Board<7,6,4>::Player1 && (outputs[0] < worstResponse || worstMove == 8))
{
worstResponse = outputs[0];
worstMove = m2;
}
else if(playerWhoTurnItIs == Board<7,6,4>::Player2 && (outputs[0] > worstResponse || worstMove == 8))
{
worstResponse = outputs[0];
worstMove = m2;
}
}
}
if(playerWhoTurnItIs == Board<7,6,4>::Player1 && (worstResponse > bestWorstResponse || bestMove == 8))
{
bestWorstResponse = worstResponse;
bestMove = m;
}
else if(playerWhoTurnItIs == Board<7,6,4>::Player2 && (worstResponse < bestWorstResponse || bestMove == 8))
{
bestWorstResponse = worstResponse;
bestMove = m;
}
}
}
if(bestMove == 8)
{
std::cerr << "wasn't able to determine the best move to make" << std::endl;
return 0;
}
if(gameNumber <= numGames * percentOfGamesToRandomiseStart && turnNumber == 0)
{
std::size_t rSelection = int(randomMove()) % (availableMoves.size());
selectedMove = availableMoves[rSelection];
moveFound = true;
}
else if(wasRandomMove)
{
std::remove(availableMoves.begin(),availableMoves.end(),bestMove);
std::size_t rSelection = int(randomMove()) % (availableMoves.size());
selectedMove = availableMoves[rSelection];
moveFound = true;
}
else
{
selectedMove = bestMove;
moveFound = true;
}
}
StateType prevState;
create_board_state(prevState,board,playerWhoTurnItIs);
NN->FeedForward(prevState);
*previousNN = *NN;
// now that we have the move, add it to the board
StateType state;
board.AddChecker(selectedMove,playerWhoTurnItIs);
create_board_state(state,board,playerWhoTurnItIsNot);
auto outputs = NN->FeedForward(state);
if(board.InARowConnected(4) == Board<7,6,4>::Player1)
{
totalLoss += NN->BackPropagateFinal({1},previousNN,alpha,beta,lambda);
winState = true;
++numGamesWonP1;
}
else if(board.InARowConnected(4) == Board<7,6,4>::Player2)
{
totalLoss += NN->BackPropagateFinal({-1},previousNN,alpha,beta,lambda);
winState = true;
++numGamesWonP2;
}
else if(!board.IsThereAvailableMove())
{
totalLoss += NN->BackPropagateFinal({0},previousNN,alpha,beta,lambda);
winState = true;
}
else if(turnNumber > 0 && !wasRandomMove)
{
NN->BackPropagate(previousNN,alpha,beta,lambda);
}
if(!wasRandomMove)
{
outputs = NN->FeedForward(state);
}
++turnNumber;
playerWhoTurnItIs = playerWhoTurnItIsNot;
}
alpha -= decayRateAlpha;
beta -= decayRateBeta;
NN->ResetEligibilityTraces();
if(gameNumber > 0 && randomChance > randomChangeFloor && gameNumber <= numGames * percentToReduceRandomOver)
{
randomChance -= randomChangeDecay;
if(randomChance < randomChangeFloor)
randomChance = randomChangeFloor;
}
if(gameNumber % gamesToOutput == 0 && gameNumber != 0)
{
totalLoss = totalLoss / gamesToOutput;
matchFile << std::fixed << std::setprecision(51) << totalLoss << std::endl;
totalLoss = 0.0;
}
}
matchFile << std::endl << "Games won: " << numGamesWonP1 << " . " << numGamesWonP2 << std::endl;
auto weights = NN->GetNetworkWeights();
matchFile << std::endl;
matchFile << std::endl;
for(const auto& w : weights)
matchFile << std::fixed << std::setprecision(51) << w << ", \n";
matchFile << std::endl;
return 0;
}
One place I think I may have an issue is the minimax that's choosing the best move to make.
There's a few additional pieces that I don't think are too pertinent to the issues I'm having.
The Problems
It doesn't seem to matter whether I train 1000 games or 3000000 games, either Player 1 or Player 2 will win the vast majority of games. To the point of like 90 out of 100 games won by one player. If I output the actual individual game moves and outputs I can see that the games won by the other player are almost always the result of a lucky random move.
At the same time, I notice that the prediction outputs sort of "favour" a player. I.e. the outputs seem to be on the negative side of 0, so Player 1 is always making the best moves it can for example, but they all seem to be predicted toward Player 2 winning.
Sometimes it's Player 1 who wins majority, other times it's Player 2. I'm assuming that this is due to the random weights initialising
slight toward one player.
The first game or so doesn't favour one player over the other, but it very quickly starts to "lean" one way.
I've tried training now over 3000000 games, that took 3 days, but the network still doesn't seem to be able to make good decisions. I've tested the network by having it play other "bots" on riddles.io Connect 4 comp.
It fails to recognise that it needs to block the opponents 4 in a row
It, even after 3000000 games, doesn't play the centre column as the first move, which we know is the only starting move you can make that will guarantee a win.
Any help and direction would be greatly appreciated. Specifically, is my implementation of TD-Lambda back-propagation correct?
Given two strings (s1, s2), Levenshtein Distance is the minimum number of operations needed to change s1 to s2 or vice versa.
I want to show the result of changing s1 to s2. For example, changing Sunday to Saturday needs 3 operations. I need to show S++u+day. "+" is for each operations needed.
Here is a javascript snippet that returns what you want. If you are familiar with the dynamic programming algorithm you should be able follow this code. All the string operations/manipulation of the return string r and handling of min/curMin are what's changed from the original version.
function edits(t, s) {
var r = "";
if (s === t) {
return s;
}
var n = s.length, m = t.length;
if (n === 0 || m === 0) {
return "+".repeat(n + m);
}
var x, y, a, b, c, min = 0;
var p = new Array(n);
for (y = 0; y < n;)
p[y] = ++y;
for (x = 0; x < m; x++) {
var e = t.charCodeAt(x);
c = x;
b = x + 1;
var currMin = min;
min = n + 1;
for (y = 0; y < n; y++) {
a = p[y];
if (a < c || b < c) {
b = (a > b ? b + 1 : a + 1);
}
else {
if (e !== s.charCodeAt(y)) {
b = c + 1;
}
else {
b = c;
}
}
if (b < min) {
min = b;
}
p[y] = b;
c = a;
}
if (min > currMin) {
r += "+";
}
else {
r += t[x];
}
}
return r;
}
EDIT: The implementation above is an version optimized for speed and space so might be harder to read. The implemetation below is a modified version of the JS version from Wikipedia and should be easier to follow.
function getEditDistance(a, b) {
if(a.length === 0) return "+".repeat(b.length);
if(b.length === 0) return "+".repeat(a.length);
var matrix = [];
// increment along the first column of each row
var i;
for(i = 0; i <= b.length; i++){
matrix[i] = [i];
}
// increment each column in the first row
var j;
for(j = 0; j <= a.length; j++){
matrix[0][j] = j;
}
var r = "", min = 0;;
// Fill in the rest of the matrix
for(i = 1; i <= b.length; i++){
var currMin = min;
min = a.length + 1;
for(j = 1; j <= a.length; j++){
if(b.charAt(i-1) == a.charAt(j-1)){
matrix[i][j] = matrix[i-1][j-1];
} else {
matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution
Math.min(matrix[i][j-1] + 1, // insertion
matrix[i-1][j] + 1)); // deletion
}
if (matrix[i][j] < min) {
min = matrix[i][j];
}
}
if (min > currMin) {
r += "+";
}
else {
r += b[i-1];
}
}
return r;
}
EDIT2: Added explanation of the algorithm and example output
Below is the levenshtein matrix from the input strings "kitten" and "sitting". What I changed in the original algorithm is that I added a check if the current rows minimum value is larger then the previous rows minimum, and if it is, there is an edit in the current row and thus adding a "+". If not and the " edit cost" for the current row is the same as the previous. Then there is no edit necessary and we just add the current character to the result string. You can follow the algorithm row by row in the image (starting at row 1, and column 1)
Examples:
> getEditDistance("kitten", "sitting");
'+itt+n+'
> getEditDistance("Sunday", "Saturday");
'S++u+day'