Recording

Many people got into CS by playing video games. But even if you didn’t, learning how to make something simple and fun is quite rewarding, not to mention nice to show off to other people.

Note: If you’re using WSL/WSL2, this won’t work due to windowing issues, so ensure your install of Python is local.

print("Hello pygame!")

Hosted by: Harsh Deep

Motivation

Install

pip install pygame

Workshop

Starter:

First, we will import the following. We’ll be using these libraries as we go along.

import sys, pygame, random

Now we can begin coding! Every pygame script begins with the following line:

pygame.init()

This initializes the library and sets up everything we need to do. We can then:

size = width, height = 500, 500

screen = pygame.display.set_mode(size)

Here we’re declaring our width, height, and size variables. We are then use these values in a built-in pygame function to create our screen.

Every game has what is called a game loop. It’s an infinite loop goes on for the game’s lifespan, and it’s where we put all of our game logic.

clock = pygame.time.Clock()
while 1:
    clock.tick(60)
    screen.fill((0, 0, 0))
    pygame.display.flip()

Here we’ve done the following. We have set our tick rate to 60 fps using the built-in pygame function. We then fill our screen with black for the background and update the display. At this point, you should be unable to see a window on your screen. That’s because we are yet to handle the event queue. Add this into the loop:

for event in pygame.event.get():
  if event.type == pygame.QUIT: sys.exit()

The event queue is simply a queue of all the events that have occurred in your game from the computer system. We can loop through these and determine if we care about any of these events. Here we only care if the event is ‘QUIT’ (Control+C on your keyboard) in which case we dip.

Setting up Main Character

We will use OOP to work with defining our characters. Pygame also lets us extend a Sprite class that provides us with some useful functionality.

