def purchase(item, image=None, budget=None, quantity=1, **kwargs): # Buys a quantity of items from user shops, sticking within a budget. # Returns actual amount spent, or None if not successful. market = next(iter(item_db.get_market(item, image, **kwargs).values())) true_cost = 0 qty_left = quantity buy_nps = [] for price, stock, link in market: np2 = NeoPage() np2.get(link) buy_link, image, item, in_stock, cost = shop_item_re.search(np2.content).groups() in_stock = util.amt(in_stock) cost = util.amt(cost) if cost <= price: to_buy = min(in_stock, qty_left) true_cost += cost * to_buy qty_left -= to_buy buy_nps.append((np2, buy_link, to_buy)) if qty_left <= 0: break if budget != None and true_cost > budget: return None ensure_np(true_cost) for np2, buy_link, to_buy in buy_nps: referer = np2.referer for _ in range(to_buy): print(f'Buying {item} from {buy_link}') np2.set_referer(referer) np2.get(f'/{buy_link}') return true_cost
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 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 purchase(item, image=None, budget=None, quantity=1, **kwargs): # Buys a quantity of items from user shops, sticking within a budget. # Returns actual amount spent, or None if not successful. market = next(iter(item_db.get_market(item, image, **kwargs).values())) true_cost = 0 qty_left = quantity buy_nps = [] for price, stock, link in market: np2 = NeoPage() np2.get(link) buy_link, image, item, in_stock, cost = shop_item_re.search( np2.content).groups() in_stock = util.amt(in_stock) cost = util.amt(cost) if cost <= price: to_buy = min(in_stock, qty_left) true_cost += cost * to_buy qty_left -= to_buy buy_nps.append((np2, buy_link, to_buy)) if qty_left <= 0: break if budget != None and true_cost > budget: return None ensure_np(true_cost) for np2, buy_link, to_buy in buy_nps: referer = np2.referer for _ in range(to_buy): print(f'Buying {item} from {buy_link}') np2.set_referer(referer) np2.get(f'/{buy_link}') return true_cost
def faerieland_jobs(jobs_to_do): np = NeoPage() np.get(path, 'type=jobs', 'voucher=basic') start = 0 jobs_by_profit = [] try: while jobs_to_do > 0: jobs = job_re.findall(np.content) has_more_pages = bool(re.search('<A HREF="employment.phtml.*?">Next 10</A>', np.content)) for image, job_link, count, name, prize in jobs: count = int(count) prize = util.amt(prize) revenue = prize * 1.25 if revenue < MIN_PROFIT: print(f'Skipping {name} (can only make {revenue})') continue price = item_db.get_price(name, image) profit = revenue - count * price print(f'Can profit by about {profit} NP: Buy {count}x {name} ({price} NP each), turn in for {revenue}') bisect.insort(jobs_by_profit, (profit, name, count, job_link)) if profit < MIN_PROFIT: continue print('Taking the job!') true_cost = inventory.purchase(name, image=image, budget=revenue - MIN_PROFIT, quantity=count) if not true_cost: print(f'Failed to acquire all the items within budget') continue # Do the job! np.get(f'/faerieland/employ/{job_link}') if 'Good job! You got all the items' in np.content: prize = re.search(r'You have been paid <b>(.*?) Neopoints</b>.', np.content)[1] prize = util.amt(prize) print(f'Turned in {count}x {name} for {prize} NP. (Profit: {prize - true_cost})') elif 'You got the job!' in np.content: print(f'Job was not completed before applying? TODO') print(np.last_file_path) return else: print(f'Error applying for job. TODO') print(np.last_file_path) return jobs_to_do -= 1 if jobs_to_do <= 0: return if has_more_pages: start += 10 np.get(path, 'type=jobs', 'voucher=basic', f'start={start}') else: break except item_db.ShopWizardBannedException: return print(jobs_by_profit) print('Did not find enough appropriate jobs.')
def faerieland_jobs(jobs_to_do): np = NeoPage() np.get(path, 'type=jobs', 'voucher=basic') start = 0 jobs_by_profit = [] try: while jobs_to_do > 0: jobs = job_re.findall(np.content) has_more_pages = bool( re.search('<A HREF="employment.phtml.*?">Next 10</A>', np.content)) for image, job_link, count, name, prize in jobs: count = int(count) prize = util.amt(prize) revenue = prize * 1.25 if revenue < MIN_PROFIT: print(f'Skipping {name} (can only make {revenue})') continue price = item_db.get_price(name, image) profit = revenue - count * price print( f'Can profit by about {profit} NP: Buy {count}x {name} ({price} NP each), turn in for {revenue}' ) bisect.insort(jobs_by_profit, (profit, name, count, job_link)) if profit < MIN_PROFIT: continue print('Taking the job!') true_cost = inventory.purchase(name, image=image, budget=revenue - MIN_PROFIT, quantity=count) if not true_cost: print(f'Failed to acquire all the items within budget') continue # Do the job! np.get(f'/faerieland/employ/{job_link}') if 'Good job! You got all the items' in np.content: prize = re.search( r'You have been paid <b>(.*?) Neopoints</b>.', np.content)[1] prize = util.amt(prize) print( f'Turned in {count}x {name} for {prize} NP. (Profit: {prize - true_cost})' ) elif 'You got the job!' in np.content: print(f'Job was not completed before applying? TODO') print(np.last_file_path) return else: print(f'Error applying for job. TODO') print(np.last_file_path) return jobs_to_do -= 1 if jobs_to_do <= 0: return if has_more_pages: start += 10 np.get(path, 'type=jobs', 'voucher=basic', f'start={start}') else: break except item_db.ShopWizardBannedException: return print(jobs_by_profit) print('Did not find enough appropriate jobs.')
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 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