Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Pygame Help
#1
Hello! I have been running into a issue with drawing rectangles, blitting text, querying a DB etc etc inside the game loop. The rectangles or text appear on screen for on fps (I think) and then disappears. The sql call,. calls every fps etc etc. I have tried to figure out how to do this outside the loop without success. I'm sure I am just missing something stupid. This is an example of a Yahtzee game I am "trying" to quickly throw together to learn. Inside the SetTableDice function it renders the "dice" but only for a quick second. Any insights?

import sys
import os
import time
import random
from collections import Counter
import pygame

pygame.init()

screen_width = 800
screen_height = 600
first_table_dice_x_position = 150;
screen = pygame.display.set_mode((screen_width, screen_height))
smallfont = pygame.font.SysFont('Corbel',20)
clock = pygame.time.Clock()
game_over = False

class Dice():
	DiceOnTable = {'d1': 1, 'd2': 2, 'd3': 3, 'd4': 4, 'd5': 6}
	turn = False

	def __inti__(self):
		pass

	def RollDice(self):
		# self.DiceOnTable.clear()

		for k in self.DiceOnTable:
			dice = random.randint(1, 6)
			self.DiceOnTable[k] = dice
			self.turn = True

		

		# sort might be needed in the future
		# self.DiceOnTable = {k: v for k, v in sorted(self.DiceOnTable.items(), key=lambda item: item[1])}

		print(self.DiceOnTable)
		# while self.turn:
		# d1 = smallfont.render(str(self.DiceOnTable['d1']), True , (0,0,0))
		# wtf = smallfont.render('WTF??', True , (0,0,0))
		# screen.blit(wtf, (100,100))
		# screen.blit(d1, (first_table_dice_x_position,250))


	def SetTableDice(self):
		d1 = self.DiceOnTable['d1'];

		pygame.draw.rect(screen,(255,255,255),[first_table_dice_x_position,250,35,40])

		d1 = smallfont.render(str(d1), True , (0,0,0))
		screen.blit(d1, (first_table_dice_x_position,250))

		# while self.turn:
		# 	time.sleep()

		# pygame.display.update()

		# time.sleep(2)

		# GameLoop()

	def CheckDice(self):
		for v in range(6):
			self.CheckForTop(v)
		
		self.CheckForChance()
		self.CheckForMultiples()
		self.CheckForLargeStraight()
		self.CheckForSmallStraight()

	def CheckForTop(self, num):
		NumOf = []
		for v in self.DiceOnTable:
			if str(self.DiceOnTable[v]) == str(num):
				NumOf.append(1)
		HowMany = str(len(NumOf))
		print('You have ' + HowMany + ' ' + str(num))

	def CheckForMultiples(self):
		counts = Counter(self.DiceOnTable.values())
		TwoOfAKind = False
		ThreeOfAKind = False

		for v in counts:
			if counts[v] == 5:
				print("You have a yahtzee with " + str(v))
			if counts[v] == 4:
				print("You have a four of a kind with " + str(v))
			if counts[v] == 2:
				TwoOfAKind = True
				print("You have a two of a kind with " + str(v))
			if counts[v] == 3:
				ThreeOfAKind = True
				print("You have a three of a kind with " + str(v))

		if TwoOfAKind and ThreeOfAKind:
				print("You have a full house")

	def CheckForLargeStraight(self):
		#the below could be done better - needs refactoring
		# self.DiceOnTable = {k: v for k, v in sorted(self.DiceOnTable.items(), key=lambda item: item[1])}

		print(self.DiceOnTable)

		if ((1 in self.DiceOnTable.values() and
				2 in self.DiceOnTable.values() and
				3 in self.DiceOnTable.values() and
				4 in self.DiceOnTable.values() and
				5 in self.DiceOnTable.values()) or
				(2 in self.DiceOnTable.values() and
				3 in self.DiceOnTable.values() and
				4 in self.DiceOnTable.values() and
				5 in self.DiceOnTable.values() and
				6 in self.DiceOnTable.values())):
			print('You have a large straight!')

	def CheckForSmallStraight(self):
		#the below could be done better - needs refactoring
		# self.DiceOnTable = {k: v for k, v in sorted(self.DiceOnTable.items(), key=lambda item: item[1])}

		if ((1 in self.DiceOnTable.values() and
				2 in self.DiceOnTable.values() and
				3 in self.DiceOnTable.values() and
				4 in self.DiceOnTable.values()) or
				(2 in self.DiceOnTable.values() and
				3 in self.DiceOnTable.values() and
				4 in self.DiceOnTable.values() and
				5 in self.DiceOnTable.values()) or
				(3 in self.DiceOnTable.values() and
				4 in self.DiceOnTable.values() and
				5 in self.DiceOnTable.values() and
				6 in self.DiceOnTable.values())):
			print('You have a small straight!')

	def CheckForChance(self):
		total = 0

		for v in self.DiceOnTable:
			total += self.DiceOnTable[v]

		print("Chance = " + str(total))

	def CreateTableDice(self):
		# for k in self.DiceOnTable:
		pass

