Files

268 lines
6.4 KiB
PHP

<?php
namespace app\models;
use Yii;
use yii\base\Model;
class DeckForm extends Model
{
const COMPUTE_TRIES = 10000;
const COMPUTE_ROUNDS = 10;
const CARD_ANY = 0;
const CARD_WEAPON_ITEM = 1;
const CARD_WEAPON_SPELL = 2;
const CARD_PREPARED = 3;
const CARD_BACKPACK_0 = 4;
const CARD_BACKPACK_2 = 5;
const CARD_INITIATE = 6;
public $size = 30;
public $weapons_items = 0;
public $weapons_spells = 0;
public $card_prepared_to_worst = 0;
public $card_backpack_0 = 0;
public $card_backpack_2 = 0;
public $card_initiate = 0;
/**
* @return array the validation rules.
*/
public function rules()
{
return [
[['size', 'weapons_items', 'weapons_spells', 'card_prepared_to_worst', 'card_backpack_0', 'card_backpack_2', 'card_initiate'], 'required'],
[['size', 'weapons_items', 'weapons_spells', 'card_prepared_to_worst', 'card_backpack_0', 'card_backpack_2', 'card_initiate'], 'integer'],
];
}
/**
* @return array customized attribute labels
*/
public function attributeLabels()
{
return [
'size' => 'Размер колоды',
'weapons_items' => 'Оружие (вещь)',
'weapons_spells' => 'Оружие (заклинание)',
'card_prepared_to_worst' => 'Готовность к худшему',
'card_backpack_0' => 'Рюкзак (0)',
'card_backpack_2' => 'Рюкзак (2)',
'card_initiate' => 'Посвящённая',
];
}
private $result;
public function compute() : ComputationResult
{
$this->result = new ComputationResult();
$this->result->probabilities = [];
for ($i = 0; $i < self::COMPUTE_ROUNDS; $i++) $this->result->probabilities[] = 0;
for ($i = 0; $i < self::COMPUTE_TRIES; $i++)
{
$this->generateTry();
}
for ($i = 0; $i < self::COMPUTE_ROUNDS; $i++) $this->result->probabilities[$i] /= (self::COMPUTE_TRIES + 0.0);
return $this->result;
}
private $deck;
private $hand;
private function fillDeck()
{
$this->deck = [];
for ($i = 0; $i < $this->size; $i++) $this->deck[] = self::CARD_ANY;
$pos = 0;
for ($i = 0; $i < $this->weapons_items && $pos < count($this->deck); $i++)
{
$this->deck[$pos++] = self::CARD_WEAPON_ITEM;
}
for ($i = 0; $i < $this->weapons_spells && $pos < count($this->deck); $i++)
{
$this->deck[$pos++] = self::CARD_WEAPON_SPELL;
}
for ($i = 0; $i < $this->card_backpack_0 && $pos < count($this->deck); $i++)
{
$this->deck[$pos++] = self::CARD_BACKPACK_0;
}
for ($i = 0; $i < $this->card_backpack_2 && $pos < count($this->deck); $i++)
{
$this->deck[$pos++] = self::CARD_BACKPACK_2;
}
for ($i = 0; $i < $this->card_prepared_to_worst && $pos < count($this->deck); $i++)
{
$this->deck[$pos++] = self::CARD_PREPARED;
}
for ($i = 0; $i < $this->card_initiate && $pos < count($this->deck); $i++)
{
$this->deck[$pos++] = self::CARD_INITIATE;
}
}
private function shuffleDeck()
{
for ($i = 0; $i < count($this->deck); $i++)
{
$j = rand(0, $i);
$t = $this->deck[$j];
$this->deck[$j] = $this->deck[$i];
$this->deck[$i] = $t;
}
}
private function addProb($round)
{
for ($i = $round; $i < self::COMPUTE_ROUNDS; $i++) $this->result->probabilities[$i]++;
}
private function haveWeapon()
{
foreach ($this->hand as $card)
{
if ($card == self::CARD_WEAPON_ITEM) return true;
if ($card == self::CARD_WEAPON_SPELL) return true;
}
return false;
}
private function tryPlay($card)
{
for ($i = 0; $i < count($this->hand); $i++)
{
if ($this->hand[$i] == $card)
{
$this->hand[$i] = $this->hand[count($this->hand) - 1];
array_pop($this->hand);
return true;
}
}
return false;
}
private function removeFromDeck($i)
{
$this->deck[$i] = $this->deck[count($this->deck) - 1];
array_pop($this->deck);
}
private function useInitiate()
{
for ($i = count($this->deck) - 1; $i >= 0 && $i > count($this->deck) - 4; $i--)
{
if ($this->deck[$i] == self::CARD_WEAPON_SPELL)
{
$this->removeFromDeck($i);
$this->hand[] = self::CARD_WEAPON_SPELL;
break;
}
}
$this->shuffleDeck();
}
private function generateTry()
{
$this->fillDeck();
$this->shuffleDeck();
// mulligan
$this->hand = [];
$replace = [];
for ($i = 0; $i < 5; $i++)
{
$current = array_pop($this->deck);
if ($current != self::CARD_ANY) $this->hand[] = $current;
else $replace[] = $current;
}
while (count($this->hand) < 5) $this->hand[] = array_pop($this->deck);
foreach ($replace as $r) $this->deck[] = $r;
$this->shuffleDeck();
$initiate_played = false;
for ($round = 0; $round < self::COMPUTE_ROUNDS; $round++)
{
if ($this->haveWeapon())
{
$this->addProb($round);
return;
}
$initiate_used = false;
for ($action = 0; $action < 3; $action++)
{
if (!$initiate_used)
{
$this->useInitiate();
$initiate_used = true;
}
if ($this->tryPlay(self::CARD_WEAPON_ITEM))
{
$this->addProb($round);
return;
}
if ($this->tryPlay(self::CARD_WEAPON_SPELL))
{
$this->addProb($round);
return;
}
// try do best action
if ($this->tryPlay(self::CARD_PREPARED))
{
for ($i = count($this->deck) - 1; $i >= 0 && $i > count($this->deck) - 10; $i--)
{
if ($this->deck[$i] == self::CARD_WEAPON_ITEM ||
$this->deck[$i] == self::CARD_WEAPON_SPELL)
{
$this->hand[] = $this->deck[$i];
$this->removeFromDeck($i);
break;
}
}
$this->shuffleDeck();
continue;
}
if ($this->tryPlay(self::CARD_BACKPACK_2))
{
for ($i = count($this->deck) - 1; $i >= 0 && $i > count($this->deck) - 13; $i--)
{
if ($this->deck[$i] == self::CARD_WEAPON_ITEM)
{
$this->removeFromDeck($i);
$this->hand[] = self::CARD_WEAPON_ITEM;
break;
}
}
$this->shuffleDeck();
continue;
}
if ($this->tryPlay(self::CARD_BACKPACK_0))
{
for ($i = count($this->deck) - 1; $i >= 0 && $i > count($this->deck) - 7; $i--)
{
if ($this->deck[$i] == self::CARD_WEAPON_ITEM)
{
$this->removeFromDeck($i);
$this->hand[] = self::CARD_WEAPON_ITEM;
break;
}
}
$this->shuffleDeck();
continue;
}
if ($this->weapons_spells > 0 && !$initiate_played)
{
if ($this->tryPlay(self::CARD_INITIATE))
{
$initiate_played = true;
}
}
$this->hand[] = array_pop($this->deck);
}
if (!$initiate_used)
{
$this->useInitiate();
$initiate_used = true;
}
}
}
}