Browse Source

Fixed db, added transactions. Game play fixes.

Fixed Calendar.
Expanded utils to try to keep useful funcs that don't go with
anything here.
* Need version.go.
Steve Thielemann 3 years ago
parent
commit
2d2ea6731a
5 changed files with 390 additions and 97 deletions
  1. 73 4
      db.go
  2. 2 1
      deck.go
  3. 263 81
      playcards.go
  4. 0 10
      space-ace.go
  5. 52 1
      utils.go

+ 73 - 4
db.go

@@ -177,12 +177,15 @@ 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)
 	if err != nil {
-		log.Panicln("SaveScore", err)
+		log.Println("SaveScore", err)
+		// When we play a hand we've already played:
+		// panic: SaveScore UNIQUE constraint failed: scores.username, scores.date, scores.hand
 	}
 }
 
 func (db *DBData) HandsPlayedOnDay(day int64) int {
-	row := db.DB.QueryRow("SELECT COUNT(*) FROM scores WHERE username=? AND date=?;")
+	row := db.DB.QueryRow("SELECT COUNT(*) FROM scores WHERE username=? AND date=?;",
+		db.User, day)
 	var value int
 	err := row.Scan(&value)
 	if err != nil {
@@ -235,7 +238,7 @@ type MonthStats struct {
 	Score     int
 }
 
-func (db *DBData) ExpireScores(month_first_t int64) {
+func (db *DBData) ExpireScores(month_first_unix int64) {
 	// step 1: acquire lock
 
 	l := db.Lock(5)
@@ -248,8 +251,74 @@ func (db *DBData) ExpireScores(month_first_t int64) {
 		return
 	}
 
-	// var Monthly map[MonthUser]MonthStats = make(map[MonthUser]MonthStats)
+	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)
+	if err != nil {
+		log.Println("ExpireScores", err)
+		return
+	}
+	defer rows.Close()
+
+	for rows.Next() {
+		var mu MonthUser
+		var score int
+		var won int
+
+		if err := rows.Scan(&mu.Date, &mu.Username, &score, &won); err != nil {
+			log.Println("ExpireScores Scan", err)
+			return
+		}
+
+		FirstOfMonthDateUnix(&mu.Date)
+
+		if mumap, found := Monthly[mu]; found {
+			// entry found
+			mumap.Days++
+			mumap.Score += score
+			mumap.Hands_Won += won
+			Monthly[mu] = mumap
+		} else {
+			// new entry
+			var ms MonthStats = MonthStats{Days: 1,
+				Score:     score,
+				Hands_Won: won,
+			}
+			Monthly[mu] = ms
+		}
+	}
+
+	if len(Monthly) == 0 {
+		// Nothing to do
+		return
+	}
+
+	// Begin transaction
+	tx, err := db.DB.Begin()
+	if err != nil {
+		log.Println("ExpireScores Begin", err)
+		return
+	}
+
+	tx.Exec(
+		"DELETE FROM scores WHERE date < ?;",
+		month_first_unix)
 
+	for mu, ms := range Monthly {
+		_, err := db.DB.Exec("INSERT INTO monthly(month, username, days, hands_won, score) VALUES(?,?,?,?,?);",
+			mu.Date, mu.Username, ms.Days, ms.Hands_Won, ms.Score)
+		if err != nil {
+			log.Println("ExpireScores Insert", err)
+			tx.Rollback()
+			return
+		}
+	}
+
+	// End transaction / Commit
+	err = tx.Commit()
+	if err != nil {
+		log.Println("ExpireScores Commit", err)
+	}
 }
 
 type ScoresDetails struct {

+ 2 - 1
deck.go

@@ -366,7 +366,8 @@ func MakeCardStates(decks int) []DeckType {
 func RemoveCard(c int, back_color string, off_x int, off_y int, left bool, right bool) string {
 	var result string
 
-	Pos := &CardPos[c]
+	// We are modifying this -- make copy
+	Pos := CardPos[c]
 	if Pos.Level > 1 {
 		Pos.Level--
 	}

+ 263 - 81
playcards.go

@@ -40,54 +40,41 @@ func StringToANSIColor(colorCode string) string {
 }
 
 type PlayCards struct {
-	Door             *door.Door
-	DB               *DBData
-	Config           *map[string]string
-	RNG              *rand.Rand
-	Seeds            []int32
-	Total_hands      int
-	Play_card        int
-	Current_streak   int
-	Best_streak      int
-	Select_card      int
-	Score            int
-	Play_day         time.Time
-	Play_day_t       int64
-	Days_played      int
-	Hand             int
-	Day_status       [42]int
-	SpaceAceTriPeaks door.Panel
-	ScorePanel       door.Panel
-	StreakPanel      door.Panel
-	LeftPanel        door.Panel
-	CmdPanel         door.Panel
-	NextQuitPanel    door.Panel
-	CalendarPanel    door.Panel
-	DeckPanel        Deck
-	Deck             []DeckType
-	State            []DeckType
-	Off_X            int
-	Off_Y            int
-	Calendar_day_t   [31]int64
-	Calendar         door.Screen
+	Door                *door.Door
+	DB                  *DBData
+	Config              *map[string]string
+	RNG                 *rand.Rand
+	Seeds               []int32
+	Total_hands         int
+	Play_card           int
+	Current_streak      int
+	Best_streak         int
+	Select_card         int
+	Score               int
+	Play_day            time.Time
+	Play_day_t          int64
+	Days_played         int
+	Hand                int
+	SpaceAceTriPeaks    door.Panel
+	ScorePanel          door.Panel
+	StreakPanel         door.Panel
+	LeftPanel           door.Panel
+	CmdPanel            door.Panel
+	NextQuitPanel       door.Panel
+	DeckPanel           Deck
+	Deck                []DeckType // [51]DeckType t
+	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_day_status [31]int
 }
 
-// Adjust date to 2:00 AM
-func NormalizeDate(date *time.Time) {
-	offset := time.Duration(date.Second())*time.Second +
-		time.Duration(date.Minute())*time.Minute +
-		time.Duration(date.Hour()-2)*time.Hour +
-		time.Duration(date.Nanosecond())*time.Nanosecond
-	*date = date.Add(-offset)
-}
-
-// Find the 1st of the Month (Normalized) 2:00 AM
-func FirstOfMonthDate(date *time.Time) {
-	if date.Day() != 1 {
-		*date = date.AddDate(0, 0, 1-date.Day())
-	}
-	NormalizeDate(date)
-}
+// 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.
 
 func cmdLineRender(bracket string, inner string, outer string) func(string) string {
 	return func(input string) string {
@@ -333,14 +320,114 @@ func (pc *PlayCards) Init() {
 	{
 		pc.Calendar = door.Screen{}
 		var month time.Time = time.Now()
+		var today_day int = month.Day()
 		FirstOfMonthDate(&month)
 
-	}
-	/*
-		// This is make_calendar_panel (not make_calendar)
+		// 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()
+			}
+		}
+
+		// clear out
+		for x := range pc.Calendar_panel_days {
+			pc.Calendar_panel_days[x] = 0
+		}
+		// pc.Calendar_panel_days = make([]int, 6*7)
+		var row int = 0
+		for x := 0; x < month_last_day; x++ {
+			var dow int = (x + First_Weekday) % 7
+			if x != 0 && dow == 0 {
+				row++
+			}
+			pc.Calendar_panel_days[row*7+dow] = x + 1
+		}
+
+		// clear out
+		for x := range pc.Calendar_day_status {
+			pc.Calendar_day_status[x] = 0
+		}
+		// pc.Calendar_day_status = make([]int, 31)
+		last_played := pc.DB.WhenPlayed()
+
+		for played, hands := range last_played {
+			if played >= month.Unix() {
+				// Ok, it is within the range
+				var played_day int = time.Unix(played, 0).Day()
+				if hands < pc.Total_hands {
+					pc.Calendar_day_status[played_day-1] = 1
+				} else {
+					pc.Calendar_day_status[played_day-1] = 2
+				}
+			}
+		}
+
+		// Get play_days_ahead from config
+		var play_days_ahead int = 0
+		if playdays, has := (*pc.Config)["play_days_ahead"]; has {
+			play_days_ahead, _ = strconv.Atoi(playdays)
+		}
+
+		// Mark all days ahead as NNY.
+		for d := 0; d < 31; d++ {
+			if today_day+play_days_ahead-1 < d {
+				pc.Calendar_day_status[d] = 3
+			}
+		}
+
+		// Get current month as string
+		var current string = strings.ToUpper(CurrentMonth(month))
+		if len(current) < 6 {
+			current = "  " + current + "  "
+		}
+
+		// FUTURE:  Replace this with TDF rendering of Month.
+
+		// make_month
+		var MonthPanel door.Panel = door.Panel{X: 3,
+			Y:           3,
+			Width:       3,
+			Style:       door.DOUBLE,
+			BorderColor: door.ColorText("BOLD YELLOW ON BLACK"),
+		}
+		for _, c := range current {
+			MonthPanel.Lines = append(MonthPanel.Lines,
+				door.Line{Text: fmt.Sprintf(" %c ", c)})
+		}
+		pc.Calendar.AddPanel(MonthPanel)
+
+		// make_weekday
+		var WeekdayPanel door.Panel = door.Panel{X: 8,
+			Y:           3,
+			Width:       41,
+			Style:       door.DOUBLE,
+			BorderColor: door.ColorText("BOLD CYAN ON BLACK"),
+		}
+		WeekdayPanel.Lines = append(WeekdayPanel.Lines,
+			door.Line{Text: " SUN   MON   TUE   WED   THU   FRI   SAT "})
+		pc.Calendar.AddPanel(WeekdayPanel)
+
+		// make_calendar_panel
+
+		var CalendarPanel door.Panel
+
 		{
 			W := 41
-			pc.CalendarPanel = door.Panel{Width: W,
+			CalendarPanel = door.Panel{Width: W,
 				Style:       door.DOUBLE,
 				BorderColor: door.ColorText("CYAN ON BLACK")}
 
@@ -363,7 +450,8 @@ func (pc *PlayCards) Init() {
 						result.Append(spaces, 1)
 					}
 					dayText = text[1+days*6 : (1+days*6)+3]
-					if dayText[1] == ' ' {
+					// if dayText[1] == ' ' {
+					if dayText[0] == ' ' {
 						result.Append(spaces, 3)
 					} else {
 						// Something is here
@@ -396,17 +484,20 @@ func (pc *PlayCards) Init() {
 			}
 
 			for row := 0; row < 6; row++ {
+				// Do this, or it doesn't get captured correctly.
+				_row := row
+				// _pc := pc
 				calendarUpdate := func() string {
 					var text string
 
 					for d := 0; d < 7; d++ {
 						text += " "
-						v := pc.Day_status[(row*7)+d]
+						v := pc.Calendar_panel_days[(_row*7)+d]
 						if v == 0 {
 							text += "   "
 						} else {
 							text += fmt.Sprintf("%-2d", v)
-							status := pc.Day_status[v-1]
+							status := pc.Calendar_day_status[v-1]
 							switch status {
 							case 0:
 								text += "o"
@@ -427,17 +518,25 @@ func (pc *PlayCards) Init() {
 					return text
 				}
 
-				pc.Calendar.Lines = append(pc.Calendar.Lines,
+				log.Printf("line %d: [%s]\n", row, calendarUpdate())
+
+				CalendarPanel.Lines = append(CalendarPanel.Lines,
 					door.Line{Text: calendarUpdate(),
 						UpdateF: calendarUpdate,
 						RenderF: calendarRender})
 			}
 		}
-	*/
+		CalendarPanel.X = 8
+		CalendarPanel.Y = 6
+
+		pc.Calendar.AddPanel(CalendarPanel)
+
+	}
+	// end make_calendar
 
 }
 
-func (pc *PlayCards) Play() {
+func (pc *PlayCards) Play() int {
 	// this defaults (for now) to playing today (if unplayed).
 	// Otherwise display calendar.
 	pc.Play_day = time.Now()
@@ -446,44 +545,125 @@ func (pc *PlayCards) Play() {
 	pc.Play_day_t = pc.Play_day.Unix()
 	played := pc.DB.HandsPlayedOnDay(pc.Play_day_t)
 
+	log.Printf("HandsPlayedOnDay(%d), %d", pc.Play_day_t, played)
 	var r int
 
 	if played == 0 {
-		pc.Door.Write("Let's play today..." + door.CRNL)
-		time.Sleep(time.Second)
+		pc.Door.Write(door.CRNL + door.Reset + "Let's play today..." + door.CRNL)
+		time.Sleep(time.Second * time.Duration(2))
 		pc.Hand = 1
 		r = pc.PlayCards()
 		if r != 'D' {
-			return
+			return r
 		}
 	} else {
 		if played < pc.Total_hands {
 			// let's finish today...
+			pc.Door.Write(door.CRNL + door.Reset + "Let's finish today..." + door.CRNL)
+			time.Sleep(time.Second * time.Duration(2))
 			pc.Hand = played + 1
 			r = pc.PlayCards()
 			if r != 'D' {
-				return
+				return r
+			}
+		}
+	}
+
+CALENDAR_UPDATE:
+	// pc.UpdateCalendarDays()
+	month_t := pc.Calendar_day_t[0]
+	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()
+			if hands < pc.Total_hands {
+				pc.Calendar_day_status[played_day-1] = 1
+			} else {
+				pc.Calendar_day_status[played_day-1] = 2
 			}
 		}
 	}
 
-	/*
-		CALENDAR_UPDATE:
-		pc.UpdateCalendarDays()
-		pc.Calendar.Update()
+	log.Printf("%#v\n", pc.Calendar)
+	log.Println("Calendar.Update()")
+	pc.Calendar.Update()
 
-		pc.Door.Write(pc.Calendar.Output())
-	*/
-}
+	log.Println("Calendar.Output()")
+	pc.Door.Write(pc.Calendar.Output())
 
-func int32toByteArray(i int32) (arr [4]byte) {
-	binary.BigEndian.PutUint32(arr[0:4], uint32(i))
-	return
-}
+	var has_playable_day bool = false
+	for x := 0; x < 31; x++ {
+		status := pc.Calendar_day_status[x]
+		if status == 0 || status == 1 {
+			has_playable_day = true
+			break
+		}
+	}
 
-func int64toByteArray(i int64) (arr [8]byte) {
-	binary.BigEndian.PutUint64(arr[0:8], uint64(i))
-	return
+	if !has_playable_day {
+		pc.Door.Write(door.CRNL + "Sorry, there are no days available to play." + door.CRNL)
+		r = press_a_key(pc.Door)
+		if r < 0 {
+			return r
+		} else {
+			return 'Q'
+		}
+	}
+
+	log.Println("Choose Day")
+	pc.Door.Write(door.CRNL + "Please choose a day : " + door.SavePos)
+
+AGAIN:
+	log.Println("Input")
+	var toplay string = pc.Door.Input(3)
+	log.Printf("Input was: %s\n", toplay)
+
+	pc.Door.Write(door.RestorePos)
+
+	var number int
+	var err error
+	number, err = strconv.Atoi(toplay)
+	if err != nil {
+		number = 0
+	}
+	if number == 0 {
+		return ' '
+	}
+
+	var status int
+	if number <= 31 {
+		status = pc.Calendar_day_status[number-1]
+		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)
+			r = pc.PlayCards()
+			if r == 'D' {
+				goto CALENDAR_UPDATE
+			}
+			return r
+		}
+		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)
+			if played < pc.Total_hands {
+				pc.Hand = played + 1
+				r = pc.PlayCards()
+				if r == 'D' {
+					goto CALENDAR_UPDATE
+				}
+				return r
+			}
+		}
+		goto AGAIN
+	}
+
+	return ' '
 }
 
 func (pc *PlayCards) PlayCards() int {
@@ -491,8 +671,8 @@ func (pc *PlayCards) PlayCards() int {
 
 	var game_width int
 	var game_height int = 20
-	pos := CardPos[27]
-	game_width = pos.X + 5
+	// pos := &CardPos[27]
+	game_width = CardPos[27].X + 5
 	MX := door.Width
 	MY := door.Height
 
@@ -523,11 +703,11 @@ Next_Hand:
 		seed_seq := make([]byte, 0)
 
 		for _, seed := range pc.Seeds {
-			ba := int32toByteArray(seed)
+			ba := Int32toByteArray(seed)
 			seed_seq = append(seed_seq, ba[:]...)
 			// sha1.Sum(ba[:])
 		}
-		pd := int64toByteArray(pc.Play_day_t)
+		pd := Int64toByteArray(pc.Play_day_t)
 		seed_seq = append(seed_seq, pd[:]...)
 		// We also need the hand # that we're playing.
 		seed_seq = append(seed_seq, byte(pc.Hand))
@@ -828,7 +1008,7 @@ Next_Hand:
 									// this seemed odd.  We quit the game ?
 									r = 'Q'
 								}
-
+								break
 							}
 						}
 
@@ -894,7 +1074,7 @@ func (pc *PlayCards) Redraw(Dealing bool) {
 
 	{
 		// Step 1:  Draw the deck "source"
-		pos := CardPos[29]
+		pos := &CardPos[29]
 
 		if pc.Play_card == 51 {
 			// out of cards
@@ -930,11 +1110,13 @@ func (pc *PlayCards) Redraw(Dealing bool) {
 	}
 
 	for x := 0; x < x_max; x++ {
-		pos := CardPos[x]
+		pos := &CardPos[x]
 		if Dealing {
 			time.Sleep(time.Duration(75) * time.Millisecond)
 		}
 
+		// log.Printf("Redraw(%d, %d)", x, pos.Level)
+
 		if Dealing {
 			c = &pc.DeckPanel.Backs[pos.Level]
 			c.X = pos.X + pc.Off_X

+ 0 - 10
space-ace.go

@@ -14,16 +14,6 @@ import (
 	"github.com/seehuhn/mt19937"
 )
 
-var SPACEACE string
-var SPACEACE_VERSION string
-var SPACEACE_COPYRIGHT string
-
-func init() {
-	SPACEACE = "Space Ace"
-	SPACEACE_VERSION = "0.0.2"
-	SPACEACE_COPYRIGHT = "(C) 2021 Bugz, Red-Green Software"
-}
-
 var Config map[string]string
 
 func press_a_key(d *door.Door) int {

+ 52 - 1
utils.go

@@ -1,6 +1,20 @@
 package main
 
-import "regexp"
+import (
+	"encoding/binary"
+	"regexp"
+	"time"
+)
+
+func Int32toByteArray(i int32) (arr [4]byte) {
+	binary.BigEndian.PutUint32(arr[0:4], uint32(i))
+	return
+}
+
+func Int64toByteArray(i int64) (arr [8]byte) {
+	binary.BigEndian.PutUint64(arr[0:8], uint64(i))
+	return
+}
 
 // Find all the words in a string
 // return [][2]int of index,length
@@ -16,3 +30,40 @@ func Abs(x int) int {
 	}
 	return x
 }
+
+// Adjust date to 2:00 AM
+func NormalizeDate(date *time.Time) {
+	offset := time.Duration(date.Second())*time.Second +
+		time.Duration(date.Minute())*time.Minute +
+		time.Duration(date.Hour()-2)*time.Hour +
+		time.Duration(date.Nanosecond())*time.Nanosecond
+	*date = date.Add(-offset)
+}
+
+// Find the 1st of the Month (Normalized) 2:00 AM
+func FirstOfMonthDate(date *time.Time) {
+	if date.Day() != 1 {
+		*date = date.AddDate(0, 0, 1-date.Day())
+	}
+	NormalizeDate(date)
+}
+
+// Find 1st of the Month (Normalize) Unix
+func FirstOfMonthDateUnix(dateUnix *int64) {
+	var date time.Time = time.Unix(*dateUnix, 0)
+	FirstOfMonthDate(&date)
+	*dateUnix = date.Unix()
+}
+
+// See:
+// https://yourbasic.org/golang/format-parse-string-time-date-example/
+// for all your time.Time.Format needs.
+
+func FormatDate(dateUnix int64, format string) string {
+	var date time.Time = time.Unix(dateUnix, 0)
+	return date.Format(format)
+}
+
+func CurrentMonth(date time.Time) string {
+	return date.Format("January")
+}