You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
305 lines
6.4 KiB
305 lines
6.4 KiB
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
|
|
}
|