Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save acron0/2cf26dd1244210032a7338ad75d4e406 to your computer and use it in GitHub Desktop.
Save acron0/2cf26dd1244210032a7338ad75d4e406 to your computer and use it in GitHub Desktop.
ClojureBridge learning path using Maria.cloud - fork me and explore Clojure using shapes, colours and animation
;; # Colours and Shapes in Clojure
;; This web page allows you to ask your computer to do tasks for you, specifically draw one or more shapes and change the colour of those shapes. We ask the computer to do these tasks in a programming language called Clojure.
;; Clojure is a functional programming language. The behaviour of your code is expressed by calling one or more functions.
;; Each function has a particular thing it does when you call it. We will discover functions that:
;; * draw particular shapes
;; * add a colour to a shape
;; * help you combine shapes
;; You will also discover several common functions used for functional programming, `map`, `filter` and `reduce`.
;; ## Create a shape by evaluating a function
;; A function is called by putting its name within a `list`. A `list` is represented by an open and close round bracket: `()`
;; We have create several functions that will draw a shape for you in this web page. You just need to call the function and tell it how big a shape you want.
;; For example, when we call the `circle` function, we need to tell it what size of circle to draw.
;; Put the cursor at the end of the code below (in the white box) and press `Ctrl+Enter` on a PC or `Cmd+Enter` on a Mac
(circle 25)
;; #### Exercise: Create a circle of size 50 (or any other size you like)
()
;; ## Arguments
;; A function can take zero or more arguments. Arguments affect the behaviour of a function.
;; So if you give a bigger number as an argument to the `circle` function, then a bigger circle is drawn. The argument represents the radius of the circle.
;; What happens if we give `circle` more that one argument
(circle)
(circle 20 30)
;; Arguments must match the same number as the argument takes
;; Different functions can take a different number of arguments
(rectangle 20 10)
;; ### Understanding what a function does
;; Functions in Clojure are documented, so you can ask them what they do. The documentation should also tell you what arguments that function takes.
(doc rectangle)
;; #### Exercise: Create other shapes by evaluating different functions
;; There are other shapes you can create, using `rectangle` and `triangle`.
;; Use the `doc` function to understand what each of those shapes does and what arguments to provide
(doc )
;; ## Functions return a value
;; func returns a value
(+ 1 2 3 4 5)
;; because functions take a value, then you can use func as an argument
(circle (* 5 5))
;; Make a rectangle that is half high as it is wide
(rectangle 40 (/ 40 2))
;; ## Adding Colours
;; We can give our shapes colours by using the `colorize` function
;; A colour is a string
(colorize "green" (circle 25))
(colorize "blue" (circle (* 5 5)))
;; Creating a rectangle
(rectangle 250 100)
;; Now colourise the rectangle
(colorize "blue" (rectangle 250 100))
;; # Making more complex shapes
(doc position)
;; We can put shapes on top of each other using the `layer` function.
(doc layer)
;; ### Example: A halloween pumpkin
(layer
(position 40 60 (colorize "orange" (circle 40)))
(position 10 30 (colorize "black" (triangle 24)))
(position 45 30 (colorize "black" (triangle 24)))
(position 20 75 (colorize "black" (rectangle 40 10)))
(position 25 74 (colorize "orange" (rectangle 10 5)))
(position 45 74 (colorize "orange" (rectangle 10 5)))
(position 33 82 (colorize "orange" (rectangle 10 5)))
(position 35 2 (colorize "black" (rectangle 10 20))))
;; # Collections
;; So far we have seen simple values, such as 1, "blue", ....
;; We can have groups of values and use them to generate multiple shapes.
;; We are going to use a Vector to hold our collection. A vector is defined with square brackets `[ ]`
;; We can put the size of squares we want in a collection and then create them
(map square [2 4 8 16 32 64 128])
;; Experiment with other shapes and values...
(min 30 20 42 60 22 42 38 62 24)
;; Here we have a collection of colours, represented as strings. A string is defined using a pair of double quotes `" "`
["red" "orange" "yellow" "green" "blue" "indigo" "violet"]
;; > We can put any kind of value into a collection (even functions, but we will see that a little later).
;; We can give our collection a name. This name represents the collection and we can use that name rather than type all the values of the collection everywhere we use it.
(def rainbow ["red" "orange" "yellow" "green" "blue" "indigo" "violet"])
;; A name can be evaluated, and it will return the collection
rainbow
;; ### Nested Collections: Periodic table
;; We can group simple values together in collections.
;; We can group collections of collections in collections.
(def periodic-table-of-elements
[["gold" "Au"] ["silver" "Ag"] ["platinum" "Pt"] ["palladium" "Pd"] ["Tennessine" "Ts"]])
;; > Clojure has many functions that are very good at working with different shapes of data.
;; Using groups of values is good where a shape needs more than one argument
;; First we will create a little helper function for you
(defn draw-rectangle [[width height]]
(rectangle width height))
(map draw-rectangle [[2 3] [4 6] [8 12] [16 24] [32 48] [64 92]])
;; ## What colours are available
;; We have given a name to the collection of colours available in the course. Evaluating the name shows you the collection
color-names
;; How did we get the coloured shapes in the collection of colours? Lets look at that and build our own collection.
;; ## Building our own collection
;; > Remember: Calling a function returns a value, so we can also include a function call as an element of a collection.
;; Lets see this by defining our favourite colours
;; Here is a very simple representation with two colours
(def my-fav-colours-simple ["blue" "green"])
;; We can now refer to this collection by its name.
my-fav-colours-simple
;; > Note: We can add more colours to our collection and re-evaluate it with `Ctrl+Enter` or `Cmd+Enter` to update the value that `my-favourite-colours` refers to.
;; We also want to see what the colour looks like, so we can include a function that will draw a shape with that particular colour
(def my-fav-colours
[["blue" (colorize "blue" (square 25))]
["green" (colorize "green" (square 25))]])
my-fav-colours
;; ## Avoid duplicate code using Iteration
;; In the `my-fav-colors` collection we used two lines of code to draw a coloured shape that were very similar. If we had a large number of colours in our collection, that would mean a lot of extra code.
;; If we needed to change the code that created the coloured shape then we would need to change a lot of our code
;; We can use a function to [iterate](https://dictionary.cambridge.org/dictionary/english/iterate) over a collection and avoid duplication. If we needed to make a change, then we would only need to change the function we used.
;; > The `map`, `filter` and `reduce` functions are often used to iterate over collections
;; ### The `map` function
;; The map function will take each value in turn from the collection.
;; The map gives that value to a function, evaluates the function with the value as an argument and keeps the result.
;; The map function keeps going until it has a new value for each of the existing values in the collection.
;; ![Clojure map function visualised](https://www.braveclojure.com/assets/images/cftbat/core-functions-in-depth/mapping.png "Clojure map function visualised")
;; Here is a simple example of `map` function. Evaluate the code and see what result you get.
(map inc [1 2 3 4 5])
;; > Feel free to experiment with different numbers if it helps
;; #### A little helper function
;; Here is a function that we have written for you.
;; This function creates a new collection with a coloured square added to each value from the original collection, using the colour from each value for the square.
(defn create-collection-with-color-shape
[color-name]
[color-name (colorize color-name (square 25))])
;; > We will talk about defining functions soon, for now just think of them as a way to wrap up some behaviour that you want to call more than once.
;; Now we can use `map` with this helper function to create a new collection with a coloured shape as well as our original collection of colour names.
(mapv create-collection-with-color-shape
my-fav-colours-simple)
;; ## Any colour so long as its blue
;; We created another function for you that shows you all the shades of a colour available.
;; For example, if you want all the shades of blue:
(colors-named "blue")
;; > How would you write some code to show just the shades of a colour?
;; >
;; > The `map` function could be helpful, however, there is a more specific function called `filter` that will make things simpler.
;; ## filter - its not just for coffee
;; The `fiter` function is similar to the `map` function as it also iterates over a collection.
;; `filter` only returns values that match a certain criteria. That criteria is defined by the function used with `filter`.
;; > The `filter` function uses a [predicate function](https://en.wikipedia.org/wiki/Predicate_(mathematical_logic)), one that returns true or false when given an argument. In Clojure we put a question mark, `?` at the end of the name of predicate functions, because they are asking if something is true or false.
;; For example, `odd?` will return true if its argument is odd and false if its argument is even.
(odd? 1)
;; `odd?` only takes one argument.
;; So if we want to have just the odd numbers from a collection, we need help from another function that iterates over collections.
;; We have seen that `map` iterates over a collection, so we could try that
(map odd? [1 2 3 4 5 6 7 8 9])
;; Using `odd?` with `map` creates a collection of true or false values. We wanted just the odd values themselves.
;; Lets use `odd?` with `filter`
(filter odd? [1 2 3 4 5 6 7 8 9])
(filter even? [1 2 3 4 5 6 7 8 9])
;; ## Filtering our favourite colours
;; With help from `filter` we can now filter our favourite colours and just get specific shades of blue.
;; Lets add some more colours
(def more-fav-colours-simple ["blue" "lightskyblue" "darkslateblue" "midnightblue"
"green" "springgreen"])
;; Here is a helper function that will determine if a colour is a kind of blue, if you evaluate this function, then we can use it in our filter
(defn is-blue? [color]
(clojure.string/includes? color "blue"))
(filter is-blue? more-fav-colours-simple)
;; But we do not have any coloured shapes in our result!
;; #### Adding coloured shapes into our filtered favourite colours
;; We have our new collection, that contains shades of the colour blue.
;; We can use map to create a new collection that also contains the coloured shapes, just like before.
(mapv create-collection-with-color-shape
(filter is-blue? more-fav-colours-simple))
;; # Defining functions
;; TODO: more examples of defining functions
;; Defining functions allows you to create your own specific behaviour.
;; We have seen two function definition already
;; * `create-collection-with-color-shape`
;; * `is-blue?` function, so lets have a look at this in more detail
;; #### nice picture of the syntax of a function
;; ## Reduce
;; TODO: example of using reduce to create a result from a collection of information
;; ### Finding the smallest / largest circle
;; If we draw a range of circles, how do we know which is the biggest or smallest? Sometimes we need to see the number values too
;; If we use map and `circle` functions to create a collection of circles, can we easily tell which is the biggest and which is the smallest
(map circle [30 20 42 60 22 42 38 62 24])
;; Hmm, seems a little tricky to know just by looking at the circles.
;; If we have a lot more circles, then it would also take time to work it out.
;; If we just look at the circle sizes in the collection we can read through them all and see which is the biggest and smallest.
;; Or we can use another function to help us.
;; Lets use `min` to find the smallest number
(min [30 20 42 60 22 42 38 62 24])
;; > `min` does not give us the correct answer if we give it a collection as an argument, it needs individual values
;; Here is how min likes to work
(min 30 20 42 60 22 42 38 62 24)
;; Is there a way to use `min` with the collection though?
(map min [30 20 42 60 22 42 38 62 24])
;; > `map` returns a collection, so in this case its not the function we want.
;; `reduce` will aggregate all the numbers in the collection, using the function we use with it. So if we use `reduce` with `min` we get a single answer
(reduce min [30 20 42 60 22 42 38 62 24])
;; And the same for a maximum value
(reduce max [30 20 42 60 22 42 38 62 24])
;; # Building more complex shapes
;; Lets have some more fun with shapes.
;; The `beside` function will put shapes beside each other
(doc beside)
;; If we give `beside` several shapes, it will draw them beside each other.
(beside (circle 50) (circle 25))
;; Rather than have lots of calls to the `circle` function, we can create a collection of sizes of circles we want to draw next to each other.
(def my-circle-sizes [50 25 42 64 18 21 47])
;; We can just `map` the `circle` function over this collection as we have before, but this draws the circles on different lines
(map circle my-circle-sizes)
;; If we want all the circles in a straight line, we can use `beside`.
(beside (map circle my-circle-sizes))
;; ### Rotating shapes
;; We can use `beside` and a helper function to draw a rotating shape.
;; The shapes will change the 'hue' (colour) of each shape drawn as well as rotate the shape a little.
(defn rotating-shapes [change-value]
(rotate change-value (rectangle 30 50)))
(beside
(map rotating-shapes (range 0 360 15)))
;; Oh, the shapes do blur into each other, so lets create another function to change the colour as we rotate
(defn rotating-shapes-and-colors [change-value]
(fill (hsl change-value 90 45)
(rotate change-value (rectangle 30 50))))
(beside
(map rotating-shapes-and-colors (range 0 360 15)))
;; The colours are still blurring into each other when they are a similar shade. If we make the shapes partially transparent, then we can see all the shapes
(defn rotating-shapes-and-colors-semi-opaque [change-value]
(fill (hsl change-value 90 45)
(opacity 0.5
(rotate change-value (rectangle 30 50)))))
(beside
(map rotating-shapes-and-colors-semi-opaque
(range 0 360 15)))
;; ## TODO: List Comprehension - `for`
;; Creating a combination lock
;; Visualise a three (or more) tumbler combination, each tumbler representing the numbers 0 to 9.
;; Animate the lock by cycling through all the combinations
;; Add a lock cracker - specify a lock code and let the cracker randomly select values until it finds the specified lock code.
;; # Challenge: Create a bee
;; > TODO: Add section on layers and cells, provide lots of hints on how to solve the challenge
;; Create a bee from the shapes we have
(circle 50)
(colorize "yellow" (circle 50))
;; We will create three layers for the bee
;; * `bee-head` is a circle with a face
;; * `bee-tail` is a circle with a triangle as a stinger
;; * `bee-body` is a series of rectangles that will be alternative colours, using cycle to alternate between black and yellow, taking enough colours each time for the body (starting with the opposite colour than last time)
;; Each layer is defined with a name using the `let` function.
;; Then `layer` is used to combine all the layers into one drawing
(beside (map (fn [color] (colorize color (rectangle 30 100)))
(take 5 (cycle ["black" "yellow"]))))
(defn bee-part []
(let [body-size 5
bee-body-colors
(take body-size
(cycle ["black" "yellow"]))
bee-body (layer
(beside
(map
(fn [color]
(colorize color
(rectangle 30 100)))
bee-body-colors)))
] ;; end of bee components
;; assemble all the bee components
(layer
bee-body
)))
(bee-part)
(defn bee []
(let
[bee-tail (layer
(position 50 50
(colorize "yellow" (circle 50)))
(position 0 10 (triangle 15)))
bee-head (layer
(position 250 50
(colorize "yellow" (circle 50))))
bee-face (layer
;; eyes
(position 235 40
(circle 10))
(position 265 40
(circle 10))
;; mouth
(position 220 60
(rectangle 20 10))
(position 230 70
(rectangle 40 10))
(position 260 60
(rectangle 20 10))
)
body-size 5
bee-body-colors (take body-size (cycle ["black" "yellow"]))
bee-body (layer
(beside
(map
(fn [color]
(colorize color
(rectangle 30 100)))
bee-body-colors)))
] ;; end of bee components
;; assemble all the bee components
(layer
bee-tail
bee-head
bee-face
bee-body
;; fix bee head being cut in half by drawing an invisible bee head shape
(position 250 50
(opacity 0 (circle 50)))
)))
;; Cells to animate
(cell (interval 250 bee))
;; Use layers to help. The order in which you add layers will help
;; Add some randomness to the bee colours to make a simple animation. For example, if you bee has black and yellow striped body, then change your function to iterate through black an yellow for each stripe to animate the bees body.
;; Or if you bee has wings, change the position of the wings to animate the bee as if it were flying
;; To animate, you can put the bee function inside a cell and set an interval to redraw the bee.
;; # Challenge: My House
;; > TODO: Add section on layers and cells, provide lots of hints on how to solve the challenge
;; Draw a house using the shapes, colours and layer functions we have created for you. Your house may be in the country side, so their could also be trees and grass. You could also create sky with sun or clouds.
;; You could try animate the house by
;; * showing doors or curtains opening and closing
;; * the sun and sky changing colour, the sun sinking and being replaced by the moon
;; #
;; ## fix bugs using structural editing
;; given a piece of code, can you spot the bug and fix it using structural editing (barf, slurp, raise)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment