Homework 3Input and Interaction |
Modified: |
ASSIGNMENT
Create a TicTacToe game that plays a human against the computer:
TEST
- Verify your implementation satisfies the assignment and behaves similarly to the example.
HINTS
Game
Keep in mind that the game is still 2D and the transformations of Chapter 4 are not required. You may find it helpful to think in terms of grand scene where all objects are placed relative to some fixed origin point.
The graphical objects (X, O, reset button and board) are good candidates for object-oriented programming. Suggest defining each object in terms of a center point and scale factor. The objects can include x,y location, size, color, etc. to allow moving and placement at different locations in the scene.
The following Game class can manage a game state that can be initialized, updated and queried.
The following:
- creates a new Game object
- resets the Game board,
- places an 'o' in the middle of the board,
- and has the computer to pick a move for 'x'.
Game g = new Game();
g.reset();
g.legalMove(5);
g.computerMove();
class Game {
private:
unsigned char board[9];
unsigned char player;
/**
* asInt - Returns a char '0'-'9' as int 0-9 .
*/
int asInt( char ch ){ return (int)(ch-48); }
unsigned char itoc(int a) { return (unsigned char)(a+'0'); }
/**
* get - Converts char at string offset to int.
*/
int Get (char * Str, int Off) { return asInt(Str[Off]); }
/**
* move - Determines computer move.
*
* Originally written by Stephen Wassell in JavaScript
* swassell@sv.span.com
*/
int move () {
char *PosLines, *Order = "2613", *PosCorns = "013215637857";
int j, i, a, b, c;
int Dat[9];
for(i=0; i<9; i++) {
Dat[i]=0;
if (board[i]=='x') Dat[i]=1;
if (board[i]=='o') Dat[i]=3;
}
if (board[4] == 'x') //if computer's in center
PosLines = "021687063285435417408426";
else
PosLines = "408426021687063285435417";
for (j = 0; j < 4; j++) {
for (i = 0; i < 24; i += 3) {
a = Get (PosLines, i);
b = Get (PosLines, i + 1);
c = Get (PosLines, i + 2);
if (Dat[a]+Dat[b]+Dat[c] == Get (Order, j)) {
if (board[a] == itoc(a)) return a;
if (board[b] == itoc(b)) return b;
if (board[c] == itoc(c)) return c;
}
}
if (j == 1) { //only between 2nd and 3rd passes
for (i = 0; i < 12; i += 3) {
a = Get (PosCorns, i);
b = Get (PosCorns, i + 1);
c = Get (PosCorns, i + 2);
if (Dat[a]+Dat[b]+Dat[c] == 6)
if (board[a] == itoc(a)) return a;
}
}
}
return -1; //no places to go
}
public:
Game() {};
/**
* computerMove - Returns computer move, if one available.
*/
int computerMove() {
int m=move();
if(m!=-1) board[m]='x';
return m;
}
/**
* reset - Resets the game.
*/
void reset() {
for(int i=0;i<=8; i++)
board[i]=itoc(i);
this->player='o';
};
/**
* winningMoves - Returns the 3 winning moves.
*/
int * winningMoves() {
int *moves=new int[3];
char wins[] = "012345678036147258048246";
int i0, i1, i2;
for (int awin=0; awin<8; awin++) {
i0 = wins[awin*3]-'0';
i1 = wins[awin*3+1]-'0';
i2 = wins[awin*3+2]-'0';
if (board[i0] == board[i1] && board[i1] == board[i2] ) {
moves[0]=i0;
moves[1]=i1;
moves[2]=i2;
return moves;
}
}
return moves;
}
/**
* finished - Returns whether game is finished.
*/
bool finished() { return won() || tied(); }
bool won() {
char wins[] = "012345678036147258048246";
int i0, i1, i2;
for (int awin=0; awin<8; awin++) {
i0 = wins[awin*3]-'0';
i1 = wins[awin*3+1]-'0';
i2 = wins[awin*3+2]-'0';
if (board[i0] == board[i1] && board[i1] == board[i2] ) return true;
}
return false;
}
/**
* tied - True when game is tied, all positions taken.
*/
bool tied() {
for (int i=0; i<=8; i++)
if (board[i] != 'x' && board[i] != 'o')
return false;
return true;
}
/**
* legalMove - Returns false when move i is illegal, no change to the board.
* - Returns true when move i is legal, places an 'o' on board position i.
*/
bool legalMove(int i) {
if(board[i]==itoc(i)) {
board[i]='o';
return true;
}
return false;
}
};
Picking and Moving
The example (execute the example) picks and moves a triangle and rectangle. Some explanation of the code is warranted.
Object modeling
The triangle and rectangle are modeled as objects (in both the graphical and programming sense) meaning that their topology is defined but not geometry.
Each has a world reference point (x, y) from which the figure is drawn. The (x,y) point is initially defined when the objects are defined and (x,y) set as the object is moved.
class Polygon {
protected:
GLfloat x, y;
GLfloat *color;
GLint id;
public:
Polygon(GLfloat x, GLfloat y, GLfloat *c, GLint id) {
this->x = x;
this->y = y;
this->id=id;
color=c;
};
void setXY( GLfloat x, GLfloat y) { this->x = x; this->y = y; }
virtual void draw(GLenum mode) abstract {};
};
class Triangle : public Polygon {
public:
Triangle(GLfloat x, GLfloat y, GLfloat *color, GLint id) : Polygon(x, y, color, id){ };
virtual void draw(GLenum mode) {
if( mode == GL_SELECT) glLoadName(id);
glColor3fv(color);
glBegin(GL_POLYGON);
glVertex2f(x-1.5, y-1.5);
glVertex2f(x+1.0, y+1.0);
glVertex2f(x+1.5, y+0.5);
glEnd();
}
};
class Rectangle : public Polygon {
public:
Rectangle(GLfloat x, GLfloat y, GLfloat *color, GLint id) : Polygon(x, y, color, id){ };
virtual void draw(GLenum mode) {
if( mode == GL_SELECT) glLoadName(id);
glColor3fv(color);
glRectf(x-0.5, y-0.5, x+0.5, y+0.5);
}
};
Global variables and constants define and identify each object.
#define TRIANGLE 6
#define RECTANGLE 9
Triangle *triangle;
Rectangle *rectangle;
Polygon *object;
Drawing
Normal drawing calls each object's draw method in RENDER mode. More below.
void display() {
glClear(GL_COLOR_BUFFER_BIT);
triangle->draw(GL_RENDER);
rectangle->draw(GL_RENDER);
glFlush();
}
Picking
When the object is drawn in method draw, the mode parameter defines whether in SELECT or RENDER mode. Recall that SELECT mode allows the identification number of the object to be placed on the picking stack. SELECT mode also records whether the object intersects the viewport window around the mouse pointer, essentially recording that the object was picked.
The code below is called when the mouse is clicked, drawing the triangle and rectangle in SELECT mode. The identification number of any object drawn intersecting the viewport around the mouse pointer is stored in selectBuf for later processing.
void click( int x, int y ) {
GLuint selectBuf[SIZE];
GLint hits;
GLint viewport[4];
glGetIntegerv (GL_VIEWPORT, viewport);
glSelectBuffer (SIZE, selectBuf);
glRenderMode(GL_SELECT);
glInitNames();
glPushName(0);
glMatrixMode (GL_PROJECTION);
glPushMatrix ();
glLoadIdentity ();
/* create 5x5 pixel picking region near cursor location */
gluPickMatrix ((GLdouble) x, (GLdouble) (viewport[3] - y), 5.0, 5.0, viewport);
gluOrtho2D (-2.0, 2.0, -2.0, 2.0);
triangle->draw(GL_SELECT);
rectangle->draw(GL_SELECT);
glMatrixMode (GL_PROJECTION);
glPopMatrix ();
glFlush ();
hits = glRenderMode (GL_RENDER);
processHits (hits, selectBuf, x, y);
glutPostRedisplay();
}
Processing
Processing any picking hits is a matter of mapping the identification number of the intersecting object to the corresponding triangle or rectangle. The global variable object then defines the object that was picked and is to be moved.
void processHits (GLint hits, GLuint buffer[], int x, int y)
{
unsigned int i, j;
GLuint *ptr;
ptr = (GLuint *) buffer+3;
if(hits == 0) return;
switch (*ptr) {
case TRIANGLE : object = triangle; break;
case RECTANGLE : object = rectangle;
}
}
Moving
The motion callback function receives the display (x,y) position of the mouse pointer as the mouse moves with a button depressed. Recall that object is the object that was picked; the world (x,y) reference point for the object is set and the scene redisplayed.
The d2w function transforms display to world coordinates, assuming the window is 500x500. The constants are:
- 500 because the symmetric window size: glutInitWindowSize (500, 500);
- 4 because the world is 4x4 units: gluOrtho2D (-2.0, 2.0, -2.0, 2.0);
- 2 because the world is 4x4 units centered at the origin.
double d2w( int d ) {
return (d/500.0)*4.0-2.0;
}
void motion(int x, int y){
object->setXY(d2w(x), d2w(500-y));
glutPostRedisplay();
}
The most basic issue with stroked characters is one of scaling. The below example uses the Glut defaults so that a scale factor of 0.001 fits stroked text nicely within the window.
#include <GL/glut.h>
void output(GLfloat x, GLfloat y, char *p, GLfloat scale) {
glPushMatrix();
glTranslatef(x, y, 0);
glScalef(scale, scale, scale);
for(; *p; p++) glutStrokeCharacter(GLUT_STROKE_ROMAN, *p);
glPopMatrix();
}
void display() {
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glColor3f(1.0,0,0);
output(0,0,"OK!!", 0.001);
glFlush();
}
int main(int argc, char **argv) {
glutInit(&argc, argv);
glutCreateWindow("glutStrokeCharacter");
glutDisplayFunc(display);
glClearColor (1.0, 1.0, 1.0, 0.0);
glutMainLoop();
}