首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >基于卡片的电脑游戏

基于卡片的电脑游戏
EN

Code Review用户
提问于 2018-06-11 17:01:45
回答 1查看 438关注 0票数 10

这是一个2人(很快就会有人工智能-我不要求帮助人工智能)的游戏与类似的纸牌游戏名为魔术的聚会。请原谅我,这是很多代码。如果您觉得需要一次检查一个文件,请这样做。

如果您想了解代码是如何工作的,代码是托管的这里

当你第一次打开游戏时,它应该是这样的:

那是开始屏幕。要开始你的回合,选择一张牌,点击左边的牌名,但最接近“转弯:天使”。当你这样做的时候,这个名字应该会变得更亮一些。这意味着卡被选中了。你现在可以通过点击敌人的名字来攻击敌人的牌。

牌每轮只能攻击一次。只有当你拥有更多的法力值时,牌才能攻击(法力是左上角和右边的蓝色条,牌的法力值是生命值左下角的蓝色矩形)卡攻击是由在生命栏下的红色矩形显示的。在右边的蓝色矩形上有一个数字的卡片会产生法力。这主要限于土地。

以下是相关档案:

代码语言:javascript
复制
┊
├Angels-And-Demons
│ ├data
│ │ ├DarkHand.js
│ │ ├LightHand.js
│ │ └styles.css
│ ├lib
│ │ └vue.js
│ ├src
│ │ ├BaseClasses.js
│ │ ├components.js
│ │ ├Dark.js
│ │ ├expandWeights.js
│ │ ├Land.js
│ │ ├Crippler.js
│ │ └Light.js
│ └game.html
┊

以下是文件:

data/DarkHand.js

代码语言:javascript
复制
var enemyWeights = [
  [Cuthulu, 1],
  [Demon, 2],
  [Ravine, 20],
  [HellHound, 3],
  [ThornKnight, 3],
  [DarkAgent, 3]
];

数据/LightHand.js

代码语言:javascript
复制
var playerWeights = [
  [EternalFlame, 1],
  [Angel, 2],
  [Mountain, 20],
  [Paladin, 3],
  [Priest, 3],
  [Lamp, 3]
];

数据/类型/data

代码语言:javascript
复制
body {
  background-color: #222222
}
div {
  display: inline-block;
}
.discard, .summonOrAttack {
  color: White;
}
.manaBar  {
   height:20px;
   background-color: Blue;
   color: White;
   text-align: center;        
}
.name {
  width: 80%;
}
.tagList {
  width:100%;
  margin: 4 auto;
}
.tagList * {
  width: 20%; 
  height:20px;
  color: White;
  padding: 2%;
  border-radius:4px;
  text-align: center;
}
.tagList .manaCost {
   background-color: MediumBlue;
}
.tagList .attack {
  background-color: DarkRed;
}
.tagList .manaPerRound {
  background-color: Blue;
}
table {
  display: inline-table;
  width: 100%;
}
.health {
  height: 20px;
  text-align: center;
}
#playerHand, #playerUnits, #enemyUnits, #enemyHand  {
  width: 20%;
  text-align: center;
}
.card {
  text-align: center;
  width: 100%;
}
#turnStats, #turn {
  color: White;
  text-align: center;
}

lib/vue.js

一份vue的本地副本,这样我就可以脱机处理它了。

src/BaseClasses.js

