【python3.x】 練習問題55本ノック【問題のみ】

はじめに

ここではpythonの練習問題を掲載しています.

データを処理する際には,必ず「データの格納」と「データの取り出し」を行うことになるので,その方法を知ることを目的としています.問題は暗算で解けるものや,頭の中で答えが分かるものが多数ありますが,あくまでも,「行いたい処理をpython3という言語ではどのように表現するのか」を確認するものです.

解答の一例は,以下のリンクで公開しています.

gotutiyan.hatenablog.com

一応筆者としては,問題と解答のタブを2つとも開きながら学習することを想定しています.また,解答のページでは,本記事に解答のコードが追加されるだけなので,解答のページだけを見ながら取り組むこともできます.

全ての問題には,必ず出力が存在します.出力はprint()を用いて,画面に表示させることを想定しています.(改行区切り?空白区切り?などの細かな体裁は気にしません.)

(2020/12/4 追記)演習問題との関係で,45番目の降順ソートの項目を削除し,ラムダ式の項目に書き換えました.

問題

1. 変数

xという変数に2を代入し,それを3倍した数を出力してください.

期待する出力:6

2. swap

変数a100を代入し,変数b200を代入します.その後,両者の値を入れ替えて,a200b100が代入されているようにしてください.出力としてはabを出力してください.出力形式は問いませんが,print(a,b)とすると,空白区切で出力できます.

期待する出力の一例:200 100

3. 四則演算+\alpha

変数a10を代入,変数b2を代入し,abの和,差,積,商を出力してください.出力形式は問いません.また,商の値が小数か整数かは問いません.

期待する出力の一例:12 8 20 5.0

4. 余り

変数a5を代入,変数b3を代入し,abで割った時の余りを求めてください.

期待する出力:2

5. べき乗

変数a5を代入,変数b10を代入し,ab乗,つまりa^bを出力してください.

期待する出力:9765625

6. if,比較演算子<, >

変数a5を代入,変数b10を代入し,abのうち大きい方を出力してください.

期待する出力:10

7. 比較演算子==, bool

変数a5を代入し,aが偶数ならTrue,そうでなければFalseを出力してください.(条件式をそのままprintすれば良いです.)

期待する出力:False

8. 文字列(ランダムアクセス)

変数に,'python'という文字列を代入し,先頭の文字を0番目として2番目の文字を出力してください.

期待する出力:t

9. 文字列(結合)

'py''thon'という2つの文字列をそれぞれ変数に代入し,結合したものを出力してください.

期待する出力:python

10. 文字列(format)

変数a5を,変数b3を代入し,これらの変数を用いて'5%3=2'という文字列を出力してください.

期待する出力:5%2=2

11. 文字列(replace)

変数に文字列'some1'を代入し,この文字列中の1oneに変換してください.

期待する出力:someone

12. 文字列(lower)

変数に文字列'This Is A Sentence .'を代入し,この文字列を全て小文字に変換してください.

期待する出力:this is a sentence .

13. 文字列(upper)

変数に文字列'This Is A Sentence .'を代入し,この文字列を全て大文字に変換してください.

期待する出力:THIS IS A SENTENCE .

14. 文字列(文字数)

変数に文字列'How many characters?'を代入し,この文字列の文字数を出力してください.空白も含めるものとします.

期待する出力:20

15. 文字列 → 数値

変数aに文字列'34'を代入し,変数bに文字列'43'を代入し,これらを数字と見なした時の和を出力してください.

期待する出力:77

16. リスト

変数にリスト[1,2,3,4,5]を代入し,(先頭を0番目として)3番目の要素を出力してください.

期待する出力:4

17. リスト(結合)

変数li1[1,2,3]のリストを代入,変数li2[4,5]のリストを代入し,2つのリストを結合して出力してください.

期待する出力:[1,2,3,4,5]

18. リスト(append)

変数にリスト[1,2,3,4,5]を代入し,このリストの末尾6,7を1つずつ順番に追加してください.その後,最終的なリストを出力してください.

期待する出力:[1,2,3,4,5,6,7]

19. リスト(insert)

変数にリスト[1,2,3,4,5]を作成し,先頭を0番目としたときに,0番目と1番目の間に100を挿入してください.

期待する出力:[1,100,2,3,4,5]

20. リスト(forによる捜査)

変数に[1,2,3,4,5]の5つの要素を持つリストを格納して,偶数の要素だけ出力してください.出力形式は問いません.

期待する出力:2 4

21. リスト(forによる捜査, enumerate)

変数に[1,2,3,4,5]の5つの要素を持つリストを格納して,添え字が偶数番目の要素だけ出力してください.出力形式は問いません.ただし,先頭の添え字を0番目とします.

期待する出力:1 3 5

22. リスト(len)

変数にリスト[11,22,33,44,55,66]を代入し,リストの要素数を出力してください.

期待する出力:6

23. リスト,if(存在確認)

リスト[11,22,33,44,55]に,値44が存在するかどうかをifを用いて判定してください.存在すればTrue,そうでなければFalseを出力します.

期待する出力:True

24. タプル,リストの負のindex

変数に[1,2,3,4,5]の5つの要素を持つリストを格納して,リストの先頭の要素と末尾の要素をタプルとして格納し,出力してください.

期待する出力:(1, 5)

25. 辞書

変数に,値の組みとして{'A':1, 'B':2, 'C':3, 'D':4, 'E':5}を持つ辞書を格納して,出力してください.

期待する出力:{'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 5}

26. 辞書(keys)

変数に,値の組みとして{'A':1, 'B':2, 'C':3, 'D':4, 'E':5}を持つ辞書を格納して,キーを要素としたリストを作成し,出力してください.

期待する出力:['A', 'B', 'C', 'D', 'E']

27. 辞書(values)

変数に,値の組みとして{'A':1, 'B':2, 'C':3, 'D':4, 'E':5}を持つ辞書を格納して,値(バリュー)を要素としたリストを作成し,出力してください.

期待する出力:[1,2,3,4,5]

28. 辞書(items)

変数に,値の組みとして{'A':1, 'B':2, 'C':3, 'D':4, 'E':5}を持つ辞書を格納して,キーと値(バリュー)の組みのタプルを要素としたリストを作成し,出力してください.

期待する出力:[('A', 1), ('B', 2), ('C', 3), ('D', 4), ('E', 5)]

29. 辞書(キーの存在確認)

今,以下のように辞書が作成済みです.

d = {'apple':10, 'grape':20, 'orange':30}

この辞書に対して,'apple'というキーが存在するかを確認し,存在しなければ,'apple'というキーに対して-1という値を追加してください.また,同様のことを'pineapple'でも行なってください.その後,最終的な辞書を出力してください.

期待する出力:{'apple': 10, 'grape': 20, 'orange': 30, 'pineapple': -1}

30. スライス1

変数に文字列'training'を代入し,先頭を0番目として,1番目から4番目までを取り出して出力してください.

期待する出力:rain

31. スライス2

変数に文字列'understand'を代入し,先頭を0番目として,奇数番目の文字列だけを取り出した文字列を出力してください.

期待する出力:nesad

32. スライス3

変数にリスト[1,2,3,4,5]を格納して,これを逆順に出力してください.

期待する出力:[5,4,3,2,1]

33. 集合

変数にリスト[1,1,2,3,3,4,5]を代入し,このリストを集合に変換して出力してください.

期待する出力:{1,2,3,4,5}

34. 積集合

変数set1に集合{1,2,3,4,5},変数set2に集合{3,4,5,6,7}を代入し,積集合を出力してください.

期待する出力:{3,4,5}

35. 和集合

変数set1に集合{1,2,3,4,5},変数set2に集合{3,4,5,6,7}を代入し,和集合を出力してください.

期待する出力:{1,2,3,4,5,6,7}

36. 差集合

変数set1に集合{1,2,3,4,5},変数set2に集合{3,4,5,6,7}を代入し,差集合を出力してください.

期待する出力:{1,2}

37. 型の確認

3つの変数に以下のように代入したコードがあります.各変数の型を出力してください.出力形式は問いません.

data1 = {'A':1, 'B':2}
data2 = "hoge"
data3 = {1,2,3,4,5}

期待する出力:<class 'dict'> <class 'str'> <class 'set'>

38. strip

文字列'This is sentence .\n'の改行記号を消したものを出力してください.

期待する出力:This is sentence .

39. split

変数に文字列'C C++ // python java'を代入し,この文字列を空白で区切った後のリストを出力してください,また,同じことを'/'でも行い,出力してください.

期待する出力:['C', 'C++', '//', 'python', 'java'],['C C++ ', '', ' python java']

40. join

変数にリスト['This', 'is', 'a', 'sentence']を代入し,これらを空白区切で結合した文字列を出力してください.

期待する出力:This is a sentence

41. max

変数にリスト[11,2,7,13,5]を代入し,最大値を出力してください.

期待する出力:13

42. min

変数にリスト[11,2,7,13,5]を代入し,最小値を出力してください.

期待する出力:2

43. sum

変数にリスト[11,2,7,13,5]を代入し,総和を出力してください.

期待する出力:38

44. 昇順ソート

変数にリスト[5,3,1,4,2]を格納し,昇順に並び替えたリストを出力してください.

期待する出力:[1,2,3,4,5]

45. ラムダ式

いま,辞書を要素とするリストが次のように与えられています.

li = [{'a': 6, 'b': 7, 'c': 6},
      {'a': 4, 'b': 2, 'c': 3},
      {'a': 1, 'b': 5, 'c': 8}]

このとき,辞書のキーが'b'の値に関して降順になるようにソートしてください.

期待する出力:[{'a': 6, 'b': 7, 'c': 6}, {'a': 1, 'b': 5, 'c': 8}, {'a': 4, 'b': 2, 'c': 3}]

46. range()

0から99の100の要素からなるリストを出力してください.

期待する出力:[0,1,2....,99](一部省略)

47. 内包表記

変数にリスト[5,4,3,2,1]を代入し,要素と添字の値(先頭を0とする)を足し合わせた数値を要素に持つリストを新たに作成してください.
内包表記を使うと便利です.

期待する出力:[5,5,5,5,5]

48. 例外処理

下記のように変数が作られています.

a = 0
b = 5

この時,\frac{a}{b}\frac{b}{a}を出力してください.ただし,ゼロ割りが発生した時にはzero divisionを出力します.
例外処理を用いて書いてみると良いと思います.

期待する出力:0, zero division

49. ビット演算

変数a10を,変数b5を代入します.この時,ab論理和論理積排他的論理和を出力してください.出力形式は問いません.

期待する出力:15 0 15

50. モジュールのインポート

mathモジュールをインポートして,\theta = \frac{\pi}{2}の下で,sin\theta^2 + cos\theta^2の値を計算してください.

期待する出力:1.0

演習1(forのネスト)

1から31までの整数を要素とするリスト1と,1から12までの整数を要素とするリスト2を作成します.リスト1とリスト2から値を1つずつ取ってきた時,1の位が一致する値が何組あるかを求めてください.例えば,111は,1の位が両方1なので,カウントに入ります.11311なども同様です.

期待する出力:38

演習2(辞書の値ソート)

辞書が以下のように定義されています.

dic = {'two':324, 'four':830, 'three':493, 'one':172, 'five':1024}

この時,値で昇順ソートしたと考えた場合のキーの並びを出力してください.

期待する出力:['one', 'two', 'three', 'four', 'five']

演習3(num2freq)

リストが以下のように定義されています.

nums = [1,2,4,3,2,1,5,1]

このリストに対して,要素の出現数を格納した辞書num2freqを作成し,出力してください.これはnum2freq[要素] = 出現数となるような辞書で,例えばnum2freq[1] = 3です.

期待する出力:{1: 3, 2: 2, 4: 1, 3: 1, 5: 1}

演習4(word2freq)

今,文字列が以下のように与えられています.

doc = 'i bought an apple .\ni ate it .\nit is delicious .'

\nは改行記号なので,3つの文が3行に渡って書かれていることになります.
この文章中の単語を用いて,キーとして単語,値として出現数を持つような辞書word2freqを作成し,出力してください.ただし,改行記号は単語に含めないでください.
ヒント:改行記号でsplitしてから空白でsplitすれば,単語に分割できます.

期待する出力:{'i': 2, 'bought': 1, 'an': 1, 'apple': 1, '.': 3, 'ate': 1, 'it': 2, 'is': 1, 'delicious': 1}

演習5(Jaccard係数)

2つの集合A,Bの類似度を計算する方法として,Jaccard係数というものがあります.例えば,文章の類似度の指標として使われています.

Jaccard(A,B) = \frac{|A \cap B|}{|A \cup B|} = \frac{AとBの積集合の要素数}{AとBの和集合の要素数}

2つのリストが以下のように与えられている時,リストを集合と見なした時のJaccard係数の値を求めてください.

list1 = [12,23,34,45,56,67,78,89]
list2 = [21,32,43,45,65,67,78,98]

期待する出力:0.23076923076923078

書評・感想:リーダブルコード

どのような本なのか

タイトル通りと言えばそれまでですが.「いかに読みやすいプログラムを書くか」をテーマに,そのためのテクニックが書かれている本です.
オライリー本には珍しく,表紙に動物が登場しません.

どのような人に読んでほしいか

プログラムを書く全人類.ただし,プログラムの基礎的な概念(変数,if,for,関数,classあたり)は分かっていて,多少の実装経験がある方が,書いてあることが身に沁みる気がします.

また,プログラムを独学で学んできた人ほど読むべきだと思います.(書き方が我流になりがちなので)

おすすめポイント

普通教えてくれないことが知れる

一般的なプログラミングの授業(もしくはネット上の教材)では,ある言語についての文法や,メソッドの紹介は行われます.しかし,変数名の付け方,コメントの付け方,アルゴリズムが追いやすいループ処理の書き方などについては,あまり説明してくれないように思います.しかし本書では,これらについて非常に丁寧に説明されています.

また,例えば"get"で始まる関数はほぼO(1)程度の非常に軽い関数である,というような,世界中のプログラマが暗黙の了解として知っているようなことも学べます.このようなことは,教えてもらわないと気づかないと思います.もちろん,本書に書いてあることが絶対とは限らないので,組織独自のコード規約があれば従うべきです.

読みやすい文章

本書の中では,とにかく「読みやすいコード=他人が最短時間で理解できるコード」という視点を常に持ちながら,テクニックが紹介されています.「他人」というのは,自分以外の人という意味に加えて,数ヶ月後の自分でもあることも強調されています.各テクニックにはほぼ全てサンプルコードが添えられていて,非常に直感的に理解できます.もちろん,それらのサンプルコードは(悪い例として提示されているものを除けば)リーダブルなので,大きく立ち止まることは無く読み進められます.また,各章には「まとめ」が設けられており,時間がなければ「まとめ」を読むだけでも十分活用できると思います.

また,日本語も非常に読みやすいです.砕けすぎているわけでもなく,だからと言って専門書並みの硬い口調な訳でもないです.前書きには"先輩から優しくアドバイスを受けている感じ"という表現がありますが,まさにその通りだと思います.

読みにくいかもしれない点

サンプルコードがC++, python, javascript, Javaの4言語で書かれている点です.終盤を除けば,どのサンプルコードも簡単なコードなので,全然読めないことはないです.(終盤のコードも,実際の開発に比べれば短くて,簡単に読めるコードです.)でもこれは,僕がC++pythonjavascriptを触ったことがあるからかもしれません.コードが読めないと問題提起が分かりにくいのは確かなので,サンプルコードが読めるかどうかは,本書を理解できるかどうかの重要な判断基準になると思います.

ですがこのことは,紹介されているテクニックが特定の言語を対象としておらず,一般的な「プログラミング」という行いに対して適用できることも示しています.

おわりに

リーダブルコードの書評と感想でした.

終盤の解説でも書かれているように,本書を読んだ瞬間から,重要なテクニックを使いこなせるようにはなりません.自分で悪い部分があることに気づき,テクニックを適用して読みやすいコードを書くという訓練から始まるのだと思います.

自分のプログラムの書き方をもう一度見つめ直す大きなきっかけになると思いますので,強くお勧めして,終わりにさせていただきます.

Processingという言語は何ができるのか

0. はじめに

ここでは,Processingという言語について,何ができるのかを解説します.

processing.org

大学などで行われるプログラミング教育では,学習の入り口としてProcessingが使われることがあります. これによって,学習者は2つのことを習得します.1つ目はプログラミングの一般的な能力,2つ目は,Processingという言語自体の文法や機能です.しかしながら,それ以降のステップでは,多くの場合,python機械学習をやったり,javascriptでWeb系の開発をしたりというように,前者のプログラミングの能力だけが活かされているような気がしています.実際に,教育的な視点でみれば,前者を重視するのは当然なのですが.(本質的には,デフォルトのProcessingがJavaベースというところで説明できます.この問題の検討は後述の5. 言語モードの変更で記述があります.)

ですが,せっかくProcessing自体の習得もしているので,その使い道が分かれば学ぶ意味もありますし,アウトプットの幅が広がると思います. このような背景から,何ができるのか,どのような使い道があるのかを紹介するために,本記事を執筆しています.

次からは,具体的な項目を列挙していきます.

1. ゲーム制作

Processingでは四角や丸などの図形を描画することができ,さらにそれを簡単に動かすことができます.さらには,画像読み込みや音楽の再生も可能なことから,ゲーム制作との相性が良いです.プログラミング教育としてのprocessingはゲーム制作が多いように思います(筆者の知ってる母数が小さいですが...).

ゲーム制作では様々なプログラミングの技術が必要になります.例えば,図形移動には変数,当たり判定にはif文,大量の敵や弾,ブロックなどを描画するにはfor文が必要になります.さらに,処理が長くなれば関数化が必要でしょうし,クラスを使うこともあるでしょう.学んだ知識を十分に活かせると思います.

一方で,これはゲーム制作全般の話をしますが,ゲーム制作の手段としては,UnityやUnrealEngineのようなゲームエンジンを用いる場合も多いと思います.Processingでの制作というのは,ゲームエンジンでの制作と比べてコードをゴリゴリ書いていくところが多いです.例えば,当たり判定1つ取っても, Processingでは座標を考えてif文を必死に書く必要がありますが,Unityでは対象のオブジェクトにあらかじめタグをつけておいて,void OnCollisionEnter2D()みたいな関数で当たり判定を制御できたりするので,簡単です.筆者なんかはコードをゴリゴリ書くのは好きなのでProcessingで作ったこともありますが,好みが分かれるところだと思っています.

ゲーム制作については,僕の記事でシューティングゲームを作るものがあるので,ご興味あればどうぞ.

gotutiyan.hatenablog.com

2. アート制作

様々な図形を描画できるので,うまく組み合わせればとても綺麗な模様を描くことができます.

f:id:gotutiyan:20200406173427p:plain
Processingで制作したアート例

制作されたアートは,TwitterやNEORT,OpenProcessingといった媒体で多くの人が公開しています.
OpenProcessingでは,作品と一緒にコードも閲覧できます.

neort.io

www.openprocessing.org

NEORTはCG系の作品もあるので,Processingによる作品だけではない事に注意です.
Twitterでは,#Processing や #つぶやきprocessing のハッシュタグで多くの作品を見ることができます.特に #つぶやきProcessing のハッシュタグは,1ツイートの上限文字数である140字という制限の元でコーディングする側面から,見応えがあります.

筆者はgithubで作品を公開しています.

github.com

3. 画像処理

processingは画像処理もできます.例えば,カラーの画像をモノクロにしたり,色を反転するなどのことができます.

また,動画もたくさんの画像が連続しているものと思えば,大量の画像を処理することと同じなので,動画処理もできます.例えばOpenCVというライブラリを使えば,動画中に出現する人の顔を認識することができます.

筆者は画像処理系の人間ではないので憶測ですが,論文につながるような画像処理系の研究でも,Processingが使われることがあると思います.(matlabも有名ですけどね.)論文の実装にも使われているかもしれませんが,基本的に,論文には具体的なツール名は書かれないので,正確なことはわかりません.(例えば,画像をモノクロ化した,という記述はあっても,その処理をどんなツールでやったのかは書かれない.)
しかし,画像処理系の研究室のHPでは,初学者向けにProcessingの教材を掲載しているところをそれなりに見るので,研究でも活躍していると思います.

4. 音声処理

minimという音声に関するライブラリを使えば,音声処理も行えるようです.例えば,音声の波形を表示することができます.

筆者は音声処理についてはよく分かっていないので,あまり深いところの言及は避けますが,minimのサンプルなどを見てみると面白いかもしれません.

5. 何ができないか

  • キーボードの情報は受け取れますが,文字列を受け取るような機能が無いので,文字列のやり取りを用いたインタラクティブな制作物は原則作れません.(何かしらのライブラリを使えばできるかもしれません.キーの情報を時系列で保存すれば,疑似的に文字列を表せるような気はしないでもないです.)

  • 画像や音声に特化しているので,機械学習やWeb開発などはできません(これもライブラリ次第ですけど..).画像や音声以外に何かやろうとすれば,Processingよりも良いツールや言語が存在するので,そっちでやるのが良いというのが現状だと思います.

6. 言語モードの変更

デフォルトの設定では,Processsingという言語で書くことになります.これはJavaベースなので,例えばArrayListなども使えます.

実はこの他に,pythonベースやjavascriptベース(p5.js)でも書くことができます.

f:id:gotutiyan:20200406212451p:plain
processingの言語モード変更

0. はじめにの項で説明した通り,Processingを学んだのにその後使われないという問題は,この言語モードの変更によって回避できる可能性があります.

pythonモードでProcessingを学べば,pythonの文法や機能を習得できるので,滑らかに機械学習などの方面に移行できますし,p5.jsでProcessingを学べば,Web開発の方面に滑らかに移行できるはずです.

将来の方向性を考えないとしても,言語モードを意識する時があります.2. アート制作で述べたつぶやきProcessingでは,いかに文字数を削減するかが課題なので,変数の宣言に型を明示的に書く必要が無いpythonモードやp5.jsが好まれる印象です.目的次第で使い分けると良いかもしれません.

7. さらなる発展へ

Processing界隈の大きなイベントとして,PCD(Processing Community Day)というものがあります.日本ではPCD Tokyoとして毎年開かれています.

pcd-tokyo.github.io

ここではProcessingを高度なレベルで活用している方のワークショップや公演が行われます.自分では考えつかなかったようなアイデアで溢れているので,面白いと思います.
中には発表スライドを上げてくださっている方もいるので,探して見ると良いかもしれません.

公式アカウントが,関連資料をまとめてくださっていました. twitter.com

8. おわりに

本記事では,Processingで何ができるかということについて紹介しました.

今回の狙いとしては,冒頭で述べたとおり,ただの教育手段としての言語として終わって欲しくないということがありました.これは特に,僕の大学がProcessingから入るカリキュラムだったこともあると思います.
そして,Processingの界隈がさらに発展していけば良いなとも思っています.本当に楽しい言語だと思うので.

以上です.

processingにおけるシーン遷移(画面遷移)を実現する一般的なテクニック

はじめに

ここでは,processingのシーン遷移について解説します.
シーン遷移を知っていれば,様々な方面に活用できます.具体的には,スタート画面,クリア画面,操作説明の画面などを簡単に作ることができます.他にも,ステージクリア制のゲーム制作,アート作品のポートフォリオアプリの制作などにも応用が利きます.

この記事では,まず基本アイデアとして,ステージ遷移の考え方について書きます.
その後,その実装方法を簡略化して3通り述べます.
最後に,単純なゲームを作成してみて,実践的な使い方を解説します.

基本アイデア

まずは概要として,基本アイデアを示します.

まずは,全てのシーンに番号を振ります(頭の中で).例えば,スタートシーンは0,ゲーム画面は1にするかー,という感じです.そして,プログラム上で番号を管理する変数を一つ持っておいて,その番号によって実行するプログラムを変えます.例えば,番号を管理する変数が0なら,スタート画面のプログラムを実行するようにします.このような実装はif文で書けます.

さて,次からは具体的な実装面を見ていきますが,シーンや,それぞれに対応する番号を次のように決めることにします(頭の中で).
まず,シーンはスタート,ゲーム,クリアの3種類を想定します.また,それぞれに対応する番号は0:スタートシーン,1:ゲームシーン,2:クリアシーン だと思うことにします.さらに,シーン遷移するためには,シーン遷移する「きっかけ」が必要です.今回は,スタート→ゲームの遷移は「スタートボタンを押す」,ゲーム→クリアの遷移は「ゲームをクリアする」ことがきっかけになるとします.

実装例

ここからは実装例を書きます.
なお,processingのテンプレである以下の形から付け加えていくことにします.

void setup(){
  // 最初の1回だけ実行される
}

void draw(){
  // 何回も実行される
}

1. draw()に直書き

一番愚直な方法です.ある意味わかりやすいかもしれません.

int scene = 0;  // シーンを指定する番号
void setup(){

}

void draw(){
  if(scene == 0){
    // スタートシーンの処理
    if(スタートボタンを押したら) scene = 1;
  }else if(scene == 1){
    // ゲームシーンの処理
    if(クリアしたら) scene = 2;
  }else if(scene == 2){
    // クリアシーンの処理
  }
}

各シーンについて,次のシーンに移れるようにsceneの値を書き換える処理を入れています.

また,この例では,全ての処理をdraw()に直書きします.一見分かりやすそうですが,例えば,ゲームシーンの処理が200行とかになった場合,プログラム上で処理の流れが追いにくくなります.

2. 関数を呼び出す

int scene = 0;  // シーンを指定する番号
void setup(){

}

void draw(){
  if(scene == 0){
    start_scene();
  }else if(scene == 1){
    game_scene();
  }else if(scene == 2){
    clear_scene();
  }
}

void start_scene(){
  // スタートシーンの処理
  if(スタートボタンを押したら) scene = 1;
}

void game_scene(){
  // ゲームシーンの処理
  if(クリアしたら) scene = 2;
}

void clear_scene(){
  // クリアシーンの処理
}

各シーンでの処理を,関数によって記述しました.ここでは,具体的な処理は全てstart_scene(), game_scene(), clear_scene()に記述します.

この実装で嬉しいことは,各シーンの処理によらず,draw()の中身は変わらないことです.とりあえずdraw()さえ見ればシーンと番号の対応が一目で分かるので,コードの可読性がぐっと上がります.

ちなみに,シーンによらず常に実行したい処理もあるかもしれません.例えば,background()や,BGMに関する処理です.このような時は,もう一つ専用の関数を作って,if文の影響を受けないところで呼び出します.

int scene = 0;
void setup(){

}

void draw(){
  common();  // if文の影響を受けないところで呼ぶ
  if(scene == 0){
    start_scene();
  }else if(scene == 1){
    game_scene();
  }else if(scene == 2){
    clear_scene();
  }
}

void common(){
  // 共通の処理
}

void start_scene(){
  // スタートシーンの処理
  if(スタートボタンを押したら) scene = 1;
}

void game_scene(){
  // ゲームシーンの処理
  if(クリアしたら) scene = 2;
}

void clear_scene(){
  // クリアシーンの処理
}

3. シーンを文字列で指定する

1.および2.では,シーンを番号で指定していました.でもこの方法は,「0番はスタート」という情報を自分が覚えておく必要があります.これでは,より複雑なゲームでは,何番が何のシーンだったかを忘れるかもしれません.「キャラ選択画面は5で,魔法使いの能力確認画面は49で..」みたいなのは,できるだけやりたくないです.

このような時,シーンの指定に文字列を使えば良いかもしれません.例えば,"start"はスタートシーン,"select_character"はキャラ選択画面, "magic_ability"が魔法使いの能力確認画面,みたいな要領です.これだと,非常に直感的に記述できます.

String scene = "start";  // シーンを指定する文字列
void setup(){

}

void draw(){
  if(scene == "start"){
    start_scene();
  }else if(scene == "game"){
    game_scene();
  }else if(scene == "clear"){
    clear_scene();
  }
}

void start_scene(){
  // スタートシーンの処理
  if(スタートボタンを押したら) scene = "game";
}

void game_scene(){
  // ゲームシーンの処理
  if(クリアしたら) scene = "clear";
}

void clear_scene(){
  // クリアシーンの処理
}

文字列はString型で扱うことができます.数字で指定していた時よりも,少しわかりやすくなった気がしますね.

単純なゲーム制作で理解するシーン遷移

最後に,具体的なサンプルを見て,実践的な感覚を掴みましょう.ここでは,3.の方法を用いて,文字列でシーンを制御します.
ここでは単純な例として,赤い四角をクリックしたらクリアできるようなゲームを考えましょう.
もちろん,スタート画面とクリア画面も作ります.

f:id:gotutiyan:20200116225907g:plain

まずはテンプレです.

void setup(){
  size(500, 500);
}

void draw(){

}

共通部分の追加

まずは共通部分です.今回は,画面更新のたびに画面を塗りつぶすことくらいです.

void setup(){
  size(500, 500);
}

void draw(){
  common();  // 呼び出し
}

// 共通部分
void common(){
  background(255); 
}

スタートシーンの追加

スタートシーンを作ります.これに伴って,シーンを指定する文字列も作成します. この文字列には,最初"start"を代入しているので,最初はスタートシーンが表示されます.

また,何かしらのキーをクリックしたらゲームが始まるようにします.これはvoid keyPressedの中で,sceneの文字列を書き換えれば良いです.

String scene = "start";
void setup(){
  size(500, 500);
  textSize(50);
}

void draw(){
  if(scene == "start") start_scene(); //呼び出し
}

void common(){
  background(255); 
}

// スタートシーンの処理
void start_scene(){
  fill(0);
  text("Start", width/5, height/2);
  text("Press any key", width/5, height/2+60);
}

void mousePressed(){ 
  // ゲームシーンへ遷移
  if(scene == "start") scene = "game"; 
}

ゲームシーンの追加

次にゲームシーンを作成します.
ゲームの内容的には,赤い四角が描画できて,クリックしたらシーン遷移すれば良いです.

String scene = "start";
void setup(){
  size(500, 500);
  textSize(50);
}

void draw(){
  common();
  if(scene == "start") start_scene();
  else if(scene == "game") game_scene();
}

void common(){
  background(255); 
}

void start_scene(){
  fill(0);
  text("Start", width/5, height/2);
  text("Press any key", width/5, height/2+60);
}

// ゲームシーンの処理
void game_scene(){
  fill(255,0,0);
  rect(30, 50, 70, 90);
}

void mousePressed(){
  if(scene == "start")scene = "game";
  else if(scene == "game"){
     // クリックできたらクリア画面に遷移
     if(get(mouseX,mouseY) == color(255,0,0)){
        scene = "clear"; 
     }
  }
}

get(x,y)は点(x,y)の色を取得する関数で,get(mouseX, mouseY)でマウス座標の色を取得できます.color(255,0,0)は赤色を表します.

クリアシーンの追加

最後にクリア画面を追加します.ここでは単純に,Clearの文字を出すことにします.

String scene = "start";
void setup(){
  size(500, 500);
  textSize(50);
}

void draw(){
  common();
  if(scene == "start") start_scene();
  else if(scene == "game") game_scene();
  else if(scene == "clear") clear_scene();  // 呼び出し
}

void common(){
  background(255); 
}

void start_scene(){
  fill(0);
  text("Start", width/5, height/2);
  text("Please click", width/5, height/2+60);
}

void game_scene(){
  fill(255,0,0);
  rect(30, 50, 70, 90);
}

// クリアシーンの処理
void clear_scene(){
  fill(0);
  text("Clear", width/5, height/2);
}

void mousePressed(){
  if(scene == "start")scene = "game";
  else if(scene == "game"){
     if(get(mouseX,mouseY) == color(255,0,0)){
        scene = "clear"; 
     }
  }
}

このようにして,スタートシーン→ゲームシーン→クリアシーンの遷移を実装できました.

おわりに

今回は,processingにおけるシーン遷移について説明し,具体的なサンプルと共に使い方を説明しました.いろいろなシーンで使えるので,ぜひ使ってみてください.

以上です.ありがとうございました.

【入門】processingで最小限の一筆書きゲームを作る

本記事は,processingで一筆書きのゲームを作る記事です.
早速ですが,完成版は画像のようになると思います.マウスを使ってマス目をなぞり,なぞったところは赤くなります.全てのマス目をなぞることができればクリアです.

f:id:gotutiyan:20200114001632g:plain
完成予想図

まだprocessingをよく知らない方でも完成までたどり着けるように、詳しく書いていきたいと思います。プログラムは60行です. 出てくるプログラムの知識は int型, float型、変数、if、配列、for程度です。どれも事前知識があると嬉しいですが、全て解説は入れているので、問題ないのではないかなと思います。

はじめに

座標系

processingを使うと、自由に図形を描くことができます。図形を描画する際に必ず指定するのが座標なのですが、processingの場合は座標系における軸の取り方がいつもとは違います。 数学における座標系は、上に行くほどy座標が増えて、右に行くほどx座標が増えます。また、原点は左下です。 対して、processingにおける座標系は、下に行くほどy座標が増えて、右に行くほどx座標が増えます。また、原点は左上です。

f:id:gotutiyan:20200102190010p:plain
座標系の違い(左:数学 右:processing)
最初は戸惑うかもしれませんが、徐々に慣れていきましょう。

setup()とdraw()

ゲームを作るに欠かせないのは「パラパラ漫画」の機構です。ページを素早くめくるように、ゲームは高速に画面が更新されています。テトリスを想像してみてください。ブロックが落ちていくだけでも、ゲーム画面は非常に高速に更新されています。この更新の速さを「フレームレート」と呼んだりしますが、フレームレートが低いことは、いわゆる「カクつく」「処理が重い」と感じる原因です。パラパラ漫画をめくる速度が遅ければ、当然絵もカクつきますね。

さて、processingにはこの「高速に画面を更新する」仕組みがあります。それがsetup()draw()です。これらはプログラミングの中では非常に重要な「関数」というものですが、今は割愛します。 setup()は、ゲームが始まった瞬間に一度だけ処理が行われます。 draw()は、ゲームを遊んでいる間、裏で何回も何回もひたすら処理が行われます。draw()が何回も実行されて、画面がその度に更新されることによって、パラパラ漫画はめくられます.ブロックは落ちるし、キャラは動きます。

具体的には、以下のようなテンプレを元に作成することになります。

void setup(){
  //1回だけ実行される処理
}

void draw(){
  //何回も実行される処理
}

初期設定

今回ゲームを作るにあたって、初期設定をしましょう。 具体的には,実行画面の大きさです。ゲームをプレイする画面を小さくしたり、大きくしたりできます。 これはsize()によって指定することができます。サイズは一度設定すればそれ以降固定されるので、setup()に書きます。 サイズは何でも良いですが、本記事では500x500にすることにします。

void setup(){
  size(500,500);
}

void draw(){
  //何回も実行される処理
}

実行ボタンを押すと、ある程度の大きさのウィンドウが表示されます。

フィールドを作成する

マス目の一辺の長さの決定と変数

一筆書きを行うためのフィールドを作成します.本記事でのフィールドとは,マス目の集合だとしましょう.一筆書きのゲームは,必ずマス目が用意されていなければなりません.今回は5x5マスによる一筆書きを実現します.目標はこんな感じです.

f:id:gotutiyan:20200111101104p:plain

いま,フィールド全体を見たときに,縦や横に並ぶマス目の数は5個です.また,フィールドの縦や横の長さは,「初期設定」のところで500x500と決めたので,マス目一つあたりの一辺の長さは自然に決まります.つまり,500/5=100です.これを,プログラムでは変数を用いて書いてみます.

変数とは,何かしらの値を格納できるものです.変数1つにつき1つの値しか格納できませんが,一度格納したものを後で上書きしたり,四則演算を行ったりできます.さらに,processingでは,変数にはというものがあります.型は変数に格納できる値の種類を表すもので,int型は,その変数には整数だけ格納できることを表します.

さて,これらを駆使して,マス目の個数と,マス目の一辺の長さは以下のように書けます.

int number_of_squares = 5;  //一辺に並べるマス目の数
int side_length = 0; //マス目の一辺の長さ
void setup(){
    size(500,500);
    side_length = width / number_of_squares; // 500/5=100
}

void draw(){
    //何回も実行される処理
}

width, heightは,変数の中でもシステム変数と呼ばれるものです.システム変数は,processingが勝手に用意してくれている変数で,公式のエディタでは桃色で表示されます.
widthは実行画面の横の長さを,heightは実行画面の縦の長さを表します.本記事であれば,width=height=500です.

フィールドの作成とfor

ここまでで,マス目の一辺の長さが決まったので,あとはそれを良い感じに並べるだけです.とは言いつつ,説明することは多いので順番にやっていきましょう.

まずは四角形の描画方法です.
rect(左上の頂点x, 左上の頂点y, 横の長さ, 縦の長さ)で書けます.

rect(0,0,100,50); // ((x,y)=(0,0)を左上の点とし,横100,縦50の四角形)

というわけで,まずは左上のマス目を書いてみます.

int number_of_squares = 5;  //一辺に並べるマス目の数
int side_length = 0; //マス目の一辺の長さ
void setup(){
    size(500,500);
    side_length = width / number_of_squares; // 500/5=100
}

void draw(){
    rect(0, 0, side_length, side_length);  //左上のマス目

}

f:id:gotutiyan:20200111002013p:plain

1つだけマス目を書けました! どんどん増やして並べていきましょう..と言いたいところですが,5x5=25個のマス目をこのように手打ちするのは,骨が折れます.できるんですけどね.

今やろうとしていることは,マス目を並べることです.もっと言えば,等間隔で並べようとしています.このようなとき,for文を用いると便利です.for文の一般的な文法はこうです.

for(int 変数名 = 初期値; 変数名 < 上限値; 変数名 += 加算値){
    //処理を書く
}

for文の変数名には,慣習的にi, j, kあたりがよく使われます.これを使えば,繰り返しの処理が簡単に記述できます.今回のゲーム制作で言えば,マス目の左上の座標を指定するのに役立ってきます.

まずは以下のコードを見てみましょう.

int number_of_squares = 5;  //一辺に並べるマス目の数
int side_length = 0; //マス目の一辺の長さ
void setup(){
  size(500,500);
  side_length = width / number_of_squares; // 500/5=100
}

void draw(){
  // for文を使って,5つのマス目を書いた
  for(int i = 0; i < number_of_squares; i++){
    rect(i*side_length, 0, side_length, side_length);
  }
}

f:id:gotutiyan:20200111002324p:plain

for文を使うことで,たった3行で5つのマス目を並べることができました.並べる時には,隣同士のマス目はside_lengthだけ離せば,綺麗に敷き詰められることを利用しています.

冗長に書けば,以下のようになります.

// for文を使用
for(int i = 0; i < number_of_squares; i++){
  rect(i*side_length, 0, side_length, side_length);
}
ーーーーーーーーーーーーーーーーーーーーーーー
// for文を未使用
rect(0, 0, side_length, side_length);
rect(side_length, 0, side_length, side_length);
rect(2*side_length, 0, side_length, side_length);
rect(3*side_length, 0, side_length, side_length);
rect(4*side_length, 0, side_length, side_length);

0→1→2→3→4と増えていく変数iが,座標指定にうまくマッチしていることが分かります.

残りの領域も敷き詰めていきます.先ほどは,1つのマス目を横に5個並べました.これと同じ要領で,「横に5個並んだもの」を縦に5個並べることを考えます.こうすれば,5x5のマス目になるはずです.つまり,for文を2つ重ねることをします.

int number_of_squares = 5;  
int side_length = 0; 
void setup(){
  size(500,500);
  side_length = width / number_of_squares; 
}

void draw(){
  // 「横に5つ並べた」ものを縦に5つ並べた
  for(int j = 0; j < number_of_squares; j++){
    // 1つのマス目を横に5つ並べた
    for(int i = 0; i < number_of_squares; i++){
      // iとjに注意
      rect(i*side_length, j*side_length, side_length, side_length);
    }
  }
}

f:id:gotutiyan:20200111101104p:plain

for文の中にfor文を入れることを,「入れ子にする」,もしくは「ネストする」などと言います.外側のfor文の変数名は j で,四角のy座標に用いています.内側のfor文の変数名は i で,四角のx座標に用いています.

以上で,フィールドの作成ができました.

なぞれるようにする

一筆書きのゲームは,マス目をなぞっていくことでプレイするものです.前章でマス目の作成は済んでいるので,次はこれをなぞれるような機構を考えます.このため,以下のような要件を満たすようにしましょう.

  • プレイヤーが最後になぞったマス目から,次に移動できるのは上下左右だけ
  • なぞったマス目は赤色になる
  • 始点はこちらで指定する

なぞったマスの覚え方と配列, if

なぞり方に関するルールは定めましたが,まずはあるマスについて,そのマスが既になぞられたかどうかを覚える必要があります.これの実現には,あるマスについて,1なら既になぞられた,0なら まだなぞられていない,となるような変数を作成します.この変数はマスの数だけ作成することが望ましいので,5x5=25個の変数を作りたいです.でも,手打ちでするのはめんどくさいですね.

// 頑張って書いた例
int visited1 = 0, visited2 = 0, ..... , visited25 = 0;

これをもっと簡単に書くために,配列を使うことにします.配列は,大量の変数を一度に作成できるものです.上記を配列で書き換えると以下のようになります.

int visited[] = new int[25];   //25個のint型変数を作成

このようにして作られた配列は,visited[0], visited[1], ...., visited[24]として,値を代入したり,値を参照したりできます.このときの0~24を添字(index)と呼びます.0から始まることに注意してください.

さらに,今回は実装上の理由から,この配列を2次元にして使います.一応1次元でもできるのですが,2次元の方が後々分かりやすくなります.5x5個のマスに対しての変数なので,配列も5x5の2次元に揃えます.

int visited[][] = new int[number_of_squares][number_of_squares];

では,これをプログラムに組み込みましょう.組み込むに当たって,新たにif文を導入します.if文は「もし〜なら」という機構を実現するものです.文法の一例は以下の通りです.

if(条件式) {
  // 条件式がTrueの時の処理
} else {
  // そうでない時の処理
}

条件式もいろいろ書けますが,この段階では==を紹介します.==は,その左側も右側も同じであれば,True,そうでなければFalseを返します.

if(5 == 5) {
  // 5と5は等しいので実行される
}

if(4 == 5){
  // 4と5は異なるので実行されない
}

次では,このif文を赤に塗るか塗らないかを決定することに使います.

int number_of_squares = 5;  
int side_length = 0; 
int visited[][] = new int[number_of_squares][number_of_squares]; // 塗ったかどうかの0 or 1
void setup(){
  size(500,500);
  side_length = width / number_of_squares;
  // v[2][2] = 1; //試しに真ん中を赤くする用
}

void draw(){
  for(int j = 0; j < number_of_squares; j++){
    for(int i = 0; i < number_of_squares; i++){
      if(visited[i][j] == 1)fill(255,0,0);  // 赤に塗る
      else fill(255);  // 白に塗る
      rect(i*side_length, j*side_length, side_length, side_length);
    }
  }
}

if文を用いれば,visited[ ][ ]が1か0かで,赤く塗るかを決められます.ここでfill()は図形を塗る色を決められる関数です.基本的にRGBなので,
fill(赤の濃さ, 緑の濃さ, 青の濃さ)
で指定します.それぞれの値は0(最小)~255(最大)です.ここでは赤を塗りたいので,fill(255, 0, 0);です.

ただ,visited[ ][ ]は初期値が全て0なので,全て白に塗られます.なので特に実行結果に変化はありません.なので,setup()中にコメントアウトされている行を削除すれば,中心のマスだけ赤くなるでしょう.

f:id:gotutiyan:20200111201227p:plain

これで,塗るかどうかを記憶するために配列を作って,if文によって塗る色を決めることができました.

なぞったマスを赤くする

一筆書きのゲームはマウスを使ってプレイします.つまり,マウスの座標から,どのマスを塗っているのかを判定する必要があります.もっと具体的にいえば,どのようなときにvisited[ ][ ]を1にするかについて詳しく書きます.ここではその方法の紹介と,実際に塗ったところが赤くなる様子まで見れるようにします.

まずは,マウスの座標を取得する方法です.これはmouseX, mouseYで取得できます.これはwidthなどと同じシステム変数です.

また,マウスをクリック&ドラッグしている間だけ,マス目を塗れるようにしましょう.これには,void mouseDragged()という関数を用います.これはマウスをドラッグしている間だけ実行されるものです.関数については中級的な内容ですが,とりあえずサンプルを見てもらえればと思います.

int number_of_squares = 5;  
int side_length = 0; 
int visited[][] = new int[number_of_squares][number_of_squares]; 
void setup(){
  size(500,500);
  side_length = width / number_of_squares; 
}

void draw(){
  for(int j = 0; j < number_of_squares; j++){
    for(int i = 0; i < number_of_squares; i++){
      if(visited[i][j] == 1)fill(255,0,0);  
      else fill(255);  
      rect(i*side_length, j*side_length, side_length, side_length);
    }
  }
}

void mouseDragged(){
  // マウスをドラッグしている間だけ実行
}

この節ではmouseDraggedに処理を記述することになります.

さて,やっと本題です.マウス座標から,今塗っているマス目を特定するにはどうすれば良いでしょうか.今やりたいことは,マウスの座標を,なんとかvisitedの添え字である0~4の値に変換することです.
また,mouseX, mouseYは,画面内に収まるとは限りません.画面サイズは500と決めていても,マウスが実行画面の外に行けば,mouseX,Yは600にも700にもなります.

このような時,constrain()を使います.これは値の下限と上限を決めて,その範囲に収めてくれる関数で,constrain(値, 下限, 上限)として使います.今回はmouseX, mouseYに対して使います.

void mouseDragged(){
  int mx = constrain(mouseX, 0, width-1);  // 0~499になるようにする
  int x = mx / side_length; //添え字に変換
  
  int my = constrain(mouseY, 0, height-1);  // 0~499になるようにする
  int y = my / side_length;  //添え字に変換
  visited[x][y] = 1;  //該当マスを赤く塗る
}

constraintsを使って,マウス座標を画面内に収まるようにします.width-1やheight-1のように1引いているのは,500の時だけ5になってしまい,添え字として使うには都合が悪いからです.499なら,499/100=4(小数切り捨て)で,大丈夫です.
こうして得た添え字を使って,visited[x][y]に1を入れたら,マスを赤く塗る機構の完成です.

やり直し機構

ゲームはトライ&エラーを繰り返してクリアするものです.プレイヤーは,時に失敗することがあります.このような時,すぐにやり直せることができれば,プレイヤーのモチベーションは続きます.

この考えに基づき,このゲームではマウスを離したら(=クリックをやめたら),塗った形跡を全て消すことにします.マウスを離した時の処理は,mouseReleased()に記述します.

int number_of_squares = 5;  
int side_length = 0; 
int visited[][] = new int[number_of_squares][number_of_squares]; 
void setup(){
  size(500,500);
  side_length = width / number_of_squares; 
}

void draw(){
  for(int j = 0; j < number_of_squares; j++){
    for(int i = 0; i < number_of_squares; i++){
      if(visited[i][j] == 1)fill(255,0,0);  
      else fill(255);  
      rect(i*side_length, j*side_length, side_length, side_length);
    }
  }
}

void mouseDragged(){
  int mx = constrain(mouseX, 0, width-1);
  int x = mx / side_length; 
  
  int my = constrain(mouseY, 0, height-1);
  int y = my / side_length; 
  visited[x][y] = 1;
}

void mouseReleased(){
  //マウスを離した時の処理 
}

次に,どうすれば塗った形跡を消せるかです.これは,visited[ ][ ]の全てに0を入れたら良いです.これはfor文を入れ子にして,以下のように書けます.

int number_of_squares = 5;  
int side_length = 0; 
int visited[][] = new int[number_of_squares][number_of_squares]; 
void setup(){
  size(500,500);
  side_length = width / number_of_squares; 
}

void draw(){
  for(int j = 0; j < number_of_squares; j++){
    for(int i = 0; i < number_of_squares; i++){
      if(visited[i][j] == 1)fill(255,0,0);  
      else fill(255);  
      rect(i*side_length, j*side_length, side_length, side_length);
    }
  }
}

void mouseDragged(){
  int mx = constrain(mouseX, 0, width-1);
  int x = mx / side_length; 
  
  int my = constrain(mouseY, 0, height-1);
  int y = my / side_length; 
  visited[x][y] = 1;
}

void mouseReleased(){
  // 全ての変数を0に
  for(int i = 0;i < number_of_squares; i++){
   for(int j = 0; j < number_of_squares; j++){
     visited[i][j] = 0;
   }
  }
}

これで,快適にトライ&エラーを繰り返すことができます.

始点を決める

一筆書きのゲームをプレイしたことがある人なら分かると思いますが,一般的には始点が決められています.複雑なステージであれば,始点を適切に指定しないとクリアできないステージとなってしまいます(別途解説しています).ここではその機構を作ります.

まずは,始点のための変数を作成します. 始点はsetup(),およびmouseDragged()で決めます.どちらも,プレイヤーがプレイを始める前には決めておくべきだからです.まずは適当に2を入れておきます.

int number_of_squares = 5;  
int side_length = 0; 
int visited[][] = new int[number_of_squares][number_of_squares];
int start_x=2, start_y=2; // 始点用変数
void setup(){
  .......

次に,ここで始点としたマス目は,実際に赤く塗らないと行けません.この処理も,setup(),およびmouseDragged()に書きます.いずれにせよ,ステージの初期化と同時に始点も塗るということです.

int number_of_squares = 5;  
int side_length = 0; 
int visited[][] = new int[number_of_squares][number_of_squares];
int start_x=3, start_y=2; // 始点用変数
void setup(){
  size(500,500);
  side_length = width / number_of_squares;
  visited[start_x][start_y] = 1; //始点を塗る
}

void draw(){
  for(int j = 0; j < number_of_squares; j++){
    for(int i = 0; i < number_of_squares; i++){
      if(visited[i][j] == 1)fill(255,0,0);  
      else fill(255);  
      rect(i*side_length, j*side_length, side_length, side_length);
    }
  }
}

void mouseDragged(){
  int mx = constrain(mouseX, 0, width-1);
  int x = mx / side_length; 
  
  int my = constrain(mouseY, 0, height-1);
  int y = my / side_length; 
  visited[x][y] = 1;
}

void mouseReleased(){
  for(int i = 0;i < number_of_squares; i++){
   for(int j = 0; j < number_of_squares; j++){
     visited[i][j] = 0;
   }
  }
  visited[start_x][start_y] = 1; //始点を塗る
}

壁マスの作成

壁マスの作成

ここまでの一筆書きのゲームは,全てのマスを塗ることができます.しかし,これは流石に面白みに欠けていると思うので,「壁のマス」は必須だと思います.壁マスは,塗ることができないマスです.

壁マスの実現にも,やはりvisited[ ][ ]を用います.今,この値は0(塗ってない),もしくは1(塗った)でした.これに,-1(壁マス)であることを追加します.今,試しに一番左上のマスを壁マスにすることを考えます.壁マスの要件はこんな感じです.

  • 壁マスは,塗ることができない
  • 壁マスは,あらかじめ灰色に塗っておく

まずは灰色に塗るところをやります.壁マスにはvisited[ ][ ]に−1を入れたら良いです.これはsetup()に書きます.また,「やり直し機構」のところで,mouseReleased()でもフィールドの塗り直しを行なっているので,ここでも忘れずに壁マスの情報を入れます.

最後に,-1である時には灰色で塗ることを書きましょう.色を塗る情報はdraw()で書いているので,灰色で塗ることもdraw()で書きます.灰色はfill(150);で実現できます.

int number_of_squares = 5;  
int side_length = 0; 
int visited[][] = new int[number_of_squares][number_of_squares];
int start_x=2, start_y=2;
int wall_x = 0, wall_y = 0; // 壁マスの添え字
void setup(){
  size(500,500);
  side_length = width / number_of_squares;
  visited[start_x][start_y] = 1;
  visited[wall_x][wall_y] = -1;  // -1を入れる
}

void draw(){
  for(int j = 0; j < number_of_squares; j++){
    for(int i = 0; i < number_of_squares; i++){
      if(visited[i][j] == 1)fill(255,0,0); 
      else if(visited[i][j] == -1)fill(150);  // 灰色に塗る
      else fill(255);  
      rect(i*side_length, j*side_length, side_length, side_length);
    }
  }
}

........

void mouseReleased(){
  for(int i = 0;i < number_of_squares; i++){
   for(int j = 0; j < number_of_squares; j++){
     visited[i][j] = 0;
   }
  }
  visited[start_x][start_y] = 1;
  visited[wall_x][wall_y] = -1;  // -1を入れる
}

次に,灰色のマスは塗れないようにします.現状では,灰色のマスにマウスをかざせば,容赦無く赤に塗りつぶしてしまいます.
これが起こる原因は,マスの状態(壁,塗ってない,塗った)を見ずに,visited[ ][ ]に1を代入していることです.よって,塗る前にマスの状態を確認して,「塗っていない」マスに対してだけ塗るようにしましょう.

void mouseDragged(){
  int mx = constrain(mouseX, 0, width-1);
  int x = mx / number_of_squares; 
  
  int my = constrain(mouseY, 0, height-1);
  int y = my / number_of_squares; 
  if(visited[x][y] == 0)visited[x][y] = 1;  // 「塗ってない」マスなら塗る
}

壁マスを作ったことによる問題

さて,前節で壁マスを導入しました.しかし,これにより一つ問題が起こります.次の画像を見てください.

f:id:gotutiyan:20200112111953g:plain

このように,壁マスの色は変わりませんが,壁マスを貫通して一筆書きができてしまいます.この問題を解決します.

結論から言うと,「最後に塗ったマスから上下左右のマスしか塗らないようにする」という制約をつけることが解決策になります.この制約の元では,壁マスから上下左右には塗ることができません(壁マスは塗れないマスなので).不思議かもしれませんが,これだけで問題は解決できます.

さらにもう一点だけです.「最後に塗ったマス」というのは,最初に塗るときには存在しません.よって,最初は,始点としていたマスを最後に塗ったマスとみなすことにします.

実装としては,最後に塗ったマスをprev_x, prev_yとして持っておけば良いです. また,(x1, y1)と(x2, y2)の2つのマスがあると仮定したとき,それらが上下左右に位置する関係かどうかを判定する方法は,以下のように書けます.

abs(x1 - x2) + abs(y1 - y2) == 1

ここで,abs( )は絶対値を返す関数です.マンハッタン距離が1であれば良い,と聞いてわかる人はそれで十分です.わからない人は,とにかく「x座標,もしくはy座標が1だけ違えばいい」と解釈すれば,分かりやすいかなと思います.

int number_of_squares = 5;  
int side_length = 0; 
int visited[][] = new int[number_of_squares][number_of_squares];
int wall_x = 0, wall_y = 0; 
int start_x=2, start_y=2;
int prev_x, prev_y; // 最後に塗ったマスの添え字
void setup(){
  size(500,500);
  side_length = width / number_of_squares;
  visited[start_x][start_y] = 1;
  visited[wall_x][wall_y] = -1;
  // 最初は,始点を最後に塗ったマスとしておく
  prev_x = start_x;
  prev_y = start_y;
}

void draw(){
  for(int j = 0; j < number_of_squares; j++){
    for(int i = 0; i < number_of_squares; i++){
      if(visited[i][j] == 1)fill(255,0,0); 
      else if(visited[i][j] == -1)fill(150);
      else fill(255);  
      rect(i*side_length, j*side_length, side_length, side_length);
    }
  }
}

void mouseDragged(){
  int mx = constrain(mouseX, 0, width-1);
  int x = mx / number_of_squares; 
  
  int my = constrain(mouseY, 0, height-1);
  int y = my / number_of_squares; 
  // 最後に塗ったマスの上下左右であれば塗るという条件を追加
  if(visited[x][y] ==0
     && (abs(prev_x - x) + abs(prev_y - y) == 1)){
       visited[x][y] = 1; //塗る
       prev_x = x;  // 最後に塗ったマスを更新(x)
       prev_y = y;  // 最後に塗ったマスを更新(y)
     }
}

void mouseReleased(){
  for(int i = 0;i < number_of_squares; i++){
   for(int j = 0; j < number_of_squares; j++){
     visited[i][j] = 0;
   }
  }
  visited[start_x][start_y] = 1;
  visited[wall_x][wall_y] = -1;
  prev_x = start_x; // 忘れず初期化
  prev_y = start_y; // 忘れず初期化
}

以上で,壁マスの設置が完了し,それによって起こる問題も解決できました.

クリア判定とテキスト表示

ここまで,フィールドを塗っていくことができるようになりました.壁マスも自由に設置できるようになったので,ステージ作りもできるようになりました.残すはクリアしたかどうかの判定のみです.ここでは,最小限という名目なので,"Clear!"という文字を画面に表示することにしましょう.

結論から言えば,visited[ ][ ]の全部の値が-1か1になっていればクリアです.
解釈として,「(壁マスを除いて)全てのマスが塗られた」,もしくは,「塗られていないマスがない」と考えれば,visited[ ][ ]が-1と1だけになれば良いというのは納得がいくでしょう.

まずは実装を見てみましょう.クリア判定は常に監視するべきもので,かつマウスの状態には関係ない処理なので,draw( )で書くことにします.

int number_of_squares = 5;  
int side_length = 0; 
int visited[][] = new int[number_of_squares][number_of_squares];
int wall_x = 0, wall_y = 0; 
int start_x=2, start_y=2;
int prev_x, prev_y;
void setup(){
  size(500,500);
  side_length = width / number_of_squares;
  visited[start_x][start_y] = 1;
  visited[wall_x][wall_y] = -1;
  prev_x = start_x;
  prev_y = start_y;
  // テキストのサイズを大きめに
  textSize(50);
}

void draw(){
  for(int j = 0; j < number_of_squares; j++){
    for(int i = 0; i < number_of_squares; i++){
      if(visited[i][j] == 1)fill(255,0,0); 
      else if(visited[i][j] == -1)fill(150);
      else fill(255);  
      rect(i*side_length, j*side_length, side_length, side_length);
    }
  }
  // クリア判定
  boolean is_clear = true;
  for(int i = 0; i < number_of_squares; i++){
    for(int j = 0; j < number_of_squares; j++){
      if(visited[i][j] == 0) is_clear = false;
    }
  }
  // クリア条件を満たしていれば,テキスト表示
  fill(0);
  if(is_clear)text("Clear!", width/3, height/2);
}

void mouseDragged(){
  int mx = constrain(mouseX, 0, width-1);
  int x = mx / number_of_squares; 
  
  int my = constrain(mouseY, 0, height-1);
  int y = my / number_of_squares; 
  if(visited[x][y] ==0
     && (abs(prev_x - x) + abs(prev_y - y) == 1)){
       visited[x][y] = 1;
       prev_x = x;
       prev_y = y;
     }
}

void mouseReleased(){
  for(int i = 0;i < number_of_squares; i++){
   for(int j = 0; j < number_of_squares; j++){
     visited[i][j] = 0;
   }
  }
  visited[start_x][start_y] = 1;
  visited[wall_x][wall_y] = -1;
  prev_x = start_x;
  prev_y = start_y;
}

クリア判定の実装は少しアルゴリズミカルなところが強いです.
まずはじめに,クリア判定用のboolean型変数を作ります.boolean型は,trueかfalseのどちらかしか取らない型です.trueかfalseしかないので,if文の条件式に直接使用できます.今回はソースコードに習って,この変数のことをis_clearと呼ぶことにします.

クリアを判定するアルゴリズムはこうです.最初,仮にクリアできているものと考えます.よって,is_clearにtrueを入れます.
次に,visited[ ][ ]の値を全部見ていき,0であればまだ塗り残しがあってクリアしていないので,is_clearにfalseを入れます.
visited[ ][ ]を全て見終わった後,まだis_clearがtrueであれば,クリアしていることが分かります.

また,テキストの表示にはtext( )を使います.具体的には,文字列と座標をtext(文字列, x座標, y座標)で指定します.今回は"Clear!"を中央あたりに表示させたいので,text("Clear!", width/3, height/2)としています.
また,テキストには文字サイズがあります.これはtextSize( )で指定できます.今回は文字サイズを50くらいにすれば良い感じなので,textSize(50)とします.注意点として,textSize( )は少し処理が重いので,draw( )で何度も実行するとゲーム自体が重くなることがあります.よってこれはsetup( )に記述することにします.

重要な注意点

とても一般的な話をしますが,今回のゲームに限らず,一筆書きというのは,どうやってもできない場合があります.この辺りの話は数学の分野の1つであるグラフ理論に関わってきます.特にオイラーグラフ(厳密には半オイラーグラフ)に密接していますが,難しいので割愛します.

今回のゲームで言えば,クリアできるかできないかは,実は壁マスの位置と始点のマスの位置で決まります.どちらのマスも,ゲームの製作者が自由に決めることができてしまうので,作ったステージがちゃんとクリアできるものかは確認する必要があります.

おわりに

以上で,最小限の一筆書きゲームが完成しました!
たった60行のプログラムですが,それなりにプログラミングの知識を必要とし,簡単なアルゴリズムも学べる良い題材だったと思います.

最後にもう一度,コードの完成図を掲載します.

int number_of_squares = 5;  
int side_length = 0; 
int visited[][] = new int[number_of_squares][number_of_squares];
int wall_x = 0, wall_y = 0; 
int start_x=2, start_y=2;
int prev_x, prev_y;
void setup(){
  size(500,500);
  side_length = width / number_of_squares;
  visited[start_x][start_y] = 1;
  visited[wall_x][wall_y] = -1;
  prev_x = start_x;
  prev_y = start_y;
  textSize(50);
}

void draw(){
  for(int j = 0; j < number_of_squares; j++){
    for(int i = 0; i < number_of_squares; i++){
      if(visited[i][j] == 1)fill(255,0,0); 
      else if(visited[i][j] == -1)fill(150);
      else fill(255);  
      rect(i*side_length, j*side_length, side_length, side_length);
    }
  }
  boolean is_clear = true;
  for(int i = 0; i < number_of_squares; i++){
    for(int j = 0; j < number_of_squares; j++){
      if(visited[i][j] == 0) is_clear = false;
    }
  }
  fill(0);
  if(is_clear)text("Clear!", width/3, height/2);
}

void mouseDragged(){
  int mx = constrain(mouseX, 0, width-1);
  int x = mx / side_length; 
  
  int my = constrain(mouseY, 0, height-1);
  int y = my / side_length; 
  if(visited[x][y] ==0
     && (abs(prev_x - x) + abs(prev_y - y) == 1)){
       visited[x][y] = 1;
       prev_x = x;
       prev_y = y;
     }
}

void mouseReleased(){
  for(int i = 0;i < number_of_squares; i++){
   for(int j = 0; j < number_of_squares; j++){
     visited[i][j] = 0;
   }
  }
  visited[start_x][start_y] = 1;
  visited[wall_x][wall_y] = -1;
  prev_x = start_x;
  prev_y = start_y;
}

これは宣伝なんですが,この記事と同じくらいの丁寧さでシューティングゲームを作るものも公開しているので,気になる方はご覧ください.

gotutiyan.hatenablog.com

ありがとうございました.

今後の展開

ここでは,このゲームをさらに拡張するためのアイデアを提案します.実装はしないのでコード例はありませんが,ご了承ください.

  • マスの数を変える
    マスの数は5とは言わず,もっと多くしても面白いでしょう.今回作成したプログラムは,number_of_squaresを変えるだけで,マス目の数を変えてプレイできるようになっています.

  • 一マス戻れるようにする
    あるマスを塗ったけど,なんか違う気がするので,一歩戻りたい時があると思います.そういう機能は今はないので,つければより遊びやすくなります.

  • 壁マスを複数つけられるようにする
    今は壁マスを1つしか作成できませんが,やはり複数作成できたほうが,ステージの難易度も調整できます.具体的には,wall_xとwall_yを配列にすれば良いです.

  • クリア時の演出をつける
    今は一瞬だけClear!の字がでるだけです.もっと派手な何かを考えると面白いです.

  • ステージ制にする
    ステージクリア制のゲームにすれば,もっと長く遊んでもらえます.各ステージについて壁マスの位置を設定するなどが考えられます.ステージに応じてマス目の数を変えれば,面白いかもしれません.

  • BGMをつける
    これは優先順位が低いかもしれませんが,やはりBGMの有無では雰囲気が変わってきます.

  • 塗ったマスに透明度を与える
    透明度を設定すれば,若干マス目が透けることになります.背景として何かの絵みたいなものを置いておけば,塗れば塗るほど後ろの絵が浮かび上がってくるような演出が可能になります.
    透明度の実装は,fill( )の4つ目に値を設定します.

他にもいろいろあると思うので,考えてみてください!
ありがとうございました.

他の最小限ゲーム制作シリーズ

本記事では一筆書きを扱いましたが,他のゲームについても同様の粒度で説明しています.合わせてご覧ください.

gotutiyan.hatenablog.com

gotutiyan.hatenablog.com

HackerRankでコンテストを主催する方法

競技プログラミングの世界では,様々なサイトでコンテストが開かれています.具体的には,AtCoderCodeForcesなどです.その中でも,HackerRank(ハッカーランク)というサイトは,自分でコンテストを開けることが特徴です.今回はその具体的な方法について説明します.

なお,ハッカーランクへの登録は済んでいるものとします.

www.hackerrank.com

目次

大まかな流れ

コンテストの作成にあたっては,以下の4つのステップで行えます.
1. コンテストを作成する
開始時間など,コンテストの詳細を決めます.
2. 問題を作成する
実際に解かれる問題を作成します.
3. コンテストに問題を登録する
1.で作成したコンテストに,2.で作成した問題を登録します.
4. 参加URLを配布する
コンテストの参加者に,参加用のURLを配布します.

コンテストを作成する

まずは新しいコンテストを作成します.自分のユーザーIDからプルダウンされるメニューから,Administrationを選びます.

f:id:gotutiyan:20200103224923p:plain

次に,緑のボタンのCreate Contestを選びます.
f:id:gotutiyan:20200103225026p:plain

次の画面ではコンテストの情報を入力します.これらの情報は,後からでも変更できます.
* Contest Name: コンテストの名前です.
* Start Time: コンテストの開始時間です.日をカレンダーから,時間を15分刻みで設定できます.
* End Time: コンテストの終了時間です.Start Timeと同じ要領で,時間を設定します.
* Organization Type: 組織の形式を選びます.よく分からなければ,otherで良いでしょう.
* Organazation Name: 組織の名前を入力します.

これで,コンテストの作成は完了です.

問題を作成する

問題情報の作成

ここでは,実際にコンテストに出題する問題を作成します.

まずは,コンテストの作成と同じようにAdministrationを選びます.

f:id:gotutiyan:20200103224923p:plain

次に,Manage Challengeのタブを選び,緑のボタンのCreate Challengeを選びます.

f:id:gotutiyan:20200103183350p:plain

その後,問題の情報を入力します.全ての欄を埋めないと完了できないので注意してください.また,ほとんどの欄では,$\frac{a}{b}$などのTex形式の数式表記をサポートしています.
* Challenge Name: 問題名です.
* Description: 問題の説明ですが,特に公開されないので,自分向けの説明を書きます.
* Problem Statement: 問題文です.
* Input Format: 入力形式です.
* Constraints: 入力の制約です.
* Output Format: 出力形式です.
* Tags: 問題のタグです.1D-arrayなど,主に解法に関するものです.

以上で,コンテスト情報の作成は終了です.

テストケースの作成

次にテストケースの作成に入ります.これはTest Casesのタブで行えます.

f:id:gotutiyan:20200103185413p:plain

テストケースの追加は,ブラウザ上で行う方法と,ファイルアップロードによる方法の2通りの方法で行えます. また,これより先では,既にテストケースとなる入出力のセットは作成済みであるものとしています.

1. ブラウザ上で行う方法
この方法は,ブラウザ上で行えるので直感的に分かりやすい方法です.しかし,あまり大きなデータはこの方法ではアップロードできないため,その場合は2.の方法を用いる必要があります.

まず,緑色のボタンのAdd Test Case を選びます.

f:id:gotutiyan:20200103225227p:plain

そして,inputとoutputの欄に,用意した入出力のセットをそれぞれ貼り付けます.

f:id:gotutiyan:20200103233029p:plain

また,用意したテストケースのうち少なくとも1つは,サンプルとして設定しなければなりません.これは,上部に表示されるSampleのチェックボタンをクリックすることで行えます.サンプルに設定したテストケースは問題文中に表示されるため,できるだけ簡単な(=サイズの小さい)データを用いたテストケースが望ましいです.

以上の操作を,用意したテストケースの数だけ行います.

2. ファイルアップロードによる方法
この方法の利点は,複数のテストケースを一度にアップロードできる点や,データのサイズが非常に大きくなってもアップロードできる点です.

まずは,Upload Zipを選びます.

f:id:gotutiyan:20200103234239p:plain

次に,この方法を使えば,既にアップロードされているテストケースは消滅しますがよろしいですか?という内容のダイアログが出てくるので,Continueを選びます.

その後,zipファイルを選択すれば,作業は終了です.

f:id:gotutiyan:20200103234957p:plain

このときのzipファイルは,inputフォルダとoutputフォルダを中身に持つものでないと受け付けてくれません.さらに,inputのフォルダには必ず"input+数字"で始まるファイル(input01.txt など),outputのフォルダには"output+数字"で始まるファイル(output01.txt など)を置く必要があります.この時,input01.txtとoutput01.txtのように,同じ数字を含むファイル同士が入出力のセットとして扱われます.

f:id:gotutiyan:20200104000413p:plain

以上で,問題の作成は完了です.

コンテストに問題を登録する

いよいよ,先ほど作った問題をコンテストで出題する問題として設定します.

再びAdministrationから,対象のコンテストの管理画面に行きます.そして,Challengesのタブに移動します.

f:id:gotutiyan:20200104001501p:plain

次にAdd Challengeのボタンを押して,Nameのところに問題のタイトルを打ちます.そうすれば,問題の名前が推薦されるので,それを選びます.

f:id:gotutiyan:20200104001636p:plain

この作業を,出題する問題の数だけ繰り返します.これにより,コンテストに問題を登録できました.

参加URLを配布する

作ったコンテストに参加して,問題を解いてもらうためには,コンテストのURLを配布する必要があります.参加URLは,コンテストの管理画面に表示されているURLのことです.これを参加予定の人たちに配布し,コンテストの開始を待ってもらってください.

f:id:gotutiyan:20200104002137p:plain

このURLを踏めば,コンテストの開始時間までのカウントダウンがされているような画面に飛ばされます.参加者には,この画面で待機してもらってください.開始時間になれば,自動的に問題を解くことができるようになります.

以上で,コンテストの開催方法についての説明は終わりです.

発展的な設定

ここからは発展ですが,コンテストに関する詳細な設定についてです.

リーダーボードにおける順位づけについて

この設定は,コンテスト管理画面のAdvanced Settingsタブより行えます.
リーダーボードとはつまり順位表です.順位づけの方式は,以下の2つから選択できます.

  1. ハッカーランクのデフォルトのリーダーボード
  2. ACM形式のリーダーボード

筆者が2.しか使ったことがないので1.については分かりませんが,2.はICPC形式で,解いた問題数が多いほど勝利,問題数が同じ時,回答時間が早い方が勝利,というものになります.誤答した時のペナルティや,順位表の凍結に関する設定もできます.

言語ごとの実行時間制限の編集

実行時間制限は,問題ごとに行えます.よってこの設定は,問題の管理画面の,Languagesタブより行えます.

各言語について時間とメモリの制限をシークバーにより入力し,必ず右上の保存のボタンを押して終了してください.各パラメータは上限と下限が決められているので,無限に時間をかけても良い,というような設定はできません.

f:id:gotutiyan:20200104161139p:plain
時間とメモリの設定(四角囲み)と,保存ボタン(矢印)

モデレータの説明とその追加

コンテストの開催にあたっては,出題予定の問題を本当に解けるかどうかを確かめる必要があります.この作業には作問者だけではなくて,少なくとも1人は別の人を呼んできて行うのが望ましいです.モデレータの機能を使えば,このような問題の確認作業が簡単に行えます.

モデレータは,コンテストと問題の両方でつけることができます.具体的には,コンテスト管理画面,もしくは問題の管理画面のModeratorsタブで,モデレータにしたい人のIDを入力します.

f:id:gotutiyan:20200104164745p:plain

問題に対してモデレータに登録された人は,その問題の編集権,およびソースコードの提出が行えます.また,コンテストに対してモデレータに登録された人は,そのコンテスト情報の編集権,およびコンテストに登録された各問題に対してソースコードの提出が行えます.これらはもちろん,コンテストの開始時間の前でも行えます.

終わりに

今回は,HackerRankでコンテストを開催するための方法について解説しました.

www.hackerrank.com

カメさんぽ攻略Tips集

はじめに

カメさんぽを知らない人はこの記事にたどり着いていない気がしますが、これはカメさんぽの記事です。カメさんぽはカービィで有名なハル研究所のゲームで、歩数によってカメ達と仲良くなるゲームになっています。

本記事では、ゲームを進める上で知っていると有利になることや、知っていると面白いことなどを中心に、Tips集みたいなのを目指してみます。特に,戦略系(=知っていると有利にゲームを進められるもの)と,面白系(特にゲームの進みに関係ないが,知っていると楽しめるもの)に分けています.

記事の信頼性のために筆者の進行度を書くと,図鑑は全て埋まり,仲良し度は(3月3日のアプデ以前のカメは)全てMAX,さんぽレベル54,勝利回数352です.(2020/03/05時点)

記事の中には一部ネタバレが存在しますのでご注意ください。
また,執筆時期の関係で,句読点がコロコロ変わっていますが,ご容赦ください.

この記事の執筆にあたっては,研究室の先輩にも結構な話題の提供をしていただきました.この場を借りてお礼をさせていただきます.

1. 戦略系

1-1. ブースト vs 釣竿

散歩に勝つために欠かせないのが、ブーストと釣竿です。ここでは,特に序盤について,どちらを強化するべきかを検討します.これらは,どちらもコインを用いて強化することができて、ブーストの最大レベルは30、釣竿の最大レベルは未知(40レベルを超えることは確認)です。最終的にはどちらも最後まで強化するので問題ないのですが、特に序盤はどちらを強化するか悩みどころです。両者の特徴を以下に挙げます。

  • 釣竿

瞬間的に大幅な歩数を稼ぐことがメリット(相手の歩数を減らす=自分の歩数を増やすと考えて問題ないため)。散歩開始直後に使っても意味がないところがデメリット。レベル30で1350歩。

  • ブースト

1時間もの間、歩数を大幅に増やせることがメリット。発動には広告を見る必要があることと、得られる効果が,結局は自分の歩き次第で変わるところがデメリット。レベル30で200%。

どちらが強い効果なのかを検討するに当たっては,強化に必要なコインが判断材料になります.ゲームの開発陣からすれば,より強い機能ほど解放させたくないので,必要コインは高くなるはずです. ここで,レベル29を30にするのに必要なコインは,ブーストが445(多分),釣竿が490コインです.よって,釣り竿の方がより強いということが言えそうです.

ですが、この記事ではあえて,ブーストをお勧めしたいと考えています。

この理由は,ロケットブーストは広告によるブーストとの兼ね合いができることがあります.広告ブーストは歩数を1.5倍~2.0倍にできるものです.ロケットブーストはレベル最大で200%,そこから広告により最大で2倍にできるので,合わせると4倍になります.個人的には,これは釣竿よりも高い効果があると考えています.

一方で,普段から本当に歩かない人は,釣竿をお勧めします.歩かないことを前提としたときでは,ジェムを使いまくってでも釣竿を連打する他ないからです.

広告ブーストが発生する歩数

アプリを落とした状態で歩くと、起動時に広告による倍率がかかります。この倍率は、1.5倍をデフォルト値として、1回の散歩中であれば上昇して行き、1.5, 1.6, 1.7, 1.8, 1.9,最後には2.0倍まで上がります。この発生条件の話です。

上記の「アプリを落とした状態で稼いだ歩数」を「貯めた歩数」と呼ぶことにすると、貯めた歩数が極端に少ない場合は広告ブーストが発生しません。この広告ボーナスが発生する歩数の境目はまだ特定できていません。というのも、277歩で発生し、295歩で発生しないケースが確認できたので、一概に境目を特定できなくなりました。ただし、280歩を超えれば確実に発生するということは言えそうです。

広告ブーストの蓄積

貯めた歩数は,一度アプリを開いてしまっても累積できるという話です。
いま、「貯めた歩数」(広告ブーストが発生する歩数を参照)が500歩であるとします。ここで、ゲーム内の選択肢としては「そのまま」、もしくは「1.5~2.0倍」というどちらかが与えられます。しかし、ここでタスクキルをすれば、その500歩は「貯めた歩数」として維持できるということです。ここからさらに500歩歩けば、貯めた歩数は1000歩になり、広告ボーナスも1000歩に対してかかってきます。

これを応用すると、ギリギリを攻めすぎて広告ブーストが発生しない歩数(250歩とか)だった時、そこで諦めるのではなくてタスクキルをしてから+αだけ歩くことで、効率的に広告ブーストを拾っていけるということが言えます。

代走による広告ブーストの倍率上げ

仲良し度MAXの星1のカメを代走させると,10分で280歩になります.この歩数は,確実に広告ブーストが発生する歩数になっています. 10分で1段階の倍率を上げることができるので,1時間あれば,広告ブーストを簡単に2倍まで持っていくことができます. ただし,10分ごとにアラームをかけるなどして,アプリの起動を忘れないようにする工夫は必要です.

仲良し度と代走の歩数の関係

代走の歩数は,仲良し度1つにつき,約200歩増えます.

降参による代走時間の短縮

仲良し度は、散歩をして勝利するか、チケットと引き換えに代走を頼むことで上げることができます。前者ならハート一個分、後者はハート1/4に相当します。対戦中のカメと同じカメを代走させることはできません。

さて、このうち代走による仲良し度の上昇についてです。まず、代走の終了条件には以下の3種類あります。
1. 指定の時間経過する
2. プレイヤーが強制的に終了させる
3. 相手のカメの降参を受け入れる
実は、このうち1と3について仲良し度が上昇します。つまり、対戦中のカメが降参した瞬間に代走を頼み、その後すぐに降参を受け入れます。こうすることで、代走時間をかけることなく,カメの仲良し度を1/4上昇させることができます。

仲良し度のために引き直しをしない

図鑑を全て埋めれば,後は仲良し度を上げることとなります.また,ほいほいの引き直しでも,仲良し度がMAXではないカメが優先して出てくるようになります.ここでは,仲良し度を上げるための,特にジェム効率に特化した内容を紹介します.

実はチケットというのはジェムを使って買うことができます。交換レートは1対1です。5枚のチケットが欲しければ5ジェム必要です。普段は買うことはできませんが、所持数以上のチケットを使おうとした時にジェムを消費して交換することができます。

さて、いま、「ほいほいして仲良し度を上げること」と、「代走によりチケットを上げること」を比較してみます。以下では、チャレンジカメは考慮していません。

ほいほい:ジェム20個(引き直せば50個)を使って、仲良し度をハート1つ分増やす。 代走:チケット最大5枚使って、仲良し度をハート1/4分増やす。

ここで重要なのが、レア3のカメ(=代走チケット5枚)としても、両者のチケット効率が等しくなることです。ほいほいして,20個のジェムを消費し1つのハートを得ることと、ジェム20個をチケット20枚に変えて代走させて、1/4のハートを4回得ることは同じです。よりレア度の低いカメを対象にしたときや,引き直して結局50ジェム消費することを考えれば,よりたくさんの利点があることがわかります.

この手法のデメリットは、代走に費やす時間が非常にかかることです。前述した降参による代走時間の短縮を用いても難しい部分があります。が、それにより得られるジェム効率としては目を見張るものがあると考えています。仲良し度の上昇を目的とした、ほいほいを引き直す時代は終わりです。(図鑑埋めのためにはどんどん弾き直しましょう。)

バリケードで減る目安歩数

バリケードは1時間相手を足止めし、これによって若干目安歩数が下がります。相手のカメの(元々の)目安歩数は24時間で到達する最大値なので、1時間足止めすれば、目安歩数の24分の1を減らすことができます。例えば,目安歩数が24000歩のカメなら,バリケードを使えば目安歩数を1000歩減らすことができます.

ハルエッグを持ったウラシマ

ウラシマと対戦すると,たまにハルエッグを持ったウラシマと戦うことができます。このウラシマに勝つといつもより多めに報酬がもらえます.特別なウラシマに対する報酬は以下の通りです.

ジェム コイン チケット
勝ち 30 80 8
負け 15 30 4
デフォルト(比較用) 10 30 3

このウラシマが出現する条件は良く分からなくて、それなりに出る人もいれば、40回程度戦ってもでない人もいます。○回に1回出るとかそういうのではなくて、確率が低いのだと思います。

ハルエッグなウラシマかどうか毎回確認できるという話

上述したように,ハルエッグを持ったウラシマの利益はハンパないです.実は,ウラシマがハルエッグを持っているか確認して,持っていなければ ほいほい をする,持っていれば そのままウラシマと歩く,というようなことができます.

この方法は簡単です.このゲームは,右上の歯車のマークからオプションを開くことができて,その一番下に「競争を中止」というボタンがあります.このボタンはその名の通り,競争を中止できます.これを利用し,まずは一旦ウラシマに対戦を挑んで,ハルエッグを持っていなければ競争を中止して, ほいほいをすれば良いのです.おそらくウラシマがハルエッグを持っているかどうかは確率なので,試行回数が増えるだけでも効果は大きいと考えています.

一方で,「ウラシマに散歩競争を挑む→競争を中止する」ことを連続で繰り返せば,いずれハルエッグなウラシマに巡り合えるかといえば,それは分かりません.おそらく確率で選ばれている気がしますが,最初の一回で持っていなければ,それ以降はずっと持っていないのかもしれません.

なお,ハルエッグなウラシマを引き当てた場合に,競争を中止し,再度ウラシマに競争を挑むと.持っていなかったということはありました.

報酬は変わる

散歩に勝つとイナバから報酬をもらえますが,これは何かを境に,コインが増えます.
序盤は(ジェム,コイン,チケット)=(10,30,3)ですが,いずれ(10,40,3)になります.
何が基準でこの変更が起こるかは分かりません.(いつの間にか変わっていました...)

2. 面白系

タップしまくると何かが起こるところ

  • 散歩画面の人間と相手のカメ
  • 右下にある歩数を書いた看板

時間帯でセリフが変わる

散歩相手のカメは色々セリフを言いますが、実は時間帯を意識したセリフになっています。朝なら「眠そうだね」(意訳)、夜なら「こんな時間まで散歩してるのか?」(意訳)みたいなことを言われるときがあります。

目安歩数の表示

目安歩数は,釣竿を使ったりバリケードを置くことで減少します.ここに表示される歩数は,実際の目安歩数の10の位を四捨五入したものになっています.
例えば,目安歩数が5000歩のカメに1050歩の釣り竿を使用した時,1回目は
5000-1050=3950 で,四捨五入して4000歩が表示されます.もう一度使うと,
3950-1050=2900 で,四捨五入して2900歩が表示されます.
このように,見た目上は5000→4000→2900で,途中で減らせた歩数が変わったように見えますが,上記のように説明がつきます.

報酬受け取りの時のイナバ

カメに勝っても負けても,イナバから報酬をもらえます.ここで,イナバをタップすると色々な反応をするので可愛いのです.触るところによっていくつか反応に種類があるので,紹介します.
・耳:左右で若干変わります
・目(額)
・ほっぺ:左右で若干変わります
・口
・手:左右それぞれ反応します

3. ガラクタデータベース

ラクタは1時間に1度出現し、5個貯めるとダイヤが2個もらえます。
ラクタは、カメさんぽに登場するいずれかのカメに由来するものとなっています。ここではそれを見ていきましょう。

ラクタ名 誰のものか 備考(「誰のものか」の根拠など)
バリケード 誰のでもない 目安歩数を1/24減らします
はたらくUFOのぬいぐるみ 誰のでもない ハルエッグの別のゲーム「はたらくUFO」の友情出演
はっぱが生えたタマゴ ウラシマ いわゆるハルエッグ
つぶれたヨーヨー ウラシマ いつも手に持ってますね,たまに卵になるけど
タツムリのカラ カメツムリ カラを捨てて甲羅にしたっぽい
電動二輪の取扱説明書 カメーイ 電動二輪=セグウェイ
ピンクの花かざり フラーラ いつも頭につけてます
チューンガムの包み紙 ブルー 口から風船ガム出してます
ペロペロキャンディの棒 カメノリ いつも食べてます
はずかしい歌詞カード カメケロ 図鑑の説明に歌関係のことが書いてます
こげた食パン トータスー 焦がしちゃったのね
三輪車のサドル ヤンカメ 乗ってます
みずみずしいサボテン カメぞう いつも手押し一輪車に乗せてます
こわれたプロペラのハネ カメコプター お尻につけてます
折れた自撮り棒 カメメ 図鑑でタップすると自撮り棒を出します
ポリスっぽいバッチ ジョン 当然こいつしかいない
脱ぎ捨てられた囚人服 ホワイト 囚人ってバレるもんね
オフィスチェアのタイヤ カメヤマ課長 座ってます
派手なフリルのカサ カメリーヌ 持ってます
ジェントルなパイプ ジェントルさん いつも口に加えてますね
賞味期限切れドッグフード ポチ 自分のエサですね
ネコジャラシ ニャカメ ずっと持ってます
飲みかけスポーツドリンク 亀田川あゆみ 一番飲んでそう
ツノみがき用のワックス トリカメトプス ツノといえばこの子しかいないような
オモチャのバトン マーチブラザーズ オモチャだったのか
友カメ募集のチラシ K. レックス 図鑑の説明に友カメの記述があります
おさいせんばこ ロングイ 手に持ってますね
真っ赤なゼリー スッポン 図鑑画面でタップするとゼリーを食べます
鼻をかんだティッシュ スーぽん 図鑑でタップすると鼻をかんでます
ミカンの皮 おこた こたつの上にみかん乗ってます
タテガミのようなカツラ サバンナ サバンナのキャラ説明の最後の文がそれです
サメの歯のような石 シャーくん それは石ではなく歯なのでは...?
御朱印帳の落し物 おへんろさん 神社を巡る「お遍路」では,各神社の到達記念に御朱印をいただけるのです
学生服の第二ボタン カメバアチャン おぶってる優しい少年のボタン
バターのかけら カメープル 頭に乗ってます
ホウキの枝毛 マタマタ ホウキ持ってます
ほんのりクサい寝袋 ねぶくろう ちゃんと洗ってね
でろでろのおしゃぶり ちびかめちゃん ちびかめちゃん,実はおしゃぶり咥えてないんですよね
カメサイズのダンボール箱 ハコスキー この子しかいない
小さなカメ人形 パペットル 操ってます
古びたサーカスのポスター Mr.ショーマン サーカス関係なので
穴のあいた虫アミ カメレオン 獲物逃げるもんな
使用済みのストロー カメ? 図鑑でタップするとジュースを飲みます
謎の任務が書かれたメモ エージェントG どんな任務なんだろう
チーム青カメ
この星のものではないネジ カメリアン ネジ取れてますよ
またがるウマのおもちゃ フィリップ これ無くして大丈夫なのか
秘伝の書 カメまる 忍者っぽいアイテムなので
車のキー 亀井沢こうへい 車乗ってるのは こうへい しか居ない
ありがたい短歌 かめのもとひとまろ ありがたい 短歌の中身 気になるな
高級そうなティアラ クイーンマリア かぶってます
宛先不明のラブレター ポストマン 誰かの大事な手紙を落とすとは・・
ささくれた竹馬 トテトテ 乗ってます
ちぎれたナワトビ トビィ 持ってます
カメブー
割れた虫メガネ ハカセ 持ってます
ジャンボ
ハミスケ
方角がデタラメな地図 チズ 持ってます
黄色いスマホケース クー太
おふとん
ナマケガメ
カメトビペンギン
ハート型のサングラス フラミ かけてます
赤い布きれ ハラカゲ お腹(首?)に巻いています
ボロボロのトゥーシューズ カメントニオ バレエでよく履かれている,つま先立ちできる靴
ダイビング用ゴーグル キッスィー つけてますね
カメコフきょうだい
アンク
漢字だらけのおふだ キョンピー 頭につけてるやつかな
ケロデューナ
ダッピィ
メカメ
チャリオ
イナバ