ドキドキするとき無敵でしょ

映画とプログラミングの話

OOC2024 当日スタッフ参加してきた

最近全然ブログが書けていなかった Gunzi です。OOC の前に投稿しようと思っていたブログが思ったより重くて間に合わなかった..

というわけで OOC に当日スタッフ参加してきたのでその備忘録を忘れないうちにまとめておく。

今回は事前準備、前夜祭、本編のフル参加をしてきた。 今回は友人を一人スタッフに誘っていたのだが、知り合いがいっぱいいたので全然アウェー感がなかった。 https://ooc.connpass.com/event/305258/ https://ooc.connpass.com/event/305241/

事前準備はいつもの蟹工船をしつつ、他のスタッフと談笑していた。Emacsユーザーを2人見つけることができたのでとても良かった。

前夜祭では早めに集まってスタッフ業務の説明を受けたり、前夜祭中にスタッフたちと談笑して明日の準備をしていた。

本編では8:45にお茶女に集合し、スタッフ業務をしながらカンファレンスに参加した。スタッフの朝は早い____

本編の感想

興奮してあまり眠れなかったので、受付業務でだいぶ疲れてしまい、スポンサー全部を回るのと、セッションを1つ聞いて終わってしまいもったいなかったなぁ…となってしまった。 あとから X で他の人のツイートを見たり、スライドを見ながらリアルタイムで聞かなかったことをかなり後悔した。次のスタッフは興奮せずにしっかり寝たい。 一緒に参加していたスタッフと、動画が公開されたら感想戦をしながら見よう、という話をしたのでそこでキャッチアップする。

セッションやスライドで自分が全然できてないポイントをいくつも見つけられたのと、体感でも勉強必要な部分だったので、モチベーションを得ることができた。 また、スポンサーブースでも普段聞けないような話を聞くことができてとても面白かった。ブース毎に色々取り組みが違っていて面白かった。
勘違いしてクイズのがしてしまったりもあったので、次はしっかり話を聞こう...

オフラインの一体感最高すぎたので、また機会を見つけてスタッフをやるぞ〜となった。こういうところで圧倒されながらモチベーションを獲得するのはとても気持ちがいい。維持していきたい。

P.S. お茶女のクロちゃんがすごいかわいい。

flannelのcni-pluginを読む

Windowsを起動すると4、5分でブルースクリーン、Gunziです。とてもつらい。

お盆休み中にプロトコル・スタック自作をしてだいぶモチベーションが回復したので、
しばらくk8sのネットワーク部分の探検をしようと思い立ち、とりあえずcni-pluginがよくわからんので、作ってみることにした。

色々やってみた結果、よくわからなかったので、よく聞くflannelのソースを読んで他のCNIプラグインがどのように動作しているのか?
をまずは調べつつ、自作CNIプラグインチャレンジをすることにした。

ただ、前提知識がやたらに多く、説明が抜けていたり若干間違えている可能性が高いので、ご了承いただきたい。

とりあえず手を動かす

どうやらShellで自作した人がいるようなので、自宅にk8sの環境を構築してやってみた。

www.altoros.com

ノード間の通信はうまくいかなかったが単一ノード内の通信はうまくできた。
どうやら入力自体は標準入力で受け取る関係上、理論上はどんな言語でも構わないようだ。

コードを読むとどうやらpodに仮想ニックを刺して作成したネットワーク名前空間に接続しているようだった。何をして動いてるかはなんとなく掴めた。

CNIプラグインとは

というわけでこいつがなんなのかについての話。

公式ドキュメントはこちら。

Network Plugins

よくわからん…
色々な資料やソースコードを読み漁ったところ、コンテナのネットワークを作成・削除するための仕組みらしい。ContainerNetworkingInterfaceだもん、そりゃそうか。

いい感じにk8sネットワークの仕組みの全体を解説している日本語資料があったので、ググりつつ、こちらで全体像を学ばせてもらった。見切り発車で始めたので、概要を掴むのにとても良かった。

speakerdeck.com

cni-plugin は仕様により実装するコマンドが決まっている。これはcni-pluginの仕様書で、この仕様書にあるCNI operationsの項目が実装する必要のあるコマンドになる。

