Browse Source

add some stuff

Getty Ritter 4 years ago
parent
commit
36f0b46b21
3 changed files with 747 additions and 0 deletions
  1. 133 0
      08/main.pony
  2. 490 0
      09/main.pony
  3. 124 0
      10/main.pony

+ 133 - 0
08/main.pony

@@ -0,0 +1,133 @@
+use "collections"
+use "files"
+use "itertools"
+
+// A basic two-dimensional point class
+class val Point is Hashable
+  let x: USize
+  let y: USize
+
+  new val create(_x: USize, _y: USize) =>
+    x = _x
+    y = _y
+
+  // because we want to use this as a key in a hashmap
+  fun hash(): USize =>
+    // hey this is probably bad, huh?
+    x.hash() + y.hash()
+
+  fun eq(other: Point): Bool =>
+    (x == other.x) and (y == other.y)
+
+  fun ne(other: Point): Bool => not eq(other)
+
+class Layer
+  let pixels: Map[Point, U8]
+  let w: USize
+  let h: USize
+
+  new create(pixels': Map[Point, U8], w': USize, h': USize) =>
+    pixels = pixels'
+    w = w'
+    h = h'
+
+  fun ref string(): String =>
+    var buf = String()
+    for y in Range(0, h) do
+      for x in Range(0, w) do
+        try
+          match pixels(Point(x, y))?
+          | '0' => buf.push(' ')
+          | '1' => buf.push('X')
+          | '2' => buf.push('?')
+          end
+        else
+          buf.push('.')
+        end
+      end
+      buf.push('\n')
+    end
+    buf.clone()
+
+  fun ref count(n: U8): USize =>
+    Iter[U8](pixels.values()).filter({(x) => x == n}).count()
+
+class Image
+  var layers: Array[Layer]
+  new create(layers': Array[Layer]) =>
+    layers = layers'
+
+  fun ref string(): String =>
+    var buf = ""
+    buf = buf.add("image [\n")
+    for (i, l) in Iter[Layer](layers.values()).enum[USize]() do
+      buf = buf.add("  layer ")
+      buf = buf.add(i.string())
+      buf = buf.add(": ")
+      buf = buf.add(l.string())
+      buf = buf.add("\n")
+    end
+    buf = buf.add("]")
+    buf.clone()
+
+  fun ref composite(): Layer =>
+    var result: Map[Point, U8] = Map[Point, U8]()
+    for layer in layers.values() do
+      for (p, v) in layer.pixels.pairs() do
+        if result.get_or_else(p, '2') == '2' then
+          if v != '2' then result(p) = v end
+        end
+      end
+    end
+    Layer(result, 25, 6)
+
+actor Main
+  fun mk_image(env: Env, width: USize, height: USize, file: File): Image =>
+    var layers = Array[Layer]()
+    var pixels = Map[Point, U8]()
+    try
+      while true do
+        for y in Range(0, height) do
+          for x in Range(0, width) do
+            pixels(Point(x, y)) = file.read(1)(0)?
+          end
+        end
+        layers.push(Layer(pixels = Map[Point, U8](), width, height))
+      end
+    end
+    Image(layers)
+
+  new create(env: Env) =>
+    let caps = recover val FileCaps.>set(FileRead).>set(FileStat) end
+    let target_file = try env.args(1)? else "input.txt" end
+    try
+      with
+        file = OpenFile(FilePath(env.root as AmbientAuth, target_file, caps)?) as File
+      do
+        let w: USize = 25
+        let h: USize = 6
+        let image = mk_image(env, w, h, file)
+        env.out.print("image is " + image.string())
+        var min: (USize | None) = None
+        var min_idx: (USize | None) = None
+        for (idx, layer) in Iter[Layer](image.layers.values()).enum() do
+          let zeroes = layer.count('0')
+          let is_min = try min as USize > zeroes else true end
+          env.out.print("layer " + idx.string() + ": " + zeroes.string())
+          if is_min then
+            min = zeroes
+            min_idx = idx
+          end
+        end
+        env.out.print("min layer " + min_idx.string() + " had " + min.string() + " zeroes")
+        try
+          let tgt = image.layers(min_idx as USize)?
+          env.out.print("  answer: " + (tgt.count('1') * tgt.count('2')).string())
+        end
+        let composite = image.composite()
+        env.out.print("composite:\n" + composite.string())
+      end
+    end
+
+
+    env.out.print(".")

+ 490 - 0
09/main.pony

@@ -0,0 +1,490 @@
+use "collections"
+use "files"
+use "format"
+use "itertools"
+
+interface Value
+  """
+  Values referred to by opcodes in instructions should all be things
+  that implement this interface
+  """
+  fun value(): I64
+
+interface Target
+  fun value(): I64
+  fun addr(): USize
+
+class Position
+  """
+  A positional operand (i.e. one whose value is fetched from a memory cell
+  """
+  let _addr: I64
+  let _value: I64
+
+  new create(__addr: I64, __value: I64) =>
+    _addr = __addr
+    _value = __value
+
+  fun addr(): USize => _addr.usize()
+  fun value(): I64 => _value
+
+class Immediate
+  """
+  An immediate operand (i.e. one whose value is a verbatim argument)
+  """
+  let _value: I64
+
+  new create(__value: I64) =>
+    _value = __value
+
+  fun value(): I64 => _value
+
+class Relative
+  """
+  A realtive operand (i.e. one whose value is fetched from memory relative to a register)
+  """
+  let _addr: USize
+  let _value: I64
+
+  new create(__addr: USize, __value: I64) =>
+    _addr = __addr
+    _value = __value
+
+  fun value(): I64 => _value
+  fun addr(): USize => _addr
+
+
+class Operands
+  """
+  The operands to a typical binary operator: two values and a target
+  that will always be in positional (i.e. non-immediate) mode
+  """
+  let _lhs: Value
+  let _rhs: Value
+  let _tgt: Target
+
+  new create(__lhs: Value, __rhs: Value, __tgt: Target) =>
+    _lhs = __lhs
+    _rhs = __rhs
+    _tgt = __tgt
+
+  fun val1(): I64 => _lhs.value()
+  fun val2(): I64 => _rhs.value()
+  fun tgt(): USize => _tgt.addr()
+
+trait Error
+  """
+  The errors we can raise will all implement this so we can give
+  nice informative error messages
+  """
+  fun message(): String
+
+class val OutOfBounds is Error
+  let _loc: USize
+  new val create(loc: USize) => _loc = loc
+  fun message(): String =>
+    "Memory access out of bounds: " + Format.int[USize](_loc)
+
+class val UnknownOperand is Error
+  let _op: I64
+  new val create(op: I64) => _op = op
+  fun message(): String =>
+    "Unknown operation: " + Format.int[I64](_op)
+
+class val NotEnoughInput is Error
+  let _pc: USize
+  new val create(pc: USize) => _pc = pc
+  fun message(): String =>
+    "Not enough input when running instruction at " + Format.int[USize](_pc)
+
+
+// I should wrap this but I haven't, so, uh, yeah
+type Opcode is I64
+// a Mode is true if immediate, false if positional
+type Modes is (Mode, Mode, Mode)
+
+primitive ModeImm
+  fun sigil(): String => ""
+primitive ModePos
+  fun sigil(): String => "*"
+primitive ModeRel
+  fun sigil(): String => "~"
+
+type Mode is (ModeImm | ModePos | ModeRel)
+
+
+class val OutputRequest
+  let _o: I64
+  new val create(_o': I64) => _o = _o'
+  fun output(): I64 => _o
+  fun string(): String =>
+    "OutputRequest(" + _o.string() + ")"
+primitive InputRequest
+  fun string(): String => "InputRequest"
+primitive Halted
+  fun string(): String => "Halted"
+
+type ProgramResult is (OutputRequest | InputRequest | Halted)
+
+
+class Program
+  """
+  All the logic we need for running programs
+  """
+
+  // an array of memory cells
+  var storage: Array[I64]
+  // the current instruction we're running
+  var pc: USize = 0
+  //
+  var input: Array[I64] = Array[I64]()
+  //
+  var rel_base: I64 = 0
+
+  // a list of errors that may or may not have happened. (right now,
+  // we'll only ever have one, but it's worth planning ahead, eh?)
+  var errors: Array[Error val] = Array[Error val]()
+
+  // a queue of debug messages
+  var debug: Array[String] = Array[String]()
+
+  new create(s: Array[I64]) =>
+    """
+    Create a Program from a verbatim storage and input array
+    """
+    storage = s
+
+  new from_file(file: File) =>
+    """
+    Create a Program by walking input from a file. This just skips any
+    errors if they happen (e.g. if there are non-numbers), which
+    might not be what we want!
+    """
+    storage = Array[I64](0)
+    for line in file.lines() do
+      for chunk in line.split(",").values() do
+        try
+          storage.push(chunk.i64()?)
+        end
+      end
+    end
+
+  fun ref send_input(i: I64) =>
+    input.push(i)
+
+  fun print(env: Env) =>
+    """
+    Print out the current state of memory
+    """
+    env.out.write("[")
+    for x in storage.values() do
+      env.out.write(Format.int[I64](x))
+      env.out.write(" ")
+    end
+    env.out.print("]")
+
+  fun ref dbg(msg: String) =>
+    """
+    Log a debug message
+    """
+    debug.push(msg)
+
+
+  fun ref read_storage(addr: USize): I64 ? =>
+    """
+    Attempt to read a word from storage, logging an error message
+    if we fail to do so
+    """
+    while addr > (storage.size() - 1) do
+        storage.push(0)
+    end
+    try
+      storage(addr)?
+    else
+      errors.push(OutOfBounds(addr))
+      error
+    end
+
+  fun ref write_storage(addr: USize, value: I64) ? =>
+    """
+    Attempt to write a word to storage, logging an error message
+    if we fail to do so
+    """
+    while addr > (storage.size() - 1) do
+        storage.push(0)
+    end
+    try
+      storage(addr)? = value
+    else
+      errors.push(OutOfBounds(addr))
+      error
+    end
+
+
+  fun ref get_immediate(addr_loc: USize): Immediate ? =>
+    """
+    Read a single immediate value at the provided loc
+    """
+    let value = read_storage(addr_loc)?
+    Immediate(value)
+
+  fun ref get_position(addr_loc: USize): Position ? =>
+    """
+    Read a positional value whose address is stored at the provided loc
+    """
+    let addr = read_storage(addr_loc)?
+    let value = read_storage(addr.usize())?
+    Position(addr, value)
+
+  fun ref get_relative(addr_loc: USize): Relative ? =>
+    """
+    Read a positional value whose address is stored at the provided loc
+    """
+    let offset = read_storage(addr_loc)?
+    let addr = offset + rel_base
+    let value = read_storage(addr.usize())?
+    dbg("      (rel: base " + rel_base.string() + " delta " + offset.string() + ")")
+    Relative(addr.usize(), value)
+
+  fun ref get_value(addr_loc: USize, mode: Mode): Value ? =>
+    """
+    Read a value at the provided loc based on the mode: if true, then
+    treat it as an immediate, otherwise treat it as a positional
+    """
+    match mode
+    | ModeImm => get_immediate(addr_loc)?
+    | ModePos => get_position(addr_loc)?
+    | ModeRel => get_relative(addr_loc)?
+    end
+
+  fun ref get_target(addr_loc: USize, mode: Mode): Target ? =>
+    """
+    Read a value at the provided loc based on the mode: if true, then
+    treat it as an immediate, otherwise treat it as a positional
+    """
+    match mode
+    | ModeImm => error
+    | ModePos => get_position(addr_loc)?
+    | ModeRel => get_relative(addr_loc)?
+    end
+
+
+  fun ref get_op_data(modes: Modes): Operands ? =>
+    """
+    Get the data that a binary op needs based on the provided modes
+    """
+    (let lm, let rm, let tm) = modes
+    Operands(
+      get_value(pc+1, lm)?,
+      get_value(pc+2, rm)?,
+      get_target(pc+3, tm)?
+    )
+
+  fun ref mk_mode(n: I64): Mode =>
+    match n
+    | 0 => ModePos
+    | 1 => ModeImm
+    | 2 => ModeRel
+    else
+      ModeImm
+    end
+
+  fun ref parse_op(n: I64): (Opcode, Modes) =>
+    """
+    Parse out an opcode and set of modes. This hard-codes modes
+    for three operands; it might be worth it in the future to generalize
+    to any N operands
+    """
+    let opcode = n % 100
+    let mode1 =   (n / 100) % 10
+    let mode2 =  (n / 1000) % 10
+    let mode3 = (n / 10000) % 10
+    (opcode, (mk_mode(mode1), mk_mode(mode2), mk_mode(mode3)))
+
+
+  fun ref do_binop(modes: Modes, op: {(I64, I64): I64}, op_name: String) ? =>
+    """
+    Get the values of the first two operands by the provided modes, run
+    the function `op` on them, and store the result of that at the
+    provided location
+    """
+    let data = get_op_data(modes)?
+    dbg("    writing " + data.val1().string() + op_name + data.val2().string()
+      + " = " + op(data.val1(), data.val2()).string() + " to *" + data.tgt().string())
+    write_storage(data.tgt(), op(data.val1(), data.val2()))?
+    pc = pc + 4
+
+  fun ref do_adjust_base(modes: Modes) ? =>
+    (let m, _, _) = modes
+    let delta = get_value(pc+1, m)?.value()
+    rel_base = rel_base + delta.i64()
+    pc = pc + 2
+
+  fun ref do_jump(modes: Modes, jump_cond: {(I64): Bool}) ? =>
+    """
+    Get the value indicated by the modes at [pc+1], and the function
+    `jump_cond` returns true on that value, then jump to the location
+    indicated by [pc+2], otherwise move ahead as usual
+    """
+    (let cond, let tgt, _) = modes
+    if jump_cond(get_value(pc+1, cond)?.value()) then
+      pc = get_value(pc+2, tgt)?.value().usize()
+    else
+      pc = pc + 3
+    end
+
+  fun ref do_input(modes: Modes) ? =>
+    """
+    Read a value from the input and put it into storage. This logs an
+    error if there are not enough values in the input queue.
+    """
+    let i = try
+      input.shift()?
+    else
+      errors.push(NotEnoughInput(pc))
+      error
+    end
+    (let m, _, _) = modes
+    let addr = match get_value(pc+1, m)?
+      | let r: Relative => r.addr().usize()
+      | let p: Position => p.addr().usize()
+    else
+      error
+    end
+    dbg("    writing " + i.string() + " to *" + addr.string())
+    write_storage(addr, i)?
+    pc = pc + 2
+
+  fun ref do_output(modes: Modes): OutputRequest ? =>
+    """
+    Write the value indicated according to the mode to the output queue.
+    """
+    (let m, _, _) = modes
+    let r = get_value(pc+1, m)?.value()
+    pc = pc + 2
+    OutputRequest(r)
+
+  fun ref at_pc(): USize => pc
+
+  fun ref fmt_op(opcode: I64, modes: Modes): String =>
+    (let a, let b, let c) = modes
+    let op1 = try read_storage(pc+1)? else -99 end
+    let op2 = try read_storage(pc+2)? else -99 end
+    let op3 = try read_storage(pc+3)? else -99 end
+    let arg1 = a.sigil() + op1.string()
+    let arg2 = b.sigil() + op2.string()
+    let arg3 = c.sigil() + op3.string()
+    match opcode
+      | 1 => "add " + arg1 + " " + arg2 + " -> " + arg3
+      | 2 => "mul " + arg1 + " " + arg2 + " -> " + arg3
+      | 3 => "input " + arg1
+      | 4 => "output " + arg1
+      | 5 => "jnz " + arg1 + " to " + arg2
+      | 6 => "jez " + arg1 + " to " + arg2
+      | 7 => "lt " + arg1 + " " + arg2 + " -> " + arg3
+      | 8 => "eq " + arg1 + " " + arg2 + " -> " + arg3
+      | 9 => "adj_base " + arg1
+      | 99 => "halt"
+    else
+      "unknown op(" + opcode.string() + ")"
+    end
+
+  fun ref run_loop(env: Env): ProgramResult ? =>
+    """
+    This continues running the VM until it encounters a halt instruction
+    or it encounters some other error
+    """
+
+    while true do
+      (let opcode, let modes) = parse_op(read_storage(pc)?)
+      env.out.print("  " + pc.string() + "/" + rel_base.string() + " -> " + fmt_op(opcode, modes))
+      match opcode
+        | 1 => do_binop(modes, {(x, y) => x + y}, "+")?
+        | 2 => do_binop(modes, {(x, y) => x * y}, "*")?
+        | 3 => try do_input(modes)? else return InputRequest end
+        | 4 => return do_output(modes)?
+        | 5 => do_jump(modes, {(x) => x != 0})?
+        | 6 => do_jump(modes, {(x) => x == 0})?
+        | 7 => do_binop(modes, {(x, y) => if x < y then 1 else 0 end}, "<")?
+        | 8 => do_binop(modes, {(x, y) => if x == y then 1 else 0 end}, "==")?
+        | 9 => do_adjust_base(modes)?
+        | 99 => return Halted
+        | let x: I64 =>
+          errors.push(UnknownOperand(x))
+          error
+      end
+    end
+    Halted
+
+  fun ref run_with(env: Env, i: Array[I64]): Array[I64] =>
+    """
+    This runs the VM and prints output: if an error occurs, it prints
+    the error message(s) and any debug messages that happened during
+    program execution, and then it prints everything printed via an
+    output instruction.
+    """
+    input.append(i)
+
+    var output = Array[I64]()
+    var is_done = false
+    try
+      repeat
+        env.out.print("...")
+        match run_loop(env)?
+          | Halted => is_done = true
+          | InputRequest => env.err.print("Not enough input for program"); is_done = true
+          | let o: OutputRequest => output.push(o.output())
+        end
+        for n in debug.values() do
+          env.out.print("[debug] " + n)
+        end
+      until is_done end
+    else
+      for err in errors.values() do
+        env.out.print(err.message())
+      end
+      for n in debug.values() do
+        env.out.print("[debug] " + n)
+      end
+    end
+    for n in output.values() do
+      env.out.print("> " + Format.int[I64](n))
+    end
+    output
+
+  fun ref run_step(env: Env): ProgramResult =>
+    try
+      env.out.print("restarting from " + pc.string())
+      let r = run_loop(env)?
+      for msg in debug.values() do
+        env.out.print(msg)
+      end
+      debug.clear()
+      r
+    else
+      for err in errors.values() do
+        env.out.print(err.message())
+      end
+      Halted
+    end
+
+  fun ref clone(): Program =>
+    Program(storage.clone())
+
+actor Main
+  new create(env: Env) =>
+    let caps = recover val FileCaps.>set(FileRead).>set(FileStat) end
+
+    let target_file = try env.args(1)? else "input.txt" end
+    try
+      with
+        file = OpenFile(FilePath(env.root as AmbientAuth, target_file, caps)?) as File
+      do
+        let program = Program.from_file(file)
+        program.run_with(env, [2])
+      end
+    else
+      // if something failed, then print an error message of some kind and exit
+      env.err.print("Couldn't read expected file `" + target_file + "`")
+      env.exitcode(99)
+    end

+ 124 - 0
10/main.pony

@@ -0,0 +1,124 @@
+use "collections"
+use "files"
+use "itertools"
+
+primitive Math
+  fun gcd(x: ISize, y: ISize): ISize =>
+    var a = x
+    var b = y
+    var d: USize = 0
+    while ((a % 2) == 0) and ((b % 2) == 0) do
+      a = a / 2
+      b = b / 2
+      d = d + 1
+    end
+    while a != b do
+      if (a % 2) == 0 then a = a / 2
+      elseif (b % 2) == 0 then b = b / 2
+      elseif a > b then a = (a - b) / 2
+      else b = (b - a) / 2
+      end
+    end
+    if d > 0 then
+      a * (2 << (d-1)).isize()
+    else
+      a
+    end
+
+// A basic two-dimensional point class
+class val Point is Hashable
+  let x: ISize
+  let y: ISize
+
+  new val create(_x: ISize, _y: ISize) =>
+    x = _x
+    y = _y
+
+  // because we want to use this as a key in a hashmap
+  fun hash(): USize =>
+    // hey this is probably bad, huh?
+    (x.hash() + y.hash()).usize()
+
+  fun eq(other: Point): Bool =>
+    (x == other.x) and (y == other.y)
+
+  fun ne(other: Point): Bool => not eq(other)
+
+  fun sub(other: Point): Point =>
+    Point(x - other.x, y - other.y)
+
+  fun simplify(): Point =>
+    let d = Math.gcd(x, y)
+    Point(x/d, y/d)
+
+actor Asteroid
+  let _loc: Point
+  let _field: AsteroidField tag
+  let _io: IO tag
+  let _visible: Map[Point, Point] = Map[Point, Point]()
+
+  new create(p: Point, field: AsteroidField tag, io: IO tag) =>
+    _loc = p
+    _field = field
+    _io = io
+
+  be print() =>
+    _io.print("Asteroid(" + _loc.x.string() + ", " + _loc.y.string() + ")")
+
+  be find_visible(loc: Point, other: Asteroid) =>
+    None
+
+actor AsteroidField
+  var _io: IO
+  var _locs: Set[Point] = Set[Point]()
+
+  new create(io: IO) =>
+    _io = io
+
+  be register(p: Point) =>
+    _locs.set(p)
+
+  be print() =>
+    _io.print("whoo")
+
+actor IO
+  let _env: Env
+  new create(env: Env) =>
+    _env = env
+
+  be print(str: String) =>
+    _env.out.print(str)
+
+actor Main
+  new create(env: Env) =>
+    let io = IO(env)
+    let field = AsteroidField(io)
+    build_field(env, io, field)
+    field.print()
+
+  fun build_field(env: Env, io: IO, field: AsteroidField): (Array[Asteroid] | None) =>
+    let caps = recover val FileCaps.>set(FileRead).>set(FileStat) end
+    let target_file = try env.args(1)? else "input.txt" end
+    try
+      var asteroids: Array[Asteroid] = Array[Asteroid]()
+      with
+        file = OpenFile(FilePath(env.root as AmbientAuth, target_file, caps)?) as File
+      do
+        for (y, line) in Iter[String](file.lines()).enum() do
+          for (x, char) in Iter[U8](line.values()).enum() do
+            if char == '#' then
+              let p = Point(x.isize(), y.isize())
+              field.register(p)
+              asteroids.push(Asteroid(p, field, io))
+            end
+          end
+        end
+      end
+      for a in asteroids.values() do
+        a.print()
+      end
+      asteroids
+    else
+      env.err.print("Couldn't read file `" + target_file + "`")
+      env.exitcode(99)
+    end