Powered by AppSignal & Oban Pro

DiceFSharp (Dice)

lib/fsharp/dice_fsharp.livemd

DiceFSharp (Dice)

#!import ../../../polyglot/lib/fsharp/Notebooks

///- --test

// The test helpers this notebook uses, from polyglot's Testing.livemd, which references Expecto by a path relative to an
// old notebook runner's install directory (unresolvable on the Kino route); here Expecto comes from NuGet.
#r "nuget: Expecto, 11.0.0-alpha8"

type AssertExceptionFormatter (ex) =
    member _.Text =
        ex.ToString()
            .Replace("\u001b[32m", "<span style=\"color: green;\">")
            .Replace("\u001b[39m", "</span>")
            .Replace("\u001b[31m", "<span style=\"color: red;\">")
            .Replace("\n", "<br/>\n")

Formatter.Register<AssertExceptionFormatter> ((fun (x : AssertExceptionFormatter) -> x.Text), "text/html")

let inline __expect fn log expected actual =
    if log then printfn $"{actual.ToDisplayString ()}"
    try
        "Testing.__expect" |> fn actual expected
    with :? Expecto.AssertException as ex ->
        AssertExceptionFormatter(ex).Display () |> ignore
        failwith (ex.GetType().FullName)

let inline __assertEqual log expected actual = __expect Expecto.Expect.equal log expected actual
let inline _assertEqual expected actual = __assertEqual true expected actual

let inline __throwsC log expected actual = __expect Expecto.Expect.throwsC log expected actual
let inline _throwsC expected actual = __throwsC true expected actual

#!import ../../../polyglot/lib/fsharp/Common.fs

#if !INTERACTIVE
open Polyglot
open Lib
#endif

open Common

sixthPowerSequence

let sixthPowerSequence () =
    1 |> Seq.unfold (fun state -> Some (state, state * 6)) |> Seq.cache
///- --test

sixthPowerSequence ()
|> Seq.take 8
|> Seq.toList
|> _assertEqual [ 1; 6; 36; 216; 1296; 7776; 46656; 279936 ]

accumulateDiceRolls

let rec accumulateDiceRolls log rolls power acc =
    match rolls with
    | _ when power < 0 ->
        log |> Option.iter ((|>) $"accumulateDiceRolls / power: {power} / acc: {acc}")
        Some (acc + 1, rolls)
    | [] -> None
    | roll :: rest when roll > 1 ->
        let coeff = sixthPowerSequence () |> Seq.item power
        let value = (roll - 1) * coeff
        log |> Option.iter ((|>) $"accumulateDiceRolls / \
            power: {power} / acc: {acc} / roll: {roll} / value: {value}"
        )
        accumulateDiceRolls log rest (power - 1) (acc + value)
    | roll :: rest ->
        log |> Option.iter ((|>) $"accumulateDiceRolls / power: {power} / acc: {acc} / roll: {roll}")
        accumulateDiceRolls log rest (power - 1) acc
///- --test

accumulateDiceRolls (Some (printfn "%s")) [ 6; 5; 4; 3; 2 ] 0 1000
|> _assertEqual (Some (1006, [ 5; 4; 3; 2 ]))
///- --test

accumulateDiceRolls (Some (printfn "%s")) [ 6; 5; 4; 3; 2 ] 1 1000
|> _assertEqual (Some (1035, [ 4; 3; 2 ]))
///- --test

accumulateDiceRolls (Some (printfn "%s")) [ 6; 5; 4; 3; 2 ] 2 1000
|> _assertEqual (Some (1208, [ 3; 2 ]))

rollWithinBounds

let rollWithinBounds log max rolls =
    let power = List.length rolls - 1
    match accumulateDiceRolls log rolls power 0 with
    | Some (result, _) when result >= 1 && result <= max -> Some result
    | _ -> None
///- --test

rollWithinBounds (Some (printfn "%s")) 2000 [ 1; 5; 4; 4; 5 ]
|> _assertEqual (Some 995)
///- --test

rollWithinBounds (Some (printfn "%s")) 2000 [ 2; 2; 6; 4; 5 ]
|> _assertEqual (Some 1715)
///- --test

