用算法解决 2048问题 终结版

根据 nneonneo 的方法完成的 Delphi 版的程序。他的程序总是用了泛型,Delphi 是从 2010 版开始支持泛型。虽然之前的版本可以使用第三方提供的泛型,比如:HouSisong大牛的DGL,但是我在实验中感觉并不好用,特别是无法直接支持 这样的定义(应该是我没搞懂如何用吧)。最后选择了带有这个功能的 Delphi 2010 。

另外,我不太清楚为什么 nneonneo 要选择 map 这个类型,我从网上查阅的资料来看,这是内部有序的类型,内部使用红黑树来维持顺序,但是从他的算法来看,这里用 hash 来维持一个一对一的对应关系即可。

引入这个算法来进行剪枝后,程序速度极大提高了。

局面分数没有修改,还是使用 float,因为我觉得已经够快了。

program NeoNeo;

{$APPTYPE CONSOLE}

uses
  SysUtils,windows,Generics.collections,classes,math;

const
  ROW_MASK=$FFFF;
  COL_MASK=$000F000F000F000F;
  CPROB_THRESH_BASE = 0.0001;
  CACHE_DEPTH_LIMIT=6;
  SEARCH_DEPTH_LIMIT=8;
  b:array[0..15] of integer=(0,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768);
type
  board_t=int64;
  row_t=word;
  get_move_func_t = function(board:board_t):integer;
  T_eval_state=record
         trans_table:TDictionary<board_t,single>;
         cprob_thresh:single;
         maxdepth:integer;
         curdepth:integer;
         cachehits:integer;
         moves_evaled:integer;
       end;

  function score_tilechoose_node(var state:T_eval_state; board:board_t; cprob:single):single; forward;
  function reverse_row(row:row_t):row_t;  forward;
  function pack_col(col:board_t):row_t; forward;
  function unpack_col(row:row_t):board_t; forward;
  function score_move_node(Var state:t_eval_state;board:board_t; cprob:single):single; forward;

var
  row_left_table:array[0..65535] of board_t;
  row_right_table:array[0..65535] of board_t;
  col_up_table:array[0..65535] of board_t;
  col_down_table:array[0..65535] of board_t;
  line_heur_score_table:array[0..65535] of single;
  row_score_table:array[0..65535] of single;

procedure init_move_tables;
var
  row:LongWord;
  line:array[0..3] of LongWord;
  result:row_t;
  i,j:integer;
begin
  fillchar(row_left_table,0,sizeof(row_left_table));
  fillchar(row_right_table,0,sizeof(row_right_table));
  fillchar(col_up_table,0,sizeof(col_up_table));
  fillchar(col_down_table,0,sizeof(col_down_table));

  for  row:= 0 to 65535 do
    begin
      line[0]:=row and $f;
      line[1]:=(row shr 4) and $f;
      line[2]:=(row shr 8) and $f;
      line[3]:=(row shr 12) and $f;

      i:=0;
      while (i<3) do
        begin
          j:=i+1;
          while (j<4) do
            begin
              if line[j]<>0 then
                  break;
              inc(j);
            end;
          if (j=4) then break;
          if (line[i]=0) then
             begin
               line[i]:=line[j];
               line[j]:=0;
               dec(i);
             end
          else
            if (line[i]=line[j]) and (line[i]<>$f) then
               begin
                 inc(line[i]);
                 line[j]:=0;
               end;
          inc(i);
        end;

      result:=(line[0]) or (line[1] shl 4) or (line[2] shl 8) or (line[3] shl 12);

      row_left_table[row]:=row xor result;
      row_right_table[reverse_row(row)]:=reverse_row(row) xor reverse_row(result);
      col_up_table[row] := unpack_col(row) xor unpack_col(result);
      col_down_table[reverse_row(row)]:=unpack_col(reverse_row(row)) xor unpack_col(reverse_row(result));

    end;
end;

function execute_move_0(var board:board_t):board_t;
var
  tmp:board_t;
  ret:board_t;
begin
  ret:=board;

  tmp:=col_up_table[pack_col((board shr (4*0))and $000F000F000F000F)];
  ret:=ret xor (tmp shl (4*0));

  tmp:=col_up_table[pack_col((board shr (4*1))and $000F000F000F000F)];
  ret:=ret xor (tmp shl (4*1));

  tmp:=col_up_table[pack_col((board shr (4*2))and $000F000F000F000F)];
  ret:=ret xor (tmp shl (4*2));

  tmp:=col_up_table[pack_col((board shr (4*3))and $000F000F000F000F)];
  ret:=ret xor (tmp shl (4*3));

  Result:=ret;
end;

function execute_move_1(var board:board_t):board_t;
var
  tmp:board_t;
  ret:board_t;
begin
  ret:=board;

  tmp:=col_down_table[pack_col((board shr (4*0))and $000F000F000F000F)];
  ret:=ret xor (tmp shl (4*0));

  tmp:=col_down_table[pack_col((board shr (4*1))and $000F000F000F000F)];
  ret:=ret xor (tmp shl (4*1));

  tmp:=col_down_table[pack_col((board shr (4*2))and $000F000F000F000F)];
  ret:=ret xor (tmp shl (4*2));

  tmp:=col_down_table[pack_col((board shr (4*3))and $000F000F000F000F)];
  ret:=ret xor (tmp shl (4*3));

  Result:=ret;
end;

function execute_move_2(var board:board_t):board_t;
var
  tmp:board_t;
  ret:board_t;
begin
  ret:=board;

  tmp:=row_left_table  [board shr (16*0) and $FFFF];
  ret:=ret xor (tmp shl (16*0));

  tmp:=row_left_table[board shr (16*1) and $FFFF];
  ret:=ret xor (tmp shl (16*1));

  tmp:=row_left_table[board shr (16*2) and $FFFF];
  ret:=ret xor (tmp shl (16*2));

  tmp:=row_left_table[board shr (16*3) and $FFFF];
  ret:=ret xor (tmp shl (16*3));

  Result:=ret;
end;

function execute_move_3(var board:board_t):board_t;
var
  tmp:board_t;
  ret:board_t;
