How do I get a picture to appear when I press a key?

Viewed 42

I'm trying to make Tic-Tac-Toe in Python, but I've run into a problem. I can't figure out how to let the players make a selection on the board. I have been trying to get an "X" in the top left square of the board. The "X" and "O" are both images, not text. The background is also an image. Every time I run my code, The window opens, the board and title are both their, but when I press the "1" key, nothing happens. Does anyone know what to do about this? I'm not sure which parts of my code are wrong so here's all of it:

import pygame
import os
import sys
from pygame.locals import *
pygame.init()

WIDTH, HEIGHT = 500, 500
P_WIDTH, P_HEIGHT = 100, 130
WIN = pygame.display.set_mode((WIDTH, HEIGHT))

main_font = pygame.font.SysFont("Comic sans", 40)
#color = (255, 255, 255)
#light_color = (170, 170, 170)
#dark_color = (100, 100, 100)


def redraw_window():
    BG = pygame.transform.scale(pygame.image.load(os.path.join("Board.jpg")).convert_alpha(), (WIDTH, HEIGHT))

    title_label1 = main_font.render("Tic", True, (0, 0, 0))
    title_label2 = main_font.render("Tac", True, (0, 0, 0))
    title_label3 = main_font.render("Toe", True, (0, 0, 0))

    Player_1 = pygame.transform.scale(pygame.image.load(os.path.join("X.jpg")).convert_alpha(), (P_WIDTH, P_HEIGHT))

    WIN.blit(BG, (0, 0))
    WIN.blit(title_label1, (10, 10))
    WIN.blit(title_label2, (10, 40))
    WIN.blit(title_label3, (10, 70))

    pygame.display.update()

def main():

    while True:
        redraw_window()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                exit(0)
            elif event.type == pygame.locals.KEYDOWN:
                if event.key == K_1:
                    WIN.blit(Player_1, (72, 35))


main()

Also, if anyone sees something that could be improved, could you point that as well?

1 Answers

So you're getting an error, it's best if you include this detail in your question.

pygame 2.0.1 (SDL 2.0.14, Python 3.6.9)
Hello from the pygame community. https://www.pygame.org/contribute.html
Traceback (most recent call last):
  File "./bad_noughts.py", line 47, in <module>
    main()
  File "./bad_noughts.py", line 44, in main
    WIN.blit(Player_1, (72, 35))
NameError: name 'Player_1' is not defined

Inside the main() function, Python doesn't know about Player_1 because it's "local" to the function redraw_window() - that means that only the redraw_window() function knows about Player_1.

So how do you fix this?

The quickest and simplest way is to move the definition of Player_1 to the main program, giving it a "global" scope.

This fixes the error, but the 'X' still doesn't appear... why? The next problem is that you're only drawing the 'X' when the 1 event is received. Then (almost immediately) the background is re-painted again, covering it up. It's only on-screen for a split-second.

One way to fix this is to separate the state of the board with the drawing of the board.

Let's introduce a new list-of-lists board_state, which has 3 rows of 3 cells, one for each area of your nought-and-crosses board:

board_state = [ [ None, None, None ], 
                [ None, None, None ],
                [ None, None, None ] ]

Using this, we can set and check the layout of the moves on the board by saying something like: board[0][0] (top-left) and board[2][2] (bottom-right). One important thing you just realised, is that lists are "zero-indexed", meaning the very first element is at number-0 (not 1)

So now when the player makes a move, we can just set the board state:

        elif event.type == pygame.locals.KEYDOWN:
            if event.key == K_1:
                board_state[0][0] = 'x'  # top-left

Now we need to adjust the window drawing function to look at the board-state, and draw what's needed:

def redraw_window():
    global board_state

    WIN.blit(BG, (0, 0))
    WIN.blit(title_label1, (10, 10))
    WIN.blit(title_label2, (10, 40))
    WIN.blit(title_label3, (10, 70))

    x_cursor = 0  # position on the board
    y_cursor = 0
    for row in ( 0, 1, 2 ):
        x_cursor = 0
        for col in ( 0, 1, 2 ):
            # if there's a move on the board, draw it
            if ( board_state[row][col] == 'x' ):
                WIN.blit( Player_1, ( x_cursor, y_cursor ) )
            elif ( board_state[row][col] == 'o' ):
                WIN.blit( Player_2, ( x_cursor, y_cursor ) )
            x_cursor += WIDTH//3                             # move the cursor across
        y_cursor += HEIGHT//3                                # move the cursor down

    pygame.display.update()

So the loops iterate through the rows and columns of the board_state. Using the (0, 1, 2) in the loop isn't best-practice, but I'm trying to keep it simple. As the loops move through the board, across then down, we also maintain a cursor-position where any player-move should be drawn.

If the board contains an 'x' or 'o', the correct bitmap is drawn. Except this code is a bit rough, so the co-ordinates will probably be a bit off, but I don't have your bitmaps, so that's the best we can do.

Reference Code:

import pygame
import os
import sys
from pygame.locals import *
pygame.init()

WIDTH, HEIGHT = 500, 500
P_WIDTH, P_HEIGHT = 100, 130
WIN = pygame.display.set_mode((WIDTH, HEIGHT))

main_font = pygame.font.SysFont("Comic sans", 40)
#color = (255, 255, 255)
#light_color = (170, 170, 170)
#dark_color = (100, 100, 100)

BG = pygame.transform.scale(pygame.image.load(os.path.join("ox_board.png")).convert_alpha(), (WIDTH, HEIGHT))
Player_1 = pygame.transform.scale(pygame.image.load(os.path.join("ox_x.png")).convert_alpha(), (P_WIDTH, P_HEIGHT))
Player_2 = pygame.transform.scale(pygame.image.load(os.path.join("ox_o.png")).convert_alpha(), (P_WIDTH, P_HEIGHT))
title_label1 = main_font.render("Tic", True, (0, 0, 0))
title_label2 = main_font.render("Tac", True, (0, 0, 0))
title_label3 = main_font.render("Toe", True, (0, 0, 0))

# moves (or not) placed on the board
# None -> no move yet
# Or 'x', 'o'
board_state = [ [ None, None, None ],
            [ None, None, None ],
            [ None, None, None ] ]


def redraw_window():
    global board_state

    WIN.blit(BG, (0, 0))
    WIN.blit(title_label1, (10, 10))
    WIN.blit(title_label2, (10, 40))
    WIN.blit(title_label3, (10, 70))

    x_cursor = 0  # position on the board
    y_cursor = 0
    for row in ( 0, 1, 2 ):
    x_cursor = 0
    for col in ( 0, 1, 2 ):
        # if there's a move on the board, draw it
        if ( board_state[row][col] == 'x' ):
            WIN.blit( Player_1, ( x_cursor, y_cursor ) )
        elif ( board_state[row][col] == 'o' ):
            WIN.blit( Player_2, ( x_cursor, y_cursor ) )
        x_cursor += WIDTH//3                             # move the cursor across
    y_cursor += HEIGHT//3                                # move the cursor down

    pygame.display.update()


def main():

    while True:
    redraw_window()

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit(0)
        elif event.type == pygame.locals.KEYDOWN:
            if event.key == K_1:
                board_state[0][0] = 'x'


main()

The board_state also allows you to check whether a move has been made in the cell already. Assume the player pushes 5 (centre cell), you can just check board_state[1][1] != None to test if a move was already placed in that cell.

Related