import random

def set_square(x, y, new_val, board):
    """
    This function indexes into the given board at position (x, y).
    We then change that value to new_val. Returns nothing.
    """
    board[x][y] = new_val

def get_square(x, y, board):
    """
    This function takes a board and returns the value at that square(x,y).
    """
    return board[x][y]

def display_board(board):
    print(*board, sep="\n")
    

def neighbour_squares(x, y, num_rows, num_cols):
    """
    (x, y) 0-based index co-ordinate pair.
    num_rows, num_cols: specifiy the max size of the board
    
    returns all valid (x, y) coordinates from starting position.
    """
    offsets = [(-1,-1), (-1,0), (-1,1),
               ( 0,-1),         ( 0,1),
               ( 1,-1), ( 1,0), ( 1,1)]
    
    result = []
    for x2, y2 in offsets:
        
        px = x + x2
        py = y + y2
        
        row_check = 0 <= px < num_rows
        col_check = 0 <= py < num_cols
        
        if row_check and col_check:
            point = (py, px)
            result.append(point)
            
    return result
    
def count_occurence_of_character_in_neighbour_squares(x, y, board, character):
    """
    returns the number of neighbours of (x,y) that are bombs. Max is 8, min is 0.
    """
    num_rows = len(board[0])
    num_cols = len(board)
        
    squares = neighbour_squares(x, y, num_rows, num_cols)

    character_found = 0
    for px, py in squares:
        
        square_value = get_square(px, py, board)
        
        if square_value == character:
            character_found += 1
          
    return character_found

def build_board(num_rows, num_cols, bomb_count=0, non_bomb_character="-"):
    board_temp = ["B"] * bomb_count + [non_bomb_character] * (num_rows * num_cols - bomb_count)

    if bomb_count:
        random.shuffle(board_temp)

    board = []
    for i in range(0, num_rows*num_cols, num_cols):
        board.append(board_temp[i:i+num_cols])
    return board

"""
Explanation: Loading our Previous Functions...
End of explanation
"""


def flag_square(row, col, player_board):
    p_square = get_square(row, col, player_board)
    
    if p_square in "012345678":
        # do nothing
        return
    
    ## set flag
    if p_square == EMPTY_SQUARE_CHARACTER:
        set_square(row, col, FLAG_CHARACTER, player_board)
    
    ## Deflag if flag is already set
    if p_square == FLAG_CHARACTER:
        set_square(row, col, EMPTY_SQUARE_CHARACTER, player_board)
        return
     
def reveal_square(row, col, player_board, game_board):
    p_square = get_square(row, col, player_board)
    
    if p_square in "012345678" or p_square == FLAG_CHARACTER:
        ## do nothing
        return
    
    g_square = get_square(row, col, game_board)
    
    if g_square == BOMB_CHARACTER:
        return game_over()
    
    else:
        bomb_count = count_occurence_of_character_in_neighbour_squares(row, col, game_board, BOMB_CHARACTER)
        set_square(row, col, str(bomb_count), player_board)

def game_over():
    print("GAME OVER")
    ## Later on, we can implement more logic here, such as asking if the player wants to play again.

"""
Explanation: Possible Solution:
End of explanation
"""


import random

NUMBER_OF_ROWS  = 3
NUMBER_OF_COLS  = 3
NUMBER_OF_BOMBS = 2 # You may remember that all_caps mean that these variables should NOT change values at runtime.

BOMB_CHARACTER = "B"
FLAG_CHARACTER = "F"
EMPTY_SQUARE_CHARACTER = "-" 

random.seed(213) # for reproducible results

game_board = build_board(NUMBER_OF_ROWS, NUMBER_OF_COLS, bomb_count = NUMBER_OF_BOMBS)
player_board = build_board(NUMBER_OF_ROWS, NUMBER_OF_COLS, bomb_count=0)

"""
Explanation: Okay, so now that we have the reveal and flag functions sorted, we are dangeriously close to actually playing a game. Lets try and play...
End of explanation
"""


print("INTERNAL GAME STATE:")
display_board(game_board)
print("")
print("PLAYER BOARD:")
display_board(player_board)

## Player board should display 2.
reveal_square(1, 0, player_board, game_board)

print("INTERNAL GAME STATE:")
display_board(game_board)
print("")
print("PLAYER BOARD:")
display_board(player_board)

"""
Explanation: The above code should be familiar to you. The only addition I've made here is that I've added some character definitions. I thought it was good idea to define what the bomb_character is just once and have all the other functions reference that constant value. 
When you start to glue the various peices of code together its perfectly normal to come up with new (and often better) ideas. So this means I'll need to go back and 'refactor' some of my older code to utilise this. 
Anyway, how about we play a game?
End of explanation
"""


## Square already revealed, nothing happens...
reveal_square(1, 0, player_board, game_board)

print("INTERNAL GAME STATE:")
display_board(game_board)
print("")
print("PLAYER BOARD:")
display_board(player_board)

## Flag a square
flag_square(2, 0, player_board)

print("INTERNAL GAME STATE:")
display_board(game_board)
print("")
print("PLAYER BOARD:")
display_board(player_board)

## Deflag square
flag_square(2, 0, player_board)

print("INTERNAL GAME STATE:")
display_board(game_board)
print("")
print("PLAYER BOARD:")
display_board(player_board)

## Reveal a bomb, Should display game over
reveal_square(1, 1, player_board, game_board)

print("INTERNAL GAME STATE:")
display_board(game_board)
print("")
print("PLAYER BOARD:")
display_board(player_board)

"""
Explanation: FUCK!
Notice that the player_board reveals a 1 here. But there is clearly two bombs nearby. What this means is that I have a bug somewhere in my code. 
It could be that my game_boards and player_boards are out of sync, or (more likely) I have some sort of indexing error in the "count_occurence_of_character_in_neighbour_squares" function. My best guess is that I've mixed up (x,y) coordinates somewhere. 
But, in a way, this is a good thing. It's a good thing because it gives me an excuse to talk a bit more about testing and debugging code. In development there is a concept called "failing fast". At heart it's a simple idea; you should test your code quickly and often. And you should do that because it's much better to see code fail when it is small and simple than when its large and complex.  If you have two thousand lines of code then that means there are two thousand possible places where the bug could be. If there are only twenty lines of code then there are only twenty possible places where the bug could be. 
The other advantage of testing code as soon as possible is that the longer bugs go unnoticed the harder they are to fix.

Okay I've made a mental note of the error, but let's carry on for the moment. Maybe we shall find another bug...
End of explanation
"""


if row_check and col_check:
    point = (py, px)
    result.append(point)
return result

for px, py in squares: # result    
    square_value = get_square(px, py, board)

"""
Explanation: Okay so after a bit more testing it looks like we have something that is working fairly well. So the only things left to do now if to solve that bug...
End of explanation
"""


if row_check and col_check:
    point = (py, px)
    result.append(point)
return result

for py, px in squares: # result    
    square_value = get_square(px, py, board)

"""
Explanation: The above two code snippets belong to two different functions. Notice that there is some confusion here regarding px and py. In the first bit of code we have the values (px, py) but store them in (py, px) order. The the for-loop says for px, py do some stuff. This means the names get switched, py becomes px and vice versa. This is bad. Its bad because this sort of error (even if it is not the source of the bug) just leads to confusion, and confusion leads to bugs and wasted time.
So the first thing to do would be to at least make the naming consistent across functions...
End of explanation
"""