rollWithinBounds (Some (printfn "%s")) 2000 [ 4; 1; 1; 2; 3 ]
|> _assertEqual None

calculateDiceCount

let inline calculateDiceCount log max =
    let rec 루프 n p =
        if p < max
        then 루프 (n + 1) (p * 6)
        else
            log |> Option.iter ((|>) $"calculateDiceCount / max: {max} / n: {n} / p: {p}")
            n
    if max = 1
    then 1
    else 루프 0 1
///- --test

calculateDiceCount (Some (printfn "%s")) 36
|> _assertEqual 2
///- --test

calculateDiceCount (Some (printfn "%s")) 7777
|> _assertEqual 6

rollDice

let private random = System.Random ()
let rollDice () =
    random.Next (1, 7)

rotateNumber

let rotateNumber max n =
    (n - 1 + max) % max + 1

rotateNumbers

let rotateNumbers max items =
    items |> Seq.map (rotateNumber max)
///- --test

[ -1 .. 14 ]
|> rotateNumbers 6
|> Seq.toList
|> _assertEqual [ 5; 6; 1; 2; 3; 4; 5; 6; 1; 2; 3; 4; 5; 6; 1; 2 ]

createSequentialRoller

let createSequentialRoller list =
    let mutable currentIndex = 0
    fun () ->
        match list |> List.tryItem currentIndex with
        | Some item ->
            currentIndex <- currentIndex + 1
            item
        | None ->
            failwith "createSequentialRoller / End of list"

rollProgressively

let rollProgressively log roll reroll max =
    let power = (calculateDiceCount log max) - 1
    let rec 루프 rolls size =
        if size < power + 1
        then 루프 (roll () :: rolls) (size + 1)
        else
            match accumulateDiceRolls log rolls power 0 with
            | Some (result, _) when result <= max -> result
            | _ when reroll -> 루프 (List.init power (fun _ -> roll ())) power
            | _ -> 루프 (roll () :: rolls) (size + 1)
    루프 [] 0
///- --test

rollProgressively None rollDice false 1
|> _assertEqual 1
///- --test

let sequentialRoll = createSequentialRoller [ 5; 4; 4; 5; 1 ]

rollProgressively (Some (printfn "%s")) sequentialRoll false 2000
|> _assertEqual 995
///- --test

let sequentialRoll = createSequentialRoller [ 5; 4; 4; 5; 2 ]

fun () -> rollProgressively (Some (printfn "%s")) sequentialRoll false 2000 |> ignore
|> _throwsC (fun ex _ ->
    SpiralSm.format_exception ex
    |> _assertEqual "System.Exception: createSequentialRoller / End of list"
)
///- --ignore

rollProgressively (Some (printfn "%s")) rollDice false 2000
///- --ignore

rollProgressively (Some (printfn "%s")) rollDice true 2000
///- --ignore

[| 1 .. 10000 |]
|> Array.Parallel.map (fun _ -> rollProgressively None rollDice false 10)
|> Array.Parallel.groupBy id
|> Array.Parallel.map (fun (k, v) -> k, v.Length)
|> Array.Parallel.sortBy fst
///- --ignore

[| 1 .. 10000 |]
|> Array.Parallel.map (fun _ -> rollProgressively None rollDice true 10)
|> Array.Parallel.groupBy id
|> Array.Parallel.map (fun (k, v) -> k, v.Length)
|> Array.Parallel.sortBy fst
///- --test

[| 1 .. 100 |]
|> Array.Parallel.iter (fun n ->
    [| 0 .. 1 |]
    |> Array.Parallel.iter (fun reroll ->
        [| 1 .. 3500 |]
        |> Array.Parallel.map (fun _ -> rollProgressively None rollDice (reroll = 1) n)
        |> Array.Parallel.groupBy id
        |> Array.length
        |> __assertEqual false n
    )
)
///- --ignore

let inline rollMax fn max n =
    [| 1 .. n |]
    |> Array.Parallel.map (fun _ -> fn max)
    |> Array.Parallel.groupBy id
    |> Array.Parallel.map (fun (_, v) -> v.Length)

