def clean_shop_till(): log = open(SALES_LOG_FILE, 'a') np = NeoPage() np.get('/market.phtml', 'type=sales') referer = np.referer if 'Nobody has bought anything' in np.content: print('No new sales.') else: tbl = re.search('''<table align=center width=530 cellpadding=3 cellspacing=0>(.*?)</table>''', np.content)[1] rows = util.table_to_tuples(tbl) total_sales = 0 for date, item, buyer, price in rows[1:-1]: price = util.amt(price) print(f'{date}: {buyer} bought {item} for {price} NP') log.write(f'{date},{buyer},{item},{price}\n') total_sales += price print(f'Total sales cleared: {total_sales} NP') print(f'Saved to {SALES_LOG_FILE}. Clearing history.') np.post('/market.phtml', 'type=sales', 'clearhistory=true') np.set_referer(referer) np.get('/market.phtml', 'type=till') amt = util.amt(re.search(r'''You currently have <b>(.*?)</b> in your till.''', np.content)[1]) if amt: print(f'Withdrawing {amt} NP from shop till.') np.post('/process_market.phtml', 'type=withdraw', f'amount={amt}') else: print(f'Shop till is empty.')
def scorchy_slots(): np = NeoPage() np.get(path) _ref_ck = np.search(r"<input type='hidden' name='_ref_ck' value='(.*?)'>")[1] np.post(path, f'_ref_ck={_ref_ck}', 'play=yes') while True:
def set_shop_prices(): np = NeoPage() g.items_stocked.clear() lim = 30 ub_count = 0 total_prices = 0 while True: np.get('/market.phtml', 'order_by=id', 'type=your', f'lim={lim}') has_next = "<input type='submit' name='subbynext' value='Next 30'>" in np.content args = [] args.append('type=update_prices') args.append('order_by=') args.append('view=') results = shop_item_re.findall(np.content) changed = False for (name, image, stock, category, obj_id_key, obj_id_val, old_cost_key, old_price, cost_key, desc, back_to_inv_key) in results: stock = int(stock) old_price = int(old_price) obj_id_val = int(obj_id_val) args.append(f'{obj_id_key}={obj_id_val}') args.append(f'{old_cost_key}={old_price}') g.items_stocked[obj_id_val] = stock my_price = old_price try: true_price = item_db.get_price(name, image, max_laxness=4, max_age=timedelta(days=7)) if true_price == None: pass elif type(true_price) == dict: print(f'Warning: {name} has multiple forms: {true_price}') elif true_price >= 1000000: ub_count += 1 print(f'Warning: {name} is unbuyable') my_price = 0 else: my_price = true_price - 1 except item_db.ShopWizardBannedException: pass if my_price != old_price: print(f'Setting {name} from {old_price} to {my_price} NP') changed = True total_prices += my_price args.append(f'{cost_key}={my_price}') args.append(f'{back_to_inv_key}=0') args.append('obj_name=') if changed: np.post('/process_market.phtml', *args) lim += 30 if not has_next: break print(f'Shop has {total_prices} NP worth of items for sale ({ub_count} unbuyable)')
def bank_interest(): path = '/bank.phtml' np = NeoPage(path) if np.contains('You have already collected your interest today.'): print('Already collected interest today.') elif np.contains('Collect Interest ('): amount = np.search(r'Collect Interest \((.*?)\)')[1] print(f"Collecting {amount} interest.") np.post('/process_bank.phtml', 'type=interest') else: print("Error collecting bank interest.")
def expellibox(): np = NeoPage(base_url=base_url) np.get(path) rand = random.randint(1, 99999) np.post(f'/games/giveaway/process_giveaway.phtml?r=rand', 'onData=[type Function]') log = open('expellibox.log', 'a') result = parse_qs(np.content) success = result['success'] prize_id = result['prize_id'] msg = result['msg'] print(rand, result) log.write(f'{rand},{result}\n')
def goto_level(lvl): np = NeoPage() #np.set_referer_path(path_game) #np.post(path_process, 'type=reset_this_thing', 'username={os.environ["NP_USER"]}') #print(np.last_file_path) #if shapeshifter(timeout=0) == 2: # print('Shapeshifter: Already done.') # return np.set_referer_path(path_game) np.post(path_process + '?type=init', f'past_level={lvl}') print(np.last_file_path)
def ensure_np(amount): # Withdraws from the bank to get up at least [amount] NP. np = NeoPage() np.get('/bank.phtml') if np.contains('Collect Interest ('): bank_interest.bank_interest() nps = np.current_np() if nps >= amount: return need = amount - nps denom = 10**max(len(str(need)), len(str(amount))) need = (need // denom + 1) * denom np.post('/process_bank.phtml', 'type=withdraw', f'amount={need}') print(f'Withdrawing {need} NP')
def anchor_management(): np = NeoPage(path) if np.contains('form-fire-cannon'): action = np.search('<input name="action" type="hidden" value="(.*?)">')[1] np.post(path, action=action) if np.contains('prize-item-name'): prize = np.search('<span class="prize-item-name">(.*?)</span>')[1] print(f'Blasted krawken; got {prize}') else: print('Blasted krawken; got unknown prize') elif np.contains('safe from sea monsters for one day'): print('Already did anchor management.') else: print("Couldn't find anchor management.")
def ensure_np(amount): # Withdraws from the bank to get up at least [amount] NP. np = NeoPage() np.get('/bank.phtml') if np.contains('Collect Interest ('): bank_interest.bank_interest() nps = np.search( r'''<a id='npanchor' href="/inventory.phtml">(.*?)</a>''')[1] nps = int(nps.replace(',', '')) if nps >= amount: return need = amount - nps denom = 10**max(len(str(need)), len(str(amount))) need = (need // denom + 1) * denom np.post('/process_bank.phtml', 'type=withdraw', f'amount={need}') print(f'Withdrawing {need} NP')
def quickstock(exclude=[]): # First list items to add them to the item db np = NeoPage() list_items() np.get('/quickstock.phtml') items = np.findall(r'''<TD align="left">(.*?)</TD><INPUT type="hidden" name="id_arr\[(.*?)\]" value="(\d+?)">''') args = [] args.append('buyitem=0') for name, idx, item_id in items: args.append(f'id_arr[{idx}]={item_id}') if name in exclude: continue policy = item_policy[name] if policy: args.append(f'radio_arr[{idx}]={policy}') np.post('/process_quickstock.phtml', *args)
def deposit_all_items(exclude=[]): # First list items to add them to the item db np = NeoPage() list_items() np.get('/quickstock.phtml') items = np.findall( r'''<TD align="left">(.*?)</TD><INPUT type="hidden" name="id_arr\[(.*?)\]" value="(\d+?)">''' ) args = [] args.append('buyitem=0') for name, idx, item_id in items: args.append(f'id_arr[{idx}]={item_id}') if name not in exclude: args.append(f'radio_arr[{idx}]=deposit') np.post('/process_quickstock.phtml', *args)
def anchor_management(): np = NeoPage(path) if np.contains('form-fire-cannon'): action = np.search( '<input name="action" type="hidden" value="(.*?)">')[1] np.post(path, action=action) if np.contains('prize-item-name'): prize = np.search('<span class="prize-item-name">(.*?)</span>')[1] print(f'Blasted krawken; got {prize}') else: print('Blasted krawken; got unknown prize') elif np.contains('safe from sea monsters for one day'): print('Already did anchor management.') else: print("Couldn't find anchor management.")
def lottery(): numbers_left = {n:4 for n in numbers} tickets = [] for _ in range(n_tickets): min_left = min(numbers_left.values()) population = [k for k, v in numbers_left.items() if v > min_left] #population = numbers if len(population) < N: population = [k for k, v in numbers_left.items() if v >= min_left] ticket = tuple(sorted(random.sample(population, N))) tickets.append(ticket) print(tickets) print(evaluate(tickets)) np = NeoPage() np.get(path) ref = np.referer for ticket in tickets: np.set_referer(ref) np.post(path_process, *[f'{x}={n}' for x, n in zip('one two three four five six'.split(), ticket)])
def jhudora_quest(): np = NeoPage(save_pages=True) np.get(path) if 'I Accept!' in np.content: # TODO: Search your SDB, gallery, shop, and Neohome Superstore for item as well. user = os.environ['NP_USER'] np.post(path_process, 'type=accept', f'username={user}') m = re.search(r"<br>Where is my <b>(.*?)</b>\?<p><img src='http://images.neopets.com/items/(.*?)'.*?>", np.content) item = m[1] image = m[2] print(f'Jhudora asked for {item} ({image})') cost = inventory.purchase(item, image=image) print(f'Bought it for {cost} NP.') np.set_referer_path(path) np.post(path_process, 'type=finished') if 'You have completed' in np.content: print(f'Quest shouuuuld be completed?') else: print(f"Quest doesn't seem to have completed") elif 'I am not ready' in np.content: print("Too early for Jhudora's Quest.") else: print("Jhudora's quest: Error!")
def update_prices(item_name, laxness=1): char_groups = 'an0 bo1 cp2 dq3 er4 fs5 gt6 hu7 iv8 jw9 kx_ ly mz'.split() c2g = dict(sum(([(c, i) for c in cs] for i, cs in enumerate(char_groups)), [])) markets = defaultdict(dict) ub_count = 0 search_count = 0 np = NeoPage('/market.phtml?type=wizard') opts = [] opts.append('type=process_wizard') opts.append('feedset=0') opts.append(f'shopwizard={item_name}') opts.append('table=shop') opts.append('criteria=exact') opts.append('min_price=0') opts.append('max_price=999999') # Repeatedly search the shop wizard, collecting all results seen. while not markets or any(len(market_data) < len(char_groups) - laxness for market_data in markets.values()): print(f'\r({sum(len(md) for md in markets.values())}/{len(markets) * len(char_groups)}) ', end='') np.post('/market.phtml', *opts) tbl = np.search(r'<table width="600".*?>(.*?)</table>') if not tbl: if np.contains('Whoa there, too many'): print('Shop wizard banned.') raise ShopWizardBannedException ub_count += 1 if ub_count >= 5: break continue tbl = tbl[1] rows = lib.table_to_tuples(tbl, raw=True)[1:] search_count += 1 if search_count >= 50: break market_data = [] obj_info_id = None for owner, item, stock, price in rows: result = re.search(r'<a href="(.*?)"><b>(.*?)</b></a>', owner) link = result[1] owner = result[2] result = re.search(r'/browseshop.phtml\?owner=(.*?)&buy_obj_info_id=(.*?)&buy_cost_neopoints=(\d+)', link) obj_info_id = int(result[2]) price = lib.amt(lib.strip_tags(price)) stock = lib.amt(stock) market_data.append((price, stock, link)) g = c2g[lib.strip_tags(rows[0][0])[0]] markets[obj_info_id][g] = market_data level2_by_item = {} # Consolidate results for each item into a quote. if markets: for obj_info_id, market_data in markets.items(): # TODO: Check suspiciously cheap prices to see if owners are frozen. level2 = sorted(sum(market_data.values(), [])) level2_by_item[obj_info_id] = level2 # The price of an item for our purposes is the price of the 2nd # cheapest item in the market. cur_amt = 0 cur_price = UNBUYABLE_PRICE for price, stock, link in level2: cur_price = price cur_amt += stock if cur_amt >= 2: break print(f'The price of {item_name} (id {obj_info_id}) is {cur_price} NP.') cur_amt = 0 for price, stock, link in level2: cur_amt += stock if cur_amt >= 30: break c = conn.cursor() c.execute(''' SELECT name FROM items WHERE obj_info_id=? ''', (obj_info_id,)) result = c.fetchone() if not result or result[0][0] != item_name: for market_data in level2: # Visit the shop and populate a bunch of fields np.get(market_data[2]) res = np.search(r'''<A href=".*?" onClick=".*?"><img src="http://images.neopets.com/items/(.*?)" .*? title="(.*?)" border="1"></a> <br> <b>(.*?)</b>''') if not res: print(f'{market_data[2]} is froze?') continue image = res[1] desc = res[2] name = res[3] c.execute(''' INSERT INTO items (name, image, desc, obj_info_id, last_updated) VALUES (?, ?, ?, ?, datetime('now')) ON CONFLICT (name, image) DO UPDATE SET desc=?, obj_info_id=?, last_updated=datetime('now') ''', (name, image, desc, obj_info_id, desc, obj_info_id)) print(f'The object id of {name} is {obj_info_id}') break else: print('Unable to find legit seller for {obj_info_id}. Will not store it in itemdb.') continue c.execute(''' UPDATE items SET price=?, price_last_updated=datetime('now') WHERE obj_info_id=? ''', (cur_price, obj_info_id)) else: print(f'It seems {item_name} is unbuyable.') # Item could not be found; assume it's unbuyable. # We use a cheap price estimate of 1,000,001 NP. # TODO: Items inserted in this way will have a wonky image property. c = conn.cursor() c.execute(''' INSERT INTO items (name, image, last_updated, price, price_last_updated) VALUES (?, NULL, datetime('now'), 1000001, datetime('now')) ON CONFLICT (name, image) DO UPDATE SET last_updated=datetime('now'), price=1000001, price_last_updated=datetime('now') ''', (item_name,)) conn.commit() return level2_by_item
def food_club(): np = NeoPage() np.get(path, 'type=bet') if np.contains('All betting gates are now closed!'): print(f"Dangit, we're late.") return maxbet = util.amt( re.search(r'You can only place up to <b>(.*?)</b> NeoPoints per bet', np.content)[1]) print(f'Max bet is {maxbet}') # We compute the current round number because it seems there's no way to # find it explicitly unless you actually make a bet. day = int( re.search( r'Next match: <b>.*?</b> the <b>(\d+).*?</b> at <b>02:00 PM NST</b>', np.content)[1]) date = neotime.now_nst().replace(day=day, hour=14, minute=0, second=0) epoch = datetime(2018, 10, 7, 14, 0, 0) from_epoch = round((date - epoch) / timedelta(days=1)) cur_round = 7093 + from_epoch print(f'Food Club round {cur_round}') dl_fc_history(cur_round - 1, cur_round) table = stats() rnd = get_rounds(table[table['round'] == cur_round])[0] for i, arena in enumerate(arena_names): participants = ', '.join(pirate_names[p.pirate] for p in rnd.pirates[i]) print(f'{arena} Arena: {participants}') bets = maxter_strategy(rnd, 1000000 / maxbet) total_bet_amt = 0 total_exp_win = 0 TER = 0 for exp_win, spend, bet in bets: if exp_win < 1.0: print('Not making -EV bet: {bet}') continue bet_amt = round(spend * maxbet) print(f'Bet {bet_amt} NP on {bet}. EV: {exp_win * bet_amt:.1f} NP') opts = [] total_odds = 1 for i, (ps, b) in enumerate(zip(rnd.pirates, bet)): if b == None: continue ps[b].pirate opts.append(f'winner{i+1}={ps[b].pirate}') opts.append(f'matches[]={i+1}') total_odds *= ps[b].closing_odds opts.append(f'bet_amount={bet_amt}') opts.append(f'total_odds={total_odds}') opts.append(f'winnings={bet_amt * total_odds}') opts.append('type=bet') np.post('/pirates/process_foodclub.phtml', *opts) total_bet_amt += bet_amt total_exp_win += bet_amt * exp_win TER += exp_win print( f'Made {total_bet_amt} NP of bets. Expect to win {total_exp_win:.1f} NP. (TER {TER:.2f}; ROI {total_exp_win / total_bet_amt:.3f})' ) np.get('/pirates/foodclub.phtml', 'type=collect') if np.contains("<input type='submit' value='Collect Your Winnings!'>"): tbl = np.search(r"<table border='0' .*?>(.*?)</table>")[1] tuples = util.table_to_tuples(tbl) earliest_rnd = int(tuples[2][0]) total_winnings = tuples[-1][-1] print( f'Note: We have {total_winnings} winnings dating from round {earliest_rnd}.' ) if cur_round - earliest_rnd >= 7: print(f'Should collect winnings before they vanish!')
def battledome(forever=False): np = NeoPage(path) np.get(path_arena) neopoints_left = True prizes_left = True while prizes_left or forever: battle_id = None if np.contains('battleid:'): print('Battledome: Already in fight.') battle_id = np.search(r"battleid:'(\d+)',")[1] else: print('Battledome: Starting fight.') np.post(path_start_fight, 'type=2', f'pet={PET_NAME}', f'npcId={npc_id}', f'toughness={toughness}') battle = json.loads(np.content) if not battle['success']: print('Battledome: Error.') return battle_id = battle['battle']['id'] time.sleep(2) np.set_referer_path(path) np.get(path_arena) ts = int(time.time() * 1000) for step in itertools.count(): intro = 1 if step == 0 else 0 np.set_referer_path(path_arena) np.post(path_arena_ajax, f'battleid={battle_id}', f'step={step}', f'intro={intro}', 'status=1') resp = json.loads(np.content) abils = resp['p1']['abils'] chosen_abil = '' for abil in ['21', '2', '14', '17', '1']: if abil in abils and not abils[abil]['hasCooldown']: chosen_abil = abil break items = re.findall( r'<li><img.*?id="(\d+)".*?title="(.*?)".*?/></li>', resp['p1']['items']) item1id = items[0][0] item2id = items[1][0] opts = [] opts.append(f'p1s=') opts.append(f'eq1={item1id}') opts.append(f'eq2={item2id}') opts.append(f'p1a={chosen_abil}') opts.append(f'chat=') opts.append('action=attack') opts.append(f'ts={ts}') opts.append(f'battleid={battle_id}') opts.append(f'step={step}') opts.append(f'intro=0') opts.append('status=1') print( f'Battledome: Attacking with {items[0][1]} and {items[1][1]}') np.set_referer_path(path_arena) np.post(path_arena_ajax, *opts) resp = json.loads(np.content) if resp['battle']['prizes'] or 'prize_messages' in resp['battle']: prizes = ', '.join( x['name'] for x in resp['battle']['prizes']) or "nothing" print(f'Battledome: Won {prizes}') if 'prize_messages' in resp['battle']: prize_messages = resp['battle']['prize_messages'] if any('reached the item limit' in m for m in prize_messages): prizes_left = False if any('reached the NP limit' in m for m in prize_messages): neopoints_left = False print(f'Battledome: {prize_messages}') break # TODO: Detect defeat. elif step > 5: print( f'Battledome: Took more than 5 steps. Something went wrong.' )
def shapeshifter(timeout=10 * 60): np = NeoPage() np.get(path_index) starting_np = np.current_np() if np.contains('Continue Game!'): np.get(path_game) elif np.contains('Start Game!'): np.post(path_process, f'type=init') # Just in case we won but left it in the completed state if np.contains('You Won!') or np.contains('You Lost!'): np.post(path_process + '?type=init') tbl = np.search(r'''<table border=1 bordercolor='gray'>(.*?)</table>''')[1] imgs = re.findall( r'''<img src='(.*?)' border=0 name='i_'>(<br><b><small>GOAL</small></b></td>)?''', tbl) imgs = imgs[:-1] N = len(imgs) goal_idx = next(i for i, (_, goal) in enumerate(imgs) if goal) to_idx = {img: i for i, (img, _) in enumerate(imgs)} tbl = np.search( r'''<table align=center cellpadding=0 cellspacing=0 border=0>(.*?)</table>''' )[1] goal_grid = [] for row in re.findall(r'''<tr>(.*?)</tr>''', tbl, flags=re.DOTALL): imgs = re.findall(r'''<img src='(.*?)' .*?>''', row) goal_row = [to_idx[img] for img in imgs] goal_grid.append(goal_row) tbl = np.search( r'''<center><b><big>ACTIVE SHAPE</big></b><p>(.*?)</table>\n''')[1] shape_grids = [] for shape_info in re.findall(r'''<table.*?>(.*?)</table>''', tbl): grid = [] for row_info in re.findall(r'''<tr>(.*?)</tr>''', shape_info): tiles = re.findall(r'''<td.*?>(.*?)</td>''', row_info) grid.append( [int('square.gif' in tile_info) for tile_info in tiles]) shape_grids.append(grid) # Compute kvho difficulty. R = len(goal_grid) C = len(goal_grid[0]) min_shifts_needed = R * C * (N - 1) - sum(map(sum, goal_grid)) shifts_available = sum( sum(sum(rows) for rows in grid) for grid in shape_grids) num_overshifts = shifts_available - min_shifts_needed num_flips = num_overshifts // N print(f'Puzzle permits {num_flips} flips ({num_overshifts} overshifts)') kvho_input = make_kvho_input(goal_grid, shape_grids, goal_idx) print(f'Waiting for kvho.') start_time = datetime.now() positions = [] try: proc = subprocess.run(['c/ss'], input=kvho_input, encoding='utf-8', capture_output=True, timeout=timeout) for line in proc.stdout.splitlines(): if 'x' in line and '=' in line: x = list( map( int, line.replace('x', ' ').replace('=', ' ').strip().split())) for c, r, _ in zip(x[::3], x[1::3], x[2::3]): positions.append((r, c)) print(f'Solution found in {datetime.now() - start_time}: {positions}') except subprocess.TimeoutExpired: print( f'Solution not found in time. Throwing this puzzle to get a new one.' ) for _ in shape_grids: positions.append((0, 0)) for i, (shape, (r, c)) in enumerate(zip(shape_grids, positions)): print(f'\rPlacing piece {i+1}/{len(positions)}', end='') np.set_referer_path(path_game) np.get(path_process, 'type=action', f'posx={c}', f'posy={r}') time.sleep(0.5) print() if np.contains('You Won!'): np.set_referer_path(path_game) np.post(path_process + '?type=init') ending_np = np.current_np() print(f'Done level, earned {ending_np - starting_np} NP') return 1 elif np.contains('reached your max neopoints'): print('Done for today.') return 2 else: print('Did not solve level??') return 0
def food_club(): np = NeoPage() np.get(path, 'type=bet') if np.contains('All betting gates are now closed!'): print(f"Dangit, we're late.") return maxbet = util.amt(re.search(r'You can only place up to <b>(.*?)</b> NeoPoints per bet', np.content)[1]) print(f'Max bet is {maxbet}') # We compute the current round number because it seems there's no way to # find it explicitly unless you actually make a bet. day = int(re.search(r'Next match: <b>.*?</b> the <b>(\d+).*?</b> at <b>02:00 PM NST</b>', np.content)[1]) date = neotime.now_nst().replace(day=day, hour=14, minute=0, second=0) epoch = datetime(2018, 10, 7, 14, 0, 0) from_epoch = round((date - epoch) / timedelta(days=1)) cur_round = 7093 + from_epoch print(f'Food Club round {cur_round}') dl_fc_history(cur_round - 1, cur_round) table = stats() rnd = get_rounds(table[table['round'] == cur_round])[0] for i, arena in enumerate(arena_names): participants = ', '.join(pirate_names[p.pirate] for p in rnd.pirates[i]) print(f'{arena} Arena: {participants}') bets = maxter_strategy(rnd, 1000000 / maxbet) total_bet_amt = 0 total_exp_win = 0 TER = 0 for exp_win, spend, bet in bets: if exp_win < 1.0: print('Not making -EV bet: {bet}') continue bet_amt = round(spend * maxbet) print(f'Bet {bet_amt} NP on {bet}. EV: {exp_win * bet_amt:.1f} NP') opts = [] total_odds = 1 for i, (ps, b) in enumerate(zip(rnd.pirates, bet)): if b == None: continue ps[b].pirate opts.append(f'winner{i+1}={ps[b].pirate}') opts.append(f'matches[]={i+1}') total_odds *= ps[b].closing_odds opts.append(f'bet_amount={bet_amt}') opts.append(f'total_odds={total_odds}') opts.append(f'winnings={bet_amt * total_odds}') opts.append('type=bet') np.post('/pirates/process_foodclub.phtml', *opts) total_bet_amt += bet_amt total_exp_win += bet_amt * exp_win TER += exp_win print(f'Made {total_bet_amt} NP of bets. Expect to win {total_exp_win:.1f} NP. (TER {TER:.2f}; ROI {total_exp_win / total_bet_amt:.3f})') np.get('/pirates/foodclub.phtml', 'type=collect') if np.contains("<input type='submit' value='Collect Your Winnings!'>"): tbl = np.search(r"<table border='0' .*?>(.*?)</table>")[1] tuples = util.table_to_tuples(tbl) earliest_rnd = int(tuples[2][0]) total_winnings = tuples[-1][-1] print(f'Note: We have {total_winnings} winnings dating from round {earliest_rnd}.') if cur_round - earliest_rnd >= 7: print(f'Should collect winnings before they vanish!')
def fetch(verbose=False): log = open('fetch.log', 'a') np = NeoPage() # TODO: Figure out how to continue on from existing game. np.get(path, 'deletegame=1') # Start new game. UNKNOWN_COST = random.choice([1,2,3,4,5]) diffs = re.findall(r'<A HREF="maze.phtml\?create=1&diff=(\d+)">', np.content) diff = '4' if '4' in diffs else '3' np.get(path, 'create=1', f'diff={diff}') maze_size = maze_sizes[diff] goal_item_img, goal_item = re.search(r'<IMG SRC="http://images.neopets.com/games/maze/(item_.*?)" WIDTH="80" HEIGHT="80"><br><b>(.*?)</b>', np.content).group(1, 2) print(f'Asked to find {goal_item} (img: {goal_item_img})') np.post(path) # State for a single game. maze = {} x_start = 0 y_start = 0 x_cur = x_start y_cur = y_start x_lower = -maze_size + 1 x_upper = maze_size - 1 y_lower = -maze_size + 1 y_upper = maze_size - 1 xy_item = None xy_exit = None while True: # Check end conditions. if np.contains('Success! You fetched the item and reached the exit!'): prize = util.amt(re.search(r'You have been awarded <b>(.*?)</b> for your efforts!', np.content)[1]) print(f'Won! Got {prize} NP') if np.contains('You have achieved a streak'): streak, cumul = re.search(r'You have achieved a streak of <b>(.*?)</b> victories, for a running total of <b>(.*?)</b> points', np.content).group(1, 2) print(f'Streak is {streak}, total score {cumul}') # Don't get too high of a score! if int(cumul) > 0: np.get(path, 'deletegame=1') np.get(path, 'create=1', 'diff=1') else: streak = 1 cumul = prize log.write(f'{diff},{UNKNOWN_COST},1,{prize},{streak},{cumul}\n') return (prize, streak, cumul) elif np.contains("Your master is very displeased!"): print(f'Ran out of time! :(') log.write(f'{diff},{UNKNOWN_COST},0,0,0,0\n') return moves_remaining = int(re.search(r'Moves Remaining: <b>(.*?)</b>', np.content)[1].split()[0]) # Update knowledge. tbl = re.search(r'<TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0" WIDTH="400">(.*?)</TABLE>', np.content, flags=re.DOTALL)[1] tiles = re.findall(r'<TD BACKGROUND="http://images.neopets.com/games/maze/(.*?).gif" WIDTH="80" HEIGHT="80".*?>(.*?)</TD>', tbl) for jitter_x, jitter_y in [(0, 0), (1, 0), (-1, 0), (0, 1), (0, -1)]: tiles_iter = iter(tiles) good = True for dy in range(-2, 3): for dx in range(-2, 3): img, content = next(tiles_iter) x = x_cur + jitter_x + dx y = y_cur + jitter_y + dy if (x, y) in maze and maze[x, y] != img_to_dirs[img]: good = False if good: x_cur += jitter_x y_cur += jitter_y break tiles_iter = iter(tiles) for dy in range(-2, 3): for dx in range(-2, 3): img, content = next(tiles_iter) x = x_cur + dx y = y_cur + dy dirs = img_to_dirs[img] maze[x, y] = dirs if 'http://images.neopets.com/games/maze/item_' in content: xy_item = (x, y) # Update knowledge of bounds if dirs == 0: if dx == 0: if dy < 0: y_lower = max(y_lower, y_cur + dy + 1) y_upper = y_lower + maze_size - 1 elif dy > 0: y_upper = min(y_upper, y_cur + dy - 1) y_lower = y_upper - maze_size + 1 if dy == 0: if dx < 0: x_lower = max(x_lower, x_cur + dx + 1) x_upper = x_lower + maze_size - 1 elif dx > 0: x_upper = min(x_upper, x_cur + dx - 1) x_lower = x_upper - maze_size + 1 # Path leading out of bounds indicates an exit if x_lower <= x <= x_upper and y_lower <= y <= y_upper: for d in DIRS: ddx, ddy = dir_to_delta[d] if dirs & d and not (x_lower <= x + ddx <= x_upper and y_lower <= y + ddy <= y_upper): xy_exit = (x, y) # Compute the goals. got_item = not np.contains('Searching for:') goals = [] if got_item: # Find the exit!! if xy_exit: goals = [xy_exit] else: # All points on any side opposite the side you started on. candidates = [] if x_lower == 0: candidates.extend((x_upper, y) for y in range(y_lower, y_upper + 1)) if x_upper == 0: candidates.extend((x_lower, y) for y in range(y_lower, y_upper + 1)) if y_lower == 0: candidates.extend((x, y_upper) for x in range(x_lower, x_upper + 1)) if y_upper == 0: candidates.extend((x, y_lower) for x in range(x_lower, x_upper + 1)) # ... that has not been a confirmed dud goals = [] for x, y in candidates: good = False for d in DIRS: dx, dy = dir_to_delta[d] if not (x_lower <= x + dx <= x_upper and y_lower <= y + dy <= y_upper) and (maze.get((x, y), -1) & d) and (maze.get((x + dx, y + dy), -1) & opp_dir[d]): good = True break if good: goals.append((x, y)) else: if xy_item: goals = [xy_item] else: # Possibilities for item location. padding = maze_size // 2 - 1 goals = [(x, y) for x in range(x_lower + padding, x_upper - padding + 1) for y in range(y_lower + padding, y_upper - padding + 1) if (x, y) not in maze] # Dijkstra's to figure out how to best reach a goal. # Distance is (# ? tiles, # known steps). prevs = {} Q = [(0, (x_cur, y_cur), None)] reached_goal = None while Q: c, (x, y), prev = heapq.heappop(Q) if (x, y) in prevs: continue if (x, y) not in maze and not (x_lower <= x <= x_upper and y_lower <= y <= y_upper): continue prevs[x, y] = prev if (x, y) in goals: reached_goal = (x, y) break dirs = maze.get((x, y), -1) c_ = c + (UNKNOWN_COST if dirs == -1 else 1) for d in DIRS: dx, dy = dir_to_delta[d] dirs_ = maze.get((x + dx, y + dy), -1) if dirs & d and dirs_ & opp_dir[d]: heapq.heappush(Q, (c_, (x + dx, y + dy), (x, y))) if not reached_goal: print(f'Ended up in a bad aimless state.') log.write(f'{diff},{UNKNOWN_COST},0,0,0,0,ERROR\n') return # Backtrace route to find which direction to walk in. cur = reached_goal route = [] while cur: route.append(cur) cur = prevs[cur] route = route[::-1] x_nxt, y_nxt = route[1] dxdy = (x_nxt - x_cur, y_nxt - y_cur) movedir = None if dxdy == (-1, 0): movedir = 2 elif dxdy == (1, 0): movedir = 3 elif dxdy == (0, -1): movedir = 0 elif dxdy == (0, 1): movedir = 1 # Print the maze. movechar = "↑↓←→"[movedir] if verbose: coords = list(maze.keys()) + goals min_x = max(x_lower - 2, min(xs(coords))) max_x = min(x_upper + 2, max(xs(coords))) min_y = max(y_lower - 2, min(ys(coords))) max_y = min(y_upper + 2, max(ys(coords))) for y in range(min_y - 1, max_y + 2): for x in range(min_x - 1, max_x + 2): c = dirs_to_unicode[maze.get((x, y), -1)] if (x, y) == (x_cur, y_cur): c = '@' if (x, y) in goals: c = C_YELLOW + c + C_ENDC elif (x, y) in route: c = (C_GREEN if got_item else C_RED) + c + C_ENDC elif abs(x - x_cur) <= 2 and abs(y - y_cur) <= 2: c = C_CYAN + c + C_ENDC if c == '?': c = C_GREY + c + C_ENDC print(c, end='') print() print(f'Moves left: {moves_remaining}') print(f'Min distance to goal: {len(route)}') print(f'Moving {movechar}') print() else: print(f'\r[{moves_remaining} {movechar}] ', end='') if len(route) > moves_remaining: print(f'No hope of winning. Giving up.') log.write(f'{diff},{UNKNOWN_COST},0,0,0,0\n') return s = re.search(r'<AREA SHAPE="poly" COORDS="57,57,57,0,91,0,91,57" HREF="maze.phtml\?action=move&movedir=0&s=(\d+)"', np.content)[1] np.get(path, 'action=move', f'movedir={movedir}', f's={s}') x_cur, y_cur = x_nxt, y_nxt
def shapeshifter(timeout=10*60): np = NeoPage() np.get(path_index) starting_np = np.current_np() if np.contains('Continue Game!'): np.get(path_game) elif np.contains('Start Game!'): np.post(path_process, f'type=init') # Just in case we won but left it in the completed state if np.contains('You Won!') or np.contains('You Lost!'): np.post(path_process + '?type=init') tbl = np.search(r'''<table border=1 bordercolor='gray'>(.*?)</table>''')[1] imgs = re.findall(r'''<img src='(.*?)' border=0 name='i_'>(<br><b><small>GOAL</small></b></td>)?''', tbl) imgs = imgs[:-1] N = len(imgs) goal_idx = next(i for i, (_, goal) in enumerate(imgs) if goal) to_idx = {img: i for i, (img, _) in enumerate(imgs)} tbl = np.search(r'''<table align=center cellpadding=0 cellspacing=0 border=0>(.*?)</table>''')[1] goal_grid = [] for row in re.findall(r'''<tr>(.*?)</tr>''', tbl, flags=re.DOTALL): imgs = re.findall(r'''<img src='(.*?)' .*?>''', row) goal_row = [to_idx[img] for img in imgs] goal_grid.append(goal_row) tbl = np.search(r'''<center><b><big>ACTIVE SHAPE</big></b><p>(.*?)</table>\n''')[1] shape_grids = [] for shape_info in re.findall(r'''<table.*?>(.*?)</table>''', tbl): grid = [] for row_info in re.findall(r'''<tr>(.*?)</tr>''', shape_info): tiles = re.findall(r'''<td.*?>(.*?)</td>''', row_info) grid.append([int('square.gif' in tile_info) for tile_info in tiles]) shape_grids.append(grid) # Compute kvho difficulty. R = len(goal_grid) C = len(goal_grid[0]) min_shifts_needed = R * C * (N - 1) - sum(map(sum, goal_grid)) shifts_available = sum(sum(sum(rows) for rows in grid) for grid in shape_grids) num_overshifts = shifts_available - min_shifts_needed num_flips = num_overshifts // N print(f'Puzzle permits {num_flips} flips ({num_overshifts} overshifts)') kvho_input = make_kvho_input(goal_grid, shape_grids, goal_idx) print(f'Waiting for kvho.') start_time = datetime.now() positions = [] try: proc = subprocess.run(['c/ss'], input=kvho_input, encoding='utf-8', capture_output=True, timeout=timeout) for line in proc.stdout.splitlines(): if 'x' in line and '=' in line: x = list(map(int, line.replace('x', ' ').replace('=', ' ').strip().split())) for c, r, _ in zip(x[::3], x[1::3], x[2::3]): positions.append((r, c)) print(f'Solution found in {datetime.now() - start_time}: {positions}') except subprocess.TimeoutExpired: print(f'Solution not found in time. Throwing this puzzle to get a new one.') for _ in shape_grids: positions.append((0, 0)) for i, (shape, (r, c)) in enumerate(zip(shape_grids, positions)): print(f'\rPlacing piece {i+1}/{len(positions)}', end='') np.set_referer_path(path_game) np.get(path_process, 'type=action', f'posx={c}', f'posy={r}') time.sleep(0.5) print() if np.contains('You Won!'): np.set_referer_path(path_game) np.post(path_process + '?type=init') ending_np = np.current_np() print(f'Done level, earned {ending_np - starting_np} NP') return 1 elif np.contains('reached your max neopoints'): print('Done for today.') return 2 else: print('Did not solve level??') return 0