# Hundir la flota (c) 2023 Baltasar MIT License <baltasarq@uvigo.es>


using Random


const FILAS = ('A':'J')
const COLS = (0:9)
@enum DIRECCION vertical=1 horizontal=2


# Longitudes de los barcos para cada jugador
const LONG_BARCOS = (1, 1, 1, 2, 2, 2, 3, 3, 4)


struct Jugador
    tablero::Matrix{Char}
    tiros::Vector{Tuple{Int8, Int8}}

    function Jugador()
        return new(
            fill(' ', (10, 10)),
            Vector{Tuple{Int8, Int8}}())
    end
end


function visualiza_tablero(t::Matrix{Char})
    # Visualizar números de columnas
    print(' ')
    for i in COLS
        print(i)
    end
    println()

    # Visualizar cada fila del tablero
    for i in FILAS
        print(i)
        for j in COLS
            print(t[i - FILAS[1] + 1, j + 1])
        end
        println()
    end
end

function visualiza_tablero_sin_barcos(t::Matrix{Char})
    # Visualizar números de columnas
    print(' ')
    for i in COLS
        print(i)
    end
    println()

    # Visualizar cada fila del tablero
    for i in FILAS
        print(i)
        for j in COLS
            ch = t[i - FILAS[1] + 1, j + 1]

            if ch == 'x'
                ch = ' '
            end

            print(ch)
        end
        println()
    end
end


# Determina si hay todavía barcos
# en el tablero
function hay_barcos(t::Matrix{Char})::Bool
    return any((c) -> c == 'x', t)
end


# Traduce desde c8 -> (3, 9)
function coordenadas_desde_notacion(c::String)::Tuple{Int8, Int8}
    toret = (Int8(0), Int8(0))

    if (length(c) == 2
        && isletter(c[1])
        && isdigit(c[2]))

        fila = Int8(uppercase(c[1]) - 'A' + 1)
        col = Int8(c[2] - '0' + 1)
        toret = (fila, col)
    end

    return toret
end


# Devuelve la siguiente posición dada una tupla,
# dependiendo de si dir es vertical (x + 1, y)
# u horizontal (x, y + 1)
function next_pos(pos::Tuple{Int8, Int8}, dir::DIRECCION)
    toret = collect(pos)

    if dir == vertical
        toret[1] += 1
    else
        toret[2] += 1
    end

    return Tuple(toret)
end


function crea_jugador_ordenador()::Jugador
    toret = Jugador()

    # Crear los barcos
    for long_barco in LONG_BARCOS
        recoloca = true
        pos = (0, 0)
        vh = horizontal

        while recoloca
            recoloca = false
            pos = (Int8(rand((1:10))), Int8(rand((1:10))))
            vh = rand((vertical, horizontal))

            cur_pos = pos
            for _ in 1:long_barco
                if (cur_pos[1] > 10
                    || cur_pos[2] > 10
                    || toret.tablero[cur_pos[1], cur_pos[2]] != ' ')
                    recoloca = true
                    break
                end

                # Siguiente pos
                cur_pos = next_pos(cur_pos, vh)
            end
        end

        # Coloca el barco
        cur_pos = pos
        for _ in 1:long_barco
            toret.tablero[cur_pos[1], cur_pos[2]] = 'x'
            cur_pos = next_pos(cur_pos, vh)
        end
    end

    return toret
end


function lee_pos(msg::String)::Tuple{Int8, Int8}
    PROMPT = "\nDame pos. notacional (como a8)\n"

    print("$PROMPT$msg")
	str_pos = readline()
	toret = coordenadas_desde_notacion(str_pos)

	while (!(toret[1] in 1:length(FILAS))
			|| !(toret[2] in 1:length(COLS)))

		print("$PROMPT$msg")
		str_pos = readline()
		toret = coordenadas_desde_notacion(str_pos)
	end

	println()
	return toret
end


function crea_jugador_humano()::Jugador
    MAX_BARCOS = length(LONG_BARCOS)
	toret = Jugador()

	for (i, long_barco) in enumerate(LONG_BARCOS)
		# Lee el barco
		visualiza_tablero(toret.tablero)
		pos = lee_pos("Barco $i/$MAX_BARCOS de longitud $(long_barco): ")
		print("Vertical u horizontal (v/h): ")
		str_vh = readline()
		vh = uppercase(str_vh)[1] == 'V' ? vertical : horizontal

		# Coloca el barco
        cur_pos = pos
        for _ in 1:long_barco
            toret.tablero[cur_pos[1], cur_pos[2]] = 'x'
            cur_pos = next_pos(cur_pos, vh)
        end
	end

	return toret
end


function tira_persona(jordr::Jugador, jpers::Jugador)
    # Pide el tiro
    visualiza_tablero_sin_barcos(jordr.tablero)
    pos_tiro = lee_pos("Dime a dónde tirar: ")

    # Guardarlo
    push!(jpers.tiros, pos_tiro)

    # Representarlo
    celda = jordr.tablero[pos_tiro[1], pos_tiro[2]]

    if celda == 'x'
        jordr.tablero[pos_tiro[1], pos_tiro[2]] = '#'
    else
        jordr.tablero[pos_tiro[1], pos_tiro[2]] = 'o'
    end
end


function tira_ordenador(jordr::Jugador, jpers::Jugador)
    # Decidir tiro
    pos_tiro = (Int8(rand(1:10)), Int8(rand(1:10)))

    while pos_tiro in jordr.tiros
        pos_tiro = (Int8(rand(1:10)), Int8(rand(1:10)))
    end

    # Guardarlo
    push!(jordr.tiros, pos_tiro)

    # Representarlo
    celda = jpers.tablero[pos_tiro[1], pos_tiro[2]]

    if celda == 'x'
        jpers.tablero[pos_tiro[1], pos_tiro[2]] = '#'
    else
        jpers.tablero[pos_tiro[1], pos_tiro[2]] = 'o'
    end
end


function main()
	Random.seed!(Int(trunc(time())))

    # Crear los jugadores, con sus barcos
    jordr = crea_jugador_ordenador()
    jpers = crea_jugador_humano()

    # Mientras haya barcos...
    while(hay_barcos(jordr.tablero)
          && hay_barcos(jpers.tablero))

        tira_persona(jordr, jpers)

        tira_ordenador(jordr, jpers)
        println("Ya he tirado:")
        visualiza_tablero(jpers.tablero)
    end

    # Fin de juego
    if hay_barcos(jpers.tablero)
        println("Ganaste !!")
    else
        println("He ganado yo !!")
    end
end

main()
