likes
comments
collection
share

go 项目学习:在 clear 命令前增加吃豆人动画

作者站长头像
站长
· 阅读数 16

最近看到一个 go 的项目,作者重写了 clear 命令,给它加了一个吃豆人的动画

项目地址:paclear

项目一共 170 行左右,非常适合用来练手

那我们来看看作者是怎么做的

此项目用了 4 个第三方库:

  • cobra:解析命令行参数,并执行命令
  • termbox:提供文本命令行界面 api
  • lipgloss:给文本添加颜色
  • clearclear 命令

准确说作者没有重写 clear,而是在 clear 之前添加了一个吃豆人的动画

  1. 添加 paclear 命令
var rootCmd = &cobra.Command{
  Use:   "paclear",
  Short: "paclear is a clear command with pacman animation",
  Run:   paclear,
}
  1. 提供两个参数 -c-s 用来设置吃豆人的颜色和吃豆人动画运动的速度

crobaflags 作用是用来定义参数,解析参数

比如下面的代码就定义了两个参数,-c-s

用户输入 -c yellowyellow 就会绑定到 color 变量上;用户输入 -s 22 就会绑定到 speed 变量上

func init() {
  rootCmd.Flags().StringVarP(&color, "color", "c", "white", "Set pacman color (available: red, green, blue, yellow, pink)")
  rootCmd.Flags().Int32VarP(&speed, "speed", "s", 1, "Set pacman multiple speed (default: 1)")
}
  1. paclear 函数执行

paclear 函数内部分为三部分:

  • 将吃豆人文本设置为 color 颜色,封装了 style 函数

  • 获取命令行的尺寸,封装了 getSize 函数

  • 输出吃豆人动画

    3.1 style 函数

    使用 lipgloss 库为吃豆人文本添加颜色,style.Render(line) 一行一行设置,返回设置好的文本

    func style(color string, lines []string) []string {
      var styled []string
      var style lipgloss.Style
    
      switch color {
      case "red":
        style = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF0000"))
      case "green":
        style = lipgloss.NewStyle().Foreground(lipgloss.Color("#00FF00"))
      case "blue":
        style = lipgloss.NewStyle().Foreground(lipgloss.Color("#0000FF"))
      case "yellow":
        style = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFFF00"))
      case "pink":
        style = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFC0CB"))
      default:
        style = lipgloss.NewStyle()
      }
      for _, line := range lines {
        styled = append(styled, style.Render(line))
      }
      return styled
    }
    

    3.2 getSize 函数

    先初始化一个 termbox,然后获取命令行的尺寸,最后关闭 termbox

    func getSize() (int, int) {
      err := termbox.Init()
      if err != nil {
        log.Fatalf("Error initializing termbox: %v", err)
      }
      defer termbox.Close()
    
      width, height := termbox.Size()
      return height, width
    }
    

    3.3. 执行吃豆人动画

    // 间隔时间
    pitch := time.Duration(20 / time.Duration(speed) * time.Millisecond)
    close := true
    cnt := 0
    // height 是吃豆人的高度
    // rows 是终端的高度,rows - height 如果小于 0 是不会进入循环的
    for y := 0; y <= rows-height; y += height {
      // width 是吃豆人的宽度
      // cols 是屏幕的宽度,cols - width / 3,width / 3 是吃豆人的 1/3,用 cols 去减,得到的值是吃豆人运动的最大范围
      for x := 0; x <= cols-width/3; x++ {
        if close {
          for j, line := range openPac {
            // \033[ 表示开始 ANSI 转义
            // %d;%dH 表示光标移动到第几行第几列
            // %s 表示要打印的字符
            fmt.Printf("\033[%d;%dH%s", y+j+1, x, line)
          }
        } else {
          for j, line := range closePac {
            fmt.Printf("\033[%d;%dH%s", y+j+1, x, line)
          }
        }
        // 每 10 帧刷新切换赤豆的的状态
        cnt++
        if cnt == 10 {
          close = !close
          cnt = 0
        }
        // 休眠运动速度的时间
        time.Sleep(pitch)
        for k := 0; k < height; k++ {
          // 将渲染在终端上的吃豆人清理掉
          fmt.Printf("\033[%d;%dH%s", y+k+1, x, strings.Repeat(" ", width))
        }
      }
    }
    

    3.4 调用 clear 执行清理

  1. 退出终端
func Execute() {
  if err := rootCmd.Execute(); err != nil {
    os.Exit(1)
  }
}

这个项目看着很简单,非常适合新手拿来练习,可以快速掌握 go 终端开发的相关知识,比如:

  • 终端相关的工具
  • 在终端输出相应的文本
  • 终端配色方案