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

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
}