AHK游戏系列:俄罗斯方块

之前用ahk写过一个贪吃蛇小游戏,最近摸鱼,又整了个俄罗斯方块,欢迎试玩,但是我不会改。

AHK版本:V2 beta3 U64

AHK游戏系列:俄罗斯方块

; AHKVERSION: V2.0 beat3 U64
t := Tetris()
t.Startup()

Hotkey("UP", (*) => t.transform_current_block())
Hotkey("Down", (*) => t.move_down())
Hotkey("Left", (*) => t.move_left())
Hotkey("Right", (*) => t.move_right())
OnExit((*) => t.Close())


Class Tetris
{
  current_block := []                                                                 ; 当前形状
  next_block    := 0                                                                  ; 下一个形状
  stop_blocks   := []                                                                 ; 停止运动的砖块
  width         := 10                                                                 ; 游戏界面宽度(格子数)
  height        := 20                                                                 ; 游戏界面高度(格子数)
  linewidth     := 1                                                                  ; 网格线宽度
  blockwidth    := 30                                                                 ; 方框大小
  width_px      := this.width * this.blockwidth + (1 + this.width) * this.linewidth   ; 游戏区域宽度
  height_px     := this.height * this.blockwidth + (1 + this.height) * this.linewidth ; 游戏区域高度
  blocks        := [[[[0,1,1],[1,1,0],[0,0,0]],[[1,0,0],[1,1,0],[0,1,0]]],[[[1,1,0],[0,1,1],[0,0,0]],[[0,1,0],[1,1,0],[1,0,0]]],[[[1,0,0],[1,0,0],[1,0,0],[1,0,0]],[[0,0,0,0],[0,0,0,0],[1,1,1,1],[0,0,0,0]]],[[[0,1,1],[0,1,1]]],[[[1,0,0],[1,1,1],[0,0,0]],[[0,1,1],[0,1,0],[0,1,0]],[[0,0,0],[1,1,1],[0,0,1]],[[0,1,0],[0,1,0],[1,1,0]]],[[[0,0,1],[1,1,1],[0,0,0]],[[0,1,0],[0,1,0],[0,1,1]],[[0,0,0],[1,1,1],[1,0,0]],[[1,1,0],[0,1,0],[0,1,0]]],[[[0,1,0],[1,1,1],[0,0,0]],[[0,1,0],[0,1,1],[0,1,0]],[[0,0,0],[1,1,1],[0,1,0]],[[0,1,0],[1,1,0],[0,1,0]]]]
  
  ; 启动入口
  Startup() {
    ; 装载gdiplus.ll提高性能
    this.hGdipModule := DllCall("LoadLibrary", "Str", "gdiplus")
    ; 启动gdip
    si := Buffer(A_PtrSize = 8 ? 24 : 16, 0), NumPut("UInt", 1, si, 0)
    DllCall("gdiplus\GdiplusStartup", "Ptr*", &pToken := 0, "Ptr", si, "Ptr", 0), this.pGdipToken := pToken
    ; 创建需要用到的画布和笔刷
    bi := Buffer(40, 0)
    NumPut("Uint", 40, "Uint", Integer(this.width_px), "Uint", Integer(this.height_px), "ushort", 1, "ushort", 32, "uInt", 0, bi, 0)
    this.hbm := DllCall("CreateDIBSection", "Ptr", 0, "Ptr", bi, "Uint", 0, "Ptr*", 0, "Ptr", 0, "Uint", 0, "Ptr")
    this.sdc := DllCall("CreateCompatibleDC", "Ptr", 0, "ptr")
    DllCall("SelectObject", "Ptr", this.sdc, "Ptr", this.hbm, "ptr")
    DllCall("gdiplus\GdipCreateFromHDC", "Ptr", this.sdc, "Ptr*", &G := 0), this.Graphics := G

    NumPut("Uint", 40, "Uint", Integer(this.blockwidth * 4), "Uint", Integer(this.blockwidth * 4), "ushort", 1, "ushort", 32, "uInt", 0, bi, 0)
    this.hbm2 := DllCall("CreateDIBSection", "Ptr", 0, "Ptr", bi, "Uint", 0, "Ptr*", 0, "Ptr", 0, "Uint", 0, "Ptr")
    this.sdc2 := DllCall("CreateCompatibleDC", "Ptr", 0, "ptr")
    DllCall("SelectObject", "Ptr", this.sdc2, "Ptr", this.hbm2, "ptr")
    DllCall("gdiplus\GdipCreateFromHDC", "Ptr", this.sdc2, "Ptr*", &G2 := 0), this.Graphics2 := G2

    DllCall("gdiplus\GdipCreateSolidFill", "UInt", 0xFFDDEDFF, "Ptr*", &pBrush1 := 0), this.pBrush1 := pBrush1
    DllCall("gdiplus\GdipCreateSolidFill", "UInt", 0xFFFFAAAA, "Ptr*", &pBrush2 := 0), this.pBrush2 := pBrush2 
    DllCall("gdiplus\GdipCreateSolidFill", "UInt", 0xFFFF5555, "Ptr*", &pBrush3 := 0), this.pBrush3 := pBrush3 
    DllCall("gdiplus\GdipCreateSolidFill", "UInt", 0xFFF0F0F0, "Ptr*", &pBrush4 := 0), this.pBrush4 := pBrush4 
    DllCall("gdiplus\GdipCreatePen1", "UInt", 0xFFCCCCCC, "float", this.linewidth, "int", 2, "Ptr*", &pPen := 0), this.pPen := pPen
    ; 获取第一个砖块
    this.get_current_block()
    ; 创建GUi
    this.CreateMainWindow()
    ; 绘制游戏界面
    this.Draw()
    ; 将网格保存到数组,便于游戏中各种判断
    this.blank_line := []
    loop(this.width)
      this.blank_line.push(0)
    this.Reset()
    ; 开始游戏
    this.timer := ObjBindMethod(this, "move_down")
    SetTimer(this.timer, 500)
  }

  Reset(){
    this.stop_blocks := []
    loop(this.height)
      this.stop_blocks.push(this.blank_line.clone())
  }


  ; 绘制游戏主界面
  Draw(){
    ; 画底色和网格
    DllCall("gdiplus\GdipFillRectangle" , "Ptr", this.Graphics, "Ptr", this.pBrush1 , "float", 0, "float", 0, "float", this.width_px, "float", this.height_px)
    loop(11){
      x := (A_Index - 1) * (this.blockwidth + this.linewidth)
      DllCall("gdiplus\GdipDrawLine" , "Ptr", this.Graphics, "Ptr", this.pPen , "float", x, "float", 0, "float", x, "float", this.height_px)
    }
    loop(21){
      y := (A_Index - 1) * (this.blockwidth + this.linewidth)
      DllCall("gdiplus\GdipDrawLine" , "Ptr", this.Graphics, "Ptr", this.pPen , "float", 0, "float", y, "float", this.width_px, "float", y)
    }
    ; 绘制当前砖块
    for(line in this.current_block){
      line_num := A_Index - 1
      for(c in line){
        if(!c)
          continue  
        x := (this.linewidth + this.blockwidth) * (A_Index - 1 + this.current_block_start_col) + this.linewidth
        y := (this.linewidth + this.blockwidth) * (line_num  + this.current_block_start_row) + this.linewidth
        w := this.blockwidth
        DllCall("gdiplus\GdipFillRectangle" , "Ptr", this.Graphics, "Ptr", this.pBrush2 , "float", x, "float", y, "float", w, "float", w)
    }}
    ; 绘制已经停止的砖块
    for(line in this.stop_blocks){
      line_num := A_Index - 1
      for(c in line){
        if(!c)
          continue
        x := (this.linewidth + this.blockwidth) * (A_Index - 1) + this.linewidth
        y := (this.linewidth + this.blockwidth) * (line_num) + this.linewidth
        w := this.blockwidth
        DllCall("gdiplus\GdipFillRectangle" , "Ptr", this.Graphics, "Ptr", this.pBrush3 , "float", x, "float", y, "float", w, "float", w)
    }}

    ; 绘制下一个砖块(提示)
    DllCall("gdiplus\GdipFillRectangle" , "Ptr", this.Graphics2, "Ptr", this.pBrush4 , "float", 0, "float", 0, "float", this.width_px, "float", this.height_px)
    for(line in this.next_block){
      line_num := A_Index
      for(c in line){
        if(c == 0)
          continue
          x := (A_Index - 1) * (this.blockwidth+1)
          y := (line_num - 1) * (this.blockwidth + 1)
          DllCall("gdiplus\GdipFillRectangle" , "Ptr", this.Graphics2, "Ptr", this.pBrush3 , "float", x, "float", y, "float", this.blockwidth, "float", this.blockwidth)
      }
    }
    ; 更新,上面只是在内存中绘制,现在更新到屏幕上
    this.ddc := DllCall("GetDC", "Ptr", this.win.Hwnd, "ptr")
    DllCall("gdi32\BitBlt", "Ptr", this.ddc, "int", 5, "int", 5, "int", this.width_px, "int", this.height_px , "Ptr", this.sdc, "int", 0, "int", 0, "Uint", 0x00CC0020)
    DllCall("gdi32\BitBlt", "Ptr", this.ddc, "int", this.width_px + 10, "int", 150, "int", this.blockwidth * 4, "int", this.blockwidth * 4 , "Ptr", this.sdc2, "int", 0, "int", 0, "Uint", 0x00CC0020)
  }
 
  ; 创建窗口
  CreateMainWindow(){
    this.win := Gui("", title := "俄罗斯方块")
    this.win.Show(format("w{} h{}", this.width_px * 1.5, this.height_px + 10))
    this.win.SetFont("S17")
    this.score_control := this.win.Add("Text", format("x{} y60", this.width_px + 20), "0")
    this.win.OnEvent("Close", ExitAPP)
  } 

  ; 从定义好的形状中随机抽取
  get_block(){
    x := random(1, this.blocks.length)
    y := random(1, this.blocks[x].length)
    return this.blocks[x][y]
  }

  ; 获取新砖块或者下一个砖块
  get_current_block(){
    if(!this.next_block)
      this.current_block := this.get_block()
    else 
      this.current_block := this.next_block
    this.next_block := this.get_block()
    this.current_block_start_row := -2
    this.current_block_start_col := 3
  }

  ; 砖块向左移动
  move_left(){
    if(!this.judge_move_left())
      return
    this.current_block_start_col -= 1
    this.Draw()
  }

  ; 砖块向右移动
  move_right(){
    if(!this.judge_move_right())
      return
    this.current_block_start_col += 1
    this.Draw()
  }

  ; 砖块向下移动
  move_down(){
    if(this.judge_move_down()){
      this.current_block_start_row += 1
    } else {
      this.update_stop_blocks()
      this.get_current_block()
      this.judge_lines()
    }
    this.Draw()
  }

  ; 变换(旋转)砖块
  transform_current_block(){
    for(line in this.blocks){
      for(c  in line){
        ; 因为cur_block是blocks里面抽取的一个元素,所以可以用等号判断,如果是自己重新构造的数组,哪怕一模一样也不相等
        if(c == this.current_block){ 
          block_style_list := line
          i := A_Index
          break 2
    }}}
    i++
    i := i > block_style_list.length ? 1 : i
    if(!this.judge_new_block(block_style_list[i]))
      return
    this.current_block := block_style_list[i]
    this.Draw()
  }

  ; 更新已停止的砖块列表
  update_stop_blocks(){
    ; 判断游戏是否失败,并重新开始
    if(this.current_block_start_row < 0){
      SetTimer(this.timer, 0)
      MsgBox("哈哈你挂了! 分数:" .  this.score_control.Text)
    this.score_control.Text := 0
      this.Reset()
      SetTimer(this.timer, 500)
      return 
    }
    for(line in this.current_block){
      line_num := A_Index
      for(c in line){
        if(c == 0)
          continue
        this.stop_blocks[this.current_block_start_row + line_num][this.current_block_start_col + A_Index] := 1
  }}}

  ; 判断是否继续下落
  judge_move_down(){
    for(line in this.current_block){
      line_num := A_Index
      for(c in line){
        if(c == 0)
          continue
        x := A_Index + this.current_block_start_col
        y := line_num + this.current_block_start_row + 1
        if(y > this.height)
          return False
        if( y >= 1 && this.stop_blocks[y][x] == 1)
          return False
    }}
    return True
  }

  ; 判断是否可以向左移动
  judge_move_left(){
    for(line in this.current_block){
      line_num := A_Index
      for(c in line){
        if(c == 0)
          continue
        x := A_Index + this.current_block_start_col - 1
        y := line_num + this.current_block_start_row
        if(x < 1 || (y >= 1 && this.stop_blocks[y][x] == 1))
          return False
    }}
    return True
  }

  ; 判断是否可以向右移动
  judge_move_right(){
    for(line in this.current_block){
      line_num := A_Index
      for(c in line){
        if(c == 0)
          continue
        x := A_Index + this.current_block_start_col + 1
        y := line_num + this.current_block_start_row
        if(x > this.width || (y >= 1 && this.stop_blocks[y][x] == 1))
          return False
    }}
    return True
  }

  ; 判断新砖块是否与边框或已经停止的砖块冲突
  judge_new_block(block){
    for(line in block){
      line_num := A_Index
      for(c in line){
        if(c == 0)
          continue
        x := A_Index + this.current_block_start_col
        y := line_num + this.current_block_start_row
        if(x < 1 || x > this.width || y > this.height || (y >= 1 && this.stop_blocks[y][x] == 1))
          return False
    }}
    return True
  }

 ; 判断是否可消除
  judge_lines(){
    i := 1
    for(line in this.stop_blocks){
      line_num := A_Index
      for(c in line){
        if(c == 0)
          continue 2
      }
    this.stop_blocks.RemoveAt(line_num)  
    this.stop_blocks.InsertAt(1, this.blank_line.Clone())
    this.score_control.text += i
    i++
  }}

  Close() {
    DllCall("gdiplus\GdipDeletePen", "Ptr", this.pPen)
    DllCall("gdiplus\GdipDeleteBrush", "Ptr", this.pBrush1)
    DllCall("gdiplus\GdipDeleteBrush", "Ptr", this.pBrush2)
    DllCall("DeleteObject", "Ptr", this.hbm)
    DllCall("DeleteObject", "Ptr", this.sdc)
    DllCall("DeleteObject", "Ptr", this.Graphics)
    DllCall("DeleteObject", "Ptr", this.hbm2)
    DllCall("DeleteObject", "Ptr", this.sdc2)
    DllCall("DeleteObject", "Ptr", this.Graphics2)
    DllCall("ReleaseDC", "Ptr", 0, "Ptr", this.ddc)
    DllCall("FreeLibrary", "Ptr", this.hGdipModule)
    DllCall("gdiplus\GdiplusShutdown", "Ptr", this.pGdipToken)
  }
}

printarr(a){
  s := ""
  for(line in a){
    for(c in line){
      s .= c
    }
    s .= '`r'
  }
  msgbox s
}

给TA捐赠
共{{data.count}}人
人已捐赠
应用

AHK VSCODE 插件推荐

2022-6-5 18:41:00

应用案例

ahk悬浮粘贴板内容在桌面,实现复制

2022-6-17 19:43:15

8 条回复 A文章作者 M管理员
  1. AHK中文社区
    1河许人给您捐赠了¥3
  2. dbgba
    dbgba给您捐赠了¥5
  3. 陌诺Mono

    牛喔

  4. 陌诺Mono
    陌诺Mono给您捐赠了¥5
  5. 园丁二号

    学习下

  6. 慢速爬行中

    厉害,学习学习

  7. james831124

    学习一下,是通过辨识颜色吗?

    • chn.fwt

      不是的,用数组保存了停留的方块。然后根据数组来判断消除和碰撞之类的

个人中心
购物车
优惠劵
有新私信 私信列表
搜索