Move tom to separate repository

This commit is contained in:
Joscha 2018-08-07 11:00:42 +00:00
commit 8403013338
3 changed files with 365 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
**/__pycache__
yaboli
websockets
*.cookie
*.conf

9
tom.conf.default Normal file
View file

@ -0,0 +1,9 @@
[general]
nick = tom
cookiefile = tom.cookie
[rooms]
# Format:
# room
# room=password
test

351
tom.py Normal file
View file

@ -0,0 +1,351 @@
import asyncio
import configparser
import logging
import re
import yaboli
from yaboli.utils import *
ELEMENTS = [
("H", "Hydrogen"),
("He", "Helium"),
("Li", "Lithium"),
("Be", "Beryllium"),
("B", "Boron"),
("C", "Carbon"),
("N", "Nitrogen"),
("O", "Oxygen"),
("F", "Fluorine"),
("Ne", "Neon"),
("Na", "Sodium"),
("Mg", "Magnesium"),
("Al", "Aluminium"),
("Si", "Silicon"),
("P", "Phosphorus"),
("S", "Sulfur"),
("Cl", "Chlorine"),
("Ar", "Argon"),
("K", "Potassium"),
("Ca", "Calcium"),
("Sc", "Scandium"),
("Ti", "Titanium"),
("V", "Vanadium"),
("Cr", "Chromium"),
("Mn", "Manganese"),
("Fe", "Iron"),
("Co", "Cobalt"),
("Ni", "Nickel"),
("Cu", "Copper"),
("Zn", "Zinc"),
("Ga", "Gallium"),
("Ge", "Germanium"),
("As", "Arsenic"),
("Se", "Selenium"),
("Br", "Bromine"),
("Kr", "Krypton"),
("Rb", "Rubidium"),
("Sr", "Strontium"),
("Y", "Yttrium"),
("Zr", "Zirconium"),
("Nb", "Niobium"),
("Mo", "Molybdenum"),
("Tc", "Technetium"),
("Ru", "Ruthenium"),
("Rh", "Rhodium"),
("Pd", "Palladium"),
("Ag", "Silver"),
("Cd", "Cadmium"),
("In", "Indium"),
("Sn", "Tin"),
("Sb", "Antimony"),
("Te", "Tellurium"),
("I", "Iodine"),
("Xe", "Xenon"),
("Cs", "Caesium"),
("Ba", "Barium"),
("La", "Lanthanum"),
("Ce", "Cerium"),
("Pr", "Praseodymium"),
("Nd", "Neodymium"),
("Pm", "Promethium"),
("Sm", "Samarium"),
("Eu", "Europium"),
("Gd", "Gadolinium"),
("Tb", "Terbium"),
("Dy", "Dysprosium"),
("Ho", "Holmium"),
("Er", "Erbium"),
("Tm", "Thulium"),
("Yb", "Ytterbium"),
("Lu", "Lutetium"),
("Hf", "Hafnium"),
("Ta", "Tantalum"),
("W", "Tungsten"),
("Re", "Rhenium"),
("Os", "Osmium"),
("Ir", "Iridium"),
("Pt", "Platinum"),
("Au", "Gold"),
("Hg", "Mercury"),
("Tl", "Thallium"),
("Pb", "Lead"),
("Bi", "Bismuth"),
("Po", "Polonium"),
("At", "Astatine"),
("Rn", "Radon"),
("Fr", "Francium"),
("Ra", "Radium"),
("Ac", "Actinium"),
("Th", "Thorium"),
("Pa", "Protactinium"),
("U", "Uranium"),
("Np", "Neptunium"),
("Pu", "Plutonium"),
("Am", "Americium"),
("Cm", "Curium"),
("Bk", "Berkelium"),
("Cf", "Californium"),
("Es", "Einsteinium"),
("Fm", "Fermium"),
("Md", "Mendelevium"),
("No", "Nobelium"),
("Lr", "Lawrencium"),
("Rf", "Rutherfordium"),
("Db", "Dubnium"),
("Sg", "Seaborgium"),
("Bh", "Bohrium"),
("Hs", "Hassium"),
("Mt", "Meitnerium"),
("Ds", "Darmstadtium"),
("Rg", "Roentgenium"),
("Cn", "Copernicium"),
("Nh", "Nihonium"),
("Fl", "Flerovium"),
("Mc", "Moscovium"),
("Lv", "Livermorium"),
("Ts", "Tennessine"),
("Og", "Oganesson"),
# Isotopes, but hey, they make the words look better
("D", "Deuterium"),
("T", "Tritium"),
# Non-elements and eastereggs
("E", "Euphorium"),
("Xyzzy", "Plugh"),
("Plugh", "Xyzzy"),
("hunter2", "*******"),
]
ELEMDICT = {symbol.lower(): name for (symbol, name) in ELEMENTS}
ELEMLENGTHS = set(len(symbol) for (symbol, _) in ELEMENTS)
class Node:
def __init__(self, stump):
self.stump = stump
self.edges = []
class Edge:
LETTER = 0
ELEMENT = 1
def __init__(self, start, end, weight, edgetype, text):
self.start = start
self.end = end
self.weight = weight
self.edgetype = edgetype
self.text = text
def elem_prefixes(text):
elems = []
for i in ELEMLENGTHS:
if len(text) < i: continue
start, rest = text[:i], text[i:]
elem = ELEMDICT.get(start.lower(), None)
if elem:
elems.append((elem, rest))
return elems
def create_graph(text):
graph = {}
stumps = [text[i:] for i in range(len(text))]
# goal node
graph[""] = Node("")
# creating all the nodes
for stump in stumps:
graph[stump] = Node(stump)
# creating single-letter links
weight = len(text)
for start in stumps:
char, end = start[:1], start[1:]
nstart = graph.get(start)
nend = graph.get(end)
edge = Edge(nstart, nend, weight, Edge.LETTER, char)
nstart.edges.append(edge)
# creating element links
for stump in stumps:
elems = elem_prefixes(stump)
for (elem, rest) in elems:
nstart = graph.get(stump)
nend = graph.get(rest)
edge = Edge(nstart, nend, 1, Edge.ELEMENT, elem)
nstart.edges.append(edge)
return graph
def smallest(graph, nodes):
smallest = None
for node in nodes:
if not smallest or node.length < smallest.length:
smallest = node
return smallest
def dijkstra(graph, start, end):
unvisited = set()
visited = set()
nstart = graph.get(start)
nstart.step = None
nstart.length = 0
for edge in nstart.edges:
node = edge.end
unvisited.add(node)
node.step = edge
node.length = edge.weight
visited.add(nstart)
while unvisited:
small = smallest(graph, unvisited)
unvisited.remove(small)
visited.add(small)
if small.stump == "":
break
for edge in small.edges:
node = edge.end
if node in visited: continue
length = small.length + edge.weight
if node in unvisited:
if length < node.length:
node.step = edge
node.length = length
else:
unvisited.add(node)
node.step = edge
node.length = length
path = []
node = graph.get(end)
while node.step:
path.append(node.step)
node = node.step.start
return reversed(path)
def format_path(path):
parts = []
part = ""
for edge in path:
if edge.edgetype == Edge.LETTER:
part += edge.text
else:
if part:
parts.append(part)
part = ""
parts.append(edge.text)
if part:
parts.append(part)
return "-".join(parts)
def shortest_path(text):
graph = create_graph(text)
path = dijkstra(graph, text, "")
return path
class Tom:
SHORT_DESCRIPTION = "spell a word using chemical elements, if possible"
DESCRIPTION = (
"'tom' attempts to spell a word using the symbols of the periodic table."
" For example, 'Hi' becomes 'Hydrogen Iodine'.\n"
"Because of this, only the letters a-z and A-Z may be used in a word.\n"
"'tom' uses en.wikipedia.org/wiki/List_of_chemical_elements for the element names.\n"
)
COMMANDS = "!tom <word> - attempts to spell the word using chemical elements\n"
AUTHOR = "Created by @Garmy using github.com/Garmelon/yaboli\n"
CREDITS = "Inspired by Tomsci. Algorithm based on a suggestion by Xyzzy.\n"
ALLOWED_CHARS = r"a-zA-Z*\w"
ELEM_RE = "([" + ALLOWED_CHARS + "]*)([^" + ALLOWED_CHARS + "]*)"
@yaboli.command("tom")
async def command_tom(self, room, message, argstr):
args = []
while argstr:
match = re.match(self.ELEM_RE, argstr)
if not match: break
args.append((match.group(1), match.group(2)))
argstr = argstr[match.end():]
spellings = []
for fst, snd in args:
if fst:
spelling = format_path(shortest_path(fst)) + snd
else:
spelling = snd
spellings.append(spelling)
text = "".join(spellings)
await room.send(text, message.mid)
class TomBot(yaboli.Bot):
SHORT_HELP = Tom.SHORT_DESCRIPTION
LONG_HELP = Tom.DESCRIPTION + Tom.COMMANDS + Tom.AUTHOR + Tom.CREDITS
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tom = Tom()
async def on_command_specific(self, room, message, command, nick, argstr):
if similar(nick, room.session.nick) and not argstr:
await self.botrulez_ping(room, message, command)
await self.botrulez_help(room, message, command, text=self.LONG_HELP)
await self.botrulez_uptime(room, message, command)
await self.botrulez_kill(room, message, command)
await self.botrulez_restart(room, message, command)
async def on_command_general(self, room, message, command, argstr):
if not argstr:
await self.botrulez_ping(room, message, command)
await self.botrulez_help(room, message, command, text=self.SHORT_HELP)
await self.tom.command_tom(room, message, command, argstr)
def main(configfile):
logging.basicConfig(level=logging.INFO)
config = configparser.ConfigParser(allow_no_value=True)
config.read(configfile)
nick = config.get("general", "nick")
cookiefile = config.get("general", "cookiefile", fallback=None)
bot = TomBot(nick, cookiefile=cookiefile)
for room, password in config.items("rooms"):
if not password:
password = None
bot.join_room(room, password=password)
asyncio.get_event_loop().run_forever()
if __name__ == "__main__":
main("tom.conf")