2024-07-03 22:37:05 -04:00
import discord
from discord import app_commands
import random
import logging
import sqlite3
class KnucklebonesGame :
def __init__ ( self , player1 , player2 , bet = 0 ) :
self . players = [ player1 , player2 ]
self . turn = 0
self . columns = { player1 : [ [ ] , [ ] , [ ] ] , player2 : [ [ ] , [ ] , [ ] ] }
self . scores = { player1 : 0 , player2 : 0 }
self . bet = bet
self . current_dice = None
def roll_dice ( self ) :
self . current_dice = random . randint ( 1 , 6 )
return self . current_dice
def place_dice ( self , player , dice , column ) :
column - = 1 # Adjust for 1-based index
2024-07-03 23:15:22 -04:00
self . columns [ player ] [ column ] . insert ( 0 , dice ) # Place at the top of the column
2024-07-03 22:37:05 -04:00
self . clear_matching_dice ( player , dice , column )
self . calculate_score ( player )
def clear_matching_dice ( self , player , dice , column ) :
opponent = self . other_player ( )
opponent_column = self . columns [ opponent ] [ column ]
self . columns [ opponent ] [ column ] = [ d for d in opponent_column if d != dice ]
def calculate_score ( self , player ) :
total_score = 0
for column in self . columns [ player ] :
if column :
column_score = sum ( column ) * len ( column )
total_score + = column_score
self . scores [ player ] = total_score
def next_turn ( self ) :
self . turn = ( self . turn + 1 ) % 2
def current_player ( self ) :
return self . players [ self . turn ]
def other_player ( self ) :
return self . players [ ( self . turn + 1 ) % 2 ]
def is_game_over ( self ) :
return all ( len ( col ) > = 3 for cols in self . columns . values ( ) for col in cols )
def winner ( self ) :
if self . scores [ self . players [ 0 ] ] > self . scores [ self . players [ 1 ] ] :
return self . players [ 0 ]
elif self . scores [ self . players [ 1 ] ] > self . scores [ self . players [ 0 ] ] :
return self . players [ 1 ]
else :
return None # It's a tie
2024-07-03 23:15:22 -04:00
def render_board ( self , perspective_player ) :
opponent = self . other_player ( ) if perspective_player == self . current_player ( ) else self . current_player ( )
2024-07-03 22:37:05 -04:00
board_str = " ``` \n "
2024-07-03 23:15:22 -04:00
board_str + = f " { opponent . display_name } ' s Board \n "
board_str + = self . render_player_board ( opponent , True )
board_str + = " --------- \n " # Separator between boards
board_str + = f " { perspective_player . display_name } ' s Board \n "
board_str + = self . render_player_board ( perspective_player , False )
2024-07-03 22:37:05 -04:00
board_str + = " ``` "
return board_str
def render_player_board ( self , player , is_opponent ) :
board_str = " "
2024-07-03 23:15:22 -04:00
for row in range ( 3 ) :
for col in range ( 3 ) :
if is_opponent :
dice = self . columns [ player ] [ col ]
else :
dice = list ( reversed ( self . columns [ player ] [ col ] ) )
if len ( dice ) > row :
board_str + = f " | { dice [ row ] } "
else :
board_str + = " | "
board_str + = " | \n "
2024-07-03 22:37:05 -04:00
return board_str
class Knucklebones :
def __init__ ( self , bot ) :
self . bot = bot
self . games = { }
self . logger = logging . getLogger ( ' Knucklebones ' )
self . logger . setLevel ( logging . DEBUG )
handler = logging . FileHandler ( filename = ' log/selena.log ' , encoding = ' utf-8 ' , mode = ' w ' )
handler . setFormatter ( logging . Formatter ( ' %(asctime)s : %(levelname)s : %(name)s : %(message)s ' ) )
self . logger . addHandler ( handler )
self . db_path = ' data/selena.db '
def setup ( self , tree : app_commands . CommandTree ) :
@tree.command ( name = " start_knucklebones " , description = " Start a game of Knucklebones " )
async def start_knucklebones_command ( interaction : discord . Interaction , opponent : discord . User = None , bet : int = 0 ) :
player1 = interaction . user
player2 = opponent or self . bot . user
if player1 == player2 :
await interaction . response . send_message ( " You cannot play against yourself! " , ephemeral = True )
return
if bet > 0 and not await self . has_enough_kibble ( player1 . id , bet ) :
await interaction . response . send_message ( " You do not have enough Kibble to place this bet. " , ephemeral = True )
return
game = KnucklebonesGame ( player1 , player2 , bet )
thread = await interaction . channel . create_thread ( name = f " Knucklebones: { player1 . display_name } vs { player2 . display_name } " , type = discord . ChannelType . public_thread )
self . games [ thread . id ] = game
if bet > 0 :
await self . deduct_kibble ( player1 . id , bet )
if player2 != self . bot . user :
await self . deduct_kibble ( player2 . id , bet )
2024-07-03 23:15:22 -04:00
await thread . send ( f " { player1 . mention } has started a game of Knucklebones against { player2 . mention } ! \n { player1 . mention } , it ' s your turn to roll the dice. " , view = RollDiceView ( self . bot ) )
2024-07-03 22:37:05 -04:00
@tree.command ( name = " check_score " , description = " Check the current score in Knucklebones " )
async def check_score_command ( interaction : discord . Interaction ) :
game = self . games . get ( interaction . channel_id )
if not game :
await interaction . response . send_message ( " There is no game in progress in this thread. " , ephemeral = True )
return
scores = [ f " { player . mention } : { score } " for player , score in game . scores . items ( ) ]
await interaction . response . send_message ( " Current scores: \n " + " \n " . join ( scores ) )
if not tree . get_command ( " start_knucklebones " ) :
tree . add_command ( start_knucklebones_command )
if not tree . get_command ( " check_score " ) :
tree . add_command ( check_score_command )
async def play_bot_turn ( self , channel , game ) :
dice = game . roll_dice ( )
column = random . randint ( 1 , 3 )
game . place_dice ( self . bot . user , dice , column )
if game . is_game_over ( ) :
winner = game . winner ( )
if winner :
await self . award_kibble ( winner . id , game . bet * 2 )
2024-07-03 23:15:22 -04:00
await channel . send ( f " { winner . mention } wins the game and { game . bet * 2 } Kibble! \n { game . render_board ( self . bot . user ) } " )
2024-07-03 22:37:05 -04:00
else :
2024-07-03 23:15:22 -04:00
await channel . send ( f " The game is a tie! \n { game . render_board ( self . bot . user ) } " )
2024-07-03 22:37:05 -04:00
del self . games [ channel . id ]
else :
game . next_turn ( )
2024-07-03 23:15:22 -04:00
await channel . send ( f " { self . bot . user . mention } rolled a { dice } and placed it in column { column } . \n It ' s now { game . current_player ( ) . mention } ' s turn! \n { game . render_board ( self . bot . user ) } " , view = RollDiceView ( self . bot ) )
2024-07-03 22:37:05 -04:00
async def has_enough_kibble ( self , user_id , amount ) :
conn = sqlite3 . connect ( self . db_path )
cursor = conn . cursor ( )
cursor . execute ( " SELECT kibble FROM currency WHERE user_id = ? " , ( user_id , ) )
row = cursor . fetchone ( )
conn . close ( )
return row and row [ 0 ] > = amount
async def deduct_kibble ( self , user_id , amount ) :
conn = sqlite3 . connect ( self . db_path )
cursor = conn . cursor ( )
cursor . execute ( " UPDATE currency SET kibble = kibble - ? WHERE user_id = ? " , ( amount , user_id ) )
conn . commit ( )
conn . close ( )
async def award_kibble ( self , user_id , amount ) :
conn = sqlite3 . connect ( self . db_path )
cursor = conn . cursor ( )
cursor . execute ( " UPDATE currency SET kibble = kibble + ? WHERE user_id = ? " , ( amount , user_id ) )
conn . commit ( )
conn . close ( )
2024-07-03 23:15:22 -04:00
class RollDiceView ( discord . ui . View ) :
def __init__ ( self , bot ) :
super ( ) . __init__ ( timeout = None )
self . bot = bot
@discord.ui.button ( label = " Roll Dice " , style = discord . ButtonStyle . primary )
async def roll_dice_button ( self , interaction : discord . Interaction , button : discord . ui . Button ) :
game = self . bot . knucklebones_module . games . get ( interaction . channel_id )
if not game :
await interaction . response . send_message ( " There is no game in progress in this thread. " , ephemeral = True )
return
if interaction . user != game . current_player ( ) :
await interaction . response . send_message ( " It ' s not your turn. " , ephemeral = True )
return
dice = game . roll_dice ( )
await interaction . response . send_message ( f " { interaction . user . mention } rolled a { dice } ! Choose a column to place it in. " , view = PlaceDiceView ( self . bot , dice ) )
class PlaceDiceView ( discord . ui . View ) :
def __init__ ( self , bot , dice ) :
super ( ) . __init__ ( timeout = None )
self . bot = bot
self . dice = dice
@discord.ui.button ( label = " Column 1 " , style = discord . ButtonStyle . secondary )
async def column_1_button ( self , interaction : discord . Interaction , button : discord . ui . Button ) :
await self . place_dice ( interaction , 1 )
@discord.ui.button ( label = " Column 2 " , style = discord . ButtonStyle . secondary )
async def column_2_button ( self , interaction : discord . Interaction , button : discord . ui . Button ) :
await self . place_dice ( interaction , 2 )
@discord.ui.button ( label = " Column 3 " , style = discord . ButtonStyle . secondary )
async def column_3_button ( self , interaction : discord . Interaction , button : discord . ui . Button ) :
await self . place_dice ( interaction , 3 )
async def place_dice ( self , interaction , column ) :
game = self . bot . knucklebones_module . games . get ( interaction . channel_id )
if not game :
await interaction . response . send_message ( " There is no game in progress in this thread. " , ephemeral = True )
return
if interaction . user != game . current_player ( ) :
await interaction . response . send_message ( " It ' s not your turn. " , ephemeral = True )
return
game . place_dice ( interaction . user , self . dice , column )
if game . is_game_over ( ) :
winner = game . winner ( )
if winner :
await self . bot . knucklebones_module . award_kibble ( winner . id , game . bet * 2 )
await interaction . response . send_message ( f " { winner . mention } wins the game and { game . bet * 2 } Kibble! \n { game . render_board ( interaction . user ) } " )
else :
await interaction . response . send_message ( f " The game is a tie! \n { game . render_board ( interaction . user ) } " )
del self . bot . knucklebones_module . games [ interaction . channel_id ]
else :
game . next_turn ( )
await interaction . response . send_message ( f " { interaction . user . mention } placed { self . dice } in column { column } . \n It ' s now { game . current_player ( ) . mention } ' s turn! \n { game . render_board ( interaction . user ) } " )
if game . current_player ( ) == self . bot . user :
await self . bot . knucklebones_module . play_bot_turn ( interaction . channel , game )
else :
await interaction . channel . send ( f " { game . current_player ( ) . mention } , it ' s your turn to roll the dice. " , view = RollDiceView ( self . bot ) )
2024-07-03 22:37:05 -04:00
def setup ( bot ) :
knucklebones = Knucklebones ( bot )
knucklebones . setup ( bot . tree )
bot . knucklebones_module = knucklebones