فهرست منبع

Working PlayCard class (replaces play_card).

This helps break that function up into more sensible parts.
Also, I can get rid of global variables now -- everything
I need is in the class.

Needed to implement move && for Deck.
Steve Thielemann 3 سال پیش
والد
کامیت
3fb2078d0c
6فایلهای تغییر یافته به همراه1464 افزوده شده و 749 حذف شده
  1. 2 2
      CMakeLists.txt
  2. 170 1
      deck.cpp
  3. 15 0
      deck.h
  4. 8 746
      main.cpp
  5. 1206 0
      play.cpp
  6. 63 0
      play.h

+ 2 - 2
CMakeLists.txt

@@ -65,6 +65,6 @@ endif()
 
 add_subdirectory(yaml-cpp)
 
-add_executable(space-ace main.cpp deck.cpp db.h db.cpp images.h)
-target_link_libraries(space-ace door++ pthread SQLiteCpp sqlite3 dl)
+add_executable(space-ace main.cpp deck.cpp db.h db.cpp play.h play.cpp images.h)
+target_link_libraries(space-ace door++ pthread SQLiteCpp sqlite3 dl yaml-cpp)
 

+ 170 - 1
deck.cpp

@@ -1,6 +1,7 @@
 #include "deck.h"
 
 #include <algorithm>
+#include <map>
 #include <sstream>
 
 Deck::Deck(int size) {
@@ -46,6 +47,47 @@ Deck::~Deck() {
   mark.clear();
 }
 
+Deck::Deck(Deck &&ref) {
+  cardback = ref.cardback;
+  for (auto c : cards)
+    delete c;
+  cards.clear();
+  cards = ref.cards;
+  ref.cards.clear();
+  for (auto b : backs)
+    delete b;
+  backs.clear();
+  backs = ref.backs;
+  ref.backs.clear();
+  for (auto m : mark)
+    delete m;
+  mark.clear();
+  mark = ref.mark;
+  ref.mark.clear();
+  card_height = ref.card_height;
+};
+
+Deck &Deck::operator=(Deck &&ref) {
+  cardback = ref.cardback;
+  for (auto c : cards)
+    delete c;
+  cards.clear();
+  cards = ref.cards;
+  ref.cards.clear();
+  for (auto b : backs)
+    delete b;
+  backs.clear();
+  backs = ref.backs;
+  ref.backs.clear();
+  for (auto m : mark)
+    delete m;
+  mark.clear();
+  mark = ref.mark;
+  ref.mark.clear();
+  card_height = ref.card_height;
+  return *this;
+}
+
 int Deck::is_deck(int c) { return c / 52; }
 int Deck::is_suit(int c) { return (c % 52) / 13; }
 int Deck::is_rank(int c) { return (c % 52) % 13; }
@@ -581,4 +623,131 @@ int find_next_closest(const cards &states, int current) {
     }
   }
   return pos;
-}
+}
+
+vector<std::string> deck_colors = {std::string("All"),     std::string("Blue"),
+                                   std::string("Cyan"),    std::string("Green"),
+                                   std::string("Magenta"), std::string("Red")};
+/**
+ * @brief menu render that sets the text color based on the color found in the
+ * text itself.
+ *
+ * @param c1 [] brackets
+ * @param c2 text within brackets
+ * @param c3 base color give (we set the FG, we use the BG)
+ * @return door::renderFunction
+ */
+door::renderFunction makeColorRender(door::ANSIColor c1, door::ANSIColor c2,
+                                     door::ANSIColor c3) {
+  door::renderFunction render = [c1, c2,
+                                 c3](const std::string &txt) -> door::Render {
+    door::Render r(txt);
+
+    bool option = true;
+    door::ColorOutput co;
+    // I need this mutable
+    door::ANSIColor textColor = c3;
+
+    // Color update:
+    {
+      std::string found;
+
+      for (auto &dc : deck_colors) {
+        if (txt.find(dc) != string::npos) {
+          found = dc;
+          break;
+        }
+      }
+
+      if (!found.empty()) {
+        if (found == "All") {
+          // handle this some other way.
+          textColor.setFg(door::COLOR::WHITE);
+        } else {
+          door::ANSIColor c = from_string(found);
+          textColor.setFg(c.getFg());
+        }
+      }
+    }
+    co.pos = 0;
+    co.len = 0;
+    co.c = c1;
+
+    int tpos = 0;
+    for (char const &c : txt) {
+      if (option) {
+        if (c == '[' or c == ']') {
+          if (co.c != c1)
+            if (co.len != 0) {
+              r.outputs.push_back(co);
+              co.reset();
+              co.pos = tpos;
+            }
+          co.c = c1;
+          if (c == ']')
+            option = false;
+        } else {
+          if (co.c != c2)
+            if (co.len != 0) {
+              r.outputs.push_back(co);
+              co.reset();
+              co.pos = tpos;
+            }
+          co.c = c2;
+        }
+      } else {
+        if (co.c != textColor)
+          if (co.len != 0) {
+            r.outputs.push_back(co);
+            co.reset();
+            co.pos = tpos;
+          }
+        co.c = textColor;
+      }
+      co.len++;
+      tpos++;
+    }
+    if (co.len != 0) {
+      r.outputs.push_back(co);
+    }
+    return r;
+  };
+  return render;
+}
+
+// convert a string to an option
+// an option to the string to store
+// This needs to be updated to use deck_colors.
+door::ANSIColor from_string(std::string colorCode) {
+  std::map<std::string, door::ANSIColor> codeMap = {
+      {std::string("BLUE"), door::ANSIColor(door::COLOR::BLUE)},
+      {std::string("RED"), door::ANSIColor(door::COLOR::RED)},
+      {std::string("CYAN"), door::ANSIColor(door::COLOR::CYAN)},
+      {std::string("GREEN"), door::ANSIColor(door::COLOR::GREEN)},
+      {std::string("MAGENTA"), door::ANSIColor(door::COLOR::MAGENTA)}};
+
+  std::string code = colorCode;
+  string_toupper(code);
+
+  auto iter = codeMap.find(code);
+  if (iter != codeMap.end()) {
+    return iter->second;
+  }
+
+  // And if it doesn't match, and isn't ALL ... ?
+  // if (code.compare("ALL") == 0) {
+  std::random_device dev;
+  std::mt19937_64 rng(dev());
+
+  std::uniform_int_distribution<size_t> idDist(0, codeMap.size() - 1);
+  iter = codeMap.begin();
+  std::advance(iter, idDist(rng));
+
+  return iter->second;
+  // }
+}
+
+std::string from_color_option(int opt) { return deck_colors[opt]; }
+void string_toupper(std::string &str) {
+  std::transform(str.begin(), str.end(), str.begin(), ::toupper);
+}

+ 15 - 0
deck.h

@@ -1,3 +1,6 @@
+#ifndef DECK_H
+#define DECK_H
+
 #include "door.h"
 
 #include <random>
@@ -99,6 +102,9 @@ public:
   enum SUIT { HEART, DIAMOND, CLUBS, SPADE };
 
   Deck(int size = 3);
+  // Deck(const Deck &) = default;
+  Deck(Deck &&);
+  Deck &operator=(Deck &&);
   Deck(door::ANSIColor backcolor, int size = 3);
   ~Deck();
 
@@ -230,3 +236,12 @@ int find_next(bool left, const cards &states, int current);
  * @return int
  */
 int find_next_closest(const cards &states, int current);
+
+extern vector<std::string> deck_colors;
+door::renderFunction makeColorRender(door::ANSIColor c1, door::ANSIColor c2,
+                                     door::ANSIColor c3);
+door::ANSIColor from_string(std::string colorCode);
+std::string from_color_option(int opt);
+void string_toupper(std::string &str);
+
+#endif

+ 8 - 746
main.cpp

@@ -9,13 +9,10 @@
 
 #include "db.h"
 #include "deck.h"
+#include "play.h"
 #include "version.h"
 #include <algorithm> // transform
 