def GameLoop():
	while not game_over:
		screen.fill((0,102,0))
		pygame.draw.rect(screen,(255,255,255),[530,20,250,550])
		text = smallfont.render('Roll Dice' , True , (255,255,255))
		pygame.draw.rect(screen,(0,0,0),[200,450,140,40])
		screen.blit(text , (240,460))
		# pygame.draw.rect(screen,(255,255,255),[first_table_dice_x_position,250,35,40])
		# pygame.draw.rect(screen,(255,255,255),[first_table_dice_x_position+40,250,35,40])
		# pygame.draw.rect(screen,(255,255,255),[first_table_dice_x_position+80,250,35,40])
		# pygame.draw.rect(screen,(255,255,255),[first_table_dice_x_position+120,250,35,40])
		# pygame.draw.rect(screen,(255,255,255),[first_table_dice_x_position+160,250,35,40])
		# pygame.draw.rect(screen,(255,255,255),[first_table_dice_x_position+200,250,35,40])
		mouse = pygame.mouse.get_pos()

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

			if event.type == pygame.MOUSEBUTTONDOWN:
	              
				if 200 <= mouse[0] <= 200+140 and 450 <= mouse[1] <= 450+40:
					dice = Dice()
					dice.RollDice()
					dice.CheckDice()
					dice.SetTableDice()

			if event.type == pygame.KEYDOWN:

				if event.key == pygame.K_ESCAPE:
					sys.exit()



		clock.tick(30)

		pygame.display.update()


GameLoop()

pygame.quit()
quit()
Reply
#2
So a little more insight. The rect is doing exactly what I told it to do. On click of the button it shows up,. but immediately goes away. How would I "pause" the game waiting on user input? Like the user clicking the dice to select them?
Reply
#3
On line 171 you reset dice to a new iteration of Dice each time the "roll dice' button is pressed. It might be best to do this only once, perhaps at the beginning of Gameloop like this:

def GameLoop():
    dice = Dice()
    while not game_over:
The problem that you're having is because dice.SetTableDice () only updates the dice when "roll dice" is clicked. I recommend that you move it down with pygame.display.update () like this:

        clock.tick(30)
        dice.SetTableDice () 
        pygame.display.update()
Reply
#4
Quote:The problem that you're having is because dice.SetTableDice () only updates the dice when "roll dice" is clicked. I recommend that you move it down with pygame.display.update () like this:

Ok at first I didn't understand this and dismissed it. But then I tried it,. and I'm having a hard time wrapping my head around,. but it does put me on a new track. So thank you for that! It seems I have a lot to learn about this game loop.
Reply
#5
So all I have to do now is update the variable's name. Furthermore, the variables must be distinct from the class name.
If that is the only method to fix it, it will not work. I've tried changing several names, but each time I get the same problem. Maybe I'm mistaken.
But, Gribouillis, thank you very much for your time. Refer to super mario bros.
Reply
#6
Pressing the "roll dice' button should probably only be pressed once but I pressed it too many times, it must have been my mistake that led to the same error as you. I will try again following the instructions of BashBedlam
slope ball
Reply
#7
Edit: Just realize this is an old thread.
Maybe this will give you some idea. Haven't done score card yet.
import pygame
import random

from itertools import count
from pygame.sprite import Group, Sprite

# Interface
class State:
    def __init__(self, engine):
        self.engine = engine

    def on_draw(self, surface): pass
    def on_event(self, event): pass
    def on_update(self, delta, ticks): pass

# Engine
class DisplayEngine:
    def __init__(self, caption, width, height, flags=0):
        # Basic Pygame Setup
        pygame.display.set_caption(caption)
        self.surface = pygame.display.set_mode((width, height), flags)
        self.rect = self.surface.get_rect()
        self.clock = pygame.time.Clock()
        self.running = True
        self.delta = 0
        self.fps = 60

        # Handle what state is active.
        self._state = State(self)
        self.next_state = None

    # Main Loop
    def run(self, state=None):
        if state:
            self._state = state

        while self.running:
            # State Changer
            if self.next_state:
                self._state = self.next_state
                self.next_state = None

            # Event Loop
            for event in pygame.event.get():
                # Just send the events to state to handle
                self._state.on_event(event)

            ticks = pygame.time.get_ticks()
            self._state.on_draw(self.surface)
            self._state.on_update(self.delta, ticks)
            pygame.display.flip()
            self.delta = self.clock.tick(self.fps)

    def quit(self):
        self.running = False

class SimpleSprite(Sprite):
    def __init__(self, image):
        super().__init__()
        self.image = image
        self.rect = image.get_rect()

class Callback:
    def __init__(self, callback, user_data=None):
        self.parent = None
        self.callback = callback
        self.user_data = user_data

    def link(self, parent):
        self.parent = parent
        return self

    def call(self):
        self.callback(self)

class Pen:
    def __init__(self, font, color):
        self.font = font
        self.color = color

    def render(self, text):
        return self.font.render(text, 1, self.color)

    def write(self, text):
        return SimpleSprite(self.render(text))

class ButtonImages:
    def __init__(self, normal, hover, pushed):
        self.normal = normal
        self.hover = hover
        self.pushed = pushed

class ButtonGroup:
    def __init__(self):
        self.buttons = Group()
        self.text = Group()

    def add(self, button):
        self.buttons.add(button)
        self.text.add(button.tsprite)

    def draw(self, surface):
        self.buttons.draw(surface)
        self.text.draw(surface)

class Button(Sprite):
    def __init__(self, images, pen, text, position, callback):
        super().__init__()
        self.pen = pen
        self.images = images
        self.image = images.normal
        self.rect = self.images.normal.get_rect(topleft=position)

        self.is_mouse_over = False
        self.is_pushed = False
        self.callback = callback.link(self)
        self.update_text(text)

    def mouse_over(self, event):
        self.is_mouse_over = self.rect.collidepoint(event.pos)
        self.update_image()

    def click(self, event):
        self.mouse_over(event)
        if self.is_mouse_over:
            #self.is_pushed = False
            self.callback.call()

    def update_image(self):
        if self.is_pushed:
            self.image = self.images.pushed
        elif self.is_mouse_over:
            self.image = self.images.hover
        else:
            self.image = self.images.normal

    def update_text(self, text):
        self.tsprite = self.pen.write(text)
        self.tsprite.rect.center = self.rect.center

# Store images. Because you want to create/load, convert, and/or size once.
class Images:
    def __init__(self):
        self.dice = self.create_dice(40, 'snow', 'black', 3)
        self.hdice = self.create_dice(40, 'skyblue', 'black', 3)
        self.sdice = self.create_dice(40, 'lightgreen', 'black', 3)

        self.button_images = self.create_button((100, 30), 'blue', 'dodgerblue', 'navy')

    def create_dice(self, size, color, dot_color, dot_size):
        surface = pygame.Surface((size, size))
        surface.fill(color)
        rect = surface.get_rect()
        inner = rect.inflate(-rect.centerx, -rect.centery)
        images = []

        for i in range(1, 7):
            s = surface.copy()
            if i in [1, 3, 5]:
                pygame.draw.circle(s, dot_color, rect.center, dot_size)

            if i != 1: # 2-6
                pygame.draw.circle(s, dot_color, inner.topleft, dot_size)
                pygame.draw.circle(s, dot_color, inner.bottomright, dot_size)

            if i > 3:
                pygame.draw.circle(s, dot_color, inner.topright, dot_size)
                pygame.draw.circle(s, dot_color, inner.bottomleft, dot_size)

            if i == 6:
                pygame.draw.circle(s, dot_color, inner.midleft, dot_size)
                pygame.draw.circle(s, dot_color, inner.midright, dot_size)

            images.append(s)
        return images

    def create_button(self, size, color, hcolor, pcolor):
        surface = pygame.Surface(size)
        surface.fill(color)
        hsurface = surface.copy()
        hsurface.fill(hcolor)
        psurface = surface.copy()
        psurface.fill(pcolor)
        return ButtonImages(surface, hsurface, psurface)

