From 6e5c4a77cec1a1ecbe16908cb3a2e23089f9115a Mon Sep 17 00:00:00 2001 From: Kuechlin Date: Tue, 12 Mar 2024 18:31:57 +0100 Subject: [PATCH] base game with block --- field.go | 73 ++++++++++++++++++++++++++++ game.go | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 3 ++ main.go | 27 +++++++++++ ui.go | 23 +++++++++ 5 files changed, 271 insertions(+) create mode 100644 field.go create mode 100644 game.go create mode 100644 go.mod create mode 100644 main.go create mode 100644 ui.go diff --git a/field.go b/field.go new file mode 100644 index 0000000..d880f70 --- /dev/null +++ b/field.go @@ -0,0 +1,73 @@ +package main + +import ( + "strings" +) + +const W = 10 +const H = 20 + +var COLORS = map[int]int{ + 0: 0, + 1: 10, + 2: 20, + 3: 40, + 4: 50, +} + +type Field struct { + Curr Piece + Cells [W * H]int +} + +type Piece struct { + Id int + X int + Y int +} + +func idx(x int, y int) int { + return x + W*y +} + +func (f *Field) CollisionDown() bool { + y := f.Curr.Y + 1 + next := idx(f.Curr.X, y) + return y >= H || f.Cells[next] != 0 +} +func (f *Field) CollisionLeft() bool { + x := f.Curr.X - 1 + next := idx(x, f.Curr.Y) + return x < 0 || f.Cells[next] != 0 +} +func (f *Field) CollisionRight() bool { + x := f.Curr.X + 1 + next := idx(x, f.Curr.Y) + return x >= W || f.Cells[next] != 0 +} + +func (f *Field) String() []string { + lines := []string{} + lines = append(lines, "┌"+strings.Repeat("─", W*2)+"┐") + piece := idx(f.Curr.X, f.Curr.Y) + for y := 0; y < H; y++ { + value := "│" + for x := 0; x < W; x++ { + i := idx(x, y) + if i == piece { + value += block(COLORS[f.Curr.Id]) + } else { + val := f.Cells[idx(x, y)] + if val == 0 { + value += empty() + } else { + value += block(COLORS[val]) + } + } + } + value += "│" + lines = append(lines, value) + } + lines = append(lines, "└"+strings.Repeat("─", W*2)+"┘") + return lines +} diff --git a/game.go b/game.go new file mode 100644 index 0000000..a2fa595 --- /dev/null +++ b/game.go @@ -0,0 +1,145 @@ +package main + +import ( + "fmt" + "math/rand" + "os" + "time" +) + +type Game struct { + done *chan bool + ticker *time.Ticker + field Field + logs []string +} + +func NewGame(done *chan bool) *Game { + game := Game{ + done: done, + ticker: time.NewTicker(time.Second), + field: Field{ + Curr: Piece{ + Id: 1, + X: 0, + Y: 0, + }, + }, + } + + go game.update() + go game.inputs() + + return &game +} + +func (g *Game) Draw() { + lines := g.field.String() + for i, line := range lines { + val := " " + line + if len(g.logs) > i { + val += " - " + g.logs[i] + } + fmt.Println(val) + } +} + +func (g *Game) Redraw() { + clear_lines(H + 2) + g.Draw() +} + +func (g *Game) update() { + for { + select { + case <-g.ticker.C: + g.moveDown() + } + } +} + +func (g *Game) log(msg string) { + if len(g.logs) >= 10 { + g.logs = append(g.logs[1:], msg) + } else { + g.logs = append(g.logs, msg) + } +} + +// ESC [ +// 27 91 __ +// - up 65 +// - down 66 +// - right 67 +// - left 68 +func (g *Game) inputs() { + var b []byte = make([]byte, 3) + for { + os.Stdin.Read(b) + if b[0] == 27 && b[1] == 91 { + switch b[2] { + case 65: + fmt.Println("up") + case 66: + g.moveDown() + case 67: + g.moveRight() + case 68: + g.moveLeft() + } + } + } +} + +func (g *Game) moveDown() { + if g.field.CollisionDown() { + // place block + i := idx(g.field.Curr.X, g.field.Curr.Y) + g.field.Cells[i] = g.field.Curr.Id + // Spawn next block + g.Spawn() + } else { + // move + g.log("move down") + g.field.Curr.Y += 1 + g.Redraw() + } +} + +func (g *Game) moveLeft() { + if g.field.CollisionLeft() { + return + } + g.log("move left") + g.field.Curr.X -= 1 + g.Redraw() +} + +func (g *Game) moveRight() { + if g.field.CollisionRight() { + return + } + g.log("move right") + g.field.Curr.X += 1 + g.Redraw() +} + +func (g *Game) Spawn() { + p := Piece{ + X: W / 2, + Y: 0, + Id: 1 + rand.Intn(4), + } + i := idx(p.X, p.Y) + + if g.field.Cells[i] != 0 { + // end game + fmt.Println("gg") + *g.done <- true + return + } + + g.log(fmt.Sprintf("span %d at %d, %d", p.Id, p.X, p.Y)) + g.field.Curr = p + g.Redraw() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..dd46555 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/kuechlin/gotris + +go 1.21.7 diff --git a/main.go b/main.go new file mode 100644 index 0000000..388a748 --- /dev/null +++ b/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "os/exec" + "time" +) + +var ticker *time.Ticker + +func main() { + exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run() + exec.Command("stty", "-F", "/dev/tty", "-echo").Run() + exec.Command("tput", "civis") + + done := make(chan bool) + game := NewGame(&done) + fmt.Println("> gotris") + game.Draw() + + for { + select { + case <-done: + return + } + } +} diff --git a/ui.go b/ui.go new file mode 100644 index 0000000..396efa5 --- /dev/null +++ b/ui.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "strings" +) + +const clear_line = "\033[1A\033[2K" + +func clear_lines(lines int) { + fmt.Print(strings.Repeat(clear_line, lines)) +} + +func colored(color int, value string) string { + return fmt.Sprintf("\033[38;5;%dm%s\033[0m", color, value) +} + +func block(color int) string { + return colored(color, "██") +} +func empty() string { + return " " +}