MiniStar 4 years ago
commit
4ad55b9c1f

+ 13 - 0
README.md

@@ -0,0 +1,13 @@
+# wxapp-2048
+
+基于[web版2048游戏](https://github.com/gabrielecirulli/2048)开发的微信小程序版2048。
+
+### 界面展示
+
+![游戏界面](./screenshot/game.jpg)
+
+### 使用方法
+
+1. 克隆本项目
+2. 在微信开发工具中添加项目
+3. 选择项目目录

BIN
screenshot/game.jpg


+ 0 - 0
src/.gitignore


+ 30 - 0
src/app.js

@@ -0,0 +1,30 @@
+//app.js
+App({
+  onLaunch: function () {
+    //调用API从本地缓存中获取数据
+    var logs = wx.getStorageSync('logs') || []
+    logs.unshift(Date.now())
+    wx.setStorageSync('logs', logs)
+  },
+  getUserInfo:function(cb){
+    var that = this
+    if(this.globalData.userInfo){
+      typeof cb == "function" && cb(this.globalData.userInfo)
+    }else{
+      //调用登录接口
+      wx.login({
+        success: function () {
+          wx.getUserInfo({
+            success: function (res) {
+              that.globalData.userInfo = res.userInfo
+              typeof cb == "function" && cb(that.globalData.userInfo)
+            }
+          })
+        }
+      })
+    }
+  },
+  globalData:{
+    userInfo:null
+  }
+})

+ 33 - 0
src/app.json

@@ -0,0 +1,33 @@
+{
+  "pages":[
+    "pages/index/index",
+    "pages/logs/logs",
+    "pages/2048/2048"
+    
+  ],
+  "window":{
+    "navigationBarBackgroundColor":"#ffffff",
+    "navigationBarTextStyle":"#1AAD16",
+    "navigationBarTitleText":"微信接口功能演示",
+    "backgroundColor":"#eeeeee",
+    "backgroundTextStyle":"light"
+  },
+  "tabBar": {
+    "color": "#353535",
+    "selectedColor": "#3cc51f",
+    "borderStyle": "white",
+    "backgroundColor": "#ffffff",
+    "list": [{
+      "pagePath": "pages/index/index",
+      "text": "游戏"
+    }, {
+      "pagePath": "pages/logs/logs",
+      "text": "日志"
+    }]
+  },
+  "networkTimeout": {
+    "request": 10000,
+    "downloadFile": 10000
+  },
+  "debug": false
+}

+ 10 - 0
src/app.wxss

@@ -0,0 +1,10 @@
+/**app.wxss**/
+.container {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: space-between;
+  padding: 200rpx 0;
+  box-sizing: border-box;
+} 

+ 136 - 0
src/pages/2048/2048.js

@@ -0,0 +1,136 @@
+var app = getApp();
+
+var Grid = require('./grid.js');
+var Tile = require('./tile.js');
+var GameManager = require('./game_manager.js');
+
+var config = {
+    data: {
+        hidden: false,
+
+        // 游戏数据可以通过参数控制
+        grids: [],
+        over: false,
+        win: false,
+        score: 0,
+        highscore: 0,
+        overMsg: '游戏结束'
+    },
+    onLoad: function() {
+        this.GameManager = new GameManager(4);
+
+        this.setData({
+            grids: this.GameManager.setup(),
+            highscore: wx.getStorageSync('highscore') || 0
+        });
+
+    },
+    onReady: function() {
+        var that = this;
+
+        // 页面渲染完毕隐藏loading
+        that.setData({
+            hidden: true
+        });
+    },
+    onShow: function() {
+        // 页面展示
+    },
+    onHide: function() {
+        // 页面隐藏
+    },
+    onUnload: function() {
+        // 页面关闭
+    },
+
+    // 更新视图数据
+    updateView: function(data) {
+        // 游戏结束
+        if(data.over){
+            data.overMsg = '游戏结束';
+        }
+
+        // 获胜
+        if(data.win){
+            data.overMsg = '恭喜';
+        }
+
+        this.setData(data);
+    },
+
+    // 重新开始
+    restart: function() {
+        this.updateView({
+            grids: this.GameManager.restart(),
+            over: false,
+            won: false,
+            score: 0
+        });
+    },
+
+    touchStartClienX: 0,
+    touchStartClientY: 0,
+    touchEndClientX: 0,
+    touchEndClientY: 0,
+    isMultiple: false, // 多手指操作
+
+    touchStart: function(events) {
+
+        // 多指操作
+        this.isMultiple = events.touches.length > 1;
+        if (this.isMultiple) {
+            return;
+        }
+
+        var touch = events.touches[0];
+
+        this.touchStartClientX = touch.clientX;
+        this.touchStartClientY = touch.clientY;
+
+    },
+
+    touchMove: function(events) {
+        var touch = events.touches[0];
+        this.touchEndClientX = touch.clientX;
+        this.touchEndClientY = touch.clientY;
+    },
+
+    touchEnd: function(events) {
+        if (this.isMultiple) {
+            return;
+        }
+
+        var dx = this.touchEndClientX - this.touchStartClientX;
+        var absDx = Math.abs(dx);
+        var dy = this.touchEndClientY - this.touchStartClientY;
+        var absDy = Math.abs(dy);
+
+        if (Math.max(absDx, absDy) > 10) {
+            var direction = absDx > absDy ? (dx > 0 ? 1 : 3) : (dy > 0 ? 2 : 0);
+
+            var data = this.GameManager.move(direction) || {
+                grids: this.data.grids,
+                over: this.data.over,
+                won: this.data.won,
+                score: this.data.score
+            };
+
+            var highscore = wx.getStorageSync('highscore') || 0;
+            if(data.score > highscore){
+                wx.setStorageSync('highscore', data.score);
+            }
+
+            this.updateView({
+                grids: data.grids,
+                over: data.over,
+                won: data.won,
+                score: data.score,
+                highscore: Math.max(highscore, data.score)
+            });
+
+        }
+
+    }
+};
+
+Page(config);

+ 5 - 0
src/pages/2048/2048.json

@@ -0,0 +1,5 @@
+{
+    "navigationBarTitleText": "2048小游戏",
+    "backgroundColor":"#faf8ef",
+    "backgroundTextStyle":"#776e65"
+}

+ 46 - 0
src/pages/2048/2048.wxml

@@ -0,0 +1,46 @@
+<view class="container">
+
+  <view class="game-body">
+    <loading hidden="{{hidden}}">
+        加载中...
+    </loading>
+    <view class="heading">
+      	<text class="title">2048</text>
+      	<view class="scores-container">
+        	<view class="score-container">{{score}}</view>
+      		<view class="best-container">{{highscore}}</view>
+      	</view>
+    </view>
+
+    <view class="above-game">
+      	<text class="game-intro">你能拿到2048吗?</text>
+      	<text class="restart-button" bindtap="restart">新游戏</text>
+    </view>
+
+    <view class="game-container">
+        <view class="game-message game-{{over ? (win ? 'won' : 'over') : ''}}">
+          	<text class="over-msg">{{overMsg}}</text>
+          	<view class="lower">
+	        	<!-- <text class="keep-playing-button">继续</text> -->
+          		<text class="retry-button" bindtap="restart">再试一次</text>
+        	</view>
+        </view>
+
+	    <view class="grid-container" bindtouchstart="touchStart" bindtouchmove="touchMove" bindtouchend="touchEnd">
+			<view wx:for="{{grids}}" wx:for-index="rowIdx" wx:for-item="row" class="grid-row">
+			  	<view wx:for="{{row}}" wx:for-index="colIdx" wx:for-item="cell" class="grid-cell">
+			  		<view class="tile tile-{{cell.value}}">
+			  			<view wx:if="{{cell}}" class="tile-inner">
+			      			{{cell.value}}
+			      		</view>
+			  		</view>
+			  	</view>
+			</view>
+	    </view>
+
+  	</view>
+  	<!-- <view class="game-explanation">
+      <view class="important">如何开始:</view> 手指上下左右滑动
+    </view> -->
+
+</view>

+ 671 - 0
src/pages/2048/2048.wxss

@@ -0,0 +1,671 @@
+
+
+.container {
+  margin: 0;
+  padding: 20px 0;
+  background: #faf8ef;
+  color: #776e65;
+  font-family: "Helvetica Neue", Arial, sans-serif;
+  font-size: 18px;
+}
+
+.heading:after {
+  content: "";
+  display: block;
+  clear: both;
+}
+.title {
+  font-size: 80px;
+  font-weight: bold;
+  margin: 0;
+  display: block;
+  float: left;
+}
+
+.scores-container {
+  float: right;
+  text-align: right;
+}
+.score-container, .best-container {
+  position: relative;
+  display: inline-block;
+  background: #bbada0;
+  padding: 15px 25px;
+  font-size: 25px;
+  height: 25px;
+  line-height: 47px;
+  font-weight: bold;
+  border-radius: 3px;
+  color: white;
+  text-align: center;
+  margin: 8px 0 0 8px;
+}
+.score-container:after, .best-container:after {
+  position: absolute;
+  width: 100%;
+  top: 10px;
+  left: 0;
+  text-transform: uppercase;
+  font-size: 13px;
+  line-height: 13px;
+  text-align: center;
+  color: #eee4da;
+}
+.score-container .score-addition, .best-container .score-addition {
+  position: absolute;
+  right: 30px;
+  color: red;
+  font-size: 25px;
+  line-height: 25px;
+  font-weight: bold;
+  color: rgba(119, 110, 101, 0.9);
+  z-index: 100;
+ 
+}
+.score-container:after {
+  content: "Score";
+}
+.best-container:after {
+  content: "Best";
+}
+p {
+  margin-top: 0;
+  margin-bottom: 10px;
+  line-height: 1.65;
+}
+a {
+  color: #776e65;
+  font-weight: bold;
+  text-decoration: underline;
+  cursor: pointer;
+}
+strong.important {
+  text-transform: uppercase;
+}
+hr {
+  border: none;
+  border-bottom: 1px solid #d8d4d0;
+  margin-top: 20px;
+  margin-bottom: 30px;
+}
+
+.game-container {
+  margin-top: 40px;
+  position: relative;
+  padding: 15px;
+  cursor: default;
+  -webkit-touch-callout: none;
+  -ms-touch-callout: none;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  -ms-touch-action: none;
+  touch-action: none;
+  background: #bbada0;
+  border-radius: 6px;
+  width: 500px;
+  height: 500px;
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+.game-container .game-message {
+  /*display: none;*/
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  background: rgba(238, 228, 218, 0.5);
+  z-index: 100;
+  text-align: center;
+}
+.game-container .game-message p {
+  font-size: 60px;
+  font-weight: bold;
+  height: 60px;
+  line-height: 60px;
+  margin-top: 222px;
+}
+.game-container .game-message .lower {
+  display: block;
+  margin-top: 59px;
+}
+.game-container .game-message a {
+  display: inline-block;
+  background: #8f7a66;
+  border-radius: 3px;
+  padding: 0 20px;
+  text-decoration: none;
+  color: #f9f6f2;
+  height: 40px;
+  line-height: 42px;
+  margin-left: 9px;
+}
+.game-container .game-message .keep-playing-button {
+  display: none;
+}
+.game-container .game-message.game-won {
+  background: rgba(237, 194, 46, 0.5);
+  color: #f9f6f2;
+}
+.game-container .game-message.game-won .keep-playing-button {
+  display: inline-block;
+}
+.game-container .game-message.game-won, .game-container .game-message.game-over {
+  display: block;
+}
+.grid-container {
+  position: absolute;
+  z-index: 1;
+}
+.grid-row {
+  margin-bottom: 15px;
+}
+.grid-row:last-child {
+  margin-bottom: 0;
+}
+.grid-row:after {
+  content: "";
+  display: block;
+  clear: both;
+}
+.grid-cell {
+  width: 106.25px;
+  height: 106.25px;
+  margin-right: 15px;
+  float: left;
+  border-radius: 3px;
+  background: rgba(238, 228, 218, 0.35);
+}
+.grid-cell:last-child {
+  margin-right: 0;
+}
+.tile-container {
+  position: absolute;
+  z-index: 2;
+}
+.tile, .tile .tile-inner {
+  width: 107px;
+  height: 107px;
+  line-height: 107px;
+}
+.tile.tile-position-1-1 {
+  -webkit-transform: translate(0px, 0px);
+  -moz-transform: translate(0px, 0px);
+  -ms-transform: translate(0px, 0px);
+  transform: translate(0px, 0px);
+}
+.tile.tile-position-1-2 {
+  -webkit-transform: translate(0px, 121px);
+  -moz-transform: translate(0px, 121px);
+  -ms-transform: translate(0px, 121px);
+  transform: translate(0px, 121px);
+}
+.tile.tile-position-1-3 {
+  -webkit-transform: translate(0px, 242px);
+  -moz-transform: translate(0px, 242px);
+  -ms-transform: translate(0px, 242px);
+  transform: translate(0px, 242px);
+}
+.tile.tile-position-1-4 {
+  -webkit-transform: translate(0px, 363px);
+  -moz-transform: translate(0px, 363px);
+  -ms-transform: translate(0px, 363px);
+  transform: translate(0px, 363px);
+}
+.tile.tile-position-2-1 {
+  -webkit-transform: translate(121px, 0px);
+  -moz-transform: translate(121px, 0px);
+  -ms-transform: translate(121px, 0px);
+  transform: translate(121px, 0px);
+}
+.tile.tile-position-2-2 {
+  -webkit-transform: translate(121px, 121px);
+  -moz-transform: translate(121px, 121px);
+  -ms-transform: translate(121px, 121px);
+  transform: translate(121px, 121px);
+}
+.tile.tile-position-2-3 {
+  -webkit-transform: translate(121px, 242px);
+  -moz-transform: translate(121px, 242px);
+  -ms-transform: translate(121px, 242px);
+  transform: translate(121px, 242px);
+}
+.tile.tile-position-2-4 {
+  -webkit-transform: translate(121px, 363px);
+  -moz-transform: translate(121px, 363px);
+  -ms-transform: translate(121px, 363px);
+  transform: translate(121px, 363px);
+}
+.tile.tile-position-3-1 {
+  -webkit-transform: translate(242px, 0px);
+  -moz-transform: translate(242px, 0px);
+  -ms-transform: translate(242px, 0px);
+  transform: translate(242px, 0px);
+}
+.tile.tile-position-3-2 {
+  -webkit-transform: translate(242px, 121px);
+  -moz-transform: translate(242px, 121px);
+  -ms-transform: translate(242px, 121px);
+  transform: translate(242px, 121px);
+}
+.tile.tile-position-3-3 {
+  -webkit-transform: translate(242px, 242px);
+  -moz-transform: translate(242px, 242px);
+  -ms-transform: translate(242px, 242px);
+  transform: translate(242px, 242px);
+}
+.tile.tile-position-3-4 {
+  -webkit-transform: translate(242px, 363px);
+  -moz-transform: translate(242px, 363px);
+  -ms-transform: translate(242px, 363px);
+  transform: translate(242px, 363px);
+}
+.tile.tile-position-4-1 {
+  -webkit-transform: translate(363px, 0px);
+  -moz-transform: translate(363px, 0px);
+  -ms-transform: translate(363px, 0px);
+  transform: translate(363px, 0px);
+}
+.tile.tile-position-4-2 {
+  -webkit-transform: translate(363px, 121px);
+  -moz-transform: translate(363px, 121px);
+  -ms-transform: translate(363px, 121px);
+  transform: translate(363px, 121px);
+}
+.tile.tile-position-4-3 {
+  -webkit-transform: translate(363px, 242px);
+  -moz-transform: translate(363px, 242px);
+  -ms-transform: translate(363px, 242px);
+  transform: translate(363px, 242px);
+}
+.tile.tile-position-4-4 {
+  -webkit-transform: translate(363px, 363px);
+  -moz-transform: translate(363px, 363px);
+  -ms-transform: translate(363px, 363px);
+  transform: translate(363px, 363px);
+}
+.tile {
+  position: absolute;
+  -webkit-transition: 100ms ease-in-out;
+  -moz-transition: 100ms ease-in-out;
+  transition: 100ms ease-in-out;
+  -webkit-transition-property: -webkit-transform;
+  -moz-transition-property: -moz-transform;
+  transition-property: transform;
+}
+.tile .tile-inner {
+  border-radius: 3px;
+  background: #eee4da;
+  text-align: center;
+  font-weight: bold;
+  z-index: 10;
+  font-size: 55px;
+}
+.tile.tile-2 .tile-inner {
+  background: #eee4da;
+  box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0);
+}
+.tile.tile-4 .tile-inner {
+  background: #ede0c8;
+  box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0), inset 0 0 0 1px rgba(255, 255, 255, 0);
+}
+.tile.tile-8 .tile-inner {
+  color: #f9f6f2;
+  background: #f2b179;
+}
+.tile.tile-16 .tile-inner {
+  color: #f9f6f2;
+  background: #f59563;
+}
+.tile.tile-32 .tile-inner {
+  color: #f9f6f2;
+  background: #f67c5f;
+}
+.tile.tile-64 .tile-inner {
+  color: #f9f6f2;
+  background: #f65e3b;
+}
+.tile.tile-128 .tile-inner {
+  color: #f9f6f2;
+  background: #edcf72;
+  box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.2381), inset 0 0 0 1px rgba(255, 255, 255, 0.14286);
+  font-size: 45px;
+}
+@media screen and (max-width:520px) {
+  .tile.tile-128 .tile-inner {
+    font-size: 25px;
+  }
+}
+.tile.tile-256 .tile-inner {
+  color: #f9f6f2;
+  background: #edcc61;
+  box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.31746), inset 0 0 0 1px rgba(255, 255, 255, 0.19048);
+  font-size: 45px;
+}
+@media screen and (max-width:520px) {
+  .tile.tile-256 .tile-inner {
+    font-size: 25px;
+  }
+}
+.tile.tile-512 .tile-inner {
+  color: #f9f6f2;
+  background: #edc850;
+  box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.39683), inset 0 0 0 1px rgba(255, 255, 255, 0.2381);
+  font-size: 45px;
+}
+@media screen and (max-width:520px) {
+  .tile.tile-512 .tile-inner {
+    font-size: 25px;
+  }
+}
+.tile.tile-1024 .tile-inner {
+  color: #f9f6f2;
+  background: #edc53f;
+  box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.47619), inset 0 0 0 1px rgba(255, 255, 255, 0.28571);
+  font-size: 35px;
+}
+@media screen and (max-width:520px) {
+  .tile.tile-1024 .tile-inner {
+    font-size: 15px;
+  }
+}
+.tile.tile-2048 .tile-inner {
+  color: #f9f6f2;
+  background: #edc22e;
+  box-shadow: 0 0 30px 10px rgba(243, 215, 116, 0.55556), inset 0 0 0 1px rgba(255, 255, 255, 0.33333);
+  font-size: 35px;
+}
+@media screen and (max-width:520px) {
+  .tile.tile-2048 .tile-inner {
+    font-size: 15px;
+  }
+}
+.tile.tile-super .tile-inner {
+  color: #f9f6f2;
+  background: #3c3a32;
+  font-size: 30px;
+}
+@media screen and (max-width:520px) {
+  .tile.tile-super .tile-inner {
+    font-size: 10px;
+  }
+}
+
+.tile-merged .tile-inner {
+  z-index: 20;
+}
+.above-game:after {
+  content: "";
+  display: block;
+  clear: both;
+}
+.game-intro {
+  float: left;
+  line-height: 42px;
+  margin-bottom: 0;
+}
+.restart-button {
+  display: inline-block;
+  background: #8f7a66;
+  border-radius: 3px;
+  padding: 0 20px;
+  text-decoration: none;
+  color: #f9f6f2;
+  height: 40px;
+  line-height: 42px;
+  display: block;
+  text-align: center;
+  float: right;
+}
+.game-explanation {
+  margin-top: 50px;
+}
+@media screen and (max-width:520px) {
+  html, body {
+    font-size: 15px;
+  }
+  body {
+    margin: 20px 0;
+    padding: 0 20px;
+  }
+  .title {
+    font-size: 27px;
+    margin-top: 15px;
+  }
+  /*.container {
+    width: 280px;
+    margin: 0 auto;
+  }*/
+  .score-container, .best-container {
+    margin-top: 0;
+    padding: 15px 10px;
+    min-width: 40px;
+  }
+  .heading {
+    margin-bottom: 10px;
+  }
+  .game-intro {
+    width: 55%;
+    display: block;
+    box-sizing: border-box;
+    line-height: 1.65;
+  }
+  .restart-button {
+    width: 42%;
+    padding: 0;
+    display: block;
+    box-sizing: border-box;
+    margin-top: 2px;
+  }
+  .game-container {
+    margin-top: 17px;
+    position: relative;
+    padding: 10px;
+    cursor: default;
+    -webkit-touch-callout: none;
+    -ms-touch-callout: none;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    -ms-touch-action: none;
+    touch-action: none;
+    background: #bbada0;
+    border-radius: 6px;
+    width: 280px;
+    height: 280px;
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+  }
+  .game-container .game-message {
+    display: none;
+    position: absolute;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    background: rgba(238, 228, 218, 0.5);
+    z-index: 100;
+    text-align: center;
+   
+  }
+  .game-container .game-message .over-msg {
+    display: block;
+    font-size: 30px;
+    font-weight: bold;
+    height: 30px;
+    line-height: 30px;
+    /*margin-top: 222px;*/
+    margin-top: 59px;
+  }
+  .game-container .game-message .lower {
+    display: block;
+    margin-top: 59px;
+  }
+  .game-container .game-message .retry-button {
+    display: inline-block;
+    background: #8f7a66;
+    border-radius: 3px;
+    padding: 0 20px;
+    text-decoration: none;
+    color: #f9f6f2;
+    height: 40px;
+    line-height: 42px;
+    margin-left: 9px;
+  }
+  .game-container .game-message .keep-playing-button {
+    display: none;
+  }
+  .game-container .game-message.game-won {
+    background: rgba(237, 194, 46, 0.5);
+    color: #f9f6f2;
+  }
+  .game-container .game-message.game-won .keep-playing-button {
+    display: inline-block;
+  }
+  .game-container .game-message.game-won, .game-container .game-message.game-over {
+    display: block;
+  }
+  .grid-container {
+    position: absolute;
+    z-index: 1;
+  }
+  .grid-row {
+    margin-bottom: 10px;
+  }
+  .grid-row:last-child {
+    margin-bottom: 0;
+  }
+  .grid-row:after {
+    content: "";
+    display: block;
+    clear: both;
+  }
+  .grid-cell {
+    width: 57.5px;
+    height: 57.5px;
+    margin-right: 10px;
+    float: left;
+    border-radius: 3px;
+    background: rgba(238, 228, 218, 0.35);
+  }
+  .grid-cell:last-child {
+    margin-right: 0;
+  }
+ 
+  .tile, .tile .tile-inner {
+    width: 58px;
+    height: 58px;
+    line-height: 58px;
+  }
+  .tile.tile-position-1-1 {
+    -webkit-transform: translate(0px, 0px);
+    -moz-transform: translate(0px, 0px);
+    -ms-transform: translate(0px, 0px);
+    transform: translate(0px, 0px);
+  }
+  .tile.tile-position-1-2 {
+    -webkit-transform: translate(0px, 67px);
+    -moz-transform: translate(0px, 67px);
+    -ms-transform: translate(0px, 67px);
+    transform: translate(0px, 67px);
+  }
+  .tile.tile-position-1-3 {
+    -webkit-transform: translate(0px, 135px);
+    -moz-transform: translate(0px, 135px);
+    -ms-transform: translate(0px, 135px);
+    transform: translate(0px, 135px);
+  }
+  .tile.tile-position-1-4 {
+    -webkit-transform: translate(0px, 202px);
+    -moz-transform: translate(0px, 202px);
+    -ms-transform: translate(0px, 202px);
+    transform: translate(0px, 202px);
+  }
+  .tile.tile-position-2-1 {
+    -webkit-transform: translate(67px, 0px);
+    -moz-transform: translate(67px, 0px);
+    -ms-transform: translate(67px, 0px);
+    transform: translate(67px, 0px);
+  }
+  .tile.tile-position-2-2 {
+    -webkit-transform: translate(67px, 67px);
+    -moz-transform: translate(67px, 67px);
+    -ms-transform: translate(67px, 67px);
+    transform: translate(67px, 67px);
+  }
+  .tile.tile-position-2-3 {
+    -webkit-transform: translate(67px, 135px);
+    -moz-transform: translate(67px, 135px);
+    -ms-transform: translate(67px, 135px);
+    transform: translate(67px, 135px);
+  }
+  .tile.tile-position-2-4 {
+    -webkit-transform: translate(67px, 202px);
+    -moz-transform: translate(67px, 202px);
+    -ms-transform: translate(67px, 202px);
+    transform: translate(67px, 202px);
+  }
+  .tile.tile-position-3-1 {
+    -webkit-transform: translate(135px, 0px);
+    -moz-transform: translate(135px, 0px);
+    -ms-transform: translate(135px, 0px);
+    transform: translate(135px, 0px);
+  }
+  .tile.tile-position-3-2 {
+    -webkit-transform: translate(135px, 67px);
+    -moz-transform: translate(135px, 67px);
+    -ms-transform: translate(135px, 67px);
+    transform: translate(135px, 67px);
+  }
+  .tile.tile-position-3-3 {
+    -webkit-transform: translate(135px, 135px);
+    -moz-transform: translate(135px, 135px);
+    -ms-transform: translate(135px, 135px);
+    transform: translate(135px, 135px);
+  }
+  .tile.tile-position-3-4 {
+    -webkit-transform: translate(135px, 202px);
+    -moz-transform: translate(135px, 202px);
+    -ms-transform: translate(135px, 202px);
+    transform: translate(135px, 202px);
+  }
+  .tile.tile-position-4-1 {
+    -webkit-transform: translate(202px, 0px);
+    -moz-transform: translate(202px, 0px);
+    -ms-transform: translate(202px, 0px);
+    transform: translate(202px, 0px);
+  }
+  .tile.tile-position-4-2 {
+    -webkit-transform: translate(202px, 67px);
+    -moz-transform: translate(202px, 67px);
+    -ms-transform: translate(202px, 67px);
+    transform: translate(202px, 67px);
+  }
+  .tile.tile-position-4-3 {
+    -webkit-transform: translate(202px, 135px);
+    -moz-transform: translate(202px, 135px);
+    -ms-transform: translate(202px, 135px);
+    transform: translate(202px, 135px);
+  }
+  .tile.tile-position-4-4 {
+    -webkit-transform: translate(202px, 202px);
+    -moz-transform: translate(202px, 202px);
+    -ms-transform: translate(202px, 202px);
+    transform: translate(202px, 202px);
+  }
+  .tile .tile-inner {
+    font-size: 35px;
+  }
+  .game-message p {
+    font-size: 30px !important;
+    height: 30px !important;
+    line-height: 30px !important;
+    margin-top: 90px !important;
+  }
+  .game-message .lower {
+    margin-top: 30px !important;
+  }
+}

