Last active
November 13, 2015 10:20
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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