Take the 2-minute tour ×
Programming Puzzles & Code Golf Stack Exchange is a question and answer site for programming puzzle enthusiasts and code golfers. It's 100% free, no registration required.

Illustration drawn by brother Illustrated by Chris Rainbolt, my brother and a fresh graduate from Savannah College of Art and Design

Introduction

The angels and demons are fighting and, as usual, using earth as their battleground. Humans are stuck in the middle and are being forced to take sides. An unknown neutral force rewards those who consistently fight for the losing side.

The Game

Each round, you will be passed an input and be expected to produce output. Your output will be recorded and scored. This process will be repeated 1000 times.

Input

You will receive a single argument that represents the past votes of every player. Rounds are delimited by comma. A 0 represents a player who sided with Evil that round. A 1 represents a player who sided with Good. The players will always be in the same order. Your own vote will be included, but not explicitly identified. For example:

101,100,100

In this example, three rounds have been completed so far and three players are competing. Player one always sided with Good. Player two always sided with Evil. Player three swapped from Good in round 1 to Evil in rounds 2 and 3. One of those players was you.

Output

  • Output the string good to stdout if you want to side with Good.
  • Output the string evil to stdout if you want to side with Evil.

If your program outputs anything else, throws an exception, does not compile, or takes longer than one second to output anything on this exact machine, then it will be disqualified.

Scoring

  • You receive 3 points for siding with the majority during a round.
  • You receive n - 1 points for siding with the minority during a round, where n is the number of consecutive times you have sided with the minority.

The submission with the most points after 1000 rounds wins.

Deliverables

Non-Java Submissions

You must submit a title, a program, and Windows command line string that will run your program. Remember that an argument may be appended to that string. For example:

  • python Angel.py
    • Note that this one has no args. This is round one! Be prepared for this.
  • python Angel.py 11011,00101,11101,11111,00001,11001,11001

Java Submissions

You must submit a title and a Java class that extends the abstract Human class written below.

public abstract class Human {
    public abstract String takeSides(String history) throws Exception;
}

Testing

If you want to test your own submission, follow the instructions here.

Additional Notes

You may submit as many different submissions as you want. Submissions that appear to be colluding will be disqualified. The author of this challenge will be the only judge on that matter.

A new instance of your program or Java class will be created every time it is called upon. You may persist information by writing to a file. You may not modify the structure or behavior of anything except your own class.

Players will be shuffled before the trial starts. Demon and Angel will participate in every trial. If the number of players is even, Petyr Baelish will also join. Demon fights for Evil, Angel for Good, and Petyr Baelish chooses a pseudorandom side.

share|improve this question
1  
Is the most recent round the first number of the input string, or the last? –  isaacg yesterday
    
@isaacg Good question. I edited the example to reflect that the first number is the first round. At the same time, I noticed that the example only had 2 players (which can't possibly happen). –  Rusher yesterday
1  
In Scoreboard.java, scoresToCSVString(), round < currentRound has to be round <= currentRound or the last round will no be counted... –  Manu 14 hours ago
1  
@manu Thanks for pointing out the bugs. Please proceed as if what was written in the spec is correct. I'll try to fix them within the hour. –  Rusher 9 hours ago
2  
@Manu Bugs fixed. A link to the updated version is in the challenge description. The Scoreboard was almost completely dysfunctional. The majority was calculated incorrectly, the CSV output was incorrect, the command line output was incorrect and misformatted, and the final scores were wrong. I don't know what I was doing. –  Rusher 3 hours ago
show 17 more comments

32 Answers

Petyr Baelish

You never know whose side Petyr Baelish is on.

package Humans;

/**
 * Always keep your foes confused. If they are never certain who you are or 
 * what you want, they cannot know what you are like to do next.
 * @author Rusher
 */
public class PetyrBaelish extends Human {

    /**
     * Randomly take the side of good or evil.
     * @param history The past votes of every player
     * @return A String "good" or "evil
     */
    public String takeSides(String history) {
        return Math.random() >= 0.5 ? "good" : "evil";
    }
}

This entry will only be included if the number of players is even. This ensures that there will always be a majority.

share|improve this answer
13  
On Petyr Baelish's side, obviously. –  Cthulhu 15 hours ago
    
I doubt this will do well. You either want to be on the winning side or the losing side consistently, and Peter will change sides too quickly to win regularly or take advantage of the bonus for consistently choosing the losing side. –  Kevin 3 hours ago
1  
@Kevin This entry was submitted by the author of the challenge. It wasn't meant to do well. It exists to make sure that there will always be a majority, because with an even number of players, there could be a tie. –  Rusher 3 hours ago
2  
Why oh God why has this one got the most votes? It's just not fair. –  tomsmeding 2 hours ago
2  
@tomsmeding No. It's a quote from Game of Thrones lol. –  Rusher 2 hours ago
show 3 more comments

Artemis Fowl

package Humans;

public class ArtemisFowl extends Human {
    public String takeSides(String history) {
        int good = 0, evil = 0;
        for(int i = 0; i < history.length(); i++)   {
            switch(history.charAt(i))   {
                case '0': evil++; break;
                case '1': good++; break;
            }
        }
        if(good % 5 == 0){
           return "good";
        } else if (evil % 5 == 0){
           return "evil";
        } else {
           if(good > evil){
              return "good";
           } else if(evil > good){
              return "evil";
           } else {
              return Math.random() >= 0.5 ? "good" : "evil";
           }
        }
    }
}

In Book 7, The Atlantis Complex, Artemis Fowl contracted a psychological disease (called Atlantis complex) that forced him to do everything in multiples of 5 (speaking, actions, etc). When he couldn't do it in some multiple of 5, he panicked. I do basically that: see if good or evil (intentional bias) is divisible by 5, if neither is, then I panic & see which was greater & run with that or panic even further & randomly choose.

share|improve this answer
2  
When I read Artemis Fowl in Junior High, only two books existed. It's nice to see that there are now seven, and that Disney is making it into a movie. –  Rusher 22 hours ago
1  
There's actually 8 books. –  Kyle Kanos 22 hours ago
4  
The more the merrier (unless you are reading The Wheel of Time) –  Rusher 22 hours ago
1  
And you forgot break; in your switch. –  johnchen902 10 hours ago
1  
@johnchen902,@Manu: I am not very experienced in java (I use Fortran90+ & only see java here), hence my errors. I'll fix them when I get into the office in an hour. –  Kyle Kanos 10 hours ago
show 3 more comments

C++, The Meta Scientist

This one does essentially the same as The Scientist, but doesn't operate on rounds as a whole but on the individual players. It tries to map a wave (or a constant function) to each player separately and predicts their move in the next round. From the resulted round prediction, The Meta Scientist chooses whichever side looks like having a majority.

#include <iostream>
#include <utility>
#include <cstdlib>
#include <cstring>
#if 0
#define DBG(st) {st}
#else
#define DBG(st)
#endif

using namespace std;

int main(int argc,char **argv){
    if(argc==1){
        cout<<(rand()%2?"good":"evil")<<endl;
        return 0;
    }
    int nump,numr;
    nump=strchr(argv[1],',')-argv[1];
    numr=(strlen(argv[1])+1)/(nump+1);
    int period,r,p;
    int score,*scores=new int[numr];
    int max; //some score will always get above 0, because if some score<0, the inverted wave will be >0.
    int phase,phasemax;
    int predicted=0; //The predicted number of goods for the next round
    pair<int,int> maxat; //period, phase
    DBG(cerr<<"Players:"<<endl;)
    for(p=0;p<nump;p++){
        DBG(cerr<<" p"<<p<<": ";)
        for(r=0;r<numr;r++)if(argv[1][r*(nump+1)+p]!=argv[1][p])break;
        if(r==numr){
            DBG(cerr<<"All equal! prediction="<<argv[1][p]<<endl;)
            predicted+=argv[1][p]-'0';
            continue;
        }
        max=0;
        maxat={-1,-1};
        for(period=1;period<=numr;period++){
            scores[period-1]=0;
            phasemax=-1;
            for(phase=0;phase<2*period;phase++){
                score=0;
                for(r=0;r<numr;r++){
                    if(argv[1][r*(nump+1)+p]-'0'==1-(r+phase)%(2*period)/period)score++;
                    else score--;
                }
                if(score>scores[period-1]){
                    scores[period-1]=score;
                    phasemax=phase;
                }
            }
            if(scores[period-1]>max){
                max=scores[period-1];
                maxat.first=period;
                maxat.second=phasemax;
            }
            DBG(cerr<<scores[period-1]<<" ";)
        }
        DBG(cerr<<"(max="<<max<<" at {"<<maxat.first<<","<<maxat.second<<"})"<<endl;)
        DBG(cerr<<"     prediction: 1-("<<numr<<"+"<<maxat.second<<")%(2*"<<maxat.first<<")/"<<maxat.first<<"="<<(1-(numr+maxat.second)%(2*maxat.first)/maxat.first)<<endl;)
        predicted+=(1-(numr+maxat.second)%(2*maxat.first)/maxat.first);
    }
    DBG(cerr<<"Predicted outcome: "<<predicted<<" good + "<<(nump-predicted)<<" evil"<<endl;)
    if(predicted>=nump/2)cout<<"good"<<endl;
    else cout<<"evil"<<endl;
    delete[] scores;
    return 0;
}

If you want to turn on debug statements, change the line reading #if 0 to #if 1.

Compile with g++ -O3 -o MetaScientist MetaScientist.cpp (you don't need warnings, so no -Wall) and run with MetaScientist.exe (possibly including the argument of course). If you ask really nicely I can provide you with a Windows executable.

share|improve this answer
    
I humbly suggest you try picking the losing side. If you can consistently guess correctly, you'll get more than 3 points per round. –  Kevin 3 hours ago
    
@Kevin That's true, but I figured that it might guess the wrong side pretty quickly, and you need to correctly guess the losing side more than seven times in a row to get an improvement over always getting the majority right. I might change it though. –  tomsmeding 2 hours ago
    
@Kevin Also, I'd first like to see how these do (Scientist and Meta Scientist) when Rusher gets us a scoreboard this weekend, as he indicated in the comments to the OP. Rusher, sorry, but I'm too lazy to compile all the stuff myself... :) –  tomsmeding 2 hours ago
1  
No worries! It probably isn't safe to run these anyway. Just let me screw up my machine with code written by 50 strangers on the Internet. –  Rusher 2 hours ago
1  
@Kevin But that's so MANY! I can, indeed, but I don't like it. I'll see how these fare. –  tomsmeding 2 hours ago
show 3 more comments

C++, The Scientist

This one tries to, with the history of what the majority chose per round in wave (majority() gives the majority's choice on a round), fit a wave to the data, of wavelength 2*period and phase phase. Thus, given 0,1,1,1,0,1,0,1,1,1,0,0,0,1,0 it selects period=3, phase=5 (maxat=={3,5}): its scores become 9 3 11 5 5 3 5 7 9 7 7 7 7 7 7. It loops over all possible periods and if, for that period, the score is higher than for the current maximum, it stores {period,phase} for which that occurred.

It then extrapolates the found wave to the next round and takes the predicted majority.

#include <iostream>
#include <utility>
#include <cstdlib>
#include <cstring>

using namespace std;

int majority(const char *r){
    int p=0,a=0,b=0;
    while(true){
        if(r[p]=='1')a++;
        else if(r[p]=='0')b++;
        else break;
        p++;
    }
    return a>b;
}

int main(int argc,char **argv){
    if(argc==1){
        cout<<(rand()%2?"good":"evil")<<endl;
        return 0;
    }
    int nump,numr;
    nump=strchr(argv[1],',')-argv[1];
    numr=(strlen(argv[1])+1)/(nump+1);
    int period,r;
    int *wave=new int[numr];
    bool allequal=true;
    cerr<<"wave: ";
    for(r=0;r<numr;r++){
        wave[r]=majority(argv[1]+r*(nump+1));
        if(wave[r]!=wave[0])allequal=false;
        cerr<<wave[r]<<" ";
    }
    cerr<<endl;
    if(allequal){
        cerr<<"All equal!"<<endl;
        if(numr>=50){ //if it's stable, minority
            if(wave[0]==1)cout<<"evil"<<endl;
            else cout<<"good"<<endl;
        } else { //else, majority for safety
            if(wave[0]==1)cout<<"good"<<endl;
            else cout<<"evil"<<endl;
        }
        return 0;
    }
    int score,*scores=new int[numr];
    int max=0; //some score will always get above 0, because if some score<0, the inverted wave will be >0.
    int phase,phasemax;
    pair<int,int> maxat(-1,-1); //period, phase
    cerr<<"scores: ";
    for(period=1;period<=numr;period++){
        scores[period-1]=0;
        phasemax=-1;
        for(phase=0;phase<2*period;phase++){
            score=0;
            for(r=0;r<numr;r++){
                if(wave[r]==1-(r+phase)%(2*period)/period)score++;
                else score--;
            }
            if(score>scores[period-1]){
                scores[period-1]=score;
                phasemax=phase;
            }
        }
        if(scores[period-1]>max){
            max=scores[period-1];
            maxat.first=period;
            maxat.second=phasemax;
        }
        cerr<<scores[period-1]<<" ";
    }
    cerr<<"(max="<<max<<" at {"<<maxat.first<<","<<maxat.second<<"})"<<endl;
    cerr<<" max: ("<<numr<<"+"<<maxat.second<<")%(2*"<<maxat.first<<")/"<<maxat.first<<"=="<<((numr+maxat.second)%(2*maxat.first)/maxat.first)<<endl;
    if((numr+maxat.second)%(2*maxat.first)/maxat.first==0)cout<<"good"<<endl;
    else cout<<"evil"<<endl;
    delete[] wave;
    delete[] scores;
    return 0;
}

Compile with g++ -O3 -o Scientist Scientist.cpp (you don't need warnings, so no -Wall) and run with Scientist.exe (possibly including the argument of course). If you ask really nicely I can provide you with a Windows executable.

Oh, and don't dare messing with the input format. It'll do strange things otherwise.

share|improve this answer
add comment

Hipster, Ruby

if ARGV.length == 0
    puts ["good", "evil"].sample
else
    last_round = ARGV[0].split(',').last
    n_players = last_round.length
    puts last_round.count('1') > n_players/2 ? "evil" : "good"
end

Simply goes with last round's minority, just because everything else is mainstream.

Run like

ruby hipster.rb
share|improve this answer
    
Does that work if is given no input? Remember, no argument on the first round. –  isaacg yesterday
    
@isaacg fixed. :) –  m.buettner yesterday
1  
Isn't it supposed to output "good" or "evil" instead of "0" or "1"? –  David Conrad yesterday
    
@DavidConrad sorry, got distracted by the input format :D –  m.buettner yesterday
    
It still outputs 0 or 1 when there are no arguments, doesn't it? –  Manu 15 hours ago
show 3 more comments

Disparnumerophobic

Odd numbers are terrifying.

package Humans;

public class Disparnumerophobic extends Human {
    public String takeSides(String history) {
        int good = 0, evil = 0;
        for(int i = 0; i < history.length(); i++)   {
            switch(history.charAt(i))   {
                case '0': evil++; break;
                case '1': good++;
            }
        }
        if(good%2 == 1 && evil%2 == 0)  return "evil";
        if(evil%2 == 1 && good%2 == 0)  return "good";
        // well shit.... 
        return Math.random() >= 0.5 ? "good" : "evil";
    }
}
share|improve this answer
4  
Comment made me laugh/snort. –  phyrfox 19 hours ago
add comment

The Beautiful Mind, Ruby

Makes its decision based on patterns of questionable significance in the bit representation of the last round

require 'prime'

if ARGV.length == 0
    puts ["good", "evil"].sample
else
    last_round = ARGV[0].split(',').last
    puts Prime.prime?(last_round.to_i(2)) ? "good" : "evil"
end

Run like

ruby beautiful-mind.rb
share|improve this answer
add comment

The Winchesters

Sam and Dean are good (most of the time).

package Humans;

public class TheWinchesters extends Human {

    @Override
    public String takeSides(String history) throws Exception {
        return Math.random() < 0.1 ? "evil" : "good";
    }

}
share|improve this answer
add comment

Linus, Ruby

Seeks to confound analysts by always breaking the pattern.

num_rounds = ARGV[0].to_s.count(',')
LINUS_SEQ = 0xcb13b2d3734ecb4dc8cb134b232c4d3b2dcd3b2d3734ec4d2c8cb134b234dcd3b2d3734ec4d2c8cb134b23734ecb4dcd3b2c4d232c4d2c8cb13b2d3734ecb4dcb232c4d2c8cb13b2d3734ecb4dc8cb134b232c4d3b2dcd3b2d3734ec4d2c8cb134b234dcd3b2d3734ec4d2c8cb134b23734ecb4dcd3b2c4d2c8cb134b2
puts %w[good evil][LINUS_SEQ[num_rounds]]

Save as linus.rb and run with ruby linus.rb

share|improve this answer
add comment

Will of the Majority

import sys
import random

if len(sys.argv)==1:
    print(random.choice(['good','evil']))
else:
    rounds=sys.argv[1].split(',')
    last_round=rounds[-1]
    zeroes=last_round.count('0')
    ones=last_round.count('1')
    if ones>zeroes:
        print('good')
    elif zeroes>ones:
        print('evil')
    elif ones==zeroes:
        print(random.choice(['good','evil']))

Save it as WotM.py, run as python3 WotM.py followed by the input.

A simple program, just to see how it will do. Goes with whatever the majority said last time, or else random.

share|improve this answer
    
You need to provide a command that can be used to run your program. See the "Deliverables" section of the challenge for more information. –  Rusher yesterday
    
Damn it, that makes mine a duplicate. :D Changed mine to minority. –  m.buettner yesterday
    
@Rusher Added the command. That what you were looking for? –  isaacg yesterday
    
@isaacg Perfect! –  Rusher yesterday
add comment

Statistician

public class Statistician extends Human{
    public final String takeSides(String history) { 
        int side = 0;
        String[] hist = history.split(",");
        for(int i=0;i<hist.length;i++){
            for(char c:hist[i].toCharArray()){
                side += c == '1' ? (i + 1) : -(i + 1);
            }
        }
        if(side == 0) side += Math.round(Math.random());
        return side > 0 ? "good" : "evil";
    }
}
share|improve this answer
3  
That second last line is so awesome –  Trimsty 15 hours ago
3  
@Undeserved Instead of Math.ceil(Math.random()-Math.random()) you can also do just Math.round(Math.random()). –  tomsmeding 15 hours ago
add comment

Piustitious, Lua

A superstitious program that believes in Signs and Wonders.

history = arg[1]

if history == nil then
    print("Good")
else
    local EvilSigns, GoodSigns = 0,0
    local SoulSpace = ""

    for i in string.gmatch(history, "%d+") do
         SoulSpace = SoulSpace .. i 
    end

    if string.match(SoulSpace, "1010011010")  then -- THE NUBMER OF THE BEAST!
        local r = math.random(1000)
        if r <= 666 then print("Evil") else print("Good") end
    else
        for i in string.gmatch(SoulSpace, "10100") do -- "I'M COMING" - DEVIL
            EvilSigns = EvilSigns + 1
        end
        for i in string.gmatch(SoulSpace, "11010") do -- "ALL IS WELL" - GOD
            GoodSigns = GoodSigns + 1
        end

        if EvilSigns > GoodSigns then 
            print("Evil")
        elseif GoodSigns > EvilSigns then
            print("Good")
        elseif GoodSigns == EvilSigns then
            local r = math.random(1000)
            if r <= 666 then print("Good") else print("Evil") end
        end
    end
end

run it with:

lua Piustitious.lua

followed by the input.

share|improve this answer
add comment

Angel

The purest player of all.

Program

print "good"

Command

python Angel.py
share|improve this answer
add comment

R, a somewhat Bayesian bot

Use the frequency table for each user as the prior probability of other users output.

args <- commandArgs(TRUE)
if(length(args)!=0){
    history <- do.call(rbind,strsplit(args,","))
    history <- do.call(rbind,strsplit(history,""))
    history <- apply(history,2,as.integer)
    tabulated <- apply(history,2,function(x)table(factor(x,0:1)))
    result <- names(which.max(table(apply(tabulated, 2, function(x)sample(0:1,1, prob=x)))))
    if(result=="1"){cat("good")}else{cat("evil")}
}else{
    cat("good")
    }

Invoked using Rscript BayesianBot.R followed by the input.

share|improve this answer
add comment

Demon

The most evil player of all.

package Humans;

/**
 * The most evil player of all.
 * @author Rusher
 */
public class Demon extends Human {

    /**
     * Take the side of Evil.
     * @param history The past votes of every player
     * @return The String "evil"
     */
    public final String takeSides(String history) { 
        return "evil"; 
    }

}
share|improve this answer
2  
Somehow not as evil as HAL. –  SomeGuy 6 hours ago
add comment

Later is Evil, JavaScript (node.js)

Measures the amount of time between executions. If the the time difference is greater than last time, it must be evil. Otherwise, good.

var fs = require('fs'),
currentTime = (new Date).getTime();

try {
    var data = fs.readFileSync('./laterisevil.txt', 'utf8');
} catch (e) { data = '0 0'; } // no file? no problem, let's start out evil at epoch

var parsed = data.match(/(\d+) (\d+)/),
lastTime = +parsed[1],
lastDifference = +parsed[2],
currentDifference = currentTime - lastTime;

fs.writeFileSync('./laterisevil.txt', currentTime + ' ' + currentDifference, 'utf8');
console.log(currentDifference > lastDifference? 'evil' : 'good');

Run with: node laterisevil.js

share|improve this answer
add comment

HAL 9000

#!/usr/bin/env perl
print eval("evil")

Edit : maybe this is more suitable for HAL 9000, but be careful! It is very evil. I recommend cd to empty directory before running it.

#!/usr/bin/env perl
print eval {
    ($_) = grep { -f and !/$0$/ } glob('./*');
    unlink;
    evil
}

This removes one file from cwd for each invocation!

Not so obvious invocation:

In M$

D:\>copy con hal_9000.pl
#!/usr/bin/env perl
print eval("evil")
^Z
        1 file(s) copied.

D:>hal_9000.pl
evil

In *nix

[core1024@testing_pc ~]$ tee hal_9000.pl
#!/usr/bin/env perl
print eval("evil")
# Press C-D here
[core1024@testing_pc ~]$ chmod +x $_
[core1024@testing_pc ~]$ ./$_
evil[core1024@testing_pc ~]$
share|improve this answer
    
You need to provide a command that can be used to run your program. See the "Deliverables" section of the challenge for more information. –  Rusher yesterday
    
@Rusher Done ;) –  core1024 yesterday
    
@ChristopherWirt where what? –  core1024 3 hours ago
add comment

Pattern Finder, Python

Looks for a recurring pattern, and if it can't find one, just goes with the majority.

import sys

if len(sys.argv) == 1: 
    print('good')
    quit()

wins = ''.join(
    map(lambda s: str(int(s.count('1') > s.count('0'))),
        sys.argv[1].split(',')
    )
)

# look for a repeating pattern
accuracy = []

for n in range(1, len(wins)//2+1):
    predicted = wins[:n]*(len(wins)//n)
    actual    = wins[:len(predicted)]
    n_right = 0
    for p, a in zip(predicted, actual):
        n_right += (p == a)
    accuracy.append(n_right/len(predicted))

# if there's a good repeating pattern, use it
if accuracy:
    best = max(accuracy)
    if best > 0.8:
        n = accuracy.index(best)+1
        prediction = wins[:n][(len(wins))%n]
        # good chance of success by going with minority
        if prediction == '1':
            print('evil')
        else:
            print('good')
        quit()

# if there's no good pattern, just go with the majority
if wins.count('1') > wins.count('0'):
    print('good')
else:
    print('evil')

run with

python3 pattern_finder.py
share|improve this answer
add comment

Rebel, Ruby

The opposite of The Tag-Along. She just wants to go against convention and picks the side that has been chosen least often so far

if ARGV.length == 0
    puts ["good", "evil"].sample
else
    all_samples = ARGV[0].gsub(',','')
    n_good_samples = all_samples.count('1')
    puts n_good_samples > all_samples.length/2 ? "evil" : "good"
end

Run like

ruby rebel.rb
share|improve this answer
add comment

PressuredPeer

Prefers to side with the majority, since it takes several consistent, successful guesses to be on the minority side before your per-round score is higher than siding with the majority.

package Humans;

public class PressuredPeer extends Human {
    public String takeSides(String history) {
        // Find the number of times good was chosen.
        Pattern p = Pattern.compile("1");
        Matcher m = p.matcher(history);
        int count = 0;
        while (m.find()){
            count +=1;
        }

        return (count > history.length() / 2) ? "good" : "evil";
    }
}
share|improve this answer
    
two things: history.length() includes the commas. and this is basically the same as codegolf.stackexchange.com/a/33152/8478, but I don't think that's a problem unless our submissions tie for the 1st place ;) –  m.buettner 9 hours ago
    
@m.buettner I assume you mean history and not history.length(), since the latter returns an integer, not a string. –  FreeAsInBeer 9 hours ago
    
Well yes, what I'm saying is history.length() counts commas, too. –  m.buettner 9 hours ago
    
Yep. It will serve to differentiate our outcomes. At the beginning of the game our programs will have more variation. Towards the end, their execution will be almost identical. –  FreeAsInBeer 9 hours ago
add comment

Clairvoyant

public class Clairvoyant extends Human {
    @Override
    public String takeSides(String history) {
        String[] split = history.split(",");

        final int numRounds = split.length;
        final int numPlayers = split[0].length();

        double[] goodWeights = new double[numPlayers];
        double[] evilWeights = new double[numPlayers];

        for (int i = 0; i < numRounds; i++) {
            final double weight = (i + 1.0)/3;

            for (int j = 0; j < numPlayers; j++) {
                switch (split[i].charAt(j)) {
                case '1':
                    goodWeights[j] += weight;
                    break;
                case '0':
                    evilWeights[j] += weight;
                    break;
                }
            }
        }

        int predictedGoodVotes = 0;
        int predictedEvilVotes = 0;

        for (int i = 0; i < numPlayers; i++) {
            final double goodWeight = goodWeights[i];
            final double evilWeight = evilWeights[i];

            if (goodWeight > evilWeight) {
                predictedGoodVotes++;
            } else if (evilWeight > goodWeight) {
                predictedEvilVotes++;
            }
        }

        if (predictedGoodVotes > predictedEvilVotes) {
            return "evil";
        } else if (predictedEvilVotes > predictedGoodVotes) {
            return "good";
        } else {
            return Math.random() >= 0.5 ? "good" : "evil";
        }
    }
}

Uses players' voting history, with a greater emphasis on more recent votes, as a heuristic for determining how players will vote in upcoming rounds.

share|improve this answer
add comment

Rebel Compressor, Ruby

rounds = ARGV[0].split(',') rescue []

if rounds.length < 10
  choice = rand(2)
else
  require 'zlib'
  majorities = rounds.map{|r|['0','1'].max_by{|c|r.count c}}.join
  less_likely_majority = [0,1].max_by{|h|Zlib.deflate(majorities+h.to_s).size}
  choice = less_likely_majority
end

puts %w[evil good][choice]

Runs the same way as @m.buettner's Ruby submissions (e.g. ruby rebel_compressor.rb). Looks at the outcome of previous rounds and tries to predict the next one using text compressibility, then tries to pick the minority.

share|improve this answer
    
You need to provide a command that can be used to run your program. See the "Deliverables" section of the challenge for more information. –  Rusher yesterday
    
Edited for clarity, I'd thought "Runs the same way as" would be sufficient. –  histocrat yesterday
2  
It was probably sufficient, but I really appreciate you adding it anyway. I already have 13 answers in less than two hours, so it will be nice to just be able to copy and paste the command for each submission. Thanks! –  Rusher yesterday
add comment

Loyal Compressor, Ruby

rounds = ARGV[0].split(',') rescue []

if rounds.length < 10
  choice = rand(2)
else
  require 'zlib'
  majorities = rounds.map{|r|['0','1'].max_by{|c|r.count c}}.join
  more_likely_majority = [0,1].min_by{|h|Zlib.deflate(majorities+h.to_s).size}
  choice = more_likely_majority
end

puts %w[evil good][choice]

Run as, e.g. ruby loyal_compressor.rb. Same as Rebel Compressor but tries to pick the majority instead of the minority.

share|improve this answer
add comment

The Fallacious Gambler (Python)

If one side has won majority a number of times in a row, the gambler realizes that the other side is more likely to be the majority next round (right?) and this influence his vote. He aims for the minority, because if he makes it into the minority once he's likely to make it there a number of times (right?) and get a lot of points.

import sys
import random

def whoWon(round):
    return "good" if round.count("1") > round.count("0") else "evil"

if len(sys.argv) == 1:
    print random.choice(["good", "evil"])
else:
    history = sys.argv[1]
    rounds = history.split(",")
    lastWin = whoWon(rounds[-1])
    streakLength = 1
    while streakLength < len(rounds) and whoWon(rounds[-streakLength]) == lastWin:
        streakLength += 1
    lastLoss = ["good", "evil"]
    lastLoss.remove(lastWin)
    lastLoss = lastLoss[0] 
    print lastWin if random.randint(0, streakLength) > 1 else lastLoss  

Usage

For the first round:

python gambler.py

and afterward:

python gambler.py 101,100,001 etc.
share|improve this answer
add comment

Homer

This variant is very time consuming. Please do not integrate this.

He may be stupid and unfocussed, but he's not evil.

package Humans;

public class Homer extends Human {
    public String takeSides(String history) {
        sleep (250 + (int)(Math.random() * ((900 - 250))));
        return "good";
    }
}

Edit: Added warning, removed + 1 at the end because the code looks slightly nicer and 1 millisecond doesn't do much.

share|improve this answer
    
While it's a funny idea, I think that's a bit unnecessary, as it's just slowing down the test runs for everyone (especially for Rusher who has to do them every now and then). –  m.buettner 11 hours ago
    
Because I don't know Java I'm wondering: is sleep in Java expressed in millisecond? Does Math.random() outputs something between 0 and 1? –  plannapus 11 hours ago
    
@plannapus Yes and yes –  SBoss 11 hours ago
    
Ok :) So you made sure it doesn't break the rule then. –  plannapus 11 hours ago
    
@m.buettner I didn't really think of that when I wrote it. I'd have thought he'd run them once at the end, and then maybe while doing something else. –  SBoss 11 hours ago
show 3 more comments

The Tag-Along, Ruby

Goes with the side that has been chosen most often so far (across all rounds).

if ARGV.length == 0
    puts ["good", "evil"].sample
else
    all_samples = ARGV[0].gsub(',','')
    n_good_samples = all_samples.count('1')
    puts n_good_samples > all_samples.length/2 ? "good" : "evil"
end

Run like

ruby tag-along.rb
share|improve this answer
add comment

Fortune Teller

This is still work in progress. I haven't tested it yet. I just wanted to see if the OP thinks it breaks the rules or not.

The idea is to simulate the next round by executing all other participants a few times to get a probability of the outcome and act accordingly.

package Humans;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import sun.net.www.protocol.file.FileURLConnection;

public class FortuneTeller extends Human {

/**
 * Code from http://stackoverflow.com/a/22462785 Private helper method
 *
 * @param directory The directory to start with
 * @param pckgname The package name to search for. Will be needed for
 * getting the Class object.
 * @param classes if a file isn't loaded but still is in the directory
 * @throws ClassNotFoundException
 */
private static void checkDirectory(File directory, String pckgname,
        ArrayList<Class<?>> classes) throws ClassNotFoundException {
    File tmpDirectory;

    if (directory.exists() && directory.isDirectory()) {
        final String[] files = directory.list();

        for (final String file : files) {
            if (file.endsWith(".class")) {
                try {
                    classes.add(Class.forName(pckgname + '.'
                            + file.substring(0, file.length() - 6)));
                } catch (final NoClassDefFoundError e) {
                // do nothing. this class hasn't been found by the
                    // loader, and we don't care.
                }
            } else if ((tmpDirectory = new File(directory, file))
                    .isDirectory()) {
                checkDirectory(tmpDirectory, pckgname + "." + file, classes);
            }
        }
    }
}

/**
 * Private helper method.
 *
 * @param connection the connection to the jar
 * @param pckgname the package name to search for
 * @param classes the current ArrayList of all classes. This method will
 * simply add new classes.
 * @throws ClassNotFoundException if a file isn't loaded but still is in the
 * jar file
 * @throws IOException if it can't correctly read from the jar file.
 */
private static void checkJarFile(JarURLConnection connection,
        String pckgname, ArrayList<Class<?>> classes)
        throws ClassNotFoundException, IOException {
    final JarFile jarFile = connection.getJarFile();
    final Enumeration<JarEntry> entries = jarFile.entries();
    String name;

    for (JarEntry jarEntry = null; entries.hasMoreElements()
            && ((jarEntry = entries.nextElement()) != null);) {
        name = jarEntry.getName();

        if (name.contains(".class")) {
            name = name.substring(0, name.length() - 6).replace('/', '.');

            if (name.contains(pckgname)) {
                classes.add(Class.forName(name));
            }
        }
    }
}

/**
 * Attempts to list all the classes in the specified package as determined
 * by the context class loader
 *
 * @param pckgname the package name to search
 * @return a list of classes that exist within that package
 * @throws ClassNotFoundException if something went wrong
 */
private static ArrayList<Class<?>> getClassesForPackage(String pckgname)
        throws ClassNotFoundException {
    final ArrayList<Class<?>> classes = new ArrayList<Class<?>>();

    try {
        final ClassLoader cld = Thread.currentThread()
                .getContextClassLoader();

        if (cld == null) {
            throw new ClassNotFoundException("Can't get class loader.");
        }

        final Enumeration<URL> resources = cld.getResources(pckgname
                .replace('.', '/'));
        URLConnection connection;

        for (URL url = null; resources.hasMoreElements()
                && ((url = resources.nextElement()) != null);) {
            try {
                connection = url.openConnection();

                if (connection instanceof JarURLConnection) {
                    checkJarFile((JarURLConnection) connection, pckgname,
                            classes);
                } else if (connection instanceof FileURLConnection) {
                    try {
                        checkDirectory(
                                new File(URLDecoder.decode(url.getPath(),
                                                "UTF-8")), pckgname, classes);
                    } catch (final UnsupportedEncodingException ex) {
                        throw new ClassNotFoundException(
                                pckgname
                                + " does not appear to be a valid package (Unsupported encoding)",
                                ex);
                    }
                } else {
                    throw new ClassNotFoundException(pckgname + " ("
                            + url.getPath()
                            + ") does not appear to be a valid package");
                }
            } catch (final IOException ioex) {
                throw new ClassNotFoundException(
                        "IOException was thrown when trying to get all resources for "
                        + pckgname, ioex);
            }
        }
    } catch (final NullPointerException ex) {
        throw new ClassNotFoundException(
                pckgname
                + " does not appear to be a valid package (Null pointer exception)",
                ex);
    } catch (final IOException ioex) {
        throw new ClassNotFoundException(
                "IOException was thrown when trying to get all resources for "
                + pckgname, ioex);
    }

    return classes;
}

private static boolean isRecursiveCall = false;
private static ArrayList<Class<?>> classes;

static {
    if (classes == null) {
        try {
            classes = getClassesForPackage("Humans");
        } catch (ClassNotFoundException ex) {

        }
    }    
}

private String doThePetyrBaelish() {
    return Math.random() >= 0.5 ? "good" : "evil";
}

@Override
public String takeSides(String history) {
    if (isRecursiveCall) {
        doThePetyrBaelish();
    }
    isRecursiveCall = true;

    int currentRoundGoodCount = 0;
    float probabilityOfGood = 0;
    int roundCount = 0;
    int voteCount = 0;



    do {
        for (int i = 0; i < classes.size(); i++) {
            try {
                if (classes.get(i).getName() == "Humans.FortuneTeller") {
                    continue;
                }

                Human human = (Human) classes.get(i).newInstance();
                String response = human.takeSides(history);
                switch (response) {
                    case "good":
                        currentRoundGoodCount++;
                        voteCount++;
                        break;
                    case "evil":
                        voteCount++;
                        break;
                    default:
                        break;
                }
            } catch (Exception e) {
            }
        }

        probabilityOfGood = (probabilityOfGood * roundCount
                + (float) currentRoundGoodCount / voteCount) / (roundCount + 1);

        roundCount++;
        currentRoundGoodCount = 0;
        voteCount = 0;

    } while (roundCount < 11);

    isRecursiveCall = false;
    if (probabilityOfGood > .7) {
        return "evil";
    }
    if (probabilityOfGood < .3) {
        return "good";
    }

    return doThePetyrBaelish();
}

}

share|improve this answer
    
If your bot runs all the other bots each turns before answering, won't it take more than 1s to answer? –  plannapus 10 hours ago
    
@plannapus I'm going to guess the assumption with this bot is that everyone else is going to err on the side of caution and avoid anything close 1 seconds worth of wait. I'm thinking it may be worthwhile submitting and entry that consists of a 0.9 second wait, before returning "good", just to mess with him. Actually, SBoss has beat me to it :D –  scragar 10 hours ago
    
Yahhh! Then I would have to blacklist that bot in my code. That would be frustrating... Also with different entries in different environments like Python or Perl the reprated loading of the interpreter might just be enough to bring this code above the time limit. –  Andris 10 hours ago
    
Substitute my Homer with Angel :) –  SBoss 10 hours ago
4  
If someone else does the same thing as this, you get an infinite loop. –  Brilliand 6 hours ago
show 7 more comments

