What Matters 2 Me ... Science, photography, games, entertainment, gadgets, health, rants and raves. 2011-10-21T18:04:11Z http://www.whatmatters2me.com/feed/atom/ WordPress Jeff <![CDATA[3D TicTacToe – 3. Programming the Arduino To Play]]> http://www.whatmatters2me.com/?p=276 2011-10-21T18:04:11Z 2011-10-20T17:10:15Z The continuation of my 3D TicTacToe LED Matrix project.  See Introduction and Part 2.

Download the code directly here: LED_Matrix_TTT4.pde

Some general notes:

1. The AI is based on game theory taken from a book I read about 20 years ago. Unfortunately I don’t remember the name of it. It uses a Minimax algorithm as well as Alpha-beta pruning to decrease the number of nodes that need to be evaluated. Wikipedia offers a concise description of these with many external links. I spent about a month learning the algorithms and coding them, so I’m sure they can be greatly enhanced, especially since more than 20 years has past. However, I am proud to say that pitting this program up against another 3D TTT I found online, that mine trounced the other.

2. I wanted the LED matrix cube to be constantly displaying the player LED spot selection, the current cube moves, or the computer thinking (LEDs going on and off). In order to do this, even while the Arduino is off doing other things, I needed to use interrupts.  Using the MsTimer2 library I was able to set up a routine, disp_LED_cube, that was called every 200 ms with 1 of 4 states:

  • state 0: Default display of all moves
  • state 1: setting up player’s move
  • state 3: blinking last computer move
  • state 4: computer “thinking”

3. The current program uses a keypad to make the moves. The keys 1-4 are for selection of the player’s move. It first uses a ‘wall’ to select the z-direction, with the keys moving the wall. After the proper wall is selected, the ’5′ key will then lock-in that wall, and display a column that represents the y-direction. Again, the 1-4 keys allow for the selection of the wished-upon column. Player then pushes ’5′ key to lock-in column. Finally, user selects for the specific LED in column they wish to use as their move. Pressing the ’5′ key will begin the Arduino deciding its move. I’m currently working on code to use a thumbstick rather than the keypad.

4. The code is GPL. Please use it as you like and definitely improve upon it. I would, of course, really like you to credit me for the work I’ve put into it should you use it.

Here is the code:

/*

	LED_Matrix_TTT4.C - An AI version of Tic-Tac-Toe, played on a 4x4x4 LED matrix
        powered by an Arduino

    Copyright (C) 2011  Jeffrey R. Gilmour, Ph.D.

    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.

    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 <http://www.gnu.org/licenses/>.

*/

#include <Sprite.h>
#include <Matrix.h>
#include <MsTimer2.h>	// Uses pins 3 & 11, so DON'T USE those

const int loadPin = 4;
const int clockPin = 5;
const int dataPin = 6;

Matrix myMatrix = Matrix(dataPin, clockPin, loadPin);

#define FALSE 0
#define TRUE !FALSE
boolean OLedsOn = TRUE;

char cube0[4][4][4], cube1[4][4][4], cube2[4][4][4], cube3[4][4][4], human_symbol='X', comp_symbol='O';

const int period = 200;

const int numRows = 2;
const int rowPins[numRows] = { 7, 8 };		// Keypad pins 1 & 2, attached to Arduino pins 7 & 8

const int numCols = 3;
const int colPins[numCols] = { 9, 10, 12 };	// Keypad pins 5, 6 & 7

const int debounceTime = 200;

const char keymap[numRows][numCols] = {		// USE PINS 1, 2, 5, 6, 7 on keypad
	{ '1', '2', '3' },
	{ '4', '5', '6' }
};

char key = 0;

int state = 0, state1x = 5, state1y = 5, state1z = 5;
int	bestmovex, bestmovey, bestmovez;

char getKey() {
	key = 0;	

	for (int column = 0; column < numCols; column++) {
		digitalWrite(colPins[column], LOW);
		for (int row = 0; row < numRows; row++) {
			if(digitalRead(rowPins[row]) == LOW) {
				delay(debounceTime);
				while(digitalRead(rowPins[row]) == LOW) ;
				key = keymap[row][column];
			}
		}
		digitalWrite(colPins[column], HIGH);
	}

	return key;
}

char check(char board[4][4]) {

	int i;

	for (i=0;i<4;i++)		/* check rows */
		if (board[i][0]==board[i][1] &&
		   board[i][0]==board[i][2] &&
		   board[i][0]==board[i][3]) if (board[i][0]!=' ')
		   	return board[i][1];

	for (i=1;i<=4;i++)		/* check columns */
		if (board[0][i]==board[1][i] &&
		   board[0][i]==board[2][i] &&
		   board[0][i]==board[3][i]) if (board[0][i]!=' ')
		   	return board[1][i];

	/* test diagonals */
	if (board[0][0]==board[1][1] && board[1][1]==board[2][2] &&
	   board[2][2]==board[3][3] && board[0][0]!=' ')
		return board[1][1];

	if (board[0][3]==board[1][2] && board[1][2]==board[2][1] &&
	   board[2][1]==board[3][0] && board[0][3]!=' ')
		 return board[1][2];

	return ' ';
}

