// Connect 4 Game
// by Mark Bennett 2020
/* Overview
*
* Plays physical Connect 4 game
*
* Can be played human v computer, computer v computer or human v human
* Control is via a keypad and an LCD screen shows menus and messages
* A servo and stepper motor are used to control dropping counters into the grid
* Detects wins and draws and shows end game message on the display
*
*
* Hardware
*
* Switch for column selector referencing - pin 2
* Servo for dropping counters - pin 3
* Stepper motor to drive column selector - pins 8 - 11
* LCD display with serial board for user interface - SDA SCL
* Keypad for user data entry - pin A0
* Piezo buzzer - pin 4
*
*/
#include <Servo.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// debug mode
const bool debug = true; // sends grid state to serial after each turn if true
// stepper motor
const int motorPin1 = 8;
const int motorPin2 = 9;
const int motorPin3 = 10;
const int motorPin4 = 11;
int motorSpeed = 1250;
int motorLookup[8] = {B01001, B00001, B00011, B00010, B00110, B00100, B01100, B01000};
int stepsPerColumn = 125; // number of steps to move 1 column
int referenceSteps = 2; // number of steps from reference position to column 1
// servo
Servo dropperservo;
const int servoPin = 3;
int servoCentre = 99; // servo drop position, centre
int servoRed = 120; // servo position to pick up red counter
int servoYellow = 75; // servo position to pick up yellow counter
int servoCurrent = servoCentre;
// switch
const int switchPin = 2;
// keypad
const int keypadPin = A0;
// voltage values for keys 1 2 3 4 5 6 7 8 9 * 0 #
// * and # are not used and read as 0
int keyValues[] = {505, 336, 251, 222, 181, 153, 141, 124, 110, 103, 93, 85, 0};
int keys[13];
char lastKeyPressed = ' ';
// lcd
LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 16, 2);
// led
const int ledPin = 13;
// buzzer
const int buzzerPin = 4;
// menu control
int menu = 0;
// game
int selectorPosition = 1;
int seed = 0;
void setup() {
// stepper motor
pinMode(motorPin1, OUTPUT);
pinMode(motorPin2, OUTPUT);
pinMode(motorPin3, OUTPUT);
pinMode(motorPin4, OUTPUT);
// servo
dropperservo.attach(servoPin);
dropperservo.write(servoCurrent);
// switch
pinMode(switchPin, INPUT);
// keyboard
for(int i = 0; i < 12; i++){
keys[i] = ((keyValues[i] - keyValues[i + 1]) / 2) + keyValues[i + 1];
}
// lcd
lcd.init();
lcd.backlight();
showMenu(0);
// led
pinMode(ledPin, OUTPUT);
// buzzer
pinMode(buzzerPin, OUTPUT);
// game
seed = analogRead(3);
Serial.begin(9600);
playTune(1);
}
void loop(){
// keypad
char keyPressed = readKeypad();
// menu navigation
if(keyPressed != ' '){
processMenuInput(keyPressed);
}
// game
seed++;
delay(50);
}
void displayMessage(String line1, String line2){
lcd.clear();
lcd.home();
lcd.print(line1);
lcd.setCursor(0, 1);
lcd.print(line2);
}
char readKeypad(){
char key = ' ';
if(analogRead(keypadPin) > keys[11]){
delay(20);
int keyInput = analogRead(keypadPin);
if(keyInput > keys[0]) key = '1';
else if(keyInput > keys[1]) key = '2';
else if(keyInput > keys[2]) key = '3';
else if(keyInput > keys[3]) key = '4';
else if(keyInput > keys[4]) key = '5';
else if(keyInput > keys[5]) key = '6';
else if(keyInput > keys[6]) key = '7';
else if(keyInput > keys[7]) key = '8';
else if(keyInput > keys[8]) key = '9';
else if(keyInput > keys[9]) key = '0';
else if(keyInput > keys[10]) key = '0';
else if(keyInput > keys[11]) key = '0';
tone(buzzerPin, 500, 50);
}
while(analogRead(keypadPin) > keys[11]){
delay(10);
}
return key;
}
void showMenu(int menu){
if(menu == 0) displayMessage("Arduino Connect4", "Press any key");
if(menu == 1) displayMessage("1 Play Connect 4", "2 Test 3 Set-up");
if(menu == 11) displayMessage("1 HvC 2 HvH", "3 CvC 0 Exit");
if(menu == 111) displayMessage("1 Human go first", "2 Second 0 Exit");
if(menu == 12) displayMessage("1 Keypad 2 Servo", "3 Select 0 Exit");
if(menu == 13) displayMessage("1 Keypad 2 Servo", "3 Select 0 Exit");
}
void processMenuInput(char key){
if(menu == 0){referenceSelector(); menu = 1; showMenu(menu);}
// top level menu
else if(menu == 1){
if(key == '1'){menu = 11; showMenu(menu);}
if(key == '2'){menu = 12; showMenu(menu);}
if(key == '3'){menu = 13; showMenu(menu);}
}
// play menu
else if(menu == 11){
if(key == '1'){menu = 111; showMenu(menu);}
if(key == '2'){playGame(1, 1);}
if(key == '3'){playGame(2, 2);}
if(key == '0'){menu = 1; showMenu(menu);}
}
else if(menu == 111){
if(key == '1'){playGame(1, 2);}
if(key == '2'){playGame(2, 1);}
if(key == '0'){menu = 11; showMenu(menu);}
}
// test menu
else if(menu == 12){
if(key == '1'){testKeypad();}
if(key == '2'){testServo();}
if(key == '3'){testMotor();}
if(key == '0'){menu = 1; showMenu(menu);}
}
// set-up menu
else if(menu == 13){
if(key == '1'){setupKeypad();}
if(key == '2'){setupServo();}
if(key == '3'){setupMotor();}
if(key == '0'){menu = 1; showMenu(menu);}
}
}
void playGame(int player1Type, int player2Type){
randomSeed(seed);
for(int i = 0; i < 100; i++){
int dump = random(50);
}
// type, 1 = human, 2 = computer
int players[] = {0, 0};
players[0] = player1Type;
players[1] = player2Type;
byte grid[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
bool gameEnd = false;
bool aborted = false;
int player = 0;
int key = 0;
int selectorMove = 0;
String colour;
bool draw = false;
int win = 0;
int row = 0;
int column = 0;
int countDown = 2000;
char keyPressed = ' ';
resetGrid(grid);
while(!gameEnd){
if(player == 0){colour = "Red";}
else{colour = "Yellow";}
if(players[player] == 1){
displayMessage(String("Player ") + (player + 1) + " " + colour, "1-7 col 9 exit");
key = getHumanMove(grid, player, colour);
if(key == 9){
gameEnd = true;
aborted = true;
}
}
else{
displayMessage(String("Player ") + (player + 1) + " " + colour, "Deciding move...");
key = decideMove(grid, player);
delay(200);
}
if(key > 0 && key < 8){
displayMessage("Executing move", "Please wait...");
selectorMove = key - selectorPosition;
if(selectorMove > 0){
rotate('R', selectorMove * stepsPerColumn);
}
if(selectorMove < 0){
rotate('L', selectorMove * -1 * stepsPerColumn);
}
selectorPosition = key;
if(player == 0) drop('R');
if(player == 1) drop('Y');
delay(2000);
column = key - 1;
row = addCounter(grid, player, column);
if(debug) sendGrid(grid);
// check for win
win = checkForWin(grid, player, column, row);
if(win > 0) gameEnd = true;
// check for draw, all full and no winner
if(!gameEnd){
draw = checkForDraw(grid);
if(draw) gameEnd = true;
}
player = abs(player - 1);
}
// check for abort if not gameEnd
if(!gameEnd && players[0] == 2 && players[1] == 2){
displayMessage("Press 0 to abort", "Wait 2s to cont");
countDown = 2000;
while(countDown > 0){
keyPressed = readKeypad();
if(keyPressed == '0'){
Serial.println(keyPressed);
gameEnd = true;
aborted = true;
}
delay(50);
countDown = countDown - 50;
}
}
}
// end game messages
if(draw){
displayMessage("Result is a Draw", "Press any key");
playTune(1);
}
if(win == 1){
displayMessage("Winner P1 Red", "Press any key");
playTune(2);
}
if(win == 2){
displayMessage("Winner P2 Yellow", "Press any key");
playTune(2);
}
if(aborted) displayMessage("Game aborted", "Press any key");
while(readKeypad() == ' '){
delay(20);
}
showMenu(menu);
}
void playTune(int tune){
if(tune == 1){
tone(buzzerPin, 262, 200);
delay(210);
tone(buzzerPin, 523, 400);
delay(410);
}
if(tune == 2){
tone(buzzerPin, 262, 200);
delay(210);
tone(buzzerPin, 330, 200);
delay(210);
tone(buzzerPin, 392, 200);
delay(210);
tone(buzzerPin, 523, 400);
delay(410);
tone(buzzerPin, 392, 200);
delay(210);
tone(buzzerPin, 523, 400);
delay(410);
}
}
int getHumanMove(byte g[], int p, String colour){
bool columnAccepted = true;
char keyPressed = ' ';
int key = 0;
do{
columnAccepted = true;
keyPressed = ' ';
while(keyPressed == ' '){
keyPressed = readKeypad();
delay(10);
}
key = 0;
if(keyPressed == '1') key = 1;
if(keyPressed == '2') key = 2;
if(keyPressed == '3') key = 3;
if(keyPressed == '4') key = 4;
if(keyPressed == '5') key = 5;
if(keyPressed == '6') key = 6;
if(keyPressed == '7') key = 7;
// exit from game
if(keyPressed == '9') key = 9;
if(key > 0 && key < 8){
// check for full column
if(g[toGrid(key - 1, 5)] != 0){
displayMessage("Full, try again", String("P") + (p + 1) + " " + colour + " 1-7");
columnAccepted = false;
}
}
}while(!columnAccepted && key != 9);
return key;
}
int decideMove(byte g[], int p){
int columns[] = {50, 50, 50, 50, 50, 50, 50};
byte tempGrid[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int win = 0;
int key = 0;
int biggest = 0;
int count = 0;
int selected = 0;
int row = 0;
int p2 = abs(p - 1);
// mark all full columns with 0s
for(int i = 0; i < 7; i++){
if(g[toGrid(i, 5)] != 0){
columns[i] = 0;
}
}
// add 1000 to winning columns[]
for(int i = 0; i < 7; i++){
if(columns[i] > 0){
for(int j = 0; j < 42; j++){
tempGrid[j] = g[j];
}
row = addCounter(tempGrid, p, i);
win = checkForWin(tempGrid, p, i, row);
if(win > 0){
columns[i] += 1000;
}
}
}
// add 50 to losing columns[]
for(int i = 0; i < 7; i++){
if(columns[i] > 0){
for(int j = 0; j < 42; j++){
tempGrid[j] = g[j];
}
row = addCounter(tempGrid, p2, i);
win = checkForWin(tempGrid, p2, i, row);
if(win > 0){
columns[i] += 50;
}
}
}
// check for give away moves
for(int i = 0; i < 7; i++){
if(columns[i] > 0){
for(int k = 0; k < 7; k++){
if(columns[k] > 0){
for(int j = 0; j < 42; j++){
tempGrid[j] = g[j];
}
row = addCounter(tempGrid, p, i);
if(i != k || row < 5){
row = addCounter(tempGrid, p2, k);
win = checkForWin(tempGrid, p2, k, row);
if(win > 0){
columns[i] -= 10;
}
}
}
}
}
}
// find biggest value in columns[]
for(int i = 0; i < 7; i++){
if(columns[i] > biggest){
biggest = columns[i];
}
}
// count biggest in columns[]
for(int i = 0; i < 7; i++){
if(columns[i] == biggest){
count++;
}
}
// random select a biggest column
selected = random(count) + 1;
if(debug){
Serial.println(String("Player ") + p);
Serial.println(String("Biggest ") + biggest);
Serial.println(String("Count ") + count);
Serial.println(String("Selected ") + selected);
}
for(int i = 0; i < 7; i++){
if(columns[i] == biggest){
selected--;
if(selected == 0){
key = i + 1;
}
}
}
if(debug){
for(int i = 0; i < 7; i++){
Serial.print(columns[i]);
Serial.print(" ");
}
Serial.println("");
Serial.println("");
}
return key;
}
int addCounter(byte g[], int p, int c){
// find row
int row = 0;
while(g[toGrid(c, row)] != 0){
row++;
}
// update array
g[toGrid(c, row)] = p + 1;
return row;
}
int checkForDraw(byte g[]){
bool draw = true;
for(int i = 0; i < 7; i++){
if(g[toGrid(i, 5)] == 0){
draw = false;
}
}
return draw;
}
int checkForWin(byte g[], int p, int c, int r){
p++;
int win = 0;
if(win == 0) win = checkFor4(g, p, c, r, 3, 6, 0, 2, -1, 1, -2, 2, -3, 3);
if(win == 0) win = checkFor4(g, p, c, r, 2, 5, 1, 3, -1, 1, -2, 2, 1, -1);
if(win == 0) win = checkFor4(g, p, c, r, 1, 4, 2, 4, -1, 1, 1, -1, 2, -2);
if(win == 0) win = checkFor4(g, p, c, r, 0, 3, 3, 5, 1, -1, 2, -2, 3, -3);
if(win == 0) win = checkFor4(g, p, c, r, 0, 3, 0, 2, 1, 1, 2, 2, 3, 3);
if(win == 0) win = checkFor4(g, p, c, r, 1, 4, 1, 3, 1, 1, 2, 2, -1, -1);
if(win == 0) win = checkFor4(g, p, c, r, 2, 5, 2, 4, 1, 1, -1, -1, -2, -2);
if(win == 0) win = checkFor4(g, p, c, r, 3, 6, 3, 5, -1, -1, -2, -2, -3, -3);
if(win == 0) win = checkFor4(g, p, c, r, 0, 3, 0, 5, 1, 0, 2, 0, 3, 0);
if(win == 0) win = checkFor4(g, p, c, r, 1, 4, 0, 5, 1, 0, 2, 0, -1, 0);
if(win == 0) win = checkFor4(g, p, c, r, 2, 5, 0, 5, 1, 0, -1, 0, -2, 0);
if(win == 0) win = checkFor4(g, p, c, r, 3, 6, 0, 5, -1, 0, -2, 0, -3, 0);
if(win == 0) win = checkFor4(g, p, c, r, 0, 6, 3, 5, 0, -1, 0, -2, 0, -3);
return win;
}
int checkFor4(byte g[], int p, int c, int r, int c1, int c2, int r1, int r2, int sc1, int sr1, int sc2, int sr2, int sc3, int sr3){
int win = 0;
if(c >= c1 && c <= c2 && r >= r1 && r <= r2 && g[toGrid(c + sc1, r + sr1)] == p && g[toGrid(c + sc2, r + sr2)] == p && g[toGrid(c + sc3, r + sr3)] == p) win = p;
return win;
}
void resetGrid(byte grid[]){
for(int i = 0; i < 42; i++){
grid[i] = 0;
}
}
void drop(char colour){
if(colour == 'R'){
dropperservo.write(servoRed);
}
else{
dropperservo.write(servoYellow);
}
delay(1000);
dropperservo.write(servoCentre);
}
int toGrid(int column, int row){
return column + (row * 7);
}
int toColumn(int grid){
return grid - (toRow(grid) * 7);
}
int toRow(int grid){
return grid / 7;
}
void sendGrid(byte g[]){
for(int r = 5; r >= 0; r--){
for(int c = 0; c < 7; c++){
Serial.print(g[toGrid(c, r)]);
Serial.print(" ");
}
Serial.println("");
}
Serial.println("");
/*
for(int i = 0; i < 42; i++){
Serial.print(g[i]);
Serial.print(" ");
}
Serial.println("");
Serial.println("");
*/
}
void testKeypad(){
int countDown = 5000;
char keyPressed = ' ';
displayMessage("Key", "Wait 5s to exit");
while(countDown > 0){
keyPressed = readKeypad();
if(keyPressed != ' '){
countDown = 5000;
lcd.setCursor(4, 0);
lcd.print(keyPressed);
}
delay(50);
countDown = countDown - 50;
}
showMenu(menu);
}
void testServo(){
char keyPressed = ' ';
displayMessage("1 Drop Red", "2 Yellow 0 Exit");
while(keyPressed != '0'){
keyPressed = readKeypad();
if(keyPressed == '1'){
dropperservo.write(servoRed);
delay(1000);
dropperservo.write(servoCentre);
}
if(keyPressed == '2'){
dropperservo.write(servoYellow);
delay(1000);
dropperservo.write(servoCentre);
}
delay(10);
}
showMenu(menu);
}
void testMotor(){
char keyPressed = ' ';
int key = 0;
int selectorMove = 0;
showTestMotorControlMenu();
while(keyPressed != '0'){
keyPressed = readKeypad();
selectorMove = 0;
key = 0;
if(keyPressed != ' '){
if(keyPressed == '1') key = 1;
if(keyPressed == '2') key = 2;
if(keyPressed == '3') key = 3;
if(keyPressed == '4') key = 4;
if(keyPressed == '5') key = 5;
if(keyPressed == '6') key = 6;
if(keyPressed == '7') key = 7;
if(key > 0){
selectorMove = key - selectorPosition;
if(selectorMove > 0){
rotate('R', selectorMove * stepsPerColumn);
}
if(selectorMove < 0){
rotate('L', selectorMove * -1 * stepsPerColumn);
}
selectorPosition = key;
}
if(keyPressed == '8'){
referenceSelector();
showTestMotorControlMenu();
}
}
delay(10);
}
showMenu(menu);
}
void showTestMotorControlMenu(){
displayMessage("1-7 Select Col", "8 Ref 0 Exit");
}
void setupKeypad(){
int countDown = 5000;
int value = 0;
displayMessage("Keypad", "Wait 5s to exit");
while(countDown > 0){
value = analogRead(keypadPin);
if(value > 40){
countDown = 5000;
}
lcd.setCursor(7, 0);
lcd.print(value);
lcd.print(" ");
delay(50);
countDown = countDown - 50;
}
showMenu(menu);
}
void setupServo(){
char keyPressed = ' ';
displayMessage("Servo", "1/4+ 2/5- 0 Exit");
while(keyPressed != '0'){
keyPressed = readKeypad();
if(keyPressed == '1'){
servoCurrent += 3;
}
if(keyPressed == '2'){
servoCurrent -= 3;
}
if(keyPressed == '4'){
servoCurrent += 1;
}
if(keyPressed == '5'){
servoCurrent -= 1;
}
lcd.setCursor(6, 0);
lcd.print(servoCurrent);
lcd.print(" ");
dropperservo.write(servoCurrent);
delay(10);
}
showMenu(menu);
}
void setupMotor(){
char keyPressed = ' ';
int value = 0;
displayMessage("Motor 9 Rst", "147L 258R 0 Exit");
while(keyPressed != '0'){
keyPressed = readKeypad();
if(keyPressed == '1'){rotate('L', 50); value -=50;}
if(keyPressed == '2'){rotate('R', 50); value +=50;}
if(keyPressed == '4'){rotate('L', 10); value -=10;}
if(keyPressed == '5'){rotate('R', 10); value +=10;}
if(keyPressed == '7'){rotate('L', 1); value -=1;}
if(keyPressed == '8'){rotate('R', 1); value +=1;}
if(keyPressed == '9') value = 0;
lcd.setCursor(6, 0);
lcd.print(String(value) + " ");
}
referenceSelector();
showMenu(menu);
}
void setOutput(int out){
digitalWrite(motorPin1, bitRead(motorLookup[out], 0));
digitalWrite(motorPin2, bitRead(motorLookup[out], 1));
digitalWrite(motorPin3, bitRead(motorLookup[out], 2));
digitalWrite(motorPin4, bitRead(motorLookup[out], 3));
}
int getSwitchState(){
if(digitalRead(switchPin) == HIGH){
return 0;
}
else{
return 1;
}
}
void referenceSelector(){
displayMessage("Referencing", "Please wait...");
while(getSwitchState() == 1){
rotate('R', 1);
}
delay(50);
while(getSwitchState() == 0){
rotate('L', 1);
}
delay(50);
while(getSwitchState() == 1){
rotate('R', 1);
}
delay(50);
rotate('R', referenceSteps);
selectorPosition = 1;
}
void rotate(char turn, int steps){
for(int j = 0; j < steps; j++){
if(turn == 'L'){
for(int i = 0; i < 8; i++){
setOutput(i);
delayMicroseconds(motorSpeed);
}
}
if(turn == 'R'){
for(int i = 7; i >= 0; i--){
setOutput(i);
delayMicroseconds(motorSpeed);
}
}
}
digitalWrite(motorPin1, 0);
digitalWrite(motorPin2, 0);
digitalWrite(motorPin3, 0);
digitalWrite(motorPin4, 0);
}
Comments