diff --git a/CharacterGenerator/CharacterGenerator.xcodeproj/project.pbxproj b/CharacterGenerator/CharacterGenerator.xcodeproj/project.pbxproj index 17ce60d..dd02186 100644 --- a/CharacterGenerator/CharacterGenerator.xcodeproj/project.pbxproj +++ b/CharacterGenerator/CharacterGenerator.xcodeproj/project.pbxproj @@ -298,16 +298,16 @@ TargetAttributes = { B621A3A21F0C1C7400E55236 = { CreatedOnToolsVersion = 9.0; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; }; B621A3B81F0C1C7500E55236 = { CreatedOnToolsVersion = 9.0; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; TestTargetID = B621A3A21F0C1C7400E55236; }; B6C87F3F1F83CD3E007CB209 = { CreatedOnToolsVersion = 9.0; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; TestTargetID = B621A3A21F0C1C7400E55236; }; @@ -558,7 +558,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.flatearthstudio.CharacterGenerator; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -572,7 +572,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.flatearthstudio.CharacterGenerator; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -587,7 +587,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.flatearthstudio.CharacterGeneratorTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CharacterGenerator.app/CharacterGenerator"; }; @@ -603,7 +603,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.flatearthstudio.CharacterGeneratorTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CharacterGenerator.app/CharacterGenerator"; }; @@ -620,7 +620,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.flatearthstudio.CharacterGeneratorUITests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = CharacterGenerator; }; @@ -637,7 +637,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.flatearthstudio.CharacterGeneratorUITests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = CharacterGenerator; }; diff --git a/CharacterGenerator/CharacterGenerator/Configuration/Currencies.json b/CharacterGenerator/CharacterGenerator/Configuration/Currencies.json index 9ce0210..fc5e296 100644 --- a/CharacterGenerator/CharacterGenerator/Configuration/Currencies.json +++ b/CharacterGenerator/CharacterGenerator/Configuration/Currencies.json @@ -20,7 +20,7 @@ }, { "symbol": "gp", - "default": true, + "is default": true, "coefficient": 1.0, "name": "gold piece", "plural": "gold pieces" diff --git a/RolePlayingCore/RolePlayingCore.xcodeproj/project.pbxproj b/RolePlayingCore/RolePlayingCore.xcodeproj/project.pbxproj index 60b4346..df9a511 100644 --- a/RolePlayingCore/RolePlayingCore.xcodeproj/project.pbxproj +++ b/RolePlayingCore/RolePlayingCore.xcodeproj/project.pbxproj @@ -380,13 +380,13 @@ TargetAttributes = { B62055ED1E19DD23002494AB = { CreatedOnToolsVersion = 8.2.1; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; B62055F61E19DD23002494AB = { CreatedOnToolsVersion = 8.2.1; DevelopmentTeam = J69E69SP27; - LastSwiftMigration = ""; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; }; }; @@ -649,7 +649,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -669,7 +669,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.flatearthstudio.RolePlayingCore; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -682,7 +682,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.flatearthstudio.RolePlayingCoreTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -695,7 +695,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.flatearthstudio.RolePlayingCoreTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/RolePlayingCore/RolePlayingCore/Currency/Currencies.swift b/RolePlayingCore/RolePlayingCore/Currency/Currencies.swift index 9df6288..85cc190 100644 --- a/RolePlayingCore/RolePlayingCore/Currency/Currencies.swift +++ b/RolePlayingCore/RolePlayingCore/Currency/Currencies.swift @@ -8,14 +8,30 @@ public struct Currencies { - /// An array of all currently loaded currencies. - public static var allCurrencies: [UnitCurrency] = [] + /// A map of all currently loaded currencies. + internal static var allCurrencies: [String: UnitCurrency] = [:] - /// Looks up the currency instance that matches the specified symbol. - /// Returns nil if the symbol isn't found. + /// Returns the unit currency corresponding to this symbol. Returns nil if no symbol matches. public static func find(_ symbol: String) -> UnitCurrency? { - return allCurrencies.first(where: { $0.symbol == symbol }) + return Currencies.allCurrencies[symbol] } + + public static func add(_ currency: UnitCurrency) { + allCurrencies[currency.symbol] = currency + } + + public static func setDefault(_ newBaseUnit: UnitCurrency) { + // Remove the old base unit from all currencies. + let oldSymbol = UnitCurrency.baseUnitCurrency.symbol + guard oldSymbol != newBaseUnit.symbol else { + return + } + + allCurrencies[oldSymbol] = nil + + UnitCurrency.baseUnitCurrency = newBaseUnit + } + } extension Currencies: Codable { @@ -27,14 +43,34 @@ extension Currencies: Codable { let coefficient: Double let name: String let plural: String - let `default`: Bool? // TODO: this is fishy + let isDefault: Bool + + private enum CodingKeys: String, CodingKey { + case symbol + case coefficient + case name + case plural + case isDefault = "is default" + } + // For writing init(_ unitCurrency: UnitCurrency) { self.symbol = unitCurrency.symbol self.coefficient = (unitCurrency.converter as! UnitConverterLinear).coefficient self.name = unitCurrency.name self.plural = unitCurrency.plural - self.default = unitCurrency == .baseUnit() + self.isDefault = unitCurrency == .baseUnit() + } + + // For reading + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + symbol = try container.decode(String.self, forKey: .symbol) + coefficient = try container.decode(Double.self, forKey: .coefficient) + name = try container.decode(String.self, forKey: .name) + plural = try container.decode(String.self, forKey: .plural) + isDefault = try container.decodeIfPresent(Bool.self, forKey: .isDefault) ?? false } } @@ -48,14 +84,13 @@ extension Currencies: Codable { let currencies = try container.decode([Currency].self, forKey: .currencies) for currency in currencies { - if Currencies.find(currency.symbol) == nil { - let converter = UnitConverterLinear(coefficient: currency.coefficient) - let unitCurrency = UnitCurrency(symbol: currency.symbol, converter: converter, name: currency.name, plural: currency.plural) - Currencies.allCurrencies.append(unitCurrency) - - if currency.default != nil, currency.default! { - UnitCurrency.default = unitCurrency - } + let converter = UnitConverterLinear(coefficient: currency.coefficient) + let unitCurrency = UnitCurrency(symbol: currency.symbol, converter: converter, name: currency.name, plural: currency.plural) + + Currencies.add(unitCurrency) + + if currency.isDefault { + Currencies.setDefault(unitCurrency) } } } @@ -64,7 +99,7 @@ extension Currencies: Codable { var container = encoder.container(keyedBy: CodingKeys.self) var currencies = [Currency]() - for unitCurrency in Currencies.allCurrencies { + for unitCurrency in Currencies.allCurrencies.values { let currency = Currency(unitCurrency) currencies.append(currency) } diff --git a/RolePlayingCore/RolePlayingCore/Currency/Money.swift b/RolePlayingCore/RolePlayingCore/Currency/Money.swift index fbb654c..860d457 100644 --- a/RolePlayingCore/RolePlayingCore/Currency/Money.swift +++ b/RolePlayingCore/RolePlayingCore/Currency/Money.swift @@ -18,7 +18,7 @@ public extension String { var value: Double? var unit: UnitCurrency = .baseUnit() - for currency in Currencies.allCurrencies { + for currency in Currencies.allCurrencies.values { if let range = self.range(of: currency.symbol), range.upperBound == self.endIndex { value = Double(self[..` to hold values of currency. -public class UnitCurrency : Dimension { +public final class UnitCurrency : Dimension { /// The singular unit name used when the unitStyle is long. public internal(set) var name: String! @@ -19,13 +19,11 @@ public class UnitCurrency : Dimension { /// The plural unit name used when the unitStyle is long. public internal(set) var plural: String! - /// The default unit currency. Set during load(). - public internal(set) static var `default`: UnitCurrency? + /// The default base unit is a currency called "credit". It may be replaced at runtime. + internal static var baseUnitCurrency = UnitCurrency(symbol: "c", converter: UnitConverterLinear(coefficient: 1.0), name: "credit", plural: "credits") - /// Returns the default unit currency. Will unwrap nil if load() hasn't been called, - /// or if none of the loaded currencies was marked default. public override class func baseUnit() -> UnitCurrency { - return UnitCurrency.default! + return baseUnitCurrency } public init(symbol: String, converter: UnitConverter, name: String, plural: String) { diff --git a/RolePlayingCore/RolePlayingCore/Dice/DiceParser.swift b/RolePlayingCore/RolePlayingCore/Dice/DiceParser.swift index c03680f..185b353 100644 --- a/RolePlayingCore/RolePlayingCore/Dice/DiceParser.swift +++ b/RolePlayingCore/RolePlayingCore/Dice/DiceParser.swift @@ -243,7 +243,7 @@ public extension String { /// - returns: Dice representing the parsed string. Returns `nil` if the string /// could not be interpreted; for example, if there are extraneous /// characters, or an unsupported dice such as d7 is specified. - public var parseDice: Dice? { + var parseDice: Dice? { var dice: Dice? = nil do { @@ -269,7 +269,7 @@ public extension KeyedDecodingContainer { /// See `String.parseDice` for supported string formats. /// /// - throws `DecodingError.dataCorrupted` if the dice is not present or could not be decoded. - public func decode(_ type: Dice.Protocol, forKey key: K) throws -> Dice { + func decode(_ type: Dice.Protocol, forKey key: K) throws -> Dice { let dice: Dice? if let number = try? self.decode(Int.self, forKey: key) { @@ -291,7 +291,7 @@ public extension KeyedDecodingContainer { /// See `String.parseDice` for supported string formats. /// /// - throws `DecodingError.dataCorrupted` if the dice could not be decoded. - public func decodeIfPresent(_ type: Dice.Protocol, forKey key: K) throws -> Dice? { + func decodeIfPresent(_ type: Dice.Protocol, forKey key: K) throws -> Dice? { let dice: Dice? if let number = try? self.decode(Int.self, forKey: key) { diff --git a/RolePlayingCore/RolePlayingCore/Player/Alignment.swift b/RolePlayingCore/RolePlayingCore/Player/Alignment.swift index 3e0b933..0ec8f01 100644 --- a/RolePlayingCore/RolePlayingCore/Player/Alignment.swift +++ b/RolePlayingCore/RolePlayingCore/Player/Alignment.swift @@ -217,9 +217,8 @@ extension Alignment: Codable { // The value must decode into either two doubles or two strings with the coding keys. let values = try decoder.container(keyedBy: CodingKeys.self) if let ethicsValue = try? values.decodeIfPresent(Double.self, forKey: .ethics), - let moralsValue = try? values.decodeIfPresent(Double.self, forKey: .morals), - ethicsValue != nil && moralsValue != nil { - self.init(ethics: ethicsValue!, morals: moralsValue!) + let moralsValue = try? values.decodeIfPresent(Double.self, forKey: .morals) { + self.init(ethics: ethicsValue, morals: moralsValue) } else { let ethics = try values.decode(Ethics.self, forKey: .ethics) let morals = try values.decode(Morals.self, forKey: .morals) diff --git a/RolePlayingCore/RolePlayingCore/Player/Player.swift b/RolePlayingCore/RolePlayingCore/Player/Player.swift index 9fe7312..5799137 100644 --- a/RolePlayingCore/RolePlayingCore/Player/Player.swift +++ b/RolePlayingCore/RolePlayingCore/Player/Player.swift @@ -11,7 +11,7 @@ import Foundation public extension AbilityScores { // Sets the ability scores to random values using 4d6-L - public mutating func roll() { + mutating func roll() { let dice = DroppingDice(.d6, times: 4, drop: .lowest) for ability in abilities { scores[ability] = dice.roll().result diff --git a/RolePlayingCore/RolePlayingCoreTests/CurrencyTests.swift b/RolePlayingCore/RolePlayingCoreTests/CurrencyTests.swift index e1ed243..6b305eb 100644 --- a/RolePlayingCore/RolePlayingCoreTests/CurrencyTests.swift +++ b/RolePlayingCore/RolePlayingCoreTests/CurrencyTests.swift @@ -8,7 +8,7 @@ import XCTest -import RolePlayingCore +@testable import RolePlayingCore class UnitCurrencyTests: XCTestCase { diff --git a/RolePlayingCore/RolePlayingCoreTests/TestCurrencies.json b/RolePlayingCore/RolePlayingCoreTests/TestCurrencies.json index 9a5486d..723f244 100644 --- a/RolePlayingCore/RolePlayingCoreTests/TestCurrencies.json +++ b/RolePlayingCore/RolePlayingCoreTests/TestCurrencies.json @@ -2,7 +2,7 @@ "currencies": [ { "symbol": "gp", - "default": true, + "is default": true, "coefficient": 1.0, "name": "gold piece", "plural": "gold pieces"