void disp_LED_cube(void) {

	if (state == 0) {		// Default display of all moves
		for (int z=0;z<4;z++)
			for (int y=0;y<4;y++)
				for (int x=0;x<4;x++) {
					if (OLedsOn) {
						if (cube0[x][y][z] == 'O') {
							if (z == 0) {
								myMatrix.write(y, x, LOW);
							} else if (z == 1) {
								myMatrix.write(y, x+4, LOW);
							} else if (z == 2) {
								myMatrix.write(y+4, x+4, LOW);
							} else if (z == 3) {
								myMatrix.write(y+4, x, LOW);
							}
						}
					} else {
						if (cube0[x][y][z] != ' ') {
							if (z == 0) {
								myMatrix.write(y, x, HIGH);
							} else if (z == 1) {
								myMatrix.write(y, x+4, HIGH);
							} else if (z == 2) {
								myMatrix.write(y+4, x+4, HIGH);
							} else if (z == 3) {
								myMatrix.write(y+4, x, HIGH);
							}
						}
					}
				}
		OLedsOn = !OLedsOn;
	}

	if (state == 1) {	// setting up player's move
		myMatrix.clear();
		if (state1z < 5) {

						if (state1z == 0) {
							myMatrix.write(state1y, state1x, HIGH);
						} else if (state1z == 1) {
							myMatrix.write(state1y, state1x+4, HIGH);
						} else if (state1z == 2) {
							myMatrix.write(state1y+4, state1x+4, HIGH);
						} else if (state1z == 3) {
							myMatrix.write(state1y+4, state1x, HIGH);
						}

		} else
		if (state1y < 5) {
				for (int z=0;z<4;z++)
						if (z == 0) {
							myMatrix.write(state1y, state1x, HIGH);
						} else if (z == 1) {
							myMatrix.write(state1y, state1x+4, HIGH);
						} else if (z == 2) {
							myMatrix.write(state1y+4, state1x+4, HIGH);
						} else if (z == 3) {
							myMatrix.write(state1y+4, state1x, HIGH);
						}
		} else
		if (state1x < 5) {
				for (int z=0;z<4;z++)
					for (int y=0;y<4;y++) {
						if (z == 0) {
							myMatrix.write(y, state1x, HIGH);
						} else if (z == 1) {
							myMatrix.write(y, state1x+4, HIGH);
						} else if (z == 2) {
							myMatrix.write(y+4, state1x+4, HIGH);
						} else if (z == 3) {
							myMatrix.write(y+4, state1x, HIGH);
						}
					}
		}
	}

	if (state == 3) {		// blinking last computer move

		if (OLedsOn) {
			if (bestmovez == 0) {
				myMatrix.write(bestmovey, bestmovex, LOW);
			} else if (bestmovez == 1) {
				myMatrix.write(bestmovey, bestmovex+4, LOW);
			} else if (bestmovez == 2) {
				myMatrix.write(bestmovey+4, bestmovex+4, LOW);
			} else if (bestmovez == 3) {
				myMatrix.write(bestmovey+4, bestmovex, LOW);
			}
		} else {
			if (bestmovez == 0) {
				myMatrix.write(bestmovey, bestmovex, HIGH);
			} else if (bestmovez == 1) {
				myMatrix.write(bestmovey, bestmovex+4, HIGH);
			} else if (bestmovez == 2) {
				myMatrix.write(bestmovey+4, bestmovex+4, HIGH);
			} else if (bestmovez == 3) {
				myMatrix.write(bestmovey+4, bestmovex, HIGH);
			}
		}
		OLedsOn = !OLedsOn;
	}

	if (state == 4) {		// computer "thinking"
		myMatrix.clear();
		for (int z=0;z<4;z++)
			for (int y=0;y<4;y++)
				for (int x=0;x<4;x++) {
					if (OLedsOn) {
						if (cube3[x][y][z] == 'O') {
							if (z == 0) {
								myMatrix.write(y, x, LOW);
							} else if (z == 1) {
								myMatrix.write(y, x+4, LOW);
							} else if (z == 2) {
								myMatrix.write(y+4, x+4, LOW);
							} else if (z == 3) {
								myMatrix.write(y+4, x, LOW);
							}
						}
					} else {
						if (cube3[x][y][z] != ' ') {
							if (z == 0) {
								myMatrix.write(y, x, HIGH);
							} else if (z == 1) {
								myMatrix.write(y, x+4, HIGH);
							} else if (z == 2) {
								myMatrix.write(y+4, x+4, HIGH);
							} else if (z == 3) {
								myMatrix.write(y+4, x, HIGH);
							}
						}
					}
				}
		OLedsOn = !OLedsOn;
	}

}

char check_cube(char cube[4][4][4]) {

	int x, y, z, n=0;
	char done, board[4][4];

	for (z=0;z<4;z++) {	/* boards in Z direction */
		for (y=0;y<4;y++)
			for (x=0;x<4;x++)
				board[x][y]=cube[x][y][z];
		done=check(board);
		if (done!=' ') return done;
	}

	for (z=0;z<4;z++) {	/* boards in Y direction */
		for (y=0;y<4;y++)
			for (x=0;x<4;x++)
				board[x][y]=cube[x][z][y];
		done=check(board);
		if (done!=' ') return done;
	}

	for (z=0;z<4;z++) {	/* boards in X direction */
		for (y=0;y<4;y++)
			for (x=0;x<4;x++)
				board[x][y]=cube[z][y][x];
		done=check(board);
		if (done!=' ') return done;
	}

	/* 3D diagonal 1 */
	if (cube[0][0][0]==cube[1][1][1] && cube[0][0][0]==cube[2][2][2]
		&& cube[0][0][0]==cube[3][3][3] && cube[0][0][0]!=' ')
		return cube[0][0][0];

	/* 3D diagonal 2 */
	if (cube[0][0][3]==cube[1][1][2] && cube[0][0][3]==cube[2][2][1]
		&& cube[0][0][3]==cube[3][3][0] && cube[0][0][3]!=' ')
		return cube[0][0][3];

	/* 3D diagonal 3 */
	if (cube[3][0][0]==cube[2][1][1] && cube[3][0][0]==cube[1][2][2]
		&& cube[3][0][0]==cube[0][3][3] && cube[3][0][0]!=' ')
		return cube[3][0][0];

	/* 3D diagonal 4 */
	if (cube[0][3][0]==cube[1][2][1] && cube[0][3][0]==cube[2][1][2]
		&& cube[0][3][0]==cube[3][0][3] && cube[0][3][0]!=' ')
		return cube[0][3][0];

	for (z=0;z<4;z++)	/* if no spaces are empty, game is a draw */
		for (y=0;y<4;y++)
			for (x=0;x<4;x++)
				if (cube[x][y][z]==' ') n++;
	if (n==0) {
		// Game is a draw - how to communicate to player?
		loop();
	}

	return done;
}

void copy_cube(char b2[4][4][4], char b1[4][4][4]) {

	int x,y,z;

	for (z=0;z<4;z++)
		for (y=0;y<4;y++)
			for (x=0;x<4;x++) b2[x][y][z]=b1[x][y][z];

}