仕様書によれば

  • ADD
  • DEL
  • CHECK

の3つを実装する必要がある。ここからはflannelのcni-pluginにある、ADD,DELCHECKに対応しているコマンドの部分を読んでいこうと思う。

なぜflannelかというと、有名なイメージが強く、システム自体がとてもシンプルなので参考にするのに向いていると思ったからだ。クラスターネットワークの仕組みもシンプルなので、実際に作成する際には参考にする。

実装を読んでみる

というわけでflannelのcni-pluginのソースコードを実際に読んでみた。 読んで思ったことや、処理についてコメントを思い思いに残しているので、いい感じにみなさんも読みとっていただければと思う。

github.com

cmdAdd()

cmdAdd()はコンテナをネットワークに追加する。
flannelではcmdAdd()→doCmdAdd→delegateCmdAddの順で処理している。
delegateCmdAddではinvoke.DelegateAddを呼び出している。
パッケージに説明があり、この関数はCNI ADD、もしくはJSONコンフィグを使用して指定されたdelegate pluginを呼び出している。
デフォルトではブリッジプラグインのため、構成時に指定されたブリッジプラグインのADDコマンドを実行している。
”bridge”以外が指定されていた場合はそれらを呼び出す。はず。多分。

delegateAddの名前の通り、最後にinvoke.DelegateAddを呼び、ブリッジプラグインのADDを実行している。
invoke package - github.com/containernetworking/cni/pkg/invoke - Go Packages

func cmdAdd(args *skel.CmdArgs) error {
// loadFlannelNetConf
// 標準入力でNetConfを受取り、ロードしてJSONをアンマーシャルする
    n, err := loadFlannelNetConf(args.StdinData)
    if err != nil {
        return fmt.Errorf("loadFlannelNetConf failed: %w", err)
    }

// /run/flannel/subnet.env からネットワーク構成を読み取る
    fenv, err := loadFlannelSubnetEnv(n.SubnetFile)
    if err != nil {
        return fmt.Errorf("loadFlannelSubnetEnv failed: %w", err)
    }

// delegateの値はflannelプラグインはデフォルトでブリッジプラグインに移譲
// 追加の設定値をブリッジプラグインに渡す必要がある場合はDelegateフィールドを利用
    if n.Delegate == nil {
        n.Delegate = make(map[string]interface{})
    } else {
// それぞれDelegateマップにキーが存在するかをチェックしている
// typeが存在するかつ値が文字列でない
        if hasKey(n.Delegate, "type") && !isString(n.Delegate["type"]) {
            return fmt.Errorf("'delegate' dictionary, if present, must have (string) 'type' field")
        }
// nameが存在する
        if hasKey(n.Delegate, "name") {
            return fmt.Errorf("'delegate' dictionary must not have 'name' field, it'll be set by flannel")
        }
// ipamが存在する
        if hasKey(n.Delegate, "ipam") {
            return fmt.Errorf("'delegate' dictionary must not have 'ipam' field, it'll be set by flannel")
        }
    }

// runtimeConfigはomitemptyになっているため、空のときはスキップされる
    if n.RuntimeConfig != nil {
        n.Delegate["runtimeConfig"] = n.RuntimeConfig
    }

    return doCmdAdd(args, n, fenv)
}

