Battleship game - how to let players place their boats? - ruby

I'm creating a battleship game in Ruby. Currently, players have 2 boats (size 3 and size 4) and I arbitrarily placed the boats by myself in the code.
Each player has its own board (array1/array2): I put a 0 when the spot is empty and 1 when there's a boat.
My question is, how can I make the players to place their ships on their board by themselves?
My idea was to ask them to enter the first point of each boat and then ask them the side (north, east, south and west) to set the orientation.
Obviously, a ship can't be placed out of bounds nor on the same space as another ship.
How can I make sure of that with my code? I have no idea where to start...
Thanks!
# Board for player 1
board1 = []
for i in 0..4
board1[i] = []
(0..4).each do
board1[i].append('O')
end
end
# Board for player 2
board2 = []
for i in 0..4
board2[i] = []
(0..4).each do
board2[i].append('O')
end
end
# Display the boards
def display_board(board)
for row in board
puts row.map { |k| "#{k}" }.join(' ')
end
end
# Generation of the player boards
array1 = [ [0, 1, 1, 1, 0], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 0, 0, 0, 0] ]
array2 = [ [0, 0, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 1], [0, 0, 1, 0, 1], [0, 0, 1, 0, 1] ]
# A player wins when he/she reaches 7 points (2 ships each to place on their grid : a small ship (3x1 side) and a large one (4x1 size))
solution = 7
# We set the number of points at 0 at the start of the game
wins_p1 = 0
wins_p2 = 0
#Starting of the game and printing the board
while true do
puts 'Welcome!'
puts 'Enter start or stop'
starting = gets.chomp
puts "\n"
case starting.downcase
when 'start'
puts 'Enter the name of P1'
name_p1 = gets.chomp
puts "\n"
puts 'Enter the name of P2'
name_p2 = gets.chomp
puts "\n"
while wins_p1 < solution || wins_p2 < solution
puts "--- #{name_p1.upcase}'S TURN ---"
puts 'Enter line number (between 1 and 5):'
row_p1 = gets.chomp.to_i-1
puts 'Enter column number (between 1 and 5):'
column_p1 = gets.chomp.to_i-1
case array1[row_p1][column_p1]
when 1
board1[row_p1][column_p1] = 'X'
wins_p1 += 1
when 0
board1[row_p1][column_p1] = '-'
when 'X', '-'
next
end
puts "\n"
puts '--------------'
display_board(board1)
puts '--------------'
puts '----------------------'
puts "#{name_p1} has #{wins_p1} point#{"s" if wins_p1 > 1}."
puts '----------------------'
puts "\n"
break if wins_p1 == solution
puts "--- #{name_p2.upcase}'S TURN ---"
puts 'Enter line number (between 1 and 5):'
row_p2 = gets.chomp.to_i-1
puts 'Enter column number (between 1 and 5):'
column_p2 = gets.chomp.to_i-1
case array2[row_p2][column_p2]
when 1
board2[row_p2][column_p2] = 'X'
wins_p2 += 1
when 0
board2[row_p2][column_p2] = '-'
when 'X', '-'
next
end
puts "\n"
puts '--------------'
display_board(board2)
puts '--------------'
puts '----------------------'
puts "#{name_p2} a #{wins_p2} point#{"s" if wins_p2 > 1}."
puts '----------------------'
puts "\n"
break if wins_p2 == solution
end
puts "#{name_p1}, you are the winner!" if wins_p1 == solution
puts "#{name_p2}, you are the winner!" if wins_p2 == solution
puts "\n"
break
when 'stop'
puts 'See you soon!'
break
else
puts 'Enter start or stop only!'
puts "\n"
end
end

Going with your idea.
Get user input (x,y) for start of ship
Make sure that space is free (0)
Create a function that checks if a ship of size N (3 or 4) would fit in each orientation. For example in pseudocode (and this can be generalized with a direction parameter):
def wouldFitEast(board, anArray, posX, posY):
steps = 0
while (steps < N)
posX = posX + 1
if !validArrayIndex(posX, posY) or anArray[posX][posY] == 1
return false;
steps = steps + 1
return true;
if there is at least one direction the ship will fit, then ask which direction the user would like, otherwise, they need to pick a different location

Related

Refactoring battleship game

I'm creating a battleship game in Ruby.
Let me explain how it works:
The game is played on 5x5 grids (one per player).
Players have 2 ships each to place on their grid. A small ship (3x1 side) and a large one (4x1 size).
Players are asked in turn to place their ships on their board. A ship can't be placed out of bounds nor on the same space as another ship.
Players take turns to shoot at the opponent grid one after the other by selecting coordinates.
The first player to reach 7 points (all boats sinked) wins the game.
As you can see, some parts of my code are very repetitive:
I ask to each player to place its 2 ships so I have the "same" code 4 times (--- PLACEMENT OF THE SIZE 3 SHIP OF #{name_p1.upcase} ---).
I ask players to take turns to shoot at the opponent grid one after the other (this is done twice).
Same problem when I print the result of a shot.
I'm very struggling to refactor my code to make it simpler and shorter.
How can it be simplified?
# Board for player 1
board1 = []
for i in 0..4
board1[i] = []
(0..4).each do
board1[i].append('O')
end
end
# Board for player 2
board2 = []
for i in 0..4
board2[i] = []
(0..4).each do
board2[i].append('O')
end
end
# Display the boards
def display_board(board)
for row in board
puts row.map { |k| "#{k}" }.join(' ')
end
end
def check_obstacle(ship_size, player_array, posX, posY, direction)
check = 0
#ship_present = 0
#space_available = 0
#ship_present += 1 if player_array[posX][posY] == 1
#space_available += 1 if player_array[posX][posY] == 0
while check < ship_size && #space_available < ship_size
case direction
when 'north' then posX -= 1
when 'east' then posY += 1
when 'south' then posX += 1
when 'west' then posY -= 1
end
#space_available += 1 if posX.between?(0, 4) && posY.between?(0, 4)
#ship_present += 1 if posX.between?(0, 4) && posY.between?(0, 4) && player_array[posX][posY] == 1
check += 1
end
#space_available == ship_size && #ship_present == 0
end
def ship_placement(ship_size, player_array, posX, posY, direction)
steps = 0
while steps < ship_size && #ship_present == 0 && #space_available == ship_size
player_array[posX][posY] = 1
case direction
when 'north' then posX -= 1
when 'east' then posY += 1
when 'south' then posX += 1
when 'west' then posY -= 1
end
steps += 1
end
puts "The ship of size #{ship_size} is placed."
end
# Generation of the player boards
array1 = [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
array2 = [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
# A player wins when he/she reaches 7 points (2 ships each to place on their grid : a small ship (3x1 side) and a large one (4x1 size))
solution = 7
# We set the number of points at 0 at the start of the game
points_p1 = 0
points_p2 = 0
#Starting of the game and printing the board
while true do
puts 'Welcome soldiers!'
puts 'To start the game, enter "start". To quit, enter "stop":'
starting = gets.chomp
puts "\n"
case starting.downcase
when 'start'
puts 'Enter the first name of the first player:'
name_p1 = gets.chomp.to_s
puts "\n"
puts 'Enter the first name of the second player:'
name_p2 = gets.chomp.to_s
puts "\n"
puts "--- PLACEMENT OF THE SIZE 3 SHIP OF #{name_p1.upcase} ---"
while true
while true
puts 'Enter LINE number (between 1 and 5):'
placement_row = gets.chomp.to_i-1
break if placement_row.between?(0, 4)
end
while true
puts 'Enter COLUMN number (between 1 and 5):'
placement_column = gets.chomp.to_i-1
break if placement_column.between?(0, 4)
end
while true
puts "Enter ship direction (north, east, south ou west)"
orientation = gets.chomp.downcase
break if %w(north east south west).include? orientation
end
if check_obstacle(3, array1, placement_row, placement_column, orientation)
ship_placement(3, array1, placement_row, placement_column, orientation)
break
else
puts "Unable to place. Please try again."
end
end
puts "--- PLACEMENT OF THE SIZE 4 SHIP OF #{name_p1.upcase} ---"
while true
while true
puts 'Enter LINE number (between 1 and 5):'
placement_row = gets.chomp.to_i-1
break if placement_row.between?(0, 4)
end
while true
puts 'Enter COLUMN number (between 1 and 5):'
placement_column = gets.chomp.to_i-1
break if placement_column.between?(0, 4)
end
while true
puts "Enter ship direction (north, east, south ou west)"
orientation = gets.chomp.downcase
break if %w(north east south west).include? orientation
end
if check_obstacle(4, array1, placement_row, placement_column, orientation)
ship_placement(4, array1, placement_row, placement_column, orientation)
break
else
puts "Unable to place. Please try again."
end
end
###############################################################
puts "--- PLACEMENT OF THE SIZE 3 SHIP OF #{name_p2.upcase} ---"
while true
while true
puts 'Enter LINE number (between 1 and 5):'
placement_row = gets.chomp.to_i-1
break if placement_row.between?(0, 4)
end
while true
puts 'Enter COLUMN number (between 1 and 5):'
placement_column = gets.chomp.to_i-1
break if placement_column.between?(0, 4)
end
while true
puts "Enter ship direction (north, east, south ou west)"
orientation = gets.chomp.downcase
break if %w(north east south west).include? orientation
end
if check_obstacle(3, array2, placement_row, placement_column, orientation)
ship_placement(3, array2, placement_row, placement_column, orientation)
break
else
puts "Unable to place. Please try again."
end
end
puts "--- PLACEMENT OF THE SIZE 4 SHIP OF #{name_p2.upcase} ---"
while true
while true
puts 'Enter LINE number (between 1 and 5):'
placement_row = gets.chomp.to_i-1
break if placement_row.between?(0, 4)
end
while true
puts 'Enter COLUMN number (between 1 and 5):'
placement_column = gets.chomp.to_i-1
break if placement_column.between?(0, 4)
end
while true
puts "Enter ship direction (north, east, south ou west)"
orientation = gets.chomp.downcase
break if %w(north east south west).include? orientation
end
if check_obstacle(4, array2, placement_row, placement_column, orientation)
ship_placement(4, array2, placement_row, placement_column, orientation)
break
else
puts "Unable to place. Please try again."
end
end
while points_p1 < solution || points_p2 < solution
puts "--- #{name_p1.upcase}'S TURN ---"
while true
puts 'Enter LINE number (between 1 and 5):'
row_p1 = gets.chomp.to_i-1
break if row_p1.between?(0, 4)
end
while true
puts 'Enter COLUMN number (between 1 and 5):'
column_p1 = gets.chomp.to_i-1
break if column_p1.between?(0, 4)
end
# Shot fired!
case array1[row_p1][column_p1]
when 1
board1[row_p1][column_p1] = 'X'
array1[row_p1][column_p1] = 'X'
points_p1 += 1
when 0
board1[row_p1][column_p1] = '-'
array1[row_p1][column_p1] = '-'
when 'X', '-'
puts 'Square already played.'
next
end
puts "\n"
puts '--------------'
display_board(board1)
puts '--------------'
puts '----------------------'
puts "#{name_p1} has #{points_p1} point#{"s" if points_p1 > 1}."
puts '----------------------'
puts "\n"
break if points_p1 == solution
puts "--- #{name_p2.upcase}'S TURN ---"
while true
puts 'Enter LINE number (between 1 and 5):'
row_p2 = gets.chomp.to_i-1
break if row_p2.between?(0, 4)
end
while true
puts 'Enter COLUMN number (between 1 and 5):'
column_p2 = gets.chomp.to_i-1
break if column_p2.between?(0, 4)
end
# Shot fired!
case array2[row_p2][column_p2]
when 1
board2[row_p2][column_p2] = 'X'
array2[row_p1][column_p1] = 'X'
points_p2 += 1
when 0
board2[row_p2][column_p2] = '-'
when 'X', '-'
next
end
puts "\n"
puts '--------------'
display_board(board2)
puts '--------------'
puts '----------------------'
puts "#{name_p2} has #{points_p2} point#{"s" if points_p2 > 1}."
puts '----------------------'
puts "\n"
break if points_p2 == solution
end
puts "Congratulations #{name_p1}, you have destroyed all the enemy ships!" if points_p1 == solution
puts "Congratulations #{name_p2}, you have destroyed all the enemy ships!" if points_p2 == solution
puts "\n"
break
when 'stop'
puts 'See you soon!'
break
else
puts 'Please make a choice between "start". To exit, enter "stop".'
puts "\n"
end
end
As David mentions in his comment, you really want to make use of Ruby's object oriented features.
Here are some components I put together and some example usage. (Seemed like a fun exercise ... I guess I got carried away.)
class ShipPoint
attr_reader :row, :column, :owner
attr_accessor :hit
def initialize(row,column, owner)
#row = row
#column = column
#owner = owner
#hit = false
end
def to_s
#hit ? "X" : owner.label
end
end
class Ship
attr_reader :points, :name
attr_accessor :owner
def initialize(row:, column:, size:, direction:, name: nil, owner: nil)
#name = name
#owner = owner
case direction
when "north"
#points = (0...size).map { ShipPoint.new(row - _1, column, self) }
when "south"
#points = (0...size).map { ShipPoint.new(row + _1, column, self) }
when "east"
#points = (0...size).map { ShipPoint.new(row, column + _1, self) }
when "west"
#points = (0...size).map { ShipPoint.new(row, column - _1, self) }
end
end
def sunk?()
#points.all? {_1.hit}
end
def column_range()
Range.new(*#points.minmax_by {_1.column}.map {_1.column})
end
def row_range()
Range.new(*#points.minmax_by {_1.row}.map {_1.row})
end
def point(row,column)
#points.find {_1.row == row && _1.column == column}
end
def label()
return #name[0] if #name
owner.ships.index(self).to_s
end
end
class Board
attr_reader :range, :ships
def initialize(size=5)
#range = Range.new(0, size-1)
#content = Array.new(size) {Array.new(size)}
#ships = []
end
def add_ship(ship)
placeable = range.cover?(ship.column_range) && range.cover?(ship.row_range) && ship.points.all? {available?(_1.row, _1.column)}
if placeable
ship.owner = self
#ships << ship
ship.points.each {|p| #content[p.row][p.column] = ship}
else
puts "ship is unplaceable"
end
end
def to_s
display = ""
#content.each_index do |row|
row_display = ""
#content[row].each_index do |column|
location = #content[row][column]
row_display << (location ? location.point(row,column).to_s : ".")
end
display << "#{row_display}\n"
end
display
end
def available?(row, column)
#content[row][column].nil?
end
def target(row:,column:)
ship = #content[row][column]
point = ship&.point(row,column)
point&.hit = true
sunk = ship&.sunk?
all_sunk = #ships.all? {_1.sunk?}
case
when all_sunk then "all sunk"
when sunk then "Sunk: #{ship.name}"
when point&.hit then :hit
else :miss
end
end
end
b1 = Board.new
b2 = Board.new
s1 = Ship.new(row: 0, column: 1, size: 4, direction: "north")
s2 = Ship.new(row: 0, column: 1, size: 4, direction: "south", name: "Battle Ship")
s3 = Ship.new(row: 0, column: 1, size: 4, direction: "east")
s4 = Ship.new(row: 0, column: 1, size: 4, direction: "west")
s5 = Ship.new(row: 4, column: 0, size: 3, direction: "east")
b1.add_ship(s1)
b1.add_ship(s2)
b1.add_ship(s3)
b1.add_ship(s4)
b1.add_ship(s5)
puts b1
puts "result = #{b1.target(row: 0, column: 0)}"
puts "result = #{b1.target(row: 0, column: 1)}"
puts "result = #{b1.target(row: 1, column: 1)}"
puts "result = #{b1.target(row: 2, column: 1)}"
puts "result = #{b1.target(row: 3, column: 1)}"
puts "result = #{b1.target(row: 4, column: 0)}"
puts "result = #{b1.target(row: 4, column: 1)}"
puts "result = #{b1.target(row: 4, column: 2)}"
puts b1

The Number of the Smallest Unoccupied Chair solution in ruby

I am learning ruby and have started practicing problems from leetcode, yesterday I have a problem which I am not able to solve since yesterday.
I tried hard doing that in ruby, but not able to do yet.
I tried this
def give_chair(a)
u = a.uniq
d = []
u.each do |i|
d << i if a.count(i) == 1
end
d
end
def smallest_chair(times, target_friend)
friend = times[target_friend]
sorted_arrival_times = times.sort
leave_time_chair = {}
chair = 0
chairs_array = []
uniq_chars_array = []
sorted_arrival_times.each do |i|
if leave_time_chair.keys.select { |k| i[0] > k }.empty?
leave_time_chair[i[1]] = chair
chair+=1
else
all_keys = leave_time_chair.keys.select { |k| k <= i[0] }
chairs_array = leave_time_chair.values
p chairs_array
if give_chair(chairs_array).empty?
leave_time_chair[i[1]] = chairs_array.sort.first
else
leave_time_chair[i[1]] = give_chair(chairs_array).sort.first
end
end
if i == friend
p leave_time_chair
return leave_time_chair[i[1]]
end
end
end
# a = [[33889,98676],[80071,89737],[44118,52565],[52992,84310],[78492,88209],[21695,67063],[84622,95452],[98048,98856],[98411,99433],[55333,56548],[65375,88566],[55011,62821],[48548,48656],[87396,94825],[55273,81868],[75629,91467]]
# b = 6
# p smallest_chair(a, b)
but it is failing for some test cases.
I am not able to create an algorithm for it.
Question = https://leetcode.com/problems/the-number-of-the-smallest-unoccupied-chair
My approach:
First I sort the times array according to arrival times.
Then I iterate over each array element
Now if the arrival time is greater than all the previous leaving time (I am creating key, value pair of leaving time and chair given) then I add a new key=> value pair in leave_time_chair (which is hash) and where key is the leaving time of current array and value is the chair given to it.
Then I increment the chair (chair+=1)
Else I get all those leaving time which are equal or less than the current arrival time (all_keys = leave_time_chair.keys.select { |k| k <= i[0] })
Then I get all the chairs of those times
Now I have all the chairs like this => [0, 0, 1, 2] so I wrote one function [ give_chair(a) ] which gives me those elements which are not repeated. like this => [1, 2] and then I assign the shortest number (chair) to the leaving time of current array. and so on...
Then if my current array is equal to the friend I return the chair of it. by extracting it from a hash (leave_time_chair) return leave_time_chair[i[1]]
my naive solution (not optimize yet), basically my idea that i flat-map the input array into an array with each element is a pair [time arrive/leave, friend index], then i will sort that array base on time (don't care arrive or leave), if both pair have same time, then i'll compare the arrive time of fiend index. Finally i loop through the sorted array and evaluate minimum free chair index each step, whenever i meet the targetFriend i return that minimum free chair index.
# #param {Integer[][]} times
# #param {Integer} target_friend
# #return {Integer}
def smallest_chair(times, target_friend)
# times = [[1,2],[4,7],[2,4]]
# targetFriend = 1
sit_times = times.each_with_index.inject([]) { |combi, (time, index)|
combi += [[time.first, index], [time.last, index]]
}
# [[1, 0], [2, 0], [4, 1], [7, 1], [2, 2], [4, 2]]
sit_times.sort! {|x, y|
c = x[0] <=> y[0]
# [[1, 0], [2, 0], [2, 2], [4, 1], [4, 2], [7, 1]]
c = times[x[1]][0] <=> times[y[1]][0] if c == 0
# [[1, 0], [2, 0], [2, 2], [4, 2], [4, 1], [7, 1]]
c
}
chairs = {} # to mark time of friend
occupied = Array.new(times.size, 0) # occupied chair: 1, otherwise: 0
min_free = 0 # current minimum not occupied chair
sit_times.each do |time, friend_index|
if target_friend == friend_index # check
return min_free
end
sit = chairs[friend_index]
if sit # leave
occupied[sit] = 0
chairs[friend_index] = nil
min_free = sit if min_free > sit
else # arrive
chairs[friend_index] = min_free
occupied[min_free] = 1
min_free += 1 until occupied[min_free] == 0 # re-calculate
end
end
end
Note: the code pass test cases on leetcode but the performance is not good.
update
here is the better version, using 3 priority queues, one for arrive times, one for leave times and the last for chair.
PriorityQueue class
class PriorityQueue
attr_reader :length
def initialize(opts={}, &comparator)
order_opt = opts.fetch(:order, :asc)
#order = order_opt == :asc ? -1 : 1
#comparator = comparator
#items = [nil]
#length = 0
end
def push(item)
#items << item
#length += 1
swim(#length)
true
end
def pop
return nil if empty?
swap(1, #length) if #length > 1
#length -= 1
sink(1) if #length > 0
#items.pop
end
def empty?
#length == 0
end
def swap(i, j)
temp = #items[i]
#items[i] = #items[j]
#items[j] = temp
end
def in_order?(i, j)
x = #items[i]
y = #items[j]
order = #comparator.nil? ? (x <=> y) : #comparator.call(x, y)
order == #order
end
def swim(from)
while (up = from / 2) >= 1
break if in_order?(up, from)
swap(up, from)
from = up
end
end
def sink(from)
while (down = from * 2) <= #length
down += 1 if down < #length && in_order?(down + 1, down)
break if in_order?(from, down)
swap(down, from)
from = down
end
end
end
smallest_chair with priority queues (note that i found using sort is faster than a queue for arrive times, but basically the idea is same)
def smallest_chair_pq(times, target_friend)
# a_pq = PriorityQueue.new { |x, y|
# x[0] <=> y[0]
# }
#
# times.each do |t|
# a_pq.push(t)
# end
# sort arrive times is faster than a priority queue
a_pq = times.sort_by(&:first).reverse
# leave times queue
l_pq = PriorityQueue.new { |x, y|
c = x[0] <=> y[0]
c = x[1] <=> y[1] if c == 0
c
}
# chair-indexes queue
# consider case a friend come in at arrive-time at1
# and there's a range chairs with leave times in range lm <= at1 <= ln
# that mean that friend could pick one of those chairs
# and according this problem requirement, should pick the minimun chair index
c_pq = PriorityQueue.new
target_time = times[target_friend][0]
last_chair_index = 0
until a_pq.empty?
a_top = a_pq.pop
arrive_time = a_top.first
if l_pq.empty?
return 0 if arrive_time == target_time
l_pq.push([a_top.last, 0])
else
l_top = l_pq.pop
if l_top.first <= arrive_time
c_pq.push(l_top.last)
until (l_ntop = l_pq.pop).nil? || arrive_time < l_ntop.first
c_pq.push(l_ntop.last)
end
l_pq.push(l_ntop) unless l_ntop.nil?
min_chair_index = c_pq.pop
return min_chair_index if arrive_time == target_time
l_pq.push([a_top.last, min_chair_index])
else
unless c_pq.empty?
chair_index = c_pq.pop
return chair_index if arrive_time == target_time
l_pq.push([a_top.last, chair_index])
else
last_chair_index += 1
return last_chair_index if arrive_time == target_time
l_pq.push([a_top.last, last_chair_index])
end
l_pq.push(l_top)
end
end
end
end

Battlefield game in ruby [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 4 years ago.
Improve this question
This is a classic battleship game for two players:
#board1 and board2 arrays are boards that players see only dots (not bumped yet), O (not ship part) and X (bumped ship part)
board1 = []
board2 = []
#Create the board that players can see through with terminal
for i in 0..4
board1.append("O")
end
for i in 0..4
board2.append("O")
end
def print_board(board1)
for row in board1
puts board1.map { |k| "#{k}" }.join(" ")
end
end
def print_board(board2)
for row in board2
puts board2.map { |k| "#{k}" }.join(" ")
end
end
print_board(board1)
puts "\n"
print_board(board2)
#array1 and array2 are obvious boards of player1 and player2 respectly
array1 = [ [0, 1, 1, 1, 0], [1, 0, 0, 0, 0], [1, 0, 1, 0, 0], [0, 0, 1, 0, 1], [0, 0, 0, 0, 0] ]
array2 = [ [1, 0, 1, 1, 0], [0, 0, 0, 0, 1], [0, 1, 0, 0, 1], [0, 1, 0, 0, 1], [0, 0, 0, 0, 0] ]
#Starting of the game and the printing the board
while true do
puts "Welcome to the game!!!"
puts "Do you want to start? (start/reset):"
a = gets.chomp
if a == 'start'
for i in 0..100
puts "Turn - Player1: "
puts "Enter row: "
q = gets.chomp
p1_row = q.to_i
puts "Enter coloumn: "
w = gets.chomp
p1_col= w.to_i
if array2[p1_row][p1_col] == 1
array2[p1_row][p1_col] ="X"
board2[p1_row][p1_col] ="X"
elsif array2[p1_row][p1_col] == 0
array2[p1_row][p1_col] ="-"
board2[p1_row][p1_col] ="-"
elsif array2[p1_row][p1_col] =="X" or array2[p1_row][p1_col] =="-"
next
end
print_board(board2)
puts "Turn - Player2: "
puts "Enter row: "
e = gets.chomp
p2_row = e.to_i
puts "Enter coloumn: "
r = gets.chomp
p2_col= r.to_i
if array1[p2_row][p2_col] == 1
array1[p2_row][p2_col] ="X"
board1[p2_row][p2_col] ="X"
elsif array1[p2_row][p2_col] == 0
array1[p2_row][p2_col] ="-"
board1[p2_row][p2_col] ="-"
elsif array1[p2_row][p2_col] =="X" or array1[p2_row][p2_col] =="-"
next
end
print_board(board1)
end
elsif a == 'reset'
puts "You are off the game"
break
else
puts "\n"
puts "Answer can be only {start} or {reset}"
end
end
I have two problems with this code. When I entered index 4 for player 2, I get "index 4 out of string (IndexError)", but I did not find why. The other problem is that, when if statement finds 1 or 0, it changes all columns, and does not change only the element of the array.
The main problem is in your board set-up. You have
for i in 0..4
board1.append("O")
end
But that only creates one dimension. Try this:
for i in 0..4
board1[i] = []
(0..4).each do
board1[i].append("O")
end
end
A secondary problem is the subroutine print_board. First, you only need one definition of the subroutine, then second, the map needs to apply to "row" not "board", like this:
def print_board(board)
for row in board
puts row.map { |k| "#{k}" }.join(" ")
end
end
There are many other problems with your code. I assume you are learning Ruby and this is an exercise to learn the Array API. In such case, it will be best for you continue the exercise yourself, learning as you go.
However, one additional hint: Learn about rubocop and run it on your code. Doing this consistently will teach you about good Ruby style while also improving your code. To be specific: Install the rubocop gem, then run rubocop against your code like this:
rubocop -SEa battleship.rb

Condition operators don't give correct result

I am trying to develop a battleship game in console. I need input of coordinates that guess where the game is. I did it:
#array1 and array2 are not obvious boards of player1 and player2 respectly
array1 = [ ['*', 'A', 'B', 'C', 'D', 'E'],['1',0, 1, 1, 1, 0], ['2',1, 0, 0, 0, 0], ['3',1, 0, 1, 0, 0], ['4',0, 0, 1, 0, 1], ['5',0, 0, 0, 0, 0] ]
array2 = [ ['*', 'A', 'B', 'C', 'D', 'E'],['1',1, 0, 1, 1, 0], ['2',0, 0, 0, 0, 1], ['3',0, 1, 0, 0, 1], ['4',0, 1, 0, 0, 1], ['5',0, 0, 0, 0, 0] ]
#arr1 and arr2 are obvious boards of player1 and player2 respectly
arr1 = [['*', 'A', 'B', 'C', 'D', 'E'],['1','.','.','.','.','.'],['2','.','.','.','.','.'],['3','.','.','.','.','.'],['4','.','.','.','.','.'],['5','.','.','.','.','.']]
arr2 = [['*', 'A', 'B', 'C', 'D', 'E'],['1', '.','.','.','.','.'],['2', '.','.','.','.','.'],['3', '.','.','.','.','.'],['4', '.','.','.','.','.'],['5', '.','.','.','.','.']]
coor = ['A', 'B', 'C', 'D', 'E']
#board1 and board2 are the final boards of player1 and player2
#this arrays will be seen during game
#every changes will be seen on these boards
board1 = arr1.map { |x| x.join(' ') }
board2 = arr2.map { |x| x.join(' ') }
#num1 and num2 are the numbers of the parts of ships respectively
num1 = 8
num2 = 8
#count1 and count2 are the numbers of poped ship parts respectively
count1 = 0
count2 = 0
#Starting of the game and the printing the board
#If we type "start" game will be started
#"Reset" game will be ended
#If we type words except "start" or "reset" program will ask "Do you want to start? (start/reset)" again
while true do
puts "Welcome to the game!!!"
puts "Do you want to start? (start/reset):"
respond = gets.chomp
if respond == 'start'
puts "\n"
puts "Player ONE"
puts board1
puts "\n"
puts "Player TWO"
puts board2
while true do
#Burada while ile player1 in shertleri olacaq
while true do
puts "\n"
puts "Turn - Player ONE"
puts "Enter coordinate: "
a = gets.chomp
a1 = a.split('')
b = coor.index(a1[0]) +1
col1 = b
row1= a1[1].to_i
if a == '""'
puts "You have to enter any coordinate"
break
elsif array1[row1][col1] == 'X' or array1[row1][col1] == '0'
puts "You have already bumped this coordinate!"
elsif col1<1 or col1>5 or row1>5 or row1<1
puts "This coordinate is out of board"
else
if array1[row1][col1] == 1
count1 = count1 + 1
arr1[row1][col1] = "X"
elsif array1[row1][col1] == 0
arr1[row1][col1] = "0"
end
board1 = arr1.map { |x| x.join(' ') }
puts "\n"
puts "Player ONE"
puts board1
puts "\n"
puts "Player TWO"
puts board2
if count1 == num1
puts "Player ONE won the game!"
break
end
end
break
end
while true do
#Burada while ile player2 in shertleri olacaq
puts "\n"
puts "Turn - Player TWO"
puts "Enter coordinate: "
c = gets.chomp
c1 = c.split('')
d = coor.index(c1[0]) + 1
col2 = d
row2= c1[1].to_i
if c == '""'
puts "You have to enter any coordinate"
break
elsif array2[row2][col2] == 'X' or array2[row2][col2] == '0'
puts "You have already bumped this coordinate!"
elsif col2<1 or col2>5 or row2>5 or row2<1
puts "This coordinate is out of board"
else
if array2[row2][col2] == 1
count2 = count2 + 1
arr2[row2][col2] = "X"
elsif array2[row2][col2] == 0
arr2[row2][col2] = "0"
end
board2 = arr2.map { |x| x.join(' ') }
puts "Player ONE"
puts board1
puts "\n"
puts "Player TWO"
puts board2
if count2 == num2
puts "Player TWO won the game!"
break
end
end
break
end
end
elsif respond == 'reset'
puts "You are off the game"
break
else
puts "\n"
puts "Answer can be only {start} or {reset}"
end
end
There are some problems, and I added if elsif else conditions to the code to solve them. One of them is for entering coordinates out of board, the second one is for not entering any coordinate, and the last one is for bumped coordinate. If these three condition are bypassed, the players can enter any coordinate.
But these codes don't work. When I check these three conditions, the result is an error. Can anyone tell me what the problem is with these conditions? Why don't they give suitable result?
When entering A8 as a value, the error you get is:
battleship.rb:54:in `<main>': undefined method `[]' for nil:NilClass (NoMethodError)
This is line 54:
elsif array1[row1][col1] == 'X' or array1[row1][col1] == '0'
In this case, the error is that array1["8"] is nil, and nil["A"] is undefined. An easy fix is to move line 56 above this one (the one that checks whether the input is actually within the board range).
elsif col1<1 or col1>5 or row1>5 or row1<1
If you do that, then since 8 is greater than 5, you'll get the "out of board" message rather than an error.
The other error you mention is when you put in an empty string. In that case the error is:
battleship.rb:48:in `<main>': undefined method `+' for nil:NilClass (NoMethodError)
Line 48 says:
b = coor.index(a1[0]) +1
In this case, the index is returning nil, and you can't add 1 to nil.
One way to fix this is to move the section a few lines down that checks whether a is blank to before line 48.

Printing a readable Matrix in Ruby

Is there a built in way of printing a readable matrix in Ruby?
For example
require 'matrix'
m1 = Matrix[[1,2], [3,4]]
print m1
and have it show
=> 1 2
3 4
in the REPL instead of:
=> Matrix[[1,2][3,4]]
The Ruby Docs for matrix make it look like that's what should show happen, but that's not what I'm seeing. I know that it would be trivial to write a function to do this, but if there is a 'right' way I'd rather learn!
You could convert it to an array:
m1.to_a.each {|r| puts r.inspect}
=> [1, 2]
[3, 4]
EDIT:
Here is a "point free" version:
puts m1.to_a.map(&:inspect)
I couldn't get it to look like the documentation so I wrote a function for you that accomplishes the same task.
require 'matrix'
m1 = Matrix[[1,2],[3,4],[5,6]]
class Matrix
def to_readable
i = 0
self.each do |number|
print number.to_s + " "
i+= 1
if i == self.column_size
print "\n"
i = 0
end
end
end
end
m1.to_readable
=> 1 2
3 4
5 6
Disclaimer: I'm the lead developer for NMatrix.
It's trivial in NMatrix. Just do matrix.pretty_print.
The columns aren't cleanly aligned, but that'd be easy to fix and we'd love any contributions to that effect.
Incidentally, nice to see a fellow VT person on here. =)
You can use the each_slice method combined with the column_size method.
m1.each_slice(m1.column_size) {|r| p r }
=> [1,2]
[3,4]
Ok, I'm a total newbie in ruby programming. I'm just making my very first incursions, but it happens I got the same problem and made this quick'n'dirty approach.
Works with the standard Matrix library and will print columns formatted with same size.
class Matrix
def to_readable
column_counter = 0
columns_arrays = []
while column_counter < self.column_size
maximum_length = 0
self.column(column_counter).each do |column_element|# Get maximal size
length = column_element.to_s.size
if length > maximal_length
maximum_length = length
end
end # now we've got the maximum size
column_array = []
self.column(column_counter).each do |column_element| # Add needed spaces to equalize each column
element_string = column_element.to_s
element_size = element_string.size
space_needed = maximal_length - element_size +1
if space_needed > 0
space_needed.times {element_string.prepend " "}
if column_counter == 0
element_string.prepend "["
else
element_string.prepend ","
end
end
column_array << element_string
end
columns_arrays << column_array # Now columns contains equal size strings
column_counter += 1
end
row_counter = 0
while row_counter < self.row_size
columns_arrays.each do |column|
element = column[row_counter]
print element #Each column yield the correspondant row in order
end
print "]\n"
row_counter += 1
end
end
end
Any correction or upgrades welcome!
This is working for me
require 'matrix'
class Matrix
def print
matrix = self.to_a
field_size = matrix.flatten.collect{|i|i.to_s.size}.max
matrix.each do |row|
puts (row.collect{|i| ' ' * (field_size - i.to_s.size) + i.to_s}).join(' ')
end
end
end
m = Matrix[[1,23,3],[123,64.5, 2],[0,0,0]]
m.print
Here is my answer:
require 'matrix'
class Matrix
def to_pretty_s
s = ""
i = 0
while i < self.column_size
s += "\n" if i != 0
j = 0
while j < self.row_size
s += ' ' if j != 0
s += self.element(i, j).to_s
j += 1
end
i += 1
end
s
end
end
m = Matrix[[0, 3], [3, 4]]
puts m # same as 'puts m.to_s'
# Matrix[[0, 3], [3, 4]]
puts m.to_pretty_s
# 0 3
# 3 4
p m.to_pretty_s
# "0 3\n3 4"
You could use Matrix#to_pretty_s to get a pretty string for format.
There is no inbuilt Ruby way of doing this. However, I have created a Module which can be included into Matrix that includes a method readable. You can find this code here, but it is also in the following code block.
require 'matrix'
module ReadableArrays
def readable(factor: 1, method: :rjust)
repr = to_a.map { |row|
row.map(&:inspect)
}
column_widths = repr.transpose.map { |col|
col.map(&:size).max + factor
}
res = ""
repr.each { |row|
row.each_with_index { |el, j|
res += el.send method, column_widths[j]
}
res += "\n"
}
res.chomp
end
end
## example usage ##
class Matrix
include ReadableArrays
end
class Array
include ReadableArrays
end
arr = [[1, 20, 3], [20, 3, 19], [-32, 3, 5]]
mat = Matrix[*arr]
p arr
#=> [[1, 20, 3], [20, 3, 19], [-2, 3, 5]]
p mat
#=> Matrix[[1, 20, 3], [20, 3, 19], [-2, 3, 5]]
puts arr.readable
#=>
# 1 20 3
# 20 3 19
# -32 3 5
puts mat.readable
#=>
# 1 20 3
# 20 3 19
# -32 3 5
puts mat.readable(method: :ljust)
#=>
# 1 20 3
# 20 3 19
# -32 3 5
puts mat.readable(method: :center)
#=>
# 1 20 3
# 20 3 19
# -32 3 5
I had this problem just yet and haven't seen anyone posting it here, so I will put my solution if it helps someone. I know 2 for loops are not the best idea, but for smaller matrix it should be okay, and it prints beautifully and just how you want it, also without of use of require 'matrix' nor 'pp'
matrix = Array.new(numRows) { Array.new(numCols) { arrToTakeValuesFrom.sample } }
for i in 0..numRows-1 do
for j in 0..numCols-1 do
print " #{matrix[i][j]} "
end
puts ""
end

Resources