only one of these functions ('render' & 'render_all') prints to terminal while the other returns "nil". I can swap them around and even make them identical. One will always work and print while the other returns "nil". I can't figure out why. I need to display all faces of my tile class at end which requires render_all and i need to render it partially while game is still playing.
class Board
def initialize(size, bombs)
#size = size
explosives = bombs
#grid = Array.new(size) {Array.new(size) {Tile.new}}
while explosives != 0
potential_bomb = #grid[rand(0...size)][rand(0...size)]
if potential_bomb.bomb == false
potential_bomb.bomb = true
potential_bomb.face = "X"
explosives -= 1
end
end
end
attr_accessor :size, :grid,
def render
grid.each do |row|
row.each do |box|
if box.shown == true
print "#{box.face}"
else
print "#"
end
end
print "\n"
end
end
def render_all
grid.each do |row|
row.each do |box|
print "#{box.face}"
end
end
end
def solved
solved = true
grid.each do |row|
row.each {|pos| solved = false if pos.shown != true && pos.bomb == false}
end
return solved
end
def [](pos)
x,y = pos
grid[x][y]
end
end
attr_accessor :size, :grid,
See that trailing comma? Is bad.
FWIW: any time there's an issue that appears order-dependent always look immediately above; it's often unrelated to the order-dependent things, rather the result of screwing something up earlier.
Related
I can't figure out how to exit the game loop. I'm having a hard tried making a lose? function, I tried doing like lose?(x) that would return true when x==1 but that didn't get it to exit the run method. Here's my code for the Game class.
class Minesweeper
attr_accessor :board
def initialize
#board = Board.new
end
def run
puts "Welcome to minesweeper!"
x = nil
play_turn until win? || lose?(x)
end
def play_turn
board.render
pos, command = get_input
debugger
if !explode?(pos, command)
board.set_input(pos,command)
else
puts "You lose!"
lose?(1)
end
end
def explode?(pos, cmd)
board.grid[pos[0]][pos[1]].bomb && cmd == "reveal"
end
def get_input
pos = nil
command = nil
until pos && check_pos(pos)
puts "What position?"
pos = parse_pos(gets.chomp)
end
until command && check_command(command)
puts
puts "What would you like to do (e.g. reveal, flag... ~ else?)"
command = gets.chomp
puts
end
[pos, command]
end
#Some code here (check_pos, etc)
def lose?(x)
return true if x == 1
false
end
def win?
# board.all? {}
end
end
I had explode? in the Board class before, but for the sake of being able to end the game, moved it here. Any help is greatly appreciated!
If we expand your run method, a little, it becomes more obvious what the issue is:
def run
puts "Welcome to minesweeper!"
x = nil
until(win? || lose(x)) do
play_turn
end
end
In this method, you're creating a new variable x which is scoped to this method and has a value of nil. This variable is never set in the scope of that method, so is always nil, meaning that the check win? || lose(x) could also be written win? || lose(nil), and lose will never return true.
If you return a value from your play_turn method, you can then use that. Note that the result of the last thing executed in the method is what is returned:
def play_turn
board.render
pos, command = get_input
debugger
if !explode?(pos, command)
board.set_input(pos,command)
true
else
puts "You lose!"
false
end
end
which means your run method can then check the result:
def run
puts "Welcome to minesweeper!"
# we now know that play_turn returns true if the turn was not a loser
# (i.e. it should continue to the next loop) and false if the player
# lost, so we can use that returned value in our loop.
while(play_turn) do
if win?
puts "looks like you won!"
# if the player wins, we want to exit the loop as well. This is done
# using break
break
end
end
end
Note that this also means that you don't need a separate lose method
Aim
My program uses an algorithm to make a board, line-by-line in an array of arrays.
The final array looks like this (a standard 3x3 board with a border):
[['-----'],['|...|'],['|...|'],['|...|'],['-----']]
Problem
The failing test is as follows:
Note: view_board puts #board
expect { board.view_board }.to output(['-----','|...|','|...|','|...|','-----']).to_stdout
Outputting:
`Failure/Error: expect { board.view_board }.to output(['-----','|...|','|...|','|...|','-----']).to_stdout
expected block to output ["-----", "|...|", "|...|", "|...|", "-----"] to stdout, but output "-----\n|...|\n|...|\n|...|\n-----\n"
Diff:`
What'd different?
I'm not sure if there's a space somewhere I've missed, if I change the final array to "-----\n", then I get:
Diff:
## -2,5 +2,5 ##
|...|
|...|
|...|
------\n
+-----
Edit:
Board-Generating Code
class Board
BOARD_ROW = '-'
BOARD_COLUMM = '|'
BEGINNING_AND_END_LENGTH = 2
def initialize(board_size = 3)
#board = []
#board_top_and_bottom = []
#board_middle = []
#board_size = board_size
end
def go
set_board
end
def set_board
set_board_top_and_bottom
set_board_middle
assemble_board
view_board
end
def set_board_top_and_bottom
(#board_size + BEGINNING_AND_END_LENGTH).times do
#board_top_and_bottom.push(BOARD_ROW)
end
#board_top_and_bottom = [#board_top_and_bottom.join]
end
def set_board_middle
add_board_edge
add_board_spaces
add_board_edge
#board_middle = [#board_middle.join]
end
def add_board_spaces
#board_size.times do
#board_middle.push('.')
end
end
def add_board_edge
#board_middle << BOARD_COLUMM
end
def assemble_board
#board << #board_top_and_bottom
#board_size.times do
#board << #board_middle
end
#board << #board_top_and_bottom
end
def view_board
puts #board
end
end
From the docs of puts:
puts(obj, ...) → nil
Writes the given object(s) to ios. Writes a newline after any that do not already end with a newline sequence. [...]
That means when your call puts ['foo', 'bar'] then Ruby will actually output "foo\nbar"
To make your spec pass change the expectation to:
expect { board.view_board }.to output("-----\n|...|\n|...|\n|...|\n-----\n").to_stdout
I am building a game that is having a problem when calling a method. A monster can appear and will get a randomized weapon, and if that weapon is ranged the monster gets a one-turn setback to give the player a fighting chance. When the method monsterRangedTurnSetback is called I get the error that it is trying to find attributes in nil:NilClass. I ended up tracing it back to a genWeapon function, and that the function isn't being able to be called. Hers some code to
def monsterRangedTurnSetback(weapon)
attribs = weapon.attributes()
attribs.each do |attrib|
if attrib == "Ranged"
return 1
else
return 0
end
end
end
def genWeapon
weaponGen = rand(1..80)
if weaponGen == 1 or weaponGen == 2
weapon = GreatSword.new
hasTwoHandedWeapon = true
elsif weaponGen == (3..23)
weapon = ShortSword.new
hasTwoHandedWeapon = false
elsif weaponGen == (24..34)
weapon = ShortBow.new
hasTwoHandedWeapon = true
elsif weaponGen == (35..48)
weapon = LongBow.new
hasTwoHandedWeapon = true
elsif weaponGen == (49..64)
weapon = Dagger.new
hasTwoHandedWeapon = false
elsif weaponGen == (65..78)
weapon = HandCrossbow.new
hasTwoHandedWeapon = false
elsif weaponGen == 79 or weaponGen == 80
weapon = HeavyCrossbow.new
hasTwoHandedWeapon = true
end
return weapon
puts weapon.name
sleep 2
end
class Orc
attr_accessor :totalDamage, :totalHealth, :armorClass, :attackText, :name,
:turnSetback
def initialize
#wep = genWeapon()
#baseDamage = 7
#weapon = #wep
#turnSetback = monsterRangedTurnSetback(#weapon)
#wep = nil
#health = 5
#hasShield = shield(#weapon)
#armorClass = 6
if #hasShield == true
#armorClass += 2
end
#challengeLevel = 1
#attackText = ["Orc stabs you", "Orc slashes at you", "Orc intimidates you"]
#name = "Orc"
end
end
class ShortSword
attr_reader :attributes, :name, :attackBonus
def initialize
#attributes = ["Melee", "1Hand"]
attackBonus = 3
name = "Short Sword"
end
end
Yes the code goes in that order, and yes I know the monster class is allowing reading of non-existent variables. Any help is appreciated.
The mistake here might be that attr_reader cannot bind to local varaibles like x but only to instance variables like #x. In your code:
attr_reader :attackBonus
def initialize
# This is a local variable that will fall out of scope once the method
# finishes. It is not saved anywhere, simply thrown away.
attackBonus = 3
end
Adding an # prefix to that will make it persist and be readable.
The same thing plays out in genWeapon where local variables are set and discarded. If you need those persisted somehow you need to include them in the return. Those properties should be part of some kind of base Weapon class anyway, where you can call Dagger.new.two_handed? or Dagger.new.hands_required.
As #engineersmnky points out there's a crippling flaw in the genWeapon method where x == (1..2) will never return true for any value of x that isn't literally (1..2). What would work is (1..2).include?(x) or (1..2) === x. Since case uses === internally it makes it easy to write:
case (rand(1..80))
when 1..2
GreatSword.new
when 3..23
ShortSword.new
# ...
end
That's still really tedious. Instead write a lookup-table:
WEAPON_PROBABILITY = {
(1..2) => GreatSword,
(3..23) => ShortSword,
(24..34) => ShortBow,
(35..48) => LongBow,
(49..64) => Dagger,
(65..78) => HandCrossbow,
(79..80) => HeavyCrossbow
}.flat_map do |range, type|
range.to_a.map do |roll|
[ roll, type ]
end
end.to_h
This maps rolls to classes. Then your generator function becomes trivial:
def gen_weapon
WEAPON_PROBABILITY[rand(1..80)].new
end
Making use of Ruby's "everything is an object" principle to make look-up tables of classes dramatically simplifies things and can make the code more immediately understandable. Always try to steer your program towards defining things in terms of data instead of procedures whenever you can.
You might want to revisit how you define some of these classes. Perhaps even include the turn_delay as a method on the Weapon class. Here's how I might refactor this to inherit specialized weapons from a weapon parent class:
class Weapon
attr_reader :attributes, :name, :attack_bonus
def initialize
#attributes = []
end
def turn_delay?
#attributes.include? :ranged
end
def two_handed?
#attributes.include? :two_hand
end
end
class ShortSword < Weapon
def initialize
#attributes = %i(melee one_hand)
#attack_bonus = 3
#name = 'Short Sword'
end
end
class LongBow < Weapon
def initialize
#attributes = %i(ranged)
#attack_bonus = 10
#name = 'Long Bow'
end
end
bow = LongBow.new
puts bow.name
puts bow.turn_delay?
sword = ShortSword.new
puts sword.name
puts sword.turn_delay?
Output:
Long Bow
true
Short Sword
false
I had too much fun with this, a large number of weapons could become cumbersome to write class definitions for. Since you picked Ruby, you can embrace some meta programming and quickly whip up new weapons by using the following (requires you've defined that base Weapon class:
[
{ klass: 'BroadSword', attributes: [:melee, :two_hand], attack_bonus: 20, name: 'Broad Sword' },
{ klass: 'Dagger', attributes: [:melee, :one_hand], attack_bonus: 1, name: 'Dagger' },
{ klass: 'ShortBow', attributes: [:ranged], attack_bonus: 5, name: 'Short Bow' },
].each do |obj|
eval <<WEAPON
class #{obj[:klass]} < Weapon
def initialize
#attributes = #{obj[:attributes]}
#name = '#{obj[:name]}'
#attack_bonus = #{obj[:attack_bonus]}
end
end
WEAPON
end
Then:
bs = BroadSword.new
puts bs.name
puts bs.two_handed?
Broad Sword
true
I am building a Tic-Tac-Toe game to be played on the command line.
module TicTacToe
class Player
attr_accessor :symbol
def initialize(symbol)
#symbol = symbol
end
end
class Board
attr_reader :spaces
def initialize
#spaces = Array.new(9)
end
def to_s
output = ""
0.upto(8) do |position|
output << "#{#spaces[position] || position}"
case position % 3
when 0, 1 then output << " | "
when 2 then output << "\n-----------\n" unless position == 8
end
end
output
end
def space_available(cell, sym)
if spaces[cell].nil?
spaces[cell] = sym
else
puts "Space unavailable"
end
end
end
class Game < Board
attr_reader :player1, :player2
def initialize
play_game
end
def play_game
#player1 = Player.new("X")
#player2 = Player.new("O")
puts Board.new
#current_turn = 1
turn
end
def move(player)
while victory != true
puts "Where would you like to move?"
choice = gets.chomp.to_i
space_available(choice, player.symbol)
puts Board
#current_turn += 1
turn
end
end
def turn
#current_turn.even? ? move(#player2) : move(#player1)
end
def victory
#still working on this
end
end
end
puts TicTacToe::Game.new
The method that is to take a user's cell choice (space_available) and alter the array with their piece ('X' or 'O') is giving me an error. I can't find why my code is throwing this particular error.
The problem is that you don't call the parent constructor in your Game class, therefore #spaces is not initialized.
Your hierarchy decision is questionable, but to make it work, you can simply change the Game constructor to:
def initialize
super
play_game
end
You are calling spaces[cell]. The error is telling you that you are calling [] on nil, which means that spaces must be nil.
Perhaps you mean #spaces? Otherwise - you need to tell the program how spaces is defined and how it is initialized. A simple spaces = {} unless spaces would work
Another way of initialising your spaces variable would be to call super when you initialize the Game:
class Game < Board
attr_reader :player1, :player2
def initialize
super
play_game
end
...
Can someone tell me what keywords method does? I had to look up why my code wasn't working and saw that someone had that method, so I added it and it works. I know it sorts, but what else does it do
class Dictionary
attr_accessor :entries
def initialize
#entries = {}
end
def add(x, value = nil)
if x.is_a?(Hash)
x.each do |key, value|
#entries[key] = value
end
else
#entries[x] = nil
end
end
def keywords
#entries.keys.sort
end
def include?(x)
#entries.keys.include?(x) ? true : false
end
def find(x)
answer = {}
#entries.each do |key, value|
if key.include?(x)
answer[key] = value
end
end
answer
end
def printable
final = #entries.sort.map {|key, value| "[#{key}] \"#{value}\""}
final.join("\n")
end
end
It takes out the keys of the hash #entries, and return them as an array sorted. It does not have any side effects.