├── .gitignore
├── Building Blocks.ipynb
├── Playing with Blocks - Matt 1.ipynb
├── Playing with Blocks - Matt 2.ipynb
├── Playing with Blocks.ipynb
├── README.md
├── building_blocks_exercises.md
├── instructor_notes.md
├── ipythonblocks.py
└── playing_with_blocks_exercises.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 | MANIFEST
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | .coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 | .pytest_cache/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 | db.sqlite3
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 | env.bak/
91 | venv.bak/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 |
--------------------------------------------------------------------------------
/Building Blocks.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "After going through this set of exercises you will know how to read text files with Python, do some common operations on strings, and encapsulate code for reuse in functions."
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": null,
13 | "metadata": {},
14 | "outputs": [],
15 | "source": [
16 | "from ipythonblocks import BlockGrid, colors"
17 | ]
18 | }
19 | ],
20 | "metadata": {
21 | "kernelspec": {
22 | "display_name": "Python 3",
23 | "language": "python",
24 | "name": "python3"
25 | },
26 | "language_info": {
27 | "codemirror_mode": {
28 | "name": "ipython",
29 | "version": 3
30 | },
31 | "file_extension": ".py",
32 | "mimetype": "text/x-python",
33 | "name": "python",
34 | "nbconvert_exporter": "python",
35 | "pygments_lexer": "ipython3",
36 | "version": "3.7.3"
37 | }
38 | },
39 | "nbformat": 4,
40 | "nbformat_minor": 1
41 | }
42 |
--------------------------------------------------------------------------------
/Playing with Blocks - Matt 2.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "After working through the exercises in this notebook you should be familiar with Python variables, indexing, [for loops][for], and [if statements][if].\n",
8 | "\n",
9 | "[for]: http://docs.python.org/2/reference/compound_stmts.html#the-for-statement\n",
10 | "[if]: http://docs.python.org/2/reference/compound_stmts.html#the-if-statement"
11 | ]
12 | },
13 | {
14 | "cell_type": "code",
15 | "execution_count": 1,
16 | "metadata": {},
17 | "outputs": [],
18 | "source": [
19 | "from ipythonblocks import BlockGrid, colors, show_color, show_color_triple"
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": 2,
25 | "metadata": {},
26 | "outputs": [],
27 | "source": [
28 | "show_color?"
29 | ]
30 | },
31 | {
32 | "cell_type": "code",
33 | "execution_count": 3,
34 | "metadata": {},
35 | "outputs": [
36 | {
37 | "data": {
38 | "text/html": [
39 | "
"
40 | ],
41 | "text/plain": [
42 | ""
43 | ]
44 | },
45 | "metadata": {},
46 | "output_type": "display_data"
47 | }
48 | ],
49 | "source": [
50 | "show_color(12, 123, 234)"
51 | ]
52 | },
53 | {
54 | "cell_type": "code",
55 | "execution_count": 4,
56 | "metadata": {},
57 | "outputs": [
58 | {
59 | "data": {
60 | "text/plain": [
61 | "Color(red=0, green=128, blue=128)"
62 | ]
63 | },
64 | "execution_count": 4,
65 | "metadata": {},
66 | "output_type": "execute_result"
67 | }
68 | ],
69 | "source": [
70 | "colors.Teal"
71 | ]
72 | },
73 | {
74 | "cell_type": "code",
75 | "execution_count": 5,
76 | "metadata": {},
77 | "outputs": [
78 | {
79 | "data": {
80 | "text/plain": [
81 | ""
82 | ]
83 | },
84 | "execution_count": 5,
85 | "metadata": {},
86 | "output_type": "execute_result"
87 | }
88 | ],
89 | "source": [
90 | "show_color"
91 | ]
92 | },
93 | {
94 | "cell_type": "code",
95 | "execution_count": 6,
96 | "metadata": {},
97 | "outputs": [
98 | {
99 | "data": {
100 | "text/html": [
101 | ""
102 | ],
103 | "text/plain": [
104 | ""
105 | ]
106 | },
107 | "metadata": {},
108 | "output_type": "display_data"
109 | }
110 | ],
111 | "source": [
112 | "show_color_triple(colors.Teal)"
113 | ]
114 | },
115 | {
116 | "cell_type": "code",
117 | "execution_count": 7,
118 | "metadata": {},
119 | "outputs": [
120 | {
121 | "data": {
122 | "text/html": [
123 | ""
124 | ],
125 | "text/plain": [
126 | ""
127 | ]
128 | },
129 | "execution_count": 7,
130 | "metadata": {},
131 | "output_type": "execute_result"
132 | }
133 | ],
134 | "source": [
135 | "BlockGrid(5, 5)"
136 | ]
137 | },
138 | {
139 | "cell_type": "code",
140 | "execution_count": 10,
141 | "metadata": {},
142 | "outputs": [],
143 | "source": [
144 | "grid = BlockGrid(width=5, height=5, fill=colors.DarkTurquoise)"
145 | ]
146 | },
147 | {
148 | "cell_type": "code",
149 | "execution_count": 11,
150 | "metadata": {},
151 | "outputs": [
152 | {
153 | "data": {
154 | "text/html": [
155 | ""
156 | ],
157 | "text/plain": [
158 | ""
159 | ]
160 | },
161 | "execution_count": 11,
162 | "metadata": {},
163 | "output_type": "execute_result"
164 | }
165 | ],
166 | "source": [
167 | "grid"
168 | ]
169 | },
170 | {
171 | "cell_type": "code",
172 | "execution_count": 12,
173 | "metadata": {},
174 | "outputs": [],
175 | "source": [
176 | "block = grid[0, 0]\n",
177 | "block.rgb = colors.Black"
178 | ]
179 | },
180 | {
181 | "cell_type": "code",
182 | "execution_count": 13,
183 | "metadata": {},
184 | "outputs": [
185 | {
186 | "data": {
187 | "text/html": [
188 | ""
189 | ],
190 | "text/plain": [
191 | ""
192 | ]
193 | },
194 | "execution_count": 13,
195 | "metadata": {},
196 | "output_type": "execute_result"
197 | }
198 | ],
199 | "source": [
200 | "grid"
201 | ]
202 | },
203 | {
204 | "cell_type": "code",
205 | "execution_count": null,
206 | "metadata": {},
207 | "outputs": [],
208 | "source": []
209 | }
210 | ],
211 | "metadata": {
212 | "kernelspec": {
213 | "display_name": "Python 3",
214 | "language": "python",
215 | "name": "python3"
216 | },
217 | "language_info": {
218 | "codemirror_mode": {
219 | "name": "ipython",
220 | "version": 3
221 | },
222 | "file_extension": ".py",
223 | "mimetype": "text/x-python",
224 | "name": "python",
225 | "nbconvert_exporter": "python",
226 | "pygments_lexer": "ipython3",
227 | "version": "3.7.3"
228 | }
229 | },
230 | "nbformat": 4,
231 | "nbformat_minor": 1
232 | }
233 |
--------------------------------------------------------------------------------
/Playing with Blocks.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "After working through the exercises in this notebook you should be familiar with Python variables, indexing, [for loops][for], and [if statements][if].\n",
8 | "\n",
9 | "[for]: http://docs.python.org/2/reference/compound_stmts.html#the-for-statement\n",
10 | "[if]: http://docs.python.org/2/reference/compound_stmts.html#the-if-statement"
11 | ]
12 | },
13 | {
14 | "cell_type": "code",
15 | "execution_count": null,
16 | "metadata": {},
17 | "outputs": [],
18 | "source": [
19 | "from ipythonblocks import BlockGrid, colors, show_color, show_color_triple"
20 | ]
21 | },
22 | {
23 | "cell_type": "code",
24 | "execution_count": null,
25 | "metadata": {},
26 | "outputs": [],
27 | "source": []
28 | }
29 | ],
30 | "metadata": {
31 | "kernelspec": {
32 | "display_name": "Python 3",
33 | "language": "python",
34 | "name": "python3"
35 | },
36 | "language_info": {
37 | "codemirror_mode": {
38 | "name": "ipython",
39 | "version": 3
40 | },
41 | "file_extension": ".py",
42 | "mimetype": "text/x-python",
43 | "name": "python",
44 | "nbconvert_exporter": "python",
45 | "pygments_lexer": "ipython3",
46 | "version": "3.7.3"
47 | }
48 | },
49 | "nbformat": 4,
50 | "nbformat_minor": 1
51 | }
52 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Introduction to Python at SciPy 2019
2 |
3 | Monday, July 8 at 8:00 AM
4 |
5 | This tutorial is a gentle introduction to Python for folks who are completely
6 | new to it and may not have much experience programming.
7 | We’ll work in a [Jupyter Notebook](https://jupyter-notebook.readthedocs.io/en/stable/),
8 | one of the most popular tools in scientific Python.
9 | You’ll learn how to write beautiful Python while practicing loops, if’s,
10 | functions, and usage of Python’s built-in features in a series of fun,
11 | interactive exercises.
12 | By the end of the tutorial we think you’ll be ready to write your own
13 | basic Python -- but most importantly, we want you to learn the form and
14 | vocabulary of Python so that you can understand Python documentation and
15 | interpret code written by others.
16 |
17 | See the tutorial description on the conference website
18 | [here](https://www.scipy2019.scipy.org/tutorial/Introduction-to-Python-and-Programming).
19 |
20 | ## Setup Instructions
21 |
22 | **Please do at least the download and install of Anaconda before coming to the tutorial!**
23 | **We can help with further setup at the tutorial.**
24 |
25 | If you don't already have Anaconda installed, download and install Anaconda
26 | for **Python 3** (_not Python 2_):
27 | https://www.anaconda.com/distribution/.
28 |
29 | If you're prompted to install [VS Code](https://code.visualstudio.com/)
30 | we recommend you **do** install it unless you already have a code editor
31 | you prefer.
32 |
33 | After installing Anaconda you can test your installation using
34 | [these instructions](http://docs.anaconda.com/anaconda/user-guide/getting-started/#write-a-python-program-using-anaconda-prompt-or-terminal).
35 |
36 |
37 | If you'd like to do your own setup, we'll be using the following Python libraries:
38 |
39 | - [Jupyter Notebook](https://jupyter-notebook.readthedocs.io/en/stable/)
40 |
41 | ## Useful Links and References
42 |
43 | While this tutorial serves as an introduction to Python, you may want to consider reading
44 | about some concepts in more detail on your own time. Below are some useful references to
45 | help you get more acquainted with Python and other programming fundamentals.
46 |
47 | - [Official Python (3) Documentation](https://docs.python.org/3/)
48 | - [Python Tutorial](https://docs.python.org/3/tutorial/index.html)
49 | - [Formatting Inputs and Outputs like Strings, Files](https://docs.python.org/3/tutorial/inputoutput.html)
50 | - [Built-in Functions](https://docs.python.org/3/library/functions.html)
51 | - [Built-in Types](https://docs.python.org/3/library/stdtypes.html)
52 | - [Software Carpentry](https://software-carpentry.org/)
53 | - [Version Control With Git Lesson](http://swcarpentry.github.io/git-novice/)
54 | - [Programming in Python Lesson](http://swcarpentry.github.io/python-novice-inflammation/)
55 | - [The Unix Shell Lesson](http://swcarpentry.github.io/shell-novice/)
56 | - [SciPy Lecture Notes - Commonly Used Packages](https://scipy-lectures.org/)
57 | - [How to Think Like a Computer Scientist Tutorial](https://runestone.academy/runestone/static/thinkcspy/index.html)
58 |
59 |
60 | ## Links to Advanced Topics
61 |
62 | After reading the links in the above section, you may consider learning
63 | about more advanced topics:
64 |
65 | - [Python Argparse Tutorial (standard library Command Line Interface module)](https://docs.python.org/3/howto/argparse.html)
66 | - [pdb - The Python Debugger](https://docs.python.org/3/library/pdb.html)
67 | - [pytest Documentation - a useful testing library](https://docs.pytest.org/en/latest/)
68 | - [Python Logging Tutorial](https://docs.python.org/3/howto/logging.html)
69 |
--------------------------------------------------------------------------------
/building_blocks_exercises.md:
--------------------------------------------------------------------------------
1 | # Exercises Accompanying the "Building Blocks" Notebook
2 |
3 | ## Exercise 1
4 |
5 | Read the file `grid2.txt` and display the resulting grid. You should
6 | recognize it if you've done it correctly. :-)
7 |
8 | ## Exercise 2
9 |
10 | Build and show the grid saved in `grid3.txt` reading lines from the file
11 | one at a time.
12 |
13 | ## Exercise 3
14 |
15 | Put your code from Exercise 2 in a function and test it out on `grid1.txt`,
16 | `grid2.txt`, and `grid3.txt`.
17 |
18 | ## Exercise 4
19 |
20 | Use the `glob` function to get a `list` of all the "frame" files in the
21 | `animation` directory. Remember that `glob` works just like `ls`.
22 |
23 | ## Exercise 5
24 |
25 | Use your function from Exercise 3, the list of files from Exercise 4, and
26 | the `.flash()` method to successively display each grid. It will make a short
27 | animation when done correctly!
28 |
--------------------------------------------------------------------------------
/instructor_notes.md:
--------------------------------------------------------------------------------
1 | # Practicing Python with `ipythonblocks`
2 |
3 | ## Overview
4 |
5 | These lessons use [`ipythonblocks`][] to teach the basic usage of Python.
6 | The "Playing with Blocks" lesson goes into Python syntax, especially
7 | indexing, `for` loops, and `if` statements. Finally, the "Building Blocks"
8 | lesson goes into reading files and building functions.
9 |
10 | [`ipythonblocks.py`][] is packaged here with the lesson so there's nothing
11 | to install.
12 |
13 | ## Agenda
14 |
15 | - Instructor intro, helper intro
16 | - Getting to know the class
17 | - Science/engineering/other
18 | - Life sciences/social sciences/physical sciences
19 | - Industry/academics/public sector
20 | - Who has already used Python?
21 | - Broad intro to Python ecosystem and scientific Python
22 | - Broad intro to Jupyter
23 | - Playing with Blocks notebook
24 | - Building Blocks notebook
25 |
26 | ## Lesson Plans
27 |
28 | *Note: These notebooks have some code pre-filled, such as imports.
29 | Explain to the students that even though this code is already written in the
30 | notebook, they must still explicitly execute those cells for them to have
31 | any effect.*
32 |
33 | ### Playing with Blocks
34 |
35 | #### Learning Goals
36 |
37 | - Use IPython's help features
38 | - Assign variables
39 | - `for` loops (both iterator and `range` style)
40 | - `if` statements
41 | - indexing
42 | - functions
43 |
44 | #### Introduction
45 |
46 | In this notebook the `BlockGrid` class has been imported for students.
47 | The exercises are in the [play_with_blocks_exercises.md][playing exercises]
48 | file.
49 |
50 | #### Variables
51 |
52 | - Use IPython's help features to look at `BlockGrid`'s doc string.
53 |
54 | - Demonstrate how to make a new `BlockGrid` object and assign it to a variable.
55 | This is a good chance to explain keyword arguments.
56 |
57 | - Show how to display the grid using the interactive echo and the
58 | `BlockGrid.show()` method.
59 |
60 | - Exercise 1
61 |
62 | #### Basic Indexing
63 |
64 | - Assign the first block of `grid` to a variable and change it's color,
65 | then display the grid.
66 |
67 | block = grid[0, 0]
68 | block.rgb = (0, 0, 0)
69 | grid
70 |
71 | - Explain Python's zero based indexing, the coordinate system of the grid
72 | and that indices are `[row, column]`.
73 |
74 | - Exercise 2
75 |
76 | - Exercise 3
77 | - You can use `[-1, -1]` to get the lower-right block and explain
78 | Python's negative indexing.
79 |
80 | - Exercise 4
81 |
82 | #### Basic Loops
83 |
84 | That's enough changing blocks one at a time, now for loops!
85 |
86 | - Set the color of every block to something using a `for` loop:
87 |
88 | for block in grid:
89 | block.rgb = (0, 0, 0)
90 |
91 | This will probably be the first introduction of Pythonic indentation,
92 | so talk about that.
93 |
94 | Then demonstrate doing the same thing with the `.animate()` method,
95 | which will show the changes as they happen:
96 |
97 | for block in grid.animate():
98 | block.rgb = (12, 123, 234)
99 |
100 | - Exercise 5
101 |
102 | #### Introducing If
103 |
104 | Now to add logic so we can make some blocks different colors from others.
105 |
106 | - Show an example `if` statement by looping over the grid but changing only
107 | one row. This will involve introducing the `block.row` attribute.
108 |
109 | for block in grid.animate():
110 | if block.row == 2:
111 | block.rgb = (0, 0, 0)
112 |
113 | A couple of new things are introduced here:
114 |
115 | - Using `==` for comparison vs. `=` for assignment.
116 | You might take this opportunity to introduce all of the comparison
117 | operators.
118 | - Indenting again for the `if` block.
119 |
120 | Also mention the `block.col` attribute.
121 |
122 | - Exercise 6
123 |
124 | - What if we want to loop over the grid once and turn the first row black,
125 | the third row white, and every other row blue? `elif` + `else`! Demo doing
126 | this.
127 |
128 | - Exercise 7
129 |
130 | - Now for `and`/`or`. Demo using `or` to change color of multiple columns
131 | with one loop through. This contrasts with above where we wanted to change
132 | multiple columns multiple colors, now we want to turn multiple columns the
133 | same color.
134 |
135 | for block in grid.animate():
136 | if block.col == 2 or block.col == 4:
137 | block.rgb = (50, 50, 50)
138 |
139 | - Exercise 8
140 |
141 | - Show the students that blocks have `.red`, `.green`, and `.blue` attributes
142 | they can use see the value of individual block color channels. (These can
143 | also be used to change the color values one at a time.)
144 |
145 | - Exercise 9
146 |
147 | #### Looping with `range`
148 |
149 | So far the students have been looping over the entire grid, but we should also
150 | introduce `range` so they can work on smaller sections of the grid without
151 | looping over the whole thing.
152 |
153 | - Take a look at the docstring for `range`.
154 | - Show an example of changing the color of a single row by looping over
155 | `range(grid.width)` and varying only the column index.
156 |
157 | - Exercise 10
158 |
159 | - Show an example of using a nested loop to change a 2D subsection of the
160 | grid somewhere near the middle.
161 |
162 | - Exercise 11
163 |
164 | #### Slicing
165 |
166 | `BlockGrids` support standard Python slicing, for example something like
167 | `grid[2:4, 1:3] = (0, 200, 0)`.
168 |
169 | - Demonstrate slicing in various ways, keeping a dialogue with the students
170 | about what to expect from each statement.
171 |
172 | - Excercise 12
173 |
174 | #### Functions
175 |
176 | Explain functions and write a couple of demo ones, e.g.:
177 |
178 | ```python
179 | def fahr_to_celsius(temp):
180 | return ((temp - 32) * (5 / 9))
181 | ```
182 |
183 | And maybe an `is_even` function?
184 |
185 | - Exercise 13
186 |
187 | - Exercise 14
188 |
189 | #### Functions, Lists, Strings, Dictionaries
190 |
191 | Want to get into Python standard types.
192 | Exercise is to write a function that follows a list of directions
193 | through a grid changing colors as it goes.
194 | Students are expected to use a dictionary to map direction names
195 | ('up', 'down', 'left', 'right') to changes in position
196 | (e.g. `[0, -1]`, `[1, 0]`, etc).
197 |
198 | - Strings
199 | + Formatting, indexing, slicing
200 | - Lists
201 | + Indexing, slicing, appending
202 | - Dictionaries
203 | + Setting and retrieving values
204 |
205 | - Exercise 15
206 |
207 | #### Free Play
208 |
209 | There are many opportunities for [creativity with `ipythonblocks`][fun blocks],
210 | give the students a while to try to make whatever they like. Suggest the
211 | possibilities if they relate block color to block position. Some possible
212 | prompts if they need ideas of things to draw:
213 |
214 | - Initials
215 | - Shape like a circle, heart, star, etc.
216 | - Repeating pattern
217 | - Rainbow
218 | - Maze
219 |
220 | If they've learned about [GitHub][] and set up accounts there they can put
221 | their notebooks in [gists][] and share them via nbviewer. Demo how to do this
222 | if it seems like something they'd be interested in. You can even show some
223 | of their work!
224 | They can also share grids on http://www.ipythonblocks.org/.
225 |
226 | ### Building Blocks
227 |
228 | #### Learning Goals
229 |
230 | - Work with lists and strings
231 | - Read a Python stacktrace
232 | - Read files
233 | - Use the standard library
234 | - Write a function
235 |
236 | #### Introduction
237 |
238 | In this set of exercises we'll go into reading simple text files and
239 | encapsulating the reader code into a function so they can reuse it on several
240 | files. There is a sort of "standard" `ipythonblocks` file format that is the
241 | output of calling the `BlockGrid.to_text` method. Here's a small but complete
242 | example from [`grid1.txt`][]:
243 |
244 | # width height
245 | 3 3
246 | # block size
247 | 20
248 | # initial color
249 | 0 0 0
250 | # row column red green blue
251 | 1 1 255 255 255
252 |
253 | Lines beginning with `#` are comments. The first six lines specify the
254 | initial state of the grid and at the end are any further modifications,
255 | specified by block index and color.
256 |
257 | Reading files introduces a lot of new concepts: files themselves, strings,
258 | and even lists depending how you do it. We'll try to approach these in a
259 | manageable fashion.
260 |
261 | Exercises for this section are in the
262 | [building_blocks_exercises.md][building exercises] file.
263 |
264 | #### Opening and Reading a File
265 |
266 | - Use IPython's help to look at how to use `open`.
267 |
268 | - Open `grid1.txt` and use tab completion or `help` to look at the methods
269 | available on the file, review your favorites.
270 |
271 | - Go over `.readlines()` if you haven't already and then use it to read
272 | `grid1.txt`.
273 |
274 | #### Lists and Strings
275 |
276 | - Show the result from `.readlines()` and note it's comprised of some new
277 | things we haven't seen yet: some kind of sequence containing
278 | character strings.
279 |
280 | - Explain the sequence thing is called a list and it works a lot like their
281 | grids.
282 | - zero based indexing and negative indices
283 | - `for thing in sequence`
284 | - Use tab completion just to give the students some idea what lists can do.
285 |
286 | - Grab a line from the list and show it, explaining that it's a text sequence
287 | we call a "string".
288 | - Show that strings are also sequences like lists and grids, e.g. indexable.
289 | - Again, use tab completion to show some of the methods on strings. Mention
290 | `split()` because we'll be using it soon.
291 | - `print` the string and note the extra empty line that shows up, then echo
292 | or `repr` the string and note the `\n` at the end.
293 | - Explain `\n` is the symbol for "new-line" and we'll take care of it soon.
294 |
295 | #### Recipe for a `BlockGrid`
296 |
297 | At this point we can grab things out of the list and we know a little about
298 | strings so let's get started on a "recipe" for building a `BlockGrid` out
299 | of the information in the file. Before getting started on the next step ask
300 | the students to figure out what the resulting grid should look like. You can
301 | lead them to the answer by first looking at the dimensions of the grid, then
302 | the fill color, and finally the modifications.
303 |
304 | - Work with the students on the recipe, asking them what to do first,
305 | second, and so on until you've created a feasible looking block of code.
306 | - Try to run it. It will probably fail because the inputs haven't been
307 | converted to integers.
308 | - Use this opportunity to introduce and show how to read backtraces.
309 | - Show how to convert strings to integers and floats.
310 | - Work with the students to fix the code and try again.
311 | - Repeat as necessary until you get the desired result.
312 |
313 | #### List Slicing
314 |
315 | For files with longer lists of grid modifications at the bottom students will
316 | want to use a loop to apply them, but we haven't yet covered list slicing.
317 |
318 | - `cat grid2.txt` to make the point that making all those modifications
319 | one-per-line would be tedious, we want to automate it!
320 | - Show some examples of list slicing, noting common gotchas such as
321 | exclusive endpoints.
322 |
323 | - Exercise 1
324 |
325 | #### Reading a File One Line at a Time
326 |
327 | In general in Python, and especially for large files, it's common to read
328 | files one line at a time instead of loading the whole thing with `readlines()`.
329 |
330 | - Work with the students again to make a new recipe for reading `grid2.txt`
331 | in which lines are read from the file one at time.
332 | - You will need to introduce `readline()` and `for line in file`.
333 |
334 | - Exercise 2
335 |
336 | #### Code Re-use With Functions
337 |
338 | In this section the students are going to construct 41 grids from 41 files
339 | and string them together into an animation using the `BlockGrid.flash()`
340 | method. Reading 41 files is obviously not something they'll want to do by
341 | copying out the file reading code 41 times.
342 |
343 | - Describe the problem the students are facing.
344 | - Introduce functions and give some demonstrations. Functions that:
345 | - print something
346 | - print an argument
347 | - print multiple arguments
348 | - return something
349 | - return multiple somethings
350 | - does something to an argument and returns the result
351 | - Ask the students what the input and output would be for the function we need.
352 |
353 | - Exercise 3
354 |
355 | #### Introduction to the Standard Library
356 |
357 | Now that we have a reader we need files to pass into it. This is a good
358 | opportunity to introduce the [`glob`][] module and point at the rest of the
359 | [standard library][pystd].
360 |
361 | - Talk about `import` and that fact that we've been importing things out of
362 | the [`ipythonblocks.py`][] file located in this directory all along.
363 | - Python has a vast library of useful code accessible via `import`.
364 | It may be helpful to open the [docs][pystd]. (IPython should have a link.)
365 | - Go to the [`glob`][] module docs and explain how we'll use the `glob` function
366 | to get a list of the files.
367 | - Show the students `from glob import glob` and tell them about how it
368 | works like `ls`.
369 |
370 | - Exercise 4
371 |
372 | - The students will probably need a reminder that they can loop over a list
373 | with `for thing in list`.
374 | - Show how to use the `BlockGrid.flash()` method to put a grid on screen for
375 | a split second.
376 |
377 | - Exercise 5
378 |
379 | #### More Free Play
380 |
381 | If there's more time to kill the students might have ideas for animations to
382 | try out.
383 |
384 | [`ipythonblocks`]: https://github.com/jiffyclub/ipythonblocks
385 | [`ipythonblocks.py`]: ./ipythonblocks.py
386 | [RGB]: http://en.wikipedia.org/wiki/RGB_color_model
387 | [colorpicker]: http://www.colorpicker.com
388 | [HTML colors]: http://en.wikipedia.org/wiki/Html_colors#X11_color_names
389 | [playing exercises]: ./playing_with_blocks_exercises.md
390 | [building exercises]: ./building_blocks_exercises.md
391 | [fun blocks]: http://nbviewer.ipython.org/urls/raw.github.com/jiffyclub/ipythonblocks/master/demos/ipythonblocks_fun.ipynb
392 | [GitHub]: http://github.com
393 | [gists]: http://gist.github.com
394 | [`grid1.txt`]: ./grid1.txt
395 | [`glob`]: http://docs.python.org/2/library/glob.html
396 | [pystd]: http://docs.python.org/2/library/index.html
397 |
--------------------------------------------------------------------------------
/ipythonblocks.py:
--------------------------------------------------------------------------------
1 | """
2 | ipythonblocks provides a BlockGrid class that displays a colored grid in the
3 | IPython Notebook. The colors can be manipulated, making it useful for
4 | practicing control flow stuctures and quickly seeing the results.
5 |
6 | """
7 |
8 | # This file is copyright 2013 by Matt Davis and covered by the license at
9 | # https://github.com/jiffyclub/ipythonblocks/blob/master/LICENSE.txt
10 |
11 | import copy
12 | import collections
13 | import json
14 | import numbers
15 | import os
16 | import sys
17 | import time
18 | import uuid
19 |
20 | from collections import namedtuple
21 | from operator import iadd
22 | from functools import reduce
23 |
24 | from IPython.display import HTML, IFrame, display, clear_output
25 | from IPython.display import Image as ipyImage
26 |
27 | __all__ = (
28 | 'Block',
29 | 'BlockGrid',
30 | 'Pixel',
31 | 'ImageGrid',
32 | 'InvalidColorSpec',
33 | 'ShapeMismatch',
34 | 'show_color',
35 | 'show_color_triple',
36 | 'embed_colorpicker',
37 | 'clear',
38 | 'colors',
39 | 'fui_colors',
40 | '__version__',
41 | )
42 | __version__ = '1.9.0'
43 |
44 | _TABLE = (''
50 | '')
51 | _TR = '{0}
'
52 | _TD = (' | ')
54 | _RGB = 'rgb({0}, {1}, {2})'
55 | _TITLE = 'Index: [{0}, {1}]
Color: ({2}, {3}, {4})'
56 |
57 | _SINGLE_ITEM = 'single item'
58 | _SINGLE_ROW = 'single row'
59 | _ROW_SLICE = 'row slice'
60 | _DOUBLE_SLICE = 'double slice'
61 |
62 | _SMALLEST_BLOCK = 1
63 |
64 | _POST_URL = 'http://www.ipythonblocks.org/post'
65 | _GET_URL_PUBLIC = 'http://www.ipythonblocks.org/get/{0}'
66 | _GET_URL_SECRET = 'http://www.ipythonblocks.org/get/secret/{0}'
67 |
68 |
69 | class InvalidColorSpec(Exception):
70 | """
71 | Error for a color value that is not a number.
72 |
73 | """
74 | pass
75 |
76 |
77 | class ShapeMismatch(Exception):
78 | """
79 | Error for when a grid assigned to another doesn't have the same shape.
80 |
81 | """
82 | pass
83 |
84 |
85 | def clear():
86 | """
87 | Clear the output of the current cell.
88 |
89 | This is a thin wrapper around IPython.display.clear_output.
90 |
91 | """
92 | clear_output()
93 |
94 |
95 | def show_color(red, green, blue):
96 | """
97 | Show a given color in the IPython Notebook.
98 |
99 | Parameters
100 | ----------
101 | red, green, blue : int
102 | Integers on the range [0 - 255].
103 |
104 | """
105 | div = ('')
107 | display(HTML(div.format(_RGB.format(red, green, blue))))
108 |
109 |
110 | def show_color_triple(rgb_triple):
111 | """
112 | Show a given color in the IPython Notebook.
113 |
114 | Parameters
115 | ----------
116 | rgb_triple : iterable
117 | A Python iterable containing three integers in the range [0 - 255]
118 | representing red, green, and blue colors.
119 |
120 | """
121 | return show_color(*rgb_triple)
122 |
123 |
124 | def embed_colorpicker():
125 | """
126 | Embed the web page www.colorpicker.com inside the IPython Notebook.
127 |
128 | """
129 | display(
130 | IFrame(src='http://www.colorpicker.com/', height='550px', width='100%')
131 | )
132 |
133 |
134 | def _color_property(name):
135 | real_name = "_" + name
136 |
137 | @property
138 | def prop(self):
139 | return getattr(self, real_name)
140 |
141 | @prop.setter
142 | def prop(self, value):
143 | value = Block._check_value(value)
144 | setattr(self, real_name, value)
145 |
146 | return prop
147 |
148 |
149 | def _flatten(thing, ignore_types=(str,)):
150 | """
151 | Yield a single item or str/unicode or recursively yield from iterables.
152 |
153 | Adapted from Beazley's Python Cookbook.
154 |
155 | """
156 | if isinstance(thing, collections.Iterable) and \
157 | not isinstance(thing, ignore_types):
158 | for i in thing:
159 | for x in _flatten(i):
160 | yield x
161 | else:
162 | yield thing
163 |
164 |
165 | def _parse_str_cell_spec(cells, length):
166 | """
167 | Parse a single string cell specification representing either a single
168 | integer or a slice.
169 |
170 | Parameters
171 | ----------
172 | cells : str
173 | E.g. '5' for an int or '5:9' for a slice.
174 | length : int
175 | The number of items in the user's In history list. Used for
176 | normalizing slices.
177 |
178 | Returns
179 | -------
180 | cell_nos : list of int
181 |
182 | """
183 | if ':' not in cells:
184 | return _parse_cells_spec(int(cells), length)
185 |
186 | else:
187 | return _parse_cells_spec(slice(*[int(x) if x else None
188 | for x in cells.split(':')]),
189 | length)
190 |
191 |
192 | def _parse_cells_spec(cells, length):
193 | """
194 | Used by _get_code_cells to parse a cell specification string into an
195 | ordered list of cell numbers.
196 |
197 | Parameters
198 | ----------
199 | cells : str, int, or slice
200 | Specification of which cells to retrieve. Can be a single number,
201 | a slice, or a combination of either separated by commas.
202 | length : int
203 | The number of items in the user's In history list. Used for
204 | normalizing slices.
205 |
206 | Returns
207 | -------
208 | cell_nos : list of int
209 | Ordered list of cell numbers derived from spec.
210 |
211 | """
212 | if isinstance(cells, int):
213 | return [cells]
214 |
215 | elif isinstance(cells, slice):
216 | return list(range(*cells.indices(length)))
217 |
218 | else:
219 | # string parsing
220 | return sorted(set(_flatten(_parse_str_cell_spec(s, length)
221 | for s in cells.split(','))))
222 |
223 |
224 | def _get_code_cells(cells):
225 | """
226 | Get the inputs of the specified cells from the notebook.
227 |
228 | Parameters
229 | ----------
230 | cells : str, int, or slice
231 | Specification of which cells to retrieve. Can be a single number,
232 | a slice, or a combination of either separated by commas.
233 |
234 | Returns
235 | -------
236 | code : list of str
237 | Contents of cells as strings in chronological order.
238 |
239 | """
240 | In = get_ipython().user_ns['In']
241 | cells = _parse_cells_spec(cells, len(In))
242 | return [In[x] for x in cells]
243 |
244 |
245 | class Block(object):
246 | """
247 | A colored square.
248 |
249 | Parameters
250 | ----------
251 | red, green, blue : int
252 | Integers on the range [0 - 255].
253 | size : int, optional
254 | Length of the sides of this block in pixels. One is the lower limit.
255 |
256 | Attributes
257 | ----------
258 | red, green, blue : int
259 | The color values for this `Block`. The color of the `Block` can be
260 | updated by assigning new values to these attributes.
261 | rgb : tuple of int
262 | Tuple of (red, green, blue) values. Can be used to set all the colors
263 | at once.
264 | row, col : int
265 | The zero-based grid position of this `Block`.
266 | size : int
267 | Length of the sides of this block in pixels. The block size can be
268 | changed by modifying this attribute. Note that one is the lower limit.
269 |
270 | """
271 |
272 | red = _color_property('red')
273 | green = _color_property('green')
274 | blue = _color_property('blue')
275 |
276 | def __init__(self, red, green, blue, size=20):
277 | self.red = red
278 | self.green = green
279 | self.blue = blue
280 | self.size = size
281 |
282 | self._row = None
283 | self._col = None
284 |
285 | @staticmethod
286 | def _check_value(value):
287 | """
288 | Check that a value is a number and constrain it to [0 - 255].
289 |
290 | """
291 | if not isinstance(value, numbers.Number):
292 | s = 'value must be a number. got {0}.'.format(value)
293 | raise InvalidColorSpec(s)
294 |
295 | return int(round(min(255, max(0, value))))
296 |
297 | @property
298 | def rgb(self):
299 | return (self._red, self._green, self._blue)
300 |
301 | @rgb.setter
302 | def rgb(self, colors):
303 | if len(colors) != 3:
304 | s = 'Setting colors requires three values: (red, green, blue).'
305 | raise ValueError(s)
306 |
307 | self.red, self.green, self.blue = colors
308 |
309 | @property
310 | def row(self):
311 | return self._row
312 |
313 | @property
314 | def col(self):
315 | return self._col
316 |
317 | @property
318 | def size(self):
319 | return self._size
320 |
321 | @size.setter
322 | def size(self, size):
323 | self._size = max(_SMALLEST_BLOCK, size)
324 |
325 | def set_colors(self, red, green, blue):
326 | """
327 | Updated block colors.
328 |
329 | Parameters
330 | ----------
331 | red, green, blue : int
332 | Integers on the range [0 - 255].
333 |
334 | """
335 | self.red = red
336 | self.green = green
337 | self.blue = blue
338 |
339 | def _update(self, other):
340 | if isinstance(other, Block):
341 | self.rgb = other.rgb
342 | self.size = other.size
343 | elif isinstance(other, collections.Sequence) and len(other) == 3:
344 | self.rgb = other
345 | else:
346 | errmsg = (
347 | 'Value must be a Block or a sequence of 3 integers. '
348 | 'Got {0!r}.'
349 | )
350 | raise ValueError(errmsg.format(other))
351 |
352 | @property
353 | def _td(self):
354 | """
355 | The HTML for a table cell with the background color of this Block.
356 |
357 | """
358 | title = _TITLE.format(self._row, self._col,
359 | self._red, self._green, self._blue)
360 | rgb = _RGB.format(self._red, self._green, self._blue)
361 | return _TD.format(title, self._size, rgb)
362 |
363 | def _repr_html_(self):
364 | return _TABLE.format(uuid.uuid4(), 0, _TR.format(self._td))
365 |
366 | def show(self):
367 | display(HTML(self._repr_html_()))
368 |
369 | __hash__ = None
370 |
371 | def __eq__(self, other):
372 | if not isinstance(other, Block):
373 | return False
374 | return self.rgb == other.rgb and self.size == other.size
375 |
376 | def __str__(self):
377 | s = ['{0}'.format(self.__class__.__name__),
378 | 'Color: ({0}, {1}, {2})'.format(self._red,
379 | self._green,
380 | self._blue)]
381 |
382 | # add position information if we have it
383 | if self._row is not None:
384 | s[0] += ' [{0}, {1}]'.format(self._row, self._col)
385 |
386 | return os.linesep.join(s)
387 |
388 | def __repr__(self):
389 | type_name = type(self).__name__
390 | return '{0}({1}, {2}, {3}, size={4})'.format(type_name,
391 | self.red,
392 | self.green,
393 | self.blue,
394 | self.size)
395 |
396 |
397 | class BlockGrid(object):
398 | """
399 | A grid of blocks whose colors can be individually controlled.
400 |
401 | Parameters
402 | ----------
403 | width : int
404 | Number of blocks wide to make the grid.
405 | height : int
406 | Number of blocks high to make the grid.
407 | fill : tuple of int, optional
408 | An optional initial color for the grid, defaults to black.
409 | Specified as a tuple of (red, green, blue). E.g.: (10, 234, 198)
410 | block_size : int, optional
411 | Length of the sides of grid blocks in pixels. One is the lower limit.
412 | lines_on : bool, optional
413 | Whether or not to display lines between blocks.
414 |
415 | Attributes
416 | ----------
417 | width : int
418 | Number of blocks along the width of the grid.
419 | height : int
420 | Number of blocks along the height of the grid.
421 | shape : tuple of int
422 | A tuple of (width, height).
423 | block_size : int
424 | Length of the sides of grid blocks in pixels. The block size can be
425 | changed by modifying this attribute. Note that one is the lower limit.
426 | lines_on : bool
427 | Whether lines are shown between blocks when the grid is displayed.
428 | This attribute can used to toggle the whether the lines appear.
429 |
430 | """
431 |
432 | def __init__(self, width, height, fill=(0, 0, 0),
433 | block_size=20, lines_on=True):
434 | self._width = width
435 | self._height = height
436 | self._block_size = block_size
437 | self.lines_on = lines_on
438 | self._initialize_grid(fill)
439 |
440 | def _initialize_grid(self, fill):
441 | grid = [[Block(*fill, size=self._block_size)
442 | for col in range(self.width)]
443 | for row in range(self.height)]
444 |
445 | self._grid = grid
446 |
447 | @property
448 | def width(self):
449 | return self._width
450 |
451 | @property
452 | def height(self):
453 | return self._height
454 |
455 | @property
456 | def shape(self):
457 | return (self._width, self._height)
458 |
459 | @property
460 | def block_size(self):
461 | return self._block_size
462 |
463 | @block_size.setter
464 | def block_size(self, size):
465 | self._block_size = size
466 |
467 | for block in self:
468 | block.size = size
469 |
470 | @property
471 | def lines_on(self):
472 | return self._lines_on
473 |
474 | @lines_on.setter
475 | def lines_on(self, value):
476 | if value not in (0, 1):
477 | s = 'lines_on may only be True or False.'
478 | raise ValueError(s)
479 |
480 | self._lines_on = value
481 |
482 | def __eq__(self, other):
483 | if not isinstance(other, BlockGrid):
484 | return False
485 | else:
486 | # compare the underlying grids
487 | return self._grid == other._grid
488 |
489 | def _view_from_grid(self, grid):
490 | """
491 | Make a new grid from a list of lists of Block objects.
492 |
493 | """
494 | new_width = len(grid[0])
495 | new_height = len(grid)
496 |
497 | new_BG = self.__class__(new_width, new_height,
498 | block_size=self._block_size,
499 | lines_on=self._lines_on)
500 | new_BG._grid = grid
501 |
502 | return new_BG
503 |
504 | @staticmethod
505 | def _categorize_index(index):
506 | """
507 | Used by __getitem__ and __setitem__ to determine whether the user
508 | is asking for a single item, single row, or some kind of slice.
509 |
510 | """
511 | if isinstance(index, int):
512 | return _SINGLE_ROW
513 |
514 | elif isinstance(index, slice):
515 | return _ROW_SLICE
516 |
517 | elif isinstance(index, tuple):
518 | if len(index) > 2:
519 | s = 'Invalid index, too many dimensions.'
520 | raise IndexError(s)
521 |
522 | elif len(index) == 1:
523 | s = 'Single indices must be integers, not tuple.'
524 | raise TypeError(s)
525 |
526 | if isinstance(index[0], slice):
527 | if isinstance(index[1], (int, slice)):
528 | return _DOUBLE_SLICE
529 |
530 | if isinstance(index[1], slice):
531 | if isinstance(index[0], (int, slice)):
532 | return _DOUBLE_SLICE
533 |
534 | elif isinstance(index[0], int) and isinstance(index[0], int):
535 | return _SINGLE_ITEM
536 |
537 | raise IndexError('Invalid index.')
538 |
539 | def __getitem__(self, index):
540 | ind_cat = self._categorize_index(index)
541 |
542 | if ind_cat == _SINGLE_ROW:
543 | return self._view_from_grid([self._grid[index]])
544 |
545 | elif ind_cat == _SINGLE_ITEM:
546 | block = self._grid[index[0]][index[1]]
547 | block._row, block._col = index
548 | return block
549 |
550 | elif ind_cat == _ROW_SLICE:
551 | return self._view_from_grid(self._grid[index])
552 |
553 | elif ind_cat == _DOUBLE_SLICE:
554 | new_grid = self._get_double_slice(index)
555 | return self._view_from_grid(new_grid)
556 |
557 | def __setitem__(self, index, value):
558 | thing = self[index]
559 |
560 | if isinstance(value, BlockGrid):
561 | if isinstance(thing, BlockGrid):
562 | if thing.shape != value.shape:
563 | raise ShapeMismatch('Both sides of grid assignment must '
564 | 'have the same shape.')
565 |
566 | for a, b in zip(thing, value):
567 | a._update(b)
568 |
569 | else:
570 | raise TypeError('Cannot assign grid to single block.')
571 |
572 | elif isinstance(value, (collections.Iterable, Block)):
573 | for b in _flatten(thing):
574 | b._update(value)
575 |
576 | def _get_double_slice(self, index):
577 | sl_height, sl_width = index
578 |
579 | if isinstance(sl_width, int):
580 | if sl_width == -1:
581 | sl_width = slice(sl_width, None)
582 | else:
583 | sl_width = slice(sl_width, sl_width + 1)
584 |
585 | if isinstance(sl_height, int):
586 | if sl_height == -1:
587 | sl_height = slice(sl_height, None)
588 | else:
589 | sl_height = slice(sl_height, sl_height + 1)
590 |
591 | rows = self._grid[sl_height]
592 | grid = [r[sl_width] for r in rows]
593 |
594 | return grid
595 |
596 | def __iter__(self):
597 | for r in range(self.height):
598 | for c in range(self.width):
599 | yield self[r, c]
600 |
601 | def animate(self, stop_time=0.2):
602 | """
603 | Call this method in a loop definition to have your changes to the grid
604 | animated in the IPython Notebook.
605 |
606 | Parameters
607 | ----------
608 | stop_time : float
609 | Amount of time to pause between loop steps.
610 |
611 | """
612 | for block in self:
613 | self.show()
614 | time.sleep(stop_time)
615 | yield block
616 | clear_output(wait=True)
617 | self.show()
618 |
619 | def _repr_html_(self):
620 | rows = range(self._height)
621 | cols = range(self._width)
622 |
623 | html = reduce(iadd,
624 | (_TR.format(reduce(iadd,
625 | (self[r, c]._td
626 | for c in cols)))
627 | for r in rows))
628 |
629 | return _TABLE.format(uuid.uuid4(), int(self._lines_on), html)
630 |
631 | def __str__(self):
632 | s = ['{0}'.format(self.__class__.__name__),
633 | 'Shape: {0}'.format(self.shape)]
634 |
635 | return os.linesep.join(s)
636 |
637 | def copy(self):
638 | """
639 | Returns an independent copy of this BlockGrid.
640 |
641 | """
642 | return copy.deepcopy(self)
643 |
644 | def show(self):
645 | """
646 | Display colored grid as an HTML table.
647 |
648 | """
649 | display(HTML(self._repr_html_()))
650 |
651 | def flash(self, display_time=0.2):
652 | """
653 | Display the grid for a time.
654 |
655 | Useful for making an animation or iteratively displaying changes.
656 |
657 | Note that this will leave the grid in place until something replaces
658 | it in the same cell. You can use the ``clear`` function to
659 | manually clear output.
660 |
661 | Parameters
662 | ----------
663 | display_time : float
664 | Amount of time, in seconds, to display the grid.
665 |
666 | """
667 | self.show()
668 | time.sleep(display_time)
669 | clear_output(wait=True)
670 |
671 | def _calc_image_size(self):
672 | """
673 | Calculate the size, in pixels, of the grid as an image.
674 |
675 | Returns
676 | -------
677 | px_width : int
678 | px_height : int
679 |
680 | """
681 | px_width = self._block_size * self._width
682 | px_height = self._block_size * self._height
683 |
684 | if self._lines_on:
685 | px_width += self._width + 1
686 | px_height += self._height + 1
687 |
688 | return px_width, px_height
689 |
690 | def _write_image(self, fp, format='png'):
691 | """
692 | Write an image of the current grid to a file-object.
693 |
694 | Parameters
695 | ----------
696 | fp : file-like
697 | A file-like object such as an open file pointer or
698 | a StringIO/BytesIO instance.
699 | format : str, optional
700 | An image format that will be understood by PIL,
701 | e.g. 'png', 'jpg', 'gif', etc.
702 |
703 | """
704 | try:
705 | # PIL
706 | import Image
707 | import ImageDraw
708 | except ImportError:
709 | # pillow
710 | from PIL import Image, ImageDraw
711 |
712 | im = Image.new(
713 | mode='RGB', size=self._calc_image_size(), color=(255, 255, 255))
714 | draw = ImageDraw.Draw(im)
715 |
716 | _bs = self._block_size
717 |
718 | for r in range(self._height):
719 | for c in range(self._width):
720 | px_r = r * _bs
721 | px_c = c * _bs
722 | if self._lines_on:
723 | px_r += r + 1
724 | px_c += c + 1
725 |
726 | rect = ((px_c, px_r), (px_c + _bs - 1, px_r + _bs - 1))
727 | draw.rectangle(rect, fill=self._grid[r][c].rgb)
728 |
729 | im.save(fp, format=format)
730 |
731 | def show_image(self):
732 | """
733 | Embed grid in the notebook as a PNG image.
734 |
735 | """
736 | if sys.version_info[0] == 2:
737 | from StringIO import StringIO as BytesIO
738 | elif sys.version_info[0] == 3:
739 | from io import BytesIO
740 |
741 | im = BytesIO()
742 | self._write_image(im)
743 | display(ipyImage(data=im.getvalue(), format='png'))
744 |
745 | def save_image(self, filename):
746 | """
747 | Save an image representation of the grid to a file.
748 | Image format will be inferred from file extension.
749 |
750 | Parameters
751 | ----------
752 | filename : str
753 | Name of file to save to.
754 |
755 | """
756 | with open(filename, 'wb') as f:
757 | self._write_image(f, format=filename.split('.')[-1])
758 |
759 | def to_text(self, filename=None):
760 | """
761 | Write a text file containing the size and block color information
762 | for this grid.
763 |
764 | If no file name is given the text is sent to stdout.
765 |
766 | Parameters
767 | ----------
768 | filename : str, optional
769 | File into which data will be written. Will be overwritten if
770 | it already exists.
771 |
772 | """
773 | if filename:
774 | f = open(filename, 'w')
775 | else:
776 | f = sys.stdout
777 |
778 | s = ['# width height', '{0} {1}'.format(self.width, self.height),
779 | '# block size', '{0}'.format(self.block_size),
780 | '# initial color', '0 0 0',
781 | '# row column red green blue']
782 | f.write(os.linesep.join(s) + os.linesep)
783 |
784 | for block in self:
785 | things = [str(x) for x in (block.row, block.col) + block.rgb]
786 | f.write(' '.join(things) + os.linesep)
787 |
788 | if filename:
789 | f.close()
790 |
791 | def _to_simple_grid(self):
792 | """
793 | Make a simple representation of the table: nested lists of
794 | of the rows containing tuples of (red, green, blue, size)
795 | for each of the blocks.
796 |
797 | Returns
798 | -------
799 | grid : list of lists
800 | No matter the class this method is called on the returned
801 | grid will be Python-style: row oriented with the top-left
802 | block in the [0][0] position.
803 |
804 | """
805 | return [[(x.red, x.green, x.blue, x.size) for x in row]
806 | for row in self._grid]
807 |
808 | def _construct_post_request(self, code_cells, secret):
809 | """
810 | Construct the request dictionary that will be posted
811 | to ipythonblocks.org.
812 |
813 | Parameters
814 | ----------
815 | code_cells : int, str, slice, or None
816 | Specify any code cells to be sent and displayed with the grid.
817 | You can specify a single cell, a Python, slice, or a combination
818 | as a string separated by commas.
819 |
820 | For example, '3,5,8:10' would send cells 3, 5, 8, and 9.
821 | secret : bool
822 | If True, this grid will not be shown randomly on ipythonblocks.org.
823 |
824 | Returns
825 | -------
826 | request : dict
827 |
828 | """
829 | if code_cells is not None:
830 | code_cells = _get_code_cells(code_cells)
831 |
832 | req = {
833 | 'python_version': tuple(sys.version_info),
834 | 'ipb_version': __version__,
835 | 'ipb_class': self.__class__.__name__,
836 | 'code_cells': code_cells,
837 | 'secret': secret,
838 | 'grid_data': {
839 | 'lines_on': self.lines_on,
840 | 'width': self.width,
841 | 'height': self.height,
842 | 'blocks': self._to_simple_grid()
843 | }
844 | }
845 |
846 | return req
847 |
848 | def post_to_web(self, code_cells=None, secret=False):
849 | """
850 | Post this grid to ipythonblocks.org and return a URL to
851 | view the grid on the web.
852 |
853 | Parameters
854 | ----------
855 | code_cells : int, str, or slice, optional
856 | Specify any code cells to be sent and displayed with the grid.
857 | You can specify a single cell, a Python, slice, or a combination
858 | as a string separated by commas.
859 |
860 | For example, '3,5,8:10' would send cells 3, 5, 8, and 9.
861 | secret : bool, optional
862 | If True, this grid will not be shown randomly on ipythonblocks.org.
863 |
864 | Returns
865 | -------
866 | url : str
867 | URL to view your grid on ipythonblocks.org.
868 |
869 | """
870 | import requests
871 |
872 | req = self._construct_post_request(code_cells, secret)
873 | response = requests.post(_POST_URL, data=json.dumps(req))
874 | response.raise_for_status()
875 |
876 | return response.json()['url']
877 |
878 | def _load_simple_grid(self, block_data):
879 | """
880 | Modify the grid to reflect the data in `block_data`, which
881 | should be a nested list of tuples as produced by `_to_simple_grid`.
882 |
883 | Parameters
884 | ----------
885 | block_data : list of lists
886 | Nested list of tuples as produced by `_to_simple_grid`.
887 |
888 | """
889 | if len(block_data) != self.height or \
890 | len(block_data[0]) != self.width:
891 | raise ShapeMismatch('block_data must have same shape as grid.')
892 |
893 | for row in range(self.height):
894 | for col in range(self.width):
895 | self._grid[row][col].rgb = block_data[row][col][:3]
896 | self._grid[row][col].size = block_data[row][col][3]
897 |
898 | @classmethod
899 | def from_web(cls, grid_id, secret=False):
900 | """
901 | Make a new BlockGrid from a grid on ipythonblocks.org.
902 |
903 | Parameters
904 | ----------
905 | grid_id : str
906 | ID of a grid on ipythonblocks.org. This will be the part of the
907 | URL after 'ipythonblocks.org/'.
908 | secret : bool, optional
909 | Whether or not the grid on ipythonblocks.org is secret.
910 |
911 | Returns
912 | -------
913 | grid : BlockGrid
914 |
915 | """
916 | import requests
917 |
918 | get_url = _GET_URL_PUBLIC if not secret else _GET_URL_SECRET
919 | resp = requests.get(get_url.format(grid_id))
920 | resp.raise_for_status()
921 | grid_spec = resp.json()
922 |
923 | grid = cls(grid_spec['width'], grid_spec['height'],
924 | lines_on=grid_spec['lines_on'])
925 | grid._load_simple_grid(grid_spec['blocks'])
926 |
927 | return grid
928 |
929 |
930 | class Pixel(Block):
931 | @property
932 | def x(self):
933 | """
934 | Horizontal coordinate of Pixel.
935 |
936 | """
937 | return self._col
938 |
939 | @property
940 | def y(self):
941 | """
942 | Vertical coordinate of Pixel.
943 |
944 | """
945 | return self._row
946 |
947 | @property
948 | def _td(self):
949 | """
950 | The HTML for a table cell with the background color of this Pixel.
951 |
952 | """
953 | title = _TITLE.format(self._col, self._row,
954 | self._red, self._green, self._blue)
955 | rgb = _RGB.format(self._red, self._green, self._blue)
956 | return _TD.format(title, self._size, rgb)
957 |
958 | def __str__(self):
959 | s = ['{0}'.format(self.__class__.__name__),
960 | 'Color: ({0}, {1}, {2})'.format(self._red,
961 | self._green,
962 | self._blue)]
963 |
964 | # add position information if we have it
965 | if self._row is not None:
966 | s[0] += ' [{0}, {1}]'.format(self._col, self._row)
967 |
968 | return os.linesep.join(s)
969 |
970 |
971 | class ImageGrid(BlockGrid):
972 | """
973 | A grid of blocks whose colors can be individually controlled.
974 |
975 | Parameters
976 | ----------
977 | width : int
978 | Number of blocks wide to make the grid.
979 | height : int
980 | Number of blocks high to make the grid.
981 | fill : tuple of int, optional
982 | An optional initial color for the grid, defaults to black.
983 | Specified as a tuple of (red, green, blue). E.g.: (10, 234, 198)
984 | block_size : int, optional
985 | Length of the sides of grid blocks in pixels. One is the lower limit.
986 | lines_on : bool, optional
987 | Whether or not to display lines between blocks.
988 | origin : {'lower-left', 'upper-left'}, optional
989 | Set the location of the grid origin.
990 |
991 | Attributes
992 | ----------
993 | width : int
994 | Number of blocks along the width of the grid.
995 | height : int
996 | Number of blocks along the height of the grid.
997 | shape : tuple of int
998 | A tuple of (width, height).
999 | block_size : int
1000 | Length of the sides of grid blocks in pixels.
1001 | lines_on : bool
1002 | Whether lines are shown between blocks when the grid is displayed.
1003 | This attribute can used to toggle the whether the lines appear.
1004 | origin : str
1005 | The location of the grid origin.
1006 |
1007 | """
1008 |
1009 | def __init__(self, width, height, fill=(0, 0, 0),
1010 | block_size=20, lines_on=True, origin='lower-left'):
1011 | super(ImageGrid, self).__init__(width, height, fill,
1012 | block_size, lines_on)
1013 |
1014 | if origin not in ('lower-left', 'upper-left'):
1015 | s = "origin keyword must be one of {'lower-left', 'upper-left'}."
1016 | raise ValueError(s)
1017 |
1018 | self._origin = origin
1019 |
1020 | def _initialize_grid(self, fill):
1021 | grid = [[Pixel(*fill, size=self._block_size)
1022 | for col in range(self.width)]
1023 | for row in range(self.height)]
1024 |
1025 | self._grid = grid
1026 |
1027 | @property
1028 | def block_size(self):
1029 | return self._block_size
1030 |
1031 | @property
1032 | def origin(self):
1033 | return self._origin
1034 |
1035 | def _transform_index(self, index):
1036 | """
1037 | Transform a single-item index from Python style coordinates to
1038 | image style coordinates in which the first item refers to column and
1039 | the second item refers to row. Also takes into account the
1040 | location of the origin.
1041 |
1042 | """
1043 | # in ImageGrid index is guaranteed to be a tuple.
1044 |
1045 | # first thing, switch the coordinates since ImageGrid is column
1046 | # major and ._grid is row major.
1047 | new_ind = [index[1], index[0]]
1048 |
1049 | # now take into account that the ImageGrid origin may be lower-left,
1050 | # while the ._grid origin is upper-left.
1051 | if self._origin == 'lower-left':
1052 | if new_ind[0] >= 0:
1053 | new_ind[0] = self._height - new_ind[0] - 1
1054 | else:
1055 | new_ind[0] = abs(new_ind[0]) - 1
1056 |
1057 | return tuple(new_ind)
1058 |
1059 | def __getitem__(self, index):
1060 | ind_cat = self._categorize_index(index)
1061 |
1062 | # ImageGrid will only support single item indexing and 2D slices
1063 | if ind_cat not in (_DOUBLE_SLICE, _SINGLE_ITEM):
1064 | s = 'ImageGrid only supports 2D indexing.'
1065 | raise IndexError(s)
1066 |
1067 | if ind_cat == _SINGLE_ITEM:
1068 | # should be able to index ._grid with new_ind regardless of any
1069 | # following coordinate transforms. let's just make sure.
1070 | self._grid[index[1]][index[0]]
1071 |
1072 | real_index = self._transform_index(index)
1073 | pixel = self._grid[real_index[0]][real_index[1]]
1074 | pixel._col, pixel._row = index
1075 | return pixel
1076 |
1077 | elif ind_cat == _DOUBLE_SLICE:
1078 | new_grid = self._get_double_slice(index)
1079 | return self._view_from_grid(new_grid)
1080 |
1081 | def _get_double_slice(self, index):
1082 | cslice, rslice = index
1083 |
1084 | if isinstance(rslice, int):
1085 | if rslice == -1:
1086 | rslice = slice(rslice, None)
1087 | else:
1088 | rslice = slice(rslice, rslice + 1)
1089 |
1090 | if isinstance(cslice, int):
1091 | if cslice == -1:
1092 | cslice = slice(cslice, None)
1093 | else:
1094 | cslice = slice(cslice, cslice + 1)
1095 |
1096 | rows = range(self._height)[rslice]
1097 | if self._origin == 'lower-left':
1098 | rows = rows[::-1]
1099 |
1100 | cols = range(self._width)[cslice]
1101 |
1102 | new_grid = [[self[c, r] for c in cols] for r in rows]
1103 |
1104 | return new_grid
1105 |
1106 | def __iter__(self):
1107 | for col in range(self.width):
1108 | for row in range(self.height):
1109 | yield self[col, row]
1110 |
1111 | def _repr_html_(self):
1112 | rows = range(self._height)
1113 | cols = range(self._width)
1114 |
1115 | if self._origin == 'lower-left':
1116 | rows = rows[::-1]
1117 |
1118 | html = reduce(iadd,
1119 | (_TR.format(reduce(iadd,
1120 | (self[c, r]._td
1121 | for c in cols)))
1122 | for r in rows))
1123 |
1124 | return _TABLE.format(uuid.uuid4(), int(self._lines_on), html)
1125 |
1126 | @classmethod
1127 | def from_web(cls, grid_id, secret=False, origin='lower-left'):
1128 | """
1129 | Make a new ImageGrid from a grid on ipythonblocks.org.
1130 |
1131 | Parameters
1132 | ----------
1133 | grid_id : str
1134 | ID of a grid on ipythonblocks.org. This will be the part of the
1135 | URL after 'ipythonblocks.org/'.
1136 | secret : bool, optional
1137 | Whether or not the grid on ipythonblocks.org is secret.
1138 | origin : {'lower-left', 'upper-left'}, optional
1139 | Set the location of the grid origin.
1140 |
1141 | Returns
1142 | -------
1143 | grid : ImageGrid
1144 |
1145 | """
1146 | import requests
1147 |
1148 | get_url = _GET_URL_PUBLIC if not secret else _GET_URL_SECRET
1149 | resp = requests.get(get_url.format(grid_id))
1150 | resp.raise_for_status()
1151 | grid_spec = resp.json()
1152 |
1153 | grid = cls(grid_spec['width'], grid_spec['height'],
1154 | lines_on=grid_spec['lines_on'], origin=origin)
1155 | grid._load_simple_grid(grid_spec['blocks'])
1156 |
1157 | return grid
1158 |
1159 |
1160 | # Convenience wrapper for color tuples with attribute access for the
1161 | # component colors
1162 | Color = namedtuple('Color', ['red', 'green', 'blue'])
1163 |
1164 | # This doesn't work on Python 2
1165 | # Color.__doc__ += """\
1166 | # Wrapper for a color tuple that provides .red, .green, and .blue
1167 | # attributes for access to the individual components.
1168 | # """
1169 |
1170 |
1171 | # As a convenience, provide some colors as a custom hybrid
1172 | # dictionary and object with the color names as attributes
1173 | class _ColorBunch(dict):
1174 | """
1175 | Customized dictionary that exposes its keys as attributes.
1176 |
1177 | """
1178 | def __init__(self, colors):
1179 | colors = {name: Color(*rgb) for name, rgb in colors.items()}
1180 | super(_ColorBunch, self).__init__(colors)
1181 | self.__dict__.update(colors)
1182 |
1183 |
1184 | # HTML colors
1185 | colors = _ColorBunch({
1186 | 'AliceBlue': (240, 248, 255),
1187 | 'AntiqueWhite': (250, 235, 215),
1188 | 'Aqua': (0, 255, 255),
1189 | 'Aquamarine': (127, 255, 212),
1190 | 'Azure': (240, 255, 255),
1191 | 'Beige': (245, 245, 220),
1192 | 'Bisque': (255, 228, 196),
1193 | 'Black': (0, 0, 0),
1194 | 'BlanchedAlmond': (255, 235, 205),
1195 | 'Blue': (0, 0, 255),
1196 | 'BlueViolet': (138, 43, 226),
1197 | 'Brown': (165, 42, 42),
1198 | 'BurlyWood': (222, 184, 135),
1199 | 'CadetBlue': (95, 158, 160),
1200 | 'Chartreuse': (127, 255, 0),
1201 | 'Chocolate': (210, 105, 30),
1202 | 'Coral': (255, 127, 80),
1203 | 'CornflowerBlue': (100, 149, 237),
1204 | 'Cornsilk': (255, 248, 220),
1205 | 'Crimson': (220, 20, 60),
1206 | 'Cyan': (0, 255, 255),
1207 | 'DarkBlue': (0, 0, 139),
1208 | 'DarkCyan': (0, 139, 139),
1209 | 'DarkGoldenrod': (184, 134, 11),
1210 | 'DarkGray': (169, 169, 169),
1211 | 'DarkGreen': (0, 100, 0),
1212 | 'DarkKhaki': (189, 183, 107),
1213 | 'DarkMagenta': (139, 0, 139),
1214 | 'DarkOliveGreen': (85, 107, 47),
1215 | 'DarkOrange': (255, 140, 0),
1216 | 'DarkOrchid': (153, 50, 204),
1217 | 'DarkRed': (139, 0, 0),
1218 | 'DarkSalmon': (233, 150, 122),
1219 | 'DarkSeaGreen': (143, 188, 143),
1220 | 'DarkSlateBlue': (72, 61, 139),
1221 | 'DarkSlateGray': (47, 79, 79),
1222 | 'DarkTurquoise': (0, 206, 209),
1223 | 'DarkViolet': (148, 0, 211),
1224 | 'DeepPink': (255, 20, 147),
1225 | 'DeepSkyBlue': (0, 191, 255),
1226 | 'DimGray': (105, 105, 105),
1227 | 'DodgerBlue': (30, 144, 255),
1228 | 'FireBrick': (178, 34, 34),
1229 | 'FloralWhite': (255, 250, 240),
1230 | 'ForestGreen': (34, 139, 34),
1231 | 'Fuchsia': (255, 0, 255),
1232 | 'Gainsboro': (220, 220, 220),
1233 | 'GhostWhite': (248, 248, 255),
1234 | 'Gold': (255, 215, 0),
1235 | 'Goldenrod': (218, 165, 32),
1236 | 'Gray': (128, 128, 128),
1237 | 'Green': (0, 128, 0),
1238 | 'GreenYellow': (173, 255, 47),
1239 | 'Honeydew': (240, 255, 240),
1240 | 'HotPink': (255, 105, 180),
1241 | 'IndianRed': (205, 92, 92),
1242 | 'Indigo': (75, 0, 130),
1243 | 'Ivory': (255, 255, 240),
1244 | 'Khaki': (240, 230, 140),
1245 | 'Lavender': (230, 230, 250),
1246 | 'LavenderBlush': (255, 240, 245),
1247 | 'LawnGreen': (124, 252, 0),
1248 | 'LemonChiffon': (255, 250, 205),
1249 | 'LightBlue': (173, 216, 230),
1250 | 'LightCoral': (240, 128, 128),
1251 | 'LightCyan': (224, 255, 255),
1252 | 'LightGoldenrodYellow': (250, 250, 210),
1253 | 'LightGray': (211, 211, 211),
1254 | 'LightGreen': (144, 238, 144),
1255 | 'LightPink': (255, 182, 193),
1256 | 'LightSalmon': (255, 160, 122),
1257 | 'LightSeaGreen': (32, 178, 170),
1258 | 'LightSkyBlue': (135, 206, 250),
1259 | 'LightSlateGray': (119, 136, 153),
1260 | 'LightSteelBlue': (176, 196, 222),
1261 | 'LightYellow': (255, 255, 224),
1262 | 'Lime': (0, 255, 0),
1263 | 'LimeGreen': (50, 205, 50),
1264 | 'Linen': (250, 240, 230),
1265 | 'Magenta': (255, 0, 255),
1266 | 'Maroon': (128, 0, 0),
1267 | 'MediumAquamarine': (102, 205, 170),
1268 | 'MediumBlue': (0, 0, 205),
1269 | 'MediumOrchid': (186, 85, 211),
1270 | 'MediumPurple': (147, 112, 219),
1271 | 'MediumSeaGreen': (60, 179, 113),
1272 | 'MediumSlateBlue': (123, 104, 238),
1273 | 'MediumSpringGreen': (0, 250, 154),
1274 | 'MediumTurquoise': (72, 209, 204),
1275 | 'MediumVioletRed': (199, 21, 133),
1276 | 'MidnightBlue': (25, 25, 112),
1277 | 'MintCream': (245, 255, 250),
1278 | 'MistyRose': (255, 228, 225),
1279 | 'Moccasin': (255, 228, 181),
1280 | 'NavajoWhite': (255, 222, 173),
1281 | 'Navy': (0, 0, 128),
1282 | 'OldLace': (253, 245, 230),
1283 | 'Olive': (128, 128, 0),
1284 | 'OliveDrab': (107, 142, 35),
1285 | 'Orange': (255, 165, 0),
1286 | 'OrangeRed': (255, 69, 0),
1287 | 'Orchid': (218, 112, 214),
1288 | 'PaleGoldenrod': (238, 232, 170),
1289 | 'PaleGreen': (152, 251, 152),
1290 | 'PaleTurquoise': (175, 238, 238),
1291 | 'PaleVioletRed': (219, 112, 147),
1292 | 'PapayaWhip': (255, 239, 213),
1293 | 'PeachPuff': (255, 218, 185),
1294 | 'Peru': (205, 133, 63),
1295 | 'Pink': (255, 192, 203),
1296 | 'Plum': (221, 160, 221),
1297 | 'PowderBlue': (176, 224, 230),
1298 | 'Purple': (128, 0, 128),
1299 | 'Red': (255, 0, 0),
1300 | 'RosyBrown': (188, 143, 143),
1301 | 'RoyalBlue': (65, 105, 225),
1302 | 'SaddleBrown': (139, 69, 19),
1303 | 'Salmon': (250, 128, 114),
1304 | 'SandyBrown': (244, 164, 96),
1305 | 'SeaGreen': (46, 139, 87),
1306 | 'Seashell': (255, 245, 238),
1307 | 'Sienna': (160, 82, 45),
1308 | 'Silver': (192, 192, 192),
1309 | 'SkyBlue': (135, 206, 235),
1310 | 'SlateBlue': (106, 90, 205),
1311 | 'SlateGray': (112, 128, 144),
1312 | 'Snow': (255, 250, 250),
1313 | 'SpringGreen': (0, 255, 127),
1314 | 'SteelBlue': (70, 130, 180),
1315 | 'Tan': (210, 180, 140),
1316 | 'Teal': (0, 128, 128),
1317 | 'Thistle': (216, 191, 216),
1318 | 'Tomato': (255, 99, 71),
1319 | 'Turquoise': (64, 224, 208),
1320 | 'Violet': (238, 130, 238),
1321 | 'Wheat': (245, 222, 179),
1322 | 'White': (255, 255, 255),
1323 | 'WhiteSmoke': (245, 245, 245),
1324 | 'Yellow': (255, 255, 0),
1325 | 'YellowGreen': (154, 205, 50)
1326 | })
1327 |
1328 |
1329 | # Flat UI colors: http://flatuicolors.com/
1330 | fui_colors = _ColorBunch({
1331 | 'Alizarin': (231, 76, 60),
1332 | 'Pomegranate': (192, 57, 43),
1333 | 'Carrot': (230, 126, 34),
1334 | 'Pumpkin': (211, 84, 0),
1335 | 'SunFlower': (241, 196, 15),
1336 | 'Orange': (243, 156, 18),
1337 | 'Emerald': (46, 204, 113),
1338 | 'Nephritis': (39, 174, 96),
1339 | 'Turquoise': (26, 188, 156),
1340 | 'GreenSea': (22, 160, 133),
1341 | 'PeterRiver': (52, 152, 219),
1342 | 'BelizeHole': (41, 128, 185),
1343 | 'Amethyst': (155, 89, 182),
1344 | 'Wisteria': (142, 68, 173),
1345 | 'WetAsphalt': (52, 73, 94),
1346 | 'MidnightBlue': (44, 62, 80),
1347 | 'Concrete': (149, 165, 166),
1348 | 'Asbestos': (127, 140, 141),
1349 | 'Clouds': (236, 240, 241),
1350 | 'Silver': (189, 195, 199)
1351 | })
1352 |
--------------------------------------------------------------------------------
/playing_with_blocks_exercises.md:
--------------------------------------------------------------------------------
1 | # Exercises Accompanying the "Playing with Blocks" Notebook
2 |
3 | ## Exercise 1
4 |
5 | Make a 5x5 `BlockGrid` with your favorite color (from the "Colors" notebook)
6 | and assign it to a variable called `grid`.
7 |
8 | ## Exercise 2
9 |
10 | Change the color of the block in the third row and fourth column.
11 |
12 | ## Exercise 3
13 |
14 | Change the color of the block in the lower-right corner of the grid.
15 |
16 | ## Exercise 4
17 |
18 | Use negative indexing to change the color of the block in the second row
19 | and first column.
20 |
21 | ## Exercise 5
22 |
23 | Use a `for` loop to change the color of every block in your grid.
24 |
25 | ## Exercise 6
26 |
27 | Use a `for` loop and an `if` statement to change the color of every block
28 | in the fourth *column* of your grid.
29 |
30 | ## Exercise 7
31 |
32 | Augment your code from Exercise 6 with an `elif` and an `else` to change the
33 | color of all blocks in your grid. But make the second column red, the
34 | third column green, and every other block gold.
35 |
36 | ## Exercise 8
37 |
38 | Use an `if` with an `or` to change the color of the blocks in the first
39 | and fifth rows to black.
40 |
41 | ## Exercise 9
42 |
43 | Use an `if` with an `and` condition to turn every block that is in the
44 | fourth column AND black to blue.
45 |
46 | ## Exercise 10
47 |
48 | Make a new 20 by 20 block grid. Use a `for` loop with `range` to change the
49 | color of every block in the third, 10th, and 18th rows.
50 |
51 | ## Exercise 11
52 |
53 | Use nested `for` loops with `range` to change the color of the bottom-left
54 | quadrant of your 20 by 20 grid.
55 |
56 | ## Exercise 12
57 |
58 | Try to get the same affects as in Exercises 10 & 11, but using slicing instead
59 | of `for` loops.
60 |
61 | ## Exercise 13
62 |
63 | Write a function that takes a grid as input and returns a new grid with
64 | the colors inverted (e.g. white becomes black, black becomes white,
65 | yellow becomes ???).
66 |
67 | ## Exercise 14
68 |
69 | Write a function that takes a grid and a color as input returns a new grid
70 | that is the input grid with an added border of the given color.
71 |
72 | BONUS: Make the color input argument optional.
73 |
74 | ## Exercise 15
75 |
76 | Write a function that can follow a list of directions
77 | (`'up'`, `'down'`, `'left'`, `'right'`) along a path, changing the color of
78 | blocks as it goes.
79 | An example function definition:
80 |
81 | ```python
82 | def path_color(grid, path, starting_point, color):
83 | ...
84 | ```
85 |
--------------------------------------------------------------------------------