170 lines
3.3 KiB
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)
|
|
}
|
|
}
|