begin
  ret:=board;

  tmp:=row_right_table[board shr (16*0) and $FFFF];
  ret:=ret xor (tmp shl (16*0));

  tmp:=row_right_table[board shr (16*1) and $FFFF];
  ret:=ret xor (tmp shl (16*1));

  tmp:=row_right_table[board shr (16*2) and $FFFF];
  ret:=ret xor (tmp shl (16*2));

  tmp:=row_right_table[board shr (16*3) and $FFFF];
  ret:=ret xor (tmp shl (16*3));

  Result:=ret;
end;

function pack_col(col:board_t):row_t;
begin
  Result:=col or (col shr 12) or (col shr 24) or (col shr 36);
end;

function unpack_col(row:row_t):board_t;
var
  tmp:board_t;
begin
  tmp:=row;
  Result:=(tmp or (tmp shl 12) or (tmp shl 24) or (tmp shl 36)) and COL_MASK;
end;

procedure print_board(board:board_t);
var
  i:integer;
begin
  for i := 0 to 3 do
    begin
      writeln(format('%4d %4d %4d %4d',
               [b[board and $f],
                b[board shr 4 and $f],
                b[board shr 8 and $f],
                b[board shr 12 and $f]]));
      board:=board shr 16;
    end;
end;

function reverse_row(row:row_t):row_t;
begin
  Result:=((row shr 12) or ((row shr 4) and $F0) or ((row shl 4) and $0F00) or (row shl 12));
end;

function execute_move(move:integer;board:board_t):Board_t;
begin
  case move of
     0://up
       begin
         Result:=execute_move_0(board);
       end;
     1://down
       begin
         Result:=execute_move_1(Board);
       end;
     2://left
       begin
         Result:=execute_move_2(board);
       end;
     3://right
       begin
         Result:=execute_move_3(Board);
       end;
     else
       Result:=0;
  end;
end;

function get_max_rank(board:board_t):integer;
var
  maxrank,k:integer;
begin
  maxrank:=0;
  while board<>0 do
    begin
      k:=board and $f;
      if k>maxrank then maxrank:=k;
      board:=board shr 4;
    end;
  Result:=maxrank;
end;

procedure init_score_tables;
var
  row:Longword;
  i,maxi:integer;
  heur_score:single;
  score:single;
  line:array[0..3] of LongWord;
  rank,maxrank:LongWord;
begin
  fillchar(line_heur_score_table,0,sizeof(line_heur_score_table));
  fillchar(row_score_table,0,sizeof(row_score_table));

  for row:= 0 to 65535 do
    begin

  line[0]:=row and $f;
  line[1]:=(row shr 4) and $f;
  line[2]:=(row shr 8) and $f;
  line[3]:=(row shr 12) and $f;

      heur_score:=0;
      score:=0;

      for i := 0 to 3 do
        begin
          rank:=line[i];

          if (rank=0) then
            begin
              heur_score:=heur_score+10000;
            end
          else
            if (rank>=2) then
              begin
                score:=score+(rank-1)*power(2,rank);
              end;
        end;

      maxi:=0;
      maxrank:=0;
      for i := 0 to 3 do
        begin
          rank:=line[i];
          if (rank>maxrank) then
             begin
               maxrank:=rank;
               maxi:=i;
             end;
        end;

      if (maxi=0) or (maxi=3) then
        heur_score:=heur_score+20000;

      for i := 1 to 3 do
        if (line[i]=line[i-1]+1) or (line[i]=line[i-1]-1) then
             heur_score:=heur_score+1000;

      if (line[0]<line[1]) and (line[1]<line[2]) and (line[2]<line[3]) then
         heur_score:=heur_score+10000;
      if (line[0]>line[1]) and (line[1]>line[2]) and (line[2]>line[3]) then
         heur_score:=heur_score+10000;

      row_score_table[row]:=score;
      line_heur_score_table[row]:=heur_score;
    end;
end;