let rec rollN max n even current =
    let roll = rollMax (rollProgressively None rollDice true) max n
    if roll |> Array.Parallel.forall ((=) even)
    then current
    else rollN max n even (current + 1)

let run () =
    let max = 10
    let n = 30
    let even = (n / max) |> int

    rollN max n even 0

// run ()
///- --ignore

let run () =
    let max = 10
    let n = 30
    let even = (n / max) |> int
    [| 1 .. 100 |]
    |> Array.Parallel.map (fun i ->
        let roll = rollN max n even 0
        printfn $"i: {i} / roll: {roll}"
        float roll
    )
    |> Array.Parallel.average

// run ()

FsCheck (test)

#r "nuget: FsCheck, 3.1.0"
#r "nuget: Expecto.FsCheck, 11.0.0-alpha8"
///- --test

type ValorDado =
    | Um
    | Dois
    | Tres
    | Quatro
    | Cinco
    | Seis

type Aspecto =
    | Passado of string
    | Presente of string
    | Futuro of string
    | Desafios of string
    | Recursos of string
    | ResultadoProjetado of string
    | InfluenciaExterna of string

type Contexto =
    | Amor of string
    | Trabalho of string
    | Saude of string
    | Dinheiro of string

type Universo =
    | Real of string
    | Virtual of string
    | Espiritual of string

type Caracteristica =
    | Aspecto of Aspecto
    | Contexto of Contexto
    | Universo of Universo
    | DadoRolado of ValorDado

type Interacao =
    | Conflito
    | Parceria
    | Crescimento
    | Estagnacao
    | Separacao
    | Harmonia
    | Desafio
    | Colaboracao
    | Progresso
    | Mudanca
    | Sucesso

type Interpretacao =
    | Interpretacao of Caracteristica * Interacao * Caracteristica

type SistemaDivinacao =
    | SistemaDivinacao of Interpretacao list * Caracteristica

let config = { Expecto.FsCheckConfig.defaultConfig with maxTest = 10000 }

let shuffleList xs seed =
    let rnd = Random (seed)
    xs
    |> List.map (fun x -> rnd.Next(), x)
    |> List.sortBy fst
    |> List.map snd




type Complexity = Simple | Moderate | Complex
type Duration = Short | Medium | Long

type Dice = D1 of int | D2 of int

type Task =
    | Task of Complexity * Duration * Task
    | NoTask

let durationOfFocus (d1: int) (d2: int) =
    match d1 + d2 with
    | sum when sum <= 4 -> Short
    | sum when sum <= 8 -> Medium
    | _ -> Long

let complexityOfTask (d1: int) (d2: int) =
    match d1 * d2 with
    | product when product <= 12 -> Simple
    | product when product <= 24 -> Moderate
    | _ -> Complex

let rec generateTaskList d1 d2 previousTask =
    match d1, d2 with
    | d1, d2 when d1 > 0 && d2 > 0 ->
        let complexity = complexityOfTask d1 d2
        let duration = durationOfFocus d1 d2
        let newTask = Task (complexity, duration, previousTask)
        generateTaskList (d1 - 1) (d2 - 1) newTask
    | _, _ -> previousTask





let properties =
    Expecto.Tests.testList "FsCheck samples" [
        let sistemaDivinacao (interpretacoes: Interpretacao list, caracteristica: Caracteristica) =
            let interpretacoes = interpretacoes |> List.sort
            SistemaDivinacao (interpretacoes, caracteristica)

        Expecto.ExpectoFsCheck.testPropertyWithConfig config "SistemaDivinacao is consistent" <|
            fun (interpretacoes: Interpretacao list, caracteristica: Caracteristica) ->
                sistemaDivinacao (interpretacoes, caracteristica)
                    = sistemaDivinacao (interpretacoes, caracteristica)

        Expecto.ExpectoFsCheck.testPropertyWithConfig config "SistemaDivinacao is variant under permutation" <|
            fun (input: Interpretacao list, caracteristica: Caracteristica) ->
                let seed = 42
                let shuffledInput = shuffleList input seed
                sistemaDivinacao (input, caracteristica) = sistemaDivinacao (shuffledInput, caracteristica)

        Expecto.ExpectoFsCheck.testPropertyWithConfig config "SistemaDivinacao can handle lists of any size" <|
            fun (input: Interpretacao list, caracteristica: Caracteristica) ->
                let sistema = sistemaDivinacao (input, caracteristica)
                sistema <> Unchecked.defaultof<_>

        Expecto.ExpectoFsCheck.testPropertyWithConfig config "SistemaDivinacao is invariant under data transformations" <|
            fun (input: Interpretacao list, caracteristica: Caracteristica, newInterpretation: Interpretacao) ->
                let containsNewInterpretation = input |> List.contains newInterpretation
                let modifiedInput =
                    if containsNewInterpretation
                    then input
                    else newInterpretation :: input
                if containsNewInterpretation
                then sistemaDivinacao (List.sort input, caracteristica)
                        = sistemaDivinacao (List.sort modifiedInput, caracteristica)
                else sistemaDivinacao (List.sort input, caracteristica)
                        <> sistemaDivinacao (List.sort modifiedInput, caracteristica)






        let focusDurationProperty =
            FsCheck.FSharp.Prop.forAll (FsCheck.FSharp.Arb.fromGen (FsCheck.FSharp.Gen.map2 (fun d1 d2 -> (d1, d2)) (FsCheck.FSharp.Gen.choose (1, 6)) (FsCheck.FSharp.Gen.choose (1, 6)))) <| fun (d1, d2) ->
                let expected =
                    match d1 + d2 with
                    | sum when sum <= 4 -> Short
                    | sum when sum <= 8 -> Medium
                    | _ -> Long
                let actual = durationOfFocus d1 d2
                expected = actual

        let taskComplexityProperty =
            FsCheck.FSharp.Prop.forAll (FsCheck.FSharp.Arb.fromGen (FsCheck.FSharp.Gen.map2 (fun d1 d2 -> (d1, d2)) (FsCheck.FSharp.Gen.choose (1, 6)) (FsCheck.FSharp.Gen.choose (1, 6)))) <| fun (d1, d2) ->
                let expected =
                    match d1 * d2 with
                    | product when product <= 12 -> Simple
                    | product when product <= 24 -> Moderate
                    | _ -> Complex
                let actual = complexityOfTask d1 d2
                expected = actual

        let taskListLengthProperty =
            FsCheck.FSharp.Prop.forAll (FsCheck.FSharp.Arb.fromGen (FsCheck.FSharp.Gen.map2 (fun d1 d2 -> (d1, d2)) (FsCheck.FSharp.Gen.choose (1, 6)) (FsCheck.FSharp.Gen.choose (1, 6)))) <| fun (d1, d2) ->
                let taskList = generateTaskList d1 d2 NoTask
                let rec taskListLength taskList =
                    match taskList with
                    | Task (_, _, nextTask) -> 1 + taskListLength nextTask
                    | NoTask -> 0
                let actual = taskListLength taskList
                let expected = min d1 d2
                expected = actual


        Expecto.ExpectoFsCheck.testProperty "Duration of focus should be calculated correctly" focusDurationProperty
        Expecto.ExpectoFsCheck.testProperty "Task complexity should be calculated correctly" taskComplexityProperty
        Expecto.ExpectoFsCheck.testProperty "Task list should have the correct length" taskListLengthProperty



    ]

let dice1 = 6
let dice2 = 6

let taskList = generateTaskList dice1 dice2 NoTask

let rec printTaskList taskList =
    match taskList with
    | Task (complexity, duration, nextTask) ->
        printfn "Complexidade: %A, Duração: %A" complexity duration
        printTaskList nextTask
    | NoTask -> ()

printTaskList taskList

Expecto.Tests.runTestsWithCLIArgs [] [||] properties
|> _assertEqual 0

main

let main args =
    let result = rollProgressively (Some (printfn "%s")) rollDice true (System.Int32.MaxValue / 10)
    trace Debug (fun () -> $"main / result: {result}") _locals
    0