代码语言:javascript
复制
class TurnManager {
  constructor() {
    this.turnNumber = -1;
  };
  nextTurn() {
    this.turnNumber++;
  };
};
class Effect {
  constructor(value, turns, name, netValueWhenDone) {
    Object.assign(this, {
      value: value,
      turns: turns,
      name: name,
      netValueWhenDone: netValueWhenDone
    });
  }
  apply(card) {
    card.activeEffects[this.name] = {}
    Object.assign(card.activeEffects[this.name], {
       value: this.value,
       turns: this.turns,
       netValueWhenDone: this.netValueWhenDone,
       remainingTurns: this.turns,
       name: this.name
    })
  }
}
class Deck {
  constructor(isHand, manaManager, weights) {
    var cards = {}
    Object.assign(this, {
      hasHadCardAdded: false,
      cards: Array(10).fill().map( () => new BlankCard ),
      currentId: 1,
      isHand: isHand,
      manaManager: manaManager,
      selectedCardID: -1,
      weights: weights ? expandWeights(weights) : undefined
    });
    if (manaManager) {
      this.hand = this.isHand ? this : this.manaManager.deck;
      this.deck = this.isHand ? this.manaManager.deck : this;
    };
  };
  sealCards() {
    Object.seal(this.cards);
  };
  updateEffects() {
    this.cards.forEach( (card) => {
      Object.values(card.activeEffects).forEach( (effect) => {
        let isNew = effect.turns === effect.remainingTurns
        if( isNew ) {
          card[effect.name] += effect.value;
        } else if ( effect.remainingTurns === 0 ) {
          card[effect.name] -= effect.value - effect.netValueWhenDone;
          delete card.activeEffects[effect.name]
        };
        effect.remainingTurns--;
      });
    });
  };
  addCardFromWeights() {
    var newCard = getRandomItem(this.weights);
    this.addCards(new newCard(this.hand, this.deck));
    this.weights.splice(this.weights.indexOf(newCard), 1);
  };
  get selectedCard() {
    return this.cards[this.selectedCardID];
  };
  enableEnemyDeck() {
    this.enemyDeck.ArrayOfCardIDs.forEach((cardID) => {
      this.enemyDeck.cards[cardID].locked = false;
    });
  };
  disableEnemyDeck() {
    this.enemyDeck.ArrayOfCardIDs.forEach((cardID) => {
      this.enemyDeck.cards[cardID].locked = true;
    });
  };
  get ArrayOfCards() {
    return Object.values(this.cards);
  };
  get ArrayOfCardIDs() {
    return Object.keys(this.cards);
  };
  attack() {
    // the opponent is always the person who attacks.

    var opponentCardID = this.enemyDeck.selectedCardID,
      opponentCard = this.enemyDeck.selectedCard,
      yourCardID = this.selectedCardID,
      yourCard = this.selectedCard;

    if (yourCardID + 1 && opponentCardID + 1 && !opponentCard.used && !yourCard.isLand && !yourCard.isPrimal) {
      opponentCard.health -= (yourCard.attack === "N/A" ? 0 : yourCard.attack);
      yourCard.health -= (opponentCard.attack === "N/A" ? 0 : opponentCard.attack);
      Object.assign(opponentCard, {
        used: true,
        selected: false
      });
      if( opponentCard.effects ) {
        Object.values(opponentCard.effects).forEach( (item) => {
          item.apply(yourCard)
        })
      }
      this.enemyDeck.selectedCardID = -1;
      this.selectedCardID = -1;
      this.enemyDeck.manaManager.mana -= opponentCard.manaCost;
      this.ArrayOfCards.forEach((card) => {
        if (card.health <= 0 && card.health !== null) {
          this.removeCards(card);
        };
      });
      if( this.hasHadCardAdded && this.ArrayOfCards.every( card => (card instanceof BlankCard || card instanceof Land)) ) {
        this.enemyDeck.win()
      }
    };
  };
  win() {
    if(this === enemyDeck ) {
      alert("Enemy wins!");
    } else if ( this === playerDeck ) {
      alert("Player wins!");
    } else {
      alert("Cat?")
    };
    gameOver = true;
  };
  addCards(...cards) {
    if( cards.some( (card) => {
      return !(card instanceof Land) && !(card instanceof BlankCard)
      })) {
      this.hasHadCardAdded = true;
    };
    var emptyCardIDs = this.cards.filter( item => item.name === null ).map( item => this.cards.indexOf(item));
    cards.forEach( (card) => {
      this.isHand ? card.inHand = true : card.inHand = false;
      card.ID = emptyCardIDs[0];
      this.cards[emptyCardIDs[0]].propogate(card);
      emptyCardIDs.shift();
    });
  };
  removeCards(...cards) {
    cards.forEach((card) => {
       this.cards[card.ID].propogate(new BlankCard);
    });
  };
  Lockdown(...cards) {
    this.ArrayOfCardIDs.forEach((cardID) => {
      this.cards[cardID].locked = true;
    });
    cards.forEach((item) => {
      this.cards[item.ID].locked = false;
    });
  };
  OpenUp() {
    this.ArrayOfCardIDs.forEach((cardID) => {
      this.cards[cardID].locked = false;
    });
  };
};
class Card {
  constructor(maxHealth, attack, nameColor, manaCost, name, inHand, hand, deck, manaPerTurn) {
    Object.assign(this, {
      maxHealth: maxHealth,
      health: maxHealth,
      attack: attack,
      name: name,
      nameColor: nameColor,
      inHand: inHand,
      deck: deck,
      manaCost: manaCost,
      manaManager: deck.manaManager,
      manaPerTurn: manaPerTurn,
      hand: hand,
      decks: [playerDeck, enemyDeck],
      selected: false,
      locked: false,
      used: false,
      activeEffects: {}
    });
  };
  discard() {
    if( this.isDecksTurn ) {
      if( confirm("Are you sure you wish to discard this card?") ) {
        if( this.inHand ) {
          this.hand.removeCards(this);
        } else {
          this.deck.removeCards(this);
        };
      };
    };
  };
  copy() {
    return Object.setPrototypeOf(Object.assign({}, this), this.__proto__);
  };
  get indexInDecks() {
    return this.decks.indexOf(this.deck);
  };
  get isDecksTurn() {
    // true means it is... and false means it is not.
    return (turnManager.turnNumber % 2 === this.decks.indexOf(this.deck));
  }
  get isLand() {
    return this instanceof Land;
  }
  get isPrimal() {
    return this instanceof Primal;
  }
  onclick() {
    if (!this.used) {
      if (this.isDecksTurn) {
        if (this.manaManager.mana >= (this.manaCost === "N/A" ? 0 : this.manaCost) && !this.isLand) {
          if (this.isPrimal) {
            enemyWins();
          } else if (!this.selected) {
            this.toggleSelected()
            this.deck.Lockdown(this);
            this.deck.enableEnemyDeck();
            this.deck.selectedCardID = this.ID;
          } else if (this.selected) {
            this.toggleSelected();
            this.deck.OpenUp();
            this.deck.disableEnemyDeck();
            this.deck.selectedCardID = -1;
          };
        };
      } else if (!this.isDecksTurn && this.deck.enemyDeck.selectedCardID + 1) {
        this.deck.selectedCardID = this.ID;
        this.deck.enemyDeck.OpenUp();
        this.deck.attack();
      };
    };
  };
  propogate(card) {
    Object.assign(this, card);
    Object.setPrototypeOf(this, card.__proto__)
  }
  summon() {
    if (this.summonCost <= this.manaManager.mana && turnManager.turnNumber % 2 === this.decks.indexOf(this.deck)) {
      if (confirm('Are you sure you want to summon this card?')) {
        this.hand.manaManager.mana -= this.summonCost === "N/A" ? 0 : this.summonCost;
        Object.assign(this, {
          inHand: false,
          used: true // summoning sickness
        });
        this.deck.addCards(this.copy());
        this.hand.removeCards(this);
      };
    };
  };
  toggleSelected() {
    Object.assign(this, {
      selected: !this.selected
    });
  };
  get style() {
    return `width: ${Math.floor(this.health / this.maxHealth * 100)}; background-color: ${this.barColor};`;
  };
  get barColor() {
    var r = 255 - (this.health / this.maxHealth) * 255;
    var g = (this.health / this.maxHealth) * 255;
    return `rgb(${Math.floor(r)}, ${Math.floor(g)}, 0)`;
  };
  get id() {
    return this.ID;
  };
};
class BlankCard extends Card {
  constructor() {
    super(null,null,null,null,null,null,null,{manaManager:null},null,null)
  }
}
class ManaManager {
  constructor(deck) {
    this.mana = 0;
    this.deck = deck;
  };
  get maxMana() {
    return 20 + this.deck.ArrayOfCards.map((i) => (i instanceof Land ? i.manaPerTurn : 0)).reduce((totalManaPerTurn, cardManaPerTurn) => {
      return totalManaPerTurn + cardManaPerTurn;
    });
  };
  get manaBarWidth() {
    return Math.floor(this.mana / this.maxMana * 100);
  };
  get manaPerTurn() {
    var result = this.deck.ArrayOfCards.reduce((accumulator, card) => {
      return accumulator + (card.manaPerTurn === "N/A" ? 0 : card.manaPerTurn);
    }, 0);
    return result;
  };
  set manaGain(mana) {
    this.mana += mana;
    if (this.mana > this.maxMana) {
      this.mana = this.maxMana;
    };
  };
};
class Primal extends Card {
  constructor(hand, deck, name) {
    super(null, "N/A", "#DD00DD", 30, name, true, hand, deck, "N/A");
    this.summonCost = 0;
  };
};
class Land extends Card {
  constructor(manaPerTurn, name, nameColor, inHand, hand, deck) {
    super(null, "N/A", nameColor, "N/A", name, deck.isHand, hand, deck, manaPerTurn);
    this.summonCost = 0;
  };
};

