178 lines
6.4 KiB
Python
Executable File
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()
|