这是一个2人(很快就会有人工智能-我不要求帮助人工智能)的游戏与类似的纸牌游戏名为魔术的聚会。请原谅我,这是很多代码。如果您觉得需要一次检查一个文件,请这样做。
如果您想了解代码是如何工作的,代码是托管的这里。
当你第一次打开游戏时,它应该是这样的:

那是开始屏幕。要开始你的回合,选择一张牌,点击左边的牌名,但最接近“转弯:天使”。当你这样做的时候,这个名字应该会变得更亮一些。这意味着卡被选中了。你现在可以通过点击敌人的名字来攻击敌人的牌。
牌每轮只能攻击一次。只有当你拥有更多的法力值时,牌才能攻击(法力是左上角和右边的蓝色条,牌的法力值是生命值左下角的蓝色矩形)卡攻击是由在生命栏下的红色矩形显示的。在右边的蓝色矩形上有一个数字的卡片会产生法力。这主要限于土地。
以下是相关档案:
┊
├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
var enemyWeights = [
[Cuthulu, 1],
[Demon, 2],
[Ravine, 20],
[HellHound, 3],
[ThornKnight, 3],
[DarkAgent, 3]
];数据/LightHand.js
var playerWeights = [
[EternalFlame, 1],
[Angel, 2],
[Mountain, 20],
[Paladin, 3],
[Priest, 3],
[Lamp, 3]
];数据/类型/data
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
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
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
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
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
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
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
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
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();发布于 2018-06-28 10:39:20
免责声明:我将不时地进入C#语法,因为我很少有直接的类型记录经验。我关注的是这个原则,而不是实际的语法。
这是您在代码库中详细说明的内容:
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基类并没有什么不同。它们不应该是属于自己的类。
这相当于做以下事情:
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();我的例子更明目张胆,但这是同样的原则。
当您想要更改值时,不应该创建新的类。拥有一个类的目的是要有一个可以包含不同值的可重用类型。
就像我的例子应该重写为:
myAge = Number(20);
fingersOnMyLeftHand = Number(5);您的代码应该重写为:
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 PhysicalArmor和MagicalArmor属性,如果当前敌人没有特定类型的装甲,只需将其设置为0即可。
一个非常小的评论,是未定义,而不是Cuthulu。不过,它的发音是"Cuthulu“。
class TurnManager {
constructor() {
this.turnNumber = -1;
};
nextTurn() {
this.turnNumber++;
};
};这并不一定是错误的,但是为什么turnNumber设置为-1呢?
我怀疑您是在一个零索引环境中进行思考,并且在游戏开始时依赖于调用nextTurn()。但我认为这里有更直观的方法:
turnList[oneIndexedTurnNumber-1]比执行逆alert("Starting turn " + (zeroIndexedTurnNumber+1))"更易读。从技术角度来看,两者都是正确的,但是第一个片段的上下文(使用它作为数组索引)立即解释了为什么要执行-1。第二个片段没有立即解释为什么要执行+1 (当然,假设变量名实际上不包含“零索引”)。turnIndex,或者重新工作以从1开始。nextTurn(),而是在回合结束后调用。这在语义(和逻辑上)上更有意义。举个简单的例子:for循环不让它的索引从-1开始,然后在第一次迭代之前自动递增。这是违反直觉的行为。https://codereview.stackexchange.com/questions/196291
复制相似问题