snipe

bot for a simple IRC game to make people say words
git clone https://a3nm.net/git/snipe/
Log | Files | Refs

snipe.py (6079B)


      1 #!/usr/bin/python3 -u
      2 
      3 import sys
      4 from random import choice
      5 from operator import attrgetter
      6 import re
      7 import pickle
      8 
      9 def fmt_score(score):
     10   return str(score) + " point" + ("s" if abs(score) != 1 else "")
     11 
     12 sys.stdin = sys.stdin.detach()
     13 
     14 num_missions = 3
     15 ranking_size = 5
     16 abort_penalty = 2
     17 mission_file = "missions"
     18 name = "arbitre"
     19 dump_file = "game.dmp"
     20 ignore = "bot"
     21 
     22 help = [
     23   "Chaque joueur a une liste secrète de mots qui lui rapportent des points.",
     24   "Lorsque quelqu'un les dit, il perd des points et le joueur en gagne.",
     25   "Si un mot est trop dur, dis-le moi et je t'en donne un autre contre "+
     26     fmt_score(abort_penalty),
     27   "Commandes : \"help\", \"info\", \"rank\""]
     28 
     29 def parse(line):
     30   m = re.match("\[([^]]*?)\] <([^>]*?)> (.*)", line)
     31   chan = m.group(1)
     32   speaker = m.group(2)
     33   message = m.group(3).lstrip()
     34   to_us = False
     35   address = name+":"
     36   if message.startswith(address):
     37     to_us = True
     38     message = message[len(address):].lstrip()
     39   if chan == speaker or chan == name:
     40     to_us = True
     41   return chan, speaker, message.rstrip(), to_us
     42 
     43 def split(message):
     44   return re.split("\W+", message, flags=re.UNICODE)
     45 
     46 def read_missions(name):
     47   f = open(name)
     48   result = []
     49   for line in f.readlines():
     50     mission = line.split(' ')
     51     mission[1] = int(mission[1])
     52     result.append(mission)
     53   f.close()
     54   return result
     55 
     56 
     57 def get_mission(missions, forbidden=[]):
     58    return choice([mission for mission in missions if mission[0] not in
     59         forbidden])
     60 
     61 def ranking(game):
     62   players = sorted(game.values(), key=attrgetter('score'))
     63   players.reverse()
     64   result = []
     65   pos = 1
     66   for player in players:
     67     if pos > ranking_size:
     68       break
     69     result.append("%s. *%s* avec %s" % (str(pos), player.sayname,
     70       fmt_score(player.score)))
     71     pos += 1
     72   return result
     73 
     74 def say_to(chan, f, message):
     75   if chan == name:
     76     say(f, message)
     77   else:
     78     say(chan, message)
     79 
     80 def say(chan, messages):
     81   for message in messages:
     82     print("[%s] %s" % (chan, message))
     83 
     84 class Player:
     85   @property
     86   def sayname(self):
     87     return self.name[0] + '_' + self.name[1:]
     88 
     89   def __init__(self, name):
     90     self.name = name
     91     self.score = 0
     92     self.missions = []
     93     self.spawn_missions()
     94 
     95   def spawn_missions(self):
     96     while len(self.missions) < num_missions:
     97       self.add_mission()
     98 
     99   def add_mission(self):
    100     self.missions.append(get_mission(missions, [mission[0] for mission in
    101       self.missions]))
    102 
    103   def private_message(self, messages):
    104     say(self.name, messages)
    105 
    106   def welcome(self):
    107     self.private_message(["Bienvenue "+self.name+" !"])
    108 
    109   def report(self):
    110     self.private_message(
    111         ["Ton score est de " + fmt_score(self.score),
    112           "Les mots à faire dire sont :"] +
    113         ["-> \"%s\" pour %s" % (mission[0], fmt_score(mission[1])) for
    114           mission in self.missions])
    115 
    116   def abort(self, word):
    117     found = False
    118     for i in range(len(self.missions)):
    119       if self.missions[i][0] == word:
    120         found = True
    121         self.missions.pop(i)
    122         self.score -= abort_penalty
    123         break
    124     self.spawn_missions()
    125     return found
    126 
    127   def succeed(self, word):
    128     found = 0
    129     for i in range(len(self.missions)):
    130       if self.missions[i][0] == word:
    131         mission = self.missions.pop(i)
    132         found = mission[1]
    133         self.score += found
    134         break
    135     # not until we talk again
    136     # self.spawn_missions()
    137     return found
    138 
    139 def to_unicode(data, errors='replace'):
    140   detection = chardet.detect(data)
    141   encoding = detection.get('encoding') or 'utf-16'
    142   return unicode(data, encoding, errors=errors)
    143 
    144 missions = read_missions(mission_file)
    145 
    146 game = {}
    147 
    148 ok = False
    149 try:
    150   f = open(dump_file, 'rb')
    151   ok = True
    152 except Exception as e:
    153   print("Could not open dump.", file=sys.stderr)
    154 
    155 if ok:
    156   game = pickle.load(f)
    157   f.close()
    158 
    159 try:
    160   while True:
    161     l = sys.stdin.readline()
    162     try:
    163       line = l.decode('utf8')
    164     except UnicodeDecodeError:
    165       line = l.decode('latin1')
    166     if (not line):
    167       raise ValueError
    168     chan, speaker, message, to_us = parse(line)
    169     if speaker.find(ignore) != -1:
    170       continue
    171     if speaker not in list(game.keys()):
    172       game[speaker] = Player(speaker)
    173     speaker = game[speaker]
    174     speaker.spawn_missions()
    175     if to_us:
    176       if message == "help":
    177         say_to(chan, speaker.name, help)
    178         speaker.report()
    179       elif message == "rank":
    180         say_to(chan, speaker.name, ranking(game))
    181       elif message == "info":
    182         speaker.report()
    183       else:
    184         if speaker.abort(message):
    185           say_to(chan, speaker.name,
    186               ["Tu as abandonné \""+message+"\" et perdu " +
    187               fmt_score(abort_penalty)])
    188           speaker.report()
    189         else:
    190           say_to(chan, speaker.name,
    191               ["Ce mot n'est pas dans ta liste. Pour de l'aide, fais " +
    192                "\"help\"."])
    193     else:
    194       words = split(message)
    195       todo = []
    196       for word in words:
    197         word = word.lower()
    198         for player in game.values():
    199           if player.name != speaker.name:
    200             score = player.succeed(word)
    201             if score > 0:
    202               lose = int(score / 2)
    203               msg = ["*** %s ! %s gagne %s et %s perd %s." % (
    204                 word, player.sayname, fmt_score(score), speaker.sayname,
    205                 fmt_score(lose))]
    206               say_to(chan, speaker.name, msg)
    207               todo.append((player.name,
    208                 "%s a dit \"%s\" et t'a fait gagner %s." % (speaker.name,
    209                   word, fmt_score(score))))
    210               todo.append((player.name,
    211                 "Un nouveau mot te sera proposé prochainement."))
    212               todo.append((speaker.name,
    213                 "Tu as dit \"%s\", tu as fait gagner %s à %s et tu as "
    214                 "perdu %s." % (word, fmt_score(score), player.name,
    215                   fmt_score(lose))))
    216               speaker.score -= lose
    217       for (player, msg) in todo:
    218         game[player].private_message([msg])
    219       for player in set([t[0] for t in todo]):
    220         game[player].report()
    221 
    222 finally:
    223   f = open(dump_file, 'wb+')
    224   pickle.dump(game, f)
    225   f.close()
    226