Skip to content

Create rock_paper_scissors.py#1534

Closed
JILORD1507 wants to merge 1 commit intogenlayerlabs:mainfrom
JILORD1507:JILORD1507-patch-1
Closed

Create rock_paper_scissors.py#1534
JILORD1507 wants to merge 1 commit intogenlayerlabs:mainfrom
JILORD1507:JILORD1507-patch-1

Conversation

@JILORD1507
Copy link

@JILORD1507 JILORD1507 commented Mar 15, 2026

Rock Paper Scissors mini game built on GenLayer's Intelligent Contract

What

  • Added a new example contract: rock_paper_scissors.py
  • Player submits a move (rock, paper, scissors)
  • AI validator picks its move using gl.nondet.exec_prompt
  • Consensus reached via gl.eq_principle.strict_eq
  • Winner is determined and scores stored on-chain

Why

  • Adds a fun beginner-friendly example that demonstrates LLM calls with proper consensus in GenLayer

Testing done

  • Deployed and tested on GenLayer Studio testnet
  • Consensus reached successfully
  • All three moves (rock, paper, scissors) tested and working

Decisions made

  • Used gl.nondet.exec_prompt inside a nondet() function to comply with GenLayer's non-determinism rules
  • Used gl.eq_principle.strict_eq to ensure validators reach consensus on the AI move

Checks

  • I have tested this code
  • I have reviewed my own PR
  • I have created an issue for this PR
  • I have set a descriptive PR title compliant with conventional commits

User facing release notes

New example contract: Rock Paper Scissors game where a player competes against an AI validator on-chain.

Summary by CodeRabbit

Release Notes

  • New Features
    • Rock Paper Scissors game playable against an AI opponent with automatic move validation
    • AI-generated responses for every player turn
    • Automatic tracking of player wins, AI wins, and ties
    • View previous game result and current score statistics at any time

Rock Paper Scissors mini game built on genlayer's intelligent contract
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 15, 2026

📝 Walkthrough

Walkthrough

A new Rock Paper Scissors smart contract class is introduced, featuring game state tracking (player wins, AI wins, ties), a play method that validates moves, generates AI moves nondeterministically, computes game outcomes, and updates counters accordingly.

Changes

Cohort / File(s) Summary
Rock Paper Scissors Contract
examples/contracts/rock_paper_scissors.py
New contract implementation with state variables for game statistics, a play() method handling move validation, nondeterministic AI move generation with fallback, outcome calculation, and state updates, plus view methods for retrieving last result and score summary.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A clever contract hops into sight,
Where players challenge the AI's might,
Rock, paper, scissors—the game takes flight,
Counting wins with nondeterministic delight,
State persists through each thrilling fight! 🎲✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly describes the file addition that constitutes the main change in this PR.
Description check ✅ Passed The PR description covers all major template sections: What (feature added), Why (value proposition), Testing done (deployment and testing details), Decisions made (implementation choices), Checks (most completed), and User-facing release notes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

Flake8 can be used to improve the quality of Python code reviews.

Flake8 is a Python linter that wraps PyFlakes, pycodestyle and Ned Batchelder's McCabe script.

To configure Flake8, add a '.flake8' or 'setup.cfg' file to your project root.

See Flake8 Documentation for more details.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
examples/contracts/rock_paper_scissors.py (1)

11-11: Add return annotations to the remaining untyped helpers.

__init__ and the nested nondet helper are the only callables in this file without return annotations, so the example still misses the repo's type-hint requirement.

✍️ Proposed fix
-    def __init__(self):
+    def __init__(self) -> None:
         self.last_result = "No game played yet"
         self.player_wins = 0
         self.ai_wins = 0
         self.ties = 0
@@
-        def nondet():
+        def nondet() -> str:
             res = gl.nondet.exec_prompt(prompt)
             backticks = "``" + "`"
             res = res.replace(backticks + "json", "").replace(backticks, "")

As per coding guidelines, "Include type hints in all Python code".

