├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── fonts ├── Ewert-Regular.ttf ├── LondrinaShadow-Regular.ttf └── VastShadow-Regular.ttf ├── lib ├── database_helper.dart └── main.dart ├── pubspec.yaml ├── test └── widget_test.dart └── word_puzzle_game.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | /android 43 | /ios 44 | /build 45 | /linux 46 | /web 47 | /windows 48 | /macos 49 | /macos 50 | /pubspec.lock 51 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "17025dd88227cd9532c33fa78f5250d548d87e9a" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 17 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 18 | - platform: android 19 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 20 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 21 | - platform: ios 22 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 23 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 24 | - platform: linux 25 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 26 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 27 | - platform: macos 28 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 29 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 30 | - platform: web 31 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 32 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 33 | - platform: windows 34 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 35 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # word_puzzle 2 | 3 | A new Flutter Word Puzzle Game. 4 | 5 | ![alt text](https://github.com/Shamir-Mateo/Word-Puzzle-Game/blob/master/word_puzzle_game.png?raw=true) 6 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /fonts/Ewert-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reddev0110/Word-Puzzle-Game/ab08a1958327e191b6c7e43a8d08fd3be533f1f2/fonts/Ewert-Regular.ttf -------------------------------------------------------------------------------- /fonts/LondrinaShadow-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reddev0110/Word-Puzzle-Game/ab08a1958327e191b6c7e43a8d08fd3be533f1f2/fonts/LondrinaShadow-Regular.ttf -------------------------------------------------------------------------------- /fonts/VastShadow-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reddev0110/Word-Puzzle-Game/ab08a1958327e191b6c7e43a8d08fd3be533f1f2/fonts/VastShadow-Regular.ttf -------------------------------------------------------------------------------- /lib/database_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'package:path/path.dart'; 3 | import 'package:sqflite/sqflite.dart'; 4 | import 'package:path_provider/path_provider.dart'; 5 | 6 | // database table and column names 7 | const String databaseName = 'Word-Puzzle.db'; 8 | const String tableCategories = 'categories'; 9 | const String tableWords = 'words'; 10 | 11 | const String columnCategory = 'category'; 12 | const String columnTime = 'time'; 13 | const String columnWord = 'word'; 14 | 15 | // Data model class for categories 16 | class ACategory { 17 | String category; 18 | String time; 19 | 20 | ACategory(this.category, {this.time = '00:00'}); 21 | 22 | ACategory.fromMap(Map map) 23 | : category = map[columnCategory], 24 | time = map[columnTime]; 25 | 26 | Map toMap() { 27 | return { 28 | columnCategory: category, 29 | columnTime: time, 30 | }; 31 | } 32 | 33 | ACategory.withTime(this.category, this.time) { 34 | this.category = category; 35 | this.time = time; 36 | } 37 | } 38 | 39 | // Data model class for words 40 | class AWord { 41 | String category; 42 | String word; 43 | 44 | AWord(this.category, this.word); 45 | 46 | AWord.fromMap(Map map) 47 | : category = map[columnCategory], 48 | word = map[columnWord]; 49 | 50 | Map toMap() { 51 | return { 52 | columnCategory: category, 53 | columnWord: word, 54 | }; 55 | } 56 | } 57 | 58 | // Singleton class to manage the database 59 | class DatabaseHelper { 60 | static final DatabaseHelper instance = DatabaseHelper._privateConstructor(); 61 | static Database? _database; 62 | 63 | DatabaseHelper._privateConstructor(); 64 | 65 | Future get database async { 66 | if (_database != null) return _database!; 67 | _database = await _initDatabase(); 68 | return _database!; 69 | } 70 | 71 | Future _initDatabase() async { 72 | Directory documentsDirectory = await getApplicationDocumentsDirectory(); 73 | String path = join(documentsDirectory.path, databaseName); 74 | return await openDatabase(path, version: 1, onCreate: _onCreate); 75 | } 76 | 77 | Future _onCreate(Database db, int version) async { 78 | await db.execute(''' 79 | CREATE TABLE $tableCategories ( 80 | $columnCategory TEXT PRIMARY KEY, 81 | $columnTime TEXT DEFAULT '0.0s' 82 | ) 83 | '''); 84 | await db.execute(''' 85 | CREATE TABLE $tableWords ( 86 | $columnCategory TEXT NOT NULL, 87 | $columnWord TEXT NOT NULL 88 | ) 89 | '''); 90 | } 91 | 92 | Future databaseExists(String path) => 93 | databaseFactory.databaseExists(path); 94 | 95 | Future initializeDatabase() async { 96 | Directory documentsDirectory = await getApplicationDocumentsDirectory(); 97 | String path = join(documentsDirectory.path, databaseName); 98 | bool exists = await databaseExists(path); 99 | 100 | if (exists) return 0; 101 | 102 | print("Initializing database"); 103 | Database db = await database; 104 | await db.rawDelete('DELETE FROM $tableCategories'); 105 | await db.rawDelete('DELETE FROM $tableWords'); 106 | 107 | List categories = [ 108 | 'Face', 109 | 'Fruits', 110 | 'Vegetables', 111 | 'Colors', 112 | 'Occupations', 113 | 'Musical Instruments', 114 | 'Flowers', 115 | 'Bar', 116 | 'Bathroom', 117 | 'House', 118 | 'Makeup', 119 | 'Family' 120 | ]; 121 | 122 | List> words = [ 123 | [ 124 | 'hair', 125 | 'skin', 126 | 'eyebrow', 127 | 'eyelash', 128 | 'ear', 129 | 'nose', 130 | 'mole', 131 | 'lip', 132 | 'chin', 133 | 'forehead', 134 | 'temple', 135 | 'eye', 136 | 'cheek', 137 | 'nostril', 138 | 'mouth' 139 | ], 140 | [ 141 | 'orange', 142 | 'lime', 143 | 'lemon', 144 | 'apricot', 145 | 'watermelon', 146 | 'grapes', 147 | 'raspberry', 148 | 'blackberry', 149 | 'strawberry', 150 | 'grapefruit', 151 | 'peach', 152 | 'plum', 153 | 'mango', 154 | 'banana', 155 | 'papaya' 156 | ], 157 | [ 158 | 'corn', 159 | 'green bean', 160 | 'lettuce', 161 | 'cucumber', 162 | 'zucchini', 163 | 'pumpkin', 164 | 'pepper', 165 | 'carrot', 166 | 'asparagus', 167 | 'potato', 168 | 'onion', 169 | 'artichoke', 170 | 'radish', 171 | 'broccoli', 172 | 'celery' 173 | ], 174 | [ 175 | 'red', 176 | 'blue', 177 | 'green', 178 | 'yellow', 179 | 'orange', 180 | 'purple', 181 | 'teal', 182 | 'pink', 183 | 'gray', 184 | 'white', 185 | 'black', 186 | 'brown' 187 | ], 188 | [ 189 | 'lawyer', 190 | 'accountant', 191 | 'scientist', 192 | 'teacher', 193 | 'pilot', 194 | 'doctor', 195 | 'actress', 196 | 'dancer', 197 | 'musician', 198 | 'photographer', 199 | 'painter', 200 | 'librarian', 201 | 'receptionist', 202 | 'travel agent', 203 | 'journalist' 204 | ], 205 | [ 206 | 'piano', 207 | 'saxophone', 208 | 'guitar', 209 | 'violin', 210 | 'viola', 211 | 'harp', 212 | 'cello', 213 | 'french horn', 214 | 'tuba', 215 | 'drum', 216 | 'trumpet', 217 | 'keyboard', 218 | 'mandolin', 219 | 'bass', 220 | 'flute' 221 | ], 222 | [ 223 | 'lily', 224 | 'flowers', 225 | 'carnation', 226 | 'tulip', 227 | 'orchid', 228 | 'gladiolus', 229 | 'daisy', 230 | 'acacia', 231 | 'chrysanthemum', 232 | 'iris', 233 | 'rose', 234 | 'freesia', 235 | 'gerbera' 236 | ], 237 | [ 238 | 'martini', 239 | 'cocktail', 240 | 'wine', 241 | 'beer', 242 | 'gin', 243 | 'whiskey', 244 | 'scotch', 245 | 'rum' 246 | ], 247 | [ 248 | 'sink', 249 | 'bathtub', 250 | 'shower', 251 | 'shower head', 252 | 'toilet', 253 | 'toilet brush', 254 | 'drain', 255 | 'sponge', 256 | 'deodorant', 257 | 'mouthwash', 258 | 'toothpaste', 259 | 'toothbrush', 260 | 'aftershave', 261 | 'soap', 262 | 'bubble bath' 263 | ], 264 | [ 265 | 'window', 266 | 'front door', 267 | 'chimney', 268 | 'roof', 269 | 'sidewalk', 270 | 'gutter', 271 | 'dormer window', 272 | 'shutter', 273 | 'porch', 274 | 'shingle', 275 | 'balcony', 276 | 'foyer', 277 | 'doorbell', 278 | 'handrail', 279 | 'staircase' 280 | ], 281 | [ 282 | 'hair dye', 283 | 'eyeshadow', 284 | 'mascara', 285 | 'eyeliner', 286 | 'blusher', 287 | 'foundation', 288 | 'lipstick', 289 | 'lip gloss', 290 | 'face powder', 291 | 'tweezers', 292 | 'mirror', 293 | 'concealer', 294 | 'brush', 295 | 'lip liner' 296 | ], 297 | [ 298 | 'grandmother', 299 | 'grandfather', 300 | 'mother', 301 | 'father', 302 | 'uncle', 303 | 'aunt', 304 | 'brother', 305 | 'sister', 306 | 'son', 307 | 'daughter', 308 | 'cousin', 309 | 'grandson', 310 | 'granddaughter', 311 | 'niece', 312 | 'nephew' 313 | ], 314 | ]; 315 | 316 | for (String category in categories) { 317 | await db.insert(tableCategories, ACategory(category).toMap()); 318 | } 319 | 320 | for (int i = 0; i < words.length; i++) { 321 | for (String word in words[i]) { 322 | await db.insert( 323 | tableWords, 324 | AWord(categories[i], word.toUpperCase().replaceAll(" ", "")) 325 | .toMap()); 326 | } 327 | } 328 | return 1; 329 | } 330 | 331 | Future dropTables() async { 332 | Database db = await database; 333 | await db.execute('DROP TABLE IF EXISTS $tableCategories'); 334 | await db.execute('DROP TABLE IF EXISTS $tableWords'); 335 | } 336 | 337 | Future insertCategory(ACategory category) async { 338 | Database db = await database; 339 | return await db.insert(tableCategories, category.toMap()); 340 | } 341 | 342 | Future queryCategory(String category) async { 343 | Database db = await database; 344 | List maps = await db.query( 345 | tableCategories, 346 | columns: [columnCategory, columnTime], 347 | where: '$columnCategory = ?', 348 | whereArgs: [category], 349 | ); 350 | if (maps.isNotEmpty) { 351 | return ACategory.fromMap(maps.first); 352 | } 353 | return null; 354 | } 355 | 356 | Future> getAllCategories() async { 357 | final Database db = await database; 358 | final List> maps = await db.query(tableCategories); 359 | return List.generate(maps.length, (i) { 360 | return ACategory.fromMap(maps[i]); 361 | }); 362 | } 363 | 364 | Future> getWords(String category) async { 365 | final Database db = await database; 366 | List maps = await db.query( 367 | tableWords, 368 | columns: [columnCategory, columnWord], 369 | where: '$columnCategory = ?', 370 | whereArgs: [category], 371 | ); 372 | return List.generate(maps.length, (i) { 373 | return AWord.fromMap(maps[i]); 374 | }); 375 | } 376 | 377 | Future getCategoryCount() async { 378 | Database db = await database; 379 | return Sqflite.firstIntValue( 380 | await db.rawQuery('SELECT COUNT(*) FROM $tableCategories')) ?? 381 | 0; 382 | } 383 | 384 | Future getAllWordsCount() async { 385 | Database db = await database; 386 | return Sqflite.firstIntValue( 387 | await db.rawQuery('SELECT COUNT(*) FROM $tableWords')) ?? 388 | 0; 389 | } 390 | 391 | Future deleteCategory(String category) async { 392 | final db = await database; 393 | return await db.delete( 394 | tableCategories, 395 | where: '$columnCategory = ?', 396 | whereArgs: [category], 397 | ); 398 | } 399 | 400 | Future updateBestTime(String category, int seconds) async { 401 | final db = await database; 402 | ACategory? previousCategory = await queryCategory(category); 403 | if (previousCategory != null) { 404 | int lastSeconds = int.parse(previousCategory.time.split(':')[1]) + 405 | int.parse(previousCategory.time.split(':')[0]) * 60; 406 | if (lastSeconds > seconds || lastSeconds == 0) { 407 | String mm = (seconds ~/ 60).toString().padLeft(2, '0'); 408 | String ss = (seconds % 60).toString().padLeft(2, '0'); 409 | return await db.update( 410 | tableCategories, 411 | ACategory.withTime(category, "$mm:$ss").toMap(), 412 | where: '$columnCategory = ?', 413 | whereArgs: [category], 414 | ); 415 | } 416 | } 417 | return 0; 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:flutter/material.dart'; 3 | //import 'package:word_puzzle/words_helper.dart'; 4 | import 'package:word_puzzle/database_helper.dart'; 5 | import 'package:flutter/widgets.dart'; 6 | import 'dart:math'; 7 | import 'dart:async'; 8 | 9 | class Todo { 10 | final String title; 11 | final String description; 12 | Todo(this.title, this.description); 13 | } 14 | 15 | void main() { 16 | runApp(MaterialApp( 17 | title: 'Word Puzzle', 18 | home: SelectWidget(), 19 | )); 20 | } 21 | 22 | class SelectWidget extends StatefulWidget { 23 | SelectWidget({Key? key}) : super(key: key); 24 | @override 25 | _SelectWidgetState createState() => _SelectWidgetState(); 26 | } 27 | 28 | double deviceWidth = 0.0; 29 | double deviceLogicalWidth = 0.0; 30 | double devicePixelRatio = 0.0; 31 | 32 | class _SelectWidgetState extends State { 33 | List categories = []; 34 | List> allWords = []; 35 | List colorList = []; 36 | 37 | getHeightWidth(context) { 38 | // Get the logical width 39 | deviceLogicalWidth = MediaQuery.of(context).size.width; 40 | devicePixelRatio = MediaQuery.of(context).devicePixelRatio; 41 | deviceWidth = deviceLogicalWidth * devicePixelRatio; 42 | 43 | print("DeviceWidth : $deviceWidth, devicePixelRati : $devicePixelRatio"); 44 | } 45 | 46 | _navigateAndDisplaySelection(BuildContext context, int index) async { 47 | await Navigator.push( 48 | context, 49 | MaterialPageRoute( 50 | builder: (context) => GameWidget( 51 | category: categories[index].category, words: allWords[index], bestTime: categories[index].time)), 52 | ); 53 | setState(() {}); 54 | } 55 | 56 | @override 57 | Widget build(BuildContext context) { 58 | getHeightWidth(context); 59 | //print(deviceWidth); 60 | return FutureBuilder( 61 | future: _initializeDatabase(), // function where you call your api 62 | builder: (BuildContext context, AsyncSnapshot snapshot) { 63 | // AsyncSnapshot 64 | if (snapshot.connectionState == ConnectionState.waiting) { 65 | return Scaffold( 66 | body: Center(child: CircularProgressIndicator()), 67 | ); 68 | } else { 69 | if (snapshot.hasError) 70 | return Center(child: Text('Error: ${snapshot.error}')); 71 | else 72 | return Scaffold( 73 | /* appBar: AppBar( 74 | title: Text('Word Puzzle'), 75 | ),*/ 76 | body: ListView.builder( 77 | itemCount: categories.length, 78 | itemBuilder: (context, index) { 79 | /*return ListTile( 80 | leading: Icon(Icons.wb_sunny), 81 | title: Text(categories[index].category), 82 | subtitle: Text(categories[index].time), 83 | onTap: () { 84 | _navigateAndDisplaySelection(context, index); 85 | }, 86 | );*/ 87 | return Container( 88 | // height: 100, 89 | color: Colors.white, 90 | child: Card( 91 | child: InkWell( 92 | onTap: () { 93 | _navigateAndDisplaySelection(context, index); 94 | }, 95 | hoverColor: Colors.blue, 96 | child: Container( 97 | margin: const EdgeInsets.only(left: 3.0, right: 3.0), 98 | // color: Colors.red, 99 | decoration: BoxDecoration( 100 | borderRadius: BorderRadius.circular(5), 101 | color: colorList[index % colorList.length], 102 | //boxShadow: [ BoxShadow(color: Colors.pink, spreadRadius: 2), ], 103 | ), 104 | child: Padding( 105 | padding: EdgeInsets.all(10), 106 | child: Column( 107 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 108 | children: [ 109 | Row(children: [ 110 | Text(categories[index].category, 111 | style: new TextStyle( 112 | fontSize: 25.0, 113 | // fontFamily: 'listFont', 114 | color: Colors.white, 115 | fontWeight: FontWeight.bold, 116 | )) 117 | ]), 118 | Row( 119 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 120 | children: [ 121 | Row( 122 | children: [ 123 | Text(" "), 124 | Icon(IconData(57746, fontFamily: 'MaterialIcons'), color: Colors.white), 125 | Text(" "), 126 | Text(categories[index].time, 127 | style: new TextStyle(fontSize: 20, color: Colors.white)), 128 | ], 129 | ), 130 | Row( 131 | children: [ 132 | Icon(IconData(58683, fontFamily: 'MaterialIcons'), color: Colors.white), 133 | Text(allWords[index].length.toString(), 134 | style: new TextStyle(fontSize: 20, color: Colors.white)), 135 | Text(" "), 136 | ], 137 | ), 138 | ], 139 | ) 140 | ], 141 | ), 142 | ), 143 | ), 144 | ), 145 | )); 146 | }, 147 | ), 148 | ); 149 | } 150 | }, 151 | ); 152 | } 153 | 154 | Future _initializeDatabase() async { 155 | DatabaseHelper helper = DatabaseHelper.instance; 156 | await helper.initializeDatabase(); 157 | int ccount = await helper.getCategoryCount(); 158 | int wcount = await helper.getAllWordsCount(); 159 | categories = await helper.getAllCategories(); 160 | 161 | colorList.add(Colors.deepOrange[400]); 162 | colorList.add(Colors.orangeAccent[400]); 163 | colorList.add(Colors.purpleAccent[400]); 164 | colorList.add(Colors.redAccent[400]); 165 | colorList.add(Colors.lightGreen[900]); 166 | colorList.add(Colors.indigoAccent); 167 | colorList.add(Colors.redAccent[400]); 168 | 169 | for (int i = 0; i < categories.length; i++) { 170 | allWords.add(await helper.getWords(categories[i].category)); 171 | } 172 | print("Database Loaded.. CCount : [$ccount] WCount[$wcount]"); 173 | } 174 | } 175 | 176 | List> gridMap = []; 177 | double gridSize = 20.0; 178 | double gridLogicalSize = 20.0; 179 | List touchItems = []; 180 | 181 | List> foundMap = []; 182 | List foundColor = []; 183 | List foundWords = []; 184 | 185 | class GameWidget extends StatefulWidget { 186 | final String category; 187 | final List words; 188 | final String bestTime; 189 | 190 | GameWidget({Key? key, required this.category, required this.words, required this.bestTime}) : super(key: key); 191 | @override 192 | _GameWidgetState createState() => _GameWidgetState(); 193 | } 194 | 195 | class _GameWidgetState extends State { 196 | int gridW = 15; 197 | int gridH = 15; 198 | List wordsList = []; 199 | 200 | Size? panSize; 201 | 202 | double x = 0.0; 203 | double y = 0.0; 204 | int timeElapsed = 10; 205 | 206 | GlobalKey _keyRed = GlobalKey(); 207 | bool? validTouchFlag; 208 | 209 | static const duration = const Duration(seconds: 1); 210 | int secondsPassed = 0; 211 | bool isActive = false; 212 | Timer? timer; 213 | void handleTick() { 214 | setState(() { 215 | secondsPassed = secondsPassed + 1; 216 | }); 217 | } 218 | 219 | void finishGame() { 220 | DatabaseHelper helper = DatabaseHelper.instance; 221 | helper.updateBestTime(widget.category, secondsPassed); 222 | timer?.cancel(); 223 | 224 | Navigator.pop(context, ACategory.withTime(widget.category, secondsPassed.toString())); 225 | showDialog( 226 | builder: (context) => new AlertDialog( 227 | title: new Text("Congratulations!"), 228 | content: new Text("Your Score is ${secondsPassed ~/ 60} m ${secondsPassed % 60} s"), 229 | ), 230 | context: context); 231 | } 232 | 233 | void _incrementDown(PointerEvent details) { 234 | _updateLocation(details); 235 | print("Press Event: $details"); 236 | // finishGame(); 237 | 238 | setState(() { 239 | touchItems.clear(); 240 | validTouchFlag = true; 241 | }); 242 | } 243 | 244 | void _incrementUp(PointerEvent details) { 245 | _updateLocation(details); 246 | print("Release Event: $details"); 247 | String selectedStr = ""; 248 | touchItems.forEach((element) { 249 | int xIndex = element.x.toInt(); // Convert to int 250 | int yIndex = element.y.toInt(); // Convert to int 251 | selectedStr = selectedStr + gridMap[yIndex][xIndex]; 252 | }); 253 | if (wordsList.contains(selectedStr) && !foundWords.contains(selectedStr)) { 254 | foundMap.add(List.generate(touchItems.length, (index) => touchItems[index])); 255 | foundColor.add(Color.fromARGB(100, Random().nextInt(255), Random().nextInt(255), Random().nextInt(255))); 256 | foundWords.add(selectedStr); 257 | 258 | if (foundWords.length == wordsList.length) finishGame(); 259 | } 260 | touchItems.clear(); 261 | } 262 | 263 | void _updateLocation(PointerEvent details) { 264 | print("Move Event: $details"); 265 | if (validTouchFlag == true) 266 | setState(() { 267 | x = details.position.dx - _getPositions().dx; 268 | y = details.position.dy - _getPositions().dy; 269 | 270 | int itemX = x ~/ gridLogicalSize; 271 | int itemY = y ~/ gridLogicalSize; 272 | 273 | // print("ItemX : $itemX, ItemY : $itemY, GridW : $gridW, GridH : $gridH"); 274 | if (!touchItems.contains(Point(itemX, itemY)) && itemX >= 0 && itemX < gridW && itemY >= 0 && itemY < gridH) { 275 | Offset itemPos = 276 | Offset(itemX * gridLogicalSize + gridLogicalSize / 2, itemY * gridLogicalSize + gridLogicalSize / 2); 277 | Offset touchPos = Offset(x, y); 278 | if ((itemPos - touchPos).distance < gridLogicalSize / 2.5) if (touchItems.length < 2) { 279 | touchItems.add(Point(itemX, itemY)); 280 | } else if (itemX + touchItems[touchItems.length - 2].x == touchItems[touchItems.length - 1].x * 2 && 281 | itemY + touchItems[touchItems.length - 2].y == touchItems[touchItems.length - 1].y * 2) { 282 | touchItems.add(Point(itemX, itemY)); 283 | } 284 | } 285 | }); 286 | } 287 | 288 | Offset _getPositions() { 289 | final RenderBox? renderBox = _keyRed.currentContext?.findRenderObject() as RenderBox?; 290 | 291 | if (renderBox == null) { 292 | // Handle the scenario when renderBox is null 293 | return Offset.zero; // or throw an exception, or any fallback value you prefer 294 | } 295 | 296 | Offset position = renderBox.localToGlobal(Offset.zero); 297 | return position; 298 | } 299 | 300 | @override 301 | void initState() { 302 | super.initState(); 303 | foundMap.clear(); 304 | foundColor.clear(); 305 | foundWords.clear(); 306 | touchItems.clear(); 307 | _initializeGame(); 308 | } 309 | 310 | Widget build(BuildContext context) { 311 | int seconds = secondsPassed % 60; 312 | int minutes = secondsPassed ~/ 60; 313 | return Scaffold( 314 | body: Container( 315 | padding: EdgeInsets.symmetric(vertical: 10), 316 | color: Colors.cyan, 317 | child: Column(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ 318 | Center( 319 | child: Container( 320 | alignment: Alignment.topRight, 321 | margin: const EdgeInsets.all(10.0), 322 | child: Row( 323 | mainAxisAlignment: MainAxisAlignment.center, 324 | children: [ 325 | Container( 326 | margin: EdgeInsets.symmetric(horizontal: 10), 327 | padding: EdgeInsets.all(5), 328 | decoration: 329 | new BoxDecoration(borderRadius: new BorderRadius.circular(10), color: Colors.cyan[800]), 330 | child: Column(mainAxisSize: MainAxisSize.min, children: [ 331 | Text('Time', 332 | style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)), 333 | Container( 334 | padding: EdgeInsets.symmetric(horizontal: 5), 335 | decoration: new BoxDecoration( 336 | borderRadius: new BorderRadius.circular(10), color: Colors.yellow[700]), 337 | child: Text( 338 | //'$timeElapsed', 339 | "$minutes:$seconds", 340 | style: TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold), 341 | )), 342 | ])), 343 | Container( 344 | margin: EdgeInsets.symmetric(horizontal: 5), 345 | padding: EdgeInsets.all(5), 346 | decoration: 347 | new BoxDecoration(borderRadius: new BorderRadius.circular(10), color: Colors.cyan[800]), 348 | child: Column(mainAxisSize: MainAxisSize.min, children: [ 349 | Text('Best Time', 350 | style: TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold)), 351 | Container( 352 | padding: EdgeInsets.symmetric(horizontal: 5), 353 | decoration: new BoxDecoration( 354 | borderRadius: new BorderRadius.circular(10), color: Colors.yellow[700]), 355 | child: Text( 356 | '${widget.bestTime}', 357 | style: TextStyle(color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold), 358 | )), 359 | ])), 360 | ], 361 | )), 362 | ), 363 | Container( 364 | child: Align( 365 | alignment: Alignment.topCenter, 366 | child: Container( 367 | child: Listener( 368 | onPointerDown: _incrementDown, 369 | onPointerMove: _updateLocation, 370 | onPointerUp: _incrementUp, 371 | child: Container( 372 | child: Column(children: [ 373 | Container( 374 | width: panSize?.width, 375 | height: panSize?.height, 376 | decoration: 377 | new BoxDecoration(borderRadius: new BorderRadius.circular(10), color: Colors.cyan[200]), 378 | //color: Colors.white, 379 | child: CustomPaint(painter: CharacterMapPainter(), key: _keyRed), 380 | ), 381 | ]))), 382 | ), 383 | ), 384 | ), 385 | Container( 386 | width: deviceLogicalWidth * 0.8, 387 | height: (wordsList.length * 10).toDouble(), 388 | child: GridView.count( 389 | primary: false, 390 | padding: const EdgeInsets.all(0), 391 | //crossAxisSpacing: 10, 392 | //mainAxisSpacing: 1 / 10, 393 | crossAxisCount: 3, 394 | childAspectRatio: 4, 395 | children: wordsList 396 | .map((data) => Container( 397 | margin: EdgeInsets.symmetric(vertical: 0, horizontal: 5), 398 | child: Container( 399 | 400 | //color: Colors.green, 401 | child: Center( 402 | child: Text(data, 403 | style: TextStyle( 404 | fontSize: 16, 405 | fontWeight: FontWeight.bold, 406 | //fontFamily: 'wordsFont', 407 | color: foundWords.contains(data) 408 | ? foundColor[foundWords.indexOf(data)] 409 | : Colors.black, 410 | ), 411 | textAlign: TextAlign.center))))) 412 | .toList(), 413 | )), 414 | ])), 415 | // Inner yellow container 416 | /*child: */ 417 | ); 418 | } 419 | 420 | void _initializeGame() { 421 | gridW = 15; 422 | gridH = 15; 423 | gridSize = (deviceWidth - 20) / gridW; 424 | gridLogicalSize = gridSize / devicePixelRatio; 425 | 426 | print("DeviceWidth : $deviceWidth, DeviceLogicalWidth : $deviceLogicalWidth, GridSize : $gridSize"); 427 | 428 | // Generate the gridMap with the correct dimensions 429 | gridMap = List>.generate(gridH, (i) => List.generate(gridW, (j) => "")); 430 | 431 | // Calculate panSize 432 | panSize = Size(gridW.toDouble() * gridSize / 1.5, gridH.toDouble() * gridSize / 1.5); 433 | 434 | // If you want to print something for debugging. 435 | print("Grid Size: $gridSize, Pan Size: $panSize"); 436 | 437 | wordsList = List.generate(widget.words.length, (index) => widget.words[index].word); 438 | wordsList.sort((b, a) => a.length.compareTo(b.length)); 439 | var random = new Random(); 440 | if (wordsList.length == 0) return; 441 | var first = generate(random.nextInt(8), wordsList[0]); 442 | Point pt = Point(random.nextInt(gridW - first.first.length + 1), random.nextInt(gridH - first.length + 1)); 443 | putOnGrid(first, pt); 444 | for (int wi = 1; wi < wordsList.length; wi++) { 445 | int dir; 446 | checkFound: 447 | for (dir = 0; dir < 8; dir++) { 448 | //find if words match exist 449 | var piece = generate(dir, wordsList[wi]); 450 | for (int i = 0; i < gridH - piece.length; i++) 451 | for (int j = 0; j < gridW - piece.first.length; j++) { 452 | int matchCharCount = 0, dismatchCharCount = 0; 453 | for (int ii = 0; ii < piece.length; ii++) 454 | for (int jj = 0; jj < piece.first.length; jj++) 455 | if (piece[ii][jj] == gridMap[i + ii][j + jj] && piece[ii][jj] != "") 456 | matchCharCount++; 457 | else if (piece[ii][jj] != gridMap[i + ii][j + jj] && gridMap[i + ii][j + jj] != "") dismatchCharCount++; 458 | if (matchCharCount > 0 && dismatchCharCount == 0) { 459 | putOnGrid(piece, Point(j, i)); 460 | break checkFound; 461 | } 462 | } 463 | } 464 | if (dir == 8) { 465 | putAsAnother: 466 | while (true) { 467 | var piece = generate(random.nextInt(8), wordsList[wi]); 468 | int i = random.nextInt(gridH - piece.length); 469 | int j = random.nextInt(gridW - piece.first.length); 470 | int matchCharCount = 0; 471 | for (int ii = 0; ii < piece.length; ii++) 472 | for (int jj = 0; jj < piece.first.length; jj++) if (gridMap[i + ii][j + jj] != "") matchCharCount++; 473 | if (matchCharCount == 0) { 474 | putOnGrid(piece, Point(j, i)); 475 | break putAsAnother; 476 | } 477 | } 478 | } 479 | } 480 | 481 | String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 482 | for (int i = 0; i < gridMap.length; i++) 483 | for (int j = 0; j < gridMap[i].length; j++) if (gridMap[i][j] == "") gridMap[i][j] = chars[random.nextInt(26)]; 484 | } 485 | 486 | void putOnGrid(List> piece, Point pt) { 487 | for (int i = 0; i < piece.length; i++) 488 | for (int j = 0; j < piece[i].length; j++) gridMap[(pt.y + i).toInt()][(pt.x + j).toInt()] = piece[i][j]; 489 | } 490 | 491 | List> generate(int direction, String aword) { 492 | List> grid; 493 | if (direction == 0) { 494 | grid = List>.generate(1, (i) => List.generate(aword.length, (j) => aword[j])); 495 | } else if (direction == 1) { 496 | grid = List>.generate( 497 | aword.length, (i) => List.generate(aword.length, (j) => i == j ? aword[i] : "")); 498 | } else if (direction == 2) { 499 | grid = List>.generate(aword.length, (i) => List.generate(1, (j) => aword[i])); 500 | } else if (direction == 3) { 501 | grid = List>.generate( 502 | aword.length, (i) => List.generate(aword.length, (j) => i + j + 1 == aword.length ? aword[i] : "")); 503 | } else if (direction == 4) { 504 | grid = List>.generate( 505 | 1, (i) => List.generate(aword.length, (j) => aword[aword.length - 1 - j])); 506 | } else if (direction == 5) { 507 | grid = List>.generate( 508 | aword.length, (i) => List.generate(aword.length, (j) => i == j ? aword[aword.length - i - 1] : "")); 509 | } else if (direction == 6) { 510 | grid = List>.generate( 511 | aword.length, (i) => List.generate(1, (j) => aword[aword.length - i - 1])); 512 | } else if (direction == 7) { 513 | grid = List>.generate( 514 | aword.length, (i) => List.generate(aword.length, (j) => i + j + 1 == aword.length ? aword[j] : "")); 515 | } else { 516 | // Handle invalid direction 517 | grid = []; 518 | } 519 | return grid; 520 | } 521 | 522 | @override 523 | void dispose() { 524 | timer?.cancel(); 525 | super.dispose(); 526 | } 527 | } 528 | 529 | class CharacterMapPainter extends CustomPainter { 530 | @override 531 | void paint(Canvas canvas, Size size) { 532 | // Define a paint object 533 | final paint = Paint() 534 | ..style = PaintingStyle.stroke 535 | ..strokeWidth = 2.0 536 | ..color = Color.fromARGB(255, 0, 188, 212); 537 | 538 | print("Size $size, GridSize $gridSize"); 539 | // Left eye 540 | canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromLTWH(0, 0, size.width, size.height), Radius.circular(10)), paint); 541 | for (int i = 0; i < gridMap.length; i++) 542 | for (int j = 0; j < gridMap[i].length; j++) { 543 | final textStyle = TextStyle(color: Colors.cyan[900], fontSize: 16, fontWeight: FontWeight.bold); 544 | final textSpan = TextSpan(text: gridMap[i][j], style: textStyle); 545 | final textPainter = TextPainter(text: textSpan, textDirection: TextDirection.ltr); 546 | textPainter.layout(); 547 | double horizontalPosition = (j) * gridLogicalSize + (gridLogicalSize - textPainter.width) / 2; 548 | double verticalPosition = (i) * gridLogicalSize + (gridLogicalSize - textPainter.height) / 2; 549 | 550 | // print("HOffset $horizontalPosition, VOffest $verticalPosition"); 551 | Offset offset = Offset(horizontalPosition, verticalPosition); 552 | textPainter.paint(canvas, offset); 553 | } 554 | //----- Found Words history 555 | List offset = []; 556 | Path path = Path(); 557 | paint.strokeWidth = gridLogicalSize; 558 | paint.strokeCap = StrokeCap.round; 559 | paint.strokeJoin = StrokeJoin.round; 560 | for (int i = 0; i < foundWords.length; i++) { 561 | offset.clear(); 562 | path.reset(); 563 | paint.color = foundColor[i]; 564 | for (int j = 0; j < foundMap[i].length; j++) 565 | offset.add(Offset((foundMap[i][j].x + 0.5) * gridLogicalSize, (foundMap[i][j].y + 0.5) * gridLogicalSize)); 566 | path.addPolygon(offset, false); 567 | canvas.drawPath(path, paint); 568 | } 569 | 570 | //----- Current drawing 571 | List offsets = []; 572 | for (int i = 0; i < touchItems.length; i++) 573 | offsets.add(Offset((touchItems[i].x + 0.5) * gridLogicalSize, (touchItems[i].y + 0.5) * gridLogicalSize)); 574 | 575 | path.reset(); 576 | path.addPolygon(offsets, false); 577 | paint.color = Color.fromRGBO(255, 0, 0, 80); 578 | canvas.drawPath(path, paint); 579 | } 580 | 581 | @override 582 | bool shouldRepaint(CharacterMapPainter oldDelegate) => true; 583 | noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); 584 | } 585 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: word_puzzle 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: '>=2.12.0 <4.0.0' 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | sqflite: ^2.4.1 27 | path_provider: ^2.0.0 28 | shared_preferences: ^2.0.0 29 | # loading: ^0.1.0 # or the latest available version that supports null safety 30 | dev_dependencies: 31 | flutter_test: 32 | sdk: flutter 33 | 34 | # For information on the generic Dart part of this file, see the 35 | # following page: https://dart.dev/tools/pub/pubspec 36 | 37 | # The following section is specific to Flutter. 38 | flutter: 39 | 40 | # The following line ensures that the Material Icons font is 41 | # included with your application, so that you can use the icons in 42 | # the material Icons class. 43 | uses-material-design: true 44 | 45 | # To add assets to your application, add an assets section, like this: 46 | # assets: 47 | # - images/a_dot_burr.jpeg 48 | # - images/a_dot_ham.jpeg 49 | 50 | # An image asset can refer to one or more resolution-specific "variants", see 51 | # https://flutter.dev/assets-and-images/#resolution-aware. 52 | 53 | # For details regarding adding assets from package dependencies, see 54 | # https://flutter.dev/assets-and-images/#from-packages 55 | 56 | # To add custom fonts to your application, add a fonts section here, 57 | # in this "flutter" section. Each entry in this list should have a 58 | # "family" key with the font family name, and a "fonts" key with a 59 | # list giving the asset and other descriptors for the font. For 60 | # example: 61 | fonts: 62 | - family: listFont 63 | fonts: 64 | - asset: fonts/LondrinaShadow-Regular.ttf 65 | - family: wordsFont 66 | fonts: 67 | - asset: fonts/VastShadow-Regular.ttf 68 | # 69 | # For details regarding fonts from package dependencies, 70 | # see https://flutter.dev/custom-fonts/#from-packages 71 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | //import 'package:word_puzzle/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | // await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /word_puzzle_game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reddev0110/Word-Puzzle-Game/ab08a1958327e191b6c7e43a8d08fd3be533f1f2/word_puzzle_game.png --------------------------------------------------------------------------------