blackjack

- irc bot to play blackjack
git clone git://git.acid.vegas/blackjack.git
Log | Files | Refs | Archive | README | LICENSE

irc.py (13519B)

      1 #!/usr/bin/env python
      2 # BlackJack IRC Bot - Developed by acidvegas in Python (https://acid.vegas/blackjack)
      3 # irc.py
      4 
      5 import inspect
      6 import os
      7 import random
      8 import socket
      9 import ssl
     10 import threading
     11 import time
     12 
     13 import config
     14 import debug
     15 
     16 # Data Directories & Files (DO NOT EDIT)
     17 data_dir   = os.path.join(os.path.dirname(os.path.realpath(inspect.stack()[-1][1])), 'data')
     18 cheat_file = os.path.join(data_dir, 'cheat.txt')
     19 help_file  = os.path.join(data_dir, 'help.txt')
     20 
     21 # Card Types
     22 club	= ('♣','clubs')
     23 diamond = ('♦','diamonds')
     24 heart   = ('♥','hearts')
     25 spade   = ('♠','spades')
     26 
     27 # Deck Table (Name, ASCII, Value, Remaining Suits)
     28 deck = {
     29 	'ace'   : [None, 11, [club,diamond,heart,spade]],
     30 	'two'   : [None, 2,  [club,diamond,heart,spade]],
     31 	'three' : [None, 3,  [club,diamond,heart,spade]],
     32 	'four'  : [None, 4,  [club,diamond,heart,spade]],
     33 	'five'  : [None, 5,  [club,diamond,heart,spade]],
     34 	'six'   : [None, 6,  [club,diamond,heart,spade]],
     35 	'seven' : [None, 7,  [club,diamond,heart,spade]],
     36 	'eight' : [None, 8,  [club,diamond,heart,spade]],
     37 	'nine'  : [None, 9,  [club,diamond,heart,spade]],
     38 	'ten'   : [None, 10, [club,diamond,heart,spade]],
     39 	'jack'  : [None, 10, [club,diamond,heart,spade]],
     40 	'queen' : [None, 10, [club,diamond,heart,spade]],
     41 	'king'  : [None, 10, [club,diamond,heart,spade]]
     42 }
     43 
     44 # Formatting Control Characters / Color Codes
     45 bold        = '\x02'
     46 italic      = '\x1D'
     47 underline   = '\x1F'
     48 reverse     = '\x16'
     49 reset       = '\x0f'
     50 white       = '00'
     51 black       = '01'
     52 blue        = '02'
     53 green       = '03'
     54 red         = '04'
     55 brown       = '05'
     56 purple      = '06'
     57 orange      = '07'
     58 yellow      = '08'
     59 light_green = '09'
     60 cyan        = '10'
     61 light_cyan  = '11'
     62 light_blue  = '12'
     63 pink        = '13'
     64 grey        = '14'
     65 light_grey  = '15'
     66 
     67 def color(msg, foreground, background=None):
     68 	if background:
     69 		return '\x03{0},{1}{2}{3}'.format(foreground, background, msg, reset)
     70 	else:
     71 		return '\x03{0}{1}{2}'.format(foreground, msg, reset)
     72 
     73 class IRC(object):
     74 	def __init__(self):
     75 		self.ace_minus = False
     76 		self.hand      = None
     77 		self.last_move = 0
     78 		self.last_time = 0
     79 		self.player    = None
     80 		self.total     = 0
     81 		self.mini_deck = False
     82 		self.sock       = None
     83 
     84 	def action(self, chan, msg):
     85 		self.sendmsg(chan, '\x01ACTION {0}\x01'.format(msg))
     86 
     87 	def connect(self):
     88 		try:
     89 			self.create_socket()
     90 			self.sock.connect((config.connection.server, config.connection.port))
     91 			if config.login.network:
     92 				self.raw('PASS ' + config.login.network)
     93 			self.raw('USER {0} 0 * :{1}'.format(config.ident.username, config.ident.realname))
     94 			self.raw('NICK ' + config.ident.nickname)
     95 		except socket.error as ex:
     96 			debug.error('Failed to connect to IRC server.', ex)
     97 			self.event_disconnect()
     98 		else:
     99 			self.listen()
    100 
    101 	def create_socket(self):
    102 		family	= socket.AF_INET6 if config.connection.ipv6 else socket.AF_INET
    103 		self.sock = socket.socket(family, socket.SOCK_STREAM)
    104 		if config.connection.vhost:
    105 			self.sock.bind((config.connection.vhost, 0))
    106 		if config.connection.ssl:
    107 			self.sock = ssl.wrap_socket(self.sock)
    108 
    109 	def draw(self):
    110 		card_type = random.choice(list(deck.keys()))
    111 		remaining = deck[card_type][2]
    112 		while not remaining:
    113 			card_type = random.choice(list(deck.keys()))
    114 			remaining = deck[card_type][2]
    115 		card_suit = random.choice(remaining)
    116 		if card_suit in (heart,diamond):
    117 			card_color = red
    118 		else:
    119 			card_color = black
    120 		card_value = deck[card_type][1]
    121 		if self.mini_deck:
    122 			card = deck[card_type][0].replace('X', card_suit[0])
    123 			card = color(card, card_color, white)
    124 			self.hand.append(card)
    125 		else:
    126 			for i in range(5):
    127 				card = deck[card_type][0][i].replace('X', card_suit[0])
    128 				card = color(card, card_color, white)
    129 				self.hand[i].append(card)
    130 		deck[card_type][2].remove(card_suit)
    131 		self.total += card_value
    132 		if card_type == 'ace' and deck['ace'][1] != 1:
    133 			deck['ace'][1] = 1
    134 		return (card_type, card_suit)
    135 
    136 	def error(self, chan, msg, reason=None):
    137 		if reason:
    138 			self.sendmsg(chan, '[{0}] {1} {2}'.format(color('ERROR', red), msg, color('({0})'.format(str(reason)), grey)))
    139 		else:
    140 			self.sendmsg(chan, '[{0}] {1}'.format(color('ERROR', red), msg))
    141 
    142 	def event_connect(self):
    143 		self.setup_deck('normal')
    144 		if config.login.nickserv:
    145 			self.identify(self.username, config.login.nickserv)
    146 		if config.login.operator:
    147 			self.oper(config.ident.username, config.login.operator)
    148 		self.join(config.connection.channel, config.connection.key)
    149 
    150 	def event_disconnect(self):
    151 		self.sock.close()
    152 		self.reset()
    153 		time.sleep(10)
    154 		self.connect()
    155 
    156 	def event_kick(self, nick, chan, kicked):
    157 		if kicked == config.ident.nickname and chan == config.connection.channel:
    158 			time.sleep(3)
    159 			self.join(config.connection.channel, config.connection.key)
    160 
    161 	def event_message(self, nick, chan, msg):
    162 		if chan == config.connection.channel:
    163 			if not msg.startswith('.'):
    164 				if msg == '@help':
    165 					self.action(chan, 'Sending help in a private message...')
    166 					help = [line.strip() for line in open(help_file).readlines() if line]
    167 					for line in help:
    168 						self.sendmsg(chan, line)
    169 				elif msg == '@cheat':
    170 					self.action(chan, 'Sending cheat sheet in a private message...')
    171 					cheat_sheet = [line.strip() for line in open(cheat_file).readlines() if line]
    172 					for line in cheat_sheet:
    173 						self.sendmsg(chan, line)
    174 			else:
    175 				cmd  = msg.split()[0][1:]
    176 				args = msg[len(cmd)+2:]
    177 				if time.time() - self.last_time < 2:
    178 					self.sendmsg(chan, color('Slow down nerd!', red))
    179 				elif cmd == 'hit':
    180 					if self.player:
    181 						if self.player == nick:
    182 							card_type, card_suit = self.draw()
    183 							if self.mini_deck:
    184 								msg_str = ''
    185 								for i in self.hand:
    186 									msg_str += ' ' + i
    187 								self.sendmsg(chan, msg_str)
    188 							else:
    189 								for i in range(5):
    190 									msg_str = ''
    191 									for i in self.hand[i]:
    192 										msg_str += ' ' + i
    193 									self.sendmsg(chan, msg_str)
    194 							if self.total > 21:
    195 								if deck['ace'][1] == 1 and not self.ace_minus:
    196 									self.total	 = self.total - 10
    197 									self.ace_minus = True
    198 									if self.total > 21:
    199 										self.sendmsg(chan, '{0} {1}'.format(color('BUST!', red), color('You went over 21 and lost!', grey)))
    200 										self.reset()
    201 									else:
    202 										self.sendmsg(chan, '{0} {1}'.format(color('You drew a {0} of {1}! Your total is now:'.format(card_type, card_suit[1]), yellow),  color(str(self.total), light_blue)))
    203 										self.last_move = time.time()
    204 								else:
    205 									self.sendmsg(chan, '{0} {1}'.format(color('BUST!', red), color('You went over 21 and lost!', grey)))
    206 									self.reset()
    207 							else:
    208 								self.sendmsg(chan, '{0} {1}'.format(color('You drew a {0} of {1}! Your total is now:'.format(card_type, card_suit[1]), yellow),  color(str(self.total), light_blue)))
    209 								self.last_move = time.time()
    210 						else:
    211 							self.error(chan, 'You are not currently playing!', '{0} is playing still'.format(self.player))
    212 					else:
    213 						self.error(chan, 'You are not currently playing!')
    214 				elif cmd == 'mini':
    215 					if not self.player:
    216 						if self.mini_deck:
    217 							self.setup_deck('normal')
    218 							self.sendmsg(chan, '{0} {1}'.format(color('Mini deck has been', yellow), color('DISABLED', red)))
    219 						else:
    220 							self.setup_deck('mini')
    221 							self.sendmsg(chan, '{0} {1}'.format(color('Mini deck has been', yellow), color('ENABLED', green)))
    222 					else:
    223 						self.error(chan, 'You can not change the deck in game!')
    224 				elif cmd == 'play':
    225 					if not self.player:
    226 						self.player = nick
    227 						self.action(chan, 'Starting a game of blackjack with {0}!'.format(nick))
    228 						for i in range(2):
    229 							self.draw()
    230 						if self.mini_deck:
    231 							msg_str = ''
    232 							for i in self.hand:
    233 								msg_str += ' ' + i
    234 							self.sendmsg(chan, msg_str)
    235 						else:
    236 							for i in range(5):
    237 								msg_str = ''
    238 								for i in self.hand[i]:
    239 									msg_str += ' ' + i
    240 								self.sendmsg(chan, msg_str)
    241 						self.sendmsg(chan, '{0} {1}'.format(color('Your total is now:', yellow), color(str(self.total), light_blue)))
    242 						self.last_move = time.time()
    243 						threading.Thread(target=self.timer).start()
    244 					elif self.player == nick:
    245 						self.error(chan, 'You have already started a game, please finish or stop the game!'.format(self.player))
    246 					else:
    247 						self.error(chan, '{0} is currently playing a game, please wait!'.format(self.player))
    248 				elif cmd == 'stand':
    249 					if self.player:
    250 						if self.player == nick:
    251 							self.sendmsg(chan, 'You have chosen to stand with {0} as your total.'.format(self.total))
    252 						else:
    253 							self.error(chan, 'You are not currently playing!', '{0} is playing still'.format(self.player))
    254 					else:
    255 						self.error(chan, 'You are not currently playing!')
    256 				elif cmd == 'stop':
    257 					if self.player:
    258 						if self.player == nick:
    259 							self.action(chan, 'Ending current game with {0}!'.format(nick))
    260 							self.reset()
    261 						else:
    262 							self.error(chan, 'You are not currently playing!', '{0} is playing still'.format(self.player))
    263 					else:
    264 						self.error(chan, 'You are not currently playing!')
    265 			self.last_time = time.time()
    266 
    267 	def event_nick_in_use(self):
    268 		debug.error_exit('BlackJack is already running.')
    269 
    270 	def event_part(self, nick, chan):
    271 		if self.player == nick:
    272 			self.sendmsg(chan, 'The game with {0} has ended.'.format(color(self.nick, light_blue)))
    273 			self.reset()
    274 
    275 	def event_quit(self, nick):
    276 		if self.player == nick:
    277 			self.sendmsg(chan, 'The game with {0} has ended.'.format(color(self.nick, light_blue)))
    278 			self.reset()
    279 
    280 	def handle_events(self, data):
    281 		args = data.split()
    282 		if args[0] == 'PING':
    283 			self.raw('PONG ' + args[1][1:])
    284 		elif args[1] == '001': # Use 002 or 003 if you run into issues.
    285 			self.event_connect()
    286 		elif args[1] == '433':
    287 			self.event_nick_in_use()
    288 		elif args[1] in ('KICK','PART','PRIVMSG','QUIT'):
    289 			nick  = args[0].split('!')[0][1:]
    290 			if nick != config.ident.nickname:
    291 				if args[1] == 'KICK':
    292 					chan   = args[2]
    293 					kicked = args[3]
    294 					self.event_kick(nick, chan, kicked)
    295 				elif args[1] == 'PART':
    296 					chan = args[2]
    297 					self.event_part(nick, chan)
    298 				elif args[1] == 'PRIVMSG':
    299 					chan = args[2]
    300 					msg  = data.split('{0} PRIVMSG {1} :'.format(args[0], chan))[1]
    301 					if chan != config.ident.nickname:
    302 						self.event_message(nick, chan, msg)
    303 				elif args[1] == 'QUIT':
    304 					self.event_quit(nick)
    305 
    306 	def identify(self, username, password):
    307 		self.sendmsg('nickserv', f'identify {username} {password}')
    308 
    309 	def join(self, chan, key=None):
    310 		self.raw(f'JOIN {chan} {key}') if key else self.raw('JOIN ' + chan)
    311 
    312 	def listen(self):
    313 		while True:
    314 			try:
    315 				data = self.sock.recv(1024).decode('utf-8')
    316 				if data:
    317 					for line in (line for line in data.split('\r\n') if line):
    318 						debug.irc(line)
    319 						if line.startswith('ERROR :Closing Link:') and config.ident.nickname in data:
    320 							raise Exception('Connection has closed.')
    321 						elif len(line.split()) >= 2:
    322 							self.handle_events(line)
    323 				else:
    324 					debug.error('No data recieved from server.')
    325 					break
    326 			except (UnicodeDecodeError,UnicodeEncodeError):
    327 				debug.error('Unicode error has occured.')
    328 			except Exception as ex:
    329 				debug.error('Unexpected error occured.', ex)
    330 				break
    331 		self.event_disconnect()
    332 
    333 	def mode(self, target, mode):
    334 		self.raw(f'MODE {target} {mode}')
    335 
    336 	def raw(self, msg):
    337 		self.sock.send(bytes(msg + '\r\n', 'utf-8'))
    338 
    339 	def reset(self):
    340 		self.ace       = [False,False]
    341 		self.last_move = 0
    342 		self.player    = None
    343 		self.total     = 0
    344 		if self.mini_deck:
    345 			self.hand = []
    346 		else:
    347 			self.hand = {0:[],1:[],2:[],3:[],4:[]}
    348 		deck['ace'][1] = 11
    349 		for card in deck:
    350 			deck[card][2] = [club,diamond,heart,spade]
    351 
    352 	def sendmsg(self, target, msg):
    353 		self.raw(f'PRIVMSG {target} :{msg}')
    354 
    355 	def setup_deck(self, deck_type):
    356 		if deck_type == 'mini':
    357 			self.hand        = []
    358 			self.mini_deck   = True
    359 			deck['ace'][0]   = 'A X'
    360 			deck['two'][0]   = '2 X'
    361 			deck['three'][0] = '3 X'
    362 			deck['four'][0]  = '4 X'
    363 			deck['five'][0]  = '5 X'
    364 			deck['six'][0]   = '6 X'
    365 			deck['seven'][0] = '7 X'
    366 			deck['eight'][0] = '8 X'
    367 			deck['nine'][0]  = '9 X'
    368 			deck['ten'][0]   = '10X'
    369 			deck['jack'][0]  = 'J X'
    370 			deck['queen'][0] = 'Q X'
    371 			deck['king'][0]  = 'K X'
    372 		elif deck_type == 'normal':
    373 			self.hand        = {0:[],1:[],2:[],3:[],4:[]}
    374 			self.mini_deck   = False
    375 			deck['ace'][0]   = ('A      ','       ','   X   ','       ','      A')
    376 			deck['two'][0]   = ('2      ','   X   ','       ','   X   ','      2')
    377 			deck['three'][0] = ('3      ','   X   ','   X   ','   X   ','      3')
    378 			deck['four'][0]  = ('4      ','  X X  ','       ','  X X  ','      4')
    379 			deck['five'][0]  = ('5      ','  X X  ','   X   ','  X X  ','      5')
    380 			deck['six'][0]   = ('6      ','  X X  ','  X X  ','  X X  ','      6')
    381 			deck['seven'][0] = ('7      ','  X X  ','  XXX  ','  X X  ','      7')
    382 			deck['eight'][0] = ('8      ','  XXX  ','  X X  ','  XXX  ','      8')
    383 			deck['nine'][0]  = ('9      ','  XXX  ','  XXX  ','  XXX  ','      9')
    384 			deck['ten'][0]   = ('10     ','  XXX  ',' XX XX ','  XXX  ','     10')
    385 			deck['jack'][0]  = ('J      ','       ','   X   ','       ','      J')
    386 			deck['queen'][0] = ('Q      ','       ','   X   ','       ','      Q')
    387 			deck['king'][0]  = ('K      ','       ','   X   ','       ','      K')
    388 
    389 	def timer(self):
    390 		while self.player:
    391 			if time.time() - self.last_move > self.game_timeout:
    392 				self.sendmsg(config.connection.channel, '{0}, you took too long! The game has ended.'.format(self.player))
    393 				self.reset()
    394 				break
    395 			else:
    396 				time.sleep(1)
    397 
    398 BlackJack = IRC()