Programming as Problem Solving (including Advanced) ANU College of Engineering & Computer Science
Home » Assignments » Assignment 1: Drawing Shapes
Assignment 1: Drawing Shapes
Upon completion of this assignment, you will have a working Haskell program that uses the CodeWorld API to draw various colourful shapes on the screen, including Rectangles, Triangles, Polygons, Circles, and Ellipses. We have prepared a basic frameork for you to start this assignment.
Deadlines
Part A: Saturday 31 March, 2018 (census date) Part B: Friday 20 April, 2018 (Week 7)
Learning Objectives
Mastering guards and case statements
Working with lists
Programming with events
Applying abstraction and functional decomposition
Getting Started
It’s okay if you don’t feel like you have mastered all of the learning objectives at the start of the assignment. This assignment is designed to be worked on while you are learning about these concepts, and provides guidance to your learning throughout.
Setting things up
- Go to https://gitlab.cecs.anu.edu.au, and login. After that, come back to this page and continue with the next step.
- Go to the assignment’s gitlab repository: https://gitlab.cecs.anu.edu.au/comp1100/comp1100assignment1.
- Click on the Fork button to create your own private version of the assignment, so you can work on it independently from everyone else.
- Click on your name and wait a little while.
- Your web page should automatically refresh, and if everything went well, display your own repository containing the assignment files. You can check this by looking above the banner “The project was successfully forked.”, it should display your name instead of comp1100.
- In the menu on the left, hover over “Settings” and then click on “Members”. Under “Select members to invite”, type in your tutor’s name, and select your tutor.
- Change the role permission from “Guest” to “Reporter” otherwise your tutor will not be able to access your assignment
- Click on the green button “Add to project”
- Click on “Overview” in the menu on the left.
- Next to the “Fork” button in your repository, there is a button which says “SSH”. Click on it and switch it to “HTTPS”.
11. Copy this URL.
12. Open IntelliJ. If your labs project opens up then close the window: click on the menu File at the top of the IntelliJ window
and select Close Project.
13. In the IntelliJ IDEA window, click on Check out from Version Control and select Git.
14. Paste the URL that you copied above into the Clone Repository dialogue box as the Git Repository URL. Set Parent directory to be ~/comp1100, and the directory name as assignment1.
15. Click Clone and follow the same IntelliJ steps as Lab 2 starting from the clone step. Make sure you add the upstream remote:
git remote add upstream https://gitlab.cecs.anu.edu.au/comp1100/comp1100-assignment1.git
Using multiple machines
You may want to work on your own personal computer for this assignment, as well as working in the lab. You can set up an IntelliJ project, cloning your project from GitLab to your personal computer in much the same way, by following the steps
starting from the step “Copy this URL” set in bold typeface above.
However, when you switch from computer A (say the labs) to computer B (say your personal computer) you should make sure to commit and push all of your changes back to GitLab from computer A, so you can pull them to computer B before resuming work. Every work session should thus proceed as follows:
1. git pull
2. Do some work…
3. git commit -a -m “<descriptive message goes here>” 4. git push
When you switch to a different machine, use that same sequence. In short, always pull before you start, and always commit and push when you finish.
Framework
Run the program
For this program, we are using cabal: a build tool for Haskell that avoids the need to build all of the pieces of your project separately.
In this assignment we will be building two related programs:
- A graphical interface program that handles user interactions generated as mouse and keyboard events. This is called
ClickToDraw.
- A texttographic program that handles user interactions generated as simply keyboard events. This is called TypeToDraw.
There are several useful commands for cabal: cabal build
This will compile all of the necessary Haskell files needed for the programs. cabal run ClickToDraw and cabal run TypeToDraw:
These will run the named program, compiling your Haskell files as necessary. cabal repl ClickToDraw and cabal repl TypeToDraw
This is probably where you will be spending most of your time trying out your code. REPL stands for ReadEvalPrint Loop. These will launch a GHCi environment and load the named program.
cabal clean
This will delete any build files and can be useful if the files become corrupted or you just want to build everything afresh. This will not delete any important files such as your source code programs.
cabal reconfigure
This will reconfigure the project for the operating system of your computer. Sometimes you will need to do this after upgrading the OS or Haskell itself. Otherwise there are not many reasons to do this.
You must execute these cabal commands in the toplevel directory of your project: ~/comp1100/assignment1 (i.e., the directory you are in when you launch the IntelliJ Terminal tool for your project).
Modules
For this assignment, we have split the program into 8 components. The reason that the program is broken up like this is to increase readability (so that your tutors can mark more efficiently) and maintainability (so that you can fix problems more efficiently) by placing functions in their respective “categories”. Moreover, there might be some functions in one module that other modules do not need. With modularisation, you can control what is exposed to other modules and what you want to use by importing. This increases the overall robustness of the program as well.
Because of this, you need to think relatively carefully when writing a new function where best to place it. The more organised you are at modularising your programs, the more easily you can diagnose and fix bugs.
The tasks for this assignment are split into 2 parts: A and B. The files you will need for part A (TypeToDraw) are: 1. src/TypeToDraw.hs
This is where the main function of the TypeToDraw program stays. You do not need to edit this file throughout the entire assignment. It contains some “black magic” and you can treat it as such. If you are interested in the code inside this function, feel free to post a question on Piazza.
2. src/ColourName.hs
We have defined a custom enumeration data type called ColourName here. This file contains the definitions and functions related to the ColourName that we have defined as well as Colour from the CodeWorld API. One function is currently left undefined, and we will get to this soon.
- src/Shape.hs
This is where custom types Shape and Tool are defined.Shapes are abstract representations of shapes. For example a rectangles is described by its width and height, polygons are represented by all of its vertices, etc.
Tools are used while you are drawing a shape, and it will hold the necessary information for that action.
You shouldn’t need to modify this file. However, if you write any functions that are accessories for Tool or Shape, feel free toplace them in this file.
- src/View.hs
This is where we mostly deal with graphical interfaces. The conversion between abstract shapes and actual displayed shapes will happen in this module.
- src/Graphic.hs
This is where the Graphic custom data type is defined. A Graphic consists of a Shape, a ColourName, and a Point which refersto location of the shape’s centre on the drawing canvas.
The additional files you will need for part B are:
- src/ClickToDraw.hs
This is home to the main function of the ClickToDraw program. You might want to edit this file during the warmup exercises.Remember to change it back before Part B though.
- src/Events.hs
This is where event handling occurs (key presses, mouse presses, mouse releases, etc.). Part B will utilise event handling to interact with the canvas, and this is where you make that happen.
- src/State.hs
Alongside event handling, we will also be doing some state transitions as your drawings evolve (adding/changing shapes). This is where the State data type is defined. You shouldn’t need to edit this file, but if you have any functions that are accessories of State, write them here.
Tasks
Before we head into programming this assignment, let’s start off with a few warmup exercises. These are not assessable and are completely optional so you can skip ahead to Part A. However, we included these exercises so that you can refresh your memory of the CodeWorld API (the graphical user interface library we are using) from Lab 4, as well as basic Haskell programming.
If a task does not have “Assessable” in its title in this specification, it can be assumed to not be assessable.
Warmup Exercises 1. Execute the program
Remember the joke in lecture about nobody executes Haskell programs? Well. Let’s change that. Without editing anything, try the cabal run Warmup command in the Terminal tool. You should see something like the following output:
~/comp1100/assignment1$
~/comp1100/assignment1$ cabal run Warmup Resolving dependencies... Configuring ShapeDrawing-0.3.2.0... Preprocessing executable 'Warmup' for ShapeDrawing-0.3.2.0.. Building executable 'Warmup' for ShapeDrawing-0.3.2.0..
[1 of 1] Compiling Main ( src/Warmup.hs, dist/build/Warmup/Warmu Linking dist/build/Warmup/Warmup ... Running Warmup... Hello world!
The file src/Warmup.hs contains the Haskell “Hello World” program. It simply prints Hello World to the terminal. You could of course simply load src/Warmup.hs into GHCi and call the main function by hand, but the cabal run Warmup command does the work of compiling the program into a machine executable and then running that program, without you having to do anything else. You can see the steps it takes to prepare and run the program in the messages about configuring, preprocessing, building, compiling, linking, and finally running:
- Resolving dependencies makes sure the libraries on which this program depends have been installed (such as CodeWorld).
- Configuring figures out the steps to take in building the program.
- Preprocessing gathers information for building.
- Building compiles the components of your program into machine code.
- Linking connects all of the components together to make a machine executable.
- Running your program is the last step.
2. Draw Something
Before anything, since we will be using the CodeWorld API, we need to import the library. Add the following code in
src/Warmup.hs below the line module Main where: import CodeWorld
After this, we will be able to use the functions in the CodeWorld library, as documented at https://hackage.haskell.org/package/codeworldapi0.2.2.1/docs/CodeWorld.html.
Start off by drawing a rectangle of some size using the drawingOf and rectangle function from CodeWorld. You replace putStrLn “Hello World!”.
3. Make it solid
So you’ve just drawn an outline of a rectangle, what about a solid rectangle? Or a solid circle?
Refer back to the CodeWorld API if you are stuck.
4. Move it somewhere
You may have experimented with drawing solid rectangles and circles, but they all appear to be in the centre of the screen. If you want it to be shifted horizontally or vertically, what should you do?
Hint 1: Think maths.
Hint 2: If you go to a place where you don’t speak the language, you need someone or a device to …?
Hint 3: Check out the CodeWorld API.
Hint 4: It starts with the letter “t”.
5. Colour it in
Eventually you will need to be able to draw shapes of different colours. So let’s try to make your horizontally and vertically shifted rectangle or circle not boring black. For example… red?
Hint is in the title.
6. More than one shape
If your program can only display one shape at a time, it’s a little bit boring. Try to draw something more interesting, such as a circle with a radius of 2 centred at and a square with sides of length 4 centred at on the same canvas.
7. Checkpoint: Warmup
It is always a good thing to checkpoint your work regularlyl, committing and pushing back to GitLab, so you don’t have a catastrophe if your dog eats your laptop. Now would be a good time to do that.
- Commit all of your changes: ~/comp1100/assignment1$
git commit -a -m "Finishing Warmup"
- Push your commits back to your remote GitLab repo: ~/comp1100/assignment1$
)6 ,3−( )3 ,3(
3. Make sure that everything is in place by going back and checking that your comp1100-assignment1 project on GitLab at https://gitlab.cecs.anu.edu.au contains all of your work so far.
Part A : TypeToDraw (Assessable)
In Part A we will be working with the TypeToDraw program. By now, you should have a rough idea of:
How to import other modules.
How to use the CodeWorld API documentation. How to utilise functions from the CodeWorld API. How to run a program using cabal.
With this knowledge in hand, let’s dive straight into Part A. You will complete the program for drawing Lines first, then come back for other shapes later. They can wait.
1. Line Shape To Picture
Let’s look at Shape more closely:
More specifically Line Point Point. This tells us that a Line contains two coordinates: the start and the end. This is an abstract representation of a line. To turn it into a Picture, we will need to use some of the functions from CodeWorld. More specifically: polyline, which takes a list of coordinates and draws a sequence of line segments between them. You can use this function to draw a straight line by having only two coordinates in the list.
Picture is not defined by us, but it is a custom type defined in the CodeWorld API. Whatever it is that needs to be drawn onto the canvas need to be a Picture before the main function can handle it. A Picture can be understood as the visual representation of the abstract shapes, thus it needs to have a shape, a colour, and a location. Since we can only apply one of these properties at once, we need to apply several transformations consecutively in order for our Picture to look the way we want it to. The order of such transformations do not matter, as the outcomes of
1. “Move a rectangle somewhere and colour it in”, and 2. “Colour a rectangle in and move it somewhere”
are the same.
You should complete the function shapeToPic :: Shape -> Picture in file src/View.hs to convert an abstract line into a CodeWorld
Picture, by pattern matching against the Line Shape.
You can test this function in GHCi by loading the View module using the Terminal command cabal repl TypeToDraw. You can then
test your shapeToPic function directly by entering an expression into GHCi, such as drawingOf (shapeToPic (Line (3, 3) (5, 7))). 2. ColourName to Colour
Just like the relationship between Shape and Picture, a ColourName is an abstract representation of some colour. To realise it in a CodeWorld picture, you will need to turn it into a value of the CodeWorld Colour type.
Provide a complete definition for the function colourNameToColour :: ColourName -> Colour in src/ColourName.hs which turns the abstract ColourName into an actual colour using functions from the CodeWorld API.
You can test this by running doctest on the file from the Terminal: doctest src/ColourName.hs. 3. Line Graphic To Picture
If you look at the definition for Graphic in src/Graphic.hs, you can see that it is defined by three elements: Shape, ColourName, and Point. The Shape is defined by the type of shape (Rectangle, Ellipse, Polygon, or Line) followed by the corresponding properties. For example, a rectangle is defined by its width and height, thus the Rectangle Side Side definition. A Graphic is an abstract representation of what the shape is. Irrespective of its colour or location, a rectangle will always have a width and height, as will ellipses with width and height.
Polygons and lines are defined a little bit differently. A polygon is defined by its vertices, and a line is defined by its start and end coordinates. As such, they are locationspecific. One way to get around this issue is instead of using absolute coordinates,
data Shape = Rectangle Side Side | Ellipse Side Side
| Polygon [Point]
| Line Point Point deriving (Show)
git push
we can use coordinates relative to each other and apply a shift afterwards. However, this is tedious and unnecessary. For the purpose of this assignment, we are going to be using absolute coordinates (coordinates as they appear on the canvas). This means that Polygons and Lines will have translations from the origin.
Using the shapeToPic and colourNameToColour functions that you have just written, and functions from CodeWorld API, partially complete the function graphicToPic :: Graphic -> Picture in src/View.hs for the case of Lines.
You can test this function with something like drawingOf (coordinatePlane & (graphicToPic (Graphic (Line (1, 1) (3, 3)) Cyan (0, 0)))).
You should see a Cyan line going from to .
4. Turn information into a Graphic
Once you have two Points and a ColourName, you can “generate” a Graphic for Lines. Complete the function getLineGraphic ::
Point -> Point -> ColourName -> Graphic.
Try it out!
The Line component of Part A is now completed! Try it out by running cabal run TypeToDraw.
5. Solid Rectangles 5.1 Rectangle To Pic
Just as with Line, complete the pattern matching for Rectangle in shapeToPic first. 5.2 Some calculations to get Graphic
If you look at the Shape.hs file, a Rectangle is defined by its width and height. However, we only have the information for the coordinates of the bottom right and the top left corners of the rectangle. From this we will need to calculate the width and height, as well as the coordinates of the centre of the rectangle so that it can be “shifted” properly from the origin.
It may be easier to think about this problem with an example: A rectangle with its two opposing corners at and has a width of 5 and a height of 8. A rectangle with its two opposing corners at (3, 9) and (4, 33) has a width of 1 and a height of 24. You might be able to see the pattern here. What if the two opposing corners are at (5, 8) and (0, 2)? What are the dimensions of this rectangle? Does the order of the two coordinates matter?
How would you calculate the coordinates of the centre of the rectangle given its two opposing corners?
You will then need to complete the function getRectangleGraphic :: Point -> Point -> ColourName -> Graphic. 5.3 Is it working?
Once you have completed the getRectangleGraphic function, you can test your rectangles. However, there may be a problem. What happens if you specify a rectangle to have a starting coordinates of and ending coordinates of ? Is the result what you have expected? Why not?
You will need to modify graphicToPic slightly to fix this problem. 6. Solid Ellipses
Ellipses are defined very similarly to Rectangles. Look back at how you completed the getRectangleGraphic, and complete getEllipseGraphic.
7. Solid Polygons
getPolygonGraphic has a slightly different type definition: [Point] -> ColourName -> Graphic. Ask yourself this question: Why is it defined this way?
Given a list of points (vertices) of a polygon, and a ColourName, how would you generate a Graphic which contains a Polygon? What kind of calculations will you need to perform?
Once you have answered these questions to yourself, complete the function getPolygonGraphic. 8. Testing!
First make sure that there are no warnings when you execute:
cabal clean
and
cabal build TypeToDraw
)8 ,5( )0 ,0(
)7 ,7( )3 ,3(
)3 ,3( )1 ,1(
)0 ,0(
The output should be something like:
~/comp1100/assignment1$ cabal clean cleaning... ~/comp1100/assignment1$ cabal build TypeToDraw Resolving dependencies...
Configuring ShapeDrawing-0.3.1.0... Preprocessing executable 'TypeToDraw' for ShapeDrawing-0.3.1.0.. Building executable 'TypeToDraw' for ShapeDrawing-0.3.1.0..
[1 of 6] Compiling ColourName [2 of 6] Compiling Shape [3 of 6] Compiling Graphic [4 of 6] Compiling State
( src/ColourName.hs, dist/build/TypeToDr ( src/Shape.hs, dist/build/TypeToDraw/Ty ( src/Graphic.hs, dist/build/TypeToDraw/ ( src/State.hs, dist/build/TypeToDraw/Ty ( src/View.hs, dist/build/TypeToDraw/Typ ( src/TypeToDraw.hs, dist/build/TypeToDr
[5 of 6] Compiling View [6 of 6] Compiling Main Linking dist/build/TypeToDraw/TypeToDraw ...
Then execute the program: cabal run TypeToDraw and see if the program does what you expect it to do for all shapes. Checkpoint: Part A submission
You must complete Part A before the first deadline, as noted above. You will submit your work so far back to GitLab as follows:
1. Commit all of your changes: ~/comp1100/assignment1$
git commit -a -m “Submitting Part A of Assignment 1” 2. Push your commits back to your remote GitLab repo:
~/comp1100/assignment1$ git push
3. Make sure that everything is in place by going back and checking that your comp1100-assignment1 project on GitLab at https://gitlab.cecs.anu.edu.au contains all of your work so far.
Part B: ClickToDraw (Assessable)
Part A completed a program that accepts input from users via the Terminal, and draws stuff on a static web page. That is sort of cool and satisfying, but not particularly userfriendly. It would be even more userfriendly to let users interact with the pictures using the mouse to draw shapes, right?
Receiving input from graphical user interface devices (mouse clicks, key presses, etc.) requires what’s called event handling. A program that receives these sorts of inputs is event driven since we cannot prompt the user in advance for a particular item of input (as we did for Part A with the keyboard). Instead, our programs must wait for particular events to arrive and decide what to do with them. A given event will trigger some action by our program. Thankfully CodeWorld has provided a simple interface function, called interactionOf, to handle the background tasks (such as capturing the events, and translating them to some nice data types, etc.), leaving us with only the task of assigning actions to various events. That’s a big part of what we are going to be doing in Part B of the assignment.
In Part B, we will be using the ClickToDraw program. interactionOf
The function interactionOf that is used in the main function of ClickToDraw is part of the CodeWorld API. Its type signature is:
Its type signature mentions the type world. This is not a specific type, but rather a type variable. We’ll get to that later; all we need to know for now is that this type can be any type we want it to be. This type is what we use to track and evolve the state of
interactionOf :: world -> (Double -> world -> world)
-> (Event -> world -> world) -> (world -> Picture) -> IO ()
our world as events occur. The first argument is an initial state for our world when the program first begins (in our case, presumably this will be our abstract representation of an empty drawing).
The remaining arguments allow us to register event handlers that respond to different events and work with the state of a world (for example, our abstractions of graphics) to transform it accordingly.
These remaining arguments must themselves be functions. They allow us to respond to events in several event domains. The second argument of type Double -> world -> world allows us to register a handler that responds to the passage of time: it will receive a Double and the state of our world, and let’s us compute a new state for our world according to the time.
The third argument of type Event -> world -> world handles input events from the user. We will look at the event type shortly, but suffice to say its values are things like key presses and releases, mouse presses and releases, and mouse movements.
The fourth argument is a function that renders the state of our world as a CodeWorld Picture, so that what you see on screen can change over time according to our world model.
Let us try to use this function in a simple way. Open the file src/WarmupInteraction.hs: src/WarmupInteraction.hs
{-# LANGUAGE OverloadedStrings #-}
module Main where import CodeWorld
type Coord = (Double, Double) initialCoord :: Coord
initialCoord = (0,0) main :: IO()
main = interactionOf initialCoord handleTime handleEvent drawState handleTime :: Double -> Coord -> Coord
handleTime _ c = c
handleEvent :: Event -> Coord -> Coord handleEvent e (x,y) = case e of
KeyPress "Up" -> (x,y+1) KeyPress "Down" -> (x,y-1) KeyPress "Left" -> (x-1,y) KeyPress "Right" -> (x+1,y) _ -> (x,y)
drawState :: Coord -> Picture drawState (x,y) = translated x y (solidCircle 1)
You can run this program using the command cabal run WarmupInteraction. Open the indicated URL in a browser and start typing the up and down arrow keys.
In this example, our world is simply a pair of coordinates, with initial state . We do nothing for the time domain, but we respond to user input events by computing a new coordinate depending on which of the up, down, right, and left arrow keys have been pressed by the user. We view the state of our world as a circle drawn at the current coordinate.
Of course, the state of the world for our picture drawing program will be all of the shapes that the user constructs through the interface. We can define a data type State accordingly:
src/State.hs
This is a custom data that combines values of three different other types: a list of Graphics, a Tool describing which drawing tool the user is currently using to draw with, ColourName which specifies what colour they are using.
Our initialState looks like: World [] (RectangleTool Nothing) Black, this indicates that there our initial drawing contains no shapes, the default initial RectangleTool is for drawing rectangles, and the colour to use for drawing is Black. We will arrange for users to switch tools using key press events.
data State = World [Graphic]
Tool ColourName
0,0
Functions handleTime, handleEvent, and drawState are more interesting. They represent the way we handle time, user interface events, and render ou drawings on screen, respectively.
handleTime
Looking back at interactionOf, the first function it asks for needs to have a type signature of Double -> world -> world. Double is the time in seconds since the program started running, and is updated constantly. The world argument to interactionOf represents the current state of the drawing, and the output world allows us to compute a new state at each time step. This is why the function is called handleTime—it handles time. Of course you can call it whatever you want, but handleTime says what it does pretty well. If the state doesn’t vary with time, this function can be a simple identity function that just ignores the time and returns the input state:
If we wanted the state to change with respect to time (in animations for example), you could make more interesting use of the handleTime.
handleEvent
The handleEvent function works in very similar ways, but instead of time in seconds (represented by Double), it defines how the state “reacts” to various events, such as KeyPress, KeyRelease, MousePress, etc. You will need to us the handleEvent to accept responses from the user, specifically KeyPress, MousePress, and MouseRelease events.
handleTime :: Double -> State -> State handleTime _ s = s
If the 1.
2.
3. 4. 5.
user wants to draw a Cyan line by clicking on the screen at coordinates and releasing the mouse at coordinates starting at a blank canvas, the state would transition as follows:
World [] (RectangleTool Nothing)
This is the initialState described above.
The user presses “L” to select the line tool and change the state to World [] (LineTool Nothing) Black
Since there are no mouse inputs just yet, the starting coordinates are Nothing for now.
The user presses “C” to select the colour Cyan, changing the state to World [] (LineTool Nothing) Cyan
The user clicks the mouse button at changing the state to World [] (LineTool (Just (1, 1))) Cyan
The user releases the mouse button at changing the state to World [Graphic (Line (1.0,1.0) (2.0,2.0)) Cyan (0.0,0.0)] (LineTool Nothing) Cyan
Note that the Tool and the ColourName do not reset to the default values after a shape has been drawn. However, the Maybe Point inside Tool reverts to Nothing.
drawState
The drawState function is very different from handleTime and handleEvent. Its job is to visually represent the state at any given time. For example, if the state is World [] Nothing Nothing, there should not be any shapes drawn on the screen. However, at the end of the interaction steps above, with the state World [Graphic (Line 6.0 6.0) Cyan (0.0,0.0)] (LineTool Nothing) Cyan, we want to construct a CodeWorld Picture with a Cyan line centred at the origin.
Putting it together
Function interactionOf handles the time increment automatically. So as the time changes, the functions are called as follows: drawState (handleTime t s)
If there is an event happening, the functions are called as follows:
drawState (handleEvent e s)
Debug Features
If you look at src/Events.hs, you will see a partially completed handleEvent function. These few lines of code enables you to debug your program more easily. When running the program, and the “D” key is pressed, the program will print the current “State” as a string to the terminal. This gives you complete oversight of the underlying state, so you can make a judgement as to whether your program is doing what you intend it to.
Event Handling
To make this program interactive, you will need to implement the handleEvent, which is called every time there is some sort of event happening, such as mouse click, key press, etc. With the framework that has been given to you, there is already an
)1 ,1(
)2 ,2( )1 ,1(
)2 ,2(
example of how to handle KeyPress events:
handleEvent e _ = case e of KeyPress key
| key == "Esc" -> initialState | key == "D" -> trace (pack (show s)) s
1. Handle the keys (Not assessable)
Let’s try some rudimentary key handling. Your task is to display the message “Hello World” in the terminal when the “H” key is pressed.
Please note that the event is only captured in the web page. As such, you will need to run your program, launch a web browser, and press “H” within the blank canvas before checking the terminal to see if the event capture has been successful. The best option is to have your IntelliJ window and your Web browser open side by side.
After you have achieved this, feel free to remove the “H” key actions from the event handler.
2. Specify the shape
Now that you have a basic understanding of event handling involving keys, you can start manipulating the state based on the key presses.
First make sure that when the canvas is initially drawn, no objects are on it.
The idea, is that the user presses one of four keys corresponding to one of four shapes, and that this selects the appropriate
Tool to be reflected in the State. For example:
1. State: World [] (RectangleTool Nothing) Black 2. User input: “E”
3. State: World [] (EllipseTool Nothing) Black
The keytoshape mapping can be found in the toolKeyMap function in src/Shape.hs. If the user presses a key that doesn’t correspond to any shapes, the state should remain unchanged:
1. State: World [] (RectangleTool Nothing) Black 2. User input: “X”
3. State: World [] (RectangleTool Nothing) Black
You can check if the state transitions are correct by pressing the “D” key which will print out the current state in your Terminal.
3. Specify the colour
If the user presses one of five keys that correspond to certain colours, the ColourName in the state should change to that colour. For example:
1. State: World [] (RectangleTool Nothing) Black 2. User input: “O”
3. State: World [] (RectangleTool Nothing) Orange
As with the shape, if the user’s input doesn’t correspond to a colour, the state remains unchanged:
1. State: World [] (RectangleTool Nothing) Black 2. User input: “Q”
3. State: World [] (RectangleTool Nothing) Black
Note that the user can select a colour first then select a shape, or the other way around. The order of such operations should not matter in your program. This is a strong advantage of eventbased input interfaces as opposed to textbased prompts.
4. Handle the mouse clicks
In order to draw a rectangle (two of the sides of which are parallel to the axis, the other two parallel to the axis), you will need the coordinates of two opposing corners. For this assignment, we will specify the top left corner, and the bottom right corner. The user will specify the corners by clicking and dragging. The point at which the user clicked would be the first of these corners, and the point at which the user released the mouse button would specify the other.
As such, there are two mouse Eventss that you need to capture: MousePress and MouseRelease:
data Event = ... ...
MousePress !MouseButton !Point MouseRelease !MouseButton Point
yx
You can ignore the exclamation points here (though COMP1130 students might be interested to explore further what they mean given our pending discussions of strictness and laziness).
You can extract the MouseButton field and the Point field the same way that you have done with KeyPress using pattern matching. Afterwards you will have to check which mouse button the user pushed (hint: MouseButton supports equality checks).
In order to have a feel of what you should do to handle mouse clicks, make your program such that every time there is a click, you print out the coordinates for the click in the terminal using trace. After you have played around with MousePress and MouseRelease events, it’s time to incorporating them into your final program.
Once a MousePress event has been registered, you can fill the Maybe type in the Tool with the starting coordinates. Once a MouseRelease event has been registered, you will need to pass the current State and the mouserelease coordinates into drawNewGraphic. If you are drawing a polygon, the Maybe Point of drawNewGraphic is set to Nothing as we do not need the mouse release coordinates for polygons. As such, you can ignore the MouseRelease events for PolygonTools.
By the time you have finished this section, you should be able to modify the state such that it contains:
1. The list of shapes that are currently on screen (for now it should be empty) 2. The tool you are using to draw along with the start coordinate
3. The colour you want the shape to be
6. Update the State
Remember the functions get***Graphics which you wrote for Part A of the assignment? Use them inside the function drawNewGraphic, which:
- Updates the list of graphics so that they appear on screen, and
- Resets the Maybe Point inside the Tool to Nothing (for RectangleTool, EllipseTool, or LineTool, or if it’s a
PolygonTool, revert the list to empty.
For example:
7. The Trigger
Upon mouse release, you can be certain the all the fields that needed to be filled have been filled. Therefore at this point, you can call the drawNewGraphic function with the MouseRelease coordinates to add the shape to the list of shapes on the screen. This way, the shape is automatically drawn when the user releases the mouse button.
You should implement this trigger in handleEvent.
After implementing the trigger, you should be able to draw lines, rectangles, and ellipses on the canvas. 8. Polygons
Polygons are a bit more tricky than rectangles and lines to handle. The shape is determined by all of its vertices, so the user needs a way to specify a list of vertices. We have come up with a way to achieve this, and from the user’s perspective, if they want to draw a polygon:
1. Press “P” to specify it’s a polygon.
2. Choose a colour (or choose a colour first then select polygon).
3. Start clicking on the screen. Every click specifies a single vertex.
4. Press the “Spacebar” to indicate that all the vertices have been specified. 5. You can expect to see a polygon on the screen.
Therefore for polygons, as we mentioned before, you need to ignore the MouseRelease events, as well as checking for the KeyPress event for the space key. Every time there is a MousePress event, you should append the current coordinates to the list inside the Line type.
For example, the state should change like this:
1. State: World [] (RectangleTool Nothing) Black 2. Press “P”
3. State: World [] (PolygonTool []) Black
4. Press “G”
5. State: World [] (PolygonTool []) Green
>>> drawNewGraphic (World [] (RectangleTool (Just (0, 0))) Yellow) (Just (3, World [Graphic (Rectangle 3.0 7.0) Yellow (1.5,3.5)] (RectangleTool Nothing)
> drawNewGraphic (World [] (PolygonTool [(1, 3), (4, 5), (6, 6)]) Green) Not World [Graphic (Polygon [(1.0,3.0),(4.0,5.0),(6.0,6.0)]) Green (0.0,0.0)] (P
6. Click on (3, 5)
7. State: World [] (PolygonTool [(3.0,5.0)]) Green
8. Click on (6, 8)
9. State: World [] (PolygonTool [(3.0,5.0),(6.0,8.0)]) Green
10. Click on (9, 2)
11. State: World [] (PolygonTool [(3.0,5.0),(6.0,8.0),(9.0,2.0)]) Green
12. Press spacebar
13. State: World [(Polygon [(3.0,5.0),(6.0,8.0),(9.0,2.0)])] (PolygonTool []) Nothing
9. Remove stuff
Make it so that when the “Backspace” key is pressed, the last drawn shape is removed from the drawing. Get rid of warnings
If you execute the commands cabal clean and cabal build in the Terminal, there should not be any warnings. If there are, read them and fix the problems. The output should be something like the following:
~/comp1100/assignment1$
~/comp1100/assignment1$ cabal clean cleaning... ~/comp1100/assignment1$ cabal build Resolving dependencies... Configuring ShapeDrawing-0.3.2.0... Preprocessing executable 'WarmupInteraction' for ShapeDrawing-0.3.2.0.. Building executable 'WarmupInteraction' for ShapeDrawing-0.3.2.0..
[1 of 1] Compiling Main ( src/WarmupInteraction.hs, dist/build/W Linking dist/build/WarmupInteraction/WarmupInteraction ... Preprocessing executable 'Warmup' for ShapeDrawing-0.3.2.0.. Building executable 'Warmup' for ShapeDrawing-0.3.2.0..
[1 of 1] Compiling Main ( src/Warmup.hs, dist/build/Warmup/Warmu Linking dist/build/Warmup/Warmup ... Preprocessing executable 'TypeToDraw' for ShapeDrawing-0.3.2.0.. Building executable 'TypeToDraw' for ShapeDrawing-0.3.2.0..
[1 of 6] Compiling ColourName [2 of 6] Compiling Shape [3 of 6] Compiling Graphic [4 of 6] Compiling State
( src/ColourName.hs, dist/build/TypeToDr ( src/Shape.hs, dist/build/TypeToDraw/Ty ( src/Graphic.hs, dist/build/TypeToDraw/ ( src/State.hs, dist/build/TypeToDraw/Ty ( src/View.hs, dist/build/TypeToDraw/Typ ( src/TypeToDraw.hs, dist/build/TypeToDr
[5 of 6] Compiling View [6 of 6] Compiling Main Linking dist/build/TypeToDraw/TypeToDraw ... Preprocessing executable 'ClickToDraw' for ShapeDrawing-0.3.2.0.. Building executable 'ClickToDraw' for ShapeDrawing-0.3.2.0..
[1 of 7] Compiling ColourName [2 of 7] Compiling Shape [3 of 7] Compiling Graphic [4 of 7] Compiling State
( src/ColourName.hs, dist/build/ClickToD ( src/Shape.hs, dist/build/ClickToDraw/C ( src/Graphic.hs, dist/build/ClickToDraw ( src/State.hs, dist/build/ClickToDraw/C ( src/Events.hs, dist/build/ClickToDraw/ ( src/View.hs, dist/build/ClickToDraw/Cl ( src/ClickToDraw.hs, dist/build/ClickTo
[5 of 7] Compiling Events [6 of 7] Compiling View [7 of 7] Compiling Main Linking dist/build/ClickToDraw/ClickToDraw ...
The order of the modules may not match the output above. This is okay! Just make sure that there aren’t any errors or warnings.
Part B Program Summary
Here is a quick summary of the behaviour you should have at this point:
- When the user presses a key, if it corresponds to a shape defined by the toolKeyMap, the state is changed such that it’s ready to draw that shape. Otherwise the state remains unchanged.
- When the user presses a key, if it corresponds to a colour defined by the colourKeyMap, the state is changed such that it’s about to draw the shape specified in step 1 in this colour. Otherwise the state remains unchanged.
- If the shape is:
A rectangle, a line, or an eclipse: The user will then be able to click and drag on the screen.
A polygon: The user will then be able to specify the vertices by clicking on the screen. Each click will register as one vertex. When the user presses the space key, the polygon is drawn. - The user will be able to draw other shapes on top of existing shapes.
- If the user presses the backspace key, the top most shape is deleted.
- You pass all the doctests.
Extensions
If you are doing COMP1100, you do not have to do any of the extensions. However, if you want to, you are welcome to! Doing so will not affect your assignment mark.
If you are doing COMP1130, you will need to do one extension from Pool A, and one from Pool B. If you do more, you need to specify in your report which ones you’d like to have marked. If you do not specify this, the first completed extension from each pool will be marked.
For extensions, there will be no instructions for progressing through the tasks. This gives you the creative freedom to seek a solution yourself. You may modify any part of the program. However, the basic functionality (the ones listed in the “Tasks” section) should remain. Your program is also expected to compile without warnings or errors.
Note: The extension functionalities must all be executed through the CodeWorld API and the interactionOf canvas. All interactions from the users must come from handleEvent (although you can modify handleEvent however you like). The first five fields of State must remain unchanged, but you can add more fields after the original fields.
Pool A Extension A.1.
Add the ability to reshape existing nonpolygon shapes. The user will click on a point on the canvas, and the top most shape which occupies that point is selected for reshaping. The user then will be able to draw the shape again which would remain on its original layer (i.e. if there are other shapes that partially cover this shape, the other shapes should remain partially covering the reshaped one too.)
Extension A.2.
Add the ability to add an additional vertex to an existing polygon. The user will click on a point on the canvas, if there is a polygon that occupies that point, such polygon is selected to add a new vertex to. The user will then click on a point on the canvas, which will be added as the last vertex of such polygon, and the canvas is updated.
You can also add the ability to add multiple vertices in one go, but this will not yield a higher mark.
Extension A.3.
Implement layers.
When the user clicks on a point on the canvas, the top most nonpolygon shape which occupies that point is selected. Then the user can move the layer that the shape is on “up” and “down” by using “P” and “L” keys respectively. (Each shape occupies its own layer.)
Pool B Extension B.1.
Add the ability to draw rectangles and polygons in real time.
Currently for rectangles, the shape is only drawn after the user has released the mouse button. Implement your solution so that as the user drags the mouse, the rectangle is repeatedly drawn between the “click” point, and the current mouse pointer’s coordinate. After the mouse is released, the rectangle is finalised.
For polygons, currently the polygon is only drawn after the user has pressed the space key. Implement your solution so that every time the user clicks on the canvas (when there are more than 2 vertices that have already been specified), a polygon between the existing vertices are drawn. When the space bar is pressed, the polygon is finalised.
Extension B.2.
The user will specify three points on the canvas, and your task is to draw a circle such that the three points all lie on the perimeter of that circle.
Report
You should write a concise technical report explaining your design choices in completing your program. The maximum word count is 1000 for COMP1100 students. For COMP1130 students, who will need to explain the implementation of extensions, the maximum word count is 2000. This is not a required word count—concise presentation is a virtue. If you are in COMP1130 and can explain the aspects of your extensions in detail in 1000 words, it is better to submit the 1000 word report than to waste your time trying to get it to 2000. Similarly for COMP1100.
Once again: These are not required word counts. They are simply the maximum number of words that your marker will read. If you can do it in fewer words without compromising the presentation, please do so.
Your report must be in PDF format, located at the root of your assignment repository on GitLab and named Report.pdf. Otherwise, it may not be marked.
The report should have a title page with the following items:
Your name
Your laboratory time and tutor Your university ID Collaborators, if any.
Content
Keep in mind that your audience is your tutors and the lecturers, who are proficient at programming and understand most programming concepts. Therefore, for example, describing how recursion works in your report is not necessary. After reading a technical report, the reader should be comprehensively aware of what problem the program is trying to solve, the reasons for major design choices in the program, as well as how it was tested.
Below is a list of questions that might be discussed in your report:
What are the objectives of your functions?
What were the conceptual or technical issues that you encountered whilst doing the assignment, and how did you get past them?
What were the assumptions that you had to make during the assignment?
What would you have done differently if you were to do it again?
What are the major design choices you have made in your program?
Why have you made those choices?
How would you make your program better?
How did you test your program? What were the results?
Which parts of your program can be confusing for the reader? Explain them if so.
Aside from what should be in your report, here are some things that should definitely not be in it:
Any content that is not your own.
Grammatical errors or misspellings. Proofread it before submission.
Informal language. This includes unnecessary abbreviations (atm, btw, ps, and so on), emojis, and emoticons. Keep in mind that this is a professional document.
Diagrams, graphs, and charts that are not relevant. Any unnecessary elements will distract from the actual content. Keep it succinct and focused.
Format
You are not required to follow any specific style guide (such as APA or Harvard). However, here are some tips which will make your report more pleasant to read, and make more sense to someone with a computer science background.
Colours should be kept minimal. If you need to use colour, make sure it is absolutely necessary.
If you are using graphics, make sure they are vector graphics (that stay sharp even as the reader zooms in on them). Any code that appears in your document should have a monospaced font (such as Consolas, Courier New, Lucida Console, or Monaco)
Any other text should be set in serif fonts (popular choices are Times, Palatino, Sabon, Minion, or Caslon).
When available, automatic ligatures should be activated.
Do not use underscore to highlight your text.
Text should be at least 1.5 spaced.
Paragraphs should be justified with automatic hyphenation.
Marks
This assignment is worth 10% of your total marks for the course. It is marked out of 100. The mark distribution is as follows: Part A:
Code: 30 marks Part B: TOTAL 100 marks
For COMP1100, Code: 70 marks (Part A + Part B)
For COMP1130, Code: 70 marks (Part A + Part B + Extensions) Report: 20 marks (if only Part A completed), 30 marks otherwise
Communicating
Do not post your code publicly, either on Piazza or via other forums. If by mistake you post your code publicly on Piazza, an email containing your post with your code will be sent to all students. This means that others will have access to your code and you may be held responsible for plagiarism.
Once again, and we cannot stress this enough: do not post your code publicly. If you need help with your code, post it privately to the instructors.
When brainstorming with your friends, do not show your code to them. There might be some pressure from your friends and even some judgements that you are not sharing the code, but it is for both your and their benefit. Anything that smells of plagiarism will be dealt with seriously and there may be serious consequences.
Sharing ideas is perfectly fine, but sharing should stop at ideas.
Course staff will not look at assignment code unless it is posted privately in piazza.
Course staff will typically give assistance by directing you to relevant exercises from the labs, or definitions and examples from the lectures.
Before the assignment is due, course staff will not give individual tips on writing functions for the assignment or how your code can be improved. You will receive their feedback when the mark is released.
Submission Checklist
Preferably 24 hours prior to the assignment deadline, you should make sure that:
You have fully read and understand the entire assignment specification.
Your work so far is pushed to GitLab.
Your program works on the lab machines—if the program does not work on the lab machines, it might fail tests used by the instructors.
The report is in PDF format, located at the root of your project on GitLab and named Report.pdf. Otherwise, it may not be marked.
UPDATED: 22 Mar 2018/ RESPONSIBLE OFFICER: Director, RSCS/ PAGE CONTACT: Course Convenor