You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
227 lines
5.8 KiB
227 lines
5.8 KiB
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
|
|
|