• Create Account

PYTHON CARD GAME - objects, methods and design

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

5 replies to this topic

#1creyes  Members

Posted 26 November 2012 - 09:57 AM

Bit of background, I've been getting pretty into programming lately and think that I have a decent grasp on most programming concepts but have never actually put my skills to the test and made anything. I've decided that I want to make a text-only card game clone (WoW tcg) to test my skills. The game doesn't have to be completely functional, but I'd like to create a structure that would allow me to one day (if I feel like it) implement a rules system etc.

That means... cards can't just be names they need to be objects with attributes etc.

I'm pretty bad at design, but I figured that the best way to handle gameplay was to create a list for every zone (allyZone = [], deck = [], hand = [], graveyard = [] etc etc) and then interact with the cards via the different lists. If that's wrong... let me know but it's what I could think of.

Now my problem is interaction with the cards as objects in those zones, and figuring out how to design the card object in general.
[source lang="python"]class deck(object): def __init__(self): self.cards = [] from random import shuffle shuffle(self.cards) def shuffleDeck(self): from random import shuffle shuffle(self.cards) def drawCard(self, hand): d = self.cards cardDrawn = d.pop(0) hand.append(cardDrawn) def viewLibrary(self): for x in self.cards: print x.name def viewNumberOfCards(self, cardsToView): for x in self.cards[:cardsToView]: print x.name class hand(object): def __init__(self): self.cards = [] def viewHand(self): for x in self.cards: print x.name def playCard(self, card): if card.type == "ally": h = self.cards cardToPlay = h.pop(card) allyZone.append(cardToPlay)class card(object): def __init__(self, name, type): self.name = name self.type = type[/source]

This barebones for sure, but you get the idea. Now obviously using a string for type in card.__init__ is a bad idea, but I'm not sure of a better way to do it and I'm not sure how I'm going to store the cardData (xml, just a python file, json i have zero clue).

Anyway my question for now is under hand.playCard(). When I try to use something like hand.playCard(hand.cards[0]) the compiler says that hand.playCard(card) needs to be an integer. I'm not sure how to get around that or what I'm supposed to use instead.

Any help would be awesome!

#2smr  GDNet+

Posted 26 November 2012 - 11:21 AM

The problem is because you're passing card into the .pop method. .pop expects to receive the number of items off the stack you want to take. It seems to me like you're expecting it to put a reference to the card being popped into "card." I think you're trying to take a card from your hand, then place that card into the ally zone:

[source lang="python"]def playCard(self, card): if card.type == "ally" and card in self.cards: self.cards.remove(card) player.putInZone(card.type, card)# to use this:hand.playCard(hand.cards[0])[/source]

You might want to think about reorganizing a bit. Your Hand object is directly referencing allyZone, which tightly couples them together. Hand will not work if you decide to rename allyZone later, or want to use it separately from allyZone.

A better solution might be to handle this logic outside, in something like a Game class that knows the rules of how cards are taken from a hand and put into play:

[source lang="python"]def SomeGame: def __init__(self): self.graveyard = []; self.deck = self.initializeDeck() def playCardFromHand(self, player, card): if player.hand.hasCard(card) and self.playCard(player, card): player.hand.removeCard(card) def playCard(self, player, card): if not self.canPlayCard(player, card): return False if card.type == "ally" and card in self.cards: player.putInZone(card.type, card) return True[/source]

This would enable you to write different implementations of the Game object to have different rules, and also could help insulate some classes from changes in other classes.

For whatever reason I can't get the spacing to be consistent between edits.

Edited by smr, 26 November 2012 - 01:46 PM.

#3creyes  Members

Posted 27 November 2012 - 11:04 AM

Thanks! that was incredibly helpful and exactly what I was looking for!

My follow up question is another design one with a little bit of implementation.

You bring up a good point that there should be some sort of "game state" class that everything interacts with like playing cards etc. I feel like this is also the place where EVERY RULES FUNCTION would go also (when ~ enters play do ~)... does that sound correct? In other words the "game state" object would contain all of the "rules objects (i've read that every rule should be made an object)" and then when a card "enters play" it checks the "game state" object and instantiates whatever rules that card has

thanks again, you've been a big help!

#4smr  GDNet+

Posted 27 November 2012 - 01:19 PM