-void string_toupper(std::string &str) {
-  std::transform(str.begin(), str.end(), str.begin(), ::toupper);
-}
-
 bool replace(std::string &str, const std::string &from, const std::string &to) {
   size_t start_pos = str.find(from);
   if (start_pos == std::string::npos)
@@ -348,97 +345,12 @@ door::Menu make_config_menu(void) {
   return m;
 }
 
+/*
 // all the possible deck colors
 vector<std::string> deck_colors = {std::string("All"),     std::string("Blue"),
                                    std::string("Cyan"),    std::string("Green"),
                                    std::string("Magenta"), std::string("Red")};
-
-/**
- * @brief menu render that sets the text color based on the color found in the
- * text itself.
- *
- * @param c1 [] brackets
- * @param c2 text within brackets
- * @param c3 base color give (we set the FG, we use the BG)
- * @return door::renderFunction
- */
-door::renderFunction makeColorRender(door::ANSIColor c1, door::ANSIColor c2,
-                                     door::ANSIColor c3) {
-  door::renderFunction render = [c1, c2,
-                                 c3](const std::string &txt) -> door::Render {
-    door::Render r(txt);
-
-    bool option = true;
-    door::ColorOutput co;
-    // I need this mutable
-    door::ANSIColor textColor = c3;
-
-    // Color update:
-    {
-      std::string found;
-
-      for (auto &dc : deck_colors) {
-        if (txt.find(dc) != string::npos) {
-          found = dc;
-          break;
-        }
-      }
-
-      if (!found.empty()) {
-        if (found == "All") {
-          // handle this some other way.
-          textColor.setFg(door::COLOR::WHITE);
-        } else {
-          door::ANSIColor c = from_string(found);
-          textColor.setFg(c.getFg());
-        }
-      }
-    }
-    co.pos = 0;
-    co.len = 0;
-    co.c = c1;
-
-    int tpos = 0;
-    for (char const &c : txt) {
-      if (option) {
-        if (c == '[' or c == ']') {
-          if (co.c != c1)
-            if (co.len != 0) {
-              r.outputs.push_back(co);
-              co.reset();
-              co.pos = tpos;
-            }
-          co.c = c1;
-          if (c == ']')
-            option = false;
-        } else {
-          if (co.c != c2)
-            if (co.len != 0) {
-              r.outputs.push_back(co);
-              co.reset();
-              co.pos = tpos;
-            }
-          co.c = c2;
-        }
-      } else {
-        if (co.c != textColor)
-          if (co.len != 0) {
-            r.outputs.push_back(co);
-            co.reset();
-            co.pos = tpos;
-          }
-        co.c = textColor;
-      }
-      co.len++;
-      tpos++;
-    }
-    if (co.len != 0) {
-      r.outputs.push_back(co);
-    }
-    return r;
-  };
-  return render;
-}
+*/
 
 door::Menu make_deck_menu(void) {
   door::Menu m(5, 5, 31);
@@ -492,38 +404,6 @@ bool iequals(const string &a, const string &b) {
   return true;
 }
 
-// convert a string to an option
-// an option to the string to store
-// This needs to be updated to use deck_colors.
-door::ANSIColor from_string(std::string colorCode) {
-  std::map<std::string, door::ANSIColor> codeMap = {
-      {std::string("BLUE"), door::ANSIColor(door::COLOR::BLUE)},
-      {std::string("RED"), door::ANSIColor(door::COLOR::RED)},
-      {std::string("CYAN"), door::ANSIColor(door::COLOR::CYAN)},
-      {std::string("GREEN"), door::ANSIColor(door::COLOR::GREEN)},
-      {std::string("MAGENTA"), door::ANSIColor(door::COLOR::MAGENTA)}};
-
-  std::string code = colorCode;
-  string_toupper(code);
-
-  auto iter = codeMap.find(code);
-  if (iter != codeMap.end()) {
-    return iter->second;
-  }
-
-  // And if it doesn't match, and isn't ALL ... ?
-  // if (code.compare("ALL") == 0) {
-  std::random_device dev;
-  std::mt19937_64 rng(dev());
-
-  std::uniform_int_distribution<size_t> idDist(0, codeMap.size() - 1);
-  iter = codeMap.begin();
-  std::advance(iter, idDist(rng));
-
-  return iter->second;
-  // }
-}
-
 // This does not seem to be working.  I keep getting zero.
 int opt_from_string(std::string colorCode) {
   for (std::size_t pos = 0; pos != deck_colors.size(); ++pos) {
@@ -535,8 +415,6 @@ int opt_from_string(std::string colorCode) {
   return 0;
 }
 
-std::string from_color_option(int opt) { return deck_colors[opt]; }
-
 int configure(door::Door &door, DBData &db) {
   auto menu = make_config_menu();
   int r = 0;
@@ -641,625 +519,6 @@ door::Panel make_score_panel(door::Door &door) {
   return p;
 }
 
-door::Panel make_streak_panel(void) {
-  const int W = 20;
-  door::Panel p(W);
-  p.setStyle(door::BorderStyle::NONE);
-  door::ANSIColor statusColor(door::COLOR::WHITE, door::COLOR::BLUE,
-                              door::ATTR::BOLD);
-  door::ANSIColor valueColor(door::COLOR::YELLOW, door::COLOR::BLUE,
-                             door::ATTR::BOLD);
-  door::renderFunction svRender = statusValue(statusColor, valueColor);
-
-  {
-    std::string text = "Playing: ";
-    auto in_time_t = std::chrono::system_clock::to_time_t(play_day);
-    std::stringstream ss;
-    ss << std::put_time(std::localtime(&in_time_t), "%B %d");
-    text.append(ss.str());
-    door::Line current(text, W);
-    current.setRender(svRender);
-    p.addLine(std::make_unique<door::Line>(current));
-  }
-  {
-    door::updateFunction currentUpdate = [](void) -> std::string {
-      std::string text = "Current Streak: ";
-      text.append(std::to_string(current_streak));
-      return text;
-    };
-    std::string currentString = currentUpdate();
-    door::Line current(currentString, W);
-    current.setRender(svRender);
-    current.setUpdater(currentUpdate);
-    p.addLine(std::make_unique<door::Line>(current));
-  }
-  {
-    door::updateFunction currentUpdate = [](void) -> std::string {
-      std::string text = "Longest Streak: ";
-      text.append(std::to_string(best_streak));
-      return text;
-    };
-    std::string currentString = currentUpdate();
-    door::Line current(currentString, W);
-    current.setRender(svRender);
-    current.setUpdater(currentUpdate);
-    p.addLine(std::make_unique<door::Line>(current));
-  }
-
-  return p;
-}
-
-door::Panel make_left_panel(void) {
-  const int W = 13;
-  door::Panel p(W);
-  p.setStyle(door::BorderStyle::NONE);
-  door::ANSIColor statusColor(door::COLOR::WHITE, door::COLOR::BLUE,
-                              door::ATTR::BOLD);
-  door::ANSIColor valueColor(door::COLOR::YELLOW, door::COLOR::BLUE,
-                             door::ATTR::BOLD);
-  door::renderFunction svRender = statusValue(statusColor, valueColor);
-
-  {
-    door::updateFunction cardsleftUpdate = [](void) -> std::string {
-      std::string text = "Cards left:";
-      text.append(std::to_string(51 - card_number));
-      return text;
-    };
-    std::string cardsleftString = "Cards left:--";
-    door::Line cardsleft(cardsleftString, W);
-    cardsleft.setRender(svRender);
-    cardsleft.setUpdater(cardsleftUpdate);
-    p.addLine(std::make_unique<door::Line>(cardsleft));
-  }
-  return p;
-}
-
-door::renderFunction commandLineRender(door::ANSIColor bracket,
-                                       door::ANSIColor inner,
-                                       door::ANSIColor outer) {
-  door::renderFunction rf = [bracket, inner,
-                             outer](const std::string &txt) -> door::Render {
-    door::Render r(txt);
-    door::ColorOutput co;
-
-    co.pos = 0;
-    co.len = 0;
-    co.c = outer;
-    bool inOuter = true;
-
-    int tpos = 0;
-    for (char const &c : txt) {
-      if (inOuter) {
-
-        // we're in the outer text
-        if (co.c != outer) {
-          if (co.len != 0) {
-            r.outputs.push_back(co);
-            co.reset();
-            co.pos = tpos;
-          }
-          co.c = outer;
-        }
-
-        // check for [
-        if (c == '[') {
-          if (co.len != 0) {
-            r.outputs.push_back(co);
-            co.reset();
-            co.pos = tpos;
-          }
-          inOuter = false;
-          co.c = bracket;
-        }
-      } else {
-        // We're not in the outer.
-
-        if (co.c != inner) {
-          if (co.len != 0) {
-            r.outputs.push_back(co);
-            co.reset();
-            co.pos = tpos;
-          }
-          co.c = inner;
-        }
-
-        if (c == ']') {
-          if (co.len != 0) {
-            r.outputs.push_back(co);
-            co.reset();
-            co.pos = tpos;
-          }
-          inOuter = true;
-          co.c = bracket;
-        }
-      }
-      co.len++;
-      tpos++;
-    }
-    if (co.len != 0)
-      r.outputs.push_back(co);
-    return r;
-  };
-  return rf;
-}
-
-door::Panel make_command_panel(void) {
-  const int W = 76;
-  door::Panel p(W);
-  p.setStyle(door::BorderStyle::NONE);
-  std::string commands;
-
-  if (door::unicode) {
-    commands = "[4/\u25c4] Left [6/\u25ba] Right [Space] Play Card [Enter] "
-               "Draw [Q]uit "
-               "[R]edraw [H]elp";
-  } else {
-    commands =
-        "[4/\x11] Left [6/\x10] Right [Space] Play Card [Enter] Draw [Q]uit "
-        "[R]edraw [H]elp";
-  }
-
-  door::ANSIColor bracketColor(door::COLOR::YELLOW, door::COLOR::BLUE,
-                               door::ATTR::BOLD);
-  door::ANSIColor innerColor(door::COLOR::CYAN, door::COLOR::BLUE,
-                             door::ATTR::BOLD);
-  door::ANSIColor outerColor(door::COLOR::GREEN, door::COLOR::BLUE,
-                             door::ATTR::BOLD);
-
-  door::renderFunction cmdRender =
-      commandLineRender(bracketColor, innerColor, outerColor);
-  door::Line cmd(commands, W);
-  cmd.setRender(cmdRender);
-  p.addLine(std::make_unique<door::Line>(cmd));
-  return p;
-}
-
-door::Panel make_tripeaks(void) {
-  std::string tripeaksText(" " SPACEACE
-                           " - Tri-Peaks Solitaire v" SPACEACE_VERSION " ");
-  door::Panel spaceAceTriPeaks(tripeaksText.size());
-  spaceAceTriPeaks.setStyle(door::BorderStyle::SINGLE);
-  spaceAceTriPeaks.setColor(
-      door::ANSIColor(door::COLOR::CYAN, door::COLOR::BLACK));
-  spaceAceTriPeaks.addLine(
-      std::make_unique<door::Line>(tripeaksText, tripeaksText.size()));
-  return spaceAceTriPeaks;
-}
-
-int play_cards(door::Door &door, DBData &db, std::mt19937 &rng) {
-  int mx = door.width;
-  int my = door.height;
-
-  // init these values:
-  card_number = 28;
-  active_card = 23;
-  hand = 1;
-  score = 0;
-  play_day = std::chrono::system_clock::now();
-
-  // cards color --
-  // configured by the player.
-
-  door::ANSIColor deck_color;
-  // std::string key("DeckColor");
-  const char *key = "DeckColor";
-  std::string currentDefault = db.getSetting(key, "ALL");
-  door.log() << key << " shows as " << currentDefault << std::endl;
-  deck_color = from_string(currentDefault);
-
-  const int height = 3;
-  Deck d(deck_color); // , height);
-  door::Panel *c;
-
-  // This displays the cards in the upper left corner.
-  // We want them center, and down some.
-
-  // const int space = 3;
-
-  // int cards_dealt_width = 59; int cards_dealt_height = 9;
-  int game_width;
-  int game_height = 20; // 13; // 9;
-  {
-    int cx, cy, level;
-    cardgo(27, cx, cy, level);
-    game_width = cx + 5; // card width
-  }
-  int off_x = (mx - game_width) / 2;
-  int off_y = (my - game_height) / 2;
-  int true_off_y = off_y;
-  // The idea is to see the cards with <<Something Unique to the card game>>,
-  // Year, Month, Day, and game (like 1 of 3).
-  // This will make the games the same/fair for everyone.
-
-next_hand:
-
-  card_number = 28;
-  active_card = 23;
-  score = 0;
-  off_y = true_off_y;
-
-  door::Panel spaceAceTriPeaks = make_tripeaks();
-  int tp_off_x = (mx - spaceAceTriPeaks.getWidth()) / 2;
-  spaceAceTriPeaks.set(tp_off_x, off_y);
-
-  off_y += 3;
-
-  // figure out what date we're playing / what game we're playing
-  time_t tt = std::chrono::system_clock::to_time_t(play_day);
-  tm local_tm = *localtime(&tt);
-  /*
-  std::cout << utc_tm.tm_year + 1900 << '-';
-  std::cout << utc_tm.tm_mon + 1 << '-';
-  std::cout << utc_tm.tm_mday << ' ';
-  std::cout << utc_tm.tm_hour << ':';
-  std::cout << utc_tm.tm_min << ':';
-  std::cout << utc_tm.tm_sec << '\n';
-  */
-
-  std::seed_seq s1{local_tm.tm_year + 1900, local_tm.tm_mon + 1,
-                   local_tm.tm_mday, hand};
-  cards deck1 = card_shuffle(s1, 1);
-  cards state = card_states();
-
-  door::Panel score_panel = make_score_panel(door);
-  door::Panel streak_panel = make_streak_panel();
-  door::Panel left_panel = make_left_panel();
-  door::Panel cmd_panel = make_command_panel();
-
-  {
-    int off_yp = off_y + 11;
-    int cxp, cyp, levelp;
-    int left_panel_x, right_panel_x;
-    // find position of card, to position the panels
-    cardgo(18, cxp, cyp, levelp);
-    left_panel_x = cxp;
-    cardgo(15, cxp, cyp, levelp);
-    right_panel_x = cxp;
-    score_panel.set(left_panel_x + off_x, off_yp);
-    streak_panel.set(right_panel_x + off_x, off_yp);
-    cmd_panel.set(left_panel_x + off_x, off_yp + 5);
-  }
-
-  bool dealing = true;
-  int r = 0;
-
-  while ((r >= 0) and (r != 'Q')) {
-    // REDRAW everything
-
-    door << door::reset << door::cls;
-    door << spaceAceTriPeaks;
-
-    {
-      // step 1:
-      // draw the deck "source"
-      int cx, cy, level;
-      cardgo(29, cx, cy, level);
-
-      if (card_number == 51)
-        level = 0; // out of cards!
-      c = d.back(level);
-      c->set(cx + off_x, cy + off_y);
-      // p3 is heigh below
-      left_panel.set(cx + off_x, cy + off_y + height);
-      door << score_panel << left_panel << streak_panel << cmd_panel;
-      door << *c;
-      if (dealing)
-        std::this_thread::sleep_for(std::chrono::seconds(1));
-    }
-
-    // I tried setting the cursor before the delay and before displaying the
-    // card. It is very hard to see / just about useless.  Not worth the effort.
-
-    for (int x = 0; x < (dealing ? 28 : 29); x++) {
-      int cx, cy, level;
-
-      cardgo(x, cx, cy, level);
-      // This is hardly visible.
-      // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
-      if (dealing)
-        std::this_thread::sleep_for(std::chrono::milliseconds(75));
-
-      if (dealing) {
-        c = d.back(level);
-        c->set(cx + off_x, cy + off_y);
-        door << *c;
-      } else {
-        // redrawing -- draw the cards with their correct "state"
-        int s = state.at(x);
-
-        switch (s) {
-        case 0:
-          c = d.back(level);
-          c->set(cx + off_x, cy + off_y);
-          door << *c;
-          break;
-        case 1:
-          // cardgo(x, space, height, cx, cy, level);
-          if (x == 28)
-            c = d.card(deck1.at(card_number));
-          else
-            c = d.card(deck1.at(x));
-          c->set(cx + off_x, cy + off_y);
-          door << *c;
-          break;
-        case 2:
-          // no card to draw.  :)
-          break;
-        }
-      }
-    }
-
-    if (dealing)
-      for (int x = 18; x < 29; x++) {
-        int cx, cy, level;
-
-        state.at(x) = 1;
-        cardgo(x, cx, cy, level);
-        // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
-        std::this_thread::sleep_for(std::chrono::milliseconds(200));
-
-        c = d.card(deck1.at(x));
-        c->set(cx + off_x, cy + off_y);
-        door << *c;
-      }
-
-    {
-      int cx, cy, level;
-      cardgo(active_card, cx, cy, level);
-      c = d.marker(1);
-      c->set(cx + off_x + 2, cy + off_y + 2);
-      door << *c;
-    }
-
-    dealing = false;
-
-    left_panel.update(door);
-    door << door::reset;
-
-    bool now_what = true;
-    while (now_what) {
-      // time might have updated, so update score panel too.
-      score_panel.update(door);
-
-      r = door.sleep_key(door.inactivity);
-      if (r > 0) {
-        // They didn't timeout/expire.  They didn't press a function key.
-        if (r < 0x1000)
-          r = std::toupper(r);
-        switch (r) {
-        case '\x0d':
-          if (card_number < 51) {
-            card_number++;
-            current_streak = 0;
-            streak_panel.update(door);
-
-            // Ok, deal next card from the pile.
-            int cx, cy, level;
-
-            if (card_number == 51) {
-              cardgo(29, cx, cy, level);
-              level = 0; // out of cards
-              c = d.back(level);
-              c->set(cx + off_x, cy + off_y);
-              door << *c;
-            }
-            cardgo(28, cx, cy, level);
-            c = d.card(deck1.at(card_number));
-            c->set(cx + off_x, cy + off_y);
-            door << *c;
-            // update the cards left_panel
-            left_panel.update(door);
-          }
-          break;
-        case 'R':
-          now_what = false;
-          break;
-        case 'Q':
-          now_what = false;
-          break;
-        case ' ':
-        case '5':
-          // can we play this card?
-          /*
-          get_logger() << "can_play( " << active_card << ":"
-                       << deck1.at(active_card) << "/"
-                       << d.is_rank(deck1.at(active_card)) << " , "
-                       << card_number << "/" << d.is_rank(deck1.at(card_number))
-                       << ") = "
-                       << d.can_play(deck1.at(active_card),
-                                     deck1.at(card_number))
-                       << std::endl;
-                       */
-
-          if (d.can_play(deck1.at(active_card), deck1.at(card_number))) {
-            // if (true) {
-            // yes we can.
-            ++current_streak;
-            if (current_streak > best_streak)
-              best_streak = current_streak;
-            streak_panel.update(door);
-            score += 10;
-            if (current_streak > 2)
-              score += 5;
-
-            // play card!
-            state.at(active_card) = 2;
-            {
-              // swap the active card with card_number (play card)
-              int temp = deck1.at(active_card);
-              deck1.at(active_card) = deck1.at(card_number);
-              deck1.at(card_number) = temp;
-              // active_card is -- invalidated here!  find "new" card.
-              int cx, cy, level;
-
-              // erase/clear active_card
-              std::vector<int> check = d.unblocks(active_card);
-              bool left = false, right = false;
-              for (const int c : check) {
-                std::pair<int, int> blockers = d.blocks[c];
-                if (blockers.first == active_card)
-                  right = true;
-                if (blockers.second == active_card)
-                  left = true;
-              }
-
-              d.remove_card(door, active_card, off_x, off_y, left, right);
-
-              /*   // old way of doing this that leaves holes.
-              cardgo(active_card, cx, cy, level);
-              c = d.back(0);
-              c->set(cx + off_x, cy + off_y);
-              door << *c;
-              */
-
-              // redraw play card #28. (Which is the "old" active_card)
-              cardgo(28, cx, cy, level);
-              c = d.card(deck1.at(card_number));
-              c->set(cx + off_x, cy + off_y);
-              door << *c;
-
-              // Did we unhide a card here?
-
-              // std::vector<int> check = d.unblocks(active_card);
-
-              /*
-              get_logger() << "active_card = " << active_card
-                           << " unblocks: " << check.size() << std::endl;
-              */
-              int new_card_shown = -1;
-              if (!check.empty()) {
-                for (const int to_check : check) {
-                  std::pair<int, int> blockers = d.blocks[to_check];
-                  /*
-                  get_logger()
-                      << "Check: " << to_check << " " << blockers.first << ","
-                      << blockers.second << " " << state.at(blockers.first)
-                      << "," << state.at(blockers.second) << std::endl;
-                      */
-                  if ((state.at(blockers.first) == 2) and
-                      (state.at(blockers.second) == 2)) {
-                    // WOOT!  Card uncovered.
-                    /*
-                    get_logger() << "showing: " << to_check << std::endl;
-                    */
-                    state.at(to_check) = 1;
-                    cardgo(to_check, cx, cy, level);
-                    c = d.card(deck1.at(to_check));
-                    c->set(cx + off_x, cy + off_y);
-                    door << *c;
-                    new_card_shown = to_check;
-                  }
-                }
-              } else {
-                // this would be a "top" card.  Should set status = 4 and
-                // display something here?
-                get_logger() << "top card cleared?" << std::endl;
-                // display something at active_card position
-                int cx, cy, level;
-                cardgo(active_card, cx, cy, level);
-                door << door::Goto(cx + off_x, cy + off_y) << door::reset
-                     << "BONUS";
-                score += 100;
-                state.at(active_card) = 3; // handle this in the "redraw"
-              }
-
-              // Find new "number" for active_card to be.
-              if (new_card_shown != -1) {
-                active_card = new_card_shown;
-              } else {
-                // active_card++;
-                int new_active = find_next_closest(state, active_card);
-
-                if (new_active != -1) {
-                  active_card = new_active;
-                } else {
-                  get_logger() << "This looks like END OF GAME." << std::endl;
-                  // bonus for cards left
-                  press_a_key(door);
-                  if (hand < total_hands) {
-                    hand++;
-                    door << door::reset << door::cls;
-                    goto next_hand;
-                  }
-                  r = 'Q';
-                  now_what = false;
-                }
-              }
-              // update the active_card marker!
-              cardgo(active_card, cx, cy, level);
-              c = d.marker(1);
-              c->set(cx + off_x + 2, cy + off_y + 2);
-              door << *c;
-            }
-          }
-          break;
-        case XKEY_LEFT_ARROW:
-        case '4': {
-          int new_active = find_next(true, state, active_card);
-          /*
-          int new_active = active_card - 1;
-          while (new_active >= 0) {
-            if (state.at(new_active) == 1)
-              break;
-            --new_active;
-          }*/
-          if (new_active >= 0) {
-
-            int cx, cy, level;
-            cardgo(active_card, cx, cy, level);
-            c = d.marker(0);
-            c->set(cx + off_x + 2, cy + off_y + 2);
-            door << *c;
-            active_card = new_active;
-            cardgo(active_card, cx, cy, level);
-            c = d.marker(1);
-            c->set(cx + off_x + 2, cy + off_y + 2);
-            door << *c;
-          }
-        } break;
-        case XKEY_RIGHT_ARROW:
-        case '6': {
-          int new_active = find_next(false, state, active_card);
-          /*
-          int new_active = active_card + 1;
-          while (new_active < 28) {
-            if (state.at(new_active) == 1)
-              break;
-            ++new_active;
-          }
-          */
-          if (new_active >= 0) { //(new_active < 28) {
-            int cx, cy, level;
-            cardgo(active_card, cx, cy, level);
-            c = d.marker(0);
-            c->set(cx + off_x + 2, cy + off_y + 2);
-            door << *c;
-            active_card = new_active;
-            cardgo(active_card, cx, cy, level);
-            c = d.marker(1);
-            c->set(cx + off_x + 2, cy + off_y + 2);
-            door << *c;
-          }
-        }
-
-        break;
-        }
-      } else
-        now_what = false;
-    }
-  }
-  if (r == 'Q') {
-    // continue with hand or quit?
-    if (hand < total_hands) {
-      press_a_key(door);
-      hand++;
-      door << door::reset << door::cls;
-      goto next_hand;
-    }
-  }
-  return r;
-}
-
 door::Panel make_about(void) {
   const int W = 60;
   door::Panel about(W);
@@ -1508,8 +767,11 @@ int main(int argc, char *argv[]) {
 
     switch (r) {
     case 1: // play game
-      r = play_cards(door, spacedb, rng);
-      break;
+    {
+      PlayCards pc(door, spacedb, rng);
+      r = pc.play_cards();
+      // r = play_cards(door, spacedb, rng);
+    }; break;
 
     case 2: // view scores
       door << "Show scores goes here!" << door::nl;

+ 1206 - 0
play.cpp

@@ -0,0 +1,1206 @@
+#include "play.h"
+#include "db.h"
+#include "deck.h"
+#include "version.h"
+
+#include <iomanip> // put_time
+#include <sstream>
+
+static std::function<std::ofstream &(void)> get_logger;
+
+static int press_a_key(door::Door &door) {
+  door << door::reset << "Press a key to continue...";
+  int r = door.sleep_key(door.inactivity);
+  door << door::nl;
+  return r;
+}
+
+/*
+In the future, this will probably check to see if they can play today or not, as
+well as displaying a calendar to show what days are available to be played.
+
+For now, it will play today.
+*/
+
+PlayCards::PlayCards(door::Door &d, DBData &dbd, std::mt19937 &r)
+    : door{d}, db{dbd}, rng{r} {
+
+  init_values();
+
+  spaceAceTriPeaks = make_tripeaks();
+  score_panel = make_score_panel();
+  streak_panel = make_streak_panel();
+  left_panel = make_left_panel();
+  cmd_panel = make_command_panel();
+
+  int mx = door.width;
+  int my = door.height;
+
+  play_day = std::chrono::system_clock::now();
+  get_logger = [this]() -> ofstream & { return door.log(); };
+}
+
+PlayCards::~PlayCards() { get_logger = nullptr; }
+
+void PlayCards::init_values(void) {
+  hand = 1;
+  total_hands = 3;
+  card_number = 28;
+  current_streak = 0;
+  best_streak = 0;
+  active_card = 23;
+  score = 0;
+}
+
+int PlayCards::play_cards(void) {
+  play_day = std::chrono::system_clock::now();
+  init_values();
+  std::string currentDefault = db.getSetting("DeckColor", "ALL");
+  get_logger() << "DeckColor shows as " << currentDefault << std::endl;
+  deck_color = from_string(currentDefault);
+
+  dp = Deck(deck_color);
+
+  // Calculate the game size
+  int game_width;
+  int game_height = 20;
+  {
+    int cx, cy, level;
+    cardgo(27, cx, cy, level);
+    game_width = cx + 5;
+  }
+
+  int mx = door.width;
+  int my = door.height;
+
+  off_x = (mx - game_width) / 2;
+  off_y = (my - game_height) / 2;
+  int true_off_y = off_y;
+
+  // we can now position things properly centered
+
+  int tp_off_x = (mx - spaceAceTriPeaks->getWidth()) / 2;
+  spaceAceTriPeaks->set(tp_off_x, off_y);
+  off_y += 3; // adjust for tripeaks panel
+
+next_hand:
+  card_number = 28;
+  active_card = 23;
+  score = 0;
+
+  // Use play_day to seed the rng
+  {
+    time_t tt = std::chrono::system_clock::to_time_t(play_day);
+    tm local_tm = *localtime(&tt);
+
+    std::seed_seq seq{local_tm.tm_year + 1900, local_tm.tm_mon + 1,
+                      local_tm.tm_mday, hand};
+    deck = card_shuffle(seq, 1);
+    state = card_states();
+  }
+
+  /*
+    door::Panel score_panel = make_score_panel();
+    door::Panel streak_panel = make_streak_panel();
+    door::Panel left_panel = make_left_panel();
+    door::Panel cmd_panel = make_command_panel();
+  */
+
+  {
+    int off_yp = off_y + 11;
+    int cxp, cyp, levelp;
+    int left_panel_x, right_panel_x;
+    // find position of card, to position the panels
+    cardgo(18, cxp, cyp, levelp);
+    left_panel_x = cxp;
+    cardgo(15, cxp, cyp, levelp);
+    right_panel_x = cxp;
+    score_panel->set(left_panel_x + off_x, off_yp);
+    streak_panel->set(right_panel_x + off_x, off_yp);
+    cmd_panel->set(left_panel_x + off_x, off_yp + 5);
+  }
+
+  bool dealing = true;
+  int r = 0;
+
+  redraw(dealing);
+
+  dealing = false;
+
+  left_panel->update(door);
+  door << door::reset;
+
+  door::Panel *c;
+
+  bool in_game = true;
+  while (in_game) {
+    // time might have updated, so update score panel too.
+    score_panel->update(door);
+
+    r = door.sleep_key(door.inactivity);
+    if (r > 0) {
+      // not a timeout or expire.
+      if (r < 0x1000) // not a function key
+        r = std::toupper(r);
+      switch (r) {
+      case '\x0d':
+        if (card_number < 51) {
+          card_number++;
+          current_streak = 0;
+          streak_panel->update(door);
+
+          // Ok, deal next card from the pile.
+          int cx, cy, level;
+
+          if (card_number == 51) {
+            cardgo(29, cx, cy, level);
+            level = 0; // out of cards
+            c = dp.back(level);
+            c->set(cx + off_x, cy + off_y);
+            door << *c;
+          }
+          cardgo(28, cx, cy, level);
+          c = dp.card(deck.at(card_number));
+          c->set(cx + off_x, cy + off_y);
+          door << *c;
+          // update the cards left_panel
+          left_panel->update(door);
+        }
+        break;
+      case 'R':
+        // now_what = false;
+        redraw(false);
+        break;
+      case 'Q':
+        in_game = false;
+        break;
+      case ' ':
+      case '5':
+        // can we play this card?
+        /*
+        get_logger() << "can_play( " << active_card << ":"
+                     << deck1.at(active_card) << "/"
+                     << d.is_rank(deck1.at(active_card)) << " , "
+                     << card_number << "/" <<
+        d.is_rank(deck1.at(card_number))
+                     << ") = "
+                     << d.can_play(deck1.at(active_card),
+                                   deck1.at(card_number))
+                     << std::endl;
+                     */
+
+        if (dp.can_play(deck.at(active_card), deck.at(card_number))) {
+          // if (true) {
+          // yes we can.
+          ++current_streak;
+          if (current_streak > best_streak)
+            best_streak = current_streak;
+          streak_panel->update(door);
+          score += 10;
+          if (current_streak > 2)
+            score += 5;
+
+          // play card!
+          state.at(active_card) = 2;
+          {
+            // swap the active card with card_number (play card)
+            int temp = deck.at(active_card);
+            deck.at(active_card) = deck.at(card_number);
+            deck.at(card_number) = temp;
+            // active_card is -- invalidated here!  find "new" card.
+            int cx, cy, level;
+
+            // erase/clear active_card
+            std::vector<int> check = dp.unblocks(active_card);
+            bool left = false, right = false;
+            for (const int c : check) {
+              std::pair<int, int> blockers = dp.blocks[c];
+              if (blockers.first == active_card)
+                right = true;
+              if (blockers.second == active_card)
+                left = true;
+            }
+
+            dp.remove_card(door, active_card, off_x, off_y, left, right);
+
+            /*   // old way of doing this that leaves holes.
+            cardgo(active_card, cx, cy, level);
+            c = d.back(0);
+            c->set(cx + off_x, cy + off_y);
+            door << *c;
+            */
+
+            // redraw play card #28. (Which is the "old" active_card)
+            cardgo(28, cx, cy, level);
+            c = dp.card(deck.at(card_number));
+            c->set(cx + off_x, cy + off_y);
+            door << *c;
+
+            // Did we unhide a card here?
+
+            // std::vector<int> check = d.unblocks(active_card);
+
+            /*
+            get_logger() << "active_card = " << active_card
+                         << " unblocks: " << check.size() << std::endl;
+            */
+            int new_card_shown = -1;
+            if (!check.empty()) {
+              for (const int to_check : check) {
+                std::pair<int, int> blockers = dp.blocks[to_check];
+                /*
+                get_logger()
+                    << "Check: " << to_check << " " << blockers.first << ","
+                    << blockers.second << " " << state.at(blockers.first)
+                    << "," << state.at(blockers.second) << std::endl;
+                    */
+                if ((state.at(blockers.first) == 2) and
+                    (state.at(blockers.second) == 2)) {
+                  // WOOT!  Card uncovered.
+                  /*
+                  get_logger() << "showing: " << to_check << std::endl;
+                  */
+                  state.at(to_check) = 1;
+                  cardgo(to_check, cx, cy, level);
+                  c = dp.card(deck.at(to_check));
+                  c->set(cx + off_x, cy + off_y);
+                  door << *c;
+                  new_card_shown = to_check;
+                }
+              }
+            } else {
+              // this would be a "top" card.  Should set status = 4 and
+              // display something here?
+              get_logger() << "top card cleared?" << std::endl;
+              // display something at active_card position
+              int cx, cy, level;
+              cardgo(active_card, cx, cy, level);
+              door << door::Goto(cx + off_x, cy + off_y) << door::reset
+                   << "BONUS";
+              score += 100;
+              state.at(active_card) = 3; // handle this in the "redraw"
+            }
+
+            // Find new "number" for active_card to be.
+            if (new_card_shown != -1) {
+              active_card = new_card_shown;
+            } else {
+              // active_card++;
+              int new_active = find_next_closest(state, active_card);
+
+              if (new_active != -1) {
+                active_card = new_active;
+              } else {
+                get_logger() << "This looks like END OF GAME." << std::endl;
+                // bonus for cards left
+                press_a_key(door);
+                if (hand < total_hands) {
+                  hand++;
+                  door << door::reset << door::cls;
+                  goto next_hand;
+                }
+                r = 'Q';
+                in_game = false;
+              }
+            }
+            // update the active_card marker!
+            cardgo(active_card, cx, cy, level);
+            c = dp.marker(1);
+            c->set(cx + off_x + 2, cy + off_y + 2);
+            door << *c;
+          }
+        }
+        break;
+      case XKEY_LEFT_ARROW:
+      case '4': {
+        int new_active = find_next(true, state, active_card);
+        /*
+        int new_active = active_card - 1;
+        while (new_active >= 0) {
+          if (state.at(new_active) == 1)
+            break;
+          --new_active;
+        }*/
+        if (new_active >= 0) {
+
+          int cx, cy, level;
+          cardgo(active_card, cx, cy, level);
+          c = dp.marker(0);
+          c->set(cx + off_x + 2, cy + off_y + 2);
+          door << *c;
+          active_card = new_active;
+          cardgo(active_card, cx, cy, level);
+          c = dp.marker(1);
+          c->set(cx + off_x + 2, cy + off_y + 2);
+          door << *c;
+        }
+      } break;
+      case XKEY_RIGHT_ARROW:
+      case '6': {
+        int new_active = find_next(false, state, active_card);
+        /*
+        int new_active = active_card + 1;
+        while (new_active < 28) {
+          if (state.at(new_active) == 1)
+            break;
+          ++new_active;
+        }
+        */
+        if (new_active >= 0) { //(new_active < 28) {
+          int cx, cy, level;
+          cardgo(active_card, cx, cy, level);
+          c = dp.marker(0);
+          c->set(cx + off_x + 2, cy + off_y + 2);
+          door << *c;
+          active_card = new_active;
+          cardgo(active_card, cx, cy, level);
+          c = dp.marker(1);
+          c->set(cx + off_x + 2, cy + off_y + 2);
+          door << *c;
+        }
+      }
+
+      break;
+      }
+    } else
+      in_game = false;
+  }
+
+  if (r == 'Q') {
+    // continue with hand, or quit?
+  }
+
+  return r;
+}
+
+void PlayCards::redraw(bool dealing) {
+  door::Panel *c;
+
+  door << door::reset << door::cls;
+  door << *spaceAceTriPeaks;
+
+  {
+    // step 1:
+    // draw the deck "source"
+    int cx, cy, level;
+    cardgo(29, cx, cy, level);
+
+    if (card_number == 51)
+      level = 0; // out of cards!
+    c = dp.back(level);
+    c->set(cx + off_x, cy + off_y);
+    // p3 is heigh below
+    left_panel->set(cx + off_x, cy + off_y + height);
+
+    // how do I update these? (hand >1)
+    score_panel->update();
+    left_panel->update();
+    streak_panel->update();
+    cmd_panel->update();
+
+    door << *score_panel << *left_panel << *streak_panel << *cmd_panel;
+    door << *c;
+    if (dealing)
+      std::this_thread::sleep_for(std::chrono::seconds(1));
+  }
+
+  for (int x = 0; x < (dealing ? 28 : 29); x++) {
+    int cx, cy, level;
+
+    cardgo(x, cx, cy, level);
+    // This is hardly visible.
+    // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
+    if (dealing)
+      std::this_thread::sleep_for(std::chrono::milliseconds(75));
+
+    if (dealing) {
+      c = dp.back(level);
+      c->set(cx + off_x, cy + off_y);
+      door << *c;
+    } else {
+      // redrawing -- draw the cards with their correct "state"
+      int s = state.at(x);
+
+      switch (s) {
+      case 0:
+        c = dp.back(level);
+        c->set(cx + off_x, cy + off_y);
+        door << *c;
+        break;
+      case 1:
+        // cardgo(x, space, height, cx, cy, level);
+        if (x == 28)
+          c = dp.card(deck.at(card_number));
+        else
+          c = dp.card(deck.at(x));
+        c->set(cx + off_x, cy + off_y);
+        door << *c;
+        break;
+      case 2:
+        // no card to draw.  :)
+        break;
+      }
+    }
+  }
+
+  if (dealing)
+    for (int x = 18; x < 29; x++) {
+      int cx, cy, level;
+
+      state.at(x) = 1;
+      cardgo(x, cx, cy, level);
+      // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
+      std::this_thread::sleep_for(std::chrono::milliseconds(200));
+
+      c = dp.card(deck.at(x));
+      c->set(cx + off_x, cy + off_y);
+      door << *c;
+    }
+
+  {
+    int cx, cy, level;
+    cardgo(active_card, cx, cy, level);
+    c = dp.marker(1);
+    c->set(cx + off_x + 2, cy + off_y + 2);
+    door << *c;
+  }
+}
+
+door::renderFunction PlayCards::statusValue(door::ANSIColor status,
+                                            door::ANSIColor value) {
+  door::renderFunction rf = [status,
+                             value](const std::string &txt) -> door::Render {
+    door::Render r(txt);
+    door::ColorOutput co;
+
+    co.pos = 0;
+    co.len = 0;
+    co.c = status;
+
+    size_t pos = txt.find(':');
+    if (pos == std::string::npos) {
+      // failed to find :, render digits/numbers in value color
+      int tpos = 0;
+      for (char const &c : txt) {
+        if (std::isdigit(c)) {
+          if (co.c != value) {
+            r.outputs.push_back(co);
+            co.reset();
+            co.pos = tpos;
+            co.c = value;
+          }
+        } else {
+          if (co.c != status) {
+            r.outputs.push_back(co);
+            co.reset();
+            co.pos = tpos;
+            co.c = status;
+          }
+        }
+        co.len++;
+        tpos++;
+      }
+      if (co.len != 0)
+        r.outputs.push_back(co);
+    } else {
+      pos++; // Have : in status color
+      co.len = pos;
+      r.outputs.push_back(co);
+      co.reset();
+      co.pos = pos;
+      co.c = value;
+      co.len = txt.length() - pos;
+      r.outputs.push_back(co);
+    }
+
+    return r;
+  };
+  return rf;
+}
+
+std::unique_ptr<door::Panel> PlayCards::make_score_panel() {
+  const int W = 25;
+  std::unique_ptr<door::Panel> p = std::make_unique<door::Panel>(W);
+  p->setStyle(door::BorderStyle::NONE);
+  door::ANSIColor statusColor(door::COLOR::WHITE, door::COLOR::BLUE,
+                              door::ATTR::BOLD);
+  door::ANSIColor valueColor(door::COLOR::YELLOW, door::COLOR::BLUE,
+                             door::ATTR::BOLD);
+  door::renderFunction svRender = statusValue(statusColor, valueColor);
+  // or use renderStatus as defined above.
+  // We'll stick with these for now.
+
+  {
+    std::string userString = "Name: ";
+    userString += door.username;
+    door::Line username(userString, W);
+    username.setRender(svRender);
+    p->addLine(std::make_unique<door::Line>(username));
+  }
+  {
+    door::updateFunction scoreUpdate = [this](void) -> std::string {
+      std::string text = "Score: ";
+      text.append(std::to_string(score));
+      return text;
+    };
+    std::string scoreString = scoreUpdate();
+    door::Line score(scoreString, W);
+    score.setRender(svRender);
+    score.setUpdater(scoreUpdate);
+    p->addLine(std::make_unique<door::Line>(score));
+  }
+  {
+    door::updateFunction timeUpdate = [this](void) -> std::string {
+      std::stringstream ss;
+      std::string text;
+      ss << "Time used: " << setw(3) << door.time_used << " / " << setw(3)
+         << door.time_left;
+      text = ss.str();
+      return text;
+    };
+    std::string timeString = timeUpdate();
+    door::Line time(timeString, W);
+    time.setRender(svRender);
+    time.setUpdater(timeUpdate);
+    p->addLine(std::make_unique<door::Line>(time));
+  }
+  {
+    door::updateFunction handUpdate = [this](void) -> std::string {
+      std::string text = "Playing Hand ";
+      text.append(std::to_string(hand));
+      text.append(" of ");
+      text.append(std::to_string(total_hands));
+      return text;
+    };
+    std::string handString = handUpdate();
+    door::Line hands(handString, W);
+    hands.setRender(svRender);
+    hands.setUpdater(handUpdate);
+    p->addLine(std::make_unique<door::Line>(hands));
+  }
+
+  return p;
+}
+
+std::unique_ptr<door::Panel> PlayCards::make_streak_panel(void) {
+  const int W = 20;
+  std::unique_ptr<door::Panel> p = std::make_unique<door::Panel>(W);
+  p->setStyle(door::BorderStyle::NONE);
+  door::ANSIColor statusColor(door::COLOR::WHITE, door::COLOR::BLUE,
+                              door::ATTR::BOLD);
+  door::ANSIColor valueColor(door::COLOR::YELLOW, door::COLOR::BLUE,
+                             door::ATTR::BOLD);
+  door::renderFunction svRender = statusValue(statusColor, valueColor);
+
+  {
+    std::string text = "Playing: ";
+    auto in_time_t = std::chrono::system_clock::to_time_t(play_day);
+    std::stringstream ss;
+    ss << std::put_time(std::localtime(&in_time_t), "%B %d");
+    text.append(ss.str());
+    door::Line current(text, W);
+    current.setRender(svRender);
+    p->addLine(std::make_unique<door::Line>(current));
+  }
+  {
+    door::updateFunction currentUpdate = [this](void) -> std::string {
+      std::string text = "Current Streak: ";
+      text.append(std::to_string(current_streak));
+      return text;
+    };
+    std::string currentString = currentUpdate();
+    door::Line current(currentString, W);
+    current.setRender(svRender);
+    current.setUpdater(currentUpdate);
+    p->addLine(std::make_unique<door::Line>(current));
+  }
+  {
+    door::updateFunction currentUpdate = [this](void) -> std::string {
+      std::string text = "Longest Streak: ";
+      text.append(std::to_string(best_streak));
+      return text;
+    };
+    std::string currentString = currentUpdate();
+    door::Line current(currentString, W);
+    current.setRender(svRender);
+    current.setUpdater(currentUpdate);
+    p->addLine(std::make_unique<door::Line>(current));
+  }
+
+  return p;
+}
+
+std::unique_ptr<door::Panel> PlayCards::make_left_panel(void) {
+  const int W = 13;
+  std::unique_ptr<door::Panel> p = std::make_unique<door::Panel>(W);
+  p->setStyle(door::BorderStyle::NONE);
+  door::ANSIColor statusColor(door::COLOR::WHITE, door::COLOR::BLUE,
+                              door::ATTR::BOLD);
+  door::ANSIColor valueColor(door::COLOR::YELLOW, door::COLOR::BLUE,
+                             door::ATTR::BOLD);
+  door::renderFunction svRender = statusValue(statusColor, valueColor);
+
+  {
+    door::updateFunction cardsleftUpdate = [this](void) -> std::string {
+      std::string text = "Cards left:";
+      text.append(std::to_string(51 - card_number));
+      return text;
+    };
+    std::string cardsleftString = "Cards left:--";
+    door::Line cardsleft(cardsleftString, W);
+    cardsleft.setRender(svRender);
+    cardsleft.setUpdater(cardsleftUpdate);
+    p->addLine(std::make_unique<door::Line>(cardsleft));
+  }
+  return p;
+}
+
+door::renderFunction PlayCards::commandLineRender(door::ANSIColor bracket,
+                                                  door::ANSIColor inner,
+                                                  door::ANSIColor outer) {
+  door::renderFunction rf = [bracket, inner,
+                             outer](const std::string &txt) -> door::Render {
+    door::Render r(txt);
+    door::ColorOutput co;
+
+    co.pos = 0;
+    co.len = 0;
+    co.c = outer;
+    bool inOuter = true;
+
+    int tpos = 0;
+    for (char const &c : txt) {
+      if (inOuter) {
+
+        // we're in the outer text
+        if (co.c != outer) {
+          if (co.len != 0) {
+            r.outputs.push_back(co);
+            co.reset();
+            co.pos = tpos;
+          }
+          co.c = outer;
+        }
+
+        // check for [
+        if (c == '[') {
+          if (co.len != 0) {
+            r.outputs.push_back(co);
+            co.reset();
+            co.pos = tpos;
+          }
+          inOuter = false;
+          co.c = bracket;
+        }
+      } else {
+        // We're not in the outer.
+
+        if (co.c != inner) {
+          if (co.len != 0) {
+            r.outputs.push_back(co);
+            co.reset();
+            co.pos = tpos;
+          }
+          co.c = inner;
+        }
+
+        if (c == ']') {
+          if (co.len != 0) {
+            r.outputs.push_back(co);
+            co.reset();
+            co.pos = tpos;
+          }
+          inOuter = true;
+          co.c = bracket;
+        }
+      }
+      co.len++;
+      tpos++;
+    }
+    if (co.len != 0)
+      r.outputs.push_back(co);
+    return r;
+  };
+  return rf;
+}
+
+std::unique_ptr<door::Panel> PlayCards::make_command_panel(void) {
+  const int W = 76;
+  std::unique_ptr<door::Panel> p = make_unique<door::Panel>(W);
+  p->setStyle(door::BorderStyle::NONE);
+  std::string commands;
+
+  if (door::unicode) {
+    commands = "[4/\u25c4] Left [6/\u25ba] Right [Space] Play Card [Enter] "
+               "Draw [Q]uit "
+               "[R]edraw [H]elp";
+  } else {
+    commands =
+        "[4/\x11] Left [6/\x10] Right [Space] Play Card [Enter] Draw [Q]uit "
+        "[R]edraw [H]elp";
+  }
+
+  door::ANSIColor bracketColor(door::COLOR::YELLOW, door::COLOR::BLUE,
+                               door::ATTR::BOLD);
+  door::ANSIColor innerColor(door::COLOR::CYAN, door::COLOR::BLUE,
+                             door::ATTR::BOLD);
+  door::ANSIColor outerColor(door::COLOR::GREEN, door::COLOR::BLUE,
+                             door::ATTR::BOLD);
+
+  door::renderFunction cmdRender =
+      commandLineRender(bracketColor, innerColor, outerColor);
+  door::Line cmd(commands, W);
+  cmd.setRender(cmdRender);
+  p->addLine(std::make_unique<door::Line>(cmd));
+  return p;
+}
+
+std::unique_ptr<door::Panel> PlayCards::make_tripeaks(void) {
+  std::string tripeaksText(" " SPACEACE
+                           " - Tri-Peaks Solitaire v" SPACEACE_VERSION " ");
+  std::unique_ptr<door::Panel> spaceAceTriPeaks =
+      std::make_unique<door::Panel>(tripeaksText.size());
+  spaceAceTriPeaks->setStyle(door::BorderStyle::SINGLE);
+  spaceAceTriPeaks->setColor(
+      door::ANSIColor(door::COLOR::CYAN, door::COLOR::BLACK));
+  spaceAceTriPeaks->addLine(
+      std::make_unique<door::Line>(tripeaksText, tripeaksText.size()));
+  return spaceAceTriPeaks;
+}
+
+#ifdef OLD_WAY
+
+int play_cards(door::Door &door, DBData &db, std::mt19937 &rng) {
+  int mx = door.width;
+  int my = door.height;
+
+  // init these values:
+  card_number = 28;
+  active_card = 23;
+  hand = 1;
+  score = 0;
+  play_day = std::chrono::system_clock::now();
+
+  // cards color --
+  // configured by the player.
+
+  door::ANSIColor deck_color;
+  // std::string key("DeckColor");
+  const char *key = "DeckColor";
+  std::string currentDefault = db.getSetting(key, "ALL");
+  door.log() << key << " shows as " << currentDefault << std::endl;
+  deck_color = from_string(currentDefault);
+
+  const int height = 3;
+  Deck d(deck_color); // , height);
+  door::Panel *c;
+
+  // This displays the cards in the upper left corner.
+  // We want them center, and down some.
+
+  // const int space = 3;
+
+  // int cards_dealt_width = 59; int cards_dealt_height = 9;
+  int game_width;
+  int game_height = 20; // 13; // 9;
+  {
+    int cx, cy, level;
+    cardgo(27, cx, cy, level);
+    game_width = cx + 5; // card width
+  }
+  int off_x = (mx - game_width) / 2;
+  int off_y = (my - game_height) / 2;
+  int true_off_y = off_y;
+  // The idea is to see the cards with <<Something Unique to the card game>>,
+  // Year, Month, Day, and game (like 1 of 3).
+  // This will make the games the same/fair for everyone.
+
+next_hand:
+
+  card_number = 28;
+  active_card = 23;
+  score = 0;
+  off_y = true_off_y;
+
+  door::Panel spaceAceTriPeaks = make_tripeaks();
+  int tp_off_x = (mx - spaceAceTriPeaks.getWidth()) / 2;
+  spaceAceTriPeaks.set(tp_off_x, off_y);
+
+  off_y += 3;
+
+  // figure out what date we're playing / what game we're playing
+  time_t tt = std::chrono::system_clock::to_time_t(play_day);
+  tm local_tm = *localtime(&tt);
+  /*
+  std::cout << utc_tm.tm_year + 1900 << '-';
+  std::cout << utc_tm.tm_mon + 1 << '-';
+  std::cout << utc_tm.tm_mday << ' ';
+  std::cout << utc_tm.tm_hour << ':';
+  std::cout << utc_tm.tm_min << ':';
+  std::cout << utc_tm.tm_sec << '\n';
+  */
+
+  std::seed_seq s1{local_tm.tm_year + 1900, local_tm.tm_mon + 1,
+                   local_tm.tm_mday, hand};
+  cards deck1 = card_shuffle(s1, 1);
+  cards state = card_states();
+
+  door::Panel score_panel = make_score_panel(door);
+  door::Panel streak_panel = make_streak_panel();
+  door::Panel left_panel = make_left_panel();
+  door::Panel cmd_panel = make_command_panel();
+
+  {
+    int off_yp = off_y + 11;
+    int cxp, cyp, levelp;
+    int left_panel_x, right_panel_x;
+    // find position of card, to position the panels
+    cardgo(18, cxp, cyp, levelp);
+    left_panel_x = cxp;
+    cardgo(15, cxp, cyp, levelp);
+    right_panel_x = cxp;
+    score_panel.set(left_panel_x + off_x, off_yp);
+    streak_panel.set(right_panel_x + off_x, off_yp);
+    cmd_panel.set(left_panel_x + off_x, off_yp + 5);
+  }
+
+  bool dealing = true;
+  int r = 0;
+
+  while ((r >= 0) and (r != 'Q')) {
+    // REDRAW everything
+
+    door << door::reset << door::cls;
+    door << spaceAceTriPeaks;
+
+    {
+      // step 1:
+      // draw the deck "source"
+      int cx, cy, level;
+      cardgo(29, cx, cy, level);
+
+      if (card_number == 51)
+        level = 0; // out of cards!
+      c = d.back(level);
+      c->set(cx + off_x, cy + off_y);
+      // p3 is heigh below
+      left_panel.set(cx + off_x, cy + off_y + height);
+      door << score_panel << left_panel << streak_panel << cmd_panel;
+      door << *c;
+      if (dealing)
+        std::this_thread::sleep_for(std::chrono::seconds(1));
+    }
+
+    // I tried setting the cursor before the delay and before displaying the
+    // card. It is very hard to see / just about useless.  Not worth the effort.
+
+    for (int x = 0; x < (dealing ? 28 : 29); x++) {
+      int cx, cy, level;
+
+      cardgo(x, cx, cy, level);
+      // This is hardly visible.
+      // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
+      if (dealing)
+        std::this_thread::sleep_for(std::chrono::milliseconds(75));
+
+      if (dealing) {
+        c = d.back(level);
+        c->set(cx + off_x, cy + off_y);
+        door << *c;
+      } else {
+        // redrawing -- draw the cards with their correct "state"
+        int s = state.at(x);
+
+        switch (s) {
+        case 0:
+          c = d.back(level);
+          c->set(cx + off_x, cy + off_y);
+          door << *c;
+          break;
+        case 1:
+          // cardgo(x, space, height, cx, cy, level);
+          if (x == 28)
+            c = d.card(deck1.at(card_number));
+          else
+            c = d.card(deck1.at(x));
+          c->set(cx + off_x, cy + off_y);
+          door << *c;
+          break;
+        case 2:
+          // no card to draw.  :)
+          break;
+        }
+      }
+    }
+
+    if (dealing)
+      for (int x = 18; x < 29; x++) {
+        int cx, cy, level;
+
+        state.at(x) = 1;
+        cardgo(x, cx, cy, level);
+        // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
+        std::this_thread::sleep_for(std::chrono::milliseconds(200));
+
+        c = d.card(deck1.at(x));
+        c->set(cx + off_x, cy + off_y);
+        door << *c;
+      }
+
+    {
+      int cx, cy, level;
+      cardgo(active_card, cx, cy, level);
+      c = d.marker(1);
+      c->set(cx + off_x + 2, cy + off_y + 2);
+      door << *c;
+    }
+
+    dealing = false;
+
+    left_panel.update(door);
+    door << door::reset;
+
+    bool now_what = true;
+    while (now_what) {
+      // time might have updated, so update score panel too.
+      score_panel.update(door);
+
+      r = door.sleep_key(door.inactivity);
+      if (r > 0) {
+        // They didn't timeout/expire.  They didn't press a function key.
+        if (r < 0x1000)
+          r = std::toupper(r);
+        switch (r) {
+        case '\x0d':
+          if (card_number < 51) {
+            card_number++;
+            current_streak = 0;
+            streak_panel.update(door);
+
+            // Ok, deal next card from the pile.
+            int cx, cy, level;
+
+            if (card_number == 51) {
+              cardgo(29, cx, cy, level);
+              level = 0; // out of cards
+              c = d.back(level);
+              c->set(cx + off_x, cy + off_y);
+              door << *c;
+            }
+            cardgo(28, cx, cy, level);
+            c = d.card(deck1.at(card_number));
+            c->set(cx + off_x, cy + off_y);
+            door << *c;
+            // update the cards left_panel
+            left_panel.update(door);
+          }
+          break;
+        case 'R':
+          now_what = false;
+          break;
+        case 'Q':
+          now_what = false;
+          break;
+        case ' ':
+        case '5':
+          // can we play this card?
+          /*
+          get_logger() << "can_play( " << active_card << ":"
+                       << deck1.at(active_card) << "/"
+                       << d.is_rank(deck1.at(active_card)) << " , "
+                       << card_number << "/" << d.is_rank(deck1.at(card_number))
+                       << ") = "
+                       << d.can_play(deck1.at(active_card),
+                                     deck1.at(card_number))
+                       << std::endl;
+                       */
+
+          if (d.can_play(deck1.at(active_card), deck1.at(card_number))) {
+            // if (true) {
+            // yes we can.
+            ++current_streak;
+            if (current_streak > best_streak)
+              best_streak = current_streak;
+            streak_panel.update(door);
+            score += 10;
+            if (current_streak > 2)
+              score += 5;
+
+            // play card!
+            state.at(active_card) = 2;
+            {
+              // swap the active card with card_number (play card)
+              int temp = deck1.at(active_card);
+              deck1.at(active_card) = deck1.at(card_number);
+              deck1.at(card_number) = temp;
+              // active_card is -- invalidated here!  find "new" card.
+              int cx, cy, level;
+
+              // erase/clear active_card
+              std::vector<int> check = d.unblocks(active_card);
+              bool left = false, right = false;
+              for (const int c : check) {
+                std::pair<int, int> blockers = d.blocks[c];
+                if (blockers.first == active_card)
+                  right = true;
+                if (blockers.second == active_card)
+                  left = true;
+              }
+
+              d.remove_card(door, active_card, off_x, off_y, left, right);
+
+              /*   // old way of doing this that leaves holes.
+              cardgo(active_card, cx, cy, level);
+              c = d.back(0);
+              c->set(cx + off_x, cy + off_y);
+              door << *c;
+              */
+
+              // redraw play card #28. (Which is the "old" active_card)
+              cardgo(28, cx, cy, level);
+              c = d.card(deck1.at(card_number));
+              c->set(cx + off_x, cy + off_y);
+              door << *c;
+
+              // Did we unhide a card here?
+
+              // std::vector<int> check = d.unblocks(active_card);
+
+              /*
+              get_logger() << "active_card = " << active_card
+                           << " unblocks: " << check.size() << std::endl;
+              */
+              int new_card_shown = -1;
+              if (!check.empty()) {
+                for (const int to_check : check) {
+                  std::pair<int, int> blockers = d.blocks[to_check];
+                  /*
+                  get_logger()
+                      << "Check: " << to_check << " " << blockers.first << ","
+                      << blockers.second << " " << state.at(blockers.first)
+                      << "," << state.at(blockers.second) << std::endl;
+                      */
+                  if ((state.at(blockers.first) == 2) and
+                      (state.at(blockers.second) == 2)) {
+                    // WOOT!  Card uncovered.
+                    /*
+                    get_logger() << "showing: " << to_check << std::endl;
+                    */
+                    state.at(to_check) = 1;
+                    cardgo(to_check, cx, cy, level);
+                    c = d.card(deck1.at(to_check));
+                    c->set(cx + off_x, cy + off_y);
+                    door << *c;
+                    new_card_shown = to_check;
+                  }
+                }
+              } else {
+                // this would be a "top" card.  Should set status = 4 and
+                // display something here?
+                get_logger() << "top card cleared?" << std::endl;
+                // display something at active_card position
+                int cx, cy, level;
+                cardgo(active_card, cx, cy, level);
+                door << door::Goto(cx + off_x, cy + off_y) << door::reset
+                     << "BONUS";
+                score += 100;
+                state.at(active_card) = 3; // handle this in the "redraw"
+              }
+
+              // Find new "number" for active_card to be.
+              if (new_card_shown != -1) {
+                active_card = new_card_shown;
+              } else {
+                // active_card++;
+                int new_active = find_next_closest(state, active_card);
+
+                if (new_active != -1) {
+                  active_card = new_active;
+                } else {
+                  get_logger() << "This looks like END OF GAME." << std::endl;
+                  // bonus for cards left
+                  press_a_key(door);
+                  if (hand < total_hands) {
+                    hand++;
+                    door << door::reset << door::cls;
+                    goto next_hand;
+                  }
+                  r = 'Q';
+                  now_what = false;
+                }
+              }
+              // update the active_card marker!
+              cardgo(active_card, cx, cy, level);
+              c = d.marker(1);
+              c->set(cx + off_x + 2, cy + off_y + 2);
+              door << *c;
+            }
+          }
+          break;
+        case XKEY_LEFT_ARROW:
+        case '4': {
+          int new_active = find_next(true, state, active_card);
+          /*
+          int new_active = active_card - 1;
+          while (new_active >= 0) {
+            if (state.at(new_active) == 1)
+              break;
+            --new_active;
+          }*/
+          if (new_active >= 0) {
+
+            int cx, cy, level;
+            cardgo(active_card, cx, cy, level);
+            c = d.marker(0);
+            c->set(cx + off_x + 2, cy + off_y + 2);
+            door << *c;
+            active_card = new_active;
+            cardgo(active_card, cx, cy, level);
+            c = d.marker(1);
+            c->set(cx + off_x + 2, cy + off_y + 2);
+            door << *c;
+          }
+        } break;
+        case XKEY_RIGHT_ARROW:
+        case '6': {
+          int new_active = find_next(false, state, active_card);
+          /*
+          int new_active = active_card + 1;
+          while (new_active < 28) {
+            if (state.at(new_active) == 1)
+              break;
+            ++new_active;
+          }
+          */
+          if (new_active >= 0) { //(new_active < 28) {
+            int cx, cy, level;
+            cardgo(active_card, cx, cy, level);
+            c = d.marker(0);
+            c->set(cx + off_x + 2, cy + off_y + 2);
+            door << *c;
+            active_card = new_active;
+            cardgo(active_card, cx, cy, level);
+            c = d.marker(1);
+            c->set(cx + off_x + 2, cy + off_y + 2);
+            door << *c;
+          }
+        }
+
+        break;
+        }
+      } else
+        now_what = false;
+    }
+  }
+  if (r == 'Q') {
+    // continue with hand or quit?
+    if (hand < total_hands) {
+      press_a_key(door);
+      hand++;
+      door << door::reset << door::cls;
+      goto next_hand;
+    }
+  }
+  return r;
+}
+#endif

+ 63 - 0
play.h

@@ -0,0 +1,63 @@
+#ifndef PLAY_H
+#define PLAY_H
+
+#include "db.h"
+#include "deck.h"
+#include "door.h"
+
+#include <chrono>
+#include <random>
+
+class PlayCards {
+private:
+  door::Door &door;
+  DBData &db;
+  std::mt19937 &rng;
+
+  std::unique_ptr<door::Panel> spaceAceTriPeaks;
+  std::unique_ptr<door::Panel> score_panel;
+  std::unique_ptr<door::Panel> streak_panel;
+  std::unique_ptr<door::Panel> left_panel;
+  std::unique_ptr<door::Panel> cmd_panel;
+
+  std::unique_ptr<door::Panel> make_score_panel();
+  std::unique_ptr<door::Panel> make_tripeaks(void);
+  std::unique_ptr<door::Panel> make_command_panel(void);
+  std::unique_ptr<door::Panel> make_streak_panel(void);
+  std::unique_ptr<door::Panel> make_left_panel(void);
+
+  int hand;
+  int total_hands;
+  int card_number;
+  int current_streak;
+  int best_streak;
+  int active_card;
+  unsigned long score;
+  Deck dp;
+  int off_x, off_y;
+  const int height = 3;
+
+  std::chrono::_V2::system_clock::time_point play_day;
+
+  door::ANSIColor deck_color;
+  cards deck;
+  cards state;
+
+  void redraw(bool dealing);
+
+public:
+  PlayCards(door::Door &d, DBData &dbd, std::mt19937 &r);
+  ~PlayCards();
+
+  int play_cards(void);
+  void init_values(void);
+  door::renderFunction statusValue(door::ANSIColor status,
+                                   door::ANSIColor value);
+  door::renderFunction commandLineRender(door::ANSIColor bracket,
+                                         door::ANSIColor inner,
+                                         door::ANSIColor outer);
+};
+
+int play_cards(door::Door &door, DBData &db, std::mt19937 &rng);
+
+#endif