src/components.js

代码语言:javascript
复制
Vue.component('card', {
  'template': ` 
                 
                   
                         {{ item.name }}
                   
                    D 
                  
                 
 
                  
                   {{item.health}} 
                  
                 
 
                  
                   {{ item.inHand ? item.summonCost : item.manaCost }}
                   {{ item.attack }} 
                   {{ item.manaPerTurn }} 
                  
               
               
`,
   'props': {
              'item': Card,
              'turnManager': TurnManager, 
              'colors': Object,
              'gameOver': Boolean
            }
});

src/Dark.js

代码语言:javascript
复制
class EnemyUnit extends Card {
   constructor(summonCost, maxHealth, attack, nameColor, name, manaCost, hand=enemyHand, deck=enemyDeck, manaPerTurn="N/A", manaManager=enemyManaManager, inHand=false) {
     super(maxHealth, attack, nameColor, manaCost, name, inHand, hand, deck, manaPerTurn);
     this.summonCost = summonCost || 0
  };
};
class Demon extends EnemyUnit {
  constructor(hand, deck) {
    super(10, 100, 10, "#DFB720", "Demon", 7, hand, deck);
  };
};
class HellHound extends EnemyUnit {
  constructor(hand, deck) {
    super(5, 60, 5, "#C0C0C0", "Hell Hound", 4, hand, deck);
  };
};
class Cuthulu extends Primal {
  constructor() {
    super(enemyHand, enemyDeck, "Cuthulu");
  };
};
class ThornKnight extends EnemyUnit {
  constructor(hand, deck) {
    super(5, 20, 5, "#DF5F30", "Thorn Knight", 3, hand, deck);
  };
};

