diff --git a/DiceKit.xcodeproj/project.pbxproj b/DiceKit.xcodeproj/project.pbxproj index bd46119..4245429 100644 --- a/DiceKit.xcodeproj/project.pbxproj +++ b/DiceKit.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 531084C31B5B0761008DD696 /* Die.Roll.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531084C11B5B0102008DD696 /* Die.Roll.swift */; }; 531E4F5F1B73F5E20073F01A /* EquatableTestUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531E4F5E1B73F5E20073F01A /* EquatableTestUtilities.swift */; }; 531E4F611B7400270073F01A /* Arbitrary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531E4F601B7400270073F01A /* Arbitrary.swift */; }; + 533B55F41BDDC0D100C08EDB /* OutcomeWithSuccessfulness_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 533B55F31BDDC0D100C08EDB /* OutcomeWithSuccessfulness_Tests.swift */; }; 533D0C771B6419F8003A7D32 /* FrequencyDistribution_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 533D0C761B6419F8003A7D32 /* FrequencyDistribution_Tests.swift */; }; 533D0C7B1B6423FA003A7D32 /* ProbabilityMass_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 533D0C7A1B6423FA003A7D32 /* ProbabilityMass_Tests.swift */; }; 533D0C7D1B643F6C003A7D32 /* Constant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 533D0C7C1B643F6C003A7D32 /* Constant.swift */; }; @@ -18,6 +19,9 @@ 533F88091B52B988003838C8 /* DiceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 533F87FE1B52B988003838C8 /* DiceKit.framework */; }; 533F880E1B52B988003838C8 /* Die_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 533F880D1B52B988003838C8 /* Die_Tests.swift */; }; 534900341B78172900FA2804 /* ArithmeticType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 534900331B78172900FA2804 /* ArithmeticType.swift */; }; + 537675F81BDDB81A00F6CE9C /* OutcomeWithSuccessfulness.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537675F71BDDB81A00F6CE9C /* OutcomeWithSuccessfulness.swift */; }; + 537675FA1BDDB88700F6CE9C /* Successfulness.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537675F91BDDB88700F6CE9C /* Successfulness.swift */; }; + 537675FC1BDDBAD400F6CE9C /* Successfulness_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537675FB1BDDBAD400F6CE9C /* Successfulness_Tests.swift */; }; 5379780B1B531B21005818EC /* Die.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5379780A1B531B21005818EC /* Die.swift */; }; 538743871B5316BA00EB15C8 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 530FEDD11B530CAB00960BFD /* Nimble.framework */; }; 538AD9651B75A66F001B5CB5 /* MockExpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538AD9641B75A66F001B5CB5 /* MockExpression.swift */; }; @@ -94,6 +98,7 @@ 531084C11B5B0102008DD696 /* Die.Roll.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Die.Roll.swift; sourceTree = ""; }; 531E4F5E1B73F5E20073F01A /* EquatableTestUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EquatableTestUtilities.swift; sourceTree = ""; }; 531E4F601B7400270073F01A /* Arbitrary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Arbitrary.swift; sourceTree = ""; }; + 533B55F31BDDC0D100C08EDB /* OutcomeWithSuccessfulness_Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutcomeWithSuccessfulness_Tests.swift; sourceTree = ""; }; 533D0C761B6419F8003A7D32 /* FrequencyDistribution_Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrequencyDistribution_Tests.swift; sourceTree = ""; }; 533D0C7A1B6423FA003A7D32 /* ProbabilityMass_Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProbabilityMass_Tests.swift; sourceTree = ""; }; 533D0C7C1B643F6C003A7D32 /* Constant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constant.swift; sourceTree = ""; }; @@ -104,6 +109,9 @@ 533F880D1B52B988003838C8 /* Die_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Die_Tests.swift; sourceTree = ""; }; 533F880F1B52B988003838C8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 534900331B78172900FA2804 /* ArithmeticType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArithmeticType.swift; sourceTree = ""; }; + 537675F71BDDB81A00F6CE9C /* OutcomeWithSuccessfulness.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutcomeWithSuccessfulness.swift; sourceTree = ""; }; + 537675F91BDDB88700F6CE9C /* Successfulness.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Successfulness.swift; sourceTree = ""; }; + 537675FB1BDDBAD400F6CE9C /* Successfulness_Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Successfulness_Tests.swift; sourceTree = ""; }; 5379780A1B531B21005818EC /* Die.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Die.swift; sourceTree = ""; }; 538AD9641B75A66F001B5CB5 /* MockExpression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockExpression.swift; sourceTree = ""; }; 538AD9681B7678B7001B5CB5 /* Die.Roll_Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Die.Roll_Tests.swift; sourceTree = ""; }; @@ -232,10 +240,12 @@ 531084BF1B5AF993008DD696 /* MultiplicationExpressionResult.swift */, 53ECD0E11B5B498A002D859F /* NegationExpression.swift */, 53ECD0E31B5B5185002D859F /* NegationExpressionResult.swift */, + 537675F71BDDB81A00F6CE9C /* OutcomeWithSuccessfulness.swift */, 53FA0EAB1B6579F300C8D3B5 /* ProbabilisticExpressionType.swift */, 539D568D1B5C7BDA00AC6A37 /* ProbabilityMass.swift */, 8B4C14011B7F8B47008AC355 /* SubtractionExpression.swift */, 8B4C14031B7FA8A2008AC355 /* SubtractionExpressionResult.swift */, + 537675F91BDDB88700F6CE9C /* Successfulness.swift */, ); path = DiceKit; sourceTree = ""; @@ -264,10 +274,12 @@ 533EB1ED1B5B293000B3F8A1 /* MultiplicationExpressionResult_Tests.swift */, 539D56831B5B6D1A00AC6A37 /* NegationExpression_Tests.swift */, 539D56851B5B6D2B00AC6A37 /* NegationExpressionResult_Tests.swift */, + 533B55F31BDDC0D100C08EDB /* OutcomeWithSuccessfulness_Tests.swift */, 53FA0EAD1B657A3600C8D3B5 /* ProbabilisticExpressionType_Tests.swift */, 533D0C7A1B6423FA003A7D32 /* ProbabilityMass_Tests.swift */, 8BFAA9F81B7ED8E800EF65AE /* SubtractionExpression_Tests.swift */, 8BFAA9FA1B7F708000EF65AE /* SubtractionExpressionResult_Tests.swift */, + 537675FB1BDDBAD400F6CE9C /* Successfulness_Tests.swift */, ); path = DiceKitTests; sourceTree = ""; @@ -430,6 +442,7 @@ 53D1EC7C1B6312E100433D2C /* FrequencyDistribution.swift in Sources */, 53FA0EAC1B6579F300C8D3B5 /* ProbabilisticExpressionType.swift in Sources */, 531084C01B5AF993008DD696 /* MultiplicationExpressionResult.swift in Sources */, + 537675F81BDDB81A00F6CE9C /* OutcomeWithSuccessfulness.swift in Sources */, 539D568C1B5C7B7700AC6A37 /* Dictionary+Functions.swift in Sources */, 6E9F18981B7FDF0D00AB8893 /* MinimizationExpressionResult.swift in Sources */, 53D1EC7E1B63133E00433D2C /* ApproximatelyEquatable.swift in Sources */, @@ -448,6 +461,7 @@ 53CFC30F1B5AD674009C6C8F /* MultiplicationExpression.swift in Sources */, 53ECD0E41B5B5185002D859F /* NegationExpressionResult.swift in Sources */, 533D0C7D1B643F6C003A7D32 /* Constant.swift in Sources */, + 537675FA1BDDB88700F6CE9C /* Successfulness.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -464,7 +478,9 @@ 53D05EEC1B546C73007CE7FC /* Int+Random_Tests.swift in Sources */, 6E9F18941B7FD16100AB8893 /* MinimizationExpression_Tests.swift in Sources */, 539D568A1B5B6D6A00AC6A37 /* AdditionExpressionResult_Tests.swift in Sources */, + 533B55F41BDDC0D100C08EDB /* OutcomeWithSuccessfulness_Tests.swift in Sources */, 533D0C771B6419F8003A7D32 /* FrequencyDistribution_Tests.swift in Sources */, + 537675FC1BDDBAD400F6CE9C /* Successfulness_Tests.swift in Sources */, 6E0F58C91B8D63690095087F /* MinimizationExpressionResult_Tests.swift in Sources */, 538AD96B1B768931001B5CB5 /* Constant_Tests.swift in Sources */, 533F880E1B52B988003838C8 /* Die_Tests.swift in Sources */, @@ -497,6 +513,8 @@ isa = XCBuildConfiguration; baseConfigurationReference = 5395EBC01C7C0A08009CC1EF /* Debug.xcconfig */; buildSettings = { + ENABLE_TESTABILITY = YES; + ONLY_ACTIVE_ARCH = YES; }; name = Debug; }; diff --git a/DiceKit.xcodeproj/xcshareddata/xcschemes/DiceKit.xcscheme b/DiceKit.xcodeproj/xcshareddata/xcschemes/DiceKit.xcscheme index 8b85f7d..7520cef 100644 --- a/DiceKit.xcodeproj/xcshareddata/xcschemes/DiceKit.xcscheme +++ b/DiceKit.xcodeproj/xcshareddata/xcschemes/DiceKit.xcscheme @@ -1,7 +1,7 @@ + version = "1.8"> diff --git a/DiceKit/FrequencyDistribution.swift b/DiceKit/FrequencyDistribution.swift index ccfef66..e5ac5b8 100644 --- a/DiceKit/FrequencyDistribution.swift +++ b/DiceKit/FrequencyDistribution.swift @@ -13,6 +13,9 @@ public protocol FrequencyDistributionOutcomeType: InvertibleMultiplicativeType, /// The number that will be used when determining how many times to perform another /// expression when multiplied by `self`. var multiplierEquivalent: Int { get } + + // Force Int for IntegerLiteralType + init(integerLiteral: Int) } @@ -49,7 +52,7 @@ public struct FrequencyDistribution: Equatable { + + public let outcome: Outcome + public let successfulness: Successfulness + + public init(_ outcome: Outcome, successfulness: Successfulness) { + self.outcome = outcome + self.successfulness = successfulness + } +} + +// MARK: - CustomStringConvertible + +extension OutcomeWithSuccessfulness: CustomStringConvertible { + public var description: String { + if successfulness == .additiveIdentity { + return "\(outcome)" + } else { + return "(\(outcome) with \(successfulness.rawDescription(surroundWithParentheses: false)))" + } + } +} + +// MARK: - CustomDebugStringConvertible + +extension OutcomeWithSuccessfulness: CustomDebugStringConvertible { + public var debugDescription: String { + return "OutcomeWithSuccessfulness(\(String(reflecting: outcome)), successfulness: \(String(reflecting: successfulness)))" + } +} + +// MARK: - Equatable + +public func == (lhs: OutcomeWithSuccessfulness, rhs: OutcomeWithSuccessfulness) -> Bool { + return lhs.outcome == rhs.outcome && lhs.successfulness == rhs.successfulness +} + +// MARK: - Comparable + +extension OutcomeWithSuccessfulness: Comparable { +} + +public func < (lhs: OutcomeWithSuccessfulness, rhs: OutcomeWithSuccessfulness) -> Bool { + if lhs.outcome == rhs.outcome { + return lhs.successfulness < rhs.successfulness + } else { + return lhs.outcome < rhs.outcome + } +} + +// MARK: - Hashable + +extension OutcomeWithSuccessfulness: Hashable { + + public var hashValue: Int { + return outcome.hashValue ^ successfulness.hashValue + } +} + +// MARK: - ArithmeticType + +extension OutcomeWithSuccessfulness: ArithmeticType { + + // TODO: Change to stored properties when allowed by Swift + public static var additiveIdentity: OutcomeWithSuccessfulness { + return OutcomeWithSuccessfulness(.additiveIdentity, successfulness: .additiveIdentity) + } + public static var multiplicativeIdentity: OutcomeWithSuccessfulness { + return OutcomeWithSuccessfulness(.multiplicativeIdentity, successfulness: .multiplicativeIdentity) + } + + // MARK: IntegerLiteralType + + public init(integerLiteral value: Int) { + self.init(Outcome(integerLiteral: value), successfulness: 0) + } +} + +public func + (lhs: OutcomeWithSuccessfulness, rhs: OutcomeWithSuccessfulness) -> OutcomeWithSuccessfulness { + let outcome = lhs.outcome + rhs.outcome + let successfulness = lhs.successfulness + rhs.successfulness + return OutcomeWithSuccessfulness(outcome, successfulness: successfulness) +} + +public func - (lhs: OutcomeWithSuccessfulness, rhs: OutcomeWithSuccessfulness) -> OutcomeWithSuccessfulness { + let outcome = lhs.outcome - rhs.outcome + let successfulness = lhs.successfulness - rhs.successfulness + return OutcomeWithSuccessfulness(outcome, successfulness: successfulness) +} + +public func * (lhs: OutcomeWithSuccessfulness, rhs: OutcomeWithSuccessfulness) -> OutcomeWithSuccessfulness { + let outcome = lhs.outcome * rhs.outcome + let successfulness = lhs.successfulness * rhs.successfulness + return OutcomeWithSuccessfulness(outcome, successfulness: successfulness) +} + +public func / (lhs: OutcomeWithSuccessfulness, rhs: OutcomeWithSuccessfulness) -> OutcomeWithSuccessfulness { + let outcome = lhs.outcome / rhs.outcome + let successfulness = lhs.successfulness / rhs.successfulness + return OutcomeWithSuccessfulness(outcome, successfulness: successfulness) +} + +public func % (lhs: OutcomeWithSuccessfulness, rhs: OutcomeWithSuccessfulness) -> OutcomeWithSuccessfulness { + let outcome = lhs.outcome % rhs.outcome + let successfulness = lhs.successfulness % rhs.successfulness + return OutcomeWithSuccessfulness(outcome, successfulness: successfulness) +} + +// MARK: - FrequencyDistributionOutcomeType + +extension OutcomeWithSuccessfulness: FrequencyDistributionOutcomeType { + + public var multiplierEquivalent: Int { + return outcome.multiplierEquivalent + } +} diff --git a/DiceKit/Successfulness.swift b/DiceKit/Successfulness.swift new file mode 100644 index 0000000..8ccba17 --- /dev/null +++ b/DiceKit/Successfulness.swift @@ -0,0 +1,183 @@ +// +// Successfulness.swift +// DiceKit +// +// Created by Brentley Jones on 10/25/15. +// Copyright © 2015 Brentley Jones. All rights reserved. +// + +/// The number of successes and failures of an `ExpressionType`. +/// +/// `Successfulness` can represent the actual number of successes and failures that occured while evaluating an +/// `ExpressionType`, or it can represent the possibility of successes and failures in the context of +/// a `FrequencyDistribution`. +public struct Successfulness: Equatable { + /// The number of successes. + /// + /// This value cannot be negative. See `rawSuccesses`. + public let successes: Int + /// The number of failures. + /// + /// This value cannot be negative. See `rawFailures`. + public let failures: Int + + /// The raw number of successes. + /// + /// This value can be negative. It's used as part of arithmetic. + public let rawSuccesses: Int + /// The raw number of failures. + /// + /// This value can be negative. It's used as part of arithmetic. + public let rawFailures: Int + + public init(successes: Int, failures: Int) { + self.rawSuccesses = successes + self.rawFailures = failures + + self.successes = max(0,successes) + self.failures = max(0,failures) + } +} + +// MARK: - CustomStringConvertible + +extension Successfulness: CustomStringConvertible { + public var description: String { + return rawDescription(surroundWithParentheses: true) + } + + func rawDescription(surroundWithParentheses surround: Bool) -> String { + let innerDescription: String + switch (successes, failures) { + case (0, 0): + innerDescription = "0 Successes" + case let (s, 0): + innerDescription = "\(s) Successes" + case let (0, f): + innerDescription = "\(f) Failures" + case let (s, f): + innerDescription = "\(s) Successes and \(f) Failures" + } + + return surround ? "(\(innerDescription))" : innerDescription + } +} + +// MARK: - CustomDebugStringConvertible + +extension Successfulness: CustomDebugStringConvertible { + public var debugDescription: String { + return "Successfulness(rawSuccesses: \(rawSuccesses), rawFailures: \(rawFailures))" + } +} + +// MARK: - CustomPlaygroundQuickLookable + +extension Successfulness: CustomPlaygroundQuickLookable { + public func customPlaygroundQuickLook() -> PlaygroundQuickLook { + return PlaygroundQuickLook.Point(Float64(successes), Float64(failures)) + } +} + +// MARK: - Equatable + +public func == (lhs: Successfulness, rhs: Successfulness) -> Bool { + return lhs.rawSuccesses == rhs.rawSuccesses && lhs.rawFailures == rhs.rawFailures +} + +// MARK: - Comparable + +extension Successfulness: Comparable { +} + +public func < (lhs: Successfulness, rhs: Successfulness) -> Bool { + let lhsMult = lhs.multiplierEquivalent + let rhsMult = rhs.multiplierEquivalent + if lhsMult == rhsMult { + if lhs.rawSuccesses == rhs.rawSuccesses { + return lhs.rawFailures < rhs.rawFailures + } else { + return lhs.rawSuccesses < rhs.rawSuccesses + } + } else { + return lhsMult < rhsMult + } +} + +// MARK: - Hashable + +extension Successfulness: Hashable { + public var hashValue: Int { + return rawSuccesses.hashValue ^ rawFailures.hashValue + } +} + +// MARK: - ArithmeticType + +extension Successfulness: ArithmeticType { + public static let additiveIdentity = Successfulness(successes: 0, failures: 0) + public static let multiplicativeIdentity = Successfulness(successes: 1, failures: 1) + + // MARK: IntegerLiteralType + + public typealias IntegerLiteralType = Int + + public init(integerLiteral value: IntegerLiteralType) { + let successes: Int + let failures: Int + if value > 0 { + // Interpret it as a successes count + successes = value + failures = 0 + } else if value < 0 { + // Interpret it as a failures count + successes = 0 + failures = -value + } else { + successes = 0 + failures = 0 + } + + self.init(successes: successes, failures: failures) + } +} + +public func + (lhs: Successfulness, rhs: Successfulness) -> Successfulness { + let successes = lhs.rawSuccesses + rhs.rawSuccesses + let failures = lhs.rawFailures + rhs.rawFailures + return Successfulness(successes: successes, failures: failures) +} + +public func - (lhs: Successfulness, rhs: Successfulness) -> Successfulness { + let successes = lhs.rawSuccesses - rhs.rawSuccesses + let failures = lhs.rawFailures - rhs.rawFailures + return Successfulness(successes: successes, failures: failures) +} + +public func * (lhs: Successfulness, rhs: Successfulness) -> Successfulness { + let successes = lhs.rawSuccesses * rhs.rawSuccesses + let failures = lhs.rawFailures * rhs.rawFailures + return Successfulness(successes: successes, failures: failures) +} + +public func / (lhs: Successfulness, rhs: Successfulness) -> Successfulness { + let successes = lhs.rawSuccesses / rhs.rawSuccesses + let failures = lhs.rawFailures / rhs.rawFailures + return Successfulness(successes: successes, failures: failures) +} + +public func % (lhs: Successfulness, rhs: Successfulness) -> Successfulness { + let successes = lhs.rawSuccesses % rhs.rawSuccesses + let failures = lhs.rawFailures % rhs.rawFailures + return Successfulness(successes: successes, failures: failures) +} + +// MARK: - FrequencyDistributionOutcomeType + +extension Successfulness: FrequencyDistributionOutcomeType { + + public var multiplierEquivalent: Int { + return successes - failures + } + +} diff --git a/DiceKitTests/Arbitrary.swift b/DiceKitTests/Arbitrary.swift index 96e1225..1dfc0c7 100644 --- a/DiceKitTests/Arbitrary.swift +++ b/DiceKitTests/Arbitrary.swift @@ -94,3 +94,35 @@ public struct FrequencyDistributionOf.shrink(bl.getFrequencyDistribution).map(FrequencyDistributionOf.init) } } + +extension Successfulness: Arbitrary { + public static func create(x : Int)(y : Int) -> Successfulness { + return Successfulness(successes: x, failures: y) + } + + public static var arbitrary : Gen { + return Successfulness.create <^> Int.arbitrary <*> Int.arbitrary + } +} + +// TODO: Replace this with the commented block once Swift generics are fixed (probably Swift 2) +extension OutcomeWithSuccessfulness: Arbitrary { + static func create(x : Int)(y : Successfulness) -> OutcomeWithSuccessfulness { + let outcome = Outcome(integerLiteral: x) + return OutcomeWithSuccessfulness(outcome, successfulness: y) + } + + public static var arbitrary : Gen { + return OutcomeWithSuccessfulness.create <^> Int.arbitrary <*> Successfulness.arbitrary + } +} + +//extension OutcomeWithSuccessfulness: Arbitrary where Outcome: Arbitrary { +// static func create(x : Outcome)(y : Successfulness) -> OutcomeWithSuccessfulness { +// return OutcomeWithSuccessfulness(outcome: x, successfulness: y) +// } +// +// public static var arbitrary : Gen { +// return OutcomeWithSuccessfulness.create <^> Outcome.arbitrary <*> Successfulness.arbitrary +// } +//} diff --git a/DiceKitTests/OutcomeWithSuccessfulness_Tests.swift b/DiceKitTests/OutcomeWithSuccessfulness_Tests.swift new file mode 100644 index 0000000..4bc0e6f --- /dev/null +++ b/DiceKitTests/OutcomeWithSuccessfulness_Tests.swift @@ -0,0 +1,267 @@ +// +// OutcomeWithSuccessfulness_Tests.swift +// DiceKit +// +// Created by Brentley Jones on 10/25/15. +// Copyright © 2015 Brentley Jones. All rights reserved. +// + +import XCTest +import Nimble +import SwiftCheck + +@testable import DiceKit + +class OutcomeWithSuccessfulness_Tests: XCTestCase { +} + +// MARK: - Initialization +extension OutcomeWithSuccessfulness_Tests { + + func test_init() { + property("init") <- forAll { + (outcome: Int, successfulness: Successfulness) in + + let x = OutcomeWithSuccessfulness(outcome, successfulness: successfulness) + + let outcomeTest = x.outcome == outcome + let successfulnessTest = x.successfulness == successfulness + + return outcomeTest && successfulnessTest + } + } +} + +// MARK: - CustomDebugStringConvertible +extension OutcomeWithSuccessfulness_Tests { + func test_CustomDebugStringConvertible() { + let outcomeWithSuccessfulness = OutcomeWithSuccessfulness(14, + successfulness: Successfulness(successes: 6, failures: 4)) + let expected = "OutcomeWithSuccessfulness(\(14), successfulness: \(String(reflecting: Successfulness(successes: 6, failures: 4))))" + + let result = String(reflecting: outcomeWithSuccessfulness) + + expect(result) == expected + } +} + +// MARK: - CustomStringConvertible +extension OutcomeWithSuccessfulness_Tests { + func test_CustomStringConvertible_onlySuccesses() { + let outcomeWithSuccessfulness = OutcomeWithSuccessfulness(14, + successfulness: Successfulness(successes: 6, failures: 0)) + let expected = "(\(outcomeWithSuccessfulness.outcome) with \(outcomeWithSuccessfulness.successfulness.rawDescription(surroundWithParentheses: false)))" + + let result = String(outcomeWithSuccessfulness) + + expect(result) == expected + } + + func test_CustomStringConvertible_onlyFailures() { + let outcomeWithSuccessfulness = OutcomeWithSuccessfulness(14, + successfulness: Successfulness(successes: 0, failures: 4)) + let expected = "(\(outcomeWithSuccessfulness.outcome) with \(outcomeWithSuccessfulness.successfulness.rawDescription(surroundWithParentheses: false)))" + + let result = String(outcomeWithSuccessfulness) + + expect(result) == expected + } + + func test_CustomStringConvertible_bothSuccessesAndFailures() { + let outcomeWithSuccessfulness = OutcomeWithSuccessfulness(14, + successfulness: Successfulness(successes: 6, failures: 4)) + let expected = "(\(outcomeWithSuccessfulness.outcome) with \(outcomeWithSuccessfulness.successfulness.rawDescription(surroundWithParentheses: false)))" + + let result = String(outcomeWithSuccessfulness) + + expect(result) == expected + } + + func test_CustomStringConvertible_noSuccessesOoFailures() { + let outcomeWithSuccessfulness = OutcomeWithSuccessfulness(14, + successfulness: Successfulness(successes: 0, failures: 0)) + let expected = "\(outcomeWithSuccessfulness.outcome)" + + let result = String(outcomeWithSuccessfulness) + + expect(result) == expected + } +} + +// MARK: - Equatable +extension OutcomeWithSuccessfulness_Tests { + + func test_shouldBeReflexive() { + property("reflexive") <- forAll { + (x: Int, y: Successfulness) in + + return EquatableTestUtilities.checkReflexive { OutcomeWithSuccessfulness(x, successfulness: y) } + } + } + + func test_shouldBeSymmetric() { + property("symmetric") <- forAll { + (x: Int, y: Successfulness) in + + return EquatableTestUtilities.checkSymmetric { OutcomeWithSuccessfulness(x, successfulness: y) } + } + } + + func test_shouldBeTransitive() { + property("transitive") <- forAll { + (x: Int, y: Successfulness) in + + return EquatableTestUtilities.checkTransitive { OutcomeWithSuccessfulness(x, successfulness: y) } + } + } + + func test_shouldBeAbleToNotEquate() { + property("non-equal") <- forAll { + (a: Int, b: Successfulness, c: Int, d: Successfulness) in + + return (a != c || b != d) ==> { + EquatableTestUtilities.checkNotEquate( + { OutcomeWithSuccessfulness(a, successfulness: b) }, + { OutcomeWithSuccessfulness(c, successfulness: d) } + ) + } + } + } +} + +// MARK: - Comparable +extension OutcomeWithSuccessfulness_Tests { + + func test_operator_lessThan() { + // Higher outcomes wins + expect(OutcomeWithSuccessfulness(9, successfulness: 14)) < OutcomeWithSuccessfulness(10, successfulness: 13) + + // Otherwise higher successfulness wins + expect(OutcomeWithSuccessfulness(10, successfulness: 12)) < OutcomeWithSuccessfulness(10, successfulness: 13) + } +} + +// MARK: - Hashable +extension OutcomeWithSuccessfulness_Tests { + + func test_hashValue() { + property("x == y then x.hashValue == y.hashValue") <- forAll { + (x: Int, y: Successfulness) in + + let a = OutcomeWithSuccessfulness(x, successfulness: y) + let b = OutcomeWithSuccessfulness(x, successfulness: y) + + return a.hashValue == b.hashValue + } + } +} + +// MARK: - AdditiveType, InvertibleAdditiveType, MultiplicativeType, InvertibleMultiplicativeType +extension OutcomeWithSuccessfulness_Tests { + + func test_additiveIdentity() { + property("x + 0 == x == 0 + x") <- forAll { + (x: Int, y: Successfulness) in + + let a = OutcomeWithSuccessfulness(x, successfulness: y) + + let a1 = a + .additiveIdentity + let a2 = .additiveIdentity + a + + return a == a1 && a == a2 + } + } + + func test_multiplicativeIdentity() { + property("x * 1 == x == 1 * x") <- forAll { + (x: Int, y: Successfulness) in + + let a = OutcomeWithSuccessfulness(x, successfulness: y) + + let a1 = a * .multiplicativeIdentity + let a2 = .multiplicativeIdentity * a + + return a == a1 && a == a2 + } + } + + func test_operator_add() { + let x = OutcomeWithSuccessfulness(5, + successfulness: Successfulness(successes: 0, failures: -1)) + let y = OutcomeWithSuccessfulness(-2, + successfulness: Successfulness(successes: 8, failures: 2)) + let expected = OutcomeWithSuccessfulness(x.outcome + y.outcome, + successfulness: x.successfulness + y.successfulness) + + let z = x + y + + expect(z) == expected + } + + func test_operator_subtract() { + let x = OutcomeWithSuccessfulness(5, + successfulness: Successfulness(successes: 0, failures: -1)) + let y = OutcomeWithSuccessfulness(-2, + successfulness: Successfulness(successes: 8, failures: 2)) + let expected = OutcomeWithSuccessfulness(x.outcome - y.outcome, + successfulness: x.successfulness - y.successfulness) + + let z = x - y + + expect(z) == expected + } + + func test_operator_multiply() { + let x = OutcomeWithSuccessfulness(5, + successfulness: Successfulness(successes: 0, failures: -1)) + let y = OutcomeWithSuccessfulness(-2, + successfulness: Successfulness(successes: 8, failures: 2)) + let expected = OutcomeWithSuccessfulness(x.outcome * y.outcome, + successfulness: x.successfulness * y.successfulness) + + let z = x * y + + expect(z) == expected + } + + func test_operator_divide() { + let x = OutcomeWithSuccessfulness(5, + successfulness: Successfulness(successes: 0, failures: -1)) + let y = OutcomeWithSuccessfulness(-2, + successfulness: Successfulness(successes: 8, failures: 2)) + let expected = OutcomeWithSuccessfulness(x.outcome / y.outcome, + successfulness: x.successfulness / y.successfulness) + + let z = x / y + + expect(z) == expected + } + + func test_operator_remainder() { + let x = OutcomeWithSuccessfulness(5, + successfulness: Successfulness(successes: 0, failures: -1)) + let y = OutcomeWithSuccessfulness(-2, + successfulness: Successfulness(successes: 8, failures: 2)) + let expected = OutcomeWithSuccessfulness(x.outcome % y.outcome, + successfulness: x.successfulness % y.successfulness) + + let z = x % y + + expect(z) == expected + } +} + +// MARK: - FrequencyDistributionOutcomeType +extension OutcomeWithSuccessfulness_Tests { + + func test_multiplierEquivalent() { + property("multiplierEquivalent") <- forAll { + (x: Int, y: Successfulness) in + + let outcomeWithSuccessfulness = OutcomeWithSuccessfulness(x, successfulness: y) + let multiplierEquivalent = x + + return outcomeWithSuccessfulness.multiplierEquivalent == multiplierEquivalent + } + } +} diff --git a/DiceKitTests/Successfulness_Tests.swift b/DiceKitTests/Successfulness_Tests.swift new file mode 100644 index 0000000..6530a7f --- /dev/null +++ b/DiceKitTests/Successfulness_Tests.swift @@ -0,0 +1,257 @@ +// +// Successfulness_Tests.swift +// DiceKit +// +// Created by Brentley Jones on 10/25/15. +// Copyright © 2015 Brentley Jones. All rights reserved. +// + +import XCTest +import Nimble +import SwiftCheck + +import DiceKit + +class Successfulness_Tests: XCTestCase { +} + +// MARK: - Initialization +extension Successfulness_Tests { + func test_init() { + property("init") <- forAll { + (successes: Int, failures: Int) in + + let successfulness = Successfulness(successes: successes, failures: failures) + + let successesTest = successfulness.rawSuccesses == successes + let failuresTest = successfulness.rawFailures == failures + + return successesTest && failuresTest + } + } + + func test_init_integerLiteral() { + property("init integerLiteral") <- forAll { + (x: Int) in + + let expectedSuccessed = x > 0 ? x : 0 + let expectedFailures = x < 0 ? -x : 0 + + let successfulness = Successfulness(integerLiteral: x) + + let successesTest = successfulness.successes == expectedSuccessed + let failuresTest = successfulness.failures == expectedFailures + + return successesTest && failuresTest + } + } +} + +// MARK: - CustomDebugStringConvertible +extension Successfulness_Tests { + func test_CustomDebugStringConvertible() { + let successfulness = Successfulness(successes: 6, failures: 4) + let expected = "Successfulness(rawSuccesses: 6, rawFailures: 4)" + + let result = String(reflecting: successfulness) + + expect(result) == expected + } +} + +// MARK: - CustomStringConvertible +extension Successfulness_Tests { + func test_CustomStringConvertible_onlySuccesses() { + let successfulness = Successfulness(successes: 6, failures: 0) + let expected = "(6 Successes)" + + let result = String(successfulness) + + expect(result) == expected + } + + func test_CustomStringConvertible_onlyFailures() { + let successfulness = Successfulness(successes: 0, failures: 4) + let expected = "(4 Failures)" + + let result = String(successfulness) + + expect(result) == expected + } + + func test_CustomStringConvertible_bothSuccessesAndFailures() { + let successfulness = Successfulness(successes: 6, failures: 4) + let expected = "(6 Successes and 4 Failures)" + + let result = String(successfulness) + + expect(result) == expected + } + + func test_CustomStringConvertible_noSuccessesOoFailures() { + let successfulness = Successfulness(successes: 0, failures: 0) + let expected = "(0 Successes)" + + let result = String(successfulness) + + expect(result) == expected + } +} + +// MARK: - Equatable +extension Successfulness_Tests { + func test_shouldBeReflexive() { + property("reflexive") <- forAll { + (x: Int, y: Int) in + + return EquatableTestUtilities.checkReflexive { Successfulness(successes: x, failures: y) } + } + } + + func test_shouldBeSymmetric() { + property("symmetric") <- forAll { + (x: Int, y: Int) in + + return EquatableTestUtilities.checkSymmetric { Successfulness(successes: x, failures: y) } + } + } + + func test_shouldBeTransitive() { + property("transitive") <- forAll { + (x: Int, y: Int) in + + return EquatableTestUtilities.checkTransitive { Successfulness(successes: x, failures: y) } + } + } + + func test_shouldBeAbleToNotEquate() { + property("non-equal") <- forAll { + (a: Int, b: Int, c: Int, d: Int) in + + return (a != c || b != d) ==> { + EquatableTestUtilities.checkNotEquate( + { Successfulness(successes: a, failures: b) }, + { Successfulness(successes: c, failures: d) } + ) + } + } + } +} + +// MARK: - Comparable +extension Successfulness_Tests { + func test_operator_lessThan() { + // Higher combinded (successes - failures) wins + expect(Successfulness(successes: 10, failures: 9)) < Successfulness(successes: 9, failures: 7) + + // Otherwise higher successes/lower failures wins + expect(Successfulness(successes: 10, failures: 9)) < Successfulness(successes: 11, failures: 10) + } +} + +// MARK: - Hashable +extension Successfulness_Tests { + func test_hashValue() { + property("x == y then x.hashValue == y.hashValue") <- forAll { + (x: Int, y: Int) in + + let a = Successfulness(successes: x, failures: y) + let b = Successfulness(successes: x, failures: y) + + return a.hashValue == b.hashValue + } + } +} + +// MARK: - AdditiveType, InvertibleAdditiveType, MultiplicativeType, InvertibleMultiplicativeType +extension Successfulness_Tests { + func test_additiveIdentity() { + property("x + 0 == x == 0 + x") <- forAll { + (x: Int, y: Int) in + + let a = Successfulness(successes: x, failures: y) + + let a1 = a + .additiveIdentity + let a2 = .additiveIdentity + a + + return a == a1 && a == a2 + } + } + + func test_multiplicativeIdentity() { + property("x * 1 == x == 1 * x") <- forAll { + (x: Int, y: Int) in + + let a = Successfulness(successes: x, failures: y) + + let a1 = a * .multiplicativeIdentity + let a2 = .multiplicativeIdentity * a + + return a == a1 && a == a2 + } + } + + func test_operator_add() { + let x = Successfulness(successes: 3, failures: 1) + let y = Successfulness(successes: -1, failures: 4) + let expected = Successfulness(successes: 2, failures: 5) + + let z = x + y + + expect(z) == expected + } + + func test_operator_subtract() { + let x = Successfulness(successes: 3, failures: 1) + let y = Successfulness(successes: -1, failures: 4) + let expected = Successfulness(successes: 4, failures: -3) + + let z = x - y + + expect(z) == expected + } + + func test_operator_multiply() { + let x = Successfulness(successes: 3, failures: 1) + let y = Successfulness(successes: -1, failures: 4) + let expected = Successfulness(successes: -3, failures: 4) + + let z = x * y + + expect(z) == expected + } + + func test_operator_divide() { + let x = Successfulness(successes: 3, failures: 1) + let y = Successfulness(successes: -1, failures: 4) + let expected = Successfulness(successes: -3, failures: 0) + + let z = x / y + + expect(z) == expected + } + + func test_operator_remainder() { + let x = Successfulness(successes: 3, failures: 1) + let y = Successfulness(successes: -1, failures: 4) + let expected = Successfulness(successes: 0, failures: 1) + + let z = x % y + + expect(z) == expected + } +} + +// MARK: - FrequencyDistributionOutcomeType +extension Successfulness_Tests { + func test_multiplierEquivalent() { + property("multiplierEquivalent") <- forAll { + (x: Int, y: Int) in + + let successfulness = Successfulness(successes: x, failures: y) + let multiplierEquivalent = max(0,x) - max(0,y) + + return successfulness.multiplierEquivalent == multiplierEquivalent + } + } +} diff --git a/TomeOfKnowledge.playground/Pages/Probability.xcplaygroundpage/Contents.swift b/TomeOfKnowledge.playground/Pages/Probability.xcplaygroundpage/Contents.swift index d11379b..3eb3f03 100644 --- a/TomeOfKnowledge.playground/Pages/Probability.xcplaygroundpage/Contents.swift +++ b/TomeOfKnowledge.playground/Pages/Probability.xcplaygroundpage/Contents.swift @@ -39,3 +39,4 @@ for (value, probability) in awesomeExpressionProbailityMass { //: [Previous](@previous) +//: [Next](@next) diff --git a/TomeOfKnowledge.playground/Pages/Successfulness.xcplaygroundpage/Contents.swift b/TomeOfKnowledge.playground/Pages/Successfulness.xcplaygroundpage/Contents.swift new file mode 100644 index 0000000..8f425c0 --- /dev/null +++ b/TomeOfKnowledge.playground/Pages/Successfulness.xcplaygroundpage/Contents.swift @@ -0,0 +1,34 @@ +//: ## Successfulness +//: +//: Often in tabletop play outcomes of various expressions have certain meaning. A game might have you roll a die and add modifiers to the result, and if the overall result is greater than a certain number then that is called a "hit." An example exrpression would be d20+5. But a roll of 20 on the d20 might also be called a hit, even if the result of 25 isn't higher than the target number. DiceKit uses a successfulness system to support these scenarios. + +import DiceKit + +//: First let's introduce the `Successfulness` type. This is a type that stores a number of successes and failures. +var successfulness = Successfulness(successes: 0, failures: 1) + +//: Oops, looks like that result is actually a failure. Instead of specifying the successes and failures that way, you can use the alternative Integer Literal init. This lets you pass one integer, and get a `Successfulness` object with that many successes if the integer is positive, or if it's negative that many failures * -1. +var integerLiteralInit = Successfulness(integerLiteral: -1) +successfulness == integerLiteralInit + +//: This type can hold multiple successes *and* failures. This allows it to support larger expressions that have multiple conditions for success or failure. How to use and implement that feature is largely up to you, but DiceKit does have some logic to keep in mind out of the box. We've already seen how it is equatable above. You can also compare two successfulness objects: +var succeedy = Successfulness(successes: 3, failures: 2) +var faily = Successfulness(successes: 1, failures: 3) +succeedy > faily + +//: The comparison looks at the difference between successes and failures (# of successes - # of failures). If both results have the same difference between successes and failures, the one with more overall successes is considered greater than the other. That means that if faily gets a handout it can actually be considered greater than succeedy. +var freebie = Successfulness(successes: 3, failures: 0) +faily = faily + freebie +succeedy > faily + +//: Not so great now, are you succeedy? Oh, by the way, Successfulness supports integer addition, subtraction, multiplication and modulo operations. Successfulness objects also have let properties for successes and failures. +faily.successes +faily.failures + +//: Finally, you can't have fewer than 0 successes or failures, but there is a public property that tracks that for arithmetic purposes. +var negativeSuccess = Successfulness(successes: -5, failures: 7) +negativeSuccess.rawSuccesses +negativeSuccess.rawFailures + + +//: [Previous](@previous) diff --git a/TomeOfKnowledge.playground/Pages/Successfulness.xcplaygroundpage/timeline.xctimeline b/TomeOfKnowledge.playground/Pages/Successfulness.xcplaygroundpage/timeline.xctimeline new file mode 100644 index 0000000..bf468af --- /dev/null +++ b/TomeOfKnowledge.playground/Pages/Successfulness.xcplaygroundpage/timeline.xctimeline @@ -0,0 +1,6 @@ + + + + + diff --git a/TomeOfKnowledge.playground/contents.xcplayground b/TomeOfKnowledge.playground/contents.xcplayground index ce3f040..39b1f63 100644 --- a/TomeOfKnowledge.playground/contents.xcplayground +++ b/TomeOfKnowledge.playground/contents.xcplayground @@ -1,9 +1,10 @@ - + + \ No newline at end of file