void get_computer_move(void) {

	long int	s0=-100000, s1, s2;
	int		test=0, x1, y1, z1, x2, y2, z2, x3, y3, z3, NoGood, NoGood2;
	int		score;
	char	done;

	state = 4;		// computer "thinking" LED display

	for (z1=0;z1<4;z1++)
	    for (y1=0;y1<4;y1++)
		for (x1=0;x1<4;x1++) {

		    s1=100000;
		    NoGood=FALSE;

		    if (cube0[x1][y1][z1]==' ') {
			copy_cube(cube1,cube0);
			cube1[x1][y1][z1]=comp_symbol;

			done = check_cube(cube1);	// Computer has WON!
			if (done==comp_symbol) {
				bestmovex = x1;
				bestmovey = y1;
				bestmovez = z1;

				state = 3;		// blinking last move of computer
				while (getKey() == 0) ;
				state = 0;

				copy_cube(cube0,cube1);

				while (getKey() == 0) ;
				loop();

			}

			for (z2=0;z2<4 && !NoGood;z2++)
			    for (y2=0;y2<4 && !NoGood;y2++)
					for (x2=0;x2<4 && !NoGood;x2++) {

				    	s2=-100000;
				    	NoGood2=FALSE;

					    if (cube1[x2][y2][z2]==' ') {
							copy_cube(cube2,cube1);
							cube2[x2][y2][z2]=human_symbol;

							for (z3=0;z3<4 && !NoGood2;z3++)
							    for (y3=0;y3<4 && !NoGood2;y3++)
									for (x3=0;x3<4 && !NoGood2;x3++) {
								    	if (cube2[x3][y3][z3]==' '){
											copy_cube(cube3,cube2);
											cube3[x3][y3][z3]=comp_symbol;
							   	 			score=score_cube(cube3);
											if (s2<score) {
										    	s2=score;
										    	if (s2>s1) NoGood2=TRUE;
											}
								    	}
									}
					   		if (s1>s2) {
					   		   	s1=s2;
					   		   	if (s1<s0) NoGood=TRUE;
							}
					    }
			}

			if (s0<s1) {
			    s0=s1;
			    bestmovex=x1;
			    bestmovey=y1;
			    bestmovez=z1;
			}
		    }
		}

	cube0[bestmovex][bestmovey][bestmovez]=comp_symbol;

	myMatrix.clear();
	state = 3;					// blinking last move of computer
	while (getKey() == 0) ;		// wait until player has pressed a button, then display regular cube
	state = 0;
}

int score_cube(char cube[4][4][4]) {

	int scores=0, x, y, z, tally_comp=0, tally_human=0;
	int comp[5]={0,0,0,0,0}, human[5]={0,0,0,0,0};
	char board[4][4];

	for (z=0;z<4;z++) {	/* boards in Z direction */
		for (y=0;y<4;y++)
			for (x=0;x<4;x++)
				board[x][y]=cube[x][y][z];
		scores+=score(board);
	}

	for (z=0;z<4;z++) {	/* boards in Y direction */
		for (y=0;y<4;y++)
			for (x=0;x<4;x++)
				board[x][y]=cube[x][z][y];
		scores+=score(board);
	}

	for (z=0;z<4;z++) {	/* boards in X direction */
		for (y=0;y<4;y++)
			for (x=0;x<4;x++)
				board[x][y]=cube[z][y][x];
		scores+=score(board);
	}

	for (z=0;z<4;z++) {	/* 3D diagonal 1 */
		tally_comp+=(cube[z][z][z]==comp_symbol);
		tally_human+=(cube[z][z][z]==human_symbol);
	}

	if (tally_comp>0 && tally_human>0) {
		tally_comp=0;
		tally_human=0;
	}

	comp[tally_comp]++;
	human[tally_human]++;

	tally_comp=0;
	tally_human=0;

	for (z=0;z<4;z++) {	/* 3D diagonal 2 */
		tally_comp+=(cube[3-z][z][z]==comp_symbol);
		tally_human+=(cube[3-z][z][z]==human_symbol);
	}

	if (tally_comp>0 && tally_human>0) {
		tally_comp=0;
		tally_human=0;
	}

	comp[tally_comp]++;
	human[tally_human]++;

	tally_comp=0;
	tally_human=0;

	for (z=0;z<4;z++) {	/* 3D diagonal 3 */
		tally_comp+=(cube[z][3-z][z]==comp_symbol);
		tally_human+=(cube[z][3-z][z]==human_symbol);
	}

	if (tally_comp>0 && tally_human>0) {
		tally_comp=0;
		tally_human=0;
	}

	comp[tally_comp]++;
	human[tally_human]++;

	tally_comp=0;
	tally_human=0;

	for (z=0;z<4;z++) {	/* 3D diagonal 4 */
		tally_comp+=(cube[z][z][3-z]==comp_symbol);
		tally_human+=(cube[z][z][3-z]==human_symbol);
	}	

	if (tally_comp>0 && tally_human>0) {
		tally_comp=0;
		tally_human=0;
	}

	comp[tally_comp]++;
	human[tally_human]++;

	scores+=comp[1]-3*human[1]+7*comp[2]-15*human[2]+31*comp[3]-63*human[3]+127*comp[4]-1000*human[4];
	return scores;

}

int score(char board[4][4]) {

	int x, y, tally_comp=0, tally_human=0, comp[5]={0,0,0,0,0}, human[5]={0,0,0,0,0};

	for (y=0;y<4;y++) {						/* Rows */
		for (x=0;x<4;x++) {
			tally_comp+=(board[x][y]==comp_symbol);
			tally_human+=(board[x][y]==human_symbol);
		}

		if (tally_comp>0 && tally_human>0) {
			tally_comp=0;
			tally_human=0;
		}

		comp[tally_comp]++;
		human[tally_human]++;

		tally_comp=0;
		tally_human=0;
	}

	for (x=0;x<4;x++) {						/* Columns */
		for (y=0;y<4;y++) {
			tally_comp+=(board[x][y]==comp_symbol);
			tally_human+=(board[x][y]==human_symbol);
		}

		if (tally_comp>0 && tally_human>0) {
			tally_comp=0;
			tally_human=0;
		}

		comp[tally_comp]++;
		human[tally_human]++;

		tally_comp=0;
		tally_human=0;
	}

	for (y=0;y<4;y++) {						/* Down diagonal */
		tally_comp+=(board[y][y]==comp_symbol);
		tally_human+=(board[y][y]==human_symbol);
	}

	if (tally_comp>0 && tally_human>0) {
		tally_comp=0;
		tally_human=0;
	}

	comp[tally_comp]++;
	human[tally_human]++;

	tally_comp=0;
	tally_human=0;

	for (y=0;y<4;y++) {						/* Up diagonal */
		tally_comp+=(board[3-y][y]==comp_symbol);
		tally_human+=(board[3-y][y]==human_symbol);
	}

	if (tally_comp>0 && tally_human>0) {
		tally_comp=0;
		tally_human=0;
	}

	comp[tally_comp]++;
	human[tally_human]++;

	y=comp[1]-3*human[1]+7*comp[2]-15*human[2]+31*comp[3]-63*human[3]+127*comp[4]-1000*human[4];
	/* Needs to be the same evaluation function as in 'score_cube' function */
	return y;

}

