1
0
Fork 0
selenium-plays-flippybitand.../play-v1.py

178 lines
6.4 KiB
Python
Executable File

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
import random
from pathlib import Path
from time import sleep
from typing import Dict, FrozenSet, Iterable, List, Set, TypeVar
from selenium import webdriver
from selenium.webdriver.remote.webelement import WebElement
_GT = TypeVar('_GT')
URL = 'https://flippybitandtheattackofthehexadecimalsfrombase16.com/'
BUFFER_SIZE = 48
BUFFER_PREPARE_SIZE = 1024
DISABLE_ANIMATIONS = True
JUST_TRUST_RANDOMNESS_STRATEGY = False
def main():
driver = webdriver.Firefox()
driver.set_window_size(525, 900)
try:
driver.get(URL)
sleep(0.25)
while driver.execute_script(
"return document.querySelector('#game-container') === null;"
):
sleep(0.1)
driver.execute_script(
"!function(){var tdtoremove = document.querySelector('td');tdtoremove.parentNode.removeChild(tdtoremove);delete tdtoremove;}();"
)
if DISABLE_ANIMATIONS:
driver.execute_script(
"""!function(){
document.body.parentNode.classList.add('novibrate');
var s = document.createElement('style');
var st = 'html { transform: none !important; } ';
st += '.missile.hover { animation-name: none !important; } ';
st += '.missile { transition-property: none !important; } ';
st += '.firing { left: 0px !important; bottom: 0px !important; display: none !important; } ';
st += '.explosion { left: 0px !important; bottom: 0px !important; display: none !important; } ';
st += '.enemy.under-attack { filter: hue-rotate(120deg); } ';
s.innerText = st;
document.head.appendChild(s);
}();"""
)
game = driver.find_element_by_css_selector("body")
game.send_keys(".") # unmute tab
sleep(0.1)
while driver.execute_script(
"return document.querySelector('#logo')?.style?.display !== 'block';"
):
sleep(0.1)
sleep(1.5)
screenshot_statup(driver, game)
game.send_keys("1 ")
while True:
main_cycle(driver, game)
finally:
driver.quit()
def main_cycle(driver: webdriver.Firefox, game: WebElement):
data = driver.execute_script(
"return [...document.querySelectorAll('.enemy:not(.under-attack)')].map(x=>x.innerText.trim()).join(',');"
)
data = list(map(lambda a: int(a, 16),
filter(len, data.split(','))))
if (datalen := len(data)) > 0:
print(f'New enemies: [{datalen}]{bytearray(data).hex().upper()}')
tosleep = 0.0
if datalen < BUFFER_SIZE:
tosleep = 0.1/(datalen**2)
deferred_keys = sort_keystrokes(
list(map(enemy_number_to_keys, data))[:BUFFER_PREPARE_SIZE])
for ks in deferred_keys[:BUFFER_SIZE]:
game.send_keys(ks+' ')
sleep(tosleep)
else:
sleep(0.1)
screenshot_and_restart_on_game_over(driver, game)
def shuffled(a: Iterable[_GT]) -> List[_GT]:
b = list(a)
random.shuffle(b)
return b
def sort_keystrokes(keystrokes: List[str], out_limit: int = BUFFER_SIZE, knowledge_limit: int = BUFFER_PREPARE_SIZE) -> List[str]:
if JUST_TRUST_RANDOMNESS_STRATEGY or len(keystrokes) <= 1:
return list(map(lambda a: ''.join(shuffled(a)), keystrokes))
keystrokes = keystrokes[:knowledge_limit]
known_activations: FrozenSet[FrozenSet[str]] = frozenset(
frozenset(a) for a in keystrokes)
activations: Dict[FrozenSet[str], List[int]] = dict(
(a, list()) for a in known_activations)
for i, k in enumerate(keystrokes):
activations[frozenset(k)].append(i)
del i
del k
out_ks: List[str] = list()
out_is: Set[int] = set()
ksi = 0
while len(out_ks) < len(keystrokes) and len(out_ks) < out_limit and len(out_ks) < knowledge_limit:
ks: List[str] = shuffled(keystrokes[ksi])
for sksi in range(len(ks)):
sks = ks[:sksi+1]
sksfs = frozenset(sks)
for sksai in sorted(activations.get(sksfs, [])):
if sksai not in out_is and ksi not in out_is:
out_ks.append(''.join(sks))
out_is.add(sksai)
ksi += 1
return out_ks[:out_limit][:knowledge_limit]
def enemy_number_to_keys(enemy_number: int) -> str:
keys = ('0'*8+bin(enemy_number)[2:])[-8:]
keypresses = list(map(
lambda x: x[0],
filter(
lambda x: x[1] == '1',
map(
lambda x: (f'{x[0]+1}', x[1]),
enumerate(keys)
))))
return ''.join(keypresses)
def screenshot_statup(driver: webdriver.Firefox, game: WebElement):
Path('scores').mkdir(exist_ok=True, parents=True)
score_path = Path('scores/startup.png')
driver.get_screenshot_as_file(str(score_path))
if not score_path.is_file():
raise FileNotFoundError(score_path)
def screenshot_and_restart_on_game_over(driver: webdriver.Firefox, game: WebElement):
if(driver.execute_script(
"return document.querySelector('html.game-over') !== null;"
)):
score = driver.execute_script(
"return parseInt(document.querySelector('#score').innerText.trim());"
)
print(f'{score=}')
sleep(3)
if(driver.execute_script(
"return document.querySelector('html.game-over') !== null;"
)):
Path('scores').mkdir(exist_ok=True, parents=True)
score = driver.execute_script(
"return parseInt(document.querySelector('#score').innerText.trim());"
)
print(f'{score=}')
print('\n'*3)
hiscore_path = Path('hiscore.txt')
if not hiscore_path.exists():
hiscore_path.write_text(str(-1), encoding='utf-8')
hiscore = int(
hiscore_path.read_text('utf-8').strip())
if score > hiscore:
score_path = Path('scores/screenshot_%04d.png' %
score).absolute()
game.screenshot(str(score_path))
if not score_path.is_file():
raise FileNotFoundError(score_path)
hiscore_path.write_text(str(score))
sleep(.2)
game.send_keys('1 1 ')
if __name__ == '__main__':
main()