LoginSignup
4
2

More than 5 years have passed since last update.

KotlinでMinesweeperを500文字プログラミングしようとして挫折した

Last updated at Posted at 2017-06-21

はじめに

QiitaのMastodonサービスQiitadonが500文字までの文章を入力できてコードブロック&マークダウンに対応してると聞いたもので、喜び勇んで・・・(中略)・・・のに挫折したにもかかわらず、Qiitadonの皆さんから刺激を受けて再度Kotlinでライフゲームを作ったは良いが500文字に収めるのに挫折したのは8年前、ちがった8日前のことでした。(ここまでテンプレート)
qiita04.png

この投稿の後、@shiracamusさんのコード最適化によって500文字以下になったり、@sumimさんのSmalltalk版ライフゲームがQiitadonに1Tootで投稿されたり、その後の@sumimさんのQiita投稿でAPL版ライフゲームを知って戦慄を覚えたり、このままでは終われないと一念発起して再びプログラミングしたコードですが、コメント外しても800文字オーバー。挫折芸人の意識が芽生え始めました。
ということで、500文字以内に収めることに挫折したので、コードをさらして次の糧にしたいと思います。文字数を抑えるため、可読性が落ちていますがご容赦を。

例によってKotlinいいね!って処をコードの後のほうにまとめています。

コード

val l="123456789"
fun r(a : String? = readLine())= if(a!=null && a.length==1) l.indexOf(a[0]) else -1
fun main(a:Array<String>){
  var r=java.util.Random()
  val W=9;val H=9;val X=10
  var v=Array(W,{CharArray(H,{'#'})})
  var s=0
  var m=emptySet<Int>()
  while(m.size<X) m+=r.nextInt(W)*10+r.nextInt(H)
  fun p(){for(i in v) println(i.map{it})}
  fun ck(x:Int,y:Int){
    if(x>=0 && x<W && y>=0 && y<H)
      if(v[y][x]=='#'){
        s++;var d=0
        for(i in -1..1)for(j in -1..1)if((i!=0||j!=0)&& m.contains((x+j)*10+y+i)) d++
        v[y][x]=l[d]-1
        if(d==0){ck(x,y-1);ck(x,y+1);ck(x-1,y);ck(x+1,y)}
      }
  }
  while(s+X<W*H){
    p()
    var x=-1; var y=-1
    while(x==-1){print("x?");x=r()};while(y==-1){print("y?");y=r()}
    if(m.contains(x*10+y)){println("BOM!");break} else ck(x,y)
  }
  for(i in m){v[i%10][i/10]='@'}
  p()
}

解説

タイトルに記載の通り、皆さん懐かしのMinesweeperです。MinesweeperはWindows7まで20年以上Windowsに標準で搭載されていたゲームだったため、時間を忘れてプレイされた方も多いのでは。今回はそんな時間泥棒のMinesweeperを実装しています。
地雷座標を二けたの数字の配列に格納し、盤面上のプレイヤーが指定した座標に地雷がないかを判定し、地雷がなければ周りの地雷に隣接していない座標も一気に開ける(←ここ重要)。そして、すべての地雷が特定できたらループを抜けてゲーム終了という実装をしています。

実行例

[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
x?1
y?1
[0, 0, 1, #, #, #, #, #, #]
[0, 0, 1, #, #, #, #, #, #]
[0, 0, 1, #, #, #, #, #, #]
[1, 1, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
x?9
y?9
[0, 0, 1, #, #, #, #, #, #]
[0, 0, 1, #, #, #, #, #, #]
[0, 0, 1, #, #, #, #, #, #]
[1, 1, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, 1]
x?3
y?4
[0, 0, 1, #, #, #, #, #, #]
[0, 0, 1, #, #, #, #, #, #]
[0, 0, 1, #, #, #, #, #, #]
[1, 1, 3, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, #, #, #, #, #, #, #, 1]
x?4
y?1
BOM!
[0, 0, 1, @, #, #, #, #, #]
[0, 0, 1, #, #, #, #, #, #]
[0, 0, 1, #, @, #, #, #, #]
[1, 1, 3, @, #, #, #, #, #]
[#, @, #, @, #, #, #, #, #]
[#, #, #, #, #, #, #, #, #]
[#, @, @, #, #, #, @, #, #]
[#, #, @, #, #, #, #, @, #]
[#, #, #, #, #, #, #, #, 1]

おっと座標(4,1)には地雷がありました。
最後は、@で地雷の場所を示してゲーム終了です。

Kotlinいいね!

今回もKotlinっていいね!となった処をまとめていきましょう。

単一式関数(Single-expression function)

Kotlinは関数が単一の式だった場合、カッコ{}や戻りの値を省略することができます。

fun r(a : String? = readLine())= if(a!=null && a.length==1) l.indexOf(a[0]) else -1

TICTACTOEを実装したときに書いた同様のコードが以下の内容でしたから、デフォルト引数との組み合わせでコンパクト化に成功しています。

fun readl(): Int {
  var ax : String? = readLine();var a=-1
  if(ax!=null && ax.length==1) a = "123".indexOf(ax[0], 0, false)
  return a+1
}

コレクション

KotlinはList、Map、Setなどのコレクションを提供しています。Kotlinのコレクションは変更可能なコレクションと変更ができないコレクションに分かれます。
今回使ったのはコレクションはInt型のSetです。変更ができないコレクションですが、そのSetに対して、データを追加するplus関数は、Setにデータを追加して新しいSetを返却します。
記載方法としては+演算子で置き換えることができます。

  var m=emptySet<Int>()
  while(m.size<X) m+=r.nextInt(W)*10+r.nextInt(H)

上記ではからのSetに地雷の座標情報を次々と追加していく操作を行っています。Setは集合なので重複が許容されません。whileループでXで定義された地雷の上限までSetに地雷の座標情報を詰め込みます。

コレクション操作関数

Kotlinはコレクションに対する操作関数を提供しています。
今回は使いませんでしたが、filterやflatMapは使い勝手の良い関数です。
ゲーム終了判定をしているwhileの条件式ですが、以下のように盤面の空いていない座標の数で判定する実装も検討しましたが、どうしても文字数が増えてしまうので断念しました。

  while(v.flatMap { it.map{ it} }.filter{it=='#'}.size < X)

終わりに

500文字プログラミング、(一度も達成できていませんが)規模感が良いですね。
たかだかこの程度のコードと記事を書くだけですが、Kotlin仕様を調べまくりで、とても勉強になります。
みなさんもぜひ500文字プログラミングに挑戦して(なんでしたら挫折して)、Qiitaに投稿してみてください。
きっと良い勉強になると思いますよ。

ではまた!

4
2
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2