Also applies to: 37-37

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/contracts/rock_paper_scissors.py` at line 11, Add explicit return
type annotations to the untyped callables: annotate the class initializer def
__init__(self) with -> None and annotate the nested helper nondet(...) with its
appropriate return type (e.g., -> int or -> bool depending on its
implementation) so both signatures include type hints; update the signatures in
the RockPaperScissors class (the __init__ method) and the nested nondet function
to satisfy the repository's "Include type hints in all Python code" rule.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/contracts/rock_paper_scissors.py`:
- Around line 19-21: The code currently silently returns when player_move is
invalid (the player_move.lower().strip() check) which leaves last_result
unchanged; instead, handle invalid moves explicitly by either raising an error
or persisting an explicit invalid outcome: update the same state path that
records the game result (last_result) with a clear status like "invalid move"
and any metadata (original input) or raise a ValueError so callers see the
failure; locate the invalid-check around player_move and modify the branch to
set last_result (or throw) rather than returning silently.
- Around line 37-47: The nondet() helper currently parses JSON and directly
accesses dat["move"], so any parse/key/type errors happen before
gl.eq_principle.strict_eq(nondet) runs; move and implement normalization and
fallback inside nondet(): catch json.loads and KeyError/TypeError, strip and
lower the extracted value (use dat.get("move") safely), verify it's one of
"rock","paper","scissors", and on any error or invalid value return the default
"rock" so strict_eq always receives a deterministic string; keep the existing
backtick-cleaning but ensure all validation and defaulting happens before
returning from nondet().
- Line 2: Replace the wildcard import in
examples/contracts/rock_paper_scissors.py (from genlayer import *) with explicit
imports of only the symbols this example uses: inspect the file to determine
which functions/classes/constants from genlayer are referenced, then change the
import to "from genlayer import <SymbolA>, <SymbolB>, ..." (and update other
example files the same way for consistency) so that F403/F405 lint errors are
avoided without changing global ruff config; if you truly intend to exempt
examples from linting instead, update the repo CI/pre-commit configuration to
exclude the examples directory and document that decision.

---

Nitpick comments:
In `@examples/contracts/rock_paper_scissors.py`:
- Line 11: Add explicit return type annotations to the untyped callables:
annotate the class initializer def __init__(self) with -> None and annotate the
nested helper nondet(...) with its appropriate return type (e.g., -> int or ->
bool depending on its implementation) so both signatures include type hints;
update the signatures in the RockPaperScissors class (the __init__ method) and
the nested nondet function to satisfy the repository's "Include type hints in
all Python code" rule.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 801b7df4-c197-4439-bc80-5f0eb54719ce

📥 Commits

Reviewing files that changed from the base of the PR and between c7962ca and 6646672.

📒 Files selected for processing (1)
  • examples/contracts/rock_paper_scissors.py

@@ -0,0 +1,78 @@
# { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" }
from genlayer import *
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Ruff configuration ==="
configs="$(fd -HI '^(pyproject\.toml|ruff\.toml|\.ruff\.toml|setup\.cfg)$' . || true)"
if [ -n "$configs" ]; then
  while IFS= read -r file; do
    [ -n "$file" ] || continue
    echo "-- $file"
    rg -n -C2 'F403|F405|per-file-ignores|extend-ignore|ignore' "$file" || true
  done <<< "$configs"
else
  echo "No Ruff config files found."
fi

echo
echo "=== GenLayer import patterns in the repo ==="
rg -n -C2 'from genlayer import \*|import genlayer' . || true

Repository: genlayerlabs/genlayer-studio

Length of output: 12946


Clarify whether from genlayer import * is intentional or if examples should be exempt from linting.

This star import pattern is used consistently across the entire codebase (tests, examples, and integration contracts), but F403 and F405 are not listed in the global ignore rules in ruff.toml. Either confirm that examples are excluded from linting in pre-commit or CI configuration, or adopt explicit imports if the SDK API is intended to be visible at the call site.

🧰 Tools
🪛 Ruff (0.15.5)

[error] 2-2: from genlayer import * used; unable to detect undefined names

