Skip to content

Instantly share code, notes, and snippets.

@jm42
Last active October 28, 2015 13:38
Show Gist options
  • Save jm42/ac7316678fe76ba14dd4 to your computer and use it in GitHub Desktop.
Save jm42/ac7316678fe76ba14dd4 to your computer and use it in GitHub Desktop.
Play littlealchemy.com
"""Play Little Alchemy"""
from selenium.webdriver import Firefox
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChains
from selenium.common.exceptions import StaleElementReferenceException, \
MoveTargetOutOfBoundsException
from os.path import join, dirname, abspath
from time import sleep
TOTAL_ELEMENTS = 550
class AlchemyState(object):
def __init__(self):
self.count = 0
self.live_elements = []
self.dead_elements = []
def loadElementsFromLibrary(self, library):
# Volvemos el contador a cero.
self.count = 0
# Vacíamos las listas, porque vamos a cargar todos los elementos de
# nuevo.
self.live_elements = []
self.dead_elements = []
# Buscamos todos los elementos con la clase "element" dentro del
# elemento dato (que debería ser el elemento con la clase "library".
for element in library.find_elements_by_class_name('element'):
# Creamos nuestro elemento y lo agregamos a la lista.
self.add(AlchemyElement.from_element(element))
def has(self, elem):
live_len = len([e for e in self.live_elements if e.id_ == elem.id_])
dead_len = len([e for e in self.dead_elements if e.id_ == elem.id_])
return live_len + dead_len > 0
def add(self, elem):
# Si ya tenemos este elemento, ni lo agregamos.
if self.has(elem):
return
# Si está muerto lo agramos a la lista de los muertos, sino a la
# lista de los vivos.
if elem.dead:
self.dead_elements.append(elem)
else:
self.live_elements.append(elem)
# Aumentamos en uno el contador.
self.count = self.count + 1
class AlchemyElement(object):
def __init__(self, id_, name):
self.id_ = str(id_)
self.name = name
self.dead = False
# Mantenemos dos listas, una de elementos a los cuales este elemento
# no es compatible así no tenemos que chequearlo cada vez.
self.incompatible = []
self.compatible = []
def already_combined_with(self, elem):
comp_len = len([e for e in self.compatible if e.id_ == elem.id_]) > 0
incp_len = len([e for e in self.incompatible if e.id_ == elem.id_]) > 0
return comp_len + incp_len > 0
def from_element(element):
elem = AlchemyElement(
element.get_attribute('data-elementid'),
element.text
)
# Vemos si tiene la clase "finalElement" para saber si está muerto
# o no.
elem.dead = 'finalElement' in element.get_attribute('class')
return elem
class LittleAlchemy(Firefox):
def start(self):
# Entar a la página es lo primero.
self.get('http://littlealchemy.com/')
# Espero a que el juego haya cargado y esté listo para hacer click en
# el boton de jugar. Espero como máximo 15 segundos.
WebDriverWait(self, 15).until(lambda x:
x.find_elements_by_class_name('progressBar')[0].text == 'PLAY')
# Clickeo en empezar a jugar sin Hugo.
self.find_elements_by_class_name('playButtonContainer')[0].click()
# Guardamos elementos que vamos a usar.
self.library = self.find_element_by_id('library')
self.workspace = self.find_element_by_id('workspace')
self.clear = self.find_element_by_id('clearWorkspace')
def apply_settings(self, settings):
self.find_element_by_id('menu').click()
self.find_elements_by_class_name('menusettings')[0].click()
for key, value in settings.items():
check = self.find_element_by_id('settings' + key)
if not check:
continue
label = self.find_element_by_xpath(
'//label[@for="settings' + key + '"]')
if (check.is_selected() and not value) \
or (not check.is_selected() and value):
label.click()
self.find_element_by_id('closePanel').click()
def search(self, text):
self.workspace.send_keys(text)
def move_from_library(self, n):
ActionChains(self) \
.move_to_element_with_offset(self.library, 25, n * 30) \
.click_and_hold() \
.move_to_element(self.workspace) \
.release() \
.perform()
def move_from_library_by_elementid(self, id_):
elements = self.library.find_elements_by_class_name('element')
position = 0
for element in elements:
if not element.is_displayed():
continue
position = position + 1
if element.get_attribute('data-elementid') == id_:
return self.move_from_library(position)
raise NameError('ID ' + str(id_) + ' not found')
def get_elements_in_workspace(self):
return self.workspace.find_elements_by_class_name('element')
def get_ids_in_workspace(self):
ids = []
for element in self.get_elements_in_workspace():
ids.append(element.get_attribute('data-elementid'))
return ids
def clear_workspace(self):
self.clear.click()
def find_element_in_workspace(self, id_):
return self.find_element_by_xpath(
"//div[@data-elementid='" + str(id_) + "']")
def main():
# Iniciamos un navegador nuevo y empezamos el juego.
driver = LittleAlchemy()
driver.start()
# Pequeña pausa para que el efecto cargue.
sleep(3)
# Configuramos el juego.
driver.apply_settings({
'CheckAlreadyCombined': True,
'HideFinalElements': True,
})
# Empezamos con el área de trabajo vacía.
driver.clear_workspace()
# Inicializamos la clase que va a almacenar el estado del juego a lo
# largo de la partida.
state = AlchemyState()
state.loadElementsFromLibrary(driver.library)
# Nos damos un primer respiro antes de entrarle con todo.
sleep(3)
# Mientras que no hayamos encontrado todos los elementos, seguimos jugando
# hasta ganar.
while state.count < TOTAL_ELEMENTS:
# Combinamos todos los elementos vivos con todos.
for elem_left in state.live_elements:
# Por cada otro elemento vamos a hacer lo mismo, esto va a hacer
# que algunos elementos se combinen y otros no.
for elem_right in state.live_elements:
# Si este elemento ya sabemos que es incompatible, ignorarlo.
if elem_left.already_combined_with(elem_right):
continue
# Buscamos el nombre del elemento y lo llevamos desde la libraria
# hasta el centro del area central.
driver.search(elem_left.name)
driver.move_from_library_by_elementid(elem_left.id_)
# Hacemos click en el área de trabajo para que vacíe el campo
# de búsqueda.
driver.workspace.click()
# Llevamos el otro elemento arriba del primero.
driver.search(elem_right.name)
driver.move_from_library_by_elementid(elem_right.id_)
# IDs de los elementos de entrada.
in_ids = [elem_left.id_, elem_right.id_]
in_ids.sort()
# IDs de los elementos de salida.
out_ids = driver.get_ids_in_workspace()
out_ids.sort()
# Si los IDs de entrada son iguales a los de salida,
# entonces nada cambió.
if in_ids == out_ids:
# No hay elemento encontrado.
elem_new = 'None'
# Lo agregamos a la lista de incompatibles para no volver
# a probar combinarlos nunca más.
elem_left.incompatible.append(elem_right)
elem_right.incompatible.append(elem_left)
# Sino, son compatibles.
else:
# Todos los IDs nuevos.
new_ids = [i for i in out_ids if i not in in_ids]
elem_new_l = []
for new_id in new_ids:
# Obtengo el nuevo elemento.
element = driver.find_element_in_workspace(new_id)
# Creamos el elemento.
elem = AlchemyElement.from_element(element)
elem_new_l.append(elem.name)
# Luego lo agregamos al estado.
state.add(elem)
# Lo agregamos a la lista de compatibles para no volver
# a probar combinarlos nunca más.
elem_left.compatible.append(elem)
elem_right.compatible.append(elem)
elem_new = ", ".join(elem_new_l)
print("{} + {} = {}".format(elem_left.name, elem_right.name,
elem_new))
sleep(0.25)
# Eliminamos todos los elementos.
driver.clear_workspace()
sleep(0.25)
# Si no hay más elementos, terminamos, pero no ganamos.
if len(state.live_elements) == 0:
break
# Saco un screenshot del juego ganado.
driver.save_screenshot(join(dirname(abspath(__file__)),
'{}.png'.format(state.count)))
# Terminamos el juego. Cerramos el navegador.
driver.quit()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment