Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aisha's RPS Challenge #2128

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ source 'https://rubygems.org'
ruby '3.0.2'

gem 'sinatra'
gem 'sinatra-contrib'

group :test do
gem 'capybara'
Expand All @@ -14,3 +15,6 @@ end
group :development, :test do
gem 'rubocop', '1.20'
end

gem 'webrick', '~> 1.3', '>= 1.3.1'
gem 'launchy'
13 changes: 13 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ GEM
xpath (~> 3.2)
diff-lcs (1.4.4)
docile (1.4.0)
launchy (2.5.0)
addressable (~> 2.7)
mini_mime (1.1.1)
mini_portile2 (2.6.1)
multi_json (1.15.0)
mustermann (1.1.1)
ruby2_keywords (~> 0.0.1)
nokogiri (1.12.3)
Expand Down Expand Up @@ -76,10 +79,17 @@ GEM
rack (~> 2.2)
rack-protection (= 2.1.0)
tilt (~> 2.0)
sinatra-contrib (2.1.0)
multi_json
mustermann (~> 1.0)
rack-protection (= 2.1.0)
sinatra (= 2.1.0)
tilt (~> 2.0)
terminal-table (3.0.1)
unicode-display_width (>= 1.1.1, < 3)
tilt (2.0.10)
unicode-display_width (2.0.0)
webrick (1.7.0)
xpath (3.2.0)
nokogiri (~> 1.8)

Expand All @@ -88,11 +98,14 @@ PLATFORMS

DEPENDENCIES
capybara
launchy
rspec
rubocop (= 1.20)
simplecov
simplecov-console
sinatra
sinatra-contrib
webrick (~> 1.3, >= 1.3.1)

RUBY VERSION
ruby 3.0.2p107
Expand Down
102 changes: 32 additions & 70 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
# RPS Challenge
# Rock Paper Scissors Challenge

Instructions
-------
This is a rock, paper, scissors game web application, that allows you to play a single-player game.

* Feel free to use google, your notes, books, etc. but work on your own
* If you refer to the solution of another coach or student, please put a link to that in your README
* If you have a partial solution, **still check in a partial solution**
* You must submit a pull request to this repo with your code by 9am Monday morning
## About
This is a weekend challenge from week 3 of the Maker's Academy course. The Makers Academy Marketing Array ( MAMA ) asked us to create a game, that they can play to take a break from their daily grind.

Task
----
They provided the following user stories:

Knowing how to build web applications is getting us almost there as web developers!

The Makers Academy Marketing Array ( **MAMA** ) have asked us to provide a game for them. Their daily grind is pretty tough and they need time to steam a little.

Your task is to provide a _Rock, Paper, Scissors_ game for them so they can play on the web with the following user stories:

```
```bash
As a marketeer
So that I can see my name in lights
I would like to register my name before playing an online game
Expand All @@ -27,60 +17,32 @@ So that I can enjoy myself away from the daily grind
I would like to be able to play rock/paper/scissors
```

Hints on functionality

- the marketeer should be able to enter their name before the game
- the marketeer will be presented the choices (rock, paper and scissors)
- the marketeer can choose one option
- the game will choose a random option
- a winner will be declared


As usual please start by

* Forking this repo
* TEST driving development of your app

[You may find this guide to setting up a new Ruby web project helpful.](https://github.com/makersacademy/course/blob/main/pills/ruby_web_project_setup_list.md)

## Bonus level 1: Multiplayer

Change the game so that two marketeers can play against each other ( _yes there are two of them_ ).

## Bonus level 2: Rock, Paper, Scissors, Spock, Lizard

Use the _special_ rules ( _you can find them here http://en.wikipedia.org/wiki/Rock-paper-scissors-lizard-Spock_ )

## Basic Rules

- Rock beats Scissors
- Scissors beats Paper
- Paper beats Rock

In code review we'll be hoping to see:

* All tests passing
* High [Test coverage](https://github.com/makersacademy/course/blob/main/pills/test_coverage.md) (>95% is good)
* The code is elegant: every class has a clear responsibility, methods are short etc.

Reviewers will potentially be using this [code review rubric](docs/review.md). Referring to this rubric in advance may make the challenge somewhat easier. You should be the judge of how much challenge you want this at this moment.

Notes on test coverage
----------------------

Please ensure you have the following **AT THE TOP** of your spec_helper.rb in order to have test coverage stats generated
on your pull request:

```ruby
require 'simplecov'
require 'simplecov-console'
## Technologies
This project is created with:
* Ruby 3.0.2
* Webrick 1.3.1
* Sinatra
* Rspec
* Capybara
* Rackup
* Launchy

