Farkle Fest 2k19: An AI dice game tournament

5

3

Farkle Fest 2k19

In this challenge, we will be playing the semi-popular dice game Farkle.

Farkle is a dice game that has players selecting to save dice or risk rerolling the die for more points to add to their total. Players should use probability, game-state considerations and opponent tendency to make their decisions.

How to play

Rules lifted from this PDF, which includes scoring. There are some differences that you should note after

Each player takes turns rolling the dice. When it's your turn, you roll all six dice at the same time. Points are earned every time you roll a 1 or 5, three of a kind, three pairs, a six-dice straight (1,2,3,4,5,6), or two triplets.

If none of your dice earned points, that's a Farkle! Since you earned no points, you pass the dice to the next player.

If you rolled at least one scoring die, you can bank your points and pass the dice to the next player, or risk the points you just earned during this round by putting some or all of the winning die (dice) aside and rolling the remaining dice. The remaining dice may earn you additional points, but if you Farkle, you lose everything you earned during the round.

If all of your dice contribute to the score you re-roll the dice and add to the points already rolled, but a farkle will lose all the points, including rollover. This is called a "hot-streak"

Scoring is based only on the dice in each roll. You can earn points by combining dice from different rolls. You can continue rolling the dice until you either Pass or Farkle. Then the next player rolls the six dice until they Pass or Farkle. Play continues until it is your turn again.

The final round starts as soon as any player reaches 20,000 or more points.

Scoring:

    five = 50
    one = 100
    triple 1 = 1000
    triple 2 = 200
    triple 3 = 300
    triple 4 = 400
    triple 5 = 500
    triple 6 = 600
    one thru six = 1500
    three pairs = 1500
    two triplets = 2500
    four of a kind = 1000
    five of a kind = 2000
    six of a kind = 3000
    farkle = 0
    three farkles in a row = -1000

Notes:

  • There are varying scoring tables. Use the one linked and rewritten above
  • Most online versions say something like "You cannot earn points by combining dice from different rolls". This is not how I play. If you roll triple sixes and reroll and end up with another six you now have quad sixes.
  • The ending score has been increased from 10,000 to 20,000. This increases the decision points and decreases randomness.
  • Some variants have a "threshold to start getting points" (often around 500). This would prevent you from keeping a roll of less than 500 if your score was 0 (or negative). This rule is not in effect.

Challenge

In this challenge, you will write a Python 3 program to play a three player, winner takes all glory game of Farkle

Your program will receive the state of the game, which contains:

    index (where you sit at the table)
    end_score (score required to win (20,000))
    game_scores (all the players current score)
    state_variable (space for you to put whatever you want)

    #round values, these change during the round
    round_score (score of the current set of dice)
    round_rollover (score from previous "hot streaks")
    round_dice (current dice)
    round_set_aside (dice you set aside before rolling)
    round_used_in_score (lets you know which dice are adding to your score)

Players should return an array showing which dice they want to set aside. This should be wrapped in a set_dice_aside(self) method

For instance, here's a player that will save dice that add to their score until the score they would get is 500 or greater

class GoForFiveHundred(Bot):
    def set_dice_aside(self):
        while (self.round_score + self.round_rollover) < 500:
            return self.round_used_in_score
        return [True, True, True, True, True, True]

Gameplay

The tournament runner can be found here: Farkle_Fest_2K19. Run main.py to run a tournament. I'll keep it updated with new submissions. Example programs can be found in farkle_bots.py. Lots of code was lifted from maxb, many thanks for that framework and code.

A tournament consists of 5000 games per 10 players (rounded up, so 14 players means 10,000 games). Each game will be three random players selected from the pool of players to fill the three positions. Players will not be able to be in the game twice.

Scoring

The winner of each game is the player with the most points at the end of the game. In the case of a tie at the end of a game all players with the maximum money amount are awarded a point. The player with the most points at the end of the tournament wins. I will post scores as I run the games.

The players submitted will be added to the pool. I added three dumb bots to start.

Caveats

Do not modify the inputs. Do not attempt to affect the execution of any other program, except via cooperating or defecting. Do not make a sacrificial submission that attempts to recognize another submission and benefit that opponent at its own expense. Standard loopholes are banned.

Limit the time taken by your bot to be ~1s per turn.

Submissions may not duplicate earlier submissions.

If you have any questions, feel free to ask.

Winning

The competition will stay open indefinitely, as new submissions are posted. However, I will declare a winner (accept an answer) based on the results two month after this question was posted (November 2nd).

Simulation of 5000 games between 5 bots completed in 675.7 seconds Each game lasted for an average of 15.987000 rounds 8 games were tied between two or more bots 0 games ran until the round limit, highest round was 48

+----------------+----+--------+--------+------+------+-------+
|Bot             |Win%|    Wins|  Played|   Max|   Avg|Avg win|
+----------------+----+--------+--------+------+------+-------+
|WaitBot         |95.6|    2894|    3028| 30500| 21988|  22365|
|BruteForceOdds  |38.7|    1148|    2967| 25150| 15157|  20636|
|GoForFiveHundred|19.0|     566|    2981| 24750| 12996|  20757|
|GoForTwoGrand   |13.0|     390|    2997| 25450| 10879|  21352|
|SetAsideAll     | 0.2|       6|    3027| 25200|  7044|  21325|
+----------------+----+--------+--------+------+------+-------+

+----------------+-------+-----+
|Bot             |   Time|Time%|
+----------------+-------+-----+
|BruteForceOdds  |2912.70| 97.0|
|WaitBot         |  58.12|  1.9|
|GoForTwoGrand   |  13.85|  0.5|
|GoForFiveHundred|  10.62|  0.4|
|SetAsideAll     |   7.84|  0.3|
+----------------+-------+-----+

And the results without WaitBot, which I'm still morally undecided on:

+----------------+----+--------+--------+------+------+-------+
|Bot             |Win%|    Wins|  Played|   Max|   Avg|Avg win|
+----------------+----+--------+--------+------+------+-------+
|BruteForceOdds  |72.0|    2698|    3748| 26600| 19522|  20675|
|GoForFiveHundred|37.2|    1378|    3706| 25350| 17340|  20773|
|GoForTwoGrand   |24.3|     927|    3813| 26400| 14143|  21326|
|SetAsideAll     | 0.1|       5|    3733| 21200|  9338|  20620|
+----------------+----+--------+--------+------+------+-------+
+----------------+-------+-----+
|Bot             |   Time|Time%|
+----------------+-------+-----+
|BruteForceOdds  |4792.26| 98.9|
|GoForTwoGrand   |  24.53|  0.5|
|GoForFiveHundred|  18.06|  0.4|
|SetAsideAll     |  13.01|  0.3|
+----------------+-------+-----+

Tyler Barron

Posted 2019-09-03T03:09:00.000

Reputation: 181

1Is it possible to have access to the score function or something like it, so that people don't have to write their own for every submission? – Jo King – 2019-09-03T04:05:22.763

@jo: yes. Triple ones will be 1000, apologies. I will expose that method to the bots, good idea – Tyler Barron – 2019-09-03T04:40:09.920

2I don't think you reset the number of farkles in a row when a player doesn't get a farkle – Jo King – 2019-09-03T06:04:40.387

3I've only read the rules once, but can a submission interact with others during a game? If not, this would likely be better as a [code-challenge] or something similar. – my pronoun is monicareinstate – 2019-09-03T07:10:54.223

3Do you need to put aside at least one die a round? The controller allows putting aside no dice. Also, can you reintroduce dice you have put aside (also allowed)? – Jo King – 2019-09-03T08:40:02.983

@JoKing: You are correct -- another good catch. I will reset the farkle count upon non-farkle. Your other question is a good one... I think your strategy is valid but I'll do some more research after work. Will respond to your strategy – Tyler Barron – 2019-09-03T12:27:19.297

@someone: You can, but indirectly. If another player is way ahead you can be more aggressive and vis versa. I think that level of interaction makes it valid for this type of competition but if you disagree I can change it – Tyler Barron – 2019-09-03T12:28:36.797

