# How to find sequences of consecutive cards of the same suit in a hand, including wild cards? (Indian Rummy)

I have a hand of 13 cards (chosen from 2 standard decks of 52 cards + 4 jokers, so it's possible for the same card to appear multiple times).

I want to search this deck to find sequences according to the rules of Indian Rummy:

• A sequence must contain at least 3 cards, or up to 10 cards

(We need at least two sequences to win / minimize loss penalties, so at least 3 cards in the hand of 13 need to be left over to form another sequence)

• Cards in a sequence have consecutive rank:

2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A (wild cards count as any rank)

• A sequence must be all of the same suit:

♦, ♣, ♠, ♥ (wild cards can count as any suit)

• A sequence is called "pure" if it contains no wild cards standing in for other cards.

eg. if Jacks are wild, the sequence 10♥, J♥, Q♥ is a pure sequence, while the (also valid) sequence 3♣, 4♣, 5♣, J♥, 7♣ is not a pure sequence.

Here's what I've come up with so far, but it does not correctly find all sequences matching the rules above.

private List<Card> FindSequences()
{
List<Card> sequence = new List<Card>();

int Count = 0;

//sort in order
cards = cards.OrderBy (i => i.FaceValue).ToList ();

//------------------------------------------------------------------------------------------------------//

for(int i = 1; i < 9; i++)
{
if((cards[i - 1].cardRank == cards[i].cardRank - 1) && (cards[i].cardRank == cards[i + 1].cardRank - 1) && (cards[i + 1].cardRank == cards[i + 2].cardRank - 1) )//&& (cards[i + 2].cardRank == cards[i + 3].cardRank - 1))
{
}
else
{
Count++;
//check If Sequence Less Then 3 Clear Data
if(sequence.Count < 3)
sequence.Clear ();
}
}

wildCard.Clear ();
return sequence;
}

• Shouldn't it return a list of card sequences, so a List<List<Card>>? Aug 4, 2018 at 4:00
• Cards in a sequence have consecutive rank: 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A(Low or HighCard) (wildcards count as any rank) Aug 13, 2018 at 13:38
• stackoverflow.com/questions/31615597/… Aug 21, 2018 at 10:26

var suites = { 'clubs' : [], 'diamonds' : [], 'hearts' : [], 'spades' : [], 'stars' : [] }; var wilds = []; var buckets = { 3 : [], 4 : [], 5 : [], 6 : [], 7 : [], 8 : [], 9 : [], 10 : [], 11 : [], 12 : [], 13 : [] }; //can have more then one run/set so these are used to hold valid runs/sets var runs = []; var sets = []; var r_num = -1; //starts at -1 so ++ will give 0 index var s_num = -1;

CLUBS, DIAMONDS, HEARTS, SPADES, STARS = range(5) WILD = 0

testHand = [(CLUBS,3), WILD, (SPADES,3), (CLUBS,4), (CLUBS,5), (CLUBS,6), (CLUBS,6), (CLUBS,7), (CLUBS,8), (SPADES,10), (HEARTS,10), WILD]

def nextInRun(card):
if card[1] == 13:
raise ValueError("cannot determine next card in run for %s" % str(card))
return (card[0],card[1]+1)

def prevInRun(card):
if card[1] == 3:
raise ValueError("cannot determine previous card in run for %s" % str(card))
return (card[0],card[1]-1)

def fit2Set(card1, card2):
return card1 == WILD or card2 == WILD or card1[1] == card2[1]

START_RUN, START_SET = range(2)

def removeCard(hand, card, startIdx=None):
hand = copy.deepcopy(hand)
if startIdx != None and hand.index(card) < startIdx:
startIdx -= 1
hand.remove(card)
return ((hand, startIdx) if startIdx != None else hand)

def startMoves(handr1,card1,startIdx):
if card1 == WILD:
#print "trying to start run with 1 WILD card"
for card2 in set(handr1):
handr2,startIdx = removeCard(handr1, card2, startIdx)
if card2 == WILD:
#print "trying to start run with 2 WILD cards"
for card3 in set(handr2):
handr3,startIdx = removeCard(handr2, card3, startIdx)
if card3 == WILD:
yield (START_RUN, 3*[WILD], handr3, startIdx)
else:
try:
card2v = prevInRun(card3)
card1v = prevInRun(card2v)
yield (START_RUN, [card1,card2,card3], handr3, startIdx)
except ValueError:
pass
else:
#print "trying to start run with WILD card and %s" % str(card2)
try:
card1v = prevInRun(card2)
card3 = nextInRun(card2)
#print "card3 = %s, handr2 = %s" % (str(card3),str(handr2))
canContinueRun = card3 in handr2
if not canContinueRun and WILD in handr2:
card3 = WILD
canContinueRun = True
if canContinueRun:
handr3,startIdx = removeCard(handr2, card3, startIdx)
yield (START_RUN, [card1,card2,card3], handr3, startIdx)
except ValueError:
pass
return # no need to start a set with a WILD
else:
try:
card2v = card2 = nextInRun(card1)
canContinueRun = card2 in handr1
#print "card2 = %s, handr1 = %s" % (str(card2),str(handr1))
if not canContinueRun and WILD in handr1:
card2v = card2
card2 = WILD
canContinueRun = True
if canContinueRun:
handr2,startIdx = removeCard(handr1, card2, startIdx)
card3 = nextInRun(card2v)
canContinueRun = card3 in handr2
#print "card3 = %s, handr2 = %s" % (str(card3),str(handr2))
if not canContinueRun and WILD in handr2:
card3 = WILD
canContinueRun = True
if canContinueRun:
handr3,startIdx = removeCard(handr2, card3, startIdx)
yield (START_RUN, [card1,card2,card3], handr3, startIdx)
except ValueError:
pass
handrs = copy.deepcopy(handr1)
for card2 in set(handr1):
handrs.remove(card2)
if not fit2Set(card1,card2):
continue
handr2,startIdx = removeCard(handr1, card2, startIdx)
for card3 in set(handrs):
if not fit2Set(card1,card3):
continue
handr3,startIdx = removeCard(handr2, card3, startIdx)
yield (START_SET, [card1,card2,card3], handr3, startIdx)

def startings(hand,startIdx=0,runs=[],sets=[]):
#print "get startings for\n hand = %s\n startIdx = %d\n runs = %s\n sets = %s" % (str(hand),startIdx,str(runs),str(sets))
if len(hand) == 0 or startIdx >= len(hand):
yield copy.deepcopy(hand),copy.deepcopy(runs),copy.deepcopy(sets)
return
card = hand[startIdx]
while hand.index(card) < startIdx:
startIdx += 1 # do not try to start with the same card twice here
if startIdx >= len(hand):
yield copy.deepcopy(hand),copy.deepcopy(runs),copy.deepcopy(sets)
return
card = hand[startIdx]
#print " actual startIdx = %d" % startIdx
handr = removeCard(hand, card)
for move in startMoves(handr, card, startIdx):
if move[0] == START_RUN:
runs2 = copy.deepcopy(runs)
runs2.append(move[1])
for starting in startings(move[2], move[3], runs2, sets):
yield starting
elif move[0] == START_SET:
sets2 = copy.deepcopy(sets)
sets2.append(move[1])
for starting in startings(move[2], move[3], runs, sets2):
yield starting
for h,r,s in startings(hand, startIdx+1, runs, sets):
yield h,r,s

def runExtensions(run,handSet):
if set(run) == set([WILD]): # run consists only of WILD cards
for card in handSet:
yield card
else:
runUnique = list(set(run))
cardv = runUnique[0] if runUnique[0] != WILD else runUnique[1]
idx = run.index(cardv)
lastCardVal = cardv
while idx < len(run)-1:
lastCardVal = nextInRun(lastCardVal)
idx += 1
try:
nextCardVal = nextInRun(lastCardVal)
if nextCardVal in handSet:
yield nextCardVal
return
if WILD in handSet:
yield WILD
except ValueError:
pass

def fillings(hand, runs, sets):
if len(runs) > 0:
run1 = runs[0]
noExtensionFound = True
usedWildExtension = False
for runExtension in runExtensions(run1,set(hand)):
noExtensionFound = False
run1e = copy.deepcopy(run1)
run1e.append(runExtension)
handr = removeCard(hand, runExtension)
runse = [run1e] + copy.deepcopy(runs[1:])
for filling in fillings(handr, runse, sets):
yield filling
if runExtension == WILD:
usedWildExtension = True
# when extending with WILD: also try without extending the first run; WILD card could be needed in another run
if noExtensionFound or usedWildExtension and len(runs) > 1:
for h,r,s in fillings(hand, copy.deepcopy(runs[1:]), sets):
r = [run1] + r
yield h,r,s
return
handr = copy.deepcopy(hand)
setse = copy.deepcopy(sets)
for card in hand:
for set_i in setse:
if fit2Set(card, set_i[0]):
handr.remove(card)
set_i.append(card)
break
yield handr,[],setse

def valueCard(card):
if card == WILD:
return 20
return card[1]

def value(hand):
return sum([valueCard(card) for card in hand])

def strRepSuit(suit):
if suit == CLUBS:
return 'clubs'
if suit == DIAMONDS:
return 'diamonds'
if suit == HEARTS:
return 'hearts'
if suit == STARS:
return 'stars'

def strRepCard(card):
if card == WILD:
return 'WILD'
return '%s%d' % (strRepSuit(card[0]), card[1])

def strRepCardSuit(card):
if card == WILD:
return 'WILD'
return strRepSuit(card[0])

def strRepVal(card):
if card == WILD:
return 'WILD'
return '%d' % card[1]

def strRepRun(run):
setRun = set(run)
if setRun == set([WILD]):
return '%d * %s' % (len(run),strRepCard(run[0]))
runUnique = list(setRun)
suit = (runUnique[0] if runUnique[0] != WILD else runUnique[1])[0]
return '%s %s' % (strRepSuit(suit), ','.join([strRepVal(card) for card in run]))

def strRepSet(set_):
setUnique = list(set(set_))
val = (setUnique[0] if setUnique[0] != WILD else setUnique[1])[1]
return '%d %s' % (val, ','.join([strRepCardSuit(card) for card in set_]))

def strRepSol(hand,runs,sets):
return ' runs: %s\n sets: %s\n hand: %s\n hand value: %d' % ('; '.join([strRepRun(run) for run in runs]), '; '.join([strRepSet(set_) for set_ in sets]), ','.join([strRepCard(card) for card in hand]), value(hand))

def optimizeHand(hand):
lowestHandValue = value(hand)
bestSolution = hand,[],[]
for handr,runs,sets in startings(hand):
#print "starting with\n runs = %s\n sets = %s\n handr = %s" % (str(runs),str(sets),str(handr))
if len(runs) == 0 and len(sets) == 0:
continue
if len(handr) == 0:
print "solution generated without filling"
lowestHandValue = 0
bestSolution = [],runs,sets
break
for solution in fillings(handr,runs,sets):
handValue = value(solution[0])
if handValue < lowestHandValue:
lowestHandValue = handValue
bestSolution = solution
print "solution improved:\n%s" % strRepSol(bestSolution[0], bestSolution[1], bestSolution[2])
if lowestHandValue == 0:
break
if lowestHandValue == 0:
break
print "best solution:\n%s" % strRepSol(bestSolution[0], bestSolution[1], bestSolution[2])
#return lowestHandValue, bestSolution

• If you answer your own question, it would be really helpful if you could describe how you solved it instead of just pasting code, so people having a similar problem can benefit from it. Aug 21, 2018 at 10:46