Add files

Signed-off-by: Bensuperpc <bensuperpc@gmail.com>
This commit is contained in:
Bensuperpc 2024-01-29 21:08:29 +01:00
commit 7d81e22900
No known key found for this signature in database
GPG Key ID: C8CB30D84F17006E
68 changed files with 4264 additions and 0 deletions

259
.clang-format Executable file
View File

@ -0,0 +1,259 @@
---
Language: Cpp
# BasedOnStyle: Chromium
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignEscapedNewlines: Left
AlignOperands: Align
AlignTrailingComments:
Kind: Always
OverEmptyLines: 0
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: false
BitFieldColonSpacing: Both
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterExternBlock: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakAfterAttributes: Never
BreakAfterJavaFieldAnnotations: false
BreakArrays: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Attach
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
BreakStringLiterals: true
ColumnLimit: 144
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^<.*\.h>'
Priority: 1
SortPriority: 0
CaseSensitive: false
- Regex: '^<.*'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 3
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '([-_](test|unittest))?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: true
IndentExternBlock: AfterExternBlock
IndentGotoLabels: true
IndentPPDirectives: None
IndentRequiresClause: true
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertBraces: false
InsertNewlineAtEOF: false
InsertTrailingCommas: None
IntegerLiteralSeparator:
Binary: 0
BinaryMinDigits: 0
Decimal: 0
DecimalMinDigits: 0
Hex: 0
HexMinDigits: 0
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
LambdaBodyIndentation: Signature
LineEnding: DeriveLF
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: NextLine
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
PPIndentWidth: -1
QualifierAlignment: Leave
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
- ParseTestProto
- ParsePartialTestProto
CanonicalDelimiter: pb
BasedOnStyle: google
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RemoveSemicolon: false
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: LexicographicNumeric
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseTab: Never
WhitespaceSensitiveMacros:
- BOOST_PP_STRINGIZE
- CF_SWIFT_NAME
- NS_SWIFT_NAME
- PP_STRINGIZE
- STRINGIZE
...

155
.clang-tidy Normal file
View File

@ -0,0 +1,155 @@
---
# Enable ALL the things! Except not really
# misc-non-private-member-variables-in-classes: the options don't do anything
# modernize-use-nodiscard: too aggressive, attribute is situationally useful
Checks: "*,\
-google-readability-todo,\
-altera-*,\
-fuchsia-*,\
fuchsia-multiple-inheritance,\
-llvm-header-guard,\
-llvm-include-order,\
-llvmlibc-*,\
-modernize-use-nodiscard,\
-misc-non-private-member-variables-in-classes"
WarningsAsErrors: ''
CheckOptions:
- key: 'bugprone-argument-comment.StrictMode'
value: 'true'
# Prefer using enum classes with 2 values for parameters instead of bools
- key: 'bugprone-argument-comment.CommentBoolLiterals'
value: 'true'
- key: 'bugprone-misplaced-widening-cast.CheckImplicitCasts'
value: 'true'
- key: 'bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression'
value: 'true'
- key: 'bugprone-suspicious-string-compare.WarnOnLogicalNotComparison'
value: 'true'
- key: 'readability-simplify-boolean-expr.ChainedConditionalReturn'
value: 'true'
- key: 'readability-simplify-boolean-expr.ChainedConditionalAssignment'
value: 'true'
- key: 'readability-uniqueptr-delete-release.PreferResetCall'
value: 'true'
- key: 'cppcoreguidelines-init-variables.MathHeader'
value: '<cmath>'
- key: 'cppcoreguidelines-narrowing-conversions.PedanticMode'
value: 'true'
- key: 'readability-else-after-return.WarnOnUnfixable'
value: 'true'
- key: 'readability-else-after-return.WarnOnConditionVariables'
value: 'true'
- key: 'readability-inconsistent-declaration-parameter-name.Strict'
value: 'true'
- key: 'readability-qualified-auto.AddConstToQualified'
value: 'true'
- key: 'readability-redundant-access-specifiers.CheckFirstDeclaration'
value: 'true'
# These seem to be the most common identifier styles
- key: 'readability-identifier-naming.AbstractClassCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassMemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ClassMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstantMemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstantParameterCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstantPointerParameterCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstexprFunctionCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstexprMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ConstexprVariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.EnumCase'
value: 'lower_case'
- key: 'readability-identifier-naming.EnumConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.FunctionCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalConstantPointerCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalFunctionCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalPointerCase'
value: 'lower_case'
- key: 'readability-identifier-naming.GlobalVariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.InlineNamespaceCase'
value: 'lower_case'
- key: 'readability-identifier-naming.LocalConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.LocalConstantPointerCase'
value: 'lower_case'
- key: 'readability-identifier-naming.LocalPointerCase'
value: 'lower_case'
- key: 'readability-identifier-naming.LocalVariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.MacroDefinitionCase'
value: 'UPPER_CASE'
- key: 'readability-identifier-naming.MemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.MethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.NamespaceCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ParameterCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ParameterPackCase'
value: 'lower_case'
- key: 'readability-identifier-naming.PointerParameterCase'
value: 'lower_case'
- key: 'readability-identifier-naming.PrivateMemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.PrivateMemberPrefix'
value: 'm_'
- key: 'readability-identifier-naming.PrivateMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ProtectedMemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ProtectedMemberPrefix'
value: 'm_'
- key: 'readability-identifier-naming.ProtectedMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.PublicMemberCase'
value: 'lower_case'
- key: 'readability-identifier-naming.PublicMethodCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ScopedEnumConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.StaticConstantCase'
value: 'lower_case'
- key: 'readability-identifier-naming.StaticVariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.StructCase'
value: 'lower_case'
- key: 'readability-identifier-naming.TemplateParameterCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.TemplateTemplateParameterCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.TypeAliasCase'
value: 'lower_case'
- key: 'readability-identifier-naming.TypedefCase'
value: 'lower_case'
- key: 'readability-identifier-naming.TypeTemplateParameterCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.UnionCase'
value: 'lower_case'
- key: 'readability-identifier-naming.ValueTemplateParameterCase'
value: 'CamelCase'
- key: 'readability-identifier-naming.VariableCase'
value: 'lower_case'
- key: 'readability-identifier-naming.VirtualMethodCase'
value: 'lower_case'
...

6
.codespellrc Normal file
View File

@ -0,0 +1,6 @@
[codespell]
builtin = clear,rare,en-GB_to_en-US,names,informal,code
check-filenames =
check-hidden =
skip = */.git,*/build,*/prefix
quiet-level = 2

185
.github/workflows/ci.yml vendored Executable file
View File

