Compare commits

..

No commits in common. "c6226cf24c0cf51b79920987aea853d946c1a32f" and "7fecd7f572cca577cde302a1b31b92192b4ba05a" have entirely different histories.

4 changed files with 2 additions and 383 deletions

View File

@ -1,7 +1,3 @@
# Gorbito
# gorbito
"Go" with the flow, in the board game Orbito!
Gorbito is a basic TUI implementation of the 2-player strategy game [Orbito](https://flexiqgames.com/en/product/orbito/).
I am writing this game to learn the Go programming language, and both the code and product might turn out messy.
"Go" with the flow, in the board gameOrbito!

24
go.mod
View File

@ -1,24 +0,0 @@
module felixalb/gorbito
go 1.23.3
require github.com/charmbracelet/bubbletea v1.2.4
require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/lipgloss v1.0.0 // indirect
github.com/charmbracelet/x/ansi v0.4.5 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.3.8 // indirect
)

37
go.sum
View File

@ -1,37 +0,0 @@
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE=
github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM=
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM=
github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=

316
main.go
View File

@ -1,316 +0,0 @@
package main
import (
"fmt"
"os"
"strings"
tea "github.com/charmbracelet/bubbletea"
)
const BOARD_SIZE = 4
type GameState int8
const (
StartTurn GameState = iota
IsMoving
HasMoved
HasPlaced
GameOver
)
type Player int8
const (
PNone Player = iota
POne
PTwo
)
type Position struct {
x int
y int
}
type GameModel struct {
board [][]Player
playerTurn Player
cursor Position
moveCursor Position
state GameState
message string
}
func (m GameModel) PrintBoard() string {
rowSeparator := "+" + strings.Repeat("---+", len(m.board)) + "\n"
playerSymbols := map[Player]byte{
PNone: '_',
POne: 'X',
PTwo: 'O',
}
lines := make([][]byte, 0)
for _, row := range m.board {
// Create the grid row
line := []byte("|" + strings.Repeat(" |", len(row)))
// Populate the player symbols
for i, p := range row {
line[(i*4)+2] = playerSymbols[p]
}
lines = append(lines, line)
}
lines[m.cursor.y][(m.cursor.x*4)+1] = '>'
lines[m.cursor.y][(m.cursor.x*4)+3] = '<'
if m.state == IsMoving {
lines[m.moveCursor.y][(m.moveCursor.x*4)+1] = '['
lines[m.moveCursor.y][(m.moveCursor.x*4)+3] = ']'
}
s := rowSeparator
for _, line := range lines {
s += string(line) + "\n"
s += rowSeparator
}
return s
}
func RotateBoard(board [][]Player) [][]Player {
// Clone the board
nBoard := make([][]Player, len(board))
for i := range board {
nBoard[i] = make([]Player, len(board[i]))
copy(nBoard[i], board[i])
}
// Rotate each ring of the board counter-clockwise
for ringStart := range len(board)/2 {
ringEnd := len(board)-ringStart-1
// Top
for x := ringStart; x < ringEnd; x++ {
nBoard[ringStart][x] = board[ringStart][x+1]
}
// Bottom
for x := ringStart+1; x <= ringEnd; x++ {
nBoard[ringEnd][x] = board[ringEnd][x-1]
}
// Left
for y := ringStart+1; y <= ringEnd; y++ {
nBoard[y][ringStart] = board[y-1][ringStart]
}
// Right
for y := ringStart; y < ringEnd; y++ {
nBoard[y][ringEnd] = board[y+1][ringEnd]
}
}
return nBoard
}
func (m GameModel) CheckVictory() GameModel {
//TODO: Check horizontal
//TODO: Check vertical
//TODO: Check diagonal
return m
}
func (m GameModel) Move() GameModel {
m.message = ""
switch m.state {
case IsMoving:
if m.board[m.cursor.y][m.cursor.x] != PNone {
m.message = "You cannot move to an occupied space"
break
}
xDiff := m.cursor.x - m.moveCursor.x
yDiff := m.cursor.y - m.moveCursor.y
if !(
(xDiff == 0 && yDiff == -1) || // Up
(xDiff == 0 && yDiff == 1) || // Down
(xDiff == -1 && yDiff == 0) || // Left
(xDiff == 1 && yDiff == 0)) { // Right
m.message = "You cannot move here. You can only move one square laterally."
break
}
m.board[m.cursor.y][m.cursor.x] = m.board[m.moveCursor.x][m.moveCursor.y]
m.board[m.moveCursor.x][m.moveCursor.y] = PNone
m.state = HasMoved
case HasPlaced:
m.board = RotateBoard(m.board)
if m.playerTurn == POne {
m.playerTurn = PTwo
} else {
m.playerTurn = POne
}
m.state = StartTurn
//TODO: m = m.CheckVictory()
//TODO: Check if the board is full; do end-game checks
default:
switch m.board[m.cursor.y][m.cursor.x] {
case PNone:
// Place piece
m.board[m.cursor.y][m.cursor.x] = m.playerTurn
m.state = HasPlaced
case m.playerTurn:
m.message = "You cannot move your own piece."
default:
// Initiate move
m.state = IsMoving
m.moveCursor = m.cursor
}
}
return m
}
func initialModel() GameModel {
initialBoard := make([][]Player, BOARD_SIZE)
for i := range BOARD_SIZE {
initialBoard[i] = make([]Player, BOARD_SIZE)
for j := range initialBoard[i] {
initialBoard[i][j] = PNone
}
}
return GameModel{
board: initialBoard,
playerTurn: POne,
}
}
func (m GameModel) Init() tea.Cmd {
return nil
}
func (m GameModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
// TODO: Cancel a move on esc/q
case "ctrl+c", "q":
return m, tea.Quit
case "up", "k":
switch m.state {
case GameOver:
break
case HasPlaced:
break
case IsMoving:
if m.moveCursor.y > 0 {
m.cursor.x = m.moveCursor.x
m.cursor.y = m.moveCursor.y-1
}
default:
m.cursor.y = (m.cursor.y + len(m.board) - 1) % len(m.board)
}
case "down", "j":
switch m.state {
case GameOver:
break
case HasPlaced:
break
case IsMoving:
if m.moveCursor.y < len(m.board)-1 {
m.cursor.x = m.moveCursor.x
m.cursor.y = m.moveCursor.y+1
}
default:
m.cursor.y = (m.cursor.y + 1) % len(m.board)
}
case "left", "h":
switch m.state {
case GameOver:
break
case HasPlaced:
break
case IsMoving:
if m.moveCursor.x > 0 {
m.cursor.x = m.moveCursor.x-1
m.cursor.y = m.moveCursor.y
}
default:
m.cursor.x = (m.cursor.x + len(m.board) - 1) % len(m.board)
}
case "right", "l":
switch m.state {
case GameOver:
break
case HasPlaced:
break
case IsMoving:
if m.moveCursor.x < len(m.board)-1 {
m.cursor.x = m.moveCursor.x+1
m.cursor.y = m.moveCursor.y
}
default:
m.cursor.x = (m.cursor.x + 1) % len(m.board)
}
case "enter", " ":
m = m.Move()
}
}
return m, nil
}
func (m GameModel) View() string {
//TODO: Redo the UI, with proper bubbletea modules, colors, etc.
s := m.PrintBoard()
msg := ""
switch m.playerTurn {
case POne:
msg += "Player 1 (X) plays\n"
case PTwo:
msg += "Player 2 (O) plays\n"
default:
// Something is wrong!
msg += "DEBUG: \n"
msg += fmt.Sprintf("Current player: %v\nCurrent MoveState: %v\nCurrent Board: %v\nCurrent Cursor: %#v\n", m.playerTurn, m.state, m.board, m.cursor)
}
switch m.state {
case StartTurn:
msg += "Place a new piece or move an opponent piece >_< \n"
case IsMoving:
msg += "Move opponent piece from [_] to >_<. \n"
case HasMoved:
msg += "Place your piece at >_< \n"
case HasPlaced:
msg += "Finish your turn, rotating the board \n"
}
s += "\n" + msg
s += "\n" + m.message
return s
}
func main() {
fmt.Println("==== Gorbito ====")
//TODO: All the stuff; player names, separate client/server model, """AI""" opponent
p := tea.NewProgram(initialModel())
if _, err := p.Run(); err != nil {
fmt.Printf("Alas, there's been an error: %v", err)
os.Exit(1)
}
}