// doCmdAddの実装
func doCmdAdd(args *skel.CmdArgs, n *NetConf, fenv *subnetEnv) error {
// テストだとcni-flannelなどを渡している
    n.Delegate["name"] = n.Name

// キーが存在しない場合はデフォルトでブリッジプラグインを選択
    if !hasKey(n.Delegate, "type") {
        n.Delegate["type"] = "bridge"
    }

// 
    if !hasKey(n.Delegate, "ipMasq") {
        // if flannel is not doing ipmasq, we should
        // subnetEnv構造体のipmasqを取得し(flannelの設定のデフォルトではTrueっぽい)、反転させて代入
        ipmasq := !*fenv.ipmasq
        n.Delegate["ipMasq"] = ipmasq
    }

// subnetEnv構造体の値を参照して代入
    if !hasKey(n.Delegate, "mtu") {
        mtu := fenv.mtu
        n.Delegate["mtu"] = mtu
    }

// ブリッジタイプが指定されている場合はisGatewayをtrueにする
    if n.Delegate["type"].(string) == "bridge" {
        if !hasKey(n.Delegate, "isGateway") {
            n.Delegate["isGateway"] = true
        }
    }

// CNIVersionが0でなければDelegateにも同様に設定
    if n.CNIVersion != "" {
        n.Delegate["cniVersion"] = n.CNIVersion
    }

// netconf構造体にipamの値が存在すれば入力の値を使用し、置換もしくは補完する
    ipam, err := getDelegateIPAM(n, fenv)
    if err != nil {
        return fmt.Errorf("failed to assemble Delegate IPAM: %w", err)
    }
    n.Delegate["ipam"] = ipam
    fmt.Fprintf(os.Stderr, "\n%#v\n", n.Delegate)

    return delegateAdd(args.ContainerID, n.DataDir, n.Delegate)
}

func delegateAdd(cid, dataDir string, netconf map[string]interface{}) error {
// netconfのマーシャル
    netconfBytes, err := json.Marshal(netconf)
    fmt.Fprintf(os.Stderr, "delegateAdd: netconf sent to delegate plugin:\n")
    os.Stderr.Write(netconfBytes)
    if err != nil {
        return fmt.Errorf("error serializing delegate netconf: %v", err)
    }

// cmdDel用に一時NetConfの保存
    // save the rendered netconf for cmdDel
    if err = saveScratchNetConf(cid, dataDir, netconfBytes); err != nil {
        return err
    }

// 指定されたプラグインでADDを実行する
    result, err := invoke.DelegateAdd(context.TODO(), netconf["type"].(string), netconfBytes, nil)
    if err != nil {
        err = fmt.Errorf("failed to delegate add: %w", err)
        return err
    }
    return result.Print()
}

cmdDel()

ADDと同じ引数を渡し、コンテナを削除する。
最後にdelegateDelを呼び出してブリッジインターフェースを削除している。
doCmdAdd()でsaveScratchNetConf()を呼び、一時的に保存したデータを削除するところまでがワンセット。

func cmdDel(args *skel.CmdArgs) error {
// 標準入出力から値を読み込みパースする
    nc, err := loadFlannelNetConf(args.StdinData)
    if err != nil {
        return err
    }

// runtimeConfigをロードする
    if nc.RuntimeConfig != nil {
// nc.Delegateの値を代入する必要があるのでmakeし領域を確保
        if nc.Delegate == nil {
            nc.Delegate = make(map[string]interface{})
        }
        nc.Delegate["runtimeConfig"] = nc.RuntimeConfig
    }

    return doCmdDel(args, nc)
}

// saveScratchNetConf で保存したファイルをロードして利用
func consumeScratchNetConf(containerID, dataDir string) (func(error), []byte, error) {
    path := filepath.Join(dataDir, containerID)

    // cleanup will do clean job when no error happens in consuming/using process
    cleanup := func(err error) {
        if err == nil {
            // Ignore errors when removing - Per spec safe to continue during DEL
            _ = os.Remove(path)
        }
    }
    netConfBytes, err := os.ReadFile(path)

    return cleanup, netConfBytes, err
}

func doCmdDel(args *skel.CmdArgs, n *NetConf) error {
    cleanup, netConfBytes, err := consumeScratchNetConf(args.ContainerID, n.DataDir)
    if err != nil {
        if os.IsNotExist(err) {
            // Per spec should ignore error if resources are missing / already removed
            return nil
        }
        return err
    }

// deferなので最後にerrの発生がなければクリーンアップ(保存されているファイルの削除)が実行される
    // cleanup will work when no error happens
    defer func() {
        cleanup(err)
    }()

// 保存されているファイルを読み込み
    nc := &types.NetConf{}
    if err = json.Unmarshal(netConfBytes, nc); err != nil {
        // Interface will remain in the bridge but will be removed when rebooting the node
        fmt.Fprintf(os.Stderr, "failed to parse netconf: %v", err)
        return nil
    }

// 
    return invoke.DelegateDel(context.TODO(), nc.Type, netConfBytes, nil)
}

