Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
9.4k views
in Technique[技术] by (71.8m points)

python - How to ask 20 multiple choice questions on pygame?

I am coding a very basic game on pygame for a project and the main function of the game is to ask 20 questions based on which operator(s) and level the user has picked to play the game at.

I am really struggling with two things:
the first being that the code which I have written does produce 20 questions with the correct operator and level of difficulty however I don't know how to blit these to the screen one at a time each time the user has answered the last one. Currently, the code only shows the last question on the screen.

The second problem I am having is making the multiple-choice buttons for each question. I have a 'Button' class in my game which is the same as the 'Text' class which you can see in the code however it also tracks when the button has been clicked on.
For each question, I need to have 4 instances of the button class on the screen one of which is the correct answer for each question and the other three and random numbers and I need the code to randomize which button is the answer for each question so the answer isn't always the same button.

I also have a stopwatch in my game and none of these can interfere with it.

    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                exit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    pygame.quit()
        gameBG=pygame.image.load('gameBG.bmp').convert()
        screen.blit(gameBG,(0,0))
        questiontxt= Text((325,45),(question), (white), 80)
        questiontxt.draw(screen)
        ticks=pygame.time.get_ticks()
        if timing == False:
            timing=True
            seconds=0
        seconds=int((ticks/1000%60))
        minutes=int((ticks/60000%24))
        out='{minutes:02d}:{seconds:02d}'.format(minutes=minutes, seconds=seconds)
        timefont.render_to(screen,(855,50),out, green)
        pygame.display.update()
        clock.tick(60)

        while j < 38:
            qnum1=int(numlist[j])
            qnum2=int(numlist[j+1])
            if section == 'mixed':
                mixedno=random.randrange(0,4)
                operators=['addition','subtraction','division','multiplication']
                qsection=operators[mixedno]
            else:
                qsection=section

            if qsection == 'addition':
                question=str(qnum1)+ '+'+ str(qnum2)+' = ?'
                answer= qnum1+qnum2
            elif qsection == 'subtraction':
                question=str(qnum1)+ '-'+ str(qnum2)+' = ?'
                answer= qnum1-qnum2
            elif qsection == 'multiplication':
                question=str(qnum1)+ 'x'+ str(qnum2)+' = ?'
                answer= qnum1*qnum2
            else:
                question=str(qnum1)+'÷'+str(qnum2)+' = ?'
                answer= qnum1/qnum2

            print(question)
            questiontxt= Text((325,45),(question), (white), 80)
            questiontxt.draw(screen)
            j=j+2
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Let's start with a new, basic pygame program. I usually start like this:

import pygame

def main():
    screen = pygame.display.set_mode((640, 480))
    clock = pygame.time.Clock()
    dt = 0
    while True:
        events = pygame.event.get()
        for e in events:
            if e.type == pygame.QUIT:
                return
        screen.fill(pygame.Color('lightgrey'))
        pygame.display.flip()
        dt = clock.tick(60)

if __name__ == '__main__':
    main()

Not much to see here. We create a window, paint it grey, handle events and keep track of the delta time (the time each frame takes).


Let's also think a moment of how the game is supposed to work. First, we have a title screen, then we select a difficulty, then we show some questions, and finally, we display the result. We call each of those parts a scene, and we jump from one to another.

Here's a way we could implement these:

import pygame
import pygame.freetype

class SimpleScene:

    FONT = None

    def __init__(self, text, next_scene):
        self.background = pygame.Surface((640, 480))
        self.background.fill(pygame.Color('lightgrey'))

        if text:
            if SimpleScene.FONT == None:
                SimpleScene.FONT = pygame.freetype.SysFont(None, 32)
            SimpleScene.FONT.render_to(self.background, (120, 180), text, pygame.Color('black'))
            SimpleScene.FONT.render_to(self.background, (119, 179), text, pygame.Color('white'))

        self.next_scene = next_scene

    def start(self):
        pass

    def draw(self, screen):
        screen.blit(self.background, (0, 0))

    def update(self, events, dt):
        for event in events:
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    return self.next_scene

