~tim/boardslam-crystal

636abcfa9314f91065bba1ed78b2649a0e4045a6 — Tim Morgan 2 years ago master
Initial commit
7 files changed, 204 insertions(+), 0 deletions(-)

A .gitignore
A .watchr
A Makefile
A README.md
A bin/boardslam.cr
A lib/boardslam.cr
A spec/boardslam_spec.cr
A  => .gitignore +1 -0
@@ 1,1 @@
/bin/boardslam

A  => .watchr +12 -0
@@ 1,12 @@
require 'open3'

def spec
  puts
  puts '========================================='
  puts
  _, stdout, wait_thr = Open3.popen2('crystal spec')
  print stdout.getc until stdout.eof?
  wait_thr.value.success?
end

watch('\.cr') { spec }

A  => Makefile +8 -0
@@ 1,8 @@
build:
	crystal build bin/boardslam.cr -o bin/boardslam

build_release:
	crystal build --static --release bin/boardslam.cr -o bin/boardslam

test:
	crystal spec

A  => README.md +57 -0
@@ 1,57 @@
# Boardslam in Crystal

This is a port of my [boardslam.rb script](https://gist.github.com/seven1m/6a36782b93f10fa15a2fc381fd91bfb1) to Crystal.

Explanation here: [seven1m.sdf.org/experiments/boardslam.cgi](http://seven1m.sdf.org/experiments/boardslam.cgi)

## Build

1. [Install Crystal](https://crystal-lang.org/docs/installation/)

2. Run `make build_release`

## Usage

```
bin/boardslam 1 2 3

1   + 2   / 3   = 1
1   - 2   + 3   = 2
1   / 2   + 3   = 3
1   + 2   + 3^0 = 4
1   * 2   + 3   = 5
1   + 2   + 3   = 6
1   * 2^2 + 3   = 7
1   - 2   + 3^2 = 8
1   + 2   * 3   = 9
1   * 2^0 + 3^2 = 10
1   * 2   + 3^2 = 11
1   + 2   + 3^2 = 12
1   * 2^2 + 3^2 = 13
1   + 2^2 + 3^2 = 14
1   + 2^2 * 3   = 15
1   + 3   * 2^2 = 16
1   * 2^3 + 3^2 = 17
1   * 2   * 3^2 = 18
1   * 3^3 - 2^3 = 19
1   - 2^3 + 3^3 = 20
2^3 - 1   * 3   = 21
3^3 - 1   - 2^2 = 22
1   * 3^3 - 2^2 = 23
1   - 2^2 + 3^3 = 24
1   * 3^3 - 2   = 25
1   - 2   + 3^3 = 26
1   + 2   * 3^2 = 27
1   * 2^0 + 3^3 = 28
1   * 2   + 3^3 = 29
1   + 2   + 3^3 = 30
1   * 2^2 + 3^3 = 31
1   + 2^2 + 3^3 = 32
2^3 - 1   + 3^3 = 34
1   * 2^3 + 3^3 = 35
1   * 2^2 * 3^2 = 36

missing answers: 33
```

License: MIT

A  => bin/boardslam.cr +13 -0
@@ 1,13 @@
require "../lib/boardslam"

args = ARGV.map { |a| a.to_i8 }
board = BoardSlam.new(args[0], args[1], args[2])
board.results.to_a.sort.each do |result, equation|
  puts equation.ljust(15) + " = " + result.to_s
end
puts
if board.missing.any?
  puts "missing answers: #{board.missing.join(", ")}"
else
  puts "all answers possible!"
end

A  => lib/boardslam.cr +85 -0
@@ 1,85 @@
class BoardSlam
  BOARD = 1..36
  OPERATIONS = %w(+ - * /)

  def initialize(x : Int8, y : Int8, z : Int8)
    @numbers = [x, y, z]
    @answers = {} of Int8 => String
  end

  getter :numbers, :answers

  def variants(num)
    [num, "#{num}^0", "#{num}^2", "#{num}^3"]
  end

  def expand(num)
    return num if num.is_a?(Int8)
    num, exp = num.split('^').map { |n| n.to_i }
    num ** exp
  end

  def results
    each_order do |x_base, y_base, z_base|
      each_variant(x_base, y_base, z_base) do |(x_pretty, x), (y_pretty, y), (z_pretty, z)|
        each_operation_pair do |op1, op2|
          result1 = op(x.to_i8, op1, y.to_i8)
          #next if op1 == "/" && x.to_i8 % y.to_i8 != 0
          result2 = op(result1, op2, z.to_i8)
          #next if op2 == "/" && result1.to_i8 % z.to_i8 != 0
          if BOARD.includes?(result2) && !answers.has_key?(result2)
            answers[result2] = "#{x_pretty.to_s.ljust(3)} #{op1} #{y_pretty.to_s.ljust(3)} #{op2} #{z_pretty.to_s.ljust(3)}"
          end
        end
      end
    end
    answers
  end

  def op(n1 : Int8, op : String, n2 : Int8) : Int8
    case op
    when "+"
      n1 + n2
    when "-"
      n1 - n2
    when "*"
      n1 * n2
    when "/"
      n1 / n2
    else
      0i8
    end
  end

  def each_order
    @numbers.permutations.each do |(x, y, z)|
      yield x, y, z
    end
  end

  def each_variant(x_base, y_base, z_base)
    variants(x_base).each do |x|
      x_expanded = expand(x)
      variants(y_base).each do |y|
        y_expanded = expand(y)
        variants(z_base).each do |z|
          z_expanded = expand(z)
          yield([x, x_expanded], [y, y_expanded], [z, z_expanded])
        end
      end
    end
  end

  def each_operation_pair
    OPERATIONS.each do |op1|
      OPERATIONS.each do |op2|
        yield(op1, op2)
        yield(op2, op1)
      end
    end
  end

  def missing
    BOARD.to_a - results.keys
  end
end

A  => spec/boardslam_spec.cr +28 -0
@@ 1,28 @@
require "spec"

require "../lib/boardslam"

describe BoardSlam do
  describe "#missing" do
    context "given 1, 1, 1" do
      it "returns the missing numbers" do
        board = BoardSlam.new(1i8, 1i8, 1i8)
        board.missing.should eq [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]
      end
    end

    context "given 2, 2, 2" do
      it "returns the missing numbers" do
        board = BoardSlam.new(2i8, 2i8, 2i8)
        board.missing.should eq [19, 21, 22, 23, 25, 26, 27, 29, 35]
      end
    end

    context "given 3, 5, 1" do
      it "returns the missing numbers" do
        board = BoardSlam.new(3i8, 5i8, 1i8)
        board.missing.should eq [19, 30]
      end
    end
  end
end