cmdCheck()

func cmdCheck(args *skel.CmdArgs) error {
    // TODO: implement
    return nil
}

まとめてきなやつ

あまりにもわからないのでとりあえず他のCNIプラグイン調べるか…と思い、flannel-io/cni-plugin を読んで正解だった。かなり理解が進んだ。
containernetworkingのパッケージにあるinvokeをなぜ呼び出しているのか?について調べたところ、どうやらインターフェースの作成をしてくれるものだったらしい。

www.cni.dev

flannelは前段で設計ファイルのパース→バリデーションを行い、実行時に問題のない形式にしている、ということが理解できた。
参考にしつつ、小さいCNIプラグインをまずは作ってみようと思う。

余談だが、k8sが出た当初、ネットワークはここまで自由ではなかったらしい。

終わりに

k8sは軽く勉強はしたが、実際にコアな部分(といっていいかはわからないが)に近いところに触れることができてとても面白い。
k8sのネットワークはどうやらプラガブルにごちょごちょできる、ということを知っていきなり触り始めたので、だいぶわからないところが多くてとても良い。
ただ、CNIプラグインを自作してる人がなかなかおらず、ブログもほとんど見つからなかった。クラスターネットワークの自作は…と思ったけど流石にあまりいなさそうな気もする。
ともあれ、仕様書、OSSになっている各種CNIプラグインソースコードといった、ナレッジとドキュメントがある。
読めばどうとでもなるので、脳筋でいけそう!ということがわかった。ひとまず続けてみる。

はじめまして!若手エンジニアふんわりLT Night!でLTしてきた話

一人暮らし、開始から1年と6ヶ月、初めてマットレスを購入したGunziです。届くのが楽しみだ。

「ブログ書いてLTしてきたんすよ~」って話をしたところ、いつものごとくおかのさんからブログを書くネタができたね、と言われたので 「できらぁ!!!!!!」と啖呵切ったので書きます。

というわけで今回はこちらのイベントでLTしてきました。

wakate-funwari-study.connpass.com

作成した資料はこちら

docs.google.com

資料の元にしたのは前回書いたブログです。

fxxkmovie.hatenadiary.jp

感想

当日は仕事が終わらなくてなんとか会場に向かったら一番最後の人が終わったギリギリに着いて、なんとか発表に間に合いました。あまりにもギリギリすぎてギリギリchopでした。
正直マニアックだし、そんな刺さらんやろな~~~、って思ったんですがちゃんとウケたので安心しました。
LT終了後の懇親会で知ったんですが、SRE、インフラ、ネットワークな人達がちょこちょこいて終わった後にわちゃわちゃ話せてよかったです。

やはりリアルイベントはいろいろな友達ができて楽しいですね。また次回も枠あったら行きます。

お盆なのでTCP/IPプロトコルスタックを自作した

はじめに

お盆はTCP/IPプロトコルスタックを自作し、夜は配信され始めたコワすぎ!シリーズを見て過ごしていたらお盆が終わりました、Gunziです。
今年はお盆休み中にKLabExpartCampのTCP/IPプロトコルスタックの実装にチャレンジしたので、その備忘録をまとめます。完成したものはこちら。
github.com

資料はこちらから見られます。

KLab Expert Camp(資料公開) - Google Drive

経緯

決して「詳解TCP/IP vol.2 実装」を完走すると貰えるからやったわけではありません!!!!ものにつられるわけないじゃないですか!!!!

閑話休題。今から2年ほど前に1度チャレンジしていて、その時はRFC見ながらひとりでやるぞ!!!ということでできるところまでやってみました。 ちなみにARPちょっと実装したところで終わって惨敗でした。いや、勝利といえば勝利だが。

その後、pandaxさんとお話する機会があった際に「人の実装を読んだり追ってみて実装してみるのも勉強になるよ」とおすすめされたので、長期休みを利用して2年越しのリベンジというわけです。