1@Tyler Are you sure it's a good idea to allow 1s per turn? You could be bruteforcing stuff (that is, simulating all your possible moves repeatedly and calculating averages or something), and that'd slow down the game to take a few seconds, and you have thousands of them. I am not sure about [koth] here, but I can see code-challenge has some advantages (for example, it's easier and faster to test and debug). – my pronoun is monicareinstate – 2019-09-03T14:40:05.817

1@someone sure let em brute force stuff – Tyler Barron – 2019-09-03T14:53:23.793

Feel free to update the challenge to invalidate WaitBot. I don't really think that that strategy should be valid – Jo King – 2019-10-07T05:21:55.897

Answers

1

BruteForceOdds

Here's that brute force bot:

from main import Controller
from farkle_bots import Bot

scoreDevice = Controller(1, 1, [], 0)

class BruteForceOdds(Bot):
    def set_dice_aside(self):
        stop_score = self.round_score + self.round_rollover
        if self.game_scores[self.index] + stop_score > max(self.game_scores + [self.end_score]):
            # Take the win
            return [True, True, True, True, True, True]

        using = self.round_used_in_score[:]
        if self.round_dice.count(5) < 3:
            for i in range(len(using)):
                if self.round_dice[i] == 5 and not self.round_set_aside[i] and using.count(True) > self.round_set_aside.count(True)+1:
                    using[i] = False
        if self.round_dice.count(1) < 3:
            for i in range(len(using)):
                if self.round_dice[i] == 1 and not self.round_set_aside[i] and using.count(True) > self.round_set_aside.count(True)+1:
                    using[i] = False
        if using.count(True) < 3 and self.round_rollover == 0:
            # Stopping here is basically never a good idea
            return using

        if bruteScore(self.round_dice, using, self.round_rollover) > stop_score:
            return using
        return [True, True, True, True, True, True]

def bruteScore(round_dice, set_aside, base_score):
    base_dice = []
    for i in range(len(round_dice)):
        if(set_aside[i]):
            base_dice.append(round_dice[i])
    totalCount = 0
    totalScore = 0
    for new_dice in allPossibleRolls(len(round_dice) - len(base_dice)):
        dice = base_dice + new_dice
        keep = [True]*len(base_dice)+[False]*len(new_dice)
        farkle, round_score, round_rollover, used_in_score = scoreDevice.score(dice, keep, base_score)
        totalCount += 1
        if not farkle:
            totalScore += round_score + round_rollover
    return totalScore / totalCount

def allPossibleRolls(length):
    if length == 0:
        yield []
        return
    for i in range(1,7):
        for l in allPossibleRolls(length-1):
            yield [i]+l

It only fares slightly better than the example program GoForFiveHundred.

EDIT: Made it a bit smarter. Now it does noticeably better than any of the example bots.

Brilliand

Posted 2019-09-03T03:09:00.000

Reputation: 1 166

Amazing! I'll run it against the other bots later today! – Tyler Barron – 2019-10-04T11:46:09.317

@TylerBarron Did you do it? I don't see it in the results listed in the question. – Brilliand – 2019-10-06T09:40:39.210

I did later than I hoped. It should have results now! I also may have a few other challengers incoming :) – Tyler Barron – 2019-10-07T16:08:37.560

1

WaitBot

class WaitBot(Bot):
	def set_dice_aside(self):
		return [self.round_rollover+self.round_score > 5000]*6

Try it online!

This basically takes no dice until the total round score is greater than 5000. Really, it's betting on getting hot streaks before farkles.

This wins about 97% of the time when put up against the default three bots.

Edit: Erm, this may not be valid, since you need to putting some or all of the winning die (dice) aside. I'm waiting on confirmation from the asker on this, since the controller allows it.

Jo King

Posted 2019-09-03T03:09:00.000

Reputation: 38 234

I just realised the TIO link you provided is useless without the given classes – The random guy – 2019-09-16T07:01:49.977