## Installation

To run this project, clone this repository, install the gems and run rackup using [bundler](https://bundler.io/):

```bash
$ git clone git@github.com:[USERNAME]/rps-challenge.git
$ cd rps-challenge
$ bundle
$ rackup
```
Then place this into your browser and you're ready to play the game:

SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
SimpleCov::Formatter::Console,
# Want a nice code coverage website? Uncomment this next line!
# SimpleCov::Formatter::HTMLFormatter
])
SimpleCov.start
```bash
http://localhost:9292
```
Once you're on the web app, enter your name and pick between Rock, Paper or Scissors and find out if you've beat the computer! Have fun!

You can see your test coverage when you run your tests. If you want this in a graphical form, uncomment the `HTMLFormatter` line and see what happens!
## Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
57 changes: 57 additions & 0 deletions app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
require 'sinatra/base'
require 'sinatra/reloader'
require './lib/player'
require './lib/computer'
require './lib/final_result'

class RockPaperScissors < Sinatra::Base
configure :development do
register Sinatra::Reloader
end

enable :sessions

get '/' do
erb :index
end

post '/name' do
session[:username] = params[:names]
redirect '/play'
end

get '/play' do
@username = session[:username]
@game = $game

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is $game variable used or needed?

erb :play
end

post '/rock' do
session[:player] = Player.new(params[:player_option])
redirect '/game'
end

post '/paper' do
session[:player] = Player.new(params[:player_option])
redirect '/game'
end

post '/scissors' do
session[:player] = Player.new(params[:player_option])
redirect '/game'
end

get '/game' do
@username = session[:username]
@player_option = session[:player].option
session[:computer] = Computer.new(["Rock", "Paper", "Scissors"])
@computer_option = session[:computer].option

$final_result = FinalResult.new
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the object FinalResult.new doesn't need to be accessed in a different route (which seems to be the case, as it's used only to calculate the result and chose the winer), it's probably good to avoid making it a global variable (it's generally best to avoid using global variables unless there is a need to):

final_result = FinalResult.new
final_result.calculate(@player_option, @computer_option)
@final_result = $final_result.winner

Maybe the name of the class could be changed to FinalResultCalculator as well, to avoid mixing up that object with the result value itself?

$final_result.calculate(@player_option, @computer_option)
@final_result = $final_result.winner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the @final_result variable go into a separate get route and have /game redirect to it (either as a post or get)? The global $final_result can stay in /game if needed?

erb :game
end

run! if app_file == $0
end
3 changes: 3 additions & 0 deletions config.ru
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
require_relative "./app"

run RockPaperScissors
64 changes: 64 additions & 0 deletions docs/RPS_recipe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
RPS (Rock, paper, scissors) Design Recipe
1. Describe the Problem

USER STORIES
As a marketeer
So that I can see my name in lights
I would like to register my name before playing an online game

As a marketeer
So that I can enjoy myself away from the daily grind
I would like to be able to play rock/paper/scissors

Hints on functionality

the marketeer should be able to enter their name before the game - /index - name - field(capybara)
the marketeer will be presented the choices (rock, paper and scissors) - click button rock, paper and scissors
the marketeer can choose one option - move to page with their button
the game will choose a random option - need random array
a winner will be declared - result page, button play again takes them to index



