diff --git a/Trial/Questions/Question 1/Answer 1 - Correct Answer.txt b/Trial/Questions/Question 1/Answer 1 - Correct Answer.txt new file mode 100644 index 0000000..3fda163 --- /dev/null +++ b/Trial/Questions/Question 1/Answer 1 - Correct Answer.txt @@ -0,0 +1 @@ +Sample Answer 1 \ No newline at end of file diff --git a/Trial/Questions/Question 1/Answer 2.txt b/Trial/Questions/Question 1/Answer 2.txt new file mode 100644 index 0000000..0cf68ca --- /dev/null +++ b/Trial/Questions/Question 1/Answer 2.txt @@ -0,0 +1 @@ +Sample Answer 2 \ No newline at end of file diff --git a/Trial/Questions/Question 1/Answer 3.txt b/Trial/Questions/Question 1/Answer 3.txt new file mode 100644 index 0000000..db78b31 --- /dev/null +++ b/Trial/Questions/Question 1/Answer 3.txt @@ -0,0 +1 @@ +Sample Answer 3 \ No newline at end of file diff --git a/Trial/Questions/Question 1/Answer 4.txt b/Trial/Questions/Question 1/Answer 4.txt new file mode 100644 index 0000000..9adabe8 --- /dev/null +++ b/Trial/Questions/Question 1/Answer 4.txt @@ -0,0 +1 @@ +Sample Answer 4 \ No newline at end of file diff --git a/Trial/Questions/Question 1/Question.html b/Trial/Questions/Question 1/Question.html new file mode 100644 index 0000000..c7be1b6 --- /dev/null +++ b/Trial/Questions/Question 1/Question.html @@ -0,0 +1,11 @@ + +
++ Sample Question +
+ + \ No newline at end of file diff --git a/Trial/Questions/Question 2/Answer 1.txt b/Trial/Questions/Question 2/Answer 1.txt new file mode 100644 index 0000000..3fda163 --- /dev/null +++ b/Trial/Questions/Question 2/Answer 1.txt @@ -0,0 +1 @@ +Sample Answer 1 \ No newline at end of file diff --git a/Trial/Questions/Question 2/Answer 2 - Correct Answer.txt b/Trial/Questions/Question 2/Answer 2 - Correct Answer.txt new file mode 100644 index 0000000..0cf68ca --- /dev/null +++ b/Trial/Questions/Question 2/Answer 2 - Correct Answer.txt @@ -0,0 +1 @@ +Sample Answer 2 \ No newline at end of file diff --git a/Trial/Questions/Question 2/Answer 3.txt b/Trial/Questions/Question 2/Answer 3.txt new file mode 100644 index 0000000..db78b31 --- /dev/null +++ b/Trial/Questions/Question 2/Answer 3.txt @@ -0,0 +1 @@ +Sample Answer 3 \ No newline at end of file diff --git a/Trial/Questions/Question 2/Answer 4.txt b/Trial/Questions/Question 2/Answer 4.txt new file mode 100644 index 0000000..9adabe8 --- /dev/null +++ b/Trial/Questions/Question 2/Answer 4.txt @@ -0,0 +1 @@ +Sample Answer 4 \ No newline at end of file diff --git a/Trial/Questions/Question 2/Question.html b/Trial/Questions/Question 2/Question.html new file mode 100644 index 0000000..c7be1b6 --- /dev/null +++ b/Trial/Questions/Question 2/Question.html @@ -0,0 +1,11 @@ + + ++ Sample Question +
+ + \ No newline at end of file diff --git a/Trial/Readme.txt b/Trial/Readme.txt new file mode 100644 index 0000000..3d9e432 --- /dev/null +++ b/Trial/Readme.txt @@ -0,0 +1,31 @@ +Folder organisation and question structure: + +1. A top-level folder called "Questions". +2. "Questions" will have subfolders for each question, each subfolder will be named as "Question #". ('#' denotes a number) +3. Each subfolder will have 5 files: + 3.1. 1 for the question, called "Question.html" and formatted as HTML (Just put text inside the tags of the sample documents) + 3.2. 4 for the answer options, each denoted as "Answer #.html", where # ranges from 1 to 4. Answers are also HTML-formatted. + 3.3. One answer file will be named as "Answer # - Correct Answer.html", which marks it as the correct answer. No special modifications to the document itself is required. + +The Python program will display these in a window, with one label for the question, 4 buttons for the answers, and 1 button for pass, which discards the question from the list of available questions. +Once a question has been used, it can not be used again. + +Relation to the Hex game: + +Whenever a player clicks on an empty hex cell, they get shown the question window. The window remains open until dismissed using one of the available buttons. The question displayed is chosen randomly. + +(Use screen mirroring to show the on-screen player content on the projector.) + +Screen extension will be used between the players, as each of their Hex games will show up in separate windows on different screens, mediated by the "SpaceDesk" software. + +Questions will show up on both player screens at the same time, and also the game windows will be duplicates of each other; so any of the player screens can be mirrored to the projector. + +A correct answer will mark the selected cell with the player's color, and dismiss the question window, and pass the turn on to the next player. + +A pass will dismiss the question window and pass the turn on to the next player. + +A wrong answer will dismiss the question window, unmark (make blank) a random cell marked with the player's color, and pass the turn on to the next player. + +All the dismissals of a question will consume it, implying that it will not be displayed again. + +Considering all the above facts, I guess we should have some 50+ small questions handy, to be on the safe side ( there are 36 hex cells on a 6x6 Hex grid, which is the one we'll use). \ No newline at end of file diff --git a/hex.py b/hex.py index 63ba224..cae257c 100644 --- a/hex.py +++ b/hex.py @@ -3,13 +3,12 @@ import time from threading import Thread import pygame -from pygame.locals import USEREVENT # constants RUN = True -LONG = 40 +LENGTH = 40 RECTANGLE_WIDTH_SCALE = 1.421 -SCREEN_WIDTH_SCALE = 54 +SCREEN_WIDTH_SCALE = 16 RECTANGLE_HEIGHT_SCALE = 1 SCREEN_HEIGHT_SCALE = 3 YELLOW = (255, 231, 0) @@ -19,16 +18,17 @@ BORDER_COLOR = (100,100,100) LEFT = 1 RIGHT = 3 -TICK_EVENT = USEREVENT+1 NUMBER_OF_HEXAGONS = 6 +INITIAL_BLUE_SCORE = 0 +INITIAL_YELLOW_SCORE = 0 player = random.choice((BLUE, YELLOW)) -def check_player(lp, rp): +def check_player(): global player - if rp: + if player == BLUE: player = YELLOW - elif lp: + elif player == YELLOW: player = BLUE @@ -41,12 +41,146 @@ def __init__(self): def render(self, text): return self.font.render(text, False, BLACK) +import sys +import os +import random +from PyQt5.QtWidgets import (QWidget, QGridLayout, QPushButton, QApplication) +from PyQt5.QtCore import (QCoreApplication, QUrl, QByteArray) +from PyQt5.QtWebEngineWidgets import QWebEngineView -class Hexagon: +CORRECT_ANSWER_POINTS = +15 +INCORRECT_ANSWER_POINTS = -15 +PASS_POINTS = 0 +#INVALID_POINTS = -sys.maxsize - 1 + +class QuestionDisplay(QWidget): + + def __init__(self, question, questions, id): + super().__init__() + self.reinit(question, questions, id) + + def find_answer_by_label(self, label): + for answer in self.question.answers: + if answer.content == label: + return answer + return None + + def show(self): + self.show() + + def clear(layout): + for i in reversed(range(layout.count())): + widgetToRemove = layout.itemAt( i ).widget() + # remove it from the layout list + layout.removeWidget( widgetToRemove ) + # remove it from the gui + widgetToRemove.setParent(None) + + def reinit(self, question, questions, id): + clear(self.grid) + self.score = None + self.question = question + self.questions = questions + self.id = id + self.initUI() + + def input_handler(self, score = None): + sender = self.sender() + answer_points = PASS_POINTS + if not (sender.text() == "Pass"): + answer = self.find_answer_by_label(sender.text()) + answer_points = CORRECT_ANSWER_POINTS if answer.is_correct_answer else INCORRECT_ANSWER_POINTS + if score is None: + self.score = answer_points + else: + self.score = score + self.questions.remove_question(self.question) + self.close() + + def launch(argv, question, questions, id = 0): + app = QApplication(argv) + display = QuestionDisplay(question, questions, id) + app.exec_() + return display + + def initUI(self): + + webview = QWebEngineView(self) + webview.setHtml(self.question.content) + webview.show() + + self.grid = QGridLayout() + grid.setSpacing(10) + + passbtn = QPushButton("Pass", self) + passbtn.clicked.connect(lambda: self.input_handler(None)) + passbtn.resize(passbtn.sizeHint()) + + answer_buttons = [] + for answer in self.question.answers: + answer_button = QPushButton(answer.content, self) + answer_button.clicked.connect(lambda: self.input_handler(None)) + answer_button.resize(answer_button.sizeHint()) + answer_buttons.append(answer_button) + + self.grid.addWidget(webview, 1, 1) + self.grid.addWidget(passbtn, 1, 2) + + for idx, answer_button in enumerate(answer_buttons): + self.grid.addWidget(answer_button, idx+3, 1) + + self.setLayout(self.grid) + + self.setGeometry(300, 300, 1280, 720) + self.setWindowTitle("Question {0}".format(self.id)) + +class Questions(object): + def __init__(self, questions): + self.questions = questions + + def get_question(self): + random.shuffle(self.questions) + return self.questions[0] + + def remove_question(self, question): + self.questions.remove(question) + + def load_questions(quiz_folder): + questions = [] + for folder_path in os.listdir(quiz_folder): + questions.append(Question.load_question(os.path.join(quiz_folder, folder_path))) + return Questions(questions) + +class Question(object): + def __init__(self, content, answers): + self.content = content + self.answers = answers + + def load_question(question_folder): + content = None + answers = [] + for file_path in os.listdir(question_folder): + canonical_path = os.path.join(question_folder, file_path) + if "Question" in file_path: + with open(canonical_path, "r") as qfile: + content = qfile.read() + elif "Answer" in file_path: + answers.append(Answer.load_answer(canonical_path)) + return Question(content, answers) + +class Answer(object): + def __init__(self, content, is_correct_answer = False): + self.content = content + self.is_correct_answer = is_correct_answer + + def load_answer(answer_file): + with open(answer_file, "r") as content: + return Answer(content.read(), "correct answer" in answer_file.lower()) if "Answer" in answer_file else None +class Hexagon: def __init__(self, display, x, y, id, blue_start, blue_end, yellow_start, yellow_end): self.display = display - self.d = LONG + self.d = LENGTH self.color = WHITE self.marked = False self.id = id @@ -57,7 +191,9 @@ def __init__(self, display, x, y, id, blue_start, blue_end, yellow_start, yellow # coordinates of centre self.x = x self.y = y - # self.rendered_id = Font().render(str(id)) + a = chr((self.id % NUMBER_OF_HEXAGONS) + 65) + d = (self.id // NUMBER_OF_HEXAGONS) + 1 + self.rendered_id = Font().render(str(a)+str(d)) self.rect = pygame.Rect(self.x - self.d/2 - 4, self.y - self.d, self.d + 8, self.d*2) def draw(self): @@ -69,13 +205,13 @@ def draw(self): (self.x - self.d/2, self.y + self.d)] pygame.draw.polygon(self.display, self.color, pl) pygame.draw.polygon(self.display, BORDER_COLOR, pl, 3) - # self.display.blit(self.rendered_id,(self.x,self.y)) + self.display.blit(self.rendered_id,(self.x-self.d/4,self.y-self.d/4)) - def update(self, x, y, lp, rp): + def update(self, x, y, player): c = self.rect.collidepoint(x, y) - if c and (lp or rp) and (not self.marked) and self.color == WHITE: - check_player(lp, rp) - self.mark() + if c and player and (not self.marked) and self.color == WHITE: + check_player() + #self.mark() return True return False @@ -92,40 +228,30 @@ class Table: def __init__(self, display, number_of_hexagons, xoffset, mark_action): self.display = display self.number_of_hexagons = number_of_hexagons - self.rect_width = LONG*number_of_hexagons*RECTANGLE_WIDTH_SCALE - self.rect_height = (LONG*number_of_hexagons+1)*RECTANGLE_HEIGHT_SCALE + self.rect_width = LENGTH*number_of_hexagons*RECTANGLE_WIDTH_SCALE + self.rect_height = (LENGTH*number_of_hexagons+1)*RECTANGLE_HEIGHT_SCALE self.xoffset = 2*self.rect_width + xoffset self.id_limit = 0 - # start suspension of a player from playing once he gets a question until the question is answered correctly or the timer expires - self.l_player_suspend, self.r_player_suspend = False, False self.mark_action = mark_action self.start() def start(self): self.hexagons = {} self.id = 0 - dx = LONG - dy = LONG*self.number_of_hexagons + dx = LENGTH + dy = LENGTH*self.number_of_hexagons # Table for i in range(self.number_of_hexagons): for e in range(self.number_of_hexagons): - x = dx + LONG*(e + i)*1.5 - y = dy + LONG*(i - e) - self.id += 1 + x = dx + LENGTH*(e + i)*1.5 + y = dy + LENGTH*(i - e) blue_start, blue_end, yellow_start, yellow_end = self.edge(self.id) self.hexagons[self.id] = Hexagon(self.display, x, y, self.id, blue_start, blue_end, yellow_start, yellow_end) - self.id_limit = self.id - for i in range(self.number_of_hexagons): - for e in range(self.number_of_hexagons): - x = dx + LONG*(e + i)*1.5 + self.xoffset - y = dy + LONG*(i - e) self.id += 1 - blue_start, blue_end, yellow_start, yellow_end = self.edge(self.id) - self.hexagons[self.id] = Hexagon(self.display, x, y, self.id, blue_start, blue_end, yellow_start, yellow_end) + self.id_limit = self.id def edge(self, id): - if id > self.id_limit: - id = id - self.id_limit + id += 1 # corner < if id == 1: return True, False, True, False @@ -159,49 +285,17 @@ def draw(self): pygame.draw.rect(self.display, BLUE, (self.rect_width, 0, self.rect_width, self.rect_height)) pygame.draw.rect(self.display, BLUE, (0, self.rect_height, self.rect_width, self.rect_height)) pygame.draw.rect(self.display, YELLOW, (self.rect_width, self.rect_height, self.rect_width, self.rect_height)) - - pygame.draw.rect(self.display, YELLOW, (self.xoffset, 0, self.rect_width, self.rect_height)) - pygame.draw.rect(self.display, BLUE, (self.rect_width + self.xoffset, 0, self.rect_width, self.rect_height)) - pygame.draw.rect(self.display, BLUE, (self.xoffset, self.rect_height, self.rect_width, self.rect_height)) - pygame.draw.rect(self.display, YELLOW, (self.rect_width + self.xoffset, self.rect_height, self.rect_width, self.rect_height)) x, y = pygame.mouse.get_pos() - # event = pygame.event.wait() - lclick, rclick = False, False - # if(event.type == pygame.MOUSEBUTTONDOWN): - # lclick, rclick = (event.button == LEFT), (event.button == RIGHT) - if x > self.xoffset and (not self.r_player_suspend): - rclick = True - elif x <= self.xoffset and (not self.l_player_suspend): - lclick = True + clicked = pygame.event.wait().type == pygame.MOUSEBUTTONDOWN won = None - for (id, hl) in self.hexagons.items(): - if id > self.id_limit: - hr = self.hexagons[id - self.id_limit] - else: - hr = self.hexagons[self.id_limit + id] - rl = hl.update(x, y, lclick, rclick) - rr = hr.update(x, y, lclick, rclick) - if rl or rr: - if rl: - #print("Processing rl event") - self.mark_action(hl) - hr.color = hl.color - hr.marked = hl.marked - won = self.solve(hl.id) - if rr: - #print("Processing rr event") - self.mark_action(hr) - hl.color = hr.color - hl.marked = hr.marked - won = self.solve(hr.id) - hl.draw() - hr.draw() + for (id, h) in self.hexagons.items(): + if h.update(x, y, clicked): + if self.mark_action(h): + h.mark() + won = self.solve(h.id) + h.draw() return won - - def suspend_player(self, l_player, r_player): - self.l_player_suspend = l_player - self.r_player_suspend = r_player - + def solve(self, id): seen = [] color = self.hexagons[id].color @@ -246,158 +340,49 @@ def end(self, chain, color): if self.hexagons[c].yellow_end: return True return False - -class Timer: - def __init__(self, duration, step, name, id, update_action, expiry_action, start_time): - self.duration = duration - self.step = step - self.name = name - self.id = id - self.update_action = update_action - self.expiry_action = expiry_action - self.start_time = start_time - - def format_time(self, time_s): - return "{:03d}:{:02d}".format(time_s//60, time_s%60) - - def __str__(self): - return "{0} ({1}) = {2}".format(self.name, self.id, self.format_time(self.duration)) - - def update(self, current_time): - if self.duration <= 0: - self.expiry_action(self) - return False - elif (abs(current_time - self.start_time) >= self.step): - start_time = current_time - self.update_action(self) - self.duration -= self.step - return True - return True - -class Question: - def __init__(self, question = None, answers = None, answer = None): - self.question = question - self.answers = answers - self.answer = answer -class display: +class Display: def __init__(self): + self.blue_score = INITIAL_BLUE_SCORE + self.yellow_score = INITIAL_YELLOW_SCORE pygame.init() - pygame.display.set_caption("Pravega Hex") + pygame.display.set_caption("Pravega Bio Quiz - Hex") icon = pygame.image.load("dna-icon.png") pygame.display.set_icon(icon) self.clock = pygame.time.Clock() self.number_of_hexagons = NUMBER_OF_HEXAGONS # os.environ["SDL_VIDEO_CENTERED"] = "1" - self.width = LONG*SCREEN_WIDTH_SCALE - self.height = LONG*self.number_of_hexagons*SCREEN_HEIGHT_SCALE + self.width = LENGTH*SCREEN_WIDTH_SCALE + self.height = LENGTH*self.number_of_hexagons*SCREEN_HEIGHT_SCALE self.display = pygame.display.set_mode((self.width, self.height)) self.won = True + self.questions = Questions.load_questions("./Trial/Questions/") self.color = None - self.xoffset = 560 + self.xoffset = 0 self.table = Table(self.display, self.number_of_hexagons, self.xoffset, self.mark_action) - self.timer_text_loc = (200, 2*self.table.rect_height+((self.height-2*self.table.rect_height)//2)) + #self.timer_text_loc = (200, 2*self.table.rect_height+((self.height-2*self.table.rect_height)//2)) self.font = Font() self.text_width = 0 - self.timers = [] - self.question_time = 120 - self.time_delta = 1 - self.time_wait = 50 - self.displayed_question = None self.display.fill(WHITE) - self.main() - - def add_timer(self, timer): - self.timers.append(timer) - self.set_timer() - - def remove_timer(self, timer = None, timer_name = None, timer_id = None): - if timer: - self.timers.remove(timer) - else: - timers_bk = self.timers.copy() - for timer in timers_bk: - if timer.name == timer_name or timer.id == timer_id: - timers_bk.remove(timer) - self.timers = timers_bk - - def set_timer(self): - pygame.time.set_timer(TICK_EVENT, self.min_sleep()*1000) - - def min_sleep(self): - return min([self.timers[i].step for i in range(len(self.timers))]) + self.main() def mark_action(self, hexagon): - if False: # disabled - # Do something with a player mark action - show a question - if hexagon.id > self.table.id_limit: - if not self.handle_r_question(): - hexagon.unmark() - else: - if not self.handle_l_question(): - hexagon.unmark() - - def show_question(self): - (question, answers, answer) = self.get_question() - self.display_question(question, answers) - return answer == self.receive_answer() - - def display_question(self, question, answers): - # TODO: display question and answers - self.displayed_question = Question()# store a reference to the display so that it can be removed - - def get_question(self): - return (None, None, None) - - def receive_answer(self): - event = pygame.event.wait() - # synchronously block until some event related to the question occurs - return None - - def remove_displayed_question(self): - if self.displayed_question: - self.displayed_question = None - # TODO: remove displayed question - pass - - def handle_r_question(self): - self.add_timer(Timer(self.question_time, self.time_delta, "R QUESTION ", -1, self.handle_timer_event, self.default_add_r_suspend_timer, time.clock())) - correct_answer = self.show_question() #This should take time - if self.displayed_question is None: - return False - if not correct_answer: - self.add_r_suspend_timer(10, self.time_delta) - self.remove_displayed_question() - self.remove_timer(timer_name = "R QUESTION ") - #pygame.time.wait(self.time_wait) - return correct_answer - - def handle_l_question(self): - self.add_timer(Timer(self.question_time, self.time_delta, "L QUESTION ", -2, self.handle_timer_event, self.default_add_l_suspend_timer, time.clock())) - correct_answer = self.show_question() #This should take time - if self.displayed_question is None: - return False - if not correct_answer: - self.add_r_suspend_timer(10, self.time_delta) - self.remove_displayed_question() - self.remove_timer(timer_name = "L QUESTION ") - #pygame.time.wait(self.time_wait) - return correct_answer + """question_display = QuestionDisplay.launch(sys.argv, self.questions.get_question(), self.questions, hexagon.id) + while question_display.score is None: + pygame.time.delay(1) + score = question_display.score + self.questions = question_display.questions + print(score) + if player == BLUE: + self.blue_score += score + elif player == YELLOW: + self.yellow_score += score + #question_display.kill() + return score > 0""" + print(hexagon.id) + return True - def default_add_r_suspend_timer(self): - self.remove_displayed_question() - self.add_r_suspend_timer(10, self.time_delta) - def default_add_l_suspend_timer(self): - self.remove_displayed_question() - self.add_l_suspend_timer(10, self.time_delta) - - def min(sequence): - low = sequence[0] - for i in sequence: - if i < low: - low = i - return low def draw_text(self, raw_text, text_loc, offset): tmp_size = self.font.font.size(raw_text) @@ -405,29 +390,13 @@ def draw_text(self, raw_text, text_loc, offset): self.text_width = tmp_size[0] size = (self.text_width, tmp_size[1]) loc = (text_loc[0], text_loc[1]+offset*(self.font.font.get_linesize())) - timer_text = self.font.render(raw_text) + text = self.font.render(raw_text) self.display.fill(WHITE, (loc[0], loc[1], size[0], size[1])) - self.display.blit(timer_text, loc) + self.display.blit(text, loc) pygame.display.update() - def handle_timer_event(self, timer): - self.draw_text(str(timer), self.timer_text_loc, timer.id - 1) - - def handle_timer_expiry_L_player_resume(self, timer): - self.handle_timer_expiry(lambda: self.table.suspend_player(False, self.table.r_player_suspend), timer) - - def handle_timer_expiry_R_player_resume(self, timer): - self.handle_timer_expiry(lambda: self.table.suspend_player(self.table.l_player_suspend, False), timer) - - def handle_timer_expiry(self, action, timer): - action() - self.draw_text("Timer {0} ({1}) Expired".format(timer.name, timer.id), self.timer_text_loc, timer.id - 1) - def main(self): - global RUN - self.timer_count = 0 - while RUN: - self.display.fill(WHITE, (0, 0, 4*self.table.rect_width+self.xoffset, 2*(self.table.rect_height+1))) + while self.update(): # display pygame.event.pump() if not self.won: @@ -438,35 +407,10 @@ def main(self): else: self.winner() pygame.display.update() - if not self.update(): - RUN = False - break - self.clock.tick(10) + self.clock.tick(1) pygame.display.quit() pygame.quit() quit() - - def add_l_suspend_timer(self, duration, step, wait = 0): - toadd = True - for timer in self.timers: - if timer.name == "L SUSPENSION: ": - toadd = False - if toadd: - self.timer_count += 1 - self.table.suspend_player(True, self.table.r_player_suspend) - self.add_timer(Timer(duration, step, "L SUSPENSION: ", self.timer_count, self.handle_timer_event, self.handle_timer_expiry_L_player_resume, time.clock())) - #pygame.time.wait(wait) - - def add_r_suspend_timer(self, duration, step, wait = 0): - toadd = True - for timer in self.timers: - if timer.name == "R SUSPENSION: ": - toadd = False - if toadd: - self.timer_count += 1 - self.table.suspend_player(self.table.l_player_suspend, True) - self.add_timer(Timer(duration, step, "R SUSPENSION: ", self.timer_count, self.handle_timer_event, self.handle_timer_expiry_R_player_resume, time.clock())) - #pygame.time.wait(wait) def winner(self): if self.color == BLUE: @@ -475,7 +419,7 @@ def winner(self): color = "yellow" if self.color: - r1 = self.font.render(color + " player won!") + r1 = self.font.render("{0} player won with {1} points!".format(color, self.blue_score if self.color == BLUE else self.yellow_score)) r2 = self.font.render("[Enter/Return] Start") r3 = self.font.render("[Esc] Exit") @@ -486,25 +430,14 @@ def winner(self): def update(self): for event in pygame.event.get(): - if event.type == TICK_EVENT: - timers_bk = self.timers.copy() - for timer in timers_bk: - if not timer.update(time.clock()): - timers_bk.remove(timer) - self.timer_count -= 1 - self.timers = timers_bk if event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: return False elif event.key == pygame.K_RETURN: self.won = False self.table.start() - if event.key == pygame.K_a: - self.add_l_suspend_timer(10, self.time_delta) - if event.key == pygame.K_d: - self.add_r_suspend_timer(10, self.time_delta) if event.type == pygame.QUIT: return False return True -display() \ No newline at end of file +Display() \ No newline at end of file diff --git a/qtdemo.py b/qtdemo.py new file mode 100644 index 0000000..e1d6390 --- /dev/null +++ b/qtdemo.py @@ -0,0 +1,177 @@ +import sys +import os +import random +from threading import Thread +from PyQt5.QtWidgets import (QWidget, QGridLayout, QPushButton, QApplication) +from PyQt5.QtCore import (QCoreApplication, QTimer) +from PyQt5.QtWebEngineWidgets import QWebEngineView + +CORRECT_ANSWER_POINTS = +15 +INCORRECT_ANSWER_POINTS = -15 +PASS_POINTS = 0 +display = None +#INVALID_POINTS = -sys.maxsize - 1 + +class QuestionDisplay(QWidget): + + def __init__(self, question, questions, id): + super().__init__() + self.pending_actions = [] + timer = QTimer() + timer.start(500) # You may change this if you wish. + timer.timeout.connect(self.handle_pending_actions) # Let the interpreter run each 500 ms. + self.grid = None + self.reinit(question, questions, id) + + def handle_pending_actions(self): + for action in self.pending_actions: + action() + + def find_answer_by_label(self, label): + for answer in self.question.answers: + if answer.content == label: + return answer + return None + + def clear(self, layout): + for i in reversed(range(layout.count())): + widgetToRemove = layout.itemAt(i).widget() + layout.removeWidget(widgetToRemove) + widgetToRemove.setParent(None) + + def kill(self): + self.hide() + #self.close() + #QCoreApplication.exit(0) + + + def reinit(self, question, questions, id = 0): + #if self.isVisible(): + # self.close() + # print("closed") + #if self.grid is not None: + # self.clear(self.grid) + self.score = None + self.question = question + self.questions = questions + self.id = id + self.initUI() + self.show() + + def input_handler(self, score = None): + sender = self.sender() + answer_points = PASS_POINTS + if not (sender.text() == "Pass"): + answer = self.find_answer_by_label(sender.text()) + answer_points = CORRECT_ANSWER_POINTS if answer.is_correct_answer else INCORRECT_ANSWER_POINTS + if score is None: + self.score = answer_points + else: + self.score = score + self.questions.remove_question(self.question) + self.kill() + + def launch(argv, question, questions, id = 0): + global display + Thread(target=lambda: QuestionDisplay.show_app(argv, question, questions, id), daemon=True).start() + print("return") + #return display + + def show_app(argv, question, questions, id = 0): + global display + print("core init") + app = QApplication(argv) + print("app init") + display = QuestionDisplay(question, questions, id) + print("app start") + app.exec_() + + def initUI(self): + + webview = QWebEngineView(self) + webview.setHtml(self.question.content) + webview.show() + + self.grid = QGridLayout() + self.grid.setSpacing(10) + + passbtn = QPushButton("Pass", self) + passbtn.clicked.connect(lambda: self.input_handler(None)) + passbtn.resize(passbtn.sizeHint()) + + answer_buttons = [] + for answer in self.question.answers: + answer_button = QPushButton(answer.content, self) + answer_button.clicked.connect(lambda: self.input_handler(None)) + answer_button.resize(answer_button.sizeHint()) + answer_buttons.append(answer_button) + + self.grid.addWidget(webview, 1, 1) + self.grid.addWidget(passbtn, 1, 2) + + for idx, answer_button in enumerate(answer_buttons): + self.grid.addWidget(answer_button, idx+3, 1) + + self.setLayout(self.grid) + + self.setGeometry(300, 300, 1280, 720) + self.setWindowTitle("Question {0}".format(self.id)) + +class Answer(object): + def __init__(self, content, is_correct_answer = False): + self.content = content + self.is_correct_answer = is_correct_answer + + def load_answer(answer_file): + with open(answer_file, "r") as content: + return Answer(content.read(), "correct answer" in answer_file.lower()) if "Answer" in answer_file else None + +class Question(object): + def __init__(self, content, answers): + self.content = content + self.answers = answers + + def load_question(question_folder): + content = None + answers = [] + for file_path in os.listdir(question_folder): + canonical_path = os.path.join(question_folder, file_path) + if "Question" in file_path: + with open(canonical_path, "r") as qfile: + content = qfile.read() + elif "Answer" in file_path: + answers.append(Answer.load_answer(canonical_path)) + return Question(content, answers) + +class Questions(object): + def __init__(self, questions): + self.questions = questions + + def get_question(self): + random.shuffle(self.questions) + return self.questions[0] + + def remove_question(self, question): + self.questions.remove(question) + + def load_questions(quiz_folder): + questions = [] + for folder_path in os.listdir(quiz_folder): + questions.append(Question.load_question(os.path.join(quiz_folder, folder_path))) + return Questions(questions) + +def main(): + global display + questions = Questions.load_questions("./Trial/Questions/") + print("showing initial") + QuestionDisplay.launch(sys.argv, questions.get_question(), questions) + print("shown, now waiting") + while display.isVisible(): + print("still waiting") + print("received input, window hidden") + print("showing window again") + QuestionDisplay.launch(sys.argv, questions.get_question(), questions) + print("window shown again") + +if __name__ == '__main__': + main() \ No newline at end of file