@ -0,0 +1,185 @@
name: Continuous Integration
on:
push:
branches:
- master
- main
- develop
pull_request:
branches:
- master
- main
- develop
jobs:
lint:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with: { python-version: "3.8" }
- name: Install codespell
run: pip3 install codespell
- name: Lint
if: always()
run: cmake -D FORMAT_COMMAND=clang-format-14 -P cmake/lint.cmake
- name: Spell check
if: always()
run: cmake -P cmake/spell.cmake
coverage:
needs: [lint]
runs-on: ubuntu-22.04
# To enable coverage, delete the last line from the conditional below and
# edit the "<name>" placeholder to your GitHub name.
# If you do not wish to use codecov, then simply delete this job from the
# workflow.
if: github.repository_owner == 'bensuperpc'
&& false
steps:
- uses: actions/checkout@v3
- name: Install LCov
run: sudo apt-get update -q
&& sudo apt-get install lcov -q -y
- name: Configure
run: cmake --preset=ci-coverage
- name: Build
run: cmake --build build/coverage -j 2
- name: Test
working-directory: build/coverage
run: ctest --output-on-failure --no-tests=error -j 2
- name: Process coverage info
run: cmake --build build/coverage -t coverage
- name: Submit to codecov.io
uses: codecov/codecov-action@v3
with:
file: build/coverage/coverage.info
sanitize:
needs: [lint]
runs-on: ubuntu-22.04
env: { CXX: clang++-14 }
steps:
- uses: actions/checkout@v3
- name: Configure
run: cmake --preset=ci-sanitize
- name: Build
run: cmake --build build/sanitize -j 2
- name: Test
working-directory: build/sanitize
env:
ASAN_OPTIONS: "strict_string_checks=1:\
detect_stack_use_after_return=1:\
check_initialization_order=1:\
strict_init_order=1:\
detect_leaks=1"
UBSAN_OPTIONS: print_stacktrace=1
run: ctest --output-on-failure --no-tests=error -j 2
test:
needs: [lint]
strategy:
matrix:
os: [macos-12, ubuntu-22.04, windows-2022]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Install static analyzers
if: matrix.os == 'ubuntu-22.04'
run: >-
sudo apt-get install clang-tidy-14 cppcheck -y -q
sudo update-alternatives --install
/usr/bin/clang-tidy clang-tidy
/usr/bin/clang-tidy-14 140
- name: Setup MultiToolTask
if: matrix.os == 'windows-2022'
run: |
Add-Content "$env:GITHUB_ENV" 'UseMultiToolTask=true'
Add-Content "$env:GITHUB_ENV" 'EnforceProcessCountAcrossBuilds=true'
- name: Configure
shell: pwsh
run: cmake "--preset=ci-$("${{ matrix.os }}".split("-")[0])"
- name: Build
run: cmake --build build --config Release -j 2
- name: Install
run: cmake --install build --config Release --prefix prefix
- name: Test
working-directory: build
run: ctest --output-on-failure --no-tests=error -C Release -j 2
docs:
# Deploy docs only when builds succeed
needs: [sanitize, test]
runs-on: ubuntu-22.04
# To enable, first you have to create an orphaned gh-pages branch:
#
# git switch --orphan gh-pages
# git commit --allow-empty -m "Initial commit"
# git push -u origin gh-pages
#
# Edit the <name> placeholder below to your GitHub name, so this action
# runs only in your repository and no one else's fork. After these, delete
# this comment and the last line in the conditional below.
# If you do not wish to use GitHub Pages for deploying documentation, then
# simply delete this job similarly to the coverage one.
if: github.ref == 'refs/heads/master'
&& github.event_name == 'push'
&& github.repository_owner == 'bensuperpc'
&& false
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with: { python-version: "3.8" }
- name: Install m.css dependencies
run: pip3 install jinja2 Pygments
- name: Install Doxygen
run: sudo apt-get update -q
&& sudo apt-get install doxygen -q -y
- name: Build docs
run: cmake "-DPROJECT_SOURCE_DIR=$PWD" "-DPROJECT_BINARY_DIR=$PWD/build"
-P cmake/docs-ci.cmake
- name: Deploy docs
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: build/docs/html

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
**/.DS_Store
.idea/
.vs/
.vscode/
build/
cmake-build-*/
prefix/
.clangd
CMakeLists.txt.user
compile_commands.json
venv/*

103
BUILDING.md Normal file
View File

@ -0,0 +1,103 @@
# Building with CMake
## Build
This project doesn't require any special command-line flags to build to keep
things simple.
### Building with Make
You can use the Makefile provided in the root of the project to easily build multiple presets:
```sh
make base # Build the base preset
```
```sh
make debug # Build the debug preset
```
### Building with CMake
Here are the steps for building in release mode with a single-configuration
generator, like the Unix Makefiles one:
```sh
cmake -S . -B build -D CMAKE_BUILD_TYPE=Release
cmake --build build
```
Here are the steps for building in release mode with a multi-configuration
generator, like the Visual Studio ones:
```sh
cmake -S . -B build
cmake --build build --config Release
```
### Building with MSVC
Note that MSVC by default is not standards compliant and you need to pass some
flags to make it behave properly. See the `flags-windows` preset in the
[CMakePresets.json](CMakePresets.json) file for the flags and with what
variable to provide them to CMake during configuration.
### Building on Apple Silicon
CMake supports building on Apple Silicon properly since 3.20.1. Make sure you
have the [latest version][1] installed.
## Install
This project doesn't require any special command-line flags to install to keep
things simple. As a prerequisite, the project has to be built with the above
commands already.
The below commands require at least CMake 3.15 to run, because that is the
version in which [Install a Project][2] was added.
Here is the command for installing the release mode artifacts with a
single-configuration generator, like the Unix Makefiles one:
```sh
cmake --install build
```
Here is the command for installing the release mode artifacts with a
multi-configuration generator, like the Visual Studio ones:
```sh
cmake --install build --config Release
```
### CMake package
This project exports a CMake package to be used with the [`find_package`][3]
command of CMake:
* Package name: `astar`
* Target name: `astar::astar`
Example usage:
```cmake
find_package(astar REQUIRED)
# Declare the imported target as a build requirement using PRIVATE, where
# project_target is a target created in the consuming project
target_link_libraries(
project_target PRIVATE
astar::astar
)
```
### Note to packagers
The `CMAKE_INSTALL_INCLUDEDIR` is set to a path other than just `include` if
the project is configured as a top level project to avoid indirectly including
other libraries when installed to a common prefix. Please review the
[install-rules.cmake](cmake/install-rules.cmake) file for the full set of
install rules.
[1]: https://cmake.org/download/
[2]: https://cmake.org/cmake/help/latest/manual/cmake.1.html#install-a-project
[3]: https://cmake.org/cmake/help/latest/command/find_package.html

60
CMakeLists.txt Normal file
View File

@ -0,0 +1,60 @@
cmake_minimum_required(VERSION 3.14)
include(cmake/prelude.cmake)
project(
astar
VERSION 0.1.0
DESCRIPTION "astar"
HOMEPAGE_URL "bensuperpc.org"
LANGUAGES NONE
)
include(cmake/project-is-top-level.cmake)
include(cmake/variables.cmake)
# ---- Declare library ----
add_library(astar_astar INTERFACE)
add_library(astar::astar ALIAS astar_astar)
set_property(
TARGET astar_astar PROPERTY
EXPORT_NAME astar
)
target_include_directories(
astar_astar ${warning_guard}
INTERFACE
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
)
target_compile_features(astar_astar INTERFACE cxx_std_20)
# ---- Install rules ----
if(NOT CMAKE_SKIP_INSTALL_RULES)
include(cmake/install-rules.cmake)
endif()
# ---- Examples ----
if(PROJECT_IS_TOP_LEVEL)
option(BUILD_EXAMPLES "Build examples tree." "${astar_DEVELOPER_MODE}")
if(BUILD_EXAMPLES)
add_subdirectory(example)
endif()
endif()
# ---- Developer mode ----
if(NOT astar_DEVELOPER_MODE)
return()
elseif(NOT PROJECT_IS_TOP_LEVEL)
message(
AUTHOR_WARNING
"Developer mode is intended for developers of astar"
)
endif()
include(cmake/dev-mode.cmake)

247
CMakePresets.json Executable file
View File

@ -0,0 +1,247 @@
{
"version": 2,
"cmakeMinimumRequired": {
"major": 3,
"minor": 14,
"patch": 0
},
"configurePresets": [
{
"name": "cmake-pedantic",
"hidden": true,
"warnings": {
"dev": true,
"deprecated": true,
"uninitialized": true,
"unusedCli": true,
"systemVars": false
},
"errors": {
"dev": false,
"deprecated": false
}
},
{
"name": "dev-mode",
"hidden": true,
"inherits": "cmake-pedantic",
"cacheVariables": {
"astar_DEVELOPER_MODE": "ON"
}
},
{
"name": "cppcheck",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_CPPCHECK": "cppcheck;--inline-suppr",
"CMAKE_C_CPPCHECK": "cppcheck;--inline-suppr"
}
},
{
"name": "clang-tidy",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_CLANG_TIDY": "clang-tidy;--header-filter=^${sourceDir}/",
"CMAKE_C_CLANG_TIDY": "clang-tidy;--header-filter=^${sourceDir}/"
}
},
{
"name": "ci-std",
"description": "This preset makes sure the project actually builds with at least the specified standard",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_EXTENSIONS": "ON",
"CMAKE_CXX_STANDARD": "20",
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
"CMAKE_C_EXTENSIONS": "ON",
"CMAKE_C_STANDARD": "17",
"CMAKE_C_STANDARD_REQUIRED": "ON"
}
},
{
"name": "flags-gcc-clang",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS": "-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -pipe -fstack-protector-strong -fstack-clash-protection -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast -Wformat-security",
"CMAKE_C_FLAGS": "-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -pipe -fstack-protector-strong -fstack-clash-protection -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wformat-security",
"CMAKE_EXE_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed,-z,noexecstack,-z,relro,-z,now",
"CMAKE_SHARED_LINKER_FLAGS": "-Wl,--allow-shlib-undefined,--as-needed,-z,noexecstack,-z,relro,-z,now"
}
},
{
"name": "flags-appleclang",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS": "-pipe -fstack-protector-strong -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion -Wcast-qual -Wformat=2 -Wundef -Wshadow -Wcast-align -Wunused -Wnull-dereference -Wdouble-promotion -Wimplicit-fallthrough -Wextra-semi -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast",
"CMAKE_C_FLAGS": "-pipe -fstack-protector-strong -Wall -Wextra -Wpedantic"
}
},
{
"name": "flags-msvc",
"description": "Note that all the flags after /W4 are required for MSVC to conform to the language standard",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS": "/sdl /guard:cf /utf-8 /diagnostics:caret /w14165 /w44242 /w44254 /w44263 /w34265 /w34287 /w44296 /w44365 /w44388 /w44464 /w14545 /w14546 /w14547 /w14549 /w14555 /w34619 /w34640 /w24826 /w14905 /w14906 /w14928 /w45038 /W4 /permissive- /volatile:iso /Zc:inline /Zc:preprocessor /Zc:enumTypes /Zc:lambda /Zc:__cplusplus /Zc:externConstexpr /Zc:throwingNew /EHsc",
"CMAKE_EXE_LINKER_FLAGS": "/machine:x64 /guard:cf"
}
},
{
"name": "flags-cuda",
"hidden": true,
"cacheVariables": {
"CMAKE_CUDA_FLAGS": "--default-stream per-thread -Xfatbin=-compress-all -arch=all-major -Xcompiler=-Wall,-Wextra,-Wconversion,-Wsign-conversion,-Wcast-qual,-Wundef,-Wshadow,-Wunused,-Wnull-dereference,-Wdouble-promotion,-Wimplicit-fallthrough,-Wextra-semi,-Woverloaded-virtual,-Wnon-virtual-dtor,-Wformat-security",
"CMAKE_CUDA_ARCHITECTURES": "50;52;53;60;61;62;70;72;75;80;86;87;89;90",
"CUDA_PROPAGATE_HOST_FLAGS": "OFF",
"CMAKE_CUDA_SEPARABLE_COMPILATION": "ON"
}
},
{
"name": "flags-debugger",
"hidden": true,
"cacheVariables": {
"CMAKE_CXX_FLAGS": "-O0 -g3 -ggdb3 -Wall -Wextra -Wpedantic -pg",
"CMAKE_C_FLAGS": "-O0 -g3 -ggdb3 -Wall -Wextra -Wpedantic -pg"
}
},
{
"name": "ci-cuda",
"description": "This preset makes sure the project actually builds with at least the specified standard",
"hidden": true,
"cacheVariables": {
"CMAKE_CUDA_STANDARD": "17",
"CMAKE_CUDA_STANDARD_REQUIRED": "ON",
"CMAKE_CUDA_EXTENSIONS": "OFF"
}
},
{
"name": "ci-linux",
"generator": "Unix Makefiles",
"hidden": true,
"inherits": ["flags-gcc-clang", "ci-std", "ci-cuda", "flags-cuda"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "ci-darwin",
"generator": "Unix Makefiles",
"hidden": true,
"inherits": ["flags-appleclang", "ci-std"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "ci-base",
"generator": "Unix Makefiles",
"hidden": true,
"inherits": ["ci-std", "flags-gcc-clang", "dev-mode", "flags-cuda", "ci-cuda"]
},
{
"name": "ci-win64",
"inherits": ["flags-msvc", "ci-std"],
"generator": "Visual Studio 17 2022",
"architecture": "x64",
"hidden": true
},
{
"name": "coverage-linux",
"binaryDir": "${sourceDir}/build/coverage",
"inherits": "ci-linux",
"hidden": true,
"cacheVariables": {
"ENABLE_COVERAGE": "ON",
"CMAKE_BUILD_TYPE": "Coverage",
"CMAKE_CXX_FLAGS_COVERAGE": "-O0 -g3 --coverage -fkeep-inline-functions -fkeep-static-functions",
"CMAKE_EXE_LINKER_FLAGS_COVERAGE": "--coverage",
"CMAKE_SHARED_LINKER_FLAGS_COVERAGE": "--coverage"
}
},
{
"name": "ci-coverage",
"inherits": ["coverage-linux", "dev-mode"],
"cacheVariables": {
"COVERAGE_HTML_COMMAND": ""
}
},
{
"name": "ci-sanitize",
"binaryDir": "${sourceDir}/build/sanitize",
"inherits": ["ci-linux", "dev-mode"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Sanitize",
"CMAKE_CXX_FLAGS_SANITIZE": "-Og -U_FORTIFY_SOURCE -g3 -fsanitize=address,undefined,leak -fno-omit-frame-pointer -fno-common",
"CMAKE_C_FLAGS_SANITIZE": "-Og -U_FORTIFY_SOURCE -g3"
},
"environment": {
"ASAN_OPTIONS": "strict_string_checks=1 detect_stack_use_after_return=1 check_initialization_order=1 strict_init_order=1 detect_leaks=1",
"UBSAN_OPTIONS": "print_stacktrace=1"
}
},
{
"name": "ci-build",
"binaryDir": "${sourceDir}/build",
"hidden": true
},
{
"name": "base",
"binaryDir": "${sourceDir}/build/base",
"inherits": ["ci-base"],
"hidden": false,
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "base-clang",
"binaryDir": "${sourceDir}/build/base-clang",
"inherits": ["ci-base"],
"hidden": false,
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++"
}
},
{
"name": "gprof",
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/build/gprof",
"inherits": ["ci-std", "flags-debugger", "dev-mode"],
"hidden": false,
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "debugger",
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/build/debugger",
"inherits": ["ci-std", "flags-debugger", "dev-mode"],
"hidden": false,
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "dev-common",
"hidden": true,
"inherits": ["dev-mode", "clang-tidy", "cppcheck"],
"cacheVariables": {
"BUILD_MCSS_DOCS": "ON"
}
},
{
"name": "ci-macos",
"inherits": ["ci-build", "ci-darwin", "dev-mode"]
},
{
"name": "ci-ubuntu",
"inherits": ["ci-build", "ci-linux", "dev-common"]
},
{
"name": "ci-windows",
"inherits": ["ci-build", "ci-win64", "dev-mode"]
}
]
}

69
CMakeUserPresets.json Normal file
View File

@ -0,0 +1,69 @@
{
"version": 2,
"cmakeMinimumRequired": {
"major": 3,
"minor": 14,
"patch": 0
},
"configurePresets": [
{
"name": "dev-linux",
"binaryDir": "${sourceDir}/build/dev-linux",
"inherits": ["dev-common", "ci-linux"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "dev-darwin",
"binaryDir": "${sourceDir}/build/dev-darwin",
"inherits": ["dev-common", "ci-darwin"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "dev-win64",
"binaryDir": "${sourceDir}/build/dev-win64",
"inherits": ["dev-common", "ci-win64"],
"environment": {
"UseMultiToolTask": "true",
"EnforceProcessCountAcrossBuilds": "true"
}
},
{
"name": "dev",
"binaryDir": "${sourceDir}/build/dev",
"inherits": "dev-linux"
},
{
"name": "dev-coverage",
"binaryDir": "${sourceDir}/build/coverage",
"inherits": ["dev-mode", "coverage-linux"]
}
],
"buildPresets": [
{
"name": "dev",
"configurePreset": "dev",
"configuration": "Debug",
"jobs": 16
}
],
"testPresets": [
{
"name": "dev",
"configurePreset": "dev",
"configuration": "Debug",
"output": {
"outputOnFailure": true
},
"execution": {
"jobs": 16,
"noTestsAction": "error"
}
}
]
}

5
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,5 @@
# Code of Conduct
* You will be judged by your contributions first, and your sense of humor
second.
* Nobody owes you anything.

19
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,19 @@
# Contributing
<!--
Short overview, rules, general guidelines, notes about pull requests and
style should go here.
-->
## Code of Conduct
Please see the [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) document.
## Getting started
Helpful notes for developers can be found in the [`HACKING.md`](HACKING.md)
document.
In addition to he above, if you use the presets file as instructed, then you
should NOT check it into source control, just as the CMake documentation
suggests.

149
HACKING.md Normal file
View File

@ -0,0 +1,149 @@
# Hacking
Here is some wisdom to help you build and test this project as a developer and
potential contributor.
If you plan to contribute, please read the [CONTRIBUTING](CONTRIBUTING.md)
guide.
## Developer mode
Build system targets that are only useful for developers of this project are
hidden if the `astar_DEVELOPER_MODE` option is disabled. Enabling this
option makes tests and other developer targets and options available. Not
enabling this option means that you are a consumer of this project and thus you
have no need for these targets and options.
Developer mode is always set to on in CI workflows.
### Presets
This project makes use of [presets][1] to simplify the process of configuring
the project. As a developer, you are recommended to always have the [latest
CMake version][2] installed to make use of the latest Quality-of-Life
additions.
You have a few options to pass `astar_DEVELOPER_MODE` to the configure
command, but this project prefers to use presets.
As a developer, you should create a `CMakeUserPresets.json` file at the root of
the project:
```json
{
"version": 2,
"cmakeMinimumRequired": {
"major": 3,
"minor": 14,
"patch": 0
},
"configurePresets": [
{
"name": "dev",
"binaryDir": "${sourceDir}/build/dev",
"inherits": ["dev-mode", "ci-<os>"],
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
}
],
"buildPresets": [
{
"name": "dev",
"configurePreset": "dev",
"configuration": "Debug"
}
],
"testPresets": [
{
"name": "dev",
"configurePreset": "dev",
"configuration": "Debug",
"output": {
"outputOnFailure": true
}
}
]
}
```
You should replace `<os>` in your newly created presets file with the name of
the operating system you have, which may be `win64`, `linux` or `darwin`. You
can see what these correspond to in the
[`CMakePresets.json`](CMakePresets.json) file.
`CMakeUserPresets.json` is also the perfect place in which you can put all
sorts of things that you would otherwise want to pass to the configure command
in the terminal.
> **Note**
> Some editors are pretty greedy with how they open projects with presets.
> Some just randomly pick a preset and start configuring without your consent,
> which can be confusing. Make sure that your editor configures when you
> actually want it to, for example in CLion you have to make sure only the
> `dev-dev preset` has `Enable profile` ticked in
> `File > Settings... > Build, Execution, Deployment > CMake` and in Visual
> Studio you have to set the option `Never run configure step automatically`
> in `Tools > Options > CMake` **prior to opening the project**, after which
> you can manually configure using `Project > Configure Cache`.
### Configure, build and test
If you followed the above instructions, then you can configure, build and test
the project respectively with the following commands from the project root on
any operating system with any build system:
```sh
cmake --preset=dev
cmake --build --preset=dev
ctest --preset=dev
```
If you are using a compatible editor (e.g. VSCode) or IDE (e.g. CLion, VS), you
will also be able to select the above created user presets for automatic
integration.
Please note that both the build and test commands accept a `-j` flag to specify
the number of jobs to use, which should ideally be specified to the number of
threads your CPU has. You may also want to add that to your preset using the
`jobs` property, see the [presets documentation][1] for more details.
### Developer mode targets
These are targets you may invoke using the build command from above, with an
additional `-t <target>` flag:
#### `coverage`
Available if `ENABLE_COVERAGE` is enabled. This target processes the output of
the previously run tests when built with coverage configuration. The commands
this target runs can be found in the `COVERAGE_TRACE_COMMAND` and
`COVERAGE_HTML_COMMAND` cache variables. The trace command produces an info
file by default, which can be submitted to services with CI integration. The
HTML command uses the trace command's output to generate an HTML document to
`<binary-dir>/coverage_html` by default.
#### `docs`
Available if `BUILD_MCSS_DOCS` is enabled. Builds to documentation using
Doxygen and m.css. The output will go to `<binary-dir>/docs` by default
(customizable using `DOXYGEN_OUTPUT_DIRECTORY`).
#### `format-check` and `format-fix`
These targets run the clang-format tool on the codebase to check errors and to
fix them respectively. Customization available using the `FORMAT_PATTERNS` and
`FORMAT_COMMAND` cache variables.
#### `run-examples`
Runs all the examples created by the `add_example` command.
#### `spell-check` and `spell-fix`
These targets run the codespell tool on the codebase to check errors and to fix
them respectively. Customization available using the `SPELL_COMMAND` cache
variable.
[1]: https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html
[2]: https://cmake.org/download/

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Bensuperpc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

162
Makefile Executable file
View File

@ -0,0 +1,162 @@
#//////////////////////////////////////////////////////////////
#// ____ //
#// | __ ) ___ _ __ ___ _ _ _ __ ___ _ __ _ __ ___ //
#// | _ \ / _ \ '_ \/ __| | | | '_ \ / _ \ '__| '_ \ / __| //
#// | |_) | __/ | | \__ \ |_| | |_) | __/ | | |_) | (__ //
#// |____/ \___|_| |_|___/\__,_| .__/ \___|_| | .__/ \___| //
#// |_| |_| //
#//////////////////////////////////////////////////////////////
#// //
#// sandbox, 2023 //
#// Created: 04, June, 2021 //
#// Modified: 18, November, 2023 //
#// file: - //
#// - //
#// Source: //
#// OS: ALL //
#// CPU: ALL //
#// //
#//////////////////////////////////////////////////////////////
PROJECT_NAME := world_of_blocks
PARALLEL := 1
GENERATOR := Ninja
PROJECT_ROOT := .
CTEST_TIMEOUT := 1500
CTEST_OPTIONS := --output-on-failure --timeout $(CTEST_TIMEOUT) --parallel $(PARALLEL) --verbose
# LANG := en
# LANG=$(LANG)
# -Werror=float-equal
.PHONY: build
build: base
.PHONY: all
all: release debug minsizerel coverage relwithdebinfo minsizerel relwithdebinfo release-clang \
debug-clang base base-clang sanitize sanitize-clang gprof $(DOCKCROSS_IMAGE) docker valgrind gdb
.PHONY: base
base:
cmake -B build/$@ -S $(PROJECT_ROOT) -G $(GENERATOR) --preset=$@
cmake --build build/$@
ctest $(CTEST_OPTIONS) --test-dir build/$@
.PHONY: base-clang
base-clang:
cmake -B build/$@ -S $(PROJECT_ROOT) -G $(GENERATOR) --preset=$@
cmake --build build/$@
ctest $(CTEST_OPTIONS) --test-dir build/$@
.PHONY: release
release:
cmake -B build/$@ -S $(PROJECT_ROOT) -G $(GENERATOR) --preset=base -DCMAKE_BUILD_TYPE=Release
cmake --build build/$@
ctest $(CTEST_OPTIONS) --test-dir build/$@
.PHONY: release-clang
release-clang:
cmake -B build/$@ -S $(PROJECT_ROOT) -G $(GENERATOR) --preset=base -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
cmake --build build/$@
ctest $(CTEST_OPTIONS) --test-dir build/$@
.PHONY: debug
debug:
cmake -B build/$@ -S $(PROJECT_ROOT) -G $(GENERATOR) --preset=dev -DCMAKE_BUILD_TYPE=Debug
cmake --build build/$@
ctest $(CTEST_OPTIONS) --test-dir build/$@
.PHONY: debug-clang
debug-clang:
cmake -B build/$@ -S $(PROJECT_ROOT) -G $(GENERATOR) --preset=dev -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
cmake --build build/$@
ctest $(CTEST_OPTIONS) --test-dir build/$@
.PHONY: coverage
coverage:
cmake -B build/$@ -S $(PROJECT_ROOT) -G $(GENERATOR) --preset=dev-coverage -DCMAKE_BUILD_TYPE=Coverage
cmake --build build/$@
ctest $(CTEST_OPTIONS) --test-dir build/$@
cmake --build build/$@ --target $@
.PHONY: sanitize
sanitize:
cmake -B build/$@ -S $(PROJECT_ROOT) -G $(GENERATOR) --preset=ci-sanitize
cmake --build build/$@
ctest $(CTEST_OPTIONS) --test-dir build/$@
sanitize-clang:
cmake -B build/$@ -S $(PROJECT_ROOT) -G $(GENERATOR) --preset=ci-sanitize \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
cmake --build build/$@
ctest $(CTEST_OPTIONS) --test-dir build/$@
.PHONY: minsizerel
minsizerel:
cmake -B build/$@ -S $(PROJECT_ROOT) -G $(GENERATOR) --preset=dev -DCMAKE_BUILD_TYPE=MinSizeRel
cmake --build build/$@
ctest $(CTEST_OPTIONS) --test-dir build/$@
.PHONY: relwithdebinfo
relwithdebinfo:
cmake -B build/$@ -S $(PROJECT_ROOT) -G $(GENERATOR) --preset=dev -DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake --build build/$@
ctest $(CTEST_OPTIONS) --test-dir build/$@
.PHONY: gprof
gprof:
cmake --preset=$@ -G $(GENERATOR)
cmake --build build/$@
@echo "Run executable and after gprof <exe> gmon.out | less"
.PHONY: perf
perf:
cmake --preset=base -G $(GENERATOR)
cmake --build build/base
perf record --all-user -e branch-misses ./build/base/bin/$(PROJECT_NAME)
.PHONY: graph
graph:
cmake -B build/$@ -S $(PROJECT_ROOT) -G $(GENERATOR) --graphviz=build/$@/graph.dot
cmake --build build/base
dot -Tpng -o build/$@/graph.png build/$@/graph.dot
.PHONY: valgrind
valgrind:
cmake -B build/$@ -S $(PROJECT_ROOT) -G $(GENERATOR) --preset=debugger
cmake --build build/$@
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose --log-file=build/$@/valgrind.log ./build/$@/bin/$(PROJECT_NAME)
.PHONY: gdb
gdb:
cmake -B build/$@ -S $(PROJECT_ROOT) -G $(GENERATOR) --preset=debugger
cmake --build build/$@
gdb build/$@/bin/$(PROJECT_NAME)
.PHONY: lint
lint:
cmake -D FORMAT_COMMAND=clang-format -P cmake/lint.cmake
cmake -P cmake/spell.cmake
.PHONY: format
format:
time find . -regex '.*\.\(cpp\|cxx\|hpp\|hxx\|c\|h\|cu\|cuh\|cuhpp\|tpp\)' -not -path '*/build/*' -not -path '.git/*' | parallel clang-format -style=file -i {} \;
.PHONY: cloc
cloc:
cloc --fullpath --not-match-d="(build|.git)" --not-match-f="(.git)" .
.PHONY: update
update:
# git submodule update --recursive --remote --force --rebase
git submodule update --init --recursive
git pull --recurse-submodules --all --progress
.PHONY: clear
clear:
rm -rf build/*

207
README.md Executable file
View File

@ -0,0 +1,207 @@
# astar
Fast and easy to use header only 2D astar algorithm library in C++20.
I made it for learning how the astar algorithm works, try to make the fastest, tested and configurable as possible for my needs (future games and works).
# How does it work
It is an [astar algorithm](https://en.wikipedia.org/wiki/A*_search_algorithm), the main idea is to find the shortest path between two points in a grid/map.
# Screenshots
![astar](resources/Screenshot_20240128_093812.png)
# Features
* [x] Header-only library C++20
* [x] Support 2D map
* [ ] Support 3D map
* [x] Configurable heuristic function and movement cost
* [x] Configurable (diagonal and more) movement
* [x] Debug mode in template argument and lambda function
* [x] Support direct access and not access to the map
* [x] Unit tests and benchmarks
# How to use it
This project is a header-only library and easy to use, just copy the `include/astar` folder in your project and include the `astar/astar.hpp` header or via CMake FetchContent_Declare.
Now you can use the `Astar::Astar` class to find the shortest path between two points in a grid.
```cpp
#include <astar/astar.hpp>
#include <iostream>
auto main() -> int {
// Create the template class with optional a type (e.g. uint32_t) and a boolean
// if you want enable debug mode (AStar::AStar<uint32_t, true>)
AStar::AStar pathFinder;
// Define the map size (width, height)
pathFinder.setWorldSize({10, 10});
// Set the heuristic function (manhattan, euclidean, octagonal etc...), it is optional, default is euclidean
pathFinder.setHeuristic(AStar::Heuristic::manhattan);
// if you want to enable diagonal movement, it is optional, default is false
pathFinder.setDiagonalMovement(true);
// Add a obstacle point (5, 5) and (5, 6)
pathFinder.addObstacle({5, 5});
pathFinder.addObstacle({5, 6});
// Find the path from (0, 0) to (9, 9)
auto path = pathFinder.findPath({0, 0}, {9, 9});
// Print the path
for (auto& p : path) {
std::cout << p.x << " " << p.y << std::endl;
}
return 0;
}
```
### Alternative version (direct access to the map)
You can use the alternative version of the library if you want astar have direct access to the map, this version is faster than the non-direct access version.
```cpp
#include <astar/astar.hpp>
#include <iostream>
auto main() -> int {
// Create the template class with optional a type (e.g. uint32_t) and a boolean
// if you want enable debug mode (AStar::AStar<uint32_t, true>)
AStar::AStarFast pathFinder;
// Set the heuristic function (manhattan, euclidean, octagonal etc...), it is optional, default is euclidean
pathFinder.setHeuristic(AStar::Heuristic::manhattan);
// if you want to enable diagonal movement, it is optional, default is false
pathFinder.setDiagonalMovement(true);
// Create world 9x9 filled with 0
std::vector<uint32_t> world(9 * 9, 0);
// set lambda function to check if is an obstacle (value == 1)
auto isObstacle = [](uint32_t value) -> bool { return value == 1; };
pathFinder.setObstacle(isObstacle);
// Add a obstacle point (5, 5) and (5, 6)
world[5 + 5 * 9] = 1;
world[5 + 6 * 9] = 1;
// Find the path from (0, 0) to (9, 9), it it equal to 0, then the path is not found
// This version of findPath() is faster due direct access to the world
auto path = pathFinder.findPath({0, 0}, {9, 9}, world, {9, 9});
// Print the path
for (auto& p : path) {
std::cout << p.x << " " << p.y << std::endl;
}
return 0;
}
```
### Debug mode
You can enable the debug mode to call a lambda function when new node is visiting by the algorithm and when new node is added to the open list.
```cpp
#include <iostream>
#include <astar/astar.hpp>
auto main() -> int {
// Enable debug mode with template argument, this helps avoid performance issues on non-debug classes
AStar::AStar<uint32_t, true> pathFinder;
// Set lambda function to debug current node
std::function<void(const AStar::Node<uint32_t>* node)> debugCurrentNode = [](const AStar::Node<uint32_t>* node) {
std::cout << "Current node: " << node->pos.x << ", " << node->pos.y << std::endl;
};
pathFinder.setDebugCurrentNode(debugCurrentNode);
// Set lambda function to debug open node
std::function<void(const AStar::Node<uint32_t>* node)> debugOpenNode = [](const AStar::Node<uint32_t>* node) {
std::cout << "Add to open list: " << node->pos.x << ", " << node->pos.y << std::endl;
};
pathFinder.setDebugOpenNode(debugOpenNode);
// Define the map size (width, height)
pathFinder.setWorldSize({10, 10});
// Set the heuristic function (manhattan, euclidean, octagonal etc...), it is optional, default is euclidean
pathFinder.setHeuristic(AStar::Heuristic::manhattan);
// if you want to enable diagonal movement, it is optional, default is false
pathFinder.setDiagonalMovement(true);
// Add a obstacle point (5, 5) and (5, 6)
pathFinder.addObstacle({5, 5});
pathFinder.addObstacle({5, 6});
// Find the path from (0, 0) to (9, 9)
auto path = pathFinder.findPath({0, 0}, {9, 9});
// Print the path
for (auto& p : path) {
std::cout << p.x << " " << p.y << std::endl;
}
return 0;
}
```
# Building and installing
See the [BUILDING](BUILDING.md) document.
# Contributing
See the [CONTRIBUTING](CONTRIBUTING.md) document.
# Sources, references and ideas
You can find here the sources, references, libs and ideas that I have used to make this library.
## Astar
Sources and references that I have used to make this library.
* [Wikipedia A* search algorithm](https://en.wikipedia.org/wiki/A*_search_algorithm)
* [A* Pathfinding](https://www.youtube.com/watch?v=-L-WgKMFuhE)
* [AStar](https://github.com/yatima1460/AStar)
* [Introduction to A*](https://theory.stanford.edu/~amitp/GameProgramming/AStarComparison.html)
* [Easy A* (star) Pathfinding](https://medium.com/@nicholas.w.swift/easy-a-star-pathfinding-7e6689c7f7b2)
* [a-star](https://www.ce.unipr.it/people/medici/a-star.html)$
* [A* Search Algorithm](https://yuminlee2.medium.com/a-search-algorithm-42c1a13fcf9f)
## Bench others astar implementations
The list of others astar implementations that I have benchmarked to compare the performance of my implementation.
* [A* Search Algorithm](https://www.geeksforgeeks.org/a-search-algorithm/)
* [a-star](https://github.com/daancode/a-star)
* [A-Star-Search-Algorithm](https://github.com/lychengrex/A-Star-Search-Algorithm)
* [Pathfinding](https://github.com/Gerard097/Pathfinding)
## Libraries
Libraries used in this project.
* [cmake-init](https://github.com/friendlyanon/cmake-init)
* [google test](https://github.com/google/googletest)
* [google benchmark](https://github.com/google/benchmark)
* [Raylib](https://github.com/raysan5/raylib)
# Others
* [Benchmark visualization](https://int-i.github.io/python/2021-11-07/matplotlib-google-benchmark-visualization/)
# Licensing
[LICENSE](LICENSE)

33
cmake/coverage.cmake Normal file
View File

@ -0,0 +1,33 @@
# ---- Variables ----
# We use variables separate from what CTest uses, because those have
# customization issues
set(
COVERAGE_TRACE_COMMAND
lcov -c -q
-o "${PROJECT_BINARY_DIR}/coverage.info"
-d "${PROJECT_BINARY_DIR}"
--include "${PROJECT_SOURCE_DIR}/*"
CACHE STRING
"; separated command to generate a trace for the 'coverage' target"
)
set(
COVERAGE_HTML_COMMAND
genhtml --legend -f -q
"${PROJECT_BINARY_DIR}/coverage.info"
-p "${PROJECT_SOURCE_DIR}"
-o "${PROJECT_BINARY_DIR}/coverage_html"
CACHE STRING
"; separated command to generate an HTML report for the 'coverage' target"
)
# ---- Coverage target ----
add_custom_target(
coverage
COMMAND ${COVERAGE_TRACE_COMMAND}
COMMAND ${COVERAGE_HTML_COMMAND}
COMMENT "Generating coverage report"
VERBATIM
)

21
cmake/dev-mode.cmake Normal file
View File

@ -0,0 +1,21 @@
include(cmake/folders.cmake)
include(CTest)
if(BUILD_TESTING)
add_subdirectory(test)
endif()
option(BUILD_MCSS_DOCS "Build documentation using Doxygen and m.css" OFF)
if(BUILD_MCSS_DOCS)
include(cmake/docs.cmake)
endif()
option(ENABLE_COVERAGE "Enable coverage support separate from CTest's" OFF)
if(ENABLE_COVERAGE)
include(cmake/coverage.cmake)
endif()
include(cmake/lint-targets.cmake)
include(cmake/spell-targets.cmake)
add_folders(Project)

112
cmake/docs-ci.cmake Normal file
View File

@ -0,0 +1,112 @@
cmake_minimum_required(VERSION 3.14)
foreach(var IN ITEMS PROJECT_BINARY_DIR PROJECT_SOURCE_DIR)
if(NOT DEFINED "${var}")
message(FATAL_ERROR "${var} must be defined")
endif()
endforeach()
set(bin "${PROJECT_BINARY_DIR}")
set(src "${PROJECT_SOURCE_DIR}")
# ---- Dependencies ----
set(mcss_SOURCE_DIR "${bin}/docs/.ci")
if(NOT IS_DIRECTORY "${mcss_SOURCE_DIR}")
file(MAKE_DIRECTORY "${mcss_SOURCE_DIR}")
file(
DOWNLOAD
https://github.com/friendlyanon/m.css/releases/download/release-1/mcss.zip
"${mcss_SOURCE_DIR}/mcss.zip"
STATUS status
EXPECTED_MD5 00cd2757ebafb9bcba7f5d399b3bec7f
)
if(NOT status MATCHES "^0;")
message(FATAL_ERROR "Download failed with ${status}")
endif()
execute_process(
COMMAND "${CMAKE_COMMAND}" -E tar xf mcss.zip
WORKING_DIRECTORY "${mcss_SOURCE_DIR}"
RESULT_VARIABLE result
)
if(NOT result EQUAL "0")
message(FATAL_ERROR "Extraction failed with ${result}")
endif()
file(REMOVE "${mcss_SOURCE_DIR}/mcss.zip")
endif()
find_program(Python3_EXECUTABLE NAMES python3 python)
if(NOT Python3_EXECUTABLE)
message(FATAL_ERROR "Python executable was not found")
endif()
# ---- Process project() call in CMakeLists.txt ----
file(READ "${src}/CMakeLists.txt" content)
string(FIND "${content}" "project(" index)
if(index EQUAL "-1")
message(FATAL_ERROR "Could not find \"project(\"")
endif()
string(SUBSTRING "${content}" "${index}" -1 content)
string(FIND "${content}" "\n)\n" index)
if(index EQUAL "-1")
message(FATAL_ERROR "Could not find \"\\n)\\n\"")
endif()
string(SUBSTRING "${content}" 0 "${index}" content)
file(WRITE "${bin}/docs-ci.project.cmake" "docs_${content}\n)\n")
macro(list_pop_front list out)
list(GET "${list}" 0 "${out}")
list(REMOVE_AT "${list}" 0)
endmacro()
function(docs_project name)
cmake_parse_arguments(PARSE_ARGV 1 "" "" "VERSION;DESCRIPTION;HOMEPAGE_URL" LANGUAGES)
set(PROJECT_NAME "${name}" PARENT_SCOPE)
if(DEFINED _VERSION)
set(PROJECT_VERSION "${_VERSION}" PARENT_SCOPE)
string(REGEX MATCH "^[0-9]+(\\.[0-9]+)*" versions "${_VERSION}")
string(REPLACE . ";" versions "${versions}")
set(suffixes MAJOR MINOR PATCH TWEAK)
while(NOT versions STREQUAL "" AND NOT suffixes STREQUAL "")
list_pop_front(versions version)
list_pop_front(suffixes suffix)
set("PROJECT_VERSION_${suffix}" "${version}" PARENT_SCOPE)
endwhile()
endif()
if(DEFINED _DESCRIPTION)
set(PROJECT_DESCRIPTION "${_DESCRIPTION}" PARENT_SCOPE)
endif()
if(DEFINED _HOMEPAGE_URL)
set(PROJECT_HOMEPAGE_URL "${_HOMEPAGE_URL}" PARENT_SCOPE)
endif()
endfunction()
include("${bin}/docs-ci.project.cmake")
# ---- Generate docs ----
if(NOT DEFINED DOXYGEN_OUTPUT_DIRECTORY)
set(DOXYGEN_OUTPUT_DIRECTORY "${bin}/docs")
endif()
set(out "${DOXYGEN_OUTPUT_DIRECTORY}")
foreach(file IN ITEMS Doxyfile conf.py)
configure_file("${src}/docs/${file}.in" "${bin}/docs/${file}" @ONLY)
endforeach()
set(mcss_script "${mcss_SOURCE_DIR}/documentation/doxygen.py")
set(config "${bin}/docs/conf.py")
file(REMOVE_RECURSE "${out}/html" "${out}/xml")
execute_process(
COMMAND "${Python3_EXECUTABLE}" "${mcss_script}" "${config}"
WORKING_DIRECTORY "${bin}/docs"
RESULT_VARIABLE result
)
if(NOT result EQUAL "0")
message(FATAL_ERROR "m.css returned with ${result}")
endif()

46
cmake/docs.cmake Normal file
View File

@ -0,0 +1,46 @@
# ---- Dependencies ----
set(extract_timestamps "")
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24")
set(extract_timestamps DOWNLOAD_EXTRACT_TIMESTAMP YES)
endif()
include(FetchContent)
FetchContent_Declare(
mcss URL
https://github.com/friendlyanon/m.css/releases/download/release-1/mcss.zip
URL_MD5 00cd2757ebafb9bcba7f5d399b3bec7f
SOURCE_DIR "${PROJECT_BINARY_DIR}/mcss"
UPDATE_DISCONNECTED YES
${extract_timestamps}
)
FetchContent_MakeAvailable(mcss)
find_package(Python3 3.6 REQUIRED)
# ---- Declare documentation target ----
set(
DOXYGEN_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/docs"
CACHE PATH "Path for the generated Doxygen documentation"
)
set(working_dir "${PROJECT_BINARY_DIR}/docs")
foreach(file IN ITEMS Doxyfile conf.py)
configure_file("docs/${file}.in" "${working_dir}/${file}" @ONLY)
endforeach()
set(mcss_script "${mcss_SOURCE_DIR}/documentation/doxygen.py")
set(config "${working_dir}/conf.py")
add_custom_target(
docs
COMMAND "${CMAKE_COMMAND}" -E remove_directory
"${DOXYGEN_OUTPUT_DIRECTORY}/html"
"${DOXYGEN_OUTPUT_DIRECTORY}/xml"
COMMAND "${Python3_EXECUTABLE}" "${mcss_script}" "${config}"
COMMENT "Building documentation using Doxygen and m.css"
WORKING_DIRECTORY "${working_dir}"
VERBATIM
)

21
cmake/folders.cmake Normal file
View File

@ -0,0 +1,21 @@
set_property(GLOBAL PROPERTY USE_FOLDERS YES)
# Call this function at the end of a directory scope to assign a folder to
# targets created in that directory. Utility targets will be assigned to the
# UtilityTargets folder, otherwise to the ${name}Targets folder. If a target
# already has a folder assigned, then that target will be skipped.
function(add_folders name)
get_property(targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS)
foreach(target IN LISTS targets)
get_property(folder TARGET "${target}" PROPERTY FOLDER)
if(DEFINED folder)
continue()
endif()
set(folder Utility)
get_property(type TARGET "${target}" PROPERTY TYPE)
if(NOT type STREQUAL "UTILITY")
set(folder "${name}")
endif()
set_property(TARGET "${target}" PROPERTY FOLDER "${folder}Targets")
endforeach()
endfunction()

View File

@ -0,0 +1 @@
include("${CMAKE_CURRENT_LIST_DIR}/astarTargets.cmake")

66
cmake/install-rules.cmake Normal file
View File

@ -0,0 +1,66 @@
if(PROJECT_IS_TOP_LEVEL)
set(
CMAKE_INSTALL_INCLUDEDIR "include/astar-${PROJECT_VERSION}"
CACHE STRING ""
)
set_property(CACHE CMAKE_INSTALL_INCLUDEDIR PROPERTY TYPE PATH)
endif()
# Project is configured with no languages, so tell GNUInstallDirs the lib dir
set(CMAKE_INSTALL_LIBDIR lib CACHE PATH "")
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
# find_package(<package>) call for consumers to find this project
set(package astar)
install(
DIRECTORY include/
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
COMPONENT astar_Development
)
install(
TARGETS astar_astar
EXPORT astarTargets
INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
)
write_basic_package_version_file(
"${package}ConfigVersion.cmake"
COMPATIBILITY SameMajorVersion
ARCH_INDEPENDENT
)
# Allow package maintainers to freely override the path for the configs
set(
astar_INSTALL_CMAKEDIR "${CMAKE_INSTALL_DATADIR}/${package}"
CACHE STRING "CMake package config location relative to the install prefix"
)
set_property(CACHE astar_INSTALL_CMAKEDIR PROPERTY TYPE PATH)
mark_as_advanced(astar_INSTALL_CMAKEDIR)
install(
FILES cmake/install-config.cmake
DESTINATION "${astar_INSTALL_CMAKEDIR}"
RENAME "${package}Config.cmake"
COMPONENT astar_Development
)
install(
FILES "${PROJECT_BINARY_DIR}/${package}ConfigVersion.cmake"
DESTINATION "${astar_INSTALL_CMAKEDIR}"
COMPONENT astar_Development
)
install(
EXPORT astarTargets
NAMESPACE astar::
DESTINATION "${astar_INSTALL_CMAKEDIR}"
COMPONENT astar_Development
)
if(PROJECT_IS_TOP_LEVEL)
include(CPack)
endif()

View File

@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.14.0)
include(FetchContent)
FetchContent_Declare(
backward-cpp
GIT_REPOSITORY https://github.com/bombela/backward-cpp.git
GIT_TAG 0ddfadc4b0f5c53e63259fe804ee595e6f01f4df) # 23-10-2022
FetchContent_MakeAvailable(backward-cpp)
# TODO: target_include_directories instead of include_directories
include_directories(${backward-cpp_SOURCE_DIR})

70
cmake/lib/benchmark.cmake Executable file
View File

@ -0,0 +1,70 @@
cmake_minimum_required(VERSION 3.14.0)
find_package(benchmark QUIET)
if (NOT benchmark_FOUND)
message(STATUS "benchmark not found on system, downloading...")
include(FetchContent)
set(CMAKE_CXX_CLANG_TIDY_TMP "${CMAKE_CXX_CLANG_TIDY}")
set(CMAKE_CXX_CLANG_TIDY "")
FetchContent_Declare(
googlebenchmark
GIT_REPOSITORY https://github.com/google/benchmark.git
GIT_TAG ca8d0f7b613ac915cd6b161ab01b7be449d1e1cd
#GIT_SHALLOW TRUE
) # 12-10-2023
# Disable tests on google benchmark
set(BENCHMARK_ENABLE_TESTING
OFF
CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_WERROR
OFF
CACHE BOOL "" FORCE)
set(BENCHMARK_FORCE_WERROR
OFF
CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_INSTALL
OFF
CACHE BOOL "" FORCE)
set(BENCHMARK_DOWNLOAD_DEPENDENCIES
ON
CACHE BOOL "" FORCE)
set(BENCHMARK_CXX_LINKER_FLAGS
""
CACHE STRING "" FORCE)
set(BENCHMARK_CXX_LIBRARIES
""
CACHE STRING "" FORCE)
set(BENCHMARK_CXX_FLAGS
""
CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS_COVERAGE
""
CACHE STRING "" FORCE)
set(CMAKE_REQUIRED_FLAGS
""
CACHE STRING "" FORCE)
FetchContent_MakeAvailable(googlebenchmark)
# Lib: benchmark::benchmark benchmark::benchmark_main
set(CMAKE_CXX_CLANG_TIDY "${CMAKE_CXX_CLANG_TIDY_TMP}")
set_target_properties(benchmark
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
endif()

22
cmake/lib/boost.cmake Executable file
View File

@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.14.0)
include(FetchContent)
if(NOT DEFINED BOOST_INCLUDE_LIBRARIES)
set(BOOST_INCLUDE_LIBRARIES system)
endif()
if(NOT DEFINED BOOST_ENABLE_CMAKE)
set(BOOST_ENABLE_CMAKE ON)
endif()
FetchContent_Declare(
Boost
GIT_REPOSITORY https://github.com/boostorg/boost.git
GIT_TAG boost-1.81.0
#GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(Boost)

15
cmake/lib/drogon.cmake Normal file
View File

@ -0,0 +1,15 @@
# https://github.com/drogonframework/drogon/issues/1288#issuecomment-1163902139
FetchContent_Declare(drogon
GIT_REPOSITORY https://github.com/drogonframework/drogon.git
GIT_TAG v1.8.4 # 08-04-2023
)
# Reset CXX_FLAGS to avoid warnings from drogon
set(CMAKE_CXX_FLAGS_OLD "${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS "-std=c++17 -O3")
FetchContent_MakeAvailable(drogon)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_OLD}")

View File

@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.14.0)
include(FetchContent)
set(FASTNOISE2_NOISETOOL OFF CACHE BOOL "Build Noise Tool" FORCE)
FetchContent_Declare(FastNoise2
GIT_REPOSITORY https://github.com/Auburn/FastNoise2.git
GIT_TAG 0928ca22cd4cfd50e9b17cec4fe9d867b59c3943 # 2023-06-07
)
FetchContent_MakeAvailable(FastNoise2)

32
cmake/lib/gtest.cmake Executable file
View File

@ -0,0 +1,32 @@
cmake_minimum_required(VERSION 3.14.0)
find_package(GTest QUIET)
if (NOT GTEST_FOUND)
message(STATUS "GTest not found on system, downloading...")
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG 2dd1c131950043a8ad5ab0d2dda0e0970596586a) # 12-10-2023
# Disable tests on gtest
set(gtest_build_tests
OFF
CACHE BOOL "" FORCE)
set(gtest_build_samples
OFF
CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
# Lib: gtest gtest_main
set_target_properties(gtest
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
endif()

31
cmake/lib/json.cmake Executable file
View File

@ -0,0 +1,31 @@
cmake_minimum_required(VERSION 3.14.0)
find_package(nlohmann_json QUIET)
if (NOT nlohmann_json_FOUND)
message(STATUS "nlohmann_json not found on system, downloading...")
include(FetchContent)
#set(CMAKE_MODULE_PATH
# ""
# CACHE STRING "" FORCE)
#set(NLOHMANN_JSON_SYSTEM_INCLUDE
# ""
# CACHE STRING "" FORCE)
FetchContent_Declare(nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG f56c6e2e30241b9245161a86ae9fecf6543bf411 # 2023-11-26
)
FetchContent_MakeAvailable(nlohmann_json)
# nlohmann_json::nlohmann_json
set_target_properties(nlohmann_json
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
include_directories(${nlohmann_json_SOURCE_DIR}/include)
endif()

42
cmake/lib/opencv.cmake Normal file
View File

@ -0,0 +1,42 @@
cmake_minimum_required(VERSION 3.14.0)
include(FetchContent)
set(OpenCV_DIR ${CMAKE_CURRENT_BINARY_DIR})
find_package(OpenCV QUIET)
if (NOT OpenCV_FOUND)
#set(OpenCV_STATIC ON)
set(BUILD_EXAMPLES CACHE BOOL OFF)
set(BUILD_DOCS CACHE BOOL OFF)
set(BUILD_TESTS CACHE BOOL OFF)
set(BUILD_PERF_TESTS CACHE BOOL OFF)
#set(BUILD_PACKAGE CACHE BOOL OFF)
set(BUILD_opencv_apps CACHE BOOL OFF)
FetchContent_Declare(
OpenCV
GIT_REPOSITORY https://github.com/opencv/opencv.git
GIT_TAG 4.7.0
#GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
FetchContent_MakeAvailable(OpenCV)
#set(OpenCV_DIR ${CMAKE_CURRENT_BINARY_DIR})
#include_directories(${OpenCV_INCLUDE_DIRS})
#message(FATAL_ERROR "OpenCV_INCLUDE_DIRS: ${OpenCV_INCLUDE_DIRS}")
#find_package(OpenCV REQUIRED)
#include_directories(${OpenCV_INCLUDE_DIRS})
#target_include_directories("${NAME}" PRIVATE
#${OPENCV_CONFIG_FILE_INCLUDE_DIR}
#${OPENCV_MODULE_opencv_core_LOCATION}/include
#${OPENCV_MODULE_opencv_highgui_LOCATION}/include
#)
#target_link_libraries("${NAME}" PRIVATE opencv_core opencv_highgui)
#target_link_libraries("${NAME}" PRIVATE ${OpenCV_LIBS})
#opencv_add_module()
endif()

6
cmake/lib/openmp.cmake Executable file
View File

@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.14.0)
find_package(OpenMP)
if(OpenMP_CXX_FOUND)
# message("OpenMP found")
endif()

14
cmake/lib/perlin_noise.cmake Executable file
View File

@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.14.0)
include(FetchContent)
FetchContent_Declare(perlin_noise
GIT_REPOSITORY https://github.com/Reputeless/PerlinNoise.git
GIT_TAG bdf39fe92b2a585cdef485bcec2bca8ab5614095 # 2022-12-30
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)
FetchContent_MakeAvailable(perlin_noise)
include_directories("${perlin_noise_SOURCE_DIR}")

18
cmake/lib/pybind11.cmake Executable file
View File

@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.15)
find_package(Python 3.8 COMPONENTS Interpreter Development REQUIRED)
find_package(pybind11)
# add_subdirectory(pybind11)
if (NOT pybind11_FOUND)
include(FetchContent)
FetchContent_Declare(
pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11.git
GIT_TAG v2.10.3
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(pybind11)
endif()
#pybind11_add_module(${PROJECT_NAME} main.cpp)

17
cmake/lib/raygui.cmake Executable file
View File

@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.14.0)
include(FetchContent)
set(BUILD_RAYLIB_CPP_EXAMPLES OFF CACHE BOOL "" FORCE)
find_package(raygui QUIET)
if (NOT raygui_FOUND)
FetchContent_Declare(raygui
GIT_REPOSITORY https://github.com/raysan5/raygui.git
GIT_TAG 4.0
)
FetchContent_MakeAvailable(raygui)
include_directories(${raygui_SOURCE_DIR})
include_directories(${raygui_SOURCE_DIR}/src)
endif()

13
cmake/lib/raylib-cpp.cmake Executable file
View File

@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.14.0)
include(FetchContent)
#find_package(raylib_cpp QUIET)
if (NOT raylib_cpp_FOUND)
FetchContent_Declare(raylib_cpp
GIT_REPOSITORY https://github.com/RobLoach/raylib-cpp.git
GIT_TAG v5.0.0 # 08-12-2023
)
FetchContent_MakeAvailable(raylib_cpp)
endif()

47
cmake/lib/raylib.cmake Executable file
View File

@ -0,0 +1,47 @@
cmake_minimum_required(VERSION 3.14.0)
find_package(raylib QUIET)
if (NOT raylib_FOUND AND NOT FETCHCONTENT_FULLY_DISCONNECTED)
message(STATUS "raylib not found on system, downloading...")
include(FetchContent)
if(NOT DEFINED BUILD_EXAMPLES)
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
endif()
if(NOT DEFINED BUILD_GAMES)
set(BUILD_GAMES OFF CACHE BOOL "" FORCE)
endif()
if(NOT DEFINED INCLUDE_EVERYTHING)
set(INCLUDE_EVERYTHING ON CACHE BOOL "" FORCE)
endif()
if(NOT DEFINED OPENGL_VERSION)
#set(OPENGL_VERSION OFF CACHE STRING "4.3" FORCE)
endif()
#set (CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_SOURCE_DIR}/install CACHE PATH "default install path" FORCE)
#set (CMAKE_INSTALL_LIBDIR ${CMAKE_BINARY_DIR}/lib CACHE PATH "default install path" FORCE)
#message(STATUS "CMAKE_INSTALL_LIBDIR: ${CMAKE_INSTALL_LIBDIR}")
FetchContent_Declare(raylib
GIT_REPOSITORY https://github.com/raysan5/raylib.git
GIT_TAG 5.0 # 08-12-2023
)
FetchContent_MakeAvailable(raylib)
set_target_properties(raylib
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
set(raylib_FOUND TRUE)
else()
find_package(raylib 5.0.0 REQUIRED)
endif()

11
cmake/lib/spdlog.cmake Normal file
View File

@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.14.0)
include(FetchContent)
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG 7e635fca68d014934b4af8a1cf874f63989352b7) # 2023-07-09
FetchContent_MakeAvailable(spdlog)
include_directories("${spdlog_SOURCE_DIR}")

View File

@ -0,0 +1,14 @@
cmake_minimum_required(VERSION 3.14.0)
include(FetchContent)
FetchContent_Declare(bs-thread-pool
GIT_REPOSITORY https://github.com/bshoshany/thread-pool.git
GIT_TAG 6790920f61ab3e928ddaea835ab6a803d467f41d # 2023-12-28
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)
FetchContent_MakeAvailable(bs-thread-pool)
include_directories("${bs-thread-pool_SOURCE_DIR}/include")

10
cmake/lib/vector.cmake Executable file
View File

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.14.0)
include(FetchContent)
FetchContent_Declare(
vector
GIT_REPOSITORY https://github.com/bensuperpc/vector.git
GIT_TAG 9febb9c84e7b73e6c621afd920dd3c8bb47a130c) # 2022-10-23
FetchContent_MakeAvailable(vector)

16
cmake/lib/zlib.cmake Executable file
View File

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.14.0)
include(FetchContent)
find_package(zlib QUIET)
set(ZLIB_LIBRARY zlib)
if (NOT zlib_FOUND)
FetchContent_Declare(
zlib
GIT_REPOSITORY https://github.com/madler/zlib.git
GIT_TAG v1.2.13
)
FetchContent_MakeAvailable(zlib)
endif()

34
cmake/lint-targets.cmake Normal file
View File

@ -0,0 +1,34 @@
set(
FORMAT_PATTERNS
source/*.cpp source/*.hpp
include/*.hpp
test/*.cpp test/*.hpp
example/*.cpp example/*.hpp
CACHE STRING
"; separated patterns relative to the project source dir to format"
)
set(FORMAT_COMMAND clang-format CACHE STRING "Formatter to use")
add_custom_target(
format-check
COMMAND "${CMAKE_COMMAND}"
-D "FORMAT_COMMAND=${FORMAT_COMMAND}"
-D "PATTERNS=${FORMAT_PATTERNS}"
-P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
COMMENT "Linting the code"
VERBATIM
)
add_custom_target(
format-fix
COMMAND "${CMAKE_COMMAND}"
-D "FORMAT_COMMAND=${FORMAT_COMMAND}"
-D "PATTERNS=${FORMAT_PATTERNS}"
-D FIX=YES
-P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
COMMENT "Fixing the code"
VERBATIM
)

52
cmake/lint.cmake Normal file
View File

@ -0,0 +1,52 @@
cmake_minimum_required(VERSION 3.14)
macro(default name)
if(NOT DEFINED "${name}")
set("${name}" "${ARGN}")
endif()
endmacro()
default(FORMAT_COMMAND clang-format)
default(
PATTERNS
source/*.cpp source/*.hpp
include/*.hpp
test/*.cpp test/*.hpp
example/*.cpp example/*.hpp
)
default(FIX NO)
set(flag --output-replacements-xml)
set(args OUTPUT_VARIABLE output)
if(FIX)
set(flag -i)
set(args "")
endif()
file(GLOB_RECURSE files ${PATTERNS})
set(badly_formatted "")
set(output "")
string(LENGTH "${CMAKE_SOURCE_DIR}/" path_prefix_length)
foreach(file IN LISTS files)
execute_process(
COMMAND "${FORMAT_COMMAND}" --style=file "${flag}" "${file}"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
RESULT_VARIABLE result
${args}
)
if(NOT result EQUAL "0")
message(FATAL_ERROR "'${file}': formatter returned with ${result}")
endif()
if(NOT FIX AND output MATCHES "\n<replacement offset")
string(SUBSTRING "${file}" "${path_prefix_length}" -1 relative_file)
list(APPEND badly_formatted "${relative_file}")
endif()
set(output "")
endforeach()
if(NOT badly_formatted STREQUAL "")
list(JOIN badly_formatted "\n" bad_list)
message("The following files are badly formatted:\n\n${bad_list}\n")
message(FATAL_ERROR "Run again with FIX=YES to fix these files.")
endif()

10
cmake/prelude.cmake Normal file
View File

@ -0,0 +1,10 @@
# ---- In-source guard ----
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
message(
FATAL_ERROR
"In-source builds are not supported. "
"Please read the BUILDING document before trying to build this project. "
"You may need to delete 'CMakeCache.txt' and 'CMakeFiles/' first."
)
endif()

View File

@ -0,0 +1,6 @@
# This variable is set by project() in CMake 3.21+
string(
COMPARE EQUAL
"${CMAKE_SOURCE_DIR}" "${PROJECT_SOURCE_DIR}"
PROJECT_IS_TOP_LEVEL
)

22
cmake/spell-targets.cmake Normal file
View File

@ -0,0 +1,22 @@
set(SPELL_COMMAND codespell CACHE STRING "Spell checker to use")
add_custom_target(
spell-check
COMMAND "${CMAKE_COMMAND}"
-D "SPELL_COMMAND=${SPELL_COMMAND}"
-P "${PROJECT_SOURCE_DIR}/cmake/spell.cmake"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
COMMENT "Checking spelling"
VERBATIM
)
add_custom_target(
spell-fix
COMMAND "${CMAKE_COMMAND}"
-D "SPELL_COMMAND=${SPELL_COMMAND}"
-D FIX=YES
-P "${PROJECT_SOURCE_DIR}/cmake/spell.cmake"
WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}"
COMMENT "Fixing spelling errors"
VERBATIM
)

31
cmake/spell.cmake Executable file
View File

@ -0,0 +1,31 @@
cmake_minimum_required(VERSION 3.14)
macro(default name)
if(NOT DEFINED "${name}")
set("${name}" "${ARGN}")
endif()
endmacro()
default(SPELL_COMMAND codespell)
default(FIX NO)
set(flag "")
if(FIX)
set(flag -w)
endif()
set(flag "${flag}" --ignore-words codespell.ignore-words.txt)
execute_process(
COMMAND "${SPELL_COMMAND}" ${flag}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
RESULT_VARIABLE result
)
if(result EQUAL "65")
message(FATAL_ERROR "Run again with FIX=YES to fix these errors.")
elseif(result EQUAL "64")
message(FATAL_ERROR "Spell checker printed the usage info. Bad arguments?")
elseif(NOT result EQUAL "0")
message(FATAL_ERROR "Spell checker returned with ${result}")
endif()

9
cmake/utile/ccache.cmake Executable file
View File

@ -0,0 +1,9 @@
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
message(NOTICE "-- ccache is enabled (found here: ${CCACHE_PROGRAM})")
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "\"${CCACHE_PROGRAM}\"")
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "\"${CCACHE_PROGRAM}\"")
else()
message(WARNING "-- ccache has not been found")
endif()

11
cmake/utile/distcc.cmake Executable file
View File

@ -0,0 +1,11 @@
find_program(DISTCC_PROGRAM distcc)
message(WARNING "distcc module is in beta.")
if(DISTCC_PROGRAM)
message(NOTICE "distcc is enabled (found here: ${DISTCC_PROGRAM})")
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "\"${DISTCC_PROGRAM}\"")
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "\"${DISTCC_PROGRAM}\"")
else()
message(NOTICE "distcc has not been found")
endif()

11
cmake/utile/lto.cmake Executable file
View File

@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.9.0)
include(CheckIPOSupported)
check_ipo_supported(RESULT supported OUTPUT error)
if(supported)
message(STATUS "IPO / LTO enabled")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
else()
message(STATUS "IPO / LTO not supported: <${error}>")
endif()

9
cmake/utile/ninja_color.cmake Executable file
View File

@ -0,0 +1,9 @@
option (FORCE_COLORED_OUTPUT "Always produce ANSI-colored output (GNU/Clang only)." TRUE)
if (${FORCE_COLORED_OUTPUT})
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
add_compile_options (-fdiagnostics-color=always)
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
add_compile_options (-fcolor-diagnostics)
endif ()
endif ()

28
cmake/variables.cmake Normal file
View File

@ -0,0 +1,28 @@
# ---- Developer mode ----
# Developer mode enables targets and code paths in the CMake scripts that are
# only relevant for the developer(s) of astar
# Targets necessary to build the project must be provided unconditionally, so
# consumers can trivially build and package the project
if(PROJECT_IS_TOP_LEVEL)
option(astar_DEVELOPER_MODE "Enable developer mode" OFF)
endif()
# ---- Warning guard ----
# target_include_directories with the SYSTEM modifier will request the compiler
# to omit warnings from the provided paths, if the compiler supports that
# This is to provide a user experience similar to find_package when
# add_subdirectory or FetchContent is used to consume this project
set(warning_guard "")
if(NOT PROJECT_IS_TOP_LEVEL)
option(
astar_INCLUDES_WITH_SYSTEM
"Use SYSTEM modifier for astar's includes, disabling warnings"
ON
)
mark_as_advanced(astar_INCLUDES_WITH_SYSTEM)
if(astar_INCLUDES_WITH_SYSTEM)
set(warning_guard SYSTEM)
endif()
endif()

View File

@ -0,0 +1 @@

32
docs/Doxyfile.in Normal file
View File

@ -0,0 +1,32 @@
# Configuration for Doxygen for use with CMake
# Only options that deviate from the default are included
# To create a new Doxyfile containing all available options, call `doxygen -g`
# Get Project name and version from CMake
PROJECT_NAME = "@PROJECT_NAME@"
PROJECT_NUMBER = "@PROJECT_VERSION@"
# Add sources
INPUT = "@PROJECT_SOURCE_DIR@/README.md" "@PROJECT_SOURCE_DIR@/include" "@PROJECT_SOURCE_DIR@/docs/pages"
EXTRACT_ALL = YES
RECURSIVE = YES
OUTPUT_DIRECTORY = "@DOXYGEN_OUTPUT_DIRECTORY@"
# Use the README as a main page
USE_MDFILE_AS_MAINPAGE = "@PROJECT_SOURCE_DIR@/README.md"
# set relative include paths
FULL_PATH_NAMES = YES
STRIP_FROM_PATH = "@PROJECT_SOURCE_DIR@/include" "@PROJECT_SOURCE_DIR@"
STRIP_FROM_INC_PATH =
# We use m.css to generate the html documentation, so we only need XML output
GENERATE_XML = YES
GENERATE_HTML = NO
GENERATE_LATEX = NO
XML_PROGRAMLISTING = NO
CREATE_SUBDIRS = NO
# Include all directories, files and namespaces in the documentation
# Disable to include only explicitly documented objects
M_SHOW_UNDOCUMENTED = YES

6
docs/conf.py.in Normal file
View File

@ -0,0 +1,6 @@
DOXYFILE = 'Doxyfile'
LINKS_NAVBAR1 = [
(None, 'pages', [(None, 'about')]),
(None, 'namespaces', []),
]

7
docs/pages/about.dox Normal file
View File

@ -0,0 +1,7 @@
/**
* @page about About
* @section about-doxygen Doxygen documentation
* This page is auto generated using
* <a href="https://www.doxygen.nl/">Doxygen</a>, making use of some useful
* <a href="https://www.doxygen.nl/manual/commands.html">special commands</a>.
*/

