diff --git a/README.md b/README.md index 060994a..c11d4d5 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,90 @@ -# FIXME Lesson title +# Tetris Game (俄罗斯方块) -[![Create a Slack Account with us](https://img.shields.io/badge/Create_Slack_Account-The_Carpentries-071159.svg)](https://swc-slack-invite.herokuapp.com/) +A simple, classic Tetris game implementation in Python using pygame. -This repository generates the corresponding lesson website from [The Carpentries](https://carpentries.org/) repertoire of lessons. +## Description -## Contributing +This is a fully functional Tetris game featuring: +- Classic Tetris gameplay with all 7 tetromino shapes +- Score tracking +- Smooth piece movement and rotation +- Line clearing mechanics +- Game over detection -We welcome all contributions to improve the lesson! Maintainers will do their best to help you if you have any -questions, concerns, or experience any difficulties along the way. +## Requirements -We'd like to ask you to familiarize yourself with our [Contribution Guide](CONTRIBUTING.md) and have a look at -the [more detailed guidelines][lesson-example] on proper formatting, ways to render the lesson locally, and even -how to write new episodes. +- Python 3.6 or higher +- pygame 2.0.0 or higher -Please see the current list of [issues][FIXME] for ideas for contributing to this -repository. For making your contribution, we use the GitHub flow, which is -nicely explained in the chapter [Contributing to a Project](http://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project) in Pro Git -by Scott Chacon. -Look for the tag ![good_first_issue](https://img.shields.io/badge/-good%20first%20issue-gold.svg). This indicates that the maintainers will welcome a pull request fixing this issue. +## Installation +1. Clone this repository: +```bash +git clone +cd introduction-to-python +``` -## Maintainer(s) +2. Install dependencies: +```bash +pip install -r requirements.txt +``` -Current maintainers of this lesson are +Or install pygame directly: +```bash +pip install pygame +``` -* FIXME -* FIXME -* FIXME +## How to Play +Run the game with: +```bash +python tetris.py +``` -## Authors +### Controls + +- **LEFT/RIGHT Arrow Keys**: Move piece horizontally +- **DOWN Arrow Key**: Soft drop (move piece down faster) +- **UP Arrow Key**: Rotate piece clockwise +- **SPACE**: Hard drop (instantly drop piece to the bottom) + +### Objective + +- Arrange falling tetromino pieces to create complete horizontal lines +- Completed lines are cleared and you earn points +- The game ends when pieces stack up to the top of the screen +- Try to achieve the highest score possible! + +## Scoring + +- Each cleared line awards 100 points +- Clear multiple lines at once for maximum efficiency -A list of contributors to the lesson can be found in [AUTHORS](AUTHORS) +## Game Features -## Citation +- 7 unique tetromino shapes with different colors: + - I-piece (Cyan): 4 blocks in a line + - O-piece (Yellow): 2x2 square + - T-piece (Magenta): T-shaped + - L-piece (Orange): L-shaped + - J-piece (Blue): Reverse L-shaped + - S-piece (Green): S-shaped + - Z-piece (Red): Z-shaped -To cite this lesson, please consult with [CITATION](CITATION) +- Automatic piece falling +- Collision detection +- Piece rotation +- Score display +- Game over screen + +## License + +This project maintains the original LICENSE.md from the repository. + +## Contributing + +Contributions are welcome! Feel free to submit issues or pull requests. + +## Authors -[lesson-example]: https://carpentries.github.io/lesson-example +See [AUTHORS](AUTHORS) file for contributors. diff --git a/_episodes/01-intro.md b/_episodes/01-intro.md deleted file mode 100644 index d50a7a3..0000000 --- a/_episodes/01-intro.md +++ /dev/null @@ -1,313 +0,0 @@ ---- -title: Python Fundamentals -teaching: 20 -exercises: 10 -questions: -- "What basic data types can I work with in Python?" -- "How can I create a new variable in Python?" -- "How do I use a function?" -- "Can I change the value associated with a variable after I create it?" -objectives: -- "Assign values to variables." -keypoints: -- "Basic data types in Python include integers, strings, and floating-point numbers." -- "Use `variable = value` to assign a value to a variable in order to record it in memory." -- "Variables are created on demand whenever a value is assigned to them." -- "Use `print(something)` to display the value of `something`." -- "Built-in functions are always available to use." ---- - -## Variables - -Any Python interpreter can be used as a calculator: -~~~ -3 + 5 * 4 -~~~ -{: .language-python} -~~~ -23 -~~~ -{: .output} - -This is great but not very interesting. -To do anything useful with data, we need to assign its value to a _variable_. -In Python, we can [assign]({{ page.root }}/reference.html#assign) a value to a -[variable]({{ page.root }}/reference.html#variable), using the equals sign `=`. -For example, we can track the weight of a patient who weighs 60 kilograms by -assigning the value `60` to a variable `weight_kg`: - -~~~ -weight_kg = 60 -~~~ -{: .language-python} - -From now on, whenever we use `weight_kg`, Python will substitute the value we assigned to -it. In layman's terms, **a variable is a name for a value**. - -In Python, variable names: - - - can include letters, digits, and underscores - - cannot start with a digit - - are [case sensitive]({{ page.root }}/reference.html#case-sensitive). - -This means that, for example: - - `weight0` is a valid variable name, whereas `0weight` is not - - `weight` and `Weight` are different variables - -## Types of data -Python knows various types of data. Three common ones are: - -* integer numbers -* floating point numbers, and -* strings. - -In the example above, variable `weight_kg` has an integer value of `60`. -If we want to more precisely track the weight of our patient, -we can use a floating point value by executing: - -~~~ -weight_kg = 60.3 -~~~ -{: .language-python} - -To create a string, we add single or double quotes around some text. -To identify and track a patient throughout our study, -we can assign each person a unique identifier by storing it in a string: - -~~~ -patient_id = '001' -~~~ -{: .language-python} - -## Using Variables in Python - -Once we have data stored with variable names, we can make use of it in calculations. -We may want to store our patient's weight in pounds as well as kilograms: - -~~~ -weight_lb = 2.2 * weight_kg -~~~ -{: .language-python} - -We might decide to add a prefix to our patient identifier: - -~~~ -patient_id = 'inflam_' + patient_id -~~~ -{: .language-python} - -## Built-in Python functions - -To carry out common tasks with data and variables in Python, -the language provides us with several built-in [functions]({{ page.root }}/reference.html#function). -To display information to the screen, we use the `print` function: - -~~~ -print(weight_lb) -print(patient_id) -~~~ -{: .language-python} - -~~~ -132.66 -inflam_001 -~~~ -{: .output} - -When we want to make use of a function, referred to as calling the function, -we follow its name by parentheses. The parentheses are important: -if you leave them off, the function doesn't actually run! -Sometimes you will include values or variables inside the parentheses for the function to use. -In the case of `print`, -we use the parentheses to tell the function what value we want to display. -We will learn more about how functions work and how to create our own in later episodes. - -We can display multiple things at once using only one `print` call: - -~~~ -print(patient_id, 'weight in kilograms:', weight_kg) -~~~ -{: .language-python} -~~~ -inflam_001 weight in kilograms: 60.3 -~~~ -{: .output} - -We can also call a function inside of another -[function call]({{ page.root }}/reference.html#function-call). -For example, Python has a built-in function called `type` that tells you a value's data type: - -~~~ -print(type(60.3)) -print(type(patient_id)) -~~~ -{: .language-python} - -~~~ - - -~~~ -{: .output} - -Moreover, we can do arithmetic with variables right inside the `print` function: - -~~~ -print('weight in pounds:', 2.2 * weight_kg) -~~~ -{: .language-python} - -~~~ -weight in pounds: 132.66 -~~~ -{: .output} - -The above command, however, did not change the value of `weight_kg`: -~~~ -print(weight_kg) -~~~ -{: .language-python} - -~~~ -60.3 -~~~ -{: .output} - -To change the value of the `weight_kg` variable, we have to -**assign** `weight_kg` a new value using the equals `=` sign: - -~~~ -weight_kg = 65.0 -print('weight in kilograms is now:', weight_kg) -~~~ -{: .language-python} - -~~~ -weight in kilograms is now: 65.0 -~~~ -{: .output} - -> ## Variables as Sticky Notes -> -> A variable in Python is analogous to a sticky note with a name written on it: -> assigning a value to a variable is like putting that sticky note on a particular value. -> -> ![Value of 65.0 with weight_kg label stuck on it](../fig/python-sticky-note-variables-01.svg) -> -> Using this analogy, we can investigate how assigning a value to one variable -> does **not** change values of other, seemingly related, variables. For -> example, let's store the subject's weight in pounds in its own variable: -> -> ~~~ -> # There are 2.2 pounds per kilogram -> weight_lb = 2.2 * weight_kg -> print('weight in kilograms:', weight_kg, 'and in pounds:', weight_lb) -> ~~~ -> {: .language-python} -> -> ~~~ -> weight in kilograms: 65.0 and in pounds: 143.0 -> ~~~ -> {: .output} -> -> ![Value of 65.0 with weight_kg label stuck on it, and value of 143.0 with weight_lb label -stuck on it](../fig/python-sticky-note-variables-02.svg) -> -> Similar to above, the expression `2.2 * weight_kg` is evaluated to `143.0`, -> and then this value is assigned to the variable `weight_lb` (i.e. the sticky -> note `weight_lb` is placed on `143.0`). At this point, each variable is -> "stuck" to completely distinct and unrelated values. -> -> Let's now change `weight_kg`: -> -> ~~~ -> weight_kg = 100.0 -> print('weight in kilograms is now:', weight_kg, 'and weight in pounds is still:', weight_lb) -> ~~~ -> {: .language-python} -> -> ~~~ -> weight in kilograms is now: 100.0 and weight in pounds is still: 143.0 -> ~~~ -> {: .output} -> -> ![Value of 100.0 with label weight_kg stuck on it, and value of 143.0 with label weight_lb -stuck on it](../fig/python-sticky-note-variables-03.svg) -> -> Since `weight_lb` doesn't "remember" where its value comes from, -> it is not updated when we change `weight_kg`. -{: .callout} - - -> ## Check Your Understanding -> -> What values do the variables `mass` and `age` have after each of the following statements? -> Test your answer by executing the lines. -> -> ~~~ -> mass = 47.5 -> age = 122 -> mass = mass * 2.0 -> age = age - 20 -> ~~~ -> {: .language-python} -> -> > ## Solution -> > ~~~ -> > `mass` holds a value of 47.5, `age` does not exist -> > `mass` still holds a value of 47.5, `age` holds a value of 122 -> > `mass` now has a value of 95.0, `age`'s value is still 122 -> > `mass` still has a value of 95.0, `age` now holds 102 -> > ~~~ -> > {: .output} -> {: .solution} -{: .challenge} - -> ## Sorting Out References -> -> Python allows you to assign multiple values to multiple variables in one line by separating -> the variables and values with commas. What does the following program print out? -> -> ~~~ -> first, second = 'Grace', 'Hopper' -> third, fourth = second, first -> print(third, fourth) -> ~~~ -> {: .language-python} -> -> > ## Solution -> > ~~~ -> > Hopper Grace -> > ~~~ -> > {: .output} -> {: .solution} -{: .challenge} - -> ## Seeing Data Types -> -> What are the data types of the following variables? -> -> ~~~ -> planet = 'Earth' -> apples = 5 -> distance = 10.5 -> ~~~ -> {: .language-python} -> -> > ## Solution -> > ~~~ -> > print(type(planet)) -> > print(type(apples)) -> > print(type(distance)) -> > ~~~ -> > {: .language-python} -> > -> > ~~~ -> > -> > -> > -> > ~~~ -> > {: .output} -> {: .solution} -{: .challenge} - -{% include links.md %} diff --git a/_episodes/02-numpy.md b/_episodes/02-numpy.md deleted file mode 100644 index bb3b819..0000000 --- a/_episodes/02-numpy.md +++ /dev/null @@ -1,801 +0,0 @@ ---- -title: Analyzing Patient Data -teaching: 45 -exercises: 20 -questions: -- "How can I process tabular data files in Python?" -objectives: -- "Explain what a library is and what libraries are used for." -- "Import a Python library and use the functions it contains." -- "Read tabular data from a file into a program." -- "Select individual values and subsections from data." -- "Perform operations on arrays of data." -keypoints: -- "Import a library into a program using `import libraryname`." -- "Use the `numpy` library to work with arrays in Python." -- "The expression `array.shape` gives the shape of an array." -- "Use `array[x, y]` to select a single element from a 2D array." -- "Array indices start at 0, not 1." -- "Use `low:high` to specify a `slice` that includes the indices from `low` to `high-1`." -- "Use `# some kind of explanation` to add comments to programs." -- "Use `numpy.mean(array)`, `numpy.max(array)`, and `numpy.min(array)` to calculate simple statistics." -- "Use `numpy.mean(array, axis=0)` or `numpy.mean(array, axis=1)` to calculate statistics across the specified axis." ---- - -Words are useful, but what's more useful are the sentences and stories we build with them. -Similarly, while a lot of powerful, general tools are built into Python, -specialized tools built up from these basic units live in -[libraries]({{ page.root }}/reference.html#library) -that can be called upon when needed. - -## Loading data into Python - -To begin processing inflammation data, we need to load it into Python. -We can do that using a library called -[NumPy](http://docs.scipy.org/doc/numpy/ "NumPy Documentation"), which stands for Numerical Python. -In general, you should use this library when you want to do fancy things with lots of numbers, -especially if you have matrices or arrays. To tell Python that we'd like to start using NumPy, -we need to [import]({{ page.root }}/reference.html#import) it: - -~~~ -import numpy -~~~ -{: .language-python} - -Importing a library is like getting a piece of lab equipment out of a storage locker and setting it -up on the bench. Libraries provide additional functionality to the basic Python package, much like -a new piece of equipment adds functionality to a lab space. Just like in the lab, importing too -many libraries can sometimes complicate and slow down your programs - so we only import what we -need for each program. - -Once we've imported the library, we can ask the library to read our data file for us: - -~~~ -numpy.loadtxt(fname='inflammation-01.csv', delimiter=',') -~~~ -{: .language-python} - -~~~ -array([[ 0., 0., 1., ..., 3., 0., 0.], - [ 0., 1., 2., ..., 1., 0., 1.], - [ 0., 1., 1., ..., 2., 1., 1.], - ..., - [ 0., 1., 1., ..., 1., 1., 1.], - [ 0., 0., 0., ..., 0., 2., 0.], - [ 0., 0., 1., ..., 1., 1., 0.]]) -~~~ -{: .output} - -The expression `numpy.loadtxt(...)` is a -[function call]({{ page.root }}/reference.html#function-call) -that asks Python to run the [function]({{ page.root }}/reference.html#function) `loadtxt` which -belongs to the `numpy` library. -This [dotted notation]({{ page.root }}/reference.html#dotted-notation) -is used everywhere in Python: the thing that appears before the dot contains the thing that -appears after. - -As an example, John Smith is the John that belongs to the Smith family. -We could use the dot notation to write his name `smith.john`, -just as `loadtxt` is a function that belongs to the `numpy` library. - -`numpy.loadtxt` has two [parameters]({{ page.root }}/reference.html#parameter): the name of the file -we want to read and the [delimiter]({{ page.root }}/reference.html#delimiter) that separates values -on a line. These both need to be character strings -(or [strings]({{ page.root }}/reference.html#string) for short), so we put them in quotes. - -Since we haven't told it to do anything else with the function's output, -the [notebook]({{ page.root }}/reference.html#notebook) displays it. -In this case, -that output is the data we just loaded. -By default, -only a few rows and columns are shown -(with `...` to omit elements when displaying big arrays). -Note that, to save space when displaying NumPy arrays, Python does not show us trailing zeros, -so `1.0` becomes `1.`. - -Our call to `numpy.loadtxt` read our file -but didn't save the data in memory. -To do that, -we need to assign the array to a variable. In a similar manner to how we assign a single -value to a variable, we can also assign an array of values to a variable using the same syntax. -Let's re-run `numpy.loadtxt` and save the returned data: - -~~~ -data = numpy.loadtxt(fname='inflammation-01.csv', delimiter=',') -~~~ -{: .language-python} - -This statement doesn't produce any output because we've assigned the output to the variable `data`. -If we want to check that the data have been loaded, -we can print the variable's value: - -~~~ -print(data) -~~~ -{: .language-python} - -~~~ -[[ 0. 0. 1. ..., 3. 0. 0.] - [ 0. 1. 2. ..., 1. 0. 1.] - [ 0. 1. 1. ..., 2. 1. 1.] - ..., - [ 0. 1. 1. ..., 1. 1. 1.] - [ 0. 0. 0. ..., 0. 2. 0.] - [ 0. 0. 1. ..., 1. 1. 0.]] -~~~ -{: .output} - -Now that the data are in memory, -we can manipulate them. -First, -let's ask what [type]({{ page.root }}/reference.html#type) of thing `data` refers to: - -~~~ -print(type(data)) -~~~ -{: .language-python} - -~~~ - -~~~ -{: .output} - -The output tells us that `data` currently refers to -an N-dimensional array, the functionality for which is provided by the NumPy library. -These data correspond to arthritis patients' inflammation. -The rows are the individual patients, and the columns -are their daily inflammation measurements. - -> ## Data Type -> -> A Numpy array contains one or more elements -> of the same type. The `type` function will only tell you that -> a variable is a NumPy array but won't tell you the type of -> thing inside the array. -> We can find out the type -> of the data contained in the NumPy array. -> -> ~~~ -> print(data.dtype) -> ~~~ -> {: .language-python} -> -> ~~~ -> float64 -> ~~~ -> {: .output} -> -> This tells us that the NumPy array's elements are -> [floating-point numbers]({{ page.root }}/reference.html#floating-point number). -{: .callout} - -With the following command, we can see the array's [shape]({{ page.root }}/reference.html#shape): - -~~~ -print(data.shape) -~~~ -{: .language-python} - -~~~ -(60, 40) -~~~ -{: .output} - -The output tells us that the `data` array variable contains 60 rows and 40 columns. When we -created the variable `data` to store our arthritis data, we did not only create the array; we also -created information about the array, called [members]({{ page.root }}/reference.html#member) or -attributes. This extra information describes `data` in the same way an adjective describes a noun. -`data.shape` is an attribute of `data` which describes the dimensions of `data`. We use the same -dotted notation for the attributes of variables that we use for the functions in libraries because -they have the same part-and-whole relationship. - -If we want to get a single number from the array, we must provide an -[index]({{ page.root }}/reference.html#index) in square brackets after the variable name, just as we -do in math when referring to an element of a matrix. Our inflammation data has two dimensions, so -we will need to use two indices to refer to one specific value: - -~~~ -print('first value in data:', data[0, 0]) -~~~ -{: .language-python} - -~~~ -first value in data: 0.0 -~~~ -{: .output} - -~~~ -print('middle value in data:', data[30, 20]) -~~~ -{: .language-python} - -~~~ -middle value in data: 13.0 -~~~ -{: .output} - -The expression `data[30, 20]` accesses the element at row 30, column 20. While this expression may -not surprise you, - `data[0, 0]` might. -Programming languages like Fortran, MATLAB and R start counting at 1 -because that's what human beings have done for thousands of years. -Languages in the C family (including C++, Java, Perl, and Python) count from 0 -because it represents an offset from the first value in the array (the second -value is offset by one index from the first value). This is closer to the way -that computers represent arrays (if you are interested in the historical -reasons behind counting indices from zero, you can read -[Mike Hoye's blog post](http://exple.tive.org/blarg/2013/10/22/citation-needed/)). -As a result, -if we have an M×N array in Python, -its indices go from 0 to M-1 on the first axis -and 0 to N-1 on the second. -It takes a bit of getting used to, -but one way to remember the rule is that -the index is how many steps we have to take from the start to get the item we want. - -!["data" is a 3 by 3 numpy array containing row 0: ['A', 'B', 'C'], row 1: ['D', 'E', 'F'], and -row 2: ['G', 'H', 'I']. Starting in the upper left hand corner, data[0, 0] = 'A', data[0, 1] = 'B', -data[0, 2] = 'C', data[1, 0] = 'D', data[1, 1] = 'E', data[1, 2] = 'F', data[2, 0] = 'G', -data[2, 1] = 'H', and data[2, 2] = 'I', -in the bottom right hand corner.](../fig/python-zero-index.svg) - -> ## In the Corner -> -> What may also surprise you is that when Python displays an array, -> it shows the element with index `[0, 0]` in the upper left corner -> rather than the lower left. -> This is consistent with the way mathematicians draw matrices -> but different from the Cartesian coordinates. -> The indices are (row, column) instead of (column, row) for the same reason, -> which can be confusing when plotting data. -{: .callout} - -## Slicing data -An index like `[30, 20]` selects a single element of an array, -but we can select whole sections as well. -For example, -we can select the first ten days (columns) of values -for the first four patients (rows) like this: - -~~~ -print(data[0:4, 0:10]) -~~~ -{: .language-python} - -~~~ -[[ 0. 0. 1. 3. 1. 2. 4. 7. 8. 3.] - [ 0. 1. 2. 1. 2. 1. 3. 2. 2. 6.] - [ 0. 1. 1. 3. 3. 2. 6. 2. 5. 9.] - [ 0. 0. 2. 0. 4. 2. 2. 1. 6. 7.]] -~~~ -{: .output} - -The [slice]({{ page.root }}/reference.html#slice) `0:4` means, "Start at index 0 and go up to, -but not including, index 4". Again, the up-to-but-not-including takes a bit of getting used to, -but the rule is that the difference between the upper and lower bounds is the number of values in -the slice. - -We don't have to start slices at 0: - -~~~ -print(data[5:10, 0:10]) -~~~ -{: .language-python} - -~~~ -[[ 0. 0. 1. 2. 2. 4. 2. 1. 6. 4.] - [ 0. 0. 2. 2. 4. 2. 2. 5. 5. 8.] - [ 0. 0. 1. 2. 3. 1. 2. 3. 5. 3.] - [ 0. 0. 0. 3. 1. 5. 6. 5. 5. 8.] - [ 0. 1. 1. 2. 1. 3. 5. 3. 5. 8.]] -~~~ -{: .output} - -We also don't have to include the upper and lower bound on the slice. If we don't include the lower -bound, Python uses 0 by default; if we don't include the upper, the slice runs to the end of the -axis, and if we don't include either (i.e., if we use ':' on its own), the slice includes -everything: - -~~~ -small = data[:3, 36:] -print('small is:') -print(small) -~~~ -{: .language-python} -The above example selects rows 0 through 2 and columns 36 through to the end of the array. - -~~~ -small is: -[[ 2. 3. 0. 0.] - [ 1. 1. 0. 1.] - [ 2. 2. 1. 1.]] -~~~ -{: .output} - -## Analyzing data - -NumPy has several useful functions that take an array as input to perform operations on its values. -If we want to find the average inflammation for all patients on -all days, for example, we can ask NumPy to compute `data`'s mean value: - -~~~ -print(numpy.mean(data)) -~~~ -{: .language-python} - -~~~ -6.14875 -~~~ -{: .output} - -`mean` is a [function]({{ page.root }}/reference.html#function) that takes -an array as an [argument]({{ page.root }}/reference.html#argument). - -> ## Not All Functions Have Input -> -> Generally, a function uses inputs to produce outputs. -> However, some functions produce outputs without -> needing any input. For example, checking the current time -> doesn't require any input. -> -> ~~~ -> import time -> print(time.ctime()) -> ~~~ -> {: .language-python} -> -> ~~~ -> Sat Mar 26 13:07:33 2016 -> ~~~ -> {: .output} -> -> For functions that don't take in any arguments, -> we still need parentheses (`()`) -> to tell Python to go and do something for us. -{: .callout} - -Let's use three other NumPy functions to get some descriptive values about the dataset. -We'll also use multiple assignment, -a convenient Python feature that will enable us to do this all in one line. - -~~~ -maxval, minval, stdval = numpy.max(data), numpy.min(data), numpy.std(data) - -print('maximum inflammation:', maxval) -print('minimum inflammation:', minval) -print('standard deviation:', stdval) -~~~ -{: .language-python} - -Here we've assigned the return value from `numpy.max(data)` to the variable `maxval`, the value -from `numpy.min(data)` to `minval`, and so on. - -~~~ -maximum inflammation: 20.0 -minimum inflammation: 0.0 -standard deviation: 4.61383319712 -~~~ -{: .output} - -> ## Mystery Functions in IPython -> -> How did we know what functions NumPy has and how to use them? -> If you are working in IPython or in a Jupyter Notebook, there is an easy way to find out. -> If you type the name of something followed by a dot, then you can use -> [tab completion]({{ page.root }}/reference.html#tab-completion) -> (e.g. type `numpy.` and then press Tab) -> to see a list of all functions and attributes that you can use. After selecting one, you -> can also add a question mark (e.g. `numpy.cumprod?`), and IPython will return an -> explanation of the method! This is the same as doing `help(numpy.cumprod)`. -> Similarly, if you are using the "plain vanilla" Python interpreter, you can type `numpy.` -> and press the Tab key twice for a listing of what is available. You can then use the -> `help()` function to see an explanation of the function you're interested in, -> for example: `help(numpy.cumprod)`. -{: .callout} - -When analyzing data, though, -we often want to look at variations in statistical values, -such as the maximum inflammation per patient -or the average inflammation per day. -One way to do this is to create a new temporary array of the data we want, -then ask it to do the calculation: - -~~~ -patient_0 = data[0, :] # 0 on the first axis (rows), everything on the second (columns) -print('maximum inflammation for patient 0:', numpy.max(patient_0)) -~~~ -{: .language-python} - -~~~ -maximum inflammation for patient 0: 18.0 -~~~ -{: .output} - -Everything in a line of code following the '#' symbol is a -[comment]({{ page.root }}/reference.html#comment) that is ignored by Python. -Comments allow programmers to leave explanatory notes for other -programmers or their future selves. - -We don't actually need to store the row in a variable of its own. -Instead, we can combine the selection and the function call: - -~~~ -print('maximum inflammation for patient 2:', numpy.max(data[2, :])) -~~~ -{: .language-python} - -~~~ -maximum inflammation for patient 2: 19.0 -~~~ -{: .output} - -What if we need the maximum inflammation for each patient over all days (as in the -next diagram on the left) or the average for each day (as in the -diagram on the right)? As the diagram below shows, we want to perform the -operation across an axis: - -![Per-patient maximum inflammation is computed row-wise across all columns using -numpy.max(data, axis=1). Per-day average inflammation is computed column-wise across all rows using -numpy.mean(data, axis=0).](../fig/python-operations-across-axes.png) - -To support this functionality, -most array functions allow us to specify the axis we want to work on. -If we ask for the average across axis 0 (rows in our 2D example), -we get: - -~~~ -print(numpy.mean(data, axis=0)) -~~~ -{: .language-python} - -~~~ -[ 0. 0.45 1.11666667 1.75 2.43333333 3.15 - 3.8 3.88333333 5.23333333 5.51666667 5.95 5.9 - 8.35 7.73333333 8.36666667 9.5 9.58333333 - 10.63333333 11.56666667 12.35 13.25 11.96666667 - 11.03333333 10.16666667 10. 8.66666667 9.15 7.25 - 7.33333333 6.58333333 6.06666667 5.95 5.11666667 3.6 - 3.3 3.56666667 2.48333333 1.5 1.13333333 - 0.56666667] -~~~ -{: .output} - -As a quick check, -we can ask this array what its shape is: - -~~~ -print(numpy.mean(data, axis=0).shape) -~~~ -{: .language-python} - -~~~ -(40,) -~~~ -{: .output} - -The expression `(40,)` tells us we have an N×1 vector, -so this is the average inflammation per day for all patients. -If we average across axis 1 (columns in our 2D example), we get: - -~~~ -print(numpy.mean(data, axis=1)) -~~~ -{: .language-python} - -~~~ -[ 5.45 5.425 6.1 5.9 5.55 6.225 5.975 6.65 6.625 6.525 - 6.775 5.8 6.225 5.75 5.225 6.3 6.55 5.7 5.85 6.55 - 5.775 5.825 6.175 6.1 5.8 6.425 6.05 6.025 6.175 6.55 - 6.175 6.35 6.725 6.125 7.075 5.725 5.925 6.15 6.075 5.75 - 5.975 5.725 6.3 5.9 6.75 5.925 7.225 6.15 5.95 6.275 5.7 - 6.1 6.825 5.975 6.725 5.7 6.25 6.4 7.05 5.9 ] -~~~ -{: .output} - -which is the average inflammation per patient across all days. - - -> ## Slicing Strings -> -> A section of an array is called a [slice]({{ page.root }}/reference.html#slice). -> We can take slices of character strings as well: -> -> ~~~ -> element = 'oxygen' -> print('first three characters:', element[0:3]) -> print('last three characters:', element[3:6]) -> ~~~ -> {: .language-python} -> -> ~~~ -> first three characters: oxy -> last three characters: gen -> ~~~ -> {: .output} -> -> What is the value of `element[:4]`? -> What about `element[4:]`? -> Or `element[:]`? -> -> > ## Solution -> > ~~~ -> > oxyg -> > en -> > oxygen -> > ~~~ -> > {: .output} -> {: .solution} -> -> What is `element[-1]`? -> What is `element[-2]`? -> -> > ## Solution -> > ~~~ -> > n -> > e -> > ~~~ -> > {: .output} -> {: .solution} -> -> Given those answers, -> explain what `element[1:-1]` does. -> -> > ## Solution -> > Creates a substring from index 1 up to (not including) the final index, -> > effectively removing the first and last letters from 'oxygen' -> {: .solution} -> -> How can we rewrite the slice for getting the last three characters of `element`, -> so that it works even if we assign a different string to `element`? -> Test your solution with the following strings: `carpentry`, `clone`, `hi`. -> -> > ## Solution -> > ~~~ -> > element = 'oxygen' -> > print('last three characters:', element[-3:]) -> > element = 'carpentry' -> > print('last three characters:', element[-3:]) -> > element = 'clone' -> > print('last three characters:', element[-3:]) -> > element = 'hi' -> > print('last three characters:', element[-3:]) -> > ~~~ -> > {: .language-python} -> > ~~~ -> > last three characters: gen -> > last three characters: try -> > last three characters: one -> > last three characters: hi -> > ~~~ -> > {: .output} -> {: .solution} -{: .challenge} - -> ## Thin Slices -> -> The expression `element[3:3]` produces an -> [empty string]({{ page.root }}/reference.html#empty-string), -> i.e., a string that contains no characters. -> If `data` holds our array of patient data, -> what does `data[3:3, 4:4]` produce? -> What about `data[3:3, :]`? -> -> > ## Solution -> > ~~~ -> > array([], shape=(0, 0), dtype=float64) -> > array([], shape=(0, 40), dtype=float64) -> > ~~~ -> > {: .output} -> {: .solution} -{: .challenge} - -> ## Stacking Arrays -> -> Arrays can be concatenated and stacked on top of one another, -> using NumPy's `vstack` and `hstack` functions for vertical and horizontal stacking, respectively. -> -> ~~~ -> import numpy -> -> A = numpy.array([[1,2,3], [4,5,6], [7, 8, 9]]) -> print('A = ') -> print(A) -> -> B = numpy.hstack([A, A]) -> print('B = ') -> print(B) -> -> C = numpy.vstack([A, A]) -> print('C = ') -> print(C) -> ~~~ -> {: .language-python} -> -> ~~~ -> A = -> [[1 2 3] -> [4 5 6] -> [7 8 9]] -> B = -> [[1 2 3 1 2 3] -> [4 5 6 4 5 6] -> [7 8 9 7 8 9]] -> C = -> [[1 2 3] -> [4 5 6] -> [7 8 9] -> [1 2 3] -> [4 5 6] -> [7 8 9]] -> ~~~ -> {: .output} -> -> Write some additional code that slices the first and last columns of `A`, -> and stacks them into a 3x2 array. -> Make sure to `print` the results to verify your solution. -> -> > ## Solution -> > -> > A 'gotcha' with array indexing is that singleton dimensions -> > are dropped by default. That means `A[:, 0]` is a one dimensional -> > array, which won't stack as desired. To preserve singleton dimensions, -> > the index itself can be a slice or array. For example, `A[:, :1]` returns -> > a two dimensional array with one singleton dimension (i.e. a column -> > vector). -> > -> > ~~~ -> > D = numpy.hstack((A[:, :1], A[:, -1:])) -> > print('D = ') -> > print(D) -> > ~~~ -> > {: .language-python} -> > -> > ~~~ -> > D = -> > [[1 3] -> > [4 6] -> > [7 9]] -> > ~~~ -> > {: .output} -> {: .solution} -> -> > ## Solution -> > -> > An alternative way to achieve the same result is to use Numpy's -> > delete function to remove the second column of A. -> > -> > ~~~ -> > D = numpy.delete(A, 1, 1) -> > print('D = ') -> > print(D) -> > ~~~ -> > {: .language-python} -> > -> > ~~~ -> > D = -> > [[1 3] -> > [4 6] -> > [7 9]] -> > ~~~ -> > {: .output} -> {: .solution} -{: .challenge} - -> ## Change In Inflammation -> -> The patient data is _longitudinal_ in the sense that each row represents a -> series of observations relating to one individual. This means that -> the change in inflammation over time is a meaningful concept. -> Let's find out how to calculate changes in the data contained in an array -> with NumPy. -> -> The `numpy.diff()` function takes an array and returns the differences -> between two successive values. Let's use it to examine the changes -> each day across the first week of patient 3 from our inflammation dataset. -> -> ~~~ -> patient3_week1 = data[3, :7] -> print(patient3_week1) -> ~~~ -> {: .language-python} -> -> ~~~ -> [0. 0. 2. 0. 4. 2. 2.] -> ~~~ -> {: .output} -> -> Calling `numpy.diff(patient3_week1)` would do the following calculations -> -> ~~~ -> [ 0 - 0, 2 - 0, 0 - 2, 4 - 0, 2 - 4, 2 - 2 ] -> ~~~ -> {: .language-python} -> -> and return the 6 difference values in a new array. -> -> ~~~ -> numpy.diff(patient3_week1) -> ~~~ -> {: .language-python} -> -> ~~~ -> array([ 0., 2., -2., 4., -2., 0.]) -> ~~~ -> {: .output} -> -> Note that the array of differences is shorter by one element (length 6). -> -> When calling `numpy.diff` with a multi-dimensional array, an `axis` argument may -> be passed to the function to specify which axis to process. When applying -> `numpy.diff` to our 2D inflammation array `data`, which axis would we specify? -> -> > ## Solution -> > Since the row axis (0) is patients, it does not make sense to get the -> > difference between two arbitrary patients. The column axis (1) is in -> > days, so the difference is the change in inflammation -- a meaningful -> > concept. -> > -> > ~~~ -> > numpy.diff(data, axis=1) -> > ~~~ -> > {: .language-python} -> {: .solution} -> -> If the shape of an individual data file is `(60, 40)` (60 rows and 40 -> columns), what would the shape of the array be after you run the `diff()` -> function and why? -> -> > ## Solution -> > The shape will be `(60, 39)` because there is one fewer difference between -> > columns than there are columns in the data. -> {: .solution} -> -> How would you find the largest change in inflammation for each patient? Does -> it matter if the change in inflammation is an increase or a decrease? -> -> > ## Solution -> > By using the `numpy.max()` function after you apply the `numpy.diff()` -> > function, you will get the largest difference between days. -> > -> > ~~~ -> > numpy.max(numpy.diff(data, axis=1), axis=1) -> > ~~~ -> > {: .language-python} -> > -> > ~~~ -> > array([ 7., 12., 11., 10., 11., 13., 10., 8., 10., 10., 7., -> > 7., 13., 7., 10., 10., 8., 10., 9., 10., 13., 7., -> > 12., 9., 12., 11., 10., 10., 7., 10., 11., 10., 8., -> > 11., 12., 10., 9., 10., 13., 10., 7., 7., 10., 13., -> > 12., 8., 8., 10., 10., 9., 8., 13., 10., 7., 10., -> > 8., 12., 10., 7., 12.]) -> > ~~~ -> > {: .language-python} -> > -> > If inflammation values *decrease* along an axis, then the difference from -> > one element to the next will be negative. If -> > you are interested in the **magnitude** of the change and not the -> > direction, the `numpy.absolute()` function will provide that. -> > -> > Notice the difference if you get the largest _absolute_ difference -> > between readings. -> > -> > ~~~ -> > numpy.max(numpy.absolute(numpy.diff(data, axis=1)), axis=1) -> > ~~~ -> > {: .language-python} -> > -> > ~~~ -> > array([ 12., 14., 11., 13., 11., 13., 10., 12., 10., 10., 10., -> > 12., 13., 10., 11., 10., 12., 13., 9., 10., 13., 9., -> > 12., 9., 12., 11., 10., 13., 9., 13., 11., 11., 8., -> > 11., 12., 13., 9., 10., 13., 11., 11., 13., 11., 13., -> > 13., 10., 9., 10., 10., 9., 9., 13., 10., 9., 10., -> > 11., 13., 10., 10., 12.]) -> > ~~~ -> > {: .language-python} -> > -> {: .solution} -{: .challenge} - -{% include links.md %} diff --git a/_episodes/03-matplotlib.md b/_episodes/03-matplotlib.md deleted file mode 100644 index 55c56c1..0000000 --- a/_episodes/03-matplotlib.md +++ /dev/null @@ -1,288 +0,0 @@ ---- -title: Visualizing Tabular Data -teaching: 35 -exercises: 20 -questions: -- "How can I visualize tabular data in Python?" -- "How can I group several plots together?" -objectives: -- "Plot simple graphs from data." -- "Plot multiple graphs in a single figure." -keypoints: -- "Use the `pyplot` module from the `matplotlib` library for creating simple visualizations." ---- - -## Visualizing data -The mathematician Richard Hamming once said, "The purpose of computing is insight, not numbers," and -the best way to develop insight is often to visualize data. Visualization deserves an entire -lecture of its own, but we can explore a few features of Python's `matplotlib` library here. While -there is no official plotting library, `matplotlib` is the _de facto_ standard. First, we will -import the `pyplot` module from `matplotlib` and use two of its functions to create and display a -[heat map]({{ page.root }}/reference.html#heat-map) of our data: - -~~~ -import matplotlib.pyplot -image = matplotlib.pyplot.imshow(data) -matplotlib.pyplot.show() -~~~ -{: .language-python} - -![Heat map representing the `data` variable. Each cell is colored by value along a color gradient -from blue to yellow.](../fig/inflammation-01-imshow.svg) - -Blue pixels in this heat map represent low values, while yellow pixels represent high values. As we -can see, inflammation rises and falls over a 40-day period. Let's take a look at the average -inflammation over time: - -~~~ -ave_inflammation = numpy.mean(data, axis=0) -ave_plot = matplotlib.pyplot.plot(ave_inflammation) -matplotlib.pyplot.show() -~~~ -{: .language-python} - -![A line graph showing the average inflammation across all patients over a 40-day period.](../fig/inflammation-01-average.svg) - -Here, we have put the average inflammation per day across all patients in the variable -`ave_inflammation`, then asked `matplotlib.pyplot` to create and display a line graph of those -values. The result is a roughly linear rise and fall, which is suspicious: we might instead expect -a sharper rise and slower fall. Let's have a look at two other statistics: - -~~~ -max_plot = matplotlib.pyplot.plot(numpy.max(data, axis=0)) -matplotlib.pyplot.show() -~~~ -{: .language-python} - -![A line graph showing the maximum inflammation across all patients over a 40-day period.](../fig/inflammation-01-maximum.svg) - -~~~ -min_plot = matplotlib.pyplot.plot(numpy.min(data, axis=0)) -matplotlib.pyplot.show() -~~~ -{: .language-python} - -![A line graph showing the minimum inflammation across all patients over a 40-day period.](../fig/inflammation-01-minimum.svg) - -The maximum value rises and falls smoothly, while the minimum seems to be a step function. Neither -trend seems particularly likely, so either there's a mistake in our calculations or something is -wrong with our data. This insight would have been difficult to reach by examining the numbers -themselves without visualization tools. - -### Grouping plots -You can group similar plots in a single figure using subplots. -This script below uses a number of new commands. The function `matplotlib.pyplot.figure()` -creates a space into which we will place all of our plots. The parameter `figsize` -tells Python how big to make this space. Each subplot is placed into the figure using -its `add_subplot` [method]({{ page.root }}/reference.html#method). The `add_subplot` method takes 3 -parameters. The first denotes how many total rows of subplots there are, the second parameter -refers to the total number of subplot columns, and the final parameter denotes which subplot -your variable is referencing (left-to-right, top-to-bottom). Each subplot is stored in a -different variable (`axes1`, `axes2`, `axes3`). Once a subplot is created, the axes can -be titled using the `set_xlabel()` command (or `set_ylabel()`). -Here are our three plots side by side: - -~~~ -import numpy -import matplotlib.pyplot - -data = numpy.loadtxt(fname='inflammation-01.csv', delimiter=',') - -fig = matplotlib.pyplot.figure(figsize=(10.0, 3.0)) - -axes1 = fig.add_subplot(1, 3, 1) -axes2 = fig.add_subplot(1, 3, 2) -axes3 = fig.add_subplot(1, 3, 3) - -axes1.set_ylabel('average') -axes1.plot(numpy.mean(data, axis=0)) - -axes2.set_ylabel('max') -axes2.plot(numpy.max(data, axis=0)) - -axes3.set_ylabel('min') -axes3.plot(numpy.min(data, axis=0)) - -fig.tight_layout() - -matplotlib.pyplot.savefig('inflammation.png') -matplotlib.pyplot.show() -~~~ -{: .language-python} - -![Three line graphs showing the daily average, maximum and minimum inflammation over a 40-day period.](../fig/inflammation-01-group-plot.svg) - -The [call]({{ page.root }}/reference.html#function-call) to `loadtxt` reads our data, -and the rest of the program tells the plotting library -how large we want the figure to be, -that we're creating three subplots, -what to draw for each one, -and that we want a tight layout. -(If we leave out that call to `fig.tight_layout()`, -the graphs will actually be squeezed together more closely.) - -The call to `savefig` stores the plot as a graphics file. This can be -a convenient way to store your plots for use in other documents, web -pages etc. The graphics format is automatically determined by -Matplotlib from the file name ending we specify; here PNG from -'inflammation.png'. Matplotlib supports many different graphics -formats, including SVG, PDF, and JPEG. - -> ## Importing libraries with shortcuts -> -> In this lesson we use the `import matplotlib.pyplot` -> [syntax]({{ page.root }}/reference.html#syntax) -> to import the `pyplot` module of `matplotlib`. However, shortcuts such as -> `import matplotlib.pyplot as plt` are frequently used. -> Importing `pyplot` this way means that after the initial import, rather than writing -> `matplotlib.pyplot.plot(...)`, you can now write `plt.plot(...)`. -> Another common convention is to use the shortcut `import numpy as np` when importing the -> NumPy library. We then can write `np.loadtxt(...)` instead of `numpy.loadtxt(...)`, -> for example. -> -> Some people prefer these shortcuts as it is quicker to type and results in shorter -> lines of code - especially for libraries with long names! You will frequently see -> Python code online using a `pyplot` function with `plt`, or a NumPy function with -> `np`, and it's because they've used this shortcut. It makes no difference which -> approach you choose to take, but you must be consistent as if you use -> `import matplotlib.pyplot as plt` then `matplotlib.pyplot.plot(...)` will not work, and -> you must use `plt.plot(...)` instead. Because of this, when working with other people it -> is important you agree on how libraries are imported. -{: .callout} - -> ## Plot Scaling -> -> Why do all of our plots stop just short of the upper end of our graph? -> -> > ## Solution -> > Because matplotlib normally sets x and y axes limits to the min and max of our data -> > (depending on data range) -> {: .solution} -> -> If we want to change this, we can use the `set_ylim(min, max)` method of each 'axes', -> for example: -> -> ~~~ -> axes3.set_ylim(0,6) -> ~~~ -> {: .language-python} -> -> Update your plotting code to automatically set a more appropriate scale. -> (Hint: you can make use of the `max` and `min` methods to help.) -> -> > ## Solution -> > ~~~ -> > # One method -> > axes3.set_ylabel('min') -> > axes3.plot(numpy.min(data, axis=0)) -> > axes3.set_ylim(0,6) -> > ~~~ -> > {: .language-python} -> {: .solution} -> -> > ## Solution -> > ~~~ -> > # A more automated approach -> > min_data = numpy.min(data, axis=0) -> > axes3.set_ylabel('min') -> > axes3.plot(min_data) -> > axes3.set_ylim(numpy.min(min_data), numpy.max(min_data) * 1.1) -> > ~~~ -> > {: .language-python} -> {: .solution} -{: .challenge} - -> ## Drawing Straight Lines -> -> In the center and right subplots above, we expect all lines to look like step functions because -> non-integer value are not realistic for the minimum and maximum values. However, you can see -> that the lines are not always vertical or horizontal, and in particular the step function -> in the subplot on the right looks slanted. Why is this? -> -> > ## Solution -> > Because matplotlib interpolates (draws a straight line) between the points. -> > One way to do avoid this is to use the Matplotlib `drawstyle` option: -> > -> > ~~~ -> > import numpy -> > import matplotlib.pyplot -> > -> > data = numpy.loadtxt(fname='inflammation-01.csv', delimiter=',') -> > -> > fig = matplotlib.pyplot.figure(figsize=(10.0, 3.0)) -> > -> > axes1 = fig.add_subplot(1, 3, 1) -> > axes2 = fig.add_subplot(1, 3, 2) -> > axes3 = fig.add_subplot(1, 3, 3) -> > -> > axes1.set_ylabel('average') -> > axes1.plot(numpy.mean(data, axis=0), drawstyle='steps-mid') -> > -> > axes2.set_ylabel('max') -> > axes2.plot(numpy.max(data, axis=0), drawstyle='steps-mid') -> > -> > axes3.set_ylabel('min') -> > axes3.plot(numpy.min(data, axis=0), drawstyle='steps-mid') -> > -> > fig.tight_layout() -> > -> > matplotlib.pyplot.show() -> > ~~~ -> > {: .language-python} -> ![Three line graphs, with step lines connecting the points, showing the daily average, maximum - and minimum inflammation over a 40-day period.](../fig/inflammation-01-line-styles.svg) -> {: .solution} -{: .challenge} - -> ## Make Your Own Plot -> -> Create a plot showing the standard deviation (`numpy.std`) -> of the inflammation data for each day across all patients. -> -> > ## Solution -> > ~~~ -> > std_plot = matplotlib.pyplot.plot(numpy.std(data, axis=0)) -> > matplotlib.pyplot.show() -> > ~~~ -> > {: .language-python} -> {: .solution} -{: .challenge} - -> ## Moving Plots Around -> -> Modify the program to display the three plots on top of one another -> instead of side by side. -> -> > ## Solution -> > ~~~ -> > import numpy -> > import matplotlib.pyplot -> > -> > data = numpy.loadtxt(fname='inflammation-01.csv', delimiter=',') -> > -> > # change figsize (swap width and height) -> > fig = matplotlib.pyplot.figure(figsize=(3.0, 10.0)) -> > -> > # change add_subplot (swap first two parameters) -> > axes1 = fig.add_subplot(3, 1, 1) -> > axes2 = fig.add_subplot(3, 1, 2) -> > axes3 = fig.add_subplot(3, 1, 3) -> > -> > axes1.set_ylabel('average') -> > axes1.plot(numpy.mean(data, axis=0)) -> > -> > axes2.set_ylabel('max') -> > axes2.plot(numpy.max(data, axis=0)) -> > -> > axes3.set_ylabel('min') -> > axes3.plot(numpy.min(data, axis=0)) -> > -> > fig.tight_layout() -> > -> > matplotlib.pyplot.show() -> > ~~~ -> > {: .language-python} -> {: .solution} -{: .challenge} - -{% include links.md %} diff --git a/_episodes/04-lists.md b/_episodes/04-lists.md deleted file mode 100644 index 6df7fbe..0000000 --- a/_episodes/04-lists.md +++ /dev/null @@ -1,531 +0,0 @@ ---- -title: Storing Multiple Values in Lists -teaching: 35 -exercises: 15 -questions: -- "How can I store many values together?" -objectives: -- "Explain what a list is." -- "Create and index lists of simple values." -- "Change the values of individual elements" -- "Append values to an existing list" -- "Reorder and slice list elements" -- "Create and manipulate nested lists" -keypoints: -- "`[value1, value2, value3, ...]` creates a list." -- "Lists can contain any Python object, including lists (i.e., list of lists)." -- "Lists are indexed and sliced with square brackets (e.g., list[0] and -list[2:9]), in the same way as strings and arrays." -- "Lists are mutable (i.e., their values can be changed in place)." -- "Strings are immutable (i.e., the characters in them cannot be changed)." ---- - -In the previous episode, we analyzed a single file with inflammation data. Our goal, however, is to -process all the inflammation data we have, which means that we still have eleven more files to go! - -The natural first step is to collect the names of all the files that we have to process. In Python, -a list is a way to store multiple values together. In this episode, we will learn how to store -multiple values in a list as well as how to work with lists. - -## Python lists - -Unlike NumPy arrays, lists are built into the language so we do not have to load a library -to use them. -We create a list by putting values inside square brackets and separating the values with commas: - -~~~ -odds = [1, 3, 5, 7] -print('odds are:', odds) -~~~ -{: .language-python} - -~~~ -odds are: [1, 3, 5, 7] -~~~ -{: .output} - -We can access elements of a list using indices -- numbered positions of elements in the list. -These positions are numbered starting at 0, so the first element has an index of 0. - -~~~ -print('first element:', odds[0]) -print('last element:', odds[3]) -print('"-1" element:', odds[-1]) -~~~ -{: .language-python} - -~~~ -first element: 1 -last element: 7 -"-1" element: 7 -~~~ -{: .output} - -Yes, we can use negative numbers as indices in Python. When we do so, the index `-1` gives us the -last element in the list, `-2` the second to last, and so on. -Because of this, `odds[3]` and `odds[-1]` point to the same element here. - -There is one important difference between lists and strings: -we can change the values in a list, -but we cannot change individual characters in a string. -For example: - -~~~ -names = ['Curie', 'Darwing', 'Turing'] # typo in Darwin's name -print('names is originally:', names) -names[1] = 'Darwin' # correct the name -print('final value of names:', names) -~~~ -{: .language-python} - -~~~ -names is originally: ['Curie', 'Darwing', 'Turing'] -final value of names: ['Curie', 'Darwin', 'Turing'] -~~~ -{: .output} - -works, but: - -~~~ -name = 'Darwin' -name[0] = 'd' -~~~ -{: .language-python} - -~~~ ---------------------------------------------------------------------------- -TypeError Traceback (most recent call last) - in () - 1 name = 'Darwin' -----> 2 name[0] = 'd' - -TypeError: 'str' object does not support item assignment -~~~ -{: .error} - -does not. - -> ## Ch-Ch-Ch-Ch-Changes -> -> Data which can be modified in place is called [mutable]({{ page.root }}/reference.html#mutable), -> while data which cannot be modified is called -> [immutable]({{ page.root }}/reference.html#immutable). -> Strings and numbers are immutable. This does not mean that variables with string or number values -> are constants, but when we want to change the value of a string or number variable, we can only -> replace the old value with a completely new value. -> -> Lists and arrays, on the other hand, are mutable: we can modify them after they have been -> created. We can change individual elements, append new elements, or reorder the whole list. For -> some operations, like sorting, we can choose whether to use a function that modifies the data -> in-place or a function that returns a modified copy and leaves the original unchanged. -> -> Be careful when modifying data in-place. If two variables refer to the same list, and you modify -> the list value, it will change for both variables! -> -> ~~~ -> salsa = ['peppers', 'onions', 'cilantro', 'tomatoes'] -> my_salsa = salsa # <-- my_salsa and salsa point to the *same* list data in memory -> salsa[0] = 'hot peppers' -> print('Ingredients in my salsa:', my_salsa) -> ~~~ -> {: .language-python} -> -> ~~~ -> Ingredients in my salsa: ['hot peppers', 'onions', 'cilantro', 'tomatoes'] -> ~~~ -> {: .output} -> -> If you want variables with mutable values to be independent, you -> must make a copy of the value when you assign it. -> -> ~~~ -> salsa = ['peppers', 'onions', 'cilantro', 'tomatoes'] -> my_salsa = list(salsa) # <-- makes a *copy* of the list -> salsa[0] = 'hot peppers' -> print('Ingredients in my salsa:', my_salsa) -> ~~~ -> {: .language-python} -> -> ~~~ -> Ingredients in my salsa: ['peppers', 'onions', 'cilantro', 'tomatoes'] -> ~~~ -> {: .output} -> -> Because of pitfalls like this, code which modifies data in place can be more difficult to -> understand. However, it is often far more efficient to modify a large data structure in place -> than to create a modified copy for every small change. You should consider both of these aspects -> when writing your code. -{: .callout} - -> ## Nested Lists -> Since a list can contain any Python variables, it can even contain other lists. -> -> For example, we could represent the products in the shelves of a small grocery shop: -> -> ~~~ -> x = [['pepper', 'zucchini', 'onion'], -> ['cabbage', 'lettuce', 'garlic'], -> ['apple', 'pear', 'banana']] -> ~~~ -> {: .language-python} -> -> Here is a visual example of how indexing a list of lists `x` works: -> -> [![x is represented as a pepper shaker containing several packets of pepper. [x[0]] is represented -> as a pepper shaker containing a single packet of pepper. x[0] is represented as a single packet of -> pepper. x[0][0] is represented as single grain of pepper. Adapted -> from @hadleywickham.](../fig/indexing_lists_python.png)][hadleywickham-tweet] -> -> Using the previously declared list `x`, these would be the results of the -> index operations shown in the image: -> -> ~~~ -> print([x[0]]) -> ~~~ -> {: .language-python} -> -> ~~~ -> [['pepper', 'zucchini', 'onion']] -> ~~~ -> {: .output} -> -> ~~~ -> print(x[0]) -> ~~~ -> {: .language-python} -> -> ~~~ -> ['pepper', 'zucchini', 'onion'] -> ~~~ -> {: .output} -> -> ~~~ -> print(x[0][0]) -> ~~~ -> {: .language-python} -> -> ~~~ -> 'pepper' -> ~~~ -> {: .output} -> -> Thanks to [Hadley Wickham][hadleywickham-tweet] -> for the image above. -{: .callout} - -> ## Heterogeneous Lists -> Lists in Python can contain elements of different types. Example: -> ~~~ -> sample_ages = [10, 12.5, 'Unknown'] -> ~~~ -> {: .language-python} -{: .callout} - -There are many ways to change the contents of lists besides assigning new values to -individual elements: - -~~~ -odds.append(11) -print('odds after adding a value:', odds) -~~~ -{: .language-python} - -~~~ -odds after adding a value: [1, 3, 5, 7, 11] -~~~ -{: .output} - -~~~ -removed_element = odds.pop(0) -print('odds after removing the first element:', odds) -print('removed_element:', removed_element) -~~~ -{: .language-python} - -~~~ -odds after removing the first element: [3, 5, 7, 11] -removed_element: 1 -~~~ -{: .output} - -~~~ -odds.reverse() -print('odds after reversing:', odds) -~~~ -{: .language-python} - -~~~ -odds after reversing: [11, 7, 5, 3] -~~~ -{: .output} - -While modifying in place, it is useful to remember that Python treats lists in a slightly -counter-intuitive way. - -As we saw earlier, when we modified the `salsa` list item in-place, if we make a list, (attempt to) -copy it and then modify this list, we can cause all sorts of trouble. This also applies to modifying -the list using the above functions: - -~~~ -odds = [1, 3, 5, 7] -primes = odds -primes.append(2) -print('primes:', primes) -print('odds:', odds) -~~~ -{: .language-python} - -~~~ -primes: [1, 3, 5, 7, 2] -odds: [1, 3, 5, 7, 2] -~~~ -{: .output} - -This is because Python stores a list in memory, and then can use multiple names to refer to the -same list. If all we want to do is copy a (simple) list, we can again use the `list` function, so we -do not modify a list we did not mean to: - -~~~ -odds = [1, 3, 5, 7] -primes = list(odds) -primes.append(2) -print('primes:', primes) -print('odds:', odds) -~~~ -{: .language-python} - -~~~ -primes: [1, 3, 5, 7, 2] -odds: [1, 3, 5, 7] -~~~ -{: .output} - -Subsets of lists and strings can be accessed by specifying ranges of values in brackets, -similar to how we accessed ranges of positions in a NumPy array. -This is commonly referred to as "slicing" the list/string. - -~~~ -binomial_name = 'Drosophila melanogaster' -group = binomial_name[0:10] -print('group:', group) - -species = binomial_name[11:23] -print('species:', species) - -chromosomes = ['X', 'Y', '2', '3', '4'] -autosomes = chromosomes[2:5] -print('autosomes:', autosomes) - -last = chromosomes[-1] -print('last:', last) -~~~ -{: .language-python} - -~~~ -group: Drosophila -species: melanogaster -autosomes: ['2', '3', '4'] -last: 4 -~~~ -{: .output} - -> ## Slicing From the End -> -> Use slicing to access only the last four characters of a string or entries of a list. -> -> ~~~ -> string_for_slicing = 'Observation date: 02-Feb-2013' -> list_for_slicing = [['fluorine', 'F'], -> ['chlorine', 'Cl'], -> ['bromine', 'Br'], -> ['iodine', 'I'], -> ['astatine', 'At']] -> ~~~ -> {: .language-python} -> -> ~~~ -> '2013' -> [['chlorine', 'Cl'], ['bromine', 'Br'], ['iodine', 'I'], ['astatine', 'At']] -> ~~~ -> {: .output} -> -> Would your solution work regardless of whether you knew beforehand -> the length of the string or list -> (e.g. if you wanted to apply the solution to a set of lists of different lengths)? -> If not, try to change your approach to make it more robust. -> -> Hint: Remember that indices can be negative as well as positive -> -> > ## Solution -> > Use negative indices to count elements from the end of a container (such as list or string): -> > -> > ~~~ -> > string_for_slicing[-4:] -> > list_for_slicing[-4:] -> > ~~~ -> > {: .language-python} -> {: .solution} -{: .challenge} - -> ## Non-Continuous Slices -> -> So far we've seen how to use slicing to take single blocks -> of successive entries from a sequence. -> But what if we want to take a subset of entries -> that aren't next to each other in the sequence? -> -> You can achieve this by providing a third argument -> to the range within the brackets, called the _step size_. -> The example below shows how you can take every third entry in a list: -> -> ~~~ -> primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37] -> subset = primes[0:12:3] -> print('subset', subset) -> ~~~ -> {: .language-python} -> -> ~~~ -> subset [2, 7, 17, 29] -> ~~~ -> {: .output} -> -> Notice that the slice taken begins with the first entry in the range, -> followed by entries taken at equally-spaced intervals (the steps) thereafter. -> If you wanted to begin the subset with the third entry, -> you would need to specify that as the starting point of the sliced range: -> -> ~~~ -> primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37] -> subset = primes[2:12:3] -> print('subset', subset) -> ~~~ -> {: .language-python} -> -> ~~~ -> subset [5, 13, 23, 37] -> ~~~ -> {: .output} -> -> Use the step size argument to create a new string -> that contains only every other character in the string -> "In an octopus's garden in the shade". Start with -> creating a variable to hold the string: -> -> ~~~ -> beatles = "In an octopus's garden in the shade" -> ~~~ -> {: .language-python} -> -> What slice of `beatles` will produce the -> following output (i.e., the first character, third -> character, and every other character through the end -> of the string)? -> ~~~ -> I notpssgre ntesae -> ~~~ -> {: .output} -> -> > ## Solution -> > To obtain every other character you need to provide a slice with the step -> > size of 2: -> > -> > ~~~ -> > beatles[0:35:2] -> > ~~~ -> > {: .language-python} -> > -> > You can also leave out the beginning and end of the slice to take the whole string -> > and provide only the step argument to go every second -> > element: -> > -> > ~~~ -> > beatles[::2] -> > ~~~ -> > {: .language-python} -> {: .solution} -{: .challenge} - -If you want to take a slice from the beginning of a sequence, you can omit the first index in the -range: - -~~~ -date = 'Monday 4 January 2016' -day = date[0:6] -print('Using 0 to begin range:', day) -day = date[:6] -print('Omitting beginning index:', day) -~~~ -{: .language-python} - -~~~ -Using 0 to begin range: Monday -Omitting beginning index: Monday -~~~ -{: .output} - -And similarly, you can omit the ending index in the range to take a slice to the very end of the -sequence: - -~~~ -months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'] -sond = months[8:12] -print('With known last position:', sond) -sond = months[8:len(months)] -print('Using len() to get last entry:', sond) -sond = months[8:] -print('Omitting ending index:', sond) -~~~ -{: .language-python} - -~~~ -With known last position: ['sep', 'oct', 'nov', 'dec'] -Using len() to get last entry: ['sep', 'oct', 'nov', 'dec'] -Omitting ending index: ['sep', 'oct', 'nov', 'dec'] -~~~ -{: .output} - -> ## Overloading -> -> `+` usually means addition, but when used on strings or lists, it means "concatenate". -> Given that, what do you think the multiplication operator `*` does on lists? -> In particular, what will be the output of the following code? -> -> ~~~ -> counts = [2, 4, 6, 8, 10] -> repeats = counts * 2 -> print(repeats) -> ~~~ -> {: .language-python} -> -> 1. `[2, 4, 6, 8, 10, 2, 4, 6, 8, 10]` -> 2. `[4, 8, 12, 16, 20]` -> 3. `[[2, 4, 6, 8, 10],[2, 4, 6, 8, 10]]` -> 4. `[2, 4, 6, 8, 10, 4, 8, 12, 16, 20]` -> -> The technical term for this is *operator overloading*: -> a single operator, like `+` or `*`, -> can do different things depending on what it's applied to. -> -> > ## Solution -> > -> > The multiplication operator `*` used on a list replicates elements of the list and concatenates -> > them together: -> > -> > ~~~ -> > [2, 4, 6, 8, 10, 2, 4, 6, 8, 10] -> > ~~~ -> > {: .output} -> > -> > It's equivalent to: -> > -> > ~~~ -> > counts + counts -> > ~~~ -> > {: .language-python} -> {: .solution} -{: .challenge} - -[hadleywickham-tweet]: https://twitter.com/hadleywickham/status/643381054758363136 - -{% include links.md %} diff --git a/_episodes/05-code-migration-01.md b/_episodes/05-code-migration-01.md deleted file mode 100644 index 974127f..0000000 --- a/_episodes/05-code-migration-01.md +++ /dev/null @@ -1,532 +0,0 @@ ---- -title: Code migration to HPC systems -teaching: 40 -exercises: 0 -questions: -- "I would like to try Hawk, can I keep working with Jupyter Notebooks? - (yes, but ...)" -- "How to access Jupyter Notebooks on Hawk?" -- "How to transition from interactive Jupyter Notebooks to automated Python scripts?" -objectives: -- "Learn how to set up your work environment (transfer files, install libraries)." -- "Understand the differences and trade-offs between Jupyter Notebooks and - Python scripts." -keypoints: -- "It is possible to use Jupyter Notebooks on Hawk via OnDemand and ssh tunnels." -- "The recommended method to setup your work environment (installing libraries) - is using Anaconda virtual environments." -- "Include the library ipykernel to make the environment reachable from Jupyter - Notebooks" -- "Use Jupyter Notebooks in the early (development) stages of your project when - lots of debugging are necessary. Move towards automated Python scripts in later - stages and submit them via SLURM job scripts." - ---- - -## Running Jupyter Notebooks on a remote HPC systems - -Although the most traditional way to interact with remote HPC and cloud systems -is through the command line (via the `ssh` and `scp` commands) some systems also -offer graphical user interfaces for some services. Specifically, on Hawk you can -deploy -Jupyter Notebooks via [OnDemand](https://openondemand.org/) (a web portal that -allow you to work with HPC systems interactively). The notes below provide -instructions for both methods of access: through OnDemand and through a `ssh` -*tunnel*. - -{::options parse_block_html="true" /} -
- - -
-
- - To access a Jupyter Notebook server via an `ssh` tunnel you need first to - login to Hawk: - - ``` - $ ssh hawk-username@hawklogin.cf.ac.uk - ``` - - Once logged in, confirm that Python 3 is accessible: - - ``` - $ module load compiler/gnu/9 - $ module load python/3.7.0 - $ python3 --version - ``` - ~~~ - Python 3.7.0 - ~~~ - {: .output} - - We need to install Jupyter Notebooks on our user account on the remote server - (we will discuss more about installing Python packages later on): - - ``` - $ python3 -m venv create my_venv - $ . my_venv/bin/activate - $ python3 -m pip install jupyterlab - ``` - - The installation process will try to download several dependencies from the - internet. Be patient, it shouldn't take more than a couple of minutes. - - Now, this is important, the Jupyter Notebook server must be run on a *compute - node*, please take a look at our best practices guidelines in the - [SCW portal](https://portal.supercomputing.wales/index.php/best-practice/). - If the concept of login and compute nodes is still not clear at this point - don't worry too much (but you can find out more in out - [Supercomputing for Beginners training course](https://arcca.github.io/hpc-intro/). - For now, run the following command to instruct the Hawk job scheduler to run - a Jupyter Notebook server on a compute node: - - ``` - $ srun -n 1 -p htc --account=scwXXXX -t 1:00:00 jupyter-lab --ip=0.0.0.0 - ``` - - ~~~ - http://ccs1015:8888/?token=77777add13ab93a0c408c287a630249c2dba93efdd3fae06 - or http://127.0.0.1:8888/?token=77777add13ab93a0c408c287a630249c2dba93efdd3fae06 - ~~~ - {: .output} - - Next, open a new terminal and create a ssh tunnel using the node and port - obtained in the previous step (e.g. ccs1015:8888): - - ``` - $ ssh -L8888:ccs1015:8888 hawk-username@hawklogin.cf.ac.uk - ``` - - You should be able to navigate to http://localhost:8888 in your web browser - (use the token provided in the output if needed). If everything went well, you - should see something like: - - Jupyter Lab Home - - Where you should be able to access the files stored your Hawk user account. - - -
- -
- 1. Go to [ARCCA OnDemand](https://arcondemand.cardiff.ac.uk) portal (this - requires access to [Cardiff University VPN](https://intranet.cardiff.ac.uk/staff/supporting-your-work/it-support/wireless-and-remote-access/off-campus-access/virtual-private-network-vpn) ). - 2. Enter your details: Hawk username and password. Once logged in you should - land on a page with useful information including the usual Message of the - Day (MOD) commonly seen when logging in to Hawk via the terminal. - - | | | - |:--------:|:--------:| - | ARCCA OnDemand login page | ARCCA landing page | - | | | - - 3. Go to "Interactive Apps" in the top menu and select "Jupyter Notebook/Lab". - This will bring you to a form where you can specify for how much time the - desktop is required, number of CPUs, partition, etc. You can also choose - to receive an email once the desktop is ready for you. Click the *Launch* - button to submit the request. - - | | | - |:--------:|:--------:| - | ARCCA OnDemand login page | OnDemand JN requirements | - | | | - - 4. After submission you request will be placed on the queue and will wait - for resources, hopefully for a short period, but this *depends on the - number of cores as well as time requested*, so please be patient. At this - point you can close the OnDemand website and come back at a later point - to check progress or wait for the email notification if the option was - selected. - - Once your request is granted you should be able to see a *Running* message, - the amount of resources granted and the time remaining. - - Click *Connect to Jupyter* to launch the Jupyter in a new web browser tab. - - | | | - |:--------:|:--------:| - | OnDemand JN queued | OnDemand JN running | - | | | - - 5. You should now have the familiar interface of Jupyter Notebooks in front of - you. It will show the documents and directories in your user account on - Hawk. To create a new Notebook, go to the dropdown menu *New* on the right - side and click on *Python 3 (ipykernel)*. A new tab will open with a new - notebook ready for you to start working. - - | | | - |:--------:|:--------:| - | OnDemand JN main | OnDemand JN new notebook | - | | | - -
-
-
- -{% include links.md %} - - -## Copying data - -To keep working on Hawk with the Notebooks we have written locally in our -Desktop computer we need to transfer them over. Depending on our platform we -can do this in a couple of ways: - -{::options parse_block_html="true" /} -
- - -
-
- On Windows you can use [MobaXterm](https://mobaxterm.mobatek.net/) to - transfer files to Hawk from your local computer. - - | | | - |:----------------:|:----------------:| - |
Open SCP session on MobaXterm

Click on **Session** to open the different connection methods available in MobaXterm | Enter details to start SPC session on MobaXterm
Select **SFTP** and enter the Remote Host (*hawklogin.cf.ac.uk*) and your **Hawk username** | - | | | - |
Open SCP session on MobaXterm

Locate the directory in your local computer and drag and drop to the remote server on the right pane. || - -
- -
- MacOS and Linux provide the command scp -r that can be used to recursively - copy your work directory over to your home directory in Hawk: - - ``` - $ scp -r arcca-python hawk-username@hawklogin.cf.ac.uk:/home/hawk-username - python-novice-inflammation-code.zip 100% 7216 193.0KB/s 00:00 - Untitled.ipynb 100% 67KB 880.2KB/s 00:00 - inflammation.png 100% 13KB 315.6KB/s 00:00 - argv_list.py 100% 42 0.4KB/s 00:00 - readings_08.py 100% 1097 10.6KB/s 00:00 - readings_09.py 100% 851 24.8KB/s 00:00 - ``` - -
- -
- With OnDemand you can also download and upload files to Hawk. In this - example we will upload the directory with the Jupyter Notebooks we have - created so far. Go to `Files` and select the directory where you wish to - upload the files (our home directory in this case), then select `Upload` - and locate the directory in your local computer. Once uploaded, the files - should be available on Hawk: - - | | | - |:----------------:|:----------------:| - | Go to Files and select directory where to upload. | Click Upload | - | Locate files in your local computer. | Locate files in your local computer. | - -
- -
-
- -## Setup your Jupyter Notebook work environment - -Depending on how you started your Jupyter Notebook you should have access to -some default packages. But these are not guaranteed to be the same (this also -applies for the version of Python) between the OnDemand and the `ssh` tunnel -methods. Moreover, it is unlikely that the remote HPC system would provide -every package you need by default. - -### Installing Python libraries - -The **recommended approach** is to create a conda virtual environment with a -`environemnt.yml` file which includes a list of all packages (and versions) -needed for your work. This file can be created and used in your local computer -and then copied to Hawk to reproduce the same environment. An example file is: - -~~~ -name: my-conda-env -dependencies: - - python=3.9.2 - - numpy - - pandas - - ipykernel -~~~ -{: .language-yaml} - -The package `ipykernel` is required here to make the environment reachable from -Jupyter Notebooks. You can find more about creating an `environment.yml` file -in the [Anaconda documentation](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#create-env-file-manually). - -On Hawk you need to first load Anaconda: - -``` -$ module load anaconda/2020.02 -$ source activate -$ which conda -``` - -~~~ -/apps/languages/anaconda/2020.02/bin/conda -~~~ -{: .output} - -And then proceed to install the virtual environment: - -``` -$ conda env create -f environment.yml -``` - -~~~ -... -libffi-3.3 | 50 KB | ##################################### | 100% -numpy-1.21.2 | 23 KB | ##################################### | 100% -pandas-1.3.4 | 9.6 MB | ##################################### | 100% -mkl-2021.4.0 | 142.6 MB | ##################################### | 100% -six-1.16.0 | 18 KB | ##################################### | 100% -Preparing transaction: done -Verifying transaction: done -Executing transaction: done -# -# To activate this environment, use -# -# $ conda activate my-conda-env -# -# To deactivate an active environment, use -# -# $ conda deactivate -~~~ -{: .output} - -We can then follow the instructions printed at the end of the installation -process to activate our environment: - -``` -$ conda activate my-conda-env -$ which python -``` - -~~~ -~/.conda/envs/my-conda-env/bin/python -~~~ -{: .output} - -We can further confirm the version of Python used by the environment: - -``` -$ python --version -``` - -~~~ -Python 3.9.2 -~~~ -{: .output} - -To deactivate the environment and return to the default Python provided by the -system (or the loaded module): - -``` -$ conda deactivate -$ python --version -``` - -~~~ -Python 3.7.6 -~~~ -{: .output} - -### Using an Anaconda virtual environment from Jupyter Notebooks - -We can access our newly installed Anaconda environment from Jupyter Notebooks -on OnDemand. For this, create a new session and when the resources are granted -click on `Connect to Jupyter`. On Jupyter Lab you might be asked to choose -which kernel to start, if so, select the name given to your virtual environment -(*my-conda-env*) in this example: - - - -If another kernel is loaded by default, you can still change it by clicking on -the top right corner of your Notebook, a similar menu should appear: - - | | | - |:----------------:|:----------------:| - | Change JN kernel manually. | Select JN kernel from menu | - -If all goes well you should be able to confirm the Python versions and path, as -well as the location of the installed libraries: - - - -At this point you should have all the packages required to continue working on -Hawk as if you were working on your local computer. - - -### A more efficient approach - -During these examples we have been requesting only 1 CPU when we launch our -Jupyter Notebook and that, hopefully, has caused our request to be fulfilled -fairly quickly. However, there will be a point where 1 CPU is no longer enough -(maybe the application has become more complex or there is more data to analyse, -and memory requirements have increased). At that point you can modify the -requirements and increase the number of CPUS, memory, time or devices (GPUs). -One point to keep in mind when increasing requirements is that this will impact -the time it takes for the system scheduler to deliver your request and allocate -you the resources, the higher the requirements, the longer it will take. - -When the time spent waiting in queue becomes excessive it is worth considering -moving away from the Jupyter Notebook workflow towards a more traditional -Python script approach (**recommended for HPC systems**). The main difference -between them is that while Jupyter Notebooks is ideal for the development -stages of a project (since you can test things out in real time and debug if -needed), the Python script approach is better suited for the production stages -where the needed for supervision and debugging is reduced. Python scripts also -have the advantage, on HPC systems, of being able to be queued for resources -and automatically executed when these are granted without you needing to be -logged in the system. - -So, how do we actually transfer our Jupyter Notebook to a Python script? -Fortunately, Jupyter Notebook developers thought of this requirement and added -a convenient export method to the Notebooks (the menus might be different -depending on if Jupyter Notebooks or Jupyter Lab was launched from OnDemand): - -| | | -|:----------------:|:----------------:| -| ![Download Notebook from JN as Py script](../fig/jupyter-notebook-download-as-python-script.png) | ![Download Notebook from JL as Py script](../fig/jupyter-lab-download-as-python-script.png) | -| Download from Jupyter Notebook | Download from Jupyter Lab | - -After choosing an appropriate name and saving the file, we should have a Python -script (a text file) with entries similar to: - -~~~ -#!/usr/bin/env python -# coding: utf-8 - -# In[1]: - - -3 + 5 * 8 - - -# In[2]: - - -weight_kg=60 - -~~~ -{: .language-python} - -Notice the `# In[X]:` that mark the position of corresponding cells in our -Jupyter Notebook and are kept for reference. We can keep them in place, they -won't cause any trouble as they are included as comments (due to the initial -`#`), but if we wanted to remove them we could do it by hand or more -efficiently by using the command line tool `sed` to find and delete lines that -start with the characters `# [` and to delete empty lines (`^` is used by `sed` -to indicate the beginning of a line and `$` to indicate the end): - -**(from this point onwards we move away from Jupyter Notebooks and start typing -commands on a terminal connected to Hawk)** - -``` -sed -e '/# In/d' -e '/^$/d' lesson1.py > lesson1_cleaned.py -``` - -This will produce the `lesson1_cleaned.py` file with entries similar to: - -~~~ -#!/usr/bin/env python -# coding: utf-8 -3+5+8 -weight_kg=60 -~~~ -{: .language-python} - -Now that we have our Python script, we need to create an additional file (job -script) to place it in the queue (submit the job). Make sure to remove any -commands from the Python script that might need additional confirmation or user -interaction as you won't be able to provide it with this method of execution. -The following is the content a job script that is equivalent to how we have -been requesting resources through OnDemand: - -~~~ -#!/bin/bash - -#SBATCH -J test # job name -#SBATCH -n 1 # number of tasks needed -#SBATCH -p htc # partition -#SBATCH --time=01:00:00 # time limit -#SBATCH -A scwXXXX # account number - -set -eu - -module purge -module load anaconda/2020.02 -module list - -# Load conda -source activate - -# Load our environment -conda activate my-conda-env - -which python -python --version - -python my-python-script.py - -~~~ -{: .language-bash} - -To submit (put it queue) the above script, on Hawk: - -``` -$ sbatch my-job-script.sh -``` - -~~~ -Submitted batch job 25859860 -~~~ -{: .output} - -You can query the current state of this job with: - -``` -$ squeue -u $USER -``` - -~~~ - JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON) - 25860025 htc test c.xxxxxxx PD 0:00 1 ccs3004 -~~~ -{: .output} - -This particular job might not spend a long time in queue and the above output -might not show it, but on completion there should be a `slurm-.out` -created in the current directory with the output produced by our script. - -There is a lot more to know about working with HTC systems and job schedulers, -once you are ready to go this route, take a look at our documentation and -training courses on these topics: - -- [**Supercomputing for Beginners**](https://arcca.github.io/hpc-intro): Why - use HPC? Accessing systems, using SLURM, loading software, file transfer and - optimising resources. -- [**Slurm: Advanced Topics**](https://arcca.github.io/slurm_advanced_topics): - Additional material to interface with HPC more effectively. - -> ## Need help? -> -> If during the above steps you found any issues or have doubts regarding your -> specific work environment, get in touch with us at arcca-help@cardiff.ac.uk. -{: .callout} - - - -{% include links.md %} diff --git a/_episodes_rmd/.gitkeep b/_episodes_rmd/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/_episodes_rmd/data/.gitkeep b/_episodes_rmd/data/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/_extras/about.md b/_extras/about.md deleted file mode 100644 index 5f07f65..0000000 --- a/_extras/about.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: About ---- -{% include carpentries.html %} -{% include links.md %} diff --git a/_extras/discuss.md b/_extras/discuss.md deleted file mode 100644 index bfc33c5..0000000 --- a/_extras/discuss.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Discussion ---- -FIXME - -{% include links.md %} diff --git a/_extras/figures.md b/_extras/figures.md deleted file mode 100644 index 0012c88..0000000 --- a/_extras/figures.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: Figures ---- - -{% include base_path.html %} -{% include manual_episode_order.html %} - - - -{% comment %} Create anchor for each one of the episodes. {% endcomment %} - -{% for lesson_episode in lesson_episodes %} - {% if site.episode_order %} - {% assign episode = site.episodes | where: "slug", lesson_episode | first %} - {% else %} - {% assign episode = lesson_episode %} - {% endif %} -
-{% endfor %} - -{% include links.md %} diff --git a/_extras/guide.md b/_extras/guide.md deleted file mode 100644 index 50f266f..0000000 --- a/_extras/guide.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: "Instructor Notes" ---- -FIXME - -{% include links.md %} diff --git a/code/.gitkeep b/code/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/code/python-novice-inflammation-code.zip b/code/python-novice-inflammation-code.zip deleted file mode 100644 index 2599dad..0000000 Binary files a/code/python-novice-inflammation-code.zip and /dev/null differ diff --git a/data/.gitkeep b/data/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/data/python-novice-inflammation-data.zip b/data/python-novice-inflammation-data.zip deleted file mode 100644 index 335e8a1..0000000 Binary files a/data/python-novice-inflammation-data.zip and /dev/null differ diff --git a/fig/.gitkeep b/fig/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/fig/MobaXterm-01-arrows.png b/fig/MobaXterm-01-arrows.png deleted file mode 100644 index c3004a4..0000000 Binary files a/fig/MobaXterm-01-arrows.png and /dev/null differ diff --git a/fig/anaconda-navigator-first-launch.png b/fig/anaconda-navigator-first-launch.png deleted file mode 100644 index 875fba4..0000000 Binary files a/fig/anaconda-navigator-first-launch.png and /dev/null differ diff --git a/fig/anaconda-navigator-notebook-launch.png b/fig/anaconda-navigator-notebook-launch.png deleted file mode 100644 index c131870..0000000 Binary files a/fig/anaconda-navigator-notebook-launch.png and /dev/null differ diff --git a/fig/indexing_lists_python.png b/fig/indexing_lists_python.png deleted file mode 100644 index 2636ca5..0000000 Binary files a/fig/indexing_lists_python.png and /dev/null differ diff --git a/fig/inflammation-01-average.svg b/fig/inflammation-01-average.svg deleted file mode 100644 index 7bc2fff..0000000 --- a/fig/inflammation-01-average.svg +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - 0 - - 5 - - 10 - - 15 - - 20 - - 25 - - 30 - - 35 - - 40 - - - - - 0 - - 2 - - 4 - - 6 - - 8 - - 10 - - 12 - - - - - - - - - - - diff --git a/fig/inflammation-01-group-plot.svg b/fig/inflammation-01-group-plot.svg deleted file mode 100644 index 7e9e995..0000000 --- a/fig/inflammation-01-group-plot.svg +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - 0 - - 10 - - 20 - - 30 - - 40 - - - - - 0 - - 2 - - 4 - - 6 - - 8 - - 10 - - 12 - average - - - - - - - - 0 - - 10 - - 20 - - 30 - - 40 - - 0 - - 5 - - 10 - - 15 - - 20 - max - - - - - - - - 0 - - 10 - - 20 - - 30 - - 40 - - 0 - - 1 - - 2 - - 3 - - 4 - - 5 - min - - - - - - - - - - - - - - - - - diff --git a/fig/inflammation-01-imshow.svg b/fig/inflammation-01-imshow.svg deleted file mode 100644 index 17c8138..0000000 --- a/fig/inflammation-01-imshow.svg +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - 0 - - 10 - - 20 - - 30 - - - - - 0 - - 10 - - 20 - - 30 - - 40 - - 50 - - - - - - - - - - diff --git a/fig/inflammation-01-line-styles.svg b/fig/inflammation-01-line-styles.svg deleted file mode 100644 index b78ba9c..0000000 --- a/fig/inflammation-01-line-styles.svg +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - 0 - - 10 - - 20 - - 30 - - 40 - - - - - 0 - - 2 - - 4 - - 6 - - 8 - - 10 - - 12 - average - - - - - - - - 0 - - 10 - - 20 - - 30 - - 40 - - 0 - - 5 - - 10 - - 15 - - 20 - max - - - - - - - - 0 - - 10 - - 20 - - 30 - - 40 - - 0 - - 1 - - 2 - - 3 - - 4 - - 5 - min - - - - - - - - - - - - - - - - - diff --git a/fig/inflammation-01-maximum.svg b/fig/inflammation-01-maximum.svg deleted file mode 100644 index d1d04b3..0000000 --- a/fig/inflammation-01-maximum.svg +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - 0 - - 5 - - 10 - - 15 - - 20 - - 25 - - 30 - - 35 - - 40 - - - - - 0.0 - - 2.5 - - 5.0 - - 7.5 - - 10.0 - - 12.5 - - 15.0 - - 17.5 - - 20.0 - - - - - - - - - - - diff --git a/fig/inflammation-01-minimum.svg b/fig/inflammation-01-minimum.svg deleted file mode 100644 index 9597f07..0000000 --- a/fig/inflammation-01-minimum.svg +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - 0 - - 5 - - 10 - - 15 - - 20 - - 25 - - 30 - - 35 - - 40 - - - - - 0 - - 1 - - 2 - - 3 - - 4 - - 5 - - - - - - - - - - - diff --git a/fig/jupyter-lab-download-as-python-script.png b/fig/jupyter-lab-download-as-python-script.png deleted file mode 100644 index f6c117b..0000000 Binary files a/fig/jupyter-lab-download-as-python-script.png and /dev/null differ diff --git a/fig/jupyter-lab-home.png b/fig/jupyter-lab-home.png deleted file mode 100644 index 85da1d2..0000000 Binary files a/fig/jupyter-lab-home.png and /dev/null differ diff --git a/fig/jupyter-notebook-data-directory.png b/fig/jupyter-notebook-data-directory.png deleted file mode 100644 index 3a51447..0000000 Binary files a/fig/jupyter-notebook-data-directory.png and /dev/null differ diff --git a/fig/jupyter-notebook-download-as-python-script.png b/fig/jupyter-notebook-download-as-python-script.png deleted file mode 100644 index 0e8971b..0000000 Binary files a/fig/jupyter-notebook-download-as-python-script.png and /dev/null differ diff --git a/fig/jupyter-notebook-launch-notebook.png b/fig/jupyter-notebook-launch-notebook.png deleted file mode 100644 index 53e5aa9..0000000 Binary files a/fig/jupyter-notebook-launch-notebook.png and /dev/null differ diff --git a/fig/jupyter-notebook-launch-notebook2.png b/fig/jupyter-notebook-launch-notebook2.png deleted file mode 100644 index ff2622e..0000000 Binary files a/fig/jupyter-notebook-launch-notebook2.png and /dev/null differ diff --git a/fig/lesson-overview.svg b/fig/lesson-overview.svg deleted file mode 100644 index 3019918..0000000 --- a/fig/lesson-overview.svg +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - Inflammation data - Analysis - Conclusion - - - - - - ? - - - - How does the - medication affect - patients? - - - - - - - Day 1 - Day 2 - Day 3 - Day 4 - Day 5 - Day 6 - Day 7 - Patients - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 0 - 0 - 1 - 3 - 1 - 2 - 4 - - - - - - - - - 0 - 1 - 2 - 1 - 2 - 1 - 3 - - - - - - - - - 0 - 1 - 1 - 3 - 3 - 2 - 6 - - - - - - - - - 0 - 0 - 2 - 0 - 4 - 2 - 2 - - - - - - - - - 0 - 1 - 1 - 3 - 3 - 1 - 3 - - - - - - - - - - - - - - - - - - - - - - - 0 - 10 - 20 - 30 - Day - - - - - - - - - - - - 0 - 10 - 20 - 30 - 40 - 50 - Patient - - Inflammation - Inflammation bouts - - - - - - - - - - - - - - - - - - - - - - - - 0 - 5 - 10 - 15 - 20 - - diff --git a/fig/mobaxterm-scp-copy-directory.png b/fig/mobaxterm-scp-copy-directory.png deleted file mode 100644 index daa5cb2..0000000 Binary files a/fig/mobaxterm-scp-copy-directory.png and /dev/null differ diff --git a/fig/mobaxterm-scp-setup.png b/fig/mobaxterm-scp-setup.png deleted file mode 100644 index 8435502..0000000 Binary files a/fig/mobaxterm-scp-setup.png and /dev/null differ diff --git a/fig/ondemand-jupyter-notebook-change-kernel-manually.png b/fig/ondemand-jupyter-notebook-change-kernel-manually.png deleted file mode 100644 index fb1d059..0000000 Binary files a/fig/ondemand-jupyter-notebook-change-kernel-manually.png and /dev/null differ diff --git a/fig/ondemand-jupyter-notebook-confirm-environment.png b/fig/ondemand-jupyter-notebook-confirm-environment.png deleted file mode 100644 index 97ae47e..0000000 Binary files a/fig/ondemand-jupyter-notebook-confirm-environment.png and /dev/null differ diff --git a/fig/ondemand-jupyter-notebook-main.png b/fig/ondemand-jupyter-notebook-main.png deleted file mode 100644 index c390682..0000000 Binary files a/fig/ondemand-jupyter-notebook-main.png and /dev/null differ diff --git a/fig/ondemand-jupyter-notebook-new-notebook.png b/fig/ondemand-jupyter-notebook-new-notebook.png deleted file mode 100644 index 2692481..0000000 Binary files a/fig/ondemand-jupyter-notebook-new-notebook.png and /dev/null differ diff --git a/fig/ondemand-jupyter-notebook-queued.png b/fig/ondemand-jupyter-notebook-queued.png deleted file mode 100644 index 326dbeb..0000000 Binary files a/fig/ondemand-jupyter-notebook-queued.png and /dev/null differ diff --git a/fig/ondemand-jupyter-notebook-requirements.png b/fig/ondemand-jupyter-notebook-requirements.png deleted file mode 100644 index 7e527ff..0000000 Binary files a/fig/ondemand-jupyter-notebook-requirements.png and /dev/null differ diff --git a/fig/ondemand-jupyter-notebook-running.png b/fig/ondemand-jupyter-notebook-running.png deleted file mode 100644 index 428d058..0000000 Binary files a/fig/ondemand-jupyter-notebook-running.png and /dev/null differ diff --git a/fig/ondemand-jupyter-notebook-select-kernel.png b/fig/ondemand-jupyter-notebook-select-kernel.png deleted file mode 100644 index e33a211..0000000 Binary files a/fig/ondemand-jupyter-notebook-select-kernel.png and /dev/null differ diff --git a/fig/ondemand-landing-page.png b/fig/ondemand-landing-page.png deleted file mode 100644 index 67882c9..0000000 Binary files a/fig/ondemand-landing-page.png and /dev/null differ diff --git a/fig/ondemand-login.png b/fig/ondemand-login.png deleted file mode 100644 index 5d10705..0000000 Binary files a/fig/ondemand-login.png and /dev/null differ diff --git a/fig/ondemand-select-jupyter-notebook-lab.png b/fig/ondemand-select-jupyter-notebook-lab.png deleted file mode 100644 index 5b7b3a6..0000000 Binary files a/fig/ondemand-select-jupyter-notebook-lab.png and /dev/null differ diff --git a/fig/ondemand-upload-files-click-upload.png b/fig/ondemand-upload-files-click-upload.png deleted file mode 100644 index 42764f0..0000000 Binary files a/fig/ondemand-upload-files-click-upload.png and /dev/null differ diff --git a/fig/ondemand-upload-files-locate-local-directory.png b/fig/ondemand-upload-files-locate-local-directory.png deleted file mode 100644 index aaddd81..0000000 Binary files a/fig/ondemand-upload-files-locate-local-directory.png and /dev/null differ diff --git a/fig/ondemand-upload-files-select-directory.png b/fig/ondemand-upload-files-select-directory.png deleted file mode 100644 index 856cd41..0000000 Binary files a/fig/ondemand-upload-files-select-directory.png and /dev/null differ diff --git a/fig/ondemand-upload-files-uploaded.png b/fig/ondemand-upload-files-uploaded.png deleted file mode 100644 index f2fdca4..0000000 Binary files a/fig/ondemand-upload-files-uploaded.png and /dev/null differ diff --git a/fig/python-operations-across-axes.png b/fig/python-operations-across-axes.png deleted file mode 100644 index 873a1af..0000000 Binary files a/fig/python-operations-across-axes.png and /dev/null differ diff --git a/fig/python-sticky-note-variables-01.svg b/fig/python-sticky-note-variables-01.svg deleted file mode 100644 index 5c76e30..0000000 --- a/fig/python-sticky-note-variables-01.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - 65.0 - - - weight_kg - diff --git a/fig/python-sticky-note-variables-02.svg b/fig/python-sticky-note-variables-02.svg deleted file mode 100644 index 7ebe2ce..0000000 --- a/fig/python-sticky-note-variables-02.svg +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - 65.0 - - - weight_kg - - - - - - - - - 143.0 - - - weight_lb - diff --git a/fig/python-sticky-note-variables-03.svg b/fig/python-sticky-note-variables-03.svg deleted file mode 100644 index b0650d3..0000000 --- a/fig/python-sticky-note-variables-03.svg +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - 100.0 - - - weight_kg - - - - - - - - - 143.0 - - - weight_lb - diff --git a/fig/python-zero-index.svg b/fig/python-zero-index.svg deleted file mode 100644 index 9ec8f82..0000000 --- a/fig/python-zero-index.svg +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - - - - - - - - 0 - 1 - 2 - - 0 - 1 - 2 - - - - - - - - - - - - - - - - data = [ - [A, B, C] - , - [D, E, F] - , - [G, H, I] - ] - - - - - data[ - 0 - , - 0 - ] = - A - - - - - data[ - 0 - , - 1 - ] = - B - - - - - data[ - 0 - , - 2 - ] = - C - - - - - data[ - 1 - , - 0 - ] = - D - - - - - data[ - 1 - , - 1 - ] = - E - - - - - data[ - 1 - , - 2 - ] = - F - - - - - data[ - 2 - , - 0 - ] = - G - - - - - data[ - 2 - , - 1 - ] = - H - - - - - data[ - 2 - , - 2 - ] = - I - - - diff --git a/files/.gitkeep b/files/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/index.md b/index.md index 43cbdef..1a7a9c1 100644 --- a/index.md +++ b/index.md @@ -1,86 +1,32 @@ --- layout: lesson -root: . # Is the only page that doesn't follow the pattern /:path/index.html -permalink: index.html # Is the only page that doesn't follow the pattern /:path/index.html +root: . +permalink: index.html --- -Python is becoming a dominant language to perform many different types of work -across many disciplines. This course is based on the Software Carpentry material -and will cover topics such as fundamentals of Python to get a basic understanding -and then focussing on storing and handling data within Python using common packages -such as numpy and matplotlib. Python has an extensive set of packages to extend -its abilities and will be highlighted within the course. Once an understanding of -Python is provided, migrating Python code onto a supercomputer has a few topics to -cover such as how to copy files, different versions of Python and installing -packages. Advice will be provided on running and storing data on our supercomputer, -“Hawk” to get the attendee started using Python efficiently within a supercomputer -environment. -This introductory course to Python is based on the freely available material -produced by the **Software Carpentry**. Due to time restrictions we are only able -to cover a few lessons, but if you find the course interesting we encourage you -to continue your learning journey with [them](https://swcarpentry.github.io/python-novice-inflammation/). +# Tetris Game (俄罗斯方块) - +Welcome to a classic Tetris game implementation in Python! -{% comment %} This is a comment in Liquid {% endcomment %} +This project features a fully functional Tetris game built with pygame, offering the classic block-stacking puzzle experience. ---- - -The best way to learn how to program is to do something useful, -so this introduction to Python is built around a common scientific task: -**data analysis**. - -### Arthritis Inflammation -We are studying **inflammation in patients** who have been given a new treatment for arthritis. - -There are 60 patients, who had their inflammation levels recorded for 40 days. -We want to analyze these recordings to study the effect of the new arthritis treatment. - -To see how the treatment is affecting the patients in general, we would like to: - -1. Calculate the average inflammation per day across all patients. -2. Plot the result to discuss and share with colleagues. - -![3-step flowchart shows inflammation data records for patients moving to the Analysis step -where a heat map of provided data is generated moving to the Conclusion step that asks the -question, How does the medication affect patients?]( -fig/lesson-overview.svg "Lesson Overview") - - -### Data Format -The data sets are stored in -[comma-separated values]({{ page.root }}/reference.html#comma-separated-values) (CSV) format: +## Quick Start -- each row holds information for a single patient, -- columns represent successive days. +1. Install pygame: `pip install pygame` +2. Run the game: `python tetris.py` +3. Enjoy! -The first three rows of our first file look like this: -~~~ -0,0,1,3,1,2,4,7,8,3,3,3,10,5,7,4,7,7,12,18,6,13,11,11,7,7,4,6,8,8,4,4,5,7,3,4,2,3,0,0 -0,1,2,1,2,1,3,2,2,6,10,11,5,9,4,4,7,16,8,6,18,4,12,5,12,7,11,5,11,3,3,5,4,4,5,5,1,1,0,1 -0,1,1,3,3,2,6,2,5,9,5,7,4,5,4,15,5,11,9,10,19,14,12,17,7,12,11,7,4,2,10,5,4,2,2,3,2,2,1,1 -~~~ -{: .source} -Each number represents the number of inflammation bouts that a particular patient experienced on a -given day. +## Game Controls -For example, value "6" at row 3 column 7 of the data set above means that the third -patient was experiencing inflammation six times on the seventh day of the clinical study. +- **Arrow Keys (Left/Right)**: Move pieces horizontally +- **Arrow Key (Down)**: Soft drop +- **Arrow Key (Up)**: Rotate piece +- **Space Bar**: Hard drop (instant drop) -In order to analyze this data and report to our colleagues, we'll have to learn a little bit -about programming. +## About Tetris -> ## Prerequisites -> -> You need to understand the concepts of **files** and **directories** and how to start a Python -> interpreter before tackling this lesson. This lesson sometimes references Jupyter -> Notebook although you can use any Python interpreter mentioned in the [Setup][lesson-setup]. -> -> The commands in this lesson pertain to **Python 3**. -{: .prereq} +Tetris is a classic tile-matching puzzle game where players arrange falling tetrominoes (geometric shapes composed of four square blocks) to create complete horizontal lines, which then disappear and award points. -### Getting Started -To get started, follow the directions on the "[Setup][lesson-setup]" page to download data -and install a Python interpreter. +The goal is to prevent the pieces from stacking up to the top of the playing field. The game ends when no more pieces can enter the play area. -{% include links.md %} +Good luck and have fun! diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5d2caa2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +pygame>=2.0.0 diff --git a/tetris.py b/tetris.py new file mode 100644 index 0000000..1de9c67 --- /dev/null +++ b/tetris.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 +""" +Simple Tetris Game +A classic Tetris implementation in Python using pygame. +""" + +import pygame +import random +import sys + +# Initialize pygame +pygame.init() + +# Constants +SCREEN_WIDTH = 300 +SCREEN_HEIGHT = 600 +BLOCK_SIZE = 30 +GRID_WIDTH = SCREEN_WIDTH // BLOCK_SIZE +GRID_HEIGHT = SCREEN_HEIGHT // BLOCK_SIZE + +# Colors +BLACK = (0, 0, 0) +WHITE = (255, 255, 255) +GRAY = (128, 128, 128) +RED = (255, 0, 0) +GREEN = (0, 255, 0) +BLUE = (0, 0, 255) +CYAN = (0, 255, 255) +MAGENTA = (255, 0, 255) +YELLOW = (255, 255, 0) +ORANGE = (255, 165, 0) + +# Tetromino shapes +SHAPES = [ + [[1, 1, 1, 1]], # I + [[1, 1], [1, 1]], # O + [[1, 1, 1], [0, 1, 0]], # T + [[1, 1, 1], [1, 0, 0]], # L + [[1, 1, 1], [0, 0, 1]], # J + [[1, 1, 0], [0, 1, 1]], # S + [[0, 1, 1], [1, 1, 0]] # Z +] + +SHAPE_COLORS = [CYAN, YELLOW, MAGENTA, ORANGE, BLUE, GREEN, RED] + + +class Tetromino: + def __init__(self): + self.shape_index = random.randint(0, len(SHAPES) - 1) + self.shape = SHAPES[self.shape_index] + self.color = SHAPE_COLORS[self.shape_index] + self.x = GRID_WIDTH // 2 - len(self.shape[0]) // 2 + self.y = 0 + + def rotate(self): + """Rotate the tetromino 90 degrees clockwise""" + self.shape = list(zip(*self.shape[::-1])) + + +class Tetris: + def __init__(self): + self.screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) + pygame.display.set_caption('Tetris') + self.clock = pygame.time.Clock() + self.grid = [[0 for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)] + self.current_piece = Tetromino() + self.game_over = False + self.score = 0 + self.fall_time = 0 + self.fall_speed = 500 # milliseconds + + def check_collision(self, piece, offset_x=0, offset_y=0): + """Check if the piece collides with the grid or boundaries""" + for y, row in enumerate(piece.shape): + for x, cell in enumerate(row): + if cell: + new_x = piece.x + x + offset_x + new_y = piece.y + y + offset_y + + # Check boundaries + if new_x < 0 or new_x >= GRID_WIDTH or new_y >= GRID_HEIGHT: + return True + + # Check grid collision + if new_y >= 0 and self.grid[new_y][new_x]: + return True + return False + + def lock_piece(self): + """Lock the current piece into the grid""" + for y, row in enumerate(self.current_piece.shape): + for x, cell in enumerate(row): + if cell: + grid_x = self.current_piece.x + x + grid_y = self.current_piece.y + y + if grid_y >= 0: + self.grid[grid_y][grid_x] = self.current_piece.color + + def clear_lines(self): + """Clear completed lines and update score""" + lines_cleared = 0 + y = GRID_HEIGHT - 1 + while y >= 0: + if all(self.grid[y]): + del self.grid[y] + self.grid.insert(0, [0 for _ in range(GRID_WIDTH)]) + lines_cleared += 1 + else: + y -= 1 + + if lines_cleared > 0: + self.score += lines_cleared * 100 + + def move(self, dx, dy): + """Move the current piece""" + if not self.check_collision(self.current_piece, dx, dy): + self.current_piece.x += dx + self.current_piece.y += dy + return True + return False + + def rotate_piece(self): + """Rotate the current piece""" + old_shape = self.current_piece.shape + self.current_piece.rotate() + + if self.check_collision(self.current_piece): + self.current_piece.shape = old_shape + + def drop_piece(self): + """Drop the piece all the way down""" + while self.move(0, 1): + pass + + def draw_grid(self): + """Draw the grid and locked pieces""" + for y in range(GRID_HEIGHT): + for x in range(GRID_WIDTH): + rect = pygame.Rect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE) + if self.grid[y][x]: + pygame.draw.rect(self.screen, self.grid[y][x], rect) + pygame.draw.rect(self.screen, GRAY, rect, 1) + + def draw_piece(self, piece): + """Draw the current piece""" + for y, row in enumerate(piece.shape): + for x, cell in enumerate(row): + if cell: + rect = pygame.Rect( + (piece.x + x) * BLOCK_SIZE, + (piece.y + y) * BLOCK_SIZE, + BLOCK_SIZE, + BLOCK_SIZE + ) + pygame.draw.rect(self.screen, piece.color, rect) + pygame.draw.rect(self.screen, GRAY, rect, 1) + + def draw_text(self, text, size, color, x, y): + """Draw text on screen""" + font = pygame.font.Font(None, size) + text_surface = font.render(text, True, color) + text_rect = text_surface.get_rect() + text_rect.midtop = (x, y) + self.screen.blit(text_surface, text_rect) + + def run(self): + """Main game loop""" + while not self.game_over: + self.fall_time += self.clock.get_rawtime() + self.clock.tick() + + # Handle events + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_LEFT: + self.move(-1, 0) + elif event.key == pygame.K_RIGHT: + self.move(1, 0) + elif event.key == pygame.K_DOWN: + self.move(0, 1) + elif event.key == pygame.K_UP: + self.rotate_piece() + elif event.key == pygame.K_SPACE: + self.drop_piece() + + # Auto fall + if self.fall_time >= self.fall_speed: + self.fall_time = 0 + if not self.move(0, 1): + self.lock_piece() + self.clear_lines() + self.current_piece = Tetromino() + + # Check game over + if self.check_collision(self.current_piece): + self.game_over = True + + # Draw everything + self.screen.fill(BLACK) + self.draw_grid() + self.draw_piece(self.current_piece) + self.draw_text(f'Score: {self.score}', 30, WHITE, SCREEN_WIDTH // 2, 10) + pygame.display.flip() + + # Game over screen + self.screen.fill(BLACK) + self.draw_text('GAME OVER', 50, RED, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 - 50) + self.draw_text(f'Final Score: {self.score}', 30, WHITE, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2) + self.draw_text('Press any key to exit', 25, WHITE, SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2 + 50) + pygame.display.flip() + + # Wait for key press to exit + waiting = True + while waiting: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + waiting = False + if event.type == pygame.KEYDOWN: + waiting = False + + pygame.quit() + sys.exit() + + +if __name__ == '__main__': + print("Starting Tetris...") + print("Controls:") + print(" LEFT/RIGHT: Move piece") + print(" DOWN: Soft drop") + print(" UP: Rotate piece") + print(" SPACE: Hard drop") + print() + + game = Tetris() + game.run()