Skip to content

Commit 2769634

Browse files
committed
2018-15
1 parent 061573a commit 2769634

File tree

2 files changed

+461
-333
lines changed

2 files changed

+461
-333
lines changed

2018/15/15.ipynb

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 43,
6+
"metadata": {},
7+
"outputs": [],
8+
"source": [
9+
"from aocd import get_data\n",
10+
"from collections import defaultdict\n",
11+
"import numpy as np\n",
12+
"import sys\n",
13+
"\n",
14+
"data = get_data(day=15, year=2018)\n",
15+
"\n",
16+
"# from aocd import submit\n",
17+
"# submit(my_answer, part=\"a\", day=25, year=2017)"
18+
]
19+
},
20+
{
21+
"cell_type": "code",
22+
"execution_count": 37,
23+
"metadata": {},
24+
"outputs": [],
25+
"source": [
26+
"# data = \"\"\"#######\n",
27+
"# #G..#E#\n",
28+
"# #E#E.E#\n",
29+
"# #G.##.#\n",
30+
"# #...#E#\n",
31+
"# #...E.#\n",
32+
"# #######\"\"\"\n",
33+
"\n",
34+
"data = \"\"\"#########\n",
35+
"#G......#\n",
36+
"#.E.#...#\n",
37+
"#..##..G#\n",
38+
"#...##..#\n",
39+
"#...#...#\n",
40+
"#.G...G.#\n",
41+
"#.....G.#\n",
42+
"#########\"\"\""
43+
]
44+
},
45+
{
46+
"cell_type": "code",
47+
"execution_count": 165,
48+
"metadata": {},
49+
"outputs": [],
50+
"source": [
51+
"from IPython.display import clear_output\n",
52+
"import time\n",
53+
"\n",
54+
"icons = {'#': '⬛️', '.': '▫️', 'G': '👺', 'E': '🧝‍♀️'}\n",
55+
"\n",
56+
"def print_grid(grid, round_num=None, attack_pos=None, \n",
57+
" elf_hitpoints=0,\n",
58+
" clear=True, delay=0.003):\n",
59+
" if clear:\n",
60+
" clear_output(wait=True)\n",
61+
" \n",
62+
" print(f\"Round #{round_num}\\n🧝‍♀️📶: {elf_hitpoints}\\n\")\n",
63+
" \n",
64+
" max_x = max(x for _,x in grid.keys())\n",
65+
" max_y = max(y for y,_ in grid.keys())\n",
66+
" for x in range(max_x+1):\n",
67+
" for y in range(max_y+1):\n",
68+
" if (x,y) == attack_pos:\n",
69+
" print('💥', end='')\n",
70+
" elif grid[(x,y)][1] < 0:\n",
71+
" print('💀', end='')\n",
72+
" else:\n",
73+
" print(icons[grid[(x,y)][0]], end='')\n",
74+
" print(\"\")\n",
75+
"\n",
76+
" if delay:\n",
77+
" time.sleep(delay)\n"
78+
]
79+
},
80+
{
81+
"cell_type": "code",
82+
"execution_count": 167,
83+
"metadata": {},
84+
"outputs": [],
85+
"source": [
86+
"dirs = [(-1,0),(0,-1),(0,1),(1,0)]\n",
87+
"neighbours = lambda x,y: [(x+dx, y+dy) for dx,dy in dirs]\n",
88+
"\n",
89+
"\n",
90+
"def get_dists(grid, target):\n",
91+
" preds = dict()\n",
92+
"\n",
93+
" dists = {p:0 for p,(v,_) in grid.items() if v == target}\n",
94+
" stack = [(p, None) for p in dists.keys()]\n",
95+
"\n",
96+
" while len(stack):\n",
97+
" pos, pred = stack.pop()\n",
98+
" d = dists.get(pos)\n",
99+
"\n",
100+
" for neigh_pos in neighbours(*pos):\n",
101+
" if neigh_pos != pred and grid[neigh_pos][0] == '.' and dists.get(neigh_pos, sys.maxsize) > d+1:\n",
102+
" dists[neigh_pos] = d+1\n",
103+
" stack.append((neigh_pos, pos))\n",
104+
"\n",
105+
" return dists\n",
106+
"\n",
107+
"def move(grid, start, dest):\n",
108+
" grid[dest] = grid[start]\n",
109+
" grid[start] = ('.', 0)\n",
110+
"\n",
111+
"def hit(grid, pos, hit_points=3):\n",
112+
" grid[pos][1] -= hit_points\n",
113+
" if grid[pos][1] <= 0:\n",
114+
" grid[pos] = ('.', -1)"
115+
]
116+
},
117+
{
118+
"cell_type": "code",
119+
"execution_count": 168,
120+
"metadata": {},
121+
"outputs": [],
122+
"source": [
123+
"def simulate_combat(data, elf_hitpoints=3, legolas_must_live = False, animate = False):\n",
124+
" \n",
125+
" grid = {(y,x):[v, 200] for y,row in enumerate(data.split('\\n')) for x,v in enumerate(row)}\n",
126+
"\n",
127+
" if animate:\n",
128+
" print_grid(grid)\n",
129+
" else:\n",
130+
" clear_output()\n",
131+
" print(f\"🧝‍♀️📶: {elf_hitpoints}\\n\")\n",
132+
"\n",
133+
" for round_num in range(0, 1000):\n",
134+
" game_over = False\n",
135+
"\n",
136+
" players_pos = sorted(pos for pos,(player,_) in grid.items() if player in 'GE')\n",
137+
"\n",
138+
" for cur_pos in players_pos:\n",
139+
" player = grid[cur_pos][0]\n",
140+
" if player == 'G':\n",
141+
" target = 'E'\n",
142+
" elif player == 'E':\n",
143+
" target = 'G'\n",
144+
" else:\n",
145+
" continue\n",
146+
"\n",
147+
" # MOVE\n",
148+
" if all(grid[p][0] != target for p in neighbours(*cur_pos)):\n",
149+
" dists = get_dists(grid, target = target)\n",
150+
" min_dist = min(dists.get(p, sys.maxsize) for p in neighbours(*cur_pos))\n",
151+
"\n",
152+
" if min_dist < sys.maxsize:\n",
153+
" for dest_pos in neighbours(*cur_pos):\n",
154+
" if dists.get(dest_pos, -1) == min_dist:\n",
155+
" move(grid, cur_pos, dest_pos)\n",
156+
" cur_pos = dest_pos\n",
157+
" break\n",
158+
"\n",
159+
" targets = [(p,grid[p][1]) for p in neighbours(*cur_pos) if grid[p][0] == target]\n",
160+
"\n",
161+
" # ATTACK!\n",
162+
" if len(targets):\n",
163+
" target_pos = min(targets, key=lambda x:x[1])[0]\n",
164+
" if player == 'E':\n",
165+
" hit(grid, target_pos, elf_hitpoints)\n",
166+
" else:\n",
167+
" hit(grid, target_pos, 3)\n",
168+
" if (grid[target_pos][1] <= 0) and legolas_must_live:\n",
169+
" return False\n",
170+
" \n",
171+
" if animate:\n",
172+
" print_grid(grid, attack_pos=target_pos,\n",
173+
" elf_hitpoints=elf_hitpoints, round_num=round_num,\n",
174+
" delay=0.05)\n",
175+
" print_grid(grid, round_num=round_num, elf_hitpoints=elf_hitpoints)\n",
176+
"\n",
177+
" \n",
178+
" if len(set(p for p,_ in grid.values())) == 3:\n",
179+
" print_grid(grid, round_num=round_num, elf_hitpoints=elf_hitpoints)\n",
180+
" print(\"\\nGAME OVER!\")\n",
181+
" tot_points = sum(h for p,h in grid.values() if p in 'GE')\n",
182+
" print(round_num, 'x', tot_points, ' -> ', round_num*tot_points)\n",
183+
" return round_num*tot_points\n",
184+
" \n",
185+
" "
186+
]
187+
},
188+
{
189+
"cell_type": "code",
190+
"execution_count": 169,
191+
"metadata": {
192+
"scrolled": false
193+
},
194+
"outputs": [
195+
{
196+
"name": "stdout",
197+
"output_type": "stream",
198+
"text": [
199+
"Round #82\n",
200+
"🧝‍♀️📶: 3\n",
201+
"\n",
202+
"⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
203+
"⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️⬛️▫️⬛️▫️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
204+
"⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️▫️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
205+
"⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️▫️▫️⬛️▫️⬛️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
206+
"⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️⬛️⬛️▫️▫️⬛️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
207+
"⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
208+
"⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
209+
"⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
210+
"⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️▫️⬛️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
211+
"⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️⬛️⬛️▫️▫️▫️▫️▫️⬛️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
212+
"⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️▫️▫️▫️▫️▫️⬛️⬛️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
213+
"⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️⬛️⬛️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
214+
"⬛️⬛️⬛️⬛️⬛️⬛️▫️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️▫️👺▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
215+
"⬛️⬛️⬛️⬛️⬛️▫️▫️▫️⬛️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️💀💀⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
216+
"⬛️⬛️⬛️⬛️⬛️▫️▫️▫️▫️▫️👺▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
217+
"⬛️⬛️⬛️⬛️⬛️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
218+
"⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️▫️⬛️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️⬛️⬛️⬛️⬛️⬛️⬛️▫️▫️⬛️⬛️\n",
219+
"⬛️▫️⬛️⬛️⬛️⬛️⬛️⬛️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️▫️▫️▫️⬛️⬛️⬛️▫️▫️▫️⬛️\n",
220+
"⬛️▫️▫️▫️⬛️⬛️⬛️⬛️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️▫️▫️▫️⬛️⬛️⬛️▫️▫️⬛️⬛️\n",
221+
"⬛️▫️▫️▫️▫️▫️⬛️👺▫️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️▫️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️⬛️\n",
222+
"⬛️⬛️▫️▫️▫️▫️⬛️▫️👺▫️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️⬛️\n",
223+
"⬛️⬛️▫️▫️▫️▫️▫️▫️▫️▫️⬛️▫️👺👺💀👺▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️⬛️\n",
224+
"⬛️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️👺💀👺👺▫️⬛️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️⬛️⬛️\n",
225+
"⬛️▫️▫️▫️▫️▫️▫️▫️▫️⬛️▫️▫️👺▫️▫️▫️👺▫️▫️▫️▫️⬛️⬛️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️\n",
226+
"⬛️▫️▫️▫️▫️▫️▫️▫️👺⬛️⬛️⬛️▫️👺▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️\n",
227+
"⬛️⬛️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️▫️▫️▫️▫️▫️▫️⬛️▫️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️\n",
228+
"⬛️⬛️▫️⬛️▫️▫️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️▫️▫️▫️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
229+
"⬛️▫️▫️⬛️▫️▫️▫️⬛️▫️▫️▫️▫️▫️▫️⬛️▫️▫️▫️▫️▫️▫️▫️▫️▫️⬛️⬛️▫️⬛️⬛️⬛️⬛️⬛️\n",
230+
"⬛️⬛️⬛️⬛️⬛️▫️▫️⬛️▫️▫️▫️▫️▫️▫️⬛️▫️⬛️▫️▫️▫️▫️▫️⬛️▫️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️\n",
231+
"⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️▫️▫️▫️👺▫️▫️👺⬛️▫️▫️▫️▫️⬛️⬛️⬛️▫️▫️▫️▫️▫️⬛️▫️⬛️\n",
232+
"⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️▫️▫️▫️▫️▫️▫️▫️▫️▫️⬛️▫️▫️▫️⬛️⬛️⬛️⬛️⬛️▫️⬛️▫️▫️▫️⬛️\n",
233+
"⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️⬛️\n",
234+
"\n",
235+
"GAME OVER!\n",
236+
"82 x 2606 -> 213692\n",
237+
"Part 1: 213692\n"
238+
]
239+
}
240+
],
241+
"source": [
242+
"res = simulate_combat(data, elf_hitpoints=3, legolas_must_live=False, animate=True)\n",
243+
"print(\"Part 1:\", res)\n",
244+
"\n",
245+
"# for elf_hitpoints in range(4, 50):\n",
246+
"# res = simulate_combat(data, elf_hitpoints=elf_hitpoints, legolas_must_live=True, animate=False)\n",
247+
"# if res is not False:\n",
248+
"# break\n",
249+
"# print(\"Part 2:\", res)"
250+
]
251+
},
252+
{
253+
"cell_type": "code",
254+
"execution_count": null,
255+
"metadata": {},
256+
"outputs": [],
257+
"source": []
258+
}
259+
],
260+
"metadata": {
261+
"kernelspec": {
262+
"display_name": "Python 3 (ipykernel)",
263+
"language": "python",
264+
"name": "python3"
265+
},
266+
"language_info": {
267+
"codemirror_mode": {
268+
"name": "ipython",
269+
"version": 3
270+
},
271+
"file_extension": ".py",
272+
"mimetype": "text/x-python",
273+
"name": "python",
274+
"nbconvert_exporter": "python",
275+
"pygments_lexer": "ipython3",
276+
"version": "3.10.8"
277+
}
278+
},
279+
"nbformat": 4,
280+
"nbformat_minor": 4
281+
}

0 commit comments

Comments
 (0)