Last active
October 28, 2015 13:38
-
-
Save jm42/ac7316678fe76ba14dd4 to your computer and use it in GitHub Desktop.
Play littlealchemy.com
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""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