class Die(Sprite):
    def __init__(self, images, position):
        super().__init__()
        # These are just reference
        self.images = images
        self.rect = images[0][0].get_rect(topleft=position)
        self.is_mouse_over = False
        self.is_selected = False
        self.roll()

    def roll(self):
        self.value = random.randint(1, 6)
        self.is_selected = False
        self.update_image()

    def mouse_over(self, mpos):
        self.is_mouse_over = self.rect.collidepoint(mpos)
        self.update_image()

    def selected(self):
        if self.is_mouse_over:
            self.is_selected = not self.is_selected
            self.update_image()

    def update_image(self):
        if self.is_selected:
            self.image = self.images[2][self.value - 1]
        elif self.is_mouse_over:
            self.image = self.images[1][self.value - 1]
        else:
            self.image = self.images[0][self.value - 1]

class DisplayLines:
    def __init__(self, pen, position):
        self.position = position
        self.lines = []
        self.pen = pen

    def add(self, text):
        self.lines.append(self.pen.render(text))

    def clear(self):
        self.lines = []

    def draw(self, surface):
        y = count(self.position[1], self.pen.font.get_linesize() + 2)
        for line in self.lines:
            surface.blit(line, (self.position[0], next(y)))

class GameState(State):
    def __init__(self, engine):
        super().__init__(engine)
        self.dice_space = 50
        self.dice_fromedgex = 30
        self.dice_fromedgey = 30

        self.images = Images()
        self.roll_button = self.create_roll_button()
        self.bgroup = ButtonGroup()
        self.dice = Group()
        self.set_up()

    def create_roll_button(self):
        pen = Pen(pygame.font.Font(None, 28), 'snow')
        pos = self.dice_space * 5 - 100 + self.dice_fromedgex, 80
        callback = Callback(self.push_rolled)
        return Button(self.images.button_images, pen, "Roll", pos, callback)

    def set_up(self):
        images = self.images.dice, self.images.hdice, self.images.sdice
        for i in range(5):
            pos = self.dice_space * i + self.dice_fromedgex, self.dice_fromedgey
            self.dice.add(Die(images, pos))

        self.bgroup.add(self.roll_button)
        pos = self.engine.rect.centerx, 10
        pen = Pen(pygame.font.Font(None, 22), 'lawngreen')
        self.lines = DisplayLines(pen, pos)
        self.get_data()

    def push_rolled(self, callback):
        for d in self.dice:
            if d.is_selected:
                d.roll()

        self.get_data()

    def get_data(self):
        self.lines.clear()
        values = [d.value for d in self.dice]
        set_v = set(values)
        if len(set_v) == 1:
            self.lines.add("Yahtzee")

        if len(set_v) == 2:
            for v in set_v:
                if values.count(v) == 4:
                    self.lines.add("4 of a kind")
                elif values.count(v) == 3:
                    self.lines.add("Fullhouse")

        if len(set_v) == 3:
            data = [values.count(i) for i in set_v]
            if 3 in data:
                self.lines.add("3 of a kind")
            else:
                self.lines.add("two pair")

        if len(set_v) == 4:
            data = list(set_v)
            data.sort()
            if [1, 2, 3, 4] == data or [2, 3, 4, 5] == data or [3, 4, 5, 6] == data:
                self.lines.add("small straight")

        if len(set_v) == 5:
            data = list(set_v)
            data.sort()
            if [1, 2, 3, 4, 5] == data or [2, 3, 4, 5, 6] == data:
                self.lines.add("large straight")

            if ([1, 2, 3, 4] == data[:-1] or
                [2, 3, 4, 5] == data[:-1] or
                [2, 3, 4, 5] == data[1:] or
                [3, 4, 5, 6] == data[1:]):

                self.lines.add("small straight")

    # Override. From state interface
    def on_draw(self, surface):
        surface.fill('gray10')
        self.dice.draw(surface)
        self.bgroup.draw(surface)
        self.lines.draw(surface)

    # Override. From state interface
    def on_event(self, event):
        if event.type == pygame.QUIT:
            self.engine.quit()
        elif event.type == pygame.MOUSEMOTION:
            for d in self.dice:
                d.mouse_over(event.pos)

            for b in self.bgroup.buttons:
                b.mouse_over(event)

        elif event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:
                self.roll_button.click(event)
                for d in self.dice:
                    d.selected()

if __name__ == '__main__':
    pygame.init()
    engine = DisplayEngine("Dice", 800, 600)
    game = GameState(engine)
    engine.run(game)
    pygame.quit()
99 percent of computer problems exists between chair and keyboard.
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020