summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorb-idea <test@test.com>2023-10-20 23:22:25 +0200
committerb-idea <test@test.com>2023-10-20 23:22:25 +0200
commit61073dcec8e896b84fafbd6110e2da55a0dd2d5e (patch)
tree22f2f3c647f02102974df58f727601e0c66d429e
parent5b8623d06472ec9a4bcac8ea1e201b7ae528d7af (diff)
added days: 15_p1, 17, 20, 21, 22, 23_p1, 24, 25
-rw-r--r--2018/aoc2018-d15-p1.py324
-rw-r--r--2018/aoc2018-d17.py144
-rw-r--r--2018/aoc2018-d20.py115
-rw-r--r--2018/aoc2018-d21.py253
-rw-r--r--2018/aoc2018-d22.py136
-rw-r--r--2018/aoc2018-d23-p1.py47
-rw-r--r--2018/aoc2018-d24.py211
-rw-r--r--2018/aoc2018-d25.py69
8 files changed, 1299 insertions, 0 deletions
diff --git a/2018/aoc2018-d15-p1.py b/2018/aoc2018-d15-p1.py
new file mode 100644
index 0000000..6a2665d
--- /dev/null
+++ b/2018/aoc2018-d15-p1.py
@@ -0,0 +1,324 @@
+#advent of code 2018
+#day 13
+#part 1
+import heapq
+
+f = open("input.txt",'r');
+Testing = False;
+Testing = True;
+if Testing:
+ f.close();
+ f = open("testinput.txt",'r');
+Testing2 = False;
+Testing2 = True;
+if Testing2:
+ f.close();
+ f = open("testinput2.txt",'r');
+Testing3 = False;
+#Testing3 = True;
+if Testing3:
+ f.close();
+ f = open("testinput3.txt",'r');
+Testing4 = False;
+#Testing4 = True;
+if Testing4:
+ f.close();
+ f = open("testinput4.txt",'r');
+Testing5 = False;
+Testing5 = True;
+if Testing5:
+ f.close();
+ f = open("testinput5.txt",'r');
+Testing6 = False;
+Testing6 = True;
+if Testing6:
+ f.close();
+ f = open("testinput6.txt",'r');
+Testing7 = False;
+Testing7 = True;
+if Testing7:
+ f.close();
+ f = open("testinput7.txt",'r');
+NotTesting = False;
+NotTesting = True;
+if NotTesting:
+ f.close();
+ f = open("input.txt",'r');
+ #f = open("shit.txt",'r');
+
+class thing:
+ def __init__(self, Pos, sign, currentID):
+ self.sign = sign;
+ self.PosX = Pos[1];
+ self.PosY = Pos[0];
+ self.IsAlive = False;
+ if (sign == "."):
+ self.IsPassable = True;
+ else:
+ self.IsPassable = False;
+ def __str__(self):
+ return self.sign;
+
+ def SquaresInRange(self,bg):
+ squares = [];
+ p = (self.PosY-1,self.PosX);
+ if(bg[p].IsPassable): squares.append(p);
+ p = (self.PosY,self.PosX-1);
+ if(bg[p].IsPassable): squares.append(p);
+ p = (self.PosY,self.PosX+1);
+ if(bg[p].IsPassable): squares.append(p);
+ p = (self.PosY+1,self.PosX);
+ if(bg[p].IsPassable): squares.append(p);
+ return squares;
+
+class creature(thing):
+ def __init__(self, Pos, sign, currentID):
+ thing.__init__(self, Pos, sign, currentID);
+ self.ID = currentID;
+ self.health = 200;
+ self.damage = 3;
+ self.side = sign;
+ self.IsAlive = True;
+ if (self.side == "G"):
+ self.enemy = "E";
+ if (self.side == "E"):
+ self.enemy = "G";
+
+ def death(self,bg):
+ self.IsPassable = True;
+ self.IsAlive = False;
+ self.health = 0;
+ unitcords[(self.PosY,self.PosX)] = None;
+ bg[(self.PosY,self.PosX)].IsPassable = True;
+
+
+ def movement(self,bg,PosNow,PosNew):
+ self.PosX = PosNew[1];
+ self.PosY = PosNew[0];
+ bg[PosNow].IsPassable = True;
+ bg[PosNew].IsPassable = False;
+ unitcords[PosNew] = self.ID;
+ unitcords[PosNow] = None;
+
+
+ def ScanProximity(self):
+ targets = [];
+ p = (self.PosY-1,self.PosX);
+ if p in unitcords and unitcords[p] in units and units[unitcords[p]].side == self.enemy and units[unitcords[p]].health >0:
+ targets.append(p);
+ p = (self.PosY,self.PosX-1);
+ if p in unitcords and unitcords[p] in units and units[unitcords[p]].side == self.enemy and units[unitcords[p]].health >0:
+ targets.append(p);
+ p = (self.PosY,self.PosX+1);
+ if p in unitcords and unitcords[p] in units and units[unitcords[p]].side == self.enemy and units[unitcords[p]].health >0:
+ targets.append(p);
+ p = (self.PosY+1,self.PosX);
+ if p in unitcords and unitcords[p] in units and units[unitcords[p]].side == self.enemy and units[unitcords[p]].health >0:
+ targets.append(p);
+ if targets:
+ targets.sort(key = lambda x: (units[unitcords[x]].health,units[unitcords[x]].PosY,units[unitcords[x]].PosX));
+ return targets;
+
+
+ def IdentifyTargets_ALL(self,creaturelist,bg):
+ points = [];
+ for c in creaturelist:
+ if units[c].side == self.side or not units[c].IsAlive: continue;
+ points.extend(units[c].SquaresInRange(bg));
+ return points;
+
+
+ def ShortestPaths(self, pointlist, bg):
+ paths = [];
+ pathlen = len(bg);
+ queue = [];
+ heapq.heappush(queue, (0,[(self.PosY,self.PosX)]));
+ visited = [];
+ while queue:
+ d, path = heapq.heappop(queue);
+ if d > pathlen:
+ return paths;
+ if path[-1] in pointlist:
+ if pathlen > len(path):
+ paths.append(path);
+ pathlen = len(path);
+ continue;
+ if path[-1] in visited:
+ continue;
+ else:
+ visited.append(path[-1]);
+ for a in bg[path[-1]].SquaresInRange(bg):
+ if a in visited: continue;
+ heapq.heappush(queue, (d+1, path + [a]));
+ return paths;
+
+ def ChosenPath(self, pointlist, bg):
+ paths = self.ShortestPaths(pointlist, bg);
+ if len(paths) != 0:
+ pathends = min([p[-1] for p in paths]);
+ paths2= self.ShortestPaths([pathends], bg);
+ if len(paths2) != 0:
+ moves = [];
+ for p in paths:
+ moves.append(p[1]);
+ moves = sorted(moves, key = lambda m: (m[0],m[1]));
+ m = moves[0];
+ self.movement(bg,(self.PosY,self.PosX),m);
+
+
+def PathSearch(bg, Coordinates, Destination):
+ pass;
+
+def strike(attacker, target,bg):
+ target.health -= attacker.damage;
+ if (target.health <= 0):
+ target.death(bg);
+
+def armystrength(army):
+ strength = 0;
+ for soldier in army:
+ strength += units[unitcords[soldier]].health;
+ return strength;
+
+def armyscores():
+ ElfScore = 0;
+ GobScore = 0;
+ for u in units:
+ if units[u].side == "G": GobScore += units[u].health;
+ if units[u].side == "E": ElfScore += units[u].health;
+
+ return max(ElfScore,GobScore);
+
+
+
+def FindEnemies(team):
+ Enemies = 0;
+ for u in units:
+ if units[u].IsAlive and units[u].side != team: Enemies += 1;
+
+ return Enemies;
+###############################################################################
+
+
+bgsizex = 0;
+bgsizey = 0;
+battleground = {};
+units = {};
+unitcords = {};
+
+identifier = 11;
+
+for y, line in enumerate(f):
+ bgsizey += 1;
+ bgsizex = 0;
+ for x, c in enumerate(line):
+ bgsizex += 1;
+ if c == "G" or c == "E":
+ battleground[(y,x)] = thing((y,x),".",identifier);
+ units[identifier] = creature((y,x),c, identifier);
+ units[identifier].movement(battleground, (y,x), (y,x));
+ identifier += 1;
+ else:
+ battleground[(y,x)] = thing((y,x),c,identifier);
+'''
+for y in range(bgsizey):
+ for x in range(bgsizex):
+ if (y,x) in unitcords:
+ print("X",end="");
+ else:
+ print(battleground[(y,x)],end=""); #'''
+
+
+unitcords = {};
+for u in units:
+ unitcords[(units[u].PosY,units[u].PosX)] = units[u].ID;
+
+
+elfs = sum([1 for x in units if units[x].IsAlive and units[x].side=="E"]);
+goblins = sum([1 for x in units if units[x].IsAlive and units[x].side=="G"]);
+
+ElapsedRounds = 0;
+
+
+while goblins and elfs:
+ fighters = [];
+ for u in units:
+ if units[u].IsAlive:
+ fighters.append(units[u].ID);
+
+ fighters = sorted(fighters, key = lambda u: (units[u].PosY,units[u].PosX));
+
+ for fighter in fighters:
+ if not units[fighter].IsAlive: continue;
+ if FindEnemies(units[fighter].side) <= 0:
+ print("loop broken FIGHTERS");
+ break;
+ tars = units[fighter].ScanProximity();
+ if tars:
+ tar = tars[0];
+ strike(units[fighter],units[unitcords[tar]],battleground);
+ else:
+ CurrentPos = (units[fighter].PosY,units[fighter].PosX);
+ IdenTars = units[fighter].IdentifyTargets_ALL(fighters,battleground);
+ units[fighter].ChosenPath(IdenTars,battleground);
+ if CurrentPos == (units[fighter].PosY,units[fighter].PosX):
+ continue;
+ else:
+ tars = units[fighter].ScanProximity();
+ if tars:
+ tar = tars[0];
+ strike(units[fighter],units[unitcords[tar]],battleground);
+
+ unitcords = {};
+ for u in units:
+ unitcords[(units[u].PosY,units[u].PosX)] = units[u].ID;
+
+ print("ROUND ", ElapsedRounds);
+ unitcords = {};
+ for u in units:
+ unitcords[(units[u].PosY,units[u].PosX)] = units[u].ID;
+
+ ElapsedRounds += 1;
+ elfs = sum([1 for x in units if units[x].IsAlive and units[x].side=="E"]);
+ goblins = sum([1 for x in units if units[x].IsAlive and units[x].side=="G"]);
+ '''
+ for y in range(bgsizey):
+ for x in range(bgsizex):
+ if (y,x) in unitcords and unitcords[(y,x)] in units and units[unitcords[(y,x)]].IsAlive:
+ print(units[unitcords[(y,x)]].side,end="");
+ else:
+ print(battleground[(y,x)],end="");
+ print();
+ '''
+
+ '''
+ for y in range(bgsizey):
+ for x in range(bgsizex):
+ print((y,x), " - ", battleground[(y,x)].IsPassable);
+ print();
+ print();
+ '''
+
+
+
+unitcords = {};
+for u in units:
+ unitcords[(units[u].PosY,units[u].PosX)] = units[u].ID;
+ print(u, f'({units[u].side} / ({units[u].PosY},{units[u].PosX})) ', units[u].health);
+
+
+for y in range(bgsizey):
+ for x in range(bgsizex):
+ if (y,x) in unitcords and unitcords[(y,x)] in units and units[unitcords[(y,x)]].IsAlive:
+ print(units[unitcords[(y,x)]].side,end="");
+ else:
+ print(battleground[(y,x)],end="");
+print();
+
+
+print("part 1 = ", ElapsedRounds, "*",armyscores(), "=",ElapsedRounds*armyscores());
+print("lil cheat = ", (ElapsedRounds-1), "*",armyscores(), "=",(ElapsedRounds-1)*armyscores());
+#I cant get the correct answer without implementing any short circuit logics
+#thats why I print 2 outputs, for number of rounds and (number of rounds -1)
+
+#190777 p1 correct
diff --git a/2018/aoc2018-d17.py b/2018/aoc2018-d17.py
new file mode 100644
index 0000000..30cd726
--- /dev/null
+++ b/2018/aoc2018-d17.py
@@ -0,0 +1,144 @@
+#advent of code 2018
+#day 17
+#part 1 and part 2
+
+#for me this is one of the most time consuming puzzles for this year
+#I got too cocky because I remembered a similar puzzle during 2022 edition
+#unfortunately 2018 day 17 was more tricky
+#I made 3 attempts, each time starting from scratch
+#first time I just scrambled something, but there was no way to calculate when to stop the algo
+#so the water was extremely overflowing
+#for second attempt I wrote 2 functions, one for flowing downwards, second for spreading
+#I spent a whole day "improving" the spreading function so it behaves correctly, to no avail
+#I figured out the logic of the puzzle, but I kept getting weird errors
+#such as water bursting through walls or flowing upwards
+#I was adding more and more if-elses, but they weren't fixing these bugs:
+#they were merely delaying them - after filling in the water correctly, the bugs were popping up again
+#third and final attempt was successful - I already learned the logic of the algorithm,
+#I just needed to implement it in bug-free way
+#on the other hand, I managed to get a pretty good runtime.
+#Every now and then I was looking up a solution for clues from others
+#one of which Michael Fogleman, probably my most common resource for learning AoC 2018
+#while his code runs for almost a minute, mine just needs less than 0.05 secs
+#it's really funny to "outperform" (in a way) someone whom you were constantly consulting for help
+#one last thing I want to mention is that during the third attempt I kept getting wrong answer
+#since I was only off by 3 and at the end there are only 3 waterfalls connecting to lowest point,
+#I was convinced that I was calculating the solution for wrong range
+#turns out I misunderstood the puzzle - you're supposed to calculate the amount of still
+#and flowing water in range of highest wall point and the lowest wall point,
+#while I kept calculating it in range from zero to lowest
+#at least this time part2 was a freebee
+
+import time;
+
+f = open("input.txt",'r');
+Testing = False;
+#Testing = True;
+if Testing:
+ f.close();
+ f = open("testinput.txt",'r');
+
+def parse_input(line):
+ line = line.replace("x=","");
+ line = line.replace("y=","");
+ line = line.replace("..",",");
+ line = "[" + line + "]";
+ cords = eval(line);
+ return cords; #line coordinates: [x, y1, y2] or [y, x1, x2]
+
+Xmax = 500; #rightmost X coordinate, just for printing the area
+Xmin = 500; #leftmost X coordinate, just for printing the area
+Ymax = 0; #lowest Y coordinate, needed for solution
+Ymin2 = 2000; #highest Y coordinate, needed for solution
+LinesX = [];
+LinesY = [];
+
+for l in f:
+ d = l[0]; #direction x or y
+ Cords = parse_input(l);
+ if d == "x":
+ Xmax = max(Cords[0], Xmax);
+ Xmin = min(Cords[0], Xmin);
+ Ymax = max(Cords[2], Ymax);
+ Ymin2 = min(Cords[1], Ymin2);
+ LinesX.append(Cords);
+ else:
+ Xmax = max(Cords[2], Xmax);
+ Xmin = min(Cords[1], Xmin);
+ Ymax = max(Cords[0], Ymax);
+ Ymin2 = min(Cords[0], Ymin2);
+ LinesY.append(Cords);
+
+Xmax += 1;
+Xmin -= 1;
+Ymin = 0;
+
+area = {};
+#initialize input into the area
+for l in LinesX:
+ x = l[0];
+ for y in range(l[1],l[2]+1):
+ area.update({(x,y):"#"});
+for l in LinesY:
+ y = l[0];
+ for x in range(l[1],l[2]+1):
+ area.update({(x,y):"#"});
+
+def wspread(cords, q):
+ r1 = 0;
+ r2 = 0;
+ x,y = cords;
+ while (area.get((x,y+1)," ") == "#" or area.get((x,y+1)," ") == "=") and area.get((x-1,y)," ") != "#":
+ r1 += 1;
+ x -= 1;
+
+ x,y = cords;
+ while (area.get((x,y+1)," ") == "#" or area.get((x,y+1)," ") == "=") and area.get((x+1,y)," ") != "#":
+ r2 += 1;
+ x += 1;
+
+ x,y = cords;
+ if area.get((x-r1-1,y)," ") == "#" and area.get((x+r2+1,y)," ") == "#":
+ for r in range(x-r1,x+r2+1):
+ area[(r,y)] = "=";
+ q.append((cords[0],cords[1]-1));
+ else:
+ for r in range(x-r1,x+r2+1):
+ area[(r,y)] = "|";
+ if area[(x-r1,y)] != "#" and area.get((x-r1,y+1)," ") == " ":
+ q.append((x-r1,y));
+ if area[(x+r2,y)] != "#" and area.get((x+r2,y+1)," ") == " ":
+ q.append((x+r2,y));
+
+def wfall(cords, q):
+ x,y = cords;
+ while area.get((x,y+1)," ")==" " and y < Ymax:
+ area[(x,y+1)] = "|";
+ y += 1;
+ if area.get((x,y)," ")=="#" or area.get((x,y)," ")=="=":
+ q.append((x,y-1));
+ elif y < Ymax:
+ q.append((x,y));
+
+t1 = time.time();
+water = [(500,0)];
+area.update({(500,0):"|"});
+
+while water:
+ w = water.pop();
+ if area.get((w[0],w[1]+1)," ")==" ":
+ wfall(w, water);
+ else:
+ wspread(w, water);
+
+part1 = 0;
+part2 = 0;
+for a in area:
+ part1 += 1*(area[a]=="|" or area[a]=="=")*(a[1] >= Ymin2);
+ part2 += 1*(area[a]=="=");
+
+t2 = time.time();
+
+print("part1 = ", part1);
+print("part2 = ", part2);
+print("runtime ", t2-t1);
diff --git a/2018/aoc2018-d20.py b/2018/aoc2018-d20.py
new file mode 100644
index 0000000..06241f5
--- /dev/null
+++ b/2018/aoc2018-d20.py
@@ -0,0 +1,115 @@
+#advent of code 2018
+#day 20
+#part 1 and part 2
+
+#I struggled with indexing when parsing the input
+#I was getting more doors mapped than there really were
+#the issue was that after parsing the content of a bracket
+#I was retreiving a ")" sign instead of the following instruction like N or W
+#+1 to the index did the work
+#a bit disappointed with part 2, on which I spent more time reading it than coding
+
+f = open("input.txt",'r');
+Testing = False;
+#Testing = True;
+if Testing:
+ f.close();
+ f = open("testinput.txt",'r');
+
+inputline = f.readline();
+inputline = inputline[1:-1];
+f.close();
+#split the contents of a bracket into separate strings, return a list of those strings
+def parse_brackets(regex):
+ regex = regex[1:];
+ close_brackets_sign = ")";
+ close_brackets_count = 1;
+ subsets = [];
+ subset = "";
+ for i, c in enumerate(regex):
+ if c == "(":
+ close_brackets_count += 1;
+ subset += c;
+ elif c == close_brackets_sign:
+ if close_brackets_count == 1:
+ subsets.append(subset);
+ break;
+ else:
+ close_brackets_count -= 1;
+ subset += c;
+ elif c == "|":
+ if close_brackets_count == 1:
+ subsets.append(subset);
+ subset = "";
+ else: subset += c;
+ else: subset += c;
+ return i, subsets;
+#fill up the "area" dictionary with rooms and doors based on provided instruction
+#recursive function, the function is called again if the path is branching due to brackets
+def parse_input(inputline, Cords, grid):
+ i = 0;
+ d = (0,0);
+ CurrentCords = Cords;
+ CurrentCords = (CurrentCords[0] +d[0],CurrentCords[1] +d[1]);
+ while i < len(inputline):
+ c = inputline[i];
+ if c == "(":
+ x, subs = parse_brackets(inputline[i:]);
+ for s in subs:
+ parse_input(s, CurrentCords, grid);
+ i += x+1;
+ elif c == "$":
+ break;
+ else:
+ if c == "N":
+ d = (0,-1);
+ elif c == "S":
+ d = (0,1);
+ elif c == "W":
+ d = (-1,0);
+ elif c == "E":
+ d = (1,0);
+ CurrentCords = (CurrentCords[0] +d[0],CurrentCords[1] +d[1]);
+ grid.update({CurrentCords:"D"});
+ CurrentCords = (CurrentCords[0] +d[0],CurrentCords[1] +d[1]);
+ grid.update({CurrentCords:"."});
+ i += 1;
+
+area = {};
+p = (0,0);
+area.update({p:"."});
+
+parse_input(inputline, p, area);
+#print the map of the area
+'''
+maxX = max([x[0] for x in area]);
+minX = min([x[0] for x in area]);
+maxY = max([y[1] for y in area]);
+minY = min([y[1] for y in area]);
+
+for y in range(minY-1, maxY+1+1):
+ for x in range(minX-1, maxX+1+1):
+ cp = area.get((x,y),"#");
+ if x==0 and y==0: cp = "X";
+ print(cp, end="");
+ print();
+'''
+
+distances = {};
+queue = [(0,0,0)];
+
+while queue:
+ x,y,d = queue.pop();
+ if ((x,y) in distances and distances.get((x,y)) <= d):
+ continue;
+ distances.update({(x,y):d});
+ if (x+1,y) in area and (x+2,y) in area: queue.append((x+2,y,d+1));
+ if (x-1,y) in area and (x-2,y) in area: queue.append((x-2,y,d+1));
+ if (x,y+1) in area and (x,y+2) in area: queue.append((x,y+2,d+1));
+ if (x,y-1) in area and (x,y-2) in area: queue.append((x,y-2,d+1));
+
+part1 = max([distances[dist] for dist in distances]);
+print("part1 = ", part1);
+
+part2 =len([distances[dist] for dist in distances if distances[dist]>=1000]);
+print("part2 = ", part2);
diff --git a/2018/aoc2018-d21.py b/2018/aoc2018-d21.py
new file mode 100644
index 0000000..890b13d
--- /dev/null
+++ b/2018/aoc2018-d21.py
@@ -0,0 +1,253 @@
+#advent of code 2018
+#day 21
+#part 1 and part 2
+
+#this puzzle in my opinion was poorly worded, or I'm just overthinking
+#part 1 was suprisingly simple since it only required running the solution for day 19
+#unfortunately, bruteforcing the answer for part 2 didn't go as well
+#reading through input its possible to identify instruction jumps and determine what causes them
+#I was stuck for like 2 days for part 2: I couldn't find a bug in my reverse-engineering
+#I even run someone else's solution (modified for my input) to verify it
+#only to get another wrong answer
+#I found a second solution and I compared it to mine
+#only to find out that the issue wasn't in logic, I simply entered wrong value in 1 line
+#at least this reverse-engineering puzzle was for me more enjoyable than the 2021 one
+
+#part 1 bruteforce
+#program halts if the value at register 0 matches the value at register X
+#(register X is between 1 and 5, depends on the input)
+#in one particular instruction near the end
+#instruction 28 in my case
+#calculating this value provides part 1 result
+
+#part 2 reverse engineering
+#you can simplify the input program by reverse-engineering
+#basically the first few lines are trash, then the program is initiated with starting values
+#if condition 1 is met, program enters a loop until condition 1 is no longer true
+#after this loop, program checks if the value at register 0 is equal to corresponding register X
+#if true, program halts
+#you can print out all values at register X, first printed message is part1, last one is part 2
+
+
+###opcode functions
+#bit modified version of day 19
+def addr(before, op):
+ bef = before.copy();
+ C = bef[op[1]] + bef[op[2]];
+ bef[op[3]] = C;
+ return bef;
+def addi(before, op):
+ bef = before.copy();
+ C = bef[op[1]] + op[2];
+ bef[op[3]] = C;
+ return bef;
+
+def mulr(before, op):
+ bef = before.copy();
+ C = bef[op[1]] * bef[op[2]];
+ bef[op[3]] = C;
+ return bef;
+def muli(before, op):
+ bef = before.copy();
+ C = bef[op[1]] * op[2];
+ bef[op[3]] = C;
+ return bef;
+
+def banr(before, op):
+ bef = before.copy();
+ C = int(bef[op[1]] & bef[op[2]]);
+ bef[op[3]] = C;
+ return bef;
+def bani(before, op):
+ bef = before.copy();
+ C = int(bef[op[1]] & op[2]);
+ bef[op[3]] = C;
+ return bef;
+
+def borr(before, op):
+ bef = before.copy();
+ C = int(bef[op[1]] | bef[op[2]]);
+ bef[op[3]] = C;
+ return bef;
+def bori(before, op):
+ bef = before.copy();
+ C = int(bef[op[1]] | op[2]);
+ bef[op[3]] = C;
+ return bef;
+
+def setr(before, op):
+ bef = before.copy();
+ C = bef[op[1]];
+ bef[op[3]] = C;
+ return bef;
+def seti(before, op):
+ bef = before.copy();
+ C = op[1];
+ bef[op[3]] = C;
+ return bef;
+
+def gtir(before, op):
+ bef = before.copy();
+ C = 0 + int( op[1] > bef[op[2]] );
+ bef[op[3]] = C;
+ return bef;
+def gtri(before, op):
+ bef = before.copy();
+ C = 0 + int( op[2] < bef[op[1]] );
+ bef[op[3]] = C;
+ return bef;
+def gtrr(before, op):
+ bef = before.copy();
+ C = 0 + int( bef[op[1]] > bef[op[2]] );
+ bef[op[3]] = C;
+ return bef;
+
+def eqir(before, op):
+ bef = before.copy();
+ C = 0 + int( op[1] == bef[op[2]] );
+ bef[op[3]] = C;
+ return bef;
+def eqri(before, op):
+ bef = before.copy();
+ C = 0 + int( op[2] == bef[op[1]] );
+ bef[op[3]] = C;
+ return bef;
+def eqrr(before, op):
+ bef = before.copy();
+ C = 0 + int( bef[op[1]] == bef[op[2]] );
+ bef[op[3]] = C;
+ return bef;
+
+MatchFunctions = {};
+MatchFunctions.update({"addr":addr});
+MatchFunctions.update({"addi":addi});
+MatchFunctions.update({"mulr":mulr});
+MatchFunctions.update({"muli":muli});
+MatchFunctions.update({"banr":banr});
+MatchFunctions.update({"bani":bani});
+MatchFunctions.update({"borr":borr});
+MatchFunctions.update({"bori":bori});
+MatchFunctions.update({"setr":setr});
+MatchFunctions.update({"seti":seti});
+MatchFunctions.update({"gtir":gtir});
+MatchFunctions.update({"gtri":gtri});
+MatchFunctions.update({"gtrr":gtrr});
+MatchFunctions.update({"eqir":eqir});
+MatchFunctions.update({"eqri":eqri});
+MatchFunctions.update({"eqrr":eqrr});
+
+#end of day19 copy-paste
+###############################################
+
+f = open("input.txt",'r');
+Testing = False;
+#Testing = True;
+if Testing:
+ f.close();
+ f = open("testinput.txt",'r');
+
+ip = int(f.readline()[4:]);
+instructions = [];
+
+for l in f:
+ l = l.split();
+ i = [];
+ i.append(l[0]);
+ l_i = [int(x) for x in l[1:]];
+ i += l_i;
+ instructions.append(i);
+
+
+part1 = None;
+instr_lim = len(instructions);
+
+while True:
+ reg = [0,0,0,0,0,0];
+ reg[ip] = 0;
+ part1 = None;
+
+ while True:
+ if( reg[ip] >= instr_lim): break;
+ if( reg[ip] >= 28):
+ #print(reg);
+ part1 = int(reg[5]);
+ break;
+ instr = instructions[reg[ip]];
+ fun = MatchFunctions[instr[0]];
+ reg = fun(reg,instr);
+ reg[ip] += 1;
+
+ if part1 != None:
+ break;
+
+print("part 1 = ", part1, "(bruteforce)");
+
+#part2
+
+store1 = set(); #var ehich determines if the loop continues
+store5 = set(); #var checked against the reg0
+part1 = None;
+part2 = None;
+p2 = None;
+
+reg5 = 10678677;
+reg1 = 2**16;
+while True:
+ reg4 = reg1%256;
+ reg5 += reg4;
+ reg5 = ((reg5%2**24)*65899)%(2**24);
+
+ if reg1<256:
+ if not store5:
+ part1 = int(reg5);
+ if reg5 not in store5:
+ part2 = int(reg5);
+ store5.add(reg5);
+ #print("regs", reg1, reg5)
+ reg1 = reg5 | 2**16;
+ if reg1 in store1:
+ break;
+ store1.add(reg1);
+ reg5 = 10678677;
+ continue;
+
+ reg1 = reg1//256;
+
+print("part 1 = ", part1);
+print("part 2 = ", part2);
+
+#my input
+'''
+#ip 2
+seti 123 0 5
+bani 5 456 5
+eqri 5 72 5
+addr 5 2 2
+seti 0 0 2
+seti 0 4 5
+bori 5 65536 1
+seti 10678677 3 5
+bani 1 255 4
+addr 5 4 5
+bani 5 16777215 5
+muli 5 65899 5
+bani 5 16777215 5
+gtir 256 1 4
+addr 4 2 2
+addi 2 1 2
+seti 27 5 2
+seti 0 6 4
+addi 4 1 3
+muli 3 256 3
+gtrr 3 1 3
+addr 3 2 2
+addi 2 1 2
+seti 25 4 2
+addi 4 1 4
+seti 17 6 2
+setr 4 6 1
+seti 7 5 2
+eqrr 5 0 4
+addr 4 2 2
+seti 5 4 2
+'''
diff --git a/2018/aoc2018-d22.py b/2018/aoc2018-d22.py
new file mode 100644
index 0000000..7dd7a86
--- /dev/null
+++ b/2018/aoc2018-d22.py
@@ -0,0 +1,136 @@
+#advent of code 2018
+#day 22
+#part 1 and part 2
+
+#at this point Im used to pathfinding problems, so I expected an easy 2-star
+#I started with a recursive djikstra-algo function, but unfortunately
+#for the actual input, I was reaching the maximum number of recurrences
+#my plan B was a queue, which in general was correct, but I was missing one detail
+#the problem isnt 2D pathfinding, but 4D (width, depth, time AND equipment)
+#to discover this I needed to look up a solution on reddit
+#said solution reminded me of heapq module which I already intended to learn
+#the results were great - my initial no-heapq code needed almost 10 minutes to finish
+#while with heapq whole program ran for less than half a second
+#while I tried to compensate for the equipment variable with some
+#"guess which equipment is probably the best choice when traversing"
+#it didnt really help as much as heapq
+#anyway, I still wasnt getting the correct answer
+#This time the issue was in the design:
+#in order to account for regions that are outside the initial scope
+#(within target coordinates range) I simply scaled up the whole map by constant factor
+#for factor = 2 the map still wasnt big enough to create the shortest path
+#I needed to scale it up by factoer = 6 to get the correct result
+#while this scale-up strategy isnt *that* bad for square-ish maps
+#this map is extremely deep, but not very wide
+#I really liked the idea to calculate the region type on the spot for part2
+#which I noticed only after struggling for 3 hours to fix my code
+#might redo the part2 to fix this
+#if the code doesnt provide correct results for part 2, try changing the SCALE variable
+#to a bigger number
+#
+
+
+import time;
+import heapq;
+
+f = open("input.txt",'r');
+Testing = False;
+#Testing = True;
+if Testing:
+ f.close();
+ f = open("testinput.txt",'r');
+
+depth = eval(f.readline().replace("depth: ",""));
+target = tuple(eval(f.readline().replace("target: ","")));
+f.close();
+#determine the type of region and other parameters
+class region:
+ def __init__(self, pos, erosion1, erosion2):
+ self.Distance = 99999; #for when I tried to use djikstra
+ self.Equipped = None;
+ self.pos = pos;
+ if pos == (0,0) or pos == target: self.geo = 0;
+ elif pos[0] == 0: self.geo = pos[1]*48271;
+ elif pos[1] == 0: self.geo = pos[0]*16807;
+ else: self.geo = erosion1*erosion2;
+ self.erosion = (self.geo + depth)%20183;
+ self.risk = self.erosion%3;
+ if (self.risk == 0):
+ self.type = "R";
+ self.Gear = ["T","C"];
+ elif (self.risk == 1):
+ self.type = "W";
+ self.Gear = ["C","N"];
+ elif (self.risk == 2):
+ self.type = "N";
+ self.Gear = ["T","N"];
+ def GetAdjacent(self, grid): #hard to read, should remake this func, but not feeling like it
+ nextcords = [];
+ nx, ny = self.pos[0]-1, self.pos[1];
+ if nx in range(SCALE*target[0] +1) and ny in range(SCALE*target[1] +1): nextcords.append((nx,ny));
+ nx, ny = self.pos[0]+1, self.pos[1];
+ if nx in range(SCALE*target[0] +1) and ny in range(SCALE*target[1] +1): nextcords.append((nx,ny));
+ nx, ny = self.pos[0], self.pos[1]-1;
+ if nx in range(SCALE*target[0] +1) and ny in range(SCALE*target[1] +1): nextcords.append((nx,ny));
+ nx, ny = self.pos[0], self.pos[1]+1;
+ if nx in range(SCALE*target[0] +1) and ny in range(SCALE*target[1] +1): nextcords.append((nx,ny));
+
+ return nextcords;
+
+area = {};
+mouth = (0,0);
+part1 = 0;
+
+#for part 2:
+#scale up the area with SCALE - wrong idea, see comment at the start
+#SCALE is a variable in case it needs to be adjusted later
+SCALE = 6;
+#initiate area and calculate part 1
+for y in range(SCALE*target[1] +1):
+ for x in range(SCALE*target[0] +1):
+ ero1 = 0;
+ ero2 = 0;
+ r1 = area.get((x-1,y),None);
+ r2 = area.get((x,y-1),None);
+ if r1 != None: ero1 = r1.erosion;
+ if r2 != None: ero2 = r2.erosion;
+ area.update({(x,y):region((x,y),ero1,ero2)}); #brackets puke
+ if ( x in range(target[0] +1) and y in range(target[1] +1)):
+ part1 += area[(x,y)].risk;
+
+print("part 1 = ", part1);
+
+equip = "T";
+area[mouth].Distance = 0;
+area[mouth].Equipped = equip;
+gears = ["T","C","N"];
+queue = [(0, mouth, "T")];
+distances = {}; #obviously I meant time, but technically the same thing
+
+t1 = time.time();
+while queue:
+ d, p, e = heapq.heappop(queue);
+ if (p[0],p[1],e) in distances and distances[(p[0],p[1],e)] <= d: continue;
+ distances[(p[0],p[1],e)] = d; #update distances with the shorter path
+ #if the current region is the target and we're holding a torch - finish loop
+ if p == target and e == "T":
+ area[p].Equipped = e;
+ area[p].Distance = d;
+ break;
+ #push to queue the same region but with changed gear
+ for g in gears:
+ if g != e and g in area[p].Gear:
+ heapq.heappush(queue, (d+7,p,g));
+ #push to queue the adjacent regions
+ neighbours = area[p].GetAdjacent(area);
+ for neighbour in neighbours:
+ if not e in area[neighbour].Gear: continue;
+ heapq.heappush(queue,(d+1,neighbour,e));
+
+t2= time.time();
+
+part2 = area[target].Distance;
+print("part2 = ", part2);
+print("\tpart 2 runtime ", t2-t1, "s");
+
+
diff --git a/2018/aoc2018-d23-p1.py b/2018/aoc2018-d23-p1.py
new file mode 100644
index 0000000..a387337
--- /dev/null
+++ b/2018/aoc2018-d23-p1.py
@@ -0,0 +1,47 @@
+#advent of code 2018
+#day 23
+#part 1 and part 2
+
+
+f = open("input.txt",'r');
+Testing = False;
+#Testing = True;
+if Testing:
+ f.close();
+ f = open("testinput.txt",'r');
+
+def manhattan(pos1, pos2):
+ mx = abs(pos1[0] - pos2[0]);
+ my = abs(pos1[1] - pos2[1]);
+ mz = abs(pos1[2] - pos2[2]);
+ return mx + my + mz;
+
+class nanobot:
+ def __init__(self, pos, r):
+ self.pos = pos;
+ self.radius = r;
+ def __str__(self):
+ return f'pos={self.pos}, r={self.radius}';
+
+bots = [];
+for l in f:
+ l = l.replace("<","(");
+ l = l.replace(">",")");
+ l = l.replace("r=","");
+ l = l.replace("pos=","");
+ p, r = eval(l);
+ bots.append(nanobot(p,r));
+
+f.close();
+
+strongest_radius = 0;
+strongest_pos = None;
+for bot in bots:
+ if bot.radius > strongest_radius:
+ strongest_radius = bot.radius;
+ strongest_pos = bot.pos;
+
+part1 = 0;
+for bot in bots:
+ if (manhattan(strongest_pos, bot.pos) <= strongest_radius): part1 += 1;
+print("part1 = ", part1);
diff --git a/2018/aoc2018-d24.py b/2018/aoc2018-d24.py
new file mode 100644
index 0000000..e5b45f5
--- /dev/null
+++ b/2018/aoc2018-d24.py
@@ -0,0 +1,211 @@
+#advent of code 2018
+#day 24
+#part 1 and part 2
+
+#this was very fun to do
+#easier version of 2018 day 15 due to no pathfinding
+#part 1 no probs, but it took me way more time to finish part 2 than it should have
+#at first I didn't account for a possible stalemate, but it was late at night
+#(I read the input and didn't see any stalemate scenarios based on weaknesses
+#but now that I think about it I was reading the testinput from the example lol)
+#so I was trying to debug whats wrong with my current code
+#instead of adding the few lines to resolve the stalemates
+#the groups were simply refusing to fight
+#but to be fair, there definitely were some bugs in a previous version,
+#because I still wasn't getting the answer even when milk boost value was getting huge
+
+import copy; #library required for the deepcopy function
+
+f = open("input.txt",'r');
+Testing = False;
+#Testing = True;
+if Testing:
+ f.close();
+ f = open("testinput.txt",'r');
+
+def readinput(group):
+ if group.find("(") != -1:
+ immu_weak = group[group.find("("):group.find(")")+1];
+ group = group.replace(immu_weak, "");
+ immu_weak = immu_weak.replace("(","");
+ immu_weak = immu_weak.replace(")","");
+ immu_weak = immu_weak.replace(", ",",");
+ if immu_weak.find(";") != -1:
+ if (immu_weak[0] == "i"):
+ i_i = 0;
+ w_i = 1;
+ elif (immu_weak[0] == "w"):
+ i_i = 1;
+ w_i = 0;
+ immu_weak = immu_weak.replace("weak to ","");
+ immu_weak = immu_weak.replace("immune to ","");
+ immu_weak = immu_weak.replace(" ","");
+ iw = immu_weak.split(";");
+ immu = iw[i_i].split(",");
+ weak = iw[w_i].split(",");
+ elif immu_weak[0] == "w":
+ immu_weak = immu_weak.replace("weak to ","");
+ immu = [];
+ weak = immu_weak.split(",");
+ else:
+ weak = [];
+ immu_weak = immu_weak.replace("immune to ","");
+ immu = immu_weak.split(",");
+ else:
+ immu = [];
+ weak = [];
+ group = group.replace("units each with", " ");
+ group = group.replace("hit points", " ");
+ group = group.replace("with an attack that does", " ");
+ group = group.replace("damage at initiative", " ");
+ g = group.split();
+ gr = [int(x) for x in g[:-2]];
+ gr.append(g[-2]);
+ gr.append(int(g[-1]));
+ gr.append(immu);
+ gr.append(weak);
+ return gr;
+
+
+class army:
+ def __init__(self, side, ID, inputlist):
+ self.side = side;
+ self.id = ID;
+ self.units = inputlist[0];
+ self.hp = inputlist[1];
+ self.dmgVal = inputlist[2];
+ self.dmgType = inputlist[3];
+ self.initiative = inputlist[4];
+ self.immune = inputlist[5];
+ self.weak = inputlist[6];
+ self.IsDead = False;
+ self.IsTargeted = False;
+ self.Target = None;
+ def __str__(self):
+ return f'<{self.side}>: {self.units} units each with {self.hp} hit points (immune to {self.immune}; weak to {self.weak}) with an attack that does {self.dmgVal} {self.dmgType} damage at initiative {self.initiative}';
+
+ def EffectivePower(self,TargetWeak,TargetImmune):
+ DamageDealt = self.units*self.dmgVal;
+ if self.dmgType in TargetWeak:
+ DamageDealt = DamageDealt*2;
+ elif self.dmgType in TargetImmune:
+ DamageDealt = 0;
+ return DamageDealt;
+
+ def TakeDamage(self, DamageReceived):
+ UnitsKilled = DamageReceived//self.hp;
+ self.units -= UnitsKilled;
+ if self.units <= 0:
+ self.IsDead = True;
+ self.units = 0;
+
+############
+#parse input
+groups = {};
+side = "imm";
+currentid = 11;
+f.readline();
+for l in f:
+ if(l=="\n"): break;
+ groups.update({currentid:army(side,currentid,readinput(l))});
+ currentid += 1;
+f.readline();
+side = "inf";
+for l in f:
+ if(l=="\n"): break;
+ groups.update({currentid:army(side,currentid,readinput(l))});
+ currentid += 1;
+f.close();
+
+immsys = 0;
+infect = 0;
+for g in groups:
+ if groups[g].side == "imm": immsys +=1;
+ if groups[g].side == "inf": infect +=1;
+
+milk = 0;
+groupcopy = copy.deepcopy(groups); #deepcopy of groups to reset them
+
+winner = "inf"; #just to initiate the algo
+
+#keep doing battles until immune system wins
+while winner == "inf":
+ groups = copy.deepcopy(groupcopy);
+ #count the initial number of groups for immune system and infection
+ #apply milk booster to immune system
+ immsys = 0;
+ infect = 0;
+ for g in groups:
+ if groups[g].side == "imm": immsys +=1;
+ if groups[g].side == "inf": infect +=1;
+ if groups[g].side == "imm": groups[g].dmgVal += milk;
+ groups[g].IsDead = False;
+
+ #battle algorythm
+ while immsys > 0 and infect > 0:
+ kills = 0;
+ UnitsBefore = 0;
+ for g in groups:
+ UnitsBefore += groups[g].units;
+ #phase 1 target selection
+ TSqueue = [g for g in groups if groups[g].IsDead == False];
+ TSqueue = sorted(TSqueue, key=lambda g: (groups[g].EffectivePower([],[]),groups[g].initiative), reverse = True);
+ for a in TSqueue:
+ groups[a].Target = None;
+ PossibleTargets = [g for g in groups if groups[g].IsDead == False and groups[g].side != groups[a].side];
+ PossibleTargets = sorted(PossibleTargets, key=lambda g: (groups[a].EffectivePower(groups[g].weak,groups[g].immune),groups[g].EffectivePower([],[]),groups[g].initiative), reverse=True);
+ for t in PossibleTargets:
+ if groups[t].IsTargeted == False and groups[a].EffectivePower(groups[t].weak,groups[t].immune) > 0:
+ groups[a].Target = int(t);
+ groups[t].IsTargeted = True;
+ break;
+
+ #phase 2 attacking
+ ATqueue = TSqueue.copy();
+ ATqueue = sorted(ATqueue, key=lambda g: groups[g].initiative, reverse = True);
+ for a in ATqueue:
+ if groups[a].IsDead or groups[a].Target == None: continue;
+ t = groups[a].Target;
+ groups[t].TakeDamage( groups[a].EffectivePower(groups[t].weak,groups[t].immune) );
+
+ #reset the IsTargeted parameter for the next iteration of the battle
+ #count the groups of each side and calculate kills
+ immsys = 0;
+ infect = 0;
+ UnitsAfter = 0;
+ for g in groups:
+ groups[g].IsTargeted = False;
+ if groups[g].side == "imm" and groups[g].IsDead == False: immsys +=1;
+ if groups[g].side == "inf" and groups[g].IsDead == False: infect +=1;
+ UnitsAfter += groups[g].units;
+
+ #if there were no kills in the current iteration of battle - finish the battle
+ kills = UnitsBefore - UnitsAfter;
+ if kills == 0:
+ break;
+
+ #declare the winner (infection wins in case of a stalemate)
+ if kills == 0:
+ winner = "inf";
+ elif immsys == 0:
+ winner = "inf";
+ else:
+ winner = "imm";
+
+ #calculate part 1
+ if milk == 0:
+ part1 = 0;
+ for g in groups:
+ if groups[g].side == winner and groups[g].IsDead == False:
+ part1 += groups[g].units;
+
+ milk += 1; #raise milk booster for upcoming new battle
+
+#calculate part 2
+part2 = 0;
+for g in groups:
+ if groups[g].side == winner and groups[g].IsDead == False:
+ part2 += groups[g].units;
+
+print("part 1 = ", part1);
+print("part 2 = ", part2);
diff --git a/2018/aoc2018-d25.py b/2018/aoc2018-d25.py
new file mode 100644
index 0000000..37367df
--- /dev/null
+++ b/2018/aoc2018-d25.py
@@ -0,0 +1,69 @@
+#advent of code 2018
+#day 25
+#part 1
+
+#at first seemed really easy, then it got a bit more complex
+#but then again it seemed easy again
+#it just required a bit more thought to put into
+#could probably be bit more optimized, especially with Constellations variable
+#which could be a simple counter instead of a list, but w/e
+
+f = open("input.txt",'r');
+Testing = False;
+#Testing = True;
+if Testing:
+ f.close();
+ f = open("testinput.txt",'r');
+
+class spacepoint:
+ def __init__( self, coords ):
+ self.Coords = coords;
+ self.Adjacent = [];
+ self.Visited = False;
+
+ def manh_dist(self, OtherPoint): #calculate manhattan distance from self to OtherPoint
+ m = 0;
+ m += abs(self.Coords[0] - OtherPoint[0]);
+ m += abs(self.Coords[1] - OtherPoint[1]);
+ m += abs(self.Coords[2] - OtherPoint[2]);
+ m += abs(self.Coords[3] - OtherPoint[3]);
+ return m;
+ #add all points from the grid that are within range to the "Adjacent" property
+ def get_adjacent(self, grid):
+ for g in grid:
+ if self == grid[g]: continue;
+ if (self.manh_dist(grid[g].Coords) <= 3):
+ self.Adjacent.append(g);
+ #recursive function(method) to make a list of all points that are in the same constellation
+ #"Visited" property prevents an infinite loop
+ def Find_Subset(self, grid, subset):
+ self.Visited = True;
+ subset.extend(self.Adjacent);
+ for n in self.Adjacent:
+ if grid[n].Visited: continue;
+ subset = grid[n].Find_Subset(grid, subset);
+ return subset;
+
+ def __str__(self):
+ return f'{self.Coords}';
+
+#parse input into the program
+FixedPoints = {};
+for l in f:
+ l = "(" + l + ")";
+ p = eval(l);
+ FixedPoints.update({p:spacepoint(p)});
+f.close();
+
+#calculate neighbouring points for each point from input
+for p in FixedPoints:
+ FixedPoints[p].get_adjacent(FixedPoints);
+
+#find all constellations
+Constellations = [];
+for p in FixedPoints:
+ if FixedPoints[p].Visited: continue;
+ Constellations.append(len(set(FixedPoints[p].Find_Subset(FixedPoints, [p]))));
+
+part1 = len(Constellations);
+print("part 1 = ", part1);