A very specific Conway's Game of Life (Ruby beginner) - ruby

Looking for feedback on obvious logic errors on this, not optimizing. I keep getting weird tick counts on the end game message (ex: 1 tick turns into 11 ticks)
The largest error I can spot while running the code is on the 2nd tick, a very large amount of alive cells appear. I am too new to this to understand why, but it seems like the #alive_cells is not resetting back to 0 after each check.
Here is my entire code, its large but it should be child's play to anyone with experience.
class CellGame
def initialize
puts "How big do you want this game?"
#size = gets.chomp.to_i
#cell_grid = Array.new(#size) { Array.new(#size) }
#grid_storage = Array.new(#size) { Array.new(#size) }
#tick_count = 0
fill_grid_with_random_cells
end
def fill_grid_with_random_cells
#cell_grid.each do |row|
row.map! do |cell|
roll = rand(10)
if roll > 9
"•"
else
" "
end
end
end
check_cells_for_future_state
end
def check_for_any_alive_cells
#cell_grid.each do |row|
if row.include?("•")
check_cells_for_future_state
break
else
end_game_print_result
end
end
end
def check_cells_for_future_state
#cell_grid.each_with_index do |row, row_index|
row.each_with_index do |cell, cell_index|
#live_neighbors = 0
add_row_shift = (row_index + 1)
if add_row_shift == #size
add_row_shift = 0
end
add_cell_shift = (cell_index + 1)
if add_cell_shift == #size
add_cell_shift = 0
end
def does_this_include_alive(cell)
if cell.include?("•")
#live_neighbors +=1
end
end
top_left_cell = #cell_grid[(row_index - 1)][(cell_index - 1)]
does_this_include_alive(top_left_cell)
top_cell = #cell_grid[(row_index - 1)][(cell_index)]
does_this_include_alive(top_cell)
top_right_cell = #cell_grid[(row_index - 1)][(add_cell_shift)]
does_this_include_alive(top_right_cell)
right_cell = #cell_grid[(row_index)][(add_cell_shift)]
does_this_include_alive(right_cell)
bottom_right_cell = #cell_grid[(add_row_shift)][(add_cell_shift)]
does_this_include_alive(bottom_right_cell)
bottom_cell = #cell_grid[(add_row_shift)][(cell_index)]
does_this_include_alive(bottom_cell)
bottom_left_cell = #cell_grid[(add_row_shift)][(cell_index - 1)]
does_this_include_alive(bottom_left_cell)
left_cell = #cell_grid[(row_index)][(cell_index - 1)]
does_this_include_alive(left_cell)
if #live_neighbors == 2 || #live_neighbors == 3
#grid_storage[row_index][cell_index] = "•"
else
#grid_storage[row_index][cell_index] = " "
end
end
end
update_cell_grid
end
def update_cell_grid
#cell_grid = #grid_storage
print_cell_grid_and_counter
end
def print_cell_grid_and_counter
system"clear"
#cell_grid.each do |row|
row.each do |cell|
print cell + " "
end
print "\n"
end
#tick_count += 1
print "\n"
print "Days passed: #{#tick_count}"
sleep(0.25)
check_for_any_alive_cells
end
def end_game_print_result
print "#{#tick_count} ticks were played, end of game."
exit
end
end