また、最近ネットワーク周りの勉強を再開したので、基礎のおさらいとプロトコルを実装したくなった時に備えて今回チャレンジしてみました。みなさんもよくありますよね?

振り返り

お盆休みが10~20日までで10日間あったため、それらの時間の大半をプロトコルスタックに費やしました。 日本よ、これがお盆だ。社会人パワー、最高です。

というわけで自分のツイートを参照しながら振り返ってみます。

8/10 1日目 進捗: step4完了

まずはDay1のstep4まで進めた。この時は受信後にキューで管理するのか、となった。 まあ実装終わったのは日付変わってからだったが。

8/11 2日目 進捗: step7完了(day1完了)

この日は午前中にstep5を終え、無事day1が完了した。

step7で何かやらかしたらしく、gdbを使ってデバッグしながらアレ…?というのを2,3時間ほどやりました。結局なんだったんだろう…

pandaxさんからはツッコミが入りました

この時、写経が間違っていたことが判明することをまだ知らない

8/12 3日目 進捗: step10完了

0時過ぎまでやってしまった。ICMPメッセージが受信できるようになった。

8/13 4日目 進捗: step10完了

この日は別件でもくもく会に参加していたので、ピープルウェアを読み進めていた

なにわ男子のライブがあったらしい

8/14 5日目 進捗: step11完了(day2完了)

12日のやらかし分のツケをここで払う羽目になった。

とはいえICMPメッセージの送受信ができるようになったのでとても嬉しかった。

実装したプロトコルが動く、というのは面白い。

8/15 6日目 進捗: step14完了

朝起きたらpandaxさんからコメントがきていた。ありがたい ちなみに複数個所間違ってて一人で死ぬほど笑った

8/16 7日目 進捗: step18完了(day3完了)

ARP応答が返ってきてアドレス解決できるようになる流れができた。キャッシュも実装して幸せを感じた。一人でやったときはルーターに直接ARP投げて返ってきたのを確認するところまでしかできなかったなぁ、というのを思い出した。

8/17 8日目 進捗: step21完了(day4完了)

この日はIPルーティング、UDPの実装を行った。たまにルーティングテーブルを覗いたりすることがあるが、実装にしてみるのははじめてだった。UDPで通信できるの感動ものである。

8/18 9日目 進捗: step22完了

この日はハニーキャットのお渡し会があったため、day5を少し進めて終わり。 ユイちゃんと加恋ちゃんがバチクソにかわいかったです。

8/19 10日目 進捗: step25完了

俺、TCPのこと何もわかってなかった…という気持ちを得たのでマスタリングTCP/IP、資料に添付されていたURLのRFCを読んでなるほど、という気持ちを得ていた。 正直あまり日常生活で意識できていなかった部分なので、知らないこともあり勉強になった。

8/20 11日目 進捗: step28完了(day5完了)

3wayハンドシェイク、実際目の前にするとテンション上がるので実装するのおすすめです。

そして完走した。バズってからの第1号だったらしいので、ドヤ顔になりました。

完走した感想

一通りやってみると思ったよりエクササイズがとけなくて、結構苦戦しました。GDBデバッグしながらなんでお前は落ちてるんだ!!!って叫びながらデバッグしたのは良い思い出。 ちなみに途中で写経がめちゃくちゃ間違えていて、step20ぐらいで気づいて泣きながら修正しました。

microps自体がとてもわかりやすく、なんでこの実装になっているんだろう?と思った時に実装を追いながら理解する、といったことがしやすかったです。 何度も資料を反復横跳びしながらこれはこうなっているからこう、というのを理解しながら進めることができ、以前よりネットワークへの理解が深まりました。 また、マスタリングTCP/IPを読むだけでは味わえない、実装を通してプロトコルを理解する、という体験は非常に良いものだな、と改めて感じました。

次に向けて

KLabExpartCampを通して、ネットワークへのモチベーションがニュッと上がりました。 通信の仕組みそのものを実装する、という行為が体系的に得た知識をより盤石なものにし、また一つ、ネットワーク力†ぢから†を付けることができました。 昔は実装できなかったUDPTCPが動いている様子を見て、こんなにも感動するとは思いもよりませんでした。

