Что тут можно в коде улучшить или оптимизировать? чтобы он не был таким длинным?
Игра в русскую рулетку в пистолет вставлено 5 патронов 4 холостых и 1 боевой
Если вы будете стрелять в себя то на следующем ходе можете снова сделать выбор в кого стрелять. Но если стреляете в соперника и не убиваете его то ход переходит ему. ДЛя него действуют такие же правила.
Так вот какая веротяность выиграть в кого стрелять?
резульат
В себя - 47% выиграть
В соперника - 52% выиграть
import itertools
x = list(map(list, set(itertools.permutations([0, 1, 0, 1, 1]))))
for gg in range(2):
победа=0
поражение=0
z=set()
for _ in x:
for num, i in enumerate(_):
if num==0:
if i==1:
if gg==0:
поражение+=1
elif gg==1:
победа+=1
else:
if gg==0: z.add((x.index(_),"всебя"))
elif gg==1: z.add((x.index(_),"внего"))
else:
for k in z.copy():
z.remove(k)
if k[-1]=='всебя':
if i==1:
if num==_.count(0):
победа+=1
else:
победа+=1
поражение+=1
else:
xc=list(k)
z.add(tuple(xc+["внего"]))
z.add(tuple(xc+["всебя"]))
elif k[-1]=='внего':
if i==1:
if num==_.count(0):
поражение+=1
else:
победа+=1
поражение+=1
else:
xc=list(k)
z.add(tuple(xc+["внего"]))
z.add(tuple(xc+["всебя"]))
if gg==0:
print("В себя -", str(int(победа/(победа+поражение)*100))+"% выиграть")
else:
print("В соперника -", str(int(победа/(победа+поражение)*100))+"% выиграть")
Ответы (3 шт):
Учитывая, что задача не поставлена чётко, я просто покажу, как можно писать гораздо более понятный код. Можно было обойтись и без Enum
, но на мой взгляд такой год гораздо проще читать, понимать и дорабатывать. Хотя размер кода и не изменился, но сам код поменялся кардинально:
from enum import Enum
from random import choice, shuffle
class Player(Enum):
One = 1
Two = 2
class Shoot(Enum):
Self = 1
Other = 2
Random = -1
class Cartridge(Enum):
Live = 1
Blank = 0
def game(clip, player: Player, shoot=Shoot.Random) -> Player:
if shoot == Shoot.Random:
shoot = choice([Shoot.Self, Shoot.Other])
else:
shuffle(clip)
bullet, *clip = clip
if bullet == Cartridge.Live:
if shoot == Shoot.Other:
return player
else:
return next_player(player)
return game(clip, next_player(player))
def next_player(player: Player):
match player:
case Player.One:
return Player.Two
case Player.Two:
return Player.One
def get_stats(clip, trials = 100_000):
stats = {}
for shoot in (Shoot.Self, Shoot.Other):
stats[shoot] = sum(game(clip, Player.One, shoot) == Player.One for _ in range(trials)) / trials
return stats
clip = [Cartridge.Live] + [Cartridge.Blank] * 4
for s, r in get_stats(clip).items():
print(f'{s}: {r:.2%}')
Вывод:
Shoot.Self: 40.01%
Shoot.Other: 60.11%
Я понял задачу так, что мы первый ход фиксируем - либо игрок стреляет в себя, либо в другого игрока. Остальные ходы - случайные. Барабан крутится один раз - в начале игры (и это завязано в коде на то, что первый ход игрока жёстко задан, это не очень хорошо, лучше потом переделать это место). Ну и собираем статистику для первого варианта и для второго, рекурсивно играя в эту игру, пока кто-нибудь не выиграет. Возможно тут что-то другое хотели, но с данным кодом гораздо проще будет переделать схему игры, чем с вашим, где вообще непонятно что происходит.
P.S. Варианты:
- Второй игрок всегда стреляет в первого, а первый стреляет случайно (кроме фиксированного первого выстрела). Вероятность выиграть у 1-го игрока резко падает:
- В себя: 20%
- В другого: 40%
- Оба игрока всегда стреляют в другого (за исключением 1-го выстрела 1-го игрока). Нет разницы со случайным выбором в кого стрелять:
- В себя: 40%
- В другого: 60%
И так, правильный ответ. Разумеется, с учётом что оба игрока играют оптимально.
Как часто бывает, для получения ответа на задачу её надо обобщить. Обозначим через P(N) вероятность победы при оптимальной игре в ситуации, когда в обойме осталось N случайных патронов. Заметим, что следующий патрон будет боевым с вероятностью 1/N.
В таком случае, получаем следующие исходы:
при выстреле в себя игрок погибает с вероятностью 1/N, иначе же выигрывает с вероятностью (1 - 1/N) P(N-1) = (N-1)/N P(N-1)
при выстреле в противника игрок мгновенно выигрывает с вероятностью 1/N, иначе же выигрывает с вероятностью (1 - 1/N) (1 - P(N-1)) = (N-1)/N (1 - P(N-1))
Поскольку каждый игрок играет оптимально, получаем формулу:
P(N) = max( (N-1)/N P(N-1), 1/N + (N-1)/N (1 - P(N-1)) )
Эту формулу можно перенести в программу напрямую:
def prob():
p = 0
n = 0
while True:
n = n+1
self = (1 - 1/n) * p
other = 1/n + (1 - 1/n) * (1 - p)
if self > other:
yield n, "self", self
p = self
else:
yield n, "other", other
p = other
import itertools
for x in itertools.islice(prob(), 10):
print(x)
Однако, запустив эту программу, можно увидеть странность: каждый чётный элемент последовательности равен 0.5. Это не случайность, и это можно доказать по индукции.
И так, теорема
P(N) = 1/2 для любого чётного положительного N, причём направление выстрела не важно
Доказательство по индукции. База:
Очевидно, что P(1) = 1 Это значит, что в ситуации с двумя патронами тот в кого стреляли, либо умирает - либо получает ход и гарантировано побеждает. Чистая ситуация 50/50. Ч.т.д.
Теперь переход.
И так, мы знаем что P(N-2) = 1/2, посчитаем P(N-1):
При выстреле в себя вероятность победы равна (N-2)/(N-1) P(N-2) = (N-2)/2(N-1)
При выстреле в противника вероятность победы равна 1/(N-1) + (N-2)/(N-1) (1 - P(N-2)) = 1/(N-1) + (N-2)/2(N-1) = N/2(N-1)
Вторая вероятность очевидно больше, и P(N-1) = N/2(N-1)
Теперь посчитаем P(N)
При выстреле в себя вероятность победы равна (N-1)/N P(N-1) = 1/2
При выстреле в противника вероятность победы равна 1/N + (N-1)/N (1 - P(N-1)) = 1/N + (N-1)/N (1 - N/2(N-1)) = 1/N + (N-1 - N/2)/N = 1/2
Ч.т.д
Окончательное решение
И так, для чётных N P(N) = 1/2 независимо от того куда стрелять
Осталось найти решение для нечётных. Но вообще-то его уже нашли раньше в процессе доказательства перехода индукции.
При выстреле в себя вероятность победы равна (N-1)/2N
При выстреле в противника вероятность победы равна (N+1)/2N
Итоговая программа:
n = 5
if n % 2 == 0:
print(f"в себя - 0.5")
print(f"в противника - 0.5")
else:
print(f"в себя - {(n-1)/2/n}")
print(f"в противника - {(n+1)/2/n}")
Буду решать задачу для n мест в барабане и m патронов. Патроны помещаются на случайные позиции. После чего нулевой игрок стреляет в себя или стреляет в противника и передаёт пистолет ему. Противник повторяет действие. Игра продолжается до первого выстрела. Противники играют наилучшим для себя образом. Требуется найти вероятность победы первого игрока при условии что он делает первый выстрел в себя и при условии что он делает первый выстрел в противника.
i – сколько было сделано ходов до сих пор.
Пусть qi – вероятность что на i-том месте патрон при условии что на предыдущих местах патронов нет. qi = m/n-i.
В частности qn-m = 1.
Обозначим:
p0(i, 0) – вероятность победы нулевого игрока, при условии что пистолет у него в руках.
p0(i, 1) – вероятность победы нулевого игрока, при условии что пистолет в руках противника.
Если нулевой игрок стреляет в себя на шаге i, то он убивает себя с вероятностью qi или продолжает игру с вероятностью 1 - qi. Во втором случае он побеждает с вероятностью p0(i + 1, 0). Собираем всё вместе:
(1 - qi) p0(i + 1, 0)
Если нулевой игрок стреляет в противника на шаге i, то он убивает его с вероятностью qi или игра продолжается вероятностью 1 - qi. Во втором случае пистолет переходит противнику и мы можем победить с вероятностью p0(i + 1, 1). Собираем всё вместе:
qi + (1 - qi) p0(i + 1, 1)
Нулевой игрок старается увеличить свои шансы на победу и выбирает максимум из доступных вариантов:
p0(i, 0) = max((1 - qi) p0(i + 1, 0), qi + (1 - qi) p0(i + 1, 1))
Если пистолет в руках противника, то он играет так, чтобы наши шансы на победу были минимальны:
p0(i, 1) = min((1 - qi) p0(i + 1, 0), qi + (1 - qi) p0(i + 1, 1))
p0 вычисляется реккурентно. База i = n - m. На этом шагу можно быть уверенным что выстрел произойдёт, все пустые позиции уже пройдены:
p0(n - m, 0) = 1 – нулевой стреляет в первого.
p0(n - m, 1) = 0 – первый стреляет в нулевого.
n, m = map(int, input().split())
q = [m / (n - i) for i in range(n - m + 1)]
p0 = [[1, 0] for _ in range(n - m + 2)]
def p0_self(i): # вероятность победы после выстрела в себя
return (1 - q[i]) * p0[i + 1][0]
def p0_other(i): # вероятность победы после выстрела в противника
return q[i] + (1 - q[i]) * p0[i + 1][1]
for i in range(n - m + 1)[::-1]:
p0[i][0] = max(p0_self(i), p0_other(i))
p0[i][1] = min(p0_self(i), p0_other(i))
print(p0_self (0))
print(p0_other(0))
Если убрать все ненужные таблицы и заменить переменные, получится такой вариант:
n, m = map(int, input().split())
a, b = 0, 1
for i in range(n - m + 1)[::-1]:
q = m / (n - i)
a, b = (1 - q) * max(a, b), q + (1 - q) * min(a, b)
print(a)
print(b)
По индукции докажем что a + b = 1 и a ≤ b. Что после замены индексов даст:
n, m = map(int, input().split())
a = 0
for j in range(m, n + 1):
a = (j - m) / j * (1 - a)
print(a)
print(1 - a)