Connect Four Game with Arduino

Remote Controlled Connect Four Game

connect_four_arduino_two_boards

connect_four_arduino

connect_four_arduino_back

connect_four_arduino_front

2. Project Aims

People have been playing board games for a long time. Nowadays many of them are transferred on computers, smartphones and tablets. But the experience of a digital game differs from their physical equivalents. Playing on an actual physical board with moving objects gives different more natural interaction than using controller and watching a 2D screen. Connect 4 is a popular board game made from 7 columns and 6 rows. Players take turns dropping their coloured disks into columns until one of the players gets 4 in a row-horizontally, vertically or diagonally. The goal of this project is to build two Connect 4 boards that will use sensors, motors and microcontrollers to detect user input, process it and send it to the other board. The system will also keep track of whose turn it is check for win, lose or draw case and do remote players moves automatically.

3. System Design

highlevelsystem12

4. Hardware Design and Implementation

4.1 Microcontroller

Central component to the system is the Microcontroller. It will take readings from sensors process it and control output devices. Microcontrollers are self-contained systems with a processor, memory and peripherals. Arduino Uno Development board was chosen for this project because it provides cheap and easy way to control external devices. It has 14 digital input/output pins, 6 analogue inputs, it can control external shield that will allow it to connect to the internet.

arduino-uno-300x190

In order to connect to the internet an Ethernet Shield will be used.

4.2 Sensors

Sensors will be used as an input device to the system to detect if a disk is dropped in one of the columns. There are many varieties of sensors that can detect moving objects.
Ultrasonic sensors send a pulse that bounces off an object and measures the time it takes for the pulse to come back to the sensor. These sensors are great for measuring distance to remote object.
Capacitive proximity sensors can measure proximity to non-metallic objects.
Proximity sensors use infrared emitter and receiver to calculate distance by detecting light reflected by an object.
Photo interrupters are similar to the proximity sensor. They use infrared emitter and receiver but placed opposite each other and detect if an object passes in between.

Considering all options the TCRT5000 proximity sensor was a good choice for detecting if a disk is dropped in one of the columns because its relatively cheap and works well in detecting close objects. It includes one infrared emitter and phototransistor in a leaded package which blocks visible light. It can detect reflected light from close distance.

tcrt5000-262x300

4.3 Multiplexer

More Analogue IO pins are needed than the provided from the Arduino board. One way of expanding the IO is by using a multiplexer. A multiplexer is a device that selects one of several analogue or digital inputs and forwards it to a single line. The 4051 family is a popular multiplexer/demultiplexer that can work with analogue values. It is not possible to read from more than one input at the same time but going through the pins one by one is quite fast.

74hc4051-300x242

The 74HC4051 has eight analogue inputs/outputs (Y0 to Y7) and three digital select inputs (S0 to S2), an active-LOW enable input (E), and a common input/output (Z).
VCC and GND are the supply voltage pins for the
digital control inputs (S0 to S2, and E).
S0, S1 and S2 select the pin to read/write from. These pins are connected to digital out pins on the Arduino. Every pin is representing a number S0 = 1; S1 = 2; S2 = 4) and if one of these Select pins is set to HIGH, the number the pin is representing will be transmitted to the 4051. For example:
If S0 and S2 are HIGH and S1 is LOW pin y5 is selected (1+0+4 = 5).
If all pins are HIGH pin y7 is selected (1+2+4 = 7).

74hc4051-control

4.4 Stepper Motor

Stepper motor is required to provide precise linear motion mechanism that delivers disks to the right column.
Stepper motors are used when motion and position has to be controlled precisely. They rotate a specific number of degrees controlled by pulses supplied on the stator windings. There are two basic types of stepper motors: variable reluctance and permanent magnet. Both are similar except that the permanent magnet stepper has permanent rotor poles and thus keeps the rotor in place when there is no current flowing. Furthermore depending on the winding arrangements steppers can be unipolar or bipolar. A bipolar motor has positive and negative polarities while the unipolar stepper motor can only operate with positive voltage. A unipolar stepper requires extra wire connected in the middle of each winding in order to produce opposite polarities. Driving bipolar stepper is more complicated because it requires bipolar voltage supply. That’s why a unipolar motor will be used.
The 28BYJ-48 Stepper Motor produces good torque for its size. It is widely used and that makes it very cheap. It has five wires and two coils divided by centre connections on each coil.

stepper

The ULN2003A driver board will be used to amplify the signal from the Arduino. It contains seven Darlington array pairs with common emitters. Each channel rated at 500mA and can withstand peak currents of 600mA.

uln2003a-281x300

The figure below shows how to connect the stepper. Inputs one to four of ULN2003A are connected to Arduino digital pins. External supply is used for stability and is grounded through the Arduino GND.

stepper_schem

A separate 5 V power supply is used, as the motor may drain more current and damage the microcontroller. For controlling the stepper, voltage must be applied to each of the coils in a specific sequence. It can be controlled with 4 and 8 steps. Using 8 steps is slower but gives more torque. The table below shows the sequence to rotate the stepper in the clockwise direction using 8 steps.

controlling-the-stepper-motor-clockwise-with-8-steps

4.5 Servo

Servo is needed to deliver a disk to the column. Servo is a motor that can rotate 90 degrees in each direction. Choosing the right servo for an entire system to perform as designed is based on the required torque, speed, size and accuracy.
The SG90 was chosen because it’s small, light servo with high output power.

servo_3-300x188

The final system that will deliver disks will consist of 1 stepper, 1 servo, gear rack and gear, pipe that holds the disks and funnel on which the disks will slide into the column.
LEDs will be used as a feedback to indicate player turns, who won and when the game ends in a draw.

5. Circuit Design

The figure below shows the final circuit design of all hardware components discussed.

untitled-sketch_schem-1

6. Software

The game play loop is depicted in the figure below. It broadly follows six steps. The first player makes a move on his Connect Four set, which is read by the Arduino. The Arduino transmits this move to the remote player’s machine, where the remote player’s machine completes the first player’s move on the remote player’s game set. In doing so, the Arduino maintains an internal “state of the game” on both machines (and on a web server). In response, the remote player makes his move on his game set, which is similarly completed by the first player’s Arduino on the first player’s machine.

flowchart

This Sketch makes use of the Ethernet library, SPI library and Servo library. At start all global variables and the 2D array will be reset to 0. After that the shield must be assigned MAC address and IP address using the Ethernet.begin() function.

byte mac[] = { 2xAB, 1xF1, 0xAE, 0x9E, 0x4E, 0x21 };
byte ip[] = { 192, 168, 0, 15 };

DNS server and Default Gateway are used to connect to web pages outside the local network.

byte dns_server[] = { 192, 168, 0, 2 };
byte gateway[] = { 255, 255, 255, 0 };

Ethernet begin will initialize the Ethernet library.

Ethernet.begin(mac, ip, dns_server, gateway);

Reading the Sensors

for (y=0; y<=6; y++) {
// select the bit
s0 = bitRead(y,0);
s1 = bitRead(y,1);
s2 = bitRead(y,2);
digitalWrite(7, s0);
digitalWrite(8, s1);
digitalWrite(9, s2);
// read analog pin 0:
sensorValue = analogRead(A0);
delay(10); // delay in between reads

}

A for loop is counting from 0 to 6. bitRead selects the three least significant bits from the integer y and store them into s0, s1 and s2. For example if y is 3(…00011 in binary), s0 is set to 1, s1 is set to 1 and s2 is set to 0. Then they are sent through pins 7, 8, and 9 to the select pins of the multiplexer and the the value is stored in variable sensorValue. The delay is set to 10 ms and each sensor is read 14 times a second.

74hc4051-control1

Check for Input

void checkForInput(){
if (sensorValue < 500){ //check if disk dropped
while (sensorValue <700){ //check if the disk had passed through the sensor
sensorValue = analogRead(A0);}
storeInArray();
win = checkForWin();
boardIsFull();
sendData(); }
}

When idle the value read by the sensor is around 1000 and when there is an object in front of the sensor the value is around 200. This function checks if there is an object in front of the sensor and then keeps reading the sensor until the object had passed – the value goes above 700. This is done to prevent reading the same inputs few times. After the value goes back above 700 the program continues with storing the input, checking if the game is over and sending the data to the other board.

Storing Data

void storeInArray(int column){ //store values in array depending on player
for (int row = 5; row >= 0; row--){
if (board[row][column] == 0){
board[row][column] = playerTurn;
if(playerTurn==1){ //alternate between players
playerTurn=2;}
else if(playerTurn==2){
playerTurn=1;}
break;}
}
}

Transfer Data

This function stores the data in a 2D array. The for loop goes through all rows in the column y starting from the bottom and checks for an empty row indicated by 0. Column y is a global variable that is updated by the for loop in void loop() function. If there is 0 in the row the value is set by playerTurn. If it’s first player turn a 1 is stored then playerTurn is changed to 2 for the next input.

Send data:

if (client.connect(server, 80)) {
 Serial.println("connected");
 // HTTP request:
 client.print( "GET /mysql_connect.php?");
 client.print("countMove=");
 client.print(countMove);
 client.print("&&");
 client.print("column=");
 client.print(y);
 client.println( " HTTP/1.1");
 client.println( "Host: 192.168.0.2" );
 client.println( "Content-Type: application/x-www-form-urlencoded" );
 client.println( "Connection: close" );
 client.println(); }
else {
 // if didn't connect to the server:
 Serial.println("connection failed");

}