(F403)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/contracts/rock_paper_scissors.py` at line 2, Replace the wildcard
import in examples/contracts/rock_paper_scissors.py (from genlayer import *)
with explicit imports of only the symbols this example uses: inspect the file to
determine which functions/classes/constants from genlayer are referenced, then
change the import to "from genlayer import <SymbolA>, <SymbolB>, ..." (and
update other example files the same way for consistency) so that F403/F405 lint
errors are avoided without changing global ruff config; if you truly intend to
exempt examples from linting instead, update the repo CI/pre-commit
configuration to exclude the examples directory and document that decision.

Comment on lines +19 to +21
player_move = player_move.lower().strip()
if player_move not in ["rock", "paper", "scissors"]:
return
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't silently accept invalid moves.

Line 21 returns without updating last_result or surfacing an error, so a caller can get a successful write and then read the previous game's result back. Revert here, or persist an explicit invalid-move outcome instead.

🛠️ Proposed fix
         player_move = player_move.lower().strip()
         if player_move not in ["rock", "paper", "scissors"]:
-            return
+            raise ValueError("player_move must be one of: rock, paper, scissors")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
player_move = player_move.lower().strip()
if player_move not in ["rock", "paper", "scissors"]:
return
player_move = player_move.lower().strip()
if player_move not in ["rock", "paper", "scissors"]:
raise ValueError("player_move must be one of: rock, paper, scissors")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/contracts/rock_paper_scissors.py` around lines 19 - 21, The code
currently silently returns when player_move is invalid (the
player_move.lower().strip() check) which leaves last_result unchanged; instead,
handle invalid moves explicitly by either raising an error or persisting an
explicit invalid outcome: update the same state path that records the game
result (last_result) with a clear status like "invalid move" and any metadata
(original input) or raise a ValueError so callers see the failure; locate the
invalid-check around player_move and modify the branch to set last_result (or
throw) rather than returning silently.

Comment on lines +37 to +47
def nondet():
res = gl.nondet.exec_prompt(prompt)
backticks = "``" + "`"
res = res.replace(backticks + "json", "").replace(backticks, "")
dat = json.loads(res)
return dat["move"].lower().strip()

ai_move = gl.eq_principle.strict_eq(nondet)

if ai_move not in ["rock", "paper", "scissors"]:
ai_move = "rock"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Move AI-response fallback inside nondet().

The fallback on Lines 46-47 only runs after gl.eq_principle.strict_eq(nondet) has already succeeded. If the model returns malformed JSON, omits "move", or produces a non-string, json.loads/dat["move"] will raise before you ever reach that branch, and divergent invalid outputs will still fail consensus. Normalize and default inside nondet() so strict_eq sees a deterministic value.

🛠️ Proposed fix
-        def nondet() -> str:
+        def nondet() -> str:
             res = gl.nondet.exec_prompt(prompt)
             backticks = "``" + "`"
-            res = res.replace(backticks + "json", "").replace(backticks, "")
-            dat = json.loads(res)
-            return dat["move"].lower().strip()
+            cleaned = res.replace(backticks + "json", "").replace(backticks, "")
+            try:
+                move = json.loads(cleaned)["move"].lower().strip()
+            except (TypeError, KeyError, json.JSONDecodeError):
+                return "rock"
+            return move if move in ["rock", "paper", "scissors"] else "rock"
 
         ai_move = gl.eq_principle.strict_eq(nondet)
-
-        if ai_move not in ["rock", "paper", "scissors"]:
-            ai_move = "rock"
🧰 Tools
🪛 Ruff (0.15.5)

[error] 38-38: gl may be undefined, or defined from star imports

(F405)


[error] 44-44: gl may be undefined, or defined from star imports

(F405)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/contracts/rock_paper_scissors.py` around lines 37 - 47, The nondet()
helper currently parses JSON and directly accesses dat["move"], so any
parse/key/type errors happen before gl.eq_principle.strict_eq(nondet) runs; move
and implement normalization and fallback inside nondet(): catch json.loads and
KeyError/TypeError, strip and lower the extracted value (use dat.get("move")
safely), verify it's one of "rock","paper","scissors", and on any error or
invalid value return the default "rock" so strict_eq always receives a
deterministic string; keep the existing backtick-cleaning but ensure all
validation and defaulting happens before returning from nondet().

@cristiam86
Copy link
Member

Sorry, not relevant

@cristiam86 cristiam86 closed this Mar 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants