extends Resource class_name MajorityJudgmentPoll """ A poll with multiple candidates, where each participant gives a single grade to each of the candidates. The candidates are then sorted by their median grade. https://en.wikipedia.org/wiki/Majority_Judgment The constitutive details of the poll are up to you, of course, the poll can be imperative or informative. """ const NOT_OPENED := -1 const NOT_CLOSED := -1 # Minimum length: 4 unicode characters (? TBD) # Maximum length: 256 unicode characters (? TBD) export(String) var title:String setget set_title, get_title # > How do we localize this? # Something like this, perhaps? # title_of_locale = { # 'en_US': "President of the United States of America in 2020", # 'fr_FR': "Président des États-Unis d'Amérique en 2020", # … # } #export(Dictionary) var localized_titles:Dictionary # or perhaps # https://docs.godotengine.org/en/stable/tutorials/i18n/internationalizing_games.html export(Resource) var grading #:MajorityJudgmentGrading # Array of MajorityJudgmentCandidate export(Array, Resource) var candidates:Array setget set_candidates, get_candidates # Array of MajorityJudgmentJudgments # If you mutate this property directly and not through add_judgment(), # remember to update the memoization cache as well with update_participants_index() export(Array, Resource) var judgments:Array setget set_judgments, get_judgments # Seconds since UNIX EPOCH (01-01-1970) export var opened_at:int = NOT_OPENED export var closed_at:int = NOT_CLOSED # Don't pass parameters in _init(). # ResourceLoader.load() won't like it. func _init(): pass # Instead, use a factory approach. static func make(__title, __grading, __candidates): var poll = load("res://addons/majority_judgment/MajorityJudgmentPoll.gd").new() poll.set_title(__title) poll.set_grading(__grading) poll.set_candidates(__candidates) return poll func set_title(__title:String) -> void: title = __title func get_title() -> String: if null == title: return '' return title func set_grading(__grading:MajorityJudgmentGrading) -> void: grading = __grading func get_grading() -> MajorityJudgmentGrading: assert(grading, "Poll has no grading.") return grading func set_candidates(__candidates:Array) -> void: assert(__candidates, "Poll requires at least one candidate.") candidates = __candidates func add_candidate(candidate:MajorityJudgmentCandidate) -> void: if not candidates: candidates = Array() candidates.append(candidate) func get_candidates() -> Array: assert(candidates, "Poll has no candidates.") return candidates func count_candidates() -> int: if not candidates: return 0 return candidates.size() func has_judgments() -> bool: if not judgments: return false return 0 < judgments.size() func set_judgments(__judgments:Array) -> void: judgments = __judgments func add_judgment(judgment:MajorityJudgmentJudgment) -> void: if not judgments: judgments = Array() if not judgment.candidate in self.candidates: printerr("Judgment Candidate is not in the Poll!") return for i in range(judgments.size()): var existing_judgment = judgments[i] if ( (existing_judgment.participant == judgment.participant) and (existing_judgment.candidate == judgment.candidate) ): judgments[i] = judgment return update_participants_index(judgment.participant) judgments.append(judgment) func get_judgments() -> Array: # assert(judgments, "Poll has no judgments.") return judgments func is_open() -> bool: var now = App.get_now() return ( (NOT_OPENED != opened_at and opened_at <= now) and (NOT_CLOSED == closed_at or closed_at >= now) ) func is_closed() -> bool: return not is_open() func open() -> void: assert(NOT_OPENED == opened_at, "Cannot open() an already opened poll.") opened_at = App.get_now() func tally() -> MajorityJudgmentPollTally: if not has_judgments(): return null # Pick relevant tallier from settings later on var tallier = MajorityJudgmentEasyTallier.new() # var tallier = MajorityJudgmentLiquidTallier.new() return tallier.tally(self) func get_or_create_participant(identifier:String) -> MajorityJudgmentParticipant: identifier = MajorityJudgmentParticipant.sanitize_name(identifier) var known_participants = get_participants_index() if not known_participants.has(identifier): known_participants[identifier] = MajorityJudgmentParticipant.make(identifier) return known_participants[identifier] # Memoization of expensive computation var __participants_index := Dictionary() # id => Participant func get_participants_index(): if __participants_index.empty(): rebuild_participants_index() return __participants_index func rebuild_participants_index(): for participant in get_participants(): assert( not __participants_index.has(participant.name), "Participants index should be consistent: names should be unique." ) __participants_index[participant.name] = participant func update_participants_index(participant:MajorityJudgmentParticipant): # Zealous sanitization, since Participants also handle it on their names var identifier = MajorityJudgmentParticipant.sanitize_name(participant.name) if not __participants_index.has(identifier): __participants_index[identifier] = participant assert( __participants_index[identifier] == participant, "Participants index should be consistent." ) func get_participants() -> Array: var participants := Array() for judgment in judgments: var participant : MajorityJudgmentParticipant = judgment.participant if not participants.has(participant): participants.append(participant) return participants func count_participants() -> int: return get_participants().size() func get_dummy_merit_profile(candidate): var merit = MajorityJudgmentCandidateMeritProfile.new() merit.grades = Array() for i in range(get_grading().grades.size()): merit.grades.append(1) merit.colors = get_grading().get_colors() return merit