嗨,我是Haskell的新手,我一直在做一个简单的井字游戏。我想知道是否有一种方法可以将我的自定义数据列表转换为字符列表。
module TicTacToe where
import Data.Char
import Data.List
import Data.List.Split
import System.Console.ANSI
type Board = [[Space]]
type Space = Maybe Player
data Player = PlayerX | PlayerO deriving (Eq, Show)
data State = Running | GameOver (Maybe Player) deriving (Eq, Show)
data Game = Game { gameBoard :: Board
, gamePlayer :: Player
, gameState :: State
} deriving (Eq, Show)
initialGame = Game { gameBoard = initialBoard
, gamePlayer = PlayerX
, gameState = Running
}
initialBoard = [ [Nothing, Nothing, Nothing]
, [Nothing, Nothing, Nothing]
, [Nothing, Nothing, Nothing]
]
printBoard = print . gameBoard
updateBoard :: Game -> Int -> Space -> Game -- updates the game board
updateBoard game i s = if (i < 1 || i > 9)
then game
else do
let b = concat $ gameBoard game
let (x,y:ys) = splitAt (i - 1) b
let b2 = if y == Nothing -- if the space equals Nothing
then x ++ [s] ++ ys -- then we will allow a PlayerX or PlayerO to be placed
else b -- otherwise b2 will equal the game board we started with
let b3 = chunksOf 3 b2
game { gameBoard = b3 }
switchPlayer :: Game -> Game -- switches the player
switchPlayer game =
case gamePlayer game of
PlayerX -> game { gamePlayer = PlayerO }
playerO -> game { gamePlayer = PlayerX }
gameLoop :: Game -> IO ()
gameLoop game = do
putStrLn "Enter a Number 1-9"
playerMove <- getLine
clearScreen
let newGame = updateBoard game (read playerMove :: Int) (Just (gamePlayer game))
let checkGame = if (newGame == game) -- If the updated game is equal to the old game
then gameLoop newGame -- then we restart the loop with no changes
else gameLoop $ switchPlayer newGame -- otherwise we restart the loop and switch the player
printBoard newGame -- print the game board
checkGame
main = gameLoop initialGame
基本上,我想使用我的gameBoard,而不是显示为[[Just PlayerX, Just PlayerO, Nothing], [Nothing Nothing, Nothing], [Nothing, Nothing, Nothing]]
我希望它显示为:
[['X', 'O', "."]
,['.', '.', '.']
,['.', '.', '.']
]
答案 0 :(得分:4)
要使您的面板以您想要的方式显示,我首先将其替换:
type Space = Maybe Player
与此:
newtype Space = Space {unSpace :: Maybe Player} deriving (Eq)
就编译器而言,这使Space
的类型与Maybe Player
完全不同(尽管在运行时将其视为相同),这意味着您可以给它一个新的实例适用于任何标准类型类。您会看到我派生了Eq
,因为将需要该实例-但我们想给它一个自定义实例Show
来显示X
或O
(或空白)的位置。这很容易做到:
instance Show Space where
show (Space (Just PlayerX)) = "X"
show (Space (Just PlayerO)) = "O"
show (Space Nothing) = "."
我应该指出,尽管将Space
变成newtype
而不是单纯的类型同义词将需要更改使用Space
类型的任何代码,并且在有点样板的方式。尽管在您现有的代码中只发现了一种情况(尽管我可能会漏掉一些),但是在updateBoard
所在的x ++ [s] ++ ys
中,您需要将其更改为x ++ [Space s] ++ ys
。 [基本上,您要做的就是在适当的位置添加对Space
和unSpace
的调用,以在两种等效类型之间进行转换。]
进行此更改不会完全按照您的要求进行,因为它仍将一行中的所有3行打印在电路板上。但随后您可以使用Board
进行同样的操作。
替换
type Board = [[Space]]
使用
newtype Board = Board {unBoard :: [[Space]]} deriving (Eq)
(这可能还会在整个代码中增加对Board
和unBoard
的调用。)
然后输入:
instance Show Board where
show (Board rows) = intercalate "\n" $ map show rows
与列表的标准实例相比,它删除外部的[..]
,并用换行符替换每行之间的,
。我相信这会给您您想要的-就像我说的那样,在这里和那里花了一些小样。
请注意,我目前无法自己对此进行检查,因此,如果我忽略了某些内容,导致无法编译或无法正常工作,我们深表歉意。(请告诉我,以便纠正)。
答案 1 :(得分:1)
罗宾·齐格蒙德(Robin Zigmond)的答案的“结构化”版本较少,它认识到只要您具有Space -> Char
类型的函数,只需要将其映射两次即可。< / p>
showSpace :: Space -> Char
showSpace (Just PlayerX) = 'X'
showSpace (Just PlayerO) = 'O'
showSpace Nothing = '.'
showBoard :: Board -> [String]
showBoard = map (map showSpace)
然后
let b = [[Just PlayerX,Just PlayerO,Nothing]
,[Nothing,Nothing,Nothing]
,[Nothing,Nothing,Nothing]
]
in showBoard b == ["XO.", "...", "..."]
实际上,showSpace
本身可以使用Maybe
仿函数定义(尽管最简单的方法是通过辅助函数maybe
):
showPlayer :: Player -> Char
showPlayer PlayerX = 'X'
showPlayer PlayerO = 'O'
showSpace :: Space -> Char
showSpace = maybe '.' showPlayer
Show
类型类的作用只是让您以单个名称{{1}为Player
,Space
,Board
等定义所有各种功能。 }},而不是使用不同的名称。