This is a documentation for Board Game Arena: play board games online !

Туториал хеартс

Извор: Board Game Arena
Пређи на навигацију Пређи на претрагу

Интродуцтион

Усинг тхис туториал, yоу цан буилд а цомплете wоркинг гаме он тхе БГА енвиронмент: Хеартс.

Бефоре yоу реад тхис туториал, yоу муст:

  • Реад тхе овералл пресентатионс оф тхе БГА Фрамеwорк (сее хере).
  • Кноw тхе рулес фор Хеартс
  • Соме-wхат кноw тхе лангуагес усед он БГА: ПХП, СQЛ, ХТМЛ, ЦСС, Јавасцрипт
  • Сетуп yоу девелопмент енвиронмент Фирст Степс wитх БГА Студио
  • Ас парт оф сетуп yоу хаве то хаве аццесс то yоур фтп хоме фолдер ин студио, wхицх wоулд хаве 'хеартс' гаме соурце цоде. Wе wилл бе усинг соме ресоурцес оф тхис гаме ин тхис туториал, со цопy ит овер то лоцал диск иф yоу хаве нот доне со.

Иф yоу стуцк оф хаве qуестион абоут тхис туториал пост он БГА Девелоперс форум

Цреате yоур фирст гаме

Иф yоу хаве нот алреадy, yоу хаве то цреате а пројецт ин БГА Студио. Фор тхис туториал yоу цан цреате а пројецт хеартсYОУНАМЕ wхере YОУНАМЕ ис yоур девелопер логин наме. Yоу цан алсо ре-усе тхе пројецт yоу хаве цреатед фор "Фирст Степс" туториал абове. Wитх тхе инитиал скелетон оф цоде провидед инитиаллy, yоу цан алреадy старт а гаме фром тхе БГА Студио.

Финд анд старт тхе гаме ин турн басед моде, маке суре ит wоркс.

Сецонд модифy тхе теxт ин хеартсYОУНАМЕ_хеартсYОУНАМЕ.тпл, релоад тхе паге ин тхе броwсер анд маке суре yоур фтп сyнц wоркс ас еxпецтед. Ноте: иф yоу хаве нот сетуп ауто-сyнц до ит ноw, мануаллy цопyинг филес ис а но-стартер.

Хоок версион цонтрол сyстем

Иф итс а реал гаме ор евен фор тхис туториал I wоулд цоммит тхе цоде то версион цонтрол ригхт ат старт. Yоу гоинг то финд yоурселф ин тхе ситуатион wхен гаме доес нот евен старт анyморе анд но wаy оф дебуггинг ит унлесс yоу хаве а wаy то реверт. Тхат ис wхере версион цонтрол бецомес верy хандy. Иф yоу дон'т кноw wхат I ам талкинг абоут тхен ат леаст бацк-уп yоур филес афтер еацх оф мајор степс. Стартинг ноw.

Цоде фор тхис туториал аваилабле он гитхуб ат https://github.com/elaskavaia/bga-heartsla

Дифферент ревисионс репресент дифферент степс алонг тхе процесс, стартинг фром оригинал темплате то а цомплете гаме.

Упдате гаме инфос анд боx грапхицс

Евен ит доес нот нотхинг yет I алwаyс старт wитх макинг суре гаме лоокс десцент ин тхе гаме селецтор, меанинг ит хас нице боx грапхицс анд информатион ис цоррецт. Фор тхат wе неед то едит гамеинфос.инц.пхп. Wхат yоу wоулд до фор реал гаме yоу wоулд го то http://boardgamegeek.com финд тхе гаме анд усе тхе информатион фром wеб-сите то филл тхе гамеинфос. Со летс до тхат. Финд "хеартс" он боардгамегеек. Оригинал релеасе 1850 :) Yоу цан филл ин yеар оф публисхинг, бгг ид, yоу цан пут Публиц Домаин ас публисхер анд публисхер ид ис 171 фор публиц домаин. Анд ас десигнер анд аутхор yоу цан јуст пут yоур оwн наме јуст фор фун. Сет нумбер оф плаyерс то 4.

 // Players configuration that can be played (ex: 2 to 4 players)
 'players' => array( 4 ),  

Тхе неxт степ ис то реплаце гаме_боx.пнг wитх ницер имагес. Фор тхис туториал јуст цопy алл филес фром имг/ фолдер оф хеартс/ темплате инто имг/ дирецторy оф yоур пројецт. Анд реплаце публисхер.пнг wитх ницер имаге фор еxампле https://github.com/elaskavaia/bga-sharedcode/blob/master/img/publisher.png.

Детаилс абоут имагес цан бе фоунд хере: Гаме арт: имг дирецторy.

Ноw импортант степ. Yоу хаве то ЛОАД тхесе филес ин студио wебсите тхроугх цонтрол панел. Со го то Цонтрол Панел -> Манагер Гамес -> хеартсYОУНАМЕ анд пресс Релоад фор 'Релоад гаме информатионс' анд 'Релоад гаме боx имаге'

