A Godot Engine app to help streamers organize Majority Judgment polls in their streams.
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.

182 lines
4.3 KiB

  1. extends Resource
  2. class_name MajorityJudgmentPoll
  3. """
  4. A poll with multiple candidates, where each participant
  5. gives a single grade to each of the candidates.
  6. The candidates are then sorted by their median grade.
  7. https://en.wikipedia.org/wiki/Majority_Judgment
  8. The constitutive details of the poll are up to you, of course,
  9. the poll can be imperative or informative.
  10. """
  11. const NOT_OPENED := -1
  12. const NOT_CLOSED := -1
  13. # Minimum length: 4 unicode characters (? TBD)
  14. # Maximum length: 256 unicode characters (? TBD)
  15. export(String) var title:String setget set_title, get_title
  16. # > How do we localize this?
  17. # Something like this, perhaps?
  18. # title_of_locale = {
  19. # 'en_US': "President of the United States of America in 2020",
  20. # 'fr_FR': "Président des États-Unis d'Amérique en 2020",
  21. # …
  22. # }
  23. #export(Dictionary) var localized_titles:Dictionary
  24. # or perhaps
  25. # https://docs.godotengine.org/en/stable/tutorials/i18n/internationalizing_games.html
  26. export(Resource) var grading #:MajorityJudgmentGrading
  27. # Array of MajorityJudgmentCandidate
  28. export(Array, Resource) var candidates:Array setget set_candidates, get_candidates
  29. # Array of MajorityJudgmentJudgments
  30. export(Array, Resource) var judgments:Array setget set_judgments, get_judgments
  31. # Seconds since UNIX EPOCH (01-01-1970)
  32. export var opened_at:int = NOT_OPENED
  33. export var closed_at:int = NOT_CLOSED
  34. # Don't pass parameters in _init().
  35. # ResourceLoader.load() won't like it.
  36. func _init():
  37. pass
  38. # Instead, use a factory approach.
  39. static func make(__title, __grading, __candidates):
  40. var poll = load("res://addons/majority_judgment/MajorityJudgmentPoll.gd").new()
  41. poll.set_title(__title)
  42. poll.set_grading(__grading)
  43. poll.set_candidates(__candidates)
  44. return poll
  45. func set_title(__title:String) -> void:
  46. title = __title
  47. func get_title() -> String:
  48. if null == title:
  49. return ''
  50. return title
  51. func set_grading(__grading:MajorityJudgmentGrading) -> void:
  52. grading = __grading
  53. func get_grading() -> MajorityJudgmentGrading:
  54. assert(grading, "Poll has no grading.")
  55. return grading
  56. func set_candidates(__candidates:Array) -> void:
  57. assert(__candidates, "Poll requires at least one candidate.")
  58. candidates = __candidates
  59. func add_candidate(candidate:MajorityJudgmentCandidate) -> void:
  60. if not candidates:
  61. candidates = Array()
  62. candidates.append(candidate)
  63. func get_candidates() -> Array:
  64. assert(candidates, "Poll has no candidates.")
  65. return candidates
  66. func count_candidates() -> int:
  67. if not candidates:
  68. return 0
  69. return candidates.size()
  70. func has_judgments() -> bool:
  71. if not judgments:
  72. return false
  73. return 0 < judgments.size()
  74. func set_judgments(__judgments:Array) -> void:
  75. judgments = __judgments
  76. func add_judgment(judgment:MajorityJudgmentJudgment) -> void:
  77. if not judgments:
  78. judgments = Array()
  79. for i in range(judgments.size()):
  80. var existing_judgment = judgments[i]
  81. if (
  82. (existing_judgment.participant == judgment.participant)
  83. and
  84. (existing_judgment.candidate == judgment.candidate)
  85. ):
  86. judgments[i] = judgment
  87. return
  88. judgments.append(judgment)
  89. func get_judgments() -> Array:
  90. # assert(judgments, "Poll has no judgments.")
  91. return judgments
  92. func is_open() -> bool:
  93. var now = App.get_now()
  94. return (
  95. (NOT_OPENED != opened_at and opened_at <= now)
  96. and
  97. (NOT_CLOSED == closed_at or closed_at >= now)
  98. )
  99. func is_closed() -> bool:
  100. return not is_open()
  101. func open() -> void:
  102. assert(NOT_OPENED == opened_at, "Cannot open() an already opened poll.")
  103. opened_at = App.get_now()
  104. func tally() -> MajorityJudgmentPollTally:
  105. if not has_judgments():
  106. return null
  107. # Pick relevant tallier from settings later on
  108. var tallier = MajorityJudgmentEasyTallier.new()
  109. # var tallier = MajorityJudgmentLiquidTallier.new()
  110. return tallier.tally(self)
  111. func get_participants() -> Array:
  112. var participants := Array()
  113. for judgment in judgments:
  114. var participant : MajorityJudgmentParticipant = judgment.participant
  115. if not participants.has(participant):
  116. participants.append(participant)
  117. return participants
  118. func count_participants() -> int:
  119. return get_participants().size()
  120. func get_dummy_merit_profile(candidate):
  121. var merit = MajorityJudgmentCandidateMeritProfile.new()
  122. merit.grades = Array()
  123. for i in range(get_grading().grades.size()):
  124. merit.grades.append(1)
  125. merit.colors = get_grading().get_colors()
  126. return merit