With only a couple of hours ahead of you in my experience with F#, I find the essence of your code fairly functional, although the graphic output is hard to grasp as it has a lot of flicker.
Below find a gentle review of your code with improvements (or at least another way to do the things) and inline comments. I've tried to improve the graphic output by using colors and a distinct update of the cells only when they change.
let playGOL rows columns = let mutable doContinue = true let size = rows*columns let alive = ConsoleColor.Green // Console colors as cell values instead of int/strings let dead = ConsoleColor.Red let combine listX listY = [ for x in listX do for y in listY do yield x, y ] let board = // create the board directly let rnd = new Random() [| for x in 1..size -> if rnd.NextDouble() < 0.2 then alive else dead |] // Initialize the array directly instead of iterate over a list then converted to array let indexToCoordinates index = index % columns + 1, index / columns + 1 // No need for parentheses let coordinatesToIndex (x, y) = (y - 1) * columns + (x - 1) // Instead of using chars I use background colors let updateCell cellIndex state = let x, y = indexToCoordinates cellIndex Console.CursorLeft <- x Console.CursorTop <- y Console.BackgroundColor <- state printf " " Console.BackgroundColor <- ConsoleColor.Black doContinue <- true let drawBoard () = board |> Array.iteri (fun i ch -> updateCell i ch) // Iterate over the board itself instead of a list let getLivingNeighboursCount idx = let x, y = indexToCoordinates idx let minX, maxX = (if x = 1 then columns else x - 1), (if x = columns then 1 else x + 1) let minY, maxY = (if y = 1 then rows else y - 1), (if y = rows then 1 else y + 1) combine [minX; x; maxX] [minY; y; maxY] |> List.filter (fun com -> com <> (x,y)) |> List.map (fun x -> board.[coordinatesToIndex x]) // Map directly from coordinates to value |> List.sumBy (fun x -> if x = alive then 1 else 0) let indexToNewState idx = let state = board.[idx] let livingNeighbours = getLivingNeighboursCount idx if livingNeighbours = 3 || livingNeighbours = 2 && state = alive then if state <> alive then updateCell idx alive // Instead of opdating the whole board just update the changed cell alive else if state <> dead then updateCell idx dead // Instead of opdating the whole board just update the changed cell dead let updateState () = doContinue <- false board |> Array.iteri (fun idx state -> (Array.set board idx (indexToNewState idx))) // Use the board itself to iterate over drawBoard() while doContinue do updateState () System.Threading.Thread.Sleep(100); Console.ReadLine() |> ignore let go() = let size = 40 Console.WindowHeight <- size + 5 Console.WindowWidth <- size + 5 playGOL size size
That said I think both solutions suffer from a generation problem (I'm not a GOL-expert so I may be wrong):

As the image shows getLivingNeighboursCount() checks against two different generations because the board cells are successively updated through the calls to Array.set in updateState() resulting in a false new state. The solution is to create a new board per generation and recursively check those while creating the next generation board:
let playGOL rows columns = let size = rows*columns let alive = ConsoleColor.Green // Console colors as cell values instead of int/strings let dead = ConsoleColor.Red let combine listX listY = [ for x in listX do for y in listY do yield x, y ] let indexToCoordinates index = index % columns + 1, index / columns + 1 // No need for parentheses let coordinatesToIndex (x, y) = (y - 1) * columns + (x - 1) // Instead of using chars I use background colors let updateCell cellIndex state = let x, y = indexToCoordinates cellIndex Console.CursorLeft <- x Console.CursorTop <- y Console.BackgroundColor <- state printf " " Console.BackgroundColor <- ConsoleColor.Black let drawBoard board = board |> Array.iteri (fun i ch -> updateCell i ch) // Iterate over the board itself instead of a list let getLivingNeighboursCount idx (board: ConsoleColor[]) = let x, y = indexToCoordinates idx let minX, maxX = (if x = 1 then columns else x - 1), (if x = columns then 1 else x + 1) let minY, maxY = (if y = 1 then rows else y - 1), (if y = rows then 1 else y + 1) combine [minX; x; maxX] [minY; y; maxY] |> List.filter (fun com -> com <> (x,y)) |> List.map (fun x -> board.[coordinatesToIndex x]) // Map directly from coordinates to value |> List.sumBy (fun x -> if x = alive then 1 else 0) // Replaced according to JanDotNet's comments // let indexToNewState idx (oldBoard: ConsoleColor[]) newBoard = // let state = oldBoard.[idx] // let livingNeighbours = oldBoard |> getLivingNeighboursCount idx // // if livingNeighbours = 3 || livingNeighbours = 2 && state = alive then // Array.set newBoard idx alive // if state <> alive then // updateCell idx alive // Instead of opdating the whole board just update the changed cell // true // else // false // else // Array.set newBoard idx dead // if state <> dead then // updateCell idx dead // Instead of opdating the whole board just update the changed cell // true // else // false // // let updateState (board: ConsoleColor[]) = // let rec updater bdr = // System.Threading.Thread.Sleep(100); // let nextBoard = [| for x in 1..size -> alive |] // let allResults = bdr |> Array.mapi (fun idx state -> indexToNewState idx bdr nextBoard) // let result = allResults |> Array.tryFind (fun res -> res) // match result with // | Some(true) -> updater nextBoard // | _ -> ignore // updater board let indexToNewState idx (oldBoard: ConsoleColor[]) newBoard = let state = oldBoard.[idx] let livingNeighbours = oldBoard |> getLivingNeighboursCount idx let newState = if livingNeighbours = 3 || livingNeighbours = 2 && state = alive then alive else dead Array.set newBoard idx newState if newState <> state then updateCell idx newState true else false let updateState (board: ConsoleColor[]) = let rec updater bdr = System.Threading.Thread.Sleep(100); let nextBoard = [| for x in 1..size -> alive |] let result = bdr |> Array.mapi (fun idx state -> indexToNewState idx bdr nextBoard) |> Array.exists (fun res -> res) match result with | false -> ignore | true -> updater nextBoard updater board let board = // create the board directly let rnd = new Random(10) [| for x in 1..size -> if rnd.NextDouble() < 0.2 then alive else dead |] // Initialize the array directly instead of iterate over a list then converted to array drawBoard board updateState board |> ignore Console.ReadLine() |> ignore let go() = let size = 30 Console.WindowHeight <- size + 5 Console.WindowWidth <- size + 5 playGOL size size