Browse Source

Add `VotingModel` for vote counting

pull/12/head
Anton Tarasenko 2 years ago
parent
commit
2606c0d001
  1. 563
      sources/BaseAPI/API/Commands/Voting/TEST_Voting.uc
  2. 305
      sources/BaseAPI/API/Commands/Voting/VotingModel.uc
  3. 1
      sources/BaseAPI/API/Math/Tests/TEST_BigInt.uc
  4. 31
      sources/Manifest.uc

563
sources/BaseAPI/API/Commands/Voting/TEST_Voting.uc

@ -0,0 +1,563 @@
/**
* Author: dkanus
* Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
* License: GPL
* Copyright 2023 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class TEST_Voting extends TestCase
abstract
dependsOn(VotingModel);
enum ExpectedOutcome {
TEST_EO_Continue,
TEST_EO_End,
TEST_EO_EndDraw,
};
protected static function VotingModel MakeVotingModel(VotingModel.VotingPolicies policies) {
local VotingModel model;
model = VotingModel(__().memory.Allocate(class'VotingModel'));
model.Initialize(policies);
return model;
}
protected static function SetVoters(
VotingModel model,
optional string voterID0,
optional string voterID1,
optional string voterID2,
optional string voterID3,
optional string voterID4,
optional string voterID5,
optional string voterID6,
optional string voterID7,
optional string voterID8,
optional string voterID9
) {
local UserID nextID;
local array<UserID> voterIDs;
if (voterID0 != "") {
nextID = UserID(__().memory.Allocate(class'UserID'));
nextID.Initialize(__().text.FromString(voterID0));
voterIDs[voterIDs.length] = nextID;
}
if (voterID1 != "") {
nextID = UserID(__().memory.Allocate(class'UserID'));
nextID.Initialize(__().text.FromString(voterID1));
voterIDs[voterIDs.length] = nextID;
}
if (voterID2 != "") {
nextID = UserID(__().memory.Allocate(class'UserID'));
nextID.Initialize(__().text.FromString(voterID2));
voterIDs[voterIDs.length] = nextID;
}
if (voterID3 != "") {
nextID = UserID(__().memory.Allocate(class'UserID'));
nextID.Initialize(__().text.FromString(voterID3));
voterIDs[voterIDs.length] = nextID;
}
if (voterID4 != "") {
nextID = UserID(__().memory.Allocate(class'UserID'));
nextID.Initialize(__().text.FromString(voterID4));
voterIDs[voterIDs.length] = nextID;
}
if (voterID5 != "") {
nextID = UserID(__().memory.Allocate(class'UserID'));
nextID.Initialize(__().text.FromString(voterID5));
voterIDs[voterIDs.length] = nextID;
}
if (voterID6 != "") {
nextID = UserID(__().memory.Allocate(class'UserID'));
nextID.Initialize(__().text.FromString(voterID6));
voterIDs[voterIDs.length] = nextID;
}
if (voterID7 != "") {
nextID = UserID(__().memory.Allocate(class'UserID'));
nextID.Initialize(__().text.FromString(voterID7));
voterIDs[voterIDs.length] = nextID;
}
if (voterID8 != "") {
nextID = UserID(__().memory.Allocate(class'UserID'));
nextID.Initialize(__().text.FromString(voterID8));
voterIDs[voterIDs.length] = nextID;
}
if (voterID9 != "") {
nextID = UserID(__().memory.Allocate(class'UserID'));
nextID.Initialize(__().text.FromString(voterID9));
voterIDs[voterIDs.length] = nextID;
}
model.UpdatePotentialVoters(voterIDs);
}
protected static function MakeFaultyYesVote(
VotingModel model,
string voterID,
VotingModel.VotingResult expected) {
local UserID id;
id = UserID(__().memory.Allocate(class'UserID'));
id.Initialize(__().text.FromString(voterID));
Issue("Illegal vote had unexpected result.");
TEST_ExpectTrue(model.AddVote(id, true) == expected);
}
protected static function MakeFaultyNoVote(
VotingModel model,
string voterID,
VotingModel.VotingResult expected) {
local UserID id;
id = UserID(__().memory.Allocate(class'UserID'));
id.Initialize(__().text.FromString(voterID));
Issue("Illegal vote had unexpected result.");
TEST_ExpectTrue(model.AddVote(id, false) == expected);
}
protected static function VoteYes(VotingModel model, string voterID, ExpectedOutcome expected) {
local UserID id;
id = UserID(__().memory.Allocate(class'UserID'));
id.Initialize(__().text.FromString(voterID));
Issue("Failed to add legitimate vote.");
TEST_ExpectTrue(model.AddVote(id, true) == VFR_Success);
if (expected == TEST_EO_Continue) {
Issue("Vote, that shouldn't have ended voting, ended it.");
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress);
} else if (expected == TEST_EO_End) {
Issue("Vote, that should've ended voting with one side's victory, didn't do it.");
TEST_ExpectTrue(model.GetStatus() == VPM_Success);
} else if (expected == TEST_EO_EndDraw) {
Issue("Vote, that should've ended voting with a draw, didn't do it.");
TEST_ExpectTrue(model.GetStatus() == VPM_Draw);
}
}
protected static function VoteNo(VotingModel model, string voterID, ExpectedOutcome expected) {
local UserID id;
id = UserID(__().memory.Allocate(class'UserID'));
id.Initialize(__().text.FromString(voterID));
Issue("Failed to add legitimate vote.");
TEST_ExpectTrue(model.AddVote(id, false) == VFR_Success);
if (expected == TEST_EO_Continue) {
Issue("Vote, that shouldn't have ended voting, ended it.");
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress);
} else if (expected == TEST_EO_End) {
Issue("Vote, that should've ended voting with one side's victory, didn't do it.");
TEST_ExpectTrue(model.GetStatus() == VPM_Failure);
} else if (expected == TEST_EO_EndDraw) {
Issue("Vote, that should've ended voting with a draw, didn't do it.");
TEST_ExpectTrue(model.GetStatus() == VPM_Draw);
}
}
protected static function TESTS() {
Test_RestrictiveVoting();
Test_CanLeaveVoting();
Test_CanChangeVoting();
Test_All();
}
protected static function Test_RestrictiveVoting() {
SubTest_RestrictiveYesVoting();
SubTest_RestrictiveNoVoting();
SubTest_RestrictiveDrawVoting();
SubTest_RestrictiveFaultyVoting();
SubTest_RestrictiveDisconnectVoting();
SubTest_RestrictiveReconnectVoting();
}
protected static function SubTest_RestrictiveYesVoting() {
local VotingModel model;
Context("Testing restrictive \"yes\" voting.");
model = MakeVotingModel(VP_Restrictive);
SetVoters(model, "1", "2", "3", "4", "5", "6");
VoteYes(model, "2", TEST_EO_Continue);
VoteYes(model, "4", TEST_EO_Continue);
VoteYes(model, "3", TEST_EO_Continue);
VoteYes(model, "1", TEST_EO_End);
}
protected static function SubTest_RestrictiveNoVoting() {
local VotingModel model;
Context("Testing restrictive \"no\" voting.");
model = MakeVotingModel(VP_Restrictive);
SetVoters(model, "1", "2", "3", "4", "5", "6");
VoteNo(model, "1", TEST_EO_Continue);
VoteNo(model, "2", TEST_EO_Continue);
VoteNo(model, "6", TEST_EO_Continue);
VoteNo(model, "4", TEST_EO_End);
}
protected static function SubTest_RestrictiveDrawVoting() {
local VotingModel model;
Context("Testing restrictive \"draw\" voting.");
model = MakeVotingModel(VP_Restrictive);
SetVoters(model, "1", "2", "3", "4", "5", "6");
VoteYes(model, "3", TEST_EO_Continue);
VoteYes(model, "2", TEST_EO_Continue);
VoteNo(model, "5", TEST_EO_Continue);
VoteYes(model, "1", TEST_EO_Continue);
VoteNo(model, "6", TEST_EO_Continue);
VoteNo(model, "4", TEST_EO_EndDraw);
}
protected static function SubTest_RestrictiveFaultyVoting() {
local VotingModel model;
Context("Testing restrictive \"faulty\" voting.");
model = MakeVotingModel(VP_Restrictive);
SetVoters(model, "1", "2", "3", "4", "5", "6");
VoteYes(model, "3", TEST_EO_Continue);
MakeFaultyYesVote(model, "3", VFR_AlreadyVoted);
VoteYes(model, "2", TEST_EO_Continue);
MakeFaultyNoVote(model, "7", VFR_NotAllowed);
VoteNo(model, "5", TEST_EO_Continue);
MakeFaultyNoVote(model, "7", VFR_NotAllowed);
MakeFaultyNoVote(model, "5", VFR_AlreadyVoted);
VoteYes(model, "1", TEST_EO_Continue);
MakeFaultyNoVote(model, "3", VFR_CannotChangeVote);
VoteYes(model, "6", TEST_EO_End);
MakeFaultyYesVote(model, "4", VFR_VotingEnded);
}
protected static function SubTest_RestrictiveDisconnectVoting() {
local VotingModel model;
Context("Testing restrictive \"disconnect\" voting.");
model = MakeVotingModel(VP_Restrictive);
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
VoteYes(model, "1", TEST_EO_Continue);
VoteYes(model, "2", TEST_EO_Continue);
VoteYes(model, "3", TEST_EO_Continue);
VoteYes(model, "4", TEST_EO_Continue);
VoteNo(model, "5", TEST_EO_Continue);
VoteNo(model, "6", TEST_EO_Continue);
VoteYes(model, "7", TEST_EO_Continue);
SetVoters(model, "2", "4", "5", "6", "8", "9", "10"); // remove 1, 3, 7 - 3 "yes" votes
MakeFaultyNoVote(model, "1", VFR_NotAllowed);
VoteNo(model, "8", TEST_EO_Continue);
VoteYes(model, "9", TEST_EO_Continue);
// Here we're at 3 "no" votes, 3 "yes" votes out of 7 total;
// disconnect "2" and "9" for "no" to win
SetVoters(model, "4", "5", "6", "8", "10");
Issue("Unexpected result after voting users disconnected.");
TEST_ExpectTrue(model.GetStatus() == VPM_Failure);
}
protected static function SubTest_RestrictiveReconnectVoting() {
local VotingModel model;
Context("Testing restrictive \"reconnecting\" voting.");
model = MakeVotingModel(VP_Restrictive);
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
VoteYes(model, "1", TEST_EO_Continue);
VoteNo(model, "2", TEST_EO_Continue);
VoteYes(model, "3", TEST_EO_Continue);
VoteNo(model, "4", TEST_EO_Continue);
VoteYes(model, "5", TEST_EO_Continue);
VoteNo(model, "6", TEST_EO_Continue);
// Disconnect 1 3 "yes" voters
SetVoters(model, "2", "4", "5", "6", "7", "8", "9", "10");
VoteYes(model, "7", TEST_EO_Continue);
VoteNo(model, "8", TEST_EO_Continue);
VoteYes(model, "9", TEST_EO_Continue);
MakeFaultyNoVote(model, "1", VFR_NotAllowed);
MakeFaultyNoVote(model, "3", VFR_NotAllowed);
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
VoteNo(model, "10", TEST_EO_EndDraw);
}
/* Testing restrictive "reconnecting" voting.
Unexpected result after voting users reconnected. [1] */
protected static function Test_CanLeaveVoting() {
SubTest_CanLeaveYesVoting();
SubTest_CanLeaveNoVoting();
SubTest_CanLeaveDrawVoting();
SubTest_CanLeaveFaultyVoting();
SubTest_CanLeaveDisconnectVoting();
SubTest_CanLeaveReconnectVoting();
}
protected static function SubTest_CanLeaveYesVoting() {
local VotingModel model;
Context("Testing \"yes\" voting where users are allowed to leave.");
model = MakeVotingModel(VP_CanLeave);
SetVoters(model, "1", "2", "3", "4", "5", "6");
VoteYes(model, "2", TEST_EO_Continue);
VoteYes(model, "4", TEST_EO_Continue);
VoteYes(model, "3", TEST_EO_Continue);
SetVoters(model, "1", "5", "6");
Issue("Unexpected result after voting users leaves.");
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress);
VoteYes(model, "1", TEST_EO_End);
}
protected static function SubTest_CanLeaveNoVoting() {
local VotingModel model;
Context("Testing \"no\" voting where users are allowed to leave.");
model = MakeVotingModel(VP_CanLeave);
SetVoters(model, "1", "2", "3", "4", "5", "6");
VoteNo(model, "1", TEST_EO_Continue);
VoteNo(model, "2", TEST_EO_Continue);
VoteNo(model, "6", TEST_EO_Continue);
SetVoters(model, "3", "4", "5");
Issue("Unexpected result after voting users leaves.");
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress);
VoteNo(model, "4", TEST_EO_End);
}
protected static function SubTest_CanLeaveDrawVoting() {
local VotingModel model;
Context("Testing \"draw\" voting where users are allowed to leave.");
model = MakeVotingModel(VP_CanLeave);
SetVoters(model, "1", "2", "3", "4", "5", "6");
VoteYes(model, "3", TEST_EO_Continue);
VoteYes(model, "2", TEST_EO_Continue);
VoteNo(model, "5", TEST_EO_Continue);
VoteYes(model, "1", TEST_EO_Continue);
VoteNo(model, "6", TEST_EO_Continue);
SetVoters(model, "4");
Issue("Unexpected result after voting users leaves.");
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress);
VoteNo(model, "4", TEST_EO_EndDraw);
}
protected static function SubTest_CanLeaveFaultyVoting() {
local VotingModel model;
Context("Testing \"faulty\" voting where users are allowed to leave.");
model = MakeVotingModel(VP_CanLeave);
SetVoters(model, "1", "2", "3", "4", "5", "6");
VoteYes(model, "3", TEST_EO_Continue);
MakeFaultyYesVote(model, "3", VFR_AlreadyVoted);
VoteYes(model, "2", TEST_EO_Continue);
MakeFaultyNoVote(model, "7", VFR_NotAllowed);
VoteNo(model, "5", TEST_EO_Continue);
MakeFaultyNoVote(model, "7", VFR_NotAllowed);
MakeFaultyNoVote(model, "5", VFR_AlreadyVoted);
VoteYes(model, "1", TEST_EO_Continue);
MakeFaultyNoVote(model, "3", VFR_CannotChangeVote);
SetVoters(model, "4", "5", "6");
Issue("Unexpected result after voting users leaves.");
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress);
VoteYes(model, "6", TEST_EO_End);
MakeFaultyYesVote(model, "4", VFR_VotingEnded);
}
protected static function SubTest_CanLeaveDisconnectVoting() {
local VotingModel model;
Context("Testing \"leave\" voting where users are allowed to leave.");
model = MakeVotingModel(VP_CanLeave);
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
VoteYes(model, "1", TEST_EO_Continue);
VoteYes(model, "2", TEST_EO_Continue);
VoteYes(model, "3", TEST_EO_Continue);
VoteYes(model, "4", TEST_EO_Continue);
VoteNo(model, "5", TEST_EO_Continue);
VoteNo(model, "6", TEST_EO_Continue);
VoteYes(model, "7", TEST_EO_Continue);
SetVoters(model, "2", "4", "5", "6", "8", "9", "10");
MakeFaultyNoVote(model, "1", VFR_NotAllowed);
VoteNo(model, "8", TEST_EO_Continue);
VoteYes(model, "10", TEST_EO_End);
}
protected static function SubTest_CanLeaveReconnectVoting() {
local VotingModel model;
Context("Testing \"reconnecting\" voting where users are allowed to leave.");
model = MakeVotingModel(VP_CanLeave);
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
VoteYes(model, "1", TEST_EO_Continue);
VoteNo(model, "2", TEST_EO_Continue);
VoteYes(model, "3", TEST_EO_Continue);
VoteNo(model, "4", TEST_EO_Continue);
VoteYes(model, "5", TEST_EO_Continue);
VoteNo(model, "6", TEST_EO_Continue);
// Disconnect 1 3 "yes" voters
SetVoters(model, "2", "4", "5", "6", "7", "8", "9", "10");
VoteYes(model, "7", TEST_EO_Continue);
VoteNo(model, "8", TEST_EO_Continue);
VoteNo(model, "9", TEST_EO_Continue);
MakeFaultyNoVote(model, "1", VFR_NotAllowed);
MakeFaultyNoVote(model, "3", VFR_NotAllowed);
VoteYes(model, "10", TEST_EO_EndDraw);
}
protected static function Test_CanChangeVoting() {
SubTest_CanChangeYesVoting();
SubTest_CanChangeNoVoting();
SubTest_CanChangeDrawVoting();
SubTest_CanChangeFaultyVoting();
SubTest_CanChangeDisconnectVoting();
SubTest_CanChangeReconnectVoting();
}
protected static function SubTest_CanChangeYesVoting() {
local VotingModel model;
Context("Testing \"yes\" voting where users are allowed to change their vote.");
model = MakeVotingModel(VP_CanChangeVote);
SetVoters(model, "1", "2", "3", "4", "5", "6");
VoteYes(model, "2", TEST_EO_Continue);
VoteYes(model, "4", TEST_EO_Continue);
VoteYes(model, "3", TEST_EO_Continue);
VoteNo(model, "6", TEST_EO_Continue);
VoteNo(model, "1", TEST_EO_Continue);
VoteYes(model, "6", TEST_EO_End);
}
protected static function SubTest_CanChangeNoVoting() {
local VotingModel model;
Context("Testing \"no\" voting where users are allowed to change their vote.");
model = MakeVotingModel(VP_CanChangeVote);
SetVoters(model, "1", "2", "3", "4", "5", "6");
VoteNo(model, "1", TEST_EO_Continue);
VoteNo(model, "2", TEST_EO_Continue);
VoteNo(model, "6", TEST_EO_Continue);
VoteYes(model, "5", TEST_EO_Continue);
VoteYes(model, "4", TEST_EO_Continue);
VoteNo(model, "5", TEST_EO_End);
}
protected static function SubTest_CanChangeDrawVoting() {
local VotingModel model;
Context("Testing \"draw\" voting where users are allowed to change their vote.");
model = MakeVotingModel(VP_CanChangeVote);
SetVoters(model, "1", "2", "3", "4", "5", "6");
VoteYes(model, "3", TEST_EO_Continue);
VoteYes(model, "2", TEST_EO_Continue);
VoteNo(model, "5", TEST_EO_Continue);
VoteYes(model, "1", TEST_EO_Continue);
VoteNo(model, "1", TEST_EO_Continue);
VoteYes(model, "1", TEST_EO_Continue);
VoteNo(model, "6", TEST_EO_Continue);
VoteNo(model, "4", TEST_EO_EndDraw);
}
protected static function SubTest_CanChangeFaultyVoting() {
local VotingModel model;
Context("Testing \"faulty\" voting where users are allowed to change their vote.");
model = MakeVotingModel(VP_CanChangeVote);
SetVoters(model, "1", "2", "3", "4", "5", "6");
VoteYes(model, "3", TEST_EO_Continue);
VoteYes(model, "5", TEST_EO_Continue);
VoteYes(model, "2", TEST_EO_Continue);
VoteNo(model, "5", TEST_EO_Continue);
VoteYes(model, "1", TEST_EO_Continue);
VoteYes(model, "6", TEST_EO_End);
MakeFaultyYesVote(model, "4", VFR_VotingEnded);
}
protected static function SubTest_CanChangeDisconnectVoting() {
local VotingModel model;
Context("Testing \"disconnect\" voting where users are allowed to change their vote.");
model = MakeVotingModel(VP_CanChangeVote);
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
VoteYes(model, "1", TEST_EO_Continue);
VoteYes(model, "2", TEST_EO_Continue);
VoteYes(model, "6", TEST_EO_Continue);
VoteYes(model, "3", TEST_EO_Continue);
VoteNo(model, "5", TEST_EO_Continue);
VoteYes(model, "5", TEST_EO_Continue);
VoteNo(model, "5", TEST_EO_Continue);
VoteYes(model, "4", TEST_EO_Continue);
VoteNo(model, "6", TEST_EO_Continue);
VoteYes(model, "7", TEST_EO_Continue);
SetVoters(model, "2", "4", "5", "6", "8", "9", "10"); // remove 1, 3, 7 - 3 "yes" votes
MakeFaultyNoVote(model, "1", VFR_NotAllowed);
VoteNo(model, "8", TEST_EO_Continue);
VoteYes(model, "5", TEST_EO_Continue);
VoteNo(model, "9", TEST_EO_Continue);
// Here we're at 3 "no" votes, 3 "yes" votes out of 7 total;
// disconnect "6" and "9" for "yes" to win
SetVoters(model, "2", "4", "5", "8", "10");
Issue("Unexpected result after voting users disconnected.");
TEST_ExpectTrue(model.GetStatus() == VPM_Success);
}
protected static function SubTest_CanChangeReconnectVoting() {
local VotingModel model;
Context("Testing \"reconnect\" voting where users are allowed to change their vote.");
model = MakeVotingModel(VP_CanChangeVote);
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
VoteYes(model, "1", TEST_EO_Continue);
VoteNo(model, "2", TEST_EO_Continue);
VoteYes(model, "3", TEST_EO_Continue);
VoteNo(model, "4", TEST_EO_Continue);
VoteYes(model, "5", TEST_EO_Continue);
VoteNo(model, "6", TEST_EO_Continue);
// Disconnect 1 3 "yes" voters
SetVoters(model, "2", "4", "5", "6", "7", "8", "9", "10");
VoteYes(model, "7", TEST_EO_Continue);
VoteNo(model, "8", TEST_EO_Continue);
VoteYes(model, "9", TEST_EO_Continue);
MakeFaultyNoVote(model, "1", VFR_NotAllowed);
MakeFaultyNoVote(model, "3", VFR_NotAllowed);
// Restore 3 "yes" voter
SetVoters(model, "2", "3", "4", "5", "6", "7", "8", "9", "10");
VoteNo(model, "3", TEST_EO_End);
}
protected static function Test_All() {
local VotingModel model;
Context("Testing permissive voting options.");
model = MakeVotingModel(VP_CanLeaveAndChangeVote);
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
VoteYes(model, "1", TEST_EO_Continue);
VoteYes(model, "2", TEST_EO_Continue);
VoteNo(model, "3", TEST_EO_Continue);
VoteYes(model, "4", TEST_EO_Continue);
VoteNo(model, "5", TEST_EO_Continue);
VoteNo(model, "6", TEST_EO_Continue);
// Disconnect 1 and 5 voters
SetVoters(model, "2", "3", "4", "6", "7", "8", "9", "10");
MakeFaultyNoVote(model, "1", VFR_NotAllowed);
MakeFaultyNoVote(model, "5", VFR_NotAllowed);
VoteYes(model, "3", TEST_EO_Continue);
VoteNo(model, "7", TEST_EO_Continue);
VoteNo(model, "8", TEST_EO_Continue);
VoteNo(model, "9", TEST_EO_Continue);
// Bring back 1, disconnect 3 and 6
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
VoteYes(model, "8", TEST_EO_Continue);
VoteNo(model, "4", TEST_EO_Continue);
// Disconnect 10, finishing voting (since now only 9 voters are available)
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9");
Issue("Unexpected result after voting users disconnected.");
TEST_ExpectTrue(model.GetStatus() == VPM_Failure);
}
defaultproperties {
caseGroup = "Commands"
caseName = "Voting model"
}

305
sources/BaseAPI/API/Commands/Voting/VotingModel.uc

@ -0,0 +1,305 @@
/**
* Author: dkanus
* Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
* License: GPL
* Copyright 2023 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
* Acedia is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License, or
* (at your option) any later version.
*
* Acedia is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/
class VotingModel extends AcediaObject;
enum VotingPolicies {
VP_Restrictive,
VP_CanLeave,
VP_CanChangeVote,
VP_CanLeaveAndChangeVote
};
enum VotingModelStatus {
VPM_Uninitialized,
VPM_InProgress,
VPM_Success,
VPM_Failure,
VPM_Draw
};
enum VotingResult {
VFR_Success,
VFR_NotAllowed,
VFR_CannotChangeVote,
VFR_AlreadyVoted,
VFR_VotingEnded
};
enum PlayerVoteStatus {
PVS_NoVote,
PVS_VoteFor,
PVS_VoteAgainst
};
var private VotingModelStatus status;
var private bool policyCanLeave, policyCanChangeVote;
var private array<UserID> votesFor, votesAgainst;
var private array<UserID> storedVotesFor, storedVotesAgainst;
var private array<UserID> allowedVoters;
protected function Constructor() {
status = VPM_Uninitialized;
policyCanLeave = false;
policyCanChangeVote = false;
}
protected function Finalizer() {
_.memory.FreeMany(allowedVoters);
_.memory.FreeMany(votesFor);
_.memory.FreeMany(votesAgainst);
_.memory.FreeMany(storedVotesFor);
_.memory.FreeMany(storedVotesAgainst);
allowedVoters.length = 0;
votesFor.length = 0;
votesAgainst.length = 0;
storedVotesFor.length = 0;
storedVotesAgainst.length = 0;
}
public final function Initialize(VotingPolicies policies) {
if (status == VPM_Uninitialized) {
policyCanLeave = (policies == VP_CanLeave) || (policies == VP_CanLeaveAndChangeVote);
policyCanChangeVote =
(policies == VP_CanChangeVote) || (policies == VP_CanLeaveAndChangeVote);
}
status = VPM_InProgress;
}
public final function VotingModelStatus GetStatus() {
return status;
}
private final function RecountVotes() {
local bool canOverturn, everyoneVoted;
local int totalPossibleVotes;
local int totalVotesFor, totalVotesAgainst;
local int lowerVoteCount, upperVoteCount, undecidedVoteCount;
if (status != VPM_InProgress) {
return;
}
if (policyCanLeave) {
totalVotesFor = votesFor.length + storedVotesFor.length;
totalVotesAgainst = votesAgainst.length + storedVotesAgainst.length;
totalPossibleVotes =
allowedVoters.length + storedVotesFor.length + storedVotesAgainst.length;
} else {
totalVotesFor = votesFor.length;
totalVotesAgainst = votesAgainst.length;
totalPossibleVotes = allowedVoters.length;
}
lowerVoteCount = Min(totalVotesFor, totalVotesAgainst);
upperVoteCount = Max(totalVotesFor, totalVotesAgainst);
undecidedVoteCount = totalPossibleVotes - (lowerVoteCount + upperVoteCount);
everyoneVoted = (undecidedVoteCount <= 0);
canOverturn = lowerVoteCount + undecidedVoteCount >= upperVoteCount;
if (everyoneVoted || !canOverturn) {
if (totalVotesFor > totalVotesAgainst) {
status = VPM_Success;
} else if (totalVotesFor < totalVotesAgainst) {
status = VPM_Failure;
} else {
status = VPM_Draw;
}
}
}
public final function bool AllowedToVote(UserID voter) {
local int i;
if (voter == none) {
return false;
}
for (i = 0; i < allowedVoters.length; i += 1) {
if (voter.IsEqual(allowedVoters[i])) {
return true;
}
}
return false;
}
public final function PlayerVoteStatus HasVoted(UserID voter) {
local int i;
if (voter == none) {
return PVS_NoVote;
}
for (i = 0; i < votesFor.length; i += 1) {
if (voter.IsEqual(votesFor[i])) {
return PVS_VoteFor;
}
}
for (i = 0; i < votesAgainst.length; i += 1) {
if (voter.IsEqual(votesAgainst[i])) {
return PVS_VoteAgainst;
}
}
return PVS_NoVote;
}
private final function EraseVote(UserID voter) {
local int i;
if (voter == none) {
return;
}
while (i < votesFor.length) {
if (voter.IsEqual(votesFor[i])) {
_.memory.Free(votesFor[i]);
votesFor.Remove(i, 1);
} else {
i += 1;
}
}
i = 0;
while (i < votesAgainst.length) {
if (voter.IsEqual(votesAgainst[i])) {
_.memory.Free(votesAgainst[i]);
votesAgainst.Remove(i, 1);
} else {
i += 1;
}
}
}
public final function UpdatePotentialVoters(array<UserID> potentialVoters) {
local int i;
_.memory.FreeMany(allowedVoters);
allowedVoters.length = 0;
for (i = 0; i < potentialVoters.length; i += 1) {
potentialVoters[i].NewRef();
allowedVoters[i] = potentialVoters[i];
}
RestoreStoredVoters(potentialVoters);
FilterCurrentVoters(potentialVoters);
RecountVotes();
}
private final function RestoreStoredVoters(array<UserID> potentialVoters) {
local int i, j;
local bool isPotentialVoter;
while (i < storedVotesFor.length) {
isPotentialVoter = false;
for (j = 0; j < potentialVoters.length; j += 1) {
if (storedVotesFor[i].IsEqual(potentialVoters[j])) {
isPotentialVoter = true;
break;
}
}
if (isPotentialVoter) {
votesFor[votesFor.length] = storedVotesFor[i];
storedVotesFor.Remove(i, 1);
} else {
i += 1;
}
}
i = 0;
while (i < storedVotesAgainst.length) {
isPotentialVoter = false;
for (j = 0; j < potentialVoters.length; j += 1) {
if (storedVotesAgainst[i].IsEqual(potentialVoters[j])) {
isPotentialVoter = true;
break;
}
}
if (isPotentialVoter) {
votesAgainst[votesAgainst.length] = storedVotesAgainst[i];
storedVotesAgainst.Remove(i, 1);
} else {
i += 1;
}
}
}
private final function FilterCurrentVoters(array<UserID> potentialVoters) {
local int i, j;
local bool isPotentialVoter;
while (i < votesFor.length) {
isPotentialVoter = false;
for (j = 0; j < potentialVoters.length; j += 1) {
if (votesFor[i].IsEqual(potentialVoters[j])) {
isPotentialVoter = true;
break;
}
}
if (isPotentialVoter) {
i += 1;
} else {
storedVotesFor[storedVotesFor.length] = votesFor[i];
votesFor.Remove(i, 1);
}
}
i = 0;
while (i < votesAgainst.length) {
isPotentialVoter = false;
for (j = 0; j < potentialVoters.length; j += 1) {
if (votesAgainst[i].IsEqual(potentialVoters[j])) {
isPotentialVoter = true;
break;
}
}
if (isPotentialVoter) {
i += 1;
} else {
storedVotesAgainst[storedVotesAgainst.length] = votesAgainst[i];
votesAgainst.Remove(i, 1);
}
}
}
public final function VotingResult AddVote(UserID voter, bool forSuccess) {
local bool votesSameWay;
local PlayerVoteStatus currentVote;
if (status != VPM_InProgress) {
return VFR_VotingEnded;
}
if (!AllowedToVote(voter)) {
return VFR_NotAllowed;
}
currentVote = HasVoted(voter);
votesSameWay = (forSuccess && currentVote == PVS_VoteFor)
|| (!forSuccess && currentVote == PVS_VoteAgainst);
if (votesSameWay) {
return VFR_AlreadyVoted;
}
if (!policyCanChangeVote && currentVote != PVS_NoVote) {
return VFR_CannotChangeVote;
}
EraseVote(voter);
voter.NewRef();
if (forSuccess) {
votesFor[votesFor.length] = voter;
} else {
votesAgainst[votesAgainst.length] = voter;
}
RecountVotes();
return VFR_Success;
}
defaultproperties {
}

1
sources/BaseAPI/API/Math/Tests/TEST_BigInt.uc

@ -99,7 +99,6 @@ protected static function SubTest_AddingSameSignValues() {
main = __().math.MakeBigInt_S("927641962323462271784269213864"); main = __().math.MakeBigInt_S("927641962323462271784269213864");
addition = __().math.MakeBigInt_S("16324234842947239847239239"); addition = __().math.MakeBigInt_S("16324234842947239847239239");
main.Add(addition); main.Add(addition);
Log("UMBRA" @ main.ToString());
TEST_ExpectTrue(main.ToString() == "927658286558305219024116453103"); TEST_ExpectTrue(main.ToString() == "927658286558305219024116453103");
main = __().math.MakeBigInt_S("16324234842947239847239239"); main = __().math.MakeBigInt_S("16324234842947239847239239");
addition = __().math.MakeBigInt_S("927641962323462271784269213864"); addition = __().math.MakeBigInt_S("927641962323462271784269213864");

31
sources/Manifest.uc

@ -43,19 +43,20 @@ defaultproperties
testCases(14) = class'TEST_TextTemplate' testCases(14) = class'TEST_TextTemplate'
testCases(15) = class'TEST_User' testCases(15) = class'TEST_User'
testCases(16) = class'TEST_Memory' testCases(16) = class'TEST_Memory'
testCases(17) = class'TEST_ArrayList' testCases(17) = class'TEST_Voting'
testCases(18) = class'TEST_HashTable' testCases(18) = class'TEST_ArrayList'
testCases(19) = class'TEST_CollectionsMixed' testCases(19) = class'TEST_HashTable'
testCases(20) = class'TEST_Iterator' testCases(20) = class'TEST_CollectionsMixed'
testCases(21) = class'TEST_Command' testCases(21) = class'TEST_Iterator'
testCases(22) = class'TEST_CommandDataBuilder' testCases(22) = class'TEST_Command'
testCases(23) = class'TEST_LogMessage' testCases(23) = class'TEST_CommandDataBuilder'
testCases(24) = class'TEST_SchedulerAPI' testCases(24) = class'TEST_LogMessage'
testCases(25) = class'TEST_BigInt' testCases(25) = class'TEST_SchedulerAPI'
testCases(26) = class'TEST_DatabaseCommon' testCases(26) = class'TEST_BigInt'
testCases(27) = class'TEST_LocalDatabase' testCases(27) = class'TEST_DatabaseCommon'
testCases(28) = class'TEST_DBConnection' testCases(28) = class'TEST_LocalDatabase'
testCases(29) = class'TEST_AcediaConfig' testCases(29) = class'TEST_DBConnection'
testCases(30) = class'TEST_UTF8EncoderDecoder' testCases(30) = class'TEST_AcediaConfig'
testCases(31) = class'TEST_AvariceStreamReader' testCases(31) = class'TEST_UTF8EncoderDecoder'
testCases(32) = class'TEST_AvariceStreamReader'
} }
Loading…
Cancel
Save