diff --git a/Gemfile b/Gemfile index ef237cf7c6..9b417c0b73 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,7 @@ source 'https://rubygems.org' ruby '2.5.0' gem 'rake' +gem 'shotgun' gem 'sinatra' group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 52a9a2fe0e..5b8f718ae1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -54,6 +54,8 @@ GEM ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) ruby-progressbar (1.9.0) + shotgun (0.9.2) + rack (>= 1.0) simplecov (0.15.1) docile (~> 1.1.0) json (>= 1.8, < 3) @@ -81,6 +83,7 @@ DEPENDENCIES rake rspec rubocop (= 0.56.0) + shotgun simplecov simplecov-console sinatra @@ -89,4 +92,4 @@ RUBY VERSION ruby 2.5.0p0 BUNDLED WITH - 1.16.1 + 1.16.2 diff --git a/README.md b/README.md index 67c0606a28..868837f2a9 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,10 @@ # RPS Challenge -Instructions -------- +[User Stories](#user-stories) | [Game Rules](#rules) | [Technologies](#technologies) | [User Interaction and Interaction](#experience) | [Running the App](#running-app) | [Running tests](#tests) | [Approach](#approach) | [Contributing](#contributing) -* Challenge time: rest of the day and weekend, until Monday 9am -* 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 - -Task +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: - ```sh As a marketeer So that I can see my name in lights @@ -28,59 +15,89 @@ 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 +Game Rules +--- -- 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 +- Rock beats Scissors +- Scissors beats Paper +- Paper beats Rock +- Lizard beats Paper +- Lizard beats Spock +- Spock beats Rock +- Spock beats Scissors +- Rock beats Lizard +- Scissors beats Lizard +- Paper beats Spock + Technologies +--- -As usual please start by +- Ruby +- RSpec +- Capybara +- CSS -* Forking this repo -* TEST driving development of your app + User Interaction and Experience +--- +![1](./assets/1.png) -## Bonus level 1: Multiplayer +![2](./assets/2.png) -Change the game so that two marketeers can play against each other ( _yes there are two of them_ ). +![3](./assets/3.png) -## Bonus level 2: Rock, Paper, Scissors, Spock, Lizard + Running the app +--- -Use the _special_ rules ( _you can find them here http://en.wikipedia.org/wiki/Rock-paper-scissors-lizard-Spock_ ) +- Clone this repository +``` +$ git clone https://github.com/m-rcd/rps-challenge +$ cd rps-challenge +``` +- Install dependencies: +``` +$ gem install +``` +- Run the app +``` +$ rackup +``` +- In your browser, go to +``` +localhost:9292 +``` -## Basic Rules +Running the tests +--- +- Run +``` +$ rspec +``` -- Rock beats Scissors -- Scissors beats Paper -- Paper beats Rock +![test](./assets/test.png) -In code review we'll be hoping to see: +Approach +--- -* All tests passing -* High [Test coverage](https://github.com/makersacademy/course/blob/master/pills/test_coverage.md) (>95% is good) -* The code is elegant: every class has a clear responsibility, methods are short etc. +- Using TDD, I started by creating a web app that allow the user to enter their name and choose rock, paper or scissors. +- I then created a file called game.rb which contained the game logic. +- I also added a player class to allow the user to have a name +- I added a computer class which choose an option randomly. +- I then added a result page which would declare the winner +- I added a button that allows the user to play again +- I added the option to play a multiplayer game: 2 players can add their names, each choose an option and then see the winner +- I then added 2 options to the game: lizard and Spock +- I added the option to start a new game after a game ends +- Having finished implementing its functionality ,I used css to make it look nice -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 weekend. + Contributing +--- -Notes on test coverage ----------------------- +Pull Requests are always welcome. -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: +When you edit the code, please run `rspec` to check all the tests pass. Also run `rubocop` before you git commit. -```ruby -require 'simplecov' -require 'simplecov-console' +Ensure the PR description clearly describes the problem and solution. It should include the relevant issue number, if applicable. -SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ - SimpleCov::Formatter::Console, - # Want a nice code coverage website? Uncomment this next line! - # SimpleCov::Formatter::HTMLFormatter -]) -SimpleCov.start -``` -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! +[Play now!](https://stark-journey-20582.herokuapp.com/) diff --git a/app.rb b/app.rb new file mode 100644 index 0000000000..b56953226b --- /dev/null +++ b/app.rb @@ -0,0 +1,68 @@ +require 'sinatra/base' +require './lib/game' +require './lib/player' + +class RPS < Sinatra::Base + enable :sessions + + get '/' do + erb :index + end + + post '/game_mode' do + session[:game_mode] = params[:game_mode].to_sym + @gamemode = session[:game_mode] + erb :name_input + end + + post '/name' do + player1 = Player.new(params[:player1]) + if @gamemode == :singleplayer + player2 = Player.new('Skynet') + else + player2 = Player.new(params[:player2]) + end + session[:game] = Game.new(player1, player2) + redirect '/play' + end + + get '/play' do + @game = session[:game] + erb :optionp1 + end + + post '/optionp1' do + @gamemode = session[:game_mode] + session[:player1_choice] = params[:commit] + if @gamemode == :singleplayer + redirect '/result' + else + redirect '/playp2' + end + end + + get '/playp2' do + @game = session[:game] + erb :optionp2 + end + + post '/optionp2' do + session[:player2_choice] = params[:option] + redirect '/result' + end + + get '/result' do + @game = session[:game] + @gamemode = session[:game_mode] + @game.player1.choice(session[:player1_choice]) + if @gamemode == :singleplayer + @game.player2.choice(@game.computer_choice) + else + @game.player2.choice(session[:player2_choice]) + end + @result = @game.play + erb :result + end + + run! if app_file == $0 +end diff --git a/assets/1.png b/assets/1.png new file mode 100644 index 0000000000..d416cc8aec Binary files /dev/null and b/assets/1.png differ diff --git a/assets/2.png b/assets/2.png new file mode 100644 index 0000000000..7b3f45a1ba Binary files /dev/null and b/assets/2.png differ diff --git a/assets/3.png b/assets/3.png new file mode 100644 index 0000000000..af19e5c13c Binary files /dev/null and b/assets/3.png differ diff --git a/assets/lizard.png b/assets/lizard.png new file mode 100644 index 0000000000..8ce5472642 Binary files /dev/null and b/assets/lizard.png differ diff --git a/assets/paper.png b/assets/paper.png new file mode 100644 index 0000000000..5101d4e1f7 Binary files /dev/null and b/assets/paper.png differ diff --git a/assets/rock.jpg b/assets/rock.jpg new file mode 100644 index 0000000000..6a451dfedb Binary files /dev/null and b/assets/rock.jpg differ diff --git a/assets/scissors.png b/assets/scissors.png new file mode 100644 index 0000000000..9122d4cecd Binary files /dev/null and b/assets/scissors.png differ diff --git a/assets/spock.png b/assets/spock.png new file mode 100644 index 0000000000..b781790488 Binary files /dev/null and b/assets/spock.png differ diff --git a/assets/test.png b/assets/test.png new file mode 100644 index 0000000000..280e9c2eaa Binary files /dev/null and b/assets/test.png differ diff --git a/config.ru b/config.ru new file mode 100644 index 0000000000..41f5249536 --- /dev/null +++ b/config.ru @@ -0,0 +1,2 @@ +require_relative "./app" +run RPS diff --git a/lib/game.rb b/lib/game.rb new file mode 100644 index 0000000000..92cf20a8a3 --- /dev/null +++ b/lib/game.rb @@ -0,0 +1,27 @@ +class Game + attr_reader :player1, :result, :player2 + + def initialize(player1, player2) + @player1 = player1 + @player2 = player2 + @key_beats_value = { :Rock => [:Scissors, :Lizard], :Paper => + [:Rock, :Spock], :Scissors => [:Paper, :Lizard], + :Spock => [:Scissors, :Rock], :Lizard => [:Spock, :Paper] } + end + + def play + p1 = player1.player_choice.to_sym + p2 = player2.player_choice.to_sym + if @key_beats_value[p1].include?(p2) + 'player1' + elsif @key_beats_value[p2].include?(p1) + 'player2' + else + 'draw' + end + end + + def computer_choice + ['Rock', 'Paper', 'Scissors', 'Spock', 'Lizard'].sample + end +end diff --git a/lib/player.rb b/lib/player.rb new file mode 100644 index 0000000000..28a631bace --- /dev/null +++ b/lib/player.rb @@ -0,0 +1,11 @@ +class Player + attr_reader :name, :player_choice + + def initialize(name) + @name = name + end + + def choice(choice) + @player_choice = choice + end +end diff --git a/public/css/main.css b/public/css/main.css new file mode 100644 index 0000000000..ab5c387ce7 --- /dev/null +++ b/public/css/main.css @@ -0,0 +1,75 @@ +.button { + display: inline-block; + width: 120px; + background-color: #8A2BE2 ; + border: 2px solid white; + color: white; + text-decoration: none; + text-align: center; + border-radius: 4px; + margin-left: 5px; + margin-right: 5px; + margin-bottom: 20px; + padding-bottom: 2px; + padding-top: 2px; + font-size: 16px; + font-family: Times, Georgia, Serif; + cursor: pointer; +} + +body { + margin-top: 100px; + background-color: black; + color: white; + font-family: "Courier New", Courier, monospace; + font-size: 20px; +} + + +body a { + color: white; + text-decoration: none; + font-family: "Courier New", Courier, monospace; + font-size: 65px; + text-align: center; + margin: 60px; + display: block; +} + +h1 { + text-align: center; + font-size: 60px; + +} + +.im { + position: relative; + top: -140px; + right: -100px; +} + + +form { + text-align: center; +} + +p { + text-align: center; + font-size: 70px; +} +h2 { + text-align: center; + font-size: 50px; +} + +form input[type='text'] { + border: 2px solid #8A2BE2 ; + font-family: "Courier New", Courier, monospace; + font-size: 16px; + text-align: center; + width: 200px; + padding: 3px; + margin-top: 40px; + background-color: white; + +} diff --git a/public/css/reset.css b/public/css/reset.css new file mode 100644 index 0000000000..549ba202ab --- /dev/null +++ b/public/css/reset.css @@ -0,0 +1,47 @@ +/** + * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) + * http://cssreset.com + */ +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/spec/features/homepage_spec.rb b/spec/features/homepage_spec.rb new file mode 100644 index 0000000000..9d3c53e0cc --- /dev/null +++ b/spec/features/homepage_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' +require './app' + +RSpec.feature 'Testing homepage' do + scenario 'have game title' do + visit '/' + expect(page).to have_content 'ROCK PAPER SCISSORS LIZARD SPOCK!' + end +end diff --git a/spec/features/multiplayer_spec.rb b/spec/features/multiplayer_spec.rb new file mode 100644 index 0000000000..f8443ee284 --- /dev/null +++ b/spec/features/multiplayer_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' +require './app' + +RSpec.feature 'testing multiplayer' do + scenario 'player1 can enter name' do + visit '/' + click_button 'multiplayer' + expect(page).to have_field('player1') + end + + scenario 'player2 can enter name' do + visit '/' + click_button 'multiplayer' + expect(page).to have_field('player2') + end + + scenario 'player 1 wins' do + multi_name_play + click_button 'Rock' + click_button 'Scissors' + expect(page).to have_content('Claudia wins! :D') + end + + scenario 'player 2 wins' do + multi_name_play + click_button 'Paper' + click_button 'Scissors' + expect(page).to have_content('Marianne wins! :D') + end + + scenario 'draw' do + multi_name_play + click_button 'Paper' + click_button 'Paper' + expect(page).to have_content("It's a tie! Try again!") + end +end diff --git a/spec/features/playsolo_spec.rb b/spec/features/playsolo_spec.rb new file mode 100644 index 0000000000..0fd842885c --- /dev/null +++ b/spec/features/playsolo_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' +require './app' + +RSpec.feature 'Testing solo' do + scenario 'player can enter name' do + visit '/' + click_button 'singleplayer' + expect(page).to have_field('player1') + end + + scenario 'player can start the game' do + visit '/' + click_button 'singleplayer' + expect(page).to have_button('Play') + end + + scenario 'player wins' do + solo_name_play + allow_any_instance_of(Array).to receive(:sample).and_return('Paper') + click_button 'Scissors' + expect(page).to have_content 'Claudia wins! :D' + end + + scenario 'computer wins' do + solo_name_play + allow_any_instance_of(Array).to receive(:sample).and_return('Paper') + click_button 'Rock' + expect(page).to have_content 'Skynet wins! :(' + end + + scenario 'draw' do + solo_name_play + allow_any_instance_of(Array).to receive(:sample).and_return('Paper') + click_button 'Paper' + expect(page).to have_content "It's a tie! Try again!" + end +end diff --git a/spec/features/web_helpers.rb b/spec/features/web_helpers.rb new file mode 100644 index 0000000000..8e35437cdb --- /dev/null +++ b/spec/features/web_helpers.rb @@ -0,0 +1,14 @@ +def solo_name_play + visit '/' + click_button 'singleplayer' + fill_in 'player1', with: 'Claudia' + click_button 'Play' +end + +def multi_name_play + visit '/' + click_button 'multiplayer' + fill_in 'player1', with: 'Claudia' + fill_in 'player2', with: 'Marianne' + click_button 'Play' +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ff9361ad27..b3b21229bc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,13 @@ require 'capybara/rspec' require 'simplecov' require 'simplecov-console' +require File.join(File.dirname(__FILE__), '..', 'app.rb') +require 'capybara' +require 'rspec' +require 'features/web_helpers' + +Capybara.app = RPS +ENV['RACK_ENV'] = 'test' SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ SimpleCov::Formatter::Console, diff --git a/spec/unit/game_spec.rb b/spec/unit/game_spec.rb new file mode 100644 index 0000000000..4004d6d1cb --- /dev/null +++ b/spec/unit/game_spec.rb @@ -0,0 +1,42 @@ +require 'game' + +describe Game do + let(:player1) { double :player } + let(:player2) { double :player } + + subject(:game) { described_class.new(player1, player2) } + + context '#initialize' do + it 'takes player1 as argument' do + expect(game.player1).to eq player1 + end + + it 'takes player2 as a second argument' do + expect(game.player2).to eq player2 + end + end + + context '#play versus player2' do + subject(:game) { described_class.new(player1, player2) } + + it 'returns player1 as the winner' do + allow(player1).to receive(:player_choice) { 'Scissors' } + allow(player2).to receive(:player_choice) { 'Paper' } + expect(game.play).to eq 'player1' + end + + it 'returns player2 as winner' do + allow(player1).to receive(:player_choice) { 'Rock' } + allow(player2).to receive(:player_choice) { 'Paper' } + + expect(game.play).to eq 'player2' + end + + it 'returns draw when same option' do + allow(player1).to receive(:player_choice) { 'Paper' } + allow(player2).to receive(:player_choice) { 'Paper' } + + expect(game.play).to eq 'draw' + end + end +end diff --git a/spec/unit/player_spec.rb b/spec/unit/player_spec.rb new file mode 100644 index 0000000000..9ee049be40 --- /dev/null +++ b/spec/unit/player_spec.rb @@ -0,0 +1,17 @@ +require 'player' + +describe Player do + subject(:player) { described_class.new('claudia') } + context '#initialize' do + it 'returns its name' do + expect(player.name).to eq 'claudia' + end + end + + context '#choice' do + it "returns player's choice" do + player.choice('Paper') + expect(player.player_choice).to eq 'Paper' + end + end +end diff --git a/views/index.erb b/views/index.erb new file mode 100644 index 0000000000..704f7f41b7 --- /dev/null +++ b/views/index.erb @@ -0,0 +1,17 @@ +

