diff --git a/config.template b/config.template index 5b374f0..f13f463 100644 --- a/config.template +++ b/config.template @@ -57,6 +57,7 @@ SyslogToFile = False dopeWars = True lemonade = True blackjack = True +videopoker = True [sentry] # detect anyone close to the bot diff --git a/mesh_bot.py b/mesh_bot.py index 9460def..0d3902f 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -29,6 +29,7 @@ def auto_response(message, snr, rssi, hop, message_from_id, channel_number, devi "dopewars": lambda: handleDopeWars(message_from_id, message, deviceID), "lemonstand": lambda: handleLemonade(message_from_id, message), "blackjack": lambda: handleBlackJack(message_from_id, message), + "videopoker": lambda: handleVideoPoker(message_from_id, message), "ask:": lambda: handle_llm(message_from_id, channel_number, deviceID, message, publicChannel), "askai": lambda: handle_llm(message_from_id, channel_number, deviceID, message, publicChannel), "joke": tell_joke, @@ -280,6 +281,45 @@ def handleBlackJack(nodeID, message): return msg +from modules.videopoker import * +def handleVideoPoker(nodeID, message): + global vpTracker + msg = "" + + # if player sends a L for leave table + if message.lower().startswith("l"): + logger.debug(f"System: VideoPoker: {nodeID} is leaving the table") + # add 16 hours to the player time to leave the table, this will be detected by bot logic as player leaving + for i in range(len(vpTracker)): + if vpTracker[i]['nodeID'] == nodeID: + vpTracker[i]['leaveTime'] = time.time() + 57600 + vpTracker[i]['cmd'] = "new" + else: + # Play Video Poker + msg = playVideoPoker(nodeID=nodeID, message=message) + + # get player's last command from tracker + last_cmd = "" + for i in range(len(vpTracker)): + if vpTracker[i]['nodeID'] == nodeID: + last_cmd = vpTracker[i]['cmd'] + + # find higest dollar amount in tracker for high score + if last_cmd == "new": + high_score = 0 + user = 0 + for i in range(len(vpTracker)): + if vpTracker[i]['highScore'] > high_score: + high_score = vpTracker[i]['highScore'] + user = vpTracker[i]['nodeID'] + if user != 0: + msg += f"\nHigh Score: {high_score} by {get_name_from_number(user)}" + + if last_cmd != "": + logger.debug(f"System: VideoPoker: {nodeID} last command: {last_cmd}") + + return msg + def handle_wxc(message_from_id, deviceID, cmd): location = get_node_location(message_from_id, deviceID) if use_meteo_wxApi and not "wxc" in cmd and not use_metric: @@ -563,6 +603,23 @@ def onReceive(packet, interface): # play the game send_message(handleLemonade(message_from_id, message_string), channel_number, message_from_id, rxNode) + for i in range(0, len(vpTracker)): + if vpTracker[i].get('nodeID') == message_from_id: + # check if the player has played in the last 8 hours + if vpTracker[i].get('time') > (time.time() - 28800): + playingGame = True + game = "VideoPoker" + if llm_enabled: + logger.debug(f"System: LLM Disabled for {message_from_id} for duration of game") + + #if time exceeds 8 hours reset the player + if vpTracker[i].get('time') < (time.time() - 28800): + logger.debug(f"System: VideoPoker: Resetting player {message_from_id}") + vpTracker.pop(i) + + # play the game + send_message(handleVideoPoker(message_from_id, message_string), channel_number, message_from_id, rxNode) + for i in range(0, len(jackTracker)): if jackTracker[i].get('nodeID') == message_from_id: # check if the player has played in the last 8 hours diff --git a/modules/blackjack.py b/modules/blackjack.py index b8b18a8..3e2f599 100644 --- a/modules/blackjack.py +++ b/modules/blackjack.py @@ -41,7 +41,7 @@ VALUES = { "A": 11, } -class Card: +class jackCard: def __init__(self, suit, rank): self.suit = suit self.rank = rank @@ -49,7 +49,7 @@ class Card: def __str__(self): return self.rank + " of " + self.suit -class Deck: +class jackDeck: """ Creating a Deck of cards and Deal two cards to both player and dealer. """ def __init__(self): @@ -78,7 +78,7 @@ class Deck: except ValueError: pass -class Hand: +class jackHand: """ Adding the values of player/dealer cards and change the values of Aces acc. to situation. """ def __init__(self): self.cards = [] @@ -98,7 +98,7 @@ class Hand: self.value -= 10 self.aces -= 1 -class Chips: +class jackChips: """ Player/dealer chips for making bets and Adding/Deducting amount in/from Player's total. """ def __init__(self): self.total = jack_starting_cash @@ -220,13 +220,13 @@ def playBlackJack(nodeID, message): # Initalize the Game msg, last_cmd = '', None p_win, d_win, draw = 0, 0, 0 - p_chips = Chips() - p_hand = Hand() - d_hand = Hand() + p_chips = jackChips() + p_hand = jackHand() + d_hand = jackHand() p_cards, d_cards = [], [] bet_money = 0 # Initalize the Cards - cards_deck = Deck() + cards_deck = jackDeck() cards_deck.shuffle() p_cards, d_cards = cards_deck.deal_cards() # Deal the cards to player and dealer diff --git a/modules/settings.py b/modules/settings.py index 6796f25..bb9d749 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -145,6 +145,7 @@ try: dopewars_enabled = config['games'].getboolean('dopeWars', True) lemonade_enabled = config['games'].getboolean('lemonade', True) blackjack_enabled = config['games'].getboolean('blackjack', True) + videoPoker_enabled = config['games'].getboolean('videoPoker', True) except KeyError as e: print(f"System: Error reading config file: {e}") diff --git a/modules/system.py b/modules/system.py index 8f0147a..da1b267 100644 --- a/modules/system.py +++ b/modules/system.py @@ -94,6 +94,12 @@ if blackjack_enabled: from modules.blackjack import * # from the spudgunman/meshing-around repo trap_list = trap_list + ("blackjack",) games_enabled = True + +# Video Poker Configuration +if videoPoker_enabled: + from modules.videopoker import * # from the spudgunman/meshing-around repo + trap_list = trap_list + ("videopoker",) + games_enabled = True # Games Configuration if games_enabled is True: @@ -106,6 +112,8 @@ if games_enabled is True: gamesCmdList += "LemonStand, " if blackjack_enabled: gamesCmdList += "BlackJack, " + if videoPoker_enabled: + gamesCmdList += "VideoPoker, " gamesCmdList = gamesCmdList[:-2] # remove the last comma # Scheduled Broadcast Configuration diff --git a/modules/videopoker.py b/modules/videopoker.py new file mode 100644 index 0000000..ba0439e --- /dev/null +++ b/modules/videopoker.py @@ -0,0 +1,364 @@ +# Port of https://github.com/devtronvarma/Video-Poker-Terminal-Game +# Adapted for Meshtastic mesh-bot by K7MHI Kelly Keeton 2024 +import random +import time +from modules.log import * + +vpStartingCash = 20 +vpTracker= [{'nodeID': 0, 'cmd': 'new', 'time': time.time(), 'cash': vpStartingCash, 'player': None, 'deck': None, 'highScore': 0, 'drawCount': 0}] + +# Define the Card class +class CardVP: + + card_values = { # value of the ace is high until it needs to be low + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + 10: 10, + 'Jack': 11, + 'Queen': 12, + 'King': 13, + 'Ace': 14 + } + + def __init__(self, suit, rank): + """ + :param suit: The face of the card, e.g. Spade or Diamond + :param rank: The value of the card, e.g 3 or King + """ + self.suit = suit.capitalize() + self.rank = rank + self.points = self.card_values[rank] + +# Function to output ascii version of the cards in a hand in the terminal +def drawCards(*cards, return_string=True): + """ + Instead of a boring text version of the card we render an ASCII image of the card. + :param cards: One or more card objects + :param return_string: By default we return the string version of the card, but the dealer hide the 1st card and we + keep it as a list so that the dealer can add a hidden card in front of the list + """ + # we will use this to prints the appropriate icons for each card + suits_name = ['Spades', 'Diamonds', 'Hearts', 'Clubs'] + suits_symbols = ['♠', '♦', '♥', '♣'] + + # create an empty list of list, each sublist is a line 2 lines for the card + lines = [[] for i in range(1)] + + for index, card in enumerate(cards): + # "King" should be "K" and "10" should still be "10" + if card.rank == 10: # ten is the only one who's rank is 2 char long + rank = str(card.rank) + space = '' # if we write "10" on the card that line will be 1 char to long + else: + rank = str(card.rank)[0] # some have a rank of 'King' this changes that to a simple 'K' ("King" doesn't fit) + space = ' ' # no "10", we use a blank space to will the void + # get the cards suit in two steps + suit = suits_name.index(card.suit) + suit = suits_symbols[suit] + + # add the individual card on a line by line basis + lines[0].append('{}{} '.format(rank, suit)) + + result = [] + result.append('1 2 3 4 5') # add the index for the cards to top row + for index, line in enumerate(lines): + result.append(''.join(lines[index])) + + # hidden cards do not use string + if return_string: + return '\n'.join(result) + else: + return result + +# Define Deck class +class DeckVP: + + def __init__(self): + self.cards = [] + self.build() + + # method for building the deck + def build(self): + for s in ['Spades', 'Diamonds', 'Hearts', 'Clubs']: + for v in range(2, 11): + self.cards.append(CardVP(s,v)) + for c in ["Jack", "Queen", "King", "Ace"]: + self.cards.append(CardVP(s,c)) + + # method to show cards in deck + def display(self): + for c in self.cards: + print(drawCards(c)) + + # method to shuffle cards in deck + def shuffle(self): + for i in range(len(self.cards) - 1, 0, -1): + r = random.randint(0, i) + self.cards[i], self.cards[r] = self.cards[r], self.cards[i] + + # method to draw card from the deck + def draw_card(self): + return self.cards.pop() + +# Define Player Class +class PlayerVP: + def __init__(self): + self.hand = [] + self.bankroll = 20 + + # Method for initial five-card draw + def draw_cards(self, deck): + for i in range(5): + self.hand.append(deck.draw_card()) + return self + + # Method for displaying player's hand + def show_hand(self): + msg = (drawCards( + self.hand[0], + self.hand[1], + self.hand[2], + self.hand[3], + self.hand[4])) + return msg + + # Method for placing a bet + def bet(self, ammount=0): + try: + bet = int(ammount) + except ValueError: + bet = 1 + + if bet > self.bankroll: + return "You can only bet the money you have. No strip poker here..." + self.bet_size = self.bankroll + elif bet > 5: + return "You can only bet 5 coins at most." + self.bet_size = 5 + else: + self.bet_size = bet + self.bankroll -= self.bet_size + + # Method for selecting cards to redraw + def redraw(self, deck, message): + # if message has single digit, then it is the card to redraw, else it is the list of cards to redraw with a comma + if len(message) == 1: + redraw_index = int(message) - 1 + self.hand[redraw_index] = deck.draw_card() + else: + redraw_list = [int(x) - 1 for x in message.split(',')] + for i in redraw_list: + self.hand[i] = deck.draw_card() + + + return self.show_hand() + + # Method for scoring hand, calculating winnings, and outputting message + def score_hand(self): + points = sorted([self.hand[i].points for i in range(5)]) + suits = [self.hand[i].suit for i in range(5)] + points_repeat = [points.count(i) for i in points] + suits_repeat = [suits.count(i) for i in suits] + diff = max(points) - min(points) + hand_name = "" + payoff = { + "Royal Flush": 10, + "Straight Flush": 9, + "Flush": 8, + "Full House": 7, + "Four of a Kind": 6, + "Three of a Kind": 5, + "Two Pair": 4, + "Straight": 3, + "Pair": 2, + "Bad Hand": -1, + } + + if 5 in suits_repeat: + if points == [10, 11, 12, 13, 14]: #find royal flush + hand_name = "Royal Flush" + self.bankroll += self.bet_size * payoff[hand_name] + self.bet_size + elif diff == 4 and max(points_repeat) == 1: # find straight flush w/o ace low + hand_name = "Straight Flush" + self.bankroll += self.bet_size * payoff[hand_name] + self.bet_size + elif diff == 12 and points[4] == 14: # find straight flush w/ace low + check = 0 + for i in range(1, 4): + check += points[i] - points[i - 1] + if check == 3: + hand_name = "Straight Flush" + self.bankroll += self.bet_size * payoff[hand_name] + self.bet_size + else: + hand_name = "Flush" + self.bankroll += self.bet_size * payoff[hand_name] + self.bet_size + else: + hand_name = "Flush" + self.bankroll += self.bet_size * payoff[hand_name] + self.bet_size + elif sorted(points_repeat) == [2,2,3,3,3]: # find full house + hand_name = "Full House" + self.bankroll += self.bet_size * payoff[hand_name] + self.bet_size + elif 4 in points_repeat: # find four of a kind + hand_name = "Four of a Kind" + self.bankroll += self.bet_size * payoff[hand_name] + self.bet_size + elif 3 in points_repeat: # find three of a kind + hand_name = "Three of a Kind" + self.bankroll += self.bet_size * payoff[hand_name] + self.bet_size + elif points_repeat.count(2) == 4: # find two-pair + hand_name = "Two Pair" + self.bankroll += self.bet_size * payoff[hand_name] + self.bet_size + elif 2 in points_repeat: # find pair + hand_name = "Pair" + self.bankroll += self.bet_size * payoff[hand_name] + self.bet_size + elif diff == 4 and max(points_repeat) == 1: # find straight w/o ace low + hand_name = "Straight" + self.bankroll += self.bet_size * payoff[hand_name] + self.bet_size + elif diff == 12 and points[4] == 14: # find straight w/ace low + check = 0 + for i in range(1, 4): + check += points[i] - points[i - 1] + if check == 3: + hand_name = "Straight" + self.bankroll += self.bet_size * payoff[hand_name] + self.bet_size + else: + hand_name = "Bad Hand" + self.bankroll += self.bet_size * payoff[hand_name] + else: # for everything else + hand_name = "Bad Hand" + self.bankroll += self.bet_size * payoff[hand_name] + + msg = "You have a {}. Your bankroll is now {} coins.".format(hand_name, self.bankroll) + self.hand = [] + return msg + + +def getLastCmdVp(nodeID): + last_cmd = "" + for i in range(len(vpTracker)): + if vpTracker[i]['nodeID'] == nodeID: + last_cmd = vpTracker[i]['cmd'] + return last_cmd + +def setLastCmdVp(nodeID, cmd): + for i in range(len(vpTracker)): + if vpTracker[i]['nodeID'] == nodeID: + vpTracker[i]['cmd'] = cmd + +def playVideoPoker(nodeID, message): + msg = "" + + # Initialize the player + if getLastCmdVp(nodeID) is None or getLastCmdVp(nodeID=nodeID) == "": + # create new player if not in tracker + logger.debug(f"System: VideoPoker: New Player {nodeID}") + vpTracker.append({'nodeID': nodeID, 'cmd': 'new', 'time': time.time(), 'cash': vpStartingCash, 'player': None, 'deck': None, 'highScore': 0, 'drawCount': 0}) + return f"Welcome to VideoPoker! you have {vpStartingCash} coins, Whats your bet?" + elif getLastCmdVp(nodeID) == "gameOver" or getLastCmdVp(nodeID) == "new": + # load the player bankroll from tracker + for i in range(len(vpTracker)): + if vpTracker[i]['nodeID'] == nodeID: + cash = vpTracker[i]['cash'] + return f"Welcome back to VideoPoker! you have {cash} coins, Whats your bet?" + + # Gather the player's bet + if getLastCmdVp(nodeID) == "new" or getLastCmdVp(nodeID) == "gameOver": + # Initialize shuffled Deck and Player + player = PlayerVP() + deck = DeckVP() + deck.shuffle() + drawCount = 0 + + # save player and deck to tracker + for i in range(len(vpTracker)): + if vpTracker[i]['nodeID'] == nodeID: + vpTracker[i]['player'] = player + vpTracker[i]['deck'] = deck + + msg = player.bet(str(message)) + if msg != None: + print(msg) + else: + setLastCmdVp(nodeID, "playing") + + # Play the game + if getLastCmdVp(nodeID) == "playing": + msg = '' + + player.draw_cards(deck) + msg += player.show_hand() + setLastCmdVp(nodeID, "redraw") + + # save player and deck to tracker + for i in range(len(vpTracker)): + if vpTracker[i]['nodeID'] == nodeID: + vpTracker[i]['player'] = player + vpTracker[i]['deck'] = deck + vpTracker[i]['drawCount'] = drawCount + + msg += f"\nDeal any cards? List them by number separated by commas" + return msg + + if getLastCmdVp(nodeID) == "redraw": + # load the player and deck from tracker + for i in range(len(vpTracker)): + if vpTracker[i]['nodeID'] == nodeID: + player = vpTracker[i]['player'] + deck = vpTracker[i]['deck'] + drawCount = vpTracker[i]['drawCount'] + + # if player wants to redraw cards, and not done so twice total + if message.lower().startswith("n"): + setLastCmdVp(nodeID, "endGame") + else: + if drawCount < 2: + msg = player.redraw(deck, message) + drawCount += 1 + setLastCmdVp(nodeID, "endGame") + # save player and deck to tracker + for i in range(len(vpTracker)): + if vpTracker[i]['nodeID'] == nodeID: + vpTracker[i]['player'] = player + vpTracker[i]['deck'] = deck + vpTracker[i]['drawCount'] = drawCount + return msg + else: + setLastCmdVp(nodeID, "endGame") + + if getLastCmdVp(nodeID) == "endGame": + msg = '' + # load the player and deck from tracker + for i in range(len(vpTracker)): + if vpTracker[i]['nodeID'] == nodeID: + player = vpTracker[i]['player'] + deck = vpTracker[i]['deck'] + + msg += player.score_hand() + + if player.bankroll < 1: + player.bankroll = vpStartingCash + return "Looks like you're out of money. Better luck next time!" + + # check if player has new high score + if player.bankroll > vpTracker[i]['highScore']: + vpTracker[i]['highScore'] = player.bankroll + msg += " You have a new personal high score of {} coins.".format(player.bankroll) + else: + msg += " Your personal high score is {} coins.".format(vpTracker[i]['highScore']) + + msg += "Place your Bet, 'L' to leave the game." + + setLastCmdVp(nodeID, "gameOver") + # reset player and deck in tracker + for i in range(len(vpTracker)): + if vpTracker[i]['nodeID'] == nodeID: + vpTracker[i]['player'] = None + vpTracker[i]['deck'] = None + vpTracker[i]['drawCount'] = 0 + + return msg + \ No newline at end of file