/* -*- LPC -*- */
/*
* $Locker: pinkfish $
* $Id: voting_room.c,v 1.9 2001/10/16 16:53:06 ceres Exp pinkfish $
*
*/
#include <board.h>
#define DEFAULT_VOTE_DURATION (2600*24*7)
/**
* Generalised voting room intended to be inherited into rooms in the mud that
* will setup the parameters and settings for votes.
* <p>
* The room does not inherantly support player initiated votes, however it is
* a fairly simple matter to add this to your own room and have your room add
* votes when players initiate them.
*
* @author Ceres
*/
/**
* This class stores the essential information about a vote. It is only used
* internally to the voting room.
* @element type used to setup vote types
* @element desc the description of the vote
*/
class vote {
string type; /* used to setup vote types */
string desc; /* description of the vote */
string *choices; /* options in the vote */
mixed *votes;
int ending; /* when the vote ends */
string *voted; /* people who've voted */
}
/**
* This class stores the essential information about an election during the
* setup phase. It is only used internally to the voting room.
*/
class election {
mixed *candidates; /* who are the candidates */
int closes; /* when does acceptance of nominees close */
}
private int vote_counter; /* Used to give each vote a unique number */
private mapping votes; /* the votes themselves */
private mapping elections; /* the elections being setup */
private nosave string _vote_file; /* Controls which vote file to use */
private nosave mixed _vote_cond;
private nosave mixed _comp_action; /* Completion action */
private nosave int _vote_duration; /* The normal duration for a vote */
private nosave int _open_voting; /* Make who voted for what known */
private nosave int _no_elections;
private nosave string _proxy; /* A proxy room to use for votes. */
private nosave mixed _stand_cond;
private nosave mixed _second_cond;
private nosave mixed _election_announcement; /* The board, person and subject */
int list_votes();
int cast_vote( string words, int num );
int stand_for_election(string position);
int second_candidate(string who, string position);
int list_elections();
int do_create_vote(string choices);
/**
* @ignore
*/
void create() {
seteuid("Room");
votes = ([ ]);
elections = ([ ]);
}
/**
* @ignore
*/
void init() {
// commands for votes
add_command("vote", "<string> on <number>", (: cast_vote($4[0], $4[1]) :) );
add_command("list", "votes", (: list_votes :));
add_command("votes", "", (: list_votes :));
// commands for elections
if (!_no_elections) {
add_command( "stand", "[for] <string>", (: stand_for_election($4[0]) :));
add_command( "second", "<string> [for] <string>",
(: second_candidate($4[0], $4[1]) :) );
add_command("list", "elections", (: list_elections :));
add_command("elections", "", (: list_elections :));
}
} /* init() */
/**
* This method is used to return the current votes. This can be overridden
* in higher functions to get the votes from somewhere else if nessessary.
* @return the votes
*/
protected mapping query_our_votes() {
if (_proxy) {
return _proxy->query_votes();
}
if (votes) {
return votes;
}
return ([ ]);
} /* query_our_votes() */
/**
* This method adds in a vote.
* @param id the id of the vote
* @param data the vote data
*/
protected void add_our_vote(int id, class vote data) {
if (!votes) {
votes = ([ ]);
}
votes[id] = data;
} /* add_our_vote() */
/**
* This method is used to return the current elections. This can be
* overridden in hgiher inherits to control things better.
* @return the elections
*/
protected mapping query_our_elections() {
if (_proxy) {
return _proxy->query_elections();
}
if (elections) {
return elections;
}
return ([ ]);
} /* query_our_elections() */
/**
* This method adds in a election.
* @param name the name of the election to add
* @param data the election data
*/
protected void add_our_election(string name, class election data) {
if (!elections) {
elections = ([ ]);
}
elections[name] = data;
} /* add_our_election() */
/**
* This is the init() which should be called as well as the default
* init() if you wish to have player added votes.
*/
void init_add_vote() {
add_command( "add", "vote with <string'choices'>",
(: do_create_vote($4[0]) :));
} /* init_add_vote() */
/**
* This setups all the stuff that needs to be setup after the room has
* been loaded. Makes all the callouts for the right amount of time
* and so on.
*/
void setup_after_load() {
int i;
string election;
int *vote_ids;
if (_proxy) {
return ;
}
vote_ids = m_indices( query_our_votes() );
for ( i = 0; i < sizeof( vote_ids ); i++ ) {
if ( query_our_votes()[ vote_ids[ i ] ]->ending < time() ) {
call_out( "end_vote", 10 * ( i + 1 ), vote_ids[ i ] );
} else {
call_out( "end_vote", query_our_votes()[ vote_ids[ i ] ]->ending - time(),
vote_ids[ i ] );
}
}
foreach(election in keys(query_our_elections())) {
if(query_our_elections()[election]->closes < time()) {
call_out( "start_election_vote", 30 * random(5), election);
} else {
call_out( "start_election_vote", query_our_elections()[election]->closes - time(),
election);
}
}
} /* setup_after_load() */
/**
* This method is called by the inheriting object to determine which save file
* to use for votes.
*
* @param str The name of the file you want the voting info saved to
* (without the .o)
*/
void set_save_file(string file) {
_vote_file = file;
if( file_size( _vote_file + ".o" ) > 0 ) {
unguarded( (: restore_object, _vote_file :) );
setup_after_load();
} else {
elections = ([ ]);
votes = ([ ]);
}
}
/**
* This method returns the save file currently used for the room.
* @return the current save file
*/
string query_save_file() { return _vote_file; }
/**
* Private function to save the rooms data file.
*/
protected void save_room() {
if (_vote_file) {
unguarded( (: save_object, _vote_file :) );
}
}
/**
* This method sets up a proxy for the room. A proxy is somewhere else
* to get all the voting information from.
* @param proxy the proxy tpo setup
*/
void set_proxy(string proxy) {
_proxy = proxy;
} /* set_proxy() */
/**
* This method sets the room to not allow elections and disable all the
* election commands.
* @param no_elections the flag
*/
void set_no_elections(int no_elections) {
_no_elections = no_elections;
} /* set_no_elections() */
/**
* This function setups if the votes should be open or not. If a vote is
* open then you know who voted for each thing.
* @param open 1 for an open system 0 for closed
*/
void set_open_voting(int open) {
_open_voting = open;
} /* set_open_voting() */
/**
* This function queries if the votes should be open or not. If a vote is
* open then you know who voted for each thing.
* @return 1 for an open system 0 for closed
*/
int query_open_voting(int open) {
return _open_voting;
} /* set_open_voting() */
/**
* This function defines a function to be called to determine if a player
* is eligible to vote.
*
* @param cond This will usually be a mixed array of an object and a function.
* It could also be a function pointer.
*
* Your function should return 1 if the player is eligible to vote or 0 if
* they are not.
*/
void set_vote_conditions(mixed cond) { _vote_cond = cond; }
/**
* This function defines a function to be called when the vote is complete.
* Typically your function will post the vote results somewhere or somesuch.
* The parameters passedinto the function are:<br>
* ( string type, string description, string* choices, mapping votes, string* voted) <br>
*
* @param cond This will usually be a mixed array of an object and a function.
* It could also be a function pointer.
*/
void set_completion_action(mixed cond) { _comp_action = cond; }
/**
* This function is used to set the normal vote and election duration. It can
* be overridden when calling add_vote. If it is not set and no duration is
* given in add_vote then the default value of DEFAULT_VOTE_DURATION is used.
*
* @param duraction The number of seconds the vote should be open.
*
* @see add_vote()
*/
void set_vote_duration(int duration) { _vote_duration = duration; }
/**
* This function is used to returns the normal vote and election duration.
* If it is not set and no duration is
* given in add_vote then the default value of DEFAULT_VOTE_DURATION is used.
*
* @param duraction The number of seconds the vote should be open.
*
* @see add_vote()
*/
int query_vote_duration() {
if (!_vote_duration) {
return DEFAULT_VOTE_DURATION;
}
return _vote_duration;
}
/**
* This function defines a function to be called to determine if a player
* is eligible to stand for election to a position.
*
* @param cond This will usually be a mixed array of an object and a function.
* It could also be a function pointer.
*
* Your function should return 1 if the player is eligible to stand or 0 if
* they are not.
*/
void set_stand_conditions(mixed cond) { _stand_cond = cond; }
/**
* This function defines a function to be called to determine if a player
* is eligible to second a canditate for election.
*
* @param cond This will usually be a mixed array of an object and a function.
* It could also be a function pointer.
*
* Your function should return 1 if the player is eligible to second or 0 if
* they are not.
*/
void set_second_conditions(mixed cond) { _second_cond = cond; }
/**
* This function defines the board, person and subject for announcements
* of elections.
*
* @param board This is the board to post to.
* @param person This is who to post as.
* @param subject This is the subject line to use.
* @param prefix The text to preceed the message. The default is
* "All eligible persons are requested to vote for the position "
* "of "
* @param suffix The text to follow the message.
*/
void set_election_announcement(string board, string person, string subject,
string prefix, string suffix) {
_election_announcement = ({ board, person, subject, prefix, suffix });
}
/**
* This function is called when a player votes. The syntax is
* "vote <choice> on <vote>".
*
* @see set_vote_conditions()
*/
int cast_vote( string which_str, int vote_id ) {
string pname;
int which, ok;
class vote this_vote;
if (_proxy) {
return _proxy->cast_vote(which_str, vote_id);
}
if ( undefinedp(query_our_votes()[vote_id])) {
add_failed_mess("There is no vote " + vote_id + ".\n");
return 0;
}
this_vote = query_our_votes()[vote_id];
which = member_array(which_str, this_vote->choices);
if (which == -1) {
if(strlen(which_str) > 1) {
add_failed_mess("There is no choice " + which_str + " for vote id " +
vote_id + ".\n");
return 0;
}
which = upper_case( which_str )[ 0 ] - 65;
}
if(which < 0 || which > sizeof(this_vote->choices) -1) {
add_failed_mess("There is no choice " + which_str + " for vote id " +
vote_id + ".\n");
return 0;
}
ok = 1;
if (functionp(_vote_cond)) {
ok = evaluate(_vote_cond, this_player(), this_vote->desc);
}
if(arrayp(_vote_cond)) {
ok = call_other(_vote_cond[0], _vote_cond[1], this_player(), this_vote->desc);
}
if(!intp(ok)) {
ok = 0;
}
if(!ok) {
add_failed_mess("Sorry, but you are not allowed to vote on this "
"subject.\n");
return 0;
}
pname = this_player()->query_name();
if(member_array(pname, this_vote->voted) != -1) {
add_succeeded_mess(({"You have already voted on this subject.\n", ""}));
return 1;
}
this_vote->voted += ({ pname });
if (_open_voting) {
this_vote->votes[which] += ({ pname });
} else {
if (arrayp(this_vote->votes[which])) {
this_vote->votes[which] = sizeof(this_vote->votes[which]);
}
this_vote->votes[which]++;
}
save_room();
add_succeeded_mess(({ "You cast your vote for "+which_str + " on " +
vote_id+".\n",
"$N casts a vote.\n" }));
return 1;
} /* cast_vote() */
/**
* This function is called when a player types 'list'. It lists the currently
* open votes.
*/
int list_votes() {
int i, j, *vote_ids;
string text;
class vote this_vote;
if ( !m_sizeof( query_our_votes() ) ) {
write( "There are no votes in progress.\n" );
return 1;
}
vote_ids = m_indices( query_our_votes() );
if ( sizeof( vote_ids ) > 1 )
write( "The following votes are in progress:\n" );
else
write( "The following vote is in progress:\n" );
text = "";
for ( i = 0; i < sizeof( vote_ids ); i++ ) {
this_vote = query_our_votes()[vote_ids[i]];
text += " "+ vote_ids[i] + ". " + this_vote->desc +"\n Choices are :\n";
for(j=0; j<sizeof(this_vote->choices); j++) {
text += sprintf(" %c. %s\n", 'A'+j, this_vote->choices[j]);
}
text += " Voting closes at "+ ctime( this_vote->ending ) +".\n\n";
}
this_player()->more_string(sprintf( "%-=*s",
(int)this_player()->query_cols(),
text ));
return 1;
}
/**
* This function is called to add a vote to the system.
*
* @param type Freeform string giving the type of vote. This is typically used
* by your completion function so that it can perform different actions for
* different types of votes.
*
* @param description The description of the vote as shown to the player.
*
* @param choices An array of vote options eg. ({"Yes", "No" })
*
* @param ending An integer time of when the vote should be terminated.
*
* @see set_completion_action()
*/
void add_vote(string type, string description, string *choices, int ending) {
class vote new_vote;
if (_proxy) {
return _proxy->add_vote(type, description, choices, ending);
}
vote_counter++;
new_vote = new(class vote);
new_vote->type = type;
new_vote->desc = description;
new_vote->choices = choices;
if (_open_voting) {
new_vote->votes = allocate(sizeof(choices), (: ({ }) :));
} else {
new_vote->votes = allocate(sizeof(choices));
}
if(ending) {
new_vote->ending = ending;
} else if(_vote_duration) {
new_vote->ending = (_vote_duration + time());
} else {
new_vote->ending = (DEFAULT_VOTE_DURATION + time());
}
new_vote->voted = ({ });
add_our_vote(vote_counter, new_vote);
//query_our_votes()[ vote_counter ] = new_vote;
call_out( "end_vote", new_vote->ending - time(), vote_counter );
save_room();
}
/**
* @ignore
*/
mapping query_votes() { return query_our_votes() + ([ ]); }
/**
* @ignore
*/
mapping query_elections() { return query_our_elections() + ([ ]); }
/**
* This function is called to terminate a vote. It calls your completion
* action.
*
* @see set_completion_action()
*/
void end_vote( int which ) {
if ( !query_our_votes()[ which ] ) {
return;
}
if(functionp(_comp_action)) {
evaluate(_comp_action, query_our_votes()[which]->type, query_our_votes()[which]->desc,
query_our_votes()[which]->choices, query_our_votes()[which]->votes,
query_our_votes()[which]->voted);
} else if(arrayp(_comp_action)) {
call_other(_comp_action[0], _comp_action[1], query_our_votes()[which]->type,
query_our_votes()[which]->desc, query_our_votes()[which]->choices, query_our_votes()[which]->votes,
query_our_votes()[which]->voted);
}
map_delete(query_our_votes(), which);
save_room();
}
/**
* This function is provided for convenience to make it easy for your vote
* end function to post to a board.
*
* @param board The name of the board to post to.
* @param name The name of the person to post as.
* @param subject The subject line to use.
* @param message The message to post.
*/
void make_announcement(string board, string name, string subject,
string message ) {
BOARD_HAND->add_message(board, name, subject,
sprintf( "%-=*s", 64, message));
}
/**
* This function is used to initiate an election.
* It sets up the election class and adds it to the mapping.
*
* @param position The name of the position the election is for.
*/
void initiate_election(string position) {
class election tmp;
if (_proxy) {
return _proxy->initiate_election(position);
}
if(query_our_elections()[position])
return;
tmp = new(class election);
tmp->candidates = ({});
if(_vote_duration) {
tmp->closes = time() + _vote_duration;
} else {
tmp->closes = time() + DEFAULT_VOTE_DURATION;
}
add_our_election(position, tmp);
call_out("start_election_vote", tmp->closes + 60, position);
save_room();
} /* initiate_election() */
/**
* This method determines if there is already an election of the
* specified type in progress.
* @param position the name of the election in progress
* @return 1 if there is an election in progress
*/
int query_election_in_progress(string position) {
if (classp(query_our_elections()[position])) {
return 1;
}
return 0;
} /* query_election_in_progress() */
/**
* This function is used by players to stand for election. The syntax is:
* "stand for <position>".
*
* @see set_stand_conditions()
*/
int stand_for_election(string position) {
int ok, i;
if (_proxy) {
return _proxy->stand_for_election(position);
}
// check if they're eligible.
ok = 1;
if(functionp(_stand_cond)) {
ok = evaluate(_stand_cond, this_player(), position);
} else if(arrayp(_stand_cond)) {
ok = call_other(_stand_cond[0], _stand_cond[1], this_player(), position);
}
if(!intp(ok))
ok = 0;
if(!ok) {
add_succeeded_mess(({ "Sorry, but you are not allowed to stand for this "
"election.\n", ""}));
return 1;
}
// check if there is an election for this position.
if(!query_our_elections()[position]) {
add_succeeded_mess(({"There is no election in progress for the post of " +
position + ".\n", ""}));
return 1;
}
// check if they're already a candidate for this position.
for(i=0; i<sizeof(query_our_elections()[position]->candidates); i++) {
if(query_our_elections()[position]->candidates[i][0] == this_player()->query_name()) {
add_succeeded_mess(({"You are already standing in this "
"election.\n", ""}));
return 1;
}
}
// add them to the list of candidates.
query_our_elections()[position]->candidates += ({({ this_player()->query_name(), "" })});
save_room();
add_succeeded_mess(({"You have been added to the list of candidates for "
"the position of " + position + ". You must now "
"find someone to second your candidacy.\n",
"$N stands for election.\n"}));
return 1;
}
/**
* This function is used by players to second candidates for election. If a
* candidates isn't seconded he/she won't be in the election. The syntax is:
* "second <player> for <position>".
*
* @see set_second_conditions()
*/
int second_candidate(string who, string position) {
mixed *candidates;
int i, found, ok;
if (_proxy) {
return _proxy->second_candidate(who, position);
}
// check if they're eligible.
ok = 1;
if(functionp(_second_cond)) {
ok = evaluate(_second_cond, this_player(), position);
} else if(arrayp(_second_cond)) {
ok = call_other(_second_cond[0], _second_cond[1], this_player(), position);
}
if(!intp(ok))
ok = 0;
if(!ok) {
add_succeeded_mess(({"Sorry, but you are not allowed to second candidates "
"in this election.\n", ""}));
return 1;
}
// check the position is up for election
if(!query_our_elections()[position]) {
add_succeeded_mess(({"There is no election in progress for " + position +
".\n", ""}));
return 1;
}
// check the candidate is standing
candidates = (query_our_elections()[position])->candidates;
for(i=0; i< sizeof(candidates); i++) {
if(candidates[i][0] == who)
found = i+1;
}
if(!found) {
add_succeeded_mess(({who + " is not standing for the position of " +
position + ".\n", ""}));
return 1;
}
if(this_player()->query_name() == who) {
add_succeeded_mess(({"You cannot second yourself.\n", ""}));
return 1;
}
if(candidates[found-1][1] != "") {
add_succeeded_mess(({candidates[found-1][0] +
" has already been seconded by " +
candidates[found-1][1] + ".\n", ""}));
return 1;
}
// mark the candidate as seconded (supported).
candidates[found-1][1] = this_player()->query_name();
save_room();
add_succeeded_mess(({candidates[found-1][0] +
" has been seconded in the election for "+
position+".\n", ""}));
return 1;
}
/**
* Once the candidacy phase is over this function starts the election vote
* itself by taking all eligible candidates, setting them as choices in the
* election and then posting an announcemment.
*/
void start_election_vote(string post) {
string str, *choices;
int i;
if(!query_our_elections()[post])
return;
if(_election_announcement[3])
str = _election_announcement[3];
else
str = "All eligible persons are requested to vote for the position of ";
str += post + "\n\nThe candidates are:\n";
choices = ({ });
for(i=0; i<sizeof(query_our_elections()[post]->candidates); i++) {
if(query_our_elections()[post]->candidates[i][1] != "") {
str += sprintf(" %s seconded by %s.\n",
query_our_elections()[post]->candidates[i][0],
query_our_elections()[post]->candidates[i][1]);
choices += ({ query_our_elections()[post]->candidates[i][0] });
}
}
if(!sizeof(choices)) {
str = "In the election for the position of " + post +
" no candidate stood for election therefore the election "
"is null and void.\n";
} else if(sizeof(choices) < 2) {
str = "In the election for the position of " + post +
" only one candidate stood for election therefore the election "
"is null and void.\n";
} else {
if(_election_announcement[4])
str += _election_announcement[4];
add_vote("election", "Election for the post of " + post + "\n", choices,
0);
}
make_announcement(_election_announcement[0], _election_announcement[1],
_election_announcement[2], str);
map_delete(query_our_elections(), post);
save_room();
}
/**
* This function is called when a player types 'elections'. It lists the
* elections currently accepting candidates.
*/
int list_elections() {
class election this_election;
string *posts, text;
int i, j;
if ( !m_sizeof( query_our_elections() ) ) {
write( "There are no elections in progress.\n" );
return 1;
}
posts = m_indices( query_our_elections() );
if ( sizeof( posts ) > 1 )
write( "The following elections are in progress:\n" );
else
write( "The following election is in progress:\n" );
text = "";
for ( i = 0; i < sizeof( posts ); i++ ) {
this_election = query_our_elections()[posts[i]];
text += " Election to the post of "+ posts[i] + ".\n";
if(!sizeof(this_election->candidates)) {
text += " No candidates have declared yet.\n";
} else {
text += " Current candidates are :\n";
for(j=0; j<sizeof(this_election->candidates); j++) {
if(this_election->candidates[j][1] != "")
text += sprintf(" %c. %s seconded by %s.\n", 'A'+j,
this_election->candidates[j][0],
this_election->candidates[j][1]);
else
text += sprintf(" %c. %s not yet seconded.\n", 'A'+j,
this_election->candidates[j][0]);
}
}
text += " Candidacies must be declared by "+
ctime( this_election->closes ) +".\n\n";
}
this_player()->more_string(sprintf( "%-=*s",
(int)this_player()->query_cols(),
text ));
return 1;
}
/** @ignore yes */
string strip_spaces(string str) {
if (!strlen(str)) {
return str;
}
while (str[0] == ' ') {
str = str[1..];
}
if (!strlen(str)) {
return str;
}
while (str[<1] == ' ') {
str = str[0..<2];
}
return str;
} /* strip_spaces() */
/**
* This is an option function which can be defined in upper level inherits.
* It allows the players to create their own votes with their own choices.
*/
int do_create_vote(string choices) {
string* bits;
bits = map(explode(choices, ","), (: strip_spaces($1) :)) - ({ "" });
write("Choices: " + query_multiple_short(bits) + ".\n");
write("What description would you like for your vote?\n");
this_player()->do_edit("", "create_vote_desc", this_object(), 0, bits);
add_succeeded_mess(({ "", "$N starts to create a new vote.\n" }));
return 1;
} /* do_create_vote() */
/** @ignore yes */
void create_vote_desc(string str, string* choices) {
if (!str) {
write("Aborting.\n");
return ;
}
write("Are you sure you wish to create a vote with a description of:\n" +
str + "\nWith vote choices of " + query_multiple_short(choices) +
".\n");
write("Please answer yes or no: ");
input_to("create_vote_desc_confirm", 0, str, choices);
} /* create_vote_desc() */
/** @ignore yes */
void create_vote_desc_confirm(string str, string desc, string* choices) {
str = lower_case(str);
if (!strlen(str) ||
(str[0] != 'y' && str[0] != 'n' && str[0] != 'q')) {
write("Please answer yes or no: ");
input_to("create_vote_desc_confirm", 0, str, choices);
}
if (str[0] == 'q' || str[0] == 'n') {
write("Ok, quitting.\n");
return ;
}
//
// Now we add the vote.
//
add_vote("freeform", desc, choices, 0);
write("Added in the vote.\n");
} /* create_vote_desc_confirm() */
/**
* This is an administrative function to allow the removal/cancellation
* of an election.
* @param election The name of the election to be cancelled
* @return Returns 1 for success, 0 for failure.
*/
int delete_election(string election) {
if(!query_our_elections()[election])
return 0;
map_delete(query_our_elections(), election);
save_room();
return 1;
}
/**
* This is an administrative function to allow the removal/cancellation
* of a vote.
* @param vote_id The id number of the vote to be cancelled.
* @return Returns 1 for success, 0 for failure.
*/
int delete_vote(int vote_id) {
if(!query_our_votes()[vote_id])
return 0;
map_delete(query_our_votes(), vote_id);
save_room();
return 1;
}