Skip to content

Instantly share code, notes, and snippets.

@Mec-iS
Last active November 13, 2015 10:20
Show Gist options
  • Save Mec-iS/7db22cade8b01d0e1c10 to your computer and use it in GitHub Desktop.
Save Mec-iS/7db22cade8b01d0e1c10 to your computer and use it in GitHub Desktop.
An agent to play Liar's Dice game, based on an API that serve information about a game been played by four players
"""
Experiment for an automated agent playing Liar's Dice.
It's a partial implementation, some possible moves are not yet implemented.
Work time spent: 9 hours, considering also the time taken to learn basic rules
of the game.
It won multiple games beating computer opponents with an average score of 27,
where the average score for computer winners were 25.
Version 0.1
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Copyleft by Lorenzo Moriondo
"""
from operator import itemgetter
import api
class Solution:
def __init__(self):
self.move = 0 # move the players are playing
self.own_dice = None # dice rolled by player 0
self.bid = None # bid in place before actual move
self.best_hand = None # (occurences, value) best occurences/value pair
self.total_dices = None # number of dices in play
self.highest_rank = None # highest rolled dice
def bid_risk(self):
"""
Return the ratio 'occurences on total number of dice'.
Used to barely esteem the risk of a bid.
@return: a float
"""
return self.bid[0] / self.total_dices
def best_hand_strength(self):
"""
Return the ratio 'best rolled dice on total number of dice'.
Used to barely define how much to gamble a raise.
@return: a float
"""
if not self.total_dices:
self.total_dices = sum(api.get_num_dice(i) for i in range(4))
return self.best_hand[0] / self.total_dices
def player_strength(self):
"""
Return the ratio 'rolled dice on total number of dice'.
Used to barely esteemate a player's strength.
@return: a float
"""
if not self.own_dice:
self.own_dice = api.get_num_dice(0)
return 1 + self.own_dice / self.total_dices
@classmethod
def return_a_move(cls, values):
"""
Return a standard move from a pair of values.
@param values: a tuple of values from decide_raise()
@return: a int representing a move as required
"""
return int(str(int(values[0] // 1)) + str(values[1]))
def decide_raise(self, play):
"""
Define Bidding or raising.
@param play: a string representing one of the accepted styles
@return: a tuple of two values representing occurrences and a value
"""
# a dictionary that holds different playing styles for a move
raising = {
"safe": Solution.return_a_move(
(self.bid[0] + 1 * self.player_strength(),
self.bid[1])
),
"low": Solution.return_a_move(
(self.bid[0] + 2 * self.player_strength(),
self.bid[1])
),
"squeeze": Solution.return_a_move(
(self.bid[0] + 1 * self.player_strength(),
self.best_hand[1])
),
"switch": Solution.return_a_move(
(self.bid[0],
self.highest_rank[1])
),
"mild": Solution.return_a_move(
(self.bid[0] * 2 * self.player_strength() + 1,
self.bid[1])
),
"extreme": Solution.return_a_move(
(self.total_dices / 2 // 1,
self.bid[1])
)
}
print("raise: ", (play, raising[play]))
return raising[play]
def make_move(self, move):
"""
Return your move. The two main options are to raise the previous bid or
to challenge it. To challenge, return -1.
Star has die number zero.
For example, to bid "at least eleven of die number four" return 114.
To bid "at least three stars" return 30.
:type move: int
:rtype: int
"""
# total number of dice in play
self.total_dices = sum(api.get_num_dice(i) for i in range(4))
print("tot dice: ", self.total_dices)
def set_current_move(move=0):
"""
Find the current move in the moves sequence
"""
if api.get_player(move) == -1:
self.move = move
return self.move
else:
move += 1
return set_current_move(move)
# set the current move been played by player 0
set_current_move()
# get the current bid in place by the previous player
occurences = api.get_move_num_dice(self.move - 1) # dice number in bid
value = api.get_move_die(self.move - 1) # die value in the bid
if not self.bid or self.bid != (occurences, value):
self.bid = (occurences, value)
# dice rolled by player 0
self.hand = [api.get_die(i) for i in range(0,api.get_num_dice(0))]
print("move:", self.move)
# sort and order player 0's dice and get (occurrences, dice values) tuple
sort = sorted(
list((self.hand.count(x), x) for x in set(self.hand)),
key=itemgetter(1),
reverse=True
)
print("hand:", sort)
setattr(self, 'best_hand', max(sort, key=itemgetter(0)))
setattr(self, 'highest_rank', max(sort, key=itemgetter(1)))
print("max hand: ", self.best_hand)
print("highest rank: ", self.highest_rank[1])
print("bid: ", self.bid)
if self.bid == (-1, -1):
# player 0 is first to move:
# place a bid
# r = self.best_hand_strength()
if api.get_num_dice(0) <= 3:
return int(
str(int(self.total_dices / 2) // 1) + str(self.best_hand[1])
)
return int(
str(self.best_hand[0] + int(self.total_dices / 8 // 1 + 2)) + str(self.hand[1])
)
else:
# raise or challenge
# try to define how much the opponent is gambling.
r = self.bid_risk()
if self.bid[1] == 0:
# if a star is bid
if r > 0.22:
print("challenge")
return -1
else:
# raise the bid on star by one
return int(
str(self.bid[0] + 1) + str(self.bid[1])
)
# else:
# change bid to another die value
# TO-DO: to be implemented
elif r > 0.32:
# opponent is over-confident: challenge him.
print("challenge")
return -1
else:
# normal playing: choose a move.
if self.best_hand[1] <= self.bid[1]:
if self.best_hand_strength() > 0.25:
# just add 1 to the occurrence
return self.decide_raise("mild")
return self.decide_raise("safe")
elif self.best_hand[1] > self.bid[1]:
# add 1 to the occurrence and change the bid value
return self.decide_raise("squeeze")
elif self.best_hand[0] < self.bid[0]:
if self.highest_rank[1] > self.bid[1]:
return self.decide_raise("switch")
if self.bid_risk() > 0.3:
return -1
return self.decide_raise("safe")
else:
return self.decide_raise("low")
return self.decide_raise("extreme")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment