Browse Source

Space-Ace in go.

Steve Thielemann 3 years ago
commit
6afb7826a8
5 changed files with 539 additions and 0 deletions
  1. 7 0
      NOTES.txt
  2. 11 0
      go.mod
  3. 4 0
      go.sum
  4. 413 0
      space-ace.go
  5. 104 0
      starfield.go

+ 7 - 0
NOTES.txt

@@ -0,0 +1,7 @@
+
+go get github.com/seehuhn/mt19937
+
+rng := rand.New(mt19937.New())
+rng.Seed(time.Now().UnixNano())
+
+

+ 11 - 0
go.mod

@@ -0,0 +1,11 @@
+module red-green/space-ace
+
+go 1.17
+
+replace red-green/door => ../door
+
+require red-green/door v0.0.0-00010101000000-000000000000
+
+require github.com/mattn/go-sqlite3 v1.14.10
+
+require github.com/seehuhn/mt19937 v1.0.0 // indirect

+ 4 - 0
go.sum

@@ -0,0 +1,4 @@
+github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk=
+github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/seehuhn/mt19937 v1.0.0 h1:r02DuVkQXfohssWZO8L/TeAlYOah7aNNubEHB/7Vtfs=
+github.com/seehuhn/mt19937 v1.0.0/go.mod h1:RikyXajNu+1Gqxm4hOacc3ckyWRd0usF6IkE3gnEcAM=

+ 413 - 0
space-ace.go

