commit
0045790ec4
@ -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…
Reference in new issue