Full-screen Tetris game using Bubbletea with title screen, ghost piece, lock delay, NES-style scoring, configurable difficulty (easy/normal/hard), and honeypot event logging. Bumps version to 0.17.0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
67 lines
1.4 KiB
Go
67 lines
1.4 KiB
Go
package tetris
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"time"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
|
|
"git.t-juice.club/torjus/oubliette/internal/shell"
|
|
)
|
|
|
|
const sessionTimeout = 10 * time.Minute
|
|
|
|
// TetrisShell is a Tetris game TUI for the honeypot.
|
|
type TetrisShell struct{}
|
|
|
|
// NewTetrisShell returns a new TetrisShell instance.
|
|
func NewTetrisShell() *TetrisShell {
|
|
return &TetrisShell{}
|
|
}
|
|
|
|
func (t *TetrisShell) Name() string { return "tetris" }
|
|
func (t *TetrisShell) Description() string { return "Tetris game TUI" }
|
|
|
|
func (t *TetrisShell) Handle(ctx context.Context, sess *shell.SessionContext, rw io.ReadWriteCloser) error {
|
|
ctx, cancel := context.WithTimeout(ctx, sessionTimeout)
|
|
defer cancel()
|
|
|
|
difficulty := configString(sess.ShellConfig, "difficulty", "normal")
|
|
|
|
m := newModel(sess, difficulty)
|
|
p := tea.NewProgram(m,
|
|
tea.WithInput(rw),
|
|
tea.WithOutput(rw),
|
|
tea.WithAltScreen(),
|
|
)
|
|
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
_, err := p.Run()
|
|
done <- err
|
|
}()
|
|
|
|
select {
|
|
case err := <-done:
|
|
return err
|
|
case <-ctx.Done():
|
|
p.Quit()
|
|
<-done
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// configString reads a string from the shell config map with a default.
|
|
func configString(cfg map[string]any, key, defaultVal string) string {
|
|
if cfg == nil {
|
|
return defaultVal
|
|
}
|
|
if v, ok := cfg[key]; ok {
|
|
if s, ok := v.(string); ok && s != "" {
|
|
return s
|
|
}
|
|
}
|
|
return defaultVal
|
|
}
|