Reputation: 9
I'm building a simple tic-tac-toe game and I've totally hit a wall with my grid.
I'd like to "puts @grid" including player input every time players have made their choice. How can I achieve this? I'm almost a total beginner with Ruby and have never build any games before. Any help is greatly appreciated, thanks in advance!
I tried making two different grids (grid and grid_with_markers) but couldn't figure out where to go from there. Looking back, having two grids also just seemed like a bad idea. I also tried having a Hash (marker_positions), but that seemed overly complicated compared to an Array.
Here is the grid
def initialize
@possible_choice = [1,2,3,4,5,6,7,8,9]
@marker_positions = [1,2,3,4,5,6,7,8,9]
@grid = "
|----|----|----|
| #{@marker_positions[0]} | #{@marker_positions[1]} | #{@marker_positions[2]} |
|----|----|----|
| #{@marker_positions[3]} | #{@marker_positions[4]} | #{@marker_positions[5]} |
|----|----|----|
| #{@marker_positions[6]} | #{@marker_positions[7]} | #{@marker_positions[8]} |
|----|----|----|
"
end
I'd like to use a method add_markers to show the grid with user input. So when a player selects number 1 @marker_positions[0] would be replaced as "X" (or "O"). Number 4 would replace @marker_positions[3] etc.
Edit: I realised the title of this post was misleading since I actually want to replace elements of @marker_positions Array with Strings ("X" or "O"). But the replaced element is selected based on user input, in other words, player_one and player_two Arrays.
def add_markers
puts @grid
end
Here's the player_one_turn method
def player_one_turn()
puts "Player One, make your choice:"
p @possible_choice
add_markers
@@player_one << @possible_choice.delete(gets.chomp.to_i)
p "Player One has chosen: #{@@player_one}"
end
And here's my entire tictactoe.rb file.
class Grid
WINNING_COMBOS = [
[1,2,3],[4,5,6],[7,8,9],
[1,4,7],[2,5,8],[3,6,9],
[1,5,9],[3,5,7]
]
attr_accessor :possible_choice
attr_accessor :marker_positions
attr_accessor :grid
def initialize
@possible_choice = [1,2,3,4,5,6,7,8,9]
@marker_positions = [1,2,3,4,5,6,7,8,9]
@grid = "
|----|----|----|
| #{@marker_positions[0]} | #{@marker_positions[1]} | #{@marker_positions[2]} |
|----|----|----|
| #{@marker_positions[3]} | #{@marker_positions[4]} | #{@marker_positions[5]} |
|----|----|----|
| #{@marker_positions[6]} | #{@marker_positions[7]} | #{@marker_positions[8]} |
|----|----|----|
"
end
def add_markers
puts @grid
end
end
class Game < Grid
@@player_one = Array.new
@@player_two = Array.new
def game
puts
puts "*** This is a tic-tac-toe game for two human players. ***"
puts
loop do
player_one_turn()
puts
if has_won?(@@player_one)
puts "The game has ended. Player One has won!"
puts
return
end
break if @@player_one.length == 5 || @@player_one.include?(nil)
player_two_turn()
puts
if has_won?(@@player_two)
puts "The game has ended. Player Two has won!"
puts
return
end
end
end
def player_one_turn()
puts "Player One, make your choice:"
p @possible_choice
add_markers
@@player_one << @possible_choice.delete(gets.chomp.to_i)
p "Player One has chosen: #{@@player_one}"
end
def player_two_turn()
puts "Player Two, make your choice:"
p @possible_choice
add_markers
@@player_two << @possible_choice.delete(gets.chomp.to_i)
p "Player Two has chosen: #{@@player_two}"
end
def has_won?(player)
WINNING_COMBOS.any? { |combo| (player & combo).size == combo.size}
end
end
new_game = Game.new
new_game.game
(I know it isn't very clean. Thank you for taking time to read all the way down here.)
Upvotes: 0
Views: 375
Reputation: 114158
So when a player selects number 1 @marker_positions[0] would be replaced as "X"
You just have to read the user input: (example shows result for player entering 1)
input = gets.to_i
#=> 1
and replace the corresponding array element: (we have to subtract 1 because array is zero-based)
@marker_positions[input - 1] = 'X'
Using the array values within the grid string is another issue. The interpolations via #{...}
happens when creating the string, just before it is assigned to @grid
in initialize
. This means the string is not being updated when the interpolated value changes afterwards – it just stays the same unless you interpolate it again:
x = 'foo'
@grid = "hello #{x}"
#=> "Hello foo"
x = 'bar'
@grid
#=> "Hello foo" <- doesn't change
@grid = "hello #{x}"
#=> "Hello bar"
To get a "fresh" grid every time you call add_markers
you could simply move the string interpolation code from initialize
into that method: (instead of "..."
, I'm using <<~EOD ... EOD
, a "squiggly" heredoc which ignores leading whitespace)
class Grid
attr_accessor :marker_positions
def initialize
@marker_positions = [1, 2, 3, 4, 5, 6, 7, 8, 9]
end
def add_markers
puts <<~EOD
|----|----|----|
| #{@marker_positions[0]} | #{@marker_positions[1]} | #{@marker_positions[2]} |
|----|----|----|
| #{@marker_positions[3]} | #{@marker_positions[4]} | #{@marker_positions[5]} |
|----|----|----|
| #{@marker_positions[6]} | #{@marker_positions[7]} | #{@marker_positions[8]} |
|----|----|----|
EOD
end
end
This would result in: (the #
are not part of the actual output)
grid = Grid.new
grid.add_markers
#|----|----|----|
#| 1 | 2 | 3 |
#|----|----|----|
#| 4 | 5 | 6 |
#|----|----|----|
#| 7 | 8 | 9 |
#|----|----|----|
grid.marker_positions[0] = 'X'
grid.marker_positions[4] = 'X'
grid.marker_positions[8] = 'X'
grid.add_markers
#|----|----|----|
#| X | 2 | 3 |
#|----|----|----|
#| 4 | X | 6 |
#|----|----|----|
#| 7 | 8 | X |
#|----|----|----|
Another – maybe cleaner – option is to define a template with placeholders that are replaced by their actual (or current) values when rendering the template:
class Grid
TEMPLATE = <<~EOD.freeze
┌───┬───┬───┐
│ 1 │ 2 │ 3 │
├───┼───┼───┤
│ 4 │ 5 │ 6 │
├───┼───┼───┤
│ 7 │ 8 │ 9 │
└───┴───┴───┘
EOD
attr_accessor :marker_positions
def initialize
@marker_positions = [1, 2, 3, 4, 5, 6, 7, 8, 9]
end
def render
puts TEMPLATE.gsub(/[1-9]/) { |d| @marker_positions[d.to_i - 1] }
end
end
gsub
scans the template string for digits 1
to 9
and replaces each occurrence d
with the block's result, which simply picks the corresponding value from our array.
Usage:
grid = Grid.new
grid.render
#┌───┬───┬───┐
#│ 1 │ 2 │ 3 │
#├───┼───┼───┤
#│ 4 │ 5 │ 6 │
#├───┼───┼───┤
#│ 7 │ 8 │ 9 │
#└───┴───┴───┘
grid.marker_positions[0] = 'X'
grid.marker_positions[4] = 'X'
grid.marker_positions[8] = 'X'
grid.render
#┌───┬───┬───┐
#│ X │ 2 │ 3 │
#├───┼───┼───┤
#│ 4 │ X │ 6 │
#├───┼───┼───┤
#│ 7 │ 8 │ X │
#└───┴───┴───┘
The Unicode box-drawing characters are just for demonstration purposes, it will work just fine with ASCII characters.
Based on the above, a very basic game loop could look like this:
grid = Grid.new
%w[X O].cycle do |marker|
grid.render
print "Player #{marker}: "
input = gets.to_i
grid.marker_positions[input - 1] = marker
# TODO: break if player wins
end
The code cycle
s between "X"
and "O"
and for each iteration:
marker_positions
What's missing is an additional step to break
the loop if a player wins. And probably some logic to validate the input (prevent a player from overwriting a spot already taken, check that input is within 1-9 etc).
Hope this helps.
A last note: assuming that users enter their input via a computer keyboard, you might want to flip the grid vertically to match the layout of a numeric keypad.
Upvotes: 1