Non-Homework 6: IO
CIS 194: Homework 6
This homework is optional (due to the fall brake). If you do it, you can still send your solution to the TA for feedback.
The general remarks about style and submission from the first week still apply.
Exercise 1: ASCII-Sokoban
The goal of this homework is to take your Sokoban game from Homework 4, and repace the CodeWorld environment with your own, text based environment.
Take your code, or this code as a starting base. Remove anything not related to the sokoban game.
Change the
Interaction
type to be suitable for text output:data Interaction world = Interaction world (String -> world -> world) (world -> String)
The first
String
replaces theEvent
type, and contains the description of one key press. The secondString
replaces thePicture
type and should contain the content of the screen. This string should contain 21'\n'
-terminated lines of 21 characters.Remove the
import CodeWorld
.Rewrite the
drawState
function to produce text output. Use suitable characters for the various things, e.g.#
for walls and so on. There is probably little need to distinguish blank and ground.You can rewrite it from scratch, or you can actually try to recreate the API of CodeWorld so that the structure of the code changes only little.
There are various ways of doing that:
You might define
type Picture = Integer -> Integer -> Maybe Char
with, for example,
blank _ _ = Nothing
, and(&)
letting the function on the left take precedence over the function on the right, andtranslated
changes the parameters appropriately.More fancy in terms of functional programming, you might want to define
type DrawFun = Integer -> Integer -> Char type Picture = DrawFun -> DrawFun
so that a
Picture
is actually a function that changes an existing picture. Now,blank = id
and(&) = (.)
. Fortranslated
you have to shift coordinates twice1.
Create your own
runInteraction :: Interaction s -> IO ()
Initially, make sure input goes through to the program directly (and not after enter is pressed):
hSetBuffering stdin NoBuffering
Then blank the screen (using
putStr "\ESCc"
) and render the the initial state. Now go to a recursive functiongo
:- Use
getChar
to read the next character. - Passes it to the handle function in the
Interaction
to get the new state. - Blanks the screen using
putStr "\ESCc"
. - Draws the state (using the draw function in the
Interaction
andputStr
). - Calls itself, with the new state.
- Use
You can make the go
function detect presses of q
to quit the game. In this case, simply do not call go
again.
Handling arrow keys is a bit tricky, as pressing the “→” key actually yields the sequence "\ESC[C"?
. (Simply run getLine
in ghci, press a key and then enter to see that.), but pressing the escape key produces simply "\ESC"
. That is tricky!
Here is one way of handling it:
Create a function
getAllInput :: IO [Char]
that usesgetChar
to read a character, then useshReady stdin
fromSystem.IO
to check if there is another character to read, and so on, until all characters that are currently available are read.Pattern match on the initial segment of that list to detect
'\ESC':'[':'C':rest
, or'\ESC':rest
, or simplyc:rest
, and act accordingly.Pass the unmatched portion of the input on to the next iteration of
go
, and prepend it to what you read fromreadAllInput
before matching on it, so that no key presses are lost.
If you are still bored, you can make it colorful, using ANSI escape sequences!
Enjoy your own sokoban.
If you like group theory: it’s a conjugate!↩