103 Commits

Author SHA1 Message Date
2e7ba82160 Adds back missing WORKSPACE file. 2024-09-08 21:43:19 -07:00
10d6de6a89 Converts from WORKSPACE config to MODULE.bazel config. 2024-09-02 01:06:19 -07:00
cf383f27c7 Refactors Abilities to Ability Groups. 2023-05-03 15:04:46 -07:00
182f76cb87 Refactors constants out of Virtues.h. 2023-04-25 13:45:09 -07:00
34aa82bf73 Refactors constants out of Ranks.h. 2023-04-25 13:42:35 -07:00
dc4295bd86 Refactors constants out of Genders.h. 2023-04-25 13:40:12 -07:00
f35c0f21d0 Refactors constants out of Freebies.h. 2023-04-25 13:38:54 -07:00
c3cd1e65ff Refactors constants out of Disciplines.h. 2023-04-25 13:32:50 -07:00
053da18030 Refactors constants out of Derangements.h. 2023-04-25 13:24:23 -07:00
d322bd0c5c Refactors constants out of Clans.h. 2023-04-25 12:56:51 -07:00
07af18f616 Refactors constants out of Character.h. 2023-04-25 12:50:46 -07:00
279c5d0893 Refactors constants out of Backgrounds.h. 2023-04-25 12:46:39 -07:00
34678d9af6 Refactors constants out of Attributes.h. 2023-04-25 12:44:21 -07:00
d1314e16a4 Refactors constants out of Archetypes.h. 2023-04-25 12:26:38 -07:00
6b1efb0f0f Refactors most constants out of Abilities.h. 2023-04-25 12:21:45 -07:00
79541c8f84 Refactors a lot of stuff out of sbf. 2023-04-25 11:43:23 -07:00
5f3bf98f03 Adds convenience functions to freebies. 2023-04-25 11:32:18 -07:00
2efe940300 Cleans up empty character tests. 2023-04-25 11:31:44 -07:00
ac2c397982 Adds convenience functions to abilities. 2023-04-25 11:31:21 -07:00
ec67dd7645 Adds .compile_commands.json to gitignore. 2023-04-25 11:30:51 -07:00
8defea8746 Implements CGGetDerangement. 2023-04-25 00:05:23 -07:00
61f6c3aae8 Implements CGSpendVirtuePoints. 2023-04-24 23:48:02 -07:00
994fe041af Implements CGGetRoad for VtM. 2023-04-24 23:40:47 -07:00
fe2b0ddfce Implements CGGetBackgrounds. 2023-04-24 23:37:15 -07:00
df1c4c0727 Adds GetBackgroundValues to CharacterType.
Renames a bunch of parameters to simplify them.
2023-04-24 23:36:53 -07:00
005fcf7f56 Adds GetBackgroundLabels. 2023-04-24 23:35:59 -07:00
d33e95a57d Removes compile_commands.json from source control. 2023-04-24 22:47:16 -07:00
bed4ea00a1 Updates to sbf.cpp. 2023-04-24 22:14:44 -07:00
482e237fb9 Adds ToLower to utils.
Renames utils functions to match other functions.
2023-04-24 22:09:20 -07:00
5d9a86cb96 Fixes and enhancements to menus. 2023-04-24 22:06:43 -07:00
a9ea107272 Fixes Character Fill* methods and some getters. 2023-04-24 22:04:21 -07:00
b4764ff480 Adds GetAttributeLabelsInGroup. 2023-04-24 22:02:28 -07:00
7d6e9ec6b8 Fixes SetBackgroundColor to return the previous color.
Fixes SetForegroundColor to return the previous color.
Fixes test_Colors to not care about the order of the foreground and background commands.
2023-04-24 14:56:12 -07:00
ec4cf99c60 Removes FillDerangements.
Refactors GetAllDerangementsLine to be simpler.
2023-04-24 14:54:45 -07:00
8ec8b1dfe0 Refactors tests to better fit with how bazel runs them. 2023-04-24 13:59:21 -07:00
3bf43ef62a Configures for bazel build and test. 2023-04-24 02:13:10 -07:00
1e7c4eb726 Lots of implementation for sbf-cpp. 2023-04-24 01:08:22 -07:00
b5b72efebc Removes a bunch of stuff from settings.json. 2023-04-24 01:01:16 -07:00
0f493366ca Makes test harnesses frendlier.
Adds Menus and Random modules to Makefile.
Adds -DDebug flag to debug builds.
2023-04-24 01:00:54 -07:00
bb95df74f4 Removes old comments from colors.cpp. 2023-04-22 00:39:58 -07:00
1f45248ccd Removes unknown values from fill functions.
Indexes will no longer match Ids.
Updated all tests to use char* argv[] instead of char** argv.
2023-04-22 00:38:48 -07:00
e8e9904de3 Implements freebies and tests. 2023-04-22 00:32:17 -07:00
1470ed80b9 Adds sbf-all.bas that combines all qb files. 2023-04-21 22:45:50 -07:00
e466196c1a Adds UTF-8 BOM to all C++ files. 2023-04-21 22:45:16 -07:00
4b4fda698f Updates comment in sbf.h with latest qb source. 2023-04-21 22:44:34 -07:00
5e57ac277d Configures clang-format and clangd.
Runs clang-format on all c++ files.
2023-04-21 21:57:29 -07:00
64c7632806 Adds TODOs for c++ version. 2023-04-18 00:29:24 -07:00
ce6a75eb80 Converts the ncurses test from wchar_t to regular char. 2023-04-18 00:28:21 -07:00
891890f779 Adds documentation to the implemented modules. 2023-04-18 00:11:29 -07:00
9b344b5d6b Implements virtues and tests. 2023-04-17 23:22:22 -07:00
05bd423109 Adds error summary to test harnesses. 2023-04-17 23:00:35 -07:00
cc64937a2e Makes TinyTest wrap expected and actual outputs in quotes.
Fixes a type on a TinyTest error message.
2023-04-17 23:00:10 -07:00
1f6df1227c Implements utils and tests. 2023-04-17 22:58:50 -07:00
d7df3a2be4 Adds tests for utils functions. 2023-04-17 19:47:39 -07:00
204a6a27df Implements ranks and tests. 2023-04-17 19:35:46 -07:00
cf1d7f5896 Implements genders and tests. 2023-04-17 18:54:24 -07:00
f8394a8d03 Implements disciplines and tests. 2023-04-17 18:44:55 -07:00
018cce461d Implements derangements and tests. 2023-04-17 18:27:38 -07:00
91271f38b0 Fixes abilities tests to check operator!=. 2023-04-16 07:17:49 -07:00
a4b0bc7e7b Implements colors and tests. 2023-04-16 04:08:51 -07:00
5501de76e2 Adds doxygen comments to SBF namespace.
Adds doxygen header to header files.
2023-04-16 04:08:33 -07:00
7c9c42b17c Cleans up Doxyfile comments. 2023-04-16 04:06:01 -07:00
382aa14611 Adds doxygen comments and MIT license comment to Abilities.h. 2023-04-16 03:01:18 -07:00
38014594ee Adds docs and read-docs Makefile targets. 2023-04-16 03:00:35 -07:00
d0ed32bc23 Adds the MIT license file. 2023-04-16 02:58:15 -07:00
09dcfd86b2 Adds a basic Doxyfile. 2023-04-16 02:57:58 -07:00
2e937ce02c Implements clans and tests. 2023-04-15 23:25:00 -07:00
670c517326 Implements backgrounds and tests. 2023-04-15 22:54:04 -07:00
3111b9a8aa Implements Attributes.
Moves attribute functions from Character.cpp to Attributes.cpp.
2023-04-15 22:15:42 -07:00
11ccce8ce1 Fixes indentation in Archetypes_test.cpp. 2023-04-15 22:14:17 -07:00
02e3b2f10a Adds a new c++ stdlib header to file associations. 2023-04-15 22:13:58 -07:00
ffbcce2d35 Implements Archetypes functions.
Adds tests for them.
2023-04-15 18:46:14 -07:00
3c30150a70 Adds operator != to AbilityType.
Makes Abiites tests use the vector compare helper.
Makes Abilities tests report "no errors" instead of "" for success.
2023-04-15 18:45:37 -07:00
20e413742c Fixes typo in Makefile "ehco" instead of "echo". 2023-04-15 18:43:46 -07:00
a5fff60a61 Adds vector compare helper to TinyTest. 2023-04-15 18:43:04 -07:00
fb02bf059a Disables clang warning for a not yet used color variable. 2023-04-15 16:30:32 -07:00
b3bb6e4031 Converts from wstring to regular string.
Cleans up empty test suites to return no tests instead of a single pass.
2023-04-15 16:30:06 -07:00
e68973c1b1 Documents Abilities.
Adds full test coverage for Abilities.
2023-04-15 16:27:36 -07:00
31a5b60f7f Outputs the error count when running tests.
Outputs a summary of failure messages after running tests.
2023-04-15 16:26:46 -07:00
3751d78dfe TinyTest improvements.
Adds ostream formatting for tuples and vectors.
Moves execute_suite implementation into test.h.
Adds a better test execution sequence and better test reporting.
Adds message support for test errors/failures/skips.
Removes more dead code from test.cpp.
2023-04-15 16:22:40 -07:00
7f95c4dabb Removes a bunch of old comments. 2023-04-14 16:18:06 -07:00
b525394a41 Adds freebie point spending. Adds cancel as an option to MenuStyle to allow cancelling. Adds includeInRandom to MenuItem to hide specific MenuItems from GetRandomMenuItemId. 2023-04-14 16:16:24 -07:00
5e27ff5b89 Allows spending freebie points on disciplines and attributes. 2023-04-13 22:59:28 -07:00
97352b489e Adds a MaybeCls sub that only clears the screen if isDebugging is set. 2023-04-13 21:11:00 -07:00
247b8c760a save-state-1 2023-04-12 22:11:23 -07:00
ff65bd12a5 Adds targets make target and cleans up makefile. 2023-04-12 15:55:01 -07:00
97b5f557c5 Adds the ability to build and run a single test suite. 2023-03-31 03:38:50 -07:00
c2f04de4f8 Changes to test system to allow per class tests. 2023-03-31 03:08:57 -07:00
c5c8bddb48 Filled in all c++ headers from QB code.
Copied methods not yet implemented from QB to cpp files.
2023-03-30 00:51:00 -07:00
64eac55804 Adds some unicode and ncurses links to Notes.md. 2023-03-28 19:26:47 -07:00
1d347e105a Cleans up makefile and adds better testing targets. 2023-03-28 19:25:32 -07:00
c47c7e5a93 Streamlines tests greatly. 2023-03-28 18:56:21 -07:00
bc961f6ba3 Cleans up the way tests work in test.cpp. 2023-03-28 01:45:05 -07:00
e2a60b14da Adds test for make_wrap_lines. 2023-03-26 19:56:51 -07:00
b6d097f84f Cleanup, comments, and making extra test aps. 2023-03-26 03:55:57 -07:00
732283b699 Adds ncursesw support. 2023-03-26 03:00:47 -07:00
f19ec79b1e Adds a basic c++ app using ncurses and a build system. 2023-03-25 17:12:32 -07:00
9ba05f381f Adds a bunch of todo items. 2023-03-17 10:51:07 -07:00
a952943814 Updates the splash screen. 2023-03-17 09:34:29 -07:00
32959aaf8c Makes rank names, attribute names, and ability names pull from arrays instead of using generic vals. 2023-03-17 07:08:06 -07:00
82df15364f Extracts the Character "class".
closes #9
2023-03-11 10:42:27 -08:00
b3c9bb61e8 Extracts menu stuff.
closes #7
2023-03-11 09:42:43 -08:00
53b0de7079 Extracts colors to separate files. 2023-03-11 09:29:22 -08:00
87 changed files with 16025 additions and 1523 deletions

260
.clang-format Normal file
View File

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

8
.gitignore vendored
View File

@@ -1,3 +1,7 @@
/bazel-bin
/bazel-out
/bazel-sbf2000
/bazel-testlogs
/dos/qb45
/dos/qbasic11
/tmp
@@ -5,3 +9,7 @@
/asm
/dos/sbf/sbf
/dos/sbf/sbf_start.command
/build
*.o
.cache
.compile_commands.json

22
.vscode/c_cpp_properties.json vendored Normal file
View File

@@ -0,0 +1,22 @@
{
"configurations": [
{
"name": "Mac",
"includePath": [
"${workspaceFolder}/**",
"/opt/homebrew/include",
"/opt/homebrew/opt/ncurses/include",
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers"
],
"defines": [],
"macFrameworkPath": [
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks"
],
"compilerPath": "/usr/bin/clang",
"cStandard": "c17",
"cppStandard": "c++17",
"intelliSenseMode": "macos-clang-arm64"
}
],
"version": 4
}

View File

@@ -1,3 +1,5 @@
{
"files.encoding": "cp437"
"[cpp]": {
"editor.tabSize": 2
}
}

398
Doxyfile Normal file
View File

@@ -0,0 +1,398 @@
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = "Storyteller's Best Friend"
PROJECT_NUMBER =
PROJECT_BRIEF =
PROJECT_LOGO =
OUTPUT_DIRECTORY = build/docs
CREATE_SUBDIRS = NO
CREATE_SUBDIRS_LEVEL = 8
ALLOW_UNICODE_NAMES = NO
OUTPUT_LANGUAGE = English
BRIEF_MEMBER_DESC = YES
REPEAT_BRIEF = YES
ABBREVIATE_BRIEF = "The $name class" \
"The $name widget" \
"The $name file" \
is \
provides \
specifies \
contains \
represents \
a \
an \
the
ALWAYS_DETAILED_SEC = NO
INLINE_INHERITED_MEMB = NO
FULL_PATH_NAMES = YES
STRIP_FROM_PATH =
STRIP_FROM_INC_PATH =
SHORT_NAMES = NO
JAVADOC_AUTOBRIEF = NO
JAVADOC_BANNER = YES
QT_AUTOBRIEF = NO
MULTILINE_CPP_IS_BRIEF = NO
PYTHON_DOCSTRING = YES
INHERIT_DOCS = YES
SEPARATE_MEMBER_PAGES = NO
TAB_SIZE = 2
ALIASES =
OPTIMIZE_OUTPUT_FOR_C = YES
OPTIMIZE_OUTPUT_JAVA = NO
OPTIMIZE_FOR_FORTRAN = NO
OPTIMIZE_OUTPUT_VHDL = NO
OPTIMIZE_OUTPUT_SLICE = NO
EXTENSION_MAPPING =
# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
# according to the Markdown format, which allows for more readable
# documentation. See https://daringfireball.net/projects/markdown/ for details.
# The output of markdown processing is further processed by doxygen, so you can
# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
# case of backward compatibilities issues.
# The default value is: YES.
MARKDOWN_SUPPORT = YES
TOC_INCLUDE_HEADINGS = 5
AUTOLINK_SUPPORT = YES
BUILTIN_STL_SUPPORT = YES
CPP_CLI_SUPPORT = NO
SIP_SUPPORT = NO
IDL_PROPERTY_SUPPORT = YES
DISTRIBUTE_GROUP_DOC = NO
GROUP_NESTED_COMPOUNDS = NO
SUBGROUPING = YES
INLINE_GROUPED_CLASSES = NO
INLINE_SIMPLE_STRUCTS = NO
TYPEDEF_HIDES_STRUCT = NO
LOOKUP_CACHE_SIZE = 0
NUM_PROC_THREADS = 1
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
EXTRACT_ALL = YES
EXTRACT_PRIVATE = NO
EXTRACT_PRIV_VIRTUAL = NO
EXTRACT_PACKAGE = NO
EXTRACT_STATIC = YES
EXTRACT_LOCAL_CLASSES = YES
EXTRACT_LOCAL_METHODS = NO
EXTRACT_ANON_NSPACES = NO
RESOLVE_UNNAMED_PARAMS = YES
HIDE_UNDOC_MEMBERS = NO
HIDE_UNDOC_CLASSES = NO
HIDE_FRIEND_COMPOUNDS = NO
HIDE_IN_BODY_DOCS = NO
INTERNAL_DOCS = NO
CASE_SENSE_NAMES = SYSTEM
HIDE_SCOPE_NAMES = NO
HIDE_COMPOUND_REFERENCE= NO
SHOW_HEADERFILE = YES
SHOW_INCLUDE_FILES = YES
SHOW_GROUPED_MEMB_INC = NO
FORCE_LOCAL_INCLUDES = NO
INLINE_INFO = YES
SORT_MEMBER_DOCS = YES
SORT_BRIEF_DOCS = NO
SORT_MEMBERS_CTORS_1ST = NO
SORT_GROUP_NAMES = NO
SORT_BY_SCOPE_NAME = NO
STRICT_PROTO_MATCHING = NO
GENERATE_TODOLIST = YES
GENERATE_TESTLIST = YES
GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= YES
ENABLED_SECTIONS =
MAX_INITIALIZER_LINES = 30
SHOW_USED_FILES = YES
SHOW_FILES = YES
SHOW_NAMESPACES = YES
FILE_VERSION_FILTER =
LAYOUT_FILE =
CITE_BIB_FILES =
#---------------------------------------------------------------------------
# Configuration options related to warning and progress messages
#---------------------------------------------------------------------------
QUIET = NO
WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES
WARN_IF_DOC_ERROR = YES
WARN_IF_INCOMPLETE_DOC = YES
WARN_NO_PARAMDOC = NO
WARN_IF_UNDOC_ENUM_VAL = NO
WARN_AS_ERROR = NO
WARN_FORMAT = "$file:$line: $text"
WARN_LINE_FORMAT = "at line $line of file $file"
WARN_LOGFILE =
#---------------------------------------------------------------------------
# Configuration options related to the input files
#---------------------------------------------------------------------------
INPUT = sbf-cpp
INPUT_ENCODING = UTF-8
INPUT_FILE_ENCODING =
FILE_PATTERNS = *.c \
*.cc \
*.cxx \
*.cpp \
*.c++ \
*.java \
*.ii \
*.ixx \
*.ipp \
*.i++ \
*.inl \
*.idl \
*.ddl \
*.odl \
*.h \
*.hh \
*.hxx \
*.hpp \
*.h++ \
*.l \
*.cs \
*.d \
*.php \
*.php4 \
*.php5 \
*.phtml \
*.inc \
*.m \
*.markdown \
*.md \
*.mm \
*.dox \
*.py \
*.pyw \
*.f90 \
*.f95 \
*.f03 \
*.f08 \
*.f18 \
*.f \
*.for \
*.vhd \
*.vhdl \
*.ucf \
*.qsf \
*.ice
RECURSIVE = YES
EXCLUDE_SYMLINKS = NO
EXCLUDE_PATTERNS = test.h \
test.cpp \
*_test.cpp
EXCLUDE_SYMBOLS =
EXAMPLE_PATH =
EXAMPLE_PATTERNS = *
EXAMPLE_RECURSIVE = NO
IMAGE_PATH =
INPUT_FILTER =
FILTER_PATTERNS =
FILTER_SOURCE_FILES = NO
FILTER_SOURCE_PATTERNS =
USE_MDFILE_AS_MAINPAGE =
FORTRAN_COMMENT_AFTER = 72
#---------------------------------------------------------------------------
# Configuration options related to source browsing
#---------------------------------------------------------------------------
SOURCE_BROWSER = NO
INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = YES
REFERENCED_BY_RELATION = NO
REFERENCES_RELATION = NO
REFERENCES_LINK_SOURCE = YES
SOURCE_TOOLTIPS = YES
USE_HTAGS = NO
VERBATIM_HEADERS = YES
#---------------------------------------------------------------------------
# Configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
ALPHABETICAL_INDEX = YES
IGNORE_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the HTML output
#---------------------------------------------------------------------------
GENERATE_HTML = YES
HTML_OUTPUT = html
HTML_FILE_EXTENSION = .html
HTML_HEADER =
HTML_FOOTER =
HTML_STYLESHEET =
HTML_EXTRA_STYLESHEET =
HTML_EXTRA_FILES =
HTML_COLORSTYLE = AUTO_LIGHT
HTML_COLORSTYLE_HUE = 220
HTML_COLORSTYLE_SAT = 100
HTML_COLORSTYLE_GAMMA = 80
HTML_TIMESTAMP = NO
HTML_DYNAMIC_MENUS = YES
HTML_DYNAMIC_SECTIONS = NO
HTML_INDEX_NUM_ENTRIES = 100
GENERATE_DOCSET = NO
DOCSET_FEEDNAME = "Doxygen generated docs"
DOCSET_FEEDURL =
DOCSET_BUNDLE_ID = org.doxygen.Project
DOCSET_PUBLISHER_ID = org.doxygen.Publisher
DOCSET_PUBLISHER_NAME = Publisher
GENERATE_HTMLHELP = YES
CHM_FILE =
HHC_LOCATION =
GENERATE_CHI = NO
CHM_INDEX_ENCODING =
BINARY_TOC = NO
TOC_EXPAND = NO
GENERATE_QHP = NO
QCH_FILE =
QHP_NAMESPACE = org.doxygen.Project
QHP_VIRTUAL_FOLDER = doc
QHP_CUST_FILTER_NAME =
QHP_CUST_FILTER_ATTRS =
QHP_SECT_FILTER_ATTRS =
QHG_LOCATION =
GENERATE_ECLIPSEHELP = NO
ECLIPSE_DOC_ID = org.doxygen.Project
DISABLE_INDEX = NO
GENERATE_TREEVIEW = YES
FULL_SIDEBAR = NO
ENUM_VALUES_PER_LINE = 4
TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO
OBFUSCATE_EMAILS = YES
HTML_FORMULA_FORMAT = png
FORMULA_FONTSIZE = 10
FORMULA_MACROFILE =
USE_MATHJAX = NO
MATHJAX_VERSION = MathJax_2
MATHJAX_FORMAT = HTML-CSS
MATHJAX_RELPATH =
MATHJAX_EXTENSIONS =
MATHJAX_CODEFILE =
SEARCHENGINE = YES
SERVER_BASED_SEARCH = NO
EXTERNAL_SEARCH = NO
SEARCHENGINE_URL =
SEARCHDATA_FILE = searchdata.xml
EXTERNAL_SEARCH_ID =
EXTRA_SEARCH_MAPPINGS =
#---------------------------------------------------------------------------
# Configuration options related to the LaTeX output
#---------------------------------------------------------------------------
GENERATE_LATEX = YES
LATEX_OUTPUT = latex
LATEX_CMD_NAME =
MAKEINDEX_CMD_NAME = makeindex
LATEX_MAKEINDEX_CMD = makeindex
COMPACT_LATEX = NO
PAPER_TYPE = a4
EXTRA_PACKAGES =
LATEX_HEADER =
LATEX_FOOTER =
LATEX_EXTRA_STYLESHEET =
LATEX_EXTRA_FILES =
PDF_HYPERLINKS = YES
USE_PDFLATEX = YES
LATEX_BATCHMODE = NO
LATEX_HIDE_INDICES = NO
LATEX_BIB_STYLE = plain
LATEX_TIMESTAMP = NO
LATEX_EMOJI_DIRECTORY =
#---------------------------------------------------------------------------
# Configuration options related to the RTF output
#---------------------------------------------------------------------------
GENERATE_RTF = NO
RTF_OUTPUT = rtf
COMPACT_RTF = NO
RTF_HYPERLINKS = NO
RTF_STYLESHEET_FILE =
RTF_EXTENSIONS_FILE =
#---------------------------------------------------------------------------
# Configuration options related to the man page output
#---------------------------------------------------------------------------
GENERATE_MAN = YES
MAN_OUTPUT = man
MAN_EXTENSION = .3
MAN_SUBDIR =
MAN_LINKS = NO
#---------------------------------------------------------------------------
# Configuration options related to the XML output
#---------------------------------------------------------------------------
GENERATE_XML = NO
XML_OUTPUT = xml
XML_PROGRAMLISTING = YES
XML_NS_MEMB_FILE_SCOPE = NO
#---------------------------------------------------------------------------
# Configuration options related to the DOCBOOK output
#---------------------------------------------------------------------------
GENERATE_DOCBOOK = NO
DOCBOOK_OUTPUT = docbook
#---------------------------------------------------------------------------
# Configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
GENERATE_AUTOGEN_DEF = NO
#---------------------------------------------------------------------------
# Configuration options related to the Perl module output
#---------------------------------------------------------------------------
GENERATE_PERLMOD = NO
PERLMOD_LATEX = NO
PERLMOD_PRETTY = YES
PERLMOD_MAKEVAR_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = NO
EXPAND_ONLY_PREDEF = NO
SEARCH_INCLUDES = YES
INCLUDE_PATH =
INCLUDE_FILE_PATTERNS =
PREDEFINED =
EXPAND_AS_DEFINED =
SKIP_FUNCTION_MACROS = YES
#---------------------------------------------------------------------------
# Configuration options related to external references
#---------------------------------------------------------------------------
TAGFILES =
GENERATE_TAGFILE =
ALLEXTERNALS = NO
EXTERNAL_GROUPS = YES
EXTERNAL_PAGES = YES
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
DIA_PATH =
HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = NO
DOT_NUM_THREADS = 0
DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10"
DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10"
DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4"
DOT_FONTPATH =
CLASS_GRAPH = YES
COLLABORATION_GRAPH = YES
GROUP_GRAPHS = YES
UML_LOOK = NO
UML_LIMIT_NUM_FIELDS = 10
DOT_UML_DETAILS = NO
DOT_WRAP_THRESHOLD = 17
TEMPLATE_RELATIONS = NO
INCLUDE_GRAPH = YES
INCLUDED_BY_GRAPH = YES
CALL_GRAPH = NO
CALLER_GRAPH = NO
GRAPHICAL_HIERARCHY = YES
DIRECTORY_GRAPH = YES
DIR_GRAPH_MAX_DEPTH = 1
DOT_IMAGE_FORMAT = png
INTERACTIVE_SVG = NO
DOT_PATH =
DOTFILE_DIRS =
MSCFILE_DIRS =
DIAFILE_DIRS =
PLANTUML_JAR_PATH =
PLANTUML_CFG_FILE =
PLANTUML_INCLUDE_PATH =
DOT_GRAPH_MAX_NODES = 50
MAX_DOT_GRAPH_DEPTH = 0
DOT_MULTI_TARGETS = NO
GENERATE_LEGEND = YES
DOT_CLEANUP = YES

21
LICENSE Normal file
View File

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

21
MODULE.bazel Normal file
View File

@@ -0,0 +1,21 @@
###############################################################################
# Bazel now uses Bzlmod by default to manage external dependencies.
# Please consider migrating your external dependencies from WORKSPACE to MODULE.bazel.
#
# For more details, please check https://github.com/bazelbuild/bazel/issues/18958
###############################################################################
"DocString to avoid warning. Yeah, yeah, I'll figure this out later."
module(name = "sbf")
# Hedron's Compile Commands Extractor for Bazel
# https://github.com/hedronvision/bazel-compile-commands-extractor
# To update config run `bazel run @hedron_compile_commands//:refresh_all`
bazel_dep(name = "hedron_compile_commands", dev_dependency = True)
git_override(
module_name = "hedron_compile_commands",
remote = "https://github.com/hedronvision/bazel-compile-commands-extractor.git",
commit = "1e08f8e0507b6b6b1f4416a9a22cf5c28beaba93",
# Replace the commit hash (above) with the latest (https://github.com/hedronvision/bazel-compile-commands-extractor/commits/main).
# Even better, set up Renovate and let it do the work for you (see "Suggestion: Updates" in the README).
)

110
MODULE.bazel.lock generated Normal file
View File

@@ -0,0 +1,110 @@
{
"lockFileVersion": 11,
"registryFileHashes": {
"https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497",
"https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2",
"https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589",
"https://bcr.bazel.build/modules/abseil-cpp/20211102.0/source.json": "7e3a9adf473e9af076ae485ed649d5641ad50ec5c11718103f34de03170d94ad",
"https://bcr.bazel.build/modules/apple_support/1.5.0/MODULE.bazel": "50341a62efbc483e8a2a6aec30994a58749bd7b885e18dd96aa8c33031e558ef",
"https://bcr.bazel.build/modules/apple_support/1.5.0/source.json": "eb98a7627c0bc486b57f598ad8da50f6625d974c8f723e9ea71bd39f709c9862",
"https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8",
"https://bcr.bazel.build/modules/bazel_features/1.11.0/source.json": "c9320aa53cd1c441d24bd6b716da087ad7e4ff0d9742a9884587596edfe53015",
"https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8",
"https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a",
"https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5",
"https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917",
"https://bcr.bazel.build/modules/bazel_skylib/1.6.1/source.json": "082ed5f9837901fada8c68c2f3ddc958bb22b6d654f71dd73f3df30d45d4b749",
"https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84",
"https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8",
"https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4",
"https://bcr.bazel.build/modules/googletest/1.11.0/source.json": "c73d9ef4268c91bd0c1cd88f1f9dfa08e814b1dbe89b5f594a9f08ba0244d206",
"https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee",
"https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37",
"https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615",
"https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814",
"https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc",
"https://bcr.bazel.build/modules/platforms/0.0.9/source.json": "cd74d854bf16a9e002fb2ca7b1a421f4403cda29f824a765acd3a8c56f8d43e6",
"https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7",
"https://bcr.bazel.build/modules/protobuf/21.7/source.json": "bbe500720421e582ff2d18b0802464205138c06056f443184de39fbb8187b09b",
"https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0",
"https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858",
"https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647",
"https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c",
"https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
"https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5",
"https://bcr.bazel.build/modules/rules_cc/0.0.9/source.json": "1f1ba6fea244b616de4a554a0f4983c91a9301640c8fe0dd1d410254115c8430",
"https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74",
"https://bcr.bazel.build/modules/rules_java/7.6.5/MODULE.bazel": "481164be5e02e4cab6e77a36927683263be56b7e36fef918b458d7a8a1ebadb1",
"https://bcr.bazel.build/modules/rules_java/7.6.5/source.json": "a805b889531d1690e3c72a7a7e47a870d00323186a9904b36af83aa3d053ee8d",
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/source.json": "a075731e1b46bc8425098512d038d416e966ab19684a10a34f4741295642fc35",
"https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0",
"https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d",
"https://bcr.bazel.build/modules/rules_license/0.0.7/source.json": "355cc5737a0f294e560d52b1b7a6492d4fff2caf0bef1a315df5a298fca2d34a",
"https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc",
"https://bcr.bazel.build/modules/rules_pkg/0.7.0/source.json": "c2557066e0c0342223ba592510ad3d812d4963b9024831f7f66fd0584dd8c66c",
"https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06",
"https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7",
"https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/source.json": "d57902c052424dfda0e71646cb12668d39c4620ee0544294d9d941e7d12bc3a9",
"https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f",
"https://bcr.bazel.build/modules/rules_python/0.22.1/MODULE.bazel": "26114f0c0b5e93018c0c066d6673f1a2c3737c7e90af95eff30cfee38d0bbac7",
"https://bcr.bazel.build/modules/rules_python/0.22.1/source.json": "57226905e783bae7c37c2dd662be078728e48fa28ee4324a7eabcafb5a43d014",
"https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c",
"https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
"https://bcr.bazel.build/modules/stardoc/0.5.1/source.json": "a96f95e02123320aa015b956f29c00cb818fa891ef823d55148e1a362caacf29",
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/source.json": "f1ef7d3f9e0e26d4b23d1c39b5f5de71f584dd7d1b4ef83d9bbba6ec7a6a6459",
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
"https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d"
},
"selectedYankedVersions": {},
"moduleExtensions": {
"@@apple_support~//crosstool:setup.bzl%apple_cc_configure_extension": {
"general": {
"bzlTransitiveDigest": "PjIds3feoYE8SGbbIq2SFTZy3zmxeO2tQevJZNDo7iY=",
"usagesDigest": "aLmqbvowmHkkBPve05yyDNGN7oh7QE9kBADr3QIZTZs=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"local_config_apple_cc": {
"bzlFile": "@@apple_support~//crosstool:setup.bzl",
"ruleClassName": "_apple_cc_autoconf",
"attributes": {}
},
"local_config_apple_cc_toolchains": {
"bzlFile": "@@apple_support~//crosstool:setup.bzl",
"ruleClassName": "_apple_cc_autoconf_toolchains",
"attributes": {}
}
},
"recordedRepoMappingEntries": [
[
"apple_support~",
"bazel_tools",
"bazel_tools"
]
]
}
},
"@@platforms//host:extension.bzl%host_platform": {
"general": {
"bzlTransitiveDigest": "xelQcPZH8+tmuOHVjL9vDxMnnQNMlwj0SlvgoqBkm4U=",
"usagesDigest": "meSzxn3DUCcYEhq4HQwExWkWtU4EjriRBQLsZN+Q0SU=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"host_platform": {
"bzlFile": "@@platforms//host:extension.bzl",
"ruleClassName": "host_platform_repo",
"attributes": {}
}
},
"recordedRepoMappingEntries": []
}
}
}
}

223
Makefile Normal file
View File

@@ -0,0 +1,223 @@
.SECONDEXPANSION:
BUILD_DIR := build
OUT_DIR := build
SRC_DIR = sbf-cpp
RELATIVE_SRC_DIR = ../$(SRC_DIR)
ifdef DEBUG
DBG_OPT_FLAGS=-g -DDEBUG
else
DBG_OPT_FLAGS=-O2
endif
ifdef ASAN
ASAN_FLAGS=-fsanitize=address
else
ASAN_FLAGS=
endif
BREW86_PREFIX := $(shell arch -x86_64 /usr/local/homebrew/bin/brew --prefix)
BREW_PREFIX := $(shell brew --prefix)
LIB_DIR = $(BUILD_DIR)/lib
INCLUDE_DIRS = sbf-cpp $(BREW_PREFIX)/opt/ncurses/include
INCLUDES := $(patsubst %, -I%, $(INCLUDE_DIRS))
LIBRARY_DIRS = $(BUILD_DIR)/lib
LIBRARIES := $(patsubst %, -L%, $(LIBRARY_DIRS)) -lncurses
FRAMEWORK_NAMES =
FRAMEWORKS := $(patsubst %, -framework %, $(FRAMEWORK_NAMES))
ARCHS = -arch arm64 -arch x86_64
CCFLAGS := -std=c++17 -Wall -fno-objc-arc -finput-charset=UTF-8 $(INCLUDES) $(DBG_OPT_FLAGS) $(ASAN_FLAGS) $(ARCHS) -fdiagnostics-show-template-tree -fno-elide-type
LDFLAGS := $(LIBRARIES) $(FRAMEWORKS) $(ARCHS) $(DBG_OPT_FLAGS) $(ASAN_FLAGS)
CC = clang++
LD = clang++
CLIAPPNAME=sbf
TESTAPPNAME=test
APPS = $(CLIAPPNAME) $(TESTAPPNAME)
APPNAME=SBF
BUNDLENAME = $(APPNAME).app
BUNDLEFOLDER = $(BUILD_DIR)/$(BUNDLENAME)
BUNDLEIDENTIFIER = com.majinnaibu.test.$(APPNAME)
BUNDLESIGNATURE = SBF_
RESOURCES_DIR = Resources
#APP_OBJECTS = Character.o Colors.o Menus.o
MODULES = Abilities Archetypes Attributes Backgrounds Character Clans Colors Derangements Disciplines Freebies Genders Menus Random Ranks Utils Virtues
APP_OBJECTS = $(patsubst %, $(BUILD_DIR)/%.o, $(MODULES))
FAT_LIBS = ncurses
FAT_LIB_OBJECTS = $(patsubst %, $(LIB_DIR)/lib%.a, $(FAT_LIBS))
TEST_MODULES = $(patsubst %, %_test, $(MODULES))
TEST_OBJECTS = $(patsubst %, $(BUILD_DIR)/%.o, $(TEST_MODULES))
TEST_MAIN_CPP = $(BUILD_DIR)/test_main.cpp
ifdef $(MODULE)
TEST_HARNESS_CPP = $(BUILD_DIR)/$(MODULE)_test_harness.cpp
TEST_HARNESS_OBJ = $(BUILD_DIR)/$(MODULE)_test_harness.o
TEST_HARNESS_APP = $(BUILD_DIR)/$(MODULE)_test
TEST_OBJ = $(BUILD_DIR)/$(MODULE)_test.o
endif
COMMA = ,
EMPTY =
SPACE = $(EMPTY) $(EMPTY)
DOCS_DIR = $(BUILD_DIR)/docs
MANDATORY_TARGETS = Makefile
.Phony: all clean app test run_all_tests run_incremental_tests targets docs read-docs
all: compile_commands.json $(patsubst %, $(BUILD_DIR)/%, $(APPS)) app
clean:
rm -rf "$(BUILD_DIR)"
mkdir -p "$(BUILD_DIR)"
mkdir -p "$(BUILD_DIR)/Contents"
mkdir -p "$(BUILD_DIR)/lib"
mkdir -p "$(BUNDLEFOLDER)/Contents/MacOS"
mkdir -p "$(BUNDLEFOLDER)/Contents/Resources"
run_all_tests: clean $(BUILD_DIR)/$(TESTAPPNAME) $(MANDATORY_TARGETS)
$(BUILD_DIR)/$(TESTAPPNAME)
run_incremental_tests: $(BUILD_DIR)/$(TESTAPPNAME) $(MANDATORY_TARGETS)
$(BUILD_DIR)/$(TESTAPPNAME)
test: run_incremental_tests
targets:
@echo "Usefull make targets"
@echo " targets - Displays this list of make targets."
@echo " clean - Deletes the build folder including all apps and intermediate build files."
@echo " all - Builds CLI apps $(subst $(SPACE),$(COMMA)$(SPACE),$(APPS)) and the app bundle $(BUNDLENAME)."
@echo " app - Builds the app bundle $(BUNDLENAME). This also indirectly builds $(CLIAPPNAME)."
@echo " test - Does an incremental build and runs all tests."
@echo " run_all_tests - Does a clean and rebuild then runs all tests."
@echo " docs - Generates documentation in the $(DOCS_DIR)"
@echo " read-docs - Opens the index.html file in your default browser. Builds the documentation if necessary."
docs: Doxyfile $(wildcard **/*.h) $(MANDATORY_TARGETS)
@doxygen Doxyfile
read-docs: docs
@open build/docs/html/index.html
$(BUILD_DIR)/_test:
@echo "You need to define MODULE when you run make run_test."
run: $(BUILD_DIR)/$(CLIAPPNAME)
$(BUILD_DIR)/$(CLIAPPNAME)
# Main app
app: $(BUILD_DIR)/$(CLIAPPNAME) $(SRC_DIR)/Info.plist $(RESOURCES_DIR)/en-US.lproj/InfoPlist.strings $(MANDATORY_TARGETS)
cp $(SRC_DIR)/Info.plist $(BUILD_DIR)/Contents/Info.plist
plutil -replace CFBundleExecutable -string "$(APPNAME)" $(BUILD_DIR)/Contents/Info.plist
plutil -replace CFBundleIdentifier -string "com.majinnaibu.$(APPNAME)" $(BUILD_DIR)/Contents/Info.plist
plutil -replace CFBundleName -string "$(APPNAME)" $(BUILD_DIR)/Contents/Info.plist
plutil -replace CFBundleSignature -string $(BUNDLESIGNATURE) $(BUILD_DIR)/Contents/Info.plist
cp $(BUILD_DIR)/Contents/Info.plist $(BUNDLEFOLDER)/Contents/
cp $(BUILD_DIR)/$(CLIAPPNAME) $(BUNDLEFOLDER)/Contents/MacOS/$(APPNAME)
cp -r $(RESOURCES_DIR)/* $(BUNDLEFOLDER)/Contents/Resources/
$(BUILD_DIR)/$(CLIAPPNAME): $(BUILD_DIR)/sbf.o $(APP_OBJECTS) $(FAT_LIB_OBJECTS) $(MANDATORY_TARGETS)
$(LD) $(LDFLAGS) -o $@ $< $(APP_OBJECTS)
$(BUILD_DIR)/$(TESTAPPNAME): $(BUILD_DIR)/test_main.o $(BUILD_DIR)/test.o $(APP_OBJECTS) $(TEST_OBJECTS) $(FAT_LIB_OBJECTS) $(TEST_MAIN_CPP) $(MANDATORY_TARGETS)
$(LD) $(LDFLAGS) -o $@ $< $(APP_OBJECTS) $(TEST_OBJECTS) $(BUILD_DIR)/test.o
$(BUILD_DIR)/test_main.o: $(TEST_MAIN_CPP) $(SRC_DIR)/*.h $(SRC_DIR)/*_test.cpp $(MANDATORY_TARGETS)
$(CC) $(CCFLAGS) -c -o $@ $<
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp $(SRC_DIR)/*.h $(MANDATORY_TARGETS)
$(CC) $(CCFLAGS) -c -o $@ $<
# Create build/test_main.cpp with a main function that calls all of the test_main_* functions in each of the $(TEST_OBJECTS).
$(TEST_MAIN_CPP): $(TEST_OBJECTS)
@echo "// This file is auto generated\n" > $(TEST_MAIN_CPP)
@echo "$(patsubst %, #include \"$(RELATIVE_SRC_DIR)/%.h\"\n, $(MODULES))" >> $(TEST_MAIN_CPP)
@echo "#include \"$(RELATIVE_SRC_DIR)/test.h\"" >> $(TEST_MAIN_CPP)
@echo "#include <clocale>" >> $(TEST_MAIN_CPP)
@echo "#include <string>" >> $(TEST_MAIN_CPP)
@echo "#include <iostream>" >> $(TEST_MAIN_CPP)
@echo "#include <algorithm>" >> $(TEST_MAIN_CPP)
@echo "#include <vector>" >> $(TEST_MAIN_CPP)
@echo "using namespace Test;" >> $(TEST_MAIN_CPP)
@echo "using std::cout;\nusing std::endl;\nusing std::for_each;" >> $(TEST_MAIN_CPP)
@echo "$(patsubst %, extern TestResults main_test_%(int argc, char* argv[]);\n, $(MODULES))" >> $(TEST_MAIN_CPP)
@echo "int main(int argc, char* argv[]) {\n setlocale(LC_ALL, \"\");\n TestResults results;\n" >> $(TEST_MAIN_CPP)
@echo "$(patsubst %, results += main_test_%(argc, argv);\n, $(MODULES))" >> $(TEST_MAIN_CPP)
@echo " auto skip_messages = results.skip_messages();" >> $(TEST_MAIN_CPP)
@echo " if (skip_messages.size() > 0) {" >> $(TEST_MAIN_CPP)
@echo " cout << \"Skipped:\" << endl;" >> $(TEST_MAIN_CPP)
@echo " for_each(skip_messages.begin(), skip_messages.end(), [](const string& message){cout << \"🚧Skipped: \" << message << endl;});" >> $(TEST_MAIN_CPP)
@echo " }" >> $(TEST_MAIN_CPP)
@echo " auto failure_messages = results.failure_messages();" >> $(TEST_MAIN_CPP)
@echo " if (failure_messages.size() > 0) {" >> $(TEST_MAIN_CPP)
@echo " cout << \"Failures:\" << endl;" >> $(TEST_MAIN_CPP)
@echo " for_each(failure_messages.begin(), failure_messages.end(), [](const string& message){cout << \"❌FAILED: \" << message << endl;});" >> $(TEST_MAIN_CPP)
@echo " }" >> $(TEST_MAIN_CPP)
@echo " auto error_messages = results.error_messages();" >> $(TEST_MAIN_CPP)
@echo " if (error_messages.size() > 0) {" >> $(TEST_MAIN_CPP)
@echo " cout << \"Errors:\" << endl;" >> $(TEST_MAIN_CPP)
@echo " for_each(error_messages.begin(), error_messages.end(), [](const string& message){cout << \"🔥ERROR: \" << message << endl;});" >> $(TEST_MAIN_CPP)
@echo " }" >> $(TEST_MAIN_CPP)
@echo " cout << \"Total tests: \" << results.total() << endl;" >> $(TEST_MAIN_CPP)
@echo " cout << \"Passed: \" << results.passed() << \" ✅\" << endl;" >> $(TEST_MAIN_CPP)
@echo " cout << \"Failed: \" << results.failed() << \" ❌\" << endl;" >> $(TEST_MAIN_CPP)
@echo " cout << \"Skipped: \" << results.skipped() << \" 🚧\" << endl;" >> $(TEST_MAIN_CPP)
@echo " cout << \"Errors: \" << results.errors() << \" 🔥\" << endl;" >> $(TEST_MAIN_CPP)
@echo "\n return 0;\n}" >> $(TEST_MAIN_CPP)
# We make our own fat libs cause homebrew sucks. # We may have to do something similar for macports, but that's not supported yet.
$(LIB_DIR)/lib%.a: $(BREW_PREFIX)/opt/$$*/lib/lib$$*.a $(BREW86_PREFIX)/opt/$$*/lib/lib$$*.a $(MANDATORY_TARGETS)
lipo -create -output $@ $(word 1, $^) $(word 2, $^)
ifdef $(MODULE)
# Runs a single test harness app e.g. build/Ranks_test made from
# sbf-cpp/Ranks{.h,.cpp,_test.cpp} and the generated test shim
# build/Ranks_test_harness.cpp
run_test: $(TEST_HARNESS_APP) $(MANDATORY_TARGETS)
$(TEST_HARNESS_APP)
$(TEST_HARNESS_CPP): $(MANDATORY_TARGETS)
@echo "// This file is auto generated\n" > $(TEST_HARNESS_CPP)
@echo "#include \"$(RELATIVE_SRC_DIR)/$(MODULE).h\"" >> $(TEST_HARNESS_CPP)
@echo "#include \"../sbf-cpp/test.h\"" >> $(TEST_HARNESS_CPP)
@echo "#include \"$(RELATIVE_SRC_DIR)/test.h\"" >> $(TEST_HARNESS_CPP)
@echo "#include <clocale>" >> $(TEST_HARNESS_CPP)
@echo "#include <string>" >> $(TEST_HARNESS_CPP)
@echo "#include <iostream>" >> $(TEST_HARNESS_CPP)
@echo "#include <algorithm>" >> $(TEST_HARNESS_CPP)
@echo "#include <vector>" >> $(TEST_HARNESS_CPP)
@echo "using namespace Test;" >> $(TEST_HARNESS_CPP)
@echo "using std::cout;\nusing std::endl;\nusing std::for_each;" >> $(TEST_HARNESS_CPP)
@echo "extern TestResults main_test_$(MODULE)(int argc, char* argv[]);" >> $(TEST_HARNESS_CPP)
@echo "int main(int argc, char** argv) {\n setlocale(LC_ALL, \"\");\n TestResults results;\n" >> $(TEST_HARNESS_CPP)
@echo " results = results + main_test_$(MODULE)(argc, argv);\n" >> $(TEST_HARNESS_CPP)
@echo " auto skip_messages = results.skip_messages();" >> $(TEST_HARNESS_CPP)
@echo " if (skip_messages.size() > 0) {" >> $(TEST_HARNESS_CPP)
@echo " cout << \"Skipped:\" << endl;" >> $(TEST_HARNESS_CPP)
@echo " for_each(skip_messages.begin(), skip_messages.end(), [](const string& message){cout << \"🚧Skipped: \" << message << endl;});" >> $(TEST_HARNESS_CPP)
@echo " }" >> $(TEST_HARNESS_CPP)
@echo " auto failure_messages = results.failure_messages();" >> $(TEST_HARNESS_CPP)
@echo " if (failure_messages.size() > 0) {" >> $(TEST_HARNESS_CPP)
@echo " cout << \"Failures:\" << endl;" >> $(TEST_HARNESS_CPP)
@echo " for_each(failure_messages.begin(), failure_messages.end(), [](const string& message){cout << \"❌FAILED: \" << message << endl;});" >> $(TEST_HARNESS_CPP)
@echo " }" >> $(TEST_HARNESS_CPP)
@echo " auto error_messages = results.error_messages();" >> $(TEST_HARNESS_CPP)
@echo " if (error_messages.size() > 0) {" >> $(TEST_HARNESS_CPP)
@echo " cout << \"Errors:\" << endl;" >> $(TEST_HARNESS_CPP)
@echo " for_each(error_messages.begin(), error_messages.end(), [](const string& message){cout << \"🔥ERROR: \" << message << endl;});" >> $(TEST_HARNESS_CPP)
@echo " }" >> $(TEST_HARNESS_CPP)
@echo " cout << \"Total tests: \" << results.total() << endl;" >> $(TEST_HARNESS_CPP)
@echo " cout << \"Passed: \" << results.passed() << \" ✅\" << endl;" >> $(TEST_HARNESS_CPP)
@echo " cout << \"Failed: \" << results.failed() << \" ❌\" << endl;" >> $(TEST_HARNESS_CPP)
@echo " cout << \"Skipped: \" << results.skipped() << \" 🚧\" << endl;" >> $(TEST_HARNESS_CPP)
@echo " cout << \"Errors: \" << results.errors() << \" 🔥\" << endl;" >> $(TEST_HARNESS_CPP)
@echo "\n return 0;\n}" >> $(TEST_HARNESS_CPP)
$(TEST_HARNESS_OBJ): $(TEST_HARNESS_CPP) $(SRC_DIR)/*.h $(TEST_CPP) $(MANDATORY_TARGETS)
$(CC) $(CCFLAGS) -c -o $@ $<
$(TEST_HARNESS_APP): $(TEST_HARNESS_OBJ) $(APP_OBJECTS) $(TEST_OBJ) $(BUILD_DIR)/test.o $(MANDATORY_TARGETS)
$(LD) $(LDFLAGS) -o $@ $< $(APP_OBJECTS) $(TEST_OBJ) $(BUILD_DIR)/test.o
endif
compile_commands.json: $(MANDATORY_TARGETS)
make clean; bear -- make

View File

@@ -0,0 +1,3 @@
/* Localized versions of Info.plist keys */
NSHumanReadableCopyright = "? Tom Hicks, 2023";

172
TODO.md
View File

@@ -1,16 +1,96 @@
# High Priority
# Low Priority
* Add freebie points see page 92 VtM
* After other generation steps allow spending the freebie points on adding dots to the sheet. This should be a nested menu like "What do you want to spend points on? -> Which of those things do you want to add a dot to?
* We probably want to show the character sheet before asking or give the user a way to display it in the main menu of this section.
* Rename items, abilities/abilities, abilityGroups rename the shared arrays and constants too
* Change CharacterType to use fields like attribute_1_1 instead of attr_strength
* Same for abilities like knowledge_computer -> ability_3_2, background_allies -> background_1, and discipline_obeah -> discipline_13.
* This would let us hard-code a given number of those fields so other format plugins can use up to that number at least.
* See if other combinations of clan and other variables cause missed questions.
* Add blood pool to sheet and figure out what should replace it for non-vampires.
* Generation affects blood pool page 173 VtM.
* Make empty strings show as "blank lines" on the character sheet so they can be filled in by hand.
* Rename items, abilities/abilities, abilityGroups rename the shared arrays and constants too
* See if other combinations of clan and other variables cause missed questions.
* Integrate "Clan Disciplines" currently you can choose any.
* See clans section of traits chapter.
* A bunch of these have multiple sets of disciplines depending on the ruleset in place. Current defaults should be VtM based.
* p156-171
* PC
* Brujah: Celerity, Potence, Presence
* Gangrel: Animalism, Fortitude, Protean
* Malkavian: Auspex, Dominate, Obfuscate
* Nosferatu: Animalism, Obfuscate, Potence
* Toreador: Auspex, Celerity, Presence
* Tremere: Auspex, Dominate, Thaumaturgy
* Ventrue: Dominate, Fortitude, Presence
* Caitiff: any
* NPC
* Anarch: Based on actual clan
* Assamite / Banu Haqim: Blood Sorcery, Celerity, Obfuscate
* Warrior Caste: Celerity, Obfuscate, Quietus
* Vizier Caste: Auspex, Celerity, Quietus / Auspex, Presence, Quietus
* Sorcerer Caste: Assamite Sorcery, Obfuscate, Quietus / Assamite Sorcery, Auspex, Quietus / Assamite Sorcery, Auspex, Celerity
* Baali: Obfuscate, Presence, Daimonion
* Cappadocian / Hecata: Auspex, Fortitude, Necromancy / Auspex, Fortitude, Oblivion
* Giovanni / Hecata: Potence, Dominate, Necromancy / (Auspex, Dominate), Fortitude, Oblivion
* Inconnu: probably any
* Lasombra: Dominate, Obtenebration, Potence / Dominate, Oblivion, Potence
* Ravnos: Animalism, Fortitude, Chimestry / Animalism, Obfuscate, Presence
* Settite: Obfuscate, Presence, Serpentis / Obfuscate, Presence, Protean
* Tzimisce: Vicissitude, Animalisim, Auspex / Animalism, Dominate, Protean
* Too many clans?
* This is complicated. We have stats available for clans not in the VtM core rulebook and clans only mentioned by name in the VtM core rulebook. There are also questions of whether a PC can use a clan and whether these terms should be treated as a clan or an adjective. This needs more research.
* Not Really a Clan
* Anarch - npc, complicated they need an original clan
* Caitiff - keep
* Inconnu - npc
* NPC Only
* Assamite/Banu Haqim
* Baali
* Cappadocian/Hecata - npc
* Giovanni/Hecata - npc
* Others
* Brujah - keep
* Gangrel - keep
* Lasombra - npc sabbat
* Malkavian - keep
* Nosferatu - keep
* Ravnos - npc, fix name from Ravanos to Ravnos
* Setite/Ministry - npc (Ministry, Ministry of Set, Followers of Set, Setites), fix name from Settite to Setite
* Toreador - keep
* Tremere - keep, swap order with Toreador
* Tzismice - npc sabbat
* Ventrue - keep
* Limit spending of points
* Abilities max 3 dots before freebie points.
* Reorder CC steps
* Concept
* Other header info
* Clan
* Nature
* Demeanor
* Attributes
* Abilities
* Disciplines
* Backgrounds
* Virtues
* Freebie Points
* Appearance
* Contacts
* Retainers
* Specialties p125
* Equipment
* Quirks
* Add attribute specialties and any other specialties from the book.
# Low Priority
* How does increasing virtues/willpower/humanity affect each other
* Add post-creation questions
* How old are you? - Already have this as age at the beginning but could move to the end.
* What was unique about your childhood?
* What kind of person were you?
* When did you first meet a vampire?
* How did the embrace change you?
* Who was your sire?
* How did your sire treat you?
* Were you presented to the prince?
* How did you meet the others in your brood?
* Where is your haven?
* What are your habitual feeding grounds?
* What motivates you?
* Create a way to choose between VtM, VtDA, and WtA then load the appropriate lookup tables.
* Completely generic plugin support would be great, but not possible.
* Have specific predefined rulsets in the app.
@@ -23,38 +103,33 @@
* Ideally this would let you create an ancient character in a modern system using a clan only available in the older system.
* Would be a neat thing to do it by character age where an old enough VtM character either gets additional access to VtDA stuff or for certain things only has access to VtDA stuff.
* Allowing them to also spend points on any of the VtDA talents/skills/knowledges/backgrounds/disciplines, but only let them choose VtDA clans.
* Extract the menu stuff to it's own bas file.
* https://qb64.com/wiki/TYPE
* https://qb64.com/wiki/$INCLUDE
* lib.BI - Type definitions. Include at the beginning of program.
* Include any dim, const, shared, or data.
* lib.BM - Sub/Function definitions. Include at the end of program.
* pm, PrintMenu, PrintMenuWithValues
* MenuItem/NewMenuItem
* MenuStyle/NewMenuStyle
* GetRandomMenuItem
* GetChoice?
* GetRandomInt?
* Extract each of the top-level menu items into their own files.
* Extract CharacterType and it's associated funcs/subs to their own files. (CharacterType.bi, CharacterType.bm)
* Extract show character sheet and save character to their own files or maybe put them in the CharacterType files.
* Extract the ui utility funcs/subs into their own files. MakeFit*, itos$, ...
* Add support for roads.
* VtM only has one. Humanity.
* If there is only one like this then choose it and don't ask.
* If there are none do something sensible so it doesn't output to the character sheet and we don't ask for a value.
* VtDA has a handfull and I don't think any are humanity.
* I don't know what WtA has, but I think it has something comparable.
* Make empty strings show as "blank lines" on the character sheet so they can be filled in by hand.
* Change CharacterType to use fields like attribute_1_1 instead of attr_strength
* Same for abilities like knowledge_computer -> ability_3_2, background_allies -> background_1, and discipline_obeah -> discipline_13.
* This would let us hard-code a given number of those fields so other format plugins can use up to that number at least.
* Extract the menu stuff to it's own bas file.
* GetChoice?
* GetRandomInt?
* Extract each of the top-level menu items into their own files.
* Extract show character sheet and save character to their own files or maybe put them in the CharacterType files.
* Extract the ui utility funcs/subs into their own files. MakeFit*, itos$, ...
# Super-Low Priority
* Make GetAttributePointsForRank and GetAbilityPointsForRank use a formula maybe.
* GetAbilityValue/SetAbilityValue use a select case and individual GetTalent, GetSkill, ... functions. Try to find a way around this.
* Maybe not possible without abstracting further in CharacterType and maybe also not possible to support the array defined stuff.
* Same for GetAttributeValue/SetAttribute.
* Make pm and MenuStyle and thus PrintMenu and PrintMenuWithValues let you print inside of ascii art boxes like the main menu screen.
* Make pm and MenuStyle let you print menus like the main menu both full screen and wrap_content width and/or height. Hell maybe let you center them vertically/horizontally and still take up a full screen.
* Find a way to support PrintMenuWithValues(Disciplines(), DISCIPLINES_COUNT, ch, ...)
* Make PrintMenu and MenuStyle let you print inside of ascii art boxes like the main menu screen.
* Make PrintMenu and MenuStyle let you print menus like the main menu both full screen and wrap_content width and/or height. Hell maybe let you center them vertically/horizontally and still take up a full screen.
* Find a way to support PrintMenu(Disciplines(), DISCIPLINES_COUNT, ch, ...)
* Ideally this would be PrintMenu(Disciplines(), DISCIPLINES_COUNT, ch, (id as Integer, ch as CharacterType) { return ch.getDisciplineValue(id);}), but we don't have anonymous functions, function pointers, or inheritance.
* Maybe something like PrintMenu(Disciplines(), DISCIPLINES_COUNT, ch, VALUE_DISCIPLINE)
* This would call something like BuildValues(ch, VALUE_DISCIPLINE) internally before doing the actual print.
* Instead of PrintMenuWithValues(Disciplines(), disciplineValues(), DISCIPLINES_COUNT)
* This would use the extra params to inspect ch for the discipline values instead of building a separate array.
* This should also work for backgrounds.
@@ -64,7 +139,6 @@
* PrintMenuWithValues(PROP_ATTRIBUTES, ch, ATTRIBUTE_PHYSICAL)
* All of these would do something like build a MenuItem for each with an id of the attribute id const, a label of the attribute name, and a value of ch(attrGroup, attrIndex)
* This will probably just go away by converting to pm over PrintMenu and PrintMenuWithValues, because we'll be creating an array of MenuItems for each one. We still have to fill the array each time, but we can look into making that suck less afterwards.
* Get attribute/ability/rank names from arrays when to print them (plural and singular) in loops like "Choose your tertiary attribute?" and "Which talent would you like to spend 1 of your 6 points on?"
* Consider supporting freeform gender strings.
* The main reason for not doing this is limiting the string length.
* We don't do it for other strings, but we don't want to add more until we have a good way to handle them.
@@ -121,7 +195,6 @@
* Custom error codes from 100 to 199
* Maybe use 27 - Out of paper.
* Use LogError in all of the SELECT CASE blocks without a CASE ELSE to throw an unknown index error.
* Sort virtues better on the character sheet.
* Start work on ports
* Mainly the C++ port.
* Consider a python port to refresh my python skills.
@@ -147,7 +220,7 @@
* Is age a freeform string or not.
* How are the saved characters getting things like "lllllll" for a demeanor.
* How are the saved characters getting blank lines "________" for age/name/nature/demeanor/clan/...?
* What happens if you choose physical as your primary, secondary, and tertiray attribute? Probably something bad.
# Reference
* QB64 Wiki [https://qb64.com/wiki]
@@ -221,8 +294,13 @@
* Youth ? runaway, child, apprentice, heir, squire, urchin
## Roads (from page 102, see page 113)
* Road of the Beast: Feed the Beast, that it will not break its chains. * Road of Blood: Vampiric blood grants the power for revenge. * Road of Chivalry: Treat your equals with honor and your betters with respect.
* Road of the Devil: We are created evil and must play our part. * Road of Heaven: God made us vampires to exact His wrath. * Road of Humanity: The struggle to maintain one?s humanity. * Road of Paradox: Existence is a lie; change reality for the better.
* Road of the Beast: Feed the Beast, that it will not break its chains.
* Road of Blood: Vampiric blood grants the power for revenge.
* Road of Chivalry: Treat your equals with honor and your betters with respect.
* Road of the Devil: We are created evil and must play our part.
* Road of Heaven: God made us vampires to exact His wrath.
* Road of Humanity: The struggle to maintain one?s humanity.
* Road of Paradox: Existence is a lie; change reality for the better.
* Road of Typhon: Sin and corruption are the keys to under- standing.
## Clans (from page 103)
@@ -244,15 +322,24 @@
## Backgrounds (from page 103)
* Allies: Human confederates, usually family or friends.
* Contacts: The number of information sources the character possesses.
* Generation: How far removed from Caine the character is. * Herd: The vessels to which the character has free and safe access. * Influence: The character?s political power within mortal society. * Mentor: The Cainite patron who advises and supports the character.
* Resources: Wealth, belongings and monthly income. * Retainers: Followers, guards and servants.
* Generation: How far removed from Caine the character is.
* Herd: The vessels to which the character has free and safe access.
* Influence: The character?s political power within mortal society.
* Mentor: The Cainite patron who advises and supports the character.
* Resources: Wealth, belongings and monthly income.
* Retainers: Followers, guards and servants.
* Status: The character?s standing in undead society.
## Disciplines (from page 103)
* Animalism: Supernatural affinity with and control of animals. * Auspex: Extrasensory perception, awareness and premonitions. * Celerity: Supernatural quickness and reflexes.
* Chimerstry: The ability to create illusions and hallucinations. * Dementation: The ability to pass madness on to a victim.
* Dominate: Mind control practiced through the piercing gaze. * Fortitude: Unearthly toughness, even to the point of resisting fire and sunlight.
* Mortis: The supernatural power to control the process of death. * Obfuscate: The ability to remain obscure and unseen, even in crowds.
* Animalism: Supernatural affinity with and control of animals.
* Auspex: Extrasensory perception, awareness and premonitions.
* Celerity: Supernatural quickness and reflexes.
* Chimerstry: The ability to create illusions and hallucinations.
* Dementation: The ability to pass madness on to a victim.
* Dominate: Mind control practiced through the piercing gaze.
* Fortitude: Unearthly toughness, even to the point of resisting fire and sunlight.
* Mortis: The supernatural power to control the process of death.
* Obfuscate: The ability to remain obscure and unseen, even in crowds.
* Obtenebration: The unearthly control over shadows.
* Potence: The Discipline of physical vigor and strength.
* Presence: The ability to attract, sway and control crowds.
@@ -274,7 +361,6 @@
|Roads|1 per dot|
## Experience Costs
|Trait|Cost|
|-|-|
|New Ability|3|
@@ -302,7 +388,6 @@
* Where is your territory?
* What drives you?
* Example Filled In Character Sheet page 106
* Archetype descriptions pages 109-113
* Road descriptions pages 113-117
@@ -339,7 +424,6 @@
* Blood Pool
## Concepts (from page 87)
* Criminal - jailbird, mafioso, cat burglar, drug dealer, bandit
* Dilettante - artist, writer, intellectual, gambler, student
* Drifter - hobo, cowboy, prostitute, hermit, pilgrim

0
WORKSPACE Normal file
View File

View File

@@ -0,0 +1,27 @@
MICSFT_PC_CP437
MICSFT_PC_CP737
MICSFT_PC_CP775
MICSFT_PC_CP850
MICSFT_PC_CP852
MICSFT_PC_CP855
MICSFT_PC_CP857
MICSFT_PC_CP860
MICSFT_PC_CP861
MICSFT_PC_CP862
MICSFT_PC_CP863
MICSFT_PC_CP864
MICSFT_PC_CP865
MICSFT_PC_CP866
MICSFT_PC_CP869
MICSFT_PC_CP874
MICSFT_WINDOWS_CP1250
MICSFT_WINDOWS_CP1251
MICSFT_WINDOWS_CP1252
MICSFT_WINDOWS_CP1253
MICSFT_WINDOWS_CP1254
MICSFT_WINDOWS_CP1255
MICSFT_WINDOWS_CP1256
MICSFT_WINDOWS_CP1257
MICSFT_WINDOWS_CP1258
MICSFT_WINDOWS_CP874
MIK

392
dos/sbf/character.bi Normal file
View File

@@ -0,0 +1,392 @@
' Each set of these index constants "NAME_*" should start at 1 and go up to NAMES_COUNT without leaving any holes.
' This also goes the same for sub indexes like NAME_GROUP_SUBGROUP_* each NAME_GROUP_* set should have GetNumNamesInGroup(NAME_GROUP) items.
Const CLAN_ANARCH = 1
Const CLAN_ASSAMITE = 2
Const CLAN_BAALI = 3
Const CLAN_BRUJAH = 4
Const CLAN_CAITIFF = 5
Const CLAN_CAPPADOCIAN = 6
Const CLAN_GANGREL = 7
Const CLAN_GIOVANNI = 8
Const CLAN_INCONNU = 9
Const CLAN_LASOMBRA = 10
Const CLAN_MALKAVIAN = 11
Const CLAN_NOSFERATU = 12
Const CLAN_RAVANOS = 13
Const CLAN_SETTITE = 14
Const CLAN_TREMERE = 15
Const CLAN_TOREADOR = 16
Const CLAN_TZISMICE = 17
Const CLAN_VENTRUE = 18
Const CLANS_COUNT = 18
Dim Shared Clans(1 To CLANS_COUNT) As String
Const ARCHETYPE_ARCHITECT = 1
Const ARCHETYPE_AUTOCRAT = 2
Const ARCHETYPE_BARBARIAN = 3
Const ARCHETYPE_BON_VIVANT = 4
Const ARCHETYPE_BRAVO = 5
Const ARCHETYPE_CAREGIVER = 6
Const ARCHETYPE_CARETAKER = 7
Const ARCHETYPE_CELEBRANT = 8
Const ARCHETYPE_CHILD = 9
Const ARCHETYPE_CONFORMIST = 10
Const ARCHETYPE_CONNIVER = 11
Const ARCHETYPE_CURMUDGEON = 12
Const ARCHETYPE_DEFENDER = 13
Const ARCHETYPE_DEVIANT = 14
Const ARCHETYPE_DIRECTOR = 15
Const ARCHETYPE_FANATIC = 16
Const ARCHETYPE_GALLANT = 17
Const ARCHETYPE_INNOVATOR = 18
Const ARCHETYPE_JESTER = 19
Const ARCHETYPE_JUDGE = 20
Const ARCHETYPE_LONER = 21
Const ARCHETYPE_MARTYR = 22
Const ARCHETYPE_MONSTER = 23
Const ARCHETYPE_PENITENT = 24
Const ARCHETYPE_REBEL = 25
Const ARCHETYPE_ROGUE = 26
Const ARCHETYPE_SURVIVOR = 27
Const ARCHETYPE_TRADITIONALIST = 28
Const ARCHETYPE_TYRANT = 29
Const ARCHETYPE_VISIONARY = 30
Const ARCHETYPES_COUNT = 30
Dim Shared Archetypes(1 To ARCHETYPES_COUNT) As String
Const DISCIPLINE_POINTS = 3
Const DISCIPLINE_ANIMALISM = 1
Const DISCIPLINE_AUSPEX = 2
Const DISCIPLINE_BARDO = 3
Const DISCIPLINE_CELERITY = 4
Const DISCIPLINE_CHIMESTRY = 5
Const DISCIPLINE_DEMENTATION = 6
Const DISCIPLINE_DOMINATE = 7
Const DISCIPLINE_FORTITUDE = 8
Const DISCIPLINE_MELPOMINEE = 9
Const DISCIPLINE_MORTIS = 10
Const DISCIPLINE_MYTHERCERIA = 11
Const DISCIPLINE_NECROMANCY = 12
Const DISCIPLINE_OBEAH = 13
Const DISCIPLINE_OBFUSCATE = 14
Const DISCIPLINE_OBTENEBRATION = 15
Const DISCIPLINE_POTENCE = 16
Const DISCIPLINE_PRESENCE = 17
Const DISCIPLINE_PROTEAN = 18
Const DISCIPLINE_QUIETUS = 19
Const DISCIPLINE_SERPENTIS = 20
Const DISCIPLINE_SPIRITUS = 21
Const DISCIPLINE_THANANTOSIS = 22
Const DISCIPLINE_THAUMATURGY = 23
Const DISCIPLINE_VICISSITUDE = 24
Const DISCIPLINES_COUNT = 24
Dim Shared Disciplines(1 To DISCIPLINES_COUNT) As String
' Virtues
Const VIRTUE_POINTS = 7
Const VIRTUE_SELF_CONTROL = 1
Const VIRTUE_COURAGE = 2
Const VIRTUE_CONSCIENCE = 3
Const VIRTUES_COUNT = 3
Dim Shared Virtues(1 To VIRTUES_COUNT) As String
' These should probably be renamed like PHYSICAL_ATTRIBUTE_STRENGTH instead.
Const ATTRIBUTE_STRENGTH = 1
Const ATTRIBUTE_DEXTERITY = 2
Const ATTRIBUTE_STAMINA = 3
Const PHYSICAL_ATTRIBUTES_COUNT = 3
Dim Shared PhysicalAttributes(1 To PHYSICAL_ATTRIBUTES_COUNT) As String
Dim Shared PhysicalAttributeAbbreviations(1 To PHYSICAL_ATTRIBUTES_COUNT) As String
Const ATTRIBUTE_CHARISMA = 1
Const ATTRIBUTE_MANIPULATION = 2
Const ATTRIBUTE_APPEARANCE = 3
Const SOCIAL_ATTRIBUTES_COUNT = 3
Dim Shared SocialAttributes(1 To SOCIAL_ATTRIBUTES_COUNT) As String
Dim Shared SocialAttributeAbbreviations(1 To SOCIAL_ATTRIBUTES_COUNT) As String
Const ATTRIBUTE_INTELLIGENCE = 1
Const ATTRIBUTE_PERCEPTION = 2
Const ATTRIBUTE_WITS = 3
Const MENTAL_ATTRIBUTES_COUNT = 3
Dim Shared MentalAttributes(1 To MENTAL_ATTRIBUTES_COUNT) As String
Dim Shared MentalAttributeAbbreviations(1 To MENTAL_ATTRIBUTES_COUNT) As String
Const ATTRIBUTE_GROUP_PHYSICAL = 1
Const ATTRIBUTE_GROUP_SOCIAL = 2
Const ATTRIBUTE_GROUP_MENTAL = 3
Const ATTRIBUTE_GROUPS_COUNT = 3
Dim Shared AttributeGroups(1 To ATTRIBUTE_GROUPS_COUNT) As String
Const ABILITY_GROUP_TALENTS = 1
Const ABILITY_GROUP_SKILLS = 2
Const ABILITY_GROUP_KNOWLEDGES = 3
Const ABILITY_GROUPS_COUNT = 3
Dim Shared AbilityGroups(1 To ABILITY_GROUPS_COUNT) As String
Const TALENT_ACTING = 1
Const TALENT_ALERTNESS = 2
Const TALENT_ATHLETICS = 3
Const TALENT_BRAWL = 4
Const TALENT_DODGE = 5
Const TALENT_EMPATHY = 6
Const TALENT_INTIMIDATION = 7
Const TALENT_LEADERSHIP = 8
Const TALENT_STREETWISE = 9
Const TALENT_SUBTERFUGE = 10
Const TALENTS_COUNT = 10
Dim Shared Talents(1 To TALENTS_COUNT) As String
Const SKILL_ANIMAL_KEN = 1
Const SKILL_DRIVE = 2
Const SKILL_ETIQUETTE = 3
Const SKILL_FIREARMS = 4
Const SKILL_MELEE = 5
Const SKILL_MUSIC = 6
Const SKILL_REPAIR = 7
Const SKILL_SECURITY = 8
Const SKILL_STEALTH = 9
Const SKILL_SURVIVAL = 10
Const SKILLS_COUNT = 10
Dim Shared Skills(1 To SKILLS_COUNT) As String
Const KNOWLEDGE_BUREAUCRACY = 1
Const KNOWLEDGE_COMPUTER = 2
Const KNOWLEDGE_FINANCE = 3
Const KNOWLEDGE_INVESTIGATION = 4
Const KNOWLEDGE_LAW = 5
Const KNOWLEDGE_LINGUISTICS = 6
Const KNOWLEDGE_MEDICINE = 7
Const KNOWLEDGE_OCCULT = 8
Const KNOWLEDGE_POLITICS = 9
Const KNOWLEDGE_SCIENCE = 10
Const KNOWLEDGES_COUNT = 10
Dim Shared Knowledges(1 To KNOWLEDGES_COUNT) As String
Const BACKGROUND_POINTS = 5
Const BACKGROUND_ALLIES = 1
Const BACKGROUND_CONTACTS = 2
Const BACKGROUND_FAME = 3
Const BACKGROUND_GENERATION = 4
Const BACKGROUND_HERD = 5
Const BACKGROUND_INFLUENCE = 6
Const BACKGROUND_MENTOR = 7
Const BACKGROUND_RESOURCES = 8
Const BACKGROUND_RETAINERS = 9
Const BACKGROUND_STATUS = 10
Const BACKGROUNDS_COUNT = 10
Dim Shared Backgrounds(1 To BACKGROUNDS_COUNT) As String
Const GENDER_MALE = 1
Const GENDER_FEMALE = 2
Const GENDER_TRANS_MALE = 3
Const GENDER_TRANS_FEMALE = 4
Const GENDER_NON_BINARY = 5
Const GENDERS_COUNT = 5
Dim Shared Genders(1 To GENDERS_COUNT) As String
Const DERANGEMENTS_COUNT = 10
Const DERANGEMENT_ID_AMNESIA = 1
Const DERANGEMENT_ID_DELUSIONS_OF_GRANDEUR = 2
Const DERANGEMENT_ID_FANTASY = 3
Const DERANGEMENT_ID_MANIC_DEPRESSION = 4
Const DERANGEMENT_ID_MULTIPLE_PERSONALITIES = 5
Const DERANGEMENT_ID_OBSESSION = 6
Const DERANGEMENT_ID_OVERCOMPENSATION = 7
Const DERANGEMENT_ID_PARANOIA = 8
Const DERANGEMENT_ID_PERFECTION = 9
Const DERANGEMENT_ID_REGRESSION = 10
Dim Shared Derangements(1 To DERANGEMENTS_COUNT) As DerangementType
Const DERANGEMENT_COLOR_AMNESIA = COLOR_DARK_RED
Const DERANGEMENT_COLOR_DELUSIONS_OF_GRANDEUR = COLOR_DARK_MAGENTA
Const DERANGEMENT_COLOR_FANTASY = COLOR_DARK_ORANGE
Const DERANGEMENT_COLOR_MANIC_DEPRESSION = COLOR_DARK_WHITE
Const DERANGEMENT_COLOR_MULTIPLE_PERSONALITIES = COLOR_DARK_BLUE
Const DERANGEMENT_COLOR_OBSESSION = COLOR_BRIGHT_GREEN
Const DERANGEMENT_COLOR_OVERCOMPENSATION = COLOR_BRIGHT_CYAN
Const DERANGEMENT_COLOR_PARANOIA = COLOR_BRIGHT_RED
Const DERANGEMENT_COLOR_PERFECTION = COLOR_BRIGHT_MAGENTA
Const DERANGEMENT_COLOR_REGRESSION = COLOR_BRIGHT_YELLOW
Dim Shared DerangementColors(1 To DERANGEMENTS_COUNT) As Integer
Dim Shared DerangementLabels(1 To DERANGEMENTS_COUNT) As String
Const DERANGEMENT_LABEL_AMNESIA = "Amnesia"
Const DERANGEMENT_LABEL_DELUSIONS_OF_GRANDEUR = "Delusions of Grandeur"
Const DERANGEMENT_LABEL_FANTASY = "Fantasy"
Const DERANGEMENT_LABEL_MANIC_DEPRESSION = "Manic-Depression"
Const DERANGEMENT_LABEL_MULTIPLE_PERSONALITIES = "Multiple Personalities"
Const DERANGEMENT_LABEL_OBSESSION = "Obsession"
Const DERANGEMENT_LABEL_OVERCOMPENSATION = "Overcompensation"
Const DERANGEMENT_LABEL_PARANOIA = "Paranoia"
Const DERANGEMENT_LABEL_PERFECTION = "Perfection"
Const DERANGEMENT_LABEL_REGRESSION = "Regression"
Dim Shared DerangementDescriptions(1 To DERANGEMENTS_COUNT) As String
Const DERANGEMENT_DESCRIPTION_AMNESIA = "You forget a segment of your past. Additionally in some cases a character can forget abilities and be unable to use them for the duration."
Const DERANGEMENT_DESCRIPTION_DELUSIONS_OF_GRA = "You imagine you are better than you are."
Const DERANGEMENT_DESCRIPTION_FANTASY = "You enter a self-created world where you are the forgotten hero."
Const DERANGEMENT_DESCRIPTION_MANIC_DEPRESSION = "You sink into deep and fitful depressions, showing no interest in anything which used to captivate your interests. You will go along with others rather than use the energy to resist. Occasional fits of great energy grab hold of you, and you will work for hours or even days on your projects. During this time you will resist even the need for sleep as you burn up blood and Willpower on your schemes."
Const DERANGEMENT_DESCRIPTION_MULTIPLE_PERSONA = "You possess a number of new personalities. You have amore than one Mature, and will switch between them. Thus you regain Willpower points in defferent ways at defferent times"
Const DERANGEMENT_DESCRIPTION_OBSESSION = "You become obsessed with some interest or fetish."
Const DERANGEMENT_DESCRIPTION_OVERCOMPENSATION = "You make up for your moral weaknesses by playing up your strengths to an extreme. You don't think you can frenzy and won't stop it."
Const DERANGEMENT_DESCRIPTION_PARANOIA = "You are convinced that you are being hunted. You hold even your closest Friends under suspicion."
Const DERANGEMENT_DESCRIPTION_PERFECTION = "All your energy is directed toward preventing anything from going wong. When it does you must make a self-control roll or frenzy."
Const DERANGEMENT_DESCRIPTION_REGRESSION = "You become childlike retreating to an earlier time when less was expected of you Willpower is regained inthe way a Child's is."
Const FREEBIES_COUNT = 7
Dim Shared FreebieCosts(1 To FREEBIES_COUNT) As Integer
Dim Shared FreebieLabels(1 To FREEBIES_COUNT) As String
Dim Shared FreebieNames(1 to FREEBIES_COUNT) As String
Dim Shared Freebies(1 To FREEBIES_COUNT) As FreebieType
Const FREEBIE_DISCIPLINE_ID = 1
Const FREEBIE_DISCIPLINE_COST = 7
Const FREEBIE_DISCIPLINE_NAME = "Discipline"
Const FREEBIE_DISCIPLINE_LABEL = "Add a discipline dot 7 points"
Const FREEBIE_ATTRIBUTE_ID = 2
Const FREEBIE_ATTRIBUTE_COST = 5
Const FREEBIE_ATTRIBUTE_NAME = "Attribute"
Const FREEBIE_ATTRIBUTE_LABEL = "Add an attribute dot 5 points"
Const FREEBIE_ABILITY_ID = 3
Const FREEBIE_ABILITY_COST = 2
Const FREEBIE_ABILITY_NAME = "Ability"
Const FREEBIE_ABILITY_LABEL = "Add an ability dot 2 points"
Const FREEBIE_VIRTUE_ID = 4
Const FREEBIE_VIRTUE_COST = 2
Const FREEBIE_VIRTUE_NAME = "Virtue"
Const FREEBIE_VIRTUE_LABEL = "Add a virtue dot 2 points"
' TODO: Make this configurable for VtDA
Const FREEBIE_HUMANITY_ID = 5
Const FREEBIE_HUMANITY_COST = 1
Const FREEBIE_HUMANITY_NAME = "Humanity"
Const FREEBIE_HUMANITY_LABEL = "Add a humanity dot 1 point"
Const FREEBIE_BACKGROUND_ID = 6
Const FREEBIE_BACKGROUND_COST = 1
Const FREEBIE_BACKGROUND_NAME = "Background"
Const FREEBIE_BACKGROUND_LABEL = "Add a background dot 1 point"
Const FREEBIE_SHOW_CHARACTER_SHEET_ID = 7
Const FREEBIE_SHOW_CHARACTER_SHEET_COST = 0
Const FREEBIE_SHOW_CHARACTER_SHEET_NAME = "Show character sheet"
Const FREEBIE_SHOW_CHARACTER_SHEET_LABEL = FREEBIE_SHOW_CHARACTER_SHEET_NAME
Type CharacterType
name As String
player As String
chronicle As String
haven As String
concept As String
age As String
gender As Integer
clan As Integer
nature As Integer
demeanor As Integer
conscience As Integer
selfControl As Integer
courage As Integer
generation As Integer
roadName As String
roadValue As Integer
willpower As Integer
bloodPool As Integer
derangementId As Integer
freebiePoints As Integer
' Disciplines
discipline_animalism As Integer
discipline_auspex As Integer
discipline_bardo As Integer
discipline_celerity As Integer
discipline_chimestry As Integer
discipline_dementation As Integer
discipline_dominate As Integer
discipline_fortitude As Integer
discipline_melpominee As Integer
discipline_mortis As Integer
discipline_mytherceria As Integer
discipline_necromancy As Integer
discipline_obeah As Integer
discipline_obfuscate As Integer
discipline_obtenebration As Integer
discipline_potence As Integer
discipline_presence As Integer
discipline_protean As Integer
discipline_quietus As Integer
discipline_serpentis As Integer
discipline_spiritus As Integer
discipline_thanantosis As Integer
discipline_thaumaturgy As Integer
discipline_vicissitude As Integer
' Attributes
attr_strength As Integer
attr_dexterity As Integer
attr_stamina As Integer
attr_appearance As Integer
attr_charisma As Integer
attr_manipulation As Integer
attr_intelligence As Integer
attr_perception As Integer
attr_wits As Integer
' Talents
talent_acting As Integer
talent_alertness As Integer
talent_athletics As Integer
talent_brawl As Integer
talent_dodge As Integer
talent_empathy As Integer
talent_intimidation As Integer
talent_leadership As Integer
talent_streetwise As Integer
talent_subterfuge As Integer
' Skills
skill_animalKen As Integer
skill_drive As Integer
skill_etiquette As Integer
skill_firearms As Integer
skill_melee As Integer
skill_music As Integer
skill_repair As Integer
skill_security As Integer
skill_stealth As Integer
skill_survival As Integer
' Knowledges
knowledge_bureaucracy As Integer
knowledge_computer As Integer
knowledge_finance As Integer
knowledge_investigation As Integer
knowledge_law As Integer
knowledge_linguistics As Integer
knowledge_medicine As Integer
knowledge_occult As Integer
knowledge_politics As Integer
knowledge_science As Integer
' Backgrounds
background_allies As Integer
background_contacts As Integer
background_fame As Integer
background_generation As Integer
background_herd As Integer
background_influence As Integer
background_mentor As Integer
background_resources As Integer
background_retainers As Integer
background_status As Integer
End Type
Type DerangementType
id As Integer
label As String
description As String
textColor As Integer
End Type
Type FreebieType
id As Integer
cost As Integer
name As String * 32
label As String * 32
End Type

998
dos/sbf/character.bm Normal file
View File

@@ -0,0 +1,998 @@
$Debug
Sub Initialize_Character_Lib
' For all of these lookup tables they should look something like this to know the mapping is correct
' Names(NAME_ITEM1) = "Item1"
' The index constants should start at 1 and go up to NAMES_COUNT without leaving any holes.
' Clans
Clans(CLAN_ANARCH) = "Anarch"
Clans(CLAN_ASSAMITE) = "Assamite"
Clans(CLAN_BAALI) = "Baali"
Clans(CLAN_BRUJAH) = "Brujah"
Clans(CLAN_CAITIFF) = "Caitiff"
Clans(CLAN_CAPPADOCIAN) = "Cappadocian"
Clans(CLAN_GANGREL) = "Gangrel"
Clans(CLAN_GIOVANNI) = "Giovanni"
Clans(CLAN_INCONNU) = "Inconnu"
Clans(CLAN_LASOMBRA) = "Lasombra"
Clans(CLAN_MALKAVIAN) = "Malkavian"
Clans(CLAN_NOSFERATU) = "Nosferatu"
Clans(CLAN_RAVANOS) = "Ravanos"
Clans(CLAN_SETTITE) = "Settite"
Clans(CLAN_TREMERE) = "Tremere"
Clans(CLAN_TOREADOR) = "Toreador"
Clans(CLAN_TZISMICE) = "Tzismice"
Clans(CLAN_VENTRUE) = "Ventrue"
' Archetypes
Archetypes(ARCHETYPE_ARCHITECT) = "Architect"
Archetypes(ARCHETYPE_AUTOCRAT) = "Autocrat"
Archetypes(ARCHETYPE_BARBARIAN) = "Barbarian"
Archetypes(ARCHETYPE_BON_VIVANT) = "Bon Vivant"
Archetypes(ARCHETYPE_BRAVO) = "Bravo"
Archetypes(ARCHETYPE_CAREGIVER) = "Caregiver"
Archetypes(ARCHETYPE_CARETAKER) = "Caretaker"
Archetypes(ARCHETYPE_CELEBRANT) = "Celebrant"
Archetypes(ARCHETYPE_CHILD) = "Child"
Archetypes(ARCHETYPE_CONFORMIST) = "Conformist"
Archetypes(ARCHETYPE_CONNIVER) = "Conniver"
Archetypes(ARCHETYPE_CURMUDGEON) = "Curmudgeon"
Archetypes(ARCHETYPE_DEFENDER) = "Defender"
Archetypes(ARCHETYPE_DEVIANT) = "Deviant"
Archetypes(ARCHETYPE_DIRECTOR) = "Director"
Archetypes(ARCHETYPE_FANATIC) = "Fanatic"
Archetypes(ARCHETYPE_GALLANT) = "Gallant"
Archetypes(ARCHETYPE_INNOVATOR) = "Innovator"
Archetypes(ARCHETYPE_JESTER) = "Jester"
Archetypes(ARCHETYPE_JUDGE) = "Judge"
Archetypes(ARCHETYPE_LONER) = "Loner"
Archetypes(ARCHETYPE_MARTYR) = "Martyr"
Archetypes(ARCHETYPE_MONSTER) = "Monster"
Archetypes(ARCHETYPE_PENITENT) = "Penitent"
Archetypes(ARCHETYPE_REBEL) = "Rebel"
Archetypes(ARCHETYPE_ROGUE) = "Rogue"
Archetypes(ARCHETYPE_SURVIVOR) = "Survivor"
Archetypes(ARCHETYPE_TRADITIONALIST) = "Traditionalist"
Archetypes(ARCHETYPE_TYRANT) = "Tyrant"
Archetypes(ARCHETYPE_VISIONARY) = "Visionary"
' Disciplines
Disciplines(DISCIPLINE_ANIMALISM) = "Animalism"
Disciplines(DISCIPLINE_AUSPEX) = "Auspex"
Disciplines(DISCIPLINE_BARDO) = "Bardo"
Disciplines(DISCIPLINE_CELERITY) = "Celerity"
Disciplines(DISCIPLINE_CHIMESTRY) = "Chimestry"
Disciplines(DISCIPLINE_DEMENTATION) = "Dementation"
Disciplines(DISCIPLINE_DOMINATE) = "Dominate"
Disciplines(DISCIPLINE_FORTITUDE) = "Fortitude"
Disciplines(DISCIPLINE_MELPOMINEE) = "Melpominee"
Disciplines(DISCIPLINE_MORTIS) = "Mortis"
Disciplines(DISCIPLINE_MYTHERCERIA) = "Mytherceria"
Disciplines(DISCIPLINE_NECROMANCY) = "Necromancy"
Disciplines(DISCIPLINE_OBEAH) = "Obeah"
Disciplines(DISCIPLINE_OBFUSCATE) = "Obfuscate"
Disciplines(DISCIPLINE_OBTENEBRATION) = "Obtenebration"
Disciplines(DISCIPLINE_POTENCE) = "Potence"
Disciplines(DISCIPLINE_PRESENCE) = "Presence"
Disciplines(DISCIPLINE_PROTEAN) = "Protean"
Disciplines(DISCIPLINE_QUIETUS) = "Quietus"
Disciplines(DISCIPLINE_SERPENTIS) = "Serpentis"
Disciplines(DISCIPLINE_SPIRITUS) = "Spiritus"
Disciplines(DISCIPLINE_THANANTOSIS) = "Thanantosis"
Disciplines(DISCIPLINE_THAUMATURGY) = "Thaumaturgy"
Disciplines(DISCIPLINE_VICISSITUDE) = "Vicissitude"
' Virtues
Virtues(VIRTUE_SELF_CONTROL) = "Self-Control"
Virtues(VIRTUE_COURAGE) = "Courage"
Virtues(VIRTUE_CONSCIENCE) = "Conscience"
' Physical Attributes
PhysicalAttributes(ATTRIBUTE_STRENGTH) = "Strength"
PhysicalAttributeAbbreviations(ATTRIBUTE_STRENGTH) = "Str."
PhysicalAttributes(ATTRIBUTE_DEXTERITY) = "Dexterity"
PhysicalAttributeAbbreviations(ATTRIBUTE_DEXTERITY) = "Dex."
PhysicalAttributes(ATTRIBUTE_STAMINA) = "Stamina"
PhysicalAttributeAbbreviations(ATTRIBUTE_STAMINA) = "Sta."
' Social Attributes
SocialAttributes(ATTRIBUTE_CHARISMA) = "Charisma"
SocialAttributeAbbreviations(ATTRIBUTE_CHARISMA) = "Cha."
SocialAttributes(ATTRIBUTE_MANIPULATION) = "Manipulation"
SocialAttributeAbbreviations(ATTRIBUTE_MANIPULATION) = "Man."
SocialAttributes(ATTRIBUTE_APPEARANCE) = "Appearance"
SocialAttributeAbbreviations(ATTRIBUTE_APPEARANCE) = "App."
' Mental Attributes
MentalAttributes(ATTRIBUTE_INTELLIGENCE) = "Intelligence"
MentalAttributeAbbreviations(ATTRIBUTE_INTELLIGENCE) = "Int."
MentalAttributes(ATTRIBUTE_PERCEPTION) = "Perception"
MentalAttributeAbbreviations(ATTRIBUTE_PERCEPTION) = "Per."
MentalAttributes(ATTRIBUTE_WITS) = "Wits"
MentalAttributeAbbreviations(ATTRIBUTE_WITS) = "Wits"
' Attribute Groups
AttributeGroups(ATTRIBUTE_GROUP_PHYSICAL) = "Physical"
AttributeGroups(ATTRIBUTE_GROUP_SOCIAL) = "Social"
AttributeGroups(ATTRIBUTE_GROUP_MENTAL) = "Mental"
' Abilities
AbilityGroups(ABILITY_GROUP_TALENTS) = "Talents"
AbilityGroups(ABILITY_GROUP_SKILLS) = "Skills"
AbilityGroups(ABILITY_GROUP_KNOWLEDGES) = "Knowledges"
' Talents
Talents(TALENT_ACTING) = "Acting"
Talents(TALENT_ALERTNESS) = "Alertness"
Talents(TALENT_ATHLETICS) = "Athletics"
Talents(TALENT_BRAWL) = "Brawl"
Talents(TALENT_DODGE) = "Dodge"
Talents(TALENT_EMPATHY) = "Empathy"
Talents(TALENT_INTIMIDATION) = "Intimidation"
Talents(TALENT_LEADERSHIP) = "Leadership"
Talents(TALENT_STREETWISE) = "Streetwise"
Talents(TALENT_SUBTERFUGE) = "Subterfuge"
' Skills
Skills(SKILL_ANIMAL_KEN) = "Animal Ken"
Skills(SKILL_DRIVE) = "Drive"
Skills(SKILL_ETIQUETTE) = "Etiquette"
Skills(SKILL_FIREARMS) = "Firearms"
Skills(SKILL_MELEE) = "Melee"
Skills(SKILL_MUSIC) = "Music"
Skills(SKILL_REPAIR) = "Repair"
Skills(SKILL_SECURITY) = "Security"
Skills(SKILL_STEALTH) = "Stealth"
Skills(SKILL_SURVIVAL) = "Survival"
' Knowwledges
Knowledges(KNOWLEDGE_BUREAUCRACY) = "Bureaucracy"
Knowledges(KNOWLEDGE_COMPUTER) = "Computer"
Knowledges(KNOWLEDGE_FINANCE) = "Finance"
Knowledges(KNOWLEDGE_INVESTIGATION) = "Investigation"
Knowledges(KNOWLEDGE_LAW) = "Law"
Knowledges(KNOWLEDGE_LINGUISTICS) = "Linguistics"
Knowledges(KNOWLEDGE_MEDICINE) = "Medicine"
Knowledges(KNOWLEDGE_OCCULT) = "Occult"
Knowledges(KNOWLEDGE_POLITICS) = "Politics"
Knowledges(KNOWLEDGE_SCIENCE) = "Science"
' Backgrounds
Backgrounds(BACKGROUND_ALLIES) = "Allies"
Backgrounds(BACKGROUND_CONTACTS) = "Contacts"
Backgrounds(BACKGROUND_FAME) = "Fame"
Backgrounds(BACKGROUND_GENERATION) = "Generation"
Backgrounds(BACKGROUND_HERD) = "Herd"
Backgrounds(BACKGROUND_INFLUENCE) = "Influence"
Backgrounds(BACKGROUND_MENTOR) = "Mentor"
Backgrounds(BACKGROUND_RESOURCES) = "Resources"
Backgrounds(BACKGROUND_RETAINERS) = "Retainers"
Backgrounds(BACKGROUND_STATUS) = "Status"
' Genders
Genders(GENDER_MALE) = "Male"
Genders(GENDER_FEMALE) = "Female"
Genders(GENDER_TRANS_MALE) = "Trans-Male"
Genders(GENDER_TRANS_FEMALE) = "Trans-Female"
Genders(GENDER_NON_BINARY) = "Non-Binary"
' Derangements
DerangementColors(DERANGEMENT_ID_AMNESIA) = DERANGEMENT_COLOR_AMNESIA
DerangementColors(DERANGEMENT_ID_DELUSIONS_OF_GRANDEUR) = DERANGEMENT_COLOR_DELUSIONS_OF_GRANDEUR
DerangementColors(DERANGEMENT_ID_FANTASY) = DERANGEMENT_COLOR_FANTASY
DerangementColors(DERANGEMENT_ID_MANIC_DEPRESSION) = DERANGEMENT_COLOR_MANIC_DEPRESSION
DerangementColors(DERANGEMENT_ID_MULTIPLE_PERSONALITIES) = DERANGEMENT_COLOR_MULTIPLE_PERSONALITIES
DerangementColors(DERANGEMENT_ID_OBSESSION) = DERANGEMENT_COLOR_OBSESSION
DerangementColors(DERANGEMENT_ID_OVERCOMPENSATION) = DERANGEMENT_COLOR_OVERCOMPENSATION
DerangementColors(DERANGEMENT_ID_PARANOIA) = DERANGEMENT_COLOR_PARANOIA
DerangementColors(DERANGEMENT_ID_PERFECTION) = DERANGEMENT_COLOR_PERFECTION
DerangementColors(DERANGEMENT_ID_REGRESSION) = DERANGEMENT_COLOR_REGRESSION
DerangementLabels(DERANGEMENT_ID_AMNESIA) = DERANGEMENT_LABEL_AMNESIA
DerangementLabels(DERANGEMENT_ID_DELUSIONS_OF_GRANDEUR) = DERANGEMENT_LABEL_DELUSIONS_OF_GRANDEUR
DerangementLabels(DERANGEMENT_ID_FANTASY) = DERANGEMENT_LABEL_FANTASY
DerangementLabels(DERANGEMENT_ID_MANIC_DEPRESSION) = DERANGEMENT_LABEL_MANIC_DEPRESSION
DerangementLabels(DERANGEMENT_ID_MULTIPLE_PERSONALITIES) = DERANGEMENT_LABEL_MULTIPLE_PERSONALITIES
DerangementLabels(DERANGEMENT_ID_OBSESSION) = DERANGEMENT_LABEL_OBSESSION
DerangementLabels(DERANGEMENT_ID_OVERCOMPENSATION) = DERANGEMENT_LABEL_OVERCOMPENSATION
DerangementLabels(DERANGEMENT_ID_PARANOIA) = DERANGEMENT_LABEL_PARANOIA
DerangementLabels(DERANGEMENT_ID_PERFECTION) = DERANGEMENT_LABEL_PERFECTION
DerangementLabels(DERANGEMENT_ID_REGRESSION) = DERANGEMENT_LABEL_REGRESSION
DerangementDescriptions(DERANGEMENT_ID_AMNESIA) = DERANGEMENT_DESCRIPTION_AMNESIA
DerangementDescriptions(DERANGEMENT_ID_DELUSIONS_OF_GRANDEUR) = DERANGEMENT_DESCRIPTION_DELUSIONS_OF_GRA
DerangementDescriptions(DERANGEMENT_ID_FANTASY) = DERANGEMENT_DESCRIPTION_FANTASY
DerangementDescriptions(DERANGEMENT_ID_MANIC_DEPRESSION) = DERANGEMENT_DESCRIPTION_MANIC_DEPRESSION
DerangementDescriptions(DERANGEMENT_ID_MULTIPLE_PERSONALITIES) = DERANGEMENT_DESCRIPTION_MULTIPLE_PERSONA
DerangementDescriptions(DERANGEMENT_ID_OBSESSION) = DERANGEMENT_DESCRIPTION_OBSESSION
DerangementDescriptions(DERANGEMENT_ID_OVERCOMPENSATION) = DERANGEMENT_DESCRIPTION_OVERCOMPENSATION
DerangementDescriptions(DERANGEMENT_ID_PARANOIA) = DERANGEMENT_DESCRIPTION_PARANOIA
DerangementDescriptions(DERANGEMENT_ID_PERFECTION) = DERANGEMENT_DESCRIPTION_PERFECTION
DerangementDescriptions(DERANGEMENT_ID_REGRESSION) = DERANGEMENT_DESCRIPTION_REGRESSION
For i = LBound(Derangements) To UBound(Derangements)
Call NewDerangement(Derangements(i), i, DerangementLabels(i), DerangementColors(i), DerangementDescriptions(i))
Next
' Freebies
FREEBIECOSTS(FREEBIE_DISCIPLINE_ID) = FREEBIE_DISCIPLINE_COST
FREEBIENAMES(FREEBIE_DISCIPLINE_ID) = FREEBIE_DISCIPLINE_NAME
FREEBIELABELS(FREEBIE_DISCIPLINE_ID) = FREEBIE_DISCIPLINE_LABEL
Call NewFreebie(Freebies(FREEBIE_DISCIPLINE_ID), FREEBIE_DISCIPLINE_ID, FREEBIE_DISCIPLINE_COST, FREEBIE_DISCIPLINE_NAME, FREEBIE_DISCIPLINE_LABEL)
FREEBIECOSTS(FREEBIE_ATTRIBUTE_ID) = FREEBIE_ATTRIBUTE_COST
FREEBIENAMES(FREEBIE_ATTRIBUTE_ID) = FREEBIE_ATTRIBUTE_NAME
FREEBIELABELS(FREEBIE_ATTRIBUTE_ID) = FREEBIE_ATTRIBUTE_LABEL
Call NewFreebie(Freebies(FREEBIE_ATTRIBUTE_ID), FREEBIE_ATTRIBUTE_ID, FREEBIE_ATTRIBUTE_COST, FREEBIE_ATTRIBUTE_NAME, FREEBIE_ATTRIBUTE_LABEL)
FREEBIECOSTS(FREEBIE_ABILITY_ID) = FREEBIE_ABILITY_COST
FREEBIENAMES(FREEBIE_ABILITY_ID) = FREEBIE_ABILITY_NAME
FREEBIELABELS(FREEBIE_ABILITY_ID) = FREEBIE_ABILITY_LABEL
Call NewFreebie(Freebies(FREEBIE_ABILITY_ID), FREEBIE_ABILITY_ID, FREEBIE_ABILITY_COST, FREEBIE_ABILITY_NAME, FREEBIE_ABILITY_LABEL)
FREEBIECOSTS(FREEBIE_VIRTUE_ID) = FREEBIE_VIRTUE_COST
FREEBIENAMES(FREEBIE_VIRTUE_ID) = FREEBIE_VIRTUE_NAME
FREEBIELABELS(FREEBIE_VIRTUE_ID) = FREEBIE_VIRTUE_LABEL
Call NewFreebie(Freebies(FREEBIE_VIRTUE_ID), FREEBIE_VIRTUE_ID, FREEBIE_VIRTUE_COST, FREEBIE_VIRTUE_NAME, FREEBIE_VIRTUE_LABEL)
FREEBIECOSTS(FREEBIE_HUMANITY_ID) = FREEBIE_HUMANITY_COST
FREEBIENAMES(FREEBIE_HUMANITY_ID) = FREEBIE_HUMANITY_NAME
FREEBIELABELS(FREEBIE_HUMANITY_ID) = FREEBIE_HUMANITY_LABEL
Call NewFreebie(Freebies(FREEBIE_HUMANITY_ID), FREEBIE_HUMANITY_ID, FREEBIE_HUMANITY_COST, FREEBIE_HUMANITY_NAME, FREEBIE_HUMANITY_LABEL)
FREEBIECOSTS(FREEBIE_BACKGROUND_ID) = FREEBIE_BACKGROUND_COST
FREEBIENAMES(FREEBIE_BACKGROUND_ID) = FREEBIE_BACKGROUND_NAME
FREEBIELABELS(FREEBIE_BACKGROUND_ID) = FREEBIE_BACKGROUND_LABEL
Call NewFreebie(Freebies(FREEBIE_BACKGROUND_ID), FREEBIE_BACKGROUND_ID, FREEBIE_BACKGROUND_COST, FREEBIE_BACKGROUND_NAME, FREEBIE_BACKGROUND_LABEL)
FREEBIECOSTS(FREEBIE_SHOW_CHARACTER_SHEET_ID) = FREEBIE_SHOW_CHARACTER_SHEET_COST
FREEBIENAMES(FREEBIE_SHOW_CHARACTER_SHEET_ID) = FREEBIE_SHOW_CHARACTER_SHEET_NAME
FREEBIELABELS(FREEBIE_SHOW_CHARACTER_SHEET_ID) = FREEBIE_SHOW_CHARACTER_SHEET_LABEL
Call NewFreebie(Freebies(FREEBIE_SHOW_CHARACTER_SHEET_ID), FREEBIE_SHOW_CHARACTER_SHEET_ID, FREEBIE_SHOW_CHARACTER_SHEET_COST, FREEBIE_SHOW_CHARACTER_SHEET_NAME, FREEBIE_SHOW_CHARACTER_SHEET_LABEL)
End Sub
' Character
Sub NewCharacter (ch As CharacterType)
' Scalars
ch.name = ""
ch.player = ""
ch.chronicle = ""
ch.haven = ""
ch.concept = ""
ch.age = ""
ch.gender = 0
ch.clan = 0
ch.nature = 0
ch.demeanor = 0
ch.generation = 13
ch.roadName = ""
ch.roadValue = 0
ch.willpower = 0
ch.bloodPool = 0
ch.derangementId = -1
ch.freebiePoints = 15
' Virtues
ch.selfControl = 1
ch.courage = 1
ch.conscience = 1
' Arrays/Objects
' Abilities (Talents/Skills/Knowledges)
For groupIndex = 1 To ABILITY_GROUPS_COUNT
For index = 1 To GetNumItemsForAbilityGroup(groupIndex)
Call SetAbilityValue(ch, groupIndex, index, 0)
Next
Next
' Attributes
For groupIndex = 1 To ATTRIBUTE_GROUPS_COUNT
For index = 1 To GetNumAttributesInGroup(groupIndex)
Call SetAttributeValue(ch, groupIndex, index, 1)
Next
Next
' Backgrounds
For index = 1 To BACKGROUNDS_COUNT
Call SetBackground(ch, index, 0)
Next
' Disciplines
For index = 1 To DISCIPLINES_COUNT
Call SetDiscipline(ch, index, 0)
Next
End Sub
' Disciplines
Function GetDisciplinePoints ()
GetDisciplinePoints = DISCIPLINE_POINTS
End Function
Sub SetDiscipline (ch As CharacterType, index As Integer, value As Integer)
Select Case index
Case DISCIPLINE_ANIMALISM
ch.discipline_animalism = value
Case DISCIPLINE_AUSPEX
ch.discipline_auspex = value
Case DISCIPLINE_BARDO
ch.discipline_bardo = value
Case DISCIPLINE_CELERITY
ch.discipline_celerity = value
Case DISCIPLINE_CHIMESTRY
ch.discipline_chimestry = value
Case DISCIPLINE_DEMENTATION
ch.discipline_dementation = value
Case DISCIPLINE_DOMINATE
ch.discipline_dominate = value
Case DISCIPLINE_FORTITUDE
ch.discipline_fortitude = value
Case DISCIPLINE_MELPOMINEE
ch.discipline_melpominee = value
Case DISCIPLINE_MORTIS
ch.discipline_mortis = value
Case DISCIPLINE_MYTHERCERIA
ch.discipline_mytherceria = value
Case DISCIPLINE_NECROMANCY
ch.discipline_necromancy = value
Case DISCIPLINE_OBEAH
ch.discipline_obeah = value
Case DISCIPLINE_OBFUSCATE
ch.discipline_obfuscate = value
Case DISCIPLINE_OBTENEBRATION
ch.discipline_obtenebration = value
Case DISCIPLINE_POTENCE
ch.discipline_potence = value
Case DISCIPLINE_PRESENCE
ch.discipline_presence = value
Case DISCIPLINE_PROTEAN
ch.discipline_protean = value
Case DISCIPLINE_QUIETUS
ch.discipline_quietus = value
Case DISCIPLINE_SERPENTIS
ch.discipline_serpentis = value
Case DISCIPLINE_SPIRITUS
ch.discipline_spiritus = value
Case DISCIPLINE_THANANTOSIS
ch.discipline_thanantosis = value
Case DISCIPLINE_THAUMATURGY
ch.discipline_thaumaturgy = value
Case DISCIPLINE_VICISSITUDE
ch.discipline_vicissitude = value
End Select
End Sub
Function GetDiscipline (ch As CharacterType, index As Integer)
Select Case index
Case DISCIPLINE_ANIMALISM
GetDiscipline = ch.discipline_animalism
Case DISCIPLINE_AUSPEX
GetDiscipline = ch.discipline_auspex
Case discipline_barde
GetDiscipline = ch.discipline_bardo
Case DISCIPLINE_CELERITY
GetDiscipline = ch.discipline_celerity
Case DISCIPLINE_CHIMESTRY
GetDiscipline = ch.discipline_chimestry
Case DISCIPLINE_DEMENTATION
GetDiscipline = ch.discipline_dementation
Case DISCIPLINE_DOMINATE
GetDiscipline = ch.discipline_dominate
Case DISCIPLINE_FORTITUDE
GetDiscipline = ch.discipline_fortitude
Case DISCIPLINE_MELPOMINEE
GetDiscipline = ch.discipline_melpominee
Case DISCIPLINE_MORTIS
GetDiscipline = ch.discipline_mortis
Case DISCIPLINE_MYTHERCERIA
GetDiscipline = ch.discipline_mytherceria
Case DISCIPLINE_NECROMANCY
GetDiscipline = ch.discipline_necromancy
Case DISCIPLINE_OBEAH
GetDiscipline = ch.discipline_obeah
Case DISCIPLINE_OBFUSCATE
GetDiscipline = ch.discipline_obfuscate
Case DISCIPLINE_OBTENEBRATION
GetDiscipline = ch.discipline_obtenebration
Case DISCIPLINE_POTENCE
GetDiscipline = ch.discipline_potence
Case DISCIPLINE_PRESENCE
GetDiscipline = ch.discipline_presence
Case DISCIPLINE_PROTEAN
GetDiscipline = ch.discipline_protean
Case DISCIPLINE_QUIETUS
GetDiscipline = ch.discipline_quietus
Case DISCIPLINE_SERPENTIS
GetDiscipline = ch.discipline_serpentis
Case DISCIPLINE_SPIRITUS
GetDiscipline = ch.discipline_spiritus
Case DISCIPLINE_THANANTOSIS
GetDiscipline = ch.discipline_thanantosis
Case DISCIPLINE_THAUMATURGY
GetDiscipline = ch.discipline_thaumaturgy
Case DISCIPLINE_VICISSITUDE
GetDiscipline = ch.discipline_vicissitude
End Select
End Function
Sub FillDisciplines (ch As CharacterType, disciplines() As Integer)
ReDim disciplines(1 To DISCIPLINES_COUNT) As Integer
For index = 1 To DISCIPLINES_COUNT
disciplines(index) = GetDiscipline(ch, index)
Next
End Sub
' Virtues
Function GetVirtuePoints ()
GetVirtuePoints = VIRTUE_POINTS
End Function
Sub SetVirtue (ch As CharacterType, index As Integer, value As Integer)
Select Case index
Case VIRTUE_SELF_CONTROL
ch.selfControl = value
Case VIRTUE_COURAGE
ch.courage = value
Case VIRTUE_CONSCIENCE
ch.conscience = value
End Select
End Sub
Function GetVirtue (ch As CharacterType, index As Integer)
value = 0
Select Case index
Case VIRTUE_SELF_CONTROL
value = ch.selfControl
Case VIRTUE_COURAGE
value = ch.courage
Case VIRTUE_CONSCIENCE
value = ch.conscience
End Select
GetVirtue = value
End Function
Sub FillVirtues (ch As CharacterType, values() As Integer)
ReDim values(1 To VIRTUES_COUNT) As Integer
For index = 1 To VIRTUES_COUNT
values(index) = GetVirtue(ch, index)
Next
End Sub
Function GetFreebiePoints(ch As CharacterType)
GetFreebiePoints = ch.freebiePoints
End Function
Sub SetFreebiePoints(ch As CharacterType, points As Integer)
ch.freebiepoints = points
End Sub
' Attributes
Sub FillAttributeValues (ch As CharacterType, values() As Integer, groupIndex As Integer)
count = GetNumAttributesInGroup(groupIndex)
ReDim values(1 To count) As Integer
For i = 1 To count
values(i) = GetAttributeValue(ch, groupIndex, i)
Next
End Sub
Function GetNumAttributesInGroup (index As Integer)
Select Case index
Case ATTRIBUTE_GROUP_PHYSICAL
count = PHYSICAL_ATTRIBUTES_COUNT
Case ATTRIBUTE_GROUP_SOCIAL
count = SOCIAL_ATTRIBUTES_COUNT
Case ATTRIBUTE_GROUP_MENTAL
count = MENTAL_ATTRIBUTES_COUNT
Case Else
count = 0
End Select
GetNumAttributesInGroup = count
End Function
Function GetAttributeName$ (groupIndex As Integer, attributeIndex As Integer)
attributeName$ = ""
Select Case groupIndex
Case ATTRIBUTE_GROUP_PHYSICAL
attributeName$ = PhysicalAttributes(attributeIndex)
Case ATTRIBUTE_GROUP_SOCIAL
attributeName$ = SocialAttributes(attributeIndex)
Case ATTRIBUTE_GROUP_MENTAL
attributeName$ = MentalAttributes(attributeIndex)
End Select
GetAttributeName = attributeName$
End Function
Sub FillAttributesInGroup (group As Integer, attributes() As String)
count = GetNumAttributesInGroup(group)
If count > 0 Then
ReDim attributes(1 To count) As String
Else
ReDim attributes(0) As String
End If
Select Case group
Case ATTRIBUTE_GROUP_PHYSICAL
For i = 1 To count
attributes(i) = PhysicalAttributes(i)
Next
Case ATTRIBUTE_GROUP_SOCIAL
For i = 1 To count
attributes(i) = SocialAttributes(i)
Next
Case ATTRIBUTE_GROUP_MENTAL
For i = 1 To count
attributes(i) = MentalAttributes(i)
Next
End Select
End Sub
Sub FillAttributeAbbreviationsInGroup (group As Integer, abbreviations() As String, count As Integer)
count = GetNumAttributesInGroup(group)
If count > 0 Then
ReDim abbreviations(1 To count) As String
Else
ReDim abbreviations(0) As String
End If
Select Case group
Case ATTRIBUTE_GROUP_PHYSICAL
For i = 1 To count
abbreviations(i) = PhysicalAttributeAbbreviations(i)
Next
Case ATTRIBUTE_GROUP_SOCIAL
For i = 1 To count
abbreviations(i) = SocialAttributeAbbreviations(i)
Next
Case ATTRIBUTE_GROUP_MENTAL
For i = 1 To count
abbreviations(i) = MentalAttributeAbbreviations(i)
Next
End Select
End Sub
Sub SetAttributeValue (ch As CharacterType, AttributeGroupIndex As Integer, AttributeIndex As Integer, Value As Integer)
Select Case AttributeGroupIndex
Case ATTRIBUTE_GROUP_PHYSICAL
Select Case AttributeIndex
Case ATTRIBUTE_STRENGTH
ch.attr_strength = Value
Case ATTRIBUTE_DEXTERITY
ch.attr_dexterity = Value
Case ATTRIBUTE_STAMINA
ch.attr_stamina = Value
End Select
Case ATTRIBUTE_GROUP_SOCIAL
Select Case AttributeIndex
Case ATTRIBUTE_CHARISMA
ch.attr_charisma = Value
Case ATTRIBUTE_MANIPULATION
ch.attr_manipulation = Value
Case ATTRIBUTE_APPEARANCE
ch.attr_appearance = Value
End Select
Case ATTRIBUTE_GROUP_MENTAL
Select Case AttributeIndex
Case ATTRIBUTE_INTELLIGENCE
ch.attr_intelligence = Value
Case ATTRIBUTE_PERCEPTION
ch.attr_perception = Value
Case ATTRIBUTE_WITS
ch.attr_wits = Value
End Select
End Select
End Sub
Function GetAttributeValue (ch As CharacterType, attributeGroupIndex As Integer, attributeIndex As Integer)
GetAttributeValue = 0
Select Case attributeGroupIndex
Case ATTRIBUTE_GROUP_PHYSICAL
Select Case attributeIndex
Case ATTRIBUTE_STRENGTH
GetAttributeValue = ch.attr_strength
Case ATTRIBUTE_DEXTERITY
GetAttributeValue = ch.attr_dexterity
Case ATTRIBUTE_STAMINA
GetAttributeValue = ch.attr_stamina
End Select
Case ATTRIBUTE_GROUP_SOCIAL
Select Case attributeIndex
Case ATTRIBUTE_CHARISMA
GetAttributeValue = ch.attr_charisma
Case ATTRIBUTE_MANIPULATION
GetAttributeValue = ch.attr_manipulation
Case ATTRIBUTE_APPEARANCE
GetAttributeValue = ch.attr_appearance
End Select
Case ATTRIBUTE_GROUP_MENTAL
Select Case attributeIndex
Case ATTRIBUTE_INTELLIGENCE
GetAttributeValue = ch.attr_intelligence
Case ATTRIBUTE_PERCEPTION
GetAttributeValue = ch.attr_perception
Case ATTRIBUTE_WITS
GetAttributeValue = ch.attr_wits
End Select
End Select
End Function
Function GetAttributePointsForRank (rank As Integer)
GetAttributePointsForRank = 0
Select Case rank
Case 1
GetAttributePointsForRank = 7
Case 2
GetAttributePointsForRank = 5
Case 3
GetAttributePointsForRank = 3
End Select
End Function
' Abilities
Sub SetAbilityValue (ch As CharacterType, groupIndex As Integer, index As Integer, value As Integer)
Select Case groupIndex
Case ABILITY_GROUP_TALENTS
Call SetTalent(ch, index, value)
Case ABILITY_GROUP_SKILLS
Call SetSkill(ch, index, value)
Case ABILITY_GROUP_KNOWLEDGES
Call SetKnowledge(ch, index, value)
End Select
End Sub
Function GetAbilityValue (ch As CharacterType, abilityIndex As Integer, itemIndex As Integer)
GetAbilityValue = 0
Select Case abilityIndex
Case ABILITY_GROUP_TALENTS
GetAbilityValue = GetTalent(ch, itemIndex)
Case ABILITY_GROUP_SKILLS
GetAbilityValue = GetSkill(ch, itemIndex)
Case ABILITY_GROUP_KNOWLEDGES
GetAbilityValue = GetKnowledge(ch, itemIndex)
End Select
End Function
Function GetAbilityPointsForRank (rank As Integer)
GetAbilityPointsForRank = 0
Select Case rank
Case 1
GetAbilityPointsForRank = 13
Case 2
GetAbilityPointsForRank = 9
Case 3
GetAbilityPointsForRank = 5
End Select
End Function
Sub FillAbilityValues (ch As CharacterType, values() As Integer, groupIndex As Integer)
count = GetNumItemsForAbilityGroup(groupIndex)
ReDim values(1 To count) As Integer
For i = 1 To count
values(i) = GetAbilityValue(ch, groupIndex, i)
Next
End Sub
Function GetNumItemsForAbilityGroup (index As Integer)
Select Case index
Case ABILITY_GROUP_TALENTS
count = TALENTS_COUNT
Case ABILITY_GROUP_SKILLS
count = SKILLS_COUNT
Case ABILITY_GROUP_KNOWLEDGES
count = KNOWLEDGES_COUNT
Case Else
count = 0
End Select
GetNumItemsForAbilityGroup = count
End Function
Function GetAbilityName$ (groupIndex As Integer, abilityIndex As Integer)
abilityName$ = ""
Select Case groupIndex
Case ABILITY_GROUP_TALENTS
abilityName$ = Talents(abilityIndex)
Case ABILITY_GROUP_SKILLS
abilityName$ = Skills(abilityIndex)
Case ABILITY_GROUP_KNOWLEDGES
abilityName$ = Knowledges(abilityIndex)
End Select
GetAbilityName = abilityName$
End Function
Sub FillAbilitiesForAbilityGroup (ability As Integer, items() As String)
count = GetNumItemsForAbilityGroup(ability)
If count > 0 Then
ReDim items(1 To count) As String
Else
ReDim items(0) As String
End If
Select Case ability
Case ABILITY_GROUP_TALENTS
For i = 1 To count
items(i) = Talents(i)
Next
Case ABILITY_GROUP_SKILLS
For i = 1 To count
items(i) = Skills(i)
Next
Case ABILITY_GROUP_KNOWLEDGES
For i = 1 To count
items(i) = Knowledges(i)
Next
End Select
End Sub
' Ability - Talents
Sub SetTalent (ch As CharacterType, index As Integer, value As Integer)
Select Case index
Case TALENT_ACTING
ch.talent_acting = value
Case TALENT_ALERTNESS
ch.talent_alertness = value
Case TALENT_ATHLETICS
ch.talent_athletics = value
Case TALENT_BRAWL
ch.talent_brawl = value
Case TALENT_DODGE
ch.talent_dodge = value
Case TALENT_EMPATHY
ch.talent_empathy = value
Case TALENT_INTIMIDATION
ch.talent_intimidation = value
Case TALENT_LEADERSHIP
ch.talent_leadership = value
Case TALENT_STREETWISE
ch.talent_streetwise = value
Case TALENT_SUBTERFUGE
ch.talent_subterfuge = value
End Select
End Sub
Function GetTalent (ch As CharacterType, index As Integer)
Select Case index
Case TALENT_ACTING
GetTalent = ch.talent_acting
Case TALENT_ALERTNESS
GetTalent = ch.talent_alertness
Case TALENT_ATHLETICS
GetTalent = ch.talent_athletics
Case TALENT_BRAWL
GetTalent = ch.talent_brawl
Case TALENT_DODGE
GetTalent = ch.talent_dodge
Case TALENT_EMPATHY
GetTalent = ch.talent_empathy
Case TALENT_INTIMIDATION
GetTalent = ch.talent_intimidation
Case TALENT_LEADERSHIP
GetTalent = ch.talent_leadership
Case TALENT_STREETWISE
GetTalent = ch.talent_streetwise
Case TALENT_SUBTERFUGE
GetTalent = ch.talent_subterfuge
End Select
End Function
' Ability - Skills
Sub SetSkill (ch As CharacterType, index As Integer, value As Integer)
Select Case index
Case SKILL_ANIMAL_KEN
ch.skill_animalKen = value
Case SKILL_DRIVE
ch.skill_drive = value
Case SKILL_ETIQUETTE
ch.skill_etiquette = value
Case SKILL_FIREARMS
ch.skill_firearms = value
Case SKILL_MELEE
ch.skill_melee = value
Case SKILL_MUSIC
ch.skill_music = value
Case SKILL_REPAIR
ch.skill_repair = value
Case SKILL_SECURITY
ch.skill_security = value
Case SKILL_STEALTH
ch.skill_stealth = value
Case SKILL_SURVIVAL
ch.skill_survival = value
End Select
End Sub
Function GetSkill (ch As CharacterType, index As Integer)
Select Case index
Case SKILL_ANIMAL_KEN
GetSkill = ch.skill_animalKen
Case SKILL_DRIVE
GetSkill = ch.skill_drive
Case SKILL_ETIQUETTE
GetSkill = ch.skill_etiquette
Case SKILL_FIREARMS
GetSkill = ch.skill_firearms
Case SKILL_MELEE
GetSkill = ch.skill_melee
Case SKILL_MUSIC
GetSkill = ch.skill_music
Case SKILL_REPAIR
GetSkill = ch.skill_repair
Case SKILL_SECURITY
GetSkill = ch.skill_security
Case SKILL_STEALTH
GetSkill = ch.skill_stealth
Case SKILL_SURVIVAL
GetSkill = ch.skill_survival
End Select
End Function
' Ability - Knowledges
Sub SetKnowledge (ch As CharacterType, index As Integer, value As Integer)
Select Case index
Case KNOWLEDGE_BUREAUCRACY
ch.knowledge_bureaucracy = value
Case KNOWLEDGE_COMPUTER
ch.knowledge_computer = value
Case KNOWLEDGE_FINANCE
ch.knowledge_finance = value
Case KNOWLEDGE_INVESTIGATION
ch.knowledge_investigation = value
Case KNOWLEDGE_LAW
ch.knowledge_law = value
Case KNOWLEDGE_LINGUISTICS
ch.knowledge_linguistics = value
Case KNOWLEDGE_MEDICINE
ch.knowledge_medicine = value
Case KNOWLEDGE_OCCULT
ch.knowledge_occult = value
Case KNOWLEDGE_POLITICS
ch.knowledge_politics = value
Case KNOWLEDGE_SCIENCE
ch.knowledge_science = value
End Select
End Sub
Function GetKnowledge (ch As CharacterType, index As Integer)
Select Case index
Case KNOWLEDGE_BUREAUCRACY
GetKnowledge = ch.knowledge_bureaucracy
Case KNOWLEDGE_COMPUTER
GetKnowledge = ch.knowledge_computer
Case KNOWLEDGE_FINANCE
GetKnowledge = ch.knowledge_finance
Case KNOWLEDGE_INVESTIGATION
GetKnowledge = ch.knowledge_investigation
Case KNOWLEDGE_LAW
GetKnowledge = ch.knowledge_law
Case KNOWLEDGE_LINGUISTICS
GetKnowledge = ch.knowledge_linguistics
Case KNOWLEDGE_MEDICINE
GetKnowledge = ch.knowledge_medicine
Case KNOWLEDGE_OCCULT
GetKnowledge = ch.knowledge_occult
Case KNOWLEDGE_POLITICS
GetKnowledge = ch.knowledge_politics
Case KNOWLEDGE_SCIENCE
GetKnowledge = ch.knowledge_science
End Select
End Function
' Backgrounds
Sub SetBackground (ch As CharacterType, index As Integer, value As Integer)
Select Case index
Case BACKGROUND_ALLIES
ch.background_allies = value
Case BACKGROUND_CONTACTS
ch.background_contacts = value
Case BACKGROUND_FAME
ch.background_fame = value
Case BACKGROUND_GENERATION
ch.background_generation = value
Case BACKGROUND_HERD
ch.background_herd = value
Case BACKGROUND_INFLUENCE
ch.background_influence = value
Case BACKGROUND_MENTOR
ch.background_mentor = value
Case BACKGROUND_RESOURCES
ch.background_resources = value
Case BACKGROUND_RETAINERS
ch.background_retainers = value
Case BACKGROUND_STATUS
ch.background_status = value
End Select
End Sub
Function GetBackground (ch As CharacterType, index As Integer)
Select Case index
Case BACKGROUND_ALLIES
GetBackground = ch.background_allies
Case BACKGROUND_CONTACTS
GetBackground = ch.background_contacts
Case BACKGROUND_FAME
GetBackground = ch.background_fame
Case BACKGROUND_GENERATION
GetBackground = ch.background_generation
Case BACKGROUND_HERD
GetBackground = ch.background_herd
Case BACKGROUND_INFLUENCE
GetBackground = ch.background_influence
Case BACKGROUND_MENTOR
GetBackground = ch.background_mentor
Case BACKGROUND_RESOURCES
GetBackground = ch.background_resources
Case BACKGROUND_RETAINERS
GetBackground = ch.background_retainers
Case BACKGROUND_STATUS
GetBackground = ch.background_status
End Select
End Function
Sub FillBackgrounds (ch As CharacterType, backgrounds() As Integer)
For index = 1 To BACKGROUNDS_COUNT
backgrounds(index) = GetBackground(ch, index)
Next
End Sub
Function GetBackgroundPoints ()
GetBackgroundPoints = BACKGROUND_POINTS
End Function
' Genders
' Derangements
Sub NewDerangement (derangement As DerangementType, id As Integer, label As String, textColor As Integer, description As String)
derangement.id = id
derangement.label = label
derangement.textColor = textColor
derangement.description = description
End Sub
Function GetAllDerangementsLine$ (ch As CharacterType)
Dim allDerangements(1) As DerangementType
Call FillDerangements(ch, allDerangements())
allDerangementsString$ = ""
For i = 0 To UBound(allDerangements) - 1
allDerangementsString$ = allDerangementsString$ + allDerangements(i).label + ", "
Next
GetAllDerangementsLine$ = allDerangementsString$
End Function
Sub FillDerangements (ch As CharacterType, myDerangements() As DerangementType)
count = 1
If ch.derangementId < 0 Or ch.derangementId >= count Then
Exit Sub
End If
ReDim myDerangements(count) As DerangementType
myDerangements(0) = Derangements(ch.derangementId)
End Sub
' Roads
function GetRoadName$(ch as charactertype)
getroadname = ch.roadName
end function
sub SetRoadName(ch as charactertype, roadName as string)
ch.roadname = roadname
end sub
function GetRoadValue(ch as charactertype)
getroadvalue = ch.roadValue
end function
sub SetRoadValue(ch as charactertype, roadValue as integer)
ch.roadvalue = roadvalue
end sub
' Freebies
sub NewFreebie (freebie As FreebieType, id As integer, cost As integer, freebieName As string, freebieLabel As string)
freebie.id = id
freebie.cost = cost
freebie.name = freebieName
freebie.label = freebieLabel
end sub
' Scalars
function GetGeneration(ch as charactertype)
getgeneration = ch.generation
end function

25
dos/sbf/colors.bi Normal file
View File

@@ -0,0 +1,25 @@
Const COLOR_DARK_BLACK = 0
Const COLOR_DARK_BLUE = 1
Const COLOR_DARK_GREEN = 2
Const COLOR_DARK_CYAN = 3
Const COLOR_DARK_RED = 4
Const COLOR_DARK_MAGENTA = 5
Const COLOR_DARK_ORANGE = 6
Const COLOR_DARK_YELLOW = 6
Const COLOR_DARK_WHITE = 7
Const COLOR_BRIGHT_BLACK = 8
Const COLOR_BRIGHT_BLUE = 9
Const COLOR_BRIGHT_GREEN = 10
Const COLOR_BRIGHT_CYAN = 11
Const COLOR_BRIGHT_RED = 12
Const COLOR_BRIGHT_MAGENTA = 13
Const COLOR_BRIGHT_ORANGE = 14
Const COLOR_BRIGHT_YELLOW = 14
Const COLOR_BRIGHT_WHITE = 15
Const COLOR_FOREGROUND_DEFAULT = COLOR_DARK_WHITE
Const COLOR_BACKGROUND_DEFAULT = COLOR_DARK_BLACK
Dim Shared ScreenColor As Integer
ScreenColor = COLOR_FOREGROUND_DEFAULT

20
dos/sbf/colors.bm Normal file
View File

@@ -0,0 +1,20 @@
Sub SetColor (c As Integer)
ScreenColor = c
Color c
End Sub
Function GetColor ()
GetColor = ScreenColor
End Function
Sub PrintWithMaybeColor (message As String, textColor As Integer, useColors As Integer)
Dim oldColor As Integer
If useColors = TRUE Then
oldColor = GetColor
SetColor (textColor)
End If
Print message
If useColors = TRUE Then
SetColor (oldColor)
End If
End Sub

27
dos/sbf/menus.bi Normal file
View File

@@ -0,0 +1,27 @@
Type MenuStyle
idWidth As Integer
labelWidth As Integer
valueWidth As Integer
screenWidth As Integer
randomItemName As String
randomItemId As Integer
randomItemColor as integer
cancelItemName As String
cancelItemId As Integer
cancelItemColor as integer
idLabelSeparator As String
labelValueSeparator As String
menuItemSpacer As String
showRandom As Integer
showCancel as integer
useColors As Integer
End Type
Type MenuItem
label As String
id As Integer
value As Integer
color As Integer
isVisible As Integer
includeInRandom as integer
End Type

179
dos/sbf/menus.bm Normal file
View File

@@ -0,0 +1,179 @@
Function GetRandomMenuItemId (items() As MenuItem, count As Integer)
numVisibleItems = 0
Dim visibleItems(count) As Integer
For i = 1 To count
If items(i).isVisible and items(i).includeinrandom Then
visibleItems(numVisibleItems) = i
numVisibleItems = numVisibleItems + 1
End If
Next
i = GetRandomInt(0, numVisibleItems - 1)
GetRandomMenuItemId = visibleItems(i)
End Function
Sub BuildMenu (items() As MenuItem, labels() As String, count As Integer)
For i = 1 To count
Dim mi As MenuItem
Call NewMenuItem(mi, labels(i), i)
items(i) = mi
Next
End Sub
Sub BuildMenuWithValues (items() As MenuItem, labels() As String, values() As Integer, count As Integer)
For i = 1 To count
Dim mi As MenuItem
Call NewMenuItemWithValue(mi, labels(i), i, values(i))
items(i) = mi
Next
End Sub
Sub BuildMenuWithColors (items() As MenuItem, labels() As String, colors() As Integer)
' Check array bounds
If LBound(items) <> 1 Or LBound(colors) <> 1 Or UBound(items) <> UBound(colors) Then End
count = UBound(items)
For i = 1 To count
Dim mi As MenuItem
Call NewMenuItemWithColor(mi, labels(i), colors(i), i)
items(i) = mi
Next
End Sub
Sub AdjustMenuStyle (style As MenuStyle, items() As MenuItem, count As Integer, ignoreValue As Integer)
maxIdWidth = 0
maxItemWidth = 0
maxValueWidth = 0
For i = 1 To count
If items(i).isVisible Then
maxIdWidth = MaxI(maxIdWidth, Len(itos$(items(i).id)))
maxItemWidth = MaxI(maxItemWidth, Len(items(i).label + style.labelValueSeparator))
maxValueWidth = MaxI(maxValueWidth, Len(itos$(items(i).value)))
End If
Next
If style.showRandom Then
maxIdWidth = MaxI(maxIdWidth, Len(itos$(style.randomItemId)))
maxItemWidth = MaxI(maxItemWidth, Len(style.randomItemName))
End If
If style.showCancel Then
maxIdWidth = MaxI(maxIdWidth, Len(itos$(style.cancelItemId)))
maxItemWidth = MaxI(maxItemWidth, Len(style.cancelItemName))
End If
style.idWidth = maxIdWidth
style.labelWidth = maxItemWidth
If Not ignoreValue Then style.valueWidth = maxValueWidth Else style.valueWidth = 0
End Sub
Sub PrintMenu (items() As MenuItem, count As Integer, style As MenuStyle)
Dim randomItem As MenuItem
Call NewMenuItemWithColor(randomItem, style.randomItemName, style.randomItemColor, style.randomItemId)
Dim cancelItem As MenuItem
Call NewMenuItemWithColor(cancelItem, style.cancelItemName, style.cancelItemColor, style.cancelItemId)
actualCount = count
If style.showCancel = TRUE Then actualCount = actualCount + 1
If style.showRandom = TRUE Then actualCount = actualCount + 1
If actualCount <= 10 Then
For i = 1 To count
If items(i).isVisible Then
title$ = GetTitle$(items(i), style)
Call PrintWithMaybeColor(title$, items(i).color, style.useColors)
End If
Next
If style.showCancel Then
cancelLabel$ = GetTitleWithoutValue$(cancelItem, style)
Call PrintWithMaybeColor(cancelLabel$, cancelItem.color, style.useColors)
End If
If style.showRandom Then
randomLabel$ = GetTitleWithoutValue$(randomItem, style)
Call PrintWithMaybeColor(randomLabel$, randomItem.color, style.useColors)
End If
Else
Dim emptyItem As MenuItem
Call NewMenuItem(emptyItem, "", 0)
itemWidth = Len(GetTitle$(emptyItem, style))
itemsPerRow = style.screenWidth \ (itemWidth + Len(style.menuItemSpacer))
columnWidth = style.screenWidth \ itemsPerRow
column = 0
For i = 1 To count
If items(i).isVisible Then
itemText$ = GetTitle$(items(i), style)
If column <> (itemsPerRow - 1) Then
If i <> count Or style.showRandom Then
textLength = Len(itemText$)
itemText$ = MakeFitL$(RTrim$(itemText$) + style.menuItemSpacer, textLength + Len(style.menuItemSpacer), " ")
End If
End If
label$ = MakeFitC$(itemText$, columnWidth, " ")
Call PrintWithMaybeColor(label$, items(i).color, style.useColors)
End If
column = (column + 1) Mod itemsPerRow
If column = 0 Then Print ""
Next
If style.showCancel Then
cancelLabel$ = MakeFitC$(GetTitleWithoutValue$(cancelItem, style), columnWidth, " ")
Call PrintWithMaybeColor(cancelLabel$, cancelItem.color, style.useColors)
End If
If style.showRandom Then
randomLabel$ = MakeFitC$(GetTitleWithoutValue$(randomItem, style), columnWidth, " ")
Call PrintWithMaybeColor(randomLabel$, randomItem.color, style.useColors)
End If
End If
End Sub
Function GetTitle$ (mi As MenuItem, ms As MenuStyle)
id$ = itos$(mi.id)
label$ = mi.label
If ms.valueWidth > 0 Then label$ = label$ + ms.labelValueSeparator
value$ = itos$(mi.value)
GetTitle$ = MakeFitR$(id$, ms.idWidth, " ") + ms.idLabelSeparator + MakeFitL$(label$, ms.labelWidth, " ") + MakeFitR$(value$, ms.valueWidth, " ")
End Function
Function GetTitleWithoutValue$ (mi As MenuItem, ms As MenuStyle)
GetTitleWithoutValue$ = MakeFitR$(itos$(mi.id), ms.idWidth, " ") + ms.idLabelSeparator + MakeFitL$(mi.label, ms.labelWidth + ms.valueWidth + Len(ms.labelValueSeparator), " ")
End Function
Sub NewMenuStyle (ms As MenuStyle)
ms.idWidth = 0
ms.labelWidth = 0
ms.valueWidth = 0
ms.screenWidth = 80
ms.randomItemName = "Random"
ms.randomItemId = 0
ms.randomItemColor = COLOR_FOREGROUND_DEFAULT
ms.cancelItemName = "Cancel"
ms.cancelItemId = -1
ms.cancelItemColor = COLOR_FOREGROUND_DEFAULT
ms.idLabelSeparator = " = "
ms.labelValueSeparator = ": "
ms.menuItemSpacer = ", "
ms.showRandom = TRUE
ms.showCancel = false
ms.useColors = FALSE
End Sub
Sub NewMenuItem (mi As MenuItem, label As String, id As Integer)
mi.id = id
mi.label = label
mi.value = 0
mi.color = COLOR_DEFAULT
mi.isVisible = TRUE
mi.includeInRandom = true
End Sub
Sub NewMenuItemWithValue (mi As MenuItem, label As String, id As Integer, value As Integer)
mi.id = id
mi.label = label
mi.value = value
mi.color = COLOR_DEFAULT
mi.isVisible = TRUE
mi.includeInRandom = true
End Sub
Sub NewMenuItemWithColor (mi As MenuItem, label As String, textColor As Integer, id As Integer)
mi.id = id
mi.label = label
mi.value = 0
mi.color = textColor
mi.isVisible = TRUE
End Sub

2673
dos/sbf/sbf-all.bas Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

13
log.txt Normal file
View File

@@ -0,0 +1,13 @@
Makefile:111: warning: overriding commands for target `build/_test'
Makefile:86: warning: ignoring old commands for target `build/_test'
clang++ -std=c++17 -finput-charset=UTF-8 -Isbf-cpp -I/opt/homebrew/opt/ncurses/include -arch arm64 -arch x86_64 -fdiagnostics-show-template-tree -fno-elide-type -c -o build/Abilities_test.o sbf-cpp/Abilities_test.cpp
clang++ -std=c++17 -finput-charset=UTF-8 -Isbf-cpp -I/opt/homebrew/opt/ncurses/include -arch arm64 -arch x86_64 -fdiagnostics-show-template-tree -fno-elide-type -c -o build/test_main.o build/test_main.cpp
clang++ -Lbuild/lib -lncurses -arch arm64 -arch x86_64 -O2 -o build/test build/test_main.o build/Abilities.o build/Archetypes.o build/Attributes.o build/Backgrounds.o build/Character.o build/Clans.o build/Colors.o build/Derangements.o build/Disciplines.o build/Genders.o build/Ranks.o build/Utils.o build/Virtues.o build/Abilities_test.o build/Archetypes_test.o build/Attributes_test.o build/Backgrounds_test.o build/Character_test.o build/Clans_test.o build/Colors_test.o build/Derangements_test.o build/Disciplines_test.o build/Genders_test.o build/Ranks_test.o build/Utils_test.o build/Virtues_test.o build/test.o
cp sbf-cpp/Info.plist build/Contents/Info.plist
plutil -replace CFBundleExecutable -string "SBF" build/Contents/Info.plist
plutil -replace CFBundleIdentifier -string "com.majinnaibu.SBF" build/Contents/Info.plist
plutil -replace CFBundleName -string "SBF" build/Contents/Info.plist
plutil -replace CFBundleSignature -string SBF_ build/Contents/Info.plist
cp build/Contents/Info.plist build/SBF.app/Contents/
cp build/sbf build/SBF.app/Contents/MacOS/SBF
cp -r Resources/* build/SBF.app/Contents/Resources/

377
sbf-cpp/Abilities.cpp Normal file
View File

@@ -0,0 +1,377 @@
#include "Abilities.h"
#include <iostream>
#include <string>
namespace SBF {
namespace {
using std::ostream;
using std::string;
using std::vector;
} // End namespace
const int kAbilityGroupUnknownId = 0;
const std::string kAbilityGroupUnknownLabel = "";
const std::string kAbilityGroupTalentsSingular = "Talent";
const std::string kAbilityGroupTalentsPlural = "Talents";
const std::string kAbilityGroupSkillsSingular = "Skill";
const std::string kAbilityGroupSkillsPlural = "Skills";
const std::string kAbilityGroupKnowledgesSingular = "Knowledge";
const std::string kAbilityGroupKnowledgesPlural = "Knowledges";
const AbilityGroup kAbilityGroupUnknown =
AbilityGroup(kAbilityGroupUnknownId, kAbilityGroupUnknownLabel, kAbilityGroupUnknownLabel);
const AbilityGroup kAbilityGroupTalents =
AbilityGroup(kAbilityGroupTalentsId, kAbilityGroupTalentsSingular, kAbilityGroupTalentsPlural);
const AbilityGroup kAbilityGroupSkills =
AbilityGroup(kAbilityGroupSkillsId, kAbilityGroupSkillsSingular, kAbilityGroupSkillsPlural);
const AbilityGroup kAbilityGroupKnowledges =
AbilityGroup(kAbilityGroupKnowledgesId, kAbilityGroupKnowledgesSingular, kAbilityGroupKnowledgesPlural);
const vector<AbilityGroup> kAbilityGroups = {kAbilityGroupTalents, kAbilityGroupSkills, kAbilityGroupKnowledges};
const int kAbilityUnknownId = 0;
const std::string kAbilityUnknownLabel = "";
const Ability kAbilityUnknown = Ability(kAbilityUnknownId, kAbilityGroupUnknownId, kAbilityUnknownLabel);
const std::string kTalentActingLabel = "Acting";
const std::string kTalentAlertnessLabel = "Alertness";
const std::string kTalentAthleticsLabel = "Athletics";
const std::string kTalentBrawlLabel = "Brawl";
const std::string kTalentDodgeLabel = "Dodge";
const std::string kTalentEmpathyLabel = "Empathy";
const std::string kTalentIntimidationLabel = "Intimidation";
const std::string kTalentLeadershipLabel = "Leadership";
const std::string kTalentStreetwiseLabel = "Streetwise";
const std::string kTalentSubterfugeLabel = "Subterfuge";
const Ability kTalentActing = Ability(kTalentActingId, kAbilityGroupTalentsId, kTalentActingLabel);
const Ability kTalentAlertness = Ability(kTalentAlertnessId, kAbilityGroupTalentsId, kTalentAlertnessLabel);
const Ability kTalentAthletics = Ability(kTalentAthleticsId, kAbilityGroupTalentsId, kTalentAthleticsLabel);
const Ability kTalentBrawl = Ability(kTalentBrawlId, kAbilityGroupTalentsId, kTalentBrawlLabel);
const Ability kTalentDodge = Ability(kTalentDodgeId, kAbilityGroupTalentsId, kTalentDodgeLabel);
const Ability kTalentEmpathy = Ability(kTalentEmpathyId, kAbilityGroupTalentsId, kTalentEmpathyLabel);
const Ability kTalentIntimidation = Ability(kTalentIntimidationId, kAbilityGroupTalentsId, kTalentIntimidationLabel);
const Ability kTalentLeadership = Ability(kTalentLeadershipId, kAbilityGroupTalentsId, kTalentLeadershipLabel);
const Ability kTalentStreetwise = Ability(kTalentStreetwiseId, kAbilityGroupTalentsId, kTalentStreetwiseLabel);
const Ability kTalentSubterfuge = Ability(kTalentSubterfugeId, kAbilityGroupTalentsId, kTalentSubterfugeLabel);
const vector<Ability> kTalents = {
kTalentActing,
kTalentAlertness,
kTalentAthletics,
kTalentBrawl,
kTalentDodge,
kTalentEmpathy,
kTalentIntimidation,
kTalentLeadership,
kTalentStreetwise,
kTalentSubterfuge,
};
const std::string kSkillAnimalKenLabel = "Animal Ken";
const std::string kSkillDriveLabel = "Drive";
const std::string kSkillEtiquetteLabel = "Etiquette";
const std::string kSkillFirearmsLabel = "Firearms";
const std::string kSkillMeleeLabel = "Melee";
const std::string kSkillMusicLabel = "Music";
const std::string kSkillRepairLabel = "Repair";
const std::string kSkillSecurityLabel = "Security";
const std::string kSkillStealthLabel = "Stealth";
const std::string kSkillSurvivalLabel = "Survival";
const Ability kSkillAnimalKen = Ability(kSkillAnimalKenId, kAbilityGroupSkillsId, kSkillAnimalKenLabel);
const Ability kSkillDrive = Ability(kSkillDriveId, kAbilityGroupSkillsId, kSkillDriveLabel);
const Ability kSkillEtiquette = Ability(kSkillEtiquetteId, kAbilityGroupSkillsId, kSkillEtiquetteLabel);
const Ability kSkillFirearms = Ability(kSkillFirearmsId, kAbilityGroupSkillsId, kSkillFirearmsLabel);
const Ability kSkillMelee = Ability(kSkillMeleeId, kAbilityGroupSkillsId, kSkillMeleeLabel);
const Ability kSkillMusic = Ability(kSkillMusicId, kAbilityGroupSkillsId, kSkillMusicLabel);
const Ability kSkillRepair = Ability(kSkillRepairId, kAbilityGroupSkillsId, kSkillRepairLabel);
const Ability kSkillSecurity = Ability(kSkillSecurityId, kAbilityGroupSkillsId, kSkillSecurityLabel);
const Ability kSkillStealth = Ability(kSkillStealthId, kAbilityGroupSkillsId, kSkillStealthLabel);
const Ability kSkillSurvival = Ability(kSkillSurvivalId, kAbilityGroupSkillsId, kSkillSurvivalLabel);
const vector<Ability> kSkills = {
kSkillAnimalKen,
kSkillDrive,
kSkillEtiquette,
kSkillFirearms,
kSkillMelee,
kSkillMusic,
kSkillRepair,
kSkillSecurity,
kSkillStealth,
kSkillSurvival,
};
const std::string kKnowledgeBureaucracyLabel = "Bureaucracy";
const std::string kKnowledgeComputerLabel = "Computer";
const std::string kKnowledgeFinanceLabel = "Finance";
const std::string kKnowledgeInvestigationLabel = "Investigation";
const std::string kKnowledgeLawLabel = "Law";
const std::string kKnowledgeLinguisticsLabel = "Linguistics";
const std::string kKnowledgeMedicineLabel = "Medicine";
const std::string kKnowledgeOccultLabel = "Occult";
const std::string kKnowledgePoliticsLabel = "Politics";
const std::string kKnowledgeScienceLabel = "Science";
const Ability kKnowledgeBureaucracy =
Ability(kKnowledgeBureaucracyId, kAbilityGroupKnowledgesId, kKnowledgeBureaucracyLabel);
const Ability kKnowledgeComputer = Ability(kKnowledgeComputerId, kAbilityGroupKnowledgesId, kKnowledgeComputerLabel);
const Ability kKnowledgeFinance = Ability(kKnowledgeFinanceId, kAbilityGroupKnowledgesId, kKnowledgeFinanceLabel);
const Ability kKnowledgeInvestigation =
Ability(kKnowledgeInvestigationId, kAbilityGroupKnowledgesId, kKnowledgeInvestigationLabel);
const Ability kKnowledgeLaw = Ability(kKnowledgeLawId, kAbilityGroupKnowledgesId, kKnowledgeLawLabel);
const Ability kKnowledgeLinguistics =
Ability(kKnowledgeLinguisticsId, kAbilityGroupKnowledgesId, kKnowledgeLinguisticsLabel);
const Ability kKnowledgeMedicine = Ability(kKnowledgeMedicineId, kAbilityGroupKnowledgesId, kKnowledgeMedicineLabel);
const Ability kKnowledgeOccult = Ability(kKnowledgeOccultId, kAbilityGroupKnowledgesId, kKnowledgeOccultLabel);
const Ability kKnowledgePolitics = Ability(kKnowledgePoliticsId, kAbilityGroupKnowledgesId, kKnowledgePoliticsLabel);
const Ability kKnowledgeScience = Ability(kKnowledgeScienceId, kAbilityGroupKnowledgesId, kKnowledgeScienceLabel);
const vector<Ability> kKnowledges = {
kKnowledgeBureaucracy,
kKnowledgeComputer,
kKnowledgeFinance,
kKnowledgeInvestigation,
kKnowledgeLaw,
kKnowledgeLinguistics,
kKnowledgeMedicine,
kKnowledgeOccult,
kKnowledgePolitics,
kKnowledgeScience,
};
vector<string> AbilityGroup::GetAbilityLabels() const {
return AbilityGroup::GetAbilityLabels(id_);
}
vector<string> AbilityGroup::GetAbilityLabels(int id) {
switch (id) {
case kAbilityGroupKnowledgesId:
return Ability::GetKnowledgeLabels();
case kAbilityGroupSkillsId:
return Ability::GetSkillLabels();
case kAbilityGroupTalentsId:
return Ability::GetTalentLabels();
}
return vector<string>();
}
int GetNumItemsForAbilityGroup(int ability_group_id) {
switch (ability_group_id) {
case kAbilityGroupTalentsId:
return kTalents.size();
case kAbilityGroupSkillsId:
return kSkills.size();
case kAbilityGroupKnowledgesId:
return kKnowledges.size();
}
return 0;
}
int AbilityGroup::GetCount() {
return kAbilityGroups.size();
}
int AbilityGroup::GetAbilityCount() const {
switch (id_) {
case kAbilityGroupKnowledgesId:
return kTalents.size();
case kAbilityGroupSkillsId:
return kSkills.size();
case kAbilityGroupTalentsId:
return kTalents.size();
}
return 0;
}
AbilityGroup::AbilityGroup() : id_(0), singular_(""), plural_("") {}
AbilityGroup::AbilityGroup(int id, const string& singular, const string& plural)
: id_(id), singular_(singular), plural_(plural) {}
int AbilityGroup::id() const {
return id_;
}
void AbilityGroup::id(int id) {
id_ = id;
}
string AbilityGroup::plural() const {
return plural_;
}
void AbilityGroup::plural(const string& plural) {
plural_ = plural;
}
string AbilityGroup::singular() const {
return singular_;
}
void AbilityGroup::singular(const string& singular) {
singular_ = singular;
}
vector<string> AbilityGroup::GetPluralLabels() {
vector<string> labels;
for_each(kAbilityGroups.begin(), kAbilityGroups.end(), [&labels](const auto& ability) {
labels.push_back(ability.plural());
});
return labels;
}
Ability::Ability() : id_(0), group_id_(0), label_("") {}
Ability::Ability(int id, int group_id, const string& label) : id_(id), group_id_(group_id), label_(label) {}
int Ability::id() const {
return id_;
}
void Ability::id(int id) {
id_ = id;
}
int Ability::group_id() const {
return group_id_;
}
void Ability::group_id(int group_id) {
group_id_ = group_id;
}
string Ability::label() const {
return label_;
}
void Ability::label(const string& label) {
label_ = label;
}
AbilityGroup AbilityGroup::FromId(int id) {
switch (id) {
case kAbilityGroupTalentsId:
return kAbilityGroupTalents;
case kAbilityGroupSkillsId:
return kAbilityGroupSkills;
case kAbilityGroupKnowledgesId:
return kAbilityGroupKnowledges;
}
return kAbilityGroupUnknown;
}
ostream& operator<<(ostream& os, const AbilityGroup& ability_group) {
os << "AbilityGroup: {id: " << ability_group.id_ << ", singular: \"" << ability_group.singular_ << "\", plural: \""
<< ability_group.plural_ << "\"}";
return os;
}
bool AbilityGroup::operator==(const AbilityGroup& other) const {
return id_ == other.id_ && singular_ == other.singular_ && plural_ == other.plural_;
}
bool AbilityGroup::operator!=(const AbilityGroup& other) const {
return !(*this == other);
}
ostream& operator<<(ostream& os, const Ability& ability) {
os << "Ability: {id: " << ability.id_ << ", group_id: " << ability.group_id_ << ", label: \"" << ability.label_
<< "\"}";
return os;
}
bool Ability::operator==(const Ability& other) const {
return id_ == other.id_ && group_id_ == other.group_id_ && label_ == other.label_;
}
bool Ability::operator!=(const Ability& other) const {
return !(*this == other);
}
string Ability::GetSkillLabel(int id) {
return Ability::FromSkillId(id).label();
}
vector<string> Ability::GetSkillLabels() {
vector<string> labels;
for_each(kSkills.begin(), kSkills.end(), [&labels](const auto& ability) { labels.push_back(ability.label()); });
return labels;
}
string Ability::GetTalentLabel(int id) {
return Ability::FromTalentId(id).label();
}
vector<string> Ability::GetTalentLabels() {
vector<string> labels;
for_each(kTalents.begin(), kTalents.end(), [&labels](const auto& ability) { labels.push_back(ability.label()); });
return labels;
}
string Ability::GetKnowledgeLabel(int id) {
return Ability::FromKnowledgeId(id).label();
}
vector<string> Ability::GetKnowledgeLabels() {
vector<string> labels;
for_each(
kKnowledges.begin(), kKnowledges.end(), [&labels](const auto& ability) { labels.push_back(ability.label()); });
return labels;
}
Ability Ability::FromKnowledgeId(int id) {
if (id > 0 && id <= kKnowledges.size()) {
return kKnowledges.at(id - 1);
}
return kAbilityUnknown;
}
Ability Ability::FromSkillId(int id) {
if (id > 0 && id <= kSkills.size()) {
return kSkills.at(id - 1);
}
return kAbilityUnknown;
}
Ability Ability::FromTalentId(int id) {
if (id > 0 && id <= kTalents.size()) {
return kTalents.at(id - 1);
}
return kAbilityUnknown;
}
Ability Ability::FromIds(int id, int group_id) {
switch (group_id) {
case kAbilityGroupKnowledgesId:
return Ability::FromKnowledgeId(id);
case kAbilityGroupSkillsId:
return Ability::FromSkillId(id);
case kAbilityGroupTalentsId:
return Ability::FromTalentId(id);
}
return kAbilityUnknown;
}
int Ability::GetCount(int group_id) {
switch (group_id) {
case kAbilityGroupKnowledgesId:
return kKnowledges.size();
case kAbilityGroupSkillsId:
return kSkills.size();
case kAbilityGroupTalentsId:
return kTalents.size();
}
return 0;
}
int Ability::GetKnowledgesCount() {
return kKnowledges.size();
}
int Ability::GetSkillsCount() {
return kSkills.size();
}
int Ability::GetTalentsCount() {
return kTalents.size();
}
} // End namespace SBF

126
sbf-cpp/Abilities.h Normal file
View File

@@ -0,0 +1,126 @@
#ifndef ABILITIES_H__
#define ABILITIES_H__
/***************************************************************************************
* @file Abilities.h
*
* @brief Defines AbiiltyType, ability and ability group constants, and ability and
* ability group functions.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
#include <string>
#include <vector>
/** \addtogroup Abilities
* @{
*/
namespace SBF {
const int kAbilityGroupTalentsId = 1;
const int kAbilityGroupSkillsId = 2;
const int kAbilityGroupKnowledgesId = 3;
const int kTalentActingId = 1;
const int kTalentAlertnessId = 2;
const int kTalentAthleticsId = 3;
const int kTalentBrawlId = 4;
const int kTalentDodgeId = 5;
const int kTalentEmpathyId = 6;
const int kTalentIntimidationId = 7;
const int kTalentLeadershipId = 8;
const int kTalentStreetwiseId = 9;
const int kTalentSubterfugeId = 10;
const int kSkillAnimalKenId = 1;
const int kSkillDriveId = 2;
const int kSkillEtiquetteId = 3;
const int kSkillFirearmsId = 4;
const int kSkillMeleeId = 5;
const int kSkillMusicId = 6;
const int kSkillRepairId = 7;
const int kSkillSecurityId = 8;
const int kSkillStealthId = 9;
const int kSkillSurvivalId = 10;
const int kKnowledgeBureaucracyId = 1;
const int kKnowledgeComputerId = 2;
const int kKnowledgeFinanceId = 3;
const int kKnowledgeInvestigationId = 4;
const int kKnowledgeLawId = 5;
const int kKnowledgeLinguisticsId = 6;
const int kKnowledgeMedicineId = 7;
const int kKnowledgeOccultId = 8;
const int kKnowledgePoliticsId = 9;
const int kKnowledgeScienceId = 10;
class AbilityGroup {
public:
AbilityGroup();
AbilityGroup(int id, const std::string& singular, const std::string& plural);
int GetAbilityCount() const;
std::vector<std::string> GetAbilityLabels() const;
int id() const;
void id(int id);
std::string plural() const;
void plural(const std::string& plural);
std::string singular() const;
void singular(const std::string& singular);
bool operator==(const AbilityGroup& other) const;
bool operator!=(const AbilityGroup& other) const;
static AbilityGroup FromId(int id);
static std::vector<std::string> GetPluralLabels();
static std::vector<std::string> GetAbilityLabels(int id);
static int GetCount();
friend std::ostream& operator<<(std::ostream& os, const AbilityGroup& ability_group);
private:
int id_;
std::string singular_;
std::string plural_;
};
std::ostream& operator<<(std::ostream& os, const AbilityGroup& ability_group);
class Ability {
public:
Ability();
Ability(int id, int group_id, const std::string& label);
int group_id() const;
void group_id(int group_id);
int id() const;
void id(int id);
std::string label() const;
void label(const std::string& label);
bool operator==(const Ability& other) const;
bool operator!=(const Ability& other) const;
static Ability FromIds(int id, int group_id);
static Ability FromKnowledgeId(int id);
static Ability FromSkillId(int id);
static Ability FromTalentId(int id);
static int GetCount(int group_id);
static std::string GetKnowledgeLabel(int id);
static std::vector<std::string> GetKnowledgeLabels();
static int GetKnowledgesCount();
static std::string GetSkillLabel(int id);
static std::vector<std::string> GetSkillLabels();
static int GetSkillsCount();
static std::string GetTalentLabel(int id);
static std::vector<std::string> GetTalentLabels();
static int GetTalentsCount();
friend std::ostream& operator<<(std::ostream& os, const Ability& ability);
private:
int id_;
int group_id_;
std::string label_;
};
std::ostream& operator<<(std::ostream& os, const Ability& ability);
} // End namespace SBF
/** @}*/
#endif // End !defined ABILITIES_H__

1095
sbf-cpp/Abilities_test.cpp Normal file

File diff suppressed because it is too large Load Diff

118
sbf-cpp/Archetypes.cpp Normal file
View File

@@ -0,0 +1,118 @@
#include "Archetypes.h"
#include <string>
#include <vector>
namespace SBF {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-const-variable"
const int kArchetypeArchitectId = 1;
const std::string kArchetypeArchitectLabel = "Architect";
const int kArchetypeAutocratId = 2;
const std::string kArchetypeAutocratLabel = "Autocrat";
const int kArchetypeBarbarianId = 3;
const std::string kArchetypeBarbarianLabel = "Barbarian";
const int kArchetypeBonVivantId = 4;
const std::string kArchetypeBonVivantLabel = "Bon Vivant";
const int kArchetypeBravoId = 5;
const std::string kArchetypeBravoLabel = "Bravo";
const int kArchetypeCaregiverId = 6;
const std::string kArchetypeCaregiverLabel = "Caregiver";
const int kArchetypeCaretakerId = 7;
const std::string kArchetypeCaretakerLabel = "Caretaker";
const int kArchetypeCelebrantId = 8;
const std::string kArchetypeCelebrantLabel = "Celebrant";
const int kArchetypeChildId = 9;
const std::string kArchetypeChildLabel = "Child";
const int kArchetypeConformist = 10;
const std::string kArchetypeConformistLabel = "Conformist";
const int kArchetypeConniverId = 11;
const std::string kArchetypeConniverLabel = "Conniver";
const int kArchetypeCurmudgeonId = 12;
const std::string kArchetypeCurmudgeonLabel = "Curmudgeon";
const int kArchetypeDefenderId = 13;
const std::string kArchetypeDefenderLabel = "Defender";
const int kArchetypeDeviantId = 14;
const std::string kArchetypeDeviantLabel = "Deviant";
const int kArchetypeDirectorId = 15;
const std::string kArchetypeDirectorLabel = "Director";
const int kArchetypeFanaticId = 16;
const std::string kArchetypeFanaticLabel = "Fanatic";
const int kArchetypeGallantId = 17;
const std::string kArchetypeGallantLabel = "Gallant";
const int kArchetypeInnovatorId = 18;
const std::string kArchetypeInnovatorLabel = "Innovator";
const int kArchetypeJesterId = 19;
const std::string kArchetypeJesterLabel = "Jester";
const int kArchetypeJudgeId = 20;
const std::string kArchetypeJudgeLabel = "Judge";
const int kArchetypeLoanerId = 21;
const std::string kArchetypeLoanerLabel = "Loaner";
const int kArchetypeMartyrId = 22;
const std::string kArchetypeMartyrLabel = "Martyr";
const int kArchetypeMonsterId = 23;
const std::string kArchetypeMonsterLabel = "Monster";
const int kArchetypePenitentId = 24;
const std::string kArchetypePenitentLabel = "Penitent";
const int kArchetypeRebelId = 25;
const std::string kArchetypeRebelLabel = "Rebel";
const int kArchetypeRogueId = 26;
const std::string kArchetypeRogueLabel = "Rogue";
const int kArchetypeSurvivorId = 27;
const std::string kArchetypeSurvivorLabel = "Survivor";
const int kArchetypeTraditionalistId = 28;
const std::string kArchetypeTraditionalistLabel = "Traditionalist";
const int kArchetypeTyrantId = 29;
const std::string kArchetypeTyrantLabel = "Tyrant";
const int kArchetypeVisionaryId = 30;
const std::string kArchetypeVisionaryLabel = "Visionary";
#pragma clang diagnostic pop
const int kArchetypesCount = 30;
const std::string kArchetypes[] = {
"",
kArchetypeArchitectLabel,
kArchetypeAutocratLabel,
kArchetypeBarbarianLabel,
kArchetypeBonVivantLabel,
kArchetypeBravoLabel,
kArchetypeCaregiverLabel,
kArchetypeCaretakerLabel,
kArchetypeCelebrantLabel,
kArchetypeChildLabel,
kArchetypeConformistLabel,
kArchetypeConniverLabel,
kArchetypeCurmudgeonLabel,
kArchetypeDefenderLabel,
kArchetypeDeviantLabel,
kArchetypeDirectorLabel,
kArchetypeFanaticLabel,
kArchetypeGallantLabel,
kArchetypeInnovatorLabel,
kArchetypeJesterLabel,
kArchetypeJudgeLabel,
kArchetypeLoanerLabel,
kArchetypeMartyrLabel,
kArchetypeMonsterLabel,
kArchetypePenitentLabel,
kArchetypeRebelLabel,
kArchetypeRogueLabel,
kArchetypeSurvivorLabel,
kArchetypeTraditionalistLabel,
kArchetypeTyrantLabel,
kArchetypeVisionaryLabel,
};
std::string GetArchetypeLabel(int archetype_id) {
if (archetype_id >= 0 && archetype_id <= kArchetypesCount) {
return kArchetypes[archetype_id];
}
return "";
}
void FillArchetypeLabels(std::vector<std::string>& labels) {
labels.clear();
for (int id = 1; id <= kArchetypesCount; id++) {
labels.push_back(GetArchetypeLabel(id));
}
}
} // End namespace SBF

29
sbf-cpp/Archetypes.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef ARCHETYPES_H__
#define ARCHETYPES_H__
/***************************************************************************************
* @file Archetypes.h
*
* @brief Defines constants and functions for working with archetypes.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
#include <string>
#include <vector>
/** \addtogroup Archetypes
* @{
*/
namespace SBF {
/// @brief Gets the label for the given archetype id.
/// @param archetypeId The id of the archetype to get the label for.
/// @return The label for the requested archetype.
std::string GetArchetypeLabel(int archetypeId);
/// @brief Fills the provided vector with all of the archetype labels. It clears the vector before filling.
/// @param archetypeLabels The vector to fill.
void FillArchetypeLabels(std::vector<std::string>& archetypeLabels);
} // End namespace SBF
/** @}*/
#endif // End !defined ARCHETYPES_H__

View File

@@ -0,0 +1,87 @@
#include "Archetypes.h"
#include <iostream>
#include <sstream>
#include "test.h"
namespace {
using namespace SBF;
using namespace Test;
using namespace std;
} // End namespace
TestResults test_GetArchetypeLabel() {
return execute_suite<string, int>(make_test_suite(
"SBF::GetArchetypeLabel",
GetArchetypeLabel,
vector<TestTuple<string, int>>({
make_test<string, int>("should return \"\" for id 0", "", make_tuple(0)),
make_test<string, int>("should return \"Architect\" for id 1", "Architect", make_tuple(1)),
make_test<string, int>("should return \"Autocrat\" for id 2", "Autocrat", make_tuple(2)),
make_test<string, int>("should return \"Barbarian\" for id 3", "Barbarian", make_tuple(3)),
make_test<string, int>("should return \"Bon Vivant\" for id 4", "Bon Vivant", make_tuple(4)),
make_test<string, int>("should return \"Bravo\" for id 5", "Bravo", make_tuple(5)),
make_test<string, int>("should return \"Caregiver\" for id 6", "Caregiver", make_tuple(6)),
make_test<string, int>("should return \"Caretaker\" for id 7", "Caretaker", make_tuple(7)),
make_test<string, int>("should return \"Celebrant\" for id 8", "Celebrant", make_tuple(8)),
make_test<string, int>("should return \"Child\" for id 9", "Child", make_tuple(9)),
make_test<string, int>("should return \"Conformist\" for id 10", "Conformist", make_tuple(10)),
make_test<string, int>("should return \"Conniver\" for id 11", "Conniver", make_tuple(11)),
make_test<string, int>("should return \"Curmudgeon\" for id 12", "Curmudgeon", make_tuple(12)),
make_test<string, int>("should return \"Defender\" for id 13", "Defender", make_tuple(13)),
make_test<string, int>("should return \"Deviant\" for id 14", "Deviant", make_tuple(14)),
make_test<string, int>("should return \"Director\" for id 15", "Director", make_tuple(15)),
make_test<string, int>("should return \"Fanatic\" for id 16", "Fanatic", make_tuple(16)),
make_test<string, int>("should return \"Gallant\" for id 17", "Gallant", make_tuple(17)),
make_test<string, int>("should return \"Innovator\" for id 18", "Innovator", make_tuple(18)),
make_test<string, int>("should return \"Jester\" for id 19", "Jester", make_tuple(19)),
make_test<string, int>("should return \"Judge\" for id 20", "Judge", make_tuple(20)),
make_test<string, int>("should return \"Loaner\" for id 21", "Loaner", make_tuple(21)),
make_test<string, int>("should return \"Martyr\" for id 22", "Martyr", make_tuple(22)),
make_test<string, int>("should return \"Monster\" for id 23", "Monster", make_tuple(23)),
make_test<string, int>("should return \"Penitent\" for id 24", "Penitent", make_tuple(24)),
make_test<string, int>("should return \"Rebel\" for id 25", "Rebel", make_tuple(25)),
make_test<string, int>("should return \"Rogue\" for id 26", "Rogue", make_tuple(26)),
make_test<string, int>("should return \"Survivor\" for id 27", "Survivor", make_tuple(27)),
make_test<string, int>("should return \"Traditionalist\" for id 28", "Traditionalist", make_tuple(28)),
make_test<string, int>("should return \"Tyrant\" for id 29", "Tyrant", make_tuple(29)),
make_test<string, int>("should return \"Visionary\" for id 30", "Visionary", make_tuple(30)),
make_test<string, int>("should return \"\" for an invalid id", "", make_tuple(31)),
})));
}
TestResults test_FillArchetypeLabels() {
return execute_suite<string>(make_test_suite(
"SBF::FillArchetypeLabels",
[]() -> string {
ostringstream error_message;
vector<string> expected = {"Architect", "Autocrat", "Barbarian", "Bon Vivant", "Bravo", "Caregiver",
"Caretaker", "Celebrant", "Child", "Conformist", "Conniver", "Curmudgeon",
"Defender", "Deviant", "Director", "Fanatic", "Gallant", "Innovator",
"Jester", "Judge", "Loaner", "Martyr", "Monster", "Penitent",
"Rebel", "Rogue", "Survivor", "Traditionalist", "Tyrant", "Visionary"};
vector<string> actual = {"This should be removed."};
FillArchetypeLabels(actual);
compare(error_message, expected, actual);
string error = error_message.str();
if (error.size() > 0) {
return error;
}
return "no errors";
},
vector<TestTuple<string>>({
make_test<string>("should fill archetype labels", "no errors", make_tuple()),
})));
}
int main(int argc, char* argv[]) {
TestResults results;
results += test_GetArchetypeLabel();
results += test_FillArchetypeLabels();
PrintResults(cout, results);
return results.failed() + results.errors();
}

277
sbf-cpp/Attributes.cpp Normal file
View File

@@ -0,0 +1,277 @@
#include "Attributes.h"
#include <sstream>
#include <string>
#include <vector>
namespace SBF {
namespace {
using std::string;
using std::vector;
} // End namespace
const int kPhysicalAttributesCount = 3;
const int kSocialAttributesCount = 3;
const int kMentalAttributesCount = 3;
const int kAttributeGroupsCount = 3;
const std::string kPhysicalAttributeStrengthLabel = "Strength";
const std::string kPhysicalAttributeStrengthAbbreviation = "Str.";
const std::string kPhysicalAttributeDexterityLabel = "Dexterity";
const std::string kPhysicalAttributeDexterityAbbreviation = "Dex.";
const std::string kPhysicalAttributeStaminaLabel = "Stamina";
const std::string kPhysicalAttributeStaminaAbbreviation = "Sta.";
const std::string kPhysicalAttributeLabels[] = {
"",
kPhysicalAttributeStrengthLabel,
kPhysicalAttributeDexterityLabel,
kPhysicalAttributeStaminaLabel,
};
const std::string kPhysicalAttributeLabelAbbreviations[] = {
"",
kPhysicalAttributeStrengthAbbreviation,
kPhysicalAttributeDexterityAbbreviation,
kPhysicalAttributeStaminaAbbreviation,
};
const std::string kSocialAttributeCharismaLabel = "Charisma";
const std::string kSocialAttributeCharismaAbbreviation = "Cha.";
const std::string kSocialAttributeManipulationLabel = "Manipulation";
const std::string kSocialAttributeManipulationAbbreviation = "Man.";
const std::string kSocialAttributeAppearanceLabel = "Appearance";
const std::string kSocialAttributeAppearanceAbbreviation = "App.";
const std::string kSocialAttributeLabels[] = {
"",
kSocialAttributeCharismaLabel,
kSocialAttributeManipulationLabel,
kSocialAttributeAppearanceLabel,
};
const std::string kSocialAttributeLabelAbbreviations[] = {
"",
kSocialAttributeCharismaAbbreviation,
kSocialAttributeManipulationAbbreviation,
kSocialAttributeAppearanceAbbreviation,
};
const std::string kMentalAttributeIntelligenceLabel = "Intelligence";
const std::string kMentalAttributeIntelligenceAbbreviation = "Int.";
const std::string kMentalAttributePerceptionLabel = "Perception";
const std::string kMentalAttributePerceptionAbbreviation = "Per.";
const std::string kMentalAttributeWitsLabel = "Wits";
const std::string kMentalAttributeWitsAbbreviation = "Wits";
const std::string kMentalAttributeLabels[] = {
"",
kMentalAttributeIntelligenceLabel,
kMentalAttributePerceptionLabel,
kMentalAttributeWitsLabel,
};
const std::string kMentalAttributeLabelAbbreviations[] = {
"",
kMentalAttributeIntelligenceAbbreviation,
kMentalAttributePerceptionAbbreviation,
kMentalAttributeWitsAbbreviation,
};
const std::string kAttributeGroupPhysicalLabel = "Physical";
const std::string kAttributeGroupSocialLabel = "Social";
const std::string kAttributeGroupMentalLabel = "Mental";
const std::string kAttributeGroups[] = {
"",
kAttributeGroupPhysicalLabel,
kAttributeGroupSocialLabel,
kAttributeGroupMentalLabel,
};
string GetAttributeGroupLabel(int attributeGroupId) {
if (attributeGroupId > 0 && attributeGroupId <= kAttributeGroupsCount) {
return kAttributeGroups[attributeGroupId];
}
return "";
}
string GetAttributeLabel(int attributeGroupId, int attributeId) {
switch (attributeGroupId) {
case kAttributeGroupPhysicalId:
return GetPhysicalAttributeLabel(attributeId);
case kAttributeGroupSocialId:
return GetSocialAttributeLabel(attributeId);
case kAttributeGroupMentalId:
return GetMentalAttributeLabel(attributeId);
default:
return "";
}
}
string GetAttributeLabelAbbreviation(int attributeGroupId, int attributeId) {
switch (attributeGroupId) {
case kAttributeGroupPhysicalId:
return GetPhysicalAttributeLabelAbbreviation(attributeId);
case kAttributeGroupSocialId:
return GetSocialAttributeLabelAbbreviation(attributeId);
case kAttributeGroupMentalId:
return GetMentalAttributeLabelAbbreviation(attributeId);
default:
return "";
}
}
int GetNumAttributesInGroup(int attributeGroupId) {
switch (attributeGroupId) {
case kAttributeGroupPhysicalId:
return kPhysicalAttributesCount;
case kAttributeGroupSocialId:
return kSocialAttributesCount;
case kAttributeGroupMentalId:
return kMentalAttributesCount;
}
return 0;
}
string GetPhysicalAttributeLabel(int attributeId) {
if (attributeId > 0 && attributeId <= kPhysicalAttributesCount) {
return kPhysicalAttributeLabels[attributeId];
}
return "";
}
string GetPhysicalAttributeLabelAbbreviation(int attributeId) {
if (attributeId > 0 && attributeId <= kPhysicalAttributesCount) {
return kPhysicalAttributeLabelAbbreviations[attributeId];
}
return "";
}
string GetSocialAttributeLabel(int attributeId) {
if (attributeId > 0 && attributeId <= kSocialAttributesCount) {
return kSocialAttributeLabels[attributeId];
}
return "";
}
string GetSocialAttributeLabelAbbreviation(int attributeId) {
if (attributeId > 0 && attributeId <= kSocialAttributesCount) {
return kSocialAttributeLabelAbbreviations[attributeId];
}
return "";
}
string GetMentalAttributeLabel(int attributeId) {
if (attributeId > 0 && attributeId <= kMentalAttributesCount) {
return kMentalAttributeLabels[attributeId];
}
return "";
}
string GetMentalAttributeLabelAbbreviation(int attributeId) {
if (attributeId > 0 && attributeId <= kMentalAttributesCount) {
return kMentalAttributeLabelAbbreviations[attributeId];
}
return "";
}
void FillAttributeGroupLabels(std::vector<std::string>& labels) {
labels.clear();
for (int id = 1; id <= kAttributeGroupsCount; id++) {
labels.push_back(kAttributeGroups[id]);
}
}
void FillAttributeLabelsInGroup(std::vector<std::string>& labels, int groupId) {
labels.clear();
switch (groupId) {
case kAttributeGroupPhysicalId:
FillPhysicalAttributeLabels(labels);
break;
case kAttributeGroupSocialId:
FillSocialAttributeLabels(labels);
break;
case kAttributeGroupMentalId:
FillMentalAttributeLabels(labels);
break;
}
}
vector<string> GetAttributeLabelsInGroup(int group_id) {
vector<string> labels;
switch (group_id) {
case kAttributeGroupPhysicalId:
FillPhysicalAttributeLabels(labels);
break;
case kAttributeGroupSocialId:
FillSocialAttributeLabels(labels);
break;
case kAttributeGroupMentalId:
FillMentalAttributeLabels(labels);
break;
}
return labels;
}
void FillPhysicalAttributeLabels(std::vector<std::string>& labels) {
labels.clear();
for (int id = 1; id <= kPhysicalAttributesCount; id++) {
labels.push_back(kPhysicalAttributeLabels[id]);
}
}
void FillSocialAttributeLabels(std::vector<std::string>& labels) {
labels.clear();
for (int id = 1; id <= kSocialAttributesCount; id++) {
labels.push_back(kSocialAttributeLabels[id]);
}
}
void FillMentalAttributeLabels(std::vector<std::string>& labels) {
labels.clear();
for (int id = 1; id <= kMentalAttributesCount; id++) {
labels.push_back(kMentalAttributeLabels[id]);
}
}
void FillAttributeLabelsInGroup(std::vector<string> labels, int group_id) {
labels.clear();
switch (group_id) {
case kAttributeGroupPhysicalId:
for (int id = 0; id <= kPhysicalAttributesCount; id++) {
labels.push_back(kPhysicalAttributeLabels[id]);
}
break;
case kAttributeGroupSocialId:
for (int id = 0; id <= kSocialAttributesCount; id++) {
labels.push_back(kSocialAttributeLabels[id]);
}
break;
case kAttributeGroupMentalId:
for (int id = 0; id <= kMentalAttributesCount; id++) {
labels.push_back(kMentalAttributeLabels[id]);
}
break;
}
}
void FillAttributeAbbreviationsInGroup(std::vector<string> abbreviations, int group_id) {
abbreviations.clear();
switch (group_id) {
case kAttributeGroupPhysicalId:
for (int id = 0; id <= kPhysicalAttributesCount; id++) {
abbreviations.push_back(kPhysicalAttributeLabelAbbreviations[id]);
}
break;
case kAttributeGroupSocialId:
for (int id = 0; id <= kSocialAttributesCount; id++) {
abbreviations.push_back(kSocialAttributeLabelAbbreviations[id]);
}
break;
case kAttributeGroupMentalId:
for (int id = 0; id <= kMentalAttributesCount; id++) {
abbreviations.push_back(kMentalAttributeLabelAbbreviations[id]);
}
break;
}
}
int GetNumAttributeGroups() {
return kAttributeGroupsCount;
}
} // End namespace SBF

116
sbf-cpp/Attributes.h Normal file
View File

@@ -0,0 +1,116 @@
#ifndef ATTRIBUTES_H__
#define ATTRIBUTES_H__
/***************************************************************************************
* @file Attributes.h
*
* @brief Defines constants and functions for working with attribute groups and attriubtes.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
#include <string>
#include <vector>
/** \addtogroup Attributes
* @{
*/
namespace SBF {
const int kPhysicalAttributeStrengthId = 1;
const int kPhysicalAttributeDexterityId = 2;
const int kPhysicalAttributeStaminaId = 3;
const int kSocialAttributeCharismaId = 1;
const int kSocialAttributeManipulationId = 2;
const int kSocialAttributeAppearanceId = 3;
const int kMentalAttributeIntelligenceId = 1;
const int kMentalAttributePerceptionId = 2;
const int kMentalAttributeWitsId = 3;
const int kAttributeGroupPhysicalId = 1;
const int kAttributeGroupSocialId = 2;
const int kAttributeGroupMentalId = 3;
/// @brief Gets the label for the attribute group with the specified id.
/// @param attribute_group_id The id of the attribute group to return the label for.
/// @return The label of the specified attribute group.
std::string GetAttributeGroupLabel(int attribute_group_id);
/// @brief Gets the label for the attribute with a specific pair of group and attribute ids.
/// @param attribute_group_id The id of the attribute group to get the label for.
/// @param attribute_id The id of the attribute within the group to return the label for.
/// @return The label of the specified attribute or an empty string if either id is invalid.
std::string GetAttributeLabel(int attribute_group_id, int attribute_id);
/// @brief Gets the label abbreviation for the attribute with a specific pair of group and attribute ids.
/// @param attribute_group_id The id of the attribute group to get the label abbreviation for.
/// @param attribute_id The id of the attribute within the group to return the label abbreviation for.
/// @return The label abbreviation of the specified attribute or an empty string if either id is invalid.
std::string GetAttributeLabelAbbreviation(int attribute_group_id, int attribute_id);
/// @brief Gets the label for the physical attribute with a specific id.
/// @param attribute_id The id of the physical attribute to get the label for.
/// @return The label of the specified attribute or an empty string if the id is invalid.
std::string GetPhysicalAttributeLabel(int attribute_id);
/// @brief Gets the label abbreviation for the physical attribute with a specific id.
/// @param attribute_id The id of the physical attribute to get the label abbreviation for.
/// @return The label abbreviation of the specified attribute or an empty string if the id is invalid.
std::string GetPhysicalAttributeLabelAbbreviation(int attribute_id);
/// @brief Gets the number of attributes in a specific attribute group.
/// @param attribute_group_id The id of the attribute group.
/// @return The number of attributes in the specified group or 0 if the id is invalid.
int GetNumAttributesInGroup(int attribute_group_id);
int GetNumAttributeGroups();
/// @brief Gets the label for the social attribute with a specific id.
/// @param attribute_id The id of the social attribute to get the label for.
/// @return The label of the specified attribute or an empty string if the id is invalid.
std::string GetSocialAttributeLabel(int attribute_id);
/// @brief Gets the label abbreviation for the social attribute with a specific id.
/// @param attribute_id The id of the social attribute to get the label abbreviation for.
/// @return The label abbreviation of the specified attribute or an empty string if the id is invalid.
std::string GetSocialAttributeLabelAbbreviation(int attribute_id);
/// @brief Gets the label for the mental attribute with a specific id.
/// @param attribute_id The id of the mental attribute to get the label for.
/// @return The label of the specified attribute or an empty string if the id is invalid.
std::string GetMentalAttributeLabel(int attribute_id);
/// @brief Gets the label abbreviation for the mental attribute with a specific id.
/// @param attribute_id The id of the mental attribute to get the label abbreviation for.
/// @return The label abbreviation of the specified attribute or an empty string if the id is invalid.
std::string GetMentalAttributeLabelAbbreviation(int attribute_id);
/// @brief Fills the provided vector with all of the valid attribute group labels
/// @param labels The vector to fill. It will be cleared first.
void FillAttributeGroupLabels(std::vector<std::string>& labels);
/// @brief Fills the provided vector with all of the valid attribute labels in a specific group.
/// @param labels The vector to fill. It will be cleared first.
/// @param group_id The group to fill attribute labels for.
void FillAttributeLabelsInGroup(std::vector<std::string>& labels, int group_id);
/// @brief Gets the valid attribute labels in a specific group.
/// @param group_id The group to fill attribute labels for.
/// @return The filled vector.
std::vector<std::string> GetAttributeLabelsInGroup(int group_id);
/// @brief Fills the provided vector with all of the valid physical attribute labels.
/// @param labels The vector to fill. It will be cleared first.
void FillPhysicalAttributeLabels(std::vector<std::string>& labels);
/// @brief Fills the provided vector with all of the valid social attribute labels.
/// @param labels The vector to fill. It will be cleared first.
void FillSocialAttributeLabels(std::vector<std::string>& labels);
/// @brief Fills the provided vector with all of the valid mental attribute labels.
/// @param labels The vector to fill. It will be cleared first.
void FillMentalAttributeLabels(std::vector<std::string>& labels);
} // End namespace SBF
/** @}*/
#endif // End !defined ATTRIBUTES_H__

307
sbf-cpp/Attributes_test.cpp Normal file
View File

@@ -0,0 +1,307 @@
#include "Attributes.h"
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#include "test.h"
namespace {
using namespace SBF;
using namespace Test;
using namespace std;
} // End namespace
TestResults test_GetAttributeGroupLabel() {
return execute_suite<string, int>(
make_test_suite("SBF::GetAttributeGroupLabel",
GetAttributeGroupLabel,
vector<TestTuple<string, int>>({
make_test<string, int>("should get \"\" for id 0", "", make_tuple(0)),
make_test<string, int>("should get \"Physical\" for id 1", "Physical", make_tuple(1)),
make_test<string, int>("should get \"Social\" for id 2", "Social", make_tuple(2)),
make_test<string, int>("should get \"Mental\" for id 3", "Mental", make_tuple(3)),
make_test<string, int>("should get \"\" for id 4", "", make_tuple(4)),
})));
}
TestResults test_GetAttributeLabel() {
return execute_suite<string, int, int>(make_test_suite(
"SBF::GetAttributeLabel",
GetAttributeLabel,
vector<TestTuple<string, int, int>>({
make_test<string, int, int>("should get \"Strength\" for group id 1 and id 1", "Strength", make_tuple(1, 1)),
make_test<string, int, int>(
"should get \"Dexterity\" for group id 1 and id 2", "Dexterity", make_tuple(1, 2)),
make_test<string, int, int>("should get \"Stamina\" for group id 1 and id 3", "Stamina", make_tuple(1, 3)),
make_test<string, int, int>("should get \"Charisma\" for group id 2 and id 1", "Charisma", make_tuple(2, 1)),
make_test<string, int, int>(
"should get \"Manipulation\" for group id 2 and id 2", "Manipulation", make_tuple(2, 2)),
make_test<string, int, int>(
"should get \"Appearance\" for group id 2 and id 3", "Appearance", make_tuple(2, 3)),
make_test<string, int, int>(
"should get \"Intelligence\" for group id 3 and id 1", "Intelligence", make_tuple(3, 1)),
make_test<string, int, int>(
"should get \"Perception\" for group id 3 and id 2", "Perception", make_tuple(3, 2)),
make_test<string, int, int>("should get \"Wits\" for group id 3 and id 3", "Wits", make_tuple(3, 3)),
make_test<string, int, int>("should get \"\" for an invalid group id 0", "", make_tuple(0, 1)),
make_test<string, int, int>("should get \"\" for an invalid id 0", "", make_tuple(1, 0)),
make_test<string, int, int>("should get \"\" for invalid group id 4", "", make_tuple(4, 1)),
make_test<string, int, int>("should get \"\" for an invalid id 4", "", make_tuple(1, 4)),
})));
}
TestResults test_GetAttributeLabelAbbreviation() {
return execute_suite<string, int, int>(make_test_suite(
"SBF::GetAttributeLabelAbbreviation",
GetAttributeLabelAbbreviation,
vector<TestTuple<string, int, int>>({
make_test<string, int, int>("should get \"Str.\" for group id 1 and id 1", "Str.", make_tuple(1, 1)),
make_test<string, int, int>("should get \"Dex.\" for group id 1 and id 2", "Dex.", make_tuple(1, 2)),
make_test<string, int, int>("should get \"Sta.\" for group id 1 and id 3", "Sta.", make_tuple(1, 3)),
make_test<string, int, int>("should get \"Cha.\" for group id 2 and id 1", "Cha.", make_tuple(2, 1)),
make_test<string, int, int>("should get \"Man.\" for group id 2 and id 2", "Man.", make_tuple(2, 2)),
make_test<string, int, int>("should get \"App.\" for group id 2 and id 3", "App.", make_tuple(2, 3)),
make_test<string, int, int>("should get \"Int.\" for group id 3 and id 1", "Int.", make_tuple(3, 1)),
make_test<string, int, int>("should get \"Per.\" for group id 3 and id 2", "Per.", make_tuple(3, 2)),
make_test<string, int, int>("should get \"Wits\" for group id 3 and id 3", "Wits", make_tuple(3, 3)),
make_test<string, int, int>("should get \"\" for an invalid group id 0", "", make_tuple(0, 1)),
make_test<string, int, int>("should get \"\" for an invalid id 0", "", make_tuple(1, 0)),
make_test<string, int, int>("should get \"\" for invalid group id 4", "", make_tuple(4, 1)),
make_test<string, int, int>("should get \"\" for an invalid id 4", "", make_tuple(1, 4)),
})));
}
TestResults test_GetPhysicalAttributeLabel() {
return execute_suite<string, int>(
make_test_suite("SBF::GetPhysicalAttributeLabel",
GetPhysicalAttributeLabel,
vector<TestTuple<string, int>>({
make_test<string, int>("should get \"Strength\" for id 1", "Strength", make_tuple(1)),
make_test<string, int>("should get \"Dexterity\" for id 2", "Dexterity", make_tuple(2)),
make_test<string, int>("should get \"Stamina\" for id 3", "Stamina", make_tuple(3)),
make_test<string, int>("should get \"\" for invalid id 0", "", make_tuple(0)),
make_test<string, int>("should get \"\" for invalid id 4", "", make_tuple(4)),
})));
}
TestResults test_GetPhysicalAttributeLabelAbbreviation() {
return execute_suite<string, int>(
make_test_suite("SBF::GetPhysicalAttributeLabelAbbreviation",
GetPhysicalAttributeLabelAbbreviation,
vector<TestTuple<string, int>>({
make_test<string, int>("should get \"Str.\" for id 1", "Str.", make_tuple(1)),
make_test<string, int>("should get \"Dex.\" for id 2", "Dex.", make_tuple(2)),
make_test<string, int>("should get \"Sta.\" for id 3", "Sta.", make_tuple(3)),
make_test<string, int>("should get \"\" for invalid id 0", "", make_tuple(0)),
make_test<string, int>("should get \"\" for invalid id 4", "", make_tuple(4)),
})));
}
TestResults test_GetSocialAttributeLabel() {
return execute_suite<string, int>(
make_test_suite("SBF::GetSocialAttributeLabel",
GetSocialAttributeLabel,
vector<TestTuple<string, int>>({
make_test<string, int>("should get \"Charisma\" for id 1", "Charisma", make_tuple(1)),
make_test<string, int>("should get \"Manipulation\" for id 2", "Manipulation", make_tuple(2)),
make_test<string, int>("should get \"Appearance\" for id 3", "Appearance", make_tuple(3)),
make_test<string, int>("should get \"\" for invalid id 0", "", make_tuple(0)),
make_test<string, int>("should get \"\" for invalid id 4", "", make_tuple(4)),
})));
}
TestResults test_GetSocialAttributeLabelAbbreviation() {
return execute_suite<string, int>(
make_test_suite("SBF::GetSocialAttributeLabelAbbreviation",
GetSocialAttributeLabelAbbreviation,
vector<TestTuple<string, int>>({
make_test<string, int>("should get \"Cha.\" for id 1", "Cha.", make_tuple(1)),
make_test<string, int>("should get \"Man.\" for id 2", "Man.", make_tuple(2)),
make_test<string, int>("should get \"App.\" for id 3", "App.", make_tuple(3)),
make_test<string, int>("should get \"\" for invalid id 0", "", make_tuple(0)),
make_test<string, int>("should get \"\" for invalid id 4", "", make_tuple(4)),
})));
}
TestResults test_GetMentalAttributeLabel() {
return execute_suite<string, int>(
make_test_suite("SBF::GetMentalAttributeLabel",
GetMentalAttributeLabel,
vector<TestTuple<string, int>>({
make_test<string, int>("should get \"Intelligence\" for id 1", "Intelligence", make_tuple(1)),
make_test<string, int>("should get \"Perception\" for id 2", "Perception", make_tuple(2)),
make_test<string, int>("should get \"Wits\" for id 3", "Wits", make_tuple(3)),
make_test<string, int>("should get \"\" for invalid id 0", "", make_tuple(0)),
make_test<string, int>("should get \"\" for invalid id 4", "", make_tuple(4)),
})));
}
TestResults test_GetMentalAttributeLabelAbbreviation() {
return execute_suite<string, int>(
make_test_suite("SBF::GetMentalAttributeLabelAbbreviation",
GetMentalAttributeLabelAbbreviation,
vector<TestTuple<string, int>>({
make_test<string, int>("should get \"Int.\" for id 1", "Int.", make_tuple(1)),
make_test<string, int>("should get \"Per.\" for id 2", "Per.", make_tuple(2)),
make_test<string, int>("should get \"Wits\" for id 3", "Wits", make_tuple(3)),
make_test<string, int>("should get \"\" for invalid id 0", "", make_tuple(0)),
make_test<string, int>("should get \"\" for invalid id 4", "", make_tuple(4)),
})));
}
TestResults test_GetNumAttributesInGroup() {
return execute_suite<int, int>(
make_test_suite("SBF::GetNumAttributesInGroup",
GetNumAttributesInGroup,
vector<TestTuple<int, int>>({
make_test<int, int>("should get 0 for invalid group 0", 0, make_tuple(0)),
make_test<int, int>("should get 3 for group 1 kAttributeGroupPhysicalId", 3, make_tuple(1)),
make_test<int, int>("should get 3 for group 2 kAttributeGropuSocialId", 3, make_tuple(2)),
make_test<int, int>("should get 3 for group 3 kAttributeGroupMentalId", 3, make_tuple(3)),
make_test<int, int>("should get 0 for invalid group 4", 0, make_tuple(4)),
})));
}
TestResults test_FillAttributeGroupLabels() {
return execute_suite<string>(make_test_suite(
"SBF::FillAttributeGroupLabels",
[]() -> string {
ostringstream error_message;
vector<string> expected = {"Physical", "Social", "Mental"};
vector<string> actual = {"This should be removed."};
FillAttributeGroupLabels(actual);
compare(error_message, expected, actual);
string error = error_message.str();
if (error.size() > 0) {
return error;
}
return "no errors";
},
vector<TestTuple<string>>({
make_test<string>("should fill attribute group labels", "no errors", make_tuple()),
})));
}
// void FillAttributeLabelInGroup(std::vector<std::string> attributeLabels);
TestResults test_FillAttributeLabelsInGroup() {
return execute_suite<string, int, vector<string>>(make_test_suite(
"SBF::FillAttributeLabelsInGroup",
[](int id, vector<string> expected) -> string {
ostringstream error_message;
vector<string> actual = {"This should be removed."};
FillAttributeLabelsInGroup(actual, id);
compare(error_message, expected, actual);
string error = error_message.str();
if (error.size() > 0) {
return error;
}
return "no errors";
},
vector<TestTuple<string, int, vector<string>>>({
make_test<string, int, vector<string>>(
"should fill an empty list for invalid group 0", "no errors", make_tuple(0, vector<string>({}))),
make_test<string, int, vector<string>>(
"should fill physical attribute labels for group 1 kAttributeGropuPhysicalId",
"no errors",
make_tuple(1, vector<string>({"Strength", "Dexterity", "Stamina"}))),
make_test<string, int, vector<string>>(
"should fill social attribute labels for group 2 kAttributeGroupSocialId",
"no errors",
make_tuple(2, vector<string>({"Charisma", "Manipulation", "Appearance"}))),
make_test<string, int, vector<string>>(
"should should fill mental attribute labels for group 3 kAttributeGroupMentalId",
"no errors",
make_tuple(3, vector<string>({"Intelligence", "Perception", "Wits"}))),
make_test<string, int, vector<string>>(
"should fill an empty list for invalid group 4", "no errors", make_tuple(4, vector<string>({}))),
})));
}
TestResults test_FillPhysicalAttributeLabels() {
return execute_suite<string>(make_test_suite(
"SBF::FillPhysicalAttributeLabels",
[]() -> string {
ostringstream error_message;
vector<string> expected = {"Strength", "Dexterity", "Stamina"};
vector<string> actual = {"This should be removed."};
FillPhysicalAttributeLabels(actual);
compare(error_message, expected, actual);
string error = error_message.str();
if (error.size() > 0) {
return error;
}
return "no errors";
},
vector<TestTuple<string>>({
make_test<string>("should fill physical attribute labels", "no errors", make_tuple()),
})));
}
// void FillSocialAttributeLabels(std::vector<std::string> socialAttributeLabels);
TestResults test_FillSocialAttributeLabels() {
return execute_suite<string>(make_test_suite(
"SBF::FillSocialAttributeLabels",
[]() -> string {
ostringstream error_message;
vector<string> expected = {"Charisma", "Manipulation", "Appearance"};
vector<string> actual = {"This should be removed."};
FillSocialAttributeLabels(actual);
compare(error_message, expected, actual);
string error = error_message.str();
if (error.size() > 0) {
return error;
}
return "no errors";
},
vector<TestTuple<string>>({
make_test<string>("should fill social attribute labels", "no errors", make_tuple()),
})));
}
// void FillMentalAttributeLabels(std::vector<std::string> mentalAttributeLabels);
TestResults test_FillMentalAttributeLabels() {
return execute_suite<string>(make_test_suite(
"SBF::FillMentalAttributeLabels",
[]() -> string {
ostringstream error_message;
vector<string> expected = {"Intelligence", "Perception", "Wits"};
vector<string> actual = {"This should be removed."};
FillMentalAttributeLabels(actual);
compare(error_message, expected, actual);
string error = error_message.str();
if (error.size() > 0) {
return error;
}
return "no errors";
},
vector<TestTuple<string>>({
make_test<string>("should fill mental attribute labels", "no errors", make_tuple()),
})));
}
int main(int argc, char* argv[]) {
TestResults results;
results += test_GetAttributeGroupLabel();
results += test_GetAttributeLabel();
results += test_GetAttributeLabelAbbreviation();
results += test_GetNumAttributesInGroup();
results += test_GetPhysicalAttributeLabel();
results += test_GetPhysicalAttributeLabelAbbreviation();
results += test_GetSocialAttributeLabel();
results += test_GetSocialAttributeLabelAbbreviation();
results += test_GetMentalAttributeLabel();
results += test_GetMentalAttributeLabelAbbreviation();
results += test_FillAttributeGroupLabels();
results += test_FillAttributeLabelsInGroup();
results += test_FillPhysicalAttributeLabels();
results += test_FillSocialAttributeLabels();
results += test_FillMentalAttributeLabels();
PrintResults(cout, results);
return results.failed() + results.errors();
}

339
sbf-cpp/BUILD Normal file
View File

@@ -0,0 +1,339 @@
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
cc_library(
name = "tinytest",
srcs = ["test.cpp"],
hdrs = ["test.h"],
)
cc_library(
name = "abilities",
srcs = ["Abilities.cpp"],
hdrs = ["Abilities.h"],
)
cc_test(
name = "abilities_test",
size = "small",
srcs = ["Abilities_test.cpp"],
deps = [
":abilities",
":tinytest",
],
)
cc_library(
name = "archetypes",
srcs = ["Archetypes.cpp"],
hdrs = ["Archetypes.h"],
)
cc_test(
name = "archetypes_test",
size = "small",
srcs = ["Archetypes_test.cpp"],
deps = [
":archetypes",
":tinytest",
],
)
cc_library(
name = "attributes",
srcs = ["Attributes.cpp"],
hdrs = ["Attributes.h"],
)
cc_test(
name = "attributes_test",
size = "small",
srcs = ["Attributes_test.cpp"],
deps = [
":attributes",
":tinytest",
],
)
cc_library(
name = "backgrounds",
srcs = ["Backgrounds.cpp"],
hdrs = ["Backgrounds.h"],
)
cc_test(
name = "backgrounds_test",
size = "small",
srcs = ["Backgrounds_test.cpp"],
deps = [
":backgrounds",
":tinytest",
],
)
cc_library(
name = "character",
srcs = ["Character.cpp"],
hdrs = ["Character.h"],
deps = [
":abilities",
":attributes",
":backgrounds",
":clans",
":colors",
":derangements",
":disciplines",
":ranks",
":virtues",
],
)
cc_test(
name = "character_test",
size = "small",
srcs = ["Character_test.cpp"],
deps = [
":character",
":tinytest",
],
)
cc_library(
name = "clans",
srcs = ["Clans.cpp"],
hdrs = ["Clans.h"],
)
cc_test(
name = "clans_test",
size = "small",
srcs = ["Clans_test.cpp"],
deps = [
":clans",
":tinytest",
],
)
cc_library(
name = "colors",
srcs = ["Colors.cpp"],
hdrs = ["Colors.h"],
)
cc_test(
name = "colors_test",
size = "small",
srcs = ["Colors_test.cpp"],
deps = [
":colors",
":tinytest",
],
)
cc_library(
name = "derangements",
srcs = ["Derangements.cpp"],
hdrs = ["Derangements.h"],
deps = [":colors"],
)
cc_test(
name = "derangements_test",
size = "small",
srcs = ["Derangements_test.cpp"],
deps = [
":derangements",
":tinytest",
],
)
cc_library(
name = "disciplines",
srcs = ["Disciplines.cpp"],
hdrs = ["Disciplines.h"],
)
cc_test(
name = "disciplines_test",
size = "small",
srcs = ["Disciplines_test.cpp"],
deps = [
":disciplines",
":tinytest",
],
)
cc_library(
name = "freebies",
srcs = ["Freebies.cpp"],
hdrs = ["Freebies.h"],
)
cc_test(
name = "freebies_test",
size = "small",
srcs = ["Freebies_test.cpp"],
deps = [
":freebies",
":tinytest",
],
)
cc_library(
name = "genders",
srcs = ["Genders.cpp"],
hdrs = ["Genders.h"],
)
cc_test(
name = "genders_test",
size = "small",
srcs = ["Genders_test.cpp"],
deps = [
":genders",
":tinytest",
],
)
cc_library(
name = "character_generator",
srcs = ["CharacterGenerator.cpp"],
hdrs = ["CharacterGenerator.h"],
deps = [
"archetypes",
"character",
"freebies",
"genders",
"menus",
],
)
cc_test(
name = "character_generator_test",
size = "small",
srcs = ["CharacterGenerator_test.cpp"],
deps = [
":character_generator",
":tinytest",
],
)
# cc_library(
# name = "main",
# srcs = ["main.cpp"],
# hdrs = ["main.h"],
# )
# cc_test(
# name = "main_test",
# srcs = ["main_test.cpp"],
# deps = [
# ":main",
# ":tinytest",
# ],
# )
cc_library(
name = "menus",
srcs = ["Menus.cpp"],
hdrs = ["Menus.h"],
deps = [
":colors",
":random",
":utils",
],
)
cc_test(
name = "menus_test",
size = "small",
srcs = ["Menus_test.cpp"],
deps = [
":menus",
":tinytest",
],
)
cc_library(
name = "random",
srcs = ["Random.cpp"],
hdrs = ["Random.h"],
)
cc_test(
name = "random_test",
size = "small",
srcs = ["Random_test.cpp"],
deps = [
":random",
":tinytest",
],
)
cc_library(
name = "ranks",
srcs = ["Ranks.cpp"],
hdrs = ["Ranks.h"],
)
cc_test(
name = "ranks_test",
size = "small",
srcs = ["Ranks_test.cpp"],
deps = [
":ranks",
":tinytest",
],
)
cc_binary(
name = "sbf",
srcs = [
"sbf.cpp",
"sbf.h",
],
deps = [
":character_generator",
":menus",
],
)
# cc_test(
# name = "sbf_test",
# srcs = ["sbf_test.cpp"],
# deps = [
# ":sbf",
# ":tinytest",
# ],
# )
cc_library(
name = "utils",
srcs = ["Utils.cpp"],
hdrs = ["Utils.h"],
)
cc_test(
name = "utils_test",
size = "small",
srcs = ["Utils_test.cpp"],
deps = [
":tinytest",
":utils",
],
)
cc_library(
name = "virtues",
srcs = ["Virtues.cpp"],
hdrs = ["Virtues.h"],
)
cc_test(
name = "virtues_test",
size = "small",
srcs = ["Virtues_test.cpp"],
deps = [
":tinytest",
":virtues",
],
)

55
sbf-cpp/Backgrounds.cpp Normal file
View File

@@ -0,0 +1,55 @@
#include "Backgrounds.h"
#include <string>
#include <vector>
namespace SBF {
namespace {
using std::string;
using std::vector;
} // End namespace
const std::string kBackgroundAlliesLabel = "Allies";
const std::string kBackgroundContactsLabel = "Contacts";
const std::string kBackgroundFameLabel = "Fame";
const std::string kBackgroundGenerationLabel = "Generation";
const std::string kBackgroundHerdLabel = "Herd";
const std::string kBackgroundInfluenceLabel = "Influence";
const std::string kBackgroundMentorLabel = "Mentor";
const std::string kBackgroundResourcesLabel = "Resources";
const std::string kBackgroundRetainersLabel = "Retainers";
const std::string kBackgroundStatusLabel = "Status";
const std::string kBackgroundLabels[] = {
"",
kBackgroundAlliesLabel,
kBackgroundContactsLabel,
kBackgroundFameLabel,
kBackgroundGenerationLabel,
kBackgroundHerdLabel,
kBackgroundInfluenceLabel,
kBackgroundMentorLabel,
kBackgroundResourcesLabel,
kBackgroundRetainersLabel,
kBackgroundStatusLabel,
};
string GetBackgroundLabel(int backgroundId) {
if (backgroundId > 0 && backgroundId <= kBackgroundsCount) {
return kBackgroundLabels[backgroundId];
}
return "";
}
void FillBackgroundLabels(vector<string>& labels) {
labels.clear();
for (int id = 1; id <= kBackgroundsCount; id++) {
labels.push_back(kBackgroundLabels[id]);
}
}
vector<string> GetBackgroundLabels() {
vector<string> labels;
FillBackgroundLabels(labels);
return labels;
}
} // End namespace SBF

45
sbf-cpp/Backgrounds.h Normal file
View File

@@ -0,0 +1,45 @@
#ifndef BACKGROUNDS_H__
#define BACKGROUNDS_H__
/***************************************************************************************
* @file Backgrounds.h
*
* @brief Defines constants and funcions for working with backgrounds.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
#include <string>
#include <vector>
/** \addtogroup Backgrounds
* @{
*/
namespace SBF {
const int kBackgroundAlliesId = 1;
const int kBackgroundContactsId = 2;
const int kBackgroundFameId = 3;
const int kBackgroundGenerationId = 4;
const int kBackgroundHerdId = 5;
const int kBackgroundInfluenceId = 6;
const int kBackgroundMentorId = 7;
const int kBackgroundResourcesId = 8;
const int kBackgroundRetainersId = 9;
const int kBackgroundStatusId = 10;
const int kBackgroundsCount = 10;
/// @brief Gets the label for a background with a specific id.
/// @param background_id The id of the background to get the label for.
/// @return The label for the specified background or an empty string if background_id is invalid.
std::string GetBackgroundLabel(int background_id);
/// @brief Fills the provided vector with all valid background labels.
/// @param background_labels The vector to fill. It will be emptied first.
void FillBackgroundLabels(std::vector<std::string>& background_labels);
/// @brief Gets a vector of all the valid background labels.
/// @return The filled vector with index = background_id -1.
std::vector<std::string> GetBackgroundLabels();
} // End namespace SBF
/** @}*/
#endif // End !defined BACKGROUNDS_H__

View File

@@ -0,0 +1,85 @@
#include "Backgrounds.h"
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#include "test.h"
namespace {
using namespace SBF;
using namespace Test;
using namespace std;
} // namespace
TestResults test_GetBackgroundLabel() {
return execute_suite<string, int>(make_test_suite(
"SBF::GetBackgroundLabel",
GetBackgroundLabel,
vector<TestTuple<string, int>>({
make_test<string, int>("should get \"\" for invalid background 0", "", make_tuple(0)),
make_test<string, int>(
"should get \"Allies\" for background 1 kBackgroundAlliesLabel", "Allies", make_tuple(1)),
make_test<string, int>(
"should get \"Contacts\" for background 2 kBackgroundContactsLabel", "Contacts", make_tuple(2)),
make_test<string, int>("should get \"Fame\" for background 3 kBackgroundFameLabel", "Fame", make_tuple(3)),
make_test<string, int>(
"should get \"Generation\" for background 4 kBackgroundGenerationLabel", "Generation", make_tuple(4)),
make_test<string, int>("should get \"Herd\" for background 5 kBackgroundHerdLabel", "Herd", make_tuple(5)),
make_test<string, int>(
"should get \"Influence\" for background 6 kBackgroundInfluenceLabel", "Influence", make_tuple(6)),
make_test<string, int>(
"should get \"Mentor\" for background 7 kBackgroundMentorLabel", "Mentor", make_tuple(7)),
make_test<string, int>(
"should get \"Resources\" for background 8 kBackgroundResourcesLabel", "Resources", make_tuple(8)),
make_test<string, int>(
"should get \"Retainers\" for background 9 kBackgroundRetainersLabel", "Retainers", make_tuple(9)),
make_test<string, int>(
"should get \"Status\" for background 10 kBackgroundStatusLabel", "Status", make_tuple(10)),
make_test<string, int>("should get \"\" for invalid background 11", "", make_tuple(11)),
})));
}
TestResults test_FillBackgroundLabels() {
return execute_suite<string>(make_test_suite(
"SBF::FillBackgroundLabels",
[]() -> string {
ostringstream error_message;
vector<string> expected = {"Allies",
"Contacts",
"Fame",
"Generation",
"Herd",
"Influence",
"Mentor",
"Resources",
"Retainers",
"Status"};
vector<string> actual = {"This should be removed."};
FillBackgroundLabels(actual);
compare(error_message, expected, actual);
string error = error_message.str();
if (error.size() > 0) {
return error;
}
return "no errors";
},
vector<TestTuple<string>>({
make_test<string>("should fill background labels", "no errors", make_tuple()),
})
));
}
int main(int argc, char* argv[]) {
TestResults results;
results += test_GetBackgroundLabel();
results += test_FillBackgroundLabels();
PrintResults(cout, results);
return results.failed() + results.errors();
}

768
sbf-cpp/Character.cpp Normal file
View File

@@ -0,0 +1,768 @@
#include "Character.h"
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include "Attributes.h"
#include "sbf-cpp/Abilities.h"
#include "sbf-cpp/Disciplines.h"
namespace SBF {
namespace {
using std::ostringstream;
using std::string;
using std::vector;
} // namespace
const int kBackgroundPoints = 5;
const int kInitialGeneration = 13;
const int kDisciplinePoints = 3;
CharacterType::CharacterType() {
// Scalars
name = "";
player = "";
chronicle = "";
haven = "";
concept = "";
age = "";
genderId = 0;
clan_id = 0;
natureId = 0;
demeanorId = 0;
generation = 3;
road_name = "";
road_value = 0;
willpower = 0;
bloodPool = 0;
derangementId = -1;
freebie_points = 15;
// Virtues
selfControl = 1;
courage = 1;
conscience = 1;
// Arrays/Objects
// Abilities (Talents/Skills/Knowledges)
for (int group_id = 1; group_id <= AbilityGroup::GetCount(); group_id++) {
const AbilityGroup group = AbilityGroup::FromId(group_id);
const int num_abilities = group.GetAbilityCount();
for (int id = 1; id <= num_abilities; id++) {
SetAbilityValue(group_id, id, 0);
}
}
// Attributes
for (int group_id = 1; group_id <= GetNumAttributeGroups(); group_id++) {
const int numAttributes = GetNumAttributesInGroup(group_id);
for (int id = 1; id <= numAttributes; id++) {
SetAttributeValue(group_id, id, 1);
}
}
// Backgrounds
for (int id = 0; id <= kBackgroundsCount; id++) {
SetBackgroundValue(id, 0);
}
// Disciplines
for (int id = 0; id <= GetNumDisciplines(); id++) {
SetDisciplineValue(id, 0);
}
}
void CharacterType::SetDisciplineValue(int id, int value) {
switch (id) {
case kDisciplineAnimalismId:
discipline_animalism = value;
break;
case kDisciplineAuspexId:
discipline_auspex = value;
break;
case kDisciplineBardoId:
discipline_bardo = value;
break;
case kDisciplineCelerityId:
discipline_celerity = value;
break;
case kDisciplineChimestryId:
discipline_chimestry = value;
break;
case kDisciplineDementationId:
discipline_dementation = value;
break;
case kDisciplineDominateId:
discipline_dominate = value;
break;
case kDisciplineFortitudeId:
discipline_fortitude = value;
break;
case kDisciplineMelpomineeId:
discipline_melpominee = value;
break;
case kDisciplineMortisId:
discipline_mortis = value;
break;
case kDisciplineMytherceriaId:
discipline_mytherceria = value;
break;
case kDisciplineNecromancyId:
discipline_necromancy = value;
break;
case kDisciplineObeahId:
discipline_obeah = value;
break;
case kDisciplineObfuscateId:
discipline_obfuscate = value;
break;
case kDisciplineObtenebrationId:
discipline_obtenebration = value;
break;
case kDisciplinePotenceId:
discipline_potence = value;
break;
case kDisciplinePresenceId:
discipline_presence = value;
break;
case kDisciplineProteanId:
discipline_protean = value;
break;
case kDisciplineQuietusId:
discipline_quietus = value;
break;
case kDisciplineSerpentisId:
discipline_serpentis = value;
break;
case kDisciplineSpiritusId:
discipline_spiritus = value;
break;
case kDisciplineThanantosisId:
discipline_thanantosis = value;
break;
case kDisciplineThaumaturgyId:
discipline_thaumaturgy = value;
break;
case kDisciplineVicissitudeId:
discipline_vicissitude = value;
break;
}
}
int CharacterType::GetDisciplineValue(int id) const {
switch (id) {
case kDisciplineAnimalismId:
return discipline_animalism;
case kDisciplineAuspexId:
return discipline_auspex;
case kDisciplineBardoId:
return discipline_bardo;
case kDisciplineCelerityId:
return discipline_celerity;
case kDisciplineChimestryId:
return discipline_chimestry;
case kDisciplineDementationId:
return discipline_dementation;
case kDisciplineDominateId:
return discipline_dominate;
case kDisciplineFortitudeId:
return discipline_fortitude;
case kDisciplineMelpomineeId:
return discipline_melpominee;
case kDisciplineMortisId:
return discipline_mortis;
case kDisciplineMytherceriaId:
return discipline_mytherceria;
case kDisciplineNecromancyId:
return discipline_necromancy;
case kDisciplineObeahId:
return discipline_obeah;
case kDisciplineObfuscateId:
return discipline_obfuscate;
case kDisciplineObtenebrationId:
return discipline_obtenebration;
case kDisciplinePotenceId:
return discipline_potence;
case kDisciplinePresenceId:
return discipline_presence;
case kDisciplineProteanId:
return discipline_protean;
case kDisciplineQuietusId:
return discipline_quietus;
case kDisciplineSerpentisId:
return discipline_serpentis;
case kDisciplineSpiritusId:
return discipline_spiritus;
case kDisciplineThanantosisId:
return discipline_thanantosis;
case kDisciplineThaumaturgyId:
return discipline_thaumaturgy;
case kDisciplineVicissitudeId:
return discipline_vicissitude;
default:
return 0;
}
}
int GetDisciplinePoints() {
return kDisciplinePoints;
}
void CharacterType::FillDisciplineValues(std::vector<int>& values) const {
// TODO: This method sucks, but was needed in QBasic.
values.clear();
values.push_back(0); // To pad the indexes.
for (int id = 1; id <= GetNumDisciplines(); id++) {
values.push_back(GetDisciplineValue(id));
}
}
vector<int> CharacterType::GetDisciplineValues() const {
vector<int> values;
FillDisciplineValues(values);
return values;
}
int GetVirtuePoints() {
return kVirtuePoints;
}
void CharacterType::SetVirtueValue(int id, int value) {
switch (id) {
case kVirtueSelfControlId:
selfControl = value;
break;
case kVirtueCourageId:
courage = value;
break;
case kVirtueConscienceId:
conscience = value;
break;
}
}
int CharacterType::GetVirtueValue(int id) const {
switch (id) {
case kVirtueSelfControlId:
return selfControl;
case kVirtueCourageId:
return courage;
case kVirtueConscienceId:
return conscience;
default:
return 0;
}
}
void CharacterType::FillVirtueValues(std::vector<int>& values) const {
values.clear();
for (int id = 0; id <= GetNumVirtues(); id++) {
values.push_back(GetVirtueValue(id));
}
}
vector<int> CharacterType::GetVirtueValues() const {
vector<int> values;
FillVirtueValues(values);
return values;
}
void CharacterType::FillAttributeValues(std::vector<int>& values, int group_id) const {
// TODO: This method sucks, but was needed in QBasic.
const int numAttributes = GetNumAttributesInGroup(group_id);
values.clear();
for (int id = 1; id <= numAttributes; id++) {
values.push_back(GetAttributeValue(group_id, id));
}
}
vector<int> CharacterType::GetAttributeValuesInGroup(int group_id) const {
vector<int> values;
FillAttributeValues(values, group_id);
return values;
}
void CharacterType::SetPhysicalAttributeValue(int id, int value) {
switch (id) {
case kPhysicalAttributeDexterityId:
attr_dexterity = value;
break;
case kPhysicalAttributeStaminaId:
attr_stamina = value;
break;
case kPhysicalAttributeStrengthId:
attr_strength = value;
break;
}
}
void CharacterType::SetSocialAttributeValue(int id, int value) {
switch (id) {
case kSocialAttributeAppearanceId:
attr_appearance = value;
break;
case kSocialAttributeCharismaId:
attr_charisma = value;
break;
case kSocialAttributeManipulationId:
attr_manipulation = value;
break;
}
}
void CharacterType::SetMentalAttributeValue(int id, int value) {
switch (id) {
case kMentalAttributeIntelligenceId:
attr_intelligence = value;
break;
case kMentalAttributePerceptionId:
attr_perception = value;
break;
case kMentalAttributeWitsId:
attr_wits = value;
break;
}
}
void CharacterType::SetAttributeValue(int group_id, int id, int value) {
switch (group_id) {
case kAttributeGroupPhysicalId:
SetPhysicalAttributeValue(id, value);
break;
case kAttributeGroupSocialId:
SetSocialAttributeValue(id, value);
break;
case kAttributeGroupMentalId:
SetMentalAttributeValue(id, value);
break;
}
}
int CharacterType::GetPhysicalAttributeValue(int id) const {
switch (id) {
case kPhysicalAttributeDexterityId:
return attr_dexterity;
case kPhysicalAttributeStaminaId:
return attr_stamina;
case kPhysicalAttributeStrengthId:
return attr_strength;
}
return 0;
}
int CharacterType::GetSocialAttributeValue(int id) const {
switch (id) {
case kSocialAttributeAppearanceId:
return attr_appearance;
case kSocialAttributeCharismaId:
return attr_charisma;
case kSocialAttributeManipulationId:
return attr_manipulation;
}
return 0;
}
int CharacterType::GetMentalAttributeValue(int id) const {
switch (id) {
case kMentalAttributeIntelligenceId:
return attr_intelligence;
case kMentalAttributePerceptionId:
return attr_perception;
case kMentalAttributeWitsId:
return attr_wits;
}
return 0;
}
int CharacterType::GetAttributeValue(int group_id, int id) const {
switch (group_id) {
case kAttributeGroupPhysicalId:
return GetPhysicalAttributeValue(id);
case kAttributeGroupSocialId:
return GetSocialAttributeValue(id);
case kAttributeGroupMentalId:
return GetMentalAttributeValue(id);
}
return 0;
}
int GetAttributePointsForRank(int id) {
switch (id) {
case kRankPrimaryId:
return 7;
case kRankSecondaryId:
return 5;
case kRankTertiaryId:
return 3;
}
return 0;
}
void CharacterType::SetAbilityValue(int group_id, int id, int value) {
switch (group_id) {
case kAbilityGroupTalentsId:
SetTalentValue(id, value);
break;
case kAbilityGroupSkillsId:
SetSkillValue(id, value);
break;
case kAbilityGroupKnowledgesId:
SetKnowledgeValue(id, value);
break;
}
}
int CharacterType::GetAbilityValue(int group_id, int id) const {
switch (group_id) {
case kAbilityGroupTalentsId:
return GetTalentValue(id);
case kAbilityGroupKnowledgesId:
return GetKnowledgeValue(id);
case kAbilityGroupSkillsId:
return GetSkillValue(id);
}
return 0;
}
int GetAbilityPointsForRank(int id) {
switch (id) {
case kRankPrimaryId:
return 13;
case kRankSecondaryId:
return 9;
case kRankTertiaryId:
return 5;
}
return 0;
}
void CharacterType::FillAbilityValues(std::vector<int>& values, int group_id) const {
const AbilityGroup group = AbilityGroup::FromId(group_id);
int num_abilities = group.GetAbilityCount();
values.clear();
for (int id = 0; id <= num_abilities; id++) {
values[id] = GetAbilityValue(group_id, id);
}
}
void CharacterType::SetTalentValue(int id, int value) {
switch (id) {
case kTalentActingId:
talent_acting = value;
break;
case kTalentAlertnessId:
talent_alertness = value;
break;
case kTalentAthleticsId:
talent_athletics = value;
break;
case kTalentBrawlId:
talent_brawl = value;
break;
case kTalentDodgeId:
talent_dodge = value;
break;
case kTalentEmpathyId:
talent_empathy = value;
break;
case kTalentIntimidationId:
talent_intimidation = value;
break;
case kTalentLeadershipId:
talent_leadership = value;
break;
case kTalentStreetwiseId:
talent_streetwise = value;
break;
case kTalentSubterfugeId:
talent_subterfuge = value;
break;
}
}
int CharacterType::GetTalentValue(int id) const {
switch (id) {
case kTalentActingId:
return talent_acting;
case kTalentAlertnessId:
return talent_alertness;
case kTalentAthleticsId:
return talent_athletics;
case kTalentBrawlId:
return talent_brawl;
case kTalentDodgeId:
return talent_dodge;
case kTalentEmpathyId:
return talent_empathy;
case kTalentIntimidationId:
return talent_intimidation;
case kTalentLeadershipId:
return talent_leadership;
case kTalentStreetwiseId:
return talent_streetwise;
case kTalentSubterfugeId:
return talent_subterfuge;
}
return 0;
}
void CharacterType::SetSkillValue(int id, int value) {
switch (id) {
case kSkillAnimalKenId:
skill_animalKen = value;
break;
case kSkillDriveId:
skill_drive = value;
break;
case kSkillEtiquetteId:
skill_etiquette = value;
break;
case kSkillFirearmsId:
skill_firearms = value;
break;
case kSkillMeleeId:
skill_melee = value;
break;
case kSkillMusicId:
skill_music = value;
break;
case kSkillRepairId:
skill_repair = value;
break;
case kSkillSecurityId:
skill_security = value;
break;
case kSkillStealthId:
skill_stealth = value;
break;
case kSkillSurvivalId:
skill_survival = value;
break;
}
}
int CharacterType::GetSkillValue(int id) const {
switch (id) {
case kSkillAnimalKenId:
return skill_animalKen;
case kSkillDriveId:
return skill_drive;
case kSkillEtiquetteId:
return skill_etiquette;
case kSkillFirearmsId:
return skill_firearms;
case kSkillMeleeId:
return skill_melee;
case kSkillMusicId:
return skill_music;
case kSkillRepairId:
return skill_repair;
case kSkillSecurityId:
return skill_security;
case kSkillStealthId:
return skill_stealth;
case kSkillSurvivalId:
return skill_survival;
}
return 0;
}
void CharacterType::SetKnowledgeValue(int id, int value) {
switch (id) {
case kKnowledgeBureaucracyId:
knowledge_bureaucracy = value;
break;
case kKnowledgeComputerId:
knowledge_computer = value;
break;
case kKnowledgeFinanceId:
knowledge_finance = value;
break;
case kKnowledgeInvestigationId:
knowledge_investigation = value;
break;
case kKnowledgeLawId:
knowledge_law = value;
break;
case kKnowledgeLinguisticsId:
knowledge_linguistics = value;
break;
case kKnowledgeMedicineId:
knowledge_medicine = value;
break;
case kKnowledgeOccultId:
knowledge_occult = value;
break;
case kKnowledgePoliticsId:
knowledge_politics = value;
break;
case kKnowledgeScienceId:
knowledge_science = value;
break;
}
}
int CharacterType::GetKnowledgeValue(int id) const {
switch (id) {
case kKnowledgeBureaucracyId:
return knowledge_bureaucracy;
case kKnowledgeComputerId:
return knowledge_computer;
case kKnowledgeFinanceId:
return knowledge_finance;
case kKnowledgeInvestigationId:
return knowledge_investigation;
case kKnowledgeLawId:
return knowledge_law;
case kKnowledgeLinguisticsId:
return knowledge_linguistics;
case kKnowledgeMedicineId:
return knowledge_medicine;
case kKnowledgeOccultId:
return knowledge_occult;
case kKnowledgePoliticsId:
return knowledge_politics;
case kKnowledgeScienceId:
return knowledge_science;
}
return 0;
}
void CharacterType::SetBackgroundValue(int id, int value) {
switch (id) {
case kBackgroundAlliesId:
background_allies = value;
break;
case kBackgroundContactsId:
background_contacts = value;
break;
case kBackgroundFameId:
background_fame = value;
break;
case kBackgroundGenerationId:
background_generation = value;
break;
case kBackgroundHerdId:
background_herd = value;
break;
case kBackgroundInfluenceId:
background_influence = value;
break;
case kBackgroundMentorId:
background_mentor = value;
break;
case kBackgroundResourcesId:
background_resources = value;
break;
case kBackgroundRetainersId:
background_retainers = value;
break;
case kBackgroundStatusId:
background_status = value;
break;
}
}
int CharacterType::GetBackgroundValue(int id) const {
switch (id) {
case kBackgroundAlliesId:
return background_allies;
case kBackgroundContactsId:
return background_contacts;
case kBackgroundFameId:
return background_fame;
case kBackgroundGenerationId:
return background_generation;
case kBackgroundHerdId:
return background_herd;
case kBackgroundInfluenceId:
return background_influence;
case kBackgroundMentorId:
return background_mentor;
case kBackgroundResourcesId:
return background_resources;
case kBackgroundRetainersId:
return background_retainers;
case kBackgroundStatusId:
return background_status;
}
return 0;
}
void CharacterType::FillBackgroundValues(std::vector<int>& values) const {
values.clear();
for (int id = 1; id <= kBackgroundsCount; id++) {
values[id] = GetBackgroundValue(id);
}
}
vector<int> CharacterType::GetBackgroundValues() const {
vector<int> values;
FillBackgroundValues(values);
return values;
}
int GetBackgroundPoints() {
return kBackgroundPoints;
}
void NewDerangement(DerangementType& derangement, int id, string label, int textColor, string description) {
derangement.id = id;
derangement.label = label;
derangement.textColor = textColor;
derangement.description = description;
}
string CharacterType::GetAllDerangementsLine() const {
if (derangementId > 0) {
return GetDerangement(derangementId).label;
}
return "";
}
int CharacterType::GetFreebiePoints() const {
return freebie_points;
}
void CharacterType::SetFreebiePoints(int value) {
freebie_points = value;
}
string CharacterType::GetRoadName() const {
return road_name;
}
void CharacterType::SetRoadName(string name) {
road_name = name;
}
int CharacterType::GetRoadValue() const {
return road_value;
}
void CharacterType::SetRoadValue(int value) {
road_value = value;
}
void CharacterType::SetGeneration(int value) {
generation = value;
}
int CharacterType::GetGeneration() const {
return generation;
}
int GetInitialGeneration() {
return kInitialGeneration;
}
int CharacterType::GetClanId() const {
return clan_id;
}
void CharacterType::SetClanId(int value) {
clan_id = value;
}
} // End namespace SBF

193
sbf-cpp/Character.h Normal file
View File

@@ -0,0 +1,193 @@
#ifndef CHARACTER_H__
#define CHARACTER_H__
/***************************************************************************************
* @file Character.h
*
* @brief Defines CharacterType.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
#include <cstdint>
#include <string>
#include <vector>
#include "Abilities.h"
#include "Attributes.h"
#include "Backgrounds.h"
#include "Clans.h"
#include "Colors.h"
#include "Derangements.h"
#include "Disciplines.h"
#include "Ranks.h"
#include "Virtues.h"
/** \addtogroup Character
* @{
*/
namespace SBF {
int GetAttributePointsForRank(int rank_id);
int GetBackgroundPoints();
int GetDisciplinePoints();
int GetVirtuePoints();
int GetInitialGeneration();
class CharacterType {
public:
CharacterType();
void FillAbilityValues(std::vector<int>& values, int group_id) const;
void FillAttributeValues(std::vector<int>& values, int group_id) const;
void FillBackgroundValues(std::vector<int>& values) const;
void FillDisciplineValues(std::vector<int>& values) const;
void FillVirtueValues(std::vector<int>& values) const;
int GetAbilityValue(int group_id, int id) const;
std::string GetAllDerangementsLine() const;
int GetAttributeValue(int group_id, int id) const;
std::vector<int> GetAttributeValuesInGroup(int group_id) const;
int GetBackgroundValue(int id) const;
std::vector<int> GetBackgroundValues() const;
int GetClanId() const;
int GetDisciplineValue(int id) const;
std::vector<int> GetDisciplineValues() const;
int GetFreebiePoints() const;
int GetGeneration() const;
int GetKnowledgeValue(int id) const;
int GetMentalAttributeValue(int id) const;
int GetPhysicalAttributeValue(int id) const;
std::string GetRoadName() const;
int GetRoadValue() const;
int GetSkillValue(int id) const;
int GetSocialAttributeValue(int id) const;
int GetTalentValue(int id) const;
int GetVirtueValue(int id) const;
std::vector<int> GetVirtueValues() const;
void SetAbilityValue(int group_id, int id, int value);
void SetAttributeValue(int group_id, int id, int value);
void SetBackgroundValue(int id, int value);
void SetClanId(int value);
void SetDisciplineValue(int id, int value);
void SetFreebiePoints(int value);
void SetGeneration(int value);
void SetKnowledgeValue(int id, int value);
void SetMentalAttributeValue(int id, int value);
void SetPhysicalAttributeValue(int id, int value);
void SetRoadName(std::string name);
void SetRoadValue(int value);
void SetSkillValue(int id, int value);
void SetSocialAttributeValue(int id, int value);
void SetTalentValue(int id, int value);
void SetVirtueValue(int id, int value);
std::string name;
std::string player;
std::string chronicle;
std::string haven;
std::string concept;
std::string age;
int genderId;
int natureId;
int demeanorId;
int conscience;
int selfControl;
int courage;
int willpower;
int bloodPool;
int derangementId;
private:
// Scalars
int clan_id;
int freebie_points;
int generation;
std::string road_name;
int road_value;
// Disciplines
int discipline_animalism;
int discipline_auspex;
int discipline_bardo;
int discipline_celerity;
int discipline_chimestry;
int discipline_dementation;
int discipline_dominate;
int discipline_fortitude;
int discipline_melpominee;
int discipline_mortis;
int discipline_mytherceria;
int discipline_necromancy;
int discipline_obeah;
int discipline_obfuscate;
int discipline_obtenebration;
int discipline_potence;
int discipline_presence;
int discipline_protean;
int discipline_quietus;
int discipline_serpentis;
int discipline_spiritus;
int discipline_thanantosis;
int discipline_thaumaturgy;
int discipline_vicissitude;
// Attributes
int attr_strength;
int attr_dexterity;
int attr_stamina;
int attr_appearance;
int attr_charisma;
int attr_manipulation;
int attr_intelligence;
int attr_perception;
int attr_wits;
// Talents
int talent_acting;
int talent_alertness;
int talent_athletics;
int talent_brawl;
int talent_dodge;
int talent_empathy;
int talent_intimidation;
int talent_leadership;
int talent_streetwise;
int talent_subterfuge;
// Skills
int skill_animalKen;
int skill_drive;
int skill_etiquette;
int skill_firearms;
int skill_melee;
int skill_music;
int skill_repair;
int skill_security;
int skill_stealth;
int skill_survival;
// Knowledges
int knowledge_bureaucracy;
int knowledge_computer;
int knowledge_finance;
int knowledge_investigation;
int knowledge_law;
int knowledge_linguistics;
int knowledge_medicine;
int knowledge_occult;
int knowledge_politics;
int knowledge_science;
// Backgrounds
int background_allies;
int background_contacts;
int background_fame;
int background_generation;
int background_herd;
int background_influence;
int background_mentor;
int background_resources;
int background_retainers;
int background_status;
}; // End class CharacterType
} // End namespace SBF
/** @}*/
#endif // !defined CHARACTER_H__

View File

@@ -0,0 +1,518 @@
#include "CharacterGenerator.h"
#include <iostream>
#include <string>
#include <vector>
#include "Archetypes.h"
#include "Character.h"
#include "Freebies.h"
#include "Genders.h"
#include "Menus.h"
#include "Random.h"
#include "Utils.h"
#include "sbf-cpp/Abilities.h"
#include "sbf-cpp/Backgrounds.h"
#include "sbf-cpp/Disciplines.h"
namespace SBF {
namespace {
using std::cout;
using std::endl;
using std::string;
using std::to_string;
using std::vector;
} // End namespace
void CGGetHeader(CharacterType& ch) {
MaybeClearScreen();
MenuStyle ms;
ch.name = GetString("What is the character's name?");
ch.player = GetString("Who is the player?");
ch.chronicle = GetString("What chronicle is the character going to be used for?");
ch.haven = GetString("What is the character's Haven?");
ch.concept = GetString("What is the character's concept?");
ch.age = GetString("How old is the character?");
vector<string> genders;
FillGenderLabels(genders);
ch.genderId = ChooseStringId(genders, ms, "What is the character's gender?");
vector<string> clans;
FillClanLabels(clans);
ch.SetClanId(ChooseStringId(clans, ms, "What clan is the character from?"));
vector<string> archetypes;
FillArchetypeLabels(archetypes);
ch.natureId = ChooseStringId(archetypes, ms, "What is the character's nature?");
ch.demeanorId = ChooseStringId(archetypes, ms, "What is the character's demeanor?");
}
void CGGetDisciplines(CharacterType& ch) {
MenuStyle ms;
int discipline_points = GetDisciplinePoints();
vector<int> discipline_values;
vector<string> discipline_labels;
FillDisciplineLabels(discipline_labels);
while (discipline_points > 0) {
MaybeClearScreen();
discipline_values = ch.GetDisciplineValues();
int discipline_id = ChooseStringIdWithValues(
discipline_labels,
discipline_values,
ms,
"Which discipline do you want to spend 1 of your " + to_string(discipline_points) + " points on?");
ch.SetDisciplineValue(discipline_id, ch.GetDisciplineValue(discipline_id) + 1);
discipline_points--;
}
}
void CGGetAttributes(CharacterType& ch) {
MenuStyle ms_without_values;
MenuStyle ms_with_values;
// indexed by group_id - 1 holds the rank_id
vector<int> attribute_ranks(GetNumAttributeGroups());
// Attribute groups menu (physical/social/mental)
vector<MenuItem> attribute_groups_menu_items;
for (size_t i = 1; i <= GetNumAttributeGroups(); i++) {
attribute_groups_menu_items.push_back(MenuItem(GetAttributeGroupLabel(i), i));
}
// Choose attribute group priorities.
int group_sum = 0;
int rank_sum = 1;
for (size_t i = 1; i < GetNumAttributeGroups(); i++) {
int next_group_id = ChooseMenuItemId(attribute_groups_menu_items,
ms_without_values,
"Choose your " + ToLower(GetRank(i).label) + " attribute?",
true);
int next_group_index = next_group_id - 1;
attribute_groups_menu_items.at(next_group_index).is_visible = false;
attribute_ranks[next_group_index] = i;
rank_sum += i + 1;
group_sum += next_group_id;
}
// General formula for last choice given 1 to count based indexing is this. (Sum from 1 to count) - (Sum of all
// previous choice IDs). Sum(1..AllAttributesCount)-Sum(Choice[1..Choice[AllAttributesCount-1]).
int last_group = rank_sum - group_sum;
attribute_ranks[last_group - 1] = GetNumAttributeGroups();
// Spend attribute points
for (int group_id = 1; group_id <= GetNumAttributeGroups(); group_id++) {
int group_index = group_id - 1;
vector<string> attribute_labels = GetAttributeLabelsInGroup(group_id);
int rank_id = attribute_ranks.at(group_index);
int attribute_points = GetAttributePointsForRank(rank_id);
while (attribute_points > 0) {
vector<int> values = ch.GetAttributeValuesInGroup(group_id);
string prompt = "Which " + ToLower(GetAttributeGroupLabel(group_id))
+ " attribute do you want to spend 1 of your " + to_string(attribute_points) + " points on?";
int attribute_id = ChooseStringIdWithValues(attribute_labels, values, ms_with_values, prompt);
ch.SetAttributeValue(group_id, attribute_id, ch.GetAttributeValue(group_id, attribute_id) + 1);
attribute_points--;
}
}
}
void CGGetBackgrounds(CharacterType& ch) {
// Spend background points
MenuStyle ms;
int background_points = GetBackgroundPoints();
vector<string> background_labels = GetBackgroundLabels();
while (background_points > 0) {
MaybeClearScreen();
vector<int> background_values = ch.GetBackgroundValues();
int background_id = ChooseStringIdWithValues(
background_labels,
background_values,
ms,
"Which background do you want to spend 1 of your " + to_string(background_points) + " points on?");
ch.SetBackgroundValue(background_id, ch.GetBackgroundValue(background_id) + 1);
background_points--;
}
}
void CGGetRoad(CharacterType& ch) {
// TODO: Update this to actually pick a road for VtDA.
ch.SetRoadName("Humanity");
}
void CGSpendVirtuePoints(CharacterType& ch) {
// Spend virtue points
MenuStyle ms;
int virtue_points = GetVirtuePoints();
vector<string> labels = GetVirtueLabels();
while (virtue_points > 0) {
vector<int> values = ch.GetVirtueValues();
int virtue_id = ChooseStringIdWithValues(
labels, values, ms, "Which virtue do you want to spend 1 of your " + to_string(virtue_points) + " points on?");
ch.SetVirtueValue(virtue_id, ch.GetVirtueValue(virtue_id) + 1);
virtue_points--;
}
}
void CGGetDerangement(CharacterType& ch) {
if (ch.GetClanId() == kClanMalkavianId) {
// If the clan is malkavian then pick a derangement.
MenuStyle ms;
ms.use_colors = true;
ch.derangementId =
ChooseStringIdWithColors(GetDerangementLabels(), GetDerangementColors(), ms, "Which derangement do you want?");
}
}
void CGSpendFreebiePoints(CharacterType& ch) {
int freebie_points = ch.GetFreebiePoints();
MenuStyle ms;
while (freebie_points > 0) {
MaybeClearScreen();
// Build the menu
vector<FreebieType> available_freebies = GetAvailableFreebies(freebie_points);
vector<MenuItem> menu_items;
for (int freebie_index = 0; freebie_index < available_freebies.size(); freebie_index++) {
FreebieType freebie = available_freebies.at(freebie_index);
MenuItem mi(freebie.label, freebie.id);
if (freebie_index + 1 == kFreebieShowCharacterSheetId) {
mi.include_in_random = false;
}
menu_items.push_back(mi);
}
string prompt = "You have " + to_string(freebie_points)
+ " freebie points remaining. What would you like to spend the points on?";
int id = ChooseMenuItemId(menu_items, ms, prompt, true);
switch (id) {
case kFreebieDisciplineId:
CGSpendDisciplinePoint(ch);
break;
case kFreebieAttributeId:
CGSpendAttributePoint(ch);
break;
case kFreebieAbilityId:
CGSpendAbilityPoint(ch);
break;
case kFreebieVirtueId:
CGSpendVirtuePoint(ch);
break;
case kFreebieHumanityId:
CGSpendHumanityPoint(ch);
break;
case kFreebieBackgroundId:
CGSpendBackgroundPoint(ch);
break;
case kFreebieShowCharacterSheetId:
ShowCharacterSheet(ch);
break;
}
freebie_points = ch.GetFreebiePoints();
}
}
void CGSpendDisciplinePoint(CharacterType& ch) {
MaybeClearScreen();
MenuStyle ms;
ms.show_cancel = true;
ms.cancel_item_id = GetNumDisciplines() + 1;
vector<int> values = ch.GetDisciplineValues();
vector<string> labels;
FillDisciplineLabels(labels);
string prompt = "Which discipline would you like to add 1 dot to?";
int discipline_id = ChooseStringIdWithValues(labels, values, ms, prompt);
if (discipline_id != ms.cancel_item_id) {
ch.SetDisciplineValue(discipline_id, ch.GetDisciplineValue(discipline_id) + 1);
ch.SetFreebiePoints(ch.GetFreebiePoints() - GetFreebieCost(kFreebieDisciplineId));
}
}
struct GroupedStatReference {
int id;
int group_id;
int item_id;
};
void CGSpendAttributePoint(CharacterType& ch) {
MaybeClearScreen();
MenuStyle ms;
ms.show_cancel = true;
int num_attributes = 0;
vector<int> num_attributes_in_group;
for (int group_id = 1; group_id <= GetNumAttributeGroups(); group_id++) {
int count = GetNumAttributesInGroup(group_id);
num_attributes_in_group.push_back(count);
num_attributes += count;
}
vector<GroupedStatReference> attribute_refs;
vector<string> labels;
vector<int> values;
int attribute_id = 1;
for (int attribute_group_id = 1; attribute_group_id <= GetNumAttributeGroups(); attribute_group_id++) {
int attribute_group_index = attribute_group_id - 1;
for (int id = 1; id <= num_attributes_in_group.at(attribute_group_index); id++) {
GroupedStatReference attribute_ref = {attribute_id, attribute_group_id, id};
attribute_refs.push_back(attribute_ref);
labels.push_back(GetAttributeLabel(attribute_group_id, id));
values.push_back(ch.GetAttributeValue(attribute_group_id, id));
attribute_id++;
}
}
string prompt = "Which attribute do you want to add one dot to?";
ms.cancel_item_id = num_attributes + 1;
int id = ChooseStringIdWithValues(labels, values, ms, prompt);
if (id == ms.cancel_item_id) {
return;
}
GroupedStatReference ref = attribute_refs.at(id - 1);
ch.SetAttributeValue(ref.group_id, ref.item_id, ch.GetAttributeValue(ref.group_id, ref.item_id) + 1);
ch.SetFreebiePoints(ch.GetFreebiePoints() - GetFreebieCost(kFreebieAttributeId));
}
void CGSpendAbilityPoint(CharacterType& ch) {
MenuStyle ms;
ms.show_cancel = true;
bool done = false;
while (!done) {
MaybeClearScreen();
ms.cancel_item_id = AbilityGroup::GetCount();
int ability_group_id =
ChooseStringId(AbilityGroup::GetPluralLabels(), ms, "What kind of ability would you like to add 1 dot to?");
if (ability_group_id == ms.cancel_item_id) {
return;
}
AbilityGroup ability_group = AbilityGroup::FromId(ability_group_id);
vector<string> labels = ability_group.GetAbilityLabels();
ms.cancel_item_id = labels.size() + 1;
int ability_id =
ChooseStringId(labels, ms, "What " + ToLower(ability_group.singular()) + " would you like to add 1 dot to?");
if (ability_id != ms.cancel_item_id) {
ch.SetAbilityValue(ability_group_id, ability_id, ch.GetAbilityValue(ability_group_id, ability_id) + 1);
ch.SetFreebiePoints(ch.GetFreebiePoints() - GetFreebieCost(kFreebieAbilityId));
}
}
}
void CGSpendVirtuePoint(CharacterType& ch) {
MenuStyle ms;
ms.show_cancel = true;
ms.cancel_item_id = GetNumVirtues() + 1;
string prompt = "What virtue would you like to add 1 dot to?";
int id = ChooseStringIdWithValues(GetVirtueLabels(), ch.GetVirtueValues(), ms, prompt);
if (id != ms.cancel_item_id) {
ch.SetVirtueValue(id, ch.GetVirtueValue(id) + 1);
ch.SetFreebiePoints(ch.GetFreebiePoints() - GetFreebieCost(kFreebieVirtueId));
}
}
void CGSpendHumanityPoint(CharacterType& ch) {
vector<string> labels = {"Yes", "No"};
string prompt = "Are you sure you want to add a dot to your " + ch.GetRoadName() + "?";
MenuStyle ms;
ms.show_random = false;
if (ChooseYesOrNo(prompt)) {
ch.SetRoadValue(ch.GetRoadValue() - 1);
ch.SetFreebiePoints(ch.GetFreebiePoints() - GetFreebieCost(kFreebieHumanityId));
}
}
void CGSpendBackgroundPoint(CharacterType& ch) {
MenuStyle ms;
ms.show_cancel = true;
vector<string> labels = GetBackgroundLabels();
ms.cancel_item_id = labels.size() + 1;
string prompt = "Which background would you like to add 1 dot to?";
int id = ChooseStringId(labels, ms, prompt);
if (id != ms.cancel_item_id) {
ch.SetBackgroundValue(id, ch.GetBackgroundValue(id) + 1);
ch.SetFreebiePoints(ch.GetFreebiePoints() - GetFreebieCost(kFreebieBackgroundId));
}
}
string FormatAttributeValue(const string& label, int value) {
return MakeFitC(label + MakeFitL(to_string(value), 2), 12);
}
string FormatAbilityWithValue(const string& label, int value) {
return MakeFitC(MakeFitL(label + ":", 14) + to_string(value), 24);
}
void SaveCharacterSheet(CharacterType& ch) {
// TODO: Fill this in.
cout << "// TODO: SaveCharacterSheet(CharacterType&)" << endl;
}
void ShowCharacterSheet(CharacterType& ch) {
const int kLeftColumnWidth = 36;
const int kRightColumnWidth = 37;
vector<string> discipline_strings;
size_t index;
for (index = 1; index <= GetNumDisciplines(); index++) {
int value = ch.GetDisciplineValue(index);
if (value > 0) {
string suffix = "";
if (value > 1) {
suffix = " x" + to_string(value);
}
discipline_strings.push_back(GetDisciplineLabel(index) + suffix);
}
}
while (discipline_strings.size() <= 3) {
discipline_strings.push_back(RepeatChar(kLeftColumnWidth, '_'));
}
vector<string> background_strings(5);
for (index = 1; index <= kBackgroundsCount; index++) {
int value = ch.GetBackgroundValue(index);
if (value > 0) {
string suffix = "";
if (value > 1) {
suffix = " x" + to_string(value);
}
background_strings.push_back(GetBackgroundLabel(index) + suffix);
}
}
while (background_strings.size() <= 5) {
background_strings.push_back(RepeatChar(kLeftColumnWidth, '_'));
}
string all_derangements_line = ch.GetAllDerangementsLine();
vector<string> derangement_strings = WordWrap(all_derangements_line, kLeftColumnWidth);
while (derangement_strings.size() <= 5) {
derangement_strings.push_back(RepeatChar(kLeftColumnWidth, '_'));
}
MaybeClearScreen();
cout << "╔══════════════════════════════════════╦═══════════════════════════════════════╗" << endl;
cout << "║ Name: " << MakeFitL(ch.name, 30) << " ║ Gender: " << MakeFitL(GetGenderLabel(ch.genderId), 14)
<< " Generation: " << MakeFitR(to_string(ch.GetGeneration()), 2) << "" << endl;
cout << "║ Clan: " << MakeFitL(GetClanLabel(ch.GetClanId()), 30) << " ║ Age: " << MakeFitL(ch.age, 32) << ""
<< endl;
cout << "╠══════════════════════════════════════╣ Player: " << MakeFitL(ch.player, 29) << "" << endl;
cout << "║ Attributes ║ Chronicle: " << MakeFitL(ch.chronicle, 26) << "" << endl;
cout << "" << MakeFitC("Physical", 12) << MakeFitC("Social", 12) << MakeFitC("Mental", 12)
<< " ║ Haven: " << MakeFitL(ch.haven, 30) + "" << endl;
cout << "" << FormatAttributeValue("Str. ", ch.GetPhysicalAttributeValue(kPhysicalAttributeStrengthId))
<< FormatAttributeValue("App. ", ch.GetSocialAttributeValue(kSocialAttributeAppearanceId))
<< FormatAttributeValue("Int. ", ch.GetMentalAttributeValue(kMentalAttributeIntelligenceId))
<< " ║ Concept: " << MakeFitL(ch.concept, 28) << "" << endl;
cout << "" << FormatAttributeValue("Dex. ", ch.GetPhysicalAttributeValue(kPhysicalAttributeDexterityId))
<< FormatAttributeValue("Cha. ", ch.GetSocialAttributeValue(kSocialAttributeCharismaId))
<< FormatAttributeValue("Per. ", ch.GetMentalAttributeValue(kMentalAttributePerceptionId))
<< " ╠═══════════════════════════════════════╣" << endl;
cout << "" << FormatAttributeValue("Sta. ", ch.GetPhysicalAttributeValue(kPhysicalAttributeStaminaId))
<< FormatAttributeValue("Man. ", ch.GetSocialAttributeValue(kSocialAttributeManipulationId))
<< FormatAttributeValue("Wit. ", ch.GetMentalAttributeValue(kMentalAttributeWitsId))
<< " ║ Derangements: ║" << endl;
cout << "╠══════════════════════════════════════╣ " << MakeFitL(derangement_strings[0], kRightColumnWidth, '_')
<< "" << endl;
cout << "║ Disciplines: ║ " << MakeFitL(derangement_strings[1], kRightColumnWidth, '_')
<< "" << endl;
cout << "" << MakeFitL(discipline_strings[0], kLeftColumnWidth) << ""
<< MakeFitL(derangement_strings[2], kRightColumnWidth, '_') << "" << endl;
cout << "" << MakeFitL(discipline_strings[1], kLeftColumnWidth) << ""
<< MakeFitL(derangement_strings[3], kRightColumnWidth, '_') << "" << endl;
cout << "" << MakeFitL(discipline_strings[2], kLeftColumnWidth) << ""
<< MakeFitL(derangement_strings[4], kRightColumnWidth, '_') << "" << endl;
cout << "╠══════════════════════════════════════╬═══════════════════════════════════════╣" << endl;
cout << "" << MakeFitL(ch.GetRoadName() + ": " + to_string(ch.GetRoadValue()), kLeftColumnWidth)
<< " ║ Nature: " << MakeFitL(GetArchetypeLabel(ch.natureId), 29) << "" << endl;
cout << "║ Willpower: " << MakeFitL(to_string(ch.willpower), 25)
<< " ║ Demeanor: " << MakeFitL(GetArchetypeLabel(ch.demeanorId), 27) << "" << endl;
cout << "╠══════════════════════════════════════╩═══════════════════════════════════════╣" << endl;
cout << "║ ║" << endl;
cout << "║ ║" << endl;
cout << "║ ║" << endl;
cout << "║ <<PRESS ANY KEY TO CONTINUE>> ║" << endl;
cout << "╚══════════════════════════════════════════════════════════════════════════════╝" << endl;
WaitForKeypress();
cout << "╔══════════════════════════════════════════════════════════════════════════════╗" << endl;
cout << "" << MakeFitC("Abilities", 76) << "" << endl;
cout << "" << MakeFitC("Talents", 24) << MakeFitC("Skills", 24) << MakeFitC("Knowledges", 24) << "" << endl;
for (index = 1; index <= 10; index++) {
cout << "" << FormatAbilityWithValue(Ability::GetTalentLabel(index), ch.GetTalentValue(index))
<< FormatAbilityWithValue(Ability::GetSkillLabel(index), ch.GetSkillValue(index))
<< FormatAbilityWithValue(Ability::GetKnowledgeLabel(index), ch.GetKnowledgeValue(index)) << "" << endl;
}
cout << "╠══════════════════════════════════════╦═══════════════════════════════════════╣" << endl;
cout << "" << MakeFitL("Backgrounds:", kLeftColumnWidth) << "" << MakeFitL("Virtues:", kRightColumnWidth) << ""
<< endl;
cout << "" << MakeFitL(background_strings[0], kLeftColumnWidth) << ""
<< MakeFitB("Conscience:", to_string(ch.conscience), kRightColumnWidth) << "" << endl;
cout << "" << MakeFitL(background_strings[1], kLeftColumnWidth) << ""
<< MakeFitB("Self-Control:", to_string(ch.selfControl), kRightColumnWidth) << "" << endl;
cout << "" << MakeFitL(background_strings[2], kLeftColumnWidth) << ""
<< MakeFitB("Courage:", to_string(ch.courage), kRightColumnWidth) << "" << endl;
cout << "" << MakeFitL(background_strings[3], kLeftColumnWidth) << "" << MakeFitL("", kRightColumnWidth) << ""
<< endl;
cout << "" << MakeFitL(background_strings[4], kLeftColumnWidth) << "" << MakeFitL("", kRightColumnWidth) << ""
<< endl;
cout << "╠══════════════════════════════════════╩═══════════════════════════════════════╣" << endl;
cout << "║ <<PRESS ANY KEY TO CONTINUE>> ║" << endl;
cout << "╚══════════════════════════════════════════════════════════════════════════════╝" << endl;
WaitForKeypress();
/*
╔══════════════════════════════════════╦═══════════════════════════════════════╗
║ Name: Ted ║ Gender: Female Generation: 13 ║
║ Clan: Ventrue ║ Age: 35 ║
╠══════════════════════════════════════╣ Player: Jeff ║
║ Attributes ║ Chronicle: Somesuch ║
║ Physical Social Mental ║ Haven: Mom's basement ║
║ Str. 1 App. 1 Int. 1 ║ Concept: Asshole ║
║ Dex. 1 Cha. 1 Per. 1 ╠═══════════════════════════════════════╣
║ Sta. 1 Man. 1 Wit. 1 ║ Derangements: ║
╠══════════════════════════════════════╣ _____________________________________ ║
║ Disciplines: ║ _____________________________________ ║
║ ____________________________________ ║ _____________________________________ ║
║ ____________________________________ ║ _____________________________________ ║
║ ____________________________________ ║ _____________________________________ ║
╠══════════════════════════════════════╬═══════════════════════════════════════╣
║ : 2 ║ Nature: Tyrant ║
║ Willpower: 1 ║ Demeanor: Traditionalist ║
╠══════════════════════════════════════╩═══════════════════════════════════════╣
║ ║
║ ║
║ ║
║ <<PRESS ANY KEY TO CONTINUE>> ║
╚══════════════════════════════════════════════════════════════════════════════╝
╔══════════════════════════════════════════════════════════════════════════════╗
║ Abilities ║
║ Talents Skills Knowledges ║
║ Acting: 0 Animal Ken: 0 Bureaucracy: 0 ║
║ Alertness: 0 Drive: 0 Computer: 0 ║
║ Athletics: 0 Etiquette: 0 Finance: 0 ║
║ Brawl: 0 Firearms: 0 Investigation:0 ║
║ Dodge: 0 Melee: 0 Law: 0 ║
║ Empathy: 0 Music: 0 Linguistics: 0 ║
║ Intimidation: 0 Repair: 0 Medicine: 0 ║
║ Leadership: 0 Security: 0 Occult: 0 ║
║ Streetwise: 0 Stealth: 0 Politics: 0 ║
║ Subterfuge: 0 Survival: 0 Science: 0 ║
╠══════════════════════════════════════╦═══════════════════════════════════════╣
║ Backgrounds: ║ Virtues: ║
║ ║ Conscience: 1 ║
║ ║ Self-Control: 1 ║
║ ║ Courage: 1 ║
║ ║ ║
║ ║ ║
╠══════════════════════════════════════╩═══════════════════════════════════════╣
║ <<PRESS ANY KEY TO CONTINUE>> ║
╚══════════════════════════════════════════════════════════════════════════════╝
*/
}
void CharacterGenerator() {
CharacterType ch;
CGGetHeader(ch);
CGGetDisciplines(ch);
CGGetAttributes(ch);
CGGetBackgrounds(ch);
CGGetRoad(ch);
CGSpendVirtuePoints(ch);
CGGetDerangement(ch);
ch.SetGeneration(GetInitialGeneration() - ch.GetBackgroundValue(kBackgroundGenerationId));
ch.willpower = ch.courage;
ch.SetRoadValue(ch.conscience + ch.selfControl);
ch.bloodPool = GetRandomInt(1, 10);
CGSpendFreebiePoints(ch);
SaveCharacterSheet(ch);
ShowCharacterSheet(ch);
}
} // End namespace SBF

View File

@@ -0,0 +1,24 @@
#ifndef CHARACTERGENERATOR_H__
#define CHARACTERGENERATOR_H__
#include "Character.h"
namespace SBF {
void CGGetAttributes(CharacterType& ch);
void CGGetBackgrounds(CharacterType& ch);
void CGGetDerangement(CharacterType& ch);
void CGGetDisciplines(CharacterType& ch);
void CGGetHeader(CharacterType& ch);
void CGGetRoad(CharacterType& ch);
void CGSpendAbilityPoint(CharacterType& ch);
void CGSpendAttributePoint(CharacterType& ch);
void CGSpendBackgroundPoint(CharacterType& ch);
void CGSpendDisciplinePoint(CharacterType& ch);
void CGSpendFreebiePoints(CharacterType& ch);
void CGSpendHumanityPoint(CharacterType& ch);
void CGSpendVirtuePoint(CharacterType& ch);
void CGSpendVirtuePoints(CharacterType& ch);
void CharacterGenerator();
void ShowCharacterSheet(CharacterType& ch);
void SaveCharacterSheet(CharacterType& ch);
} // namespace SBF
#endif // End !defined(CHARACTERGENERATOR_H__)

View File

@@ -0,0 +1,25 @@
#include "CharacterGenerator.h"
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#include "test.h"
namespace {
using namespace SBF;
using namespace Test;
using std::cout;
} // End namespace
int main(int argc, char* argv[]) {
TestResults results;
results.skip("// TODO: SBF::CharacterGenerator::*");
PrintResults(cout, results);
return results.failed() + results.errors();
}

View File

@@ -0,0 +1,18 @@
#include "Character.h"
#include <iostream>
#include "test.h"
using namespace SBF;
using namespace Test;
using namespace std;
int main(int argc, char* argv[]) {
TestResults results;
results.skip("// TODO: SBF::Character::*");
PrintResults(cout, results);
return results.failed() + results.errors();
}

64
sbf-cpp/Clans.cpp Normal file
View File

@@ -0,0 +1,64 @@
#include "Clans.h"
#include <string>
#include <vector>
namespace SBF {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-const-variable"
const std::string kClanAnarchLabel = "Anarch";
const std::string kClanAssamiteLabel = "Assamite";
const std::string kClanBaaliLabel = "Baali";
const std::string kClanBrujahLabel = "Brujah";
const std::string kClanCaitiffLabel = "Caitiff";
const std::string kClanCappadocianLabel = "Cappadocian";
const std::string kClanGangrelLabel = "Gangrel";
const std::string kClanGiovanniLabel = "Giovanni";
const std::string kClanInconnuLabel = "Inconnu";
const std::string kClanLasombraLabel = "Lasombra";
const std::string kClanMalkavianLabel = "Malkavian";
const std::string kClanNosferatuLabel = "Nosferatu";
const std::string kClanRavanosLabel = "Ravanos";
const std::string kClanSettiteLabel = "Settite";
const std::string kClanToreadorLabel = "Toreador";
const std::string kClanTremereLabel = "Tremere";
const std::string kClanTzismiceLabel = "Tzismice";
const std::string kClanVentrueLabel = "Ventrue";
const int kClansCount = 18;
const std::string kClanLabels[] = {
"",
kClanAnarchLabel,
kClanAssamiteLabel,
kClanBaaliLabel,
kClanBrujahLabel,
kClanCaitiffLabel,
kClanCappadocianLabel,
kClanGangrelLabel,
kClanGiovanniLabel,
kClanInconnuLabel,
kClanLasombraLabel,
kClanMalkavianLabel,
kClanNosferatuLabel,
kClanRavanosLabel,
kClanSettiteLabel,
kClanToreadorLabel,
kClanTremereLabel,
kClanTzismiceLabel,
kClanVentrueLabel,
};
#pragma clang diagnostic pop
std::string GetClanLabel(int clan_id) {
if (clan_id > 0 && clan_id <= kClansCount) {
return kClanLabels[clan_id];
}
return "";
}
void FillClanLabels(std::vector<std::string>& labels) {
labels.clear();
for (int id = 1; id <= kClansCount; id++) {
labels.push_back(GetClanLabel(id));
}
}
} // End namespace SBF

48
sbf-cpp/Clans.h Normal file
View File

@@ -0,0 +1,48 @@
#ifndef CLANS_H__
#define CLANS_H__
/***************************************************************************************
* @file Clans.h
*
* @brief Defines constants and functions for working with clans.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
#include <string>
#include <vector>
/** \addtogroup Clans
* @{
*/
namespace SBF {
const int kClanAnarchId = 1;
const int kClanAssamiteId = 2;
const int kClanBaaliId = 3;
const int kClanBrujahId = 4;
const int kClanCaitiffId = 5;
const int kClanCappadocianId = 6;
const int kClanGangrelId = 7;
const int kClanGiovanniId = 8;
const int kClanInconnuId = 9;
const int kClanLasombraId = 10;
const int kClanMalkavianId = 11;
const int kClanNosferatuId = 12;
const int kClanRavanosId = 13;
const int kClanSettiteId = 14;
const int kClanToreadorId = 15;
const int kClanTremereId = 16;
const int kClanTzismiceId = 17;
const int kClanVentrueId = 18;
/// @brief Gets the label for a clan with a specific id.
/// @param clan_id The id of the clan to get the label for.
/// @return The label of the specified clan or an empty string if clan_id is invalid.
std::string GetClanLabel(int clan_id);
/// @brief Fills the provided vector with all valid clans.
/// @param clans The vector to fill. It will be emptied first.
void FillClanLabels(std::vector<std::string>& clans);
} // End namespace SBF
/** @}*/
#endif // End !defined CLANS_H__

92
sbf-cpp/Clans_test.cpp Normal file
View File

@@ -0,0 +1,92 @@
#include "Clans.h"
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#include "test.h"
namespace {
using namespace SBF;
using namespace Test;
using namespace std;
} // End namespace
TestResults test_GetClanLabel() {
return execute_suite<string, int>(make_test_suite(
"SBF::GetClanLabel",
GetClanLabel,
vector<TestTuple<string, int>>({
make_test<string, int>("should get \"\" for invalid clan 0", "", make_tuple(0)),
make_test<string, int>("should get \"Anarch\" for clan 1 kClanAnarch", "Anarch", make_tuple(1)),
make_test<string, int>("should get \"Assamite\" for clan 2 kClanAssamite", "Assamite", make_tuple(2)),
make_test<string, int>("should get \"Baali\" for clan 3 kClanBaali", "Baali", make_tuple(3)),
make_test<string, int>("should get \"Brujah\" for clan 4 kClanBrujah", "Brujah", make_tuple(4)),
make_test<string, int>("should get \"Caitiff\" for clan 5 kClanCaitiff", "Caitiff", make_tuple(5)),
make_test<string, int>(
"should get \"Cappadocian\" for clan 6 kClanCappadocian", "Cappadocian", make_tuple(6)),
make_test<string, int>("should get \"Gangrel\" for clan 7 kClanGangrel", "Gangrel", make_tuple(7)),
make_test<string, int>("should get \"Giovanni\" for clan 8 kClanGiovanni", "Giovanni", make_tuple(8)),
make_test<string, int>("should get \"Inconnu\" for clan 9 kClanInconnu", "Inconnu", make_tuple(9)),
make_test<string, int>("should get \"Lasombra\" for clan 10 kClanLasombra", "Lasombra", make_tuple(10)),
make_test<string, int>("should get \"Malkavian\" for clan 11 kClanMalkavian", "Malkavian", make_tuple(11)),
make_test<string, int>("should get \"Nosferatu\" for clan 12 kClanNosferatu", "Nosferatu", make_tuple(12)),
make_test<string, int>("should get \"Ravanos\" for clan 13 kClanRavanos", "Ravanos", make_tuple(13)),
make_test<string, int>("should get \"Settite\" for clan 14 kClanSettite", "Settite", make_tuple(14)),
make_test<string, int>("should get \"Toreador\" for clan 15 kClanToreador", "Toreador", make_tuple(15)),
make_test<string, int>("should get \"Tremere\" for clan 16 kClanTremere", "Tremere", make_tuple(16)),
make_test<string, int>("should get \"Tzismice\" for clan 17 kClanTzismice", "Tzismice", make_tuple(17)),
make_test<string, int>("should get \"Ventrue\" for clan 18 kClanVentrue", "Ventrue", make_tuple(18)),
make_test<string, int>("should get \"\" for invalid clan 19", "", make_tuple(19)),
})));
}
TestResults test_FillClanLabels() {
return execute_suite<string>(make_test_suite(
"SBF::FillClanLabels",
[]() -> string {
ostringstream error_message;
vector<string> expected = {"Anarch",
"Assamite",
"Baali",
"Brujah",
"Caitiff",
"Cappadocian",
"Gangrel",
"Giovanni",
"Inconnu",
"Lasombra",
"Malkavian",
"Nosferatu",
"Ravanos",
"Settite",
"Toreador",
"Tremere",
"Tzismice",
"Ventrue"};
vector<string> actual = {"This should be removed."};
FillClanLabels(actual);
compare(error_message, expected, actual);
string error = error_message.str();
if (error.size() > 0) {
return error;
}
return "no errors";
},
vector<TestTuple<string>>({
make_test<string>("should fill clan labels", "no errors", make_tuple()),
})
));
}
int main(int argc, char* argv[]) {
TestResults results;
results += test_GetClanLabel();
results += test_FillClanLabels();
return results.failed() + results.errors();
}

34
sbf-cpp/Colors.cpp Normal file
View File

@@ -0,0 +1,34 @@
#include "Colors.h"
namespace SBF {
uint8_t g_foreground_color = kColorDefaultForeground;
uint8_t g_background_color = kColorDefaultBackground;
void FillColors(std::vector<uint8_t>& colors) {
colors.clear();
for (uint8_t color = kColorDarkBlack; color <= kColorBrightWhite; color++) {
colors.push_back(color);
}
}
uint8_t GetBackgroundColor() {
return g_background_color;
}
uint8_t GetForegroundColor() {
return g_foreground_color;
}
uint8_t SetBackgroundColor(uint8_t color) {
uint8_t previous_color = g_background_color;
g_background_color = color;
return previous_color;
}
uint8_t SetForegroundColor(uint8_t color) {
uint8_t previous_color = g_foreground_color;
g_foreground_color = color;
return previous_color;
}
} // End namespace SBF

92
sbf-cpp/Colors.h Normal file
View File

@@ -0,0 +1,92 @@
#ifndef COLORS_H__
#define COLORS_H__
/***************************************************************************************
* @file Colors.h
*
* @brief Defines constants and functions for working with screen colors.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
#include <cstdint>
#include <ostream>
#include <vector>
/** \addtogroup Abilities
* @{
*/
namespace SBF {
const uint8_t kColorDarkBlack = 0;
const uint8_t kColorDarkRed = 1;
const uint8_t kColorDarkGreen = 2;
const uint8_t kColorDarkYellow = 3;
const uint8_t kColorDarkBlue = 4;
const uint8_t kColorDarkMagenta = 5;
const uint8_t kColorDarkCyan = 6;
const uint8_t kColorDarkWhite = 7;
const uint8_t kColorBrightBlack = 8;
const uint8_t kColorBrightRed = 9;
const uint8_t kColorBrightGreen = 10;
const uint8_t kColorBrightYellow = 11;
const uint8_t kColorBrightBlue = 12;
const uint8_t kColorBrightMagenta = 13;
const uint8_t kColorBrightCyan = 14;
const uint8_t kColorBrightWhite = 15;
const uint8_t kColorDefaultForeground = kColorBrightWhite;
const uint8_t kColorDefaultBackground = kColorDarkBlack;
/// @brief Sets the stored foreground color.
/// @param color The new foreground color.
uint8_t SetForegroundColor(uint8_t color);
/// @brief Gets the stored foreground color.
/// @return The foreground color.
uint8_t GetForegroundColor();
/// @brief Sets the stored background color.
/// @param color The new background color.
uint8_t SetBackgroundColor(uint8_t color);
/// @brief Gets the stored background color.
/// @return The background color.
uint8_t GetBackgroundColor();
/// @brief Fills the provided vector with all of the possible color values. It will be cleared before filling.
/// @param colors The vector to fill.
void FillColors(std::vector<uint8_t>& colors);
template <typename TChar, typename TTraits>
auto& Reset(std::basic_ostream<TChar, TTraits>& os) {
return os << "\033[m";
}
template <typename TChar, typename TTraits>
auto& ForegroundColor(std::basic_ostream<TChar, TTraits>& os) {
return os << "\033[38;5;" + std::to_string(GetForegroundColor()) + "m";
}
template <typename TChar, typename TTraits>
auto& BackgroundColor(std::basic_ostream<TChar, TTraits>& os) {
return os << "\033[48;5;" + std::to_string(GetBackgroundColor()) + "m";
}
template <typename TChar, typename TTraits>
auto& Colors(std::basic_ostream<TChar, TTraits>& os) {
return os << ForegroundColor << BackgroundColor;
}
template <typename TChar, typename TTraits>
auto& TrueColorForeground(std::basic_ostream<TChar, TTraits>& os, uint8_t red, uint8_t green, uint8_t blue) {
return os << "\033[38;2;" << std::to_string(red) << ";" << std::to_string(green) << ";" << std::to_string(blue)
<< "m";
}
template <typename TChar, typename TTraits>
auto& TrueColorBackground(std::basic_ostream<TChar, TTraits>& os, uint8_t red, uint8_t green, uint8_t blue) {
return os << "\033[48;2;" << std::to_string(red) << ";" << std::to_string(green) << ";" << std::to_string(blue)
<< "m";
}
} // End namespace SBF
/** @}*/
#endif // End !defined COLORS_H__

416
sbf-cpp/Colors_test.cpp Normal file
View File

@@ -0,0 +1,416 @@
#include "Colors.h"
#include <iostream>
#include <regex>
#include <string>
#include <tuple>
#include <vector>
#include "test.h"
namespace {
using namespace SBF;
using namespace Test;
using namespace std;
} // End namespace
namespace SBF {
extern uint8_t g_foreground_color;
extern uint8_t g_background_color;
} // namespace SBF
string escape_string(const string& text, const string& pattern = "\033", const string& replace = "\\033") {
return regex_replace(text, regex(pattern), replace);
}
TestResults test_FillColors() {
return execute_suite<string>(make_test_suite(
"SBF::FillColors",
[]() -> string {
ostringstream error_message;
vector<uint8_t> expected = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
vector<uint8_t> actual = {255};
FillColors(actual);
compare(error_message, expected, actual);
string error = error_message.str();
if (error.size() > 0) {
return error;
}
return "no errors";
},
vector<TestTuple<string>>({
make_test<string>("should fill colors", "no errors", make_tuple()),
})));
}
TestResults test_GetBackgroundColor() {
auto fnToTest = [](uint8_t background_color) -> uint8_t {
SBF::g_background_color = background_color;
return GetBackgroundColor();
};
return execute_suite<uint32_t, uint32_t>(make_test_suite(
"SBF::GetBackgroundColor",
fnToTest,
vector<TestTuple<uint32_t, uint32_t>>({
make_test<uint32_t, uint32_t>(
"should get kColorDarkBlack when background color is 0", kColorDarkBlack, make_tuple(0U)),
make_test<uint32_t, uint32_t>(
"should get kColorDarkBlue when background color is 1", kColorDarkRed, make_tuple(1U)),
make_test<uint32_t, uint32_t>(
"should get kColorDarkGreen when background color is 2", kColorDarkGreen, make_tuple(2U)),
make_test<uint32_t, uint32_t>(
"should get kColorDarkCyan when background color is 3", kColorDarkYellow, make_tuple(3U)),
make_test<uint32_t, uint32_t>(
"should get kColorDarkRed when background color is 4", kColorDarkBlue, make_tuple(4U)),
make_test<uint32_t, uint32_t>(
"should get kColorDarkMagenta when background color is 5", kColorDarkMagenta, make_tuple(5U)),
make_test<uint32_t, uint32_t>(
"should get kColorDarkYellow when background color is 6", kColorDarkCyan, make_tuple(6U)),
make_test<uint32_t, uint32_t>(
"should get kColorDarkWhite when background color is 7", kColorDarkWhite, make_tuple(7U)),
make_test<uint32_t, uint32_t>(
"should get kColorBrightBlack when background color is 8", kColorBrightBlack, make_tuple(8U)),
make_test<uint32_t, uint32_t>(
"should get kColorBrightBlue when background color is 9", kColorBrightRed, make_tuple(9U)),
make_test<uint32_t, uint32_t>(
"should get kColorBrightGreen when background color is 10", kColorBrightGreen, make_tuple(10U)),
make_test<uint32_t, uint32_t>(
"should get kColorBrightCyan when background color is 11", kColorBrightYellow, make_tuple(11U)),
make_test<uint32_t, uint32_t>(
"should get kColorBrightRed when background color is 12", kColorBrightBlue, make_tuple(12U)),
make_test<uint32_t, uint32_t>(
"should get kColorBrightMagenta when background color is 13", kColorBrightMagenta, make_tuple(13U)),
make_test<uint32_t, uint32_t>(
"should get kColorBrightYellow when background color is 14", kColorBrightCyan, make_tuple(14U)),
make_test<uint32_t, uint32_t>(
"should get kColorBrightWhite when background color is 15", kColorBrightWhite, make_tuple(15U)),
})));
}
TestResults test_GetForegroundColor() {
auto fnToTest = [](uint8_t foreground_color) -> uint8_t {
SBF::g_foreground_color = foreground_color;
return GetForegroundColor();
};
return execute_suite<uint32_t, uint32_t>(make_test_suite(
"SBF::GetForegroundColor",
fnToTest,
vector<TestTuple<uint32_t, uint32_t>>({
make_test<uint32_t, uint32_t>(
"should get kColorDarkBlack when foreground color is 0", kColorDarkBlack, make_tuple(0U)),
make_test<uint32_t, uint32_t>(
"should get kColorDarkBlue when foreground color is 1", kColorDarkRed, make_tuple(1U)),
make_test<uint32_t, uint32_t>(
"should get kColorDarkGreen when foreground color is 2", kColorDarkGreen, make_tuple(2U)),
make_test<uint32_t, uint32_t>(
"should get kColorDarkCyan when foreground color is 3", kColorDarkYellow, make_tuple(3U)),
make_test<uint32_t, uint32_t>(
"should get kColorDarkRed when foreground color is 4", kColorDarkBlue, make_tuple(4U)),
make_test<uint32_t, uint32_t>(
"should get kColorDarkMagenta when foreground color is 5", kColorDarkMagenta, make_tuple(5U)),
make_test<uint32_t, uint32_t>(
"should get kColorDarkYellow when foreground color is 6", kColorDarkCyan, make_tuple(6U)),
make_test<uint32_t, uint32_t>(
"should get kColorDarkWhite when foreground color is 7", kColorDarkWhite, make_tuple(7U)),
make_test<uint32_t, uint32_t>(
"should get kColorBrightBlack when foreground color is 8", kColorBrightBlack, make_tuple(8U)),
make_test<uint32_t, uint32_t>(
"should get kColorBrightBlue when foreground color is 9", kColorBrightRed, make_tuple(9U)),
make_test<uint32_t, uint32_t>(
"should get kColorBrightGreen when foreground color is 10", kColorBrightGreen, make_tuple(10U)),
make_test<uint32_t, uint32_t>(
"should get kColorBrightCyan when foreground color is 11", kColorBrightYellow, make_tuple(11U)),
make_test<uint32_t, uint32_t>(
"should get kColorBrightRed when foreground color is 12", kColorBrightBlue, make_tuple(12U)),
make_test<uint32_t, uint32_t>(
"should get kColorBrightMagenta when foreground color is 13", kColorBrightMagenta, make_tuple(13U)),
make_test<uint32_t, uint32_t>(
"should get kColorBrightYellow when foreground color is 14", kColorBrightCyan, make_tuple(14U)),
make_test<uint32_t, uint32_t>(
"should get kColorBrightWhite when foreground color is 15", kColorBrightWhite, make_tuple(15U)),
})));
}
TestResults test_SetBackgroundColor() {
TestResults results;
auto set_background_color_get_background_color = [](uint8_t color) -> uint8_t {
SBF::g_background_color = 255;
SetBackgroundColor(color);
return SBF::g_background_color;
};
results += execute_suite<uint32_t, uint32_t>(make_test_suite(
"SBF::SetBackgroundColor",
set_background_color_get_background_color,
vector<TestTuple<uint32_t, uint32_t>>({
make_test<uint32_t, uint32_t>(
"should set background color to kColorDarkBlack when color is 0", kColorDarkBlack, make_tuple(0U)),
make_test<uint32_t, uint32_t>(
"should set background color to kColorDarkBlue when color is 4", kColorDarkBlue, make_tuple(4U)),
make_test<uint32_t, uint32_t>(
"should set background color to kColorDarkGreen when color is 2", kColorDarkGreen, make_tuple(2U)),
make_test<uint32_t, uint32_t>(
"should set background color to kColorDarkCyan when color is 6", kColorDarkCyan, make_tuple(6U)),
make_test<uint32_t, uint32_t>(
"should set background color to kColorDarkRed when color is 1", kColorDarkRed, make_tuple(1U)),
make_test<uint32_t, uint32_t>(
"should set background color to kColorDarkMagenta when color is 5", kColorDarkMagenta, make_tuple(5U)),
make_test<uint32_t, uint32_t>(
"should set background color to kColorDarkYellow when color is 3", kColorDarkYellow, make_tuple(3U)),
make_test<uint32_t, uint32_t>(
"should set background color to kColorDarkWhite when color is 7", kColorDarkWhite, make_tuple(7U)),
make_test<uint32_t, uint32_t>(
"should set background color to kColorBrightBlack when color is 8", kColorBrightBlack, make_tuple(8U)),
make_test<uint32_t, uint32_t>(
"should set background color to kColorBrightBlue when color is 12", kColorBrightBlue, make_tuple(12U)),
make_test<uint32_t, uint32_t>(
"should set background color to kColorBrightGreen when color is 10", kColorBrightGreen, make_tuple(10U)),
make_test<uint32_t, uint32_t>(
"should set background color to kColorBrightCyan when color is 14", kColorBrightCyan, make_tuple(14U)),
make_test<uint32_t, uint32_t>(
"should set background color to kColorBrightRed when color is 9", kColorBrightRed, make_tuple(9U)),
make_test<uint32_t, uint32_t>("should set background color to kColorBrightMagenta when color is 13",
kColorBrightMagenta,
make_tuple(13U)),
make_test<uint32_t, uint32_t>("should set background color to kColorBrightYellow when color is 11",
kColorBrightYellow,
make_tuple(11U)),
make_test<uint32_t, uint32_t>(
"should set background color to kColorBrightWhite when color is 15", kColorBrightWhite, make_tuple(15U)),
})));
auto set_background_color_returns = [](uint8_t color, uint8_t previous_color) -> uint32_t {
SBF::g_background_color = previous_color;
return SetBackgroundColor(color);
};
results += execute_suite<uint32_t, uint8_t, uint8_t>(make_test_suite(
"SBF::SetBackgroundColor",
set_background_color_returns,
vector<TestTuple<uint32_t, uint8_t, uint8_t>>({
make_test<uint32_t, uint8_t, uint8_t>(
"should set return the previous color when changing from kColorBrightYellow to kColorDarkGreen",
uint32_t(kColorBrightYellow),
make_tuple(kColorDarkGreen, kColorBrightYellow)),
make_test<uint32_t, uint8_t, uint8_t>(
"should set return the previous color when changing from kColorBrightMagenta to kColorDarkMagenta",
uint32_t(kColorBrightMagenta),
make_tuple(kColorDarkMagenta, kColorBrightMagenta)),
make_test<uint32_t, uint8_t, uint8_t>(
"should set return the previous color when changing from kColorBrightCyan to kColorBrightCyan",
uint32_t(kColorBrightCyan),
make_tuple(kColorBrightCyan, kColorBrightCyan)),
})));
return results;
}
TestResults test_SetForegroundColor() {
TestResults results;
auto set_foreground_color_get_foreground_color = [](uint8_t color) -> uint8_t {
SBF::g_foreground_color = 255;
SetForegroundColor(color);
return SBF::g_foreground_color;
};
results += execute_suite<uint32_t, uint32_t>(make_test_suite(
"SBF::SetForegroundColor",
set_foreground_color_get_foreground_color,
vector<TestTuple<uint32_t, uint32_t>>({
make_test<uint32_t, uint32_t>(
"should set foreground color to kColorDarkBlack when color is 0", kColorDarkBlack, make_tuple(0U)),
make_test<uint32_t, uint32_t>(
"should set foreground color to kColorDarkBlue when color is 4", kColorDarkBlue, make_tuple(4U)),
make_test<uint32_t, uint32_t>(
"should set foreground color to kColorDarkGreen when color is 2", kColorDarkGreen, make_tuple(2U)),
make_test<uint32_t, uint32_t>(
"should set foreground color to kColorDarkCyan when color is 6", kColorDarkCyan, make_tuple(6U)),
make_test<uint32_t, uint32_t>(
"should set foreground color to kColorDarkRed when color is 1", kColorDarkRed, make_tuple(1U)),
make_test<uint32_t, uint32_t>(
"should set foreground color to kColorDarkMagenta when color is 5", kColorDarkMagenta, make_tuple(5U)),
make_test<uint32_t, uint32_t>(
"should set foreground color to kColorDarkYellow when color is 3", kColorDarkYellow, make_tuple(3U)),
make_test<uint32_t, uint32_t>(
"should set foreground color to kColorDarkWhite when color is 7", kColorDarkWhite, make_tuple(7U)),
make_test<uint32_t, uint32_t>(
"should set foreground color to kColorBrightBlack when color is 8", kColorBrightBlack, make_tuple(8U)),
make_test<uint32_t, uint32_t>(
"should set foreground color to kColorBrightBlue when color is 12", kColorBrightBlue, make_tuple(12U)),
make_test<uint32_t, uint32_t>(
"should set foreground color to kColorBrightGreen when color is 10", kColorBrightGreen, make_tuple(10U)),
make_test<uint32_t, uint32_t>(
"should set foreground color to kColorBrightCyan when color is 14", kColorBrightCyan, make_tuple(14U)),
make_test<uint32_t, uint32_t>(
"should set foreground color to kColorBrightRed when color is 9", kColorBrightRed, make_tuple(9U)),
make_test<uint32_t, uint32_t>("should set foreground color to kColorBrightMagenta when color is 13",
kColorBrightMagenta,
make_tuple(13U)),
make_test<uint32_t, uint32_t>("should set foreground color to kColorBrightYellow when color is 11",
kColorBrightYellow,
make_tuple(11U)),
make_test<uint32_t, uint32_t>(
"should set foreground color to kColorBrightWhite when color is 15", kColorBrightWhite, make_tuple(15U)),
})));
auto set_foreground_color_returns = [](uint8_t color, uint8_t previous_color) -> uint32_t {
SBF::g_foreground_color = previous_color;
return SetForegroundColor(color);
};
results += execute_suite<uint32_t, uint8_t, uint8_t>(make_test_suite(
"SBF::SetForegroundColor",
set_foreground_color_returns,
vector<TestTuple<uint32_t, uint8_t, uint8_t>>({
make_test<uint32_t, uint8_t, uint8_t>(
"should set return the previous color when changing from kColorBrightYellow to kColorDarkGreen",
uint32_t(kColorBrightYellow),
make_tuple(kColorDarkGreen, kColorBrightYellow)),
make_test<uint32_t, uint8_t, uint8_t>(
"should set return the previous color when changing from kColorBrightMagenta to kColorDarkMagenta",
uint32_t(kColorBrightMagenta),
make_tuple(kColorDarkMagenta, kColorBrightMagenta)),
make_test<uint32_t, uint8_t, uint8_t>(
"should set return the previous color when changing from kColorBrightCyan to kColorBrightCyan",
uint32_t(kColorBrightCyan),
make_tuple(kColorBrightCyan, kColorBrightCyan)),
})));
return results;
}
TestResults test_Reset() {
string suite_name = "SBF::Reset";
string test_name = "should write the reset code to the stream";
string expected = "\\033[m";
auto fnToTest = []() -> string {
ostringstream os;
os << Reset;
return escape_string(os.str());
};
return execute_suite(make_test_suite(
suite_name, fnToTest, vector<TestTuple<string>>({make_test<string>(test_name, expected, make_tuple())})));
}
TestResults test_ForegroundColor() {
auto fnToTest = [](uint8_t color) -> string {
SetForegroundColor(color);
ostringstream os;
os << ForegroundColor;
return escape_string(os.str());
};
return execute_suite(
make_test_suite("SBF::ForegroundColor",
fnToTest,
vector<TestTuple<string, uint8_t>>({
make_test<string, uint8_t>("should write \"\\033[38;5;15m\" kColorBrightWhite to the stream",
"\\033[38;5;15m",
make_tuple(kColorBrightWhite)),
make_test<string, uint8_t>("should write \"\\033[38;5;11m\" kColorBrightYellow to the stream",
"\\033[38;5;11m",
make_tuple(kColorBrightYellow)),
make_test<string, uint8_t>("should write \"\\033[38;5;2m\" kColorDarkGreen to the stream",
"\\033[38;5;2m",
make_tuple(kColorDarkGreen)),
make_test<string, uint8_t>("should write \"\\033[38;5;4m\" kColorDarkBlue to the stream",
"\\033[38;5;4m",
make_tuple(kColorDarkBlue)),
})));
}
TestResults test_BackgroundColor() {
auto fnToTest = [](uint8_t color) -> string {
SetBackgroundColor(color);
ostringstream os;
os << BackgroundColor;
return escape_string(os.str());
};
return execute_suite(
make_test_suite("SBF::BackgroundColor",
fnToTest,
vector<TestTuple<string, uint8_t>>({
make_test<string, uint8_t>("should write \"\\033[48;5;15m\" kColorBrightWhite to the stream",
"\\033[48;5;15m",
make_tuple(kColorBrightWhite)),
make_test<string, uint8_t>("should write \"\\033[48;5;11m\" kColorBrightYellow to the stream",
"\\033[48;5;11m",
make_tuple(kColorBrightYellow)),
make_test<string, uint8_t>("should write \"\\033[48;5;2m\" kColorDarkGreen to the stream",
"\\033[48;5;2m",
make_tuple(kColorDarkGreen)),
make_test<string, uint8_t>("should write \"\\033[48;5;4m\" kColorDarkBlue to the stream",
"\\033[48;5;4m",
make_tuple(kColorDarkBlue)),
})));
}
TestResults test_Colors() {
// TODO: Find a way to check for the presence of both colors and no extra characters without regard to their order.
auto fnToTest = [](uint8_t foreground_color, uint8_t background_color) -> string {
ostringstream error_message;
SetForegroundColor(foreground_color);
SetBackgroundColor(background_color);
regex foreground_color_regex("\033\\[38;5;" + to_string(foreground_color) + "m");
regex background_color_regex("\033\\[48;5;" + to_string(background_color) + "m");
ostringstream os;
os << SBF::Colors;
string colors_string = os.str();
if (!regex_search(colors_string, foreground_color_regex)) {
error_message << "missing foreground color ";
}
if (!regex_search(colors_string, background_color_regex)) {
error_message << "missing background color ";
}
if (!regex_replace(regex_replace(colors_string, foreground_color_regex, ""), background_color_regex, "").empty()) {
error_message << "extra characters in stream ";
}
if (!error_message.str().empty()) {
error_message << " actual: " << escape_string(os.str());
return error_message.str();
}
return "no errors";
};
return execute_suite(make_test_suite(
"SBF::Colors",
fnToTest,
vector<TestTuple<string, uint8_t, uint8_t>>({
make_test<string, uint8_t, uint8_t>("should write \"\\033[38;5;15m\\033[48;5;6m\" to the stream",
"no errors",
make_tuple(kColorBrightWhite, kColorDarkCyan)),
make_test<string, uint8_t, uint8_t>("should write \"\\033[38;5;11m\\033[48;5;2m\" to the stream",
"no errors",
make_tuple(kColorBrightYellow, kColorDarkGreen)),
make_test<string, uint8_t, uint8_t>("should write \"\\033[38;5;2m\\033[48;5;13m\" to the stream",
"no errors",
make_tuple(kColorDarkGreen, kColorBrightMagenta)),
make_test<string, uint8_t, uint8_t>("should write \"\\033[38;5;4m\\033[48;5;1m\" to the stream",
"no errors",
make_tuple(kColorDarkBlue, kColorDarkRed)),
})));
}
TestResults test_TrueColorForeground() {
// TODO: test_TrueColorForeground();
return TestResults().skip("// TODO: test_TrueColorForeground");
}
TestResults test_TrueColorBackground() {
// TODO: test_TrueColorBackground();
return TestResults().skip("// TODO: test_TrueColorBackground");
}
int main(int argc, char* argv[]) {
TestResults results;
results += test_FillColors();
results += test_GetBackgroundColor();
results += test_GetForegroundColor();
results += test_SetBackgroundColor();
results += test_SetForegroundColor();
results += test_Reset();
results += test_ForegroundColor();
results += test_BackgroundColor();
results += test_Colors();
results += test_TrueColorForeground();
results += test_TrueColorBackground();
PrintResults(cout, results);
return results.failed() + results.errors();
}

217
sbf-cpp/Derangements.cpp Normal file
View File

@@ -0,0 +1,217 @@
#include "Derangements.h"
#include <ostream>
namespace SBF {
namespace {
using std::ostream;
using std::string;
using std::vector;
} // End namespace
const uint8_t kDerangementAmnesiaTextColor = kColorDarkRed;
const std::string kDerangementAmnesiaLabel = "Amnesia";
const std::string kDerangementAmnesiaDescription =
R"---(You forget a segment of your past. Additionally in some cases a character can forget abilities and be unable to use them for the duration.)---";
const uint8_t kDerangementDelusionsOfGrandeurTextColor = kColorDarkMagenta;
const std::string kDerangementDelusionsOfGrandeurLabel = "Delusions of Grandeur";
const std::string kDerangementDelusionsOfGrandeurDescription = R"---(You imagine you are better than you are.)---";
const uint8_t kDerangementFantasyTextColor = kColorDarkYellow;
const std::string kDerangementFantasyLabel = "Fantasy";
const std::string kDerangementFantasyDescription =
R"---(You enter a self-created world where you are the forgotten hero.)---";
const uint8_t kDerangementManicDepressionTextColor = kColorDarkWhite;
const std::string kDerangementManicDepressionLabel = "Manic-Depression";
const std::string kDerangementManicDepressionDescription =
R"---(You sink into deep and fitful depressions, showing no interest in anything which used to captivate your interests. You will go along with others rather than use the energy to resist. Occasional fits of great energy grab hold of you, and you will work for hours or even days on your projects. During this time you will resist even the need for sleep as you burn up blood and Willpower on your schemes.)---";
const uint8_t kDerangementMultiplePersonalitiesTextColor = kColorDarkBlue;
const std::string kDerangementMultiplePersonalitiesLabel = "Multiple Personalities";
const std::string kDerangementMultiplePersonalitiesDescription =
R"---(You possess a number of new personalities. You have amore than one Mature, and will switch between them. Thus you regain Willpower points in defferent ways at defferent times)---";
const uint8_t kDerangementObsessionTextColor = kColorBrightGreen;
const std::string kDerangementObsessionLabel = "Obsession";
const std::string kDerangementObsessionDescription = R"---(You become obsessed with some interest or fetish.)---";
const uint8_t kDerangementOvercompensationTextColor = kColorBrightCyan;
const std::string kDerangementOvercompensationLabel = "Overcompensation";
const std::string kDerangementOvercompensationDescription =
R"---(You make up for your moral weaknesses by playing up your strengths to an extreme. You don't think you can frenzy and won't stop it.)---";
const uint8_t kDerangementParanoiaTextColor = kColorBrightRed;
const std::string kDerangementParanoiaLabel = "Paranoia";
const std::string kDerangementParanoiaDescription =
R"---(You are convinced that you are being hunted. You hold even your closest Friends under suspicion.)---";
const uint8_t kDerangementPerfectionTextColor = kColorBrightMagenta;
const std::string kDerangementPerfectionLabel = "Perfection";
const std::string kDerangementPerfectionDescription =
R"---(All your energy is directed toward preventing anything from going wong. When it does you must make a self-control roll or frenzy.)---";
const uint8_t kDerangementRegressionTextColor = kColorBrightYellow;
const std::string kDerangementRegressionLabel = "Regression";
const std::string kDerangementRegressionDescription =
R"---(You become childlike retreating to an earlier time when less was expected of you Willpower is regained inthe way a Child's is.)---";
const DerangementType kDerangementUnknown = {0, "", "", 0};
const DerangementType kDerangementAmnesia = {
kDerangementAmnesiaId,
kDerangementAmnesiaLabel,
kDerangementAmnesiaDescription,
kDerangementAmnesiaTextColor,
};
const DerangementType kDerangementDelusionsOfGrandeur = {
kDerangementDelusionsOfGrandeurId,
kDerangementDelusionsOfGrandeurLabel,
kDerangementDelusionsOfGrandeurDescription,
kDerangementDelusionsOfGrandeurTextColor,
};
const DerangementType kDerangementFantasy = {
kDerangementFantasyId,
kDerangementFantasyLabel,
kDerangementFantasyDescription,
kDerangementFantasyTextColor,
};
const DerangementType kDerangementManicDepression = {
kDerangementManicDepressionId,
kDerangementManicDepressionLabel,
kDerangementManicDepressionDescription,
kDerangementManicDepressionTextColor,
};
const DerangementType kDerangementMultiplePersonalities = {
kDerangementMultiplePersonalitiesId,
kDerangementMultiplePersonalitiesLabel,
kDerangementMultiplePersonalitiesDescription,
kDerangementMultiplePersonalitiesTextColor,
};
const DerangementType kDerangementObsession = {
kDerangementObsessionId,
kDerangementObsessionLabel,
kDerangementObsessionDescription,
kDerangementObsessionTextColor,
};
const DerangementType kDerangementOvercompensation = {
kDerangementOvercompensationId,
kDerangementOvercompensationLabel,
kDerangementOvercompensationDescription,
kDerangementOvercompensationTextColor,
};
const DerangementType kDerangementParanoia = {
kDerangementParanoiaId,
kDerangementParanoiaLabel,
kDerangementParanoiaDescription,
kDerangementParanoiaTextColor,
};
const DerangementType kDerangementPerfection = {
kDerangementPerfectionId,
kDerangementPerfectionLabel,
kDerangementPerfectionDescription,
kDerangementPerfectionTextColor,
};
const DerangementType kDerangementRegression = {
kDerangementRegressionId,
kDerangementRegressionLabel,
kDerangementRegressionDescription,
kDerangementRegressionTextColor,
};
const int kDerangementsCount = 10;
const uint8_t kDerangementTextColors[] = {
0,
kDerangementAmnesiaTextColor,
kDerangementDelusionsOfGrandeurTextColor,
kDerangementFantasyTextColor,
kDerangementManicDepressionTextColor,
kDerangementMultiplePersonalitiesTextColor,
kDerangementObsessionTextColor,
kDerangementOvercompensationTextColor,
kDerangementParanoiaTextColor,
kDerangementPerfectionTextColor,
kDerangementRegressionTextColor,
};
const std::string kDerangementLabels[] = {
"",
kDerangementAmnesiaLabel,
kDerangementDelusionsOfGrandeurLabel,
kDerangementFantasyLabel,
kDerangementManicDepressionLabel,
kDerangementMultiplePersonalitiesLabel,
kDerangementObsessionLabel,
kDerangementOvercompensationLabel,
kDerangementParanoiaLabel,
kDerangementPerfectionLabel,
kDerangementRegressionLabel,
};
const std::string kDerangementDescriptions[] = {
"",
kDerangementAmnesiaDescription,
kDerangementDelusionsOfGrandeurDescription,
kDerangementFantasyDescription,
kDerangementManicDepressionDescription,
kDerangementMultiplePersonalitiesDescription,
kDerangementObsessionDescription,
kDerangementOvercompensationDescription,
kDerangementParanoiaDescription,
kDerangementPerfectionDescription,
kDerangementRegressionDescription,
};
const DerangementType kDerangements[] = {
{0, "", "", 0},
kDerangementAmnesia,
kDerangementDelusionsOfGrandeur,
kDerangementFantasy,
kDerangementManicDepression,
kDerangementMultiplePersonalities,
kDerangementObsession,
kDerangementOvercompensation,
kDerangementParanoia,
kDerangementPerfection,
kDerangementRegression,
};
bool operator==(const DerangementType& left, const DerangementType& right) {
return left.description == right.description && left.id == right.id && left.label == right.label
&& left.textColor == right.textColor;
}
bool operator!=(const DerangementType& left, const DerangementType& right) {
return !(left == right);
}
ostream& operator<<(ostream& os, const DerangementType& derangement) {
os << "Derangement: {id: " << derangement.id << ", label: \"" << derangement.label << "\", description: \""
<< derangement.description << "\", textColor: " << (int)derangement.textColor << "}";
return os;
}
DerangementType GetDerangement(int derangementId) {
if (derangementId > 0 && derangementId <= kDerangementsCount) {
return kDerangements[derangementId];
}
return kDerangementUnknown;
}
void FillDerangements(vector<DerangementType>& derangements) {
derangements.clear();
for (int id = 1; id <= kDerangementsCount; id++) {
derangements.push_back(GetDerangement(id));
}
}
vector<string> GetDerangementLabels() {
vector<string> labels;
for (int id = 1; id <= kDerangementsCount; id++) {
labels.push_back(kDerangementLabels[id]);
}
return labels;
}
vector<uint8_t> GetDerangementColors() {
vector<uint8_t> colors;
for (int id = 1; id <= kDerangementsCount; id++) {
colors.push_back(kDerangementTextColors[id]);
}
return colors;
}
} // End namespace SBF

70
sbf-cpp/Derangements.h Normal file
View File

@@ -0,0 +1,70 @@
#ifndef DERANGEMENTS_H__
#define DERANGEMENTS_H__
#include "Colors.h"
/***************************************************************************************
* @file Derangements.h
*
* @brief Defines constants and functions for working with derangements.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
#include <string>
#include <vector>
/** \addtogroup Derangements
* @{
*/
namespace SBF {
struct DerangementType {
int id;
std::string label;
std::string description;
uint8_t textColor;
};
const int kDerangementAmnesiaId = 1;
const int kDerangementDelusionsOfGrandeurId = 2;
const int kDerangementFantasyId = 3;
const int kDerangementManicDepressionId = 4;
const int kDerangementMultiplePersonalitiesId = 5;
const int kDerangementObsessionId = 6;
const int kDerangementOvercompensationId = 7;
const int kDerangementParanoiaId = 8;
const int kDerangementPerfectionId = 9;
const int kDerangementRegressionId = 10;
/// @brief Determines the equality of two derangements.
/// @param left A derangement to compare.
/// @param right A derangement to compare.
/// @return True if the derangements are equal and false otherwise.
bool operator==(const DerangementType& left, const DerangementType& right);
/// @brief Determines the inequality of two derangements.
/// @param left A derangement to compare.
/// @param right A derangement to compare.
/// @return Tre if the derangements are unequal and false otherwise.
bool operator!=(const DerangementType& left, const DerangementType& right);
/// @brief Writes a derangement to an ostream.
/// @param os The ostream to write to.
/// @param derangement The derangement to write.
/// @return The provided ostream for chaining.
std::ostream& operator<<(std::ostream& os, const DerangementType& derangement);
/// @brief Gets the derangement with a specific id.
/// @param derangement_id The id of the derangement to get.
/// @return The derangement with an id of derangement_id or kDerangementUnknown if derangement_id is invalid.
DerangementType GetDerangement(int derangement_id);
/// @brief Fills the provided vector with all valid derangements.
/// @param derangements The vector to fill. It will be emptied first.
void FillDerangements(std::vector<DerangementType>& derangements);
std::vector<std::string> GetDerangementLabels();
std::vector<uint8_t> GetDerangementColors();
} // End namespace SBF
/** @}*/
#endif // End !defined DERANGEMENTS_H__

View File

@@ -0,0 +1,249 @@
#include "Derangements.h"
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#include "test.h"
namespace {
using namespace SBF;
using namespace Test;
using namespace std;
} // namespace
const DerangementType kDerangementAmnesia = {
1,
"Amnesia",
R"---(You forget a segment of your past. Additionally in some cases a character can forget abilities and be unable to use them for the duration.)---",
1,
};
const DerangementType kDerangementDelusionsOfGrandeur = {
2,
"Delusions of Grandeur",
R"---(You imagine you are better than you are.)---",
5,
};
const DerangementType kDerangementFantasy = {
3,
"Fantasy",
R"---(You enter a self-created world where you are the forgotten hero.)---",
3,
};
const DerangementType kDerangementManicDepression = {
4,
"Manic-Depression",
R"---(You sink into deep and fitful depressions, showing no interest in anything which used to captivate your interests. You will go along with others rather than use the energy to resist. Occasional fits of great energy grab hold of you, and you will work for hours or even days on your projects. During this time you will resist even the need for sleep as you burn up blood and Willpower on your schemes.)---",
7,
};
const DerangementType kDerangementMultiplePersonalities = {
5,
"Multiple Personalities",
R"---(You possess a number of new personalities. You have amore than one Mature, and will switch between them. Thus you regain Willpower points in defferent ways at defferent times)---",
4,
};
const DerangementType kDerangementObsession = {
6,
"Obsession",
R"---(You become obsessed with some interest or fetish.)---",
10,
};
const DerangementType kDerangementOvercompensation = {
7,
"Overcompensation",
R"---(You make up for your moral weaknesses by playing up your strengths to an extreme. You don't think you can frenzy and won't stop it.)---",
14,
};
const DerangementType kDerangementParanoia = {
8,
"Paranoia",
R"---(You are convinced that you are being hunted. You hold even your closest Friends under suspicion.)---",
9,
};
const DerangementType kDerangementPerfection = {
9,
"Perfection",
R"---(All your energy is directed toward preventing anything from going wong. When it does you must make a self-control roll or frenzy.)---",
13,
};
const DerangementType kDerangementRegression = {
10,
"Regression",
R"---(You become childlike retreating to an earlier time when less was expected of you Willpower is regained inthe way a Child's is.)---",
11,
};
TestResults test_GetDerangement() {
return execute_suite<DerangementType, int>(make_test_suite(
"SBF::GetDerangement",
GetDerangement,
vector<TestTuple<DerangementType, int>>({
make_test<DerangementType, int>(
"should get {0, \"\", \"\", 0} for invalid derangement id 0", {0, "", "", 0}, make_tuple(0)),
make_test<DerangementType, int>(
"should get amnesia for derangement id 1", kDerangementAmnesia, make_tuple(1)),
make_test<DerangementType, int>(
"should get delusions of grandeur for derangement id 2", kDerangementDelusionsOfGrandeur, make_tuple(2)),
make_test<DerangementType, int>(
"should get fantasy for derangement id 3", kDerangementFantasy, make_tuple(3)),
make_test<DerangementType, int>(
"should get manic-depression for derangement id 4", kDerangementManicDepression, make_tuple(4)),
make_test<DerangementType, int>("should get multiple personalities for derangement id 5",
kDerangementMultiplePersonalities,
make_tuple(5)),
make_test<DerangementType, int>(
"should get obsession for derangement id 6", kDerangementObsession, make_tuple(6)),
make_test<DerangementType, int>(
"should get overcompensation for derangement id 7", kDerangementOvercompensation, make_tuple(7)),
make_test<DerangementType, int>(
"should get paranoia for derangement id 8", kDerangementParanoia, make_tuple(8)),
make_test<DerangementType, int>(
"should get perfection for derangement id 9", kDerangementPerfection, make_tuple(9)),
make_test<DerangementType, int>(
"should get regression for derangement id 10", kDerangementRegression, make_tuple(10)),
make_test<DerangementType, int>(
"should get {0, \"\", \"\", 0} for invalid derangement id 11", {0, "", "", 0}, make_tuple(11)),
})));
}
TestResults test_FillDerangements() {
auto fnToTest = []() -> string {
ostringstream error_message;
vector<DerangementType> expected = {
kDerangementAmnesia,
kDerangementDelusionsOfGrandeur,
kDerangementFantasy,
kDerangementManicDepression,
kDerangementMultiplePersonalities,
kDerangementObsession,
kDerangementOvercompensation,
kDerangementParanoia,
kDerangementPerfection,
kDerangementRegression,
};
vector<DerangementType> actual = {{-1, "This should be removed.", "This should be removed.", 255}};
FillDerangements(actual);
compare(error_message, expected, actual);
string error = error_message.str();
if (error.size() > 0) {
return error;
}
return "no errors";
};
return execute_suite<string>(
make_test_suite("SBF::FillDerangements",
fnToTest,
vector<TestTuple<string>>({
make_test<string>("should fill derangements", "no errors", make_tuple()),
})));
}
TestResults test_DerangementType_operator_extract() {
return execute_suite<string, DerangementType>(make_test_suite(
"SBF::DerangementType::operator<<",
[](const DerangementType& derangement) {
ostringstream os;
os << derangement;
return os.str();
},
vector<TestTuple<string, DerangementType>>({
make_test<string, DerangementType>("should print amnesia",
"Derangement: {id: 1, label: \"Amnesia\", description: \""
+ kDerangementAmnesia.description + "\", textColor: 1}",
make_tuple(kDerangementAmnesia)),
make_test<string, DerangementType>("should print delusions of grandeur",
"Derangement: {id: 2, label: \"Delusions of Grandeur\", description: \""
+ kDerangementDelusionsOfGrandeur.description + "\", textColor: 5}",
make_tuple(kDerangementDelusionsOfGrandeur)),
make_test<string, DerangementType>("should print fantasy",
"Derangement: {id: 3, label: \"Fantasy\", description: \""
+ kDerangementFantasy.description + "\", textColor: 3}",
make_tuple(kDerangementFantasy)),
make_test<string, DerangementType>("should print manic depression",
"Derangement: {id: 4, label: \"Manic-Depression\", description: \""
+ kDerangementManicDepression.description + "\", textColor: 7}",
make_tuple(kDerangementManicDepression)),
make_test<string, DerangementType>("should print multiple personalities",
"Derangement: {id: 5, label: \"Multiple Personalities\", description: \""
+ kDerangementMultiplePersonalities.description + "\", textColor: 4}",
make_tuple(kDerangementMultiplePersonalities)),
make_test<string, DerangementType>("should print obsession",
"Derangement: {id: 6, label: \"Obsession\", description: \""
+ kDerangementObsession.description + "\", textColor: 10}",
make_tuple(kDerangementObsession)),
make_test<string, DerangementType>("should print overcompensation",
"Derangement: {id: 7, label: \"Overcompensation\", description: \""
+ kDerangementOvercompensation.description + "\", textColor: 14}",
make_tuple(kDerangementOvercompensation)),
make_test<string, DerangementType>("should print paranoia",
"Derangement: {id: 8, label: \"Paranoia\", description: \""
+ kDerangementParanoia.description + "\", textColor: 9}",
make_tuple(kDerangementParanoia)),
make_test<string, DerangementType>("should print perfection",
"Derangement: {id: 9, label: \"Perfection\", description: \""
+ kDerangementPerfection.description + "\", textColor: 13}",
make_tuple(kDerangementPerfection)),
make_test<string, DerangementType>("should print regression",
"Derangement: {id: 10, label: \"Regression\", description: \""
+ kDerangementRegression.description + "\", textColor: 11}",
make_tuple(kDerangementRegression)),
make_test<string, DerangementType>(
"should print an unknown derangement",
"Derangement: {id: 0, label: \"MyLabel\", description: \"My Description\", textColor: 5}",
make_tuple((DerangementType){0, "MyLabel", "My Description", 5})),
})));
}
TestResults test_DerangementType_operator_equal_to() {
return execute_suite<bool, DerangementType, DerangementType>(make_test_suite(
"SBF::DerangementType::operator==",
[](const DerangementType& left, const DerangementType& right) { return left == right; },
vector<TestTuple<bool, DerangementType, DerangementType>>({
make_test<bool, DerangementType, DerangementType>(
"should return true when comparing a derangement to itself",
true,
make_tuple(kDerangementDelusionsOfGrandeur, kDerangementDelusionsOfGrandeur)),
make_test<bool, DerangementType, DerangementType>(
"should return true when comparing two different instances created with the same values",
true,
make_tuple(kDerangementFantasy, DerangementType({3, "Fantasy", kDerangementFantasy.description, 3}))),
make_test<bool, DerangementType, DerangementType>(
"should return false when comparing two different derangements",
false,
make_tuple(kDerangementManicDepression, kDerangementParanoia)),
})));
}
TestResults test_DerangementType_operator_not_equal_to() {
return execute_suite<bool, DerangementType, DerangementType>(make_test_suite(
"SBF::DerangementType::operator!=",
[](const DerangementType& left, const DerangementType& right) { return left != right; },
vector<TestTuple<bool, DerangementType, DerangementType>>({
make_test<bool, DerangementType, DerangementType>(
"should return false when comparing a derangement to itself",
false,
make_tuple(kDerangementDelusionsOfGrandeur, kDerangementDelusionsOfGrandeur)),
make_test<bool, DerangementType, DerangementType>(
"should return false when comparing two different instances created with the same values",
false,
make_tuple(kDerangementFantasy, DerangementType({3, "Fantasy", kDerangementFantasy.description, 3}))),
make_test<bool, DerangementType, DerangementType>(
"should return true when comparing two different derangements",
true,
make_tuple(kDerangementManicDepression, kDerangementParanoia)),
})));
}
int main(int argc, char* argv[]) {
TestResults results;
results += test_DerangementType_operator_extract();
results += test_DerangementType_operator_equal_to();
results += test_DerangementType_operator_not_equal_to();
results += test_GetDerangement();
results += test_FillDerangements();
return results.failed() + results.errors();
}

81
sbf-cpp/Disciplines.cpp Normal file
View File

@@ -0,0 +1,81 @@
#include "Disciplines.h"
#include <string>
#include <vector>
namespace SBF {
using std::string;
using std::vector;
const std::string kDisciplineAnimalismLabel = "Animalism";
const std::string kDisciplineAuspexLabel = "Auspex";
const std::string kDisciplineBardoLabel = "Bardo";
const std::string kDisciplineCelerityLabel = "Celerity";
const std::string kDisciplineChimestryLabel = "Chimestry";
const std::string kDisciplineDementationLabel = "Dementation";
const std::string kDisciplineDominateLabel = "Dominate";
const std::string kDisciplineFortitudeLabel = "Fortitude";
const std::string kDisciplineMelpomineeLabel = "Melpominee";
const std::string kDisciplineMortisLabel = "Mortis";
const std::string kDisciplineMytherceriaLabel = "Mytherceria";
const std::string kDisciplineNecromancyLabel = "Necromancy";
const std::string kDisciplineObeahLabel = "Obeah";
const std::string kDisciplineObfuscateLabel = "Obfuscate";
const std::string kDisciplineObtenebrationLabel = "Obtenebration";
const std::string kDisciplinePotenceLabel = "Potence";
const std::string kDisciplinePresenceLabel = "Presence";
const std::string kDisciplineProteanLabel = "Protean";
const std::string kDisciplineQuietusLabel = "Quietus";
const std::string kDisciplineSerpentisLabel = "Serpentis";
const std::string kDisciplineSpiritusLabel = "Spiritus";
const std::string kDisciplineThanantosisLabel = "Thanantosis";
const std::string kDisciplineThaumaturgyLabel = "Thaumaturgy";
const std::string kDisciplineVicissitudeLabel = "Vicissitude";
const int kDisciplinesCount = 24;
const std::string kDisciplines[] = {
"",
kDisciplineAnimalismLabel,
kDisciplineAuspexLabel,
kDisciplineBardoLabel,
kDisciplineCelerityLabel,
kDisciplineChimestryLabel,
kDisciplineDementationLabel,
kDisciplineDominateLabel,
kDisciplineFortitudeLabel,
kDisciplineMelpomineeLabel,
kDisciplineMortisLabel,
kDisciplineMytherceriaLabel,
kDisciplineNecromancyLabel,
kDisciplineObeahLabel,
kDisciplineObfuscateLabel,
kDisciplineObtenebrationLabel,
kDisciplinePotenceLabel,
kDisciplinePresenceLabel,
kDisciplineProteanLabel,
kDisciplineQuietusLabel,
kDisciplineSerpentisLabel,
kDisciplineSpiritusLabel,
kDisciplineThanantosisLabel,
kDisciplineThaumaturgyLabel,
kDisciplineVicissitudeLabel,
};
string GetDisciplineLabel(int disciplineId) {
if (disciplineId > 0 && disciplineId <= kDisciplinesCount) {
return kDisciplines[disciplineId];
}
return "";
}
void FillDisciplineLabels(vector<string>& labels) {
labels.clear();
for (int id = 1; id <= kDisciplinesCount; id++) {
labels.push_back(GetDisciplineLabel(id));
}
}
int GetNumDisciplines() {
return kDisciplinesCount;
}
} // End namespace SBF

58
sbf-cpp/Disciplines.h Normal file
View File

@@ -0,0 +1,58 @@
#ifndef DISCIPLINES_H__
#define DISCIPLINES_H__
/***************************************************************************************
* @file Disciplines.h
*
* @brief Defines constants and functions for working with disciplines.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
#include <string>
#include <vector>
/** \addtogroup Disciplines
* @{
*/
namespace SBF {
const int kDisciplineAnimalismId = 1;
const int kDisciplineAuspexId = 2;
const int kDisciplineBardoId = 3;
const int kDisciplineCelerityId = 4;
const int kDisciplineChimestryId = 5;
const int kDisciplineDementationId = 6;
const int kDisciplineDominateId = 7;
const int kDisciplineFortitudeId = 8;
const int kDisciplineMelpomineeId = 9;
const int kDisciplineMortisId = 10;
const int kDisciplineMytherceriaId = 11;
const int kDisciplineNecromancyId = 12;
const int kDisciplineObeahId = 13;
const int kDisciplineObfuscateId = 14;
const int kDisciplineObtenebrationId = 15;
const int kDisciplinePotenceId = 16;
const int kDisciplinePresenceId = 17;
const int kDisciplineProteanId = 18;
const int kDisciplineQuietusId = 19;
const int kDisciplineSerpentisId = 20;
const int kDisciplineSpiritusId = 21;
const int kDisciplineThanantosisId = 22;
const int kDisciplineThaumaturgyId = 23;
const int kDisciplineVicissitudeId = 24;
/// @brief Gets the label for a discipline with a specific id.
/// @param discipline_id The id of the discipline to get the label for.
/// @return The label of the discipline or an empty string if discipline_id is invalid.
std::string GetDisciplineLabel(int discipline_id);
/// @brief Fills the provided vector with all of the valid discipline labels.
/// @param discipline_labels The vector to fill. It will be cleared first.
void FillDisciplineLabels(std::vector<std::string>& discipline_labels);
int GetNumDisciplines();
} // End namespace SBF
/** @}*/
#endif // End !defined DISCIPLINES_H__

View File

@@ -0,0 +1,84 @@
#include "Disciplines.h"
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#include "test.h"
namespace {
using namespace SBF;
using namespace Test;
using namespace std;
} // End namespace
TestResults test_GetDisciplineLabel() {
return execute_suite<string, int>(make_test_suite(
"SBF::GetDisciplineLabel",
GetDisciplineLabel,
vector<TestTuple<string, int>>({
make_test<string, int>("should get \"\" for invalid discipline id 0", "", make_tuple(0)),
make_test<string, int>("should get \"Animalism\" for id 1", "Animalism", make_tuple(1)),
make_test<string, int>("should get \"Auspex\" for id 2", "Auspex", make_tuple(2)),
make_test<string, int>("should get \"Bardo\" for id 3", "Bardo", make_tuple(3)),
make_test<string, int>("should get \"Celerity\" for id 4", "Celerity", make_tuple(4)),
make_test<string, int>("should get \"Chimestry\" for id 5", "Chimestry", make_tuple(5)),
make_test<string, int>("should get \"Dementation\" for id 6", "Dementation", make_tuple(6)),
make_test<string, int>("should get \"Dominate\" for id 7", "Dominate", make_tuple(7)),
make_test<string, int>("should get \"Fortitude\" for id 8", "Fortitude", make_tuple(8)),
make_test<string, int>("should get \"Melpominee\" for id 9", "Melpominee", make_tuple(9)),
make_test<string, int>("should get \"Mortis\" for id 10", "Mortis", make_tuple(10)),
make_test<string, int>("should get \"Mytherceria\" for id 11", "Mytherceria", make_tuple(11)),
make_test<string, int>("should get \"Necromancy\" for id 12", "Necromancy", make_tuple(12)),
make_test<string, int>("should get \"Obeah\" for id 13", "Obeah", make_tuple(13)),
make_test<string, int>("should get \"Obfuscate\" for id 14", "Obfuscate", make_tuple(14)),
make_test<string, int>("should get \"Obtenebration\" for id 15", "Obtenebration", make_tuple(15)),
make_test<string, int>("should get \"Potence\" for id 16", "Potence", make_tuple(16)),
make_test<string, int>("should get \"Presence\" for id 17", "Presence", make_tuple(17)),
make_test<string, int>("should get \"Protean\" for id 18", "Protean", make_tuple(18)),
make_test<string, int>("should get \"Quietus\" for id 19", "Quietus", make_tuple(19)),
make_test<string, int>("should get \"Serpentis\" for id 20", "Serpentis", make_tuple(20)),
make_test<string, int>("should get \"Spiritus\" for id 21", "Spiritus", make_tuple(21)),
make_test<string, int>("should get \"Thanantosis\" for id 22", "Thanantosis", make_tuple(22)),
make_test<string, int>("should get \"Thaumaturgy\" for id 23", "Thaumaturgy", make_tuple(23)),
make_test<string, int>("should get \"Vicissitude\" for id 24", "Vicissitude", make_tuple(24)),
make_test<string, int>("should get \"\" for invalid discipline id 25", "", make_tuple(25)),
})));
}
TestResults test_FillDisciplineLabels() {
auto fnToTest = []() -> string {
ostringstream error_message;
vector<string> expected = {
"Animalism", "Auspex", "Bardo", "Celerity", "Chimestry", "Dementation",
"Dominate", "Fortitude", "Melpominee", "Mortis", "Mytherceria", "Necromancy",
"Obeah", "Obfuscate", "Obtenebration", "Potence", "Presence", "Protean",
"Quietus", "Serpentis", "Spiritus", "Thanantosis", "Thaumaturgy", "Vicissitude",
};
vector<string> actual = {"This should be removed."};
FillDisciplineLabels(actual);
compare(error_message, expected, actual);
string error = error_message.str();
if (error.size() > 0) {
return error;
}
return "no errors";
};
return execute_suite<string>(
make_test_suite("SBF::FillDisciplineLabels",
fnToTest,
vector<TestTuple<string>>({
make_test<string>("should fill disciplines", "no errors", make_tuple()),
})));
}
int main(int argc, char* argv[]) {
TestResults results;
results += test_GetDisciplineLabel();
results += test_FillDisciplineLabels();
return results.failed() + results.errors();
}

146
sbf-cpp/Freebies.cpp Normal file
View File

@@ -0,0 +1,146 @@
#include "Freebies.h"
#include <ostream>
namespace SBF {
namespace {
using std::ostream;
using std::vector;
} // End namespace
const int kFreebieDisciplineCost = 7;
const std::string kFreebieDisciplineName = "Discipline";
const std::string kFreebieDisciplineLabel = "Add a discipline dot 7 points";
const FreebieType kFreebieDiscipline = {
kFreebieDisciplineId, kFreebieDisciplineCost, kFreebieDisciplineName, kFreebieDisciplineLabel};
const int kFreebieAttributeCost = 5;
const std::string kFreebieAttributeName = "Attribute";
const std::string kFreebieAttributeLabel = "Add an attribute dot 5 points";
const FreebieType kFreebieAttribute = {
kFreebieAttributeId, kFreebieAttributeCost, kFreebieAttributeName, kFreebieAttributeLabel};
const int kFreebieAbilityCost = 2;
const std::string kFreebieAbilityName = "Ability";
const std::string kFreebieAbilityLabel = "Add an ability dot 2 points";
const FreebieType kFreebieAbility = {kFreebieAbilityId, kFreebieAbilityCost, kFreebieAbilityName, kFreebieAbilityLabel};
const int kFreebieVirtueCost = 2;
const std::string kFreebieVirtueName = "Virtue";
const std::string kFreebieVirtueLabel = "Add a virtue dot 2 points";
const FreebieType kFreebieVirtue = {kFreebieVirtueId, kFreebieVirtueCost, kFreebieVirtueName, kFreebieVirtueLabel};
const int kFreebieHumanityCost = 1;
const std::string kFreebieHumanityName = "Humanity";
const std::string kFreebieHumanityLabel = "Add a humanity dot 1 point";
const FreebieType kFreebieHumanity = {
kFreebieHumanityId, kFreebieHumanityCost, kFreebieHumanityName, kFreebieHumanityLabel};
const int kFreebieBackgroundCost = 1;
const std::string kFreebieBackgroundName = "Background";
const std::string kFreebieBackgroundLabel = "Add a background dot 1 point";
const FreebieType kFreebieBackground = {
kFreebieBackgroundId, kFreebieBackgroundCost, kFreebieBackgroundName, kFreebieBackgroundLabel};
const int kFreebieShowCharacterSheetCost = 0;
const std::string kFreebieShowCharacterSheetName = "Show character sheet";
const std::string kFreebieShowCharacterSheetLabel = "Show character sheet";
const FreebieType kFreebieShowCharacterSheet = {kFreebieShowCharacterSheetId,
kFreebieShowCharacterSheetCost,
kFreebieShowCharacterSheetName,
kFreebieShowCharacterSheetLabel};
const int kFreebieCosts[] = {
0,
kFreebieDisciplineCost,
kFreebieAttributeCost,
kFreebieAbilityCost,
kFreebieVirtueCost,
kFreebieHumanityCost,
kFreebieBackgroundCost,
kFreebieShowCharacterSheetCost,
};
const std::string kFreebieNames[] = {
"",
kFreebieDisciplineName,
kFreebieAttributeName,
kFreebieAbilityName,
kFreebieVirtueName,
kFreebieHumanityName,
kFreebieBackgroundName,
kFreebieShowCharacterSheetName,
};
const std::string kFreebieLabels[] = {
"",
kFreebieDisciplineLabel,
kFreebieAttributeLabel,
kFreebieAbilityLabel,
kFreebieVirtueLabel,
kFreebieHumanityLabel,
kFreebieBackgroundLabel,
kFreebieShowCharacterSheetLabel,
};
const FreebieType kFreebies[] = {
kFreebieUnknown,
kFreebieDiscipline,
kFreebieAttribute,
kFreebieAbility,
kFreebieVirtue,
kFreebieHumanity,
kFreebieBackground,
kFreebieShowCharacterSheet,
};
ostream& operator<<(ostream& os, const FreebieType& freebie) {
os << "Freebie: {id: " << freebie.id << ", cost: " << freebie.cost << ", name: \"" << freebie.name << "\", label: \""
<< freebie.label << "\"}";
return os;
}
bool operator==(const FreebieType& left, const FreebieType& right) {
return left.id == right.id && left.cost == right.cost && left.name == right.name && left.label == right.label;
}
bool operator!=(const FreebieType& left, const FreebieType& right) {
return !(left == right);
}
FreebieType GetFreebie(int freebie_id) {
if (freebie_id > 0 && freebie_id <= kFreebiesCount) {
return kFreebies[freebie_id];
}
return kFreebieUnknown;
}
void FillFreebies(vector<FreebieType>& freebies) {
freebies.clear();
for (int id = 1; id <= kFreebiesCount; id++) {
freebies.push_back(GetFreebie(id));
}
}
vector<FreebieType> GetFreebies() {
vector<FreebieType> freebies;
FillFreebies(freebies);
return freebies;
}
vector<FreebieType> GetAvailableFreebies(int freebie_points) {
vector<FreebieType> freebies;
for (int id = 1; id <= kFreebiesCount; id++) {
FreebieType freebie = GetFreebie(id);
if (freebie.cost <= freebie_points) {
freebies.push_back(freebie);
}
}
return freebies;
}
int GetFreebieCost(int id) {
if (id > 0 && id <= kFreebiesCount) {
return kFreebieCosts[id];
}
return 0;
}
} // End namespace SBF

62
sbf-cpp/Freebies.h Normal file
View File

@@ -0,0 +1,62 @@
#ifndef FREEBIES_H__
#define FREEBIES_H__
#include <string>
#include <vector>
namespace SBF {
struct FreebieType {
int id;
int cost;
std::string name;
std::string label;
};
const int kFreebiesCount = 7;
const FreebieType kFreebieUnknown = {0, 0, "", ""};
const int kFreebieDisciplineId = 1;
const int kFreebieAttributeId = 2;
const int kFreebieAbilityId = 3;
const int kFreebieVirtueId = 4;
// TODO: Make this configurable for VtDA
const int kFreebieHumanityId = 5;
const int kFreebieBackgroundId = 6;
const int kFreebieShowCharacterSheetId = 7;
/// @brief Writes a freebie to an ostream.
/// @param os The ostream to write to.
/// @param freebie The freebie to write.
/// @return The provided ostream for chaining.
std::ostream& operator<<(std::ostream& os, const FreebieType& freebie);
/// @brief Determines if two freebies are equal.
/// @param left A freebie to compare.
/// @param right A freebie to compare.
/// @return True if both freebies are equal and false otherwise.
bool operator==(const FreebieType& left, const FreebieType& right);
/// @brief Determines if two freebies are unequal.
/// @param left A freebie to compare.
/// @param right A freebie to compare.
/// @return True if both freebies are unequal and false otherwise.
bool operator!=(const FreebieType& left, const FreebieType& right);
/// @brief Gets a freebie with a specific id.
/// @param freebie_id The id of the freebie to get.
/// @return The freebie with id == freebie_id or the unknown freebie kFreebieUnknown if freebie_id is invalid.
FreebieType GetFreebie(int freebie_id);
/// @brief Fills the provided vector with all valid freebies.
/// @param freebies The vector to fill. It will be cleared first.
void FillFreebies(std::vector<FreebieType>& freebies);
std::vector<FreebieType> GetFreebies();
std::vector<FreebieType> GetAvailableFreebies(int freebie_points);
int GetFreebieCost(int id);
} // End namespace SBF
#endif

208
sbf-cpp/Freebies_test.cpp Normal file
View File

@@ -0,0 +1,208 @@
#include "Freebies.h"
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#include "test.h"
namespace {
using namespace SBF;
using namespace Test;
using namespace std;
} // End namespace
const int kFreebieDisciplineCost = 7;
const std::string kFreebieDisciplineName = "Discipline";
const std::string kFreebieDisciplineLabel = "Add a discipline dot 7 points";
const FreebieType kFreebieDiscipline = {
kFreebieDisciplineId, kFreebieDisciplineCost, kFreebieDisciplineName, kFreebieDisciplineLabel};
const int kFreebieAttributeCost = 5;
const std::string kFreebieAttributeName = "Attribute";
const std::string kFreebieAttributeLabel = "Add an attribute dot 5 points";
const FreebieType kFreebieAttribute = {
kFreebieAttributeId, kFreebieAttributeCost, kFreebieAttributeName, kFreebieAttributeLabel};
const int kFreebieAbilityCost = 2;
const std::string kFreebieAbilityName = "Ability";
const std::string kFreebieAbilityLabel = "Add an ability dot 2 points";
const FreebieType kFreebieAbility = {kFreebieAbilityId, kFreebieAbilityCost, kFreebieAbilityName, kFreebieAbilityLabel};
const int kFreebieVirtueCost = 2;
const std::string kFreebieVirtueName = "Virtue";
const std::string kFreebieVirtueLabel = "Add a virtue dot 2 points";
const FreebieType kFreebieVirtue = {kFreebieVirtueId, kFreebieVirtueCost, kFreebieVirtueName, kFreebieVirtueLabel};
const int kFreebieHumanityCost = 1;
const std::string kFreebieHumanityName = "Humanity";
const std::string kFreebieHumanityLabel = "Add a humanity dot 1 point";
const FreebieType kFreebieHumanity = {
kFreebieHumanityId, kFreebieHumanityCost, kFreebieHumanityName, kFreebieHumanityLabel};
const int kFreebieBackgroundCost = 1;
const std::string kFreebieBackgroundName = "Background";
const std::string kFreebieBackgroundLabel = "Add a background dot 1 point";
const FreebieType kFreebieBackground = {
kFreebieBackgroundId, kFreebieBackgroundCost, kFreebieBackgroundName, kFreebieBackgroundLabel};
const int kFreebieShowCharacterSheetCost = 0;
const std::string kFreebieShowCharacterSheetName = "Show character sheet";
const std::string kFreebieShowCharacterSheetLabel = "Show character sheet";
const FreebieType kFreebieShowCharacterSheet = {kFreebieShowCharacterSheetId,
kFreebieShowCharacterSheetCost,
kFreebieShowCharacterSheetName,
kFreebieShowCharacterSheetLabel};
TestResults test_FreebieType_operator_extract() {
auto fnToTest = [](const FreebieType& freebie) -> string {
ostringstream os;
os << freebie;
return os.str();
};
return execute_suite<string, FreebieType>(make_test_suite(
"SBF::FreebieType::operator<<",
fnToTest,
vector<TestTuple<string, FreebieType>>({
make_test<string, FreebieType>("should print an invalid freebie",
"Freebie: {id: 16, cost: 42, name: \"asdf\", label: \"fdsa\"}",
make_tuple((FreebieType){16, 42, "asdf", "fdsa"})),
make_test<string, FreebieType>("should print the unknown freebie",
"Freebie: {id: 0, cost: 0, name: \"\", label: \"\"}",
make_tuple(kFreebieUnknown)),
make_test<string, FreebieType>(
"should print the discipline freebie",
"Freebie: {id: 1, cost: 7, name: \"Discipline\", label: \"Add a discipline dot 7 points\"}",
make_tuple(kFreebieDiscipline)),
make_test<string, FreebieType>(
"should print the attribute freebie",
"Freebie: {id: 2, cost: 5, name: \"Attribute\", label: \"Add an attribute dot 5 points\"}",
make_tuple(kFreebieAttribute)),
make_test<string, FreebieType>(
"should print the ability freebie",
"Freebie: {id: 3, cost: 2, name: \"Ability\", label: \"Add an ability dot 2 points\"}",
make_tuple(kFreebieAbility)),
make_test<string, FreebieType>(
"should print the virtue freebie",
"Freebie: {id: 4, cost: 2, name: \"Virtue\", label: \"Add a virtue dot 2 points\"}",
make_tuple(kFreebieVirtue)),
make_test<string, FreebieType>(
"should print the humanity freebie",
"Freebie: {id: 5, cost: 1, name: \"Humanity\", label: \"Add a humanity dot 1 point\"}",
make_tuple(kFreebieHumanity)),
make_test<string, FreebieType>(
"should print the background freebie",
"Freebie: {id: 6, cost: 1, name: \"Background\", label: \"Add a background dot 1 point\"}",
make_tuple(kFreebieBackground)),
make_test<string, FreebieType>(
"should print the show character sheet freebie",
"Freebie: {id: 7, cost: 0, name: \"Show character sheet\", label: \"Show character sheet\"}",
make_tuple(kFreebieShowCharacterSheet)),
})));
}
TestResults test_FreebieType_operator_equal_to() {
return execute_suite<bool, FreebieType, FreebieType>(make_test_suite(
"SBF::FreebieType::operator==",
[](const FreebieType& left, const FreebieType& right) -> bool { return left == right; },
vector<TestTuple<bool, FreebieType, FreebieType>>({
make_test<bool, FreebieType, FreebieType>("should return true when comparing a freebie to itself",
true,
make_tuple(kFreebieBackground, kFreebieBackground)),
make_test<bool, FreebieType, FreebieType>(
"should return true when comparing two different instances created with the same values",
true,
make_tuple(
kFreebieAbility,
(FreebieType){kFreebieAbilityId, kFreebieAbilityCost, kFreebieAbilityName, kFreebieAbilityLabel})),
make_test<bool, FreebieType, FreebieType>("should return false when comparing two different freebies",
false,
make_tuple(kFreebieHumanity, kFreebieVirtue)),
})));
}
TestResults test_FreebieType_operator_not_equal_to() {
return execute_suite<bool, FreebieType, FreebieType>(make_test_suite(
"SBF::FreebieType::operator!=",
[](const FreebieType& left, const FreebieType& right) -> bool { return left != right; },
vector<TestTuple<bool, FreebieType, FreebieType>>({
make_test<bool, FreebieType, FreebieType>("should return false when comparing a freebie to itself",
false,
make_tuple(kFreebieBackground, kFreebieBackground)),
make_test<bool, FreebieType, FreebieType>(
"should return false when comparing two different instances created with the same values",
false,
make_tuple(
kFreebieAbility,
(FreebieType){kFreebieAbilityId, kFreebieAbilityCost, kFreebieAbilityName, kFreebieAbilityLabel})),
make_test<bool, FreebieType, FreebieType>("should return true when comparing two different freebies",
true,
make_tuple(kFreebieHumanity, kFreebieVirtue)),
})));
}
TestResults test_GetFreebie() {
return execute_suite<FreebieType, int>(make_test_suite(
"SBF::GetFreebie",
GetFreebie,
vector<TestTuple<FreebieType, int>>({
make_test<FreebieType, int>("should get the unknown freebie for invalid id 0", {0, 0, "", ""}, make_tuple(0)),
make_test<FreebieType, int>("should get the discipline freebie for id 1",
{1, 7, "Discipline", "Add a discipline dot 7 points"},
make_tuple(1)),
make_test<FreebieType, int>("should get the attribute freebie for id 2",
{2, 5, "Attribute", "Add an attribute dot 5 points"},
make_tuple(2)),
make_test<FreebieType, int>("should get the ability freebie for id 3",
{3, 2, "Ability", "Add an ability dot 2 points"},
make_tuple(3)),
make_test<FreebieType, int>("should get the virtue freebie for id 4",
{4, 2, "Virtue", "Add a virtue dot 2 points"},
make_tuple(4)),
make_test<FreebieType, int>("should get the humanity freebie for id 5",
{5, 1, "Humanity", "Add a humanity dot 1 point"},
make_tuple(5)),
make_test<FreebieType, int>("should get the background freebie for id 6",
{6, 1, "Background", "Add a background dot 1 point"},
make_tuple(6)),
make_test<FreebieType, int>("should get the show character sheet freebie for id 7",
{7, 0, "Show character sheet", "Show character sheet"},
make_tuple(7)),
make_test<FreebieType, int>("should get the unknown freebie for invalid id 8", {0, 0, "", ""}, make_tuple(8)),
})));
}
TestResults test_FillFreebies() {
auto fnToTest = []() -> string {
ostringstream error_message;
vector<FreebieType> expected = {
kFreebieDiscipline,
kFreebieAttribute,
kFreebieAbility,
kFreebieVirtue,
kFreebieHumanity,
kFreebieBackground,
kFreebieShowCharacterSheet,
};
vector<FreebieType> actual = {{-1, -2, "This should be removed.", "This should be removed"}};
FillFreebies(actual);
compare(error_message, expected, actual);
string error = error_message.str();
if (error.size() > 0) {
return error;
}
return "no errors";
};
return execute_suite<string>(make_test_suite(
"SBF::FillFreebies",
fnToTest,
vector<TestTuple<string>>({make_test<string>("should fill freebies", "no errors", make_tuple())})));
}
int main(int argc, char* argv[]) {
TestResults results;
results += test_FreebieType_operator_extract();
results += test_FreebieType_operator_equal_to();
results += test_FreebieType_operator_not_equal_to();
results += test_GetFreebie();
results += test_FillFreebies();
return results.failed() + results.errors();
}

38
sbf-cpp/Genders.cpp Normal file
View File

@@ -0,0 +1,38 @@
#include "Genders.h"
#include <string>
#include <vector>
namespace SBF {
using std::string;
using std::vector;
const int kGendersCount = 5;
const std::string kGenderMaleLabel = "Male";
const std::string kGenderFemaleLabel = "Female";
const std::string kGenderTransMaleLabel = "Trans-Male";
const std::string kGenderTransFemaleLabel = "Trans-Female";
const std::string kGenderNonBinaryLabel = "Non-Binary";
const std::string kGenderLabels[] = {
"",
kGenderMaleLabel,
kGenderFemaleLabel,
kGenderTransMaleLabel,
kGenderTransFemaleLabel,
kGenderNonBinaryLabel,
};
string GetGenderLabel(int genderId) {
if (genderId > 0 && genderId <= kGendersCount) {
return kGenderLabels[genderId];
}
return "";
}
void FillGenderLabels(vector<string>& labels) {
labels.clear();
for (int id = 1; id <= kGendersCount; id++) {
labels.push_back(GetGenderLabel(id));
}
}
} // End namespace SBF

35
sbf-cpp/Genders.h Normal file
View File

@@ -0,0 +1,35 @@
#ifndef GENDERS_H__
#define GENDERS_H__
/***************************************************************************************
* @file Genders.h
*
* @brief Defines constants and functions for working with genders.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
#include <string>
#include <vector>
/** \addtogroup Genders
* @{
*/
namespace SBF {
const int kGenderMaleId = 1;
const int kGenderFemaleId = 2;
const int kGenderTransMaleId = 3;
const int kGenderTransFemaleId = 4;
const int kGenderNonBinaryId = 5;
/// @brief Gets a gender label with a specific id.
/// @param gender_id The id of the gender to return the label for.
/// @return The label of the specified gender or an empty string if gender_id is invalid.
std::string GetGenderLabel(int gender_id);
/// @brief Fills the provided vector with all of the valid gender labels.
/// @param gender_labels The vector to fill. It will be cleared first.
void FillGenderLabels(std::vector<std::string>& gender_labels);
} // End namespace SBF
/** @}*/
#endif // End !defined GENDERS_H__

71
sbf-cpp/Genders_test.cpp Normal file
View File

@@ -0,0 +1,71 @@
#include "Genders.h"
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#include "test.h"
namespace {
using namespace SBF;
using namespace Test;
using namespace std;
} // End namespace
const std::string kGenderMaleLabel = "Male";
const std::string kGenderFemaleLabel = "Female";
const std::string kGenderTransMaleLabel = "Trans-Male";
const std::string kGenderTransFemaleLabel = "Trans-Female";
const std::string kGenderNonBinaryLabel = "Non-Binary";
TestResults test_GetGenderLabel() {
return execute_suite<string, int>(make_test_suite(
"SBF::GetGenderLabel",
GetGenderLabel,
vector<TestTuple<string, int>>({
make_test<string, int>("should get \"\" for invalid gender id 0", "", make_tuple(0)),
make_test<string, int>("should get \"Male\" for gender id 1", "Male", make_tuple(1)),
make_test<string, int>("should get \"Female\" for gender id 2", "Female", make_tuple(2)),
make_test<string, int>("should get \"Trans-Male\" for gender id 3", "Trans-Male", make_tuple(3)),
make_test<string, int>("should get \"Trans-Female\" for gender id 4", "Trans-Female", make_tuple(4)),
make_test<string, int>("should get \"Non-Binary\" for gender id 5", "Non-Binary", make_tuple(5)),
make_test<string, int>("should get \"\" for invalid gender id 6", "", make_tuple(6)),
})));
}
TestResults test_FillGenderLabels() {
auto fnToTest = []() -> string {
ostringstream error_message;
vector<string> expected = {
kGenderMaleLabel,
kGenderFemaleLabel,
kGenderTransMaleLabel,
kGenderTransFemaleLabel,
kGenderNonBinaryLabel,
};
vector<string> actual = {"This should be removed."};
FillGenderLabels(actual);
compare(error_message, expected, actual);
string error = error_message.str();
if (error.size() > 0) {
return error;
}
return "no errors";
};
return execute_suite<string>(make_test_suite("SBF::FillGenderLabels",
fnToTest,
vector<TestTuple<string>>({
make_test<string>("should fill genders", "no errors", make_tuple()),
})));
}
int main(int argc, char* argv[]) {
TestResults results;
results += test_GetGenderLabel();
results += test_FillGenderLabels();
return results.failed() + results.errors();
}

30
sbf-cpp/Info.plist Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en-US</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleDisplayName</key>
<string>Storyteller's Best Friend</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>? Tom Hicks, 2023</string>
<key>CFBundleDisplayName</key>
<string></string>
<key>CFBundleExecutable</key>
<string></string>
<key>CFBundleIdentifier</key>
<string></string>
<key>CFBundleName</key>
<string></string>
<key>CFBundleSignature</key>
<string></string>
</dict>
</plist>

416
sbf-cpp/Menus.cpp Normal file
View File

@@ -0,0 +1,416 @@
#include "Menus.h"
#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
#include "Colors.h"
#include "Random.h"
#include "Utils.h"
namespace SBF {
namespace {
using std::cin;
using std::cout;
using std::endl;
using std::ostream;
using std::pair;
using std::string;
using std::to_string;
using std::vector;
} // End namespace
void BuildMenuWithValues(vector<MenuItem> items, vector<string> labels, vector<int> values);
void BuildMenuWithColors(vector<MenuItem> items, vector<string> labels, vector<uint8_t> colors);
void AdjustMenuStyle(MenuStyle& style, vector<MenuItem> items, bool ignoreValue);
string GetTitle(MenuItem item, MenuStyle style);
string GetTitleWithoutValue(MenuItem item, MenuStyle style);
void NewMenuStyle(MenuStyle& style);
void NewMenuItem(MenuItem& item, string label, int id);
void NewMenuItemWithValue(MenuItem& item, string label, int id, int value);
void NewMenuItemWithColor(MenuItem& item, string label, int id, uint8_t color);
void MenuStyle::Adjust(vector<MenuItem> menu_items, bool ignore_value) {
size_t max_id_width = 0;
size_t max_item_width = 0;
size_t max_value_width = 0;
for_each(menu_items.begin(), menu_items.end(), [&](MenuItem menu_item) {
if (menu_item.is_visible) {
max_id_width = std::max(max_id_width, to_string(menu_item.id).size());
max_item_width = std::max(max_item_width, (menu_item.label + label_value_separator).size());
max_value_width = std::max(max_value_width, to_string(menu_item.value).size());
}
});
if (show_random) {
max_id_width = std::max(max_id_width, to_string(random_item_id).size());
max_item_width = std::max(max_item_width, random_item_name.size());
}
if (show_cancel) {
max_id_width = std::max(max_id_width, to_string(cancel_item_id).size());
max_item_width = std::max(max_item_width, cancel_item_name.size());
}
id_width = max_id_width;
label_width = max_item_width;
value_width = ignore_value ? 0 : max_value_width;
}
ostream& PrintMenu(ostream& os, vector<MenuItem> items, MenuStyle style) {
MenuItem random_item = MenuItem(style.random_item_name, style.random_item_id, style.random_item_color);
MenuItem cancel_item = MenuItem(style.cancel_item_name, style.cancel_item_id, style.cancel_item_color);
size_t actual_count = 0;
for_each(items.begin(), items.end(), [&actual_count](MenuItem item) {
if (item.is_visible) {
actual_count++;
}
});
if (style.show_cancel) {
actual_count++;
}
if (style.show_random) {
actual_count++;
}
if (actual_count <= 10) {
for_each(items.begin(), items.end(), [&style, &os](MenuItem item) {
if (item.is_visible) {
string title = GetTitle(item, style);
PrintWithMaybeColor(os, title, item.color, style.use_colors) << endl;
}
});
if (style.show_cancel) {
string title = GetTitleWithoutValue(cancel_item, style);
PrintWithMaybeColor(os, title, cancel_item.color, style.use_colors) << endl;
}
if (style.show_random) {
string title = GetTitleWithoutValue(random_item, style);
PrintWithMaybeColor(os, title, random_item.color, style.use_colors) << endl;
}
} else {
MenuItem empty_item;
size_t item_width = GetTitle(empty_item, style).size();
size_t items_per_row = style.screen_width / (item_width + style.menu_item_spacer.size());
size_t column_width = style.screen_width / items_per_row;
const size_t count = items.size();
int column = 0;
for (int i = 0; i < count; i++) {
const MenuItem& item = items[i];
if (item.is_visible) {
string item_text = GetTitle(item, style);
if (column != items_per_row - 1) {
if (i != count || style.show_random || style.show_cancel) {
size_t text_length = item_text.size();
item_text = MakeFitL(
RightTrim(item_text) + style.menu_item_spacer, text_length + style.menu_item_spacer.size(), ' ');
}
}
string label = MakeFitC(item_text, column_width, ' ');
PrintWithMaybeColor(os, label, item.color, style.use_colors);
column = (column + 1) % items_per_row;
if (column == 0) {
cout << endl;
}
}
}
if (style.show_cancel) {
string title = MakeFitC(GetTitleWithoutValue(cancel_item, style), column_width, ' ');
PrintWithMaybeColor(os, title, cancel_item.color, style.use_colors) << endl;
}
if (style.show_random) {
string title = MakeFitC(GetTitleWithoutValue(random_item, style), column_width, ' ');
PrintWithMaybeColor(os, title, random_item.color, style.use_colors) << endl;
}
}
return os;
}
vector<MenuItem> BuildMenu(vector<string> labels) {
vector<MenuItem> menu_items;
int id = 1;
for_each(
labels.begin(), labels.end(), [&menu_items, &id](string label) { menu_items.push_back(MenuItem(label, id++)); });
return menu_items;
}
vector<MenuItem> BuildMenuWithValues(vector<pair<string, int>> items) {
vector<MenuItem> menu_items;
size_t id = 1;
for (auto pr : items) {
menu_items.push_back(MenuItem(pr.first, id, pr.second));
}
return menu_items;
}
vector<MenuItem> BuildMenuWithValues(vector<string> labels, vector<int> values) {
vector<MenuItem> menu_items;
size_t count = std::min(labels.size(), values.size());
for (size_t i = 0; i < count; i++) {
menu_items.push_back(MenuItem(labels.at(i), i + 1, values.at(i)));
}
return menu_items;
}
vector<MenuItem> BuildMenuWithColors(vector<pair<string, uint8_t>> items) {
vector<MenuItem> menu_items;
size_t id = 1;
for (auto pr : items) {
menu_items.push_back(MenuItem(pr.first, id, pr.second));
}
return menu_items;
}
vector<MenuItem> BuildMenuWithColors(vector<string> labels, vector<uint8_t> colors) {
vector<MenuItem> menu_items;
size_t count = std::min(labels.size(), colors.size());
for (size_t i = 0; i < count; i++) {
menu_items.push_back(MenuItem(labels.at(i), i + 1, colors.at(i)));
}
return menu_items;
}
int GetRandomMenuItemId(vector<MenuItem> items) {
int num_visible_items = 0;
size_t count = items.size();
vector<int> visible_item_ids;
for (int i = 0; i < count; i++) {
MenuItem item = items.at(i);
if (item.is_visible && item.include_in_random) {
visible_item_ids.push_back(item.id);
num_visible_items++;
}
}
return visible_item_ids[GetRandomInt(0, num_visible_items - 1)];
}
MenuStyle::MenuStyle()
: id_width(0),
label_width(0),
value_width(0),
screen_width(80),
random_item_name("Random"),
random_item_id(0),
random_item_color(kColorDefaultForeground),
cancel_item_name("Cancel"),
cancel_item_id(-1),
cancel_item_color(kColorDefaultForeground),
id_label_separator(" = "),
label_value_separator(": "),
menu_item_spacer(", "),
show_random(true),
show_cancel(false),
use_colors(false) {}
MenuItem::MenuItem()
: label(""), id(0), value(0), color(kColorDefaultForeground), is_visible(true), include_in_random(true) {}
MenuItem::MenuItem(string label, int id)
: label(label), id(id), value(0), color(kColorDefaultForeground), is_visible(true), include_in_random(true) {}
MenuItem::MenuItem(string label, int id, int value)
: label(label), id(id), value(value), color(kColorDefaultForeground), is_visible(true), include_in_random(true) {}
MenuItem::MenuItem(string label, int id, uint8_t color)
: label(label), id(id), value(0), color(color), is_visible(true), include_in_random(true) {}
ostream& PrintWithMaybeColor(ostream& os, const string& text, uint8_t text_color, bool use_colors) {
uint8_t previous_color = GetForegroundColor();
if (use_colors) {
SetForegroundColor(text_color);
os << ForegroundColor;
}
os << text;
if (use_colors) {
SetForegroundColor(previous_color);
os << ForegroundColor;
}
return os;
}
string GetTitleWithoutValue(MenuItem item, MenuStyle style) {
string id_string = MakeFitR(to_string(item.id), style.id_width, ' ');
int label_width = style.label_width + style.value_width + style.label_value_separator.size();
string label_string = MakeFitL(item.label, label_width, ' ');
return id_string + style.id_label_separator + label_string;
}
string GetTitle(MenuItem item, MenuStyle style) {
string id = to_string(item.id);
string label = item.label;
if (style.value_width > 0) {
label += style.label_value_separator;
}
string value = to_string(item.value);
string formatted_id = MakeFitR(id, style.id_width);
string formatted_label = MakeFitL(label, style.label_width);
string formatted_value = MakeFitR(value, style.value_width);
return formatted_id + style.id_label_separator + formatted_label + formatted_value;
}
std::ostream& operator<<(std::ostream& os, const MenuStyle& style) {
os << "MenuStyle {id_width: " << style.id_width << ", label_width: " << style.label_width
<< ", value_width: " << style.value_width << ", screen_width: " << style.screen_width
<< ", show_random: " << (style.show_random ? "true" : "false") << ", random_item_id: " << style.random_item_id
<< ", random_item_name: " << style.random_item_name << ", random_item_color: " << (int)style.random_item_color
<< ", show_cancel: " << (style.show_cancel ? "true" : "false") << ", cancel_item_id: " << style.cancel_item_id
<< ", cancel_item_name: " << style.cancel_item_name << ", cancel_item_color: " << (int)style.cancel_item_color
<< ", id_label_separator: \"" << style.id_label_separator << "\", label_value_separator: \""
<< style.label_value_separator << "\", menu_item_spacer: \"" << style.menu_item_spacer
<< "\", use_colors: " << (style.use_colors ? "true" : "false") << "}";
return os;
}
std::ostream& operator<<(std::ostream& os, const MenuItem& item) {
os << "MenuItem {id: " << item.id << ", label: \"" << item.label << "\", value: " << item.value
<< ", color: " << (int)item.color << ", is_visible: " << (item.is_visible ? "true" : "false")
<< ", include_in_random: " << (item.include_in_random ? "true" : "false") << "}";
return os;
}
bool MenuStyle::operator==(const MenuStyle& other) {
return cancel_item_color == other.cancel_item_color && cancel_item_id == other.cancel_item_id
&& cancel_item_name == other.cancel_item_name && id_label_separator == other.id_label_separator
&& id_width == other.id_width && label_value_separator == other.label_value_separator
&& label_width == other.label_width && menu_item_spacer == other.menu_item_spacer
&& random_item_color == other.random_item_color && random_item_id == other.random_item_id
&& random_item_name == other.random_item_name && screen_width == other.screen_width
&& show_cancel == other.show_cancel && show_random == other.show_random && use_colors == other.use_colors
&& value_width == other.value_width;
}
bool MenuStyle::operator!=(const MenuStyle& other) {
return !(*this == other);
}
bool MenuItem::operator==(const MenuItem& other) {
return color == other.color && id == other.id && include_in_random == other.include_in_random
&& is_visible == other.is_visible && label == other.label && value == other.value;
}
bool MenuItem::operator!=(const MenuItem& other) {
return !(*this == other);
}
int GetChoice(int min, int max) {
int choice;
do {
choice = GetChoice();
} while (choice < min || choice > max);
return choice;
}
int GetChoice() {
int choice;
string line;
bool has_error;
do {
has_error = false;
getline(cin, line);
try {
if (line.empty()) {
return 0;
}
choice = stoi(line);
} catch (...) {
has_error = true;
}
} while (has_error);
return choice;
}
int GetMenuChoice(vector<MenuItem> menu_items, MenuStyle style) {
int choice;
while (true) {
choice = GetChoice();
if (style.show_random && choice == style.random_item_id) {
return choice;
}
if (style.show_cancel && choice == style.cancel_item_id) {
return choice;
}
for (MenuItem item : menu_items) {
if (item.id == choice) {
return choice;
}
}
}
}
string GetString(string prompt) {
cout << prompt << endl;
string response;
getline(cin, response);
return response;
}
int ChooseStringId(vector<string> labels, MenuStyle style, const string& prompt) {
MaybeClearScreen();
vector<MenuItem> menu_items = BuildMenu(labels);
style.Adjust(menu_items);
cout << prompt << endl;
PrintMenu(cout, menu_items, style);
int choice = GetMenuChoice(menu_items, style);
if (choice == style.random_item_id) {
choice = GetRandomMenuItemId(menu_items);
}
return choice;
}
bool ChooseYesOrNo(string prompt) {
MenuStyle style;
style.show_random = false;
vector<MenuItem> menu_items = BuildMenu({"Yes", "No"});
style.Adjust(menu_items, true);
cout << prompt << endl;
PrintMenu(cout, menu_items, style);
int choice = GetMenuChoice(menu_items, style);
if (choice == style.random_item_id) {
choice = GetRandomMenuItemId(menu_items);
}
return choice == 1;
}
int ChooseStringIdWithValues(vector<string> labels, vector<int> values, MenuStyle style, const string& prompt) {
MaybeClearScreen();
vector<MenuItem> menu_items = BuildMenuWithValues(labels, values);
style.Adjust(menu_items, false);
cout << prompt << endl;
PrintMenu(cout, menu_items, style);
int choice = GetMenuChoice(menu_items, style);
if (choice == style.random_item_id) {
choice = GetRandomMenuItemId(menu_items);
}
return choice;
}
int ChooseStringIdWithColors(vector<string> labels, vector<uint8_t> colors, MenuStyle style, const string& prompt) {
MaybeClearScreen();
// Check array bounds
vector<MenuItem> menu_items = BuildMenuWithColors(labels, colors);
style.Adjust(menu_items);
cout << prompt << endl;
PrintMenu(cout, menu_items, style);
int choice = GetMenuChoice(menu_items, style);
if (choice == style.random_item_id) {
choice = GetRandomMenuItemId(menu_items);
}
return choice;
}
int ChooseMenuItemId(vector<MenuItem> menu_items, MenuStyle style, const string& prompt, bool ignore_value) {
MaybeClearScreen();
style.Adjust(menu_items, ignore_value);
cout << prompt << endl;
PrintMenu(cout, menu_items, style);
int choice = GetMenuChoice(menu_items, style);
if (choice == style.random_item_id) {
choice = GetRandomMenuItemId(menu_items);
}
return choice;
}
void WaitForKeypress() {
// TODO: Make this press any key to continue.
}
} // End namespace SBF

135
sbf-cpp/Menus.h Normal file
View File

@@ -0,0 +1,135 @@
#ifndef MENUS_H__
#define MENUS_H__
/***************************************************************************************
* @file Menus.h
*
* @brief Defines constants and functions for working with menus.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
#if !defined(DEBUG)
#include <iostream>
#endif
#include "Colors.h"
namespace SBF {
class MenuStyle;
class MenuItem;
} // namespace SBF
// End forward declarations
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
/** \addtogroup Menus
* @{
*/
namespace SBF {
class MenuStyleBuilder {
public:
MenuStyleBuilder();
MenuStyleBuilder& SetScreenWidth(int screen_width);
MenuStyleBuilder& SetRandomItemName(const std::string& name);
MenuStyleBuilder& SetRandomItemId(int id);
MenuStyleBuilder& SetRandomItemColor(uint8_t color);
MenuStyleBuilder& SetCancelItemName(const std::string& name);
MenuStyleBuilder& SetCancelItemId(int id);
MenuStyleBuilder& SetCancelItemColor(uint8_t color);
MenuStyleBuilder& SetIdLabelSeparator(const std::string& separator);
MenuStyleBuilder& SetLabelValueSeparator(const std::string& separator);
MenuStyleBuilder& SetMenuItemSeparator(const std::string& separator);
MenuStyleBuilder& SetShowRandom(bool show_random);
MenuStyleBuilder& SetShowCancel(bool show_cancel);
MenuStyleBuilder& SetUseColors(bool use_colors);
MenuStyle Build();
};
class MenuStyle {
public:
MenuStyle();
void Adjust(std::vector<MenuItem> menu_items, bool ignore_value = true);
friend std::ostream& operator<<(std::ostream& os, const MenuStyle& style);
bool operator==(const MenuStyle& other);
bool operator!=(const MenuStyle& other);
int id_width;
int label_width;
int value_width;
int screen_width;
std::string random_item_name;
int random_item_id;
uint8_t random_item_color;
std::string cancel_item_name;
int cancel_item_id;
uint8_t cancel_item_color;
std::string id_label_separator;
std::string label_value_separator;
std::string menu_item_spacer;
bool show_random;
bool show_cancel;
bool use_colors;
};
class MenuItem {
public:
MenuItem();
MenuItem(std::string label, int id);
MenuItem(std::string label, int id, int value);
MenuItem(std::string label, int id, uint8_t color);
friend std::ostream& operator<<(std::ostream& os, const MenuItem& item);
bool operator==(const MenuItem& other);
bool operator!=(const MenuItem& other);
std::string label;
int id;
int value;
uint8_t color;
bool is_visible;
bool include_in_random;
};
// TODO: Make a menu class to hold GetRandomMenuItemId, the various BuildMenu* methods, and possibly PrintMenu.
int GetRandomMenuItemId(std::vector<MenuItem> items);
std::vector<MenuItem> BuildMenu(std::vector<std::string> labels);
std::vector<MenuItem> BuildMenuWithValues(std::vector<std::pair<std::string, int>> items);
std::vector<MenuItem> BuildMenuWithValues(std::vector<std::string> labels, std::vector<int> values);
std::vector<MenuItem> BuildMenuWithColors(std::vector<std::pair<std::string, uint8_t>> items);
std::vector<MenuItem> BuildMenuWithColors(std::vector<std::string> labels, std::vector<uint8_t> colors);
std::ostream& PrintMenu(std::ostream& os, std::vector<MenuItem> items, MenuStyle style);
std::string GetTitle(MenuItem item, MenuStyle style);
std::string GetTitleWithoutValue(MenuItem item, MenuStyle style);
std::ostream& PrintWithMaybeColor(std::ostream& os,
const std::string& text,
uint8_t text_color = kColorDefaultForeground,
bool use_colors = false);
int ChooseStringId(std::vector<std::string> labels, MenuStyle style, const std::string& prompt);
bool ChooseYesOrNo(std::string prompt);
int GetChoice(int min, int max);
int GetChoice();
int GetMenuChoice(std::vector<MenuItem> menu_items, MenuStyle style);
std::string GetString(std::string prompt);
int ChooseStringIdWithValues(std::vector<std::string> labels,
std::vector<int> values,
MenuStyle style,
const std::string& prompt);
int ChooseMenuItemId(std::vector<MenuItem> menu_items, MenuStyle style, const std::string& prompt, bool ignore_value);
int ChooseStringIdWithColors(std::vector<std::string> labels,
std::vector<uint8_t> colors,
MenuStyle style,
const std::string& prompt);
void WaitForKeypress();
/// Clears the screen if not a debug build.
inline void MaybeClearScreen() {
#if !defined(DEBUG)
std::cout << "\033[1;1H\033[2J";
#endif
}
} // End namespace SBF
/** @}*/
#endif // !defined MENUS_H__

129
sbf-cpp/Menus_test.cpp Normal file
View File

@@ -0,0 +1,129 @@
#include "Menus.h"
#include <iostream>
#include <regex>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#include "Colors.h"
#include "test.h"
namespace {
using namespace SBF;
using namespace Test;
using namespace std;
} // End namespace
string escape_string(const string& text, const string& pattern = "\033", const string& replace = "\\033") {
return regex_replace(text, regex(pattern), replace);
}
TestResults test_MenuItem_constructor() {
return TestResults().skip("// TODO: test_MenuItem_constructor");
}
TestResults test_MenuItem_constructor_string_int() {
return TestResults().skip("// TODO: test_MenuItem_constructor_string_int");
}
TestResults test_MenuItem_constructor_string_int_int() {
return TestResults().skip("// TODO: test_MenuItem_constructor_string_int_int");
}
TestResults test_MenuItem_constructor_string_int_uint8_t() {
return TestResults().skip("// TODO: test_MenuItem_constructor_string_int_uint8_t");
}
TestResults test_MenuStyle_constructor() {
return TestResults().skip("// TODO: test_MenuStyle_constructor");
}
TestResults test_MenuStyle_Adjust() {
return TestResults().skip("// TODO: test_Menu_Style_Adjust");
}
TestResults test_GetRandomMenuItemId() {
return TestResults().skip("// TODO: test_GetRandomMenuItemId");
}
TestResults test_BuildMenu() {
return TestResults().skip("// TODO: test_BuildMenu");
}
TestResults test_BuildMenuWithValues() {
return TestResults().skip("// TODO: test_BuildMenuWithValues");
}
TestResults test_BuildMenuWithColors() {
return TestResults().skip("// TODO: test_BuildMenuWithColors");
}
TestResults test_PrintMenu() {
return TestResults().skip("// TODO: test_PrintMenu");
}
TestResults test_GetTitle() {
return TestResults().skip("// TODO: test_GetTitle");
}
TestResults test_GetTitleWithoutValue() {
return TestResults().skip("// TODO: test_GetTitleWithoutValue");
}
TestResults test_PrintWithMaybeColor() {
auto fnToTest = [](const string& text, uint8_t text_color, bool use_colors) -> string {
SetForegroundColor(kColorDefaultForeground);
SetBackgroundColor(kColorDefaultBackground);
ostringstream error_message;
ostringstream os;
PrintWithMaybeColor(os, text, text_color, use_colors);
if (GetForegroundColor() != kColorDefaultForeground) {
error_message << " Foreground color was changed to " << GetForegroundColor();
}
if (GetBackgroundColor() != kColorDefaultBackground) {
error_message << " Background color was changed to " << GetBackgroundColor();
}
if (error_message.str().size() > 0) {
return error_message.str();
}
return escape_string(os.str());
};
return execute_suite(
make_test_suite("SBF::PrintWithMaybeColor",
fnToTest,
vector<TestTuple<string, string, uint8_t, bool>>({
make_test<string, string, uint8_t, bool>(
"should write \"Hello, world!\" in dark yellow and reset the color afterward",
"\\033[38;5;3mHello, world!\\033[38;5;15m",
make_tuple(string("Hello, world!"), kColorDarkYellow, true)),
make_test<string, string, uint8_t, bool>(
"should print \"much less than we should\" without changing the color",
"much less than we should",
make_tuple(string("much less than we should"), kColorDarkGreen, false)),
})));
}
int main(int argc, char* argv[]) {
TestResults results;
results += test_MenuItem_constructor();
results += test_MenuItem_constructor_string_int();
results += test_MenuItem_constructor_string_int_int();
results += test_MenuItem_constructor_string_int_uint8_t();
results += test_MenuStyle_constructor();
results += test_MenuStyle_Adjust();
results += test_GetRandomMenuItemId();
results += test_BuildMenu();
results += test_BuildMenuWithValues();
results += test_BuildMenuWithColors();
results += test_PrintMenu();
results += test_GetTitle();
results += test_GetTitleWithoutValue();
results += test_PrintWithMaybeColor();
PrintResults(cout, results);
return results.failed() + results.errors();
}

8
sbf-cpp/NOTES.md Normal file
View File

@@ -0,0 +1,8 @@
https://www.fileformat.info/info/unicode/char/2551/index.htm
http://melvilletheatre.com/articles/ncurses-extended-characters/index.html
https://superjamie.github.io/2022/08/06/ncursesw
https://pdcurses.org/
https://invisible-island.net/ncurses/announce-5.8.html
https://www.mail-archive.com/pdcurses-l@lightlink.com/msg00129.html
https://www.projectpluto.com/win32a.htm
https://github.com/Bill-Gray/PDCursesMod

48
sbf-cpp/Questions.md Normal file
View File

@@ -0,0 +1,48 @@
## Which is uglier
```c++
(*after_each)()
after_each->operator()()
after_each.value()();
```
## What do all of these command line args do and which do I really want?
```bash
clang \
-xc++ \
-D_FORTIFY_SOURCE=1 \
-fstack-protector \
-fcolor-diagnostics \
-Wall \
-Wthread-safety \
-Wself-assign \
-fno-omit-frame-pointer \
-O0 \
-DDEBUG \
-std=c++11 \
-iquote \
. \
-iquote \
bazel-out/darwin_arm64-fastbuild/bin \
-MD \
-MF \
bazel-out/darwin_arm64-fastbuild/bin/sbf-cpp/_objs/menus/Menus.d \
-DBAZEL_CURRENT_REPOSITORY="" \
-frandom-seed=bazel-out/darwin_arm64-fastbuild/bin/sbf-cpp/_objs/menus/Menus.o \
-isysroot \
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk \
-F/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks \
-F/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks \
-no-canonical-prefixes \
-pthread \
-no-canonical-prefixes \
-Wno-builtin-macro-redefined \
-D__DATE__="redacted" \
-D__TIMESTAMP__="redacted" \
-D__TIME__="redacted" \
-target \
arm64-apple-macosx13.3 \
-c \
sbf-cpp/Menus.cpp \
-o \
bazel-out/darwin_arm64-fastbuild/bin/sbf-cpp/_objs/menus/Menus.o
```

14
sbf-cpp/Random.cpp Normal file
View File

@@ -0,0 +1,14 @@
#include "Random.h"
#include <random>
namespace SBF {
std::random_device g_random_device;
std::mt19937_64 g_random_number_engine(g_random_device());
std::uniform_int_distribution<int> g_random_int_generator;
int GetRandomInt(int min, int max) {
int r = g_random_int_generator(g_random_number_engine);
return r % (max - min + 1) + min;
}
} // End namespace SBF

7
sbf-cpp/Random.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef RANDOM_H__
#define RANDOM_H__
namespace SBF {
int GetRandomInt(int min, int max);
} // End namespace SBF
#endif // End RANDOM_H__

29
sbf-cpp/Random_test.cpp Normal file
View File

@@ -0,0 +1,29 @@
#include "Random.h"
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#include "test.h"
namespace {
using namespace SBF;
using namespace Test;
using namespace std;
} // End namespace
TestResults test_something() {
return TestResults().skip("SBF::Random::*");
}
int main(int argc, char* argv[]) {
TestResults results;
results += test_something();
PrintResults(cout, results);
return results.failed() + results.errors();
}

56
sbf-cpp/Ranks.cpp Normal file
View File

@@ -0,0 +1,56 @@
#include "Ranks.h"
#include <ostream>
#include <string>
#include <vector>
namespace SBF {
namespace {
using std::ostream;
using std::string;
using std::vector;
} // End namespace
const std::string kRankPrimaryLabel = "Primary";
const std::string kRankSecondaryLabel = "Secondary";
const std::string kRankTertiaryLabel = "Tertiary";
const int kRanksCount = 3;
const RankType kRankPrimary = {kRankPrimaryId, kRankPrimaryLabel};
const RankType kRankSecondary = {kRankSecondaryId, kRankSecondaryLabel};
const RankType kRankTertiary = {kRankTertiaryId, kRankTertiaryLabel};
const RankType kRankUnknown = {0, ""};
const RankType kRanks[]{
kRankUnknown,
kRankPrimary,
kRankSecondary,
kRankTertiary,
};
ostream& operator<<(ostream& os, const RankType& rank) {
os << "Rank: {id: " << rank.id << ", label: \"" << rank.label << "\"}";
return os;
}
bool operator==(const RankType& left, const RankType& right) {
return left.id == right.id && left.label == right.label;
}
bool operator!=(const RankType& left, const RankType& right) {
return !(left == right);
}
RankType GetRank(int rankId) {
if (rankId > 0 && rankId <= kRanksCount) {
return kRanks[rankId];
}
return kRankUnknown;
}
void FillRanks(std::vector<RankType>& ranks) {
ranks.clear();
for (int id = 1; id <= kRanksCount; id++) {
ranks.push_back(GetRank(id));
}
}
} // End namespace SBF

56
sbf-cpp/Ranks.h Normal file
View File

@@ -0,0 +1,56 @@
#ifndef RANKS_H__
#define RANKS_H__
/***************************************************************************************
* @file Ranks.h
*
* @brief Defines constants and functions for working with ranks.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
#include <string>
#include <vector>
/** \addtogroup
* @{
*/
namespace SBF {
const int kRankPrimaryId = 1;
const int kRankSecondaryId = 2;
const int kRankTertiaryId = 3;
struct RankType {
int id;
std::string label;
};
/// @brief Writes a rank to an ostream.
/// @param os The ostream to write to.
/// @param rank The rank to write.
/// @return The provided ostream for chaining.
std::ostream& operator<<(std::ostream& os, const RankType& rank);
/// @brief Determines if two ranks are equal.
/// @param left A rank to compare.
/// @param right A rank to compare.
/// @return True if both ranks are equal and false otherwise.
bool operator==(const RankType& left, const RankType& right);
/// @brief Determines if two ranks are unequal.
/// @param left A rank to compare.
/// @param right A rank to compare.
/// @return True if both ranks are unequal and false otherwise.
bool operator!=(const RankType& left, const RankType& right);
/// @brief Gets a rank with a specific id.
/// @param rank_id The id of the rank to get.
/// @return The rank with id == rank_id or the unknown rank kRankUnknown if rank_id is invalid.
RankType GetRank(int rank_id);
/// @brief Fills the provided vector with all valid rank labels.
/// @param ranks The vector to fill. It will be cleared first.
void FillRanks(std::vector<RankType>& ranks);
} // End namespace SBF
/** @}*/
#endif // !defined RANKS_H__

139
sbf-cpp/Ranks_test.cpp Normal file
View File

@@ -0,0 +1,139 @@
#include "Ranks.h"
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#include "test.h"
namespace {
using namespace SBF;
using namespace Test;
using namespace std;
} // End namespace
const std::string kRankPrimaryLabel = "Primary";
const std::string kRankSecondaryLabel = "Secondary";
const std::string kRankTertiaryLabel = "Tertiary";
const RankType kRankPrimary = {kRankPrimaryId, kRankPrimaryLabel};
const RankType kRankSecondary = {kRankSecondaryId, kRankSecondaryLabel};
const RankType kRankTertiary = {kRankTertiaryId, kRankTertiaryLabel};
const RankType kRankUnknown = {0, ""};
const RankType kRanks[]{
kRankUnknown,
kRankPrimary,
kRankSecondary,
kRankTertiary,
};
TestResults test_GetRank() {
return execute_suite<RankType, int>(make_test_suite(
"SBF::GetRank",
GetRank,
vector<TestTuple<RankType, int>>({
make_test<RankType, int>("should get {0, \"\"} for invalid rank id 0", RankType({0, ""}), make_tuple(0)),
make_test<RankType, int>(
"should get {1, \"Primary\"} for rank id 1", RankType({1, "Primary"}), make_tuple(1)),
make_test<RankType, int>(
"should get {2, \"Secondary\"} for rank id 2", RankType({2, "Secondary"}), make_tuple(2)),
make_test<RankType, int>(
"should get {3, \"Tertiary\"} for rank id 3", RankType({3, "Tertiary"}), make_tuple(3)),
make_test<RankType, int>("should get {0, \"\"} for invalid rank id 6", RankType({0, ""}), make_tuple(6)),
})));
}
TestResults test_FillRanks() {
auto fnToTest = []() -> string {
ostringstream error_message;
vector<RankType> expected = {
kRankPrimary,
kRankSecondary,
kRankTertiary,
};
vector<RankType> actual = {{-5, "This should be removed."}};
FillRanks(actual);
compare(error_message, expected, actual);
string error = error_message.str();
if (error.size() > 0) {
return error;
}
return "no errors";
};
return execute_suite<string>(make_test_suite("SBF::FillRanks",
fnToTest,
vector<TestTuple<string>>({
make_test<string>("should fill ranks", "no errors", make_tuple()),
})));
}
TestResults test_RankType_operator_extract() {
auto fnToTest = [](const RankType& rank) -> string {
ostringstream os;
os << rank;
return os.str();
};
return execute_suite<string, RankType>(make_test_suite(
"SBF::RankType::operator<<",
fnToTest,
vector<TestTuple<string, RankType>>({
make_test<string, RankType>(
"should print an invalid rank", "Rank: {id: 0, label: \"\"}", make_tuple((RankType){0, ""})),
make_test<string, RankType>(
"should print primary", "Rank: {id: 1, label: \"Primary\"}", make_tuple(kRankPrimary)),
make_test<string, RankType>(
"should print secondary", "Rank: {id: 2, label: \"Secondary\"}", make_tuple(kRankSecondary)),
make_test<string, RankType>(
"should print tertiary", "Rank: {id: 3, label: \"Tertiary\"}", make_tuple(kRankTertiary)),
})));
}
TestResults test_RankType_operator_equal_to() {
return execute_suite<bool, RankType, RankType>(make_test_suite(
"SBF::RankType::operator==",
[](const RankType& left, const RankType& right) { return left == right; },
vector<TestTuple<bool, RankType, RankType>>({
make_test<bool, RankType, RankType>(
"should return true when comparing an rank to itself", true, make_tuple(kRankPrimary, kRankPrimary)),
make_test<bool, RankType, RankType>(
"should return true when comparing two different instances created with the same values",
true,
make_tuple(kRankSecondary, RankType({kRankSecondaryId, kRankSecondaryLabel}))),
make_test<bool, RankType, RankType>("should return false when comparing two different rank gropus",
false,
make_tuple(kRankPrimary, kRankTertiary)),
})));
}
TestResults test_RankType_operator_not_equal_to() {
return execute_suite<bool, RankType, RankType>(make_test_suite(
"SBF::RankType::operator!=",
[](const RankType& left, const RankType& right) { return left != right; },
vector<TestTuple<bool, RankType, RankType>>({
make_test<bool, RankType, RankType>(
"should return false when comparing an rank to itself", false, make_tuple(kRankPrimary, kRankPrimary)),
make_test<bool, RankType, RankType>(
"should return false when comparing two different instances created with the same values",
false,
make_tuple(kRankSecondary, RankType({kRankSecondaryId, kRankSecondaryLabel}))),
make_test<bool, RankType, RankType>("should return true when comparing two different rank gropus",
true,
make_tuple(kRankSecondary, kRankTertiary)),
})));
}
int main(int argc, char* argv[]) {
TestResults results;
results += test_RankType_operator_extract();
results += test_RankType_operator_equal_to();
results += test_RankType_operator_not_equal_to();
results += test_GetRank();
results += test_FillRanks();
PrintResults(cout, results);
return results.failed() + results.errors();
}

7
sbf-cpp/TODO.md Normal file
View File

@@ -0,0 +1,7 @@
* Character.{h,cpp} and tests
* main.{h,cpp} and tests
* sbf.{h,cpp} and tests
* bazel build mac app
* bazel test sbf
* bazel build/test main (the test ncurses app)
* bazel build doxygen docs.

View File

@@ -0,0 +1,276 @@
## test_fn implicit template values
### Error Message
test.cpp(350, 16): argument types are: (const wchar_t [18], lambda [](int id)->std::wstring, std::__1::vector<std::__1::tuple<const wchar_t *, const wchar_t *, std::__1::tuple<int>, lambda [](std::__1::wstring left, std::__1::wstring right)->bool, lambda [](int id)->void, lambda [](int id)->void, bool>, std::__1::allocator<std::__1::tuple<const wchar_t *, const wchar_t *, std::__1::tuple<int>, lambda [](std::__1::wstring left, std::__1::wstring right)->bool, lambda [](int id)->void, lambda [](int id)->void, bool>>>, lambda [](std::__1::wstring left, std::__1::wstring right)->bool, lambda [](int id)->void, lambda [](int id)->void, bool)
test_fn
+2 overloads
### Declaration
template<typename TResult, typename FnToTest, typename... TInputParams>
test_method_result test_fn(
wstring suiteName, /* suiteName - This is the name of the test suite. It will be used in logs. */
FnToTest testFn, /* testFn - This is the function to be tested. It will be called with std::apply and the inputParams from each test in tests below. */
vector<
tuple<
wstring /* testName */,
TResult /* expectedOutput */,
tuple<TInputParams...> /* inputParams - The input parameters for this test. These will be used when calling std::apply with testFn to execute the test. */,
bool(*)(const TResult expected, const TResult actual) /* testCompareFn - If this is not nullprt then this function will be called instead of suiteCompareFn to determine if the test passes. Use this to check for side effects of the test. Return true if the test passes and false otherwise. */,
void(*)(TInputParams...) /* testSetupFn - If this is not nullptr this function is called before each test to setup the environment. It is called with std::apply and inputParams so you can use them to mock records with specific IDs or calculate an expected result. */,
void(*)(TInputParams...) /* testTeardownFn - If this is not nullptr this function is called after each test to cleanup any allocated/shared resources. */,
bool /* testIsEnabled - If this is false the test, setup, and teardown functions are not run. */
> /* <no name> - This is the data and config functions for a specific test run. */
> tests, /* tests - Each tuple in the vector is a test run with a name, enabledStatus, input, expected output, and optional compare, setup, and teardown functions. */
bool(*suiteCompareFn)(TResult a, TResult b) /* suiteCompareFn used to compare*/,
void(*suiteSetupFn)() /* suiteSetupFn called before running the test suite */,
void(*suiteTeardownFn)() /* suiteTeardownFn called after all tests have run */,
bool /* suiteIsEnabled - If this is false the entire test suite is skipped. The skipped tests will be reported as skipped/disabled. */
);
### For SO post
(
const wchar_t [18],
lambda [](int id)->std::wstring,
std::vector<
std::tuple<
const wchar_t *,
const wchar_t *,
std::tuple<int>,
lambda [](std::wstring left, std::wstring right)->bool,
lambda [](int id)->void,
lambda [](int id)->void,
bool
>,
std::allocator<
std::tuple<
const wchar_t *,
const wchar_t *,
std::tuple<int>,
lambda [](std::wstring left, std::wstring right)->bool,
lambda [](int id)->void,
lambda [](int id)->void,
bool
>
>
>,
lambda [](std::wstring left, std::wstring right)->bool,
lambda [](int id)->void,
lambda [](int id)->void,
bool
)
template<typename TResult, typename FnToTest, typename... TInputParams>
test_method_result test_fn(
wstring suiteName,
FnToTest testFn,
vector<
tuple<
wstring,
TResult,
tuple<TInputParams...>,
bool(*)(const TResult expected, const TResult actual),
void(*)(TInputParams...),
void(*)(TInputParams...),
bool
>
> tests,
bool(*suiteCompareFn)(TResult a, TResult b),
void(*suiteSetupFn)(),
void(*suiteTeardownFn)(),
bool
);
### For SO Post v2
(
const wchar_t [18], // const wchar_t* suiteName // Literal wstring
lambda [](int id)->std::wstring, // wstring(*)(int id) testFn //
std::vector< // Begin vector<...> tests //
std::tuple< // Begin tuple<...> a test //
const wchar_t *, // const wchar_t* testName // Literal const wchar_t*
const wchar_t *, // const wchar_t* expected // Literal const wchar_t*
std::tuple<int>, // tuple<int> inputParams // int id
lambda [](std::wstring left, std::wstring right)->bool, // bool(*)(const wstring expected, const wstring actual) compareFn // dummy equals fn
lambda [](int id)->void, // void(*)(int id) testSetupFn // dummy fn
lambda [](int id)->void, // void(*)(int id) testTeardownFn // dummy fn
bool // bool testIsEnabled // true
>, // End tuple<...> test //
std::allocator< // Begin skipping allocator //
std::tuple<
const wchar_t *,
const wchar_t *,
std::tuple<int>,
lambda [](std::wstring left, std::wstring right)->bool,
lambda [](int id)->void,
lambda [](int id)->void,
bool
>
> // End skipping allocator //
>, // End vector<...> tests
lambda [](std::wstring left, std::wstring right)->bool, // bool(*)() // dummy equals fn
lambda [](int id)->void, // void(*)(int id) // dummy fn
lambda [](int id)->void, // void(*)(int di) // dummy fn
bool // bool suiteIsEnabled // true
)
template<typename TResult, typename FnToTest, typename... TInputParams>
test_method_result test_fn(
wstring suiteName, // wstring suiteName
FnToTest testFn, // FnToTest testName
vector< // Begin vector<...> tests
tuple< // Begin tuple<...> a test
wstring, // wstring testName
TResult, // TResult expectedResult
tuple<TInputParams...>, // tuple<TInputParams...> inputParams
bool(*)(const TResult expected, const TResult actual), // bool(*)(const TResult expected, const TResult actual) testCompareFn
void(*)(TInputParams...), // void(*)(TInputParams...) testSetupFn
void(*)(TInputParams...), // void(*)(TInputParams...) testTeardownFn
bool // bool isTestEnabled
> // End tuple<...> a test
> tests, // End vector<...> tests
bool(*suiteCompareFn)(TResult a, TResult b), // bool(*)(TResult a, TResult b) suiteCompareFn
void(*suiteSetupFn)(), // void(*)() suiteSetupFn
void(*suiteTeardownFn)(), // void(*)() suiteTeardownFn
bool // bool isSuiteEnabled
);
### For SO Post v3
### Scratch
(
const wchar_t [18], // const wchar_t* suiteName // Literal wstring
lambda [](int id)->std::wstring, // wstring(*)(int id) testFn // (wstring)(*)(int id) testFn
std::vector< // Begin vector<...> tests //
std::tuple< // Begin tuple<...> a test //
const wchar_t *, // const wchar_t* testName // Literal const wchar_t*
const wchar_t *, // const wchar_t* expected // Literal const wchar_t*
std::tuple<int>, // tuple<int> inputParams // int id
lambda [](std::wstring left, std::wstring right)->bool, // bool(*)(const wstring expected, const wstring actual) compareFn // dummy equals fn
lambda [](int id)->void, // void(*)(int id) testSetupFn // dummy fn
lambda [](int id)->void, // void(*)(int id) testTeardownFn // dummy fn
bool // bool testIsEnabled // true
>, // End tuple<...> test //
std::allocator<...> // Skipping allocator //
>, // End vector<...> tests
lambda [](std::wstring left, std::wstring right)->bool, // bool(*)() // dummy equals fn
lambda [](int id)->void, // void(*)(int id) // dummy fn
lambda [](int id)->void, // void(*)(int di) // dummy fn
bool // bool suiteIsEnabled // true
)
template<typename TResult, typename FnToTest, typename... TInputParams>
test_method_result test_fn(
wstring suiteName, // wstring suiteName
FnToTest testFn, // FnToTest testName
vector< // Begin vector<...> tests
tuple< // Begin tuple<...> a test
wstring, // wstring testName
TResult, // TResult expectedResult
tuple<TInputParams...>, // tuple<TInputParams...> inputParams
bool(*)(const TResult expected, const TResult actual), // bool(*)(const TResult expected, const TResult actual) testCompareFn
void(*)(TInputParams...), // void(*)(TInputParams...) testSetupFn
void(*)(TInputParams...), // void(*)(TInputParams...) testTeardownFn
bool // bool isTestEnabled
> // End tuple<...> a test
> tests, // End vector<...> tests
bool(*suiteCompareFn)(TResult a, TResult b), // bool(*)(TResult a, TResult b) suiteCompareFn
void(*suiteSetupFn)(), // void(*)() suiteSetupFn
void(*suiteTeardownFn)(), // void(*)() suiteTeardownFn
bool // bool isSuiteEnabled
);
### For SO Post v4
### Scratch
(
(wstring)const wchar_t [18], // wstring suiteName // Literal wstring
lambda [](int id)->std::wstring, // wstring(*)(int id) testFn // (wstring)(*)(int id) testFn
std::vector< // Begin vector<...> tests //
std::tuple< // Begin tuple<...> a test //
const wchar_t *, // const wchar_t* testName // Literal const wchar_t*
const wchar_t *, // const wchar_t* expected // Literal const wchar_t*
std::tuple<int>, // tuple<int> inputParams // int id
lambda [](std::wstring left, std::wstring right)->bool, // bool(*)(const wstring expected, const wstring actual) compareFn // dummy equals fn
lambda [](int id)->void, // void(*)(int id) testSetupFn // dummy fn
lambda [](int id)->void, // void(*)(int id) testTeardownFn // dummy fn
bool // bool testIsEnabled // true
>, // End tuple<...> test //
std::allocator<...> // Skipping allocator //
>, // End vector<...> tests
lambda [](std::wstring left, std::wstring right)->bool, // bool(*)() // dummy equals fn
lambda [](int id)->void, // void(*)(int id) // dummy fn
lambda [](int id)->void, // void(*)(int di) // dummy fn
bool // bool suiteIsEnabled // true
)
template<typename TResult, typename FnToTest, typename... TInputParams>
test_method_result test_fn(
wstring suiteName, // wstring suiteName
FnToTest testFn, // FnToTest testName
vector< // Begin vector<...> tests
tuple< // Begin tuple<...> a test
wstring, // wstring testName
TResult, // TResult expectedResult
tuple<TInputParams...>, // tuple<TInputParams...> inputParams
bool(*)(const TResult expected, const TResult actual), // bool(*)(const TResult expected, const TResult actual) testCompareFn
void(*)(TInputParams...), // void(*)(TInputParams...) testSetupFn
void(*)(TInputParams...), // void(*)(TInputParams...) testTeardownFn
bool // bool isTestEnabled
> // End tuple<...> a test
> tests, // End vector<...> tests
bool(*suiteCompareFn)(TResult a, TResult b), // bool(*)(TResult a, TResult b) suiteCompareFn
void(*suiteSetupFn)(), // void(*)() suiteSetupFn
void(*suiteTeardownFn)(), // void(*)() suiteTeardownFn
bool // bool isSuiteEnabled
);
```c++
typedef FnTotest wstring(*)(int id);
typedef TResult wstring;
typedef TInputParams int;
(
wstring, // wstring suiteName // wstring suiteName // (wstring)L"MyClass::MyMethod"
lambda [](int id)->wstring, // wstring(*)(int id) testFn // FnToTest testFn // [](int id){return (wstring)(id==0?L"IS_ZERO":L"IS_NOT_ZERO");}
vector< // Begin vector<...> tests // vector< // vector({
tuple< // Begin tuple<...> a test // tuple< // make_tuple(
basic_string<wchar_t...>, // basic_string<...> testName // wstring testName // (wstring)L"ShouldReturn_IS_ZERO_for_id_0"
basic_string<wchar_t...>, // basic_string<...> expectedResult // TResult expectedResult // (wstring)L"IS_ZERO"
tuple<int>, // tuple<int> inputParams // tuple<TInputParams...> // make_tuple(0)
lambda [](wstring left, wstring right)->bool, // bool(*)(wstring left, wstring right) testCompareFn // bool(*)(const TResult expected, TResult actual) // [](wstring left, wstring right){return left==right;}
lambda [](int id)->void, // void(*)(int id) testSetupFn // void(*)(int id) testSetupFn // []()(int id)[] dummy function
lambda [](int id)->void, // void(*)(int id) testSetupFn // void(*)(int id) testTeardownFn // []()(int id)[] dummy function
bool // bool isTestEnabled // bool isTestEnabled // true
>, // End tuple<...> a test // > a test // )
allocator<> // Skipping allocator //
>, // End vector<...> tests // > tests // })
lambda [](wstring left, wstring right)->bool, // bool(*)(wstring left, wstring right) suiteCompareFn // bool(*)(TResult a, TResult b) suiteCompareFn // [](wstring left, wstring right){return left==right;}
lambda [](int id)->void, // void(*)(int id) suiteSetupFn // void(*)(int id) suiteSetupFn // [](int id){} dummy function
lambda [](int id)->void, // void(*)(int id) suiteTeardownFn // void(*)(int id) suiteTeardownFn // [](int id){} dummy function
bool // bool isSuiteEnabled // bool isSuiteEnabled // true
)
```
TResult basic_string<...>, wstring /* const TResult */, wstring
test.cpp(350, 16): argument types are: (wstring, lambda [](int id)->wstring, vector<tuple<basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t>>, basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t>>, tuple<int>, lambda [](wstring left, wstring right)->bool, lambda [](int id)->void, lambda [](int id)->void, bool>, allocator<tuple<basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t>>, basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t>>, tuple<int>, lambda [](wstring left, wstring right)->bool, lambda [](int id)->void, lambda [](int id)->void, bool>>>, lambda [](wstring left, wstring right)->bool, lambda [](int id)->void, lambda [](int id)->void, bool)
instance of constructor "
std::__1::vector<_Tp, _Allocator>::vector
[with
_Tp=std::__1::tuple<
std::__1::wstring,
std::__1::wstring,
std::__1::tuple<
const std::__1::wstring &, int
>
>,
_Allocator=std::__1::function<bool (const std::__1::wstring &, const std::__1::wstring &)>
]" matches the argument listC/C++(289)
test.cpp(667, 13): argument types are: ({...})
class std::__1::vector<
std::__1::tuple<
std::__1::wstring,
std::__1::wstring,
std::__1::tuple<const std::__1::wstring &, int>
>,
std::__1::function<bool (const std::__1::wstring &, const std::__1::wstring &)>
>

182
sbf-cpp/Utils.cpp Normal file
View File

@@ -0,0 +1,182 @@
#define _XOPEN_SOURCE_EXTENDED
#include "Utils.h"
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace SBF;
namespace SBF {
namespace {
using std::ostringstream;
using std::string;
using std::vector;
} // End namespace
vector<string> WordWrap(const string& text, const size_t max_width) {
vector<string> lines;
string output = "";
string thisLine = "";
string nextChunk = "";
int32_t thisLineStartPosition = 0;
int32_t thisLineCurrentPosition = 0;
int32_t nextSpace = -1;
int32_t textLength = text.size();
size_t thisLineLength = 0;
bool done = false;
while (!done) {
nextSpace = GetIndexOf(text, " ", thisLineCurrentPosition);
if (nextSpace < 0) {
nextSpace = textLength;
}
nextChunk = GetSubstring(text, thisLineCurrentPosition, nextSpace - thisLineCurrentPosition);
auto nextChunkLength = nextChunk.size();
if (nextChunkLength > 0) {
auto needsSpace = thisLine.size() > 0;
if (needsSpace) {
thisLine = thisLine + " ";
}
thisLineLength = thisLine.size();
if (nextChunkLength > max_width) {
nextChunk = GetSubstring(text, thisLineCurrentPosition, max_width - thisLineLength);
nextSpace = thisLineStartPosition + max_width;
thisLine = thisLine + nextChunk;
thisLineCurrentPosition = nextSpace;
} else if (thisLineLength + nextChunkLength > max_width) {
thisLine = MakeFitL(thisLine, max_width, L' ');
} else {
thisLine = thisLine + nextChunk;
thisLineCurrentPosition = nextSpace + 1;
}
thisLineLength = thisLine.size();
} else {
thisLineCurrentPosition = nextSpace + 1;
}
if (thisLineLength >= max_width || thisLineCurrentPosition > textLength) {
if (thisLineCurrentPosition > textLength) {
done = true;
}
thisLine = MakeFitL(thisLine, max_width, L'_');
output += thisLine + (done ? "" : "\n");
lines.push_back(thisLine);
thisLine = "";
thisLineLength = thisLine.size();
thisLineStartPosition = thisLineCurrentPosition;
}
}
return lines;
}
string RepeatChar(const size_t length, const char ch) {
if (ch == '\0') {
return RepeatChar(length, ' ');
}
string str = "";
for (size_t i = 0; i < length; i++) {
str += ch;
}
return str;
}
string Left(const string& text, const size_t length) {
return text.substr(0, length);
}
string Right(const string& text, const size_t length) {
size_t text_length = text.size();
size_t starting_position = text_length - length;
if (text_length >= length) {
return text.substr(starting_position);
} else {
return text;
}
}
string MakeFitC(const string& text, const size_t length, const char pad_character) {
size_t text_length = text.size();
size_t left_pad_length = length >= text_length ? (length - text_length) / 2 : 0;
size_t right_pad_length = (length >= text_length + left_pad_length) ? (length - text_length - left_pad_length) : 0;
string left_pad = RepeatChar(left_pad_length, pad_character != '\0' ? pad_character : ' ');
string right_pad = RepeatChar(right_pad_length, pad_character != '\0' ? pad_character : ' ');
size_t total_chop = (text_length >= length) ? text_length - length : 0;
size_t left_chop = total_chop / 2; // + 1
string ret = left_pad + (text == "" ? "" : text.substr(left_chop, length)) + right_pad;
return ret;
}
std::string MakeFitB(const std::string& prefix,
const std::string& suffix,
const size_t length,
const char pad_character) {
return MakeFitL(MakeFitL(prefix, length - suffix.size(), pad_character != '\0' ? pad_character : ' ') + suffix,
length,
pad_character != '\0' ? pad_character : ' ');
}
string MakeFitL(const string& text, const size_t length, const char pad_character) {
return Left(text + RepeatChar(length, pad_character != '\0' ? pad_character : ' '), length);
}
string MakeFitR(const string& text, const size_t length, const char pad_character) {
return Right(RepeatChar(length, pad_character != '\0' ? pad_character : ' ') + text, length);
}
string GetSubstring(const string& text, const size_t start, const size_t length) {
return text.substr(std::min<size_t>(start, text.length()), std::max<size_t>(length, 0));
}
size_t GetIndexOf(const string& text, const string& search, const size_t start) {
return text.find(search, start);
}
bool is_whitespace(char ch) {
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == '\f' || ch == '\v';
}
string LeftTrim(const string& text) {
if (text == "") {
return "";
}
size_t index = 0;
size_t length = text.size();
while (index < length) {
if (is_whitespace(text.at(index))) {
index++;
} else {
return text.substr(index);
}
}
return "";
}
string RightTrim(const string& text) {
if (text == "") {
return "";
}
size_t last_index = text.size() - 1;
size_t index = last_index;
while (index != string::npos) {
if (is_whitespace(text.at(index))) {
index--;
} else {
return text.substr(0, index + 1);
}
}
return "";
}
string ToLower(const string& text) {
ostringstream os;
for_each(text.begin(), text.end(), [&os](unsigned char ch) { os << (char)tolower(ch); });
return os.str();
}
} // End namespace SBF

105
sbf-cpp/Utils.h Normal file
View File

@@ -0,0 +1,105 @@
#ifndef UTILS_H__
#define UTILS_H__
/***************************************************************************************
* @file Utils.h
*
* @brief Defines various utility functions.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
#include <string>
#include <vector>
/** \addtogroup Utils
* @{
*/
namespace SBF {
/// @brief Gets the first index of search in text starting at start.
/// @param text The text to search.
/// @param search The text to search for.
/// @param start The position to start searching at.
/// @return The position of the string if found and std::npos if not found.
size_t GetIndexOf(const std::string& text, const std::string& search, const size_t start);
/// @brief Collapses white space and attempts to word wrap text to a max of max_width columns.
/// @param text The text to wrap.
/// @param max_width The number of columns to wrap to.
/// @return The wrapped text.
std::vector<std::string> WordWrap(const std::string& text, const size_t max_width);
/// @brief Gets a substring of another string.
/// @param text The text to split.
/// @param start The starting position.
/// @param length The length of the substring.
/// @return The sub string of text.
std::string GetSubstring(const std::string& text, const size_t start, const size_t length);
/// @brief Pads on the right or truncates text to length using pad_character.
/// @param text The text to operate on.
/// @param length The desired length to make text.
/// @param pad_character The character to pad with.
/// @return The modified string.
std::string MakeFitL(const std::string& text, const size_t length, const char pad_character = ' ');
/// @brief Pads on both sides or truncates text to length using pad_character.
/// @param text The text to operate on.
/// @param length The desired length to make text.
/// @param pad_character The character to pad with.
/// @return The modified string.
std::string MakeFitC(const std::string& text, const size_t length, const char pad_character = ' ');
/// @brief Pads on the left or truncates text to length using pad_character.
/// @param text The text to operate on.
/// @param length The desired length to make text.
/// @param pad_character The character to pad with.
/// @return The modified string.
std::string MakeFitR(const std::string& text, const size_t length, const char pad_character = ' ');
/// @brief Pads or truncates the space between two strings.
/// @param prefix The text to put on the left.
/// @param suffix The text to put on the right.
/// @param length The desired length to make the result.
/// @param pad_character The character to pad with.
/// @return The modified string.
std::string MakeFitB(const std::string& prefix,
const std::string& suffix,
const size_t length,
const char pad_character = ' ');
/// @brief Gets the leftmost length characters of text.
/// @param text The text to operate on.
/// @param length The maximum number of characters to return.
/// @return The leftmost n characters of text where n is the lesser of text.size and length.
std::string Left(const std::string& text, const size_t length);
/// @brief Gets the rightmost length of characters of text.
/// @param text The text to operate on.
/// @param length The maximum number of characters to return.
/// @return The rightmost n characters of text where n is the lesser of text.size and length.
std::string Right(const std::string& text, const size_t length);
/// @brief Removes whitespace from the left side of text.
/// @param text The text to operate on.
std::string LeftTrim(const std::string& text);
/// @brief Removes whitespace from the right side of text.
/// @param text The text to operate on.
std::string RightTrim(const std::string& text);
/// @brief Gets a string made by repeating a character.
/// @param length The length of the string to return.
/// @param ch The character to repeat.
/// @return The repeated string.
std::string RepeatChar(const size_t length, const char ch = ' ');
/// @brief Gets the lowercase version of a string. This is a temporary hack until I figure out how to add ICU4C as a
/// bazel dependency.
/// @param text The text to convert.
/// @return The lowercase version of text. Currently this only supports ASCII characters.
std::string ToLower(const std::string& text);
} // End namespace SBF
/** @}*/
#endif // End !defined UTILS_H__

333
sbf-cpp/Utils_test.cpp Normal file
View File

@@ -0,0 +1,333 @@
#include "Utils.h"
#include <iostream>
#include <string>
#include <tuple>
#include <vector>
#include "test.h"
namespace {
using namespace SBF;
using namespace Test;
using namespace std;
} // End namespace
TestResults test_GetIndexOf() {
string long_text =
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's "
"standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to "
"make a type specimen book. It has survived not only five centuries, but also the leap into electronic "
"typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset "
"sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker "
"including versions of Lorem Ipsum.";
return execute_suite<size_t, string, string, size_t>(make_test_suite(
"SBF::Utils::GetIndexOf(const std::string &text, const std::string &search, const size_t start)",
GetIndexOf,
vector<TestTuple<size_t, string, string, size_t>>({
make_test<size_t, string, string, size_t>(
"should get 0 for the first word in a string", 0, make_tuple(long_text, string("Lorem"), size_t(0))),
make_test<size_t, string, string, size_t>("should get the location of a word in the larger string",
22,
make_tuple(long_text, string("dummy"), size_t(0))),
make_test<size_t, string, string, size_t>(
"should get -1 for the location of a word that is not int the string",
-1,
make_tuple(long_text, string("acid"), size_t(0))),
make_test<size_t, string, string, size_t>(
"should should get the location of a subsequent word within the string",
120,
make_tuple(long_text, string("dummy"), size_t(100))),
make_test<size_t, string, string, size_t>(
"should get -1 for the location of a word that is before start location in the string",
-1,
make_tuple(long_text, string("dummy"), size_t(long_text.size()))),
make_test<size_t, string, string, size_t>("should get the location of a word within a simple string",
6,
make_tuple(string("these are words"), string("are"), size_t(0))),
make_test<size_t, string, string, size_t>("should get the first location of a word within the string",
4,
make_tuple(string("one two one two"), string("two"), size_t(0))),
make_test<size_t, string, string, size_t>("should get the second location of a word within the string",
12,
make_tuple(string("one two one two"), string("two"), size_t(5))),
})));
}
TestResults test_GetSubstring() {
return execute_suite<string, string, size_t, size_t>(make_test_suite(
"SBF::Utils::GetSubstring(const std::string &text, const size_t start, const size_t length)",
GetSubstring,
vector<TestTuple<string, string, size_t, size_t>>({
make_test<string, string, size_t, size_t>(
"should get an empty string if start is too big", "", make_tuple(string("asdf"), size_t(6), size_t(2))),
make_test<string, string, size_t, size_t>(
"should get an empty string if count is 0", "", make_tuple(string("asdf"), size_t(0), size_t(0))),
make_test<string, string, size_t, size_t>(
"should get a string that starts at 0", "as", make_tuple(string("asdf"), size_t(0), size_t(2))),
make_test<string, string, size_t, size_t>(
"should get the whole string", "asdf", make_tuple(string("asdf"), size_t(0), size_t(4))),
make_test<string, string, size_t, size_t>("should get a partial string if count is too big",
"asdf",
make_tuple(string("asdf"), size_t(0), size_t(8))),
make_test<string, string, size_t, size_t>(
"should get a substring", "234", make_tuple(string("1234567890"), size_t(1), size_t(3))),
make_test<string, string, size_t, size_t>(
"should get a word", "Paris", make_tuple(string("Where is Paris?"), size_t(9), size_t(5))),
})));
}
TestResults test_Left() {
return execute_suite<string, string, size_t>(make_test_suite(
"SBF::Utils::Left(const std::string& text, const size_t length)",
Left,
vector<TestTuple<string, string, size_t>>({
make_test<string, string, size_t>(
"should get a substring", "Micro", make_tuple(string("Microsoft QBasic"), size_t(5))),
make_test<string, string, size_t>("should get the whole string if length is equal to text.size()",
"Microsoft QBasic",
make_tuple(string("Microsoft QBasic"), size_t(16))),
make_test<string, string, size_t>("should get the whole string if length is greater than text.size()",
"Microsoft QBasic",
make_tuple(string("Microsoft QBasic"), size_t(20))),
make_test<string, string, size_t>(
"should get an empty string if length is 0", "", make_tuple(string("Microsoft QBasic"), size_t(0))),
make_test<string, string, size_t>(
"should get an empty string if text is empty", "", make_tuple(string(""), size_t(1))),
})));
}
TestResults test_Right() {
return execute_suite<string, string, size_t>(make_test_suite(
"SBF::Utils::Right(const std::string& text, const size_t length)",
Right,
vector<TestTuple<string, string, size_t>>({
make_test<string, string, size_t>(
"should get a substring", "Basic", make_tuple(string("Microsoft QBasic"), size_t(5))),
make_test<string, string, size_t>("should get the whole string if length is equal to text.size()",
"Microsoft QBasic",
make_tuple(string("Microsoft QBasic"), size_t(16))),
make_test<string, string, size_t>("should get the whole string if length is greater than text.size()",
"Microsoft QBasic",
make_tuple(string("Microsoft QBasic"), size_t(20))),
make_test<string, string, size_t>(
"should get an empty string if length is 0", "", make_tuple(string("Microsoft QBasic"), size_t(0))),
make_test<string, string, size_t>(
"should get an empty string if text is empty", "", make_tuple(string(""), size_t(1))),
})));
}
TestResults test_MakeFitB() {
return TestResults().skip(
"SBF::Utils::MakeFitB(const std::string &prefix, const std::string &suffix, const size_t length)");
}
TestResults test_MakeFitC() {
return execute_suite<string, string, int32_t, char>(make_test_suite(
"SBF::Utils::MakeFitC(const std::string &text, const size_t length)",
MakeFitC,
vector<TestTuple<string, string, int32_t, char>>({
make_test<string, string, int32_t, char>(
"should truncate a string that is too long", "soft ", make_tuple(string("Microsoft QBasic"), 5, 'A')),
make_test<string, string, int32_t, char>(
"should pad a string that is too short", "AAMicroAAA", make_tuple(string("Micro"), 10, 'A')),
make_test<string, string, int32_t, char>(
"should return a string that is perfectly sized", "Micro", make_tuple(string("Micro"), 5, 'A')),
make_test<string, string, int32_t, char>("should pad the string with spaces if padCh is the null character",
" Micro ",
make_tuple(string("Micro"), 10, '\0')),
make_test<string, string, int32_t, char>(
"should return a padded string even if text is empty", "ZZZZZZZZZZ", make_tuple(string(""), 10, 'Z')),
})));
}
TestResults test_MakeFitL() {
return execute_suite<string, string, int32_t, char>(make_test_suite(
"SBF::Utils::MakeFitL(const std::string &text, const size_t length)",
MakeFitL,
vector<TestTuple<string, string, int32_t, char>>({
make_test<string, string, int32_t, char>(
"should truncate a string that is too long", "Micro", make_tuple(string("Microsoft QBasic"), 5, 'A')),
make_test<string, string, int32_t, char>(
"should pad a string that is too short", "MicroAAAAA", make_tuple(string("Micro"), 10, 'A')),
make_test<string, string, int32_t, char>(
"should return a string that is perfectly sized", "Micro", make_tuple(string("Micro"), 5, 'A')),
make_test<string, string, int32_t, char>("should pad the string with spaces if padCh is the null character",
"Micro ",
make_tuple(string("Micro"), 10, '\0')),
make_test<string, string, int32_t, char>(
"should return a padded string even if text is empty", "ZZZZZZZZZZ", make_tuple(string(""), 10, 'Z')),
})));
}
TestResults test_MakeFitR() {
return execute_suite<string, string, int32_t, char>(make_test_suite(
"SBF::Utils::MakeFitR(const std::string &text, const size_t length)",
MakeFitR,
vector<TestTuple<string, string, int32_t, char>>({
make_test<string, string, int32_t, char>(
"should truncate a string that is too long", "Basic", make_tuple(string("Microsoft QBasic"), 5, 'A')),
make_test<string, string, int32_t, char>(
"should pad a string that is too short", "AAAAAMicro", make_tuple(string("Micro"), 10, 'A')),
make_test<string, string, int32_t, char>(
"should return a string that is perfectly sized", "Micro", make_tuple(string("Micro"), 5, 'A')),
make_test<string, string, int32_t, char>("should pad the string with spaces if padCh is the null character",
" Micro",
make_tuple(string("Micro"), 10, '\0')),
make_test<string, string, int32_t, char>(
"should return a padded string even if text is empty", "ZZZZZZZZZZ", make_tuple(string(""), 10, 'Z')),
})));
}
TestResults test_LeftTrim() {
return execute_suite<string, string>(make_test_suite(
"SBF::Utils::LeftTrim(const std::string &text)",
LeftTrim,
vector<TestTuple<string, string>>({
make_test<string, string>("should trim a string with spaces",
"this is a string with spaces on either end ",
make_tuple(string(" this is a string with spaces on either end "))),
make_test<string, string>("should trim a string with tabs",
"this is a string with tabs on either end\t\t\t\t",
make_tuple(string("\t\t\t\tthis is a string with tabs on either end\t\t\t\t"))),
make_test<string, string>("should trim a string with newlines",
"this is a string with newlines on either end\n\n\n\n",
make_tuple(string("\n\n\n\nthis is a string with newlines on either end\n\n\n\n"))),
make_test<string, string>(
"should trim a string with carrige returns",
"this is a string with carriage returns on either end\r\r\r\r",
make_tuple(string("\r\r\r\rthis is a string with carriage returns on either end\r\r\r\r"))),
make_test<string, string>(
"should trim a string with mixed whitespace",
"this is a string with mixed whitespace on either end\f\v\r\n\t ",
make_tuple(string(" \t\n\r\v\fthis is a string with mixed whitespace on either end\f\v\r\n\t "))),
make_test<string, string>("should get an unmodified string if there is nothing to trim",
"this is a string that won't be trimmed",
make_tuple(string("this is a string that won't be trimmed"))),
make_test<string, string>("should get an empty string for an empty string", "", make_tuple(string(""))),
make_test<string, string>(
"should get an empty string for an all whitespace string", "", make_tuple(string(" \t\n\r\r\n\t "))),
})));
}
TestResults test_RightTrim() {
return execute_suite<string, string>(make_test_suite(
"SBF::Utils::RightTrim(const std::string &text)",
RightTrim,
vector<TestTuple<string, string>>({
make_test<string, string>("should trim a string with spaces",
" this is a string with spaces on either end",
make_tuple(string(" this is a string with spaces on either end "))),
make_test<string, string>("should trim a string with tabs",
"\t\t\t\tthis is a string with tabs on either end",
make_tuple(string("\t\t\t\tthis is a string with tabs on either end\t\t\t\t"))),
make_test<string, string>("should trim a string with newlines",
"\n\n\n\nthis is a string with newlines on either end",
make_tuple(string("\n\n\n\nthis is a string with newlines on either end\n\n\n\n"))),
make_test<string, string>(
"should trim a string with carrige returns",
"\r\r\r\rthis is a string with carriage returns on either end",
make_tuple(string("\r\r\r\rthis is a string with carriage returns on either end\r\r\r\r"))),
make_test<string, string>(
"should trim a string with mixed whitespace",
" \t\n\r\v\fthis is a string with mixed whitespace on either end",
make_tuple(string(" \t\n\r\v\fthis is a string with mixed whitespace on either end\f\v\r\n\t "))),
make_test<string, string>("should get an unmodified string if there is nothing to trim",
"this is a string that won't be trimmed",
make_tuple(string("this is a string that won't be trimmed"))),
make_test<string, string>("should get an empty string for an empty string", "", make_tuple(string(""))),
make_test<string, string>(
"should get an empty string for an all whitespace string", "", make_tuple(string(" \t\n\r\r\n\t "))),
})));
}
TestResults test_RepeatChar() {
return execute_suite<string, size_t, char>(make_test_suite(
"SBF::Utils::RepeatChar(const size_t length, const char ch)",
RepeatChar,
vector<TestTuple<string, size_t, char>>({
make_test<string, size_t, char>("should make a string", "YYYYY", make_tuple(size_t(5), 'Y')),
make_test<string, size_t, char>(
"should make a string of spaces if ch is the null character", " ", make_tuple(size_t(5), '\0')),
make_test<string, size_t, char>("should make an empty string if length is 0", "", make_tuple(size_t(0), 'C')),
})));
}
TestResults test_RegexReplace() {
return TestResults()
.skip(
"SBF::Utils::RegexReplace(const string& text, const string& pattern, const string& replace) // TODO: Fill "
"this in.")
.skip("SBF::Utils::RegexReplace(const string& text, regex regex, const string& replace) // TODO: Fill this in.");
}
TestResults test_WordWrap() {
// TODO: Treat newlines and tabs in text as spaces.
auto fnToTest = [](string text, int32_t column_width, vector<string> expected) -> string {
ostringstream error_message;
try {
vector<string> actual = WordWrap(text, column_width);
compare(error_message, expected, actual);
} catch (const exception& ex) {
error_message << ex.what();
} catch (...) {
error_message << "threw something that was not an exception";
}
string error_string = error_message.str();
if (error_string.size() > 0) {
return error_message.str();
}
return "no errors";
};
return execute_suite<string, string, int32_t, vector<string>>(make_test_suite(
"SBF::Utils::WordWrap(const std::string &text, const size_t max_width)",
fnToTest,
vector<TestTuple<string, string, int32_t, vector<string>>>({
make_test<string, string, int32_t, vector<string>>("should return the string if it is shorter than max_width",
"no errors",
make_tuple(string("0123"), 5, vector<string>({"0123_"}))),
make_test<string, string, int32_t, vector<string>>(
"should return the string if its length is equal to max_width",
"no errors",
make_tuple(string("01234"), 5, vector<string>({"01234"}))),
make_test<string, string, int32_t, vector<string>>(
"should wrap a string to two lines if it has no whitespace",
"no errors",
make_tuple(string("012345"), 5, vector<string>({"01234", "5____"}))),
make_test<string, string, int32_t, vector<string>>(
"should wrap a string to three lines if it has no whitespace",
"no errors",
make_tuple(string("01234567890"), 5, vector<string>({"01234", "56789", "0____"}))),
make_test<string, string, int32_t, vector<string>>(
"should wrap a string with even spacing",
"no errors",
make_tuple(string("01 23 45 67 89 01"), 5, vector<string>({"01 23", "45 67", "89 01"}))),
make_test<string, string, int32_t, vector<string>>(
"should collapse whitespace to a single space",
"no errors",
make_tuple(
string("01 34 67 90 23 56 89 "), 5, vector<string>({"01 34", "67 90", "23 56", "89___"}))),
})));
}
int main(int argc, char* argv[]) {
TestResults results;
results += test_GetIndexOf();
results += test_GetSubstring();
results += test_Left();
results += test_LeftTrim();
results += test_MakeFitC();
results += test_MakeFitL();
results += test_MakeFitR();
results += test_RegexReplace();
results += test_Right();
results += test_RightTrim();
results += test_RepeatChar();
results += test_WordWrap();
PrintResults(cout, results);
return results.failed() + results.errors();
}

47
sbf-cpp/Virtues.cpp Normal file
View File

@@ -0,0 +1,47 @@
#include "Virtues.h"
#include <string>
#include <vector>
namespace SBF {
namespace {
using std::string;
using std::vector;
} // End namespace
const std::string kVirtueUnknownLabel = "";
const std::string kVirtueSelfControlLabel = "Self-Control";
const std::string kVirtueCourageLabel = "Courage";
const std::string kVirtueConscienceLabel = "Conscience";
const int kVirtuesCount = 3;
const std::string kVirtueLabels[] = {
kVirtueUnknownLabel,
kVirtueSelfControlLabel,
kVirtueCourageLabel,
kVirtueConscienceLabel,
};
const string GetVirtueLabel(int id) {
if (id > 0 && id <= kVirtuesCount) {
return kVirtueLabels[id];
}
return "";
}
void FillVirtueLabels(vector<string>& labels) {
labels.clear();
for (int id = 1; id <= kVirtuesCount; id++) {
labels.push_back(GetVirtueLabel(id));
}
}
vector<string> GetVirtueLabels() {
vector<string> labels;
FillVirtueLabels(labels);
return labels;
}
int GetNumVirtues() {
return kVirtuesCount;
}
} // End namespace SBF

37
sbf-cpp/Virtues.h Normal file
View File

@@ -0,0 +1,37 @@
#ifndef VIRTUES_H__
#define VIRTUES_H__
/***************************************************************************************
* @file Virtues.h
*
* @brief Defines constants and functions for working with virtues.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
#include <string>
#include <vector>
/** \addtogroup Virtues
* @{
*/
namespace SBF {
const int kVirtuePoints = 7;
const int kVirtueSelfControlId = 1;
const int kVirtueCourageId = 2;
const int kVirtueConscienceId = 3;
/// @brief Gets the label for virtue with the specified id.
/// @param virtue_id The id of the virtue to find.
/// @return The label for the specified virtue or an empty string if virtue_id is invalid.
const std::string GetVirtueLabel(int virtue_id);
/// @brief Fills the vector with all of the valid virtue labels.
/// @param virtue_labels The vector to fill. It will be cleared first.
void FillVirtueLabels(std::vector<std::string>& virtue_labels);
std::vector<std::string> GetVirtueLabels();
int GetNumVirtues();
} // End namespace SBF
/** @}*/
#endif // End !defined VIRTUES_H__

73
sbf-cpp/Virtues_test.cpp Normal file
View File

@@ -0,0 +1,73 @@
#include "Virtues.h"
#include <iostream>
#include <string>
#include <tuple>
#include <vector>
#include "test.h"
namespace {
using namespace SBF;
using namespace Test;
using namespace std;
} // End namespace
const std::string kVirtueUnknownLabel = "";
const std::string kVirtueSelfControlLabel = "Self-Control";
const std::string kVirtueCourageLabel = "Courage";
const std::string kVirtueConscienceLabel = "Conscience";
const std::string kVirtueLabels[] = {
kVirtueUnknownLabel,
kVirtueSelfControlLabel,
kVirtueCourageLabel,
kVirtueConscienceLabel,
};
TestResults test_GetVirtueLabel() {
return execute_suite<string, int>(make_test_suite(
"SBF::GetVirtueLabel",
GetVirtueLabel,
vector<TestTuple<string, int>>({
make_test<string, int>("should get \"\" for invalid virtue id 0", "", make_tuple(0)),
make_test<string, int>("should get \"Self-Control\" for virtue id 1", "Self-Control", make_tuple(1)),
make_test<string, int>("should get \"Courage\" for virtue id 2", "Courage", make_tuple(2)),
make_test<string, int>("should get \"Conscience\" for virtue id 3", "Conscience", make_tuple(3)),
make_test<string, int>("should get \"\" for invalid virtue id 4", "", make_tuple(4)),
})));
}
TestResults test_FillVirtueLabels() {
auto fnToTest = []() -> string {
ostringstream error_message;
vector<string> expected = {
kVirtueSelfControlLabel,
kVirtueCourageLabel,
kVirtueConscienceLabel,
};
vector<string> actual = {"This should be removed."};
FillVirtueLabels(actual);
compare(error_message, expected, actual);
string error = error_message.str();
if (error.size() > 0) {
return error;
}
return "no errors";
};
return execute_suite<string>(make_test_suite("SBF::FillVirtueLabels",
fnToTest,
vector<TestTuple<string>>({
make_test<string>("should fill ranks", "no errors", make_tuple()),
})));
}
int main(int argc, char* argv[]) {
TestResults results;
results += test_GetVirtueLabel();
results += test_FillVirtueLabels();
PrintResults(cout, results);
return results.failed() + results.errors();
}

81
sbf-cpp/main.cpp Normal file
View File

@@ -0,0 +1,81 @@
#define _XOPEN_SOURCE_EXTENDED
#include <ncursesw/curses.h>
#include <cstdio>
#include <iostream>
#include <string>
#define KEY_ESCAPE 0033
using namespace std;
/*
| ╔ | WACS_D_ULCORNER | WACS_BDDB |
| ╚ | WACS_D_LLCORNER | WACS_DDBB |
| ╗ | WACS_D_URCORNER | WACS_BBDD |
| ╝ | WACS_D_LRCORNER | WACS_DBBD |
| ╣ | WACS_D_RTEE | WACS_DBDD |
| ╠ | WACS_D_LTEE | WACS_DDDB |
| ╩ | WACS_D_BTEE | WACS_DDBD |
| ╦ | WACS_D_TTEE | WACS_BDDD |
| ═ | WACS_D_HLINE | WACS_BDBD |
| ║ | WACS_D_VLINE | WACS_DBDB |
| ╬ | WACS_D_PLUS | WACS_DDDD |
*/
int set_double_border(WINDOW* screen) {
return wborder_set(screen,
WACS_D_VLINE /*left side*/,
WACS_D_VLINE /*right side*/,
WACS_D_HLINE /*top side*/,
WACS_D_HLINE /*bottom side*/,
WACS_D_ULCORNER /*top left corner*/,
WACS_D_URCORNER /*top right corner*/,
WACS_D_LLCORNER /*bottom left corner*/,
WACS_D_LRCORNER /*bottom right corner*/);
}
int main(int argc, char* argv[]) {
setlocale(LC_ALL, "");
char buffer[255] = {0};
WINDOW* pWindow = nullptr;
// Load ncurses and initialize stdscr.
initscr();
pWindow = stdscr;
// Enable raw mode to give us direct access to extra keys.
raw();
// Disable echoing characters as they are typed.
noecho();
// Enable keypad characters. (function keys, arrow keys, and numpad keys.)
keypad(pWindow, true);
// Remove the delay when pressing esc.
set_escdelay(0);
int key = KEY_RESIZE;
while (key != KEY_ESCAPE) {
if (key == KEY_RESIZE) {
// Clear the screen.
werase(pWindow);
// Draw a box around the window with default chars.
set_double_border(pWindow);
// Display the event
mvwaddstr(pWindow, 2, 2, "Resized");
// Get the window width and height.
int width;
int height;
getmaxyx(pWindow, height, width);
// Display the width and height.
snprintf(buffer, sizeof(buffer), "width: %i, height: %i", width, height);
mvwaddstr(pWindow, 1, 2, buffer);
} else {
// Display the keycode of the key that was pressed in hex and octal.
snprintf(buffer, sizeof(buffer), "0x%04X %04o pressed", key, key);
mvwaddstr(pWindow, 2, 2, buffer);
}
wrefresh(pWindow);
key = wgetch(pWindow);
}
endwin();
return 0;
}

19
sbf-cpp/main.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef MAIN_H__
#define MAIN_H__
/***************************************************************************************
* @file main.h
*
* @brief Defines stuff for the main app.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
/** \addtogroup Unsorted
* @{
*/
namespace SBF {}
/** @}*/
#endif

22
sbf-cpp/main_test.cpp Normal file
View File

@@ -0,0 +1,22 @@
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#include "Menus.h"
#include "test.h"
namespace {
using namespace SBF;
using namespace Test;
using namespace std;
} // End namespace
int main(int argc, char* argv[]) {
TestResults results;
PrintResults(cout, results);
return results.failed() + results.errors();
}

133
sbf-cpp/sbf.cpp Normal file
View File

@@ -0,0 +1,133 @@
#include "sbf.h"
#define _XOPEN_SOURCE_EXTENDED
#include <cstdio>
#include <iostream>
#include <string>
#include <vector>
#include "Abilities.h"
#include "Archetypes.h"
#include "Attributes.h"
#include "Backgrounds.h"
#include "Character.h"
#include "CharacterGenerator.h"
#include "Clans.h"
#include "Disciplines.h"
#include "Freebies.h"
#include "Genders.h"
#include "Menus.h"
#include "Random.h"
#include "Utils.h"
#include "Virtues.h"
#define KEY_ESCAPE 0033
namespace {
using namespace SBF;
using std::cout;
using std::endl;
} // namespace
void CharacterGeneratorForDummies();
void CombatComputer();
void DiceRoller();
void MainMenu();
void RandomCharacterGenerator();
void SaveCharacterSheet(CharacterType& ch);
void ShowCharacterSheet(CharacterType& ch);
void ShowSplashScreen();
void VehicleGenerator();
int main(int argc, char* argv[]) {
setlocale(LC_ALL, "");
ShowSplashScreen();
MainMenu();
return 0;
}
void MainMenu() {
int choice = 0;
do {
MaybeClearScreen();
cout << "╔══════════════════════════════════════════════════════════════════════════════╗" << endl
<< "║ What are you going to do? ║" << endl
<< "║ 1 = Character Generator ║" << endl
<< "║ 2 = Character Generator for Dummies ║" << endl
<< "║ 3 = Combat Computer ║" << endl
<< "║ 4 = Dice Roller ║" << endl
<< "║ 5 = Random Character Generator ║" << endl
<< "║ 6 = ║" << endl
<< "║ 7 = Vehicle Generator ║" << endl
<< "║ ║" << endl
<< "║ ║" << endl
<< "║ ║" << endl
<< "║ ║" << endl
<< "║ ║" << endl
<< "║ ║" << endl
<< "║ ║" << endl
<< "║ ║" << endl
<< "║ ║" << endl
<< "║ ║" << endl
<< "║ ║" << endl
<< "║ 0 = End ║" << endl
<< "║ ║" << endl
<< "╚══════════════════════════════════════════════════════════════════════════════╝" << endl;
choice = GetChoice(0, 7);
switch (choice) {
case 1:
CharacterGenerator();
break;
case 2:
CharacterGeneratorForDummies();
break;
case 3:
CombatComputer();
break;
case 4:
DiceRoller();
break;
case 5:
RandomCharacterGenerator();
break;
case 7:
VehicleGenerator();
break;
}
} while (choice != 0);
}
void ShowSplashScreen() {
cout << "Welcome to Tom's Storyteller's Best Friend. This is a program that is meant to" << endl
<< "aid storytellers in running Vampire: the Masquerade Chronicles and Vampire: the" << endl
<< "Dark Ages Chronicles. This program could aid in running campaigns for other" << endl
<< "role-playing games especially those from White Wolf(tm). If you would like" << endl
<< "anything added please open a github issue. https://github.com/headhunter45/sbf" << endl
<< " Press any key to continue" << endl;
WaitForKeypress();
}
void CharacterGeneratorForDummies() {
// TODO: Fill in this function.
cout << "// TODO: CharacterGeneratorForDummies()" << endl;
}
void CombatComputer() {
// TODO: Fill in this function.
cout << "// TODO: CombatComputer()" << endl;
}
void DiceRoller() {
// TODO: Fill in this function.
cout << "// TODO: DiceRoller()" << endl;
}
void RandomCharacterGenerator() {
// TODO: Fill in this function.
cout << "// TODO: RandomCharacterGenerator()" << endl;
}
void VehicleGenerator() {
// TODO: Fill in this function.
cout << "// TODO: VehicleGenerator()" << endl;
}

257
sbf-cpp/sbf.h Normal file
View File

@@ -0,0 +1,257 @@
#ifndef SBF_H__
#define SBF_H__
/***************************************************************************************
* @file sbf.h
*
* @brief Holds code copied from the BASIC version that hasn't been relocated yet.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
#include <string>
/** \addtogroup Unsorted
* @{
*/
namespace SBF {
using std::string;
/*
' This sub is not called. It is here so it can be copied whenever I need to make a new bordered screen.
Sub BlankScreen
Print "╔══════════════════════════════════════════════════════════════════════════════╗"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "║ ║"
Print "╚══════════════════════════════════════════════════════════════════════════════╝"
End Sub
Sub SaveCharacterSheet (ch As CharacterType)
Call MaybePrint("TODO: Fill in SaveCharacterSheet")
Call MaybePrint(ch.name)
' Where do you want the file to be saved? (default is C:\Windows\Desktop)?
' What do you want the file to be called? (default is CHAR1)?
'CHAR1.TXT
'/------------------------------------------------------------------------------\
'| Name: sadf | Sex: Male Generation: 12 |
'| Clan: Brujah | Age: mmmmmm |
'|--------------------------------------| Player: fdsa |
'| Attributes | Chronicle: jfjf |
'| Physical Social Mental | Haven: kkkkkk |
'| Str. 5 App. 2 Int. 1 | Concept: llllll |
'| Dex. 3 Cha. 2 Per. 1 |---------------------------------------|
'| Sta. 2 Man. 4 Wit. 4 | Derangementss: |
'|--------------------------------------| _____________________________________ |
'| Disciplines: | _____________________________________ |
'| Obtenebration | _____________________________________ |
'| Obtenebration | _____________________________________ |
'| Obtenebration | _____________________________________ |
'|------------------------------------------------------------------------------|
'| Beast: 8 | Nature: Fanatic |
'| Willpower: 2 | Demeanor: Architect |
'|------------------------------------------------------------------------------|
'| Abilities |
'| Talents Skills Knowledges |
'| Acting: 6 Animal Ken: 2 Bureaucracy: 1 |
'| Alertness: 2 Drive: 2 Computer: 2 |
'| Athletics: 2 Etiquette: 2 Finance: 1 |
'| Brawl: 1 Firearms: 1 Investigation: 1 |
'| Dodge: 1 Melee: 1 Law: 0 |
'| Empathy: 1 Music: 1 Linguistics: 0 |
'| Intimidation: 0 Repair: 0 Medecine: 0 |
'| Leadership: 0 Security: 0 Occult: 0 |
'| Streetwise: 0 Stealth: 0 Politics: 0 |
'| Subterfuge: 0 Survival: 0 Science: 0 |
'|------------------------------------------------------------------------------|
'| Backgrounds: | Virtues: |
'| Allies | Conscience: 0 |
'| Contacts | Conviction: 3 |
'| Contacts | Instinct: 5 |
'| Fame | Self-Control: 0 |
'| Generation | Courage: 2 |
'|--------------------------------------/ |
'| |
'\------------------------------------------------------------------------------/
'CHAR2.TXT
'/------------------------------------------------------------------------------\
'| Name: _______________________________| Sex: Female Generation: 13 |
'| Clan: Lasombra | Age: ________________________________ |
'|--------------------------------------| Player: _____________________________ |
'| Attributes | Chronicle: __________________________ |
'| Physical Social Mental | Haven: ______________________________ |
'| Str. 2 App. 3 Int. 3 | Concept: ____________________________ |
'| Dex. 1 Cha. 2 Per. 5 |---------------------------------------|
'| Sta. 3 Man. 3 Wit. 2 | Derangements: |
'|--------------------------------------| _____________________________________ |
'| Disciplines: | _____________________________________ |
'| Vicissitude | _____________________________________ |
'| Spiritus | _____________________________________ |
'| Auspex | _____________________________________ |
'|------------------------------------------------------------------------------|
'| Chivalry: 7 | Nature: Autocrat |
'| Willpower: 3 | Demeanor: Defender |
'|------------------------------------------------------------------------------|
'| Abilities |
'| Talents Skills Knowledges |
'| Acting: 0 Animal Ken: 0 Bureaucracy: 2 |
'| Alertness: 2 Drive: 0 Computer: 3 |
'| Athletics: 1 Etiquette: 1 Finance: 2 |
'| Brawl: 1 Firearms: 1 Investigation: 1 |
'| Dodge: 2 Melee: 1 Law: 0 |
'| Empathy: 0 Music: 0 Linguistics: 2 |
'| Intimidation: 1 Repair: 1 Medecine: 1 |
'| Leadership: 0 Security: 1 Occult: 1 |
'| Streetwise: 0 Stealth: 0 Politics: 0 |
'| Subterfuge: 2 Survival: 0 Science: 1 |
'|------------------------------------------------------------------------------|
'| Backgrounds: | Virtues: |
'| Contacts | Conscience: 2 |
'| Influence | Conviction: 0 |
'| Allies | Instinct: 0 |
'| Herd | Self-Control: 5 |
'| Status | Courage: 3 |
'|--------------------------------------/ |
'| |
'\------------------------------------------------------------------------------/
'RANDY.TXT
'/------------------------------------------------------------------------------\
'| Name: Randy | Sex: Female Generation: 12 |
'| Clan: Gangrel | Age: ________________________________ |
'|--------------------------------------| Player: _____________________________ |
'| Attributes | Chronicle: __________________________ |
'| Physical Social Mental | Haven: ______________________________ |
'| Str. 3 App. 1 Int. 2 | Concept: ____________________________ |
'| Dex. 6 Cha. 3 Per. 3 |---------------------------------------|
'| Sta. 1 Man. 2 Wit. 3 | Derangements: |
'|--------------------------------------| _____________________________________ |
'| Disciplines: | _____________________________________ |
'| Animalism | _____________________________________ |
'| Celerity | _____________________________________ |
'| Mortis | _____________________________________ |
'|------------------------------------------------------------------------------|
'| Devil: 7 | Nature: Traditionalist |
'| Willpower: 3 | Demeanor: Bon vivant |
'|------------------------------------------------------------------------------|
'| Abilities |
'| Talents Skills Knowledges |
'| Acting: 1 Animal Ken: 0 Bureaucracy: 1 |
'| Alertness: 2 Drive: 0 Computer: 0 |
'| Athletics: 1 Etiquette: 0 Finance: 1 |
'| Brawl: 1 Firearms: 0 Investigation: 3 |
'| Dodge: 1 Melee: 0 Law: 1 |
'| Empathy: 1 Music: 0 Linguistics: 3 |
'| Intimidation: 1 Repair: 0 Medecine: 0 |
'| Leadership: 0 Security: 2 Occult: 2 |
'| Streetwise: 0 Stealth: 1 Politics: 2 |
'| Subterfuge: 1 Survival: 2 Science: 0 |
'|------------------------------------------------------------------------------|
'| Backgrounds: | Virtues: |
'| Retainers | Conscience: 0 |
'| Contacts | Conviction: 2 |
'| Retainers | Instinct: 5 |
'| Generation | Self-Control: 0 |
'| Herd | Courage: 3 |
'|--------------------------------------/ |
'| |
'\------------------------------------------------------------------------------/
End Sub
Sub CharacterGenerator ()
Dim ch As CharacterType
Call NewCharacter(ch)
Call CGGetHeader(ch)
Call CGGetDisciplines(ch)
Call CGGetAttributes(ch)
Call CGGetAbilities(ch)
Call CGGetBackgrounds(ch)
Call CGGetRoad(ch)
Call CGSpendVirtuePoints(ch)
Call CGGetDerangement(ch)
' Generation starts at 13 and goes down 1 point per point of the "generation" background.
ch.generation = INITIAL_GENERATION - GetBackground(ch, BACKGROUND_GENERATION)
' Willpower
ch.willpower = ch.courage
' Humanity
ch.roadValue = ch.conscience + ch.selfControl
' Blood Pool - The only die roll.
ch.bloodPool = GetRandomInt(1, 10)
' Spend freebie points
Call CGSpendFreebiePoints(ch)
Call SaveCharacterSheet(ch)
Call ShowCharacterSheet(ch)
End Sub
'... 0123456789
'160 áíóúñѪº¿⌐
'170 ¬½¼¡«»░▒▓│
'180 ┤╡╢╖╕╣║╗╝╜
'190 ╛┐└┴┬├─┼╞╟
'200 ╚╔╩╦╠═╬╧╨╤
'210 ╥╙╘╒╓╫╪┘┌█
'220 ▄▌▐▀αßΓπΣσσ
'230 µτΦΘΩδ2∞φε∩
' enquote forms s/^([╔║╚═╠].*[╗║╝╣])$/print "$1"/g
' Simpler character generator with fewer questions and more things done randomly without asking.
Sub CharacterGeneratorForDummies
Call MaybePrint("CharacterGeneratorForDummies")
End Sub
' Maybe just remove this. It's kinda pointless. It asks some questions and calculates a contested roll.
' C1 dice pool, C1 difficulty, C2 dice pool, C2 difficulty, then rolls all the dice and does the math.
' In practice it's just slower than rolling the dice
Sub CombatComputer
Call MaybePrint("CombatComputer")
End Sub
' Asks for a number of dice and a difficulty. Rolls the dice, calculates botches and successes.
Sub DiceRoller
Call MaybePrint("DiceRoller")
End Sub
' Like the character generator if you choose random for everything. Should do random names/ages too, but doesn't yet.
Sub RandomCharacterGenerator
Call MaybePrint("RandomCharacterGenerator")
End Sub
' This had a function at one point but got taken out. Will only come back if the disassembly can figure it out.
Sub Choice6
Call MaybePrint("Unnamed choice 6")
End Sub
' Like the character generator but for vehicles. Much simpler with fewer questions. Prints a vehicle sheet when done.
Never finished and crashes mid way through currently. Sub VehicleGenerator Call MaybePrint("VehicleGenerator") End Sub
Sub MaybePrint (message As String)
If isDebugging Then Print message
End Sub
*/
} // End namespace SBF
/** #}*/
#endif // End !defined SBF_H__

32
sbf-cpp/sbf_test.cpp Normal file
View File

@@ -0,0 +1,32 @@
#include "sbf.h"
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
#include "test.h"
using namespace SBF;
using namespace Test;
using namespace std;
namespace Test::sbf {} // End namespace Test::sbf
using namespace Test::sbf;
TestResults main_test_sbf(int argc, char* argv[]) {
TestResults results;
return results;
}
int main(int argc, char* argv[]) {
TestResults results = main_test_sbf(argc, argv);
PrintResults(cout, results);
return results.failed() + results.errors();
}
namespace Test::sbf {} // namespace Test::sbf

283
sbf-cpp/test.cpp Normal file
View File

@@ -0,0 +1,283 @@
#define _XOPEN_SOURCE_EXTENDED
#include "test.h"
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <string>
#include <vector>
namespace Test {
namespace {
using std::endl;
using std::string;
using std::vector;
} // End namespace
// Test lifecycle
// suiteSetupFn(); - This is called to allocate any suite level resources. This is called once when the suite begins.
// These functions may be called in parallel but execution will not proceed past this block until they have all
// finished.
// testSetupFn(); - This is called once for every test in tests. You may use it to allocate resources or setup mocks,
// stubs, and spies. testFn(...); - This is called once for every test to execute the test. Only one of these test
// functions will actually be run for each test in tests. They should return true if the test passed, return false if
// the test failed or there was an error, and be nullptr if they should be skipped. The executed function will be
// called with expectedOutput and the result of testFn(...). They can be used to test functions with side effects,
// especially void functions. maybe_compare_function; - This is the highest priority compare function. If it is not
// nullptr then it will be called. suite_compare_function; - This is the second highest priority compare function. If
// maybe_compare_function is nullptr and this is not nullptr then it will be called.
// [](TResult expected, TResult actual) { return expected, actual; } - This is the lowest priority compare function.
// If all other compare functions are nullptr then this will be called to evaluate the test. testTeardownFn(); - This
// is called once for every test in tests. You must free/release any resources allocated by testSetupFn.
// This ends the parallel test functions section all tests will have completed before execution proceeds.
// Collect reports - Ths step is not visible to the user at this point, but data returned by all of the test functions
// is collected here. This is where you will eventually be able to format/log data for reports. suiteTeardownFn(); -
// This is called after all test calls have completed, all testTeardownFn calls have completed, and all test
// reports/logs have been written. You should free any resources allocated in suiteSetupFn.
// TODO: Add TShared(*)(string /*test_name*/, UUID /*testRunId*/) allocateSharedData to the test tuple to make some
// shared data that can be used in a thread safe way by setup, teardown, and evaluate steps of the test.
// TODO: Add TShared to be returned by the setup functions, and consumed by the evaluate and teardown functions.
// Suite setup/teardown functions should allocate/free.
// Test setup/teardown functions should consume the data allocated by Suite setup.
// Test setup functions may allocate additional resources. If they do then the allocated resources they should be
// freed by test teardown function. Suite and/or Test compare functions may consume this shared data, but it will not
// be shared with the execution of testFn.
// This function is called to execute a test suite. You provide it with some configuration info, optional utility
// callback functions, and test data (input parameters for each call to testFn and the expected result). It returns a
// TestResults that should be treated as an opaque data type. Not all parameters are named in code, but they are named
// and explained in the comments and will be described by those names below.
// string suite_label - This is the name of this test suite. It is used for reporting messages.
// FnToTest testFn - This is the function to test. This may be replaced if necessary by function. It may not currently
// support class methods, but that is planned. vector<tuple<...>> tests - This is the test run data. Each tuple in the
// vector is a single test run. It's members are explained below.
// string test_name - This is the name of this test. It is used for reporting messages.
// TResult expectedOutput - This is the expected result of executing this test.
// bool(*)(const TResult expected, const TResult actual) maybe_compare_function - This is optional. If unset or set
// to nullptr it is skipped. If set to a function it is called to evaluate the test results. It takes the expected
// and actual results as parameters and should return true if the test passed and false otherwise. This may be
// changed to return a TestResults at some point. void(*)(TInputParams...) testSetupFn - This is optional. If unset
// or set to nullptr it is skipped. If set to a function it is called before each test to setup the environment for
// the test. You may use it to allocate resources and setup mocks, stubs, and spies. void(*)(TInputParams...)
// testTeardownFn - This is optiona. If unset or set to nullptr it is skipped. If set to a function it is called
// after each test to cleanup the environment after the test. You should free resources allocated by testSetupFn.
// bool isEnabled - This is optional. If unset or set to true the test is run. If set to false this test is skipped.
// If skipped it will be reported as a skipped/disabled test.
// bool(*)(const TResult expected, const TResult actual) suite_compare_function - This is optional. If unset or set to
// nullptr it is skipped. If set to a function and maybe_compare_function is not called for a test run then this
// function is called to evaluate the test results. It takes the expected and actual results as parameters and should
// return true if the test passed and false otherwise. This may be changed to return a TestResults at some point.
// void(*)() suiteSetupFn - This is optional. If unset or set to nullptr it is skipped. If set to a function it is
// called before starting this test suite to setup the environment. You may use it to allocate resources and setup
// mocks, stubs, and spies. void(*)() suiteTeardownFn - This is optional. If unset or set to nullptr it is skipped. If
// set to a function it is called after all tests in this suite have finished and all reporting has finished. You
// should free resources allocated by suiteSetupFn.
// This method should be called like so. This is the minimal call and omits all of the optional params. This is the most
// common usage. You should put one tuple of inputs and expected output for each test case.
// results = collect_and_report_TestResultstest_fn(
// "Test: functionUnderTest",
// functionUnderTest,
// vector({
// make_tuple(
// "ShouldReturnAppleForGroupId_1_and_ItemId_2",
// string("Apple"),
// make_tuple(1,2),
// ),
// }),
// );
// The suites can be run from one file as such. From a file called ThingDoer_test.cpp to test the class/methods
// ThingDoer declared in ThingDoer.cpp. This isn't mandatory but is a best practice. You can use testFn without calling
// collect_and_report_TestResults() and also could call it from a normal int main(int argc, char* argv[]) or other
// function.
// TestResults test_main_ThingDoer(int argc, char* argv[]) {
// TestResults results;
// results = collect_and_report_TestResults(results, testFn("doThing1", ...), argc, argv);
// results = collect_and_report_TestResults(results, testFn("doThing2", ...), argc, argv);
// return results;
// }
// Then some test harness either generated or explicit can call test_main_ThingDoer(...) and optionally reported there.
// Reporting granularity is controlled by how frequently you call collect_and_report_TestResults(...). You can combine
// test results with results = results + testFn(..); and then collect_and_report_TestResults on the aggregate
// TestResults value.
// _Step_9 - if T2 is a single value then make_tuple<T2>(T2) and call longer version
// auto testFunction = [](int id){return id==0?"":"";};
// auto compareFunction = [](const string a, const string b){return a==b;};
// template<typename TResult, typename FnToTest, typename... TInputParams>
// _Step_10 -
// test_fn(string, _FnToTest, vector<tuple<string, _T1, _CompareFn, <tuple<_T2...>>>)
// Default to (string, _FnToTest, vector<tuple<"", _T1, [](a,b){return a==b;}, make_tuple())
// Also allow make_tuple(T2) if the last param is not a tuple.
TestResults::TestResults() : errors_(0), failed_(0), passed_(0), skipped_(0), total_(0) {}
TestResults::TestResults(const TestResults& other)
: error_messages_(other.error_messages_),
errors_(other.errors_),
failed_(other.failed_),
failure_messages_(other.failure_messages_),
passed_(other.passed_),
skip_messages_(other.skip_messages_),
skipped_(other.skipped_),
total_(other.total_) {}
TestResults::TestResults(uint32_t errors,
uint32_t failed,
uint32_t passed,
uint32_t skipped,
uint32_t total,
vector<string> error_messages,
vector<string> failure_messages,
vector<string> skip_messages)
: error_messages_(error_messages),
errors_(errors),
failed_(failed),
failure_messages_(failure_messages),
passed_(passed),
skip_messages_(skip_messages),
skipped_(skipped),
total_(total) {}
TestResults& TestResults::error() {
errors_++;
return *this;
}
TestResults& TestResults::error(string message) {
errors_++;
error_messages_.push_back(message);
return *this;
}
TestResults& TestResults::fail() {
total_++;
failed_++;
return *this;
}
TestResults& TestResults::fail(const string& message) {
total_++;
failed_++;
failure_messages_.push_back(message);
return *this;
}
vector<string> TestResults::failure_messages() {
return failure_messages_;
}
TestResults& TestResults::pass() {
total_++;
passed_++;
return *this;
}
TestResults& TestResults::skip() {
total_++;
skipped_++;
return *this;
}
TestResults& TestResults::skip(const string& message) {
total_++;
skipped_++;
skip_messages_.push_back(message);
return *this;
}
vector<string> TestResults::skip_messages() {
return skip_messages_;
}
vector<string> TestResults::error_messages() {
return error_messages_;
}
uint32_t TestResults::errors() {
return errors_;
}
uint32_t TestResults::failed() {
return failed_;
}
uint32_t TestResults::passed() {
return passed_;
}
uint32_t TestResults::skipped() {
return skipped_;
}
uint32_t TestResults::total() {
return total_;
}
TestResults TestResults::operator+(const TestResults& other) const {
vector<string> error_messages;
error_messages.insert(error_messages.end(), error_messages_.begin(), error_messages_.end());
error_messages.insert(error_messages.end(), other.error_messages_.begin(), other.error_messages_.end());
vector<string> failure_messages;
failure_messages.insert(failure_messages.end(), failure_messages_.begin(), failure_messages_.end());
failure_messages.insert(failure_messages.end(), other.failure_messages_.begin(), other.failure_messages_.end());
vector<string> skip_messages;
skip_messages.insert(skip_messages.end(), skip_messages_.begin(), skip_messages_.end());
skip_messages.insert(skip_messages.end(), other.skip_messages_.begin(), other.skip_messages_.end());
return TestResults(errors_ + other.errors_,
failed_ + other.failed_,
passed_ + other.passed_,
skipped_ + other.skipped_,
total_ + other.total_,
error_messages,
failure_messages,
skip_messages);
}
TestResults& TestResults::operator+=(const TestResults& other) {
error_messages_.insert(error_messages_.end(), other.error_messages_.begin(), other.error_messages_.end());
errors_ += other.errors_;
failed_ += other.failed_;
failure_messages_.insert(failure_messages_.end(), other.failure_messages_.begin(), other.failure_messages_.end());
passed_ += other.passed_;
skip_messages_.insert(skip_messages_.end(), other.skip_messages_.begin(), other.skip_messages_.end());
skipped_ += other.skipped_;
total_ += other.total_;
return *this;
}
void PrintResults(std::ostream& os, TestResults results) {
auto skip_messages = results.skip_messages();
if (skip_messages.size() > 0) {
os << "Skipped:" << endl;
for_each(skip_messages.begin(), skip_messages.end(), [&os](const string& message) {
os << "🚧Skipped: " << message << endl;
});
}
auto failure_messages = results.failure_messages();
if (failure_messages.size() > 0) {
os << "Failures:" << endl;
for_each(failure_messages.begin(), failure_messages.end(), [&os](const string& message) {
os << "❌FAILED: " << message << endl;
});
}
auto error_messages = results.error_messages();
if (error_messages.size() > 0) {
os << "Errors:" << endl;
for_each(error_messages.begin(), error_messages.end(), [&os](const string& message) {
os << "🔥ERROR: " << message << endl;
});
}
os << "Total tests: " << results.total() << endl;
os << "Passed: " << results.passed() << "" << endl;
os << "Failed: " << results.failed() << "" << endl;
os << "Skipped: " << results.skipped() << " 🚧" << endl;
os << "Errors: " << results.errors() << " 🔥" << endl;
}
MaybeTestConfigureFunction DefaultTestConfigureFunction() {
return std::nullopt;
}
} // End namespace Test

553
sbf-cpp/test.h Normal file
View File

@@ -0,0 +1,553 @@
#ifndef TEST_H__
#define TEST_H__
/***************************************************************************************
* @file test.h
*
* @brief Defines structs and functions for implementing TinyTest.
* @copyright
* Copyright 2023 Tom Hicks
* Licensed under the MIT license see the LICENSE file for details.
***************************************************************************************/
#include <cstdint>
#include <functional>
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
// Test lifecycle
// suite_setup_function(); - This is called to allocate any suite level resources. This is called once when the suite
// begins. These functions may be called in parallel but execution will not proceed past this block until they have all
// finished.
// test_setup_function(); - This is called once for every test in tests. You may use it to allocate resources or setup
// mocks, stubs, and spies. function_to_test(...); - This is called once for every test to execute the test. Only one
// of these test functions will actually be run for each test in tests. They should return true if the test passed,
// return false if the test failed or there was an error, and be nullptr if they should be skipped. The executed
// function will be called with expected_output and the result of function_to_test(...). They can be used to test
// functions with side effects, especially void functions. test_compare_function; - This is the highest priority
// compare function. If it is not nullptr then it will be called. suite_compare_function; - This is the second highest
// priority compare function. If test_compare_function is nullptr and this is not nullptr then it will be called.
// [](TResult expected, TResult actual) { return expected, actual; } - This is the lowest priority compare function.
// If all other compare functions are nullptr then this will be called to evaluate the test. test_teardown_function();
// - This is called once for every test in tests. You must free/release any resources allocated by
// test_setup_function.
// This ends the parallel test functions section all tests will have completed before execution proceeds.
// Collect reports - Ths step is not visible to the user at this point, but data returned by all of the test functions
// is collected here. This is where you will eventually be able to format/log data for reports.
// suite_teardown_function(); - This is called after all test calls have completed, all test_teardown_function calls
// have completed, and all test reports/logs have been written. You should free any resources allocated in
// suite_setup_function.
// Tuple printer from: https://stackoverflow.com/questions/6245735/pretty-print-stdtuple/31116392#58417285
template <typename TChar, typename TTraits, typename... TArgs>
auto& operator<<(std::basic_ostream<TChar, TTraits>& os, std::tuple<TArgs...> const& t) {
std::apply([&os](auto&&... args) { ((os << args << " "), ...); }, t);
return os;
}
template <typename TChar, typename TTraits, typename TItem>
auto& operator<<(std::basic_ostream<TChar, TTraits>& os, std::vector<TItem> v) {
os << "[ ";
for (auto it = v.begin(); it != v.end(); it++) {
if (it != v.begin()) {
os << ", ";
}
os << *it;
}
os << " ]";
return os;
}
template <typename TChar, typename TTraits, typename TItem>
auto& compare(std::basic_ostream<TChar, TTraits>& error_message,
std::vector<TItem> expected,
std::vector<TItem> actual) {
if (expected.size() != actual.size()) {
error_message << "size mismatch expected: " << expected.size() << ", actual: " << actual.size();
return error_message;
}
for (size_t index = 0; index < expected.size(); index++) {
if (expected[index] != actual[index]) {
error_message << "vectors differ at index " << index << ", \"" << expected[index] << "\" != \"" << actual[index]
<< "\", expected: \"" << expected << "\", actual: \"" << actual << "\"";
return error_message;
}
}
return error_message;
}
namespace Test {
using std::string;
using std::tuple;
using std::vector;
/// @brief
class TestResults {
public:
/// @brief Creates an empty TestResults instance representing no tests run.
TestResults();
/// @brief Creates a new TestResults instance that is a copy of other.
/// @param other
TestResults(const TestResults& other);
/// @brief Creates a new TestResults instance with specific counts.
/// @param errors The number of errors while running the tests.
/// @param failed The number of failed tests.
/// @param passed The number of passed tests.
/// @param skipped The number of skipped tests.
/// @param total The total number of tests run. This should equal the sum of failed, passed, and skipped tests.
/// @param error_messages The list of error messages.
/// @param failure_messages The list of failure messages.
/// @param skip_messages The list of skip messages.
TestResults(uint32_t errors,
uint32_t failed,
uint32_t passed,
uint32_t skipped,
uint32_t total,
std::vector<std::string> error_messages,
std::vector<std::string> failure_messages,
std::vector<std::string> skip_messages);
/// @brief Adds an error. This increments errors.
/// @return A reference to this instance. Used for chaining.
TestResults& error();
/// @brief Adds an error with a message. This increments errors as well as saving the error message.
/// @param message The error message.
/// @return A reference to this instance. Used for chaining.
TestResults& error(std::string message);
/// @brief Adds a failed test. This increments total and failed.
/// @return A reference to this instance. Used for chaining.
TestResults& fail();
/// @brief Adds a failed test with a message. This increments total and failed as well as saving the failure message.
/// @param message The reason the test failed.
/// @return A reference to this instance. Used for chaining.
TestResults& fail(const std::string& message);
/// @brief Adds a passed test. This increments total and passed.
/// @return A reference to this instance. Used for chaining.
TestResults& pass();
/// @brief Adds a skipped test. This increments total and skipped.
/// @return A reference to this instance. Used for chaining.
TestResults& skip();
/// @brief Adds a skipped test with a message. This increments total and skipped as well as saving the skip message.
/// @param message The reason the test was skipped.
/// @return A reference to this instance. Used for chaining.
TestResults& skip(const std::string& message);
/// @brief Getter for the list of error messages.
/// @return
vector<string> error_messages();
/// @brief Getter for the count of errors.
/// @return
uint32_t errors();
/// @brief Getter for the count of failed tests.
/// @return The count of failed tests.
uint32_t failed();
/// @brief Getter for the list of failure messages.
/// @return The list of failure messages.
vector<string> failure_messages();
/// @brief Getter for the count of passed tests.
/// @return The count of passed tests.
uint32_t passed();
/// @brief Getter for the count of skipped tests.
/// @return The count of skipped tests.
uint32_t skipped();
/// @brief Getter for the list of skip messages.
/// @return The list of skip messages.
vector<string> skip_messages();
/// @brief Getter for the count of total tests.
/// @return The count of total tests run.
uint32_t total();
/// @brief Returns the combination of this and another TestResults instance.
/// @param other The other TestResults instance to add to this one.
/// @return The combination of the two TestResults instances.
TestResults operator+(const TestResults& other) const;
/// @brief Adds another TestResults to this one and returns a reference to this instance.
/// @param other The other TestResults instance to add to this one.
/// @return A reference to this instance.
TestResults& operator+=(const TestResults& other);
private:
std::vector<std::string> error_messages_;
uint32_t errors_;
uint32_t failed_;
std::vector<std::string> failure_messages_;
uint32_t passed_;
std::vector<std::string> skip_messages_;
uint32_t skipped_;
uint32_t total_;
};
/// @brief
/// @tparam TResult
template <typename TResult>
using TestCompareFunction = std::function<bool(const TResult& expected, const TResult& actual)>;
/// @brief
/// @tparam TResult
template <typename TResult>
using MaybeTestCompareFunction = std::optional<TestCompareFunction<TResult>>;
template <typename TResult>
MaybeTestCompareFunction<TResult> DefaultTestCompareFunction() {
return std::nullopt;
}
using TestConfigureFunction = std::function<void()>;
using MaybeTestConfigureFunction = std::optional<TestConfigureFunction>;
MaybeTestConfigureFunction DefaultTestConfigureFunction();
// TODO: For some reason all hell breaks loose if test_name or expected output are const&. Figure out why.
/// @brief
/// @tparam TResult
/// @tparam ...TInputParams
template <typename TResult, typename... TInputParams>
using TestTuple =
std::tuple<std::string /* test_name */,
TResult /* expected_output */,
std::tuple<TInputParams...> /* input_params - The input parameters for this test. These will be used when
calling std::apply with function_to_test to execute the test. */
,
MaybeTestCompareFunction<TResult> /* test_compare_function - If this is not nullprt then this function
will be called instead of suite_compare_function to determine if the
test passes. Use this to check for side effects of the test. Return
true if the test passes and false otherwise. */
,
MaybeTestConfigureFunction /* test_setup_function - If this is not nullptr this function is called before
each test to setup the environment. It is called with std::apply and
input_params so you can use them to mock records with specific IDs or
calculate an expected result. */
,
MaybeTestConfigureFunction /* test_teardown_function If this is not nullptr this function is called after
each test to cleanup any allocated/shared resources. */
,
bool /* is_enabled If this is false the test, setup, and teardown functions are not run. */>;
/// @brief
/// @tparam TResult
/// @tparam TFunctionToTest
/// @tparam ...TInputParams
template <typename TResult, typename... TInputParams>
using TestSuite = std::tuple<std::string,
std::function<TResult(TInputParams...)>,
std::vector<TestTuple<TResult, TInputParams...>>,
MaybeTestCompareFunction<TResult>,
MaybeTestConfigureFunction,
MaybeTestConfigureFunction,
bool>;
// This function is called to execute a test suite. You provide it with some configuration info, optional utility
// callback functions, and test data (input parameters for each call to function_to_test and the expected result). It
// returns a TestResults that should be treated as an opaque data type. Not all parameters are named in code, but they
// are named and explained in the comments and will be described by those names below.
// string suite_name - This is the name of this test suite. It is used for reporting messages.
// TFunctionToTest function_to_test - This is the function to test. This may be replaced if necessary by
// std::function. It may not currently support class methods, but that is planned. vector<tuple<...>> tests - This is
// the test run data. Each tuple in the vector is a single test run. It's members are explained below.
// string test_name - This is the name of this test. It is used for reporting messages.
// TResult expected_output - This is the expected result of executing this test.
// bool(*)(const TResult expected, const TResult actual) test_compare_function - This is optional. If unset or set
// to nullptr it is skipped. If set to a function it is called to evaluate the test results. It takes the expected
// and actual results as parameters and should return true if the test passed and false otherwise. This may be
// changed to return a TestResults at some point. void(*)(TInputParams...) test_setup_function - This is optional.
// If unset or set to nullptr it is skipped. If set to a function it is called before each test to setup the
// environment for the test. You may use it to allocate resources and setup mocks, stubs, and spies.
// void(*)(TInputParams...) test_teardown_function - This is optiona. If unset or set to nullptr it is skipped. If
// set to a function it is called after each test to cleanup the environment after the test. You should free
// resources allocated by test_setup_function. bool is_enabled - This is optional. If unset or set to true the test
// is run. If set to false this test is skipped. If skipped it will be reported as a skipped/disabled test.
// bool(*)(const TResult expected, const TResult actual) suite_compare_function - This is optional. If unset or set to
// nullptr it is skipped. If set to a function and test_compare_function is not called for a test run then this
// function is called to evaluate the test results. It takes the expected and actual results as parameters and should
// return true if the test passed and false otherwise. This may be changed to return a TestResults at some point.
// void(*)() suite_setup_function - This is optional. If unset or set to nullptr it is skipped. If set to a function
// it is called before starting this test suite to setup the environment. You may use it to allocate resources and
// setup mocks, stubs, and spies. void(*)() suite_teardown_function - This is optional. If unset or set to nullptr it
// is skipped. If set to a function it is called after all tests in this suite have finished and all reporting has
// finished. You should free resources allocated by suite_setup_function.
// This method should be called like so. This is the minimal call and omits all of the optional params. This is the most
// common usage. You should put one tuple of inputs and expected output for each test case.
// results = collect_and_report_test_resultstest_fn(
// "Test: function_under_test",
// function_under_test,
// vector({
// make_tuple(
// "ShouldReturnAppleForGroupId_1_and_ItemId_2",
// string("Apple"),
// make_tuple(1,2),
// ),
// }),
// );
// The suites can be run from one file as such. From a file called ThingDoer_test.cpp to test the class/methods
// ThingDoer declared in ThingDoer.cpp. This isn't mandatory but is a best practice. You can use function_to_test
// without calling collect_and_report_test_results() and also could call it from a normal int main(int argc, char**
// argv) or other function.
// TestResults test_main_ThingDoer(int argc, char* argv[]) {
// TestResults results;
// results = collect_and_report_test_results(results, function_to_test("do_thing1", ...), argc, argv);
// results = collect_and_report_test_results(results, function_to_test("do_thing2", ...), argc, argv);
// return results;
// }
// Then some test harness either generated or explicit can call test_main_ThingDoer(...) and optionally reported there.
// Reporting granularity is controlled by how frequently you call collect_and_report_test_results(...). You can combine
// test results with results = results + function_to_test(..); and then collect_and_report_test_results on the aggregate
// TestResults value.
/// @brief
/// @tparam TResult The result type of the test.
/// @tparam TInputParams... The types of parameters sent to the test function.
/// @param suite_label The label for this test suite. For example a class name such as "MortgageCalculator".
/// @param function_to_test The function to be tested. It will be called with std::apply and a
/// std::tuple<TInputParams...> made from each item in tests.
/// @param tests A std::vector of test runs.
/// @param suite_compare_function A function used to compare the expected and actual test results. This can be
/// overridden per test by setting test_compare_function.
/// @param after_all This is called before each suite is started to setup the environment. This is where you should
/// build mocks, setup spies, and test fixtures.
/// @param before_all This is called after each suite has completed to cleanup anything allocated in suite_before_each.
/// @param is_enabled If false none of these tests are run and they are all reported as skipped.
template <typename TResult, typename... TInputParams>
TestResults execute_suite(std::string suite_label,
std::function<TResult(TInputParams...)> function_to_test,
vector<TestTuple<TResult, TInputParams...>> tests,
MaybeTestCompareFunction<TResult> suite_compare = std::nullopt,
MaybeTestConfigureFunction before_all = std::nullopt,
MaybeTestConfigureFunction after_all = std::nullopt,
bool is_enabled = true) {
TestResults results;
std::cout << "🚀Beginning Suite: " << suite_label << std::endl;
// Step 1: Suite Setup
if (before_all.has_value()) {
(*before_all)();
}
// Step 2: Execute Tests
for_each(tests.begin(),
tests.end(),
[&suite_label, &function_to_test, &results, &suite_compare](TestTuple<TResult, TInputParams...> test_data) {
// Step 2a: Extract our variables from the TestTuple.
const std::string& test_name = std::get<0>(test_data);
const std::string qualified_test_name = suite_label + "::" + test_name;
const TResult& expected_output = std::get<1>(test_data);
std::tuple<TInputParams...> input_params = std::get<2>(test_data);
MaybeTestCompareFunction<TResult> maybe_compare_function = std::get<3>(test_data);
TestCompareFunction<TResult> compare_function =
maybe_compare_function.has_value() ? *maybe_compare_function
: suite_compare.has_value() ? *suite_compare
: [](const TResult& l, const TResult& r) { return l == r; };
MaybeTestConfigureFunction before_each = std::get<4>(test_data);
MaybeTestConfigureFunction after_each = std::get<5>(test_data);
bool is_enabled = std::get<6>(test_data);
if (!is_enabled) {
std::cout << " 🚧Skipping Test: " << test_name << std::endl;
results.skip(qualified_test_name);
return;
}
// Step 2b: Test Setup
std::cout << " Beginning Test: " << test_name << std::endl;
if (before_each.has_value()) {
(*before_each)();
}
TResult actual;
try {
// Step 2c: Execute the test method.
actual = std::apply(function_to_test, input_params);
} catch (const std::exception& ex) {
std::ostringstream os;
os << "Caught exception \"" << ex.what() << "\"";
results.error(qualified_test_name + " " + os.str());
std::cout << " 🔥ERROR: " << os.str() << std::endl;
} catch (const std::string& message) {
std::ostringstream os;
os << "Caught string \"" << message << "\"";
results.error(qualified_test_name + " " + os.str());
std::cout << " 🔥ERROR: " << os.str() << std::endl;
} catch (...) {
string message = "Caught something that is neither an std::exception nor an std::string.";
results.error(qualified_test_name + " " + message);
std::cout << " 🔥ERROR: " << message << std::endl;
}
// Step 2d: Pass or fail.
if (compare_function(expected_output, actual)) {
results.pass();
std::cout << " ✅PASSED" << std::endl;
} else {
std::ostringstream os;
os << "expected: \"" << expected_output << "\", actual: \"" << actual << "\"";
results.fail(qualified_test_name + " " + os.str());
std::cout << " ❌FAILED: " << os.str() << std::endl;
}
// Step 2e: Test Teardown
if (after_each.has_value()) {
(*after_each)();
}
std::cout << " Ending Test: " << test_name << std::endl;
});
// Step 3: Suite Teardown
if (after_all.has_value()) {
(*after_all)();
}
std::cout << "Ending Suite: " << suite_label << std::endl;
return results;
}
/// @brief
/// @tparam TResult The result type of the test.
/// @tparam TInputParams... The types of parameters sent to the test function.
/// @param suite_label The label for this test suite. For example a class name such as "MortgageCalculator".
/// @param function_to_test The function to be tested. It will be called with std::apply and a
/// std::tuple<TInputParams...> made from each item in tests.
/// @param tests A std::vector of test runs.
/// @param suite_compare A function used to compare the expected and actual test results. This can be
/// overridden per test by setting test_compare.
/// @param after_all This is called before each suite is started to setup the environment. This is where you should
/// build mocks, setup spies, and test fixtures.
/// @param before_all This is called after each suite has completed to cleanup anything allocated in suite_before_each.
/// @param is_enabled If false the test is reported as skipped. If true the test is run as normal.
template <typename TResult, typename... TInputParams>
TestResults execute_suite(std::string suite_label,
std::function<TResult(TInputParams...)> function_to_test,
std::initializer_list<TestTuple<TResult, TInputParams...>> tests,
MaybeTestCompareFunction<TResult> suite_compare = std::nullopt,
MaybeTestConfigureFunction before_all = std::nullopt,
MaybeTestConfigureFunction after_all = std::nullopt,
bool is_enabled = true) {
std::vector test_data = std::vector(tests);
return execute_suite(suite_label, function_to_test, tests, suite_compare, before_all, after_all, is_enabled);
}
/// @brief
/// @tparam TResult The result type of the test.
/// @tparam ...TInputParams The types of parameters sent to the test function.
/// @param test_name The label for this test. For example "should calculate the interest".
/// @param expected The expected output of calling the test function with these input parameters.
/// @param input_params The input parameters to use when calling the test function.
/// @param test_compare_fn An optional function that can be used to compare the expected and actual return values. This
/// is good for when you only care about certain fields being equal.
/// @param before_each This is called to setup the environment before running the test. This is where you should build
/// mocks, setup spies, and set any other values you need before calling the test function.
/// @param after_each This is called after each test run to cleanup anything allocated in before_each.
/// @param is_enabled If false this test run is not executed and considered skipped for reporting purposes.
/// @return A TestTuple suitable for use as a test run when calling test_fn.
template <typename TResult, typename... TInputParams>
TestTuple<TResult, TInputParams...> make_test(const string& test_name,
const TResult& expected,
tuple<TInputParams...> input_params,
MaybeTestCompareFunction<TResult> test_compare_fn = std::nullopt,
MaybeTestConfigureFunction before_each = std::nullopt,
MaybeTestConfigureFunction after_each = std::nullopt,
bool is_enabled = true) {
return make_tuple(test_name, expected, input_params, test_compare_fn, before_each, after_each, is_enabled);
}
/// @brief
/// @tparam TResult
/// @tparam TFunctionToTest
/// @tparam ...TInputParams
/// @param suite_name
/// @param function_to_test
/// @param test_data
/// @param compare
/// @param before_each
/// @param after_each
/// @param is_enabled
/// @return
template <typename TResult, typename TFunctionToTest, typename... TInputParams>
TestSuite<TResult, TInputParams...> make_test_suite(const string& suite_name,
TFunctionToTest function_to_test,
vector<TestTuple<TResult, TInputParams...>> test_data,
MaybeTestCompareFunction<TResult> compare = std::nullopt,
MaybeTestConfigureFunction before_each = std::nullopt,
MaybeTestConfigureFunction after_each = std::nullopt,
bool is_enabled = true) {
return make_tuple(suite_name, function_to_test, test_data, compare, before_each, after_each, is_enabled);
}
template <typename TResult, typename TFunctionToTest, typename... TInputParams>
TestSuite<TResult, TInputParams...> make_test_suite(
const string& suite_name,
TFunctionToTest function_to_test,
std::initializer_list<TestTuple<TResult, TInputParams...>> test_data,
MaybeTestCompareFunction<TResult> compare = std::nullopt,
MaybeTestConfigureFunction before_each = std::nullopt,
MaybeTestConfigureFunction after_each = std::nullopt,
bool is_enabled = true) {
return make_tuple(suite_name, function_to_test, test_data, compare, before_each, after_each, is_enabled);
}
/// @brief
/// @tparam TResult The result type of the test.
/// @tparam TInputParams... The types of parameters sent to the test function.
/// @param test_suite A tuple representing the test suite configuration.
template <typename TResult, typename... TInputParams>
TestResults execute_suite(const TestSuite<TResult, TInputParams...>& test_suite) {
return execute_suite<TResult, TInputParams...>(
std::get<0>(test_suite), std::get<1>(test_suite), std::get<2>(test_suite)
// TODO: make this work for the optional parts of the tuple too.
);
}
/// @brief
/// @tparam ...TInputParams
/// @param first
/// @param second
/// @return
template <typename... TInputParams>
MaybeTestConfigureFunction coalesce(MaybeTestConfigureFunction first, MaybeTestConfigureFunction second) {
if (first.has_value()) {
if (second.has_value()) {
// This is the only place we actually need to combine them.
return [&first, &second](TInputParams... input_params) {
*first(input_params...);
*second(input_params...);
};
} else {
return first;
}
} else {
return second;
}
}
/// @brief Writes a friendly version of results to the provided stream.
/// @param os The stream to write to.
/// @param results The TestResults to write.
void PrintResults(std::ostream& os, TestResults results);
} // End namespace Test
// TODO: Add TShared(*)(string /*test_name*/, UUID /*test_run_id*/) allocate_shared_data to the test tuple to make some
// shared data that can be used in a thread safe way by setup, teardown, and evaluate steps of the test.
// TODO: Add TShared to be returned by the setup functions, and consumed by the evaluate and teardown functions.
// Suite setup/teardown functions should allocate/free.
// Test setup/teardown functions should consume the data allocated by suite setup.
// Test setup functions may allocate additional resources. If they do then the allocated resources they should be
// freed by test teardown function. Suite and/or test compare functions may consume this shared data, but it will not
// be shared with the execution of function_to_test.
#endif // End !defined TEST_H__