class MainCharacter(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        # image
        # rect
        self.image = pygame.image.load("./image0.jpg")
        self.image = pygame.transform.scale(self.image, (50, 50))

        self.rect = self.image.get_rect()
        self.rect.center = (250,250)

Each sprite has to have an image and a rect (rectangular coordinates) so that pygame can draw it out. We’ve also defined self.rect.center so the sprite will start in the middle.

Putting the main character in a group


In pygame, groups are used to ‘group’ similar characters together to operate on them all at once. Let’s define a group for the main character before the game loop:

main = MainCharacter()

main_sprite = pygame.sprite.Group()
main_sprite.add(main)

Once our MainCharacter is in a group, we can also draw it out. Insert the following code inside of your loop. You should now see an image in the center of your screen.

main_sprite.draw(screen)

At this point your code should look like:

import sys, pygame, random, os

pygame.init()

size = width, height = 500, 500
screen = pygame.display.set_mode(size)
speed = [0, 0]
class MainCharacter(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        # image
        # rect
        self.image = pygame.image.load("panda.jpg")
        self.image = pygame.transform.scale(self.image, (50, 50))

        self.rect = self.image.get_rect()
        self.rect.center = (250,250)

main = MainCharacter()

main_sprite = pygame.sprite.Group()
main_sprite.add(main)

clock = pygame.time.Clock()
while 1:
    clock.tick(60)
    screen.fill((0, 0, 0))
    main_sprite.draw(screen)
    pygame.display.flip()

Handling keyboard input

Introduce a speed array near the top of your code:

speed = [0,0]

We will use this to handle the main character’s speed as the player moves them with the keyboard. Add the following to your game loop:

key = pygame.key.get_pressed()

if (key[pygame.K_a]):
    if (speed[0] > -20):
        speed[0] -= 1.5
if (key[pygame.K_d]):
    if (speed[0] < 20):
        speed[0] += 1.5
if (key[pygame.K_w]):
    if (speed[1] > -20):
        speed[1] -= 1.5
if (key[pygame.K_s]):
    if (speed[1] < 20):
        speed[1] += 1.5

pygame.key.get_pressed returns a dictionary with whether or not that key has been pressed. We can then handle the case for each key. However,, once you click, it doesn’t slow down. So let’s introduce some slow-down behavior:

    if (speed[0] > 0):
        speed[0] -= .5
    elif (speed[0] < 0):
        speed[0] += .5


    if (speed[1] > 0):
        speed[1] -= .5
    elif (speed[1] < 0):
        speed[1] += .5

Now the main character should slow down after the player releases the key. But we can still go off screen. To fix that, we’ll make the main character bounce at the boundaries of the screen:

    if main.rect.left < 0 or main.rect.right > width:
        speed[0] = -speed[0] * 1.5
    if main.rect.top < 0 or main.rect.bottom > height:
        speed[1] = -speed[1] * 1.5
        
        
    main.rect = main.rect.move(speed)


Your code should now look something like:

import sys, pygame, random, os

pygame.init()

size = width, height = 500, 500
screen = pygame.display.set_mode(size)
speed = [0, 0]
class MainCharacter(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        # image
        # rect
        self.image = pygame.image.load("panda.jpg")
        self.image = pygame.transform.scale(self.image, (50, 50))

        self.rect = self.image.get_rect()
        self.rect.center = (250,250)

main = MainCharacter()

main_sprite = pygame.sprite.Group()
main_sprite.add(main)

clock = pygame.time.Clock()
while 1:
    clock.tick(60)
    
    key = pygame.key.get_pressed()

    if (key[pygame.K_a]):
        if (speed[0] > -20):
            speed[0] -= 1.5
    if (key[pygame.K_d]):
        if (speed[0] < 20):
            speed[0] += 1.5
    if (key[pygame.K_w]):
        if (speed[1] > -20):
            speed[1] -= 1.5
    if (key[pygame.K_s]):
        if (speed[1] < 20):
            speed[1] += 1.5

     if (speed[0] > 0):
        speed[0] -= .5
    elif (speed[0] < 0):
        speed[0] += .5


    if (speed[1] > 0):
        speed[1] -= .5
    elif (speed[1] < 0):
        speed[1] += .5
        
    if main.rect.left < 0 or main.rect.right > width:
        speed[0] = -speed[0] * 1.5
    if main.rect.top < 0 or main.rect.bottom > height:
        speed[1] = -speed[1] * 1.5
        
    main.rect = main.rect.move(speed)

    
    screen.fill((0, 0, 0))
    main_sprite.draw(screen)
    pygame.display.flip()

Bad Guy Class

class Corona(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load("./image0.jpg")
        self.image = pygame.transform.scale(self.image, (10, 10))
        self.rect = self.image.get_rect()
        self.badSpeed = [3, 3]
        self.rect.center = (random.randint(0,500), random.randint(0,500))
    def update(self):
        self.rect = self.rect.move(self.badSpeed)

        if self.rect.left < 0 or self.rect.right > width:
            self.badSpeed[0] = -self.badSpeed[0] * 1.5
            self.badSpeed[1] = self.badSpeed[1] * random.uniform(.5, 1.1)
        if self.rect.top < 0 or self.rect.bottom > height:
            self.badSpeed[1] = -self.badSpeed[1] * 1.5
            self.badSpeed[0] = self.badSpeed[0] * random.uniform(.5, 1.1)

        if (self.badSpeed[0] > 6):
            self.badSpeed[0] -= .5
        if (self.badSpeed[0] < -6):
            self.badSpeed[0] += .5

        if (self.badSpeed[1] > 6):
            self.badSpeed[1] -= .5
        if (self.badSpeed[1] < -6):
            self.badSpeed[1] += .5

The only thing that should look unfamiliar here is the update method. This is inherited from the sprite class, and we can call update on an entire group. It will run that update method for each individual sprite. We are also using random.uniform() to introduce directional variation in our bad guys.

We can then add them to a group. Let’s make a group and add three enemies to it:

badSprites = pygame.sprite.Group()

for i in range(0, 3):
    badSprites.add(Corona())

If we introduce the line badSprites.update() inside of our game loop followed by a badSprites.draw(screen) we should see them moving around the screen.

Handling Collisions

When the main character collides with a bad guy, we want them to lose a life. There are many ways of doing this, but I chose:

class MainCharacter(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        # image
        # rect
        self.image = pygame.image.load("/Users/ahmed/Desktop/image0.jpg")
        self.image = pygame.transform.scale(self.image, (50, 50))

        self.rect = self.image.get_rect()
        self.rect.center = (250,250)

        self.totalLives = 5
        self.lastHit = 0
    def update(self):
        self.lastHit = pygame.time.get_ticks()
        self.totalLives -= 1

        if (self.totalLives <= 0):
            sys.exit()

This will close the game once the player runs out of lives. We can then insert the following into our game loop:

    collisions = pygame.sprite.spritecollideany(main, badSprites)

    if (collisions != None):
        if (pygame.time.get_ticks() - main.lastHit > 1000):
            main_sprite.update()

pygame.sprite.spritecollideany(main, badSprites) returns either None or a sprite name that collided. If it’s not None, we know there is a collision, and if it’s outside of the grace period of the lastHit parameter we added, we go ahead and call update to deduct a life.

Using text to display lives

Here we just have some more syntactical sugar. Insert into your game loop:

    myFont = pygame.font.SysFont("Times New Roman", 18)
    numsLivesDraw = myFont.render(str(main.totalLives), 1, (250, 250, 250))
    screen.blit(numsLivesDraw, (30,30))

In line 2, we declare how we want to render the text. In line 3, we’re actually adding it to the screen by blitting it.

And we’re done! The overall code should look like:

import sys, pygame, random, os

pygame.init()

size = width, height = 500, 500
base_path = os.path.dirname(__file__)
print(os.path.join(base_path, "pandas.jpg"))

screen = pygame.display.set_mode(size)
speed = [0, 0]

class MainCharacter(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        # image
        # rect
        self.image = pygame.image.load(os.path.join(base_path, "panda.jpg"))
        self.image = pygame.transform.scale(self.image, (50, 50))

        self.rect = self.image.get_rect()
        self.rect.center = (250,250)

        self.totalLives = 5
        self.lastHit = 0
    def update(self):
        self.lastHit = pygame.time.get_ticks()
        self.totalLives -= 1

        if (self.totalLives <= 0):
            sys.exit()

class Corona(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(os.path.join(base_path, "panda.jpg"))
        self.image = pygame.transform.scale(self.image, (10, 10))
        self.rect = self.image.get_rect()
        self.badSpeed = [3, 3]
        self.rect.center = (random.randint(0,500), random.randint(0,500))
    def update(self):
        self.rect = self.rect.move(self.badSpeed)

        if self.rect.left < 0 or self.rect.right > width:
            self.badSpeed[0] = -self.badSpeed[0] * 1.5
            self.badSpeed[1] = self.badSpeed[1] * random.uniform(.5, 1.1)
        if self.rect.top < 0 or self.rect.bottom > height:
            self.badSpeed[1] = -self.badSpeed[1] * 1.5
            self.badSpeed[0] = self.badSpeed[0] * random.uniform(.5, 1.1)

        if (self.badSpeed[0] > 6):
            self.badSpeed[0] -= .5
        if (self.badSpeed[0] < -6):
            self.badSpeed[0] += .5

        if (self.badSpeed[1] > 6):
            self.badSpeed[1] -= .5
        if (self.badSpeed[1] < -6):
            self.badSpeed[1] += .5

main = MainCharacter()

main_sprite = pygame.sprite.Group()
main_sprite.add(main)

badSprites = pygame.sprite.Group()

for i in range(0, 3):
    badSprites.add(Corona())

clock = pygame.time.Clock()
while 1:
    clock.tick(60)

    key = pygame.key.get_pressed()

    if (key[pygame.K_a]):
        if (speed[0] > -20):
            speed[0] -= 1.5
    if (key[pygame.K_d]):
        if (speed[0] < 20):
            speed[0] += 1.5
    if (key[pygame.K_w]):
        if (speed[1] > -20):
            speed[1] -= 1.5
    if (key[pygame.K_s]):
        if (speed[1] < 20):
            speed[1] += 1.5


    if (speed[0] > 0):
        speed[0] -= .5
    elif (speed[0] < 0):
        speed[0] += .5

    if (speed[1] > 0):
        speed[1] -= .5
    elif (speed[1] < 0):
        speed[1] += .5

    if main.rect.left < 0 or main.rect.right > width:
        speed[0] = -speed[0] * 1.5
    if main.rect.top < 0 or main.rect.bottom > height:
        speed[1] = -speed[1] * 1.5

    main.rect = main.rect.move(speed)
    badSprites.update()

    for event in pygame.event.get():
        if event.type == pygame.QUIT: sys.exit()
    collisions = pygame.sprite.spritecollideany(main, badSprites)

    if (collisions != None):
        if (pygame.time.get_ticks() - main.lastHit > 1000):
            main_sprite.update()

    print(pygame.time.get_ticks())
    screen.fill((0, 0, 0))
    main_sprite.draw(screen)
    badSprites.draw(screen)

    myFont = pygame.font.SysFont("Times New Roman", 18)
    numsLivesDraw = myFont.render(str(main.totalLives), True, (250, 250, 250))
    screen.blit(numsLivesDraw, (30,30))


    pygame.display.flip()

Learn more

Ethics

  • What we still haven’t learned from Gamergate - the rise of gaming and gaming subcultures is a really cool way computer science has hit the world today, but it also has had many issues with gender diversity. Here’s a hindsight look from a large scale issue from 2014 and its long term impact.

Open Source - Permissive vs Copyleft Licenses

There are two broad categories in open-source licenses that most licenses fall under: Permissive and Copyleft.

Permissive licenses include MIT and ApacheV2. These basically say do whatever you want with the code, and if you use it somewhere else, you often have to include the license. These are popular for small projects when people don’t care too much about what others do and want their open-source software to be good enough for commercial use. The workshop repos use this, for example.

Copyleft licenses are stronger, such as GPLv3, where they want future software development to also have the same freedoms the software was available under. Basically, that means that any work including it also has to be open source. This is often used by large projects like Linux or by people who want to increase the spread of Open Source. However, this type of license makes it hard for commercial use since most commercial software is proprietary (i.e., not open source). The repo for this website actually uses GPLv3, as the template we used to make this uses GPLv3.

For these weekly projects, use any license as long as it’s open source.

There is much debate on these two and these two links are a good start to read more:

Ideas

Feel free to come up with anything you want as long as it’s chatbot related. Here are some ideas to help you get started, but feel free to come up with more. Don’t worry if it’s already been done or if someone else is doing it. The point is learning and fun. :)

  • What kind of games do you like to play? Could you make a simplified version of one of them?

  • Were there any examples that you found interesting? Could you recreate some of that functionality on your own (without copying, of course)?

  • It doesn’t have to be a game (though that is more fun), it can even just be some animation or some interactive art.

  • Have fun with images and custom backgrounds too, for example a main character can have your profile picture or something.

  • Try to add some humor to it. Often a simple game becomes a lot more fun if you add something unique or some type of inside joke to it.

Requirements

  • It should use pygame.

  • You should show it off in your README.md file, with animations/video and an explanation of the game.

  • Has to use an Open Source license via a LICENSE file

  • Have a README.md with explanation about what your project does, how to install it, how to contribute, and how to use it. Check out HackerGirl’s Art of Readme.

Contributors: Ahmed, Harsh, Maaheen