27
example/CMakeLists.txt Normal file
View File

@ -0,0 +1,27 @@
cmake_minimum_required(VERSION 3.14)
project(astarExamples CXX)
include(../cmake/project-is-top-level.cmake)
include(../cmake/folders.cmake)
if(PROJECT_IS_TOP_LEVEL)
find_package(astar REQUIRED)
endif()
add_custom_target(run-examples)
function(add_example NAME)
add_executable("${NAME}" "${NAME}.cpp")
target_link_libraries("${NAME}" PRIVATE astar::astar)
target_compile_features("${NAME}" PRIVATE cxx_std_20)
add_custom_target("run_${NAME}" COMMAND "${NAME}" VERBATIM)
add_dependencies("run_${NAME}" "${NAME}")
add_dependencies(run-examples "run_${NAME}")
endfunction()
add_example(basic_example)
add_example(debug_example)
add_example(basic_fast_example)
add_folders(Example)

31
example/basic_example.cpp Normal file
View File

@ -0,0 +1,31 @@
#include <astar/astar.hpp>
#include <iostream>
auto main() -> int {
// Create the template class with optional a type (e.g. uint32_t) and a boolean
// if you want enable debug mode (AStar::AStar<uint32_t, true>)
AStar::AStar pathFinder;
// Define the map size (width, height)
pathFinder.setWorldSize({10, 10});
// Set the heuristic function (manhattan, euclidean, octagonal etc...), it is optional, default is euclidean
pathFinder.setHeuristic(AStar::Heuristic::manhattan);
// if you want to enable diagonal movement, it is optional, default is false
pathFinder.setDiagonalMovement(true);
// Add a obstacle point (5, 5) and (5, 6)
pathFinder.addObstacle({5, 5});
pathFinder.addObstacle({5, 6});
// Find the path from (0, 0) to (9, 9), it it equal to 0, then the path is not found
auto path = pathFinder.findPath({0, 0}, {9, 9});
// Print the path
for (auto& p : path) {
std::cout << p.x << " " << p.y << std::endl;
}
return 0;
}

