1 # the classic Indian pallanguli game AI
2 # http://library.thinkquest.org/26408/data/texts/34079638.shtml
6 __author__
= 'Sridhar Ratnakumar <http://nearfar.org/>'
11 MAXSEEDS
= BOARD_SIZE
* SEEDS_PER_CUP
17 def __init__(self
, store
):
18 self
.store
= store
# number of seeds on player's store
21 return Player(self
.store
)
24 "Distribute the seeds from my pristine store to the cups in the board."
30 for index
in range(seeds
/SEEDS_PER_CUP
):
31 self
.store
-= SEEDS_PER_CUP
34 def give(self
, seeds
):
35 self
.store
+= len(list(seeds
))
38 return '<Player: %d seeds>' % self
.store
44 def create(player1
, player2
):
45 ll
= [None] * (BOARD_SIZE
*2)
47 for player
, offset
in [(player1
, 0),
48 (player2
, BOARD_SIZE
)]:
49 for index
in player
.distribute():
50 ll
[offset
+ index
] = SEEDS_PER_CUP
58 "Pick all the seeds from `cup` and yield them one by one"
64 for index
in range(seeds
):
67 def next(self
, index
):
69 while self
[index
] == None:
74 "Return the count of seeds on both sides of the board"
75 return (sum([n
for n
in self
[:BOARD_SIZE
] if n
is not None]),
76 sum([n
for n
in self
[BOARD_SIZE
:] if n
is not None]))
78 def __getitem__(self
, index
):
79 if type(index
) is not slice: # __getslice__ too calls this function
80 index
= index
% len(self
)
81 return super(Board
, self
).__getitem
__(index
)
83 def __setitem__(self
, index
, value
):
84 index
= index
% len(self
)
85 super(Board
, self
).__setitem
__(index
, value
)
90 # return ('*' * stars) + ('_' * (SEEDS_PER_CUP-stars))
96 return 'B: %s\n %s\nA: %s\n' % (
97 ' | '.join([fill(n
) for n
in self
[:BOARD_SIZE
-1:-1]]),
99 ' | '.join([fill(n
) for n
in self
[:BOARD_SIZE
]]))
102 class EmptyCup(Exception): pass
104 class NoPossibleTurn(Exception): pass
106 def minimax(game
, player
, depth
):
107 if game
.endGame(player
) or depth
== 0:
108 return game
.evaluate(player
)
112 for cgame
in game
.allMoves(player
):
113 beta
= max(beta
, minimax(cgame
, not player
, depth
-1))
117 for cgame
in game
.allMoves(player
):
118 alpha
= min(alpha
, minimax(cgame
, not player
, depth
-1))
124 The Pallanguli Game object.
126 Player A is the good player represented by `True`
127 Player B is the bad player represented by `False`
130 def __init__(self
, pA
, pB
, board
, parentIndex
):
131 self
.pA
, self
.pB
= pA
, pB
132 self
.parentIndex
= parentIndex
133 self
.players
= {True: pA
, False: pB
}
134 self
.board
= board
or Board
.create(pA
, pB
)
136 def clone(self
, index
):
137 return Game(self
.pA
.clone(),
142 def cupOwner(self
, index
):
143 if (index
% len(self
.board
)) < BOARD_SIZE
:
144 return self
.players
[True]
146 return self
.players
[False]
148 def allMoves(self
, playerIndex
):
150 if playerIndex
is False:
153 player
= self
.players
[playerIndex
]
157 index
= self
.board
.next(index
)
158 if self
.cupOwner(index
) is not player
:
159 break # we invaded the other player's cups
161 if self
.board
[index
] == 0:
162 continue # no seeds to pick; skip
164 # for each valid cup of mine,
165 cgame
= self
.clone(index
)
166 cgame
.doTurn(index
, cgame
.players
[playerIndex
])
169 def endGame(self
, playerIndex
):
171 rack
= self
.board
[:BOARD_SIZE
]
173 rack
= self
.board
[BOARD_SIZE
:]
175 return len([n
for n
in rack
if n
is not None and n
> 0]) == 0
177 def evaluate(self
, playerIndex
):
178 return self
.players
[True].store
- self
.players
[False].store
180 def dopABestTurn(self
):
183 for cgame
in self
.allMoves(True):
184 value
= minimax(cgame
, False, MAXDEPTH
)
185 if bestIndex
is None or value
> alpha
:
186 bestIndex
, alpha
= cgame
.parentIndex
, value
187 self
.doTurn(bestIndex
, self
.players
[True])
190 def dopBTurn(self
, indexOfpB
):
191 self
.doTurn(BOARD_SIZE
+ indexOfpB
, self
.players
[False])
193 def doTurn(self
, cup
, player
):
194 "Do turn for player `player` from cup `cup`"
197 assert self
.cupOwner(cup
) is player
, \
198 'Player [%s] must move seed from his side only, not #%d' % \
204 # pick seeds from cup `index` and distribute it
205 for offset
in b
.pick(index
):
206 index
= b
.next(index
)
208 if b
[index
] != None: # valid cup?
212 self
.cupOwner(index
).give(b
.pick(index
))
214 index
= b
.next(index
)
217 index
= b
.next(index
)
219 # if the next cup is empty, pick all seeds from
221 player
.give(b
.pick(index
))
226 # ..else, continue playing
230 return '%sA,B: %s, %s\n' % (self
.board
,
235 def getFromUser(prompt
, default
):
236 q
= "%s (%s) " % (prompt
, default
)
237 return raw_input(q
).strip() or default
239 def toBool(userInput
):
240 if userInput
.lower() in ['y', 'yes', 'yep']:
242 if userInput
.lower() in ['n', 'no', 'nope']:
244 assert "Invalid userInput for toBool: %s" % userInput
246 def playPallanguli():
247 aSeeds
= int(getFromUser("Player A's seeds", MAXSEEDS
))
248 bSeeds
= int(getFromUser("Player B's seeds", MAXSEEDS
))
249 assert aSeeds
+ bSeeds
== 2*MAXSEEDS
251 g
= Game(Player(aSeeds
),
259 index
= int(getFromUser('Adversary chose:', 1))
261 print '--> ADVERSARY picked cup #%d' % index
264 pBStarts
= toBool(getFromUser('Adversary starts?', 'n'))
269 index
= g
.dopABestTurn()
270 print '--> YOU picked cup #%d' % (index
+1)
276 if __name__
== '__main__':