···11+import fileinput
22+33+floor = 0
44+basement = None
55+66+for line in fileinput.input():
77+ for i, char in enumerate(line, start=1):
88+ if char == "(":
99+ floor += 1
1010+ elif char == ")":
1111+ floor -= 1
1212+1313+ if floor == -1 and basement is None:
1414+ basement = i
1515+1616+print "Santa's floor: %d" % floor
1717+print "Entered basement at character %d" % basement
+14
2015/day02.py
···11+import fileinput
22+33+paper = 0
44+ribbon = 0
55+66+for line in fileinput.input():
77+ l, w, h = [int(n) for n in line.strip().split('x')]
88+ a, b, c = l*w, w*h, h*l
99+1010+ paper += 2*(a+b+c) + min(a, b, c)
1111+ ribbon += (l*w*h) + 2*(l+w+h - max(l, w, h))
1212+1313+print "ft^2 of paper: %d" % paper
1414+print "Feet of ribbon: %d" % ribbon
+23
2015/day03.py
···11+import fileinput
22+from collections import namedtuple
33+44+DIRS = {
55+ "^": (0, 1),
66+ ">": (1, 0),
77+ "v": (0, -1),
88+ "<": (-1, 0),
99+}
1010+1111+def visit_houses(path):
1212+ house = (0, 0)
1313+ for c in path:
1414+ yield house
1515+ house = tuple(map(sum, zip(house, DIRS[c])))
1616+1717+path = fileinput.input()[0].strip())
1818+1919+year_1_houses = set(visit_houses(path))
2020+year_2_houses = set(visit_houses(path[::2])) | set(visit_houses(path[1::2]))
2121+2222+print "Houses visited in year 1: %d" % len(year_1_houses)
2323+print "Houses visited in year 2: %d" % len(year_2_houses)
+28
2015/day04.py
···11+import fileinput
22+import hashlib
33+44+secret_key = fileinput.input()[0].strip()
55+i = 1
66+77+hash1 = None
88+hash2 = None
99+1010+print "Searching for hashes..."
1111+1212+while True:
1313+ s = secret_key + str(i)
1414+ h = hashlib.md5(s).hexdigest()
1515+1616+ if h[:5] == "00000":
1717+ hash1 = (s, h) if hash1 == None else hash1
1818+1919+ if h[:6] == "000000":
2020+ hash2 = (s, h) if hash2 == None else hash2
2121+2222+ if hash1 and hash2:
2323+ break
2424+ else:
2525+ i += 1
2626+2727+print "Hashed %s to %s" % hash1
2828+print "Hashed %s to %s" % hash2
+51
2015/day05.py
···11+import fileinput
22+import re
33+44+VOWELS = {'a', 'e', 'i', 'o', 'u'}
55+NAUGHTY = {'ab', 'cd', 'pq', 'xy'}
66+77+nice_count_1 = 0
88+nice_count_2 = 0
99+1010+for string in fileinput.input():
1111+ # Ruleset 1
1212+ num_letters = len(string)
1313+ num_vowels = 0
1414+ double_letter = False
1515+1616+ for i, char in enumerate(string):
1717+ if char in VOWELS:
1818+ num_vowels += 1
1919+2020+ if i < num_letters - 1:
2121+ if char + string[i+1] in NAUGHTY:
2222+ break
2323+2424+ if char == string[i+1]:
2525+ double_letter = True
2626+2727+ else:
2828+ if double_letter and (num_vowels >= 3):
2929+ nice_count_1 += 1
3030+3131+3232+ # Ruleset 2
3333+ overlap = False
3434+ is_pair = False
3535+3636+ for i, char in enumerate(string):
3737+ if i < len(string) - 2:
3838+ if char == string[i+2]:
3939+ overlap = True
4040+ break
4141+4242+ if re.search(r'(..).*\1', string):
4343+ is_pair = True
4444+4545+4646+ if overlap and is_pair:
4747+ nice_count_2 += 1
4848+4949+5050+print "Number of nice strings (first ruleset): %d" % nice_count_1
5151+print "Number of nice strings (second ruleset): %d" % nice_count_2
+26
2015/day06.py
···11+import fileinput
22+import re
33+44+lights = [[0 for x in range(1000)] for x in range(1000)]
55+66+for line in fileinput.input():
77+ cmd, x1, y1, x2, y2 = re.match(r'(.+) (\d+),(\d+) through (\d+),(\d+)', line).groups()
88+ x1 = int(x1)
99+ x2 = int(x2)
1010+ y1 = int(y1)
1111+ y2 = int(y2)
1212+1313+ for x in range(x1, x2+1):
1414+ for y in range(y1, y2+1):
1515+ if cmd == "turn off":
1616+ # lights[x][y] = 0
1717+ lights[x][y] = max(0, lights[x][y] - 1)
1818+ elif cmd == "turn on":
1919+ # lights[x][y] = 1
2020+ lights[x][y] += 1
2121+ elif cmd == "toggle":
2222+ # lights[x][y] ^= 1
2323+ lights[x][y] += 2
2424+2525+# print "Lights on: %d" % sum([sum(x) for x in lights])
2626+print "Total brightness: %d" % sum([sum(x) for x in lights])
+57
2015/day07.py
···11+import fileinput
22+import re
33+44+connections = {}
55+66+# Set up dictionary of connections
77+for line in fileinput.input():
88+ rule, wire = re.search(r'(.*) -> (.*)', line).groups()
99+ value = None
1010+1111+ if len(rule.split()) == 1:
1212+ value = (rule,)
1313+ elif 'NOT' in rule:
1414+ value = ('NOT', rule.split()[1])
1515+ else:
1616+ value = (rule.split()[1], rule.split()[0], rule.split()[2])
1717+1818+ connections[wire] = value
1919+2020+connections2 = connections.copy()
2121+2222+def follow(wire, c):
2323+ rule = c[wire]
2424+ val = None
2525+2626+ # Base case
2727+ if len(rule) == 1:
2828+ if rule[0].isdigit():
2929+ return int(rule[0])
3030+ else:
3131+ return follow(rule[0], c)
3232+3333+ elif len(rule) == 2:
3434+ return ~follow(rule[1], c)
3535+3636+ else:
3737+ if rule[0] == 'AND':
3838+ val = (int(rule[1]) if rule[1].isdigit() else follow(rule[1], c)) & (int(rule[2]) if rule[2].isdigit() else follow(rule[2], c))
3939+ elif rule[0] == 'OR':
4040+ val = (int(rule[1]) if rule[1].isdigit() else follow(rule[1], c)) | (int(rule[2]) if rule[2].isdigit() else follow(rule[2], c))
4141+ elif rule[0] == 'LSHIFT':
4242+ val = follow(rule[1], c) << int(rule[2])
4343+ elif rule[0] == 'RSHIFT':
4444+ val = follow(rule[1], c) >> int(rule[2])
4545+4646+ if type(val) is int:
4747+ c[wire] = (str(val),)
4848+4949+ return val
5050+5151+s = follow('a', connections)
5252+5353+print "Signal to wire a: %d" % s
5454+5555+connections2['b'] = (str(s), )
5656+5757+print "After overriding b to %s, signal to a is %d" % (s, follow('a', connections2))
···11+#!/usr/bin/env python
22+import fileinput
33+import itertools
44+import re
55+66+def parse_line(line):
77+ departure, arrival, distance = re.match(r'(\w+) to (\w+) = (\d+)', line).groups()
88+ return departure, arrival, int(distance)
99+1010+def sum_of_paths(d):
1111+ # Yield sum of all possible routes
1212+ for p in itertools.permutations(locations, len(locations)):
1313+ yield sum(d[p[i]][p[i+1]] for i in range(len(p) - 1))
1414+1515+distances = {}
1616+1717+# Parse input
1818+for line in fileinput.input():
1919+ frm, to, dist = parse_line(line)
2020+ if distances.get(frm):
2121+ distances[frm][to] = dist
2222+ else:
2323+ distances[frm] = {to: dist}
2424+2525+ if distances.get(to):
2626+ distances[to][frm] = dist
2727+ else:
2828+ distances[to] = {frm: dist}
2929+3030+locations = distances.keys()
3131+3232+print "Shortest route: %d" % min(sum_of_paths(distances))
3333+print "Longest route: %d" % max(sum_of_paths(distances))
+28
2015/day10.py
···11+import fileinput
22+33+string = fileinput.input()[0].strip()
44+55+def look_and_say(s):
66+ occurences = 0
77+ number = s[0]
88+ ret = ''
99+1010+ for n in s:
1111+ if number == n:
1212+ occurences += 1
1313+ else:
1414+ ret += str(occurences) + number
1515+ number = n
1616+ occurences = 1
1717+1818+ return ret + str(occurences) + number
1919+2020+for _ in range(40):
2121+ string = look_and_say(string)
2222+2323+print "Length after 40 iterations: %d" % len(string)
2424+2525+for _ in range(10):
2626+ string = look_and_say(string)
2727+2828+print "Length after 50 iterations: %d" % len(string)
+40
2015/day11.py
···11+import fileinput
22+33+password = fileinput.input()[0].strip()
44+55+def is_valid(p):
66+ if any(x in p for x in ['i', 'o', 'l']):
77+ return False
88+99+ straight = False
1010+ pairs = set()
1111+1212+ for i in range(len(p) - 2):
1313+ if ord(p[i]) == ord(p[i+1]) - 1 == ord(p[i+2]) - 2:
1414+ straight = True
1515+ if p[i] == p[i+1]:
1616+ pairs.add(p[i])
1717+1818+ if p[-1] == p[-2]:
1919+ pairs.add(p[-1])
2020+2121+ return straight and (len(pairs) >= 2)
2222+2323+def next_pass(p):
2424+ # Recursion is fantastic.
2525+ if p == 'z':
2626+ return 'a'
2727+ elif p[-1] == 'z':
2828+ return next_pass(p[:-1]) + 'a'
2929+ else:
3030+ return p[:-1] + chr(ord(p[-1]) + 1)
3131+3232+3333+print "Santa's current password is %s" % password
3434+3535+for _ in range(2):
3636+ while True:
3737+ password = next_pass(password)
3838+ if is_valid(password):
3939+ print "His next password should be %s" % password
4040+ break
+31
2015/day12.py
···11+import fileinput
22+import re
33+import json
44+55+def no_red_sum(x):
66+ s = 0
77+88+ if type(x) == list:
99+ for y in x:
1010+ if type(y) == int:
1111+ s += y
1212+ else:
1313+ s += no_red_sum(y)
1414+ elif type(x) == dict:
1515+ for k in x.keys():
1616+ if x[k] == 'red':
1717+ return 0
1818+ s += no_red_sum(x[k])
1919+ elif type(x) == int:
2020+ return x
2121+2222+ return s
2323+2424+2525+document = fileinput.input()[0]
2626+2727+print "Sum of all numbers: %d" % sum(int(x) for x in re.findall('-?\d+', document))
2828+2929+j = json.loads(document)
3030+3131+print "Sum of non-reds items: %d" % no_red_sum(j)
+40
2015/day13.py
···11+#!/usr/bin/env python
22+import fileinput
33+import itertools
44+import re
55+66+def parse_line(line):
77+ p1, gl, diff, p2 = re.search(r'(\w+) .* (gain|lose) (\d+) .* (\w+).', line).groups()
88+ return p1, p2, int(diff) * (-1 if gl == 'lose' else 1)
99+1010+def happiness_deltas(d):
1111+ # Not optimal because it includes cyclic permutations and reversals
1212+ for p in itertools.permutations(d.keys(), len(d.keys())):
1313+ total = 0
1414+ for i in range(len(p)):
1515+ total += d[p[i]][p[(i+1) % len(p)]]
1616+ total += d[p[(i+1) % len(p)]][p[i]]
1717+1818+ yield total
1919+2020+2121+people = {}
2222+2323+# Parse input
2424+for line in fileinput.input():
2525+ p1, p2, diff = parse_line(line)
2626+ if p1 in people:
2727+ people[p1][p2] = diff
2828+ else:
2929+ people[p1] = {p2: diff}
3030+3131+3232+print "Largest total change in happiness: %d" % max(happiness_deltas(people))
3333+3434+# Add self into list of keys for second part of problem
3535+for person in people.keys():
3636+ people[person]["iKevinY"] = 0
3737+3838+people["iKevinY"] = {person: 0 for person in people.keys()}
3939+4040+print "After adding me: %d" % max(happiness_deltas(people))
+26
2015/day14.py
···11+import fileinput
22+import re
33+44+def fly(time, speed, fly_for, rest_for):
55+ cycle = fly_for + rest_for
66+ return speed * (
77+ ((time // cycle) * fly_for) + # flying time * full cycles completed
88+ min(fly_for, time % cycle) # remaining incomplete flying time
99+ )
1010+1111+TOTAL_TIME = 2503
1212+1313+reindeer = [[int(n) for n in re.findall(r'\d+', line)] for line in fileinput.input()]
1414+1515+print "Winning reindeer distance: %d" % max(fly(TOTAL_TIME, *r) for r in reindeer)
1616+1717+points = [0] * len(reindeer)
1818+1919+for t in range(1, TOTAL_TIME + 1):
2020+ positions = [fly(t, *r) for r in reindeer]
2121+ for i, pos in enumerate(positions):
2222+ if pos == max(positions):
2323+ points[i] += 1
2424+2525+print "Highest point total: %d" % max(points)
2626+
+19
2015/day15.py
···11+import fileinput
22+import itertools
33+import re
44+55+def mul(vals):
66+ ret = 1
77+ for x in vals:
88+ ret *= x
99+ return ret
1010+1111+ingredients = [[int(n) for n in re.findall(r'\w+ (-?\d+)', line)] for line in fileinput.input()]
1212+1313+cookies = []
1414+1515+for combo in itertools.combinations_with_replacement(ingredients, 100): # 100 teaspoons of ingredients
1616+ cookies.append([max(0, sum(c)) for c in tuple(zip(*combo))])
1717+1818+print "Best cookie total: %d" % max(mul(c[:-1]) for c in cookies)
1919+print "Best 500 calories: %d" % max(mul(c[:-1]) for c in cookies if c[-1] == 500)
+37
2015/day16.py
···11+import fileinput
22+import re
33+44+TICKER = {
55+ 'children': 3,
66+ 'cats': 7,
77+ 'samoyeds': 2,
88+ 'pomeranians': 3,
99+ 'akitas': 0,
1010+ 'vizslas': 0,
1111+ 'goldfish': 5,
1212+ 'trees': 3,
1313+ 'cars': 2,
1414+ 'perfumes': 1,
1515+}
1616+1717+sues = [{k: int(v) for k, v in re.findall(r'(\w+): (\d+)', line)} for line in fileinput.input()]
1818+1919+def which_sue(sues, outdated=False):
2020+ for i, sue in enumerate(sues, start=1):
2121+ for key in sue:
2222+ if outdated and (key in ('cats', 'trees')):
2323+ if not (sue[key] > TICKER[key]):
2424+ break
2525+ elif outdated and (key in ('pomeranians', 'goldfish')):
2626+ if not (sue[key] < TICKER[key]):
2727+ break
2828+2929+ elif TICKER[key] != sue[key]:
3030+ break
3131+ else:
3232+ return i
3333+ else:
3434+ return -1
3535+3636+print "The gift was from Sue #%d." % which_sue(sues)
3737+print "With ranges gives Sue #%d." % which_sue(sues, outdated=True)
+21
2015/day17.py
···11+import fileinput
22+import itertools
33+import re
44+55+LITRES = 150
66+77+def permute_containers(containers, litres):
88+ for i in range(1, len(containers) + 1):
99+ for c in itertools.combinations(containers, i):
1010+ if sum(c) == litres:
1111+ yield c
1212+1313+containers = [int(n) for n in fileinput.input()]
1414+permutations = list(permute_containers(containers, LITRES))
1515+min_perm_len = min(len(p) for p in permutations)
1616+1717+print "Containers: {}".format(containers)
1818+print "Litres of eggnog: %d" % LITRES
1919+2020+print "Valid combinations: %d" % len(permutations)
2121+print "Minimal combinations: %d" % len([p for p in permutations if len(p) == min_perm_len])
+62
2015/day18.py
···11+import fileinput
22+import itertools
33+import re
44+55+def get_neighbours(x, y):
66+ square = [(a, b)
77+ for a in range(max(0, x-1), min(x+2, WIDTH))
88+ for b in range(max(0, y-1), min(y+2, HEIGHT))]
99+ square.remove((x, y))
1010+ return square
1111+1212+def next_state(grid, x, y, sticky_corners=False):
1313+ if sticky_corners:
1414+ if (x in (0, WIDTH-1)) and (y in (0, HEIGHT-1)):
1515+ return True
1616+1717+ neighbours = get_neighbours(x, y)
1818+ on_neighbours = sum(grid[b][a] for a, b in neighbours)
1919+2020+ if grid[y][x]:
2121+ return int(on_neighbours in (2, 3))
2222+ else:
2323+ return int(on_neighbours == 3)
2424+2525+def next_grid(grid, sticky_corners=False):
2626+ return [[next_state(grid, x, y, sticky_corners) for x in range(WIDTH)] for y in range(HEIGHT)]
2727+2828+def count_lights(grid):
2929+ return sum(sum(x for x in row) for row in grid)
3030+3131+def print_grid(grid):
3232+ for row in grid:
3333+ print ''.join('#' if c else '.' for c in row)
3434+3535+3636+# Parse input
3737+rows = [line for line in fileinput.input()]
3838+LIGHTS = [[1 if c == '#' else 0 for c in row.strip()] for row in rows]
3939+4040+# Define constants
4141+WIDTH = len(LIGHTS[0])
4242+HEIGHT = len(LIGHTS)
4343+ITERATIONS = 100
4444+4545+print "Simulating {}x{} grid of lights.".format(WIDTH, HEIGHT)
4646+4747+grid = LIGHTS
4848+for _ in range(ITERATIONS):
4949+ grid = next_grid(grid)
5050+5151+print "Lights on after {} iterations: {}".format(ITERATIONS, count_lights(grid))
5252+5353+# Stick corners initially
5454+grid = LIGHTS
5555+for x in (0, WIDTH-1):
5656+ for y in (0, HEIGHT-1):
5757+ grid[y][x] = 1
5858+5959+for _ in range(ITERATIONS):
6060+ grid = next_grid(grid, sticky_corners=True)
6161+6262+print "Lights on with stuck corners: {}".format(count_lights(grid))
+107
2015/day19.py
···11+import fileinput
22+import itertools
33+import re
44+import sys
55+66+REPLACEMENTS = []
77+ELECTRONS = []
88+MOLECULE = ""
99+MOL_LEN = len(MOLECULE)
1010+1111+for line in fileinput.input():
1212+ if "=>" in line:
1313+ m = re.findall(r'\w+', line)
1414+ if m[0] == "e":
1515+ ELECTRONS.append(m[1])
1616+ else:
1717+ REPLACEMENTS.append(m)
1818+ else:
1919+ MOLECULE = line.strip()
2020+2121+print "Elements:", len(re.findall(r'[A-Z]', MOLECULE))
2222+print "Rn/Ar:", len(re.findall(r'Rn', MOLECULE)), "* 2"
2323+print "Y:", len(re.findall(r'Y', MOLECULE))
2424+2525+# molecule = MOLECULE
2626+2727+# reps = {m[1][::-1]: m[0][::-1]
2828+# for m in REPLACEMENTS}
2929+3030+# def rep(x):
3131+# return reps[x.group()]
3232+3333+# count = 0
3434+# while molecule != 'e':
3535+# molecule = re.sub('|'.join(reps.keys()), rep, molecule, 1)
3636+# count += 1
3737+3838+# print(count)
3939+4040+sys.exit()
4141+4242+4343+outs = set()
4444+4545+def molefy(mole):
4646+ for old, new in REPLACEMENTS:
4747+ for pos in (m.start() for m in re.finditer(old, mole)):
4848+ new_string = mole[:pos] + new + mole[pos+len(old):]
4949+ yield new_string
5050+5151+def delete(mole):
5252+ for old, new in REPLACEMENTS:
5353+ for pos in (m.start() for m in re.finditer(new, mole)):
5454+ new_string = mole[:pos] + old + mole[pos+len(new):]
5555+ yield new_string
5656+5757+def replace(old, new, s):
5858+ for pos in (m.start() for m in re.finditer(new, s)):
5959+ yield "{}{}{}".format(s[:pos], old, s[pos+len(new):])
6060+6161+6262+LOWEST = 100000
6363+6464+def submol(m, i=1,):
6565+ if m in ELECTRONS:
6666+ LOWEST = min(LOWEST, i)
6767+6868+ for r in REPLACEMENTS:
6969+ if r[1] in m:
7070+ for x in replace(r[0], r[1], m):
7171+ submol(x, i+1)
7272+7373+7474+submol(MOLECULE)
7575+print LOWEST
7676+7777+sys.exit()
7878+7979+iterations = 1
8080+mols = set()
8181+mols.add(MOLECULE)
8282+new_mols = set()
8383+# seen = set()
8484+8585+while True:
8686+ iterations += 1
8787+ for m in mols:
8888+ for r in REPLACEMENTS:
8989+ if r[1] in m:
9090+ for x in replace(r[0], r[1], m):
9191+ if x in ELECTRONS:
9292+ print iterations
9393+ sys.exit()
9494+9595+ # if x not in seen:
9696+ new_mols.add(x)
9797+ # seen.add(x)
9898+9999+ if len(new_mols) == 0:
100100+ sys.exit("BLALALLA")
101101+102102+ mols = new_mols
103103+ new_mols = set()
104104+105105+106106+107107+# print len(outs)
+23
2015/day20.py
···11+import fileinput
22+33+PRESENTS = int(fileinput.input()[0].strip())
44+55+def factors(n):
66+ return set(x for tup in ([i, n//i]
77+ for i in range(1, int(n**0.5)+1) if n % i == 0) for x in tup)
88+99+# house = 1
1010+1111+# while True:
1212+# if sum(factors(house)) * 10 >= PRESENTS:
1313+# print house
1414+# break
1515+# house += 1
1616+1717+house = 1
1818+1919+while True:
2020+ if sum(x for x in factors(house) if (x * 50 >= house)) * 11 >= PRESENTS:
2121+ print house
2222+ break
2323+ house += 1
+76
2015/day21.py
···11+import fileinput
22+import itertools
33+from collections import namedtuple
44+55+Item = namedtuple('Item', ['name', 'cost', 'dmg', 'arm'])
66+Char = namedtuple('Character', ['hp', 'dmg', 'arm'])
77+88+# Cost, Damage, Armour
99+WEAPONS = [
1010+ Item("Dagger", 8, 4, 0),
1111+ Item("Shortsword", 10, 5, 0),
1212+ Item("Warhammer", 25, 6, 0),
1313+ Item("Longsword", 40, 7, 0),
1414+ Item("Greataxe", 74, 8, 0),
1515+]
1616+1717+ARMOUR = [
1818+ Item("Leather", 13, 0, 1),
1919+ Item("Chainmail", 31, 0, 2),
2020+ Item("Splintmail", 53, 0, 3),
2121+ Item("Bandedmail", 75, 0, 4),
2222+ Item("Platemail", 102, 0, 5),
2323+]
2424+2525+RINGS = [
2626+ Item("Damage +1", 25, 1, 0),
2727+ Item("Damage +2", 50, 2, 0),
2828+ Item("Damage +3", 100, 3, 0),
2929+ Item("Defense +1", 20, 0, 1),
3030+ Item("Defense +2", 40, 0, 2),
3131+ Item("Defense +3", 80, 0, 3),
3232+]
3333+3434+# HP, Damage, Armour
3535+boss_data = []
3636+3737+for line in fileinput.input():
3838+ boss_data.append(int(line.split(': ')[1]))
3939+4040+BOSS = Char(*boss_data)
4141+HP = 100
4242+4343+def boss_fight(player_hp, boss, weapon, armour, rings):
4444+ player_dmg = weapon.dmg + sum(r.dmg for r in rings)
4545+ player_arm = sum(a.arm for a in armour) + sum(r.arm for r in rings)
4646+ player = Char(player_hp, player_dmg, player_arm)
4747+4848+ loadout = ', '.join((x for x in (weapon.name, ', '.join(a.name for a in armour), ', '.join(r.name for r in rings)) if x))
4949+ loadout_cost = weapon.cost + sum(a.cost for a in armour) + sum(r.cost for r in rings)
5050+5151+ player_hurt = boss_hurt = 0
5252+5353+ while player.hp - player_hurt > 0:
5454+ boss_hurt += max(1, player.dmg - boss.arm)
5555+5656+ if boss.hp - boss_hurt <= 0:
5757+ return True, loadout_cost, loadout
5858+5959+ player_hurt += max(1, boss.dmg - player.arm)
6060+6161+ return False, loadout_cost, loadout
6262+6363+6464+loadouts = []
6565+6666+# 1 weapon, 0-1 armour, 0-2 rings
6767+for weapon in WEAPONS:
6868+ for num_a in range(2):
6969+ for armour in itertools.combinations(ARMOUR, num_a):
7070+ for num_r in range(3):
7171+ for rings in itertools.combinations(RINGS, num_r):
7272+ loadouts.append((weapon, armour, rings))
7373+7474+7575+print "Optimal spend to win: {}g <{}>".format(*min((c, l) for win, c, l in (boss_fight(HP, BOSS, *l) for l in loadouts) if win))
7676+print "Worst spend and lose: {}g <{}>".format(*max((c, l) for win, c, l in (boss_fight(HP, BOSS, *l) for l in loadouts) if not win))
+183
2015/day22.py
···11+import fileinput
22+import itertools
33+import sys
44+from collections import namedtuple
55+66+class Character:
77+ def __init__(self, name, hp, mp, dmg, armour):
88+ # Stats
99+ self.name = name
1010+ self.hp = hp
1111+ self.mp = mp
1212+ self.dmg = dmg
1313+ self.armour = armour
1414+1515+ # Status Effects
1616+ self.shielded = 0
1717+ self.poisoned = 0
1818+ self.recharging = 0
1919+2020+ def print_player(self):
2121+ print '{} has {} hit points, {} mana, {} armour'.format(self.name, self.hp, self.mp, self.armour)
2222+2323+ def print_boss(self):
2424+ print '{} has {} hit points'.format(self.name, self.hp)
2525+2626+ def process_status(self):
2727+ if self.shielded:
2828+ self.shielded -= 1
2929+ self.armour = 7
3030+ # print "Shield's timer is now {}.".format(self.shielded)
3131+ if self.shielded == 0:
3232+ self.armour = 0
3333+ # print 'Shield wears off.'
3434+3535+ if self.poisoned:
3636+ self.poisoned -= 1
3737+ self.hp -= 3
3838+ # print 'Poison deals 3 damage; its timer is now {}.'.format(self.poisoned)
3939+ if self.poisoned == 0:
4040+ pass
4141+ # print 'Poison effects expired.'
4242+4343+ if self.recharging:
4444+ self.recharging -= 1
4545+ self.mp += 101
4646+ # print 'Recharge provides 101 mana; its timer is now {}.'.format(self.recharging)
4747+ if self.recharging == 0:
4848+ pass
4949+ # print 'Recharge wears off.'
5050+5151+5252+Spell = namedtuple('Spell', ['name', 'cost', 'dmg', 'heal', 'effect', 'duration'])
5353+5454+# Magic Missile costs 53 mana. It instantly does 4 damage.
5555+# Drain costs 73 mana. It instantly does 2 damage and heals you for 2 hit points.
5656+# Shield costs 113 mana. It starts an effect that lasts for 6 turns. While it is active, your armor is increased by 7.
5757+# Poison costs 173 mana. It starts an effect that lasts for 6 turns. At the start of each turn while it is active, it deals the boss 3 damage.
5858+# Recharge costs 229 mana. It starts an effect that lasts for 5 turns. At the start of each turn while it is active, it gives you 101 new mana.
5959+SPELLS = [
6060+ Spell('Missl', 53, 4, 0, None, None),
6161+ Spell('Drain', 73, 2, 2, None, None),
6262+ Spell('Shild', 113, 0, 0, 'shielded', 6),
6363+ Spell('Poisn', 173, 0, 0, 'poisoned', 6),
6464+ Spell('Rchrg', 229, 0, 0, 'recharge', 5),
6565+]
6666+6767+6868+def boss_fight(player, boss, spell_order, hard_mode=False):
6969+ spent_mana = 0
7070+ effects = []
7171+ # print ', '.join(s.name for s in spell_order)
7272+7373+ for spell in spell_order:
7474+ if hard_mode:
7575+ player.hp -= 1
7676+ if player.hp <= 0:
7777+ # print 'Player died. Game over. :('
7878+ return sys.maxint
7979+8080+ # print '-- Player Turn --'
8181+ # player.print_player()
8282+ # boss.print_boss()
8383+8484+ player.process_status()
8585+ boss.process_status()
8686+8787+8888+ # Pick a spell to use
8989+ if spell.cost > player.mp:
9090+ # print 'Not enough mana to cast.'
9191+ return sys.maxint
9292+9393+ # Disallow repeated effects
9494+ if spell.effect == 'shielded' and player.shielded:
9595+ return sys.maxint
9696+ elif spell.effect == 'poisoned' and boss.poisoned:
9797+ return sys.maxint
9898+ elif spell.effect == 'recharge' and player.recharging:
9999+ return sys.maxint
100100+101101+ # print '> iKevinY casted {}!'.format(spell.name)
102102+ spent_mana += spell.cost
103103+ player.mp -= spell.cost
104104+ boss.hp -= spell.dmg
105105+106106+107107+ if spell.heal:
108108+ # print 'iKevinY healed for {} hit points.'.format(spell.heal)
109109+ player.hp += spell.heal
110110+111111+ if spell.effect == 'shielded':
112112+ player.shielded = spell.duration
113113+ elif spell.effect == 'poisoned':
114114+ boss.poisoned = spell.duration
115115+ elif spell.effect == 'recharge':
116116+ player.recharging = spell.duration
117117+118118+ if boss.hp <= 0:
119119+ # print 'Boss died. You win! :)'
120120+ print spent_mana, '@', ' '.join(s.name for s in spell_order)
121121+ return spent_mana
122122+123123+ # print '-- Boss Turn --'
124124+ # player.print_player()
125125+ # boss.print_boss()
126126+ player.process_status()
127127+ boss.process_status()
128128+129129+ if boss.hp <= 0:
130130+ # print 'Boss died. You win! :)'
131131+ print spent_mana, '@', ' '.join(s.name for s in spell_order)
132132+ return spent_mana
133133+134134+ # print '> Boss attacks for {} damage!'.format(max(1, boss.dmg - player.armour))
135135+ player.hp -= max(1, boss.dmg - player.armour)
136136+ if player.hp <= 0:
137137+ # print 'Player died. Game over. :('
138138+ return sys.maxint
139139+140140+ # print
141141+142142+ else:
143143+ # Fight did not terminate
144144+ return sys.maxint
145145+146146+ return spent_mana
147147+148148+149149+# # HP, Damage
150150+# boss_data = []
151151+152152+# for line in fileinput.input():
153153+# boss_data.append(int(line.split(': ')[1]))
154154+155155+MOST_EFFICIENT = sys.maxint
156156+157157+# boss = Character('BOSS', 13, 0, 8, 0)
158158+# player = Character('iKevinY', 10, 250, 0, 0) # 50HP, 500MP
159159+# boss_fight(player, boss, [SPELLS[3], SPELLS[0]])
160160+161161+# sys.exit()
162162+163163+for n in range(9, 12):
164164+ found = False
165165+ for so in itertools.product(SPELLS, repeat=n):
166166+ if sum(s.cost for s in so) > MOST_EFFICIENT:
167167+ continue
168168+169169+ boss = Character('BOSS', 55, 0, 8, 0) # 55hp, 8dmg INPUT
170170+ player = Character('iKevinY', 50, 500, 0, 0) # 50HP, 500MP
171171+172172+ # boss = Character('BOSS', 13, 0, 8, 0)
173173+ # player = Character('iKevinY', 10, 250, 0, 0) # 50HP, 500MP
174174+ a = boss_fight(player, boss, so, hard_mode=True)
175175+176176+ if a < MOST_EFFICIENT:
177177+ MOST_EFFICIENT = a
178178+ found = True
179179+180180+ if found:
181181+ break
182182+183183+print 'Most efficient MP: %i' % MOST_EFFICIENT
+67
2015/day23.py
···11+import fileinput
22+import re
33+44+INS = []
55+66+for line in fileinput.input():
77+ INS.append(line.strip())
88+99+pc = 0
1010+ra = 1
1111+rb = 0
1212+1313+while 0 <= pc < len(INS):
1414+ ins = INS[pc]
1515+ print '{0: <16} a: {1}, b: {2}'.format(ins, ra, rb)
1616+1717+ if 'hlf' in ins:
1818+ if 'a' in ins:
1919+ ra /= 2
2020+ else:
2121+ rb /= 2
2222+2323+ elif 'tpl' in ins:
2424+ if 'a' in ins:
2525+ ra *= 3
2626+ else:
2727+ rb *= 3
2828+2929+ elif 'inc' in ins:
3030+ if 'a' in ins:
3131+ ra += 1
3232+ else:
3333+ rb += 1
3434+3535+ elif 'jmp' in ins:
3636+ offset = int(re.findall('\d+', ins)[0]) * (-1 if '-' in ins else 1)
3737+ pc += offset
3838+ pc -= 1 # compensate for pc++ at end
3939+4040+ elif 'jie' in ins:
4141+ offset = int(re.findall('\d+', ins)[0]) * (-1 if '-' in ins else 1)
4242+ if 'a' in ins:
4343+ if ra % 2 == 0:
4444+ pc += offset
4545+ pc -= 1
4646+ else:
4747+ if rb % 2 == 0:
4848+ pc += offset
4949+ pc -= 1
5050+5151+ elif 'jio' in ins:
5252+ offset = int(re.findall('\d+', ins)[0]) * (-1 if '-' in ins else 1)
5353+ if 'a' in ins:
5454+ if ra == 1:
5555+ pc += offset
5656+ pc -= 1
5757+ else:
5858+ if rb == 1:
5959+ pc += offset
6060+ pc -= 1
6161+6262+6363+ pc += 1
6464+6565+print "halt\n--"
6666+print "Register A: %d" % ra
6767+print "Register B: %d" % rb
+31
2015/day24.py
···11+import fileinput
22+import itertools
33+from tqdm import tqdm
44+55+PACKAGES = []
66+77+for line in fileinput.input():
88+ PACKAGES.append(int(line))
99+1010+PACKAGES.reverse()
1111+1212+def quantum_package(packages, target_weight):
1313+ for n in range(1, len(PACKAGES) - 2):
1414+ found = False
1515+ for p in itertools.combinations(PACKAGES, n):
1616+ if sum(p) == target_weight:
1717+ # print p
1818+ yield p
1919+ found = True
2020+2121+ if found:
2222+ return
2323+2424+def prod(it):
2525+ p = 1
2626+ for i in it:
2727+ p *= i
2828+ return p
2929+3030+print "3-compartment package QE:", min(prod(c) for c in quantum_package(PACKAGES, sum(PACKAGES) / 3))
3131+print "4-compartment package QE:", min(prod(c) for c in quantum_package(PACKAGES, sum(PACKAGES) / 4))
+26
2015/day25.py
···11+import fileinput
22+import re
33+44+# ROW, COL = (int(x) for x in re.findall(r'\d+', fileinput.input()[0]))
55+ROW, COL = 3010, 3019
66+77+88+def next_code(c):
99+ return (c * 252533) % 33554393
1010+1111+def code_no(row, col):
1212+ # s = 1
1313+ # for x in range(row + col - 1):
1414+ # s += x
1515+1616+ # return s + col - 1
1717+1818+ return (((row + col - 1) * (row + col - 2)) / 2) + col
1919+2020+code = 20151125
2121+2222+# for x in range(code_no(ROW, COL) - 1):
2323+# code = next_code(code)
2424+2525+2626+print (code * pow(252533, code_no(ROW, COL) - 1, 33554393)) % 33554393