/*
// File: /d/Conf/voting_room.c
// Purpose: A generic inheritable voting room
// Credits:
// 93-03-16 Douglas Reay (Pallando @TMI and other muds) wrote it.
// This is not part of TMI's distribution, but may be used by
// others, at the discretion of the author, on the same terms.
// 93-06-28 Pallando split off the stuff specific to TMI's conferences
*/
#include <config.h>
#include <mudlib.h>
inherit ROOM;
#define DEFAULT_SAVE_FILE TMP_DIR "votes"
#define TMP_FILE TMP_DIR + geteuid( this_player() ) + ".prop"
#define DEFAULT_OPTIONS ({ "endorse", "reject", "abstain" })
mapping vote_data;
mapping new_data;
string save_file;
string *default_options;
// room_create() has to be called after the restore_object in create() so that
// the room description can be changed without losing the vote data.
void room_create()
{
set( "light", 1 );
set( "short", "A generic voting room" );
set( "long", @EndText
The long description of a generic voting room.
Type "help room" to get aid on the facilities available.
EndText
);
}
// To alter the save_file in a voting room inheriting this one, add a function
// create() { save_file = <...>; ::create(); }
// See at TMI-2 /d/Conf/room/conf_voting_room.c for an example.
void create()
{
::create();
seteuid( getuid() );
if( !save_file ) save_file = DEFAULT_SAVE_FILE;
restore_object( save_file );
if( !vote_data ) vote_data = ([]);
if( !new_data ) new_data = ([]);
if( !default_options ) default_options = DEFAULT_OPTIONS;
room_create();
}
int is_convener()
{
return wizardp( this_player() );
}
string is_voter()
{
return capitalize( geteuid( this_player() ) );
}
void init()
{
add_action( "cmd_help", "help" );
add_action( "cmd_list", "list" );
if( is_voter() )
{
add_action( "cmd_vote", "vote" );
add_action( "cmd_propose", "propose" );
}
if( !is_convener() ) return;
add_action( "cmd_nlist", "nlist" );
add_action( "cmd_verify", "verify" );
add_action( "cmd_proposer", "proposer" );
add_action( "cmd_unverify", "unverify" );
add_action( "cmd_edit", "edit" );
add_action( "cmd_options", "options" );
add_action( "cmd_proxy", "proxy" );
}
/************************ Functions anyone can use ************************/
int cmd_help( string a )
{
if( a != "room" ) return 0;
write( ""+
"help room this message\n"+
"list lists the proposals\n"+
"list <p> lists proposal <p>\n"+
"" );
if( is_voter() ) write( ""+
"vote <p> <o> Vote option <o> on proposal <p>\n"+
"vote <p> Withdraw your vote on proposal <p>\n"+
"propose <p> Make a new proposal <p> (subject to convener verification)\n"+
"" );
if( is_convener() ) write( ""+
"nlist <p> List the unverified proposals.\n"+
"verify <p> Move proposal <p> to the verified list.\n"+
"verify <p> as <new_p> Rename proposal <p>\n"+
"unverify <p> Move proposal <p> to the unverified list.\n"+
" (Or, if <p> is already unverified, delete it.)\n"+
"proposer <p> <name> Set the proposer of proposal <p> to <name>\n"+
"edit <p> Edit text of proposal <p>\n"+
"options <p> <o>,<o>,... Set the options for proposal <p> to <o>,<o>...\n"+
"proxy <voter> votes <p> <o> Vote for someone else.\n"+
"" );
return 1;
}
int cmd_list( string a )
{
string *names, option, *voters, ret;
mapping proposal;
int i, j;
names = keys( vote_data );
if( !sizeof( names ) )
{
write( "There are no current proposals.\n" );
return 1;
}
if( !a )
{
ret = "";
for( i = sizeof( names ) ; i ; i-- )
{
proposal = vote_data[names[(i-1)]];
ret += names[(i-1)] + "\tproposed by " + proposal["proposer"] + "\n";
ret += proposal["text"];
ret += wrap( "Valid options: " + implode( proposal["options"], ", " ) );
}
this_player()-> more( explode( ret, "\n" ) );
return 1;
}
proposal = vote_data[a];
if( !mapp( proposal ) )
{
write( wrap( "Current proposals are: " + implode( names, ", " ) ) );
return 1;
}
write( a + "\tproposed by " + proposal["proposer"] + "\n" );
write( proposal["text"] );
write( wrap( "Valid options: " + implode( proposal["options"], ", " ) ) );
if( !sizeof( keys( proposal["votes"] ) ) )
{
write( "No votes yet cast on it.\n" );
} else {
i = sizeof( proposal["options"] );
if( i > 1 ) write( "Total votes cast = " +
sizeof( keys( proposal["votes"] ) ) + "\n" );
for( ; i ; i-- )
{
option = proposal["options"][(i-1)];
voters = proposal["cast"][option];
j = sizeof( voters );
write( "Option: "+option+" - "+j+" vote"+((j-1)?"s":"")+"\n" );
if( j ) write( " "+implode(voters,"\n ")+"\n" );
}
}
return 1;
}
/************************ Functions voters can use ************************/
// The optional second argument is used by cmd_proxy()
varargs int cmd_vote( string a, string voter )
{
string name, option, cast;
string *names;
mapping proposal;
if( !a )
{ notify_fail( "Syntax: vote <proposal> <option>\n" ); return 0; }
if( sscanf( a, "%s %s", name, option ) < 2 )
{ name = a; option = 0; }
names = keys( vote_data );
if( !sizeof( names ) )
{
write( "There are no current proposals.\n" );
return 1;
}
if( -1 == member_array( name, names ) )
{
write( wrap( "The current proposals are: " + implode( names, ", " ) ) );
return 1;
}
proposal = vote_data[name];
if( !voter ) voter = is_voter();
if( !option )
{
if( cast = proposal["votes"][voter] )
{
proposal["cast"][cast] -= ({ voter });
map_delete( proposal["votes"], voter );
vote_data[name] = proposal;
save_object( save_file );
write( "You uncast your vote on proposal " + name + "\n" );
return 1;
}
write( "You have no vote cast on proposal " + name + "\n" );
return 1;
}
if( -1 == member_array( option, proposal["options"] ) )
{
write( wrap( "The valid options are: " +
implode( proposal["options"], ", " ) ) );
return 1;
}
if( cast = proposal["votes"][voter] )
proposal["cast"][cast] -= ({ voter });
proposal["votes"][voter] = option;
proposal["cast"][option] += ({ voter });
vote_data[name] = proposal;
save_object( save_file );
write( "You vote " + option + " on proposal " + name + "\n" );
return 1;
}
int cmd_propose( string a )
{
string tmp;
if( !a ) { notify_fail( "Syntax: propose <name>\n" ); return 0; }
if( -1 != member_array( a, keys( vote_data ) + keys( new_data ) ) )
{ notify_fail( "There is already a proposal named "+a+"\n" ); return 0; }
notify_fail( "You may not have spaces in the proposal name.\n" );
if( sscanf( a, "%*s %s", tmp ) ) return 0;
write( "Enter the text of your proposal.\n" );
this_player()-> edit( TMP_FILE, "callback_propose", this_object(), a );
return 1;
}
// This function is used by cmd_propose()
int callback_propose( string a )
{
mapping proposal;
string text;
proposal = ([]);
text = read_file( TMP_FILE );
rm( TMP_FILE );
if( !text ) { notify_fail( "No proposal entered.\n" ); return 0; }
proposal["text"] = text;
proposal["proposer"] = is_voter();
proposal["options"] = default_options;
// This duplicates the get_options() incase someone's input_to is interupted.
new_data[a] = proposal;
write( "Your proposal will be added when a convener verifies it.\n" );
save_object( save_file );
write( "Press <return> for the options to be "+
implode( default_options, ", " ) + "\n" +
"Otherwise, enter your new options, seperating each one with a comma\n"+
"New options = " );
input_to( "get_options", ({ 0, a }) );
return 1;
}
// This function initalises a proposal to accept votes on its options,
// If any votes have already been cast then these are cleared.
// Used by get_options() and cmd_verify()
mapping init_votes( mapping proposal )
{
int i;
proposal["votes"] = ([]);
proposal["cast"] = ([]);
if( !proposal["options"] ) proposal["options"] = default_options;
for( i = sizeof( proposal["options"] ) ; i ; i-- )
proposal["cast"][( proposal["options"][(i-1)] )] = ({ });
return proposal;
}
// This function is used by callback_propose() and cmd_options()
int get_options( string opts, mixed args )
{
string *options;
mapping proposal;
if( opts ) options = explode( opts, "," );
else options = default_options;
proposal = ( args[0] ? vote_data[(args[1])] : new_data[(args[1])] );
proposal["options"] = options;
proposal = init_votes( proposal );
if( args[0] ) vote_data[(args[1])] = proposal;
else new_data[(args[1])] = proposal;
write( wrap( "The options for proposal " + args[1] + " are: " +
implode( options, ", " ) ) );
save_object( save_file );
return 1;
}
/************************ Functions conveners can use ************************/
int cmd_nlist( string a )
{
if( a && !undefinedp( new_data[a] ) )
write( wrap( identify( new_data[a] ) ) );
else if( sizeof( keys( new_data ) ) )
write( wrap( implode( keys( new_data ), " " ) ) );
else write( "There are no unverified proposals.\n" );
return 1;
}
int cmd_verify( string a )
{
mapping proposal;
string name, new_name;
int i;
if( !a )
{
notify_fail( "Syntax: verify <name>[ as <new_name>]\n" );
return 0;
}
if( sscanf( a, "%s as %s", name, new_name ) < 2 )
{ name = a; new_name = a; }
if( !new_data[name] )
{
if( vote_data[name] && ( name != new_name ) )
{
vote_data[new_name] = vote_data[name];
map_delete( vote_data, name );
write( "Proposal " + name + " renamed as " + new_name + "\n" );
save_object( save_file );
return 1;
}
notify_fail( "I do not recognise " + name + " as the name of an "+
"unverified proposal.\n" );
return 0;
}
proposal = init_votes( new_data[name] );
vote_data[new_name] = proposal;
map_delete( new_data, name );
write( wrap( "ADDING PROPOSAL " + new_name + "\n" + identify( proposal ) ) );
save_object( save_file );
return 1;
}
int cmd_proposer( string a )
{
string name, proposer;
if( !a || sscanf( a, "%s %s", name, proposer ) <2 )
{
notify_fail( "Syntax: proposer <proposal> <proposer>\n" );
return 0;
}
if( !vote_data[name] )
{
notify_fail( name + " is not a verified proposal.\n" );
return 0;
}
vote_data[name]["proposer"] = proposer;
save_object( save_file );
write( "Ok.\n" );
return 1;
}
int cmd_unverify( string a )
{
if( !a ) { notify_fail( "Syntax: unverify <proposal_name>\n" ); return 0; }
if( new_data[a] )
{
map_delete( new_data, a );
save_object( save_file );
write( "The unverified proposal " + a + " now deleted.\n" );
return 1;
}
if( !vote_data[a] )
{
notify_fail( "No such proposal: " + a + "\n" );
return 0;
}
new_data[a] = vote_data[a];
map_delete( vote_data, a );
save_object( save_file );
write( "Proposal " + a + " moved to unverified list.\n" );
return 1;
}
int cmd_edit( string a )
{
mapping proposal;
if( !a ) { notify_fail( "Syntax: edit <p>\n" ); return 0; }
proposal = vote_data[a];
if( !proposal ) { notify_fail( "No such proposal.\n" ); return 0; }
rm( TMP_FILE );
write_file( TMP_FILE, proposal["text"] );
write( "Edit the text of the proposal:\n" );
this_player()-> edit( TMP_FILE, "callback_edit", this_object(), a );
return 1;
}
// This function is used by cmd_edit()
int callback_edit( string a )
{
string text;
text = read_file( TMP_FILE );
rm( TMP_FILE );
if( !text )
{
write( "Unverifying proposal.\n" );
new_data[a] = vote_data[a];
map_delete( vote_data, a );
save_object( save_file );
return 1;
}
vote_data[a]["text"] = text;
save_object( save_file );
write( "Ok.\n" );
return 1;
}
int cmd_options( string a )
{
string proposal_name, options;
int veri;
if( !a || ( sscanf( a, "%s %s", proposal_name, options ) < 2 ) )
{
notify_fail( "Syntax: options <p> <o1>,<o2>,<o3>,...\n" );
return 0;
}
if( !undefinedp( vote_data[proposal_name] ) ) veri = 1;
else if( !undefinedp( new_data[proposal_name] ) ) veri = 0;
else { notify_fail( proposal_name + " is not a proposal.\n" ); return 0; }
get_options( options, ({ veri, proposal_name }) );
return 1;
}
int cmd_proxy( string a )
{
string voter, vote;
if( !a || ( sscanf( a, "%s votes %s", voter, vote ) < 2 ) )
{
notify_fail( "Syntax: proxy <voter> votes <proposal> <option>\n" );
return 0;
}
return cmd_vote( vote, voter );
}
/************************ Functions debuggers can use ************************/
mixed query_vote_data( mixed a )
{
if( !is_convener() ) return;
if( a ) return vote_data;
write( "vote_data = " + dump_variable( vote_data ) + "\n" );
// If you don't have the dump_variable() simul_efun then don't worry
// ... it does much the same as identify()
return 1;
}
mixed query_new_data() { if( is_convener() ) return new_data; }
void set_new_data( mapping a ) { if( is_convener() ) new_data = a; }
void set_vote_data( mapping a ) { if( is_convener() ) vote_data = a; }
// NB If you had confidence about not losing data due to crashes, you could
// delete all the save_object() calls and put one in the remove() function.