268 lines
6.4 KiB
PHP
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;
|
|
}
|
|
}
|
|
}
|
|
}
|