Two bullet roulette

From Rosetta Code
Task
Two bullet roulette
You are encouraged to solve this task according to the task description, using any language you may know.

The following is supposedly a question given to mathematics graduates seeking jobs on Wall Street:

A revolver handgun has a revolving cylinder with six chambers for bullets.

It is loaded with the following procedure:

1. Check the first chamber to the right of the trigger for a bullet. If a bullet is seen, the cylinder is rotated one chamber clockwise and the next chamber checked until an empty chamber is found.

2. A cartridge containing a bullet is placed in the empty chamber.

3. The cylinder is then rotated one chamber clockwise.

To randomize the cylinder's position, the cylinder is spun, which causes the cylinder to take a random position from 1 to 6 chamber rotations clockwise from its starting position.

When the trigger is pulled the gun will fire if there is a bullet in position 0, which is just counterclockwise from the loading position.

The gun is unloaded by removing all cartridges from the cylinder.

According to the legend, a suicidal Russian imperial military officer plays a game of Russian roulette by putting two bullets in a six-chamber cylinder and pulls the trigger twice. If the gun fires with a trigger pull, this is considered a successful suicide.

The cylinder is always spun before the first shot, but it may or may not be spun after putting in the first bullet and may or may not be spun after taking the first shot.

Which of the following situations produces the highest probability of suicide?

A. Spinning the cylinder after loading the first bullet, and spinning again after the first shot.

B. Spinning the cylinder after loading the first bullet only.

C. Spinning the cylinder after firing the first shot only.

D. Not spinning the cylinder either after loading the first bullet or after the first shot.

E. The probability is the same for all cases.

Task
  1. Run a repeated simulation of each of the above scenario, calculating the percentage of suicide with a randomization of the four spinning, loading and firing order scenarios.
  2. Show the results as a percentage of deaths for each type of scenario.
  3. The hand calculated probabilities are 5/9, 7/12, 5/9, and 1/2. A correct program should produce results close enough to those to allow a correct response to the interview question.


Reference

Youtube video on the Russian 1895 Nagant revolver [[1]]

11l

Translation of: Go
UInt32 seed = 0
F nonrandom(n)
   :seed = 1664525 * :seed + 1013904223
   R Int(:seed >> 16) % n

V cylinder = [0B] * 6

F rshift()
   V t = :cylinder[5]
   L(i) (4..0).step(-1)
      :cylinder[i + 1] = :cylinder[i]
   :cylinder[0] = t

F unload()
   L(i) 6
      :cylinder[i] = 0B

F load()
   L :cylinder[0]
      rshift()
   :cylinder[0] = 1B
   rshift()

F spin()
   L 1..nonrandom(6)
      rshift()

F fire()
   V shot = :cylinder[0]
   rshift()
   R shot

F method(s)
   unload()
   L(c) s
      S c
         ‘L’
            load()
         ‘S’
            spin()
         ‘F’
            I fire()
               R 1
   R 0

F mstring(s)
   [String] l
   L(c) s
      S c
         ‘L’
            l [+]= ‘load’
         ‘S’
            l [+]= ‘spin’
         ‘F’
            l [+]= ‘fire’
   R l.join(‘, ’)

