--[[ This is a reasonably good player written by Jesse Hughes, adapted from the stock_ai included in gnome_hearts 0.1.3. This player has perfect recall of what has been played (which isn't exactly fair), but he's not the cleverest lad around. After all, he was written by a piss-poor hearts player.]] --[[ The script is first executed as soon as a player is loaded. This is the beginning of each game. So, use global code to set up your player. ]] math.randomseed = os.time() valid_cards = {} -- The following array keeps track of bad suits: a suit that some -- other player doesn't have. These suits can be dangerous to take. player_out_suits = {} seen_cards = {} player = 2 -- remove card from hand and add it to result function select_card(card, result, hand) local i = have_card(card, hand) if i then table.insert(result, card) table.remove(hand, i) end end function card_string(card) local s,r if card[suit] == 0 then s = "clubs" else if card[suit] == 1 then s = "diamonds" else if card[suit] == 2 then s = "hearts" else s = "spades" end end end if card[rank] == 1 or card[rank] == 14 then r = "Ace" else if card[rank] == 11 then r = "Jack" else if card[rank] == 12 then r = "Queen" else if card[rank] == 13 then r = "King" else r = card[rank] end end end end if r == nil or s == nil then return "??" end return table.concat({r, "of", s}, " ") end function print_card_list(hand) for _, card in ipairs(hand) do print(card_string(card)) end end -- Add all the cards played thus far to the list seen_cards function record_played_cards(trick) -- Record my own cards! if table.getn(seen_cards) == 0 then for _, card in ipairs(hand) do table.insert(seen_cards, card) end end for index = 1, 4 do local card = trick[index] if card ~= nil and not have_card(card, seen_cards) then table.insert(seen_cards,card) end end end -- returns true if the only unseen cards of same suit beat this card function is_giveaway_card(card) if table.getn(get_cards_of_suit(seen_cards,card[suit])) == 13 then return false end for i = 2, card[rank] do if not is_seen({card[suit],i}) then return false end end return true end -- returns true if the card has been seen function is_seen(card) return have_card(card, seen_cards) end -- returns true if the suit still has some scoring cards unseen, -- i.e., if the suit is either hearts or spades with an unplayed Queen. function is_scoring_suit(suit) if suit == hearts then return true else if suit == spades and (not is_seen({spades,queen}) or have_card({spades,queen}, hand)) then return true else return false end end end -- returns number of cards not accounted for function num_unseen(suit) -- print (13 - table.getn(get_cards_of_suit(seen_cards, suit)), "unseen for suit",suit) return 13 - table.getn(get_cards_of_suit(seen_cards, suit)) end -- returns number of players out of a suit (some player doesn't have it) -- Note: includes the player himself in this count, but that should be -- harmless. function num_players_out(suit) local num_bad = 0 for i = 0, 3 do if is_out_of_suit(suit, i) then num_bad = num_bad + 1 end end return num_bad end function is_out_of_suit(suit,player) for _, bs in ipairs(player_out_suits) do if suit == bs[1] and player == bs[2] then return true end end return false end -- return all clubs and diamonds function get_dull_suit_cards(list, include_bonus) local card, result = nil, {} if type(list) ~= "table" then return {} end for _, card in ipairs(list) do if card[suit] == clubs or card[suit] == diamonds then table.insert(result, {card[suit], card[rank]}) end end return result end function dump_cards_from_list(hand,list,result) table.sort(list, descending) while table.getn(result) < 3 and table.getn(list) > 0 do select_card(list[1],result,hand) table.remove(list,1) end end --[[ This function is called when you have to select three cards to pass to the -- next player. Return a list of three cards. ]] function select_cards() -- print_card_list(hand) -- If I have ace or king of spades, get rid of it. -- If I have fewer than four spades, get rid of the queen. -- If my lowest heart is greater than 6, get rid of every heart over 10. -- If I have fewer than three diamonds, get rid of as many as possible. -- If I have fewer than four clubs, get rid of as many as possible. -- Get rid of the highest diamonds/clubs. local result = {} local index = 0 local clubs_list = get_cards_of_suit(hand, clubs) local diamonds_list = get_cards_of_suit(hand, diamonds) local spades_list = get_cards_of_suit(hand, spades) local hearts_list = get_cards_of_suit(hand, hearts) -- first pass on the high spades if have_card({spades, ace_high}, hand) then select_card({spades, ace_high}, result, hand) end if have_card({spades, king}, hand) then select_card({spades, king}, result, hand) end if have_card({spades, queen},hand) and table.getn(spades_list) < 4 then select_card({spades, queen}, result, hand) end -- if our lowest heart is above six, get rid of all heart face cards. if table.getn(hearts_list) > 0 then table.sort(hearts_list, ascending) if hearts_list[1][rank] > six then while table.getn(result) < 3 and table.getn(hearts_list) > 0 do table.sort(hearts_list, descending) if hearts_list[1][rank] < jack then break else select_card(hearts_list[1],result,hand) end hearts_list = get_cards_of_suit(hand, hearts) end end end if table.getn(clubs_list) < table.getn(diamonds_list) then if table.getn(clubs_list) > 0 and table.getn(clubs_list) < 4 then dump_cards_from_list(hand,clubs_list,result) end if table.getn(diamonds_list) > 0 and table.getn(diamonds_list) < 4 then dump_cards_from_list(hand,diamonds_list,result) end else if table.getn(diamonds_list) > 0 and table.getn(diamonds_list) < 4 then dump_cards_from_list(hand,diamonds_list,result) end if table.getn(clubs_list) > 0 and table.getn(clubs_list) < 4 then dump_cards_from_list(hand,clubs_list,result) end end local tmp = get_dull_suit_cards(hand) dump_cards_from_list(hand,tmp,result) -- I shouldn't need to dump any more hearts or spades, but -- it's always possible that I was dealt only these two suits! dump_cards_from_list(hearts_list,tmp,result) dump_cards_from_list(spades_list,tmp,result) -- print ("Passing:") -- print_card_list(result) -- print "----" return result end -- sort two card lists by number of cards function ascending_len(one, two) assert(type(one) == "table", "sort ascending: one is not a list") assert(type(two) == "table", "sort ascending: two is not a list") if table.getn(one) < table.getn(two) then return true end return false end -- sort two card lists by number of cards descending function descending_len(one, two) assert(type(one) == "table", "sort ascending: one is not a list") assert(type(two) == "table", "sort ascending: two is not a list") if table.getn(one) > table.getn(two) then return true end return false end function unseen_above(card) local suit_seen = get_cards_of_suit(seen_cards, card[suit]) table.sort(suit_seen,descending) local count = 14 - card[rank] for _,c in ipairs(suit_seen) do if c[rank] > card[rank] then count = count - 1 else break end end return count end function spades_is_iffy() if have_card({spades,queen},hand) then return true end local tmp = get_cards_of_suit(hand,spades) table.sort(tmp,descending) if not is_seen({spades,queen}) and tmp[1][rank] > queen then return true end return false end function choose_lead_card() -- call a card a giveaway card if it is lower than any card in the -- suit not yaet seen and someone has a card in the suit not yet -- seen. If we lead with a giveaway card, someone else will take -- the trick. -- If I have the ace or king of a non-dangerous suit, then play it. -- If I do not have anything queen or above of spades and if spades -- are not bad, then play the highest spade below the queen. -- Play a giveaway heart. -- Even if spades are bad, play a low spade provided I have nothing -- above Jack in my hand. -- Play a giveaway card in any suit. Choose the suit with the most -- folks out of it. -- Pick the card that has the most trump cards above it unseen. -- (We may as well restrict ourselves to the lowest card of each -- suit.) ------------ -- If I have an ace or king of a non-dangerous suit and there are -- at least 8 unplayed cards of that suit, play it. -- print "--------------------------" -- print("Player", me, "picking from...") local valid_cards = get_valid_cards(hand) local cards = {} -- print_card_list(valid_cards) for i = 0, 3 do local tmp = get_cards_of_suit(valid_cards, i) if tmp ~= nil and table.getn(tmp) > 0 then table.insert(cards, tmp) end end table.sort(cards, descending_len) for _,list in ipairs(cards) do table.sort(list,descending) if list[1][rank] > queen and not is_scoring_suit(list[1][suit]) and num_players_out(list[1][suit]) == 0 and num_unseen(list[1][suit]) > 5 then -- print ("Seems safe to lead with",card_string(list[1])) return list[1] end end -- If I do not have anything queen or above of spades and if no one -- is out of spades, then play the highest spade below the queen. if num_players_out(spades) == 0 and not is_seen({spades,queen}) then for _,spadelist in ipairs(cards) do if spadelist[1][suit] == spades then table.sort(spadelist,descending) if spadelist[1][rank] < 12 then -- print ("Cough out a high spade:",card_string(spadelist[1])) return spadelist[1] end break end end end -- Play a giveaway heart. for _,heartlist in ipairs(cards) do if heartlist[1][suit] == hearts then table.sort(heartlist,ascending) if is_giveaway_card(heartlist[1]) then -- print("Giveaway:",card_string(heartlist[1])) return heartlist[1] end break end end -- Even if spades are bad, play a low spade provided I have nothing -- above Jack in my hand and the Queen is still out. if not is_seen({spades,queen}) then for _,spadelist in ipairs(cards) do if spadelist[1][suit] == spades then table.sort(spadelist,descending) if spadelist[1][rank] < 12 then table.sort(spadelist,ascending) -- print ("Cough out a low spade: ",card_string(spadelist[1])) return spadelist[1] end break end end end -- Play a giveaway card in any suit. Choose the suit with the most -- folks out of it. for _,list in ipairs(cards) do table.sort(list,ascending) if is_giveaway_card(list[1]) and (list[1][suit] ~= spades or list[table.getn(list)][rank] < queen) then -- print ("Giveaway:",card_string(list[1])) return list[1] end end -- Pick the card that has the most trump cards above it unseen. -- (We may as well restrict ourselves to the lowest card of each -- suit.) local best_suit = {} local count = 0 for _,challenger in ipairs(cards) do if challenger[1][suit] ~= spades or not spades_is_iffy() then -- If we have the Queen or above in Spades, then it's not the best bet, unless -- either there is no best_suit or the best_suit has count of 0 table.sort(challenger,ascending) if table.getn(best_suit) == 0 then best_suit = challenger count = unseen_above(best_suit[1]) -- print("Default best card",card_string(best_suit[1]),"count",count) else local c_count = unseen_above(challenger[1]) -- print(c_count,"unseen cards above",card_string(challenger[1])) if c_count > count then best_suit = challenger count = c_count end end end end if table.getn(best_suit) > 0 then -- try a non-points spade. if count == 0 then for _,challenger in ipairs(cards) do if challenger[1][suit] == spades then local ptless = get_pointless_cards(challenger) table.sort(ptless, ascending) if table.getn(ptless) > 0 and unseen_above(ptless[1]) > 0 then -- print("Might as well go with a spade:",card_string(ptless[1])) return ptless[1] end end end end -- print("Best bet:",card_string(best_suit[1])) return best_suit[1] else table.sort(valid_cards,ascending) -- print("Scraping", card_string(valid_cards[1])) return valid_cards[1] end end function avoid_trick(valid_cards,high_card) -- lifted from stock_ai. -- play the higest card that doesn't take the trick table.sort(valid_cards, descending) for _, card in ipairs(valid_cards) do if card[suit] ~= high_card[suit] or card[rank] < high_card[rank] then return card end end -- we can't dodge, so take it with the fewest points. -- If we're the last player, use a high card. Otherwise use a low -- card and hope that someone else gets it. local pointless_cards = get_pointless_cards(valid_cards) if trick_get_num_played() == 3 then table.sort(pointless_cards, descending) else table.sort(pointless_cards, ascending) end if table.getn(pointless_cards) > 0 then return pointless_cards[1] else -- Have to play a point, try to go low. table.sort(valid_cards, ascending) return valid_cards[1] end end function take_trick(valid_cards,high_card) -- if someone else will take the Queen, play it! -- Getting rid of the Queen is important. if have_card({spades,queen},valid_cards) and queen < high_card[rank] then return {spades,queen} end -- play the highest card that's not a point local pointless_cards = get_pointless_cards(valid_cards) if table.getn(pointless_cards) > 0 then table.sort(pointless_cards, descending) -- Try not to trump the Queen of Spades if high_card[suit] == spades and not have_card({spades,queen},seen_cards) then for _, card in ipairs(pointless_cards) do if card[rank] < queen then return card end end end return pointless_cards[1] else -- Have to play a point, try to go low. table.sort(valid_cards, ascending) return valid_cards[1] end end function dump_cards(valid_cards,high_card) -- We don't have the suit at all. First, try to dump high spades. if have_card({spades,queen},valid_cards) then return {spades,queen} else if have_card({spades,ace},valid_cards) and is_scoring_suit(spades) then return {spades,ace} else if have_card({spades,king},valid_cards) and is_scoring_suit(spades) then return {spades,king} else -- Dump high cards (above queen), any suit. table.sort(valid_cards, descending) if valid_cards[1][rank] > jack then return valid_cards[1] else -- Dump the highest point card we have. local point_cards = get_point_cards(valid_cards) if table.getn(point_cards) > 0 then table.sort(point_cards, points_descending) return point_cards[1] else return valid_cards[1] end end end end end end -- Called when we are the fourth player on the trick. function choose_final_card() -- Fourth: -- Have the suit: -- If there are no points in the trick, take it with the highest card we have. -- If there are points in the trick, play the highest card that won't take it. -- If we are forced to take it, take it with the highest card we have. -- Not have the suit: -- Play the Queen of Spades. -- Play the King or Ace of Spades -- Play the highest Queen or above card we have. -- Play the highest heart. -- Play the highest card we have. local valid_cards = get_valid_cards(hand) local high_card = get_highest_card() -- print "--" -- print("Player", me, "picking final card from...") -- print_card_list(valid_cards) if valid_cards[1][suit] == high_card[suit] then if trick_get_score() == 0 then -- print "I should take it." return take_trick(valid_cards,high_card) else -- print "I should avoid it." return avoid_trick(valid_cards,high_card) end else -- print "Dump." return dump_cards(valid_cards,high_card) end end -- return true if one of the remaining players doesn't have the suit -- of the trick and either (has hearts or queen of spades is not played) function is_dangerous_trick(high_card) if num_unseen(high_card[suit]) + trick_get_num_played() < 6 then -- print "Not enough cards! Dangerous!" return true end for i = 1,4 do local card = trick[i] if card == nil and is_out_of_suit(trick_get_trump,i) and (not is_out_of_suit(hearts,i) or not have_card({spades,queen},seen_cards)) then -- print "Upcoming player is dangerous!" return true end end return false end function choose_mid_card() -- Have the suit: -- If the suit is not dangerous and has at least 7 cards in other folks hands, -- play the highest card we have. -- Else, play the highest card that won't take the trick. -- Not have the suit: -- Play the Queen of Spades. -- Play the highest Queen or above card we have. -- Play the highest heart. -- If the suit is not dangerous and has at least 7 cards in other folks hands, -- play the highest card we have. local valid_cards = get_valid_cards(hand) local high_card = get_highest_card() -- print "--" -- print("Player", me, "picking middle card from...") -- print_card_list(valid_cards) if valid_cards[1][suit] == high_card[suit] then if trick_get_score() == 0 and not is_dangerous_trick(high_card) then -- print("I think I should take the trick.") return take_trick(valid_cards,high_card) else -- print("I think I should avoid the trick.") return avoid_trick(valid_cards,high_card) end else -- print("Dump.") return dump_cards(valid_cards,high_card) end end --[[ This function is called whenever you have to play a card. You must return a single variable: a valid card in the form of {suit, rank}. Use the function is_valid_card({suit, rank}) to check if it is valid to play that card. ]] function play_card() -- LEADING: -- Call a suit bad if I know that one player is out of that suit. -- Call a suit dangerous if either it contains an unseen scoring -- card (i.e., spades before the queen has been seen or hearts anytime) or -- it is bad. -- If I have the ace or king of a non-dangerous suit, then play it. -- If I do not have anything queen or above of spades and if spades -- are not bad, then play the highest spade below the queen. -- If I have a heart that I know will not take the trick, play it -- (if possible). (Play the highest heart that won't take the trick). -- Even if spades are bad, play a low spade provided I have nothing -- above Jack in my hand. -- Play the highest diamond/club that will not take the trick. -- FOLLOWING: -- Not fourth: -- Have the suit: -- If the suit is not dangerous and has at least 7 cards in other folks hands, -- play the highest card we have. -- Else, play the highest card that won't take the trick. -- Not have the suit: -- Play the Queen of Spades. -- Play the highest Queen or above card we have. -- Play the highest heart. -- Fourth: -- Have the suit: -- If there are no points in the trick, take it with the highest card we have. -- If there are points in the trick, play the highest card that won't take it. -- If we are forced to take it, take it with the highest card we have. -- Not have the suit: -- As above. record_played_cards(trick) if trick_get_num_played() == 0 then return choose_lead_card() else if trick_get_num_played() == 3 then return choose_final_card() else return choose_mid_card() end end end --[[ This function is called at the end of each trick so you can see what cards were played by all the players. Use trick_get_winner() to see who's won under the current ruleset and use trick_get_score() to see how much points the trick was worth. ]] function trick_end() local trump = trick_get_trump() for index = 1, 4 do local card = trick[index] if not is_out_of_suit(trump, index) then if card[suit] ~= trump then table.insert(player_out_suits,{trump,index}) break end end end record_played_cards(trick) end --[[ This function is called at the end of each round. ]] function round_end() player_out_suits = {} seen_cards = {} end