Terraforming-mars: Move cards in HTML_data to Vue templates.

Created on 18 Mar 2020  Â·  17Comments  Â·  Source: bafolts/terraforming-mars

We should move all the cards in HTML_data to Vue templates. As a first pass we should store the HTML of the cards in separate files.

enhancement

Most helpful comment

I see your point @vincentneko but let me try explain why I think this approach makes sense:

The presentation logic is in the Card component, which already is a client side component. The presentation is then populated from card properties like tags, type, cost and so on. The details/description of a card is just another property. This property is HTML, as there is no better way to represent it.

Using this approach we combine everything that makes a specific card in one place and all information not specific to one card in another.

All 17 comments

What exactly needs to be done here? Currently we have the cards in HTML_data.ts like this:

[CardName.CUPOLA_CITY,`
        <div class="title background-color-automated ">Cupola City</div>
        <div class="price ">16</div>
        <div class="tag tag1 tag-building "></div>
        <div class="tag tag2 tag-city "></div>
        <div class="card-number">029</div>
        <div class="content ">
            <div class="requirements requirements-max ">max 9% O2</div>
            <div class="production-box production-box-size1a ">
                <div class="production-prefix ">&#x2796;&#xFE0E;</div><div class="energy production "></div>
                <div class="production-prefix ">&#x2795;&#xFE0E;</div><div class="money production ">3</div>
            </div>
            <div class="tile city-tile " style="margin-left:20px "></div>
            <div class="description ">
                (Oxygen must be 9% or less. Place a City tile. Decrease your Energy production 1 step and increase your MC production 3 steps.)
            </div>
        </div>
`],

in cards/CupolaCity.ts there is data like this:

export class CupolaCity implements IProjectCard {
    public cost: number = 16;
    public tags: Array<Tags> = [Tags.CITY, Tags.STEEL];
    public cardType: CardType = CardType.AUTOMATED;
    public name: CardName = CardName.CUPOLA_CITY;
    public canPlay(player: Player, game: Game): boolean {
      return game.getOxygenLevel() <= 9 + player.getRequirementsBonus(game) &&
        player.getProduction(Resources.ENERGY) >= 1 &&
        game.board.getAvailableSpacesForCity(player).length > 0;
    }
    public play(player: Player, game: Game) {
      return new SelectSpace(
          'Select a space for city tile',
          game.board.getAvailableSpacesForCity(player),
          (space: ISpace) => {
            game.addCityTile(player, space.id);
            player.setProduction(Resources.ENERGY,-1);
            player.setProduction(Resources.MEGACREDITS,3);
            return undefined;
          }
      );
    }

}

Should we add attributes cardNumber and template to the cards classes like this? Also we should move all the strings to an i18n file.

export class CupolaCity implements IProjectCard {
    public cost: number = 16;
    public tags: Array<Tags> = [Tags.CITY, Tags.STEEL];
    public cardType: CardType = CardType.AUTOMATED;
    public name: CardName = CardName.CUPOLA_CITY;
    public canPlay(player: Player, game: Game): boolean {
      return game.getOxygenLevel() <= 9 + player.getRequirementsBonus(game) &&
        player.getProduction(Resources.ENERGY) >= 1 &&
        game.board.getAvailableSpacesForCity(player).length > 0;
    }
    public play(player: Player, game: Game) {
      return new SelectSpace(
          i18n.select_a_space_for_city_tile',
          game.board.getAvailableSpacesForCity(player),
          (space: ISpace) => {
            game.addCityTile(player, space.id);
            player.setProduction(Resources.ENERGY,-1);
            player.setProduction(Resources.MEGACREDITS,3);
            return undefined;
          }
      );
    }
    public cardNumber: number = 29;
    public template: string = `
        <div class="requirements requirements-max ">max 9% O2</div>
        <div class="production-box production-box-size1a ">
            <div class="production-prefix ">&#x2796;&#xFE0E;</div><div class="energy production "></div>
            <div class="production-prefix ">&#x2795;&#xFE0E;</div><div class="money production ">3</div>
        </div>
        <div class="tile city-tile " style="margin-left:20px "></div>
        <div class="description ">
            ${i18n.cupola_city}
        </div>`

}

I'm not a Vue expert, so I need help in how to use the template in the game.

I had a similar idea to what you are describing, @funkybaboon, and have already implemented parts of it as a proof of concept. I hope the approach will also enable us to implement further features like #776 in the future.

I will try cleaning things up a bit and submit a PR later today. I think it's easier to discuss it that way and see what I forgot about.

Implemented the gist of @funkybaboon's idea in pull request #796 and looking forward to your comments. It would be especially great if you, @bafolts, could have a look at it to see if this aligns with your original intention.

I think the original idea from @bafolts is to keep the server logic and client/presentation logic in 2 separate areas.
Adding html data to the server side cards is not going into this direction.
It may also add some extra processing for the server (not sure about this)

The idea here is to have a Vue js component for every card instead of a generic Card component.

Can you give us some hints how we should do this?

I'm not a Vue JS expert at all.
I would say that the card component needs to be modified to nest another dynamic component that will be the card name.

Something like:
<component v-bind:is="cardName"></component>

instead of
<div :class="cardNameToCssClass(card)" v-i18n v-html=this.getData()></div>

from the Vue Js doc

I see your point @vincentneko but let me try explain why I think this approach makes sense:

The presentation logic is in the Card component, which already is a client side component. The presentation is then populated from card properties like tags, type, cost and so on. The details/description of a card is just another property. This property is HTML, as there is no better way to represent it.

Using this approach we combine everything that makes a specific card in one place and all information not specific to one card in another.

