Ara en el mode d'un sol jugador afegirem un algorisme que intenti guanyar. Així com en l'apartat anterior era molt fàcil guanyar a la màquina, ara serà especialment difícil.
L'algorisme que intenta que la màquina guanyi es basa en quatre passos:
1- Busca l'opció de tancar una línia en posar la fitxa (guanyar immediatament)
2- Busca l'opció que el contrari estigui a punt de tancar una línia (bloquejar que guanyi)
3- Busca una línia en la qual només hi hagi una fitxa pròpia (per tendir a tancar-la)
4- Busca una línia en la qual el contrari hi tingui una fitxa (per dificultar-li el joc)
Es van provant cada un dels passos, en aquest ordre, fins aconseguir posar la fitxa. En el cas, poc probable que no es pugui posar la fitxa en cap dels passos, aquesta es situa aleatòriament en una casella buida. Per facilitar la gestió, s'ha creat una funció que determina el jugador contrari.
La següent taula mostra les opcions dels diferents passos; on el símbol ♦ correspon a la fitxa de la màquina, ★ correspon al jugador i ▭ és una casella en blanc.
| Pas | Busca una línia amb | Fitxa a posar | Descripció | ||
| 0 | ♦ | ♦ | ▭ | ♦ | Mira si amb aquesta tirada la màquina guanya |
| 1 | ★ | ★ | ▭ | ♦ | Mira si amb aquesta tirada el jugador guanya (per impedir-li ocupar la casella) |
| 2 | ♦ | ▭ | ▭ | ♦ | Mira si queda alguna línia amb una casella de la màquina i dues en blanc |
| 3 | ★ | ▭ | ▭ | ♦ | Mira si queda alguna línia amb una casella de l'usuari i dues en blanc |
Per a cada pas, es proven les vuit opcions possibles (dues diagonals, tres fileres i tres columnes) i en cada opció es miren les tres possibles combinacions (ja que la fitxa que posem pot anar a qualsevol de les tres posicions de la linia. La funció posaFitxa rep les indicacions de què ha de mirar i en quina línia i prova les tres combinacions. La funció tirAuto és la que recorre tots els passos i crida a la funció posaFitxa per a cada una de les línies que cal mirar. Les variables fet i acabat, respectivament, serveixen per controlar que només es posi una fitxa en cada tirada.
pant_principal.dart
import 'dart:math'; import 'package:flutter/material.dart'; import 'package:tres_linia/components/boto.dart'; import 'package:tres_linia/components/casella.dart'; import 'package:tres_linia/components/fitxa.dart'; import 'package:tres_linia/core/estils.dart';
class PantPral extends StatefulWidget {
const PantPral({super.key});
@override
State<PantPral> createState() => _PantPralState();
}
class _PantPralState extends State<PantPral> {
List<List<int>> estatCaselles = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
int numJugadors = 0; // Cal esperar que es premi un botó
int jugadorActual = 0; // No hi ha jugador fins que es prem un botó
int quiGuanya = 0; // De moment, no hi ha guanyador
String notificacio = "Tira:";
@override
Widget build(BuildContext context) {
return Column(
children: [
SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Boto(textBoto: "1 jugador", enUs: numJugadors != 2, accio: () {
setState(() {
inici(1);
});
},),
SizedBox(
width: 20,
),
Boto(textBoto: "2 jugadors", enUs: numJugadors != 1, accio: () {
setState(() {
inici(2);
});
},)
],
),
SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
notificacio,
style: Estils.estilTextInfo
),
SizedBox(
width: 20,
),
Fitxa(estatAct: jugadorActual, mida: 25)
],
),
for(int i = 0; i < 3; i++) filera(i),
Expanded(child: Text(" "))
],
);
}
Row filera(int pY) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
for(int i = 0; i < 3; i++) Casella(posX: i, posY: pY, estAct: estatCaselles[pY][i],
posaFitxa: () {
setState(() {
// L'acció de picar la casella només es fa si la partida està en curs
if((quiGuanya == 0) && (numJugadors > 0)){
if(estatCaselles[pY][i] == 0) {
estatCaselles[pY][i] = jugadorActual;
jugadorActual = jugContrari(jugadorActual);
quiGuanya = guanyador();
}
// Després de cada tirada, mirem si hi ha guanyador
// Si només hi ha un jugador, l'altre es tira automàticament
if((quiGuanya == 0) && (numJugadors == 1)){
tirAuto(jugadorActual);
jugadorActual = jugContrari(jugadorActual);
quiGuanya = guanyador();
}
}
});
},
),
],
);
}
void inici(int numJug){
if(numJugadors == 0){
numJugadors = numJug;
estatCaselles = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
jugadorActual = 1; // Comença el jugador 1
quiGuanya = 0; // De moment, no hi ha guanyador
notificacio = "Tira:";
}
}
bool posaFitxa(List<List<int>> coord, int fitBuscar, int fitBusc, int fitPosar){
// Aquesta funció només funciona si la llista és de tres fileres de dos elements
// Cada filera conté les coordenades d'un dels tres punts a provar
// Si linia és cert, busquem acabar la línia
bool fet = false;
int casella1 = estatCaselles[coord[0][0]][coord[0][1]];
int casella2 = estatCaselles[coord[1][0]][coord[1][1]];
int casella3 = estatCaselles[coord[2][0]][coord[2][1]];
if((casella1 == 0) && (casella2 == fitBusc) && (casella3 == fitBuscar) && !fet){
estatCaselles[coord[0][0]][coord[0][1]] = fitPosar; // Casella 1
fet = true;
}
if((casella1 == fitBuscar) && (casella2 == 0) && (casella3 == fitBusc) && !fet){
estatCaselles[coord[1][0]][coord[1][1]] = fitPosar; // Casella 2
fet = true;
}
if((casella1 == fitBusc) && (casella2 == fitBuscar) && (casella3 == 0) && !fet){
estatCaselles[coord[2][0]][coord[2][1]] = fitPosar; // Casella 3
fet = true;
}
return fet;
}
void tirAuto(int jug){
bool acabat = false;
int fitMirar = jug; // En principi, busquem el mateix jugador (màquina)
int fitMir = jug;
List<List<List<int>>> coordProvar = [[[0, 0], [1, 1], [2, 2]], // Diagonal \
[[2, 0], [1, 1], [0, 2]], // Diagonal /
[[0, 0], [0, 1], [0, 2]], // Filera 0
[[1, 0], [1, 1], [1, 2]], // Filera 1
[[2, 0], [2, 1], [2, 2]], // Filera 2
[[0, 0], [1, 0], [2, 0]], // Columna 0
[[0, 1], [1, 1], [2, 1]], // Columna 1
[[0, 2], [1, 2], [2, 2]]]; // Columna 2
for(int i = 0; i < 4; i++){ // Quatre passos
if(!acabat){
if((i == 1) || (i == 3)){
// En els passos 1 i 3, es mira el jugador contrari
fitMirar = jugContrari(jug);
}
if(i < 2){
fitMir = fitMirar; // En els dos primers passos, busquem tancar línia
} else {
fitMir = 0; // En els altres dos busquem línies amb dues caselles buides
}
// En cada cas, provem totes les linies fins posar la fitxa
for(int j = 0; j < coordProvar.length; j++){ // Recorrem totes les línies possibles
if(!acabat){
acabat = posaFitxa(coordProvar[j], fitMirar, fitMir, jug);
}
}
}
}
// Si encara no hem posat la fitxa, ho fem aleatòriament
if(!acabat){
int opcions = comptaCaselles(0);
int pos = Random().nextInt(opcions);
int posAct = 0;
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++){
if(estatCaselles[i][j] == 0){
if(posAct == pos){
estatCaselles[i][j] = jug;
}
posAct++;
}
}
}
}
}
int jugContrari(int jugdr){
int cont = jugdr + 1;
if(cont == 3) {
cont = 1;
}
return cont;
}
int comptaCaselles(int val){
// Compta les caselles que tenen un determinat valor
int total = 0;
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++){
if(estatCaselles[i][j] == val){
total++;
}
}
}
return total;
}
int guanyador(){
int guanya = 0;
// Atès que només s'afegeix una fitxa cada cop, només hi pot haver un guanyador
// Primer mirem les diagonals
if((estatCaselles[0][0] != 0) && (estatCaselles[0][0] == estatCaselles[1][1])
&& (estatCaselles[0][0] == estatCaselles[2][2])){
guanya = estatCaselles[0][0];
}
if((estatCaselles[0][2] != 0) && (estatCaselles[0][2] == estatCaselles[1][1])
&& (estatCaselles[0][2] == estatCaselles[2][0])){
guanya = estatCaselles[0][2];
}
// Mirem fileres i columnes
for(int i = 0; i < 3; i++){
if((estatCaselles[i][0] != 0) && (estatCaselles[i][0] == estatCaselles[i][1])
&& (estatCaselles[i][0] == estatCaselles[i][2])){
guanya = estatCaselles[i][0];
}
}
for(int i = 0; i < 3; i++){
if((estatCaselles[0][i] != 0) && (estatCaselles[0][i] == estatCaselles[1][i])
&& (estatCaselles[0][i] == estatCaselles[2][i])){
guanya = estatCaselles[0][i];
}
}
// Si no hi ha guanyador, mirem si el taulell ja és ple
// Si ho és, hi ha empat
if((guanya == 0) && (comptaCaselles(0) == 0)){
guanya = 3;
}
// S'acaba la partida si hi ha guanyador o no hi ha caselles
// Es posa com a jugador al guanyador, per mostrar-lo
// En cas d'empat, es deixa buit
if(guanya > 0){
numJugadors = 0;
if(guanya == 3){
notificacio = "Empat!";
jugadorActual = 0;
} else {
notificacio = "Guanya:";
jugadorActual = guanya;
}
}
return guanya;
}
}
Aquest exemple s'acaba aquí, però encara s'hi podrien introduir millores. Es podria afegir l'opció de triar quin jugador és el que comença o que els jugadors poquessin triar els colors de les seves fitxes. També es podria variar el nivell del joc, fent que només s'implementin (segons el nivell triat) alguns dels quatre passos.

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