Skip to content

Commit ba51772

Browse files
committed
Refactoring-7: Separate code concepts into files or modules
1 parent cb41530 commit ba51772

File tree

6 files changed

+184
-146
lines changed

6 files changed

+184
-146
lines changed

episodes/7-refactoring-fortran/solution/CMakeLists.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@ project(
99
)
1010

1111
# Define src files
12-
file(GLOB PROJ_SRC_FILES "${PROJECT_SOURCE_DIR}/src/*.f90")
12+
set(SRC_DIR "${PROJECT_SOURCE_DIR}/src")
13+
set(PROJ_SRC_FILES
14+
"${SRC_DIR}/main.f90"
15+
"${SRC_DIR}/animation.f90"
16+
"${SRC_DIR}/cli.f90"
17+
"${SRC_DIR}/game_of_life.f90"
18+
"${SRC_DIR}/io.f90"
19+
)
1320

1421
# Build src executables
1522
add_executable("${PROJECT_NAME}" "${PROJ_SRC_FILES}")
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
module animation
2+
implicit none
3+
public
4+
5+
logical, parameter :: animate = .true.
6+
7+
contains
8+
9+
!> Output the current board to the terminal
10+
subroutine draw_board(board)
11+
!> The current state of the board
12+
integer, dimension(:,:), allocatable, intent(in) :: board
13+
14+
integer :: row, col, nrow, ncol
15+
character(:), allocatable :: output
16+
17+
nrow = size(board, 1)
18+
ncol = size(board, 2)
19+
20+
allocate(character(nrow) :: output)
21+
22+
! Clear the terminal screen
23+
call system("clear")
24+
25+
do row=1, nrow
26+
output = ""
27+
do col=1, ncol
28+
if (board(row,col) == 1) then
29+
output = trim(output)//"#"
30+
else
31+
output = trim(output)//"."
32+
endif
33+
enddo
34+
print *, output
35+
enddo
36+
end subroutine draw_board
37+
38+
end module animation
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module cli
2+
implicit none
3+
public
4+
5+
contains
6+
7+
!> Read a cli arg at a given index and return it as a string (character array)
8+
subroutine read_cli_arg(arg_index, arg)
9+
!> The index of the cli arg to try and read
10+
integer, intent(in) :: arg_index
11+
!> The string into which to store the cli arg
12+
character(len=:), allocatable, intent(out) :: arg
13+
14+
integer :: argl
15+
character(len=:), allocatable :: cli_arg_temp_store
16+
17+
call get_command_argument(arg_index, length=argl)
18+
allocate(character(argl) :: cli_arg_temp_store)
19+
call get_command_argument(arg_index, cli_arg_temp_store)
20+
arg = trim(cli_arg_temp_store)
21+
end subroutine read_cli_arg
22+
23+
end module cli
Lines changed: 4 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,10 @@
1-
! =======================================================
2-
! Conway's game of life
3-
!
4-
! =======================================================
5-
! Adapted from https://github.com/tuckerrc/game_of_life
6-
! =======================================================
7-
program game_of_life
8-
1+
module game_of_life
2+
use animation, only : draw_board
93
implicit none
10-
11-
logical, parameter :: animate = .true.
12-
integer, dimension(:,:), allocatable :: starting_board
13-
integer :: generation_number
14-
logical :: steady_state = .false.
15-
16-
!! CLI args
17-
character(len=:), allocatable :: executable_name, input_filename
18-
19-
! Get current_board file path from command line
20-
if (command_argument_count() == 1) then
21-
call read_cli_arg(1, input_filename)
22-
else
23-
write(*,'(A)') "Error: Invalid input"
24-
call read_cli_arg(0, executable_name)
25-
write(*,'(A,A,A)') "Usage: ", executable_name, " <input_file_name>"
26-
stop
27-
end if
28-
29-
call read_model_from_file(input_filename, starting_board)
30-
31-
call find_steady_state(steady_state, generation_number, starting_board, animate)
32-
33-
if (steady_state) then
34-
write(*,'(a,i6,a)') "Reached steady after ", generation_number, " generations"
35-
else
36-
write(*,'(a,i6,a)') "Did NOT Reach steady after ", generation_number, " generations"
37-
end if
4+
public
385

