Initial Commit

main
gabe farrell 4 years ago
commit 0045790ec4

Binary file not shown.

@ -0,0 +1,304 @@
package board
import (
"bufio"
"fmt"
"math/rand"
"os"
"time"
"unicode"
"github.com/ghfarrell/go-battleship/cprint"
)
type Point struct {
Row rune
Col int
}
type Ship struct {
Name string
Length int
Coords []Point
}
/*
Enemy board's state is - for an empty space, O for a missed
shot, and X for a hit
Friendly board's state is - for an empty space, O for a boat
and X for a boat space that has been hit
*/
type Board struct {
Board map[Point]byte
Ships []Ship
}
func (b *Board) Initialize() {
rand.Seed(int64(time.Now().Nanosecond()))
b.Board = make(map[Point]byte, 100)
for c := 'a'; c <= 'j'; c++ {
for i := 1; i <= 10; i++ {
b.Board[CoordToPoint(c, i)] = '-'
}
}
b.Ships = make([]Ship, 5)
b.Ships[0].Name = "Carrier"
b.Ships[1].Name = "Battleship"
b.Ships[2].Name = "Cruiser"
b.Ships[3].Name = "Submarine"
b.Ships[4].Name = "Destroyer"
b.Ships[0].Length = 5
b.Ships[1].Length = 4
b.Ships[2].Length = 3
b.Ships[3].Length = 3
b.Ships[4].Length = 2
for _, s := range b.Ships {
s.Coords = make([]Point, 0)
}
}
/*
helper func turns 0 to false and anything else to true
for ai placing ship logic
*/
func itob(i int) bool {
if i == 0 {
return false
} else {
return true
}
}
/*
helper func turns "v" or "h" to true and false respectively
for the ship placement logic
*/
func HVToBool(c rune) bool {
if c == 'h' || c == 'H' {
return false
} else if c == 'v' || c == 'V' {
return true
} else {
cprint.Printf["Red"]("Invalid orientation! Defaulting to vertical\n")
return true
}
}
func RandPoint() Point {
rows := []rune{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
r := rows[rand.Intn(10)]
c := rand.Intn(10) + 1
return Point{r, c}
}
func InputPoint() (Point, error) {
var row rune
var col int
_, err := fmt.Scanf("%c%d\n", &row, &col)
if err != nil {
cprint.Printf["Red"]("Bad input. Enter coordinate as [row][col] i.e. b5\n")
}
if unicode.IsUpper(row) {
row = unicode.ToLower(row)
}
if col < 1 || col > 10 || row < 'a' || row > 'j' {
return Point{}, fmt.Errorf("Invalid coordinate")
} else {
return Point{row, col}, nil
}
}
func (p Point) IsValid() bool {
if p.Row > 'j' || p.Row < 'a' || p.Col > 10 || p.Col < 1 {
return false
}
return true
}
func (p Point) NudgeRight(i int) Point {
return Point{p.Row, p.Col + i}
}
func (p Point) NudgeUp(i int) Point {
return Point{p.Row - rune(i), p.Col}
}
func (p Point) NudgeDown(i int) Point {
return Point{p.Row + rune(i), p.Col}
}
func (p Point) NudgeLeft(i int) Point {
return Point{p.Row, p.Col - i}
}
func CoordToPoint(b rune, i int) Point {
if unicode.IsUpper(b) {
b = unicode.ToLower(b)
}
return Point{b, i}
}
func (b Board) print() {
fmt.Println(" 1 2 3 4 5 6 7 8 9 10")
for c := 'a'; c <= 'j'; c++ {
fmt.Printf("%c", c)
for i := 1; i <= 10; i++ {
cprint.Printf["White"](" " + string(b.Board[CoordToPoint(c, i)]))
}
fmt.Println()
}
}
func (b Board) GameOver() bool {
for c := 'a'; c <= 'j'; c++ {
for i := 1; i <= 10; i++ {
if b.Board[CoordToPoint(c, i)] == 'O' {
return false
}
}
}
return true
}
func (b Board) shipExistsAt(coords []Point) bool {
for _, p := range coords {
if b.Board[p] != '-' {
return true
}
}
return false
}
// ok so for some stupid reason that i cant figure out, even if i pass the ship
// parameter here by reference the new coordinate array that the ship contains
// WILL NOT be updated when the function returns, and i literally have no idea why
// so thats why it returns the Ship with the new array as well as the error
func (b *Board) PlaceShip(s Ship, p Point, vertical bool) (Ship, error) {
switch vertical {
case true:
if p.Row+rune(s.Length) > 'j' {
break
}
for i := 0; i < s.Length; i++ {
s.Coords = append(s.Coords, Point{p.Row + rune(i), p.Col})
}
if !b.shipExistsAt(s.Coords) {
for _, c := range s.Coords {
b.Board[c] = 'O'
}
} else {
s.Coords = make([]Point, 0)
return s, fmt.Errorf("Ship already exists at that point")
}
return s, nil
case false:
if p.Col+s.Length > 10 {
break
}
for i := 0; i < s.Length; i++ {
s.Coords = append(s.Coords, Point{p.Row, p.Col + i})
}
if !b.shipExistsAt(s.Coords) {
for _, c := range s.Coords {
b.Board[c] = 'O'
}
} else {
s.Coords = make([]Point, 0)
return s, fmt.Errorf("Ship already exists at that point")
}
return s, nil
default:
s.Coords = make([]Point, 0)
return s, fmt.Errorf("Coordinate out of bounds")
}
s.Coords = make([]Point, 0)
return s, fmt.Errorf("Coordinate out of bounds.")
}
func (b *Board) PlaceShips() {
stdin := bufio.NewReader(os.Stdin)
var vert rune
var vertBool bool
for i := range b.Ships {
s := b.Ships[i]
for {
b.print()
cprint.Printf["Yellow"]("Where do you want your %s? (Length %d): ", s.Name, s.Length)
p, err := InputPoint()
if err != nil {
cprint.Printf["Red"]("Error: " + err.Error() + "\n")
}
cprint.Printf["White"]("Placed Horizontally or Vertically (h or v): ")
_, err = fmt.Scanf("%c", &vert)
stdin.ReadString('\n')
if err != nil {
stdin.ReadString('\n')
cprint.Printf["Red"]("Bad input! Enter 'v', 'h', 'vertical', or 'horizontal'\n")
}
vertBool = HVToBool(vert)
if b.Ships[i], err = b.PlaceShip(s, p, vertBool); err == nil {
s := b.Ships[i]
ClearScreen()
if vertBool {
cprint.Printf["Cyan"]("%s placed vertically at coordinate [%c%d]!\n", s.Name, p.Row, p.Col)
} else {
cprint.Printf["Cyan"]("%s placed horizontally at coordinate [%c%d]!\n", s.Name, p.Row, p.Col)
}
break
} else {
cprint.Printf["Red"]("Error: " + err.Error() + "\n")
}
}
}
}
func (b *Board) AutoPlaceShips() {
var err error
for i := range b.Ships {
for {
s := b.Ships[i]
p := RandPoint()
v := rand.Intn(2)
b.Ships[i], err = b.PlaceShip(s, p, itob(v))
s = b.Ships[i]
if err == nil {
break
}
}
}
}
func (b Board) Sunk(s Ship) bool {
for _, p := range s.Coords {
if b.Board[p] == 'O' { //there are still spots yet to be hit on the ship
time.Sleep(5)
return false
}
}
return true
}
/*
returns whether or not the shot hit, and if the ship has been sunk,
it returns the sunken ship. otherwise, it returns an empty Ship
*/
func (b Board) CheckForHit(c Point) (hit bool, ship Ship) {
if b.Board[c] == 'O' {
hit = true
for _, s := range b.Ships {
for _, p := range s.Coords {
if p == c {
ship = s
break
}
}
if ship.Name == s.Name {
break
}
}
} else {
hit = false
ship = Ship{}
}
return hit, ship
}

@ -0,0 +1,33 @@
package board
import (
"fmt"
"github.com/ghfarrell/go-battleship/cprint"
)
// clearscreen and printlogo functions have to be in this package so that I can use
// them in the ship placement loop :/
func PrintLogo() {
cprint.Printf["Magenta"](" /$$$$$$$ /$$$$$$ /$$$$$$$$/$$$$$$$$/$$ /$$$$$$$$ \n")
cprint.Printf["Magenta"]("| $$__ $$ /$$__ $$|__ $$__/__ $$__/ $$ | $$_____/ \n")
cprint.Printf["Magenta"]("| $$ \\ $$| $$ \\ $$ | $$ | $$ | $$ | $$ \n")
cprint.Printf["Magenta"]("| $$$$$$$ | $$$$$$$$ | $$ | $$ | $$ | $$$$$ \n")
cprint.Printf["Magenta"]("| $$__ $$| $$__ $$ | $$ | $$ | $$ | $$__/ \n")
cprint.Printf["Magenta"]("| $$ \\ $$| $$ | $$ | $$ | $$ | $$ | $$ \n")
cprint.Printf["Magenta"]("| $$$$$$$/| $$ | $$ | $$ | $$ | $$$$$$$$| $$$$$$$$| \n")
cprint.Printf["Magenta"]("|_______/ |__/ |__/ |__/ |__/ |________/|________/ \n")
cprint.Printf["Magenta"](" /$$$$$$ /$$ /$$ /$$$$$$ /$$$$$$$\n")
cprint.Printf["Magenta"](" /$$__ $$| $$ | $$|_ $$_/| $$__ $$\n")
cprint.Printf["Magenta"]("| $$ \\__/| $$ | $$ | $$ | $$ \\ $$\n")
cprint.Printf["Magenta"]("| $$$$$$ | $$$$$$$$ | $$ | $$$$$$$/\n")
cprint.Printf["Magenta"](" \\____ $$| $$__ $$ | $$ | $$____/ \n")
cprint.Printf["Magenta"](" /$$ \\ $$| $$ | $$ | $$ | $$ \n")
cprint.Printf["Magenta"](" $$$$$$/| $$ | $$ /$$$$$$| $$ \n")
cprint.Printf["Magenta"](" \\______/ |__/ |__/|______/|__/ \n")
cprint.Printf["Red"]("Press ctrl + c at any time to quit.\n\n")
}
func ClearScreen() {
fmt.Print("\033[H\033[2J")
PrintLogo()
}

@ -0,0 +1,58 @@
package cprint
import (
"fmt"
"github.com/jwalton/go-supportscolor"
)
var Printf map[string]func(string, ...interface{})
func init() {
colors := []string{
"Red",
"Blue",
"Yellow",
"White",
"Magenta",
"Cyan",
"Green",
}
Printf = make(map[string]func(s string, a ...interface{}))
if supportscolor.Stdout().SupportsColor {
Printf["Yellow"] = func(s string, a ...interface{}) {
p := "\u001b[33;1m" + s + "\u001b[0m"
fmt.Printf(p, a...)
}
Printf["Red"] = func(s string, a ...interface{}) {
p := "\u001b[31;1m" + s + "\u001b[0m"
fmt.Printf(p, a...)
}
Printf["Cyan"] = func(s string, a ...interface{}) {
p := "\u001b[36;1m" + s + "\u001b[0m"
fmt.Printf(p, a...)
}
Printf["Magenta"] = func(s string, a ...interface{}) {
p := "\u001b[35;1m" + s + "\u001b[0m"
fmt.Printf(p, a...)
}
Printf["White"] = func(s string, a ...interface{}) {
p := "\u001b[37;1m" + s + "\u001b[0m"
fmt.Printf(p, a...)
}
Printf["Green"] = func(s string, a ...interface{}) {
p := "\u001b[32;1m" + s + "\u001b[0m"
fmt.Printf(p, a...)
}
Printf["Blue"] = func(s string, a ...interface{}) {
p := "\u001b[34;1m" + s + "\u001b[0m"
fmt.Printf(p, a...)
}
} else {
for _, c := range colors {
Printf[c] = func(s string, a ...interface{}) {
fmt.Printf(s, a...)
}
}
}
}

@ -0,0 +1,122 @@
package main
import (
"math/rand"
"time"
"github.com/ghfarrell/go-battleship/board"
"github.com/ghfarrell/go-battleship/cprint"
)
/*
____________
| |
| |
| |
| |
|____________|
*/
func RollTheBones() int {
for i := 0; i < 2; i++ {
board.ClearScreen()
cprint.Printf["White"](" _______\n")
cprint.Printf["White"](" /\\ \\\n")
cprint.Printf["White"](" /()\\ () \\\n")
cprint.Printf["White"]("/ \\_______\\\n")
cprint.Printf["White"]("\\ /() /\n")
cprint.Printf["White"](" \\()/ () /\n")
cprint.Printf["White"](" \\/_____()/\n")
time.Sleep(250 * time.Millisecond)
board.ClearScreen()
cprint.Printf["White"](" _______\n")
cprint.Printf["White"](" /\\ () ()\\\n")
cprint.Printf["White"](" /()\\ () \\\n")
cprint.Printf["White"]("() \\()___()\\\n")
cprint.Printf["White"]("\\ ()/ /\n")
cprint.Printf["White"](" \\()/ () /\n")
cprint.Printf["White"](" \\/_______/\n")
time.Sleep(250 * time.Millisecond)
board.ClearScreen()
cprint.Printf["White"](" _______\n")
cprint.Printf["White"](" /\\() ()\\\n")
cprint.Printf["White"](" /()\\() ()\\\n")
cprint.Printf["White"]("/() \\()___()\\\n")
cprint.Printf["White"]("\\ ()/() /\n")
cprint.Printf["White"](" \\()/ /\n")
cprint.Printf["White"](" \\/_____()/\n")
time.Sleep(250 * time.Millisecond)
board.ClearScreen()
cprint.Printf["White"](" _______\n")
cprint.Printf["White"](" /\\ ()\\\n")
cprint.Printf["White"](" /()\\ () \\\n")
cprint.Printf["White"]("/ \\()_____\\\n")
cprint.Printf["White"]("\\ /() ()/\n")
cprint.Printf["White"](" \\()/ () /\n")
cprint.Printf["White"](" \\/()___()/\n")
time.Sleep(250 * time.Millisecond)
}
r := rand.Intn(6) + 1
resultDice(r)
return r
}
func resultDice(r int) {
switch r {
case 1:
board.ClearScreen()
cprint.Printf["White"](" ____________\n")
cprint.Printf["White"](" | |\n")
cprint.Printf["White"](" | |\n")
cprint.Printf["White"](" | () |\n")
cprint.Printf["White"](" | |\n")
cprint.Printf["White"](" |____________|\n")
time.Sleep(1 * time.Second)
case 2:
board.ClearScreen()
cprint.Printf["White"](" ____________\n")
cprint.Printf["White"](" | |\n")
cprint.Printf["White"](" | () |\n")
cprint.Printf["White"](" | |\n")
cprint.Printf["White"](" | () |\n")
cprint.Printf["White"](" |____________|\n")
time.Sleep(1 * time.Second)
case 3:
board.ClearScreen()
cprint.Printf["White"](" ____________\n")
cprint.Printf["White"](" | |\n")
cprint.Printf["White"](" | () |\n")
cprint.Printf["White"](" | () |\n")
cprint.Printf["White"](" | () |\n")
cprint.Printf["White"](" |____________|\n")
time.Sleep(1 * time.Second)
case 4:
board.ClearScreen()
cprint.Printf["White"](" ____________\n")
cprint.Printf["White"](" | |\n")
cprint.Printf["White"](" | () () |\n")
cprint.Printf["White"](" | |\n")
cprint.Printf["White"](" | () () |\n")
cprint.Printf["White"](" |____________|\n")
time.Sleep(1 * time.Second)
case 5:
board.ClearScreen()
cprint.Printf["White"](" ____________\n")
cprint.Printf["White"](" | |\n")
cprint.Printf["White"](" | () () |\n")
cprint.Printf["White"](" | () |\n")
cprint.Printf["White"](" | () () |\n")
cprint.Printf["White"](" |____________|\n")
time.Sleep(1 * time.Second)
case 6:
board.ClearScreen()
cprint.Printf["White"](" ____________\n")
cprint.Printf["White"](" | |\n")
cprint.Printf["White"](" | () () |\n")
cprint.Printf["White"](" | () () |\n")
cprint.Printf["White"](" | () () |\n")
cprint.Printf["White"](" |____________|\n")
time.Sleep(1 * time.Second)
}
}

@ -0,0 +1,9 @@
module github.com/ghfarrell/go-battleship
go 1.17
require (
github.com/jwalton/go-supportscolor v1.1.0 // direct
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 // indirect
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
)

@ -0,0 +1,7 @@
github.com/jwalton/go-supportscolor v1.1.0 h1:HsXFJdMPjRUAx8cIW6g30hVSFYaxh9yRQwEWgkAR7lQ=
github.com/jwalton/go-supportscolor v1.1.0/go.mod h1:hFVUAZV2cWg+WFFC4v8pT2X/S2qUUBYMioBD9AINXGs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 h1:SgQ6LNaYJU0JIuEHv9+s6EbhSCwYeAf5Yvj6lpYlqAE=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

@ -0,0 +1,115 @@
/*
TODO:
- give AI a hard mode
- make AI communicate with player (sassy)
*/
/*
COLORS:
- Cyan: Game talking to user
- Red: Errors or hit
- White/Yellow: Expecting input
- Green: Response to input
- Magenta: Static game text and game over text
- Blue: Debug and AI talking
*/
package main
import (
"flag"
"fmt"
"math"
"math/rand"
"os"
"os/signal"
"time"
"github.com/ghfarrell/go-battleship/board"
"github.com/ghfarrell/go-battleship/cprint"
"github.com/ghfarrell/go-battleship/player"
)
func PlayerGoesFirst() bool {
var playerGuess int
for {
cprint.Printf["Cyan"]("Time to determine who goes first! Guess a number 1-6: ")
_, err := fmt.Scanf("%d\n", &playerGuess)
if err != nil {
cprint.Printf["Red"]("Bad input! Try again\n")
} else {
break
}
}
aiGuess := rand.Intn(6) + 1
cprint.Printf["Cyan"]("The AI made its guess! Time to see who is closest...\n")
time.Sleep(1 * time.Second)
r := RollTheBones()
fmt.Println()
cprint.Printf["Cyan"]("The number is %d!\n", r)
cprint.Printf["Cyan"]("The AI guessed %d, so the winner is...\n", aiGuess)
if math.Abs(float64(r-playerGuess)) < math.Abs(float64(r-aiGuess)) {
cprint.Printf["Cyan"]("You! You will make your strike first.\n")
} else if math.Abs(float64(r-playerGuess)) == math.Abs(float64(r-aiGuess)) {
cprint.Printf["Cyan"]("You... via tiebreaker!\nYou both guessed %d, but I like you more than the AI.\n", playerGuess)
} else {
cprint.Printf["Cyan"]("The AI! Too bad! They will strike first.\n")
}
time.Sleep(3 * time.Second)
return true
}
func playerWin() {
}
func aiWin() {
}
func main() {
var (
User player.Player
ai player.AI
userFirst bool
)
debug := flag.Bool("debug", false, "Turns debug mode on")
flag.Parse()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
cprint.Printf["Red"]("\nExiting Battleship... ")
os.Exit(1)
}()
User.Initialize()
ai.Initialize()
if *debug {
User.DebugPlaceShips()
cprint.Printf["Magenta"]("User ships placed...\n")
ai.PlaceShips()
cprint.Printf["Magenta"]("AI ships placed...\n")
userFirst = true
} else {
board.ClearScreen()
cprint.Printf["Cyan"]("Time to place your ships!\n\n")
User.PlaceShips()
ai.PlaceShips()
userFirst = PlayerGoesFirst()
}
//main gameplay loop
if userFirst {
for {
if User.Guess(&ai, *debug) || ai.Guess(&User, *debug) {
break
}
}
} else {
for {
if ai.Guess(&User, *debug) || User.Guess(&ai, *debug) {
break
}
}
}
}