2. Design the Class System
Consider diagramming out the classes and their relationships. Take care to focus on the details you see as important, not everything. The diagram below uses asciiflow.com but you could also use excalidraw.com, draw.io, or miro.com

┌────────────────────────────┐
│ MusicPlayer │
│ │
│ - add(track) │
│ - all │
│ - search_by_title(keyword) │
│ => [tracks...] │
└───────────┬────────────────┘
│ owns a list of
┌─────────────────────────┐
│ Track(title, artist) │
│ │
│ - title │
│ - artist │
│ - format │
│ => "TITLE by ARTIST" │
└─────────────────────────┘
Also design the interface of each class in more detail.

--------------------------------------------------------------------------------------

3. Create Examples as Integration Tests
Create examples of the classes being used together in different situations and combinations that reflect the ways in which the system will be used.


4. Create Examples as Unit Tests
Create examples, where appropriate, of the behaviour of each relevant class at a more granular level of detail.
# EXAMPLE

# Constructs a track
track = Track.new("Carte Blanche", "Veracocha")
track.title # => "Carte Blanche"
Encode each example as a test. You can add to the above list as you go.

5. Implement the Behaviour
After each test you write, follow the test-driving process of red, green, refactor to implement the behaviour.

7 changes: 7 additions & 0 deletions lib/computer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Computer
attr_reader :option

def initialize(options)
@option = options.sample
end
end
24 changes: 24 additions & 0 deletions lib/final_result.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class FinalResult

def initialize
@rules = {
Rock: { Rock: :draw, Paper: :lose, Scissors: :win },
Paper: { Rock: :win, Paper: :draw, Scissors: :lose },

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this way of going about determining the rules of the game

Scissors: { Rock: :lose, Paper: :win, Scissors: :draw }
}

@result = []
end

def calculate(player_option, comp_option)
player = player_option.to_sym
comp = comp_option.to_sym
@result = @rules[player][comp]
end

def winner
return "Congratulations! You won, you beat the computer!" if @result == :win
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than having human-readable messages returned by this class, it might be better to let this responsibility to the view (in the .erb file) to have all the content / text displayed to the user. This way, it becomes easier to change it, if we want to change the text displayed, translate it in other languages, etc. The method winner could then return either the result directly (:win, :lose, etc), and the the message to display would be part of the view layer

return "Oh well, it's a draw!" if @result == :draw
return "Oh no, you lost! Better luck next time!" if @result == :lose
end
end
7 changes: 7 additions & 0 deletions lib/player.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Player
attr_reader :option

def initialize(options)
@option = options
end
end
23 changes: 23 additions & 0 deletions spec/computer_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require 'computer'

describe Computer do
context "returns Rock, Paper, Scissor as an option for the computer" do
it "it returns rock" do
comp = Computer.new(["Rock", "Paper", "Scissors"])
allow(comp).to receive(:option).and_return("Rock")
expect(comp.option).to eq "Rock"
end

it "it returns paper" do
comp = Computer.new(["Rock", "Paper", "Scissors"])
allow(comp).to receive(:option).and_return("Paper")
expect(comp.option).to eq "Paper"
end

it "it returns scissors" do
comp = Computer.new(["Rock", "Paper", "Scissors"])
allow(comp).to receive(:option).and_return("Scissors")
expect(comp.option).to eq "Scissors"
end
end
end
19 changes: 19 additions & 0 deletions spec/features/choose_player_option_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
feature "Choose option" do
scenario "when player chooses rock, return rock" do
sign_in_and_play
click_button 'Rock'
expect(page).to have_content "Aisha played Rock"
end

scenario "when player chooses paper, return paper" do
sign_in_and_play
click_button 'Paper'
expect(page).to have_content "Aisha played Paper"
end

scenario "when player chooses scissors, return scissors" do
sign_in_and_play
click_button 'Scissors'
expect(page).to have_content "Aisha played Scissors"
end
end