[pygame] help changing display when clicking a letter?

[pygame] help changing display when clicking a letter?

Postby dboxall123 » Wed Jul 31, 2013 12:19 pm

Hello all. I'm hoping someone here can help me, because this is doing my head in now. I'm making a hangman game in pygame and up until last night thought everything was going well. I have generated a random word from a list, created a list of letters for the user to click on, displayed a number of dashes equal to the amount of letters in the secret word, and checked if the letter clicked on is in the word. now I'm stuck. If the letter is in the word, I want it to display the letter above the dashes. I've sort of done this, but the trouble is the letter just flashes up for a split second and then goes again. Here is the code, I have highlight the relevant parts in # signs.

Code: Select all
import pygame,random,sys
from pygame.locals import *

fps=30
screen_width=600
screen_height=400
box_size=25
gap_size=5
board_width=13
board_height=2

x_margin=int((screen_width-(board_width*(box_size+gap_size)))/2)
y_margin=int((screen_height-(box_size*3)))

#------Colours------#

white=(255,255,255)
black=(0,0,0)
red=(255,0,0)
green=(0,255,0)
blue=(0,0,255)

word_list='baboon badger bear beaver camel clam cobra cougar coyote crow deer donkey duck eagle ferret frog goat goose hawk lion lizard llama mole monkey moose mouse mule newt otter panda parrot pigeon platypus python rabbit raven rhino salmon seal shark sheep skunk sloth snake spider stork swan tiger toad trout turkey turtle weasel whale wolf wombat zebra'.split()


def main():
    global fps,screen,word_list
    pygame.init()
    clock=pygame.time.Clock()

    screen=pygame.display.set_mode([screen_width,screen_height])
    pygame.display.set_caption('Hangman 2')

    mouse_x=0
    mouse_y=0
    word=random_word(word_list).upper()
    print(word) 
   
    while True:
        mouse_clicked=False

        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == MOUSEMOTION:
                mouse_x,mouse_y=event.pos
            elif event.type ==  MOUSEBUTTONUP:
                mouse_x,mouse_y=event.pos
                mouse_clicked=True

        ##################################################
        ###################################################
        box_x,box_y=get_box_at_pixel(mouse_x,mouse_y)
        if box_x != None and box_y != None:
            if mouse_clicked:
                #HAD TO DO BACKWARDS. WORK ON DIFFERENT ALGORITM LATER
                letter=get_letter_in_box(box_y,box_x)
                if is_in_word(letter,word):
                   show_letter(letter,word)   
         ###################################################
         ##################################################   
        screen.fill(black)
        display_letters()
       
        dashes(word)
       
        clock.tick(10)       
   
        pygame.display.flip()

       
def random_word(word_list):
    word=word_list[random.randint(0,len(word_list)-1)]
    return word


def left_top_coords_of_box(box_x,box_y):
    #Create coordinates for letter boxes
    left=box_x*(box_size+gap_size)+x_margin
    top=box_y*(box_size+gap_size)+y_margin
    return (left,top)


def get_box_at_pixel(x,y):
    #Get pixel coords for each square in grid
    letter=chr(65)
    for box_x in range(board_width):
        for box_y in range(board_height):
            left,top=left_top_coords_of_box(box_x,box_y)
            box_rect=pygame.Rect(left,top,box_size,box_size)
            if box_rect.collidepoint(x,y):
                return (box_x,box_y)
    return (None,None)


def get_letter_in_box(y,x):
    #Returns the letter that the user clicked on
    #HAD TO DO BACKWARDS. WILL WORK ON DIFFERENT ALGORITHM LATER
    letter=65
    grid=[]
    for box_y in range(board_height):
        grid.append([])
        for box_x in range(board_width):
            grid[box_y].append(chr(letter))
            letter+=1
    return(grid[y][x])
           
           
def display_letters():
    #shows available letters in a grid
    #HAD TO DO LETTERS BACKWARD. WORK ON ALGORITHM LATER
    font=pygame.font.Font(None,30)
    letter=65

    for box_y in range(board_height):
       
        for box_x in range(board_width):
            left,top=left_top_coords_of_box(box_x,box_y)
            pygame.draw.rect(screen,white,(left,top,box_size,box_size))

            text=font.render(str(chr(letter)),True,red)
            text_rect=text.get_rect()
            text_x=left+(box_size/2)-(text_rect.width/2)
            text_y=top+(box_size/2)-(text_rect.height/2.5)
           
            screen.blit(text,[text_x,text_y])
            letter+=1


