indexing description: "A PUZZLE_FILE class that is responsible for managing a file in which a puzzle may be stored." author: "Larry Bush" date: "$Date: Date: 2001/04/21 $" revision: "$Revision: 1.00$" -- Larry Bush -- OOPD "Spring" (seems more like winter) 2001 -- Email: Lawrence_Bush@dps.state.ny.us -- Home Phone (Weekends and Evenings) : (518) 674-0408 -- Work Phone (M-F day): (518) 486-2896 -- This file is responsible for: -- Responsible for managing a file in which a puzzle may be stored -- This includes distinguishing between puzzles in progress and finalized -- puzzles. -- -- PUZZLE_FILE -- -- Inheritance Hierarchy: PF inherits FILE_HANDLER -- -- -- What it does: (Puzzle Handler) Puzzle File controls interactions with puzzles. For example, puzzle file is used to open a new puzzle via PFI then verify and convert the puzzle to an object. It also allows access to the puzzle namely for game room. Basically, it does everything you need to select, read, verify, convert/store, and play a puzzle. -- -- Major Features: set_name_and_retrieve_puzzle – uses file name parameter -- retrieve puzzle – uses PFI -- -- Class interactions: Client-Supplier Client of puzzle, puzzle file interface -- Supplier to game room. -- -- class code deferred class PUZZLE_FILE inherit FILE_HANDLER feature{NONE} -- lb code select_file : PUZZLE_FILE_INTERFACE -- declaration for a file selector object -- lb code end max_r, max_c : INTEGER -- Maximum dimensions for a puzzle I'm retrieving retrieved : PLAIN_TEXT_FILE -- Puzzle file. Use this variable while checking the validity -- of the puzzle in the file. line_count : INTEGER -- Used to size a puzzle upon creation -- RML HW5 link_section, designer_section : BOOLEAN -- Sections in the file marked with '-1' and '-2' respectively -- RML HW5 end_of_puzzle_data : BOOLEAN -- If you get to and through the designer section there is no more data. -- RML HW5 old_puzzle_file : BOOLEAN -- If the puzle_file does not contain designer data, then the puzzle file is considered 'old'. --RML HW5 solve_extension : STRING is "pzl" -- Single place identifying files extension for finalized. --RML HW5 design_extension : STRING is "pip" -- Single place identifying files extension for puzzles in progress. check_retrieved is -- Is `retrieved' a valid Puzzle? Set `retrieved_is_valid' accordingly. do retrieved_is_valid := True link_section := False designer_section := False end_of_puzzle_data := False if not retrieved.exists then put_file_warning( "No such file!" ) retrieved_is_valid := False elseif not retrieved.is_plain then put_file_warning( "Not a plain file!" ) retrieved_is_valid := False else retrieved.open_read check_first_line line_count := 1 -- RML from until not retrieved_is_valid or not retrieved.file_readable loop -- RML Note: -- RML 1. puzzles with no designer data will normally exit loop with retrieved.file_readable = False -- RML 2. puzzles with designer data will normally exit loop with end_of_puzzle_data = True from until not retrieved_is_valid or not retrieved.file_readable or end_of_puzzle_data loop -- RML HW5 if link_section then check_links_line elseif designer_section then -- RML HW5 check_designer_section -- RML HW5 else retrieved.readint if retrieved.lastint = -1 then link_section := True elseif retrieved.lastint = -2 then -- RML HW5 designer_section := True -- RML HW5 link_section := False -- RML HW5 else check_next_line end end line_count := line_count + 1 end -- All puzzle files with designer data will have check sums and will exit the loop with end_of_puzzle_data = True -- Some old puzzle files do not contain check sums. These puzzles exit the loop with retrieved.file_readable = False. If we -- encounter an old puzzle, we will write anonomous designer data and a check sum to the file. -- RML HW5 old_puzzle_file := not end_of_puzzle_data retrieved.close end end check_first_line is -- Check first line of `retrieved'. do check_integer( 1, configuration.design_columns, "Illegal width for Room!" ) if retrieved_is_valid then max_c := retrieved.lastint check_integer( 1, configuration.design_rows, "Illegal height for Room!" ) if retrieved_is_valid then max_r := retrieved.lastint end end end check_next_line is -- Check a typical line of `retrieved'. do if not configuration.is_legal_saved_piece_code( retrieved.lastint ) then retrieved_is_valid := False put_file_warning( "Illegal piece code!" ) else check_integer( 1, max_c, "Illegal column number!" ) if retrieved_is_valid and not designer_section then -- RML HW5 check_integer( 1, max_r, "Illegal row number!" ) end end end -- RML HW5 check_designer_section is -- Check to see if there are three non blank lines of designer information left in the file. -- This routine presupposes that the last data in the file is three strings. require proper_entry_into_designer_section: retrieved.last_integer = -2 local index : INTEGER designer_data : STRING good_time : TIME_VALUE do from index := 1 until index > 3 or not retrieved_is_valid loop if not retrieved.end_of_file then retrieved.read_line designer_data := clone( retrieved.last_string ) designer_data.left_adjust designer_data.right_adjust if designer_data.empty then put_file_warning( "Unexpected end of file encountered while reading designer data!" ) retrieved_is_valid := False end if index = 3 then create good_time if not good_time.valid_time_value_string( designer_data ) then put_file_warning( "Improper time value in designer data!" ) retrieved_is_valid := False end end end index := index + 1 end_of_puzzle_data := True end end -- Teleporter links check_links_line is --check a certain "links" line in the file for validity do check_integer ( 1, max_c, "Illegal column number for first portal piece." ) if retrieved_is_valid and not designer_section then -- RML HW5 check_integer( 1, max_r, "Illegal row number for first portal piece." ) if retrieved_is_valid then check_integer ( 1, max_c, "Illegal column number for second portal piece." ) if retrieved_is_valid then check_integer ( 1, max_r, "Illegal row number for second portal piece." ) end end end end check_integer( l, u : INTEGER ; msg : STRING ) is -- Get next integer from `retrieved' if there is one. If not, complain about -- unexpected end of file. If so, complain using `msg' if integer is out of -- bounds. do if not retrieved.file_readable then put_file_warning( "Unexpected end of file encountered!" ) retrieved_is_valid := False else retrieved.readint if retrieved.lastint = -2 then -- RML HW5 designer_section := True -- RML HW5 link_section := False -- RML HW5 elseif retrieved.lastint < l or retrieved.lastint > u then put_file_warning( msg ) retrieved_is_valid := False end end end really_save( p : PUZZLE ) is -- We've got a good file, now really save the current puzzle. local i : INTEGER do file.create_read_write file.putint( p.columns ) file.putchar( ' ' ) file.putint( p.rows ) file.new_line from i := 1 until i > p.num_pieces loop file.putint( p.piece( i ) ) ; file.putchar( ' ' ) file.putint( p.column( i ) ) ; file.putchar( ' ' ) file.putint( p.row( i ) ) ; file.new_line i := i + 1 end -- Teleport if p.num_links > 0 then file.putint ( -1 ) file.new_line from p.scan_links_start until p.scan_links_done loop file.putint (p.scan_links_get.a) ; file.putchar ( ' ' ) file.putint (p.scan_links_get.b) ; file.putchar ( ' ' ) file.putint (p.scan_links_get.c) ; file.putchar ( ' ' ) file.putint (p.scan_links_get.d) ; file.putchar ( ' ' ) file.new_line p.scan_links_next end end -- RML HW5 if p.puzzle_information_available then file.putint ( -2 ); file.new_line file.put_string( p.designer_name ); file.new_line file.put_string( p.puzzle_title ); file.new_line file.put_string( p.final_time ); file.new_line file.put_integer( p.calc_check_sum ) end file.close end -- RML HW5 Mostly rewritten really_retrieve is -- Given that user has selected a valid file, use it to -- initialize the room. local r, c, cd : INTEGER ; r2, c2 : INTEGER -- Too much for one routine? do end_of_puzzle_data := False -- RML HW5 file.open_read file.readint ; max_c := file.lastint file.readint ; max_r := file.lastint create puzzle.make_with_dimensions( line_count - 1, max_c, max_r, finalized ) puzzle.set_title( title ) from until file.end_of_file or end_of_puzzle_data loop file.readint if file.lastint = -1 then file.readint ; r := file.lastint from until file.end_of_file or r = -2 loop file.readint ; c := file.lastint file.readint ; r2 := file.lastint file.readint ; c2 := file.lastint puzzle.put_link (r, c, r2, c2) if not file.end_of_file then file.readint ; r := file.lastint end end end if file.lastint = -2 then file.read_line; puzzle.set_designer_name( file.last_string ) file.read_line; puzzle.set_puzzle_title( file.last_string ) file.read_line; puzzle.set_final_time( file.last_string ) -- The puzzle now has all the information necessary to calculate a check sum file.read_integer -- read the check sum from the file validate_puzzle_check_sum( file.last_integer, puzzle.calc_check_sum ) end_of_puzzle_data := True elseif not file.end_of_file then cd := file.lastint file.readint; c := file.lastint file.readint; r := file.lastint puzzle.put_entry( cd, c, r ) end end file.close -- If the file is an old file, then we will enter anonymous designer data and a check sum to the file. if old_puzzle_file then -- RML HW5 fix_old_file -- RML HW5 end -- RML HW5 end -- RML HW5 fix_old_file is -- Write anonymous designer data and a check sum to the old puzzle file. -- Note that old puzzle files are not validated until read in again which is okay. do file.open_append file.put_integer( -2 ) file.put_string( "%N" + puzzle.unknown_name ) file.put_string( "%N" + puzzle.unknown_title ) file.put_string( "%N" + puzzle.unknown_time + "%N" ) file.put_integer( puzzle.calc_check_sum ) file.close end validate_puzzle_check_sum( num_from_file, num_from_puzzle : INTEGER ) is -- compare the numbers to determine if the file has been externally modified do if num_from_file /= num_from_puzzle then retrieved_is_valid := False put_file_warning( "Puzzle file has been externally modified!" ) end end extension( fn : STRING ) : STRING is -- Return extension of the file name `fn' -- without the "." -- file_name.ext -- 1234567890123 require fn /= Void local i : INTEGER do from i := fn.count until fn.item( i ) = '.' or else i = 0 loop i := i - 1 end if i > 0 then -- RML Result := fn.substring( i, fn.count ) Result := fn.substring( i+1, fn.count ) end end -- lb code puzzle_folder : STRING is -- Returns the puzzle folder name. -- On the first call the folder routine is called. once Result := clone( configuration.puzzle_folder ) end feature retrieve is -- This does preliminary checks before really reading the file. -- For example, it checks that the file name is valid, and creates a file object -- with it, and attaches it to another variable (file) then calls really retrieve -- to actually read the file. do retrieved_is_valid := False if file_name /= Void and then file_name.count > 0 then !!retrieved.make( file_name ) check_retrieved if retrieved_is_valid then file := retrieved really_retrieve end end end -- RML HW4 retrieve_cancelled : BOOLEAN is -- Has the user cancelled the retrieval of a file? do Result := file_name = Void end finalized : BOOLEAN is -- Is the current file name that for a finalized puzzle? require file_name /= Void and then file_name.count > 0 local s, ext : STRING do s := extension( file_name ) s.to_upper ext := clone( solve_extension ) ext.to_upper Result := s.is_equal( ext ) end puzzle : PUZZLE -- Most recently retrieved puzzle. file : PLAIN_TEXT_FILE -- file where current puzzle is stored, Void if the puzzle was -- neither retrieved from a file nor saved to one. title : STRING is -- Title of puzzle based on the file name. require file /= Void local i : INTEGER do Result := clone( file.name ) from i := Result.index_of( '\', 1 ) until i = 0 -- Remove MS directory loop Result.replace_substring( "", 1, i ) i := Result.index_of( '\', 1 ) end from i := Result.index_of( '/', 1 ) until i = 0 -- Remove Unix directory loop Result.replace_substring( "", 1, i ) i := Result.index_of( '/', 1 ) end i := Result.index_of( '.', 1 ) -- Remove extension if i > 0 then Result.replace_substring( "", i, Result.count ) end end save_as( p : PUZZLE ) is -- Save to a user selected file. require p /= Void local saved : PLAIN_TEXT_FILE do -- lb code file_name := select_file.get_file_name_for_saving -- end lb code if file_name /= Void and then file_name.count > 0 then if finalized then put_file_warning( "The extension, " + solve_extension + ", is reserved for finalized puzzles." ) else !!saved.make( file_name ) if not saved.exists then file := saved really_save( p ) elseif not saved.is_plain then put_file_warning( "Not a plain file!" ) elseif ok_to_overwrite then file := saved really_save( p ) end end end end finalize( p : PUZZLE ) is -- Save to a user selected file. local saved, old_file : PLAIN_TEXT_FILE old_name : STRING acceptable_escape : BOOLEAN -- RML HW5 do from until acceptable_escape loop -- RML HW5 -- lb code file_name := select_file.get_file_name_for_finalizing -- end lb code if file_name /= Void and then file_name.count > 0 then !!saved.make( file_name ) if not saved.exists then old_file := file old_name := clone( file_name ) file := saved really_save( p ) file := old_file file_name := clone( old_name ) acceptable_escape := True -- RML HW5 else put_file_warning( "File already exists!%NCannot overwrite finalized puzzle." ) end else -- Cancel button was selected -- RML HW5 acceptable_escape := True -- RML HW5 end end end save( p : PUZZLE ) is -- Save to the current file if there is one. Otherwise call save_as. do if file /= Void then really_save( p ) else save_as( p ) end end retrieved_is_valid : BOOLEAN retrieve_puzzle is -- Attempt to retrieve completed puzzle file selected by user. do retrieved_is_valid := False -- lb code -- was -- get_completed_puzzle_file_name file_name := select_file.get_completed_puzzle_file_name -- lb code end retrieve end -- lb code set_name_and_retrieve_puzzle( puzzle_name : STRING ) is -- Attempt to retrieve completed puzzle file who's name was passed in. do retrieved_is_valid := False -- lb code -- was -- get_completed_puzzle_file_name file_name := puzzle_name -- lb code end retrieve end -- lb code end retrieve_puzzle_in_progress is -- Attempt to retrieve puzzle file selected by user. do retrieved_is_valid := False -- lb code file_name := select_file.get_puzzle_in_progress_file_name -- lb code end retrieve if retrieved_is_valid then if finalized then -- detach puzzle from file. -- RML NOV 23, 2000 file_name := Void -- forces a Save As for Save for a finalized puzzle -- read into the Design Room file := Void end end end clear is -- Clear my file -- RML and set all attributes to initial values do file := Void file_name := Void -- RML HW4 retrieved := Void -- RML HW4 puzzle := Void -- RML HW4 line_count := 0 -- RML HW4 end -- Need to clean up "save", "save as", and "finalize". -- Looks like this is the first major item on the agenda for the Summer. end