haskell将自定义数据列表转换为字符列表?

时间:2019-02-11 07:50:04

标签: haskell

嗨,我是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', "."]
,['.', '.', '.']
,['.', '.', '.']
]

2 个答案:

答案 0 :(得分:4)

要使您的面板以您想要的方式显示,我首先将其替换:

type Space =  Maybe Player

与此:

newtype Space = Space {unSpace :: Maybe Player} deriving (Eq)

就编译器而言,这使Space的类型与Maybe Player完全不同(尽管在运行时将其视为相同),这意味着您可以给它一个新的实例适用于任何标准类型类。您会看到我派生了Eq,因为将需要该实例-但我们想给它一个自定义实例Show来显示XO (或空白)的位置。这很容易做到:

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。 [基本上,您要做的就是在适当的位置添加对SpaceunSpace的调用,以在两种等效类型之间进行转换。]

进行此更改不会完全按照您的要求进行,因为它仍将一行中的所有3行打印在电路板上。但随后您可以使用Board进行同样的操作。

替换

type Board = [[Space]]

使用

newtype Board = Board {unBoard :: [[Space]]} deriving (Eq)

(这可能还会在整个代码中增加对BoardunBoard的调用。)

然后输入:

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}为PlayerSpaceBoard等定义所有各种功能。 }},而不是使用不同的名称。