In [79]:
import numpy as np
from random import shuffle
#################################################################################
######################################SETUP######################################
#################################################################################
numbers=(1,1,1,2,2,3,3,4,4,5)
colors=('r','y','g','b','w') #possibly add multi... "m"
ManyCol=len(colors)
#CREATE AN ARRAY OF FIVE HANDS, ONE ON EACH ROW WITH CARDS REPRESENTED BY SERIAL NUMBERS FROM
#THE ORIGINAL DECK, TOP TO BOTTOM, STARTING AT 0. WE DEAL EACH HAND FROM THE TOP OF THE DECK.
table=np.arange(20).reshape(5,4)
#[A,B,C,D,E]=table #MAY USE LETTERING OF PLAYERS, BUT PRLY NOT
#CREATE A FULL SHUFFLED DECK WITH NOMENCLATURE AS IN [2,0] FOR "3 red." THINK OF THE INDICES IN deck
#AS SERIAL NUMBERS WRITTEN ON THE BACKS OF THE CARDS. DeckOut CONVERTS TO EXTERNAL FORMAT LIKE "r3".
def CreateDeck():
deck=[]
for color in range(ManyCol):
for number in numbers:
deck.append([color,number-1]) #internal format numbers range 0-4; colors are numbered
shuffle(deck)
#deck=tuple(deck) #converts deck from list to tuple (mutable to immutable)
return deck #...maybe keep as list if want multiple deals
deck=CreateDeck()
def PrintDeckOut(deck):
DeckOut=[colors[card[0]]+str(card[1]+1) for card in deck]
for i in range(int(len(deck)/4)+1): #PRINTS DECK 4 AT A TIME
print(DeckOut[4*i:4*(i+1)])
####################################################################################################
#####################################PUBLIC DATA####################################################
####################################################################################################
############# stacks/extras******************for logging played/publicly visible cards
#THE ARRAY stacks GIVES THE LAST NUMBER SUCCESSFULLY PLAYED FOR EACH COLOR AND extras IS A
#ManyColx5 ARRAY TELLING THE NUMBER OF EXTRAS OF EACH CARD. FOR EXAMPLE, A 5 COLOR GAME WITH ONLY
#A 1R DISCARD AND 1Y PLAYED MIGHT LOOK LIKE stacks=[0,1,0,0,0] AND
#extras=[[1,1,1,1,0],[1,1,1,1,0],[2,1,1,1,0],[2,1,1,1,0],[2,1,1,1,0]].
stacks=[[1,0,0,0,0]]*ManyCol
extras=[[2,1,1,1,0]]*ManyCol
#THE ROWS IN THE ARRAY priority TELL THE CARDS SERIAL NUMBERS IN THE ORDER THAT PLAYERS
#WILL SEARCH. TOP PRIORITY ARE CARDS CLUED BOTH NUMBER AND COLOR. NEXT COME NUMBER OR COLOR. THEN,
#UNCLUED CARDS. CHRONOLOGICAL ORDER BREAKS TIES. FOR EXAMPLE, IF A PLAYER HAS BEEN TOLD CARDS 6
#AND 8 ARE 4's, BUT DOESN'T KNOW ABOUT THE OTHER CARDS 21, 22, THEN HER SEARCH PRIORITY WILL BE
#priority[2]=[8,6,22,21]. 6 AND 8 HAVE BEEN CLUED, SO THEY GET PRIORITY OVER 21 AND 22. THEN, THE NEWER
#CARDS 8 AND 22 GET PRIORITY OVER 6 AND 21 (RESP.) THEN PLAYER 2 WILL SEARCH FOR PLAYS FROM 8 TO 21.
#PRIORITY CAN CHANGE IN CERTAIN SITUATIONS, SUCH AS A CLUED CARD MOVING TO THE LOWEST PRIORITY WHEN IT IS
#KNOWN TO BE DEAD. THE LOWEST PRIORITY CARD IS CALLED CHOP BECAUSE IT IS UP NEXT FOR DISCARD.
priority=np.copy(table[:,::-1])
############# PubInfo/ClueKinds*********************for logging public clue information
#THE COORDINATES (i,j,k) IN THE ARRAY PubInfo TELL WHETHER THE HOLDER OF THE CARD WITH SERIAL NUMBER
#i THINKS IT'S POSSIBLE, BASED ONLY ON PUBLIC INFORMATION (STACKS, CLUES, AND DISCARDS) FOR CARD i
#TO BE COLOR j AND NUMBER k. THE COORDINATES (i,j) IN THE ARRAY ClueKinds TELLS WHETHER CARD i HAS
#BEEN CLUED COLOR (j=0) OR NUMBER (j=1). IF A CLUED CARD IS KNOWN TO BE DISCARDABLE, THE COLOR VALUE
#CHANGES TO -1. ALL THIS AIDS IN DETERMINING priority WHEN INGENSTING A NEW CLUE OR DRAWING A NEW CARD.
PubInfo=np.ones(shape=(len(deck),ManyCol,5), dtype='uint8', order='C')
ClueKinds=np.zeros(shape=(len(deck),2),dtype='uint8',order='C')
####################################################################################################
#####################################PRIVATE DATA###################################################
####################################################################################################
#THE COORDINATES (i,j,k) IN THE ARRAY AllClues TELLS WHETHER PLAYER i CAN SEE THAT A COLOR j/NUMBER k
#CARD WAS EVER CLUED. WHEN A CLUED CARD IS DISCARDED, AllClues FORGETS IT WAS EVER CLUED.
AllClues=np.zeros(shape=(5,ManyCol,5),dtype='uint8',order='C')
#THE ROWS IN THE PRIVATE ARRAY status DESCRIBE EACH PERSPECTIVE ABOUT CARDS.
#NUMBERS AND STATUSES ARE TENTATIVE.
# -900) DISCARDED TO TRASH OR UNSUCCESSFULLY PLAYED
# -800) SURVIVOR, UNSAVED
# -700) ENDANGERED TWINLESS OR DOUBLE CHOP, UNSAVED
# -125) DEAD (UNKNOWN TO PLAYER)
# -100) DEAD (KNOWN TO PLAYER)
# 000) DEFAULT (DRAW PILE OR UNNOTEWORTHY)
# 025) MARKED (NUMBER AND/OR COLOR)
# 050) TWINLESS 4 (NOT ENDANGERED)
# 075) TWINLESS 3 (NOT ENDANGERED)
# 100) EXPECTED TO PLAY IN QUEUE
# 125) EXPECTED TO PLAY NEXT (NOT 250)
# 250) EXPECTED TO IGNITE CHAIN (UNMARKED)
# 900) PLAYED TO STACK
##################################status=np.zeros((5,len(deck)))
#THE PRIVATE ARRAY info HOLDS THE PERSPECTIVES OF EACH OF THE FIVE PLAYERS.
#COORDINATES (i,j,k) POINT TO AN 8 BIT STRING CORRESPONDING TO INFORMATION
#THAT PLAYER i KNOWS ABOUT PLAYER j'S HAND WITH RESPECT TO CLUE k. THE FIRST
#4 BITS HAVE NEGATIVE INFORMATION FOR THE CORRESPONDING 4 CARDS IN PLAYER j'S
#HAND. THE LAST 4 BITS HAVE POSITIVIE INFORMATION.
###################################info=np.ndarray(shape=(5,5,10), dtype=np.uint8, order='C')
#For 6 colors, need shape=5,11
#THE PRIVATE ARRAY metainfo HOLDS METAINFORMATION ABOUT EACH HAND, SIMILAR TO info.
#COORDINATES (i,j,k,l) DESCRIBE IN THE SAME WAY AS info WHAT PLAYER i THINKS
#PLAYER j KNOWS ABOUT PLAYER k'S HAND WITH RESPECT TO CLUE l.
#####################################metainfo=np.ndarray(shape=(5,5,5,10), dtype=np.uint8, order='C')
In [80]:
#TEST 5 COLOR CASE
def TEST():
global deck, stacks, status, priority
deck=[[0,4],[3,2],[1,1],[1,0],
[0,1],[2,0],[2,3],[0,2],
[4,0],[4,0],[3,3],[1,2],
[1,4],[0,3],[0,2],[4,1],
[0,0],[2,2],[1,3],[2,0]]+[[0,0]]*10
status=[[1,0,0,0,0]]*ManyCol #status edit?
PrintDeckOut(deck)
In [81]:
#WHEN A CARD IS SUCCESSFULLY PLAYED, THE CORRESPONDING COLOR ROW IN stacks M SHIFT FORWARD.
#WHEN A 5 IS PLAYED, THAT COLOR ROW BECOMES A ROW OF ZEROS.
def play(color):
stacks[color]=[0]+stacks[color][:-1]
#IN CASES OF AMBIGUITY, THE LATEST PLAYERS AFTER A CLUE ARE BEST TO RESPOND. Mod5SkipBack
#IS USED TO BACKWARD TRAVERSE PLAYERS WHO CAN RESPOND (CURRENTLY EXCLUDING skip, THE CLUE GIVER).
def Mod5SkipBack(value, skip, SkipAlso=-1):
while True:
value=(value-1)%5
if value not in {skip,SkipAlso}:
return value
#UPDATES PubInfo, ClueKinds, AND priority AFTER A CLUE IS GIVEN.
def RecordClue(player, value, ColOrNum):
gather=[] #gather will collect the clued cards
if ColOrNum: #ColOrNum: =0 is color, =1 is number
print('Recording Number %u to player %u'%(value+1, player)) #value+1 is external format
else:
print('Recording Color %c to player %u'%(colors[value], player))
for card in range(4): #goes by pure chronological order to record clued cards
serial=table[player,card]
if deck[serial][ColOrNum]==value:
gather.append(1)
if stacks[deck[serial][0]].index(1)<=deck[serial][1]:
ClueKinds[serial,ColOrNum]=1 #ClueKinds tracks whether cards are clued color or number
else:
ClueKinds[serial]=[-1,0] #When a card is already played, it is marked as kind [-1,0]
else: gather.append(0) #...to mean discard
print('RecordClue updated which cards are clued and gathered:')
print('ClueKinds:',[list(ClueKinds[card]) for card in table[player]])
print('gather:',gather)
if ColOrNum: #upd is the 2D array for how to update each card
upd=[np.subtract([1,1,1,1],gather)]*5 #depending on color or number, it has to be applied
upd[value]=gather #across all numbers and colors(resp.), hence the *5
upd=np.transpose([upd]*ManyCol,axes=(2,0,1)) #and *ManyCol
else:
upd=[np.subtract([1,1,1,1],gather)]*ManyCol
upd[value]=gather
upd=np.transpose([upd]*5,axes=(2,1,0))
PubInfo[table[player],:,:]=np.logical_and(PubInfo[table[player],:,:],upd)
print('update array for player', player,':\n', upd)
surry=[] #surry is a surrogate for priority
for s in [2,1,0,-1]: #possible numbers of clues; higher sum = higher priority
for card in [3,2,1,0]: #loop over cards in reverse chronological order
serial=table[player,card]
#print(serial, deck[serial],sum(ClueKinds[card]))
if sum(ClueKinds[serial])==s:
surry.append(serial)
priority[player]=surry
print('New priority for player %u is'%(player),surry,'\n\n')
def react(player, color, number, queue=[]):
print('Looking for color %c and number %u in player %u'%(colors[color], number+1, player))
#number+1 is external format
print('and skipping serials', queue,'... priority is ', priority[player])
for suspect in priority[player]:
if suspect in queue: #skips cards already queued to play
continue
elif PubInfo[suspect,color,number]: #returns highest priority card that is possibly color&number
print('Returning', suspect)
return suspect
return -1
#Start at clue giver going backward to look for a possible chain starting at the next color
#card that needs to be played on color and ending at number-1. Once a card is found, look at
#that same player for the next card and keep going backward until number-1 is reached.
def respond(player, giver, color, number):
print('Player %u responds to color %c and number %u from player %u'%(player, colors[color], number+1, giver))
i=stacks[color].index(1) #...number +1 is external format
queue=[]
other=Mod5SkipBack(giver, player)
print('The next card is numbered %u and the first player to search is %u'%(i+1, other)) #i+1 is external format
fail=0 ##Would like to allow giver to make chains involving cards in his hand
while i<number:
WouldPlay=react(other, color, i, queue) #card that player 'other' would try to play if he thought
print('Player %u would play %u in response'%(player, WouldPlay)) #...he was told to play [color,i]
if deck[WouldPlay]==[color,i]:
print('It matches color %c and number %u'%(colors[color], i+1)) #i+1 is external format
queue.append(WouldPlay)
i+=1 #success=traverse all hands looking for next number.
fail=0
print('New number and fail count are', i+1, fail) #i+1 is external format
else:
print('It did not match color %c and number %u'%(colors[color], i+1)) #i+1 is external format
other=Mod5SkipBack(other,player,giver)
fail+=1
print('New fail count is', fail)
if fail==3:
print('Did not find needed card in any visible hands. Appending next card to player', player)
queue.append(react(player, color, i, queue))
i+=1
fail=0
print('Looking for color %c and number %u.'%(colors[color],i+1)) #i+1 is external format
print('Returning queue', queue, '\n\n')
DeckOut=[colors[card[0]]+str(card[1]+1) for card in deck]
if queue:
print('Player %u is told that %s should play first'%(player,DeckOut[queue[0]]))
if len(queue)>1:
print('Then, %s should play second'%(DeckOut[queue[1]]))
if len(queue)>2:
print('Then, %s should play third'%(DeckOut[queue[2]]))
if len(queue)>3:
print('Then, %s should play fourth'%(DeckOut[queue[3]]))
if len(queue)>4:
print('Finally, %s should play last'%(DeckOut[queue[4]]))
return queue
In [ ]:
TEST() #TEST() is a good place to modify the deck, stacks, priorities, etc. and
#test out different scenarios.
play(0) #Colors are valued 0=r,y,g,b,w=4.
play(0) #Playing two reds makes r3 the next playable.
play(3) #Playing three blues makes b4 the next playable
play(3)
play(3)
RecordClue(2,4,0) #RecordClue(i,j,k) updates the arrays pertaining to public clue information
RecordClue(2,3,1) #...when clue type k (k=0:color; k=1:number) of value j is given to player i.
#Colors and Numbers are ordered 0=r,y,g,b,w=4 and 0=1,2,3,4,5=4(resp.).
#e.g. The first RecordClue updates public information pertaining to a
#...w clue to player 2.
#e.g. The second RecordClue updates public information pertaining to a
#...4 clue to player 2.
respond(2,1,4,2) #respond(i,j,k,l) evokes player i to respond to a color k/number l clue
#given by player j to, say, a 6th player that everyone can see.
#e.g. player 2 understands a "play 3w" clue (color=4, number=2) given
#...by player 1.