void init_cube (void) {

	int x,y,z;

	for(z=0;z<4;z++)
		for(y=0;y<4;y++)
			for(x=0;x<4;x++) cube0[x][y][z]=' ';
}

void get_player_move(void) {

	int x,y,z;

	while (getKey() == 0) ;

	state = 1;
	state1x = 5;
	state1y = 5;
	state1z = 5;

	do {
		while (getKey() == 0) ;
		if (key < '5')
			state1x = (key - '0') - 1;
	} while (key != '5' || state1x > 3 );
	x = state1x;

	do {
		while (getKey() == 0) ;
		if (key < '5')
			state1y = (key - '0') - 1;
	} while (key != '5' || state1y > 3 );
	y = state1y;

	do {
		while (getKey() == 0) ;
		if (key < '5')
			state1z = (key - '0') - 1;
	} while (key != '5' || state1z > 3 );
	z = state1z;

	if (cube0[x][y][z]!=' ') {			// Invalid move - redo entry
		get_player_move();
	}

	cube0[x][y][z]=human_symbol;
	state = 0;
}

void setup() {

	for (int row = 0; row < numRows; row++) {
		pinMode(rowPins[row], INPUT);
		digitalWrite(rowPins[row], HIGH);
	}
	for (int column = 0; column < numCols; column++) {
		pinMode(colPins[column], OUTPUT);
		digitalWrite(colPins[column], HIGH);
	}
}

void loop() {
	char done, Buf[12];
	int x,y,z;

	state = 1;	// Pre-game cube LED flashing
	done = ' ';

	myMatrix.clear();

	init_cube();
	MsTimer2::set(period, disp_LED_cube);
	MsTimer2::start();

	while (getKey() == 0) ;			// Press 1 to go first
	state = 0;		// Normal display of cube LED
	if (key != '1') {
		human_symbol='O';
		comp_symbol='X';
		get_computer_move();
	}

	do {
		state = 0;
		get_player_move();
		done=check_cube(cube0);
		if (done != ' ') break;
		get_computer_move();
		done=check_cube(cube0);
	} while (done==' ');

	if (done==human_symbol) ;			// Player has WON!
	else ;						// Compute has WON!

}

As always, if you have any questions, need further assistance or require additional pictures or diagrams, please let me know in the comments and I’ll do my best to address them.

Up Next: Figuring Out How To Select Player Moves (determining pin combinations for particular keys on an unknown keypad, and hard coding)

]]>
0
Jeff <![CDATA[3D TicTacToe – 2. Developing The Protoboard To Control The Matrix]]> http://www.whatmatters2me.com/?p=274 2011-10-20T22:29:19Z 2011-06-03T17:11:34Z The continuation of my 3D TicTacToe LED Matrix project.  See Introduction and Part 1.

The 4x4x4 LED matrix is controlled by a Maxim MAX7219 display driver IC.  Using the MAX7219 reduces the number of Arduino pins needed to control and power the matrix from 16 down to 5.  The 3D TicTacToe sketch uses the Arduino Matrix library by Nicholas Zimbetti and simple coding from his hello_matrix sketch.  This library should already be included with all up-to-date arduino installations.  The design of the protoboard is based on Michael Margolis’s Arduino Cookbook project 7.13 “Controlling an Array of LEDs by Using MAX72xx Shift Registers” and the Maxim MAX7219 datasheet.

Shopping List:

  • printed circuit board
  • MAX7219 display driver IC
  • 33 kΩ resistor
  • 0.1 μF capacitor
  • 10 μF capacitor
  • 24 AWG solid wire in various colors (I used 22 AWG and the resulting LED matrix / protoboard connection is extremely stiff)
  • box with lid (I used a $2 white cigar box from Michaels)

 

Connections:

To power the matrix connect the Arduino’s 5V pin directly to the MAX7219 V+ (pin 19) and through a ~30 kΩ resistor to ISET (pin 18).  I used a 33 kΩ resistor on mine.  Connect the Arduino’s GND to the MAX7219 GND pins (4 and 9).  Insert 0.1 and 10 μF capacitors between the 5V and GND to prevent noise spikes.

To control the matrix connect Arduino pins 4, 5, and 6 to the MAX7219 pins 12 (LOAD), 13 (CLK), and 1 (DIN), respectively.

The anode wires of the LED matrix will be connected to the MAX7219 SEG pins A-G, plus SEG DP, while the cathode wires will be connected to DIG pins 0-7.  Use 24 AWG wires that are long enough to reach where they need to go on the LED matrix.  Mine were a bit long at ~20 cm, but the extra should fit in the box you use or can be trimmed later.  I suggest using one color for the anode and a different color for cathode.  Solder 8 wires of one color to SEG pins A-DP and the 8 other wires to DIG pins 0-7.

If you want a nice looking 3D matrix display you’ll want a box to contain the Arduino, protoboard and wires, and a top for the LED matrix to sit on.  I used the same printout as used to drill the board holes to determine where to drill small holes in the cigar box top for the wires.  You’ll only need the seven side holes, but make the corner hole about 1.5x as big as the others.  Once the wires are soldered to the protoboard, thread them through the appropriate holes of the cigar box top.  Connect the wires to the appropriate lines on the LED matrix.  You’ll probably want to attach the wires temporarily to the LED matrix for testing before permanently soldering them.

Below are shots of the protoboard.  I know they’re difficult to make out with the gaggle of wires everywhere, and so I’ve taken shots from various directions.

Shot A: max7219-dsc_0584pps shows 33 kΩ resistor from pin 18 to 5V, and pin 19 straight to 5V.

Shot B: max7219-dsc_0587pps difficult to see pins 4 (buried) and 9 to GND

