From 76117814055fc4e1665df1fdda485333601358b5 Mon Sep 17 00:00:00 2001 From: connoraird Date: Tue, 7 Oct 2025 10:34:15 +0100 Subject: [PATCH 01/11] Add challenge for ep 7 and unfinished solution --- .../challenge/CMakeLists.txt | 15 ++ .../7-refactoring-fortran/challenge/README.md | 43 +++++ .../challenge/src/game_of_life.f90 | 176 ++++++++++++++++++ .../solution/CMakeLists.txt | 15 ++ .../7-refactoring-fortran/solution/README.md | 22 +++ .../solution/src/game_of_life.f90 | 176 ++++++++++++++++++ 6 files changed, 447 insertions(+) create mode 100644 episodes/7-refactoring-fortran/challenge/CMakeLists.txt create mode 100644 episodes/7-refactoring-fortran/challenge/README.md create mode 100644 episodes/7-refactoring-fortran/challenge/src/game_of_life.f90 create mode 100644 episodes/7-refactoring-fortran/solution/CMakeLists.txt create mode 100644 episodes/7-refactoring-fortran/solution/README.md create mode 100644 episodes/7-refactoring-fortran/solution/src/game_of_life.f90 diff --git a/episodes/7-refactoring-fortran/challenge/CMakeLists.txt b/episodes/7-refactoring-fortran/challenge/CMakeLists.txt new file mode 100644 index 0000000..59284c2 --- /dev/null +++ b/episodes/7-refactoring-fortran/challenge/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +# Set project name +project( + "game-of-life" + LANGUAGES "Fortran" + VERSION "0.0.1" + DESCRIPTION "Conway's game of life" +) + +# Define src files +set(PROJ_SRC_FILES "${PROJECT_SOURCE_DIR}/src/game_of_life.f90") + +# Build src executables +add_executable("${PROJECT_NAME}" "${PROJ_SRC_FILES}") diff --git a/episodes/7-refactoring-fortran/challenge/README.md b/episodes/7-refactoring-fortran/challenge/README.md new file mode 100644 index 0000000..781fd84 --- /dev/null +++ b/episodes/7-refactoring-fortran/challenge/README.md @@ -0,0 +1,43 @@ +# Introduction to Unit Testing in Fortran - Challenge: Identify bad practice for unit testing Fortran + +This exercise aims to teach principles of writing better Fortran; that is Fortran which is clear, maintainable and testable. + +To do this, there is a [src](./src) code provided which has been written using bad practice. The task is to refactor this src +using best practice principles so that the result is a more testable code but the actual behaviour of the program unchanged. + +## The src code + +Take a look at the [src](./src) code provided. This is an implementation of +[Conway's game of life](http://en.wikipedia.org/wiki/Conway%27s_Game_of_Life). The program reads in a data file which represents +the starting state of the system. The system is then evolved and printed to the terminal screen for each time step. To build and +run the src code use the following commands from within this dir. + +```bash +cmake -B build +cmake --build build +./build/game-of-life ../models/model-1.dat # Or another data file +``` + +## Tasks + +Implement the principles described in [the refactoring lesson](#tasks). + +To ensure you are not changing the actual behaviour of the src code, every time you make a change, +compare the output before and after. To do this store the output before making a change in a file +called `before.dat` + +```sh +./build/game-of-life path/to/model/file > before.dat +``` + +Then, after you make a change regenerate the output and store within a file `after.dat` + +```sh +./build/game-of-life path/to/model/file > after.dat +``` + +If you have successfully not changed the behaviour there should be no output from the command + +```sh +diff before.dat after.dat +``` diff --git a/episodes/7-refactoring-fortran/challenge/src/game_of_life.f90 b/episodes/7-refactoring-fortran/challenge/src/game_of_life.f90 new file mode 100644 index 0000000..200366b --- /dev/null +++ b/episodes/7-refactoring-fortran/challenge/src/game_of_life.f90 @@ -0,0 +1,176 @@ +! ======================================================= +! Conway's game of life +! +! ======================================================= +! Adapted from https://github.com/tuckerrc/game_of_life +! ======================================================= +program game_of_life + + implicit none + + !! Board args + integer :: nrow, ncol + integer :: i, generation_number + integer, dimension(:,:), allocatable :: current_board, new_board + + !! Animation args + integer, dimension(8) :: date_time_values + integer :: mod_ms_step + logical :: steady_state = .false. + + !! CLI args + integer :: argl + character(len=:), allocatable :: cli_arg_temp_store, input_fname + + !! File IO args + character(len=80) :: text_to_discard + integer :: input_file_io + integer :: iostat + + ! Get current_board file path from command line + if (command_argument_count() == 1) then + call get_command_argument(1, length=argl) + allocate(character(argl) :: input_fname) + call get_command_argument(1, input_fname) + else + write(*,'(A)') "Error: Invalid input" + call get_command_argument(0, length=argl) + allocate(character(argl) :: cli_arg_temp_store) + call get_command_argument(0, cli_arg_temp_store) + write(*,'(A,A,A)') "Usage: ", cli_arg_temp_store, " " + deallocate(cli_arg_temp_store) + stop + end if + + ! Open input file + open(unit=input_file_io, & + file=input_fname, & + status='old', & + IOSTAT=iostat) + + if( iostat /= 0) then + write(*,'(a)') ' *** Error when opening '//input_fname + stop 1 + end if + + ! Read in current_board from file + read(input_file_io,'(a)') text_to_discard ! Skip first line + read(input_file_io,*) nrow, ncol + + ! Verify the number of rows read from the file + if (nrow < 1 .or. nrow > 100) then + write (*,'(a,i6)') "nrow must be a positive integer less than 100 found ", nrow + stop 1 + end if + + ! Verify the number of columns read from the file + if (ncol < 1 .or. ncol > 100) then + write (*,'(a,i6)') "ncol must be a positive integer less than 100 found ", ncol + stop 1 + end if + + allocate(current_board(nrow, ncol)) + allocate(new_board(nrow, ncol)) + + read(input_file_io,'(a)') text_to_discard ! Skip next line + ! Populate the boards starting state + do i = 1, nrow + read(input_file_io,*) current_board(i, :) + end do + + close(input_file_io) + + new_board = 0 + generation_number = 0 + + ! Clear the terminal screen + call system ("clear") + + ! Iterate until we reach a steady state + do while(.not. steady_state .and. generation_number < 100) + ! Advance the simulation in the steps of the requested number of milliseconds + call date_and_time(VALUES=date_time_values) + mod_ms_step = mod(date_time_values(8), 250) + + if (mod_ms_step == 0) then + call run_next_iteration() + + generation_number = generation_number + 1 + end if + + end do + + if (steady_state) then + write(*,'(a,i6,a)') "Reached steady after ", generation_number, " generations" + else + write(*,'(a,i6,a)') "Did NOT Reach steady after ", generation_number, " generations" + end if + + deallocate(current_board) + deallocate(new_board) + +contains + + !> Evolve the board into the state of the next iteration + subroutine run_next_iteration() + integer :: i, j, sum + character(nrow) :: output + + ! Clear the terminal screen + call system("clear") + + ! Draw the current board + do i=1, nrow + output = "" + do j=1, ncol + if (current_board(i,j) == 1) then + output = trim(output)//"#" + else + output = trim(output)//"." + endif + enddo + print *, output + enddo + + ! Calculate the new board + do i=2, nrow-1 + do j=2, ncol-1 + sum = 0 + sum = current_board(i, j-1) & + + current_board(i+1, j-1) & + + current_board(i+1, j) & + + current_board(i+1, j+1) & + + current_board(i, j+1) & + + current_board(i-1, j+1) & + + current_board(i-1, j) & + + current_board(i-1, j-1) + if(current_board(i,j)==1 .and. sum<=1) then + new_board(i,j) = 0 + elseif(current_board(i,j)==1 .and. sum<=3) then + new_board(i,j) = 1 + elseif(current_board(i,j)==1 .and. sum>=4)then + new_board(i,j) = 0 + elseif(current_board(i,j)==0 .and. sum==3)then + new_board(i,j) = 1 + endif + enddo + enddo + + ! Check for steady state + steady_state = .true. + do i=1, nrow + do j=1, ncol + if (.not. current_board(i, j) == new_board(i, j)) then + steady_state = .false. + exit + end if + end do + if (.not. steady_state) exit + end do + + current_board = new_board + + return + end subroutine run_next_iteration + +end program game_of_life diff --git a/episodes/7-refactoring-fortran/solution/CMakeLists.txt b/episodes/7-refactoring-fortran/solution/CMakeLists.txt new file mode 100644 index 0000000..eada6f8 --- /dev/null +++ b/episodes/7-refactoring-fortran/solution/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +# Set project name +project( + "game-of-life" + LANGUAGES "Fortran" + VERSION "0.0.1" + DESCRIPTION "Conway's game of life" +) + +# Define src files +file(GLOB PROJ_SRC_FILES "${PROJECT_SOURCE_DIR}/src/*.f90") + +# Build src executables +add_executable("${PROJECT_NAME}" "${PROJ_SRC_FILES}") diff --git a/episodes/7-refactoring-fortran/solution/README.md b/episodes/7-refactoring-fortran/solution/README.md new file mode 100644 index 0000000..68a8e11 --- /dev/null +++ b/episodes/7-refactoring-fortran/solution/README.md @@ -0,0 +1,22 @@ +# Introduction to Unit Testing in Fortran - Solution: Identify bad practice for unit testing Fortran + +The solution provided here is an entirely self-contained project which can be run, as before, using +the following commands from within this dir. + +```bash +cmake -B build +cmake --build build +./build/game-of-life ../models/model-1.dat # Or another data file +``` + +## Tasks + +> Implement the principles described in [the refactoring lesson](#tasks) + +1. Replace magic numbers with constants +2. Change of variable name +3. Break large procedures into smaller units +4. Wrap program functionality in procedures +5. Replace repeated code with a procedure +6. Replace global variables with procedure arguments +7. Separate code concepts into files or modules diff --git a/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 b/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 new file mode 100644 index 0000000..200366b --- /dev/null +++ b/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 @@ -0,0 +1,176 @@ +! ======================================================= +! Conway's game of life +! +! ======================================================= +! Adapted from https://github.com/tuckerrc/game_of_life +! ======================================================= +program game_of_life + + implicit none + + !! Board args + integer :: nrow, ncol + integer :: i, generation_number + integer, dimension(:,:), allocatable :: current_board, new_board + + !! Animation args + integer, dimension(8) :: date_time_values + integer :: mod_ms_step + logical :: steady_state = .false. + + !! CLI args + integer :: argl + character(len=:), allocatable :: cli_arg_temp_store, input_fname + + !! File IO args + character(len=80) :: text_to_discard + integer :: input_file_io + integer :: iostat + + ! Get current_board file path from command line + if (command_argument_count() == 1) then + call get_command_argument(1, length=argl) + allocate(character(argl) :: input_fname) + call get_command_argument(1, input_fname) + else + write(*,'(A)') "Error: Invalid input" + call get_command_argument(0, length=argl) + allocate(character(argl) :: cli_arg_temp_store) + call get_command_argument(0, cli_arg_temp_store) + write(*,'(A,A,A)') "Usage: ", cli_arg_temp_store, " " + deallocate(cli_arg_temp_store) + stop + end if + + ! Open input file + open(unit=input_file_io, & + file=input_fname, & + status='old', & + IOSTAT=iostat) + + if( iostat /= 0) then + write(*,'(a)') ' *** Error when opening '//input_fname + stop 1 + end if + + ! Read in current_board from file + read(input_file_io,'(a)') text_to_discard ! Skip first line + read(input_file_io,*) nrow, ncol + + ! Verify the number of rows read from the file + if (nrow < 1 .or. nrow > 100) then + write (*,'(a,i6)') "nrow must be a positive integer less than 100 found ", nrow + stop 1 + end if + + ! Verify the number of columns read from the file + if (ncol < 1 .or. ncol > 100) then + write (*,'(a,i6)') "ncol must be a positive integer less than 100 found ", ncol + stop 1 + end if + + allocate(current_board(nrow, ncol)) + allocate(new_board(nrow, ncol)) + + read(input_file_io,'(a)') text_to_discard ! Skip next line + ! Populate the boards starting state + do i = 1, nrow + read(input_file_io,*) current_board(i, :) + end do + + close(input_file_io) + + new_board = 0 + generation_number = 0 + + ! Clear the terminal screen + call system ("clear") + + ! Iterate until we reach a steady state + do while(.not. steady_state .and. generation_number < 100) + ! Advance the simulation in the steps of the requested number of milliseconds + call date_and_time(VALUES=date_time_values) + mod_ms_step = mod(date_time_values(8), 250) + + if (mod_ms_step == 0) then + call run_next_iteration() + + generation_number = generation_number + 1 + end if + + end do + + if (steady_state) then + write(*,'(a,i6,a)') "Reached steady after ", generation_number, " generations" + else + write(*,'(a,i6,a)') "Did NOT Reach steady after ", generation_number, " generations" + end if + + deallocate(current_board) + deallocate(new_board) + +contains + + !> Evolve the board into the state of the next iteration + subroutine run_next_iteration() + integer :: i, j, sum + character(nrow) :: output + + ! Clear the terminal screen + call system("clear") + + ! Draw the current board + do i=1, nrow + output = "" + do j=1, ncol + if (current_board(i,j) == 1) then + output = trim(output)//"#" + else + output = trim(output)//"." + endif + enddo + print *, output + enddo + + ! Calculate the new board + do i=2, nrow-1 + do j=2, ncol-1 + sum = 0 + sum = current_board(i, j-1) & + + current_board(i+1, j-1) & + + current_board(i+1, j) & + + current_board(i+1, j+1) & + + current_board(i, j+1) & + + current_board(i-1, j+1) & + + current_board(i-1, j) & + + current_board(i-1, j-1) + if(current_board(i,j)==1 .and. sum<=1) then + new_board(i,j) = 0 + elseif(current_board(i,j)==1 .and. sum<=3) then + new_board(i,j) = 1 + elseif(current_board(i,j)==1 .and. sum>=4)then + new_board(i,j) = 0 + elseif(current_board(i,j)==0 .and. sum==3)then + new_board(i,j) = 1 + endif + enddo + enddo + + ! Check for steady state + steady_state = .true. + do i=1, nrow + do j=1, ncol + if (.not. current_board(i, j) == new_board(i, j)) then + steady_state = .false. + exit + end if + end do + if (.not. steady_state) exit + end do + + current_board = new_board + + return + end subroutine run_next_iteration + +end program game_of_life From 9cf26a421ca5a5eac79199a9cfc0c6b6d2d32bc2 Mon Sep 17 00:00:00 2001 From: connoraird Date: Tue, 7 Oct 2025 11:10:47 +0100 Subject: [PATCH 02/11] Refactoring-1: Replace magic numbers with constants --- .../solution/src/game_of_life.f90 | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 b/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 index 200366b..70aa454 100644 --- a/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 +++ b/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 @@ -9,13 +9,14 @@ program game_of_life implicit none !! Board args + integer, parameter :: max_generations = 100, max_nrows = 100, max_ncols = 100 integer :: nrow, ncol integer :: i, generation_number integer, dimension(:,:), allocatable :: current_board, new_board !! Animation args integer, dimension(8) :: date_time_values - integer :: mod_ms_step + integer :: mod_ms_step, ms_per_step = 250 logical :: steady_state = .false. !! CLI args @@ -58,14 +59,14 @@ program game_of_life read(input_file_io,*) nrow, ncol ! Verify the number of rows read from the file - if (nrow < 1 .or. nrow > 100) then - write (*,'(a,i6)') "nrow must be a positive integer less than 100 found ", nrow + if (nrow < 1 .or. nrow > max_nrows) then + write (*,'(a,i6,a,i6)') "nrow must be a positive integer less than ", max_nrows," found ", nrow stop 1 end if ! Verify the number of columns read from the file - if (ncol < 1 .or. ncol > 100) then - write (*,'(a,i6)') "ncol must be a positive integer less than 100 found ", ncol + if (ncol < 1 .or. ncol > max_ncols) then + write (*,'(a,i6,a,i6)') "ncol must be a positive integer less than ", max_ncols," found ", ncol stop 1 end if @@ -87,10 +88,10 @@ program game_of_life call system ("clear") ! Iterate until we reach a steady state - do while(.not. steady_state .and. generation_number < 100) + do while(.not. steady_state .and. generation_number < max_generations) ! Advance the simulation in the steps of the requested number of milliseconds call date_and_time(VALUES=date_time_values) - mod_ms_step = mod(date_time_values(8), 250) + mod_ms_step = mod(date_time_values(8), ms_per_step) if (mod_ms_step == 0) then call run_next_iteration() From f24abd67aa91da6d718281b1704fe7271295cbae Mon Sep 17 00:00:00 2001 From: connoraird Date: Tue, 7 Oct 2025 11:12:15 +0100 Subject: [PATCH 03/11] Refactoring-2: Change of variable name --- .../solution/src/game_of_life.f90 | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 b/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 index 70aa454..2bfea66 100644 --- a/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 +++ b/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 @@ -11,7 +11,7 @@ program game_of_life !! Board args integer, parameter :: max_generations = 100, max_nrows = 100, max_ncols = 100 integer :: nrow, ncol - integer :: i, generation_number + integer :: row, generation_number integer, dimension(:,:), allocatable :: current_board, new_board !! Animation args @@ -21,7 +21,7 @@ program game_of_life !! CLI args integer :: argl - character(len=:), allocatable :: cli_arg_temp_store, input_fname + character(len=:), allocatable :: cli_arg_temp_store, input_filename !! File IO args character(len=80) :: text_to_discard @@ -31,8 +31,8 @@ program game_of_life ! Get current_board file path from command line if (command_argument_count() == 1) then call get_command_argument(1, length=argl) - allocate(character(argl) :: input_fname) - call get_command_argument(1, input_fname) + allocate(character(argl) :: input_filename) + call get_command_argument(1, input_filename) else write(*,'(A)') "Error: Invalid input" call get_command_argument(0, length=argl) @@ -45,12 +45,12 @@ program game_of_life ! Open input file open(unit=input_file_io, & - file=input_fname, & + file=input_filename, & status='old', & IOSTAT=iostat) if( iostat /= 0) then - write(*,'(a)') ' *** Error when opening '//input_fname + write(*,'(a)') ' *** Error when opening '//input_filename stop 1 end if @@ -75,8 +75,8 @@ program game_of_life read(input_file_io,'(a)') text_to_discard ! Skip next line ! Populate the boards starting state - do i = 1, nrow - read(input_file_io,*) current_board(i, :) + do row = 1, nrow + read(input_file_io,*) current_board(row, :) end do close(input_file_io) @@ -114,17 +114,17 @@ program game_of_life !> Evolve the board into the state of the next iteration subroutine run_next_iteration() - integer :: i, j, sum + integer :: row, col, sum character(nrow) :: output ! Clear the terminal screen call system("clear") ! Draw the current board - do i=1, nrow + do row=1, nrow output = "" - do j=1, ncol - if (current_board(i,j) == 1) then + do col=1, ncol + if (current_board(row,col) == 1) then output = trim(output)//"#" else output = trim(output)//"." @@ -134,34 +134,34 @@ subroutine run_next_iteration() enddo ! Calculate the new board - do i=2, nrow-1 - do j=2, ncol-1 + do row=2, nrow-1 + do col=2, ncol-1 sum = 0 - sum = current_board(i, j-1) & - + current_board(i+1, j-1) & - + current_board(i+1, j) & - + current_board(i+1, j+1) & - + current_board(i, j+1) & - + current_board(i-1, j+1) & - + current_board(i-1, j) & - + current_board(i-1, j-1) - if(current_board(i,j)==1 .and. sum<=1) then - new_board(i,j) = 0 - elseif(current_board(i,j)==1 .and. sum<=3) then - new_board(i,j) = 1 - elseif(current_board(i,j)==1 .and. sum>=4)then - new_board(i,j) = 0 - elseif(current_board(i,j)==0 .and. sum==3)then - new_board(i,j) = 1 + sum = current_board(row, col-1) & + + current_board(row+1, col-1) & + + current_board(row+1, col) & + + current_board(row+1, col+1) & + + current_board(row, col+1) & + + current_board(row-1, col+1) & + + current_board(row-1, col) & + + current_board(row-1, col-1) + if(current_board(row,col)==1 .and. sum<=1) then + new_board(row,col) = 0 + elseif(current_board(row,col)==1 .and. sum<=3) then + new_board(row,col) = 1 + elseif(current_board(row,col)==1 .and. sum>=4)then + new_board(row,col) = 0 + elseif(current_board(row,col)==0 .and. sum==3)then + new_board(row,col) = 1 endif enddo enddo ! Check for steady state steady_state = .true. - do i=1, nrow - do j=1, ncol - if (.not. current_board(i, j) == new_board(i, j)) then + do row=1, nrow + do col=1, ncol + if (.not. current_board(row, col) == new_board(row, col)) then steady_state = .false. exit end if From ba57095680a01da79ce4210f3dd8ca62d447f17c Mon Sep 17 00:00:00 2001 From: connoraird Date: Tue, 7 Oct 2025 11:24:11 +0100 Subject: [PATCH 04/11] Refactoring-3: Break large procedures into smaller units --- .../solution/src/game_of_life.f90 | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 b/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 index 2bfea66..be36927 100644 --- a/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 +++ b/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 @@ -94,7 +94,10 @@ program game_of_life mod_ms_step = mod(date_time_values(8), ms_per_step) if (mod_ms_step == 0) then - call run_next_iteration() + call evolve_board() + call check_for_steady_state() + current_board = new_board + call draw_board() generation_number = generation_number + 1 end if @@ -113,27 +116,9 @@ program game_of_life contains !> Evolve the board into the state of the next iteration - subroutine run_next_iteration() + subroutine evolve_board() integer :: row, col, sum - character(nrow) :: output - ! Clear the terminal screen - call system("clear") - - ! Draw the current board - do row=1, nrow - output = "" - do col=1, ncol - if (current_board(row,col) == 1) then - output = trim(output)//"#" - else - output = trim(output)//"." - endif - enddo - print *, output - enddo - - ! Calculate the new board do row=2, nrow-1 do col=2, ncol-1 sum = 0 @@ -157,21 +142,43 @@ subroutine run_next_iteration() enddo enddo - ! Check for steady state - steady_state = .true. + return + end subroutine evolve_board + + !> Check if we have reached steady state, i.e. current and new board match + subroutine check_for_steady_state() + integer :: row, col + do row=1, nrow do col=1, ncol if (.not. current_board(row, col) == new_board(row, col)) then steady_state = .false. - exit + return end if end do - if (.not. steady_state) exit end do + steady_state = .true. + end subroutine check_for_steady_state - current_board = new_board + !> Output the current board to the terminal + subroutine draw_board() + integer :: row, col + character(nrow) :: output - return - end subroutine run_next_iteration + ! Clear the terminal screen + call system("clear") + + do row=1, nrow + output = "" + do col=1, ncol + if (current_board(row,col) == 1) then + output = trim(output)//"#" + else + output = trim(output)//"." + endif + enddo + print *, output + enddo + end subroutine draw_board end program game_of_life From 2f8f3d0a7d59abcf1ad567e9607b1f989fe0d49d Mon Sep 17 00:00:00 2001 From: connoraird Date: Tue, 7 Oct 2025 11:52:48 +0100 Subject: [PATCH 05/11] Refactoring-4: Wrap program functionality in procedures --- .../solution/src/game_of_life.f90 | 168 +++++++++++------- 1 file changed, 100 insertions(+), 68 deletions(-) diff --git a/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 b/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 index be36927..5091e8c 100644 --- a/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 +++ b/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 @@ -10,24 +10,17 @@ program game_of_life !! Board args integer, parameter :: max_generations = 100, max_nrows = 100, max_ncols = 100 - integer :: nrow, ncol - integer :: row, generation_number + integer :: nrow, ncol, generation_number integer, dimension(:,:), allocatable :: current_board, new_board - - !! Animation args - integer, dimension(8) :: date_time_values - integer :: mod_ms_step, ms_per_step = 250 logical :: steady_state = .false. + !> Whether to animate the board + logical, parameter :: animate = .true. + !! CLI args integer :: argl character(len=:), allocatable :: cli_arg_temp_store, input_filename - !! File IO args - character(len=80) :: text_to_discard - integer :: input_file_io - integer :: iostat - ! Get current_board file path from command line if (command_argument_count() == 1) then call get_command_argument(1, length=argl) @@ -43,77 +36,116 @@ program game_of_life stop end if - ! Open input file - open(unit=input_file_io, & - file=input_filename, & - status='old', & - IOSTAT=iostat) - - if( iostat /= 0) then - write(*,'(a)') ' *** Error when opening '//input_filename - stop 1 - end if - - ! Read in current_board from file - read(input_file_io,'(a)') text_to_discard ! Skip first line - read(input_file_io,*) nrow, ncol + call read_model_from_file() - ! Verify the number of rows read from the file - if (nrow < 1 .or. nrow > max_nrows) then - write (*,'(a,i6,a,i6)') "nrow must be a positive integer less than ", max_nrows," found ", nrow - stop 1 - end if + call find_steady_state() - ! Verify the number of columns read from the file - if (ncol < 1 .or. ncol > max_ncols) then - write (*,'(a,i6,a,i6)') "ncol must be a positive integer less than ", max_ncols," found ", ncol - stop 1 + if (steady_state) then + write(*,'(a,i6,a)') "Reached steady after ", generation_number, " generations" + else + write(*,'(a,i6,a)') "Did NOT Reach steady after ", generation_number, " generations" end if - allocate(current_board(nrow, ncol)) - allocate(new_board(nrow, ncol)) - - read(input_file_io,'(a)') text_to_discard ! Skip next line - ! Populate the boards starting state - do row = 1, nrow - read(input_file_io,*) current_board(row, :) - end do - - close(input_file_io) + deallocate(current_board) + deallocate(new_board) - new_board = 0 - generation_number = 0 +contains - ! Clear the terminal screen - call system ("clear") + !> Populate the board from a provided file + subroutine read_model_from_file() + !> A flag to indicate if reading the file was successful + character(len=:), allocatable :: io_error_message + + ! Board definition args + integer :: row + + ! File IO args + integer :: input_file_io, iostat + character(len=80) :: text_to_discard + + input_file_io = 1111 + + ! Open input file + open(unit=input_file_io, & + file=input_filename, & + status='old', & + IOSTAT=iostat) + + if( iostat == 0) then + ! Read in board from file + read(input_file_io,'(a)') text_to_discard ! Skip first line + read(input_file_io,*) nrow, ncol + + ! Verify the number of rows and columns read from the file + if (nrow < 1 .or. nrow > max_nrows) then + allocate(character(100) :: io_error_message) + write (io_error_message,'(a,i6,a,i6)') "nrow must be a positive integer less than ", max_nrows, " found ", nrow + elseif (ncol < 1 .or. ncol > max_ncols) then + allocate(character(100) :: io_error_message) + write (io_error_message,'(a,i6,a,i6)') "ncol must be a positive integer less than ", max_ncols, " found ", ncol + end if + else + allocate(character(100) :: io_error_message) + write(io_error_message,'(a)') ' *** Error when opening '//input_filename + endif + + if (.not. allocated(io_error_message)) then + + allocate(current_board(nrow, ncol)) + + read(input_file_io,'(a)') text_to_discard ! Skip next line + ! Populate the boards starting state + do row = 1, nrow + read(input_file_io,*) current_board(row, :) + end do - ! Iterate until we reach a steady state - do while(.not. steady_state .and. generation_number < max_generations) - ! Advance the simulation in the steps of the requested number of milliseconds - call date_and_time(VALUES=date_time_values) - mod_ms_step = mod(date_time_values(8), ms_per_step) + end if - if (mod_ms_step == 0) then - call evolve_board() - call check_for_steady_state() - current_board = new_board - call draw_board() + close(input_file_io) - generation_number = generation_number + 1 + if (allocated(io_error_message)) then + write (*,*) io_error_message + deallocate(io_error_message) + stop end if + end subroutine read_model_from_file - end do + !> Find the steady state of the Game of Life board + subroutine find_steady_state() - if (steady_state) then - write(*,'(a,i6,a)') "Reached steady after ", generation_number, " generations" - else - write(*,'(a,i6,a)') "Did NOT Reach steady after ", generation_number, " generations" - end if + !! Animation args + integer, dimension(8) :: date_time_values + integer :: mod_ms_step + integer, parameter :: ms_per_step = 250 - deallocate(current_board) - deallocate(new_board) + allocate(new_board(size(current_board,1), size(current_board, 2))) + new_board = 0 -contains + ! Clear the terminal screen + if (animate) call system ("clear") + + ! Iterate until we reach a steady state + steady_state = .false. + generation_number = 0 + mod_ms_step = 0 + do while(.not. steady_state .and. generation_number < max_generations) + if (animate) then + ! Advance the simulation in the steps of the requested number of milliseconds + call date_and_time(VALUES=date_time_values) + mod_ms_step = mod(date_time_values(8), ms_per_step) + end if + + if (mod_ms_step == 0) then + call evolve_board() + call check_for_steady_state() + current_board = new_board + if (animate) call draw_board() + + generation_number = generation_number + 1 + end if + + end do + end subroutine find_steady_state !> Evolve the board into the state of the next iteration subroutine evolve_board() From a5f5f2a0a114ea78a45e220b1c1ada8146967d5f Mon Sep 17 00:00:00 2001 From: connoraird Date: Tue, 7 Oct 2025 12:06:16 +0100 Subject: [PATCH 06/11] Refactoring-5: Replace repeated code with a procedure --- .../solution/src/game_of_life.f90 | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 b/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 index 5091e8c..c23cb4d 100644 --- a/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 +++ b/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 @@ -18,21 +18,15 @@ program game_of_life logical, parameter :: animate = .true. !! CLI args - integer :: argl - character(len=:), allocatable :: cli_arg_temp_store, input_filename + character(len=:), allocatable :: executable_name, input_filename ! Get current_board file path from command line if (command_argument_count() == 1) then - call get_command_argument(1, length=argl) - allocate(character(argl) :: input_filename) - call get_command_argument(1, input_filename) + call read_cli_arg(1, input_filename) else write(*,'(A)') "Error: Invalid input" - call get_command_argument(0, length=argl) - allocate(character(argl) :: cli_arg_temp_store) - call get_command_argument(0, cli_arg_temp_store) - write(*,'(A,A,A)') "Usage: ", cli_arg_temp_store, " " - deallocate(cli_arg_temp_store) + call read_cli_arg(0, executable_name) + write(*,'(A,A,A)') "Usage: ", executable_name, " " stop end if @@ -46,11 +40,24 @@ program game_of_life write(*,'(a,i6,a)') "Did NOT Reach steady after ", generation_number, " generations" end if - deallocate(current_board) - deallocate(new_board) - contains + !> Read a cli arg at a given index and return it as a string (character array) + recursive subroutine read_cli_arg(arg_index, arg) + !> The index of the cli arg to try and read + integer, intent(in) :: arg_index + !> The string into which to store the cli arg + character(len=:), allocatable, intent(out) :: arg + + integer :: argl + character(len=:), allocatable :: cli_arg_temp_store + + call get_command_argument(arg_index, length=argl) + allocate(character(argl) :: cli_arg_temp_store) + call get_command_argument(arg_index, cli_arg_temp_store) + arg = trim(cli_arg_temp_store) + end subroutine read_cli_arg + !> Populate the board from a provided file subroutine read_model_from_file() !> A flag to indicate if reading the file was successful From cb41530ed2820c66bb30b17785b274f13c7bc2ce Mon Sep 17 00:00:00 2001 From: connoraird Date: Tue, 7 Oct 2025 14:26:29 +0100 Subject: [PATCH 07/11] Refactoring-6: Replace global variables with procedure arguments --- .../solution/src/game_of_life.f90 | 93 +++++++++++++------ 1 file changed, 66 insertions(+), 27 deletions(-) diff --git a/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 b/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 index c23cb4d..bf62604 100644 --- a/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 +++ b/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 @@ -8,14 +8,10 @@ program game_of_life implicit none - !! Board args - integer, parameter :: max_generations = 100, max_nrows = 100, max_ncols = 100 - integer :: nrow, ncol, generation_number - integer, dimension(:,:), allocatable :: current_board, new_board - logical :: steady_state = .false. - - !> Whether to animate the board logical, parameter :: animate = .true. + integer, dimension(:,:), allocatable :: starting_board + integer :: generation_number + logical :: steady_state = .false. !! CLI args character(len=:), allocatable :: executable_name, input_filename @@ -30,9 +26,9 @@ program game_of_life stop end if - call read_model_from_file() + call read_model_from_file(input_filename, starting_board) - call find_steady_state() + call find_steady_state(steady_state, generation_number, starting_board, animate) if (steady_state) then write(*,'(a,i6,a)') "Reached steady after ", generation_number, " generations" @@ -43,7 +39,7 @@ program game_of_life contains !> Read a cli arg at a given index and return it as a string (character array) - recursive subroutine read_cli_arg(arg_index, arg) + subroutine read_cli_arg(arg_index, arg) !> The index of the cli arg to try and read integer, intent(in) :: arg_index !> The string into which to store the cli arg @@ -59,12 +55,16 @@ recursive subroutine read_cli_arg(arg_index, arg) end subroutine read_cli_arg !> Populate the board from a provided file - subroutine read_model_from_file() + subroutine read_model_from_file(input_filename, board) + character(len=:), allocatable, intent(in) :: input_filename + integer, dimension(:,:), allocatable, intent(out) :: board + !> A flag to indicate if reading the file was successful character(len=:), allocatable :: io_error_message ! Board definition args - integer :: row + integer :: row, nrow, ncol + integer, parameter :: max_nrows = 100, max_ncols = 100 ! File IO args integer :: input_file_io, iostat @@ -98,12 +98,12 @@ subroutine read_model_from_file() if (.not. allocated(io_error_message)) then - allocate(current_board(nrow, ncol)) + allocate(board(nrow, ncol)) read(input_file_io,'(a)') text_to_discard ! Skip next line ! Populate the boards starting state do row = 1, nrow - read(input_file_io,*) current_board(row, :) + read(input_file_io,*) board(row, :) end do end if @@ -118,14 +118,27 @@ subroutine read_model_from_file() end subroutine read_model_from_file !> Find the steady state of the Game of Life board - subroutine find_steady_state() + subroutine find_steady_state(steady_state, generation_number, input_board, animate) + !> Whether the board has reached a steady state + logical, intent(out) :: steady_state + !> The number of generations that have been processed + integer, intent(out) :: generation_number + !> The starting state of the board + integer, dimension(:,:), allocatable, intent(in) :: input_board + !> Whether to animate the board + logical, intent(in) :: animate + + integer, dimension(:,:), allocatable :: current_board, new_board + integer, parameter :: max_generations = 100 !! Animation args integer, dimension(8) :: date_time_values integer :: mod_ms_step integer, parameter :: ms_per_step = 250 - allocate(new_board(size(current_board,1), size(current_board, 2))) + allocate(current_board(size(input_board,1), size(input_board, 2))) + allocate(new_board(size(input_board,1), size(input_board, 2))) + current_board = input_board new_board = 0 ! Clear the terminal screen @@ -143,10 +156,10 @@ subroutine find_steady_state() end if if (mod_ms_step == 0) then - call evolve_board() - call check_for_steady_state() + call evolve_board(current_board, new_board) + call check_for_steady_state(steady_state, current_board, new_board) current_board = new_board - if (animate) call draw_board() + if (animate) call draw_board(current_board) generation_number = generation_number + 1 end if @@ -155,8 +168,16 @@ subroutine find_steady_state() end subroutine find_steady_state !> Evolve the board into the state of the next iteration - subroutine evolve_board() - integer :: row, col, sum + subroutine evolve_board(current_board, new_board) + !> The current state of the board + integer, dimension(:,:), allocatable, intent(in) :: current_board + !> The new state of the board + integer, dimension(:,:), allocatable, intent(inout) :: new_board + + integer :: row, col, sum, nrow, ncol + + nrow = size(current_board, 1) + ncol = size(current_board, 2) do row=2, nrow-1 do col=2, ncol-1 @@ -185,8 +206,18 @@ subroutine evolve_board() end subroutine evolve_board !> Check if we have reached steady state, i.e. current and new board match - subroutine check_for_steady_state() - integer :: row, col + subroutine check_for_steady_state(steady_state, current_board, new_board) + !> Whether the board has reached a steady state + logical, intent(out) :: steady_state + !> The current state of the board + integer, dimension(:,:), allocatable, intent(in) :: current_board + !> The new state of the board + integer, dimension(:,:), allocatable, intent(inout) :: new_board + + integer :: row, col, nrow, ncol + + nrow = size(current_board, 1) + ncol = size(current_board, 2) do row=1, nrow do col=1, ncol @@ -200,9 +231,17 @@ subroutine check_for_steady_state() end subroutine check_for_steady_state !> Output the current board to the terminal - subroutine draw_board() - integer :: row, col - character(nrow) :: output + subroutine draw_board(board) + !> The current state of the board + integer, dimension(:,:), allocatable, intent(in) :: board + + integer :: row, col, nrow, ncol + character(:), allocatable :: output + + nrow = size(board, 1) + ncol = size(board, 2) + + allocate(character(nrow) :: output) ! Clear the terminal screen call system("clear") @@ -210,7 +249,7 @@ subroutine draw_board() do row=1, nrow output = "" do col=1, ncol - if (current_board(row,col) == 1) then + if (board(row,col) == 1) then output = trim(output)//"#" else output = trim(output)//"." From ba51772fba00071df133f83624a9d175d79cef8f Mon Sep 17 00:00:00 2001 From: connoraird Date: Tue, 7 Oct 2025 15:03:06 +0100 Subject: [PATCH 08/11] Refactoring-7: Separate code concepts into files or modules --- .../solution/CMakeLists.txt | 9 +- .../solution/src/animation.f90 | 38 +++++ .../solution/src/cli.f90 | 23 +++ .../solution/src/game_of_life.f90 | 149 +----------------- .../7-refactoring-fortran/solution/src/io.f90 | 70 ++++++++ .../solution/src/main.f90 | 41 +++++ 6 files changed, 184 insertions(+), 146 deletions(-) create mode 100644 episodes/7-refactoring-fortran/solution/src/animation.f90 create mode 100644 episodes/7-refactoring-fortran/solution/src/cli.f90 create mode 100644 episodes/7-refactoring-fortran/solution/src/io.f90 create mode 100644 episodes/7-refactoring-fortran/solution/src/main.f90 diff --git a/episodes/7-refactoring-fortran/solution/CMakeLists.txt b/episodes/7-refactoring-fortran/solution/CMakeLists.txt index eada6f8..1b9b0d8 100644 --- a/episodes/7-refactoring-fortran/solution/CMakeLists.txt +++ b/episodes/7-refactoring-fortran/solution/CMakeLists.txt @@ -9,7 +9,14 @@ project( ) # Define src files -file(GLOB PROJ_SRC_FILES "${PROJECT_SOURCE_DIR}/src/*.f90") +set(SRC_DIR "${PROJECT_SOURCE_DIR}/src") +set(PROJ_SRC_FILES + "${SRC_DIR}/main.f90" + "${SRC_DIR}/animation.f90" + "${SRC_DIR}/cli.f90" + "${SRC_DIR}/game_of_life.f90" + "${SRC_DIR}/io.f90" +) # Build src executables add_executable("${PROJECT_NAME}" "${PROJ_SRC_FILES}") diff --git a/episodes/7-refactoring-fortran/solution/src/animation.f90 b/episodes/7-refactoring-fortran/solution/src/animation.f90 new file mode 100644 index 0000000..427eef4 --- /dev/null +++ b/episodes/7-refactoring-fortran/solution/src/animation.f90 @@ -0,0 +1,38 @@ +module animation + implicit none + public + + logical, parameter :: animate = .true. + +contains + + !> Output the current board to the terminal + subroutine draw_board(board) + !> The current state of the board + integer, dimension(:,:), allocatable, intent(in) :: board + + integer :: row, col, nrow, ncol + character(:), allocatable :: output + + nrow = size(board, 1) + ncol = size(board, 2) + + allocate(character(nrow) :: output) + + ! Clear the terminal screen + call system("clear") + + do row=1, nrow + output = "" + do col=1, ncol + if (board(row,col) == 1) then + output = trim(output)//"#" + else + output = trim(output)//"." + endif + enddo + print *, output + enddo + end subroutine draw_board + +end module animation diff --git a/episodes/7-refactoring-fortran/solution/src/cli.f90 b/episodes/7-refactoring-fortran/solution/src/cli.f90 new file mode 100644 index 0000000..ff45d96 --- /dev/null +++ b/episodes/7-refactoring-fortran/solution/src/cli.f90 @@ -0,0 +1,23 @@ +module cli + implicit none + public + +contains + + !> Read a cli arg at a given index and return it as a string (character array) + subroutine read_cli_arg(arg_index, arg) + !> The index of the cli arg to try and read + integer, intent(in) :: arg_index + !> The string into which to store the cli arg + character(len=:), allocatable, intent(out) :: arg + + integer :: argl + character(len=:), allocatable :: cli_arg_temp_store + + call get_command_argument(arg_index, length=argl) + allocate(character(argl) :: cli_arg_temp_store) + call get_command_argument(arg_index, cli_arg_temp_store) + arg = trim(cli_arg_temp_store) + end subroutine read_cli_arg + +end module cli diff --git a/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 b/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 index bf62604..587ab59 100644 --- a/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 +++ b/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 @@ -1,122 +1,10 @@ -! ======================================================= -! Conway's game of life -! -! ======================================================= -! Adapted from https://github.com/tuckerrc/game_of_life -! ======================================================= -program game_of_life - +module game_of_life + use animation, only : draw_board implicit none - - logical, parameter :: animate = .true. - integer, dimension(:,:), allocatable :: starting_board - integer :: generation_number - logical :: steady_state = .false. - - !! CLI args - character(len=:), allocatable :: executable_name, input_filename - - ! Get current_board file path from command line - if (command_argument_count() == 1) then - call read_cli_arg(1, input_filename) - else - write(*,'(A)') "Error: Invalid input" - call read_cli_arg(0, executable_name) - write(*,'(A,A,A)') "Usage: ", executable_name, " " - stop - end if - - call read_model_from_file(input_filename, starting_board) - - call find_steady_state(steady_state, generation_number, starting_board, animate) - - if (steady_state) then - write(*,'(a,i6,a)') "Reached steady after ", generation_number, " generations" - else - write(*,'(a,i6,a)') "Did NOT Reach steady after ", generation_number, " generations" - end if + public contains - !> Read a cli arg at a given index and return it as a string (character array) - subroutine read_cli_arg(arg_index, arg) - !> The index of the cli arg to try and read - integer, intent(in) :: arg_index - !> The string into which to store the cli arg - character(len=:), allocatable, intent(out) :: arg - - integer :: argl - character(len=:), allocatable :: cli_arg_temp_store - - call get_command_argument(arg_index, length=argl) - allocate(character(argl) :: cli_arg_temp_store) - call get_command_argument(arg_index, cli_arg_temp_store) - arg = trim(cli_arg_temp_store) - end subroutine read_cli_arg - - !> Populate the board from a provided file - subroutine read_model_from_file(input_filename, board) - character(len=:), allocatable, intent(in) :: input_filename - integer, dimension(:,:), allocatable, intent(out) :: board - - !> A flag to indicate if reading the file was successful - character(len=:), allocatable :: io_error_message - - ! Board definition args - integer :: row, nrow, ncol - integer, parameter :: max_nrows = 100, max_ncols = 100 - - ! File IO args - integer :: input_file_io, iostat - character(len=80) :: text_to_discard - - input_file_io = 1111 - - ! Open input file - open(unit=input_file_io, & - file=input_filename, & - status='old', & - IOSTAT=iostat) - - if( iostat == 0) then - ! Read in board from file - read(input_file_io,'(a)') text_to_discard ! Skip first line - read(input_file_io,*) nrow, ncol - - ! Verify the number of rows and columns read from the file - if (nrow < 1 .or. nrow > max_nrows) then - allocate(character(100) :: io_error_message) - write (io_error_message,'(a,i6,a,i6)') "nrow must be a positive integer less than ", max_nrows, " found ", nrow - elseif (ncol < 1 .or. ncol > max_ncols) then - allocate(character(100) :: io_error_message) - write (io_error_message,'(a,i6,a,i6)') "ncol must be a positive integer less than ", max_ncols, " found ", ncol - end if - else - allocate(character(100) :: io_error_message) - write(io_error_message,'(a)') ' *** Error when opening '//input_filename - endif - - if (.not. allocated(io_error_message)) then - - allocate(board(nrow, ncol)) - - read(input_file_io,'(a)') text_to_discard ! Skip next line - ! Populate the boards starting state - do row = 1, nrow - read(input_file_io,*) board(row, :) - end do - - end if - - close(input_file_io) - - if (allocated(io_error_message)) then - write (*,*) io_error_message - deallocate(io_error_message) - stop - end if - end subroutine read_model_from_file - !> Find the steady state of the Game of Life board subroutine find_steady_state(steady_state, generation_number, input_board, animate) !> Whether the board has reached a steady state @@ -230,33 +118,4 @@ subroutine check_for_steady_state(steady_state, current_board, new_board) steady_state = .true. end subroutine check_for_steady_state - !> Output the current board to the terminal - subroutine draw_board(board) - !> The current state of the board - integer, dimension(:,:), allocatable, intent(in) :: board - - integer :: row, col, nrow, ncol - character(:), allocatable :: output - - nrow = size(board, 1) - ncol = size(board, 2) - - allocate(character(nrow) :: output) - - ! Clear the terminal screen - call system("clear") - - do row=1, nrow - output = "" - do col=1, ncol - if (board(row,col) == 1) then - output = trim(output)//"#" - else - output = trim(output)//"." - endif - enddo - print *, output - enddo - end subroutine draw_board - -end program game_of_life +end module game_of_life \ No newline at end of file diff --git a/episodes/7-refactoring-fortran/solution/src/io.f90 b/episodes/7-refactoring-fortran/solution/src/io.f90 new file mode 100644 index 0000000..7f8709d --- /dev/null +++ b/episodes/7-refactoring-fortran/solution/src/io.f90 @@ -0,0 +1,70 @@ +module io + implicit none + public + +contains + + !> Populate the board from a provided file + subroutine read_model_from_file(input_filename, board) + character(len=:), allocatable, intent(in) :: input_filename + integer, dimension(:,:), allocatable, intent(out) :: board + + !> A flag to indicate if reading the file was successful + character(len=:), allocatable :: io_error_message + + ! Board definition args + integer :: row, nrow, ncol + integer, parameter :: max_nrows = 100, max_ncols = 100 + + ! File IO args + integer :: input_file_io, iostat + character(len=80) :: text_to_discard + + input_file_io = 1111 + + ! Open input file + open(unit=input_file_io, & + file=input_filename, & + status='old', & + IOSTAT=iostat) + + if( iostat == 0) then + ! Read in board from file + read(input_file_io,'(a)') text_to_discard ! Skip first line + read(input_file_io,*) nrow, ncol + + ! Verify the number of rows and columns read from the file + if (nrow < 1 .or. nrow > max_nrows) then + allocate(character(100) :: io_error_message) + write (io_error_message,'(a,i6,a,i6)') "nrow must be a positive integer less than ", max_nrows, " found ", nrow + elseif (ncol < 1 .or. ncol > max_ncols) then + allocate(character(100) :: io_error_message) + write (io_error_message,'(a,i6,a,i6)') "ncol must be a positive integer less than ", max_ncols, " found ", ncol + end if + else + allocate(character(100) :: io_error_message) + write(io_error_message,'(a)') ' *** Error when opening '//input_filename + endif + + if (.not. allocated(io_error_message)) then + + allocate(board(nrow, ncol)) + + read(input_file_io,'(a)') text_to_discard ! Skip next line + ! Populate the boards starting state + do row = 1, nrow + read(input_file_io,*) board(row, :) + end do + + end if + + close(input_file_io) + + if (allocated(io_error_message)) then + write (*,*) io_error_message + deallocate(io_error_message) + stop + end if + end subroutine read_model_from_file + +end module io diff --git a/episodes/7-refactoring-fortran/solution/src/main.f90 b/episodes/7-refactoring-fortran/solution/src/main.f90 new file mode 100644 index 0000000..18267cb --- /dev/null +++ b/episodes/7-refactoring-fortran/solution/src/main.f90 @@ -0,0 +1,41 @@ +! ======================================================= +! Conway's game of life +! +! ======================================================= +! Adapted from https://github.com/tuckerrc/game_of_life +! ======================================================= +program main + use animation, only : animate + use cli, only : read_cli_arg + use game_of_life, only : check_for_steady_state, evolve_board, find_steady_state + use io, only : read_model_from_file + implicit none + + integer, dimension(:,:), allocatable :: starting_board + integer :: generation_number + logical :: steady_state = .false. + + !! CLI args + character(len=:), allocatable :: executable_name, input_filename + + ! Get current_board file path from command line + if (command_argument_count() == 1) then + call read_cli_arg(1, input_filename) + else + write(*,'(A)') "Error: Invalid input" + call read_cli_arg(0, executable_name) + write(*,'(A,A,A)') "Usage: ", executable_name, " " + stop + end if + + call read_model_from_file(input_filename, starting_board) + + call find_steady_state(steady_state, generation_number, starting_board, animate) + + if (steady_state) then + write(*,'(a,i6,a)') "Reached steady after ", generation_number, " generations" + else + write(*,'(a,i6,a)') "Did NOT Reach steady after ", generation_number, " generations" + end if + +end program main From 07997398e6c1a46a1474bceba08a6dab8c096c72 Mon Sep 17 00:00:00 2001 From: connoraird Date: Mon, 17 Nov 2025 10:21:39 +0000 Subject: [PATCH 09/11] Add model data to refectoring exercise dir and add last refactoring point --- .../7-refactoring-fortran/models/model-1.dat | 34 +++++++++++++++++++ .../7-refactoring-fortran/solution/README.md | 1 + 2 files changed, 35 insertions(+) create mode 100644 episodes/7-refactoring-fortran/models/model-1.dat diff --git a/episodes/7-refactoring-fortran/models/model-1.dat b/episodes/7-refactoring-fortran/models/model-1.dat new file mode 100644 index 0000000..ed1129c --- /dev/null +++ b/episodes/7-refactoring-fortran/models/model-1.dat @@ -0,0 +1,34 @@ +nrow ncol + 31 31 +Board + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 diff --git a/episodes/7-refactoring-fortran/solution/README.md b/episodes/7-refactoring-fortran/solution/README.md index 68a8e11..7464c4d 100644 --- a/episodes/7-refactoring-fortran/solution/README.md +++ b/episodes/7-refactoring-fortran/solution/README.md @@ -20,3 +20,4 @@ cmake --build build 5. Replace repeated code with a procedure 6. Replace global variables with procedure arguments 7. Separate code concepts into files or modules +8. Use `use` instead of `include` From be40ecc57aaf514f46ef953a2d796407c4b79b69 Mon Sep 17 00:00:00 2001 From: connoraird Date: Fri, 5 Dec 2025 15:03:43 +0000 Subject: [PATCH 10/11] Correct titles and add links --- episodes/7-refactoring-fortran/challenge/README.md | 6 +++--- episodes/7-refactoring-fortran/solution/README.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/episodes/7-refactoring-fortran/challenge/README.md b/episodes/7-refactoring-fortran/challenge/README.md index 781fd84..7377501 100644 --- a/episodes/7-refactoring-fortran/challenge/README.md +++ b/episodes/7-refactoring-fortran/challenge/README.md @@ -1,4 +1,4 @@ -# Introduction to Unit Testing in Fortran - Challenge: Identify bad practice for unit testing Fortran +# Refactoring Fortran This exercise aims to teach principles of writing better Fortran; that is Fortran which is clear, maintainable and testable. @@ -20,7 +20,7 @@ cmake --build build ## Tasks -Implement the principles described in [the refactoring lesson](#tasks). +Implement the principles described in [the refactoring lesson](https://github-pages.arc.ucl.ac.uk/fortran-unit-testing-lesson/2-refactor-fortran.html). To ensure you are not changing the actual behaviour of the src code, every time you make a change, compare the output before and after. To do this store the output before making a change in a file @@ -30,7 +30,7 @@ called `before.dat` ./build/game-of-life path/to/model/file > before.dat ``` -Then, after you make a change regenerate the output and store within a file `after.dat` +Then, after you make a change regenerate the output and store within a file `after.dat` ```sh ./build/game-of-life path/to/model/file > after.dat diff --git a/episodes/7-refactoring-fortran/solution/README.md b/episodes/7-refactoring-fortran/solution/README.md index 7464c4d..a225449 100644 --- a/episodes/7-refactoring-fortran/solution/README.md +++ b/episodes/7-refactoring-fortran/solution/README.md @@ -1,4 +1,4 @@ -# Introduction to Unit Testing in Fortran - Solution: Identify bad practice for unit testing Fortran +# Refactoring Fortran - Solution The solution provided here is an entirely self-contained project which can be run, as before, using the following commands from within this dir. @@ -11,7 +11,7 @@ cmake --build build ## Tasks -> Implement the principles described in [the refactoring lesson](#tasks) +> Implement the principles described in [the refactoring lesson](https://github-pages.arc.ucl.ac.uk/fortran-unit-testing-lesson/2-refactor-fortran.html) 1. Replace magic numbers with constants 2. Change of variable name From bb7f8ba675bacc7e9a49ef75529e2699efcb58d9 Mon Sep 17 00:00:00 2001 From: connoraird Date: Fri, 5 Dec 2025 15:04:51 +0000 Subject: [PATCH 11/11] Replace the old exercise 2 with the refactoring exercises --- .../challenge/CMakeLists.txt | 15 -- .../challenge/README.md | 22 -- .../challenge/src/game_of_life.f90 | 184 --------------- .../models/model-2.dat | 34 --- .../models/model-3.dat | 34 --- .../models/model-4.dat | 34 --- .../solution/CMakeLists.txt | 15 -- .../solution/README.md | 37 --- .../solution/src/game_of_life.f90 | 60 ----- .../solution/src/game_of_life_mod.f90 | 211 ------------------ .../challenge/CMakeLists.txt | 0 .../challenge/README.md | 0 .../challenge/src/game_of_life.f90 | 0 .../models/model-1.dat | 0 .../solution/CMakeLists.txt | 0 .../solution/README.md | 0 .../solution/src/animation.f90 | 0 .../solution/src/cli.f90 | 0 .../solution/src/game_of_life.f90 | 0 .../solution/src/io.f90 | 0 .../solution/src/main.f90 | 0 .../7-refactoring-fortran/models/model-1.dat | 34 --- 22 files changed, 680 deletions(-) delete mode 100644 episodes/2-intro-to-fortran-unit-tests/challenge/CMakeLists.txt delete mode 100644 episodes/2-intro-to-fortran-unit-tests/challenge/README.md delete mode 100644 episodes/2-intro-to-fortran-unit-tests/challenge/src/game_of_life.f90 delete mode 100644 episodes/2-intro-to-fortran-unit-tests/models/model-2.dat delete mode 100644 episodes/2-intro-to-fortran-unit-tests/models/model-3.dat delete mode 100644 episodes/2-intro-to-fortran-unit-tests/models/model-4.dat delete mode 100644 episodes/2-intro-to-fortran-unit-tests/solution/CMakeLists.txt delete mode 100644 episodes/2-intro-to-fortran-unit-tests/solution/README.md delete mode 100644 episodes/2-intro-to-fortran-unit-tests/solution/src/game_of_life.f90 delete mode 100644 episodes/2-intro-to-fortran-unit-tests/solution/src/game_of_life_mod.f90 rename episodes/{7-refactoring-fortran => 2-refactoring-fortran}/challenge/CMakeLists.txt (100%) rename episodes/{7-refactoring-fortran => 2-refactoring-fortran}/challenge/README.md (100%) rename episodes/{7-refactoring-fortran => 2-refactoring-fortran}/challenge/src/game_of_life.f90 (100%) rename episodes/{2-intro-to-fortran-unit-tests => 2-refactoring-fortran}/models/model-1.dat (100%) rename episodes/{7-refactoring-fortran => 2-refactoring-fortran}/solution/CMakeLists.txt (100%) rename episodes/{7-refactoring-fortran => 2-refactoring-fortran}/solution/README.md (100%) rename episodes/{7-refactoring-fortran => 2-refactoring-fortran}/solution/src/animation.f90 (100%) rename episodes/{7-refactoring-fortran => 2-refactoring-fortran}/solution/src/cli.f90 (100%) rename episodes/{7-refactoring-fortran => 2-refactoring-fortran}/solution/src/game_of_life.f90 (100%) rename episodes/{7-refactoring-fortran => 2-refactoring-fortran}/solution/src/io.f90 (100%) rename episodes/{7-refactoring-fortran => 2-refactoring-fortran}/solution/src/main.f90 (100%) delete mode 100644 episodes/7-refactoring-fortran/models/model-1.dat diff --git a/episodes/2-intro-to-fortran-unit-tests/challenge/CMakeLists.txt b/episodes/2-intro-to-fortran-unit-tests/challenge/CMakeLists.txt deleted file mode 100644 index eada6f8..0000000 --- a/episodes/2-intro-to-fortran-unit-tests/challenge/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -cmake_minimum_required(VERSION 3.9 FATAL_ERROR) - -# Set project name -project( - "game-of-life" - LANGUAGES "Fortran" - VERSION "0.0.1" - DESCRIPTION "Conway's game of life" -) - -# Define src files -file(GLOB PROJ_SRC_FILES "${PROJECT_SOURCE_DIR}/src/*.f90") - -# Build src executables -add_executable("${PROJECT_NAME}" "${PROJ_SRC_FILES}") diff --git a/episodes/2-intro-to-fortran-unit-tests/challenge/README.md b/episodes/2-intro-to-fortran-unit-tests/challenge/README.md deleted file mode 100644 index 23fe056..0000000 --- a/episodes/2-intro-to-fortran-unit-tests/challenge/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Introduction to Unit Testing in Fortran - Challenge: Identify bad practice for unit testing Fortran - -This exercise aims to highlight aspects of Fortran src code which make it difficult to unit test and how to write more testable -Fortran - -## The code - -Take a look at the [src](./src) code provided. This is an implementation of -[Conway's game of life](http://en.wikipedia.org/wiki/Conway%27s_Game_of_Life). The program reads in a data file which represents -the starting state of the system. The system is then evolved and printed to the terminal screen for each time step. To build and -run the src code use the following commands from within this dir. - -```bash -cmake -B build -cmake --build build -./build/game-of-life ../models/model-1.dat # Or another data file -``` - -## Tasks - -1. Can you identify the aspects of this Fortran code which make it difficult to unit test? -2. Try to improve the src to make it more unit testable. diff --git a/episodes/2-intro-to-fortran-unit-tests/challenge/src/game_of_life.f90 b/episodes/2-intro-to-fortran-unit-tests/challenge/src/game_of_life.f90 deleted file mode 100644 index 888e77a..0000000 --- a/episodes/2-intro-to-fortran-unit-tests/challenge/src/game_of_life.f90 +++ /dev/null @@ -1,184 +0,0 @@ -! ======================================================= -! Conway's game of life -! -! ======================================================= -! Adapted from https://github.com/tuckerrc/game_of_life -! ======================================================= -program game_of_life - - implicit none - - !! Board args - integer, parameter :: max_nrow = 100, max_ncol = 100, max_generations = 100 - integer :: nrow, ncol - integer :: row, generation_number - integer, dimension(:,:), allocatable :: current_board, new_board - - !! Animation args - integer, dimension(8) :: date_time_values - integer :: mod_ms_step, ms_per_step = 250 - logical :: steady_state = .false. - - !! CLI args - integer :: argl - character(len=:), allocatable :: cli_arg_temp_store, input_fname - - !! File IO args - character(len=80) :: text_to_discard - integer :: input_file_io - integer :: iostat - - ! Get current_board file path from command line - if (command_argument_count() == 1) then - call get_command_argument(1, length=argl) - allocate(character(argl) :: input_fname) - call get_command_argument(1, input_fname) - else - write(*,'(A)') "Error: Invalid input" - call get_command_argument(0, length=argl) - allocate(character(argl) :: cli_arg_temp_store) - call get_command_argument(0, cli_arg_temp_store) - write(*,'(A,A,A)') "Usage: ", cli_arg_temp_store, " " - deallocate(cli_arg_temp_store) - stop - end if - - ! Open input file - open(unit=input_file_io, & - file=input_fname, & - status='old', & - IOSTAT=iostat) - - if( iostat /= 0) then - write(*,'(a)') ' *** Error when opening '//input_fname - stop 1 - end if - - ! Read in current_board from file - read(input_file_io,'(a)') text_to_discard ! Skip first line - read(input_file_io,*) nrow, ncol - - ! Verify the number of rows read from the file - if (nrow < 1 .or. nrow > max_nrow) then - write (*,'(a,i6,a,i6)') "nrow must be a positive integer less than ", max_nrow, " found ", nrow - stop 1 - end if - - ! Verify the number of columns read from the file - if (ncol < 1 .or. ncol > max_ncol) then - write (*,'(a,i6,a,i6)') "ncol must be a positive integer less than ", max_ncol, " found ", ncol - stop 1 - end if - - allocate(current_board(nrow, ncol)) - allocate(new_board(nrow, ncol)) - - read(input_file_io,'(a)') text_to_discard ! Skip next line - ! Populate the boards starting state - do row = 1, nrow - read(input_file_io,*) current_board(row, :) - end do - - close(input_file_io) - - new_board = 0 - generation_number = 0 - - ! Clear the terminal screen - call system ("clear") - - ! Iterate until we reach a steady state - do while(.not. steady_state .and. generation_number < max_generations) - ! Advance the simulation in the steps of the requested number of milliseconds - call date_and_time(VALUES=date_time_values) - mod_ms_step = mod(date_time_values(8), ms_per_step) - - if (mod_ms_step == 0) then - call evolve_board() - call check_for_steady_state() - current_board = new_board - call draw_board() - - generation_number = generation_number + 1 - end if - - end do - - if (steady_state) then - write(*,'(a,i6,a)') "Reached steady after ", generation_number, " generations" - else - write(*,'(a,i6,a)') "Did NOT Reach steady after ", generation_number, " generations" - end if - - deallocate(current_board) - deallocate(new_board) - -contains - - !> Evolve the board into the state of the next iteration - subroutine evolve_board() - integer :: row, col, sum - - do row=2, nrow-1 - do col=2, ncol-1 - sum = 0 - sum = current_board(row, col-1) & - + current_board(row+1, col-1) & - + current_board(row+1, col) & - + current_board(row+1, col+1) & - + current_board(row, col+1) & - + current_board(row-1, col+1) & - + current_board(row-1, col) & - + current_board(row-1, col-1) - if(current_board(row,col)==1 .and. sum<=1) then - new_board(row,col) = 0 - elseif(current_board(row,col)==1 .and. sum<=3) then - new_board(row,col) = 1 - elseif(current_board(row,col)==1 .and. sum>=4)then - new_board(row,col) = 0 - elseif(current_board(row,col)==0 .and. sum==3)then - new_board(row,col) = 1 - endif - enddo - enddo - - return - end subroutine evolve_board - - !> Check if we have reached steady state, i.e. current and new board match - subroutine check_for_steady_state() - integer :: row, col - - do row=1, nrow - do col=1, ncol - if (.not. current_board(row, col) == new_board(row, col)) then - steady_state = .false. - return - end if - end do - end do - steady_state = .true. - end subroutine check_for_steady_state - - !> Output the current board to the terminal - subroutine draw_board() - integer :: row, col - character(nrow) :: output - - ! Clear the terminal screen - call system("clear") - - do row=1, nrow - output = "" - do col=1, ncol - if (current_board(row,col) == 1) then - output = trim(output)//"#" - else - output = trim(output)//"." - endif - enddo - print *, output - enddo - end subroutine draw_board - -end program game_of_life diff --git a/episodes/2-intro-to-fortran-unit-tests/models/model-2.dat b/episodes/2-intro-to-fortran-unit-tests/models/model-2.dat deleted file mode 100644 index bcefe2b..0000000 --- a/episodes/2-intro-to-fortran-unit-tests/models/model-2.dat +++ /dev/null @@ -1,34 +0,0 @@ -nrow ncol - 31 31 -Board - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 diff --git a/episodes/2-intro-to-fortran-unit-tests/models/model-3.dat b/episodes/2-intro-to-fortran-unit-tests/models/model-3.dat deleted file mode 100644 index 0aa62f2..0000000 --- a/episodes/2-intro-to-fortran-unit-tests/models/model-3.dat +++ /dev/null @@ -1,34 +0,0 @@ -nrow ncol - 31 31 -Board - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 diff --git a/episodes/2-intro-to-fortran-unit-tests/models/model-4.dat b/episodes/2-intro-to-fortran-unit-tests/models/model-4.dat deleted file mode 100644 index 9317fe3..0000000 --- a/episodes/2-intro-to-fortran-unit-tests/models/model-4.dat +++ /dev/null @@ -1,34 +0,0 @@ -nrow ncol - 31 31 -Board - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 diff --git a/episodes/2-intro-to-fortran-unit-tests/solution/CMakeLists.txt b/episodes/2-intro-to-fortran-unit-tests/solution/CMakeLists.txt deleted file mode 100644 index eada6f8..0000000 --- a/episodes/2-intro-to-fortran-unit-tests/solution/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -cmake_minimum_required(VERSION 3.9 FATAL_ERROR) - -# Set project name -project( - "game-of-life" - LANGUAGES "Fortran" - VERSION "0.0.1" - DESCRIPTION "Conway's game of life" -) - -# Define src files -file(GLOB PROJ_SRC_FILES "${PROJECT_SOURCE_DIR}/src/*.f90") - -# Build src executables -add_executable("${PROJECT_NAME}" "${PROJ_SRC_FILES}") diff --git a/episodes/2-intro-to-fortran-unit-tests/solution/README.md b/episodes/2-intro-to-fortran-unit-tests/solution/README.md deleted file mode 100644 index 4168041..0000000 --- a/episodes/2-intro-to-fortran-unit-tests/solution/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Introduction to Unit Testing in Fortran - Solution: Identify bad practice for unit testing Fortran - -The solution provided here is an entirely self-contained project which can be run, as before, using -the following commands from within this dir. - -```bash -cmake -B build -cmake --build build -./build/game-of-life ../models/model-1.dat # Or another data file -``` - -## Task 1 - -> Can you identify the aspects of this Fortran code which make it difficult to unit test? - -There are several issues with this Fortran code which make it hard to unit test. Find the suggested fixes listed below. - -1. Everything is contained within a single program. This prevents us from using individual procedures within test modules. - Effectively preventing us from testing them. - -2. There is a lot of global state used across multiple procedures. This makes tests dependent on one another therefore - complicating the management of data/state between tests. - -3. There is a lot of logic not contained within procedures. Wrapping this in procedures opens up more of the code which can be - tested. - -## Task 2 - -> Try to improve the src to make it more unit testable. - -1. To allow procedures to be tested, they have been moved out of the program into the module file - [game_of_life_mod.f90](./src/game_of_life_mod.f90) which can now be used within a test. - -2. The reliance on global state has been removed by passing all required values into each procedure at the point it is called. - -3. Logic originally in main program code has been wrapped into procedures allowing it to be tested. For example, there are two - new procedures `find_steady_state` and `read_model_from_file`. diff --git a/episodes/2-intro-to-fortran-unit-tests/solution/src/game_of_life.f90 b/episodes/2-intro-to-fortran-unit-tests/solution/src/game_of_life.f90 deleted file mode 100644 index fc4c837..0000000 --- a/episodes/2-intro-to-fortran-unit-tests/solution/src/game_of_life.f90 +++ /dev/null @@ -1,60 +0,0 @@ -! ======================================================= -! Conway's game of life -! -! ======================================================= -! Adapted from https://github.com/tuckerrc/game_of_life -! ======================================================= -program game_of_life - use game_of_life_mod, only : read_model_from_file, find_steady_state - - implicit none - - !! Board args - integer, parameter :: max_nrow = 100, max_ncol = 100 - integer :: generation_number - integer, dimension(:,:), allocatable :: current_board - - !! Animation args - logical :: steady_state = .false. - - !! CLI args - integer :: argl - character(len=:), allocatable :: cli_arg_temp_store, input_fname - - !! IO args - character(len=:), allocatable :: io_error_message - - ! Get current_board file path from command line - if (command_argument_count() == 1) then - call get_command_argument(1, length=argl) - allocate(character(argl) :: input_fname) - call get_command_argument(1, input_fname) - else - write(*,'(A)') "Error: Invalid input" - call get_command_argument(0, length=argl) - allocate(character(argl) :: cli_arg_temp_store) - call get_command_argument(0, cli_arg_temp_store) - write(*,'(A,A,A)') "Usage: ", cli_arg_temp_store, " " - deallocate(cli_arg_temp_store) - stop - end if - - call read_model_from_file(input_fname, max_nrow, max_ncol, current_board, io_error_message) - - if (allocated(io_error_message)) then - write (*,*) io_error_message - deallocate(io_error_message) - stop - end if - - call find_steady_state(.true., steady_state, generation_number, current_board) - - if (steady_state) then - write(*,'(a,i6,a)') "Reached steady after ", generation_number, " generations" - else - write(*,'(a,i6,a)') "Did NOT Reach steady after ", generation_number, " generations" - end if - - deallocate(current_board) - -end program game_of_life diff --git a/episodes/2-intro-to-fortran-unit-tests/solution/src/game_of_life_mod.f90 b/episodes/2-intro-to-fortran-unit-tests/solution/src/game_of_life_mod.f90 deleted file mode 100644 index aecd7ec..0000000 --- a/episodes/2-intro-to-fortran-unit-tests/solution/src/game_of_life_mod.f90 +++ /dev/null @@ -1,211 +0,0 @@ -module game_of_life_mod - implicit none - - public - -contains - - !> Find the steady state of the Game of Life board - subroutine find_steady_state(animate, steady_state, generation_number, current_board) - !> Whether to animate the board - logical, intent(in) :: animate - !> Whether the board has reached a steady state - logical, intent(out) :: steady_state - !> The number of generations that have been processed - integer, intent(out) :: generation_number - !> The current state of the board - integer, dimension(:,:), allocatable, intent(inout) :: current_board - - integer, dimension(:,:), allocatable :: new_board - - !! Board args - integer, parameter :: max_generations = 100 - - !! Animation args - integer, dimension(8) :: date_time_values - integer :: mod_ms_step - integer, parameter :: ms_per_step = 250 - - allocate(new_board(size(current_board,1), size(current_board, 2))) - new_board = 0 - - ! Clear the terminal screen - if (animate) call system ("clear") - - ! Iterate until we reach a steady state - steady_state = .false. - generation_number = 0 - mod_ms_step = 0 - do while(.not. steady_state .and. generation_number < max_generations) - if (animate) then - ! Advance the simulation in the steps of the requested number of milliseconds - call date_and_time(VALUES=date_time_values) - mod_ms_step = mod(date_time_values(8), ms_per_step) - end if - - if (mod_ms_step == 0) then - call evolve_board(current_board, new_board) - call check_for_steady_state(current_board, new_board, steady_state) - current_board = new_board - if (animate) call draw_board(current_board) - - generation_number = generation_number + 1 - end if - - end do - end subroutine find_steady_state - - !> Evolve the board into the state of the next iteration - subroutine evolve_board(current_board, new_board) - !> The board as it currently is before this iteration - integer, dimension(:,:), allocatable, intent(in) :: current_board - !> The board into which the new state will be stored after this iteration - integer, dimension(:,:), allocatable, intent(inout) :: new_board - - integer :: row, col, num_rows, num_cols, sum - - num_rows = size(current_board, 1) - num_cols = size(current_board, 2) - - do row=2, num_rows-1 - do col=2, num_cols-1 - sum = 0 - sum = current_board(row, col-1) & - + current_board(row+1, col-1) & - + current_board(row+1, col) & - + current_board(row+1, col+1) & - + current_board(row, col+1) & - + current_board(row-1, col+1) & - + current_board(row-1, col) & - + current_board(row-1, col-1) - if(current_board(row,col)==1 .and. sum<=1) then - new_board(row,col) = 0 - elseif(current_board(row,col)==1 .and. sum<=3) then - new_board(row,col) = 1 - elseif(current_board(row,col)==1 .and. sum>=4)then - new_board(row,col) = 0 - elseif(current_board(row,col)==0 .and. sum==3)then - new_board(row,col) = 1 - endif - enddo - enddo - end subroutine evolve_board - - !> Check if we have reached steady state, i.e. current and new board match - subroutine check_for_steady_state(current_board, new_board, steady_state) - !> The board as it currently is before this iteration - integer, dimension(:,:), allocatable, intent(in) :: current_board - !> The board into which the new state has been stored after this iteration - integer, dimension(:,:), allocatable, intent(in) :: new_board - !> Logical to indicate whether current and new board match - logical, intent(out) :: steady_state - - integer :: row, col, num_rows, num_cols - - num_rows = size(current_board, 1) - num_cols = size(current_board, 2) - - do row=1, num_rows - do col=1, num_cols - if (.not. current_board(row, col) == new_board(row, col)) then - steady_state = .false. - return - end if - end do - end do - - steady_state = .true. - end subroutine check_for_steady_state - - !> Output the current board to the terminal - subroutine draw_board(current_board) - !> The board as it currently is for this iteration - integer, dimension(:,:), allocatable, intent(in) :: current_board - - integer :: row, col, num_rows, num_cols - character(len=:), allocatable :: output - - call system("clear") - - num_rows = size(current_board, 1) - num_cols = size(current_board, 2) - - allocate(character(num_rows) :: output) - - do row=1, num_rows - output = "" - do col=1, num_cols - if (current_board(row,col) == 1) then - output = trim(output)//"#" - else - output = trim(output)//"." - endif - enddo - print *, output - enddo - - deallocate(output) - end subroutine draw_board - - !> Populate the a board from the provided file - subroutine read_model_from_file(input_fname, max_nrow, max_ncol, board, io_error_message) - !> The name of the file to read in the board - character(len=:), allocatable, intent(in) :: input_fname - !> The maximum allowed number of rows - integer, intent(in) :: max_nrow - !> The maximum allowed number of columns - integer, intent(in) :: max_ncol - !> The board to be populated - integer, dimension(:,:), allocatable, intent(out) :: board - !> A flag to indicate if reading the file was successful - character(len=:), allocatable,intent(out) :: io_error_message - - ! Board definition args - integer :: nrow, ncol, row - - ! File IO args - integer :: input_file_io, iostat - character(len=80) :: text_to_discard - - input_file_io = 1111 - - ! Open input file - open(unit=input_file_io, & - file=input_fname, & - status='old', & - IOSTAT=iostat) - - if( iostat == 0) then - ! Read in board from file - read(input_file_io,'(a)') text_to_discard ! Skip first line - read(input_file_io,*) nrow, ncol - - ! Verify the number of rows and columns read from the file - if (nrow < 1 .or. nrow > max_nrow) then - allocate(character(100) :: io_error_message) - write (io_error_message,'(a,i6,a,i6)') "nrow must be a positive integer less than ", max_nrow, " found ", nrow - elseif (ncol < 1 .or. ncol > max_ncol) then - allocate(character(100) :: io_error_message) - write (io_error_message,'(a,i6,a,i6)') "ncol must be a positive integer less than ", max_ncol, " found ", ncol - end if - else - allocate(character(100) :: io_error_message) - write(io_error_message,'(a)') ' *** Error when opening '//input_fname - endif - - if (.not. allocated(io_error_message)) then - - allocate(board(nrow, ncol)) - - read(input_file_io,'(a)') text_to_discard ! Skip next line - ! Populate the boards starting state - do row = 1, nrow - read(input_file_io,*) board(row, :) - end do - - end if - - close(input_file_io) - end subroutine read_model_from_file - -end module game_of_life_mod diff --git a/episodes/7-refactoring-fortran/challenge/CMakeLists.txt b/episodes/2-refactoring-fortran/challenge/CMakeLists.txt similarity index 100% rename from episodes/7-refactoring-fortran/challenge/CMakeLists.txt rename to episodes/2-refactoring-fortran/challenge/CMakeLists.txt diff --git a/episodes/7-refactoring-fortran/challenge/README.md b/episodes/2-refactoring-fortran/challenge/README.md similarity index 100% rename from episodes/7-refactoring-fortran/challenge/README.md rename to episodes/2-refactoring-fortran/challenge/README.md diff --git a/episodes/7-refactoring-fortran/challenge/src/game_of_life.f90 b/episodes/2-refactoring-fortran/challenge/src/game_of_life.f90 similarity index 100% rename from episodes/7-refactoring-fortran/challenge/src/game_of_life.f90 rename to episodes/2-refactoring-fortran/challenge/src/game_of_life.f90 diff --git a/episodes/2-intro-to-fortran-unit-tests/models/model-1.dat b/episodes/2-refactoring-fortran/models/model-1.dat similarity index 100% rename from episodes/2-intro-to-fortran-unit-tests/models/model-1.dat rename to episodes/2-refactoring-fortran/models/model-1.dat diff --git a/episodes/7-refactoring-fortran/solution/CMakeLists.txt b/episodes/2-refactoring-fortran/solution/CMakeLists.txt similarity index 100% rename from episodes/7-refactoring-fortran/solution/CMakeLists.txt rename to episodes/2-refactoring-fortran/solution/CMakeLists.txt diff --git a/episodes/7-refactoring-fortran/solution/README.md b/episodes/2-refactoring-fortran/solution/README.md similarity index 100% rename from episodes/7-refactoring-fortran/solution/README.md rename to episodes/2-refactoring-fortran/solution/README.md diff --git a/episodes/7-refactoring-fortran/solution/src/animation.f90 b/episodes/2-refactoring-fortran/solution/src/animation.f90 similarity index 100% rename from episodes/7-refactoring-fortran/solution/src/animation.f90 rename to episodes/2-refactoring-fortran/solution/src/animation.f90 diff --git a/episodes/7-refactoring-fortran/solution/src/cli.f90 b/episodes/2-refactoring-fortran/solution/src/cli.f90 similarity index 100% rename from episodes/7-refactoring-fortran/solution/src/cli.f90 rename to episodes/2-refactoring-fortran/solution/src/cli.f90 diff --git a/episodes/7-refactoring-fortran/solution/src/game_of_life.f90 b/episodes/2-refactoring-fortran/solution/src/game_of_life.f90 similarity index 100% rename from episodes/7-refactoring-fortran/solution/src/game_of_life.f90 rename to episodes/2-refactoring-fortran/solution/src/game_of_life.f90 diff --git a/episodes/7-refactoring-fortran/solution/src/io.f90 b/episodes/2-refactoring-fortran/solution/src/io.f90 similarity index 100% rename from episodes/7-refactoring-fortran/solution/src/io.f90 rename to episodes/2-refactoring-fortran/solution/src/io.f90 diff --git a/episodes/7-refactoring-fortran/solution/src/main.f90 b/episodes/2-refactoring-fortran/solution/src/main.f90 similarity index 100% rename from episodes/7-refactoring-fortran/solution/src/main.f90 rename to episodes/2-refactoring-fortran/solution/src/main.f90 diff --git a/episodes/7-refactoring-fortran/models/model-1.dat b/episodes/7-refactoring-fortran/models/model-1.dat deleted file mode 100644 index ed1129c..0000000 --- a/episodes/7-refactoring-fortran/models/model-1.dat +++ /dev/null @@ -1,34 +0,0 @@ -nrow ncol - 31 31 -Board - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0