{----------------------------------------------------------------------------------------------------- engineSpec.txt ~ rev. 2011.08.11 XGB Web and Software Design ~ www.xgbdesign.com This is a program to calculate the displacement of a reciprocating-piston engine, given the number of cylinders, bore, and stroke. It also allows users to "design" an engine by specifying the number of cylinders, the desired displacement, and desired bore/stroke ratio. The program then returns close approximations of these data plus the bore and stroke that fit these specifications. The program works with both metric and English units of measurement. PLEASE NOTE: This source code file has been saved with the ".txt" extension to obviate browser difficulties, particularly with Internet Explorer. To compile and run this code, just copy and paste this text into a new document, but save the file with the extension ".hs" PLEASE NOTE: This code is licensed under the terms of the GNU General Public License, Version 3 (http://www.gnu.org/copyleft/gpl.html#header). You are free to use and modify this source code, but if you do, kindly cite XGB Web and Software Design, whether in a link from your website, or or in your own source code. Thanks! -----------------------------------------------------------------------------------------------------} -- "Impure" IO functions: --------------------------------------------------------------------------- main :: IO () main = do selection <- getMenuSelection dispatch selection promptToRepeat getMenuSelection :: IO String getMenuSelection = do putStrLn "\nSelect one:" putStrLn " (a) Specify engine parameters using metric units" putStrLn " (b) Specify engine parameters using English units" putStrLn " (c) Calculate engine displacement using metric units" putStrLn " (d) Calculate engine displacement using English units" getLine dispatch :: String -> IO () dispatch e | e `elem` ["A","a","B","b"] = parametersIO metric | e `elem` ["C","c","D","d"] = displacementIO metric | otherwise = do putStrLn "\nINVALID ENTRY: Your entry must be in the range \"A\" through \"D\" or \"a\" through \"d\"." main where metric = e `elem` ["A","a","C","c"] displacementIO :: Bool -> IO () displacementIO metric = do -- Inputs: let units = if metric then "millimeters" else "inches" putStrLn "\nEnter number of cylinders, and bore and" putStrLn $ "stroke in " ++ units ++ ", separated by spaces:" specs <- getLine -- Output: let s = fmap read . words $ specs putStrLn "\n ENGINE DISPLACEMENT:" putStrLn $ results (s !! 0) (s !! 1) (s !! 2) metric parametersIO :: Bool -> IO () parametersIO metric = do -- Inputs: let units = if metric then "centimeters" else "inches" putStrLn $ "\nEnter number of cylinders, displacement in cubic " ++ units ++ "," putStrLn "and bore/stroke ratio, separated by spaces:" specs <- getLine -- Output: let a = fmap read . words $ specs p = if metric then 1 else 2 c = (a !! 0) r = (a !! 2) b = precision p $ bore c (a !! 1) r metric s = precision p $ b / r putStrLn "\n ENGINE PARAMETERS:" putStrLn $ results c b s metric promptToRepeat :: IO () promptToRepeat = do putStrLn "\nWould you like to examine another engine? (y/_)" reply <- getLine if reply `elem` ["Y","y","Yes","yes"] then main else putStrLn "Exiting program...\n" -- "Pure" functions: ------------------------------------------------------------------------- -- If the displacement is rendered in metric units, then raw displacement -- is converted from cubic millimeters to cubic centimeters: displacement :: (Floating a) => a -> a -> a -> Bool -> a displacement cyls bore stroke metric = if metric then raw / 1000 else raw where raw = pi * bore * bore * stroke * cyls / 4 -- If the bore is rendered in metric units, then raw bore is converted -- from centimeters to millimeters: bore :: (Floating a) => a -> a -> a -> Bool -> a bore cyls disp ratio metric = if metric then raw * 10 else raw where raw = (4 * disp * ratio / pi / cyls) ** (1 / 3) -- This function takes as arguments the number of decimal places desired, -- and the number to be "rounded," and returns the rounded number: precision :: (Integral b, RealFrac a, Floating a) => b -> a -> a precision places n = fromIntegral (floor (n * factor + 0.5)) / factor where factor = 10 ^ places results :: (RealFrac a, Floating a) => a -> a -> a -> Bool -> String results cyls bore stroke metric = concat [ "\n No. cylinders: ", show . floor $ cyls, "\n Displacement: ", volFormat (displacement cyls bore stroke metric) metric, "\n Bore: ", lenFormat bore metric, "\n Stroke: ", lenFormat stroke metric, "\n B/S Ratio: ", fix 3 $ bore / stroke] where volFormat entry metric | metric = concat [fix 3 entry, " cc. (", fix 3 (entry / 16.387064), " cu.in.)"] | otherwise = concat [fix 3 entry, " cu.in. (", fix 3 (entry * 16.387064), " cc.)"] lenFormat entry metric | metric = concat [fix 1 entry, " mm. (", fix 2 (entry / 25.4), " in.)"] | otherwise = concat [fix 4 entry, " in. (", fix 1 (entry * 25.4), " mm.)"] fix p = show . precision p