Skip to content

Commit

Permalink
feat: add new pathfinding example
Browse files Browse the repository at this point in the history
Issue #15
  • Loading branch information
morinim committed Jun 14, 2024
1 parent 679de8f commit 329fe1e
Show file tree
Hide file tree
Showing 3 changed files with 362 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

find_package(Threads)

file(GLOB EXAMPLES_SRC CONFIGURE_DEPENDS *.cc symbolic_regression/*.cc sonar/*.cc)
file(GLOB EXAMPLES_SRC CONFIGURE_DEPENDS *.cc pathfinding/*.cc sonar/*.cc symbolic_regression/*.cc)
foreach (example_src ${EXAMPLES_SRC})
# Gets the filename (`example`) without directory / longest extension
# (`NAME_WE`) from the full filename (${example_src})
Expand Down
169 changes: 169 additions & 0 deletions src/examples/pathfinding/pathfinding01.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/*
* \remark This file is part of ULTRA.
*
* \copyright Copyright (C) 2024 EOS di Manlio Morini.
*
* \license
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/
*
* \see https://github.com/morinim/ultra/wiki/pathfinding_tutorial
*/

#include <iostream>
#include <vector>

#include "kernel/ultra.h"

using maze = std::vector<std::string>;

enum cell {Start = 'S', Goal = 'G', Wall = '*', Empty = ' '};

using cell_coord = std::pair<unsigned, unsigned>;

// Taxicab distance.
double distance(cell_coord c1, cell_coord c2)
{
return std::max(c1.first, c2.first) - std::min(c1.first, c2.first) +
std::max(c1.second, c2.second) - std::min(c1.second, c2.second);
}

enum cardinal_dir {north, south, west, east};

cell_coord update_coord(const maze &m, cell_coord start, cardinal_dir d)
{
auto to(start);

switch(d)
{
case north:
if (start.first > 0)
--to.first;
break;

case south:
if (start.first + 1 < m.size())
++to.first;
break;

case west:
if (start.second > 0)
--to.second;
break;

default:
if (start.second + 1 < m[0].size())
++to.second;
}

return m[to.first][to.second] == Empty ? to : start;
}

std::pair<cell_coord, unsigned> run(const ultra::ga::individual &path,
const maze &m,
cell_coord start, cell_coord goal)
{
cell_coord now(start);

unsigned step(0);
for (; step < path.parameters() && now != goal; ++step)
now = update_coord(m, now, cardinal_dir(path[step]));

return {now, step};
}

void print_maze(const maze &m)
{
const std::string hr(m[0].size() + 2, '-');

std::cout << hr << '\n';

for (const auto &rows : m)
{
std::cout << '|';

for (const auto &cell : rows)
std::cout << cell;

std::cout << "|\n";
}

std::cout << hr << '\n';
}

maze path_on_maze(const ultra::ga::individual &path, const maze &base,
cell_coord start, cell_coord goal)
{
auto ret(base);
auto now = start;

for (const auto &dir : path)
{
auto &c = ret[now.first][now.second];

if (now == start)
c = Start;
else if (now == goal)
{
c = Goal;
break;
}
else
c = '.';

now = update_coord(base, now, cardinal_dir(dir));
}

return ret;
}

int main()
{
using namespace ultra;

const cell_coord start{0, 0}, goal{16, 8};
const maze m =
{
" * ",
" * *** * ",
" * * ",
" *** ****",
" * * ",
" ***** **",
" * ",
"** * ****",
" * * ",
"** * * * ",
" * * ",
" ******* ",
" * ",
"**** * * ",
" * * * ",
" *** * **",
" * "
};

const auto length(m.size() * m[0].size());

// A candidate solution is a sequence of `length` integers each representing
// a cardinal direction.
ga::problem prob(length, {0, 4});

prob.params.population.individuals = 150;
prob.params.evolution.generations = 20;

// The fitness function.
auto f = [m, start, goal](const ga::individual &x)
{
const auto final(run(x, m, start, goal));

return -distance(final.first, goal) - final.second / 1000.0;
};

ga::search search(prob, f);

const auto best_path(search.run().best_individual);

print_maze(path_on_maze(best_path, m, start, goal));
}
192 changes: 192 additions & 0 deletions src/examples/pathfinding/pathfinding02.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/*
* \remark This file is part of ULTRA.
*
* \copyright Copyright (C) 2024 EOS di Manlio Morini.
*
* \license
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/
*
* \see https://github.com/morinim/ultra/wiki/pathfinding_tutorial
*/

#include <iostream>
#include <vector>

#include "kernel/ultra.h"

using maze = std::vector<std::string>;

enum cell {Start = 'S', Goal = 'G', Wall = '*', Empty = ' '};

using cell_coord = std::pair<unsigned, unsigned>;

// Taxicab distance.
double distance(cell_coord c1, cell_coord c2)
{
return std::max(c1.first, c2.first) - std::min(c1.first, c2.first) +
std::max(c1.second, c2.second) - std::min(c1.second, c2.second);
}

enum cardinal_dir {north, south, west, east};

bool crossing(const maze &m, cell_coord pos)
{
unsigned n(0);

n += pos.first > 0 && m[pos.first - 1][pos.second] == Empty;
n += pos.first + 1 < m.size() && m[pos.first + 1][pos.second] == Empty;
n += pos.second > 0 && m[pos.first][pos.second - 1] == Empty;
n += pos.second + 1 < m[0].size() && m[pos.first][pos.second + 1] == Empty;

return n > 2;
}

cell_coord update_coord(const maze &m, cell_coord start, cardinal_dir d)
{
Expects(d == north || d == south || d == west || d == east);
auto to(start);

switch(d)
{
case north:
if (start.first > 0)
--to.first;
break;

case south:
if (start.first + 1 < m.size())
++to.first;
break;

case west:
if (start.second > 0)
--to.second;
break;

default:
if (start.second + 1 < m[0].size())
++to.second;
}

return m[to.first][to.second] == Empty ? to : start;
}

std::vector<cell_coord> extract_path(const ultra::ga::individual &dirs,
const maze &m,
cell_coord start, cell_coord goal)
{
std::vector<cell_coord> ret;

cell_coord now(start);

for (unsigned i(0); i < dirs.parameters() && now != goal; ++i)
{
const auto dir(dirs[i]);
cell_coord prev;
do
{
prev = now;
ret.push_back(now);
now = update_coord(m, now, cardinal_dir(dir));
} while (now != prev && now != goal && !crossing(m, now));

if (now == goal)
ret.push_back(goal);
}

return ret;
}

std::pair<cell_coord, unsigned> run(const ultra::ga::individual &dirs,
const maze &m,
cell_coord start, cell_coord goal)
{
const auto path(extract_path(dirs, m, start, goal));

return {path.back(), path.size()};
}

void print_maze(const maze &m)
{
const std::string hr(m[0].size() + 2, '-');

std::cout << hr << '\n';

for (const auto &rows : m)
{
std::cout << '|';

for (const auto &cell : rows)
std::cout << cell;

std::cout << "|\n";
}

std::cout << hr << '\n';
}

maze path_on_maze(const std::vector<cell_coord> &path, maze base,
cell_coord goal)
{
for (const auto &c : path)
base[c.first][c.second] = '.';

base[path.front().first][path.front().second] = Start;

if (path.back() == goal)
base[goal.first][goal.second] = Goal;

return base;
}

int main()
{
using namespace ultra;

const cell_coord start{0, 0}, goal{16, 16};
const maze m =
{
" * ",
" * *** * ********",
" * * ",
" *** ********* * ",
" * * * * ",
" ***** ***** *** ",
" * * * ",
"** * ***** * * * ",
" * * * * * * ",
"** * * * * * * * ",
" * * * * * ",
" ******* ********",
" * * ",
"**** * * * ***** ",
" * * * * * ",
" *** * ***** * * ",
" * * * "
};

const auto length(m.size() * m[0].size() / 2);

// A candidate solution is a sequence of `length` integers each representing
// a cardinal direction.
ga::problem prob(length, {0, 4});

prob.params.population.individuals = 150;
prob.params.evolution.generations = 20;

auto f = [m, start, goal](const ultra::ga::individual &x)
{
const auto final(run(x, m, start, goal));

return -distance(final.first, goal) - final.second / 1000.0;
};

ga::search search(prob, f);

const auto best_path(extract_path(search.run().best_individual, m, start,
goal));

print_maze(path_on_maze(best_path, m, goal));
}

0 comments on commit 329fe1e

Please sign in to comment.