36
example/basic_fast_example.cpp Executable file
View File

@ -0,0 +1,36 @@
#include <astar/astar.hpp>
#include <iostream>
auto main() -> int {
// Create the template class with optional a type (e.g. uint32_t) and a boolean
// if you want enable debug mode (AStar::AStar<uint32_t, true>)
AStar::AStarFast pathFinder;
// Set the heuristic function (manhattan, euclidean, octagonal etc...), it is optional, default is euclidean
pathFinder.setHeuristic(AStar::Heuristic::manhattan);
// if you want to enable diagonal movement, it is optional, default is false
pathFinder.setDiagonalMovement(true);
// Create world 9x9 filled with 0
std::vector<uint32_t> world(9 * 9, 0);
// set lambda function to check if is an obstacle (value == 1)
auto isObstacle = [](uint32_t value) -> bool { return value == 1; };
pathFinder.setObstacle(isObstacle);
// Add a obstacle point (5, 5) and (5, 6)
world[5 + 5 * 9] = 1;
world[5 + 6 * 9] = 1;
// Find the path from (0, 0) to (9, 9), it it equal to 0, then the path is not found
// This version of findPath() is faster due direct access to the world
auto path = pathFinder.findPath({0, 0}, {9, 9}, world, {9, 9});
// Print the path
for (auto& p : path) {
std::cout << p.x << " " << p.y << std::endl;
}
return 0;
}

43
example/debug_example.cpp Normal file
View File

@ -0,0 +1,43 @@
#include <iostream>
#include <astar/astar.hpp>
auto main() -> int {
// Enable debug mode with template argument, this helps avoid performance issues on non-debug classes
AStar::AStar<uint32_t, true> pathFinder;
// Set lambda function to debug current node
std::function<void(const AStar::Node<uint32_t>* node)> debugCurrentNode = [](const AStar::Node<uint32_t>* node) {
std::cout << "Current node: " << node->pos.x << ", " << node->pos.y << std::endl;
};
pathFinder.setDebugCurrentNode(debugCurrentNode);
// Set lambda function to debug open node
std::function<void(const AStar::Node<uint32_t>* node)> debugOpenNode = [](const AStar::Node<uint32_t>* node) {
std::cout << "Add to open list: " << node->pos.x << ", " << node->pos.y << std::endl;
};
pathFinder.setDebugOpenNode(debugOpenNode);
// Define the map size (width, height)
pathFinder.setWorldSize({10, 10});
// Set the heuristic function (manhattan, euclidean, octagonal etc...), it is optional, default is euclidean
pathFinder.setHeuristic(AStar::Heuristic::manhattan);
// if you want to enable diagonal movement, it is optional, default is false
pathFinder.setDiagonalMovement(true);
// Add a obstacle point (5, 5) and (5, 6)
pathFinder.addObstacle({5, 5});
pathFinder.addObstacle({5, 6});
// Find the path from (0, 0) to (9, 9)
auto path = pathFinder.findPath({0, 0}, {9, 9});
// Print the path
for (auto& p : path) {
std::cout << p.x << " " << p.y << std::endl;
}
return 0;
}

360
include/astar/astar.hpp Normal file
View File