Shot C: max7219-dsc_0588pps MAX7219 pins 12 (LOAD) and 13 (CLK)

If you have any questions, need further assistance or require additional pictures or diagrams, please let me know in the comments and I’ll do my best to address them.

Up Next: Programming the Arduino To Play 3D TicTacToe

 

]]>
0
Jeff <![CDATA[3D TicTacToe – 1. Creating the 4x4x4 LED matrix]]> http://www.whatmatters2me.com/?p=269 2011-06-03T19:44:53Z 2011-05-25T17:11:16Z 1. Creating the 4x4x4 LED matrix

The continuation of my 3D TicTacToe LED Matrix project.  See Introduction.

The matrix is based on an 8×8 LED matrix design, where it’s been divided into fourths and wired to work on four levels. Here’s a diagram of an 8×8 matrix: 8x8 LED matrix The structure of the 3D matrix is entirely composed of the anode LED leads soldered together, the cathode LED leads soldered together and a few extra wires to make connections. Here’s a picture of the semi-final product (note: from a previous build):

led-matrix-build-dsc_0572pp

 

Four 4×4 LED matrices need to be created. Practically any LEDs will do, as I initially just used the cheapest ones I could find, but the current build is using 350 MCD yellow ones (see shopping list). Note also that different LEDs have different lead lengths. You want lengths that are 25 mm or more. I tried using shorter length LEDs, but the resulting matrices were too small to work with. My 4×4 matrices are modeled after the above 8×8 matrix. There’s an Instructables.com project by Electronics Man where he suggests drilling holes in a board to place the LEDs properly aligned at the correct distances from each other. I used his example to make the board and securely place the 16 LEDs for soldering. However, I believe his overall LED matrix design is different from mine, and will likely not work with the circuitry and software used in my project. Here’s an OpenDocument drawing template of the 21 mm-spaced LED matrix holes that I printed out and taped on the board. I then used a punch to make a small dent at each of the sixteen points before drilling. I used a hand-powered drill starting with the smallest size drill bit and worked up in bit size until the LEDs fit snugly into the holes.

Fit the LEDs into the holes with the cathode (shorter) leads above the anode leads at about 45 degrees.

led-matrix-build-dsc_0565pp

 

Bend the cathode leads down right at the base and push them so they’re all pointing to the top of the board.

led-matrix-build-dsc_0566pp

 

There should be plenty of space between the anode and cathode leads. Now with small needle nose pliers bend the anode (longer) leads about 3 mm above the LED base – perpendicular to the cathode leads. They should be well above the cathodes.

led-matrix-build-dsc_0573pp led-matrix-build-dsc_0574pp

 

Adjust all of the leads so they overlap appropriately and solder them together. Gently prod out the LEDs from the other side of the board, and you should have a very stable 4×4 matrix. Make three more identical matrices, except two should have the cathodes pointing down instead of up.  The two pointing down are levels 1 & 3, the the two pointing up are levels 2 & 4.

The four matrices now need to be soldered together. I differentiate between the LEADS (the extra lead sticking out that’s not soldered to the next lead) and the LINE of soldered-together leads. It’s not straight forward so consult the following:

Top Level 4 Anode Leads:  attach to anode line of Level 3.  Bend the leads down so they can be soldered to the level below.  Note that you can attach additional anode connections anywhere along the Level 4 anode line to the corresponding anode line below it on Level 3 – this will add support to the structure (see picture below).

Top Level 4 Cathode Leads: attach to cathode line of Level 1 (bottom).  Do this last, after all of the other levels have been soldered together.  You’ll need to use wire to make the connections long enough.  I recommend 24 AWG.

Level 3 Anode Leads: leave alone for now.

Level 3 Cathode Leads: attach to cathode line of Level 2.  As before, bend the leads down so they can be soldered to the level below, and attach addititonal cathode connections to support structure.

Level 2 Anode Leads: leave alone for now.

Level 2 Cathode Leads: leave alone for now.

Bottom Level 1 Anode Leads: bend leads up to solder to Level 2 line.  Attach additional anode connections to support structure.

Bottom Level 1 Cathode Leads: leave alone for now.

I’ll admit soldering the levels together is much more of an art than a science.  It’s warping a single 8×8 matrix into 4 levels and you need to be careful to solder the correct leads and lines. I still haven’t developed a technique that I’m really happy with.  This picture shows the final soldered together structure including a support attachment:  led-matrix-build-dsc_0578pp

Once all of the levels have been soldered together, and a few structural connections added for stability, the wires that connect the matrix to the controlling prototype board can be added.  I used 8 green for the cathodes and 8 red for the anodes.  Make the wires long enough to reach the board – I suggest starting with 25 cm to be sure it’s long enough.  Use somewhere around 24 AWG solid (not stranded) to make it somewhat pliable.  Solder these wires to the leads above that were left alone.  Finally, clip all extraneous leads and wires off the matrix.  Finished matrix:  led-matrix-build-dsc_0579pp

Up Next: Developing The Protoboard To Control The Matrix

 

]]>
0
Jeff <![CDATA[3D TicTacToe on a 4x4x4 LED Matrix]]> http://www.whatmatters2me.com/?p=229 2011-06-03T19:47:08Z 2011-05-10T23:46:05Z Here’s a project that I’ve been working on for over a month. It’s an Arduino controlled and AI-playing version of TicTicToe, where the “board” is a 4x4x4 matrix using 64 LEDs. Now, LED matrices have been done before with sizes anywhere from 3^3 to 8^3 (…). And 3D LED matrix versions of TicTacToe have also been done (…). But, I believe this is the first 4x4x4 LED matrix of TicTacToe that actually plays the game against you. And unless you’re very, very careful, it will beat you.

The project has several parts to it:

  1. Creating the 4x4x4 LED matrix
  2. Developing the protoboard to control the matrix
  3. Programming the Arduino to play 4x4x4 TicTacToe
  4. Figuring out how to select player moves

 

In the next several posts I’ll go over each aspect of the project, so that if you’re interested you can create your own (and hopefully modify to make even better!). The source code for the game-playing AI is included as well. Note that I’m new to soldering, wiring and electronics in general, so it took me dozens of hours to design and put together a functioning semi-final product. There were frustrating hours where the LED matrix was unstable and some LEDs weren’t lighting up; I destroyed two $8 MAX7219 ICs I’m guessing from either static electricity or short-circuiting; I initially used cheap 26 AWG stranded wire where the thins strands short-circuited the matrix; etc. Also, I made extensive use of Michael Margolis’s Arduino Cookbook, especially for controlling the matrix and putting together the electronics. It’s a great book and I highly recommend it.

Shopping List

(I used mostly Jameco because it’s local, but of course there are dozens of other great online stores to get these from):

  • Arduino Uno, Jameco #2121105
  • LEDs x64, Jameco #333622 – you’ll probably want 70+ for testing, mistakes; the leads on these are 1.1 cm, the longer the better
  • MAX7219 IC, Jameco #312160
  • keypad, Jameco #2082206
  • printed circuit board, Radioshack #276-150
  • 330k ohm resistor
  • 10 μF capacitor
  • 0.1 μF capacitor
  • 24 AWG solid wire in various colors
  • box with lid (I used a $2 white cigar box from Michaels)

Check out the upcoming posts for more details…

Video of it doing its thing:

]]>
0
Jeff <![CDATA[What Matters 2 Me.com Up & Running]]> http://www.whatmatters2me.com/?p=318 2011-05-10T23:44:46Z 2011-05-10T23:43:13Z What Matters 2 Me.com was down for several months due to a complete server overhaul, and technical issues with virtual hostnames.  The good news is that it’s back.  Also, I’ve recently acquired an Arduino Uno and become completely addicted to it and electronics in general.  In fact, the main reason this site is back up and running was the desire to include Arduino and other “Maker” projects on it.  My plan is to do lots of Arduino posts of various projects of mine and others, Arduino news and Arduino-related things.  The larger community of Makers is also of great interest.  In fact the 2011 Bay Area Makers Faire is May 21 & 22, and I am very excited to be visiting it for the first time.  Expect more posts on it!

 

]]>
0
Jeff <![CDATA[XBMC on the Acer Aspire Revo 1600 HOWTO]]> http://www.whatmatters2me.com/?p=132 2011-05-11T04:37:41Z 2010-01-23T00:55:11Z So you’ve heard about running XBMC on the Acer Aspire Revo 1600 and you need more info. You’ve come to the right place! This is an attempt to give a HOWTO on taking a new Revo, installing Ubuntu Karmic 9.10 and XBMC, and tweaking it so that the Nvidia Ion graphics chip does the decoding (VDPAU) for smooth playing of even HD videos. Also, it includes setting up audio over HDMI.

As you’ve probably heard, the Revo gives you more than your usual money’s worth by including an onboard Nvidia Ion graphics chip. The Nvidia engineers have very kindly released drivers which allow the graphics chip to do the video decoding rather than bogging down the rather slow Intel Atom 230 CPU with the chore. This results in only ~20% CPU utilization with near flawless playback of HD videos, instead of 100% CPU overload and very choppy video. A $200 computer that can do this is a very good deal.

I recommend a full install of Ubuntu Karmic 9.10. Other sites have instructions for installing a “minimal” gnome setup. I think this is silly. It strips the system of a host of useful applications, while providing non-discernible “improvements”. With the normal full installation of Ubuntu not only will you be able to play video and audio (movies and music) files, but should you choose, also browse the Internet, do email, perform normal office tasks (word processing, spreadsheets, presentations) and more. Provided, the Acer Revo is not a powerhouse and I wouldn’t recommend it for editing photos or videos.

Note that I am NOT an expert on any of this. I’ve been an avid Linux user since Linus announced it back in 1991, but XBMC, the Revo, and the VDPAU driver are all relatively new to me. I will attempt to provide appropriate links for more information on all of these. In fact, all of the information here has been gleamed from numerous websites and postings from other generous XBMC users. I’ll try and note that as possible. Also, I’m assuming at least some competency with a Linux system. If there are an questions feel free to ask them in the comments section.  Read this entire article, including the notes at the end, before attempting.

Reference System and Hardware: this is a list of my setup and all of the equipment I used that was necessary to make it work. Of course, other equipment will likely work, but this is what I used.

  • Acer Aspire Revo 1600, model #AR1600-U910H
  • Patriot 2 GB PC2-8400 800 MHz CL6 Memory, model #PSD22G8002S (swapped with installed 1 GB module)
  • Samsung 40″ LCD TV, model #LN40A550 (1920×1080, HDMI)
  • 2 meter HDMI cable (don’t pay more than $10 or you’re being ripped off)
  • 2 GB USB flashdrive
  • MCE remote, model #GP-IR02BK

INSTALLING UBUNTU KARMIC 9.10 ON THE ACER ASPIRE REVO

The Acer Revo doesn’t have an optical drive, so the OS will need to be installed via an USB optical drive or a USB flashdrive. I recommend a USB flashdrive installation and that’s what I’ll describe here.

  1. Download the Ubuntu Karmic 9.10 iso image from ubuntu.com.
  2. Download Unetbootin from unetbootin.sourceforge.net and install it.
  3. Insert a FAT formatted >2 GB flash drive into the computer. Note that FAT is already the default filesystem for most flash drives. Run Unetbootin, select Diskimage and tell it where to find the iso image file you downloaded. Next to “Type” USB Drive should be automatically chosen.  Next to “Drive” select either the letter of the USB flash drive (for Windows) or the correct device (i.e. /dev/sdb1 for Linux).  Hit “OK” to set up the flash drive as a Ubuntu installation drive.  When it’s finished hit “Exit”, remove the USB flashdrive and insert it into your smokin’ Acer Aspire Revo!The model of Revo that I have had Windows XP pre-installed. I decided to keep a 30 GB drive partition with the XP still on it, so I’ll explain how I did that.  I’ll assume you’ve already hooked the Revo up with the mouse, keyboard and an appropriate display. Hopefully, the display is connected via an HDMI cable.  Also it’s advantageous to have it attached to your router either through ethernet or USB wireless – during installation it will set the computer clock and download additional content.
  4. Turn on the Revo and hit DELETE when the display suggests it. Under “Advanced BIOS Features” select “Hard Disk Drive Priority” and change the USB flashdrive to the top position. Hit F10 to save and exit. Select “Default” from the Unetbootin menu that comes up, and the computer should boot into Ubuntu Live ready for installation.
  5. Double click on the Installation icon.  Just follow the simple on screen instructions until you get to the partition page.  At this point, if you want to keep the original M$ Windows installation, select the bottom option, click on the Windows bar and slide it to indicate how large you want to keep that partition.  Again, I selected 30 GB which would allow me to install and occasionally play M$ Windows games. (I highly recommend Torchlight – a Diablo clone – it’s gorgeous, fun, works well on the Revo, and is just $20, downloadable from Direct2Drive.) Otherwise, if you just want a simple Ubuntu system select the top option. Continue following the simple on screen instructions. On the “Who are you?” page I selected “Log in automatically”. Installation should take less than 30 minutes. Reboot minus the flashdrive.

