How To Make 2048 Game With Flutter
Hello, this blog will record the making of 2048 game with Flutter. This game is fun to kill time. This game used to be popular 3-4 years ago when I was in college. At that time, the computer club I was in organized a game competition for youngsters at Science Week. If anyone does not know, they can play at 2048 game.
Start
Ready, New Flutter Pproject
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '2048',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: '2048'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Color(0xfffbf9f3),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'2048 Game',style: TextStyle(fontSize: 32,fontWeight: FontWeight.bold,color: Color(0xff888179)),
),
Text(
'using Flutter',style: TextStyle(fontSize: 18,color: Color(0xff888179))
),
],
),
)),
);
}
}
Create a table:
This game is played on a 4×4 grid.
Set a block size for each square in the grid.
const double BLOCK_SIZE = 80;
Draw a 4×4 grid.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Color(0xfffbf9f3),
child: Center(
child: Container(
decoration: BoxDecoration(
color: Color(0xffbaad9e),
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 6, color: Color(0xffbaad9e))),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
buildRow(),
buildRow(),
buildRow(),
buildRow(),
])),
)),
);
}
Row buildRow() {
return Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
buildBlockUnit(),
buildBlockUnit(),
buildBlockUnit(),
buildBlockUnit(),
]);
}
Container buildBlockUnit() {
return Container(
decoration: BoxDecoration(
color: Color(0xffcbc0b1),
borderRadius: BorderRadius.circular(4),
),
width: BLOCK_SIZE,
height: BLOCK_SIZE,
margin: EdgeInsets.all(3),
);
}
Create BlockUnit
BlockUnit is a class that stores values in table fields such as numbers, background colors, text colors.
class BlockUnit {
int value;
Color colorBackground;
Color colorText;
BlockUnit({this.value = 0, this.colorBackground, this.colorText});
}
So the grid of the game is a 2D BlockUnit.
List<List<BlockUnit>> table;
Set a default value for the table. I will ask to configure every channel = 2 first to test.
@override
void initState() {
initTable();
super.initState();
}
void initTable() {
table = List();
for (int row = 0; row < 4; row++) {
List<BlockUnit> list = List();
for (int col = 0; col < 4; col++) {
list.add(BlockUnit(
value: 2,
colorBackground: Color(0xffeee4d9),
colorText: Color(0xff776e64)));
}
table.add(list);
}
}
Concatenate variables in BlockUnit with Container Widget.
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Color(0xfffbf9f3),
child: Center(
child: Container(
decoration: BoxDecoration(
color: Color(0xffbaad9e),
borderRadius: BorderRadius.circular(8),
border: Border.all(width: 6, color: Color(0xffbaad9e))),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: buildTable())),
)),
);
}
List<Row> buildTable() {
List<Row> listRow = List();
for (int row = 0; row < 4; row++) {
listRow.add(
Row(mainAxisSize: MainAxisSize.min, children: buildRowBlockUnit(row)));
}
return listRow;
}
Container buildBlockUnit(int row, int col) {
return Container(
decoration: BoxDecoration(
color: table[row][col].colorBackground,
borderRadius: BorderRadius.circular(4),
),
width: BOX_SIZE,
height: BOX_SIZE,
margin: EdgeInsets.all(3),
child: Center(child: Text(
"" + table[row][col].value.toString(),
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: table[row][col].colorText
),)),
);
}
List<Widget> buildRowBlockUnit(int row) {
List<Widget> list = List();
for (int col = 0; col < 4; col++) {
list.add(buildBlockUnit(row, col));
}
return list;
}
try run
Create BlockUnitManager
BlockUnit in the game is not only the number 2, but the number 4 8 16 …. 2048, each of which has a different color. BlockUnitManager collects each BlockUnit value and block randomization ability. which I will try to write only type 2 , 4 , 8 first
in the random part Because the value of the block is 2^n, so I randomize the power of the random number instead, for example, randomly 0-6. If randomized 4, the resulting value is 2^4 = 16.
const int BLOCK_VALUE_NONE = 1;
const int BLOCK_VALUE_2 = 2;
const int BLOCK_VALUE_4 = 4;
const int BLOCK_VALUE_8 = 8;
class BlockUnitManager {
static BlockUnit randomBlock(){
Random random = Random();
int value = pow(2, random.nextInt(6)).toInt();
return create(value);
}
static BlockUnit create(int value) {
if(value == BLOCK_VALUE_2) {
return BlockUnit(
value: 2,
colorBackground: Color(0xffeee4d9),
colorText: Color(0xff776e64));
}else if(value == BLOCK_VALUE_4) {
return BlockUnit(
value: 4,
colorBackground: Color(0xffede0c8),
colorText: Color(0xff776e64));
}else if(value == BLOCK_VALUE_8) {
return BlockUnit(
value: 8,
colorBackground: Color(0xfff2b179),
colorText: Color(0xffffffff));
}else {
return BlockUnit(
value: 0,
colorBackground: Color(0xffccc0b3),
colorText: Color(0x00776e64));
}
}
}
and then define initTable() to randomize the block
void initTable() { table = List(); for (int row = 0; row < 4; row++) { List<BlockUnit> list = List(); for (int col = 0; col < 4; col++) { list.add(BlockUnitManager.randomBlock()); } table.add(list); } }
Control button
Later, make a button for it to move left, right, top and bottom so that it can be played and tested easily.
prepare function for creating control buttons which I will put at the bottom of the app
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Color(0xfffbf9f3),
child: Column(children: <Widget>[
Expanded(
child: Center(
child: Container(
...
)),
buildControlButton()
])),
);
}
There are 4 ways of scrolling, so it’s easy to declare a constant.
const int DIRECTION_UP = 0;
const int DIRECTION_LEFT = 1;
const int DIRECTION_RIGHT = 2;
const int DIRECTION_DOWN = 3;
Create 4 control buttons and for beauty, add curved edges
(actually this one I copied from the snake game)
Container buildControlButton() {
return Container(
padding: EdgeInsets.all(8),
color: Color(0xffede0c8),
child:
Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
buildControlDirectionButton(Icons.keyboard_arrow_left, DIRECTION_LEFT),
Container(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
buildControlDirectionButton(Icons.keyboard_arrow_up, DIRECTION_UP),
buildControlDirectionButton(
Icons.keyboard_arrow_down, DIRECTION_DOWN),
],
)),
buildControlDirectionButton(
Icons.keyboard_arrow_right, DIRECTION_RIGHT),
]),
);
}
GestureDetector buildControlDirectionButton(IconData icon, int direction) {
return GestureDetector(
onTap: () {
//
},
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft:
direction == DIRECTION_UP || direction == DIRECTION_LEFT
? Radius.circular(8)
: Radius.circular(0),
topRight:
direction == DIRECTION_UP || direction == DIRECTION_RIGHT
? Radius.circular(8)
: Radius.circular(0),
bottomLeft: direction == DIRECTION_LEFT ||
direction == DIRECTION_DOWN
? Radius.circular(8)
: Radius.circular(0),
bottomRight: direction == DIRECTION_RIGHT ||
direction == DIRECTION_DOWN
? Radius.circular(8)
: Radius.circular(0))),
child: Icon(icon, size: 48),
));
}
got the button
BlockUnit more
Let’s add details of different types of blocks, which I have done up to 2^11, which is 2048.
const int BLOCK_VALUE_16 = 16;
const int BLOCK_VALUE_32 = 32;
const int BLOCK_VALUE_64 = 64;
const int BLOCK_VALUE_128 = 128;
const int BLOCK_VALUE_256 = 256;
const int BLOCK_VALUE_512 = 512;
const int BLOCK_VALUE_1024 = 1024;
const int BLOCK_VALUE_2048 = 2048;
Configure background color Text color for all
static BlockUnit create(int value) {
if (value == BLOCK_VALUE_NONE) {
return BlockUnit(
value: value,
colorBackground: Color(0xffccc0b3),
colorText: Color(0x00ffffff),
fontSize: 26);
} else if (value == BLOCK_VALUE_2) {
return BlockUnit(
value: value,
colorBackground: Color(0xffeee4d9),
colorText: Color(0xff776e64));
} else if (value == BLOCK_VALUE_4) {
return BlockUnit(
value: value,
colorBackground: Color(0xffede0c8),
colorText: Color(0xff776e64));
} else if (value == BLOCK_VALUE_8) {
return BlockUnit(
value: value,
colorBackground: Color(0xfff2b179),
colorText: Color(0xffffffff));
} else if (value == BLOCK_VALUE_16) {
return BlockUnit(
value: value,
colorBackground: Color(0xfff49663),
colorText: Color(0xffffffff));
} else if (value == BLOCK_VALUE_32) {
return BlockUnit(
value: value,
colorBackground: Color(0xfff77b63),
colorText: Color(0xffffffff));
} else if (value == BLOCK_VALUE_64) {
return BlockUnit(
value: value,
colorBackground: Color(0xfff45639),
colorText: Color(0xffffffff));
} else if (value == BLOCK_VALUE_128) {
return BlockUnit(
value: value,
colorBackground: Color(0xffedce71),
colorText: Color(0xffffffff));
} else if (value == BLOCK_VALUE_256) {
return BlockUnit(
value: value,
colorBackground: Color(0xfff0cb63),
colorText: Color(0xffffffff));
} else if (value == BLOCK_VALUE_512) {
return BlockUnit(
value: value,
colorBackground: Color(0xffecc752),
colorText: Color(0xffffffff));
} else if (value == BLOCK_VALUE_1024) {
return BlockUnit(
value: value,
colorBackground: Color(0xffeec62c),
colorText: Color(0xffffffff));
} else if (value == BLOCK_VALUE_2048) {
return BlockUnit(
value: value,
colorBackground: Color(0xffeec309),
colorText: Color(0xffffffff));
} else {
return BlockUnit(
value: value,
colorBackground: Color(0xffeec309),
colorText: Color(0xffffffff));
}
}
}
Specify the number that you want to randomly randomize 0-11, then set the value to 12.
const int COUNT_BLOCK_TYPE =12;
static BoxUnit randomBlock() {
Random random = Random();
int value = pow(2, random.nextInt(COUNT_BLOCK_TYPE )).toInt();
return create(value);
}
arrived
Increase the size of the text
It looks like the block pixel size 1024 , 2048 is too big compared to the block size. Therefore, in the case of 4-digit numbers, we should reduce the size of the text a little.
Add a variable to set the text size to BlockUnit, set the default as well.
class BlockUnit {
...
double fontSize;
BlockUnit(
{this.value = 0,
this.colorBackground,
this.colorText,
this.fontSize = 32});
}
In the case of 1024,2048 make the size smaller.
...
} else if (value == BLOCK_VALUE_1024) {
return BlockUnit (
value: value,
colorBackground: Color(0xffeec62c),
colorText: Color(0xffffffff),
fontSize: 26);
} else if (value== BLOCK_VALUE_2048) {
return BlockUnit (
value: value,
colorBackground: Color(0xffeec309),
colorText: Color(0xffffffff),
fontSize: 26);
}
...
look a little better
Block shift
comes to the point of the game is to move the block
First of all, set The table randomizes only 2 numbers first by adjusting it so that random can receive max.
static BlockUnit randomBox({int maxPow = COUNT_BLOCK_TYPE}) {
Random random = Random();
int value = pow(2, random.nextInt(maxPow)).toInt();
return create(value);
}
We will randomly only an empty block with number 2, put maxPow =2,
it will randomly come out 0 and 1,
2^0 = 1 is an empty block,
2^1 = 2 is lock number 2.
void initTable() {
table = List();
for (int row = 0; row < 4; row++) {
List<BlockUnit > list = List();
for (int col = 0; col < 4; col++) {
list.add(BlockUnitManager.randomBox(maxPow: 2));
}
table.add(list);
}
}
Next, write a method for moving left-right. Let me tell you that the method I use is my own idea, so it's not the best practice. The
principle I use is BlockUnit, we store the value as a list, so it can be deleted and inserted
. free If found, move it to the last row and do it all. In
the case of the right, do it alternately.
will get about this
moveLeft() {
setState(() {
for (int row = 0; row < 4; row++) {
int col = 0;
int count = 0;
while (count < 4 && col< 4) {
if (table[row][col].value == Block_VALUE_NONE) {
BlockUnit blockEmpty = table[row][col];
table[row].removeAt(col);
table[row].add(blockEmpty);
count++;
} else {
col++;
}
}
}
});
}
moveRight() {
setState(() {
for (int row = 0; row < 4; row++) {
int col = 3;
int count = 0;
while (count < 4 && col< 4) {
if (table[row][col].value == Block_VALUE_NONE) {
BlockUnit blockEmpty = table[row][col];
table[row].removeAt(col);
table[row].insert(0,blockEmpty);
count++;
} else {
col--;
}
}
}
});
}
Now on the control button, let’s call the method.
GestureDetector buildControlDirectionButton(IconData icon, int direction) {
return GestureDetector(
onTap: () {
if(direction == DIRECTION_LEFT){
moveLeft();
}else if(direction == DIRECTION_RIGHT){
moveRight();
}
},
...
}
Can move left and right
Put blocks together
After shifting, the next step is Make blocks with the same number in the direction we move combine together. The method is simple as usual.
Here’s how: Let’s say it moves to the left. We use the same method described above first. so that all the numbered blocks move to the left
Then add the numbers of the same number next to each other. and delete one Once removed there will be spaces inserted. We then made a new left move so that all the blocks were aligned to the left. Finished.
There are only 3 types of block combinations because there are 4 channels,
including channels 0 – 1, 1 – 2, 2 – 3.
such as
moveLeft() {
setState(() {
for (int row = 0; row < 4; row++) {
moveAllBlockToLeft(row);
combineAllBlockToLeft(row);
moveAllBlockToLeft(row);
}
});
}
void combineAllBlockToLeft(int row) {
if (table[row][0].value == table[row][1].value &&
table[row][0].value != BLOCK_VALUE_NONE) {
table[row][0] = BlockUnitManager.create(table[row][0].value * 2);
table[row][1] = BlockUnitManager.create(BLOCK_VALUE_NONE);
}
if (table[row][1].value == table[row][2].value &&
table[row][1].value != BLOCK_VALUE_NONE) {
table[row][1] = BlockUnitManager.create(table[row][1].value * 2);
table[row][2] = BlockUnitManager.create(BLOCK_VALUE_NONE);
}
if (table[row][2].value == table[row][3].value &&
table[row][2].value != BLOCK_VALUE_NONE) {
table[row][2] = BlockUnitManager.create(table[row][2].value * 2);
table[row][3] = BlockUnitManager.create(BLOCK_VALUE_NONE);
}
}
void moveAllBlockToLeft(int row) {
int col = 0;
int count = 0;
// move all box in row to left
while (count < 4 && col< 4) {
if (table[row][col].value == BLOCK_VALUE_NONE) {
BlockUnit boxEmpty = table[row][col];
table[row].removeAt(col);
table[row].add(boxEmpty);
count++;
} else {
col++;
}
}
}
The case of right shifting is similar. same principle
moveRight() {
setState(() {
for (int row = 0; row < 4; row++) {
moveAllBlockToRight(row);
combineAllBlockToRight(row);
moveAllBlockToRight(row);
}
});
}
void moveAllBlockToRight(int row) {
int col = 3;
int count = 0;
while (count < 4 && col >= 0) {
if (table[row][col].value == BLOCK_VALUE_NONE) {
BlockUnit boxEmpty = table[row][col];
table[row].removeAt(col);
table[row].insert(0, boxEmpty);
count++;
} else {
col--;
}
}
}
void combineAllBlockToRight(int row) {
if (table[row][3].value == table[row][2].value &&
table[row][3].value != BLOCK_VALUE_NONE) {
table[row][3] = BlockUnitManager.create(table[row][3].value * 2);
table[row][2] = BlockUnitManager.create(BLOCK_VALUE_NONE);
}
if (table[row][2].value == table[row][1].value &&
table[row][2].value != BLOCK_VALUE_NONE) {
table[row][2] = BlockUnitManager.create(table[row][2].value * 2);
table[row][1] = BlockUnitManager.create(BLOCK_VALUE_NONE);
}
if (table[row][1].value == table[row][0].value &&
table[row][1].value != BLOCK_VALUE_NONE) {
table[row][1] = BlockUnitManager.create(table[row][1].value * 2);
table[row][0] = BlockUnitManager.create(BLOCK_VALUE_NONE);
}
}
Later, I made some moves downwards. This one is slightly different from moving left and right because moving left and right, we can delete, insert through the list of tables, but vertically. We can't do that because list values are stored as rows. vertical scroll It is necessary to retrieve the value from the table vertically and store it in the outside list first, then move the value from the outside list instead. Then save the value in the outside list back to the table.
void moveAllBlockToDown(int col) {
int row = 3;
int count = 0;
List<BlockUnit> listVertical = List();
listVertical.add(table[0][col]);
listVertical.add(table[1][col]);
listVertical.add(table[2][col]);
listVertical.add(table[3][col]);
while (count < 4 && row >= 0) {
if (listVertical[row].value == BLOCK_VALUE_NONE) {
BlockUnit blockEmpty= listVertical[row];
listVertical.removeAt(row);
listVertical.insert(0, blockEmpty);
count++;
} else {
row--;
}
}
for (int row = 0; row < 4; row++) {
table[row][col] = listVertical[row];
}
}
void combineAllBlockToDown(int col) {
if (table[3][col].value == table[2][col].value &&
table[3][col].value != BLOCK_VALUE_NONE) {
table[3][col] = BlockUnitManager.create(table[3][col].value * 2);
table[2][col] = BlockUnitManager.create(BLOCK_VALUE_NONE);
}
if (table[2][col].value == table[1][col].value &&
table[2][col].value != BLOCK_VALUE_NONE) {
table[2][col] = BlockUnitManager.create(table[2][col].value * 2);
table[1][col] = BlockUnitManager.create(BLOCK_VALUE_NONE);
}
if (table[1][col].value == table[0][col].value &&
table[1][col].value != BLOCK_VALUE_NONE) {
table[1][col] = BlockUnitManager.create(table[1][col].value * 2);
table[0][col] = BlockUnitManager.create(BLOCK_VALUE_NONE);
}
}
But the overall picture still uses the same principle, which is to move down, combine adjacent blocks. move down again
moveDown() {
setState(() {
for (int col = 0; col < 4; col++) {
moveAllBlockToDown(col);
combineAllBlockToDown(col);
moveAllBlockToDown(col);
}
});
}
Add a push button to work
GestureDetector buildControlDirectionButton(IconData icon, int direction) {
return GestureDetector(
onTap: () {
...
} else if (direction == DIRECTION_DOWN) {
moveDown();
}
},
...

In the case of moving up, it’s similar to moving down. Do it alternately.
void moveAllBlockToUp(int col) {
int row = 0;
int count = 0;
List<BlockUnit> listVertical = List();
listVertical.add(table[0][col]);
listVertical.add(table[1][col]);
listVertical.add(table[2][col]);
listVertical.add(table[3][col]);
while (count < 4 && row < 4) {
if (listVertical[row].value == BLOCK_VALUE_NONE) {
BlockUnit blockEmpty = listVertical[row];
listVertical.removeAt(row);
listVertical.add(blockEmpty );
count++;
} else {
row++;
}
}
for (int row = 0; row < 4; row++) {
table[row][col] = listVertical[row];
}
}
void combineAllBlockToUp(int col) {
if (table[0][col].value == table[1][col].value &&
table[0][col].value != BLOCK_VALUE_NONE) {
table[0][col] = BlockUnitManager.create(table[0][col].value * 2);
table[1][col] = BlockUnitManager.create(BLOCK_VALUE_NONE);
}
if (table[1][col].value == table[2][col].value &&
table[1][col].value != BLOCK_VALUE_NONE) {
table[1][col] = BlockUnitManager.create(table[1][col].value * 2);
table[2][col] = BlockUnitManager.create(BLOCK_VALUE_NONE);
}
if (table[2][col].value == table[3][col].value &&
table[2][col].value != BLOCK_VALUE_NONE) {
table[2][col] = BlockUnitManager.create(table[2][col].value * 2);
table[3][col] = BlockUnitManager.create(BLOCK_VALUE_NONE);
}
}
moveUp() {
setState(() {
for (int col = 0; col < 4; col++) {
moveAllBlockToUp(col);
combineAllBlockToUp(col);
moveAllBlockToUp(col);
}
});
}
GestureDetector buildControlDirectionButton(IconData icon, int direction) {
return GestureDetector(
onTap: () {
...
} else if (direction == DIRECTION_UP) {
moveUp();
}
},
...

Random new blocks added after sliding.
In this game, when we move blocks It will randomize a new block. put in the empty space Only blocks 2 -4 will be random,
which we need to keep the empty slots in the table. then randomly select
So create a class to store the position in the table.
class Coordinate {
int row;
int col;
Coordinate({this.row, this.col});
}
Create method in BlockUnitManager For random just block 2 and 4
class BlockUnitManager {
...
static BlockUnit randomSimpleBlock() {
Random random = Random();
int value = random.nextInt(2);
if (value == 0) {
return create(BLOCK_VALUE_2);
}
return create(BLOCK_VALUE_4);
}
}
Write the random blocks to the table. It collects empty spaces in the table. Then a channel will be selected. and then randomize the block
randomSimpleBlockToTable() {
List<Coordinate> listBlockUnitEmpty = List();
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
if (table[row][col].value == BLOCK_VALUE_NONE) {
listBlockUnitEmpty.add(Coordinate(row: row, col: col));
}
}
}
Random random = Random();
int index = random.nextInt(listBlockUnitEmpty.length);
int row = listBlockUnitEmpty[index].row;
int col = listBlockUnitEmpty[index].col;
table[row][col] = BlockUnitManager.randomSimpleBlock();
}
Set to call this command after pressing the scroll key.
GestureDetector buildControlDirectionButton(IconData icon, int direction) {
return GestureDetector(
onTap: () {
...
randomSimpleBlockToTable();
},
...
see results
Delay
when we press scroll It randomly puts blocks into it, which is so fast, so fast that we don’t know which one is random. So it has to delay a bit.
Create a variable that defines It’s delayed now.
bool delayMode = false;
add code to delay time i tried about 0.2 second is good
GestureDetector buildControlDirectionButton(IconData icon, int direction) {
return GestureDetector(
onTap: () {
if(!delayMode) {
delayMode = true;
if (direction == DIRECTION_LEFT) {
moveLeft();
} else if (direction == DIRECTION_RIGHT) {
moveRight();
} else if (direction == DIRECTION_DOWN) {
moveDown();
} else if (direction == DIRECTION_UP) {
moveUp();
}
Future.delayed(const Duration(milliseconds: 200), () {
setState(() {
delayMode = false;
randomSimpleBlockToTable();
});
});
}
},..
Solve problems
The next problem is if we move the block and then we push it in a direction that no block can move. But the game still adds random blocks. In fact, if no block moves, it will not randomly add. Otherwise the game will end difficult.
The solution is that we need to check if block shift or block merge is happening or not. If it happens, it’s random.
Which we have to adjust the scrolling code a little, return boolean to see if scrolling or merging happened?
Example of left shift
bool moveLeft() {
bool move = false;
setState(() {
for (int row = 0; row < 4; row++) {
bool moveByNormal = moveAllBlockToLeft(row);
bool moveByCombine = combineAllBlockToLeft(row);
moveAllBlockToLeft(row);
move = move || moveByNormal || moveByCombine;
}
});
return move;
}
bool combineAllBlockToLeft(int row) {
bool move= false;
if (table[row][0].value == table[row][1].value &&
table[row][0].value != BLOCK_VALUE_NONE) {
table[row][0] = BlockUnitManager.create(table[row][0].value * 2);
table[row][1] = BlockUnitManager.create(BLOCK_VALUE_NONE);
move = true;
}
if (table[row][1].value == table[row][2].value &&
table[row][1].value != BLOCK_VALUE_NONE) {
table[row][1] = BlockUnitManager.create(table[row][1].value * 2);
table[row][2] = BlockUnitManager.create(BLOCK_VALUE_NONE);
move = true;
}
if (table[row][2].value == table[row][3].value &&
table[row][2].value != BLOCK_VALUE_NONE) {
table[row][2] = BlockUnitManager.create(table[row][2].value * 2);
table[row][3] = BlockUnitManager.create(BLOCK_VALUE_NONE);
move = true;
}
return move;
}
bool moveAllBlockToLeft(int row) {
bool move = false;
int col = 0;
int count = 0;
// move all BLOCK in row to left
while (count < 4 && col < 4) {
if (table[row][col].value == BLOCK_VALUE_NONE) {
if(col < 4 - 1) {
if (table[row][col + 1].value != BLOCK_VALUE_NONE) {
move = true;
}
}
BlockUnit blockEmpty = table[row][col];
table[row].removeAt(col);
table[row].add(blockEmpty);
count++;
} else {
col++;
}
}
return move;
}
If the move method returns true , then a move or combination occurs, a new random item can be generated.
GestureDetector buildControlDirectionButton(IconData icon, int direction) {
return GestureDetector(
onTap: () {
if(!delayMode) {
delayMode = true;
bool move= true;
if (direction == DIRECTION_LEFT) {
move = moveLeft();
} else if (direction == DIRECTION_RIGHT) {
move = moveRight();
} else if (direction == DIRECTION_DOWN) {
move = moveDown();
} else if (direction == DIRECTION_UP) {
move = moveUp();
}
Future.delayed(const Duration(milliseconds: 200), () {
setState(() {
delayMode = false;
if(move) {
randomSimpleBlockToTable();
}
});
});
}
},
...
make a scoreboard
There is also a lack of a menu to display game scores. and a button to start a new game
Announcement of score variables
int score = 0;
Create a menu bar for the game. which I will place on the top of the screen Add a button to start a new game as well.
Container buildMenu() {
return Container(
padding: EdgeInsets.only(top: 36, bottom: 12, left: 16, right: 16),
color: Color(0xffede0c8),
child:
Row(mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GestureDetector(onTap: () {
//
},
child: Container(constraints: BoxConstraints(minWidth: 120),
decoration: BoxDecoration(color: Color(0xff8f7a66),
borderRadius: BorderRadius.circular(4)),
padding: EdgeInsets.all(12),
child: Column(children: <Widget>[
Text("New Game", style: TextStyle(fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.white))
]))),
Expanded(child: Container()),
Container(constraints: BoxConstraints(minWidth: 120),
decoration: BoxDecoration(color: Color(0xffbbada0),
borderRadius: BorderRadius.circular(4)),
padding: EdgeInsets.all(4),
child: Column(children: <Widget>[
Text("SCORE", style: TextStyle(fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white)),
Text("$score", style: TextStyle(fontSize: 26,
fontWeight: FontWeight.bold,
color: Colors.white))
]))
]),
);
}
put the menu on top
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Color(0xfffbf9f3),
child: Column(children: <Widget>[
buildMenu(),
Expanded(
...
),
buildControlButton()
])),
);
}
Later added more points. When blocking together
bool combineAllBlockToLeft(int row) {
bool move = false;
if (table[row][0].value == table[row][1].value &&
table[row][0].value != BLOCK_VALUE_NONE) {
table[row][0] = BlockUnitManager.create(table[row][0].value * 2);
table[row][1] = BlockUnitManager.create(BLOCK_VALUE_NONE);
score += table[row][0].value;
move = true;
}
...
result
Start a new game
Let’s collapse. Initializing the game is the initGame() method.
@override
void initState() {
initGame();
super.initState();
}
void initGame() {
score = 0;
initTable();
randomSimpleBlockToTable();
randomSimpleBlockToTable();
}
then restart() is initGame()
void restart() {
initGame();
}
Game over
Make a screen that shows the end of the game. It will be used as a dialog that says Game Over and has a button to restart the game. First,
prepare the dialog screen.
void showGameOverDialog() {
// flutter defined function
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
content: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Text("Game Over ):",
style: TextStyle(
fontSize: 32,
color: Colors.pink[800],
fontWeight: FontWeight.bold)),
RaisedButton(
padding: EdgeInsets.symmetric(horizontal: 18, vertical: 6),
color: Color(0xff8f7a66),
child: Text("Play again",
style: TextStyle(
fontSize: 22,
color: Colors.white,
fontWeight: FontWeight.bold)),
onPressed: () {
Navigator.of(context).pop();
restart();
},
)
]));
},
);
}
Write a method to check whether Game Over or not.
The method is that there must be no spaces in the table. and must not have the same number next to each other both vertically and horizontally
If the conditions are met, the game is over.
bool isGameOver() {
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 4; col++) {
if (table[row][col].value == BLOCK_VALUE_NONE) {
return false;
}
if (col < 4 - 1) {
if (table[row][col].value == table[row][col + 1].value) {
return false;
}
}
}
}
for (int col = 0; col < 4; col++) {
for (int row = 0; row < 4; row++) {
if (table[row][col].value == BLOCK_VALUE_NONE) {
return false;
}
if (row < 4 - 1) {
if (table[row][col].value == table[row + 1][col].value) {
return false;
}
}
}
}
return true;
}
then check the conditions when we press the scroll button
GestureDetector buildControlDirectionButton(IconData icon, int direction) { return GestureDetector( onTap: () { if (!delayMode) { ... Future.delayed(const Duration(milliseconds: 200), () { if (move) { delayMode = false; setState(() { randomSimpleBlockToTable(); if (isGameOver()) { showGameOverDialog(); } }); } else { delayMode = false; if (isGameOver ()) { showGameOverDialog (); } } }); ...
Finished
It’s done. This is the whole 2048 game in rough form. Written in Flutter. In general, it’s fun. It’s not that complicated.
Code on Github
https://github.com/benznest/2048-game-flutter