Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
Rafa Castillo Passols
/
peponator
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
e0afdbe0
authored
May 15, 2025
by
Diego Pérez Peña
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Página de dificultades estilizada y pequeñas correcciones
parent
a2213ba5
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
334 additions
and
70 deletions
lib/modelo/dificultad.dart
lib/modelo/listaDificultad.dart
lib/paginas/pantalla_dificultad.dart
lib/paginas/pantalla_juego.dart
lib/peponator_app.dart
lib/widgets/dificultad_dialog.dart
lib/widgets/peponator_mensaje_factory.dart
lib/widgets/widget_dificultad.dart
lib/modelo/dificultad.dart
View file @
e0afdbe0
...
...
@@ -8,28 +8,39 @@ import 'dart:math';
/// Dado un número de intentos, conseguir adivinar un número en una
/// dificultad mayor debería de dar una puntuación mayor.
class
Dificultad
{
// TODO: SISTEMA PARA CALCULAR PUNTUACIÓN. Pensar en log2(limite)
final
String
_id
;
final
int
_limite
;
final
int
_maxIntentos
;
final
String
_nombre
;
const
Dificultad
({
required
limite
,
required
maxIntentos
,
required
nombre
}):
_limite
=
limite
,
_maxIntentos
=
maxIntentos
,
_nombre
=
nombre
;
const
Dificultad
({
required
String
id
,
required
int
limite
,
required
int
maxIntentos
,
required
String
nombre
}):
_id
=
id
,
_limite
=
limite
,
_maxIntentos
=
maxIntentos
,
_nombre
=
nombre
;
String
get
id
=>
_id
;
int
get
limite
=>
_limite
;
int
get
maxIntentos
=>
_maxIntentos
;
String
get
nombre
=>
_nombre
;
factory
Dificultad
.
desdeJson
(
Map
<
String
,
dynamic
>
json
)
{
return
Dificultad
(
limite:
int
.
parse
(
json
[
'limite'
]),
maxIntentos:
int
.
parse
(
json
[
'maxIntentos'
]),
nombre:
json
[
'nombre'
]
id:
json
[
'id'
],
limite:
int
.
parse
(
json
[
'limite'
]),
maxIntentos:
int
.
parse
(
json
[
'maxIntentos'
]),
nombre:
json
[
'nombre'
]
);
}
String
toJson
()
{
return
'''
{
"id": "
$id
",
"limite": "
$limite
",
"maxIntentos": "
$maxIntentos
",
"nombre": "
$nombre
"
...
...
@@ -37,19 +48,23 @@ class Dificultad {
'''
;
}
// Los números de intentos de estas dificultades están calculados como log2(limite)+1
static
final
Dificultad
facil
=
Dificultad
(
id:
'4f3ad59c-31b3-11f0-9cd2-0242ac120002'
,
limite:
100
,
maxIntentos:
7
+
1
,
nombre:
"Fácil"
);
nombre:
'Fácil'
);
static
final
Dificultad
normal
=
Dificultad
(
id:
'4f3ad736-31b3-11f0-9cd2-0242ac120002'
,
limite:
500
,
maxIntentos:
9
+
1
,
nombre:
"Normal"
nombre:
'Normal'
);
static
final
Dificultad
dificil
=
Dificultad
(
id:
'4f3ad7f4-31b3-11f0-9cd2-0242ac120002'
,
limite:
1000
,
maxIntentos:
10
+
1
,
nombre:
"Dificil"
nombre:
'Difícil'
);
int
generarNumero
()
=>
Random
().
nextInt
(
limite
)
+
1
;
...
...
@@ -70,12 +85,13 @@ class Dificultad {
factory
Dificultad
.
copia
(
Dificultad
otro
)
{
return
Dificultad
(
id:
otro
.
id
,
limite:
otro
.
limite
,
maxIntentos:
otro
.
maxIntentos
,
nombre:
otro
.
nombre
);
}
@override
bool
operator
==(
Object
other
)
{
if
(
other
is
!
Dificultad
)
{
...
...
lib/modelo/listaDificultad.dart
View file @
e0afdbe0
...
...
@@ -14,7 +14,6 @@ class ListaDificultad extends ChangeNotifier {
int
_indiceSeleccionado
=
0
;
bool
_cargando
=
true
;
ListaDificultad
()
{
_cargarDificultades
();
}
...
...
lib/paginas/pantalla_dificultad.dart
View file @
e0afdbe0
...
...
@@ -13,12 +13,45 @@ class PantallaDificultad extends StatelessWidget {
child:
manager
.
listo
?
ListView
.
separated
(
itemBuilder:
(
context
,
index
)
{
return
DificultadWidget
(
indice:
index
,);
Color
?
color
;
Color
?
text
;
if
(
index
==
0
){
color
=
(
Theme
.
of
(
context
).
brightness
==
Brightness
.
dark
)?
Color
.
fromARGB
(
255
,
0
,
86
,
3
)
:
Colors
.
lightGreenAccent
.
shade100
;
text
=
(
Theme
.
of
(
context
).
brightness
==
Brightness
.
dark
)?
Colors
.
white
:
Colors
.
black
;
}
else
if
(
index
==
1
){
color
=
(
Theme
.
of
(
context
).
brightness
==
Brightness
.
dark
)?
Colors
.
blue
.
shade800
:
Colors
.
blue
.
shade200
;
text
=
(
Theme
.
of
(
context
).
brightness
==
Brightness
.
dark
)?
Colors
.
white
:
Colors
.
black
;
}
else
if
(
index
==
2
){
color
=
(
Theme
.
of
(
context
).
brightness
==
Brightness
.
dark
)?
Color
.
fromARGB
(
255
,
150
,
10
,
0
)
:
Colors
.
red
.
shade300
;
text
=
(
Theme
.
of
(
context
).
brightness
==
Brightness
.
dark
)?
Colors
.
white
:
Colors
.
black
;
}
return
DificultadWidget
(
key:
Key
(
manager
.
dificultades
[
index
].
id
),
staticDif:
index
<
3
,
indice:
index
,
backgroundColor:
color
,
foregroundColor:
text
,
);
},
separatorBuilder:
(
context
,
index
)
=>
const
SizedBox
(
height:
8
),
itemCount:
manager
.
length
)
:
C
ircularProgressIndicator
(
)
:
C
enter
(
child:
CircularProgressIndicator
()
)
);
}
);
...
...
@@ -32,7 +65,7 @@ class PantallaDificultad extends StatelessWidget {
Dificultad
seleccionada
=
Provider
.
of
<
ListaDificultad
>(
context
,
listen:
false
).
seleccionada
;
Navigator
.
maybePop
(
context
,
seleccionada
);
}),
title:
const
Text
(
"Selecciona dificultad"
),
title:
const
Text
(
'Selecciona dificultad'
),
),
body:
construirListaDificultad
(),
floatingActionButton:
FloatingActionButton
(
...
...
lib/paginas/pantalla_juego.dart
View file @
e0afdbe0
This diff is collapsed.
Click to expand it.
lib/peponator_app.dart
View file @
e0afdbe0
...
...
@@ -20,7 +20,7 @@ class PeponatorApp extends StatelessWidget {
),
darkTheme:
ThemeData
.
dark
(),
themeMode:
ThemeMode
.
system
,
home:
PantallaJuego
(
fromHome:
true
),
home:
PantallaJuego
(),
),
);
}
...
...
lib/widgets/dificultad_dialog.dart
View file @
e0afdbe0
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:peponator/modelo/dificultad.dart'
;
import
'package:uuid/uuid.dart'
;
class
DificultadDialog
extends
StatefulWidget
{
final
Dificultad
?
dificultad
;
...
...
@@ -11,27 +12,85 @@ class DificultadDialog extends StatefulWidget {
State
<
DificultadDialog
>
createState
()
=>
_DificultadDialogState
();
}
// TODO: HACER QUE 'HECHO' SE DESABILITE CUANDO CAMPOS VACIOS
// TODO: MEJORAR EL ASPECTO
// TODO: CAMBIAR PARA USAR IDIOMA
class
_DificultadDialogState
extends
State
<
DificultadDialog
>
{
late
TextEditingController
limiteController
;
late
TextEditingController
intentosController
;
late
TextEditingController
nombreController
;
int
?
limite
;
int
?
intentos
;
String
?
nombre
;
String
?
errorLimite
;
String
?
errorIntentos
;
String
?
errorNombre
;
@override
Widget
build
(
BuildContext
context
)
{
TextEditingController
limiteController
=
TextEditingController
(
void
initState
()
{
super
.
initState
();
limiteController
=
TextEditingController
(
text:
widget
.
dificultad
?.
limite
.
toString
()
);
TextEditingController
intentosController
=
TextEditingController
(
text:
widget
.
dificultad
?.
maxIntentos
.
toString
()
limiteController
.
addListener
(()
=>
setState
(()
{
errorLimite
=
calculateErrorLimite
(
limiteController
.
text
);
limite
=
int
.
tryParse
(
limiteController
.
text
);
})
);
if
(
limiteController
.
text
.
isEmpty
){
errorLimite
=
calculateErrorLimite
(
limiteController
.
text
);
}
intentosController
=
TextEditingController
(
text:
widget
.
dificultad
?.
maxIntentos
.
toString
()
);
intentosController
.
addListener
(()
=>
setState
(()
{
errorIntentos
=
calculateErrorIntentos
(
intentosController
.
text
);
intentos
=
int
.
tryParse
(
intentosController
.
text
);
})
);
if
(
intentosController
.
text
.
isEmpty
){
errorIntentos
=
calculateErrorLimite
(
intentosController
.
text
);
}
nombreController
=
TextEditingController
(
text:
widget
.
dificultad
?.
nombre
.
toString
()
);
TextEditingController
nombreController
=
TextEditingController
(
text:
widget
.
dificultad
?.
nombre
nombreController
.
addListener
(()
=>
setState
(()
{
nombre
=
nombreController
.
text
;
if
(
nombre
==
null
||
nombre
!.
isEmpty
){
errorNombre
=
'El nombre no puede estar vacío'
;
}
else
{
errorNombre
=
null
;
}
})
);
if
(
nombreController
.
text
.
isEmpty
){
errorNombre
=
'El nombre no puede estar vacío'
;
}
}
@override
void
dispose
()
{
limiteController
.
dispose
();
intentosController
.
dispose
();
nombreController
.
dispose
();
super
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
SimpleDialog
(
title:
Text
(
widget
.
dificultad
!=
null
?
"Editar dificultad"
:
"Añadir dificultad"
:
"Añadir dificultad"
,
textAlign:
TextAlign
.
center
),
children:
[
SimpleDialogOption
(
...
...
@@ -43,6 +102,10 @@ class _DificultadDialogState extends State<DificultadDialog> {
controller:
limiteController
,
keyboardType:
TextInputType
.
number
,
inputFormatters:
[
FilteringTextInputFormatter
.
digitsOnly
],
decoration:
InputDecoration
(
border:
OutlineInputBorder
(),
errorText:
errorLimite
,
),
),
const
SizedBox
(
height:
16
,),
Text
(
"Número de intentos"
),
...
...
@@ -50,10 +113,21 @@ class _DificultadDialogState extends State<DificultadDialog> {
controller:
intentosController
,
keyboardType:
TextInputType
.
number
,
inputFormatters:
[
FilteringTextInputFormatter
.
digitsOnly
],
decoration:
InputDecoration
(
border:
OutlineInputBorder
(),
errorText:
errorIntentos
),
),
const
SizedBox
(
height:
16
,),
Text
(
"Nombre"
),
TextField
(
controller:
nombreController
),
TextField
(
controller:
nombreController
,
maxLength:
8
,
decoration:
InputDecoration
(
border:
OutlineInputBorder
(),
errorText:
errorNombre
),
),
],
),
),
...
...
@@ -61,27 +135,86 @@ class _DificultadDialogState extends State<DificultadDialog> {
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
SimpleDialogOption
(
onPressed:
()
{
Navigator
.
pop
(
context
,
Dificultad
(
limite:
int
.
parse
(
limiteController
.
text
),
maxIntentos:
int
.
parse
(
intentosController
.
text
),
nombre:
nombreController
.
text
,
)
);
},
child:
Text
(
"Hecho"
),
Expanded
(
child:
SimpleDialogOption
(
child:
TextButton
(
style:
TextButton
.
styleFrom
(
backgroundColor:
Color
.
fromARGB
(
255
,
0
,
86
,
3
),
foregroundColor:
Colors
.
white
,
disabledBackgroundColor:
Colors
.
black26
,
disabledForegroundColor:
Color
.
fromARGB
(
255
,
40
,
40
,
40
),
),
onPressed:
(
errorLimite
!=
null
||
errorIntentos
!=
null
||
errorNombre
!=
null
)?
null
:
()
{
if
(
errorLimite
==
null
&&
errorIntentos
==
null
){
Navigator
.
pop
(
context
,
Dificultad
(
id:
(
widget
.
dificultad
==
null
)?
Uuid
().
v1
()
:
widget
.
dificultad
!.
id
,
limite:
int
.
parse
(
limiteController
.
text
),
maxIntentos:
int
.
parse
(
intentosController
.
text
),
nombre:
nombreController
.
text
,
)
);
}
},
child:
Text
(
"Hecho"
,
textScaler:
TextScaler
.
linear
(
1.1
),
)
),
),
),
SimpleDialogOption
(
onPressed:
()
=>
Navigator
.
pop
(
context
),
child:
Text
(
"Cancelar"
),
Expanded
(
child:
SimpleDialogOption
(
child:
TextButton
(
style:
TextButton
.
styleFrom
(
backgroundColor:
Color
.
fromARGB
(
255
,
150
,
10
,
0
),
foregroundColor:
Colors
.
white
,
disabledBackgroundColor:
Colors
.
black26
,
disabledForegroundColor:
Color
.
fromARGB
(
255
,
40
,
40
,
40
),
),
onPressed:
()
=>
Navigator
.
pop
(
context
),
child:
Text
(
'Cancelar'
,
textScaler:
TextScaler
.
linear
(
1.1
),
)
)
),
)
],
),
)
],
);
}
String
?
calculateErrorLimite
(
String
num
){
try
{
int
i
=
int
.
parse
(
num
);
if
(
i
<
2
){
return
'El límite mínimo es 2'
;
}
}
on
Exception
{
if
(
num
.
isEmpty
){
return
'Por favor, rellena este campo'
;
}
return
'El número es demasiado grande'
;
}
return
null
;
}
String
?
calculateErrorIntentos
(
String
num
){
try
{
int
i
=
int
.
parse
(
num
);
if
(
i
<
1
){
return
'El número mínimo de intentos es 1'
;
}
}
on
Exception
{
if
(
num
.
isEmpty
){
return
'Por favor, rellena este campo'
;
}
return
'El número es demasiado grande'
;
}
return
null
;
}
}
lib/widgets/peponator_mensaje_factory.dart
View file @
e0afdbe0
import
'package:peponator/widgets/peponator_mensaje.dart'
;
abstract
class
Pepon
e
torMensajeFactory
{
abstract
class
Pepon
a
torMensajeFactory
{
static
PeponatorMensaje
mensajeInicial
(
int
inferior
,
int
superior
)
{
return
PeponatorMensaje
(
message:
"¡Hola! Estoy pensando en un número del
$inferior
"
...
...
@@ -11,7 +11,7 @@ abstract class PeponetorMensajeFactory {
static
PeponatorMensaje
respuesta
(
int
intento
,
int
numAdivinar
)
{
StringBuffer
sb
=
StringBuffer
(
"El
$intento
está por "
);
sb
.
write
(
intento
<
numAdivinar
?
"debajo. "
:
"encima. "
);
sb
.
write
(
"
\n
Sigue intentándolo!"
);
sb
.
write
(
"
\n
¡
Sigue intentándolo!"
);
return
PeponatorMensaje
(
message:
sb
.
toString
());
}
...
...
@@ -19,15 +19,15 @@ abstract class PeponetorMensajeFactory {
static
PeponatorMensaje
victoria
(
int
numIntentos
,
int
numPistas
)
{
String
s1
=
numIntentos
>
1
?
"s"
:
""
;
String
s2
=
numPistas
>
1
?
"s"
:
""
;
StringBuffer
sb
=
StringBuffer
(
"
Felicidades!
Lo has conseguido en
$numIntentos
intento
$s1
"
);
sb
.
write
(
numPistas
==
0
?
"
y sin pistas!"
:
"usando
$numPistas
pistas
$s2
"
);
StringBuffer
sb
=
StringBuffer
(
"
¡Felicidades! ¡
Lo has conseguido en
$numIntentos
intento
$s1
"
);
sb
.
write
(
numPistas
==
0
?
"
y sin pistas!"
:
" usando
$numPistas
pista
$s2
!
"
);
return
PeponatorMensaje
(
message:
sb
.
toString
());
}
static
PeponatorMensaje
derrota
()
{
return
PeponatorMensaje
(
message:
"
Que pena! La próxima lo conseguirá
s"
message:
"
¡Qué pena! A la próxima seguro que lo consigue
s"
);
}
}
\ No newline at end of file
lib/widgets/widget_dificultad.dart
View file @
e0afdbe0
...
...
@@ -3,26 +3,42 @@ import 'package:provider/provider.dart';
import
'package:peponator/modelo/modelo.dart'
;
import
'package:peponator/widgets/dificultad_dialog.dart'
;
// TODO: HACER QUE SEA MENOS FEO
// TODO: MARCAR DIFICULTAD SELECCIONADA
class
DificultadWidget
extends
StatelessWidget
{
final
bool
_staticDif
;
final
int
indice
;
final
Color
?
backgroundColor
;
final
Color
?
foregroundColor
;
const
DificultadWidget
({
super
.
key
,
required
this
.
indice
});
const
DificultadWidget
({
required
super
.
key
,
staticDif
=
false
,
required
this
.
indice
,
this
.
backgroundColor
,
this
.
foregroundColor
}):
_staticDif
=
staticDif
;
bool
get
staticDif
=>
_staticDif
;
@override
Widget
build
(
BuildContext
context
)
{
ListaDificultad
listaDificultad
=
Provider
.
of
(
context
,
listen:
false
);
Dificultad
dificultad
=
listaDificultad
.
get
(
indice
);
bool
seleccionado
=
indice
==
listaDificultad
.
indiceSeleccionado
;
Color
bg
=
backgroundColor
??
((
Theme
.
of
(
context
).
brightness
==
Brightness
.
light
)?
Colors
.
grey
.
shade300
:
Colors
.
grey
.
shade800
);
return
Container
(
decoration:
seleccionado
?
BoxDecoration
(
border:
Border
.
all
(
color:
Colors
.
red
))
:
null
,
child:
GestureDetector
(
final
interior
=
Container
(
decoration:
seleccionado
?
BoxDecoration
(
border:
Border
.
all
(
color:
(
Theme
.
of
(
context
).
brightness
==
Brightness
.
dark
)?
Colors
.
white
:
Colors
.
black
,
width:
3.0
)
)
:
null
,
child:
InkWell
(
splashFactory:
InkRipple
.
splashFactory
,
onTap:
()
=>
listaDificultad
.
select
(
indice
),
onLongPress:
()
async
{
onLongPress:
(
staticDif
)?
null
:
(
)
async
{
Dificultad
?
cambiado
=
await
showDialog
<
Dificultad
>(
context:
context
,
...
...
@@ -32,27 +48,93 @@ class DificultadWidget extends StatelessWidget {
listaDificultad
.
update
(
indice
,
cambiado
);
}
},
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
Row
(
children:
[
const
SizedBox
(
width:
8
,
height:
16
,),
Text
(
dificultad
.
nombre
,
style:
Theme
.
of
(
context
).
textTheme
.
titleLarge
,
child:
Padding
(
padding:
EdgeInsets
.
only
(
top:
(
seleccionado
)?
13.0
:
16.0
,
bottom:
(
seleccionado
)?
13.0
:
16.0
),
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
SizedBox
(
width:
MediaQuery
.
of
(
context
).
size
.
width
/
3
-
3
,
child:
Padding
(
padding:
EdgeInsets
.
only
(
left:
(
seleccionado
)?
5.0
:
8.0
),
child:
Text
(
dificultad
.
nombre
,
textAlign:
TextAlign
.
left
,
style:
Theme
.
of
(
context
).
textTheme
.
titleLarge
?.
copyWith
(
color:
foregroundColor
),
),
),
),
SizedBox
(
width:
MediaQuery
.
of
(
context
).
size
.
width
/
3
-
3
,
child:
Text
(
'
${dificultad.maxIntentos}
intentos'
,
textAlign:
TextAlign
.
center
,
style:
TextStyle
(
color:
foregroundColor
),
)
),
SizedBox
(
width:
MediaQuery
.
of
(
context
).
size
.
width
/
3
-
3
,
child:
Padding
(
padding:
EdgeInsets
.
only
(
right:
(
seleccionado
)?
5.0
:
8.0
),
child:
Text
(
'1 -
${dificultad.limite}
'
,
textAlign:
TextAlign
.
right
,
style:
TextStyle
(
color:
foregroundColor
),
),
),
],
),
],
),
),
),
);
if
(
staticDif
){
return
Material
(
color:
bg
,
child:
interior
);
}
return
Material
(
color:
bg
,
child:
Dismissible
(
key:
key
!,
background:
Container
(
color:
Colors
.
red
,
alignment:
Alignment
.
centerLeft
,
child:
Padding
(
padding:
const
EdgeInsets
.
only
(
left:
8.0
),
child:
Icon
(
Icons
.
delete
,
color:
Colors
.
white
,
size:
32.0
,
),
Text
(
"Intentos:
${dificultad.maxIntentos}
"
),
Row
(
children:
[
Text
(
"1 -
${dificultad.limite}
"
),
const
SizedBox
(
width:
8
,
height:
16
,),
],
),
),
secondaryBackground:
Container
(
color:
Colors
.
red
,
alignment:
Alignment
.
centerRight
,
child:
Padding
(
padding:
const
EdgeInsets
.
only
(
right:
8.0
),
child:
Icon
(
Icons
.
delete
,
color:
Colors
.
white
,
size:
32.0
,
),
]
,
)
,
),
onDismissed:
(
direction
)
{
if
(
seleccionado
)
listaDificultad
.
select
(
0
);
listaDificultad
.
delete
(
indice
);
},
child:
interior
),
);
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment