Rock Paper Scissors Machine
Overview
We have created a machine that will play a fully functioning, intuitive game of Rock/Paper/Scissors (abbreviated as RPS) with a user. The machine is represented by a human-like hand, capable of seperate and independant wrist, arm, finger and thumb motion. The players' hand goes into a glove equipped with flex sensors, which wirelessly transmits data to the machine based on what the player chose. The machine then reads this data, randomly chooses a throw of its own, and displays win/loss/tie both on an LCD screen and in the form of a thumbs up/down/side motion.
Team Members
Chris Carhart - Mechanical Engineer 2010
Kevin Kao - Mechanical Engineer 2010
Reed Walker - Electrical Engineer 2009
The Players' Glove
The players' glove is a large, leather grill glove with a wireless RF transmitter and two flex sensors embedded inside, one on the index finger, and one on the ring finger. We assumed, reasonably, that the three throws could be determined by the position of the index and ring fingers alone. It also has a power light and an on/off switch. The goal was to have it easily used by everyone, and high percentage success rate of transmitting the throw the user chooses.
RF transmission
i dunno anything about this actually
Flex sensors
Flex sensors are used to tell what throw the player chose. Flex sensors have a varying resistance based on the bend of the sensor - a higher bend angle means higher resistance value. A high bend angle (>60 degrees or so) will have a resistance high enough so that the RC reader reads a low signal.
- If the player throws rock, both flex sensors will be bent, and the read resistances will be high
- If the player throws scissors, the ring finger flex sensor will be bent, but the index finger's sensor will not.
- If the player throws paper, neither of the flex sensors will be bent.
These three combinations are enough to send three unique signals to the PIC.
Other Glove Features
The power switch can turn the game on or off. In the off position, the PIC will display a blank screen until the glove is turned back on. The power light on the glove lets the player know whether the glove is on or off.
The Mechanical Arm
The hand consists of five separate RC servos which all together control arm motion, wrist motion, thumb motion, and finger motion.
The Arm
The base of the arms consists of two Futaba S3004 servos, one for lateral up and down (arm servo) motion and one for axial wrist rotation (wrist servo). The servos are structurally held together with aluminum brackets purchased from lynxmotion.com and the arm is extended with an aluminum tube attached directly to the wrist servo horn, allowing for rotation of the hand. A spring connects the base of the arm servo to base of the wrist servo which stabilizes (and slows) the arm's downward motion as well as reduces the torque needed on the upward swing. The arm servo moves at maximum speed on the downward swing, and at a slightly slower speed on the upward swing, thus mimicking natural RPS motion.
The Wrist
As mentioned above, the wrist motion is created by an aluminum tube and a corresponding servo servo. The tube is mounted with screws to a bracket attached to the arm servo. Its role is to rotate 90 degrees if the PIC chooses Paper, and rotate 0/90/180 degrees for thumbs up/middle/down corresponding to a player's win/tie/loss. For instance if the PIC throws rock and the player throws scissors (player loses), the wrist servo will rotate downwards 180 degrees so the thumb can do a thumbs down.
The Palm
The palm is a sheet metal aluminum bracket designed to mount to the aluminum rod and to support three small servos (one for the thumb and two for the remaining fingers).
The Thumb
The thumb is simply a carbon fiber rod attached to a small servo which is mounted to the palm.
The thumb servo is a Blue Arrow BA-TS-4.3. Its role is to rotate upwards for a player win or loss, and rotate slightly away from the fingers if the PIC throws paper.
The Fingers
The fingers are made up of two brackets. Each bracket consists of two aluminum fingers attached to one another with q-tip shafts and hot glue and each bracket is attached to a small finger servo.
Both finger servos are Blue Arrow BA-TS-9.0s. One servo controls the index and middle fingers, and the other one controls the ring and pinky fingers (ie, index/middle fingers move together, ring/pinky moves together). Both servos default at the rock position. The upper (index and middle fingers) servo rotates about 120 degrees clockwise if the PIC throws scissors. Both servos rotate 120 degrees clockwise if the PIC throws paper.
The Stand
The base of the stand is a piece of plywood spray-painted brown. This provides stability. Four wooden pillars then support an acrylic layer, which the arm rests on. The arm is screwed onto the acrylic, and the acrylic is screwed onto the four pillars. The LCD is mounted on a propped up circuit board, which is attached to a bracket. The LCD bracket is also screwed onto the acrylic. The PIC and all the wires are between the acrylic and the wooden base. This protects the fragile electronics but lets us still check on things if necessary.
Code
The goal of the code was to:
- Separately control 5 different servos
- Read 3 inputs from the player's glove
- Randomly generate a number, and based on that number throw R/P/S
- Display the results on an LCD screen
- Recognize when the player has turned off the glove
Note that the servo control and the random number generator portions were borrowed from the internet.
The outline of the code:
1. Declare constants
2. Interrupts for Servo control
3. Random number generator function
(start of main part of program)
4. Generate a random number
5. Reset arm to default rock position
6. Rotate arm servo 4 times, on the 4th ("shoot"), based on random number, control wrist/fingers to do chosen throw
7. Read and determine what the player threw from the glove
8. Display what the computer threw, what you threw on the LCD, display win or lose
9. Rotate thumb/wrist based on whether the player won or lost
10. Read resistance to see if the glove is turned off. If the glove is turned off do not start game until glove is turned back on
11. Go back to step 4
/* rps_w_consts.c, k. kao 3/19/2009 */ #include <18f4520.h> #device high_ints=TRUE #DEVICE ADC=10 #fuses HS, NOLVP, NOWDT, NOPROTECT #use delay(clock=40000000) // 20 MHz crystal on PCB #include <lcd_flex.c> int16 RCservo[6]; // 6 because we need 5 servos int RCcount; int i=0; char x; //used for random integer generation //all the following constants are specific to our specific servo setup. the ones you use will //vary based on the starting position, power, and type of the servo used. these constants //can be changed at will until correct behavior is achieved. //for our servos, 4999 is the max that can be reached in one direction, and 2300 is the minimum //for the small finger and thumb servos, where the larger servos have a minimum of about 600. //the servos will not respond to commmands to move to a position higher/lower than those cutoffs. int16 resistance1; int16 resistance3; int16 res3temp; int16 resistance5=3000; //resistance 5 (pin A5 here) measures input resistance int16 old_res5; int16 new_res5; int random_byte; //used for random integer generation int wins=0; int losses=0; int ties=0; int determine = 0; int index=0; int ring=0; int16 upper_high_const = 2300; //upper fingers "paper" constant int16 upper_low_const = 4999; //upper fingers "rock" constant int16 lower_high_const = 4899; //lower fingers "paper" constant int16 lower_low_const = 2300; //lower fingers "rock" constant int16 wrist_down_const = 199; //wrist "thumbs down" constant int16 wrist_side_const = 2700; //wrist "paper" constant int16 wrist_vert_const = 4899; //wrist "rock" constant int16 thumbs_down_const =2800;//this is actually only used for scissors int16 thumbs_up_const=4999; int16 thumbs_mid_const=3700; int16 thumbs_down_paper_const = 1150; //thumbs down constant for paper int16 arm_up_const = 2200; int16 arm_down_const=1000; int16 res_cutoff = 600;//cutoff resistance for when to start the game int16 rock_const = 1000;//cutoff for rock int16 not_rock_const = 1500;//cutoff for not rock, really could be the same as the one above int16 paper_scissors_const=2500; //over this is scissors, under is paper //this servo part was borrowed from the wiki and m. peshkin, original comments included #INT_TIMER2 // designates that this is the routine to call when timer2 overflows void MyInterruptRoutine() { if (++RCcount >= 20) RCcount = 0; // 20mS cycle; 20 interrupts if ((RCcount & 3) == 0) { // on the 4mS boundaries turn all the pins low output_low(PIN_A0); // comment out the pins you don't want involved output_low(PIN_A2); output_low(PIN_C5); output_low(PIN_C6); output_low(PIN_C7); set_timer3((int16) 60536 + RCservo[RCcount>>2]); // yes 60536, not 65536. Go high 4000uS (5000*0.8uS) from now, and sooner due to desired High period } // your ISR stuff goes here, after the RC part, so as not to disrupt the timing of turning the servos Low } #INT_TIMER3 fast // "fast" allows Timer 3 to interrupt Timer 2's ISR void InterruptTimer3() { // this ISR is called when Timer 3 times out, to set one of the RC servo output pins high switch (RCcount>>2) { case 0: output_high(PIN_A0); // comment out the pins you don't want involved break; case 2: output_high(PIN_A2); break; case 3: output_high(PIN_C5); break; case 4: output_high(PIN_C6); break; case 1: output_high(PIN_C7); break; } } //random number generator - borrowed from the internet //any google search will lead you to a random number generator, these are public domain by now int rand(void) { int sum; sum = 0; // This calculates parity on the selected bits (mask = 0xb4). if(random_byte & 0x80) sum = 1; if(random_byte & 0x20) sum ^= 1; if(random_byte & 0x10) sum ^= 1; if(random_byte & 0x04) sum ^= 1; random_byte <<= 1; random_byte |= sum; return(random_byte); //printf(lcd_putc, random_byte); } void srand(int seed) { random_byte = seed; } //end of the random number generator void main() { lcd_init(); setup_adc_ports(AN0_TO_AN5); // Enable analog inputs; choices run from just AN0, up to AN0_TO_AN11 setup_adc(ADC_CLOCK_INTERNAL); // the range selected has to start with AN0 delay_us(10); // wait 10uS for ADC to settle to a newly selected input delay_ms(40); setup_timer_2(T2_DIV_BY_4, 77, 16); // clock at 16KHz; interrupt every 4*50nS * 4 * (77+1) * 16 = 1.0mS setup_timer_3(T3_INTERNAL | T3_DIV_BY_4); // Timer 3 is used for the High period for the RC servo signal, ticks every (4*50nS) * 4 = 0.8uS enable_interrupts(INT_TIMER3); enable_interrupts(INT_TIMER2); enable_interrupts(GLOBAL); //end of borrowed servo code while (TRUE) { printf(lcd_putc, "\f "); //clear the lcd i=0; //this next if statement essentially does this: if I (the computer) has won twice in a row, //keep doing one specific throw because it's obviously working. if (wins+losses+ties%2>0) { srand(wins+ties); } x = rand()%3; //the RNG generates a random number but i only want 3 different numbers so i take its remainder //x = 2; activate this and the next line to see if the RNG is working // printf(lcd_putc, "%u", x); delay_ms(1000); while (resistance5 >res_cutoff) //while the switch is off, we want it to display a blank screen { set_adc_channel(5); delay_us(10); resistance5 = read_adc(); //printf(lcd_putc, "\f (%lu)",resistance5); activate these to see what you're reading // delay_ms(400); printf(lcd_putc, "\f"); delay_ms(100); } //the next four commands reset the hand - fingers to "rock position", wrist down, thumb to neutral RCservo[4] = upper_high_const; RCservo[1] = lower_high_const; RCservo[2]=wrist_vert_const; RCservo[3]=thumbs_mid_const; printf(lcd_putc, "\f(Win-Loss-Tie): \n = (%u-%u-%u)", wins,losses,ties); //display total wins, losses, ties delay_ms(2500); //so the user can read the wins/losses/ties while(i<5) //"rock/paper/scissors/shoot" is 4 things so we want it to run 4 times { if (i!=4) //if not on shoot { RCservo[0]=2700; //arm down but not all the way } if (i==4) //if it is on shoot put the arm all the way down { RCservo[0]=3050; delay_ms(100);//so the fingers activate on the lower end of the downswing } if (i==4 && x == 1)//if it's on "shoot" and PIC chooses paper { RCservo[2]=wrist_side_const; //rotate wrist RCservo[4] = upper_low_const;//extend upper fingers RCservo[1] = lower_low_const;//extend lower fingers RCservo[3] = thumbs_up_const;//extend thumb } if (i==4 && x == 2)//if its on "shoot" and PIC chooses scissors { RCservo[4] = upper_low_const; //extend upper fingers RCservo[3] = thumbs_down_const; //set thumb to middle } switch(i)//on first count, display rock, second paper, etc { case(1): printf(lcd_putc,"\f\nROCK!"); break; case(2): printf(lcd_putc,"\f\nPAPER!"); break; case(3): printf(lcd_putc,"\f\nSCISSORS!"); break; case(4): printf(lcd_putc,"\f\nSHOOT!"); break; } delay_ms(400); //small delay between R/P/S/S if(i!=4)//this is the upswing - we want it to be slower than the downswing, so its artificially slowed { for (RCservo[0]=2700;RCservo[0]>899;RCservo[0]--){ delay_us(200); } } delay_ms(200); i++; } i =0; delay_ms(1000); //we'll wait a little bit for the user to input his throw //this next part is reading resistances from the flex sensors. The x3 is for greater //sensitivity. 1 is index here, 3 is ring finger //for (i=0;i<5;i++){ set_adc_channel(1); // pin A1 delay_us(10); resistance1 = read_adc(); resistance1 = resistance1*3;// for greater sensitivity //printf(lcd_putc, "\f (%lu)",resistance1); activate this to see what A1 is reading delay_ms(100); //resistance3 is flaky so we want to read it 5 times to get an average. res3temp=0; for (i=0;i<5;i++){ set_adc_channel(3); delay_us(10); resistance3 = read_adc(); resistance3 = resistance3*3; //printf(lcd_putc, "\f (%lu)",resistance3); res3temp = res3temp+resistance3; delay_ms(50); } resistance3 = res3temp/5; //printf(lcd_putc, "\f (%lu)",resistance3); delay_ms(100); if (resistance1<rock_const)//if read resistance is lower than a specified cutoff, we know index is down { index = 1; } else{ index = 0;} if (resistance3<1500)//if read resistance is lower than a specified cutoff, we know ring is down { ring = 1; } else { ring = 0;} //"determine" is what the person throws. 3 is rock, 1 is scissors, 0 is paper if (resistance1 < rock_const) { determine = 3; } //resistance3, the lower two fingers, determine whether you've thrown scissors/paper if(resistance1>rock_const &&resistance3>paper_scissors_const) { determine = 1;//scissors } if(resistance1>rock_const&&resistance3<paper_scissors_const) { determine = 0;//paper } switch(x) //this switch acts based on what the computer chose. arbitrarily 0 is rock, 1 is paper, 2 is scissors { case (0): i++; printf(lcd_putc, "\fMACHINE:ROCK\nHUMAN:"); switch(determine)//this switch is based on determine; ie what the player threw { case(0)://0 means paper printf(lcd_putc,"PAPER"); delay_ms(1500); printf(lcd_putc,"\f\nYOU WIN"); wins++; RCservo[3]=thumbs_up_const; //you win, so thumbs up break; case(1)://1 must be scissors printf(lcd_putc,"SCISSORS"); delay_ms(1500); printf(lcd_putc,"\f\nYOU LOSE"); losses++; RCservo[2]=wrist_down_const; //rotate wrist down, give thumbs down RCservo[3]=thumbs_up_const; break; case(3): printf(lcd_putc,"ROCK");//3 means rock delay_ms(1500); printf(lcd_putc,"\f\nTIE"); ties++; RCservo[2]=wrist_side_const; RCservo[3]=thumbs_up_const; //tie so don't really move break; } delay_ms(1500);//delay so user can read the screen break; case (1): i++; printf(lcd_putc, "\fMACHINE:PAPER\nHUMAN:"); switch(determine)//paper's thumbs up/down is handled at the end of the switch; it's more complicated { case(0): //you threw paper printf(lcd_putc,"PAPER"); delay_ms(1500); printf(lcd_putc,"\f\nTIE"); ties++; break; case(1): printf(lcd_putc,"SCISSORS"); delay_ms(1500); printf(lcd_putc,"\f\nYOU WIN"); wins++; break; case(3): printf(lcd_putc,"ROCK"); delay_ms(1500); printf(lcd_putc,"\f\nYOU LOSE"); losses++; break; } delay_ms(1500);//delay to read screen delay_ms(200); break; case (2): i=0; printf(lcd_putc, "\fMACHINE:SCISSORS\nHUMAN:"); switch(determine) { case(0): printf(lcd_putc,"PAPER"); delay_ms(1500); printf(lcd_putc,"\f\nYOU LOSE"); losses++; //RCservo[2]=wrist_down_const; //the thumb/wrist motion for win/loss is the same as "rock" //RCservo[3]=thumbs_up_const; break; case(1): printf(lcd_putc,"SCISSORS"); delay_ms(1500); printf(lcd_putc,"\f\nTIE"); ties++; //RCservo[3]=thumbs_mid_const; break; case(3): printf(lcd_putc,"ROCK"); delay_ms(1500); printf(lcd_putc,"\f\nYOU WIN"); wins++; //RCservo[3]=thumbs_up_const; break; } delay_ms(1500); break; } if (x ==2) //if pic threw scissors, do thumbs down/up commands here { RCservo[4] = upper_high_const; RCservo[1] = lower_high_const; delay_ms(200); if(determine==3) { RCservo[3]=thumbs_up_const; } if(determine==1) { RCservo[2]=wrist_side_const; RCservo[3]=thumbs_up_const; } if(determine==0) { RCservo[2]=wrist_down_const; //the thumb/wrist motion for win/loss is the same as "rock" RCservo[3]=thumbs_up_const; } } if (x ==1) { RCservo[4] = upper_high_const; RCservo[1] = lower_high_const; delay_ms(200); if(determine==3) { RCservo[2]=wrist_down_const; RCservo[3]=thumbs_up_const; } if(determine==1) { RCservo[2]=wrist_vert_const; RCservo[3]=thumbs_up_const; } if(determine==0) { RCservo[2]=wrist_side_const; RCservo[3]=thumbs_up_const; } } } delay_ms(1500); for (i=0;i<5;i++){ set_adc_channel(5); // read R5 to see if the player turned the glove off delay_us(10); new_res5 = read_adc(); if (new_res5<old_res5){ resistance5 = new_res5;} old_res5 = new_res5;} //printf(lcd_putc, "\f (%lu)",resistance5); delay_ms(40); }
Results
Overall, the RPS machine was a great success. Its mechanically intensive design coupled with an intuitive, user-friendly interface proved to be a winning combination, especially with people without an extensive technical background. Wireless operation of the hand became an essential part of the project - it helped to greatly reduce clutter, despite limited range. The fact that RPS is a universally known game also increased its appeal - no lengthy explanation was needed to let the user see what was going on.
Video of the RPS machine in action can be found here
Next Steps
The RPS machine was developed and created within a five week timespan. While it is fully operational, other features can be added to make it a more complete, enjoyable experience.
- Spoken results
The machine could say "Rock, Paper, Scissors, Shoot!" while the arm is moving. It could also say "You Win", "You lose", or some variation of those.
- Servo cushioning
The repetitive motion can generate lots of wear and tear on the servos. Cushioning between each part could extend the lifespan of each servo.
- Power issues
The arm's downward speed can be viewed as slightly lower than desired. A more powerful servo, or a higher voltage power supply could greatly increase the speed of gameplay.
- Range issues
The range of the RF transmission is about 1' on a good day. A more powerful wireless transmission system or a range amplifier would make the RPS machine more reliable.
- AI
The machine's code could include more detailed strategies for which to play against a human.