Skip to content
Snippets Groups Projects
Commit c9817318 authored by Benoît Harrault's avatar Benoît Harrault
Browse files

Merge branch '1-add-minimal-game-framework' into 'master'

Resolve "Add minimal game framework"

Closes #1

See merge request !1
parents 1007cae3 39a80ee1
Branches 4-add-animations
Tags Release_0.0.1_1
1 merge request!1Resolve "Add minimal game framework"
Pipeline #2543 passed
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Data extends ChangeNotifier {
// Configuration available values
List _availableLangValues = ['fr'];
List _availableWordLengthValues = ['5'];
List _availableSkinValues = ['default'];
List get availableLangValues => _availableLangValues;
List get availableWordLengthValues => _availableWordLengthValues;
List get availableSkinValues => _availableSkinValues;
// Application default configuration
String _lang = '';
String _langDefault = 'fr';
String _length = '';
String _lengthDefault = '5';
String _skin = '';
String _skinDefault = 'default';
// Game data
bool _gameIsRunning = false;
String _word = '';
final int _recentWordsCount = 20;
List _recentWords = [];
List<String> _guesses = [];
String _currentGuess = '';
int _maxGuessesCount = 7;
String get lang => _lang;
void updateLang(String lang) {
_lang = lang;
notifyListeners();
}
String get length => _length;
void updateLength(String length) {
_length = length;
notifyListeners();
}
String get skin => _skin;
void updateSkin(String skin) {
_skin = skin;
notifyListeners();
}
List<String> get guesses => _guesses;
String get currentGuess => _currentGuess;
int get maxGuessesCount => _maxGuessesCount;
void currentGuessAddLetter(String letter) {
if (_currentGuess.length < int.parse(_length)) {
_currentGuess = _currentGuess + letter;
notifyListeners();
}
}
void currentGuessRemoveLetter() {
if (_currentGuess.length > 0) {
_currentGuess = _currentGuess.substring(0, _currentGuess.length - 1);
notifyListeners();
}
}
void currentGuessSubmitWord() {
if (_currentGuess.length == int.parse(_length)) {
print('check word: '+_currentGuess);
if (_currentGuess != _word) {
print('wrong');
} else {
print('ok found');
}
addGuess(_currentGuess);
notifyListeners();
}
}
void addGuess(String word) {
print('addGuess('+word+')');
_guesses.add(word);
_currentGuess = '';
}
getParameterValue(String parameterCode) {
switch(parameterCode) {
case 'lang': { return _lang; }
break;
case 'length': { return _length; }
break;
case 'skin': { return _skin; }
break;
}
}
List getParameterAvailableValues(String parameterCode) {
switch(parameterCode) {
case 'lang': { return _availableLangValues; }
break;
case 'length': { return _availableWordLengthValues; }
break;
case 'skin': { return _availableSkinValues; }
break;
}
return [];
}
setParameterValue(String parameterCode, String parameterValue) async {
switch(parameterCode) {
case 'lang': { updateLang(parameterValue); }
break;
case 'length': { updateLength(parameterValue); }
break;
case 'skin': { updateSkin(parameterValue); }
break;
}
final prefs = await SharedPreferences.getInstance();
prefs.setString(parameterCode, parameterValue);
}
void initParametersValues() async {
final prefs = await SharedPreferences.getInstance();
setParameterValue('lang', prefs.getString('lang') ?? _langDefault);
setParameterValue('length', prefs.getString('length') ?? _lengthDefault);
setParameterValue('skin', prefs.getString('skin') ?? _skinDefault);
}
bool get gameIsRunning => _gameIsRunning;
void updateGameIsRunning(bool gameIsRunning) {
_gameIsRunning = gameIsRunning;
notifyListeners();
}
String get word => _word;
void updateWord(String word) {
_word = word;
if (word != null) {
_recentWords.insert(0, word);
_recentWords = _recentWords.take(_recentWordsCount).toList();
}
notifyListeners();
}
bool isRecentlyPicked(String word) {
return _recentWords.contains(word);
}
void resetGame() {
_word = '';
_guesses = [];
_currentGuess = '';
notifyListeners();
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:overlay_support/overlay_support.dart';
import '../layout/game.dart';
import '../layout/parameters.dart';
import '../provider/data.dart';
import '../utils/game_utils.dart';
class Home extends StatefulWidget {
static const String id = 'home';
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
@override
void initState() {
super.initState();
Data myProvider = Provider.of<Data>(context, listen: false);
myProvider.initParametersValues();
}
@override
Widget build(BuildContext context) {
Data myProvider = Provider.of<Data>(context);
List<Widget> menuActions = [];
if (myProvider.gameIsRunning) {
menuActions = [
FlatButton(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: Colors.blue,
width: 4,
),
),
margin: EdgeInsets.all(8),
child: Image(
image: AssetImage('assets/icons/button_back.png'),
fit: BoxFit.fill
),
),
onPressed: () => toast('Long press to quit game...'),
onLongPress: () => GameUtils.resetGame(myProvider),
),
];
}
return Scaffold(
appBar: AppBar(
actions: menuActions,
),
body: SafeArea(
child: Center(
child: myProvider.gameIsRunning
? Game.buildGameWidget(myProvider)
: Parameters.buildParametersSelector(myProvider)
),
)
);
}
}
import '../provider/data.dart';
import '../utils/game_utils.dart';
import '../utils/random_pick_word.dart';
class GameUtils {
static Future<void> resetGame(Data myProvider) async {
myProvider.updateGameIsRunning(false);
}
static Future<void> startGame(Data myProvider) async {
print('Starting game');
print('- lang: ' + myProvider.lang);
print('- length: ' + myProvider.length);
myProvider.resetGame();
await pickWord(myProvider);
myProvider.updateGameIsRunning(true);
}
static Future<void> pickWord(Data myProvider) async {
RandomPickWord randomPickWord;
String word = '';
int attempts = 0;
do {
randomPickWord = RandomPickWord();
int wordLength = int.parse(myProvider.length);
await randomPickWord.init(myProvider.lang, wordLength);
if (randomPickWord.word != '') {
word = randomPickWord.word;
}
attempts++;
if ((word != '') && !myProvider.isRecentlyPicked(word)) {
myProvider.updateWord(word);
}
} while (myProvider.word != word && attempts < 10);
print('Picked word: ' + word);
}
static bool addLetter(Data myProvider, String letter) {
print('addLetter: ' + letter);
myProvider.currentGuessAddLetter(letter);
return true;
}
static bool removeLetter(Data myProvider) {
print('removeLetter');
myProvider.currentGuessRemoveLetter();
return true;
}
static List<String> getTips(Data myProvider, String candidate) {
String word = myProvider.word;
int wordLength = int.parse(myProvider.length);
List<String> tips = List<String>.filled(wordLength, '', growable: false);
if ((word.length != wordLength) || (candidate.length != wordLength)) {
return tips;
}
String replaceCharAt(String oldString, int index, String newChar) {
return oldString.substring(0, index) + newChar + oldString.substring(index + 1);
}
print('getTips: candidate "'+candidate+'" / word "'+word+'"');
// Check correctly placed letters
print('Check correctly placed letters');
for (int i = 0; i < wordLength; i++) {
if ((tips[i] == '') && (word[i] == candidate[i])) {
print('Found "'+word[i]+'" on the right place: '+(i+1).toString());
word = replaceCharAt(word, i, ' ');
candidate = replaceCharAt(candidate, i, ' ');
tips[i] = 'good';
}
}
// Check misplaced letters
print('Check misplaced letters');
for (int i = 0; i < wordLength; i++) {
for (int j = 0; j < wordLength; j++) {
if ((candidate[j] != ' ') && (candidate[j] == word[i])) {
print('Found "'+candidate[j]+'" on the wrong place: '+(j+1).toString()+' instead of '+(i+1).toString());
word = replaceCharAt(word, i, ' ');
candidate = replaceCharAt(candidate, j, ' ');
tips[j] = 'misplaced';
}
}
}
print('Finished check letters: '+tips.toString());
return tips;
}
static bool submitWord(Data myProvider) {
print('submitWord');
// TODO: check this word is allowed
myProvider.currentGuessSubmitWord();
return true;
}
static bool isGameFinished(Data myProvider) {
print('isGameFinished');
if (myProvider.guesses.length > 0) {
if (myProvider.guesses[myProvider.guesses.length - 1] == myProvider.word) {
return true;
}
}
return false;
}
}
import 'dart:async';
import 'dart:convert';
import "dart:math";
import 'package:flutter/services.dart';
class RandomPickWord {
static Set<String> wordList = <String>{};
static Set<String> dictionary = <String>{};
static int _length = 5;
RandomPickWord();
String _word = '';
String get word => _word;
init(String lang, int length) async {
_length = length;
_word = '';
await wordFromLocalFile(lang, length);
}
Future<void> wordFromLocalFile(String lang, int length) async {
if (length != _length || wordList.isEmpty || dictionary.isEmpty) {
_length = length;
wordList.clear();
try {
String wordsFile = 'words-' + length.toString() + '-' + lang;
var data = await rootBundle.loadString('assets/files/' + wordsFile + '.txt');
LineSplitter.split(data).forEach((line) {
if (line.length == length) {
wordList.add(line.toUpperCase());
}
});
} catch (e) {
throw "Failed loading words database";
}
}
print('Words found in file: ' + wordList.length.toString());
// Check we have enough words
if (wordList.length < 1) {
print('Not enough words in list.');
_word = '';
} else {
final _random = new Random();
_word = wordList.elementAt(_random.nextInt(wordList.length));
}
print('Picked word: ' + _word);
}
}
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.2"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
ffi:
dependency: transitive
description:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
file:
dependency: transitive
description:
name: file
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.2"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.3"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.11"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
nested:
dependency: transitive
description:
name: nested
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
overlay_support:
dependency: "direct main"
description:
name: overlay_support
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.1"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.5"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
process:
dependency: transitive
description:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.4"
provider:
dependency: "direct main"
description:
name: provider
url: "https://pub.dartlang.org"
source: hosted
version: "6.0.2"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.13"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.11"
shared_preferences_ios:
dependency: transitive
description:
name: shared_preferences_ios
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.8"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
win32:
dependency: transitive
description:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0+1"
sdks:
dart: ">=2.16.1 <3.0.0"
flutter: ">=2.8.0"
name: momomotus
description: A motus-like game application.
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ">=2.16.1 <3.0.0"
dependencies:
flutter:
sdk: flutter
provider: ^6.0.2
shared_preferences: ^2.0.6
overlay_support: ^1.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
assets:
- assets/files/
- assets/icons/
- assets/skins/
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:momomotus/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment