RosettaCodeData/Task/Word-search/Fortran/word-search.f
2026-04-30 12:34:36 -04:00

509 lines
16 KiB
Fortran

program word_search_rosetta_complete
implicit none
integer, parameter :: GRID_SIZE = 10
integer, parameter :: MAX_WORDS = 5000
integer, parameter :: MAX_WORD_LEN = 20
integer, parameter :: MIN_PLACED = 25
character(len=1) :: grid(GRID_SIZE, GRID_SIZE)
character(len=MAX_WORD_LEN) :: words(MAX_WORDS), original_words(MAX_WORDS)
integer :: word_lens(MAX_WORDS), original_lens(MAX_WORDS)
integer :: num_words
integer :: length_limits(3:10)
type :: word_placement
character(len=MAX_WORD_LEN) :: word
integer :: row, col
integer :: dx, dy
end type
type(word_placement) :: placed(200), all_found(500)
integer :: num_placed, num_found
integer :: attempt
call init_random_seed()
call load_words(original_words, original_lens, num_words)
print '(A,I0,A)', "Loaded ", num_words, " valid words"
! Try to create puzzle
do attempt = 1, 10
words = original_words
word_lens = original_lens
call create_puzzle(words, word_lens, num_words, grid, placed, num_placed)
! VB stops when we have 25+ words (success!)
if (num_placed >= MIN_PLACED) then
print '(A,I0)', "Success on attempt ", attempt
! Find ALL words embedded in the final grid
call find_all_words(original_words, original_lens, num_words, grid, all_found, num_found)
call print_results(grid, placed, num_placed, all_found, num_found)
exit
end if
print '(A,I0,A,I0,A)', "Attempt ", attempt, " placed ", num_placed, " words, retrying..."
end do
contains
subroutine init_random_seed()
integer :: seed_size, clock, i
integer, allocatable :: seed(:)
call random_seed(size=seed_size)
allocate(seed(seed_size))
call system_clock(count=clock)
seed = clock + 37 * [(i, i=1, seed_size)]
call random_seed(put=seed)
deallocate(seed)
end subroutine
subroutine load_words(words, word_lens, num_words)
character(len=*), intent(out) :: words(:)
integer, intent(out) :: word_lens(:)
integer, intent(out) :: num_words
character(len=MAX_WORD_LEN) :: word
integer :: ios, i
logical :: valid
num_words = 0
open(10, file='unixdict.txt', status='old', action='read', iostat=ios)
if (ios /= 0) then
print *, "Error opening unixdict.txt"
stop
end if
do
read(10, '(A)', iostat=ios) word
if (ios /= 0) exit
word = trim(word)
if (len_trim(word) <= 2 .or. len_trim(word) > 10) cycle
valid = .true.
do i = 1, len_trim(word)
if (word(i:i) < 'a' .or. word(i:i) > 'z') then
valid = .false.
exit
end if
end do
if (valid) then
num_words = num_words + 1
if (num_words > MAX_WORDS) exit
words(num_words) = trim(word)
word_lens(num_words) = len_trim(word)
end if
end do
close(10)
end subroutine
subroutine shuffle_words(words, word_lens, num_words)
character(len=*), intent(inout) :: words(:)
integer, intent(inout) :: word_lens(:)
integer, intent(in) :: num_words
integer :: i, r, temp_len
character(len=MAX_WORD_LEN) :: temp_word
real :: rr
do i = num_words, 2, -1
call random_number(rr)
r = int(rr * i) + 1
temp_word = words(i)
words(i) = words(r)
words(r) = temp_word
temp_len = word_lens(i)
word_lens(i) = word_lens(r)
word_lens(r) = temp_len
end do
end subroutine
function count_spaces(grid) result(count)
character(len=1), intent(in) :: grid(:,:)
integer :: count, i, j
count = 0
do i = 1, GRID_SIZE
do j = 1, GRID_SIZE
if (grid(j, i) == ' ') count = count + 1
end do
end do
end function
subroutine create_puzzle(words, word_lens, num_words, grid, placed, num_placed)
character(len=*), intent(inout) :: words(:)
integer, intent(inout) :: word_lens(:)
integer, intent(in) :: num_words
character(len=1), intent(out) :: grid(:,:)
type(word_placement), intent(out) :: placed(:)
integer, intent(out) :: num_placed
integer :: i
real :: r
! Initialize grid
grid = ' '
num_placed = 0
! Pre-seed ROSETTACODE letters (VB strategy)
call random_number(r)
grid(int(r * 5) + 6, 1) = 'R'
call random_number(r)
grid(int(r * 9) + 2, 2) = 'O'
call random_number(r)
grid(int(r * 9) + 2, 3) = 'S'
call random_number(r)
grid(int(r * 9) + 2, 4) = 'E'
grid(2, 5) = 'T'
grid(10, 5) = 'T'
call random_number(r)
grid(int(r * 10) + 1, 6) = 'A'
call random_number(r)
grid(int(r * 10) + 1, 7) = 'C'
call random_number(r)
grid(int(r * 10) + 1, 8) = 'O'
call random_number(r)
grid(int(r * 10) + 1, 9) = 'D'
call random_number(r)
grid(int(r * 10) + 1, 10) = 'E'
! Set length limits (pack the grid EXTREMELY full like VB)
length_limits(3) = 500 ! VB had 200 - go even higher
length_limits(4) = 100
length_limits(5) = 50
length_limits(6) = 30
length_limits(7) = 20
length_limits(8) = 10
length_limits(9) = 5
length_limits(10) = 3
! Shuffle words
call shuffle_words(words, word_lens, num_words)
! Place words systematically (VB strategy - FILL THE GRID!)
do i = 1, num_words
! VB stops when grid is completely full
if (count_spaces(grid) == 0) exit
! Check length limit
if (word_lens(i) >= 3 .and. word_lens(i) <= 10) then
if (length_limits(word_lens(i)) > 0) then
if (place_word_systematic(words(i), word_lens(i), grid, placed, num_placed)) then
length_limits(word_lens(i)) = length_limits(word_lens(i)) - 1
end if
end if
end if
end do
! Fill remaining spaces with random lowercase
call fill_remaining(grid)
end subroutine
logical function place_word_systematic(word, word_len, grid, placed, num_placed)
character(len=*), intent(in) :: word
integer, intent(in) :: word_len
character(len=1), intent(inout) :: grid(:,:)
type(word_placement), intent(inout) :: placed(:)
integer, intent(inout) :: num_placed
integer :: start_pos, test_num, spot, x, y, d, dir_test
integer :: dx(0:7), dy(0:7)
integer :: rdir, rdd
real :: r
! Direction arrays
dx = [1, 1, 0, -1, -1, -1, 0, 1]
dy = [0, 1, 1, 1, 0, -1, -1, -1]
place_word_systematic = .false.
! Random starting position
call random_number(r)
start_pos = int(r * 100)
! Random direction to traverse grid
call random_number(r)
if (r < 0.5) then
rdir = -1
else
rdir = 1
end if
! Try all 100 positions
do test_num = 0, 99
spot = mod(start_pos + test_num * rdir + 100, 100)
y = spot / 10 + 1
x = mod(spot, 10) + 1
! If first letter matches or space is empty
if (grid(x, y) == word(1:1) .or. grid(x, y) == ' ' .or. &
(grid(x, y) >= 'A' .and. grid(x, y) <= 'Z' .and. &
char(ichar(grid(x, y)) + 32) == word(1:1))) then
! Try all 8 directions
call random_number(r)
d = int(r * 8)
call random_number(r)
if (r < 0.5) then
rdd = -1
else
rdd = 1
end if
do dir_test = 0, 7
d = mod(d + dir_test * rdd + 8, 8)
if (can_place_vb(word, word_len, x, y, dx(d), dy(d), grid)) then
call place_word_vb(word, word_len, x, y, dx(d), dy(d), grid)
num_placed = num_placed + 1
placed(num_placed)%word = trim(word)
placed(num_placed)%col = x
placed(num_placed)%row = y
placed(num_placed)%dx = dx(d)
placed(num_placed)%dy = dy(d)
place_word_systematic = .true.
return
end if
end do
end if
end do
end function
logical function can_place_vb(word, word_len, x, y, dx, dy, grid)
character(len=*), intent(in) :: word
integer, intent(in) :: word_len, x, y, dx, dy
character(len=1), intent(in) :: grid(:,:)
integer :: i, cx, cy
logical :: fills_something
character(len=1) :: grid_char
can_place_vb = .false.
fills_something = .false.
! Check bounds
do i = 0, word_len - 1
cx = x + i * dx
cy = y + i * dy
if (cx < 1 .or. cx > GRID_SIZE .or. cy < 1 .or. cy > GRID_SIZE) return
end do
! Check if can place and if fills at least one space
do i = 0, word_len - 1
cx = x + i * dx
cy = y + i * dy
grid_char = grid(cx, cy)
if (grid_char == ' ') then
fills_something = .true.
else if (grid_char /= word(i+1:i+1)) then
! Allow uppercase match
if (grid_char >= 'A' .and. grid_char <= 'Z') then
if (char(ichar(grid_char) + 32) /= word(i+1:i+1)) return
else
return
end if
end if
end do
can_place_vb = fills_something
end function
subroutine place_word_vb(word, word_len, x, y, dx, dy, grid)
character(len=*), intent(in) :: word
integer, intent(in) :: word_len, x, y, dx, dy
character(len=1), intent(inout) :: grid(:,:)
integer :: i, cx, cy
do i = 0, word_len - 1
cx = x + i * dx
cy = y + i * dy
! Only place lowercase if empty or already matches
if (grid(cx, cy) == ' ' .or. grid(cx, cy) == word(i+1:i+1)) then
grid(cx, cy) = word(i+1:i+1)
end if
end do
end subroutine
subroutine fill_remaining(grid)
character(len=1), intent(inout) :: grid(:,:)
integer :: i, j
real :: r
do i = 1, GRID_SIZE
do j = 1, GRID_SIZE
if (grid(j, i) == ' ') then
call random_number(r)
grid(j, i) = char(int(r * 26) + ichar('a'))
end if
end do
end do
end subroutine
subroutine find_all_words(words, word_lens, num_words, grid, all_found, num_found)
character(len=*), intent(in) :: words(:)
integer, intent(in) :: word_lens(:)
integer, intent(in) :: num_words
character(len=1), intent(in) :: grid(:,:)
type(word_placement), intent(out) :: all_found(:)
integer, intent(out) :: num_found
integer :: i, x, y, d, j, cx, cy
integer :: dx(0:7), dy(0:7)
character(len=MAX_WORD_LEN) :: template
logical :: matches
dx = [1, 1, 0, -1, -1, -1, 0, 1]
dy = [0, 1, 1, 1, 0, -1, -1, -1]
num_found = 0
do i = 1, num_words
do y = 1, GRID_SIZE
do x = 1, GRID_SIZE
! Check if first letter matches (case-insensitive)
if (grid(x, y) /= words(i)(1:1) .and. &
char(ichar(grid(x, y)) - 32) /= char(ichar(words(i)(1:1)) - 32)) cycle
do d = 0, 7
! Check bounds
cx = x + (word_lens(i) - 1) * dx(d)
cy = y + (word_lens(i) - 1) * dy(d)
if (cx < 1 .or. cx > GRID_SIZE .or. cy < 1 .or. cy > GRID_SIZE) cycle
! Build template
template = ''
do j = 0, word_lens(i) - 1
cx = x + j * dx(d)
cy = y + j * dy(d)
template(j+1:j+1) = grid(cx, cy)
end do
! Check match (case-insensitive)
matches = .true.
do j = 1, word_lens(i)
if (template(j:j) /= words(i)(j:j) .and. &
char(ichar(template(j:j)) - 32) /= char(ichar(words(i)(j:j)) - 32)) then
matches = .false.
exit
end if
end do
if (matches) then
num_found = num_found + 1
if (num_found > 500) return
all_found(num_found)%word = trim(words(i))
all_found(num_found)%col = x
all_found(num_found)%row = y
all_found(num_found)%dx = dx(d)
all_found(num_found)%dy = dy(d)
end if
end do
end do
end do
end do
end subroutine
subroutine print_results(grid, placed, num_placed, all_found, num_found)
character(len=1), intent(in) :: grid(:,:)
type(word_placement), intent(in) :: placed(:), all_found(:)
integer, intent(in) :: num_placed, num_found
integer :: i, j, uppercase_count
character(len=20) :: dir_name
print *
print *, "Word Search Puzzle (10x10)"
print *, "=========================="
print *
write(*, '(A)', advance='no') " "
do j = 0, 9
write(*, '(I2)', advance='no') j
end do
print *
print *
do i = 1, GRID_SIZE
write(*, '(I3,A)', advance='no') i-1, " "
do j = 1, GRID_SIZE
write(*, '(A2)', advance='no') grid(j, i)
end do
print *
end do
uppercase_count = 0
do i = 1, GRID_SIZE
do j = 1, GRID_SIZE
if (grid(j, i) >= 'A' .and. grid(j, i) <= 'Z') uppercase_count = uppercase_count + 1
end do
end do
print *
print '(A,I0)', "ROSETTACODE letters: ", uppercase_count
print *
print '(A,I0,A)', "Words placed: ", num_placed
print '(A,I0,A)', "Total words found embedded: ", num_found, " (including overlaps)"
print *, "=========================="
print *
print *, "Placed words:"
do i = 1, min(num_placed, 50)
dir_name = get_direction_name(placed(i)%dx, placed(i)%dy)
write(*, '(I3,A,A12,A,I2,A,I2,A,A)') &
i, '. ', trim(placed(i)%word), &
' (', placed(i)%col-1, ',', placed(i)%row-1, ') ', trim(dir_name)
end do
print *
print *, "All words found in grid:"
do i = 1, min(num_found, 100)
dir_name = get_direction_name(all_found(i)%dx, all_found(i)%dy)
write(*, '(I3,A,A12,A,I2,A,I2,A,A)') &
i, '. ', trim(all_found(i)%word), &
' (', all_found(i)%col-1, ',', all_found(i)%row-1, ') ', trim(dir_name)
end do
end subroutine
function get_direction_name(dx, dy) result(name)
integer, intent(in) :: dx, dy
character(len=20) :: name
if (dy == 0 .and. dx > 0) then
name = "E"
else if (dy == 0 .and. dx < 0) then
name = "W"
else if (dx == 0 .and. dy > 0) then
name = "S"
else if (dx == 0 .and. dy < 0) then
name = "N"
else if (dx > 0 .and. dy > 0) then
name = "SE"
else if (dx > 0 .and. dy < 0) then
name = "NE"
else if (dx < 0 .and. dy > 0) then
name = "SW"
else if (dx < 0 .and. dy < 0) then
name = "NW"
end if
end function
end program word_search_rosetta_complete