Ноw трy то старт тхе гаме агаин. Иф yоу соме-хоw интродуцед а сyнтаx еррор ин гамеинфос филе ит маy нот ацтуаллy wорк (гаме wон'т старт). Алwаyс усе "Еxпресс Старт" буттон то старт тхе гаме. Yоу схоулд сее а стандард стате промпт фром темплате. Yоу схоулд сее 4 плаyерс он тхе ригхт, тестдуде0 .. тестдуде3. То сwитцх бетwеен тхем пресс тхе ред арроw буттон неар тхеир намес, ит wилл опен анотхер таб. Тхис wаy yоу дон'т неед то логин анд логоут фром мултипле аццоунтс!

Цоде Рев [1]

Лаyоут анд Грапхицс

Ин тхис сецтион wе wилл до грапхицс оф тхе гаме, анд маин лаyоут оф тхе гаме.

Фирст цопy а сприте wитх цардс имаге фром хеартс имг/цардс.јпг инто имг/ фолдер оф yоур пројецт.

Едит .тпл то адд соме дивс то репресент плаyер таблеау анд ханд ареа

<div id="myhand_wrap" class="whiteblock">
    <h3>My Hand</h3>
    <div id="myhand">
    </div>
</div>

Иф yоу рефресх yоу схоулд сее ноw wхите ареа wитх Мy Ханд титле.


Хеартсла-тпл2.пнг

Ноw летс адд а цард инто тхе ханд, јуст yоу цан феел ит. Едит .тпл анд а плаyертаблецард див инсиде а ханд див

...
    <div id="myhand">
       <div class="playertablecard"></div>
    </div>
...

Едит .цсс филе

.playertablecard {
    display: inline-block;
    position: relative;
    margin-top: 5px;
    width: 72px;
    height: 96px;
    background-image: url('img/cards.jpg'); /* temp hack to see it */
}

Wхен yоу едит ЦСС ремембер тхат yоу хаве то ФОРЦЕ-релоад паге, и.е. Схифт-Ф5, отхерwисе итс цацхед. Саме wхен yоу цханге еxистинг грапхицс филес.

Yоу схоулд сее тхис:

Хеартсла-тпл3.пнг

Аwесоме! Ноw летс до тхе рест оф лаyоут.

Тхере аре феw wаyс оф хоw хтмл цоулд хаве беен генератед, yоу цоулд хаве старт wитх нотхинг анд генерате алл бy јава сцрипт. Ор yоу цоулд хаве стартед wитх цомплете гаме маркуп ин хтмл анд маке јава сцрипт јуст хиде анд мове пиецес ароунд. БГА фрамеwорк провидес алсо а тхирд wаy wхицх ис миx оф ботх плус темплате енгине то генерате ХТМЛ усинг пхп. Со летс до тхат.

Цханге .тпл филе то хаве тхис инсиде

<div id="playertables">

    <!-- BEGIN player -->
    <div class="playertable whiteblock playertable_{DIR}">
        <div class="playertablename" style="color:#{PLAYER_COLOR}">
            {PLAYER_NAME}
        </div>
        <div class="playertablecard" id="playertablecard_{PLAYER_ID}">
        </div>
    </div>
    <!-- END player -->

</div>

<div id="myhand_wrap" class="whiteblock">
    <h3>{MY_HAND}</h3>
    <div id="myhand">
    </div>
</div>

Wхат wе дид ис wе аддед "блоцк" плаyер, ит ис маркед уп усинг хтмл цомментс. {ВАР} нотатион ис усед то ињецт вариаблес анд

  <!-- BEGIN xxx --> 
   inside 
  <!-- END xxx --> 

еффецтивелy аллоwс ус то до темплате лоопс.

Ин .виеw.пхп инсерт тхис цоде афтер 'Плаце yоур цоде белоw' цоммент


        $template = self::getGameName() . "_" . self::getGameName();
        
        $directions = array( 'S', 'W', 'N', 'E' );
        
        // this will inflate our player block with actual players data
        $this->page->begin_block($template, "player");
        foreach ( $players as $player_id => $info ) {
            $dir = array_shift($directions);
            $this->page->insert_block("player", array ("PLAYER_ID" => $player_id,
                    "PLAYER_NAME" => $players [$player_id] ['player_name'],
                    "PLAYER_COLOR" => $players [$player_id] ['player_color'],
                    "DIR" => $dir ));
        }
        // this will make our My Hand text translatable
        $this->tpl['MY_HAND'] = self::_("My hand");

Анд релоад. Иф еверyтхинг wент wелл yоу схоулд сее тхис:

Хеартсла-тпл4.пнг

Тхесе аре "таблеау" ареас фор 4 плаyерс плус Мy ханд висибле онлy то оне плаyер. Тхеy нот еxацтлy хоw wе wантед тхем то бе бецаусе wе дид нот едит .цсс yет.

Ноw едит .цсс, адд тхесе линес афтер импорт бефоре оур превиоус дефинитион

/** Table layout **/

#playertables {
    position: relative;
    width: 710px;
    height: 340px;
}

.playertablename {
    font-weight: bold;
}

.playertable {
    position: absolute;
    text-align: center;
    width: 180px;
    height: 130px;
}

.playertable_N {
    left: 50%;
    top: 0px;
    margin-left: -90px; /* half of 180 */
}
.playertable_S {
    left: 50%;
    bottom: 0px;
    margin-left: -90px; /* half of 180 */
}
.playertable_W {
    left: 0px;
    top: 50%;
    margin-top: -55px; /* half of 130 */
}
.playertable_E {
    right: 0px;
    top: 50%;
    margin-top: -55px; /* half of 130 */
}


Ноw yоу Схифт-Релоад анд yоу схоулд сее тхис: Хеартсла-тпл5.пнг

Тхис ис алмост алл wе неед фор грапхицс анд лаyоут, тхере аре феw тwеакс лефт тхере бут летс до соме море хеавy лифтинг ноw.

Гаме Интерфаце ЈС Стоцк

БГА фрамеwорк провидес феw оут оф тхе боx цлассес то деал wитх цардс. Тхе цлиент сиде цонтаинс цласс цаллед Стоцк анд ит цан бе усед фор анy дyнамиц хтмл 'пиецес' манагемент тхат усес цоммон сприте имаге. Он тхе сервер сиде wе wилл усе Децк цласс wхицх wе дисцусс латер.

Иф yоу опен цардс.јпг ин ан имаге виеwер yоу цан сее тхат ит ис а "сприте" имаге - 13x4 грид оф имагес ститцхед тогетхер, wхицх ис верy еффициент wаy то транспорт имагес. Со wе wилл усе Стоцк цласс то марк уп тхесе имагес анд цреате "цард" дивс фор ус.

Ат фирст, wе неед то адд "ебг/стоцк" ас а депенденцy:

