浏览代码

Add support for keep highest/keep lowest

Sam Jaffe 2 年之前
父节点
当前提交
fcf1a1d230

+ 0 - 63
dice-roll.xcodeproj/xcshareddata/xcschemes/dice-td.xcscheme

@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<Scheme
-   LastUpgradeVersion = "1230"
-   version = "1.3">
-   <BuildAction
-      parallelizeBuildables = "YES"
-      buildImplicitDependencies = "YES">
-   </BuildAction>
-   <TestAction
-      buildConfiguration = "Debug"
-      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
-      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      shouldUseLaunchSchemeArgsEnv = "YES"
-      codeCoverageEnabled = "YES"
-      onlyGenerateCoverageForSpecifiedTargets = "YES">
-      <CodeCoverageTargets>
-         <BuildableReference
-            BuildableIdentifier = "primary"
-            BlueprintIdentifier = "CD38F50821C83912007A732C"
-            BuildableName = "libdice.dylib"
-            BlueprintName = "dice"
-            ReferencedContainer = "container:dice-roll.xcodeproj">
-         </BuildableReference>
-      </CodeCoverageTargets>
-      <Testables>
-         <TestableReference
-            skipped = "NO">
-            <BuildableReference
-               BuildableIdentifier = "primary"
-               BlueprintIdentifier = "CD38F52521C87771007A732C"
-               BuildableName = "dice-td.xctest"
-               BlueprintName = "dice-td"
-               ReferencedContainer = "container:dice-roll.xcodeproj">
-            </BuildableReference>
-         </TestableReference>
-      </Testables>
-   </TestAction>
-   <LaunchAction
-      buildConfiguration = "Debug"
-      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
-      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
-      launchStyle = "0"
-      useCustomWorkingDirectory = "NO"
-      ignoresPersistentStateOnLaunch = "NO"
-      debugDocumentVersioning = "YES"
-      debugServiceExtension = "internal"
-      allowLocationSimulation = "YES">
-   </LaunchAction>
-   <ProfileAction
-      buildConfiguration = "Release"
-      shouldUseLaunchSchemeArgsEnv = "YES"
-      savedToolIdentifier = ""
-      useCustomWorkingDirectory = "NO"
-      debugDocumentVersioning = "YES">
-   </ProfileAction>
-   <AnalyzeAction
-      buildConfiguration = "Debug">
-   </AnalyzeAction>
-   <ArchiveAction
-      buildConfiguration = "Release"
-      revealArchiveInOrganizer = "YES">
-   </ArchiveAction>
-</Scheme>

+ 1 - 1
dice-roll.xcodeproj/xcshareddata/xcschemes/dice.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1230"
+   LastUpgradeVersion = "1240"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"

+ 6 - 0
include/dice-roll/die.h

@@ -21,9 +21,15 @@ template <typename T> static sign sgn(T val) {
 
 int sgn(sign);
 
+struct keep {
+  enum { All, Highest, Lowest } method;
+  int amount;
+};
+
 struct die {
   sign sgn;
   int num, sides;
+  keep keep{keep::All, 0};
 };
 
 struct mod {

+ 4 - 0
include/dice-roll/parser.h

@@ -86,6 +86,10 @@ private:
    */
   void parse_const(sign s, int value);
   void parse_dc(char token);
+  void parse_keep(char token);
+
+  int read(int value, bool error_on_eof);
+  int read() { return read(0, true); }
 };
 
 }

+ 12 - 1
src/io.cxx

@@ -41,10 +41,21 @@ std::ostream & operator<<(std::ostream & out, difficulty_class::test t) {
   }
 }
 
+std::ostream & operator<<(std::ostream & out, keep const & k) {
+  switch (k.method) {
+  case keep::Highest:
+    return out << 'k' << 'h' << k.amount;
+  case keep::Lowest:
+    return out << 'k' << 'l' << k.amount;
+  default:
+    return out;
+  }
+}
+
 std::ostream & operator<<(std::ostream & out, dice const & d) {
   if (d.num != 1) out << d.num << '{';
   for (die const & di : d.of) {
-    out << di.sgn << di.num << 'd' << di.sides;
+    out << di.sgn << di.num << 'd' << di.sides << di.keep;
   }
   for (mod m : d.modifier) {
     out << m.sign << m.value;

+ 28 - 6
src/parser.cxx

@@ -29,9 +29,28 @@ void parser::parse_dN(sign s, int value) {
     throw unexpected_token("Expected a number of sides", is_.tellg());
   }
   is_ >> dice_.of.back().sides;
+  if (std::strchr("kK", is_.peek())) {
+    is_.get();
+    parse_keep(is_.get());
+  }
   parse_impl(sign::ZERO);
 }
 
+void parser::parse_keep(char token) {
+  switch (token) {
+  case 'h':
+  case 'H':
+    dice_.of.back().keep = {keep::Highest, read()};
+    break;
+  case 'l':
+  case 'L':
+    dice_.of.back().keep = {keep::Lowest, read()};
+    break;
+  default:
+    throw unexpected_token("Expected high/low in keep expression", is_.tellg());
+  }
+}
+
 void parser::parse_const(sign s, int value) {
   if (value) { // Zero is not a modifier we care about
     dice_.modifier.push_back({s, std::abs(value)});
@@ -55,16 +74,20 @@ void parser::parse_dc(char token) {
   }
 }
 
-void parser::parse_impl(sign s) {
-  advance_over_whitespace(is_);
+int parser::read(int value, bool error_on_eof) {
   // By defaulting this to zero, we can write a more elegant handling of
   // expressions like 1d4+1d6+5+1
-  int value = 0;
   if (isnumber(is_.peek())) {
     is_ >> value;
-  } else if (is_.peek() == EOF && s != sign::ZERO) {
+  } else if (is_.peek() == EOF && error_on_eof) {
     throw unexpected_token("Unexpected EOF while parsing", -1);
   }
+  return value;
+}
+
+void parser::parse_impl(sign s) {
+  advance_over_whitespace(is_);
+  int const value = read(0, s != sign::ZERO);
   advance_over_whitespace(is_);
   switch (is_.peek()) {
   case 'd':
@@ -89,9 +112,8 @@ void parser::parse_impl(sign s) {
 
 dice parser::parse() {
   dice_ = {};
-  int value{1};
   advance_over_whitespace(is_);
-  if (isnumber(is_.peek())) { is_ >> value; }
+  int const value = read(1, false);
   advance_over_whitespace(is_);
 
   switch (is_.peek()) {

+ 12 - 0
src/roll.cxx

@@ -32,6 +32,18 @@ die_roll roll_impl(die const & d, engine::random & gen) {
   for (int i = 0; i < d.num; ++i) {
     hits.push_back(gen.roll(d.sides));
   }
+  switch (d.keep.method) {
+  case keep::Highest:
+    for (int i = 0; i < d.num - d.keep.amount; ++i) {
+      hits.erase(std::min_element(hits.begin(), hits.end()));
+    }
+  case keep::Lowest:
+    for (int i = 0; i < d.num - d.keep.amount; ++i) {
+      hits.erase(std::max_element(hits.begin(), hits.end()));
+    }
+  default:
+    break;
+  }
   return {d.sgn, hits};
 }
 

+ 6 - 0
test/xcode_gtest_helper.h

@@ -10,9 +10,15 @@
 
 #if defined(__APPLE__)
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wquoted-include-in-framework-header"
+#pragma clang diagnostic ignored "-Wcomma"
+
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#pragma clang diagnostic pop
+
 #if defined(TARGET_OS_OSX)
 // This is a hack to allow XCode to properly display failures when running
 // unit tests.