Another Rock Paper Scissors game

A place for Ren'Py tutorials and reusable Ren'Py code.
Forum rules
Do not post questions here!

This forum is for example code you want to show other people. Ren'Py questions should be asked in the Ren'Py Questions and Announcements forum.
Post Reply
Message
Author
User avatar
Imperf3kt
Lemma-Class Veteran
Posts: 3791
Joined: Mon Dec 14, 2015 5:05 am
itch: Imperf3kt
Location: Your monitor
Contact:

Another Rock Paper Scissors game

#1 Post by Imperf3kt »

3 2 1 GO!
'Rock'

What'd you choose? Did I lose? I can't tell from this side of the screen.

If you're here then you've probably come for some code that allows you to place a game of Rock Paper Scissors into your Visual Novel and not to play against me.
I can understand that, I can play against somebody else.

Before we begin, allow me to inform you that I am by no means an expert. The methods described herein are only how I achieved my desired result. They may not be standard or optimised and there may be better ways to achieve the same result. A lot of the work here was trial and error until I got it to function as desired.

So I guess you want that code huh? Well first, lets take a quick look at what Rock Paper Scissors actually is. Those of you who know may want to skip this bit.

Rock Paper Scissors
Rock Paper Scissors is a zero-sum game where the objective is to select one of three moves in order to beat your opponent. Games are typically played 'best of three' where the overall winner is determined by whichever player has the most wins out of three games.

In a classic game of Rock Paper Scissors, the moves available are (as you may have guessed):
- Rock - a clenched fist, resembling a rock.
- Paper - a flat palm, resembling a leaflet of parchment.
- Scissors - index and middle finger outstretched in the shape of a "V", resembling the blades of a pair of scissors.

Rock smashes scissors but is vulnerable to paper. Rock can be considered a 'brute force' type attack.
Paper wraps Rock but is easily cut by Scissors. Paper can be considered a 'sealing' attack.
Scissors cuts Paper but is smashed by Rock. Scissors can be considered a conservative attack.

In other words, Rock beats Scissors, Scirrors beats Paper and Paper beats Rock.

You may wish to visit the Wikipedia article for more information about the game, its history and its role in historical events. It is a rather fascinating read.

THE CODE
So now you know a little bit about Rock Paper Scissors, you may be wondering how we implement it in a computer game.
I use the following method, which uses a little bit of Python and some core Ren'Py functions.

You can use this too, attribution not necessary. Simply place the file in your game folder and thats it, well... almost.
To use the game we need to call it from somewhere in our script.
Lets use an example:

Code: Select all

label start:
    call rps_select
    return
This (plus one of the two files at the bottom of this post) is about all you need to put the game in your own game, but of course, this is the cookbook section! Its no fun to just dump some code and leave. So lets go over the processes involved!

You can find the necessary files at the end of this post, but those of you who want to read about the script and functions used, read on. Otherwise, skip to the end, download the file, and drop it in your game folder, add the above code somewhere and enjoy!

THE BREAK-DOWN
Defaults

Code: Select all

default result = "none"
default selection = "none"
default score = 0
default computer = 0
default ties = 0
This section of the script defaults all the variables we use during the game. This is required for keeping track of scores and both yours and the AI's selection. Generally, these values should not be adjusted.

Choice Menu

Code: Select all

label rps_select:
    menu:
        "Rock":
            $selection = "rock"
            $result = renpy.random.choice(['rock', 'paper', 'scissors'])
        "Paper":
            $selection = "paper"
            $result = renpy.random.choice(['rock', 'paper', 'scissors'])
        "Scissors":
            $selection = "scissors"
            $result = renpy.random.choice(['rock', 'paper', 'scissors'])
        "End":
            return
This is where Ren'Py will begin the Rock Paper Scissors game. It utilises a simple choice menu for maximum compatibility.
The player is presented four choices:
- Rock
- Paper
- Scissors
- End

By selecting Rock, Paper or Scissors, two variables are updated; selection and result. 'selection' keeps track of a player's chosen move, likewise, 'result' tracks the computer's move. The computer's choice is determined at random using the renpy.random.choice function to select one of the three options (rock, paper or scissors).

