Jump to content

Malifaux Odds and a Bleg


011121

Recommended Posts

So after a few recent discussions I found that I really wanted to have a way to determine the odds of a given flip for the purpose of making predictions and analyses. I haven't seen any for malifaux so I built my own.

I started off with a spreadsheet calculating the odds by brute force but it was going to spiral out of control before it actually became useful. So I started over and decided to build a program to determine the odds experimentally, i.e. by repeatedly simulating the flip until the result regresses to a mean value. The program is built and as far as I can tell working fine (see bleg below).

Some caveats.

1) The program assumes a fresh deck at the start of a test. The reason for this is because I am really not prepared to build a program that tests every possibility of previous draws from the deck. It assumes you start with all 54 cards in the deck, but if you have + or - flips it will appropriately draw cards from the 54 card deck without replacement (i.e. if you draw the ace of masks you can't draw it again).

2) Similarly the program cannot account for cheating, there are just way too many variables for me to simulate it.

3) Because I am determining the probabilities experimentally there is of course an error. There's a compromise to be made between accuracy and time investment. I currently have the program making 50,000 repetitions of a given test and averaging the results. To measure the error I ran a test of a fixed duel using a stat of 4 vs a target of 10 five times (each "time" consisting of 50,000 averaged repetitions). The resulting 5 tests had a mean value of 0.6133 (61.33% chance of success) with a standard deviation of 0.0016 (.16%).

Results

For the moment I'm going to post two tables of results (below). My hope is to build a front end and host the program (maybe after significant efficiency improvements) so people can use it. Not sure how long that may take as front end stuff is very new to me (this is a learning project for me).

The first table is a cross reference of the likelihood of succeeding at a fixed number test (such as a terrifying test). The numbers across the top are the difficulties of the test (Red). Along the left side are the various stat values (i.e. Wp, Df, whatever in Blue). The numbers in the table represent the chance of succeeding as a decimal, with 1 = 100% chance of success. I went up to 17 because that covered everything I could think of except aSeamus terrifying if he buffs himself up. If people want me to expand the table I can.

Simple Duel:

[TABLE]

[TR]

[TD=width: 70]

[/TD]

[TD=width: 70]1

[/TD]

[TD=width: 70]2

[/TD]

[TD=width: 70]3

[/TD]

[TD=width: 70]4[/TD]

[TD=width: 70]5[/TD]

[TD=width: 70]6

[/TD]

[TD=width: 70]7[/TD]

[TD=width: 70]8[/TD]

[TD=width: 70]9[/TD]

[TD=width: 70]10[/TD]

[TD=width: 70]11[/TD]

[TD=width: 70]12[/TD]

[TD=width: 70]13[/TD]

[TD=width: 70]14[/TD]

[TD=width: 70]15[/TD]

[TD=width: 70]16[/TD]

[TD=width: 70]17[/TD]

[/TR]

[TR]

[TD]1

[/TD]

[TD]1.00

[/TD]

[TD]0.98[/TD]

[TD]0.91

[/TD]

[TD]0.84[/TD]

[TD]0.76[/TD]

[TD]0.69[/TD]

[TD]0.61[/TD]

[TD]0.54[/TD]

[TD]0.46[/TD]

[TD]0.39[/TD]

[TD]0.32[/TD]

[TD]0.24[/TD]

[TD]0.17[/TD]

[TD]0.09[/TD]

[TD]0.02[/TD]

[TD]0.00[/TD]

[TD]0.00[/TD]

[/TR]

[TR]

[TD]2

[/TD]

[TD]1.00

[/TD]

[TD]1.00

[/TD]

[TD]0.98

[/TD]

[TD]0.91[/TD]

[TD]0.83[/TD]

[TD]0.76[/TD]

[TD]0.69[/TD]

[TD]0.61[/TD]

[TD]0.54[/TD]

[TD]0.47[/TD]

[TD]0.39[/TD]

[TD]0.31[/TD]

[TD]0.25[/TD]

[TD]0.17[/TD]

[TD]0.09[/TD]

[TD]0.02[/TD]

[TD]0.00[/TD]

