Преглед изворни кода

feat:增加生成缩略图代码。 refactor:重构盘子摆放代码。feat:增加支持移动条件下盘子组合和摆放。

wangfengdev пре 8 месеци
родитељ
комит
9605abe1fa

+ 2 - 3
auto_fill_jewel_v3/BoostGeometryTools.cpp

@@ -1,8 +1,8 @@
 //
 //  BoostGeometryTools.cpp
-//  Test
+//  auto_fill_jewel_v3
 //
-//  Created by 高慕白 on 2024/12/3.
+//  Created by Red on 2024/11/26.
 //
 
 #include "BoostGeometryTools.hpp"
@@ -88,4 +88,3 @@ bool BoostGeometryTools::pointIntersetsBox(BoostGeometryTools::BoostPoint& ptA,B
 {
     return boost::geometry::intersects(ptA,polyB) ;
 }
-

+ 7 - 4
auto_fill_jewel_v3/BoostGeometryTools.hpp

@@ -1,9 +1,9 @@
 //
 //  BoostGeometryTools.hpp
-//  Test
-//
-//  Created by 高慕白 on 2024/12/3.
+//  auto_fill_jewel_v3
 //
+//  Created by Red on 2024/11/26.
+// 几何计算的相关代码。
 
 #ifndef BoostGeometryTools_hpp
 #define BoostGeometryTools_hpp
@@ -21,7 +21,7 @@ struct BoostGeometryTools {
     static bool isAIntersectsB(BoostPolygon& polyA, BoostPolygon& polyB);//检验多边形A和B是否相交
     static bool isAWithinB(BoostPolygon& polyA, BoostPolygon& polyB) ;// 测试 A 在 B 的内部
     
-    static BoostPolygon makeBox( float lowerLeftX,float lowerLeftY,float wid,float hei ) ;
+    static BoostPolygon makeBox( float lowerLeftX,float lowerLeftY,float wid,float hei ) ; 
     static BoostPolygon makeRotatedBox( float lowerLeftX,float lowerLeftY,float wid,float hei,float rotdeg) ;//绕0,0点旋转
     static BoostPolygon makeRotateNTranslateBox( float lowerLeftX,float lowerLeftY,float wid,float hei,float rotdeg,float dx,float dy) ;//先绕0,0点旋转,然后平移dx和dy
     static void rotatePoint( float x0, float y0, float rotRad, float& x1,float& y1) ;//围绕0,0点旋转一个点,rotRad使用弧度
@@ -30,4 +30,7 @@ struct BoostGeometryTools {
     static bool pointIntersetsBox(BoostPoint& ptA,BoostPolygon& polyB) ;//检验点是否在多边形内部
 } ;
 
+
+
+
 #endif /* BoostGeometryTools_hpp */

+ 3 - 4
auto_fill_jewel_v3/BoxPositionTool.cpp

@@ -1,8 +1,8 @@
 //
 //  BoxPositionTool.cpp
-//  Test
+//  auto_fill_jewel_v3
 //
-//  Created by 高慕白 on 2024/12/3.
+//  Created by Red on 2024/11/29.
 //
 
 #include "BoxPositionTool.hpp"
@@ -25,7 +25,7 @@ void BoxPositionTool::solve(const int visWidth,const int visHeight, vector<vecto
                     numSolv-- ;
                     boxIndex++ ;
                     resultPositions.push_back({x+box[0]/2, y+box[1]/2}) ;
-                    cout<<"debug box at x,y "<<x<<","<<y<<endl;
+                    //cout<<"debug box at x,y "<<x<<","<<y<<endl;
                     x = x+box[0]-1 ;
                 }
             }
@@ -50,4 +50,3 @@ bool BoxPositionTool::interectsWithOthers(BoostGeometryTools::BoostPolygon& poly
     }
     return false ;
 }
-

+ 3 - 3
auto_fill_jewel_v3/BoxPositionTool.hpp

@@ -1,9 +1,9 @@
 //
 //  BoxPositionTool.hpp
-//  Test
-//
-//  Created by 高慕白 on 2024/12/3.
+//  auto_fill_jewel_v3
 //
+//  Created by Red on 2024/11/29.
+// 多个盘子包围盒子摆放工具类
 
 #ifndef BoxPositionTool_hpp
 #define BoxPositionTool_hpp

+ 2 - 3
auto_fill_jewel_v3/FillGlobalConfig.cpp

@@ -1,8 +1,8 @@
 //
 //  FillGlobalConfig.cpp
-//  Test
+//  auto_fill_jewel_v3
 //
-//  Created by 高慕白 on 2024/12/3.
+//  Created by Red on 2024/11/25.
 //
 
 #include "FillGlobalConfig.hpp"
@@ -205,4 +205,3 @@ bool FillGlobalConfig::initPlateItems( string filename )
     }
     return true ;
 }
-

+ 8 - 9
auto_fill_jewel_v3/FillGlobalConfig.hpp

@@ -1,20 +1,18 @@
 //
 //  FillGlobalConfig.hpp
-//  Test
-//
-//  Created by 高慕白 on 2024/12/3.
+//  auto_fill_jewel_v3
 //
+//  Created by Red on 2024/11/25.
+// 从 config.json 读取全局盘子和宝石配置数据
 
 #ifndef FillGlobalConfig_hpp
 #define FillGlobalConfig_hpp
 
 #include <stdio.h>
-
-#include <stdio.h>
 #include "contourdata.h"
 #include <string>
 using std::string;
-#include "ajson5.h"
+#include "ajson5.hpp"
 #include <unordered_map>
 using std::unordered_map;
 #include <vector>
