Skip to content

Instantly share code, notes, and snippets.

@zuzuleinen
Forked from Chemaclass/racing_horses.go
Last active April 1, 2024 05:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zuzuleinen/79413aa7933d7d6c6d84ec6ba8c3910a to your computer and use it in GitHub Desktop.
Save zuzuleinen/79413aa7933d7d6c6d84ec6ba8c3910a to your computer and use it in GitHub Desktop.
Racing horses using goroutines
package main
import (
"fmt"
"math/rand"
"os"
"os/exec"
"os/signal"
"time"
)
const milliDelay = 400
var horseNames = [20][2]string{
{"Alloping", "Giggles"},
{"A-lot", "Gallop"},
{"BoJack", "Jack"},
{"Baroness", "Belle"},
{"Bucksnort", "Buckaroo"},
{"Count", "Clopperstein"},
{"Duchess", "Whirlwind"},
{"Lady", "Hoofers"},
{"Gallopalot", "Gallopadore"},
{"Hoof", "Hearted"},
{"Marquis", "Clipclapper"},
{"Mr.", "Trot-a-lot"},
{"Neigh", "Sayer"},
{"Princess", "Neight"},
{"Professor", "Neighsley"},
{"Sir", "Trotsworth"},
{"Sugar", "Cube"},
{"Whinny", "McWhinerson"},
{"Thunder", "Hooves"},
{"Zomby", "McStompface"},
}
type Horse struct {
Name string // The name of the horse
Line int // The competition line
Position int // The position in its line
IsWinner bool // A flag to know if it's the winner
}
func (h *Horse) Clone(other *Horse) {
h.Name = other.Name
h.Line = other.Line
h.Position = other.Position
h.IsWinner = other.IsWinner
}
func (h Horse) Letter() string {
return fmt.Sprintf("%c", h.Name[0])
}
func (h Horse) IsFound() bool {
return h.Name != ""
}
func (h Horse) String() string {
return fmt.Sprintf("%s (line:%d)", h.Name, h.Line)
}
func main() {
defer func() { showCursor() }()
setUpBoard()
const linesCount, lineLength = 12, 30
board := generateRaceBoard(linesCount, lineLength)
renderGame(board)
winnerChan := make(chan Horse)
for line := range board {
// each horse will be moved in different processes
go startHorseRunning(board, line, winnerChan)
}
winner := <-winnerChan // wait until one horse reaches the end
// render one last time to ensure the latest board state
renderRaceBoard(board, &winner)
renderWinner(winner)
}
func setUpBoard() {
hideCursor()
// Force call showCursor() after Ctrl+C
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for sig := range c {
showCursor()
fmt.Sprintln(sig.String())
os.Exit(1)
}
}()
}
func hideCursor() {
fmt.Print("\x1b[?25l")
}
func showCursor() {
fmt.Print("\x1b[?25h")
}
func generateRaceBoard(lines, lineLength int) [][]*Horse {
board := make([][]*Horse, lines)
for line := range board {
board[line] = make([]*Horse, lineLength)
board[line][0] = &Horse{
Name: generateName(),
Position: 0,
Line: line,
IsWinner: false,
}
}
return board
}
func generateName() string {
name := horseNames[rand.Intn(len(horseNames))][0]
surname := horseNames[rand.Intn(len(horseNames))][1]
return name + " " + surname
}
func renderGame(board [][]*Horse) {
go func() {
for {
time.Sleep(milliDelay * time.Millisecond)
renderRaceBoard(board, nil)
}
}()
}
func renderRaceBoard(board [][]*Horse, winner *Horse) {
clearScreen()
fmt.Println()
for line := range board {
renderRaceLine(board, line, winner)
}
fmt.Println()
}
func clearScreen() {
cmd := exec.Command("clear")
cmd.Stdout = os.Stdout
cmd.Run()
}
func renderRaceLine(board [][]*Horse, line int, winner *Horse) {
fmt.Printf(" %.2d | ", line)
var current Horse
for col := range board[line] {
renderRacePosition(board, line, col, &current, winner)
}
fmt.Printf("| %s", current.Name)
if winner != nil && current.Name == winner.Name {
fmt.Print(" [Won!]")
}
fmt.Println()
}
func renderRacePosition(
board [][]*Horse,
line, col int,
current *Horse,
winner *Horse,
) {
if board[line][col] == nil {
fmt.Printf(" ")
return
}
current.Clone(board[line][col])
if winner != nil && current.Name == winner.Name {
removeChars(current.Position + 1)
for range board[line] {
fmt.Printf("-")
}
}
fmt.Print(current.Letter())
}
func removeChars(n int) {
fmt.Printf("\033[%dD", n)
fmt.Printf("\033[K")
}
func startHorseRunning(board [][]*Horse, line int, winnerChan chan Horse) {
for {
select {
case <-winnerChan: // check if another horse finished in
return // such a case, then stop the for loop
default:
time.Sleep(time.Millisecond * time.Duration(rand.Intn(milliDelay)))
moveHorseOnePos(board, line, winnerChan)
}
}
}
func moveHorseOnePos(board [][]*Horse, line int, winnerChan chan Horse) {
cols := len(board[line])
for col := cols - 1; col > 0; col-- {
if board[line][col-1] == nil {
continue
}
// here we identify that there is a horse in
// the following position, so we move it to the
// current pos, and we set nil in the other one
board[line][col] = board[line][col-1]
board[line][col].Position++
board[line][col-1] = nil
// try to declare a winner
if board[line][col].Position+1 == cols {
winnerChan <- *board[line][col]
}
break
}
}
func renderWinner(h Horse) {
fmt.Println("Race finished!")
fmt.Printf("# Winner: %s\n", h)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment