Lecture 33 (Sherriff) - Gamebox 3

Lecture Date: Wednesday, November 9

See the remaining code for jumping_puzzle.py at http://cs1110.cs.virginia.edu/code/gamebox/. Note that the level that is loaded from a file in part 4 is included as a comment at the top of jumping_puzzle_pt4.py.

We will look at infinite_jumper.py and some other example code and finish up with a discussion about game design.

First, what makes up a game?

Here are a couple of definitions:

  • "Playing a game is the voluntary attempt to overcome unnecessary obstacles." -Bernard Suits
  • "A game is a system in which players engage in artificial conflict, defined by rules, that results in a quantifiable outcome." - Salen & Zimmerman, Rules of Play

Another way of thinking about it is that all games have:

  • goals - something to work toward
  • rules - the framework around the game
  • feedback - letting the player know what's going on
  • voluntary participation / players - people willingly playing the game

Let's consider these things in the context of the games we play.

When we make a game, we have to consider our requirements for the game somewhat differently than a "normal" program we write. When we want to write a program that reads in election data and do a calculation, for example, we can pretty easily test to see if we are successful. Games, however, have what we call the "second-order design" problem. When we make a game, we might want a player to feel excited or scared or something else. But the code we write doesn't explicitly create "excitement." It makes a player character, walls, coins, etc. The only way we can know whether we are successful is playtesting. A lot of playtesting.

No audio today due to recording error.

more ...

Lecture 32 (Sherriff) - Gamebox 2

Lecture Date: Monday, November 7

We will keep going with building our simple game today.

Part 2:

# jumping puzzle game - pt2 - add ground and a character that can move and jump

import pygame
import gamebox
camera = gamebox.Camera(800,600)

character = gamebox.from_color(50, 50, "red", 15, 40)
character.yspeed = 0
walls = [gamebox.from_color(-100, 600, "black", 3000, 100)]
time = 9000
score = 0


def tick(keys):
    global time, score

    # clear display
    camera.clear("blue")

    # decrease timer per call of tick
    time -= 1

    # calculate timer
    seconds = str(int((time/ticks_per_second))).zfill(3)

    if pygame.K_RIGHT in keys:
        character.x += 5
    if pygame.K_LEFT in keys:
        character.x -= 5
    character.yspeed += 1
    character.y = character.y + character.yspeed

    for wall in walls:
        if character.bottom_touches(wall):
            character.yspeed = 0
            if pygame.K_SPACE in keys:
                character.yspeed = -15
        if character.touches(wall):
            character.move_to_stop_overlapping(wall)
        camera.draw(wall)


    # write timer and score to screen
    time_box = gamebox.from_text(650,30,"Time Remaining: " + seconds,"arial",24,"white")
    score_box = gamebox.from_text(75,30,"Score: " + str(score),"arial",24,"white")
    camera.draw(time_box)
    camera.draw(score_box)

    # draw the character
    camera.draw(character)

    # display the screen
    camera.display()


ticks_per_second = 30

# keep this line the last one in your program
gamebox.timer_loop(ticks_per_second, tick)

Part 3:

# jumping puzzle game - pt3 - add a platform and coin that can be collected

import pygame
import gamebox
camera = gamebox.Camera(800,600)

character = gamebox.from_color(50, 50, "red", 15, 40)
character.yspeed = 0
walls = [
    gamebox.from_color(-100, 600, "black", 3000, 100),
    gamebox.from_color(200, 500, "black", 100, 15)
]
coins = [gamebox.from_color(300, 450, "yellow", 12, 12)]
time = 9000
score = 0

def tick(keys):
    global time, score

    # clear display
    camera.clear("blue")

    # decrease timer per call of tick
    time -= 1

    # calculate timer
    seconds = str(int((time/ticks_per_second))).zfill(3)

    if pygame.K_RIGHT in keys:
        character.x += 5
    if pygame.K_LEFT in keys:
        character.x -= 5
    character.yspeed += 1
    character.y = character.y + character.yspeed

    for wall in walls:
        if character.bottom_touches(wall):
            character.yspeed = 0
            if pygame.K_SPACE in keys:
                character.yspeed = -15
        if character.touches(wall):
            character.move_to_stop_overlapping(wall)
        camera.draw(wall)

    for coin in coins:
        if character.touches(coin):
            score += 1
            coins.remove(coin)
        camera.draw(coin)


    # write timer and score to screen
    time_box = gamebox.from_text(650,30,"Time Remaining: " + seconds,"arial",24,"white")
    score_box = gamebox.from_text(75,30,"Score: " + str(score),"arial",24,"white")
    camera.draw(time_box)
    camera.draw(score_box)

    # draw the character
    camera.draw(character)

    # display the screen
    camera.display()


ticks_per_second = 30

# keep this line the last one in your program
gamebox.timer_loop(ticks_per_second, tick)

more ...

Lecture 31 (Sherriff) - Gamebox

Lecture Date: Friday, November 4

We start the game project today (well, yesterday in lab) and we'll look at some of the neat things you can do with PyGame and gamebox! We will also go over the parameters of POTD 16 and the final project.

We will build some pieces of a very simple game!

# jumping puzzle game - pt1 - draw the score and timer on the screen

import pygame
import gamebox
camera = gamebox.Camera(800,600)

time = 9000
score = 0

def tick(keys):
    global time, score

    # clear display
    camera.clear("blue")

    # decrease timer per call of tick
    time -= 1

    # calculate timer
    seconds = str(int((time/ticks_per_second))).zfill(3)

    # write timer and score to screen
    time_box = gamebox.from_text(650,30,"Time Remaining: " + seconds,"arial",24,"white")
    score_box = gamebox.from_text(75,30,"Score: " + str(score),"arial",24,"white")
    camera.draw(time_box)
    camera.draw(score_box)
    camera.display()


ticks_per_second = 30

# keep this line the last one in your program
gamebox.timer_loop(ticks_per_second, tick)

more ...

Lecture 30 (Sherriff) - Test 2

Lecture Date: Wednesday, November 2

Test 2 is today! Go to your normal lecture location at the normal time. You only need to bring a writing utensil with you.

more ...


Lecture 28 (Sherriff) - Reading Data

Lecture Date: Friday, October 28

Two examples from https://github.com/fivethirtyeight/data!

Sleeping Alone data - http://cs1110.cs.virginia.edu/sleeping.csv

filename = "sleeping.csv"

def married_analysis():
    datafile = open("sleeping.csv", "r")

    married_breakdown = {}

    for line in datafile:
        split_line = line.strip().split(";")

        if split_line[2] == "Married":
            if split_line[4] in married_breakdown:
                married_breakdown[split_line[4]] += 1
            else:
                married_breakdown[split_line[4]] = 1

    return married_breakdown


def age_analysis():

    possible_responses = ['Never', 'Once a year or less', 'Once a month or less', 'A few times per month', 'A few times per week', 'Every night']

    possible_ages = ['18-29','30-44','45-60','> 60']

    for age in possible_ages:
        datafile = open("sleeping.csv", "r")
        datafile.readline()
        age_breakdown = [0,0,0,0,0,0]
        total_responses = 0
        print("-------",age,"-------")
        for line in datafile:
            split_line = line.strip().split(";")
            if split_line[4] in possible_responses and split_line[6] == age:
                index = possible_responses.index(split_line[4])
                age_breakdown[index] += 1
                total_responses += 1

        for i in range(len(possible_responses)):
            print(possible_responses[i], ":", format(100*age_breakdown[i]/total_responses, "0.2f") +"%")

print(married_analysis())
age_analysis()

Thanksgiving Traditions - http://cs1110.cs.virginia.edu/thanksgiving.csv

datafile = open("thanksgiving.csv", "r")

main_dish = {}
total_responses = 0

for line in datafile:
    split_line = line.strip().split(";")

    if split_line[2] != "":
        total_responses += 1
        if split_line[2] in main_dish:
            main_dish[split_line[2]] += 1
        else:
            main_dish[split_line[2]] = 1

for dish in main_dish:
    print(dish, ":", format(100*main_dish[dish]/total_responses, "0.2f")+"%")

more ...

Lecture 27 (Sherriff) - Reading the Web 2

Lecture Date: Wednesday, October 26

Let's review what we've done and do some more examples!

First, back to that weather example we ended with on Monday... Yeah... that was slightly harder than I thought it would be. We have to use a slightly different method to get a very particular tag that says that it is the temperature. This data is stored in a tag attribute, which we can access with the curly braces as shown below.

# https://www.wunderground.com/US/VA/Charlottesville.html

import urllib.request
import bs4

web = urllib.request.urlopen("https://www.wunderground.com/US/VA/Charlottesville.html")
page = web.read()

parsedPage = bs4.BeautifulSoup(page, "html.parser")

for tag in parsedPage.find_all("span", {"data-variable" : "temperature"}, class_="wx-data"):
    print(tag.span.text)

What could you do with this? Well, it's actually pretty easy to have this code continuously running on a Raspberry Pi with one of these hooked up to it: https://www.adafruit.com/products/306. And then you can have a light bar changing color based on the temperature! Neat!