def main():
    pygame.init()
    screen = pygame.display.set_mode((640, 480))
    clock = pygame.time.Clock()
    dt = 0
    scenes = {
        'TITLE': SimpleScene('PRESS SPACE TO START', 'GAME'),
        'GAME': SimpleScene('Can you press [SPACE]', 'RESULT'),
        'RESULT': SimpleScene('You win! 100 points!', 'TITLE'),
    }
    scene = scenes['TITLE']
    while True:
        events = pygame.event.get()
        for e in events:
            if e.type == pygame.QUIT:
                return

        next_scene = scene.update(events, dt)
        if next_scene:
            scene = scenes[next_scene]
            scene.start()

        scene.draw(screen)

        pygame.display.flip()
        dt = clock.tick(60)

if __name__ == '__main__':
    main()

enter image description here

We can now transition between the scenes by pressing space. As you can see, each scene is like a miniature game itself; of course we only have the SimpleScene that does nothing but draw a simple text, so let's change that and implement the real game. We also have a game state, so we need to keep track if it, too.

Here's how it could look like:

import pygame
import pygame.freetype
import random

class SimpleScene:

    FONT = None

    def __init__(self, next_scene, *text):
        self.background = pygame.Surface((640, 480))
        self.background.fill(pygame.Color('lightgrey'))

        y = 80
        if text:
            if SimpleScene.FONT == None:
                SimpleScene.FONT = pygame.freetype.SysFont(None, 32)
            for line in text:
                SimpleScene.FONT.render_to(self.background, (120, y), line, pygame.Color('black'))
                SimpleScene.FONT.render_to(self.background, (119, y-1), line, pygame.Color('white'))
                y += 50

        self.next_scene = next_scene
        self.additional_text = None

    def start(self, text):
        self.additional_text = text

    def draw(self, screen):
        screen.blit(self.background, (0, 0))
        if self.additional_text:
            y = 180
            for line in self.additional_text:
                SimpleScene.FONT.render_to(screen, (120, y), line, pygame.Color('black'))
                SimpleScene.FONT.render_to(screen, (119, y-1), line, pygame.Color('white'))
                y += 50

    def update(self, events, dt):
        for event in events:
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    return (self.next_scene, None)

class GameState:
    def __init__(self, difficulty):
        self.difficulty = difficulty
        self.questions = [
            ('How many legs has a cow?', 4),
            ('How many legs has a bird?', 2),
            ('What is 1 x 1 ?', 1)
        ]
        self.current_question = None
        self.right = 0
        self.wrong = 0

    def pop_question(self):
        q = random.choice(self.questions)
        self.questions.remove(q)
        self.current_question = q
        return q

    def answer(self, answer):
        if answer == self.current_question[1]:
            self.right += 1
        else:
            self.wrong += 1

    def get_result(self):
        return f'{self.right} answers correct', f'{self.wrong} answers wrong', '', 'Good!' if self.right > self.wrong else 'You can do better!'

