-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathintcode.rb
226 lines (205 loc) · 5.53 KB
/
intcode.rb
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# Intcode parser
class Intcode
# Binary opcodes (i.e., 'a+b' or 'a*b' where you have two args and a return)
# Since these are pretty simple (so far), we're just sticking procs directly in the hash.
OPCODES_BINARYOP = {
1 => ->(a,b) { a + b },
2 => ->(a,b) { a * b },
7 => ->(a,b) { a < b ? 1 : 0 },
8 => ->(a,b) { a == b ? 1 : 0 }
}
OPCODE_ADD = 1
OPCODE_MULT = 2
OPCODE_INPUT = 3
OPCODE_OUTPUT = 4
OPCODE_JUMPIFTRUE = 5
OPCODE_JUMPIFFALSE = 6
OPCODE_LT = 7
OPCODE_EQ = 8
OPCODE_REBASE = 9
OPCODE_HALT = 99
MODE_POSITION = 0
MODE_IMMEDIATE = 1
MODE_RELATIVE = 2
OPCODE_NAMES = {
1 => 'add ',
2 => 'mult',
3 => 'inp ',
4 => 'out ',
5 => 'jit ',
6 => 'jif ',
7 => 'lt ',
8 => 'eq ',
9 => 'reb ',
99 => 'halt',
}
STATUS_RUN = 0
STATUS_INPUT = 3
STATUS_HALT = 99
# Create the intcode processor with the given memory array
def initialize(memory = [], debug_mode = false)
@memory = memory.clone
@ip = 0
@modes = []
@opcode = 0
@input = []
@output = []
@base = 0
@debug_mode = debug_mode
@status = STATUS_RUN
end
def self.file2mem(file)
file.read.split(',').map { |value| value.to_i }
end
# Debugging output helper
def dbg
if @debug_mode then
opname = (OPCODE_NAMES.key? @opcode) ? OPCODE_NAMES[@opcode] : "HCF(#{@opcode})"
args = case @opcode
when 1, 2, 7, 8
"#{dbga(0)}, #{dbga(1)} => #{dbga(2,true)}"
when 3
"#{@input[0]} => #{dbga(0)}"
when 4
"#{dbga(0)}"
when 9
"+#{dbga(0)}"
when 5, 6
"#{dbga(0)} to #{dbga(1)}"
else
""
end
puts "@#{@ip-1}: #{opname} #{args} "
end
end
def dbga(pos, sto=false)
mode = @modes[-(1+pos)]
if sto then
return "@#{peek(pos)}"
elsif mode == MODE_POSITION then
return "p:#{peek(pos)}(#{peek(pos, MODE_POSITION)})"
elsif mode == MODE_RELATIVE then
return "r:#{peek(pos)}+#{@base}(#{peek(pos, MODE_RELATIVE)})"
else
return "i:#{peek(pos)}"
end
end
def decode_step
value = peek
# Pad out the code to a five digit string
padded = ('0' * (5-value.to_s.length)) + value.to_s
# Set up our argument state
@modes = padded[0..2].split('').map{|i| i.to_i}
# Advance the instruction pointer
@ip += 1
@opcode = padded[3..4].to_i
end
def peek(pos=0, mode = MODE_IMMEDIATE)
value = case mode
when MODE_IMMEDIATE
@memory[@ip+pos]
when MODE_POSITION
@memory[peek(pos)]
when MODE_RELATIVE
@memory[peek(pos) + @base]
end
(value == nil) ? 0 : value
end
def argument
value = peek(0, @modes.pop)
@ip += 1
value
end
def sto_argument
mode = @modes.pop
value = mode==MODE_RELATIVE ? peek + @base : peek
@ip += 1
value
end
def step
opcode = decode_step
output = nil
dbg()
# For binary opcodes
if OPCODES_BINARYOP.key? opcode then
# Get the argument values
a = argument
b = argument
sto = sto_argument
# Set the output address value to the result of the opcode call
@memory[sto] = OPCODES_BINARYOP[opcode].call(a,b)
elsif opcode==OPCODE_INPUT then
sto = sto_argument
if @input.length > 0 then
@memory[sto] = @input.shift
else
puts 'Supply input:'
@memory[sto] = STDIN.gets.chomp.to_i
end
@status = STATUS_RUN
elsif opcode==OPCODE_OUTPUT then
a = argument
output = a
@output << a
elsif opcode==OPCODE_JUMPIFTRUE then
test = argument
new_location = argument
@ip = new_location if test != 0
elsif opcode==OPCODE_JUMPIFFALSE then
test = argument
new_location = argument
@ip = new_location if test == 0
elsif opcode==OPCODE_REBASE
@base += argument
elsif opcode==OPCODE_HALT
@status = STATUS_HALT
# Halt and catch fire
else
fail "Unknown opcode #{opcode} at instruction pointer #{@ip}"
@status = STATUS_HALT
end
output
end
def continue(input = [], output_handler = nil)
return if @status == STATUS_HALT
@status = STATUS_RUN
@input += input if input.class == Array
@input << input if input.class == Integer
output = nil
while @status == STATUS_RUN do
if (peek != OPCODE_INPUT) || @input.length >0 then
output = step
output_handler.call(output) if output != nil && output_handler != nil
else
@status = STATUS_INPUT
end
end
output
end
# Run this computer on its input memory starting at location 0
def run(input=[])
@output = []
@input = input.clone
# Start the instruction pointer at 0
@ip = 0
@status = STATUS_RUN
# Keep going until we crash or hit a halt opcode
while peek != OPCODE_HALT do
output = step
puts "OUT: #{output}" unless output==nil
end
@status = STATUS_HALT
@output
end
# Memory accessor
def memory
@memory
end
# Output lives in memory location zero if not explicitly set
def output
@output.clone
end
def status
@status
end
end