Have you ever gone to a website and wanted to download, say, all of the pictures on that page without having to click all of them? Maybe you want to download a whole bunch of .mp3 files?

# Code based on https://automatetheboringstuff.com/chapter11/
import urllib.request, os, bs4

count = 10 # how many comics to download

url = 'http://xkcd.com'              # starting url
os.makedirs('xkcd', exist_ok=True)   # store comics in ./xkcd

while count > 0:

    # First, download the page.
    print('Downloading page', url)
    webpage = urllib.request.urlopen(url)

    parsed_page = bs4.BeautifulSoup(webpage.read(), "html.parser")

    # Use BeautifulSoup to find the URL of the comic image.
    comic_elem = parsed_page.select('#comic img')
    if comic_elem == []:
         print('Could not find comic image.')
    else:
        comic_url = 'http:' + comic_elem[0].get('src')
        # Download the image.
        print('Downloading image', comic_url)
        comic_page = urllib.request.urlopen(comic_url)
        count -= 1

        # Save the image to ./xkcd.
        image_file = os.path.join('xkcd', os.path.basename(comic_url))
        with open(image_file, 'b+w') as file:
            file.write(comic_page.read())
        file.close()

    # Get the Prev button's url.
    prev_link = parsed_page.select('a[rel="prev"]')[0]
    url = 'http://xkcd.com' + prev_link.get('href')

print('Done.')

If time, we'll pick a dataset from https://github.com/fivethirtyeight/data and do some more example programs.

more ...

Test 2 Review Guide

Test date: Wednesday, November 2, 2016
Test location: normal lecture hall at the normal time (you MUST go to your assigned section)
Test duration: 50 minutes (for 1110 and 1111)
Test format: writing on paper; bring pen/pencil (and nothing else)
Review session: Tuesday, November 1, 5:00-6:00 PM in Chem 402

Review Sheet

General Points:

  • The format of the test will largely match that of Test 1 - some short answer, some code reading, some multiple choice, and a large coding question at the end.
  • Everything from the first part of the semester is still fair game. It's hard to ask a coding question without you still knowing how to do an if statement, for instance.

Not on the Test:

  • Specifics of the encryption chase
  • BeautifulSoup

Functions:

  • You WILL be asked to write functions on this test
  • Know what the function header / signature is
  • Know the difference between a void return and a function that returns a data type of some kind (and when you would use each)
  • Know what named / optional parameters are and how they are used
  • Know what positional parameters are and that they must come first in a function call
  • Know the difference between pass by value and pass by reference

Files:

  • Know how to read structured files (i.e. csv) from a local file or the Internet
  • Know how to write data to a file

String Processing:

  • Know some of the basic methods for parsing strings (.find(), .index(), etc.)
  • Know what regular expressions are, how to read a simple one, and when you might use them (but we will not ask you to write a regular expression)

Study Hints

Practice problems

Here are some problems you can practice with (however, we do not have the solutions available - you can probably Google for them):

  • Chapter 5 (p. 229): 11, 12, 13, 17, 20
  • Chapter 6 (p. 288): 1, 2, 4, 6
  • Chapter 8 (p. 366): 6, 7, 9, 10

Practice coding on paper

You'll be writing code on paper. This feels different than writing it in PyCharm. You don't want to be surprised by how different it feels. Practice writing code by hand.

A few small syntax errors is OK. But if you are really off we will take off points. Try to write correct code.

We'll give you any imports you might need - so don't worry about memorizing those.

Try re-solving the POTD and Lab problems on paper without looking at your code or textbook.

You can find more sample problems in Programming Challenges in the textbook. We do not, however, have the answer key to share with you.

Also remember that speed matters. 50 minutes is not a long time.

Practice reading code

We will show you code and ask you what it does. Practice thinking through code without running it.

Review the Lectures

Not everything in the book is equally important. Review the lecture notes to see what we emphasized. If you are confused by some point, check the audio. You might want to listen to the audio of the other instructor (the one you didn't hear in class) so that you can get a different perspective on the material.

more ...

Lecture 26 (Sherriff) - Reading the Web

Lecture Date: Monday, October 24

We will first start by reviewing regular expressions, doing a few more examples. One good use for regular expressions is password validation:

^.{4,8}$ - Any set of characters, between 4 and 8 characters long.

^[a-zA-Z]\w{3,14}$ - Must start with a letter, then can have anything else between 3 and 14 characters long.

Reading plain text is one thing. Reading through an HTML website is a bit trickier. We could just basically ignore all the HTML stuff and just grab what we want. But what if the stuff we want is indicated by the HTML?

BeautifulSoup is an HTML parsing library. It allows you to specifically look through HTML to find particular tags that you can pull out.

BeautifulSoup4 Docs - http://www.crummy.com/software/BeautifulSoup/bs4/doc/

Using Beautiful Soup:

# First install BeautifulSoup4

import urllib.request
import bs4

web = urllib.request.urlopen("https://cs1110.cs.virginia.edu/souptest.html")
page = web.read()

parsedPage = bs4.BeautifulSoup(page, "html.parser")

for tag in parsedPage.find_all('h1'):
    print(tag)

HTML you can parse:

<html>
    <head>
        <title>Test Page!</title>
    </head>
    <body>
        <h1>Section 1</h1>
            Here is some really interesting information!
        <h1>Section 2</h1>
            Some more really cool stuff!
        <h1>Section 3</h1>
            Even more stuff you care about!
    </body>
</html>

Reading the UVa Men's Basketball schedule:

import urllib.request
import bs4

web = urllib.request.urlopen("http://www.virginiasports.com/sports/m-baskbl/sched/va-m-baskbl-sched.html")
page = web.read()

parsedPage = bs4.BeautifulSoup(page, "html.parser")

for tag in parsedPage.find_all('td', class_="sch-col-1"):
    print(tag)

Reading Lou's List:

import urllib.request
import bs4

web = urllib.request.urlopen("http://rabi.phys.virginia.edu/mySIS/CS2/page.php?Semester=1168&Type=Group&Group=CompSci")
page = web.read()

parsed_page = bs4.BeautifulSoup(page, "html.parser")

for section_tag in parsed_page.find_all('td', class_="CourseName"):
    print(section_tag.text)

more ...

Lecture 25 (Sherriff) - Regular Expressions

Lecture Date: Friday, October 21

Using the string libraries to parse a body of text is really good for reading information from top to bottom and making decisions based on that information. It's also good for parsing data into another data structure.

But sometimes you just want to find all of one particular kind of data. Or you want to verify that some piece of text follows a very particular format. For those instances, we can use regular expressions.

A regular expression is a form of pattern matching. Using a defined set of tokens, we can set up a pattern that must be met before a piece of text will be accepted and returned.

Here's a starter list of tokens that you might use:

[abc] - one of those three characters
[a-z] - all lowercase
[a-z0-9] - add in numbers
. - any one character
\. - an actual period
* - 0 to many
? - 0 or 1
+ - 1 to many

Let's test some regular expressions at https://regex101.com/!

What are some ways we can use regular expressions?

import re
import urllib.request

def is_valid_password(password):
    regex = re.compile(r"^.*(?=.{8,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*$")
    results = regex.search(password)
    if results != None:
        return True
    return False

def find_possible_names(text):
    regex = re.compile(r"[A-Z][a-z]*")
    results = regex.search(text)
    if results != None:
        return results.group()
    return None

def find_all_possible_names(text):
    names = []

    regex = re.compile(r"[A-Z][a-z]*")
    results = regex.findall(text)
    if len(results) != 0:
        for name in results:
            names.append(name)
    return names

def find_all_phone_numbers(text):
    numbers = []

    regex = re.compile(r"[0-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]")
    results = regex.findall(text)
    if len(results) != 0:
        for number in results:
            numbers.append(number)

    return numbers

def find_phone_numbers_on_website(url):
    stream = urllib.request.urlopen(url)
    numbers = []

    for line in stream:
        decoded = line.decode("UTF-8").strip()
        numbers += find_all_phone_numbers(decoded)
    return numbers

print(is_valid_password("password"))
print(is_valid_password("abCd723223"))
print(find_possible_names("some text before Mark and more"))
print(find_possible_names("some text before Mark and Bob stuff"))
print(find_all_possible_names("some text before Mark and Bob stuff"))
print(find_all_phone_numbers("askljaslkdf 434-982-2688 alksdjfla"))
print(find_phone_numbers_on_website("http://www.archives.gov/about/organization/telephone-list.html"))

Can we improve our name finder for Alice in Wonderland?

import urllib.request
import re

url = "http://cs1110.cs.virginia.edu/alice.txt"
possible_names = {}

stream = urllib.request.urlopen(url)
for line in stream:
    decoded = line.decode("UTF-8").strip()
    regex = re.compile(r"[A-Z][a-z]+")
    results = regex.findall(decoded)
    if len(results) > 0:
        for result in results:
            if result in possible_names:
                possible_names[result] += 1
            else:
                possible_names[result] = 1

print(possible_names)

for item in possible_names:
    if possible_names[item] > 30:
        print(item, possible_names[item])

Where might we look for some emails to harvest...

more ...