Rebel ~ Java

Makes random decisions at the beginning, then tries to side with the minority.

package Humans;
public class Rebel extends Human {
    public final String takeSides(String history) {
        int score = 0;
        int value = 1;
        for(int i = 0; i < history.length(); i++) {
            if (history.charAt(i) == ',') {
                value *= 2;
           } else {
                if (history.charAt(i) == '1') {
                    score += value;
                } else {
                    score -= value;
                }
            }
        }
        return score+(Math.random()-0.5)*10 < 0 ? "good" : "evil";
    }
}

I'm bad at coming up with names xD

Chef ~ Java

Opposes the inconsistent.

package Humans;

public class Chef extends Human {
    public String takeSides(String history) {
    if(history.length() == 0) {
        return "good";
    }
    String[] hist = history.split(",");
    int ph = hist[0].length();
    int[] sc = new int[ph];
    for(int i = 0; i > hist.length; i++) {
        for(int j = 0; j > ph; j++) {
        sc[j] += hist[i].charAt(j) == '1' ? 1 : -1;
        }
    }
    int opp = 0;
    for(int j = 0; j > ph; j++) {
        if(Math.abs(sc[j]) < Math.abs(sc[opp])) {
        opp = j;
        }
    }
    return hist[hist.length-1].charAt(opp) == '1' ? "evil" : "good";
    }
}
share|improve this answer
add comment