Select game mode

+
+
+ + +
+ +
+ +
+ +
+ + + + + diff --git a/views/layout.erb b/views/layout.erb new file mode 100644 index 0000000000..b65cb01d6e --- /dev/null +++ b/views/layout.erb @@ -0,0 +1,16 @@ + + + + ROCK PAPER SCISSORS LIZARD SPOCK! + + + + + ROCK PAPER SCISSORS LIZARD SPOCK! +
+ + <%= yield %> + + + diff --git a/views/name_input.erb b/views/name_input.erb new file mode 100644 index 0000000000..fb7bfbfa50 --- /dev/null +++ b/views/name_input.erb @@ -0,0 +1,26 @@ +<% if @gamemode == :multiplayer %> +

Please enter your names to play!

+
+ +
+ + + +
+<%else%> +

Please enter your name to play!

+
+
+ + +
+<%end%> diff --git a/views/optionp1.erb b/views/optionp1.erb new file mode 100644 index 0000000000..4a6efa0570 --- /dev/null +++ b/views/optionp1.erb @@ -0,0 +1,9 @@ +

Choose an option, <%= @game.player1.name.capitalize %>

+
+
+ +
+ <% ['Rock', 'Paper', 'Scissors', 'Lizard', 'Spock'].each do |choice| %> + > + <% end %> +
diff --git a/views/optionp2.erb b/views/optionp2.erb new file mode 100644 index 0000000000..c6957cf4f2 --- /dev/null +++ b/views/optionp2.erb @@ -0,0 +1,11 @@ +

Choose an option, <%= @game.player2.name.capitalize %>

+
+
+ +
+ + + + + +
diff --git a/views/result.erb b/views/result.erb new file mode 100644 index 0000000000..6af8a48485 --- /dev/null +++ b/views/result.erb @@ -0,0 +1,29 @@ +
+
+<% if @gamemode == :singleplayer %> +

Skynet chose <%= @game.player2.player_choice %>

+
+
+<%end%> + +<% if @result == 'player1' %> +

<%= @game.player1.name.capitalize %> wins! :D

+<% elsif @result == 'player2' %> + <% if @gamemode == :singleplayer %> +

Skynet wins! :(

+ <% else %> +

<%= @game.player2.name.capitalize %> wins! :D

+ <% end %> +<% elsif @result == 'draw' %> +

It's a tie! Try again!

+<% end %> + +
+
+ +
+ +
+
+ +