El joc consisteix en deixar caure fitxes des de la part superior de la pantalla per tal que es situïn en la posició més baixa d'aquella columna que encara estigui lliure. L'objectiu és que un dels dos jugadors consegueixi fer una línia de quatre fitxes, en vertical, horitzontal o diagonal. L'ús dels polsadors és el següent:
| Polsador | Finalitat |
| 1 | Desplaçament a l'esquerra |
| 3 | Deixar caure la fitxa |
| 5 | Reinici |

El programa és el següent:
#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> // Carrega el fitxer de funcions necessari per al compilador XC8 #define _XTAL_FREQ 4000000 // La freqüència del rellotge és 4 MHz #define cic_int 5 // Nombre de cicles per a la intermitència
char Port; // Gestió del port a la funció Envia_max char Compta; // Comptador de bits a la funció Envia_max char Sortida[6]; // Valors a enviar al MAX7221 (48 bits) char Sorti[6]; // Valors a enviar al MAX7221 des de la interrupció char Actiu; // Variable que diu quin color està actiu Actiu = 0: Apagat, 1: Vermell, 3: Blau char Polsad; // Polsador que s'ha premut char figura[8][8]; // Matriu que guarda l'estat del tauler (0=buit, 1=vermell, 4=blau) char x = 0; // Coordenada X del cursor (0 a 7) char y = 0; // Coordenada Y del cursor (0 a 7) char mirar = 1; // Espera que es deixi anar el polsador char compt_int = 0; // Comptador de cicles per a la intermitència char cur_on = 1; // Controla l'estat del cursor (ences/apagat) char color = 1; // Comencem amb vermell (jugador 1) char partida_acabada = 0; // Estat del joc: 0=jugant, 1=acabada
void Envia3max(char Valor[]); // Envia valors als MAX7221 (per software) void Envia_max(void); // Envia valors als MAX7221 (per interrupció/ASM) void Ini3max(void); // Inicialitza els xips de la matriu void Apaga(void); // Apaga tots els LED // Funcionament del joc char Polsador(void); // Llegeix quin polsador s'ha premut char Fi_joc(char jugador); // Comprova si hi ha 4 en ratlla char TrobaFilaPerCaure(char columna); // Calcula la gravetat de la fitxa // Caràcters pantalla LCD void EnviaL(char Caracter); // Envia un caràcter a l'LCD (gestionant el conflicte) void Esborra(void); // Esborra la pantalla void Cursor(char Filera, char Columna); // Mou el cursor de text void Envia_String(const char* text); // Envia frases senceres void MostraGuanyador(char jugador); // Gestiona el missatge final de victòria
void main (void) {
OPTION_REG = 0b10000101; // Configuració de Timer0: Prescaler 64
TRISC = 0; // Tot el port C és de sortida
TRISB = 0; // Tot el port B és de sortida
TRISA = 0xFF; // Tot el port A és d'entrada
ANSEL = 0b00000101; // Configura AN0 i AN2 com entrada analògica
ANSELH = 0; // Desactiva les altres entrades analògiques
PORTC = 0; // Inicialitza a 0 el port C
PORTB = 0; // Inicialitza a 0 el port B
ADCON1 = 0b00010000; // Posa el conversor a 1/8 de la freqüència
ADCON0 = 0b00001001; // Activa el conversor A/D connectat a AN2
TXSTAbits.BRGH = 1; // Configuració d'alta velocitat
BAUDCTLbits.BRG16 = 0; // Paràmetre de velocitat de 8 bits
SPBRG = 25; // Velocitat de 9600 baud (@ 4MHz)
TXSTAbits.SYNC = 0; // Comunicació asíncrona
TXSTAbits.TX9 = 0; // Comunicació de 8 bits
Ini3max(); // Inicialitza els tres MAX7221
__delay_ms(2000); // Retard perque encengui correctament
Esborra(); // Neteja qualsevol text anterior
__delay_ms(10);
Envia_String("Connecta 4"); // Escriu el títol inicial
Actiu = 1; // Activa el color vermell per la interrupció
TMR0 = 100; // Presselecció de 100 per al Timer0
INTCON = 0b10100000; // Activem GIE i T0IE
Apaga(); // Apaga tots els LED de la matriu
// Neteja del tauler lògic (bucle)
for (signed char j = 0; j < 8; j++){
for (signed char k = 0; k < 8; k++){
figura[j][k] = 0; // Posa totes les caselles a 0 (buit)
}
}
x = 0; // Cursor comença a la dreta
y = 0; // Cursor a dalt
partida_acabada = 0; // Estat inicial: jugant
color = 1; // Comença el jugador Vermell
// Bucle infinit
while (1) {
// a) Joc en marxa
if (partida_acabada == 0) {
Polsad = Polsador(); // Llegim els polsadors
if (mirar == 1) {
// Polsador 1: Moure Dreta
if (Polsad == 1) {
x = (x + 1) % 8; // Incrementa x i fa la volta (0-7)
mirar = 0;
}
// Polsador 3: Deixar caure fitxa
if (Polsad == 3) {
signed char fila_on_caura = TrobaFilaPerCaure(x); // Calcula on cau
if (fila_on_caura != -1) { // Si la columna no està plena
figura[fila_on_caura][x] = color; // Guarda la fitxa
// Comprovar victòria
if (Fi_joc(color) == 1) {
partida_acabada = 1; // Marca el joc com acabat
MostraGuanyador(color); // Mostra missatge a l'LCD
} else {
// Canvi de torn (1->4, 4->1)
if (color == 1) color = 4;
else color = 1;
}
}
mirar = 0;
}
} else {
if (Polsad == 0) mirar = 1; // Espera que deixis anar el botó
}
} else {
// b) joc acabat
Polsad = Polsador();
// Només permetem reiniciar amb P5 quan s'ha acabat
if (mirar == 1 && Polsad == 5) {
for (signed char j = 0; j < 8; j++) {
for (signed char k = 0; k < 8; k++) {
figura[j][k] = 0; // Esborra tauler
}
}
x = 0;
color = 1;
partida_acabada = 0;
mirar = 0;
Esborra(); // Esborra missatge de victòria
__delay_ms(10);
Envia_String("Connecta 4"); // Posa títol de nou
} else if (Polsad == 0) {
mirar = 1;
}
}
// fitxes a la matriu de led
for (signed char j = 0; j < 8; j++){ // Per cada fila
char mascara;
char col;
Sortida[0] = 0; // Vermells a 0
Sortida[4] = 0; // Blaus a 0
for (signed char k = 0; k < 8; k++){ // Per cada columna
// Dibuixar cursor (si no ha acabat la partida)
if (partida_acabada == 0 && (y == j) && (x == k)){
if (cur_on == 1) col = color; // Cursor parpellejant
else col = figura[j][k]; // O el que hi hagi a sota
} else {
col = figura[j][k]; // Color de la fitxa guardada
}
// Convertir color (1 o 4) a bits per enviar
mascara = col & 0b0000001; // Bit vermell
Sortida[0] = Sortida[0] | (mascara << k);
mascara = (col & 0b0000100) >> 2; // Bit blau
Sortida[4] = Sortida[4] | (mascara << k);
}
Sortida[1] = j+1; // Selecciona fila vermell
Sortida[5] = j+1; // Selecciona fila blau
Envia3max(Sortida); // Envia dades als drivers
}
__delay_ms(1); // Petit retard de refresc
// Control intermitència del cursor
compt_int++;
if (compt_int == cic_int){
compt_int = 0;
cur_on = (cur_on + 1) % 2; // Inverteix estat cursor
}
}
}
void __interrupt() temporit(void){
if (INTCONbits.T0IF) { // Si ha saltat el Timer0
TMR0 = 100; // Recarrega el Timer
INTCONbits.T0IF = 0; // Neteja el flag
if (Actiu != 0) {
Actiu--; // Cicle de colors
if (Actiu == 0) Actiu = 3;
}
Sorti[0] = 0x00; // Neteja buffers
Sorti[4] = 0x00;
if (Actiu == 1) Sorti[0] = 0x01; // Activa vermell
if (Actiu == 3) Sorti[4] = 0x01; // Activa blau
Sorti[1] = 0x0C; // Shutdown mode
Sorti[5] = 0x0C; // Shutdown mode
Envia_max(); // Envia via ASM
}
}
// funcions matriu de led
void Envia3max(char Valor[]) {
INTCONbits.T0IE = 0; // Atura interrupcions
char Port = 0; // Estat del port B
char Temp;
for (signed char j = 5; j >= 0; j--){ // 6 bytes a enviar
for (signed char k = 1; k < 9; k++){ // 8 bits per byte
Temp = Valor[j] & 0b10000000;
if (Temp == 0) Port = Port & 0b11101111; // Data = 0
else Port = Port | 0b00010000; // Data = 1
Valor[j] = Valor[j] << 1;
PORTB = Port; // Escriu Data
Port = Port | 0b00100000; // Clock Alt
PORTB = Port;
Port = Port & 0b11011111; // Clock Baix
PORTB = Port;
}
}
Port = Port | 0b01000000; // Activa Latch (bit 6) per copiar a les sortides
PORTB = Port; // Ho posa al port B
INTCONbits.T0IE = 1; // Reactiva interrupcions a l'acabar
}
void Envia_max(void) {
asm("banksel _Port");
asm("bcf (_Port&7fh),5"); // Clock a 0
asm("bcf (_Port&7fh),6"); // Latch a 0
asm("movf (_Port&7fh),w");
asm("banksel PORTB");
asm("movwf PORTB");
asm("banksel _Compta");
asm("movlw 48"); // 48 bits total
asm("movwf (_Compta&7fh)");
asm("Bucle:");
asm("banksel _Port");
asm("bcf (_Port&7fh),4"); // Data a 0 per defecte
asm("banksel _Sorti");
asm("rlf (_Sorti&7fh),f"); // Rota bits...
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"); // Si el bit era 1...
asm("bsf (_Port&7fh),4"); // ...posa Data a 1
asm("movf (_Port&7fh),w");
asm("banksel PORTB");
asm("movwf PORTB"); // Actualitza Port
asm("banksel _Port");
asm("bsf (_Port&7fh),5"); // Clock Alt
asm("movf (_Port&7fh),w");
asm("banksel PORTB");
asm("movwf PORTB");
asm("banksel _Port");
asm("bcf (_Port&7fh),5"); // Clock Baix
asm("movf (_Port&7fh),w");
asm("banksel PORTB");
asm("movwf PORTB");
asm("banksel _Compta");
asm("decfsz (_Compta&7fh),f"); // Decrementa comptador
asm("goto (Bucle&7ffh)"); // Repeteix
asm("banksel _Port");
asm("bsf (_Port&7fh),6"); // Latch Alt
asm("movf (_Port&7fh),w");
asm("banksel PORTB");
asm("movwf PORTB");
}
void Ini3max(void) {
char Bytes[6];
Bytes[0] = 0x00;
Bytes[1] = 0x0C;
Bytes[2] = 0x00;
Bytes[3] = 0x0C;
Bytes[4] = 0x00;
Bytes[5] = 0x0C;
Envia3max(Bytes);
Bytes[0] = 0x00;
Bytes[1] = 0x09;
Bytes[2] = 0x00;
Bytes[3] = 0x09;
Bytes[4] = 0x00;
Bytes[5] = 0x09;
Envia3max(Bytes);
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);
}
}
char TrobaFilaPerCaure(char columna) {
// Comença des de baix (fila 7) i puja
for (signed char fila = 7; fila >= 0; fila--) {
if (figura[fila][columna] == 0) return fila; // Retorna primera buida
}
return -1; // Columna plena
}
char Fi_joc(char jugador) {
char x, y;
// 1. Comprovació Horizontal
for (y = 0; y < 8; y++) {
for (x = 0; x < 5; x++) {
if (figura[y][x] == jugador &&
figura[y][x+1] == jugador &&
figura[y][x+2] == jugador &&
figura[y][x+3] == jugador) return 1;
}
}
// 2. Comprovació Vertical
for (x = 0; x < 8; x++) {
for (y = 0; y < 5; y++) {
if (figura[y][x] == jugador &&
figura[y+1][x] == jugador &&
figura[y+2][x] == jugador &&
figura[y+3][x] == jugador) return 1;
}
}
// 3. Diagonal Baix-Dreta
for (y = 0; y < 5; y++) {
for (x = 0; x < 5; x++) {
if (figura[y][x] == jugador &&
figura[y+1][x+1] == jugador &&
figura[y+2][x+2] == jugador &&
figura[y+3][x+3] == jugador) return 1;
}
}
// 4. Diagonal Dalt-Dreta
for (y = 3; y < 8; y++) {
for (x = 0; x < 5; x++) {
if (figura[y][x] == jugador &&
figura[y-1][x+1] == jugador &&
figura[y-2][x+2] == jugador &&
figura[y-3][x+3] == jugador) return 1;
}
}
return 0; // Ningú guanya
}
char Polsador(void) {
char Pols = 0;
ADCON0bits.GO = 1; // Posa en marxa el conversor
while (ADCON0bits.GO == 1); // Espera que acabi
if (ADRESH < 220 && ADRESH > 200) Pols = 1; // Comprova polsador 1
if (ADRESH < 194 && ADRESH > 174) Pols = 2; // Comprova polsador 2
if (ADRESH < 163 && ADRESH > 143) Pols = 3; // Comprova polsador 3
if (ADRESH < 90 && ADRESH > 70) Pols = 4; // Comprova polsador 4
if (ADRESH < 55 && ADRESH > 35) Pols = 5; // Comprova polsador 5
return Pols;
}
void EnviaL(char Caracter) {
char estat_int = INTCONbits.GIE;
INTCONbits.GIE = 0;
RCSTAbits.SPEN = 1;
TXSTAbits.TXEN = 1;
TXREG = Caracter;
while (TXSTAbits.TRMT == 0);
RCSTAbits.SPEN = 0;
TXSTAbits.TXEN = 0;
INTCONbits.GIE = estat_int;
}
void Esborra(void) {
EnviaL(254); // Prefix de control
EnviaL(1); // Comanda 1: Esborrar
__delay_ms(5); // Espera necessària
}
void Cursor(char Filera, char Columna) {
char Posicio = 0;
if (Filera == 2) Posicio = 64; // Línia 2 comença a 64
if (Columna > 0 && Columna < 33) {
Posicio = Posicio + (Columna - 1);
}
Posicio = Posicio + 128; // Bit de comanda cursor
EnviaL(254);
EnviaL(Posicio);
}
void Envia_String(const char* text) {
for (unsigned char i = 0; text[i] != '\0'; i++) {
EnviaL(text[i]); // Envia caràcter a caràcter
}
}
void MostraGuanyador(char jugador) {
Esborra(); // Neteja pantalla
__delay_ms(10);
if (jugador == 1) Envia_String("Guanya Vermell");
else Envia_String("Guanya Blau");
Cursor(2, 1); // Línia 2
Envia_String("Reinici BTN5");
}

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