├── .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 | '{2}
') 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 | --------------------------------------------------------------------------------