この記事は新歓ブログリレー2026 15日目の記事です
こんにちは、25Bのくあらんてぃんです。traPショートコーディング部の部長をしています(空虚な真)。
皆さんはプログラミングをしたことはありますか?プログラムコードの量というものは、基本的に時間をかけるにつれて長くなっていくものです。それが良いことだとしても悪いことだとしても。しかし、一部の人々は時間をかけてコードの長さを削ろうとします。この営みはショートコーディング、もしくはコードゴルフと呼ばれ、traP内でも細々と楽しまれています。
ゲーム班の活動を一切していないことに気付いたので、今回はゲーム界のFizzBuzzこと2048を実装してみたいと思います。
JavaScriptを用いてWebで実装してもよかったのですが、僕はPythonのほうが得意なので、Pythonでのコードゴルフがしやすそうなターミナル上のゲームとして実装してみます。
__init__
コードを表示
実装してみました。遊んでみるとこんな感じです。

いくつか技術的な部分を説明してみます。
27行目で stty raw -echo というコマンドを使用して、ターミナル上で矢印などの入力をリアルタイムに受け取れるモードに切り替えています。このモードでは \n の改行はカーソルを1つ下に下げることしかしてくれないらしいため、\r と組み合わせて使用しています。
whileループ内に散見される \x1b というのはエスケープシーケンスの開始を意味する文字であり、\x1bc は画面とカーソル位置のリセットを、\x1b[A ~ \x1b[D はそれぞれ矢印キーの上下右左を表しています。
2048の仕様は僕の経験則です。盤面とスコアがわかると鶴亀算的に4のタイルが生成される確率が10%と求められたりします。盤面の回転を実装することで上下左右の移動を1つの処理にまとめたのが自慢ポイントです。
それではこれを短くしていきます。
盤面を一次元配列に
まずは盤面 b を一次元配列にしてみます。
コードを表示
こうなりました。byte数変化はプラマイゼロです。二次元配列の平坦化は sum(2darr, []) を使うとできます。この平坦化やスライスの取り方などが微妙に長さを取るため、今のままだとあまりメリットが感じられないという結果になりました。
add()
現状は if empty: で空きセルが存在するかを確認していますが、よく考えたら空きセルが存在するときしか add() は呼ばれません。確認をなくしたらワンライナーで書けそうです。さらに三項演算子の部分もより短い書き方がありそう……?
![c = random.choice def add(): b[c([i for i in range(16) if b[i] < 1])] = c([2] * 9 + [4])](https://trap.jp/content/images/2026/03/add.png)
こうなりました。73bytes減です。三項演算子は if ... else が固定でかかるので意外と弱いです。2 + (random.random() < 0.1) * 2 などもありですが、2が9個、4が1個入っているリストから1つ選ぶ方が、random.choice の共通化もできるのでよさそうな気がします。あと、非負整数だとわかっているならば == 0 は < 1 とした方が短いです。
merge()
現状は、0を取り除く→合成→再度0を取り除く→足りない0を補う、というアルゴリズムです。
0を取り除くのを2回行っている時点で明らかに何か最適化できそうですね。
![def merge(row): line = [v for v in row if v] + [0] * 9 for i in range(4): if line[i] == line[i + 1]: line[i] *= 2 line.pop(i + 1) return line[:4]](https://trap.jp/content/images/2026/03/merge.png)
70bytes減です。最初に0を十分に加えて後で最初の4つを返すという戦法ですね。4つ加えれば足りるだろ~と思ったらエラー吐いたので1桁の最大数の分加えておきました。
print()
31-33行目の出力部分もいい感じにまとめたいですね。

フォーマッターをかけると長すぎて改行されてしまい、文字数自体は増えてしまいましたが、改行をなくせば43bytes減です。2重 str.join() でも書けますが、条件付き改行みたいにしたほうが短そうでした。
残りの部分について
向きの取得と回転の部分ももう少しよさそうな方法がありそうです。

-87bytes。4回のループのうち、ちょうどいいタイミングで merge() を実行します。前半はもっと削れそうですが一旦これくらいで。
play()
普通に play() 要らないんで外に出しちゃいましょう。

コードを表示
print() が改行されてしまうのは甘んじて受け入れましょう。1058bytesになりました。ここからが本番です。フォーマッターを解除して、余分な改行や空白をすべて取り除いてみます。
code.strip()

コードを表示
727bytesになりました。331bytes減ですね。実は、空白の片方の隣が記号の場合、その空白はなくてもよいです。AIがやってくれなかったので温かみのある手作業で消していきます。……と思ったら消し忘れがありましたね。面倒なので次の作業のときに一緒に消しておきます。
code.replace()
変数名や繰り返し登場する関数名は1文字でいいです。置換していきます。

コードを表示
-86で641bytesです。やったのは置換だけですね。ついでに True を 1 で置換したりもしています。
:=
セイウチです。セイウチ演算子は代入を式として行える貴重な演算子です。今回使えそうなところは少ない気がしますが使っていきましょう。

コードを表示
626bytes。print は None を返すため、評価をつなげることで while の継続判定 r(1)!="\x03" を入れ込みます。また、同時にセイウチで k に代入します。僕はここ以外でセイウチを上手く使えなかったのですが、上手い人は他にも使いどころを見出せるのだと思います。
any%
ぶっちゃけ try ... finally 要らない気がしてきた。矢印キーと Ctrl + C 以外入力されないという前提でもよくね?ということで仕様を変えて短くしてみます。

コードを表示
507bytes。矢印キーの英大文字の部分だけに反応させるようにしました。A などを打っても反応するのはご愛嬌。止めた後の出力が壊れるのは reset を打ったりして対応してください。
諸々
諸々気になった部分を短縮します。

コードを表示
495bytesになりました。n(4) をさらに共通化させるのと、\x1b を1文字にまとめるのとで短縮しています。500を切ったのでひとまず満足ですね。ここからさらに短くするなら何かしらimportを一つ消したり等号をうまく言い換えたりする感じかと思います。もしくは Ctrl + C で止まらなくするような仕様を短縮しやすいものに変えるという方法もありそう。
base64.b85encode(zlib.compress(code, 9))
おまけです。上のコードを圧縮すると486bytesになりました。CTFではこのように圧縮して難読化されたファイルを解析する問題が出たりします。

コードを表示
コードゴルフは、最終的な1バイト単位の切り詰めに関しては言語仕様との闘いだったりしますが、序盤のアルゴリズム単位の改善についてはゲームやサービス製作でも活用できるようなアイデアやテクニックが含まれているんじゃないかなーと思っています。気になった人はCodinGameやAtCoderの問題でコードゴルフに挑戦してみてください!
この2048をもっと短く書けた!という方も任意の手段で教えてください!お願いします。
明日の担当は @genMira です!お楽しみに!