Created
April 19, 2024 17:06
-
-
Save jadon1979/183dca7a80060cfdd8f4fbac76e689bb to your computer and use it in GitHub Desktop.
A code test for a working command line version of Battleship.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require_relative './ship.rb' | |
| # Represents the game board | |
| class Board | |
| FLEET = { | |
| carrier: 5, | |
| battleship: 4, | |
| cruiser: 3, | |
| submarine: 2, | |
| destroyer: 2 | |
| }.freeze | |
| BOARD_SYMBOLS = { | |
| water: '-', | |
| miss: 'x', | |
| hit: '!' | |
| }.freeze | |
| attr_accessor :ships | |
| # Initializes and generates a new game board with a specified board size. | |
| # | |
| # @param board_size [Integer] the size of the game board. | |
| def initialize(grid_size) | |
| @grid_size = grid_size | |
| @grid = Array.new(@grid_size * @grid_size, BOARD_SYMBOLS[:water]) | |
| @ships = Array.new(FLEET.length) | |
| @total_sunk = 0 | |
| generate | |
| end | |
| # Display the game board's current state | |
| def display | |
| letters = grid_letters.to_a[0...@grid_size] | |
| puts column_headers | |
| @grid.each_slice(@grid_size) { |row| puts "#{letters.shift[0]}: #{row.join(' ')}" } | |
| end | |
| # Fires at the game board with the specified coordinates. | |
| # | |
| # @param coord [String] the coordinates to fire at (e.g., "A1"). | |
| def fire(coord) | |
| row = grid_letters[coord.downcase[0]] | |
| col = coord[1].to_i - 1 | |
| # Make sure that the coords are not off the grid | |
| return unless row && col && (row >= 0 && row < @grid_size) && (col >= 0 && col < @grid_size) | |
| # Get the element at the translated grid coordinate | |
| pos = @grid[row * @grid_size + col] | |
| if [ BOARD_SYMBOLS[:water], BOARD_SYMBOLS[:miss] ].include?(pos) | |
| puts "MISS!" | |
| @grid[row * @grid_size + col] = BOARD_SYMBOLS[:miss] | |
| else | |
| ships[pos].hit | |
| @total_sunk += 1 if ships[pos].sunk? | |
| @grid[row * 10 + col] = BOARD_SYMBOLS[:hit] | |
| end | |
| end | |
| # Check if all ships have been sunk. | |
| def fleet_destroyed? | |
| @total_sunk >= FLEET.length | |
| end | |
| private | |
| # Iterate the Fleet and attempt to add the ships | |
| def generate | |
| FLEET.each_with_index do |(ship, size), index| | |
| loop do | |
| coords = random_coords(size) | |
| direction = [ :horizontal, :vertical ].sample | |
| next unless valid_coords?(coords, direction, size) | |
| add_ship(ship, coords, direction, index) | |
| break; | |
| end | |
| end | |
| end | |
| # Checks if the coordinates are valid. | |
| # | |
| # @param coords [Array<Integer>] the row and column indices | |
| # @param direction [Symbol] the direction to check the coordinates. | |
| # @param size [Integer] the size of the ship (number of cells it occupies). | |
| # @return [Boolean] true if the coordinates are valid, otherwise false. | |
| def valid_coords?(coords, direction, size) | |
| starts, ends = coords | |
| size.times do |i| | |
| row, col = next_coords(direction, starts, ends, i) | |
| return false if row >= @grid_size || col >= @grid_size || @grid[row * @grid_size + col] != BOARD_SYMBOLS[:water] | |
| end | |
| true | |
| end | |
| # Add the ship to it's proper indices. | |
| # | |
| # @param coords [Symbol] the key name of the ship in FLEET | |
| # @param coords [Array<Integer>] the row and column indices | |
| # @param direction [Symbol] the direction to check the coordinates. | |
| # @param [Integer] the index of the ship | |
| def add_ship(ship, coords, direction, index) | |
| starts, ends = coords | |
| size = FLEET[ship] | |
| size.times do |i| | |
| row, col = next_coords(direction, starts, ends, i) | |
| @grid[row * @grid_size + col] = index | |
| end | |
| @ships[index] = Ship.new(ship, size) | |
| end | |
| # Adjust the row or column index per direction | |
| # | |
| # @param direction [Symbol] the direction to point the coordinates. | |
| # @param starts [Integer] the row index | |
| # @param ends [Integer] the column index | |
| # @param index [Integer] the index to add to the coord | |
| # @return [Array<Integer>] The index adjusted coordinates | |
| def next_coords(direction, starts, ends, index) | |
| direction == :horizontal ? [starts, ends + index] : [starts + index, ends] | |
| end | |
| # Setup the column headers for the game board | |
| # @return [String] the formated column headers | |
| def column_headers | |
| @column_headers ||= " #{(1...@grid_size + 1).to_a.join(' ')}" | |
| end | |
| # Setup the row references to allow for letter and integer combinations | |
| # @return [Hash] A hash of letters with their associated index | |
| def grid_letters | |
| @grid_letters ||= ('a'...'z').each_with_index.to_h | |
| end | |
| # Return random coordinates for ship placement | |
| # | |
| # @param size [Integer] the range to generate the coordinates | |
| # @return [Array<Integer>] The Randomly generated coordinates per size | |
| def random_coords(size) | |
| [rand(@grid_size), rand(@grid_size - size + 1)] | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require_relative './player.rb' | |
| # Represents the game. Manages the flow of the game and players' turns. | |
| class Game | |
| attr_reader :player1, :player2 | |
| # Initializes a new game with two players and a specified board size. | |
| # | |
| # @param board_size [Integer] the size of the game board. | |
| def initialize(board_size = 10) | |
| @player1 = Player.new("Player 1", board_size) | |
| @player2 = Player.new("Player 2", board_size) | |
| @current_player = @player1 | |
| @opponent_player = @player2 | |
| end | |
| # Starts the game loop. Players take turns firing at each other's boards until one player wins. | |
| def play | |
| loop do | |
| take_turn(@current_player, @opponent_player) | |
| break if game_over? | |
| switch_players | |
| end | |
| puts "Game over!" | |
| end | |
| private | |
| # Executes a single turn in the game. | |
| # | |
| # @param current_player [Player] the player taking the turn. | |
| # @param opponent_player [Player] the opponent player. | |
| def take_turn(current_player, opponent_player) | |
| current_player.display_board | |
| opponent_player.display_board | |
| puts "#{current_player.name}, enter coordinates to fire:" | |
| coord = gets.chomp | |
| current_player.fire(opponent_player.board, coord) | |
| end | |
| # Switches the current player with the opponent player. | |
| def switch_players | |
| @current_player, @opponent_player = @opponent_player, @current_player | |
| end | |
| # Checks if the game is over (i.e., if one player's ships are all sunk). | |
| # | |
| # @return [Boolean] true if the game is over, otherwise false. | |
| def game_over? | |
| @player1.board.fleet_destroyed? || @player2.board.fleet_destroyed? | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require_relative './board.rb' | |
| # Represents a player in the game. Manages the player's name, game board, and firing actions. | |
| class Player | |
| attr_reader :name, :board | |
| # Initializes a new player with the specified name and creates a new game board. | |
| # | |
| # @param name [String] the name of the player. | |
| # @param board_size [Integer] the size of the game board. | |
| def initialize(name, board_size) | |
| @name = name | |
| @board = Board.new(board_size) | |
| end | |
| # Fires at the opponent's game board with the specified coordinates. | |
| # | |
| # @param opponent_board [Board] the opponent's game board. | |
| # @param coord [String] the coordinates to fire at (e.g., "A1"). | |
| def fire(opponent_board, coord) | |
| opponent_board.fire(coord) | |
| end | |
| # Displays the player's own game board. | |
| def display_board | |
| puts "#{name}'s Board:" | |
| board.display | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Represents a ship piece for the board | |
| class Ship | |
| attr_reader :name, :size | |
| attr_accessor :hits | |
| # Initializes a new ship with the specified name and size. | |
| # | |
| # @param name [Symbol] the name of the ship. | |
| # @param size [Integer] the size of the ship (number of cells it occupies). | |
| def initialize(name, size) | |
| @name = name | |
| @size = size | |
| @hits = 0 | |
| end | |
| # Registers a hit on the ship. | |
| def hit | |
| puts "HIT!" | |
| @hits += 1 | |
| puts "You sank my #{name}" if sunk? | |
| end | |
| # Checks if the ship has been sunk (all cells hit). | |
| # | |
| # @return [Boolean] true if the ship has been sunk, otherwise false. | |
| def sunk? | |
| hits >= size | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment