From 9b2f49093442098c23b5274906bad2e682dcb0ad Mon Sep 17 00:00:00 2001 From: Rose Hogenson Date: Wed, 2 Sep 2026 21:44:39 -0700 Subject: Initial commit --- life.go | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 life.go (limited to 'life.go') diff --git a/life.go b/life.go new file mode 100644 index 0000000..078e72c --- /dev/null +++ b/life.go @@ -0,0 +1,214 @@ +package main + +import ( + "bufio" + "fmt" + "math/rand/v2" + "os" + "runtime" + "sync" + "time" + + "golang.org/x/term" +) + +var stdout = bufio.NewWriter(os.Stdout) + +const ( + left = -(1 + iota) + right +) + +func readKeys(out chan<- rune) { + defer close(out) + stdin := bufio.NewReader(os.Stdin) + for { + b, err := stdin.ReadByte() + if err != nil { + break + } + if b == '\x1b' { + buf := make([]byte, 2) + n, err := stdin.Read(buf) + switch string(buf[:n]) { + case "[D": + out <- left + case "[C": + out <- right + } + if err != nil { + break + } + } else { + out <- rune(b) + } + } +} + +type board struct { + buf []bool + rows int + cols int +} + +func newBoard(rows, cols int) board { + return board{ + buf: make([]bool, rows*cols), + rows: rows, + cols: cols, + } +} + +func (b board) row(i int) []bool { + start := i * b.cols + return b.buf[start : start+b.cols] +} + +func (b board) neighborCount(i, j int) int { + count := 0 + for di := -1; di <= 1; di++ { + for dj := -1; dj <= 1; dj++ { + if di == 0 && dj == 0 { + continue + } + ii := i + di + jj := j + dj + if ii < 0 { + ii = b.rows - 1 + } else if ii == b.rows { + ii = 0 + } + if jj < 0 { + jj = b.cols - 1 + } else if jj == b.cols { + jj = 0 + } + if b.row(ii)[jj] { + count++ + } + } + } + return count +} + +var numCPU = runtime.NumCPU() + +func step(nextBoard, activeBoard board) { + chunkSize := (activeBoard.rows + numCPU - 1) / numCPU + wg := new(sync.WaitGroup) + for start := 0; start < activeBoard.rows; start += chunkSize { + wg.Add(1) + go func() { + defer wg.Done() + for i := start; i < min(start+chunkSize, activeBoard.rows); i++ { + for j := range activeBoard.cols { + count := activeBoard.neighborCount(i, j) + nextBoard.row(i)[j] = count == 3 || count == 2 && activeBoard.row(i)[j] + } + } + }() + } + wg.Wait() +} + +func life() error { + cols, rows, err := term.GetSize(int(os.Stdout.Fd())) + if err != nil { + return err + } + oldState, err := term.MakeRaw(int(os.Stdin.Fd())) + if err != nil { + return err + } + defer term.Restore(int(os.Stdin.Fd()), oldState) + + board1 := newBoard(rows, cols) + for i := range board1.rows { + for j := range board1.cols { + board1.row(i)[j] = rand.IntN(2) != 0 + } + } + board2 := newBoard(rows, cols) + activeBoard := board1 + nextBoard := board2 + + const minDelay = time.Second / 300 + + delay := 250 * time.Millisecond + running := true + oneTick := false + + frameTimer := time.NewTicker(minDelay) + defer frameTimer.Stop() + lastFrameTime := time.Now() + accumulator := time.Duration(0) + + keyCh := make(chan rune, 1024) + go readKeys(keyCh) + + for now := range frameTimer.C { + Read: + for { + var key rune + select { + case key = <-keyCh: + default: + break Read + } + switch key { + case 'q': + return nil + case ' ': + running = !running + case '.': + if !running { + oneTick = true + } + case left: + delay += delay / 3 + case right: + delay = max(delay-delay/3, minDelay) + } + } + + accumulator += now.Sub(lastFrameTime) + lastFrameTime = now + if oneTick || accumulator >= delay { + if accumulator >= delay { + accumulator -= delay + } + if running || oneTick { + oneTick = false + step(nextBoard, activeBoard) + activeBoard, nextBoard = nextBoard, activeBoard + } + } + // Prevent falling behind. + if accumulator > delay { + accumulator = delay + } + + stdout.WriteString("\x1b[H") + for i := range activeBoard.rows { + if i > 0 { + stdout.WriteString("\r\n") + } + for j := range activeBoard.cols { + if activeBoard.row(i)[j] { + stdout.WriteByte('#') + } else { + stdout.WriteByte(' ') + } + } + } + stdout.Flush() + } + panic("unreachable") +} + +func main() { + if err := life(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} -- cgit v1.3.1