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) } }