最近はCiliumに興味を持ち、少しずつ調べているので、また近いうちにeBPFやXDP、CNIの話なんかをブログにまとめていけるよう、頑張ります。

P.S. ちなみに夏休みはいつまでも続くそうです。興味を持ったそこのみなさん、あなたも実装してみてはいかがでしょうか。

Ciliumに出てきたわからない用語とか調べたのでメモ

okano さんから「ブログを書け!!!!」(※誇張しています)とアドバイスされたので、最近調べててわからなかった用語を調べていたのでまとめた。
しばらくネットワークから離れていたのでわからないことも多いな。また今度色々書きます。
次回はどうしよう。クラスタ構築して導入の話かデバッグの話ができるといいなあ。

南北(North-South)トラフィック・東西(East-West)トラフィック

トラフィックの向きのこと。

図を描いたときに上から下にクライアントまでの通信を表現するため、このような方角で表すらしい。

  • 南北トラフィック
    • 末端のアプリケーションから外部ネットワークや基幹に向かう縦方向の通信
    • DC対クライアント、DC対IoTデバイス など
  • 東西トラフィック
    • 内部ネットワークで発生する末端同士の横方向の通信
    • データセンター内で転送される通信

参考資料

南北トラフィック・東西トラフィック

ちょっと脱線するが、気になって調べたので書いておく

  • 東西トラフィックがなぜ増大するのか?
    • 従来のネットワークでは仮想マシン間の通信が非効率的
      • 仮想環境にはルーティング機能がない
        • 画像内では仮想基盤上にルーターが存在しない場合を想定
        • 仮想基盤上でルーティングする方法がなければ直接ルーターに行くしかない
    • 特定の経路にトラフィックの負荷が集中し、レスポンスが遅いなどの障害が発生する場合がある

VXLAN, GENEVE

複数のクラスターにまたがるネットワークの接続をする際に使用するプロトコル

overlay-mode の時に使用する。どちらもデータセンター向けのプロトコルっぽい。

  • VXLAN
    • ethenet フレームを UDP/IP でカプセル化し、50byte のヘッダを追加
      • VXLAN に対応するスイッチないと使えなそう
    • データセンター内部の VM の移動を楽にすることができる
      • L2 ネットワーク内での VM 移動だけなら問題なく接続できるが L3 をまたぐとセグメントが異なるので通信ができなくなる
      • VXLAN でトンネリングすることによって VXLAN 同士でつながっていれば通信ができるような状態になる
  • GENEVE
    • VXLAN と NVGRE の統合したものらしい
    • どう使われてるかよくわからなかった…またソース読んだりしながら調べてみよう

参考

C++ の Redis クライアントを使う

はじめに

和ホラーを見たら怖さと後半の駆け足具合でヒヤヒヤしました。二度美味しいと感じた Gunzi です

突然ジョブキューに興味が湧いてきたので C++ で実装するために調べた。メモ
Redis クライアントは以下のページにまとめられている

redis.io

今回は redis-plus-plus を使用する

github.com

準備

redis-plus-plus のページに沿ってインストールを進める
Redis サーバーは docker-compose を使用して構築する。今後、ジョブキューの記事を書く際に使用するためである
docker-compose.yml は以下の通り

version: '3'
services:
  redis:
    image: "redis:latest"
    ports:
      - "6379:6379"
    volumes:
      - "./data/redis:/data"

使い方

Redis が取り扱うことのできるデータ構造についてはここを確認する
Redisのデータ型と抽象化の紹介 – Redis 日本語訳

というわけで何個か気になったやつを触ってみた
Redis クライアントに触ってみる · GitHub

ビルドはこれで

g++ -o app main.cpp /usr/local/lib/libredis++.a /usr/local/lib/libhiredis.a -pthread

実行結果

./app
redis に ping を打つ
PONG
string 型
val
list 型
nanigashi soregashi something 
(interger) 1
上限付リスト
(interger) 5
nanigashi soregashi something hoge fuga 
指定範囲外の要素を削除
nanigashi soregashi something 
キューの操作
nanigashi
soregashi
5 秒後にタイムアウト
終了

感想