This function sends HTTP GET request to mysql_connect.php with the variables countMove and y(column) that will be stored in a database and displayed on displayData.php.

This is what is sent:
http://192.168.0.2/mysql_connect.php?countMove=countMove&column=y
Everything before the question mark is the address of the web server. The remaining data is the two variables and their values after the equal signs.

Receive data:

if (client.connect(server, 80)) {
Serial.print("connected... ");
// Make a HTTP request:
 client.println("GET /displayData.php?");
 client.println( " HTTP/1.1");
 client.println( "Host: 192.168.0.2" );//web server IP
 client.println( "Content-Type: application/x-www-form-urlencoded" );
 client.println( "Connection: close" );
 client.println(); }
else {
 Serial.println("connection failed"); }
if (client.connected()) {
if(client.find("</tr><tr><td>")){
 move = client.parseInt();
 if(client.find("</td><td>")){
 column = client.parseInt(); }
}
else
Serial.println("result not found");
client.stop();
delay(1000); // check again in 1 seconds }

Send HTPP GET request to displayData.php. It will read the page as a HTML source code. Then look for specific tag that precedes the value I’m looking for. The top row of the table will be …</tr><tr><td>move</td><td>column…. The client will use the find method to search through the incoming data and it will return True if the searched string is found. First lt will search for “</tr><tr><td>” that precedes the move number, then parseInt() function will return the first integer value after the current position and it will stop at the first character that is not an integer. After that another if statement will search for “</td><td>” that precedes the column value.

Control of stepper motor and servo

After receiving the remote players’ data a function call to autoInput will calculate the number of steps and direction according to current position and the distance it has to move.

void autoInput(int remoteColumn){
  int moveDistance = 0;
  if (currentPosition == remoteColumn){
    dropDisk(); }
  //check direction
  else if(currentPosition < remoteColumn){
    moveDistance = remoteColumn-currentPosition;
    clockwise(moveDistance);
    dropDisk(); }
  else if(currentPosition > remoteColumn){
    moveDistance = currentPosition-remoteColumn;
    anticlockwise(moveDistance);
    dropDisk(); }
  else{
    Serial.println("wrong remoteColumn"); 
 }
  currentPosition=remoteColumn; //update current position
}

 

The sequence controlling the motor is stored in constant variable stepSequance[8] = {B1000, B1100, B0100, B0110, B0010, B0011, B0001, B1001};

This particular motor has Stride Angle of 5.625 and is geared down by factor of 64. This gives (360/5.625*64)/8=512 steps per revolution. The gear used has diameter of 15 mm and it will move 47.16 mm in one revolution or 1 mm in 10.8567 steps. Distance between two adjacent columns is 35 mm. That gives 35*10.8567= 380 steps between two neighbouring columns.

void clockwise(int moveDistance){
  //move in the clockwise direction
  for(int i = 0; i < moveDistance*380; i++){
    for(int j = 7; j >= 0; j--){
      setDriverIn(j);
      delay(1); }
  }
}
void anticlockwise(int moveDistance){
  //move in the anticlockwise direction
  for(int i = 0; i < moveDistance*380; i++){
    for(int j = 0; j < 8; j++){
      setDriverIn(j);
      delay(1); }
  }
}
void setDriverIn(int i){
  //send step sequence to stepper
  digitalWrite(2, bitRead(stepSequance[i], 0));
  digitalWrite(3, bitRead(stepSequance[i], 1));
  digitalWrite(5, bitRead(stepSequance[i], 2));
  digitalWrite(6, bitRead(stepSequance[i], 3));
}

Next function rotates the Servo and pushes one disk into the column.

void dropDisk(){
  //drop disck areturn to initial position
  servoDrop.write(180); // turn servo to 90 degrees
  delay(500); // wait half a second
  servoDrop.write(90); // return servo to initial position
  delay(500); // wait half a second
}

Check for Win or Draw

   Horizontal check

for (int i = 0; i < 6; i++){ //horizontal check
  for (int j = 0; j < 4; j++){
    if((board[i][j] == 1) && (board[i][j+1] == 1)
&& (board[i][j+2] == 1) && (board[i][j+3] == 1)){
return player1;

       break; }
       else if((board[i][j] == 2) && (board[i][j+1] == 2)
&& (board[i][j+2] == 2) && (board[i][j+3] == 2)){

       return player2;
       break; }
  }
}

This loop checks for horizontal win. The outer loop selects the row and the inner loop checks for four consecutive 1’s or four 2’s in that row. If true return who won and exit the loop.

   Vertical check

for (int k=0; k<6; k++){ //vertical check
  for (int l=0; l<3; l++){
    if((board[l][k] == 1) && (board[l+1][k] == 1) && (board[l+2][k] == 1) && (board[l+3][k] == 1)){   
      return player1;
      break; }
      else if((board[l][k] == 2) && (board[l+1][k] == 2)
&& (board[l+2][k] == 2) && (board[l+3][k] == 2)){

      return player2;
      break; }
  }
}

This loop checks for vertical win. In this case the outer loop selects the column and the inner loop checks for four consecutive 1’s or four 2’s in that column.

Diagonal check in the NW direction

for (int m=0; m < 3; m++){ //diagonal win "\"

  for (int n=0; n < 4; n++){

    if((board[m][n] == 1) && (board[m+1][n+1] == 1)
&& (board[m+2][n+2] == 1) && (board[m+3][n+3] == 1)){

      return player1;
      break; }
    else if((board[m][n] == 2) && (board[m+1][n+1] == 2)
&& (board[m+2][n+2] == 2) && (board[m+3][n+3] == 2)){

      return player2;
      break; }
  }
}

This loop checks for diagonal win in the” \” direction. In this case the outer loop selects the row and the inner loop selects the column. Starting from the top left corner both the row and column are incremented by 1 three times. The loop checks all possible combinations in the “\” direction. The tables below show all cells the loop goes through.

 Diagonal check in the NE direction

for (int o=0; o<3; o++){ //diagonal check "/"

  for (int p=3; p<7; p++){

    if((board[o][p] == 1) && (board[o+1][p-1] == 1)
&& (board[o+2][p-2] == 1) && (board[o+3][p-3] == 1)){

      return player1;
      break; }
    else if((board[o][p] == 2) && (board[o+1][p-1] == 2)
&& (board[o+2][p-2] == 2) && (board[o+3][p-3] == 2)){

      return player2;
      break; }
  }
}

In the same way as “\” direction but this time the inner loop is going from columns 3 to 6 and decrementing the column index while incrementing the rows.

Check for Draw

void boardIsFull(){
  countMoves++;
  if (countMoves > 42){
    endDraw(); }
  }

void endDraw(){
  //indicate that the game ended in a draw
  Serial.println("Board is full"); //used for testing change it to
indicate with led that the game ended in draw and stop the game

}

This function keeps track of the inputs. Every time there is an input the counter is incremented by 1 and when it reaches 42 then the board is full and no one won the game.

6.1 Web Server

The Ethernet shield can run a server on itself. The first setup tested was one of the Arduino boards acting as a web server and the other as a client and that worked fine. But after combining both sketches so that both boards act as a server and as a client the data didn’t update properly, because the Arduino can’t do two things at once. It had to disconnect the server run the rest of the code, then connects as a client, then disconnect the client and connect as a server again in a loop. One way to overcome this problem was to use a web server on external machine. There are two pages one that receives data from the Arduino and stores it in a database and another that displays the data from the database.

PHP MYSQL

The function to connect to MySQL database is called mysql_connect. It returns a pointer to the database connection. The correct host, username, password and database name have to be provided to establish connection. After connecting @mysql_select_db will selects arduino_db. The php script below shows how to connect to arduino_db get values from the web page and store them in members move and columns of the ard_db table.

Connect to database

<?php
$db_host = "localhost";
$db_username = "root";
$db_pass = "";
$db_name = "arduino_db";

$con = @mysql_connect("$db_host" , "$db_username" , "$db_pass") or die ("could not connect to mysql");
@mysql_select_db("$db_name") or die ("no database");


$move = $_GET['column'];
$count = $_GET['countMove'];


$sql = "INSERT INTO ard_DB (move, columns) VALUES ('$move', '$count')";
mysql_query($sql, $con);
mysql_close($con);
?>

Then another php script will extract the data from ard_db and display it on displayData.php. The script below show how this is done. First connect to the database, and then $sql = “SELECT * FROM ard_db” will select all data from table ard_db. Next line will store it into a variable $myData. After that a while loop will fetch the data and display it in a table.

Displaying the data
</head>
<body>
<?php
$con = @mysql_connect("$db_host" , "$db_username" , "$db_pass") or die ("could not connect to mysql");
@mysql_select_db("$db_name") or die ("no database");

$sql = "SELECT * FROM ard_db";
$myData = mysql_query($sql, $con);
echo "<table border=1>
<tr> <th>Column</th> <th>Move</th> </tr>";


while ($record = mysql_fetch_array($myData)){
echo"<tr>";
echo "<td>" . $record['move'] . "</td>";
echo "<td>" . $record['columns'] . "</td>";
echo "</tr>"; }


echo "</table>";
mysql_close($con);
?>

 

The complete codes for controlling the Arduino and the two web pages are available at GitHub

 

7. Further development

  • adding wireless communication
  • checking if the rules are followed
  • storing scores
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Create a free website or blog at WordPress.com.

Up ↑

Create your website at WordPress.com
Get started
%d bloggers like this: