Added more stuff for proper Object orientation, abstraction and so on

--- UI still not working gotta fix that
main
Lachfrosch 2024-06-16 23:56:48 +02:00
parent 3b8eb7c0e6
commit 480dbdacc6
8 changed files with 488 additions and 166 deletions

View File

@ -1,5 +1,5 @@
/// Enum for the stone colors. /// Enum for the stone colors.
enum Color { enum StoneColors {
red, red,
green, green,
blue, blue,
@ -7,4 +7,3 @@ enum Color {
pink, pink,
special, // not a "real" color; indicates that a stone is special (Not retarded, in a nice way, like mama always used to tell me i am her special child) special, // not a "real" color; indicates that a stone is special (Not retarded, in a nice way, like mama always used to tell me i am her special child)
} }

253
lib/game/Board.dart 100644
View File

@ -0,0 +1,253 @@
import 'dart:math';
import 'package:bubbletwist/game/stone/SpecialStone.dart';
import '../enums/StoneColor.dart';
import 'Game.dart';
import 'stone/PentaSpecialStone.dart';
import 'stone/Stone.dart';
import 'stone/StoneLocation.dart';
class Board {
static const int boardSize = 8;
final Game game;
late final List<List<Stone?>> stones =
List.filled(boardSize, List.filled(boardSize, Stone()));
Board(this.game) {
for (var row in stones) {
for (var stone in row) {
stone = Stone();
}
}
// The map generates with pairs of 3+ stones already in place.
// To fix that we just let the game remove all 3+ stone pairs until none are left
//while (checkBoard());
}
void updateBoard() {
game.updateBoard();
}
bool swapStones(StoneLocation sl1, StoneLocation sl2) {
// Check if the two stones that have been clicked are next to each other
if (!(sl1.row >= sl2.row - 1 && sl1.row <= sl2.row + 1) ||
!(sl1.column >= sl2.column - 1 && sl1.column <= sl2.column + 1)) {
return false; // If they are not next to each other just abort
}
final s1 = game.getStone(sl1);
final ss1 = s1 is PentaSpecialStone ? s1 : null;
if (ss1 != null) {
ss1.stoneColor = getStone(sl2)!.stoneColor;
performSpecialStone(ss1, sl1);
return true;
}
final s2 = game.getStone(sl2);
final ss2 = s2 is PentaSpecialStone ? s2 : null;
if (ss2 != null) {
ss2.stoneColor = getStone(sl1)!.stoneColor;
performSpecialStone(ss2, sl2);
return true;
}
// Swap stones
swap(sl1, sl2);
if (checkBoard()) {
// Check if swapping was a valid move
// If Swap is valid, Stones have been removed and others have to fall down
applyGravity();
while (checkBoard()) {
// Then we have to keep checking until no more 3+ stones are created by falling stones
applyGravity();
}
return true;
} else {
// If Swap is invalid, the stones get swapped back
swap(sl1, sl2);
return false;
}
}
bool checkBoard() {
bool stuffDeleted = false;
// FOR HORIZONTALS
for (int row = 0; row < boardSize; ++row) {
// First go through each row
int startPosition = 0;
int counter = 1;
for (int column = 1; column < boardSize; ++column) {
// And then through each column
if (stones[row][column] == null || stones[row][column - 1] == null) {
// Ignore deleted stones
continue;
}
final colorsAreSame = stones[row][column]!.stoneColor ==
stones[row][column - 1]!.stoneColor;
// If both stones have the same color increase counter
if (colorsAreSame) {
counter++;
}
// If they are not the same color or we reach the end of the Board,
// check the counter to see if the last 3+ stones had the same color
if (!colorsAreSame || column == boardSize - 1) {
if (counter >= 3) {
// And if we had 3+ stones, remove them
if (game.isRunning()) {
game.addPoints(counter);
game.addTime(3);
}
for (int k = 0; k < counter; ++k) {
final st = StoneLocation(row: row, column: startPosition + k);
removeStone(st);
stuffDeleted = true;
}
if (counter == 3 && game.isRunning()) {
//stones[row][startPosition] = TripleSpecialStone(this);
}
if (counter == 4 && game.isRunning()) {
//stones[row][startPosition] = QuadSpecialStone(this, false);
}
if (counter == 5 && game.isRunning()) {
stones[row][startPosition] = PentaSpecialStone(this);
}
}
// Reset counter and start position after each color change
counter = 1;
startPosition = column;
}
}
}
// FOR VERTICALS
for (int column = 0; column < boardSize; ++column) {
// First go through each column
int startPosition = 0;
int counter = 1;
for (int row = 1; row < boardSize; ++row) {
// And then through each row
if (stones[row][column] == null || stones[row - 1][column] == null) {
// Ignore deleted stones
continue;
}
// If both stones have the same color increase counter
final colorsAreSame = stones[row][column]!.stoneColor ==
stones[row - 1][column]!.stoneColor;
if (colorsAreSame) {
counter++;
}
// If they are not the same color or we reach the end of the Board,
// check the counter to see if the last 3+ stones had the same color
if (!colorsAreSame || row == boardSize - 1) {
if (counter >= 3) {
//And if we had 3+ stones, remove them
if (game.isRunning()) {
game.addPoints(counter);
game.addTime(3);
}
for (int k = 0; k < counter; ++k) {
final st = StoneLocation(row: startPosition + k, column: column);
removeStone(st);
stuffDeleted = true;
}
if (counter == 3 && game.isRunning()) {
//stones[startPosition][column] = TripleSpecialStone(this);
}
if (counter == 4 && game.isRunning()) {
//stones[startPosition][column] = QuadSpecialStone(this, true);
}
if (counter == 5 && game.isRunning()) {
stones[startPosition][column] = PentaSpecialStone(this);
}
}
// Reset counter and start position after each color change
counter = 1;
startPosition = row;
}
}
}
if (stuffDeleted) {
// If stuff has been deleted we have to update the Gui
game.updateBoard();
}
return stuffDeleted;
}
void removeStone(StoneLocation sl) {
if (sl.row < 0 ||
sl.row >= boardSize ||
sl.column < 0 ||
sl.column >= boardSize) {
return;
}
if (!game.isRunning()) {
// If game is not running, just give the stones a new color
stones[sl.row][sl.column]!.stoneColor =
StoneColors.values[Random().nextInt(StoneColors.values.length)];
} else {
// Otherwise mark stones as deleted
stones[sl.row][sl.column] = null;
}
}
void applyGravity() {
bool stonesHaveFallenDown;
// Move Stones down 1 row if the stone below them is marked as deleted
do {
stonesHaveFallenDown = false;
for (int column = 0; column < boardSize; ++column) {
for (int row = boardSize - 1; row > 0; --row) {
if (stones[row][column] == null) {
if (stones[row - 1][column] == null) {
continue;
}
stones[row][column] = stones[row - 1][column];
stones[row - 1][column] = null;
stonesHaveFallenDown = true;
}
}
}
} while (
stonesHaveFallenDown); // Continue doing so until all deleted Stones are at the top most position
// Then generate new stones that rain from the sky)
for (var row in stones) {
for (var stone in row) {
if (stone == null) {
stone = Stone();
}
}
}
game.updateBoard();
}
void performSpecialStone(SpecialStone ss, StoneLocation sl) {
//Delete Stones
ss.performSpecialStoneAction();
removeStone(sl);
//show Deleted Stones
updateBoard();
//Make Stones fall down into new "holes"
applyGravity();
updateBoard(); //Show the Gravity Effect
while (checkBoard()) {
// Then we have to keep checking until no more 3+ stones are created by falling stones
applyGravity();
}
}
void swap(StoneLocation sl1, StoneLocation sl2) {
final tmp = stones[sl1.row][sl1.column];
stones[sl1.row][sl1.column] = stones[sl2.row][sl2.column];
stones[sl2.row][sl2.column] = tmp;
game.updateBoard();
}
Stone? getStone(StoneLocation sl) {
return stones[sl.row][sl.column];
}
}

View File

@ -0,0 +1,121 @@
import 'dart:async';
import '../enums/StoneColor.dart';
import 'Board.dart';
import 'IGameConsumer.dart';
import 'stone/Stone.dart';
import 'stone/StoneLocation.dart';
class Game {
IGameConsumer gameConsumer;
late int timeInSeconds;
int startTime = 60; // hardcoded
int points = 0;
bool running = false;
late Board board;
late Timer counterTimer;
StoneLocation stoneToSwap = StoneLocation(row: -1, column: -1);
Game(this.gameConsumer) {
timeInSeconds = startTime;
board = Board(this);
}
int getPoints() => points;
int getTimeInSeconds() => timeInSeconds;
void addPoints(int pointsToAdd) {
if (timeInSeconds != 0 && running) {
points += pointsToAdd;
gameConsumer.updatePoints();
}
}
void addTime(int timeToAdd) {
if (timeInSeconds != 0 && running) {
timeInSeconds += timeToAdd;
gameConsumer.updateTime();
}
}
void endGame() {
running = false;
counterTimer?.cancel();
gameConsumer.gameStopped();
}
Future<void> countDown() async {
do {
timeInSeconds--;
gameConsumer.updateTime();
if (timeInSeconds > 0) {
await Future.delayed(Duration(seconds: 1));
}
} while (timeInSeconds > 0);
if (running) {
endGame();
}
}
StoneColors? getStoneColor(StoneLocation location) {
return board.getStone(location)?.getStoneColor();
}
Stone? getStone(StoneLocation location) {
return board.getStone(location);
}
void start() {
if (!running) {
timeInSeconds = startTime;
points = 0;
board = Board(this);
gameConsumer.updateStones();
counterTimer = Timer.periodic(Duration(seconds: 1), (_) => countDown());
gameConsumer.updatePoints();
running = true;
}
}
void stop() {
if (running) {
running = false;
timeInSeconds = 0;
counterTimer.cancel();
}
}
bool swapStones(StoneLocation sl1, StoneLocation sl2) {
return board.swapStones(sl1, sl2);
}
void updateBoard() {
if (running) {
gameConsumer.updateStones();
}
}
bool isRunning() => running;
bool swap(StoneLocation sl) {
if (stoneToSwap.row == -1) {
stoneToSwap = sl;
return false;
} else {
swapStones(stoneToSwap, sl);
stoneToSwap = StoneLocation(row: -1, column: -1);
return true;
}
}
bool performSpecialStone(StoneLocation sl) {
Stone? s = getStone(sl);
/*if (s is TriggerableSpecialStone) {
s.setActivationLocation(sl);
board.performSpecialStone(s, sl);
return true;
}*/
return false;
}
}

View File

@ -0,0 +1,27 @@
import '../Board.dart';
import 'SpecialStone.dart';
import 'StoneLocation.dart';
class PentaSpecialStone extends SpecialStone {
PentaSpecialStone(Board board) : super(board);
int getSpecialStoneNumber() {
return 5;
}
void performSpecialStoneAction() {
StoneLocation sl = StoneLocation(row: -1, column: -1);
for (int i = 0; i < Board.boardSize; ++i) {
for (int j = 0; j < Board.boardSize; ++j) {
sl.row = i;
sl.column = j;
if (this == board.getStone(sl)) {
continue;
}
if (board.getStone(sl)?.getStoneColor() == this.getStoneColor()) {
board.removeStone(sl);
}
}
}
}
}

View File

@ -0,0 +1,14 @@
import '../Board.dart';
import 'Stone.dart';
abstract class SpecialStone extends Stone {
final Board board;
SpecialStone(this.board);
/// Returns the associated number of the special stone
int getSpecialStoneNumber();
/// Performs the action of the special stone
void performSpecialStoneAction();
}

View File

@ -1,10 +1,11 @@
import 'dart:math'; import 'dart:math';
import '../../enums/Color.dart';
import '../../enums/StoneColor.dart';
/// Class to represent a stone on the board. /// Class to represent a stone on the board.
class Stone { class Stone {
/// Color of the stone /// Color of the stone
late Color color; late StoneColors stoneColor;
Stone() { Stone() {
setRandomColor(); setRandomColor();
@ -12,16 +13,17 @@ class Stone {
/// Getter for the color. /// Getter for the color.
/// \return color /// \return color
Color getColor() => color; StoneColors getStoneColor() => stoneColor;
/// Setter for the color /// Setter for the color
/// \param color /// \param color
void setColor(Color color) { void setColor(StoneColors stoneColor) {
this.color = color; this.stoneColor = stoneColor;
} }
/// method that sets a random color /// method that sets a random color
void setRandomColor() { void setRandomColor() {
color = Color.values[Random().nextInt(Color.values.length)]; stoneColor =
StoneColors.values[Random().nextInt(StoneColors.values.length)];
} }
} }

View File

@ -0,0 +1,10 @@
/// Simple structure that stores two ints to locate a stone on the board.
class StoneLocation {
int row;
int column;
StoneLocation({
required this.row,
required this.column,
});
}

View File

@ -1,7 +1,11 @@
import 'dart:math'; import 'package:bubbletwist/enums/StoneColor.dart';
import 'package:bubbletwist/game/Game.dart';
import 'package:bubbletwist/game/IGameConsumer.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'game/stone/Stone.dart';
import 'game/stone/StoneLocation.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
} }
@ -9,7 +13,7 @@ void main() {
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({super.key}); const MyApp({super.key});
final int gridSize = 12; final int gridSize = 8;
// This widget is the root of your application. // This widget is the root of your application.
@override @override
@ -20,7 +24,7 @@ class MyApp extends StatelessWidget {
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true, useMaterial3: true,
), ),
home: const MyHomePage(title: 'Flutter Demo Home Page', gridSize: 12), home: const MyHomePage(title: 'Flutter Demo Home Page', gridSize: 8),
); );
} }
} }
@ -44,44 +48,25 @@ class MyHomePage extends StatefulWidget {
State<MyHomePage> createState() => _MyHomePageState(); State<MyHomePage> createState() => _MyHomePageState();
} }
class _MyHomePageState extends State<MyHomePage> { class _MyHomePageState extends State<MyHomePage> implements IGameConsumer {
late List<List<int>> _grid; late List<List<Stone?>> _grid;
int _selectedRow = -1; late Game game;
int _selectedCol = -1; final int _score = 0;
int _score = 0;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_initializeGrid(); game = Game(this);
}
void _initializeGrid() {
_grid = List.generate(widget.gridSize, _grid = List.generate(widget.gridSize,
(_) => List.generate(widget.gridSize, (_) => Random().nextInt(5))); (index) => List.generate(widget.gridSize, (index) => Stone()));
_removeInitialMatches(); game.start();
}
void _removeInitialMatches() {
bool hasMatches;
do {
hasMatches = _removeMatches();
_applyGravity();
} while (hasMatches);
_score = 0;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Column( title: Text(widget.title),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Candy Crush'),
],
),
), ),
body: Center( body: Center(
child: Column( child: Column(
@ -90,12 +75,11 @@ class _MyHomePageState extends State<MyHomePage> {
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text( child: Text(
'Score: $_score', 'Score: $_score',
style: TextStyle(fontSize: 24), style: const TextStyle(fontSize: 24),
), ),
), ),
Expanded( Expanded(
child: GridView.builder( child: GridView.builder(
shrinkWrap: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: widget.gridSize, crossAxisCount: widget.gridSize,
), ),
@ -103,13 +87,18 @@ class _MyHomePageState extends State<MyHomePage> {
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
int row = index ~/ widget.gridSize; int row = index ~/ widget.gridSize;
int col = index % widget.gridSize; int col = index % widget.gridSize;
StoneLocation location = StoneLocation(row: row, column: col);
return GestureDetector( return GestureDetector(
onTap: () => _onTileTap(row, col), onTap: () {
setState(() {
//game.handleTap(row, col);
});
},
child: Container( child: Container(
margin: const EdgeInsets.all(2), margin: const EdgeInsets.all(2),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _getColor(_grid[row][col]),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
color: _getColorForStone(game.getStone(location)),
), ),
), ),
); );
@ -122,143 +111,50 @@ class _MyHomePageState extends State<MyHomePage> {
); );
} }
Color _getColorForStone(Stone? stone) {
Color _getColor(int value) { // Beispielhafte Farben basierend auf dem Stein.
switch (value) { switch (stone?.getStoneColor()) {
case 0: case StoneColors.red:
return Colors.red; return Colors.red;
case 1: case StoneColors.green:
return Colors.blue;
case 2:
return Colors.green; return Colors.green;
case 3: case StoneColors.blue:
return Colors.blue;
case StoneColors.yellow:
return Colors.yellow; return Colors.yellow;
case 4:
return Colors.orange;
default: default:
return Colors.white; return Colors.grey;
} }
} }
void _onTileTap(int row, int col) { @override
void gameStopped() {
// TODO: implement gameStopped
}
@override
void updatePoints() {
int _score = game.getPoints();
}
@override
void updateStones() {
setState(() { setState(() {
if (_selectedRow == -1 && _selectedCol == -1) { // _grid aktualisieren
_selectedRow = row;
_selectedCol = col;
} else {
if (_isValidMove(_selectedRow, _selectedCol, row, col)) {
_swapTiles(_selectedRow, _selectedCol, row, col);
while (_removeMatches()) {
_applyGravity();
}
}
_selectedRow = -1;
_selectedCol = -1;
}
});
}
bool _isValidMove(int row1, int col1, int row2, int col2) {
if ((row1 == row2 && (col1 - col2).abs() == 1) ||
(col1 == col2 && (row1 - row2).abs() == 1)) {
int temp = _grid[row1][col1];
_grid[row1][col1] = _grid[row2][col2];
_grid[row2][col2] = temp;
bool isValid = _findMatches(row1, col1).isNotEmpty ||
_findMatches(row2, col2).isNotEmpty;
_grid[row2][col2] = _grid[row1][col1];
_grid[row1][col1] = temp;
return isValid;
}
return false;
}
List<List<int>> _findMatches(int row, int col) {
int color = _grid[row][col];
List<List<int>> matches = [];
// Check horizontally
List<int> horizontalMatch = [];
for (int c = col; c >= 0 && _grid[row][c] == color; c--) {
horizontalMatch.add(c);
}
for (int c = col + 1; c < widget.gridSize && _grid[row][c] == color; c++) {
horizontalMatch.add(c);
}
if (horizontalMatch.length >= 3) {
for (int c in horizontalMatch) {
matches.add([row, c]);
}
}
// Check vertically
List<int> verticalMatch = [];
for (int r = row; r >= 0 && _grid[r][col] == color; r--) {
verticalMatch.add(r);
}
for (int r = row + 1; r < widget.gridSize && _grid[r][col] == color; r++) {
verticalMatch.add(r);
}
if (verticalMatch.length >= 3) {
for (int r in verticalMatch) {
matches.add([r, col]);
}
}
return matches;
}
bool _removeMatches() {
List<List<int>> allMatches = [];
int scoreIncrement = 0;
for (int row = 0; row < widget.gridSize; row++) { for (int row = 0; row < widget.gridSize; row++) {
for (int col = 0; col < widget.gridSize; col++) { for (int col = 0; col < widget.gridSize; col++) {
List<List<int>> matches = _findMatches(row, col); _grid[row][col] = game.getStone(StoneLocation(row: row, column: col));
if (matches.isNotEmpty) {
allMatches.addAll(matches);
scoreIncrement++; // Increment score based on number of matches
} }
} }
}
for (var match in allMatches) {
_grid[match[0]][match[1]] = -1; // Mark the tile as removed
}
setState(() {
_score += scoreIncrement;
}); });
return allMatches.isNotEmpty;
} }
@override
void _applyGravity() { void updateTime() {
for (int col = 0; col < widget.gridSize; col++) { // TODO: implement updateTime
int emptyRow = widget.gridSize - 1;
for (int row = widget.gridSize - 1; row >= 0; row--) {
if (_grid[row][col] != -1) {
_grid[emptyRow][col] = _grid[row][col];
if (emptyRow != row) {
_grid[row][col] = -1;
}
emptyRow--;
}
} }
for (int row = emptyRow; row >= 0; row--) { Game getGame() {
_grid[row][col] = Random().nextInt(5); return game;
}
}
}
void _swapTiles(int row1, int col1, int row2, int col2) {
int temp = _grid[row1][col1];
_grid[row1][col1] = _grid[row2][col2];
_grid[row2][col2] = temp;
} }
} }