@ -0,0 +1,360 @@
#pragma once
#include <algorithm>
#include <cmath>
#include <concepts>
#include <cstdint>
#include <functional>
#include <iostream>
#include <memory>
#include <queue>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
template <typename T>
concept ArithmeticType = std::is_arithmetic<T>::value;
template <typename T>
concept IntegerType = std::is_integral<T>::value;
template <typename T>
concept FloatingPointType = std::is_floating_point<T>::value;
namespace AStar {
template <IntegerType T = int32_t>
class Vec2 {
public:
Vec2() = default;
Vec2(T x_, T y_) : x(x_), y(y_) {}
bool operator==(const Vec2& pos) const noexcept { return (x == pos.x && y == pos.y); }
Vec2 operator=(const Vec2& pos) noexcept {
x = pos.x;
y = pos.y;
return *this;
}
Vec2 operator+(const Vec2& pos) noexcept { return {x + pos.x, y + pos.y}; }
Vec2 operator-(const Vec2& pos) noexcept { return {x - pos.x, y - pos.y}; }
Vec2 operator*(const Vec2& pos) noexcept { return {x * pos.x, y * pos.y}; }
Vec2 operator/(const Vec2& pos) noexcept { return {x / pos.x, y / pos.y}; }
struct hash {
size_t operator()(const Vec2& pos) const noexcept { return std::hash<size_t>()(pos.x ^ (pos.y << 4)); }
};
T x = 0;
T y = 0;
};
typedef Vec2<int32_t> Vec2i;
template <IntegerType T = uint32_t>
class Node {
public:
explicit Node() : pos(Vec2i(0, 0)), parentNode(nullptr) {}
explicit Node(const Vec2i& pos, Node* parent = nullptr) : pos(pos), parentNode(parent) {}
explicit Node(const Vec2i& pos, const T pathCost, const T heuristicCost, Node* parent = nullptr)
: pathCost(pathCost), heuristicCost(heuristicCost), pos(pos), parentNode(parent) {}
inline T getTotalCost() const noexcept { return pathCost + heuristicCost; }
struct hash {
size_t operator()(const Node* node) const noexcept { return std::hash<size_t>()(node->pos.x ^ (node->pos.y << 4)); }
};
T pathCost = 0;
T heuristicCost = 0;
Vec2i pos = {0, 0};
Node* parentNode = nullptr;
};
namespace Heuristic {
static inline Vec2i deltaVec(const Vec2i& source, const Vec2i& target) noexcept {
return {std::abs(source.x - target.x), std::abs(source.y - target.y)};
}
static inline uint32_t manhattan(const Vec2i& source, const Vec2i& target, const uint32_t weight) noexcept {
auto delta = deltaVec(source, target);
return weight * (delta.x + delta.y);
}
static inline uint32_t octagonal(const Vec2i& source, const Vec2i& target, const uint32_t weight) noexcept {
auto delta = deltaVec(source, target);
return weight * (delta.x + delta.y) + (-6) * std::min(delta.x, delta.y);
}
static inline uint32_t euclidean(const Vec2i& source, const Vec2i& target, const uint32_t weight) noexcept {
auto delta = deltaVec(source, target);
return weight * static_cast<uint32_t>(std::sqrt(std::pow(delta.x, 2) + std::pow(delta.y, 2)));
}
static inline uint32_t chebyshev(const Vec2i& source, const Vec2i& target, const uint32_t weight) noexcept {
auto delta = deltaVec(source, target);
return weight * std::max(delta.x, delta.y);
}
static inline uint32_t euclideanNoSQR(const Vec2i& source, const Vec2i& target, const uint32_t weight) noexcept {
auto delta = deltaVec(source, target);
return weight * static_cast<uint32_t>(std::pow(delta.x, 2) + std::pow(delta.y, 2));
}
static constexpr uint32_t dijkstra([[maybe_unused]] const Vec2i& source,
[[maybe_unused]] const Vec2i& target,
const uint32_t weight = 0) noexcept {
return 0;
}
}; // namespace Heuristic
template <IntegerType T = uint32_t, bool enableDebug = false>
class AStarVirtual {
public:
explicit AStarVirtual()
: _heuristicFunction(&Heuristic::euclidean),
_directionsCount(4),
_heuristicWeight(10),
_mouvemementCost(10),
_debugCurrentNode([](Node<T>*) {}),
_debugOpenNode([](Node<T>*) {}) {
_directions = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
}
void setHeuristic(const std::function<uint32_t(Vec2i, Vec2i, uint32_t)>& heuristic) {
_heuristicFunction = std::bind(heuristic, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
}
std::function<uint32_t(Vec2i, Vec2i, uint32_t)>& getHeuristic() noexcept { return _heuristicFunction; }
void setHeuristicWeight(const uint32_t weight) noexcept { _heuristicWeight = weight; }
uint32_t getHeuristicWeight() const noexcept { return _heuristicWeight; }
void setDiagonalMovement(const bool enableDiagonalMovement) noexcept {
_directionsCount = (enableDiagonalMovement ? _directions.size() : _directions.size() / 2);
}
void setMouvemementCost(const size_t cost) noexcept { _mouvemementCost = cost; }
size_t getMouvemementCost() const noexcept { return _mouvemementCost; }
void setCustomDirections(const std::vector<Vec2i>& directions) noexcept {
_directions = directions;
_directionsCount = static_cast<size_t>(directions.size());
}
std::vector<Vec2i>& getDirections() noexcept { return _directions; }
void setDebugCurrentNode(const std::function<void(Node<T>*)>& debugCurrentNode) noexcept { _debugCurrentNode = debugCurrentNode; }
void setDebugOpenNode(const std::function<void(Node<T>*)>& debugOpenNode) noexcept { _debugOpenNode = debugOpenNode; }
protected:
std::function<uint32_t(Vec2i, Vec2i, uint32_t)> _heuristicFunction;
std::vector<Vec2i> _directions;
size_t _directionsCount;
T _heuristicWeight;
size_t _mouvemementCost = 10;
// Only used if enableDebug is true
std::function<void(Node<T>*)> _debugCurrentNode;
std::function<void(Node<T>*)> _debugOpenNode;
};
template <IntegerType T = uint32_t, bool enableDebug = false>
class AStar final : public AStarVirtual<T, enableDebug> {
public:
explicit AStar() {}
std::vector<Vec2i> findPath(const Vec2i source, const Vec2i& target) {
if (target.x < 0 || target.x >= _worldSize.x || target.y < 0 || target.y >= _worldSize.y) {
return {};
}
Node<T>* currentNode = nullptr;
auto compareFn = [](const Node<T>* a, const Node<T>* b) { return a->getTotalCost() > b->getTotalCost(); };
std::priority_queue<Node<T>*, std::vector<Node<T>*>, decltype(compareFn)> openNodeVecPQueue =
std::priority_queue<Node<T>*, std::vector<Node<T>*>, decltype(compareFn)>(compareFn);
std::unordered_map<Vec2i, Node<T>*, Vec2i::hash> openNodeMap;
std::unordered_map<Vec2i, Node<T>*, Vec2i::hash> closedNodeMap;
openNodeVecPQueue.push(new Node<T>(source));
openNodeMap.insert({source, openNodeVecPQueue.top()});
while (!openNodeVecPQueue.empty()) {
currentNode = openNodeVecPQueue.top();
if constexpr (enableDebug) {
AStarVirtual<T, enableDebug>::_debugCurrentNode(currentNode);
}
if (currentNode->pos == target) {
break;
}
openNodeVecPQueue.pop();
openNodeMap.erase(currentNode->pos);
closedNodeMap.insert({currentNode->pos, currentNode});
for (size_t i = 0; i < AStarVirtual<T, enableDebug>::_directionsCount; ++i) {
Vec2i newPos = currentNode->pos + AStarVirtual<T, enableDebug>::_directions[i];
if (_obstacles.contains(newPos)) {
continue;
}
if (closedNodeMap.contains(newPos)) {
continue;
}
if (newPos.x < 0 || newPos.x >= _worldSize.x || newPos.y < 0 || newPos.y >= _worldSize.y) {
continue;
}
T nextCost = currentNode->pathCost + AStarVirtual<T, enableDebug>::_mouvemementCost;
Node<T>* nextNode = openNodeMap.find(newPos) != openNodeMap.end() ? openNodeMap[newPos] : nullptr;
if (nextNode == nullptr) {
nextNode = new Node<T>(newPos, currentNode);
nextNode->pathCost = nextCost;
nextNode->heuristicCost = static_cast<T>(AStarVirtual<T, enableDebug>::_heuristicFunction(
nextNode->pos, target, AStarVirtual<T, enableDebug>::_heuristicWeight));
openNodeVecPQueue.push(nextNode);
openNodeMap.insert({nextNode->pos, nextNode});
} else if (nextCost < nextNode->pathCost) {
nextNode->parentNode = currentNode;
nextNode->pathCost = nextCost;
}
if constexpr (enableDebug) {
AStarVirtual<T, enableDebug>::_debugOpenNode(nextNode);
}
}
}
std::vector<Vec2i> path;
if (currentNode->pos == target) [[likely]] {
path.reserve(currentNode->getTotalCost() / 10);
while (currentNode != nullptr) {
path.push_back(currentNode->pos);
currentNode = currentNode->parentNode;
}
}
for (auto& [key, value] : openNodeMap) {
delete value;
}
for (auto& [key, value] : closedNodeMap) {
delete value;
}
return path;
}
void addObstacle(const Vec2i& pos) { _obstacles.insert(pos); }
void removeObstacle(const Vec2i& pos) { _obstacles.erase(pos); }
std::unordered_set<Vec2i, Vec2i::hash>& getObstacles() noexcept { return _obstacles; }
void clear() { _obstacles.clear(); }
void setWorldSize(const Vec2i& worldSize_) noexcept { _worldSize = worldSize_; }
private:
std::unordered_set<Vec2i, Vec2i::hash> _obstacles;
Vec2i _worldSize = {0, 0};
};
// Fast AStar are faster than normal AStar but use more ram and direct access to the map
template <IntegerType T = uint32_t, bool enableDebug = false, IntegerType U = uint32_t>
class AStarFast final : public AStarVirtual<T, enableDebug> {
public:
explicit AStarFast() : _isObstacleFunction([](U value) { return value == 1; }) {}
// Same as AStar::findPath() but use direct access to the map
std::vector<Vec2i> findPath(const Vec2i& source, const Vec2i& target, const std::vector<U>& map, const Vec2i& worldSize) {
if (target.x < 0 || target.x >= worldSize.x || target.y < 0 || target.y >= worldSize.y) {
return {};
}
Node<T>* currentNode = nullptr;
auto compareFn = [](const Node<T>* a, const Node<T>* b) { return a->getTotalCost() > b->getTotalCost(); };
std::priority_queue<Node<T>*, std::vector<Node<T>*>, decltype(compareFn)> openNodeVecPQueue =
std::priority_queue<Node<T>*, std::vector<Node<T>*>, decltype(compareFn)>(compareFn);
std::unordered_map<Vec2i, Node<T>*, Vec2i::hash> openNodeMap;
std::unordered_map<Vec2i, Node<T>*, Vec2i::hash> closedNodeMap;
openNodeVecPQueue.push(new Node<T>(source));
openNodeMap.insert({source, openNodeVecPQueue.top()});
while (!openNodeVecPQueue.empty()) {
currentNode = openNodeVecPQueue.top();
if constexpr (enableDebug) {
AStarVirtual<T, enableDebug>::_debugCurrentNode(currentNode);
}
if (currentNode->pos == target) {
break;
}
openNodeVecPQueue.pop();
openNodeMap.erase(currentNode->pos);
closedNodeMap.insert({currentNode->pos, currentNode});
for (size_t i = 0; i < AStarVirtual<T, enableDebug>::_directionsCount; ++i) {
Vec2i newPos = currentNode->pos + AStarVirtual<T, enableDebug>::_directions[i];
if (_isObstacleFunction(map[newPos.x + newPos.y * worldSize.x])) {
continue;
}
if (closedNodeMap.contains(newPos)) {
continue;
}
if (newPos.x < 0 || newPos.x >= worldSize.x || newPos.y < 0 || newPos.y >= worldSize.y) {
continue;
}
T nextCost = currentNode->pathCost + AStarVirtual<T, enableDebug>::_mouvemementCost;
Node<T>* nextNode = openNodeMap.find(newPos) != openNodeMap.end() ? openNodeMap[newPos] : nullptr;
if (nextNode == nullptr) {
nextNode = new Node<T>(newPos, currentNode);
nextNode->pathCost = nextCost;
nextNode->heuristicCost = static_cast<T>(AStarVirtual<T, enableDebug>::_heuristicFunction(
nextNode->pos, target, AStarVirtual<T, enableDebug>::_heuristicWeight));
openNodeVecPQueue.push(nextNode);
openNodeMap.insert({nextNode->pos, nextNode});
} else if (nextCost < nextNode->pathCost) [[likely]] {
nextNode->parentNode = currentNode;
nextNode->pathCost = nextCost;
}
if constexpr (enableDebug) {
AStarVirtual<T, enableDebug>::_debugOpenNode(nextNode);
}
}
}
std::vector<Vec2i> path;
if (currentNode->pos == target) [[likely]] {
path.reserve(currentNode->getTotalCost() / 10);
while (currentNode != nullptr) {
path.push_back(currentNode->pos);
currentNode = currentNode->parentNode;
}
}
for (auto& [key, value] : openNodeMap) {
delete value;
}
for (auto& [key, value] : closedNodeMap) {
delete value;
}
return path;
}
void setObstacle(const std::function<bool(U)>& isObstacleFunction) noexcept { _isObstacleFunction = isObstacleFunction; }
std::function<bool(U)>& getObstacle() noexcept { return _isObstacleFunction; }
private:
std::function<bool(U)> _isObstacleFunction;
};
} // namespace AStar

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

82
test/CMakeLists.txt Normal file
View File

@ -0,0 +1,82 @@
cmake_minimum_required(VERSION 3.14)
project(astarTests LANGUAGES C CXX)
include(../cmake/project-is-top-level.cmake)
include(../cmake/folders.cmake)
# ---- Dependencies ----
if(PROJECT_IS_TOP_LEVEL)
find_package(astar REQUIRED)
enable_testing()
endif()
function(test_bench_generator TEST_BENCH_NAME IS_TEST ADD_TO_TEST)
if (IS_TEST)
add_executable("${TEST_BENCH_NAME}" "source/test/${TEST_BENCH_NAME}.cpp" source/generator/generator.cpp source/generator/generator.hpp)
else()
add_executable("${TEST_BENCH_NAME}" "source/benchmark/${TEST_BENCH_NAME}.cpp" source/generator/generator.cpp source/generator/generator.hpp)
endif()
if (IS_TEST)
target_link_libraries("${TEST_BENCH_NAME}" PRIVATE gtest)
else()
target_link_libraries("${TEST_BENCH_NAME}" PRIVATE benchmark::benchmark)
endif()
target_link_libraries("${TEST_BENCH_NAME}" PRIVATE astar::astar)
target_link_libraries("${TEST_BENCH_NAME}" PRIVATE raylib)
target_link_libraries("${TEST_BENCH_NAME}" PRIVATE FastNoise2)
#target_link_libraries("${TEST_BENCH_NAME}" PRIVATE spdlog::spdlog nlohmann_json::nlohmann_json)
#if (OpenMP_FOUND OR OpenMP_CXX_FOUND)
# target_link_libraries("${TEST_BENCH_NAME}" PRIVATE OpenMP::OpenMP_CXX)
#endif()
set_target_properties("${TEST_BENCH_NAME}"
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
#if(NOT CMAKE_BUILD_TYPE MATCHES Debug AND NOT CMAKE_BUILD_TYPE MATCHES Coverage)
# add_test(NAME "${TEST_BENCH_NAME}" COMMAND $<TARGET_FILE:${TEST_BENCH_NAME}>)
#elseif()
# message(STATUS "Disable ${BENCH_NAME}, Performance benchmark test only run on Release/RelWithDebInfo/MinSizeRel")
#endif()
if (ADD_TO_TEST)
add_test(NAME "${TEST_BENCH_NAME}" COMMAND $<TARGET_FILE:${TEST_BENCH_NAME}>)
endif()
target_compile_features("${TEST_BENCH_NAME}" PRIVATE cxx_std_20)
endfunction()
# ---- Tests ----
if(NOT WIN32)
include(../cmake/lib/gtest.cmake)
include(../cmake/lib/benchmark.cmake)
#include(../cmake/lib/openmp.cmake)
include(../cmake/lib/raygui.cmake)
include(../cmake/lib/raylib.cmake)
include(../cmake/lib/fast_noise2.cmake)
#include(../cmake/lib/spdlog.cmake)
#include(../cmake/lib/json.cmake)
include(../cmake/utile/ccache.cmake)
include_directories(source)
test_bench_generator(astar_test true true)
test_bench_generator(astar_bench false true)
test_bench_generator(path_finder false false)
endif()
# ---- End-of-file commands ----
add_folders(Test)

View File

@ -0,0 +1,195 @@
#include <array>
#include <string>
#include <string_view>
#include <vector>
#include <benchmark/benchmark.h>
#include "astar/astar.hpp"
#include "generator/generator.hpp"
static constexpr int64_t multiplier = 4;
static constexpr int64_t minRange = 16;
static constexpr int64_t maxRange = 256;
static constexpr int64_t minThreadRange = 1;
static constexpr int64_t maxThreadRange = 1;
static constexpr int64_t repetitions = 1;
static void DoSetup([[maybe_unused]] const benchmark::State& state) {}
static void DoTeardown([[maybe_unused]] const benchmark::State& state) {}
template <IntegerType T>
static void astar_bench(benchmark::State& state) {
auto range = state.range(0);
int mapWidth = range;
int mapHeight = range;
float lacunarity = 1.6f;
float octaves = 6;
float gain = 3.5f;
float frequency = 1.7f;
float weightedStrength = 0.034f;
float multiplier = 118;
Generator generator(-972960945);
benchmark::DoNotOptimize(generator);
generator.setLacunarity(lacunarity);
generator.setOctaves((uint32_t)octaves);
generator.setGain(gain);
generator.setFrequency(frequency);
generator.setWeightedStrength(weightedStrength);
generator.setMultiplier((uint32_t)multiplier);
std::vector<uint32_t> heightmap = generator.generate2dMeightmap(0, 0, 0, mapWidth, 0, mapHeight);
benchmark::DoNotOptimize(heightmap);
std::vector<uint8_t> blocks = std::vector<uint8_t>(mapWidth * mapHeight, 0);
benchmark::DoNotOptimize(blocks);
AStar::AStar<T, false> pathFinder;
benchmark::DoNotOptimize(pathFinder);
pathFinder.setWorldSize({mapWidth, mapHeight});
pathFinder.setHeuristic(AStar::Heuristic::euclidean);
pathFinder.setDiagonalMovement(true);
for (uint64_t x = 0; x < mapWidth; x++) {
for (uint64_t y = 0; y < mapHeight; y++) {
uint64_t index = x + y * mapWidth;
uint8_t value = static_cast<uint8_t>(heightmap[index]);
if (value < 128) {
blocks[index] = 0;
} else {
blocks[index] = 1;
pathFinder.addObstacle({static_cast<int32_t>(x), static_cast<int32_t>(y)});
}
}
}
blocks[0] = 0;
pathFinder.removeObstacle({0, 0});
blocks[mapWidth * mapHeight - 1] = 0;
pathFinder.removeObstacle({mapWidth - 1, mapHeight - 1});
AStar::Vec2i source(0, 0);
AStar::Vec2i target(mapWidth - 1, mapHeight - 1);
std::vector<AStar::Vec2i> path;
benchmark::DoNotOptimize(path);
for (auto _ : state) {
path = pathFinder.findPath(source, target);
state.PauseTiming();
if (path.size() == 0) {
state.SkipWithError("No path found");
}
state.ResumeTiming();
benchmark::ClobberMemory();
}
state.SetItemsProcessed(state.iterations());
state.SetBytesProcessed(state.iterations() * sizeof(path));
}
BENCHMARK(astar_bench<uint32_t>)
->Name("astar_bench<uint32_t>")
->RangeMultiplier(multiplier)
->Range(minRange, maxRange)
->ThreadRange(minThreadRange, maxThreadRange)
->Unit(benchmark::kNanosecond)
->Setup(DoSetup)
->Teardown(DoTeardown)
->MeasureProcessCPUTime()
->UseRealTime()
->Repetitions(repetitions);
template <IntegerType T>
static void astar_bench_fast(benchmark::State& state) {
auto range = state.range(0);
int mapWidth = range;
int mapHeight = range;
float lacunarity = 1.6f;
float octaves = 6;
float gain = 3.5f;
float frequency = 1.7f;
float weighted_strength = 0.034f;
float multiplier = 118;
Generator generator(-972960945);
benchmark::DoNotOptimize(generator);
generator.setLacunarity(lacunarity);
generator.setOctaves((uint32_t)octaves);
generator.setGain(gain);
generator.setFrequency(frequency);
generator.setWeightedStrength(0.0f);
generator.setMultiplier((uint32_t)multiplier);
std::vector<uint32_t> heightmap = generator.generate2dMeightmap(0, 0, 0, mapWidth, 0, mapHeight);
benchmark::DoNotOptimize(heightmap);
std::vector<uint32_t> blocks = std::vector<uint32_t>(mapWidth * mapHeight, 0);
benchmark::DoNotOptimize(blocks);
AStar::AStarFast<T, false, uint32_t> pathFinder;
benchmark::DoNotOptimize(pathFinder);
pathFinder.setHeuristic(AStar::Heuristic::euclidean);
pathFinder.setDiagonalMovement(true);
for (uint64_t x = 0; x < mapWidth; x++) {
for (uint64_t y = 0; y < mapHeight; y++) {
uint64_t index = x + y * mapWidth;
uint8_t value = static_cast<uint8_t>(heightmap[index]);
if (value < 128) {
blocks[index] = 0;
} else {
blocks[index] = 1;
}
}
}
blocks[0] = 0;
blocks[mapWidth * mapHeight - 1] = 0;
AStar::Vec2i source(0, 0);
AStar::Vec2i target(mapWidth - 1, mapHeight - 1);
std::vector<AStar::Vec2i> path;
benchmark::DoNotOptimize(path);
for (auto _ : state) {
path = pathFinder.findPath(source, target, blocks, {mapWidth, mapHeight});
state.PauseTiming();
if (path.size() == 0) {
state.SkipWithError("No path found");
}
state.ResumeTiming();
benchmark::ClobberMemory();
}
state.SetItemsProcessed(state.iterations());
state.SetBytesProcessed(state.iterations() * sizeof(path));
}
BENCHMARK(astar_bench_fast<uint32_t>)
->Name("astar_bench_fast<uint32_t>")
->RangeMultiplier(multiplier)
->Range(minRange, maxRange)
->ThreadRange(minThreadRange, maxThreadRange)
->Unit(benchmark::kNanosecond)
->Setup(DoSetup)
->Teardown(DoTeardown)
->MeasureProcessCPUTime()
->UseRealTime()
->Repetitions(repetitions);
// Run the benchmark
// BENCHMARK_MAIN();
int main(int argc, char** argv) {
::benchmark::Initialize(&argc, argv);
::benchmark::RunSpecifiedBenchmarks();
}

View File

@ -0,0 +1,258 @@
#include <array> // std::array
#include <chrono> // std::chrono::system_clock
#include <cmath> // std::abs
#include <cstdint> // std::uint32_t
#include <iostream> // std::cout, std::endl
#include <map> // std::map
#include <memory> // std::unique_ptr
#include <random> // std::random_device, std::mt19937, std::uniform_int_distribution
#include <vector> // std::vector
#include "astar/astar.hpp"
#include "generator/generator.hpp"
#include "raylib.h"
#define RAYGUI_IMPLEMENTATION
extern "C" {
#include "src/raygui.h"
}
auto main() -> int {
// Set log level for Raylib
SetTraceLogLevel(LOG_WARNING);
const int screenWidth = 1920;
const int screenHeight = 1080;
const int mapWidth = 192;
const int mapHeight = 108;
const uint32_t targetFPS = 120;
const uint32_t ImageUpdatePerSecond = 30;
SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_MSAA_4X_HINT);
InitWindow(screenWidth, screenHeight, "Path finder by Bensuperpc");
SetTargetFPS(targetFPS);
float lacunarity = 1.6f;
float octaves = 6;
float gain = 3.5f;
float frequency = 1.7f;
float weighted_strength = 0.034f;
float multiplier = 118;
Generator generator_2(-972960945);
generator_2.setLacunarity(lacunarity);
generator_2.setOctaves((uint32_t)octaves);
generator_2.setGain(gain);
generator_2.setFrequency(frequency);
generator_2.setWeightedStrength(0.0f);
generator_2.setMultiplier((uint32_t)multiplier);
AStar::AStar<uint32_t, false> pathFinder;
pathFinder.setWorldSize({mapWidth, mapHeight});
pathFinder.setHeuristic(AStar::Heuristic::manhattan);
pathFinder.setDiagonalMovement(true);
size_t manhattanPathSize = 0;
size_t euclideanPathSize = 0;
size_t euclideanNoSQRPathSize = 0;
size_t octagonalPathSize = 0;
size_t chebyshevPathSize = 0;
size_t dijkstraPathSize = 0;
std::vector<uint32_t> heightmap;
heightmap = generator_2.generate2dMeightmap(0, 0, 0, mapWidth, 0, mapHeight);
std::vector<uint8_t> blocks = std::vector<uint8_t>(mapWidth * mapHeight, 0);
uint64_t framesCounter = 0;
bool needUpdate = true;
while (!WindowShouldClose()) {
framesCounter++;
if (IsKeyPressed(KEY_S)) {
const std::string filename = "screenshot_" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count()) + ".png";
TakeScreenshot(filename.c_str());
}
if (IsKeyPressed(KEY_R)) {
generator_2.randomizeSeed();
needUpdate = true;
}
if (framesCounter % (targetFPS / ImageUpdatePerSecond) == 0) {
if (needUpdate) {
needUpdate = false;
generator_2.setLacunarity(lacunarity);
generator_2.setOctaves((uint32_t)octaves);
generator_2.setGain(gain);
generator_2.setFrequency(frequency);
generator_2.setWeightedStrength(weighted_strength);
generator_2.setMultiplier((uint32_t)multiplier);
pathFinder.clear();
heightmap = generator_2.generate2dMeightmap(0, 0, 0, screenWidth, 0, screenHeight);
for (uint64_t x = 0; x < mapWidth; x++) {
for (uint64_t y = 0; y < mapHeight; y++) {
uint64_t index = x + y * mapWidth;
uint8_t value = static_cast<uint8_t>(heightmap[index]);
if (value < 128) {
blocks[index] = 0;
} else {
blocks[index] = 1;
pathFinder.addObstacle({static_cast<int32_t>(x), static_cast<int32_t>(y)});
}
}
}
blocks[0] = 0;
pathFinder.removeObstacle({0, 0});
blocks[mapWidth * mapHeight - 1] = 0;
pathFinder.removeObstacle({mapWidth - 1, mapHeight - 1});
pathFinder.setHeuristic(AStar::Heuristic::manhattan);
auto start1 = std::chrono::high_resolution_clock::now();
auto path = pathFinder.findPath({0, 0}, {mapWidth - 1, mapHeight - 1});
auto stop1 = std::chrono::high_resolution_clock::now();
if (path.empty()) {
std::cout << "Path not found" << std::endl;
}
auto duration1 = std::chrono::duration_cast<std::chrono::microseconds>(stop1 - start1);
std::cout << "Path search: " << duration1.count() << " microseconds" << std::endl;
manhattanPathSize = path.size();
for (auto& i : path) {
uint64_t index = i.x + i.y * mapWidth;
blocks[index] = 2;
}
pathFinder.setHeuristic(AStar::Heuristic::euclidean);
path = pathFinder.findPath({0, 0}, {mapWidth - 1, mapHeight - 1});
euclideanPathSize = path.size();
for (auto& i : path) {
uint64_t index = i.x + i.y * mapWidth;
blocks[index] = 3;
}
pathFinder.setHeuristic(AStar::Heuristic::octagonal);
path = pathFinder.findPath({0, 0}, {mapWidth - 1, mapHeight - 1});
octagonalPathSize = path.size();
for (auto& i : path) {
uint64_t index = i.x + i.y * mapWidth;
blocks[index] = 4;
}
pathFinder.setHeuristic(AStar::Heuristic::chebyshev);
path = pathFinder.findPath({0, 0}, {mapWidth - 1, mapHeight - 1});
chebyshevPathSize = path.size();
for (auto& i : path) {
uint64_t index = i.x + i.y * mapWidth;
blocks[index] = 5;
}
pathFinder.setHeuristic(AStar::Heuristic::euclideanNoSQR);
path = pathFinder.findPath({0, 0}, {mapWidth - 1, mapHeight - 1});
euclideanNoSQRPathSize = path.size();
for (auto& i : path) {
uint64_t index = i.x + i.y * mapWidth;
blocks[index] = 6;
}
pathFinder.setHeuristic(AStar::Heuristic::dijkstra);
path = pathFinder.findPath({0, 0}, {mapWidth - 1, mapHeight - 1});
dijkstraPathSize = path.size();
for (auto& i : path) {
uint64_t index = i.x + i.y * mapWidth;
blocks[index] = 7;
}
}
}
ClearBackground(RAYWHITE);
BeginDrawing();
// Draw white if blocks[index] == 0 else black
int size = 10;
for (uint64_t x = 0; x < mapWidth; x++) {
for (uint64_t y = 0; y < mapHeight; y++) {
uint64_t index = x + y * mapWidth;
if (blocks[index] == 0) {
DrawRectangle(static_cast<float>(x * size), static_cast<float>(y * size), size, size, WHITE);
} else if (blocks[index] == 1) {
DrawRectangle(static_cast<float>(x * size), static_cast<float>(y * size), size, size, BLACK);
} else if (blocks[index] == 2) {
DrawRectangle(static_cast<float>(x * size), static_cast<float>(y * size), size, size, RED);
} else if (blocks[index] == 3) {
DrawRectangle(static_cast<float>(x * size), static_cast<float>(y * size), size, size, GREEN);
} else if (blocks[index] == 4) {
DrawRectangle(static_cast<float>(x * size), static_cast<float>(y * size), size, size, BLUE);
} else if (blocks[index] == 5) {
DrawRectangle(static_cast<float>(x * size), static_cast<float>(y * size), size, size, YELLOW);
} else if (blocks[index] == 6) {
DrawRectangle(static_cast<float>(x * size), static_cast<float>(y * size), size, size, ORANGE);
} else if (blocks[index] == 7) {
DrawRectangle(static_cast<float>(x * size), static_cast<float>(y * size), size, size, PURPLE);
}
}
}
// display FPS
DrawRectangle(screenWidth - 90, 10, 80, 20, Fade(SKYBLUE, 0.95f));
DrawText(TextFormat("FPS: %02d", GetFPS()), screenWidth - 80, 15, 15, DARKGRAY);
DrawRectangle(0, 0, 275, 200, Fade(SKYBLUE, 0.95f));
DrawRectangleLines(0, 0, 275, 200, BLUE);
GuiSlider((Rectangle){70, 10, 165, 20}, "Lacunarity", TextFormat("%2.3f", lacunarity), &lacunarity, -0.0f, 5.0f);
GuiSlider((Rectangle){70, 40, 165, 20}, "Octaves", TextFormat("%2.3f", octaves), &octaves, 1, 12);
GuiSlider((Rectangle){70, 70, 165, 20}, "Gain", TextFormat("%2.3f", gain), &gain, -0.0f, 16.0f);
GuiSlider((Rectangle){70, 100, 165, 20}, "Frequency", TextFormat("%2.3f", frequency), &frequency, -0.0f, 10.0f);
GuiSlider((Rectangle){70, 130, 165, 20}, "Weight", TextFormat("%2.3f", weighted_strength), &weighted_strength, -5.0f, 5.0f);
GuiSlider((Rectangle){70, 160, 165, 20}, "Multiplier", TextFormat("%2.3f", multiplier), &multiplier, 1, 512);
// display info each color for each heuristic
DrawRectangle(0, 200, 275, 190, Fade(SKYBLUE, 0.95f));
DrawRectangleLines(0, 200, 275, 190, BLUE);
std::string manhattanText = "Manhattan: " + std::to_string(manhattanPathSize);
DrawRectangle(10, 210, 20, 20, RED);
DrawText(manhattanText.c_str(), 40, 210, 20, DARKGRAY);
std::string euclideanText = "Euclidean: " + std::to_string(euclideanPathSize);
DrawRectangle(10, 240, 20, 20, GREEN);
DrawText(euclideanText.c_str(), 40, 240, 20, DARKGRAY);
std::string octagonalText = "Octagonal: " + std::to_string(octagonalPathSize);
DrawRectangle(10, 270, 20, 20, BLUE);
DrawText(octagonalText.c_str(), 40, 270, 20, DARKGRAY);
std::string chebyshevText = "Chebyshev: " + std::to_string(chebyshevPathSize);
DrawRectangle(10, 300, 20, 20, YELLOW);
DrawText(chebyshevText.c_str(), 40, 300, 20, DARKGRAY);
std::string euclideanNoSQRText = "EuclideanNoSQR: " + std::to_string(euclideanNoSQRPathSize);
DrawRectangle(10, 330, 20, 20, ORANGE);
DrawText(euclideanNoSQRText.c_str(), 40, 330, 20, DARKGRAY);
std::string dijkstraText = "Dijkstra: " + std::to_string(dijkstraPathSize);
DrawRectangle(10, 360, 20, 20, PURPLE);
DrawText(dijkstraText.c_str(), 40, 360, 20, DARKGRAY);
EndDrawing();
}
CloseWindow();
return 0;
}

View File

@ -0,0 +1,310 @@
#include <algorithm>
#include <chrono>
#include <filesystem>
#include <iostream>
#include <numeric>
#include <random>
#include <string>
#include <string_view>
#include <vector>
// #include <omp.h>
#include "generator.hpp"
Generator::Generator(int32_t _seed) : seed(_seed) {
fnSimplex = FastNoise::New<FastNoise::Perlin>();
fnFractal = FastNoise::New<FastNoise::FractalFBm>();
fnFractal->SetSource(fnSimplex);
fnFractal->SetOctaveCount(octaves);
fnFractal->SetGain(gain);
fnFractal->SetLacunarity(lacunarity);
fnFractal->SetWeightedStrength(weighted_strength);
}
Generator::Generator() {
fnSimplex = FastNoise::New<FastNoise::Perlin>();
fnFractal = FastNoise::New<FastNoise::FractalFBm>();
fnFractal->SetSource(fnSimplex);
fnFractal->SetOctaveCount(octaves);
fnFractal->SetGain(gain);
fnFractal->SetLacunarity(lacunarity);
fnFractal->SetWeightedStrength(weighted_strength);
randomizeSeed();
}
Generator::~Generator() {}
void Generator::reseed(int32_t _seed) {
this->seed = _seed;
}
int32_t Generator::randomizeSeed() {
std::random_device rd;
std::mt19937_64 gen(rd());
std::uniform_int_distribution<uint32_t> dis(std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::max());
this->seed = dis(gen);
return seed;
}
uint32_t Generator::get_seed() const {
return seed;
}
void Generator::setOctaves(uint32_t _octaves) {
this->octaves = _octaves;
fnFractal->SetOctaveCount(octaves);
}
uint32_t Generator::getOctaves() const {
return octaves;
}
void Generator::setLacunarity(float _lacunarity) {
this->lacunarity = _lacunarity;
fnFractal->SetLacunarity(lacunarity);
}
float Generator::getLacunarity() const {
return lacunarity;
}
void Generator::setGain(float _gain) {
this->gain = _gain;
fnFractal->SetGain(gain);
}
float Generator::getGain() const {
return gain;
}
void Generator::setFrequency(float _frequency) {
this->frequency = _frequency;
}
float Generator::getFrequency() const {
return frequency;
}
void Generator::setWeightedStrength(float _weighted_strength) {
this->weighted_strength = _weighted_strength;
fnFractal->SetWeightedStrength(weighted_strength);
}
float Generator::getWeightedStrength() const {
return weighted_strength;
}
void Generator::setMultiplier(uint32_t _multiplier) {
this->multiplier = _multiplier;
}
uint32_t Generator::getMultiplier() const {
return multiplier;
}
std::vector<uint32_t> Generator::generate2dMeightmap(const int32_t begin_x,
[[maybe_unused]] const int32_t begin_y,
const int32_t begin_z,
const uint32_t size_x,
[[maybe_unused]] const uint32_t size_y,
const uint32_t size_z) {
constexpr bool debug = false;
std::vector<uint32_t> heightmap(size_x * size_z);
std::vector<float> noise_output(size_x * size_z);
if (fnFractal.get() == nullptr) {
std::cout << "fnFractal is nullptr" << std::endl;
return heightmap;
}
fnFractal->GenUniformGrid2D(noise_output.data(), begin_x, begin_z, size_x, size_z, frequency, seed);
// Convert noise_output to heightmap
for (uint32_t i = 0; i < size_x * size_z; i++) {
heightmap[i] = static_cast<uint32_t>((noise_output[i] + 1.0) * multiplier);
if constexpr (debug) {
std::cout << "i: " << i << ", value: " << noise_output[i] << ", heightmap: " << heightmap[i] << std::endl;
}
}
if constexpr (debug) {
// cout max and min
auto minmax = std::minmax_element(heightmap.begin(), heightmap.end());
std::cout << "min: " << static_cast<int32_t>(*minmax.first) << std::endl;
std::cout << "max: " << static_cast<int32_t>(*minmax.second) << std::endl;
}
return heightmap;
}
std::vector<uint32_t> Generator::generate3dHeightmap(const int32_t begin_x,
const int32_t begin_y,
const int32_t begin_z,
const uint32_t size_x,
const uint32_t size_y,
const uint32_t size_z) {
constexpr bool debug = false;
std::vector<uint32_t> heightmap(size_x * size_y * size_z);
std::vector<float> noise_output(size_x * size_y * size_z);
if (fnFractal.get() == nullptr) {
std::cout << "fnFractal is nullptr" << std::endl;
return heightmap;
}
fnFractal->GenUniformGrid3D(noise_output.data(), begin_x, begin_y, begin_z, size_x, size_y, size_z, frequency, seed);
// Convert noise_output to heightmap
for (uint32_t i = 0; i < size_x * size_y * size_z; i++) {
heightmap[i] = static_cast<uint32_t>((noise_output[i] + 1.0) * multiplier);
if constexpr (debug) {
std::cout << "i: " << i << ", noise_output: " << noise_output[i] << ", heightmap: " << heightmap[i] << std::endl;
}
}
if constexpr (debug) {
// cout max and min
auto minmax = std::minmax_element(heightmap.begin(), heightmap.end());
std::cout << "min: " << static_cast<int32_t>(*minmax.first) << std::endl;
std::cout << "max: " << static_cast<int32_t>(*minmax.second) << std::endl;
}
return heightmap;
}
/*
std::unique_ptr<Chunk> Generator::generateChunk(const int32_t chunk_x,
const int32_t chunk_y,
const int32_t chunk_z,
const bool generate_3d_terrain) {
const int32_t real_x = chunk_x * Chunk::chunk_size_x;
const int32_t real_y = chunk_y * Chunk::chunk_size_y;
const int32_t real_z = chunk_z * Chunk::chunk_size_z;
std::vector<Block> blocks;
std::unique_ptr<Chunk> _chunk = std::make_unique<Chunk>();
if (generate_3d_terrain) {
blocks = std::move(generate3d(real_x, real_y, real_z, Chunk::chunk_size_x, Chunk::chunk_size_y, Chunk::chunk_size_z));
} else {
blocks = std::move(generate2d(real_x, real_y, real_z, Chunk::chunk_size_x, Chunk::chunk_size_y, Chunk::chunk_size_z));
}
_chunk->set_blocks(blocks);
_chunk->set_chuck_pos(chunk_x, chunk_y, chunk_z);
return _chunk;
}
[[nodiscard]] std::vector<std::unique_ptr<Chunk>> Generator::generateChunks(const int32_t begin_chunk_x,
const int32_t begin_chunk_y,
const int32_t begin_chunk_z,
const uint32_t size_x,
const uint32_t size_y,
const uint32_t size_z,
const bool generate_3d_terrain) {
constexpr bool debug = false;
std::vector<std::unique_ptr<Chunk>> chunks;
chunks.reserve(size_x * size_y * size_z);
#pragma omp parallel for collapse(3) schedule(auto)
for (int32_t x = begin_chunk_x; x < begin_chunk_x + size_x; x++) {
for (int32_t z = begin_chunk_y; z < begin_chunk_y + size_z; z++) {
for (int32_t y = begin_chunk_z; y < begin_chunk_z + size_y; y++) {
auto gen_chunk = generateChunk(x, y, z, generate_3d_terrain);
#pragma omp critical
chunks.emplace_back(std::move(gen_chunk));
}
}
}
return chunks;
}
std::vector<Block> Generator::generate2d(const int32_t begin_x,
const int32_t begin_y,
const int32_t begin_z,
const uint32_t size_x,
const uint32_t size_y,
const uint32_t size_z) {
constexpr bool debug = false;
std::vector<uint32_t> heightmap;
std::vector<Block> blocks = std::vector<Block>(size_x * size_y * size_z, Block());
heightmap = std::move(generate2dMeightmap(begin_x, begin_y, begin_z, size_x, size_y, size_z));
// Generate blocks
for (uint32_t x = 0; x < size_x; x++) {
for (uint32_t z = 0; z < size_z; z++) {
// Noise value is divided by 4 to make it smaller and it is used as the height of the Block (z)
std::vector<Block>::size_type vec_index = math::convert_to_1d(x, z, size_x, size_z);
uint32_t noise_value = heightmap[vec_index] / 4;
for (uint32_t y = 0; y < size_y; y++) {
// Calculate real y from begin_y
vec_index = math::convert_to_1d(x, y, z, size_x, size_y, size_z);
if constexpr (debug) {
std::cout << "x: " << x << ", z: " << z << ", y: " << y << " index: " << vec_index
<< ", noise: " << static_cast<int32_t>(noise_value) << std::endl;
}
Block& current_block = blocks[vec_index];
// If the noise value is greater than the current Block, make it air
if (noise_value > 120) {
current_block.block_type = block_type::stone;
continue;
}
}
}
}
return blocks;
}
std::vector<Block> Generator::generate3d(const int32_t begin_x,
const int32_t begin_y,
const int32_t begin_z,
const uint32_t size_x,
const uint32_t size_y,
const uint32_t size_z) {
constexpr bool debug = false;
std::vector<Block> blocks = std::vector<Block>(size_x * size_y * size_z, Block());
std::vector<uint32_t> heightmap = generate3dHeightmap(begin_x, begin_y, begin_z, size_x, size_y, size_z);
// Generate blocks
for (uint32_t x = 0; x < size_x; x++) {
for (uint32_t z = 0; z < size_z; z++) {
for (uint32_t y = 0; y < size_y; y++) {
size_t vec_index = math::convert_to_1d(x, y, z, size_x, size_y, size_z);
const uint32_t noise_value = heightmap[vec_index];
auto& current_block = blocks[vec_index];
if constexpr (debug) {
std::cout << "x: " << x << ", z: " << z << ", y: " << y << " index: " << vec_index
<< ", noise: " << static_cast<int32_t>(noise_value) << std::endl;
}
if (noise_value > 120) {
current_block.block_type = block_type::stone;
continue;
}
}
}
}
return blocks;
}
*/