@ -0,0 +1,299 @@
package player
import (
"fmt"
"math/rand"
"time"
"github.com/ghfarrell/go-battleship/board"
"github.com/ghfarrell/go-battleship/cprint"
)
type Direction int
const (
Up Direction = iota
Right
Down
Left
)
func (d Direction) opposite() Direction {
switch d {
case Up:
return Down
case Down:
return Up
case Left:
return Right
case Right:
return Left
default:
//impossible but compiler yells at me if i dont
return Up
}
}
/*
In ship slice, ships are ordered by length, longest to smallest, and alphabetically
*/
type Player struct {
Guesses []board.Point
Friendly board.Board
Enemy board.Board
}
type AI struct {
// a Column value of -1 means the pivot is not in use
Pivot board.Point
PivotOffset int
Dir Direction
Player
}
func (p *Player) Initialize() {
p.Friendly.Initialize()
p.Enemy.Initialize()
p.Guesses = make([]board.Point, 0)
}
func (p *AI) Initialize() {
p.Friendly.Initialize()
p.Enemy.Initialize()
p.Guesses = make([]board.Point, 0)
p.PivotOffset = 1
p.Pivot = board.Point{
Row: 'a',
Col: -1,
}
p.Dir = Up
}
func (p Player) PrintFriendly() {
fmt.Println(" 1 2 3 4 5 6 7 8 9 10")
for c := 'a'; c <= 'j'; c++ {
fmt.Printf("%c", c)
for i := 1; i <= 10; i++ {
cprint.Printf["White"](" " + string(p.Friendly.Board[board.CoordToPoint(c, i)]))
}
fmt.Println()
}
}
func (p Player) PrintEnemy() {
fmt.Println(" 1 2 3 4 5 6 7 8 9 10")
for c := 'a'; c <= 'j'; c++ {
fmt.Printf("%c", c)
for i := 1; i <= 10; i++ {
cprint.Printf["Red"](" " + string(p.Enemy.Board[board.CoordToPoint(c, i)]))
}
fmt.Println()
}
}
func hitResponse() string {
r := []string{
"A perfect shot!",
"Nice hit!",
"Dead on!",
"Incredible shot!",
"Nice one!",
}
return r[rand.Intn(len(r))]
}
func missResponse() string {
r := []string{
"So close!",
"Try hitting a boat next time...",
"Almost!",
"Too bad!",
"No dice!",
}
return r[rand.Intn(len(r))]
}
func (p Player) hasGuessed(c board.Point) bool {
for _, i := range p.Guesses {
if i == c {
return true
}
}
return false
}
func (p Player) DebugPrintGuesses() {
cprint.Printf["Blue"]("[")
for _, c := range p.Guesses {
cprint.Printf["Blue"](" %c%d ", c.Row, c.Col)
}
cprint.Printf["Blue"]("]")
}
func (p *Player) PlaceShips() {
p.Friendly.PlaceShips()
}
func (p *Player) DebugPlaceShips() {
p.Friendly.AutoPlaceShips()
}
func (a *AI) PlaceShips() {
a.Friendly.AutoPlaceShips()
}
func (p *Player) Guess(a *AI, debug bool) bool {
var (
c board.Point
err error
)
board.ClearScreen()
p.PrintEnemy()
p.PrintFriendly()
if debug {
a.PrintFriendly()
}
if debug {
cprint.Printf["Blue"]("DEBUG: USER GUESSES -> ")
p.DebugPrintGuesses()
cprint.Printf["Blue"]("\nDEBUG: AI GUESSES -> ")
a.DebugPrintGuesses()
fmt.Println()
}
for {
cprint.Printf["Yellow"]("Pick a coordinate to attack: ")
c, err = board.InputPoint()
if err != nil {
cprint.Printf["Red"]("Error: " + err.Error() + "\n")
} else if p.hasGuessed(c) {
cprint.Printf["Red"]("Woops! You already guessed that spot.\n")
} else {
p.Guesses = append(p.Guesses, c)
break
}
}
if hit, ship := a.Friendly.CheckForHit(c); hit {
a.Friendly.Board[c] = 'X'
p.Enemy.Board[c] = 'X'
cprint.Printf["Green"](hitResponse() + "\n")
cprint.Printf["Green"]("You hit the AI's %s!\n", ship.Name)
if a.Friendly.Sunk(ship) {
cprint.Printf["Green"]("The AI's %s sunk!\n", ship.Name)
}
if a.Friendly.GameOver() {
cprint.Printf["Magenta"]("YOU WIN! THE EVIL AI IS DEFEATED!\n")
return true
}
} else {
p.Enemy.Board[c] = 'O'
cprint.Printf["Red"](missResponse() + "\n")
}
time.Sleep(2 * time.Second)
return false
}
func (a AI) randomGuess() board.Point {
for {
c := board.RandPoint()
if !a.hasGuessed(c) {
return c
}
}
}
/*
guessing alg should work like:
- randomly guess a spot until you hit, that spot is the pivot
- go one direction (say, up) until either you miss or you sink
- if you miss, go one spot below the pivot
- if that misses, go left from the pivot
- if that misses, go right from the pivot
- since you go all directions, one must work and you sink a ship
- when you sink, go back to random guesses and repeat
*/
/*
TODO:
- it guessed up and missed, so when it guessed right and hit, it guessed that
exact same spot again which is impossible to guess the same spot twice...
*/
func (a *AI) getGuess() (c board.Point) {
if a.Pivot.Col == -1 || len(a.Guesses) < 1 {
c = a.randomGuess()
return
}
switch a.Dir {
case Up:
c = a.Pivot.NudgeUp(a.PivotOffset)
case Down:
c = a.Pivot.NudgeDown(a.PivotOffset)
case Left:
c = a.Pivot.NudgeLeft(a.PivotOffset)
case Right:
c = a.Pivot.NudgeRight(a.PivotOffset)
}
if a.hasGuessed(c) { // if the bot somehow comes up with a point that has already been guessed,
c = a.randomGuess() // ill deal with it later
} else if c.IsValid() {
a.PivotOffset++
return
} else { // if the nudged coordinate is out of bounds, go the opposite way
a.PivotOffset = 1
a.Dir = a.Dir.opposite()
c = a.getGuess()
}
return
}
func (a *AI) Guess(p *Player, debug bool) bool {
c := a.getGuess()
a.Guesses = append(a.Guesses, c)
cprint.Printf["White"]("Now the AI's time to strike...\n")
if !debug {
time.Sleep(1 * time.Second)
}
cprint.Printf["White"]("The AI has fired at coordinate %c%d!\n", c.Row, c.Col)
if !debug {
time.Sleep(1 * time.Second)
}
if hit, sunkShip := p.Friendly.CheckForHit(c); hit {
cprint.Printf["Red"]("You've been hit!\n")
p.Friendly.Board[c] = 'X'
if a.Pivot.Col == -1 { // if AI wasn't guessing on a pivot and hit,
a.Pivot = c // set the new pivot
a.PivotOffset = 1
a.Dir = Up
if debug {
cprint.Printf["Blue"]("DEBUG: New pivot: %c%d\n", a.Pivot.Row, a.Pivot.Col)
}
} // else, keep on guessing on that pivot
cprint.Printf["Red"]("Your %s has taken a hit!\n", sunkShip.Name)
if p.Friendly.Sunk(sunkShip) {
// a little more complex case: if the AI was guessing on a line and sunk a ship that is
// shorter than the line it was guessing on, then the line must be composed of multiple
// ships, so after sinking one of the two ships, it will travel in the opposite direction
// to sink the other ship that composed that line
if a.PivotOffset > sunkShip.Length {
a.PivotOffset = 1
a.Dir = a.Dir.opposite()
} else {
// reset pivot
a.Pivot.Col = -1
cprint.Printf["Red"]("The AI has sunk your %s!\n", sunkShip.Name)
}
}
if p.Friendly.GameOver() {
cprint.Printf["Magenta"]("OH GLOB! YOU LOST! YOU STINK!\n")
return true
}
} else {
if a.Pivot.Col != -1 { // if AI was guessing on a pivot and missed...
if a.PivotOffset > 2 { // if the AI was following a track of hits and missed
a.Dir = a.Dir.opposite() // go the opposite direction
} else { // cycle directions
a.Dir++
}
a.PivotOffset = 1
}
cprint.Printf["Green"]("Phew! The AI doesn't have very good aim.")
}
time.Sleep(3 * time.Second)
return false
}