def is_in_word(letter,word):
    if letter in word:
        return True
    else:
        return False


def dashes(word):
    #Show dashes for word, same amount as letters in word
    dash_length=(box_size+gap_size)*len(word)
    pos=(screen_width/2)-(dash_length/2)
    pos_list=[]
    for dash in range(len(word)):
        pygame.draw.rect(screen,white,(pos,y_margin-box_size,box_size,2))
        pos_list.append(pos)
        pos=pos+(box_size+gap_size)
    return pos_list

########################################################
########################################################
def show_letter(letter,word):
    #Should blit the image to the screen, but only shows up
    #for a split second. I think that I somehow need to create
    #of 'update()'function aand add the letter and pos in a list,
    #but I really can't work out how!
    pos=dashes(word)
    font=pygame.font.Font(None,30)
    text_list=[]
    pos_list=[]
    for i in range(len(word)):
        if letter ==  word[i]:
         
            text=font.render(str(letter),True,red)
            text_rect=text.get_rect()
            text_x=pos[i]
            text_y=screen_height-(4*box_size+text_rect.height)
            screen.blit(text,(text_x,text_y))
           
    pygame.display.flip()
###################################################   
###################################################   
       
   
           
main() 


I can see why this doesn't work. I think I need to somehow ass the position and text into a list, and then update the screen with the list. The problem is that I cant work out how. Can anyone help me out here? I've been messing around with this for hours, and it's probably very simple but I just can't see it. I would relly appreciate some help.

Thanks
Dan
Last edited by Yoriz on Wed Jul 31, 2013 6:06 pm, edited 1 time in total.
Reason: changed title
dboxall123
 
Posts: 121
Joined: Fri Jul 12, 2013 5:28 pm

Re: Displaying letters when clicked on

Postby Mekire » Wed Jul 31, 2013 1:34 pm

There is a simple solution that will get it working; but the real problem is much deeper. You have some major structural issues here.

The quick dirty solution is to take these lines outside of your while loop:
Code: Select all
screen.fill(black)
display_letters()
dashes(word)
pygame.display.flip()

If you replace your main function with the following it will work:
Code: Select all
def main():
    global fps,screen,word_list
    pygame.init()
    clock=pygame.time.Clock()

    screen=pygame.display.set_mode([screen_width,screen_height])
    pygame.display.set_caption('Hangman 2')

    mouse_x=0
    mouse_y=0
    word=random_word(word_list).upper()
    print(word)

    screen.fill(black)
    dashes(word)
    display_letters()
    pygame.display.flip()
    while True:
        mouse_clicked=False

        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == MOUSEMOTION:
                mouse_x,mouse_y=event.pos
            elif event.type ==  MOUSEBUTTONUP:
                mouse_x,mouse_y=event.pos
                mouse_clicked=True

        ##################################################
        ###################################################
        box_x,box_y=get_box_at_pixel(mouse_x,mouse_y)
        if box_x != None and box_y != None:
            if mouse_clicked:
                #HAD TO DO BACKWARDS. WORK ON DIFFERENT ALGORITM LATER
                letter=get_letter_in_box(box_y,box_x)
                if is_in_word(letter,word):
                   show_letter(letter,word)
         ###################################################
         ##################################################
        clock.tick(60)

If you would like to see the proper way to organize a program like this I can show you, but it will require classes.
-Mek
User avatar
Mekire
 
Posts: 1120
Joined: Thu Feb 07, 2013 11:33 pm
Location: Asakusa, Japan

Re: Displaying letters when clicked on

Postby dboxall123 » Wed Jul 31, 2013 1:45 pm

Yeh mate that would be great. Thanks a lot! I've read up about classes but this being my first ever game, I'm wan't really going to try using any classes yet. But yeh, if you don't mind i would love to see how to do it properly
dboxall123
 
Posts: 121
Joined: Fri Jul 12, 2013 5:28 pm

Re: Displaying letters when clicked on

Postby dboxall123 » Wed Jul 31, 2013 4:58 pm

I've been trying to do this for nearly a month now (not this game, just programming in general). I started learning python about a month ago and pygame about two weeks ago. How long does this sort of thing take to get the hang of? If, after a month of doing this, I still can't work out little problems like this, and my code is as bad as you say, is it really worth me carrying on? Does it take longer than this to produce a half decent code? Just curious really
dboxall123
 
Posts: 121
Joined: Fri Jul 12, 2013 5:28 pm

Re: Displaying letters when clicked on

Postby metulburr » Wed Jul 31, 2013 5:54 pm

