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

259 lines
9.1 KiB
Ada

pragma Ada_2022;
with Ada.Containers; use Ada.Containers;
with Ada.Containers.Vectors;
with Ada.Numerics.Discrete_Random;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Text_IO.Unbounded_IO; use Ada.Text_IO.Unbounded_IO;
procedure Word_Search is
MIN_WORDS : constant Positive := 25;
MIN_WORD_LEN : constant Positive := 3;
MAX_WORD_LEN : constant Positive := 9;
SIDE_LEN : constant Positive := 10;
MESSAGE : constant String := "ROSETTACODE";
DICT_FILENAME : constant String := "unixdict.txt";
type Directions is (N, NE, E, SE, S, SW, W, NW);
type Move is record
DX, DY : Integer;
end record;
type Moves_Arr is array (Directions) of Move;
Moves : constant Moves_Arr := [
N => (0, -1), NE => (1, -1), E => (1, 0), SE => (1, 1),
S => (0, 1), SW => (-1, 1), W => (-1, 0), NW => (-1, -1)
];
subtype Grid_Dimension is Positive range 1 .. SIDE_LEN;
type Matrix is array (Grid_Dimension, Grid_Dimension) of Character;
subtype LC_Chars is Character range 'a' .. 'z';
subtype Valid_Lengths is Positive range MIN_WORD_LEN .. MAX_WORD_LEN;
package Word_Vectors is new Ada.Containers.Vectors (Positive, Unbounded_String);
type Dictionary is array (Valid_Lengths) of Word_Vectors.Vector;
Dict : Dictionary;
type Placement is record
Word : Unbounded_String;
Start : Unbounded_String;
end record;
package Placement_Vectors is new Ada.Containers.Vectors (Positive, Placement);
Placings : Placement_Vectors.Vector;
subtype Ten_K is Positive range 1 .. 10000;
package Rand_Dimension is new Ada.Numerics.Discrete_Random (Grid_Dimension);
package Rand_Dir is new Ada.Numerics.Discrete_Random (Directions);
package Rand_Len is new Ada.Numerics.Discrete_Random (Valid_Lengths);
package Rand_10k is new Ada.Numerics.Discrete_Random (Ten_K);
Dimension_Gen : Rand_Dimension.Generator;
Dir_Gen : Rand_Dir.Generator;
Len_Gen : Rand_Len.Generator;
Ten_K_Gen : Rand_10k.Generator;
procedure Load_Dictionary (Filename : String) is
Dict_File : File_Type;
Dict_Word : Unbounded_String;
Dict_Word_Len : Positive;
Word_OK : Boolean;
begin
Open (File => Dict_File, Mode => In_File, Name => Filename);
while not End_Of_File (Dict_File) loop
Dict_Word := Get_Line (Dict_File);
Dict_Word_Len := Length (Dict_Word);
if Dict_Word_Len >= MIN_WORD_LEN and then
Dict_Word_Len <= MAX_WORD_LEN
then
Word_OK := True;
for C of To_String (Dict_Word) loop
if C not in LC_Chars then
Word_OK := False;
exit;
end if;
end loop;
if Word_OK then
Dict (Dict_Word_Len).Append (Dict_Word);
end if;
end if;
end loop;
end Load_Dictionary;
function Pick_Random_Word return Unbounded_String is
Word_Length : Positive := Rand_Len.Random (Len_Gen);
Rand : constant Natural := Rand_10k.Random (Ten_K_Gen);
Word_Ix : Positive;
begin
Word_Length := Rand_Len.Random (Len_Gen);
if Word_Length > 4 then -- Reduce number of words > 4 chars
Word_Length := Rand_Len.Random (Len_Gen);
end if;
Word_Ix := Positive (Rand) mod (Positive (Dict (Word_Length).Length) - 1) + 1;
return Dict (Word_Length) (Word_Ix);
end Pick_Random_Word;
function Not_Too_Long (X, Y : Grid_Dimension; Len : Positive; Dir : Directions) return Boolean is
begin
case Dir is
when N => return Y - Len > 0;
when S => return Y + Len <= SIDE_LEN;
when E => return X + Len <= SIDE_LEN;
when W => return X - Len > 0;
when NE => return X + Len <= SIDE_LEN and then Y - Len > 0;
when SW => return Y + Len <= SIDE_LEN and then X - Len > 0;
when SE => return Y + Len <= SIDE_LEN and then X + Len <= SIDE_LEN;
when NW => return Y - Len > 0 and then X - Len > 0;
end case;
end Not_Too_Long;
function Is_Empty (G : Matrix; Row, Col : Positive) return Boolean is
(G (Row, Col) = ' ');
function Count_Empties (G : Matrix) return Natural is
Count : Natural := 0;
begin
for Row in Grid_Dimension loop
for Col in Grid_Dimension loop
Count := Count + (if Is_Empty (G, Row, Col) then 1 else 0);
end loop;
end loop;
return Count;
end Count_Empties;
function Can_Place (G : Matrix; X, Y : Grid_Dimension; Word : Unbounded_String; Dir : Directions)
return Boolean is
GX : Grid_Dimension := X;
GY : Grid_Dimension := Y;
Len : constant Positive := Length (Word);
begin
for C in 1 .. Len loop
if not Is_Empty (G, GX, GY) and then
G (GX, GY) /= Element (Word, C)
then
return False;
else
GX := GX + Moves (Dir).DX;
GY := GY + Moves (Dir).DY;
end if;
end loop;
return True;
end Can_Place;
procedure Insert_Word (G : in out Matrix;
X, Y : Grid_Dimension;
Dir : Directions;
Word : Unbounded_String) is
-- We assume it fits. You've checked first haven't you!
GX : Grid_Dimension := X;
GY : Grid_Dimension := Y;
begin
for C in 1 .. Length (Word) loop
G (GX, GY) := Element (Word, C);
GX := GX + Moves (Dir).DX;
GY := GY + Moves (Dir).DY;
end loop;
end Insert_Word;
function Try_To_Place_Word (G : in out Matrix; Word : Unbounded_String; Max_Tries : Positive)
return Boolean is
-- Attempt to place the given word in the grid, return success or failure.
X1, Y1 : Grid_Dimension;
Dir : Directions;
Try : Positive := 1;
Place : Placement;
begin
while Try <= Max_Tries loop
X1 := Rand_Dimension.Random (Dimension_Gen);
Y1 := Rand_Dimension.Random (Dimension_Gen);
Dir := Rand_Dir.Random (Dir_Gen);
if Not_Too_Long (X1, Y1, Length (Word), Dir) and then
Can_Place (G, X1, Y1, Word, Dir)
then
Insert_Word (G, X1, Y1, Dir, Word);
Place.Word := Word;
Place.Start := To_Unbounded_String (X1'Image & LC_Chars'Val (Integer (Y1) + 64));
Placings.Append (Place);
return True;
end if;
Try := Try + 1;
end loop;
return False;
end Try_To_Place_Word;
procedure Print_Matrix (M : Matrix) is
begin
Put_Line (" A B C D E F G H I J"); New_Line;
for Row in Grid_Dimension loop
Put (Row'Image);
Set_Col (6);
for Col in Grid_Dimension loop
Put (M (Row, Col) & " ");
end loop;
New_Line;
end loop;
New_Line;
end Print_Matrix;
function Place_Message (G : in out Matrix; Msg : String) return Boolean is
-- Attempt to put message in grid, return False if unable.
Msg_Posn : Positive := 1;
begin
for Row in Grid_Dimension loop
for Col in Grid_Dimension loop
if Is_Empty (G, Row, Col) then
G (Row, Col) := Msg (Msg_Posn);
if Msg_Posn = Msg'Length then
return True;
else
Msg_Posn := Msg_Posn + 1;
end if;
end if;
end loop;
end loop;
return False;
end Place_Message;
Grid : Matrix;
Words_Placed : Natural;
Message_Placed : Boolean;
A_Word : Unbounded_String;
Attempt : Positive;
Col : Positive_Count := 1;
begin
Load_Dictionary (DICT_FILENAME);
Rand_Dimension.Reset (Dimension_Gen);
Rand_Dir.Reset (Dir_Gen);
Rand_Len.Reset (Len_Gen);
Rand_10k.Reset (Ten_K_Gen);
loop
Grid := [others => [others => ' ']];
Words_Placed := 0; Message_Placed := False; Placings.Clear;
Attempt := 1;
Builder :
loop
A_Word := Pick_Random_Word;
if Try_To_Place_Word (Grid, A_Word, 50) then
Words_Placed := @ + 1;
end if;
if Count_Empties (Grid) = MESSAGE'Length then
if Place_Message (Grid, MESSAGE) then
Message_Placed := True;
exit Builder;
end if;
elsif Count_Empties (Grid) < MESSAGE'Length then
-- Put_Line ("No room for message, giving up.");
exit Builder;
elsif Attempt > 1000 then
Put_Line ("Giving up after 1000 tries.");
exit Builder;
end if;
Attempt := Attempt + 1;
end loop Builder;
exit when Words_Placed >= MIN_WORDS and then Message_Placed;
end loop;
Print_Matrix (Grid);
Put_Line ("Words placed:" & Words_Placed'Image);
for P of Placings loop
Set_Col (Col); Put (P.Word);
Set_Col (Col + 9); Put (P.Start);
Col := (if Col < 61 then Col + 20 else 1);
end loop;
end Word_Search;