@ -0,0 +1,66 @@
package board_test
import (
"math/rand"
"testing"
"time"
"github.com/ghfarrell/go-battleship/board"
"github.com/ghfarrell/go-battleship/cprint"
)
var b board.Board
func init() {
rand.Seed(int64(time.Now().Nanosecond()))
b.Board = make(map[board.Point]byte, 100)
for c := 'a'; c <= 'j'; c++ {
for i := 1; i <= 10; i++ {
b.Board[board.CoordToPoint(c, i)] = '-'
}
}
b.Ships = make([]board.Ship, 5)
b.Ships[0].Name = "Carrier"
b.Ships[1].Name = "Battleship"
b.Ships[2].Name = "Cruiser"
b.Ships[3].Name = "Submarine"
b.Ships[4].Name = "Destroyer"
b.Ships[0].Length = 5
b.Ships[1].Length = 4
b.Ships[2].Length = 3
b.Ships[3].Length = 3
b.Ships[4].Length = 2
for _, s := range b.Ships {
s.Coords = make([]board.Point, 0)
}
}
func TestPlaceShip(t *testing.T) {
s := b.Ships[0]
testPoint := board.Point{
Row: 'a',
Col: 1,
}
solPoints := make([]board.Point, 0)
solPoints = append(solPoints, board.Point{Row: 'a', Col: 1})
solPoints = append(solPoints, board.Point{Row: 'b', Col: 1})
solPoints = append(solPoints, board.Point{Row: 'c', Col: 1})
solPoints = append(solPoints, board.Point{Row: 'd', Col: 1})
solPoints = append(solPoints, board.Point{Row: 'e', Col: 1})
s, _ = b.PlaceShip(s, testPoint, true)
cprint.Printf["Red"]("%s: %v\n", s.Name, s.Coords)
for i := 0; i < 5; i++ {
cprint.Printf["Red"]("%v\n", s.Coords[i])
if s.Coords[i] != solPoints[i] {
t.Errorf("Wrong points; Expected %v got %v", solPoints, s.Coords)
}
}
}
func TestAutoPlaceShips(t *testing.T) {
b.AutoPlaceShips()
for _, s := range b.Ships {
if len(s.Coords) < 1 {
t.Errorf("Expect coordinate array, got %v", s.Coords)
}
}
}
Loading…
Cancel
Save