town/cmd/signup/main.go

351 lines
8.9 KiB
Go

package main
import (
"bytes"
"fmt"
"log"
"os"
"path"
"strings"
"time"
"git.tilde.town/tildetown/town/signup"
"github.com/MakeNowJust/heredoc/v2"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
// TODO remove sidebar
// TODO add /help
const (
maxInputLength = 10000
logDir = "/town/var/signup"
)
type scene struct {
Name string
Description string
Host *character
Write func([]byte)
}
type character struct {
Name string
Description string
}
func newCharacter(name, description string) *character {
return &character{
Name: name,
Description: description,
}
}
func (c *character) Say(msg string) string {
verb := "says"
if c.Name == "you" {
verb = "say"
}
return fmt.Sprintf("[-:-:b]%s[-:-:-] %s: '%s'",
c.Name,
verb,
strings.TrimSpace(msg))
}
func main() {
logFile := path.Join(logDir, fmt.Sprintf("%d", time.Now().Unix()))
logF, err := os.Create(logFile)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
logger := log.New(logF, "", log.Ldate|log.Ltime)
db, err := signup.NewDB()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
err = _main(logger, db)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(3)
}
}
func _main(l *log.Logger, db *signup.DB) error {
l.Println("starting a session")
pages := tview.NewPages()
mainFlex := tview.NewFlex()
innerFlex := tview.NewFlex()
input := tview.NewTextArea()
input.SetBorder(true).SetBorderColor(tcell.ColorPaleTurquoise)
input.SetTitle("press ctrl+d to send")
input.SetMaxLength(2000)
title := tview.NewTextView()
title.SetDynamicColors(true)
title.SetTextAlign(tview.AlignCenter)
title.SetText("[purple]the tilde town sign up portal[-]")
msgScroll := tview.NewTextView()
msgScroll.SetDynamicColors(true)
msgScroll.SetBackgroundColor(tcell.ColorBlack)
msgScroll.SetTextColor(tcell.ColorWhite)
sidebar := tview.NewTextView()
sidebar.SetBorder(true)
sidebar.SetDynamicColors(true)
sidebar.SetBackgroundColor(tcell.ColorBlack)
sidebar.SetTextColor(tcell.ColorGray)
sidebar.SetText(heredoc.Doc(`
[-:-:b]hey here are some hints[-:-:-]
quit by pressing [-:-:b]ctrl-c[-:-:-]
(your responses won't be saved)
type stuff. send it with [-:-:b]ctrl+d[-:-:-]
try a [-:-:b]verb[-:-:-] using [-:-:b]/[-:-:-] like:
[-:-:b]/nod[-:-:-]
`))
innerFlex.SetDirection(tview.FlexColumn)
innerFlex.AddItem(msgScroll, 0, 3, false)
innerFlex.AddItem(sidebar, 0, 1, false)
mainFlex.SetDirection(tview.FlexRow)
mainFlex.AddItem(title, 1, -1, false)
mainFlex.AddItem(innerFlex, 0, 1, false)
mainFlex.AddItem(input, 5, -1, true)
pages.AddPage("main", mainFlex, true, true)
app := tview.NewApplication()
app.SetRoot(pages, true)
player := newCharacter("you", "TODO")
su := &signup.TownSignup{ID: -1}
save := func() {
su.Created = time.Now()
err := db.InsertSignup(su)
if err != nil {
l.Printf("failed to write to db: %s", err.Error())
l.Printf("dumping values: %#v", su)
return
}
}
scenes := []*scene{
{
Name: "start",
Description: heredoc.Doc(`
You open your eyes.
You're in some kind of workshop.
Wires and computers in various state of disrepair are strewn across
tables and shelves. It smells faintly of burnt cedar.
The wires and components before you slowly drag
themselves into the shape of a small humanoid.
[-:-:b]wire guy says:[-:-:-]
hello, welcome to the application for membership in tilde town.
first, please let me know what a good [-:-:b]email address[-:-:-] is for you?
just say it out loud. as many times as you need. to get it right.
when you're ready to move on, [-:-:b]/nod[-:-:-]
`),
Host: newCharacter("wire guy", "a lil homonculus made of discarded computer cables"),
Write: func(b []byte) {
su.Email = string(b)
},
},
{
Name: "nodded",
Description: heredoc.Doc(`
The workshop fades away. You hear the sound of a dial up modem
in the distance.
Trees spring up out of the ground around you: birches, oaks, maples,
firs, yews, pines, cypresses complete with tiny swamps around their trunks,
junipers, redwoods, cedars, towering palms waving gently in a breeze, eucalyptus,
banyan. the smell is riotous like a canvas with all the colors splashed on. birds
start to sing.
a shrike alights on a branch in front of you.
[-:-:b]the shrike says:[-:-:-]
phweeturpff. how did you find out about the town? did anyone refer you?
just say your answer out loud. when you've said what you want, [-:-:b]/lean[-:-:-]
against a tree.
`),
Write: func(b []byte) {
su.How = string(b)
},
Host: newCharacter("the shrike", "a little grey bird. it has a pretty song."),
},
{
Name: "leaned",
Description: heredoc.Doc(`
You sink backwards into the forest. You find yourself floating in darkness.
At the far reaches of your vision you can make out a faint neon grid. Around you
float pieces of consumer electronic appliances from 1980s Earth. A VCR approaches
you and speaks, flapping its tape slot cover with each word.
[-:-:b]the vcr says:[-:-:-]
welcome! thank you for coming this far. just two questions left. what about
tilde town interests you? what kind of stuff might you want to get up to here?
as usual, just say your answer. when you're satisfied, please [-:-:b]/spin[-:-:-]
around in this void.
`),
Write: func(b []byte) {
su.Why = string(b)
},
Host: newCharacter("the vcr", "a black and grey VCR from 1991"),
},
{
Name: "spun",
Description: heredoc.Doc(`
You realize your eyes have been shut. You open them and, in an instant,
the neon grid and polygons are gone. You're in a convenience store. Outside
it's dark besides a single pool of light coming from a street lamp. it's illuminating
some litter and a rusty, blue 1994 pontiac grand am.
The shelves around you are stocked with products you've never heard of before like
Visible Pants, Petty Burgers, Gentle Rice, Boo Sponge, Power Banjo, Superware, Kneephones,
and Diet Coagulator. A mop is mopping the floor and turns to you.
[-:-:b]the mop says:[-:-:-]
swishy slop. last question. where online can we get to know you? do you have a personal
website or social media presence? we'll take whatever.
say some links and words out loud, you know the drill.
when you're happy you can submit this whole experience by leaving the
store. just [-:-:b]/open[-:-:-] the door.
`),
Write: func(b []byte) {
su.Links = string(b)
},
Host: newCharacter("the mop", "a greying mop with a wooden handle."),
},
{
Name: "done",
Description: heredoc.Doc(`
thank you for applying to tilde.town!
please be on the look out for an email from [-:-:b]root@tilde.town[-:-:-]
you can [-:-:b]/quit[-:-:-] now
ok bye have a good one~
`),
Write: func(b []byte) {
su.Extra = string(b)
},
},
}
sceneIx := 0
currentScene := scenes[sceneIx]
inputBuff := []byte{}
inputWriter := bytes.NewBuffer(inputBuff)
advanceScene := func(fromScene, sorryMsg string) {
if currentScene.Name != fromScene {
return
}
if inputWriter.Len() == 0 {
fmt.Fprintln(msgScroll, currentScene.Host.Say(sorryMsg))
return
}
currentScene.Write(inputBuff)
inputBuff = []byte{}
inputWriter = bytes.NewBuffer(inputBuff)
sceneIx++
currentScene = scenes[sceneIx]
fmt.Fprintln(msgScroll, heredoc.Doc(`
[purple]----------[-:-:-]
`))
fmt.Fprintln(msgScroll, currentScene.Description)
}
handleInput := func(msg string) {
msg = strings.TrimSpace(msg)
if msg == "" {
return
}
if strings.HasPrefix(msg, "/") {
split := strings.Split(msg, " ")
if len(split) > 0 {
msg = split[0]
}
switch strings.TrimPrefix(msg, "/") {
case "quit":
l.Println("got /quit")
app.Stop()
case "look":
fmt.Fprintln(msgScroll, "")
fmt.Fprintln(msgScroll, currentScene.Description)
case "nod":
advanceScene("start",
"i'm sorry, before going further could you share an email with me?")
case "lean":
advanceScene("nodded", "phweeturpff")
case "spin":
advanceScene("leaned", "hmm did you say something?")
case "open":
advanceScene("spun", "just the one last thing please")
save()
}
return
}
if inputWriter.Len() > maxInputLength {
fmt.Fprintln(msgScroll,
currentScene.Host.Say("sorry I've heard more than I can remember :( maybe it's time to move on"))
return
}
fmt.Fprintln(msgScroll, player.Say(msg))
fmt.Fprintln(inputWriter, msg)
}
defer func() {
if currentScene.Name == "done" {
currentScene.Write(inputBuff)
db.UpdateSignup(su)
}
}()
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyCtrlD:
handleInput(input.GetText())
input.SetText("", false)
return nil
}
return event
})
app.SetAfterDrawFunc(func(_ tcell.Screen) {
fmt.Fprintln(msgScroll, currentScene.Description)
app.SetAfterDrawFunc(nil)
})
return app.Run()
}