[/TR]

[TR]

[TD]3

[/TD]

[TD]1.00

[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]0.98[/TD]

[TD]0.91[/TD]

[TD]0.83[/TD]

[TD]0.76[/TD]

[TD]0.68[/TD]

[TD]0.61[/TD]

[TD]0.54[/TD]

[TD]0.46[/TD]

[TD]0.39[/TD]

[TD]0.31[/TD]

[TD]0.24[/TD]

[TD]0.17[/TD]

[TD]0.09[/TD]

[TD]0.02[/TD]

[/TR]

[TR]

[TD]4

[/TD]

[TD]1.00

[/TD]

[TD]1.00

[/TD]

[TD]1.00

[/TD]

[TD]1.00[/TD]

[TD]0.98[/TD]

[TD]0.91[/TD]

[TD]0.83[/TD]

[TD]0.76[/TD]

[TD]0.68[/TD]

[TD]0.61[/TD]

[TD]0.54[/TD]

[TD]0.47[/TD]

[TD]0.39[/TD]

[TD]0.31[/TD]

[TD]0.24[/TD]

[TD]0.17[/TD]

[TD]0.09[/TD]

[/TR]

[TR]

[TD]5

[/TD]

[TD]1.00

[/TD]

[TD]1.00

[/TD]

[TD]1.00

[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]0.98[/TD]

[TD]0.91[/TD]

[TD]0.83[/TD]

[TD]0.76[/TD]

[TD]0.69[/TD]

[TD]0.61[/TD]

[TD]0.54[/TD]

[TD]0.47[/TD]

[TD]0.39[/TD]

[TD]0.31[/TD]

[TD]0.24[/TD]

[TD]0.17[/TD]

[/TR]

[TR]

[TD]6

[/TD]

[TD]1.00

[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]0.98[/TD]

[TD]0.91[/TD]

[TD]0.83[/TD]

[TD]0.76[/TD]

[TD]0.69[/TD]

[TD]0.61[/TD]

[TD]0.54[/TD]

[TD]0.47[/TD]

[TD]0.39[/TD]

[TD]0.31[/TD]

[TD]0.24[/TD]

[/TR]

[TR]

[TD]7

[/TD]

[TD]1.00

[/TD]

[TD]1.00

[/TD]

[TD]1.00

[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]1.00

[/TD]

[TD]1.00[/TD]

[TD]0.98[/TD]

[TD]0.91[/TD]

[TD]0.83[/TD]

[TD]0.76[/TD]

[TD]0.68[/TD]

[TD]0.61[/TD]

[TD]0.54[/TD]

[TD]0.47[/TD]

[TD]0.39[/TD]

[TD]0.31[/TD]

[/TR]

[TR]

[TD]8

[/TD]

[TD]1.00

[/TD]

[TD]1.00

[/TD]

[TD]1.00

[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]1.00

[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]0.98[/TD]

[TD]0.91[/TD]

[TD]0.83[/TD]

[TD]0.76[/TD]

[TD]0.68[/TD]

[TD]0.61[/TD]

[TD]0.53[/TD]

[TD]0.46[/TD]

[TD]0.39[/TD]

[/TR]

[TR]

[TD]9

[/TD]

[TD]1.00

[/TD]

[TD]1.00

[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]1.00

[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]0.98[/TD]

[TD]0.91[/TD]

[TD]0.84[/TD]

[TD]0.76[/TD]

[TD]0.68[/TD]

[TD]0.61[/TD]

[TD]0.54

[/TD]

[TD]0.46

[/TD]

[/TR]

[TR]

[TD]10

[/TD]

[TD]1.00

[/TD]

[TD]1.00

[/TD]

[TD]1.00

[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]1.00

[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]1.00[/TD]

[TD]0.98[/TD]

[TD]0.91[/TD]

[TD]0.83[/TD]

[TD]0.76[/TD]

[TD]0.68[/TD]

[TD]0.61

[/TD]

[TD]0.54[/TD]

[/TR]

[/TABLE]

The second table is for an opposed duel. This is just a sample as there are a large number of such tables based on the presence or positive or negative flips for attacker or defender. The following table is for an attacker and defender with no positive or negative flips. In this case the top row is for the defender's stat (Red) while the left side is the attacker's stat (Blue). Odds displayed the same (as a decimal.

Opposed Duel:

[TABLE]

[TR]

[TD=width: 70]

[/TD]

[TD=width: 70]1

[/TD]

[TD=width: 70]2[/TD]

[TD=width: 70]3[/TD]

[TD=width: 70]4[/TD]

[TD=width: 70]5[/TD]

[TD=width: 70]6[/TD]

[TD=width: 70]7[/TD]

[TD=width: 70]8[/TD]

[TD=width: 70]9[/TD]

[TD=width: 70]10[/TD]

[/TR]

[TR]

[TD]1[/TD]

[TD]0.54

[/TD]

[TD]0.47[/TD]

[TD]0.40[/TD]

[TD]0.33[/TD]

[TD]0.28[/TD]

[TD]0.22[/TD]

[TD]0.18[/TD]

[TD]0.13[/TD]

[TD]0.10[/TD]

[TD]0.07[/TD]

[/TR]

[TR]

[TD]2[/TD]

[TD]0.61

[/TD]

[TD]0.54[/TD]

[TD]0.46[/TD]

[TD]0.39[/TD]

[TD]0.33[/TD]

[TD]0.28[/TD]

[TD]0.22[/TD]

[TD]0.18[/TD]

[TD]0.14[/TD]

[TD]0.10[/TD]

[/TR]

[TR]

[TD]3[/TD]

[TD]0.67

[/TD]

[TD]0.61[/TD]

[TD]0.54[/TD]

[TD]0.46[/TD]

[TD]0.40[/TD]

[TD]0.33[/TD]

[TD]0.28[/TD]

[TD]0.22[/TD]

[TD]0.18[/TD]

[TD]0.13[/TD]

[/TR]

[TR]

[TD]4[/TD]

[TD]0.73

[/TD]

[TD]0.67[/TD]

[TD]0.60[/TD]

[TD]0.54[/TD]

[TD]0.46[/TD]

[TD]0.40[/TD]

[TD]0.33[/TD]

[TD]0.27[/TD]

[TD]0.22[/TD]

[TD]0.18[/TD]

[/TR]

[TR]

[TD]5[/TD]

[TD]0.78

[/TD]

[TD]0.73[/TD]

[TD]0.66[/TD]

[TD]0.61[/TD]

[TD]0.54[/TD]

[TD]0.46[/TD]

[TD]0.40[/TD]

[TD]0.33[/TD]

[TD]0.27[/TD]

[TD]0.22[/TD]

[/TR]

[TR]

[TD]6[/TD]

[TD]0.82

[/TD]

[TD]0.78[/TD]

[TD]0.72[/TD]

[TD]0.67[/TD]

[TD]0.60[/TD]

[TD]0.54[/TD]

[TD]0.46[/TD]

[TD]0.40[/TD]

[TD]0.33[/TD]

[TD]0.28[/TD]

[/TR]

[TR]

[TD]7[/TD]

[TD]0.87

[/TD]

[TD]0.82[/TD]

[TD]0.78[/TD]

[TD]0.72[/TD]

[TD]0.67[/TD]

[TD]0.60[/TD]

[TD]0.54[/TD]

[TD]0.46[/TD]

[TD]0.39[/TD]

[TD]0.34[/TD]

[/TR]

[TR]

[TD]8[/TD]

[TD]0.90

[/TD]

[TD]0.87[/TD]

[TD]0.83[/TD]

[TD]0.78[/TD]

[TD]0.72[/TD]

[TD]0.67[/TD]

[TD]0.61[/TD]

[TD]0.54[/TD]

[TD]0.46[/TD]

[TD]0.40[/TD]

[/TR]

[TR]

[TD]9[/TD]

[TD]0.93

[/TD]

[TD]0.90[/TD]

[TD]0.86[/TD]

[TD]0.82[/TD]

[TD]0.77[/TD]

[TD]0.73[/TD]

[TD]0.67[/TD]

[TD]0.60[/TD]

[TD]0.53[/TD]

[TD]0.46[/TD]

[/TR]

[TR]

[TD]10[/TD]

[TD]0.96

[/TD]

[TD]0.93[/TD]

[TD]0.90[/TD]

[TD]0.86[/TD]

[TD]0.82[/TD]

[TD]0.77[/TD]

[TD]0.73[/TD]

[TD]0.67[/TD]

[TD]0.60[/TD]

[TD]0.53[/TD]

[/TR]

[/TABLE]

Bleg

Okay so all that out of the way, here's the bleg: I *think* the program works. That said It'd be a big help if I could get corroboration by having others test it by calculating the odds and comparing to what I got. They may not match exactly due to the error above but they should be very close, if I'm right and you're right. Alternatively I'll post the Python code, as soon as I have it annotated, and people who read Python can let me know if they see anything wrong. Also if you're better at Python than I am (not hard, I've only been using it for a year and a half at this point) and have suggestions to improve efficiency I'm very interested.

Thanks, and I hope this is helpful!

---------- Post added at 10:33 PM ---------- Previous post was at 10:11 PM ----------

Okay here's the code, my annotations suck, let me know if anything needs clarification.

[code]'''/ Created on Aug 18, 2012 @author: 011121 ''' from random import choice ''' object that contains a value and suit along with getter methods for both and custom comparison and to string methods. Comparison looks first at value, then at suit. ''' class Card: def __init__(self, value, suit): self.suit = suit self.value = value def getSuit(self): return self.suit def getValue(self): return self.value def __cmp__(self, other): if (self.value < other.value): return -1 elif (self.value > other.value): return 1 elif (self.getSuit() == other.getSuit()): return 0 else: return 1 def __str__(self): value = str(self.getValue()) suit = self.getSuit() return(value + suit) ''' object that contains card objects, when instantiated creates a full deck of 54 cards including both jokers. cardDraw method selects a random card, removes it from the deck and returns it. ''' class Deck: def __init__(self, used = None): self.deck = [] suits = ["R", "C", "T", "M"] for suit in suits: for num in range(13): self.deck.append(Card(num+1, suit)) self.deck.append(Card(14, "J")) #red joker self.deck.append(Card(0, "J")) #black joker if (used != None): for item in used: self.deck.remove(item) def __str__(self): string = "" for card in self.deck: string += str(card) + " " return string def cardDraw(self): card = choice(self.deck) self.deck.remove(card) return card ''' this method runs a single opposed duel. Additional flip and flip sign paramerets are used to define positive and negative flips. A duel where the attacker has --- would be run by calling opposedDuel with attackerAdditionalFlips = 3 and attackerFlipSign = "-" ''' def opposedDuel(attackerDeck, attackerStat, defenderDeck, defenderStat, attackerAdditionalFlips = 0, attackerFlipSign = "+", defenderAdditionalFlips = 0, defenderFlipSign = "+"): 'attacker draws' attackerDraw = [] for i in range (1 + attackerAdditionalFlips): attackValue = attackerDeck.cardDraw().getValue() attackerDraw.append(attackValue) 'defender draws' defenderDraw = [] for i in range (1 + defenderAdditionalFlips): defendValue = defenderDeck.cardDraw().getValue() defenderDraw.append(defendValue) 'attacker card value determined' if (0 in attackerDraw): attackCard = 0 elif (attackerFlipSign == "+"): attackCard = max(attackerDraw) elif (attackerFlipSign == "-"): attackCard = min(attackerDraw) 'defender card value determined' if (0 in defenderDraw): defendCard = 0 elif (defenderFlipSign == "+"): defendCard = max(defenderDraw) elif (defenderFlipSign == "-"): defendCard = min(defenderDraw) 'duel resolution' success = (attackCard + attackerStat) - (defendCard + defenderStat) return success ''' this method runs a single simple duel. Additional flip and flip sign paramerets are used to define positive and negative flips. A duel where the model has --- would be run by calling fixedDuel with additionalFlips = 3 and flipSign = "-" ''' def fixedDuel(deck, stat, target, additionalFlips = 0, flipSign = "+"): 'model draws' draw = [] for i in range(1 + additionalFlips): value = deck.cardDraw().getValue() draw.append(value) 'card value determined and duel resolution' if (0 in draw): success = (0 + stat) - target elif (flipSign == "+"): success = (max(draw) + stat) - target elif (flipSign == "-"): success = (min(draw) + stat) - target return success ''' test harness for testing fixed duel for a set number of repetitions ''' def testFixedDuel(repetitions, stat, target, additionalFlips = 0, flipSign = "+"): successRate = 0 for i in range(repetitions): deck = Deck() success = fixedDuel(deck, stat, target, additionalFlips, flipSign) if (success >= 0): successRate += 1 return float(successRate)/repetitions ''' test harness for testing oposed duel for a set number of repetitions ''' def testOpposedDuel(repetitions, attackerStat, defenderStat, attackerAdditionalFlips = 0, attackerFlipSign = "+", defenderAdditionalFlips = 0, defenderFlipSign = "+"): successRate = 0 for i in range(repetitions): attackerDeck = Deck() defenderDeck = Deck() success = opposedDuel(attackerDeck, attackerStat, defenderDeck, defenderStat, attackerAdditionalFlips, attackerFlipSign, defenderAdditionalFlips, defenderFlipSign) if (success >= 0): successRate += 1 return float(successRate)/repetitions if __name__ == '__main__': pass[/code]

Link to comment
Share on other sites

I'd have to set it up so that each of those times it drew a different card, basically it would be the spreadsheet method I started with but in program form. I'd have to rework some parts of the program rather extensively (namely an alternate card draw method) but it certainly would be more efficient and would be a handy double check of the results.

I'll work it up tonight.

Link to comment
Share on other sites

Hrrm. I can do it easily enough if we are talking about just straight flips but when you add in the positive and negative flip options it increases dramatically the work and complication.

Consider as you said for a straight flip opposed duel there are 54*54 possibilities. But if both sides have a + then it becomes (54*53)*(54*53) possibilities. If both sides have --- we're looking at (54*53*52*51)^2 possibilities. That's about a billion times as many calculations as I was doing.

Link to comment
Share on other sites

Okay, I've made a lot of improvements to the original code.

Now Flips (i.e +++ or --), decks, cards, and damage profiles (i.e. 3/4/6) are objects.

Methods include the original fixed and opposed tests along with an attack evaluation (returning a flip object used for damage determination), damage determination, and a composite full attack routine method that combines a opposed duel, attack resolution and damage determination into one.

I feel pretty good about the code although I ran into a brick wall with the timeit feature when trying to determine whether keeping the cards as objects that contain individual variables is better than storing the value and suit data in either a list or tuple.

I would still be very keen to hear if anyone has gotten similar results independently.

here's the code:

[code]'''/ Created on Aug 18, 2012 @author: 011121 ''' from random import choice ''' ######################################################################## Data structures ''' ''' object that contains a value and suit along with getter methods for both and custom comparison and to string methods. Comparison looks first at value, then at suit. ''' class Card: def __init__(self, value, suit): self.suit = suit self.value = value def getSuit(self): return self.suit def getValue(self): return self.value def __cmp__(self, other): if (self.value < other.value): return -1 elif (self.value > other.value): return 1 elif (self.getSuit() == other.getSuit()): return 0 else: return 1 def __str__(self): value = str(self.getValue()) suit = self.getSuit() return(value + suit) ''' Represents flip modifications to draws. object that contains a value and sign along with getter methods for both and to string methods. ''' class Flip: def __init__(self, value, sign): self.sign = sign self.value = value def getSign(self): return self.sign def getValue(self): return self.value def __str__(self): value = str(self.getValue()) sign = self.getSign() return(value + sign) defaultFlip = Flip(0, "+") ''' Represents a damage profile. object that contains values for three grades of damage. ''' class Damage: def __init__(self, weak = "weak", moderate = "moderate", severe = "severe"): self.damage = (weak, moderate, severe) def getDamage(self, type): return self.damage[type] def __str__(self): return(str(self.damage)) defaultDamage = Damage() ''' object that contains card objects, when instantiated creates a full deck of 54 cards including both jokers. cardDraw method selects a random card, removes it from the deck and returns it. ''' class Deck: def __init__(self, used = None): self.deck = [] suits = ["R", "C", "T", "M"] for suit in suits: for num in range(13): self.deck.append(Card(num+1, suit)) self.deck.append(Card(14, "J")) #red joker self.deck.append(Card(0, "J")) #black joker if (used != None): for item in used: self.deck.remove(item) def __str__(self): string = "" for card in self.deck: string += str(card) + " " return string def cardDraw(self): card = choice(self.deck) self.deck.remove(card) return card ''' end of structures ############################################################################ Methods ''' ''' this method draws one or more cards and determines result based on flips ''' def drawResult(deck, flip = defaultFlip): draw = [] for i in range (1 + flip.getValue()): attackValue = deck.cardDraw().getValue() draw.append(attackValue) if (0 in draw): card = 0 elif (flip.getSign() == "+"): card = max(draw) elif (flip.getSign() == "-"): card = min(draw) return card ''' this method runs a single opposed duel. Additional flip and flip sign paramerets are used to define positive and negative flips. A duel where the attacker has --- would be run by calling opposedDuel with attackerAdditionalFlips = 3 and attackerFlipSign = "-" ''' def opposedDuel(attackerDeck, attackerStat, defenderDeck, defenderStat, attackerFlip = defaultFlip, defenderFlip = defaultFlip): 'attacker draws' attackCard = drawResult(attackerDeck, attackerFlip) 'defender draws' defendCard = drawResult(defenderDeck, defenderFlip) 'duel resolution' success = (attackCard + attackerStat) - (defendCard + defenderStat) return success ''' this method runs a single simple duel. Additional flip and flip sign paramerets are used to define positive and negative flips. A duel where the model has --- would be run by calling fixedDuel with additionalFlips = 3 and flipSign = "-" ''' def fixedDuel(deck, stat, target, flip = defaultFlip): 'model draws' result = drawResult(deck, flip) success = result + stat - target return success ''' evaluates the result of an opposed duel as an attack, returns a flip object ''' def evalAttack(success): if(success < 0): result = None elif(success == 0): result = Flip(2, "-") elif(success < 6): result = Flip(1, "-") elif(success < 11): result = defaultFlip else: result = Flip(1, "+") return result ''' makes a damage flip, returning the type of damage done based on a damage object ''' def damageFlip (deck, damage = defaultDamage, flip = defaultFlip): 'model draws' success = drawResult(deck, flip) 'determine damage' if (success == 0): result = 0 elif (success < 6): result = damage.getDamage(0) elif (success < 11): result = damage.getDamage(1) else: result = damage.getDamage(2) return result ''' runs a full attack routine including damage if the attack hits. returns the damage inflicted. ''' def attackAndDamage(attackDeck, attackStat, defendDeck, defendStat, damage = defaultDamage): result = opposedDuel(attackDeck, attackStat, defendDeck, defendStat) print ("opposed test " + str(result)) result = evalAttack(result) print ("attack result " + str(result)) if (result != None): result = damageFlip(attackDeck, damage, result) else: result = 0 return result ''' end of methods ################################################################################# Test harnesses ''' ''' test harness for testing fixed duel for a set number of repetitions ''' def testFixedDuel(repetitions, stat, target, flips = defaultFlip): successRate = 0 for i in range(repetitions): deck = Deck() success = fixedDuel(deck, stat, target, flips) if (success >= 0): successRate += 1 return float(successRate)/repetitions ''' test harness for testing oposed duel for a set number of repetitions ''' def testOpposedDuel(repetitions, attackerStat, defenderStat, attackerFlips = defaultFlip, defenderFlips = defaultFlip): successRate = 0 for i in range(repetitions): attackerDeck = Deck() defenderDeck = Deck() success = opposedDuel(attackerDeck, attackerStat, defenderDeck, defenderStat, attackerFlips, defenderFlips) if (success >= 0): successRate += 1 return float(successRate)/repetitions if __name__ == '__main__': pass [/code]

Link to comment
Share on other sites

corrected the damage flip method to make an additional flip if the red joker is flipped.

[code]'''/ Created on Aug 18, 2012 @author: 011121 ''' from random import choice ''' ######################################################################## Data structures ''' ''' object that contains a value and suit along with getter methods for both and custom comparison and to string methods. Comparison looks first at value, then at suit. ''' class Card: def __init__(self, value, suit): self.suit = suit self.value = value def getSuit(self): return self.suit def getValue(self): return self.value def __cmp__(self, other): if (self.value < other.value): return -1 elif (self.value > other.value): return 1 elif (self.getSuit() == other.getSuit()): return 0 else: return 1 def __str__(self): value = str(self.getValue()) suit = self.getSuit() return(value + suit) ''' Represents flip modifications to draws. object that contains a value and sign along with getter methods for both and to string methods. ''' class Flip: def __init__(self, value, sign): self.sign = sign self.value = value def getSign(self): return self.sign def getValue(self): return self.value def __str__(self): value = str(self.getValue()) sign = self.getSign() return(value + sign) defaultFlip = Flip(0, "+") ''' Represents a damage profile. object that contains values for three grades of damage. ''' class Damage: def __init__(self, weak = "weak", moderate = "moderate", severe = "severe"): self.damage = (weak, moderate, severe) def getDamage(self, type): return self.damage[type] def __str__(self): return(str(self.damage)) defaultDamage = Damage() ''' object that contains card objects, when instantiated creates a full deck of 54 cards including both jokers. cardDraw method selects a random card, removes it from the deck and returns it. ''' class Deck: def __init__(self, used = None): self.deck = [] suits = ["R", "C", "T", "M"] for suit in suits: for num in range(13): self.deck.append(Card(num+1, suit)) self.deck.append(Card(14, "J")) #red joker self.deck.append(Card(0, "J")) #black joker if (used != None): for item in used: self.deck.remove(item) def __str__(self): string = "" for card in self.deck: string += str(card) + " " return string def cardDraw(self): card = choice(self.deck) self.deck.remove(card) return card ''' end of structures ############################################################################ Methods ''' ''' this method draws one or more cards and determines result based on flips ''' def drawResult(deck, flip = defaultFlip): draw = [] for i in range (1 + flip.getValue()): attackValue = deck.cardDraw().getValue() draw.append(attackValue) if (0 in draw): card = 0 elif (flip.getSign() == "+"): card = max(draw) elif (flip.getSign() == "-"): card = min(draw) return card ''' this method runs a single opposed duel. Additional flip and flip sign paramerets are used to define positive and negative flips. A duel where the attacker has --- would be run by calling opposedDuel with attackerAdditionalFlips = 3 and attackerFlipSign = "-" ''' def opposedDuel(attackerDeck, attackerStat, defenderDeck, defenderStat, attackerFlip = defaultFlip, defenderFlip = defaultFlip): 'attacker draws' attackCard = drawResult(attackerDeck, attackerFlip) 'defender draws' defendCard = drawResult(defenderDeck, defenderFlip) 'duel resolution' success = (attackCard + attackerStat) - (defendCard + defenderStat) return success ''' this method runs a single simple duel. Additional flip and flip sign paramerets are used to define positive and negative flips. A duel where the model has --- would be run by calling fixedDuel with additionalFlips = 3 and flipSign = "-" ''' def fixedDuel(deck, stat, target, flip = defaultFlip): 'model draws' result = drawResult(deck, flip) success = result + stat - target return success ''' evaluates the result of an opposed duel as an attack, returns a flip object ''' def evalAttack(success): if(success < 0): result = None elif(success == 0): result = Flip(2, "-") elif(success < 6): result = Flip(1, "-") elif(success < 11): result = defaultFlip else: result = Flip(1, "+") return result ''' makes a damage flip, returning the type of damage done based on a damage object ''' def damageFlip (deck, damage = defaultDamage, flip = defaultFlip): 'model draws' success = drawResult(deck, flip) 'determine damage in case of red joker a second damage flip is also done' if (success == 0): result = 0 elif (success < 6): result = damage.getDamage(0) elif (success < 11): result = damage.getDamage(1) else: result = damage.getDamage(2) if (success == 14): result = str(result) + " and " + str(damageFlip(deck, damage)) return result ''' runs a full attack routine including damage if the attack hits. returns the damage inflicted. ''' def attackAndDamage(attackDeck, attackStat, defendDeck, defendStat, damage = defaultDamage): result = opposedDuel(attackDeck, attackStat, defendDeck, defendStat) result = evalAttack(result) if (result != None): result = damageFlip(attackDeck, damage, result) else: result = 0 return result ''' end of methods ################################################################################# Test harnesses ''' ''' test harness for testing fixed duel for a set number of repetitions ''' def testFixedDuel(repetitions, stat, target, flips = defaultFlip): successRate = 0 for i in range(repetitions): deck = Deck() success = fixedDuel(deck, stat, target, flips) if (success >= 0): successRate += 1 return float(successRate)/repetitions ''' test harness for testing oposed duel for a set number of repetitions ''' def testOpposedDuel(repetitions, attackerStat, defenderStat, attackerFlips = defaultFlip, defenderFlips = defaultFlip): successRate = 0 for i in range(repetitions): attackerDeck = Deck() defenderDeck = Deck() success = opposedDuel(attackerDeck, attackerStat, defenderDeck, defenderStat, attackerFlips, defenderFlips) if (success >= 0): successRate += 1 return float(successRate)/repetitions if __name__ == '__main__': pass [/code]

Since we've been talking about the doppelganger's ability to help you win initiative 68.7% of the time given the usual caveats (fresh deck, no cheating or soulstone use).

Link to comment
Share on other sites

So just an observation, unless you do not have a Control Deck, you will always have 6-7 cards in your hand before ANY opposed Duel. So your base deck starts with 47-48 cards in the deck, and 6-7 known for Cheating (or at least removed from possibility).

You seem to be pursuing a specific brute-force simulation, so I am not critiqueing, just raising the thought that the cards in hand should affect the odds.

Link to comment
Share on other sites

The issue with that is, as jonas notes, you have over 25 million combinations of opening hands. In order to simulate the event accurately you'd have to run the simulation with every possible opening hand or, at the very least, a goodly proportion of them.

With a much better computer It could certainly be done but my little netbook isn't handling it.

Link to comment
Share on other sites

I guess I misread the initial posts - my bad :)

I like the python, though a brute-force approach is inefficient, as you said. If you can model the set, you can get to good guidelines and a solid predictive model.

Though would it be of interest to look at the math around this? If not, I will move-on.

I am not an expert, but I play with statistics alot.

Link to comment
Share on other sites

I'm open to other ideas of how to determine the odds besides running a multitude of simulations but I'm just not aware of any realistic options. If you try calculating the odds directly it quickly spirals out of control when you do anything more complicated than an opposed duel with no + or - flips.

But as before if anyone has a good idea of how to do it I'm happy to write the code...

Link to comment
Share on other sites

For what its worth I don't think it is worth calculating the odds modified for your opening hands.

Firstly, in Game you aren't going to know your opponents hand so you won't know what the odds are.

Secondly, the odds change every single flip of the game. You aren't going to sit there and work out what has already gone to work out what the chances of you managing to get either 2 moderate or 1 severe damage flip in 1 models activation.

For the amount of variation in the process, you can get the approximate answer from the new deck data.

There was a thread on this stuff earlier, which worked out the various chances of getting moderate damage on negative flips (and probbably a load of other stuff but I couldn't tell you exactly what). I remember reading it about 8-12 months ago. Might be worth searching that.

And if I get a spare few minutes I'll look for a link to it.

Link to comment
Share on other sites

  • 8 months later...

Grad school annihilated any time I had for anything else so i never got to design a front end, unfortunately. The back end is perfectly usable, and if I do say so myself pretty cool. Although i never used it myself I did build in the capacity to take cards out of the decks when instantiated to simulate non-fresh decks.

All the code in the last posted code segment is up to date as I recall.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information