How long does this sort of thing take to get the hang of? If, after a month of doing this, I still can't work out little problems like this, and my code is as bad as you say, is it really worth me carrying on? Does it take longer than this to produce a half decent code? Just curious really


not to discourage you, but years and years. practice makes perfect. The first month in programming is like a baby taking its first steps.
New Users, Read This
OS Ubuntu 14.04, Arch Linux, Gentoo, Windows 7/8
https://github.com/metulburr
steam
User avatar
metulburr
 
Posts: 1560
Joined: Thu Feb 07, 2013 4:47 pm
Location: Elmira, NY

Re: Displaying letters when clicked on

Postby Yoriz » Wed Jul 31, 2013 6:01 pm

I've been playing around with python for a few years now (don't know exactly how long) but i don't consider myself to be that great at it, what tends to happen is you learn at the time the part of the language you need to overcome the current problem, then move onto the next problem and start over and feel like a noob again, if some time passes before you come across a similar problem to one you have overcome before, you have relearn some of it again.
Programming is such a vast thing that you are constantly learning, i think it would be very hard to retain all the ins and outs of a whole language at anytime.
I dont think a month is considered a very long time in the programming world, it might get you some basics but the more you want to do the more complicated it gets.
You just have to keep hammering away at it.
New Users, Read This
Join the #python-forum IRC channel on irc.freenode.net!
Image
User avatar
Yoriz
 
Posts: 1154
Joined: Fri Feb 08, 2013 1:35 am
Location: UK

Re: [pygame] help changing display when clicking a letter?

Postby Mekire » Thu Aug 01, 2013 12:43 am