@@ -0,0 +1,413 @@
+package main
+
+import (
+	"database/sql"
+	"fmt"
+	"math/rand"
+	"os"
+	"path/filepath"
+	"red-green/door"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+	"unicode"
+
+	_ "github.com/mattn/go-sqlite3"
+	"github.com/seehuhn/mt19937"
+)
+
+func pctUpdate(pct *int64) func() int64 {
+	return func() int64 {
+		return *pct
+	}
+}
+
+// Can I add a function to Door?
+// NO:  cannot define new methods on non-local type door.Door
+
+/*
+func (d *door.Door) PressAKey() {
+	d.Write(door.Reset + door.CRNL + "Press a key to continue...")
+	d.Key()
+	d.Write(door.CRNL)
+}
+*/
+
+func find_words(text string) [][]int {
+	word, _ := regexp.Compile("([A-Za-z]+)")
+	return word.FindAllStringIndex(text, -1)
+}
+
+func press_a_key(d *door.Door) int {
+	green := door.ColorText("BOLD GREEN")
+	blue := door.ColorText("BOLD BLUE")
+	yellow := door.ColorText("BOLD YELLOW")
+	any_caps := green
+	any_lower := yellow
+
+	any_color := func(text string) string {
+		var output string
+		var lastColor string
+
+		for _, letter := range text {
+			if unicode.IsUpper(letter) {
+				if lastColor != any_caps {
+					lastColor = any_caps
+					output += any_caps
+				}
+				output += string(letter)
+			} else {
+				if lastColor != any_lower {
+					lastColor = any_lower
+					output += any_lower
+				}
+				output += string(letter)
+			}
+		}
+		return output
+	}
+
+	text := "Press a key to continue..."
+	work_text := []rune(text)
+
+	d.Write(door.Reset + any_color(text))
+
+	var r int = -1
+	var t int
+	sleep_ms := 250
+	ms_sleep := 0
+
+	words := find_words(text)
+	var word int = 0
+	var wpos int = 0
+	current_word := text[words[word][0]:words[word][1]]
+	t = 0
+
+	r = d.WaitKey(0, int64(sleep_ms)*1000)
+
+	for r == -1 {
+		ms_sleep += sleep_ms
+
+		if ms_sleep > 1000 {
+			ms_sleep -= 1000
+			t++
+			if t >= int(door.Inactivity) {
+				d.Write(strings.Repeat("\b", len(text)))
+				d.Write(any_color(text) + door.CRNL)
+				return -1
+			}
+		}
+		wpos++
+
+		if wpos == len(current_word) {
+			word++
+			wpos = 0
+			work_text = []rune(text)
+
+			if word == len(words) {
+				word = 0
+				if any_caps == green {
+					any_caps = blue
+				} else {
+					any_caps = green
+				}
+				d.Write(strings.Repeat("\b", len(text)))
+				d.Write(any_color(string(work_text)))
+				current_word = text[words[word][0]:words[word][1]]
+				goto READKEY
+			}
+
+			current_word = text[words[word][0]:words[word][1]]
+		}
+
+		{
+			c := rune(current_word[wpos])
+
+			for !unicode.IsLower(c) {
+				wpos++
+				if wpos == len(current_word) {
+					wpos = 0
+					word++
+					work_text = []rune(text)
+					if word == len(words) {
+						word = 0
+					}
+					current_word = text[words[word][0]:words[word][1]]
+				}
+				c = rune(current_word[wpos])
+			}
+
+			// Ok, we found something that's lower case
+			work_text[words[word][0]+wpos] = unicode.ToUpper(c)
+		}
+		d.Write(strings.Repeat("\b", len(text)))
+		d.Write(any_color(string(work_text)))
+
+	READKEY:
+		r = d.WaitKey(0, int64(sleep_ms)*1000)
+	}
+
+	d.Write(strings.Repeat("\b", len(text)))
+	d.Write(any_color(text))
+	return r
+}
+
+func MainMenu() door.Menu {
+	// Make the main menu
+	m := door.Menu{Panel: door.Panel{Width: 45,
+		X:           5,
+		Y:           5,
+		Style:       door.DOUBLE,
+		Title:       "[ Main Menu: ]",
+		TitleOffset: 3,
+		BorderColor: door.ColorText("BRI CYAN ON BLA")}}
+	m.SelectedR = door.MakeMenuRender(door.ColorText("BOLD CYAN"),
+		door.ColorText("BOLD BLUE"),
+		door.ColorText("BOLD CYAN"),
+		door.ColorText("BOLD BLUE"))
+	m.UnselectedR = door.MakeMenuRender(door.ColorText("BOLD YEL ON BLUE"),
+		door.ColorText("BOLD WHI ON BLUE"),
+		door.ColorText("BOLD YEL ON BLUE"),
+		door.ColorText("BOLD CYAN ON BLUE"))
+
+	m.AddSelection("A", "ANSI Display")
+	m.AddSelection("D", "Display Information (dropfile, screen)")
+	m.AddSelection("I", "Input Prompt Demo")
+	m.AddSelection("P", "Progress Bars Demo")
+	m.AddSelection("S", "Show Panel")
+
+	m.AddSelection("Q", "Quit")
+	return m
+}
+
+func display_information(d *door.Door) {
+	d.Write(door.Clrscr)
+
+	keyColor := door.ColorText("BRI GREEN")
+	sepColor := door.ColorText("BRI YEL")
+	valColor := door.ColorText("CYAN")
+	nice_format := func(key string, value string) string {
+		return fmt.Sprintf("%s%-20s %s: %s%s", keyColor, key, sepColor, valColor, value) + door.CRNL
+	}
+
+	d.Write(nice_format("BBS Software", d.Config.BBSID))
+	d.Write(nice_format("Real Name", d.Config.Real_name))
+	d.Write(nice_format("Handle", d.Config.Handle))
+	d.Write(nice_format("User #", strconv.Itoa(d.Config.User_number)))
+	d.Write(nice_format("Security Level", strconv.Itoa(d.Config.Security_level)))
+	d.Write(nice_format("Node #", strconv.Itoa(d.Config.Node)))
+	d.Write(nice_format("Unicode", strconv.FormatBool(door.Unicode)))
+	d.Write(nice_format("CP437", strconv.FormatBool(door.CP437)))
+	d.Write(nice_format("Screen Size", fmt.Sprintf("%d X %d", door.Width, door.Height)))
+}
+
+func display_ansi(d *door.Door) {
+	space := SPACE()
+	d.Write(door.Clrscr)
+	for _, line := range space {
+		if door.Unicode {
+			d.Write(door.CP437_to_Unicode(line) + door.CRNL)
+		} else {
+			d.Write(line + door.CRNL)
+		}
+	}
+}
+
+func input_demo(d *door.Door) {
+	inputColor := door.ColorText("BRI WHI ON BLUE")
+	inputColor2 := door.ColorText("BRI WHI ON GREEN")
+	prompt := door.Line{Text: "What is YOUR Name: "}
+	prompt.RenderF = door.RenderBlueYellow
+	d.Write(prompt.Output() + inputColor)
+	name := d.Input(25)
+	d.Write(door.Reset + door.CRNL)
+	prompt.Text = "What is Your Quest: "
+	d.Write(prompt.Output() + inputColor2)
+	quest := d.Input(35)
+	d.Write(door.Reset + door.CRNL)
+	prompt.Text = "What is your Favorite CoLoR: "
+	d.Write(prompt.Output() + inputColor2)
+	color := d.Input(15)
+	d.Write(door.Reset + door.CRNL)
+	d.Write(fmt.Sprintf("You're %s on the %s quest, and fond of %s."+door.CRNL, name, quest, color))
+}
+
+func progress_bars(d *door.Door) {
+	d.Write(door.Clrscr)
+
+	bar := door.BarLine{Line: door.Line{DefaultColor: door.ColorText("BOLD YELLOW")}, Width: 20, Style: door.HALF_STEP}
+	bar2 := door.BarLine{Width: 30, Style: door.SOLID, PercentStyle: door.PERCENT_SPACE}
+
+	bar2.ColorRange = []door.BarRange{
+		{2500, door.ColorText("RED")},
+		{5000, door.ColorText("BROWN")},
+		{7500, door.ColorText("BOLD YEL")},
+		{9500, door.ColorText("GREEN")},
+		{10100, door.ColorText("BRI GRE")}}
+
+	bar3 := door.BarLine{Width: 15, Style: door.GRADIENT, Line: door.Line{DefaultColor: door.ColorText("CYAN")}}
+
+	var percentage int64
+	bar.UpdateP = pctUpdate(&percentage)
+	bar2.UpdateP = pctUpdate(&percentage)
+	bar3.UpdateP = pctUpdate(&percentage)
+
+	update_bars := func() {
+		bar.Update()
+		bar2.Update()
+		bar3.Update()
+	}
+
+	d.Write(door.Goto(3, 12) + "Half-Step")
+	d.Write(door.Goto(25, 12) + "% with space and Color Range")
+	d.Write(door.Goto(57, 12) + "Gradient")
+
+	bar_start := door.Goto(3, 15)
+
+	for f := 0; f <= 100; f++ {
+		percentage = int64(f * 100)
+
+		update_bars()
+		d.Write(bar_start + bar.Output() + "  " + door.Reset + bar2.Output() + door.Reset + "  " + bar3.Output())
+
+		if d.Disconnected {
+			// don't continue to sleep if we're disconnected
+			break
+		}
+
+		time.Sleep(time.Millisecond * 100)
+	}
+
+}
+
+func panel_demo(d *door.Door) {
+	width := 55
+	fmtStr := "%-55s"
+	p := door.Panel{X: 5, Y: 5, Width: width, Style: door.DOUBLE, BorderColor: door.ColorText("CYAN ON BLUE"), Title: "[ Panel Demo ]"}
+
+	lineColor := door.ColorText("BRIGHT WHI ON BLUE")
+	// Add lines to the panel
+	for _, line := range []string{"The BBS Door Panel Demo", "(C) 2021 Red Green Software, https://red-green.com"} {
+		if door.Unicode {
+			line = strings.Replace(line, "(C)", "\u00a9", -1)
+		}
+		l := door.Line{Text: fmt.Sprintf(fmtStr, line), DefaultColor: lineColor}
+		p.Lines = append(p.Lines, l)
+	}
+	p.Lines = append(p.Lines, p.Spacer())
+	p.Lines = append(p.Lines, door.Line{Text: fmt.Sprintf(fmtStr, "Welcome to golang!"), DefaultColor: lineColor})
+
+	d.Write(door.Clrscr)
+	d.Write(p.Output())
+}
+
+func createTable(db *sql.DB) {
+	db.Exec("CREATE TABLE IF NOT EXISTS settings(username TEXT, setting TEXT, value TEXT, PRIMARY KEY(username, setting));")
+	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\"));")
+}
+
+func main() {
+	var message string
+
+	// Get path to binary, and chdir to it.
+	binaryPath, _ := os.Executable()
+	binaryPath = filepath.Dir(binaryPath)
+	_ = os.Chdir(binaryPath)
+
+	fmt.Println("Starting space-ace")
+
+	rng := rand.New(mt19937.New())
+	rng.Seed(time.Now().UnixNano())
+
+	sqliteDatabase, err := sql.Open("sqlite3", "./space-database.db")
+	if err != nil {
+		fmt.Printf("%#v\n", err)
+	}
+	defer sqliteDatabase.Close()
+	createTable(sqliteDatabase)
+	d := door.Door{}
+
+	d.Init("space-ace")
+
+	s := StarField{}
+	s.Regenerate()
+
+	s.Display(&d)
+	d.Write(door.Goto(1, 1) + door.Reset)
+	d.Key()
+
+	bold := door.Color(1, 37, 40)
+	bolder := door.ColorText("BLI BOLD YEL ON BLUE")
+	d.Write("Welcome to " + bolder + "door32.sys" + door.Reset + door.CRNL + "..." + door.CRNL)
+	key := press_a_key(&d)
+	d.Write(fmt.Sprintf("Key %s%d / %x%s", bold, key, key, door.Reset) + door.CRNL)
+
+	b := door.AlertBox("Warning: golang is in use!", 1)
+	d.Write(door.ColorText("BRI WHI ON GREEN"))
+	for _, line := range b {
+		d.Write(line + door.CRNL)
+	}
+	d.Write(door.Reset + door.CRNL)
+
+	left := d.TimeLeft()
+
+	message = fmt.Sprintf("You have %0.2f minutes / %0.2f seconds remaining..."+door.CRNL, left.Minutes(), left.Seconds())
+	d.Write(message)
+
+	press_a_key(&d)
+
+	mainmenu := MainMenu()
+	var choice int
+
+	for choice >= 0 {
+		d.Write(door.Clrscr)
+		choice = mainmenu.Choose(&d)
+
+		if choice < 0 {
+			break
+		}
+		option := mainmenu.GetOption(choice)
+
+		// fmt.Printf("Choice: %d, Option: %c\n", choice, option)
+
+		switch option {
+		case 'A':
+			display_ansi(&d)
+			press_a_key(&d)
+		case 'D':
+			display_information(&d)
+			press_a_key(&d)
+		case 'I':
+			d.Write(door.Reset + door.CRNL + door.CRNL)
+			input_demo(&d)
+			press_a_key(&d)
+		case 'P':
+			progress_bars(&d)
+			press_a_key(&d)
+		case 'S':
+			panel_demo(&d)
+			press_a_key(&d)
+		case 'Q':
+			choice = -1
+			break
+		}
+
+	}
+
+	d.Write(door.Reset + door.CRNL)
+
+	message = fmt.Sprintf("Returning to %s ..."+door.CRNL, d.Config.BBSID)
+	d.Write(message)
+
+	d.WaitKey(1, 0)
+	left = d.TimeLeft()
+
+	message = fmt.Sprintf("You had %0.2f minutes / %0.2f seconds remaining!"+door.CRNL, left.Minutes(), left.Seconds())
+	d.Write(message)
+
+	left = d.TimeUsed()
+	d.Write(fmt.Sprintf("You used %0.2f seconds / %0.2f minutes."+door.CRNL, left.Seconds(), left.Minutes()))
+
+	fmt.Println("Exiting space-ace")
+}

