diff options
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=""${workspace_loc:/pacman/include}""/> + </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=""${workspace_loc:/pacman/include}""/> + </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=""${workspace_loc:/pacman/include}""/> + </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=""${workspace_loc:/pacman/include}""/> + </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=""${workspace_loc:/pacman/include}""/> + </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=""${workspace_loc:/pacman/include}""/> + </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><project>/.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><project>/.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 "${INPUTS}"" 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 "${INPUTS}"" 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 @@ -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 Binary files differnew file mode 100644 index 0000000..66e19ec --- /dev/null +++ b/fonts/freefont/FreeSansBold.ttf 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 Binary files differnew file mode 100644 index 0000000..471af7a --- /dev/null +++ b/media/playfield_pacman.png 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 Binary files differnew file mode 100644 index 0000000..5308bb1 --- /dev/null +++ b/media/tiles_all.png 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); +} + + + |