@@ -83,6 +81,8 @@ struct FillGlobalConfig {
     //levelId 是关卡主编号值, subIndex是子关卡的索引值(从0开始)
     LevelData* getLevelData(int levelId,int subIndex) { return (_mapLevelDatas.find(levelId)!=_mapLevelDatas.end())?(&_mapLevelDatas[levelId][subIndex]):nullptr; }
     int        getSubLevelCount(int levelId) { return (_mapLevelDatas.find(levelId)!=_mapLevelDatas.end())?(_mapLevelDatas[levelId].size()):0; }
+    int getLevelCount() { return _mapLevelDatas.size() ; }
+    LevelData& getLevelDataByIndex(int index) { auto it = _mapLevelDatas.begin(); std::advance(it,index); return it->second[0] ;}
     
     //盘子基本信息
     struct PlateItem {
@@ -92,7 +92,7 @@ struct FillGlobalConfig {
         string _pngName ;
         string _contourName;
         float _blx,_bly,_trx,_try ;// bl for bottom-left, tr for top-right.
-        int    _bigJewCap ;//大宝石容量
+        double    _bigJewCap ;//大宝石容量
         float _bbwid,_bbhei ;//精灵图片尺寸
         string _etc ;
     } ;
@@ -110,5 +110,4 @@ private:
     
 } ;
 
-
-#endif /* FillGlobalConfig_hpp */
+#endif /* GlobalConfig_hpp */

+ 4 - 3
auto_fill_jewel_v3/FillResult.hpp

@@ -1,9 +1,9 @@
 //
 //  FillResult.hpp
-//  Test
-//
-//  Created by 高慕白 on 2024/12/3.
+//  auto_fill_jewel_v3
 //
+//  Created by Red on 2024/11/26.
+// 定义填充结果信息
 
 #ifndef FillResult_hpp
 #define FillResult_hpp
@@ -20,4 +20,5 @@ struct FillResult {
     
 } ;
 
+
 #endif /* FillResult_hpp */

+ 183 - 0
auto_fill_jewel_v3/GridPositionTool.cpp

@@ -0,0 +1,183 @@
+//
+//  GridPositionTool.cpp
+//  auto_fill_jewel_v3
+//
+//  Created by Red on 2024/12/4.
+//
+
+#include "GridPositionTool.hpp"
+#include <utility>
+#include <iostream>
+using std::pair ;
+
+#define GRIDPOSITIONTOOL_BIG_GRID_NCOLS 3
+#define GRIDPOSITIONTOOL_BIG_GRID_NROWS 3
+#define GRIDPOSITIONTOOL_GAME_WIDTH 640
+#define GRIDPOSITIONTOOL_GAME_HEIGHT 720
+#define GRIDPOSITIONTOOL_LG_W 210
+#define GRIDPOSITIONTOOL_LG_H 270
+#define GRIDPOSITIONTOOL_MD_W 210
+#define GRIDPOSITIONTOOL_MD_H 135
+#define GRIDPOSITIONTOOL_SM_W 105
+#define GRIDPOSITIONTOOL_SM_H 135
+
+
+
+void GridPositionTool::solve(const bool movable,const vector<int>& plateTypeIdArr , vector<vector<int>>& resultPositions )
+{
+    //定义格网左上角为0,0
+    //pair坐标是先列后行, pair<col,row>
+    vector<vector<int>> bigIndices = {
+        {6,4,7},
+        {1,2,3},
+        {8,5,9}
+    } ;
+    
+    vector<vector<int>> midIndices = {
+        {11, 7,13},
+        {12, 8,14},
+        { 1, 3, 5},
+        { 2, 4, 6},
+        {15, 9,17},
+        {16,10,18}
+    } ;
+    
+    vector<vector<int>> smlIndices = {
+        { 21,22,13,14,25,26},
+        { 23,24,15,16,27,28},
+        {  1, 2, 5, 6, 9,10},
+        {  3, 4, 7, 8,11,12},
+        { 29,30,17,18,33,34},
+        { 31,32,19,20,35,36}
+    } ;
+    
+    if( movable ) {
+        vector<vector<int>> bigIndices2 = {
+            {7,5, 8,-1},
+            {2,3, 4, 1},
+            {9,6,10,-1}
+        } ;
+        
+        vector<vector<int>> midIndices2 = {
+            {13, 9,15,-1},
+            {14,10,16,-1},
+            { 3, 5, 7, 1},
+            { 4, 6, 8, 2},
+            {17,11,19,-1},
+            {18,12,20,-1}
+        } ;
+        
+        vector<vector<int>> smlIndices2 = {
+            {25,26,17,18,29,30,-1,-1},
+            {27,28,19,20,31,32,-1,-1},
+            { 5, 6, 9,10,13,14, 1, 2},
+            { 7, 8,11,12,15,16, 3, 4},
+            {33,34,21,22,37,38,-1,-1},
+            {35,36,23,24,39,40,-1,-1}
+        } ;
+        bigIndices = bigIndices2 ;
+        midIndices = midIndices2;
+        smlIndices = smlIndices2;
+    }
+    
+    int currindex = 1 ;
+    auto fgc = FillGlobalConfig::getInstance() ;
+    //先安排大盘子
+    for(int ijew = 0 ; ijew < plateTypeIdArr.size(); ++ ijew ) {
+        int typeId = plateTypeIdArr[ijew] ;
+        FillGlobalConfig::PlateItem* plate = fgc->getPlateItemById(typeId) ;
+        if( plate->_size == FILLGLOBALCONFIG_PLATESIZE_LG ) {
+            int col=0;
+            int row=0;
+            if( getIndexPositionInGrid(bigIndices, currindex, col, row) ) {
+                vector<int> posi = computePositionByGridColRow(FILLGLOBALCONFIG_PLATESIZE_LG, col, row) ;
+                resultPositions.push_back(posi) ;
+                currindex++ ;
+            }
+        }
+    }
+    
+    //安排中盘子
+    currindex = currindex*2 - 1 ;
+    for(int ijew = 0 ; ijew < plateTypeIdArr.size(); ++ ijew ) {
+        int typeId = plateTypeIdArr[ijew] ;
+        FillGlobalConfig::PlateItem* plate = fgc->getPlateItemById(typeId) ;
+        if( plate->_size == FILLGLOBALCONFIG_PLATESIZE_MD ) {
+            int col=0;
+            int row=0;
+            if( getIndexPositionInGrid(midIndices, currindex, col, row) ) {
+                vector<int> posi = computePositionByGridColRow(FILLGLOBALCONFIG_PLATESIZE_MD, col, row) ;
+                resultPositions.push_back(posi) ;
+                currindex++ ;
+            }
+        }
+    }
+    
+    //安排小盘子
+    currindex = currindex*2 - 1 ;
+    for(int ijew = 0 ; ijew < plateTypeIdArr.size(); ++ ijew ) {
+        int typeId = plateTypeIdArr[ijew] ;
+        FillGlobalConfig::PlateItem* plate = fgc->getPlateItemById(typeId) ;
+        if( plate->_size == FILLGLOBALCONFIG_PLATESIZE_SM ) {
+            int col=0;
+            int row=0;
+            if( getIndexPositionInGrid(smlIndices, currindex, col, row) ) {
+                vector<int> posi = computePositionByGridColRow(FILLGLOBALCONFIG_PLATESIZE_SM, col, row) ;
+                resultPositions.push_back(posi) ;
+                currindex++ ;
+            }
+        }
+    }
+    
+}
+
+
+bool GridPositionTool::getIndexPositionInGrid( const vector<vector<int>>& gridRows, const int index, int& col,int& row)
+{
+    col = -1 ;
+    row = -1 ;
+    for(int irow = 0 ; irow < gridRows.size();++irow ) {
+        for(int icol=0; icol < gridRows[irow].size(); ++ icol ) {
+            if( gridRows[irow][icol] == index ) {
+                col = icol ;
+                row = irow ;
+                return true ;
+            }
+        }
+    }
+    std::cout<<"error: could not found index from grid. "<<index<<std::endl;
+    return false;
+}
+
+
+vector<int> GridPositionTool::computePositionByGridColRow(int plateSize, int col, int row )
+{
+    if( plateSize == FILLGLOBALCONFIG_PLATESIZE_LG ) {
+        //将左上角原点的网格坐标换算成左下角原点网格坐标
+        row = GRIDPOSITIONTOOL_BIG_GRID_NROWS - 1 - row ;
+        int x0 = 0 ;
+        int y0 = GRIDPOSITIONTOOL_GAME_HEIGHT/2 - GRIDPOSITIONTOOL_BIG_GRID_NROWS*GRIDPOSITIONTOOL_LG_H/2;
+        int widthAndMargin = GRIDPOSITIONTOOL_GAME_WIDTH / GRIDPOSITIONTOOL_BIG_GRID_NCOLS ;
+        return {
+            (int)(x0+(col+0.5)*widthAndMargin       ) ,
+            (int)(y0+(row+0.5)*GRIDPOSITIONTOOL_LG_H) } ;
+    }else if( plateSize==FILLGLOBALCONFIG_PLATESIZE_MD ) {
+        //将左上角原点的网格坐标换算成左下角原点网格坐标
+        row = GRIDPOSITIONTOOL_BIG_GRID_NROWS*2 - 1 - row ;
+        int x0 = 0 ;
+        int y0 = GRIDPOSITIONTOOL_GAME_HEIGHT/2 - GRIDPOSITIONTOOL_BIG_GRID_NROWS * 2 * GRIDPOSITIONTOOL_MD_H/2;
+        int widthAndMargin = GRIDPOSITIONTOOL_GAME_WIDTH / GRIDPOSITIONTOOL_BIG_GRID_NCOLS ;
+        return {
+            (int)(x0+(col+0.5)*widthAndMargin       ) ,
+            (int)(y0+(row+0.5)*GRIDPOSITIONTOOL_MD_H) } ;
+    }else { // FILLGLOBALCONFIG_JEWELSIZE_SM
+        //将左上角原点的网格坐标换算成左下角原点网格坐标
+        row = GRIDPOSITIONTOOL_BIG_GRID_NROWS*2 - 1 - row ;
+        int x0 = 0 ;
+        int y0 = GRIDPOSITIONTOOL_GAME_HEIGHT/2 - GRIDPOSITIONTOOL_BIG_GRID_NROWS * 2 * GRIDPOSITIONTOOL_SM_H/2;
+        int widthAndMargin = GRIDPOSITIONTOOL_GAME_WIDTH / (GRIDPOSITIONTOOL_BIG_GRID_NCOLS*2) ;
+        return {
+            (int)(x0+(col+0.5)*widthAndMargin       ) ,
+            (int)(y0+(row+0.5)*GRIDPOSITIONTOOL_SM_H) } ;
+    }
+}

+ 37 - 0
auto_fill_jewel_v3/GridPositionTool.hpp

@@ -0,0 +1,37 @@
+//
+//  GridPositionTool.hpp
+//  auto_fill_jewel_v3
+//
+//  Created by Red on 2024/12/4.
+//
+
+#ifndef GridPositionTool_hpp
+#define GridPositionTool_hpp
+
+#include <stdio.h>
+#include <vector>
+#include "FillGlobalConfig.hpp"
+
+using std::vector;
+
+struct GridPositionTool {
+    /// 大盘子 210 210 210。      3x3=9个网格
+    /// 中盘子 210 210 210。       3x6=18 个网格
+    /// 小盘子 110 110 110 110 110 110  6x6=36 个网格
+    
+    /// 算法 https://t7le908iko.feishu.cn/docx/Cn4tdr0S7o76Ffx4lrVcJtManBg 第6节的顺序进行填充
+    /// @param plateTypeIdArr   盘子类型ID数组,通过盘子类型ID可以获取盘子大中小尺寸信息
+    /// @param resultPositions 是输出结果,对应输入矩形顺序,每个数据是一个box中心在整个可视化区域(640,720)的坐标。可视化区域坐标原点在左下角。
+    void solve(const bool movable, const vector<int>& plateTypeIdArr , vector<vector<int>>& resultPositions ) ;
+    
+private:
+    //给定顺序值index,从网格Grid中找到对应的网格列行坐标。列行坐标从0开始计。
+    //找到了返回true,反之返回false
+    bool getIndexPositionInGrid( const vector<vector<int>>& gridRows, const int index, int& col,int& row) ;
+    
+    //输入网格col和row,col和row是左上角网格原点,返回的是盘子中心x,y像素坐标 游戏区左下角为坐标原点。
+    vector<int> computePositionByGridColRow(int plateSize, int col, int row ) ;
+    
+} ;
+
+#endif /* GridPositionTool_hpp */

+ 264 - 37
auto_fill_jewel_v3/LevelGenerate.cpp

@@ -1,8 +1,8 @@
 //
 //  LevelGenerate.cpp
-//  Test
+//  auto_fill_jewel_v3
 //
-//  Created by 高慕白 on 2024/12/3.
+//  Created by Red on 2024/11/23.
 //
 
 #include "LevelGenerate.hpp"
@@ -12,10 +12,12 @@
 #include "FillGlobalConfig.hpp"
 #include <tuple>
 #include "BoxPositionTool.hpp"
+#include "GridPositionTool.hpp"
 
 using namespace std;
 
 bool LevelGenerate::generate( FillGlobalConfig::LevelData levelData,
+                             const bool isMovable,
                              vector<tuple<int,vector<vector<FillResult>>>>& resultPlateFillResults,
                              vector<ContourData::Point>& resultPlateCenterPointArr
                              )
@@ -59,15 +61,15 @@ bool LevelGenerate::generate( FillGlobalConfig::LevelData levelData,
     }
     //等效小宝石数量
     int effSmallJewelsCount = bigJewelCnt*4 + midJewelCnt*2 + smlJewelCnt ;
-    cout<<"bigJewelCnt "<<bigJewelCnt<<endl;
-    cout<<"midJewelCnt "<<midJewelCnt<<endl;
-    cout<<"smlJewelCnt "<<smlJewelCnt<<endl;
-    cout<<"effSmallJewelsCount "<<effSmallJewelsCount<<endl;
+    //cout<<"bigJewelCnt "<<bigJewelCnt<<endl;
+    //cout<<"midJewelCnt "<<midJewelCnt<<endl;
+    //cout<<"smlJewelCnt "<<smlJewelCnt<<endl;
+    //cout<<"effSmallJewelsCount "<<effSmallJewelsCount<<endl;
     
     //构建符合难度条件的盘子和层数组合
-    vector<PlateIdAndLayerCnt> resPlateNLyrCnt = generatePlateTypeAndLayerCnts(levelData._difficulty,effSmallJewelsCount);
+    vector<PlateIdAndLayerCnt> resPlateNLyrCnt = generatePlateTypeAndLayerCnts2(isMovable, levelData._difficulty,effSmallJewelsCount);
     if( resPlateNLyrCnt.size()==0 ) {
-        cout<<"failed at generatePlateTypeAndLayerCnts"<<endl;
+        //cout<<"failed at generatePlateTypeAndLayerCnts"<<endl;
         return false ;
     }
     
@@ -78,6 +80,7 @@ bool LevelGenerate::generate( FillGlobalConfig::LevelData levelData,
     //对每个盘子组合进行填充宝石
     RandomGridFiller filler ;
     filler._seed = this->_seed ;
+    srand(filler._seed);
     
     //构建每个盘子每个层的填充结果容器
     vector< tuple<int,int,vector<FillResult>> > plateIdLyrFillResultsArray ;// tuple<plateId, layerIndex, fillresults_array >
@@ -86,7 +89,7 @@ bool LevelGenerate::generate( FillGlobalConfig::LevelData levelData,
     //从顶到底部,逐层进行填充
     int residues = 0 ;
     for(int nlayer = maxLyrCnt; nlayer > 0 ; nlayer-- ) {
-        cout<<"for layer "<<nlayer<<endl;
+        //cout<<"for layer "<<nlayer<<endl;
         
         //满足该层数的所有盘子ID
         vector<int> plateIdArr ;
@@ -97,7 +100,7 @@ bool LevelGenerate::generate( FillGlobalConfig::LevelData levelData,
         for(int ip=0;ip<plateIdArr.size();++ip ) {
             currLyrNeedEquvSmlCnt += fgc->getPlateItemById(plateIdArr[ip])->_bigJewCap*4 ;
         }
-        cout<<"layer need eqsmCnt "<<currLyrNeedEquvSmlCnt<<endl;
+        //cout<<"layer need eqsmCnt "<<currLyrNeedEquvSmlCnt<<endl;
         
         //根据所需数量和所需求解百分比,计算从库存取出每类宝石多少个。
         float solPercent = 0.7 ;//根据需求文档,每层可解百分比都是70%.
@@ -106,7 +109,7 @@ bool LevelGenerate::generate( FillGlobalConfig::LevelData levelData,
         for(auto itp=chosenJewIdNCnt.begin();itp!=chosenJewIdNCnt.end();++itp) {
             choseEqSmCnt += jewSz2SmlCnt( fgc->getJewelItemById(itp->first)->_size) * itp->second ;
         }
-        cout<<"layer choose eqsmCnt "<<choseEqSmCnt<<endl;
+        //cout<<"layer choose eqsmCnt "<<choseEqSmCnt<<endl;
         
         //逐个盘子填充
         for(auto itpid = plateIdArr.begin();itpid!=plateIdArr.end();++itpid) {
@@ -123,7 +126,7 @@ bool LevelGenerate::generate( FillGlobalConfig::LevelData levelData,
                 std::get<1>(jewIdSzCntStorageMap[itjn->first]) += itjn->second ;//剩余的加回到库存里
             }
         }
-        cout<<"layer chosen jewels residues "<<residues<<endl;
+        //cout<<"layer chosen jewels residues "<<residues<<endl;
     }
     
     //查看库存是否全部用掉了
@@ -134,12 +137,12 @@ bool LevelGenerate::generate( FillGlobalConfig::LevelData levelData,
         while( residueJewIdAndCnts.size()>0 ) {
             //新建一个小盘子
             extraPlateCnt++;
-            cout<<"add extra plate "<< extraPlateCnt <<endl;
+            //cout<<"add extra plate "<< extraPlateCnt <<endl;
             int plateId = smlPlate->_id ;
             for(int ilyr = 1 ; ilyr <= maxLyrCnt; ++ ilyr ) {
                 vector<FillResult> frs = fillPlateOneLayer(filler, plateId, residueJewIdAndCnts) ;
                 plateIdLyrFillResultsArray.push_back( {plateId,ilyr,frs} );
-                cout<<"add extra lyr "<<endl;
+                //cout<<"add extra lyr "<<endl;
                 if( residueJewIdAndCnts.size()==0 ) break ;
             }
             if( residueJewIdAndCnts.size()==0 ) break ;
@@ -153,17 +156,19 @@ bool LevelGenerate::generate( FillGlobalConfig::LevelData levelData,
     {
         vector<int> plateIdArr1;
         for(auto itp = resultPlateFillResults.begin();itp!=resultPlateFillResults.end();++itp) plateIdArr1.push_back( std::get<0>(*itp) );
-        placePlates(plateIdArr1, resultPlateCenterPointArr) ;
+        placePlates(isMovable, plateIdArr1, resultPlateCenterPointArr) ;
     }
     
     
     auto ms1 = std::chrono::system_clock::now().time_since_epoch() ;
     uint64_t ms11 = std::chrono::duration_cast<chrono::milliseconds>(ms1).count() ;
-    cout<<"duration "<<ms11-ms00<<endl;
+    //cout<<"duration "<<ms11-ms00<<endl;
     return true ;
 }
 
-vector<LevelGenerate::PlateIdAndLayerCnt> LevelGenerate::generatePlateTypeAndLayerCnts( const int difficulty,const int totEquvSmallJewelCnt)
+// this method is deprecated, use generatePlateTypeAndLayerCnts2()
+[[deprecated]]
+vector<LevelGenerate::PlateIdAndLayerCnt> LevelGenerate::generatePlateTypeAndLayerCnts(const int difficulty,const int totEquvSmallJewelCnt)
 {
     assert(difficulty>=0&&difficulty<=2) ;
     vector<vector<LevelGenerate::PlateIdAndLayerCnt>> possibleResults ;
@@ -184,9 +189,11 @@ vector<LevelGenerate::PlateIdAndLayerCnt> LevelGenerate::generatePlateTypeAndLay
     //                                                 普通               难              极难
     vector< vector<float> > diffPlateCntPercent0 = { {0.3,0.3,0.0}, {0.2,0.4,0.0}, {0.1,0.5,0.0} } ;
     vector< vector<float> > diffPlateCntPercent1 = { {0.7,0.7,0.0}, {0.6,0.8,0.1}, {0.5,0.9,0.1} } ;
+    bool isFirstGood = false;//由于盘子百分比计算不一定能满足盘子和宝石要求,为了保证至少有一个盘子组合可以用这里保留第一个可以装下宝石的盘子组合,如果有更优的方案替换之。
+    vector<LevelGenerate::PlateIdAndLayerCnt> firstAvailablePlateComb ;
     
     for(int ipbig = 1; ipbig <= PLATE_BG_MAX_CNT; ++ ipbig ) {
-        for(int ipmid = 1 ; ipmid <= PLATE_MD_MAX_CNT; ++ ipmid ) {
+        for(int ipmid = 0 ; ipmid <= PLATE_MD_MAX_CNT; ++ ipmid ) {
             int effsmPlateCnt = ipbig*6 + ipmid*3 ;
             if( effsmPlateCnt>PLATE_SM_MAX_CNT ) continue ;
             for(int ipsml = 0 ; ipsml <= PLATE_SM_MAX_CNT; ++ ipsml ) {
@@ -205,8 +212,30 @@ vector<LevelGenerate::PlateIdAndLayerCnt> LevelGenerate::generatePlateTypeAndLay
                     if( diffPlateCntPercent0[difficulty][iplatetype]>percentArr[iplatetype] || diffPlateCntPercent1[difficulty][iplatetype]<percentArr[iplatetype]  )
                         percentOk=false;
                 }
-                if(!percentOk) continue ;//盘子百分比不符合要求,跳过
+                if( isFirstGood == true ){
+                    //至少有一个装下宝石的方案了
+                    if( !percentOk ) continue ;//盘子百分比不符合要求,跳过
+                }
                 
+                if( isFirstGood==false ) {
+                    //保证至少有一个可以满足宝石的盘子组合
+                    vector<int> pidArr ;
+                    vector<int> capArr ;
+                    vector<int> lyrArr ;
+                    for(int it=0;it<ipbig;++it) { pidArr.push_back(bigPlate->_id);capArr.push_back(bigPlate->_bigJewCap*4); lyrArr.push_back(1); }
+                    for(int it=0;it<ipmid;++it) { pidArr.push_back(midPlate->_id);capArr.push_back(midPlate->_bigJewCap*4); lyrArr.push_back(1); }
+                    for(int it=0;it<ipsml;++it) { pidArr.push_back(smlPlate->_id);capArr.push_back(smlPlate->_bigJewCap*4); lyrArr.push_back(1); }
+                    bool lyrOk = isJustFilledAll2(capArr, lyrArr, totEquvSmallJewelCnt) ;
+                    if( lyrOk ){
+                        isFirstGood = true ;
+                        //保存结果
+                        firstAvailablePlateComb.resize(capArr.size());
+                        for(int it = 0 ; it < capArr.size(); ++ it ) {
+                            firstAvailablePlateComb[it]._plateId = pidArr[it] ;
+                            firstAvailablePlateComb[it]._layerCnt = lyrArr[it] ;
+                        }
+                    }
+                }
                 //计算每个类型的层数
                 if( difficulty== FILLGLOBALCONFIG_DIFFCULTY_NORM ) {
                     //全部两层
@@ -265,12 +294,220 @@ vector<LevelGenerate::PlateIdAndLayerCnt> LevelGenerate::generatePlateTypeAndLay
             }
         }
     }
-    //debug 查看全部符合要求的结果
+    //随机挑选一个结果返回
+    if( possibleResults.size()> 0 ) {
+        int randindex = rand() % possibleResults.size();
+        results = possibleResults[randindex] ;
+    }else {
+        results = firstAvailablePlateComb ;
+    }
+    return results ;
+}
+
+//考虑可移动关卡的条件
+vector<LevelGenerate::PlateIdAndLayerCnt> LevelGenerate::generatePlateTypeAndLayerCnts2(const bool isMovable,const int difficulty,const int totEquvSmallJewelCnt)
+{
+    //移动关卡相当于多了一个 大盘子,整个关卡最多可以达到10个大盘子
+    //如果是移动关卡,那么至少需要保证如下等价小盘子数量,如果减少层数仍然不能满足那么就有几个盘子输出几个
+    const int MAX_LG_PLATE_CNT = 9 + (isMovable?1:0) ;
+    const int MAX_SM_PLATE_CNT = MAX_LG_PLATE_CNT*4 ;
+    const int MAX_SM_JEWEL_CNT_PER_LAYER = MAX_LG_PLATE_CNT * 24 ;
+    
+    const int minEquaSmPlateCntForMovable = 16 ; //
+    const int smPlateEquaSmJewelCnt = 6 ;// 小盘子装1.5个大,3个中,6个小
+    
+    //一个关卡最多出现大、中、小盘子个数
+    int bigPlateMaxCnt = 9  ;// 1个大盘子=2两个中盘子=4个小盘子
+    if( isMovable ) {
+        bigPlateMaxCnt = 10 ;
+    }
+    int midPlateMaxCnt = bigPlateMaxCnt * 2 ;
+    int smlPlateMaxCnt = bigPlateMaxCnt * 2 ;
+    
+    
+    vector<LevelGenerate::PlateIdAndLayerCnt> results ;
+    
+    FillGlobalConfig* fgc = FillGlobalConfig::getInstance() ;
+    FillGlobalConfig::PlateItem* bigPlate = fgc->getPlateItemBySzNIndex(FILLGLOBALCONFIG_PLATESIZE_LG, 0) ;
+    FillGlobalConfig::PlateItem* midPlate = fgc->getPlateItemBySzNIndex(FILLGLOBALCONFIG_PLATESIZE_MD, 0) ;
+    FillGlobalConfig::PlateItem* smlPlate = fgc->getPlateItemBySzNIndex(FILLGLOBALCONFIG_PLATESIZE_SM, 0) ;
+    vector<FillGlobalConfig::PlateItem*> usedPlates = {bigPlate,midPlate,smlPlate} ;
+    
+    int equvSmPlateCnt = ceil( totEquvSmallJewelCnt * 1.0 / 6) ; // 本款需要多少个一层小盘子的总数量
+    int layerCnt0 = 1 ;//盘子层数下限
+    int layerCnt1 = 1 ;//盘子层数上限
+    
+    //计算可能的层取值范围
+    bool forceToFitMovable = false ;
+    if( isMovable ) {
+        if( equvSmPlateCnt <= minEquaSmPlateCntForMovable ) {
+            //不足移动关卡所需最小的等效小盘子个数,那么直接组合盘子即可。
+            layerCnt0 = 1 ;
+            layerCnt1 = 1 ;
+            forceToFitMovable = false ;
+        }else
+        {
+            //10个大盘子,6个大宝石,等效于每层最多放置 10*6*4=240个小宝石
+            layerCnt0 = fmax(1 , totEquvSmallJewelCnt / MAX_SM_JEWEL_CNT_PER_LAYER ) ;
+            if( layerCnt0<3 ) layerCnt1 = 3 ;
+            else layerCnt1 = layerCnt0 + 1 ;
+            forceToFitMovable = true ;
+        }
+    }else {
+        layerCnt0 = fmax(1 , totEquvSmallJewelCnt / MAX_SM_JEWEL_CNT_PER_LAYER ) ;
+        if( layerCnt0<3 ) layerCnt1 = 3 ;
+        else layerCnt1 = layerCnt0 + 1 ;
+        forceToFitMovable = false ;
+    }
+    
+    int bigPlateCnt0 = totEquvSmallJewelCnt * 1.0 / layerCnt1 / 24 ; //大盘子最少需要数量
+    int bigPlateCnt1 = totEquvSmallJewelCnt * 1.0 / layerCnt0 / 24 + 1; //大盘子最多需要数量
+    int midPlateCnt0 = bigPlateCnt0*2 ;
+    int midPlateCnt1 = bigPlateCnt1*2 ;
+    int smlPlateCnt0 = bigPlateCnt0*4 ;
+    int smlPlateCnt1 = bigPlateCnt1*4 ;
+    
+    bigPlateCnt1 = fmin( bigPlateMaxCnt ,bigPlateCnt1 ) ;
+    midPlateCnt1 = fmin( midPlateMaxCnt ,midPlateCnt1 ) ;
+    smlPlateCnt1 = fmin( smlPlateMaxCnt ,smlPlateCnt1 ) ;
+    
+    bool findFirstAvailableComb = false ;//第一个完全放入宝石的组合,用于当不满足任何条件时,至少返回一个可装入全部宝石的默认方案
+    vector<vector<LevelGenerate::PlateIdAndLayerCnt>> possibleResults ;//满足全部百分比要求的组合
+    vector<LevelGenerate::PlateIdAndLayerCnt> firstAvailablePlateComb ;//第一个满足全部宝石填充的组合,如果需要移动还要考虑最小移动盘子个数的条件
+    int firstAvailableLyrCnt = 0 ;
+    
+    //难度对盘子数量的限制                                 普通               难              极难
+    vector< vector<float> > hardPlateCntPercent0 = { {0.3,0.3,0.0}, {0.2,0.4,0.0}, {0.1,0.5,0.0} } ;
+    vector< vector<float> > hardPlateCntPercent1 = { {0.7,0.7,0.0}, {0.6,0.8,0.1}, {0.5,0.9,0.1} } ;
+    
+    //穷举全部盘子和层的组合找到符合条件的组合
+    for(int ibig = 0; ibig <= bigPlateCnt1; ++ ibig ) {
+        for(int imid = 0; imid <= midPlateCnt1 ; ++ imid ) {
+            for(int isml = 0 ; isml <= smlPlateCnt1; ++ isml ) {
+                int equvSmPlateCnt1 = isml+imid*2+ibig*4 ;
+                if( equvSmPlateCnt1==0 || equvSmPlateCnt1 > MAX_SM_PLATE_CNT ) continue ;
+                if( equvSmPlateCnt1 < smlPlateCnt0 ) continue ;
+                if( isMovable && forceToFitMovable ) if( equvSmPlateCnt1<minEquaSmPlateCntForMovable ) continue ;
+                
+                float bigPlateCntPercent = ibig*4.0 / equvSmPlateCnt1 ;
+                float midPlateCntPercent = imid*2.0 / equvSmPlateCnt1 ;
+                float smlPlateCntPercent = isml*1.0 / equvSmPlateCnt1 ;
+                vector<float> percentArr = {bigPlateCntPercent,midPlateCntPercent,smlPlateCntPercent} ;
+                //判断每个盘子百分比是否满足难度要求
+                bool percentOk = true ;
+                for(int iplatetype=0;iplatetype<percentArr.size();++iplatetype) {
+                    if( hardPlateCntPercent0[difficulty][iplatetype]>percentArr[iplatetype] || hardPlateCntPercent1[difficulty][iplatetype]<percentArr[iplatetype]  )
+                        percentOk=false;
+                }
+                if( findFirstAvailableComb == true ){
+                    //至少有一个装下宝石的方案了
+                    if( !percentOk ) continue ;//盘子百分比不符合要求,跳过
+                }
+                
+                if( findFirstAvailableComb==false || (findFirstAvailableComb && firstAvailableLyrCnt>3 )  ) {
+                    //保证至少有一个可以满足宝石的盘子组合,优先保存总层数大于3的情况
+                    //当前盘子组合下最小满足的层数
+                    int upperLyrCnt = ceil( totEquvSmallJewelCnt * 1.0 / (equvSmPlateCnt1*6) ) ;
+                    bool upperLyrCntOk = true ;
+                    if( findFirstAvailableComb ) {
+                        if( upperLyrCnt >= firstAvailableLyrCnt ) {
+                            upperLyrCntOk = false ;
+                        }
+                    }
+                    vector<int> pidArr ;
+                    vector<int> capArr ;
+                    vector<int> lyrArr ;
+                    for(int it=0;it<ibig;++it) { pidArr.push_back(bigPlate->_id);capArr.push_back(bigPlate->_bigJewCap*4); lyrArr.push_back(upperLyrCnt); }
+                    for(int it=0;it<imid;++it) { pidArr.push_back(midPlate->_id);capArr.push_back(midPlate->_bigJewCap*4); lyrArr.push_back(upperLyrCnt); }
+                    for(int it=0;it<isml;++it) { pidArr.push_back(smlPlate->_id);capArr.push_back(smlPlate->_bigJewCap*4); lyrArr.push_back(upperLyrCnt); }
+                    bool lyrOk = isJustFilledAll2(capArr, lyrArr, totEquvSmallJewelCnt) ;
+                    if( lyrOk==false && upperLyrCnt>1 ) {
+                        for(int it=0;it<lyrArr.size();++it ) {
+                            lyrArr[it] -= 1 ;
+                            lyrOk = isJustFilledAll2(capArr, lyrArr, totEquvSmallJewelCnt) ;
+                            if( lyrOk ) break ;
+                        }
+                    }
+                    if( lyrOk && upperLyrCntOk ){
+                        findFirstAvailableComb = true ;
+                        firstAvailableLyrCnt = upperLyrCnt ;
+                        //保存结果
+                        firstAvailablePlateComb.resize(capArr.size());
+                        for(int it = 0 ; it < capArr.size(); ++ it ) {
+                            firstAvailablePlateComb[it]._plateId = pidArr[it] ;
+                            firstAvailablePlateComb[it]._layerCnt = lyrArr[it] ;
+                        }
+                    }
+                }
+                //计算每个类型的层数
+                if( difficulty== FILLGLOBALCONFIG_DIFFCULTY_NORM ) {
+                    //全部两层
+                    vector<int> pidArr ;
+                    vector<int> capArr ;
+                    vector<int> lyrArr ;
+                    for(int it=0;it<ibig;++it) { pidArr.push_back(bigPlate->_id);capArr.push_back(bigPlate->_bigJewCap*4); lyrArr.push_back(2); }
+                    for(int it=0;it<imid;++it) { pidArr.push_back(midPlate->_id);capArr.push_back(midPlate->_bigJewCap*4); lyrArr.push_back(2); }
+                    for(int it=0;it<isml;++it) { pidArr.push_back(smlPlate->_id);capArr.push_back(smlPlate->_bigJewCap*4); lyrArr.push_back(2); }
+                    bool lyrOk = isJustFilledAll2(capArr, lyrArr, totEquvSmallJewelCnt) ;
+                    if(!lyrOk) continue;
+                    
+                    //保存结果
+                    vector<LevelGenerate::PlateIdAndLayerCnt> tres(capArr.size()) ;
+                    for(int it = 0 ; it < capArr.size(); ++ it ) {
+                        tres[it]._plateId = pidArr[it] ;
+                        tres[it]._layerCnt = lyrArr[it] ;
+                    }
+                    possibleResults.push_back(tres) ;
+                    
+                }else if( difficulty==FILLGLOBALCONFIG_DIFFCULTY_HARD2 ) {
+                    //全部3层
+                    vector<int> pidArr ;
+                    vector<int> capArr ;
+                    vector<int> lyrArr ;
+                    for(int it=0;it<ibig;++it) { pidArr.push_back(bigPlate->_id);capArr.push_back(bigPlate->_bigJewCap*4); lyrArr.push_back(3); }
+                    for(int it=0;it<imid;++it) { pidArr.push_back(midPlate->_id);capArr.push_back(midPlate->_bigJewCap*4); lyrArr.push_back(3); }
+                    for(int it=0;it<isml;++it) { pidArr.push_back(smlPlate->_id);capArr.push_back(smlPlate->_bigJewCap*4); lyrArr.push_back(3); }
+                    //cout<<"debug big "<<ibig<<" mid "<<imid<<" sml "<<isml<<endl;
+                    bool lyrOk = isJustFilledAll2(capArr, lyrArr, totEquvSmallJewelCnt) ;
+                    if(!lyrOk) continue;
+                    //保存结果
+                    vector<LevelGenerate::PlateIdAndLayerCnt> tres(capArr.size()) ;
+                    for(int it = 0 ; it < capArr.size(); ++ it ) {
+                        tres[it]._plateId = pidArr[it] ;
+                        tres[it]._layerCnt = lyrArr[it] ;
+                    }
+                    possibleResults.push_back(tres) ;
+                }else{
+                    //50% 2层, 50% 3层
+                    vector<int> pidArr ;
+                    vector<int> capArr ;
+                    vector<int> lyrArr ;
+                    for(int it=0;it<ibig;++it) { pidArr.push_back(bigPlate->_id);capArr.push_back(bigPlate->_bigJewCap*4); lyrArr.push_back((rand()%2==0)?2:3); }
+                    for(int it=0;it<imid;++it) { pidArr.push_back(midPlate->_id);capArr.push_back(midPlate->_bigJewCap*4); lyrArr.push_back((rand()%2==0)?2:3); }
+                    for(int it=0;it<isml;++it) { pidArr.push_back(smlPlate->_id);capArr.push_back(smlPlate->_bigJewCap*4); lyrArr.push_back((rand()%2==0)?2:3); }
+                    bool lyrOk = isJustFilledAll2(capArr, lyrArr, totEquvSmallJewelCnt) ;
+                    if(!lyrOk) continue;
+                    //保存结果
+                    vector<LevelGenerate::PlateIdAndLayerCnt> tres(capArr.size()) ;
+                    for(int it = 0 ; it < capArr.size(); ++ it ) {
+                        tres[it]._plateId = pidArr[it] ;
+                        tres[it]._layerCnt = lyrArr[it] ;
+                    }
+                    possibleResults.push_back(tres) ;
+                }
+                
+            }
+        }
+    }
+    //随机挑选一个结果返回
     if( possibleResults.size()> 0 ) {
         int randindex = rand() % possibleResults.size();
         results = possibleResults[randindex] ;
+    }else {
+        results = firstAvailablePlateComb ;
     }
     return results ;
+    
 }
 
 
@@ -392,7 +629,7 @@ vector<FillResult> LevelGenerate::fillPlateOneLayer(RandomGridFiller& filler,con
         eqSmStorageCnt+=it->second*jewEqSmCnt ;
     }
     static int s_debug_cnt = 0;
-    cout<<"debug "<< s_debug_cnt << " storage eqsm count "<<eqSmStorageCnt<< " currplate cap "<< plateEqSmCap <<endl;
+    //cout<<"debug "<< s_debug_cnt << " storage eqsm count "<<eqSmStorageCnt<< " currplate cap "<< plateEqSmCap <<endl;
     unordered_map<int, int> jewForPlateMap ;// <jewId,Cnt>
     int loopCnt = plateEqSmCap*jewTypeCnt*4;// 随机数量多给4倍
     while(plateEqSmCap>0 && eqSmStorageCnt>0 && loopCnt>0 ) {
@@ -418,7 +655,7 @@ vector<FillResult> LevelGenerate::fillPlateOneLayer(RandomGridFiller& filler,con
         --loopCnt ;
     }
     
-    cout<<"debug "<< s_debug_cnt ++ <<" storage eqsm count "<<eqSmStorageCnt<< " currplate cap "<< plateEqSmCap <<endl;
+    //cout<<"debug "<< s_debug_cnt ++ <<" storage eqsm count "<<eqSmStorageCnt<< " currplate cap "<< plateEqSmCap <<endl;
     
     for(auto it = jewForPlateMap.begin();it!=jewForPlateMap.end();++it) {
         RandomGridFiller::JewelBox jbox ;
@@ -456,7 +693,7 @@ void LevelGenerate::regroupPlateLyrFillResults( vector<tuple<int,int,vector<Fill
     int num = pidlyrFillArray.size() ;
     int nloop = num ;
     while( nloop > 0 ) {
-        cout<<"regroup for layer "<<currLayer<<endl;
+        //cout<<"regroup for layer "<<currLayer<<endl;
         for( auto itlyr = pidlyrFillArray.begin(); itlyr!=pidlyrFillArray.end(); ++ itlyr ) {
             int pid = std::get<0>( *itlyr ) ;
             int lyr = std::get<1>( *itlyr ) ;
@@ -487,27 +724,17 @@ void LevelGenerate::regroupPlateLyrFillResults( vector<tuple<int,int,vector<Fill
         if( num==0 ) break ;
         nloop--;
     }
-    cout<<"last plate lyr num "<<num<<endl;
+    //cout<<"last plate lyr num "<<num<<endl;
 }
 
 
 
-void LevelGenerate::placePlates( const vector<int>& plateIdArr, vector<ContourData::Point>& resultPlateCenterPointArr )
+void LevelGenerate::placePlates(const bool movable, const vector<int>& plateIdArr, vector<ContourData::Point>& resultPlateCenterPointArr )
 {
-    auto fgc = FillGlobalConfig::getInstance() ;
-    vector<vector<int>> plateSizeArr ;
-    for(int ip = 0 ; ip < plateIdArr.size();++ip ) {
-        FillGlobalConfig::PlateItem* plate1 = fgc->getPlateItemById(plateIdArr[ip]) ;
-        vector<int> sz = { (int)plate1->_bbwid, (int)plate1->_bbhei } ;
-        plateSizeArr.push_back(sz) ;
-    }
-    BoxPositionTool positionTool ;
+    GridPositionTool gpt ;
     vector<vector<int>> resPosis ;
-    const int GAME_REGION_WIDTH = 640 ;
-    const int GAME_REGION_HEIGHT = 720 ;
-    positionTool.solve(GAME_REGION_WIDTH,GAME_REGION_HEIGHT,plateSizeArr, resPosis) ;
+    gpt.solve(movable, plateIdArr, resPosis) ;
     for(int ip=0;ip<resPosis.size();++ip ) {
         resultPlateCenterPointArr.push_back( { (float)resPosis[ip][0] , (float)resPosis[ip][1]} ) ;
     }
 }
-

+ 12 - 4
auto_fill_jewel_v3/LevelGenerate.hpp

@@ -1,9 +1,9 @@
 //
 //  LevelGenerate.hpp
-//  Test
-//
-//  Created by 高慕白 on 2024/12/3.
+//  auto_fill_jewel_v3
 //
+//  Created by Red on 2024/11/23.
+// 关卡生成类,包含生成盘子组合,然后填充宝石。
 
 #ifndef LevelGenerate_hpp
 #define LevelGenerate_hpp
@@ -35,6 +35,7 @@ public:
     
     //盘子和宝石的组合有解返回true,反之返回false
     bool generate( FillGlobalConfig::LevelData levelData,
+                  const bool isMovable,
                   vector<tuple<int,vector<vector<FillResult>>>>& resultPlateFillResults,
                   vector<ContourData::Point>& resultPlateCenterPointArr
                   ) ;
@@ -43,7 +44,14 @@ private:
     int _seed ;
     
     //根据难度数据和宝石总数计算需要使用的每个盘子的个数
+    /// @param difficulty 难度:0-普通,1-难,2-极难
+    /// @param totEquvSmallJewelCnt 全部等效小盘子数量
+    /// @return 返回盘子和层数的组合
     vector<PlateIdAndLayerCnt> generatePlateTypeAndLayerCnts(const int difficulty,const int totEquvSmallJewelCnt);
+    
+    //考虑可移动关卡的条件
+    vector<PlateIdAndLayerCnt> generatePlateTypeAndLayerCnts2(const bool isMovable,const int difficulty,const int totEquvSmallJewelCnt);
+    
    
     
     //判断一个组合是否恰恰装入宝石个数,规则是如果少一个盘就恰好装不下.
@@ -75,7 +83,7 @@ private:
     void regroupPlateLyrFillResults( vector<tuple<int,int,vector<FillResult>>>& pidlyrFillArray, vector<tuple<int,vector<vector<FillResult>>>>& results ) ;
     
     /// 摆放盘子
-    void placePlates( const vector<int>& plateIdArr, vector<ContourData::Point>& resultPlateCenterPointArr ) ;
+    void placePlates(const bool movable, const vector<int>& plateIdArr, vector<ContourData::Point>& resultPlateCenterPointArr ) ;
     
 } ;
 

+ 100 - 0
auto_fill_jewel_v3/LevelThumbTool.cpp

@@ -0,0 +1,100 @@
+//
+//  LevelThumbTool.cpp
+//  auto_fill_jewel_v3
+//
+//  Created by Red on 2024/12/4.
+//
+
+#include "LevelThumbTool.hpp"
+#include <iostream>
+#include <sstream>
+using std::stringstream;
+using std::cout;
+using std::endl;
+
+void LevelThumbTool::makeThumb(string outpngname, vector<int>& plateIdArr,vector<ContourData::Point>& platePosiArr )
+{
+    int visWid = 640 ;
+    int visHei = 720 ;
+    int canvasWid = visWid+270 ;
+    int canvasHei = visHei+100 ;
+    
+    int x0 = 20 ;
+    int y0 = 50 ;
+    
+    cv::Mat levelimage = cv::Mat::ones( canvasHei, canvasWid , CV_8UC3 );
+    cv::Point p0(x0 ,y0 ) ;
+    cv::Point p1(x0+visWid,y0+visHei) ;
+    cv::rectangle(levelimage, p0, p1, cv::Scalar(191,191,191),2,cv::LINE_8) ;
+    for(int ip = 0 ; ip < plateIdArr.size(); ++ ip ) {
+        FillGlobalConfig::PlateItem* plate = FillGlobalConfig::getInstance()->getPlateItemById(plateIdArr[ip]) ;
+        string pngName = string("./轮廓/sg_盘子轮廓/")+ plate->_pngName ;
+        cv::Mat png = cv::imread(pngName) ;
+        
+        int x = platePosiArr[ip].x ;
+        int y = platePosiArr[ip].y ;
+        y = 720 - y ; //左下坐标转左上坐标
+        x+=x0;
+        y+=y0;
+        int wid = plate->_bbwid ;
+        int hei = plate->_bbhei ;
+        x = x-wid/2 ;
+        y = y-hei/2 ;
+        //cout<<x<<","<<y<<","<<wid<<","<<hei <<" canvas " << levelimage.rows <<"," << levelimage.cols <<endl;
+        png.copyTo( levelimage(cv::Rect(x,y,wid,hei)) ) ;
+    }
+    cv::imwrite(outpngname, levelimage) ;
+}
+
+void LevelThumbTool::drawthumb(
+                               string outname ,
+                               vector<tuple<int,vector<vector<FillResult>>>>& fillResultsArr,
+                               vector<ContourData::Point>& posiArr ) {
+    vector<int> plateIdArr ;
+    for(int ip = 0 ; ip < fillResultsArr.size();++ip ) {
+        plateIdArr.push_back( std::get<0>(fillResultsArr[ip]) ) ;
+    }
+    makeThumb(outname, plateIdArr, posiArr) ;
+}
+
+
+void LevelThumbTool::makeThumb2(string outpngname, vector<tuple<int,vector<vector<FillResult>>>>& fillResultsArr,vector<ContourData::Point>& platePosiArr )
+{
+    int visWid = 640 ;
+    int visHei = 720 ;
+    int canvasWid = visWid+270 ;
+    int canvasHei = visHei+100 ;
+    
+    int x0 = 20 ;
+    int y0 = 50 ;
+    
+    cv::Mat levelimage = cv::Mat::ones( canvasHei, canvasWid , CV_8UC3 );
+    cv::Point p0(x0 ,y0 ) ;
+    cv::Point p1(x0+visWid,y0+visHei) ;
+    cv::rectangle(levelimage, p0, p1, cv::Scalar(191,191,191),2,cv::LINE_8) ;
+    for(int ip = 0 ; ip < fillResultsArr.size(); ++ ip ) {
+        int plateTypeId = std::get<0>( fillResultsArr[ip] ) ;
+        int nlyr = (int) std::get<1>( fillResultsArr[ip] ).size() ;
+        
+        FillGlobalConfig::PlateItem* plate = FillGlobalConfig::getInstance()->getPlateItemById(plateTypeId) ;
+        string pngName = string("./轮廓/sg_盘子轮廓/")+ plate->_pngName ;
+        cv::Mat png = cv::imread(pngName) ;
+        
+        int x = platePosiArr[ip].x ;
+        int y = platePosiArr[ip].y ;
+        y = 720 - y ; //左下坐标转左上坐标
+        x+=x0;
+        y+=y0;
+        int wid = plate->_bbwid ;
+        int hei = plate->_bbhei ;
+        x = x-wid/2 ;
+        y = y-hei/2 ;
+        //cout<<x<<","<<y<<","<<wid<<","<<hei <<" canvas " << levelimage.rows <<"," << levelimage.cols <<endl;
+        png.copyTo( levelimage(cv::Rect(x,y,wid,hei)) ) ;
+        //写入层数
+        stringstream ss ;
+        ss<<" x"<<nlyr ;
+        cv::putText(levelimage, ss.str(), cv::Point(x+5,y+25), cv::FONT_HERSHEY_PLAIN, 1, cv::Scalar(103,101,238)) ;
+    }
+    cv::imwrite(outpngname, levelimage) ;
+}

+ 36 - 0
auto_fill_jewel_v3/LevelThumbTool.hpp

@@ -0,0 +1,36 @@
+//
+//  LevelThumbTool.hpp
+//  auto_fill_jewel_v3
+//
+//  Created by Red on 2024/12/4.
+// 生成关卡盘子摆放的缩略图
+
+#ifndef LevelThumbTool_hpp
+#define LevelThumbTool_hpp
+
+#include <stdio.h>
+#include <vector>
+using std::vector;
+#include "contourdata.h"
+#include "FillGlobalConfig.hpp"
+#include <opencv2/core.hpp>
+#include <opencv2/imgproc.hpp>
+#include <opencv2/highgui.hpp>
+#include "FillResult.hpp"
+#include <tuple>
+using std::tuple;
+
+struct LevelThumbTool {
+    
+    void drawthumb(
+                   string outname , // 输出png文件名
+                   vector<tuple<int,vector<vector<FillResult>>>>& fillResultsArr, // tuple<盘子类型ID, <层数组<一层填充结果>>>
+                   vector<ContourData::Point>& posiArr  //盘子中心坐标位置数组,坐标系原点在游戏区域左下角
+                   ) ;
+    //deprecated, use makeThumb2
+    void makeThumb(string outpngname, vector<int>& plateIdArr,vector<ContourData::Point>& platePosiArr ) ;
+    void makeThumb2(string outpngname, vector<tuple<int,vector<vector<FillResult>>>>& fillResultsArr,vector<ContourData::Point>& platePosiArr ) ;
+} ;
+
+
+#endif /* LevelThumbTool_hpp */

+ 22 - 13
auto_fill_jewel_v3/RandomGridFiller.cpp

@@ -1,8 +1,8 @@
 //
 //  RandomGridFiller.cpp
-//  Test
+//  auto_fill_jewel_v3
 //
-//  Created by 高慕白 on 2024/12/3.
+//  Created by Red on 2024/11/26.
 //
 
 #include "RandomGridFiller.hpp"
@@ -18,7 +18,6 @@ void RandomGridFiller::fill( vector<JewelBox>& jewelsArray ,    //需要填充
           vector<FillResult>& results         //填充结果
           )
 {
-    srand(_seed) ;
     results.clear() ;
     
     if( jewelsArray.size()==0 ) return ;
@@ -54,11 +53,12 @@ void RandomGridFiller::fill( vector<JewelBox>& jewelsArray ,    //需要填充
     int chosenTotCoverage = 9999999 ;
     float chosenTotDist = 999999 ;
     for(int itry = 0 ;itry < TRY_CNT; ++ itry ) {
+        //cout<<"debug fill randomFillOneRound "<<itry<<endl;
         vector<FillResult> fillResults ;
         float jewDistSum=0;
         int cov = 0 ;
         int badcnt = randomFillOneRound(jewelsArray, bottomLeftX, bottomLeftY, topRightX, topRightY, potPositions, fillResults, jewDistSum,gridWid,gridHei,cov);
-        cout<<"itry-"<<itry<<" badcnt "<<badcnt<<" jewDistSum "<<jewDistSum<<" cov "<<cov <<endl;
+        //cout<<"itry-"<<itry<<" badcnt "<<badcnt<<" jewDistSum "<<jewDistSum<<" cov "<<cov <<endl;
         if( chosen==false ) {
             chosen = true ;
             chosenTotCoverage = cov ;
@@ -106,7 +106,7 @@ int RandomGridFiller::randomFillOneRound( vector<JewelBox>& jewelsArray,
     
     //点位从外到内
     vector<DistancePosition> distPositions ;
-    generateDistanceRandomSlotPositions((topRightX-bottomLeftX)/2, (topRightY-bottomLeftY)/2, potPositions, distPositions) ;
+    generateDistanceRandomSlotPositions(bottomLeftX+(topRightX-bottomLeftX)/2, bottomLeftY+(topRightY-bottomLeftY)/2, potPositions, distPositions) ;
     
     //使用网格描述盘子点位占用情况
     const int GRID_X_NUM = (topRightX - bottomLeftX+1)/gridWid ;
@@ -132,8 +132,11 @@ int RandomGridFiller::randomFillOneRound( vector<JewelBox>& jewelsArray,
                                                                                       jewBox._width,
                                                                                       jewBox._height ,
                                                                                       rotdeg) ;
+        float plateCenterX = bottomLeftX+(topRightX-bottomLeftX)/2 ;
+        float plateCenterY = bottomLeftY+(topRightY-bottomLeftY)/2 ;
         vector<FillResult> tempFillResults ;
         vector<int> sumOfTakenCountArray ;//保留占位数求和
+        vector<float> dist2CenterArray ;
         const int NUM_TRY = distPositions.size() ;//最大尝试数量
         int itry = 0 ;//尝试数量
         //将潜在点位按照距离盘子中心的距离进行排序,宝石按照这个距离进行填入盘子
@@ -146,6 +149,7 @@ int RandomGridFiller::randomFillOneRound( vector<JewelBox>& jewelsArray,
             //是否落在盘子内部
             bool isJewWithinPlate = BoostGeometryTools::isAWithinB(jpoly2, plateBox) ;
             if( isJewWithinPlate ) {
+                float dist1 = sqrt( (posi.x-plateCenterX)*(posi.x-plateCenterX)+(posi.y-plateCenterY)*(posi.y-plateCenterY) );
                 itry ++ ;
                 FillResult fr ;
                 fr._jewelTypeId = jewBox._jewelTypeId ;
@@ -158,6 +162,7 @@ int RandomGridFiller::randomFillOneRound( vector<JewelBox>& jewelsArray,
                 int tsum = totalCoverageGridCount(jpoly2, jewelGridDataArray) ;
                 //cout<<"ij-"<<ij<<" itry-"<<itry<<" tsum "<<tsum<<endl;
                 sumOfTakenCountArray.push_back(tsum) ;
+                dist2CenterArray.push_back(dist1);
                 positionIndices.push_back(ip); //positionIndices.push_back(ip) ;
                 if( itry == NUM_TRY ) break ;
             }
@@ -168,11 +173,18 @@ int RandomGridFiller::randomFillOneRound( vector<JewelBox>& jewelsArray,
             FillResult fr = tempFillResults[0] ;
             int tsum = sumOfTakenCountArray[0] ;
             int posiIndex =positionIndices[0] ;
-            for(int itry = 1 ; itry < tempFillResults.size();++ itry ) {
-                if( tsum > sumOfTakenCountArray[itry]) {
-                    tsum = sumOfTakenCountArray[itry] ;
-                    fr = tempFillResults[itry] ;
-                    posiIndex =positionIndices[itry] ;
+            float tdist = dist2CenterArray[0] ;
+            for(int itry1 = 0 ; itry1 < tempFillResults.size();++ itry1 ) {
+                if( tsum > sumOfTakenCountArray[itry1]) {
+                    tsum = sumOfTakenCountArray[itry1] ;
+                    tdist = dist2CenterArray[itry1];
+                    fr = tempFillResults[itry1] ;
+                    posiIndex =positionIndices[itry1] ;
+                }else if( tsum == sumOfTakenCountArray[itry1] && tdist < dist2CenterArray[itry1] ) {
+                    tsum = sumOfTakenCountArray[itry1] ;
+                    tdist = dist2CenterArray[itry1];
+                    fr = tempFillResults[itry1] ;
+                    posiIndex =positionIndices[itry1] ;
                 }
             }
             distPositions[posiIndex]._isTaken = true ;//记录位置已经占用
@@ -181,8 +193,6 @@ int RandomGridFiller::randomFillOneRound( vector<JewelBox>& jewelsArray,
             setGridCoverageByBox(jpoly2, jewelGridDataArray[randJewIndex]);//写入子网格数据
             results.push_back(fr);//记录最优结果
             resultsJewIndices.push_back(randJewIndex) ;
-            //debug
-            //cout<<"ij-"<<ij<<" pos "<<fr._x<<","<<fr._y<< " cov " << tsum <<endl;
         }
     }
     
@@ -348,4 +358,3 @@ void RandomGridFiller::generateDistanceRandomSlotPositions(const float centerx,c
     //排序
     std::sort(results.begin(), results.end(), [](DistancePosition& p1,DistancePosition& p2)->bool{return p1._distance>p2._distance; } );
 }
-

+ 10 - 3
auto_fill_jewel_v3/RandomGridFiller.hpp

@@ -1,9 +1,13 @@
 //
 //  RandomGridFiller.hpp
-//  Test
-//
-//  Created by 高慕白 on 2024/12/3.
+//  auto_fill_jewel_v3
 //
+//  Created by Red on 2024/11/26.
+// 随机矩形填充
+// 1. 每个宝石使用其外接矩形。
+// 2. 采用随机点位填充到盘子中,不考虑宝石之间重叠率问题,但是宝石必须要在盘子内部。
+// 3. 尝试5次,最后选择一组重叠率最小的。
+// 4. 要求效率尽量高,2帧完成一关宝石填充(?)
 
 #ifndef RandomGridFiller_hpp
 #define RandomGridFiller_hpp
@@ -93,4 +97,7 @@ private:
     void generateDistanceRandomSlotPositions(const float centerx,const float centery,const vector<ContourData::Point>& points, vector<DistancePosition>& results ) ;
 } ;
 
+
+
+
 #endif /* RandomGridFiller_hpp */

+ 2 - 3
auto_fill_jewel_v3/SpriteData.hpp

@@ -1,8 +1,8 @@
 //
 //  SpriteData.hpp
-//  Test
+//  auto_fill_jewel_tight
 //
-//  Created by 高慕白 on 2024/12/3.
+//  Created by Red on 2024/11/13.
 //
 
 #ifndef SpriteData_hpp
@@ -23,5 +23,4 @@ struct SpriteData {
     float _csx,_csy;//contour scale x,y
 };
 
-
 #endif /* SpriteData_hpp */

+ 3429 - 0
auto_fill_jewel_v3/ajson5.hpp

@@ -0,0 +1,3429 @@
+// ArduinoJson - arduinojson.org
+// Copyright Benoit Blanchon 2014-2019
+// MIT License
+
+#pragma once
+
+#ifdef __cplusplus
+
+#define ARDUINOJSON_VERSION "5.13.5"
+#define ARDUINOJSON_VERSION_MAJOR 5
+#define ARDUINOJSON_VERSION_MINOR 13
+#define ARDUINOJSON_VERSION_REVISION 5
+#include <stddef.h>  // for size_t
+#include <stdint.h>  // for uint8_t
+#include <string.h>
+namespace ArduinoJson {
+namespace Internals {
+class NonCopyable {
+ protected:
+  NonCopyable() {}
+ private:
+  NonCopyable(const NonCopyable&);
+  NonCopyable& operator=(const NonCopyable&);
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+#ifndef ARDUINOJSON_EMBEDDED_MODE
+#if defined(ARDUINO) || defined(__IAR_SYSTEMS_ICC__) || defined(__XC) || \
+    defined(__ARMCC_VERSION)
+#define ARDUINOJSON_EMBEDDED_MODE 1
+#else
+#define ARDUINOJSON_EMBEDDED_MODE 0
+#endif
+#endif
+#if ARDUINOJSON_EMBEDDED_MODE
+#ifndef ARDUINOJSON_USE_DOUBLE
+#define ARDUINOJSON_USE_DOUBLE 0
+#endif
+#ifndef ARDUINOJSON_USE_LONG_LONG
+#define ARDUINOJSON_USE_LONG_LONG 0
+#endif
+#ifndef ARDUINOJSON_USE_INT64
+#define ARDUINOJSON_USE_INT64 0
+#endif
+#ifndef ARDUINOJSON_ENABLE_STD_STRING
+#define ARDUINOJSON_ENABLE_STD_STRING 0
+#endif
+#ifndef ARDUINOJSON_ENABLE_STD_STREAM
+#define ARDUINOJSON_ENABLE_STD_STREAM 0
+#endif
+#ifndef ARDUINOJSON_DEFAULT_NESTING_LIMIT
+#define ARDUINOJSON_DEFAULT_NESTING_LIMIT 10
+#endif
+#else  // ARDUINOJSON_EMBEDDED_MODE
+#ifndef ARDUINOJSON_USE_DOUBLE
+#define ARDUINOJSON_USE_DOUBLE 1
+#endif
+#ifndef ARDUINOJSON_USE_LONG_LONG
+#if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1800)
+#define ARDUINOJSON_USE_LONG_LONG 1
+#else
+#define ARDUINOJSON_USE_LONG_LONG 0
+#endif
+#endif
+#ifndef ARDUINOJSON_USE_INT64
+#if defined(_MSC_VER) && _MSC_VER <= 1700
+#define ARDUINOJSON_USE_INT64 1
+#else
+#define ARDUINOJSON_USE_INT64 0
+#endif
+#endif
+#ifndef ARDUINOJSON_ENABLE_STD_STRING
+#define ARDUINOJSON_ENABLE_STD_STRING 1
+#endif
+#ifndef ARDUINOJSON_ENABLE_STD_STREAM
+#define ARDUINOJSON_ENABLE_STD_STREAM 1
+#endif
+#ifndef ARDUINOJSON_DEFAULT_NESTING_LIMIT
+#define ARDUINOJSON_DEFAULT_NESTING_LIMIT 50
+#endif
+#endif  // ARDUINOJSON_EMBEDDED_MODE
+#ifdef ARDUINO
+#ifndef ARDUINOJSON_ENABLE_ARDUINO_STRING
+#define ARDUINOJSON_ENABLE_ARDUINO_STRING 1
+#endif
+#ifndef ARDUINOJSON_ENABLE_ARDUINO_STREAM
+#define ARDUINOJSON_ENABLE_ARDUINO_STREAM 1
+#endif
+#else  // ARDUINO
+#ifndef ARDUINOJSON_ENABLE_ARDUINO_STRING
+#define ARDUINOJSON_ENABLE_ARDUINO_STRING 0
+#endif
+#ifndef ARDUINOJSON_ENABLE_ARDUINO_STREAM
+#define ARDUINOJSON_ENABLE_ARDUINO_STREAM 0
+#endif
+#endif  // ARDUINO
+#ifndef ARDUINOJSON_ENABLE_PROGMEM
+#ifdef PROGMEM
+#define ARDUINOJSON_ENABLE_PROGMEM 1
+#else
+#define ARDUINOJSON_ENABLE_PROGMEM 0
+#endif
+#endif
+#ifndef ARDUINOJSON_ENABLE_ALIGNMENT
+#ifdef ARDUINO_ARCH_AVR
+#define ARDUINOJSON_ENABLE_ALIGNMENT 0
+#else
+#define ARDUINOJSON_ENABLE_ALIGNMENT 1
+#endif
+#endif
+#ifndef ARDUINOJSON_ENABLE_DEPRECATED
+#define ARDUINOJSON_ENABLE_DEPRECATED 1
+#endif
+#ifndef ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD
+#define ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD 1e7
+#endif
+#ifndef ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD
+#define ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD 1e-5
+#endif
+#if ARDUINOJSON_USE_LONG_LONG && ARDUINOJSON_USE_INT64
+#error ARDUINOJSON_USE_LONG_LONG and ARDUINOJSON_USE_INT64 cannot be set together
+#endif
+namespace ArduinoJson {
+namespace Internals {
+#if ARDUINOJSON_USE_DOUBLE
+typedef double JsonFloat;
+#else
+typedef float JsonFloat;
+#endif
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+#if ARDUINOJSON_USE_LONG_LONG
+typedef long long JsonInteger;
+typedef unsigned long long JsonUInt;
+#elif ARDUINOJSON_USE_INT64
+typedef __int64 JsonInteger;
+typedef unsigned _int64 JsonUInt;
+#else
+typedef long JsonInteger;
+typedef unsigned long JsonUInt;
+#endif
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+class JsonArray;
+class JsonObject;
+namespace Internals {
+union JsonVariantContent {
+  JsonFloat asFloat;     // used for double and float
+  JsonUInt asInteger;    // used for bool, char, short, int and longs
+  const char* asString;  // asString can be null
+  JsonArray* asArray;    // asArray cannot be null
+  JsonObject* asObject;  // asObject cannot be null
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+struct JsonVariantDefault {
+  static T get() {
+    return T();
+  }
+};
+template <typename T>
+struct JsonVariantDefault<const T> : JsonVariantDefault<T> {};
+template <typename T>
+struct JsonVariantDefault<T&> : JsonVariantDefault<T> {};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+class JsonArray;
+class JsonObject;
+namespace Internals {
+enum JsonVariantType {
+  JSON_UNDEFINED,         // JsonVariant has not been initialized
+  JSON_UNPARSED,          // JsonVariant contains an unparsed string
+  JSON_STRING,            // JsonVariant stores a const char*
+  JSON_BOOLEAN,           // JsonVariant stores a bool
+  JSON_POSITIVE_INTEGER,  // JsonVariant stores an JsonUInt
+  JSON_NEGATIVE_INTEGER,  // JsonVariant stores an JsonUInt that must be negated
+  JSON_ARRAY,             // JsonVariant stores a pointer to a JsonArray
+  JSON_OBJECT,            // JsonVariant stores a pointer to a JsonObject
+  JSON_FLOAT              // JsonVariant stores a JsonFloat
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+struct JsonVariantAs {
+  typedef T type;
+};
+template <>
+struct JsonVariantAs<char*> {
+  typedef const char* type;
+};
+template <>
+struct JsonVariantAs<JsonArray> {
+  typedef JsonArray& type;
+};
+template <>
+struct JsonVariantAs<const JsonArray> {
+  typedef const JsonArray& type;
+};
+template <>
+struct JsonVariantAs<JsonObject> {
+  typedef JsonObject& type;
+};
+template <>
+struct JsonVariantAs<const JsonObject> {
+  typedef const JsonObject& type;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+#ifdef _MSC_VER  // Visual Studio
+#define FORCE_INLINE  // __forceinline causes C4714 when returning std::string
+#define NO_INLINE __declspec(noinline)
+#define DEPRECATED(msg) __declspec(deprecated(msg))
+#elif defined(__GNUC__)  // GCC or Clang
+#define FORCE_INLINE __attribute__((always_inline))
+#define NO_INLINE __attribute__((noinline))
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
+#define DEPRECATED(msg) __attribute__((deprecated(msg)))
+#else
+#define DEPRECATED(msg) __attribute__((deprecated))
+#endif
+#else  // Other compilers
+#define FORCE_INLINE
+#define NO_INLINE
+#define DEPRECATED(msg)
+#endif
+namespace ArduinoJson {
+namespace Internals {
+template <typename TImpl>
+class JsonVariantCasts {
+ public:
+#if ARDUINOJSON_ENABLE_DEPRECATED
+  DEPRECATED("use as<JsonArray>() instead")
+  FORCE_INLINE JsonArray &asArray() const {
+    return impl()->template as<JsonArray>();
+  }
+  DEPRECATED("use as<JsonObject>() instead")
+  FORCE_INLINE JsonObject &asObject() const {
+    return impl()->template as<JsonObject>();
+  }
+  DEPRECATED("use as<char*>() instead")
+  FORCE_INLINE const char *asString() const {
+    return impl()->template as<const char *>();
+  }
+#endif
+  FORCE_INLINE operator JsonArray &() const {
+    return impl()->template as<JsonArray &>();
+  }
+  FORCE_INLINE operator JsonObject &() const {
+    return impl()->template as<JsonObject &>();
+  }
+  template <typename T>
+  FORCE_INLINE operator T() const {
+    return impl()->template as<T>();
+  }
+ private:
+  const TImpl *impl() const {
+    return static_cast<const TImpl *>(this);
+  }
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <bool Condition, typename T = void>
+struct EnableIf {};
+template <typename T>
+struct EnableIf<true, T> {
+  typedef T type;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename TBase, typename TDerived>
+class IsBaseOf {
+ protected:  // <- to avoid GCC's "all member functions in class are private"
+  typedef char Yes[1];
+  typedef char No[2];
+  static Yes &probe(const TBase *);
+  static No &probe(...);
+ public:
+  enum {
+    value = sizeof(probe(reinterpret_cast<TDerived *>(0))) == sizeof(Yes)
+  };
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T, typename U>
+struct IsSame {
+  static const bool value = false;
+};
+template <typename T>
+struct IsSame<T, T> {
+  static const bool value = true;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+struct IsChar {
+  static const bool value = IsSame<T, char>::value ||
+                            IsSame<T, signed char>::value ||
+                            IsSame<T, unsigned char>::value;
+};
+template <typename T>
+struct IsChar<const T> : IsChar<T> {};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+struct IsConst {
+  static const bool value = false;
+};
+template <typename T>
+struct IsConst<const T> {
+  static const bool value = true;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+struct RemoveReference {
+  typedef T type;
+};
+template <typename T>
+struct RemoveReference<T&> {
+  typedef T type;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename TString, typename Enable = void>
+struct StringTraits {
+  static const bool has_append = false;
+  static const bool has_equals = false;
+};
+template <typename TString>
+struct StringTraits<const TString, void> : StringTraits<TString> {};
+template <typename TString>
+struct StringTraits<TString&, void> : StringTraits<TString> {};
+}  // namespace Internals
+}  // namespace ArduinoJson
+#if ARDUINOJSON_ENABLE_ARDUINO_STREAM
+#include <Stream.h>
+namespace ArduinoJson {
+namespace Internals {
+struct ArduinoStreamTraits {
+  class Reader {
+    Stream& _stream;
+    char _current, _next;
+   public:
+    Reader(Stream& stream) : _stream(stream), _current(0), _next(0) {}
+    void move() {
+      _current = _next;
+      _next = 0;
+    }
+    char current() {
+      if (!_current) _current = read();
+      return _current;
+    }
+    char next() {
+      if (!_next) _next = read();
+      return _next;
+    }
+   private:
+    char read() {
+      char c = 0;
+      _stream.readBytes(&c, 1);
+      return c;
+    }
+  };
+  static const bool has_append = false;
+  static const bool has_equals = false;
+};
+template <typename TStream>
+struct StringTraits<
+    TStream,
+    typename EnableIf<
+        IsBaseOf<Stream, typename RemoveReference<TStream>::type>::value>::type>
+    : ArduinoStreamTraits {};
+}  // namespace Internals
+}  // namespace ArduinoJson
+#endif
+namespace ArduinoJson {
+namespace Internals {
+template <typename TChar>
+struct CharPointerTraits {
+  class Reader {
+    const TChar* _ptr;
+   public:
+    Reader(const TChar* ptr)
+        : _ptr(ptr ? ptr : reinterpret_cast<const TChar*>("")) {}
+    void move() {
+      ++_ptr;
+    }
+    char current() const {
+      return char(_ptr[0]);
+    }
+    char next() const {
+      return char(_ptr[1]);
+    }
+  };
+  static bool equals(const TChar* str, const char* expected) {
+    const char* actual = reinterpret_cast<const char*>(str);
+    if (!actual || !expected) return actual == expected;
+    return strcmp(actual, expected) == 0;
+  }
+  static bool is_null(const TChar* str) {
+    return !str;
+  }
+  typedef const char* duplicate_t;
+  template <typename Buffer>
+  static duplicate_t duplicate(const TChar* str, Buffer* buffer) {
+    if (!str) return NULL;
+    size_t size = strlen(reinterpret_cast<const char*>(str)) + 1;
+    void* dup = buffer->alloc(size);
+    if (dup != NULL) memcpy(dup, str, size);
+    return static_cast<duplicate_t>(dup);
+  }
+  static const bool has_append = false;
+  static const bool has_equals = true;
+  static const bool should_duplicate = !IsConst<TChar>::value;
+};
+template <typename TChar>
+struct StringTraits<TChar*, typename EnableIf<IsChar<TChar>::value>::type>
+    : CharPointerTraits<TChar> {};
+}  // namespace Internals
+}  // namespace ArduinoJson
+#if ARDUINOJSON_ENABLE_PROGMEM
+namespace ArduinoJson {
+namespace Internals {
+template <>
+struct StringTraits<const __FlashStringHelper*, void> {
+  class Reader {
+    const char* _ptr;
+   public:
+    Reader(const __FlashStringHelper* ptr)
+        : _ptr(reinterpret_cast<const char*>(ptr)) {}
+    void move() {
+      _ptr++;
+    }
+    char current() const {
+      return pgm_read_byte_near(_ptr);
+    }
+    char next() const {
+      return pgm_read_byte_near(_ptr + 1);
+    }
+  };
+  static bool equals(const __FlashStringHelper* str, const char* expected) {
+    const char* actual = reinterpret_cast<const char*>(str);
+    if (!actual || !expected) return actual == expected;
+    return strcmp_P(expected, actual) == 0;
+  }
+  static bool is_null(const __FlashStringHelper* str) {
+    return !str;
+  }
+  typedef const char* duplicate_t;
+  template <typename Buffer>
+  static duplicate_t duplicate(const __FlashStringHelper* str, Buffer* buffer) {
+    if (!str) return NULL;
+    size_t size = strlen_P((const char*)str) + 1;
+    void* dup = buffer->alloc(size);
+    if (dup != NULL) memcpy_P(dup, (const char*)str, size);
+    return static_cast<duplicate_t>(dup);
+  }
+  static const bool has_append = false;
+  static const bool has_equals = true;
+  static const bool should_duplicate = true;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+#endif
+#if ARDUINOJSON_ENABLE_STD_STREAM
+#include <istream>
+namespace ArduinoJson {
+namespace Internals {
+struct StdStreamTraits {
+  class Reader {
+    std::istream& _stream;
+    char _current, _next;
+   public:
+    Reader(std::istream& stream) : _stream(stream), _current(0), _next(0) {}
+    void move() {
+      _current = _next;
+      _next = 0;
+    }
+    char current() {
+      if (!_current) _current = read();
+      return _current;
+    }
+    char next() {
+      if (!_next) _next = read();
+      return _next;
+    }
+   private:
+    Reader& operator=(const Reader&);  // Visual Studio C4512
+    char read() {
+      return _stream.eof() ? '\0' : static_cast<char>(_stream.get());
+    }
+  };
+  static const bool has_append = false;
+  static const bool has_equals = false;
+};
+template <typename TStream>
+struct StringTraits<
+    TStream,
+    typename EnableIf<IsBaseOf<
+        std::istream, typename RemoveReference<TStream>::type>::value>::type>
+    : StdStreamTraits {};
+}  // namespace Internals
+}  // namespace ArduinoJson
+#endif
+#if ARDUINOJSON_ENABLE_STD_STRING || ARDUINOJSON_ENABLE_ARDUINO_STRING
+#if ARDUINOJSON_ENABLE_ARDUINO_STRING
+#include <WString.h>
+#endif
+#if ARDUINOJSON_ENABLE_STD_STRING
+#include <string>
+#endif
+namespace ArduinoJson {
+namespace Internals {
+template <typename TString>
+struct StdStringTraits {
+  typedef const char* duplicate_t;
+  template <typename Buffer>
+  static duplicate_t duplicate(const TString& str, Buffer* buffer) {
+    if (!str.c_str()) return NULL;  // <- Arduino string can return NULL
+    size_t size = str.length() + 1;
+    void* dup = buffer->alloc(size);
+    if (dup != NULL) memcpy(dup, str.c_str(), size);
+    return static_cast<duplicate_t>(dup);
+  }
+  static bool is_null(const TString& str) {
+    return !str.c_str();
+  }
+  struct Reader : CharPointerTraits<char>::Reader {
+    Reader(const TString& str) : CharPointerTraits<char>::Reader(str.c_str()) {}
+  };
+  static bool equals(const TString& str, const char* expected) {
+    const char* actual = str.c_str();
+    if (!actual || !expected) return actual == expected;
+    return 0 == strcmp(actual, expected);
+  }
+  static void append(TString& str, char c) {
+    str += c;
+  }
+  static void append(TString& str, const char* s) {
+    str += s;
+  }
+  static const bool has_append = true;
+  static const bool has_equals = true;
+  static const bool should_duplicate = true;
+};
+#if ARDUINOJSON_ENABLE_ARDUINO_STRING
+template <>
+struct StringTraits<String, void> : StdStringTraits<String> {};
+template <>
+struct StringTraits<StringSumHelper, void> : StdStringTraits<StringSumHelper> {
+};
+#endif
+#if ARDUINOJSON_ENABLE_STD_STRING
+template <>
+struct StringTraits<std::string, void> : StdStringTraits<std::string> {};
+#endif
+}  // namespace Internals
+}  // namespace ArduinoJson
+#endif
+namespace ArduinoJson {
+namespace Internals {
+class JsonVariantTag {};
+template <typename T>
+struct IsVariant : IsBaseOf<JsonVariantTag, T> {};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename TImpl>
+class JsonVariantComparisons {
+ public:
+  template <typename TComparand>
+  friend bool operator==(const JsonVariantComparisons &variant,
+                         TComparand comparand) {
+    return variant.equals(comparand);
+  }
+  template <typename TComparand>
+  friend typename EnableIf<!IsVariant<TComparand>::value, bool>::type
+  operator==(TComparand comparand, const JsonVariantComparisons &variant) {
+    return variant.equals(comparand);
+  }
+  template <typename TComparand>
+  friend bool operator!=(const JsonVariantComparisons &variant,
+                         TComparand comparand) {
+    return !variant.equals(comparand);
+  }
+  template <typename TComparand>
+  friend typename EnableIf<!IsVariant<TComparand>::value, bool>::type
+  operator!=(TComparand comparand, const JsonVariantComparisons &variant) {
+    return !variant.equals(comparand);
+  }
+  template <typename TComparand>
+  friend bool operator<=(const JsonVariantComparisons &left, TComparand right) {
+    return left.as<TComparand>() <= right;
+  }
+  template <typename TComparand>
+  friend bool operator<=(TComparand comparand,
+                         const JsonVariantComparisons &variant) {
+    return comparand <= variant.as<TComparand>();
+  }
+  template <typename TComparand>
+  friend bool operator>=(const JsonVariantComparisons &variant,
+                         TComparand comparand) {
+    return variant.as<TComparand>() >= comparand;
+  }
+  template <typename TComparand>
+  friend bool operator>=(TComparand comparand,
+                         const JsonVariantComparisons &variant) {
+    return comparand >= variant.as<TComparand>();
+  }
+  template <typename TComparand>
+  friend bool operator<(const JsonVariantComparisons &varian,
+                        TComparand comparand) {
+    return varian.as<TComparand>() < comparand;
+  }
+  template <typename TComparand>
+  friend bool operator<(TComparand comparand,
+                        const JsonVariantComparisons &variant) {
+    return comparand < variant.as<TComparand>();
+  }
+  template <typename TComparand>
+  friend bool operator>(const JsonVariantComparisons &variant,
+                        TComparand comparand) {
+    return variant.as<TComparand>() > comparand;
+  }
+  template <typename TComparand>
+  friend bool operator>(TComparand comparand,
+                        const JsonVariantComparisons &variant) {
+    return comparand > variant.as<TComparand>();
+  }
+ private:
+  const TImpl *impl() const {
+    return static_cast<const TImpl *>(this);
+  }
+  template <typename T>
+  const typename JsonVariantAs<T>::type as() const {
+    return impl()->template as<T>();
+  }
+  template <typename T>
+  bool is() const {
+    return impl()->template is<T>();
+  }
+  template <typename TString>
+  typename EnableIf<StringTraits<TString>::has_equals, bool>::type equals(
+      const TString &comparand) const {
+    const char *value = as<const char *>();
+    return StringTraits<TString>::equals(comparand, value);
+  }
+  template <typename TComparand>
+  typename EnableIf<!IsVariant<TComparand>::value &&
+                        !StringTraits<TComparand>::has_equals,
+                    bool>::type
+  equals(const TComparand &comparand) const {
+    return as<TComparand>() == comparand;
+  }
+  template <typename TVariant2>
+  bool equals(const JsonVariantComparisons<TVariant2> &right) const {
+    using namespace Internals;
+    if (is<bool>() && right.template is<bool>())
+      return as<bool>() == right.template as<bool>();
+    if (is<JsonInteger>() && right.template is<JsonInteger>())
+      return as<JsonInteger>() == right.template as<JsonInteger>();
+    if (is<JsonFloat>() && right.template is<JsonFloat>())
+      return as<JsonFloat>() == right.template as<JsonFloat>();
+    if (is<JsonArray>() && right.template is<JsonArray>())
+      return as<JsonArray>() == right.template as<JsonArray>();
+    if (is<JsonObject>() && right.template is<JsonObject>())
+      return as<JsonObject>() == right.template as<JsonObject>();
+    if (is<char *>() && right.template is<char *>())
+      return StringTraits<const char *>::equals(as<char *>(),
+                                                right.template as<char *>());
+    return false;
+  }
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+struct IsSignedIntegral {
+  static const bool value =
+      IsSame<T, signed char>::value || IsSame<T, signed short>::value ||
+      IsSame<T, signed int>::value || IsSame<T, signed long>::value ||
+#if ARDUINOJSON_USE_LONG_LONG
+      IsSame<T, signed long long>::value ||
+#endif
+#if ARDUINOJSON_USE_INT64
+      IsSame<T, signed __int64>::value ||
+#endif
+      false;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+struct IsUnsignedIntegral {
+  static const bool value =
+      IsSame<T, unsigned char>::value || IsSame<T, unsigned short>::value ||
+      IsSame<T, unsigned int>::value || IsSame<T, unsigned long>::value ||
+#if ARDUINOJSON_USE_LONG_LONG
+      IsSame<T, unsigned long long>::value ||
+#endif
+#if ARDUINOJSON_USE_INT64
+      IsSame<T, unsigned __int64>::value ||
+#endif
+      false;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+struct IsIntegral {
+  static const bool value = IsSignedIntegral<T>::value ||
+                            IsUnsignedIntegral<T>::value ||
+                            IsSame<T, char>::value;
+};
+template <typename T>
+struct IsIntegral<const T> : IsIntegral<T> {};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename TImpl>
+class JsonVariantOr {
+ public:
+  template <typename T>
+  typename EnableIf<!IsIntegral<T>::value, T>::type operator|(
+      const T &defaultValue) const {
+    if (impl()->template is<T>())
+      return impl()->template as<T>();
+    else
+      return defaultValue;
+  }
+  const char *operator|(const char *defaultValue) const {
+    const char *value = impl()->template as<const char *>();
+    return value ? value : defaultValue;
+  }
+  template <typename Integer>
+  typename EnableIf<IsIntegral<Integer>::value, Integer>::type operator|(
+      const Integer &defaultValue) const {
+    if (impl()->template is<double>())
+      return impl()->template as<Integer>();
+    else
+      return defaultValue;
+  }
+ private:
+  const TImpl *impl() const {
+    return static_cast<const TImpl *>(this);
+  }
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+class JsonArraySubscript;
+template <typename TKey>
+class JsonObjectSubscript;
+template <typename TImpl>
+class JsonVariantSubscripts {
+ public:
+  size_t size() const {
+    return impl()->template as<JsonArray>().size() +
+           impl()->template as<JsonObject>().size();
+  }
+  FORCE_INLINE const JsonArraySubscript operator[](size_t index) const;
+  FORCE_INLINE JsonArraySubscript operator[](size_t index);
+  template <typename TString>
+  FORCE_INLINE
+      typename EnableIf<StringTraits<TString>::has_equals,
+                        const JsonObjectSubscript<const TString &> >::type
+      operator[](const TString &key) const {
+    return impl()->template as<JsonObject>()[key];
+  }
+  template <typename TString>
+  FORCE_INLINE typename EnableIf<StringTraits<TString>::has_equals,
+                                 JsonObjectSubscript<const TString &> >::type
+  operator[](const TString &key) {
+    return impl()->template as<JsonObject>()[key];
+  }
+  template <typename TString>
+  FORCE_INLINE typename EnableIf<StringTraits<const TString *>::has_equals,
+                                 JsonObjectSubscript<const TString *> >::type
+  operator[](const TString *key) {
+    return impl()->template as<JsonObject>()[key];
+  }
+  template <typename TString>
+  FORCE_INLINE
+      typename EnableIf<StringTraits<TString *>::has_equals,
+                        const JsonObjectSubscript<const TString *> >::type
+      operator[](const TString *key) const {
+    return impl()->template as<JsonObject>()[key];
+  }
+ private:
+  const TImpl *impl() const {
+    return static_cast<const TImpl *>(this);
+  }
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+class DummyPrint {
+ public:
+  size_t print(char) {
+    return 1;
+  }
+  size_t print(const char* s) {
+    return strlen(s);
+  }
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename TString>
+class DynamicStringBuilder {
+ public:
+  DynamicStringBuilder(TString &str) : _str(str) {}
+  size_t print(char c) {
+    StringTraits<TString>::append(_str, c);
+    return 1;
+  }
+  size_t print(const char *s) {
+    size_t initialLen = _str.length();
+    StringTraits<TString>::append(_str, s);
+    return _str.length() - initialLen;
+  }
+ private:
+  DynamicStringBuilder &operator=(const DynamicStringBuilder &);
+  TString &_str;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename Print>
+class IndentedPrint {
+ public:
+  explicit IndentedPrint(Print &p) : sink(&p) {
+    level = 0;
+    tabSize = 2;
+    isNewLine = true;
+  }
+  size_t print(char c) {
+    size_t n = 0;
+    if (isNewLine) n += writeTabs();
+    n += sink->print(c);
+    isNewLine = c == '\n';
+    return n;
+  }
+  size_t print(const char *s) {
+    size_t n = 0;
+    while (*s) n += print(*s++);
+    return n;
+  }
+  void indent() {
+    if (level < MAX_LEVEL) level++;
+  }
+  void unindent() {
+    if (level > 0) level--;
+  }
+  void setTabSize(uint8_t n) {
+    if (n < MAX_TAB_SIZE) tabSize = n & MAX_TAB_SIZE;
+  }
+ private:
+  Print *sink;
+  uint8_t level : 4;
+  uint8_t tabSize : 3;
+  bool isNewLine : 1;
+  size_t writeTabs() {
+    size_t n = 0;
+    for (int i = 0; i < level * tabSize; i++) n += sink->print(' ');
+    return n;
+  }
+  static const int MAX_LEVEL = 15;    // because it's only 4 bits
+  static const int MAX_TAB_SIZE = 7;  // because it's only 3 bits
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+class Encoding {
+ public:
+  static char escapeChar(char c) {
+    const char *p = escapeTable(false);
+    while (p[0] && p[1] != c) {
+      p += 2;
+    }
+    return p[0];
+  }
+  static char unescapeChar(char c) {
+    const char *p = escapeTable(true);
+    for (;;) {
+      if (p[0] == '\0') return c;
+      if (p[0] == c) return p[1];
+      p += 2;
+    }
+  }
+ private:
+  static const char *escapeTable(bool excludeIdenticals) {
+    return &"\"\"\\\\b\bf\fn\nr\rt\t"[excludeIdenticals ? 4 : 0];
+  }
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+bool isNaN(T x) {
+  return x != x;
+}
+template <typename T>
+bool isInfinity(T x) {
+  return x != 0.0 && x * 2 == x;
+}
+}  // namespace Internals
+}  // namespace ArduinoJson
+#include <stdlib.h>  // for size_t
+namespace ArduinoJson {
+namespace Internals {
+template <typename T, typename F>
+struct alias_cast_t {
+  union {
+    F raw;
+    T data;
+  };
+};
+template <typename T, typename F>
+T alias_cast(F raw_data) {
+  alias_cast_t<T, F> ac;
+  ac.raw = raw_data;
+  return ac.data;
+}
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T, size_t = sizeof(T)>
+struct FloatTraits {};
+template <typename T>
+struct FloatTraits<T, 8 /*64bits*/> {
+  typedef int64_t mantissa_type;
+  static const short mantissa_bits = 52;
+  static const mantissa_type mantissa_max =
+      (static_cast<mantissa_type>(1) << mantissa_bits) - 1;
+  typedef int16_t exponent_type;
+  static const exponent_type exponent_max = 308;
+  template <typename TExponent>
+  static T make_float(T m, TExponent e) {
+    if (e > 0) {
+      for (uint8_t index = 0; e != 0; index++) {
+        if (e & 1) m *= positiveBinaryPowerOfTen(index);
+        e >>= 1;
+      }
+    } else {
+      e = TExponent(-e);
+      for (uint8_t index = 0; e != 0; index++) {
+        if (e & 1) m *= negativeBinaryPowerOfTen(index);
+        e >>= 1;
+      }
+    }
+    return m;
+  }
+  static T positiveBinaryPowerOfTen(int index) {
+    static T factors[] = {
+        1e1,
+        1e2,
+        1e4,
+        1e8,
+        1e16,
+        forge(0x4693B8B5, 0xB5056E17),  // 1e32
+        forge(0x4D384F03, 0xE93FF9F5),  // 1e64
+        forge(0x5A827748, 0xF9301D32),  // 1e128
+        forge(0x75154FDD, 0x7F73BF3C)   // 1e256
+    };
+    return factors[index];
+  }
+  static T negativeBinaryPowerOfTen(int index) {
+    static T factors[] = {
+        forge(0x3FB99999, 0x9999999A),  // 1e-1
+        forge(0x3F847AE1, 0x47AE147B),  // 1e-2
+        forge(0x3F1A36E2, 0xEB1C432D),  // 1e-4
+        forge(0x3E45798E, 0xE2308C3A),  // 1e-8
+        forge(0x3C9CD2B2, 0x97D889BC),  // 1e-16
+        forge(0x3949F623, 0xD5A8A733),  // 1e-32
+        forge(0x32A50FFD, 0x44F4A73D),  // 1e-64
+        forge(0x255BBA08, 0xCF8C979D),  // 1e-128
+        forge(0x0AC80628, 0x64AC6F43)   // 1e-256
+    };
+    return factors[index];
+  }
+  static T negativeBinaryPowerOfTenPlusOne(int index) {
+    static T factors[] = {
+        1e0,
+        forge(0x3FB99999, 0x9999999A),  // 1e-1
+        forge(0x3F50624D, 0xD2F1A9FC),  // 1e-3
+        forge(0x3E7AD7F2, 0x9ABCAF48),  // 1e-7
+        forge(0x3CD203AF, 0x9EE75616),  // 1e-15
+        forge(0x398039D6, 0x65896880),  // 1e-31
+        forge(0x32DA53FC, 0x9631D10D),  // 1e-63
+        forge(0x25915445, 0x81B7DEC2),  // 1e-127
+        forge(0x0AFE07B2, 0x7DD78B14)   // 1e-255
+    };
+    return factors[index];
+  }
+  static T nan() {
+    return forge(0x7ff80000, 0x00000000);
+  }
+  static T inf() {
+    return forge(0x7ff00000, 0x00000000);
+  }
+  static T forge(uint32_t msb, uint32_t lsb) {
+    return alias_cast<T>((uint64_t(msb) << 32) | lsb);
+  }
+};
+template <typename T>
+struct FloatTraits<T, 4 /*32bits*/> {
+  typedef int32_t mantissa_type;
+  static const short mantissa_bits = 23;
+  static const mantissa_type mantissa_max =
+      (static_cast<mantissa_type>(1) << mantissa_bits) - 1;
+  typedef int8_t exponent_type;
+  static const exponent_type exponent_max = 38;
+  template <typename TExponent>
+  static T make_float(T m, TExponent e) {
+    if (e > 0) {
+      for (uint8_t index = 0; e != 0; index++) {
+        if (e & 1) m *= positiveBinaryPowerOfTen(index);
+        e >>= 1;
+      }
+    } else {
+      e = -e;
+      for (uint8_t index = 0; e != 0; index++) {
+        if (e & 1) m *= negativeBinaryPowerOfTen(index);
+        e >>= 1;
+      }
+    }
+    return m;
+  }
+  static T positiveBinaryPowerOfTen(int index) {
+    static T factors[] = {1e1f, 1e2f, 1e4f, 1e8f, 1e16f, 1e32f};
+    return factors[index];
+  }
+  static T negativeBinaryPowerOfTen(int index) {
+    static T factors[] = {1e-1f, 1e-2f, 1e-4f, 1e-8f, 1e-16f, 1e-32f};
+    return factors[index];
+  }
+  static T negativeBinaryPowerOfTenPlusOne(int index) {
+    static T factors[] = {1e0f, 1e-1f, 1e-3f, 1e-7f, 1e-15f, 1e-31f};
+    return factors[index];
+  }
+  static T forge(uint32_t bits) {
+    return alias_cast<T>(bits);
+  }
+  static T nan() {
+    return forge(0x7fc00000);
+  }
+  static T inf() {
+    return forge(0x7f800000);
+  }
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename TFloat>
+struct FloatParts {
+  uint32_t integral;
+  uint32_t decimal;
+  int16_t exponent;
+  int8_t decimalPlaces;
+  FloatParts(TFloat value) {
+    uint32_t maxDecimalPart = sizeof(TFloat) >= 8 ? 1000000000 : 1000000;
+    decimalPlaces = sizeof(TFloat) >= 8 ? 9 : 6;
+    exponent = normalize(value);
+    integral = uint32_t(value);
+    for (uint32_t tmp = integral; tmp >= 10; tmp /= 10) {
+      maxDecimalPart /= 10;
+      decimalPlaces--;
+    }
+    TFloat remainder = (value - TFloat(integral)) * TFloat(maxDecimalPart);
+    decimal = uint32_t(remainder);
+    remainder = remainder - TFloat(decimal);
+    decimal += uint32_t(remainder * 2);
+    if (decimal >= maxDecimalPart) {
+      decimal = 0;
+      integral++;
+      if (exponent && integral >= 10) {
+        exponent++;
+        integral = 1;
+      }
+    }
+    while (decimal % 10 == 0 && decimalPlaces > 0) {
+      decimal /= 10;
+      decimalPlaces--;
+    }
+  }
+  static int16_t normalize(TFloat& value) {
+    typedef FloatTraits<TFloat> traits;
+    int16_t powersOf10 = 0;
+    int8_t index = sizeof(TFloat) == 8 ? 8 : 5;
+    int bit = 1 << index;
+    if (value >= ARDUINOJSON_POSITIVE_EXPONENTIATION_THRESHOLD) {
+      for (; index >= 0; index--) {
+        if (value >= traits::positiveBinaryPowerOfTen(index)) {
+          value *= traits::negativeBinaryPowerOfTen(index);
+          powersOf10 = int16_t(powersOf10 + bit);
+        }
+        bit >>= 1;
+      }
+    }
+    if (value > 0 && value <= ARDUINOJSON_NEGATIVE_EXPONENTIATION_THRESHOLD) {
+      for (; index >= 0; index--) {
+        if (value < traits::negativeBinaryPowerOfTenPlusOne(index)) {
+          value *= traits::positiveBinaryPowerOfTen(index);
+          powersOf10 = int16_t(powersOf10 - bit);
+        }
+        bit >>= 1;
+      }
+    }
+    return powersOf10;
+  }
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename Print>
+class JsonWriter {
+ public:
+  explicit JsonWriter(Print &sink) : _sink(sink), _length(0) {}
+  size_t bytesWritten() const {
+    return _length;
+  }
+  void beginArray() {
+    writeRaw('[');
+  }
+  void endArray() {
+    writeRaw(']');
+  }
+  void beginObject() {
+    writeRaw('{');
+  }
+  void endObject() {
+    writeRaw('}');
+  }
+  void writeColon() {
+    writeRaw(':');
+  }
+  void writeComma() {
+    writeRaw(',');
+  }
+  void writeBoolean(bool value) {
+    writeRaw(value ? "true" : "false");
+  }
+  void writeString(const char *value) {
+    if (!value) {
+      writeRaw("null");
+    } else {
+      writeRaw('\"');
+      while (*value) writeChar(*value++);
+      writeRaw('\"');
+    }
+  }
+  void writeChar(char c) {
+    char specialChar = Encoding::escapeChar(c);
+    if (specialChar) {
+      writeRaw('\\');
+      writeRaw(specialChar);
+    } else {
+      writeRaw(c);
+    }
+  }
+  template <typename TFloat>
+  void writeFloat(TFloat value) {
+    if (isNaN(value)) return writeRaw("NaN");
+    if (value < 0.0) {
+      writeRaw('-');
+      value = -value;
+    }
+    if (isInfinity(value)) return writeRaw("Infinity");
+    FloatParts<TFloat> parts(value);
+    writeInteger(parts.integral);
+    if (parts.decimalPlaces) writeDecimals(parts.decimal, parts.decimalPlaces);
+    if (parts.exponent < 0) {
+      writeRaw("e-");
+      writeInteger(-parts.exponent);
+    }
+    if (parts.exponent > 0) {
+      writeRaw('e');
+      writeInteger(parts.exponent);
+    }
+  }
+  template <typename UInt>
+  void writeInteger(UInt value) {
+    char buffer[22];
+    char *end = buffer + sizeof(buffer) - 1;
+    char *ptr = end;
+    *ptr = 0;
+    do {
+      *--ptr = char(value % 10 + '0');
+      value = UInt(value / 10);
+    } while (value);
+    writeRaw(ptr);
+  }
+  void writeDecimals(uint32_t value, int8_t width) {
+    char buffer[16];
+    char *ptr = buffer + sizeof(buffer) - 1;
+    *ptr = 0;
+    while (width--) {
+      *--ptr = char(value % 10 + '0');
+      value /= 10;
+    }
+    *--ptr = '.';
+    writeRaw(ptr);
+  }
+  void writeRaw(const char *s) {
+    _length += _sink.print(s);
+  }
+  void writeRaw(char c) {
+    _length += _sink.print(c);
+  }
+ protected:
+  Print &_sink;
+  size_t _length;
+ private:
+  JsonWriter &operator=(const JsonWriter &);  // cannot be assigned
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+class JsonArray;
+class JsonObject;
+class JsonVariant;
+namespace Internals {
+class JsonArraySubscript;
+template <typename TKey>
+class JsonObjectSubscript;
+template <typename Writer>
+class JsonSerializer {
+ public:
+  static void serialize(const JsonArray &, Writer &);
+  static void serialize(const JsonArraySubscript &, Writer &);
+  static void serialize(const JsonObject &, Writer &);
+  template <typename TKey>
+  static void serialize(const JsonObjectSubscript<TKey> &, Writer &);
+  static void serialize(const JsonVariant &, Writer &);
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename Print>
+class Prettyfier {
+ public:
+  explicit Prettyfier(IndentedPrint<Print>& p) : _sink(p) {
+    _previousChar = 0;
+    _inString = false;
+  }
+  size_t print(char c) {
+    size_t n = _inString ? handleStringChar(c) : handleMarkupChar(c);
+    _previousChar = c;
+    return n;
+  }
+  size_t print(const char* s) {
+    size_t n = 0;
+    while (*s) n += print(*s++);
+    return n;
+  }
+ private:
+  Prettyfier& operator=(const Prettyfier&);  // cannot be assigned
+  bool inEmptyBlock() {
+    return _previousChar == '{' || _previousChar == '[';
+  }
+  size_t handleStringChar(char c) {
+    bool isQuote = c == '"' && _previousChar != '\\';
+    if (isQuote) _inString = false;
+    return _sink.print(c);
+  }
+  size_t handleMarkupChar(char c) {
+    switch (c) {
+      case '{':
+      case '[':
+        return writeBlockOpen(c);
+      case '}':
+      case ']':
+        return writeBlockClose(c);
+      case ':':
+        return writeColon();
+      case ',':
+        return writeComma();
+      case '"':
+        return writeQuoteOpen();
+      default:
+        return writeNormalChar(c);
+    }
+  }
+  size_t writeBlockClose(char c) {
+    size_t n = 0;
+    n += unindentIfNeeded();
+    n += _sink.print(c);
+    return n;
+  }
+  size_t writeBlockOpen(char c) {
+    size_t n = 0;
+    n += indentIfNeeded();
+    n += _sink.print(c);
+    return n;
+  }
+  size_t writeColon() {
+    size_t n = 0;
+    n += _sink.print(": ");
+    return n;
+  }
+  size_t writeComma() {
+    size_t n = 0;
+    n += _sink.print(",\r\n");
+    return n;
+  }
+  size_t writeQuoteOpen() {
+    _inString = true;
+    size_t n = 0;
+    n += indentIfNeeded();
+    n += _sink.print('"');
+    return n;
+  }
+  size_t writeNormalChar(char c) {
+    size_t n = 0;
+    n += indentIfNeeded();
+    n += _sink.print(c);
+    return n;
+  }
+  size_t indentIfNeeded() {
+    if (!inEmptyBlock()) return 0;
+    _sink.indent();
+    return _sink.print("\r\n");
+  }
+  size_t unindentIfNeeded() {
+    if (inEmptyBlock()) return 0;
+    _sink.unindent();
+    return _sink.print("\r\n");
+  }
+  char _previousChar;
+  IndentedPrint<Print>& _sink;
+  bool _inString;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+class StaticStringBuilder {
+ public:
+  StaticStringBuilder(char *buf, size_t size) : end(buf + size - 1), p(buf) {
+    *p = '\0';
+  }
+  size_t print(char c) {
+    if (p >= end) return 0;
+    *p++ = c;
+    *p = '\0';
+    return 1;
+  }
+  size_t print(const char *s) {
+    char *begin = p;
+    while (p < end && *s) *p++ = *s++;
+    *p = '\0';
+    return size_t(p - begin);
+  }
+ private:
+  char *end;
+  char *p;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+#if ARDUINOJSON_ENABLE_STD_STREAM
+#if ARDUINOJSON_ENABLE_STD_STREAM
+#include <ostream>
+namespace ArduinoJson {
+namespace Internals {
+class StreamPrintAdapter {
+ public:
+  explicit StreamPrintAdapter(std::ostream& os) : _os(os) {}
+  size_t print(char c) {
+    _os << c;
+    return 1;
+  }
+  size_t print(const char* s) {
+    _os << s;
+    return strlen(s);
+  }
+ private:
+  StreamPrintAdapter& operator=(const StreamPrintAdapter&);
+  std::ostream& _os;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+#endif  // ARDUINOJSON_ENABLE_STD_STREAM
+#endif
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+class JsonPrintable {
+ public:
+  template <typename Print>
+  typename EnableIf<!StringTraits<Print>::has_append, size_t>::type printTo(
+      Print &print) const {
+    JsonWriter<Print> writer(print);
+    JsonSerializer<JsonWriter<Print> >::serialize(downcast(), writer);
+    return writer.bytesWritten();
+  }
+#if ARDUINOJSON_ENABLE_STD_STREAM
+  std::ostream &printTo(std::ostream &os) const {
+    StreamPrintAdapter adapter(os);
+    printTo(adapter);
+    return os;
+  }
+#endif
+  size_t printTo(char *buffer, size_t bufferSize) const {
+    StaticStringBuilder sb(buffer, bufferSize);
+    return printTo(sb);
+  }
+  template <size_t N>
+  size_t printTo(char (&buffer)[N]) const {
+    return printTo(buffer, N);
+  }
+  template <typename TString>
+  typename EnableIf<StringTraits<TString>::has_append, size_t>::type printTo(
+      TString &str) const {
+    DynamicStringBuilder<TString> sb(str);
+    return printTo(sb);
+  }
+  template <typename Print>
+  size_t prettyPrintTo(IndentedPrint<Print> &print) const {
+    Prettyfier<Print> p(print);
+    return printTo(p);
+  }
+  size_t prettyPrintTo(char *buffer, size_t bufferSize) const {
+    StaticStringBuilder sb(buffer, bufferSize);
+    return prettyPrintTo(sb);
+  }
+  template <size_t N>
+  size_t prettyPrintTo(char (&buffer)[N]) const {
+    return prettyPrintTo(buffer, N);
+  }
+  template <typename Print>
+  typename EnableIf<!StringTraits<Print>::has_append, size_t>::type
+  prettyPrintTo(Print &print) const {
+    IndentedPrint<Print> indentedPrint(print);
+    return prettyPrintTo(indentedPrint);
+  }
+  template <typename TString>
+  typename EnableIf<StringTraits<TString>::has_append, size_t>::type
+  prettyPrintTo(TString &str) const {
+    DynamicStringBuilder<TString> sb(str);
+    return prettyPrintTo(sb);
+  }
+  size_t measureLength() const {
+    DummyPrint dp;
+    return printTo(dp);
+  }
+  size_t measurePrettyLength() const {
+    DummyPrint dp;
+    return prettyPrintTo(dp);
+  }
+ private:
+  const T &downcast() const {
+    return *static_cast<const T *>(this);
+  }
+};
+#if ARDUINOJSON_ENABLE_STD_STREAM
+template <typename T>
+inline std::ostream &operator<<(std::ostream &os, const JsonPrintable<T> &v) {
+  return v.printTo(os);
+}
+#endif
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename TImpl>
+class JsonVariantBase : public JsonPrintable<TImpl>,
+                        public JsonVariantCasts<TImpl>,
+                        public JsonVariantComparisons<TImpl>,
+                        public JsonVariantOr<TImpl>,
+                        public JsonVariantSubscripts<TImpl>,
+                        public JsonVariantTag {};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+class RawJsonString {
+ public:
+  explicit RawJsonString(T str) : _str(str) {}
+  operator T() const {
+    return _str;
+  }
+ private:
+  T _str;
+};
+template <typename String>
+struct StringTraits<RawJsonString<String>, void> {
+  static bool is_null(RawJsonString<String> source) {
+    return StringTraits<String>::is_null(static_cast<String>(source));
+  }
+  typedef RawJsonString<const char*> duplicate_t;
+  template <typename Buffer>
+  static duplicate_t duplicate(RawJsonString<String> source, Buffer* buffer) {
+    return duplicate_t(StringTraits<String>::duplicate(source, buffer));
+  }
+  static const bool has_append = false;
+  static const bool has_equals = false;
+  static const bool should_duplicate = StringTraits<String>::should_duplicate;
+};
+}  // namespace Internals
+template <typename T>
+inline Internals::RawJsonString<T> RawJson(T str) {
+  return Internals::RawJsonString<T>(str);
+}
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+struct IsFloatingPoint {
+  static const bool value = IsSame<T, float>::value || IsSame<T, double>::value;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+struct RemoveConst {
+  typedef T type;
+};
+template <typename T>
+struct RemoveConst<const T> {
+  typedef T type;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+class JsonArray;
+class JsonObject;
+class JsonVariant : public Internals::JsonVariantBase<JsonVariant> {
+  template <typename Print>
+  friend class Internals::JsonSerializer;
+ public:
+  JsonVariant() : _type(Internals::JSON_UNDEFINED) {}
+  JsonVariant(bool value) {
+    using namespace Internals;
+    _type = JSON_BOOLEAN;
+    _content.asInteger = static_cast<JsonUInt>(value);
+  }
+  template <typename T>
+  JsonVariant(T value, typename Internals::EnableIf<
+                           Internals::IsFloatingPoint<T>::value>::type * = 0) {
+    using namespace Internals;
+    _type = JSON_FLOAT;
+    _content.asFloat = static_cast<JsonFloat>(value);
+  }
+  template <typename T>
+  DEPRECATED("Second argument is not supported anymore")
+  JsonVariant(T value, uint8_t,
+              typename Internals::EnableIf<
+                  Internals::IsFloatingPoint<T>::value>::type * = 0) {
+    using namespace Internals;
+    _type = JSON_FLOAT;
+    _content.asFloat = static_cast<JsonFloat>(value);
+  }
+  template <typename T>
+  JsonVariant(
+      T value,
+      typename Internals::EnableIf<Internals::IsSignedIntegral<T>::value ||
+                                   Internals::IsSame<T, char>::value>::type * =
+          0) {
+    using namespace Internals;
+    if (value >= 0) {
+      _type = JSON_POSITIVE_INTEGER;
+      _content.asInteger = static_cast<JsonUInt>(value);
+    } else {
+      _type = JSON_NEGATIVE_INTEGER;
+      _content.asInteger = static_cast<JsonUInt>(-value);
+    }
+  }
+  template <typename T>
+  JsonVariant(T value,
+              typename Internals::EnableIf<
+                  Internals::IsUnsignedIntegral<T>::value>::type * = 0) {
+    using namespace Internals;
+    _type = JSON_POSITIVE_INTEGER;
+    _content.asInteger = static_cast<JsonUInt>(value);
+  }
+  template <typename TChar>
+  JsonVariant(
+      const TChar *value,
+      typename Internals::EnableIf<Internals::IsChar<TChar>::value>::type * =
+          0) {
+    _type = Internals::JSON_STRING;
+    _content.asString = reinterpret_cast<const char *>(value);
+  }
+  JsonVariant(Internals::RawJsonString<const char *> value) {
+    _type = Internals::JSON_UNPARSED;
+    _content.asString = value;
+  }
+  JsonVariant(const JsonArray &array);
+  JsonVariant(const JsonObject &object);
+  template <typename T>
+  const typename Internals::EnableIf<Internals::IsIntegral<T>::value, T>::type
+  as() const {
+    return variantAsInteger<T>();
+  }
+  template <typename T>
+  const typename Internals::EnableIf<Internals::IsSame<T, bool>::value, T>::type
+  as() const {
+    return variantAsInteger<int>() != 0;
+  }
+  template <typename T>
+  const typename Internals::EnableIf<Internals::IsFloatingPoint<T>::value,
+                                     T>::type
+  as() const {
+    return variantAsFloat<T>();
+  }
+  template <typename T>
+  typename Internals::EnableIf<Internals::IsSame<T, const char *>::value ||
+                                   Internals::IsSame<T, char *>::value,
+                               const char *>::type
+  as() const {
+    return variantAsString();
+  }
+  template <typename T>
+  typename Internals::EnableIf<Internals::StringTraits<T>::has_append, T>::type
+  as() const {
+    const char *cstr = variantAsString();
+    if (cstr) return T(cstr);
+    T s;
+    printTo(s);
+    return s;
+  }
+  template <typename T>
+  typename Internals::EnableIf<
+      Internals::IsSame<typename Internals::RemoveReference<T>::type,
+                        JsonArray>::value,
+      JsonArray &>::type
+  as() const {
+    return variantAsArray();
+  }
+  template <typename T>
+  typename Internals::EnableIf<
+      Internals::IsSame<typename Internals::RemoveReference<T>::type,
+                        const JsonArray>::value,
+      const JsonArray &>::type
+  as() const {
+    return variantAsArray();
+  }
+  template <typename T>
+  typename Internals::EnableIf<
+      Internals::IsSame<typename Internals::RemoveReference<T>::type,
+                        JsonObject>::value,
+      JsonObject &>::type
+  as() const {
+    return variantAsObject();
+  }
+  template <typename T>
+  typename Internals::EnableIf<
+      Internals::IsSame<typename Internals::RemoveReference<T>::type,
+                        const JsonObject>::value,
+      const JsonObject &>::type
+  as() const {
+    return variantAsObject();
+  }
+  template <typename T>
+  typename Internals::EnableIf<Internals::IsSame<T, JsonVariant>::value,
+                               T>::type
+  as() const {
+    return *this;
+  }
+  template <typename T>
+  typename Internals::EnableIf<Internals::IsIntegral<T>::value, bool>::type is()
+      const {
+    return variantIsInteger();
+  }
+  template <typename T>
+  typename Internals::EnableIf<Internals::IsFloatingPoint<T>::value, bool>::type
+  is() const {
+    return variantIsFloat();
+  }
+  template <typename T>
+  typename Internals::EnableIf<Internals::IsSame<T, bool>::value, bool>::type
+  is() const {
+    return variantIsBoolean();
+  }
+  template <typename T>
+  typename Internals::EnableIf<Internals::IsSame<T, const char *>::value ||
+                                   Internals::IsSame<T, char *>::value ||
+                                   Internals::StringTraits<T>::has_append,
+                               bool>::type
+  is() const {
+    return variantIsString();
+  }
+  template <typename T>
+  typename Internals::EnableIf<
+      Internals::IsSame<typename Internals::RemoveConst<
+                            typename Internals::RemoveReference<T>::type>::type,
+                        JsonArray>::value,
+      bool>::type
+  is() const {
+    return variantIsArray();
+  }
+  template <typename T>
+  typename Internals::EnableIf<
+      Internals::IsSame<typename Internals::RemoveConst<
+                            typename Internals::RemoveReference<T>::type>::type,
+                        JsonObject>::value,
+      bool>::type
+  is() const {
+    return variantIsObject();
+  }
+  bool success() const {
+    return _type != Internals::JSON_UNDEFINED;
+  }
+ private:
+  JsonArray &variantAsArray() const;
+  JsonObject &variantAsObject() const;
+  const char *variantAsString() const;
+  template <typename T>
+  T variantAsFloat() const;
+  template <typename T>
+  T variantAsInteger() const;
+  bool variantIsBoolean() const;
+  bool variantIsFloat() const;
+  bool variantIsInteger() const;
+  bool variantIsArray() const {
+    return _type == Internals::JSON_ARRAY;
+  }
+  bool variantIsObject() const {
+    return _type == Internals::JSON_OBJECT;
+  }
+  bool variantIsString() const {
+    return _type == Internals::JSON_STRING ||
+           (_type == Internals::JSON_UNPARSED && _content.asString &&
+            !strcmp("null", _content.asString));
+  }
+  Internals::JsonVariantType _type;
+  Internals::JsonVariantContent _content;
+};
+DEPRECATED("Decimal places are ignored, use the float value instead")
+inline JsonVariant float_with_n_digits(float value, uint8_t) {
+  return JsonVariant(value);
+}
+DEPRECATED("Decimal places are ignored, use the double value instead")
+inline JsonVariant double_with_n_digits(double value, uint8_t) {
+  return JsonVariant(value);
+}
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+struct IsArray {
+  static const bool value = false;
+};
+template <typename T>
+struct IsArray<T[]> {
+  static const bool value = true;
+};
+template <typename T, size_t N>
+struct IsArray<T[N]> {
+  static const bool value = true;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+class JsonArray;
+class JsonObject;
+class JsonBuffer : Internals::NonCopyable {
+ public:
+  JsonArray &createArray();
+  JsonObject &createObject();
+  template <typename TString>
+  DEPRECATED("char* are duplicated, you don't need strdup() anymore")
+  typename Internals::EnableIf<!Internals::IsArray<TString>::value,
+                               const char *>::type strdup(const TString &src) {
+    return Internals::StringTraits<TString>::duplicate(src, this);
+  }
+  template <typename TString>
+  DEPRECATED("char* are duplicated, you don't need strdup() anymore")
+  const char *strdup(TString *src) {
+    return Internals::StringTraits<TString *>::duplicate(src, this);
+  }
+  virtual void *alloc(size_t size) = 0;
+ protected:
+  ~JsonBuffer() {}
+  static FORCE_INLINE size_t round_size_up(size_t bytes) {
+#if ARDUINOJSON_ENABLE_ALIGNMENT
+    const size_t x = sizeof(void *) - 1;
+    return (bytes + x) & ~x;
+#else
+    return bytes;
+#endif
+  }
+};
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename TChar>
+class StringWriter {
+ public:
+  class String {
+   public:
+    String(TChar** ptr) : _writePtr(ptr), _startPtr(*ptr) {}
+    void append(char c) {
+      *(*_writePtr)++ = TChar(c);
+    }
+    const char* c_str() const {
+      *(*_writePtr)++ = 0;
+      return reinterpret_cast<const char*>(_startPtr);
+    }
+   private:
+    TChar** _writePtr;
+    TChar* _startPtr;
+  };
+  StringWriter(TChar* buffer) : _ptr(buffer) {}
+  String startString() {
+    return String(&_ptr);
+  }
+ private:
+  TChar* _ptr;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename TReader, typename TWriter>
+class JsonParser {
+ public:
+  JsonParser(JsonBuffer *buffer, TReader reader, TWriter writer,
+             uint8_t nestingLimit)
+      : _buffer(buffer),
+        _reader(reader),
+        _writer(writer),
+        _nestingLimit(nestingLimit) {}
+  JsonArray &parseArray();
+  JsonObject &parseObject();
+  JsonVariant parseVariant() {
+    JsonVariant result;
+    parseAnythingTo(&result);
+    return result;
+  }
+ private:
+  JsonParser &operator=(const JsonParser &);  // non-copiable
+  static bool eat(TReader &, char charToSkip);
+  FORCE_INLINE bool eat(char charToSkip) {
+    return eat(_reader, charToSkip);
+  }
+  const char *parseString();
+  bool parseAnythingTo(JsonVariant *destination);
+  inline bool parseArrayTo(JsonVariant *destination);
+  inline bool parseObjectTo(JsonVariant *destination);
+  inline bool parseStringTo(JsonVariant *destination);
+  static inline bool isBetween(char c, char min, char max) {
+    return min <= c && c <= max;
+  }
+  static inline bool canBeInNonQuotedString(char c) {
+    return isBetween(c, '0', '9') || isBetween(c, '_', 'z') ||
+           isBetween(c, 'A', 'Z') || c == '+' || c == '-' || c == '.';
+  }
+  static inline bool isQuote(char c) {
+    return c == '\'' || c == '\"';
+  }
+  JsonBuffer *_buffer;
+  TReader _reader;
+  TWriter _writer;
+  uint8_t _nestingLimit;
+};
+template <typename TJsonBuffer, typename TString, typename Enable = void>
+struct JsonParserBuilder {
+  typedef typename StringTraits<TString>::Reader InputReader;
+  typedef JsonParser<InputReader, TJsonBuffer &> TParser;
+  static TParser makeParser(TJsonBuffer *buffer, TString &json,
+                            uint8_t nestingLimit) {
+    return TParser(buffer, InputReader(json), *buffer, nestingLimit);
+  }
+};
+template <typename TJsonBuffer, typename TChar>
+struct JsonParserBuilder<TJsonBuffer, TChar *,
+                         typename EnableIf<!IsConst<TChar>::value>::type> {
+  typedef typename StringTraits<TChar *>::Reader TReader;
+  typedef StringWriter<TChar> TWriter;
+  typedef JsonParser<TReader, TWriter> TParser;
+  static TParser makeParser(TJsonBuffer *buffer, TChar *json,
+                            uint8_t nestingLimit) {
+    return TParser(buffer, TReader(json), TWriter(json), nestingLimit);
+  }
+};
+template <typename TJsonBuffer, typename TString>
+inline typename JsonParserBuilder<TJsonBuffer, TString>::TParser makeParser(
+    TJsonBuffer *buffer, TString &json, uint8_t nestingLimit) {
+  return JsonParserBuilder<TJsonBuffer, TString>::makeParser(buffer, json,
+                                                             nestingLimit);
+}
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename TDerived>
+class JsonBufferBase : public JsonBuffer {
+ public:
+  template <typename TString>
+  typename Internals::EnableIf<!Internals::IsArray<TString>::value,
+                               JsonArray &>::type
+  parseArray(const TString &json,
+             uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+    return Internals::makeParser(that(), json, nestingLimit).parseArray();
+  }
+  template <typename TString>
+  JsonArray &parseArray(
+      TString *json, uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+    return Internals::makeParser(that(), json, nestingLimit).parseArray();
+  }
+  template <typename TString>
+  JsonArray &parseArray(
+      TString &json, uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+    return Internals::makeParser(that(), json, nestingLimit).parseArray();
+  }
+  template <typename TString>
+  typename Internals::EnableIf<!Internals::IsArray<TString>::value,
+                               JsonObject &>::type
+  parseObject(const TString &json,
+              uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+    return Internals::makeParser(that(), json, nestingLimit).parseObject();
+  }
+  template <typename TString>
+  JsonObject &parseObject(
+      TString *json, uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+    return Internals::makeParser(that(), json, nestingLimit).parseObject();
+  }
+  template <typename TString>
+  JsonObject &parseObject(
+      TString &json, uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+    return Internals::makeParser(that(), json, nestingLimit).parseObject();
+  }
+  template <typename TString>
+  typename Internals::EnableIf<!Internals::IsArray<TString>::value,
+                               JsonVariant>::type
+  parse(const TString &json,
+        uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+    return Internals::makeParser(that(), json, nestingLimit).parseVariant();
+  }
+  template <typename TString>
+  JsonVariant parse(TString *json,
+                    uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+    return Internals::makeParser(that(), json, nestingLimit).parseVariant();
+  }
+  template <typename TString>
+  JsonVariant parse(TString &json,
+                    uint8_t nestingLimit = ARDUINOJSON_DEFAULT_NESTING_LIMIT) {
+    return Internals::makeParser(that(), json, nestingLimit).parseVariant();
+  }
+ protected:
+  ~JsonBufferBase() {}
+ private:
+  TDerived *that() {
+    return static_cast<TDerived *>(this);
+  }
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnon-virtual-dtor"
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic push
+#endif
+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
+#endif
+namespace ArduinoJson {
+namespace Internals {
+class DefaultAllocator {
+ public:
+  void* allocate(size_t size) {
+    return malloc(size);
+  }
+  void deallocate(void* pointer) {
+    free(pointer);
+  }
+};
+template <typename TAllocator>
+class DynamicJsonBufferBase
+    : public JsonBufferBase<DynamicJsonBufferBase<TAllocator> > {
+  struct Block;
+  struct EmptyBlock {
+    Block* next;
+    size_t capacity;
+    size_t size;
+  };
+  struct Block : EmptyBlock {
+    uint8_t data[1];
+  };
+ public:
+  enum { EmptyBlockSize = sizeof(EmptyBlock) };
+  DynamicJsonBufferBase(size_t initialSize = 256)
+      : _head(NULL), _nextBlockCapacity(initialSize) {}
+  ~DynamicJsonBufferBase() {
+    clear();
+  }
+  size_t size() const {
+    size_t total = 0;
+    for (const Block* b = _head; b; b = b->next) total += b->size;
+    return total;
+  }
+  virtual void* alloc(size_t bytes) {
+    alignNextAlloc();
+    return canAllocInHead(bytes) ? allocInHead(bytes) : allocInNewBlock(bytes);
+  }
+  void clear() {
+    Block* currentBlock = _head;
+    while (currentBlock != NULL) {
+      _nextBlockCapacity = currentBlock->capacity;
+      Block* nextBlock = currentBlock->next;
+      _allocator.deallocate(currentBlock);
+      currentBlock = nextBlock;
+    }
+    _head = 0;
+  }
+  class String {
+   public:
+    String(DynamicJsonBufferBase* parent)
+        : _parent(parent), _start(NULL), _length(0) {}
+    void append(char c) {
+      if (_parent->canAllocInHead(1)) {
+        char* end = static_cast<char*>(_parent->allocInHead(1));
+        *end = c;
+        if (_length == 0) _start = end;
+      } else {
+        char* newStart =
+            static_cast<char*>(_parent->allocInNewBlock(_length + 1));
+        if (_start && newStart) memcpy(newStart, _start, _length);
+        if (newStart) newStart[_length] = c;
+        _start = newStart;
+      }
+      _length++;
+    }
+    const char* c_str() {
+      append(0);
+      return _start;
+    }
+   private:
+    DynamicJsonBufferBase* _parent;
+    char* _start;
+    size_t _length;
+  };
+  String startString() {
+    return String(this);
+  }
+ private:
+  void alignNextAlloc() {
+    if (_head) _head->size = this->round_size_up(_head->size);
+  }
+  bool canAllocInHead(size_t bytes) const {
+    return _head != NULL && _head->size + bytes <= _head->capacity;
+  }
+  void* allocInHead(size_t bytes) {
+    void* p = _head->data + _head->size;
+    _head->size += bytes;
+    return p;
+  }
+  void* allocInNewBlock(size_t bytes) {
+    size_t capacity = _nextBlockCapacity;
+    if (bytes > capacity) capacity = bytes;
+    if (!addNewBlock(capacity)) return NULL;
+    _nextBlockCapacity *= 2;
+    return allocInHead(bytes);
+  }
+  bool addNewBlock(size_t capacity) {
+    size_t bytes = EmptyBlockSize + capacity;
+    Block* block = static_cast<Block*>(_allocator.allocate(bytes));
+    if (block == NULL) return false;
+    block->capacity = capacity;
+    block->size = 0;
+    block->next = _head;
+    _head = block;
+    return true;
+  }
+  TAllocator _allocator;
+  Block* _head;
+  size_t _nextBlockCapacity;
+};
+}  // namespace Internals
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic pop
+#endif
+#endif
+typedef Internals::DynamicJsonBufferBase<Internals::DefaultAllocator>
+    DynamicJsonBuffer;
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+class JsonBufferAllocated {
+ public:
+  void *operator new(size_t n, JsonBuffer *jsonBuffer) throw() {
+    if (!jsonBuffer) return NULL;
+    return jsonBuffer->alloc(n);
+  }
+  void operator delete(void *, JsonBuffer *)throw();
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+struct ListNode : public Internals::JsonBufferAllocated {
+  ListNode() throw() : next(NULL) {}
+  ListNode<T> *next;
+  T content;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+class ListConstIterator {
+ public:
+  explicit ListConstIterator(const ListNode<T> *node = NULL) : _node(node) {}
+  const T &operator*() const {
+    return _node->content;
+  }
+  const T *operator->() {
+    return &_node->content;
+  }
+  bool operator==(const ListConstIterator<T> &other) const {
+    return _node == other._node;
+  }
+  bool operator!=(const ListConstIterator<T> &other) const {
+    return _node != other._node;
+  }
+  ListConstIterator<T> &operator++() {
+    if (_node) _node = _node->next;
+    return *this;
+  }
+  ListConstIterator<T> &operator+=(size_t distance) {
+    while (_node && distance) {
+      _node = _node->next;
+      --distance;
+    }
+    return *this;
+  }
+ private:
+  const ListNode<T> *_node;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+class List;
+template <typename T>
+class ListIterator {
+  friend class List<T>;
+ public:
+  explicit ListIterator(ListNode<T> *node = NULL) : _node(node) {}
+  T &operator*() const {
+    return _node->content;
+  }
+  T *operator->() {
+    return &_node->content;
+  }
+  bool operator==(const ListIterator<T> &other) const {
+    return _node == other._node;
+  }
+  bool operator!=(const ListIterator<T> &other) const {
+    return _node != other._node;
+  }
+  ListIterator<T> &operator++() {
+    if (_node) _node = _node->next;
+    return *this;
+  }
+  ListIterator<T> &operator+=(size_t distance) {
+    while (_node && distance) {
+      _node = _node->next;
+      --distance;
+    }
+    return *this;
+  }
+  operator ListConstIterator<T>() const {
+    return ListConstIterator<T>(_node);
+  }
+ private:
+  ListNode<T> *_node;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+class List {
+ public:
+  typedef T value_type;
+  typedef ListNode<T> node_type;
+  typedef ListIterator<T> iterator;
+  typedef ListConstIterator<T> const_iterator;
+  explicit List(JsonBuffer *buffer) : _buffer(buffer), _firstNode(NULL) {}
+  bool success() const {
+    return _buffer != NULL;
+  }
+  size_t size() const {
+    size_t nodeCount = 0;
+    for (node_type *node = _firstNode; node; node = node->next) nodeCount++;
+    return nodeCount;
+  }
+  iterator add() {
+    node_type *newNode = new (_buffer) node_type();
+    if (_firstNode) {
+      node_type *lastNode = _firstNode;
+      while (lastNode->next) lastNode = lastNode->next;
+      lastNode->next = newNode;
+    } else {
+      _firstNode = newNode;
+    }
+    return iterator(newNode);
+  }
+  iterator begin() {
+    return iterator(_firstNode);
+  }
+  iterator end() {
+    return iterator(NULL);
+  }
+  const_iterator begin() const {
+    return const_iterator(_firstNode);
+  }
+  const_iterator end() const {
+    return const_iterator(NULL);
+  }
+  void remove(iterator it) {
+    node_type *nodeToRemove = it._node;
+    if (!nodeToRemove) return;
+    if (nodeToRemove == _firstNode) {
+      _firstNode = nodeToRemove->next;
+    } else {
+      for (node_type *node = _firstNode; node; node = node->next)
+        if (node->next == nodeToRemove) node->next = nodeToRemove->next;
+    }
+  }
+ protected:
+  JsonBuffer *_buffer;
+ private:
+  node_type *_firstNode;
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+class ReferenceType {
+ public:
+  bool operator==(const ReferenceType& other) const {
+    return this == &other;
+  }
+  bool operator!=(const ReferenceType& other) const {
+    return this != &other;
+  }
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename Source, typename Enable = void>
+struct ValueSaver {
+  template <typename Destination>
+  static bool save(JsonBuffer*, Destination& destination, Source source) {
+    destination = source;
+    return true;
+  }
+};
+template <typename Source>
+struct ValueSaver<
+    Source, typename EnableIf<StringTraits<Source>::should_duplicate>::type> {
+  template <typename Destination>
+  static bool save(JsonBuffer* buffer, Destination& dest, Source source) {
+    if (!StringTraits<Source>::is_null(source)) {
+      typename StringTraits<Source>::duplicate_t dup =
+          StringTraits<Source>::duplicate(source, buffer);
+      if (!dup) return false;
+      dest = dup;
+    } else {
+      dest = reinterpret_cast<const char*>(0);
+    }
+    return true;
+  }
+};
+template <typename Char>
+struct ValueSaver<
+    Char*, typename EnableIf<!StringTraits<Char*>::should_duplicate>::type> {
+  template <typename Destination>
+  static bool save(JsonBuffer*, Destination& dest, Char* source) {
+    dest = reinterpret_cast<const char*>(source);
+    return true;
+  }
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+#define JSON_ARRAY_SIZE(NUMBER_OF_ELEMENTS) \
+  (sizeof(JsonArray) + (NUMBER_OF_ELEMENTS) * sizeof(JsonArray::node_type))
+namespace ArduinoJson {
+class JsonObject;
+class JsonBuffer;
+namespace Internals {
+class JsonArraySubscript;
+}
+class JsonArray : public Internals::JsonPrintable<JsonArray>,
+                  public Internals::ReferenceType,
+                  public Internals::NonCopyable,
+                  public Internals::List<JsonVariant>,
+                  public Internals::JsonBufferAllocated {
+ public:
+  explicit JsonArray(JsonBuffer *buffer) throw()
+      : Internals::List<JsonVariant>(buffer) {}
+  const Internals::JsonArraySubscript operator[](size_t index) const;
+  Internals::JsonArraySubscript operator[](size_t index);
+  template <typename T>
+  bool add(const T &value) {
+    return add_impl<const T &>(value);
+  }
+  template <typename T>
+  bool add(T *value) {
+    return add_impl<T *>(value);
+  }
+  template <typename T>
+  DEPRECATED("Second argument is not supported anymore")
+  bool add(T value, uint8_t) {
+    return add_impl<const JsonVariant &>(JsonVariant(value));
+  }
+  template <typename T>
+  bool set(size_t index, const T &value) {
+    return set_impl<const T &>(index, value);
+  }
+  template <typename T>
+  bool set(size_t index, T *value) {
+    return set_impl<T *>(index, value);
+  }
+  template <typename T>
+  typename Internals::EnableIf<Internals::IsFloatingPoint<T>::value, bool>::type
+  set(size_t index, T value, uint8_t decimals) {
+    return set_impl<const JsonVariant &>(index, JsonVariant(value, decimals));
+  }
+  template <typename T>
+  typename Internals::JsonVariantAs<T>::type get(size_t index) const {
+    const_iterator it = begin() += index;
+    return it != end() ? it->as<T>() : Internals::JsonVariantDefault<T>::get();
+  }
+  template <typename T>
+  bool is(size_t index) const {
+    const_iterator it = begin() += index;
+    return it != end() ? it->is<T>() : false;
+  }
+  JsonArray &createNestedArray();
+  JsonObject &createNestedObject();
+  void remove(size_t index) {
+    remove(begin() += index);
+  }
+  using Internals::List<JsonVariant>::remove;
+  static JsonArray &invalid() {
+    static JsonArray instance(NULL);
+    return instance;
+  }
+  template <typename T, size_t N>
+  bool copyFrom(T (&array)[N]) {
+    return copyFrom(array, N);
+  }
+  template <typename T>
+  bool copyFrom(T *array, size_t len) {
+    bool ok = true;
+    for (size_t i = 0; i < len; i++) {
+      ok &= add(array[i]);
+    }
+    return ok;
+  }
+  template <typename T, size_t N1, size_t N2>
+  bool copyFrom(T (&array)[N1][N2]) {
+    bool ok = true;
+    for (size_t i = 0; i < N1; i++) {
+      JsonArray &nestedArray = createNestedArray();
+      for (size_t j = 0; j < N2; j++) {
+        ok &= nestedArray.add(array[i][j]);
+      }
+    }
+    return ok;
+  }
+  template <typename T, size_t N>
+  size_t copyTo(T (&array)[N]) const {
+    return copyTo(array, N);
+  }
+  template <typename T>
+  size_t copyTo(T *array, size_t len) const {
+    size_t i = 0;
+    for (const_iterator it = begin(); it != end() && i < len; ++it)
+      array[i++] = *it;
+    return i;
+  }
+  template <typename T, size_t N1, size_t N2>
+  void copyTo(T (&array)[N1][N2]) const {
+    size_t i = 0;
+    for (const_iterator it = begin(); it != end() && i < N1; ++it) {
+      it->as<JsonArray>().copyTo(array[i++]);
+    }
+  }
+#if ARDUINOJSON_ENABLE_DEPRECATED
+  DEPRECATED("use remove() instead")
+  FORCE_INLINE void removeAt(size_t index) {
+    return remove(index);
+  }
+#endif
+ private:
+  template <typename TValueRef>
+  bool set_impl(size_t index, TValueRef value) {
+    iterator it = begin() += index;
+    if (it == end()) return false;
+    return Internals::ValueSaver<TValueRef>::save(_buffer, *it, value);
+  }
+  template <typename TValueRef>
+  bool add_impl(TValueRef value) {
+    iterator it = Internals::List<JsonVariant>::add();
+    if (it == end()) return false;
+    return Internals::ValueSaver<TValueRef>::save(_buffer, *it, value);
+  }
+};
+namespace Internals {
+template <>
+struct JsonVariantDefault<JsonArray> {
+  static JsonArray &get() {
+    return JsonArray::invalid();
+  }
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+struct JsonPair {
+  const char* key;
+  JsonVariant value;
+};
+}  // namespace ArduinoJson
+#define JSON_OBJECT_SIZE(NUMBER_OF_ELEMENTS) \
+  (sizeof(JsonObject) + (NUMBER_OF_ELEMENTS) * sizeof(JsonObject::node_type))
+namespace ArduinoJson {
+class JsonArray;
+class JsonBuffer;
+namespace Internals {
+template <typename>
+class JsonObjectSubscript;
+}
+class JsonObject : public Internals::JsonPrintable<JsonObject>,
+                   public Internals::ReferenceType,
+                   public Internals::NonCopyable,
+                   public Internals::List<JsonPair>,
+                   public Internals::JsonBufferAllocated {
+ public:
+  explicit JsonObject(JsonBuffer* buffer) throw()
+      : Internals::List<JsonPair>(buffer) {}
+  template <typename TString>
+  Internals::JsonObjectSubscript<const TString&> operator[](
+      const TString& key) {
+    return Internals::JsonObjectSubscript<const TString&>(*this, key);
+  }
+  template <typename TString>
+  Internals::JsonObjectSubscript<TString*> operator[](TString* key) {
+    return Internals::JsonObjectSubscript<TString*>(*this, key);
+  }
+  template <typename TString>
+  const Internals::JsonObjectSubscript<const TString&> operator[](
+      const TString& key) const {
+    return Internals::JsonObjectSubscript<const TString&>(
+        *const_cast<JsonObject*>(this), key);
+  }
+  template <typename TString>
+  const Internals::JsonObjectSubscript<TString*> operator[](
+      TString* key) const {
+    return Internals::JsonObjectSubscript<TString*>(
+        *const_cast<JsonObject*>(this), key);
+  }
+  template <typename TValue, typename TString>
+  bool set(const TString& key, const TValue& value) {
+    return set_impl<const TString&, const TValue&>(key, value);
+  }
+  template <typename TValue, typename TString>
+  bool set(const TString& key, TValue* value) {
+    return set_impl<const TString&, TValue*>(key, value);
+  }
+  template <typename TValue, typename TString>
+  bool set(TString* key, const TValue& value) {
+    return set_impl<TString*, const TValue&>(key, value);
+  }
+  template <typename TValue, typename TString>
+  bool set(TString* key, TValue* value) {
+    return set_impl<TString*, TValue*>(key, value);
+  }
+  template <typename TValue, typename TString>
+  DEPRECATED("Second argument is not supported anymore")
+  typename Internals::EnableIf<Internals::IsFloatingPoint<TValue>::value,
+                               bool>::type
+      set(const TString& key, TValue value, uint8_t) {
+    return set_impl<const TString&, const JsonVariant&>(key,
+                                                        JsonVariant(value));
+  }
+  template <typename TValue, typename TString>
+  DEPRECATED("Second argument is not supported anymore")
+  typename Internals::EnableIf<Internals::IsFloatingPoint<TValue>::value,
+                               bool>::type
+      set(TString* key, TValue value, uint8_t) {
+    return set_impl<TString*, const JsonVariant&>(key, JsonVariant(value));
+  }
+  template <typename TValue, typename TString>
+  typename Internals::JsonVariantAs<TValue>::type get(
+      const TString& key) const {
+    return get_impl<const TString&, TValue>(key);
+  }
+  template <typename TValue, typename TString>
+  typename Internals::JsonVariantAs<TValue>::type get(TString* key) const {
+    return get_impl<TString*, TValue>(key);
+  }
+  template <typename TValue, typename TString>
+  bool is(const TString& key) const {
+    return is_impl<const TString&, TValue>(key);
+  }
+  template <typename TValue, typename TString>
+  bool is(TString* key) const {
+    return is_impl<TString*, TValue>(key);
+  }
+  template <typename TString>
+  JsonArray& createNestedArray(const TString& key) {
+    return createNestedArray_impl<const TString&>(key);
+  }
+  template <typename TString>
+  JsonArray& createNestedArray(TString* key) {
+    return createNestedArray_impl<TString*>(key);
+  }
+  template <typename TString>
+  JsonObject& createNestedObject(const TString& key) {
+    return createNestedObject_impl<const TString&>(key);
+  }
+  template <typename TString>
+  JsonObject& createNestedObject(TString* key) {
+    return createNestedObject_impl<TString*>(key);
+  }
+  template <typename TString>
+  bool containsKey(const TString& key) const {
+    return findKey<const TString&>(key) != end();
+  }
+  template <typename TString>
+  bool containsKey(TString* key) const {
+    return findKey<TString*>(key) != end();
+  }
+  template <typename TString>
+  void remove(const TString& key) {
+    remove(findKey<const TString&>(key));
+  }
+  template <typename TString>
+  void remove(TString* key) {
+    remove(findKey<TString*>(key));
+  }
+  using Internals::List<JsonPair>::remove;
+  static JsonObject& invalid() {
+    static JsonObject instance(NULL);
+    return instance;
+  }
+ private:
+  template <typename TStringRef>
+  iterator findKey(TStringRef key) {
+    iterator it;
+    for (it = begin(); it != end(); ++it) {
+      if (Internals::StringTraits<TStringRef>::equals(key, it->key)) break;
+    }
+    return it;
+  }
+  template <typename TStringRef>
+  const_iterator findKey(TStringRef key) const {
+    return const_cast<JsonObject*>(this)->findKey<TStringRef>(key);
+  }
+  template <typename TStringRef, typename TValue>
+  typename Internals::JsonVariantAs<TValue>::type get_impl(
+      TStringRef key) const {
+    const_iterator it = findKey<TStringRef>(key);
+    return it != end() ? it->value.as<TValue>()
+                       : Internals::JsonVariantDefault<TValue>::get();
+  }
+  template <typename TStringRef, typename TValueRef>
+  bool set_impl(TStringRef key, TValueRef value) {
+    if (Internals::StringTraits<TStringRef>::is_null(key)) return false;
+    iterator it = findKey<TStringRef>(key);
+    if (it == end()) {
+      it = Internals::List<JsonPair>::add();
+      if (it == end()) return false;
+      bool key_ok =
+          Internals::ValueSaver<TStringRef>::save(_buffer, it->key, key);
+      if (!key_ok) return false;
+    }
+    return Internals::ValueSaver<TValueRef>::save(_buffer, it->value, value);
+  }
+  template <typename TStringRef, typename TValue>
+  bool is_impl(TStringRef key) const {
+    const_iterator it = findKey<TStringRef>(key);
+    return it != end() ? it->value.is<TValue>() : false;
+  }
+  template <typename TStringRef>
+  JsonArray& createNestedArray_impl(TStringRef key);
+  template <typename TStringRef>
+  JsonObject& createNestedObject_impl(TStringRef key);
+};
+namespace Internals {
+template <>
+struct JsonVariantDefault<JsonObject> {
+  static JsonObject& get() {
+    return JsonObject::invalid();
+  }
+};
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+class StaticJsonBufferBase : public JsonBufferBase<StaticJsonBufferBase> {
+ public:
+  class String {
+   public:
+    String(StaticJsonBufferBase* parent) : _parent(parent) {
+      _start = parent->_buffer + parent->_size;
+    }
+    void append(char c) {
+      if (_parent->canAlloc(1)) {
+        char* last = static_cast<char*>(_parent->doAlloc(1));
+        *last = c;
+      }
+    }
+    const char* c_str() const {
+      if (_parent->canAlloc(1)) {
+        char* last = static_cast<char*>(_parent->doAlloc(1));
+        *last = '\0';
+        return _start;
+      } else {
+        return NULL;
+      }
+    }
+   private:
+    StaticJsonBufferBase* _parent;
+    char* _start;
+  };
+  StaticJsonBufferBase(char* buffer, size_t capa)
+      : _buffer(buffer), _capacity(capa), _size(0) {}
+  size_t capacity() const {
+    return _capacity;
+  }
+  size_t size() const {
+    return _size;
+  }
+  virtual void* alloc(size_t bytes) {
+    alignNextAlloc();
+    if (!canAlloc(bytes)) return NULL;
+    return doAlloc(bytes);
+  }
+  void clear() {
+    _size = 0;
+  }
+  String startString() {
+    return String(this);
+  }
+ protected:
+  ~StaticJsonBufferBase() {}
+ private:
+  void alignNextAlloc() {
+    _size = round_size_up(_size);
+  }
+  bool canAlloc(size_t bytes) const {
+    return _size + bytes <= _capacity;
+  }
+  void* doAlloc(size_t bytes) {
+    void* p = &_buffer[_size];
+    _size += bytes;
+    return p;
+  }
+  char* _buffer;
+  size_t _capacity;
+  size_t _size;
+};
+}  // namespace Internals
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnon-virtual-dtor"
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic push
+#endif
+#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
+#endif
+template <size_t CAPACITY>
+class StaticJsonBuffer : public Internals::StaticJsonBufferBase {
+ public:
+  explicit StaticJsonBuffer()
+      : Internals::StaticJsonBufferBase(_buffer, CAPACITY) {}
+ private:
+  char _buffer[CAPACITY];
+};
+}  // namespace ArduinoJson
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#elif defined(__GNUC__)
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
+#pragma GCC diagnostic pop
+#endif
+#endif
+namespace ArduinoJson {
+namespace Internals {
+template <typename TInput>
+void skipSpacesAndComments(TInput& input) {
+  for (;;) {
+    switch (input.current()) {
+      case ' ':
+      case '\t':
+      case '\r':
+      case '\n':
+        input.move();
+        continue;
+      case '/':
+        switch (input.next()) {
+          case '*':
+            input.move();  // skip '/'
+            for (;;) {
+              input.move();
+              if (input.current() == '\0') return;
+              if (input.current() == '*' && input.next() == '/') {
+                input.move();  // skip '*'
+                input.move();  // skip '/'
+                break;
+              }
+            }
+            break;
+          case '/':
+            for (;;) {
+              input.move();
+              if (input.current() == '\0') return;
+              if (input.current() == '\n') break;
+            }
+            break;
+          default:
+            return;
+        }
+        break;
+      default:
+        return;
+    }
+  }
+}
+}  // namespace Internals
+}  // namespace ArduinoJson
+template <typename TReader, typename TWriter>
+inline bool ArduinoJson::Internals::JsonParser<TReader, TWriter>::eat(
+    TReader &reader, char charToSkip) {
+  skipSpacesAndComments(reader);
+  if (reader.current() != charToSkip) return false;
+  reader.move();
+  return true;
+}
+template <typename TReader, typename TWriter>
+inline bool
+ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseAnythingTo(
+    JsonVariant *destination) {
+  skipSpacesAndComments(_reader);
+  switch (_reader.current()) {
+    case '[':
+      return parseArrayTo(destination);
+    case '{':
+      return parseObjectTo(destination);
+    default:
+      return parseStringTo(destination);
+  }
+}
+template <typename TReader, typename TWriter>
+inline ArduinoJson::JsonArray &
+ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseArray() {
+  if (_nestingLimit == 0) return JsonArray::invalid();
+  _nestingLimit--;
+  JsonArray &array = _buffer->createArray();
+  if (!eat('[')) goto ERROR_MISSING_BRACKET;
+  if (eat(']')) goto SUCCESS_EMPTY_ARRAY;
+  for (;;) {
+    JsonVariant value;
+    if (!parseAnythingTo(&value)) goto ERROR_INVALID_VALUE;
+    if (!array.add(value)) goto ERROR_NO_MEMORY;
+    if (eat(']')) goto SUCCES_NON_EMPTY_ARRAY;
+    if (!eat(',')) goto ERROR_MISSING_COMMA;
+  }
+SUCCESS_EMPTY_ARRAY:
+SUCCES_NON_EMPTY_ARRAY:
+  _nestingLimit++;
+  return array;
+ERROR_INVALID_VALUE:
+ERROR_MISSING_BRACKET:
+ERROR_MISSING_COMMA:
+ERROR_NO_MEMORY:
+  return JsonArray::invalid();
+}
+template <typename TReader, typename TWriter>
+inline bool ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseArrayTo(
+    JsonVariant *destination) {
+  JsonArray &array = parseArray();
+  if (!array.success()) return false;
+  *destination = array;
+  return true;
+}
+template <typename TReader, typename TWriter>
+inline ArduinoJson::JsonObject &
+ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseObject() {
+  if (_nestingLimit == 0) return JsonObject::invalid();
+  _nestingLimit--;
+  JsonObject &object = _buffer->createObject();
+  if (!eat('{')) goto ERROR_MISSING_BRACE;
+  if (eat('}')) goto SUCCESS_EMPTY_OBJECT;
+  for (;;) {
+    const char *key = parseString();
+    if (!key) goto ERROR_INVALID_KEY;
+    if (!eat(':')) goto ERROR_MISSING_COLON;
+    JsonVariant value;
+    if (!parseAnythingTo(&value)) goto ERROR_INVALID_VALUE;
+    if (!object.set(key, value)) goto ERROR_NO_MEMORY;
+    if (eat('}')) goto SUCCESS_NON_EMPTY_OBJECT;
+    if (!eat(',')) goto ERROR_MISSING_COMMA;
+  }
+SUCCESS_EMPTY_OBJECT:
+SUCCESS_NON_EMPTY_OBJECT:
+  _nestingLimit++;
+  return object;
+ERROR_INVALID_KEY:
+ERROR_INVALID_VALUE:
+ERROR_MISSING_BRACE:
+ERROR_MISSING_COLON:
+ERROR_MISSING_COMMA:
+ERROR_NO_MEMORY:
+  return JsonObject::invalid();
+}
+template <typename TReader, typename TWriter>
+inline bool ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseObjectTo(
+    JsonVariant *destination) {
+  JsonObject &object = parseObject();
+  if (!object.success()) return false;
+  *destination = object;
+  return true;
+}
+template <typename TReader, typename TWriter>
+inline const char *
+ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseString() {
+  typename RemoveReference<TWriter>::type::String str = _writer.startString();
+  skipSpacesAndComments(_reader);
+  char c = _reader.current();
+  if (isQuote(c)) {  // quotes
+    _reader.move();
+    char stopChar = c;
+    for (;;) {
+      c = _reader.current();
+      if (c == '\0') break;
+      _reader.move();
+      if (c == stopChar) break;
+      if (c == '\\') {
+        c = Encoding::unescapeChar(_reader.current());
+        if (c == '\0') break;
+        _reader.move();
+      }
+      str.append(c);
+    }
+  } else {  // no quotes
+    for (;;) {
+      if (!canBeInNonQuotedString(c)) break;
+      _reader.move();
+      str.append(c);
+      c = _reader.current();
+    }
+  }
+  return str.c_str();
+}
+template <typename TReader, typename TWriter>
+inline bool ArduinoJson::Internals::JsonParser<TReader, TWriter>::parseStringTo(
+    JsonVariant *destination) {
+  bool hasQuotes = isQuote(_reader.current());
+  const char *value = parseString();
+  if (value == NULL) return false;
+  if (hasQuotes) {
+    *destination = value;
+  } else {
+    *destination = RawJson(value);
+  }
+  return true;
+}
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4522)
+#endif
+namespace ArduinoJson {
+namespace Internals {
+class JsonArraySubscript : public JsonVariantBase<JsonArraySubscript> {
+ public:
+  FORCE_INLINE JsonArraySubscript(JsonArray& array, size_t index)
+      : _array(array), _index(index) {}
+  FORCE_INLINE JsonArraySubscript& operator=(const JsonArraySubscript& src) {
+    _array.set(_index, src);
+    return *this;
+  }
+  template <typename T>
+  FORCE_INLINE JsonArraySubscript& operator=(const T& src) {
+    _array.set(_index, src);
+    return *this;
+  }
+  template <typename T>
+  FORCE_INLINE JsonArraySubscript& operator=(T* src) {
+    _array.set(_index, src);
+    return *this;
+  }
+  FORCE_INLINE bool success() const {
+    return _index < _array.size();
+  }
+  template <typename T>
+  FORCE_INLINE typename JsonVariantAs<T>::type as() const {
+    return _array.get<T>(_index);
+  }
+  template <typename T>
+  FORCE_INLINE bool is() const {
+    return _array.is<T>(_index);
+  }
+  template <typename TValue>
+  FORCE_INLINE bool set(const TValue& value) {
+    return _array.set(_index, value);
+  }
+  template <typename TValue>
+  FORCE_INLINE bool set(TValue* value) {
+    return _array.set(_index, value);
+  }
+  template <typename TValue>
+  DEPRECATED("Second argument is not supported anymore")
+  FORCE_INLINE bool set(const TValue& value, uint8_t) {
+    return _array.set(_index, value);
+  }
+ private:
+  JsonArray& _array;
+  const size_t _index;
+};
+template <typename TImpl>
+inline JsonArraySubscript JsonVariantSubscripts<TImpl>::operator[](
+    size_t index) {
+  return impl()->template as<JsonArray>()[index];
+}
+template <typename TImpl>
+inline const JsonArraySubscript JsonVariantSubscripts<TImpl>::operator[](
+    size_t index) const {
+  return impl()->template as<JsonArray>()[index];
+}
+#if ARDUINOJSON_ENABLE_STD_STREAM
+inline std::ostream& operator<<(std::ostream& os,
+                                const JsonArraySubscript& source) {
+  return source.printTo(os);
+}
+#endif
+}  // namespace Internals
+inline Internals::JsonArraySubscript JsonArray::operator[](size_t index) {
+  return Internals::JsonArraySubscript(*this, index);
+}
+inline const Internals::JsonArraySubscript JsonArray::operator[](
+    size_t index) const {
+  return Internals::JsonArraySubscript(*const_cast<JsonArray*>(this), index);
+}
+}  // namespace ArduinoJson
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+namespace ArduinoJson {
+inline JsonArray &JsonArray::createNestedArray() {
+  if (!_buffer) return JsonArray::invalid();
+  JsonArray &array = _buffer->createArray();
+  add(array);
+  return array;
+}
+inline JsonObject &JsonArray::createNestedObject() {
+  if (!_buffer) return JsonObject::invalid();
+  JsonObject &object = _buffer->createObject();
+  add(object);
+  return object;
+}
+}  // namespace ArduinoJson
+inline ArduinoJson::JsonArray &ArduinoJson::JsonBuffer::createArray() {
+  JsonArray *ptr = new (this) JsonArray(this);
+  return ptr ? *ptr : JsonArray::invalid();
+}
+inline ArduinoJson::JsonObject &ArduinoJson::JsonBuffer::createObject() {
+  JsonObject *ptr = new (this) JsonObject(this);
+  return ptr ? *ptr : JsonObject::invalid();
+}
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4522)
+#endif
+namespace ArduinoJson {
+namespace Internals {
+template <typename TStringRef>
+class JsonObjectSubscript
+    : public JsonVariantBase<JsonObjectSubscript<TStringRef> > {
+  typedef JsonObjectSubscript<TStringRef> this_type;
+ public:
+  FORCE_INLINE JsonObjectSubscript(JsonObject& object, TStringRef key)
+      : _object(object), _key(key) {}
+  FORCE_INLINE this_type& operator=(const this_type& src) {
+    _object.set(_key, src);
+    return *this;
+  }
+  template <typename TValue>
+  FORCE_INLINE typename EnableIf<!IsArray<TValue>::value, this_type&>::type
+  operator=(const TValue& src) {
+    _object.set(_key, src);
+    return *this;
+  }
+  template <typename TValue>
+  FORCE_INLINE this_type& operator=(TValue* src) {
+    _object.set(_key, src);
+    return *this;
+  }
+  FORCE_INLINE bool success() const {
+    return _object.containsKey(_key);
+  }
+  template <typename TValue>
+  FORCE_INLINE typename JsonVariantAs<TValue>::type as() const {
+    return _object.get<TValue>(_key);
+  }
+  template <typename TValue>
+  FORCE_INLINE bool is() const {
+    return _object.is<TValue>(_key);
+  }
+  template <typename TValue>
+  FORCE_INLINE typename EnableIf<!IsArray<TValue>::value, bool>::type set(
+      const TValue& value) {
+    return _object.set(_key, value);
+  }
+  template <typename TValue>
+  FORCE_INLINE bool set(const TValue* value) {
+    return _object.set(_key, value);
+  }
+  template <typename TValue>
+  DEPRECATED("Second argument is not supported anymore")
+  FORCE_INLINE bool set(const TValue& value, uint8_t) {
+    return _object.set(_key, value);
+  }
+ private:
+  JsonObject& _object;
+  TStringRef _key;
+};
+#if ARDUINOJSON_ENABLE_STD_STREAM
+template <typename TStringRef>
+inline std::ostream& operator<<(std::ostream& os,
+                                const JsonObjectSubscript<TStringRef>& source) {
+  return source.printTo(os);
+}
+#endif
+}  // namespace Internals
+}  // namespace ArduinoJson
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+namespace ArduinoJson {
+template <typename TStringRef>
+inline JsonArray &JsonObject::createNestedArray_impl(TStringRef key) {
+  if (!_buffer) return JsonArray::invalid();
+  JsonArray &array = _buffer->createArray();
+  set(key, array);
+  return array;
+}
+template <typename TStringRef>
+inline JsonObject &JsonObject::createNestedObject_impl(TStringRef key) {
+  if (!_buffer) return JsonObject::invalid();
+  JsonObject &object = _buffer->createObject();
+  set(key, object);
+  return object;
+}
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+inline bool isdigit(char c) {
+  return '0' <= c && c <= '9';
+}
+inline bool issign(char c) {
+  return '-' == c || c == '+';
+}
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+inline bool isFloat(const char* s) {
+  if (!s) return false;
+  if (!strcmp(s, "NaN")) return true;
+  if (issign(*s)) s++;
+  if (!strcmp(s, "Infinity")) return true;
+  if (*s == '\0') return false;
+  while (isdigit(*s)) s++;
+  if (*s == '.') {
+    s++;
+    while (isdigit(*s)) s++;
+  }
+  if (*s == 'e' || *s == 'E') {
+    s++;
+    if (issign(*s)) s++;
+    if (!isdigit(*s)) return false;
+    while (isdigit(*s)) s++;
+  }
+  return *s == '\0';
+}
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+inline bool isInteger(const char* s) {
+  if (!s || !*s) return false;
+  if (issign(*s)) s++;
+  while (isdigit(*s)) s++;
+  return *s == '\0';
+}
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+inline T parseFloat(const char* s) {
+  typedef FloatTraits<T> traits;
+  typedef typename traits::mantissa_type mantissa_t;
+  typedef typename traits::exponent_type exponent_t;
+  if (!s) return 0;  // NULL
+  bool negative_result = false;
+  switch (*s) {
+    case '-':
+      negative_result = true;
+      s++;
+      break;
+    case '+':
+      s++;
+      break;
+  }
+  if (*s == 't') return 1;  // true
+  if (*s == 'n' || *s == 'N') return traits::nan();
+  if (*s == 'i' || *s == 'I')
+    return negative_result ? -traits::inf() : traits::inf();
+  mantissa_t mantissa = 0;
+  exponent_t exponent_offset = 0;
+  while (isdigit(*s)) {
+    if (mantissa < traits::mantissa_max / 10)
+      mantissa = mantissa * 10 + (*s - '0');
+    else
+      exponent_offset++;
+    s++;
+  }
+  if (*s == '.') {
+    s++;
+    while (isdigit(*s)) {
+      if (mantissa < traits::mantissa_max / 10) {
+        mantissa = mantissa * 10 + (*s - '0');
+        exponent_offset--;
+      }
+      s++;
+    }
+  }
+  int exponent = 0;
+  if (*s == 'e' || *s == 'E') {
+    s++;
+    bool negative_exponent = false;
+    if (*s == '-') {
+      negative_exponent = true;
+      s++;
+    } else if (*s == '+') {
+      s++;
+    }
+    while (isdigit(*s)) {
+      exponent = exponent * 10 + (*s - '0');
+      if (exponent + exponent_offset > traits::exponent_max) {
+        if (negative_exponent)
+          return negative_result ? -0.0f : 0.0f;
+        else
+          return negative_result ? -traits::inf() : traits::inf();
+      }
+      s++;
+    }
+    if (negative_exponent) exponent = -exponent;
+  }
+  exponent += exponent_offset;
+  T result = traits::make_float(static_cast<T>(mantissa), exponent);
+  return negative_result ? -result : result;
+}
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+namespace Internals {
+template <typename T>
+T parseInteger(const char *s) {
+  if (!s) return 0;  // NULL
+  if (*s == 't') return 1;  // "true"
+  T result = 0;
+  bool negative_result = false;
+  switch (*s) {
+    case '-':
+      negative_result = true;
+      s++;
+      break;
+    case '+':
+      s++;
+      break;
+  }
+  while (isdigit(*s)) {
+    result = T(result * 10 + T(*s - '0'));
+    s++;
+  }
+  return negative_result ? T(~result + 1) : result;
+}
+}  // namespace Internals
+}  // namespace ArduinoJson
+namespace ArduinoJson {
+inline JsonVariant::JsonVariant(const JsonArray &array) {
+  if (array.success()) {
+    _type = Internals::JSON_ARRAY;
+    _content.asArray = const_cast<JsonArray *>(&array);
+  } else {
+    _type = Internals::JSON_UNDEFINED;
+    _content.asArray = 0;  // <- prevent warning 'maybe-uninitialized'
+  }
+}
+inline JsonVariant::JsonVariant(const JsonObject &object) {
+  if (object.success()) {
+    _type = Internals::JSON_OBJECT;
+    _content.asObject = const_cast<JsonObject *>(&object);
+  } else {
+    _type = Internals::JSON_UNDEFINED;
+    _content.asObject = 0;  // <- prevent warning 'maybe-uninitialized'
+  }
+}
+inline JsonArray &JsonVariant::variantAsArray() const {
+  if (_type == Internals::JSON_ARRAY) return *_content.asArray;
+  return JsonArray::invalid();
+}
+inline JsonObject &JsonVariant::variantAsObject() const {
+  if (_type == Internals::JSON_OBJECT) return *_content.asObject;
+  return JsonObject::invalid();
+}
+template <typename T>
+inline T JsonVariant::variantAsInteger() const {
+  using namespace Internals;
+  switch (_type) {
+    case JSON_UNDEFINED:
+      return 0;
+    case JSON_POSITIVE_INTEGER:
+    case JSON_BOOLEAN:
+      return T(_content.asInteger);
+    case JSON_NEGATIVE_INTEGER:
+      return T(~_content.asInteger + 1);
+    case JSON_STRING:
+    case JSON_UNPARSED:
+      return parseInteger<T>(_content.asString);
+    default:
+      return T(_content.asFloat);
+  }
+}
+inline const char *JsonVariant::variantAsString() const {
+  using namespace Internals;
+  if (_type == JSON_UNPARSED && _content.asString &&
+      !strcmp("null", _content.asString))
+    return NULL;
+  if (_type == JSON_STRING || _type == JSON_UNPARSED) return _content.asString;
+  return NULL;
+}
+template <typename T>
+inline T JsonVariant::variantAsFloat() const {
+  using namespace Internals;
+  switch (_type) {
+    case JSON_UNDEFINED:
+      return 0;
+    case JSON_POSITIVE_INTEGER:
+    case JSON_BOOLEAN:
+      return static_cast<T>(_content.asInteger);
+    case JSON_NEGATIVE_INTEGER:
+      return -static_cast<T>(_content.asInteger);
+    case JSON_STRING:
+    case JSON_UNPARSED:
+      return parseFloat<T>(_content.asString);
+    default:
+      return static_cast<T>(_content.asFloat);
+  }
+}
+inline bool JsonVariant::variantIsBoolean() const {
+  using namespace Internals;
+  if (_type == JSON_BOOLEAN) return true;
+  if (_type != JSON_UNPARSED || _content.asString == NULL) return false;
+  return !strcmp(_content.asString, "true") ||
+         !strcmp(_content.asString, "false");
+}
+inline bool JsonVariant::variantIsInteger() const {
+  using namespace Internals;
+  return _type == JSON_POSITIVE_INTEGER || _type == JSON_NEGATIVE_INTEGER ||
+         (_type == JSON_UNPARSED && isInteger(_content.asString));
+}
+inline bool JsonVariant::variantIsFloat() const {
+  using namespace Internals;
+  return _type == JSON_FLOAT || _type == JSON_POSITIVE_INTEGER ||
+         _type == JSON_NEGATIVE_INTEGER ||
+         (_type == JSON_UNPARSED && isFloat(_content.asString));
+}
+#if ARDUINOJSON_ENABLE_STD_STREAM
+inline std::ostream &operator<<(std::ostream &os, const JsonVariant &source) {
+  return source.printTo(os);
+}
+#endif
+}  // namespace ArduinoJson
+template <typename Writer>
+inline void ArduinoJson::Internals::JsonSerializer<Writer>::serialize(
+    const JsonArray& array, Writer& writer) {
+  writer.beginArray();
+  JsonArray::const_iterator it = array.begin();
+  while (it != array.end()) {
+    serialize(*it, writer);
+    ++it;
+    if (it == array.end()) break;
+    writer.writeComma();
+  }
+  writer.endArray();
+}
+template <typename Writer>
+inline void ArduinoJson::Internals::JsonSerializer<Writer>::serialize(
+    const JsonArraySubscript& arraySubscript, Writer& writer) {
+  serialize(arraySubscript.as<JsonVariant>(), writer);
+}
+template <typename Writer>
+inline void ArduinoJson::Internals::JsonSerializer<Writer>::serialize(
+    const JsonObject& object, Writer& writer) {
+  writer.beginObject();
+  JsonObject::const_iterator it = object.begin();
+  while (it != object.end()) {
+    writer.writeString(it->key);
+    writer.writeColon();
+    serialize(it->value, writer);
+    ++it;
+    if (it == object.end()) break;
+    writer.writeComma();
+  }
+  writer.endObject();
+}
+template <typename Writer>
+template <typename TKey>
+inline void ArduinoJson::Internals::JsonSerializer<Writer>::serialize(
+    const JsonObjectSubscript<TKey>& objectSubscript, Writer& writer) {
+  serialize(objectSubscript.template as<JsonVariant>(), writer);
+}
+template <typename Writer>
+inline void ArduinoJson::Internals::JsonSerializer<Writer>::serialize(
+    const JsonVariant& variant, Writer& writer) {
+  switch (variant._type) {
+    case JSON_FLOAT:
+      writer.writeFloat(variant._content.asFloat);
+      return;
+    case JSON_ARRAY:
+      serialize(*variant._content.asArray, writer);
+      return;
+    case JSON_OBJECT:
+      serialize(*variant._content.asObject, writer);
+      return;
+    case JSON_STRING:
+      writer.writeString(variant._content.asString);
+      return;
+    case JSON_UNPARSED:
+      writer.writeRaw(variant._content.asString);
+      return;
+    case JSON_NEGATIVE_INTEGER:
+      writer.writeRaw('-');  // Falls through.
+    case JSON_POSITIVE_INTEGER:
+      writer.writeInteger(variant._content.asInteger);
+      return;
+    case JSON_BOOLEAN:
+      writer.writeBoolean(variant._content.asInteger != 0);
+      return;
+    default:  // JSON_UNDEFINED
+      return;
+  }
+}
+#ifdef __GNUC__
+#define ARDUINOJSON_PRAGMA(x) _Pragma(#x)
+#define ARDUINOJSON_COMPILE_ERROR(msg) ARDUINOJSON_PRAGMA(GCC error msg)
+#define ARDUINOJSON_STRINGIFY(S) #S
+#define ARDUINOJSON_DEPRECATION_ERROR(X, Y) \
+  ARDUINOJSON_COMPILE_ERROR(ARDUINOJSON_STRINGIFY(X is a Y from ArduinoJson 6 but version 5 is installed. Visit arduinojson.org to get more information.))
+#define StaticJsonDocument ARDUINOJSON_DEPRECATION_ERROR(StaticJsonDocument, class)
+#define DynamicJsonDocument ARDUINOJSON_DEPRECATION_ERROR(DynamicJsonDocument, class)
+#define JsonDocument ARDUINOJSON_DEPRECATION_ERROR(JsonDocument, class)
+#define DeserializationError ARDUINOJSON_DEPRECATION_ERROR(DeserializationError, class)
+#define deserializeJson ARDUINOJSON_DEPRECATION_ERROR(deserializeJson, function)
+#define deserializeMsgPack ARDUINOJSON_DEPRECATION_ERROR(deserializeMsgPack, function)
+#define serializeJson ARDUINOJSON_DEPRECATION_ERROR(serializeJson, function)
+#define serializeMsgPack ARDUINOJSON_DEPRECATION_ERROR(serializeMsgPack, function)
+#define serializeJsonPretty ARDUINOJSON_DEPRECATION_ERROR(serializeJsonPretty, function)
+#define measureMsgPack ARDUINOJSON_DEPRECATION_ERROR(measureMsgPack, function)
+#define measureJson ARDUINOJSON_DEPRECATION_ERROR(measureJson, function)
+#define measureJsonPretty ARDUINOJSON_DEPRECATION_ERROR(measureJsonPretty, function)
+#endif
+
+using namespace ArduinoJson;
+
+#else
+
+#error ArduinoJson requires a C++ compiler, please change file extension to .cc or .cpp
+
+#endif

+ 0 - 7
auto_fill_jewel_v3/contourdata.cpp

@@ -1,10 +1,3 @@
-//
-//  contourdata.cpp
-//  Test
-//
-//  Created by 高慕白 on 2024/12/3.
-//
-
 #include "contourdata.h"
 #include <cmath>
 using namespace ArduinoJson;

+ 4 - 11
auto_fill_jewel_v3/contourdata.h

@@ -1,15 +1,8 @@
-//
-//  contourdata.hpp
-//  Test
-//
-//  Created by 高慕白 on 2024/12/3.
-//
-
-#ifndef contourdata_hpp
-#define contourdata_hpp
+#ifndef CONTOURDATA_H
+#define CONTOURDATA_H
 
 // 定义轮廓数据 by wf 2024-11-13
-#include "ajson5.h"
+#include "ajson5.hpp"
 #include <fstream>
 #include <vector>
 #include <string>
@@ -45,4 +38,4 @@ public:
 
 };
 
-#endif /* contourdata_hpp */
+#endif // CONTOURDATA_H

+ 4 - 2
auto_fill_jewel_v3/contourdatatools.cpp

@@ -1,12 +1,14 @@
 //
 //  contourdatatools.cpp
-//  Test
+//  auto_fill_jewel_v3
 //
-//  Created by 高慕白 on 2024/12/3.
+//  Created by Red on 2024/11/25.
 //
 
 #include "contourdatatools.hpp"
 
+
+
 ContourData ContourDataTools::convex_hull(ContourData& cd)
 {
     BoostGeometryTools::BoostPolygon poly ;

+ 3 - 3
auto_fill_jewel_v3/contourdatatools.hpp

@@ -1,9 +1,9 @@
 //
 //  contourdatatools.hpp
-//  Test
-//
-//  Created by 高慕白 on 2024/12/3.
+//  auto_fill_jewel_v3
 //
+//  Created by Red on 2024/11/25.
+// 计算轮廓数据包围盒子
 
 #ifndef contourdatatools_hpp
 #define contourdatatools_hpp

+ 29 - 66
auto_fill_jewel_v3/main.cpp

@@ -1,8 +1,29 @@
+//
+//  main.cpp
+//  auto_fill_jewel_v3
+//
+//  Created by Red on 2024/11/22.
+//
+/*
+ 需求:
+ 1.给定宝石种类和每类数量
+ 2.程序随机给出5种盘子组合
+ 3.关卡由多个盘子组成,每个盘子包含若干不同层,每层上面包含宝石。
+ 4.宝石有大、中、小三个尺度,按面积算: 1大=2中=4小
+ 5.盘子有大、小、迷你3种,其容量分别对应 6个大宝石,3个大宝石,1个大宝石。(确认没有中盘子)
+ 6.每个关卡最大有9个大盘子。
+ 7.盘子之间的层数差异在0-1个。迷你盘子数量可以很多,这个是不是需要人工控制后面再问吧。
+ 8.盘子会移动,但是盘子间不会重叠。
+ 9.大中小号盘子有横的有竖着的。
+ 10.重叠区百分比可以从0.05到0.2,程序自行判断最优值。
+ 11.宝石只提供 80x80的资源,对应大。如果有中和小,需要程序缩放(0.75,0.50)。大、中、小是不同的宝石各消各的。
+ */
+
 #include <iostream>
 #include <string>
 #include <vector>
 #include "SpriteData.hpp"
-#include "ajson5.h"
+#include "ajson5.hpp"
 #include "contourdata.h"
 
 #include "LevelGenerate.hpp"
@@ -15,7 +36,9 @@
 #include <sstream>
 #include <ctime>
 #include <chrono>
-
+#include <tuple>
+#include <sstream>
+#include "LevelThumbTool.hpp"
 using namespace ArduinoJson;
 using namespace std;
 
@@ -44,76 +67,16 @@ int main(int argc, const char * argv[]) {
 
     LevelGenerate genv3 ;
     genv3.generate( *FillGlobalConfig::getInstance()->getLevelData(100, 0),
+                   false,
                    resultPlateFillResults,
                    resultPlateCenterPointArr
                    ) ;
 
     cout<<"write result to "<<outname<<endl;
     writeLevelJson(resultPlateFillResults, resultPlateCenterPointArr, outname);
-
-
-//    string levelCsvfilename = argv[1] ;
-//    LevelInputConfig levelInputConfig ;
-//    bool isok = levelInputConfig.readFromCsv(levelCsvfilename) ;
-//    LevelGenerate genv3 ;
-//    genv3.generate(levelInputConfig) ;
-
-    if(false)
-    {//测试6个大的
-        const float BIG_JEW_WID = 80  ;
-        vector<RandomGridFiller::JewelBox> jewBoxArray ;
-        RandomGridFiller::JewelBox jb1,jb2 ;
-        jb1._cnt = 3  ;
-        jb1._height = BIG_JEW_WID ;
-        jb1._width = BIG_JEW_WID*0.67;
-        jb1._jewelTypeId = 1 ;
-
-        jb2._cnt = 3*2  ;
-        jb2._height = BIG_JEW_WID*0.75    ;
-        jb2._width = BIG_JEW_WID*0.75  ;
-        jb2._jewelTypeId = 2 ;
-        jewBoxArray.push_back(jb1) ;
-        jewBoxArray.push_back(jb2) ;
-
-        auto ms0 = std::chrono::system_clock::now().time_since_epoch() ;
-        uint64_t ms00 = std::chrono::duration_cast<chrono::milliseconds>(ms0).count() ;
-
-        RandomGridFiller RBFiller ;
-        RBFiller._seed = 5 ;
-        vector< vector<FillResult> > results(1) ;
-        RBFiller.fill(jewBoxArray, 5, 14 , 200, 265, results[0] ) ;
-
-        auto ms1 = std::chrono::system_clock::now().time_since_epoch() ;
-        uint64_t ms11 = std::chrono::duration_cast<chrono::milliseconds>(ms1).count() ;
-        cout<<"duration(ms) "<<ms11-ms00<<endl;
-
-        for(int ires = 0 ;ires < results.size();++ ires ) {
-            cv::Mat plateimage = cv::Mat::zeros( 270, 210, CV_8UC3 );
-            for(int ibox = 0 ; ibox < results[ires].size(); ++ ibox ) {
-                FillResult& fr = results[ires][ibox] ;
-                BoostGeometryTools::BoostPolygon poly1 = BoostGeometryTools::makeRotateNTranslateBox(-fr._width/2, -fr._height/2, fr._width, fr._height, results[ires][ibox]._rotdeg, results[ires][ibox]._x, results[ires][ibox]._y) ;
-                vector<cv::Point> cvpoints ;
-                for(int ipt = 0 ;ipt < poly1.outer().size();++ ipt ) {
-                    cv::Point p2 ;
-                    p2.x = poly1.outer()[ipt].get<0>();
-                    p2.y = poly1.outer()[ipt].get<1>() ;
-                    cvpoints.push_back(p2) ;
-                }
-                cv::polylines(plateimage, cvpoints, true, cv::Scalar(rand()%255,rand()%255,rand()%255));
-            }
-            //
-            stringstream ss ;
-            ss<<"win"<<ires ;
-            cv::imshow( ss.str().c_str() , plateimage );
-
-        }
-
-        cv::waitKey();
-
-    }
-
-    
-    
+    LevelThumbTool ltt ;
+    ltt.makeThumb2(outname+"_thumb.png", resultPlateFillResults, resultPlateCenterPointArr) ;
+ 
     
     return 0;
 }