mcpi (empty) → 0.0.0.1
raw patch · 14 files changed
+1326/−0 lines, 14 filesdep +basedep +mcpidep +networksetup-changed
Dependencies added: base, mcpi, network, pipes, split, transformers
Files
- Setup.hs +2/−0
- examples/Flatten.hs +105/−0
- examples/Freefall.hs +79/−0
- examples/HMCPI.hs +110/−0
- examples/IsOnGold.hs +23/−0
- examples/README +56/−0
- mcpi.cabal +116/−0
- src/Data/MineCraft/Pi/Block.hs +372/−0
- src/Data/MineCraft/Pi/Camera.hs +45/−0
- src/Data/MineCraft/Pi/Other.hs +53/−0
- src/Data/MineCraft/Pi/Player.hs +49/−0
- src/Data/MineCraft/Pi/Types.hs +91/−0
- src/Network/MineCraft/Pi/Client.hs +86/−0
- src/Network/MineCraft/Pi/Client/Internal.hs +139/−0
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ examples/Flatten.hs view
@@ -0,0 +1,105 @@+{-# LANGUAGE RecordWildCards #-}++{-+License:++This code is placed in the Public Domain.++Author:++Douglas Burke (dburke.gw@gmail.com)++Usage:++ ./flatten [<radius>]++Convert all blocks within the given distance of the player,+and at a height one below that of the player, to gold.+Any blocks above this are removed.++If radius is not given then it defaults to 5. There is no+sanity check to ensure that radius is positive.++It could check that we are not about to place blocks onto+air/water.+-}++module Main where++import Control.Monad (forM_, when)+import Control.Monad.IO.Class++import Data.Maybe (listToMaybe)++import System.Environment+import System.Exit+import System.IO++import Data.MineCraft.Pi.Block+import Data.MineCraft.Pi.Other+import Data.MineCraft.Pi.Player+import Data.MineCraft.Pi.Types++import Network.MineCraft.Pi.Client++-- | Given x0,y0 and a radius, return all the block positions within+-- the circle. We use the coordinate of each block to determine+-- whether it is in or out.+--+-- Could be much-more efficient.+circleCoords :: Int -> Int -> Int -> [(Int, Int)]+circleCoords x0 y0 r = + let r2 = r * r+ isIn (a,b) = (a-x0)*(a-x0) + (b-y0)*(b-y0) <= r2+ in filter isIn [(i,j) | i <- [x0-r .. x0+r], j <- [y0-r .. y0+r]]+ +-- | Flatten the area surrounding the user to a distance+-- of r (radius) blocks. The ground is set to the given+-- block type.+--+-- TODO: shoud check that the blocks are not going onto+-- water/over air/replacing ice so that they do not sink,+-- but it may not be worth the effort.+--+flattenArea :: BlockType -> Int -> MCPI ()+flattenArea bType r = do+ liftIO $ putStrLn "Connected to MineCraft"+ Pos {..} <- getPlayerTile+ liftIO $ putStrLn $ "User is at x,y=" ++ show _x ++ "," ++ show _y +++ " and a height of " ++ show _z++ forM_ (circleCoords _x _y r) $ \(i,j) -> do+ -- clear everything above this point+ -- it depends on whether height means max z containing a+ -- block, or the max connected z (probably the latter)+ --+ let p0 = Pos i j $ _z - 1+ h <- getHeight p0+ when (h >= _z) $ setBlocks + (Pos i j _z)+ (Pos i j h)+ air++ setBlock p0 bType++ liftIO $ putStrLn "Exiting from MineCraft"++maybeRead :: Read a => String -> Maybe a+maybeRead = fmap fst . listToMaybe . reads++usage :: IO ()+usage = do+ progName <- getProgName+ hPutStrLn stderr $ "usage: " ++ progName ++ " [<radius>]"+ exitFailure++main :: IO ()+main = do+ args <- getArgs+ case args of+ [] -> runMCPI (flattenArea goldOre 5)+ [rstr] -> case maybeRead rstr of+ Just r -> runMCPI (flattenArea goldOre r)+ _ -> usage+ _ -> usage+
+ examples/Freefall.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE RecordWildCards #-}++{-+License:++This code is placed in the Public Domain.++Author:++Douglas Burke (dburke.gw@gmail.com)++Usage:++ ./freefall [<height>]++Example program showing how to send commands to MineCraft on+Raspberry Pi.++The program increases the user's current height by the+given argument, which defaults to 100. There is no sanity+check to ensure that the height is positive.+-}++module Main where++import Control.Monad (when)+import Control.Monad.IO.Class++import Data.Maybe (listToMaybe)++import System.Environment+import System.Exit+import System.IO++import Data.MineCraft.Pi.Block+import Data.MineCraft.Pi.Other+import Data.MineCraft.Pi.Player+import Data.MineCraft.Pi.Types++import Network.MineCraft.Pi.Client++moveUser :: Int -> MCPI ()+moveUser j = do+ liftIO $ putStrLn "Connected to MineCraft"+ Pos {..} <- getPlayerTile+ chatPost $ "Jumping from " ++ show _z ++ " to " ++ show (_z + j)+ let newPos = Pos _x _y (_z + j)+ bType <- getBlock newPos+ if bType == air+ then do+ setPlayerTile newPos+ bType_ <- getBlock $ Pos _x _y (_z + j - 1)+ when (bType_ == air) (chatPost "Weeeeeeeeee!!!!!!")++ else liftIO $ putStrLn ("Awwwww: jump would end in " ++ + showBlock bType) ++ liftIO $ putStrLn "Exiting from MineCraft"++maybeRead :: Read a => String -> Maybe a+maybeRead = fmap fst . listToMaybe . reads++usage :: IO ()+usage = do+ progName <- getProgName+ hPutStrLn stderr $ "usage: " ++ progName ++ " [<jump>]"+ hPutStrLn stderr "\n The default jump is 100 (blocks)."+ exitFailure++main :: IO ()+main = do+ args <- getArgs+ case args of+ [] -> runMCPI (moveUser 100)+ [jstr] -> case maybeRead jstr of+ Just j -> runMCPI (moveUser j)+ _ -> usage+ _ -> usage+
+ examples/HMCPI.hs view
@@ -0,0 +1,110 @@+{-+License:++This code is placed in the Public Domain.++Author:++Douglas Burke (dburke.gw@gmail.com)++Usage:++ ./hmcpi++Connects to MineCraft-PI and allows you to enter commands (it can be+thought of as telnet specialized to connect to just MineCraft).++At present there is no attempt to provide an improved interface, such+as help messages, auto-completion of commands, ignoring blank or+comment lines. These could be added, but at least the current+implementation does let you see exactly what is sent to and from+MineCraft-PI.++One useful change would be for control-D - or some other terminal+character or string - to exit the program. At the moment the only+way to exit is to kill the program (e.g. with control-C).++-}++module Main where++import qualified Control.Exception as CE++import Control.Concurrent (forkIO)+import Control.Monad (when)+import Control.Proxy++import Network (PortID (..), connectTo)++import System.Environment (getArgs, getProgName)+import System.Exit (exitFailure)+import System.IO ( BufferMode(LineBuffering), Handle+ , hClose, hIsTerminalDevice+ , hPutStrLn, hSetBuffering+ , stderr, stdin, stdout )++-- | Link together the two handles, so that content+-- moves from the first to the second.+--+-- It might be nice to clean out user input - e.g. skip blank+-- or empty lines - as well as provide support (e.g. help), but+-- for now just pass through everything. This at least means+-- that we can see what the server is actually doing, and+-- means we can use the same code for connecting user input+-- to the server as well as displaying the user output.+--+pipeTo :: Handle -> Handle -> IO ()+pipeTo inHdl outHdl =+ runProxy $ hGetLineS inHdl >-> hPutStrLnD outHdl++{- How best to handle a user exit? Need to signal to the+ main program that we have finished++pipeToWithExit :: Handle -> Handle -> IO ()+pipeToWithExit inHdl outHdl =+ runProxy $ hGetLineS inHdl >->+ takeWhileD ('\EOT' `notElem`) >->+ hPutStrLnD outHdl++-}++pipeToWithExit :: Handle -> Handle -> IO ()+pipeToWithExit = pipeTo+++-- | Only display the message if @stdin@ is connected to+-- a terminal device (i.e. the input is not being piped+-- from a file).+--+logMsg :: String -> IO ()+logMsg msg = hIsTerminalDevice stdin >>= \flag -> when flag (putStrLn msg)++mcpi :: IO ()+mcpi = + let errorHandler :: CE.IOException -> IO ()+ errorHandler _ = do+ hPutStrLn stderr "ERROR: Unable to connect MineCraft. Is it running?"+ exitFailure++ in CE.bracket+ (connectTo "127.0.0.1" (PortNumber 4711))+ hClose+ (\server -> do+ logMsg "*** Connected to MineCraft"+ mapM_ (`hSetBuffering` LineBuffering) [stdin, stdout, server]+ _ <- forkIO $ stdin `pipeToWithExit` server+ server `pipeTo` stdout+ )+ `CE.catch` errorHandler+ +usage :: IO ()+usage = do+ progName <- getProgName+ hPutStrLn stderr $ "Usage: " ++ progName+ exitFailure++main :: IO ()+main = do+ args <- getArgs+ if null args then mcpi else usage+
+ examples/IsOnGold.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE RecordWildCards #-}++module Main where++import Control.Monad (when)++import Data.MineCraft.Pi.Block +import Data.MineCraft.Pi.Player+import Data.MineCraft.Pi.Types++import Network.MineCraft.Pi.Client++-- | Returns @True@ if the player is standing on gold ore.+isOnGold :: IO Bool+isOnGold = runMCPI $ do+ Pos {..} <- getPlayerTile+ bType <- getBlock $ Pos _x _y (_z-1)+ return (bType == goldOre)++main :: IO ()+main = do+ flag <- isOnGold+ when flag $ putStrLn "Look down!"
+ examples/README view
@@ -0,0 +1,56 @@++There are several programs, illustrating some very *basic*+interactions with MineCraft-PI.++*) freefall++Source code: Freefall.hs ++Usage:+ freefall+ freefall jump++Increase the height of the player by jump blocks. If not specified the+jump is 100 blocks. A check is made to ensure that the jump does not+place the player into a block. Messages are sent to MineCraft to+inform the player what is happening.++There is no check that jump is valid (e.g. is positive, or not so+great that it exceeds the limits of the world), but this could be+added quite easily.+ +*) flatten++Source code: Flatten.hs ++Usage:+ flatten+ flatten radius++Flatten the area surrounding the user, converting the floor into gold+and removing any blocks above it. The area converted is a circle of+the given radius (this defaults to 5 if not given). The central tile+is not counted, so that a radius of 1 will change the blocks in front+of, behind, and to the side of the player.++There is no check that the blocks that are added are actually+supported, although this could be added by an enterprising programmer.++*) hmcpi++Source code: HMCPI.hs++Usage:+ hmcpi+ hmcpi < filename++This provides a direct interface to MineCraft-PI. You enter the full+commands, such as chat.post(Hello World!) or world.getBlock(0,0,0),+and see the response from the game. The program is essentially telnet+but specialized to only talk to MineCraft. There is currently no+attempt at providing a nicer interface, such as help, automatic+completion of commands, or cleaning user input.++Note that this program does not take use the hmcpi library, since it+is a low-level interface.+
+ mcpi.cabal view
@@ -0,0 +1,116 @@+name: mcpi+Version: 0.0.0.1+Stability: experimental+License: PublicDomain+Author: Douglas Burke (dburke.gw@gmail.com)+Maintainer: dburke.gw@gmail.com+Category: network, raspberrypi, minecraft+Synopsis: Connect to MineCraft running on a Raspberry PI.+Description:+ The MineCraft edition for Raspberry PI comes with a Java and+ Python API.+ .+ This is a *very* basic, and *incomplete* Haskell version. I+ fully expect everything to change in later versions.+ .+ Two very simple examples are included in the examples/ directory,+ as well as a way to interact with MineCraft directly.+ .+ Please see the TODO file in the source code for an incomplete+ list of possible changes.++Tested-With: GHC==7.4.1+Cabal-Version: >= 1.8+Build-Type: Simple++-- #Source-repository head+-- # type: mercurial+-- # location: https://bitbucket.org/doug_burke/grabtweets++Data-Files: examples/README+ examples/*.hs++Flag build-examples+ Description: Build the example programs (defaults to True)+ Default: True++Library+ Build-Depends:+ base >=3 && < 5,+ network == 2.3.*,+ split == 0.2.*,+ transformers == 0.3.*++ Hs-Source-Dirs: src/++ Exposed-Modules:+ Network.MineCraft.Pi.Client+ Network.MineCraft.Pi.Client.Internal+ Data.MineCraft.Pi.Block+ Data.MineCraft.Pi.Camera+ Data.MineCraft.Pi.Other+ Data.MineCraft.Pi.Player+ Data.MineCraft.Pi.Types++ ghc-options:+ -Wall++Executable flatten+ if !flag(build-examples)+ Buildable: False++ Main-Is: Flatten.hs+ Hs-Source-Dirs: examples/ ++ ghc-options:+ -Wall++ Build-Depends:+ base,+ mcpi,+ transformers++Executable freefall+ if !flag(build-examples)+ Buildable: False++ Main-Is: Freefall.hs+ Hs-Source-Dirs: examples/ ++ ghc-options:+ -Wall++ Build-Depends:+ base,+ mcpi,+ transformers++Executable hmcpi+ if !flag(build-examples)+ Buildable: False++ Main-Is: HMCPI.hs+ Hs-Source-Dirs: examples/ ++ ghc-options:+ -Wall++ Build-Depends:+ base,+ network,+ pipes == 3.1.*++Executable isongold+ if !flag(build-examples)+ Buildable: False++ Main-Is: IsOnGold.hs+ Hs-Source-Dirs: examples/ ++ ghc-options:+ -Wall++ Build-Depends:+ base,+ mcpi,+ transformers
+ src/Data/MineCraft/Pi/Block.hs view
@@ -0,0 +1,372 @@+--------------------------------------------------------------------------------+-- |+-- Module : Block+-- License : Public Domain+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : Haskell 98+--+-- Block handling.+--+-- It probably makes sense for the block types in+-- @Data.MineCraftg.Pi.Types@ - namely @BlockType@ and @BlockData@+-- to be moved into this module.+--+-- See <http://www.minecraftwiki.net/wiki/Data_values_(Pocket_Edition)> and+-- <http://www.minecraftwiki.net/wiki/Pi_Edition_version_history>, although I+-- have not cross-matched and verified all this information.+--+--------------------------------------------------------------------------------++module Data.MineCraft.Pi.Block+ (+ -- * Queries+ getBlock+ , getBlockData+ , getBlocks++ -- * Commands+ , setBlock+ , setBlockData+ , setBlocks+ , setBlocksData++ -- * Blocks+ , showBlock+ , air+ , stone+ , grass+ , dirt+ , cobblestone+ , woodPlanks+ , sapling+ , bedrock+ , water+ , waterStationary+ , lava+ , lavaStationary+ , sand+ , gravel+ , goldOre+ , ironOre+ , coalOre+ , wood+ , leaves+ , glass+ , lapisLazuliOre+ , lapisLazuliBlock+ , sandstone+ , bed+ , cobweb+ , grassTall+ , wool+ , flowerYellow+ , flowerCyan+ , mushroomBrown+ , mushroomRed+ , goldBlock+ , ironBlock+ , stoneSlabDouble+ , stoneSlab+ , brickBlock+ , tnt+ , bookshelf+ , mossStone+ , obsidian+ , torch+ , fire+ , stairsWood+ , chest+ , diamondOre+ , diamondBlock+ , craftingTable+ , farmland+ , furnaceInactive+ , furnaceActive+ , doorWood+ , ladder+ , stairsCobblestone+ , doorIron+ , redstoneOre+ , snow+ , ice+ , snowBlock+ , cactus+ , clay+ , sugarCane+ , fence+ , glowstoneBlock+ , bedrockInvisible+ , stoneBrick+ , glassPane+ , melon+ , fenceGate+ , glowingObsidian+ , netherReactorCore+ ) where++import Control.Monad (liftM)++import Data.Maybe (fromMaybe)+import Data.Word (Word16)++import Data.MineCraft.Pi.Types+import Network.MineCraft.Pi.Client+import Network.MineCraft.Pi.Client.Internal++-- | Block types.+air, stone, grass, dirt, cobblestone,+ woodPlanks, sapling, bedrock, water,+ waterStationary, lava, lavaStationary, sand,+ gravel, goldOre, ironOre, coalOre,+ wood, leaves, glass,+ lapisLazuliOre, lapisLazuliBlock,+ sandstone, bed, cobweb, grassTall, wool,+ flowerYellow, flowerCyan,+ mushroomBrown, mushroomRed,+ goldBlock, ironBlock, stoneSlabDouble, stoneSlab,+ brickBlock, tnt, bookshelf, mossStone, obsidian,+ torch, fire, stairsWood, chest, diamondOre,+ diamondBlock, craftingTable, farmland,+ furnaceInactive, furnaceActive, doorWood, ladder,+ stairsCobblestone, doorIron, redstoneOre,+ snow, ice, snowBlock, cactus, clay, sugarCane,+ fence, glowstoneBlock, bedrockInvisible,+ stoneBrick, glassPane, melon, fenceGate,+ glowingObsidian, netherReactorCore+ :: BlockType++air = BlockType 0+stone = BlockType 1+grass = BlockType 2+dirt = BlockType 3+cobblestone = BlockType 4+woodPlanks = BlockType 5+sapling = BlockType 6+bedrock = BlockType 7+water = BlockType 8+waterStationary = BlockType 9+lava = BlockType 10+lavaStationary = BlockType 11+sand = BlockType 12+gravel = BlockType 13+goldOre = BlockType 14+ironOre = BlockType 15+coalOre = BlockType 16+wood = BlockType 17+leaves = BlockType 18++glass = BlockType 20++lapisLazuliOre = BlockType 21+lapisLazuliBlock = BlockType 22++sandstone = BlockType 24++bed = BlockType 26++cobweb = BlockType 30+grassTall = BlockType 31++wool = BlockType 35++flowerYellow = BlockType 37+flowerCyan = BlockType 38+mushroomBrown = BlockType 39+mushroomRed = BlockType 40+goldBlock = BlockType 41+ironBlock = BlockType 42+stoneSlabDouble = BlockType 43+stoneSlab = BlockType 44+brickBlock = BlockType 45+tnt = BlockType 46+bookshelf = BlockType 47+mossStone = BlockType 48+obsidian = BlockType 49+torch = BlockType 50+fire = BlockType 51++stairsWood = BlockType 53+chest = BlockType 54++diamondOre = BlockType 56+diamondBlock = BlockType 57+craftingTable = BlockType 58 ++farmland = BlockType 60+furnaceInactive = BlockType 61+furnaceActive = BlockType 62++doorWood = BlockType 64+ladder = BlockType 65++stairsCobblestone = BlockType 67++doorIron = BlockType 71++redstoneOre = BlockType 73++snow = BlockType 78+ice = BlockType 79+snowBlock = BlockType 80+cactus = BlockType 81+clay = BlockType 82+sugarCane = BlockType 83++fence = BlockType 85++glowstoneBlock = BlockType 89++bedrockInvisible = BlockType 95++stoneBrick = BlockType 98++glassPane = BlockType 102+melon = BlockType 103++fenceGate = BlockType 107++glowingObsidian = BlockType 246+netherReactorCore = BlockType 247++-- For now assume the table is small enough it is not+-- worth using a map.+blockNames :: [(Word16, String)]+blockNames = + [ (0, "Air")+ , (1, "Stone")+ , (2, "Grass")+ , (3, "Dirt")+ , (4, "Cobblestone")+ , (5, "Wooden Plank")+ , (6, "Sapling")+ , (7, "BedRock")+ , (8, "Water")+ , (9, "Stationary water")+ , (10, "Lava")+ , (11, "Stationary lava")+ , (12, "Sand")+ , (13, "Gravel")+ , (14, "Gold Ore")+ , (15, "Iron Ore")+ , (16, "Coal Ore")+ , (17, "Wood")+ , (18, "Leaves")+ , (20, "Glass")+ , (21, "Lapis Lazuli Ore")+ , (22, "Lapis Lazuli Block")+ , (24, "Sandstone")+ , (26, "Bed")+ , (30, "Cobweb")+ , (31, "Tall Grass")+ , (35, "Wool")+ , (37, "Yellow Flower")+ , (38, "Cyan Flower")+ , (39, "Brown Mushroom")+ , (40, "Brown Mushroom")+ , (41, "Gold Block")+ , (42, "Iron Block")+ , (43, "Double Stone Slab")+ , (44, "Stone Slab")+ , (45, "Brick Block")+ , (46, "TNT")+ , (47, "Bookshelf")+ , (48, "Moss Stone")+ , (49, "Obsidian")+ , (50, "Torch")+ , (51, "Fire")+ , (53, "Wooden Stairs")+ , (54, "Chest")+ , (56, "Diamond Ore")+ , (57, "Diamond Block")+ , (58, "Crafting Table")+ , (59, "Wheat Seeds") -- valid?+ , (60, "Farmland")+ , (61, "Furnace")+ , (62, "Burning Furnace")+ , (63, "Sign Post")+ , (64, "Wooden Door")+ , (65, "Ladder")+ , (67, "Cobblestone Stairs")+ , (68, "Wall Sign") -- valid?+ , (71, "Iron Door")+ , (73, "Redstone Ore")+ , (74, "Glowing Redstone Ore") -- valid?+ , (78, "Snow")+ , (79, "Ice")+ , (80, "Snow Block")+ , (81, "Cactus")+ , (82, "Clay")+ , (83, "Sugar Cane")+ , (85, "Fence")+ , (87, "Netherrack") -- valid?+ , (89, "Glowstone Block")+ , (95, "Invisible Bedrock")+ , (98, "Stone Brick")+ , (102, "Glass Pane")+ , (103, "Melon")+ , (105, "Melon Stem") -- valid?+ , (107, "Fence Gate")+ , (108, "Brick Stairs") -- valid?+ , (109, "Stone Brick Stairs") -- valid?+ , (112, "Nether Brick") -- valid?+ , (114, "Nether Brick Stairs") -- valid?+ , (128, "Sandstone Stairs") -- valid?+ , (155, "Block of Quartz") -- valid?+ , (156, "Quartz Stairs") -- valid?+ , (245, "Stone Cutter") -- valid?+ , (246, "Glowing Obsidian")+ , (247, "Nether Reactor Core")+ , (249, "Update Game Block") -- valid?+ , (253, "Grass Block (mimic)") -- valid?+ , (254, "Leaves (mysterious)") -- valid?+ , (255, ".name") -- valid?+ ]++-- | Return a name for the block type, or "Unknown block <number>"+-- if unknown.+showBlock :: BlockType -> String+showBlock (BlockType n) =+ fromMaybe ("Unknown block <" ++ show n ++ ">")+ $ lookup n blockNames++-- | What is the block at this position? See also `getBlockData`.+getBlock :: IPos -> MCPI BlockType+getBlock pos = fromMC `liftM` query "world.getBlock" [toMC pos]++-- | What is the block at this position? See also `getBlock`.+getBlockData :: IPos -> MCPI (BlockType, BlockData)+getBlockData pos = fromMC `liftM` query "world.getBlockWithData" [toMC pos]++-- | Get the blocks in the cuboid defined by the start and end positions.+--+getBlocks :: + IPos -- ^ One corner of the cuboid. + -> IPos -- ^ Opposite corner.+ -> MCPI [BlockType] -- ^ The order has not been specified.+getBlocks spos epos = fromMC `liftM`+ query "world.getBlocks" [toMC spos, toMC epos]++-- | Change the block at the position. See also `setBlockData`.+setBlock :: IPos -> BlockType -> MCPI ()+setBlock pos bt = command "world.setBlock" [toMC pos, toMC bt]++-- | Change the block at the position. See also `setBlock`.+setBlockData :: IPos -> (BlockType, BlockData) -> MCPI ()+setBlockData pos (bt, bd) =+ command "world.setBlock" [toMC pos, toMC bt, toMC bd]++-- | Set all the blocks in the cuboid to the same type. See also+-- `setBlocksData`.+setBlocks :: IPos -> IPos -> BlockType -> MCPI ()+setBlocks spos epos bt =+ command "world.setBlocks" [toMC spos, toMC epos, toMC bt]++-- | Set all the blocks in the cuboid to the same type. See also+-- `setBlocks`.+setBlocksData :: IPos -> IPos -> (BlockType, BlockData) -> MCPI ()+setBlocksData spos epos (bt, bd) =+ command "world.setBlocks" [toMC spos, toMC epos, toMC bt, toMC bd]++
+ src/Data/MineCraft/Pi/Camera.hs view
@@ -0,0 +1,45 @@+--------------------------------------------------------------------------------+-- |+-- Module : Camera+-- License : Public Domain+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : Haskell 98+--+-- Handle the camera position.+--+-- *Note* At present this module does not handle multiple users.+--+--------------------------------------------------------------------------------++module Data.MineCraft.Pi.Camera+ (+ -- * Commands+ setCameraNormal+ , setCameraThirdPerson+ , setCameraFixed+ , setCameraPos+ ) where++import Data.MineCraft.Pi.Types++import Network.MineCraft.Pi.Client+import Network.MineCraft.Pi.Client.Internal++setCameraFixed :: MCPI ()+setCameraFixed = command "camera.mode.setFixed" []++setCameraNormal :: MCPI ()+setCameraNormal = command "camera.mode.setNormal" []++setCameraThirdPerson :: MCPI ()+setCameraThirdPerson = command "camera.mode.setThirdPerson" []++-- | Change the position of the camera.+setCameraPos :: IPos -> MCPI ()+setCameraPos pos = command "camera.mode.setPos" [toMC pos]++-- TODO: the Python API does not have the ThirdPerson variant+-- but it does have setFollow, to follow a user.+
+ src/Data/MineCraft/Pi/Other.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE RecordWildCards #-}++--------------------------------------------------------------------------------+-- |+-- Module : Other+-- License : Public Domain+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : RecordWildCards+--+-- A mixture of routines.+--+--------------------------------------------------------------------------------++module Data.MineCraft.Pi.Other+ (+ -- * Queries+ getHeight++ -- * Commands+ , chatPost+ ) where++import Control.Monad (liftM)++import Data.MineCraft.Pi.Types++import Network.MineCraft.Pi.Client+import Network.MineCraft.Pi.Client.Internal++-- | Send a message to the server.+--+-- Until I find out otherwise, I am going to assume that+-- any conversion done by @show msg@ - such as protecting+-- any double quotes - is sufficient. The spec does not+-- mention what is required.+--+-- There is no attempt to remove or replace invalid characters. The+-- MineCraft server API uses ASCII and it is likely that new lines+-- are not allowed in the message, although this is not specified.+--+chatPost :: String -> MCPI ()+chatPost msg = command "chat.post" [msg]++-- | Return the height of the world at this position. The spec says+-- that the input uses the (x,z) coordinates, but I am assuming this+-- is a typo and am using (x,y) instead.+--+-- It would be easy to test.+getHeight :: IPos -> MCPI Int+getHeight Pos {..} = fromMC `liftM` query "world.getHeight" [toMC _x, toMC _y]+
+ src/Data/MineCraft/Pi/Player.hs view
@@ -0,0 +1,49 @@+--------------------------------------------------------------------------------+-- |+-- Module : Player+-- License : Public Domain+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : Haskell 98+--+-- Players in MineCraft.+--+-- *Note* this module currently does not support multiple players.+--+--------------------------------------------------------------------------------++module Data.MineCraft.Pi.Player+ (+ -- * Queries+ getPlayerTile+ , getPlayerPos++ -- * Commands+ , setPlayerTile+ , setPlayerPos+ ) where++import Control.Monad (liftM)++import Data.MineCraft.Pi.Types++import Network.MineCraft.Pi.Client+import Network.MineCraft.Pi.Client.Internal++-- | Where is the user. See also `getPlayerPos`.+getPlayerTile :: MCPI IPos +getPlayerTile = fromMC `liftM` query "player.getTile" []++-- | Move the user. See also `setPlayerPos`.+setPlayerTile :: IPos -> MCPI ()+setPlayerTile pos = command "player.setTile" [toMC pos]++-- | Where is the user. See also `getPlayerPos`.+getPlayerPos :: MCPI FPos+getPlayerPos = fromMC `liftM` query "player.getPos" []++-- | Move the user. See also `setPlayerPos`.+setPlayerPos :: FPos -> MCPI ()+setPlayerPos pos = command "player.setPos" [toMC pos]+
+ src/Data/MineCraft/Pi/Types.hs view
@@ -0,0 +1,91 @@+--------------------------------------------------------------------------------+-- |+-- Module : Types+-- License : Public Domain+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : Haskell 98+--+-- Useful data types. The block types should probably move to+-- "Data.MineCraft.Pi.Block".+--+--------------------------------------------------------------------------------++module Data.MineCraft.Pi.Types + (+ Pos(..)+ , IPos+ , FPos++ , BlockType(..)+ , BlockData(..)++ ) where++import Data.List (intercalate)+import Data.List.Split (splitOn)+import Data.Word (Word16)++import Network.MineCraft.Pi.Client++-- | Represent the position of an entity. Note that we use+-- @(X,Y,Z)@ order rather than @(X,Z,Y)@ used by the MineCraft API.+--+-- This *should* be replaced by one of the \"standard\" Haskell+-- 3D vector types.+--+data Pos a = Pos { _x :: a, _y :: a, _z :: a }+ deriving (Eq, Show)++type IPos = Pos Int+type FPos = Pos Float++instance FromMineCraft a => FromMineCraft (Pos a) where+ fromMC s = let toks = map fromMC $ splitOn "," s+ in case toks of+ (x:z:y:[]) -> Pos x y z+ _ -> error $ "*Expected 3 values* " ++ s++instance ToMineCraft a => ToMineCraft (Pos a) where+ toMC (Pos x y z) = intercalate "," $ map toMC [x,z,y]++-- | Represent a block.+--+-- We should probably combine `BlockType` and `BlockData`.+-- I have not looked to see whether it is worth using+-- an integer (as I currently do) or just an enumerated+-- type (e.g. if there are large ranges of the range 0 to 1023+-- that do not represent a valid block).+--+-- Use `Data.MineCraft.Pi.Block.showBlock` for a more readable+-- way to convert to a @String@.+newtype BlockType = BlockType Word16 + deriving (Eq, Ord, Show)++-- | Data on a block.+newtype BlockData = BlockData Int+ deriving (Eq, Ord, Show)++{- Looks like this range includes entities and other items than just+blocks.+instance Bounded BlockType where+ minBound = BlockType 0+ maxBound = BlockType 1023+-}++instance ToMineCraft BlockType where+ toMC (BlockType bt) = show bt++instance FromMineCraft BlockType where+ fromMC val = let i = eRead val+ in if i >= 0 && i < 1024+ then BlockType i+ else error $ "*invalid block type: " ++ val++instance ToMineCraft BlockData where+ toMC (BlockData bd) = show bd++instance FromMineCraft BlockData where+ fromMC = BlockData . eRead+
+ src/Network/MineCraft/Pi/Client.hs view
@@ -0,0 +1,86 @@+--------------------------------------------------------------------------------+-- |+-- Module : Client+-- License : Public Domain+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : Haskell 98+--+-- The main interface for connecting to the Raspberry-PI version+-- of MineCraft. The 'Network.MineCraft.Pi.Client.Internal' module+-- provides lower-level access in case this module in insufficient.+--+-- There are two types of calls to MineCraft: "command" and "query".+-- Commands change the state of the server and do not return anything,+-- queries return information from the server, and presumably does not+-- change the server state. This terminology may change.+--+-- I am not sure the use of the @FromMineCraft@ and @ToMineCraft@+-- type classes is justified, given that the API has a very limited+-- set of types.+--+--------------------------------------------------------------------------------++module Network.MineCraft.Pi.Client+ ( MCPI+ , runMCPI++ -- * Conversion routines+ , FromMineCraft(..)+ , ToMineCraft(..)++ -- * Utility routine+ , eRead+ ) where++import Data.List (intercalate)+import Data.List.Split (splitOn)+import Data.Maybe (fromMaybe, listToMaybe)++import Network.MineCraft.Pi.Client.Internal++-- | Convert the return value from MineCraft into+-- a Haskell type.+class FromMineCraft a where+ fromMC :: String -> a++-- | Send a value to MineCraft.+class ToMineCraft a where+ toMC :: a -> String++-- Isn't this in base somewhere by now?+maybeRead :: Read a => String -> Maybe a+maybeRead = fmap fst . listToMaybe . reads++-- | Convert a value, but error out if it can not be+-- converted.+eRead :: (Read a) => String -> a+eRead s = fromMaybe (error ("*Conversion error* " ++ s)) $ maybeRead s++instance FromMineCraft Int where+ fromMC = eRead ++instance ToMineCraft Int where+ toMC = show++instance FromMineCraft Float where+ fromMC = eRead++instance ToMineCraft Float where+ toMC = show++instance FromMineCraft a => FromMineCraft [a] where+ fromMC = map fromMC . splitOn ","++instance ToMineCraft a => ToMineCraft [a] where+ toMC = intercalate "," . map toMC++instance (FromMineCraft a, FromMineCraft b) => FromMineCraft (a, b) where+ fromMC s = case splitOn "," s of+ (a:b:[]) -> (fromMC a, fromMC b)+ _ -> error $ "*Expected 2 values* " ++ s++instance (ToMineCraft a, ToMineCraft b) => ToMineCraft (a, b) where+ toMC (a,b) = toMC a ++ "," ++ toMC b+
+ src/Network/MineCraft/Pi/Client/Internal.hs view
@@ -0,0 +1,139 @@+{-# LANGUAGE RecordWildCards #-}++--------------------------------------------------------------------------------+-- |+-- Module : Internal+-- License : Public Domain+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : RecordWildCards+--+-- Internal types for connecting to the Raspberry-PI version+-- of MineCraft. Most users are expected to use 'Network.MineCraft.Pi.Client'+-- rather than this module, but it is provided in case the former+-- is not sufficient.+--+--------------------------------------------------------------------------------++module Network.MineCraft.Pi.Client.Internal+ ( MCPI+ , runMCPI+ , runMCPI'+ , query+ , command+ ) where++import qualified Control.Exception as CE++import Control.Exception (bracket)+import Control.Monad (when)+import Control.Monad.IO.Class+import Control.Monad.Trans.Reader++import Data.List (intercalate)++import Network.BSD+import Network.Socket++import System.Exit+import System.IO++-- | Represent a program that communicates with a MineCraft PI+-- server.+--+type MCPI = ReaderT ConnInfo IO++-- | Connection information.+data ConnInfo = ConnInfo {+ _ciHandle :: Handle -- ^ Connection to the MineCraft program+ , _ciDebug :: Bool -- ^ Should messages to and from MineCraft+ -- be printed to @stderr@.+ }++-- | Commands do not return anything, queries do.+type Command = String+type Query = String+type Argument = String++-- | The port used by MineCraft is fixed.+mcPort :: String+mcPort = "4711"++-- | Open a connection to the Minecraft server or call+-- exitFailure, after displaying an error message to @stderr@.+--+openMCPI ::+ Bool -- ^ Set to @True@ to get debugging messages printed to @stderr@.+ -> IO ConnInfo+openMCPI flag = do+ let ehdl :: CE.IOException -> IO ()+ ehdl _ = hPutStrLn stderr "ERROR: Unable to connect to MineCraft-PI. Is it running?" >>+ exitFailure+ + as <- getAddrInfo Nothing Nothing (Just mcPort)+ let a = head as -- note: getAddrInfo never returns an empty list+ sock <- socket (addrFamily a) Stream defaultProtocol+ setSocketOption sock KeepAlive 1+ connect sock (addrAddress a) `CE.catch` ehdl+ h <- socketToHandle sock ReadWriteMode+ hSetBuffering h LineBuffering++ return $ ConnInfo h flag++-- | Close the connection.+closeMCPI :: ConnInfo -> IO ()+closeMCPI = hClose . _ciHandle++logMsg :: ConnInfo -> String -> String -> MCPI ()+logMsg ConnInfo {..} hdr msg = + when _ciDebug $ liftIO $ hPutStrLn stderr $ "*DBG*" ++ hdr ++ "*" ++ msg++-- It would be nice to do the argument marshalling here, i.e. have+-- something like @command :: Command -> [Argument] -> MCPI ()@+-- which would be run like @command "player.setTile" [Pos 0 0 0]@,+-- but I do not want to deal with heterogeneous lists at this time.+-- Instead, we force the caller to do the conversion.+--+addArgs :: String -> [Argument] -> String+addArgs a bs = a ++ "(" ++ intercalate "," bs ++ ")"++-- | Run a MineCraft command. +command :: Command -> [Argument] -> MCPI ()+command comm args = do+ ci <- ask+ let commstr = addArgs comm args+ logMsg ci "COMMAND" commstr+ liftIO $ hPutStrLn (_ciHandle ci) commstr++-- | Run a MineCraft query, returning the response.+query :: Query -> [Argument] -> MCPI String+query qry args = do+ ci <- ask+ let qrystr = addArgs qry args+ logMsg ci "QUERY" qrystr+ liftIO $ hPutStrLn (_ciHandle ci) qrystr+ ans <- liftIO $ hGetLine (_ciHandle ci)+ logMsg ci "RESPONSE" ans+ return ans++-- | Run a Raspberry-PI program. The flag determines whether the+-- messages sent to, and received from, the server, are+-- printed to @stderr@.+--+-- An exception is raised if the server is not running, or+-- can not be contacted.+runMCPI' :: Bool -> MCPI a -> IO a+runMCPI' flag p = + bracket+ (openMCPI flag)+ closeMCPI+ (runReaderT p)++-- | Run a Raspberry-PI program.+--+-- An exception is raised if the server is not running, or+-- can not be contacted.+runMCPI :: MCPI a -> IO a+runMCPI = runMCPI' False+