I've been having some fun with the idea of simple text-based games in Go1. The original BASIC games book2 was not hard to find, and it was interesting to revisit it again.
The first thing that stared me in the face was just how bad (and by "bad" I mean incomprehensible) most of the code was. In my nostalgia I had completely lost sight of this.
The second thing was how nicely self-contained each game was, focussed on one simple idea, and implicitly pedagogical. Indeed, the games can be divided into a few distinct families3.
So I came up with a hypothetical organization for my hypothetical book – it would need some simple stuff to start out with (with for loops, conditionals, functions, and so on), and slowly introduce more stuff. An explicit non-goal would be teaching the language in its complete form (a link to Effective Go4 should suffice for the motivated reader).
In terms of Games, the rough plan I came up with was: start with some simple guessing game, then maybe a slightly more involved version, then a card game, then the classic Battleship around the mid-point, followed by a sort of "dungeon RPG" with some optional discussion at each point (e.g. should the computer in "Battleship" be dumb and random, or learn?)
To get an idea of what this might look like, I wrote the first four of these. Here is what might pass for the very first one, a modified version of "Rock, Paper, Scissors".
package main
import (
"fmt"
"math/rand"
"strings"
"time"
)
const (
rock = iota
paper
scissors
lizard
spock
maxOptions
)
var handOptions = []string{"rock", "paper", "scissors", "lizard", "spock"}
type precedence struct {
hand1 int
reason string
hand2 int
}
var precedences = []precedence{
{spock, "smashes", scissors},
{spock, "vaporises", rock},
{lizard, "posions", spock},
{lizard, "eats", paper},
{rock, "crushes", lizard},
{rock, "cruses", scissors},
{paper, "covers", rock},
{paper, "disproves", spock},
{scissors, "cuts", paper},
{scissors, "decapitates", lizard},
}
func showUsage() {
fmt.Println("\n Welcome to ROCK - PAPER - SCISSORS - LIZARD - SPOCK ")
fmt.Println("----------------------------------------------------------\n")
fmt.Println("We each pick one of the following : \n")
for i := 0; i < maxOptions; i++ {
fmt.Printf("--> %s <--\n", handOptions[i])
}
fmt.Println()
}
func getPlayerChoice() int {
for {
fmt.Printf("\nEnter your choice : ")
var choice string
fmt.Scan(&choice)
choice = strings.ToLower(choice)
for i := 0; i < maxOptions; i++ {
if handOptions[i] == choice {
return i
}
}
}
}
func getComputerChoice() (choice int) {
choice = rand.Intn(maxOptions)
fmt.Printf("I chose ")
for i := 0; i < 3; i++ {
fmt.Printf(".")
time.Sleep(200 * time.Millisecond)
}
fmt.Printf(" %s\n\n", handOptions[choice])
return
}
func main() {
showUsage()
rand.Seed(time.Now().Unix())
var playerWins, computerWins int
for {
fmt.Printf("\nThe score is: Me %d, You %d\n", computerWins, playerWins)
playerChoice := getPlayerChoice()
computerChoice := getComputerChoice()
playerWon := false
computerWon := false
for _, p := range precedences {
if p.hand1 == playerChoice && p.hand2 == computerChoice {
fmt.Printf("Alas! %s %s %s! You Win! :(\n",
handOptions[p.hand1], p.reason, handOptions[p.hand2])
playerWon = true
}
if p.hand1 == computerChoice && p.hand2 == playerChoice {
fmt.Printf("Aha! %s %s %s! I win! :)\n",
handOptions[p.hand1], p.reason, handOptions[p.hand2])
computerWon = true
}
}
if playerWon {
playerWins++
} else if computerWon {
computerWins++
} else {
fmt.Println("Hey, look, it was a draw!\n\n")
}
fmt.Printf("\nPlay another round ? (enter 'y' or 'n') : ")
var another string
fmt.Scan(&another)
if strings.ToLower(another) == "y" {
continue
} else {
break
}
}
fmt.Println("\nOh well, until next time then ... \n")
}
Try it out! It's meant to be simple and straightforward – but wait! Before you pass judgement, I present to you the original version:
10 PRINT TAB(21);"GAME OF ROCK, SCISSORS, PAPER"
20 PRINT TAB(15);"CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"
25 PRINT:PRINT:PRINT
30 INPUT "HOW MANY GAMES";Q
40 IF Q<11 THEN 60
50 PRINT "SORRY, BUT WE AREN'T ALLOWED TO PLAY THAT MANY.": GOTO 30
60 FOR G=1 TO Q
70 PRINT: PRINT "GAME NUMBER";G
80 X=INT(RND(1)*3+1)
90 PRINT "3=ROCK...2=SCISSORS...1=PAPER"
100 INPUT "1...2...3...WHAT'S YOUR CHOICE";K
110 IF (K-1)*(K-2)*(K-3)<>0 THEN PRINT "INVALID.": GOTO 90
120 PRINT "THIS IS MY CHOICE..."
130 ON X GOTO 140,150,160
140 PRINT "...PAPER": GOTO 170
150 PRINT "...SCISSORS": GOTO 170
160 PRINT "...ROCK"
170 IF X=K THEN 250
180 IF X>K THEN 230
190 IF X=1 THEN 210
200 PRINT "YOU WIN!!!":H=H+1: GOTO 260
210 IF K<>3 THEN 200
220 PRINT "WOW! I WIN!!!":C=C+1:GOTO 260
230 IF K<>1 OR X<>3 THEN 220
240 GOTO 200
250 PRINT "TIE GAME. NO WINNER."
260 NEXT G
270 PRINT: PRINT "HERE IS THE FINAL GAME SCORE:"
280 PRINT "I HAVE WON";C;"GAME(S)."
290 PRINT "YOU HAVE WON";H;"GAME(S)."
300 PRINT "AND";Q-(C+H);"GAME(S) ENDED IN A TIE."
310 PRINT: PRINT "THANKS FOR PLAYING!!"
320 END
Yes, folks, that is the kind of stuff I cannot believe I squinted over and read again and again :(