src/expandWeights.js

代码语言:javascript
复制
function expandWeights(weights) {
  let result = [];
  weights.forEach( (item) => {
    let weightedItem = item[0];
    for(var i = 0; i < item[1]; i++) {
      result.push(weightedItem);
    };
  });
  return result;
};

src/Land.js

代码语言:javascript
复制
class Stone extends Land {
  constructor(hand, deck, name) {
    super(1, name, '#B0C4DE', deck.isHand, hand, deck);
  };
};
class Mountain extends Stone {
  constructor(hand=playerHand, deck=playerDeck) {
    super(hand, deck, "Mountain");
  };
};
class Ravine extends Stone {
  constructor(hand=enemyHand, deck=enemyDeck) {
    super(hand, deck, "Ravine");
  };
};

src/Crippler.js

代码语言:javascript
复制
class Crippler extends Card {
  constructor(hand, deck, name, effects, maxHealth, summonCost) {
    super(maxHealth, "N/A", "#C0C0C0", 9, name, true, hand, deck, "N/A");
    this.summonCost = summonCost || 0;
    this.effects = effects;
  };
};
class DarkAgent extends Crippler {
  constructor(hand=enemyHand, deck=enemyDeck) {
    super(hand, deck, "Dark Agent", {
      manaCost: new Effect(3, 3, "manaCost", 1),
      health: new Effect(-10, 3, "health", -5),
      attack: new Effect(-3, 3, "attack", -5)
    }, 20, 10);
  };
};
class Lamp extends Crippler {
  constructor(hand=enemyHand, deck=enemyDeck) {
    super(hand, deck, "Lamp", {
      manaCost: new Effect(3, 3, "manaCost", 1),
      health: new Effect(-10, 3, "health", -5),
      attack: new Effect(-3, 3, "attack", -5)
    }, 20, 10);
  };
};