396
contains
407

41-
!> Read a cli arg at a given index and return it as a string (character array)
42-
subroutine read_cli_arg(arg_index, arg)
43-
!> The index of the cli arg to try and read
44-
integer, intent(in) :: arg_index
45-
!> The string into which to store the cli arg
46-
character(len=:), allocatable, intent(out) :: arg
47-
48-
integer :: argl
49-
character(len=:), allocatable :: cli_arg_temp_store
50-
51-
call get_command_argument(arg_index, length=argl)
52-
allocate(character(argl) :: cli_arg_temp_store)
53-
call get_command_argument(arg_index, cli_arg_temp_store)
54-
arg = trim(cli_arg_temp_store)
55-
end subroutine read_cli_arg
56-
57-
!> Populate the board from a provided file
58-
subroutine read_model_from_file(input_filename, board)
59-
character(len=:), allocatable, intent(in) :: input_filename
60-
integer, dimension(:,:), allocatable, intent(out) :: board
61-
62-
!> A flag to indicate if reading the file was successful
63-
character(len=:), allocatable :: io_error_message
64-
65-
! Board definition args
66-
integer :: row, nrow, ncol
67-
integer, parameter :: max_nrows = 100, max_ncols = 100
68-
69-
! File IO args
70-
integer :: input_file_io, iostat
71-
character(len=80) :: text_to_discard
72-
73-
input_file_io = 1111
74-
75-
! Open input file
76-
open(unit=input_file_io, &
77-
file=input_filename, &
78-
status='old', &
79-
IOSTAT=iostat)
80-
81-
if( iostat == 0) then
82-
! Read in board from file
83-
read(input_file_io,'(a)') text_to_discard ! Skip first line
84-
read(input_file_io,*) nrow, ncol
85-
86-
! Verify the number of rows and columns read from the file
87-
if (nrow < 1 .or. nrow > max_nrows) then
88-
allocate(character(100) :: io_error_message)
89-
write (io_error_message,'(a,i6,a,i6)') "nrow must be a positive integer less than ", max_nrows, " found ", nrow
90-
elseif (ncol < 1 .or. ncol > max_ncols) then
91-
allocate(character(100) :: io_error_message)
92-
write (io_error_message,'(a,i6,a,i6)') "ncol must be a positive integer less than ", max_ncols, " found ", ncol
93-
end if
94-
else
95-
allocate(character(100) :: io_error_message)
96-
write(io_error_message,'(a)') ' *** Error when opening '//input_filename
97-
endif
98-
99-
if (.not. allocated(io_error_message)) then
100-
101-
allocate(board(nrow, ncol))
102-
103-
read(input_file_io,'(a)') text_to_discard ! Skip next line
104-
! Populate the boards starting state
105-
do row = 1, nrow
106-
read(input_file_io,*) board(row, :)
107-
end do
108-
109-
end if
110-
111-
close(input_file_io)
112-
113-
if (allocated(io_error_message)) then
114-
write (*,*) io_error_message
115-
deallocate(io_error_message)
116-
stop
117-
end if
118-
end subroutine read_model_from_file
119-
1208
!> Find the steady state of the Game of Life board
1219
subroutine find_steady_state(steady_state, generation_number, input_board, animate)
12210
!> Whether the board has reached a steady state
@@ -230,33 +118,4 @@ subroutine check_for_steady_state(steady_state, current_board, new_board)
230118
steady_state = .true.
231119
end subroutine check_for_steady_state
232120

