Add-ons / Rock Paper Scissors

A little library to make the rock-paper-scissor logic simpler. Can be used for any number of odd elements (except 1, of course.)

Author Cyrus Firheir
Website https://github.com/cyrusfirheir/cycy-wrote-custom-macros/tree/master/rock-paper-scissors
Story format SugarCube 2
Last checked Wed Oct 07 2020
License Unlicense
Download rps.zip

Index

Overview

A little library to make the rock-paper-scissor logic simpler. Can be used for any number of odd elements (except 1, of course.)

Installation

If using the Twine desktop/web app, copy contents of rock-paper-scissors.js to Story JavaScript.

If using a compiler like Tweego, drop rock-paper-scissors.js to your source folder.

Example usage

The following example uses the 'extended' version of RPS, rock-paper-scissors-lizard-spock.

<<set _rps to new RPS([
    "rock", "spock", "paper", "lizard", "scissors"
])>>

<<set _player to _rps.elements.random()>>
<<set _opponent to _rps.elements.random()>>

You played <<= _player>>.
They played <<= _opponent>>.

<<set _result to _rps.compare(_player, _opponent)>>

<<if _result gt 0>>
    You win!
<<elseif _result lt 0>>
    You lose...
<<else>>
    It's a draw.
<</if>>

Usage - The RPS object

The RPS object does the math to calculate the outcome of a match. Takes in the array of elements as an argument (see how to arrange the elements.)

NOTE: The elements do not have to be a string. Internally, a Map object is used, so the elements can be of any type.

Example:

var rpsTest = new RPS([
    "rock", "paper", "scissors"
]);

JavaScript API

<RPS Object>.elements

<RPS Object>.elements

An array of the elements that was passed in as the argument while instantiating the RPS object.

Example:

var rpsTest = new RPS([
  "rock", "paper", "scissors"
]);

rpsTest.elements; // returns ["rock", "paper", "scissors"]

<RPS Object>.compare(p1, p2)

<RPS Object>.compare(p1, p2)

Calculates the outcome of a match.

Arguments:

Returns:

Example:

var rpsTest = new RPS([
  "rock", "paper", "scissors"
]);

rpsTest.compare("rock", "scissors"); // returns 1. And that's a win.
rpsTest.compare("rock", "paper"); // returns -1. A loss.
rpsTest.compare("rock", "rock"); // returns 0. A draw.

How to arrange the elements

The way any RPS system works is as follows:

  1. There are an odd number of elements to keep the game balanced.
  2. Every element wins against half of the remaining elements, and loses to the other half.

To adhere to those principles, the order of the elements has to follow one simple rule:

No element should have its winning and losing brackets interleave.

That statement can be broken down further to these rules:

  1. Each element has to occur before the entire bracket of elements it loses to.
  2. Each element has to occur after the entire bracket of elements it wins against.

Of course, an array doesn't go on forever on both sides, nor does it make a closed loop, but for this implementation, arrays are cyclic and the positions can freely change given they stay in the same cyclic order. Which means, ABC, BCA, and CAB are all the same.

Some xamples

  1. Classic rock-paper-scissors:

    • The array is ["rock", "paper", "scissors"].
    • Each element's winning match comes before it, and the losing match comes after.
  2. Rock-paper-scissors-lizard-spock:

    • The array is ["rock", "spock", "paper", "lizard", "scissors"].
    • Same with this. Winning matches to the 'left', and losing matches to the 'right'.
    • Note how the two new elements took positions between the existing ones. For the system to stay balanced, this is a guaranteed side-effect.

Live demo

Demo Twee code:

:: Start
<<set _rps to new RPS([
  "rock", "paper", "scissors"
])>>\
<<set _player to _rps.elements.random()>>\
<<set _opponent to _rps.elements.random()>>\
You played <<= _player>>.
They played <<= _opponent>>.
<<set _result to _rps.compare(_player, _opponent)>>\
<<if _result gt 0>>
  You win!
<<elseif _result lt 0>>
  You lose...
<<else>>
  It's a draw.
<</if>>\