src/Light.js

代码语言:javascript
复制
class PlayerUnit extends Card {
  constructor(summonCost, maxHealth, attack, nameColor, name, manaCost, hand=playerHand, deck=playerDeck, manaPerTurn="N/A",manaManager=playerManaManager, inHand=false) {
    super(maxHealth, attack, nameColor, manaCost, name, inHand, hand, deck, manaPerTurn);
    this.summonCost = summonCost || 0;
  };
};
class EternalFlame extends Primal {
  constructor() {
    super(playerHand, playerDeck, "Eternal Flame");
  };
};
class Angel extends PlayerUnit {
  constructor(hand, deck) {
    super(10, 100, 10, "#DFB720", "Angel", 7, hand, deck);
  };
};
class Paladin extends PlayerUnit {
  constructor(hand, deck) {
    super(5, 60, 5, "#C0C0C0", "Paladin", 4, hand, deck);
  };
};
class Priest extends PlayerUnit {
  constructor(hand, deck) {
    super(5, 20, 5, "#DF5F30", "Priest", 3, hand, deck);
  };
};

game.html

代码语言:javascript
复制
    Angels & Demons
    
    
    
    
    
    
    
    
    
    
  
  
  
    
          
             {{ playerMana.mana }} 
          
          
             {{ enemyMana.mana }} 
          
          


          
             
          
          
                
          
          
             Turn: {{ turnManager.turnNumber % 2 ? "Demons" : "Angels" }} 
            End turn.
          
          
            
          
          
            
          
      
    
    
      const turnManager = new TurnManager;
      var gameOver = false;
      function getRandomItem(array){
        return array[Math.floor(Math.random()*array.length)]
      };

      function nextTurn(){
        turnManager.nextTurn();
        if( turnManager.turnNumber % 2 === 0 ) {
          playerManaManager.manaGain = playerManaManager.manaPerTurn;
          try{playerHand.addCardFromWeights()}catch(e){};
          playerDeck.updateEffects()
        } else if ( turnManager.turnNumber % 2 === 1 ) {
          enemyManaManager.manaGain = enemyManaManager.manaPerTurn;
          try{enemyHand.addCardFromWeights()}catch(e){};
        };
        playerDeck.ArrayOfCardIDs.forEach( (cardID) => {
          Object.assign(playerDeck.cards[cardID], {
            used: false,
            selected: false,
            locked: false
          });
        });
        playerDeck.selectedCardID = -1;
        enemyDeck.selectedCardID = -1;
        enemyDeck.ArrayOfCardIDs.forEach( (cardID) => {
          Object.assign(enemyDeck.cards[cardID], {
            used: false,
            selected: false,
            locked: false
          });
        });
      };
      const DarkerColors = {
        "#DFB720": "#BF5700",
        "#B0C4DE": "#90A4CE",
        "#C0C0C0": "#A0A0A0",
        "#DD00DD": "#BB00BB",
        "#DF5F30": "#BF3F10"
      },
           LighterColors = {
        "#DFB720": "#FFD940",
        "#B0C4DE": "#D0E4FE",
        "#C0C0C0": "#E0E0E0",
        "#DD00DD": "#FD00FD ",
        "#DF5F30": "#FF7F50"
      }
      var playerDeck = new Deck(false, null, playerWeights),
          enemyDeck = new Deck(false, null, enemyWeights),
          enemyHand = new Deck(true, new ManaManager(enemyDeck), enemyWeights),
          playerHand = new Deck(true, new ManaManager(playerDeck), playerWeights),
          playerManaManager = playerHand.manaManager,
          enemyManaManager = enemyHand.manaManager;
      Object.assign(enemyDeck, {
        manaManager: enemyManaManager,
        enemyDeck: playerDeck
      });
      playerDeck.manaManager = playerManaManager;
      playerDeck.enemyDeck = enemyDeck;
      enemyDeck.Lockdown()

      new Vue({
        el: "#game",
        data: {
          playerCards: playerDeck.cards,
          enemyCards: enemyDeck.cards,
          playerHand: playerHand.cards,
          enemyHand: enemyHand.cards,

          playerMana: playerManaManager,
          enemyMana: enemyManaManager,
          colors: {
            DarkerColors: DarkerColors,
            LighterColors: LighterColors
          },

          turnManager: turnManager,
          gameOver: gameOver
        }
      });
      playerDeck.sealCards();
      playerHand.sealCards();
      enemyDeck.sealCards();
      enemyHand.sealCards();
      nextTurn();
