-
-
Save zuzuleinen/79413aa7933d7d6c6d84ec6ba8c3910a to your computer and use it in GitHub Desktop.
Racing horses using goroutines
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, ¤t, 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