--- tags: Game 2 - Unit 1 --- # Lesson 7 - Pixel Coordinates In this lesson, we will talk in more detail about how the position of objects is represented in Pygame projects using screen coordinates. And we'll look at some examples of how to use Python variables to track and change the position of items. At the end of this lesson, you will build a treasure hunt game, where the user must click on different parts of the screen until they find a hidden treasure chest! ## 2D Virtual Plane The games that we'll be creating during this course will be two-dimensional games. This will make things a bit easier, since we'll only have to worry about how players, items, etc. can move up/down/left/right along the x & y-axis. However, there are a few differences between the Cartesian coordinates you are probably used to using and the screen coordinates that Pygame uses. ### Cartesian vs. Screen Coordinates When drawing different elements onto our `Turtle.Screen()`, we used a coordinate system that you were probably familiar with, where the origin (0, 0) was at the center of the screen. ![cartesian-coords](https://hackmd.io/_uploads/HJd3vUB1s.png) #### Screen Coordinates However, our Pygame programs use a coordinate system that has the origin (0, 0) at the top left corner of the screen. In this system, there are no negative coordinates. You can *move* in a negative direction, but the (x, y) positions will always be positive. ![screen-coords](https://hackmd.io/_uploads/H1fMasa35.png) -image from Al Sweigart's [Invent with Python](https://inventwithpython.com/chapter12.html) ## Coordinate Math Most of the games and animations won't require us to use too much math. However, we will need to use addition, subtraction, multiplication, and division from time to time to update different values in our game state. ### Moving Left & Right We can represent the position of elements using (x, y) coordinates. To move an item left, we can *subtract* from the current `x-position`. To move an item right, we can *add* `x` to the current `x-position`. ### Moving Up & Down To move an item up, we can *add* to the current `y-position`. To move an item down, we can *subtract* from the current `y-position`. ### Calculating Distance ### `abs()` Function Taking the **absolute value** of a number means taking the magnitude of a real number without regard to its sign. When taking the absolute value of a positive number, the result doesn't change. However, negative numbers become positive -> `abs(-4)` = `4` We can use absolute value in our Pygame programs to find the *distance* between two objects. ``` distance = abs(position_item_1 - position_item_2) ``` # Project: X Marks the Spot This variation of a guessing game requires the user to click on different parts of the screen to find the hidden treasure. An event handler will compare where the user clicked to a secret location. If the user is close enough, they will "find" the hidden treasure. ### Setup 1. When setting up our project, we'll want to import the `random` module so that we can generate a random (x, y) coordinate for the treasure's location. And we'll want to perform some of the standard Pygame project setup that we've done in the last couple of projects: * importing required modules * defining the size of the display * setting an appropriate caption ```python import io, sys, pygame, random pygame.init() # define the size of our display size = width, height = 600, 400 screen = pygame.display.set_mode(size) pygame.display.set_caption("Click to find the treasure!") ``` ### Guessing with Images Then, we'll want to create a couple of different images to show where the user has guessed so far, and eventually, where the treasure was hidden. We'll use ![red-x](https://upload.wikimedia.org/wikipedia/commons/6/60/X_mark.png) to mark where there was *NO* treasure and ![treasure](https://upload.wikimedia.org/wikipedia/commons/thumb/8/8e/Treasure_chest_with_gold_coins.svg/64px-Treasure_chest_with_gold_coins.svg.png) after the user finds the hidden location of the treasure! 2. Add the following code to load the two images described above and resize them for our game. ```python try: # Python2 from urllib2 import urlopen except ImportError: # Python3 from urllib.request import urlopen # load the red 'X' image # source - https://commons.wikimedia.org/wiki/File:X_mark.png image_url = "https://upload.wikimedia.org/wikipedia/commons/6/60/X_mark.png" image_str = urlopen(image_url).read() image_file = io.BytesIO(image_str) red_x = pygame.image.load(image_file) red_x = pygame.transform.scale(red_x, (32, 32)) x_rect = red_x.get_rect() # load the treasure chest image # source - https://commons.wikimedia.org/wiki/File:Treasure_chest_with_gold_coins.svg image_url = "https://upload.wikimedia.org/wikipedia/commons/8/8e/Treasure_chest_with_gold_coins.svg" image_str = urlopen(image_url).read() image_file = io.BytesIO(image_str) treasure = pygame.image.load(image_file) treasure = pygame.transform.scale(treasure, (64, 64)) treasure_rect = treasure.get_rect() ``` ### State Before handling any guesses, we'll want to add a couple of items to our game state. 3. Use the `width` and `height` of the screen to generate a random location for the treasure ```python # generate a random (x, y) position where we can hide the treasure treasure_x = random.randint(0, width) treasure_y = random.randint(0, height) ``` 4. And initialize a list we can use to track where the user has guessed, as well as a boolean variable to track whether or not the treasure has been found yet. ```python # use a list to store the (x, y) positions the user has guessed - it will initially be empty guesses = [] found = False # will be True once we find the treasure ``` ### Main Loop 5. Within our main loop, we'll want to listen for `MOUSEBUTTONDOWN` and `QUIT` events. Quitting will terminate the program. ```python # main loop while 1: guess_x, guess_y = -100, -100 # handle events for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() ``` But a mouse click will require some custom logic. 6. First, we need to get the (x, y) coordinates of where the user clicked ```python elif event.type == pygame.MOUSEBUTTONDOWN: # only save new guesses if we haven't found the treasure yet if not found: guess_x = pygame.mouse.get_pos()[0] guess_y = pygame.mouse.get_pos()[1] guesses.append([guess_x, guess_y]) ``` 7. Then, we will compare the user's coordinates to coordinates of our buried treasure. The code below should be added inside the `if` block we started in step 6. ```python # check to see if the guess is close enough to the position of the treasure if abs(guess_x - treasure_x) <= 50: if abs(guess_y - treasure_y) <= 50: found = True ``` Finally, we need to redraw the screen. 7. For every incorrect guess, we'll draw a red 'X'. If the user found the treasure, we'll draw a treasure chest. The code below should be added *inside* the `while` loop, but *outside of* (after) the `for` loop ```python screen.fill('white') # draw a red 'X' over all locations that were guessed incorrectly for g in guesses: x_rect.center = (g[0], g[1]) screen.blit(red_x, x_rect) # if the treasure was found, draw a treasure chest if found: treasure_rect.center = (g[0], g[1]) screen.blit(treasure, treasure_rect) pygame.display.update() ``` When you play the game, it should look something like... ![treasure-hunt-final](https://hackmd.io/_uploads/HknuYoSkj.gif) **Right on!** You have used Pygame's click event & pixel coordinate system to create a guessing game in which the user has to discover the secret location of the buried treasure! ## Final Thoughts * How could we make the game more challenging? * Could you make the window larger so there is a bigger area to search? * Or make it so that the user must be "closer" to the treasure for their guess to be counted as correct? * What changes would we need to make to the main game loop to allow the user to play again?