I didn't mean to discourage you. The fact that you are trying to use functions that each have a narrow specific purpose shows great progress in the right direction. The structural issues are things that, without seeing done differently, you really have no way of knowing how to fix. You need to see a clean game loop in order to know what one should look like. You need to be taught that the screen should only be updated in one place after every frame. These aren't things people can really just figure out on there own (endless bad code examples on the web don't help either).

Anyway, take a look (comes with my standard caveat that, it could probably still be cleaner):
Code: Select all
import os
import sys
import random
import pygame

#Color constants
WHITE = (255,255,255)
BLACK = (0,0,0)
RED = (255,0,0)
GREEN = (0,255,0)
BLUE = (0,0,255)
YELLOW = (255,255,0)

#Allignment constants
LETTERS_MARGIN = (107,330)
CELL_SIZE = (25,25)
CELL_GAP = (5,5)
DASH_GAP = 10
DASH_Y = 300

WORD_LIST = ('baboon badger bear beaver camel clam cobra cougar coyote crow '
             'deer donkey duck eagle ferret frog goat goose hawk lion lizard '
             'llama mole monkey moose mouse mule newt otter panda parrot '
             'pigeon platypus python rabbit raven rhino salmon seal shark '
             'sheep skunk sloth snake spider stork swan tiger toad trout '
             'turkey turtle weasel whale wolf wombat zebra').split()


class Control(object):
    def __init__(self):
        os.environ["SDL_VIDEO_CENTERED"] = '1'
        pygame.init()
        self.screen = pygame.display.set_mode((600,400))
        self.screen_rect = self.screen.get_rect()
        self.clock = pygame.time.Clock()
        self.fps = 60.0
        self.done = False
        self.font = pygame.font.Font(None,30)
        self.font_huge = pygame.font.Font(None,120)
        self.letter_dict = self.make_letters()
        self.new_game()

    def new_game(self):
        self.word = random.choice(WORD_LIST).upper()
        self.dashes,self.dash_rect = self.make_dashes()
        self.solution = ["_"]*len(self.word)
        self.win = False

    def make_letters(self):
        letter_surfaces = {}
        letters_per_row = 13
        spacing = (CELL_SIZE[0]+CELL_GAP[0],CELL_SIZE[1]+CELL_GAP[1])
        for i,char in enumerate("ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
            box,box_rect = self.make_box_letter(char,WHITE,RED)
            col,row = divmod(i,letters_per_row)
            box_rect.x = LETTERS_MARGIN[0]+spacing[0]*row
            box_rect.y = LETTERS_MARGIN[1]+spacing[1]*col
            letter_surfaces[char] = [box,box_rect]
        return letter_surfaces

    def make_box_letter(self,letter,bg_color,font_color):
        box = pygame.Surface(CELL_SIZE).convert()
        box_rect = box.get_rect()
        box.fill(bg_color)
        letter = self.font.render(letter,True,font_color)
        letter_rect = letter.get_rect(center=(box_rect.centerx,14))
        box.blit(letter,letter_rect)
        return [box,box_rect]

    def make_dashes(self):
        fence,post = len(self.word),len(self.word)-1
        dashes = pygame.Surface((CELL_SIZE[0]*fence+DASH_GAP*post,CELL_SIZE[1]))
        spacer = CELL_SIZE[0]+DASH_GAP
        for i in range(fence):
            dashes.fill(RED,(i*spacer,CELL_SIZE[1]-5,CELL_SIZE[0],5))
        dash_rect = dashes.get_rect(center=(self.screen_rect.centerx,DASH_Y))
        return [dashes,dash_rect]

    def guess(self,mouse):
        for chosen in self.letter_dict:
            if self.letter_dict[chosen][1].collidepoint(mouse):
                if chosen in self.word:
                    self.on_success(chosen)
                else:
                    self.on_failure(chosen)

    def on_success(self,chosen):
        for i,letter in enumerate(self.word):
            if letter == chosen:
                self.solution[i] = letter
                self.add_letter(letter,i)
        success = self.make_box_letter(chosen,BLUE,YELLOW)[0]
        self.letter_dict[chosen][0] = success

    def on_failure(self,chosen):
        failure = self.make_box_letter(chosen,RED,WHITE)[0]
        self.letter_dict[chosen][0] = failure

    def add_letter(self,letter,index):
        render_letter = self.font.render(letter,True,WHITE)
        letter_rect = render_letter.get_rect()
        spacer = CELL_SIZE[0]+DASH_GAP
        letter_rect.center = (12+spacer*index,10)
        self.dashes.blit(render_letter,letter_rect)

    def update(self):
        self.screen.fill(BLACK)
        for letter,letter_rect in self.letter_dict.values():
            self.screen.blit(letter,letter_rect)
        self.screen.blit(self.dashes,self.dash_rect)

    def event_loop(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.done = True
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1:
                    self.guess(event.pos)

    def main_loop(self):
        while not self.done:
            self.event_loop()
            self.update()
            pygame.display.update()
            self.clock.tick(self.fps)


if __name__ == "__main__":
    run_it = Control()
    run_it.main_loop()
    pygame.quit()
    sys.exit()

I didn't institute much more than you did as I wanted to leave most of that to you,
-Mek
User avatar
Mekire
 
Posts: 1120
Joined: Thu Feb 07, 2013 11:33 pm
Location: Asakusa, Japan

Re: [pygame] help changing display when clicking a letter?

Postby dboxall123 » Thu Aug 01, 2013 9:35 am

Ahh, good. Maybe i'm not quite as dumb as i've been thinking I am then. Thanks for the reassurance guys. And Mek, thanks for writing that code out, that's really cool of you to give me an example of how things should be done. Now Ive got to do some serious reading to do, because I don't really understand much of what's going on in it. Anyway, I ended up having to use the dirty version that you first posted. I've now finished all of the game logic, and just have to finish it off with some hangman pics. I would use your version with the class, but I'm so close to finishing now! I think I just want to get it done for the minute, just so that I've got something to show my mates, then I'll (try to) write it out properly using the example that you've given. Seiing as how I've already worked out all the logic, hopefully it won't be too dificult to do it properly.Thanks again!
Actually, while I'm here I may as well ask. I was thinking of jut making the hangman pics in MS Paint, then loading them and putting them in a 'hangman_pic_ list'. That way, I can just set up a counter 'incorrect guesses' and blit 'hangman_pic_list[incorrect_guesses]. Does that seem a resonable way to do it? To me, it seems easier than using pygame to draw the pics. And if I did do that would it go in the same class as everything else, or does it get it's own seperate class? Dont' thnk it needs a seperate class does it?
Thanks again!
Dan
dboxall123
 
Posts: 121
Joined: Fri Jul 12, 2013 5:28 pm

Re: [pygame] help changing display when clicking a letter?

Postby Mekire » Thu Aug 01, 2013 2:01 pm

That sounds completely reasonable. There is no reason to draw everything with fills and pygame.draw functions. As it would not be much more than a list of images you could easily keep it in the same class. You could also make a simple class to do it that had the list of frames, a rect, and a current frame attribute. That might be a good exercise for you.

-Mek
User avatar
Mekire
 
Posts: 1120
Joined: Thu Feb 07, 2013 11:33 pm
Location: Asakusa, Japan


Return to Game Development

Who is online

Users browsing this forum: No registered users and 2 guests