diff --git a/katas/2-generic_falling_blocks_game_solver/eddbot/Rakefile b/katas/2-generic_falling_blocks_game_solver/eddbot/Rakefile new file mode 100644 index 0000000..6c72e07 --- /dev/null +++ b/katas/2-generic_falling_blocks_game_solver/eddbot/Rakefile @@ -0,0 +1,7 @@ +require "minitest/test_task" + +Minitest::TestTask.create(:test) do |t| + t.test_globs = ["test/**/*_test.rb"] +end + +task :default => [:test] \ No newline at end of file diff --git a/katas/2-generic_falling_blocks_game_solver/eddbot/main.rb b/katas/2-generic_falling_blocks_game_solver/eddbot/main.rb new file mode 100644 index 0000000..7bc8466 --- /dev/null +++ b/katas/2-generic_falling_blocks_game_solver/eddbot/main.rb @@ -0,0 +1,11 @@ +# This is an empty main file for your kata +# You can run it with `bundle exec ruby katas/generic_falling_blocks_game_solver/eddsansome/main.rb` +# Feel free to add your code below and remove this comment ... + +require 'bundler/inline' + +gemfile do + source 'https://rubygems.org' + + # Add your gems here +end diff --git a/katas/2-generic_falling_blocks_game_solver/eddbot/src/solver.rb b/katas/2-generic_falling_blocks_game_solver/eddbot/src/solver.rb new file mode 100644 index 0000000..3413e5e --- /dev/null +++ b/katas/2-generic_falling_blocks_game_solver/eddbot/src/solver.rb @@ -0,0 +1,192 @@ +# frozen_string_literal: true + +class Solver + def initialize(game_plane) + @game_plane = game_plane + end + + # bounds checking + def get_val(row_val, col_val) + return 1 if row_val >= game_board.size || col_val >= game_board.first.size || row_val.negative? || col_val.negative? + + game_board[row_val][col_val] + end + + def recommended_block + # brute force + + # for each 0 square on the gameboard, from bottom to top + # superimpose each piece over the board, and add one to each + # grid num + + # if all of the nums are 1, then the shape fits + # otherwise the shape doens't + + # we need a way to check which was found first, and simply return it + + 5.downto(0).to_a.each do |row| + next if game_board[row].sum == 10 # skip if row is filled + + if game_board[row].sum.zero? && row <= 1 # lob any old shit in if the rows are clear + self.orientation = :any + + return 'Any' + end + + recs = [] + 0.upto(9).to_a.each do |col| + # if the block is filled, just skip + next if game_board[row][col] == 1 + + square = get_val(row, col) + get_val(row, col + 1) + get_val(row - 1, col) + get_val(row - 1, col + 1) + + if square.zero? + score = [] + # how are we going to check the score? + score << 10 if game_board[row].sum == 8 + score << 10 if game_board[row - 1].sum == 8 + + recs << { shape: 'Square', score: score.sum, orientation: :any } + end + + upwards_line = get_val(row, col) + get_val(row - 1, col) + get_val(row - 2, col) + get_val(row - 3, col) + + if upwards_line.zero? + + score = [] + + score << 10 if game_board[row].sum == 9 + score << 10 if game_board[row - 1].sum == 9 + score << 10 if game_board[row - 2].sum == 9 + score << 10 if game_board[row - 3].sum == 9 + + recs << { shape: 'Line', score: score.sum, orientation: :orientation_1 } + end + + sideways_line = get_val(row, col) + get_val(row, col + 1) + get_val(row, col + 2) + get_val(row, col + 3) + + if sideways_line.zero? + score = 0 + + score = 10 if game_board[row].sum == 6 + + recs << { shape: 'Line', score: score, orientation: :orientation_2 } + end + + z_block_sideways = get_val(row, col) + get_val(row, col + 1) + get_val(row - 1, col - 1) + get_val(row - 1, col) + + if z_block_sideways.zero? + score = [] + + score << 10 if game_board[row].sum == 8 + # this should really be 8, but otherwise it will be equal with the square. + score << 10 if game_board[row - 1].sum == 7 + + recs << { shape: 'Z', score: score.sum, orientation: :orientation_1 } + end + + z_block_upwards = get_val(row, + col) + get_val(row - 1, col) + get_val(row - 1, col + 1) + get_val(row - 2, col + 1) + + if z_block_upwards.zero? + score = [] + + score << 10 if game_board[row].sum == 9 + score << 10 if game_board[row - 1].sum == 8 + score << 10 if game_board[row - 2].sum == 9 + + recs << { shape: 'Z', score: score.sum, orientation: :orientation_2 } + end + + l_block_1 = get_val(row, col) + get_val(row - 1, col) + get_val(row - 2, col) + get_val(row - 2, col - 1) + if l_block_1.zero? + score = [] + + score << 10 if game_board[row].sum == 9 + score << 10 if game_board[row - 1].sum == 9 + score << 10 if game_board[row - 2].sum == 8 + + recs << { shape: 'L', score: score.sum, orientation: :orientation_3 } + end + + l_block_2 = get_val(row, col) + get_val(row - 1, col) + get_val(row - 1, col + 1) + get_val(row - 1, col + 2) + if l_block_2.zero? + score = [] + + score << 10 if game_board[row].sum == 9 + score << 10 if game_board[row - 1].sum == 7 + + recs << { shape: 'L', score: score.sum, orientation: :orientation_2 } + end + + l_block_3 = get_val(row, col) + get_val(row, col + 1) + get_val(row, col + 2) + get_val(row - 1, col + 2) + + if l_block_3.zero? + score = [] + + score << 10 if game_board[row].sum == 7 + score << 10 if game_board[row - 1].sum == 9 + + recs << { shape: 'L', score: score.sum, orientation: :orientation_4 } + end + + n_block_sideways = get_val(row, + col) + get_val(row, col + 1) + get_val(row - 1, col + 1) + get_val(row - 1, col + 2) + + if n_block_sideways.zero? + score = [] + + score << 10 if game_board[row].sum == 8 + # this should really be 8, but otherwise it will be equal with the square. + score << 10 if game_board[row - 1].sum == 7 + + recs << { shape: 'N', score: score.sum, orientation: :orientation_1 } + end + + n_block_upwards = get_val(row, + col) + get_val(row - 1, col) + get_val(row - 1, col - 1) + get_val(row - 2, col - 1) + + if n_block_upwards.zero? + score = [] + + score << 10 if game_board[row].sum == 9 + score << 10 if game_board[row - 1].sum == 8 + score << 10 if game_board[row - 2].sum == 9 + + recs << { shape: 'N', score: score.sum, orientation: :orientation_2 } + end + + # for each row find the max score and return it + next unless recs.any? + + winner = recs.max { |a, b| a[:score] <=> b[:score] } + + self.orientation = winner[:orientation] + + return winner[:shape] + end + end + end + + def recommended_orientation + recommended_block + + orientation + end + + private + + attr_reader :game_plane + + attr_accessor :orientation + + def game_board + @game_board ||= game_plane.split("\n").map do |line| + if line == '' + Array.new(10, 0) + else + line.chars.each_slice(2).map { _1.join == '▓▓' ? 1 : 0 } + end + end + end +end diff --git a/katas/2-generic_falling_blocks_game_solver/eddbot/test/integration/generic_falling_blocks_game_integration_test.rb b/katas/2-generic_falling_blocks_game_solver/eddbot/test/integration/generic_falling_blocks_game_integration_test.rb new file mode 100644 index 0000000..a6ae10e --- /dev/null +++ b/katas/2-generic_falling_blocks_game_solver/eddbot/test/integration/generic_falling_blocks_game_integration_test.rb @@ -0,0 +1,178 @@ +require 'minitest/autorun' +require_relative '../../src/solver' + +class GenericFallingBlocksGameIntegrationTest < Minitest::Test + def test_recommends_any_block_when_plane_is_level + game_plane = <<~PLANE + + + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + PLANE + assert_equal 'Any', described_class.new(game_plane).recommended_block + assert_equal :any, described_class.new(game_plane).recommended_orientation + end + + def test_correctly_recommends_square_block_any_orientation + game_plane = <<~PLANE + + + ▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + PLANE + instance = described_class.new(game_plane) + assert_equal 'Square', instance.recommended_block + assert_equal :any, instance.recommended_orientation + end + + def test_correctly_recommends_square_block_deep + game_plane = <<~PLANE + + + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + PLANE + instance = described_class.new(game_plane) + assert_equal 'Square', instance.recommended_block + assert_equal :any, instance.recommended_orientation + end + + def test_correctly_recommends_line_block_orientation_1 + game_plane = <<~PLANE + + + ▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + PLANE + instance = described_class.new(game_plane) + assert_equal 'Line', instance.recommended_block + assert_equal :orientation_1, instance.recommended_orientation + end + + def test_correctly_recommends_line_block_orientation_2 + game_plane = <<~PLANE + + + ▓▓▓▓▓▓▓▓▓▓ ▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + PLANE + instance = described_class.new(game_plane) + assert_equal 'Line', instance.recommended_block + assert_equal :orientation_2, instance.recommended_orientation + end + + def test_correctly_recommends_z_block_orientation_1 + game_plane = <<~PLANE + + + ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + PLANE + instance = described_class.new(game_plane) + assert_equal 'Z', instance.recommended_block + assert_equal :orientation_1, instance.recommended_orientation + end + + def test_correctly_recommends_z_block_orientation_2 + game_plane = <<~PLANE + + + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + PLANE + instance = described_class.new(game_plane) + assert_equal 'Z', instance.recommended_block + assert_equal :orientation_2, instance.recommended_orientation + end + + def test_correctly_recommends_l_orientation_2 + game_plane = <<~PLANE + + + ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + PLANE + instance = described_class.new(game_plane) + assert_equal 'L', instance.recommended_block + assert_equal :orientation_2, instance.recommended_orientation + end + + def test_correctly_recommends_l_orientation_3 + game_plane = <<~PLANE + + + ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + PLANE + instance = described_class.new(game_plane) + assert_equal 'L', instance.recommended_block + assert_equal :orientation_3, instance.recommended_orientation + end + + def test_correctly_recommends_l_orientation_4 + game_plane = <<~PLANE + + + ▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + PLANE + instance = described_class.new(game_plane) + assert_equal 'L', instance.recommended_block + assert_equal :orientation_4, instance.recommended_orientation + end + + def test_correctly_recommends_n_block_orientation_1 + game_plane = <<~PLANE + + + ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + PLANE + instance = described_class.new(game_plane) + assert_equal 'N', instance.recommended_block + assert_equal :orientation_1, instance.recommended_orientation + end + + def test_correctly_recommends_n_block_orientation_2 + game_plane = <<~PLANE + + + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + PLANE + instance = described_class.new(game_plane) + assert_equal 'N', instance.recommended_block + assert_equal :orientation_2, instance.recommended_orientation + end + + + private + + def described_class + Solver + end +end \ No newline at end of file