- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我用 Haskell 编写了一个简单的井字游戏程序。它在命令行上运行,具有单人和两人模式,并在与它对战时实现极小极大算法。
我习惯于用面向对象语言编写正确的代码,但 Haskell 对我来说是新的。这段代码工作得相当好,但似乎很难阅读(即使对我来说也是如此!)。关于如何使这段代码更加...Haskelian 有什么建议吗?
import Data.List
import Data.Char
import Data.Maybe
import Control.Monad
data Square = A | B | C | D | E | F | G | H | I | X | O deriving (Read, Eq, Ord)
instance Show Square where
show A = "a"
show B = "b"
show C = "c"
show D = "d"
show E = "e"
show F = "f"
show G = "g"
show H = "h"
show I = "i"
show X = "X"
show O = "O"
type Row = [Square]
type Board = [Row]
data Player = PX | PO deriving (Read, Eq)
instance Show Player where
show PX = "Player X"
show PO = "Player O"
data Result = XWin | Tie | OWin deriving (Read, Show, Eq, Ord)
main :: IO ()
main = do
putStrLn "Let's play some tic tac toe!!!"
putStrLn "Yeeeaaaaaahh!!!"
gameSelect
gameSelect :: IO ()
gameSelect = do
putStrLn "Who gonna play, one playa or two??? (Enter 1 or 2)"
gameMode <- getLine
case gameMode of "1" -> onePlayerMode
"2" -> twoPlayerMode
gameMode -> gameSelect
where onePlayerMode = do
putStrLn "One playa"
putStrLn "Cool! Get ready to play...AGAINST MY INVINCIBLE TIC TAC TOE AI!!!!! HAHAHAHA!!!"
gameLoop 1 emptyBoard PX
twoPlayerMode = do
putStrLn "Two players"
gameLoop 2 emptyBoard PX
emptyBoard = [[A,B,C],[D,E,F],[G,H,I]]
gameLoop :: Int -> Board -> Player -> IO ()
gameLoop noOfPlayers board player = do
case detectWin board of Just XWin -> endgame board XWin
Just OWin -> endgame board OWin
Just Tie -> endgame board Tie
Nothing -> if noOfPlayers == 1
then if player == PX
then enterMove 1 board player
else enterBestMove board PO
else enterMove 2 board player
enterMove :: Int -> Board -> Player -> IO ()
enterMove noOfPlayers board player = do
displayBoard board
if noOfPlayers == 1
then do putStrLn ("Make your move. (A-I)")
else do putStrLn (show player ++ ", it's your turn. (A-I)")
move <- getLine
print move
if not $ move `elem` ["a","b","c","d","e","f","g","h","i"]
then do
putStrLn $ move ++ " is not a move, doofus"
gameLoop noOfPlayers board player
else if (read (map toUpper move) :: Square) `elem` [ sq | sq <- concat board]
then do
gameLoop noOfPlayers (newBoard (read (map toUpper move) :: Square) player board) (if player == PX then PO else PX)
else do
putStrLn "That square is already occupied"
gameLoop noOfPlayers board player
enterBestMove :: Board -> Player -> IO ()
enterBestMove board player = gameLoop 1 (newBoard bestmove player board) PX
where bestmove = fst $ findBestMove PO board
findBestMove :: Player -> Board -> (Square, Result)
findBestMove player board
| player == PO = findMax results
| player == PX = findMin results
where findMin = foldl1 (\ acc x -> if snd x < snd acc then x else acc)
findMax = foldl1 (\ acc x -> if snd x > snd acc then x else acc)
results = [ (sq, getResult b) | (sq, b) <- boards player board ]
getResult b = if detectWin b == Nothing
then snd (findBestMove (if player == PX then PO else PX) b)
else fromJust $ detectWin b
boards :: Player -> Board -> [(Square, Board)]
boards player board = [(sq, newBoard sq player board) | sq <- concat board, sq /= X, sq /=O]
displayBoard :: Board -> IO ()
displayBoard board = do
mapM_ print board
newBoard :: Square -> Player -> Board -> Board
newBoard move player board = [ [if sq == move then mark else sq | sq <- row] | row <- board]
where mark = if player == PX then X else O
detectWin :: Board -> (Maybe Result)
detectWin board
| [X,X,X] `elem` board ++ transpose board = Just XWin
| [X,X,X] `elem` [diagonal1 board, diagonal2 board] = Just XWin
| [O,O,O] `elem` board ++ transpose board = Just OWin
| [O,O,O] `elem` [diagonal1 board, diagonal2 board] = Just OWin
| [X,X,X,X,X,O,O,O,O] == (sort $ concat board) = Just Tie
| otherwise = Nothing
where
diagonal1 :: Board -> [Square]
diagonal1 bs = bs!!0!!0 : bs!!1!!1 : bs!!2!!2 : []
diagonal2 :: Board -> [Square]
diagonal2 bs = bs!!0!!2 : bs!!1!!1 : bs!!2!!0 : []
endgame :: Board -> Result -> IO ()
endgame board result = do
displayBoard board
if result `elem` [XWin, OWin]
then
let player = if result == XWin then PX else PO
in do
putStrLn ("The game is over, and " ++ show player ++ " wins!")
putStrLn ((if player == PX then show PO else show PX) ++ " is a loser lol")
else do
putStrLn "The game is a tie"
putStrLn "You are both losers! Ugh!"
putStrLn "Want to play again? (y/n)"
again <- getLine
if again `elem` ["y", "Y", "yes", "Yes", "YES"]
then gameSelect
else do
putStrLn "Goodbye"
<小时/>
编辑:特别感谢@Chi 和@Caridorc,我做了以下更改。进一步的建议也将被考虑和更新
import Data.List
import Data.Char
import Data.Maybe
import Control.Monad
data Square = A | B | C | D | E | F | G | H | I | X | O deriving (Read, Eq, Ord)
instance Show Square where
show A = "a"
show B = "b"
show C = "c"
show D = "d"
show E = "e"
show F = "f"
show G = "g"
show H = "h"
show I = "i"
show X = "X"
show O = "O"
type Row = [Square]
type Board = [Row]
data Player = PX | PO deriving (Read, Eq)
instance Show Player where
show PX = "Player X"
show PO = "Player O"
data Result = XWin | Tie | OWin deriving (Read, Show, Eq, Ord)
main :: IO ()
main = do
putStrLn "Let's play some tic tac toe!!!"
putStrLn "Yeeeaaaaaahh!!!"
gameSelect
gameSelect :: IO ()
gameSelect = do
putStrLn "Who gonna play, one playa or two??? (Enter 1 or 2)"
gameMode <- getLine
case gameMode of
"1" -> onePlayerMode
"2" -> twoPlayerMode
_ -> gameSelect
where onePlayerMode = do
putStrLn "One playa"
putStrLn "Cool! Get ready to play...AGAINST MY INVINCIBLE TIC TAC TOE AI!!!!! HAHAHAHA!!!"
gameLoop 1 emptyBoard PX
twoPlayerMode = do
putStrLn "Two players"
gameLoop 2 emptyBoard PX
emptyBoard = [[A,B,C],[D,E,F],[G,H,I]]
displayBoard :: Board -> IO ()
displayBoard board = do
mapM_ print board
otherPlayer :: Player -> Player
otherPlayer PX = PO
otherPlayer PO = PX
gameLoop :: Int -> Board -> Player -> IO ()
gameLoop noOfPlayers board player = do
case detectWin board of
Just res -> endgame board res
Nothing -> case noOfPlayers of
1 -> case player of
PX -> enterMove 1 board player
PO -> enterBestMove board PO
2 -> enterMove 2 board player
enterMove :: Int -> Board -> Player -> IO ()
enterMove noOfPlayers board player = do
displayBoard board
case noOfPlayers of
1 -> do putStrLn ("Make your move. (A-I)")
2 -> do putStrLn (show player ++ ", it's your turn. (A-I)")
move <- getLine
print move
if not $ move `elem` ["a","b","c","d","e","f","g","h","i"] then do
putStrLn $ move ++ " is not a move, doofus"
gameLoop noOfPlayers board player
else if (read (map toUpper move) :: Square) `elem` (concat board) then do
gameLoop noOfPlayers (newBoard (read (map toUpper move) :: Square) player board) (otherPlayer player)
else do
putStrLn "That square is already occupied"
gameLoop noOfPlayers board player
enterBestMove :: Board -> Player -> IO ()
enterBestMove board player = gameLoop 1 (newBoard bestmove player board) PX
where bestmove = fst $ findBestMove PO board
findBestMove :: Player -> Board -> (Square, Result) -- minimax algorithm
findBestMove player board
| player == PO = findMax results
| player == PX = findMin results
where findMin = foldl1 (\ acc x -> if snd x < snd acc then x else acc)
findMax = foldl1 (\ acc x -> if snd x > snd acc then x else acc)
results = [ (sq, getResult b) | (sq, b) <- boards player board ]
getResult b = case detectWin b of
Nothing -> snd (findBestMove (otherPlayer player) b)
Just x -> x
boards :: Player -> Board -> [(Square, Board)]
boards player board = [(sq, newBoard sq player board) | sq <- concat board, sq /= X, sq /=O]
newBoard :: Square -> Player -> Board -> Board
newBoard move player board = [ [if sq == move then mark else sq | sq <- row] | row <- board]
where mark = if player == PX then X else O
detectWin :: Board -> (Maybe Result)
detectWin board
| [X,X,X] `elem` (triplets board) = Just XWin
| [O,O,O] `elem` (triplets board) = Just OWin
| [X,X,X,X,X,O,O,O,O] == (sort $ concat board) = Just Tie
| otherwise = Nothing
triplets :: Board -> [[Square]]
triplets board = board ++ transpose board ++ [diagonal1] ++ [diagonal2]
where
flat = concat board
diagonal1 = [flat !! 0, flat !! 4, flat !! 8]
diagonal2 = [flat !! 2, flat !! 4, flat !! 6]
endgame :: Board -> Result -> IO ()
endgame board result = do
displayBoard board
putStrLn $ endGameMessage result
putStrLn "Want to play again? (y/n)"
again <- getLine
if again `elem` ["y", "Y", "yes", "Yes", "YES"]
then gameSelect
else do
putStrLn "Goodbye"
endGameMessage :: Result -> String
endGameMessage result
| result `elem` [XWin, OWin] = winnerNotice ++ loserNotice
| otherwise = "The game is a tie\n" ++ "You are both losers! Ugh!"
where
winner = case result of
XWin -> PX
OWin -> PO
winnerNotice = "The game is over, and " ++ show winner ++ " wins!\n"
loserNotice = (show $ otherPlayer winner) ++ " is a loser lol"
最佳答案
代码风格通常是个人喜好的问题,在 Haskell 中可以说比其他具有“标准”风格指南的语言更重要。尽管如此,这里还是有一些随机的建议。
<小时/>不要过度缩进case
:只需使用另一行
case gameMode of "1" -> onePlayerMode
"2" -> twoPlayerMode
gameMode -> gameSelect
对比
case gameMode of
"1" -> onePlayerMode
"2" -> twoPlayerMode
gameMode -> gameSelect
甚至
case gameMode of
"1" -> onePlayerMode
"2" -> twoPlayerMode
_ -> gameSelect
<小时/>
case
通常优先于 if .. == Constructor
:
if player == PX
then enterMove 1 board player
else enterBestMove board PO
对比
case player of
PX -> enterMove 1 board player
PY -> enterBestMove board PO
<小时/>
我强烈建议不要使用像 fromJust
这样的部分函数,因为如果您忘记事先检查 Nothing
,它们可能会导致您的程序崩溃。存在更安全的替代方案,它们永远不会导致此类崩溃——减轻程序员的负担。
if detectWin b == Nothing
then snd (findBestMove (if player == PX then PO else PX) b)
else fromJust $ detectWin b
对比
case detectWin b of
Nothing -> snd $ findBestMove (if player == PX then PO else PX) b
Just x -> x
或
fromMaybe (snd $ findBestMove (if player == PX then PO else PX) b)
$ detectWin b
<小时/>
尝试分解常用函数。例如
nextPlayer PX = PO
nextPlayer PO = PX
可以替代使用
if player == PX then PO else PX
<小时/>
当只有一条语句时,不需要 do
:
if noOfPlayers == 1
then do putStrLn ("Make your move. (A-I)") -- no need for parentheses here
else do putStrLn (show player ++ ", it's your turn. (A-I)")
<小时/>
既然您在标题中提到了 where
,那么我要声明的是,总的来说,我对 where
的感受很复杂。我知道我经常倾向于避免 where
而支持 let
,但这种感觉并不与许多其他 Haskeller 一样,所以要小心一点。
就我个人而言,我倾向于将 where
的使用限制为单行:
foo = f x y
where x = ...
y = ...
特别是在可能跨越多行的do
block 中,我更喜欢let
s:
foo = do
line
line using x -- what is x ??!?
line
...
line
where x = ... -- ah, here it is
对比
foo = do
line
let x = ...
line using x
line
...
line
但是,请随意采用您认为更具可读性的样式。
<小时/>另外,不要忘记添加一些评论,正如@mawalker 指出的那样。有些定义是显而易见的,不需要任何解释。其他人可以从几行解释目的中受益。
关于haskell - Where 子句的最佳实践,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34663638/
本文分享自华为云社区《大模型LLM之分布式训练》,作者: 码上开花_Lancer。 随着语言模型参数量和所需训练数据量的急速增长,单个机器上有限的资源已无法满足大语言模型训练的要求。需要设计分布式训
本文分享自华为云社区《五大基础算法--动态规划法》,作者: 大金(内蒙的)。 一、基本概念 动态规划法,和分治法极其相似。区别就是,在求解子问题时,会保存该子问题的解,后面的子问题求解时,可以直接拿来
pip install scp pip install pexpect 测试代码: import os import stat import paramiko # 用于调用scp命令 def s
我目前正在实现“ token ”REST 服务。 token 只是一个字符串,由一些参数构建而成,然后经过哈希处理并在一定时间后过期。 我想在我的 REST 服务中有一个可以验证 token 的端点,
打开软删除后,我在客户端上添加一条记录,推送,删除添加的记录推送,然后尝试使用与初始记录相同的主键添加新记录(然后推送),我得到一个异常(exception)。 EntityDomainManager
打开软删除后,我在客户端上添加一条记录,推送,删除添加的记录推送,然后尝试使用与初始记录相同的主键添加新记录(然后推送),我得到一个异常(exception)。 EntityDomainManager
我有一个应用程序,每 x 秒接收一次天气信息。我想将此数据保存到 XML 文件中。 我应该为每个天气通知创建一个新的 XML 文件,还是将每个通知附加到同一个 XML 文件中?我不确定 XML 标准的
我猜我们大多数人都必须在某个时候处理这个问题,所以我想我会问这个问题。 当您的 BLL 中有很多集合并且您发现自己一遍又一遍地编写相同的旧内联(匿名)谓词时,显然有必要进行封装,但实现封装的最佳方
我有一些 c# 代码已经运行了一段时间了..我不得不说,虽然我了解 OO 原则的基础知识,但显然有不止一种方法可以给猫剥皮(尽管我讨厌那个短语!)。 因此,我有一个基本抽象类作为基本数据服务类,如下所
我设计了一个 SQL 数据库系统(使用 Postgre),我有一个问题,即创建一个关系/引用的常见做法是什么,这种关系/引用即使在引用的对象被删除时也能持续存在。 比如有一个UserORM,还有Act
我们的目标是搜索用户输入的字符串并计算在其中找到多少元音。不幸的是我被困在这里,有什么帮助吗? def numVowels(s): vowels= "AEIOUaeiou" if s
我有一个适用于我的“items”int 数组的旋转函数。下面的代码完成了它,除了我不必要地传输值。我正在努力实现“就地”轮换。我的意思是 ptrs 会递增或递减,而不是从数组中获取值。我需要通过这种方
我有一个 json 存储在我的应用程序文档文件夹中,我需要在我的所有 View 中使用它。我正在加载 json 并将其添加到每个 View 中的 NSMutableArray。但现在我了解到,我可以将
我用 C++ 开始了一个项目。这种语言的内存管理对我来说是新的。 我过去常常使用 new () 创建对象,然后传递指针,虽然它可以工作,但调试起来很痛苦,人们看到代码时会用有趣的眼神看着我。我为它没有
已结束。 这个问题是 off-topic .它目前不接受答案。 想要改进这个问题? Update the question所以它是on-topic堆栈溢出。 关闭 10 年前。 Improve thi
保持类松散耦合是编写易于理解、修改和调试的代码的一个重要方面——我明白这一点。然而,作为一个新手,几乎任何时候我都会超越我所苦苦挣扎的最简单的例子。 我或多或少地了解如何将字符串、整数和简单数据类型封
我发现我需要编写大量重复代码,因为我无法从其他 Controller 调用函数。例如,这里新闻提要内容在我的代码中重复,我对一个 Controller 做一些特定的事情,然后需要像这样加载我的新闻提要
假设需要一种数字数据类型,其允许值在指定范围内。更具体地说,假设要定义一个整数类型,其最小值为0,最大值为5000。这种情况在很多情况下都会出现,例如在对数据库数据类型,XSD数据类型进行建模时。 在
假设我想循环整个数组来访问每个元素。使用 for 循环、for...in 循环或 for...of 循环是 JavaScript 开发人员的标准做法吗? 例如: var myArray = ["app
我有一个旧的 SL4/ria 应用程序,我希望用 Breeze 取代它。我有一个关于内存使用和缓存的问题。我的应用程序加载工作列表(一个典型的用户可以访问大约 1,000 个这些工作)。此外,还有很多
我是一名优秀的程序员,十分优秀!