The final option, 'End', allows a player to exit the Rock Paper Scissors minigame and return to the narrative.

Since there is no jump or any other action provided, Ren'Py continues to the next label after a choice has been made (except of course, if 'End' was chosen).

The Decider

Code: Select all

label results:

####rock####

    if result == "rock":
        if selection == "rock":
            jump tie
        elif selection == "paper":
            jump win
        elif selection == "scissors":
            jump lose

####paper####

    elif result == "paper":
        if selection == "rock":
            jump lose
        elif selection == "paper":
            jump tie
        elif selection == "scissors":
            jump win

####scissors####

    elif result == "scissors":
        if selection == "rock":
            jump win
        elif selection == "paper":
            jump lose
        elif selection == "scissors":
            jump tie

This section of the script is where the winner is calculated. It starts by checking the variable 'result', to determine what move the computer made. Once it finds which move was made, another variable is checked - the player's move.
This is compared against the computer's move and accordingly jumps to an associated label.

This is mostly self-explanatory so I won't go in-depth about it.

Once the results are evaluated, Ren'Py continues at one of the following labels:
- tie
- win
- lose

These labels modify the score and then return the player to 'rps_select', to either continue playing or end the game.

The Outcome

Code: Select all


label tie:
    
    "We tied with [result]!"
    $ ties += 1
    jump rps_select
    
label win:
    
    "[selection] beats [result], you win!"
    $ score += 1
    jump rps_select
    
label lose:
    
    "[result] beats [selection], you lost!"
    $ computer += 1
    jump rps_select
The Score
Now that we have established the basic structure of the minigame, it's time to do something with it.

NOTICE:
Because there is no real skill involved, only pot luck, I highly discourage you from using the game to determine anything related to the ending a player reaches or other likewise similarly important features and events. Many players are very agitated by RNGesus dependant gameplay and this has a negative effect on your audience. Be mindful of this when implementing this minigame.

The first thing you may want to do with the score is show it to the player. For this, we will require a screen.
Lets go ahead and make a very simple screen which we will call "stats":

Code: Select all

screen stats():
    
    modal False
    zorder 100
    vbox:
        xalign 0.5
        ypos 0.01
        text "{color=#000}Computer: [computer] You: [score] Ties: [ties]{/color}"
Adding "modal False" allows a player to click as if the screen were not there.
A zorder greater than 50 is necessary for our screen to show on top of all other layers, so I've chosen 100 but depending on what you've added to your game you may not require this at all. The bigger the number, the more "to the front" the screen will show.

A simple vbox aligned to the screen top center contains our text and some interpolated variables. Use a HTML color-hex hash value to change the color - current color is black.
No styling has been added, so the text in this screen will inherit the interface styles. You can add styles if you want or even images, animation, backgrounds etc, but that is not part of this cookbook entry. There are plenty of other examples for those, so I won't be covering any here.

So our screen is ready, how do we put that in-game. The simplest method is to "show screen stats" when you want the screen to appear. We want the screen visible at all times the game is running, so we add it at the very start of our script like so:

Code: Select all

label rps_select:
    show screen stats
We'll want to remove this screen upon ending the minigame, so we add "hide screen stats" to our choice menu option "End":

Code: Select all

            "End":
                hide screen stats
                return

And there you have it, one basic stats screen which updates itself each time a user returns to the choice menu.

Whats that? You want even more functionality? You want to award players gacha prizes for winning?
No problem!

Lets go ahead and make some items to give the player. We'll start with apples.
This goes in the default section.

Code: Select all

default apples = 0
Great, now there needs to be a way to give them to the player. Lets go to the "win" label:

Code: Select all

label win:
    
    "[selection] beats [result], you win!"
    $ score += 1
    $ apples += 1
    "+1 Apple"
    "You have [apples] apples."
    jump rps_select
Cool, so whenever the player wins they'll get an apple, isn't that great?!
you can add this to the stats screen or anywhere else in your game or even change dialogue etc based on how many apples a player has.
Example:

Code: Select all

define f = Character("Fred")

label start:
    f "I need some apples, do you have three?"
    menu:
        "You think to yourself for a moment..."
        
        "Yeah" if apples >= 3:
            jump fred_fed
        "Yeah" if apples <3:
            jump you_liar
        "No, sorry.":
            jump fred_starves
There are many other things you could use this for and just as many other ways of achieving it, for example, you could use an inventory system to track gacha like items.

Alas, I'm not here to teach you about inventories or feeding Fred apples, so that is best left for the a different cookbook.

Additional Fun
But wait, there's more!
But how can there possibly be more, you ask? How about we allow the player to cheat.

What's that, 'How do you cheat when you don't know what move the computer is making?' Why, we just tell the computer what move we want it to make, based on our player's choice of course!

First up we're gonna need to add a few extra things. These additions are:
- Some variables to track cheating
- A screen to enable and disable the cheating
- (Optional) A password system to award players the ability to cheat only after clearing the game once
- A way to force the computer to use a losing move based on the player's move.

So lets get to it. I'll use the same logic as set out earlier to make the screen and add variables that will be required. I won't be explaining this one in as much detail, sorry.

Code: Select all

screen cheats():
    default unlock_cheats = False

    tag menu

    use game_menu(_("Cheats"), scroll="viewport"):

        style_prefix "cheats"
        

        vbox:
            textbutton _("Enable cheats") action ToggleScreenVariable("unlock_cheats")
            if unlock_cheats:
                input:
                    value VariableInputValue('nolose', returnable=False)
        
        if nolose == "renpy":
            
            vbox:
                label _("Never Lose")
                hbox:
                    textbutton _("On") action SetField(persistent, "unlose", 1)
                    textbutton _("Off") action SetField(persistent, "unlose", 0)
            
            vbox:
                label _("Rock on!")
                hbox:
                    textbutton _("On") action SetField(persistent, "rock_only", 1)
                    textbutton _("Off") action SetField(persistent, "rock_only", 0)
            
            vbox:
                label _("Papercut!")
                hbox:
                    textbutton _("On") action SetField(persistent, "paper_only", 1)
                    textbutton _("Off") action SetField(persistent, "paper_only", 0)
                        
            vbox:
                label _("Blades!")
                hbox:
                    textbutton _("On") action SetField(persistent, "scissors_only", 1)
                    textbutton _("Off") action SetField(persistent, "scissors_only", 0)


style cheats_label is gui_label
style cheats_label_text is gui_label_text
style cheats_text is gui_text

style cheats_label_text:
    size gui.label_text_size

default nolose = ""
That's the screen done, but we're not done yet, we still need to make it accessible.
Add the following line to your navigation screen found in screens.rpy

screens.rpy

Code: Select all

textbutton _("Cheats") action ShowMenu('cheats')
Alright, let's head over to the script again, we need to add a bunch of stuff so we can take advantage of these new settings we just created.

Code: Select all

default persistent.scissors_only = 0
default persistent.paper_only = 0
default persistent.rock_only = 0
default persistent.unlose = 0
That takes care of the persistents. Now to modify the game code so it recognises the difference between when we're cheating and when we're not.

Code: Select all

label rps_select:
    
    if not persistent.unlose:
        menu:
            
            "Rock":
                $selection = "rock"
                $result = renpy.random.choice(['rock', 'paper', 'scissors'])
            "Paper":
                $selection = "paper"
                $result = renpy.random.choice(['rock', 'paper', 'scissors'])
            "Scissors":
                $selection = "scissors"
                $result = renpy.random.choice(['rock', 'paper', 'scissors'])
            "End":
                return
        
    else:
        menu:
            
            "Rock":
                $selection = "rock"
                $result = "scissors"
            "Paper":
                $selection = "paper"
                $result = "rock"
            "Scissors":
                $selection = "scissors"
                $result = "paper"
            "End":
                return
                
    if not persistent.unlose:
        
        if persistent.rock_only:
            $ result = "rock"
        if persistent.paper_only:
            $ result = "paper"
        if persistent.scissors_only:
            $ result = "scissors"
There is a bug here guys, can you spot it.

Code: Select all

    if persistent.rock_only:
        $ result = "rock"
    if persistent.paper_only:
        $ result = "paper"
    if persistent.scissors_only:
        $ result = "scissors"
