summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcitrons <citrons@mondecitronne.com>2025-01-29 21:57:36 -0600
committercitrons <citrons@mondecitronne.com>2025-01-29 21:57:36 -0600
commitdcc8a3a4f93eb5b7dcc965e63ad2ea6057a8ee6b (patch)
tree02d7fa114a86322d92322533b2ee70874f01b121
parente3a5cdb759ba98ebcdf43e38efaa9d33aa29f466 (diff)
keyboard and mouse handling
-rw-r--r--football/main.go237
-rw-r--r--football/platitudes.go81
-rw-r--r--tui/event.go136
-rw-r--r--tui/input.go57
4 files changed, 405 insertions, 106 deletions
diff --git a/football/main.go b/football/main.go
index a6cd954..f6332c1 100644
--- a/football/main.go
+++ b/football/main.go
@@ -2,9 +2,13 @@ package main
import (
"citrons.xyz/talk/tui"
+ "zgo.at/termfo/keys"
"math/rand"
"log"
+ "os"
"strings"
+ "strconv"
+ "fmt"
"time"
)
@@ -13,14 +17,20 @@ type ball struct {
y int
vx int
vy int
+ explode int
inGoal bool
}
-func main() {
- tui.Start()
+var username = os.Getenv("USER")
+
+var score struct {
+ home int
+ guest int
+}
+func main() {
s := tui.Size()
- var balls [20]ball
+ var balls [50]ball
for i := 0; i < len(balls); i++ {
balls[i].x = rand.Int() % s.Width - 1
balls[i].y = rand.Int() % s.Height
@@ -36,69 +46,198 @@ func main() {
}
}
- var sb strings.Builder
+ if username == "" {
+ username = "GUEST"
+ }
+ username = string([]rune(username)[:10])
+
+ tui.Start()
+
+ var (
+ sb strings.Builder
+ response string
+ lastKey keys.Key
+ mouseButton int
+ mouseHeld bool
+ scrolled int
+ responsesUnlocked = 5
+ )
+ t := time.After(time.Second / 15)
for {
- tui.Clear()
- s := tui.Size()
- for y := 0; y < s.Height; y++ {
- for x := 0; x < s.Width; x++ {
- if (x + 66) % 10 == 1 && (y + 2) % 5 == 1 {
- tui.Write(x, y, "*", tui.Style {Fg: tui.Green})
+ select {
+ case ev := <-tui.Events():
+ if ev.Err != nil {
+ log.Fatal(ev.Err)
+ }
+ if ev.Key == keys.Escape || ev.Key == 'c' | keys.Ctrl {
+ tui.End()
+ return
+ }
+ if ev.Key == keys.Backspace {
+ sb.Reset()
+ }
+ if ev.Key == keys.Enter && len(sb.String()) > 0 {
+ sb.Reset()
+ var i int
+ if responsesUnlocked < 40 {
+ i = rand.Int() % responsesUnlocked
+ } else {
+ i = 30 + rand.Int() % (responsesUnlocked - 30)
}
+ response = platitudes[i]
+ if responsesUnlocked < len(platitudes) && rand.Int() % 3 == 0 {
+ if responsesUnlocked < 12 || score.guest > 0 {
+ responsesUnlocked++
+ }
+ }
+ }
+ if ev.TextInput != 0 {
+ sb.WriteRune(ev.TextInput)
+ }
+ if ev.Mouse.Pressed {
+ mouseHeld = true
+ mouseButton = ev.Mouse.Button
+ fire(balls[:], ev.Mouse.X, ev.Mouse.Y)
+ } else if ev.Mouse.Released {
+ mouseHeld = false
+ }
+ scrolled += ev.Mouse.Scroll
+ if ev.Key != 0 {
+ lastKey = ev.Key
}
+ case <-t:
+ stepFootball(balls[:])
+ t = time.After(time.Second / 15)
}
- tui.Write(2, 1, "normal", tui.Style {Fg: tui.Red})
- tui.Write(2, 2, "bold", tui.Style {Fg: tui.Yellow, Bold: true})
- tui.Write(2, 3, "italic", tui.Style {Fg: tui.Green, Italic: true})
- tui.Write(2, 4, "underline", tui.Style {Fg: tui.Blue, Underline: true})
- tui.Write(2, 5, "strikethrough", tui.Style {Fg: tui.Magenta, Strikethrough: true})
- tui.Write(2, 6, "hooray!", tui.Style {Fg: tui.White, Bold: true, Italic: true, Underline: true, Strikethrough: true})
- gx := s.Width / 2 - 1
- for i := 0; i < len(balls); i++ {
- if balls[i].inGoal {
- continue
+ if score.home == len(balls) {
+ response = "wow, you did very poorly."
+ }
+
+ tui.Clear()
+ drawFootball(balls[:])
+ tui.Write(1, 9, " DIALOG:", tui.Style {Fg: tui.Red, Bold: true})
+ tui.Write(1, 10, " ", tui.DefaultStyle)
+ w := tui.Write(2, 10, sb.String(), tui.DefaultStyle)
+ tui.MoveCursor(2 + w, 10)
+ tui.Write(1, 11, " RESPONSE:", tui.Style {Fg: tui.Green, Bold: true})
+ tui.Write(1, 12, " ", tui.DefaultStyle)
+ tui.Write(2, 12, response, tui.DefaultStyle)
+
+ tui.Write(1, 14, " TIP: click the mouse", tui.Style {Fg: tui.Yellow, Bold: true})
+
+ tui.Write(1, 15, " ", tui.DefaultStyle)
+ tui.Write(2, 15, lastKey.Name(), tui.DefaultStyle)
+ tui.Write(11, 15, " ", tui.DefaultStyle)
+ tui.Write(12, 15, strconv.FormatInt(int64(rune(lastKey)), 16), tui.DefaultStyle)
+
+ if mouseHeld {
+ tui.Write(1, 18, " ", tui.DefaultStyle)
+ tui.Write(2, 18, strconv.Itoa(mouseButton), tui.DefaultStyle)
+ }
+ tui.Write(1, 19, " ", tui.DefaultStyle)
+ tui.Write(2, 19, strconv.Itoa(scrolled), tui.DefaultStyle)
+
+ s := tui.Size()
+ tui.Write(s.Width - 26, 1, " ", tui.DefaultStyle)
+ tui.Write(s.Width - 25, 1, "HOME", tui.Style {Bg: tui.Green, Bold: true})
+ hs := fmt.Sprintf(" %02d", score.home)
+ tui.Write(s.Width - 26, 2, hs, tui.Style {Fg: tui.Yellow, Bold: true})
+
+ tui.Write(s.Width - 16, 1, " ", tui.DefaultStyle)
+ tui.Write(s.Width - 15, 1, username, tui.Style {Bg: tui.Red, Bold: true})
+ gs := fmt.Sprintf(" %02d", score.guest)
+ tui.Write(s.Width - 16, 2, gs, tui.Style {Fg: tui.Yellow, Bold: true})
+
+ if err := tui.Present(); err != nil {
+ log.Fatal(err)
+ }
+ }
+}
+
+func stepFootball(balls []ball) {
+ s := tui.Size()
+ gx := s.Width / 2 - 1
+ for i := 0; i < len(balls); i++ {
+ if balls[i].inGoal {
+ continue
+ }
+ if balls[i].explode > 0 {
+ balls[i].explode--
+ if balls[i].explode == 0 {
+ balls[i].inGoal = true
}
- balls[i].x += balls[i].vx
- balls[i].y += balls[i].vy
- if balls[i].x < 0 {
- balls[i].x = 0
- balls[i].vx = -balls[i].vx
- } else if balls[i].x >= s.Width - 1 {
+ continue
+ }
+
+ balls[i].x += balls[i].vx
+ balls[i].y += balls[i].vy
+ if balls[i].x < 0 {
+ balls[i].x = 0
+ balls[i].vx = -balls[i].vx
+ } else if balls[i].x >= s.Width - 1 {
+ if balls[i].x - s.Width < 2 {
balls[i].x = s.Width - 2
balls[i].vx = -balls[i].vx
+ } else {
+ balls[i].x = rand.Int() % s.Width
}
- if balls[i].y < 0 {
- balls[i].y = 0
- balls[i].vy = -balls[i].vy
- } else if balls[i].y >= s.Height {
+ }
+ if balls[i].y < 0 {
+ balls[i].y = 0
+ balls[i].vy = -balls[i].vy
+ } else if balls[i].y >= s.Height {
+ if balls[i].y - s.Height < 2 {
balls[i].y = s.Height - 1
balls[i].vy = -balls[i].vy
+ } else {
+ balls[i].y = rand.Int() % s.Height
}
- if balls[i].x >= gx - 1 && balls[i].x <= gx + 1 && balls[i].y == 0 {
- balls[i].inGoal = true
- continue
- }
- tui.Write(balls[i].x, balls[i].y, "⚽", tui.DefaultStyle)
}
- tui.Write(gx, 0, "🥅", tui.DefaultStyle)
+ if balls[i].x >= gx - 1 && balls[i].x <= gx + 1 && balls[i].y == 0 {
+ balls[i].inGoal = true
+ score.home++
+ continue
+ }
+ }
+}
- w := tui.Write(2, 8, sb.String(), tui.DefaultStyle)
- tui.MoveCursor(2 + w, 8)
- err := tui.Present()
- if err != nil {
- log.Fatal(err)
+func fire(balls []ball, x int, y int) {
+ for i := 0; i < len(balls); i++ {
+ if balls[i].explode > 0 || balls[i].inGoal {
+ continue
}
+ if x >= balls[i].x - 2 && x <= balls[i].x + 3 &&
+ y >= balls[i].y - 1 && y <= balls[i].y + 1 {
+ balls[i].explode = 5
+ score.guest++
+ }
+ }
+}
- select {
- case ev := <-tui.Events():
- switch ev := ev.(type) {
- case tui.TextInput:
- sb.WriteRune(rune(ev))
- case error:
- log.Fatal(ev)
+func drawFootball(balls []ball) {
+ s := tui.Size()
+ for y := 0; y < s.Height; y++ {
+ for x := 0; x < s.Width; x++ {
+ if (x + 66) % 10 == 1 && (y + 2) % 5 == 1 {
+ tui.Write(x, y, "*", tui.Style {Fg: tui.Green})
}
- case <-time.After(time.Second / 16):
}
}
+ tui.Write(2, 1, "normal", tui.Style {Fg: tui.Red})
+ tui.Write(2, 2, "bold", tui.Style {Fg: tui.Yellow, Bold: true})
+ tui.Write(2, 3, "italic", tui.Style {Fg: tui.Green, Italic: true})
+ tui.Write(2, 4, "underline", tui.Style {Fg: tui.Blue, Underline: true})
+ tui.Write(2, 5, "strikethrough", tui.Style {Fg: tui.Magenta, Strikethrough: true})
+ tui.Write(2, 6, "hooray!", tui.Style {Fg: tui.White, Bold: true, Italic: true, Underline: true, Strikethrough: true})
+
+ for _, b := range balls {
+ if b.explode > 0 {
+ tui.Write(b.x, b.y, "💥", tui.DefaultStyle)
+ } else if !b.inGoal {
+ tui.Write(b.x, b.y, "⚽", tui.DefaultStyle)
+ }
+ }
+ gx := s.Width / 2 - 1
+ tui.Write(gx, 0, "🥅", tui.DefaultStyle)
}
diff --git a/football/platitudes.go b/football/platitudes.go
new file mode 100644
index 0000000..c7bfd33
--- /dev/null
+++ b/football/platitudes.go
@@ -0,0 +1,81 @@
+package main
+
+var platitudes = []string {
+ "yep",
+ "ok",
+ "ok then",
+ "yeah",
+ "uh huh",
+ "that's a funny thing to say.",
+ "uh huh",
+ "sure",
+ "yeah",
+ "yeah",
+ "alright.",
+ "cool, I guess",
+ "I wish the referee would stop you from exploding the Footballs. it's against the rules.",
+ "no",
+ "nope",
+ "no!",
+ "maybe",
+ "that's fine and all, but can you stop exploding my balls?",
+ "that's fine and all, but can you stop exploding my balls?",
+ "that's fine and all, but can you stop exploding my balls?",
+ "that's fine and all, but can you stop exploding my balls?",
+ "that's fine and all, but can you stop exploding my balls?",
+ "is that the best you can do?",
+ "I think you should focus on the game.",
+ "you're not focusing on the game enough!!",
+ "please, let's focus on the game.",
+ "we can talk another time.",
+ "can we please talk another time?",
+ "is now really the best time to talk?",
+ "why would you say that?",
+ "could you be any more of an asshole?",
+ "wipe that smirk off your ugly face.",
+ "it's taking every ounce of willpower in my body not to punch you right now.",
+ "to be honest, I don't even like Football.",
+ "I think you should respect Football more.",
+ "there is nothing in my life other than Football.",
+ "I don't even know what that means.",
+ "there's nothing I love more than Football.",
+ "I wish it wasn't against the rules of Football to kill you.",
+ "never talk to me again.",
+ "I hate you.",
+ "I really hate you.",
+ "I really, really hate you.",
+ "go to hell.",
+ "fuck you",
+ "can we be friends?",
+ "can we be friends?",
+ "can we be friends?",
+ "can we be friends?",
+ "I love you.",
+ "do you want to come to my place?",
+ "will you marry me?",
+ "I need you in my life.",
+ "I need you badly.",
+ "will you kiss me?",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please make out with me.",
+ "please, please, please.",
+}
+
diff --git a/tui/event.go b/tui/event.go
new file mode 100644
index 0000000..4d0a535
--- /dev/null
+++ b/tui/event.go
@@ -0,0 +1,136 @@
+package tui
+
+import (
+ "os"
+ "os/signal"
+ "zgo.at/termfo"
+ "zgo.at/termfo/keys"
+ "strings"
+ "strconv"
+ "bufio"
+ "syscall"
+ "sync"
+)
+
+type Event struct {
+ TextInput rune
+ Key keys.Key
+ Mouse struct {
+ Button int
+ Pressed bool
+ Released bool
+ X, Y int
+ Scroll int
+ }
+ Err error
+ Resize bool
+}
+
+var evChan chan Event
+var once sync.Once
+func Events() <-chan Event {
+ once.Do(func() {
+ evChan = make(chan Event, 2)
+
+ // buffer FindKeys
+ ch := make(chan termfo.Event, 16)
+ go func() {
+ r := bufio.NewReader(os.Stdin)
+ for e := range terminfo.FindKeys(r) {
+ ch <- e
+ if e.Err != nil {
+ return
+ }
+ }
+ }()
+
+ go func() {
+ for kev := range ch {
+ var ev Event
+ if kev.Err != nil {
+ ev.Err = kev.Err
+ evChan <- ev
+ return
+ }
+
+ noMods := kev.Key & (keys.Ctrl | keys.Alt) == 0
+ notSpecial := kev.Key.WithoutMods() < (1 << 32)
+ if noMods && notSpecial {
+ r := rune(kev.Key.WithoutMods())
+ if kev.Key & keys.Shift != 0 && r >= 'a' && r <= 'z' {
+ r -= 0x20
+ }
+ ev.TextInput = r
+ }
+
+ switch kev.Key {
+ case keys.Mouse:
+ if err := parseMouse(&ev, ch); err != nil {
+ ev.Err = err
+ evChan <- ev
+ return
+ }
+ default:
+ ev.Key = kev.Key
+ }
+
+ evChan <- ev
+ }
+ }()
+
+ sigs := make(chan os.Signal, 1)
+ signal.Notify(sigs, syscall.SIGWINCH)
+ go func() {
+ for _ = range sigs {
+ evChan <- Event {Resize: true}
+ }
+ }()
+ })
+
+ return evChan
+}
+
+func parseMouse(ev *Event, ch <-chan termfo.Event) error {
+ var (
+ arg strings.Builder
+ args []int
+ down bool
+ )
+ parse: for kev := range ch {
+ if kev.Err != nil {
+ return kev.Err
+ }
+ switch {
+ case kev.Key >= '0' && kev.Key <= '9':
+ arg.WriteRune(rune(kev.Key))
+ case kev.Key == ';':
+ i, _ := strconv.Atoi(arg.String())
+ args = append(args, i)
+ arg.Reset()
+ case kev.Key == 'm' | keys.Shift:
+ down = true
+ fallthrough
+ default:
+ i, _ := strconv.Atoi(arg.String())
+ args = append(args, i)
+ break parse
+ }
+ }
+ if len(args) != 3 {
+ return nil
+ }
+ ev.Mouse.X = args[1] - 1
+ ev.Mouse.Y = args[2] - 1
+ switch args[0] {
+ case 64:
+ ev.Mouse.Scroll = -1
+ case 65:
+ ev.Mouse.Scroll = 1
+ default:
+ ev.Mouse.Button = args[0]
+ ev.Mouse.Pressed = down
+ ev.Mouse.Released = !down
+ }
+
+ return nil
+}
diff --git a/tui/input.go b/tui/input.go
deleted file mode 100644
index a200e53..0000000
--- a/tui/input.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package tui
-
-import (
- "os"
- "os/signal"
- "golang.org/x/term"
- "unicode"
- "syscall"
- "bufio"
-)
-
-type Event interface {}
-
-type TextInput rune
-type Paste string
-type Resize Dims
-
-var evChan chan Event
-func Events() <-chan Event {
- if evChan != nil {
- return evChan
- }
- evChan = make(chan Event, 1)
-
- go func() {
- rd := bufio.NewReader(os.Stdin)
- for {
- r, _, err := rd.ReadRune()
- if err != nil {
- evChan <- err
- return
- }
- switch {
- case r == '\033':
- // todo
- case unicode.IsControl(r):
- default:
- evChan <- TextInput(r)
- }
- }
- }()
-
- sigs := make(chan os.Signal, 1)
- signal.Notify(sigs, syscall.SIGWINCH)
- go func() {
- for _ = range sigs {
- w, h, err := term.GetSize(0)
- if err == nil {
- evChan <- Resize {w, h}
- } else {
- evChan <- err
- }
- }
- }()
-
- return evChan
-} \ No newline at end of file