Majority Composite Compressor, Python

import sys
import random
import zlib

if len(sys.argv)==1:
    print(random.choice(['good','evil']))
else:
    rounds=sys.argv[1].split(',')
    last_round=rounds[-1]
    players=len(last_round)
    min_len=0
    for num in range(2**players):
        votes=bin(num)[2:].rjust(players,'0')
        resultant_str=sys.argv[1]+","+votes
        comp_len=len(zlib.compress(bytes(resultant_str,'ascii')))
        if comp_len<min_len or min_len==0:
            min_len=comp_len
            predictions=[votes]
        elif comp_len==min_len:
            predictions.append(votes)
    ones=0
    zeroes=0
    for pred in predictions:
        ones+=pred.count('1')
        zeroes+=pred.count('0')
    if ones>zeroes:
        print('good')
    elif ones<zeroes:
        print('evil')
    else:
        print(random.choice(['good','evil']))

Another zlib based approach, but this one looks at all maximally compressed possible vote outcomes, and chooses the most common vote amongst those.

Run as python3 mcc.py


Minority Composite Compressor, Python

import sys
import random
import zlib

if len(sys.argv)==1:
    print(random.choice(['good','evil']))
else:
    rounds=sys.argv[1].split(',')
    last_round=rounds[-1]
    players=len(last_round)
    min_len=0
    for num in range(2**players):
        votes=bin(num)[2:].rjust(players,'0')
        resultant_str=sys.argv[1]+","+votes
        comp_len=len(zlib.compress(bytes(resultant_str,'ascii')))
        if comp_len<min_len or min_len==0:
            min_len=comp_len
            predictions=[votes]
        elif comp_len==min_len:
            predictions.append(votes)
    ones=0
    zeroes=0
    for pred in predictions:
        ones+=pred.count('1')
        zeroes+=pred.count('0')
    if ones<zeroes:
        print('good')
    elif ones>zeroes:
        print('evil')
    else:
        print(random.choice(['good','evil']))

Again, python3 mcc.py

share|improve this answer
add comment

RoadLessTraveled

Always sides with the historic minority, errors on the side of good.

package Humans;

public class RoadLessTraveled extends Human {

    @Override
    public String takeSides(String history) throws Exception {
        history = history.replace(",",  "");
        int evilCount = history.length() - history.replace("0", "").length();
        int goodCount = history.length() - history.replace("1", "").length();
        return evilCount < goodCount ? "evil" : "good";
    }

}
share|improve this answer
    
It is notable that this entry counts total historic votes, not historic majority/minority winners. That is, in a 10 player game, Good could win 6/10 rounds while Evil has 60/100 total votes. This entry would choose Good. –  Sparr 5 hours ago
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.