summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.cproject175
-rw-r--r--.externalToolBuilders/org.eclipse.cdt.make.core.ScannerConfigBuilder.launch7
-rw-r--r--.externalToolBuilders/org.eclipse.cdt.make.core.makeBuilder.launch22
-rw-r--r--.gitignore4
-rw-r--r--.project47
-rw-r--r--.settings/language.settings.xml25
-rw-r--r--.settings/org.eclipse.cdt.codan.core.prefs109
-rw-r--r--.settings/org.eclipse.cdt.core.prefs6
-rw-r--r--COPYING23
-rw-r--r--Makefile33
-rw-r--r--README.md46
-rw-r--r--fonts/freefont/COPYING24
-rw-r--r--fonts/freefont/FreeSansBold.ttfbin0 -> 416128 bytes
-rw-r--r--include/pacman/game.hpp312
-rw-r--r--include/pacman/globals.hpp67
-rw-r--r--include/pacman/graphics.hpp214
-rw-r--r--include/pacman/maze.hpp336
-rw-r--r--include/pacman/utils.hpp46
-rw-r--r--media/edit.sh21
-rw-r--r--media/playfield_pacman.0.txt36
-rw-r--r--media/playfield_pacman.pngbin0 -> 13132 bytes
-rw-r--r--media/playfield_pacman.txt46
-rw-r--r--media/tiles_all.pngbin0 -> 3028 bytes
-rw-r--r--media/tiles_all.txt8
-rw-r--r--src/game.cpp528
-rw-r--r--src/ghost.cpp424
-rw-r--r--src/graphics.cpp302
-rw-r--r--src/maze.cpp555
-rw-r--r--src/pacman.cpp276
-rw-r--r--src/utils.cpp142
30 files changed, 3834 insertions, 0 deletions
diff --git a/.cproject b/.cproject
new file mode 100644
index 0000000..895850e
--- /dev/null
+++ b/.cproject
@@ -0,0 +1,175 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
+ <storageModule moduleId="org.eclipse.cdt.core.settings">
+ <cconfiguration id="cdt.managedbuild.config.gnu.exe.debug.1232566612">
+ <storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.config.gnu.exe.debug.1232566612" moduleId="org.eclipse.cdt.core.settings" name="Debug">
+ <externalSettings/>
+ <extensions>
+ <extension id="org.eclipse.cdt.core.GNU_ELF" point="org.eclipse.cdt.core.BinaryParser"/>
+ <extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+ <extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+ <extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+ <extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
+ <extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+ </extensions>
+ </storageModule>
+ <storageModule moduleId="cdtBuildSystem" version="4.0.0">
+ <configuration artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe,org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.debug" cleanCommand="rm -rf" description="" id="cdt.managedbuild.config.gnu.exe.debug.1232566612" name="Debug" parent="cdt.managedbuild.config.gnu.exe.debug">
+ <folderInfo id="cdt.managedbuild.config.gnu.exe.debug.1232566612." name="/" resourcePath="">
+ <toolChain id="cdt.managedbuild.toolchain.gnu.exe.debug.1365973117" name="Linux GCC" superClass="cdt.managedbuild.toolchain.gnu.exe.debug">
+ <targetPlatform id="cdt.managedbuild.target.gnu.platform.exe.debug.699990338" name="Debug Platform" superClass="cdt.managedbuild.target.gnu.platform.exe.debug"/>
+ <builder buildPath="${workspace_loc:/pacman01}/Debug" id="cdt.managedbuild.target.gnu.builder.exe.debug.1911837107" keepEnvironmentInBuildfile="false" managedBuildOn="true" name="Gnu Make Builder" superClass="cdt.managedbuild.target.gnu.builder.exe.debug"/>
+ <tool id="cdt.managedbuild.tool.gnu.archiver.base.2057343435" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/>
+ <tool id="cdt.managedbuild.tool.gnu.cpp.compiler.exe.debug.477617998" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.exe.debug">
+ <option id="gnu.cpp.compiler.exe.debug.option.optimization.level.1722490109" name="Optimization Level" superClass="gnu.cpp.compiler.exe.debug.option.optimization.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.optimization.level.none" valueType="enumerated"/>
+ <option defaultValue="gnu.cpp.compiler.debugging.level.max" id="gnu.cpp.compiler.exe.debug.option.debugging.level.1734181908" name="Debug Level" superClass="gnu.cpp.compiler.exe.debug.option.debugging.level" useByScannerDiscovery="false" valueType="enumerated"/>
+ <option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="gnu.cpp.compiler.option.include.paths.1831982937" name="Include paths (-I)" superClass="gnu.cpp.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
+ <listOptionValue builtIn="false" value="/usr/include/SDL2"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/pacman/include}&quot;"/>
+ </option>
+ <option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="gnu.cpp.compiler.option.preprocessor.def.676728227" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols">
+ <listOptionValue builtIn="false" value="_REENTRANT=1"/>
+ </option>
+ <option id="gnu.cpp.compiler.option.dialect.std.2087727144" name="Language standard" superClass="gnu.cpp.compiler.option.dialect.std" useByScannerDiscovery="true" value="gnu.cpp.compiler.dialect.c++17" valueType="enumerated"/>
+ <inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.1506770238" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
+ </tool>
+ <tool id="cdt.managedbuild.tool.gnu.c.compiler.exe.debug.1805184010" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.exe.debug">
+ <option defaultValue="gnu.c.optimization.level.none" id="gnu.c.compiler.exe.debug.option.optimization.level.156497272" name="Optimization Level" superClass="gnu.c.compiler.exe.debug.option.optimization.level" useByScannerDiscovery="false" valueType="enumerated"/>
+ <option defaultValue="gnu.c.debugging.level.max" id="gnu.c.compiler.exe.debug.option.debugging.level.290704636" name="Debug Level" superClass="gnu.c.compiler.exe.debug.option.debugging.level" useByScannerDiscovery="false" valueType="enumerated"/>
+ <option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="gnu.c.compiler.option.include.paths.1533730267" name="Include paths (-I)" superClass="gnu.c.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
+ <listOptionValue builtIn="false" value="/usr/include/SDL2"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/pacman/include}&quot;"/>
+ </option>
+ <option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="gnu.c.compiler.option.preprocessor.def.symbols.1774720749" name="Defined symbols (-D)" superClass="gnu.c.compiler.option.preprocessor.def.symbols" useByScannerDiscovery="false" valueType="definedSymbols">
+ <listOptionValue builtIn="false" value="_REENTRANT=1"/>
+ </option>
+ <inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.488533926" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
+ </tool>
+ <tool id="cdt.managedbuild.tool.gnu.c.linker.exe.debug.948972707" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.exe.debug"/>
+ <tool id="cdt.managedbuild.tool.gnu.cpp.linker.exe.debug.581927154" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.exe.debug">
+ <option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="gnu.cpp.link.option.libs.255454586" name="Libraries (-l)" superClass="gnu.cpp.link.option.libs" useByScannerDiscovery="false" valueType="libs">
+ <listOptionValue builtIn="false" value="SDL2"/>
+ <listOptionValue builtIn="false" value="SDL2_image"/>
+ <listOptionValue builtIn="false" value="SDL2_ttf"/>
+ </option>
+ <inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.1244106497" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
+ <additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
+ <additionalInput kind="additionalinput" paths="$(LIBS)"/>
+ </inputType>
+ </tool>
+ <tool id="cdt.managedbuild.tool.gnu.assembler.exe.debug.683946619" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.exe.debug">
+ <option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="gnu.both.asm.option.include.paths.1411357867" name="Include paths (-I)" superClass="gnu.both.asm.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
+ <listOptionValue builtIn="false" value="/usr/include/SDL2"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/pacman/include}&quot;"/>
+ </option>
+ <inputType id="cdt.managedbuild.tool.gnu.assembler.input.1426008460" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
+ </tool>
+ </toolChain>
+ </folderInfo>
+ </configuration>
+ </storageModule>
+ <storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
+ </cconfiguration>
+ <cconfiguration id="cdt.managedbuild.config.gnu.exe.release.1489811840">
+ <storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.config.gnu.exe.release.1489811840" moduleId="org.eclipse.cdt.core.settings" name="Release">
+ <externalSettings/>
+ <extensions>
+ <extension id="org.eclipse.cdt.core.GNU_ELF" point="org.eclipse.cdt.core.BinaryParser"/>
+ <extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+ <extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+ <extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+ <extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
+ <extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
+ </extensions>
+ </storageModule>
+ <storageModule moduleId="cdtBuildSystem" version="4.0.0">
+ <configuration artifactName="${ProjName}" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe,org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.release" cleanCommand="rm -rf" description="" id="cdt.managedbuild.config.gnu.exe.release.1489811840" name="Release" parent="cdt.managedbuild.config.gnu.exe.release">
+ <folderInfo id="cdt.managedbuild.config.gnu.exe.release.1489811840." name="/" resourcePath="">
+ <toolChain id="cdt.managedbuild.toolchain.gnu.exe.release.1236899225" name="Linux GCC" superClass="cdt.managedbuild.toolchain.gnu.exe.release">
+ <targetPlatform id="cdt.managedbuild.target.gnu.platform.exe.release.876945268" name="Debug Platform" superClass="cdt.managedbuild.target.gnu.platform.exe.release"/>
+ <builder buildPath="${workspace_loc:/pacman01}/Release" id="cdt.managedbuild.target.gnu.builder.exe.release.182784210" keepEnvironmentInBuildfile="false" managedBuildOn="true" name="Gnu Make Builder" superClass="cdt.managedbuild.target.gnu.builder.exe.release"/>
+ <tool id="cdt.managedbuild.tool.gnu.archiver.base.1276082853" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/>
+ <tool id="cdt.managedbuild.tool.gnu.cpp.compiler.exe.release.37130241" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.exe.release">
+ <option id="gnu.cpp.compiler.exe.release.option.optimization.level.182324359" name="Optimization Level" superClass="gnu.cpp.compiler.exe.release.option.optimization.level" useByScannerDiscovery="false" value="gnu.cpp.compiler.optimization.level.most" valueType="enumerated"/>
+ <option defaultValue="gnu.cpp.compiler.debugging.level.none" id="gnu.cpp.compiler.exe.release.option.debugging.level.1204878426" name="Debug Level" superClass="gnu.cpp.compiler.exe.release.option.debugging.level" useByScannerDiscovery="false" valueType="enumerated"/>
+ <option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="gnu.cpp.compiler.option.include.paths.326357946" name="Include paths (-I)" superClass="gnu.cpp.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
+ <listOptionValue builtIn="false" value="/usr/include/SDL2"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/pacman/include}&quot;"/>
+ </option>
+ <option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="gnu.cpp.compiler.option.preprocessor.def.1854947580" name="Defined symbols (-D)" superClass="gnu.cpp.compiler.option.preprocessor.def" useByScannerDiscovery="false" valueType="definedSymbols">
+ <listOptionValue builtIn="false" value="_REENTRANT=1"/>
+ </option>
+ <inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.559792972" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
+ </tool>
+ <tool id="cdt.managedbuild.tool.gnu.c.compiler.exe.release.1421431622" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.exe.release">
+ <option defaultValue="gnu.c.optimization.level.most" id="gnu.c.compiler.exe.release.option.optimization.level.296340700" name="Optimization Level" superClass="gnu.c.compiler.exe.release.option.optimization.level" useByScannerDiscovery="false" valueType="enumerated"/>
+ <option defaultValue="gnu.c.debugging.level.none" id="gnu.c.compiler.exe.release.option.debugging.level.13203684" name="Debug Level" superClass="gnu.c.compiler.exe.release.option.debugging.level" useByScannerDiscovery="false" valueType="enumerated"/>
+ <option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="gnu.c.compiler.option.include.paths.1206440806" name="Include paths (-I)" superClass="gnu.c.compiler.option.include.paths" useByScannerDiscovery="false" valueType="includePath">
+ <listOptionValue builtIn="false" value="/usr/include/SDL2"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/pacman/include}&quot;"/>
+ </option>
+ <option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="gnu.c.compiler.option.preprocessor.def.symbols.366454444" name="Defined symbols (-D)" superClass="gnu.c.compiler.option.preprocessor.def.symbols" useByScannerDiscovery="false" valueType="definedSymbols">
+ <listOptionValue builtIn="false" value="_REENTRANT=1"/>
+ </option>
+ <inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.1798772024" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
+ </tool>
+ <tool id="cdt.managedbuild.tool.gnu.c.linker.exe.release.119101961" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.exe.release"/>
+ <tool id="cdt.managedbuild.tool.gnu.cpp.linker.exe.release.887134328" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.exe.release">
+ <option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="gnu.cpp.link.option.libs.1429831366" name="Libraries (-l)" superClass="gnu.cpp.link.option.libs" valueType="libs">
+ <listOptionValue builtIn="false" value="SDL2"/>
+ <listOptionValue builtIn="false" value="SDL2_image"/>
+ <listOptionValue builtIn="false" value="SDL2_ttf"/>
+ </option>
+ <inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.559766355" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
+ <additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
+ <additionalInput kind="additionalinput" paths="$(LIBS)"/>
+ </inputType>
+ </tool>
+ <tool id="cdt.managedbuild.tool.gnu.assembler.exe.release.1404169083" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.exe.release">
+ <option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="gnu.both.asm.option.include.paths.1801232559" name="Include paths (-I)" superClass="gnu.both.asm.option.include.paths" valueType="includePath">
+ <listOptionValue builtIn="false" value="/usr/include/SDL2"/>
+ <listOptionValue builtIn="false" value="&quot;${workspace_loc:/pacman/include}&quot;"/>
+ </option>
+ <inputType id="cdt.managedbuild.tool.gnu.assembler.input.1555131261" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
+ </tool>
+ </toolChain>
+ </folderInfo>
+ </configuration>
+ </storageModule>
+ <storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
+ </cconfiguration>
+ </storageModule>
+ <storageModule moduleId="org.eclipse.cdt.core.pathentry">
+ <pathentry kind="src" path=""/>
+ <pathentry kind="out" path=""/>
+ </storageModule>
+ <storageModule moduleId="cdtBuildSystem" version="4.0.0">
+ <project id="pacman01.cdt.managedbuild.target.gnu.exe.1462227168" name="Executable" projectType="cdt.managedbuild.target.gnu.exe"/>
+ </storageModule>
+ <storageModule moduleId="scannerConfiguration">
+ <autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
+ <scannerConfigBuildInfo instanceId="cdt.managedbuild.config.gnu.exe.debug.1232566612;cdt.managedbuild.config.gnu.exe.debug.1232566612.;cdt.managedbuild.tool.gnu.c.compiler.exe.debug.1805184010;cdt.managedbuild.tool.gnu.c.compiler.input.488533926">
+ <autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
+ </scannerConfigBuildInfo>
+ <scannerConfigBuildInfo instanceId="cdt.managedbuild.config.gnu.exe.release.1489811840;cdt.managedbuild.config.gnu.exe.release.1489811840.;cdt.managedbuild.tool.gnu.c.compiler.exe.release.1421431622;cdt.managedbuild.tool.gnu.c.compiler.input.1798772024">
+ <autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
+ </scannerConfigBuildInfo>
+ <scannerConfigBuildInfo instanceId="cdt.managedbuild.config.gnu.exe.release.1489811840;cdt.managedbuild.config.gnu.exe.release.1489811840.;cdt.managedbuild.tool.gnu.cpp.compiler.exe.release.37130241;cdt.managedbuild.tool.gnu.cpp.compiler.input.559792972">
+ <autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
+ </scannerConfigBuildInfo>
+ <scannerConfigBuildInfo instanceId="cdt.managedbuild.config.gnu.exe.debug.1232566612;cdt.managedbuild.config.gnu.exe.debug.1232566612.;cdt.managedbuild.tool.gnu.cpp.compiler.exe.debug.477617998;cdt.managedbuild.tool.gnu.cpp.compiler.input.1506770238">
+ <autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
+ </scannerConfigBuildInfo>
+ </storageModule>
+ <storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
+ <storageModule moduleId="refreshScope" versionNumber="2">
+ <configuration configurationName="Debug">
+ <resource resourceType="PROJECT" workspacePath="/pacman01"/>
+ </configuration>
+ <configuration configurationName="Release">
+ <resource resourceType="PROJECT" workspacePath="/pacman01"/>
+ </configuration>
+ </storageModule>
+ <storageModule moduleId="org.eclipse.cdt.make.core.buildtargets"/>
+ <storageModule moduleId="org.eclipse.cdt.internal.ui.text.commentOwnerProjectMappings"/>
+</cproject> \ No newline at end of file
diff --git a/.externalToolBuilders/org.eclipse.cdt.make.core.ScannerConfigBuilder.launch b/.externalToolBuilders/org.eclipse.cdt.make.core.ScannerConfigBuilder.launch
new file mode 100644
index 0000000..843e428
--- /dev/null
+++ b/.externalToolBuilders/org.eclipse.cdt.make.core.ScannerConfigBuilder.launch
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.ant.AntBuilderLaunchConfigurationType">
+ <booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
+ <stringAttribute key="org.eclipse.ui.externaltools.ATTR_DISABLED_BUILDER" value="org.eclipse.cdt.make.core.ScannerConfigBuilder"/>
+ <mapAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS"/>
+ <booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
+</launchConfiguration>
diff --git a/.externalToolBuilders/org.eclipse.cdt.make.core.makeBuilder.launch b/.externalToolBuilders/org.eclipse.cdt.make.core.makeBuilder.launch
new file mode 100644
index 0000000..58290b7
--- /dev/null
+++ b/.externalToolBuilders/org.eclipse.cdt.make.core.makeBuilder.launch
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.ant.AntBuilderLaunchConfigurationType">
+ <booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
+ <stringAttribute key="org.eclipse.ui.externaltools.ATTR_DISABLED_BUILDER" value="org.eclipse.cdt.make.core.makeBuilder"/>
+ <mapAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS">
+ <mapEntry key="org.eclipse.cdt.core.errorOutputParser" value="org.eclipse.cdt.autotools.core.ErrorParser;org.eclipse.cdt.core.GASErrorParser;org.eclipse.cdt.core.GLDErrorParser;org.eclipse.cdt.core.GCCErrorParser;org.eclipse.cdt.core.GmakeErrorParser;org.eclipse.cdt.core.VCErrorParser;org.eclipse.cdt.core.CWDLocator;org.eclipse.cdt.core.MakeErrorParser;"/>
+ <mapEntry key="org.eclipse.cdt.make.core.append_environment" value="true"/>
+ <mapEntry key="org.eclipse.cdt.make.core.build.arguments" value=""/>
+ <mapEntry key="org.eclipse.cdt.make.core.build.command" value="make"/>
+ <mapEntry key="org.eclipse.cdt.make.core.build.target.auto" value="all"/>
+ <mapEntry key="org.eclipse.cdt.make.core.build.target.clean" value="clean"/>
+ <mapEntry key="org.eclipse.cdt.make.core.build.target.inc" value="all"/>
+ <mapEntry key="org.eclipse.cdt.make.core.enableAutoBuild" value="false"/>
+ <mapEntry key="org.eclipse.cdt.make.core.enableCleanBuild" value="true"/>
+ <mapEntry key="org.eclipse.cdt.make.core.enableFullBuild" value="true"/>
+ <mapEntry key="org.eclipse.cdt.make.core.enabledIncrementalBuild" value="true"/>
+ <mapEntry key="org.eclipse.cdt.make.core.environment" value=""/>
+ <mapEntry key="org.eclipse.cdt.make.core.stopOnError" value="false"/>
+ <mapEntry key="org.eclipse.cdt.make.core.useDefaultBuildCmd" value="true"/>
+ </mapAttribute>
+ <booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
+</launchConfiguration>
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..75b05b8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/media_external/
+/Debug/
+/bin/
+/obj/
diff --git a/.project b/.project
new file mode 100644
index 0000000..7f59836
--- /dev/null
+++ b/.project
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>pacman</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
+ <triggers>clean,full,incremental,</triggers>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
+ <triggers>full,incremental,</triggers>
+ <arguments>
+ <dictionary>
+ <key>LaunchConfigHandle</key>
+ <value>&lt;project&gt;/.externalToolBuilders/org.eclipse.cdt.make.core.makeBuilder.launch</value>
+ </dictionary>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
+ <triggers>full,incremental,</triggers>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
+ <triggers>full,incremental,</triggers>
+ <arguments>
+ <dictionary>
+ <key>LaunchConfigHandle</key>
+ <value>&lt;project&gt;/.externalToolBuilders/org.eclipse.cdt.make.core.ScannerConfigBuilder.launch</value>
+ </dictionary>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.cdt.core.ccnature</nature>
+ <nature>org.eclipse.cdt.core.cnature</nature>
+ <nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
+ <nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
+ </natures>
+</projectDescription>
diff --git a/.settings/language.settings.xml b/.settings/language.settings.xml
new file mode 100644
index 0000000..af1a11b
--- /dev/null
+++ b/.settings/language.settings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project>
+ <configuration id="cdt.managedbuild.config.gnu.exe.debug.1232566612" name="Debug">
+ <extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
+ <provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
+ <provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
+ <provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
+ <provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector" console="false" env-hash="1541271538745106720" id="org.eclipse.cdt.managedbuilder.core.GCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
+ <language-scope id="org.eclipse.cdt.core.gcc"/>
+ <language-scope id="org.eclipse.cdt.core.g++"/>
+ </provider>
+ </extension>
+ </configuration>
+ <configuration id="cdt.managedbuild.config.gnu.exe.release.1489811840" name="Release">
+ <extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
+ <provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
+ <provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
+ <provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
+ <provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuiltinSpecsDetector" console="false" env-hash="1541268105239560638" id="org.eclipse.cdt.managedbuilder.core.GCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT GCC Built-in Compiler Settings" parameter="${COMMAND} ${FLAGS} -E -P -v -dD &quot;${INPUTS}&quot;" prefer-non-shared="true">
+ <language-scope id="org.eclipse.cdt.core.gcc"/>
+ <language-scope id="org.eclipse.cdt.core.g++"/>
+ </provider>
+ </extension>
+ </configuration>
+</project> \ No newline at end of file
diff --git a/.settings/org.eclipse.cdt.codan.core.prefs b/.settings/org.eclipse.cdt.codan.core.prefs
new file mode 100644
index 0000000..da30759
--- /dev/null
+++ b/.settings/org.eclipse.cdt.codan.core.prefs
@@ -0,0 +1,109 @@
+eclipse.preferences.version=1
+org.eclipse.cdt.codan.checkers.errnoreturn=Warning
+org.eclipse.cdt.codan.checkers.errnoreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No return\\")",implicit\=>false}
+org.eclipse.cdt.codan.checkers.errreturnvalue=Error
+org.eclipse.cdt.codan.checkers.errreturnvalue.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused return value\\")"}
+org.eclipse.cdt.codan.checkers.localvarreturn=-Warning
+org.eclipse.cdt.codan.checkers.localvarreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Returning the address of a local variable\\")"}
+org.eclipse.cdt.codan.checkers.nocommentinside=-Error
+org.eclipse.cdt.codan.checkers.nocommentinside.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Nesting comments\\")"}
+org.eclipse.cdt.codan.checkers.nolinecomment=-Error
+org.eclipse.cdt.codan.checkers.nolinecomment.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Line comments\\")"}
+org.eclipse.cdt.codan.checkers.noreturn=Error
+org.eclipse.cdt.codan.checkers.noreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No return value\\")",implicit\=>false}
+org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation=Error
+org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Abstract class cannot be instantiated\\")"}
+org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem=Error
+org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Ambiguous problem\\")"}
+org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Assignment in condition\\")"}
+org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem=Error
+org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Assignment to itself\\")"}
+org.eclipse.cdt.codan.internal.checkers.BlacklistProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.BlacklistProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Function or method is blacklisted\\")",blacklist\=>()}
+org.eclipse.cdt.codan.internal.checkers.CStyleCastProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.CStyleCastProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"C-Style cast instead of C++ cast\\")",checkMacro\=>true}
+org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No break at end of case\\")",no_break_comment\=>"no break",last_case_param\=>false,empty_case_param\=>false,enable_fallthrough_quickfix_param\=>false}
+org.eclipse.cdt.codan.internal.checkers.CatchByReference=Warning
+org.eclipse.cdt.codan.internal.checkers.CatchByReference.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Catching by reference is recommended\\")",unknown\=>false,exceptions\=>()}
+org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem=Error
+org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Circular inheritance\\")"}
+org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization=Warning
+org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Class members should be properly initialized\\")",skip\=>true}
+org.eclipse.cdt.codan.internal.checkers.CopyrightProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.CopyrightProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Lack of copyright information\\")",regex\=>".*Copyright.*"}
+org.eclipse.cdt.codan.internal.checkers.DecltypeAutoProblem=Error
+org.eclipse.cdt.codan.internal.checkers.DecltypeAutoProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid 'decltype(auto)' specifier\\")"}
+org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem=Error
+org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Field cannot be resolved\\")"}
+org.eclipse.cdt.codan.internal.checkers.FloatCompareProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.FloatCompareProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Direct float comparison\\")"}
+org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem=Error
+org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Function cannot be resolved\\")"}
+org.eclipse.cdt.codan.internal.checkers.GotoStatementProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.GotoStatementProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Goto statement used\\")"}
+org.eclipse.cdt.codan.internal.checkers.InvalidArguments=Error
+org.eclipse.cdt.codan.internal.checkers.InvalidArguments.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid arguments\\")"}
+org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem=Error
+org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid template argument\\")"}
+org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem=Error
+org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Label statement not found\\")"}
+org.eclipse.cdt.codan.internal.checkers.MagicNumberProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.MagicNumberProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Avoid magic numbers\\")",checkArray\=>true,checkOperatorParen\=>true,exceptions\=>(1,0,-1,2,1.0,0.0,-1.0)}
+org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem=Error
+org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Member declaration not found\\")"}
+org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem=Error
+org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Method cannot be resolved\\")"}
+org.eclipse.cdt.codan.internal.checkers.MissCaseProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.MissCaseProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Missing cases in switch\\")"}
+org.eclipse.cdt.codan.internal.checkers.MissDefaultProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.MissDefaultProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Missing default in switch\\")",defaultWithAllEnums\=>false}
+org.eclipse.cdt.codan.internal.checkers.MissReferenceProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.MissReferenceProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Missing reference return value in assignment operator\\")"}
+org.eclipse.cdt.codan.internal.checkers.MissSelfCheckProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.MissSelfCheckProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Missing self check in assignment operator\\")"}
+org.eclipse.cdt.codan.internal.checkers.MultipleDeclarationsProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.MultipleDeclarationsProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Multiple variable declaration\\")"}
+org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker=-Info
+org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Name convention for function\\")",pattern\=>"^[a-z]",macro\=>true,exceptions\=>()}
+org.eclipse.cdt.codan.internal.checkers.NoDiscardProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.NoDiscardProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Return value not evaluated\\")",macro\=>true}
+org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Class has a virtual method and non-virtual destructor\\")"}
+org.eclipse.cdt.codan.internal.checkers.OverloadProblem=Error
+org.eclipse.cdt.codan.internal.checkers.OverloadProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid overload\\")"}
+org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem=Error
+org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid redeclaration\\")"}
+org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem=Error
+org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid redefinition\\")"}
+org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Return with parenthesis\\")"}
+org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Format String Vulnerability\\")"}
+org.eclipse.cdt.codan.internal.checkers.ShallowCopyProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.ShallowCopyProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Miss copy constructor or assignment operator\\")",onlynew\=>false}
+org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Statement has no effect\\")",macro\=>true,exceptions\=>()}
+org.eclipse.cdt.codan.internal.checkers.StaticVariableInHeaderProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.StaticVariableInHeaderProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Static variable in header file\\")"}
+org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Suggested parenthesis around expression\\")",paramNot\=>false}
+org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Suspicious semicolon\\")",else\=>false,afterelse\=>false}
+org.eclipse.cdt.codan.internal.checkers.SymbolShadowingProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.SymbolShadowingProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Symbol shadowing\\")",paramFuncParameters\=>true}
+org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem=Error
+org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Type cannot be resolved\\")"}
+org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused function declaration\\")",macro\=>true}
+org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused static function\\")",macro\=>true}
+org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem=Warning
+org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused variable declaration in file scope\\")",macro\=>true,exceptions\=>("@(\#)","$Id")}
+org.eclipse.cdt.codan.internal.checkers.UsingInHeaderProblem=-Warning
+org.eclipse.cdt.codan.internal.checkers.UsingInHeaderProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Using directive in header\\")"}
+org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem=Error
+org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Symbol is not resolved\\")"}
+org.eclipse.cdt.codan.internal.checkers.VirtualMethodCallProblem=-Error
+org.eclipse.cdt.codan.internal.checkers.VirtualMethodCallProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Virtual method call in constructor/destructor\\")"}
diff --git a/.settings/org.eclipse.cdt.core.prefs b/.settings/org.eclipse.cdt.core.prefs
new file mode 100644
index 0000000..c8ec5df
--- /dev/null
+++ b/.settings/org.eclipse.cdt.core.prefs
@@ -0,0 +1,6 @@
+doxygen/doxygen_new_line_after_brief=true
+doxygen/doxygen_use_brief_tag=false
+doxygen/doxygen_use_javadoc_tags=true
+doxygen/doxygen_use_pre_tag=false
+doxygen/doxygen_use_structural_commands=false
+eclipse.preferences.version=1
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..234e3b3
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,23 @@
+The MIT License (MIT)
+Author: Sven Gothel <[email protected]> and Svenson Han Gothel
+Copyright © 2020 Gothel Software e.K.
+
+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.
+
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..125bc4e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,33 @@
+# A simple Makefile for compiling small SDL projects
+
+# set the compiler
+CPP := g++ -x c++ -std=c++17 -c
+LN := g++
+
+# set the compiler flags
+# DBGFLAGS := -ggdb3 -O0
+DBGFLAGS := -O3
+CPPFLAGS := -Wall -Iinclude ${DBGFLAGS} `sdl2-config --cflags`
+LNFLAGS := -Wall ${DBGFLAGS} -lm `sdl2-config --libs` -lSDL2_image -lSDL2_ttf
+
+HEADERS := include/pacman/*
+
+obj/%.o: src/%.cpp $(HEADERS) Makefile
+ $(CPP) -o $@ $(CPPFLAGS) $<
+
+# default recipe
+all: obj bin bin/pacman
+
+bin/pacman: obj/maze.o obj/graphics.o obj/game.o obj/pacman.o obj/ghost.o obj/utils.o
+ $(LN) -o $@ $^ $(LNFLAGS)
+
+obj:
+ mkdir -p $@
+
+bin:
+ mkdir -p $@
+
+clean:
+ rm -rf obj bin Debug
+
+.PHONY: all clean
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1ccbde6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,46 @@
+# Pacman, a naive implementation of the classic game in C++
+
+[Original document location](https://jausoft.com/cgit/cs_class/pacman.git/about/).
+
+## Git Repository
+This project's canonical repositories is hosted on [Gothel Software](https://jausoft.com/cgit/cs_class/pacman.git/).
+
+## Goals
+This project has been created to demonstrate a complex system written in modern C++
+for our computer science class.
+
+Besides management of animated sprite graphics, maze environment and tile positioning,
+the interesting part might be the ghost's state machine and their movements.
+
+To implement the original pacman game behavior like weighted tile collision,
+ghost algorithm, etc. - we have used Jamey Pittman's [The Pac-Man Dossier](https://www.gamedeveloper.com/design/the-pac-man-dossier)
+and [Understanding Pac-Man Ghost Behavior](https://gameinternals.com/understanding-pac-man-ghost-behavior)
+for reference.
+
+## Supported Platforms
+C++17 and better where the [SDL2 library](https://www.libsdl.org/) is supported.
+
+## Building Binaries
+The project requires make, g++ >= 8.3 and the libsdl2 for building.
+
+Installing build dependencies on Debian (11 or better):
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.sh}
+apt install git build-essential g++ gcc libc-dev make
+apt install libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev libsdl2-mixer-dev
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For a generic build use:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.sh}
+git clone https://jausoft.com/cgit/cs_class/pacman.git
+cd pacman
+make
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The binary shall be build to `bin/pacman`.
+
+## Changes
+
+**0.0.1**
+
+* Initial commit with working status and prelim ghost algorithm.
+
diff --git a/fonts/freefont/COPYING b/fonts/freefont/COPYING
new file mode 100644
index 0000000..cb90e14
--- /dev/null
+++ b/fonts/freefont/COPYING
@@ -0,0 +1,24 @@
+https://fonts2u.com/free-sans-bold.font
+
+Basic font information:
+
+Copyright notice
+Copyleft 2002, 2003, 2005, 2008, 2009, 2010 Free Software Foundation.
+
+Font family
+FreeSans
+
+Font subfamily
+Bold
+
+Full font name
+Free Sans Bold
+
+Name table version
+Version $Revision: 1.199 $
+
+Postscript font name
+FreeSansBold
+
+Manufacturer name
+GNU
diff --git a/fonts/freefont/FreeSansBold.ttf b/fonts/freefont/FreeSansBold.ttf
new file mode 100644
index 0000000..66e19ec
--- /dev/null
+++ b/fonts/freefont/FreeSansBold.ttf
Binary files differ
diff --git a/include/pacman/game.hpp b/include/pacman/game.hpp
new file mode 100644
index 0000000..b084487
--- /dev/null
+++ b/include/pacman/game.hpp
@@ -0,0 +1,312 @@
+/*
+ * Author: Sven Gothel <[email protected]> and Svenson Han Gothel
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * 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.
+ */
+#ifndef PACMAN_GAME_HPP_
+#define PACMAN_GAME_HPP_
+
+#include <pacman/maze.hpp>
+#include <pacman/graphics.hpp>
+
+//
+// score_t
+//
+
+enum class score_t : int {
+ NONE = 0,
+ PELLET = 10,
+ PELLET_POWER = 50,
+ GHOST_1 = 200,
+ GHOST_2 = 400,
+ GHOST_3 = 800,
+ GHOST_4 = 1600,
+ CHERRY = 100,
+ STRAWBERRY = 300,
+ ORANGE = 500,
+ APPLE = 700,
+ MELON = 1000,
+ GALAXIAN = 2000,
+ BELL = 300,
+ KEY = 5000
+};
+constexpr int number(const score_t item) noexcept {
+ return static_cast<int>(item);
+}
+score_t tile_to_score(const tile_t tile);
+
+//
+// global_tex_t
+//
+
+class global_tex_t {
+ private:
+ std::shared_ptr<texture_t> all_images;
+ std::vector<std::shared_ptr<texture_t>> textures;
+ animtex_t atex_pellet_power;
+
+ /**
+ * @param tile
+ * @return -1 if tile not handled, otherwise a valid textures index
+ */
+ int tile_to_texidx(const tile_t tile) const;
+
+ /**
+ * @param idx
+ * @return -1 if wrong idx, otherwise a valid textures index
+ */
+ int validate_texidx(const int idx) const;
+
+ public:
+ enum class special_idx : int {
+ GHOST_SCARED_BLUE = 10,
+ GHOST_SCARED_PINK = 11
+ };
+ static constexpr int number(const special_idx item) noexcept {
+ return static_cast<int>(item);
+ }
+
+ global_tex_t(SDL_Renderer* rend);
+
+ ~global_tex_t() {
+ destroy();
+ }
+
+ void destroy();
+
+ std::shared_ptr<texture_t> get_all_images() { return all_images; }
+
+ std::shared_ptr<texture_t> get_tex(const tile_t tile);
+ std::shared_ptr<const texture_t> get_tex(const tile_t tile) const;
+
+ std::shared_ptr<texture_t> get_tex(const int idx);
+ std::shared_ptr<const texture_t> get_tex(const int idx) const;
+
+ bool tick() {
+ atex_pellet_power.tick();
+ return true;
+ }
+
+ void draw_tile(const tile_t tile, SDL_Renderer* rend, const int x, const int y);
+
+ std::string toString() const;
+};
+
+//
+// ghost_t
+//
+
+/**
+ * See https://www.gamedeveloper.com/design/the-pac-man-dossier
+ */
+class ghost_t {
+ public:
+ enum class personality_t {
+ /** Red */
+ BLINKY,
+ /** Orange */
+ CLYDE,
+ /** Cyan or blue */
+ INKY,
+ /** Pink or mangenta */
+ PINKY
+ };
+
+ enum class mode_t {
+ AWAY,
+ HOME,
+ LEAVE_HOME,
+ CHASE,
+ SCATTER,
+ SCARED,
+ PHANTOM
+ };
+
+ /** mode durations in ms */
+ enum class mode_duration_t : int {
+ HOMESTAY = 4000,
+ CHASING = 20000,
+ SCATTERING = 7000,
+ SCARED = 10000,
+ PHANTOM = 20000
+ };
+ static constexpr int number(const mode_duration_t item) noexcept {
+ return static_cast<int>(item);
+ }
+ private:
+ const int ms_per_atex = 500;
+
+ const float fields_per_sec;
+
+ personality_t id; // not necessarily unique
+ mode_t mode;
+ int mode_ms_left;
+ direction_t dir_, last_dir;
+ int frame_count;
+
+ animtex_t atex_normal;
+ animtex_t atex_scared;
+ animtex_t atex_phantom;
+ animtex_t * atex;
+
+ acoord_t pos;
+ acoord_t target;
+
+ bool log_moves = false;
+
+ static int id_to_yoff(ghost_t::personality_t id);
+
+ animtex_t& get_tex();
+
+ animtex_t& get_phantom_tex() {
+ return atex_phantom;
+ }
+
+ acoord_t get_personal_target() const;
+
+ public:
+ ghost_t(const personality_t id_, SDL_Renderer* rend, const float fields_per_sec_=8);
+
+ ~ghost_t() {
+ destroy();
+ }
+
+ void destroy();
+
+ void set_log_moves(const bool v) { log_moves = v; }
+
+ mode_t get_mode() const { return mode; }
+ void set_mode(const mode_t m);
+
+ void set_next_dir();
+
+ direction_t get_dir() const { return dir_; }
+
+ const acoord_t& get_pos() const { return pos; }
+ const acoord_t& get_target() const { return target; }
+
+ /**
+ * A game engine tick needs to:
+ * - adjust speed (acceleration?)
+ * - adjust position, taking direction, speed and collision into account.
+ *
+ * @return true if object is still alive, otherwise false
+ */
+ bool tick();
+
+ void draw(SDL_Renderer* rend);
+
+ std::string toString() const;
+};
+
+std::string to_string(ghost_t::personality_t id);
+std::string to_string(ghost_t::mode_t m);
+
+//
+// pacman_t
+//
+
+class pacman_t {
+ public:
+ enum class mode_t {
+ HOME,
+ NORMAL,
+ POWERED,
+ DEAD
+ };
+
+ private:
+ /** mode durations in ms */
+ enum class mode_duration_t : int {
+ HOMESTAY = 2000,
+ INPOWER = ghost_t::number(ghost_t::mode_duration_t::SCARED),
+ DEADANIM = 2000
+ };
+ static constexpr int number(const mode_duration_t item) noexcept {
+ return static_cast<int>(item);
+ }
+ const int ms_per_tex = 167;
+
+ const float fields_per_sec;
+
+ const bool auto_move;
+
+ mode_t mode;
+ int mode_ms_left;
+ int lives;
+ direction_t dir_, last_dir;
+ int frame_count;
+ int steps_left;
+ uint64_t score;
+
+ animtex_t atex_left;
+ animtex_t atex_right;
+ animtex_t atex_up;
+ animtex_t atex_down;
+ animtex_t atex_dead;
+ animtex_t atex_home;
+ animtex_t * atex;
+
+ acoord_t pos;
+
+ uint64_t perf_fields_walked_t0 =0;
+
+ animtex_t& get_tex();
+
+ public:
+ pacman_t(SDL_Renderer* rend, const float fields_per_sec_=8, bool auto_move_=true);
+
+ ~pacman_t() {
+ destroy();
+ }
+
+ void destroy();
+
+ void set_mode(const mode_t m);
+
+ /**
+ * Set direction
+ */
+ void set_dir(direction_t dir);
+
+ direction_t get_dir() const { return dir_; }
+
+ const acoord_t& get_pos() const { return pos; }
+
+ uint64_t get_score() const { return score; }
+
+ /**
+ * A game engine tick needs to:
+ * - adjust speed (acceleration?)
+ * - adjust position, taking direction, speed and collision into account.
+ *
+ * @return true if object is still alive, otherwise false
+ */
+ bool tick();
+
+ void draw(SDL_Renderer* rend);
+
+ std::string toString() const;
+};
+
+std::string to_string(pacman_t::mode_t m);
+
+#endif /* PACMAN_GAME_HPP_ */
diff --git a/include/pacman/globals.hpp b/include/pacman/globals.hpp
new file mode 100644
index 0000000..faf0fc5
--- /dev/null
+++ b/include/pacman/globals.hpp
@@ -0,0 +1,67 @@
+/*
+ * Author: Sven Gothel <[email protected]> and Svenson Han Gothel
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * 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.
+ */
+#ifndef PACMAN_GLOBALS_HPP_
+#define PACMAN_GLOBALS_HPP_
+
+#include <pacman/maze.hpp>
+#include <pacman/graphics.hpp>
+#include <pacman/game.hpp>
+
+#include <cmath>
+#include <memory>
+
+//
+// globals
+//
+
+extern int win_pixel_width;
+extern int win_pixel_scale;
+
+extern int get_frames_per_sec();
+inline int get_ms_per_frame() { return (int)std::round(1000.0 / (float)get_frames_per_sec()); }
+
+extern std::unique_ptr<maze_t> pacman_maze;
+
+extern std::shared_ptr<global_tex_t> global_tex;
+
+typedef std::shared_ptr<ghost_t> ghost_ref;
+extern std::vector<ghost_ref> ghosts;
+
+typedef std::shared_ptr<pacman_t> pacman_ref;
+extern pacman_ref pacman;
+
+/**
+ * By default the original pacman behavior is being implemented:
+ * - weighted (round) tile position for collision tests
+ * - pinky's up-target not 4 ahead, but 4 ahead and 4 to the left
+ * - ...
+ *
+ * If false, a more accurate implementation, the pacman bugfix, is used:
+ * - pixel accurate tile position for collision tests
+ * - pinky's up-traget to be 4 ahead as intended
+ * - ...
+ */
+extern bool use_original_pacman_behavior();
+
+#endif /* PACMAN_GLOBALS_HPP_ */
diff --git a/include/pacman/graphics.hpp b/include/pacman/graphics.hpp
new file mode 100644
index 0000000..a6b34b4
--- /dev/null
+++ b/include/pacman/graphics.hpp
@@ -0,0 +1,214 @@
+/*
+ * Author: Sven Gothel <[email protected]> and Svenson Han Gothel
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * 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.
+ */
+#ifndef PACMAN_GRAPHICS_HPP_
+#define PACMAN_GRAPHICS_HPP_
+
+#include <atomic>
+#include <memory>
+#include <vector>
+#include <string>
+#include <inttypes.h>
+
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_image.h>
+#include <SDL2/SDL_timer.h>
+#include <SDL2/SDL_ttf.h>
+
+class texture_t {
+ private:
+ static std::atomic<int> counter;
+ int id;
+ SDL_Texture* tex;
+ int x, y, width, height;
+ bool owner;
+
+ public:
+ /**
+ * Empty texture
+ */
+ texture_t()
+ : id(counter++), tex(nullptr), x(0), y(0), width(0), height(0), owner(false) {}
+
+ texture_t(SDL_Renderer* rend, const std::string& fname);
+
+ texture_t(SDL_Renderer* rend, SDL_Surface* surface);
+
+ texture_t(SDL_Texture* t, int x_, int y_, int w_, int h_, bool owner=true)
+ : id(counter++), tex(t), x(x_), y(y_), width(w_), height(h_), owner(owner) {}
+
+ texture_t(SDL_Texture* t, int w_, int h_, bool owner=true)
+ : id(counter++), tex(t), x(0), y(0), width(w_), height(h_), owner(owner) {}
+
+ texture_t(const texture_t&) = delete;
+ void operator=(const texture_t&) = delete;
+
+ ~texture_t() {
+ destroy();
+ }
+
+ void destroy();
+
+ bool is_owner() const { return owner; }
+ void disown() { owner = false; }
+
+ int get_id() const { return id; }
+ int get_x() const { return x; }
+ int get_y() const { return y; }
+ int get_width() const { return width; }
+ int get_height() const { return height; }
+ SDL_Texture* get_sdltex() { return tex; }
+
+ void draw_scaled_dimpos(SDL_Renderer* rend, const int x_pos, const int y_pos);
+ void draw_scaled_dim(SDL_Renderer* rend, const int x_pos, const int y_pos);
+ void draw(SDL_Renderer* rend, const int x_pos, const int y_pos, const bool maze_offset);
+
+ void draw(SDL_Renderer* rend, const float x_pos, const float y_pos, const bool maze_offset);
+
+ std::string toString() const;
+};
+
+struct tex_sub_coord_t {
+ int x;
+ int y;
+};
+
+/**
+ * Add sub-textures to the storage list of texture_t from file,
+ * where the last one is the sole owner of the common SDL_Texture instance.
+ * @param storage
+ * @param rend
+ * @param filename
+ * @param w
+ * @param h
+ * @param x_off
+ * @return number of added sub-textures, last one is owner of the SDL_Texture instance
+ */
+int add_sub_textures(std::vector<std::shared_ptr<texture_t>>& storage, SDL_Renderer* rend,
+ const std::string& filename, int w, int h, int x_off);
+
+/**
+ * Add sub-textures to the storage list of texture_t from given global texture owner.
+ *
+ * None of the sub-textures is the owner of the common SDL_Texture instance.
+ * @param storage
+ * @param rend
+ * @param global_texture
+ * @param x_off
+ * @param y_off
+ * @param w
+ * @param h
+ * @param tex_positions
+ * @return number of added sub-textures
+ */
+int add_sub_textures(std::vector<std::shared_ptr<texture_t>>& storage, SDL_Renderer* rend,
+ const std::shared_ptr<texture_t>& global_texture, int x_off, int y_off, int w, int h,
+ const std::vector<tex_sub_coord_t>& tex_positions);
+
+class animtex_t {
+ private:
+ std::string name;
+ std::vector<std::shared_ptr<texture_t>> textures;
+
+ int ms_per_atex;
+ int atex_ms_left;
+ size_t animation_index;
+ bool paused;
+
+ public:
+ animtex_t(std::string name_, int ms_per_atex_, const std::vector<std::shared_ptr<texture_t>>& textures_);
+
+ animtex_t(std::string name_, SDL_Renderer* rend, int ms_per_atex_, const std::vector<const char*>& filenames);
+
+ animtex_t(std::string name_, SDL_Renderer* rend, int ms_per_atex_, const std::string& filename, int w, int h, int x_off);
+
+ animtex_t(std::string name_, SDL_Renderer* rend, int ms_per_atex_, const std::shared_ptr<texture_t>& global_texture,
+ int x_off, int y_off, int w, int h, const std::vector<tex_sub_coord_t>& tex_positions);
+
+ ~animtex_t() {
+ destroy();
+ }
+
+ void destroy();
+
+ std::shared_ptr<texture_t> get_tex(const size_t idx) { return idx < textures.size() ? textures[idx] : nullptr; }
+ std::shared_ptr<const texture_t> get_tex(const size_t idx) const { return idx < textures.size() ? textures[idx] : nullptr; }
+
+ std::shared_ptr<texture_t> get_tex() { return animation_index < textures.size() ? textures[animation_index] : nullptr; }
+ std::shared_ptr<const texture_t> get_tex() const { return animation_index < textures.size() ? textures[animation_index] : nullptr; }
+
+ int get_width() { std::shared_ptr<texture_t> tex = get_tex(); return nullptr!=tex ? tex->get_width() : 0; }
+ int get_height() { std::shared_ptr<texture_t> tex = get_tex(); return nullptr!=tex ? tex->get_height() : 0; }
+
+ void reset();
+ void pause(bool enable);
+ void tick();
+
+ void draw(SDL_Renderer* rend, const float x, const float y, const bool maze_offset) {
+ std::shared_ptr<texture_t> tex = get_tex();
+ if( nullptr != tex ) {
+ tex->draw(rend, x, y, maze_offset);
+ }
+ }
+
+ std::string toString() const;
+};
+
+/**
+ * Storage for a rendered text allowing caching for performance.
+ */
+struct text_texture_t {
+ std::string text;
+ texture_t texture;
+ bool scaled_pos;
+ int x_pos;
+ int y_pos;
+
+ text_texture_t(const std::string text_, SDL_Renderer* rend, SDL_Surface* surface, bool scaled_pos_, int x_, int y_)
+ : text(text_), texture(rend, surface), scaled_pos(scaled_pos_), x_pos(x_), y_pos(y_) {}
+
+ void redraw(SDL_Renderer* rend) {
+ if( scaled_pos ) {
+ texture.draw_scaled_dimpos(rend, x_pos, y_pos);
+ } else {
+ texture.draw_scaled_dim(rend, x_pos, y_pos);
+ }
+ }
+};
+
+/**
+ *
+ * @param rend
+ * @param font
+ * @param text
+ * @param x
+ * @param y
+ * @param r
+ * @param g
+ * @param b
+ */
+std::shared_ptr<text_texture_t> draw_text(SDL_Renderer* rend, TTF_Font* font, const std::string& text, int x, int y, uint8_t r, uint8_t g, uint8_t b);
+
+std::shared_ptr<text_texture_t> draw_text_scaled(SDL_Renderer* rend, TTF_Font* font, const std::string& text, uint8_t r, uint8_t g, uint8_t b, std::function<void(const texture_t& texture, int &x, int&y)> scaled_coord);
+
+#endif /* PACMAN_GRAPHICS_HPP_ */
diff --git a/include/pacman/maze.hpp b/include/pacman/maze.hpp
new file mode 100644
index 0000000..5ac7691
--- /dev/null
+++ b/include/pacman/maze.hpp
@@ -0,0 +1,336 @@
+/*
+ * Author: Sven Gothel <[email protected]> and Svenson Han Gothel
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * 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.
+ */
+#ifndef PACMAN_MAZE_HPP_
+#define PACMAN_MAZE_HPP_
+
+#include <pacman/utils.hpp>
+
+#include <string>
+#include <vector>
+#include <functional>
+#include <cmath>
+
+//
+// misc
+//
+inline constexpr int round_to_int(const float f) {
+ return (int)std::round(f);
+}
+inline constexpr int floor_to_int(const float f) {
+ return (int)std::floor(f);
+}
+inline constexpr int ceil_to_int(const float f) {
+ return (int)std::ceil(f);
+}
+
+//
+// direction_t
+//
+enum class direction_t : int {
+ UP = 0,
+ LEFT = 1,
+ DOWN = 2,
+ RIGHT = 3
+};
+constexpr int number(const direction_t d) noexcept {
+ return static_cast<int>(d);
+}
+std::string to_string(direction_t dir);
+direction_t inverse(direction_t dir);
+direction_t rot_left(direction_t dir);
+direction_t rot_right(direction_t dir);
+
+//
+// tile_t
+//
+enum class tile_t : int {
+ EMPTY = 0,
+ WALL = 1,
+ GATE = 2,
+ PELLET = 3,
+ PELLET_POWER = 4,
+ CHERRY = 5,
+ STRAWBERRY = 6,
+ ORANGE = 7,
+ APPLE = 8,
+ MELON = 9,
+ GALAXIAN = 10,
+ BELL = 11,
+ KEY = 12
+};
+constexpr int number(const tile_t item) noexcept {
+ return static_cast<int>(item);
+}
+std::string to_string(tile_t tile);
+
+//
+// box_t
+//
+class box_t {
+ private:
+ int x_pos;
+ int y_pos;
+ int width;
+ int height;
+
+ public:
+ box_t(const int x, const int y, const int w, const int h)
+ : x_pos(x), y_pos(y), width(w), height(h) {}
+
+ void set_dim(const int x, const int y, const int w, const int h) {
+ x_pos = x; y_pos = y; width = w; height = h;
+ }
+ int get_x() const { return x_pos; }
+ int get_y() const { return y_pos; }
+ int get_width() const { return width; }
+ int get_height() const { return height; }
+
+ std::string toString() const;
+};
+
+//
+// acoord_t
+//
+class maze_t; // fwd
+
+class acoord_t {
+ public:
+ typedef std::function<bool(direction_t d, int x_pos, int y_pos, tile_t)> collisiontest_t;
+
+ private:
+ static constexpr const bool DEBUG_BOUNDS = false;
+
+ int x_pos_i, y_pos_i;
+ float x_pos_f, y_pos_f;
+ direction_t last_dir;
+ bool last_collided;
+ int fields_walked_i;
+ float fields_walked_f;
+
+ bool step_impl(const maze_t& maze, direction_t dir, const bool test_only, const float fields_per_frame, collisiontest_t ct);
+
+ public:
+ acoord_t(const int x, const int y);
+
+ void reset_stats();
+
+ void set_pos(const int x, const int y);
+
+ int get_x_i() const { return x_pos_i; }
+ int get_y_i() const { return y_pos_i; }
+ int get_fields_walked_i() const { return fields_walked_i; }
+ float get_x_f() const { return x_pos_f; }
+ float get_y_f() const { return y_pos_f; }
+ float get_fields_walked_f() const { return fields_walked_f; }
+
+ /**
+ * Almost pixel accurate collision test.
+ *
+ * Note: This is not used in orig pacman game,
+ * since it uses tile weighted (rounded) tile position test only.
+ */
+ bool intersects_f(const acoord_t& other) const;
+
+ /**
+ * Weighted tile (rounded) test, i.e. simply comparing the tile position.
+ *
+ * This is used in orig pacman game.
+ *
+ * The weighted tile position is determined in step(..) implementation.
+ */
+ bool intersects_i(const acoord_t& other) const;
+
+ /**
+ * Intersection test using either the pixel accurate float method
+ * or the original pacman game weighted int method
+ * depending on use_original_pacman_behavior().
+ */
+ bool intersects(const acoord_t& other) const;
+
+ /**
+ * Pixel accurate position test for intersection.
+ */
+ bool intersects_f(const box_t& other) const;
+
+ float distance(const float x, const float y) const;
+
+ float distance(const acoord_t& other) const {
+ return distance(other.x_pos_f, other.y_pos_f);
+ }
+
+ float sq_distance(const float x, const float y) const;
+
+ float sq_distance(const acoord_t& other) const {
+ return sq_distance(other.x_pos_f, other.y_pos_f);
+ }
+
+ void incr_fwd(const maze_t& maze, const direction_t dir, const int tile_count);
+ void incr_fwd(const maze_t& maze, const int tile_count) {
+ incr_fwd(maze, last_dir, tile_count);
+ }
+ void incr_left(const maze_t& maze, const int tile_count) {
+ incr_fwd(maze, rot_left(last_dir), tile_count);
+ }
+ void incr_right(const maze_t& maze, const int tile_count) {
+ incr_fwd(maze, rot_right(last_dir), tile_count);
+ }
+
+ void step(const maze_t& maze, direction_t dir, const float fields_per_sec, const int frames_per_sec) {
+ step_impl(maze, dir, false, fields_per_sec / frames_per_sec, nullptr);
+ }
+
+ /**
+ *
+ * @param maze
+ * @param dir
+ * @param fields_per_sec
+ * @param frames_per_sec
+ * @param ct
+ * @return true if successful, otherwise false for collision
+ */
+ bool step(const maze_t& maze, direction_t dir, const float fields_per_sec, const int frames_per_sec, collisiontest_t ct) {
+ return step_impl(maze, dir, false, fields_per_sec / frames_per_sec, ct);
+ }
+
+ bool test(const maze_t& maze, direction_t dir, collisiontest_t ct) {
+ return step_impl(maze, dir, true, 0.50, ct);
+ }
+
+ bool is_transitioning(const float fields_per_sec, const int frames_per_sec);
+
+ /**
+ * Returns whether the last step has collided according to the given collistiontest_t or not.
+ */
+ bool has_collided() const { return last_collided; }
+
+ direction_t get_last_dir() const { return last_dir; }
+
+ std::string toString() const;
+ std::string toShortString() const;
+};
+
+//
+// maze_t
+//
+
+class maze_t {
+ public:
+ class field_t {
+ private:
+ int width, height;
+ std::vector<tile_t> tiles;
+ int count[13];
+
+ public:
+ field_t();
+
+ void set_dim(const int w, const int h) { width=w; height=h; }
+ void add_tile(const tile_t tile);
+
+ void clear();
+ bool validate_size() const { return tiles.size() == (size_t)width * (size_t)height; }
+
+ int get_width() const { return width; }
+ int get_height() const { return height; }
+
+ int get_count(const tile_t tile) const { return count[number(tile)]; }
+
+ tile_t get_tile(const int x, const int y) const;
+ tile_t get_tile_nc(const int x, const int y) const { return tiles[y*width+x]; }
+ void set_tile(const int x, const int y, tile_t tile);
+
+ std::string toString() const;
+ };
+ private:
+ static constexpr const bool DEBUG = false;
+ std::string filename;
+ acoord_t top_left_pos;
+ acoord_t bottom_left_pos;
+ acoord_t bottom_right_pos;
+ acoord_t top_right_pos;
+ acoord_t pacman_start_pos;
+ box_t ghost_home;
+ acoord_t ghost_home_pos;
+ acoord_t ghost_start_pos;
+ int ppt_x, ppt_y;
+ std::string texture_file;
+ field_t active;
+ field_t original;
+
+ bool digest_position_line(const std::string& name, acoord_t& dest, const std::string& line);
+ bool digest_box_line(const std::string& name, box_t& dest, const std::string& line);
+
+ public:
+ maze_t(const std::string& fname);
+
+ bool is_ok() const { return active.get_width() > 0 && active.get_height() > 0; };
+
+ int get_width() const { return active.get_width(); }
+ int get_height() const { return active.get_height(); }
+ const acoord_t& get_top_left_corner() const { return top_left_pos; }
+ const acoord_t& get_bottom_left_corner() const { return bottom_left_pos; }
+ const acoord_t& get_bottom_right_corner() const { return bottom_right_pos; }
+ const acoord_t& get_top_right_corner() const { return top_right_pos; }
+ const acoord_t& get_pacman_start_pos() const { return pacman_start_pos; }
+ const box_t& get_ghost_home_box() const { return ghost_home; }
+ const acoord_t& get_ghost_home_pos() const { return ghost_home_pos; }
+ const acoord_t& get_ghost_start_pos() const { return ghost_start_pos; }
+
+ int get_ppt_x() const { return ppt_x; }
+ int get_ppt_y() const { return ppt_y; }
+ int x_to_pixel(const int x, const int win_scale, const bool maze_offset) const {
+ return x * ppt_x * win_scale - ( maze_offset ? ppt_x/4 * win_scale : 0 );
+ }
+ int y_to_pixel(const int y, const int win_scale, const bool maze_offset) const {
+ return y * ppt_y * win_scale - ( maze_offset ? ppt_y/4 * win_scale : 0 );
+ }
+ int x_to_pixel(const float x, const int win_scale, const bool maze_offset) const {
+ return round_to_int(x * ppt_x * win_scale) - ( maze_offset ? ( ppt_x/4 * win_scale ) : 0 );
+ }
+ int y_to_pixel(const float y, const int win_scale, const bool maze_offset) const {
+ return round_to_int(y * ppt_y * win_scale) - ( maze_offset ? ( ppt_y/4 * win_scale ) : 0 );
+ }
+ std::string get_texture_file() const { return texture_file; }
+ int get_pixel_width() const { return get_width() * ppt_x; }
+ int get_pixel_height() const { return get_height() * ppt_x; }
+
+ int clip_pos_x(const int x) const {
+ return std::max(0, std::min(get_width()-1, x));
+ }
+ int clip_pos_y(const int y) const {
+ return std::max(0, std::min(get_height()-1, y));
+ }
+
+ int get_count(const tile_t tile) const { return active.get_count(tile); }
+ tile_t get_tile(const int x, const int y) const { return active.get_tile(x, y); }
+ void set_tile(const int x, const int y, tile_t tile) { active.set_tile(x, y, tile); }
+
+ void draw(std::function<void(const int x_pos, const int y_pos, tile_t tile)> draw_pixel);
+
+ void reset();
+
+ std::string toString() const;
+};
+
+#endif /* PACMAN_MAZE_HPP_ */
diff --git a/include/pacman/utils.hpp b/include/pacman/utils.hpp
new file mode 100644
index 0000000..c0fcf5b
--- /dev/null
+++ b/include/pacman/utils.hpp
@@ -0,0 +1,46 @@
+/*
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * 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.
+ */
+#ifndef PACMAN_UTILS_HPP_
+#define PACMAN_UTILS_HPP_
+
+#include <cstdint>
+#include <cstdarg>
+
+/**
+ * See <http://man7.org/linux/man-pages/man2/clock_gettime.2.html>
+ * <p>
+ * Regarding avoiding kernel via VDSO,
+ * see <http://man7.org/linux/man-pages/man7/vdso.7.html>,
+ * clock_gettime seems to be well supported at least on kernel >= 4.4.
+ * Only bfin and sh are missing, while ia64 seems to be complicated.
+ */
+uint64_t getCurrentMilliseconds() noexcept;
+
+uint64_t getElapsedMillisecond() noexcept;
+
+float get_fps(const uint64_t t0, const uint64_t t1, const float event_count);
+
+void log_print(const char * format, ...) noexcept;
+
+#endif /* PACMAN_UTILS_HPP_ */
diff --git a/media/edit.sh b/media/edit.sh
new file mode 100644
index 0000000..9b4939a
--- /dev/null
+++ b/media/edit.sh
@@ -0,0 +1,21 @@
+#! /bin/bash
+
+# merge
+convert pacman-l2.13x13.png pacman-r2.13x13.png pacman-u2.13x13.png pacman-d2.13x13.png +append pacman-l2r2u2d2.13x13.png
+
+# cut out single images to remove gap
+fname=ghost-blinky4.14p2x14.png; for i in 0 1 2 3 ; do let x=${i}*14+${i}*2; convert $fname -crop 14x14+${x}+0 `basename $fname .png`-${i}.png ; done
+fname=ghost-clyde4.14p2x14.png; for i in 0 1 2 3 ; do let x=${i}*14+${i}*2; convert $fname -crop 14x14+${x}+0 `basename $fname .png`-${i}.png ; done
+fname=ghost-eyes4.14p2x14.png; for i in 0 1 2 3 ; do let x=${i}*14+${i}*2; convert $fname -crop 14x14+${x}+0 `basename $fname .png`-${i}.png ; done
+fname=ghost-inky4.14p2x14.png; for i in 0 1 2 3 ; do let x=${i}*14+${i}*2; convert $fname -crop 14x14+${x}+0 `basename $fname .png`-${i}.png ; done
+fname=ghost-pinky4.14p2x14.png; for i in 0 1 2 3 ; do let x=${i}*14+${i}*2; convert $fname -crop 14x14+${x}+0 `basename $fname .png`-${i}.png ; done
+
+# merge single images to gap-less
+fname=ghost-blinky4; fnames= ; for i in 0 1 2 3 ; do fnames="${fnames} ${fname}.14p2x14-${i}.png" ; done ; convert $fnames +append ${fname}.14x14.png
+fname=ghost-clyde4; fnames= ; for i in 0 1 2 3 ; do fnames="${fnames} ${fname}.14p2x14-${i}.png" ; done ; convert $fnames +append ${fname}.14x14.png
+fname=ghost-eyes4; fnames= ; for i in 0 1 2 3 ; do fnames="${fnames} ${fname}.14p2x14-${i}.png" ; done ; convert $fnames +append ${fname}.14x14.png
+fname=ghost-inky4; fnames= ; for i in 0 1 2 3 ; do fnames="${fnames} ${fname}.14p2x14-${i}.png" ; done ; convert $fnames +append ${fname}.14x14.png
+fname=ghost-pinky4; fnames= ; for i in 0 1 2 3 ; do fnames="${fnames} ${fname}.14p2x14-${i}.png" ; done ; convert $fnames +append ${fname}.14x14.png
+
+# merge them in one file
+convert ghost-blinky4.14x14.png ghost-clyde4.14x14.png ghost-inky4.14x14.png ghost-pinky4.14x14.png ghost-eyes4.14x14.png -append ghosts-bcipe.5x14x14.png
diff --git a/media/playfield_pacman.0.txt b/media/playfield_pacman.0.txt
new file mode 100644
index 0000000..c7bfe6b
--- /dev/null
+++ b/media/playfield_pacman.0.txt
@@ -0,0 +1,36 @@
+____________________________
+____________________________
+____________________________
+||||||||||||||||||||||||||||
+|............||............|
+|.||||.|||||.||.|||||.||||.|
+|*|__|.|___|.||.|___|.|__|*|
+|.||||.|||||.||.|||||.||||.|
+|..........................|
+|.||||.||.||||||||.||.||||.|
+|.||||.||.||||||||.||.||||.|
+|......||....||....||......|
+||||||.|||||_||_|||||.||||||
+_____|.|||||_||_|||||.|_____
+_____|.||__________||.|_____
+_____|.||_|||--|||_||.|_____
+||||||.||_|______|_||.||||||
+______.___|______|___.______
+||||||.||_|______|_||.||||||
+_____|.||_||||||||_||.|_____
+_____|.||__________||.|_____
+_____|.||_||||||||_||.|_____
+||||||.||_||||||||_||.||||||
+|............||............|
+|.||||.|||||.||.|||||.||||.|
+|.||||.|||||.||.|||||.||||.|
+|*..||.......__.......||..*|
+|||.||.||.||||||||.||.||.|||
+|||.||.||.||||||||.||.||.|||
+|......||....||....||......|
+|.||||||||||.||.||||||||||.|
+|.||||||||||.||.||||||||||.|
+|..........................|
+||||||||||||||||||||||||||||
+____________________________
+____________________________
diff --git a/media/playfield_pacman.png b/media/playfield_pacman.png
new file mode 100644
index 0000000..471af7a
--- /dev/null
+++ b/media/playfield_pacman.png
Binary files differ
diff --git a/media/playfield_pacman.txt b/media/playfield_pacman.txt
new file mode 100644
index 0000000..1fc461f
--- /dev/null
+++ b/media/playfield_pacman.txt
@@ -0,0 +1,46 @@
+28 36 224 288 // dimension tile and visual: [net field dim: 28 x 31] x 8 -> [net visual dim: 224 x 248]
+ 1 4 // top-left corner
+ 1 32 // bottom-left corner
+26 32 // bottom-right corner
+26 4 // top-right corner
+14 26 // pacman start position
+10 15 8 5 // ghost home box incl. walls and gate [x, y, widht, height]
+13 17 // ghost home center position
+14 14 // ghost start position
+playfield_pacman.png
+____________________________
+____________________________
+____________________________
+||||||||||||||||||||||||||||
+|............||............|
+|.||||.|||||.||.|||||.||||.|
+|*|__|.|___|.||.|___|.|__|*|
+|.||||.|||||.||.|||||.||||.|
+|..........................|
+|.||||.||.||||||||.||.||||.|
+|.||||.||.||||||||.||.||||.|
+|......||....||....||......|
+||||||.|||||_||_|||||.||||||
+_____|.|||||_||_|||||.|_____
+_____|.||__________||.|_____
+_____|.||_|||--|||_||.|_____
+||||||.||_|______|_||.||||||
+______.___|______|___.______
+||||||.||_|______|_||.||||||
+_____|.||_||||||||_||.|_____
+_____|.||__________||.|_____
+_____|.||_||||||||_||.|_____
+||||||.||_||||||||_||.||||||
+|............||............|
+|.||||.|||||.||.|||||.||||.|
+|.||||.|||||.||.|||||.||||.|
+|*..||.......__.......||..*|
+|||.||.||.||||||||.||.||.|||
+|||.||.||.||||||||.||.||.|||
+|......||....||....||......|
+|.||||||||||.||.||||||||||.|
+|.||||||||||.||.||||||||||.|
+|..........................|
+||||||||||||||||||||||||||||
+____________________________
+____________________________
diff --git a/media/tiles_all.png b/media/tiles_all.png
new file mode 100644
index 0000000..5308bb1
--- /dev/null
+++ b/media/tiles_all.png
Binary files differ
diff --git a/media/tiles_all.txt b/media/tiles_all.txt
new file mode 100644
index 0000000..74e051b
--- /dev/null
+++ b/media/tiles_all.txt
@@ -0,0 +1,8 @@
+- line 0: 12x14x14: 10 fruits + ghost_scared_blue + ghost_scared_pink
+- line 1: 12x14x14: pacman_dead_anim
+- line 2: 8x13x13: pacman_anim 2x left + 2x right + 2x up + 2x down
+- line 3: 4x14x14: ghost_anim 4x blinky
+- line 4: 4x14x14: ghost_anim 4x clyde
+- line 5: 4x14x14: ghost_anim 4x inky
+- line 6: 4x14x14: ghost_anim 4x pinky
+- line 7: 4x14x14: ghost_anim 4x eyes
diff --git a/src/game.cpp b/src/game.cpp
new file mode 100644
index 0000000..c7d86e0
--- /dev/null
+++ b/src/game.cpp
@@ -0,0 +1,528 @@
+/*
+ * Author: Sven Gothel <[email protected]> and Svenson Han Gothel
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * 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.
+ */
+#include <pacman/game.hpp>
+#include <pacman/globals.hpp>
+
+#include <limits>
+
+#include <cstdio>
+#include <time.h>
+
+static constexpr const bool DEBUG_GFX_BOUNDS = false;
+
+//
+// globals across modules 'globals.hpp'
+//
+
+int win_pixel_width = 0;
+int win_pixel_scale = 1;
+
+static int frames_per_sec=0;
+int get_frames_per_sec() { return frames_per_sec; }
+
+std::unique_ptr<maze_t> pacman_maze;
+std::shared_ptr<global_tex_t> global_tex;
+std::vector<ghost_ref> ghosts;
+pacman_ref pacman;
+
+static bool original_pacman_behavior = true;
+bool use_original_pacman_behavior() { return original_pacman_behavior; }
+
+//
+// score_t
+//
+
+score_t tile_to_score(const tile_t tile) {
+ switch(tile) {
+ case tile_t::PELLET: return score_t::PELLET;
+ case tile_t::PELLET_POWER: return score_t::PELLET_POWER;
+ case tile_t::CHERRY: return score_t::CHERRY;
+ case tile_t::STRAWBERRY: return score_t::STRAWBERRY;
+ case tile_t::ORANGE: return score_t::ORANGE;
+ case tile_t::APPLE: return score_t::APPLE;
+ case tile_t::MELON: return score_t::MELON;
+ case tile_t::GALAXIAN: return score_t::GALAXIAN;
+ case tile_t::BELL: return score_t::BELL;
+ case tile_t::KEY: return score_t::KEY;
+ default: return score_t::NONE;
+ }
+}
+
+//
+// global_tex_t
+//
+
+int global_tex_t::tile_to_texidx(const tile_t tile) const {
+ if( tile_t::PELLET <= tile && tile <= tile_t::KEY ) {
+ const int tile_i = static_cast<int>(tile);
+ const int pellet_i = static_cast<int>(tile_t::PELLET);
+ const int idx = tile_i - pellet_i;
+ if( 0 <= idx && (size_t)idx < textures.size() ) {
+ return idx;
+ }
+ }
+ return -1;
+}
+
+int global_tex_t::validate_texidx(const int idx) const {
+ if( 0 <= idx && (size_t)idx < textures.size() ) {
+ return idx;
+ }
+ return -1;
+}
+
+global_tex_t::global_tex_t(SDL_Renderer* rend)
+: all_images( std::make_shared<texture_t>(rend, "media/tiles_all.png") ),
+ atex_pellet_power( "PP", rend, 250, all_images, 0, 0, 14, 14, { { 1*14, 0 }, { -1, -1} })
+{
+ add_sub_textures(textures, rend, all_images, 0, 0, 14, 14, {
+ { 0*14, 0 }, { 1*14, 0 }, { 2*14, 0 }, { 3*14, 0 }, { 4*14, 0 }, { 5*14, 0 }, { 6*14, 0 },
+ { 7*14, 0 }, { 8*14, 0 }, { 9*14, 0 }, { 10*14, 0 }, { 11*14, 0 }, { 12*14, 0 }, { 13*14, 0 }, } );
+}
+
+void global_tex_t::destroy() {
+ for(size_t i=0; i<textures.size(); ++i) {
+ textures[i]->destroy();
+ }
+ textures.clear();
+ all_images->destroy();
+}
+
+std::shared_ptr<texture_t> global_tex_t::get_tex(const int idx) {
+ const int idx2 = validate_texidx(idx);
+ return 0 <= idx2 ? textures[idx2] : nullptr;
+}
+std::shared_ptr<const texture_t> global_tex_t::get_tex(const int idx) const {
+ const int idx2 = validate_texidx(idx);
+ return 0 <= idx2 ? textures[idx2] : nullptr;
+}
+
+std::shared_ptr<texture_t> global_tex_t::get_tex(const tile_t tile) {
+ const int idx = tile_to_texidx(tile);
+ return 0 <= idx ? textures[idx] : nullptr;
+}
+std::shared_ptr<const texture_t> global_tex_t::get_tex(const tile_t tile) const {
+ const int idx = tile_to_texidx(tile);
+ return 0 <= idx ? textures[idx] : nullptr;
+}
+
+void global_tex_t::draw_tile(const tile_t tile, SDL_Renderer* rend, const int x, const int y) {
+ if( tile_t::PELLET_POWER == tile ) {
+ atex_pellet_power.draw(rend, x, y, true /* maze_offset */);
+ } else {
+ std::shared_ptr<texture_t> tex = get_tex(tile);
+ if( nullptr != tex ) {
+ tex->draw(rend, x, y, true /* maze_offset */);
+ }
+ }
+}
+
+std::string global_tex_t::toString() const {
+ return "tiletex[count "+std::to_string(textures.size())+"]";
+}
+
+//
+// main
+//
+
+TTF_Font* font_ttf = nullptr;
+
+static void on_window_resized(SDL_Renderer* rend, const int win_width_l, const int win_height_l) {
+ int win_pixel_height=0;
+ SDL_GetRendererOutputSize(rend, &win_pixel_width, &win_pixel_height);
+
+ float sx = win_pixel_width / pacman_maze->get_pixel_width();
+ float sy = win_pixel_height / pacman_maze->get_pixel_height();
+ win_pixel_scale = static_cast<int>( std::round( std::fmin<float>(sx, sy) ) );
+
+ if( nullptr != font_ttf ) {
+ TTF_CloseFont(font_ttf);
+ font_ttf = nullptr;
+ }
+ int font_height;
+ {
+ const std::string fontfilename = "fonts/freefont/FreeSansBold.ttf";
+ font_height = pacman_maze->get_ppt_y() * win_pixel_scale;
+ font_ttf = TTF_OpenFont(fontfilename.c_str(), font_height);
+ }
+ log_print("Window Resized: %d x %d pixel ( %d x %d logical ) @ %d hz\n",
+ win_pixel_width, win_pixel_height, win_width_l, win_height_l, get_frames_per_sec());
+ log_print("Pixel scale: %f x %f -> %d, font[ok %d, height %d]\n", sx, sy, win_pixel_scale, nullptr!=font_ttf, font_height);
+}
+
+static std::string get_usage(const std::string& exename) {
+ return "Usage: "+exename+" [-step] [-show_fps] [-no_vsync] [-fps <int>] [-speed <int>] [-wwidth <int>] [-wheight <int>] [-show_ghost_moves] [-show_targets] [-bugfix]";
+}
+
+int main(int argc, char *argv[])
+{
+ bool auto_move = true;
+ bool show_fps = false;
+ bool enable_vsync = true;
+ int forced_fps = -1;
+ float fields_per_sec=8;
+ int win_width = 640, win_height = 720;
+ bool show_ghost_moves = false;
+ bool show_targets = false;
+ {
+ for(int i=1; i<argc; ++i) {
+ if( 0 == strcmp("-step", argv[i]) ) {
+ auto_move = false;
+ } else if( 0 == strcmp("-show_fps", argv[i]) ) {
+ show_fps = true;
+ } else if( 0 == strcmp("-no_vsync", argv[i]) ) {
+ enable_vsync = false;
+ } else if( 0 == strcmp("-fps", argv[i]) && i+1<argc) {
+ forced_fps = atoi(argv[i+1]);
+ enable_vsync = false;
+ ++i;
+ } else if( 0 == strcmp("-speed", argv[i]) && i+1<argc) {
+ fields_per_sec = atof(argv[i+1]);
+ ++i;
+ } else if( 0 == strcmp("-wwidth", argv[i]) && i+1<argc) {
+ win_width = atoi(argv[i+1]);
+ ++i;
+ } else if( 0 == strcmp("-wheight", argv[i]) && i+1<argc) {
+ win_height = atoi(argv[i+1]);
+ ++i;
+ } else if( 0 == strcmp("-show_ghost_moves", argv[i]) ) {
+ show_ghost_moves = true;
+ } else if( 0 == strcmp("-show_targets", argv[i]) ) {
+ show_targets = true;
+ } else if( 0 == strcmp("-bugfix", argv[i]) ) {
+ original_pacman_behavior = false;
+ }
+ }
+ log_print("%s\n", get_usage(argv[0]).c_str());
+ log_print("- auto_move %d\n", auto_move);
+ log_print("- show_fps %d\n", show_fps);
+ log_print("- enable_vsync %d\n", enable_vsync);
+ log_print("- forced_fps %d\n", forced_fps);
+ log_print("- fields_per_sec %5.2f\n", fields_per_sec);
+ log_print("- win size %d x %d\n", win_width, win_height);
+ log_print("- show_ghost_moves %d\n", show_ghost_moves);
+ log_print("- show_targets %d\n", show_targets);
+ log_print("- use_bugfix_pacman %d\n", !use_original_pacman_behavior());
+ }
+
+ pacman_maze = std::make_unique<maze_t>("media/playfield_pacman.txt");
+
+ if( !pacman_maze->is_ok() ) {
+ log_print("Maze: Error: %s\n", pacman_maze->toString().c_str());
+ return -1;
+ }
+ {
+ log_print("--- 8< ---\n");
+ const int maze_width = pacman_maze->get_width();
+ pacman_maze->draw( [&maze_width](int x, int y, tile_t tile) {
+ fprintf(stderr, "%s", to_string(tile).c_str());
+ if( x == maze_width-1 ) {
+ fprintf(stderr, "\n");
+ }
+ (void)y;
+ });
+ log_print("--- >8 ---\n");
+ log_print("Maze: %s\n", pacman_maze->toString().c_str());
+ }
+
+ if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
+ log_print("error initializing SDL: %s\n", SDL_GetError());
+ }
+
+ if ( ( IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG ) != IMG_INIT_PNG ) {
+ log_print("error initializing SDL_image: %s\n", SDL_GetError());
+ }
+
+ if( 0 != TTF_Init() ) {
+ log_print("Font: Error initializing.\n");
+ }
+
+ if( enable_vsync ) {
+ SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");
+ }
+
+ SDL_Window* win = SDL_CreateWindow("Pacman",
+ SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED,
+ win_width,
+ win_height,
+ SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE);
+
+ const Uint32 render_flags = enable_vsync ? SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC :
+ SDL_RENDERER_ACCELERATED;
+ Uint32 fullscreen_flags = 0;
+ bool uses_vsync = false;
+
+ SDL_Renderer* rend = SDL_CreateRenderer(win, -1, render_flags);
+ {
+ SDL_RendererInfo info;
+ SDL_GetRendererInfo(rend, &info);
+ bool _uses_vsync = 0 != ( info.flags & SDL_RENDERER_PRESENTVSYNC );
+ uses_vsync = _uses_vsync | enable_vsync; // FIXME: Assume yes if enforced with enable_vsync, since info.flags is not reliable
+ log_print("renderer: name: %s\n", info.name);
+ log_print("renderer: accel %d\n", 0 != ( info.flags & SDL_RENDERER_ACCELERATED ));
+ log_print("renderer: soft %d\n", 0 != ( info.flags & SDL_RENDERER_SOFTWARE ));
+ log_print("renderer: vsync %d -> %d\n", _uses_vsync, uses_vsync);
+ }
+
+ std::unique_ptr<texture_t> pacman_maze_tex = std::make_unique<texture_t>(rend, "media/"+pacman_maze->get_texture_file());
+ {
+ int win_pixel_width_=0;
+ int win_pixel_height_=0;
+ SDL_GetRendererOutputSize(rend, &win_pixel_width_, &win_pixel_height_);
+ {
+ SDL_DisplayMode mode;
+ {
+ const int num_displays = SDL_GetNumVideoDisplays();
+ for(int i=0; i<num_displays; ++i) {
+ bzero(&mode, sizeof(mode));
+ SDL_GetCurrentDisplayMode(i, &mode);
+ log_print("Display %d: %d x %d @ %d Hz\n", i, mode.w, mode.h, mode.refresh_rate);
+ }
+ }
+ const int win_display_idx = SDL_GetWindowDisplayIndex(win);
+ bzero(&mode, sizeof(mode));
+ SDL_GetCurrentDisplayMode(win_display_idx, &mode); // SDL_GetWindowDisplayMode(..) fails on some systems (wrong refresh_rate and logical size
+ log_print("WindowDisplayMode: %d x %d @ %d Hz @ display %d\n", mode.w, mode.h, mode.refresh_rate, win_display_idx);
+ if( 0 < forced_fps ) {
+ frames_per_sec = forced_fps;
+ } else {
+ frames_per_sec = mode.refresh_rate;
+ }
+ }
+ on_window_resized(rend, win_pixel_width_, win_pixel_height_);
+ }
+ SDL_SetWindowSize(win, pacman_maze->get_pixel_width()*win_pixel_scale,
+ pacman_maze->get_pixel_height()*win_pixel_scale);
+
+ global_tex = std::make_shared<global_tex_t>(rend);
+ pacman = std::make_shared<pacman_t>(rend, fields_per_sec, auto_move);
+ ghosts.push_back( std::make_shared<ghost_t>(ghost_t::personality_t::BLINKY, rend, fields_per_sec) );
+ ghosts.push_back( std::make_shared<ghost_t>(ghost_t::personality_t::PINKY, rend, fields_per_sec) );
+ // ghosts.push_back( std::make_shared<ghost_t>(ghost_t::personality_t::CLYDE, rend, fields_per_sec) );
+ // ghosts.push_back( std::make_shared<ghost_t>(ghost_t::personality_t::INKY, rend, fields_per_sec) );
+ for(ghost_ref g : ghosts) {
+ g->set_log_moves(show_ghost_moves);
+ }
+
+ bool close = false;
+ bool set_dir = false;
+ bool pause = true;
+ direction_t dir = pacman->get_dir();
+
+ const uint64_t fps_range_ms = 3000;
+ uint64_t t0 = getCurrentMilliseconds();
+ uint64_t frame_count = 0;
+ uint64_t t1 = t0;
+ uint64_t td_print_fps = t0;
+ uint64_t reset_fps_frame = fps_range_ms;
+
+ uint64_t last_score = pacman->get_score();
+ std::shared_ptr<text_texture_t> ttex_score = nullptr;
+ std::shared_ptr<text_texture_t> ttex_score_title = nullptr;
+
+ while (!close) {
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ case SDL_QUIT:
+ close = true;
+ break;
+
+ case SDL_KEYUP:
+ set_dir = false;
+ break;
+
+ case SDL_WINDOWEVENT:
+ switch (event.window.event) {
+ case SDL_WINDOWEVENT_SHOWN:
+ pause = false;
+ break;
+ case SDL_WINDOWEVENT_HIDDEN:
+ pause = true;
+ break;
+ case SDL_WINDOWEVENT_RESIZED:
+ on_window_resized(rend, event.window.data1, event.window.data2);
+ ttex_score_title = nullptr;
+ ttex_score = nullptr;
+ break;
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ // INFO_PRINT("Window SizeChanged: %d x %d\n", event.window.data1, event.window.data2);
+ break;
+ }
+ break;
+
+ case SDL_KEYDOWN:
+ // keyboard API for key pressed
+ switch (event.key.keysym.scancode) {
+ case SDL_SCANCODE_Q:
+ [[fallthrough]];
+ case SDL_SCANCODE_ESCAPE:
+ close = true;
+ break;
+ case SDL_SCANCODE_P:
+ pause = pause ? false : true;
+ break;
+ case SDL_SCANCODE_R:
+ pacman_maze->reset();
+ break;
+ case SDL_SCANCODE_W:
+ case SDL_SCANCODE_UP:
+ dir = direction_t::UP;
+ set_dir = true;
+ break;
+ case SDL_SCANCODE_A:
+ case SDL_SCANCODE_LEFT:
+ dir = direction_t::LEFT;
+ set_dir = true;
+ break;
+ case SDL_SCANCODE_S:
+ case SDL_SCANCODE_DOWN:
+ dir = direction_t::DOWN;
+ set_dir = true;
+ break;
+ case SDL_SCANCODE_D:
+ case SDL_SCANCODE_RIGHT:
+ dir = direction_t::RIGHT;
+ set_dir = true;
+ break;
+ case SDL_SCANCODE_F:
+ fullscreen_flags = 0 == fullscreen_flags ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0;
+ SDL_SetWindowFullscreen(win, fullscreen_flags);
+ break;
+ default:
+ // nop
+ break;
+ }
+ }
+ }
+ if( pause ) {
+ SDL_Delay( 1000 / 60 );
+ continue;
+ }
+ if( set_dir ) {
+ pacman->set_dir(dir);
+ }
+ global_tex->tick();
+ for(ghost_ref g : ghosts) {
+ g->tick();
+ }
+ pacman->tick();
+
+ SDL_RenderClear(rend);
+
+ pacman_maze_tex->draw(rend, 0, 0, false /* maze_offset */);
+ pacman_maze->draw( [&rend](int x, int y, tile_t tile) {
+ global_tex->draw_tile(tile, rend, x, y);
+ });
+ pacman->draw(rend);
+ for(ghost_ref ghost : ghosts) {
+ ghost->draw(rend);
+
+ if( show_targets ) {
+ uint8_t r, g, b, a;
+ SDL_GetRenderDrawColor(rend, &r, &g, &b, &a);
+ SDL_SetRenderDrawColor(rend, 150, 150, 150, 255);
+ const acoord_t& p1 = ghost->get_pos();
+ const acoord_t& p2 = ghost->get_target();
+ SDL_RenderDrawLine(rend,
+ pacman_maze->x_to_pixel( p1.get_x_i(), win_pixel_scale, false ),
+ pacman_maze->y_to_pixel( p1.get_y_i(), win_pixel_scale, false ),
+ pacman_maze->x_to_pixel( p2.get_x_i(), win_pixel_scale, false ),
+ pacman_maze->y_to_pixel( p2.get_y_i(), win_pixel_scale, false ) );
+ SDL_SetRenderDrawColor(rend, r, g, b, a);
+ }
+ }
+
+ if( nullptr != font_ttf ) {
+ if( nullptr == ttex_score_title ) {
+ const std::string highscore_s("HIGH SCORE");
+ ttex_score_title = draw_text_scaled(rend, font_ttf, highscore_s, 255, 255, 255, [&](const texture_t& tex, int &x, int&y) {
+ x = ( pacman_maze->get_pixel_width()*win_pixel_scale - tex.get_width() ) / 2;
+ y = pacman_maze->x_to_pixel(0, win_pixel_scale, false);
+ });
+ } else {
+ ttex_score_title->redraw(rend);
+ }
+ if( nullptr != ttex_score && last_score == pacman->get_score() ) {
+ ttex_score->redraw(rend);
+ } else {
+ std::string score_s = std::to_string( pacman->get_score() );
+ ttex_score = draw_text_scaled(rend, font_ttf, score_s, 255, 255, 255, [&](const texture_t& tex, int &x, int&y) {
+ x = ( pacman_maze->get_pixel_width()*win_pixel_scale - tex.get_width() ) / 2;
+ y = pacman_maze->x_to_pixel(1, win_pixel_scale, false);
+ });
+ last_score = pacman->get_score();
+ }
+ }
+
+ // swap double buffer incl. v-sync
+ SDL_RenderPresent(rend);
+ ++frame_count;
+ if( !uses_vsync ) {
+ const uint64_t ms_per_frame = (uint64_t)std::round(1000.0 / (float)get_frames_per_sec());
+ const uint64_t ms_last_frame = getCurrentMilliseconds() - t1;
+ if( ms_per_frame > ms_last_frame + 1 )
+ {
+ const uint64_t td = ms_per_frame - ms_last_frame;
+ #if 1
+ struct timespec ts { (long)(td/1000UL), (long)((td%1000UL)*1000000UL) };
+ nanosleep( &ts, NULL );
+ #else
+ SDL_Delay( td );
+ #endif
+ // INFO_PRINT("soft-sync exp %zd > has %zd, delay %zd (%lds, %ldns)\n", ms_per_frame, ms_last_frame, td, ts.tv_sec, ts.tv_nsec);
+ }
+ }
+ t1 = getCurrentMilliseconds();
+ if( show_fps && fps_range_ms <= t1 - td_print_fps ) {
+ const float fps = get_fps(t0, t1, frame_count);
+ std::string fps_str(64, '\0');
+ const int written = std::snprintf(&fps_str[0], fps_str.size(), "fps %6.2f", fps);
+ fps_str.resize(written);
+ // log_print("%s, td %" PRIu64 "ms, frames %" PRIu64 "\n", fps_str.c_str(), t1-t0, frame_count);
+ log_print("%s\n", fps_str.c_str());
+ td_print_fps = t1;
+ }
+ if( 0 == --reset_fps_frame ) {
+ t0 = t1;
+ frame_count = 0;
+ reset_fps_frame = fps_range_ms;
+ }
+ } // loop
+
+ pacman->destroy();
+ ghosts.clear();
+ pacman_maze_tex->destroy();
+
+ SDL_DestroyRenderer(rend);
+
+ SDL_DestroyWindow(win);
+
+ TTF_CloseFont(font_ttf);
+
+ SDL_Quit();
+
+ return 0;
+}
diff --git a/src/ghost.cpp b/src/ghost.cpp
new file mode 100644
index 0000000..d583990
--- /dev/null
+++ b/src/ghost.cpp
@@ -0,0 +1,424 @@
+/*
+ * Author: Sven Gothel <[email protected]> and Svenson Han Gothel
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * 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.
+ */
+#include <pacman/game.hpp>
+#include <pacman/globals.hpp>
+
+#include <limits>
+
+#include <cstdio>
+#include <time.h>
+
+static constexpr const bool DEBUG_GFX_BOUNDS = false;
+
+//
+// ghost_t
+//
+
+int ghost_t::id_to_yoff(ghost_t::personality_t id) {
+ switch( id ) {
+ case ghost_t::personality_t::BLINKY:
+ return 41 + 0*14;
+ case ghost_t::personality_t::CLYDE:
+ return 41 + 1*14;
+ case ghost_t::personality_t::INKY:
+ return 41 + 2*14;
+ case ghost_t::personality_t::PINKY:
+ return 41 + 3*14;
+ default:
+ return 0;
+ }
+}
+
+animtex_t& ghost_t::get_tex() {
+ switch( mode ) {
+ case mode_t::SCARED:
+ return atex_scared;
+
+ case mode_t::PHANTOM:
+ return atex_phantom;
+
+ default:
+ return atex_normal;
+ }
+}
+
+ghost_t::ghost_t(const personality_t id_, SDL_Renderer* rend, const float fields_per_sec_)
+: fields_per_sec( fields_per_sec_ ),
+ id( id_ ),
+ mode( mode_t::HOME ),
+ mode_ms_left ( number( mode_duration_t::HOMESTAY ) ),
+ dir_( direction_t::LEFT ),
+ last_dir( dir_ ),
+ frame_count ( 0 ),
+ atex_normal( "N", rend, ms_per_atex, global_tex->get_all_images(), 0, id_to_yoff(id), 14, 14, { { 0*14, 0 }, { 1*14, 0 }, { 2*14, 0 }, { 3*14, 0 } }),
+ atex_scared( "S", rend, ms_per_atex, global_tex->get_all_images(), 0, 0, 14, 14, { { 10*14, 0 } }),
+ atex_phantom( "P", rend, ms_per_atex, global_tex->get_all_images(), 0, 41 + 4*14, 14, 14, { { 0*14, 0 }, { 1*14, 0 }, { 2*14, 0 }, { 3*14, 0 } }),
+ atex( &get_tex() ),
+ pos( pacman_maze->get_ghost_home_pos() ),
+ target( pacman_maze->get_ghost_home_pos() )
+{
+ set_mode( mode_t::HOME );
+}
+
+
+void ghost_t::destroy() {
+ atex_normal.destroy();
+ atex_scared.destroy();
+ atex_phantom.destroy();
+}
+
+acoord_t ghost_t::get_personal_target() const {
+ switch( mode ) {
+ case mode_t::HOME:
+ if( ghost_t::personality_t::BLINKY == id ) {
+ return pacman->get_pos();
+ } else {
+ return pacman_maze->get_ghost_home_pos();
+ }
+
+ case mode_t::LEAVE_HOME:
+ return pacman_maze->get_ghost_start_pos();
+
+ case mode_t::CHASE:
+ switch( id ) {
+ case ghost_t::personality_t::BLINKY:
+ return pacman->get_pos();
+ case ghost_t::personality_t::CLYDE:
+ return pacman_maze->get_top_left_corner();
+ case ghost_t::personality_t::INKY:
+ return pacman_maze->get_bottom_left_corner();
+ case ghost_t::personality_t::PINKY: {
+ acoord_t r = pacman->get_pos();
+ if( use_original_pacman_behavior() ) {
+ r.incr_fwd(*pacman_maze, 4);
+ r.incr_left(*pacman_maze, 4);
+ } else {
+ r.incr_fwd(*pacman_maze, 4);
+ }
+ return r;
+ }
+ default:
+ return pacman->get_pos();
+ }
+
+ case mode_t::SCATTER:
+ // FIXME ???
+ switch( id ) {
+ case ghost_t::personality_t::BLINKY:
+ return pacman_maze->get_top_right_corner();
+ case ghost_t::personality_t::CLYDE:
+ return pacman_maze->get_top_left_corner();
+ case ghost_t::personality_t::INKY:
+ return pacman_maze->get_bottom_left_corner();
+ case ghost_t::personality_t::PINKY:
+ [[fallthrough]];
+ default:
+ return pacman_maze->get_bottom_right_corner();
+ }
+ case mode_t::PHANTOM:
+ return pacman_maze->get_ghost_home_pos();
+
+ case mode_t::SCARED:
+ [[fallthrough]];
+ // dummy, since an RNG is being used
+ default:
+ return pacman_maze->get_ghost_start_pos();
+ }
+}
+
+void ghost_t::set_mode(const mode_t m) {
+ const mode_t old_mode = mode;
+ switch( m ) {
+ case mode_t::HOME:
+ if( ghost_t::personality_t::BLINKY == id ) {
+ // positioned outside of the box and chasing right away
+ mode = mode_t::CHASE;
+ mode_ms_left = number( mode_duration_t::CHASING );
+ dir_ = direction_t::LEFT;
+ pos = pacman_maze->get_ghost_start_pos();
+ } else {
+ mode = m;
+ mode_ms_left = number( mode_duration_t::HOMESTAY );
+ pos = pacman_maze->get_ghost_home_pos();
+ }
+ break;
+ case mode_t::LEAVE_HOME:
+ mode = m;
+ mode_ms_left = number( mode_duration_t::HOMESTAY );
+ break;
+ case mode_t::CHASE:
+ mode = m;
+ mode_ms_left = number( mode_duration_t::CHASING );
+ if( mode_t::LEAVE_HOME != old_mode ) {
+ dir_ = direction_t::LEFT;
+ } else if( mode_t::SCARED != old_mode ) {
+ dir_ = inverse(dir_);
+ }
+ break;
+ case mode_t::SCATTER:
+ mode = m;
+ mode_ms_left = number( mode_duration_t::SCATTERING );
+ if( mode_t::HOME == old_mode || mode_t::LEAVE_HOME == old_mode ) {
+ // NOP
+ } else if( mode_t::SCARED != old_mode ) {
+ dir_ = inverse(dir_);
+ }
+ break;
+ case mode_t::SCARED:
+ if( mode_t::HOME == old_mode || mode_t::LEAVE_HOME == old_mode ) {
+ // NOP
+ } else {
+ mode = m;
+ mode_ms_left = number( mode_duration_t::SCARED );
+ dir_ = inverse(dir_);
+ }
+ break;
+ case mode_t::PHANTOM:
+ mode = m;
+ mode_ms_left = number( mode_duration_t::PHANTOM );
+ break;
+ default:
+ mode = m;
+ mode_ms_left = -1;
+ break;
+ }
+ target = get_personal_target();
+ log_print("%s set_mode: %s -> %s [%d ms], pos %s -> %s\n", to_string(id).c_str(), to_string(old_mode).c_str(), to_string(mode).c_str(), mode_ms_left,
+ pos.toShortString().c_str(), target.toShortString().c_str());
+}
+
+void ghost_t::set_next_dir() {
+ if( pos.is_transitioning(fields_per_sec, get_frames_per_sec()) ) {
+ return; // NOP
+ }
+ constexpr const int U = 0;
+ constexpr const int L = 1;
+ constexpr const int D = 2;
+ constexpr const int R = 3;
+
+ const direction_t cur_dir = dir_;
+ const direction_t inv_dir = inverse(cur_dir);
+ const direction_t left_dir = rot_left(cur_dir);
+ const direction_t right_dir = rot_right(cur_dir);
+ direction_t new_dir;
+ int choice = 0;
+ const float d_inf = pacman_maze->get_width() * pacman_maze->get_height() * 10; // std::numeric_limits<float>::max();
+
+ acoord_t up = pos; // 0
+ acoord_t left = pos; // 1
+ acoord_t down = pos; // 2
+ acoord_t right = pos; // 3
+ acoord_t::collisiontest_t collisiontest = [&](direction_t d, int x, int y, tile_t tile) -> bool {
+ (void)d; (void)x; (void)y;
+ return ( mode_t::LEAVE_HOME == mode || mode_t::PHANTOM == mode ) ?
+ tile_t::WALL == tile : ( tile_t::WALL == tile || tile_t::GATE == tile );
+ };
+
+ const bool dir_coll[4] = {
+ !up.step(*pacman_maze, direction_t::UP, fields_per_sec, get_frames_per_sec(), collisiontest),
+ !left.step(*pacman_maze, direction_t::LEFT, fields_per_sec, get_frames_per_sec(), collisiontest),
+ !down.step(*pacman_maze, direction_t::DOWN, fields_per_sec, get_frames_per_sec(), collisiontest),
+ !right.step(*pacman_maze, direction_t::RIGHT, fields_per_sec, get_frames_per_sec(), collisiontest) };
+
+ if( dir_coll[ ::number(left_dir) ] && dir_coll[ ::number(right_dir) ] ) {
+ // walls left and right
+ if( dir_coll[ ::number(cur_dir) ] ) {
+ // dead-end, can only return .. unusual (not in orig map)
+ new_dir = inv_dir;
+ choice = 10;
+ } else {
+ // straight ahead and walls left and right
+ new_dir = cur_dir;
+ choice = 20;
+ }
+ } else {
+ // find shortest path
+ float dir_dist[4] = {
+ dir_coll[U] ? d_inf : up.sq_distance(target),
+ dir_coll[L] ? d_inf : left.sq_distance(target),
+ dir_coll[D] ? d_inf : down.sq_distance(target),
+ dir_coll[R] ? d_inf : right.sq_distance(target) };
+
+ // penalty for reversal
+ dir_dist[ ::number(inv_dir) ] += 2*2;
+
+ if( log_moves ) {
+ log_print(std::string("collisions u "+std::to_string(dir_coll[U])+", l "+std::to_string(dir_coll[L])+", d "+std::to_string(dir_coll[D])+", r "+std::to_string(dir_coll[R])+"\n").c_str());
+ log_print(std::string("distances u "+std::to_string(dir_dist[U])+", l "+std::to_string(dir_dist[L])+", d "+std::to_string(dir_dist[D])+", r "+std::to_string(dir_dist[R])+"\n").c_str());
+ }
+ // Check for a clear short path, reversal has been punished
+ if( !dir_coll[U] && dir_dist[U] < dir_dist[D] && dir_dist[U] < dir_dist[L] && dir_dist[U] < dir_dist[R] ) {
+ new_dir = direction_t::UP;
+ choice = 30;
+ } else if( !dir_coll[L] && dir_dist[L] < dir_dist[U] && dir_dist[L] < dir_dist[D] && dir_dist[L] < dir_dist[R] ) {
+ new_dir = direction_t::LEFT;
+ choice = 31;
+ } else if( !dir_coll[D] && dir_dist[D] < dir_dist[U] && dir_dist[D] < dir_dist[L] && dir_dist[D] < dir_dist[R] ) {
+ new_dir = direction_t::DOWN;
+ choice = 32;
+ } else if( !dir_coll[R] && dir_dist[R] < dir_dist[U] && dir_dist[R] < dir_dist[D] && dir_dist[R] < dir_dist[L] ) {
+ new_dir = direction_t::RIGHT;
+ choice = 33;
+ } else {
+ if( !dir_coll[ ::number(cur_dir) ] ) {
+ // straight ahead .. no better choice
+ new_dir = cur_dir;
+ choice = 40;
+ } else if( !dir_coll[U] && dir_dist[U] <= dir_dist[D] && dir_dist[U] <= dir_dist[L] && dir_dist[U] <= dir_dist[R] ) {
+ new_dir = direction_t::UP;
+ choice = 50;
+ } else if( !dir_coll[L] && dir_dist[L] <= dir_dist[U] && dir_dist[L] <= dir_dist[D] && dir_dist[L] <= dir_dist[R] ) {
+ new_dir = direction_t::LEFT;
+ choice = 51;
+ } else if( !dir_coll[D] && dir_dist[D] <= dir_dist[U] && dir_dist[D] <= dir_dist[L] && dir_dist[D] <= dir_dist[R] ) {
+ new_dir = direction_t::DOWN;
+ choice = 52;
+ } else if( !dir_coll[R] /* && dir_dist[R] <= dir_dist[U] && dir_dist[R] <= dir_dist[D] && dir_dist[R] <= dir_dist[L] */ ) {
+ new_dir = direction_t::RIGHT;
+ choice = 53;
+ } else {
+ new_dir = direction_t::LEFT;
+ choice = 60;
+ }
+ }
+ }
+ if( dir_ != new_dir ) {
+ last_dir = dir_;
+ }
+ dir_ = new_dir;
+ if( log_moves ) {
+ log_print("%s set_next_dir: %s -> %s (%d), %s [%d ms], pos %s -> %s\n", to_string(id).c_str(), to_string(cur_dir).c_str(), to_string(new_dir).c_str(),
+ choice, to_string(mode).c_str(), mode_ms_left, pos.toShortString().c_str(), target.toShortString().c_str());
+ }
+}
+
+bool ghost_t::tick() {
+ ++frame_count;
+
+ atex = &get_tex();
+ atex->tick();
+
+ bool collision_maze = false;
+
+ if( 0 < mode_ms_left ) {
+ mode_ms_left = std::max( 0, mode_ms_left - get_ms_per_frame() );
+ }
+
+ const bool in_transition = pos.is_transitioning(fields_per_sec, get_frames_per_sec());
+ if( !in_transition ) {
+ if( mode_t::AWAY == mode ) {
+ return true; // NOP
+ } else if( mode_t::HOME == mode ) {
+ if( 0 == mode_ms_left ) {
+ set_mode( mode_t::LEAVE_HOME );
+ }
+ } else if( mode_t::LEAVE_HOME == mode ) {
+ if( pos.intersects(target) ) {
+ set_mode( mode_t::CHASE );
+ } else if( 0 == mode_ms_left ) { // ooops
+ pos = pacman_maze->get_ghost_start_pos();
+ set_mode( mode_t::CHASE );
+ }
+ } else if( mode_t::CHASE == mode ) {
+ if( !pos.intersects_f( pacman_maze->get_ghost_home_box() ) ) {
+ target = get_personal_target(); // update ...
+ }
+ if( 0 == mode_ms_left ) {
+ set_mode( mode_t::SCATTER );
+ }
+ } else if( mode_t::SCATTER == mode ) {
+ if( 0 == mode_ms_left ) {
+ set_mode( mode_t::CHASE );
+ }
+ } else if( mode_t::SCARED == mode ) {
+ if( 0 == mode_ms_left ) {
+ set_mode( mode_t::SCATTER );
+ }
+ } else if( mode_t::PHANTOM == mode ) {
+ if( pos.intersects(target) || 0 == mode_ms_left ) {
+ set_mode( mode_t::HOME );
+ }
+ }
+ }
+
+ collision_maze = !pos.step(*pacman_maze, dir_, fields_per_sec, get_frames_per_sec(), [&](direction_t d, int x, int y, tile_t tile) -> bool {
+ (void)d; (void)x; (void)y;
+ return ( mode_t::LEAVE_HOME == mode || mode_t::PHANTOM == mode ) ?
+ tile_t::WALL == tile : ( tile_t::WALL == tile || tile_t::GATE == tile );
+ });
+ set_next_dir();
+
+ if( DEBUG_GFX_BOUNDS ) {
+ log_print("tick[%s]: frame %3.3d, %s, pos %s -> %s, crash[maze %d], textures %s\n",
+ to_string(id).c_str(), frame_count, to_string(dir_).c_str(), pos.toShortString().c_str(), target.toShortString().c_str(), collision_maze, atex->toString().c_str());
+ }
+ return true;
+}
+
+void ghost_t::draw(SDL_Renderer* rend) {
+ if( mode_t::AWAY != mode ) {
+ atex->draw(rend, pos.get_x_f(), pos.get_y_f(), ghost_t::mode_t::HOME != mode /* maze_offset */);
+ }
+}
+
+std::string ghost_t::toString() const {
+ return to_string(id)+"["+to_string(mode)+"["+std::to_string(mode_ms_left)+" ms], "+to_string(dir_)+", "+pos.toString()+" -> "+target.toShortString()+", "+atex->toString()+"]";
+}
+
+std::string to_string(ghost_t::personality_t id) {
+ switch( id ) {
+ case ghost_t::personality_t::BLINKY:
+ return "blinky";
+ case ghost_t::personality_t::CLYDE:
+ return "clyde";
+ case ghost_t::personality_t::INKY:
+ return "inky";
+ case ghost_t::personality_t::PINKY:
+ return "pinky";
+ default:
+ return "unknown";
+ }
+}
+
+std::string to_string(ghost_t::mode_t m) {
+ switch( m ) {
+ case ghost_t::mode_t::AWAY:
+ return "away";
+ case ghost_t::mode_t::HOME:
+ return "home";
+ case ghost_t::mode_t::LEAVE_HOME:
+ return "leave_home";
+ case ghost_t::mode_t::CHASE:
+ return "chase";
+ case ghost_t::mode_t::SCATTER:
+ return "scatter";
+ case ghost_t::mode_t::SCARED:
+ return "scared";
+ case ghost_t::mode_t::PHANTOM:
+ return "phantom";
+ default:
+ return "unknown";
+ }
+}
+
diff --git a/src/graphics.cpp b/src/graphics.cpp
new file mode 100644
index 0000000..a3248eb
--- /dev/null
+++ b/src/graphics.cpp
@@ -0,0 +1,302 @@
+/*
+ * Author: Sven Gothel <[email protected]> and Svenson Han Gothel
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * 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.
+ */
+#include <pacman/maze.hpp>
+#include <pacman/graphics.hpp>
+#include <pacman/globals.hpp>
+
+#include <cstdio>
+
+static constexpr const bool DEBUG_LOG = false;
+
+//
+// texture_t
+//
+
+std::atomic<int> texture_t::counter = 0;
+
+texture_t::texture_t(SDL_Renderer* rend, const std::string& fname)
+: id(counter++)
+{
+ SDL_Surface* surface = IMG_Load(fname.c_str());
+ if( nullptr != surface ) {
+ if( DEBUG_LOG ) {
+ log_print("texture_t::surface: fmt %u 0x%X, %d x %d pitch %d\n", surface->format->format, surface->format->format, surface->w, surface->h, surface->pitch);
+ }
+ tex = SDL_CreateTextureFromSurface(rend, surface);
+ SDL_FreeSurface(surface);
+ if( nullptr == tex ) {
+ log_print("texture_t: Error loading %s: %s\n", fname.c_str(), SDL_GetError());
+ }
+ } else {
+ tex = nullptr;
+ log_print("texture_t::surface: Error loading %s: %s\n", fname.c_str(), SDL_GetError());
+ }
+ x = 0;
+ y = 0;
+ width = 0;
+ height = 0;
+ Uint32 format = 0;
+ if( nullptr != tex ) {
+ SDL_QueryTexture(tex, &format, NULL, &width, &height);
+ if( DEBUG_LOG ) {
+ log_print("texture_t: fmt %u 0x%X, %d x %d\n", format, format, width, height);
+ }
+ }
+ owner = true;
+}
+
+texture_t::texture_t(SDL_Renderer* rend, SDL_Surface* surface)
+: id(counter++)
+{
+ tex = SDL_CreateTextureFromSurface(rend, surface);
+ x = 0;
+ y = 0;
+ SDL_QueryTexture(tex, NULL, NULL, &width, &height);
+ owner = true;
+}
+
+void texture_t::destroy() {
+ if( owner && nullptr != tex ) {
+ SDL_DestroyTexture(tex);
+ }
+ tex = nullptr;
+}
+
+void texture_t::draw_scaled_dimpos(SDL_Renderer* rend, const int x_pos, const int y_pos) {
+ if( nullptr != tex ) {
+ SDL_Rect src = { .x=x, .y=y, .w=width, .h=height};
+ const int win_pixel_offset = ( win_pixel_width - pacman_maze->get_pixel_width()*win_pixel_scale ) / 2;
+ SDL_Rect dest = { .x=win_pixel_offset + x_pos,
+ .y=y_pos,
+ .w=width, .h=height };
+ SDL_RenderCopy(rend, tex, &src, &dest);
+ }
+}
+void texture_t::draw_scaled_dim(SDL_Renderer* rend, const int x_pos, const int y_pos) {
+ if( nullptr != tex ) {
+ SDL_Rect src = { .x=x, .y=y, .w=width, .h=height};
+ const int win_pixel_offset = ( win_pixel_width - pacman_maze->get_pixel_width()*win_pixel_scale ) / 2;
+ SDL_Rect dest = { .x=win_pixel_offset + ( pacman_maze->x_to_pixel(x_pos, win_pixel_scale, false) ),
+ .y=pacman_maze->y_to_pixel(y_pos, win_pixel_scale, false),
+ .w=width, .h=height };
+ SDL_RenderCopy(rend, tex, &src, &dest);
+ }
+}
+void texture_t::draw(SDL_Renderer* rend, const int x_pos, const int y_pos, const bool maze_offset) {
+ if( nullptr != tex ) {
+ SDL_Rect src = { .x=x, .y=y, .w=width, .h=height};
+ const int win_pixel_offset = ( win_pixel_width - pacman_maze->get_pixel_width()*win_pixel_scale ) / 2;
+ SDL_Rect dest = { .x=win_pixel_offset + ( pacman_maze->x_to_pixel(x_pos, win_pixel_scale, maze_offset) ),
+ .y=pacman_maze->y_to_pixel(y_pos, win_pixel_scale, maze_offset),
+ .w=width*win_pixel_scale, .h=height*win_pixel_scale };
+ SDL_RenderCopy(rend, tex, &src, &dest);
+ }
+}
+void texture_t::draw(SDL_Renderer* rend, const float x_pos, const float y_pos, const bool maze_offset) {
+ if( nullptr != tex ) {
+ SDL_Rect src = { .x=x, .y=y, .w=width, .h=height};
+ const int win_pixel_offset = ( win_pixel_width - pacman_maze->get_pixel_width()*win_pixel_scale ) / 2;
+ SDL_Rect dest = { .x=win_pixel_offset + pacman_maze->x_to_pixel(x_pos, win_pixel_scale, maze_offset),
+ .y=pacman_maze->y_to_pixel(y_pos, win_pixel_scale, maze_offset),
+ .w=width*win_pixel_scale, .h=height*win_pixel_scale };
+ SDL_RenderCopy(rend, tex, &src, &dest);
+ }
+}
+
+std::string texture_t::toString() const {
+ return "id "+std::to_string(id) + " " + std::to_string(x)+"/"+std::to_string(y) + " " + std::to_string(width)+"x"+std::to_string(height) + ", owner " + std::to_string(owner);
+}
+
+//
+// add_sub_textures(..)
+//
+
+int add_sub_textures(std::vector<std::shared_ptr<texture_t>>& storage, SDL_Renderer* rend, const std::string& filename, int w, int h, int x_off)
+{
+ std::unique_ptr<texture_t> all = std::make_unique<texture_t>(rend, filename);
+ all->disown();
+ if( DEBUG_LOG ) {
+ log_print("add_sub_textures: each ( %d + %d ) x %d, all: %s\n", w, x_off, h, all->toString().c_str());
+ }
+ const size_t size_start = storage.size();
+
+ for(int y=0; y<all->get_height(); y+=h) {
+ for(int x=0; x<all->get_width(); x+=w+x_off) {
+ if( storage.size() > size_start ) {
+ storage[ storage.size() - 1 ]->disown(); // only last entry is owner of the SDL_Texture
+ }
+ storage.push_back( std::make_shared<texture_t>(all->get_sdltex(), x, y, w, h, true /* owner*/) );
+ if( DEBUG_LOG ) {
+ log_print("add_sub_textures: tex %zd [%d][%d]: %s\n", storage.size()-1, x, y, storage[ storage.size() - 1 ]->toString().c_str());
+ }
+ }
+ }
+ return storage.size() - size_start;
+}
+
+int add_sub_textures(std::vector<std::shared_ptr<texture_t>>& storage, SDL_Renderer* rend, const std::shared_ptr<texture_t>& global_texture,
+ int x_off, int y_off, int w, int h, const std::vector<tex_sub_coord_t>& tex_positions)
+{
+ if( DEBUG_LOG ) {
+ log_print("add_sub_textures: each %d x %d, all: %s\n", w, h, global_texture->toString().c_str());
+ }
+ const size_t size_start = storage.size();
+
+ for(tex_sub_coord_t p : tex_positions) {
+ const int x = x_off+p.x;
+ const int y = y_off+p.y;
+ if( 0 <= x && 0 <= y && x+w <= global_texture->get_width() && y+h <= global_texture->get_height() ) {
+ storage.push_back( std::make_shared<texture_t>(global_texture->get_sdltex(), x, y, w, h, false /* owner*/) );
+ } else {
+ storage.push_back( std::make_shared<texture_t>() );
+ }
+ if( DEBUG_LOG ) {
+ log_print("add_sub_textures: tex %zd [%d][%d]: %s\n", storage.size()-1, x, y, storage[ storage.size() - 1 ]->toString().c_str());
+ }
+ }
+ return storage.size() - size_start;
+}
+
+//
+// animtex_t
+//
+
+animtex_t::animtex_t(std::string name_, int ms_per_atex_, const std::vector<std::shared_ptr<texture_t>>& textures_) {
+ for(size_t i=0; i<textures_.size(); ++i) {
+ texture_t& o = *textures_[i];
+ textures.push_back( std::make_shared<texture_t>(o.get_sdltex(), o.get_x(), o.get_y(), o.get_width(), o.get_height(), false /* owner */) );
+ }
+ ms_per_atex = ms_per_atex_;
+ atex_ms_left = 0;
+ animation_index = 0;
+ paused = false;
+}
+animtex_t::animtex_t(std::string name_, SDL_Renderer* rend, int ms_per_atex_, const std::vector<const char*>& filenames)
+: name(name_)
+{
+ for(size_t i=0; i<filenames.size(); ++i) {
+ textures.push_back( std::make_shared<texture_t>(rend, filenames[i]) );
+ }
+ ms_per_atex = ms_per_atex_;
+ atex_ms_left = 0;
+ animation_index = 0;
+ paused = false;
+}
+
+animtex_t::animtex_t(std::string name_, SDL_Renderer* rend, int ms_per_atex_, const std::string& filename, int w, int h, int x_off)
+: name(name_)
+{
+ add_sub_textures(textures, rend, filename, w, h, x_off);
+ ms_per_atex = ms_per_atex_;
+ atex_ms_left = 0;
+ animation_index = 0;
+ paused = false;
+}
+
+animtex_t::animtex_t(std::string name_, SDL_Renderer* rend, int ms_per_atex_, const std::shared_ptr<texture_t>& global_texture,
+ int x_off, int y_off, int w, int h, const std::vector<tex_sub_coord_t>& tex_positions)
+: name(name_)
+{
+ add_sub_textures(textures, rend, global_texture, x_off, y_off, w, h, tex_positions);
+ ms_per_atex = ms_per_atex_;
+ atex_ms_left = 0;
+ animation_index = 0;
+ paused = false;
+}
+
+void animtex_t::destroy() {
+ for(size_t i=0; i<textures.size(); ++i) {
+ textures[i]->destroy();
+ }
+ textures.clear();
+}
+
+void animtex_t::pause(bool enable) {
+ paused = enable;
+ if( enable ) {
+ animation_index = 0;
+ }
+}
+
+void animtex_t::reset() {
+ animation_index = 0;
+ atex_ms_left = ms_per_atex;
+}
+
+void animtex_t::tick() {
+ if( !paused ) {
+ if( 0 < atex_ms_left ) {
+ atex_ms_left = std::max( 0, atex_ms_left - get_ms_per_frame() );
+ }
+ if( 0 == atex_ms_left ) {
+ atex_ms_left = ms_per_atex;
+ if( textures.size() > 0 ) {
+ animation_index = ( animation_index + 1 ) % textures.size();
+ } else {
+ animation_index = 0;
+ }
+ }
+ }
+}
+
+std::string animtex_t::toString() const {
+ std::shared_ptr<const texture_t> tex = get_tex();
+ std::string tex_s = nullptr != tex ? tex->toString() : "null";
+ return name+"[anim "+std::to_string(atex_ms_left)+"/"+std::to_string(ms_per_atex)+
+ " ms, paused "+std::to_string(paused)+", idx "+std::to_string(animation_index)+"/"+std::to_string(textures.size())+
+ ", textures["+tex_s+"]]";
+}
+
+std::shared_ptr<text_texture_t> draw_text(SDL_Renderer* rend, TTF_Font* font, const std::string& text, int x, int y, uint8_t r, uint8_t g, uint8_t b) {
+ SDL_Color foregroundColor = { r, g, b, 255 };
+
+ SDL_Surface* textSurface = TTF_RenderText_Solid(font, text.c_str(), foregroundColor);
+ if( nullptr != textSurface ) {
+ std::shared_ptr<text_texture_t> ttex = std::make_shared<text_texture_t>(text, rend, textSurface, false, x, y);
+ SDL_FreeSurface(textSurface);
+ ttex->texture.draw_scaled_dim(rend, x, y);
+ // log_print("draw_text: '%s', tex %s\n", text.c_str(), tex.toString().c_str());
+ return ttex;
+ } else {
+ log_print("draw_text: Null texture for '%s': %s\n", text.c_str(), SDL_GetError());
+ return nullptr;
+ }
+}
+
+std::shared_ptr<text_texture_t> draw_text_scaled(SDL_Renderer* rend, TTF_Font* font, const std::string& text, uint8_t r, uint8_t g, uint8_t b, std::function<void(const texture_t& tex, int &x, int&y)> scaled_coord) {
+ SDL_Color foregroundColor = { r, g, b, 255 };
+
+ SDL_Surface* textSurface = TTF_RenderText_Solid(font, text.c_str(), foregroundColor);
+ if( nullptr != textSurface ) {
+ std::shared_ptr<text_texture_t> ttex = std::make_shared<text_texture_t>(text, rend, textSurface, true, 0, 0);
+ SDL_FreeSurface(textSurface);
+ scaled_coord(ttex->texture, ttex->x_pos, ttex->y_pos);
+ ttex->texture.draw_scaled_dimpos(rend, ttex->x_pos, ttex->y_pos);
+ // log_print("draw_text: '%s', tex %s\n", text.c_str(), tex.toString().c_str());
+ return ttex;
+ } else {
+ log_print("draw_text: Null texture for '%s': %s\n", text.c_str(), SDL_GetError());
+ return nullptr;
+ }
+}
diff --git a/src/maze.cpp b/src/maze.cpp
new file mode 100644
index 0000000..c749b69
--- /dev/null
+++ b/src/maze.cpp
@@ -0,0 +1,555 @@
+/*
+ * Author: Sven Gothel <[email protected]> and Svenson Han Gothel
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * 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.
+ */
+#include <pacman/maze.hpp>
+#include <pacman/globals.hpp>
+
+#include <iostream>
+#include <fstream>
+
+#include <strings.h>
+
+//
+// direction_t
+//
+std::string to_string(direction_t dir) {
+ switch(dir) {
+ case direction_t::UP: return "U";
+ case direction_t::LEFT: return "L";
+ case direction_t::DOWN: return "D";
+ case direction_t::RIGHT: return "R";
+ default: return "?";
+ }
+}
+
+direction_t inverse(direction_t dir) {
+ switch(dir) {
+ case direction_t::UP: return direction_t::DOWN;
+ case direction_t::LEFT: return direction_t::RIGHT;
+ case direction_t::DOWN: return direction_t::UP;
+ case direction_t::RIGHT: return direction_t::LEFT;
+ default: return direction_t::LEFT;
+ }
+}
+
+direction_t rot_left(direction_t dir) {
+ switch(dir) {
+ case direction_t::UP: return direction_t::LEFT;
+ case direction_t::LEFT: return direction_t::DOWN;
+ case direction_t::DOWN: return direction_t::RIGHT;
+ case direction_t::RIGHT: return direction_t::UP;
+ default: return direction_t::UP;
+ }
+}
+
+direction_t rot_right(direction_t dir) {
+ switch(dir) {
+ case direction_t::UP: return direction_t::RIGHT;
+ case direction_t::LEFT: return direction_t::UP;
+ case direction_t::DOWN: return direction_t::LEFT;
+ case direction_t::RIGHT: return direction_t::DOWN;
+ default: return direction_t::UP;
+ }
+}
+
+//
+// tile_t
+//
+std::string to_string(tile_t tile) {
+ switch(tile) {
+ case tile_t::EMPTY: return " ";
+ case tile_t::PELLET: return ".";
+ case tile_t::PELLET_POWER: return "*";
+ case tile_t::WALL: return "X";
+ case tile_t::GATE: return "-";
+ default: return "?";
+ }
+}
+
+//
+// box_t
+//
+std::string box_t::toString() const {
+ return "["+std::to_string(x_pos)+"/"+std::to_string(y_pos)+" "+std::to_string(width)+"x"+std::to_string(height)+"]";
+}
+
+//
+// acoord_t
+//
+
+acoord_t::acoord_t(const int x, const int y)
+: x_pos_i(x), y_pos_i(y),
+ x_pos_f(x), y_pos_f(y),
+ last_dir(direction_t::LEFT),
+ last_collided(false),
+ fields_walked_i(0),
+ fields_walked_f(0)
+{}
+
+void acoord_t::reset_stats() {
+ fields_walked_i = 0;
+ fields_walked_f = 0;
+}
+
+void acoord_t::set_pos(const int x, const int y) {
+ x_pos_i = x;
+ y_pos_i = y;
+ x_pos_f = x;
+ y_pos_f = y;
+ last_dir = direction_t::LEFT;
+ last_collided = false;
+}
+
+bool acoord_t::intersects_f(const acoord_t& other) const {
+ return !( x_pos_f + 0.999 < other.x_pos_f || other.x_pos_f + 0.999 < x_pos_f ||
+ y_pos_f + 0.999 < other.y_pos_f || other.y_pos_f + 0.999 < y_pos_f );
+}
+
+bool acoord_t::intersects_i(const acoord_t& other) const {
+ return x_pos_i == other.x_pos_i && y_pos_i == other.y_pos_i;
+}
+
+bool acoord_t::intersects(const acoord_t& other) const {
+ return use_original_pacman_behavior() ? intersects_i(other) : intersects_f(other);
+}
+
+bool acoord_t::intersects_f(const box_t& other) const {
+ return !( x_pos_f + 0.999 < other.get_x() || other.get_x() + other.get_width() < x_pos_f ||
+ y_pos_f + 0.999 < other.get_y() || other.get_y() + other.get_height() < y_pos_f );
+}
+
+float acoord_t::distance(const float x, const float y) const {
+ const float x_d = std::abs(x - x_pos_f);
+ const float y_d = std::abs(y - y_pos_f);
+ return std::sqrt(x_d * x_d + y_d * y_d);
+}
+
+float acoord_t::sq_distance(const float x, const float y) const {
+ const float x_d = std::abs(x - x_pos_f);
+ const float y_d = std::abs(y - y_pos_f);
+ return x_d * x_d + y_d * y_d;
+}
+
+void acoord_t::incr_fwd(const maze_t& maze, const direction_t dir, const int tile_count) {
+ const float fields_per_frame = tile_count;
+ switch( dir ) {
+ case direction_t::DOWN:
+ if( round_to_int(y_pos_f + fields_per_frame) < maze.get_height() ) {
+ y_pos_f = y_pos_f + fields_per_frame;
+ } else {
+ y_pos_f = maze.get_height(); // clip only, no overflow to other side
+ }
+ y_pos_i = maze.clip_pos_y( round_to_int(y_pos_f) );
+ x_pos_i = maze.clip_pos_x( round_to_int(x_pos_f) );
+ x_pos_f = x_pos_i;
+ break;
+
+ case direction_t::RIGHT:
+ if( round_to_int(x_pos_f + fields_per_frame) < maze.get_width() ) {
+ x_pos_f = x_pos_f + fields_per_frame;
+ } else {
+ x_pos_f = maze.get_width(); // clip only, no overflow to other side
+ }
+ x_pos_i = maze.clip_pos_x( round_to_int(x_pos_f) );
+ y_pos_i = maze.clip_pos_y( round_to_int(y_pos_f) );
+ y_pos_f = y_pos_i;
+ break;
+
+ case direction_t::UP:
+ if( round_to_int(y_pos_f - fields_per_frame) >= 0 ) {
+ y_pos_f = y_pos_f - fields_per_frame;
+ } else {
+ y_pos_f = 0; // clip only, no overflow to other side
+ }
+ y_pos_i = maze.clip_pos_y( round_to_int(y_pos_f) );
+ x_pos_i = maze.clip_pos_x( round_to_int(x_pos_f) );
+ x_pos_f = x_pos_i;
+ break;
+
+ case direction_t::LEFT:
+ [[fallthrough]];
+ default:
+ if( round_to_int(x_pos_f - fields_per_frame) >= 0 ) {
+ x_pos_f = x_pos_f - fields_per_frame;
+ } else {
+ x_pos_f = 0; // clip only, no overflow to other side
+ }
+ x_pos_i = maze.clip_pos_x( round_to_int(x_pos_f) );
+ y_pos_i = maze.clip_pos_y( round_to_int(y_pos_f) );
+ y_pos_f = y_pos_i;
+ break;
+ }
+}
+
+bool acoord_t::is_transitioning(const float fields_per_sec, const int frames_per_sec) {
+ const float fields_per_frame = fields_per_sec / frames_per_sec;
+ if( std::abs( std::round(x_pos_f) - x_pos_f ) < fields_per_frame/2.0 &&
+ std::abs( std::round(y_pos_f) - y_pos_f ) < fields_per_frame/2.0 ) {
+ // on tile center within step width
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool acoord_t::step_impl(const maze_t& maze, direction_t dir, const bool test_only, const float fields_per_frame, collisiontest_t ct) {
+ /**
+ * The new float position, pixel accurate.
+ */
+ float new_x_pos_f;
+ float new_y_pos_f;
+
+ /**
+ * The forward look-ahead int position for wall collision tests.
+ *
+ * Depending on the direction, it adds a whole tile position
+ * to the pixel accurate position only to test for wall collisions.
+ *
+ * This way, it avoids 'overstepping' from its path!
+ */
+ int fwd_x_pos_i;
+ int fwd_y_pos_i;
+
+ /**
+ * This step walking distance will be accumulated for statistics.
+ */
+ float fields_stepped_f = 0;
+
+ /**
+ * The resulting int position will be weighted (rounded)
+ * as the original pacman game.
+ */
+ switch( dir ) {
+ case direction_t::DOWN:
+ if( round_to_int(y_pos_f + fields_per_frame) < maze.get_height() ) {
+ new_y_pos_f = y_pos_f + fields_per_frame;
+ fields_stepped_f = fields_per_frame;
+ if( new_y_pos_f > std::floor(new_y_pos_f) ) {
+ fwd_y_pos_i = std::min(maze.get_height()-1, floor_to_int(y_pos_f) + 1);
+ } else {
+ fwd_y_pos_i = std::min(maze.get_height()-1, floor_to_int(new_y_pos_f));
+ }
+ } else {
+ new_y_pos_f = 0;
+ fwd_y_pos_i = 0;
+ }
+ fwd_x_pos_i = maze.clip_pos_x( round_to_int(x_pos_f) );
+ new_x_pos_f = fwd_x_pos_i;
+ break;
+
+ case direction_t::RIGHT:
+ if( round_to_int(x_pos_f + fields_per_frame) < maze.get_width() ) {
+ new_x_pos_f = x_pos_f + fields_per_frame;
+ fields_stepped_f = fields_per_frame;
+ if( new_x_pos_f > std::floor(new_x_pos_f) ) {
+ fwd_x_pos_i = std::min(maze.get_width()-1, floor_to_int(x_pos_f) + 1);
+ } else {
+ fwd_x_pos_i = std::min(maze.get_width()-1, floor_to_int(new_x_pos_f));
+ }
+ } else {
+ new_x_pos_f = 0;
+ fwd_x_pos_i = 0;
+ }
+ fwd_y_pos_i = maze.clip_pos_y( round_to_int(y_pos_f) );
+ new_y_pos_f = fwd_y_pos_i;
+ break;
+
+ case direction_t::UP:
+ if( round_to_int(y_pos_f - fields_per_frame) >= 0 ) {
+ new_y_pos_f = y_pos_f - fields_per_frame;
+ fields_stepped_f = fields_per_frame;
+ if( new_y_pos_f < std::ceil(new_y_pos_f) ) {
+ fwd_y_pos_i = std::max(0, ceil_to_int(y_pos_f) - 1);
+ } else {
+ fwd_y_pos_i = std::max(0, ceil_to_int(new_y_pos_f));
+ }
+ } else {
+ new_y_pos_f = maze.get_height() - 1;
+ fwd_y_pos_i = ceil_to_int(new_y_pos_f);
+ }
+ fwd_x_pos_i = maze.clip_pos_x( round_to_int(x_pos_f) );
+ new_x_pos_f = fwd_x_pos_i;
+ break;
+
+ case direction_t::LEFT:
+ [[fallthrough]];
+ default:
+ if( round_to_int(x_pos_f - fields_per_frame) >= 0 ) {
+ new_x_pos_f = x_pos_f - fields_per_frame;
+ fields_stepped_f = fields_per_frame;
+ if( new_x_pos_f < std::ceil(new_x_pos_f) ) {
+ fwd_x_pos_i = std::max(0, ceil_to_int(x_pos_f) - 1);
+ } else {
+ fwd_x_pos_i = std::max(0, ceil_to_int(new_x_pos_f));
+ }
+ } else {
+ new_x_pos_f = maze.get_width() - 1;
+ fwd_x_pos_i = ceil_to_int(new_x_pos_f);
+ }
+ fwd_y_pos_i = maze.clip_pos_y( round_to_int(y_pos_f) );
+ new_y_pos_f = fwd_y_pos_i;
+ break;
+
+ }
+ // Collision test with walls
+ const tile_t tile = maze.get_tile(fwd_x_pos_i, fwd_y_pos_i);
+ bool collision = nullptr != ct ? ct(dir, fwd_x_pos_i, fwd_y_pos_i, tile) : false;
+ if( DEBUG_BOUNDS ) {
+ log_print("%s: %s -> %s: %5.2f/%5.2f %2.2d/%2.2d -> %5.2f/%5.2f %2.2d/%2.2d, tile '%s', collision %d\n",
+ test_only ? "test" : "step",
+ to_string(last_dir).c_str(), to_string(dir).c_str(),
+ x_pos_f, y_pos_f, x_pos_i, y_pos_i,
+ new_x_pos_f, new_y_pos_f, fwd_x_pos_i, fwd_y_pos_i, to_string(tile).c_str(), collision);
+ }
+ if( !test_only ) {
+ if( !collision ) {
+ last_collided = false;
+ x_pos_f = new_x_pos_f;
+ y_pos_f = new_y_pos_f;
+ const int x_pos_i_old = x_pos_i;
+ const int y_pos_i_old = y_pos_i;
+ /**
+ * Resulting int position is be weighted (rounded)
+ * as the original pacman game.
+ */
+ x_pos_i = maze.clip_pos_x( round_to_int(x_pos_f) );
+ y_pos_i = maze.clip_pos_y( round_to_int(y_pos_f) );
+ last_dir = dir;
+ fields_walked_i += std::abs(x_pos_i - x_pos_i_old) + std::abs(y_pos_i - y_pos_i_old);
+ fields_walked_f += fields_stepped_f;
+ } else {
+ last_collided = true;
+ // re-align pixel accurate position, no 'overstepping'
+ switch( dir ) {
+ case direction_t::RIGHT:
+ x_pos_f = std::floor(x_pos_f);
+ break;
+
+ case direction_t::LEFT:
+ x_pos_f = std::ceil(x_pos_f);
+ break;
+
+ case direction_t::DOWN:
+ y_pos_f = std::floor(y_pos_f);
+ break;
+
+ case direction_t::UP:
+ y_pos_f = std::ceil(y_pos_f);
+ break;
+ }
+ }
+ }
+ return !collision;
+}
+
+std::string acoord_t::toString() const {
+ return "["+std::to_string(x_pos_f)+"/"+std::to_string(y_pos_f)+" "+std::to_string(x_pos_i)+"/"+std::to_string(y_pos_i)+
+ ", last[dir "+to_string(last_dir)+", collided "+std::to_string(last_collided)+"], walked["+std::to_string(fields_walked_f)+", "+std::to_string(fields_walked_i)+"]]";
+}
+
+std::string acoord_t::toShortString() const {
+ return "["+std::to_string(x_pos_f)+"/"+std::to_string(y_pos_f)+" "+std::to_string(x_pos_i)+"/"+std::to_string(y_pos_i)+"]";
+}
+
+//
+// maze_t::field_t
+//
+
+maze_t::field_t::field_t()
+: width(0), height(0)
+{
+ bzero(&count, sizeof(count));
+}
+
+void maze_t::field_t::clear() {
+ width = 0; height = 0;
+ tiles.clear();
+ bzero(&count, sizeof(count));
+}
+
+tile_t maze_t::field_t::get_tile(const int x, const int y) const {
+ if( 0 <= x && x < width && 0 <= y && y < height ) {
+ return tiles[y*width+x];
+ }
+ return tile_t::EMPTY;
+}
+
+void maze_t::field_t::add_tile(const tile_t tile) {
+ tiles.push_back(tile);
+ ++count[number(tile)];
+}
+
+void maze_t::field_t::set_tile(const int x, const int y, tile_t tile) {
+ if( 0 <= x && x < width && 0 <= y && y < height ) {
+ const tile_t old_tile = tiles[y*width+x];
+ tiles[y*width+x] = tile;
+ --count[number(old_tile)];
+ ++count[number(tile)];
+ }
+}
+
+std::string maze_t::field_t::toString() const {
+ return "field["+std::to_string(width)+"x"+std::to_string(height)+", pellets["+std::to_string(get_count(tile_t::PELLET))+", power "+std::to_string(get_count(tile_t::PELLET_POWER))+"]]";
+}
+
+//
+// maze_t
+//
+
+bool maze_t::digest_position_line(const std::string& name, acoord_t& dest, const std::string& line) {
+ if( -1 == dest.get_x_i() || -1 == dest.get_y_i() ) {
+ int x_pos = 0, y_pos = 0;
+ sscanf(line.c_str(), "%d %d", &x_pos, &y_pos);
+ dest.set_pos(x_pos, y_pos);
+ if( DEBUG ) {
+ log_print("maze: read %s position: %s\n", name.c_str(), dest.toString().c_str());
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool maze_t::digest_box_line(const std::string& name, box_t& dest, const std::string& line) {
+ if( -1 == dest.get_x() || -1 == dest.get_y() ) {
+ int x_pos = 0, y_pos = 0, w = 0, h = 0;
+ sscanf(line.c_str(), "%d %d %d %d", &x_pos, &y_pos, &w, &h);
+ dest.set_dim(x_pos, y_pos, w, h);
+ if( DEBUG ) {
+ log_print("maze: read %s box: %s\n", name.c_str(), dest.toString().c_str());
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+maze_t::maze_t(const std::string& fname)
+: filename(fname),
+ top_left_pos(-1, -1),
+ bottom_left_pos(-1, -1),
+ bottom_right_pos(-1, -1),
+ top_right_pos(-1, -1),
+ pacman_start_pos(-1, -1),
+ ghost_home(-1, -1, -1, -1),
+ ghost_home_pos(-1, -1),
+ ghost_start_pos(-1, -1),
+ ppt_x( -1 ),
+ ppt_y( -1 )
+{
+ int field_line_iter = 0;
+ std::fstream file;
+ file.open(fname, std::ios::in);
+ if( file.is_open() ) {
+ std::string line;
+ while( std::getline(file, line) ) {
+ if( 0 == original.get_width() || 0 == original.get_height() ) {
+ int w=-1, h=-1;
+ int visual_width=-1, visual_height=-1;
+ sscanf(line.c_str(), "%d %d %d %d", &w, &h, &visual_width, &visual_height);
+ original.set_dim(w, h);
+ ppt_x = visual_width / original.get_width();
+ ppt_y = visual_height / original.get_height();
+ if( DEBUG ) {
+ log_print("maze: read dimension: %s\n", toString().c_str());
+ }
+ } else if( digest_position_line("top_left_pos", top_left_pos, line) ) {
+ } else if( digest_position_line("bottom_left_pos", bottom_left_pos, line) ) {
+ } else if( digest_position_line("bottom_right_pos", bottom_right_pos, line) ) {
+ } else if( digest_position_line("top_right_pos", top_right_pos, line) ) {
+ } else if( digest_position_line("pacman", pacman_start_pos, line) ) {
+ } else if( digest_box_line("ghost_home", ghost_home, line) ) {
+ } else if( digest_position_line("ghost_home", ghost_home_pos, line) ) {
+ } else if( digest_position_line("ghost_start", ghost_start_pos, line) ) {
+ } else if( 0 == texture_file.length() ) {
+ texture_file = line;
+ } else if( field_line_iter < original.get_height() ) {
+ if( DEBUG ) {
+ log_print("maze: read line y = %d, len = %zd: %s\n", field_line_iter, line.length(), line.c_str());
+ }
+ if( line.length() == (size_t)original.get_width() ) {
+ for(int x=0; x<original.get_width(); ++x) {
+ const char c = line[x];
+ switch( c ) {
+ case '_':
+ original.add_tile(tile_t::EMPTY);
+ break;
+ case '|':
+ original.add_tile(tile_t::WALL);
+ break;
+ case '-':
+ original.add_tile(tile_t::GATE);
+ break;
+ case '.':
+ original.add_tile(tile_t::PELLET);
+ break;
+ case '*':
+ original.add_tile(tile_t::PELLET_POWER);
+ break;
+ default:
+ log_print("maze error: unknown tile @ %d / %d: '%c'\n", x, field_line_iter, c);
+ break;
+ }
+ }
+ }
+ ++field_line_iter;
+ }
+ }
+ file.close();
+ if( original.validate_size() ) {
+ reset();
+ return; // OK
+ }
+ } else {
+ log_print("Could not open maze file: %s\n", filename.c_str());
+ }
+ original.clear();
+ pacman_start_pos.set_pos(0, 0);
+ ghost_home_pos.set_pos(0, 0);
+ ghost_start_pos.set_pos(0, 0);
+ ppt_x = 0;
+ ppt_y = 0;
+}
+
+void maze_t::draw(std::function<void(const int x, const int y, tile_t tile)> draw_pixel) {
+ for(int y=0; y<get_height(); ++y) {
+ for(int x=0; x<get_width(); ++x) {
+ draw_pixel(x, y, active.get_tile_nc(x, y));
+ }
+ }
+}
+
+void maze_t::reset() {
+ active = original;
+}
+
+std::string maze_t::toString() const {
+ std::string errstr = is_ok() ? "ok" : "error";
+ return filename+"["+errstr+", "+active.toString()+
+ ", pacman "+pacman_start_pos.toShortString()+
+ ", ghost[box "+ghost_home.toString()+", home "+ghost_home_pos.toShortString()+
+ ", start "+ghost_start_pos.toShortString()+
+ "], tex "+texture_file+
+ ", ppt "+std::to_string(ppt_x)+"x"+std::to_string(ppt_y)+
+ "]";
+}
diff --git a/src/pacman.cpp b/src/pacman.cpp
new file mode 100644
index 0000000..a28f826
--- /dev/null
+++ b/src/pacman.cpp
@@ -0,0 +1,276 @@
+/*
+ * Author: Sven Gothel <[email protected]> and Svenson Han Gothel
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * 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.
+ */
+#include <pacman/game.hpp>
+#include <pacman/globals.hpp>
+
+#include <limits>
+
+#include <cstdio>
+#include <time.h>
+
+static constexpr const bool DEBUG_GFX_BOUNDS = false;
+
+//
+// pacman_t
+//
+
+animtex_t& pacman_t::get_tex() {
+ switch( mode ) {
+ case pacman_t::mode_t::HOME:
+ return atex_home;
+ case pacman_t::mode_t::NORMAL:
+ [[fallthrough]];
+ case pacman_t::mode_t::POWERED:
+ switch( dir_ ) {
+ case direction_t::DOWN:
+ return atex_down;
+
+ case direction_t::RIGHT:
+ return atex_right;
+
+ case direction_t::UP:
+ return atex_up;
+
+ case direction_t::LEFT:
+ [[fallthrough]];
+ default:
+ return atex_left;
+ }
+ case pacman_t::mode_t::DEAD:
+ return atex_dead;
+ default:
+ return atex_left;
+ }
+}
+
+pacman_t::pacman_t(SDL_Renderer* rend, const float fields_per_sec_, bool auto_move_)
+: fields_per_sec( fields_per_sec_ ),
+ auto_move(auto_move_),
+ mode( mode_t::HOME ),
+ mode_ms_left ( number( mode_duration_t::HOMESTAY ) ),
+ lives( 3 ),
+ dir_( direction_t::LEFT ),
+ last_dir( dir_ ),
+ frame_count ( 0 ),
+ steps_left( auto_move ? -1 : 0),
+ score( 0 ),
+ atex_left( "L", rend, ms_per_tex, global_tex->get_all_images(), 0, 28, 13, 13, { { 0*13, 0 }, { 1*13, 0 } }),
+ atex_right("R", rend, ms_per_tex, global_tex->get_all_images(), 0, 28, 13, 13, { { 2*13, 0 }, { 3*13, 0 } }),
+ atex_up( "U", rend, ms_per_tex, global_tex->get_all_images(), 0, 28, 13, 13, { { 4*13, 0 }, { 5*13, 0 } }),
+ atex_down( "D", rend, ms_per_tex, global_tex->get_all_images(), 0, 28, 13, 13, { { 6*13, 0 }, { 7*13, 0 } }),
+ atex_dead( "X", rend, ms_per_tex, global_tex->get_all_images(), 0, 14, 14, 14, {
+ { 0*14, 0 }, { 1*14, 0 }, { 2*14, 0 }, { 3*14, 0 }, { 4*14, 0 }, { 5*14, 0 },
+ { 6*14, 0 }, { 7*14, 0 }, { 8*14, 0 }, { 9*14, 0 }, { 10*14, 0 }, { 11*14, 0 } }),
+ atex_home( "H", ms_per_tex, { atex_dead.get_tex(0) }),
+ atex( &get_tex() ),
+ pos( pacman_maze->get_pacman_start_pos() )
+{ }
+
+void pacman_t::destroy() {
+ atex_left.destroy();
+ atex_right.destroy();
+ atex_up.destroy();
+ atex_down.destroy();
+ atex_dead.destroy();
+ atex_home.destroy();
+}
+
+void pacman_t::set_mode(const mode_t m) {
+ const mode_t old_mode = mode;
+ switch( m ) {
+ case mode_t::HOME:
+ mode = m;
+ mode_ms_left = number( mode_duration_t::HOMESTAY );
+ pos = pacman_maze->get_pacman_start_pos();
+ for(ghost_ref g : ghosts) {
+ g->set_mode(ghost_t::mode_t::HOME);
+ }
+ break;
+ case mode_t::NORMAL:
+ mode = m;
+ mode_ms_left = -1;
+ perf_fields_walked_t0 = getCurrentMilliseconds();
+ pos.reset_stats();
+ break;
+ case mode_t::POWERED:
+ mode = m;
+ mode_ms_left = number( mode_duration_t::INPOWER );
+ for(ghost_ref g : ghosts) {
+ g->set_mode(ghost_t::mode_t::SCARED);
+ }
+ break;
+ case mode_t::DEAD:
+ mode = m;
+ mode_ms_left = number( mode_duration_t::DEADANIM );
+ atex_dead.reset();
+ for(ghost_ref g : ghosts) {
+ g->set_mode(ghost_t::mode_t::AWAY);
+ }
+ break;
+ default:
+ mode = m;
+ mode_ms_left = -1;
+ break;
+ }
+ log_print("pacman set_mode: %s -> %s [%d ms], %s\n", to_string(old_mode).c_str(), to_string(mode).c_str(), mode_ms_left, pos.toShortString().c_str());
+}
+
+void pacman_t::set_dir(direction_t dir) {
+ const bool collision_maze = !pos.test(*pacman_maze, dir, [](direction_t d, int x, int y, tile_t tile) -> bool {
+ (void)d; (void)x; (void)y; return tile_t::WALL == tile || tile_t::GATE == tile;
+ });
+ if( !collision_maze ) {
+ if( !auto_move ) {
+ ++steps_left;
+ }
+ dir_ = dir;
+ perf_fields_walked_t0 = getCurrentMilliseconds();
+ pos.reset_stats();
+ }
+}
+
+bool pacman_t::tick() {
+ ++frame_count;
+
+ atex = &get_tex();
+ atex->tick();
+
+ bool collision_maze = false;
+ bool collision_enemies = false;
+
+ if( 0 < mode_ms_left ) {
+ mode_ms_left = std::max( 0, mode_ms_left - get_ms_per_frame() );
+ }
+
+ if( mode_t::HOME == mode ) {
+ if( 0 == mode_ms_left ) {
+ set_mode( mode_t::NORMAL );
+ }
+ } else if( mode_t::DEAD == mode ) {
+ if( 0 == mode_ms_left ) {
+ set_mode( mode_t::HOME );
+ }
+ } else {
+ // NORMAL and POWERED
+
+ if( mode_t::POWERED == mode ) {
+ if( 0 == mode_ms_left ) {
+ set_mode( mode_t::NORMAL );
+ }
+ }
+
+ /**
+ * Pacman's position depends on:
+ * - its direction
+ * - speed (change_per_tick)
+ * - environment
+ */
+ if( auto_move || 0 < steps_left ) {
+ if( !auto_move ) {
+ --steps_left;
+ }
+ collision_maze = !pos.step(*pacman_maze, dir_, fields_per_sec, get_frames_per_sec(), [&](direction_t d, int x, int y, tile_t tile) -> bool {
+ (void)d;
+ if( tile_t::PELLET <= tile && tile <= tile_t::KEY ) {
+ score += ::number( tile_to_score(tile) );
+ pacman_maze->set_tile(x, y, tile_t::EMPTY);
+ if( tile_t::PELLET_POWER == tile ) {
+ set_mode( mode_t::POWERED );
+ }
+ if( 0 == pacman_maze->get_count( tile_t::PELLET ) && 0 == pacman_maze->get_count( tile_t::PELLET_POWER ) ) {
+ pacman_maze->reset(); // FIXME: Actual end of level ...
+ }
+ return false;
+ }
+ return tile_t::WALL == tile || tile_t::GATE == tile;
+ });
+ if( collision_maze ) {
+ uint64_t t1 = getCurrentMilliseconds();
+ if( pos.get_fields_walked_i() > 0 ) {
+ const float fps = get_fps(perf_fields_walked_t0, t1, pos.get_fields_walked_f());
+ log_print("pacman: fields %.2f/s, td %" PRIu64 " ms, %s\n", fps, t1-perf_fields_walked_t0, pos.toString().c_str());
+ }
+ pos.reset_stats();
+ perf_fields_walked_t0 = getCurrentMilliseconds();
+ }
+ }
+ // Collision test with ghosts
+ for(ghost_ref g : ghosts) {
+ if( pos.intersects(g->get_pos()) ) {
+ const ghost_t::mode_t g_mode = g->get_mode();
+ if( ghost_t::mode_t::CHASE <= g_mode && g_mode <= ghost_t::mode_t::SCATTER ) {
+ collision_enemies = true;
+ } else if( ghost_t::mode_t::SCARED == g_mode ) {
+ score += ::number( score_t::GHOST_1 ); // FIXME
+ g->set_mode( ghost_t::mode_t::PHANTOM );
+ }
+ }
+ }
+
+ if( collision_enemies ) {
+ set_mode( mode_t::DEAD );
+ }
+ }
+
+ if( DEBUG_GFX_BOUNDS ) {
+ log_print("tick: frame %3.3d, %s, %s, crash[maze %d, ghosts %d], textures %s\n",
+ frame_count, to_string(dir_).c_str(), pos.toString().c_str(), collision_maze, collision_enemies, atex->toString().c_str());
+ }
+ return !collision_enemies;
+}
+
+void pacman_t::draw(SDL_Renderer* rend) {
+ atex->draw(rend, pos.get_x_f(), pos.get_y_f(), true /* maze_offset */);
+
+ if( DEBUG_GFX_BOUNDS ) {
+ uint8_t r, g, b, a;
+ SDL_GetRenderDrawColor(rend, &r, &g, &b, &a);
+ SDL_SetRenderDrawColor(rend, 0, 255, 0, 255);
+ SDL_Rect bounds = { .x=pacman_maze->x_to_pixel(pos.get_x_f(), win_pixel_scale, true), .y=pacman_maze->y_to_pixel(pos.get_y_f(), win_pixel_scale, true),
+ .w=atex->get_width()*win_pixel_scale, .h=atex->get_height()*win_pixel_scale};
+ SDL_RenderDrawRect(rend, &bounds);
+ SDL_SetRenderDrawColor(rend, r, g, b, a);
+ }
+}
+
+std::string pacman_t::toString() const {
+ return "pacman["+to_string(mode)+"["+std::to_string(mode_ms_left)+" ms], "+to_string(dir_)+", "+pos.toString()+", "+atex->toString()+"]";
+}
+
+
+std::string to_string(pacman_t::mode_t m) {
+ switch( m ) {
+ case pacman_t::mode_t::HOME:
+ return "home";
+ case pacman_t::mode_t::NORMAL:
+ return "normal";
+ case pacman_t::mode_t::POWERED:
+ return "powered";
+ case pacman_t::mode_t::DEAD:
+ return "dead";
+ default:
+ return "unknown";
+ }
+}
+
diff --git a/src/utils.cpp b/src/utils.cpp
new file mode 100644
index 0000000..6f94bb7
--- /dev/null
+++ b/src/utils.cpp
@@ -0,0 +1,142 @@
+/*
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * 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.
+ */
+#include <pacman/utils.hpp>
+
+#include <string>
+#include <cstdio>
+#include <cinttypes>
+#include <cmath>
+
+#include <ctime>
+
+static const uint64_t NanoPerMilli = 1000000UL;
+static const uint64_t MilliPerOne = 1000UL;
+
+//
+// Cut from jaulib
+//
+#include <type_traits>
+
+template <typename T>
+constexpr ssize_t sign(const T x) noexcept
+{
+ return (T(0) < x) - (x < T(0));
+}
+
+template <typename T>
+constexpr T invert_sign(const T x) noexcept
+{
+ return std::numeric_limits<T>::min() == x ? std::numeric_limits<T>::max() : -x;
+}
+
+template<typename T>
+constexpr size_t digits10(const T x, const ssize_t x_sign, const bool sign_is_digit=true) noexcept
+{
+ if( x_sign == 0 ) {
+ return 1;
+ }
+ if( x_sign < 0 ) {
+ return 1 + static_cast<size_t>( std::log10<T>( invert_sign<T>( x ) ) ) + ( sign_is_digit ? 1 : 0 );
+ } else {
+ return 1 + static_cast<size_t>( std::log10<T>( x ) );
+ }
+}
+
+template< class value_type,
+ std::enable_if_t< std::is_integral_v<value_type>,
+ bool> = true>
+std::string to_decstring(const value_type& v, const char separator=',', const size_t width=0) noexcept {
+ const ssize_t v_sign = sign<value_type>(v);
+ const size_t digit10_count1 = digits10<value_type>(v, v_sign, true /* sign_is_digit */);
+ const size_t digit10_count2 = v_sign < 0 ? digit10_count1 - 1 : digit10_count1; // less sign
+
+ const size_t comma_count = 0 == separator ? 0 : ( digit10_count1 - 1 ) / 3;
+ const size_t net_chars = digit10_count1 + comma_count;
+ const size_t total_chars = std::max<size_t>(width, net_chars);
+ std::string res(total_chars, ' ');
+
+ value_type n = v;
+ size_t char_iter = 0;
+
+ for(size_t digit10_iter = 0; digit10_iter < digit10_count2 /* && char_iter < total_chars */; digit10_iter++ ) {
+ const int digit = v_sign < 0 ? invert_sign( n % 10 ) : n % 10;
+ n /= 10;
+ if( 0 < digit10_iter && 0 == digit10_iter % 3 ) {
+ res[total_chars-1-(char_iter++)] = separator;
+ }
+ res[total_chars-1-(char_iter++)] = '0' + digit;
+ }
+ if( v_sign < 0 /* && char_iter < total_chars */ ) {
+ res[total_chars-1-(char_iter++)] = '-';
+ }
+ return res;
+}
+
+
+//
+// utils.hpp
+//
+
+/**
+ * See <http://man7.org/linux/man-pages/man2/clock_gettime.2.html>
+ * <p>
+ * Regarding avoiding kernel via VDSO,
+ * see <http://man7.org/linux/man-pages/man7/vdso.7.html>,
+ * clock_gettime seems to be well supported at least on kernel >= 4.4.
+ * Only bfin and sh are missing, while ia64 seems to be complicated.
+ */
+uint64_t getCurrentMilliseconds() noexcept {
+ struct timespec t;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ return static_cast<uint64_t>( t.tv_sec ) * MilliPerOne +
+ static_cast<uint64_t>( t.tv_nsec ) / NanoPerMilli;
+}
+
+static uint64_t _exe_start_time = getCurrentMilliseconds();
+
+uint64_t getElapsedMillisecond() noexcept {
+ return getCurrentMilliseconds() - _exe_start_time;
+}
+
+float get_fps(const uint64_t t0, const uint64_t t1, const float event_count) {
+ const uint64_t td_ms = t1 - t0;
+ float fps;
+ if( td_ms > 0 ) {
+ fps = ( event_count / (float)td_ms ) * 1000.0;
+ } else {
+ fps = 0;
+ }
+ return fps;
+}
+
+void log_print(const char * format, ...) noexcept {
+ fprintf(stderr, "[%s] ", to_decstring(getElapsedMillisecond(), ',', 9).c_str());
+ va_list args;
+ va_start (args, format);
+ vfprintf(stderr, format, args);
+ va_end (args);
+}
+
+
+