define([
    "dojo","dojo/_base/declare",
    "ebg/core/gamegui",
    "ebg/counter",
    "ebg/stock"     /// <==== HERE
],

Тхен адд тхис то јс цонтруцтор, тхис wилл дефине сизе оф оур цардс

            console.log('hearts constructor');
            this.cardwidth = 72;
            this.cardheight = 96;

Тхе стоцк ис инитиализед ин Јавасцрипт "сетуп" метход лике тхис:

    // TODO: Set up your game interface here, according to "gamedatas"

    // Player hand
    this.playerHand = new ebg.stock(); // new stock object for hand
    this.playerHand.create( this, $('myhand'), this.cardwidth, this.cardheight );

Ас параметерс оф тхе "цреате" метход, wе провидед тхе wидтх/хеигхт оф ан итем (а цард), анд тхе цонтаинер див "мyханд" - wхицх ис ан ид оф "див" елемент фром оур .тпл филе репресентинг плаyер ханд.


Тхен, wе муст телл тхе стоцк wхат аре тхе итемс ит ис гоинг то дисплаy дуринг итс лифе: тхе 52 цардс оф а стандард цард гаме фром "ЦСС сприте" имаге намед "цардс.јпг" wитх алл тхе цардс аррангед ин 4 роwс анд 13 цолумнс.

Хере'с хоw wе телл стоцк wхат аре тхе итемс тyпе то дисплаy:

            this.playerHand.image_items_per_row = 13; // 13 images per row


            // Create cards types:
            for (var color = 1; color <= 4; color++) {
                for (var value = 2; value <= 14; value++) {
                    // Build card type id
                    var card_type_id = this.getCardUniqueId(color, value);
                    this.playerHand.addItemType(card_type_id, card_type_id, g_gamethemeurl + 'img/cards.jpg', card_type_id);
                }
            }

Анд адд тхис фунцтион то утилитиес сецтион

        // Get card unique identifier based on its color and value
        getCardUniqueId : function(color, value) {
            return (color - 1) * 13 + (value - 2);
        },

Еxпланатионс:

  • Ат фирст, wе телл тхе стоцк цомпонент тхат оур ЦСС сприте цонтаинс 13 итемс пер роw. Тхис wаy, ит цан финд тхе цоррецт имаге фор еацх цард тyпе ид.
  • Тхен фор тхе 4x13 цардс, wе цалл "аддИтемТyпе" метход тхат цреате тхе тyпе. Тхе аргументс аре тхе тyпе ид, тхе wеигхт оф тхе цард (фор сортинг пурпосе), тхе УРЛ оф оур ЦСС сприте, анд тхе поситион оф оур цард имаге ин тхе ЦСС сприте. Ит хаппенс то бе тхе саме нумбер ин оур цасе.

Ноте: wе неед то генерате а униqуе ИД фор еацх тyпе оф цард басед он итс цолор анд валуе. Фор тхат wе цреате а фунцтион "гетЦардУниqуеИд".

Ноw летс адд тхе 5 оф Хеарт то плаyер'с ханд јуст фор фун (тхис цоде wилл го ин сетуп метход афтер тyпес инитиализатион):

// 2 - hears, 5 is 5, and 42 is card id, it normally would come from db
this.playerHand.addToStockWithId( this.getCardUniqueId( 2, 5 ), 42 );

Иф yоу релоад ноw yоу схоулд сее 5 оф хеартс ин "yоур ханд".

Стоцк цонтрол цан хандле цлицкинг он итемс анд формс тхе селецтион, yоу цан иммедиателy реацт то селецтион ор yоу цан qуерy ит латер, фор еxампле wхен усер прессес соме отхер буттон.

Летс хоок ит уп, адд тхис ин сетуп метход, афтер тхис.плаyерХард ис инитиалисед

    dojo.connect( this.playerHand, 'onChangeSelection', this, 'onPlayerHandSelectionChanged' );


Тхен финд Плаyер'с ацтион цоммент сецтион анд адд хандлер афтер тхе цоммент

        onPlayerHandSelectionChanged : function() {
            var items = this.playerHand.getSelectedItems();

            if (items.length > 0) {
                if (this.checkAction('playCard', true)) {
                    // Can play a card

                    var card_id = items[0].id;
                    console.log("on playCard "+card_id);

                    this.playerHand.unselectAll();
                } else if (this.checkAction('giveCards')) {
                    // Can give cards => let the player select some cards
                } else {
                    this.playerHand.unselectAll();
                }
            }
        },

Но иф yоу релоад, опен јс Цонсоле, цлицк он цард ин Мy Ханд анд yоу схоулд сее

 on playCard 42

принтед он тхе цонсоле

Гаме Датабасе анд Гаме Инитиалисатион

Неxт степ yоу wант то десигн гаме датабасе анд сетуп неw гаме (он сервер сиде). Фор тхат wе неед а) модифy датабасе сцхема то адд оур цардс дата б) адд соме глобал "вариаблес" инто еxистинг глобалс табле.

То модифy сцхема фирст еxит yоу еxистинг гаме(с). Опен дбмодел.сqл филе анд унцоммент цард табле цреатион

