🐀

Go版slコマンド「og」を作った

2024/01/05に公開

作ったもの

ターミナルでgoを間違えてogと打ったときに、逆さまのGopherくんが空から降ってくるコマンドを作りました。
slコマンドはターミナル上に蒸気機関車が表示されますが、このコマンドは画面上にGopherくんが表示されます。

https://github.com/hamao0820/og

環境

  • MacOS: バージョン14.2.1(23C71)
  • チップ: Apple M1
go version go1.21.5 darwin/arm64

インストール

$ git clone https://github.com/hamao0820/og.git
$ cd og
$ go install

cgoの関係で、GoReleaserがうまく使えませんでした。
何かわかる方はコメントで教えていただきたいです。

試したこと

以下の記事を参考にしました。
https://zenn.dev/kou_pg_0131/articles/goreleaser-usage

.goreleaser.yaml
builds:
  - env:
-     - CGO_ENABLED=0
+     - CGO_ENABLED=1
    goos:
-     - linux
      - windows
      - darwin

とすると、

goreleaser release --snapshot --rm-dist

は動くのですが、GitHub上では

⨯ release failed after 32s                 error=failed to build for darwin_amd64_v1: exit status 1: # runtime/cgo
clang: error: argument unused during compilation: '-arch x86_64' [-Werror,-Wunused-command-line-argument]

というエラーが発生します。

使い方

og

Macで実行すると、ターミナルがフルスクリーンの場合はうまく表示されないので、注意してください。

実装

https://github.com/hamao0820/og

Gopherくんの表示には、Ebitengineを使用しています。

main.goの全文を載せておきます。

コード
main.go
package main

import (
	"bytes"
	"embed"
	"image"
	_ "image/png"
	"log"
	"math"
	"math/rand"
	"os"

	"github.com/hajimehoshi/ebiten/v2"
)

//go:embed images/*
var fs embed.FS

type Game struct {
	image *ebiten.Image
	x, y  int
}

const (
	width  = 370
	height = 320
)

var (
	screenwidth, screenHeight = ebiten.ScreenSizeInFullscreen()
)

func NewGame() (*Game, error) {
	names := []string{"cry", "faint", "scare"}
	name := names[rand.Intn(len(names))]
	b, err := fs.ReadFile("images/" + name + ".png")
	if err != nil {
		return nil, err
	}
	img, _, err := image.Decode(bytes.NewReader(b))
	if err != nil {
		return nil, err
	}

	reversed := ebiten.NewImage(width, height)

	// Rotate 180 degrees
	op := &ebiten.DrawImageOptions{}
	op.GeoM.Rotate(math.Pi)
	op.GeoM.Translate(width, height)

	reversed.DrawImage(ebiten.NewImageFromImage(img), op)

	return &Game{
		image: reversed,
		x:     screenwidth/2 - width/2,
		y:     -height,
	}, nil
}

func (g *Game) Update() error {
	g.y += 10
	ebiten.SetWindowPosition(g.x, g.y)

	if g.y > screenHeight {
		// 画面の外に出たら終了
		os.Exit(0)
	}
	return nil
}

func (g *Game) Draw(screen *ebiten.Image) {
	op := &ebiten.DrawImageOptions{}
	if g.y < 0 {
		op.GeoM.Translate(0, float64(g.y))
	}
	screen.DrawImage(g.image, op)
}

func (g *Game) Layout(w, h int) (int, int) {
	return width, height
}

func main() {
	game, err := NewGame()
	if err != nil {
		log.Fatal(err)
	}
	ebiten.SetWindowSize(width, height)
	ebiten.SetWindowDecorated(false)
	ebiten.SetWindowMousePassthrough(true)
	ebiten.SetWindowFloating(true)
	ebiten.SetWindowClosingHandled(true)

	op := &ebiten.RunGameOptions{}
	op.ScreenTransparent = true
	if err := ebiten.RunGameWithOptions(game, op); err != nil {
		log.Fatal(err)
	}
}

ライセンス

今回使用した画像は、tenntennさんが作成したものです。

https://github.com/tenntenn/gopher-stickers

オリジナルのThe Go gopher(Gopherくん)は、Renée Frenchによってデザインされました。

まとめ

かなりシンプルな実装になりました。
slコマンドと違って、signalまわりの実装はしていません。

リリースについてわかる方がいましたら教えていただけるとありがたいです。

参考記事

https://qiita.com/ramenjuniti/items/fd502b4870f8e4ea87bb

https://zenn.dev/kou_pg_0131/articles/goreleaser-usage

Discussion