While this won't cause the game to error out or anything, it does mean that if a player enables more than one cheat simultaneously, they'll end up with unexpected results.
For example, if I enable rock_only - which should force the result to be rock - and I also enable scissors_only, then the result will always be scissors instead.
I'm looking into making this better, but for now this is what I have. I'll update things if I discover a fix.


I hope you enjoyed this wall of text and that it helps you in your adventures in Ren'Py. If you encounter any issues while using this code or just want to ask anything, feel free to reply.

rps.rpy
(1.92 KiB) Downloaded 224 times
rps_with_cheats.rpy
(4.51 KiB) Downloaded 145 times
Note: The password for enabling cheats is:
renpy
It must be lowecase.
Warning: May contain trace amounts of gratuitous plot.
pro·gram·mer (noun) An organism capable of converting caffeine into code.

Current project: GGD Mentor

Twitter

User avatar
gas
Miko-Class Veteran
Posts: 842
Joined: Mon Jan 26, 2009 7:21 pm
Contact:

Re: Another Rock Paper Scissors game

#2 Post by gas »

I can suggest you a trick to streamline the code.
Instead of checking for each move, you can use a list like

Code: Select all

default winners=[0,1,2,0]
Use numbers instead of strings ( rock is 0, scissors is 1, paper is 2)
Same number, is a tie. If computer sign index is the very next in the list, you win.
The check is

Code: Select all

if selection==winners[result+1]:
    "You won!"
No other check: what's left is obviously a win for the opponent!
The whole engine in 5 lines (and you can come with games with 4, 5, 100 signs)
If you want to debate on a reply I gave to your posts, please QUOTE ME or i'll not be notified about. << now red so probably you'll see it.

10 ? "RENPY"
20 GOTO 10

RUN

Human Bolt Diary
Regular
Posts: 111
Joined: Fri Oct 11, 2013 12:46 am
Contact:

Re: Another Rock Paper Scissors game

#3 Post by Human Bolt Diary »

You can simplify the decision checks by replacing the nested if statements with dictionaries. This will also make it easier to implement more options (Rock, paper, scissors, lizard, spock, etc)
The following is untested, but should give you the general idea:

Code: Select all

label results:

    rock_vs = {
        "rock": "tie",
        "paper": "win",
        "scissors": "lose",
    }

    paper_vs = {
        "rock": "lose",
        "paper": "tie",
        "scissors": "win",
    }

    scissors_vs = {
        "rock": "win",
        "paper": "lose",
        "scissors": "tie",
    }

    result_mapping = {
        "rock": rock_vs,
        "paper": paper_vs,
        "scissors": scissors_vs,
    }

    outcome = result_mapping[result][selection]
    $ renpy.jump(outcome)


User avatar
Imperf3kt
Lemma-Class Veteran
Posts: 3791
Joined: Mon Dec 14, 2015 5:05 am
itch: Imperf3kt
Location: Your monitor
Contact:

Re: Another Rock Paper Scissors game

#4 Post by Imperf3kt »

Thanks both of you.
I'll update the first post a bit later with the suggested changes.

Many thanks about the dictionaries, I looked into using them but they were confusing and I couldn't get them to work so just went with lists and nested conditionals.
Warning: May contain trace amounts of gratuitous plot.
pro·gram·mer (noun) An organism capable of converting caffeine into code.

Current project: GGD Mentor

Twitter

User avatar
Imperf3kt
Lemma-Class Veteran
Posts: 3791
Joined: Mon Dec 14, 2015 5:05 am
itch: Imperf3kt
Location: Your monitor
Contact:

Re: Another Rock Paper Scissors game

#5 Post by Imperf3kt »

Been working on this a bit lately.

I was not able to get the previous suggestions to work, but I have made a small number of improvements which I'll add sometime soon.

My main focus has been working on an Android version, which currently looks like this
https://i.imgur.com/OCNxGxG.png
Warning: May contain trace amounts of gratuitous plot.
pro·gram·mer (noun) An organism capable of converting caffeine into code.

Current project: GGD Mentor

Twitter

Post Reply

Who is online

Users browsing this forum: No registered users