V tests = 100000
L(m) [‘LSLSFSF’, ‘LSLSFF’, ‘LLSFSF’, ‘LLSFF’]
   V sum = 0
   L 0 .< tests
      sum += method(m)
   V pc = Float(sum) * 100 / tests
   print(‘#<40 produces #2.3% deaths.’.format(mstring(m), pc))
Output:
load, spin, load, spin, fire, spin, fire produces 55.434% deaths.
load, spin, load, spin, fire, fire       produces 58.373% deaths.
load, load, spin, fire, spin, fire       produces 55.428% deaths.
load, load, spin, fire, fire             produces 50.041% deaths.

AutoHotkey

methods =
(
load, spin, load, spin, fire, spin, fire
load, spin, load, spin, fire, fire
load, load, spin, fire, spin, fire
load, load, spin, fire, fire
)

for i, method in StrSplit(methods, "`n", "`r"){
	death := 0
	main:
	loop 100000	{
		sixGun := []
		for i, v in StrSplit(StrReplace(method," "), ",")
			if %v%()
				continue, main
	}
	output .= Format("{1:0.3f}", death/1000) "% Deaths for : """ method """`n"
}
MsgBox % output
return

load(){
	global
	if !sixGun.Count()
		sixGun := [0,1,0,0,0,0]
	else
		if sixGun[2]
			sixGun[1] := 1
	sixGun[2] := 1
}
fire(){
	global
	if bullet := sixGun[1]
		death++
	temp := sixGun[6]
	loop, 5
		sixGun[7-A_Index] := sixGun[6-A_Index]
	sixGun[1] := temp
	return bullet
}
spin(){
	global
	Random, rnd, 1, 12
	loop, % rnd	{
		temp := sixGun[6]
		loop, 5
			sixGun[7-A_Index] := sixGun[6-A_Index]
		sixGun[1] := temp
	}
}
Output:
55.478% Deaths for : "load, spin, load, spin, fire, spin, fire"
58.210% Deaths for : "load, spin, load, spin, fire, fire"
55.782% Deaths for : "load, load, spin, fire, spin, fire"
50.280% Deaths for : "load, load, spin, fire, fire"

C

Translation of: Go
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

static int nextInt(int size) {
    return rand() % size;
}

static bool cylinder[6];

static void rshift() {
    bool t = cylinder[5];
    int i;
    for (i = 4; i >= 0; i--) {
        cylinder[i + 1] = cylinder[i];
    }
    cylinder[0] = t;
}

static void unload() {
    int i;
    for (i = 0; i < 6; i++) {
        cylinder[i] = false;
    }
}

static void load() {
    while (cylinder[0]) {
        rshift();
    }
    cylinder[0] = true;
    rshift();
}

static void spin() {
    int lim = nextInt(6) + 1;
    int i;
    for (i = 1; i < lim; i++) {
        rshift();
    }
}

static bool fire() {
    bool shot = cylinder[0];
    rshift();
    return shot;
}

static int method(const char *s) {
    unload();
    for (; *s != '\0'; s++) {
        switch (*s) {
        case 'L':
            load();
            break;
        case 'S':
            spin();
            break;
        case 'F':
            if (fire()) {
                return 1;
            }
            break;
        }
    }
    return 0;
}

static void append(char *out, const char *txt) {
    if (*out != '\0') {
        strcat(out, ", ");
    }
    strcat(out, txt);
}

static void mstring(const char *s, char *out) {
    for (; *s != '\0'; s++) {
        switch (*s) {
        case 'L':
            append(out, "load");
            break;
        case 'S':
            append(out, "spin");
            break;
        case 'F':
            append(out, "fire");
            break;
        }
    }
}

static void test(char *src) {
    char buffer[41] = "";
    const int tests = 100000;
    int sum = 0;
    int t;
    double pc;

    for (t = 0; t < tests; t++) {
        sum += method(src);
    }

    mstring(src, buffer);
    pc = 100.0 * sum / tests;

    printf("%-40s produces %6.3f%% deaths.\n", buffer, pc);
}

int main() {
    srand(time(0));

    test("LSLSFSF");
    test("LSLSFF");
    test("LLSFSF");
    test("LLSFF");

    return 0;
}
Output:
load, spin, load, spin, fire, spin, fire produces 55.456% deaths.
load, spin, load, spin, fire, fire       produces 58.301% deaths.
load, load, spin, fire, spin, fire       produces 55.487% deaths.
load, load, spin, fire, fire             produces 50.289% deaths.

C++

Translation of: C
#include <array>
#include <iomanip>
#include <iostream>
#include <random>
#include <sstream>

class Roulette {
private:
    std::array<bool, 6> cylinder;

    std::mt19937 gen;
    std::uniform_int_distribution<> distrib;

    int next_int() {
        return distrib(gen);
    }

    void rshift() {
        std::rotate(cylinder.begin(), cylinder.begin() + 1, cylinder.end());
    }

    void unload() {
        std::fill(cylinder.begin(), cylinder.end(), false);
    }

    void load() {
        while (cylinder[0]) {
            rshift();
        }
        cylinder[0] = true;
        rshift();
    }

    void spin() {
        int lim = next_int();
        for (int i = 1; i < lim; i++) {
            rshift();
        }
    }

    bool fire() {
        auto shot = cylinder[0];
        rshift();
        return shot;
    }

public:
    Roulette() {
        std::random_device rd;
        gen = std::mt19937(rd());
        distrib = std::uniform_int_distribution<>(1, 6);

        unload();
    }

    int method(const std::string &s) {
        unload();
        for (auto c : s) {
            switch (c) {
            case 'L':
                load();
                break;
            case 'S':
                spin();
                break;
            case 'F':
                if (fire()) {
                    return 1;
                }
                break;
            }
        }
        return 0;
    }
};

std::string mstring(const std::string &s) {
    std::stringstream ss;
    bool first = true;

    auto append = [&ss, &first](const std::string s) {
        if (first) {
            first = false;
        } else {
            ss << ", ";
        }
        ss << s;
    };

    for (auto c : s) {
        switch (c) {
        case 'L':
            append("load");
            break;
        case 'S':
            append("spin");
            break;
        case 'F':
            append("fire");
            break;
        }
    }

    return ss.str();
}

void test(const std::string &src) {
    const int tests = 100000;
    int sum = 0;

    Roulette r;
    for (int t = 0; t < tests; t++) {
        sum += r.method(src);
    }

    double pc = 100.0 * sum / tests;

    std::cout << std::left << std::setw(40) << mstring(src) << " produces " << pc << "% deaths.\n";
}

int main() {
    test("LSLSFSF");
    test("LSLSFF");
    test("LLSFSF");
    test("LLSFF");

    return 0;
}
Output:
load, spin, load, spin, fire, spin, fire produces 55.487% deaths.
load, spin, load, spin, fire, fire       produces 58.542% deaths.
load, load, spin, fire, spin, fire       produces 55.675% deaths.
load, load, spin, fire, fire             produces 50.051% deaths.

EasyLang

Translation of: C
len cyl[] 6
proc rshift . .
   h = cyl[6]
   for i = 6 downto 2
      cyl[i] = cyl[i - 1]
   .
   cyl[1] = h
.
proc unload . .
   for i = 1 to 6
      cyl[i] = 0
   .
.
proc load . .
   while cyl[1] = 1
      rshift
   .
   cyl[1] = 1
   rshift
.
proc spin . .
   lim = randint 6
   for i = 1 to lim - 1
      rshift
   .
.
func fire .
   shot = cyl[1]
   rshift
   return shot
.
func method m[] .
   unload
   for m in m[]
      if m = 1
         load
      elif m = 2
         spin
      elif m = 3
         if fire = 1
            return 1
         .
      .
   .
   return 0
.
method$[] = [ "load" "spin" "fire" ]
proc test m[] . .
   n = 100000
   for i = 1 to n
      sum += method m[]
   .
   for i = 1 to len m[]
      write method$[m[i]] & " "
   .
   print "-> " & 100 * sum / n & "% death"
.
test [ 1 2 1 2 3 2 3 ]
test [ 1 2 1 2 3 3 ]
test [ 1 1 2 3 2 3 ]
test [ 1 1 2 3 3 ]

Factor

Translation of: Julia
Translation of: Python
USING: accessors assocs circular formatting fry kernel literals
math random sequences ;
IN: rosetta-code.roulette

CONSTANT: cyl $[ { f f f f f f } <circular> ]

: cylinder ( -- seq ) cyl [ drop f ] map! ;

: load ( seq -- seq' )
    0 over nth [ dup rotate-circular ] when
    t 0 rot [ set-nth ] [ rotate-circular ] [ ] tri ;

: spin ( seq -- seq' ) [ 6 random 1 + + ] change-start ;

: fire ( seq -- ? seq' )
    [ 0 swap nth ] [ rotate-circular ] [ ] tri ;

: LSLSFSF ( -- ? ) cylinder load spin load spin fire spin fire drop or ;
: LSLSFF  ( -- ? ) cylinder load spin load spin fire fire drop or ;
: LLSFSF  ( -- ? ) cylinder load load spin fire spin fire drop or ;
: LLSFF   ( -- ? ) cylinder load load spin fire fire drop or ;

: percent ( ... n quot: ( ... -- ... ? ) -- ... x )
    0 -rot '[ _ call( -- ? ) 1 0 ? + ] [ times ] keepd /f 100 * ; inline

: run-test ( description quot -- )
    100,000 swap percent
    "Method <%s> produces %.3f%% deaths.\n" printf ;

: main ( -- )
    {
        { "load, spin, load, spin, fire, spin, fire" [ LSLSFSF ] }
        { "load, spin, load, spin, fire, fire" [ LSLSFF ] }
        { "load, load, spin, fire, spin, fire" [ LLSFSF ] }
        { "load, load, spin, fire, fire" [ LLSFF ] }
    } [ run-test ] assoc-each ;

MAIN: main
Output:
"rosetta-code.roulette" run
Method <load, spin, load, spin, fire, spin, fire> produces 55.598% deaths.
Method <load, spin, load, spin, fire, fire> produces 58.390% deaths.
Method <load, load, spin, fire, spin, fire> produces 55.500% deaths.
Method <load, load, spin, fire, fire> produces 49.841% deaths.

FreeBASIC

Translation of: Wren
Type Revolver
    cylinder(0 To 5) As Integer
End Type

Sub rshift(r As Revolver)
    Dim t As Integer = r.cylinder(5)
    For i As Integer = 4 To 0 Step -1
        r.cylinder(i + 1) = r.cylinder(i)
    Next i
    r.cylinder(0) = t
End Sub

Sub unload(r As Revolver)
    For i As Integer = 0 To 5
        r.cylinder(i) = 0
    Next i
End Sub

Sub load(r As Revolver)
    While r.cylinder(0) <> 0
        rshift(r)
    Wend
    r.cylinder(0) = -1
    rshift(r)
End Sub

Sub spin(r As Revolver)
    For i As Integer = 1 To Int(Rnd * 6) + 1
        rshift(r)
    Next i
End Sub

Function fire(r As Revolver) As Integer
    Dim As Integer shot = r.cylinder(0)
    rshift(r)
    Return shot
End Function

Function method(r As Revolver, s As String) As Integer
    unload(r)
    For i As Integer = 1 To Len(s)
        Dim c As String = Mid(s, i, 1)
        If c = "L" Then
            load(r)
        Elseif c = "S" Then
            spin(r)
        Elseif c = "F" Then
            If fire(r) <> 0 Then Return 1
        End If
    Next i
    Return 0
End Function

Function mstring(s As String) As String
    Dim As String l = ""
    For i As Integer = 1 To Len(s)
        Dim As String c = Mid(s, i, 1)
        If c = "L" Then
            l &= "load, "
        Elseif c = "S" Then
            l &= "spin, "
        Elseif c = "F" Then
            l &= "fire, "
        End If
    Next i
    Return Left(l, Len(l) - 2)
End Function

Dim As Revolver rev
Dim As Integer tests = 100000
Dim As String methods(0 To 3) = {"LSLSFSF", "LSLSFF", "LLSFSF", "LLSFF"}

For m As Integer = 0 To 3 'In methods
    Dim sum As Integer = 0
    For t As Integer = 1 To tests
        sum += method(rev, methods(m))
    Next t
    Print mstring(methods(m)), " produces "; sum * 100.0 / tests; "% deaths."
Next m

Sleep
Output:
load, spin, load, spin, fire, spin, fire   produces  55.385% deaths.
load, spin, load, spin, fire, fire         produces  58.204% deaths.
load, load, spin, fire, spin, fire         produces  55.372% deaths.
load, load, spin, fire, fire               produces  50.052% deaths.

Go

Translation of: Wren

Though procedural rather than OO.

package main

import (
    "fmt"
    "math/rand"
    "strings"
    "time"
)

var cylinder = [6]bool{}

func rshift() {
    t := cylinder[5]
    for i := 4; i >= 0; i-- {
        cylinder[i+1] = cylinder[i]
    }
    cylinder[0] = t
}

func unload() {
    for i := 0; i < 6; i++ {
        cylinder[i] = false
    }
}

func load() {
    for cylinder[0] {
        rshift()
    }
    cylinder[0] = true
    rshift()
}

func spin() {
    var lim = 1 + rand.Intn(6)
    for i := 1; i < lim; i++ {
        rshift()
    }
}

func fire() bool {
    shot := cylinder[0]
    rshift()
    return shot
}

func method(s string) int {
    unload()
    for _, c := range s {
        switch c {
        case 'L':
            load()
        case 'S':
            spin()
        case 'F':
            if fire() {
                return 1
            }
        }
    }
    return 0
}

func mstring(s string) string {
    var l []string
    for _, c := range s {
        switch c {
        case 'L':
            l = append(l, "load")
        case 'S':
            l = append(l, "spin")
        case 'F':
            l = append(l, "fire")
        }
    }
    return strings.Join(l, ", ")
}

func main() {
    rand.Seed(time.Now().UnixNano())
    tests := 100000
    for _, m := range []string{"LSLSFSF", "LSLSFF", "LLSFSF", "LLSFF"} {
        sum := 0
        for t := 1; t <= tests; t++ {
            sum += method(m)
        }
        pc := float64(sum) * 100 / float64(tests)
        fmt.Printf("%-40s produces %6.3f%% deaths.\n", mstring(m), pc)
    }
}
Output:

Sample run:

load, spin, load, spin, fire, spin, fire produces 55.267% deaths.
load, spin, load, spin, fire, fire       produces 58.110% deaths.
load, load, spin, fire, spin, fire       produces 55.405% deaths.
load, load, spin, fire, fire             produces 49.889% deaths.

Java

import java.util.BitSet;
import java.util.concurrent.ThreadLocalRandom;

public class TwoBulletRoulette {

	public static void main(String[] aArgs) {		
		Revolver handgun = new Revolver();
		final int simulationCount = 100_000;		
	
		for ( Situation situation : Situation.values() ) {
			double deaths = 0.0;
			for ( int i = 0; i < simulationCount; i++ ) {
				ResultState resultState = handgun.operateInMode(situation);
				if ( resultState == ResultState.DEAD) {
					deaths += 1.0;
				}
			}
			final double deathRate = ( deaths / simulationCount ) * 100;
			String percentage = String.format("%4.1f%%", deathRate);
			System.out.println("Situation " + situation + " produces " + percentage + " deaths");
		}
	}	
	
}

enum Situation { A, B, C, D }

enum ResultState { ALIVE, DEAD }

/**
 * Representation of a six cylinder revolving chamber pistol.
 */
class Revolver {
	
	public Revolver() {
		chambers = new BitSet(chamberCount);			
		random = ThreadLocalRandom.current();
	}	
	
	public ResultState operateInMode(Situation aSituation) {
		return switch ( aSituation ) {
			case A -> useSituationA();
			case B -> useSituationB();
			case C -> useSituationC();
			case D -> useSituationD();
		};
	}
	
	// PRIVATE //
	
	private void unload() {		
		chambers.clear();
	}
	
	private void load() {
		while ( chambers.get(loadingChamber) ) {
			rotateClockwise();
		}		
		chambers.set(loadingChamber);
		rotateClockwise();
	}	
	
	private void spin() {
		final int spins = random.nextInt(0, chamberCount);
		for ( int i = 0; i < spins; i++ ) {
			rotateClockwise();
		}
	}
	
	private boolean fire() {
		boolean fire = chambers.get(firingChamber);
		chambers.set(firingChamber, false);
		rotateClockwise();
		return fire;			
	}
	
	private void rotateClockwise() {		
		final boolean temp = chambers.get(chamberCount - 1);
		for ( int i = chamberCount - 2; i >= 0; i-- ) {
			chambers.set(i + 1, chambers.get(i));
		}
		chambers.set(firingChamber, temp);
	}
		
	private ResultState useSituationA() {
		unload();
		load();
		spin();
		load();
		spin();
		if ( fire() ) {
			return ResultState.DEAD;
		};
		spin();
		if ( fire() ) {
			return ResultState.DEAD;
		};
		
		return ResultState.ALIVE;
	}
	
	private ResultState useSituationB() {
		unload();
		load();
		spin();
		load();
		spin();
		if ( fire() ) {
			return ResultState.DEAD;
		};
		if ( fire() ) {
			return ResultState.DEAD;
		};
		
		return ResultState.ALIVE;
	}
	
	private ResultState useSituationC() {
		unload();
		load();
		load();
		spin();
		if ( fire() ) {
			return ResultState.DEAD;
		};
		spin();
		if ( fire() ) {
			return ResultState.DEAD;
		};
		
		return ResultState.ALIVE;
	}
	
	private ResultState useSituationD() {
		unload();
		load();
		load();
		spin();
		if ( fire() ) {
			return ResultState.DEAD;
		};
		if ( fire() ) {
			return ResultState.DEAD;
		};
		
		return ResultState.ALIVE;
	}	
	
	private BitSet chambers;
	private ThreadLocalRandom random;
	
	private final int firingChamber = 0;
	private final int loadingChamber = 1;
	private final int chamberCount = 6;
	
}
Output:
Situation A produces 55.6% deaths
Situation B produces 58.2% deaths
Situation C produces 55.7% deaths
Situation D produces 49.7% deaths

JavaScript

let Pistol = function(method) {
  this.fired = false;
  this.cylinder = new Array(6).fill(false);
  this.trigger = 0;
  this.rshift = function() {
    this.trigger = this.trigger == 0 ? 5 : this.trigger-1;
  }
  this.load = function() {
    while (this.cylinder[this.trigger]) this.rshift();
    this.cylinder[this.trigger] = true;
    this.rshift();
  }
  // actually we don't need this here: just for completeness
  this.unload = function() { this.cylinder.fill(false); }

  this.spin = function() { this.trigger = Math.floor(Math.random() * 6); }
  this.fire = function() {
    if (this.cylinder[this.trigger]) this.fired = true;
    this.rshift();
  }
  this.exec = function() {
    if (!method) console.error('No method provided');
    else {
      method = method.toUpperCase();
      for (let x = 0; x < method.length; x++)
        switch (method[x]) {
          case 'F' : this.fire(); break;
          case 'L' : this.load(); break;
          case 'S' : this.spin(); break;
          case 'U' : this.unload(); break;
          default: console.error(`Unknown character in method: ${method[x]}`);
        }
      return this.fired;
    }
  }
}

// simulating
const ITERATIONS = 25e4;
let methods = 'lslsfsf lslsff llsfsf llsff'.split(' '),
    bodyCount;
console.log(`@ ${ITERATIONS.toLocaleString('en')} iterations:`);
console.log();
for (let x = 0; x < methods.length; x++) {
  bodyCount = 0;
  for (let y = 1; y <= ITERATIONS; y++)
    if (new Pistol(methods[x]).exec()) bodyCount++;
  console.log(`${methods[x]}:`);
  console.log(`deaths: ${bodyCount.toLocaleString('en')} (${(bodyCount / ITERATIONS * 100).toPrecision(3)} %) `);
  console.log();
}
Output:

Example:

@ 250,000 iterations: 

lslsfsf:
deaths: 139,030 (55.6 %) 

lslsff:
deaths: 145,912 (58.4 %)

llsfsf:
deaths: 138,628 (55.5 %)

llsff:
deaths: 125,268 (50.1 %) 

Julia

Translation of: Python
const cyl = zeros(Bool, 6)

function load()
    while cyl[1]
        cyl .= circshift(cyl, 1)
    end
    cyl[1] = true
    cyl .= circshift(cyl, 1)
end

spin() = (cyl .= circshift(cyl, rand(1:6)))

fire() = (shot = cyl[1]; cyl .= circshift(cyl, 1); shot)

function LSLSFSF()
    cyl .= 0
    load(); spin(); load(); spin()
    fire() && return true
    spin(); return fire()
end

function LSLSFF()
    cyl .= 0
    load(); spin(); load(); spin()
    fire() && return true
    return fire()
end

function LLSFSF()
    cyl .= 0
    load(); load(); spin()
    fire() && return true
    spin(); return fire()
end

function LLSFF()
    cyl .= 0
    load(); load(); spin()
    fire() && return true
    return fire()
end

function testmethods(N = 10000000)
    for (name, method) in [("load, spin, load, spin, fire, spin, fire", LSLSFSF),
                           ("load, spin, load, spin, fire, fire", LSLSFF),
                           ("load, load, spin, fire, spin, fire", LLSFSF),
                           ("load, load, spin, fire, fire", LLSFF)]
        percentage = 100 * sum([method() for _ in 1:N]) / N
        println("Method $name produces $percentage per cent deaths.")
    end
end

testmethods()
Output:
Method load, spin, load, spin, fire, spin, fire produces 55.54253 per cent deaths.
Method load, spin, load, spin, fire, fire produces 58.32598 per cent deaths.
Method load, load, spin, fire, spin, fire produces 55.54244 per cent deaths.
Method load, load, spin, fire, fire produces 50.02247 per cent deaths.

Kotlin

Translation of: C
import kotlin.random.Random

val cylinder = Array(6) { false }

fun rShift() {
    val t = cylinder[cylinder.size - 1]
    for (i in (0 until cylinder.size - 1).reversed()) {
        cylinder[i + 1] = cylinder[i]
    }
    cylinder[0] = t
}

fun unload() {
    for (i in cylinder.indices) {
        cylinder[i] = false
    }
}

fun load() {
    while (cylinder[0]) {
        rShift()
    }
    cylinder[0] = true
    rShift()
}

fun spin() {
    val lim = Random.nextInt(0, 6) + 1
    for (i in 1..lim) {
        rShift()
    }
}

fun fire(): Boolean {
    val shot = cylinder[0]
    rShift()
    return shot
}

fun method(s: String): Int {
    unload()
    for (c in s) {
        when (c) {
            'L' -> {
                load()
            }
            'S' -> {
                spin()
            }
            'F' -> {
                if (fire()) {
                    return 1
                }
            }
        }
    }
    return 0
}

fun mString(s: String): String {
    val buf = StringBuilder()
    fun append(txt: String) {
        if (buf.isNotEmpty()) {
            buf.append(", ")
        }
        buf.append(txt)
    }
    for (c in s) {
        when (c) {
            'L' -> {
                append("load")
            }
            'S' -> {
                append("spin")
            }
            'F' -> {
                append("fire")
            }
        }
    }
    return buf.toString()
}

fun test(src: String) {
    val tests = 100000
    var sum = 0

    for (t in 0..tests) {
        sum += method(src)
    }

    val str = mString(src)
    val pc = 100.0 * sum / tests
    println("%-40s produces %6.3f%% deaths.".format(str, pc))
}

fun main() {
    test("LSLSFSF");
    test("LSLSFF");
    test("LLSFSF");
    test("LLSFF");
}
Output:
load, spin, load, spin, fire, spin, fire produces 55.638% deaths.
load, spin, load, spin, fire, fire       produces 58.140% deaths.
load, load, spin, fire, spin, fire       produces 55.725% deaths.
load, load, spin, fire, fire             produces 49.875% deaths.

Mathematica/Wolfram Language

ClearAll[Unload, Load, Spin, Fire]
Unload[] := ConstantArray[False, 6]
Load[state_List] := Module[{s = state},
  While[s[[2]],
   s = RotateRight[s, 1]
   ];
  s[[2]] = True;
  s
  ]
Spin[state_List] := RotateRight[state, RandomInteger[{1, 6}]]
Fire[state_List] := Module[{shot},
  shot = First[state];
  {RotateRight[state, 1], shot}
  ]
ClearAll[LSLSFSF]
LSLSFSF[] := Module[{state, shot},
  state = Unload[];
  state = Load[state];
  state = Spin[state];
  state = Load[state];
  state = Spin[state];
  {state, shot} = Fire[state];
  If[shot,
   Return[True]
   ];
  state = Spin[state];
  {state, shot} = Fire[state];
  If[shot,
   Return[True]
   ];
  Return[False]
  ]
ClearAll[LSLSFF]
LSLSFF[] := Module[{state, shot},
  state = Unload[];
  state = Load[state];
  state = Spin[state];
  state = Load[state];
  state = Spin[state];
  {state, shot} = Fire[state];
  If[shot,
   Return[True]
   ];
  {state, shot} = Fire[state];
  If[shot,
   Return[True]
   ];
  Return[False]
  ]
ClearAll[LLSFSF]
LLSFSF[] := Module[{state, shot},
  state = Unload[];
  state = Load[state];
  state = Load[state];
  state = Spin[state];
  {state, shot} = Fire[state];
  If[shot,
   Return[True]
   ];
  state = Spin[state];
  {state, shot} = Fire[state];
  If[shot,
   Return[True]
   ];
  Return[False]
  ]
ClearAll[LLSFF]
LLSFF[] := Module[{state, shot},
  state = Unload[];
  state = Load[state];
  state = Load[state];
  state = Spin[state];
  {state, shot} = Fire[state];
  If[shot,
   Return[True]
   ];
  {state, shot} = Fire[state];
  If[shot,
   Return[True]
   ];
  Return[False]
  ]
n = 10^5;
Count[Table[LSLSFSF[], n], True]/N[n]
Count[Table[LSLSFF[], n], True]/N[n]
Count[Table[LLSFSF[], n], True]/N[n]
Count[Table[LLSFF[], n], True]/N[n]
Output:
0.55243
0.58272
0.55423
0.49975

Nim

import algorithm, random, sequtils, strformat, strutils, tables

type
  Revolver = array[6, bool]
  Action {.pure.} = enum Load, Spin, Fire, Error

const Actions = {'L': Load, 'S': Spin, 'F': Fire}.toTable

func spin(revolver: var Revolver; count: Positive) =
  revolver.rotateLeft(-count)

func load(revolver: var Revolver) =
  while revolver[1]:
    revolver.spin(1)
  revolver[1] = true
  revolver.spin(1)

func fire(revolver: var Revolver): bool =
  result = revolver[0]
  revolver.spin(1)

proc test(scenario: string) =
  let actions = scenario.mapIt(Actions.getOrDefault(it, Error))
  var deaths = 0
  var count = 100_000
  for _ in 1..count:
    var revolver: Revolver
    for action in actions:
      case action
      of Load:
        revolver.load()
      of Spin:
        revolver.spin(rand(1..6))
      of Fire:
        if revolver.fire():
          inc deaths
          break
      of Error:
        raise newException(ValueError, "encountered an unknown action.")
  echo &"""{100 * deaths / count:5.2f}% deaths for scenario {actions.join(", ")}."""

randomize()
for scenario in ["LSLSFSF", "LSLSFF", "LLSFSF", "LLSFF"]:
  test(scenario)
Output:
55.73% deaths for scenario Load, Spin, Load, Spin, Fire, Spin, Fire.
58.09% deaths for scenario Load, Spin, Load, Spin, Fire, Fire.
55.74% deaths for scenario Load, Load, Spin, Fire, Spin, Fire.
50.14% deaths for scenario Load, Load, Spin, Fire, Fire.

Odin

/* imports */
import "core:fmt"
import "core:strings"
import "core:math/rand"
/* globals */
cylinder := [6]bool{}
/* main block */
main :: proc() {
	rand.set_global_seed(42)
	tests := 100000
	sequence := [?]string{"LSLSFSF", "LSLSFF", "LLSFSF", "LLSFF"}
	for m in sequence {
		sum := 0
		for t in 0 ..< tests {
			sum += method(m)
		}
		pc: f64 = cast(f64)sum * 100 / cast(f64)tests
		fmt.printf("%-40s produces %6.3f%% deaths.\n", mstring(m), pc)
	}
}
/* definitions */
rshift :: proc() {
	t := cylinder[len(cylinder) - 1]
	copy(cylinder[1:], cylinder[0:])
	cylinder[0] = t
}
unload :: proc() {
	cylinder = false // array programming
}
load :: proc() {
	for cylinder[0] {
		rshift()
	}
	cylinder[0] = true
	rshift()
}
spin :: proc() {
	data: []int = {1, 2, 3, 4, 5, 6}
	lim := rand.choice(data[:])
	for i in 0 ..< lim {
		rshift()
	}
}
fire :: proc() -> bool {
	shot := cylinder[0]
	rshift()
	return shot
}
method :: proc(s: string) -> int {
	unload()
	for character in s {
		switch character {
		case 'L':
			load()
		case 'S':
			spin()
		case 'F':
			if fire() {
				return 1
			}
		}
	}
	return 0
}
mstring :: proc(s: string) -> string {
	l: [dynamic]string
	for character in s {
		switch character {
		case 'L':
			append(&l, "load")
		case 'S':
			append(&l, "spin")
		case 'F':
			append(&l, "fire")
		}
	}
	return strings.join(l[:], ", ")
}
Output:
load, spin, load, spin, fire, spin, fire produces 55.771% deaths.
load, spin, load, spin, fire, fire       produces 58.313% deaths.
load, load, spin, fire, spin, fire       produces 55.487% deaths.
load, load, spin, fire, fire             produces 49.972% deaths.

Perl

Translation of: Raku
use strict;
use warnings;
use feature 'say';

my @cyl;
my $shots = 6;

sub load  {
    push @cyl, shift @cyl while $cyl[1];
    $cyl[1] = 1;
    push @cyl, shift @cyl
}

sub spin  { push @cyl, shift @cyl for 0 .. int rand @cyl }
sub fire  { push @cyl, shift @cyl; $cyl[0] }

sub LSLSFSF {
    @cyl = (0) x $shots;
    load, spin, load, spin;
    return 1 if fire;
    spin;
    fire
}

sub LSLSFF {
    @cyl = (0) x $shots;
    load, spin, load, spin;
    fire or fire
}

sub LLSFSF {
    @cyl = (0) x $shots;
    load, load, spin;
    return 1 if fire;
    spin;
    fire
}

sub LLSFF {
    @cyl = (0) x $shots;
    load, load, spin;
    fire or fire
}

my $trials = 10000;

for my $ref (<LSLSFSF LSLSFF LLSFSF LLSFF>) {
    no strict 'refs';
    my $total = 0;
    $total += &$ref for 1..$trials;
    printf "%7s %.2f%%\n", $ref, $total / $trials * 100;
}
Output:
LSLSFSF 55.04%
 LSLSFF 58.77%
 LLSFSF 55.09%
  LLSFF 50.13%

Phix

with javascript_semantics
function spin(sequence revolver, integer count)
    while count do
        revolver = revolver[$]&revolver[1..$-1]
        count -= 1
    end while
    return revolver
end function
 
function load(sequence revolver)
    while revolver[1] do
        revolver = spin(revolver,1) 
    end while
    revolver[1] = true
    revolver = spin(revolver,1) 
    return revolver
end function
 
function fire(sequence revolver, bool dead)
    if revolver[1] then dead = true end if
    revolver = spin(revolver,1) 
    return {revolver,dead}
end function
 
procedure test(sequence me)
    {string method, atom expected} = me
    integer deaths = 0,
            limit = 100_000
    for n=1 to limit do
        sequence revolver = repeat(false,6)
        bool dead = false
        for i=1 to length(method) do
            integer ch = method[i]
            switch ch
              case 'L': revolver = load(revolver)
              case 'S': revolver = spin(revolver,rand(6))
              case 'F': {revolver,dead} = fire(revolver,dead)
            end switch 
        end for
        deaths += dead
    end for
    printf(1,"%s: %5.2f (expected %.2f%%)\n",{method,100*deaths/limit,expected*100})
end procedure
 
printf(1,"Load/Spin/Fire method percentage fatalities:\n")
papply({{"LSLSFSF",5/9},{"LSLSFF",7/12},{"LLSFSF",5/9},{"LLSFF",1/2}},test)
Output:
Load/Spin/Fire method percentage fatalities:
LSLSFSF: 55.40 (expected 55.56%)
LSLSFF: 58.33 (expected 58.33%)
LLSFSF: 55.54 (expected 55.56%)
LLSFF: 50.03 (expected 50.00%)

Python

""" Russian roulette problem """
import numpy as np

class Revolver:
    """ simulates 6-shot revolving cylinger pistol """

    def __init__(self):
        """ start unloaded """
        self.cylinder = np.array([False] * 6)

    def unload(self):
        """ empty all chambers of cylinder """
        self.cylinder[:] = False

    def load(self):
        """ load a chamber (advance til empty if full already), then advance once """
        while self.cylinder[1]:
            self.cylinder[:] = np.roll(self.cylinder, 1)
        self.cylinder[1] = True

    def spin(self):
        """ spin cylinder, randomizing position of chamber to be fired """
        self.cylinder[:] = np.roll(self.cylinder, np.random.randint(1, high=7))

    def fire(self):
        """ pull trigger of revolver, return True if fired, False if did not fire """
        shot = self.cylinder[0]
        self.cylinder[:] = np.roll(self.cylinder, 1)
        return shot

    def LSLSFSF(self):
        """ load, spin, load, spin, fire, spin, fire """
        self.unload()
        self.load()
        self.spin()
        self.load()
        self.spin()
        if self.fire():
            return True
        self.spin()
        if self.fire():
            return True
        return False

    def LSLSFF(self):
        """ load, spin, load, spin, fire, fire """
        self.unload()
        self.load()
        self.spin()
        self.load()
        self.spin()
        if self.fire():
            return True
        if self.fire():
            return True
        return False

    def LLSFSF(self):
        """ load, load, spin, fire, spin, fire """
        self.unload()
        self.load()
        self.load()
        self.spin()
        if self.fire():
            return True
        self.spin()
        if self.fire():
            return True
        return False

    def LLSFF(self):
        """ load, load, spin, fire, fire """
        self.unload()
        self.load()
        self.load()
        self.spin()
        if self.fire():
            return True
        if self.fire():
            return True
        return False


if __name__ == '__main__':

    REV = Revolver()
    TESTCOUNT = 100000
    for (name, method) in [['load, spin, load, spin, fire, spin, fire', REV.LSLSFSF],
                           ['load, spin, load, spin, fire, fire', REV.LSLSFF],
                           ['load, load, spin, fire, spin, fire', REV.LLSFSF],
                           ['load, load, spin, fire, fire', REV.LLSFF]]:

        percentage = 100 * sum([method() for _ in range(TESTCOUNT)]) / TESTCOUNT
        print("Method", name, "produces", percentage, "per cent deaths.")
Output:
Method load, spin, load, spin, fire, spin, fire produces 55.652 per cent deaths.
Method load, spin, load, spin, fire, fire produces 58.239 per cent deaths.
Method load, load, spin, fire, spin, fire produces 55.774 per cent deaths.
Method load, load, spin, fire, fire produces 50.071 per cent deaths.

Raku

unit sub MAIN ($shots = 6);

my @cyl;

sub load () {
    @cyl.=rotate(-1) while @cyl[1];
    @cyl[1] = 1;
    @cyl.=rotate(-1);
}

sub spin () { @cyl.=rotate: (^@cyl).pick }

sub fire () { @cyl.=rotate; @cyl[0] }

sub LSLSFSF {
    @cyl = 0 xx $shots;
    load, spin, load, spin;
    return 1 if fire;
    spin;
    fire
}

sub LSLSFF {
    @cyl = 0 xx $shots;
    load, spin, load, spin;
    fire() || fire
}

sub LLSFSF {
    @cyl = 0 xx $shots;
    load, load, spin;
    return 1 if fire;
    spin;
    fire
}

sub LLSFF {
    @cyl = 0 xx $shots;
    load, load, spin;
    fire() || fire
}

my %revolver;
my $trials = 100000;

for ^$trials {
    %revolver<LSLSFSF> += LSLSFSF;
    %revolver<LSLSFF>  += LSLSFF;
    %revolver<LLSFSF>  += LLSFSF;
    %revolver<LLSFF>   += LLSFF;
}

say "{.fmt('%7s')}: %{(%revolver{$_} / $trials × 100).fmt('%.2f')}"
  for <LSLSFSF LSLSFF LLSFSF LLSFF>
Sample output (default; 6 shooter):
LSLSFSF: %55.37
 LSLSFF: %58.30
 LLSFSF: %55.42
  LLSFF: %50.29

Though if you go and look at the Wikipedia article for the 1895 Nagant revolver mentioned in the task reference section, you'll see it is actually a 7 shot revolver... so, run again with 7 chambers:

raku roulette.raku 7

Sample output (7 shooter):
LSLSFSF: %49.29
 LSLSFF: %51.14
 LLSFSF: %48.74
  LLSFF: %43.08

Or, how about a Ruger GP100 10 round revolver?

raku roulette.raku 10

Sample output (10 shooter):
LSLSFSF: %36.00
 LSLSFF: %37.00
 LLSFSF: %36.13
  LLSFF: %29.77

Doesn't change the answers, B (LSLSFF) is definitely the worst most likely choice in all cases.

REXX

Translation of: GO


This REXX version eliminates the spinning of the bullet chamber if the random number for a spin is   6   (which would
normally just spin the bullet chamber around to its initial position,   thereby saving some busywork by the program).

Changing the cartridge chamber from an index array to a simple string made the program around   200%   faster.

/*REXX pgm simulates scenarios for a two─bullet Russian roulette game with a 6 cyl. gun.*/
parse arg cyls tests seed .                      /*obtain optional arguments from the CL*/
if  cyls=='' |  cyls==","  then  cyls=      6    /*Not specified?  Then use the default.*/
if tests=='' | tests==","  then tests= 100000    /* "      "         "   "   "     "    */
if datatype(seed, 'W')  then call random ,,seed  /* "      "         "   "   "     "    */
cyls_ = cyls - 1;          @0= copies(0, cyls)   /*shortcut placeholder for cylinders-1 */
@abc= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'               /*indices for the various options used.*/
scenarios= 'LSLSFsF  LSLSFF  LLSFSF  LLSFF'      /*the list of scenarios to be tested.  */
#= words(scenarios)                              /*the number of actions in a scenario. */
                                                 /*The scenarios are case insensitive.  */
        do m=1  for #;     q= word(scenarios, m) /*test each of the scenarios specified.*/
                           sum= 0                /*initialize the  sum  to zero.        */
           do tests;       sum= sum + method()   /*added the sums up for the percentages*/
           end   /*tests*/
                                    pc= left( (sum * 100 / tests)"%",  7)
        say act()   '  (option'     substr(@abc, m, 1)")   produces  "    pc    ' deaths.'
        end   /*m*/
exit 0                                           /*stick a fork in it,  we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
fire:   != left(@, 1);      @= right(@, cyls_)left(@, 1);   /* ◄──── next cyl.*/  return !
load:   if left(@, 1)  then @= right(@, cyls_)left(@, 1);  @= 1||right(@, cyls_); return
spin:   ?= random(1, cyls);    if ?\==cyls  then @= substr(@ || @, ? + 1, cyls);  return
/*──────────────────────────────────────────────────────────────────────────────────────*/
method: @= @0;   do a=1  for length(q);          y= substr(q, a, 1)
                 if y=='L'  then call load
                            else if y=='S'  then call spin
                                            else if y=='F'  then if fire()  then return 1
                 end   /*a*/;                                                    return 0
/*──────────────────────────────────────────────────────────────────────────────────────*/
act:    $=;      do a=1  for length(q);          y= substr(q, a, 1)
                 if y=='L'  then $= $", load"
                 if y=='S'  then $= $", spin"
                 if y=='F'  then $= $", fire"
                 end   /*a*/;                  return right( strip( strip($, , ",") ), 45)
output   when using the default inputs,   showing that 2nd option   B   has the highest probability for a suicide:
     load, spin, load, spin, fire, spin, fire   (option A)   produces   55.44%   deaths.
           load, spin, load, spin, fire, fire   (option B)   produces   58.487%  deaths.
           load, load, spin, fire, spin, fire   (option C)   produces   55.82%   deaths.
                 load, load, spin, fire, fire   (option D)   produces   50.021%  deaths.

Ruby

Out of morbid interest I added strategy E: load, spin, shoot, load, spin, shoot.

class Revolver
  attr_accessor :strategy
  attr_reader :notches, :shot_count

  def initialize(strategy = [:load, :spin, :shoot], num_chambers = 6) # default like Deer hunter
    @chambers = Array.new(num_chambers) # by default 6 nils
    @strategy = strategy
    @notches, @shot_count, @loaded_count = 0, 0, 0
  end

  def load
    raise "gun completely loaded " if @chambers.all? :loaded
    @chambers.rotate! until @chambers[1] == nil #not sure about this; Raku rotates -1
    @chambers[1] = :loaded
    @chambers.rotate! #not sure about this; Raku rotates -1
    @loaded_count += 1
  end

  def spin
    @chambers.rotate!(rand(1..@chambers.size))
  end

  def unload
    @chambers.fill(nil)
    @loaded_count = 0
  end

  def shoot
    @chambers[0] = nil
    @chambers.rotate!
  end

  def play
    strategy.each{|action| send(action)}
    @shot_count += 1
    @notches += 1 unless @chambers.count(:loaded) == @loaded_count # all bullets still there?
    unload
  end
end

strategies = {:A => [:load, :spin, :load, :spin, :shoot, :spin, :shoot],
              :B => [:load, :spin, :load, :spin, :shoot, :shoot],
              :C => [:load, :load, :spin, :shoot, :spin, :shoot],
              :D => [:load, :load, :spin, :shoot, :shoot],
              :E => [:load, :spin, :shoot, :load, :spin, :shoot]}

n = 100_000
puts "simulation of #{n} runs:"
strategies.each do |name, strategy|
  gun = Revolver.new(strategy) # Revolver.new(strategy, 10) for a 10-shooter
  n.times{gun.play}
  puts "Strategy #{name}: #{gun.notches.fdiv(gun.shot_count)}"
end
Output:
simulation of 100000 runs:
Strategy A: 0.55728
Strategy B: 0.58316
Strategy C: 0.5598
Strategy D: 0.49876
Strategy E: 0.44323

V (Vlang)

Translation of: Kotlin
import rand

__global cylinder = []bool{len:6}

fn main() {
	test("LSLSFSF")
	test("LSLSFF")
	test("LLSFSF")
	test("LLSFF")
}

fn test(src string) {
	tests := 100000
	mut sum := 0
	for _ in 0..tests {
		sum += method(src)
	}
	println('${m_string(src)} produces ${100.0 * f32(sum) / f32(tests)}% deaths.')
}

fn rshift() {
	t := cylinder[5]
	for i := 4; i >= 0; i-- {
		cylinder[i+1] = cylinder[i]
	}
	cylinder[0] = t
}

fn unload() {
	for i := 0; i < 6; i++ {
		cylinder[i] = false
	}
}

fn load() {
	for cylinder[0] {
		rshift()
	}
	cylinder[0] = true
	rshift()
}

fn spin() {
	mut lim := 1 + rand.intn(6) or {exit(1)}
	for i := 1; i < lim; i++ {
		rshift()
	}
}

fn fire() bool {
	shot := cylinder[0]
	rshift()
	return shot
}

fn method(s string) int {
	unload()
	for c in s {
		match c.ascii_str() {
			'L' {load()}
			'S' {spin()}
			'F' {if fire() == true {return 1}}
			else {}
		}
	}
	return 0
}

fn m_string(s string) string {
	mut l := []string{}
	for c in s {
		match c.ascii_str() {
		   'L' {l << "load"}
		   'S' {l << "spin"}
		   'F' {l << "fire"}
		   else {}
		}
	}
	return l.join(', ')
}
Output:
load, spin, load, spin, fire, spin, fire produces 55.795% deaths.
load, spin, load, spin, fire, fire produces 58.453% deaths.
load, load, spin, fire, spin, fire produces 55.468% deaths.
load, load, spin, fire, fire produces 49.868% deaths.

Wren

Library: Wren-fmt
import "random" for Random
import "./fmt" for Fmt

var Rand = Random.new()

class Revolver {
    construct new() { 
        _cylinder = List.filled(6, false)
    }

    rshift() {
        var t = _cylinder[-1]
        for (i in 4..0) _cylinder[i+1] = _cylinder[i]
        _cylinder[0] = t
    }

    unload() {
        for (i in 0..5) _cylinder[i] = false
    }

    load() {
        while (_cylinder[0]) rshift()
        _cylinder[0] = true
        rshift()
    }

    spin() {
        for (i in 1..Rand.int(1, 7)) rshift()
    }

    fire() {
        var shot = _cylinder[0]
        rshift()
        return shot
    }

    method(s) {
        unload()
        for (c in s) {
            if (c == "L") {
                load()
            } else if (c == "S") {
                spin()
            } else if (c == "F") {
                if (fire()) return 1
            }
        }
        return 0
    }

    static mstring(s) {
        var l = []
        for (c in s) {
            if (c == "L") {
                l.add("load")
            } else if (c == "S") {
                l.add("spin")
            } else if (c == "F") {
                l.add("fire")
            }
        }
        return l.join(", ")
    }
}

var rev = Revolver.new()
var tests = 100000
for (m in ["LSLSFSF", "LSLSFF", "LLSFSF", "LLSFF"]) {
    var sum = 0
    for (t in 1..tests) sum = sum + rev.method(m)
    Fmt.print("$-40s produces $6.3f\% deaths.", Revolver.mstring(m), sum * 100 / tests)
}
Output:

Sample run:

load, spin, load, spin, fire, spin, fire produces 55.500% deaths.
load, spin, load, spin, fire, fire       produces 58.162% deaths.
load, load, spin, fire, spin, fire       produces 55.512% deaths.
load, load, spin, fire, fire             produces 50.013% deaths.