├── .clang-format ├── .clang-tidy ├── .cmake-format.yaml ├── CMakeLists.txt ├── LICENSE ├── cmake ├── Cache.cmake ├── CompilerWarnings.cmake ├── Conan.cmake ├── Doxygen.cmake ├── PreventInSourceBuilds.cmake ├── Sanitizers.cmake ├── StandardProjectSettings.cmake └── StaticAnalyzers.cmake ├── examples ├── 16bit_counter.cpp ├── 16bit_counter_with_map_and_strings.cpp ├── hello_commodore.cpp ├── hello_x16.cpp ├── pong.cpp ├── pong_templates.cpp ├── simple_game │ ├── 6502.hpp │ ├── CMakeLists.txt │ ├── chargen.hpp │ ├── commodore64.hpp │ ├── game.cpp │ ├── geometry.hpp │ ├── petscii.hpp │ ├── vicii.hpp │ └── x16.hpp ├── test.asm ├── test.c ├── test.cpp ├── test2.cpp ├── test3.cpp ├── test_lambda.cpp └── test_mod.cpp ├── include ├── 6502.hpp ├── assembly.hpp ├── lib1funcs.hpp ├── np_int.hpp ├── optimizer.hpp ├── personalities │ ├── c64.hpp │ └── x16.hpp └── personality.hpp ├── readme.md ├── src ├── 6502-c++.cpp └── CMakeLists.txt └── test ├── CMakeLists.txt ├── catch_main.cpp ├── constexpr_tests.cpp └── tests.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | AccessModifierOffset: -2 2 | AlignAfterOpenBracket: DontAlign 3 | AlignConsecutiveAssignments: false 4 | AlignConsecutiveDeclarations: false 5 | AlignEscapedNewlines: Left 6 | AlignOperands: true 7 | AlignTrailingComments: false 8 | AllowAllParametersOfDeclarationOnNextLine: false 9 | AllowShortBlocksOnASingleLine: Always 10 | AllowShortCaseLabelsOnASingleLine: true 11 | AllowShortFunctionsOnASingleLine: All 12 | AllowShortIfStatementsOnASingleLine: WithoutElse 13 | AllowShortLoopsOnASingleLine: true 14 | AlwaysBreakAfterDefinitionReturnType: None 15 | AlwaysBreakAfterReturnType: None 16 | AlwaysBreakBeforeMultilineStrings: true 17 | AlwaysBreakTemplateDeclarations: MultiLine 18 | BinPackArguments: false 19 | BinPackParameters: false 20 | BraceWrapping: 21 | AfterCaseLabel: false 22 | AfterClass: true 23 | AfterEnum: false 24 | AfterFunction: true 25 | AfterNamespace: false 26 | AfterObjCDeclaration: false 27 | AfterStruct: true 28 | AfterUnion: false 29 | BeforeCatch: false 30 | BeforeElse: false 31 | IndentBraces: false 32 | SplitEmptyFunction: false 33 | SplitEmptyNamespace: true 34 | SplitEmptyRecord: true 35 | BreakAfterJavaFieldAnnotations: true 36 | BreakBeforeBinaryOperators: NonAssignment 37 | BreakBeforeBraces: Custom 38 | BreakBeforeInheritanceComma: true 39 | BreakBeforeTernaryOperators: true 40 | BreakConstructorInitializers: BeforeColon 41 | BreakConstructorInitializersBeforeComma: false 42 | BreakStringLiterals: true 43 | ColumnLimit: 120 44 | CommentPragmas: '^ IWYU pragma:' 45 | CompactNamespaces: false 46 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 47 | ConstructorInitializerIndentWidth: 2 48 | ContinuationIndentWidth: 2 49 | Cpp11BracedListStyle: false 50 | DerivePointerAlignment: false 51 | DisableFormat: false 52 | ExperimentalAutoDetectBinPacking: true 53 | FixNamespaceComments: true 54 | ForEachMacros: 55 | - foreach 56 | - Q_FOREACH 57 | - BOOST_FOREACH 58 | IncludeCategories: 59 | - Priority: 2 60 | Regex: ^"(llvm|llvm-c|clang|clang-c)/ 61 | - Priority: 3 62 | Regex: ^(<|"(gtest|gmock|isl|json)/) 63 | - Priority: 1 64 | Regex: .* 65 | IncludeIsMainRegex: (Test)?$ 66 | IndentCaseLabels: false 67 | IndentWidth: 2 68 | IndentWrappedFunctionNames: true 69 | JavaScriptQuotes: Leave 70 | JavaScriptWrapImports: true 71 | KeepEmptyLinesAtTheStartOfBlocks: true 72 | Language: Cpp 73 | MacroBlockBegin: '' 74 | MacroBlockEnd: '' 75 | MaxEmptyLinesToKeep: 2 76 | NamespaceIndentation: Inner 77 | ObjCBlockIndentWidth: 7 78 | ObjCSpaceAfterProperty: true 79 | ObjCSpaceBeforeProtocolList: false 80 | PointerAlignment: Right 81 | ReflowComments: true 82 | SortIncludes: true 83 | SortUsingDeclarations: false 84 | SpaceAfterCStyleCast: false 85 | SpaceAfterTemplateKeyword: false 86 | SpaceBeforeAssignmentOperators: true 87 | SpaceBeforeParens: ControlStatements 88 | SpaceInEmptyParentheses: false 89 | SpacesBeforeTrailingComments: 0 90 | SpacesInAngles: false 91 | SpacesInCStyleCastParentheses: false 92 | SpacesInContainerLiterals: true 93 | SpacesInParentheses: false 94 | SpacesInSquareBrackets: false 95 | Standard: c++20 96 | TabWidth: 8 97 | UseTab: Never 98 | 99 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: '*,-fuchsia-*,-google-*,-zircon-*,-abseil-*,-modernize-use-trailing-return-type,-llvm*,-altera*,-misc-non-private-member-variables-in-classes,-readability-else-after-return' 3 | WarningsAsErrors: '' 4 | HeaderFilterRegex: '' 5 | FormatStyle: none 6 | 7 | 8 | -------------------------------------------------------------------------------- /.cmake-format.yaml: -------------------------------------------------------------------------------- 1 | additional_commands: 2 | foo: 3 | flags: 4 | - BAR 5 | - BAZ 6 | kwargs: 7 | DEPENDS: '*' 8 | HEADERS: '*' 9 | SOURCES: '*' 10 | bullet_char: '*' 11 | dangle_parens: false 12 | enum_char: . 13 | line_ending: unix 14 | line_width: 120 15 | max_pargs_hwrap: 3 16 | separate_ctrl_name_with_space: false 17 | separate_fn_name_with_space: false 18 | tab_size: 2 19 | 20 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | # Set the project name to your project name, my project isn't very descriptive 4 | project(6502-c++ CXX) 5 | include(cmake/StandardProjectSettings.cmake) 6 | include(cmake/PreventInSourceBuilds.cmake) 7 | 8 | # Link this 'library' to set the c++ standard / compile-time options requested 9 | add_library(project_options INTERFACE) 10 | target_compile_features(project_options INTERFACE cxx_std_20) 11 | 12 | if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 13 | option(ENABLE_BUILD_WITH_TIME_TRACE "Enable -ftime-trace to generate time tracing .json files on clang" OFF) 14 | if(ENABLE_BUILD_WITH_TIME_TRACE) 15 | target_compile_options(project_options INTERFACE -ftime-trace) 16 | endif() 17 | endif() 18 | 19 | # Link this 'library' to use the warnings specified in CompilerWarnings.cmake 20 | add_library(project_warnings INTERFACE) 21 | 22 | # enable cache system 23 | include(cmake/Cache.cmake) 24 | 25 | # standard compiler warnings 26 | include(cmake/CompilerWarnings.cmake) 27 | set_project_warnings(project_warnings) 28 | 29 | # sanitizer options if supported by compiler 30 | include(cmake/Sanitizers.cmake) 31 | enable_sanitizers(project_options) 32 | 33 | # enable doxygen 34 | include(cmake/Doxygen.cmake) 35 | enable_doxygen() 36 | 37 | # allow for static analysis options 38 | include(cmake/StaticAnalyzers.cmake) 39 | 40 | option(BUILD_SHARED_LIBS "Enable compilation of shared libraries" OFF) 41 | option(ENABLE_TESTING "Enable Test Builds" ON) 42 | option(ENABLE_FUZZING "Enable Fuzzing Builds" OFF) 43 | 44 | # Very basic PCH example 45 | option(ENABLE_PCH "Enable Precompiled Headers" OFF) 46 | if(ENABLE_PCH) 47 | # This sets a global PCH parameter, each project will build its own PCH, which is a good idea if any #define's change 48 | # 49 | # consider breaking this out per project as necessary 50 | target_precompile_headers( 51 | project_options 52 | INTERFACE 53 | 54 | 55 | 56 | ) 57 | endif() 58 | 59 | # Set up some extra Conan dependencies based on our needs before loading Conan 60 | set(CONAN_EXTRA_REQUIRES "") 61 | set(CONAN_EXTRA_OPTIONS "") 62 | 63 | if(CPP_STARTER_USE_SDL) 64 | set(CONAN_EXTRA_REQUIRES ${CONAN_EXTRA_REQUIRES} sdl2/2.0.10@bincrafters/stable) 65 | # set(CONAN_EXTRA_OPTIONS ${CONAN_EXTRA_OPTIONS} sdl2:wayland=True) 66 | endif() 67 | 68 | include(cmake/Conan.cmake) 69 | run_conan() 70 | 71 | if(ENABLE_TESTING) 72 | enable_testing() 73 | message("Building Tests. Be sure to check out test/constexpr_tests for constexpr testing") 74 | add_subdirectory(test) 75 | endif() 76 | 77 | if(ENABLE_FUZZING) 78 | message("Building Fuzz Tests, using fuzzing sanitizer https://www.llvm.org/docs/LibFuzzer.html") 79 | add_subdirectory(fuzz_test) 80 | endif() 81 | 82 | add_subdirectory(src) 83 | add_subdirectory(examples/simple_game) 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /cmake/Cache.cmake: -------------------------------------------------------------------------------- 1 | option(ENABLE_CACHE "Enable cache if available" ON) 2 | if(NOT ENABLE_CACHE) 3 | return() 4 | endif() 5 | 6 | set(CACHE_OPTION 7 | "ccache" 8 | CACHE STRING "Compiler cache to be used") 9 | set(CACHE_OPTION_VALUES "ccache" "sccache") 10 | set_property(CACHE CACHE_OPTION PROPERTY STRINGS ${CACHE_OPTION_VALUES}) 11 | list( 12 | FIND 13 | CACHE_OPTION_VALUES 14 | ${CACHE_OPTION} 15 | CACHE_OPTION_INDEX) 16 | 17 | if(${CACHE_OPTION_INDEX} EQUAL -1) 18 | message( 19 | STATUS 20 | "Using custom compiler cache system: '${CACHE_OPTION}', explicitly supported entries are ${CACHE_OPTION_VALUES}") 21 | endif() 22 | 23 | find_program(CACHE_BINARY ${CACHE_OPTION}) 24 | if(CACHE_BINARY) 25 | message(STATUS "${CACHE_OPTION} found and enabled") 26 | set(CMAKE_CXX_COMPILER_LAUNCHER ${CACHE_BINARY}) 27 | else() 28 | message(WARNING "${CACHE_OPTION} is enabled but was not found. Not using it") 29 | endif() 30 | -------------------------------------------------------------------------------- /cmake/CompilerWarnings.cmake: -------------------------------------------------------------------------------- 1 | # from here: 2 | # 3 | # https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md 4 | 5 | function(set_project_warnings project_name) 6 | option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" TRUE) 7 | 8 | set(MSVC_WARNINGS 9 | /W4 # Baseline reasonable warnings 10 | /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data 11 | /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data 12 | /w14263 # 'function': member function does not override any base class virtual member function 13 | /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not 14 | # be destructed correctly 15 | /w14287 # 'operator': unsigned/negative constant mismatch 16 | /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside 17 | # the for-loop scope 18 | /w14296 # 'operator': expression is always 'boolean_value' 19 | /w14311 # 'variable': pointer truncation from 'type1' to 'type2' 20 | /w14545 # expression before comma evaluates to a function which is missing an argument list 21 | /w14546 # function call before comma missing argument list 22 | /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect 23 | /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? 24 | /w14555 # expression has no effect; expected expression with side- effect 25 | /w14619 # pragma warning: there is no warning number 'number' 26 | /w14640 # Enable warning on thread un-safe static member initialization 27 | /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior. 28 | /w14905 # wide string literal cast to 'LPSTR' 29 | /w14906 # string literal cast to 'LPWSTR' 30 | /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied 31 | /permissive- # standards conformance mode for MSVC compiler. 32 | ) 33 | 34 | set(CLANG_WARNINGS 35 | -Wall 36 | -Wextra # reasonable and standard 37 | -Wshadow # warn the user if a variable declaration shadows one from a parent context 38 | -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps 39 | # catch hard to track down memory errors 40 | -Wold-style-cast # warn for c-style casts 41 | -Wcast-align # warn for potential performance problem casts 42 | -Wunused # warn on anything being unused 43 | -Woverloaded-virtual # warn if you overload (not override) a virtual function 44 | -Wpedantic # warn if non-standard C++ is used 45 | -Wconversion # warn on type conversions that may lose data 46 | -Wsign-conversion # warn on sign conversions 47 | -Wnull-dereference # warn if a null dereference is detected 48 | -Wdouble-promotion # warn if float is implicit promoted to double 49 | -Wformat=2 # warn on security issues around functions that format output (ie printf) 50 | ) 51 | 52 | if(WARNINGS_AS_ERRORS) 53 | set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) 54 | set(MSVC_WARNINGS ${MSVC_WARNINGS} /WX) 55 | endif() 56 | 57 | set(GCC_WARNINGS 58 | ${CLANG_WARNINGS} 59 | -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist 60 | -Wduplicated-cond # warn if if / else chain has duplicated conditions 61 | -Wduplicated-branches # warn if if / else branches have duplicated code 62 | -Wlogical-op # warn about logical operations being used where bitwise were probably wanted 63 | -Wuseless-cast # warn if you perform a cast to the same type 64 | ) 65 | 66 | if(MSVC) 67 | set(PROJECT_WARNINGS ${MSVC_WARNINGS}) 68 | elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 69 | set(PROJECT_WARNINGS ${CLANG_WARNINGS}) 70 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 71 | set(PROJECT_WARNINGS ${GCC_WARNINGS}) 72 | else() 73 | message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") 74 | endif() 75 | 76 | target_compile_options(${project_name} INTERFACE ${PROJECT_WARNINGS}) 77 | 78 | endfunction() 79 | -------------------------------------------------------------------------------- /cmake/Conan.cmake: -------------------------------------------------------------------------------- 1 | macro(run_conan) 2 | # Download automatically, you can also just copy the conan.cmake file 3 | if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake") 4 | message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan") 5 | file(DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.16.1/conan.cmake" "${CMAKE_BINARY_DIR}/conan.cmake") 6 | endif() 7 | 8 | include(${CMAKE_BINARY_DIR}/conan.cmake) 9 | 10 | conan_cmake_run( 11 | REQUIRES 12 | ${CONAN_EXTRA_REQUIRES} 13 | catch2/2.13.6 14 | cli11/1.9.1 15 | fmt/7.1.3 16 | spdlog/1.8.5 17 | ctre/3.3.4 18 | OPTIONS 19 | ${CONAN_EXTRA_OPTIONS} 20 | BASIC_SETUP 21 | CMAKE_TARGETS # individual targets to link to 22 | BUILD 23 | missing) 24 | endmacro() 25 | -------------------------------------------------------------------------------- /cmake/Doxygen.cmake: -------------------------------------------------------------------------------- 1 | function(enable_doxygen) 2 | option(ENABLE_DOXYGEN "Enable doxygen doc builds of source" OFF) 3 | if(ENABLE_DOXYGEN) 4 | set(DOXYGEN_CALLER_GRAPH YES) 5 | set(DOXYGEN_CALL_GRAPH YES) 6 | set(DOXYGEN_EXTRACT_ALL YES) 7 | find_package(Doxygen REQUIRED dot) 8 | doxygen_add_docs(doxygen-docs ${PROJECT_SOURCE_DIR}) 9 | 10 | endif() 11 | endfunction() 12 | -------------------------------------------------------------------------------- /cmake/PreventInSourceBuilds.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # This function will prevent in-source builds 3 | function(AssureOutOfSourceBuilds) 4 | # make sure the user doesn't play dirty with symlinks 5 | get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) 6 | get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) 7 | 8 | # disallow in-source builds 9 | if("${srcdir}" STREQUAL "${bindir}") 10 | message("######################################################") 11 | message("Warning: in-source builds are disabled") 12 | message("Please create a separate build directory and run cmake from there") 13 | message("######################################################") 14 | message(FATAL_ERROR "Quitting configuration") 15 | endif() 16 | endfunction() 17 | 18 | assureoutofsourcebuilds() 19 | -------------------------------------------------------------------------------- /cmake/Sanitizers.cmake: -------------------------------------------------------------------------------- 1 | function(enable_sanitizers project_name) 2 | 3 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 4 | option(ENABLE_COVERAGE "Enable coverage reporting for gcc/clang" FALSE) 5 | 6 | if(ENABLE_COVERAGE) 7 | target_compile_options(${project_name} INTERFACE --coverage -O0 -g) 8 | target_link_libraries(${project_name} INTERFACE --coverage) 9 | endif() 10 | 11 | set(SANITIZERS "") 12 | 13 | option(ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" FALSE) 14 | if(ENABLE_SANITIZER_ADDRESS) 15 | list(APPEND SANITIZERS "address") 16 | endif() 17 | 18 | option(ENABLE_SANITIZER_LEAK "Enable leak sanitizer" FALSE) 19 | if(ENABLE_SANITIZER_LEAK) 20 | list(APPEND SANITIZERS "leak") 21 | endif() 22 | 23 | option(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR "Enable undefined behavior sanitizer" FALSE) 24 | if(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR) 25 | list(APPEND SANITIZERS "undefined") 26 | endif() 27 | 28 | option(ENABLE_SANITIZER_THREAD "Enable thread sanitizer" FALSE) 29 | if(ENABLE_SANITIZER_THREAD) 30 | if("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS) 31 | message(WARNING "Thread sanitizer does not work with Address and Leak sanitizer enabled") 32 | else() 33 | list(APPEND SANITIZERS "thread") 34 | endif() 35 | endif() 36 | 37 | option(ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" FALSE) 38 | if(ENABLE_SANITIZER_MEMORY AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 39 | if("address" IN_LIST SANITIZERS 40 | OR "thread" IN_LIST SANITIZERS 41 | OR "leak" IN_LIST SANITIZERS) 42 | message(WARNING "Memory sanitizer does not work with Address, Thread and Leak sanitizer enabled") 43 | else() 44 | list(APPEND SANITIZERS "memory") 45 | endif() 46 | endif() 47 | 48 | list( 49 | JOIN 50 | SANITIZERS 51 | "," 52 | LIST_OF_SANITIZERS) 53 | 54 | endif() 55 | 56 | if(LIST_OF_SANITIZERS) 57 | if(NOT 58 | "${LIST_OF_SANITIZERS}" 59 | STREQUAL 60 | "") 61 | target_compile_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) 62 | target_link_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) 63 | endif() 64 | endif() 65 | 66 | endfunction() 67 | -------------------------------------------------------------------------------- /cmake/StandardProjectSettings.cmake: -------------------------------------------------------------------------------- 1 | # Set a default build type if none was specified 2 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 3 | message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.") 4 | set(CMAKE_BUILD_TYPE 5 | RelWithDebInfo 6 | CACHE STRING "Choose the type of build." FORCE) 7 | # Set the possible values of build type for cmake-gui, ccmake 8 | set_property( 9 | CACHE CMAKE_BUILD_TYPE 10 | PROPERTY STRINGS 11 | "Debug" 12 | "Release" 13 | "MinSizeRel" 14 | "RelWithDebInfo") 15 | endif() 16 | 17 | # Generate compile_commands.json to make it easier to work with clang based tools 18 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 19 | 20 | option(ENABLE_IPO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)" OFF) 21 | 22 | if(ENABLE_IPO) 23 | include(CheckIPOSupported) 24 | check_ipo_supported( 25 | RESULT 26 | result 27 | OUTPUT 28 | output) 29 | if(result) 30 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) 31 | else() 32 | message(SEND_ERROR "IPO is not supported: ${output}") 33 | endif() 34 | endif() 35 | if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 36 | add_compile_options(-fcolor-diagnostics) 37 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 38 | add_compile_options(-fdiagnostics-color=always) 39 | else() 40 | message(STATUS "No colored compiler diagnostic set for '${CMAKE_CXX_COMPILER_ID}' compiler.") 41 | endif() 42 | 43 | -------------------------------------------------------------------------------- /cmake/StaticAnalyzers.cmake: -------------------------------------------------------------------------------- 1 | option(ENABLE_CPPCHECK "Enable static analysis with cppcheck" OFF) 2 | option(ENABLE_CLANG_TIDY "Enable static analysis with clang-tidy" OFF) 3 | option(ENABLE_INCLUDE_WHAT_YOU_USE "Enable static analysis with include-what-you-use" OFF) 4 | 5 | if(ENABLE_CPPCHECK) 6 | find_program(CPPCHECK cppcheck) 7 | if(CPPCHECK) 8 | set(CMAKE_CXX_CPPCHECK 9 | ${CPPCHECK} 10 | --suppress=missingInclude 11 | --enable=all 12 | --inline-suppr 13 | --inconclusive 14 | -i 15 | ${CMAKE_SOURCE_DIR}/imgui/lib) 16 | else() 17 | message(SEND_ERROR "cppcheck requested but executable not found") 18 | endif() 19 | endif() 20 | 21 | if(ENABLE_CLANG_TIDY) 22 | find_program(CLANGTIDY clang-tidy) 23 | if(CLANGTIDY) 24 | set(CMAKE_CXX_CLANG_TIDY ${CLANGTIDY} -extra-arg=-Wno-unknown-warning-option) 25 | else() 26 | message(SEND_ERROR "clang-tidy requested but executable not found") 27 | endif() 28 | endif() 29 | 30 | if(ENABLE_INCLUDE_WHAT_YOU_USE) 31 | find_program(INCLUDE_WHAT_YOU_USE include-what-you-use) 32 | if(INCLUDE_WHAT_YOU_USE) 33 | set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE}) 34 | else() 35 | message(SEND_ERROR "include-what-you-use requested but executable not found") 36 | endif() 37 | endif() 38 | -------------------------------------------------------------------------------- /examples/16bit_counter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | enum Colors : uint8_t { WHITE = 0x01 }; 8 | 9 | inline volatile uint8_t &memory_loc(const uint16_t loc) { 10 | return *reinterpret_cast(loc); 11 | } 12 | 13 | inline void poke(const uint16_t loc, const uint8_t value) { 14 | memory_loc(loc) = value; 15 | } 16 | 17 | inline std::uint8_t peek(const std::uint16_t loc) { return memory_loc(loc); } 18 | 19 | inline void decrement_border_color() { --memory_loc(0xd020); } 20 | 21 | inline void increment_border_color() { ++memory_loc(0xd020); } 22 | 23 | inline bool joystick_down() { 24 | uint8_t joystick_state = memory_loc(0xDC00); 25 | return (joystick_state & 2) == 0; 26 | } 27 | 28 | void use_data(std::array &data); 29 | 30 | inline void puts(uint8_t x, uint8_t y, std::string_view str) { 31 | const auto start = 0x400 + (y * 40 + x); 32 | 33 | std::memcpy(const_cast(&memory_loc(start)), str.data(), 34 | str.size()); 35 | } 36 | 37 | inline void putc(uint8_t x, uint8_t y, uint8_t c) { 38 | const auto start = 0x400 + (y * 40 + x); 39 | poke(start, c); 40 | } 41 | 42 | inline void put_hex(uint8_t x, uint8_t y, uint8_t value) { 43 | const auto put_nibble = [](auto x, auto y, uint8_t nibble) { 44 | if (nibble <= 9) { 45 | putc(x, y, nibble + 48); 46 | } else { 47 | putc(x, y, nibble - 9); 48 | } 49 | }; 50 | 51 | put_nibble(x + 1, y, 0xF & value); 52 | put_nibble(x, y, 0xF & (value >> 4)); 53 | } 54 | 55 | inline void put_hex(uint8_t x, uint8_t y, uint16_t value) { 56 | put_hex(x+2,y, static_cast(0xFF & value)); 57 | put_hex(x,y, static_cast(0xFF & (value >> 8))); 58 | } 59 | 60 | struct Clock { 61 | using milliseconds = std::chrono::duration; 62 | 63 | // return elapsed time since last restart 64 | [[nodiscard]] milliseconds restart() { 65 | // stop Timer A 66 | poke(0xDC0E, 0b00000000); 67 | 68 | // last value 69 | const auto previous_value = static_cast( 70 | peek(0xDC04) | (static_cast(peek(0xDC05)) << 8)); 71 | 72 | // reset timer 73 | poke(0xDC04, 0xFF); 74 | poke(0xDC05, 0xFF); 75 | 76 | // restart timer A 77 | poke(0xDC0E, 0b00010001); 78 | 79 | return milliseconds{0xFFFF - previous_value}; 80 | } 81 | 82 | Clock() { [[maybe_unused]] const auto value = restart(); } 83 | }; 84 | 85 | int main() { 86 | 87 | // static constexpr std::array data{0}; 88 | // std::memcpy(const_cast(&memory_loc(0x400)), data.data(), 89 | // data.size()); 90 | 91 | /* 92 | puts(5, 5, "hello world"); 93 | puts(10, 10, "hellooooo world"); 94 | */ 95 | Clock game_clock{}; 96 | 97 | 98 | std::uint16_t counter = 0; 99 | 100 | std::uint8_t y = 15; 101 | while (true) { 102 | const auto us_elapsed = game_clock.restart().count(); 103 | 104 | put_hex(5, y, us_elapsed); 105 | put_hex(11, y, counter); 106 | 107 | if (y++ == 20) { 108 | y = 15; 109 | } 110 | 111 | ++counter; 112 | increment_border_color(); 113 | } 114 | 115 | /* 116 | const auto background_color = [](Colors col) { 117 | memory_loc(0xd021) = static_cast(col); 118 | }; 119 | 120 | background_color(Colors::WHITE); 121 | 122 | while(true) { 123 | if (joystick_down()) { 124 | increment_border_color(); 125 | } else { 126 | decrement_border_color(); 127 | } 128 | } 129 | */ 130 | } 131 | -------------------------------------------------------------------------------- /examples/16bit_counter_with_map_and_strings.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | enum Colors : uint8_t { WHITE = 0x01 }; 8 | 9 | static volatile uint8_t &memory_loc(const uint16_t loc) { 10 | return *reinterpret_cast(loc); 11 | } 12 | 13 | static void poke(const uint16_t loc, const uint8_t value) { 14 | memory_loc(loc) = value; 15 | } 16 | 17 | static std::uint8_t peek(const std::uint16_t loc) { return memory_loc(loc); } 18 | 19 | static void decrement_border_color() { --memory_loc(0xd020); } 20 | 21 | static void increment_border_color() { ++memory_loc(0xd020); } 22 | 23 | static bool joystick_down() { 24 | uint8_t joystick_state = peek(0xDC00); 25 | return (joystick_state & 2) == 0; 26 | } 27 | 28 | void use_data(std::array &data); 29 | 30 | static void puts(uint8_t x, uint8_t y, std::string_view str) { 31 | const auto start = 0x400 + (y * 40 + x); 32 | 33 | std::memcpy(const_cast(&memory_loc(start)), str.data(), 34 | str.size()); 35 | } 36 | 37 | 38 | template 39 | struct Graphic 40 | { 41 | std::array data; 42 | 43 | static constexpr auto width() noexcept { 44 | return Width; 45 | } 46 | 47 | static constexpr auto height() noexcept { 48 | return Height; 49 | } 50 | 51 | constexpr Graphic() = default; 52 | 53 | constexpr Graphic(std::array data_) noexcept : data(data_) {} 54 | constexpr Graphic(std::initializer_list data_) noexcept { 55 | std::copy(begin(data_), end(data_), begin(data)); 56 | } 57 | 58 | constexpr auto &operator()(const std::uint8_t x, const std::uint8_t y) noexcept { 59 | return data[y * Width + x]; 60 | } 61 | 62 | constexpr const auto &operator()(const std::uint8_t x, const std::uint8_t y) const noexcept { 63 | return data[y * Width + x]; 64 | } 65 | }; 66 | 67 | static void putc(uint8_t x, uint8_t y, uint8_t c) { 68 | const auto start = 0x400 + (y * 40 + x); 69 | poke(start, c); 70 | } 71 | 72 | static void put_hex(uint8_t x, uint8_t y, uint8_t value) { 73 | const auto put_nibble = [](auto x, auto y, uint8_t nibble) { 74 | if (nibble <= 9) { 75 | putc(x, y, nibble + 48); 76 | } else { 77 | putc(x, y, nibble - 9); 78 | } 79 | }; 80 | 81 | put_nibble(x + 1, y, 0xF & value); 82 | put_nibble(x, y, 0xF & (value >> 4)); 83 | } 84 | 85 | static void put_hex(uint8_t x, uint8_t y, uint16_t value) { 86 | put_hex(x+2,y, static_cast(0xFF & value)); 87 | put_hex(x,y, static_cast(0xFF & (value >> 8))); 88 | } 89 | 90 | static void put_graphic(uint8_t x, uint8_t y, const auto &graphic) 91 | { 92 | for (uint8_t cur_y = 0; cur_y < graphic.height(); ++cur_y) { 93 | for (uint8_t cur_x = 0; cur_x < graphic.width(); ++cur_x) { 94 | putc(cur_x + x, cur_y + y, graphic(cur_x, cur_y)); 95 | } 96 | } 97 | } 98 | 99 | struct Clock { 100 | using milliseconds = std::chrono::duration; 101 | 102 | // return elapsed time since last restart 103 | [[nodiscard]] milliseconds restart() { 104 | // stop Timer A 105 | poke(0xDC0E, 0b00000000); 106 | 107 | // last value 108 | const auto previous_value = static_cast( 109 | peek(0xDC04) | (static_cast(peek(0xDC05)) << 8)); 110 | 111 | // reset timer 112 | poke(0xDC04, 0xFF); 113 | poke(0xDC05, 0xFF); 114 | 115 | // restart timer A 116 | poke(0xDC0E, 0b00010001); 117 | 118 | return milliseconds{0xFFFF - previous_value}; 119 | } 120 | 121 | Clock() { [[maybe_unused]] const auto value = restart(); } 122 | }; 123 | 124 | int main() { 125 | 126 | // static constexpr std::array data{0}; 127 | // std::memcpy(const_cast(&memory_loc(0x400)), data.data(), 128 | // data.size()); 129 | 130 | static constexpr auto pic = 131 | Graphic<5,4>{ 132 | 78,119,77,32,32, 133 | 101,32,32,80,32, 134 | 101,79,101,103,32, 135 | 76,101,76,122,88 136 | }; 137 | 138 | static constexpr auto map1 = 139 | Graphic<4, 2>{ 140 | 1,0,1,0, 141 | 1,1,1,1 142 | }; 143 | 144 | static constexpr auto map2 = 145 | Graphic<6, 3>{ 146 | 1,0,1,0,0,0, 147 | 1,1,1,1,0,1, 148 | 0,0,0,1,0,0 149 | }; 150 | 151 | /* 152 | static constexpr auto map = 153 | Graphic<10, 2>{ 154 | 0,1,0,1,0,0,0,1,0,0, 155 | 1,0,0,0,0,0,1,0,0,0, 156 | }; 157 | */ 158 | // put_graphic(10,10,pic); 159 | 160 | 161 | const auto draw_map = [](const auto &map) { 162 | for (std::uint8_t y=0; y < map.height(); ++y) { 163 | for (std::uint8_t x = 0; x < map.width(); ++x) { 164 | if (map(x, y) == 1) { 165 | put_graphic(x*4, y*4, pic); 166 | } 167 | } 168 | } 169 | }; 170 | 171 | puts(5, 17, "timing history"); 172 | puts(21, 17, "16bit counter"); 173 | 174 | 175 | // draw_map(map1); 176 | draw_map(map2); 177 | 178 | 179 | Clock game_clock{}; 180 | 181 | 182 | std::uint16_t counter = 0; 183 | std::uint8_t y = 19; 184 | 185 | while (true) { 186 | const auto us_elapsed = game_clock.restart().count(); 187 | 188 | put_hex(5, y, us_elapsed); 189 | put_hex(21, y, counter); 190 | 191 | if (y++ == 24) { 192 | y = 19; 193 | } 194 | 195 | ++counter; 196 | increment_border_color(); 197 | } 198 | 199 | /* 200 | const auto background_color = [](Colors col) { 201 | memory_loc(0xd021) = static_cast(col); 202 | }; 203 | 204 | background_color(Colors::WHITE); 205 | 206 | while(true) { 207 | if (joystick_down()) { 208 | increment_border_color(); 209 | } else { 210 | decrement_border_color(); 211 | } 212 | } 213 | */ 214 | } 215 | -------------------------------------------------------------------------------- /examples/hello_commodore.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | enum Colors : uint8_t { WHITE = 0x01 }; 8 | 9 | static volatile uint8_t &memory_loc(const uint16_t loc) { 10 | return *reinterpret_cast(loc); 11 | } 12 | 13 | static void poke(const uint16_t loc, const uint8_t value) { 14 | memory_loc(loc) = value; 15 | } 16 | 17 | static std::uint8_t peek(const std::uint16_t loc) { return memory_loc(loc); } 18 | 19 | static void puts(uint8_t x, uint8_t y, std::string_view str) { 20 | const auto start = 0x400 + (y * 40 + x); 21 | 22 | std::memcpy(const_cast(&memory_loc(start)), str.data(), 23 | str.size()); 24 | } 25 | 26 | int main() { 27 | puts(15, 10, "hello commodore!"); 28 | } 29 | 30 | -------------------------------------------------------------------------------- /examples/hello_x16.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() 4 | { 5 | x16::vera::puts({15,20}, petscii::PETSCII("HELLO X16 FROM C++!")); 6 | } 7 | 8 | -------------------------------------------------------------------------------- /examples/pong.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | namespace { 9 | volatile uint8_t &memory(const uint16_t loc) 10 | { 11 | return *reinterpret_cast(loc); 12 | } 13 | 14 | template 15 | auto square(T t) { 16 | return t * t; 17 | } 18 | 19 | constexpr bool test_bit(const uint8_t data, const uint8_t bit) 20 | { 21 | return (data & (1 << bit)) != 0; 22 | }; 23 | 24 | struct Color 25 | { 26 | uint8_t num; 27 | uint8_t r; 28 | uint8_t g; 29 | uint8_t b; 30 | }; 31 | 32 | struct JoyStick 33 | { 34 | static constexpr uint16_t JOYSTICK_PORT_A = 56320; // joystick #2 35 | static constexpr uint16_t JOYSTICK_PORT_B = 56321; // joystick #1 36 | 37 | struct PortData 38 | { 39 | uint8_t data; 40 | }; 41 | 42 | JoyStick(const PortData d) 43 | : up(!test_bit(d.data, 0)), 44 | down(!test_bit(d.data, 1)), 45 | left(!test_bit(d.data, 2)), 46 | right(!test_bit(d.data, 3)), 47 | fire(!test_bit(d.data, 4)) 48 | { 49 | } 50 | 51 | JoyStick(const uint8_t port_num) 52 | : JoyStick(PortData{port_num==2?memory(JOYSTICK_PORT_A):memory(JOYSTICK_PORT_B)}) 53 | { 54 | } 55 | 56 | auto direction_vector() const 57 | { 58 | return std::make_pair(left?-1:(right?1:0), up?-1:(down?1:0)); 59 | } 60 | 61 | bool up,down,left,right,fire; 62 | }; 63 | 64 | /// 65 | /// New Code 66 | /// 67 | template 68 | auto operator+=(std::pair lhs, const std::pair &rhs) 69 | { 70 | lhs.first += rhs.first; 71 | lhs.second += rhs.second; 72 | return lhs; 73 | } 74 | 75 | template 76 | decltype(auto) operator*=(std::pair &lhs, const std::pair &rhs) 77 | { 78 | lhs.first *= rhs.first; 79 | lhs.second *= rhs.second; 80 | return (lhs); 81 | } 82 | 83 | void set_bit(const uint16_t loc, const uint8_t bitnum, bool val) 84 | { 85 | if (val) { 86 | memory(loc) |= (1 << bitnum); 87 | } else { 88 | memory(loc) &= (0xFF ^ (1 << bitnum)); 89 | } 90 | } 91 | 92 | struct Player 93 | { 94 | Player(const uint8_t num, std::pair sprite_pos, 95 | const std::pair &start_pos) 96 | : player_num(num), 97 | pos(sprite_pos) 98 | { 99 | pos = start_pos; 100 | } 101 | 102 | void update_position() 103 | { 104 | pos.second += JoyStick(player_num).direction_vector().second * 3; 105 | }; 106 | 107 | void scored() { 108 | ++score; 109 | } 110 | 111 | const uint8_t player_num; 112 | std::pair pos; 113 | uint8_t score = '0'; 114 | }; 115 | /// 116 | /// End New Code 117 | /// 118 | 119 | 120 | 121 | 122 | struct VIC_II 123 | { 124 | static constexpr uint8_t SPRITE_ALIGNMENT = 64; 125 | static constexpr uint16_t SPRITE_DATA_POINTERS = 2040; 126 | static constexpr uint16_t VIDEO_REGISTERS = 53248; 127 | static constexpr uint16_t VIDEO_MEMORY = 1024; 128 | static constexpr uint16_t BORDER_COLOR = 53280; 129 | static constexpr uint16_t BACKGROUND_COLOR = 53281; 130 | static constexpr uint16_t SPRITE_POSITION_REGISTERS = VIDEO_REGISTERS; 131 | static constexpr uint16_t SPRITE_ENABLE_BITS = VIDEO_REGISTERS + 21; 132 | static constexpr uint16_t SPRITE_EXPAND_VERTICAL = VIDEO_REGISTERS + 23; 133 | static constexpr uint16_t SPRITE_PRIORITY = VIDEO_REGISTERS + 27; 134 | static constexpr uint16_t SPRITE_MULTICOLOR = VIDEO_REGISTERS + 28; 135 | static constexpr uint16_t SPRITE_EXPAND_HORIZONTAL = VIDEO_REGISTERS + 29; 136 | static constexpr uint16_t SPRITE_COLLISIONS = VIDEO_REGISTERS + 30; 137 | static constexpr uint16_t SPRITE_0_COLOR = VIDEO_REGISTERS + 39; 138 | static constexpr uint16_t SPRITE_1_COLOR = SPRITE_0_COLOR + 1; 139 | static constexpr uint16_t SPRITE_2_COLOR = SPRITE_1_COLOR + 1; 140 | static constexpr uint16_t SCREEN_RASTER_LINE = 53266; 141 | 142 | 143 | volatile uint8_t& border() { 144 | return memory(BORDER_COLOR); 145 | } 146 | 147 | volatile uint8_t& background() { 148 | return memory(BACKGROUND_COLOR); 149 | } 150 | 151 | volatile uint8_t& display(uint8_t x, uint8_t y) 152 | { 153 | return memory(VIDEO_MEMORY + y * 40 + x); 154 | } 155 | 156 | template 157 | static auto color_comparison(const Color &lhs, const Color &rhs) 158 | { 159 | // distance between colors: 160 | // sqrt( (r1 - r2)^2 + (g1 - g2)^2 + (b1 - b2)^2 ) 161 | return (square(lhs.r - r) + square(lhs.g - g) + square(lhs.b - b)) 162 | < (square(rhs.r - r) + square(rhs.g - g) + square(rhs.b - b)); 163 | } 164 | 165 | template 166 | static auto nearest_color(const Colors &colors) 167 | { 168 | return *std::min_element(std::begin(colors), std::end(colors), 169 | color_comparison); 170 | } 171 | 172 | auto frame(Player &p1, Player &p2) 173 | { 174 | struct Frame 175 | { 176 | Frame(VIC_II &t_vic, Player &p1, Player &p2) 177 | : player1(p1), player2(p2), vic(t_vic) 178 | { 179 | while (memory(SCREEN_RASTER_LINE) != 250) {} 180 | } 181 | 182 | ~Frame() { 183 | vic.display(10, 12) = player1.score; 184 | vic.display(20, 12) = player2.score; 185 | } 186 | 187 | Player &player1; 188 | Player &player2; 189 | VIC_II &vic; 190 | }; 191 | 192 | return Frame(*this, p1, p2); 193 | } 194 | 195 | template 196 | struct SpriteLine { 197 | uint8_t pixels[3]; 198 | 199 | typedef uint_least64_t numeric_t; 200 | 201 | /// Pixel format converter. 202 | static constexpr uint8_t c(const numeric_t m) 203 | { 204 | if constexpr (multicolor) 205 | // 0b00ii00jj00kk00ll -> 0biijjkkll 206 | return uint8_t(m >> 0 & 3 << 0) | 207 | uint8_t(m >> 2 & 3 << 2) | 208 | uint8_t(m >> 4 & 3 << 4) | 209 | uint8_t(m >> 6 & 3 << 6); 210 | return uint8_t(m); 211 | } 212 | 213 | /// Input bits per byte. 214 | constexpr static auto ibb = multicolor ? 16 : 8; 215 | 216 | /// Constructor. 217 | constexpr SpriteLine(const numeric_t line) 218 | : pixels{c(line >> (2 * ibb)), c(line >> ibb), c(line)} 219 | {} 220 | }; 221 | 222 | template 223 | struct Sprite { 224 | typedef SpriteLine Line; 225 | alignas(SPRITE_ALIGNMENT) Line lines[SPRITE_ALIGNMENT / sizeof(Line)]; 226 | }; 227 | 228 | typedef Sprite HighResSprite; 229 | typedef Sprite MultiColorSprite; 230 | 231 | /// 232 | /// New Code 233 | /// 234 | template 235 | void enable_sprite(const uint8_t sprite_number, 236 | const Sprite& bitmap, 237 | const bool low_priority, 238 | const bool double_width, const bool double_height) 239 | { 240 | #if 0 // error: static_assert expression is not an integral constant 241 | static_assert((std::ptrdiff_t(bitmap.memory) & 0x7000) != 0x1000, 242 | "The addresses 0x1000 to 0x1fff and 0x9000 to 0x9fff" 243 | "point to the character generator ROM, not RAM."); 244 | static_assert(std::ptrdiff_t(bitmap.memory) < 0x4000, 245 | "The data must be within the first (default)" 246 | " 16KiB VIC-II bank."); 247 | #endif 248 | memory(SPRITE_DATA_POINTERS + sprite_number) 249 | = (std::ptrdiff_t(bitmap.lines) & 0x3fff) / SPRITE_ALIGNMENT; 250 | set_bit(SPRITE_ENABLE_BITS, sprite_number, true); 251 | set_bit(SPRITE_EXPAND_HORIZONTAL, sprite_number, double_width); 252 | set_bit(SPRITE_EXPAND_VERTICAL, sprite_number, double_height); 253 | set_bit(SPRITE_MULTICOLOR, sprite_number, multicolor); 254 | set_bit(SPRITE_PRIORITY, sprite_number, low_priority); 255 | } 256 | 257 | auto sprite_collisions() { 258 | const auto collisions = memory(SPRITE_COLLISIONS); 259 | 260 | return std::make_tuple( 261 | test_bit(collisions, 0),test_bit(collisions, 1),test_bit(collisions, 2), 262 | test_bit(collisions, 3),test_bit(collisions, 4),test_bit(collisions, 5), 263 | test_bit(collisions, 6),test_bit(collisions, 7)); 264 | } 265 | 266 | volatile uint8_t &sprite_1_color() 267 | { 268 | return memory(SPRITE_1_COLOR); 269 | } 270 | 271 | volatile uint8_t &sprite_2_color() 272 | { 273 | return memory(SPRITE_2_COLOR); 274 | } 275 | 276 | std::pair 277 | sprite_pos(const uint8_t sprite_num) 278 | { 279 | return { 280 | memory(SPRITE_POSITION_REGISTERS + sprite_num * 2), 281 | memory(SPRITE_POSITION_REGISTERS + sprite_num * 2 + 1) 282 | }; 283 | } 284 | /// 285 | /// End New Code 286 | /// 287 | 288 | 289 | }; 290 | 291 | } 292 | 293 | int main() 294 | { 295 | const std::array colors = {{ 296 | Color{0, 0x00, 0x00, 0x00}, 297 | Color{1, 0xFF, 0xFF, 0xFF}, 298 | Color{2, 0x88, 0x39, 0x32}, 299 | Color{3, 0x67, 0xB6, 0xBD}, 300 | Color{4, 0x8B, 0x3F, 0x96}, 301 | Color{5, 0x55, 0xA0, 0x49}, 302 | Color{6, 0x40, 0x31, 0x8D}, 303 | Color{7, 0xBF, 0xCE, 0x72}, 304 | Color{8, 0x8B, 0x54, 0x29}, 305 | Color{9, 0x57, 0x42, 0x00}, 306 | Color{10, 0xB8, 0x69, 0x62}, 307 | Color{11, 0x50, 0x50, 0x50}, 308 | Color{12, 0x78, 0x78, 0x78}, 309 | Color{13, 0x94, 0xE0, 0x89}, 310 | Color{14, 0x78, 0x69, 0xC4}, 311 | Color{15, 0x9F, 0x9F, 0x9F} 312 | }}; 313 | 314 | VIC_II vic; 315 | 316 | /// The ball image. 317 | static const VIC_II::HighResSprite sBall 318 | {{ 319 | 0b000000000000000000000000, 320 | 0b000000000000000000000000, 321 | 0b000000000111111000000000, 322 | 0b000000011111111110000000, 323 | 0b000000110111111111000000, 324 | 0b000001100011111111100000, 325 | 0b000001110111111111100000, 326 | 0b000011111111111111110000, 327 | 0b000011111111111111110000, 328 | 0b000011111111111111110000, 329 | 0b000000111111111111000000, 330 | 0b000011000000000000110000, 331 | 0b000011111111111111110000, 332 | 0b000001111111111111100000, 333 | 0b000001111111111111100000, 334 | 0b000000111111111111000000, 335 | 0b000000011111111110000000, 336 | 0b000000000111111000000000, 337 | 0b000000000000000000000000, 338 | 0b000000000000000000000000, 339 | 0b000000000000000000000000 340 | }}; 341 | 342 | /// The bat image. 343 | static const VIC_II::MultiColorSprite sBat = {{ 344 | 0x000002200000, 345 | 0x000002200000, 346 | 0x000002200000, 347 | 0x000002200000, 348 | 0x000002200000, 349 | 0x000002200000, 350 | 0x000002200000, 351 | 0x000002200000, 352 | 0x000002200000, 353 | 0x000002200000, 354 | 0x000002200000, 355 | 0x000002200000, 356 | 0x000002200000, 357 | 0x000002200000, 358 | 0x000002200000, 359 | 0x000002200000, 360 | 0x000003300000, 361 | 0x000001100000, 362 | 0x000003300000, 363 | 0x000001100000, 364 | 0x000003300000 365 | }}; 366 | 367 | vic.enable_sprite(0, sBall, true, false, false); 368 | vic.enable_sprite(1, sBat, false, false, true); 369 | vic.enable_sprite(2, sBat, false, false, true); 370 | vic.border() = vic.nearest_color<128,128,128>(colors).num; // 50% grey 371 | vic.background() = vic.nearest_color<0,0,0>(colors).num; // black 372 | vic.sprite_1_color() = vic.nearest_color<255,0,0>(colors).num; // red 373 | vic.sprite_2_color() = vic.nearest_color<0,255,0>(colors).num; // green 374 | 375 | 376 | /// 377 | /// Game Logic 378 | /// 379 | std::pair ball_velocity{1,1}; 380 | 381 | const auto reset_ball = [&vic]{ 382 | // resets position but keeps velocity 383 | vic.sprite_pos(0) = std::make_pair(255/2, 255/2); 384 | }; 385 | 386 | reset_ball(); 387 | 388 | Player p1(1, vic.sprite_pos(1), {15, 255/2}); 389 | Player p2(2, vic.sprite_pos(2), {255, 255/2}); 390 | 391 | /// 392 | /// Game Loop 393 | /// 394 | while (true) { 395 | auto frame = vic.frame(p1, p2); 396 | 397 | if (const auto [s0, s1, s2, s3, s4, s5, s6, s7] = vic.sprite_collisions(); 398 | s0 && (s1 || s2)) 399 | { 400 | // ball hit paddle, invert ball x velocity 401 | ball_velocity *= std::make_pair(-1, 1); 402 | // "bounce" ball out of collision area 403 | vic.sprite_pos(0) += std::make_pair(ball_velocity.first, 0); 404 | } 405 | 406 | 407 | // Update paddle positions 408 | p1.update_position(); 409 | p2.update_position(); 410 | 411 | 412 | const auto score = [reset_ball](auto &player){ 413 | // called when a player scores 414 | player.scored(); 415 | reset_ball(); 416 | }; 417 | 418 | if (const auto [ball_x, ball_y] = vic.sprite_pos(0) += ball_velocity; 419 | ball_y == 45 || ball_y == 235) 420 | { 421 | // ball hit the top or bottom wall, invert ball y velocity 422 | ball_velocity *= std::make_pair(1, -1); 423 | } else if (ball_x == 1) { 424 | // ball hit left wall, player 2 scored 425 | score(p2); 426 | } else if (ball_x == 255) { 427 | // ball hit right wall, player 1 scored 428 | score(p1); 429 | } 430 | } 431 | } 432 | 433 | -------------------------------------------------------------------------------- /examples/pong_templates.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | namespace { 9 | volatile uint8_t &memory(const uint16_t loc) 10 | { 11 | return *reinterpret_cast(loc); 12 | } 13 | 14 | template 15 | auto square(T t) { 16 | return t * t; 17 | } 18 | 19 | constexpr bool test_bit(const uint8_t data, const uint8_t bit) 20 | { 21 | return (data & (1 << bit)) != 0; 22 | }; 23 | 24 | struct Color 25 | { 26 | uint8_t num; 27 | uint8_t r; 28 | uint8_t g; 29 | uint8_t b; 30 | }; 31 | 32 | struct JoyStick 33 | { 34 | static constexpr uint16_t JOYSTICK_PORT_A = 56320; // joystick #2 35 | static constexpr uint16_t JOYSTICK_PORT_B = 56321; // joystick #1 36 | 37 | struct PortData 38 | { 39 | uint8_t data; 40 | }; 41 | 42 | JoyStick(const PortData d) 43 | : up(!test_bit(d.data, 0)), 44 | down(!test_bit(d.data, 1)), 45 | left(!test_bit(d.data, 2)), 46 | right(!test_bit(d.data, 3)), 47 | fire(!test_bit(d.data, 4)) 48 | { 49 | } 50 | 51 | JoyStick(const uint8_t port_num) 52 | : JoyStick(PortData{port_num==2?memory(JOYSTICK_PORT_A):memory(JOYSTICK_PORT_B)}) 53 | { 54 | } 55 | 56 | auto direction_vector() const 57 | { 58 | return std::make_pair(left?-1:(right?1:0), up?-1:(down?1:0)); 59 | } 60 | 61 | bool up,down,left,right,fire; 62 | }; 63 | 64 | /// 65 | /// New Code 66 | /// 67 | template 68 | auto operator+=(std::pair lhs, const std::pair &rhs) 69 | { 70 | lhs.first += rhs.first; 71 | lhs.second += rhs.second; 72 | return lhs; 73 | } 74 | 75 | template 76 | decltype(auto) operator*=(std::pair &lhs, const std::pair &rhs) 77 | { 78 | lhs.first *= rhs.first; 79 | lhs.second *= rhs.second; 80 | return (lhs); 81 | } 82 | 83 | void set_bit(const uint16_t loc, const uint8_t bitnum, bool val) 84 | { 85 | if (val) { 86 | memory(loc) |= (1 << bitnum); 87 | } else { 88 | memory(loc) &= (0xFF ^ (1 << bitnum)); 89 | } 90 | } 91 | 92 | struct Player 93 | { 94 | Player(const uint8_t num, std::pair sprite_pos, 95 | const std::pair &start_pos) 96 | : player_num(num), 97 | pos(sprite_pos) 98 | { 99 | pos = start_pos; 100 | } 101 | 102 | void update_position() 103 | { 104 | pos.second += JoyStick(player_num).direction_vector().second * 3; 105 | }; 106 | 107 | void scored() { 108 | ++score; 109 | } 110 | 111 | const uint8_t player_num; 112 | std::pair pos; 113 | uint8_t score = '0'; 114 | }; 115 | /// 116 | /// End New Code 117 | /// 118 | 119 | 120 | 121 | 122 | struct VIC_II 123 | { 124 | static constexpr uint16_t SPRITE_DATA_POINTERS = 2040; 125 | static constexpr uint16_t VIDEO_REGISTERS = 53248; 126 | static constexpr uint16_t VIDEO_MEMORY = 1024; 127 | static constexpr uint8_t SPRITE_STARTING_BANK = 192; 128 | static constexpr uint16_t BORDER_COLOR = 53280; 129 | static constexpr uint16_t BACKGROUND_COLOR = 53281; 130 | static constexpr uint16_t SPRITE_POSITION_REGISTERS = VIDEO_REGISTERS; 131 | static constexpr uint16_t SPRITE_ENABLE_BITS = VIDEO_REGISTERS + 21; 132 | static constexpr uint16_t SPRITE_EXPAND_VERTICAL = VIDEO_REGISTERS + 23; 133 | static constexpr uint16_t SPRITE_PRIORITY = VIDEO_REGISTERS + 27; 134 | static constexpr uint16_t SPRITE_MULTICOLOR = VIDEO_REGISTERS + 28; 135 | static constexpr uint16_t SPRITE_EXPAND_HORIZONTAL = VIDEO_REGISTERS + 29; 136 | static constexpr uint16_t SPRITE_COLLISIONS = VIDEO_REGISTERS + 30; 137 | static constexpr uint16_t SPRITE_0_COLOR = VIDEO_REGISTERS + 39; 138 | static constexpr uint16_t SPRITE_1_COLOR = SPRITE_0_COLOR + 1; 139 | static constexpr uint16_t SPRITE_2_COLOR = SPRITE_1_COLOR + 1; 140 | static constexpr uint16_t SCREEN_RASTER_LINE = 53266; 141 | 142 | 143 | volatile uint8_t& border() { 144 | return memory(BORDER_COLOR); 145 | } 146 | 147 | volatile uint8_t& background() { 148 | return memory(BACKGROUND_COLOR); 149 | } 150 | 151 | volatile uint8_t& display(uint8_t x, uint8_t y) 152 | { 153 | return memory(VIDEO_MEMORY + y * 40 + x); 154 | } 155 | 156 | template 157 | static auto color_comparison(const Color &lhs, const Color &rhs) 158 | { 159 | // distance between colors: 160 | // sqrt( (r1 - r2)^2 + (g1 - g2)^2 + (b1 - b2)^2 ) 161 | return (square(lhs.r - r) + square(lhs.g - g) + square(lhs.b - b)) 162 | < (square(rhs.r - r) + square(rhs.g - g) + square(rhs.b - b)); 163 | } 164 | 165 | template 166 | static auto nearest_color(const Colors &colors) 167 | { 168 | return *std::min_element(std::begin(colors), std::end(colors), 169 | color_comparison); 170 | } 171 | 172 | auto frame(Player &p1, Player &p2) 173 | { 174 | struct Frame 175 | { 176 | Frame(VIC_II &t_vic, Player &p1, Player &p2) 177 | : player1(p1), player2(p2), vic(t_vic) 178 | { 179 | while (memory(SCREEN_RASTER_LINE) != 250) {} 180 | } 181 | 182 | ~Frame() { 183 | vic.display(10, 12) = player1.score; 184 | vic.display(20, 12) = player2.score; 185 | } 186 | 187 | Player &player1; 188 | Player &player2; 189 | VIC_II &vic; 190 | }; 191 | 192 | return Frame(*this, p1, p2); 193 | } 194 | 195 | void write_multi_color_pixel(uint16_t) 196 | { 197 | // 0th case 198 | } 199 | 200 | void write_pixel(uint16_t) 201 | { 202 | // 0th case 203 | } 204 | 205 | template 206 | void write_multi_color_pixel(uint16_t loc, uint8_t d1, uint8_t d2, 207 | uint8_t d3, uint8_t d4, D ... d) 208 | { 209 | memory(loc) = (d1 << 6) | (d2 << 4) | (d3 << 2) | d4; 210 | write_multi_color_pixel(loc + 1, d...); 211 | } 212 | 213 | template 214 | void write_pixel(uint16_t loc, bool d1, bool d2, bool d3, bool d4, 215 | bool d5, bool d6, bool d7, bool d8, D ... d) 216 | { 217 | memory(loc) = (d1 << 7) | (d2 << 6) | (d3 << 5) | (d4 << 4) | (d5 << 3) | (d6 << 2) | (d7 << 1) | d8; 218 | write_pixel(loc + 1, d...); 219 | } 220 | 221 | template 222 | void make_sprite(uint8_t memory_loc, D ... d) 223 | { 224 | if constexpr(sizeof...(d) == 12 * 21) { 225 | write_multi_color_pixel((SPRITE_STARTING_BANK + memory_loc) * 64, d...); 226 | } else { 227 | write_pixel((SPRITE_STARTING_BANK + memory_loc) * 64, d...); 228 | } 229 | } 230 | 231 | /// 232 | /// New Code 233 | /// 234 | void enable_sprite(const uint8_t sprite_number, const uint8_t memory_loc, 235 | const bool multicolor, const bool low_priority, 236 | const bool double_width, const bool double_height) 237 | { 238 | memory(SPRITE_DATA_POINTERS + sprite_number) 239 | = SPRITE_STARTING_BANK + memory_loc; 240 | set_bit(SPRITE_ENABLE_BITS, sprite_number, true); 241 | set_bit(SPRITE_EXPAND_HORIZONTAL, sprite_number, double_width); 242 | set_bit(SPRITE_EXPAND_VERTICAL, sprite_number, double_height); 243 | set_bit(SPRITE_MULTICOLOR, sprite_number, multicolor); 244 | set_bit(SPRITE_PRIORITY, sprite_number, low_priority); 245 | } 246 | 247 | auto sprite_collisions() { 248 | const auto collisions = memory(SPRITE_COLLISIONS); 249 | 250 | return std::make_tuple( 251 | test_bit(collisions, 0),test_bit(collisions, 1),test_bit(collisions, 2), 252 | test_bit(collisions, 3),test_bit(collisions, 4),test_bit(collisions, 5), 253 | test_bit(collisions, 6),test_bit(collisions, 7)); 254 | } 255 | 256 | volatile uint8_t &sprite_1_color() 257 | { 258 | return memory(SPRITE_1_COLOR); 259 | } 260 | 261 | volatile uint8_t &sprite_2_color() 262 | { 263 | return memory(SPRITE_2_COLOR); 264 | } 265 | 266 | std::pair 267 | sprite_pos(const uint8_t sprite_num) 268 | { 269 | return { 270 | memory(SPRITE_POSITION_REGISTERS + sprite_num * 2), 271 | memory(SPRITE_POSITION_REGISTERS + sprite_num * 2 + 1) 272 | }; 273 | } 274 | /// 275 | /// End New Code 276 | /// 277 | 278 | 279 | }; 280 | 281 | } 282 | 283 | 284 | 285 | 286 | int main() 287 | { 288 | const std::array colors = {{ 289 | Color{0, 0x00, 0x00, 0x00}, 290 | Color{1, 0xFF, 0xFF, 0xFF}, 291 | Color{2, 0x88, 0x39, 0x32}, 292 | Color{3, 0x67, 0xB6, 0xBD}, 293 | Color{4, 0x8B, 0x3F, 0x96}, 294 | Color{5, 0x55, 0xA0, 0x49}, 295 | Color{6, 0x40, 0x31, 0x8D}, 296 | Color{7, 0xBF, 0xCE, 0x72}, 297 | Color{8, 0x8B, 0x54, 0x29}, 298 | Color{9, 0x57, 0x42, 0x00}, 299 | Color{10, 0xB8, 0x69, 0x62}, 300 | Color{11, 0x50, 0x50, 0x50}, 301 | Color{12, 0x78, 0x78, 0x78}, 302 | Color{13, 0x94, 0xE0, 0x89}, 303 | Color{14, 0x78, 0x69, 0xC4}, 304 | Color{15, 0x9F, 0x9F, 0x9F} 305 | }}; 306 | 307 | VIC_II vic; 308 | 309 | vic.make_sprite(0, 310 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 311 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 312 | 0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0, 313 | 0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0, 314 | 0,0,0,0,0,0,1,1,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, 315 | 0,0,0,0,0,1,1,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0, 316 | 0,0,0,0,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0, 317 | 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0, 318 | 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0, 319 | 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0, 320 | 0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, 321 | 0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0, 322 | 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0, 323 | 0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0, 324 | 0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0, 325 | 0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, 326 | 0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0, 327 | 0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0, 328 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 329 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 330 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 331 | ); 332 | 333 | vic.make_sprite(1, 334 | 0,0,0,0,0,2,2,0,0,0,0,0, 335 | 0,0,0,0,0,2,2,0,0,0,0,0, 336 | 0,0,0,0,0,2,2,0,0,0,0,0, 337 | 0,0,0,0,0,2,2,0,0,0,0,0, 338 | 0,0,0,0,0,2,2,0,0,0,0,0, 339 | 0,0,0,0,0,2,2,0,0,0,0,0, 340 | 0,0,0,0,0,2,2,0,0,0,0,0, 341 | 0,0,0,0,0,2,2,0,0,0,0,0, 342 | 0,0,0,0,0,2,2,0,0,0,0,0, 343 | 0,0,0,0,0,2,2,0,0,0,0,0, 344 | 0,0,0,0,0,2,2,0,0,0,0,0, 345 | 0,0,0,0,0,2,2,0,0,0,0,0, 346 | 0,0,0,0,0,2,2,0,0,0,0,0, 347 | 0,0,0,0,0,2,2,0,0,0,0,0, 348 | 0,0,0,0,0,2,2,0,0,0,0,0, 349 | 0,0,0,0,0,2,2,0,0,0,0,0, 350 | 0,0,0,0,0,3,3,0,0,0,0,0, 351 | 0,0,0,0,0,1,1,0,0,0,0,0, 352 | 0,0,0,0,0,3,3,0,0,0,0,0, 353 | 0,0,0,0,0,1,1,0,0,0,0,0, 354 | 0,0,0,0,0,3,3,0,0,0,0,0 355 | ); 356 | 357 | vic.enable_sprite(0, 0, false, true, false, false); 358 | vic.enable_sprite(1, 1, true, false, false, true); 359 | vic.enable_sprite(2, 1, true, false, false, true); 360 | 361 | vic.border() = vic.nearest_color<128,128,128>(colors).num; // 50% grey 362 | vic.background() = vic.nearest_color<0,0,0>(colors).num; // black 363 | vic.sprite_1_color() = vic.nearest_color<255,0,0>(colors).num; // red 364 | vic.sprite_2_color() = vic.nearest_color<0,255,0>(colors).num; // green 365 | 366 | 367 | /// 368 | /// Game Logic 369 | /// 370 | std::pair ball_velocity{1,1}; 371 | 372 | const auto reset_ball = [&vic]{ 373 | // resets position but keeps velocity 374 | vic.sprite_pos(0) = std::make_pair(255/2, 255/2); 375 | }; 376 | 377 | reset_ball(); 378 | 379 | Player p1(1, vic.sprite_pos(1), {15, 255/2}); 380 | Player p2(2, vic.sprite_pos(2), {255, 255/2}); 381 | 382 | /// 383 | /// Game Loop 384 | /// 385 | while (true) { 386 | auto frame = vic.frame(p1, p2); 387 | 388 | if (const auto [s0, s1, s2, s3, s4, s5, s6, s7] = vic.sprite_collisions(); 389 | s0 && (s1 || s2)) 390 | { 391 | // ball hit paddle, invert ball x velocity 392 | ball_velocity *= std::make_pair(-1, 1); 393 | // "bounce" ball out of collision area 394 | vic.sprite_pos(0) += std::make_pair(ball_velocity.first, 0); 395 | } 396 | 397 | 398 | // Update paddle positions 399 | p1.update_position(); 400 | p2.update_position(); 401 | 402 | 403 | const auto score = [reset_ball](auto &player){ 404 | // called when a player scores 405 | player.scored(); 406 | reset_ball(); 407 | }; 408 | 409 | if (const auto [ball_x, ball_y] = vic.sprite_pos(0) += ball_velocity; 410 | ball_y == 45 || ball_y == 235) 411 | { 412 | // ball hit the top or bottom wall, invert ball y velocity 413 | ball_velocity *= std::make_pair(1, -1); 414 | } else if (ball_x == 1) { 415 | // ball hit left wall, player 2 scored 416 | score(p2); 417 | } else if (ball_x == 255) { 418 | // ball hit right wall, player 1 scored 419 | score(p1); 420 | } 421 | } 422 | } 423 | 424 | -------------------------------------------------------------------------------- /examples/simple_game/6502.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_C_6502_HPP 2 | #define INC_6502_C_6502_HPP 3 | 4 | #include 5 | 6 | 7 | namespace mos6502 { 8 | 9 | static volatile std::uint8_t &memory_loc(const std::uint16_t loc) 10 | { 11 | return *reinterpret_cast(loc); 12 | } 13 | 14 | static void poke(const std::uint16_t loc, const std::uint8_t value) { memory_loc(loc) = value; } 15 | 16 | static std::uint8_t peek(const std::uint16_t loc) { return memory_loc(loc); } 17 | 18 | }// namespace mos6502 19 | 20 | #endif// INC_6502_C_6502_HPP 21 | -------------------------------------------------------------------------------- /examples/simple_game/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # Note: this is compiling for the host OS, so not really useful 3 | # but it is handy as a sanity check 4 | add_executable(game game.cpp geometry.hpp) 5 | 6 | target_link_libraries( 7 | game 8 | PRIVATE project_options 9 | project_warnings) 10 | 11 | target_compile_options(game PRIVATE -Wno-volatile) 12 | -------------------------------------------------------------------------------- /examples/simple_game/chargen.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace petscii { 4 | constexpr static std::array uppercase = { 5 | 0x3C, 0x66, 0x6E, 0x6E, 0x60, 0x62, 0x3C, 0x00, 6 | 0x18, 0x3C, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, 7 | 0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00, 8 | 0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00, 9 | 0x78, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0x78, 0x00, 10 | 0x7E, 0x60, 0x60, 0x78, 0x60, 0x60, 0x7E, 0x00, 11 | 0x7E, 0x60, 0x60, 0x78, 0x60, 0x60, 0x60, 0x00, 12 | 0x3C, 0x66, 0x60, 0x6E, 0x66, 0x66, 0x3C, 0x00, 13 | 0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, 14 | 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 15 | 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x6C, 0x38, 0x00, 16 | 0x66, 0x6C, 0x78, 0x70, 0x78, 0x6C, 0x66, 0x00, 17 | 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x00, 18 | 0x63, 0x77, 0x7F, 0x6B, 0x63, 0x63, 0x63, 0x00, 19 | 0x66, 0x76, 0x7E, 0x7E, 0x6E, 0x66, 0x66, 0x00, 20 | 0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, 21 | 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x00, 22 | 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x0E, 0x00, 23 | 0x7C, 0x66, 0x66, 0x7C, 0x78, 0x6C, 0x66, 0x00, 24 | 0x3C, 0x66, 0x60, 0x3C, 0x06, 0x66, 0x3C, 0x00, 25 | 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 26 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, 27 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, 28 | 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00, 29 | 0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00, 30 | 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x00, 31 | 0x7E, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x7E, 0x00, 32 | 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, 33 | 0x0C, 0x12, 0x30, 0x7C, 0x30, 0x62, 0xFC, 0x00, 34 | 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00, 35 | 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, 0x18, 36 | 0x00, 0x10, 0x30, 0x7F, 0x7F, 0x30, 0x10, 0x00, 37 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 38 | 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x00, 39 | 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 40 | 0x66, 0x66, 0xFF, 0x66, 0xFF, 0x66, 0x66, 0x00, 41 | 0x18, 0x3E, 0x60, 0x3C, 0x06, 0x7C, 0x18, 0x00, 42 | 0x62, 0x66, 0x0C, 0x18, 0x30, 0x66, 0x46, 0x00, 43 | 0x3C, 0x66, 0x3C, 0x38, 0x67, 0x66, 0x3F, 0x00, 44 | 0x06, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 45 | 0x0C, 0x18, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00, 46 | 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00, 47 | 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, 48 | 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 49 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 50 | 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 51 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 52 | 0x00, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00, 53 | 0x3C, 0x66, 0x6E, 0x76, 0x66, 0x66, 0x3C, 0x00, 54 | 0x18, 0x18, 0x38, 0x18, 0x18, 0x18, 0x7E, 0x00, 55 | 0x3C, 0x66, 0x06, 0x0C, 0x30, 0x60, 0x7E, 0x00, 56 | 0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C, 0x00, 57 | 0x06, 0x0E, 0x1E, 0x66, 0x7F, 0x06, 0x06, 0x00, 58 | 0x7E, 0x60, 0x7C, 0x06, 0x06, 0x66, 0x3C, 0x00, 59 | 0x3C, 0x66, 0x60, 0x7C, 0x66, 0x66, 0x3C, 0x00, 60 | 0x7E, 0x66, 0x0C, 0x18, 0x18, 0x18, 0x18, 0x00, 61 | 0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00, 62 | 0x3C, 0x66, 0x66, 0x3E, 0x06, 0x66, 0x3C, 0x00, 63 | 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 64 | 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x18, 0x30, 65 | 0x0E, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0E, 0x00, 66 | 0x00, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00, 67 | 0x70, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x70, 0x00, 68 | 0x3C, 0x66, 0x06, 0x0C, 0x18, 0x00, 0x18, 0x00, 69 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 70 | 0x08, 0x1C, 0x3E, 0x7F, 0x7F, 0x1C, 0x3E, 0x00, 71 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 72 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 73 | 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 74 | 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 75 | 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 76 | 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 77 | 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 78 | 0x00, 0x00, 0x00, 0xE0, 0xF0, 0x38, 0x18, 0x18, 79 | 0x18, 0x18, 0x1C, 0x0F, 0x07, 0x00, 0x00, 0x00, 80 | 0x18, 0x18, 0x38, 0xF0, 0xE0, 0x00, 0x00, 0x00, 81 | 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFF, 0xFF, 82 | 0xC0, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, 83 | 0x03, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xC0, 84 | 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 85 | 0xFF, 0xFF, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 86 | 0x00, 0x3C, 0x7E, 0x7E, 0x7E, 0x7E, 0x3C, 0x00, 87 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 88 | 0x36, 0x7F, 0x7F, 0x7F, 0x3E, 0x1C, 0x08, 0x00, 89 | 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 90 | 0x00, 0x00, 0x00, 0x07, 0x0F, 0x1C, 0x18, 0x18, 91 | 0xC3, 0xE7, 0x7E, 0x3C, 0x3C, 0x7E, 0xE7, 0xC3, 92 | 0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x3C, 0x00, 93 | 0x18, 0x18, 0x66, 0x66, 0x18, 0x18, 0x3C, 0x00, 94 | 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 95 | 0x08, 0x1C, 0x3E, 0x7F, 0x3E, 0x1C, 0x08, 0x00, 96 | 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0x18, 97 | 0xC0, 0xC0, 0x30, 0x30, 0xC0, 0xC0, 0x30, 0x30, 98 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 99 | 0x00, 0x00, 0x03, 0x3E, 0x76, 0x36, 0x36, 0x00, 100 | 0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, 101 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 102 | 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 103 | 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 104 | 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 105 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 106 | 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 107 | 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 108 | 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 109 | 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0x33, 0x33, 110 | 0xFF, 0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xC0, 0x80, 111 | 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 112 | 0x18, 0x18, 0x18, 0x1F, 0x1F, 0x18, 0x18, 0x18, 113 | 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 114 | 0x18, 0x18, 0x18, 0x1F, 0x1F, 0x00, 0x00, 0x00, 115 | 0x00, 0x00, 0x00, 0xF8, 0xF8, 0x18, 0x18, 0x18, 116 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 117 | 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x18, 0x18, 0x18, 118 | 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x00, 0x00, 0x00, 119 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18, 0x18, 0x18, 120 | 0x18, 0x18, 0x18, 0xF8, 0xF8, 0x18, 0x18, 0x18, 121 | 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 122 | 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 123 | 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 124 | 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 125 | 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 126 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 127 | 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xFF, 0xFF, 128 | 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, 129 | 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 130 | 0x18, 0x18, 0x18, 0xF8, 0xF8, 0x00, 0x00, 0x00, 131 | 0xF0, 0xF0, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, 132 | 0xF0, 0xF0, 0xF0, 0xF0, 0x0F, 0x0F, 0x0F, 0x0F, 133 | 0xC3, 0x99, 0x91, 0x91, 0x9F, 0x99, 0xC3, 0xFF, 134 | 0xE7, 0xC3, 0x99, 0x81, 0x99, 0x99, 0x99, 0xFF, 135 | 0x83, 0x99, 0x99, 0x83, 0x99, 0x99, 0x83, 0xFF, 136 | 0xC3, 0x99, 0x9F, 0x9F, 0x9F, 0x99, 0xC3, 0xFF, 137 | 0x87, 0x93, 0x99, 0x99, 0x99, 0x93, 0x87, 0xFF, 138 | 0x81, 0x9F, 0x9F, 0x87, 0x9F, 0x9F, 0x81, 0xFF, 139 | 0x81, 0x9F, 0x9F, 0x87, 0x9F, 0x9F, 0x9F, 0xFF, 140 | 0xC3, 0x99, 0x9F, 0x91, 0x99, 0x99, 0xC3, 0xFF, 141 | 0x99, 0x99, 0x99, 0x81, 0x99, 0x99, 0x99, 0xFF, 142 | 0xC3, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xC3, 0xFF, 143 | 0xE1, 0xF3, 0xF3, 0xF3, 0xF3, 0x93, 0xC7, 0xFF, 144 | 0x99, 0x93, 0x87, 0x8F, 0x87, 0x93, 0x99, 0xFF, 145 | 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x81, 0xFF, 146 | 0x9C, 0x88, 0x80, 0x94, 0x9C, 0x9C, 0x9C, 0xFF, 147 | 0x99, 0x89, 0x81, 0x81, 0x91, 0x99, 0x99, 0xFF, 148 | 0xC3, 0x99, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xFF, 149 | 0x83, 0x99, 0x99, 0x83, 0x9F, 0x9F, 0x9F, 0xFF, 150 | 0xC3, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xF1, 0xFF, 151 | 0x83, 0x99, 0x99, 0x83, 0x87, 0x93, 0x99, 0xFF, 152 | 0xC3, 0x99, 0x9F, 0xC3, 0xF9, 0x99, 0xC3, 0xFF, 153 | 0x81, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, 154 | 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xFF, 155 | 0x99, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xE7, 0xFF, 156 | 0x9C, 0x9C, 0x9C, 0x94, 0x80, 0x88, 0x9C, 0xFF, 157 | 0x99, 0x99, 0xC3, 0xE7, 0xC3, 0x99, 0x99, 0xFF, 158 | 0x99, 0x99, 0x99, 0xC3, 0xE7, 0xE7, 0xE7, 0xFF, 159 | 0x81, 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0x81, 0xFF, 160 | 0xC3, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xC3, 0xFF, 161 | 0xF3, 0xED, 0xCF, 0x83, 0xCF, 0x9D, 0x03, 0xFF, 162 | 0xC3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xC3, 0xFF, 163 | 0xFF, 0xE7, 0xC3, 0x81, 0xE7, 0xE7, 0xE7, 0xE7, 164 | 0xFF, 0xEF, 0xCF, 0x80, 0x80, 0xCF, 0xEF, 0xFF, 165 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 166 | 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, 0xFF, 0xE7, 0xFF, 167 | 0x99, 0x99, 0x99, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 168 | 0x99, 0x99, 0x00, 0x99, 0x00, 0x99, 0x99, 0xFF, 169 | 0xE7, 0xC1, 0x9F, 0xC3, 0xF9, 0x83, 0xE7, 0xFF, 170 | 0x9D, 0x99, 0xF3, 0xE7, 0xCF, 0x99, 0xB9, 0xFF, 171 | 0xC3, 0x99, 0xC3, 0xC7, 0x98, 0x99, 0xC0, 0xFF, 172 | 0xF9, 0xF3, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 173 | 0xF3, 0xE7, 0xCF, 0xCF, 0xCF, 0xE7, 0xF3, 0xFF, 174 | 0xCF, 0xE7, 0xF3, 0xF3, 0xF3, 0xE7, 0xCF, 0xFF, 175 | 0xFF, 0x99, 0xC3, 0x00, 0xC3, 0x99, 0xFF, 0xFF, 176 | 0xFF, 0xE7, 0xE7, 0x81, 0xE7, 0xE7, 0xFF, 0xFF, 177 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xCF, 178 | 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 179 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xFF, 180 | 0xFF, 0xFC, 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0xFF, 181 | 0xC3, 0x99, 0x91, 0x89, 0x99, 0x99, 0xC3, 0xFF, 182 | 0xE7, 0xE7, 0xC7, 0xE7, 0xE7, 0xE7, 0x81, 0xFF, 183 | 0xC3, 0x99, 0xF9, 0xF3, 0xCF, 0x9F, 0x81, 0xFF, 184 | 0xC3, 0x99, 0xF9, 0xE3, 0xF9, 0x99, 0xC3, 0xFF, 185 | 0xF9, 0xF1, 0xE1, 0x99, 0x80, 0xF9, 0xF9, 0xFF, 186 | 0x81, 0x9F, 0x83, 0xF9, 0xF9, 0x99, 0xC3, 0xFF, 187 | 0xC3, 0x99, 0x9F, 0x83, 0x99, 0x99, 0xC3, 0xFF, 188 | 0x81, 0x99, 0xF3, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, 189 | 0xC3, 0x99, 0x99, 0xC3, 0x99, 0x99, 0xC3, 0xFF, 190 | 0xC3, 0x99, 0x99, 0xC1, 0xF9, 0x99, 0xC3, 0xFF, 191 | 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 192 | 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xE7, 0xE7, 0xCF, 193 | 0xF1, 0xE7, 0xCF, 0x9F, 0xCF, 0xE7, 0xF1, 0xFF, 194 | 0xFF, 0xFF, 0x81, 0xFF, 0x81, 0xFF, 0xFF, 0xFF, 195 | 0x8F, 0xE7, 0xF3, 0xF9, 0xF3, 0xE7, 0x8F, 0xFF, 196 | 0xC3, 0x99, 0xF9, 0xF3, 0xE7, 0xFF, 0xE7, 0xFF, 197 | 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 198 | 0xF7, 0xE3, 0xC1, 0x80, 0x80, 0xE3, 0xC1, 0xFF, 199 | 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 200 | 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 201 | 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 202 | 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 203 | 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 204 | 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 205 | 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 206 | 0xFF, 0xFF, 0xFF, 0x1F, 0x0F, 0xC7, 0xE7, 0xE7, 207 | 0xE7, 0xE7, 0xE3, 0xF0, 0xF8, 0xFF, 0xFF, 0xFF, 208 | 0xE7, 0xE7, 0xC7, 0x0F, 0x1F, 0xFF, 0xFF, 0xFF, 209 | 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x00, 210 | 0x3F, 0x1F, 0x8F, 0xC7, 0xE3, 0xF1, 0xF8, 0xFC, 211 | 0xFC, 0xF8, 0xF1, 0xE3, 0xC7, 0x8F, 0x1F, 0x3F, 212 | 0x00, 0x00, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 213 | 0x00, 0x00, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 214 | 0xFF, 0xC3, 0x81, 0x81, 0x81, 0x81, 0xC3, 0xFF, 215 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 216 | 0xC9, 0x80, 0x80, 0x80, 0xC1, 0xE3, 0xF7, 0xFF, 217 | 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 218 | 0xFF, 0xFF, 0xFF, 0xF8, 0xF0, 0xE3, 0xE7, 0xE7, 219 | 0x3C, 0x18, 0x81, 0xC3, 0xC3, 0x81, 0x18, 0x3C, 220 | 0xFF, 0xC3, 0x81, 0x99, 0x99, 0x81, 0xC3, 0xFF, 221 | 0xE7, 0xE7, 0x99, 0x99, 0xE7, 0xE7, 0xC3, 0xFF, 222 | 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 0xF9, 223 | 0xF7, 0xE3, 0xC1, 0x80, 0xC1, 0xE3, 0xF7, 0xFF, 224 | 0xE7, 0xE7, 0xE7, 0x00, 0x00, 0xE7, 0xE7, 0xE7, 225 | 0x3F, 0x3F, 0xCF, 0xCF, 0x3F, 0x3F, 0xCF, 0xCF, 226 | 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 227 | 0xFF, 0xFF, 0xFC, 0xC1, 0x89, 0xC9, 0xC9, 0xFF, 228 | 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE, 229 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 230 | 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 231 | 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 232 | 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 233 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 234 | 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 235 | 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 236 | 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 237 | 0xFF, 0xFF, 0xFF, 0xFF, 0x33, 0x33, 0xCC, 0xCC, 238 | 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 239 | 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 240 | 0xE7, 0xE7, 0xE7, 0xE0, 0xE0, 0xE7, 0xE7, 0xE7, 241 | 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xF0, 0xF0, 242 | 0xE7, 0xE7, 0xE7, 0xE0, 0xE0, 0xFF, 0xFF, 0xFF, 243 | 0xFF, 0xFF, 0xFF, 0x07, 0x07, 0xE7, 0xE7, 0xE7, 244 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 245 | 0xFF, 0xFF, 0xFF, 0xE0, 0xE0, 0xE7, 0xE7, 0xE7, 246 | 0xE7, 0xE7, 0xE7, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 247 | 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xE7, 0xE7, 0xE7, 248 | 0xE7, 0xE7, 0xE7, 0x07, 0x07, 0xE7, 0xE7, 0xE7, 249 | 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 250 | 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 251 | 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 252 | 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 253 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 254 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 255 | 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0x00, 0x00, 256 | 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 257 | 0xF0, 0xF0, 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 258 | 0xE7, 0xE7, 0xE7, 0x07, 0x07, 0xFF, 0xFF, 0xFF, 259 | 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 260 | 0x0F, 0x0F, 0x0F, 0x0F, 0xF0, 0xF0, 0xF0, 0xF0, 261 | }; 262 | constexpr static std::uint8_t lowercase[] = { 263 | 0x3C, 0x66, 0x6E, 0x6E, 0x60, 0x62, 0x3C, 0x00, 264 | 0x00, 0x00, 0x3C, 0x06, 0x3E, 0x66, 0x3E, 0x00, 265 | 0x00, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x7C, 0x00, 266 | 0x00, 0x00, 0x3C, 0x60, 0x60, 0x60, 0x3C, 0x00, 267 | 0x00, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3E, 0x00, 268 | 0x00, 0x00, 0x3C, 0x66, 0x7E, 0x60, 0x3C, 0x00, 269 | 0x00, 0x0E, 0x18, 0x3E, 0x18, 0x18, 0x18, 0x00, 270 | 0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x7C, 271 | 0x00, 0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x00, 272 | 0x00, 0x18, 0x00, 0x38, 0x18, 0x18, 0x3C, 0x00, 273 | 0x00, 0x06, 0x00, 0x06, 0x06, 0x06, 0x06, 0x3C, 274 | 0x00, 0x60, 0x60, 0x6C, 0x78, 0x6C, 0x66, 0x00, 275 | 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 276 | 0x00, 0x00, 0x66, 0x7F, 0x7F, 0x6B, 0x63, 0x00, 277 | 0x00, 0x00, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x00, 278 | 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, 279 | 0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, 280 | 0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x06, 281 | 0x00, 0x00, 0x7C, 0x66, 0x60, 0x60, 0x60, 0x00, 282 | 0x00, 0x00, 0x3E, 0x60, 0x3C, 0x06, 0x7C, 0x00, 283 | 0x00, 0x18, 0x7E, 0x18, 0x18, 0x18, 0x0E, 0x00, 284 | 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3E, 0x00, 285 | 0x00, 0x00, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, 286 | 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x3E, 0x36, 0x00, 287 | 0x00, 0x00, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x00, 288 | 0x00, 0x00, 0x66, 0x66, 0x66, 0x3E, 0x0C, 0x78, 289 | 0x00, 0x00, 0x7E, 0x0C, 0x18, 0x30, 0x7E, 0x00, 290 | 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, 291 | 0x0C, 0x12, 0x30, 0x7C, 0x30, 0x62, 0xFC, 0x00, 292 | 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00, 293 | 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, 0x18, 294 | 0x00, 0x10, 0x30, 0x7F, 0x7F, 0x30, 0x10, 0x00, 295 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 296 | 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x00, 297 | 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 298 | 0x66, 0x66, 0xFF, 0x66, 0xFF, 0x66, 0x66, 0x00, 299 | 0x18, 0x3E, 0x60, 0x3C, 0x06, 0x7C, 0x18, 0x00, 300 | 0x62, 0x66, 0x0C, 0x18, 0x30, 0x66, 0x46, 0x00, 301 | 0x3C, 0x66, 0x3C, 0x38, 0x67, 0x66, 0x3F, 0x00, 302 | 0x06, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 303 | 0x0C, 0x18, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00, 304 | 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00, 305 | 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, 306 | 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 307 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 308 | 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 309 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 310 | 0x00, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00, 311 | 0x3C, 0x66, 0x6E, 0x76, 0x66, 0x66, 0x3C, 0x00, 312 | 0x18, 0x18, 0x38, 0x18, 0x18, 0x18, 0x7E, 0x00, 313 | 0x3C, 0x66, 0x06, 0x0C, 0x30, 0x60, 0x7E, 0x00, 314 | 0x3C, 0x66, 0x06, 0x1C, 0x06, 0x66, 0x3C, 0x00, 315 | 0x06, 0x0E, 0x1E, 0x66, 0x7F, 0x06, 0x06, 0x00, 316 | 0x7E, 0x60, 0x7C, 0x06, 0x06, 0x66, 0x3C, 0x00, 317 | 0x3C, 0x66, 0x60, 0x7C, 0x66, 0x66, 0x3C, 0x00, 318 | 0x7E, 0x66, 0x0C, 0x18, 0x18, 0x18, 0x18, 0x00, 319 | 0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x3C, 0x00, 320 | 0x3C, 0x66, 0x66, 0x3E, 0x06, 0x66, 0x3C, 0x00, 321 | 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x00, 0x00, 322 | 0x00, 0x00, 0x18, 0x00, 0x00, 0x18, 0x18, 0x30, 323 | 0x0E, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0E, 0x00, 324 | 0x00, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00, 325 | 0x70, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x70, 0x00, 326 | 0x3C, 0x66, 0x06, 0x0C, 0x18, 0x00, 0x18, 0x00, 327 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 328 | 0x18, 0x3C, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, 329 | 0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00, 330 | 0x3C, 0x66, 0x60, 0x60, 0x60, 0x66, 0x3C, 0x00, 331 | 0x78, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0x78, 0x00, 332 | 0x7E, 0x60, 0x60, 0x78, 0x60, 0x60, 0x7E, 0x00, 333 | 0x7E, 0x60, 0x60, 0x78, 0x60, 0x60, 0x60, 0x00, 334 | 0x3C, 0x66, 0x60, 0x6E, 0x66, 0x66, 0x3C, 0x00, 335 | 0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00, 336 | 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 337 | 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x6C, 0x38, 0x00, 338 | 0x66, 0x6C, 0x78, 0x70, 0x78, 0x6C, 0x66, 0x00, 339 | 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x00, 340 | 0x63, 0x77, 0x7F, 0x6B, 0x63, 0x63, 0x63, 0x00, 341 | 0x66, 0x76, 0x7E, 0x7E, 0x6E, 0x66, 0x66, 0x00, 342 | 0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, 343 | 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x00, 344 | 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x0E, 0x00, 345 | 0x7C, 0x66, 0x66, 0x7C, 0x78, 0x6C, 0x66, 0x00, 346 | 0x3C, 0x66, 0x60, 0x3C, 0x06, 0x66, 0x3C, 0x00, 347 | 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 348 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x00, 349 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, 350 | 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00, 351 | 0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x00, 352 | 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x00, 353 | 0x7E, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x7E, 0x00, 354 | 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0x18, 355 | 0xC0, 0xC0, 0x30, 0x30, 0xC0, 0xC0, 0x30, 0x30, 356 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 357 | 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 358 | 0x33, 0x99, 0xCC, 0x66, 0x33, 0x99, 0xCC, 0x66, 359 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 360 | 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 361 | 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 362 | 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 363 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 364 | 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 365 | 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 366 | 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 367 | 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0x33, 0x33, 368 | 0xCC, 0x99, 0x33, 0x66, 0xCC, 0x99, 0x33, 0x66, 369 | 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 370 | 0x18, 0x18, 0x18, 0x1F, 0x1F, 0x18, 0x18, 0x18, 371 | 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x0F, 372 | 0x18, 0x18, 0x18, 0x1F, 0x1F, 0x00, 0x00, 0x00, 373 | 0x00, 0x00, 0x00, 0xF8, 0xF8, 0x18, 0x18, 0x18, 374 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 375 | 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x18, 0x18, 0x18, 376 | 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x00, 0x00, 0x00, 377 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x18, 0x18, 0x18, 378 | 0x18, 0x18, 0x18, 0xF8, 0xF8, 0x18, 0x18, 0x18, 379 | 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 380 | 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 381 | 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 382 | 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 383 | 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 384 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 385 | 0x01, 0x03, 0x06, 0x6C, 0x78, 0x70, 0x60, 0x00, 386 | 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF0, 0xF0, 0xF0, 387 | 0x0F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 388 | 0x18, 0x18, 0x18, 0xF8, 0xF8, 0x00, 0x00, 0x00, 389 | 0xF0, 0xF0, 0xF0, 0xF0, 0x00, 0x00, 0x00, 0x00, 390 | 0xF0, 0xF0, 0xF0, 0xF0, 0x0F, 0x0F, 0x0F, 0x0F, 391 | 0xC3, 0x99, 0x91, 0x91, 0x9F, 0x99, 0xC3, 0xFF, 392 | 0xFF, 0xFF, 0xC3, 0xF9, 0xC1, 0x99, 0xC1, 0xFF, 393 | 0xFF, 0x9F, 0x9F, 0x83, 0x99, 0x99, 0x83, 0xFF, 394 | 0xFF, 0xFF, 0xC3, 0x9F, 0x9F, 0x9F, 0xC3, 0xFF, 395 | 0xFF, 0xF9, 0xF9, 0xC1, 0x99, 0x99, 0xC1, 0xFF, 396 | 0xFF, 0xFF, 0xC3, 0x99, 0x81, 0x9F, 0xC3, 0xFF, 397 | 0xFF, 0xF1, 0xE7, 0xC1, 0xE7, 0xE7, 0xE7, 0xFF, 398 | 0xFF, 0xFF, 0xC1, 0x99, 0x99, 0xC1, 0xF9, 0x83, 399 | 0xFF, 0x9F, 0x9F, 0x83, 0x99, 0x99, 0x99, 0xFF, 400 | 0xFF, 0xE7, 0xFF, 0xC7, 0xE7, 0xE7, 0xC3, 0xFF, 401 | 0xFF, 0xF9, 0xFF, 0xF9, 0xF9, 0xF9, 0xF9, 0xC3, 402 | 0xFF, 0x9F, 0x9F, 0x93, 0x87, 0x93, 0x99, 0xFF, 403 | 0xFF, 0xC7, 0xE7, 0xE7, 0xE7, 0xE7, 0xC3, 0xFF, 404 | 0xFF, 0xFF, 0x99, 0x80, 0x80, 0x94, 0x9C, 0xFF, 405 | 0xFF, 0xFF, 0x83, 0x99, 0x99, 0x99, 0x99, 0xFF, 406 | 0xFF, 0xFF, 0xC3, 0x99, 0x99, 0x99, 0xC3, 0xFF, 407 | 0xFF, 0xFF, 0x83, 0x99, 0x99, 0x83, 0x9F, 0x9F, 408 | 0xFF, 0xFF, 0xC1, 0x99, 0x99, 0xC1, 0xF9, 0xF9, 409 | 0xFF, 0xFF, 0x83, 0x99, 0x9F, 0x9F, 0x9F, 0xFF, 410 | 0xFF, 0xFF, 0xC1, 0x9F, 0xC3, 0xF9, 0x83, 0xFF, 411 | 0xFF, 0xE7, 0x81, 0xE7, 0xE7, 0xE7, 0xF1, 0xFF, 412 | 0xFF, 0xFF, 0x99, 0x99, 0x99, 0x99, 0xC1, 0xFF, 413 | 0xFF, 0xFF, 0x99, 0x99, 0x99, 0xC3, 0xE7, 0xFF, 414 | 0xFF, 0xFF, 0x9C, 0x94, 0x80, 0xC1, 0xC9, 0xFF, 415 | 0xFF, 0xFF, 0x99, 0xC3, 0xE7, 0xC3, 0x99, 0xFF, 416 | 0xFF, 0xFF, 0x99, 0x99, 0x99, 0xC1, 0xF3, 0x87, 417 | 0xFF, 0xFF, 0x81, 0xF3, 0xE7, 0xCF, 0x81, 0xFF, 418 | 0xC3, 0xCF, 0xCF, 0xCF, 0xCF, 0xCF, 0xC3, 0xFF, 419 | 0xF3, 0xED, 0xCF, 0x83, 0xCF, 0x9D, 0x03, 0xFF, 420 | 0xC3, 0xF3, 0xF3, 0xF3, 0xF3, 0xF3, 0xC3, 0xFF, 421 | 0xFF, 0xE7, 0xC3, 0x81, 0xE7, 0xE7, 0xE7, 0xE7, 422 | 0xFF, 0xEF, 0xCF, 0x80, 0x80, 0xCF, 0xEF, 0xFF, 423 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 424 | 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, 0xFF, 0xE7, 0xFF, 425 | 0x99, 0x99, 0x99, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 426 | 0x99, 0x99, 0x00, 0x99, 0x00, 0x99, 0x99, 0xFF, 427 | 0xE7, 0xC1, 0x9F, 0xC3, 0xF9, 0x83, 0xE7, 0xFF, 428 | 0x9D, 0x99, 0xF3, 0xE7, 0xCF, 0x99, 0xB9, 0xFF, 429 | 0xC3, 0x99, 0xC3, 0xC7, 0x98, 0x99, 0xC0, 0xFF, 430 | 0xF9, 0xF3, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 431 | 0xF3, 0xE7, 0xCF, 0xCF, 0xCF, 0xE7, 0xF3, 0xFF, 432 | 0xCF, 0xE7, 0xF3, 0xF3, 0xF3, 0xE7, 0xCF, 0xFF, 433 | 0xFF, 0x99, 0xC3, 0x00, 0xC3, 0x99, 0xFF, 0xFF, 434 | 0xFF, 0xE7, 0xE7, 0x81, 0xE7, 0xE7, 0xFF, 0xFF, 435 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xCF, 436 | 0xFF, 0xFF, 0xFF, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 437 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xE7, 0xFF, 438 | 0xFF, 0xFC, 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0xFF, 439 | 0xC3, 0x99, 0x91, 0x89, 0x99, 0x99, 0xC3, 0xFF, 440 | 0xE7, 0xE7, 0xC7, 0xE7, 0xE7, 0xE7, 0x81, 0xFF, 441 | 0xC3, 0x99, 0xF9, 0xF3, 0xCF, 0x9F, 0x81, 0xFF, 442 | 0xC3, 0x99, 0xF9, 0xE3, 0xF9, 0x99, 0xC3, 0xFF, 443 | 0xF9, 0xF1, 0xE1, 0x99, 0x80, 0xF9, 0xF9, 0xFF, 444 | 0x81, 0x9F, 0x83, 0xF9, 0xF9, 0x99, 0xC3, 0xFF, 445 | 0xC3, 0x99, 0x9F, 0x83, 0x99, 0x99, 0xC3, 0xFF, 446 | 0x81, 0x99, 0xF3, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, 447 | 0xC3, 0x99, 0x99, 0xC3, 0x99, 0x99, 0xC3, 0xFF, 448 | 0xC3, 0x99, 0x99, 0xC1, 0xF9, 0x99, 0xC3, 0xFF, 449 | 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 450 | 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xE7, 0xE7, 0xCF, 451 | 0xF1, 0xE7, 0xCF, 0x9F, 0xCF, 0xE7, 0xF1, 0xFF, 452 | 0xFF, 0xFF, 0x81, 0xFF, 0x81, 0xFF, 0xFF, 0xFF, 453 | 0x8F, 0xE7, 0xF3, 0xF9, 0xF3, 0xE7, 0x8F, 0xFF, 454 | 0xC3, 0x99, 0xF9, 0xF3, 0xE7, 0xFF, 0xE7, 0xFF, 455 | 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 456 | 0xE7, 0xC3, 0x99, 0x81, 0x99, 0x99, 0x99, 0xFF, 457 | 0x83, 0x99, 0x99, 0x83, 0x99, 0x99, 0x83, 0xFF, 458 | 0xC3, 0x99, 0x9F, 0x9F, 0x9F, 0x99, 0xC3, 0xFF, 459 | 0x87, 0x93, 0x99, 0x99, 0x99, 0x93, 0x87, 0xFF, 460 | 0x81, 0x9F, 0x9F, 0x87, 0x9F, 0x9F, 0x81, 0xFF, 461 | 0x81, 0x9F, 0x9F, 0x87, 0x9F, 0x9F, 0x9F, 0xFF, 462 | 0xC3, 0x99, 0x9F, 0x91, 0x99, 0x99, 0xC3, 0xFF, 463 | 0x99, 0x99, 0x99, 0x81, 0x99, 0x99, 0x99, 0xFF, 464 | 0xC3, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xC3, 0xFF, 465 | 0xE1, 0xF3, 0xF3, 0xF3, 0xF3, 0x93, 0xC7, 0xFF, 466 | 0x99, 0x93, 0x87, 0x8F, 0x87, 0x93, 0x99, 0xFF, 467 | 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x81, 0xFF, 468 | 0x9C, 0x88, 0x80, 0x94, 0x9C, 0x9C, 0x9C, 0xFF, 469 | 0x99, 0x89, 0x81, 0x81, 0x91, 0x99, 0x99, 0xFF, 470 | 0xC3, 0x99, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xFF, 471 | 0x83, 0x99, 0x99, 0x83, 0x9F, 0x9F, 0x9F, 0xFF, 472 | 0xC3, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xF1, 0xFF, 473 | 0x83, 0x99, 0x99, 0x83, 0x87, 0x93, 0x99, 0xFF, 474 | 0xC3, 0x99, 0x9F, 0xC3, 0xF9, 0x99, 0xC3, 0xFF, 475 | 0x81, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xFF, 476 | 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xFF, 477 | 0x99, 0x99, 0x99, 0x99, 0x99, 0xC3, 0xE7, 0xFF, 478 | 0x9C, 0x9C, 0x9C, 0x94, 0x80, 0x88, 0x9C, 0xFF, 479 | 0x99, 0x99, 0xC3, 0xE7, 0xC3, 0x99, 0x99, 0xFF, 480 | 0x99, 0x99, 0x99, 0xC3, 0xE7, 0xE7, 0xE7, 0xFF, 481 | 0x81, 0xF9, 0xF3, 0xE7, 0xCF, 0x9F, 0x81, 0xFF, 482 | 0xE7, 0xE7, 0xE7, 0x00, 0x00, 0xE7, 0xE7, 0xE7, 483 | 0x3F, 0x3F, 0xCF, 0xCF, 0x3F, 0x3F, 0xCF, 0xCF, 484 | 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 0xE7, 485 | 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 486 | 0xCC, 0x66, 0x33, 0x99, 0xCC, 0x66, 0x33, 0x99, 487 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 488 | 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 489 | 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 490 | 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 491 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 492 | 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 493 | 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 494 | 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 495 | 0xFF, 0xFF, 0xFF, 0xFF, 0x33, 0x33, 0xCC, 0xCC, 496 | 0x33, 0x66, 0xCC, 0x99, 0x33, 0x66, 0xCC, 0x99, 497 | 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 498 | 0xE7, 0xE7, 0xE7, 0xE0, 0xE0, 0xE7, 0xE7, 0xE7, 499 | 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xF0, 0xF0, 0xF0, 500 | 0xE7, 0xE7, 0xE7, 0xE0, 0xE0, 0xFF, 0xFF, 0xFF, 501 | 0xFF, 0xFF, 0xFF, 0x07, 0x07, 0xE7, 0xE7, 0xE7, 502 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 503 | 0xFF, 0xFF, 0xFF, 0xE0, 0xE0, 0xE7, 0xE7, 0xE7, 504 | 0xE7, 0xE7, 0xE7, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 505 | 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xE7, 0xE7, 0xE7, 506 | 0xE7, 0xE7, 0xE7, 0x07, 0x07, 0xE7, 0xE7, 0xE7, 507 | 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 508 | 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 509 | 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 510 | 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 511 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 512 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 513 | 0xFE, 0xFC, 0xF9, 0x93, 0x87, 0x8F, 0x9F, 0xFF, 514 | 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x0F, 0x0F, 0x0F, 515 | 0xF0, 0xF0, 0xF0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 516 | 0xE7, 0xE7, 0xE7, 0x07, 0x07, 0xFF, 0xFF, 0xFF, 517 | 0x0F, 0x0F, 0x0F, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 518 | 0x0F, 0x0F, 0x0F, 0x0F, 0xF0, 0xF0, 0xF0, 0xF0 }; 519 | 520 | } 521 | -------------------------------------------------------------------------------- /examples/simple_game/commodore64.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_C_C64_HPP 2 | #define INC_6502_C_C64_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "6502.hpp" 9 | #include "petscii.hpp" 10 | #include "vicii.hpp" 11 | 12 | namespace c64 { 13 | 14 | struct Joystick 15 | { 16 | std::uint8_t state{}; 17 | 18 | [[nodiscard]] constexpr bool up() const noexcept { return (state & 1) == 0; } 19 | [[nodiscard]] constexpr bool left() const noexcept { return (state & 4) == 0; } 20 | [[nodiscard]] constexpr bool fire() const noexcept { return (state & 16) == 0; } 21 | [[nodiscard]] constexpr bool right() const noexcept { return (state & 8) == 0; } 22 | [[nodiscard]] constexpr bool down() const noexcept { return (state & 2) == 0; } 23 | 24 | [[nodiscard]] constexpr bool operator==(const Joystick &other) const = default; 25 | [[nodiscard]] constexpr bool operator!=(const Joystick &other) const = default; 26 | }; 27 | 28 | struct Clock 29 | { 30 | using milliseconds = std::chrono::duration; 31 | static constexpr std::uint16_t TimerAControl = 0xDC0E; 32 | static constexpr std::uint16_t TimerALowByte = 0xDC04; 33 | static constexpr std::uint16_t TimerAHighByte = 0xDC05; 34 | 35 | 36 | // return elapsed time since last restart 37 | [[nodiscard]] milliseconds restart() noexcept 38 | { 39 | // stop Timer A 40 | mos6502::poke(TimerAControl, 0b00000000); 41 | 42 | // last value 43 | const auto previous_value = static_cast( 44 | mos6502::peek(TimerALowByte) | (static_cast(mos6502::peek(TimerAHighByte)) << 8)); 45 | 46 | // reset timer 47 | mos6502::poke(TimerALowByte, 0xFF); 48 | mos6502::poke(TimerAHighByte, 0xFF); 49 | 50 | // restart timer A 51 | mos6502::poke(TimerAControl, 0b00010001); 52 | 53 | return milliseconds{ 0xFFFF - previous_value }; 54 | } 55 | 56 | Clock() noexcept { [[maybe_unused]] const auto value = restart(); } 57 | }; 58 | }// namespace c64 59 | 60 | #endif// INC_6502_C_C64_HPP 61 | -------------------------------------------------------------------------------- /examples/simple_game/game.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "chargen.hpp" 12 | #include "commodore64.hpp" 13 | 14 | struct GameState; 15 | 16 | using Square_Passable = bool (*)(const std::uint8_t type) noexcept; 17 | 18 | struct Map_Action 19 | { 20 | geometry::rect box; 21 | 22 | using Action_Func = void (*)(GameState &); 23 | Action_Func action = nullptr; 24 | 25 | constexpr void execute_if_collision(geometry::rect object, GameState &game) const 26 | { 27 | if (action == nullptr) { return; } 28 | 29 | if (box.intersects(object)) { action(game); } 30 | } 31 | }; 32 | 33 | 34 | template struct Map 35 | { 36 | std::string_view name; 37 | petscii::Graphic layout; 38 | Square_Passable passable = nullptr; 39 | std::uint8_t step_scale; 40 | std::span actions; 41 | 42 | [[nodiscard]] constexpr std::uint8_t location_value(geometry::point loc) const noexcept 43 | { 44 | const auto descaled_location = 45 | geometry::point{ static_cast(loc.x / Scale), static_cast(loc.y / Scale) }; 46 | return layout[descaled_location]; 47 | } 48 | 49 | [[nodiscard]] constexpr bool location_passable(geometry::point loc, geometry::size obj_size) const noexcept 50 | { 51 | if (passable != nullptr) { 52 | for (const auto &p : obj_size) { 53 | if (!passable(location_value(p + loc))) { return false; } 54 | } 55 | return true; 56 | } 57 | return false; 58 | } 59 | }; 60 | 61 | struct GameState 62 | { 63 | enum struct State { Walking, SystemMenu, AboutBox, Exit, AlmostDead }; 64 | State state = State::Walking; 65 | 66 | std::uint8_t endurance{ 8 }; 67 | std::uint8_t stamina{ max_stamina() }; 68 | std::uint16_t cash{ 100 }; 69 | std::uint8_t step_counter{ 0 }; 70 | std::uint8_t stamina_counter{ 0 }; 71 | geometry::point location{ 20, 12 }; 72 | 73 | bool redraw = true; 74 | bool redraw_stats = true; 75 | 76 | c64::Clock game_clock{}; 77 | 78 | Map const *current_map = nullptr; 79 | Map const *last_map = nullptr; 80 | 81 | constexpr void goto_last_map(geometry::point new_location) 82 | { 83 | std::swap(current_map, last_map); 84 | location = new_location; 85 | redraw = true; 86 | } 87 | 88 | constexpr void set_current_map(const Map &new_map) 89 | { 90 | last_map = std::exchange(current_map, &new_map); 91 | redraw = true; 92 | } 93 | 94 | constexpr void execute_actions(geometry::point new_location, const auto &character) noexcept 95 | { 96 | if (new_location.x + character.size().width > 40) { new_location.x = location.x; } 97 | if (new_location.y + character.size().height > 20) { new_location.y = location.y; } 98 | 99 | 100 | if (current_map && current_map->location_passable(new_location, character.size())) { 101 | step_counter += current_map->step_scale; 102 | 103 | while (step_counter >= endurance) { 104 | redraw_stats = true; 105 | step_counter -= endurance; 106 | 107 | if (stamina == 1) { 108 | state = State::AlmostDead; 109 | } else { 110 | stamina -= 1; 111 | } 112 | 113 | stamina_counter += 1; 114 | 115 | if (stamina_counter == endurance * 3) { 116 | endurance += 1; 117 | stamina_counter = 0; 118 | } 119 | } 120 | 121 | location = new_location; 122 | 123 | for (const auto &action : current_map->actions) { 124 | action.execute_if_collision({ location, character.size() }, *this); 125 | } 126 | } 127 | } 128 | 129 | [[nodiscard]] constexpr std::uint8_t max_stamina() const noexcept { return endurance * 5; } 130 | 131 | struct JoyStick1StateChanged 132 | { 133 | c64::Joystick state; 134 | }; 135 | 136 | 137 | struct JoyStick2StateChanged 138 | { 139 | c64::Joystick state; 140 | }; 141 | 142 | struct TimeElapsed 143 | { 144 | c64::Clock::milliseconds us; 145 | }; 146 | 147 | using Event = std::variant; 148 | 149 | std::uint8_t last_joystick_2_state = mos6502::peek(0xDC00); 150 | 151 | Event next_event() noexcept 152 | { 153 | const auto new_joystick_2_state = mos6502::peek(0xDC00); 154 | 155 | if (new_joystick_2_state != last_joystick_2_state) { 156 | last_joystick_2_state = new_joystick_2_state; 157 | return JoyStick2StateChanged{ c64::Joystick{ new_joystick_2_state } }; 158 | } 159 | 160 | return TimeElapsed{ game_clock.restart() }; 161 | } 162 | }; 163 | 164 | 165 | 166 | template static constexpr auto from_pixels_to_petscii(const petscii::Graphic &pixels) 167 | { 168 | petscii::Graphic result{}; 169 | 170 | constexpr auto charset = petscii::load_charset(petscii::uppercase); 171 | 172 | for (uint8_t x = 0; x < pixels.width(); x += 8) { 173 | for (uint8_t y = 0; y < pixels.height(); y += 8) { 174 | std::uint8_t best_match = 32; 175 | std::size_t match_count = 0; 176 | 177 | std::uint8_t cur_char = 0; 178 | for (const auto &glyph : charset) { 179 | 180 | const auto count = pixels.match_count(glyph, x, y); 181 | if (count > match_count) { 182 | best_match = cur_char; 183 | match_count = count; 184 | } 185 | 186 | ++cur_char; 187 | } 188 | 189 | result(x / 8, y / 8) = best_match; 190 | } 191 | } 192 | 193 | return result; 194 | } 195 | 196 | template struct SimpleSprite 197 | { 198 | geometry::point location{}; 199 | bool is_shown = false; 200 | petscii::Graphic graphic; 201 | petscii::Graphic saved_background{}; 202 | 203 | constexpr SimpleSprite(std::initializer_list data_) noexcept : graphic(data_) {} 204 | }; 205 | 206 | 207 | // helper type for the visitor #4 208 | template struct overloaded : Ts... 209 | { 210 | using Ts::operator()...; 211 | }; 212 | // explicit deduction guide (not needed as of C++20) 213 | template overloaded(Ts...) -> overloaded; 214 | 215 | 216 | struct TextBox 217 | { 218 | consteval TextBox(std::span t_lines) 219 | : lines{ t_lines }, box{ geometry::rect{ { 0, 0 }, 220 | { static_cast( 221 | std::max_element(begin(lines), 222 | end(lines), 223 | [](std::string_view lhs, std::string_view rhs) { return lhs.size() < rhs.size(); }) 224 | ->size() 225 | + 1), 226 | static_cast(lines.size() + 1) } } 227 | .centered() } 228 | {} 229 | 230 | void hide(GameState &game) 231 | { 232 | displayed = false; 233 | game.redraw = true; 234 | } 235 | 236 | bool show([[maybe_unused]] GameState &game) 237 | { 238 | if (!displayed) { 239 | displayed = true; 240 | 241 | clear(box, vicii::Colors::grey); 242 | draw_box(box, vicii::Colors::white); 243 | 244 | for (auto pos = box.top_left() + geometry::point{ 1, 1 }; const auto &str : lines) { 245 | puts(pos, str, vicii::Colors::grey); 246 | pos = pos + geometry::point{ 0, 1 }; 247 | } 248 | } 249 | 250 | if (selected) { 251 | selected = false; 252 | return true; 253 | } else { 254 | return false; 255 | } 256 | } 257 | 258 | bool process_event(const GameState::Event &e) 259 | { 260 | if (not displayed) { return false; } 261 | 262 | if (const auto *ptr = std::get_if(&e); ptr) { 263 | if (ptr->state.fire()) { selected = true; } 264 | return true; 265 | } 266 | 267 | return false; 268 | } 269 | 270 | std::span lines; 271 | geometry::rect box; 272 | bool selected{ false }; 273 | bool displayed{ false }; 274 | }; 275 | 276 | struct Menu 277 | { 278 | consteval Menu(std::span t_options) 279 | : options{ t_options }, box{ geometry::rect{ { 0, 0 }, 280 | { static_cast( 281 | std::max_element(begin(options), 282 | end(options), 283 | [](std::string_view lhs, std::string_view rhs) { return lhs.size() < rhs.size(); }) 284 | ->size() 285 | + 1), 286 | static_cast(options.size() + 1) } } 287 | .centered() } 288 | 289 | {} 290 | 291 | void highlight(std::uint8_t selection) 292 | { 293 | const auto cur_y = static_cast(selection + 1 + box.top_left().y); 294 | for (std::uint8_t cur_x = 1; cur_x < box.width(); ++cur_x) { 295 | vicii::invertc(geometry::point{ static_cast(box.top_left().x + cur_x), cur_y }); 296 | } 297 | } 298 | 299 | void unhighlight(std::uint8_t selection) { highlight(selection); } 300 | 301 | void hide(GameState &game) 302 | { 303 | displayed = false; 304 | game.redraw = true; 305 | } 306 | 307 | bool show([[maybe_unused]] GameState &game, std::uint8_t &selection) 308 | { 309 | if (!displayed) { 310 | displayed = true; 311 | 312 | clear(box, vicii::Colors::grey); 313 | draw_box(box, vicii::Colors::white); 314 | 315 | for (auto pos = box.top_left() + geometry::point{ 1, 1 }; const auto &str : options) { 316 | puts(pos, str, vicii::Colors::grey); 317 | pos = pos + geometry::point{ 0, 1 }; 318 | } 319 | 320 | highlight(current_selection); 321 | } 322 | 323 | if (current_selection != next_selection) { 324 | unhighlight(current_selection); 325 | highlight(next_selection); 326 | current_selection = next_selection; 327 | } 328 | 329 | if (selected) { 330 | selected = false; 331 | selection = current_selection; 332 | return true; 333 | } else { 334 | return false; 335 | } 336 | } 337 | 338 | bool process_event(const GameState::Event &e) 339 | { 340 | if (not displayed) { return false; } 341 | 342 | if (const auto *ptr = std::get_if(&e); ptr) { 343 | // wrap around up and down during selection 344 | if (ptr->state.up()) { 345 | if (current_selection == 0) { 346 | next_selection = static_cast(options.size() - 1); 347 | } else { 348 | next_selection = current_selection - 1; 349 | } 350 | } 351 | 352 | if (ptr->state.down()) { 353 | if (current_selection == options.size() - 1) { 354 | next_selection = 0; 355 | } else { 356 | next_selection = current_selection + 1; 357 | } 358 | } 359 | 360 | if (ptr->state.fire()) { selected = true; } 361 | 362 | return true; 363 | } 364 | 365 | return false; 366 | } 367 | 368 | std::span options; 369 | geometry::rect box; 370 | std::uint8_t current_selection{ 0 }; 371 | std::uint8_t next_selection{ 0 }; 372 | bool selected{ false }; 373 | bool displayed{ false }; 374 | }; 375 | 376 | 377 | int main() 378 | { 379 | // static constexpr auto charset = load_charset(uppercase); 380 | 381 | // clang-format off 382 | static constexpr auto inn = petscii::Graphic { 383 | 32,233,160,160,223,32, 384 | 233,160,160,160,160,223, 385 | 160,137,142,142,160,160, 386 | 160,160,160,160,79,160, 387 | 160,160,160,160,76,160, 388 | }; 389 | 390 | static constexpr auto gym = petscii::Graphic { 391 | 32,233,160,160,223,32, 392 | 233,160,160,160,160,223, 393 | 160,135,153,141,160,160, 394 | 160,160,160,160,79,160, 395 | 160,160,160,160,76,160, 396 | }; 397 | 398 | static constexpr auto trading_post = petscii::Graphic { 399 | 32,233,160,160,223,32, 400 | 233,160,160,160,160,223, 401 | 148,146,129,132,133,160, 402 | 160,160,160,160,79,160, 403 | 160,160,160,160,76,160, 404 | }; 405 | 406 | static constexpr auto town = petscii::ColoredGraphic{ 407 | { 408 | 32, 32,32, 32, 409 | 233,223,233,223, 410 | 224,224,224,224, 411 | 104,104,104,104 412 | }, 413 | { 414 | 2,2,10,10, 415 | 4,4,7,7, 416 | 4,4,7,7, 417 | 11,11,11,11 418 | } 419 | }; 420 | 421 | static constexpr auto mountain = petscii::ColoredGraphic{ 422 | { 423 | 32, 78, 77, 32, 424 | 32, 32, 233, 223, 425 | 233, 223, 32, 32, 426 | 32, 78, 77, 32 }, 427 | { 428 | 1, 9, 9, 1, 429 | 1, 1, 8, 8, 430 | 9, 9, 1, 1, 431 | 1, 8, 8, 1 432 | 433 | } 434 | }; 435 | 436 | 437 | auto character = SimpleSprite{ 438 | 32, 87, 439 | 78, 79, 440 | 78, 77 }; 441 | 442 | static constexpr auto ore_town_actions = std::array { 443 | Map_Action { {{0,19},{40,1}}, [](GameState &g) { g.goto_last_map({0, 4}); } }, 444 | }; 445 | 446 | static constexpr auto ore_town = Map{ 447 | "ore town", 448 | { 449 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 450 | 0, 4, 0, 0, 0, 0, 6, 0, 0, 1, 451 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 452 | 0, 0, 5, 0, 0, 0, 0, 0, 0, 1, 453 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 454 | }, 455 | [](const std::uint8_t type) noexcept { return type != 1; }, 456 | 1, 457 | ore_town_actions 458 | }; 459 | 460 | static constexpr auto wool_town_actions = std::array { 461 | Map_Action { {{0,0},{1,20}}, [](GameState &g) { g.goto_last_map({6, 12}); } }, 462 | Map_Action { {{39,0},{1,20}}, [](GameState &g) { g.goto_last_map({12, 12}); } }, 463 | }; 464 | 465 | static constexpr auto wool_town = Map{ 466 | "wool town", 467 | { 468 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 469 | 0, 4, 0, 0, 0, 0, 6, 0, 0, 0, 470 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 471 | 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 472 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 473 | }, 474 | [](const std::uint8_t type) noexcept { return type != 1; }, 475 | 1, 476 | wool_town_actions 477 | }; 478 | 479 | static constexpr auto wheat_town_actions = std::array { 480 | Map_Action { {{0,0},{1,20}}, [](GameState &g) { g.goto_last_map({22, 16}); } }, 481 | Map_Action { {{39,0},{1,20}}, [](GameState &g) { g.goto_last_map({28, 16}); } }, 482 | Map_Action { {{0,0},{40,1}}, [](GameState &g) { g.goto_last_map({22, 13}); } }, 483 | }; 484 | 485 | 486 | static constexpr auto wheat_town = Map{ 487 | "wheat town", 488 | { 489 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 490 | 0, 4, 0, 0, 0, 0, 6, 0, 0, 0, 491 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 492 | 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 493 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 494 | }, 495 | [](const std::uint8_t type) noexcept { return type != 1; }, 496 | 1, 497 | wheat_town_actions 498 | }; 499 | 500 | static constexpr auto brick_town_actions = std::array { 501 | Map_Action { {{0,0},{1,20}}, [](GameState &g) { g.goto_last_map({30, 4}); } }, 502 | Map_Action { {{39,0},{1,20}}, [](GameState &g) { g.goto_last_map({36, 4}); } }, 503 | Map_Action { {{0,0},{40,1}}, [](GameState &g) { g.goto_last_map({32, 0}); } }, 504 | Map_Action { {{0,19},{40,1}}, [](GameState &g) { g.goto_last_map({32, 8}); } }, 505 | }; 506 | 507 | static constexpr auto brick_town = Map{ 508 | "brick town", 509 | { 510 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 511 | 0, 4, 0, 0, 0, 0, 6, 0, 0, 0, 512 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 513 | 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 514 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 515 | }, 516 | [](const std::uint8_t type) noexcept { return type != 1; }, 517 | 1, 518 | brick_town_actions 519 | }; 520 | 521 | static constexpr auto wood_town_actions = std::array { 522 | Map_Action { {{0,0},{1,20}}, [](GameState &g) { g.goto_last_map({14, 0}); } }, 523 | Map_Action { {{39,0},{1,20}}, [](GameState &g) { g.goto_last_map({20, 0}); } }, 524 | Map_Action { {{0,19},{40,1}}, [](GameState &g) { g.goto_last_map({16, 4}); } }, 525 | }; 526 | 527 | static constexpr auto wood_town = Map{ 528 | "wood town", 529 | { 530 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 531 | 0, 4, 0, 0, 0, 0, 6, 0, 0, 0, 532 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 533 | 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 534 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 535 | }, 536 | [](const std::uint8_t type) noexcept { return type != 1; }, 537 | 1, 538 | wood_town_actions 539 | }; 540 | 541 | 542 | static constexpr auto overview_actions = std::array { 543 | Map_Action { {{0,0},{4,4}}, [](GameState &g) { g.set_current_map(ore_town); } }, 544 | Map_Action { {{8,12},{4,4}}, [](GameState &g) { g.set_current_map(wool_town); } }, 545 | Map_Action { {{24,16},{4,4}}, [](GameState &g) { g.set_current_map(wheat_town); } }, 546 | Map_Action { {{32,4},{4,4}}, [](GameState &g) { g.set_current_map(brick_town); } }, 547 | Map_Action { {{16,0},{4,4}}, [](GameState &g) { g.set_current_map(wood_town); } } 548 | }; 549 | 550 | static constexpr auto overview_map = Map{ 551 | "the world", 552 | { 553 | 3, 1, 1, 0, 3, 0, 0, 0, 0, 0, 554 | 0, 0, 1, 1, 0, 0, 0, 0, 3, 0, 555 | 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 556 | 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 557 | 0, 1, 1, 1, 0, 0, 3, 0, 0, 0, 558 | }, 559 | [](const std::uint8_t type) noexcept { return type != 1; }, 560 | 10, 561 | overview_actions 562 | }; 563 | // clang-format on 564 | 565 | 566 | static constexpr std::array tile_types{ 567 | [](geometry::point) {}, 568 | [](geometry::point p) { vicii::put_graphic(p, mountain); }, 569 | [](geometry::point) {}, 570 | [](geometry::point p) { vicii::put_graphic(p, town); }, 571 | [](geometry::point p) { vicii::put_graphic(p, inn); }, 572 | [](geometry::point p) { vicii::put_graphic(p, gym); }, 573 | [](geometry::point p) { vicii::put_graphic(p, trading_post); }, 574 | }; 575 | 576 | 577 | const auto draw_map = [](const auto &map) { 578 | for (std::size_t tile = 0; tile < tile_types.size(); ++tile) { 579 | for (const auto &pos : map.size()) { 580 | if (map[pos] == tile) { 581 | tile_types[tile]({ static_cast(pos.x * 4), static_cast(pos.y * 4) }); 582 | } 583 | } 584 | } 585 | }; 586 | 587 | GameState game; 588 | game.state = GameState::State::Walking; 589 | game.current_map = &overview_map; 590 | 591 | constexpr auto show_stats = [](const auto &cur_game) { 592 | puts(geometry::point{ 1, 21 }, petscii::PETSCII("STAMINA:"), vicii::Colors::light_grey); 593 | put_hex(geometry::point{ 12, 21 }, cur_game.stamina, vicii::Colors::white); 594 | put_hex(geometry::point{ 15, 21 }, cur_game.max_stamina(), vicii::Colors::white); 595 | puts(geometry::point{ 14, 21 }, petscii::PETSCII("/"), vicii::Colors::light_grey); 596 | puts(geometry::point{ 1, 22 }, petscii::PETSCII("ENDURANCE:"), vicii::Colors::light_grey); 597 | put_hex(geometry::point{ 12, 22 }, cur_game.endurance, vicii::Colors::white); 598 | puts(geometry::point{ 1, 23 }, petscii::PETSCII("CASH:"), vicii::Colors::light_grey); 599 | put_hex(geometry::point{ 12, 23 }, cur_game.cash, vicii::Colors::white); 600 | }; 601 | 602 | vicii::Screen screen; 603 | 604 | static constexpr auto menu_options = std::array{ std::string_view{ "about game" }, std::string_view{ "exit menu" } }; 605 | 606 | Menu m(menu_options); 607 | 608 | static constexpr auto url = petscii::PETSCII("HTTPS://GITHUB.COM/LEFTICUS/6502-CPP"); 609 | 610 | static constexpr auto about_text = std::array{ std::string_view{ "created in c++20 by jason turner" }, 611 | std::string_view{ "using an automated conversion of" }, 612 | std::string_view{ "gcc generated avr code to 6502" }, 613 | std::string_view{ "assembly." }, 614 | std::string_view{ url.data(), url.size() } }; 615 | 616 | TextBox about_box(about_text); 617 | 618 | static constexpr auto almost_dead_text = std::array{ std::string_view{ "you became so exhausted that you" }, 619 | std::string_view{ "passed out and passers by stole" }, 620 | std::string_view{ "some of your cash and items." }, 621 | std::string_view{ "" }, 622 | std::string_view{ "a kind soul has dropped you off at a" }, 623 | std::string_view{ "nearby inn." } }; 624 | 625 | TextBox almost_dead(almost_dead_text); 626 | 627 | auto eventHandler = overloaded{ [&](const GameState::JoyStick2StateChanged &e) { 628 | auto new_loc = game.location; 629 | put_hex({ 36, 1 }, e.state.state, vicii::Colors::dark_grey); 630 | 631 | if (e.state.fire()) { 632 | game.state = GameState::State::SystemMenu; 633 | return; 634 | } 635 | 636 | if (e.state.up()) { --new_loc.y; } 637 | if (e.state.down()) { ++new_loc.y; } 638 | if (e.state.left()) { --new_loc.x; } 639 | if (e.state.right()) { ++new_loc.x; } 640 | 641 | if (new_loc != game.location) { 642 | game.execute_actions(new_loc, character.graphic); 643 | screen.show(game.location, character); 644 | } 645 | }, 646 | [](const GameState::TimeElapsed &e) { 647 | vicii::put_hex({ 36, 0 }, e.us.count(), vicii::Colors::dark_grey); 648 | } }; 649 | 650 | while (game.state != GameState::State::Exit) { 651 | const auto next_event = game.next_event(); 652 | 653 | if (not m.process_event(next_event) && not about_box.process_event(next_event) 654 | && not almost_dead.process_event(next_event)) { 655 | // if no gui elements needed the event, then we handle it 656 | std::visit(eventHandler, next_event); 657 | } 658 | 659 | if (game.redraw) { 660 | screen.hide(character); 661 | vicii::cls(); 662 | 663 | mos6502::poke(53280, 0); 664 | mos6502::poke(53281, 0); 665 | 666 | game.redraw = false; 667 | game.redraw_stats = true; 668 | draw_map(game.current_map->layout); 669 | draw_box(geometry::rect{ { 0, 20 }, { 39, 4 } }, vicii::Colors::dark_grey); 670 | 671 | puts(geometry::point{ 10, 20 }, game.current_map->name, vicii::Colors::white); 672 | 673 | screen.show(game.location, character); 674 | } 675 | 676 | if (game.redraw_stats) { 677 | show_stats(game); 678 | game.redraw_stats = false; 679 | } 680 | 681 | if (std::uint8_t result = 0; game.state == GameState::State::SystemMenu && m.show(game, result)) { 682 | // we had a menu item selected 683 | m.hide(game); 684 | if (result == 0) { 685 | game.state = GameState::State::AboutBox; 686 | } else { 687 | game.state = GameState::State::Walking; 688 | } 689 | } else if (game.state == GameState::State::AboutBox && about_box.show(game)) { 690 | about_box.hide(game); 691 | game.state = GameState::State::Walking; 692 | } else if (game.state == GameState::State::AlmostDead && almost_dead.show(game)) { 693 | almost_dead.hide(game); 694 | game.set_current_map(wheat_town); 695 | game.cash /= 2; 696 | game.stamina = game.max_stamina(); 697 | game.state = GameState::State::Walking; 698 | } 699 | } 700 | } 701 | -------------------------------------------------------------------------------- /examples/simple_game/geometry.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_C_GEOMETRY_HPP 2 | #define INC_6502_C_GEOMETRY_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace geometry { 8 | struct point 9 | { 10 | std::uint8_t x; 11 | std::uint8_t y; 12 | 13 | [[nodiscard]] constexpr auto operator<=>(const point &other) const = default; 14 | }; 15 | 16 | constexpr point operator+(const point &lhs, const point &rhs) noexcept 17 | { 18 | return point{ static_cast(lhs.x + rhs.x), static_cast(lhs.y + rhs.y) }; 19 | } 20 | 21 | struct size 22 | { 23 | std::uint8_t width; 24 | std::uint8_t height; 25 | [[nodiscard]] constexpr bool operator==(const size &other) const = default; 26 | [[nodiscard]] constexpr bool operator!=(const size &other) const = default; 27 | }; 28 | 29 | struct rect 30 | { 31 | [[nodiscard]] constexpr std::uint8_t left() const noexcept { return tl.x; } 32 | [[nodiscard]] constexpr std::uint8_t top() const noexcept { return tl.y; } 33 | [[nodiscard]] constexpr std::uint8_t bottom() const noexcept 34 | { 35 | return static_cast(tl.y + size_.height); 36 | } 37 | [[nodiscard]] constexpr std::uint8_t right() const noexcept { return static_cast(tl.x + size_.width); } 38 | 39 | [[nodiscard]] constexpr const point &top_left() const noexcept { return tl; } 40 | [[nodiscard]] constexpr point bottom_right() const noexcept { return point{ right(), bottom() }; } 41 | [[nodiscard]] constexpr point bottom_left() const noexcept { return point{ left(), bottom() }; } 42 | [[nodiscard]] constexpr point top_right() const noexcept { return point{ right(), top() }; } 43 | [[nodiscard]] constexpr std::uint8_t width() const noexcept { return size_.width; } 44 | [[nodiscard]] constexpr std::uint8_t height() const noexcept { return size_.height; } 45 | 46 | [[nodiscard]] constexpr const auto &size() const noexcept { return size_; } 47 | 48 | // returns a rectangle of this size, but centered in the screen (40x20) 49 | [[nodiscard]] constexpr auto centered() const noexcept 50 | { 51 | return rect{ 52 | { static_cast((40 - size_.width) / 2), static_cast((20 - size_.height) / 2) }, size_ 53 | }; 54 | } 55 | 56 | [[nodiscard]] constexpr bool intersects(const rect &other) const noexcept 57 | { 58 | const auto my_tl = top_left(); 59 | const auto my_br = bottom_right(); 60 | 61 | const auto other_tl = other.top_left(); 62 | const auto other_br = other.bottom_right(); 63 | 64 | return my_tl.x < other_br.x && my_br.x > other_tl.x && my_tl.y < other_br.y && my_br.y > other_tl.y; 65 | }; 66 | 67 | point tl; 68 | geometry::size size_; 69 | }; 70 | 71 | struct point_in_size 72 | { 73 | point p; 74 | size s; 75 | 76 | constexpr auto &operator++() noexcept 77 | { 78 | ++p.x; 79 | if (p.x == s.width) { 80 | p.x = 0; 81 | ++p.y; 82 | } 83 | return *this; 84 | } 85 | [[nodiscard]] constexpr bool operator==(const point_in_size &other) const = default; 86 | [[nodiscard]] constexpr bool operator!=(const point_in_size &other) const = default; 87 | [[nodiscard]] constexpr const point &operator*() const { return p; } 88 | }; 89 | 90 | 91 | constexpr auto begin(const size s) { return point_in_size{ { 0, 0 }, s }; } 92 | 93 | constexpr auto end(const size s) { return point_in_size{ { 0, s.height }, s }; } 94 | }// namespace geometry 95 | 96 | #endif// INC_6502_C_GEOMETRY_HPP 97 | -------------------------------------------------------------------------------- /examples/simple_game/petscii.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_C_PETSCII_HPP 2 | #define INC_6502_C_PETSCII_HPP 3 | 4 | #include "geometry.hpp" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace petscii { 10 | 11 | enum named_chars : std::uint8_t { 12 | closed_circle = 81 13 | }; 14 | 15 | template struct Graphic 16 | { 17 | std::array data{}; 18 | 19 | static constexpr const auto &size() noexcept { return size_; } 20 | 21 | constexpr Graphic() = default; 22 | 23 | constexpr explicit Graphic(std::array data_) noexcept : data(data_) {} 24 | constexpr Graphic(std::initializer_list data_) noexcept { std::copy(begin(data_), end(data_), begin(data)); } 25 | 26 | constexpr auto &operator[](const geometry::point p) noexcept 27 | { 28 | return data[static_cast(p.y * size_.width + p.x)]; 29 | } 30 | 31 | constexpr const auto &operator[](const geometry::point p) const noexcept { return data[static_cast(p.y * size_.width + p.x)]; } 32 | 33 | constexpr std::size_t match_count(const auto &graphic, const geometry::point start_point) const 34 | { 35 | std::size_t count = 0; 36 | 37 | for (const auto &point : graphic.size()) { 38 | if (graphic[point] == (*this)[point + start_point]) { ++count; } 39 | } 40 | 41 | return count; 42 | } 43 | 44 | 45 | constexpr bool match(const auto &graphic, const geometry::point p) const 46 | { 47 | return match_count(graphic, p) == (graphic.size().width * graphic.size().height); 48 | } 49 | }; 50 | 51 | template struct ColoredGraphic 52 | { 53 | Graphic data; 54 | Graphic colors; 55 | }; 56 | 57 | 58 | [[nodiscard]] constexpr char charToPETSCII2(char c) noexcept 59 | { 60 | if (c >= 'a' && c <= 'z') { return c - 'a' + 1; } 61 | if (c >= 'A' && c <= 'Z') { return c - 'A' + 65; } 62 | if (c == '@') { return 0; } 63 | if (c =='_') { return 100; } 64 | return c; 65 | } 66 | 67 | 68 | [[nodiscard]] constexpr char charToPETSCII(char c) noexcept 69 | { 70 | if (c == '@') { return 0; } 71 | if (c >= 'A' && c <= 'Z') { return c - 'A' + 1; } 72 | return c; 73 | } 74 | 75 | template constexpr auto PETSCII2(const char (&value)[Size]) noexcept 76 | { 77 | std::array result{}; 78 | std::transform(std::begin(value), std::prev(std::end(value)), std::begin(result), charToPETSCII2); 79 | return result; 80 | } 81 | template constexpr auto PETSCII(const char (&value)[Size]) noexcept 82 | { 83 | std::array result{}; 84 | std::transform(std::begin(value), std::prev(std::end(value)), std::begin(result), charToPETSCII); 85 | return result; 86 | } 87 | 88 | static constexpr auto load_charset(std::span bits) 89 | { 90 | std::array, 256> results{}; 91 | 92 | for (std::size_t idx = 0; idx < 256; ++idx) { 93 | petscii::Graphic glyph{}; 94 | 95 | for (std::uint8_t row = 0; row < 8; ++row) { 96 | const auto input_row = bits[idx * 8 + row]; 97 | glyph[geometry::point{ 0, row }] = (0b1000'0000 & input_row) == 0 ? 0 : 1; 98 | glyph[geometry::point{ 1, row }] = (0b0100'0000 & input_row) == 0 ? 0 : 1; 99 | glyph[geometry::point{ 2, row }] = (0b0010'0000 & input_row) == 0 ? 0 : 1; 100 | glyph[geometry::point{ 3, row }] = (0b0001'0000 & input_row) == 0 ? 0 : 1; 101 | glyph[geometry::point{ 4, row }] = (0b0000'1000 & input_row) == 0 ? 0 : 1; 102 | glyph[geometry::point{ 5, row }] = (0b0000'0100 & input_row) == 0 ? 0 : 1; 103 | glyph[geometry::point{ 6, row }] = (0b0000'0010 & input_row) == 0 ? 0 : 1; 104 | glyph[geometry::point{ 7, row }] = (0b0000'0001 & input_row) == 0 ? 0 : 1; 105 | } 106 | 107 | results[idx] = glyph; 108 | } 109 | 110 | return results; 111 | } 112 | 113 | template static constexpr auto from_pixels_to_2x2(const petscii::Graphic &pixels) 114 | { 115 | petscii::Graphic result{}; 116 | 117 | using GlyphType = std::pair, std::uint8_t>; 118 | constexpr std::array lookup_map{ GlyphType{ { 0, 0, 0, 0 }, 32 }, 119 | GlyphType{ { 1, 0, 0, 0 }, 126 }, 120 | GlyphType{ { 0, 1, 0, 0 }, 124 }, 121 | GlyphType{ { 1, 1, 0, 0 }, 226 }, 122 | GlyphType{ { 0, 0, 1, 0 }, 123 }, 123 | GlyphType{ { 1, 0, 1, 0 }, 97 }, 124 | GlyphType{ { 0, 1, 1, 0 }, 255 }, 125 | GlyphType{ { 1, 1, 1, 0 }, 236 }, 126 | GlyphType{ { 0, 0, 0, 1 }, 108 }, 127 | GlyphType{ { 1, 0, 0, 1 }, 127 }, 128 | GlyphType{ { 0, 1, 0, 1 }, 225 }, 129 | GlyphType{ { 1, 1, 0, 1 }, 251 }, 130 | GlyphType{ { 0, 0, 1, 1 }, 98 }, 131 | GlyphType{ { 1, 0, 1, 1 }, 252 }, 132 | GlyphType{ { 0, 1, 1, 1 }, 254 }, 133 | GlyphType{ { 1, 1, 1, 1 }, 160 } }; 134 | 135 | 136 | for (uint8_t x = 0; x < pixels.size().width; x += 2) { 137 | for (uint8_t y = 0; y < pixels.size().height; y += 2) { 138 | for (const auto &glyph : lookup_map) { 139 | if (pixels.match(glyph.first, {x, y})) { 140 | result[geometry::point{static_cast(x / 2), static_cast(y / 2)}] = glyph.second; 141 | break;// go to next Y, we found our match 142 | } 143 | } 144 | } 145 | } 146 | 147 | return result; 148 | } 149 | 150 | 151 | 152 | }// namespace petscii 153 | 154 | 155 | #endif// INC_6502_C_PETSCII_HPP 156 | -------------------------------------------------------------------------------- /examples/simple_game/vicii.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_C_VICII_HPP 2 | #define INC_6502_C_VICII_HPP 3 | 4 | #include "6502.hpp" 5 | #include "geometry.hpp" 6 | #include 7 | #include 8 | #include 9 | 10 | namespace vicii { 11 | enum struct Colors : std::uint8_t { 12 | black = 0, 13 | white = 1, 14 | red = 2, 15 | cyan = 3, 16 | violet = 4, 17 | green = 5, 18 | blue = 6, 19 | yellow = 7, 20 | orange = 8, 21 | brown = 9, 22 | light_red = 10, 23 | dark_grey = 11, 24 | grey = 12, 25 | light_green = 13, 26 | light_blue = 14, 27 | light_grey = 15 28 | }; 29 | 30 | 31 | [[maybe_unused]] static void decrement_border_color() { mos6502::poke(0xd020, mos6502::peek(0xd020) - 1); } 32 | 33 | // static void increment_border_color() { mos6502::poke(0xd020, mos6502::peek(0xd020) + 1); } 34 | 35 | static inline void putc(geometry::point location, std::uint8_t c, Colors color) 36 | { 37 | const auto offset = static_cast(location.y * 40 + location.x); 38 | const auto start = static_cast(0x400 + offset); 39 | mos6502::poke(start, c); 40 | mos6502::poke(offset + 0xD800, static_cast(color)); 41 | } 42 | 43 | static inline std::uint8_t getc(geometry::point location) 44 | { 45 | const auto start = static_cast(0x400 + (location.y * 40 + location.x)); 46 | return mos6502::peek(start); 47 | } 48 | 49 | static inline void invertc(geometry::point location) 50 | { 51 | const auto start = static_cast(0x400 + (location.y * 40 + location.x)); 52 | mos6502::memory_loc(start) += 128; 53 | } 54 | 55 | static inline void set_background(const vicii::Colors color) { 56 | mos6502::poke(53280, static_cast(color)); 57 | } 58 | 59 | static inline void set_border(const vicii::Colors color) { 60 | mos6502::poke(53281, static_cast(color)); 61 | } 62 | 63 | 64 | static inline void puts(const geometry::point loc, const auto &range, const vicii::Colors color = vicii::Colors::white) 65 | { 66 | const auto offset = static_cast(loc.y * 40 + loc.x); 67 | 68 | const std::uint16_t start = 0x400 + offset; 69 | 70 | using namespace std; 71 | 72 | std::copy(begin(range), end(range), &mos6502::memory_loc(start)); 73 | 74 | for (std::uint16_t color_loc = 0; color_loc < size(range); ++color_loc) { 75 | mos6502::poke(static_cast(0xD800 + color_loc + offset), static_cast(color)); 76 | } 77 | } 78 | 79 | 80 | static inline void put_hex(geometry::point start, uint8_t value, Colors color) 81 | { 82 | const auto put_nibble = [color](geometry::point location, std::uint8_t nibble) { 83 | if (nibble <= 9) { 84 | putc(location, nibble + 48, color); 85 | } else { 86 | putc(location, nibble - 9, color); 87 | } 88 | }; 89 | 90 | put_nibble(start + geometry::point{ 1, 0 }, 0xF & value); 91 | put_nibble(start , 0xF & (value >> 4)); 92 | } 93 | 94 | static inline void put_hex(geometry::point location, std::uint16_t value, Colors color) 95 | { 96 | put_hex(location, static_cast(0xFF & (value >> 8)), color); 97 | put_hex(location + geometry::point{ 2, 0 }, static_cast(0xFF & value), color); 98 | } 99 | 100 | static inline void put_graphic(geometry::point location, const auto &graphic) 101 | { 102 | for (const auto &p : graphic.size()) { putc(p + location, graphic[p], Colors::white); } 103 | } 104 | 105 | static inline void put_graphic(geometry::point location, const auto &graphic) requires requires { graphic.colors[{0, 0}]; } 106 | { 107 | for (const auto &p : graphic.data.size()) { 108 | putc(p + location, graphic.data[p], static_cast(graphic.colors[p])); 109 | } 110 | } 111 | 112 | 113 | static inline void cls() 114 | { 115 | for (std::uint16_t i = 0x400; i < 0x400 + 1000; ++i) { mos6502::poke(i, 32); } 116 | } 117 | 118 | struct Screen 119 | { 120 | void load(geometry::point location, auto &s) 121 | { 122 | for (const auto &p : s.size()) { 123 | s[p] = getc(p + location); 124 | } 125 | } 126 | 127 | void hide(auto &s) 128 | { 129 | if (s.is_shown) { 130 | put_graphic(s.location, s.saved_background); 131 | s.is_shown = false; 132 | } 133 | } 134 | 135 | void show(geometry::point loc, auto &s) 136 | { 137 | if (s.is_shown) { put_graphic(s.location, s.saved_background); } 138 | 139 | s.is_shown = true; 140 | 141 | s.location = loc; 142 | 143 | load(loc, s.saved_background); 144 | 145 | put_graphic(loc, s.graphic); 146 | } 147 | }; 148 | 149 | static void draw_vline(geometry::point begin, const geometry::point end, Colors c) 150 | { 151 | while (begin < end) { 152 | putc(begin, 93, c); 153 | begin = begin + geometry::point{ 0, 1 }; 154 | } 155 | } 156 | 157 | static void draw_hline(geometry::point begin, const geometry::point end, Colors c) 158 | { 159 | while (begin < end) { 160 | putc(begin, 67, c); 161 | begin = begin + geometry::point{1,0}; 162 | } 163 | } 164 | 165 | static inline void draw_box(geometry::rect geo, Colors color) 166 | { 167 | putc(geo.top_left(), 85, color); 168 | putc(geo.top_right(), 73, color); 169 | putc(geo.bottom_right(), 75, color); 170 | putc(geo.bottom_left(), 74, color); 171 | 172 | draw_hline(geo.top_left() + geometry::point{1,0}, geo.top_right(), color); 173 | draw_hline(geo.bottom_left() + geometry::point{ 1, 0 }, geo.bottom_right(), color); 174 | 175 | draw_vline(geo.top_left() + geometry::point{ 0, 1 }, geo.bottom_left(), color); 176 | draw_vline(geo.top_right() + geometry::point{ 0, 1 }, geo.bottom_right(), color); 177 | } 178 | 179 | static inline void clear(geometry::rect box, Colors color) { 180 | for (const auto &p : box.size()) { 181 | putc(p + box.top_left(), ' ', color); 182 | } 183 | } 184 | 185 | }// namespace vicii 186 | 187 | #endif// INC_6502_C_VICII_HPP 188 | -------------------------------------------------------------------------------- /examples/simple_game/x16.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_C_CPP_X16 2 | #define INC_6502_C_CPP_X16 3 | 4 | #include <6502.hpp> 5 | #include 6 | #include 7 | #include 8 | 9 | namespace x16::vera { 10 | static constexpr std::uint16_t ADDR_L = 0x9f20; 11 | static constexpr std::uint16_t ADDR_M = 0x9f21; 12 | static constexpr std::uint16_t ADDR_H = 0x9f22; 13 | static constexpr std::uint16_t DATA0 = 0x9f23; 14 | 15 | // this assumes we are already in a text mode, initialized by the kernal 16 | static void puts(const geometry::point &loc, std::span str) { 17 | mos6502::poke(ADDR_L, loc.x * 2); 18 | mos6502::poke(ADDR_M, loc.y); 19 | mos6502::poke(ADDR_H, 0x20); // auto increment by 2 after each poke of the character, we are skipping the attribute byte 20 | 21 | for (const auto c : str) { 22 | mos6502::poke(DATA0, c); 23 | } 24 | } 25 | } 26 | 27 | #endif 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/test.asm: -------------------------------------------------------------------------------- 1 | main: 2 | .LFB0: 3 | .cfi_startproc 4 | movb $1, 53280 5 | xorl %eax, %eax 6 | ret 7 | 8 | -------------------------------------------------------------------------------- /examples/test.c: -------------------------------------------------------------------------------- 1 | int main() 2 | { 3 | *((char *)0xd020) = 0x01; // set the border color to white on a c64 4 | } 5 | -------------------------------------------------------------------------------- /examples/test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | enum Colors : uint8_t 4 | { 5 | WHITE=0x01 6 | }; 7 | 8 | static volatile uint8_t &memory_loc(const uint16_t loc) 9 | { 10 | return *reinterpret_cast(loc); 11 | } 12 | 13 | static void decrement_border_color() 14 | { 15 | --memory_loc(0xd020); 16 | } 17 | 18 | static void increment_border_color() 19 | { 20 | ++memory_loc(0xd020); 21 | } 22 | 23 | static bool joystick_down() 24 | { 25 | uint8_t joystick_state = memory_loc(0xDC00); 26 | return (joystick_state & 0x2) == 0; 27 | } 28 | 29 | int main() 30 | { 31 | const auto background_color = [](Colors col) { 32 | memory_loc(0xd021) = static_cast(col); 33 | }; 34 | 35 | background_color(Colors::WHITE); 36 | 37 | while(true) { 38 | if (joystick_down()) { 39 | // increment_border_color(); 40 | } else { 41 | decrement_border_color(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/test2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | enum class Colors : uint8_t 4 | { 5 | WHITE=0x01, 6 | BLACK=0x00 7 | }; 8 | 9 | inline volatile uint8_t &memory_loc(const uint16_t loc) 10 | { 11 | return *reinterpret_cast(loc); 12 | } 13 | 14 | inline void decrement_border_color() 15 | { 16 | --memory_loc(0xd020); 17 | } 18 | 19 | inline void increment_border_color() 20 | { 21 | ++memory_loc(0xd020); 22 | } 23 | 24 | inline bool joystick_down() 25 | { 26 | uint8_t joystick_state = memory_loc(0xDC00); 27 | return (joystick_state & 0x2) == 0; 28 | } 29 | 30 | int main() 31 | { 32 | const auto background_color = [](Colors col) { 33 | memory_loc(0xd021) = static_cast(col); 34 | }; 35 | 36 | const auto border_color = [](Colors col) { 37 | memory_loc(0xd020) = static_cast(col); 38 | }; 39 | 40 | background_color(Colors::WHITE); 41 | 42 | while(true) { 43 | if (joystick_down()) { 44 | border_color(Colors::WHITE); 45 | } else { 46 | border_color(Colors::BLACK); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/test3.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | enum class Colors : uint8_t 4 | { 5 | WHITE=0x01, 6 | BLACK=0x00 7 | }; 8 | 9 | volatile uint8_t &memory_loc(const uint16_t loc) 10 | { 11 | return *reinterpret_cast(loc); 12 | } 13 | 14 | void decrement_border_color() 15 | { 16 | --memory_loc(0xd020); 17 | } 18 | 19 | void increment_border_color() 20 | { 21 | ++memory_loc(0xd020); 22 | } 23 | 24 | bool joystick_down() 25 | { 26 | uint8_t joystick_state = memory_loc(0xDC00); 27 | return (joystick_state & 0x2) == 0; 28 | } 29 | 30 | int main() 31 | { 32 | const auto background_color = [](Colors col) { 33 | memory_loc(0xd021) = static_cast(col); 34 | }; 35 | 36 | const auto border_color = [](Colors col) { 37 | memory_loc(0xd020) = static_cast(col); 38 | }; 39 | 40 | background_color(Colors::WHITE); 41 | 42 | bool joydown = joystick_down(); 43 | 44 | while(true) { 45 | bool newjoydown = joystick_down(); 46 | if (joydown != newjoydown) { 47 | increment_border_color(); 48 | joydown = newjoydown; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/test_lambda.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | enum Colors : uint8_t { WHITE = 0x01 }; 8 | 9 | static volatile uint8_t &memory_loc(const uint16_t loc) { 10 | return *reinterpret_cast(loc); 11 | } 12 | 13 | static void poke(const uint16_t loc, const uint8_t value) { 14 | memory_loc(loc) = value; 15 | } 16 | 17 | static std::uint8_t peek(const std::uint16_t loc) { return memory_loc(loc); } 18 | 19 | static void decrement_border_color() { --memory_loc(0xd020); } 20 | 21 | static void increment_border_color() { ++memory_loc(0xd020); } 22 | 23 | static bool joystick_down() { 24 | uint8_t joystick_state = peek(0xDC00); 25 | return (joystick_state & 2) == 0; 26 | } 27 | 28 | void use_data(std::array &data); 29 | 30 | static void puts(uint8_t x, uint8_t y, std::string_view str) { 31 | const auto start = 0x400 + (y * 40 + x); 32 | 33 | std::memcpy(const_cast(&memory_loc(start)), str.data(), 34 | str.size()); 35 | } 36 | 37 | 38 | template 39 | struct Graphic 40 | { 41 | std::array data; 42 | 43 | static constexpr auto width() noexcept { 44 | return Width; 45 | } 46 | 47 | static constexpr auto height() noexcept { 48 | return Height; 49 | } 50 | 51 | constexpr Graphic() = default; 52 | 53 | constexpr Graphic(std::array data_) noexcept : data(data_) {} 54 | constexpr Graphic(std::initializer_list data_) noexcept { 55 | std::copy(begin(data_), end(data_), begin(data)); 56 | } 57 | 58 | constexpr auto &operator()(const std::uint8_t x, const std::uint8_t y) noexcept { 59 | return data[y * Width + x]; 60 | } 61 | 62 | constexpr const auto &operator()(const std::uint8_t x, const std::uint8_t y) const noexcept { 63 | return data[y * Width + x]; 64 | } 65 | }; 66 | 67 | static void putc(uint8_t x, uint8_t y, uint8_t c) { 68 | const auto start = 0x400 + (y * 40 + x); 69 | poke(start, c); 70 | } 71 | 72 | static void put_hex(uint8_t x, uint8_t y, uint8_t value) { 73 | const auto put_nibble = [](auto x, auto y, uint8_t nibble) { 74 | if (nibble <= 9) { 75 | putc(x, y, nibble + 48); 76 | } else { 77 | putc(x, y, nibble - 9); 78 | } 79 | }; 80 | 81 | put_nibble(x + 1, y, 0xF & value); 82 | put_nibble(x, y, 0xF & (value >> 4)); 83 | } 84 | 85 | static void put_hex(uint8_t x, uint8_t y, uint16_t value) { 86 | put_hex(x+2,y, static_cast(0xFF & value)); 87 | put_hex(x,y, static_cast(0xFF & (value >> 8))); 88 | } 89 | 90 | static void put_graphic(uint8_t x, uint8_t y, const auto &graphic) 91 | { 92 | for (uint8_t cur_y = 0; cur_y < graphic.height(); ++cur_y) { 93 | for (uint8_t cur_x = 0; cur_x < graphic.width(); ++cur_x) { 94 | putc(cur_x + x, cur_y + y, graphic(cur_x, cur_y)); 95 | } 96 | } 97 | } 98 | 99 | static void cls() { 100 | for (std::uint16_t i = 0x400; i < 0x400 + 1000; ++i) { 101 | poke(i, 32); 102 | } 103 | } 104 | 105 | struct Clock { 106 | using milliseconds = std::chrono::duration; 107 | 108 | // return elapsed time since last restart 109 | [[nodiscard]] milliseconds restart() { 110 | // stop Timer A 111 | poke(0xDC0E, 0b00000000); 112 | 113 | // last value 114 | const auto previous_value = static_cast( 115 | peek(0xDC04) | (static_cast(peek(0xDC05)) << 8)); 116 | 117 | // reset timer 118 | poke(0xDC04, 0xFF); 119 | poke(0xDC05, 0xFF); 120 | 121 | // restart timer A 122 | poke(0xDC0E, 0b00010001); 123 | 124 | return milliseconds{0xFFFF - previous_value}; 125 | } 126 | 127 | Clock() { [[maybe_unused]] const auto value = restart(); } 128 | }; 129 | 130 | int main() { 131 | cls(); 132 | 133 | auto fib = [f0 = 0u, f1 = 1u] mutable { 134 | f0 = std::exchange(f1, f0 + f1); 135 | return f0; 136 | }; 137 | 138 | for (std::uint8_t y = 0; y < 25; ++y) { 139 | put_hex(30, y, fib()); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /examples/test_mod.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | enum Colors : uint8_t { WHITE = 0x01 }; 8 | 9 | static volatile uint8_t &memory_loc(const uint16_t loc) { 10 | return *reinterpret_cast(loc); 11 | } 12 | 13 | static void poke(const uint16_t loc, const uint8_t value) { 14 | memory_loc(loc) = value; 15 | } 16 | 17 | static std::uint8_t peek(const std::uint16_t loc) { return memory_loc(loc); } 18 | 19 | static void decrement_border_color() { --memory_loc(0xd020); } 20 | 21 | static void increment_border_color() { ++memory_loc(0xd020); } 22 | 23 | static bool joystick_down() { 24 | uint8_t joystick_state = peek(0xDC00); 25 | return (joystick_state & 2) == 0; 26 | } 27 | 28 | unsigned int __attribute__ ((noinline)) multiply(unsigned int x, unsigned int y) { 29 | unsigned int result = 0; 30 | unsigned int input = x; 31 | unsigned int add_in = y; 32 | while (input != 0) { 33 | if (input & 1) { 34 | result += add_in; 35 | } 36 | add_in <<= 1; 37 | input >>= 1; 38 | } 39 | 40 | return result; 41 | } 42 | 43 | 44 | void use_data(std::array &data); 45 | 46 | static void puts(uint8_t x, uint8_t y, std::string_view str) { 47 | const auto start = 0x400 + (y * 40 + x); 48 | 49 | std::memcpy(const_cast(&memory_loc(start)), str.data(), 50 | str.size()); 51 | } 52 | 53 | 54 | template 55 | struct Graphic 56 | { 57 | std::array data; 58 | 59 | static constexpr auto width() noexcept { 60 | return Width; 61 | } 62 | 63 | static constexpr auto height() noexcept { 64 | return Height; 65 | } 66 | 67 | constexpr Graphic() = default; 68 | 69 | constexpr Graphic(std::array data_) noexcept : data(data_) {} 70 | constexpr Graphic(std::initializer_list data_) noexcept { 71 | std::copy(begin(data_), end(data_), begin(data)); 72 | } 73 | 74 | constexpr auto &operator()(const std::uint8_t x, const std::uint8_t y) noexcept { 75 | return data[y * Width + x]; 76 | } 77 | 78 | constexpr const auto &operator()(const std::uint8_t x, const std::uint8_t y) const noexcept { 79 | return data[y * Width + x]; 80 | } 81 | }; 82 | 83 | static void putc(uint8_t x, uint8_t y, uint8_t c) { 84 | const auto start = 0x400 + (y * 40 + x); 85 | poke(start, c); 86 | } 87 | 88 | static void put_hex(uint8_t x, uint8_t y, uint8_t value) { 89 | const auto put_nibble = [](auto x, auto y, uint8_t nibble) { 90 | if (nibble <= 9) { 91 | putc(x, y, nibble + 48); 92 | } else { 93 | putc(x, y, nibble - 9); 94 | } 95 | }; 96 | 97 | put_nibble(x + 1, y, 0xF & value); 98 | put_nibble(x, y, 0xF & (value >> 4)); 99 | } 100 | 101 | static void put_hex(uint8_t x, uint8_t y, uint16_t value) { 102 | put_hex(x+2,y, static_cast(0xFF & value)); 103 | put_hex(x,y, static_cast(0xFF & (value >> 8))); 104 | } 105 | 106 | static void put_graphic(uint8_t x, uint8_t y, const auto &graphic) 107 | { 108 | for (uint8_t cur_y = 0; cur_y < graphic.height(); ++cur_y) { 109 | for (uint8_t cur_x = 0; cur_x < graphic.width(); ++cur_x) { 110 | putc(cur_x + x, cur_y + y, graphic(cur_x, cur_y)); 111 | } 112 | } 113 | } 114 | 115 | struct Clock { 116 | using milliseconds = std::chrono::duration; 117 | 118 | // return elapsed time since last restart 119 | [[nodiscard]] milliseconds restart() { 120 | // stop Timer A 121 | poke(0xDC0E, 0b00000000); 122 | 123 | // last value 124 | const auto previous_value = static_cast( 125 | peek(0xDC04) | (static_cast(peek(0xDC05)) << 8)); 126 | 127 | // reset timer 128 | poke(0xDC04, 0xFF); 129 | poke(0xDC05, 0xFF); 130 | 131 | // restart timer A 132 | poke(0xDC0E, 0b00010001); 133 | 134 | return milliseconds{0xFFFF - previous_value}; 135 | } 136 | 137 | Clock() { [[maybe_unused]] const auto value = restart(); } 138 | }; 139 | 140 | int main() { 141 | 142 | // static constexpr std::array data{0}; 143 | // std::memcpy(const_cast(&memory_loc(0x400)), data.data(), 144 | // data.size()); 145 | 146 | static constexpr auto pic = 147 | Graphic<5,4>{ 148 | 78,119,77,32,32, 149 | 101,32,32,80,32, 150 | 101,79,101,103,32, 151 | 76,101,76,122,88 152 | }; 153 | 154 | static constexpr auto map1 = 155 | Graphic<4, 2>{ 156 | 1,0,1,0, 157 | 1,1,1,1 158 | }; 159 | 160 | static constexpr auto map2 = 161 | Graphic<6, 3>{ 162 | 1,0,1,0,0,0, 163 | 1,1,1,1,0,1, 164 | 0,0,0,1,0,0 165 | }; 166 | 167 | /* 168 | static constexpr auto map = 169 | Graphic<10, 2>{ 170 | 0,1,0,1,0,0,0,1,0,0, 171 | 1,0,0,0,0,0,1,0,0,0, 172 | }; 173 | */ 174 | // put_graphic(10,10,pic); 175 | 176 | 177 | const auto draw_map = [](const auto &map) { 178 | for (std::uint8_t y=0; y < map.height(); ++y) { 179 | for (std::uint8_t x = 0; x < map.width(); ++x) { 180 | if (map(x, y) == 1) { 181 | put_graphic(x*4, y*4, pic); 182 | } 183 | } 184 | } 185 | }; 186 | 187 | 188 | // draw_map(map1); 189 | 190 | Clock game_clock{}; 191 | 192 | 193 | std::uint16_t counter = 0; 194 | std::uint8_t y = 19; 195 | 196 | while (true) { 197 | const auto us_elapsed = game_clock.restart().count(); 198 | 199 | draw_map(map2); 200 | 201 | puts(5, 17, "timing history"); 202 | puts(21, 17, "16bit counter"); 203 | 204 | 205 | put_hex(5, y, us_elapsed); 206 | put_hex(21, y, counter); 207 | put_hex(26, y, static_cast(multiply(counter, y))); 208 | // put_hex(31, y, counter*10); 209 | 210 | 211 | 212 | if (y++ == 24) { 213 | y = 19; 214 | } 215 | 216 | ++counter; 217 | increment_border_color(); 218 | } 219 | 220 | /* 221 | const auto background_color = [](Colors col) { 222 | memory_loc(0xd021) = static_cast(col); 223 | }; 224 | 225 | background_color(Colors::WHITE); 226 | 227 | while(true) { 228 | if (joystick_down()) { 229 | increment_border_color(); 230 | } else { 231 | decrement_border_color(); 232 | } 233 | } 234 | */ 235 | } 236 | -------------------------------------------------------------------------------- /include/6502.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_CPP_6502_HPP 2 | #define INC_6502_CPP_6502_HPP 3 | 4 | #include "assembly.hpp" 5 | 6 | struct mos6502 : ASMLine 7 | { 8 | enum class OpCode { 9 | unknown, 10 | 11 | adc, 12 | AND, 13 | asl, 14 | 15 | bcc, 16 | bcs, 17 | beq, 18 | bit, 19 | bmi, 20 | bne, 21 | bpl, 22 | bvs, 23 | 24 | cpx, 25 | cpy, 26 | cmp, 27 | clc, 28 | 29 | dec, 30 | dex, 31 | dey, 32 | 33 | eor, 34 | 35 | inc, 36 | inx, 37 | iny, 38 | 39 | jmp, 40 | jsr, 41 | 42 | lda, 43 | ldx, 44 | ldy, 45 | lsr, 46 | 47 | nop, 48 | 49 | ORA, 50 | 51 | pha, 52 | php, 53 | pla, 54 | plp, 55 | 56 | rol, 57 | ror, 58 | rts, 59 | 60 | sbc, 61 | sec, 62 | sta, 63 | stx, 64 | sty, 65 | 66 | tax, 67 | tay, 68 | tsx, 69 | txa, 70 | txs, 71 | tya, 72 | }; 73 | 74 | static bool get_is_branch(const OpCode o) 75 | { 76 | switch (o) { 77 | case OpCode::beq: 78 | case OpCode::bne: 79 | case OpCode::bmi: 80 | case OpCode::bpl: 81 | case OpCode::bcc: 82 | case OpCode::bcs: 83 | case OpCode::bvs: 84 | return true; 85 | case OpCode::adc: 86 | case OpCode::AND: 87 | case OpCode::asl: 88 | case OpCode::bit: 89 | case OpCode::cpx: 90 | case OpCode::cpy: 91 | case OpCode::cmp: 92 | case OpCode::clc: 93 | case OpCode::dec: 94 | case OpCode::dex: 95 | case OpCode::eor: 96 | case OpCode::inc: 97 | case OpCode::inx: 98 | case OpCode::iny: 99 | case OpCode::dey: 100 | case OpCode::jmp: 101 | case OpCode::jsr: 102 | case OpCode::lda: 103 | case OpCode::ldx: 104 | case OpCode::ldy: 105 | case OpCode::lsr: 106 | case OpCode::nop: 107 | case OpCode::ORA: 108 | case OpCode::pha: 109 | case OpCode::php: 110 | case OpCode::pla: 111 | case OpCode::plp: 112 | case OpCode::rol: 113 | case OpCode::ror: 114 | case OpCode::rts: 115 | case OpCode::sbc: 116 | case OpCode::sec: 117 | case OpCode::sta: 118 | case OpCode::sty: 119 | case OpCode::stx: 120 | case OpCode::tax: 121 | case OpCode::tay: 122 | case OpCode::tsx: 123 | case OpCode::txa: 124 | case OpCode::txs: 125 | case OpCode::tya: 126 | 127 | case OpCode::unknown: 128 | break; 129 | } 130 | return false; 131 | } 132 | 133 | static bool get_is_comparison(const OpCode o) 134 | { 135 | switch (o) { 136 | case OpCode::bit: 137 | case OpCode::cmp: 138 | case OpCode::cpy: 139 | case OpCode::cpx: 140 | return true; 141 | case OpCode::adc: 142 | case OpCode::AND: 143 | case OpCode::asl: 144 | case OpCode::beq: 145 | case OpCode::bne: 146 | case OpCode::bmi: 147 | case OpCode::bpl: 148 | case OpCode::bcc: 149 | case OpCode::bcs: 150 | case OpCode::bvs: 151 | case OpCode::clc: 152 | case OpCode::dec: 153 | case OpCode::dex: 154 | case OpCode::eor: 155 | case OpCode::inc: 156 | case OpCode::inx: 157 | case OpCode::iny: 158 | case OpCode::dey: 159 | case OpCode::jmp: 160 | case OpCode::jsr: 161 | case OpCode::lda: 162 | case OpCode::ldx: 163 | case OpCode::ldy: 164 | case OpCode::lsr: 165 | case OpCode::nop: 166 | case OpCode::ORA: 167 | case OpCode::pha: 168 | case OpCode::php: 169 | case OpCode::pla: 170 | case OpCode::plp: 171 | case OpCode::rol: 172 | case OpCode::ror: 173 | case OpCode::rts: 174 | case OpCode::sbc: 175 | case OpCode::sec: 176 | case OpCode::sta: 177 | case OpCode::stx: 178 | case OpCode::sty: 179 | case OpCode::tax: 180 | case OpCode::tay: 181 | case OpCode::tsx: 182 | case OpCode::txa: 183 | case OpCode::txs: 184 | case OpCode::tya: 185 | case OpCode::unknown: 186 | break; 187 | } 188 | return false; 189 | } 190 | 191 | 192 | explicit mos6502(const OpCode o) 193 | : ASMLine(Type::Instruction, std::string{ to_string(o) }), opcode(o), is_branch(get_is_branch(o)), is_comparison(get_is_comparison(o)) 194 | { 195 | } 196 | 197 | mos6502(const Type t, std::string s) 198 | : ASMLine(t, std::move(s)) 199 | { 200 | } 201 | 202 | mos6502(const OpCode o, Operand t_o) 203 | : ASMLine(Type::Instruction, std::string{ to_string(o) }), opcode(o), op(std::move(t_o)), is_branch(get_is_branch(o)), is_comparison(get_is_comparison(o)) 204 | { 205 | } 206 | 207 | constexpr static std::string_view to_string(const OpCode o) 208 | { 209 | switch (o) { 210 | case OpCode::lda: return "lda"; 211 | case OpCode::asl: return "asl"; 212 | case OpCode::rol: return "rol"; 213 | case OpCode::ldx: return "ldx"; 214 | case OpCode::ldy: return "ldy"; 215 | case OpCode::tay: return "tay"; 216 | case OpCode::tya: return "tya"; 217 | case OpCode::tax: return "tax"; 218 | case OpCode::tsx: return "tsx"; 219 | case OpCode::txa: return "txa"; 220 | case OpCode::txs: return "txs"; 221 | case OpCode::cpy: return "cpy"; 222 | case OpCode::eor: return "eor"; 223 | case OpCode::sta: return "sta"; 224 | case OpCode::sty: return "sty"; 225 | case OpCode::stx: return "stx"; 226 | case OpCode::pha: return "pha"; 227 | case OpCode::pla: return "pla"; 228 | case OpCode::php: return "php"; 229 | case OpCode::plp: return "plp"; 230 | case OpCode::lsr: return "lsr"; 231 | case OpCode::ror: return "ror"; 232 | case OpCode::AND: return "and"; 233 | case OpCode::inc: return "inc"; 234 | case OpCode::dec: return "dec"; 235 | case OpCode::ORA: return "ora"; 236 | case OpCode::cmp: return "cmp"; 237 | case OpCode::bne: return "bne"; 238 | case OpCode::bmi: return "bmi"; 239 | case OpCode::beq: return "beq"; 240 | case OpCode::jmp: return "jmp"; 241 | case OpCode::adc: return "adc"; 242 | case OpCode::sbc: return "sbc"; 243 | case OpCode::rts: return "rts"; 244 | case OpCode::clc: return "clc"; 245 | case OpCode::sec: return "sec"; 246 | case OpCode::bit: return "bit"; 247 | case OpCode::jsr: return "jsr"; 248 | case OpCode::bpl: return "bpl"; 249 | case OpCode::bcc: return "bcc"; 250 | case OpCode::bcs: return "bcs"; 251 | case OpCode::nop: return "nop"; 252 | case OpCode::inx: return "inx"; 253 | case OpCode::dex: return "dex"; 254 | case OpCode::cpx: return "cpx"; 255 | case OpCode::dey: return "dey"; 256 | case OpCode::iny: return "iny"; 257 | case OpCode::bvs: return "bvs"; 258 | case OpCode::unknown: return ""; 259 | } 260 | 261 | return ""; 262 | } 263 | 264 | [[nodiscard]] std::string to_string() const 265 | { 266 | switch (type) { 267 | case ASMLine::Type::Label: 268 | return text;// + ':'; 269 | case ASMLine::Type::Directive: 270 | case ASMLine::Type::Instruction: { 271 | return fmt::format("\t{} {:15}\t; {}", text, op.value, comment); 272 | } 273 | } 274 | throw std::runtime_error("Unable to render: " + text); 275 | } 276 | 277 | 278 | OpCode opcode = OpCode::unknown; 279 | Operand op; 280 | std::string comment; 281 | bool is_branch = false; 282 | bool is_comparison = false; 283 | }; 284 | 285 | #endif//INC_6502_CPP_6502_HPP 286 | -------------------------------------------------------------------------------- /include/assembly.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_CPP_ASSEMBLY_HPP 2 | #define INC_6502_CPP_ASSEMBLY_HPP 3 | 4 | struct Operand 5 | { 6 | enum class Type { 7 | empty, 8 | literal, 9 | reg /*ister*/ 10 | }; 11 | 12 | Type type = Type::empty; 13 | int reg_num = 0; 14 | std::string value; 15 | 16 | Operand() = default; 17 | 18 | bool operator==(const Operand &other) const 19 | { 20 | return type == other.type && reg_num == other.reg_num && value == other.value; 21 | } 22 | 23 | Operand(const Type t, std::string v) 24 | : type(t), value(std::move(v)) 25 | { 26 | assert(type == Type::literal); 27 | } 28 | 29 | Operand(const Type t, const int num) 30 | : type(t), reg_num(num) 31 | { 32 | assert(type == Type::reg); 33 | } 34 | }; 35 | 36 | 37 | struct ASMLine 38 | { 39 | enum class Type { 40 | Label, 41 | Instruction, 42 | Directive 43 | }; 44 | 45 | ASMLine(Type t, std::string te) : type(t), text(std::move(te)) {} 46 | 47 | Type type; 48 | std::string text; 49 | }; 50 | 51 | #endif//INC_6502_CPP_ASSEMBLY_HPP 52 | -------------------------------------------------------------------------------- /include/lib1funcs.hpp: -------------------------------------------------------------------------------- 1 | 2 | static constexpr std::string_view __mulhi3 = 3 | R"( 4 | ;;; based on protocol from gcc's calling conventions for AVR 5 | ;;; 16x16 = 16 multiply 6 | ;;; R25:R24 = R23:R22 * R25:R24 7 | ;;; Clobbers: __tmp_reg__, R21..R23 8 | 9 | __mulhi3: 10 | mov __temp_reg__,r24 11 | mov r21,r25 12 | ldi r25,0 13 | ldi r24,0 14 | cp __temp_reg__,__zero_reg__ 15 | cpc r21,__zero_reg__ 16 | breq .__mulhi3_L5 17 | .__mulhi3_L4: 18 | sbrs __temp_reg__,0 19 | rjmp .__mulhi3_L3 20 | add r24,r22 21 | adc r25,r23 22 | .__mulhi3_L3: 23 | lsr r21 24 | ror __temp_reg__ 25 | lsl r22 26 | rol r23 27 | cp __temp_reg__,__zero_reg__ 28 | cpc r21,__zero_reg__ 29 | brne .__mulhi3_L4 30 | ret 31 | .__mulhi3_L5: 32 | ret 33 | )"; 34 | 35 | 36 | static constexpr std::string_view __mulqi3 = 37 | R"( 38 | ;;; based on protocol from gcc's calling conventions for AVR 39 | ;;; 8x8 = 8 multiply 40 | ;;; R24 = R22 * R24 41 | ;;; Clobbers: __tmp_reg__, R22, R24 42 | 43 | __mulqi3: 44 | ldi __temp_reg__,0 45 | __mulqi_1: 46 | sbrc r24,0 47 | add __temp_reg__,r22 48 | lsr r24 49 | lsl r22 50 | cpse r24,__zero_reg__ 51 | rjmp __mulqi_1 52 | mov r24, __temp_reg__ 53 | ret 54 | )"; -------------------------------------------------------------------------------- /include/np_int.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | template struct np_int 7 | { 8 | Int val; 9 | 10 | // intentionally implicit conversions 11 | // from the underlying type allowed 12 | [[gnu::always_inline]] constexpr np_int(Int i) noexcept : val{ i } {} 13 | 14 | [[gnu::always_inline]] constexpr explicit operator Int() noexcept { return val; } 15 | 16 | [[gnu::always_inline]] constexpr auto operator<=>(const np_int &) const noexcept = default; 17 | 18 | template 19 | [[gnu::always_inline]] constexpr auto operator<=>(const np_int &other) const noexcept 20 | requires(std::is_signed_v == std::is_signed_v) 21 | { 22 | return val == other.val ? std::strong_ordering::equal 23 | : val < other.val ? std::strong_ordering::less 24 | : std::strong_ordering::greater; 25 | } 26 | }; 27 | 28 | template constexpr bool np_or_integral_v = std::is_integral_v; 29 | 30 | template constexpr bool np_or_integral_v> = true; 31 | 32 | template concept np_or_integral = np_or_integral_v; 33 | 34 | template [[nodiscard, gnu::always_inline, gnu::const]] constexpr auto val(Int i) noexcept 35 | { 36 | return i; 37 | } 38 | 39 | template [[nodiscard, gnu::always_inline, gnu::const]] constexpr auto val(np_int i) noexcept 40 | { 41 | return i.val; 42 | } 43 | 44 | template consteval auto calculate_common_int() 45 | { 46 | constexpr auto size = std::max(sizeof(LHS), sizeof(RHS)); 47 | if constexpr (std::is_unsigned_v && std::is_unsigned_v) { 48 | if constexpr (size == 1) { 49 | return std::uint8_t{}; 50 | } else if constexpr (size == 2) { 51 | return std::uint16_t{}; 52 | } else if constexpr (size == 4) { 53 | return std::uint32_t{}; 54 | } else if constexpr (size == 8) { 55 | return std::uint64_t{}; 56 | } 57 | } else { 58 | if constexpr (size == 1) { 59 | return std::int8_t{}; 60 | } else if constexpr (size == 2) { 61 | return std::int16_t{}; 62 | } else if constexpr (size == 4) { 63 | return std::int32_t{}; 64 | } else if constexpr (size == 8) { 65 | return std::int64_t{}; 66 | } 67 | } 68 | } 69 | 70 | template using common_int_t = decltype(calculate_common_int()); 71 | 72 | template 73 | [[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator>>(np_int lhs, 74 | np_or_integral auto rhs) noexcept 75 | { 76 | return np_int{ static_cast(lhs.val >> static_cast(rhs)) }; 77 | } 78 | 79 | template 80 | [[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator<<(np_int lhs, 81 | np_or_integral auto rhs) noexcept 82 | { 83 | return np_int{ static_cast(lhs.val << static_cast(rhs)) }; 84 | } 85 | 86 | template 87 | [[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator+(np_int lhs, np_int rhs) noexcept 88 | { 89 | using Type = common_int_t; 90 | return np_int{ static_cast(lhs.val + rhs.val) }; 91 | } 92 | 93 | template 94 | [[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator-(np_int lhs, np_int rhs) noexcept 95 | { 96 | using Type = common_int_t; 97 | return np_int{ static_cast(lhs.val - rhs.val) }; 98 | } 99 | 100 | template 101 | [[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator*(np_int lhs, np_int rhs) noexcept 102 | { 103 | using Type = common_int_t; 104 | return np_int{ static_cast(lhs.val * rhs.val) }; 105 | } 106 | 107 | template 108 | [[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator/(np_int lhs, np_int rhs) noexcept 109 | { 110 | using Type = common_int_t; 111 | return np_int{ static_cast(lhs.val / rhs.val) }; 112 | } 113 | 114 | template 115 | [[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator|(np_int lhs, np_int rhs) noexcept 116 | { 117 | return np_int{ static_cast(lhs.val | rhs.val) }; 118 | } 119 | 120 | template 121 | [[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator&(np_int lhs, np_int rhs) noexcept 122 | { 123 | return np_int{ static_cast(lhs.val & rhs.val) }; 124 | } 125 | 126 | template 127 | [[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator%(np_int lhs, np_int rhs) noexcept 128 | { 129 | using Type = common_int_t; 130 | return np_int{ static_cast(lhs.val % rhs.val) }; 131 | } 132 | 133 | template 134 | [[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator^(np_int lhs, np_int rhs) noexcept 135 | { 136 | return np_int{ static_cast(lhs.val ^ rhs.val) }; 137 | } 138 | 139 | template 140 | [[gnu::always_inline]] constexpr auto &operator+=(np_int &lhs, np_or_integral auto rhs) noexcept 141 | { 142 | lhs.val += static_cast(rhs); 143 | return lhs; 144 | } 145 | 146 | template 147 | [[gnu::always_inline]] constexpr auto &operator-=(np_int &lhs, np_or_integral auto rhs) noexcept 148 | { 149 | lhs.val -= static_cast(rhs); 150 | return lhs; 151 | } 152 | 153 | template 154 | [[gnu::always_inline]] constexpr auto &operator/=(np_int &lhs, np_or_integral auto rhs) noexcept 155 | { 156 | lhs.val /= static_cast(rhs); 157 | return lhs; 158 | } 159 | 160 | template 161 | [[gnu::always_inline]] constexpr auto &operator*=(np_int &lhs, np_or_integral auto rhs) noexcept 162 | { 163 | lhs.val /= static_cast(rhs); 164 | return lhs; 165 | } 166 | 167 | template 168 | [[gnu::always_inline]] constexpr auto &operator%=(np_int &lhs, np_or_integral auto rhs) noexcept 169 | { 170 | lhs.val %= static_cast(rhs); 171 | return lhs; 172 | } 173 | 174 | template 175 | [[gnu::always_inline]] constexpr auto &operator&=(np_int &lhs, np_or_integral auto rhs) noexcept 176 | { 177 | lhs.val &= static_cast(rhs); 178 | return lhs; 179 | } 180 | 181 | template 182 | [[gnu::always_inline]] constexpr auto &operator|=(np_int &lhs, np_or_integral auto rhs) noexcept 183 | { 184 | lhs.val |= static_cast(rhs); 185 | return lhs; 186 | } 187 | 188 | template 189 | [[gnu::always_inline]] constexpr auto &operator^=(np_int &lhs, np_or_integral auto rhs) noexcept 190 | { 191 | lhs.val ^= static_cast(rhs); 192 | return lhs; 193 | } 194 | 195 | template 196 | [[gnu::always_inline]] constexpr auto &operator<<=(np_int &lhs, np_or_integral auto rhs) noexcept 197 | { 198 | lhs.val <<= static_cast(rhs); 199 | return lhs; 200 | } 201 | 202 | template 203 | [[gnu::always_inline]] constexpr auto &operator>>=(np_int &lhs, np_or_integral auto rhs) noexcept 204 | { 205 | lhs.val >>= static_cast(rhs); 206 | return lhs; 207 | } 208 | 209 | using uint_np8_t = np_int; 210 | using uint_np16_t = np_int; 211 | using uint_np32_t = np_int; 212 | using uint_np64_t = np_int; 213 | 214 | using int_np8_t = np_int; 215 | using int_np16_t = np_int; 216 | using int_np32_t = np_int; 217 | using int_np64_t = np_int; 218 | 219 | template 220 | constexpr bool np_meets_requirements = 221 | sizeof(T) == sizeof(np_int) 222 | && std::is_trivially_destructible_v> &&std::is_trivially_move_constructible_v> 223 | &&std::is_trivially_copy_constructible_v> &&std::is_trivially_copy_assignable_v> 224 | &&std::is_trivially_move_assignable_v>; 225 | 226 | static_assert(np_meets_requirements); 227 | static_assert(np_meets_requirements); 228 | static_assert(np_meets_requirements); 229 | static_assert(np_meets_requirements); 230 | 231 | static_assert(np_meets_requirements); 232 | static_assert(np_meets_requirements); 233 | static_assert(np_meets_requirements); 234 | static_assert(np_meets_requirements); 235 | 236 | // ensures that First paired with any of Rest, in any order 237 | // results in the same type as First again 238 | template 239 | constexpr bool is_same_combinations_v = (std::is_same_v> && ...) 240 | && (std::is_same_v> && ...); 241 | 242 | static_assert(is_same_combinations_v); 243 | static_assert(is_same_combinations_v); 244 | static_assert(is_same_combinations_v); 245 | static_assert(is_same_combinations_v); 246 | 247 | static_assert(is_same_combinations_v); 248 | static_assert(is_same_combinations_v); 249 | static_assert(is_same_combinations_v); 250 | static_assert(is_same_combinations_v); 251 | 252 | static_assert(is_same_combinations_v); 253 | static_assert(is_same_combinations_v); 254 | static_assert(is_same_combinations_v); 255 | static_assert(is_same_combinations_v); 256 | 257 | auto left_shift(uint_np8_t value, int val) { return value << val; } 258 | 259 | auto left_shift(std::uint8_t value, int val) { return static_cast(value << val); } 260 | 261 | std::uint8_t unsigned_value(); 262 | std::int8_t signed_value(); 263 | 264 | 265 | int main(int argc, const char **) 266 | { 267 | uint_np8_t a{ 15 }; 268 | auto value = a + uint_np16_t{ 10 }; 269 | 270 | value += uint_np16_t{ 16 }; 271 | 272 | [[maybe_unused]] const auto comparison = value < uint_np8_t{ 3 }; 273 | // return static_cast(value); 274 | 275 | int i = 1; 276 | [[maybe_unused]] int v = (i << argc); 277 | 278 | 279 | return std::cmp_less(unsigned_value(), signed_value()); 280 | } 281 | -------------------------------------------------------------------------------- /include/optimizer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_CPP_OPTIMIZER_HPP 2 | #define INC_6502_CPP_OPTIMIZER_HPP 3 | 4 | #include "6502.hpp" 5 | #include "personality.hpp" 6 | #include 7 | #include 8 | 9 | 10 | constexpr bool consume_directives(auto &begin, const auto &end) 11 | { 12 | if (begin != end && begin->type == ASMLine::Type::Directive) { 13 | ++begin; 14 | return true; 15 | } 16 | return false; 17 | } 18 | 19 | 20 | constexpr bool consume_labels(auto &begin, const auto &end) 21 | { 22 | if (begin != end && begin->type == ASMLine::Type::Label) { 23 | ++begin; 24 | return true; 25 | } 26 | return false; 27 | } 28 | 29 | constexpr bool is_opcode(const mos6502 &op, const auto... opcodes) { return ((op.opcode == opcodes) || ...); } 30 | 31 | constexpr bool is_end_of_block(const auto &begin) 32 | { 33 | if (begin->text.ends_with("__optimizable") || begin->op.value.ends_with("__optimizable")) { 34 | return false; 35 | } 36 | 37 | if (begin->type == ASMLine::Type::Label) { return true; } 38 | 39 | return is_opcode(*begin, 40 | mos6502::OpCode::jsr, 41 | mos6502::OpCode::jmp, 42 | mos6502::OpCode::bcc, 43 | mos6502::OpCode::bcs, 44 | mos6502::OpCode::beq, 45 | mos6502::OpCode::bne, 46 | mos6502::OpCode::bpl); 47 | } 48 | 49 | constexpr bool consume_end_of_block(auto &begin, const auto &end) 50 | { 51 | if (begin != end && is_end_of_block(begin)) { 52 | ++begin; 53 | return true; 54 | } 55 | return false; 56 | } 57 | 58 | static std::vector> get_optimizable_blocks(std::vector &statements) 59 | { 60 | std::vector> blocks; 61 | 62 | auto begin = std::begin(statements); 63 | auto end = std::end(statements); 64 | 65 | const auto find_end_of_block = [](auto &find_begin, const auto &find_end) { 66 | while (find_begin != find_end) { 67 | if (is_end_of_block(find_begin)) { return; } 68 | ++find_begin; 69 | } 70 | }; 71 | 72 | while (begin != end) { 73 | while (consume_end_of_block(begin, end) || consume_directives(begin, end) || consume_labels(begin, end)) {} 74 | 75 | const auto block_start = begin; 76 | find_end_of_block(begin, end); 77 | 78 | blocks.emplace_back(block_start, begin); 79 | } 80 | 81 | return blocks; 82 | } 83 | 84 | static bool is_virtual_register_op(const mos6502 &op, const Personality &personality) 85 | { 86 | for (int i = 0; i < 32; ++i) { 87 | if (personality.get_register(i).value == op.op.value) { return true; } 88 | } 89 | 90 | return false; 91 | } 92 | 93 | static bool optimize_dead_tax(std::span &block) 94 | { 95 | for (auto itr = block.begin(); itr != block.end(); ++itr) { 96 | if (is_opcode(*itr, mos6502::OpCode::tax, mos6502::OpCode::tsx, mos6502::OpCode::ldx)) { 97 | for (auto inner = std::next(itr); inner != block.end(); ++inner) { 98 | if (is_opcode(*inner, 99 | mos6502::OpCode::txa, 100 | mos6502::OpCode::txs, 101 | mos6502::OpCode::stx, 102 | mos6502::OpCode::inx, 103 | mos6502::OpCode::dex, 104 | mos6502::OpCode::cpx)) { 105 | break; 106 | } 107 | if (is_opcode(*inner, mos6502::OpCode::tax, mos6502::OpCode::tsx, mos6502::OpCode::ldx)) { 108 | // redundant store found 109 | *itr = mos6502(ASMLine::Type::Directive, "; removed dead load of X: " + itr->to_string()); 110 | return true; 111 | } 112 | } 113 | } 114 | } 115 | 116 | return false; 117 | } 118 | 119 | bool optimize_dead_sta(std::span &block, const Personality &personality) 120 | { 121 | for (auto itr = block.begin(); itr != block.end(); ++itr) { 122 | if (itr->opcode == mos6502::OpCode::sta && is_virtual_register_op(*itr, personality)) { 123 | for (auto inner = std::next(itr); inner != block.end(); ++inner) { 124 | if (not inner->op.value.starts_with("#<(") && inner->op.value.find('(') != std::string::npos) { 125 | // this is an indexed operation, which is risky to optimize a sta around on the virtual registers, 126 | // so we'll skip this block 127 | break; 128 | } 129 | if (inner->op.value == itr->op.value) { 130 | if (is_opcode(*inner, mos6502::OpCode::sta)) { 131 | // redundant store found 132 | *itr = mos6502(ASMLine::Type::Directive, "; removed dead store of a: " + itr->to_string()); 133 | return true; 134 | } else { 135 | // someone else is operating on us, time to abort 136 | break; 137 | } 138 | } 139 | } 140 | } 141 | } 142 | 143 | return false; 144 | } 145 | 146 | bool optimize_redundant_ldy(std::span &block) 147 | { 148 | for (auto itr = block.begin(); itr != block.end(); ++itr) { 149 | if (itr->opcode == mos6502::OpCode::ldy && itr->op.value.starts_with('#')) { 150 | for (auto inner = std::next(itr); inner != block.end(); ++inner) { 151 | if (is_opcode(*inner, 152 | mos6502::OpCode::cpy, 153 | mos6502::OpCode::tya, 154 | mos6502::OpCode::tay, 155 | mos6502::OpCode::sty, 156 | mos6502::OpCode::iny, 157 | mos6502::OpCode::dey)) { 158 | break;// break, these all operate on Y 159 | } 160 | // we found a matching ldy 161 | if (is_opcode(*inner, mos6502::OpCode::ldy)) { 162 | // with the same value 163 | // note: this operation is only safe because we know that our system only uses Y 164 | // for index operations and we don't rely (or even necessarily *want* the changes to N,Z) 165 | if (inner->op.value == itr->op.value) { 166 | *inner = mos6502(ASMLine::Type::Directive, "; removed redundant ldy: " + inner->to_string()); 167 | return true; 168 | } else { 169 | break; 170 | } 171 | } 172 | } 173 | } 174 | } 175 | 176 | return false; 177 | } 178 | 179 | bool optimize_redundant_lda(std::span &block, const Personality &personality) 180 | { 181 | // look for a literal or virtual register load into A 182 | // that is redundant later 183 | for (auto itr = block.begin(); itr != block.end(); ++itr) { 184 | if (itr->opcode == mos6502::OpCode::lda 185 | && (itr->op.value.starts_with('#') || is_virtual_register_op(*itr, personality))) { 186 | for (auto inner = std::next(itr); inner != block.end(); ++inner) { 187 | if (is_opcode(*inner, 188 | mos6502::OpCode::tay, 189 | mos6502::OpCode::tax, 190 | mos6502::OpCode::sta, 191 | mos6502::OpCode::pha, 192 | mos6502::OpCode::nop)) { 193 | continue;// OK to skip instructions that don't modify A or change flags 194 | } 195 | if (inner->type == ASMLine::Type::Directive) { 196 | continue;// OK to skip directives 197 | } 198 | if (is_opcode(*inner, mos6502::OpCode::lda)) { 199 | if (inner->op == itr->op) { 200 | // we found a matching lda, after an sta, we can remove it 201 | *inner = mos6502(ASMLine::Type::Directive, "; removed redundant lda: " + inner->to_string()); 202 | return true; 203 | } else { 204 | break; 205 | } 206 | } 207 | 208 | break;// we can only optimize around tax and comments right now 209 | } 210 | } 211 | } 212 | 213 | return false; 214 | } 215 | 216 | bool optimize_redundant_lda_after_sta(std::span &block) 217 | { 218 | for (auto itr = block.begin(); itr != block.end(); ++itr) { 219 | if (itr->opcode == mos6502::OpCode::sta) { 220 | for (auto inner = std::next(itr); inner != block.end(); ++inner) { 221 | if (is_opcode(*inner, 222 | mos6502::OpCode::tax, 223 | mos6502::OpCode::tay, 224 | mos6502::OpCode::clc, 225 | mos6502::OpCode::sec, 226 | mos6502::OpCode::sta, 227 | mos6502::OpCode::pha, 228 | mos6502::OpCode::txs, 229 | mos6502::OpCode::php, 230 | mos6502::OpCode::sty, 231 | mos6502::OpCode::nop)) { 232 | continue;// OK to skip instructions that don't modify A or change flags 233 | } 234 | if (inner->type == ASMLine::Type::Directive) { 235 | continue;// OK to skip directives 236 | } 237 | if (is_opcode(*inner, mos6502::OpCode::lda)) { 238 | if (inner->op == itr->op) { 239 | // we found a matching lda, after a sta, we can remove it 240 | *inner = mos6502(ASMLine::Type::Directive, "; removed redundant lda: " + inner->to_string()); 241 | return true; 242 | } else { 243 | break; 244 | } 245 | } 246 | 247 | break;// we can only optimize around tax and comments right now 248 | } 249 | } 250 | } 251 | 252 | return false; 253 | } 254 | 255 | bool optimize(std::vector &instructions, [[maybe_unused]] const Personality &personality) 256 | { 257 | 258 | 259 | // remove unused flag-fix-up blocks 260 | // it might make sense in the future to only insert these if determined they are needed? 261 | for (size_t op = 10; op < instructions.size(); ++op) { 262 | if (instructions[op].opcode == mos6502::OpCode::lda || instructions[op].opcode == mos6502::OpCode::bcc 263 | || instructions[op].opcode == mos6502::OpCode::bcs || instructions[op].opcode == mos6502::OpCode::ldy 264 | || instructions[op].opcode == mos6502::OpCode::inc || instructions[op].opcode == mos6502::OpCode::clc 265 | || instructions[op].opcode == mos6502::OpCode::sec || instructions[op].text.starts_with("; Handle N / S")) { 266 | if (instructions[op - 1].text == "; END remove if next is lda, bcc, bcs, ldy, inc, clc, sec" 267 | || (instructions[op - 2].text == "; END remove if next is lda, bcc, bcs, ldy, inc, clc, sec" 268 | && instructions[op - 1].type == ASMLine::Type::Directive)) { 269 | for (size_t inner_op = op - 1; inner_op > 1; --inner_op) { 270 | instructions[inner_op] = 271 | mos6502(ASMLine::Type::Directive, "; removed unused flag fix-up: " + instructions[inner_op].to_string()); 272 | 273 | if (instructions[inner_op].text.find("; BEGIN") != std::string::npos) { return true; } 274 | } 275 | } 276 | } 277 | } 278 | 279 | // replace use of __zero_reg__ with literal 0 280 | for (auto &op : instructions) { 281 | if (op.type == ASMLine::Type::Instruction && op.op.type == Operand::Type::literal 282 | && op.op.value == personality.get_register(1).value && op.opcode != mos6502::OpCode::sta) { 283 | // replace use of zero reg with literal 0 284 | const auto old_string = op.to_string(); 285 | op.op.value = "#0"; 286 | op.comment = "replaced use of register 1 with a literal 0, because of AVR GCC __zero_reg__ ; " + old_string; 287 | } 288 | } 289 | 290 | bool optimizer_run = false; 291 | for (auto &block : get_optimizable_blocks(instructions)) { 292 | const bool block_optimized = optimize_redundant_lda_after_sta(block) 293 | || optimize_dead_sta(block, personality) || optimize_dead_tax(block) 294 | || optimize_redundant_ldy(block) || optimize_redundant_lda(block, personality); 295 | 296 | optimizer_run = optimizer_run || block_optimized; 297 | } 298 | return optimizer_run; 299 | } 300 | 301 | #endif// INC_6502_CPP_OPTIMIZER_HPP 302 | -------------------------------------------------------------------------------- /include/personalities/c64.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_C_C64_HPP 2 | #define INC_6502_C_C64_HPP 3 | 4 | #include "../personality.hpp" 5 | 6 | struct C64 : Personality 7 | { 8 | 9 | void insert_autostart_sequence(std::vector &new_instructions) const override 10 | { 11 | constexpr static auto start_address = 0x0801; 12 | 13 | // first 2 bytes is the load address for a PRG file. 14 | new_instructions.emplace_back(ASMLine::Type::Directive, ".word " + std::to_string(start_address)); 15 | new_instructions.emplace_back(ASMLine::Type::Directive, "* = " + std::to_string(start_address)); 16 | new_instructions.emplace_back(ASMLine::Type::Directive, "; jmp to start of program with BASIC"); 17 | new_instructions.emplace_back(ASMLine::Type::Directive, ".byt $0B,$08,$0A,$00,$9E,$32,$30,$36,$31,$00,$00,$00"); 18 | } 19 | 20 | [[nodiscard]] Operand get_register(const int reg_num) const override 21 | { 22 | switch (reg_num) { 23 | // http://sta.c64.org/cbm64mem.html 24 | case 0: return Operand(Operand::Type::literal, "$4e");// unused, int->fp routine pointer 25 | case 1: return Operand(Operand::Type::literal, "$4f"); 26 | case 2: return Operand(Operand::Type::literal, "$50");// unused 27 | case 3: return Operand(Operand::Type::literal, "$51");// unused 28 | case 4: return Operand(Operand::Type::literal, "$52");// bit buffer for rs232 29 | case 5: return Operand(Operand::Type::literal, "$53");// counter for rs232 30 | case 6: return Operand(Operand::Type::literal, "$54");// unused 31 | case 7: return Operand(Operand::Type::literal, "$55");// unused 32 | case 8: return Operand(Operand::Type::literal, "$56");// unused 33 | case 9: return Operand(Operand::Type::literal, "$57");// unused 34 | case 10: return Operand(Operand::Type::literal, "$58");// Current BASIC line number 35 | case 11: return Operand(Operand::Type::literal, "$59");// Current BASIC line number 36 | case 12: return Operand(Operand::Type::literal, "$5a");// arithmetic register #3 37 | case 13: return Operand(Operand::Type::literal, "$5b"); 38 | case 14: return Operand(Operand::Type::literal, "$5c"); 39 | case 15: return Operand(Operand::Type::literal, "$5d"); 40 | case 16: return Operand(Operand::Type::literal, "$5e"); 41 | case 17: return Operand(Operand::Type::literal, "$5f"); 42 | case 18: return Operand(Operand::Type::literal, "$60"); 43 | case 19: return Operand(Operand::Type::literal, "$61"); 44 | case 20: return Operand(Operand::Type::literal, "$62"); 45 | case 21: return Operand(Operand::Type::literal, "$63"); 46 | case 22: return Operand(Operand::Type::literal, "$64"); 47 | case 23: return Operand(Operand::Type::literal, "$65"); 48 | case 24: return Operand(Operand::Type::literal, "$66"); 49 | case 25: return Operand(Operand::Type::literal, "$67"); 50 | case 26: return Operand(Operand::Type::literal, "$68"); 51 | case 27: return Operand(Operand::Type::literal, "$69"); 52 | case 28: return Operand(Operand::Type::literal, "$6a"); 53 | case 29: return Operand(Operand::Type::literal, "$6b"); 54 | case 30: return Operand(Operand::Type::literal, "$6c"); 55 | case 31: return Operand(Operand::Type::literal, "$6d"); 56 | } 57 | throw std::runtime_error("Unhandled register number: " + std::to_string(reg_num)); 58 | } 59 | }; 60 | 61 | #endif// INC_6502_C_C64_HPP 62 | -------------------------------------------------------------------------------- /include/personalities/x16.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_C_X16_HPP 2 | #define INC_6502_C_X16_HPP 3 | 4 | #include "../personality.hpp" 5 | 6 | struct X16 : Personality 7 | { 8 | 9 | void insert_autostart_sequence(std::vector &new_instructions) const override 10 | { 11 | constexpr static auto start_address = 0x0801; 12 | 13 | // first 2 bytes is the load address for a PRG file. 14 | new_instructions.emplace_back(ASMLine::Type::Directive, ".word " + std::to_string(start_address)); 15 | new_instructions.emplace_back(ASMLine::Type::Directive, "* = " + std::to_string(start_address)); 16 | new_instructions.emplace_back(ASMLine::Type::Directive, "; jmp to start of program with BASIC"); 17 | new_instructions.emplace_back(ASMLine::Type::Directive, ".byt $0B,$08,$0A,$00,$9E,$32,$30,$36,$31,$00,$00,$00"); 18 | } 19 | 20 | [[nodiscard]] Operand get_register(const int reg_num) const override 21 | { 22 | switch (reg_num) { 23 | // based on c64, they are basically the same system, but I'm using a different file so they can fork from each other 24 | case 0: return Operand(Operand::Type::literal, "$4e");// unused, int->fp routine pointer 25 | case 1: return Operand(Operand::Type::literal, "$4f"); 26 | case 2: return Operand(Operand::Type::literal, "$50");// unused 27 | case 3: return Operand(Operand::Type::literal, "$51");// unused 28 | case 4: return Operand(Operand::Type::literal, "$52");// bit buffer for rs232 29 | case 5: return Operand(Operand::Type::literal, "$53");// counter for rs232 30 | case 6: return Operand(Operand::Type::literal, "$54");// unused 31 | case 7: return Operand(Operand::Type::literal, "$55");// unused 32 | case 8: return Operand(Operand::Type::literal, "$56");// unused 33 | case 9: return Operand(Operand::Type::literal, "$57");// unused 34 | case 10: return Operand(Operand::Type::literal, "$58");// Current BASIC line number 35 | case 11: return Operand(Operand::Type::literal, "$59");// Current BASIC line number 36 | case 12: return Operand(Operand::Type::literal, "$5a");// arithmetic register #3 37 | case 13: return Operand(Operand::Type::literal, "$5b"); 38 | case 14: return Operand(Operand::Type::literal, "$5c"); 39 | case 15: return Operand(Operand::Type::literal, "$5d"); 40 | case 16: return Operand(Operand::Type::literal, "$5e"); 41 | case 17: return Operand(Operand::Type::literal, "$5f"); 42 | case 18: return Operand(Operand::Type::literal, "$60"); 43 | case 19: return Operand(Operand::Type::literal, "$61"); 44 | case 20: return Operand(Operand::Type::literal, "$62"); 45 | case 21: return Operand(Operand::Type::literal, "$63"); 46 | case 22: return Operand(Operand::Type::literal, "$64"); 47 | case 23: return Operand(Operand::Type::literal, "$65"); 48 | case 24: return Operand(Operand::Type::literal, "$66"); 49 | case 25: return Operand(Operand::Type::literal, "$67"); 50 | case 26: return Operand(Operand::Type::literal, "$68"); 51 | case 27: return Operand(Operand::Type::literal, "$69"); 52 | case 28: return Operand(Operand::Type::literal, "$6a"); 53 | case 29: return Operand(Operand::Type::literal, "$6b"); 54 | case 30: return Operand(Operand::Type::literal, "$6c"); 55 | case 31: return Operand(Operand::Type::literal, "$6d"); 56 | } 57 | throw std::runtime_error("Unhandled register number: " + std::to_string(reg_num)); 58 | } 59 | }; 60 | 61 | #endif// INC_6502_C_X16_HPP 62 | -------------------------------------------------------------------------------- /include/personality.hpp: -------------------------------------------------------------------------------- 1 | #ifndef INC_6502_CPP_PERSONALITY_HPP 2 | #define INC_6502_CPP_PERSONALITY_HPP 3 | 4 | #include 5 | #include "6502.hpp" 6 | 7 | class Personality 8 | { 9 | public: 10 | virtual void insert_autostart_sequence(std::vector &new_instructions) const = 0; 11 | [[nodiscard]] virtual Operand get_register(const int reg_num) const = 0; 12 | 13 | virtual ~Personality() = default; 14 | Personality(const Personality &) = delete; 15 | Personality(Personality &&) = delete; 16 | Personality &operator=(const Personality &) = delete; 17 | Personality &operator=(Personality &&) = delete; 18 | 19 | protected: 20 | Personality() = default; 21 | }; 22 | 23 | #endif//INC_6502_CPP_PERSONALITY_HPP 24 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | Attempts to translate x86 assembly into mos6502 assembly. 4 | 5 | # Why? 6 | 7 | Why not? I figured I just barely knew some x86 and some 6502, so why not learn some more about both. Besides, this project can actually have some interesting applications. 8 | 9 | # Example 10 | 11 | After compilation (requires a full C++14 compiler), you can run something like this: 12 | 13 | ```assembly_x86 14 | // test.asm 15 | main: 16 | movb $1, 53280 17 | xorl %eax, %eax 18 | ret 19 | ``` 20 | 21 | ```bash 22 | cat test.asm | x86-to-6502 23 | ``` 24 | 25 | And get this output: 26 | 27 | ``` 28 | main: 29 | ldy #$1 30 | sty 53280 31 | lda $00 32 | rts 33 | ``` 34 | 35 | # Caveats 36 | 37 | * Nothing is guaranteed. This could break your computer. Who knows? 38 | * All values are truncated to 8 bit. We have no support for 16bit math or pointers yet 39 | * Only as many instructions are supported as I have needed to support to get my test cases working 40 | 41 | # To Do 42 | 43 | *Everything* 44 | 45 | Well, lots of things. 46 | 47 | ## Better code organization 48 | 49 | I really had no idea what I was doing when I started this. So, like all code, it's going to need some improvements along the way to make it more organized and more maintainable. 50 | 51 | ## Improvements 52 | 53 | * Keep track of input source lines and add comments in output to show where lines came from, for learning / debugging purposes 54 | * Better / more efficient translation 55 | * Support for 16bit math? Maybe? We need to at least be able to do 16bit pointer math 56 | * Consider supporting the "Sweet 16" virtual 16bit CPU that Woz designed? 57 | * Maybe for 16bit operation we try to detect if the input code is working on 8bit registers or 16+bit registers and do the right thing? 58 | * Support for more CPU instructions 59 | * A test suite 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | option(CPP_STARTER_USE_SDL "Enable compilation of SDL sample" OFF) 2 | 3 | # sdl 4 | if(CPP_STARTER_USE_SDL) 5 | message("Using SDL2") 6 | add_subdirectory(sdl) 7 | endif() 8 | 9 | # Generic test that uses conan libs 10 | add_executable(6502-c++ 6502-c++.cpp) 11 | target_link_libraries( 12 | 6502-c++ 13 | PRIVATE project_options 14 | project_warnings 15 | CONAN_PKG::ctre 16 | CONAN_PKG::cli11 17 | CONAN_PKG::fmt 18 | CONAN_PKG::spdlog) 19 | 20 | target_include_directories(6502-c++ 21 | PRIVATE "${CMAKE_SOURCE_DIR}") -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Automatically enable catch2 to generate ctest targets 2 | if(CONAN_CATCH2_ROOT_DEBUG) 3 | include(${CONAN_CATCH2_ROOT_DEBUG}/lib/cmake/Catch2/Catch.cmake) 4 | else() 5 | include(${CONAN_CATCH2_ROOT}/lib/cmake/Catch2/Catch.cmake) 6 | endif() 7 | 8 | add_library(catch_main STATIC catch_main.cpp) 9 | target_link_libraries(catch_main PUBLIC CONAN_PKG::catch2 CONAN_PKG::fmt) 10 | target_link_libraries(catch_main PRIVATE project_options) 11 | 12 | add_executable(tests tests.cpp) 13 | target_link_libraries(tests PRIVATE project_warnings project_options catch_main) 14 | 15 | # automatically discover tests that are defined in catch based test files you can modify the unittests. Set TEST_PREFIX 16 | # to whatever you want, or use different for different binaries 17 | catch_discover_tests( 18 | tests 19 | TEST_PREFIX 20 | "approval_tests." 21 | PROPERTIES 22 | ENVIRONMENT CXX_6502=$ ENVIRONMENT X64=/usr/bin/x64 23 | REPORTER 24 | xml 25 | OUTPUT_DIR 26 | . 27 | OUTPUT_PREFIX 28 | "approval_tests." 29 | OUTPUT_SUFFIX 30 | .xml 31 | 32 | ) 33 | 34 | # Add a file containing a set of constexpr tests 35 | add_executable(constexpr_tests constexpr_tests.cpp) 36 | target_link_libraries(constexpr_tests PRIVATE project_options project_warnings catch_main) 37 | 38 | catch_discover_tests( 39 | constexpr_tests 40 | TEST_PREFIX 41 | "constexpr." 42 | REPORTER 43 | xml 44 | OUTPUT_DIR 45 | . 46 | OUTPUT_PREFIX 47 | "constexpr." 48 | OUTPUT_SUFFIX 49 | .xml) 50 | 51 | # Disable the constexpr portion of the test, and build again this allows us to have an executable that we can debug when 52 | # things go wrong with the constexpr testing 53 | add_executable(relaxed_constexpr_tests constexpr_tests.cpp) 54 | target_link_libraries(relaxed_constexpr_tests PRIVATE project_options project_warnings catch_main) 55 | target_compile_definitions(relaxed_constexpr_tests PRIVATE -DCATCH_CONFIG_RUNTIME_STATIC_REQUIRE) 56 | 57 | catch_discover_tests( 58 | relaxed_constexpr_tests 59 | TEST_PREFIX 60 | "relaxed_constexpr." 61 | REPORTER 62 | xml 63 | OUTPUT_DIR 64 | . 65 | OUTPUT_PREFIX 66 | "relaxed_constexpr." 67 | OUTPUT_SUFFIX 68 | .xml) 69 | -------------------------------------------------------------------------------- /test/catch_main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN // This tells the catch header to generate a main 2 | 3 | #include 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/constexpr_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | constexpr unsigned int Factorial(unsigned int number) 4 | { 5 | return number <= 1 ? number : Factorial(number - 1) * number; 6 | } 7 | 8 | TEST_CASE("Factorials are computed with constexpr", "[factorial]") 9 | { 10 | STATIC_REQUIRE(Factorial(1) == 1); 11 | STATIC_REQUIRE(Factorial(2) == 2); 12 | STATIC_REQUIRE(Factorial(3) == 6); 13 | STATIC_REQUIRE(Factorial(10) == 3628800); 14 | } 15 | -------------------------------------------------------------------------------- /test/tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | enum struct OptimizationLevel : char { O0 = '0', O1 = '1', O2 = '2', O3 = '3', Os = 's' }; 7 | 8 | enum struct Optimize6502 : char { Enabled = '1', Disabled = '0' }; 9 | 10 | std::vector execute_c64_program(const std::string_view &name, 11 | const std::string_view script, 12 | OptimizationLevel o, 13 | Optimize6502 o6502, 14 | std::uint16_t start_address_dump, 15 | std::uint16_t end_address_dump) 16 | { 17 | 18 | const char *x64_executable = std::getenv("X64"); 19 | REQUIRE(x64_executable != nullptr); 20 | const char *mos6502_cpp_executable = std::getenv("CXX_6502"); 21 | REQUIRE(mos6502_cpp_executable != nullptr); 22 | 23 | const auto optimization_level = [&]() -> std::string_view { 24 | switch (o) { 25 | case OptimizationLevel::Os: return "-Os"; 26 | case OptimizationLevel::O1: return "-O1"; 27 | case OptimizationLevel::O2: return "-O2"; 28 | case OptimizationLevel::O3: return "-O3"; 29 | case OptimizationLevel::O0: return "-O0"; 30 | } 31 | 32 | return "unknown"; 33 | }(); 34 | 35 | const auto optimize_6502 = [&]() -> std::string_view { 36 | switch (o6502) { 37 | case Optimize6502::Enabled: return "--optimize=1"; 38 | case Optimize6502::Disabled: return "--optimize=0"; 39 | } 40 | 41 | return "unknown"; 42 | }(); 43 | 44 | const auto optimize_6502_name = [&]() -> std::string_view { 45 | switch (o6502) { 46 | case Optimize6502::Enabled: return "-optimize"; 47 | case Optimize6502::Disabled: return "-no-optimize"; 48 | } 49 | 50 | return "unknown"; 51 | }(); 52 | 53 | const auto source_filename{ fmt::format("{}{}{}.cpp", name, optimization_level, optimize_6502_name) }; 54 | const auto vice_script_filename{ fmt::format("{}{}{}-vice_script", name, optimization_level, optimize_6502_name) }; 55 | const auto prg_filename{ fmt::format("{}{}{}.prg", name, optimization_level, optimize_6502_name) }; 56 | const auto ram_dump_filename{ fmt::format("{}{}{}-ram_dump", name, optimization_level, optimize_6502_name) }; 57 | 58 | 59 | { 60 | std::ofstream source(source_filename); 61 | source << script; 62 | } 63 | 64 | { 65 | std::ofstream vice_script(vice_script_filename); 66 | vice_script << fmt::format( 67 | R"( 68 | until e5d1 69 | l "{}" 0 70 | keybuf run\n 71 | until e147 72 | bsave "{}" 0 {:x} {:x} 73 | quit 74 | )", 75 | prg_filename, 76 | ram_dump_filename, 77 | start_address_dump, 78 | end_address_dump); 79 | } 80 | 81 | 82 | REQUIRE(system(fmt::format( 83 | "{} {} -t C64 {} {}", mos6502_cpp_executable, source_filename, optimization_level, optimize_6502) 84 | .c_str()) 85 | == EXIT_SUCCESS); 86 | REQUIRE( 87 | system(fmt::format( 88 | "xvfb-run -d {} +vsync -sounddev dummy +saveres -warp -moncommands {}", x64_executable, vice_script_filename) 89 | .c_str()) 90 | == EXIT_SUCCESS); 91 | 92 | std::ifstream memory_dump(ram_dump_filename, std::ios::binary); 93 | 94 | std::vector data; 95 | data.resize(static_cast(end_address_dump - start_address_dump + 1)); 96 | memory_dump.read(data.data(), std::ssize(data)); 97 | 98 | std::vector return_value{ data.begin(), data.end() }; 99 | return return_value; 100 | } 101 | 102 | TEMPLATE_TEST_CASE_SIG("Can write to memory", 103 | "", 104 | ((OptimizationLevel O), O), 105 | OptimizationLevel::Os, 106 | OptimizationLevel::O0, 107 | OptimizationLevel::O1, 108 | OptimizationLevel::O2, 109 | OptimizationLevel::O3) 110 | { 111 | constexpr static std::string_view program = 112 | R"( 113 | int main() 114 | { 115 | *reinterpret_cast(0x400) = 10; 116 | } 117 | )"; 118 | 119 | const auto result = execute_c64_program("write_to_memory", program, O, Optimize6502::Enabled, 0x400, 0x400); 120 | 121 | REQUIRE(result.size() == 1); 122 | CHECK(result[0] == 10); 123 | } 124 | 125 | TEMPLATE_TEST_CASE_SIG("Can write to memory via function call", 126 | "", 127 | ((OptimizationLevel O), O), 128 | OptimizationLevel::Os, 129 | OptimizationLevel::O0, 130 | OptimizationLevel::O1, 131 | OptimizationLevel::O2, 132 | OptimizationLevel::O3) 133 | { 134 | constexpr static std::string_view program = 135 | R"( 136 | 137 | void poke(unsigned int location, unsigned char value) { 138 | *reinterpret_cast(location) = value; 139 | } 140 | 141 | int main() 142 | { 143 | poke(0x400, 10); 144 | poke(0x401, 11); 145 | } 146 | 147 | )"; 148 | 149 | const auto result = 150 | execute_c64_program("write_to_memory_via_function", program, O, Optimize6502::Enabled, 0x400, 0x401); 151 | 152 | REQUIRE(result.size() == 2); 153 | 154 | CHECK(result[0] == 10); 155 | CHECK(result[1] == 11); 156 | } 157 | 158 | 159 | TEMPLATE_TEST_CASE_SIG("Can execute loop > 256", 160 | "", 161 | ((OptimizationLevel O), O), 162 | OptimizationLevel::Os, 163 | OptimizationLevel::O0, 164 | OptimizationLevel::O1, 165 | OptimizationLevel::O2, 166 | OptimizationLevel::O3) 167 | { 168 | constexpr static std::string_view program = 169 | R"( 170 | 171 | int main() 172 | { 173 | for (unsigned short i = 0x400; i < 0x400 + 1000; ++i) { 174 | *reinterpret_cast(i) = 32; 175 | } 176 | 177 | // while (true) { 178 | // don't allow main to exit, otherwise we get READY. on the screen 179 | // } 180 | } 181 | 182 | )"; 183 | 184 | const auto result = execute_c64_program("execute_long_loop_cls", program, O, Optimize6502::Enabled, 0x400, 0x7E7); 185 | 186 | REQUIRE(result.size() == 1000); 187 | 188 | CHECK(std::all_of(begin(result), end(result), [](const auto b) { return b == 32; })); 189 | } 190 | 191 | TEMPLATE_TEST_CASE_SIG("Write to 2D Array", 192 | "", 193 | ((OptimizationLevel O, Optimize6502 O6502), O, O6502), 194 | (OptimizationLevel::Os, Optimize6502::Disabled), 195 | (OptimizationLevel::Os, Optimize6502::Enabled), 196 | (OptimizationLevel::O0, Optimize6502::Disabled), 197 | (OptimizationLevel::O0, Optimize6502::Enabled), 198 | (OptimizationLevel::O1, Optimize6502::Enabled), 199 | (OptimizationLevel::O2, Optimize6502::Enabled), 200 | (OptimizationLevel::O3, Optimize6502::Disabled), 201 | (OptimizationLevel::O3, Optimize6502::Enabled)) 202 | { 203 | constexpr static std::string_view program = 204 | R"( 205 | 206 | void poke(unsigned int location, unsigned char value) { 207 | *reinterpret_cast(location) = value; 208 | } 209 | 210 | void putc(unsigned char x, unsigned char y, unsigned char c) { 211 | const auto start = 0x400 + (y * 40 + x); 212 | poke(start, c); 213 | } 214 | 215 | 216 | int main() 217 | { 218 | for (unsigned char y = 0; y < 25; ++y) { 219 | for (unsigned char x = 0; x < 40; ++x) { 220 | putc(x, y, y); 221 | } 222 | } 223 | 224 | // while (true) { 225 | // don't allow main to exit, otherwise we get READY. on the screen 226 | // } 227 | } 228 | 229 | )"; 230 | 231 | const auto result = execute_c64_program("write_to_2d_array", program, O, O6502, 0x400, 0x7E7); 232 | 233 | REQUIRE(result.size() == 1000); 234 | 235 | for (std::size_t x = 0; x < 40; ++x) { 236 | for (std::size_t y = 0; y < 25; ++y) { CHECK(result[y * 40 + x] == y); } 237 | } 238 | } 239 | --------------------------------------------------------------------------------