CREATE TABLE IF NOT EXISTS `card` (
  `card_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `card_type` varchar(16) NOT NULL,
  `card_type_arg` int(11) NOT NULL,
  `card_location` varchar(16) NOT NULL,
  `card_location_arg` int(11) NOT NULL,
  PRIMARY KEY (`card_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

Тхис ис "цард" табле wхицх wоулд бе манагед бy Децк пхп цласс.

Ин аддитион wе wант а литтле пиеце оф информатион ин тхе плаyерс табле:

 -- add info about first player
 ALTER TABLE `player` ADD `player_first` BOOLEAN NOT NULL DEFAULT '0';

Нот суре wхy тхеy пут тхис инто плаyер табле, wе цоулд хаве глобал дб вариабле то холд фирст плаyер ас еасилy. Бут I ам јуст фоллоwинг еxистинг цоде море-лесс.

Неxт wе финаллy гет инто .гаме.пхп цласс, wхере тхе маин логиц анд дб интерацтион wоулд бе. Финд пхп цонструцтор wхицх схоулд бе

 function __construct( )

Тхис ис фирст фунцтион ин а филе.

        parent::__construct();
        self::initGameStateLabels( array( 
                         "currentHandType" => 10, 
                         "trickColor" => 11, 
                         "alreadyPlayedHearts" => 12,
                          ) );

        $this->cards = self::getNew( "module.common.deck" );
        $this->cards->init( "card" );

Хере wе аре инитиализинг тхрее "Гаме Стате Вариаблес" wхицх аре вариабле сторед ин датабасе. Тхеy аре интегерс. Ит муст старт wитх но лоwер тхен 10 синце тхе отхерс онес аре ресервед. Тхесе валуес аре сторед бy нумериц ид'с ин тхе датабасе, бут ин тхе пхп wе ассоциате тхем wитх стринг лабелс фор цонвениенце оф аццесс. Тхе вариаблес аре "трицкЦолор" - нумберс фром 1 то 4 тхат мап то цард суит (нот суре wхy итс цаллед цолор маyбе итс транслатион фром френцх); "алреадyПлаyедХеартс" - ис боолеан флаг (wелл 0 ор 1) индицатион еитхер сомебодy усе хеартс он тхе трицк; "цуррентХандТyпе" - сторес тхе валуе то индицате wхо то гиве цардс дуринг еxцханге.

Неxт 2 линес аре цреатинг $тхис->цардс објецт анд ассоциатинг ит wитх "цард" табле ин тхе тхе датабасе.

Иф wе цаллед табле фоо тхе ласт статемент wоулд хаве беен $тхис->цардс->инит( "фоо" )


Ат тхис поинт I wоулд старт а неw гаме анд маке суре ит стартс, тхен еxит.

Иф yоу маде а мистаке ин .сqл ор пхп цонструцтор гаме wон'т старт анд гоод луцк дебуггинг ит (тхат ис wхy ит импортант то цхецк онце ин а wхиле то маке суре ит стилл стартс wхиле yоу ремембер wхат yоу хаве цхангед)

Цоде Рев [2]

Ноw wе цан го то гаме инитиализатион сетупНеwГаме, тхис метход ис цаллед онце wхен табле ис цреатед.

Ин yоур темплате yоу схоулд хаве цоде тхат деалс wитх плаyер табле, јуст леаве ит ас ис. Старт инсертинг тхе отхер цоде афтер "Старт тхе гаме инитиализатион" цоммент

        // Init global values with their initial values

        // Note: hand types: 0 = give 3 cards to player on the left
        //                   1 = give 3 cards to player on the right
        //                   2 = give 3 cards to player on tthe front
        //                   3 = keep cards
        self::setGameStateInitialValue( 'currentHandType', 0 );
        
        // Set current trick color to zero (= no trick color)
        self::setGameStateInitialValue( 'trickColor', 0 );
        
        // Mark if we already played some heart during this hand
        self::setGameStateInitialValue( 'alreadyPlayedHearts', 0 );

Хере wе инитиализед алл тхе глобалс то 0.

Неxт ис то цреате оур цардс ин тхе датабасе. Wе хаве оне децк оф цардс со итс преттy симпле

        // Create cards
        $cards = array ();
        foreach ( $this->colors as $color_id => $color ) {
            // spade, heart, diamond, club
            for ($value = 2; $value <= 14; $value ++) {
                //  2, 3, 4, ... K, A
                $cards [] = array ('type' => $color_id,'type_arg' => $value,'nbr' => 1 );
            }
        }
        
        $this->cards->createCards( $cards, 'deck' );

Тхис цоде тхат wилл цреате оне оф еацх цард. Бут дон'т рун ит yет, бецаусе wе миссинг $тхис->цолорс. Со wе хаве стате оф тхе гаме ин тхе датабасе, бут тхере ис соме статиц гаме информатион wхицх невер цхангес, тхис информатион схоулд бе сторед ин материал.инц.пхп анд тхис wаy ит цан бе аццессед фром алл .пхп филес. Wе wилл едит тхис филе ноw бy аддинг тхесе линес

$this->colors = array(
    1 => array( 'name' => clienttranslate('spade'),
                'nametr' => self::_('spade') ),
    2 => array( 'name' => clienttranslate('heart'),
                'nametr' => self::_('heart') ),
    3 => array( 'name' => clienttranslate('club'),
                'nametr' => self::_('club') ),
    4 => array( 'name' => clienttranslate('diamond'),
                'nametr' => self::_('diamond') )
);

$this->values_label = array(
    2 =>'2',
    3 => '3',
    4 => '4',
    5 => '5',
    6 => '6',
    7 => '7',
    8 => '8',
    9 => '9',
    10 => '10',
    11 => clienttranslate('J'),
    12 => clienttranslate('Q'),
    13 => clienttranslate('K'),
    14 => clienttranslate('A')
);

Wхере $тхис->цолорс wилл дефине Суит лабелс анд $тхис->валуес_лабел дефинес валуе лабелс. Иф yоу нотицед wе хаве тwо оф еацх лабел фор суитс. Тхис ис бецаусе wе неед сометимес транслатед валуес он пхп сиде анд сометимес wе дон'т. Ин тхис цасе наметр wилл ретурн а транслатед валуе ригхт ин пхп, wхицх ис онлy усефулл wхен yоу тхроw еxцептионс то схоw ригхт стрингс. Иф yоу пассинг валуе то цлиент виа нотифицатион yоу схоулд алwаyс усе унтранслатед стрингс, анд цлиент wилл транслате ит. 'цлиенттранслате' маркс тхе валуе фор транслатион бут доес нот ацтуаллy цханге ит фор пхп. Фор море абоут тхис wондерфул транслатион стуфф сее Транслатионс сецтион.

Фулл гаме модел сyнцхронисатион

Ноw ат анy поинт ин тхе гаме wе неед то маке суре тхат датабасе информатион цан бе рефлецтед бацк ин ИУ, со wе фиx гетАллДатас фунцтион то ретурн алл поссибле дата wе неед то рецонструцт тхе гаме. Тхе темплате фор гетАллДатас алреадy такинг царе оф плаyер инфо, летс јуст адд ханд анд таблеау дата бефоре wе ретурн ресулт.

        // Cards in player hand
        $result['hand'] = $this->cards->getCardsInLocation( 'hand', $current_player_id );
        
        // Cards played on the table
        $result['cardsontable'] = $this->cards->getCardsInLocation( 'cardsontable' );


Ноw он тхе цлиент сиде wе схоулд дисплаy тхис дата, со ин yоур .јс филе ин сетуп фунцтион (wхицх ис тхе рецеивер оф гетАллДатас) адд реплаце оур хацк оф путтинг хеартс оф 5 дирецтлy инто ханд wитх:

            // Cards in player's hand
            for ( var i in this.gamedatas.hand) {
                var card = this.gamedatas.hand[i];
                var color = card.type;
                var value = card.type_arg;
                this.playerHand.addToStockWithId(this.getCardUniqueId(color, value), card.id);
            }

            // Cards played on table
            for (i in this.gamedatas.cardsontable) {
                var card = this.gamedatas.cardsontable[i];
                var color = card.type;
                var value = card.type_arg;
                var player_id = card.location_arg;
                this.playCardOnTable(player_id, color, value, card.id);
            }

Тхис схоулд схоw ханд анд таблеау цардс ноw, еxцепт wе аре миссинг плаyЦардОнТабле фунцтион. Со финд гетЦардУниqуеИд фунцтион wхицх схоулд бе ин утилитиес сецтион анд адд тхис афтер

        playCardOnTable : function(player_id, color, value, card_id) {
            // player_id => direction
            dojo.place(this.format_block('jstpl_cardontable', {
                x : this.cardwidth * (value - 2),
                y : this.cardheight * (color - 1),
                player_id : player_id
            }), 'playertablecard_' + player_id);

            if (player_id != this.player_id) {
                // Some opponent played a card
                // Move card from player panel
                this.placeOnObject('cardontable_' + player_id, 'overall_player_board_' + player_id);
            } else {
                // You played a card. If it exists in your hand, move card from there and remove
                // corresponding item

                if ($('myhand_item_' + card_id)) {
                    this.placeOnObject('cardontable_' + player_id, 'myhand_item_' + card_id);
                    this.playerHand.removeFromStockById(card_id);
                }
            }

            // In any case: move it to its final destination
            this.slideToObject('cardontable_' + player_id, 'playertablecard_' + player_id).play();
        },

Фор тхат то wорк wе алсо неед то адд цард темпле ин .тпл филе

// Javascript HTML templates

var jstpl_cardontable = '<div class="cardontable" id="cardontable_${player_id}" style="background-position:-${x}px -${y}px">\
                        </div>';


Wхат тхис доес ис басицаллy ит цреатес анотхер цард објецт, бецаусе иф ит ис нот оур цард итс нот ин оур ханд (стоцк) со wе хаве то цреате ит оут оф тхин аир. Тецхниц то до тхат ис то имплоде а јс темплате објецт дефинед ин .тпл филе wитх соме параметерс, wхицх wилл јуст басицаллy цреате а "див" стринг (yес yоу цоулд хаве усед стринг цонцатенатион бут ит wоулд нот бе фанцy). Ноw дојо.плаце плацес ит (тхе див) он топ оф плацехолдер. Ноw wе хаве ан објецт wитх ид оф 'цардонтабле_' + плаyер_ид, депендинг он wхо ис плаyинг ит wе еитхер плаце ит он плаyер минибоард ор ин ханд (анд ремове фром ханд стоцк). Тхен wе анимате тхе цард моде.

Wе алсо схоулд фиx оур .цсс филе ноw то адд стyле фор цардонтабле анд ремове бацкгроунд фор плаyертаблецард wхицх реаллy ис а плацехолдер див анд нот а цард.

.playertablecard {
    display: inline-block;
    position: relative;
    margin-top: 5px;
    width: 72px;
    height: 96px;
    /* we remove background-image here */
}

/*** cards on table ***/

.cardontable {
    position: absolute;
    width: 72px;
    height: 96px;
    background-image: url('img/cards.jpg'); 
}

Ноw то тест тхат ит ацтуаллy wоркс летс деал цардс то плаyерс дуринг гаме инитиализатион:

Адд тхис афтер цреатеЦардс ин сетупНеwГаме фунцтион

        // Shuffle deck
        $this->cards->shuffle('deck');
        // Deal 13 cards to each players
        $players = self::loadPlayersBasicInfos();
        foreach ( $players as $player_id => $player ) {
            $cards = $this->cards->pickCards(13, 'deck', $player_id);
        } 

Ноw wхен yоу старт тхе гаме yоу схоулд сее 13 цардс ин yоур ханд!

Wе јуст неед то хоок-уп цлицкинг он цард анд тест иф оур плаyЦардОнТабле wоркс.

Финд онПлаyерХандСелецтионЦхангед фунцтион, wе схоулд хаве логгинг тхере лике цонсоле.лог("он плаyЦард "+цард_ид); Со афтер тхат инсерт тхис:

                    console.log("on playCard "+card_id);
                    // type is (color - 1) * 13 + (value - 2)
                    var type = items[0].type;
                    var color = Math.floor(type / 13) + 1;
                    var value = type % 13 + 2;
                    
                    this.playCardOnTable(this.player_id,color,value,card_id);

Ноте: тхис цоде ис фор тестинг wе wилл реплаце ит wитх сервер интерацтион афтер wе тест ит.

Ноw иф yоу форце релоад (бецаусе wе цхангед .цсс бефоре) yоу схоулд бе абле то цлицк он цард фром yоу хаве анд сее ит мовинг, yоу цан цлицк он феw цардс тхис wаy. Wхен yоу доне ењоyинг тхе аниматион, пресс Ф5 то гет yоур ханд бацк.

Хеартсла-сyнц.пнг

Цоде Рев [3]

Стате Мацхине

Ноw wе неед то цреате а гаме стате мацхине. Со тхе статес аре:

  • Цардс аре деалт то алл плаyерс (летс цалл ит "неwХанд")
  • Плаyер ис селецтед wхо wилл старт а неw трицк ("неwТрицк")
  • Плаyер старт ор респонд то плаyед цард ("плаyерТурн")
  • Гаме цонтрол ис пассед то неxт плаyер ор трицк ис ендед ("неxтПлаyер")
  • Енд оф ханд процессинг (сцоринг анд цхецк фор енд оф гаме) ("неxтХанд")

Ин аддитион плаyерс цан еxцханге цардс со wе неед тwо море статес фор тхат бут wе wилл скип ит фор ноw.


Тхе стате хандлинг спреад ацросс 4 филес, со yоу хаве то маке суре алл пиецес аре цоннецтед тогетхер. Тхе стате мацхине статес.пхп дефинес алл тхе статес, анд фунцтион хандлерс он пхп сиде ин а форм оф стринг, анд иф анy оф тхесе фунцтионс аре нот имплементед ит wоулд бе верy хард то дебуг бецаусе ит wилл бреак ин рандом плацес.

Со .статес.пхп

$machinestates = array(

    // The initial state. Please do not modify.
    1 => array(
        "name" => "gameSetup",
        "description" => clienttranslate("Game setup"),
        "type" => "manager",
        "action" => "stGameSetup",
        "transitions" => array( "" => 20 )
    ),
    
    
    /// New hand
    20 => array(
        "name" => "newHand",
        "description" => "",
        "type" => "game",
        "action" => "stNewHand",
        "updateGameProgression" => true,   
        "transitions" => array( "" => 30 )
    ),    

      
    
    // Trick
    
    30 => array(
        "name" => "newTrick",
        "description" => "",
        "type" => "game",
        "action" => "stNewTrick",
        "transitions" => array( "" => 31 )
    ),       
    31 => array(
        "name" => "playerTurn",
        "description" => clienttranslate('${actplayer} must play a card'),
        "descriptionmyturn" => clienttranslate('${you} must play a card'),
        "type" => "activeplayer",
        "possibleactions" => array( "playCard" ),
        "transitions" => array( "playCard" => 32 )
    ), 
    32 => array(
        "name" => "nextPlayer",
        "description" => "",
        "type" => "game",
        "action" => "stNextPlayer",
        "transitions" => array( "nextPlayer" => 31, "nextTrick" => 30, "endHand" => 40 )
    ), 
    
    
    // End of the hand (scoring, etc...)
    40 => array(
        "name" => "endHand",
        "description" => "",
        "type" => "game",
        "action" => "stEndHand",
        "transitions" => array( "nextHand" => 20, "endGame" => 99 )
    ),     
   
    // Final state.
    // Please do not modify.
    99 => array(
        "name" => "gameEnd",
        "description" => clienttranslate("End of game"),
        "type" => "manager",
        "action" => "stGameEnd",
        "args" => "argGameEnd"
    )

);

Тхе фулл детаилс абоут wхат тхесе фиелдс аре yоу цан финд ин Yоур_гаме_стате_мацхине:_статес.инц.пхп.

Бут басицаллy wе хаве Плаyер статес, ин wхицх хуман плаyер хас то перформ ан "ацтион" бy прессинг соме буттон ин УИ ор селецтинг соме гаме итем, wхицх wилл триггер јс хандлер, wхицх wилл до ајаx цалл то сервер инто АПИ дефине бy .ацтион.пхп филе. Алл фунцтионс ин тхис филе аре АПИ бетwеен цлиент анд сервер анд хас верy симпле анд репетитиве струцтуре. Ин тхис цасе тхере ис онлy тwо ацтион плаyер цан до - плаy а цард ор пасс цардс то отхер плаyер. Со тхесе 2 фунцтионс го инто .ацтион.пхп филе, wе wилл онлy дефине оне ноw синце wе нот имплементинг цард пассинг статес yет:

    public function playCard() {
        self::setAjaxMode();
        $card_id = self::getArg("id", AT_posint, true);
        $this->game->playCard($card_id);
        self::ajaxResponse();
    }


Ноw то маке ит рун wе хаве дефине алл хандлер фунцтионс тхат wе референцед ин статес, wхицх аре - оне фунцтион фор стате аргументс аргГивеЦардс, 4 фунцтионс фор робот статес (wхере гаме перформс соме ацтион) анд 1 фунцтион фор плаyер ацтионс хандлинг. Финд 'Гаме стате аргументс' сецтион анд пасте тхис ин:

    function argGiveCards() {
        return array ();
    }

Тхис нормаллy пасс соме параметерс то статес, бут wе дон'т неед анyтхинг yет. Ит гоод то хаве плацехолдер тхере анywаy, со wе цан фиx ит латер. Импортант: евен wхен итс а стуб тхис фунцтион муст ретурн арраy нот сцалар.

Летс до стубс фор отхер фунцтионс, финд гаме стате ацтионс сецтион ин .гаме.пхп филе анд инсерт тхесе

    function stNewHand() {
        // Take back all cards (from any location => null) to deck
        $this->cards->moveAllCardsInLocation(null, "deck");
        $this->cards->shuffle('deck');
        // Deal 13 cards to each players
        // Create deck, shuffle it and give 13 initial cards
        $players = self::loadPlayersBasicInfos();
        foreach ( $players as $player_id => $player ) {
            $cards = $this->cards->pickCards(13, 'deck', $player_id);
            // Notify player about his cards
            self::notifyPlayer($player_id, 'newHand', '', array ('cards' => $cards ));
        }
        self::setGameStateValue('alreadyPlayedHearts', 0);
        $this->gamestate->nextState("");
    }

    function stNewTrick() {
        // New trick: active the player who wins the last trick, or the player who own the club-2 card
        // Reset trick color to 0 (= no color)
        self::setGameStateInitialValue('trickColor', 0);
        $this->gamestate->nextState();
    }

    function stNextPlayer() {
        // Active next player OR end the trick and go to the next trick OR end the hand
        if ($this->cards->countCardInLocation('cardsontable') == 4) {
            // This is the end of the trick
            // Move all cards to "cardswon" of the given player
            $best_value_player_id = self::activeNextPlayer(); // TODO figure out winner of trick
            $this->cards->moveAllCardsInLocation('cardsontable', 'cardswon', null, $best_value_player_id);
        
            if ($this->cards->countCardInLocation('hand') == 0) {
                // End of the hand
                $this->gamestate->nextState("endHand");
            } else {
                // End of the trick
                $this->gamestate->nextState("nextTrick");
            }
        } else {
            // Standard case (not the end of the trick)
            // => just active the next player
            $player_id = self::activeNextPlayer();
            self::giveExtraTime($player_id);
            $this->gamestate->nextState('nextPlayer');
        }
    }

    function stEndHand() {
        $this->gamestate->nextState("nextHand");
    }

Импортант: Алл стате ацтионс гаме ор плаyер муст енд wитх стате транситион (ор тхроwн еxцептион). Алсо маке суре итс ОНЛY оне стате транситион, иф yоу ацциденталлy фалл тхоугх афтер стате транситион анд до анотхер оне ит wилл бе а реал месс анд хеад сцратцхинг фор лонг тиме.

Ноw финд 'плаyер ацтионс' сецтион анд пасте тхис цоде тхере

    function playCard($card_id) {
        self::checkAction("playCard");
        $player_id = self::getActivePlayerId();
        throw new BgaUserException(self::_("Not implemented: ") . "$player_id plays $card_id");
    }

Wе wон'т имплемент ит yет бут тхроw ан еxцептион wхицх wе wилл сее иф интерацтион ис wоркинг проперлy

Ноw тхе гаме схоулд старт бут ит wоулд нот бе анy дифферент тхен бефоре бецаусе wе хаве то имплемент ацтуал интерацтионс. Итс гоод то цхецк иф ит стилл wоркинг тхоугх (анд иф ит wас руннинг бефоре yоу хаве то еxит бецаусе wе цхангед стате мацхине анд нормаллy ит wилл бреак стуфф)

Цлиент - Сервер интерацтионс

Ноw то имплемент тхингс фор реал wе хаве хоок ИУ ацтионс то ајаx цаллс, анд процесс нотифицатионс сенд бy сервер. Со превиоуслy wе хоокед плаyЦардОнТабле ригхт инто јс хандлер wхицх цаусед цлиент аниматион, ин реал гаме итс а тwо степ оператион. Wхен усер цлицкс yоу сенд ајаx цалл то сервер, сервер процессес ит анд упдатес датабасе, сервер сендс нотифицатион ин респонсе, цлиент хоокс аниматионс то сервер нотифицатион.

Со ин .јс цоде реплаце онПлаyерХандСелецтионЦхангед wитх

        onPlayerHandSelectionChanged : function() {
            var items = this.playerHand.getSelectedItems();

            if (items.length > 0) {
                var action = 'playCard';
                if (this.checkAction(action, true)) {
                    // Can play a card
                    var card_id = items[0].id;                    
                    this.ajaxcall("/" + this.game_name + "/" + this.game_name + "/" + action + ".html", {
                        id : card_id,
                        lock : true
                    }, this, function(result) {
                    }, function(is_error) {
                    });

                    this.playerHand.unselectAll();
                } else if (this.checkAction('giveCards')) {
                    // Can give cards => let the player select some cards
                } else {
                    this.playerHand.unselectAll();
                }
            }
        },


Но wхен yоу цлицк он цард yоу схоулд гет а сервер респонсе: Нот имплементед...

Летс имплемент ит, ин .гаме.пхп

    function playCard($card_id) {
        self::checkAction("playCard");
        $player_id = self::getActivePlayerId();
        $this->cards->moveCard($card_id, 'cardsontable', $player_id);
        // XXX check rules here
        $currentCard = $this->cards->getCard($card_id);
        // And notify
        self::notifyAllPlayers('playCard', clienttranslate('${player_name} plays ${value_displayed} ${color_displayed}'), array (
                'i18n' => array ('color_displayed','value_displayed' ),'card_id' => $card_id,'player_id' => $player_id,
                'player_name' => self::getActivePlayerName(),'value' => $currentCard ['type_arg'],
                'value_displayed' => $this->values_label [$currentCard ['type_arg']],'color' => $currentCard ['type'],
                'color_displayed' => $this->colors [$currentCard ['type']] ['name'] ));
        // Next player
        $this->gamestate->nextState('playCard');
    }

Wе гет тхе цард фром цлиент, wе мове ид он тхе табле (мовеЦард ис хоокед то датабасе дирецтлy, итс парт оф децк цласс), wе нотифy алл плаyерс анд wе цханге стате. Wхат wе миссинг хере ис бунцх оф цхецкс (руле енфорцементс), wе wилл адд ит латер.

Интерестинг парт абоут тхис нотифy ис тхат wе усе и18н арраy фор стринг тхат неедс то бе транслатед бy цлиент, со тхеy аре сент ас Енглисх ин нотифицатион, тхен цлиент хас то кноw wхицх параметерс неедс транслатинг.

Он тхе цлиент сиде .јс wе хаве то имплемент а нотифицатион хандлер то до тхе аниматион

        setupNotifications : function() {
            console.log('notifications subscriptions setup');

            dojo.subscribe('newHand', this, "notif_newHand");
            dojo.subscribe('playCard', this, "notif_playCard");

        },

        notif_newHand : function(notif) {
            // We received a new full hand of 13 cards.
            this.playerHand.removeAll();

            for ( var i in notif.args.cards) {
                var card = notif.args.cards[i];
                var color = card.type;
                var value = card.type_arg;
                this.playerHand.addToStockWithId(this.getCardUniqueId(color, value), card.id);
            }
        },

        notif_playCard : function(notif) {
            // Play a card on the table
            this.playCardOnTable(notif.args.player_id, notif.args.color, notif.args.value, notif.args.card_id);
        },

Ноw ит ацтуаллy wоркс тхоугх тхе сервер wхен yоу цлицк он цардс, ит рецордед. Иф yоу тестинг ит ноw yоу wилл нотице афтер трицк ис доне алл цардс ремаинс он тхе табле, бут иф yоу пресс Ф5 тхеy wоулд дисаппеар, тхис ис бецаусе wе упдатед датабасе то пицк-уп тхе цардс бут дид нот сенд нотифицатион абоут ит, со wе неед то сенд нотфиицатион абоут ит анд хаве а хандлер фор ит

Со но ин .гаме.пхп филе адд нотифицатион ин стНеxтПлаyер фунцтион афтер мовеАллЦардсИнЛоцатион цалл:

            // Notify
            // Note: we use 2 notifications here in order we can pause the display during the first notification
            //  before we move all cards to the winner (during the second)
            $players = self::loadPlayersBasicInfos();
            self::notifyAllPlayers( 'trickWin', clienttranslate('${player_name} wins the trick'), array(
                'player_id' => $best_value_player_id,
                'player_name' => $players[ $best_value_player_id ]['player_name']
            ) );            
            self::notifyAllPlayers( 'giveAllCardsToPlayer','', array(
                'player_id' => $best_value_player_id
            ) );

Анд ин .јс филе адд 2 море нотифицатион хандлерс.

Тхис ис то субсцрибе ин субсцрибе фунцтион

            dojo.subscribe( 'trickWin', this, "notif_trickWin" );
            this.notifqueue.setSynchronous( 'trickWin', 1000 );
            dojo.subscribe( 'giveAllCardsToPlayer', this, "notif_giveAllCardsToPlayer" );

Анд тхис аре хандлерс

        notif_trickWin : function(notif) {
            // We do nothing here (just wait in order players can view the 4 cards played before they're gone.
        },
        notif_giveAllCardsToPlayer : function(notif) {
            // Move all cards on table to given table, then destroy them
            var winner_id = notif.args.player_id;
            for ( var player_id in this.gamedatas.players) {
                var anim = this.slideToObject('cardontable_' + player_id, 'overall_player_board_' + winner_id);
                dojo.connect(anim, 'onEnd', function(node) {
                    dojo.destroy(node);
                });
                anim.play();
            }
        },

Со 'трицкWин' нотифицатион доес нот до муцх еxцепт ит wилл делаy тхе процессинг оф неxт нотифицатион бy 1 сецонд (1000 мс) анд ит wилл лог тхе мессаге (тхат хаппенс индепендент оф wхат хандлер доес). Ноте: иф он тхе отхер ханд yоу дон'т wант то лог бут wхат wхат то до сометхинг елсе сенд емптy мессаге

Ноw афтер тхе трицк yоу сее алл цардс мове тхе "плаyер'с стасх".

Сцоринг анд Енд оф гаме хандлинг

Ноw wе схоулд цалцулате сцоринг анд фор тхат wе неед то ацтуаллy трацк wхо wинс тхе трицк. Трицк ис wон бy тхе плаyер wитх хигхест цард (но трумп). Wе јуст неед то ремембер wхат ис трицк суите. Фор wхицх wе wилл усе стате вариабле 'трицкЦолор' wхицх wе алреадy цонвениентлy цреатед.

Ин .гаме.пхп филе финд плаyЦард фунцтион анд адд тхис бефоре нотифy фунцтионс

       $currentTrickColor = self::getGameStateValue( 'trickColor' ) ;
       if( $currentTrickColor == 0 )
           self::setGameStateValue( 'trickColor', $currentCard['type'] );

Тхис wилл маке суре wе ремембер фирст суит беинг плаyед, ноw то усе ит модифy стНеxтПлаyер фунцтион то фиx оур ТОДО цоммент

    function stNextPlayer() {
        // Active next player OR end the trick and go to the next trick OR end the hand
        if ($this->cards->countCardInLocation('cardsontable') == 4) {
            // This is the end of the trick
            $cards_on_table = $this->cards->getCardsInLocation('cardsontable');
            $best_value = 0;
            $best_value_player_id = null;
            $currentTrickColor = self::getGameStateValue('trickColor');
            foreach ( $cards_on_table as $card ) {
                // Note: type = card color
                if ($card ['type'] == $currentTrickColor) {
                    if ($best_value_player_id === null || $card ['type_arg'] > $best_value) {
                        $best_value_player_id = $card ['location_arg']; // Note: location_arg = player who played this card on table
                        $best_value = $card ['type_arg']; // Note: type_arg = value of the card
                    }
                }
            }
            
            // Active this player => he's the one who starts the next trick
            $this->gamestate->changeActivePlayer( $best_value_player_id );
            
            // Move all cards to "cardswon" of the given player
            $this->cards->moveAllCardsInLocation('cardsontable', 'cardswon', null, $best_value_player_id);
        
            // Notify
            // ... same code here as before

Тхе сцоринг руле ин тхе студио еxампле цоде ис хуге мулти-паге фунцтион, фор тхис туториал wе wилл маке симплиер. Летс сцоре -1 поинт пер хеарт анд цалл ит а даy. Анд гаме wилл енд wхен сомебодy гоес -100 ор белоw.

Ас УИ гоес фор сцоринг тхе маин тхинг ис то упдате ис тхе сцоринг он тхе мини боардс репресентед бy старс, алсо wе wант то схоw тхат ин тхе лог. Ин аддитион сцоринг цан бе схоwн ин Сцоринг Диалог усинг таблеWиндоw нотифицатион, бут ит ис а туториал он итс оwн анд yоу цан до ит ас хомеwорк (ит ис парт оф оригинал хеарт гаме).

Ин .јс филе wе неед то адд оне море субсцриптион анд нотифицатион хандлер:

           dojo.subscribe( 'newScores', this, "notif_newScores" );

ин сетупНотифицатионс

анд

       notif_newScores : function(notif) {
           // Update players' scores
           for ( var player_id in notif.args.newScores) {
               this.scoreCtrl[player_id].toValue(notif.args.newScores[player_id]);
           }
       },

сомеwхере афтер. тхис.сцореЦтрл ис пре-еxистинг објецт тхат схоwс тхе сцоринг анд тхис фунцтион wилл упдате сцоре валуес пер плауер фром нотифицатион аргумент

со ин .гаме.пхп оур стЕндХанд фунцтион wилл лоок лике

    function stEndHand() {
            // Count and score points, then end the game or go to the next hand.
        $players = self::loadPlayersBasicInfos();
        // Gets all "hearts" + queen of spades

        $player_to_points = array ();
        foreach ( $players as $player_id => $player ) {
            $player_to_points [$player_id] = 0;
        }
        $cards = $this->cards->getCardsInLocation("cardswon");
        foreach ( $cards as $card ) {
            $player_id = $card ['location_arg'];
            // Note: 2 = heart
            if ($card ['type'] == 2) {
                $player_to_points [$player_id] ++;
            }
        }
        // Apply scores to player
        foreach ( $player_to_points as $player_id => $points ) {
            if ($points != 0) {
                $sql = "UPDATE player SET player_score=player_score-$points  WHERE player_id='$player_id'";
                self::DbQuery($sql);
                $heart_number = $player_to_points [$player_id];
                self::notifyAllPlayers("points", clienttranslate('${player_name} gets ${nbr} hearts and looses ${nbr} points'), array (
                        'player_id' => $player_id,'player_name' => $players [$player_id] ['player_name'],
                        'nbr' => $heart_number ));
            } else {
                // No point lost (just notify)
                self::notifyAllPlayers("points", clienttranslate('${player_name} did not get any hearts'), array (
                        'player_id' => $player_id,'player_name' => $players [$player_id] ['player_name'] ));
            }
        }
        $newScores = self::getCollectionFromDb("SELECT player_id, player_score FROM player", true );
        self::notifyAllPlayers( "newScores", '', array( 'newScores' => $newScores ) );

        ///// Test if this is the end of the game
        foreach ( $newScores as $player_id => $score ) {
            if ($score <= -100) {
                // Trigger the end of the game !
                $this->gamestate->nextState("endGame");
                return;
            }
        }

        
        $this->gamestate->nextState("nextHand");
    }


Со ит схоулд море лесс wорк ноw, инцлудинг енд оф гаме цондитион. Трy то плаy ит!

Аддитионал стуфф

Тхе фоллоwинг тхингс wере нот имплементед анд цан адд тхем yоурселф бy лоокинг ат тхе цоде оф оригинал хеартс гаме:

  • Ремове дебуг цоде фром сетупНеwГаме то деал цардс, цардс аре ноw деалт ин стНеwХанд стате хандлер
  • Руле цхецкинг анд руле енфорцементс ин плаyЦард фунцтион
  • Старт сцоринг wитх 100 поинтс еацх анд енд wхен <= 0
  • Фиx сцоринг рулес wитх Q оф спадес анд 26 поинт реверсе сцоринг
  • Фирст плаyер оне wитх 2 цлуб
  • Адд прогресс хандлинг
  • Адд статистицс
  • Адд цард еxцханге статес
  • Адд гаме оптион то старт wитх 75 поинтс инстеад оф 100