+ 104 - 0
starfield.go

@@ -0,0 +1,104 @@
+package main
+
+import (
+	"fmt"
+	"math/rand"
+	"red-green/door"
+	"strings"
+	"time"
+
+	"github.com/seehuhn/mt19937"
+)
+
+type StarPos struct {
+	Y      int
+	X      int
+	Symbol int
+	Color  bool
+}
+
+type StarField struct {
+	RNG *rand.Rand
+	MX  int
+	MY  int
+	Sky map[StarPos]bool
+}
+
+func (s *StarField) make_pos() StarPos {
+	for {
+		pos := StarPos{X: s.RNG.Intn(s.MX), Y: s.RNG.Intn(s.MY)}
+		_, found := (*s).Sky[pos]
+		if !found {
+			return pos
+		}
+	}
+}
+
+func (s *StarField) Regenerate() {
+	s.MX = door.Width
+	s.MY = door.Height
+	s.Sky = make(map[StarPos]bool)
+
+	s.RNG = rand.New(mt19937.New())
+	s.RNG.Seed(time.Now().UnixNano())
+
+	max_stars := ((s.MX * s.MY) / 40)
+	for i := 0; i < max_stars; i++ {
+		pos := s.make_pos()
+		pos.Symbol = i % 2
+		pos.Color = i%5 < 2
+		s.Sky[pos] = true
+	}
+}
+
+func (s *StarField) Display(d *door.Door) {
+	white := door.ColorText("WHITE")
+	dark := door.ColorText("BOLD BLACK")
+
+	d.Write(door.Reset + door.Clrscr)
+	var stars [2]string
+	stars[0] = "."
+	if door.Unicode {
+		stars[1] = "\u2219"
+	} else {
+		stars[1] = "\xf9"
+	}
+
+	var i int = 0
+	var last_pos StarPos
+
+	for pos, _ := range s.Sky {
+		use_goto := true
+
+		if i != 0 {
+			if pos.Y == last_pos.Y {
+				dx := pos.X - last_pos.X
+				if dx <= 0 {
+					use_goto = false
+				} else {
+					if dx < 5 {
+						d.Write(strings.Repeat(" ", dx))
+						use_goto = false
+					} else {
+						d.Write(fmt.Sprintf("\x1b[%dC", dx))
+						use_goto = false
+					}
+				}
+			}
+		}
+
+		if use_goto {
+			d.Write(door.Goto(pos.X, pos.Y))
+		}
+
+		if pos.Color {
+			d.Write(dark)
+		} else {
+			d.Write(white)
+		}
+		d.Write(stars[pos.Symbol])
+		last_pos = pos
+		last_pos.X++
+		i++
+	}
+}