liuyuqi-dellpc 2 years ago
parent
commit
cba00e5e18
47 changed files with 2055 additions and 187 deletions
  1. 1 1
      android/build.gradle
  2. 1 1
      android/gradle/wrapper/gradle-wrapper.properties
  3. 3 3
      ios/Runner.xcodeproj/project.pbxproj
  4. 67 0
      lib/generated/intl/messages_all.dart
  5. 30 0
      lib/generated/intl/messages_en.dart
  6. 30 0
      lib/generated/intl/messages_zh.dart
  7. 96 0
      lib/generated/l10n.dart
  8. 5 0
      lib/l10n/intl_en.arb
  9. 5 0
      lib/l10n/intl_zh.arb
  10. 29 99
      lib/main.dart
  11. 19 0
      lib/model/BlockInfo.dart
  12. 9 0
      lib/model/Config.dart
  13. 4 0
      lib/model/Display.dart
  14. 16 0
      lib/model/GameStatus.dart
  15. 72 0
      lib/pages/index_page.dart
  16. 15 0
      lib/reducers/index.dart
  17. 54 0
      lib/reducers/moveDown.dart
  18. 53 0
      lib/reducers/moveLeft.dart
  19. 53 0
      lib/reducers/moveRight.dart
  20. 53 0
      lib/reducers/moveUp.dart
  21. 11 0
      lib/reducers/updateState.dart
  22. 60 0
      lib/service/BlockFactory.dart
  23. 27 0
      lib/service/gameInit.dart
  24. 159 0
      lib/store/GameState.dart
  25. 30 0
      lib/utils/Device.dart
  26. 16 0
      lib/utils/Screen.dart
  27. 57 0
      lib/views/Blocks.dart
  28. 71 0
      lib/views/GameBg.dart
  29. 52 0
      lib/views/ModeSelector.dart
  30. 69 0
      lib/views/NumberText.dart
  31. 109 0
      lib/views/Playground.dart
  32. 139 0
      lib/views/Scores.dart
  33. 38 0
      lib/views/block/BaseBlock.dart
  34. 54 0
      lib/views/block/CombinBlock.dart
  35. 50 0
      lib/views/block/MoveBlock.dart
  36. 33 0
      lib/views/block/NewBlock.dart
  37. 30 0
      lib/views/block/StaticBlock.dart
  38. 9 0
      linux/flutter/generated_plugin_registrant.cc
  39. 13 0
      linux/flutter/generated_plugin_registrant.h
  40. 15 0
      linux/flutter/generated_plugins.cmake
  41. 25 0
      linux/flutter/generated_plugins.mk
  42. 332 0
      pubspec.lock
  43. 11 53
      pubspec.yaml
  44. 0 30
      test/widget_test.dart
  45. 8 0
      windows/flutter/GeneratedPlugins.props
  46. 9 0
      windows/flutter/generated_plugin_registrant.cc
  47. 13 0
      windows/flutter/generated_plugin_registrant.h

+ 1 - 1
android/build.gradle

@@ -5,7 +5,7 @@ buildscript {
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.0.0'
+        classpath 'com.android.tools.build:gradle:4.1.0'
     }
 }
 

+ 1 - 1
android/gradle/wrapper/gradle-wrapper.properties

@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip

+ 3 - 3
ios/Runner.xcodeproj/project.pbxproj

@@ -311,7 +311,7 @@
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = yoqi.me.flutter2048;
+				PRODUCT_BUNDLE_IDENTIFIER = yoqi.me.flutter_2048;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				VERSIONING_SYSTEM = "apple-generic";
 			};
@@ -441,7 +441,7 @@
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = yoqi.me.flutter2048;
+				PRODUCT_BUNDLE_IDENTIFIER = yoqi.me.flutter_2048;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				VERSIONING_SYSTEM = "apple-generic";
 			};
@@ -464,7 +464,7 @@
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = yoqi.me.flutter2048;
+				PRODUCT_BUNDLE_IDENTIFIER = yoqi.me.flutter_2048;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				VERSIONING_SYSTEM = "apple-generic";
 			};

+ 67 - 0
lib/generated/intl/messages_all.dart

@@ -0,0 +1,67 @@
+// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+// This is a library that looks up messages for specific locales by
+// delegating to the appropriate library.
+
+// Ignore issues from commonly used lints in this file.
+// ignore_for_file:implementation_imports, file_names, unnecessary_new
+// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering
+// ignore_for_file:argument_type_not_assignable, invalid_assignment
+// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases
+// ignore_for_file:comment_references
+
+import 'dart:async';
+
+import 'package:intl/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+import 'package:intl/src/intl_helpers.dart';
+
+import 'messages_en.dart' as messages_en;
+import 'messages_zh.dart' as messages_zh;
+
+typedef Future<dynamic> LibraryLoader();
+Map<String, LibraryLoader> _deferredLibraries = {
+  'en': () => new Future.value(null),
+  'zh': () => new Future.value(null),
+};
+
+MessageLookupByLibrary _findExact(String localeName) {
+  switch (localeName) {
+    case 'en':
+      return messages_en.messages;
+    case 'zh':
+      return messages_zh.messages;
+    default:
+      return null;
+  }
+}
+
+/// User programs should call this before using [localeName] for messages.
+Future<bool> initializeMessages(String localeName) async {
+  var availableLocale = Intl.verifiedLocale(
+    localeName,
+    (locale) => _deferredLibraries[locale] != null,
+    onFailure: (_) => null);
+  if (availableLocale == null) {
+    return new Future.value(false);
+  }
+  var lib = _deferredLibraries[availableLocale];
+  await (lib == null ? new Future.value(false) : lib());
+  initializeInternalMessageLookup(() => new CompositeMessageLookup());
+  messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
+  return new Future.value(true);
+}
+
+bool _messagesExistFor(String locale) {
+  try {
+    return _findExact(locale) != null;
+  } catch (e) {
+    return false;
+  }
+}
+
+MessageLookupByLibrary _findGeneratedMessagesFor(String locale) {
+  var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor,
+      onFailure: (_) => null);
+  if (actualLocale == null) return null;
+  return _findExact(actualLocale);
+}

+ 30 - 0
lib/generated/intl/messages_en.dart

@@ -0,0 +1,30 @@
+// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+// This is a library that provides messages for a en locale. All the
+// messages from the main program should be duplicated here with the same
+// function name.
+
+// Ignore issues from commonly used lints in this file.
+// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
+// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
+// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
+// ignore_for_file:unused_import, file_names
+
+import 'package:intl/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+
+final messages = new MessageLookup();
+
+typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
+
+class MessageLookup extends MessageLookupByLibrary {
+  String get localeName => 'en';
+
+  static m0(name) => "Welcome ${name}";
+
+  final messages = _notInlinedMessages(_notInlinedMessages);
+  static _notInlinedMessages(_) => <String, Function> {
+    "pageHomeConfirm" : MessageLookupByLibrary.simpleMessage("Confirm"),
+    "pageHomeWelcome" : m0,
+    "titleName" : MessageLookupByLibrary.simpleMessage("2048")
+  };
+}

+ 30 - 0
lib/generated/intl/messages_zh.dart

@@ -0,0 +1,30 @@
+// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+// This is a library that provides messages for a zh locale. All the
+// messages from the main program should be duplicated here with the same
+// function name.
+
+// Ignore issues from commonly used lints in this file.
+// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
+// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
+// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
+// ignore_for_file:unused_import, file_names
+
+import 'package:intl/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+
+final messages = new MessageLookup();
+
+typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
+
+class MessageLookup extends MessageLookupByLibrary {
+  String get localeName => 'zh';
+
+  static m0(name) => "Welcome ${name}";
+
+  final messages = _notInlinedMessages(_notInlinedMessages);
+  static _notInlinedMessages(_) => <String, Function> {
+    "pageHomeConfirm" : MessageLookupByLibrary.simpleMessage("Confirm"),
+    "pageHomeWelcome" : m0,
+    "titleName" : MessageLookupByLibrary.simpleMessage("2048")
+  };
+}

+ 96 - 0
lib/generated/l10n.dart