EN

回答 1

Code Review用户

回答已采纳

发布于 2018-06-28 10:39:20

免责声明:我将不时地进入C#语法,因为我很少有直接的类型记录经验。我关注的是这个原则,而不是实际的语法。

类滥用

这是您在代码库中详细说明的内容:

代码语言:javascript
复制
class Demon extends EnemyUnit {
  constructor(hand, deck) {
    super(10, 100, 10, "#DFB720", "Demon", 7, hand, deck);
  };
};
class HellHound extends EnemyUnit {
  constructor(hand, deck) {
    super(5, 60, 5, "#C0C0C0", "Hell Hound", 4, hand, deck);
  };
};

这些继承的类实际上与它们的EnemyUnit基类并没有什么不同。它们不应该是属于自己的类。

这相当于做以下事情:

代码语言:javascript
复制
class Number {
    constructor(value) {
        this.Value = value;
    }
}

class Five extends Number {
    constructor() { 
        super(5)
    }
}

class Twenty extends Number {
    constructor() {
        super(20)
    }
}

var myAge = new Twenty();
var fingersOnMyLeftHand = new Five();

我的例子更明目张胆,但这是同样的原则。

当您想要更改值时,不应该创建新的类。拥有一个类的目的是要有一个可以包含不同值的可重用类型。

就像我的例子应该重写为:

代码语言:javascript
复制
myAge = Number(20);
fingersOnMyLeftHand = Number(5);

您的代码应该重写为:

代码语言:javascript
复制
function CreateDemon(hand, deck) {
    return new EnemyUnit(10, 100, 10, "#DFB720", "Demon", 7, hand, deck);
}

function CreateHellHound(hand, deck) {
    return new EnemyUnit(5, 60, 5, "#C0C0C0", "Hell Hound", 4, hand, deck);
}

不要将继承用作创建某些预设值的方法。这不是继承的目的。

只有当您希望创建一个新的单元,其行为与现有EnemyUnit类型的行为不同时,才会继承。(例如,攻击总是等同于健康,或“英雄”单位类型)。

特别是对于游戏设计,您将看到大多数游戏倾向于不为不同的单元类型继承,而是拥有一个具有可省略属性的单一单元类型。例如,如果您同时拥有物理装甲和魔法盔甲,则不需要为它们创建单独的类。只需同时赋予EnemyUnit PhysicalArmorMagicalArmor属性,如果当前敌人没有特定类型的装甲,只需将其设置为0即可。

一个非常小的评论,是未定义,而不是Cuthulu。不过,它的发音是"Cuthulu“。

代码语言:javascript
复制
class TurnManager {
  constructor() {
    this.turnNumber = -1;
  };
  nextTurn() {
    this.turnNumber++;
  };
};

这并不一定是错误的,但是为什么turnNumber设置为-1呢?

我怀疑您是在一个零索引环境中进行思考,并且在游戏开始时依赖于调用nextTurn()。但我认为这里有更直观的方法:

  • 纸牌游戏从第1轮开始,而不是0场。不要混合代码隐藏索引和用户界面编号。
    • 例如,我认为turnList[oneIndexedTurnNumber-1]比执行逆alert("Starting turn " + (zeroIndexedTurnNumber+1))"更易读。从技术角度来看,两者都是正确的,但是第一个片段的上下文(使用它作为数组索引)立即解释了为什么要执行-1。第二个片段没有立即解释为什么要执行+1 (当然,假设变量名实际上不包含“零索引”)。

  • 为此,我建议一个严格的命名约定:数字是1-索引,索引是0-索引。这意味着您需要将您的字段重命名为turnIndex,或者重新工作以从1开始。
  • 我不会在游戏开始时(和回合开始时)调用nextTurn(),而是在回合结束后调用。这在语义(和逻辑上)上更有意义。举个简单的例子:for循环不让它的索引从-1开始,然后在第一次迭代之前自动递增。这是违反直觉的行为。
票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/196291

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档