233-
!> Output the current board to the terminal
234-
subroutine draw_board(board)
235-
!> The current state of the board
236-
integer, dimension(:,:), allocatable, intent(in) :: board
237-
238-
integer :: row, col, nrow, ncol
239-
character(:), allocatable :: output
240-
241-
nrow = size(board, 1)
242-
ncol = size(board, 2)
243-
244-
allocate(character(nrow) :: output)
245-
246-
! Clear the terminal screen
247-
call system("clear")
248-
249-
do row=1, nrow
250-
output = ""
251-
do col=1, ncol
252-
if (board(row,col) == 1) then
253-
output = trim(output)//"#"
254-
else
255-
output = trim(output)//"."
256-
endif
257-
enddo
258-
print *, output
259-
enddo
260-
end subroutine draw_board
261-
262-
end program game_of_life
121+
end module game_of_life
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
module io
2+
implicit none
3+
public
4+
5+
contains
6+
7+
!> Populate the board from a provided file
8+
subroutine read_model_from_file(input_filename, board)
9+
character(len=:), allocatable, intent(in) :: input_filename
10+
integer, dimension(:,:), allocatable, intent(out) :: board
11+
12+
!> A flag to indicate if reading the file was successful
13+
character(len=:), allocatable :: io_error_message
14+
15+
! Board definition args
16+
integer :: row, nrow, ncol
17+
integer, parameter :: max_nrows = 100, max_ncols = 100
18+
19+
! File IO args
20+
integer :: input_file_io, iostat
21+
character(len=80) :: text_to_discard
22+
23+
input_file_io = 1111
24+
25+
! Open input file
26+
open(unit=input_file_io, &
27+
file=input_filename, &
28+
status='old', &
29+
IOSTAT=iostat)
30+
31+
if( iostat == 0) then
32+
! Read in board from file
33+
read(input_file_io,'(a)') text_to_discard ! Skip first line
34+
read(input_file_io,*) nrow, ncol
35+
36+
! Verify the number of rows and columns read from the file
37+
if (nrow < 1 .or. nrow > max_nrows) then
38+
allocate(character(100) :: io_error_message)
39+
write (io_error_message,'(a,i6,a,i6)') "nrow must be a positive integer less than ", max_nrows, " found ", nrow
40+
elseif (ncol < 1 .or. ncol > max_ncols) then
41+
allocate(character(100) :: io_error_message)
42+
write (io_error_message,'(a,i6,a,i6)') "ncol must be a positive integer less than ", max_ncols, " found ", ncol
43+
end if
44+
else
45+
allocate(character(100) :: io_error_message)
46+
write(io_error_message,'(a)') ' *** Error when opening '//input_filename
47+
endif
48+
49+
if (.not. allocated(io_error_message)) then
50+
51+
allocate(board(nrow, ncol))
52+
53+
read(input_file_io,'(a)') text_to_discard ! Skip next line
54+
! Populate the boards starting state
55+
do row = 1, nrow
56+
read(input_file_io,*) board(row, :)
57+
end do
58+
59+
end if
60+
61+
close(input_file_io)
62+
63+
if (allocated(io_error_message)) then
64+
write (*,*) io_error_message
65+
deallocate(io_error_message)
66+
stop
67+
end if
68+
end subroutine read_model_from_file
69+
70+
end module io
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
! =======================================================
2+
! Conway's game of life
3+
!
4+
! =======================================================
5+
! Adapted from https://github.com/tuckerrc/game_of_life
6+
! =======================================================
7+
program main
8+
use animation, only : animate
9+
use cli, only : read_cli_arg
10+
use game_of_life, only : check_for_steady_state, evolve_board, find_steady_state
11+
use io, only : read_model_from_file
12+
implicit none
13+
14+
integer, dimension(:,:), allocatable :: starting_board
15+
integer :: generation_number
16+
logical :: steady_state = .false.
17+
18+
!! CLI args
19+
character(len=:), allocatable :: executable_name, input_filename
20+
21+
! Get current_board file path from command line
22+
if (command_argument_count() == 1) then
23+
call read_cli_arg(1, input_filename)
24+
else
25+
write(*,'(A)') "Error: Invalid input"
26+
call read_cli_arg(0, executable_name)
27+
write(*,'(A,A,A)') "Usage: ", executable_name, " <input_file_name>"
28+
stop
29+
end if
30+
31+
call read_model_from_file(input_filename, starting_board)
32+
33+
call find_steady_state(steady_state, generation_number, starting_board, animate)
34+
35+
if (steady_state) then
36+
write(*,'(a,i6,a)') "Reached steady after ", generation_number, " generations"
37+
else
38+
write(*,'(a,i6,a)') "Did NOT Reach steady after ", generation_number, " generations"
39+
end if
40+
41+
end program main

0 commit comments

Comments
 (0)