In my opinion it's a good idea for your Hand and card implementations to know as little as possible (preferably nothing, if you can manage it) about the rules of the game. Hand should know only about how to receive and remove a card, fetch a card, maybe search for a particular type of card, and maybe some way to sort the cards (some players prefer to keep their cards sorted). Even sorting is a little tricky because different games may have different ways to sort cards. This can probably be accomplished by implementing some sort of "sorter" interface. You could create classes "MyGameHandSorter" or a "PokerHandSorter" or "BridgeHandSorter," for example. At that point you could either "MyGameHandSorter().sort(myHand)". You can even go through the trouble of formally declaring your interfaces, "class IHandSorter(object): ...class PokerHandSorter(IHandSorter)" but it's probably not necessary. Remember, python does duck typing.

As far as implementing every game rule as an object: It depends. Some rules are better left implemented directly into your Game class. Things like phases of a player turn, what can be done during the different phases, etc. might be better left in Game.

That being said, if you're making a game in the same genre as MtG, each card could have its own set of special rules associated with it. You should spend some time trying to classify these rules down into as many "rule types" as you can. From there, find a way to represent the data that is associated with these rule types. That data will be held in your Card implementation for the game, either directly in the card or attached to the card through some sort of CardProperties member on Card or something. I'd probably just stick them in Card. With MtG there are always exceptions, and these will have to be handled in your Game.playCard implementation. I wouldn't put them on Card, personally. Cards should just be data. This way you don't risk having to rewrite individual card implementations if you decide to create some specialized version of your game.

In fact, you probably won't be implementing a "playCard" method on game. You'll more likely be implementing what we call the command pattern. The player will build commands indicating what she would like to do, then dispatch those to the implementation of Game. The reason you have to do this is because in games like MtG, the opposing player has an opportunity to interrupt your action. You cannot immediately resolve a healing spell, for example, because the opposing player might cast an interrupt to prevent the spell. Another player might interrupt that spell somehow, or perform some other action (maybe even eliminate the player with an instant damage spell!) that affects the outcome. All these commands will need to be collected from all the players, then resolved by Game in the proper order and the outcome will need to change the state of the game, cards, and players. And finally those outcomes will need to be presented to the players.

I bet you thought you were picking an easy game coding project, didn't you?

Edited by smr, 27 November 2012 - 01:21 PM.

#5creyes  Members

Posted 03 December 2012 - 12:02 PM

First of all, I'd like to thank you @smr for all the help you gave - I learned a ton just be reading your posts. As for "picking an easy project" - that's sort of true. I definitely thought it would be easier but I'm glad it turned out to be more difficult because it's forced me to read about/I've learned a lot about oop and program design. Because of all that, I have all of the infrastructure done (can draw cards, play cards to each of the different zones etc etc)

I think my final question before I put this mini-project to bed for a while (until I learn a lot more about oop, and gui stuffs) is about data-storage. I know this is a much discussed topic but I haven't really found a solid answer. I'm pretty sure what I want to do is make an XML file similar to

[source lang="xml"]<card> <name>"Random Name"</name> <type>Ally</type> <subtype>Human Warrior</subtype> <attack>5</attack> <health>3</attack> <abilities>When this comes into play, gain 5 life</abilities></card>[/source]

But I can't seem to find any tutorials online about how to parse those into objects. I'd assume I make a prototype class before hand but my xml/python knowledge is zero. If someone can point me in the direction of a tutorial or if there's a better way to do this (sqlite maybe? then make all the class instances sql queries?) that'd be much appreciated!

#6smr  GDNet+

Posted 03 December 2012 - 02:42 PM

You're welcome!

As far as using XML, I wouldn't. XML is a bit more difficult to parse than, say, JSON. JSON is JavaScript Object Notation and looks pretty much the same as defining lists and dicts in python:

[ // <-- opening square brace because we're defining a list
{ // <-- opening with a curly brace because we're defining a dict
"name": "Random Name", //<-- commas here to separate the key/value pairs in the dict. JSON also requires the key to be in DOUBLE quotes, even though this is not necessary in JavaScript. All strings are double quoted.
"type": "Ally",
"subtype": "Human Warrior",
"attack": 5,
"health": 3,
"abilities": When this comes into play, gain 5 life
}, // <-- notice the comma here. Necessary because it's not the last item in the list

{ // Begins next card
"name": "Random Name 2",
"type": "Ally",
"subtype": "Human Warrior",
"attack": 5,
"health": 3,
"abilities": When this comes into play, gain 5 life
}
]


And in python, importing JSON is already built in:

import json