ジョブキュー作るなら Redis の LPUSH, RPOP があればひとまず形にはできそう
ポーリングは欠点があるそうなので、模索が必要だ

キューの実装には種類があるようなので、次回はいくつか作成してみたいと思う
RPOPLPUSH – Redis 日本語訳

Steam のオススメ

背景

友人がゲーム何買えばいいかわからんw!とのことだったので、自分の記憶を頼りに面白かった(実際にプレイした)なかで面白かったやつをピックアップしてみた。

とりあえず買え

Portal

言わずと知れた FPS パズルゲーム。高校生の時にハマりすぎて1日中やっていたら、次の日乗り物酔いがひどい状態になった。
その状態でもプレイし続けるぐらいには面白かったのでおすすめ。
古いけど良いゲーム。

store.steampowered.com

続編もあるよ。

store.steampowered.com

Left 4 Dead 2

大体みんな持ってる。やろうぜ、って言うとそこら中からワラワラプレイヤーが寄ってくる程度には有名。
最大4人でプレイできるので、友達と一緒に楽しめる。全体的にわかりやすい。
自分は苦手なので難易度イージーにして頑張ってた。

store.steampowered.com

Unrailed!

パーティーゲーム。みんなでやると面白い。
操作が単純でカジュアルにできていい。

store.steampowered.com

コンピューターのオタクが好きなやつ

スーパーハカーになれるゲーム。

store.steampowered.com

ハードウェアの仕様書とにらめっこしながらコード書いたり回路組めるよ!最高だね♡

store.steampowered.com

store.steampowered.com

store.steampowered.com

store.steampowered.com

1と違って公式で日本語翻訳出てるのでやりやすいかも。スーパーハカーになれます。

store.steampowered.com

超メタゲー。動画を見るとネタバレになるので完全初見でやると楽しい。

store.steampowered.com

ストラテジー or シミュレーション

Factorio

不時着した惑星から脱出するために工業製品を生産して技術力上げてロケットを打ち上げるゲーム。
自動化の自動化の自動化の自動化の自動化の自動化の自動化の自動化の自動化の自動化の... を繰り返していく。
どんどんでかくなっていく工場に興奮するタイプの人におすすめ。

store.steampowered.com

Cities: Skylines

街づくりゲーム。MODも豊富で長く遊べる1本。

store.steampowered.com

Sid Meier’s Civilization® VI

無限に遊べる。夜10時から始めて朝10時になるまでやめられなかった。

store.steampowered.com

ハードコア or スピード or 死にゲー

DOOM

武器をぶっぱなしながらデーモンを駆逐していくゲーム。棒立ちだとすぐ死ぬので激しく動き回る必要がある。
このスピードが癖になる。ハイスピードアクションを求めるならやっとけ。

store.steampowered.com

Downwell

1サイクルが短くて遊びやすい。落ち続けるのが気持ちいい。

store.steampowered.com

Flywrench

友人に勧められて買ったゲーム。曲がめちゃくちゃいいんだけど、基本操作説明があんまりなかった気がする。

store.steampowered.com

Hotline Miami

むずいけどサイクル速いのでよい。

store.steampowered.com

Rain World

世界観が好きで買ったらめちゃくちゃ難しくて笑った。

store.steampowered.com

Super Hexagon

避けゲー。左右キーしか使わないので楽(簡単ではない)

store.steampowered.com

パズル

Hexcells

サクサク進む。続編もあって1本目全クリしたら次買ってみるといいと思う。

store.steampowered.com

Opus Magnum

錬金術師になれるぞ!ちなみに他プレイヤーとの手数比較を問題を解いた最後に見せられるんだけど、どうしたらそうなる...って気持ちになる。
動きのあるパズルを組めるので絵的に面白い。

store.steampowered.com

ストーリー楽しめるやつ

NieR:Automata

ゲームの難易度自体は低い。ごり押しでもなんとかなる。
いろんなゲームの種類の動きをやることになるのでちょっと複雑かも。

store.steampowered.com

INSIDE

意外とアクションが難しい。パズルの難易度ちょっと高めだけど、解けると気持ちいい。

store.steampowered.com