class SettingScene:

    def __init__(self):
        self.background = pygame.Surface((640, 480))
        self.background.fill(pygame.Color('lightgrey'))

        if SimpleScene.FONT == None:
            SimpleScene.FONT = pygame.freetype.SysFont(None, 32)

        SimpleScene.FONT.render_to(self.background, (120, 50), 'Select your difficulty level', pygame.Color('black'))
        SimpleScene.FONT.render_to(self.background, (119, 49), 'Select your difficulty level', pygame.Color('white'))

        self.rects = []
        x = 120
        y = 120
        for n in range(4):
            rect = pygame.Rect(x, y, 80, 80)
            self.rects.append(rect)
            x += 100

    def start(self, *args):
        pass

    def draw(self, screen):
        screen.blit(self.background, (0, 0))
        n = 1
        for rect in self.rects:
            if rect.collidepoint(pygame.mouse.get_pos()):
                pygame.draw.rect(screen, pygame.Color('darkgrey'), rect)
            pygame.draw.rect(screen, pygame.Color('darkgrey'), rect, 5)                
            SimpleScene.FONT.render_to(screen, (rect.x+30, rect.y+30), str(n), pygame.Color('black'))
            SimpleScene.FONT.render_to(screen, (rect.x+29, rect.y+29), str(n), pygame.Color('white'))
            n+=1

    def update(self, events, dt):
        for event in events:
            if event.type == pygame.MOUSEBUTTONDOWN:
                n = 1
                for rect in self.rects:
                    if rect.collidepoint(event.pos):
                        return ('GAME', GameState(n))
                    n += 1

class GameScene:
    def __init__(self):
        if SimpleScene.FONT == None:
            SimpleScene.FONT = pygame.freetype.SysFont(None, 32)

        self.rects = []
        x = 120
        y = 120
        for n in range(4):
            rect = pygame.Rect(x, y, 80, 80)
            self.rects.append(rect)
            x += 100

    def start(self, gamestate):
        self.background = pygame.Surface((640, 480))
        self.background.fill(pygame.Color('lightgrey'))
        self.gamestate = gamestate
        question, answer = gamestate.pop_question()
        SimpleScene.FONT.render_to(self.background, (120, 50), question, pygame.Color('black'))
        SimpleScene.FONT.render_to(self.background, (119, 49), question, pygame.Color('white'))


    def draw(self, screen):
        screen.blit(self.background, (0, 0))
        n = 1
        for rect in self.rects:
            if rect.collidepoint(pygame.mouse.get_pos()):
                pygame.draw.rect(screen, pygame.Color('darkgrey'), rect)
            pygame.draw.rect(screen, pygame.Color('darkgrey'), rect, 5)
            SimpleScene.FONT.render_to(screen, (rect.x+30, rect.y+30), str(n), pygame.Color('black'))
            SimpleScene.FONT.render_to(screen, (rect.x+29, rect.y+29), str(n), pygame.Color('white'))
            n+=1

    def update(self, events, dt):
        for event in events:
            if event.type == pygame.MOUSEBUTTONDOWN:
                n = 1
                for rect in self.rects:
                    if rect.collidepoint(event.pos):
                        self.gamestate.answer(n)
                        if self.gamestate.questions:
                            return ('GAME', self.gamestate)
                        else:
                            return ('RESULT', self.gamestate.get_result())
                    n += 1

def main():
    pygame.init()
    screen = pygame.display.set_mode((640, 480))
    clock = pygame.time.Clock()
    dt = 0
    scenes = {
        'TITLE':    SimpleScene('SETTING', 'Welcome to the quiz', '', '', '', 'press [SPACE] to start'),
        'SETTING':  SettingScene(),
        'GAME':     GameScene(),
        'RESULT':   SimpleScene('TITLE', 'Here is your result:'),
    }
    scene = scenes['TITLE']
    while True:
        events = pygame.event.get()
        for e in events:
            if e.type == pygame.QUIT:
                return

        result = scene.update(events, dt)
        if result:
            next_scene, state = result
            if next_scene:
                scene = scenes[next_scene]
                scene.start(state)

        scene.draw(screen)

        pygame.display.flip()
        dt = clock.tick(60)

if __name__ == '__main__':
    main()

enter image description here

Of course it's not 100% finished, but it should give you an idea on how to structure your game to get what you want.

As you can see, each scene handles its own aspect of the game, and the game scene jumps to itself while passing the game state. To add a timer, you can simply store the current game time (e.g. pygame.time.get_ticks()) in the game state the first time its start function is called, and calculate the remaining time in the update function of the scene. If the timer runs out, jump to a scene that displays the result.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...