Maker.io main logo

Human vs Robot – Rock Paper Scissors with MyCobot 280 M5Stack

2025-11-24 | By Mirko Pavleski

License: General Public License Arduino M5Stack

Today I received a package containing a few Elephant Robotics products. The shipment is well packaged and fully protected from mechanical impacts during transport. The package contains the myCobot 280 M5 robot arm, myCobot Camera Flange 2.0, and myCobot Adaptive Gripper. There is also a G-shape Base 2.0, which serves to attach the robot arm to the base.

f

The main component in this kit is the Robotic Arm. Тhe box contains the Robotic Arm, power supply, a USB cable, and wires for connecting external modules. There are also the basic instructions, certificate, and links. From the very first glance, it is clear that it is solidly made and of high quality, and I will examine its functionality later.

MyCobot 280 is a 6-DOF collaborative robot (Cobot), which weighs 850g, has a payload of 250g, and an effective working radius of 280mm. It is small in size but powerful in function. It can be adapted to various application scenarios such as scientific research, education, smart home, and commercial research. As the name suggests, this model features an m5Stack core, with three buttons as well as a large number of input/output ports and pins for controlling additional external devices. It supports multiple programming languages ​​like Python, C++, Arduino, C#, etc., and also the platforms Windows, Android, Mac OSX, and Linux. The overall structure of the mechanical arm is simple and stylish and contains 6 high-performance servo motors with fast response, small inertia, and smooth rotation.

hj

And before I start describing my custom project, I want to describe a simple way of "drag teaching". Drag Teaching is an intuitive method for programming the movements of a robotic arm, especially common in cobots and specialized industrial robots. Instead of using complex code, the operator can literally grab the robot arm and physically guide it through the desired path. The robot can then reproduce the recorded path with high precision at normal operating speed. In this way, we can most easily capture the performance of this Robotic Arm, such as accuracy, speed, and smoothness of motion.

The following is a description of the main project that will demonstrate the functionality of all three components: the Robotic Arm, the camera, and the gripper. So I decided to develop a version of the well-known game Rock-Paper-Scissors in which each player simultaneously forms one of three shapes with an outstretched hand - Rock, Paper, and Scissors.

vbncm

At a specific moment, which will be indicated on the display and with an audio signal, the human and the robot will show one of the three shapes, and then, according to the rules of the game, the winner will be declared. Let me emphasize that the robot generates a shape completely randomly, and at the same moment, when it receives information that the human has displayed a shape.

 

Let's start with the Python part. Here we use myCobot Camera Flange 2.0, which is connected to the PC via a USB cable. On Windows 10 and Windows 11 OS, the camera is installed automatically. First of all, a Python environment with appropriate support (libraries) should be installed on the PC. Python version 3.11 is installed from the given link, and during installation, the "Add Python to PATH" option should be checked.

bvh

The Python program can use multiple tools to perform the given task. First, I created a code with the OpenCV library, but the ability to determine the shape of the hand was relatively weak. Then I created a code with the TensorFlow platform, which worked well, but the whole installation procedure was very complex. In the end, I found the simplest but functional solution using the MediaPipe framework from Google.

hbui

It is necessary to install several components by running these two lines in the CMD (command prompt) one by one:

python -m pip install --upgrade pip

python -m pip install "mediapipe==0.10.20" opencv-python pyserial

At the end of this text, the Python code named "MediaPIPE_Code" is given. We just need to start this Python script, and in the CMD window, we need to select the serial port on which the M5Stack microcontroller is installed. In my case, it is COM17 (meaning option 1). Now a window with the video camera appears, and now we need to show the appropriate shape in front of the camera. As you can see, the program detects the three shapes with great accuracy. In the command window, you can see the result that is sent to the serial port.

Next comes the Arduino part, which is significantly more complicated, so I will try to explain the installation method in detail. First, let's install support in Arduino IDE specifically for this M5Stack microcontroller.

- Open the Arduino IDE and select File --> Preferences --> Settings to add the URL address (https://dl.espressif.com/dl/package_esp32_index.json) below to the additional board manager

- After adding, select the Tools --> Board --> Boards Manager, in the new pop-up dialog, input and search M5Stack, click Install (Important note: Here you should select M5Stack core VER 2.0.2, not the latest version)

op

- Next Tools --> Board --> M5Stack Arduino select M5Stack-Core-ESP32

jkli

Next, we need to install the support libraries.

Go to Sketch --> Include Library --> Manage Library, and in the search box, enter M5Stack. I will install the M5Stack library version 0.4.5

In the same way, we install the "MyCobotBasic" library.

hgiyu

Now we will do some simple tests to check the communication between the Robotic Arm and the PC, as well as the functionality of the libraries. In Arduino, we select the appropriate port (in my case COM17) and upload the code named "First communication TEST".

gftyu

At this stage, there is no need to connect the 12V power supply. If everything is OK, certain information will appear on the M5Stack display. We can also test the functionality of the buttons.

Next, we connect the 12V power supply as well as the gripper and upload the following code named "Systematic test Code". This code first sets all the servos (Joints) to the default position, which is 0 Degrees, and then tests one by one all the servo motors as well as the Gripper.

cghfg

If this part also works, then we are done with testing, and we need to move on to the implementation of the specific task, which is the Rock, Paper, Scissors interactive game, Human vs Robot.

For this purpose, I attached the robotic arm to a solid base for stability, and placed the camera on a special stand, although I could have mounted it directly on the robotic arm in a certain way (for example, it could have been mounted directly above the gripper, but for this particular project, this is impossible because the camera and the matrix display would always be in the opposite direction.). I did this for practical reasons, to protect the camera from unwanted arm movements during testing, and also, with this type of mounting, the device is universal and ready for other projects.

hjkhj

So we need to connect the myCobot Camera Flange 2.0 camera to the PC USB port and activate the Python script. On the M5Stack microcontroller, we first need to upload the code named "Rock_Paper_Scissors_Game".

Three options are displayed on the screen. At the beginning, the arm is in some arbitrary position. Now we press the B button, which gradually takes the arm into a position ready for the game. When it stabilizes, to start the game, we press the A button. Now the robot straightens up, and the matrix display lights up in blue, which indicates that the game has started. At the same moment, a countdown of 3,2,1 begins, after which we have to show a certain shape with our hand in front of the camera.

cgj

Until the human shows a shape, the robot stands still and waits for data from the human.

jhkhj

Upon receiving such data, the robot immediately randomly generates one of the three characters: Rock, Paper, or Scissors. To repeat, Rock is a closed Gripper, Paper is an open Gripper, and Scissors is an open-closed gripper animation. When both players show their shape, it is displayed on the display, on the upper half - human, and on the lower - robot. Now the winner is determined. If the Robot wins, the message "I WIN" appears on the TFT display on a green surface, the upper matrix display also lights up green, and the robot tilts back. If the man wins, the message "YOU WIN" appears on a red background on the TFT display, the upper matrix display lights up red, and the robot leans forward, that is, bows. If the result is a draw, the message "TIE" is displayed on a yellow background on the TFT display, the upper matrix display lights up yellow, and the robot moves left and right.

nbk

The game is accompanied by appropriate audio effects throughout. After this, the game ends, and the robot returns to the "ready to play position". The "C" button has the function of displaying the total score at any moment of the game. By pressing it, the score is displayed and automatically disappears after two seconds.

huo

And finally, a short conclusion. This project demonstrates a complete interactive game between human and robot — the classic Rock-Paper-Scissors — using the MyCobot 280 M5 robotic arm. The robot recognizes human hand gestures via a Python + MediaPipe vision system and responds with real-time motion, LED feedback, sounds, and on-screen results displayed on the M5Stack TFT display. It’s a perfect blend of hardware control, AI gesture detection, and embedded programming.

Copy Code
/*
Human vs Robot – Rock Paper Scissors Interactive Game with MyCobot 280 & M5Stack by mirecemk
         Python + Arduino real-time interaction!
*/

#include <M5Stack.h>
#include <MyCobotBasic.h>

// ===================== CONFIG =====================
#define SERVO_SPEED 70     // doubled speed (was ~35)
#define SPEAKER_PIN 25     // M5Stack Basic speaker (DAC1)
#define DAC_VOL_HI 160
#define DAC_VOL_LO 0

// ===================== SOUND (DAC-safe) =====================
static inline void initSpeaker() {
  pinMode(SPEAKER_PIN, OUTPUT);
  dacWrite(SPEAKER_PIN, DAC_VOL_LO);
}
static inline void stopTone() { dacWrite(SPEAKER_PIN, DAC_VOL_LO); }

static inline void playTone(int freq, int duration_ms) {
  if (freq <= 0 || duration_ms <= 0) { stopTone(); delay(duration_ms); return; }
  const uint32_t total_us = (uint32_t)duration_ms * 1000UL;
  const uint32_t half_period_us = 500000UL / (uint32_t)freq;
  if (half_period_us == 0) { stopTone(); delay(duration_ms); return; }
  const uint32_t t_start = micros();
  while ((micros() - t_start) < total_us) {
    dacWrite(SPEAKER_PIN, DAC_VOL_HI);
    delayMicroseconds(half_period_us);
    dacWrite(SPEAKER_PIN, DAC_VOL_LO);
    delayMicroseconds(half_period_us);
    yield();
  }
  stopTone();
}

static inline void countdownBeep(int n) {
  const int f = (n == 3) ? 1200 : (n == 2) ? 1000 : 800;
  playTone(f, 180);
}
static inline void shootSound()    { playTone(1500, 120); delay(60); playTone(1700, 120); }
static inline void victorySound()  { playTone(523,160); delay(60); playTone(659,160); delay(60); playTone(784,220); }
static inline void defeatSound()   { playTone(784,160); delay(60); playTone(659,160); delay(60); playTone(523,220); }
static inline void tieSound()      { playTone(1000,120); delay(80); playTone(1000,120); }
static inline void selectSound()   { playTone(900,80); }
static inline void clickSound()    { playTone(700,60); }

// ===================== ROBOT / GAME LOGIC =====================
MyCobotBasic myCobot;

enum GameState { READY, COUNTDOWN, REVEAL, RESULT, GAME_OVER };
GameState currentState = READY;

String humanGesture = "", robotGesture = "", gameResult = "";
unsigned long gameStartTime = 0;
int countdownNumber = 3;

int humanWins = 0, robotWins = 0;

void setGameLED(int r, int g, int b) { myCobot.setLEDRGB(r, g, b); }
void setReadyLED()        { setGameLED(0,0,0); }
void setGameRunningLED()  { setGameLED(0,0,255); }
void setRobotWinLED()     { setGameLED(0,255,0); }
void setHumanWinLED()     { setGameLED(255,0,0); }
void setTieLED()          { setGameLED(255,255,0); }

// ===================== MOTION =====================
void moveToNeutral() {
  myCobot.writeAngle(1, 0, SERVO_SPEED);  delay(200);
  myCobot.writeAngle(2, 0, SERVO_SPEED);  delay(200);
  myCobot.writeAngle(3, 0, SERVO_SPEED);  delay(200);
  myCobot.writeAngle(4, -90, SERVO_SPEED);delay(200);
  myCobot.writeAngle(5, 0, SERVO_SPEED);  delay(200);
  myCobot.writeAngle(6, -45, SERVO_SPEED);delay(200);
  myCobot.setGripperState(1, 25);
}

void moveToGamePosition() { myCobot.writeAngle(4, 0, SERVO_SPEED*1.1); delay(800); }

void victoryRoutine() { myCobot.writeAngle(4, -50, SERVO_SPEED); delay(400); myCobot.writeAngle(4, 0, SERVO_SPEED); delay(300); }
void defeatRoutine()  { myCobot.writeAngle(4,  50, SERVO_SPEED); delay(400); myCobot.writeAngle(4, 0, SERVO_SPEED); delay(300); }
void tieRoutine()     { myCobot.writeAngle(5,  50, SERVO_SPEED); delay(250); myCobot.writeAngle(5,-50,SERVO_SPEED); delay(250); myCobot.writeAngle(5,0,SERVO_SPEED); delay(300); }

void showPaper()    { clickSound(); myCobot.setGripperState(0, 80); delay(1000); }
void showRock()     { clickSound(); myCobot.setGripperState(1, 40); delay(1000); }
void showScissors() {
  playTone(1000, 80); delay(120);
  playTone(1200, 80);
  myCobot.setGripperState(1, 80); delay(500);
  myCobot.setGripperState(0, 80); delay(500);
  myCobot.setGripperState(1, 80); delay(1000);
}

// ===================== DISPLAY =====================
void showReadyScreen() {
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextSize(4);
  M5.Lcd.setCursor(50, 10);   M5.Lcd.print("ROCK");
  M5.Lcd.setCursor(170, 10);  M5.Lcd.print("PAPER");
  M5.Lcd.setCursor(80, 60);   M5.Lcd.println("SCISSORS");
  M5.Lcd.setCursor(110, 110); M5.Lcd.println("GAME");

  M5.Lcd.setTextSize(3);
  // Your custom positions preserved
  M5.Lcd.setCursor(30, 160);  M5.Lcd.println("A:  START");
  M5.Lcd.setCursor(30, 200);  M5.Lcd.println("B:  POSITION");
  M5.Lcd.setCursor(30, 240);  M5.Lcd.println("C:  RESULT");
}

void showGameOverScreen() {
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextSize(4);
  M5.Lcd.setCursor(40, 40);   M5.Lcd.println("Game over");
  M5.Lcd.setTextSize(3);
  M5.Lcd.setCursor(30, 130);  M5.Lcd.println("A: New Game");
  M5.Lcd.setCursor(30, 170);  M5.Lcd.println("B: Reposition");
  M5.Lcd.setCursor(30, 210);  M5.Lcd.println("C: Total Score");
}

void showTotalResultScreen() {
  M5.Lcd.fillRect(0, 0, 320, 120, RED);
  M5.Lcd.setTextColor(BLACK);
  M5.Lcd.setTextSize(4);
  M5.Lcd.setCursor(40, 40);
  M5.Lcd.printf("HUMAN: %d", humanWins);

  M5.Lcd.fillRect(0, 120, 320, 120, GREEN);
  M5.Lcd.setTextColor(BLACK);
  M5.Lcd.setTextSize(4);
  M5.Lcd.setCursor(40, 160);
  M5.Lcd.printf("ROBOT: %d", robotWins);

  delay(2500);
  if (currentState == GAME_OVER) showGameOverScreen();
  else showReadyScreen();
}

// ===================== GAME LOGIC =====================
void initializeGame() {
  currentState = READY;
  humanGesture = ""; robotGesture = ""; gameResult = "";
  setReadyLED();
  showReadyScreen();
}

void startNewGame() {
  currentState = COUNTDOWN;
  countdownNumber = 3;
  gameStartTime = millis();

  moveToGamePosition();
  setGameRunningLED();

  M5.Lcd.fillScreen(BLUE);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextSize(4);
  M5.Lcd.setCursor(50, 100);
  M5.Lcd.println("Waiting...");
  delay(600);

  for (int i = 3; i > 0; i--) {
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setTextColor(YELLOW);
    M5.Lcd.setTextSize(10);
    M5.Lcd.setCursor(140, 100);
    M5.Lcd.println(i);
    countdownBeep(i);
    delay(650);
  }

  M5.Lcd.fillScreen(GREEN);
  M5.Lcd.setTextColor(BLACK);
  M5.Lcd.setTextSize(5);
  M5.Lcd.setCursor(70, 100);
  M5.Lcd.println("SHOOT!");
  shootSound();
  delay(700);

  currentState = REVEAL;
  M5.Lcd.fillScreen(BLUE);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextSize(4);
  M5.Lcd.setCursor(35, 80);  M5.Lcd.println("Waiting for");
  M5.Lcd.setCursor(75, 120); M5.Lcd.println("gesture");
}

void generateRobotGesture() {
  int choice = random(0, 3);
  robotGesture = (choice == 0) ? "rock" : (choice == 1) ? "paper" : "scissors";
}

void determineWinner() {
  if (humanGesture == robotGesture) gameResult = "tie";
  else if ((humanGesture == "rock"     && robotGesture == "scissors") ||
           (humanGesture == "paper"    && robotGesture == "rock")     ||
           (humanGesture == "scissors" && robotGesture == "paper"))   gameResult = "human";
  else gameResult = "robot";
}

void showResults() {
  M5.Lcd.fillRect(0, 0, 320, 120, RED);
  M5.Lcd.setTextColor(BLACK);  M5.Lcd.setTextSize(4);
  M5.Lcd.setCursor(50, 40);    M5.Lcd.print("H: "); M5.Lcd.println(humanGesture);

  M5.Lcd.fillRect(0, 120, 320, 120, GREEN);
  M5.Lcd.setTextColor(BLACK);  M5.Lcd.setTextSize(4);
  M5.Lcd.setCursor(50, 160);   M5.Lcd.print("R: "); M5.Lcd.println(robotGesture);

  delay(600);

  if (robotGesture == "rock")      showRock();
  else if (robotGesture == "paper") showPaper();
  else                               showScissors();

  delay(400);

  if (gameResult == "robot") {
    robotWins++;
    setRobotWinLED();
    M5.Lcd.fillScreen(GREEN);
    M5.Lcd.setTextColor(BLACK);
    M5.Lcd.setTextSize(5);
    M5.Lcd.setCursor(80, 100); M5.Lcd.println("I WIN!");
    victorySound();
    victoryRoutine();
  } else if (gameResult == "human") {
    humanWins++;
    setHumanWinLED();
    M5.Lcd.fillScreen(RED);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setTextSize(5);
    M5.Lcd.setCursor(50, 100); M5.Lcd.println("YOU WIN!");
    defeatSound();
    defeatRoutine();
  } else {
    setTieLED();
    M5.Lcd.fillScreen(YELLOW);
    M5.Lcd.setTextColor(BLACK);
    M5.Lcd.setTextSize(5);
    M5.Lcd.setCursor(90, 100); M5.Lcd.println("TIE!");
    tieSound();
    tieRoutine();
  }

  delay(1600);
  moveToNeutral();
  showGameOverScreen();
  currentState = GAME_OVER;
  setReadyLED();
}

// ===================== COMMAND & MAIN LOOP =====================
void processHumanGesture(String gesture) {
  if (currentState != REVEAL) return;
  humanGesture = gesture;
  generateRobotGesture();
  determineWinner();
  showResults();
}

void processCommand(String command) {
  command.trim(); command.toLowerCase();
  if (command.startsWith("human:")) {
    String gesture = command.substring(6);
    if (gesture == "rock" || gesture == "paper" || gesture == "scissors") {
      selectSound();
      processHumanGesture(gesture);
    }
  } else if (command == "reset") {
    clickSound();
    initializeGame();
  }
}

void setup() {
  M5.begin();
  Serial.begin(115200);
  myCobot.setup();
  myCobot.powerOn();
  initSpeaker();
  for (int i = 0; i < 3; ++i) { playTone(900 + i*150, 90); delay(90); }
  randomSeed(analogRead(0));
  initializeGame();
  }


void loop() {
  M5.update();

  if (M5.BtnB.wasPressed()) {
    clickSound();
    moveToNeutral();
    setReadyLED();
    showReadyScreen();
    currentState = READY;
  }

  if (M5.BtnA.wasPressed()) {
    selectSound();
    if (currentState == READY || currentState == GAME_OVER)
      startNewGame();
  }

  if (M5.BtnC.wasPressed()) {
    if (currentState == READY || currentState == GAME_OVER) {
      clickSound();
      showTotalResultScreen();
    }
  }

  if (Serial.available() > 0) {
    String command = Serial.readStringUntil('\n');
    processCommand(command);
  }

  delay(100);
}

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.