View Single Post
  #31  
Old 17th April 2023, 01:39 AM
CaptainDredlokk CaptainDredlokk is offline
Satan
 
Join Date: Oct 2010
Location: Hell
Total Awards: 1
You're Appreciated! 
I managed to get the first bit of functionality out of the CPU opponent in my poker game. It's able to look at its cards and "bluff" as to how good or bad its hand is if it has a high enough bluff stat paired with a high enough dice roll. As it currently stands, an AI with a perfect stat and an AI with a stat as low as possible can both successfully perform an action and blunder if they roll a 100 or a 0 respectively. I kinda like how this works and want to continue to use it, as it's cool to think that even an underdog who is truly bad at bluffing, has poor perception, or is overly-careful can still pull a brilliant move out of nowhere and shake up the game - keeps the player unpredictable also.

After doing so, it then has the option to draw cards. I need to add a betting phase before this, however I haven't refined how I want the betting function to work just yet. I also need to look at how bluffing works in relation to drawing and discarding cards - bluffing your hand as exceptional makes little sense if you're just going to drop cards. I'm thinking a combination of stats can be used here, where if the player has a hand that's "not great but serviceable" based on their perception roll and a high enough action score (what the CPU uses to decide if it's going to bet/call, raise, or fold) in conjunction with its aggressiveness roll, and their bluff roll will be able to bypass drawing and play the "my hand is so good I really don't need to draw" gambit, vs a player who has just as good of an initial bluff roll but really no good hand or isn't quite as ballsy as another CPU in terms of stats, (and rolls) it will instead pass a poker face that is impossible to be read by opponents. As of right now, the CPU simply passes the inverse of its hand value, unless its hand is average (as the inverse of average is just average in the code logic) in which case it passes a "poker face."

It took a minute to really wrap my head around the decision tree for drawing and discarding. As of now, the CPU will try and go for the most statistically likely hand - if it has a pair already, it'll try and build upon that pair/trips. If it has nothing, it will then look at its hand to see how many "approaching straights" and "approaching flushes" it can find. If there is a tie, (3 cards of the same suit vs 3 cards within 5 values of eachother) it will try for a flush, since it's much more likely to get a flush than a straight. Then it tries for a flush. It will then group all of the cards to be kept into "good cards" and all of the cards to be discarded into "bad cards," and parse the positions of the bad cards from the physical hands into a vector called "selections," which is passed to the function by address and back to the calling function to be used by the existing hand discard method, slightly rearranged for CPU usage.

Eventually I wanna play with the CPU stats a bit more - maybe make an exceptionally aggressive player who has a lower perception (which kind of serves as the "intelligence" stat) more likely to say "screw this pair - I wanna go right for a flush" - but I'm a bit worried that over-complicating the draw function is going to make it perform extremely poor randomly. It would definitely add to the unpredictability of it, but I don't want that to be at the cost of random bouts of incoherent behavior.



All four players (P1 is user, P2-4 are CPU's) putting in a $5 ante, getting their cards dealt, and all but the first player pass tells to the table - some of them are bluffs, some of them are not (I think player 2 bluffs - awful hand with a poker face that says otherwise, but I didn't save the screenshot)



Player 3 discarding cards 1, 2, and 5 (or 0, 1, and 4 in the actual data structure) due to them not matching the pair that they already have, and managing to turn their pair of 8's into a three of a kind. (As well as giving a new tell when they get their hand) All of the text displayed here including the hand is of course going to be behind the scenes in the final product - it's displayed for testing and verification purposes here however to make sure everything is behaving correctly. (hence the temporary "this is a test" and iter counts lol) There are a ton of moving parts and data to these functions so I had a lot of bug fixing and flow-charting to get it to take the shape of something I was happy with.



Player 4 finds no pairs, so it sends its entire hand to the "bad cards" vector, and creates two copies of it - flushCheck and straightCheck - so it can modify them during each pass without losing the order from the bad cards. It then goes through flushCheck and straightCheck and removes sets of cards - all matching suit in the flush vector, and (from left to right) sets of cards that are no more than 4 lower than the highest card. (which is always the leftmost card) - and pushes them to the back of a vector of card vectors - flushCards and straightCards (I'm horrible at variable naming lol) - then repeats the process until it has removed all cards from flushCheck and straightCheck. Then it finds the biggest sub-vector among flushCards and straightCards (with ties being broken by the largest flush) and if there are ties, chooses the first element in the supervector. (as due to the sorting, the first is always going to comprise of the leftmost cards, which are always the highest value cards on a "card rank" level) Then just like with pairs, parses the suit and value to the actual physical locations of the player's physical hand, passes the "bad cards" to the selection vector, and uses the selection vector to select the bad cards to discard.







Some code snippets under the hood.

It's really not anything too impressive at all, however it took a surprising amount of work to get this far. (work and school don't help with progress lol) I've got a lot of cleaning up and refining once I get a true MVP down, and I'd like to refactor everything in main into a "game" class that allows the player to choose between 5 card draw, 7 card stud, Texas Hold 'Em, and maybe some other card games too like Blackjack. Definitely want to refactor all of the raw pointers into smart pointers too, as I understand it's way safer to do so in modern iterations of C++. I'm pretty proud of it as my first real "big" project on my own however - learning a whole lot about the C++ syntax and how data behaves.
Reply With Quote