Modelando conjuntos de dados opostos com um swap

5

Eu tenho um sistema de dados que premia, por exemplo, dano ao perdedor de um teste oposto. Os jogadores lançam sua reserva de dados, com o número alvo de cada jogador sendo o maior número lançado pelo seu oponente. Qualquer jogada acima ou igual ao alvo conta como um sucesso. Os jogadores então comparam seu número de sucessos e o perdedor toma a diferença como dano.

No caso de os jogadores terem um número igual de sucessos, o jogador com a maior série de dados ganha e causa 1 ponto de dano ao perdedor. (Começando com o dado mais alto, os jogadores descartam dados correspondentes até que um dos jogadores tenha um dado maior.)

Eu modelei isso no AnyDice assim: Dados da briga

function: brawl A:s vs B:s {
  SA: A >= 1@B
  SB: B >= 1@A
  if SA-SB=0 {
    result:(A > B) - (A < B)
  }
  result:SA-SB
}
output [brawl 3d6 vs 3d6] named "A vs B Damage"

Tudo bem, mas minha pergunta é:

Como eu modelaria uma situação semelhante, mas com o Jogador A sendo capaz de trocar seus dados mais baixos pelos mais altos do Jogador B?

Além disso, se alguém sabe como eu posso otimizar o código acima, seria apreciado. Atualmente estou limitado a pools razoavelmente pequenos.

Exemplo # 1:

Player A rolls: 4,3,3,2,1
Player B rolls: 4,4,2,2,2

Player A swaps dice, exchanges his 1 result for Player B's 4 result.
Player A's final pool: 4,4,3,3,2
Player B's final pool: 4,2,2,2,1

Both players' target number is 4 (taken as the highest value of the other player's pool).
Player A has two successes.
Player B has one success.
Player B takes one "damage".

Exemplo # 2:

Player A rolls: 4,3,3,2,1
Player B rolls: 6,4,2,2,2

Player A swaps dice, exchanges his 1 result for Player B's 6 result.
Player A's final pool: 6,4,3,3,2
player B's final pool: 4,2,2,2,1

Player A's target number is 4 (2 successes).
Player B's target number is 6 (0 successes).

Player B takes two "damage".

Exemplo # 3:

Player A rolls: 4,3,2,2,1
Player B rolls: 4,4,4,3,1

Player A swaps dice, exchanges his 1 result for Player B's 4 result.
Player A's final pool: 4,4,3,2,2
Player B's final pool: 4,4,3,1,1

Player A's target number is 4 (2 successes).
Player B's target number is 4 (2 successes).

Tiebreak!
Player A: 4,4,3,2,2
Player B: 4,4,3,1,1
Player A wins the tiebreak awarding 1 damage to player B.

Se houver uma solução de Troll, isso também seria aceitável. Eu nunca usei Troll antes, mas acho que deveria ser capaz de pegá-lo.

