UPDATE: LEGO NXT Tic-Tac-Toe Robot
LEGO Mindstorms NXT TicTacToe (Noughts & Crosses) Playing Robot was programmed with NXC, using Daniele Benedettelli's 'TicTacToe AI' Code. It uses a Hitechnic Colour Sensor to detect the coloured balls placed on the playing board. The game has 3 difficulty levels starting with 'Novice' that places balls at random. Next is 'Normal', followed by the almost impossible 'Expert' level.

I've made a new HD Video of the LEGO NXT TicTacToe Robot in action, which can be seen below. I have also posted my modified NXC Tic-Tac-Toe Mechanincal Control Source Code for the robot as well. Please note, that the "ttt_ai.nxc" code require to compile the listed code, is copyrighted to Daniele Benedettelli, and you will need to purchase his 'Lego Mindstorms NXT: Thinking Robots' Book to gain access to the Code. I highly recommend this book of Daniele's which is worth it just for the excellent tutor value the source code provides.
LEGO NXT Tic-Tac-Toe Robot's NXC Code listing:
/*
NXT firmware 1.28 or Higher is needed for the use with Hitechnic color sensor.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. In particular, you must report the name of the original author.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see www.gnu.org.
Original Code by Daniele Benedettelli (www.robotics.benedettelli.com).Modified by Ray McNamara (www.rjmcnamara.com) to suite a larger scale robot, & to use the Hitechnic Colour Sensor.
*/
/* Files to download to NXT
! Sonar.rso
AnotherGame.rso
Blank.rso
ChooseLevel.rso
ClearBoard.rso
Cross.rso
ExpertLevel.rso
GameDrawn.rso
Goodbye.rso
I_Win.rso
NormalLevel.rso
Nought.rso
NoviceLevel.rso
Play.rso
PressOrangeButt.rso
TicTacToe.rso
TryAgain.rso
UseArrowButtons
Yes.rso
YourTurn.rso
YouWin.rso
ttt.ric
*/
#define TURN OUT_C
#define ARM OUT_B
#define DELIVER OUT_A
#define TURN_TOUCH IN_2
#define US IN_3
#define LIGHT IN_1
// arm positions as angles from zero position
#define ARM0 0
// these are the arm default positions that can be changed by
// the calibration program
#define _S1D 33
#define _S2D 52
#define _S3B1D 80
#define _B2D 100
#define _B3D 130
#define SENSOR1 1
#define SENSOR2 2
#define SENSOR3 3
#define BALL1 3
#define BALL2 4
#define BALL3 5
#define BALL 0
// amount of reflected light from the ball (human uses the Lighter colour)
#define O_TICK_VAL 5 // Yellow
#define X_TICK_VAL 2 // Blue
#define MAIN_TTT
#include "ttt_ai.nxc"
// indexed by type (SENSOR/BALL) and turnstate(oddity)
const int arm_pos1[] = {BALL1, SENSOR1, BALL2, SENSOR2};
const int arm_pos2[] = {BALL3, SENSOR3, BALL3, SENSOR3};
// turntable angles from zero
const int turn_angle[][] = {
{ 0, -45, -90, -135, -180, 135, 90, 45 },
{ 45, 0, -45, -90, -135, -180, 135, 90 },
{ 90, 45, 0, -45, -90, -135, -180, 135 },
{ 135, 90, 45, 0, -45, -90, -135, -180 },
{ 180, 135, 90, 45, 0, -45, -90, -135 },
{ -135, 180, 135, 90, 45, 0, -45, -90 },
{ -90, -135, 180, 135, 90, 45, 0, -45 },
{ -45, -90, -135, 180, 135, 90, 45, 0 }
};
int arm_angle[6][6];
int default_arm_angle[][] = {
{0-0 , _S1D-0 , _S2D-0 , _S3B1D-0 , _B2D-0 , _B3D-0 },
{-_S1D , _S1D-_S1D , _S2D-_S1D , _S3B1D-_S1D , _B2D-_S1D , _B3D-_S1D },
{-_S2D , _S1D-_S2D , _S2D-_S2D , _S3B1D-_S2D , _B2D-_S2D , _B3D-_S2D },
{-_S3B1D, _S1D-_S3B1D, _S2D-_S3B1D, _S3B1D-_S3B1D, _B2D-_S3B1D, _B3D-_S3B1D},
{-_B2D , _S1D-_B2D , _S2D-_B2D , _S3B1D-_B2D , _B2D-_B2D , _B3D-_B2D },
{-_B3D , _S1D-_B3D , _S2D-_B3D , _S3B1D-_B3D , _B2D-_B3D , _B3D-_B3D }
};
int arm_state;
int turn_state;
int light_offset;
bool color_sensor = true;
int r=0;
int g=0;
int b=0;
int BallColour=0;
const int turn_cell_corr[] = { 7, 0, 1, 6 ,0 ,2, 5, 4, 3 };
void MoveUntilStall(byte motor, int speed, int min, int ts)
{
int s = min+1;
// start motor
OnFwd(motor,speed);
Wait(100);
if (speed<=40) Wait(100);
// until a stall is detected
while( s > min )
{
s = MotorRotationCount(motor);
Wait(ts);
ClearScreen();
s = abs(s-MotorRotationCount(motor));
}
// stop motor
Off(motor);
}
void MyRotateMotor( byte port, int power, int ref )
{
int startpos, pos, e, eold, P, I, D, PID, PIDsat, t;
int count = 0;
eold = 0;
e = 10;
bool done = false;
startpos = MotorRotationCount(port) + ref;
until(done)
{
pos = MotorRotationCount(port);
e = startpos-pos;
PID = 5*e + 3*(e-eold);
PIDsat = abs(PID)>power? sign(PID)*power : PID;
eold = e;
if (abs(e)<1)
{
count++;
}
OnFwd(port,PIDsat);
Wait(25);
if (count>3)
{
done = true;
}
}
}
void ResetTurntable()
{
OnRevReg(TURN,40,OUT_REGMODE_SPEED);
while(Sensor(TURN_TOUCH));
Wait(100);
until(Sensor(TURN_TOUCH));
Off(TURN);
RotateMotor(TURN,30,5);
turn_state = 0;
}
void ResetArm()
{
// Home Arm
OnRev(ARM, 60);
Wait(SEC_1);
Off(ARM);
MoveUntilStall(ARM,-30,1,30);
RotateMotorExPID(ARM,100,10,0,0,1,40,30,90);
arm_state = ARM0;
}
void Deliver()
{
RotateMotorExPID(DELIVER,60, 160,0,false,true,40,20,80);
RotateMotorExPID(DELIVER,80,-160,0,false,true,40,20,80);
PlayFile("Cross.rso");
until (SoundFlags() == SOUND_FLAGS_IDLE);
Wait(SEC_1);
PlayFile("YourTurn.rso");
until (SoundFlags() == SOUND_FLAGS_IDLE);
}
void MoveArm(int new_state)
{
int angle;
angle = arm_angle[arm_state][new_state];
RotateMotor(ARM,50,angle);
arm_state = new_state;
}
void Turn(int new_state)
{
int angle;
angle = turn_angle[turn_state][new_state];
MyRotateMotor(TURN,70,angle);
turn_state = new_state;
}
void InitHW()
{
TextOut(0,LCD_LINE1,"Hardware init");
SetSensorTouch(TURN_TOUCH);
SetSensorLowspeed(US);
SetSensorLowspeed(LIGHT);
LoadArmSettings();
ResetArm();
ResetTurntable();
MoveUntilStall(DELIVER,-20,1,30);
RotateMotor(DELIVER,80,20);
}
int MeasureBall()
{
int meas = 0;
repeat(5)
{
if (ReadSensorHTColor(LIGHT, BallColour, r, g, b)) {
meas += BallColour;
Wait(100); }
}
meas /= 5;
if (BallColour == 0) {PlayFile("Blank.rso");}
if (BallColour == 5) {PlayFile("Nought.rso");}
until (SoundFlags() == SOUND_FLAGS_IDLE);
if (meas == 2) return X_TICK;
if (meas == 5) return O_TICK;
return CURSOR;
}
int Place(int cell, byte type)
{
int next_state = turn_cell_corr[cell];
int read, parity;
parity = (cell%2); // turn state parity (1 if even, 0 if odd)
if (cell!=4)
{
Turn(next_state);
//NumOut(0,0,type+2*parity);
//NumOut(20,0,arm_pos1[type+2*parity]);
//Wait(1000);
MoveArm(arm_pos1[type+2*parity]);
}
else
MoveArm(arm_pos2[type+2*parity]);
if (type==BALL) Deliver();
if (type==SENSOR)
{
read = MeasureBall();
return read;
}
return 0;
}
int reading[9];
const int read_seq[] = {4,1,0,3,6,7,8,5,2};
void ScanBoard ()
{
bool found = false;
until (found)
{
for(int i=0; i<9;i++)
{
DrawBoard(NO_CURSOR);
if (IsLegal(read_seq[i]))
{
reading[read_seq[i]] = Place(read_seq[i],SENSOR);
if (reading[read_seq[i]]==O_TICK)
{
ApplyMove(human,read_seq[i]);
found = true;
break;
}
}
}
}
}
void GetMoveHW()
{
bool done = false;
int t;
Wait(200);
while(SensorUS(US)>10);
until(SensorUS(US)<12);
PlayFile("! Sonar.rso");
until (SoundFlags() == SOUND_FLAGS_IDLE);
ScanBoard();
DrawBoard(NO_CURSOR);
}
void ComputeMoveHW()
{
int move;
move = ComputeMove(AIlevel);
//TextOut(0,0, FormatNum("move: %d",move));Wait(1000);
DrawBoard(NO_CURSOR);
Place(move,BALL);
MoveArm(ARM0);
ResetTurntable();
}
void WelcomeScreen()
{
TextOut(0,LCD_LINE1,"Clear board and",true);
PlayFile("ClearBoard.rso");
until (SoundFlags() == SOUND_FLAGS_IDLE);
Wait(SEC_1);
TextOut(0,LCD_LINE2,"press orange Btn");
PlayFile("PressOrangeButt.rso");
until (SoundFlags() == SOUND_FLAGS_IDLE);
until(ButtonPressed(BTNCENTER,true));
while(ButtonPressed(BTNCENTER,true));
}
task main()
{
OnRev(ARM, 60);
Wait(SEC_1);
Off(ARM);
PlayFile("Play.rso");
until (SoundFlags() == SOUND_FLAGS_IDLE);
Wait(500);
PlayFile("TicTacToe.rso");
until (SoundFlags() == SOUND_FLAGS_IDLE);
InitHW();
bool another = true;
while (another)
{
WelcomeScreen();
AIlevel = SelectAILevel();
InitBoard(); // init game
until (GetGameStatus()!=OK) // until someone wins
{
DrawBoard(NO_CURSOR);
GetMoveHW(); // user moves
if (GetGameStatus()==OK)
{
ComputeMoveHW(); // computer moves
}
}
DisplayResults();
ResetArm();
ResetTurntable();
another = AskForNewGame();
}
PlayFile("Goodbye.rso");
until (SoundFlags() == SOUND_FLAGS_IDLE);
Wait(SEC_1);
}
CODE Downloads
Download the listed NXC Code: TicTacToeBot.zip 3.1Kb
Download RSO Sound Files used: TicTacToeSounds.zip 66.2Kb
loading...
loading...

Download PDF format





Very cool! This is really neat.
loading...
loading...