How do I continue tic-tac-toe game from saved yaml file? - ruby

I have the following tic-tac-toe game: (I'm a noob, please disregard the design of the class, the game works, that's all I care about for now.)
#a tic tac toe game
class TicTacToe
require "yaml"
attr_accessor :player1, :player2
#crates playes and a game board to play tic tac toe
def initialize()
#player1 = Player.new("Player One", "x")
#player2 = Player.new("Player Two", "o")
#game_board = Board.new
end
#prints the board
def print_board
#game_board.board.each_with_index do |row, index|
puts "#{row.join(" | ")}"
puts "---------" unless index == 2
end
puts
end
#determines whose move it is
def move
if #turn % 2 == 1
player_one_turn
else
player_two_turn
end
#turn += 1
end
def valid_move?(row, col)
if #game_board.board[row][col] == " "
return true
else
return false
end
end
#player ones turn
def player_one_turn
print_board
puts "#{#player1.name} it's your turn:"
puts "Enter a row (0-2)"
row = gets.chomp.to_i
puts "Enter a column (0-2)"
col = gets.chomp.to_i
if valid_move?(row, col)
#game_board.board[row][col] = #player1.shape
else
puts "There's already a shape at that position."
player_one_turn
end
if win?(#player1.shape)
winner(#player1.name)
#winner = true
end
end
#player two's turn
def player_two_turn
print_board
puts "#{#player2.name} it's your turn:"
puts "Enter a row (0-2)"
row = gets.chomp.to_i
puts "Enter a column (0-2)"
col = gets.chomp.to_i
if valid_move?(row, col)
#game_board.board[row][col] = #player2.shape
else
puts "There's already a shape at that position."
player_two_turn
end
if win?(#player2.shape)
winner(#player2.name)
#winner = true
end
end
def win?(shape)
if (#game_board.board[0][0] == shape) && (#game_board.board[0][1] == shape) && (#game_board.board[0][2] == shape)
return true
elsif (#game_board.board[1][0] == shape) && (#game_board.board[1][1] == shape) && (#game_board.board[1][2] == shape)
return true
elsif (#game_board.board[2][0] == shape) && (#game_board.board[2][1] == shape) && (#game_board.board[2][2] == shape)
return true
elsif (#game_board.board[0][0] == shape) && (#game_board.board[1][0] == shape) && (#game_board.board[2][0] == shape)
return true
elsif (#game_board.board[0][1] == shape) && (#game_board.board[1][1] == shape) && (#game_board.board[2][1] == shape)
return true
elsif (#game_board.board[0][2] == shape) && (#game_board.board[1][2] == shape) && (#game_board.board[2][2] == shape)
return true
elsif (#game_board.board[0][0] == shape) && (#game_board.board[1][1] == shape) && (#game_board.board[2][2] == shape)
return true
elsif (#game_board.board[0][2] == shape) && (#game_board.board[1][1] == shape) && (#game_board.board[2][0] == shape)
return true
else
return false
end
end
def draw?
if #turn > 9
print_board
puts "The game ended in a draw. :)"
#winner = true
return true
end
false
end
def winner(winner_name)
puts "#{winner_name}, YOU WIN!!!"
end
def play
#turn = 1
#winner = false
until #winner
move unless draw?
save
end
end
#a class that generates an empty board
class Board
attr_accessor :board
def initialize
#board = [[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]
end
end
#a class that assigns creates plaers and assigns them a shape "x" or "o"
class Player
attr_accessor :name, :shape
def initialize(name, shape)
#name = name
#shape = shape
end
end
def save
yaml = YAML::dump(self)
File.open("save.txt", "w"){|file| file.write(yaml)}
end
def self.load
file = File.read("save.txt")
YAML::load_file(file)
end
end
game = TicTacToe.new
game.play
I want to start playing the game, quit the program in the middle of the game and then come back and finish it later after I call TicTacToe.load. However, when I do this now, the YAML file is loaded, but program does not resume where it's supposed to.
Can someone tell me if there is a way to do what I'm trying to do?

I regionally thought that doing something like YAML::load(self) would automatically load the save state of the file I was referring to via some kind of magic. However, I have come to learn that that the way that I designed my class and the dependencies in my "play" function would not allow me to load the previous state if my file.
When loading a YAML file, one has to load the file to a variable and then manually assign the object values to values of the class. That way, the current state of the variables are pretty much being manually assigned to the instance variables of the class. For example, I could have done something like this: file = YAML.load("file_name"), then assign variables values like: #board = file.board.
Had I known this before, I would have designed my class with less dependencies so that it would be loadable in a much cleaner and more convenient way.

Related

Ruby instance attribute somehow being set to `true` instead of the intended value upon another attributes modification

Learning oop in Ruby and encountering some unexpected behavior in a class i've written. Here is my Radio class, I can change the volume attribute, but when I change the freq attribute, the band attribute is changed from fm to true. I cannot figure out why this is happening.
my file init.rb that's calling the Radio class:
require_relative 'classes/radio'
fm_radio = Radio.fm
fm_radio.volume = 10
puts fm_radio.status
#volume: 10 band: fm frequency: 88.0
fm_radio.freq = 99.0
puts fm_radio.status
#volume: 10 band: true frequency: 99.0
puts fm_radio.band
# true
my file radio.rb that contains the Radio class:
class Radio
attr_accessor :volume, :freq
attr_reader :band
def initialize(band)
#band = band
if band == 'am'
#freq = 540.0
else
#freq = 88.0
end
#volume = 1
end
def volume=(value)
return if value < 1 || value > 10
#volume = value
end
def freq=(value)
if #band = 'am' && (540.0..1600).include?(value)
#freq = value
elsif #band = 'fm' && (88.0..108.0).include?(value)
#freq = value
else
puts 'out of range'
end
end
def crank_it_up
#volume = 11
end
def status
"volume: #{#volume} band: #{#band} frequency: #{#freq}"
end
def self.am
Radio.new('am')
end
def self.fm
Radio.new('fm')
end
end
Your error is in these two lines:
if #band = 'am' && (540.0..1600).include?(value)
elsif #band = 'fm' && (88.0..108.0).include?(value)
Using a single =, you're assigning to #band what is evaluated from 'am' && (540.0..1600).include?(value), which eventually will be a boolean value.
You must use == to compare #band with another string.

Building tic tac toe game through scratch stuck while checking winning

I can play the game. Switch the player all working fine but not getting result who won the game.
def initialize_board
#count = 9
#player = PLAYER_ONE #current_player
#board = Array.new(3){ Array.new(3, " ") }
end
def play
inputs = get_inputs
return false if !inputs
update_board(inputs)
print_board
end
def switch_player
if(#player == PLAYER_ONE)
#player = PLAYER_TWO
else
#player = PLAYER_ONE
end
end
def game_over?
# #count = #count - 1
# #count <= 0
if check_winner
puts "#{#player} won "
end
end
def check_winner
WIN_COMBINATIONS.find do |indices|
binding.pry
values = #board.values_at(*indices)
values.all?('X') || values.all?('O')
end
end
Here I am getting indices [0,1,2] in all cases while debugging.
The main reason why you're not getting the winner is because your 'values = #board.values_at(*indices)' statement returns an array of arrays. And values.all?('X') || values.all?('O') checks not an 'X' or 'O' pattern but an array object. So you need to flatten an array first.
values.flatten!
Stefan already answered similar question , but his board was one-dimensional because of %w expression, you can read about it here

Building Tic Tac Toe game to learn OOP, but my game board isn't updating

These are incomplete codes, but it should at least update the game board until it fills up and error out. I don't know why its not updating the board. It definitely is registering my inputs as well as the computer's since the game does print my coordinates. Can someone please help me figure out what I'm doing wrong?
class Board
attr_reader :grid
def initialize
#grid = Array.new(3) { Array.new(3) }
#human = HumanPlayer.new
#computer = ComputerPlayer.new
#game = Game.new
end
def place_mark(pos)
if (pos[0] < 0 || pos[0] > 2) || (pos[1] < 0 || pos[1] > 2)
puts "Invalid coordinates! Please try again!"
#game.play
elsif empty?(pos)
if #game.current_player == "human"
puts "Player 1 goes: #{pos}"
#grid[pos[0]][pos[1]] = 'X'
elsif #game.current_player == "computer"
puts "Computer goes: #{pos}"
#grid[pos[0]][pos[1]] = "O"
end
if winner
puts "Congratulations! #{#game.current_player} wins!"
else
#game.switch_players!
end
else
puts "Space Taken! Please Try Again!"
#game.play
end
end
def empty?(pos)
#grid[pos[0]][pos[1]].nil?
end
def winner
#Need to set winning combinations
false
end
def over?
false #for now, it will go until all spaces are filled. Still need to set end game condition.
end
end
class HumanPlayer
def display
p Board.new.grid
end
def get_move
puts "Please enter your the quadrant you wish to place your mark."
pos = gets.chomp.scan(/[0-9]/).map!(&:to_i)
Board.new.place_mark(pos)
end
end
class ComputerPlayer
def get_move
x = rand(3).round
y = rand(3).round
Board.new.place_mark([x,y])
end
end
class Game
##turn_tracker = 0
def current_player
##turn_tracker.even? ? "human" : "computer"
end
def switch_players!
##turn_tracker += 1
play
end
def play_turn
if current_player == "human"
HumanPlayer.new.display
Board.new.place_mark(HumanPlayer.new.get_move)
elsif current_player == "computer"
ComputerPlayer.new.get_move
end
end
def play
play_turn until ##turn_tracker == 9 #Still need to set win conditions
end
end
board = Game.new
board.play
1) Game Initiates with Game#new#play
2) #play will run the game until 9 turns have passed (temporary
condition). It is passed to #play_turn
3) #play_turn figures out whose turn it is by using the
current_player.
4) It is then passed to HumanPlayer.get_move or
ComputerPlayer#get_move. These two will determine the moves of each
player and pass it to Board#place_mark.
5) #place_mark will determine if the move is valid using #empty? If
valid, it SHOULD update the grid. Then passes to the
Game#switch_players!
6)#switch_players! will change the player and passes back to #play.
7) It should iterate through this loop.
You always generate a new board, which then of course is again initalized with the starting position:
class HumanPlayer
def display
p Board.new.grid
end
...
end
As you want to learn something, I don't present you a solution.

Using a Ruby while loop to access a class method

I'm creating a simple game of rock-paper-scissors that can be played in the console. The RPS game itself is stored in one class, and the console-player version is stored in another class. The RPS game is working and the console-player is working for one game, but when I try to loop through to allow the users to play 3 games I'm getting stuck. The users still aren't allowed to play more than three games, but the console no longer outputs the winner of the game.
Here's my code so far:
class Game
attr_accessor :status
def initialize(name1, name2)
#name1 = name1
#name2 = name2
#status = []
end
def play(str1, str2)
if #status.size == 3
total = #status.inject(0) { |i, total| total += i }
if total > 0
"Game over, Player 2 wins"
elsif total < 0
"Game over, Player 1 wins"
end
else
if (str1 == 'rock' && str2 == 'paper') || (str1 == 'scissors' && str2 == 'rock') || (str1 == 'paper' && str2 == 'scissors')
#status << 1
"Player 2 wins!"
elsif (str2 == 'rock' && str1 == 'paper') || (str2 == 'scissors' && str1 == 'rock') || (str2 == 'paper' && str1 == 'scissors')
#status << -1
"Player 1 wins!"
else
"No winner"
end
end
end
end
require 'io/console'
class RPSPlayer
def start
puts "Enter player 1 name"
#player1 = gets.chomp
puts "Enter player 2 name"
#player2 = gets.chomp
#new_game = Game.new(#player1, #player2)
puts "#{#player1} challenges #{#player2} to an R-P-S showdown."
player1_prompt = "#{#player1}: what's your move?"
player2_prompt = "#{#player2}: what's your move?"
while #new_game.status.size < 3
puts player1_prompt
str1 = gets.chomp
puts player2_prompt
str2 = gets.chomp
#new_game.play(str1, str2)
end
end
end
If I delete the while loop in the console game (just prompting the players for input and inputting into #new_game) I get the winner name, but when I'm in the while loop the console just prompts the players for their move 3 times, without giving any output. Can anyone tell me why that would be?
The method #play returns a string, it does not print it. To output the winner, you need to puts the result:
while #new_game.status.size < 3
puts player1_prompt
str1 = gets.chomp
puts player2_prompt
str2 = gets.chomp
puts #new_game.play(str1, str2)
end

How do I make my tic-tac-toe program not skip a players turn?

I wrote a tic-tac-toe program. The problem is that when a user enters an invalid coordinate, the user is notified however, it skips his turn. For example: if player "x" enters valid coordinates, and player "o" enters valid coordinates, player "o" is notified but his/her turn gets skipped and player "x" goes again. How do I fix this so that player "o" gets another chance to enter in valid coordinates?
Here is my code:
class Game
def initialize
#board=Array.new
#board[1]="1 __|"
#board[2]="__"
#board[3]="|__"
#board[4]="\n2 __|"
#board[5]="__"
#board[6]="|__"
#board[7]="\n3 |"
#board[8]=" "
#board[9]="| "
#turn="o"
#win_status = false
end
def turn
#turn
end
def show_board
puts " 1 2 3"
#board.each do |i|
print i
end
puts ""
end
def set_turn #switches turns
if #turn == "x"
#turn = "o"
else #turn == "o"
#turn = "x"
end
end
def make_move
puts "Enter x coordinate"
x=gets.to_i
puts "Enter y coordinate"
y=gets.to_i
if y==1 && x==1
#board[1]="1 _"+#turn+"|"
elsif y==2 && x==1
#board[2]="_"+#turn
elsif y==3 && x==1
#board[3]="|_"+#turn
elsif y==1 && x==2
#board[4]="\n2 _"+#turn+"|"
elsif y==2 && x==2
#board[5]="_"+#turn
elsif y==3 && x==2
#board[6]="|_"+#turn
elsif y==1 && x==3
#board[7]="\n3 "+#turn+"|"
elsif y==2 && x==3
#board[8]=" "+#turn
elsif y==3 && x==3
#board[9]="| "+#turn+" \n"
else
puts "you entered an invalid coordinate"
end
end
def win_combo
return [[#board[1][4] + #board[2][1] + #board[3][2]], [#board[4][5] + #board[5][1] + #board[6][2]], [#board[7][5] + #board[8][1] + #board[9][2]],[#board[1][4] + #board[4][5] + #board[7][5]], [#board[2][1] + #board[5][1] + #board[8][1]], [#board[3][2] + #board[6][2] + #board[9][2]], [#board[1][4] + #board[5][1] + #board[9][2]], [#board[3][2] + #board[5][1] + #board[7][5]]]
end
def check_win
#if some row or column or diagonal is "xxx" or "ooo" then set #win_status = true
self.win_combo.each do |arr|
str = arr.join
if str == "xxx"
puts "X Wins!"
return true
elsif str == "ooo"
puts "O Wins!"
return true
end
end
return false
end
g = Game.new
while g.check_win != true
g.show_board
g.set_turn
g.make_move
end
end
You could call make_move again to prompt for the user to enter new coordinates.
puts "you entered an invalid coordinate"
make_move
You could call make_move from within your else clause once you know that a player
has entered an invalid move.
else
puts "you entered an invalid coordinate"
make_move
end
Look at the condition in make_move that is triggered when the user enters an invalid coordinate. After that code is triggered, make_move's execution is over, so it steps out into your g.check_win loop.
Think about your functions as pieces that can be reused. make_move is called whenever you want the current user to make a move. How can you execute this functionality again, after make_move tells the user their input was invalid?
I'll give you a hint: functions can call themselves.

Resources