Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

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

pygame - How to make multiple choice games on python

I am making a game which includes multiple choice questions. It will include the user choosing one of the three options to answer the question. The options will change for every new question.

As I am new to python, I am confused and unsure on how to start. I am not able to make clickable text boxes. I am making this game on pygame.

In a pre-answered question: How to ask 20 multiple choice questions on pygame? I would like some advice on how to create something like this. The code for this is in the link.

Below is an image on what @sloth has successfully managed to create.

enter image description here

So for my question, instead of the numbers as the options, my game will have different answers that change for every new question. Below is my draft of the question which I planned on Word. I am going to try to code this image in pygame.

enter image description here

If you have any tips or can show me how to begin, it'll be very appreciated. Thank you.


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

1 Reply

0 votes
by (71.8m points)

To the game scene class, you could add something like choices.

 self.choices = ['x', '-', '*', '+'] 

I have never used pygame.freetype before but seems like the rendering text is done using

SimpleScene.FONT.render_to

So you can iterate over the choices and draw them

SimpleScene.FONT.render_to(screen, (rect.x+29, rect.y+29), str(self.choices[n]), 
pygame.Color('white'))

To have different options for different questions, you can expand the self.choices list to be a 2D list. Then iterate through that list and render them depending on what the current question is. Also looks like sloths original code does not adjust the size so rendering words would not work. So I have slightly modified the way boxes are rendered to fit words. Also I have added a comment called #CHANGE to show the changes that were made to the previous one.

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

        #CHANGE
        self.questions = [
            (" 4 _ 6 = 10 ?", 4),
            ("Which one is a fruit?", 2)
        ]
        self.current_question = None

        #CHANGE
        self.question_index = 0
        
        self.right = 0
        self.wrong = 0


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

        #CHANGE
        self.question_index += 1
        
        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 = []

        #CHANGE
        for n in range(4):
            rect = pygame.Rect(50, (n * 70) + 100, 500, 50)
            self.rects.append(rect)


    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)

            #CHANGE
            SimpleScene.FONT.render_to(screen, (rect.x+30, rect.y+15), str(n), pygame.Color('black'))
            SimpleScene.FONT.render_to(screen, (rect.x+29, rect.y+14), 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 = []
        
        for n in range(4):
            rect = pygame.Rect(50, (n * 70) + 100, 500, 50)
            self.rects.append(rect)

        #CHANGE
        self.choices = [['x', '-', '*', '+'], ["whatever", "apple", "whatever", "whatever"]] 

    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 = 0
        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)

            #CHANGE
            for i in range(len(self.choices)):
                if self.gamestate.question_index ==  i + 1:
                    SimpleScene.FONT.render_to(screen, (rect.x+30, rect.y+20), str(self.choices[i][n]), pygame.Color('black'))
                    SimpleScene.FONT.render_to(screen, (rect.x+29, rect.y+19), str(self.choices[i][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()

Bug was that the questions were being picked at random so the indices didn't align. Since the original code was not designed to have different choices, the class handling answer does not have access to the random variable controlling which question gets picked. So there isn't really any way to have random questions without sacrificing encaptulation. So I suggest just remove the randomness and pop the question directly i.e

Replace

q = random.choice(self.questions)

with

q = self.questions[0]

Its in GameState class in pop_questions method.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...