瀏覽代碼

Working tests on updated db.

Dates use DBDate (YYYYMMDD).
Steve Thielemann 2 年之前
父節點
當前提交
62d08eecf8
共有 5 個文件被更改,包括 375 次插入118 次删除
  1. 68 42
      db.go
  2. 41 26
      db_test.go
  3. 75 0
      dbdate.go
  4. 79 0
      dbdate_test.go
  5. 112 50
      playcards.go

+ 68 - 42
db.go

@@ -12,6 +12,14 @@ import (
 	_ "github.com/mattn/go-sqlite3"
 )
 
+//
+// Tables:
+//
+// settings(username, setting, value)
+// scores(username, when, date, hand, won, run, score)
+// monthly(month, username, days, hands_won, bestrun, score)
+//
+
 // Make sure the go-sqlite3 is here.  Keep the db related things together.
 
 type DBData struct {
@@ -41,33 +49,37 @@ const LOCK_SETTING = "LOCK"
 const LOCK_CLEAR = "0"
 
 func (db *DBData) Create() {
-	_, err := db.DB.Exec(
-		"CREATE TABLE IF NOT EXISTS settings(username TEXT, setting TEXT, value TEXT, PRIMARY KEY(username, setting));")
-	if err != nil {
-		log.Panicln(err)
-	}
-	_, err = db.DB.Exec(
-		"CREATE TABLE IF NOT EXISTS scores ( username TEXT, `when` INTEGER, date INTEGER, hand INTEGER, won INTEGER, score INTEGER, PRIMARY KEY(username, date, hand));")
-	if err != nil {
-		log.Panicln(err)
-	}
-	_, err = db.DB.Exec(
-		"CREATE TABLE IF NOT EXISTS monthly ( month	INTEGER, username TEXT, days INTEGER, hands_won INTEGER, score INTEGER, PRIMARY KEY(month, username) );")
-	if err != nil {
-		log.Panicln(err)
+	var SQLLines []string = []string{
+		"CREATE TABLE IF NOT EXISTS settings( " +
+			"username TEXT, setting TEXT, value TEXT, " +
+			"PRIMARY KEY(username, setting));",
+		"CREATE TABLE IF NOT EXISTS scores( " +
+			"username TEXT, `when` INTEGER, date INTEGER, " +
+			"hand INTEGER, won INTEGER, run INTEGER, " +
+			" score INTEGER, " +
+			"PRIMARY KEY(username, date, hand));",
+		"CREATE TABLE IF NOT EXISTS monthly( " +
+			"month INTEGER, username TEXT, days INTEGER, " +
+			"hands_won INTEGER, bestrun INTEGER, " +
+			"score INTEGER, " +
+			"PRIMARY KEY(month, username) );",
+	}
+	for _, sql := range SQLLines {
+		_, err := db.DB.Exec(sql)
+		if err != nil {
+			log.Panicln("DBData.Create:", err)
+		}
 	}
+
 	// Make sure our settings LOCK value exists
 	db.DB.Exec(
-		"INSERT INTO settings(username, setting, value) VALUES(?,?,?);",
+		"INSERT INTO settings(username, setting, value) "+
+			"VALUES(?,?,?);",
 		LOCK_USERNAME, LOCK_SETTING, LOCK_CLEAR)
 	// An error here is OK.  It means the setting already exists.
-	/*
-		if err != nil {
-			log.Printf("LOCK %#v\n", err)
-		}
-	*/
 }
 
+// Lock the database (for Monthly maint)
 func (db *DBData) Lock(timeout int) bool {
 	var now time.Time = time.Now()
 	var value string = fmt.Sprintf("%d,%d", os.Getpid(), now.Unix())
@@ -120,7 +132,7 @@ RetryLock:
 	}
 
 	time.Sleep(time.Duration(50) * time.Millisecond)
-	howOld = time.Until(now) // now.Sub(time.Now())
+	howOld = time.Until(now)
 	if int(-howOld.Seconds()) > timeout {
 		return false
 	}
@@ -173,9 +185,9 @@ func (db *DBData) SetSetting(setting string, value string) {
 	// log.Printf("SetSetting %s %s = %s\n", db.User, setting, value)
 }
 
-func (db *DBData) SaveScore(when int64, date int64, hand int, won int, score int) {
-	_, err := db.DB.Exec("INSERT INTO scores(username, `when`, date, hand, won, score) VALUES(?,?,?,?,?,?);",
-		db.User, when, date, hand, won, score)
+func (db *DBData) SaveScore(when DBDate, date DBDate, hand int, won int, run int, score int) {
+	_, err := db.DB.Exec("INSERT INTO scores(username, `when`, date, hand, won, run, score) VALUES(?,?,?,?,?,?,?);",
+		db.User, int(when), int(date), hand, won, run, score)
 	if err != nil {
 		log.Println("SaveScore", err)
 		// When we play a hand we've already played:
@@ -183,9 +195,9 @@ func (db *DBData) SaveScore(when int64, date int64, hand int, won int, score int
 	}
 }
 
-func (db *DBData) HandsPlayedOnDay(day int64) int {
+func (db *DBData) HandsPlayedOnDay(day DBDate) int {
 	row := db.DB.QueryRow("SELECT COUNT(*) FROM scores WHERE username=? AND date=?;",
-		db.User, day)
+		db.User, int(day))
 	var value int
 	err := row.Scan(&value)
 	if err != nil {
@@ -197,8 +209,8 @@ func (db *DBData) HandsPlayedOnDay(day int64) int {
 /*
 WhenPlayed = GetPlayed  (as far as I can tell...)
 */
-func (db *DBData) WhenPlayed() map[int64]int {
-	var result map[int64]int = make(map[int64]int)
+func (db *DBData) WhenPlayed() map[DBDate]int {
+	var result map[DBDate]int = make(map[DBDate]int)
 
 	rows, err := db.DB.Query("SELECT date, COUNT(hand) FROM scores WHERE username=? GROUP BY date;",
 		db.User)
@@ -209,7 +221,7 @@ func (db *DBData) WhenPlayed() map[int64]int {
 	defer rows.Close()
 
 	for rows.Next() {
-		var date int64
+		var date DBDate
 		var hands int
 
 		if err := rows.Scan(&date, &hands); err != nil {
@@ -228,17 +240,18 @@ func (db *DBData) WhenPlayed() map[int64]int {
 }
 
 type MonthUser struct {
-	Date     int64
+	Date     DBDate
 	Username string
 }
 
 type MonthStats struct {
 	Days      int
 	Hands_Won int
+	Run       int
 	Score     int
 }
 
-func (db *DBData) ExpireScores(month_first_unix int64) {
+func (db *DBData) ExpireScores(month_first DBDate) {
 	// step 1: acquire lock
 
 	l := db.Lock(5)
@@ -251,8 +264,10 @@ func (db *DBData) ExpireScores(month_first_unix int64) {
 	defer db.Unlock()
 
 	var Monthly map[MonthUser]MonthStats = make(map[MonthUser]MonthStats)
-	rows, err := db.DB.Query("SELECT date, username, SUM(score), SUM(won) FROM scores WHERE date < ? GROUP BY date, username ORDER BY date, SUM(score) DESC;",
-		month_first_unix)
+	rows, err := db.DB.Query("SELECT date, username, SUM(score), SUM(won), MAX(run) "+
+		"FROM scores WHERE date < ? "+
+		"GROUP BY date, username ORDER BY date, SUM(score) DESC;",
+		month_first)
 	if err != nil {
 		log.Println("ExpireScores", err)
 		return
@@ -263,25 +278,31 @@ func (db *DBData) ExpireScores(month_first_unix int64) {
 		var mu MonthUser
 		var score int
 		var won int
+		var run int
 
-		if err := rows.Scan(&mu.Date, &mu.Username, &score, &won); err != nil {
+		if err := rows.Scan(&mu.Date, &mu.Username, &score, &won, &run); err != nil {
 			log.Println("ExpireScores Scan", err)
 			return
 		}
 
-		FirstOfMonthDateUnix(&mu.Date)
+		mu.Date.First()
+		// FirstOfMonthDateUnix(&mu.Date)
 
 		if mumap, found := Monthly[mu]; found {
 			// entry found
 			mumap.Days++
 			mumap.Score += score
 			mumap.Hands_Won += won
+			if run > mumap.Run {
+				mumap.Run = run
+			}
 			Monthly[mu] = mumap
 		} else {
 			// new entry
 			var ms MonthStats = MonthStats{Days: 1,
 				Score:     score,
 				Hands_Won: won,
+				Run:       run,
 			}
 			Monthly[mu] = ms
 		}
@@ -303,11 +324,12 @@ func (db *DBData) ExpireScores(month_first_unix int64) {
 
 	tx.Exec(
 		"DELETE FROM scores WHERE date < ?;",
-		month_first_unix)
+		month_first)
 
 	for mu, ms := range Monthly {
-		_, err := tx.Exec("INSERT INTO monthly(month, username, days, hands_won, score) VALUES(?,?,?,?,?);",
-			mu.Date, mu.Username, ms.Days, ms.Hands_Won, ms.Score)
+		_, err := tx.Exec("INSERT INTO monthly(month, username, days, hands_won, bestrun, score) "+
+			"VALUES(?,?,?,?,?,?);",
+			mu.Date, mu.Username, ms.Days, ms.Hands_Won, ms.Run, ms.Score)
 		if err != nil {
 			log.Println("ExpireScores Insert", err)
 			tx.Rollback()
@@ -333,7 +355,8 @@ type ScoresDetails struct {
 
 func (db *DBData) GetScoresOnDay(date int64) []ScoresDetails {
 	var result []ScoresDetails
-	rows, err := db.DB.Query("SELECT username, date, hand, won, score FROM SCORES WHERE date=? ORDER BY username, hand;",
+	rows, err := db.DB.Query("SELECT username, date, hand, won, score FROM SCORES "+
+		"WHERE date=? ORDER BY username, hand;",
 		date)
 	if err != nil {
 		log.Println("GetScoresOnDay", date, err)
@@ -367,7 +390,8 @@ type ScoresData struct {
 func (db *DBData) GetScores(limit int) []ScoresData {
 	var result []ScoresData
 
-	rows, err := db.DB.Query("SELECT date, username, SUM(score), SUM(won) FROM SCORES GROUP BY date, username ORDER BY SUM(score) DESC LIMIT ?;",
+	rows, err := db.DB.Query("SELECT date, username, SUM(score), SUM(won) "+
+		"FROM SCORES GROUP BY date, username ORDER BY SUM(score) DESC LIMIT ?;",
 		limit)
 	if err != nil {
 		log.Println("GetScores", err)
@@ -396,13 +420,15 @@ type MonthlyData struct {
 	User      string
 	Days      int
 	Hands_Won int
+	Best_Run  int
 	Score     int
 }
 
 func (db *DBData) GetMonthlyScores(limit int) []MonthlyData {
 	var result []MonthlyData
 
-	rows, err := db.DB.Query("SELECT month, username, days, hands_won, score FROM monthly ORDER BY score DESC LIMIT ?;",
+	rows, err := db.DB.Query("SELECT month, username, days, hands_won, bestrun, score "+
+		"FROM monthly ORDER BY score DESC LIMIT ?;",
 		limit)
 	if err != nil {
 		log.Println("GetMonthlyScores", err)
@@ -413,7 +439,7 @@ func (db *DBData) GetMonthlyScores(limit int) []MonthlyData {
 	for rows.Next() {
 		var md MonthlyData
 
-		if err := rows.Scan(&md.Date, &md.User, &md.Days, &md.Hands_Won, &md.Score); err != nil {
+		if err := rows.Scan(&md.Date, &md.User, &md.Days, &md.Hands_Won, &md.Best_Run, &md.Score); err != nil {
 			log.Println("GetMonthlyScores Scan", err)
 			return result
 		}

+ 41 - 26
db_test.go

@@ -3,56 +3,66 @@ package main
 import (
 	"os"
 	"testing"
-	"time"
 )
 
+const DEBUG_DB bool = false
+
 // Test the db.ExpireScores
 func TestExpireScores(t *testing.T) {
 	const testdb = ".space-test.db"
 
 	db := DBData{}
+	// Start with empty database
 	os.Remove(testdb)
+
 	db.Open(testdb)
 	defer db.Close()
-	defer os.Remove(testdb)
+	if !DEBUG_DB {
+		defer os.Remove(testdb)
+	}
 
 	db.User = "Testing"
+
 	// stuff score data into database
-	// SaveScore(when int64, date int64, hand int, won int, score int)
+	// SaveScore(when int64, date int64, hand int, won int, best int, score int)
 	type TData struct {
-		when  int64
-		date  int64
+		when  DBDate
+		date  DBDate
 		hand  int
 		won   int
+		run   int
 		score int
 	}
 
 	var data []TData = []TData{
-		{when: 1645929178, date: 1645858800, hand: 1, won: 1, score: 1115},
-		{1645929274, 1645858800, 2, 0, 955},
-		{1645929373, 1645858800, 3, 0, 700},
-		{1645931772, 1643698800, 1, 0, 660},
-		{1645931841, 1643698800, 2, 0, 645},
-		{1645931942, 1643698800, 3, 0, 695},
-		{1645988653, 1645945200, 1, 0, 645},
-		{1645988739, 1645945200, 2, 0, 750},
-		{1645988820, 1645945200, 3, 0, 530},
-		{1645993583, 1643785200, 1, 1, 1045},
-		{1646004744, 1643785200, 2, 0, 700},
-		{1646004825, 1643785200, 3, 0, 600},
-	}
+		{20230101, 20230101, 1, 0, 8, 750},
+		{20230101, 20230101, 2, 0, 8, 780},
+		{20230101, 20230101, 3, 1, 5, 980},
+		{20230101, 20230102, 1, 1, 9, 945},
+		{20230101, 20230102, 2, 1, 6, 985},
+		{20230101, 20230102, 3, 0, 10, 700},
+		{20230101, 20230115, 1, 0, 7, 600},
+		{20230101, 20230115, 2, 0, 7, 730},
+		{20230101, 20230115, 3, 1, 5, 1000}}
+
+	var score, best, won int
+
 	for idx := range data {
 		db.SaveScore(data[idx].when, data[idx].date, data[idx].hand,
-			data[idx].won, data[idx].score)
+			data[idx].won, data[idx].run, data[idx].score)
+
+		// Calculate the score, best, and won
+		score += data[idx].score
+		if data[idx].run > best {
+			best = data[idx].run
+		}
+		won += data[idx].won
 	}
 
 	// Data loaded .. call Expire!
-	var next_month time.Time = time.Unix(1645929274, 0)
-	next_month = next_month.AddDate(0, 1, 0)
-	FirstOfMonthDate(&next_month)
-
-	var next_unix int64 = next_month.Unix()
-	db.ExpireScores(next_unix)
+	var next_month DBDate = 20230202 //time.Time = time.Unix(1645929274, 0)
+	next_month.SetDay(1)
+	db.ExpireScores(next_month)
 
 	// 1. Verify the scores table is empty.
 	scores := db.GetScores(5)
@@ -69,13 +79,15 @@ func TestExpireScores(t *testing.T) {
 			User      string
 			Days      int
 			Hands_Won int
+			Best      int
 			Score     int
 		}
 	*/
+
 	if len(monthly) != 1 {
 		t.Errorf("Monthly Scores: got %d, expected 1.\n", len(monthly))
 	} else {
-		var md MonthlyData = MonthlyData{1643698800, "Testing", 4, 2, 9040}
+		var md MonthlyData = MonthlyData{20230101, "Testing", 3, won, best, score}
 
 		if monthly[0].Date != md.Date {
 			t.Errorf("Date %d, expected %d.\n", monthly[0].Date, md.Date)
@@ -89,6 +101,9 @@ func TestExpireScores(t *testing.T) {
 		if monthly[0].Hands_Won != md.Hands_Won {
 			t.Errorf("Hands %d, expected %d.\n", monthly[0].Hands_Won, md.Hands_Won)
 		}
+		if monthly[0].Best_Run != md.Best_Run {
+			t.Errorf("Run %d, expected %d.\n", monthly[0].Best_Run, md.Best_Run)
+		}
 		if monthly[0].Score != md.Score {
 			t.Errorf("Score %d, expected %d.\n", monthly[0].Score, md.Score)
 		}

+ 75 - 0
dbdate.go

@@ -0,0 +1,75 @@
+package main
+
+import "time"
+
+type DBDate uint32
+
+// YYYYMMDD max 8 digits.  int32.
+
+func (d *DBDate) Year() int {
+	return int(*d) / 10000
+}
+
+func (d *DBDate) Month() int {
+	return (int(*d) / 100) % 100
+}
+
+func (d *DBDate) Day() int {
+	return int(*d) % 100
+}
+
+// Set day - does no validation
+func (d *DBDate) SetDay(day int) {
+	var i int = int(*d)
+	i -= (i % 100)
+	i += day
+	*d = DBDate(i)
+}
+
+// Make the first of the month
+func (d *DBDate) First() {
+	d.SetDay(1)
+}
+
+// Make end of month
+func (d *DBDate) Last() {
+	var value int = int(*d)
+	var lastDay time.Time = time.Date(value/10000,
+		time.Month((value/100)%100)+1,
+		0, 2, 2, 2, 0, time.Local)
+	*d = DBDate(lastDay.Year()*10000 +
+		int(lastDay.Month())*100 + lastDay.Day())
+}
+
+func NewDBDate(year, month, day int) DBDate {
+	return DBDate(year*10000 + month*100 + day)
+}
+
+func (d *DBDate) YMD() (int, int, int) {
+	var i int = int(*d)
+	return i / 10000, (i / 100) % 100, i % 100
+}
+
+// Convert time.Time to DBDate
+func ToDBDate(point time.Time) DBDate {
+	return DBDate(point.Year()*10000 +
+		int(point.Month())*100 + point.Day())
+}
+
+// Convert DBDate to time.Time
+func DBDateTime(point DBDate) time.Time {
+	// I'm not sure about hours, minutes, seconds.
+
+	var value int = int(point)
+	var result time.Time = time.Date(value/10000,
+		time.Month((value/100)%100),
+		value%100, 2, 2, 2, 0, time.Local)
+	return result
+}
+
+//
+// Store dates as uint32
+// YYYYMMDD
+//
+// So: Jan 21, 2022 => 20220121
+//

+ 79 - 0
dbdate_test.go

@@ -0,0 +1,79 @@
+package main
+
+import "testing"
+
+func TestDBDate(t *testing.T) {
+
+	table := map[DBDate][5]int{
+		DBDate(20220127): {2022, 1, 27, 20220101, 20220131},
+		DBDate(22000220): {2200, 2, 20, 22000201, 22000228}, // Not a leap year
+		DBDate(20200217): {2020, 2, 17, 20200201, 20200229}, // 2020 Leap year
+		DBDate(19991031): {1999, 10, 31, 19991001, 19991031},
+		DBDate(19900228): {1990, 2, 28, 19900201, 19900228},
+		DBDate(20381231): {2038, 12, 31, 20381201, 20381231},
+	}
+
+	for d, ts := range table {
+		newDate := NewDBDate(ts[0], ts[1], ts[2])
+		if newDate != d {
+			t.Errorf("NewDBDate %v, got %v expected %v\n",
+				ts, newDate, d)
+		}
+		year, month, day := newDate.YMD()
+		if (year != ts[0]) || (month != ts[1]) || (day != ts[2]) {
+			t.Errorf("YMD %v, got %d,%d,%d expected %v\n",
+				newDate, year, month, day, ts)
+		}
+		// convert DBDate to time.Time
+		newTime := DBDateTime(d)
+		// convert time.Time to DBDate
+		dateFromTime := ToDBDate(newTime)
+
+		if newTime.Year() != ts[0] {
+			t.Errorf("DBDateTime %v, Year got %d expected %d\n", d, newTime.Year(), ts[0])
+		}
+		if int(newTime.Month()) != ts[1] {
+			t.Errorf("DBDateTime %v, Month got %d expected %d\n", d, newTime.Month(), ts[1])
+		}
+		if newTime.Day() != ts[2] {
+			t.Errorf("DBDateTime %v, Day got %d expected %d\n", d, newTime.Day(), ts[2])
+		}
+
+		if newTime.Year() != newDate.Year() {
+			t.Errorf("Year %v, got %d and %d\n",
+				d, newTime.Year(), newDate.Year())
+		}
+		if int(newTime.Month()) != newDate.Month() {
+			t.Errorf("Month %v, got %d and %d\n",
+				d, newTime.Month(), newDate.Month())
+		}
+		if newTime.Day() != newDate.Day() {
+			t.Errorf("Year %v, got %d and %d\n",
+				d, newTime.Day(), newDate.Day())
+		}
+
+		if dateFromTime.Year() != newDate.Year() {
+			t.Errorf("DFT Year %v, got %d and %d\n",
+				d, dateFromTime.Year(), newDate.Year())
+		}
+		if dateFromTime.Month() != newDate.Month() {
+			t.Errorf("DFT Month %v, got %d and %d\n",
+				d, dateFromTime.Month(), newDate.Month())
+		}
+		if dateFromTime.Day() != newDate.Day() {
+			t.Errorf("DFT Year %v, got %d and %d\n",
+				d, dateFromTime.Day(), newDate.Day())
+		}
+
+		newDate.First()
+		if int(newDate) != ts[3] {
+			t.Errorf("First %v, got %d, expected %d\n",
+				d, newDate, ts[3])
+		}
+		newDate.Last()
+		if int(newDate) != ts[4] {
+			t.Errorf("Last %v, got %d, expected %d\n",
+				d, newDate, ts[4])
+		}
+	}
+}

+ 112 - 50
playcards.go

@@ -49,10 +49,11 @@ type PlayCards struct {
 	Play_card           int
 	Current_streak      int
 	Best_streak         int
+	Hand_streak         int
+	Month_streak        int
 	Select_card         int
 	Score               int
-	Play_day            time.Time
-	Play_day_t          int64
+	Play_day            DBDate
 	Days_played         int
 	Hand                int
 	SpaceAceTriPeaks    door.Panel
@@ -66,12 +67,13 @@ type PlayCards struct {
 	State               []DeckType // [51]DeckType
 	Off_X               int
 	Off_Y               int
-	Calendar_day_t      [31]int64
 	Calendar            door.Screen
-	Calendar_panel_days [42]int // Where is each day positioned on the Calendar?
+	Calendar_panel_days [42]int // Where is each day positioned on the Calendar? 42 = 7 day * 6 lines
 	Calendar_day_status [31]int
 }
 
+// Calendar_day        [31]DBDate // deprecate
+
 // Possibly change Deck to [51]DeckType.  This game always only has 1 deck.
 // Possibly change State [51]DeckType to [51]int8.
 // There are few states to track.
@@ -110,6 +112,11 @@ func (pc *PlayCards) InitValues() {
 	pc.Best_streak = 0
 	best := pc.DB.GetSetting("best_streak", "0")
 	pc.Best_streak, _ = strconv.Atoi(best)
+	pc.Month_streak = 0
+	// Init the best streak for this month
+	// TODO: Pull from DB.
+	pc.Hand_streak = 0
+	// Best for this hand
 	pc.Select_card = 23
 	pc.Score = 0
 
@@ -120,20 +127,21 @@ func (pc *PlayCards) Init() {
 	pc.InitValues()
 
 	// PlayCards::PlayCards()
-	pc.Play_day = time.Now()
-	NormalizeDate(&pc.Play_day)
-	pc.Play_day_t = pc.Play_day.Unix()
+	pc.Play_day = ToDBDate(time.Now())
+	// NormalizeDate(&pc.Play_day)
+	//pc.Play_day_t = pc.Play_day.Unix()
 
 	pc.DeckPanel.Init()
 
-	var last_played int64
+	var last_played DBDate
 	last := pc.DB.GetSetting("last_played", "0")
-	last_played, _ = strconv.ParseInt(last, 10, 64)
+	temp, _ := strconv.ParseInt(last, 10, 64)
+	last_played = DBDate(temp)
 	days := pc.DB.GetSetting("days_played", "0")
 	pc.Days_played, _ = strconv.Atoi(days)
 
-	if last_played != pc.Play_day_t {
-		pc.DB.SetSetting("last_played", strconv.FormatInt(pc.Play_day_t, 10))
+	if last_played != pc.Play_day {
+		pc.DB.SetSetting("last_played", strconv.FormatInt(int64(pc.Play_day), 10))
 		pc.DB.SetSetting("days_played", "0")
 		pc.Days_played = 0
 	}
@@ -217,7 +225,7 @@ func (pc *PlayCards) Init() {
 			if !ok {
 				format = "January 2"
 			}
-			txt := fmt.Sprintf("Playing: %s", pc.Play_day.Format(format))
+			txt := fmt.Sprintf("Playing: %s", DBDateTime(pc.Play_day).Format(format))
 			txt += strings.Repeat(" ", W-len(txt))
 			return txt
 		}
@@ -236,11 +244,26 @@ func (pc *PlayCards) Init() {
 				UpdateF: currentUpdate,
 				RenderF: svRender,
 			})
+		handUpdate := func() string {
+			txt := fmt.Sprintf("Hand Streak   : %d", pc.Hand_streak)
+			txt += strings.Repeat(" ", W-len(txt))
+			return txt
+		}
+		pc.StreakPanel.Lines = append(pc.StreakPanel.Lines,
+			door.Line{Text: handUpdate(),
+				UpdateF: handUpdate,
+				RenderF: svRender,
+			})
 		longestUpdate := func() string {
 			txt := fmt.Sprintf("Longest Streak: %d", pc.Best_streak)
 			txt += strings.Repeat(" ", W-len(txt))
 			return txt
 		}
+
+		// TODO:
+		// Add:  This Month and This Hand values
+		// pc.Month_streak, pc.Hand_streak
+
 		pc.StreakPanel.Lines = append(pc.StreakPanel.Lines,
 			door.Line{Text: longestUpdate(),
 				UpdateF: longestUpdate,
@@ -318,29 +341,45 @@ func (pc *PlayCards) Init() {
 	// calendar = make_calendar();
 	{
 		pc.Calendar = door.Screen{}
-		var month time.Time = time.Now()
+		var month DBDate = ToDBDate(time.Now())
+		month.First()
 		var today_day int = month.Day()
-		FirstOfMonthDate(&month)
+		// FirstOfMonthDate(&month)
+		var month_end DBDate = month
+		month_end.Last()
 
 		// clear out
-		for x := range pc.Calendar_day_t {
-			pc.Calendar_day_t[x] = 0
-		}
-		// pc.Calendar_day_t = make([]int64, 31)
-		pc.Calendar_day_t[0] = month.Unix()
-		var First_Weekday int = int(month.Weekday())
-		var month_last_day = 0
-		var month_end time.Time = month
-		for {
-			month_end = month_end.AddDate(0, 0, 1)
-			NormalizeDate(&month_end)
-			if month_end.Day() == 1 {
-				break
-			} else {
-				pc.Calendar_day_t[month_end.Day()-1] = month_end.Unix()
-				month_last_day = month_end.Day()
+		/*
+			for x := range pc.Calendar_day {
+				pc.Calendar_day[x] = 0
 			}
-		}
+			// pc.Calendar_day_t = make([]int64, 31)
+			pc.Calendar_day[0] = month // .Unix()
+		*/
+		var First_Weekday int = int(DBDateTime(month).Weekday())
+		var month_last_day = month_end.Day()
+
+		/*
+			var calendarDay DBDate = month
+			for x := 1; x <= month_last_day; x++ {
+				calendarDay.SetDay(x) //  = DBDate((int(calendarDay) % 100) + x)
+				pc.Calendar_day[x-1] = calendarDay
+			}
+		*/
+
+		// var month_end time.Time = month
+		/*
+			for {
+				month_end = month_end.AddDate(0, 0, 1)
+				NormalizeDate(&month_end)
+				if month_end.Day() == 1 {
+					break
+				} else {
+					pc.Calendar_day_t[month_end.Day()-1] = month_end.Unix()
+					month_last_day = month_end.Day()
+				}
+			}
+		*/
 
 		// clear out
 		for x := range pc.Calendar_panel_days {
@@ -364,9 +403,9 @@ func (pc *PlayCards) Init() {
 		last_played := pc.DB.WhenPlayed()
 
 		for played, hands := range last_played {
-			if played >= month.Unix() {
+			if played >= month {
 				// Ok, it is within the range
-				var played_day int = time.Unix(played, 0).Day()
+				var played_day int = played.Day()
 				if hands < pc.Total_hands {
 					pc.Calendar_day_status[played_day-1] = 1
 				} else {
@@ -389,7 +428,7 @@ func (pc *PlayCards) Init() {
 		}
 
 		// Get current month as string
-		var current string = strings.ToUpper(CurrentMonth(month))
+		var current string = strings.ToUpper(CurrentMonth(DBDateTime(month)))
 		if len(current) < 6 {
 			current = "  " + current + "  "
 		}
@@ -538,13 +577,13 @@ func (pc *PlayCards) Init() {
 func (pc *PlayCards) Play() rune {
 	// this defaults (for now) to playing today (if unplayed).
 	// Otherwise display calendar.
-	pc.Play_day = time.Now()
-	NormalizeDate(&pc.Play_day)
+	pc.Play_day = ToDBDate(time.Now())
+	// NormalizeDate(&pc.Play_day)
 
-	pc.Play_day_t = pc.Play_day.Unix()
-	played := pc.DB.HandsPlayedOnDay(pc.Play_day_t)
+	// pc.Play_day_t = pc.Play_day.Unix()
+	played := pc.DB.HandsPlayedOnDay(pc.Play_day)
 
-	log.Printf("HandsPlayedOnDay(%d), %d", pc.Play_day_t, played)
+	log.Printf("HandsPlayedOnDay(%d), %d", pc.Play_day, played)
 	var r rune
 
 	if played == 0 {
@@ -570,13 +609,15 @@ func (pc *PlayCards) Play() rune {
 
 CALENDAR_UPDATE:
 	// pc.UpdateCalendarDays()
-	month_t := pc.Calendar_day_t[0]
+	month_t := pc.Play_day // pc.Calendar_day[0]
+	month_t.SetDay(1)
+
 	last_played := pc.DB.WhenPlayed()
 
 	for played, hands := range last_played {
 		if played >= month_t {
 			// Ok, it is within the range
-			var played_day int = time.Unix(played, 0).Day()
+			var played_day int = played.Day()
 			log.Printf("update: month_unix %d, played %d, hands %d (total %d)\n",
 				month_t, played_day, hands, pc.Total_hands)
 			if hands < pc.Total_hands {
@@ -639,8 +680,8 @@ AGAIN:
 		if status == 0 {
 			// play full day
 			pc.Hand = 1
-			pc.Play_day_t = pc.Calendar_day_t[number-1]
-			pc.Play_day = time.Unix(pc.Play_day_t, 0)
+			pc.Play_day.SetDay(number) //  = pc.Calendar_day[number-1]
+			// pc.Play_day = time.Unix(pc.Play_day_t, 0)
 			r = pc.PlayCards()
 			if r == 'D' {
 				goto CALENDAR_UPDATE
@@ -649,9 +690,9 @@ AGAIN:
 		}
 		if status == 1 {
 			// Play half day
-			pc.Play_day_t = pc.Calendar_day_t[number-1]
-			pc.Play_day = time.Unix(pc.Play_day_t, 0)
-			played := pc.DB.HandsPlayedOnDay(pc.Play_day_t)
+			pc.Play_day.SetDay(number) // = pc.Calendar_day[number-1]
+			// pc.Play_day = time.Unix(pc.Play_day_t, 0)
+			played := pc.DB.HandsPlayedOnDay(pc.Play_day)
 			if played < pc.Total_hands {
 				pc.Hand = played + 1
 				r = pc.PlayCards()
@@ -697,6 +738,8 @@ Next_Hand:
 	pc.Select_card = 23
 	pc.Score = 0
 	pc.Current_streak = 0
+	pc.Hand_streak = 0
+	// Don't touch Month_streak.
 
 	// Use play day to seed RNG
 	{
@@ -708,7 +751,7 @@ Next_Hand:
 			seed_seq = append(seed_seq, ba[:]...)
 			// sha1.Sum(ba[:])
 		}
-		pd := Int64toByteArray(pc.Play_day_t)
+		pd := Int64toByteArray(int64(pc.Play_day))
 		seed_seq = append(seed_seq, pd[:]...)
 		// We also need the hand # that we're playing.
 		seed_seq = append(seed_seq, byte(pc.Hand))
@@ -825,9 +868,25 @@ Next_Hand:
 			switch r {
 			case '\x0d':
 				// Next Card
+				var updateStreakPanel bool = false
+				if pc.Current_streak > pc.Hand_streak {
+					pc.Hand_streak = pc.Current_streak
+					updateStreakPanel = true
+				}
+
+				if pc.Current_streak > pc.Month_streak {
+					pc.Month_streak = pc.Current_streak
+					updateStreakPanel = true
+				}
+
 				if pc.Current_streak > pc.Best_streak {
 					pc.Best_streak = pc.Current_streak
 					save_streak = true
+					updateStreakPanel = true
+					// pc.Door.Write(pc.StreakPanel.Update())
+				}
+
+				if updateStreakPanel {
 					pc.Door.Write(pc.StreakPanel.Update())
 				}
 
@@ -860,6 +919,9 @@ Next_Hand:
 
 			case 'Q', 'q':
 				// Possibly prompt here for [N]ext hand or [Q]uit
+				// Odd:  It seems like streaks should be tracked in
+				// one place -- when we play a card...
+
 				if pc.Current_streak > pc.Best_streak {
 					pc.Best_streak = pc.Current_streak
 					pc.Door.Write(pc.StreakPanel.Update())
@@ -885,13 +947,13 @@ Next_Hand:
 				} else {
 					if r == 'D' || r == 'Q' {
 						if pc.Score >= 50 {
-							pc.DB.SaveScore(time.Now().Unix(), pc.Play_day_t, pc.Hand, 0, pc.Score)
+							pc.DB.SaveScore(ToDBDate(time.Now()), pc.Play_day, pc.Hand, 0, pc.Hand_streak, pc.Score)
 						}
 
 						in_game = false
 					} else {
 						if r == 'N' {
-							pc.DB.SaveScore(time.Now().Unix(), pc.Play_day_t, pc.Hand, 0, pc.Score)
+							pc.DB.SaveScore(ToDBDate(time.Now()), pc.Play_day, pc.Hand, 0, pc.Hand_streak, pc.Score)
 							pc.Hand++
 							goto Next_Hand
 						}
@@ -989,7 +1051,7 @@ Next_Hand:
 								pc.Door.Write(pc.ScorePanel.Update())
 
 								// Save Score
-								pc.DB.SaveScore(time.Now().Unix(), pc.Play_day_t, pc.Hand, 1, pc.Score)
+								pc.DB.SaveScore(ToDBDate(time.Now()), pc.Play_day, pc.Hand, 1, pc.Hand_streak, pc.Score)
 								pc.NextQuitPanel.Update()
 								pc.Door.Write(pc.NextQuitPanel.Output())