tictactoe/main.go

170 lines
3.3 KiB
Go

package main
import (
"fmt"
"os"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
var (
colorRed = lipgloss.NewStyle().Foreground(lipgloss.Color("#ff0000"))
colorGreen = lipgloss.NewStyle().Foreground(lipgloss.Color("#76c165"))
colorBlue = lipgloss.NewStyle().Foreground(lipgloss.Color("#1a73e8"))
colorLemon = lipgloss.NewStyle().Foreground(lipgloss.Color("#e7ef80"))
)
type model struct {
board [][]int // 2-D game board. 0 for nothing, 1 for o, 2 for x
turn int // who's turn is it
cursor []int // coordinates of cursor position in the board
msg string
moves int // number of occupied cells
movesHistory [][]int // list of coords
}
func initialModel() model {
board := make([][]int, 3)
for i := range board {
board[i] = make([]int, 3)
}
return model{
board: board,
turn: 1,
cursor: make([]int, 2),
msg: "",
}
}
func (m model) Init() tea.Cmd {
return nil
}
func (m model) getWinner() int {
return 0
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.msg = ""
switch msg := msg.(type) {
case tea.KeyMsg:
key := msg.String()
switch key {
case "q", "ctrl+c":
m.msg = "Bye!"
return m, tea.Quit
case "u":
if m.moves == 0 {
m.msg = "Nothing to undo"
return m, nil
}
last := m.movesHistory[m.moves-1]
m.board[last[0]][last[1]] = 0
m.cursor[0] = last[0]
m.cursor[1] = last[1]
m.movesHistory = m.movesHistory[:m.moves-1]
m.turn = 1 - m.turn + 2 // change 1 to 2, 2 to 1
m.moves--
m.msg = "Undone"
return m, nil
case "enter", " ":
if m.board[m.cursor[0]][m.cursor[1]] == 0 {
m.board[m.cursor[0]][m.cursor[1]] = m.turn
m.turn = 1 - m.turn + 2 // change 1 to 2, 2 to 1
m.moves++
m.movesHistory = append(m.movesHistory, []int{m.cursor[0], m.cursor[1]})
} else {
m.msg = "That cell is already occupied!"
}
case "up", "k":
if m.cursor[0] > 0 {
m.cursor[0]--
}
case "down", "j":
if m.cursor[0] < 2 {
m.cursor[0]++
}
case "left", "h":
if m.cursor[1] > 0 {
m.cursor[1]--
}
case "right", "l":
if m.cursor[1] < 2 {
m.cursor[1]++
}
}
}
winner := m.getWinner()
if winner != 0 {
if winner == 1 {
m.msg = "o wins!"
} else {
m.msg = "x wins!"
}
return m, tea.Quit
}
if m.moves == 9 {
m.msg = "It's a tie!"
m.turn = 1 - m.turn + 2 // show last turn
return m, tea.Quit
}
return m, nil
}
func (m model) View() (s string) {
s = "\n"
// s += fmt.Sprintf("%v", m.cursor)
indent := " "
for x, row := range m.board {
var v string
s += indent
for y, cell := range row {
switch cell {
case 0:
v = "_"
case 1:
v = colorGreen.Render("o")
case 2:
v = colorBlue.Render("x")
}
if x == m.cursor[0] && y == m.cursor[1] {
s += fmt.Sprintf(" [%s] ", v)
} else {
s += fmt.Sprintf(" %s ", v)
}
}
s += "\n\n"
}
if m.turn == 1 {
s += fmt.Sprintf("\n %s turn\n", colorGreen.Render("o"))
} else {
s += fmt.Sprintf("\n %s turn\n", colorBlue.Render("x"))
}
s += "\n " + colorLemon.Render(m.msg) + "\n\n"
s += "[enter/space] select; [hjkl/arrows] move around; [u]ndo; [q/ctrl-c] quit"
return
}
func main() {
p := tea.NewProgram(initialModel())
if err := p.Start(); err != nil {
fmt.Printf("Alas, there's been an error: %v", err)
os.Exit(1)
}
}