@gomfunkel but now in your PR you are trying to introduce content field. It is a presentation logic mixed with data on server side.

I have no really good ideas of how to implement it. But one of the variants is to make one universal template for all the cards. And configure each individual card by css rules.

You can make a template for green, blue and red cards, along with tags, title, and price. You can't make one universal template, as the content and content formatting differs too much, f.ex.
Screenshot 2020-06-17 at 13 44 23
Screenshot 2020-06-17 at 13 43 48

We can hide all text blocks in popups like it is done in official app.
All icons seems to be just blocks with fixed or display-block positioning.

@gomfunkel but now in your PR you are trying to introduce content field. It is a presentation logic mixed with data on server side.

Yes, because imho this is a _property_ of a card, just like price and tags and so on. It awfully is rather complex HTML, not a simple type like a number for the price.

Are we able to reach a decision regarding this discussion? No matter what the decision is, I think we need it to continue implementation of either the idea from @funkybaboon / my PR or something else.

@bafolts: Do you have an opinion on this?

The original goal was to get us out of our mono-file of HTML_data. Whether the logic is client side or server side wasn't of concern. Some cards are very different but most cards share common properties. Almost every card will have a cost, name, and tags. From there it gets a little more creative with the description and text of the card.

Without a template today any project that requires updating something like cost will require us to update every card in HTML_data. This was my original concern and intention when I created this issue.

Takins the CUPOLA_CITY example already in this post. Ideally it would become a template which could be used like.

<card :card=card :resource=resource :player=player></card>

How we called the card would not change. What happened inside the template would change. I would recommend creating some mechanism that would allow us to port the cards from HTML_data over one at a time as it will be cumbersome to migrate them all. If we determine that we have ported the card to use the template we would then run it through some template which takes our current HTML and does something like:

<div>
        <div class="title background-color-{{getCardColor(card)}}">{{ card.name }}</div>
        <div class="price ">{{card.cost}}</div>
        <div class="tag tag1 tag-building "></div>
        <div class="tag tag2 tag-city "></div>
        <div class="card-number">{{ card.number }}</div>
        <div class="content ">
            <div class="requirements requirements-max " v-if="hasRequirements">max 9% O2</div>
            <div class="production-box production-box-size1a " v-if="hasProduction">
                <div class="production-prefix " v-if="hasEnergyProduction">&#x2796;&#xFE0E;</div><div class="energy production "></div>
                <div class="production-prefix " v-if="hasMegaCreditProduction">&#x2795;&#xFE0E;</div><div class="money production ">3</div>
            </div>
            <div v-if="showCityTile" class="tile city-tile " style="margin-left:20px "></div>
            <div class="description ">{{ card.description }}</div>
        </div>
</div>

Above is only a rough example and not fully flushed out. Some of the information like description, requirements, production, and cardNumber we don't have on the client and we don't care about on the server. The card component class may still contain information for every card but the HTML would at least only be generated once.

// Somewhere in the card component class

interface CardMetadata {
   public description: string;
   public cardNumber: number;
   public requirements: CardRequirements;
}

const cardMetadata = new Map<CardName, CardMetadata>([[
    CardName.CUPOLA_CITY, { description: "This is cupola city card", cardNumber: 29, requirements: [{type: "max", desc: "oxygen 9%"}] }
]]);

// Inside the render method of card component.

// use the new template since we have metadata for the card
if (cardMetadata.has(card.name)) {
 // use card template with client side metadata
} else {
 // pull from HTML_data like we do today
}

Eventually as all the cards were ported to CardMetadata our build size should decrease as we wont have all the duplicate HTML. Updating every card should become much simpler as well. I would start with a few cards at a time with a pattern like this as the CardMetadata interface will most likely change as we add cards since some cards are very unique. Inevitably we will end up with in our template a one off for some of the promo cards which are very different.

I'm interested in this, as I've found myself dealing with cross-referencing a card's HTML to its coded behavior many times. A shared CardMetadata would help no matter whether the card was generated by template, or HTML. As two examples, cost and tags are really rather easy to combine.

Now, should this be a Vue component? Sure! But even as a stepping stone, that CardMetadata can be used to generate HTML in HTML_data.

Excuse me for not reading all of the content above, it's close to 2AM here, but this also opens up simpler and more reliable card behavior, too. CardMetadata like:

play: [{type: ResourceType.HEAT, scale: Scale.PRODUCTION, units: 7}]

can be run through an algorithm that knows to give the project funder 7 heat production. O_O

I will give this a trial run on one card to introduce the potential pattern and get a pull request up. After that I can do the busy work to migrate the rest of the cards.

Great. I'm also happy to help in any way with this one - I feel like this
will be a huge help for future expansions and ongoing maintenance and
reliability. Just let me know.

On Wed, Sep 30, 2020, 11:00 AM Brian Folts notifications@github.com wrote:

I will give this a trial run on one card to introduce the potential
pattern and get a pull request up. After that I can do the busy work to
migrate the rest of the cards.

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/bafolts/terraforming-mars/issues/412#issuecomment-701447104,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AADE6KLIVYUI3JAGQB5WD7LSINBXHANCNFSM4LOAHA4A
.

I should have a script extracting all card data around to put them into individual files. This might help reducing the amount of manual work so I‘m looking forward to your pull request and see if it could help reducing the busy work.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dabewi picture dabewi  Â·  5Comments

duckmammal picture duckmammal  Â·  4Comments

ATiredVegan picture ATiredVegan  Â·  5Comments

dabewi picture dabewi  Â·  3Comments

cdekker picture cdekker  Â·  3Comments