A simple Rock Paper Scissors game, demonstrating a while loop and how easy user input is in Ruby.
While loops in Ruby work much like any other language, it will run while the condition given to the while loop is true. One thing to note is that in Ruby, features such as loops (while, for, each, etc.), if/else statements, and more need an end keyword to close them off.
User input in Ruby has been made easy, simply use gets.chomp and store it in a variable, no need for scanners or additional code.
With your newly acquired input, you can proceed to use it as intended, which in the case of our Rock Paper Scissors program, we use the first user input as their choice between rock, paper, or scissors.
Rock Paper Scissors would be pointless without an oppenent, so our program pits you against Ruby's RNG. Ruby tries to make coding simple. All you need to get a random number in range with Ruby is rand(1..3). What this does is picks a random number using the minimum range on the left, maximum range on the right, and the .. in between to symbolize "to" as in 1 to 3.
With both the users input and Ruby's RNG, we can now run through conditionals to figure out who won or if it was a tie. Keep in mind that the conditional blocks need an end after the final statement of that block, only one end is needed for a whole block of conditionals.
You will notice we are also keeping track of the user's wins, computer's wins, and the ties. These will be used later.
The program will now take in a second user input for whether they want to play another round or not. Just like before, use the gets.chomp to get this input.
If the user wanted to play again, we use the next keyword to exit the conditional block and continue the loop over again.
If the user wants to end the game, we then display those wins/ties we were keeping track of throughout the program and end the loop exiting the program.
The program begins by asking the user for their choice between rock, paper, and scissors. In the example output, I chose 1 for rock. The computer chose 2 for paper, resulting in my loss. I decide to play one more time and choose 3 for scissors this time. The computer chose 2 for paper again resulting in my win. I respond with "n" to end the program and it displays the wins/losses/ties.
A short but complex program showing off Ruby's Multi-Threading capabilities, flexibility with compiling, as well as the each loop.
The BEGIN and END statements are different from each other in Ruby. If there is more than one BEGIN statement in a program, they are executed in the order. If there is more than one END statement, they are executed in the reverse of the order. the first END one is executed last.
This provides the programmer with much more flexibility than other languages, being able to delay blocks of code until the end or set blocks of code to execute at the start.
Multi-Threading is a very powerful tool to programmers, and Ruby caters to programmers. Threads can easily be created with Thread.new as seen in the sleepsort method. Using Ruby's each loop, we create a thread for each item in the argument list, sort the list, and join the threads at the end.
Another special feature of Ruby, is methods do not need a return type or a specified return object, methods will return the last object evaluated, or if you would like to specify a specific object to be returned, you can do that too. By having rlst at the end of the method, that gets returned.
As mentioned earlier, the BEGIN block at the end is actually executed at the start of the program, while the END block from the beginning of the program is executed after.
From the output, you will see that the BEGIN block got printed first even though it was placed at the end of the program. Then it sorts the array, and prints the END block at the end even though it was placed at the beginning of the program.
A longer and more complex example of Ruby that demonstrates how to imitate interfaces and enums, initialize and access 2D arrays, and work with third-party tools to work with GUI as Ruby does not have native GUI capability like other languages.
Starting off with the "interface" for the TicTacToe project. Ruby does not have built in interface functionality like we are used to with Java. However, you can imitate the functionality of interfaces with Ruby's modules with the module definition. The module TicTacToeModule contains the methods and imitation "enums" (more on those shortly), to be implemented by the logic class that will need to "include" this module class and basically overwrite the methods, much like the other languages.
Ruby also does not have enumerators, but you can also imitate enum functionality with modules. The modules BoardValue and State contain the custom enum variables for the values of the board spots and state of the game.
As for the methods, since this is an "interface", we leave these unimplemented. Nothing needs to be in them, but it might be useful to raise some errors for if they never get implemented when they should be in another class.
The functionality of this module class almost replicates that of interfaces from the languages we are used to.
A seperate class contains the logic for the TicTacToe game. This is the class that will be implementing our TicTacToeModule. To do this, at the top of the file, use require_relative 'class name' to connect it to this class. To be able to use it though, you also need to include the class as well. Doing both of those steps will allow the current file to gain access to the methods and enums we defined in the module class.
For this games logic, a x/y point system is used. In langauges like Java, this is a built in class, in Ruby there is no built in Point functionality, so we create it here, as well as a to string method for printing these points later.
In Ruby, it is customary to name your constructors "initialize" which will allow Ruby to call this constructor when you use .new on the object class.
In Ruby, arrays are initialized a bit differently, especially 2D arrays. To initialize a 1D array you can simply do Array.new(size), not too bad. 2D arrays can be a bit more confusing. You want to initialize an array inside the intialization of another array with Array.new(size) { Array.new(size)}.
Here, we are setting each spot in the 3x3 grid array to OPEN and resetting the array of moves
The method spotFill handles the logic to check if the spot the player chooses is a valid move as well as checking if any win/game over conditions have been met after the move is placed. Most of this is fairly simple logic utilizing lots of conditionals. The one thing to note, is that once the arrays are created, they can be accessed similarly to most other languages. For 1D arrays you do array[index] and for 2D arrays you do array[index][index2].
Here is a simple method to check if the game is over.
A simple method to get the game state.
A simple method to get the current grid, but note the 2D array gridGet is initialized a bit differently, setting the value at each location in initialization.
This is a method to get the list of moves, copy them into a new array, and return it. It works very similarly to how it would be done in other languages.
Most of the logic for this class was simple and easy to understand not requireing to much explanation. The next class though handles how to implement a third-party tool to be able to implement GUI with Ruby, which will be a bit more complex and unique when compared to other languages.
Unlike languages like Java, Ruby does not have built in GUI capability. It is still possible however thanks to some third-party tools for GUI. FXRuby is what is used in this example and is recommended because of the easy installation/integration. Once you have Ruby installed, you can simply open your terminal and type gem install fxruby and it will begin installing the FXRuby library. FXRuby comes with many graphical features that integrate well with Ruby. To allow a class to have access to the FXRuby library, you will need to require fox16 and include Fox.
For GUI to work, you need to either create your own class inheriting FXMainWindow or use the built in one. In this example, we create our own window class to include the implemented TicTacToe game logic.
In the class, we need to initialize some frames for the content that will show in the window. We create a overarching content frame for everything, and then create some inner frames for things like the board and move list. There are a few different types of frames you can use like the horizontal frame, vertical frame, and matrix frame. You can also create labels for the frames.
Horizontal and Vertical frames are fairly simple, just creating basic frames. For the grid of buttons though, we require a matrix frame since this works well for the 2D array of buttons we have.
There are also visual seperators you can create to organize your window and show where the ends of a frame are.
For buttons, you can create them using FXButton with some custom parameters.
We also need to create buttons for each spot on the TicTacToe grid. We create the button with our parameters, and add it to the button grid array we have.
To make the button do something when clicked, you need to define the action with .connect(SEL_COMMAND), and then with a do statement, you add your logic.
We have to repeat this for each of the 9 buttons of the grid. We were unable to achieve this with a loop as it would link all of the buttons to only one action, since all of the actions are seperate, they need to be seperately defined. The logic for buttons 3-9 has been removed from the example on the right to keep t compact. You can download the full Ruby file below with the full working logic.
This is the logic for button 2, just to show the similarities. Only thing to change between the button logics are the number of the button.
We also need to make the logic for the new game button, which all it does is reseat all of the text and start a new TicTacToe game.
When the game ends, we want it to display the list of moves for that game and who the winner was or if it was a tie. This can be done by utilizing the .getMoves method from the TicTacToeGame.rb and appending it all together in a for loop.
To finish off our custom window class, we need a method to create the window. We can use the FXMainWindow super class method, while adding the build in show method to make sure the window gets shown.
With the class done, all that is left is to create the application object, window object, and use the methods .create and .run on it. Remember Ruby has no need for a main method, any code outside the class will be executed as if it was a main method.
In the gif on the right, you can see the TicTacToe application in action. That window is what opens after the program is ran. The gif shows three rounds, one where the user wins, one tie, and one where the computer wins. It may be hard to see as it happens almost simultaneously, but the user (X) goes first each game, and the computer (O) chooses after. Buttons that have already been claimed can not be chosen and will not execute any code if clicked again. When the new game button is clicked, it resets all of the spots and move list. The layout and aesthetics is pretty simple, but this was a very simple example of the GUI capability these third-party tools offer.
A short and sweet example highlighting variable attribute access, array functionality, and a method for error handling in arrays.
You may have noticed the attr_accessor in the TicTacToe example. What this does is allow both read and write access to a variable. In other languages, you would need both a getter and setter method. With ruby, you can simply add the attr_accessor tag and that variable can now be read and written to like how getters and setters do, without the need for the extra code.
To demonstrate this, we will initialize a few Car objects. A car has a make and model, and remember, after initialization, we do not have any getter or setter methods to alter these.
Since the attr_accessor tag was applied to the variables, they can be modified or read with .model.
With arrays, there is an easy way to handle out of bounds errors. Ruby's fetch can handle this. Here we are trying to acces index 5 of the array which is out of bounds, so it will output the out of bounds message we passed in as an argument. If the number was inbounds, it would output the object.
Ruby also has some quicker ways of accessing the first and last elements of an array built in to the language with .first and .last. Much like other languages, the last element can be removed from the array with .pop.
The program starts by printing car1 before the model was modified. Then after modifying ith through the attribute access, we print it again showing the updated model. When the cars are in an array, they can still be accessed much like other languages we are used to and printed. In code, we tried to fetch index 5 of the car array, which is out of bounds, so the error is displayed instead of the car object. The rest of the output demonstrates those other array accessing methods and iteration over them even after an element is popped.
A port of our class Banking Program to Ruby, but now with some complex GUI functionality.
For the most part, translating the main classes over to Ruby was trivial. It uses many of the concepts we have already gone over and compared. All the customer class does is initialize the Customer object and define a toString method.
For the Account class, we initialize the object and variables, which is a bit different than before. Since the child classes also have the constructors with the arguments number, customer, balance, and type, in the superclass constructor, you can just have one argument and break apart the pieces into th seperate variables.
Defining the methods balance, deposit, and withdraw, are almost the same as how they are defined in java.
For this class, the method identify acts as the toString method for accounts. Since we are dealing with currency, we want to format the balance before we print it, which is done with '%.2f' % @balance.
We also made a small change to the account numbers. As you will see from the final output, accounts can be created from within the GUI, with the first account starting at 1, and incrementing with each new account. We also wanted to pad this with 0's to make it a length of 5 digits (00001, 00002, ..., 00010, etc).
The Saving Account file initializes saving account objects and defined the accrue method on saving accounts.
The inner code of the methods is almost directly copied from the java equivalent, just making sure to include the @ since the variables are instance defined.
The checking count is translated in a similar fashion as the Saving Account.
When it comes to the bank file, it is a little different from the java equivalent.
We make sure to require all of the relavent classes before we try to access any items from other classes.
For the list of accounts, we can initialize it as an array in the add method, but need to check if an array already exists as @accounts. To do this, we can check if the accounts variable responds to .each. If it does, then that means it already exists and we can append another account onto it, otherwise we can initialize it as a new array.
For the GUI, we added functionality to deposit and withdraw from accounts already created. To do this we added a seperate deposit and withdrawel method to the bank class that takes in an index and amount. We then find the account at that index and call the deposit and withdrawel methods for the account.
The accrue method loops through the accounts and applies the accrue method on each, much like how you would expect from the java implementation.
Lastly, we have the toString method which will just loop through each account calling the identify method discussed earlier.
We made all of the GUI functionality in a seperate class to highlight all of the changes needed to make this port work with GUI.
Much like the TicTacToe example, make sure to require the relavent items like the Bank class and fox16 items for FXRuby, and include the Fox module.
All of the logic can be created in the window initialize method for the application. At the start, create/set all of the relavent vartiables needed for the logic. We will want to loop through the customers and accounts while keeping track of the current one we are on to allow the user to select them to operate on.
Call the FXMainWindow super method to create the window as well as create a bank object to operate on.
Next, we will want to section the window into a few different frames to hold items like buttons, text, and input fields. This will include a main contents frame to hold everything, a left frame to hold the list of accounts with withdraw and deposit frames, and a right frame to hold the new customer frame, customer select frame, accrue frame, and account creation frame.
Throughout the creation of these frames, we also create the necessary buttons like before in the TicTacToe example and connecting the SEL_COMMAND with the code to execute on click.
For selecting the customer and account, we will have both a next and back button to iterate through the arrays to allow the user to operate on the selected customer or account. We have a deposit and withdraw button that will take the input text from the text fields to deposit or withdraw money from the currently selected class. We can also take text input to create new customers in the new customer frame. Accounts can be created with the open checking account and open saving account buttons. And lastly a button to accrue interest. The logic for the buttons are fairly straight forward utilizing the methods defined in the previous classes.
One difference from the TicTacToe example, is that we create an update method that is also called in many of the buttons connects to update the text of accounts as things like balance are changed and new accounts are created. This method just updates the text of the frame by reprinting the new bank to a string.
Then just like the TicTacToe example, we need to create an application object, window object, and use the create and run methods.
As you can see from the gif on the right, this is the final layout created by those frames. The gif goes through creating a few new customers (Nolan, Tyler, Maya, and John) and iterating through them at the top in the current customer frame.
Then we open both a checking and savings account under Nolan and Tyler, and showing off how to iterate through them and select an account.
With an account selected you can deposit and withrdraw money from the selected account and the balance will update in the accounts list whenever changed. Interest can also be accrued for savings accounts.
Below will be the download links for all of the above files, instructions for how your project directory should be setup for each project, and how to run them. Each file will be a clickable link to download.
For the Rock Paper Scissors program, since it is a single file, there is no need for any complex setup. As long as you have Ruby installed, from the command line you can cd into the whatever directory the file is downloaded into and run the program by typing ruby RockPaperScissors.rb. Here is the file:
Download: RockPaperScissors.rb
For the Bottom Up program, since it is a single file, there is no need for any complex setup. As long as you have Ruby installed, from the command line you can cd into the whatever directory the file is downloaded into and run the program by typing ruby BottomUp.rb. Here is the file:
Since the Tic Tac Toe program consists of multiple files and the code has been built in a way with a specific project structure, we will include the full project as a download. Once downloaded, unpack the zip and as long as Ruby is installed, just cd into the inner project directory with the lib subdirectory and TicTacToeGUI.rb file and run the command ruby TicTacToeGUI.rb. Here is the project download:
Download: RubyTicTacToeGUI.zip
For the Car Objects program, since it is a single file, there is no need for any complex setup. As long as you have Ruby installed, from the command line you can cd into the whatever directory the file is downloaded into and run the program by typing ruby example.rb. Here is the file:
Since the Bank program consists of multiple files and the code has been built in a way with a specific project structure, we will include the full project as a download. Once downloaded, unpack the zip and as long as Ruby is installed, just cd into the inner project directory with the lib subdirectory and BankGUI.rb file and run the command ruby BankGUI.rb. Here is the project download:
#!/usr/bin/ruby
# Ruby Rock, Paper, Scissors program
# Author: Maya Murphy - CS354 - LW - FA23
puts "Rock, Paper, Scissors"
# Initialize counters
@userTie = 0
@userWin = 0
@userLose = 0
@playGame = true
while @playGame
# Starting Game
puts "Enter your choice - 1 for Rock, 2 for Paper, and 3 for Scissors: "
@userChoice = gets.chomp
@computerChoice = rand(1..3)
# Keep track of results
@noPlayGame = false
while !@noPlayGame
if @userChoice == "1"
if @computerChoice == 1
puts "I chose Rock."
puts "We tie!"
@userTie += 1
break
elsif @computerChoice == 2
puts "I chose Paper."
puts "I win!"
@userLose += 1
break
else
puts "I chose Scissors."
puts "You win!"
@userWin += 1
break
end
elsif @userChoice == "2"
if @computerChoice == 1
puts "I chose Rock."
puts "You win!"
@userWin += 1
break
elsif @computerChoice == 2
puts "I chose Paper."
puts "We tie!"
@userTie += 1
break
else
puts "I chose Scissors."
puts "I win!"
@userLose += 1
break
end
elsif @userChoice == "3"
if @computerChoice == 1
puts "I chose Rock."
puts "I win!"
@userLose += 1
break
elsif @computerChoice == 2
puts "I chose Paper."
puts "You win!"
@userWin += 1
break
else
puts "I chose Scissors."
puts "We tie!"
@userTie += 1
break
end
else
puts "Invalid input. Try again."
break
end
end
# Ask if user wants to keep playing
puts "Play again (y/n)?"
@playGame = gets.chomp
if @playGame == "y" || @playGame == "Y"
next # like continue in Java
elsif @playGame == "n" || @playGame == "N"
puts "You won #{@userWin} times"
puts "You lost #{@userLose} times"
puts "We tied #{@userTie} times"
@noPlayGame = true
else
puts "Invalid input. I'm quitting."
puts "You won #{@userWin} times"
puts "You lost #{@userLose} times"
puts "We tied #{@userTie} times"
end
@playGame = false
end
#!/usr/bin/ruby
# Ruby program that effectively compiles from the bottom up
# Author: John Crowe - CS354 - LW - FA23
# Purposefully arranging the End block at the start to write a bottom up program
END {
sslst = sleepsort $nums
puts(sslst.inspect)
print << "a", << "b", << "c" # extension of the print method in the BEGIN block
Ruby has End Blocks which can define what happens at
a
the end of the program. This allows for the flexibility
b
required to write a program like this
c
}
# This method takes in an array and uses multithreading to sort it
def sleepsort(lst)
threads = []
rlst = []
lst.each do |i|
threads << Thread.new {
sleep(i)
rlst << i
}
end
threads.each do |thr|
thr.join
end
rlst
end
# multiline comments can be made with =begin comment =end
=begin
defining and building the nums global variable array
it goes up to 8 elements as that then would make 8
threads later when "sorted"
=end
$nums = (0..7).to_a.sort{ rand() - 0.5 }[0..7]
puts "The original array:"
puts($nums.inspect)
print "The sorted array:\n"
# Purposefully putting the BEGIN block at the end to show Ruby's flexibility
BEGIN { print << EOF # can print multiple lines by specifying a delimiter for
it to reach
Ruby has Begin blocks which defines what happens at
the beginning of the program. Here is it being used
to write a program that effectively executes from
the bottom up.
EOF
}
#!/usr/bin/ruby
# TicTacToe Module (Interface Imitation)
# Author: Nolan Olhausen - CS354 - LW - FA23
# This module imitates an interface, defining methods to be implemented later
module TicTacToeModule
# This module imitates enum behavior, defining constants
module BoardValue
X = 'X'
O = 'O'
OPEN = ''
end
# This module imitates enum behavior, defining constants
module State
X_WON = 'X Wins!'
O_WON = 'O Wins!'
TIE = 'No Winner'
IN_PROGRESS = 'In Progress'
ERROR = 'Error'
end
# Below are the methods defined by the TicTacToe module to be implemented
# in including class
def newGame
raise NotImplementedError, "Must implement 'newGame' method in including class"
end
def gameOver
raise NotImplementedError, "Must implement 'gameOver' method in including class"
end
def spotFill(player,row,col)
raise NotImplementedError, "Must implement 'spotFill' method in including class"
end
def getState
raise NotImplementedError, "Must implement 'getState' method in including class"
end
def getGrid
raise NotImplementedError, "Must implement 'getGrid' method in including class"
end
def getMoves
raise NotImplementedError, "Must implement 'getMoves' method in including class"
end
end
#!/usr/bin/ruby
# TicTacToe Game Logic ("Implements" TicTacToeModule)
# Author: Nolan Olhausen - CS354 - LW - FA23
# To use other classes in your project, use require_relative and the path
require_relative 'TicTacToeModule'
# Game class
class TicTacToeGame
# include the module class to use it
include TicTacToeModule
# Point class for points in grid
class Point
attr_accessor :x, :y
def initialize(x, y) # initialize methods can be used as .new
@x = x
@y = y
end
def to_s # to string method
"row #{x}, col #{y}"
end
end
# define variables
@prevPlayer = nil
@grid = nil
@moves = nil
@state = nil
@moveGood = nil
@isOver = nil
@moveCounter = 0
@moveIndex = 0
@moveArray = 0
@moveGetI = 0
@moveGetA = 0
# initialize game
def initialize
newGame
end
# method to setup game
def newGame
# set variables
@state = State::IN_PROGRESS
@moveCounter = 0
@moveIndex = 0
@moveGetA = 0
@moveGetI = 0
@moveArray = 0
@grid = Array.new(3) { Array.new(3)} # how to initialize 2d array
@moves = Array.new(9) # how to initialize single array
@prevPlayer = BoardValue::OPEN
# set all board positions to OPEN
for row in 0..2 do
for col in 0..2 do
@grid[row][col] = BoardValue::OPEN # how to access 2D array element
end
for i in 0..8 do
@moves[i] = nil # how to access single array element
end
end
end
# method for players to fill grid spot
def spotFill(player,row,col)
if @state == State::IN_PROGRESS
if player != @prevPlayer
if @grid[row][col] == BoardValue::OPEN
@prevPlayer = player
@grid[row][col] = player
@moveGood = true
# initialize method being called with .new
@moves[@moveIndex] = Point.new(row,col)
@moveCounter += 1
@moveIndex += 1
if @moveCounter < 9
for i in 0..2 do
if ((@grid[i][0] == player && @grid[i][1] == player &&
@grid[i][2] == player) || (@grid[0][i] == player &&
@grid[1][i] == player && @grid[2][i] == player) ||
(@grid[0][0] == player && @grid[1][1] == player &&
@grid[2][2] == player) || (@grid[2][0] == player &&
@grid[1][1] == player && @grid[0][2] == player))
if player == BoardValue::X
@state = State::X_WON
elsif player == BoardValue::O
@state = State::O_WON
else
@state = State::ERROR
end
end
end
else
for i in 0..2 do
if ((@grid[i][0] == player && @grid[i][1] == player &&
@grid[i][2] == player) || (@grid[0][i] == player &&
@grid[1][i] == player && @grid[2][i] == player) ||
(@grid[0][0] == player && @grid[1][1] == player &&
@grid[2][2] == player) || (@grid[2][0] == player &&
@grid[1][1] == player && @grid[0][2] == player))
if player == BoardValue::X
@state = State::X_WON
elsif player == BoardValue::O
@state = State::O_WON
else
@state = State::ERROR
end
else
@state = State::TIE
end
end
end
else
@moveGood = false
end
else
@moveGood = false
end
else
@moveGood = false
end
return @moveGood
end
# check if over method
def gameOver
if @state == State::IN_PROGRESS
@isOver = false
else
@isOver = true
end
return @isOver
end
# get game state
def getState
return @state
end
#get current grid
def getGrid
gridGet = [
[@grid[0][0], @grid[0][1], @grid[0][2]],
[@grid[1][0], @grid[1][1], @grid[1][2]],
[@grid[2][0], @grid[2][1], @grid[2][2]]
]
return gridGet
end
# get move list
def getMoves
@moveGetA = 0
@moveGetI = 0
for i in 0..8 do
if @moves[i] != nil
@moveGetI += 1
end
end
moveGet = Array.new(@moveGetI)
for i in 0..8 do
if @moves[i] != nil
moveGet[@moveGetA] = @moves[i]
@moveGetA += 1
end
end
return moveGet
end
end
#!/usr/bin/ruby
# TicTacToe GUI (Using Third-Party FXRuby)
# Author: Nolan Olhausen - CS354 - LW - FA23
require_relative 'lib/TicTacToeGame' # if files are in a subdirectory, full path from
# mains location is needed
require 'fox16' # require installed FXRuby
include Fox # include to use
include TicTacToeModule # include to use, since TicTacToeGame requires it, it can be
# accessed since this file requires TicTacToeGame
# method for GUI window setup as well as any window element logic (aka buttons)
class TicTacToeMainWindow < FXMainWindow
# initialize (.new method)
def initialize(application, title, wide, high)
# use FXRuby's Main Window initialize with arguments
super(application, title, :width => wide, :height => high)
# outer content window that holds all contents
@contents = FXHorizontalFrame.new(self,
LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y,
:padLeft => 0, :padRight => 0, :padTop => 0, :padBottom => 0)
# inner game board window
@board_frame = FXVerticalFrame.new(@contents,
FRAME_SUNKEN|LAYOUT_FILL_Y|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
# label for window, to show text for title of the window
FXLabel.new(@board_frame, "Board", nil, JUSTIFY_CENTER_X|LAYOUT_FILL_X)
# 2D array of buttons, another way to setup 2D arrays
@buttonGrid = [[nil, nil, nil],[nil,nil,nil],[nil,nil,nil]]
#inner button window that contains the 9 grid buttons, placed in board window
@button_frame = FXMatrix.new(@board_frame, 3, MATRIX_BY_COLUMNS | LAYOUT_FILL_X )
# visual line seperator to visually seperate windows
FXHorizontalSeparator.new(@board_frame, SEPARATOR_GROOVE|LAYOUT_FILL_X)
# window to show move history at end of game
@moves_frame = FXVerticalFrame.new(@contents,
FRAME_SUNKEN|LAYOUT_FILL_Y|LAYOUT_FILL_X|LAYOUT_TOP,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
# label for move window
FXLabel.new(@moves_frame, "Moves", nil, JUSTIFY_CENTER_X|LAYOUT_FILL_X)
# Horizontal divider line
FXHorizontalSeparator.new(@moves_frame, SEPARATOR_GROOVE|LAYOUT_FILL_X)
# inner text window for moves, used to display text
@text_frame = FXText.new(@moves_frame, nil, 0, TEXT_READONLY | TEXT_WORDWRAP |
LAYOUT_FILL_X | LAYOUT_FILL_Y)
# window for new game button
@newGame_frame = FXVerticalFrame.new(@contents, LAYOUT_FILL_Y | LAYOUT_LEFT)
# create new game button
newGameButton = FXButton.new(@newGame_frame, "New Game", :opts => FRAME_THICK|
FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 50, :padRight => 50, :padTop => 25, :padBottom => 25)
# create game
@game = TicTacToeGame.new
# create grid button and add to array
button1 = FXButton.new(@button_frame, "", :opts => FRAME_THICK|FRAME_RAISED|
LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 96, :padRight => 96, :padTop => 96, :padBottom => 96)
@buttonGrid[0][0] = button1
# create button functionality
button1.connect(SEL_COMMAND) do
if @game.spotFill(BoardValue::X, 0, 0)
button1.text = "X"
if @game.gameOver
endGame
else
done = false
# logic for computers move
while(!done)
randRow = rand(0..2)
randCol = rand(0..2)
if(@game.spotFill(BoardValue::O, randRow, randCol))
@buttonGrid[randRow][randCol].text = "O"
done = true
end
end
if @game.gameOver
endGame
end
end
end
end
# logic repeats for all 9 buttons, could not be done with a for loop as each
# button needs to be seperately connected
button2 = FXButton.new(@button_frame, "", :opts => FRAME_THICK|FRAME_RAISED|
LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 96, :padRight => 96, :padTop => 96, :padBottom => 96)
@buttonGrid[0][1] = button2
button2.connect(SEL_COMMAND) do
if @game.spotFill(BoardValue::X, 0, 1)
button2.text = "X"
if @game.gameOver
endGame
else
done = false
while(!done)
randRow = rand(0..2)
randCol = rand(0..2)
if(@game.spotFill(BoardValue::O, randRow, randCol))
@buttonGrid[randRow][randCol].text = "O"
done = true
end
end
if @game.gameOver
endGame
end
end
end
end
# logic for buttons 3-9 removed here to keep example compact, download complete
# file at the bottom of website for full version
# logic for new game button
newGameButton.connect(SEL_COMMAND) do
# reset game
@game.newGame
# reset button texts
button1.text = ""
button2.text = ""
button3.text = ""
button4.text = ""
button5.text = ""
button6.text = ""
button7.text = ""
button8.text = ""
button9.text = ""
# reset move list text
@text_frame.text = ""
end
# end game method, displays move list and winner
def endGame
@text_frame.text = ""
textTmp = ""
moveArray = @game.getMoves
index = 0
for i in moveArray do
if index%2 == 0
textTmp << "X: "
else
textTmp << "O: "
end
textTmp << i.to_s + "\n"
index += 1
end
if @game.getState == State::TIE
textTmp << "NO WINNER\n"
elsif @game.getState == State::X_WON
textTmp << "WINNER: X\n"
elsif @game.getState == State::O_WON
textTmp << "WINNER: O\n"
else
textTmp << "ERROR\n"
end
@text_frame.text = textTmp
end
end
# create method for window creation/placement
def create
super
show(PLACEMENT_SCREEN)
end
end
# create the application
application = FXApp.new
# create the window
main_window = TicTacToeMainWindow.new(application, "Tic Tac Toe", 1800, 800)
# call create method on application
application.create
# run application
application.run
#!/usr/bin/ruby
# Car Example Program
# Author: Tyler Pierce - CS354 - LW - FA23
class Car
attr_accessor :make
attr_accessor :model
# Create the object
def initialize(make = "Undefined", model = "Undefined")
@make = make
@model = model
end
# Print string representation
def toString
"This car is a #{@make} #{@model}."
end
end
# Main
car1 = Car.new("Ford", "F-150")
car2 = Car.new("Toyota", "Corolla")
car3 = Car.new("Nissan", "Versa")
car4 = Car.new("Mazda", "3")
puts car1.toString
#Change Car Model
car1.model = "F-350"
puts car1.toString
cars = [car1, car2, car3, car4]
puts cars[1].toString
puts "\n"
# Error Handling
puts cars.fetch(5, "Out of Bounds")
puts "\n"
# First and Lasts
puts "first: " + cars.first.toString
puts "last: " + cars.last.toString
car5 = Car.new("Audi", "R8")
cars << car5
puts "last: " + cars.last.toString
puts "\n"
# Iteration over array
cars.each do |car|
puts car.toString
end
puts "\n"
cars.pop
# Iteration over array
cars.each do |car|
puts car.toString
end
Rock, Paper, Scissors
Enter your choice - 1 for Rock, 2 for Paper,
and 3 for Scissors:
1
I chose Paper.
I win!
Play again (y/n)?
y
Enter your choice - 1 for Rock, 2 for Paper,
and 3 for Scissors:
3
I chose Paper.
You win!
Play again (y/n)?
n
You won 1 times
You lost 1 times
We tied 0 times
Ruby has Begin blocks which defines what happens at
the beginning of the program. Here is it being used
to write a program that effectively executes from
the bottom up.
The original array:
[0, 3, 2, 1, 7, 5, 4, 6]
The sorted array:
[0, 1, 2, 3, 4, 5, 6, 7]
Ruby has End Blocks which can define what happens at
the end of the program. This allows for the flexibility
required to write a program like this
This car is a Ford F-150.
This car is a Ford F-350.
This car is a Toyota Corolla.
Out of Bounds
first: This car is a Ford F-350.
last: This car is a Mazda 3.
last: This car is a Audi R8.
This car is a Ford F-350.
This car is a Toyota Corolla.
This car is a Nissan Versa.
This car is a Mazda 3.
This car is a Audi R8.
This car is a Ford F-350.
This car is a Toyota Corolla.
This car is a Nissan Versa.
This car is a Mazda 3.
#!/usr/bin/ruby
# Customer file
# Author: Maya Murphy - CS354 - LW - FA23
class Customer
def initialize(name)
@name = name
end
def String()
@name
end
end
#!/usr/bin/ruby
# This class is the superclass for checking and savings
# Author: John Crowe - CS354 - LW - FA23
require_relative "Customer" # Tells the file that ./Customer.rb is needed
# Superclass for the Savings and Checking Accounts
class Account
attr_accessor :number, :customer, :balance, :type
def initialize(input)
@number = input[:number]
@customer = input[:customer]
@balance = input[:balance]
@type = input[:type]
end
def balance
@balance
end
def deposit(amount)
@balance+=amount
end
def withdraw(amount)
@balance-=amount
end
def identify
formatted_number = '%05d' % @number
formatted_balance = '%.2f' % @balance
"#{formatted_number}:#{@customer.String}:#{@type}:#{formatted_balance}"
end
def accrue(rate)
raise 'this method should be overridden and return the appropriate result'
end
end
#!/usr/bin/ruby
# SavingAccount file
# Author: Maya Murphy - CS354 - LW - FA23
require_relative "Account"
class SavingAccount < Account
def initialize(number, customer, balance)
@number, @customer, @balance, @type = number, customer, balance, "Savings"
@interest=0
end
def accrue(rate)
@interest+=@balance*rate
@balance+=@balance*rate
end
end
#!/usr/bin/ruby
# CheckingAccount file
# Author: Maya Murphy - CS354 - LW - FA23
require_relative "Account"
class CheckingAccount < Account
def initialize(number, customer, balance)
@number, @customer, @balance, @type = number, customer, balance, "Checking"
end
def accrue(rate)
end
end
#!/usr/bin/ruby
# Bank file
# Author: Tyler Pierce - CS354 - LW - FA23
require_relative "CheckingAccount"
require_relative "SavingAccount"
require_relative "Account"
require_relative "Customer"
class Bank
@accounts
def add(account)
if (account.is_a?(Account))
if (@accounts.respond_to?("each"))
@accounts << account
else
@accounts = [account]
end
else
print "Error: Expected Account type in function \"add\""
end
end
def depos(index, amount)
@accounts[index].deposit(amount)
end
def withd(index, amount)
@accounts[index].withdraw(amount)
end
def accrue(rate)
@accounts.each do |account|
account.accrue(rate)
end
end
def toString
r=""
@accounts.each do |account|
r+=account.identify+"\n"
end
return r
end
end
#!/usr/bin/ruby
# Bank GUI (Using Third-Party FXRuby)
# Author: Nolan Olhausen - CS354 - LW - FA23
require_relative 'lib/Bank' # if files are in a subdirectory, full path from
# mains location is needed
require 'fox16' # require installed FXRuby
include Fox # include to use
# method for GUI window setup as well as any window element logic (aka buttons)
class BankMainWindow < FXMainWindow
# initialize (.new method)
def initialize(application, title, wide, high)
@currentCustomer
@accountNumber = 00001
@amount = 0
@customers
@accounts
@customerIndex = 0
@accountIndex = 0
# use FXRuby's Main Window initialize with arguments
super(application, title, :width => wide, :height => high)
@bank = Bank.new
# outer content window that holds all contents
@contents = FXHorizontalFrame.new(self,
LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y,
:padLeft => 0, :padRight => 0, :padTop => 0, :padBottom => 0)
@left_frame = FXVerticalFrame.new(@contents,
:opts => FRAME_SUNKEN|LAYOUT_FILL_Y|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10,)
@right_frame = FXVerticalFrame.new(@contents,
FRAME_SUNKEN|LAYOUT_FILL_Y|LAYOUT_TOP,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
@customer_frame = FXVerticalFrame.new(@right_frame,
FRAME_SUNKEN|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
FXLabel.new(@customer_frame, "Current Customer:", nil, JUSTIFY_LEFT|LAYOUT_FILL_X)
currCust = FXLabel.new(@customer_frame, "None", nil, JUSTIFY_LEFT|LAYOUT_FILL_X)
@selectCustButton_frame = FXHorizontalFrame.new(@customer_frame,
FRAME_SUNKEN|LAYOUT_LEFT|LAYOUT_FILL_X,
:padLeft => 0, :padRight => 0, :padTop => 0, :padBottom => 0)
customerBack = FXButton.new(@selectCustButton_frame, "Back", :opts => FRAME_THICK|
FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
customerBack.connect(SEL_COMMAND) do
if @customerIndex == 0
@currentCustomer = @customers.last
@customerIndex = @customers.length()-1
currCust.text = @currentCustomer.String()
else
@customerIndex = @customerIndex - 1
@currentCustomer = @customers[@customerIndex]
currCust.text = @currentCustomer.String()
end
end
customerNext = FXButton.new(@selectCustButton_frame, "Next", :opts => FRAME_THICK|
FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
customerNext.connect(SEL_COMMAND) do
if @customerIndex == @customers.length()-1
@currentCustomer = @customers.first
@customerIndex = 0
currCust.text = @currentCustomer.String()
else
@customerIndex = @customerIndex + 1
@currentCustomer = @customers[@customerIndex]
currCust.text = @currentCustomer.String()
end
end
@accrue_frame = FXVerticalFrame.new(@right_frame,
FRAME_SUNKEN|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
FXLabel.new(@accrue_frame, "Accrue Interest", nil, JUSTIFY_LEFT|LAYOUT_FILL_X)
accrueButton = FXButton.new(@accrue_frame, "Accrue", :opts => FRAME_THICK|
FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
accrueButton.connect(SEL_COMMAND) do
@bank.accrue(0.02)
update
end
@addAccount_frame = FXVerticalFrame.new(@right_frame,
FRAME_SUNKEN|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
FXLabel.new(@addAccount_frame, "Open Account", nil, JUSTIFY_LEFT|LAYOUT_FILL_X)
addChecking = FXButton.new(@addAccount_frame, "Open Checking Account", :opts => FRAME_THICK|
FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
addChecking.connect(SEL_COMMAND) do
@bank.add(CheckingAccount.new(@accountNumber.to_s, @currentCustomer, 0.00))
if (@accounts.respond_to?("each"))
@accounts << CheckingAccount.new(@accountNumber.to_s, @currentCustomer, 0.00)
else
@accounts = [CheckingAccount.new(@accountNumber.to_s, @currentCustomer, 0.00)]
end
@accountNumber = @accountNumber + 1
update
end
addSaving = FXButton.new(@addAccount_frame, "Open Savings Account", :opts => FRAME_THICK|
FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
addSaving.connect(SEL_COMMAND) do
@bank.add(SavingAccount.new(@accountNumber.to_s, @currentCustomer, 0.00))
if (@accounts.respond_to?("each"))
@accounts << SavingAccount.new(@accountNumber.to_s, @currentCustomer, 0.00)
else
@accounts = [SavingAccount.new(@accountNumber.to_s, @currentCustomer, 0.00)]
end
@accountNumber = @accountNumber + 1
update
end
@addCustomer_frame = FXVerticalFrame.new(@right_frame,
FRAME_SUNKEN|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
FXLabel.new(@addCustomer_frame, "New Customer", nil, JUSTIFY_LEFT|LAYOUT_FILL_X)
@newCustomer = FXTextField.new(@addCustomer_frame, 20)
createCustomer = FXButton.new(@addCustomer_frame, "Sign Up", :opts => FRAME_THICK|
FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
createCustomer.connect(SEL_COMMAND) do
if (@customers.respond_to?("each"))
@customers << Customer.new(@newCustomer.text)
else
@customers = [Customer.new(@newCustomer.text)]
end
end
@accounts_frame = FXVerticalFrame.new(@left_frame,
FRAME_SUNKEN|LAYOUT_FILL_Y|LAYOUT_FILL_X|LAYOUT_TOP,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
FXLabel.new(@accounts_frame, "Accounts", nil, JUSTIFY_LEFT|LAYOUT_FILL_X)
FXHorizontalSeparator.new(@accounts_frame, SEPARATOR_GROOVE|LAYOUT_FILL_X)
@text_frame = FXText.new(@accounts_frame, nil, 0, TEXT_READONLY | TEXT_WORDWRAP |
LAYOUT_FILL_X |LAYOUT_FILL_Y)
@selected_frame = FXVerticalFrame.new(@left_frame,
FRAME_SUNKEN|LAYOUT_LEFT|LAYOUT_FILL_X,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
FXLabel.new(@selected_frame, "Selected Account:", nil, JUSTIFY_LEFT|LAYOUT_FILL_X)
selectedAccount = FXLabel.new(@selected_frame, "None", nil, JUSTIFY_LEFT|LAYOUT_FILL_X)
@selectedButton_frame = FXHorizontalFrame.new(@selected_frame,
FRAME_SUNKEN|LAYOUT_LEFT|LAYOUT_FILL_X,
:padLeft => 0, :padRight => 0, :padTop => 0, :padBottom => 0)
accountBack = FXButton.new(@selectedButton_frame, "Back", :opts => FRAME_THICK|
FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
accountBack.connect(SEL_COMMAND) do
if @accountIndex == 0
@accountIndex = @accounts.length()-1
selectedAccount.text = @accounts[@accountIndex].identify
else
@accountIndex = @accountIndex - 1
selectedAccount.text = @accounts[@accountIndex].identify
end
end
accountNext = FXButton.new(@selectedButton_frame, "Next", :opts => FRAME_THICK|
FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
accountNext.connect(SEL_COMMAND) do
if @accountIndex == @accounts.length()-1
@accountIndex = 0
selectedAccount.text = @accounts[@accountIndex].identify
else
@accountIndex = @accountIndex + 1
selectedAccount.text = @accounts[@accountIndex].identify
end
end
@action_frame = FXHorizontalFrame.new(@left_frame,
FRAME_SUNKEN|LAYOUT_BOTTOM|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
@deposit_frame = FXVerticalFrame.new(@action_frame,
FRAME_SUNKEN|LAYOUT_FILL_Y|LAYOUT_BOTTOM|LAYOUT_LEFT,
:padLeft => 0, :padRight => 10, :padTop => 0, :padBottom => 0)
FXLabel.new(@deposit_frame, "Deposit", nil, JUSTIFY_LEFT|LAYOUT_FILL_X)
FXHorizontalSeparator.new(@deposit_frame, SEPARATOR_GROOVE|LAYOUT_FILL_X)
depositField = FXTextField.new(@deposit_frame, 10)
depButton = FXButton.new(@deposit_frame, "Deposit", :opts => FRAME_THICK|
FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
depButton.connect(SEL_COMMAND) do
@bank.depos(@accountIndex, Float(depositField.text))
update
end
@withdraw_frame = FXVerticalFrame.new(@action_frame,
FRAME_SUNKEN|LAYOUT_FILL_Y|LAYOUT_BOTTOM|LAYOUT_RIGHT,
:padLeft => 0, :padRight => 10, :padTop => 0, :padBottom => 0)
FXLabel.new(@withdraw_frame, "Withdraw", nil, JUSTIFY_LEFT|LAYOUT_FILL_X)
FXHorizontalSeparator.new(@withdraw_frame, SEPARATOR_GROOVE|LAYOUT_FILL_X)
withdrawField = FXTextField.new(@withdraw_frame, 10)
withButton = FXButton.new(@withdraw_frame, "Withdraw", :opts => FRAME_THICK|
FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,
:padLeft => 10, :padRight => 10, :padTop => 10, :padBottom => 10)
withButton.connect(SEL_COMMAND) do
@bank.withd(@accountIndex, Float(withdrawField.text))
update
end
def update
@text_frame.text = @bank.toString
end
end
# create method for window creation/placement
def create
super
show(PLACEMENT_SCREEN)
end
end
# create the application
application = FXApp.new
# create the window
main_window = BankMainWindow.new(application, "Ruby Bank", 450, 450)
# call create method on application
application.create
# run application
application.run