El joc és una adaptació del joc clàssic Reversi, també anomenat Othello. En aquest cas les fitxes són de colors blau i vermell. L'ús dels polsadors és el següent:
| Polsador | Finalitat |
| 0 | Reinici |
| 1 | Desplaçament amunt |
| 2 | Desplaçament avall |
| 3 | Desplaçament a l'esquerra |
| 4 | Desplaçament a la dreta |
| 5 | Selecció |

El programa és el següent:
// Joc "Reversi" o "Othello". En el codi parlarem d'Othello. // Configuració inicial del PIC. Igual per a tots els projectes. // Carrega el fitxer de funcions necessaries per al compilador #pragma config FOSC = INTRCIO, WDTE = OFF, PWRTE = OFF, MCLRE = OFF, CP = OFF #pragma config CPD = OFF, BOREN = OFF, IESO = OFF, FCMEN = OFF #include <xc.h> #define _XTAL_FREQ 4000000 // Constants del joc. // Dono noms als números per entendre el codi. #define FITXA_BUIDA 0 #define FITXA_BLAVA 1 #define FITXA_VERMELLA 2 #define CURSOR 3
// Variables globals. // "Char" crea variables per guardar informació de la matriu LED. char Port; char Compta; // Comptador de BITS enviats. char Sortida[6]; // Valors a enviar per les dades de colors. char Sorti[6]; // Valors a enviar per la interrupció de colors. char Actiu; // Variable que diu quin color està actiu. // Variables del joc Othello. char tauler[8][8]; // Genera la graella 8x8. char cursor_x, cursor_y; // On situem el cursor, coordenada X i Y. char jugador_actual; // Variable que marca de qui és el torn del jugador. char comptador_moviment; // Variable que compta per fer el parpelleig. char mode_test; // Allterna entre fitxes vermelles i blaves char mirar; char moviment_invalid; // Controla animació d'error char joc_acabat; // Variable per detectar si el joc ha acabat char btn0_anterior = 0; // Per al control de BTN0 - detecta canvi d'estat
// Declaro funcions seguint ST. void Envia3max(char Valor[]); // Envia colors a la matriu LED. void Envia_max(void); void Ini3max(void); // Configura la matriu en la situació inicial. void Apaga(void); char Polsador1(void); // Funció analògica de botons que funciona. char detectaBTN0(void); // Detecta el flanc descendent de BTN0 (RA3) void inicialitzaOthello(void); // Posa les 4 fitxes inicials del joc. void actualitzaTauler(void); // Mostra el tauler a la matriu LED. void llegeixBotons(void); // Mira si s'han premut botons void mouCursor(signed char dx, signed char dy); // Mou el cursor verd pel tauler. void animacioError(void); // Mostra l'error visual char esMovimentValid(unsigned char x, unsigned char y); // Valida moviments // Busca flanc en una direcció char cercaiFlanqueja(unsigned char x, unsigned char y, signed char dx, signed char dy, char jugador); void capturaFitxes(unsigned char x, unsigned char y, char jugador); // Captura fitxes en totes les direccions char taulerComplet(void); // Comprova si el tauler està completament ple void comptaFitxes(unsigned char *blaves, unsigned char *vermelles); // Compta fitxes de cada color void mostraGuanyador(char guanyador); // Mostra el guanyador a la pantalla
// Funció principal
void main(void) {
OPTION_REG = 0b10000101;
TRISC = 0;
TRISB = 0;
TRISA = 0xFF;
ANSEL = 0b00000101;
ANSELH = 0;
PORTC = 0;
PORTB = 0;
ADCON1 = 0b00010000;
ADCON0 = 0b00001001;
// Inicialització matriu amb RGB
Ini3max();
Actiu = 1;
TMR0 = 100;
INTCON = 0b10100000;
// Activa interrupcions
// Inicialització variables del joc
cursor_x = 2;
cursor_y = 2;
comptador_moviment = 0;
btn0_anterior = 1; // Inicialitza BTN0 en estat repos (sense pressió)
// Test inicial: Parpelleig per confirmar que funciona
for (unsigned char i = 0; i < 2; i++) {
// Encén tot vermell
for (unsigned char filera = 1; filera <= 8; filera++) {
Sortida[1] = filera;
Sortida[3] = filera; Sortida[5] = filera;
Sortida[0] = 0xFF; Sortida[2] = 0x00; Sortida[4] = 0x00;
Envia3max(Sortida);
}
__delay_ms(300);
Apaga();
__delay_ms(300);
}
// Inicialització del joc Othello
inicialitzaOthello();
// Bucle principal del joc
while (1) {
comptador_moviment++;
// Incrementa per efectes de parpelleig
// Evita desbordament del comptador
if (comptador_moviment >= 128) {
comptador_moviment = 0;
// Reinicia el comptador
}
llegeixBotons();
// Processa entrada botons analògics i BTN0 (RA3)
actualitzaTauler();
// Actualitza visualització
__delay_ms(50);
}
}
// Interrupció.
void __interrupt() temporit(void) {
if (INTCONbits.T0IF) {
TMR0 = 100;
INTCONbits.T0IF = 0;
if (Actiu != 0) {
Actiu--;
if (Actiu == 0) {
Actiu = 3;
}
}
Sorti[0] = 0x00;
Sorti[2] = 0x00; Sorti[4] = 0x00;
if (Actiu == 1) Sorti[0] = 0x01; // Vermell
if (Actiu == 2) Sorti[2] = 0x01; // Verd
if (Actiu == 3) Sorti[4] = 0x01; // Blau
Sorti[1] = 0x0C;
Sorti[3] = 0x0C; Sorti[5] = 0x0C;
Envia_max();
}
}
// Funcions bàsiques.
// 1.Funció Envia3max.
void Envia3max(char Valor[]) {
INTCONbits.T0IE = 0; // Desactiva interrupcions durant enviament
char Port = 0; // Variable on guardem l'estat del port B.
char Temp; // Variable temporal
for (signed char j = 5; j >= 0; j--) { // Enviem 6 bits
for (signed char k = 1; k < 9; k++) { // De 8 bits
Temp = Valor[j] & 0b10000000; // Agafem només el primer bit
// Si el bit és 0, desactiva el pin de dades.
// Si el bit és 1, activa el pin de dades.
// Mirar si l'interruptor està encès o apagat.
if (Temp == 0) {
Port = Port & 0b11101111;
} else {
Port = Port |
0b00010000;
}
Valor[j] = (char)(Valor[j] << 1);
// Desplaça els bits per situar el següent.
PORTB = Port;
Port = Port | 0b00100000;
PORTB = Port;
Port = Port & 0b11011111;
PORTB = Port;
}
}
Port = Port |
0b01000000;
PORTB = Port;
INTCONbits.T0IE = 1; // Reactiva interrupcions
}
void Envia_max(void) {
asm("banksel _Port");
asm("bcf (_Port&7fh),5");
asm("bcf (_Port&7fh),6");
asm("movf (_Port&7fh),w");
asm("banksel PORTB");
asm("movwf PORTB");
asm("banksel _Compta");
asm("movlw 48");
asm("movwf (_Compta&7fh)");
asm("Bucle:");
asm("banksel _Port");
asm("bcf (_Port&7fh),4");
asm("banksel _Sorti");
asm("rlf (_Sorti&7fh),f");
asm("rlf ((_Sorti+1)&7fh),f");
asm("rlf ((_Sorti+2)&7fh),f");
asm("rlf ((_Sorti+3)&7fh),f");
asm("rlf ((_Sorti+4)&7fh),f");
asm("rlf ((_Sorti+5)&7fh),f");
asm("banksel _Port");
asm("btfsc STATUS,0");
asm("bsf (_Port&7fh),4");
asm("movf (_Port&7fh),w");
asm("banksel PORTB");
asm("movwf PORTB");
asm("banksel _Port");
asm("bsf (_Port&7fh),5");
asm("movf (_Port&7fh),w");
asm("banksel PORTB");
asm("movwf PORTB");
asm("banksel _Port");
asm("bcf (_Port&7fh),5");
asm("movf (_Port&7fh),w");
asm("banksel PORTB");
asm("movwf PORTB");
asm("banksel _Compta");
asm("decfsz (_Compta&7fh),f");
asm("goto (Bucle&7ffh)");
asm("banksel _Port");
asm("bsf (_Port&7fh),6");
asm("movf (_Port&7fh),w");
asm("banksel PORTB");
asm("movwf PORTB");
}
void Ini3max(void) {
char Bytes[6];
// Shutdown mode
Bytes[0] = 0x00;
Bytes[1] = 0x0C;
Bytes[2] = 0x00; Bytes[3] = 0x0C;
Bytes[4] = 0x00; Bytes[5] = 0x0C;
Envia3max(Bytes);
// Decode mode
Bytes[0] = 0x00; Bytes[1] = 0x09;
Bytes[2] = 0x00; Bytes[3] = 0x09;
Bytes[4] = 0x00; Bytes[5] = 0x09;
Envia3max(Bytes);
// Scan limit
Bytes[0] = 0x07; Bytes[1] = 0x0B;
Bytes[2] = 0x07; Bytes[3] = 0x0B;
Bytes[4] = 0x07; Bytes[5] = 0x0B;
Envia3max(Bytes);
}
void Apaga(void) {
char Bytes[6];
for (unsigned char j = 1; j <= 8; j++) {
Bytes[1] = j;
Bytes[3] = j; Bytes[5] = j;
Bytes[0] = 0x00; Bytes[2] = 0x00; Bytes[4] = 0x00;
Envia3max(Bytes);
}
}
// 6. Funció POLSADOR.
// Llegeix botons analògics:
char Polsador1(void) {
char Pols = 0;
ADCON0bits.GO = 1;
while (ADCON0bits.GO == 1)
;
// Mirarà quin voltatge ha llegit i decidirà el botó que estem utilitzant.
if (ADRESH < 220 && ADRESH > 200) {
Pols = 1;
}
if (ADRESH < 194 && ADRESH > 174) {
Pols = 2;
}
if (ADRESH < 163 && ADRESH > 143) {
Pols = 3;
}
if (ADRESH < 90 && ADRESH > 70) {
Pols = 4;
}
if (ADRESH < 55 && ADRESH > 35) {
Pols = 5;
}
return Pols; // Retorna el botó premut.
}
char detectaBTN0(void) {
// Llegeix només el bit 3 de PORTA (RA3)
char btn0_actual = (PORTA & 0b00001000) >> 3;
// Detecta flanc descendent: transició de 1 a 0 (quan es prem el botó)
// Compara l'estat anterior amb l'actual
if (btn0_anterior == 1 && btn0_actual == 0) {
// Flanc descendent detectat - botó premut
btn0_anterior = btn0_actual;
return 1; // BTN0 ha estat premut
}
// Actualitza l'estat anterior per a la propera consulta
btn0_anterior = btn0_actual;
return 0; // BTN0 no ha estat premut
}
// Funció de validació.
char esMovimentValid(unsigned char x, unsigned char y) {
// Comprova si la casella està buida
if (tauler[y][x] == FITXA_BUIDA) {
// Comprova les 8 caselles adjacents
for (unsigned char dy = 0; dy < 3; dy++) {
for (unsigned char dx = 0; dx < 3; dx++) {
// Salta la casella central (1,1)
if (dx == 1 && dy == 1) {
continue;
}
// Calcula les coordenades del veí de manera segura
unsigned char vei_x = (unsigned char)(x + dx - 1);
unsigned char vei_y = (unsigned char)(y + dy - 1);
// Comprova si el veí està dins dels límits del tauler
if (vei_x < 8 && vei_y < 8) {
if (tauler[vei_y][vei_x] == FITXA_BLAVA || tauler[vei_y][vei_x] == FITXA_VERMELLA) {
return 1; // Moviment vàlid - hi ha fitxa adjacent
}
}
}
}
}
return 0; // Moviment invàlid
}
char cercaiFlanqueja(unsigned char x, unsigned char y, signed char dx, signed char dy, char jugador) {
// Busca fitxes enemigues en la direcció (dx, dy)
// Si troba una fitxa del jugador, flanqueja les fitxes enemigues del mig
char enemiga = (jugador == FITXA_BLAVA) ?
FITXA_VERMELLA : FITXA_BLAVA;
signed char posicio_x = (signed char)(x + dx);
signed char posicio_y = (signed char)(y + dy);
unsigned char fitxes_capturades = 0;
// Camina en la direcció mentre trobi fitxes enemigues
while (posicio_x >= 0 && posicio_x < 8 && posicio_y >= 0 && posicio_y < 8) {
if (tauler[posicio_y][posicio_x] == enemiga) {
fitxes_capturades++;
} else if (tauler[posicio_y][posicio_x] == jugador) {
// Trobat fitxa del jugador!
// Flanqueja les del mig
if (fitxes_capturades > 0) {
// Captura totes les fitxes enemigues del mig
signed char x_captura = (signed char)(x + dx);
signed char y_captura = (signed char)(y + dy);
for (unsigned char i = 0; i < fitxes_capturades; i++) {
tauler[y_captura][x_captura] = jugador;
x_captura = (signed char)(x_captura + dx);
y_captura = (signed char)(y_captura + dy);
}
return 1; // S'han capturat fitxes
}
return 0; // No hi havia fitxes del mig
} else {
// Trobat casella buida sense fitxes del jugador
return 0; // Flanqueig no vàlid
}
posicio_x = (signed char)(posicio_x + dx);
posicio_y = (signed char)(posicio_y + dy);
}
return 0; // S'ha sortit del tauler sense trobar flanc
}
void capturaFitxes(unsigned char x, unsigned char y, char jugador) {
// Comprova les 8 direccions possibles (horitzontals, verticals i diagonals)
// Direccions: (dx, dy)
// Les 8 direccions del tauler
signed char direccions[8][2] = {
{1, 0}, // Dreta
{-1, 0}, // Esquerra
{0, 1}, // Avall
{0, -1}, // Amunt
{1, 1}, // Diagonal avall-dreta
{-1, -1}, // Diagonal amunt-esquerra
{1, -1}, // Diagonal amunt-dreta
{-1, 1} // Diagonal avall-esquerra
};
// Busca flanqueig en cada direcció
for (unsigned char i = 0; i < 8; i++) {
cercaiFlanqueja(x, y, direccions[i][0], direccions[i][1], jugador);
}
}
// Funció animació d'error.
// Es generarà a la matriu tres pantalles vermelles en cas d'error.
void animacioError(void) {
// Animació d'error: parpelleig vermell de tota la matriu
for (unsigned char i = 0; i < 3; i++) {
// Encén tot en vermell
for (unsigned char filera = 1; filera <= 8; filera++) {
Sortida[1] = filera;
Sortida[3] = filera; Sortida[5] = filera;
Sortida[0] = 0xFF; // Tot vermell
Sortida[2] = 0x00; // Verd apagat
Sortida[4] = 0x00; // Blau apagat
Envia3max(Sortida);
}
__delay_ms(150);
// Apaga tot
Apaga();
__delay_ms(150);
}
}
char taulerComplet(void) {
// Recorre tot el tauler buscant caselles buides
for (unsigned char y = 0; y < 8; y++) {
for (unsigned char x = 0; x < 8; x++) {
if (tauler[y][x] == FITXA_BUIDA) {
return 0; // Hi ha almenys una casella buida
}
}
}
return 1; // Tauler complet
}
void comptaFitxes(unsigned char *blaves, unsigned char *vermelles) {
*blaves = 0;
*vermelles = 0;
// Recorre tot el tauler comptant fitxes
for (unsigned char y = 0; y < 8; y++) {
for (unsigned char x = 0; x < 8; x++) {
if (tauler[y][x] == FITXA_BLAVA) {
(*blaves)++;
} else if (tauler[y][x] == FITXA_VERMELLA) {
(*vermelles)++;
}
}
}
}
void mostraGuanyador(char guanyador) {
// Lletra "B" per BLAVES
const unsigned char lletra_B[8] = {
0b11111100,
0b11000110,
0b11000110,
0b11111100,
0b11000110,
0b11000110,
0b11111100,
0b00000000
};
// Lletra "V" per VERMELLES
const unsigned char lletra_V[8] = {
0b11000011,
0b11000011,
0b11000011,
0b01100110,
0b01100110,
0b00111100,
0b00011000,
0b00000000
};
// Animació de guanyador: parpelleig amb el color guanyador
for (unsigned char cicle = 0; cicle < 5; cicle++) {
// Mostra la lletra del guanyador
for (unsigned char filera = 1; filera <= 8; filera++) {
Sortida[1] = filera;
Sortida[3] = filera;
Sortida[5] = filera;
if (guanyador == FITXA_BLAVA) {
Sortida[0] = lletra_V[filera-1];
Sortida[2] = 0x00;
Sortida[4] = 0x00;
} else {
Sortida[0] = 0x00;
Sortida[2] = 0x00;
Sortida[4] = lletra_B[filera-1];
}
Envia3max(Sortida);
}
__delay_ms(500);
// Apaga
Apaga();
__delay_ms(300);
}
// Mostra tauler final amb totes les fitxes del color guanyador
for (unsigned char filera = 1; filera <= 8; filera++) {
Sortida[1] = filera;
Sortida[3] = filera;
Sortida[5] = filera;
if (guanyador == FITXA_BLAVA) {
Sortida[0] = 0xFF; // Tot vermell
Sortida[2] = 0x00;
Sortida[4] = 0x00;
} else {
Sortida[0] = 0x00;
Sortida[2] = 0x00;
Sortida[4] = 0xFF; // Tot blau
}
Envia3max(Sortida);
}
// Manté la pantalla encesa
__delay_ms(3000);
}
// Funcions del joc.
// 1.Funció inicialitzaOthello. Inicia el joc.
void inicialitzaOthello(void) {
// Inicialitza el tauler buit
for (unsigned char y = 0; y < 8; y++) {
for (unsigned char x = 0; x < 8; x++) {
tauler[y][x] = FITXA_BUIDA;
}
}
// Primera part de la funció, neteja el tauler.
// Configuració inicial del joc.
// Situa les 4 fitxes centrals segons les regles oficials.
// Utilitzo com a noms de variables els colors del joc actual: vermell i blau.
tauler[3][3] = FITXA_VERMELLA; // (3,3) d4 - vermella (visualment blau)
tauler[3][4] = FITXA_BLAVA; // (3,4) e4 - blava (visualment vermell)
tauler[4][3] = FITXA_BLAVA; // (4,3) d5 - blava (visualment vermell)
tauler[4][4] = FITXA_VERMELLA; // (4,4) e5 - vermella (visualment blau)
// Posició inicial del cursor
cursor_x = 2;
cursor_y = 2; // Posició c3
jugador_actual = FITXA_BLAVA;
mode_test = 0;
mirar = 1;
// Inicialitza control de botons
moviment_invalid = 0;
joc_acabat = 0; // Joc en curs
}
void actualitzaTauler(void) {
// Actualitza la visualització completa del tauler
for (unsigned char filera = 1; filera <= 8; filera++) {
unsigned char y = filera - 1;
// Converteix a índex 0-7
// Prepara dades per aquesta filera
Sortida[1] = filera;
Sortida[3] = filera; Sortida[5] = filera;
Sortida[0] = 0x00;
Sortida[2] = 0x00;
Sortida[4] = 0x00;
for (unsigned char x = 0; x < 8; x++) {
unsigned char contingut = tauler[y][x];
// Cursor amb parpelleig estable (només si el joc no ha acabat)
if (x == cursor_x && y == cursor_y && joc_acabat == 0) {
if ((comptador_moviment & 0x0F) < 12) {
contingut = CURSOR;
}
}
// Assigna colors segons contingut
switch(contingut) {
case FITXA_BLAVA:
Sortida[0] |= (1 << (7-x)); // Vermell per fitxes blaves
break;
case FITXA_VERMELLA:
Sortida[4] |= (1 << (7-x)); // Blau per fitxes vermelles
break;
case CURSOR:
Sortida[2] |= (1 << (7-x)); // Verd per cursor
break;
default: // FITXA_BUIDA - no fa res (deixa LED apagat)
break;
}
}
// Envia aquesta filera als MAX7221
Envia3max(Sortida);
}
}
// 2. Funció llegeix botons.
void llegeixBotons(void) {
if (detectaBTN0()) {
// BTN0 ha estat premut - reinicia el joc completament
// Apaga la matriu LED
Apaga();
__delay_ms(200); // Petita pausa per a visibilitat
// Reinicialitza totes les variables del joc
inicialitzaOthello();
joc_acabat = 0;
comptador_moviment = 0;
mirar = 1;
return;
// Surt de la funció per evitar processar altres botons
}
// Si el joc ha acabat i no es prem BTN0, no processa més botons
if (joc_acabat == 1) {
return;
}
// Sistema de control igual que el joc de memòria
char polsador_premut = Polsador1();
// Llegeix polsadors analògics
if (mirar == 1) {
if (polsador_premut == 1) { // BTN1 - Amunt
mouCursor(0, -1);
mirar = 0;
}
if (polsador_premut == 2) { // BTN2 - Avall
mouCursor(0, 1);
mirar = 0;
}
if (polsador_premut == 3) { // BTN3 - Esquerra
mouCursor(-1, 0);
mirar = 0;
}
if (polsador_premut == 4) { // BTN4 - Dreta
mouCursor(1, 0);
mirar = 0;
}
if (polsador_premut == 5) { // BTN5 - Selecció amb flanqueig
if (esMovimentValid(cursor_x, cursor_y)) {
tauler[cursor_y][cursor_x] = jugador_actual;
capturaFitxes(cursor_x, cursor_y, jugador_actual);
if (taulerComplet()) {
unsigned char fitxes_blaves, fitxes_vermelles;
comptaFitxes(&fitxes_blaves, &fitxes_vermelles);
// Determina el guanyador
char guanyador;
if (fitxes_blaves > fitxes_vermelles) {
guanyador = FITXA_BLAVA;
} else {
guanyador = FITXA_VERMELLA;
}
// Mostra el guanyador
joc_acabat = 1;
__delay_ms(500); // Petita pausa abans de mostrar resultat
mostraGuanyador(guanyador);
// El joc queda en estat finalitzat
return;
}
// Canvia el torn al següent jugador
jugador_actual = (jugador_actual == FITXA_BLAVA) ?
FITXA_VERMELLA : FITXA_BLAVA;
} else {
animacioError();
// Passa el torn automàticament
jugador_actual = (jugador_actual == FITXA_BLAVA) ?
FITXA_VERMELLA : FITXA_BLAVA;
}
mirar = 0;
}
} else {
if (polsador_premut == 0) { // Si no s'ha premut cap polsador
mirar = 1;
// Permet que es detectin nous premuts
}
}
}
void mouCursor(signed char dx, signed char dy) {
unsigned char nova_x = (unsigned char)(cursor_x + dx);
unsigned char nova_y = (unsigned char)(cursor_y + dy);
// Comprova límits i actualitza posició
if (nova_x < 8) {
cursor_x = nova_x;
}
if (nova_y < 8) {
cursor_y = nova_y;
}
}

Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.