'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.
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.
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
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!
Code: Select all
default result = "none"
default selection = "none"
default score = 0
default computer = 0
default ties = 0
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
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 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
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}"
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
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
Code: Select all
label win:
"[selection] beats [result], you win!"
$ score += 1
$ apples += 1
"+1 Apple"
"You have [apples] apples."
jump rps_select
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
Alas, I'm not here to teach you about inventories or feeding Fred apples, so that is best left for the a different cookbook.
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 = ""
Add the following line to your navigation screen found in screens.rpy
screens.rpy
Code: Select all
textbutton _("Cheats") action ShowMenu('cheats')
Code: Select all
default persistent.scissors_only = 0
default persistent.paper_only = 0
default persistent.rock_only = 0
default persistent.unlose = 0
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"
Code: Select all
if persistent.rock_only:
$ result = "rock"
if persistent.paper_only:
$ result = "paper"
if persistent.scissors_only:
$ result = "scissors"
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.
Note: The password for enabling cheats is:
renpy
It must be lowecase.