Haskell IO,在同一行获取两个输入并进行验证
Haskell IO, getting two input in the same line and doing validation
module TicTacToe (tictactoe) where
import Control.Applicative
import Control.Monad
import Control.Monad.State
import Data.Char
import Data.List
import Text.Printf
tictactoe :: IO ()
tictactoe = do
let grid = [' ',' ',' ',' ',' ',' ',' ',' ',' ']
let count = 0
output_grid grid count
output_grid :: String -> Int -> IO()
output_grid grid count = do
putStr ".---.---.---.\n"
printf "| %c | %c | %c |\n" (grid !! 0) (grid !! 1) (grid !! 2)
putStr ".---.---.---.\n"
printf "| %c | %c | %c |\n" (grid !! 3) (grid !! 4) (grid !! 5)
putStr ".---.---.---.\n"
printf "| %c | %c | %c |\n" (grid !! 6) (grid !! 7) (grid !! 8)
putStr ".---.---.---.\n" -- output grid
if count `mod` 2 == 0
then putStr "O MOVE\n"
else putStr "X MOVE\n" -- tell player which to move
if count `mod` 2 == 0
then player_input grid 'O' count
else player_input grid 'X' count
player_input :: String -> Char -> Int -> IO()
player_input grid sym count = do
inp <- getLine
let x = (read (takeWhile (/= ' ') inp) :: Int)
let y = (read (drop 1 (dropWhile (/= ' ') inp)) :: Int)
if (x < 1) || (x > 3) || (y < 1) || (y > 3)
then putStr "INVALID POSITION \n"
else return ()
if (x < 1) || (x > 3) || (y < 1) || (y > 3)
then player_input grid sym count
else return ()
let target = (x - 1) * 3 + (y - 1)
if (grid !! target /= ' ')
then putStr "INVALID POSITION \n"
else return ()
if (grid !! target /= ' ')
then player_input grid sym count
else return ()
let new_grid = (take target grid) ++ [sym] ++ (drop (target + 1) grid)
if (check_win new_grid sym)
then output_terminate new_grid sym
else if count == 8
then output_terminate new_grid 'D'
else output_grid new_grid (count + 1)
check_win :: String -> Char -> Bool
check_win grid sym = do
if (grid !! 0 == sym) && (grid !! 1 == sym) && (grid !! 2 == sym)
then True
else if (grid !! 3 == sym) && (grid !! 4 == sym) && (grid !! 5 == sym)
then True
else if (grid !! 6 == sym) && (grid !! 7 == sym) && (grid !! 8 == sym)
then True
else if (grid !! 0 == sym) && (grid !! 3 == sym) && (grid !! 6 == sym)
then True
else if (grid !! 1 == sym) && (grid !! 4 == sym) && (grid !! 7 == sym)
then True
else if (grid !! 2 == sym) && (grid !! 5 == sym) && (grid !! 8 == sym)
then True
else if (grid !! 0 == sym) && (grid !! 4 == sym) && (grid !! 8 == sym)
then True
else if (grid !! 2 == sym) && (grid !! 4 == sym) && (grid !! 6 == sym)
then True
else False
output_terminate :: String -> Char -> IO()
output_terminate grid winner = do
putStr ".---.---.---.\n"
printf "| %c | %c | %c |\n" (grid !! 0) (grid !! 1) (grid !! 2)
putStr ".---.---.---.\n"
printf "| %c | %c | %c |\n" (grid !! 3) (grid !! 4) (grid !! 5)
putStr ".---.---.---.\n"
printf "| %c | %c | %c |\n" (grid !! 6) (grid !! 7) (grid !! 8)
putStr ".---.---.---.\n"
if winner == 'D'
then putStr "DRAW \n"
else printf "%c WINS \n" winner
我是 Haskell 的初学者,我正在开发一款小型 TicTacToe 游戏。这是我用来让玩家输入符号坐标的函数,比如 2 2 (这意味着符号放置在中心),他们想要放入.我想在上面添加一些验证功能。到目前为止,它只能处理超出范围的输入,如 12 2,并避免覆盖已占用的网格。但我想做更多。例如,2(只有 1 个输入),1 2 xy(xy 不应该在这里),以及 abcde(随机输入没有意义)。我想让程序也能处理这些无效输入。
作为一般性建议,如果我们将验证与用户交互分开,通常会更简洁。我们可以为验证结果使用自定义类型。
data Validation
= CorrectMove Int Int -- correct input
| OutOfBounds -- off the board
| NonEmpty -- can not play on the same cell twice
| ParseError -- input is not two integers
使用上面的内容,我们可以定义一个自定义函数来进行验证。 (下面,为了简单起见,我利用了 Text.Read.readMaybe
,但是 Prelude
中的 reads
也可以在稍作改动后使用。)
import Text.Read (readMaybe)
validate
:: String -- ^ the user input
-> String -- ^ the grid (should be its own type)
-> Validation
validate input grid = case words input of
[xStr, yStr] -> -- two words, let's parse them
case (readMaybe xStr, readMaybe yStr) of
(Just x, Just y)
| x < 1 || x > 3 || y < 1 || y > 3 -> OutOfBounds
| cell grid x y /= ' ' -> NotEmpty
| otherwise -> CorrectMove x y
_ -> ParseError -- two words, but not two integers
_ -> ParseError -- not two words
以上利用了我们在下面定义的自定义网格访问函数:
-- coordinates must be in-bounds
cell :: String -> Int -> Int -> Char
cell grid x y = grid !! ((x - 1) * 3 + y - 1)
之后,我们可以在执行用户交互时利用我们的验证:
player_input :: String -> Char -> Int -> IO()
player_input grid sym count = do
inp <- getLine
case validate inp grid of
ParseError -> putStrLn "Invalid input!"
NonEmpty -> putStrLn "Cell not empty!"
OutOfBounds -> putStrLn "Invalid coordinates!"
CorrectMove x y -> do
putStrLn $ "ValidMove in cell " ++ show (x,y)
-- here we can use x and y, knowing they are valid
-- and update the game state
module TicTacToe (tictactoe) where
import Control.Applicative
import Control.Monad
import Control.Monad.State
import Data.Char
import Data.List
import Text.Printf
tictactoe :: IO ()
tictactoe = do
let grid = [' ',' ',' ',' ',' ',' ',' ',' ',' ']
let count = 0
output_grid grid count
output_grid :: String -> Int -> IO()
output_grid grid count = do
putStr ".---.---.---.\n"
printf "| %c | %c | %c |\n" (grid !! 0) (grid !! 1) (grid !! 2)
putStr ".---.---.---.\n"
printf "| %c | %c | %c |\n" (grid !! 3) (grid !! 4) (grid !! 5)
putStr ".---.---.---.\n"
printf "| %c | %c | %c |\n" (grid !! 6) (grid !! 7) (grid !! 8)
putStr ".---.---.---.\n" -- output grid
if count `mod` 2 == 0
then putStr "O MOVE\n"
else putStr "X MOVE\n" -- tell player which to move
if count `mod` 2 == 0
then player_input grid 'O' count
else player_input grid 'X' count
player_input :: String -> Char -> Int -> IO()
player_input grid sym count = do
inp <- getLine
let x = (read (takeWhile (/= ' ') inp) :: Int)
let y = (read (drop 1 (dropWhile (/= ' ') inp)) :: Int)
if (x < 1) || (x > 3) || (y < 1) || (y > 3)
then putStr "INVALID POSITION \n"
else return ()
if (x < 1) || (x > 3) || (y < 1) || (y > 3)
then player_input grid sym count
else return ()
let target = (x - 1) * 3 + (y - 1)
if (grid !! target /= ' ')
then putStr "INVALID POSITION \n"
else return ()
if (grid !! target /= ' ')
then player_input grid sym count
else return ()
let new_grid = (take target grid) ++ [sym] ++ (drop (target + 1) grid)
if (check_win new_grid sym)
then output_terminate new_grid sym
else if count == 8
then output_terminate new_grid 'D'
else output_grid new_grid (count + 1)
check_win :: String -> Char -> Bool
check_win grid sym = do
if (grid !! 0 == sym) && (grid !! 1 == sym) && (grid !! 2 == sym)
then True
else if (grid !! 3 == sym) && (grid !! 4 == sym) && (grid !! 5 == sym)
then True
else if (grid !! 6 == sym) && (grid !! 7 == sym) && (grid !! 8 == sym)
then True
else if (grid !! 0 == sym) && (grid !! 3 == sym) && (grid !! 6 == sym)
then True
else if (grid !! 1 == sym) && (grid !! 4 == sym) && (grid !! 7 == sym)
then True
else if (grid !! 2 == sym) && (grid !! 5 == sym) && (grid !! 8 == sym)
then True
else if (grid !! 0 == sym) && (grid !! 4 == sym) && (grid !! 8 == sym)
then True
else if (grid !! 2 == sym) && (grid !! 4 == sym) && (grid !! 6 == sym)
then True
else False
output_terminate :: String -> Char -> IO()
output_terminate grid winner = do
putStr ".---.---.---.\n"
printf "| %c | %c | %c |\n" (grid !! 0) (grid !! 1) (grid !! 2)
putStr ".---.---.---.\n"
printf "| %c | %c | %c |\n" (grid !! 3) (grid !! 4) (grid !! 5)
putStr ".---.---.---.\n"
printf "| %c | %c | %c |\n" (grid !! 6) (grid !! 7) (grid !! 8)
putStr ".---.---.---.\n"
if winner == 'D'
then putStr "DRAW \n"
else printf "%c WINS \n" winner
我是 Haskell 的初学者,我正在开发一款小型 TicTacToe 游戏。这是我用来让玩家输入符号坐标的函数,比如 2 2 (这意味着符号放置在中心),他们想要放入.我想在上面添加一些验证功能。到目前为止,它只能处理超出范围的输入,如 12 2,并避免覆盖已占用的网格。但我想做更多。例如,2(只有 1 个输入),1 2 xy(xy 不应该在这里),以及 abcde(随机输入没有意义)。我想让程序也能处理这些无效输入。
作为一般性建议,如果我们将验证与用户交互分开,通常会更简洁。我们可以为验证结果使用自定义类型。
data Validation
= CorrectMove Int Int -- correct input
| OutOfBounds -- off the board
| NonEmpty -- can not play on the same cell twice
| ParseError -- input is not two integers
使用上面的内容,我们可以定义一个自定义函数来进行验证。 (下面,为了简单起见,我利用了 Text.Read.readMaybe
,但是 Prelude
中的 reads
也可以在稍作改动后使用。)
import Text.Read (readMaybe)
validate
:: String -- ^ the user input
-> String -- ^ the grid (should be its own type)
-> Validation
validate input grid = case words input of
[xStr, yStr] -> -- two words, let's parse them
case (readMaybe xStr, readMaybe yStr) of
(Just x, Just y)
| x < 1 || x > 3 || y < 1 || y > 3 -> OutOfBounds
| cell grid x y /= ' ' -> NotEmpty
| otherwise -> CorrectMove x y
_ -> ParseError -- two words, but not two integers
_ -> ParseError -- not two words
以上利用了我们在下面定义的自定义网格访问函数:
-- coordinates must be in-bounds
cell :: String -> Int -> Int -> Char
cell grid x y = grid !! ((x - 1) * 3 + y - 1)
之后,我们可以在执行用户交互时利用我们的验证:
player_input :: String -> Char -> Int -> IO()
player_input grid sym count = do
inp <- getLine
case validate inp grid of
ParseError -> putStrLn "Invalid input!"
NonEmpty -> putStrLn "Cell not empty!"
OutOfBounds -> putStrLn "Invalid coordinates!"
CorrectMove x y -> do
putStrLn $ "ValidMove in cell " ++ show (x,y)
-- here we can use x and y, knowing they are valid
-- and update the game state