package main import ( "crypto/sha1" "encoding/binary" "fmt" "math/rand" "red-green/door" "strconv" "strings" "time" "unicode" ) func StringToANSIColor(colorCode string) string { colors := []string{ "BLUE", "BROWN", "RED", "CYAN", "GREEN", "MAGENTA", "WHITE", } code := strings.ToUpper(colorCode) for _, c := range colors { if c == code { return door.ColorText(code) } } if code == "ALL" { rand.Seed(time.Now().UnixNano()) pos := rand.Intn(len(colors)) return door.ColorText(colors[pos]) } return door.ColorText("WHITE") } 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 Calendar door.Panel DeckPanel Deck Deck []DeckType State []DeckType Off_X int Off_Y 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) } func cmdLineRender(bracket string, inner string, outer string) func(string) string { cmdRender := func(input string) string { var result string inOuter := true var lastColor string for _, c := range input { if c == '[' { inOuter = false if lastColor != bracket { result += bracket lastColor = bracket } result += string(c) continue } if c == ']' { inOuter = true if lastColor != bracket { result += bracket lastColor = bracket } result += string(c) continue } if inOuter { if lastColor != outer { result += outer lastColor = outer } } else { if lastColor != inner { result += inner lastColor = inner } } result += string(c) } return result } return cmdRender } func (pc *PlayCards) InitValues() { hands := pc.DB.GetSetting("hands_per_day", "3") pc.Total_hands, _ = strconv.Atoi(hands) pc.Play_card = 28 pc.Current_streak = 0 pc.Best_streak = 0 best := pc.DB.GetSetting("best_streak", "0") pc.Best_streak, _ = strconv.Atoi(best) pc.Select_card = 23 pc.Score = 0 } func (pc *PlayCards) Init() { // init_values() pc.InitValues() // PlayCards::PlayCards() pc.Play_day = time.Now() NormalizeDate(&pc.Play_day) pc.Play_day_t = pc.Play_day.Unix() pc.DeckPanel.Init() var last_played int64 last := pc.DB.GetSetting("last_played", "0") last_played, _ = strconv.ParseInt(last, 10, 64) 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)) pc.DB.SetSetting("days_played", "0") pc.Days_played = 0 } pc.Seeds = make([]int32, 0) // config _seed parts := strings.Split((*pc.Config)["_seed"], ",") for _, seed := range parts { i, _ := strconv.ParseInt(seed, 10, 32) pc.Seeds = append(pc.Seeds, int32(i)) } pc.Hand = 0 pc.Total_hands = 0 // spaceAceTriPeaks = make_tripeaks(); { tripeakstext := " " + SPACEACE + " - Tri-Peaks Solitaire v" + SPACEACE_VERSION + " " pc.SpaceAceTriPeaks = door.Panel{Width: len(tripeakstext), Style: door.SINGLE, BorderColor: door.ColorText("CYAN ON BLACK"), } pc.SpaceAceTriPeaks.Lines = append(pc.SpaceAceTriPeaks.Lines, door.Line{Text: tripeakstext}) } svRender := RenderStatusValue(door.ColorText("BOLD WHITE ON BLUE"), door.ColorText("BOLD YELLOW ON BLUE")) // score_panel = make_score_panel(); { W := 25 pc.ScorePanel = door.Panel{Width: W} text := "Name: " + pc.Door.Config.Real_name pc.ScorePanel.Lines = append(pc.ScorePanel.Lines, door.Line{Text: fmt.Sprintf("%*s", -W, text), RenderF: svRender}) scoreUpdate := func() string { txt := fmt.Sprintf("Score: %d", pc.Score) txt += strings.Repeat(" ", W-len(txt)) return txt } pc.ScorePanel.Lines = append(pc.ScorePanel.Lines, door.Line{Text: scoreUpdate(), UpdateF: scoreUpdate, RenderF: svRender, }) timeUpdate := func() string { var left int = int(pc.Door.TimeLeft().Minutes()) var used int = int(pc.Door.TimeUsed().Minutes()) txt := fmt.Sprintf("Time used: %3d / %3d", used, left) txt += strings.Repeat(" ", W-len(txt)) return txt } pc.ScorePanel.Lines = append(pc.ScorePanel.Lines, door.Line{Text: timeUpdate(), UpdateF: timeUpdate, RenderF: svRender, }) handUpdate := func() string { txt := fmt.Sprintf("Playing Hand %d of %d", pc.Hand, pc.Total_hands) txt += strings.Repeat(" ", W-len(txt)) return txt } pc.ScorePanel.Lines = append(pc.ScorePanel.Lines, door.Line{Text: handUpdate(), UpdateF: handUpdate, RenderF: svRender, }) } // streak_panel = make_streak_panel(); { W := 20 pc.StreakPanel = door.Panel{Width: W} dateUpdate := func() string { format, ok := (*pc.Config)["date_format"] if !ok { format = "January 2" } txt := fmt.Sprintf("Playing: %s", pc.Play_day.Format(format)) txt += strings.Repeat(" ", W-len(txt)) return txt } pc.StreakPanel.Lines = append(pc.StreakPanel.Lines, door.Line{Text: dateUpdate(), UpdateF: dateUpdate, RenderF: svRender, }) currentUpdate := func() string { txt := fmt.Sprintf("Current Streak: %d", pc.Current_streak) txt += strings.Repeat(" ", W-len(txt)) return txt } pc.StreakPanel.Lines = append(pc.StreakPanel.Lines, door.Line{Text: currentUpdate(), UpdateF: currentUpdate, RenderF: svRender, }) longestUpdate := func() string { txt := fmt.Sprintf("Longest Streak: %d", pc.Best_streak) txt += strings.Repeat(" ", W-len(txt)) return txt } pc.StreakPanel.Lines = append(pc.StreakPanel.Lines, door.Line{Text: longestUpdate(), UpdateF: longestUpdate, RenderF: svRender, }) } // left_panel = make_left_panel(); { W := 13 pc.LeftPanel = door.Panel{Width: W} leftUpdate := func() string { txt := fmt.Sprintf("Cards left:%d", 51-pc.Play_card) txt += strings.Repeat(" ", W-len(txt)) return txt } pc.LeftPanel.Lines = append(pc.LeftPanel.Lines, door.Line{Text: leftUpdate(), UpdateF: leftUpdate, RenderF: svRender, }) } // cmd_panel = make_command_panel(); { W := 76 pc.CmdPanel = door.Panel{Width: W} var commands string if door.Unicode { commands = "[4/\u25c4] Left [6/\u25ba] Right [Space] Play Card [Enter] Draw [Q]uit [R]edraw [H]elp" commands += strings.Repeat(" ", W-len([]rune(commands))) } else { commands = "[4/\x11] Left [6/\x10] Right [Space] Play Card [Enter] Draw [Q]uit [R]edraw [H]elp" commands += strings.Repeat(" ", W-len(commands)) } cmdRender := cmdLineRender(door.ColorText("BOLD YELLOW ON BLUE"), door.ColorText("BOLD CYAN ON BLUE"), door.ColorText("BOLD GREEN ON BLUE")) pc.CmdPanel.Lines = append(pc.CmdPanel.Lines, door.Line{Text: commands, RenderF: cmdRender}) } // next_quit_panel = make_next_panel(); { W := 50 pc.NextQuitPanel = door.Panel{Width: W, Style: door.DOUBLE, BorderColor: door.ColorText("BOLD YELLOW ON BLUE"), } nextUpdate := func() string { var text string if pc.Select_card != -1 { text = "[C]ontinue this hand" } if pc.Hand < pc.Total_hands { text += " [N]ext Hand [Q]uit" } else { text += " [D]one [Q]uit" } text = " " + text + " " text += strings.Repeat(" ", W-len(text)) return text } pc.NextQuitPanel.Lines = append(pc.NextQuitPanel.Lines, door.Line{Text: nextUpdate(), UpdateF: nextUpdate, RenderF: cmdLineRender(door.ColorText("BOLD YEL ON BLU"), door.ColorText("BOLD CYAN ON BLU"), door.ColorText("BOLD GREE ON BLUE"))}) } // calendar = make_calendar(); { W := 41 pc.Calendar = door.Panel{Width: W, Style: door.DOUBLE, BorderColor: door.ColorText("CYAN ON BLACK")} calendarRender := func(text string) string { // var result string // var lastColor string result := door.Render{Line: text} digits := door.ColorText("CYAN ON BLACK") digits_play := door.ColorText("BOLD CYAN ON BLACK") spaces := door.ColorText("WHITE ON BLACK") open := door.ColorText("BOLD GREEN ON BLACK") hands := door.ColorText("BOLD YELLOW ON BLACK") full := door.ColorText("RED ON BLACK") nny := door.ColorText("BOLD BLACK ON BLACK") for days := 0; days < 7; days++ { var dayText string if days == 0 { result.Append(spaces, 1) } dayText = text[1+days*6 : (1+days*6)+3] if dayText[1] == ' ' { result.Append(spaces, 3) } else { // Something is here cday := dayText[2] if cday == 'o' || cday == 'h' { result.Append(digits_play, 2) } else { result.Append(digits, 2) } switch cday { case 'o': result.Append(open, 1) case 'h': result.Append(hands, 1) case 'x': result.Append(full, 1) case 'u': result.Append(nny, 1) } } if days == 6 { result.Append(spaces, 1) } else { result.Append(spaces, 3) } } return result.Result } for row := 0; row < 6; row++ { calendarUpdate := func() string { var text string for d := 0; d < 7; d++ { text += " " v := pc.Day_status[(row*7)+d] if v == 0 { text += " " } else { text += fmt.Sprintf("%-2d", v) status := pc.Day_status[v-1] switch status { case 0: text += "o" case 1: text += "h" case 2: text += "x" case 3: text += "u" } } if d == 6 { text += " " } else { text += " " } } return text } pc.Calendar.Lines = append(pc.Calendar.Lines, door.Line{Text: calendarUpdate(), UpdateF: calendarUpdate, RenderF: calendarRender}) } } } func (pc *PlayCards) Play() { // this defaults (for now) to playing today (if unplayed). // Otherwise display calendar. pc.Play_day = time.Now() NormalizeDate(&pc.Play_day) pc.Play_day_t = pc.Play_day.Unix() // played := db.HandsPlayedOnDay(pc.Play_day_t) played := 0 var r int if played == 0 { pc.Door.Write("Let's play today..." + door.CRNL) time.Sleep(time.Second) pc.Hand = 1 r = pc.PlayCards() if r != 'D' { return } } else { if played < pc.Total_hands { // let's finish today... pc.Hand = played + 1 r = pc.PlayCards() if r != 'D' { return } } } /* CALENDAR_UPDATE: pc.UpdateCalendarDays() pc.Calendar.Update() pc.Door.Write(pc.Calendar.Output()) */ } 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 } func (pc *PlayCards) PlayCards() int { pc.InitValues() var game_width int var game_height int = 20 pos := CardPos[27] game_width = pos.X + 5 MX := door.Width MY := door.Height pc.Off_X = (MX - game_width) / 2 pc.Off_Y = (MY - game_height) / 2 // _ = off_x // We can now position things properly centered pc.SpaceAceTriPeaks.X = (MX - pc.SpaceAceTriPeaks.Width) / 2 pc.SpaceAceTriPeaks.Y = pc.Off_Y pc.Off_Y += 3 currentDefault := pc.DB.GetSetting("DeckColor", "ALL") Next_Hand: deck_color := StringToANSIColor(currentDefault) pc.DeckPanel.SetBackColor(deck_color) pc.Play_card = 28 pc.Select_card = 23 pc.Score = 0 pc.Current_streak = 0 // Use play day to seed RNG { // Secret Squirrel method of seeding the RNG sha1 := sha1.New() seed_seq := make([]byte, 0) for _, seed := range pc.Seeds { ba := int32toByteArray(seed) seed_seq = append(seed_seq, ba[:]...) // sha1.Sum(ba[:]) } 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)) result := sha1.Sum(seed_seq) var seed int64 = int64(binary.BigEndian.Uint64(result[0:8])) pc.RNG.Seed(seed) pc.Deck = ShuffleCards(pc.RNG, 1) pc.State = MakeCardStates(1) } // Position the panels { off_yp := pc.Off_Y + 11 left_panel_x := CardPos[18].X right_panel_x := CardPos[15].X pc.ScorePanel.X = left_panel_x + pc.Off_X pc.ScorePanel.Y = off_yp pc.StreakPanel.X = right_panel_x + pc.Off_X pc.StreakPanel.Y = off_yp pc.CmdPanel.X = left_panel_x + pc.Off_X pc.CmdPanel.Y = off_yp + 5 next_off_x := (MX - pc.NextQuitPanel.Width) / 2 pc.NextQuitPanel.X = next_off_x pc.NextQuitPanel.Y = CardPos[10].Y + pc.Off_Y } var Dealing bool = true var r int = 0 pc.Redraw(Dealing) Dealing = false pc.LeftPanel.Update() pc.Door.Write(door.Reset) var c *door.Panel var in_game bool = true var save_streak bool = false var waiting int = 0 for in_game { var output string output = pc.ScorePanel.Update() if output != "" { pc.Door.Write(output) { // Redisplay Mark / Selected Card Pos := &CardPos[pc.Select_card] c = &pc.DeckPanel.Mark[1] c.X = Pos.X + pc.Off_X c.Y = Pos.Y + pc.Off_Y pc.Door.Write(c.Output()) } } if save_streak { save_streak = false pc.DB.SetSetting("best_streak", strconv.Itoa(pc.Best_streak)) } waiting = 0 for waiting < int(door.Inactivity) { r = pc.Door.WaitKey(1, 0) if r == -1 { // TIMEOUT is expected here waiting++ output = pc.ScorePanel.Update() if output != "" { pc.Door.Write(output) { // Redisplay Mark / Selected Card Pos := &CardPos[pc.Select_card] c = &pc.DeckPanel.Mark[1] c.X = Pos.X + pc.Off_X c.Y = Pos.Y + pc.Off_Y pc.Door.Write(c.Output()) } } } else { break } } if r > 0 { // Not a timeout if r < 0x1000 { // not a function key switch unicode.ToUpper(rune(r)) { case '\x0d': // Next Card if pc.Current_streak > pc.Best_streak { pc.Best_streak = pc.Current_streak save_streak = true pc.Door.Write(pc.StreakPanel.Update()) } if pc.Play_card < 51 { pc.Play_card++ pc.Current_streak = 0 pc.Door.Write(pc.StreakPanel.Update()) pc.Door.Write(pc.LeftPanel.Update()) // Deal the next card if pc.Play_card == 51 { // out of cards cpos := &CardPos[29] c = &pc.DeckPanel.Backs[0] c.X = cpos.X + pc.Off_X c.Y = cpos.Y + pc.Off_Y pc.Door.Write(c.Output()) } cpos := &CardPos[28] c = &pc.DeckPanel.Cards[pc.Deck[pc.Play_card]] c.X = cpos.X + pc.Off_X c.Y = cpos.Y + pc.Off_Y pc.Door.Write(c.Output()) } case 'R': pc.Redraw(false) case 'Q': // Possibly prompt here for [N]ext hand or [Q]uit if pc.Current_streak > pc.Best_streak { pc.Best_streak = pc.Current_streak pc.Door.Write(pc.StreakPanel.Update()) } pc.NextQuitPanel.Update() pc.Door.Write(pc.NextQuitPanel.Output()) if pc.State[26] == 2 { pc.Door.Write(door.ColorText("BLACK")) } else { pc.Door.Write(door.Reset) } if pc.Hand < pc.Total_hands { r = pc.Door.GetOneOf("CNQ") } else { r = pc.Door.GetOneOf("CDQ") } if r == 'C' { // Continue pc.Redraw(false) } else { if r == 'D' || r == 'Q' { if pc.Score >= 50 { // pc.DB.SaveScore(now, pc.Time_T, .. pc.Score) _ = pc.Score } in_game = false } else { if r == 'N' { // pc.DB.SaveScore(...) _ = pc.Score pc.Hand++ goto Next_Hand } } } } } } } return 0 } func (pc *PlayCards) Redraw(Dealing bool) { // stars.display() pc.Door.Write(door.Clrscr + door.Reset) pc.Door.Write(pc.SpaceAceTriPeaks.Output()) var c *door.Panel { // Step 1: Draw the deck "source" pos := CardPos[29] if pc.Play_card == 51 { // out of cards pos.Level = 0 } c = &pc.DeckPanel.Backs[pos.Level] c.X = pos.X + pc.Off_X c.Y = pos.Y + pc.Off_Y pc.LeftPanel.X = pos.X + pc.Off_X pc.LeftPanel.Y = pos.Y + pc.Off_Y + 3 // const height see deck pc.ScorePanel.Update() pc.LeftPanel.Update() pc.StreakPanel.Update() pc.CmdPanel.Update() pc.Door.Write(pc.ScorePanel.Output()) pc.Door.Write(pc.LeftPanel.Output()) pc.Door.Write(pc.StreakPanel.Output()) pc.Door.Write(pc.CmdPanel.Output()) pc.Door.Write(c.Output()) if Dealing { time.Sleep(time.Second) } } var x_max int = 29 if Dealing { x_max = 28 } for x := 0; x < x_max; x++ { pos := CardPos[x] if Dealing { time.Sleep(time.Duration(75) * time.Millisecond) } if Dealing { c = &pc.DeckPanel.Backs[pos.Level] c.X = pos.X + pc.Off_X c.Y = pos.Y + pc.Off_Y pc.Door.Write(c.Output()) } else { switch pc.State[x] { case 0: c = &pc.DeckPanel.Backs[pos.Level] c.X = pos.X + pc.Off_X c.Y = pos.Y + pc.Off_Y pc.Door.Write(c.Output()) case 1: if x == 28 { c = &pc.DeckPanel.Cards[pc.Deck[pc.Play_card]] } else { c = &pc.DeckPanel.Cards[pc.Deck[x]] } c.X = pos.X + pc.Off_X c.Y = pos.Y + pc.Off_Y pc.Door.Write(c.Output()) case 2: // no card to draw. case 3: // peak cleared, draw bonus var output string = door.Goto(pos.X+pc.Off_X, pos.Y+pc.Off_Y) output += Bonus() pc.Door.Write(output) } } } if Dealing { for x := 18; x < 29; x++ { pc.State[x] = 1 // CardPos[x] X Y Level Pos := &CardPos[x] time.Sleep(time.Duration(200) * time.Millisecond) c = &pc.DeckPanel.Cards[pc.Deck[x]] c.X = Pos.X + pc.Off_X c.Y = Pos.Y + pc.Off_Y pc.Door.Write(c.Output()) } } { Pos := &CardPos[pc.Select_card] c = &pc.DeckPanel.Mark[1] c.X = Pos.X + pc.Off_X c.Y = Pos.Y + pc.Off_Y pc.Door.Write(c.Output()) } } func Bonus() string { return door.ColorText("BOLD YELLOW") + "BONUS" }