I couldn't see where your code went wrong. It does have a recursive call which can easily cause strange behavior. Here is what I came up with:
class CellGame
def initialize(size)
#size = size; #archive = []
#grid = Array.new(size) { Array.new(size) { rand(3).zero? } }
end
def lives_on?(row, col)
neighborhood = (-1..1).map { |r| (-1..1).map { |c| #grid[row + r] && #grid[row + r][col + c] } }
its_alive = neighborhood[1].delete_at(1)
neighbors = neighborhood.flatten.count(true)
neighbors == 3 || neighbors == 2 && its_alive
end
def next_gen
(0...#size).map { |row| (0...#size).map { |col| lives_on?(row, col) } }
end
def play
tick = 0; incr = 1
loop do
#archive.include?(#grid) ? incr = 0 : #archive << #grid
sleep(0.5); system "clear"; #grid = next_gen
puts "tick - #{tick += incr}"
puts #grid.map { |row| row.map { |cell| cell ? '*' : ' ' }.inspect }
end
end
end
cg = CellGame.new 10
cg.play
The tick count stops but the program keeps running through the oscillator at the end.

I wanted to revisit this and confidently say I have figured it out! Here is my new solution - still super beginner focused. Hope it helps someone out.
class Game
# Uses constants for values that won't change
LIVE = "🦄"
DEAD = " "
WIDTH = 68
HEIGHT = 34
def initialize
# Sets our grid to a new empty grid (set by method below)
#grid = empty_grid
# Randomly fills our grid with live cells
#grid.each do |row|
# Map will construct our new array, we use map! to edit the #grid
row.map! do |cell|
if rand(10) == 1
LIVE # Place a live cell
else
DEAD # Place a dead cell
end
end
end
# Single line implimentation
# #grid.each {|row|row.map! {|cell|rand(10) == 1 ? LIVE : DEAD}}
loop_cells #start the cycle
end
def empty_grid
Array.new(HEIGHT) do
# Creates an array with HEIGHT number of empty arrays
Array.new(WIDTH) do
# Fills each array with a dead cell WIDTH number of times
DEAD
end
end
# Single line implimentation
# Array.new(HEIGHT){ Array.new(WIDTH) { DEAD } }
end
def print_grid # Prints our grid to the terminal
system "clear" # Clears the terminal window
# Joins cells in each row with an empty space
rows = #grid.map do |row|
row.join(" ")
end
# Print rows joined by a new line
print rows.join("\n")
# Single line implimentation
# print #grid.map{|row| row.join(" ")}.join("\n")
end
def loop_cells
print_grid # Start by printing the current grid
new_grid = empty_grid # Set an empty grid (this will be the next life cycle)
# Loop through every cell in every row
#grid.each_with_index do |row, row_index|
row.each_with_index do |cell, cell_index|
# Find the cells friends
friends = find_friends(row_index, cell_index)
# Apply life or death rules
if cell == LIVE
state = friends.size.between?(2,3)
else
state = friends.size == 3
end
# Set cell in new_grid for the next cycle
new_grid[row_index][cell_index] = state ? LIVE : DEAD
end
end
# Replace grid and start over
#grid = new_grid
start_over
end
def find_friends(row_index, cell_index)
# Ruby can reach backwards through arrays and start over at the end - but it cannot reach forwards. If we're going off the grid, start over at 0
row_fix = true if (row_index + 1) == HEIGHT
cell_fix = true if (cell_index + 1) == WIDTH
# You'll see below I will use 0 if one of these values is truthy when checking cells to the upper right, right, lower right, lower, and lower left.
# Check each neighbor, use 0 if we're reaching too far
friends = [
#grid[(row_index - 1)][(cell_index - 1)],
#grid[(row_index - 1)][(cell_index)],
#grid[(row_index - 1)][(cell_fix ? 0 : cell_index + 1)],
#grid[(row_index)][(cell_fix ? 0 : cell_index + 1)],
#grid[(row_fix ? 0 : row_index + 1)][(cell_fix ? 0 : cell_index + 1)],
#grid[(row_fix ? 0 : row_index + 1)][(cell_index)],
#grid[(row_fix ? 0 : row_index + 1)][(cell_index - 1)],
#grid[(row_index)][(cell_index - 1)]
]
# Maps live neighbors into an array, removes nil values
friends.map{|x| x if x == LIVE}.compact
end
def start_over
sleep 0.1
loop_cells
end
end
# Start game when file is run
Game.new

Related

Is there a a function in Ruby to increment an objects variable inside an array in this example?

The drop1.amount or drop2.amount of Drop object in this example will not increase after the first time theyre run through.
class Drop
attr_accessor :item, :price, :amount
end
drop1 = Drop.new()
drop1.item = "item1"
drop1.price = 2247
drop1.amount = 1
drop2 = Drop.new()
drop2.item = "item2"
drop2.price = 4401
drop2.amount = 60
x = 0
array = []
while x < 10
rand1 = rand(2)
if rand1 == 0
if array.include? drop1.item
drop1.amount = drop1.amount + 1
else
array << drop1.item
array << drop1.amount
end
elsif rand1 == 1
if array.include? drop2.item
drop2.amount = drop2.amount + 60
else
array << drop2.item
array << drop2.amount
end
end
x += 1
end
puts array.to_s.gsub('"', '').gsub('[', '').gsub(']', '')
puts ""
puts drop1.amount
puts drop2.amount
Example of expected output:
item2, 240, item1, 6
6
240
Example of actual result:
item2, 60, item1, 1
6
240
I am looking for a change to the else statements in lines 24 and 32. The purpose of the this program is to create an array of items that will display the "item" one time and an incremented "amount" when a drop is randomly chosen multiple times.
array << drop1.amount does not make an alias of drop1.amount, it makes a one-time copy of the number value contained in drop1.amount. When you update drop1.amount, the copy in array is unchanged. Instead, put a reference to the object onto the result array or update the result array value directly (depending on whether you want to modify the original or not).
For example we can stick to the existing design with something like:
# ...
if array.include? drop1.item
array[array.index(drop1.item)+1] += 1
drop1.amount += 1 # optionally update the original (less ideal than an alias)
else
array << drop1.item
array << drop1.amount
end
# ...
if array.include? drop2.item
array[array.index(drop2.item)+1] += 60
drop2.amount += 60
else
array << drop2.item
array << drop2.amount
end
# ...
While this emits the expected output, this sort of awkward searching and repeated code suggests that there are fundamental design flaws.
I'd write the program something like:
Drop = Struct.new(:item, :price, :amount)
drops = [
Drop.new("item1", 2247, 1),
Drop.new("item2", 4401, 60)
]
increment_amounts = drops.map {|e| e.amount}
result = [nil] * drops.size
10.times do
choice = rand(drops.size)
if result[choice]
result[choice].amount += increment_amounts[choice]
else
result[choice] = drops[choice]
end
end
puts result.compact
.shuffle
.flat_map {|e| [e.item, e.amount]}
.to_s
.gsub(/["\[\]]/, "")
puts "\n" + drops.map {|e| e.amount}.join("\n")
Suggestions which the above version illustrates:
Use a struct instead of a class for such a simple type and set its properties using the constructor rather than accessors.
Use arrays instead of thing1, thing2, etc. This makes it a lot easier to make random choices (among other things). Note that the above version is expandable if you later decide to add more drops. After adding a third or 100 drops (along with corresponding increment amounts), everything just works.
Prefer a clear name like result instead of a generic name like array.
x = 0 ... while x < 10 ... x += 1 is clearer as 10.times.
Pass a regex to gsub instead of a string to avoid chaining multiple calls.
I don't know if I undestand properly the logic, but consider using a Hash instead of an Array:
h = {}
10.times do |x|
rand1 = rand(2)
if rand1 == 0
if h.has_key? drop1.item
drop1.amount += 1
h[drop1.item] = drop1.amount
else
h[drop1.item] = drop1.amount
end
elsif rand1 == 1
if h.has_key? drop2.item
drop2.amount += 60
h[drop2.item] = drop2.amount
else
h[drop2.item] = drop2.amount
end
end
end
For checking the result:
p h
p drop1.amount
p drop2.amount
Other option, if it is viable for you, let the class do the job defining it like this:
class Drop
attr_accessor :item, :price, :amount
def initialize(item:'no_name', price: 0, amount: 0)
#item = item
#price = price
#amount = amount
#counts = 0
#increment = amount
end
def count!
#counts += 1
#amount += #increment if #counts > 1
end
end
Then store the instances in an array:
drops = []
drops << Drop.new(item: 'item1', price: 2247, amount: 1)
drops << Drop.new(item: 'item2', price: 4401, amount: 60)
Run the random picking sampling the drops array:
10.times do |x|
drops.sample.count!
end
Check the result:
drops.each do |drop|
puts "#{drop.item} - #{drop.amount} - #{drop.price}"
end
You can also define a reset method which restores the amount and the counts to the original value:
def reset
#amount = #increment
#count = 0
end

Player vs computer [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
Created this connect 4 game following a guide online but the guide didn't explain how to make it computer vs player it only explained how to make it player vs player. I've tried adding a computer variable that generated a random number 1..8 for the columns but it wasn't working was just sitting there asking player two to pick a location.
Here's the player code below how do i implement player vs computer ?
class Board
attr_accessor :board
attr_reader :turn, :identifier
def initialize
#board = [
[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,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,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0]
]
#turn = 0
#identifier = 1
end
def choose_column(col)
7.downto(0) do |i|
if board[i][col-1] == 0
board[i][col-1] = identifier
break
end
end
false
end
def win?
return true if check_rows?
return true if check_columns?
return true if check_diagonal?
end
def check_validity(sum,num)
if num == 1
sum = 0 if sum < 0
sum += 1
elsif num == -1
sum = 0 if sum > 0
sum -= 1
else
sum = 0
end
end
def check_rows?
8.times do |i|
board[i].inject(0) do |sum, num|
sum = check_validity(sum,num)
return true if sum.abs == 4
sum
end
end
false
end
def check_columns?
8.times do |i|
column = []
board.each { |row| column.push(row[i]) }
column.inject(0) do |sum, num|
sum = check_validity(sum,num)
return true if sum.abs == 4
sum
end
end
false
end
def check_diagonal?
board[0..2].each_with_index do |row, row_i|
row.each_with_index do |column, col_i|
if col_i <=3
sum = board[row_i][col_i]
1.upto(3) { |i| sum += board[row_i+i][col_i+i] }
return true if sum.abs == 4
end
if col_i >= 3
sum = board[row_i][col_i]
1.upto(3) { |i| sum += board[row_i+i][col_i-i] }
return true if sum.abs == 4
end
end
end
false
end
def new_game
#turn = 1
end
def game_flow
new_game
while turn < 43
assign_player
player = #identifier == 1 ? 1 : 2
puts draw_board
print "Round #{#turn}. Player #{player}'s turn. Choose column (1-8): "
input = gets.chomp
while !validate_input(input) || check_full?(input)
print "Invalid Pick. Try again: "
input = gets.chomp
end
input = validate_input(input)
choose_column(input)
if win?
puts draw_board
puts "Congratulations. Player #{player} won"
break
end
#turn += 1
end
puts "Draw!" unless win?
return true
end
def assign_player
#identifier = #turn.odd? ? 1 : -1
end
def validate_input(input)
return input !~ /^[1-8]$/ ? false : input.to_i
end
def check_full?(column)
column = column.to_i
column -= 1
return board[0][column] != 0
end
def draw_board
print "1 2 3 4 5 6 7 8\n"
rep = { 0 => ".", 1 => "O", -1 => "X" }
board.each do |row|
print "#{rep[row[0]]} #{rep[row[1]]} #{rep[row[2]]} #{rep[row[3]]} #{rep[row[4]]} #{rep[row[5]]} #{rep[row[6]]} #{rep[row[7]]}\n"
end
print " "
end
end
board = Board.new
board.game_flow
Like Ken White says, if you want the computer to select a choice, you need to detect when it is the computer's turn. It looks like in your example this is encoded in the #player variable. You can have a simple if branch which only prompts for input when it is the human player's turn. Everything else could remain the same, but you would update your game_flow method to consider this other case:
if player == 1
print "Round #{#turn}. Player #{player}'s turn. Choose column (1-8): "
input = gets.chomp
while !validate_input(input) || check_full?(input)
print "Invalid Pick. Try again: "
input = gets.chomp
end
input = validate_input(input)
else
input = rand(1..8)
end
Hope this helps.

How to correctly write a ruby method

i'm trying ruby. I want to make a program which can continue the sequence.
1
11
21
1211
111221
(which next line is a calculated each number of previous line)
For example last line is(see previous) one of 1, one of 2, and two of 1.
I make a code and it works fine:
5.times do
result_seq = []
count = 1
puts initial_seq.join
initial_seq.size.times do
if (value = initial_seq.shift) == initial_seq.first
count += 1
else
result_seq << count << value
count = 1
end
end
initial_seq = result_seq
end
But now I want to write a simple method called Next
I want to make:
sec = Sequence.new(1)
sec.next -> will return 11
sec.next.next -> will return 21
sec.next.next.next -> will return 1211
How can i write it correctly using my code?
UPD
I wrote the tests for it:
require "spec_helper"
require "sequence"
describe Sequence do
let(:sequence) { Sequence.new("1") }
describe "to_s" do
it "return initial value" do
expect(sequence.to_s).to eql "1"
end
end
describe "next" do
it "generate next state" do
expect(sequence.next.to_s).to eql "11"
end
it "return Sequence instance" do
expect(sequence.next).to be_an_instance_of(Sequence)
end
it "generate next state 2 times" do
expect(sequence.next.next.to_s).to eql "21"
end
it "generate next state 3 times" do
expect(sequence.next.next.next.to_s).to eql "1211"
end
end
end
class Sequence
attr_reader :initial_seq
def initialize(initial_seq = [])
#initial_seq = initial_seq
print_next
end
def print_next
result_seq = []
count = 1
puts initial_seq.join
initial_seq.size.times do
if (value = initial_seq.shift) == initial_seq.first
count += 1
else
result_seq << count << value
count = 1
end
end
#initial_seq = result_seq
self #<===== The most important part for being able to chain `print_next`
end
end
Usage:
Sequence.new([1]).print_next.print_next.print_next.print_next
1
11
21
1211
111221
edit
If you want to initialize it with integer argument, not array:
def initialize(number)
#initial_seq = [number]
print_next
end
Sequence.new(1).print_next.print_next
1
11
21
Or, if you do not want initialize to accept an argument (assuming, it will always start with 1):
def initialize
#initial_seq = [1]
print_next
end
Sequence.new.print_next.print_next
1
11
21
Ruby provides Enumerators, which behave almost like in OP. Leaving the original code almost unaltered:
seq = Enumerator.new do |yielder|
initial_seq = [1]
loop do #endless loop, but don't worry, its lazy
result_seq = []
count = 1
yielder << initial_seq.join
initial_seq.size.times do
if (value = initial_seq.shift) == initial_seq.first
count += 1
else
result_seq << count << value
count = 1
end
end
initial_seq = result_seq
end
end
5.times{puts seq.next}
puts seq.next

Merging Ranges using Sets - Error - Stack level too deep (SystemStackError)

I have a number of ranges that I want merge together if they overlap. The way I’m currently doing this is by using Sets.
This is working. However, when I attempt the same code with a larger ranges as follows, I get a `stack level too deep (SystemStackError).
require 'set'
ranges = [Range.new(73, 856), Range.new(82, 1145), Range.new(116, 2914), Range.new(3203, 3241)]
set = Set.new
ranges.each { |r| set << r.to_set }
set.flatten!
sets_subsets = set.divide { |i, j| (i - j).abs == 1 } # this line causes the error
puts sets_subsets
The line that is failing is taken directly from the Ruby Set Documentation.
I would appreciate it if anyone could suggest a fix or an alternative that works for the above example
EDIT
I have put the full code I’m using here:
Basically it is used to add html tags to an amino acid sequence according to some features.
require 'set'
def calculate_formatting_classes(hsps, signalp)
merged_hsps = merge_ranges(hsps)
sp = format_signalp(merged_hsps, signalp)
hsp_class = (merged_hsps - sp[1]) - sp[0]
rank_format_positions(sp, hsp_class)
end
def merge_ranges(ranges)
set = Set.new
ranges.each { |r| set << r.to_set }
set.flatten
end
def format_signalp(merged_hsps, sp)
sp_class = sp - merged_hsps
sp_hsp_class = sp & merged_hsps # overlap regions between sp & merged_hsp
[sp_class, sp_hsp_class]
end
def rank_format_positions(sp, hsp_class)
results = []
results += sets_to_hash(sp[0], 'sp')
results += sets_to_hash(sp[1], 'sphsp')
results += sets_to_hash(hsp_class, 'hsp')
results.sort_by { |s| s[:pos] }
end
def sets_to_hash(set = nil, cl)
return nil if set.nil?
hashes = []
merged_set = set.divide { |i, j| (i - j).abs == 1 }
merged_set.each do |s|
hashes << { pos: s.min.to_i - 1, insert: "<span class=#{cl}>" }
hashes << { pos: s.max.to_i - 0.1, insert: '</span>' } # for ordering
end
hashes
end
working_hsp = [Range.new(7, 136), Range.new(143, 178)]
not_working_hsp = [Range.new(73, 856), Range.new(82, 1145),
Range.new(116, 2914), Range.new(3203, 3241)]
sp = Range.new(1, 20).to_set
# working
results = calculate_formatting_classes(working_hsp, sp)
# Not Working
# results = calculate_formatting_classes(not_working_hsp, sp)
puts results
Here is one way to do this:
ranges = [Range.new(73, 856), Range.new(82, 1145),
Range.new(116, 2914), Range.new(3203, 3241)]
ranges.size.times do
ranges = ranges.sort_by(&:begin)
t = ranges.each_cons(2).to_a
t.each do |r1, r2|
if (r2.cover? r1.begin) || (r2.cover? r1.end) ||
(r1.cover? r2.begin) || (r1.cover? r2.end)
ranges << Range.new([r1.begin, r2.begin].min, [r1.end, r2.end].max)
ranges.delete(r1)
ranges.delete(r2)
t.delete [r1,r2]
end
end
end
p ranges
#=> [73..2914, 3203..3241]
The other answers aren't bad, but I prefer a simple recursive approach:
def merge_ranges(*ranges)
range, *rest = ranges
return if range.nil?
# Find the index of the first range in `rest` that overlaps this one
other_idx = rest.find_index do |other|
range.cover?(other.begin) || other.cover?(range.begin)
end
if other_idx
# An overlapping range was found; remove it from `rest` and merge
# it with this one
other = rest.slice!(other_idx)
merged = ([range.begin, other.begin].min)..([range.end, other.end].max)
# Try again with the merged range and the remaining `rest`
merge_ranges(merged, *rest)
else
# No overlapping range was found; move on
[ range, *merge_ranges(*rest) ]
end
end
Note: This code assumes each range is ascending (e.g. 10..5 will break it).
Usage:
ranges = [ 73..856, 82..1145, 116..2914, 3203..3241 ]
p merge_ranges(*ranges)
# => [73..2914, 3203..3241]
ranges = [ 0..10, 5..20, 30..50, 45..80, 50..90, 100..101, 101..200 ]
p merge_ranges(*ranges)
# => [0..20, 30..90, 100..200]
I believe your resulting set has too many items (2881) to be used with divide, which if I understood correctly, would require 2881^2881 iterations, which is such a big number (8,7927981983090337174360463368808e+9966) that running it would take nearly forever even if you didn't get stack level too deep error.
Without using sets, you can use this code to merge the ranges:
module RangeMerger
def merge(range_b)
if cover?(range_b.first) && cover?(range_b.last)
self
elsif cover?(range_b.first)
self.class.new(first, range_b.last)
elsif cover?(range_b.last)
self.class.new(range_b.first, last)
else
nil # Unmergable
end
end
end
module ArrayRangePusher
def <<(item)
if item.kind_of?(Range)
item.extend RangeMerger
each_with_index do |own_item, idx|
own_item.extend RangeMerger
if new_range = own_item.merge(item)
self[idx] = new_range
return self
end
end
end
super
end
end
ranges = [Range.new(73, 856), Range.new(82, 1145), Range.new(116, 2914), Range.new(3203, 3241)]
new_ranges = Array.new
new_ranges.extend ArrayRangePusher
ranges.each do |range|
new_ranges << range
end
puts ranges.inspect
puts new_ranges.inspect
This will output:
[73..856, 82..1145, 116..2914, 3203..3241]
[73..2914, 3203..3241]
which I believe is the intended output for your original problem. It's a bit ugly, but I'm a bit rusty at the moment.
Edit: I don't think this has anything to do with your original problem before the edits which was about merging ranges.

TicTacToe invalid move method issue

Below is my take for the TicTacToe game. So far it works, but it's not perfect. I'm having an issue with one of the methods - Game#invalid_move_check? after the game asked you "where to:"once choose the new destination , the game change the symbol like if it was a new turn .in fact not , suppose to keep the same player symbol until next turn.
P.S the code probably need some refactoring. I'm in a learning phase.
class Game
def initialize(symbol)
#board = Array.new(3){Array.new(3)}
# [0,1,2]
# [3,4,5]
# [6,7,8]
#symbol = ["X", "O"]
end
WINNING_COMBO = [
# Horizontal wins:
[0, 1, 2], [3, 4, 5], [6, 7, 8],
# Vertical wins:
[0, 3, 6], [1, 4, 7], [2, 5, 8],
# Diagonal wins:
[0, 4, 8], [2, 4, 6]
]
def create_players
# create both players
#names = []
print "Please enter the name of the first player: "
#player_1 =gets.chomp
#names << #player_1
print "Please enter the name of the second player: "
#player_2 = gets.chomp
#names << #player_2
puts "\n"
puts"welcome #{#player_1.upcase} and #{#player_2.upcase}"
puts"------------------------------------------------"
puts"\n"
puts "Randomizing who'll start..."
puts"\n"
# assign player by calling the player_assigment function that will determine who will start first
player_assigment
puts"\n"
end
def player_assigment
# merge the names array and symbol array
# with the zip method and return a nested array with player and symbol.
#choice = #names.zip(#symbol)
# iterate over the choice nested array and
# print out each player and their assigned symbol
#choice.each do |player, symbol|
puts "#{player.upcase} will use #{symbol}"
end
end
def current
#current = #names.first
#current
end
def switch_turn
#current = #names.last
#current
end
def first_turn
current
puts "#{#current.upcase} turn"
#marker = #symbol.first
make_move(#marker)
end
def next_turn
switch_turn
puts "#{#current.upcase} turn"
#marker = #symbol.last
make_move(#marker)
end
def check_win?(first_arr, second_arr)
WINNING_COMBO.select do |item|
if
item == first_arr
puts"#{#player_1} won!!"
elsif
item == second_arr
puts "#{#player_2} won!!"
end
end
end
def mapping(move, marker)
case move
when 0
arr_index = 0
index = 0
invalid_move_check?(arr_index,index)
#board[0][0] = marker
when 1
arr_index = 0
index = 1
invalid_move_check?(arr_index,index)
#board[0][1] = marker
when 2
arr_index = 0
index = 2
invalid_move_check?(arr_index,index)
#board[0][2] = marker
when 3
arr_index = 1
index = 0
invalid_move_check?(arr_index,index)
#board[1][0] = marker
when 4
arr_index = 1
index = 1
invalid_move_check?(arr_index,index)
#board[1][1] = marker
when 5
arr_index = 1
index = 2
invalid_move_check?(arr_index,index)
#board[1][2] = marker
when 6
arr_index = 2
index = 0
invalid_move_check?(arr_index,index)
#board[2][0] = marker
when 7
arr_index = 2
index = 1
invalid_move_check?(arr_index,index)
#board[2][1] = marker
when 8
arr_index = 2
index = 2
invalid_move_check?(arr_index,index)
#board[2][2] = marker
end
end
def invalid
puts"move invalid"
end
def invalid_move_check?(arr_index, index)
array = #board
if array[arr_index][index] == "X" ||
array[arr_index][index] == "O"
invalid
puts "Where to :"
#move = gets.chomp.to_i
mapping(#move,#marker)
end
end
def make_move(marker)
# after each turn the make_move method will called to place move on the board
puts "Where to :"
#move = gets.chomp.to_i
mapping(#move,#marker)
print_board
end
# display board in a matrix format
def print_board
#board.each_slice(1) { |a| p a }
puts"\n"
end
def instructions
puts "Instructions :Enter your first move by entering a number 1-9"
puts "corresponding to the grid on the bottom and press enter"
puts"\n"
puts "0 | 1 | 2 ",
"----------",
"3 | 4 | 5 ",
"----------",
"6 | 7 | 8 "
print"\n"
end
def self.start(symbol)
# start a new game
new_game =Game.new(symbol)
# create players
new_game.create_players
new_game.instructions
new_game.print_board
# Checking wining combo for matching patter if none
while new_game.check_win?(#move_first, #move_second) do
new_game.first_turn
# the player switch turn
new_game.next_turn
end
end
loop do
puts"------------------------------------------------"
puts" Welcome to tictactoe ".upcase
puts"------------------------------------------------"
print"\n"
Game.start(#symbol)
end
end
This should do the trick:
#will return true or false to check validity of move
def invalid_move_check?(arr_index, index)
array = #board
if array[arr_index][index] == "X" ||
array[arr_index][index] == "O"
invalid
puts "Where to :"
#move = gets.chomp.to_i
mapping(#move,#marker)
return true
end
return false
end
def mapping(move, marker)
case move
...
when 0
arr_index = 0
index = 0
unless invalid_move_check?(arr_index,index) #change all cases
#board[0][0] = marker #to have the assignment of board
#only if the move is valid
end
...
end
end
The reason of your bug is that the assignment happens even if the move is invalid.
This is just a band-aid solution to your current problem, as for refactoring ,there are things that can be done to optimize your code and make it better :) But you still have to fix first your 'ending'. Refactoring your own code would be a very good practice. I wish you a joyful ruby journey
!
After scratching the previous code and rethink the code i 've come up with this solution , not elegant as i will want it to be but it works .i move the symbol assignment in the if/else block code and therefore when a move is invalid next move will use the symbol of the current player
def mapping(move, symbol)
case move
when 0
if #board[0][0]=="X" || #board[0][0] == "O"
invalid
make_move
else
#board[0][0] = symbol
track_move(#move)
print_board
end
...
end
end

Resources