-
Notifications
You must be signed in to change notification settings - Fork 184
/
Copy pathpart1.lisp
executable file
·168 lines (148 loc) · 6.68 KB
/
part1.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#!/usr/bin/sbcl --script
(defconstant +immune+ (intern "immune"))
(defconstant +infection+ (intern "infection"))
(defconstant +weak+ (intern "weak"))
(defun group-type-p (type)
"Is this a group type?"
(member type (list +immune+ +infection+)))
(deftype group-type ()
"Type for a unit group."
'(satisfies group-type-p))
(defstruct (group
(:constructor new-group
(type units hit-points attack-damage attack-type
initiative weaknesses immunities)))
"A group of units of either type."
(type 'immune :type group-type :read-only t)
(units 0 :type fixnum)
(hit-points 0 :type fixnum :read-only t)
(attack-damage 0 :type fixnum :read-only t)
(attack-type 'fire :type symbol :read-only t)
(initiative 0 :type fixnum :read-only t)
(weaknesses nil :type list :read-only t)
(immunities nil :type list :read-only t))
(defun group-effective-power (group)
"Effective power of a group."
(* (group-units group) (group-attack-damage group)))
(defun group-greaterp (group1 group2)
"Check if first group is greater than second group"
(cond ((> (group-effective-power group1) (group-effective-power group2)) t)
((< (group-effective-power group1) (group-effective-power group2)) nil)
(t (> (group-initiative group1) (group-initiative group2)))))
(defun damage (attacker target)
"Damage generate by an attacker to a target"
(cond
((null target) 0)
((eq (group-type attacker) (group-type target)) 0)
((member (group-attack-type attacker) (group-immunities target)) 0)
((member (group-attack-type attacker) (group-weaknesses target))
(* 2 (group-effective-power attacker)))
(t (group-effective-power attacker))))
(defun choose-target (attacker groups)
"Choose the target based on damage, effective power and initiative."
(let* ((type (group-type attacker))
(attack-type (group-attack-type attacker))
(groups (remove type groups :key #'group-type)))
(labels ((max-target (target1 target2)
(let ((damage1 (damage attacker target1))
(damage2 (damage attacker target2)))
(cond ((> damage1 damage2) target1)
((< damage1 damage2) target2)
((zerop damage1) nil)
(t (let ((power1 (group-effective-power target1))
(power2 (group-effective-power target2))
(initiative1 (group-initiative target1))
(initiative2 (group-initiative target2)))
(cond ((> power1 power2) target1)
((< power1 power2) target2)
((> initiative1 initiative2) target1)
(t target2))))))))
(reduce #'max-target groups :initial-value nil))))
(defun choose-targets (groups)
"Select targets for each group"
(labels ((iter (groups targets pairs)
(if (null groups)
pairs
(let* ((group (car groups))
(target (choose-target group targets)))
(iter (cdr groups)
(remove target targets :test #'equalp)
(cons (cons group target) pairs))))))
(iter (sort (copy-list groups) #'group-greaterp) groups nil)))
(defun attack-target (attacker target)
"Attack a target if there are units left. Modifies target."
(when (and target (not (zerop (group-units attacker))))
(let ((hit-units (floor (damage attacker target) (group-hit-points target))))
(decf (group-units target) (min hit-units (group-units target))))))
(defun attack-targets (group-pairs)
"Attack each target from each group."
(mapcar #'(lambda (pr)
(attack-target (car pr) (cdr pr))
(car pr))
(sort group-pairs #'> :key (lambda (pr) (group-initiative (car pr))))))
(defun finish-p (groups)
"Is the fight over?"
(null (cdr (remove-duplicates groups :key #'group-type))))
(defun fight (groups)
"Fight till only a team survives, return the amount of surviving units."
(if (finish-p groups)
(reduce #'+ groups :key #'group-units)
(let ((groups (attack-targets (choose-targets groups))))
(fight (remove 0 groups :key #'group-units)))))
(defun split (sequence &optional (item #\Space))
"Split a sequence by item."
(loop :for i = 0 :then (1+ j)
:as j = (or (position item sequence :start i))
:unless (eq i j)
:collect (subseq sequence i j)
:while j))
(defun trim (sequence &key (item #\Space) (test #'eql))
"Remove a item from beginning or end of the sequence"
(labels ((item-p (x) (funcall test x item)))
(subseq sequence
(position-if-not #'item-p sequence)
(1+ (position-if-not #'item-p sequence :from-end t)))))
(defun parse-option (string)
"Parse a group immunity / weakness."
(let ((items (split string)))
(cons (intern (car items))
(mapcar (lambda (x) (intern (trim x :item #\,)))
(cddr items)))))
(defun parse-group-options (string)
"Parse all group immunities / weaknesses."
(let* ((assoc (mapcar #'parse-option (split string #\;)))
(weaknesses (mapcan #'cdr (remove +immune+ assoc :key #'car)))
(immunities (mapcan #'cdr (remove +weak+ assoc :key #'car))))
(values weaknesses immunities)))
(defun parse-group-mandatory (string type weaknesses immunities)
"Parse the mandatory group data."
(let ((items (split string)))
(new-group type
(parse-integer (nth 0 items))
(parse-integer (nth 4 items))
(parse-integer (nth 12 items))
(intern (nth 13 items))
(parse-integer (nth 17 items))
weaknesses immunities)))
(defun parse-group (string type)
"Parse a group."
(let ((items (split string #\()))
(if (null (cdr items))
(parse-group-mandatory (car items) type nil nil)
(let ((items2 (split (cadr items) #\))))
(multiple-value-call #'parse-group-mandatory
(funcall #'concatenate 'string (car items) (cadr items2))
type
(parse-group-options (car items2)))))))
(defun parse-groups ()
"Parse all the groups."
(labels ((iter (type groups)
(let ((line (read-line *standard-input* nil 'eof)))
(if (or (eq line 'eof) (zerop (length line)))
groups
(iter type (cons (parse-group line type) groups))))))
(let* ((type (intern (string-downcase (car (split (read-line))))))
(groups (iter type nil))
(type (intern (string-downcase (trim (read-line) :item #\:)))))
(iter type groups))))
(format t "~a~%" (fight (parse-groups)))