Congratulations! You’ve got a full Ubuntu Karmic operating system.

INSTALLING THE LATEST NVIDIA VIDEO DRIVERS

  1. First update standard Ubuntu. Open up a terminal window and bring Ubuntu completely up to date with the following commands:

    sudo apt-get update
    sudo apt-get upgrade [Note: hold off on reboot]
    sudo apt-get dist-upgrade [Note: hold off on reboot]

    It may take a while to download and install all the latest updates. Note that throughout the rest of this installation the system may pop up boxes asking you to reboot, update or install a graphics driver. You can safely close and ignore them.

  2. Now it’s time to grab the latest drivers for the Nvidia Ion graphics chip. From the terminal window run:

    sudo add-apt-repository ppa:nvidia-vdpau/ppa
    sudo apt-get update [Note: DO NOT upgrade]
    sudo apt-get install nvidia-glx-195 nvidia-195-modaliases

  3. Reboot the machine.
  4. The drivers aren’t installed yet. From the upper task bar select System -> Administration -> Hardware Drivers. Select “NVIDIA accelerated graphics driver (version 195) [Recommended]”.  Click on the “Activate” button, and then the “Close” button. Click to See Larger Image
  5. Reboot again.
  6. Finally, again from the upper task bar select System -> Administration -> Synaptic Package Manager. Search for “libvdpau1” and make sure it’s installed.
  7. Click to See Larger Image

SELECT THE HDMI AUDIO DRIVER

By default, Ubuntu uses the standard audio ports for output. In order to get sound from your HDMI-attached TV do the following:

Right-click on the Sound icon in the upper panel. Select “Sound Preferences” and then the Hardware tab. From the Profile menu select “Digital Stereo (HDMI) Output”. Click to See Larger Image Click the “Close” button. That’s it – you’ve got TV sound.

INSTALL XBMC

Installation couldn’t be much easier. From the terminal run:

sudo add-apt-repository ppa:team-xbmc
sudo apt-get update
sudo apt-get install xbmc
sudo apt-get update

You’ll now find XBMC from Applications -> Sound & Video -> XBMC Media Center

Everything should be working. To verify that your Nvidia chip is doing the decoding, hit “o” while running a video. The second line from the top should include something similar to “dc:ff-h264_vdpau”. Click to See Larger Image

For more help on all things XBMC, visit their official website at http://xbmc.org , the Wiki at http://xbmc.org/wiki/?title=XBMC_Online_Manual, and the XBMC forums at http://xbmc.org/forum

BTW, I highly recommend the Aeon theme. It really shines on HDTV.

ADDITIONAL INFORMATION

I also purchased an MCE remote (Mediagate model #GP-IR02BK). This is an infrared remote control designed for controlling a media center. It makes the whole Revo/XBMC feel like an actual appliance since you won’t be using the keyboard and mouse much afterwards. I believe practically any MCE remote will work. Along with the remote you also get an IR receiver that plugs into a USB port. To get mine working I had to install the drivers with “sudo apt-get install lirc”. After that the remote magically controlled XBMC.

Like I noted in the parts section, I swapped out the computer’s initial 1 GB memory card for a 2 GB memory card.  I also increased the shared video memory from 256 KB to 512 KB in BIOS.  There are some reports that 2 GB and 512 KB shared is necessary to get properly working VPDAU.  I have not verified this as I installed the 2 GB before installing Ubuntu.  I’d like to hear if anyone has problems.

[NOTE: Added on February 2, 2010]

I just realized that since I swapped out the original 1 GB memory module for a new 2 GB module, that the 1 GB module is just sitting around on my desk unused.  So, if anyone’s interested in using it to upgrade their system to 2 GB, I’ll give it to the first person to request it in the comments.

]]>
5
Jeff <![CDATA[Developing RAW Photos under Ubuntu]]> http://www.whatmatters2me.com/?p=130 2011-05-10T17:43:13Z 2009-10-31T21:22:45Z I just spent way too much time trying to figure out how to process RAW photos under Ubuntu. There’s tons of discussions and websites with very little concrete information. After much digging it comes down to just this:

sudo apt-get install gimp-ufraw
(typed in a terminal window)

Gimp will now see and open RAW files. The RAW files will first open up into a UFRaw plugin dialog. In this dialog you can adjust color balance, exposure, curves, etc. Selecting ‘OK’ then imports the image into the normal Gimp editing window.

I’m using a Nikon D70 DSLR camera with NEF RAW files and everything worked flawlessly under the just released Karmic Koala Ubuntu 9.10.

]]>
0
Jeff <![CDATA[Kubuntu / Windows XP Dual Boot on Asus Eee PC 1000]]> http://www.whatmatters2me.com/?p=51 2009-02-16T17:51:29Z 2008-12-09T05:43:57Z This is a HOWTO on installing Kubuntu 8.10 and Windows XP as dual booting systems on an Asus Eee PC 1000.  As you probably know the Eee 1000 is advertised as a 40 GB SSD.  It is not.  It has both a (fast write) 8 GB SSD and a (slow write) 32 GB SSD.  I wanted my Linux Kubuntu on the fast drive, and M$ Windows as well as the /home directory and swap partition of Linux on the slower drive.  For my particular needs I believe what I’ve stitched together is the most simple and elegant.

This HOWTO assumes you have a basic understanding of Linux/Unix, and commands such as fdisk and mount.

The process can be divided up into 5 main parts:

1) Using a LiveCD Linux disk to set up all the partitions.

2) Installing Kubuntu

3) Copying the GRUB booter to a memory card / flashdrive

4) Installing M$ Windows XP

5) Copying linux.bin to C:\ and editing C:\boot.ini

and that should give a dual booting system.

OK, now for all of the details:

1) Use a LiveCD Linux disk to set up all the partitions.

Since the Eee 1000 does not have an optic drive, I use UNetbootin to convert the distribution’s LiveCD image file to a bootable USB flashdrive.  Get UNetbootin here [1] and the latest Kubuntu LiveCD image here [2].

Using your Kubuntu LiveCD flashdrive boot up Linux on the Eee 1000.  Using fdisk set up the partitions to your liking.  There’s an infinite number of ways of setting up the two systems, but like I said, I wanted Linux on the 8 GB drive and XP on the second.  If you put XP on the second drive then you need at least 1 cylinder on the first drive to put the booter.  Here’s the partitions I created:

Partition   Cylinders   Type (Hex code)     Blocks
sda1        1-980       Linux (83)         7871818
sda2        981-981     NTFS (7)              8032
sdb1        1-2438      NTFS (7)          19583203
sdb2        2439-3875   Linux (83)        11542702
sdb3        3876-3924   Linux swap (82)     393592

fdisk complained something about the sda drive when I exited, but it still worked.  This should work out to about an 8 GB Linux system with an 11.8 GB /home and 403 MB swap, and a 20.1 GB NTFS partition for Windows XP.

2) Install Kubuntu

Now hit the Install icon to start the installation!

On Step 4 use Manual disk setup, select the appropriate partitions, and set their mount points and file systems:

sda1:  /      (Ext3 journaling file system)
sdb2:  /home  (Ext3)
sdb3:  swap

On Step 6 select “Advanced…”, and set “Install boot loader” and specify the Device as /dev/sda1.  Hit the OK button to return to Step 6 and then the Install button.

When it’s done installing reboot into your fresh Kubuntu system!  Visit this excellent HOWTO [3] to install an Eee 1000-specific kernel to get all the drivers functioning (wireless, etc) – this step can be done after the dual boot installation is finished.  NOTE: wireless, bluetooth and a few other things are disabled by default in the BIOS.  Enable them in BIOS to have them recognized by the kernel.

3) Copy the GRUB booter to a memory card / flashdrive

Initially, after you’ve installed M$ Windows XP you can no longer boot into Linux.  This next step allows you to subsequently tell M$ Windows how to dual boot.

Stick a flashdrive or SD card preformatted to VFAT into the machine and mount it.  I just used the Kubuntu LiveCD flashdrive that I installed the system with.  Then run the following command:

dd if=/dev/sda1 of=/(usb-drive)/linux.bin bs=512 count=1

where, of course, “usb-drive” is where you’ve mounted the drive.

4) Install M$ Windows XP

I kludged an internal CD drive into a USB drive using a cheap SATA to USB converter – not pretty, but it worked.  Do whatever is necessary to get M$ Windows XP installed.  Here’s what’s important:

M$ Windows won’t boot from the second (32 GB) drive without an NTFS partition on the first (8 GB) drive.  During installation, tell it to use the 8 MB NTFS for the MBR and the 20 GB NTFS for the system.

Unfortunately, you’ll end up with a miniscule C:\ drive with several hidden files and then the main installation on D:\.  Nothing major, but it’s not a single drive and you’ll have to get used to installing everything to the D:\ drive.

5) Copy linux.bin to C:\ and edit C:\boot.ini

All of the information here I pulled from this great article [4] by Ed Park.

First, copy the linux.bin file from the USB drive to C:\.

Second, run notepad and edit C:\boot.ini.  Note that C:\boot.ini is a hidden system file, so it probably won’t show up in Windows Explorer.  To edit the file, try: Start->Run and enter: notepad C:\boot.ini.  Add the following line at the end: c:\linux.bin=”Linux”

To make C:\boot.ini writable, you can either :

  • Use Explorer:
    • Go to Tools->Folder Options->View and select Show hidden files and folders and deselect Hide protected operating system files (Recommended).
    • Right-click on the file, view the Properties and uncheck Read-only.  You can now edit the file.
    • After editing the file, restore the settings to their original state.
  • Use the command-line:
    • Make the file writable: attrib -R -S -H C:\boot.ini.
    • After you’ve finished editing the file, put the settings back: attrib +R +S +H C:\boot.ini

For reference, here is a copy of Ed’s boot.ini file.

Finally, reboot again.  You should be able to pick either Linux or M$ Windows.  Selecting Linux will start GRUB.

I hope this HOWTO is as useful to others as the following websites were to me.  I’ll update it as necessary to make it more useful.  Ask any questions in the comments below.  I’m not an expert, but I’ll try to answer as possible.

References:

[1] UNetbootin – http://unetbootin.sourceforge.net

[2] Kubuntu LiveCD image – http://www.kubuntu.org/getkubuntu/download

[3] http://www.chrishorsnell.co.uk/2008/09/asus-eee-1000-perfect-install-with-ubuntu/

[4] http://www.geocities.com/epark/linux/grub-w2k-HOWTO.html

]]>
4
Jeff <![CDATA[NaNoWriMo.org]]> http://www.whatmatters2me.com/?p=19 2008-10-08T05:21:45Z 2008-10-05T03:36:06Z NaNoWriMo is short for National Novel Writing Month. According to its website “National Novel Writing Month is a fun, seat-of-your-pants approach to novel writing. Participants begin writing November 1. The goal is to write a 175-page (50,000-word) novel by midnight, November 30.” and “Because of the limited writing window, the ONLY thing that matters in NaNoWriMo is output. It’s all about quantity, not quality. The kamikaze approach forces you to lower your expectations, take risks, and write on the fly.”

It sounds like a heck of a lot of fun to me, and since I’ve always wanted to write my own Great American Novel, I’ve signed up! I plan on blogging about my experience here, and even putting up my nightly additions to the novel (possibly in a separate self-contained website). Stay tuned as the writing commences at midnight November 1st..

Ever wanted to write your own novel? Get more information at NaNoWriMo.org.

nanowrimo_participant_icon_small2.gif ]]>
0
Jeff <![CDATA[Now with iPod Touch Goodness]]> http://www.whatmatters2me.com/?p=15 2008-10-05T05:40:00Z 2008-07-27T00:38:42Z If you’re viewing WhatMatters2Me with an iPod touch/iPhone you’ll now see a slick pared-down front page. I think it looks quite nice. Of course, there’s a link at the bottom of the page if you want to view the original unadulterated version.

]]>
0