+ 245 - 0
src/pages/2048/game_manager.js

@@ -0,0 +1,245 @@
+var Grid = require('./grid.js');
+var Tile = require('./tile.js');
+
+function GameManager(size) {
+    this.size = size;
+    this.startTiles = 2;
+}
+
+GameManager.prototype = {
+    setup: function() {
+
+        this.grid = new Grid(this.size);
+        this.score = 0;
+        this.over = false;
+        this.won = false;
+        this.addStartTiles();
+        return this.grid.cells;
+    },
+
+    // 初始化数据
+    addStartTiles: function() {
+        for (var x = 0; x < this.startTiles; x++) {
+            this.addRandomTiles();
+        }
+    },
+
+    // 在一个随机单元格中随机填充2或4
+    addRandomTiles: function() {
+
+        if (this.grid.cellsAvailable()) {
+            var value = Math.random() < 0.9 ? 2 : 4;
+            var cell = this.grid.randomAvailableCell();
+            var tile = new Tile(cell, value);
+            this.grid.insertTile(tile); // 插入一个单元格
+        }
+
+    },
+
+    actuate: function() {
+
+        return {
+            grids: this.grid.cells,
+            over: this.over,
+            won: this.won,
+            score: this.score
+        }
+    },
+
+    // 偏移向量
+    getVector: function(direction) {
+        
+        var map = {
+            0: { // 上
+                x: -1,
+                y: 0
+            },
+            1: { // 右
+                x: 0,
+                y: 1
+            },
+            2: { // 下
+                x: 1,
+                y: 0
+            },
+            3: { // 左
+                x: 0,
+                y: -1
+            }
+        };
+        return map[direction];
+    },
+
+    buildTraversals: function(vector) {
+        var traversals = {
+            x: [],
+            y: []
+        };
+
+        for (var pos = 0; pos < this.size; pos++) {
+            traversals.x.push(pos);
+            traversals.y.push(pos);
+        }
+
+        // 为什么要加这个,看findFarthestTail
+        if (vector.x === 1) {
+            // 向右时
+            traversals.x = traversals.x.reverse();
+        }
+
+        if (vector.y === 1) {
+            // 向下
+            traversals.y = traversals.y.reverse();
+        }
+
+        return traversals;
+    },
+
+    // 把当前单元格挪至下一个可放置的区域
+    moveTile: function(tile, cell) {
+        this.grid.cells[tile.x][tile.y] = null;
+        this.grid.cells[cell.x][cell.y] = tile;
+        tile.updatePosition(cell);
+    },
+
+    // 特定方向移动单元格
+    move: function(direction) {
+        // 0: up, 1: right, 2: down, 3: left
+        var self = this;
+        var vector = this.getVector(direction);
+        var traversals = this.buildTraversals(vector);
+
+        var cell;
+        var tile;
+        var moved = false;
+        self.prepareTiles();
+
+        traversals.x.forEach(function(x) {
+            traversals.y.forEach(function(y) {
+                // console.log('x:', x, 'y:', y);
+                cell = {
+                    x: x,
+                    y: y
+                };
+                tile = self.grid.cellContent(cell);
+
+                if (tile) { // 单元格有内容
+                    var positions = self.findFarthestTail(cell, vector);
+                    var next = self.grid.cellContent(positions.next);
+
+                    if (next && next.value === tile.value && !next.mergedFrom) {
+                        // 当前格子和其移动方向格子内容相同,需要合并
+                        var merged = new Tile(positions.next, tile.value * 2); // 合并后的格子信息
+
+                        merged.mergedFrom = [tile, next];
+
+                        self.grid.insertTile(merged); // 把合并的盒子插入到当前格子数据中
+                        self.grid.removeTile(tile); // 删除当前格子内容
+
+                        tile.updatePosition(positions.next);
+
+                        self.score += merged.value;
+                        if (merged.value === 2048) self.won = true;
+                    } else {
+                        self.moveTile(tile, positions.farthest);
+                    }
+
+                    // 是否从当前位置移到当前位置
+                    if (!self.positionsEqual(cell, tile)) {
+                        moved = true;
+                    }
+                }
+            });
+        });
+
+        if (moved) {
+            this.addRandomTiles();
+
+            if (!this.movesAvailable()) {
+                this.over = true;
+            }
+
+            return this.actuate();
+        }
+
+        // return this.grid.cells
+
+    },
+
+    prepareTiles: function() {
+
+        var tile;
+        for (var x = 0; x < this.size; x++) {
+            for (var y = 0; y < this.size; y++) {
+                tile = this.grid.cells[x][y];
+                if (tile) {
+                    tile.mergedFrom = null;
+                    tile.savePosition();
+                }
+            }
+        }
+    },
+
+    positionsEqual: function(first, second) {
+        return first.x === second.x && first.y === second.y;
+    },
+
+    movesAvailable: function() {
+        return this.grid.cellsAvailable() || this.tileMatchesAvailable();
+    },
+
+    tileMatchesAvailable: function() {
+        var self = this;
+
+        var tile;
+
+        for (var x = 0; x < this.size; x++) {
+            for (var y = 0; y < this.size; y++) {
+                tile = this.grid.cellContent({ x: x, y: y });
+
+                if (tile) {
+                    for (var direction = 0; direction < 4; direction++) {
+                        var vector = self.getVector(direction);
+                        var cell = { x: x + vector.x, y: y + vector.y };
+
+                        var other = self.grid.cellContent(cell);
+
+                        if (other && other.value === tile.value) {
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+
+        return false;
+    },
+
+    // 找到当前偏移方向存在最远的空单元格
+    // 如:向右偏移,那么返回当前行最靠右的空单元格及其右侧距离其最远的一个格子,向下一样
+    findFarthestTail: function(cell, vector) {
+        var previous;
+
+        // 当前单元格在范围内且存在可用单元格
+        do {
+            previous = cell;
+            cell = {
+                x: previous.x + vector.x,
+                y: previous.y + vector.y
+            };
+        }
+        while (this.grid.withinBounds(cell) && this.grid.emptyCell(cell));
+
+        return {
+            farthest: previous,
+            next: cell
+        }
+    },
+
+    // 重新开始
+    restart: function() {
+        return this.setup();
+    }
+}
+
+module.exports = GameManager;

+ 103 - 0
src/pages/2048/grid.js

@@ -0,0 +1,103 @@
+function Grid(size) {
+    this.size = size;
+    this.cells = this.empty();
+}
+
+Grid.prototype = {
+
+    // 构造一个空的矩阵[[null,..,size.length],[]]
+    empty: function() {
+        var cells = [];
+
+        for (var x = 0; x < this.size; x++) {
+            var row = cells[x] = [];
+
+            for (var y = 0; y < this.size; y++) {
+                row.push(null);
+            }
+        }
+
+        // [[{x:0,y:0},{x:0,y:1}],[]]
+        return cells;
+    },
+
+    // 在空格子中随机挑选出一个格子
+    randomAvailableCell: function() {
+        var cells = this.availableCells();
+
+        // 存在可填充的格子
+        if (cells.length) {
+            return cells[Math.floor(Math.random() * cells.length)];
+        }
+    },
+
+    // 获取可填充的格子坐标
+    availableCells: function() {
+        var cells = [];
+
+        for (var i = 0; i < this.size; i++) {
+            for (var j = 0; j < this.size; j++) {
+
+                // 当前格子无内容
+                if (!this.cells[i][j]) {
+                    cells.push({
+                        x: i,
+                        y: j
+                    });
+                }
+            }
+        }
+
+        return cells;
+    },
+
+    // 是否存在空单元格
+    cellsAvailable: function() {
+        return !!this.availableCells().length;
+    },
+
+    cellAvailable: function(cell) {
+        return !this.cellContent(cell);
+    },
+
+    insertTile: function(tile) {
+        this.cells[tile.x][tile.y] = tile;
+    },
+
+    removeTile: function(tile) {
+        this.cells[tile.x][tile.y] = null;
+    },
+
+    /* 
+     * 获取单元格内容
+     * @param {object} cell {x:0,y:0} 单元格坐标
+     */
+    cellContent: function(cell) {
+        if (this.withinBounds(cell)) {
+            return this.cells[cell.x][cell.y] || null;
+        } else {
+            return null;
+        }
+    },
+
+    /*
+     * 空单元格,格子还未填充数字
+     */
+    emptyCell: function(cell) {
+        return !this.cellContent(cell);
+    },
+
+    withinBounds: function(cell) {
+        return cell.x >= 0 && cell.x < this.size && cell.y >= 0 && cell.y < this.size;
+    },
+
+    eachCell: function(callback) {
+        for (var x = 0; x < this.size; x++) {
+            for (var y = 0; y < this.size; y++) {
+                callback(x, y, this.cells[x][y]);
+            }
+        }
+    }
+}
+
+module.exports = Grid;

+ 5 - 0
src/pages/2048/local_storage_manager.js

@@ -0,0 +1,5 @@
+function LocalStorageManager(){
+	
+}
+
+module.exports = LocalStorageManager;

+ 37 - 0
src/pages/2048/tile.js

@@ -0,0 +1,37 @@
+function Tile(position, value) {
+    this.x = position.x;
+    this.y = position.y;
+    this.value = value || 2;
+
+    this.previousPosition = null;
+    this.mergedFrom = null;
+}
+
+Tile.prototype = {
+
+    // 记录格子上次的位置
+    savePosition: function() {
+        this.previousPosition = { 
+        	x: this.x, 
+        	y: this.y 
+        };
+    },
+
+    // 更新当前格子的位置
+    updatePosition: function(position) {
+        this.x = position.x;
+        this.y = position.y;
+    },
+
+    serialize: function() {
+        return {
+            position: {
+                x: this.x,
+                y: this.y
+            },
+            value: this.value
+        };
+    }
+}
+
+module.exports = Tile;

+ 46 - 0
src/pages/index/index.js

@@ -0,0 +1,46 @@
+//获取应用实例
+var app = getApp();
+
+var config = {
+    data: {
+        disable: false,
+        gameList: ['2048']
+    },
+
+    onLoad: function() {
+        var that = this
+            //调用应用实例的方法获取全局数据
+        app.getUserInfo(function(userInfo) {
+            //更新数据
+            that.setData({
+                userInfo: userInfo
+            })
+        })
+    },
+    onReady: function() {
+        // 页面渲染完毕
+    },
+    onShow: function() {
+        // 页面展示
+    },
+    onHide: function() {
+        // 页面隐藏
+    },
+    onUnload: function() {
+        // 页面关闭
+    }
+};
+
+config.data.gameList.forEach(function(v) {
+    config['start' + v] = function() {
+
+        config.data.disable = true;
+
+        // 这里需要注意每个游戏文件夹名称需和js名称保持一致
+        wx.navigateTo({
+            url: '../' + v + '/' + v
+        })
+    }
+});
+
+Page(config);

+ 3 - 0
src/pages/index/index.json

@@ -0,0 +1,3 @@
+{
+    "navigationBarTitleText":"游戏列表"
+}

+ 9 - 0
src/pages/index/index.wxml

@@ -0,0 +1,9 @@
+<!--index.wxml-->
+<view class="container">
+  	
+	<view wx:for-items="{{gameList}}" class="usermotto">
+	    <button type="primary" disabled="{{disabled}}" bindtap="start{{item}}"> {{item}} </button>
+	</view>
+
+	<text class="more">更多内容,敬请期待...</text>
+</view>

+ 7 - 0
src/pages/index/index.wxss

@@ -0,0 +1,7 @@
+/**index.wxss**/
+.more{
+	color: #aaa;
+	font-size: 14px;
+	position:fixed;
+	bottom: 20px;
+}

+ 14 - 0
src/pages/logs/logs.js

@@ -0,0 +1,14 @@
+//logs.js
+var util = require('../../utils/util.js')
+Page({
+  data: {
+    logs: []
+  },
+  onLoad: function () {
+    this.setData({
+      logs: (wx.getStorageSync('logs') || []).map(function (log) {
+        return util.formatTime(new Date(log))
+      })
+    })
+  }
+})

+ 3 - 0
src/pages/logs/logs.json

@@ -0,0 +1,3 @@
+{
+    "navigationBarTitleText": "查看启动日志"
+}

+ 6 - 0
src/pages/logs/logs.wxml

@@ -0,0 +1,6 @@
+<!--logs.wxml-->
+<view class="container log-list">
+  <block wx:for="{{logs}}" wx:for-item="log">
+    <text class="log-item">{{index + 1}}. {{log}}</text>
+  </block>
+</view>

+ 8 - 0
src/pages/logs/logs.wxss

@@ -0,0 +1,8 @@
+.log-list {
+  display: flex;
+  flex-direction: column;
+  padding: 40rpx;
+}
+.log-item {
+  margin: 10rpx;
+}

+ 21 - 0
src/utils/util.js

@@ -0,0 +1,21 @@
+function formatTime(date) {
+  var year = date.getFullYear()
+  var month = date.getMonth() + 1
+  var day = date.getDate()
+
+  var hour = date.getHours()
+  var minute = date.getMinutes()
+  var second = date.getSeconds()
+
+
+  return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
+}
+
+function formatNumber(n) {
+  n = n.toString()
+  return n[1] ? n : '0' + n
+}
+
+module.exports = {
+  formatTime: formatTime
+}