Eu consegui chegar ao mesmo lugar com o Troll como com o código AnyDice acima, mas estou preso na implementação da mecânica de troca. : (

a:=5d6;
b:=5d6;

result := (count (max b) <= a)-(count (max a) <= b);

aa := sum(max(a -- b));
bb := sum(max(b -- a));
tiebreak := if aa > bb then 1
       else if aa < bb then -1
       else 0;

 if result = 0  then tiebreak  else result
    
por Doug 03.01.2018 / 14:43

3 respostas

Aqui está uma simples solução "força bruta" do AnyDice para o seu problema:

function: brawl A:s vs B:s {
  SA: A >= 1@B
  SB: B >= 1@A
  if SA = SB { result: (A > B) - (A < B) }
  else { result: SA - SB }
}
function: set element I:n in SEQ:s to N:n {
  NEW: {}
  loop J over {1 .. #SEQ} {
    if I = J { NEW: {NEW, N} }
    else { NEW: {NEW, J@SEQ} }
  }
  result: NEW
}
function: brawl A:s vs B:s with optional swap {
  AX: [sort [set element #A in A to 1@B]]
  BX: [sort [set element 1 in B to #A@A]]
  NOSWAP: [brawl A vs B]
  SWAP: [brawl AX vs BX]
  result: [highest of NOSWAP and SWAP]
}
output [brawl 3d6 vs 3d6 with optional swap] named "A vs B Damage"

A função [brawl A vs B] faz exatamente a mesma coisa que no seu código original (mesmo que eu ajustei um pouco), enquanto a função de ajuda [set element I in SEQ to N] é de esta resposta . A nova função [brawl A vs B with optional swap] apenas chama a primeira função duas vezes, uma vez com o menor dado de A trocou com o maior dado de B e uma vez sem, e retorna o melhor resultado dos dois.

O bom desta abordagem é que na verdade não precisamos determinar exatamente quando é vantajoso para A trocar os dados. Tudo o que precisamos supor é que, com os dados reais rolados na mesa, o jogador A é inteligente o suficiente para fazer as contas e descobrir se a troca irá melhorar ou piorar sua pontuação.

No entanto, neste caso em particular, a estratégia ótima é bem simples: A deve trocar seu menor teste de dado com o maior valor de B, se e somente se for menor que o maior de B. (Se eles são iguais, então trocá-los não faz diferença, é claro.) Portanto, a seguinte função otimizada irá, de fato, dar os mesmos resultados neste caso:

function: brawl A:s vs B:s with optional swap {
  if #A@A >= 1@B {
    result: [brawl A vs B]
  }
  AX: [sort [set element #A in A to 1@B]]
  BX: [sort [set element 1 in B to #A@A]]
  result: [brawl AX vs BX]
}

Como alternativa, aqui está um programa em Python que calcula a mesma coisa , usando (uma versão ligeiramente modificada) do gerador de conjuntos de dados de esta resposta :

# generate all possible sorted NdD rolls and their probabilities
# see http://en.wikipedia.org/wiki/Multinomial_distribution for the math
# original: https://rpg.stackexchange.com/questions/63120/anydice-help-ore-like-resolution/65440#65440
# (this version modified to return rolls as simple n-tuples of integers, sorted in descending order)

factorial = [1.0]
def dice_pool(n, d):
    for i in range(len(factorial), n+1):
        factorial.append(factorial[i-1] * i)
    nom = factorial[n] / float(d)**n
    for roll, den in _dice_pool(n, d):
        yield roll, nom / den

def _dice_pool(n, d):
    if d > 1:
        for i in range(0, n+1):
            highest = (d,) * i
            for roll, den in _dice_pool(n-i, d-1):
                yield highest + roll, den * factorial[i]
    else:
        yield (d,) * n, factorial[n]

def brawl_with_swap(rollA, rollB):
   # optionally swap A's lowest roll with B's highest:
   minA = rollA[-1]
   maxB = rollB[0]
   if minA < maxB:
       rollA = sorted(rollA[:-1] + (maxB,), reverse=True)
       rollB = sorted(rollB[1:] + (minA,), reverse=True)
   # scoring:
   scoreA = sum(x >= rollB[0] for x in rollA)
   scoreB = sum(x >= rollA[0] for x in rollB)
   if scoreA != scoreB:
       return scoreA - scoreB
   else:
       return (rollA > rollB) - (rollA < rollB)

stats = {}
for rollA, probA in dice_pool(3,6):
    for rollB, probB in dice_pool(3,6):
        result = brawl_with_swap(rollA, rollB)
        if result not in stats: stats[result] = 0.0
        stats[result] += probA * probB

for result, prob in sorted(stats.items()):
    print("%+2d:%8.4f%% %s" % (result, 100*prob, "#" * int(60*prob + 0.5)))

Ao contrário da simulação estocástica de A.B., este código calcula as probabilidades exatas (bem, exatas até a precisão do ponto flutuante, de qualquer forma) dos vários resultados diretamente, enumerando todos os possíveis lançamentos de dados e suas probabilidades, assim como o AnyDice. É um pouco mais rápido do que AnyDice, no entanto, com o caso 3D6 3D6 vs. tendo apenas cerca de 0,1 segundos e 4d6 vs. 4d6 tendo apenas 0,25 segundos na servidor TIO

    
07.01.2018 / 09:27

Assumindo que ambos os jogadores jogam de forma otimizada e se eu entendi a sua explicação, você pode usar este código para encontrar a probabilidade de certos resultados dependendo de variáveis como dados usados e número de dados lançados para cada jogador.

O link fornecido acima permite que você insira entradas diferentes para essas variáveis, bem como o número de iterações. É padronizado usar um d6 com 5 dados por pool de jogadores e 100.000 iterações.

from random import randint

sidesOfDie = int(input())
numberofRolls = int(input())
loops = int(input())

iterator = 0
winsA = 0
winsB = 0
ties = 0
tiesWinA = 0
tiesWinB = 0
sameRollBefore = 0
sameRollAfter = 0
damageToA = 0
damageToB = 0

while iterator != loops:
    iterator += 1

    valuesA = []
    valuesB = []

    while len(valuesA) < numberofRolls:
        valuesA.append(randint(1, sidesOfDie))
        valuesB.append(randint(1, sidesOfDie))

    valuesA = sorted(valuesA)[::-1]
    valuesB = sorted(valuesB)[::-1]

    if valuesA == valuesB:
        sameRollBefore += 1

    temp = valuesB[0]
    valuesB[0] = valuesA[4]
    valuesA[4] = temp
    valuesA = sorted(valuesA)[::-1]
    valuesB = sorted(valuesB)[::-1]

    targetA = int(valuesB[0])
    targetB = int(valuesA[0])

    countA = 0
    countB = 0
    for v in valuesA:
        if int(v) == targetA:
            countA += 1
    for v in valuesB:
        if int(v) == targetB:
            countB += 1

    if countA == countB:
        ties += 1
        if valuesA == valuesB:
            sameRollAfter += 1
        else:
            count = 0
            while valuesA[count] == valuesB[count] and count != numberofRolls:
                count += 1
            else:
                if valuesA[count] > valuesB[count]:
                    tiesWinA += 1
                    damageToB += 1
                else:
                    tiesWinB += 1
                    damageToA += 1
    elif countA > countB:
        winsA += 1
        damageToB += (countA - countB)
    else:
        winsB += 1
        damageToA += (countB - countA)

print('Total number of iterations:', iterator)
print('Dice used: d' + str(sidesOfDie))
print('Number of dice rolled by each player for each iteration:', numberofRolls)

print('\nPlayer A wins:', winsA)
print('Player A win percentage:', winsA/iterator)
print('Player B wins:', winsB)
print('Player B win percentage:', winsB/iterator)

print('\nTotal damage done:', damageToA + damageToB)
print('Damage done to Player A:', damageToA)
print('Average damage done to Player A per iteration:', damageToA/iterator)
print('Damage done to Player B:', damageToB)
print('Average damage done to Player B per iteration:', damageToB/iterator)

print('\nTiebreakers:', ties)
print('Tiebreaker percentage:', ties/iterator)
print('%d tiebreakers won by Player A with a percentage of' % tiesWinA, tiesWinA/ties)
print('%d tiebreakers won by Player B with a percentage of' % tiesWinB, tiesWinB/ties)
print('%d same rolls after swapping with a percentage of' % sameRollAfter, sameRollAfter/ties)

print('\nNumber of same rolls before swapping:', sameRollBefore)

Está escrito em Python, pois não estou familiarizado com o AnyDice.

    
03.01.2018 / 18:04

Torben, criador de Dados de Troll , forneceu a resposta:

a := 5d6;
b := 5d6;
if (min a) < (max b) then (
  ab := (a -- (min a)) U (max b);
  ba := (b -- (max b)) U (min a);

  result := (count (max ba) <= ab)-(count (max ab) <= ba);

  aa := sum(max(ab -- ba));
  bb := sum(max(ba -- ab));
  tiebreak := if aa > bb then 1
      else if aa < bb then -1
      else 0;

  if result = 0  then tiebreak  else result
) else (
  result := (count (max b) <= a)-(count (max a) <= b);

  aa := sum(max(a -- b));
  bb := sum(max(b -- a));
  tiebreak := if aa > bb then 1
      else if aa < bb then -1
      else 0;

 if result = 0  then tiebreak  else result
)
    
05.01.2018 / 10:26