I put the AI together similarly to how I planned it earlier. I put in ways of determining which cards had better chances of making a meld, and dumping the card with the least chance and the highest value. I also kept track of discards and opponent cards, to determine how valuable a card is to the other player. I wasn’t sure of the optimal balance between these strategies, so I gave them all weights set in the constructor so I could adjust it easily. This ended up being a good idea.
The first step is drawing. I always draw a card, unless the card in the discard gives me a meld or adds to a current meld in my hand. This seemed like the simplest strategy, and an effective one too. I didn’t think there was a point in picking up cards that give me a chance for melds in the future, because I could usually draw a card and have a chance of getting a meld immediately. Even if I couldn’t, there is still a good chance of getting a card that gets me closer to a meld, making both strategies even in that case. The other advantage is that even if I get closer to a meld, my opponent probably won’t drop any cards that would help me if they saw me picking up a card, so I would have to finish the meld with a draw from the deck anyway. It helps to not let the other player know my plan.
The next step is discarding. This step was actually made of several smaller steps. The first step was checking for big gin. Since we aren’t playing with actual big gin, I had to add a check for it and a way of dealing with it. I found that if you get big gin, there is always at least one card that you can discard and still get gin. I just go through my cards and find one that when discarded does just that. It’s unlikely, but I had to have some check for it otherwise my program would crash.
The next step is to check for cards that when discarded, would let me knock. If I find any cards that let me do that, I drop the card that will give me the lowest hand and knock. This isn’t that important, but helps get quick knocks. Any advantage I can get is welcome, especially if it isn’t that complicated or hard.
The real bulk of the process is choosing which card to discard. The first step is to find the value of each card in my hand. First, I make a copy of my hand, then remove all of the melds to find the deadwood. There is no point in discarding a card in a meld. After this, I check each card for its value in making future melds. If I pair with that card, I add the pair weight twice. I then look for the other cards that would finish the meld. If I find either of them in the discard or the other player’s hand, I subtract the pair weight for each. This means that if both cards are already known to be somewhere I can’t access, there is no benefit to this pair, and I am now back where I started. I do the same thing for runs, where if there are two available finishes to a run, I add the run weight twice, and once for only one available finish. I subtract the point value of the card times the value weight, making it more likely to drop higher cards.
The next step in this process is to find the value of the card to my opponent’s hand. I run the same process again, but for what I know of my opponent’s hand, multiplied by the opponent weight. This makes it less likely to drop cards that will likely help out my opponent. I then add this value to the other value, and discard the card with the lowest total value.
Passing cards was the simplest step. I just called my discard function and passed what it returned. I had to set the card to be tracked as in the opponent’s hand instead of the discard, but that was it.
I wasn’t sure what the value of each aspect was, so I set the meld weights to 1, the opponent weight to 1, and the value weight to .1, meaning that it would only ever break a tie. This worked alright for the dummy, and against the early AIs of some other students. I felt like this was good enough, and submitted my early AI like this.
This is where things start to get weird. Many people seemed to have trouble with the dummy, which just threw away high cards. With my weighting, mine worked pretty much the opposite way, keeping high cards as long as they start a meld in my hand. I lost most games against other people, but beat the dummy out of the tournament for everyone else. It made me think that if I just submitted the dummy, I would have a 50% chance of winning the tournament, because I would have to get a lucky win against the dummy and beat everyone else.
This obviously wouldn’t actually be a good strategy, but adjusting the weighting could make my AI play more like the dummy. I increased the value weight, and got better results the higher I went. Even with a value weight of 1, I still beat everyone I tested against, even the dummy. At this weight, I would throw away pairs if I had a single card a few points lower. I will try to find the best weight before the final tournament, because that is probably not the best strategy.
I could theoretically have it change the weights based on the score, because a higher weight prevents big losses, and a lower weight has a better chance of bigger wins. If I need to make a big comeback, I could switch to a higher weight and play for gins and undercuts for bonus points. Unfortunately, accessing the scores is not possible currently, so I couldn’t if I wanted to. It probably wouldn’t be that helpful anyway.
It’s funny that this is what I ended up doing, because my original strategy was to throw away high cards, even in pairs. I figured it couldn’t actually be a good strategy, and weighted my AI to play differently. It turned out to be far more effective.