{
function score_board(board:board_t;tbl):single;
begin
  Result:=(tbl[board and row_mask])+
          (tbl[board shr 16 and row_mask])+
          (tbl[board shr 32 and row_mask])+
          (tbl[board shr 48 and row_mask]);
end;

function score_col_board(board:board_t;tbl):single;
begin
  Result:=(tbl[pack_col[board and col_mask]])+
          (tbl[pack_col[board shr 4 and col_mask])+
          (tbl[pack_col[board shr 8 and col_mask])+
          (tbl[pack_col[board shr 12 and col_mask]);
end;
}
function score_heur_board(board:board_t):single;
begin
  Result:=(line_heur_score_table[board and row_mask])+
          (line_heur_score_table[board shr 16 and row_mask])+
          (line_heur_score_table[board shr 32 and row_mask])+
          (line_heur_score_table[board shr 48 and row_mask])+
          (line_heur_score_table[pack_col(board and col_mask)])+
          (line_heur_score_table[pack_col(board shr 4 and col_mask)])+
          (line_heur_score_table[pack_col(board shr 8 and col_mask)])+
          (line_heur_score_table[pack_col(board shr 12 and col_mask)])+100000;
end;

function score_board(board:board_t):single;
begin
  result:=(row_score_table[board and row_mask])+
          (row_score_table[(board shr 16) and row_mask] )+
          (row_score_table[(board shr 32) and row_mask])+
          (row_score_table[(board shr 48) and row_mask]);
end;

function score_tilechoose_node(var state:T_eval_state; board:board_t; cprob:single):single;
var
  res:single;
  num_open,i:integer;
begin
  res:=0;
  num_open:=0;

  for i:=0 to 15 do
    begin
    if (board shr (4*i) and $f)=0 then
      inc(num_open);
    end;

  cprob:=cprob /num_open;

  for i := 0 to 15 do
    begin
      if ((board shr (4*i)) and $f =0) then
        begin
          res:=res+score_move_node(state,board or (Int64(1) shl (4*i)) ,cprob *0.9) *0.9;
          res:=res+score_move_node(state,board or (Int64(2) shl (4*i)),cprob *0.1) *0.1;
        end;
    end;

  Result:=Res / num_open;
end;

function score_move_node(Var state:t_eval_state;board:board_t; cprob:single):single;
var
  move:integer;
  best:single;
  res:single;
  newboard:board_t;
begin
  if (cprob<state.cprob_thresh) or (state.curdepth >= SEARCH_DEPTH_LIMIT) then
    begin
      if (state.curdepth > state.curdepth) then
        begin
          state.maxdepth:=state.curdepth;
        end;
      Result:=score_heur_board(board);
      exit;
    end;

   if (state.curdepth < CACHE_DEPTH_LIMIT) then
     begin
       if state.trans_table.ContainsKey(board) then
         begin
           inc(state.cachehits,1);
           Result:=state.trans_table[board];
           exit;
         end;

     end;

   best:=0;

   inc(state.curdepth);
   for move := 0 to 3 do
     begin
       newboard:=execute_move(move,board);
       inc(state.moves_evaled);
       if (board = newboard) then
         continue;
       res:=score_tilechoose_node(state,newboard,cprob);
       if res > best then
         best:=res;
     end;
   dec(state.curdepth);

   if state.curdepth < CACHE_DEPTH_LIMIT then
     begin
       if state.trans_table.ContainsKey(board) then
         begin
           state.trans_table[board]:=best;
         end
       else
         state.trans_table.Add(board,best);
     end;

   result:=best;
end;

function t_score_toplevel_move(var state:t_eval_state;board:board_t;move:integer):single;
var
  newboard:board_t;
begin
  newboard:=execute_move(move,board);
  if board=newboard then
    begin
      Result:=0;
      exit;
    end;

  state.cprob_thresh:=CPROB_THRESH_BASE;

  Result:=score_tilechoose_node(state,newboard,1)+1e-6;
end;


function score_toplevel_move(board:board_t; move:integer):single;
var
  res:single;
  start,finish:DWORD;
  state:t_eval_state;
begin
  state.trans_table:=TDictionary<board_t,single>.Create;
  state.cprob_thresh:=0;
  state.maxdepth:=0;
  state.curdepth:=0;
  state.cachehits:=0;
  state.moves_evaled:=0;

  start:=GetTickCount;
  res:=t_score_toplevel_move(state,board,move);
  finish:=GetTickCount;

  writeln(format('Move %d: result %f: eval %d moves. (%d Cache hits)',
      [move,res,state.moves_evaled,state.cachehits])+'in '+IntToStr(Finish-Start)+' ms');
  state.trans_table.Free;

  result:=res;
end;

function find_best_move(board:board_t):integer;
var
   move:integer;
   best:single;
   bestmove:integer;
   res:single;
begin
  best:=0;
  bestmove:=-1;

  print_board(board);
  writeln(format('Current scores: heur %.0f, actual %.0f',[score_heur_board(board),score_board(board)]));
  for move := 0 to 3 do
    begin
      res:=score_toplevel_move(board,move);
      if (res>best) then
        begin
          best:=res;
          bestmove:=move;
        end;
    end;
  Result:=bestmove;
end;

function draw_tile:integer;
begin
  if Random(9)=0 then Result:=2
  else  Result:=1;
end;

function insert_tile_rand(board:board_t;tile:integer):board_t;
var
  num_open:integer;
  i,index:integer;
  tmp:board_t;
begin
  num_open:=0;
  for i := 0 to 15 do
    if (board shr (4*i) and $f)=0 then
      begin
        inc(num_open)
      end;
  if num_open =0 then
    begin
      writeln('insert_tile_rand:no open spots!');
      Result:= board;
      exit;
    end;

  index:=random(num_open);
  for i := 0 to 15 do
    begin
      if ((board shr (4*i))and $f)<>0 then
         continue;
      if (index=0) then
         begin
           tmp:=tile;
           board:=board or (tmp shl (4 *i));
           break;
         end;
      dec(index);
    end;
  Result:=board;
end;

function initial_board:board_t;
var
   board:board_t;
   i:integer;
begin
  //randomize;
  board:=0;
  for i := 0 to 1 do
    board:=insert_tile_rand(board,draw_tile);
  Result:=board;
end;

procedure play_game(get_move:get_move_func_t);
var
  board:board_t;
  moveno:integer;
  scorepenalty:integer;
  move:integer;
  newboard:board_t;
  tile:integer;
begin

  board:=initial_board;
  moveno:=0;
  scorepenalty:=0;

  while True do
    begin
      move:=0;
      while move<4 do
        begin
          if execute_move(move,board)<>board then
              break;
          inc(move);
        end;

      if move=4 then break;

      inc(moveno);
      writeln(format('Move %d , current score=%.0f, %d',[moveno,score_board(board)-scorepenalty,scorepenalty]));

      move:=get_move(board);
      if (move < 0) then break;

      newboard:=execute_move(move,board);
      if (newboard=board) then
        begin
          writeln('Illegal move!');
          dec(moveno);
          continue;
        end;

      tile:=draw_tile;
      if tile=2 then inc(scorepenalty,4);
      board:=insert_tile_rand(newboard,tile);
      writeln;
    end;
   print_board(board);
   writeln(Format('Game Over. Your score is %.0f. The hightest rank you achieved was %d.',[score_board(board)-scorepenalty,get_max_rank(board)]));
end;

//Main
begin
  init_move_tables;
  init_score_tables;
  play_game (find_best_move);

  readln;
end.

 

没有运行完,中途暂停截图:

2048——Final

上面的程序中使用的是伪随机数,这样可以方便比较和评估。如果需要测试更多局面,请查找并打开 randomize 。

写完程序的一点感受:时代在变化,算法依然非常重要,技术的进步使得人们可以更加关注于该用什么而不是如何实现。

代码下载

2048_T

www.lab-z.com
Zoologist
2014-4-26

利用算法解决2048游戏

在 http://stackoverflow.com/questions/22342854/what-is-the-optimal-algorithm-for-the-game-2048/22389702#22389702 上有很多大牛提出了自己的解决方法。

ovolve 使用了 a-b剪枝,这是典型的用在博弈上的算法,比如:之前的中国象棋就是用了这个方法。

Nicola Pezzotti 的特点在于贡献了一个很简易的计算局面整洁度的算法.....

问题是上面的都是 js 语言编写的,对于我来说很难理解,并且很难进行评估。

个人感觉算法本身不难,困难的是如何理解他们使用的数据结构,更具体来说困难度在于如何进行局面评估的算法(这个问题中,需要对盘面顺序程度和整体分数进行评估)。

最终,目光落在 nneonneo 的算法上。他使用了 bit-map 的技术,能够充分利用空间:每一个格子从 0-15 ,对应着 2^0 - 2^15 ,整个局面用64位即可表示。这样做大大

降低了传递参数之类的耗时,客观上节省了时间。此外,还使用数组直接存放了行列的分数,评估行列分数直接让数组值相加即可,这是非常经典的空间换时间的做法。

他的程序可以在他的 git上下载到 https://github.com/nneonneo/2048-ai ,我这里也存了一份可以在 vs2008下编译通过的

nneonneo

我的程序完全仿照了他的程序,只是没有实现他用来剪枝的泛型 typedef std::map trans_table_t; 因为我使用的 Delphi 2010 没有对应的功能,

在我的 I5 2450 上,一步大约需要2s左右。

program NeoNeo;

{$APPTYPE CONSOLE}

uses
  SysUtils,math,windows;

const
  ROW_MASK=$FFFF;
  COL_MASK=$000F000F000F000F;
  CPROB_THRESH_BASE = 0.0001;
  CACHE_DEPTH_LIMIT=1;
  SEARCH_DEPTH_LIMIT=3;
  b:array[0..13] of integer=(0,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192);
type
  board_t=int64;
  row_t=word;
  get_move_func_t = function(board:board_t):integer;
  T_eval_state=record
         cprob_thresh:single;
         maxdepth:integer;
         curdepth:integer;
         cachehits:integer;
         moves_evaled:integer;
       end;

  function score_tilechoose_node(var state:T_eval_state; board:board_t; cprob:single):single; forward;
  function reverse_row(row:row_t):row_t;  forward;
  function pack_col(col:board_t):row_t; forward;
  function unpack_col(row:row_t):board_t; forward;
  function score_move_node(Var state:t_eval_state;board:board_t; cprob:single):single; forward;

var
  row_left_table:array[0..65535] of board_t;
  row_right_table:array[0..65535] of board_t;
  col_up_table:array[0..65535] of board_t;
  col_down_table:array[0..65535] of board_t;
  line_heur_score_table:array[0..65535] of single;
  row_score_table:array[0..65535] of single;

procedure init_move_tables;
var
  row:LongWord;
  line:array[0..3] of LongWord;
  result:row_t;
  i,j:integer;
begin
  fillchar(row_left_table,0,sizeof(row_left_table));
  fillchar(row_right_table,0,sizeof(row_right_table));
  fillchar(col_up_table,0,sizeof(col_up_table));
  fillchar(col_down_table,0,sizeof(col_down_table));

  for  row:= 0 to 65535 do
    begin
      line[0]:=row and $f;
      line[1]:=(row shr 4) and $f;
      line[2]:=(row shr 8) and $f;
      line[3]:=(row shr 12) and $f;

      i:=0;
      while (i<3) do
        begin
          j:=i+1;
          //for j := i+1 to 3 do
          while (j<4) do
            begin
              if line[j]<>0 then
                  break;
              inc(j);
            end;
          if (j=4) then break;
          if (line[i]=0) then
             begin
               line[i]:=line[j];
               line[j]:=0;
               dec(i);
             end
          else
            if (line[i]=line[j]) and (line[i]<>$f) then
               begin
                 inc(line[i]);
                 line[j]:=0;
               end;
          inc(i);
        end;

      result:=(line[0]) or (line[1] shl 4) or (line[2] shl 8) or (line[3] shl 12);

      row_left_table[row]:=row xor result;
      row_right_table[reverse_row(row)]:=reverse_row(row) xor reverse_row(result);
      col_up_table[row] := unpack_col(row) xor unpack_col(result);
      col_down_table[reverse_row(row)]:=unpack_col(reverse_row(row)) xor unpack_col(reverse_row(result));
    end;
end;

function execute_move_0(var board:board_t):board_t;
var
  tmp:board_t;
  ret:board_t;
begin
  ret:=board;

  tmp:=col_up_table[pack_col((board shr (4*0))and $000F000F000F000F)];
  ret:=ret xor (tmp shl (4*0));

  tmp:=col_up_table[pack_col((board shr (4*1))and $000F000F000F000F)];
  ret:=ret xor (tmp shl (4*1));

  tmp:=col_up_table[pack_col((board shr (4*2))and $000F000F000F000F)];
  ret:=ret xor (tmp shl (4*2));

  tmp:=col_up_table[pack_col((board shr (4*3))and $000F000F000F000F)];
  ret:=ret xor (tmp shl (4*3));

  Result:=ret;
end;

function execute_move_1(var board:board_t):board_t;
var
  tmp:board_t;
  ret:board_t;
begin
  ret:=board;

  tmp:=col_down_table[pack_col((board shr (4*0))and $000F000F000F000F)];
  ret:=ret xor (tmp shl (4*0));

  tmp:=col_down_table[pack_col((board shr (4*1))and $000F000F000F000F)];
  ret:=ret xor (tmp shl (4*1));

  tmp:=col_down_table[pack_col((board shr (4*2))and $000F000F000F000F)];
  ret:=ret xor (tmp shl (4*2));

  tmp:=col_down_table[pack_col((board shr (4*3))and $000F000F000F000F)];
  ret:=ret xor (tmp shl (4*3));

  Result:=ret;
end;

function execute_move_2(var board:board_t):board_t;
var
  tmp:board_t;
  ret:board_t;
begin
  ret:=board;

  tmp:=row_left_table[board shr (16*0) and $FFFF];
  ret:=ret xor (tmp shl (16*0));

  tmp:=row_left_table[board shr (16*1) and $FFFF];
  ret:=ret xor (tmp shl (16*1));

  tmp:=row_left_table[board shr (16*2) and $FFFF];
  ret:=ret xor (tmp shl (16*2));

  tmp:=row_left_table[board shr (16*3) and $FFFF];
  ret:=ret xor (tmp shl (16*3));

  Result:=ret;
end;

function execute_move_3(var board:board_t):board_t;
var
  tmp:board_t;
  ret:board_t;
begin
  ret:=board;

  tmp:=row_right_table[board shr (16*0) and $FFFF];
  ret:=ret xor (tmp shl (16*0));

  tmp:=row_right_table[board shr (16*1) and $FFFF];
  ret:=ret xor (tmp shl (16*1));

  tmp:=row_right_table[board shr (16*2) and $FFFF];
  ret:=ret xor (tmp shl (16*2));

  tmp:=row_right_table[board shr (16*3) and $FFFF];
  ret:=ret xor (tmp shl (16*3));

  Result:=ret;
end;

function pack_col(col:board_t):row_t;
begin
  Result:=col or (col shr 12) or (col shr 24) or (col shr 36);
end;

function unpack_col(row:row_t):board_t;
var
  tmp:board_t;
begin
  tmp:=row;
  Result:=(tmp or (tmp shl 12) or (tmp shl 24) or (tmp shl 36)) and COL_MASK;
end;

procedure print_board(board:board_t);
var
  i:integer;
begin
  for i := 0 to 3 do
    begin
      writeln(format('%4d %4d %4d %4d',
               [b[board and $f],
                b[board shr 4 and $f],
                b[board shr 8 and $f],
                b[board shr 12 and $f]]));
      board:=board shr 16;
    end;
end;

function reverse_row(row:row_t):row_t;
begin
  Result:=((row shr 12) or ((row shr 4) and $F0) or ((row shl 4) and $0F00) or (row shl 12));
end;

function execute_move(move:integer;board:board_t):Board_t;
begin
  case move of
     0://up
       begin
         Result:=execute_move_0(board);
       end;
     1://down
       begin
         Result:=execute_move_1(Board);
       end;
     2://left
       begin
         Result:=execute_move_2(board);
       end;
     3://right
       begin
         Result:=execute_move_3(Board);
       end;
     else
       Result:=0;
  end;
end;

function get_max_rank(board:board_t):integer;
var
  maxrank,k:integer;
begin
  maxrank:=0;
  while board<>0 do
    begin
      k:=board and $f;
      if k>maxrank then maxrank:=k;
      board:=board shr 4;
    end;
  Result:=maxrank;
end;

procedure init_score_tables;
var
  row:Longword;
  i,maxi:integer;
  heur_score:single;
  score:single;
  line:array[0..3] of LongWord;
  rank,maxrank:LongWord;
begin
  fillchar(line_heur_score_table,0,sizeof(line_heur_score_table));
  fillchar(row_score_table,0,sizeof(row_score_table));

  for row:= 0 to 65535 do
    begin

  line[0]:=row and $f;
  line[1]:=(row shr 4) and $f;
  line[2]:=(row shr 8) and $f;
  line[3]:=(row shr 12) and $f;

      heur_score:=0;
      score:=0;

      for i := 0 to 3 do
        begin
          rank:=line[i];

          if (rank=0) then
            begin
              heur_score:=heur_score+10000;
            end
          else
            if (rank>=2) then
              begin
                score:=score+(rank-1)*power(2,rank);
              end;
        end;

      maxi:=0;
      maxrank:=0;
      for i := 0 to 3 do
        begin
          rank:=line[i];
          if (rank>maxrank) then
             begin
               maxrank:=rank;
               maxi:=i;
             end;
        end;

      if (maxi=0) or (maxi=3) then
        heur_score:=heur_score+20000;

      for i := 1 to 3 do
        if (line[i]=line[i-1]+1) or (line[i]=line[i-1]-1) then
             heur_score:=heur_score+1000;

      if (line[0]<line[1]) and (line[1]<line[2]) and (line[2]<line[3]) then
         heur_score:=heur_score+10000;
      if (line[0]>line[1]) and (line[1]>line[2]) and (line[2]>line[3]) then
         heur_score:=heur_score+10000;

      row_score_table[row]:=score;
      line_heur_score_table[row]:=heur_score;
    end;
end;

{
function score_board(board:board_t;tbl):single;
begin
  Result:=(tbl[board and row_mask])+
          (tbl[board shr 16 and row_mask])+
          (tbl[board shr 32 and row_mask])+
          (tbl[board shr 48 and row_mask]);
end;

function score_col_board(board:board_t;tbl):single;
begin
  Result:=(tbl[pack_col[board and col_mask]])+
          (tbl[pack_col[board shr 4 and col_mask])+
          (tbl[pack_col[board shr 8 and col_mask])+
          (tbl[pack_col[board shr 12 and col_mask]);
end;
}
function score_heur_board(board:board_t):single;
begin
  Result:=(line_heur_score_table[board and row_mask])+
          (line_heur_score_table[board shr 16 and row_mask])+
          (line_heur_score_table[board shr 32 and row_mask])+
          (line_heur_score_table[board shr 48 and row_mask])+
          (line_heur_score_table[pack_col(board and col_mask)])+
          (line_heur_score_table[pack_col(board shr 4 and col_mask)])+
          (line_heur_score_table[pack_col(board shr 8 and col_mask)])+
          (line_heur_score_table[pack_col(board shr 12 and col_mask)])+100000;
end;

function score_board(board:board_t):single;
begin
  result:=(row_score_table[board and row_mask])+
          (row_score_table[(board shr 16) and row_mask] )+
          (row_score_table[(board shr 32) and row_mask])+
          (row_score_table[(board shr 48) and row_mask]);
end;

function score_tilechoose_node(var state:T_eval_state; board:board_t; cprob:single):single;
var
  res:single;
  num_open,i:integer;
begin
  res:=0;
  num_open:=0;

  for i:=0 to 15 do
    begin
      //tmp:=board shr (4*i);
    if (board shr (4*i) and $f)=0 then
      inc(num_open);
    end;

  cprob:=cprob /num_open;

  for i := 0 to 15 do
    begin
      if ((board shr (4*i)) and $f =0) then
        begin
          res:=res+score_move_node(state,board or (Board_t (1) shl (4*i)),cprob *0.9) *0.9;
          res:=res+score_move_node(state,board or (Board_t (2) shl (4*i)),cprob *0.1) *0.1;
        end;
    end;

  Result:=Res / num_open;
end;

function score_move_node(Var state:t_eval_state;board:board_t; cprob:single):single;
var
  move:integer;
  best:single;
  res:single;
  newboard:board_t;
  //i:IsingleMapIterator;
begin
  if (cprob<state.cprob_thresh) or (state.curdepth >= SEARCH_DEPTH_LIMIT) then
    begin
      if (state.curdepth > state.curdepth) then
        begin
          state.maxdepth:=state.curdepth;
        end;
      Result:=score_heur_board(board);
      exit;
    end;

{   if (state.curdepth < CACHE_DEPTH_LIMIT) then
     begin
//       i:=state.trans_table.Find(board);
       if state.trans_table.Count(board)>0 then
         begin
           inc(state.cachehits,1);
           Result:=state.trans_table.Find(board).Value;
           exit;
         end;

     end;    }
   best:=0;

   inc(state.curdepth);
   for move := 0 to 3 do
     begin
       newboard:=execute_move(move,board);
       inc(state.moves_evaled);
       if (board = newboard) then
         continue;
       res:=score_tilechoose_node(state,newboard,cprob);
       if res > best then
         best:=res;
     end;
   dec(state.curdepth);

   {if state.curdepth < CACHE_DEPTH_LIMIT then
     begin end; //state.trans_table[board]:=best;}

   result:=best;
end;

function t_score_toplevel_move(var state:t_eval_state;board:board_t;move:integer):single;
var
  newboard:board_t;
begin
  newboard:=execute_move(move,board);
  if board=newboard then
    begin
      Result:=0;
      exit;
    end;

  state.cprob_thresh:=CPROB_THRESH_BASE;

  Result:=score_tilechoose_node(state,newboard,1)+1e-6;
end;


function score_toplevel_move(board:board_t; move:integer):single;
var
  res:single;
  start,finish:DWORD;
  state:t_eval_state;
begin

  state.cprob_thresh:=0;
  state.maxdepth:=0;
  state.curdepth:=0;
  state.cachehits:=0;
  state.moves_evaled:=0;

  start:=GetTickCount;
  res:=t_score_toplevel_move(state,board,move);
  finish:=GetTickCount;

  writeln(format('Move %d: result %f: eval %d moves ',
      [move,res,state.moves_evaled])+'in '+IntToStr(Finish-Start)+' ms');
  result:=res;
end;

function find_best_move(board:board_t):integer;
var
   move:integer;
   best:single;
   bestmove:integer;
   res:single;
begin
  best:=0;
  bestmove:=-1;

  print_board(board);
  writeln(format('Current scores: heur %.0f, actual %.0f',[score_heur_board(board),score_board(board)]));
  for move := 0 to 3 do
    begin
      res:=score_toplevel_move(board,move);
      if (res>best) then
        begin
          best:=res;
          bestmove:=move;
        end;
    end;
  Result:=bestmove;
end;

function draw_tile:integer;
begin
  if Random(9)=0 then Result:=2
  else  Result:=1;
end;

function insert_tile_rand(board:board_t;tile:integer):board_t;
var
  num_open:integer;
  i,index:integer;
  tmp:board_t;
begin
  num_open:=0;
  for i := 0 to 15 do
    if (board shr (4*i) and $f)=0 then
      begin
        inc(num_open)
      end;
  if num_open =0 then
    begin
      writeln('insert_tile_rand:no open spots!');
      Result:= board;
      exit;
    end;

  index:=random(num_open);
  for i := 0 to 15 do
    begin
      if ((board shr (4*i))and $f)<>0 then
         continue;
      if (index=0) then
         begin
           tmp:=tile;
           board:=board or (tmp shl (4 *i));
           break;
         end;
      dec(index);
    end;
  Result:=board;
end;

function initial_board:board_t;
var
   board:board_t;
   i:integer;
begin
  randomize;
  board:=0;
  for i := 0 to 1 do
    board:=insert_tile_rand(board,draw_tile);
  Result:=board;
end;

procedure play_game(get_move:get_move_func_t);
var
  board:board_t;
  moveno:integer;
  scorepenalty:integer;
  move:integer;
  newboard:board_t;
  tile:integer;
begin

  board:=initial_board;

  moveno:=0;
  scorepenalty:=0;

  while True do
    begin
      move:=0;
      while move<4 do
        begin
          if execute_move(move,board)<>board then
              break;
          inc(move);
        end;

      if move=4 then break;

      inc(moveno);
      writeln(format('Move %d , current score=%.0f, %d',[moveno,score_board(board)-scorepenalty,scorepenalty]));

      move:=get_move(board);
      if (move < 0) then break;

      newboard:=execute_move(move,board);
      if (newboard=board) then
        begin
          writeln('Illegal move!');
          dec(moveno);
          continue;
        end;

      tile:=draw_tile;
      if tile=2 then inc(scorepenalty,4);
      board:=insert_tile_rand(newboard,tile);
      writeln;
    end;
   print_board(board);
   writeln(Format('Game Over. Your score is %.0f. The hightest rank you achieved was %d.',[score_board(board)-scorepenalty,get_max_rank(board)]));
end;

//Main
begin
  init_move_tables;
  init_score_tables;
  play_game (find_best_move);
  readln;
end.

 

目前上面这个程序可以达到 1024 (超长发挥可以达到2048)

2024

后面会进行的改进:

1.使用整数代替程序中的single进行局面分数的评估。

2.实现nneonneo的剪枝算法,计划使用HouSisong大牛的DGL泛型库 http://blog.csdn.net/housisong/article/category/152693

2014/4/26 修正了盘面分数的问题:

修改之前
res:=res+score_move_node(state,board or 1 shl (4*i),cprob *0.9) *0.9;

修改之后
res:=res+score_move_node(state,board or (Board_t (1) shl (4*i)),cprob *0.9) *0.9;

分离GOP和VBT文件的工具

一个用于分离GOP和VBT文件的工具

这个工具是用来分离Intel Baytrail公版BIOS中的VBT和GOP文件的。其中的GOP是传统意义上的VBIOS,VBT文件是他的配置文件。在之前,配置

信息通常会写入到VBIOS中,但是在BayTrail上他们是分开独立的。

用法:

BTYGVS [文件名]

比如:BTYGVS IFWI_2013_10_01 即可在当前目录下生成这个BIOS中

包括的GOP和VBT。

gvs

下载

bytgvs

LZMA 压缩的例子

前一段时间研究了一下 LZ77 算法,后来又看了一下它的改进版本 LZMA。虽然基本思想已经完全领悟,但是要想具体写出代码还是很有难度,直接研究实现算法又被很多细节阻挡。好在时代不同了,虽然你无法写出具体代码,但是也有现成的库供你调用。

目前开源的比较好用的就是 7-zip 了,这里可以看到中文的介绍:http://sparanoid.com/lab/7z/

同时他还提供了 SDK 供以调用 http://sparanoid.com/lab/7z/sdk.html 。

对于 Delphi 的用户,能够使用的 SDK 在 http://www.birtles.org.uk/programming/ 这里可以下载到。是原生的 Delphi 编写无需第三方库。为了测试我编写了一个Console模式下压缩的小程序,并且在程序中我避免使用这个库内部定义的 Stream 而是直接使用 TMemoryStream。

program LZMAencoder;

{$APPTYPE CONSOLE}

{$R *.res}

uses
System.SysUtils, Windows, Classes,ULZMAEncoder,ULZMACommon;

var
FInput,Foutput:TMemoryStream; //直接使用基础的 MemoryStream
encoder:TLZMAEncoder; //编码器
i:integer; //算法方面的要求,最后要补零。具体请查看LZ77描述
begin
FInput:=TMemoryStream.Create; //创建和读取一个文件作为压缩的源
FInput.LoadFromFile('C:\Users\Administrator\Documents\RAD Studio\Projects\LZMAEncoder\original.txt');

encoder:=TLZMAEncoder.Create;

encoder.SetAlgorithm(2); //设置压缩比,看代码似乎没有真正实现
encoder.SetDictionarySize(1 shl 23); //设置字典大小
encoder.SeNumFastBytes(128); //不知道这个是什么
encoder.SetMatchFinder(1); //找到1个匹配
encoder.SetLcLpPb(3, 0, 2); //应该是LZ77算法用到的几个参数
encoder.SetEndMarkerMode(false); //是否写入流结束标志。因为压缩之后的头上有大小
//所以这里完全不用写入
FOutput:=TMemoryStream.Create;
encoder.WriteCoderProperties(FOutput); //写入头信息
for i := 0 to 7 do
WriteByte(FOutput,(FInput.Size shr (8 * i)) and $FF); //补全完整流

encoder.Code(Finput, FOutput, -1, -1); //解压过程
encoder.free;
FOutput.SaveToFile('C:\Users\Administrator\Documents\RAD Studio\Projects\LZMAEncoder\result.7z');
FOutput.Destroy;
Finput.Destroy;
readln;
end.

压缩结果可以直接用 7-zip 打开(WinZip, WinRar不行)。

Capture

原始的  LZMA 代码 LZMA.442b

本文提到的例子:LZMAEncoder

PuttyTel的辅助工具升级到 V2.1

最近升级了一下之前编写的一款方便串口调试的小工具 http://www.lab-z.com/puttytel%E7%9A%84%E8%BE%85%E5%8A%A9%E5%B7%A5%E5%85%B7/

V2.0 可以自己设置波特率,也可以兼容更多的能接受命令行参数的串口软件。

V2.1 上一个版本会确认当前的命令行参数,对于普通用户来说是多余的。删掉这个重新编译。另外,调整代码将 JediAPILib.inc 放在源程序的目录下。

还有就是多显示一位版本号,之前只能显示大版本,比如:2 修改之后可以显示 2.1 这样的版本号。

screen

SerialPortChooserII2.1

SPCII,SPC2

PuttyTel的辅助工具

工作需要通过串口来进行Debug。之前工作环境是Windows XP,一直使用一款软件(我也不清楚具体应该叫做什么名字,因为我下载时它的名字是“好用的串口工具.exe”),如同他的名字一样,确实很好用。但是令人郁闷的是自从切换到了Windows7 64位系统下,这个工具工作不再正常。于是又开始寻找起来新的工具。要求是:不能是盗版,必须支持 Windows 7 64Bit,灵活最好不需要安装,能够将Log记录到文本文件中,使用上必须简单...........

后来用了一段时间的 Putty 发现功能非常强大。美中不足是每次打开操作复杂,首先,运行之后会弹出询问权限的窗口,其次需要手动输入com号,而我使用的是USB串口,经常因为插拔而导致端口的变化,于是每次还要去设备管理器查看当前的端口,在打开设备管理器还会弹出权限窗口........真的很麻烦。

Putty是开源软件,下载代码后试图修改去掉限制。再后来发现Putty一起发布的有个功能单一的PuttyTel,可以通过命令行方式进行端口的指定和调用,权衡安全性和复杂性最后就编写这个工具软件。所谓安全性,指的是如果我自己编译发布Putty,非常可能在代码中加入后门。如果你是直接使用标准版就不会有此之虞。在这个神奇的xx,上述的担忧绝非杞人忧天,在2012年曾经发生过这样的事情,后面有空的时候我会简单介绍一下。

使用方法非常简单:使用时将这个文件和PuttyTel放在同一个目录下,然后运行之后双击当前端口列表中出现的需要串口即可立即调用起来。

源程序在Source目录下。特别注意这是Delphi XE4 下面Build通过的,并没有使用XE4的新特性,如果你使用其他版本Delphi需要修改对应的头文件。

spc

SerialPortChooser

参考:

1.Putty的官方网站(因为安全问题请不要使用国内的Putty软件,为了方便我在EXE目录下放置了一个官网上下载的PuttyTel,你可以通过比对MD5进行验证)
http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html

2.关于Putty命令行调用方式来自
http://tartarus.org/~simon/putty-snapshots/htmldoc/Chapter3.html
例如:-serial com4 -sercfg 115200,8,n,1,N

如果你用Delphi处理文件发现有乱码,不妨考虑一下编码的问题

最近编写了一个间的的程序,主要的代码是

begin
  AssignFile(rFile,'xyz.csv');
  AssignFile(wFile,'111.txt');
  reset(rFile);
  rewrite(wFile);
  s:='';
  i:=0;
  while NOT eof(rFile) do
    begin
      readln(rFile,s);
      writeln(wFile,s);
    end;
  closefile(rFile);
  closefile(wFile);
end.

 

运行之后发现结果竟然并非预期,这个是原始文件内容

org

这是运行之后的结果

fail

多次查找无法确定原因,后来在想到可能是 unicode导致的问题,找到了下面的文章

http://stackoverflow.com/questions/14232900/unicode-text-file-output-differs-between-xe2-and-delphi-2009

但是因为 Delphi2010 并没有 AssignFile(rFile,'',CP_UTF8); 这样的Override,所以直接升级到XE4.再次运行现象消失问题解决。因此,如果你在读取文本文件时遇到奇怪的无法解释的问题,不妨先考虑是否为 unicode 导致。解决方法上,最简单就是升级Delphi版本,此外还可以使用MemoryStream来自行处理。

根据分区确定硬盘

这一系列文章是根据cutebunny 的BLOG “windows的磁盘操作” 写成的,主要是部分修改原作中的代码,使之兼容Unicode和Windows 7 64bit. 原文可以在下面的网址找到

http://cutebunny.blog.51cto.com 。 本文是参考 “windows的磁盘操作之四——根据逻辑分区号获得物理磁盘号”写成。实现的功能简单的说就是输入 c: ,程序返回这个

盘符是处于 \\physicalDriveX 上的。

// GetPD.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "windows.h"

/******************************************************************************
* Function: get disk's physical number from its drive letter
*           e.g. C-->0 (C: is on disk0)
* input: letter, drive letter
* output: N/A
* return: Succeed, disk number
*         Fail, -1
******************************************************************************/
DWORD GetPhysicalDriveFromPartitionLetter(TCHAR letter)
{
    HANDLE hDevice;               // handle to the drive to be examined
    BOOL result;                 // results flag
    DWORD readed;                   // discard results
    STORAGE_DEVICE_NUMBER number;   //use this to get disk numbers
 
    TCHAR path[MAX_PATH];
    wsprintf(path, L"\\\\.\\%c:", letter);
    hDevice = CreateFile(path, // drive to open
                         GENERIC_READ | GENERIC_WRITE,    // access to the drive
                         FILE_SHARE_READ | FILE_SHARE_WRITE,    //share mode
                         NULL,             // default security attributes
                         OPEN_EXISTING,    // disposition
                         0,                // file attributes
                         NULL);            // do not copy file attribute
    if (hDevice == INVALID_HANDLE_VALUE) // cannot open the drive
    {
        fprintf(stderr, "CreateFile() Error: %ld\n", GetLastError());
        return DWORD(-1);
    }
 
    result = DeviceIoControl(
                hDevice,                // handle to device
                IOCTL_STORAGE_GET_DEVICE_NUMBER, // dwIoControlCode
                NULL,                            // lpInBuffer
                0,                               // nInBufferSize
                &number,           // output buffer
                sizeof(number),         // size of output buffer
                &readed,       // number of bytes returned
                NULL      // OVERLAPPED structure
            );
    if (!result) // fail
    {
        fprintf(stderr, "IOCTL_STORAGE_GET_DEVICE_NUMBER Error: %ld\n", GetLastError());
        (void)CloseHandle(hDevice);
        return (DWORD)-1;
    }
    printf("%d %d %d\n\n", number.DeviceType, number.DeviceNumber, number.PartitionNumber);
 
    (void)CloseHandle(hDevice);
    return number.DeviceNumber;
}


int _tmain(int argc, _TCHAR* argv[])
{
	GetPhysicalDriveFromPartitionLetter('c');	
	getchar();
	return 0;
}

 

运行结果如下 (注意,运行时需要管理员的权限)

lt2phy

其中的 7 是 FILE_DEVICE_DISK, 0 表示 PhysicalDrive0 ,2 表示这个硬盘上有2个分区。

GetPD

参考:

1.cutebunny 的BLOG “windows的磁盘操作” 可以在这里下载 WindowsDisk
2.http://msdn.microsoft.com/en-us/library/windows/desktop/bb968800(v=vs.85).aspx IOCTL_STORAGE_GET_DEVICE_NUMBER control code
3.http://msdn.microsoft.com/en-us/library/windows/desktop/bb968801(v=vs.85).aspx STORAGE_DEVICE_NUMBER structure

使用Indy10实现的简单的 Console下发送邮件

使用Indy10实现的简单的 Console下发送邮件

这个例子使用了 Indy10 通过 SMTP来发送邮件。通过命令行参数来指定文件的内容(Body),并且能够添加附件。

使用方法例如:

scmail /server[smtp.sina.com] /to[405623608@qq.com] /from[zoologist@sina.com] /subject[Hi, buddy] /body[c:\body.txt] /atta[c:\1.bin] /user[zoologist@sina.com] /password[xxxx]

当然,上面的to可以换成你自己的邮箱,from和user 需要填写为你自己的邮箱地址,还有最后的密码也要填写上你自己邮箱的密码(目前免费邮箱发送邮件都要进行身份验证,当然你也可以临时注册一个邮箱)。

运行结果:

scmail

代码和可执行程序

scmail

改动代码提取器

这是一个自动提取改动代码的工作,更准确的说是设计给EDK2代码使用的。比如,你在一套代码中做出了改动,只要改动文件有相应的注释,工具找到这样的注释标记即可将改动后的文件提取到上一层目录中同样的子目录下。程序会扫描如下文件的内容来检测是否有标记信息 *.c;*.h;*.bat;*.xml;*.txt;*.asl;*.inf;*.dsc;*.fdf;*.bat;*.asm;*.uni;*.vfi。

例如:修改的代码位于 c:\working\bkc26\ 下,代码中的改动用 Tag_SC01,Tag_SC02 这样的进行标记后,选择目录,使用Go 按钮进行分析后,找到的修改后的文件会自动拷贝到 c:\working\labz\目录中。这样将会便于分发给需要者而不用进行全部文件的拷贝。如果拷贝过程中出现错误将会在下方的Memo中显示出来。

另外,我现在正在使用 梅捷 SY-D2700-U3M 的主板,固化了Intel D2700 Atom CPU。具有零噪音(无风扇),节能环保,扩展丰富等等特点。唯一的问题是:经常硬盘丢失。具体是在使用中忽然BSOD,然后自动重启之后在Setup中无法找到硬盘,重启无效,必须关机一次再开机。看起来不像是BIOS问题,并且我确定不是硬盘问题,有谁知道这个问题如何解决可以告诉我。谢谢!

gcs

gcs