@@ -0,0 +1,96 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+import 'package:flutter/material.dart';
+import 'package:intl/intl.dart';
+import 'intl/messages_all.dart';
+
+// **************************************************************************
+// Generator: Flutter Intl IDE plugin
+// Made by Localizely
+// **************************************************************************
+
+// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars
+// ignore_for_file: join_return_with_assignment, prefer_final_in_for_each
+// ignore_for_file: avoid_redundant_argument_values
+
+class S {
+  S();
+  
+  static S current;
+  
+  static const AppLocalizationDelegate delegate =
+    AppLocalizationDelegate();
+
+  static Future<S> load(Locale locale) {
+    final name = (locale.countryCode?.isEmpty ?? false) ? locale.languageCode : locale.toString();
+    final localeName = Intl.canonicalizedLocale(name); 
+    return initializeMessages(localeName).then((_) {
+      Intl.defaultLocale = localeName;
+      S.current = S();
+      
+      return S.current;
+    });
+  } 
+
+  static S of(BuildContext context) {
+    return Localizations.of<S>(context, S);
+  }
+
+  /// `2048`
+  String get titleName {
+    return Intl.message(
+      '2048',
+      name: 'titleName',
+      desc: '',
+      args: [],
+    );
+  }
+
+  /// `Confirm`
+  String get pageHomeConfirm {
+    return Intl.message(
+      'Confirm',
+      name: 'pageHomeConfirm',
+      desc: '',
+      args: [],
+    );
+  }
+
+  /// `Welcome {name}`
+  String pageHomeWelcome(Object name) {
+    return Intl.message(
+      'Welcome $name',
+      name: 'pageHomeWelcome',
+      desc: '',
+      args: [name],
+    );
+  }
+}
+
+class AppLocalizationDelegate extends LocalizationsDelegate<S> {
+  const AppLocalizationDelegate();
+
+  List<Locale> get supportedLocales {
+    return const <Locale>[
+      Locale.fromSubtags(languageCode: 'en'),
+      Locale.fromSubtags(languageCode: 'zh'),
+    ];
+  }
+
+  @override
+  bool isSupported(Locale locale) => _isSupported(locale);
+  @override
+  Future<S> load(Locale locale) => S.load(locale);
+  @override
+  bool shouldReload(AppLocalizationDelegate old) => false;
+
+  bool _isSupported(Locale locale) {
+    if (locale != null) {
+      for (var supportedLocale in supportedLocales) {
+        if (supportedLocale.languageCode == locale.languageCode) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+}

+ 5 - 0
lib/l10n/intl_en.arb

@@ -0,0 +1,5 @@
+{
+    "titleName":"2048",
+    "pageHomeConfirm": "Confirm",
+    "pageHomeWelcome": "Welcome {name}"
+}

+ 5 - 0
lib/l10n/intl_zh.arb

@@ -0,0 +1,5 @@
+{
+    "titleName": "2048",
+    "pageHomeConfirm": "Confirm",
+    "pageHomeWelcome": "Welcome {name}"
+}

+ 29 - 99
lib/main.dart

@@ -1,113 +1,43 @@
 import 'package:flutter/material.dart';
+import 'pages/index_page.dart';
+import 'generated/l10n.dart';
+import 'package:flutter_localizations/flutter_localizations.dart';
 
-void main() {
-  runApp(MyApp());
-}
+void main() => runApp(GameApp());
 
-class MyApp extends StatelessWidget {
-  // This widget is the root of your application.
+class GameApp extends StatefulWidget {
+  
   @override
-  Widget build(BuildContext context) {
-    return MaterialApp(
-      title: 'Flutter Demo',
-      theme: ThemeData(
-        // This is the theme of your application.
-        //
-        // Try running your application with "flutter run". You'll see the
-        // application has a blue toolbar. Then, without quitting the app, try
-        // changing the primarySwatch below to Colors.green and then invoke
-        // "hot reload" (press "r" in the console where you ran "flutter run",
-        // or simply save your changes to "hot reload" in a Flutter IDE).
-        // Notice that the counter didn't reset back to zero; the application
-        // is not restarted.
-        primarySwatch: Colors.blue,
-      ),
-      home: MyHomePage(title: 'Flutter Demo Home Page'),
-    );
-  }
+  State<StatefulWidget> createState() => _GameAppState();
 }
 
-class MyHomePage extends StatefulWidget {
-  MyHomePage({Key key, this.title}) : super(key: key);
-
-  // This widget is the home page of your application. It is stateful, meaning
-  // that it has a State object (defined below) that contains fields that affect
-  // how it looks.
-
-  // This class is the configuration for the state. It holds the values (in this
-  // case the title) provided by the parent (in this case the App widget) and
-  // used by the build method of the State. Fields in a Widget subclass are
-  // always marked "final".
-
-  final String title;
-
-  @override
-  _MyHomePageState createState() => _MyHomePageState();
-}
-
-class _MyHomePageState extends State<MyHomePage> {
-  int _counter = 0;
-
-  void _incrementCounter() {
-    setState(() {
-      // This call to setState tells the Flutter framework that something has
-      // changed in this State, which causes it to rerun the build method below
-      // so that the display can reflect the updated values. If we changed
-      // _counter without calling setState(), then the build method would not be
-      // called again, and so nothing would appear to happen.
-      _counter++;
-    });
-  }
+class _GameAppState extends State<GameApp> {
 
   @override
   Widget build(BuildContext context) {
-    // This method is rerun every time setState is called, for instance as done
-    // by the _incrementCounter method above.
-    //
-    // The Flutter framework has been optimized to make rerunning build methods
-    // fast, so that you can just rebuild anything that needs updating rather
-    // than having to individually change instances of widgets.
-    return Scaffold(
-      appBar: AppBar(
-        // Here we take the value from the MyHomePage object that was created by
-        // the App.build method, and use it to set our appbar title.
-        title: Text(widget.title),
-      ),
-      body: Center(
-        // Center is a layout widget. It takes a single child and positions it
-        // in the middle of the parent.
-        child: Column(
-          // Column is also a layout widget. It takes a list of children and
-          // arranges them vertically. By default, it sizes itself to fit its
-          // children horizontally, and tries to be as tall as its parent.
-          //
-          // Invoke "debug painting" (press "p" in the console, choose the
-          // "Toggle Debug Paint" action from the Flutter Inspector in Android
-          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
-          // to see the wireframe for each widget.
-          //
-          // Column has various properties to control how it sizes itself and
-          // how it positions its children. Here we use mainAxisAlignment to
-          // center the children vertically; the main axis here is the vertical
-          // axis because Columns are vertical (the cross axis would be
-          // horizontal).
-          mainAxisAlignment: MainAxisAlignment.center,
-          children: <Widget>[
-            Text(
-              'You have pushed the button this many times:',
-            ),
-            Text(
-              '$_counter',
-              style: Theme.of(context).textTheme.headline4,
-            ),
-          ],
+    return MaterialApp(
+        localizationsDelegates: [
+        S.delegate,
+        GlobalMaterialLocalizations.delegate,
+        GlobalWidgetsLocalizations.delegate,
+        GlobalCupertinoLocalizations.delegate,
+      ],
+      supportedLocales: S.delegate.supportedLocales,
+      debugShowCheckedModeBanner: false,
+      title: "2048",
+      theme: ThemeData(primaryColor: Colors.orange),
+      home: Material(
+        color: Color(0xfffaf8ef),
+        child: SafeArea(
+          left: false,
+          right: false,
+          child: Stack(
+            children: <Widget>[
+              IndexPage(),
+            ],
+          ),
         ),
       ),
-      floatingActionButton: FloatingActionButton(
-        onPressed: _incrementCounter,
-        tooltip: 'Increment',
-        child: Icon(Icons.add),
-      ), // This trailing comma makes auto-formatting nicer for build methods.
     );
   }
 }

+ 19 - 0
lib/model/BlockInfo.dart

@@ -0,0 +1,19 @@
+class BlockInfo {
+  BlockInfo({this.value, this.current, this.before, this.isNew = true}) {
+    this.before = this.before == null ? this.current : this.before;
+  }
+
+  int value;
+  int current;
+  int before;
+  bool needMove = false;
+  bool needCombine = false;
+  bool isNew = false;
+
+  void reset() {
+    value = 0;
+    needMove = false;
+    needCombine = false;
+    isNew = false;
+  }
+}

+ 9 - 0
lib/model/Config.dart

@@ -0,0 +1,9 @@
+import 'package:flutter/cupertino.dart';
+
+class CustomColor {
+  static Color textColor = Color(0xfffaf8ef);
+}
+
+class BaseConfig {}
+
+class ApiConfig {}

+ 4 - 0
lib/model/Display.dart

@@ -0,0 +1,4 @@
+class Display {
+  static double borderMargin = 10;
+  static double spacerUnit = 2.5;
+}

+ 16 - 0
lib/model/GameStatus.dart

@@ -0,0 +1,16 @@
+/// 当前状态
+class GameStatus {
+  GameStatus({
+    this.adds,
+    this.moves,
+    this.scores,
+    this.total,
+    this.end,
+  });
+
+  int scores;
+  int total;
+  int adds;
+  int moves;
+  bool end;
+}

+ 72 - 0
lib/pages/index_page.dart

@@ -0,0 +1,72 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_2048/service/gameInit.dart';
+import 'package:flutter_2048/views/Blocks.dart';
+import 'package:flutter_2048/views/Scores.dart';
+import 'package:flutter_redux/flutter_redux.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:redux/redux.dart';
+import 'package:redux_thunk/redux_thunk.dart';
+import '../views/GameBg.dart';
+import '../views/ModeSelector.dart';
+import '../views/Playground.dart';
+import '../model/Display.dart';
+import '../reducers/index.dart';
+import '../store/GameState.dart';
+
+/// 首页
+class IndexPage extends StatelessWidget {
+
+  @override
+  Widget build(BuildContext context) {
+    //ScreenUtil 初始化实例
+    ScreenUtil.init(
+        BoxConstraints(
+            maxWidth: MediaQuery.of(context).size.width,
+            maxHeight: MediaQuery.of(context).size.height),
+        designSize: Size(360, 640),
+        orientation: Orientation.portrait);  // 竖屏
+
+    return StoreProvider(
+      store: Store<GameState>(
+        gameReducer,
+        middleware: [thunkMiddleware],
+        initialState: GameState.initial(4),
+      ),
+      child: StoreConnector<GameState, GameProps>(
+        converter: (store) =>
+            GameProps(started: store.state.status.total != null),
+        onInit: (store) {
+          gameInit(store, 4);
+        },
+        builder: (context, props) {
+          return props.started
+              ? Container(
+                  margin: EdgeInsets.all(Display.borderMargin),
+                  child: Column(
+                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                    crossAxisAlignment: CrossAxisAlignment.stretch,
+                    children: <Widget>[
+                      ModeSelector(),
+                      Scores(),
+                      Stack(
+                        children: <Widget>[
+                          GameBg(),
+                          Blocks(),
+                          Playground(),
+                        ],
+                      ),
+                    ],
+                  ),
+                )
+              : Container();
+        },
+      ),
+    );
+  }
+}
+
+class GameProps {
+  bool started;
+
+  GameProps({this.started});
+}

+ 15 - 0
lib/reducers/index.dart

@@ -0,0 +1,15 @@
+import 'package:flutter_2048/reducers/updateState.dart';
+import './moveDown.dart';
+import './moveLeft.dart';
+import './moveRight.dart';
+import './moveUp.dart';
+import '../store/GameState.dart';
+import 'package:redux/redux.dart';
+
+final gameReducer = combineReducers<GameState>([
+  TypedReducer<GameState, UpdateStateAction>(updateState),
+  TypedReducer<GameState, MoveLeftAction>(moveLeft),
+  TypedReducer<GameState, MoveRightAction>(moveRight),
+  TypedReducer<GameState, MoveUpAction>(moveUp),
+  TypedReducer<GameState, MoveDownAction>(moveDown),
+]);

+ 54 - 0
lib/reducers/moveDown.dart

@@ -0,0 +1,54 @@
+import 'package:flutter_2048/store/GameState.dart';
+
+class MoveDownAction {}
+
+GameState moveDown(GameState state, MoveDownAction action) {
+  if (state.status.end) return state.clone();
+
+  var clonestate = state.clone();
+  var i, j, k;
+  bool isMoved = false;
+  bool haveMove = false;
+  bool haveCombin = false;
+  for (i = 0; i < clonestate.mode; i++) {
+    j = k = clonestate.mode - 1;
+    while (true) {
+      while (j > -1 && clonestate.getBlock(j, i).value == 0) j--;
+      if (j < 0) break;
+
+      if (j < k) {
+        isMoved = haveMove = true;
+        var block = clonestate.getBlock(j, i);
+        block.needMove = true;
+        block.needCombine = false;
+        clonestate.swapBlock(k * clonestate.mode + i, j * clonestate.mode + i);
+      }
+
+      if (k < clonestate.mode - 1 &&
+          clonestate.getBlock(k, i).value ==
+              clonestate.getBlock(k + 1, i).value &&
+          !clonestate.getBlock(k + 1, i).needCombine) {
+        var currentBlock = clonestate.getBlock(k, i);
+        var prevBlock = clonestate.getBlock(k + 1, i);
+        prevBlock.before =
+            isMoved ? currentBlock.before : (k * clonestate.mode + i);
+
+        prevBlock.current = (k + 1) * clonestate.mode + i;
+        prevBlock.needMove = true;
+        prevBlock.needCombine = haveCombin = true;
+        prevBlock.value <<= 1;
+        clonestate.status.scores += prevBlock.value;
+        currentBlock.reset();
+        currentBlock.current = currentBlock.before = k * clonestate.mode + i;
+      } else {
+        k--;
+      }
+      j--;
+    }
+  }
+
+  if (haveMove || haveCombin) {
+    clonestate.update();
+  }
+  return clonestate;
+}

+ 53 - 0
lib/reducers/moveLeft.dart

@@ -0,0 +1,53 @@
+import 'package:flutter_2048/store/GameState.dart';
+
+class MoveLeftAction {}
+
+GameState moveLeft(GameState state, MoveLeftAction action) {
+  if (state.status.end) return state.clone();
+
+  var clonestate = state.clone();
+  int i, j, k;
+  bool isMoved = false;
+  bool haveMove = false;
+  bool haveCombin = false;
+  for (i = 0; i < clonestate.mode; i++) {
+    j = k = 0;
+    while (true) {
+      while (j < clonestate.mode && clonestate.getBlock(i, j).value == 0) j++;
+      if (j > clonestate.mode - 1) break;
+
+      if (j > k) {
+        isMoved = haveMove = true;
+        var block = clonestate.getBlock(i, j);
+        block.needMove = true;
+        block.needCombine = false;
+        clonestate.swapBlock(i * clonestate.mode + k, i * clonestate.mode + j);
+      }
+
+      if (k > 0 &&
+          clonestate.getBlock(i, k).value ==
+              clonestate.getBlock(i, k - 1).value &&
+          clonestate.getBlock(i, k - 1).needCombine != true) {
+        var currentBlock = clonestate.getBlock(i, k);
+        var prevBlock = clonestate.getBlock(i, k - 1);
+        prevBlock.before =
+            isMoved ? currentBlock.before : (i * clonestate.mode + k);
+        prevBlock.current = i * clonestate.mode + k - 1;
+        prevBlock.needMove = true;
+        prevBlock.needCombine = haveCombin = true;
+        prevBlock.value <<= 1;
+        clonestate.status.scores += prevBlock.value;
+        currentBlock.reset();
+        currentBlock.current = currentBlock.before = i * clonestate.mode + k;
+      } else {
+        k++;
+      }
+      j++;
+    }
+  }
+
+  if (haveMove || haveCombin) {
+    clonestate.update();
+  }
+  return clonestate;
+}

+ 53 - 0
lib/reducers/moveRight.dart

@@ -0,0 +1,53 @@
+import 'package:flutter_2048/store/GameState.dart';
+
+class MoveRightAction {}
+
+GameState moveRight(GameState state, MoveRightAction action) {
+  if (state.status.end) return state.clone();
+
+  var clonestate = state.clone();
+  int i, j, k;
+  bool isMoved = false;
+  bool haveMove = false;
+  bool haveCombin = false;
+  for (i = 0; i < clonestate.mode; i++) {
+    j = k = clonestate.mode - 1;
+    while (true) {
+      while (j > -1 && clonestate.getBlock(i, j).value == 0) j--;
+      if (j < 0) break;
+
+      if (j < k) {
+        isMoved = haveMove = true;
+        var block = clonestate.getBlock(i, j);
+        block.needMove = true;
+        block.needCombine = false;
+        clonestate.swapBlock(i * clonestate.mode + k, i * clonestate.mode + j);
+      }
+
+      if (k < clonestate.mode - 1 &&
+          clonestate.getBlock(i, k).value ==
+              clonestate.getBlock(i, k + 1).value &&
+          clonestate.getBlock(i, k + 1).needCombine != true) {
+        var currentBlock = clonestate.getBlock(i, k);
+        var prevBlock = clonestate.getBlock(i, k + 1);
+        prevBlock.before =
+            isMoved ? currentBlock.before : (i * clonestate.mode + k);
+        prevBlock.current = i * clonestate.mode + k + 1;
+        prevBlock.needMove = true;
+        prevBlock.needCombine = haveCombin = true;
+        prevBlock.value <<= 1;
+        clonestate.status.scores += prevBlock.value;
+        currentBlock.reset();
+        currentBlock.current = currentBlock.before = i * clonestate.mode + k;
+      } else {
+        k--;
+      }
+      j--;
+    }
+  }
+
+  if (haveMove || haveCombin) {
+    clonestate.update();
+  }
+  return clonestate;
+}

+ 53 - 0
lib/reducers/moveUp.dart

@@ -0,0 +1,53 @@
+import 'package:flutter_2048/store/GameState.dart';
+
+class MoveUpAction {}
+
+GameState moveUp(GameState state, MoveUpAction action) {
+  if (state.status.end) return state.clone();
+
+  var clonestate = state.clone();
+  int i, j, k;
+  bool isMoved = false;
+  bool haveMove = false;
+  bool haveCombin = false;
+  for (i = 0; i < clonestate.mode; i++) {
+    j = k = 0;
+    while (true) {
+      while (j < clonestate.mode && clonestate.getBlock(j, i).value == 0) j++;
+      if (j > clonestate.mode - 1) break;
+
+      if (j > k) {
+        isMoved = haveMove = true;
+        var block = clonestate.getBlock(j, i);
+        block.needMove = true;
+        block.needCombine = false;
+        clonestate.swapBlock(k * clonestate.mode + i, j * clonestate.mode + i);
+      }
+
+      if (k > 0 &&
+          clonestate.getBlock(k, i).value ==
+              clonestate.getBlock(k - 1, i).value &&
+          clonestate.getBlock(k - 1, i).needCombine != true) {
+        var currentBlock = clonestate.getBlock(k, i);
+        var prevBlock = clonestate.getBlock(k - 1, i);
+        prevBlock.before =
+            isMoved ? currentBlock.before : (k * clonestate.mode + i);
+        prevBlock.current = (k - 1) * clonestate.mode + i;
+        prevBlock.needMove = true;
+        prevBlock.needCombine = haveCombin = true;
+        prevBlock.value <<= 1;
+        clonestate.status.scores += prevBlock.value;
+        currentBlock.reset();
+        currentBlock.current = currentBlock.before = k * clonestate.mode + i;
+      } else {
+        k++;
+      }
+      j++;
+    }
+  }
+
+  if (haveMove || haveCombin) {
+    clonestate.update();
+  }
+  return clonestate;
+}

+ 11 - 0
lib/reducers/updateState.dart

@@ -0,0 +1,11 @@
+import 'package:flutter_2048/store/GameState.dart';
+
+class UpdateStateAction {
+  GameState state;
+
+  UpdateStateAction(this.state);
+}
+
+GameState updateState(GameState state, UpdateStateAction action) {
+  return action.state;
+}

+ 60 - 0
lib/service/BlockFactory.dart

@@ -0,0 +1,60 @@
+import 'package:flutter/animation.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_2048/views/block/CombinBlock.dart';
+import 'package:flutter_2048/views/block/MoveBlock.dart';
+import 'package:flutter_2048/views/block/NewBlock.dart';
+import 'package:flutter_2048/views/block/StaticBlock.dart';
+import 'package:flutter_2048/model/BlockInfo.dart';
+
+class BlockFactory {
+  AnimationController combinController;
+  AnimationController addController;
+  AnimationController moveController;
+  int _mode;
+
+  BlockFactory(TickerProvider provider, int mode) {
+    combinController = AnimationController(
+        duration: const Duration(milliseconds: 60), vsync: provider);
+    addController = AnimationController(
+        duration: const Duration(milliseconds: 80), vsync: provider);
+    moveController = AnimationController(
+        duration: const Duration(milliseconds: 95), vsync: provider);
+    _mode = mode;
+  }
+
+  Widget create(BlockInfo info) {
+    if (info.isNew) {
+      return NewBlock(
+        info: info,
+        controller: this.addController,
+      );
+    }
+
+    if (info.needMove && info.needCombine) {
+      return CombinBlock(
+        info: info,
+        mode: _mode,
+        combinController: combinController,
+        moveController: moveController,
+      );
+    }
+
+    if (info.needMove && info.needCombine != true) {
+      return MoveBlock(info: info, mode: _mode, controller: moveController);
+    }
+
+    return StaticBlock(
+      info: info,
+      controller: this.addController,
+    );
+  }
+
+  play() {
+    moveController.forward().whenComplete(() {
+      addController.forward();
+      combinController.forward().whenComplete(() {
+        combinController.reverse();
+      });
+    });
+  }
+}

+ 27 - 0
lib/service/gameInit.dart

@@ -0,0 +1,27 @@
+import 'package:flutter_2048/reducers/updateState.dart';
+import 'package:flutter_2048/store/GameState.dart';
+import 'package:flutter_2048/model/GameStatus.dart';
+import 'package:redux/redux.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+gameInit(Store<GameState> store, int mode) async {
+  SharedPreferences prefs = await SharedPreferences.getInstance();
+
+  var key = 'total_' + mode.toString();
+
+  if (store.state.status.total != null &&
+      store.state.status.scores > store.state.status.total) {
+    prefs.setInt(key, store.state.status.scores);
+  }
+  var state = GameState.initial(mode);
+
+  state.status = GameStatus(
+    adds: 0,
+    end: false,
+    moves: 0,
+    total: prefs.getInt(key) ?? 0,
+    scores: 0,
+  );
+
+  store.dispatch(UpdateStateAction(state));
+}

+ 159 - 0
lib/store/GameState.dart

@@ -0,0 +1,159 @@
+import 'dart:math';
+
+import 'package:flutter_2048/model/BlockInfo.dart';
+import 'package:flutter_2048/model/GameStatus.dart';
+
+class GameState {
+  GameState({this.data, this.status, this.mode});
+
+  int mode;
+  GameStatus status;
+  List<List<BlockInfo>> data;
+
+  /// 初始化
+  static GameState initial(int mode) {
+    var random = new Random(DateTime.now().millisecondsSinceEpoch);
+    var gamesize = mode * mode;
+    var block1 = random.nextInt(gamesize);
+    var block2 = random.nextInt(gamesize);
+
+    while (block1 == block2) {
+      block2 = random.nextInt(gamesize);
+    }
+
+    var newdata = List<List<BlockInfo>>();
+
+    for (var i = 0; i < mode; i++) {
+      var row = List<BlockInfo>();
+      for (var j = 0; j < mode; j++) {
+        var current = i * mode + j;
+        row.add(BlockInfo(
+            value: current == block1 || current == block2 ? 2 : 0,
+            current: current));
+      }
+      newdata.add(row);
+    }
+
+    return GameState(
+      mode: mode,
+      status: GameStatus(
+        end: false,
+        scores: 0,
+        total: null,
+      ),
+      data: newdata,
+    );
+  }
+
+  BlockInfo getBlock(int i, int j) {
+    return this.data[i][j];
+  }
+
+  void update() {
+    // 获取空格数, 将上一次的所有格子设成旧的
+    int count = 0;
+    for (var i = 0; i < mode; i++) {
+      for (var j = 0; j < mode; j++) {
+        var block = getBlock(i, j);
+        block.isNew = false;
+        if (block.value == 0) {
+          count++;
+        }
+      }
+    }
+
+    // 有空格
+    if (count > 0) {
+      // 生成新的数字
+      var random = new Random(DateTime.now().millisecondsSinceEpoch);
+      var newpos = getBlankPosition(random.nextInt(count));
+
+      var newblock = getBlock(newpos ~/ mode, newpos % mode);
+      newblock.value = (random.nextInt(2) + 1) * 2;
+      newblock.before = newblock.current = newpos;
+      newblock.isNew = true;
+      newblock.needCombine = newblock.needMove = false;
+    }
+
+    // 检测
+    status.end = false;
+    if (count <= 1) {
+      status.end = isEnd();
+    }
+  }
+
+  bool isEnd() {
+    int i, j;
+    for (i = 0; i < mode; i++) {
+      for (j = 0; j < mode - 1; j++) {
+        if (data[i][j].value == data[i][j + 1].value) {
+          return false;
+        }
+      }
+    }
+
+    for (j = 0; j < mode; j++) {
+      for (i = 0; i < mode - 1; i++) {
+        if (data[i][j].value == data[i + 1][j].value) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  int getBlankPosition(int blank) {
+    var index = 0;
+    for (int i = 0; i < mode; i++) {
+      for (int j = 0; j < mode; j++) {
+        if (getBlock(i, j).value == 0) {
+          if (index == blank) {
+            return i * mode + j;
+          } else {
+            index++;
+          }
+        }
+      }
+    }
+    return -1;
+  }
+
+  void swapBlock(int block1, int block2) {
+    data[block1 ~/ mode][block1 % mode].current = block2;
+    data[block1 ~/ mode][block1 % mode].before = block1;
+    data[block2 ~/ mode][block2 % mode].current = block1;
+    data[block2 ~/ mode][block2 % mode].before = block2;
+    var temp = data[block1 ~/ mode][block1 % mode];
+    data[block1 ~/ mode][block1 % mode] = data[block2 ~/ mode][block2 % mode];
+    data[block2 ~/ mode][block2 % mode] = temp;
+  }
+
+  GameState clone() {
+    var newdata = List<List<BlockInfo>>();
+    for (var i = 0; i < mode; i++) {
+      var row = List<BlockInfo>();
+      for (var j = 0; j < mode; j++) {
+        row.add(BlockInfo(
+          current: data[i][j].current,
+          value: data[i][j].value,
+          isNew: false,
+        ));
+      }
+      newdata.add(row);
+    }
+
+    return GameState(
+      data: newdata,
+      mode: this.mode,
+      status: this.status == null
+          ? null
+          : GameStatus(
+              adds: this.status.adds,
+              end: this.status.end,
+              moves: this.status.moves,
+              scores: this.status.scores,
+              total: this.status.total,
+            ),
+    );
+  }
+}

+ 30 - 0
lib/utils/Device.dart

@@ -0,0 +1,30 @@
+import 'dart:io';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+
+class Device {
+  static getRatio(int value) {
+    int uiwidth = value is int ? value : 750;
+    // return ScreenUtil.mediaQueryData.size.width / uiwidth;
+    return ScreenUtil().uiSize.width / uiwidth;
+  }
+
+  static getRpx(double value) {
+    return value * getRatio(750);
+  }
+
+  static getBottomPadding() {
+    return ScreenUtil().uiSize.height;
+  }
+
+  static getTopPadding() {
+    // return ScreenUtil.mediaQueryData.padding.top;
+    return ScreenUtil().uiSize.height;
+  }
+
+  static getWidth() {
+    return ScreenUtil().uiSize.width;
+    // return ScreenUtil.mediaQueryData.size.width;
+  }
+
+  static Platform platform = Platform();
+}

+ 16 - 0
lib/utils/Screen.dart

@@ -0,0 +1,16 @@
+import '../model/Display.dart';
+import './Device.dart';
+
+class Screen {
+  /// 随着大小间距变化
+  static double getBorderWidth(int gameType) {
+    return Display.spacerUnit / gameType * Display.borderMargin;
+  }
+
+  static double getBlockWidth(int gameType) {
+    return (Screen.stageWidth - getBorderWidth(gameType) * (gameType + 1)) /
+        gameType;
+  }
+
+  static double get stageWidth => Device.getWidth() - Display.borderMargin * 2;
+}

+ 57 - 0
lib/views/Blocks.dart

@@ -0,0 +1,57 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_2048/service/BlockFactory.dart';
+import 'package:flutter_2048/model/BlockInfo.dart';
+import 'package:flutter_2048/store/GameState.dart';
+import 'package:flutter_2048/utils/Screen.dart';
+import 'package:flutter_redux/flutter_redux.dart';
+
+class Blocks extends StatefulWidget {
+  @override
+  _BlocksState createState() => _BlocksState();
+}
+
+class _BlocksState extends State<Blocks> with TickerProviderStateMixin {
+  @override
+  Widget build(BuildContext context) {
+    return StoreConnector<GameState, BlocksProps>(
+      converter: (store) => BlocksProps(
+            data: store.state.data,
+            mode: store.state.mode,
+            padding: Screen.getBorderWidth(store.state.mode),
+          ),
+      builder: (context, props) {
+        var blockFactory = BlockFactory(this, props.mode);
+        blockFactory.play();
+        return Container(
+          width: Screen.stageWidth,
+          height: Screen.stageWidth,
+          padding: EdgeInsets.fromLTRB(props.padding, props.padding, 0, 0),
+          child: Stack(
+            fit: StackFit.expand,
+            children: getBlocks(blockFactory, props),
+          ),
+        );
+      },
+    );
+  }
+
+  getBlocks(BlockFactory blockFactory, BlocksProps props) {
+    var blocks = <Widget>[];
+    props.data.forEach((row) {
+      row.forEach((block) {
+        if (block.value != 0) {
+          blocks.add(blockFactory.create(block));
+        }
+      });
+    });
+    return blocks;
+  }
+}
+
+class BlocksProps {
+  int mode;
+  double padding;
+  List<List<BlockInfo>> data;
+
+  BlocksProps({this.padding, this.mode, this.data});
+}

+ 71 - 0
lib/views/GameBg.dart

@@ -0,0 +1,71 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_redux/flutter_redux.dart';
+import '../store/GameState.dart';
+import '../utils/Screen.dart';
+/// 游戏格子背景
+class GameBg extends StatefulWidget {
+  @override
+  State<StatefulWidget> createState() => GameBgState();
+}
+
+class GameBgState extends State<StatefulWidget> {
+
+  @override
+  Widget build(BuildContext context) {
+    return StoreConnector<GameState, GameBgProps>(
+      converter: (store) => GameBgProps(
+        borderWidth: Screen.getBorderWidth(store.state.mode),
+        blockWidth: Screen.getBlockWidth(store.state.mode),
+        mode: store.state.mode,
+      ),
+      builder: (context, vm) {
+        return Container(
+          padding: EdgeInsets.fromLTRB(vm.borderWidth, vm.borderWidth, 0, 0),
+          decoration: BoxDecoration(
+            color: const Color(0xffbbada0),
+            border: Border.all(color: Colors.transparent, width: 0),
+            borderRadius: BorderRadius.circular(5),
+          ),
+          child: getGrid(vm),
+        );
+      },
+    );
+  }
+
+  getGrid(GameBgProps props) {
+    var rows = <Widget>[];
+    for (var i = 0; i < props.mode; i++) {
+      var columns = <Widget>[];
+      for (var j = 0; j < props.mode; j++) {
+        columns.add(Container(
+          width: props.blockWidth,
+          height: props.blockWidth,
+          decoration: BoxDecoration(
+            color: Color.fromRGBO(238, 228, 218, 0.35),
+            border: Border.all(color: Colors.transparent, width: 0),
+            borderRadius: BorderRadius.circular(5),
+          ),
+          margin:
+              EdgeInsets.fromLTRB(0, 0, props.borderWidth, props.borderWidth),
+        ));
+      }
+      rows.add(Row(
+        textDirection: TextDirection.ltr,
+        crossAxisAlignment: CrossAxisAlignment.center,
+        children: columns,
+      ));
+    }
+    return Column(
+      children: rows,
+      crossAxisAlignment: CrossAxisAlignment.center,
+    );
+  }
+}
+
+class GameBgProps {
+  double borderWidth;
+  double blockWidth;
+  int mode;
+
+  GameBgProps({this.borderWidth, this.blockWidth, this.mode});
+}

+ 52 - 0
lib/views/ModeSelector.dart

@@ -0,0 +1,52 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_2048/service/gameInit.dart';
+import 'package:flutter_redux/flutter_redux.dart';
+import '../store/GameState.dart';
+// 选择模式界面
+class ModeSelector extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return StoreConnector<GameState, ModeSelectorProps>(
+      converter: (store) {
+        return ModeSelectorProps(
+          mode: store.state.mode,
+          onChange: (mode) => gameInit(store, mode),
+        );
+      },
+      builder: (context, vm) {
+        return Container(
+          child: Row(
+            mainAxisAlignment: MainAxisAlignment.spaceBetween,
+            children: <Widget>[
+              FlatButton(
+                color: Color(0xff8f7a66),
+                textColor: Colors.white,
+                onPressed: () => vm.onChange(3),
+                child: Text('3 x 3'),
+              ),
+              FlatButton(
+                color: Color(0xff8f7a66),
+                textColor: Colors.white,
+                onPressed: () => vm.onChange(4),
+                child: Text('4 x 4'),
+              ),
+              FlatButton(
+                color: Color(0xff8f7a66),
+                textColor: Colors.white,
+                onPressed: () => vm.onChange(6),
+                child: Text('6 x 6'),
+              ),
+            ],
+          ),
+        );
+      },
+    );
+  }
+}
+
+class ModeSelectorProps {
+  ModeSelectorProps({this.mode, this.onChange});
+
+  int mode;
+  Function onChange;
+}

+ 69 - 0
lib/views/NumberText.dart

@@ -0,0 +1,69 @@
+import 'package:flutter/material.dart';
+
+class BlockStyle {
+  Color textColor;
+  Color background;
+
+  BlockStyle({this.textColor, this.background});
+}
+
+var styles = Map.fromEntries([
+  MapEntry(2,
+      BlockStyle(textColor: Color(0xff776e65), background: Color(0xffeee4da))),
+  MapEntry(4,
+      BlockStyle(textColor: Color(0xff776e65), background: Color(0xffede0c8))),
+  MapEntry(8,
+      BlockStyle(textColor: Color(0xfff9f6f2), background: Color(0xfff2b179))),
+  MapEntry(16,
+      BlockStyle(textColor: Color(0xfff9f6f2), background: Color(0xfff59563))),
+  MapEntry(32,
+      BlockStyle(textColor: Color(0xfff9f6f2), background: Color(0xfff67c5f))),
+  MapEntry(64,
+      BlockStyle(textColor: Color(0xfff9f6f2), background: Color(0xfff65e3b))),
+  MapEntry(128,
+      BlockStyle(textColor: Color(0xfff9f6f2), background: Color(0xffedcf72))),
+  MapEntry(256,
+      BlockStyle(textColor: Color(0xfff9f6f2), background: Color(0xffedcc61))),
+  MapEntry(512,
+      BlockStyle(textColor: Color(0xfff9f6f2), background: Color(0xffedc850))),
+  MapEntry(1024,
+      BlockStyle(textColor: Color(0xfff9f6f2), background: Color(0xffedc53f))),
+  MapEntry(2048,
+      BlockStyle(textColor: Color(0xfff9f6f2), background: Color(0xffedc22e))),
+]);
+
+class NumberText extends StatelessWidget {
+  final int value;
+  final double size;
+
+  NumberText({this.value, this.size});
+
+  @override
+  Widget build(BuildContext context) {
+    var numberText = this.value.toString();
+    return Container(
+      width: size,
+      height: size,
+      decoration: BoxDecoration(
+        color: (this.value ~/ 2048) > 1
+            ? styles[this.value ~/ 2048].background
+            : styles[this.value].background,
+        border: Border.all(color: Colors.transparent, width: 0),
+        borderRadius: BorderRadius.circular(5),
+      ),
+      child: Center(
+        child: Text(
+          numberText,
+          style: TextStyle(
+            fontWeight: FontWeight.bold,
+            color: (this.value ~/ 2048) > 1
+                ? styles[this.value ~/ 2048].textColor
+                : styles[this.value].textColor,
+            fontSize:
+                size / (numberText.length <= 2 ? 2 : numberText.length * 0.8),
+          ),
+        ),
+      ),
+    );
+  }
+}

+ 109 - 0
lib/views/Playground.dart

@@ -0,0 +1,109 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_redux/flutter_redux.dart';
+import '../reducers/moveDown.dart';
+import '../reducers/moveLeft.dart';
+import '../reducers/moveRight.dart';
+import '../reducers/moveUp.dart';
+import '../store/GameState.dart';
+import '../utils/Screen.dart';
+
+const pressTimeout = 200;
+const dragLength = 300;
+/// 上下左右滑动事件
+class Playground extends StatelessWidget {
+  
+  @override
+  Widget build(BuildContext context) {
+    return StoreConnector<GameState, PlaygroundProps>(
+      converter: (store) {
+        return PlaygroundProps(
+          end: store.state.status.end,
+          mode: store.state.mode,
+          startTime: 0,
+          onDown: () => store.dispatch(MoveDownAction()),
+          onLeft: () => store.dispatch(MoveLeftAction()),
+          onRight: () => store.dispatch(MoveRightAction()),
+          onUp: () => store.dispatch(MoveUpAction()),
+        );
+      },
+      builder: (context, props) {
+        return props.end != true
+            ? GestureDetector(
+                onHorizontalDragStart: (evt) => onDragStart(evt, props),
+                onHorizontalDragEnd: (evt) => onHorizontalDragEnd(evt, props),
+                onVerticalDragStart: (evt) => onDragStart(evt, props),
+                onVerticalDragEnd: (evt) => onVerticalDragEnd(evt, props),
+                child: Container(
+                  color: Colors.transparent,
+                  height: Screen.stageWidth,
+                ),
+              )
+            : Container(
+                decoration: BoxDecoration(
+                    borderRadius: BorderRadius.circular(5),
+                    color: Color.fromRGBO(255, 255, 255, 0.4)),
+                height: Screen.stageWidth,
+                child: Center(
+                  child: Text(
+                    'Game Over',
+                    style: TextStyle(
+                      fontSize: 50,
+                      color: Color(0xff776e65),
+                      fontWeight: FontWeight.bold,
+                    ),
+                  ),
+                ),
+              );
+      },
+    );
+  }
+
+  void onDragStart(DragStartDetails evt, PlaygroundProps props) {
+    props.startTime = DateTime.now().millisecondsSinceEpoch;
+  }
+
+  void onHorizontalDragEnd(DragEndDetails evt, PlaygroundProps props) {
+    if (DateTime.now().millisecondsSinceEpoch - props.startTime >
+            pressTimeout ||
+        evt.primaryVelocity.abs() < dragLength) return;
+
+    if (evt.primaryVelocity > 0) {
+      props.onRight();
+    } else {
+      props.onLeft();
+    }
+  }
+
+  void onVerticalDragEnd(DragEndDetails evt, PlaygroundProps props) {
+    if (DateTime.now().millisecondsSinceEpoch - props.startTime >
+            pressTimeout ||
+        evt.primaryVelocity.abs() < dragLength) return;
+    // 是否ios和android纵轴是相反的?
+    if (evt.primaryVelocity < 0) {
+      props.onUp();
+    } else {
+      props.onDown();
+    }
+  }
+}
+
+class PlaygroundProps {
+  int mode;
+  bool end;
+  int startTime;
+  Function onLeft;
+  Function onRight;
+  Function onUp;
+  Function onDown;
+
+  PlaygroundProps({
+    this.end,
+    this.mode,
+    this.startTime,
+    this.onDown,
+    this.onLeft,
+    this.onRight,
+    this.onUp,
+  });
+}

+ 139 - 0
lib/views/Scores.dart

@@ -0,0 +1,139 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_2048/service/gameInit.dart';
+import 'package:flutter_2048/store/GameState.dart';
+import 'package:flutter_redux/flutter_redux.dart';
+
+class Scores extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return StoreConnector<GameState, ScoresProps>(
+      converter: (store) => ScoresProps(
+        scores: store.state.status.scores,
+        total: store.state.status.total,
+        isEnd: store.state.status.end,
+        reset: () {
+          gameInit(store, store.state.mode);
+        },
+      ),
+      // onDidChange: (props) {
+      //   if (props.isEnd && props.scores > props.total) {
+      //     SharedPreferences.getInstance().then((refs) {
+      //       refs.setInt('total_' + props.mode.toString(), props.scores);
+      //     });
+      //   }
+      // },
+      builder: (context, props) {
+        return Column(
+          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+          children: <Widget>[
+            Row(
+              mainAxisAlignment: MainAxisAlignment.spaceBetween,
+              children: <Widget>[
+                Text(
+                  '2048',
+                  style: TextStyle(
+                      fontSize: 50,
+                      color: Color(0xff776e65),
+                      fontWeight: FontWeight.bold),
+                ),
+                Row(
+                  mainAxisAlignment: MainAxisAlignment.end,
+                  children: <Widget>[
+                    Container(
+                      padding: EdgeInsets.fromLTRB(23, 5, 23, 5),
+                      margin: EdgeInsets.only(right: 5),
+                      decoration: BoxDecoration(
+                        color: Color(0xffbbada0),
+                        border: Border.all(color: Colors.transparent, width: 0),
+                        borderRadius: BorderRadius.circular(5),
+                      ),
+                      child: Column(
+                        children: <Widget>[
+                          Text(
+                            'SCORE',
+                            style: TextStyle(
+                                color: Color(0xffeee4da),
+                                fontWeight: FontWeight.bold),
+                          ),
+                          Text(
+                            props.scores.toString(),
+                            style: TextStyle(
+                                color: Colors.white,
+                                fontWeight: FontWeight.bold),
+                          )
+                        ],
+                      ),
+                    ),
+                    Container(
+                      padding: EdgeInsets.fromLTRB(23, 5, 23, 5),
+                      decoration: BoxDecoration(
+                        color: Color(0xffbbada0),
+                        border: Border.all(color: Colors.transparent, width: 0),
+                        borderRadius: BorderRadius.circular(5),
+                      ),
+                      child: Column(
+                        children: <Widget>[
+                          Text(
+                            'BEST',
+                            style: TextStyle(
+                                color: Color(0xffeee4da),
+                                fontWeight: FontWeight.bold),
+                          ),
+                          Text(
+                            props.total.toString(),
+                            style: TextStyle(
+                                color: Colors.white,
+                                fontWeight: FontWeight.bold),
+                          )
+                        ],
+                      ),
+                    )
+                  ],
+                ),
+              ],
+            ),
+            Row(
+              mainAxisAlignment: MainAxisAlignment.spaceBetween,
+              children: <Widget>[
+                Column(
+                  crossAxisAlignment: CrossAxisAlignment.start,
+                  children: <Widget>[
+                    Text(
+                      'Play 2048 Game flutter',
+                      style: TextStyle(
+                          color: Color(0xff776e65),
+                          fontWeight: FontWeight.bold),
+                    ),
+                    Text(
+                      'Join and get to the 2048 tile!',
+                      style: TextStyle(color: Color(0xff776e65)),
+                    ),
+                  ],
+                ),
+                FlatButton(
+                  color: Color(0xff8f7a66),
+                  textColor: Colors.white,
+                  onPressed: () => props.reset(),
+                  child: Text(
+                    'New Game',
+                    style: TextStyle(fontWeight: FontWeight.bold),
+                  ),
+                )
+              ],
+            ),
+          ],
+        );
+      },
+    );
+  }
+}
+
+class ScoresProps {
+  ScoresProps({this.mode, this.total, this.scores, this.isEnd, this.reset});
+
+  int mode;
+  int total;
+  int scores;
+  bool isEnd;
+  Function reset;
+}

+ 38 - 0
lib/views/block/BaseBlock.dart

@@ -0,0 +1,38 @@
+import 'package:flutter/widgets.dart';
+import 'package:flutter_2048/store/GameState.dart';
+import 'package:flutter_2048/utils/Screen.dart';
+import 'package:flutter_redux/flutter_redux.dart';
+
+abstract class BaseBlock extends AnimatedWidget {
+  BaseBlock({Key key, Animation animation})
+      : super(
+          key: key,
+          listenable: animation,
+        );
+
+  @override
+  Widget build(BuildContext context) {
+    return StoreConnector<GameState, BlockProps>(
+      converter: (store) => BlockProps(
+            blockWidth: Screen.getBlockWidth(store.state.mode),
+            borderWidth: Screen.getBorderWidth(store.state.mode),
+            mode: store.state.mode,
+          ),
+      builder: buildBlock,
+    );
+  }
+
+  @protected
+  Widget buildBlock(
+    BuildContext context,
+    BlockProps props,
+  );
+}
+
+class BlockProps {
+  double blockWidth;
+  double borderWidth;
+  int mode;
+
+  BlockProps({this.blockWidth, this.borderWidth, this.mode});
+}

+ 54 - 0
lib/views/block/CombinBlock.dart

@@ -0,0 +1,54 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_2048/views/NumberText.dart';
+import 'package:flutter_2048/views/block/BaseBlock.dart';
+import 'package:flutter_2048/views/block/MoveBlock.dart';
+import 'package:flutter_2048/model/BlockInfo.dart';
+
+class CombinBlock extends BaseBlock {
+  final BlockInfo info;
+  final int mode;
+  final AnimationController moveController;
+
+  CombinBlock({
+    Key key,
+    this.info,
+    this.mode,
+    this.moveController,
+    AnimationController combinController,
+  }) : super(
+          key: key,
+          animation:
+              Tween<double>(begin: 1, end: 1.25).animate(combinController),
+        );
+
+  @override
+  Widget buildBlock(BuildContext context, BlockProps props) {
+    Animation<double> animation = listenable;
+    return Stack(
+      fit: StackFit.expand,
+      children: <Widget>[
+        MoveBlock(
+          info: BlockInfo(
+            isNew: false,
+            value: info.value ~/ 2,
+            before: info.before,
+            current: info.current,
+          ),
+          mode: mode,
+          controller: moveController,
+        ),
+        Positioned(
+          top: (info.current ~/ props.mode) *
+              (props.blockWidth + props.borderWidth),
+          left: (info.current % props.mode) *
+              (props.blockWidth + props.borderWidth),
+          child: Transform.scale(
+            scale: animation.value,
+            origin: Offset(0.5, 0.5),
+            child: NumberText(value: this.info.value, size: props.blockWidth),
+          ),
+        )
+      ],
+    );
+  }
+}

+ 50 - 0
lib/views/block/MoveBlock.dart

@@ -0,0 +1,50 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_2048/views/NumberText.dart';
+import 'package:flutter_2048/views/block/BaseBlock.dart';
+import 'package:flutter_2048/model/BlockInfo.dart';
+
+double getBegin(BlockInfo info, int mode) {
+  return (info.current % mode == info.before % mode
+          ? info.before ~/ mode - info.current ~/ mode
+          : info.before % mode - info.current % mode) *
+      1.0;
+}
+
+class MoveBlock extends BaseBlock {
+  final BlockInfo info;
+  final int mode;
+
+  MoveBlock({
+    Key key,
+    this.info,
+    this.mode,
+    AnimationController controller,
+  }) : super(
+          key: key,
+          animation: Tween<double>(begin: getBegin(info, mode), end: 0)
+              .animate(controller),
+        );
+
+  @override
+  Widget buildBlock(BuildContext context, BlockProps props) {
+    Animation<double> animation = listenable;
+    var direction = info.current % mode == info.before % mode ? 1 : 0;
+    return Positioned(
+      top:
+          (info.current ~/ props.mode) * (props.blockWidth + props.borderWidth),
+      left:
+          (info.current % props.mode) * (props.blockWidth + props.borderWidth),
+      child: Transform.translate(
+        offset: direction == 0
+            ? Offset(
+                animation.value * (props.blockWidth + props.borderWidth), 0)
+            : Offset(
+                0, animation.value * (props.blockWidth + props.borderWidth)),
+        child: NumberText(value: this.info.value, size: props.blockWidth),
+      ),
+    );
+  }
+}
+
+void getDirection(double current, double before) {}

+ 33 - 0
lib/views/block/NewBlock.dart

@@ -0,0 +1,33 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_2048/views/NumberText.dart';
+import 'package:flutter_2048/views/block/BaseBlock.dart';
+import 'package:flutter_2048/model/BlockInfo.dart';
+
+class NewBlock extends BaseBlock {
+  final BlockInfo info;
+
+  NewBlock({
+    Key key,
+    this.info,
+    AnimationController controller,
+  }) : super(
+          key: key,
+          animation: new Tween<double>(begin: 0.1, end: 1.0).animate(controller),
+        );
+
+  @override
+  Widget buildBlock(BuildContext context, BlockProps props) {
+    Animation<double> animation = listenable;
+    return Positioned(
+      top:
+          (info.current ~/ props.mode) * (props.blockWidth + props.borderWidth),
+      left:
+          (info.current % props.mode) * (props.blockWidth + props.borderWidth),
+      child: Transform.scale(
+        scale: animation.value,
+        origin: Offset(0.5, 0.5),
+        child: NumberText(value: this.info.value, size: props.blockWidth),
+      ),
+    );
+  }
+}

+ 30 - 0
lib/views/block/StaticBlock.dart

@@ -0,0 +1,30 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_2048/views/NumberText.dart';
+import 'package:flutter_2048/views/block/BaseBlock.dart';
+import 'package:flutter_2048/model/BlockInfo.dart';
+
+class StaticBlock extends BaseBlock {
+  final BlockInfo info;
+
+  StaticBlock({
+    Key key,
+    this.info,
+    AnimationController controller,
+  }) : super(
+          key: key,
+          animation:
+              new Tween<double>(begin: 0.0, end: 0.0).animate(controller),
+        );
+
+  @override
+  Widget buildBlock(BuildContext context, BlockProps props) {
+    return Positioned(
+      top:
+          (info.current ~/ props.mode) * (props.blockWidth + props.borderWidth),
+      left:
+          (info.current % props.mode) * (props.blockWidth + props.borderWidth),
+      child: NumberText(value: this.info.value, size: props.blockWidth),
+    );
+  }
+}

+ 9 - 0
linux/flutter/generated_plugin_registrant.cc

@@ -0,0 +1,9 @@
+//
+//  Generated file. Do not edit.
+//
+
+#include "generated_plugin_registrant.h"
+
+
+void fl_register_plugins(FlPluginRegistry* registry) {
+}

+ 13 - 0
linux/flutter/generated_plugin_registrant.h

@@ -0,0 +1,13 @@
+//
+//  Generated file. Do not edit.
+//
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include <flutter_linux/flutter_linux.h>
+
+// Registers Flutter plugins.
+void fl_register_plugins(FlPluginRegistry* registry);
+
+#endif  // GENERATED_PLUGIN_REGISTRANT_

+ 15 - 0
linux/flutter/generated_plugins.cmake

@@ -0,0 +1,15 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)

+ 25 - 0
linux/flutter/generated_plugins.mk

@@ -0,0 +1,25 @@
+# Plugins to include in the build.
+GENERATED_PLUGINS=\
+
+GENERATED_PLUGINS_DIR=flutter\ephemeral\.plugin_symlinks
+# A plugin library name plugin name with _plugin appended.
+GENERATED_PLUGIN_LIB_NAMES=$(foreach plugin,$(GENERATED_PLUGINS),$(plugin)_plugin)
+
+# Variables for use in the enclosing Makefile. Changes to these names are
+# breaking changes.
+PLUGIN_TARGETS=$(GENERATED_PLUGINS)
+PLUGIN_LIBRARIES=$(foreach plugin,$(GENERATED_PLUGIN_LIB_NAMES),\
+	$(OUT_DIR)/lib$(plugin).so)
+PLUGIN_LDFLAGS=$(patsubst %,-l%,$(GENERATED_PLUGIN_LIB_NAMES))
+PLUGIN_CPPFLAGS=$(foreach plugin,$(GENERATED_PLUGINS),\
+	-I$(GENERATED_PLUGINS_DIR)/$(plugin)/linux)
+
+# Targets
+
+# Implicit rules don't match phony targets, so list plugin builds explicitly.
+
+.PHONY: $(GENERATED_PLUGINS)
+$(GENERATED_PLUGINS):
+	make -C $(GENERATED_PLUGINS_DIR)/$@/linux \
+		OUT_DIR=$(OUT_DIR) \
+		FLUTTER_EPHEMERAL_DIR="$(abspath flutter\ephemeral)"

+ 332 - 0
pubspec.lock

@@ -0,0 +1,332 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+  async:
+    dependency: transitive
+    description:
+      name: async
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.5.0"
+  boolean_selector:
+    dependency: transitive
+    description:
+      name: boolean_selector
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.0"
+  characters:
+    dependency: transitive
+    description:
+      name: characters
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.0"
+  charcode:
+    dependency: transitive
+    description:
+      name: charcode
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.2.0"
+  clock:
+    dependency: transitive
+    description:
+      name: clock
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.0"
+  collection:
+    dependency: transitive
+    description:
+      name: collection
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.15.0"
+  cupertino_icons:
+    dependency: "direct main"
+    description:
+      name: cupertino_icons
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.3"
+  device_info:
+    dependency: "direct main"
+    description:
+      name: device_info
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.2"
+  device_info_platform_interface:
+    dependency: transitive
+    description:
+      name: device_info_platform_interface
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.1"
+  fake_async:
+    dependency: transitive
+    description:
+      name: fake_async
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.2.0"
+  ffi:
+    dependency: transitive
+    description:
+      name: ffi
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.2"
+  file:
+    dependency: transitive
+    description:
+      name: file
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "6.1.2"
+  flutter:
+    dependency: "direct main"
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  flutter_localizations:
+    dependency: "direct main"
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  flutter_redux:
+    dependency: "direct main"
+    description:
+      name: flutter_redux
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.8.2"
+  flutter_screenutil:
+    dependency: "direct main"
+    description:
+      name: flutter_screenutil
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "4.0.4+1"
+  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"
+  intl:
+    dependency: "direct main"
+    description:
+      name: intl
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.17.0"
+  js:
+    dependency: transitive
+    description:
+      name: js
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.6.3"
+  matcher:
+    dependency: transitive
+    description:
+      name: matcher
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.12.10"
+  meta:
+    dependency: transitive
+    description:
+      name: meta
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.3.0"
+  path:
+    dependency: transitive
+    description:
+      name: path
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.8.0"
+  path_provider_linux:
+    dependency: transitive
+    description:
+      name: path_provider_linux
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.2"
+  path_provider_platform_interface:
+    dependency: transitive
+    description:
+      name: path_provider_platform_interface
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.1"
+  path_provider_windows:
+    dependency: transitive
+    description:
+      name: path_provider_windows
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.3"
+  platform:
+    dependency: transitive
+    description:
+      name: platform
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.0.2"
+  plugin_platform_interface:
+    dependency: transitive
+    description:
+      name: plugin_platform_interface
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.1"
+  process:
+    dependency: transitive
+    description:
+      name: process
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "4.2.3"
+  redux:
+    dependency: transitive
+    description:
+      name: redux
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "5.0.0"
+  redux_thunk:
+    dependency: "direct main"
+    description:
+      name: redux_thunk
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.4.0"
+  shared_preferences:
+    dependency: "direct main"
+    description:
+      name: shared_preferences
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.6"
+  shared_preferences_linux:
+    dependency: transitive
+    description:
+      name: shared_preferences_linux
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.2"
+  shared_preferences_macos:
+    dependency: transitive
+    description:
+      name: shared_preferences_macos
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.2"
+  shared_preferences_platform_interface:
+    dependency: transitive
+    description:
+      name: shared_preferences_platform_interface
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.0"
+  shared_preferences_web:
+    dependency: transitive
+    description:
+      name: shared_preferences_web
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.1"
+  shared_preferences_windows:
+    dependency: transitive
+    description:
+      name: shared_preferences_windows
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.2"
+  sky_engine:
+    dependency: transitive
+    description: flutter
+    source: sdk
+    version: "0.0.99"
+  source_span:
+    dependency: transitive
+    description:
+      name: source_span
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.8.0"
+  stack_trace:
+    dependency: transitive
+    description:
+      name: stack_trace
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.10.0"
+  stream_channel:
+    dependency: transitive
+    description:
+      name: stream_channel
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.0"
+  string_scanner:
+    dependency: transitive
+    description:
+      name: string_scanner
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.0"
+  term_glyph:
+    dependency: transitive
+    description:
+      name: term_glyph
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.2.0"
+  test_api:
+    dependency: transitive
+    description:
+      name: test_api
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.2.19"
+  typed_data:
+    dependency: transitive
+    description:
+      name: typed_data
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.3.0"
+  vector_math:
+    dependency: transitive
+    description:
+      name: vector_math
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.0"
+  win32:
+    dependency: transitive
+    description:
+      name: win32
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.5"
+  xdg_directories:
+    dependency: transitive
+    description:
+      name: xdg_directories
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.2.0"
+sdks:
+  dart: ">=2.12.0 <3.0.0"
+  flutter: ">=2.0.0"

+ 11 - 53
pubspec.yaml

@@ -1,16 +1,5 @@
 name: flutter_2048
 description: 2048 game.
-
-# The following defines the version and build number for your application.
-# A version number is three numbers separated by dots, like 1.2.43
-# followed by an optional build number separated by a +.
-# Both the version and the builder number may be overridden in flutter
-# build by specifying --build-name and --build-number, respectively.
-# In Android, build-name is used as versionName while build-number used as versionCode.
-# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
-# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
-# Read more about iOS versioning at
-# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 version: 1.0.0+1
 
 environment:
@@ -20,53 +9,22 @@ dependencies:
   flutter:
     sdk: flutter
 
-
-  # The following adds the Cupertino Icons font to your application.
-  # Use with the CupertinoIcons class for iOS style icons.
-  cupertino_icons: ^0.1.3
+  flutter_localizations: # 国际化
+    sdk: flutter
+  intl: ^0.17.0
+  cupertino_icons: ^1.0.2 # ios风格图标
+  flutter_screenutil: ^4.0.3+2 # 屏幕工具
+  device_info: ^2.0.2
+  flutter_redux: ^0.8.2 # 状态管理
+  redux_thunk: ^0.4.0
+  shared_preferences: ^2.0.6
 
 dev_dependencies:
   flutter_test:
     sdk: flutter
 
-# For information on the generic Dart part of this file, see the
-# following page: https://dart.dev/tools/pub/pubspec
-
-# The following section is specific to Flutter.
 flutter:
-
-  # The following line ensures that the Material Icons font is
-  # included with your application, so that you can use the icons in
-  # the material Icons class.
   uses-material-design: true
 
-  # To add assets to your application, add an assets section, like this:
-  # assets:
-  #   - images/a_dot_burr.jpeg
-  #   - images/a_dot_ham.jpeg
-
-  # An image asset can refer to one or more resolution-specific "variants", see
-  # https://flutter.dev/assets-and-images/#resolution-aware.
-
-  # For details regarding adding assets from package dependencies, see
-  # https://flutter.dev/assets-and-images/#from-packages
-
-  # To add custom fonts to your application, add a fonts section here,
-  # in this "flutter" section. Each entry in this list should have a
-  # "family" key with the font family name, and a "fonts" key with a
-  # list giving the asset and other descriptors for the font. For
-  # example:
-  # fonts:
-  #   - family: Schyler
-  #     fonts:
-  #       - asset: fonts/Schyler-Regular.ttf
-  #       - asset: fonts/Schyler-Italic.ttf
-  #         style: italic
-  #   - family: Trajan Pro
-  #     fonts:
-  #       - asset: fonts/TrajanPro.ttf
-  #       - asset: fonts/TrajanPro_Bold.ttf
-  #         weight: 700
-  #
-  # For details regarding fonts from package dependencies,
-  # see https://flutter.dev/custom-fonts/#from-packages
+flutter_intl:
+  enabled: true

+ 0 - 30
test/widget_test.dart

@@ -1,30 +0,0 @@
-// 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:flutter_2048/main.dart';
-
-void main() {
-  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
-    // Build our app and trigger a frame.
-    await tester.pumpWidget(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);
-  });
-}

+ 8 - 0
windows/flutter/GeneratedPlugins.props

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ImportGroup Label="PropertySheets"/>
+  <PropertyGroup Label="UserMacros"/>
+  <PropertyGroup/>
+  <ItemDefinitionGroup/>
+  <ItemGroup/>
+</Project>

+ 9 - 0
windows/flutter/generated_plugin_registrant.cc

@@ -0,0 +1,9 @@
+//
+//  Generated file. Do not edit.
+//
+
+#include "generated_plugin_registrant.h"
+
+
+void RegisterPlugins(flutter::PluginRegistry* registry) {
+}

+ 13 - 0
windows/flutter/generated_plugin_registrant.h

@@ -0,0 +1,13 @@
+//
+//  Generated file. Do not edit.
+//
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include <flutter/plugin_registry.h>
+
+// Registers Flutter plugins.
+void RegisterPlugins(flutter::PluginRegistry* registry);
+
+#endif  // GENERATED_PLUGIN_REGISTRANT_