View File

@ -0,0 +1,115 @@
#ifndef WORLD_OF_CUBE_GENERATOR_HPP
#define WORLD_OF_CUBE_GENERATOR_HPP
#include <algorithm>
#include <chrono>
#include <filesystem>
#include <iostream>
#include <numeric>
#include <random>
#include <string>
#include <string_view>
#include <vector>
// #include <omp.h>
#ifndef FASTNOISE_HPP_INCLUDED
#define FASTNOISE_HPP_INCLUDED
#ifdef __GNUC__
#pragma GCC system_header
#endif
#ifdef __clang__
#pragma clang system_header
#endif
#include "FastNoise/FastNoise.h"
#endif
class Generator {
public:
explicit Generator(int32_t _seed);
explicit Generator();
~Generator();
void reseed(int32_t _seed);
int32_t randomizeSeed();
uint32_t get_seed() const;
void setOctaves(uint32_t _octaves);
uint32_t getOctaves() const;
void setLacunarity(float _lacunarity);
float getLacunarity() const;
void setGain(float _gain);
float getGain() const;
void setFrequency(float _frequency);
float getFrequency() const;
void setWeightedStrength(float _weighted_strength);
float getWeightedStrength() const;
void setMultiplier(uint32_t _multiplier);
uint32_t getMultiplier() const;
std::vector<uint32_t> generate2dMeightmap(const int32_t begin_x,
[[maybe_unused]] const int32_t begin_y,
const int32_t begin_z,
const uint32_t size_x,
[[maybe_unused]] const uint32_t size_y,
const uint32_t size_z);
std::vector<uint32_t> generate3dHeightmap(const int32_t begin_x,
const int32_t begin_y,
const int32_t begin_z,
const uint32_t size_x,
const uint32_t size_y,
const uint32_t size_z);
/*
std::unique_ptr<Chunk> generateChunk(const int32_t chunk_x, const int32_t chunk_y, const int32_t chunk_z, const bool generate_3d_terrain);
[[nodiscard]] std::vector<std::unique_ptr<Chunk>> generateChunks(const int32_t begin_chunk_x,
const int32_t begin_chunk_y,
const int32_t begin_chunk_z,
const uint32_t size_x,
const uint32_t size_y,
const uint32_t size_z,
const bool generate_3d_terrain);
std::vector<Block> generate2d(const int32_t begin_x,
const int32_t begin_y,
const int32_t begin_z,
const uint32_t size_x,
const uint32_t size_y,
const uint32_t size_z);
std::vector<Block> generate3d(const int32_t begin_x,
const int32_t begin_y,
const int32_t begin_z,
const uint32_t size_x,
const uint32_t size_y,
const uint32_t size_z);
*/
private:
// default seed
int32_t seed = 404;
FastNoise::SmartNode<FastNoise::Perlin> fnSimplex;
FastNoise::SmartNode<FastNoise::FractalFBm> fnFractal;
int32_t octaves = 6;
float lacunarity = 0.5f;
float gain = 3.5f;
float frequency = 0.4f;
float weighted_strength = 0.0f;
uint32_t multiplier = 128;
};
#endif // WORLD_OF_CUBE_GENERATOR_HPP

View File

@ -0,0 +1,87 @@
#include "astar/astar.hpp"
#include "gtest/gtest.h"
TEST(AStar, basic_path_1) {
int mapWidth = 4;
int mapHeight = 4;
AStar::AStar pathFinder;
pathFinder.setWorldSize({mapWidth, mapWidth});
pathFinder.setHeuristic(AStar::Heuristic::euclidean);
pathFinder.setDiagonalMovement(true);
AStar::Vec2i source(0, 0);
AStar::Vec2i target(mapWidth - 1, mapHeight - 1);
std::cout << "AStar::AStar pathFinder;" << std::endl;
auto path = pathFinder.findPath(source, target);
EXPECT_EQ(path.size(), 4);
for (size_t i = 0; i < path.size(); i++) {
EXPECT_EQ(path[i].x, path.size() - i - 1);
EXPECT_EQ(path[i].y, path.size() - i - 1);
}
}
TEST(AStar, basic_path_2) {
int mapWidth = 10;
int mapHeight = 10;
AStar::AStar pathFinder;
pathFinder.setWorldSize({mapWidth, mapWidth});
pathFinder.setHeuristic(AStar::Heuristic::euclidean);
pathFinder.setDiagonalMovement(true);
AStar::Vec2i source(0, 0);
AStar::Vec2i target(mapWidth - 1, mapHeight - 1);
auto path = pathFinder.findPath(source, target);
EXPECT_EQ(path.size(), 10);
for (size_t i = 0; i < path.size(); i++) {
EXPECT_EQ(path[i].x, path.size() - i - 1);
EXPECT_EQ(path[i].y, path.size() - i - 1);
}
}
TEST(AStar, basic_diagonal_path_wrong_1) {
int mapWidth = 10;
int mapHeight = 10;
AStar::AStar pathFinder;
pathFinder.setWorldSize({mapWidth, mapHeight});
pathFinder.setHeuristic(AStar::Heuristic::euclidean);
pathFinder.setDiagonalMovement(true);
AStar::Vec2i source(0, 0);
AStar::Vec2i target(19, 19);
auto path = pathFinder.findPath(source, target);
EXPECT_EQ(path.size(), 0);
}
TEST(AStar, basic_diagonal_path_wrong_2) {
int mapWidth = 10;
int mapHeight = 10;
AStar::AStar pathFinder;
pathFinder.setWorldSize({mapWidth, mapHeight});
pathFinder.setHeuristic(AStar::Heuristic::euclidean);
pathFinder.setDiagonalMovement(true);
pathFinder.addObstacle({0, 1});
pathFinder.addObstacle({1, 1});
pathFinder.addObstacle({1, 0});
AStar::Vec2i source(0, 0);
AStar::Vec2i target(9, 9);
auto path = pathFinder.findPath(source, target);
EXPECT_EQ(path.size(), 0);
}
auto main(int argc, char** argv) -> int {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

91
tools/graphic.py Executable file
View File

@ -0,0 +1,91 @@
# Based on work: https://int-i.github.io/python/2021-11-07/matplotlib-google-benchmark-visualization/
# Modified by: Bensuperpc
from argparse import ArgumentParser
from itertools import groupby
import json
import operator
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import pandas as pd
# Extract the benchmark name from the benchmark name string
def extract_label_from_benchmark(benchmark):
bench_full_name = benchmark['name']
bench_name = bench_full_name.split('/')[0] # Remove all after /
if (bench_name.startswith('BM_')): # Remove if string start with BM_
return bench_name[3:] # Remove BM_
else:
return bench_name
# Extract the benchmark size from the benchmark
def extract_size_from_benchmark(benchmark):
bench_name = benchmark['name']
return bench_name.split('/')[1] # Remove all before /
if __name__ == "__main__":
# ./prog_name --benchmark_format=json --benchmark_out=result.json
parser = ArgumentParser()
parser.add_argument('path', help='benchmark result json file')
args = parser.parse_args()
with open(args.path) as file:
benchmark_result = json.load(file)
benchmarks = benchmark_result['benchmarks']
elapsed_times = groupby(benchmarks, extract_label_from_benchmark)
data1 = None
data2 = None
for key, group in elapsed_times:
benchmark = list(group)
x = list(map(extract_size_from_benchmark, benchmark))
y1 = list(map(operator.itemgetter('bytes_per_second'), benchmark))
y2 = list(map(operator.itemgetter('items_per_second'), benchmark))
if data1 is None:
data1 = pd.DataFrame({'size': x, key: y1})
else:
data1[key] = y1
if data2 is None:
data2 = pd.DataFrame({'size': x, key: y2})
else:
data2[key] = y2
df1 = pd.melt(data1, id_vars=['size'], var_name='Benchmark', value_name='bytes_per_second')
df1_max_indices = df1.groupby(['size', 'Benchmark'])['bytes_per_second'].transform(max) == df1['bytes_per_second']
df1 = df1.loc[df1_max_indices]
df1.reset_index(drop=True, inplace=True)
df2 = pd.melt(data2, id_vars=['size'], var_name='Benchmark', value_name='items_per_second')
df2_max_indices = df2.groupby(['size', 'Benchmark'])['items_per_second'].transform(max) == df2['items_per_second']
df2 = df2.loc[df2_max_indices]
df2.reset_index(drop=True, inplace=True)
sns.set_theme()
fig, ax = plt.subplots(2, 1)
fig.set_size_inches(16, 9)
fig.set_dpi(96)
sns.lineplot(data=df1, x='size', y='bytes_per_second', hue='Benchmark', ax=ax[0])
sns.lineplot(data=df2, x='size', y='items_per_second', hue='Benchmark', ax=ax[1])
ax[0].set_title('Bytes per second')
ax[1].set_title('Items per second')
ax[0].set_xlabel('Array size')
ax[1].set_xlabel('Array size')
ax[0].set_ylabel('byte per second')
ax[1].set_ylabel('items per second')
fig.tight_layout()
plt.savefig('benchmark.png', bbox_inches='tight', dpi=300)
plt.show()