/* ARM assembly Raspberry PI  */
/*  program prisonniers.s   */

/* REMARK 1 : this program use routines in a include file
   see task Include a file language arm assembly
   for the routine affichageMess conversion10
   see at end of this program the instruction include */
/* for constantes see task include a file in arm assembly */
/************************************/
/* Constantes                       */
/************************************/
.include "../constantes.inc"

.equ NBDOORS,   100
.equ NBLOOP, 1000

/*********************************/
/* Initialized data              */
/*********************************/
.data
sMessResult:        .asciz "Random strategie  : @ sur 1000 \n"
sMessResultOPT:     .asciz "Optimal strategie : @ sur 1000 \n"
szCarriageReturn:   .asciz "\n"
.align 4
iGraine:  .int 123456
/*********************************/
/* UnInitialized data            */
/*********************************/
.bss
sZoneConv:        .skip 24
tbDoors:          .skip 4 * NBDOORS
tbTest:           .skip 4 * NBDOORS
/*********************************/
/*  code section                 */
/*********************************/
.text
.global main
main:                                 @ entry of program

    ldr r1,iAdrtbDoors
    mov r2,#0
1:                                    @ loop init doors table
    add r3,r2,#1
    str r3,[r1,r2,lsl #2]
    add r2,r2,#1
    cmp r2,#NBDOORS
    blt 1b

    mov r9,#0                         @ loop counter
    mov r10,#0                        @ counter successes random strategie
    mov r11,#0                        @ counter successes optimal strategie
2:
    ldr r0,iAdrtbDoors
    mov r1,#NBDOORS
    bl knuthShuffle

    ldr r0,iAdrtbDoors
    bl aleaStrategie
    cmp r0,#NBDOORS
    addeq r10,r10,#1

    ldr r0,iAdrtbDoors
    bl optimaStrategie
    cmp r0,#NBDOORS
    addeq r11,r11,#1

    add r9,r9,#1
    cmp r9,#NBLOOP
    blt 2b

    mov r0,r10                        @ result display
    ldr r1,iAdrsZoneConv
    bl conversion10                   @ call decimal conversion
    ldr r0,iAdrsMessResult
    ldr r1,iAdrsZoneConv              @ insert conversion in message
    bl strInsertAtCharInc
    bl affichageMess

    mov r0,r11                        @ result display
    ldr r1,iAdrsZoneConv
    bl conversion10                   @ call decimal conversion
    ldr r0,iAdrsMessResultOPT
    ldr r1,iAdrsZoneConv              @ insert conversion in message
    bl strInsertAtCharInc
    bl affichageMess

100:                                  @ standard end of the program
    mov r0, #0                        @ return code
    mov r7, #EXIT                     @ request to exit program
    svc #0                            @ perform the system call

iAdrszCarriageReturn:     .int szCarriageReturn
iAdrsMessResult:          .int sMessResult
iAdrsMessResultOPT:        .int sMessResultOPT
iAdrtbDoors:              .int tbDoors
iAdrtbTest:               .int tbTest
iAdrsZoneConv:            .int sZoneConv
/******************************************************************/
/*            random door test strategy                           */
/******************************************************************/
/* r0 contains the address of table */
aleaStrategie:
    push {r1-r7,lr}              @ save registers
    ldr r6,iAdrtbTest            @ table doors tests address
    mov r1,r0                    @ save table doors address
    mov r4,#0                    @ counter number of successes
    mov r2,#0                    @ prisonners indice
1:
    bl razTable                  @ zero to table doors tests
    mov r5,#0                    @ counter of door tests
    add r7,r2,#1
2:
    mov r0,#NBDOORS - 1
    bl genereraleas              @ random test
    add r0,r0,#1
    ldr r3,[r6,r0,lsl #2]        @ doors also tested ?
    cmp r3,#0
    bne 2b                       @ yes
    ldr r3,[r1,r0,lsl #2]        @ load N° door
    cmp r3,r7                    @ compar N° door N° prisonner
    addeq r4,r4,#1               @ succes
    beq 3f
    mov r3,#1                    @ top test table item
    str r3,[r6,r0,lsl #2]
    add r5,r5,#1
    cmp r5,#NBDOORS / 2          @ number tests maxi ?
    blt 2b                       @ no -> loop
3:
    add r2,r2,#1                 @ other prisonner
    cmp r2,#NBDOORS
    blt 1b

    mov r0,r4                    @ return number of successes
100:
    pop {r1-r7,lr}
    bx lr                        @ return
/******************************************************************/
/*     raz test table                                             */
/******************************************************************/
razTable:
    push {r0-r2,lr}              @ save registers
    ldr r0,iAdrtbTest
    mov r1,#0                    @ item indice
    mov r2,#0
1:
    str r2,[r0,r1,lsl #2]        @ store zero à item
    add r1,r1,#1
    cmp r1,#NBDOORS
    blt 1b
100:
    pop {r0-r2,lr}
    bx lr                        @ return
/******************************************************************/
/*            random door test strategy                           */
/******************************************************************/
/* r0 contains the address of table */
optimaStrategie:
    push {r1-r7,lr}              @ save registers
    mov r4,#0                    @ counter number of successes
    mov r2,#0                    @ counter prisonner
1:
    mov r5,#0                    @ counter test
    mov r1,r2                    @ first test = N° prisonner
2:
    ldr r3,[r0,r1,lsl #2]        @ load N° door
    cmp r3,r2
    addeq r4,r4,#1               @ equal -> succes
    beq 3f
    mov r1,r3                    @ new test with N° door
    add r5,r5,#1
    cmp r5,#NBDOORS / 2          @ test number maxi ?
    blt 2b
3:
    add r2,r2,#1                 @ other prisonner
    cmp r2,#NBDOORS
    blt 1b

    mov r0,r4
100:
    pop {r1-r7,lr}
    bx lr                        @ return
/******************************************************************/
/*     knuth Shuffle                                  */
/******************************************************************/
/* r0 contains the address of table */
/* r1 contains the number of elements */
knuthShuffle:
    push {r2-r5,lr}                   @ save registers
    mov r5,r0                         @ save table address
    mov r2,#0                         @ start index
1:
    mov r0,r2                         @ generate aleas
    bl genereraleas
    ldr r3,[r5,r2,lsl #2]             @ swap number on the table
    ldr r4,[r5,r0,lsl #2]
    str r4,[r5,r2,lsl #2]
    str r3,[r5,r0,lsl #2]
    add r2,#1                         @ next number
    cmp r2,r1                         @ end ?
    blt 1b                            @ no -> loop
100:
    pop {r2-r5,lr}
    bx lr                             @ return
/***************************************************/
/*   Generation random number                  */
/***************************************************/
/* r0 contains limit  */
genereraleas:
    push {r1-r4,lr}                   @ save registers
    ldr r4,iAdriGraine
    ldr r2,[r4]
    ldr r3,iNbDep1
    mul r2,r3,r2
    ldr r3,iNbDep1
    add r2,r2,r3
    str r2,[r4]                       @ maj de la graine pour l appel suivant
    cmp r0,#0
    beq 100f
    mov r1,r0                         @ divisor
    mov r0,r2                         @ dividende
    bl division
    mov r0,r3                         @ résult = remainder

100:                                  @ end function
    pop {r1-r4,lr}                    @ restaur registers
    bx lr                             @ return
/*****************************************************/
iAdriGraine: .int iGraine
iNbDep1:     .int 0x343FD
iNbDep2:     .int 0x269EC3
/***************************************************/
/*      ROUTINES INCLUDE                           */
/***************************************************/
.include "../affichage.inc"


NumOfTrials := 20000
randomFailTotal := 0, strategyFailTotal := 0
prisoners := [], drawers := [], Cards := []
loop, 100
	prisoners[A_Index] := A_Index				; create prisoners
	, drawers[A_Index] := true				; create drawers

loop, % NumOfTrials
{
	loop, 100
		Cards[A_Index] := A_Index			; create cards for this iteration
	loop, 100
	{
		Random, rnd, 1, Cards.count()
		drawers[A_Index] := Cards.RemoveAt(rnd)		; randomly place cards in drawers
	}
	;-------------------------------------------
	; randomly open drawers
	RandomList := []
	loop, 100
		RandomList[A_Index] := A_Index
	Fail := false
	while (A_Index <=100) && !Fail
	{
		thisPrisoner := A_Index
		res := ""
		while (thisCard <> thisPrisoner) && !Fail
		{
			Random, rnd, 1, % RandomList.Count()	; choose random number
			NextDrawer := RandomList.RemoveAt(rnd)	; remove drawer from random list (don't choose more than once)
			thisCard := drawers[NextDrawer]		; get card from this drawer
			if (A_Index > 50)
				Fail := true
		}
		if Fail
			randomFailTotal++
	}
	;-------------------------------------------
	; use optimal strategy
	Fail := false
	while (A_Index <=100) && !Fail
	{
		counter := 1, thisPrisoner := A_Index
		NextDrawer := drawers[thisPrisoner]		; 1st trial, drawer whose outside number is prisoner number
		while (drawers[NextDrawer] <> thisPrisoner) && !Fail
		{
			NextDrawer := drawers[NextDrawer]	; drawer with the same number as that of the revealed card
			if ++counter > 50
				Fail := true
		}
		if Fail
			strategyFailTotal++
	}
}
MsgBox %  "Number Of Trials = " NumOfTrials
		. "`nOptimal Strategy:`t" (1 - strategyFailTotal/NumOfTrials) *100 " % success rate"
		. "`nRandom Trials:`t" (1 - randomFailTotal/NumOfTrials) *100 " % success rate"


O = 50
N = 2*O
iterations = 10000

REM From the numbers 0 to N-1 inclusive, pick O of them.
function shuffle(N, O)
 dim array(N)
 for i = 0 to N-1
  array[i] = i
 next i
 for i = 0 to O-1
  swapindex = i + rand*(N-i)
  swapvalue = array[swapindex]
  array[swapindex] = array[i]
  array[i] = swapvalue
 next i
 return array
end function

REM given N drawers with O to open, prisoner P chooses randomly: does he choose well?
function chooserandom(drawers, N, O, p)
  choices = shuffle(N, O)
  for i = 0 to O-1
   if drawers[choices[i]] = p then return true
  next i
  return false
end function

REM N prisoners randomly choose O drawers to open: do they all choose well?
function allchooserandom(N, O)
 drawers = shuffle(N, N)
 for p = 0 to N-1
  goodchoice = chooserandom(drawers, N, O, p)
  if not goodchoice then return false
 next p
 return true
end function

REM given N drawers with O to open, prisoner P chooses smartly: does he choose well?
function choosesmart(drawers, N, O, p)
 numopened = 0
 i = p
 while numopened < O
  numopened += 1
  if drawers[i] = p then return true
  i = drawers[i]
 end while
 return false
end function

REM N prisoners smartly choose O drawers to open: do they all choose well?
function allchoosesmart(N, O)
 drawers = shuffle(N, N)
 for p = 0 to N-1
  goodchoice = choosesmart(drawers, N, O, p)
  if not goodchoice then return false
 next p
 return true
end function

cls
print N; " prisoners choosing ";O;" drawers, ";iterations;" iterations:"

total = 0
for iteration = 1 to iterations
 if allchooserandom(N, O) then total += 1
next iteration

print "Random choices: "; total;" out of ";iterations
print "Observed ratio: "; total/iterations; ", expected ratio: "; (O/N)^N

total = 0
for iteration = 1 to iterations
 if allchoosesmart(N, O) then total += 1
next iteration

print "Smart choices: "; total;" out of ";iterations
print "Observed ratio: "; total/iterations; ", expected ratio with N=2*O: greater than about 0.30685": REM for N=100, O=50 particularly, about 0.3118


get "libhdr"

manifest $(
    seed = 12345   // for pseudorandom number generator
    size = 100     // amount of drawers and prisoners
    tries = 50     // amount of tries each prisoner may make
    simul = 2000   // amount of simulations to run
$)

let randto(n) = valof
$(  static $( state = seed $)
    let mask = 1
    mask := (mask<<1)|1 repeatuntil mask > n
    state := random(state) repeatuntil ((state >> 8) & mask) < n
    resultis (state >> 8) & mask
$)

// initialize drawers
let placeCards(d, n) be
$(  for i=0 to n-1 do d!i := i;
    for i=0 to n-2 do
    $(  let j = i+randto(n-i)
        let k = d!i
        d!i := d!j
        d!j := k
    $)
$)

// random strategy (prisoner 'p' tries to find his own number)
let randoms(d, p, t) = valof
$(  for n = 1 to t do
        if d!randto(size) = p then resultis true
    resultis false
$)

// optimal strategy
let optimal(d, p, t) = valof
$(  let last = p
    for n = 1 to t do
        test d!last = p
            then resultis true
            else last := d!last
    resultis false
$)

// run a simulation given a strategy
let simulate(d, strat, n, t) = valof
$(  placeCards(d, n)
    for p = 0 to n-1 do
        if not strat(d, p, t) then resultis false
    resultis true
$)

// run many simulations and count the successes
let runSimulations(d, strat, n, amt, t) = valof
$(  let succ = 0
    for i = 1 to amt do
        if simulate(d, strat, n, t) do
            succ := succ + 1
    resultis succ
$)

let run(d, name, strat, n, amt, t) be
$(  let s = runSimulations(d, strat, n, amt, t);
    writef("%S: %I5 of %I5, %N percent.*N", name, s, amt, s*10/(amt/10))
$)

let start() be
$(  let d = vec size-1
    run(d, " Random", randoms, size, simul, tries)
    run(d, "Optimal", optimal, size, simul, tries)
$)


#include<stdbool.h>
#include<stdlib.h>
#include<stdio.h>
#include<time.h>

#define LIBERTY false
#define DEATH true

typedef struct{
	int cardNum;
	bool hasBeenOpened;
}drawer;

drawer *drawerSet;

void initialize(int prisoners){
	int i,j,card;
	bool unique;

	drawerSet = ((drawer*)malloc(prisoners * sizeof(drawer))) -1;

	card = rand()%prisoners + 1;
	drawerSet[1] = (drawer){.cardNum = card, .hasBeenOpened = false};

	for(i=1 + 1;i<prisoners + 1;i++){
		unique = false;
		while(unique==false){
			for(j=0;j<i;j++){
				if(drawerSet[j].cardNum == card){
					card = rand()%prisoners + 1;
					break;
				}
			}
			if(j==i){
				unique = true;
			}
		}
		drawerSet[i] = (drawer){.cardNum = card, .hasBeenOpened = false};
	}

}

void closeAllDrawers(int prisoners){
	int i;
	for(i=1;i<prisoners + 1;i++)
		drawerSet[i].hasBeenOpened = false;
}

bool libertyOrDeathAtRandom(int prisoners,int chances){
	int i,j,chosenDrawer;

	for(i= 1;i<prisoners + 1;i++){
		bool foundCard = false;
		for(j=0;j<chances;j++){
			do{
				chosenDrawer = rand()%prisoners + 1;
			}while(drawerSet[chosenDrawer].hasBeenOpened==true);
			if(drawerSet[chosenDrawer].cardNum == i){
				foundCard = true;
				break;
			}
			drawerSet[chosenDrawer].hasBeenOpened = true;
		}
		closeAllDrawers(prisoners);
		if(foundCard == false)
			return DEATH;
	}

	return LIBERTY;
}

bool libertyOrDeathPlanned(int prisoners,int chances){
	int i,j,chosenDrawer;
	for(i=1;i<prisoners + 1;i++){
		chosenDrawer = i;
		bool foundCard = false;
		for(j=0;j<chances;j++){
			drawerSet[chosenDrawer].hasBeenOpened = true;

			if(drawerSet[chosenDrawer].cardNum == i){
				foundCard = true;
				break;
			}
			if(chosenDrawer == drawerSet[chosenDrawer].cardNum){
				do{
                    chosenDrawer = rand()%prisoners + 1;
				}while(drawerSet[chosenDrawer].hasBeenOpened==true);
			}
			else{
				chosenDrawer = drawerSet[chosenDrawer].cardNum;
			}

		}

		closeAllDrawers(prisoners);
		if(foundCard == false)
			return DEATH;
	}

	return LIBERTY;
}

int main(int argc,char** argv)
{
	int prisoners, chances;
	unsigned long long int trials,i,count = 0;
        char* end;

	if(argc!=4)
		return printf("Usage : %s <Number of prisoners> <Number of chances> <Number of trials>",argv[0]);

	prisoners = atoi(argv[1]);
	chances = atoi(argv[2]);
	trials = strtoull(argv[3],&end,10);

	srand(time(NULL));

	printf("Running random trials...");
	for(i=0;i<trials;i+=1L){
		initialize(prisoners);

		count += libertyOrDeathAtRandom(prisoners,chances)==DEATH?0:1;
	}

	printf("\n\nGames Played : %llu\nGames Won : %llu\nChances : %lf %% \n\n",trials,count,(100.0*count)/trials);

        count = 0;

	printf("Running strategic trials...");
	for(i=0;i<trials;i+=1L){
		initialize(prisoners);

		count += libertyOrDeathPlanned(prisoners,chances)==DEATH?0:1;
	}

	printf("\n\nGames Played : %llu\nGames Won : %llu\nChances : %lf %% \n\n",trials,count,(100.0*count)/trials);
	return 0;
}


using System;
using System.Linq;

namespace Prisoners {
    class Program {
        static bool PlayOptimal() {
            var secrets = Enumerable.Range(0, 100).OrderBy(a => Guid.NewGuid()).ToList();

            for (int p = 0; p < 100; p++) {
                bool success = false;

                var choice = p;
                for (int i = 0; i < 50; i++) {
                    if (secrets[choice] == p) {
                        success = true;
                        break;
                    }
                    choice = secrets[choice];
                }

                if (!success) {
                    return false;
                }
            }

            return true;
        }

        static bool PlayRandom() {
            var secrets = Enumerable.Range(0, 100).OrderBy(a => Guid.NewGuid()).ToList();

            for (int p = 0; p < 100; p++) {
                var choices = Enumerable.Range(0, 100).OrderBy(a => Guid.NewGuid()).ToList();

                bool success = false;
                for (int i = 0; i < 50; i++) {
                    if (choices[i] == p) {
                        success = true;
                        break;
                    }
                }

                if (!success) {
                    return false;
                }
            }

            return true;
        }

        static double Exec(uint n, Func<bool> play) {
            uint success = 0;
            for (uint i = 0; i < n; i++) {
                if (play()) {
                    success++;
                }
            }
            return 100.0 * success / n;
        }

        static void Main() {
            const uint N = 1_000_000;
            Console.WriteLine("# of executions: {0}", N);
            Console.WriteLine("Optimal play success rate: {0:0.00000000000}%", Exec(N, PlayOptimal));
            Console.WriteLine(" Random play success rate: {0:0.00000000000}%", Exec(N, PlayRandom));
        }
    }
}


#include <cstdlib>   // for rand
#include <algorithm> // for random_shuffle
#include <iostream>  // for output

using namespace std;

class cupboard {
public:
    cupboard() {
        for (int i = 0; i < 100; i++)
            drawers[i] = i;
        random_shuffle(drawers, drawers + 100);
    }

    bool playRandom();
    bool playOptimal();

private:
    int drawers[100];
};

bool cupboard::playRandom() {
    bool openedDrawers[100] = { 0 };
    for (int prisonerNum = 0; prisonerNum < 100; prisonerNum++) { // loops through prisoners numbered 0 through 99
        bool prisonerSuccess = false;
        for (int i = 0; i < 100 / 2; i++) {  // loops through 50 draws for each prisoner
            int drawerNum = rand() % 100;
            if (!openedDrawers[drawerNum]) {
                openedDrawers[drawerNum] = true;
                break;
            }
            if (drawers[drawerNum] == prisonerNum) {
                prisonerSuccess = true;
                break;
            }
        }
        if (!prisonerSuccess)
            return false;
    }
    return true;
}

bool cupboard::playOptimal() {
    for (int prisonerNum = 0; prisonerNum < 100; prisonerNum++) {
        bool prisonerSuccess = false;
        int checkDrawerNum = prisonerNum;
        for (int i = 0; i < 100 / 2; i++) {
            if (drawers[checkDrawerNum] == prisonerNum) {
                prisonerSuccess = true;
                break;
            } else
                checkDrawerNum = drawers[checkDrawerNum];
        }
        if (!prisonerSuccess)
            return false;
    }
    return true;
}

double simulate(char strategy) {
    int numberOfSuccesses = 0;
    for (int i = 0; i < 10000; i++) {
        cupboard d;
        if ((strategy == 'R' && d.playRandom()) || (strategy == 'O' && d.playOptimal())) // will run playRandom or playOptimal but not both because of short-circuit evaluation
            numberOfSuccesses++;
    }

    return numberOfSuccesses * 100.0 / 10000;
}

int main() {
    cout << "Random strategy:  " << simulate('R') << " %" << endl;
    cout << "Optimal strategy: " << simulate('O') << " %" << endl;
    system("PAUSE"); // for Windows
    return 0;
}


(ns clojure-sandbox.prisoners)

(defn random-drawers []
  "Returns a list of shuffled numbers"
  (-> 100
      range
      shuffle))

(defn search-50-random-drawers [prisoner-number drawers]
  "Select 50 random drawers and return true if the prisoner's number was found"
  (->> drawers
      shuffle ;; Put drawer contents in random order
      (take 50) ;; Select first 50, equivalent to selecting 50 random drawers
      (filter (fn [x] (= x prisoner-number))) ;; Filter to include only those that match prisoner number
      count
      (= 1))) ;; Returns true if the number of matching numbers is 1

(defn search-50-optimal-drawers [prisoner-number drawers]
  "Open 50 drawers according to the agreed strategy, returning true if prisoner's number was found"
  (loop [next-drawer prisoner-number ;; The drawer index to start on is the prisoner's number
         drawers-opened 0] ;; To keep track of how many have been opened as 50 is the maximum
    (if (= drawers-opened 50)
      false ;; If 50 drawers have been opened, the prisoner's number has not been found
      (let [result (nth drawers next-drawer)] ;; Open the drawer given by next number
        (if (= result prisoner-number) ;; If prisoner number has been found
          true ;; No need to keep opening drawers - return true
          (recur result (inc drawers-opened))))))) ;; Restart the loop using the resulting number as the drawer number

(defn try-luck [drawers drawer-searching-function]
  "Returns 1 if all prisoners find their number otherwise 0"
  (loop [prisoners (range 100)] ;; Start with 100 prisoners
    (if (empty? prisoners) ;; If they've all gone and found their number
      1 ;; Return true- they'll all live
      (let [res (-> prisoners
                    first
                    (drawer-searching-function drawers))] ;; Otherwise, have the first prisoner open drawers according to the specified method
        (if (false? res) ;; If this prisoner didn't find their number
          0 ;; no prisoners will be freed so we can return false and stop
          (recur (rest prisoners))))))) ;; Otherwise they've found the number, so we remove them from the queue and repeat with the others

(defn simulate-100-prisoners []
  "Simulates all prisoners searching the same drawers by both strategies, returns map showing whether each was successful"
  (let [drawers (random-drawers)] ;; Create 100 drawers with randomly ordered prisoner numbers
    {:random (try-luck drawers search-50-random-drawers) ;; True if all prisoners found their number using random strategy
     :optimal (try-luck drawers search-50-optimal-drawers)})) ;; True if all prisoners found their number using optimal strategy

(defn simulate-n-runs [n]
  "Simulate n runs of the 100 prisoner problem and returns a success count for each search method"
  (loop [random-successes 0
         optimal-successes 0
         run-count 0]
    (if (= n run-count) ;; If we've done the loop n times
      {:random-successes random-successes ;; return results
       :optimal-successes optimal-successes
       :run-count run-count}
      (let [next-result (simulate-100-prisoners)] ;; Otherwise, run for another batch of prisoners
        (recur (+ random-successes (:random next-result)) ;; Add result of run to the total successs count
               (+ optimal-successes (:optimal next-result))
               (inc run-count)))))) ;; increment run count and run again

(defn -main [& args]
  "For 5000 runs, print out the success frequency for both search methods"
  (let [{:keys [random-successes optimal-successes run-count]} (simulate-n-runs 5000)]
    (println (str "Probability of survival with random search: " (float (/ random-successes run-count))))
    (println (str "Probability of survival with ordered search: " (float (/ optimal-successes run-count))))))


% This program needs to be merged with PCLU's "misc" library
% to use the random number generator.
%
% pclu -merge $CLUHOME/lib/misc.lib -compile prisoners.clu

% Seed the random number generator with the current time
init_rng = proc ()
    d: date := now()
    seed: int := ((d.hour*60) + d.minute)*60 + d.second
    random$seed(seed)
end init_rng

% Place cards in drawers randomly
make_drawers = proc (n: int) returns (sequence[int])
    d: array[int] := array[int]$predict(1,n)

    % place each card in its own drawer
    for i: int in int$from_to(1,n) do
        array[int]$addh(d,i)
    end

    % shuffle the cards
    for i: int in int$from_to_by(n,2,-1) do
        j: int := random$next(i)+1
        t: int := d[i]
        d[i] := d[j]
        d[j] := t
    end
    return(sequence[int]$a2s(d))
end make_drawers

% Random strategy
rand_strat = proc (p, tries: int, d: sequence[int]) returns (bool)
    n: int := sequence[int]$size(d)
    for i: int in int$from_to(1,tries) do
        if p = d[random$next(n)+1] then return(true) end
    end
    return(false)
end rand_strat

% Optimal strategy
opt_strat = proc (p, tries: int, d: sequence[int]) returns (bool)
    last: int := p
    for i: int in int$from_to(1,tries) do
        if d[last]=p then return(true) end
        last := d[last]
    end
    return(false)
end opt_strat

% Run one simulation given a strategy
simulate = proc (n, tries: int,
                 strat: proctype (int,int,sequence[int]) returns (bool))
           returns (bool)
    d: sequence[int] := make_drawers(n)
    for p: int in int$from_to(1,n) do
        % If one prisoner fails, they all hang
        if ~strat(p,tries,d) then return(false) end
    end
    return(true)
end simulate

% Run many simulations and count the successes
run_simulations = proc (amount, n, tries: int,
                        strat: proctype (int,int,sequence[int]) returns (bool))
                  returns (int)
    ok: int := 0
    for i: int in int$from_to(1,amount) do
        if simulate(n,tries,strat) then
            ok := ok + 1
        end
    end
    return(ok)
end run_simulations

% Run simulations and show the results
show = proc (title: string,
             amount, n, tries: int,
             strat: proctype (int,int,sequence[int]) returns (bool))
    po: stream := stream$primary_output()
    stream$puts(po, title || ": ")

    ok: int := run_simulations(amount, n, tries, strat)
    perc: real := real$i2r(ok)*100.0/real$i2r(amount)

    stream$putright(po, int$unparse(ok), 7)
    stream$puts(po, " out of ")
    stream$putright(po, int$unparse(amount), 7)
    stream$putl(po, ", " || f_form(perc, 3, 2) || "%")
end show

start_up = proc ()
    prisoners   = 100
    tries       = 50
    simulations = 50000

    init_rng()

    show(" Random", simulations, prisoners, tries, rand_strat)
    show("Optimal", simulations, prisoners, tries, opt_strat)
end start_up


10 rem 100 prisoners
20 rem set arrays
30 rem dr = drawers containing card values
40 rem ig = a list of numbers 1 through 100, shuffled to become the
41 rem guess sequence for each inmate - method 1
50 dim dr(100),ig(100)
55 rem initialize drawers with own card in each drawer
60 for i=1 to 100:dr(i)=i:next

1000 print chr$(147);"how many trials for each method";:input tt
1010 for m=1 to 2:su(m)=0:fa(m)=0
1015 for tn=1 to tt
1020 on m gosub 2000,3000
1025 rem ip = number of inmates who passed
1030 if ip=100 then su(m)=su(m)+1
1040 if ip<100 then fa(m)=fa(m)+1
1045 next tn
1055 next m

1060 print chr$(147);"Results:":print
1070 print "Out of";tt;"trials, the results are"
1071 print "as follows...":print
1072 print "1. Random Guessing:"
1073 print "  ";su(1);"successes"
1074 print "  ";fa(1);"failures"
1075 print "  ";su(1)/tn;"{left-crsr}% success rate.":print
1077 print "2. Chained Number Picking:"
1078 print "  ";su(2);"successes"
1079 print "  ";fa(2);"failures"
1080 print "  ";(su(2)/tn)*100;"{left-crsr}% success rate.":print
1100 print:print "Again?"
1110 get k$:if k$="" then 1110
1120 if k$="y" then 1000
1500 end

2000 rem random guessing method
2005 for x=1 to 100:ig(x)=x:next:ip=0:gosub 4000
2007 for i=1 to 100
2010 for x=1 to 100:t=ig(x):np=int(rnd(1)*100)+1:ig(x)=ig(np):ig(np)=t:next
2015 for g=1 to 50
2020 if dr(ig(g))=i then ip=ip+1:next i:return
2025 next g
2030 return

3000 rem chained method
3005 ip=0:gosub 4000
3007 rem iterate through each inmate
3010 fori=1to100
3015 ng=i:forg=1to50
3020 cd=dr(ng)
3025 ifcd=ithenip=ip+1:nexti:return
3030 ifcd<>ithenng=cd
3035 nextg:return

4000 rem shuffle the drawer cards randomly
4010 x=rnd(-ti)
4020 for i=1 to 100
4030 r=int(rnd(1)*100)+1:t=dr(i):dr(i)=dr(r):dr(r)=t:next
4040 return


(defparameter *samples* 10000)
(defparameter *prisoners* 100)
(defparameter *max-guesses* 50)

(defun range (n)
  "Returns a list from 0 to N."
  (loop
     for i below n
     collect i))

(defun nshuffle (list)
  "Returns a shuffled LIST."
  (loop
     for i from (length list) downto 2
     do (rotatef (nth (random i) list)
                 (nth (1- i) list)))
  list)

(defun build-drawers ()
  "Returns a list of shuffled drawers."
  (nshuffle (range *prisoners*)))

(defun strategy-1 (drawers p)
  "Returns T if P is found in DRAWERS under *MAX-GUESSES* using a random strategy."
  (loop
     for i below *max-guesses*
     thereis (= p (nth (random *prisoners*) drawers))))

(defun strategy-2 (drawers p)
  "Returns T if P is found in DRAWERS under *MAX-GUESSES* using an optimal strategy."
  (loop
     for i below *max-guesses*
     for j = p then (nth j drawers)
     thereis (= p (nth j drawers))))

(defun 100-prisoners-problem (strategy &aux (drawers (build-drawers)))
  "Returns T if all prisoners find their number using the given STRATEGY."
  (every (lambda (e) (eql T e))
         (mapcar (lambda (p) (funcall strategy drawers p)) (range *prisoners*))))

(defun sampling (strategy)
  (loop
     repeat *samples*
     for result = (100-prisoners-problem strategy)
     count result))

(defun compare-strategies ()
  (format t "Using a random strategy in ~4,2F % of the cases the prisoners are free.~%" (* (/ (sampling #'strategy-1) *samples*) 100))
  (format t "Using an optimal strategy in ~4,2F % of the cases the prisoners are free.~%" (* (/ (sampling #'strategy-2) *samples*) 100)))


include "cowgol.coh";
include "argv.coh";

# Parameters
const Drawers     := 100;   # Amount of drawers (and prisoners)
const Attempts    := 50;    # Amount of attempts a prisoner may make
const Simulations := 2000;  # Amount of simulations to run

typedef NSim is int(0, Simulations);

# Random number generator
record RNG is
    x: uint8;
    a: uint8;
    b: uint8;
    c: uint8;
    state @at(0): int32;
end record;

sub RandomByte(r: [RNG]): (byte: uint8) is
    r.x := r.x + 1;
    r.a := r.a ^ r.c ^ r.x;
    r.b := r.b + r.a;
    r.c := r.c + (r.b >> 1) ^ r.a;
    byte := r.c;
end sub;

sub RandomUpTo(r: [RNG], limit: uint8): (rslt: uint8) is
    var x: uint8 := 1;
    while x < limit loop
        x := x << 1;
    end loop;
    x := x - 1;

    loop
        rslt := RandomByte(r) & x;
        if rslt < limit then
            break;
        end if;
    end loop;
end sub;

# Drawers (though marked 0..99 instead of 1..100)
var drawers: uint8[Drawers];
typedef Drawer is @indexof drawers;
typedef Prisoner is Drawer;

# Place cards randomly in drawers
sub InitDrawers(r: [RNG]) is
    var x: Drawer := 0;
    while x < Drawers loop
        drawers[x] := x;
        x := x + 1;
    end loop;

    x := 0;
    while x < Drawers - 1 loop
        var y := x + RandomUpTo(r, Drawers-x);
        var t := drawers[x];
        drawers[x] := drawers[y];
        drawers[y] := t;
        x := x + 1;
    end loop;
end sub;

# A prisoner can apply a strategy and either succeed or not
interface Strategy(p: Prisoner, r: [RNG]): (success: uint8);

# The stupid strategy: open drawers randomly.
sub Stupid implements Strategy is
    # Let's assume the prisoner is smart enough not to reopen an open drawer
    var opened: Drawer[Drawers];
    MemZero(&opened[0], @bytesof opened);

    # Open random drawers
    success := 0;
    var triesLeft: uint8 := Attempts;
    while triesLeft != 0 loop
        var d := RandomUpTo(r, Drawers); # grab a random drawer
        if opened[d] != 0 then
            continue; # Ignore it if a drawer was already open
        else
            triesLeft := triesLeft - 1;
            opened[d] := 1;
            if drawers[d] == p then # found it!
                success := 1;
                return;
            end if;
        end if;
    end loop;
end sub;

# The optimal strategy: open the drawer for each number
sub Optimal implements Strategy is
    var current := p;
    var triesLeft: uint8 := Attempts;
    success := 0;
    while triesLeft != 0 loop
        current := drawers[current];
        if current == p then
            success := 1;
            return;
        end if;
        triesLeft := triesLeft - 1;
    end loop;
end sub;

# Run a simulation
sub Simulate(s: Strategy, r: [RNG]): (success: uint8) is
    InitDrawers(r); # place cards randomly in drawer
    var p: Prisoner := 0;
    success := 1; # if they all succeed the simulation succeeds
    while p < Drawers loop # but for each prisoner...
        if s(p, r) == 0 then # if he fails, the simulation fails
            success := 0;
            return;
        end if;
        p := p + 1;
    end loop;
end sub;

# Run an amount of simulations and report the amount of successes
sub Run(n: NSim, s: Strategy, r: [RNG]): (successes: NSim) is
    successes := 0;
    while n > 0 loop
        successes := successes + Simulate(s, r) as NSim;
        n := n - 1;
    end loop;
end sub;

# Initialize RNG with number given on command line (defaults to 0)
var rng: RNG; rng.state := 0;
ArgvInit();
var arg := ArgvNext();
if arg != 0 as [uint8] then
    (rng.state, arg) := AToI(arg);
end if;

sub RunAndPrint(name: [uint8], strat: Strategy) is
    print(name);
    print(" strategy: ");
    var succ := Run(Simulations, strat, &rng) as uint32;
    print_i32(succ);
    print(" out of ");
    print_i32(Simulations);
    print(" - ");
    print_i32(succ * 100 / Simulations);
    print("%\n");
end sub;

RunAndPrint("Stupid", Stupid);
RunAndPrint("Optimal", Optimal);


prisoners = (1..100).to_a
N = 100_000
generate_rooms = ->{ (1..100).to_a.shuffle }

res = N.times.count do
  rooms = generate_rooms.call
  prisoners.all? { |pr| rooms[1, 100].sample(50).includes?(pr) }
end
puts "Random strategy : %11.4f %%" % (res.fdiv(N) * 100)

res = N.times.count do
  rooms = generate_rooms.call
  prisoners.all? do |pr|
    cur_room = pr
    50.times.any? do
      cur_room = rooms[cur_room - 1]
      found = (cur_room == pr)
      found
    end
  end
end
puts "Optimal strategy: %11.4f %%" % (res.fdiv(N) * 100)


import std.array;
import std.random;
import std.range;
import std.stdio;
import std.traits;

bool playOptimal() {
    auto secrets = iota(100).array.randomShuffle();

    prisoner:
    foreach (p; 0..100) {
        auto choice = p;
        foreach (_; 0..50) {
            if (secrets[choice] == p) continue prisoner;
            choice = secrets[choice];
        }
        return false;
    }

    return true;
}

bool playRandom() {
    auto secrets = iota(100).array.randomShuffle();

    prisoner:
    foreach (p; 0..100) {
        auto choices = iota(100).array.randomShuffle();
        foreach (i; 0..50) {
            if (choices[i] == p) continue prisoner;
        }
        return false;
    }

    return true;
}

double exec(const size_t n, bool function() play) {
    size_t success = 0;
    for (int i = n; i > 0; i--) {
        if (play()) {
            success++;
        }
    }
    return 100.0 * success / n;
}

void main() {
    enum N = 1_000_000;
    writeln("# of executions: ", N);
    writefln("Optimal play success rate: %11.8f%%", exec(N, &playOptimal));
    writefln(" Random play success rate: %11.8f%%", exec(N, &playRandom));
}


for i range 100
  drawer[] &= i
  sampler[] &= i
.
subr shuffle_drawer
  for i = len drawer[] downto 2
    r = random i
    swap drawer[r] drawer[i - 1]
  .
.
subr play_random
  call shuffle_drawer
  found = 1
  prisoner = 0
  while prisoner < 100 and found = 1
    found = 0
    i = 0
    while i < 50 and found = 0
      r = random (100 - i)
      card = drawer[sampler[r]]
      swap sampler[r] sampler[100 - i - 1]
      if card = prisoner
        found = 1
      .
      i += 1
    .
    prisoner += 1
  .
.
subr play_optimal
  call shuffle_drawer
  found = 1
  prisoner = 0
  while prisoner < 100 and found = 1
    reveal = prisoner
    found = 0
    i = 0
    while i < 50 and found = 0
      card = drawer[reveal]
      if card = prisoner
        found = 1
      .
      reveal = card
      i += 1
    .
    prisoner += 1
  .
.
n = 10000
pardoned = 0
for round range n
  call play_random
  pardoned += found
.
print "random: " & 100.0 * pardoned / n & "%"
#
pardoned = 0
for round range n
  call play_optimal
  pardoned += found
.
print "optimal: " & 100.0 * pardoned / n & "%"


defmodule HundredPrisoners do
  def optimal_room(_, _, _, []), do: []
  def optimal_room(prisoner, current_room, rooms, [_ | tail]) do
    found = Enum.at(rooms, current_room - 1) == prisoner
    next_room = Enum.at(rooms, current_room - 1)
    [found] ++ optimal_room(prisoner, next_room, rooms, tail)
  end

  def optimal_search(prisoner, rooms) do
    Enum.any?(optimal_room(prisoner, prisoner, rooms, Enum.to_list(1..50)))
  end
end

prisoners = 1..100
n = 1..10_000
generate_rooms = fn -> Enum.shuffle(1..100) end

random_strategy = Enum.count(n,
  fn _ ->
  rooms = generate_rooms.()
  Enum.all?(prisoners, fn pr -> pr in (rooms |> Enum.take_random(50)) end)
end)

IO.puts "Random strategy: #{random_strategy} / #{n |> Range.size}"

optimal_strategy = Enum.count(n,
  fn _ ->
  rooms = generate_rooms.()
  Enum.all?(prisoners,
    fn pr -> HundredPrisoners.optimal_search(pr, rooms) end)
end)

IO.puts "Optimal strategy: #{optimal_strategy} / #{n |> Range.size}"


let rnd = System.Random()
let shuffled min max =
    [|min..max|] |> Array.sortBy (fun _ -> rnd.Next(min,max+1))

let drawers () = shuffled 1 100

// strategy randomizing drawer opening
let badChoices (drawers' : int array) =
    Seq.init 100 (fun _ -> shuffled 1 100 |> Array.take 50) // selections for each prisoner
    |> Seq.map (fun indexes -> indexes |> Array.map(fun index -> drawers'.[index-1])) // transform to cards
    |> Seq.mapi (fun i cards -> cards |> Array.contains i) // check if any card matches prisoner number
    |> Seq.contains false // true means not all prisoners got their cards
let outcomeOfRandom runs =
    let pardons = Seq.init runs (fun _ -> badChoices (drawers ()))
                  |> Seq.sumBy (fun badChoice -> if badChoice |> not then 1.0 else 0.0)
    pardons/ float runs

// strategy optimizing drawer opening
let smartChoice max prisoner (drawers' : int array) =
    prisoner
    |> Seq.unfold (fun selection ->
        let card = drawers'.[selection-1]
        Some (card, card))
    |> Seq.take max
    |> Seq.contains prisoner
let smartChoices (drawers' : int array) =
    seq { 1..100 }
    |> Seq.map (fun prisoner -> smartChoice 50 prisoner drawers')
    |> Seq.filter (fun result -> result |> not) // remove all but false results
    |> Seq.isEmpty // empty means all prisoners got their cards
let outcomeOfOptimize runs =
    let pardons = Seq.init runs (fun _ -> smartChoices (drawers()))
                  |> Seq.sumBy (fun smartChoice' -> if smartChoice' then 1.0 else 0.0)
    pardons/ float runs

printfn $"Using Random Strategy: {(outcomeOfRandom 20000):p2}"
printfn $"Using Optimum Strategy: {(outcomeOfOptimize 20000):p2}"


USING: arrays formatting fry io kernel math random sequences ;

: setup ( -- seq seq ) 100 <iota> dup >array randomize ;

: rand ( -- ? )
    setup [ 50 sample member? not ] curry find nip >boolean not ;

: trail ( m seq -- n )
    50 pick '[ [ nth ] keep over _ = ] replicate [ t = ] any?
    2nip ;

: optimal ( -- ? ) setup [ trail ] curry [ and ] map-reduce ;

: simulate ( m quot -- x )
    dupd replicate [ t = ] count swap /f 100 * ; inline

"Simulation count: 10,000" print
10,000 [ rand ] simulate "Random play success: "
10,000 [ optimal ] simulate "Optimal play success: "
[ write "%.2f%%\n" printf ] [email protected]


INCLUDE ran4.seq

100      CONSTANT #drawers
#drawers CONSTANT #players
100000   CONSTANT #tries

CREATE drawers  #drawers CELLS ALLOT                    \ index 0..#drawers-1

: drawer[]                              ( n -- addr )   \ return address of drawer n
    CELLS drawers +
;

: random_drawer                         ( -- n )        \ n=0..#drawers-1 random drawer
    RAN4 ( d ) XOR ( n ) #drawers MOD
;

: random_drawer[]                       ( -- addr )     \ return address of random drawer
    random_drawer drawer[]
;

: swap_indirect                         ( addr1 addr2 -- )  \ swaps the values at the two addresses
    2DUP @ SWAP @                       ( addr1 addr2 n2 n1 )
    ROT ! SWAP !                        \ store n1 at addr2 and n2 at addr1
;

: init_drawers                          ( -- ) \ shuffle cards into drawers
    #drawers 0 DO
        I I drawer[] !                  \ store cards in order
    LOOP
    #drawers 0 DO
        I drawer[]  random_drawer[]     ( addr-drawer-i addr-drawer-rnd )
        swap_indirect
    LOOP
;

: random_turn                           ( player - f )
    #drawers 2 / 0 DO
		random_drawer
		drawer[] @
		OVER = IF
			DROP TRUE UNLOOP EXIT	\ found his number
		THEN
	LOOP
	DROP FALSE
;

0 VALUE player

: cycle_turn                            ( player - f )
	DUP TO player			( next-drawer )
    #drawers 2 / 0 DO
		drawer[] @
		DUP player = IF
			DROP TRUE UNLOOP EXIT	\ found his number
		THEN
	LOOP
	DROP FALSE
;

: turn                                  ( strategy player - f )
    SWAP 0= IF                          \ random play
        random_turn
    ELSE
        cycle_turn
    THEN
;

: play                                  ( strategy -- f ) \ return true if prisioners survived
    init_drawers
    #players 0 DO
        DUP I turn
        0= IF
            DROP FALSE UNLOOP EXIT 	\ this player did not survive, UNLOOP, return false
        THEN
    LOOP
    DROP TRUE                           \ all survived, return true
;

: trie					( strategy - nr-saved )
	0				( strategy nr-saved )
	#tries 0 DO
		OVER play IF 1+ THEN
	LOOP
	NIP
;

0 trie . CR	\ random strategy
1 trie . CR	\ follow the card number strategy


SUBROUTINE SHUFFLE_ARRAY(INT_ARRAY)
    ! Takes an input array and shuffles the elements by swapping them
    ! in pairs in turn 10 times
    IMPLICIT NONE

    INTEGER, DIMENSION(100), INTENT(INOUT) :: INT_ARRAY
    INTEGER, PARAMETER :: N_PASSES = 10
    ! Local Variables

    INTEGER :: TEMP_1, TEMP_2   ! Temporaries for swapping elements
    INTEGER :: I, J, PASS       ! Indices variables
    REAL :: R                   ! Randomly generator value

    CALL RANDOM_SEED()  ! Seed the random number generator

    DO PASS=1, N_PASSES
        DO I=1, SIZE(INT_ARRAY)

            ! Get a random index to swap with
            CALL RANDOM_NUMBER(R)
            J = CEILING(R*SIZE(INT_ARRAY))

            ! In case generated index value
            ! exceeds array size
            DO WHILE (J > SIZE(INT_ARRAY))
                J = CEILING(R*SIZE(INT_ARRAY))
            END DO

            !  Swap the two elements
            TEMP_1 = INT_ARRAY(I)
            TEMP_2 = INT_ARRAY(J)
            INT_ARRAY(I) = TEMP_2
            INT_ARRAY(J) = TEMP_1
        ENDDO
    ENDDO
END SUBROUTINE SHUFFLE_ARRAY

SUBROUTINE RUN_RANDOM(N_ROUNDS)
    ! Run the 100 prisoner puzzle simulation N_ROUNDS times
    ! in the scenario where each prisoner selects a drawer at random
    IMPLICIT NONE

    INTEGER, INTENT(IN) :: N_ROUNDS ! Number of simulations to run in total

    INTEGER :: ROUND, PRISONER, CHOICE, I       ! Iteration variables
    INTEGER :: N_SUCCESSES                      ! Number of successful trials
    REAL(8) :: TOTAL                            ! Total number of trials as real
    LOGICAL :: NUM_FOUND = .FALSE.              ! Prisoner has found their number

    INTEGER, DIMENSION(100) :: CARDS, CHOICES   ! Arrays representing card allocations
                                                ! to draws and drawer choice order

    ! Both cards and choices are randomly assigned.
    ! This being the drawer (allocation represented by index),
    ! and what drawer to pick for Nth/50 choice
    ! (take first 50 elements of 100 element array)
    CARDS = (/(I, I=1, 100, 1)/)
    CHOICES = (/(I, I=1, 100, 1)/)

    N_SUCCESSES = 0
    TOTAL = REAL(N_ROUNDS)

    ! Run the simulation for N_ROUNDS rounds
    ! when a prisoner fails to find their number
    ! after 50 trials, set that simulation to fail
    ! and start the next round
    ROUNDS_LOOP: DO ROUND=1, N_ROUNDS
        CALL SHUFFLE_ARRAY(CARDS)
        PRISONERS_LOOP: DO PRISONER=1, 100
            NUM_FOUND = .FALSE.
            CALL SHUFFLE_ARRAY(CHOICES)
            CHOICE_LOOP: DO CHOICE=1, 50
                IF(CARDS(CHOICE) == PRISONER) THEN
                    NUM_FOUND = .TRUE.
                    EXIT CHOICE_LOOP
                ENDIF
            ENDDO CHOICE_LOOP
            IF(.NOT. NUM_FOUND) THEN
                EXIT PRISONERS_LOOP
            ENDIF
        ENDDO PRISONERS_LOOP
        IF(NUM_FOUND) THEN
            N_SUCCESSES = N_SUCCESSES + 1
        ENDIF
    ENDDO ROUNDS_LOOP

    WRITE(*, '(A, F0.3, A)') "Random drawer selection method success rate: ", &
        100*N_SUCCESSES/TOTAL, "%"

END SUBROUTINE RUN_RANDOM

SUBROUTINE RUN_OPTIMAL(N_ROUNDS)
    ! Run the 100 prisoner puzzle simulation N_ROUNDS times in the scenario
    ! where each prisoner selects firstly the drawer with their number and then
    ! subsequently the drawer matching the number of the card present
    ! within that current drawer
    IMPLICIT NONE

    INTEGER, INTENT(IN) :: N_ROUNDS

    INTEGER :: ROUND, PRISONER, CHOICE, I   ! Iteration variables
    INTEGER :: CURRENT_DRAW                 ! ID of the current draw
    INTEGER :: N_SUCCESSES                  ! Number of successful trials
    REAL(8) :: TOTAL                        ! Total number of trials as real
    LOGICAL :: NUM_FOUND = .FALSE.          ! Prisoner has found their number
    INTEGER, DIMENSION(100) :: CARDS        ! Array representing card allocations

    ! Cards are randomly assigned to a drawer
    ! (allocation represented by index),
    CARDS = (/(I, I=1, 100, 1)/)

    N_SUCCESSES = 0
    TOTAL = REAL(N_ROUNDS)

    ! Run the simulation for N_ROUNDS rounds
    ! when a prisoner fails to find their number
    ! after 50 trials, set that simulation to fail
    ! and start the next round
    ROUNDS_LOOP: DO ROUND=1, N_ROUNDS
        CARDS = (/(I, I=1, 100, 1)/)
        CALL SHUFFLE_ARRAY(CARDS)
        PRISONERS_LOOP: DO PRISONER=1, 100
            CURRENT_DRAW = PRISONER
            NUM_FOUND = .FALSE.
            CHOICE_LOOP: DO CHOICE=1, 50
                IF(CARDS(CURRENT_DRAW) == PRISONER) THEN
                    NUM_FOUND = .TRUE.
                    EXIT CHOICE_LOOP
                ELSE
                    CURRENT_DRAW = CARDS(CURRENT_DRAW)
                ENDIF
            ENDDO CHOICE_LOOP
            IF(.NOT. NUM_FOUND) THEN
                EXIT PRISONERS_LOOP
            ENDIF
        ENDDO PRISONERS_LOOP
        IF(NUM_FOUND) THEN
            N_SUCCESSES = N_SUCCESSES + 1
        ENDIF
    ENDDO ROUNDS_LOOP
    WRITE(*, '(A, F0.3, A)') "Optimal drawer selection method success rate: ", &
        100*N_SUCCESSES/TOTAL, "%"

END SUBROUTINE RUN_OPTIMAL

PROGRAM HUNDRED_PRISONERS
    ! Run the two scenarios for the 100 prisoners puzzle of random choice
    ! and optimal choice (choice based on drawer contents)
    IMPLICIT NONE
    INTEGER, PARAMETER :: N_ROUNDS = 50000
    WRITE(*,'(A, I0, A)') "Running simulation for ", N_ROUNDS, " trials..."
    CALL RUN_RANDOM(N_ROUNDS)
    CALL RUN_OPTIMAL(N_ROUNDS)
END PROGRAM HUNDRED_PRISONERS


#include once "knuthshuf.bas"   'use the routines in https://rosettacode.org/wiki/Knuth_shuffle#FreeBASIC

function gus( i as long, strat as boolean ) as long
    if strat then return i
    return 1+int(rnd*100)
end function

sub trials( byref c_success as long, byref c_fail as long, byval strat as boolean )
        dim as long i, j, k, guess, drawer(1 to 100)
    for i = 1 to 100
        drawer(i) = i
    next i
    for j = 1 to 1000000 'one million trials of prisoners
        knuth_up( drawer() )  'shuffles the cards in the drawers
            for i = 1 to 100 'prisoner number
            guess = gus(i, strat)
            for k = 1 to 50 'each prisoner gets 50 tries
                if drawer(guess) = i then goto next_prisoner
                guess = gus(drawer(guess), strat)
            next k
            c_fail += 1
            goto next_trial
            next_prisoner:
        next i
        c_success += 1
        next_trial:
    next j
end sub

randomize timer
dim as long c_fail=0, c_success=0

trials( c_success, c_fail, false )

print using "For prisoners guessing randomly we had ####### successes and ####### failures.";c_success;c_fail

c_success = 0
c_fail = 0

trials( c_success, c_fail, true )

print using "For prisoners using the strategy we had ####### successes and ####### failures.";c_success;c_fail


' Gambas module file

Public DrawerArray As Long[]
Public NumberFromDrawer As Long
Public FoundOwnNumber As Long

Public Sub Main()

  Dim NumberOfPrisoners As Long
  Dim Selections As Long
  Dim Tries As Long

  Print "Number of prisoners (default, 100)?"
  Try Input NumberOfPrisoners
  If Error Then NumberOfPrisoners = 100

  Print "Number of selections (default, half of prisoners)?"
  Try Input Selections
  If Error Then Selections = NumberOfPrisoners / 2

  Print "Number of tries (default, 1000)?"
  Try Input Tries
  If Error Then Tries = 1000

  Dim AllFoundOptimal As Long = 0
  Dim AllFoundRandom As Long = 0
  Dim AllFoundRandomMem As Long = 0

  Dim i As Long
  Dim OptimalCount As Long
  Dim RandomCount As Long
  Dim RandomMenCount As Long

  Dim fStart As Float = Timer

  For i = 1 To Tries
    OptimalCount = HundredPrisoners_Optimal(NumberOfPrisoners, Selections)
    RandomCount = HundredPrisoners_Random(NumberOfPrisoners, Selections)
    RandomMenCount = HundredPrisoners_Random_Mem(NumberOfPrisoners, Selections)

    If OptimalCount = NumberOfPrisoners Then AllFoundOptimal += 1
    If RandomCount = NumberOfPrisoners Then AllFoundRandom += 1
    If RandomMenCount = NumberOfPrisoners Then AllFoundRandomMem += 1
  Next

  Dim fTime As Float = Timer - fStart
  fTime = Round(ftime, -1)

  Print
  Print "Result with " & NumberOfPrisoners & " prisoners, " & Selections & " selections and " & Tries & " tries. "
  Print
  Print "Optimal: " & AllFoundOptimal & " of " & Tries & ": " & Str(AllFoundOptimal / Tries * 100) & " %"
  Print "Random: " & AllFoundRandom & " of " & Tries & ": " & Str(AllFoundRandom / Tries * 100) & " %"
  Print "RandomMem: " & AllFoundRandomMem & " of " & Tries & ": " & Str(AllFoundRandomMem / Tries * 100) & " %"
  Print
  Print "Elapsed Time: " & fTime & " sec"
  Print
  Print "Trials/sec: " & Round(Tries / fTime, -1)

End

Function HundredPrisoners_Optimal(NrPrisoners As Long, NrSelections As Long) As Long

  DrawerArray = New Long[NrPrisoners]
  Dim Counter As Long

  For Counter = 0 To DrawerArray.Max
    DrawerArray[Counter] = Counter + 1
  Next

  DrawerArray.Shuffle()

  Dim i As Long
  Dim j As Long
  FoundOwnNumber = 0

  For i = 1 To NrPrisoners
    For j = 1 To NrSelections
      If j = 1 Then NumberFromDrawer = DrawerArray[i - 1]

      If NumberFromDrawer = i Then
        FoundOwnNumber += 1
        Break
      Endif
      NumberFromDrawer = DrawerArray[NumberFromDrawer - 1]
    Next
  Next
  Return FoundOwnNumber

End

Function HundredPrisoners_Random(NrPrisoners As Long, NrSelections As Long) As Long

  Dim RandomDrawer As Long
  Dim Counter As Long

  DrawerArray = New Long[NrPrisoners]

  For Counter = 0 To DrawerArray.Max
    DrawerArray[Counter] = Counter + 1
  Next

  DrawerArray.Shuffle()

  Dim i As Long
  Dim j As Long
  FoundOwnNumber = 0

  Randomize

  For i = 1 To NrPrisoners
    For j = 1 To NrSelections
      RandomDrawer = CLong(Rand(NrPrisoners - 1))
      NumberFromDrawer = DrawerArray[RandomDrawer]
      If NumberFromDrawer = i Then
        FoundOwnNumber += 1
        Break
      Endif
    Next
  Next
  Return FoundOwnNumber

End

Function HundredPrisoners_Random_Mem(NrPrisoners As Long, NrSelections As Long) As Long

  Dim SelectionArray As New Long[NrPrisoners]
  Dim Counter As Long

  DrawerArray = New Long[NrPrisoners]

  For Counter = 0 To DrawerArray.Max
    DrawerArray[Counter] = Counter + 1

  Next

  For Counter = 0 To SelectionArray.Max
    SelectionArray[Counter] = Counter + 1

  Next

  DrawerArray.Shuffle()

  Dim i As Long
  Dim j As Long
  FoundOwnNumber = 0

  For i = 1 To NrPrisoners
    SelectionArray.Shuffle()
    For j = 1 To NrSelections
      NumberFromDrawer = DrawerArray[SelectionArray[j - 1] - 1]
      If NumberFromDrawer = i Then
        FoundOwnNumber += 1
        Break
      Endif
      NumberFromDrawer = DrawerArray[NumberFromDrawer - 1]
    Next
  Next
  Return FoundOwnNumber

End


package main

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

// Uses 0-based numbering rather than 1-based numbering throughout.
func doTrials(trials, np int, strategy string) {
    pardoned := 0
trial:
    for t := 0; t < trials; t++ {
        var drawers [100]int
        for i := 0; i < 100; i++ {
            drawers[i] = i
        }
        rand.Shuffle(100, func(i, j int) {
            drawers[i], drawers[j] = drawers[j], drawers[i]
        })
    prisoner:
        for p := 0; p < np; p++ {
            if strategy == "optimal" {
                prev := p
                for d := 0; d < 50; d++ {
                    this := drawers[prev]
                    if this == p {
                        continue prisoner
                    }
                    prev = this
                }
            } else {
                // Assumes a prisoner remembers previous drawers (s)he opened
                // and chooses at random from the others.
                var opened [100]bool
                for d := 0; d < 50; d++ {
                    var n int
                    for {
                        n = rand.Intn(100)
                        if !opened[n] {
                            opened[n] = true
                            break
                        }
                    }
                    if drawers[n] == p {
                        continue prisoner
                    }
                }
            }
            continue trial
        }
        pardoned++
    }
    rf := float64(pardoned) / float64(trials) * 100
    fmt.Printf("  strategy = %-7s  pardoned = %-6d relative frequency = %5.2f%%\n\n", strategy, pardoned, rf)
}

func main() {
    rand.Seed(time.Now().UnixNano())
    const trials = 100000
    for _, np := range []int{10, 100} {
        fmt.Printf("Results from %d trials with %d prisoners:\n\n", trials, np)
        for _, strategy := range [2]string{"random", "optimal"} {
            doTrials(trials, np, strategy)
        }
    }
}


import java.util.function.Function
import java.util.stream.Collectors
import java.util.stream.IntStream

class Prisoners {
    private static boolean playOptimal(int n) {
        List<Integer> secretList = IntStream.range(0, n).boxed().collect(Collectors.toList())
        Collections.shuffle(secretList)

        prisoner:
        for (int i = 0; i < secretList.size(); ++i) {
            int prev = i
            for (int j = 0; j < secretList.size() / 2; ++j) {
                if (secretList.get(prev) == i) {
                    continue prisoner
                }
                prev = secretList.get(prev)
            }
            return false
        }
        return true
    }

    private static boolean playRandom(int n) {
        List<Integer> secretList = IntStream.range(0, n).boxed().collect(Collectors.toList())
        Collections.shuffle(secretList)

        prisoner:
        for (Integer i : secretList) {
            List<Integer> trialList = IntStream.range(0, n).boxed().collect(Collectors.toList())
            Collections.shuffle(trialList)

            for (int j = 0; j < trialList.size() / 2; ++j) {
                if (Objects.equals(trialList.get(j), i)) {
                    continue prisoner
                }
            }

            return false
        }
        return true
    }

    private static double exec(int n, int p, Function<Integer, Boolean> play) {
        int succ = 0
        for (int i = 0; i < n; ++i) {
            if (play.apply(p)) {
                succ++
            }
        }
        return (succ * 100.0) / n
    }

    static void main(String[] args) {
        final int n = 100_000
        final int p = 100
        System.out.printf("# of executions: %d\n", n)
        System.out.printf("Optimal play success rate: %f%%\n", exec(n, p, Prisoners.&playOptimal))
        System.out.printf("Random play success rate: %f%%\n", exec(n, p, Prisoners.&playRandom))
    }
}


import System.Random
import Control.Monad.State

numRuns        = 10000
numPrisoners   = 100
numDrawerTries = 50
type Drawers   = [Int]
type Prisoner  =  Int
type Prisoners = [Int]

main = do
  gen <- getStdGen
  putStrLn $ "Chance of winning when choosing randomly: "  ++ (show $ evalState runRandomly gen)
  putStrLn $ "Chance of winning when choosing optimally: " ++ (show $ evalState runOptimally gen)

runRandomly :: State StdGen Double
runRandomly =
  let runResults = replicateM numRuns $ do
         drawers <- state $ shuffle [1..numPrisoners]
         allM (\prisoner -> openDrawersRandomly drawers prisoner numDrawerTries) [1..numPrisoners]
   in  ((/ fromIntegral numRuns) . fromIntegral . sum . map fromEnum) `liftM` runResults

openDrawersRandomly :: Drawers -> Prisoner -> Int -> State StdGen Bool
openDrawersRandomly drawers prisoner triesLeft = go triesLeft []
  where go 0 _ = return False
        go triesLeft seenDrawers = do
          try <- state $ randomR (1, numPrisoners)
          case try of
            x | x == prisoner        -> return True
              | x `elem` seenDrawers -> go triesLeft seenDrawers
              | otherwise            -> go (triesLeft - 1) (x:seenDrawers)

runOptimally :: State StdGen Double
runOptimally =
  let runResults = replicateM numRuns $ do
         drawers <- state $ shuffle [1..numPrisoners]
         return $ all (\prisoner -> openDrawersOptimally drawers prisoner numDrawerTries) [1..numPrisoners]
   in  ((/ fromIntegral numRuns) . fromIntegral . sum . map fromEnum) `liftM` runResults

openDrawersOptimally :: Drawers -> Prisoner -> Int -> Bool
openDrawersOptimally drawers prisoner triesLeft = go triesLeft prisoner
  where go 0 _ = False
        go triesLeft drawerToTry =
          let thisDrawer = drawers !! (drawerToTry - 1)
           in if thisDrawer == prisoner then True else go (triesLeft - 1) thisDrawer

-- Haskel stdlib is lacking big time, so here some necessary 'library' functions

-- make a list of 'len' random values in range 'range' from 'gen'
randomLR :: Integral a => Random b => a -> (b, b) -> StdGen -> ([b], StdGen)
randomLR 0 range gen = ([], gen)
randomLR len range gen =
  let (x, newGen) = randomR range gen
      (xs, lastGen) = randomLR (len - 1) range newGen
  in (x : xs, lastGen)

-- shuffle a list by a generator
shuffle :: [a] -> StdGen -> ([a], StdGen)
shuffle list gen = (shuffleByNumbers numbers list, finalGen)
  where
    n = length list
    (numbers, finalGen) = randomLR n (0, n-1) gen
    shuffleByNumbers :: [Int] -> [a] -> [a]
    shuffleByNumbers [] _ = []
    shuffleByNumbers _ [] = []
    shuffleByNumbers (i:is) xs = let (start, x:rest) = splitAt (i `mod` length xs) xs
                                 in x : shuffleByNumbers is (start ++ rest)

-- short-circuit monadic all
allM :: Monad m => (a -> m Bool) -> [a] -> m Bool
allM func [] = return True
allM func (x:xs) = func x >>= \res -> if res then allM func xs else return False


NB. game is solvable by optimal strategy when the length (#) of the
NB. longest (>./) cycle (C.) is at most 50.
opt=: 50 >: [: >./ [: > [: #&.> C.

NB. for each prisoner randomly open 50 boxes ((50?100){y) and see if
NB. the right card is there (p&e.). if not return 0.
rand=: monad define
for_p. i.100 do. if. -.p e.(50?100){y do. 0 return. end.
end. 1
)

NB. use both strategies on the same shuffles y times.
simulate=: monad define
'o r'=. y %~ 100 * +/ ((rand,opt)@?~)"0 y # 100
('strategy';'win rate'),('random';(":o),'%'),:'optimal';(":r),'%'
)


(math/seedrandom (os/cryptorand 8))

(defn drawers
  "create list and shuffle it"
  [prisoners]
  (var x (seq [i :range [0 prisoners]] i))
  (loop [i :down [(- prisoners 1) 0]]
    (var j (math/floor (* (math/random) (+ i 1))))
    (var k (get x i))
    (put x i (get x j))
    (put x j k))
  x)

(defn optimal-play
  "optimal decision path"
  [prisoners drawers]
  (var result 0)
  (loop [i :range [0 prisoners]]
    (var choice i)
    (loop [j :range [0 50] :until (= (get drawers choice) i)]
      (set choice (get drawers choice)))
    (cond
      (= (get drawers choice) i) (++ result)
      (break)))
  result)

(defn random-play
  "random decision path"
  [prisoners d]
  (var result 0)
  (var options (drawers prisoners))
  (loop [i :range [0 prisoners]]
    (var choice 0)
    (loop [j :range [0 (/ prisoners 2)] :until (= (get d j) (get options i))]
      (set choice j))
    (cond
      (= (get d choice) (get options i)) (++ result)
      (break)))
  result)

(defn main [& args]
  (def prisoners 100)
  (var optimal-success 0)
  (var random-success 0)
  (var sims 10000)
  (for i 0 sims
    (var d (drawers prisoners))
    (if (= (optimal-play prisoners d) prisoners)
      (++ optimal-success))
    (if (= (random-play prisoners d) prisoners)
      (++ random-success)))
  (printf "Simulation count:  %d" sims)
  (printf "Optimal play wins: %.1f%%" (* (/ optimal-success sims) 100))
  (printf "Random play wins:  %.1f%%" (* (/ random-success sims) 100)))


import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {
    private static boolean playOptimal(int n) {
        List<Integer> secretList = IntStream.range(0, n).boxed().collect(Collectors.toList());
        Collections.shuffle(secretList);

        prisoner:
        for (int i = 0; i < secretList.size(); ++i) {
            int prev = i;
            for (int j = 0; j < secretList.size() / 2; ++j) {
                if (secretList.get(prev) == i) {
                    continue prisoner;
                }
                prev = secretList.get(prev);
            }
            return false;
        }
        return true;
    }

    private static boolean playRandom(int n) {
        List<Integer> secretList = IntStream.range(0, n).boxed().collect(Collectors.toList());
        Collections.shuffle(secretList);

        prisoner:
        for (Integer i : secretList) {
            List<Integer> trialList = IntStream.range(0, n).boxed().collect(Collectors.toList());
            Collections.shuffle(trialList);

            for (int j = 0; j < trialList.size() / 2; ++j) {
                if (Objects.equals(trialList.get(j), i)) {
                    continue prisoner;
                }
            }

            return false;
        }
        return true;
    }

    private static double exec(int n, int p, Function<Integer, Boolean> play) {
        int succ = 0;
        for (int i = 0; i < n; ++i) {
            if (play.apply(p)) {
                succ++;
            }
        }
        return (succ * 100.0) / n;
    }

    public static void main(String[] args) {
        final int n = 100_000;
        final int p = 100;
        System.out.printf("# of executions: %d\n", n);
        System.out.printf("Optimal play success rate: %f%%\n", exec(n, p, Main::playOptimal));
        System.out.printf("Random play success rate: %f%%\n", exec(n, p, Main::playRandom));
    }
}


const _ = require('lodash');

const numPlays = 100000;

const setupSecrets = () => {
	// setup the drawers with random cards
	let secrets = [];

	for (let i = 0; i < 100; i++) {
		secrets.push(i);
	}

	return _.shuffle(secrets);
}

const playOptimal = () => {

	let secrets = setupSecrets();

	// Iterate once per prisoner
	loop1:
	for (let p = 0; p < 100; p++) {

		// whether the prisoner succeedss
		let success = false;

		// the drawer number the prisoner chose
		let choice = p;

		// The prisoner can choose up to 50 cards
		loop2:
		for (let i = 0; i < 50; i++) {

			// if the card in the drawer that the prisoner chose is his card
			if (secrets[choice] === p){
				success = true;
				break loop2;
			}

			// the next drawer the prisoner chooses will be the number of the card he has.
			choice = secrets[choice];

		}	// each prisoner gets 50 chances

		if (!success) return false;

	} // iterate for each prisoner

	return true;
}

const playRandom = () => {

	let secrets = setupSecrets();

	// iterate for each prisoner
	for (let p = 0; p < 100; p++) {

		let choices = setupSecrets();

		let success = false;

		for (let i = 0; i < 50; i++) {

			if (choices[i] === p) {
				success = true;
				break;
			}
		}

		if (!success) return false;
	}

	return true;
}

const execOptimal = () => {

	let success = 0;

	for (let i = 0; i < numPlays; i++) {

		if (playOptimal()) success++;

	}

	return 100.0 * success / 100000;
}

const execRandom = () => {

	let success = 0;

	for (let i = 0; i < numPlays; i++) {

		if (playRandom()) success++;

	}

	return 100.0 * success / 100000;
}

console.log("# of executions: " + numPlays);
console.log("Optimal Play Success Rate: " + execOptimal());
console.log("Random Play Success Rate: " + execRandom());


export LC_ALL=C
< /dev/urandom tr -cd '0-9' | fold -w 1 | jq -MRcnr -f 100-prisoners.jq


using Random, Formatting

function randomplay(n, numprisoners=100)
    pardoned, indrawer, found = 0, collect(1:numprisoners), false
    for i in 1:n
        shuffle!(indrawer)
        for prisoner in 1:numprisoners
            found = false
            for reveal in randperm(numprisoners)[1:div(numprisoners, 2)]
                indrawer[reveal] == prisoner && (found = true) && break
            end
            !found && break
        end
        found && (pardoned += 1)
    end
    return 100.0 * pardoned / n
end

function optimalplay(n, numprisoners=100)
    pardoned, indrawer, found = 0, collect(1:numprisoners), false
    for i in 1:n
        shuffle!(indrawer)
        for prisoner in 1:numprisoners
            reveal = prisoner
            found = false
            for j in 1:div(numprisoners, 2)
                card = indrawer[reveal]
                card == prisoner && (found = true) && break
                reveal = card
            end
            !found && break
        end
        found && (pardoned += 1)
    end
    return 100.0 * pardoned / n
 end

const N = 100_000
println("Simulation count: $N")
println("Random play wins: ", format(randomplay(N), precision=8), "% of simulations.")
println("Optimal play wins: ", format(optimalplay(N), precision=8), "% of simulations.")


val playOptimal: () -> Boolean = {
    val secrets = (0..99).toMutableList()
    var ret = true
    secrets.shuffle()
    prisoner@ for(i in 0 until 100){
        var prev = i
        draw@ for(j in 0 until  50){
            if (secrets[prev] == i) continue@prisoner
            prev = secrets[prev]
        }
        ret = false
        break@prisoner
    }
    ret
}

val playRandom: ()->Boolean = {
    var ret = true
    val secrets = (0..99).toMutableList()
    secrets.shuffle()
    prisoner@ for(i in 0 until 100){
        val opened = mutableListOf<Int>()
        val genNum : () ->Int = {
            var r = (0..99).random()
            while (opened.contains(r)) {
                r = (0..99).random()
            }
            r
        }
        for(j in 0 until 50){
            val draw = genNum()
            if ( secrets[draw] == i) continue@prisoner
            opened.add(draw)
        }
        ret = false
        break@prisoner
    }
    ret
}

fun exec(n:Int, play:()->Boolean):Double{
    var succ = 0
    for (i in IntRange(0, n-1)){
        succ += if(play()) 1 else 0
    }
    return (succ*100.0)/n
}

fun main() {
    val N = 100_000
    println("# of executions: $N")
    println("Optimal play success rate: ${exec(N, playOptimal)}%")
    println("Random play success rate: ${exec(N, playRandom)}%")
}


function shuffle(tbl)
  for i = #tbl, 2, -1 do
    local j = math.random(i)
    tbl[i], tbl[j] = tbl[j], tbl[i]
  end
  return tbl
end

function playOptimal()
    local secrets = {}
    for i=1,100 do
        secrets[i] = i
    end
    shuffle(secrets)

    for p=1,100 do
        local success = false

        local choice = p
        for i=1,50 do
            if secrets[choice] == p then
                success = true
                break
            end
            choice = secrets[choice]
        end

        if not success then
            return false
        end
    end

    return true
end

function playRandom()
    local secrets = {}
    for i=1,100 do
        secrets[i] = i
    end
    shuffle(secrets)

    for p=1,100 do
        local choices = {}
        for i=1,100 do
            choices[i] = i
        end
        shuffle(choices)

        local success = false
        for i=1,50 do
            if choices[i] == p then
                success = true
                break
            end
        end

        if not success then
            return false
        end
    end

    return true
end

function exec(n,play)
    local success = 0
    for i=1,n do
        if play() then
            success = success + 1
        end
    end
    return 100.0 * success / n
end

function main()
    local N = 1000000
    print("# of executions: "..N)
    print(string.format("Optimal play success rate: %f", exec(N, playOptimal)))
    print(string.format("Random play success rate: %f", exec(N, playRandom)))
end

main()


ClearAll[PlayRandom, PlayOptimal]
PlayRandom[n_] :=
 Module[{pardoned = 0, sampler, indrawer, found, reveal},
  sampler = indrawer = Range[100];
  Do[
   indrawer //= RandomSample;
   found = 0;
   Do[
    reveal = RandomSample[sampler, 50];
    If[MemberQ[indrawer[[reveal]], p],
     found++;
     ]
    ,
    {p, 100}
    ];
   If[found == 100, pardoned++];
   ,
   {n}
   ];
  N[pardoned/n]
  ]
PlayOptimal[n_] :=
 Module[{pardoned = 0, indrawer, reveal, found, card},
  indrawer = Range[100];
  Do[
   indrawer //= RandomSample;
   Do[
    reveal = p;
    found = False;
    Do[
     card = indrawer[[reveal]];
     If[card == p,
      found = True;
      Break[];
      ];
     reveal = card;
     ,
     {g, 50}
     ];
    If[! found, Break[]];
    ,
    {p, 100}
    ];
   If[found, pardoned++];
   ,
   {n}
   ];
  N[pardoned/n]
  ];
PlayRandom[1000]
PlayOptimal[10000]


function [randSuccess,idealSuccess]=prisoners(numP,numG,numT)
    %numP is the number of prisoners
    %numG is the number of guesses
    %numT is the number of trials
    randSuccess=0;

    %Random
    for trial=1:numT
        drawers=randperm(numP);
        won=1;
        for i=1:numP
            correct=0;
            notopened=drawers;
            for j=1:numG
                ind=randi(numel(notopened));
                m=notopened(ind);
                if m==i
                    correct=1;
                    break;
                end
                notopened(ind)=[];
            end
            if correct==0
                won=0;
                break;
            end
        end
        randSuccess=randSuccess*(trial-1)/trial+won/trial;
    end

    %Ideal
    idealSuccess=0;

    for trial=1:numT
        drawers=randperm(numP);
        won=1;
        for i=1:numP
            correct=0;
            guess=i;
            for j=1:numG
                m=drawers(guess);
                if m==i
                    correct=1;
                    break;
                end
                guess=m;
            end
            if correct==0
                won=0;
                break;
            end
        end
        idealSuccess=idealSuccess*(trial-1)/trial+won/trial;
    end
    disp(['Probability of success with random strategy: ' num2str(randSuccess*100) '%']);
    disp(['Probability of success with ideal strategy: ' num2str(idealSuccess*100) '%']);
end


playRandom = function(n)
    // using 0-99 instead of 1-100
    pardoned = 0
    numInDrawer = range(99)
    choiceOrder = range(99)
    for round in range(1, n)
    	numInDrawer.shuffle
        choiceOrder.shuffle
        for prisoner in range(99)
            found = false
            for card in choiceOrder[:50]
                if card == prisoner then
                    found = true
                    break
                end if
            end for
            if not found then break
        end for
        if found then pardoned = pardoned + 1
    end for
    return pardoned / n * 100
end function

playOptimal = function(n)
    // using 0-99 instead of 1-100
    pardoned = 0
    numInDrawer = range(99)
    for round in range(1, n)
    	numInDrawer.shuffle
        for prisoner in range(99)
            found = false
	    drawer = prisoner
            for i in range(1,50)
                card = numInDrawer[drawer]
                if card == prisoner then
                    found = true
                    break
                end if
                drawer = card
            end for
            if not found then break
        end for
        if found then pardoned = pardoned + 1
    end for
    return pardoned / n * 100
end function

print "Random:  " + playRandom(10000) + "%"
print "Optimal: " + playOptimal(10000) + "%"


import random, sequtils, strutils

type
  Sample = tuple
    succ: int
    fail: int

const
  numPrisoners = 100
  numDrawsEachPrisoner = numPrisoners div 2
  numDrawings: Positive = 1_000_000 div 1

proc `$`(s: Sample): string =
  "Succs: $#\tFails: $#\tTotal: $#\tSuccess Rate: $#%." % [$s.succ, $s.fail, $(s.succ + s.fail), $(s.succ.float / (s.succ + s.fail).float * 100.0)]

proc prisonersWillBeReleasedSmart(): bool =
  result = true
  var drawers = toSeq(0..<numPrisoners)
  drawers.shuffle
  for prisoner in 0..<numPrisoners:
    var drawer = prisoner
    block inner:
      for _ in 0..<numDrawsEachPrisoner:
        if drawers[drawer] == prisoner: break inner
        drawer = drawers[drawer]
      return false

proc prisonersWillBeReleasedRandom(): bool =
  result = true
  var drawers = toSeq(0..<numPrisoners)
  drawers.shuffle
  for prisoner in 0..<numPrisoners:
    var selectDrawer = toSeq(0..<numPrisoners)
    selectDrawer.shuffle
    block inner:
      for i in 0..<numDrawsEachPrisoner:
        if drawers[selectDrawer[i]] == prisoner: break inner
      return false

proc massDrawings(prisonersWillBeReleased: proc(): bool): Sample =
  var success = 0
  for i in 1..numDrawings:
    if prisonersWillBeReleased():
      inc(success)
  return (success, numDrawings - success)

randomize()
echo $massDrawings(prisonersWillBeReleasedSmart)
echo $massDrawings(prisonersWillBeReleasedRandom)


program Prisoners100;

const
  rounds  = 100000;

type
  tValue = Uint32;
  tPrisNum = array of tValue;
var
  drawers,
  PrisonersChoice : tPrisNum;

procedure shuffle(var N:tPrisNum);
var
  i,j,lmt : nativeInt;
  tmp: tValue;
Begin
  lmt := High(N);
  For i := lmt downto 1 do
  begin
    //take on from index i..limit
    j := random(i+1);
    //exchange with i
    tmp := N[i];N[i]:= N[j];N[j]:= tmp;
  end;
end;

function PardonedRandom(maxTestNum: NativeInt):boolean;
var
  PrisNum,TestNum,Lmt : NativeUint;
  Pardoned : boolean;
Begin
  IF maxTestNum <=0 then
  Begin
    PardonedRandom := false;
    EXIT;
  end;
  Lmt := High(drawers);
  IF (maxTestNum >= Lmt) then
  Begin
    PardonedRandom := true;
    EXIT;
  end;

  shuffle(drawers);
  PrisNum := 0;
  repeat
    //every prisoner uses his own list of drawers
    shuffle(PrisonersChoice);
    TestNum := 0;
    repeat
      Pardoned := drawers[PrisonersChoice[TestNum]] = PrisNum;
      inc(TestNum);
    until Pardoned OR (TestNum>=maxTestNum);
    IF Not(Pardoned) then
      BREAK;
    inc(PrisNum);
  until PrisNum>=Lmt;
  PardonedRandom:= Pardoned;
end;

function PardonedOptimized(maxTestNum: NativeUint):boolean;
var
  PrisNum,TestNum,NextNum,Cnt,Lmt : NativeUint;
  Pardoned : boolean;
Begin
  IF maxTestNum <=0 then
  Begin
    PardonedOptimized := false;
    EXIT;
  end;
  Lmt := High(drawers);
  IF (maxTestNum >= Lmt) then
  Begin
    PardonedOptimized := true;
    EXIT;
  end;

  shuffle(drawers);
  Lmt := High(drawers);
  IF maxTestNum >= Lmt then
  Begin
    PardonedOptimized := true;
    EXIT;
  end;
  PrisNum := 0;
  repeat
    Cnt := 0;
    NextNum := PrisNum;
    repeat
      TestNum := NextNum;
      NextNum := drawers[TestNum];
      inc(cnt);
      Pardoned := NextNum = PrisNum;
    until Pardoned OR (cnt >=maxTestNum);

    IF Not(Pardoned) then
      BREAK;
    inc(PrisNum);
  until PrisNum>Lmt;
  PardonedOptimized := Pardoned;
end;

procedure CheckRandom(testCount : NativeUint);
var
  i,cnt : NativeInt;
Begin
  cnt := 0;
  For i := 1 to rounds do
    IF PardonedRandom(TestCount) then
      inc(cnt);
  writeln('Randomly  ',cnt/rounds*100:7:2,'% get pardoned out of ',rounds,' checking max ',TestCount);
end;

procedure CheckOptimized(testCount : NativeUint);
var
  i,cnt : NativeInt;
Begin
  cnt := 0;
  For i := 1 to rounds do
    IF PardonedOptimized(TestCount) then
      inc(cnt);
  writeln('Optimized ',cnt/rounds*100:7:2,'% get pardoned out of ',rounds,' checking max ',TestCount);
end;

procedure OneCompareRun(PrisCnt:NativeInt);
var
  i,lmt :nativeInt;
begin
  setlength(drawers,PrisCnt);
  For i := 0 to PrisCnt-1 do
    drawers[i] := i;
  PrisonersChoice := copy(drawers);

  //test
  writeln('Checking ',PrisCnt,' prisoners');

  lmt := PrisCnt;
  repeat
    CheckOptimized(lmt);
    dec(lmt,PrisCnt DIV 10);
  until lmt < 0;
  writeln;

  lmt := PrisCnt;
  repeat
    CheckRandom(lmt);
    dec(lmt,PrisCnt DIV 10);
  until lmt < 0;
  writeln;
  writeln;
end;

Begin
  //init
  randomize;
  OneCompareRun(20);
  OneCompareRun(100);
end.


use strict;
use warnings;
use feature 'say';
use List::Util 'shuffle';

sub simulation {
    my($population,$trials,$strategy) = @_;
    my $optimal   = $strategy =~ /^o/i ? 1 : 0;
    my @prisoners = 0..$population-1;
    my $half      = int $population / 2;
    my $pardoned  = 0;

    for (1..$trials) {
        my @drawers = shuffle @prisoners;
        my $total = 0;
        for my $prisoner (@prisoners) {
            my $found = 0;
            if ($optimal) {
                my $card = $drawers[$prisoner];
                if ($card == $prisoner) {
                    $found = 1;
                } else {
                    for (1..$half-1) {
                        $card = $drawers[$card];
                        ($found = 1, last) if $card == $prisoner
                    }
                }
            } else {
                for my $card ( (shuffle @drawers)[0..$half]) {
                    ($found = 1, last) if $card == $prisoner
                }
            }
            last unless $found;
            $total++;
        }
        $pardoned++ if $total == $population;
    }
    $pardoned / $trials * 100
}

my $population = 100;
my $trials     = 10000;
say " Simulation count: $trials\n" .
(sprintf " Random strategy pardons: %6.3f%% of simulations\n", simulation $population, $trials, 'random' ) .
(sprintf "Optimal strategy pardons: %6.3f%% of simulations\n", simulation $population, $trials, 'optimal');

$population = 10;
$trials     = 100000;
say " Simulation count: $trials\n" .
(sprintf " Random strategy pardons: %6.3f%% of simulations\n", simulation $population, $trials, 'random' ) .
(sprintf "Optimal strategy pardons: %6.3f%% of simulations\n", simulation $population, $trials, 'optimal');


(de shuffle (Lst)
   (by '(NIL (rand)) sort Lst) )

# Extend this class with a `next-guess>` method and a `str>` method.
(class +Strategy +Entity)
(dm prev-drawer> (Num)
   (=: prev Num) )

(class +Random +Strategy)
(dm T (Prisoner)
   (=: guesses (nth (shuffle (range 1 100)) 51)) )
(dm next-guess> ()
   (pop (:: guesses)) )
(dm str> ()
   "Random" )

(class +Optimal +Strategy)
(dm T (Prisoner)
   (=: prisoner-id Prisoner) )
(dm next-guess> ()
   (or (: prev) (: prisoner-id)) )
(dm str> ()
   "Optimal/Wikipedia" )

(de test-strategy (Strategy)
   "Simulate one round of 100 prisoners who use `Strategy`"
   (let Drawers (shuffle (range 1 100))
      (for Prisoner (range 1 100)
         (NIL # Break and return NIL if any prisoner fails their test.
            (let Strat (new (list Strategy) Prisoner)
               (do 50 # Try 50 iterations of `Strat`. Break and return T iff success.
                  (T (= Prisoner (prev-drawer> Strat (get Drawers (next-guess> Strat))))
                     T ) ) ) )
         T ) ) )

(de test-strategy-n-times (Strategy N)
   "Simulate `N` rounds of 100 prisoners who use `Strategy`"
   (let Successes 0
      (do N
         (when (test-strategy Strategy)
            (inc 'Successes) ) )
      (prinl "We have a " (/ (* 100 Successes) N) "% success rate with " N " trials.")
      (prinl "This is using the " (str> Strategy) " strategy.") ) )


function play(integer prisoners, iterations, bool optimal)
    sequence drawers = shuffle(tagset(prisoners))
    integer pardoned = 0
    bool found = false
    for i=1 to iterations do
        drawers = shuffle(drawers)
        for prisoner=1 to prisoners do
            found = false
            integer drawer = iff(optimal?prisoner:rand(prisoners))
            for j=1 to prisoners/2 do
                drawer = drawers[drawer]
                if drawer==prisoner then found = true exit end if
                if not optimal then drawer = rand(prisoners) end if
            end for
            if not found then exit end if
        end for
        pardoned += found
    end for
    return 100*pardoned/iterations
end function

constant iterations = 100_000
printf(1,"Simulation count: %d\n",iterations)
for prisoners=10 to 100 by 90 do
    atom random = play(prisoners,iterations,false),
         optimal = play(prisoners,iterations,true)
    printf(1,"Prisoners:%d, random:%g, optimal:%g\n",{prisoners,random,optimal})
end for


/# Rosetta Code problem: http://rosettacode.org/wiki/100_prisoners
by Galileo, 05/2022 #/

include ..\Utilitys.pmt

def random rand * 1 + int enddef

def shuffle
    len var l
    l for var a
        l random var b
        b get var p
        a get b set
        p a set
    endfor
enddef

def play var optimal var iterations var prisoners
    0 var pardoned

    ( prisoners for endfor )

    iterations for drop
        shuffle
        prisoners for var prisoner
            false var found
            optimal if prisoner else prisoners random endif
            prisoners 2 / int for drop
                get dup prisoner == if true var found exitfor
                else
                    optimal not if drop prisoners random endif
                endif
            endfor
            found not if exitfor endif
            drop
        endfor
        pardoned found + var pardoned
    endfor
    drop
    pardoned 100 * iterations /
enddef

"Please, be patient ..." ?

( "Optimal: " 100 10000 true play
  " Random: " 100 10000 false play
  " Prisoners: " prisoners ) lprint


100H:
/* PARAMETERS */
DECLARE N$DRAWERS  LITERALLY '100';  /* AMOUNT OF DRAWERS */
DECLARE N$ATTEMPTS LITERALLY '50';   /* ATTEMPTS PER PRISONER */
DECLARE N$SIMS     LITERALLY '2000'; /* N. OF SIMULATIONS TO RUN */
DECLARE RAND$SEED  LITERALLY '193';  /* RANDOM SEED */

/* CP/M CALLS */
BDOS: PROCEDURE (FN, ARG); DECLARE FN BYTE, ARG ADDRESS; GO TO 5; END BDOS;
EXIT: PROCEDURE; CALL BDOS(0, 0); END EXIT;
PRINT: PROCEDURE (S); DECLARE S ADDRESS; CALL BDOS(9, S); END PRINT;

/* PRINT NUMBER */
PRINT$NUMBER: PROCEDURE (N);
    DECLARE S (6) BYTE INITIAL ('.....$');
    DECLARE (P, N) ADDRESS, C BASED P BYTE;
    P = .S(5);
DIGIT:
    P = P - 1;
    C = N MOD 10 + '0';
    N = N / 10;
    IF N > 0 THEN GO TO DIGIT;
    CALL PRINT(P);
END PRINT$NUMBER;

/* RANDOM NUMBER GENERATOR */
RAND$BYTE: PROCEDURE BYTE;
    DECLARE (X, A, B, C) BYTE
        INITIAL (RAND$SEED, RAND$SEED, RAND$SEED, RAND$SEED);
    X = X+1;
    A = A XOR C XOR X;
    B = B+A;
    C = C+SHR(B,1)+A;
    RETURN C;
END RAND$BYTE;

/* GENERATE RANDOM NUMBER FROM 0 TO MAX */
RAND$MAX: PROCEDURE (MAX) BYTE;
    DECLARE (X, R, MAX) BYTE;
    X = 1;
    DO WHILE X < MAX;
        X = SHL(X,1);
    END;
    X = X-1;
    DO WHILE 1;
        R = RAND$BYTE AND X;
        IF R < MAX THEN RETURN R;
    END;
END RAND$MAX;

/* PLACE CARDS RANDOMLY IN DRAWERS */
INIT$DRAWERS: PROCEDURE (DRAWERS);
    DECLARE DRAWERS ADDRESS, (D BASED DRAWERS, I, J, K) BYTE;
    DO I=0 TO N$DRAWERS-1;
        D(I) = I;
    END;
    DO I=0 TO N$DRAWERS-1;
        J = I + RAND$MAX(N$DRAWERS-I);
        K = D(I);
        D(I) = D(J);
        D(J) = K;
    END;
END INIT$DRAWERS;

/* PRISONER OPENS RANDOM DRAWERS */
RANDOM$STRATEGY: PROCEDURE (DRAWERS, P) BYTE;
    DECLARE DRAWERS ADDRESS, D BASED DRAWERS BYTE;
    DECLARE (P, I, TRIES) BYTE;

    /* KEEP TRACK OF WHICH DRAWERS HAVE BEEN OPENED */
    DECLARE OPEN (N$DRAWERS) BYTE;
    DO I=0 TO N$DRAWERS-1;
        OPEN(I) = 0;
    END;

    /* OPEN RANDOM DRAWERS */
    TRIES = N$ATTEMPTS;
    DO WHILE TRIES > 0;
        IF NOT OPEN(I := RAND$MAX(N$DRAWERS)) THEN DO;
            /* IF WE FIND OUR NUMBER, SUCCESS */
            IF D(I) = P THEN RETURN 1;
            OPEN(I) = 1;
            TRIES = TRIES - 1;
        END;
    END;

    RETURN 0; /* WE DID NOT FIND OUR NUMBER */
END RANDOM$STRATEGY;

/* PRISONER USES OPTIMAL STRATEGY */
OPTIMAL$STRATEGY: PROCEDURE (DRAWERS, P) BYTE;
    DECLARE DRAWERS ADDRESS, D BASED DRAWERS BYTE;
    DECLARE (P, I, TRIES) BYTE;
    TRIES = N$ATTEMPTS;
    I = P;
    DO WHILE TRIES > 0;
        I = D(I); /* OPEN DRAWER W/ CURRENT NUMBER */
        IF I = P THEN RETURN 1; /* DID WE FIND IT? */
        TRIES = TRIES - 1;
    END;
    RETURN 0;
END OPTIMAL$STRATEGY;

/* RUN A SIMULATION */
DECLARE RANDOM LITERALLY '0';
DECLARE OPTIMAL LITERALLY '1';
SIMULATE: PROCEDURE (STRAT) BYTE;
    DECLARE (STRAT, P, R) BYTE;

    /* PLACE CARDS IN DRAWERS */
    DECLARE DRAWERS (N$DRAWERS) BYTE;
    CALL INIT$DRAWERS(.DRAWERS);

    /* TRY EACH PRISONER */
    DO P=0 TO N$DRAWERS-1;
        DO CASE STRAT;
            R = RANDOM$STRATEGY(.DRAWERS, P);
            R = OPTIMAL$STRATEGY(.DRAWERS, P);
        END;

        /* IF ONE PRISONER FAILS THEY ALL HANG */
        IF NOT R THEN RETURN 0;
    END;

    RETURN 1; /* IF THEY ALL SUCCEED NONE HANG */
END SIMULATE;

/* RUN MANY SIMULATIONS AND COUNT THE SUCCESSES */
RUN$SIMULATIONS: PROCEDURE (N, STRAT) ADDRESS;
    DECLARE STRAT BYTE, (I, N, SUCC) ADDRESS;
    SUCC = 0;
    DO I=1 TO N;
        SUCC = SUCC + SIMULATE(STRAT);
    END;
    RETURN SUCC;
END RUN$SIMULATIONS;

/* RUN AND PRINT SIMULATIONS */
RUN$AND$PRINT: PROCEDURE (NAME, STRAT, N);
    DECLARE (NAME, N, S) ADDRESS, STRAT BYTE;
    CALL PRINT(NAME);
    CALL PRINT(.' STRATEGY: $');
    S = RUN$SIMULATIONS(N, STRAT);
    CALL PRINT$NUMBER(S);
    CALL PRINT(.' OUT OF $');
    CALL PRINT$NUMBER(N);
    CALL PRINT(.' - $');
    CALL PRINT$NUMBER( S*10 / (N/10) );
    CALL PRINT(.(37,13,10,'$'));
END RUN$AND$PRINT;

CALL RUN$AND$PRINT(.'RANDOM$', RANDOM, N$SIMS);
CALL RUN$AND$PRINT(.'OPTIMAL$', OPTIMAL, N$SIMS);
CALL EXIT;
EOF


optimalSeq(drawers, n) =
  iterate(ind => drawers[ind - 1], n)
  |> takeUntil(ind => drawers[ind - 1] == n)

optimalTrial(drawers) =
  range(1, 100)
  |> map(optimalSeq(drawers))

randomSeq(drawers, n) =
  iterate(ind => randRange(1, 100), randRange(1, 100))
  |> takeUntil(ind => drawers[ind - 1] == n)

randomTrial(drawers) =
  range(1, 100)
  |> map(randomSeq(drawers))

checkLength(seq) =
  length(take(51, seq)) <= 50

numTrials = 3000

runTrials(trialFunc) =
  for t in range(1, numTrials)
  yield
    range(1, 100)
    |> shuffle
    |> toArray
    |> trialFunc
    |> map(checkLength)
    |> all

countSuccess(trialFunc) =
  runTrials(trialFunc)
  |> filter(id)
  |> length

optimalCount = countSuccess(optimalTrial)
randomCount = countSuccess(randomTrial)

output =
  format("optimal: {} / {} = {} prob\nrandom: {} / {} = {} prob", [
    optimalCount, numTrials, optimalCount / numTrials,
    randomCount, numTrials, randomCount / numTrials,
  ])
  |> println


### Clear Screen from old Output
Clear-Host

Function RandomOpening ()
  {
    $Prisoners = 1..100 | Sort-Object {Get-Random}
    $Cupboard = 1..100 | Sort-Object {Get-Random}
    ## Loop for the Prisoners
    $Survived = $true
    for ($I=1;$I -le 100;$i++)
      {
          $OpeningListe = 1..100 | Sort-Object {Get-Random}
          $Gefunden = $false
          ## Loop for the trys of every prisoner
          for ($X=1;$X -le 50;$X++)
            {
                $OpenNumber = $OpeningListe[$X]
                IF ($Cupboard[$OpenNumber] -eq $Prisoners[$I])
                  {
                      $Gefunden = $true
                  }
                ## Cancel loop if prisoner found his number (yeah i know, dirty way ^^ )
                IF ($Gefunden)
                  {
                    $X = 55
                  }
            }
          IF ($Gefunden -eq $false)
            {
              $I = 120
              $Survived = $false
            }
      }
    Return $Survived
  }

  Function StrategyOpening ()
  {
    $Prisoners = 1..100 | Sort-Object {Get-Random}
    $Cupboard = 1..100 | Sort-Object {Get-Random}
    $Survived = $true
    for ($I=1;$I -le 100;$i++)
      {
          $Gefunden = $false
          $OpeningNumber = $Prisoners[$I-1]
          for ($X=1;$X -le 50;$X++)
            {
                IF ($Cupboard[$OpeningNumber-1] -eq $Prisoners[$I-1])
                  {
                      $Gefunden = $true
                  }
                else
                  {
                    $OpeningNumber = $Cupboard[$OpeningNumber-1]
                  }
                IF ($Gefunden)
                  {
                    $X = 55
                  }
            }
          IF ($Gefunden -eq $false)
            {
              $I = 120
              $Survived = $false
            }
      }
    Return $Survived
  }

$MaxRounds = 10000

Function TestRandom
  {
    $WinnerRandom = 0
    for ($Round = 1; $Round -le $MaxRounds;$Round++)
      {
        IF (($Round%1000) -eq 0)
          {
            $Time = Get-Date
            Write-Host "Currently we are at rount $Round at $Time"
          }
        $Rueckgabewert = RandomOpening
        IF ($Rueckgabewert)
          {
            $WinnerRandom++
          }
      }

    $Prozent = (100/$MaxRounds)*$WinnerRandom
    Write-Host "There are $WinnerRandom survivors whit random opening. This is $Prozent percent"
  }

Function TestStrategy
  {
    $WinnersStrategy = 0
      for ($Round = 1; $Round -le $MaxRounds;$Round++)
        {
          IF (($Round%1000) -eq 0)
            {
              $Time = Get-Date
              Write-Host "Currently we are at $Round at $Time"
            }
          $Rueckgabewert = StrategyOpening
          IF ($Rueckgabewert)
            {
              $WinnersStrategy++
            }
        }

    $Prozent = (100/$MaxRounds)*$WinnersStrategy
    Write-Host "There are $WinnersStrategy survivors whit strategic opening. This is $Prozent percent"
  }

Function Main ()
  {
    Clear-Host
    TestRandom
    TestStrategy
  }

Main


IntList drawers = new IntList();
int trials = 100000;
int succes_count;

void setup() {
  for (int i = 0; i < 100; i++) {
    drawers.append(i);
  }
  println(trials + " trials\n");

  //Random strategy
  println("Random strategy");
  succes_count = trials;
  for (int i = 0; i < trials; i++) {
    drawers.shuffle();
    for (int prisoner = 0; prisoner < 100; prisoner++) {
      boolean found = false;
      for (int attempt = 0; attempt < 50; attempt++) {
        if (drawers.get(int(random(drawers.size()))) == prisoner) {
          found = true;
          break;
        }
      }
      if (!found) {
        succes_count--;
        break;
      }
    }
  }
  println(" Succeses: " + succes_count);
  println(" Succes rate: " + 100.0 * succes_count / trials + "%\n");

  //Optimal strategy
  println("Optimal strategy");
  succes_count = trials;
  for (int i = 0; i < trials; i++) {
    drawers.shuffle();
    for (int prisoner = 0; prisoner < 100; prisoner++) {
      boolean found = false;
      int next = prisoner;
      for (int attempt = 0; attempt < 50; attempt++) {
        next = drawers.get(next);
        if (next == prisoner) {
          found = true;
          break;
        }
      }
      if (!found) {
        succes_count--;
        break;
      }
    }
  }
  println(" Succeses: " + succes_count);
  print(" Succes rate: " + 100.0 * succes_count / trials + "%");
}


#PRISONERS=100
#DRAWERS  =100
#LOOPS    = 50
#MAXPROBE = 10000
OpenConsole()

Dim p1(#PRISONERS,#DRAWERS)
Dim p2(#PRISONERS,#DRAWERS)
Dim d(#DRAWERS)

For i=1 To #DRAWERS : d(i)=i : Next
Start:
For probe=1 To #MAXPROBE
  RandomizeArray(d(),1,100)
  c1=0 : c2=0
  For m=1 To #PRISONERS
    p2(m,1)=d(m) : If d(m)=m : p2(m,0)=1 : EndIf
    For n=1 To #LOOPS
      p1(m,n)=d(Random(100,1))
      If p1(m,n)=m : p1(m,0)=1 : EndIf
      If n>1 : p2(m,n)=d(p2(m,n-1)) : If p2(m,n)=m : p2(m,0)=1 : EndIf : EndIf
    Next n
  Next m

  For m=1 To #PRISONERS
    If p1(m,0) : c1+1 : p1(m,0)=0 : EndIf
    If p2(m,0) : c2+1 : p2(m,0)=0 : EndIf
  Next m

  If c1=#PRISONERS : w1+1 : EndIf
  If c2=#PRISONERS : w2+1 : EndIf
Next probe
Print("TRIALS: "+Str(#MAXPROBE))
Print("  RANDOM= "+StrF(100*w1/#MAXPROBE,2)+"%   STATEGY= "+StrF(100*w2/#MAXPROBE,2)+"%")
PrintN(~"\tFIN =q.") : inp$=Input()
w1=0 : w2=0
If inp$<>"q" : Goto Start : EndIf


import random

def play_random(n):
    # using 0-99 instead of ranges 1-100
    pardoned = 0
    in_drawer = list(range(100))
    sampler = list(range(100))
    for _round in range(n):
        random.shuffle(in_drawer)
        found = False
        for prisoner in range(100):
            found = False
            for reveal in random.sample(sampler, 50):
                card = in_drawer[reveal]
                if card == prisoner:
                    found = True
                    break
            if not found:
                break
        if found:
            pardoned += 1
    return pardoned / n * 100   # %

def play_optimal(n):
    # using 0-99 instead of ranges 1-100
    pardoned = 0
    in_drawer = list(range(100))
    for _round in range(n):
        random.shuffle(in_drawer)
        for prisoner in range(100):
            reveal = prisoner
            found = False
            for go in range(50):
                card = in_drawer[reveal]
                if card == prisoner:
                    found = True
                    break
                reveal = card
            if not found:
                break
        if found:
            pardoned += 1
    return pardoned / n * 100   # %

if __name__ == '__main__':
    n = 100_000
    print(" Simulation count:", n)
    print(f" Random play wins: {play_random(n):4.1f}% of simulations")
    print(f"Optimal play wins: {play_optimal(n):4.1f}% of simulations")


t = 100000 #number of trials
success.r = rep(0,t) #this will keep track of how many prisoners find their ticket on each trial for the random method
success.o = rep(0,t) #this will keep track of how many prisoners find their ticket on each trial for the optimal method

#random method
for(i in 1:t){
  escape = rep(F,100)
  ticket = sample(1:100)
  for(j in 1:length(prisoner)){
    escape[j] = j %in% sample(ticket,50)
  }
  success.r[i] = sum(escape)
}

#optimal method
for(i in 1:t){
  escape = rep(F,100)
  ticket = sample(1:100)
  for(j in 1:100){
    boxes = 0
    current.box = j
    while(boxes<50 && !escape[j]){
      boxes=boxes+1
      escape[j] = ticket[current.box]==j
      current.box = ticket[current.box]
    }
  }
  success.o[i] = sum(escape)
}

cat("Random method resulted in a success rate of ",100*mean(success.r==100),
    "%.\nOptimal method resulted in a success rate of ",100*mean(success.o==100),"%.",sep="")


Const Found = -1, Searching = 0, Status = 1, Tries = 2
Const Attempt = 1, Victories = 2, RandomW = 1, ChainW = 2
Randomize Timer

Dim Shared Prisoners(1 To 100, Status To Tries) As Integer, Drawers(1 To 100) As Integer, Results(1 To 2, 1 To 2) As Integer
Print "100 prisoners"
Print "Random way to search..."
For a = 1 To 2000
    Init
    Results(RandomW, Attempt) = Results(RandomW, Attempt) + 1
    RandomWay
    If verify% Then Results(RandomW, Victories) = Results(RandomW, Victories) + 1
Next

Print: Print "Chain way to search..."
For a = 1 To 2000
    Init
    Results(ChainW, Attempt) = Results(ChainW, Attempt) + 1
    ChainWay
    If verify% Then Results(ChainW, Victories) = Results(ChainW, Victories) + 1
Next
Print: Print "Results: "
Print " Attempts "; Results(RandomW, Attempt); " "; "Victories "; Results(RandomW, Victories); " Ratio:"; Results(RandomW, Victories); "/"; Results(RandomW, Attempt)
Print
Print " Attempts "; Results(ChainW, Attempt); " "; "Victories "; Results(ChainW, Victories); " Ratio:"; Results(ChainW, Victories); "/"; Results(ChainW, Attempt)
End

Function verify%
    Dim In As Integer
    Print "veryfing "
    verify = 0
    For In = 1 To 100
        If Prisoners(In, Status) = Searching Then Exit For
    Next
    If In = 101 Then verify% = Found
End Function

Sub ChainWay
    Dim In As Integer, ChainChoice As Integer
    Print "Chain search"
    For In = 1 To 100
        ChainChoice = In
        Do
            Prisoners(In, Tries) = Prisoners(In, Tries) + 1
            If Drawers(ChainChoice) = In Then Prisoners(In, Status) = Found: Exit Do
            ChainChoice = Drawers(ChainChoice)
        Loop Until Prisoners(In, Tries) = 50
    Next In
End Sub

Sub RandomWay
    Dim In As Integer, RndChoice As Integer
    Print "Random search"
    For In = 1 To 100
        Do
            Prisoners(In, Tries) = Prisoners(In, Tries) + 1
            If Drawers(Int(Rnd * 100) + 1) = In Then Prisoners(In, Status) = Found: Exit Do
        Loop Until Prisoners(In, Tries) = 50
    Next
    Print "Executed "
End Sub

Sub Init
    Dim I As Integer, I2 As Integer
    Print "initialization"
    For I = 1 To 100
        Prisoners(I, Status) = Searching
        Prisoners(I, Tries) = Searching
        Do
            Drawers(I) = Int(Rnd * 100) + 1
            For I2 = 1 To I
                If Drawers(I2) = Drawers(I) Then Exit For
            Next
            If I2 = I Then Exit Do
        Loop
    Next I
    Print "Done "
End Sub