質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.00%

テトリス クラス設計の考え方

解決済

回答 5

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 4,193

mightyMask

score 108

前提

オブジェクト指向を用いたテトリスの設計
ここで質問させていただいた者です。
この続きの質問です。これは見ても見なくても結構です。

ブロック4つを一まとまりにしたものをテトリミノと呼びます。
テトリミノは7種類あります。
テトリミノには回転軸がブロックの中心に存在するものと、交差点に存在する物の2種類あり、以下の図の赤い部分が回転軸です。
イメージ説明

質問内容

テトリミノのクラスの設計はどの様にするのが上手な設計でしょうか。

私は以下の様にテトリミノが存在する座標と、ミノの形を持つ二次配列を持たせる設計をしていました。

int x = 3, y = 1;
boolean[][] shape = new boolean[][]
    { { true , true , false },            //    ■■□
      { false, true , true  },            //    □■■
      { false, false, false } };        //    □□□


この設計だと、例えば右回転をさせたいなら以下の様なアルゴリズムで簡単に処理することができます。

//  Boolean2次配列をコピー
private boolean[][] copy( boolean[][] args )
{
    boolean[][] ret = new boolean[args.length][args[0].length];
    for( int x=0 ; x< args   .length ; x++ ){
    for( int y=0 ; y< args[0].length ; y++ ){
        ret[y][x] = args[y][x];
    }
    }

    return ret;
}

//  右回転
void rotateRight()
{
    boolean[][] temp = copy( shape );
    for( int y = 0 ; y < shape.length ; y++ ){
    for( int x = 0 ; x < shape.length ; x++ ){
        shape[y][x] = temp[shape.length -x -1]y];
    }
    }
}


しかし、オブジェクト指向的な設計を目指すなら、ブロックを4つ持っていると言う方が良いですよね。

ブロックが保持する内容は以下の二通りが考えられますが、いずれも問題点があり、私には良い解決方法が見当たりません。

1.フィールドの中のどこに位置しているかを表す座標
この場合、回転させるという処理を書くのがすごく大変です。
2.回転軸からの相対座標(この場合、テトリミノが回転軸の座標を保持)
この場合、回転させる処理は簡潔に書けそうですが、回転軸がブロックの中心にあるものと、交差点にあるものにより、保持する内容が異なってしまいます。

いずれかの良い問題解決の方法、それがないならこの二つ以外に良い方法があれば教えてください。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • mightyMask

    2016/11/24 23:41

    ますます意味が分からないです。さらにとは何に対してのさらにですか?ブロックによって変わるならってのは回転軸のことですか?何が8×8なのですか?テトリミノの形を表すbooean配列のことですか?あなたが先ほどから言っている事はおかしな事だらけです。

    キャンセル

  • mightyMask

    2016/11/24 23:48

    それから、なぜ修正依頼の場所に書き込んでらっしゃるのでしょうか?普通は他の方のように回答に載せるものじゃないのですか?

    キャンセル

  • 退会済みユーザー

    退会済みユーザー

    2016/11/25 07:55

    8*8あれば3*3も4*4も回転軸をど真ん中に持っていける。 (一マス2*2を使う)

    キャンセル

回答 5

+4

テトリミノのクラスの設計はどの様にするのが上手な設計でしょうか

テトリスのような仕様(公式ルール)がすでに固まっていて、
シンプルなゲームは、シンプルに設計するのが良いと思います。

逆に、キャラが追加されるたびに仕様が拡張されていくカードゲームなどは、
仕様を変更しやすいよう、多少複雑になっても、コアの抽象度を高く設計します。


オブジェクト指向的な設計を目指すなら、
ブロックを4つ持っていると言う方が良いですよね

そうでしょうか? 正解が必ず決まっているというより、作るものにもよります。

今回、テトリスのブロックは形が違うだけですし、7パターンしかないので、
(練習で作るのであれば)現状のような二次元配列の座標でも十分だと思います。

しかしもし、これがたとえば、ブロックの種類がそれぞれ異なっていて、
同種類のブロックが並ぶと連鎖で消せるような、より複雑なルールなら、
ユニットがブロックを持つ(合成する)設計を採用するかもしれません。
ブロックに処理を委譲すると、再帰的に処理できるパターンがあるからです。

けっきょく、オブジェクト指向も手段なので、目的に合わせて使いわけます。


オブジェクト指向を駆使したものが
抽象度が高くなるのですか?(コメ欄)

プログラミングは書くより読む方が難しいので、修正コストが高いです。
とくに、大規模なプログラムを長い間少しずつ拡張するのは大変です。

変更コストを下げたい → 交換しやすくする →
(中心部の)抽象度を上げる → オブジェクト指向を使って設計する

ですから、結果的にはそうなるのですが、
上のような順番で根本には「変更コストを下げたい」という意識があります。

テトリスのブロックパターンが7つのまま固定なら、そんなに凝る必要はありません。
しかしもし、たとえば100パターンくらいに増やす予定なら、もっと抽象化します。


オブジェクト指向はシンプルな設計を
するのには向いていないのですかね?(コメ欄)

オブジェクト指向は、複雑な設計を「シンプル化」するのに向きます。
しかし、元からシンプルな設計対象に使うと、過剰設計になる場合があります。

たとえると、建物は木造よりコンクリの方が耐久力がありますが、田んぼにビルは建てませんし、
自転車より自動車の方が早いですが、歩いて5分なら車に乗る必要もありません。

同様にたとえば、ジャンケンゲームをオブジェクト指向で組むのは無意味に過剰です。
逆に、麻雀くらい複雑なルールだと効果がありそうです。テトリスは微妙なラインです。

ですから、オブジェクト指向などを駆使して高度な設計をするのは、
複雑性や不確定性に柔軟に対応するための、技術的な投資なのです。

オブジェクト指向が手続き指向の上位互換で、つねに勝るわけではありません。
プログラミングに「銀の弾丸」はなく、適材適所の使い分けがあるだけです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/11/23 04:20

    練習で作ると言っても、本番のつもりでやりたいというか、本来ならこうすべきという形にしたいと思っています。
    その様な機能をつけたくなれば、booleanの2次元配列をBlockクラスの2次配列にすることで、様々なルールに対応できそうです。やはり2次配列で持った方が良い気がしてきました。
    シンプルというのが、具体度が高く、オブジェクト指向を駆使したものが抽象度が高くなるのですか?オブジェクト指向はシンプルな設計をするのには向いていないのですかね?

    キャンセル

  • 2016/11/23 10:34 編集

    「最初から捨てるつもりの作品を練習のため作る」、これは悪いことではないです。

    これは人月の神話という40年くらい前の本で紹介されている考え方なのですが、業務として開発されるソフトウェアでも1回目に作ったものはまずい点が多い(設計面でも品質面でも性能面でも)ものなので、Ver.2で改善したものを出そうとするくらいなら最初から捨てるつもりのVer.1を作って作り直したものを真のVer.1として出そうという考え方、「パイロットシステム」です。

    実際、多くの開発者の経験でも完璧な設計からソフトウェアを作れるということはまれで、実際に作ってみることで完璧な設計を「発見」できるものであるという面はあります。

    キャンセル

  • 2016/11/23 22:10

    >mightyMaskさん
    コメント欄に書くと長くなってしまうので、ご質問の回答は本文に追記しました。

    キャンセル

  • 2016/11/23 22:20

    >yubaさん
    プロトタイプを作る考え方は私も好みです。

    事前設計で変化に完全に対応するのは難しいので、
    プロトタイプやリファクタリングなどで、
    事後的に再設計するのは有効だと思います。

    最初から抽象的で汎用的な設計を目指すと、たいてい上手くいかなくて、
    何回も同じように書いていたプログラムを抽象化、ライブラリ化する、
    発見的なアプローチの方が上手くいった経験が実際にも多いです。

    最初は「YAGNI」で書いてみて、
    後から「DRY」に書き直していくのが良いと思います。

    キャンセル

checkベストアンサー

+1

これはクラス設計というよりデータ構造設計と表現することが多い分野になると思いますが、答えはわりと簡単です。

回転軸という概念は設計者の頭の中にだけおいておいて、プログラムに回転処理はさせないのが最もシンプルです。最初から各テトリミノが4種類のビットパターンを持っていてそれを切り換えるだけとすれば十分ですから。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/11/23 03:10

    その方法ももちろん考えたのですが、後から機能を付け加えたい時に楽に実装できるのがいい設計だという事を踏まえると、ちょっと違うんじゃないかと思いました。
    例えば、後から上の7個以外の形のブロックも追加したいと思った時、1種類だけの記述で済むか、4種類とも記述しなくてはいけないかという事が変わると思うのです。

    上向きの形が分かっていれば、後は右、下、左向きの形を生成するアルゴリズムを組めば良さそうですかね。コンストラクタでこの処理を行い、後は4種類の形を保持し続ける。
    でもそうなると、元のソースとそこまで変わらない様な...

    ビットパターンという事はテトリミノの形を配列で保持するという事ですよね?
    オブジェクト指向的に考えるならテトリミノオブジェクトが4つのブロックオブジェクトを持っているとしなくてはならない気がします。

    キャンセル

  • 2016/11/23 03:23

    > 後から機能を付け加えたい時に楽に実装できるのがいい設計

    はい、教科書には必ずこう書いてあります。しかしこれが言うは易く行うは難しの典型で、まったくもって簡単な話ではありません。
    何が難しいかというと、後からどんな機能を付け足したくなるか、最初の開発の時点では想像もつかないということです(もし想像できているのならそれを拡張性の話ではなく最初からそれを仕様に盛り込むだけの話ですから)。

    いま例として、「7個以外の形のブロックを追加」という拡張が出てきました。でもこんな拡張はどうですか? 「今の方向によって回転軸が変化するようなブロックを追加したい」「向きによって形が変わるブロックを追加したい」⋯ こんなのが出てくると、回転処理を組み直すくらいなら最初から4パターンを格納しておく方式の方が柔軟性が高かったとも言えます。
    (後付けでこんなこと言うのもあれなんですが、一応こういう柔軟性を考えた上でのご提案でした)

    > テトリミノオブジェクトが4つのブロックオブジェクトを持っているとしなくてはならない気がします。

    はい、そうなります。ただ、これはオブジェクト指向とあまり関係ありません。ごく普通のデータ構造です。

    キャンセル

  • 2016/11/23 04:10

    後付けでもその様な事はとても参考になります。
    向きごとに回転軸が変わるのはライフゲームの様になりそうなので置いときますが、向きごとに形が変わるブロックを追加という変更なら、コンストラクタでそれぞれの向きの形を生成する方針なら割と融通が利きそうですね。今までのソースでは対応できなかった事ですので参考になりました。

    キャンセル

+1

自分が思い浮かぶことをコメントしてみます。(とりとめない感じですがご容赦です)

  1. テトリミノとパターンの分割
    テトリミノに2次元の配列(=パターン)を持たせるということは彼の仕事がより複雑になると思えるので、最初にテトリミノと、その現在のパターンを別の役割を担うものとして設計を分離すると思います。そしてテトリミノがパターンに何をどう要求するかをパターンの実装になるべく左右されないように考えようとします。例えばテトリミノがパターンにしてほしいのは「回転したらどのパターンになるの?」と「今のパターンの座標は(中心がここだとしたら)どこになるの?」です。後者はストリームで返してくれればよいでしょう。それはフィールド上へパターンを描画するのにも使えますし、フィールド上に既に存在している別のテトリミノとの衝突判定にも使えるでしょうから。分離によりテトリミノ・パターンの双方にとって自分がやるべき仕事をより単純化して考えることができるようになると思います。

  2. パターンの回転
    自分は2通りをイメージします。1つは質問者さんのイメージと同じで回転に伴い自身の状態を変更する方法です。もう一つはありえるパターンの全てをimmutableなオブジェクトと考える方法です。immutableなので自身の状態は変えず、「右に90度回転させたら自分はどのパターンになるか」だけを覚えさせます。
    後者を想定するのは別の動機もあり「全てのパターンの定義をソース上に自分自身で書くのが苦痛」なので計算機に計算させたいという根源的な欲求です。全てのパターンを計算すればその過程で位相のバリエーションも考慮するはずなので「どうせ回転も含め全てのパターンの列挙を最初にやるのだし、それならゲーム中に回転計算しなくていいからお得」と考えます。発想が逆かも知れませんがパターンの中心は機械的に計算するのに都合のよいものと仮定してパターン列挙の計算をすると思います。(オブジェクト指向と関係ない話ですね)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/11/23 03:53

    テトリミノとパターンの分割。これは思いつきませんでした。テトリミノの動きが複雑化してきたらやはりこの様に分けるべきかもしれません。配列はパターンが持つべきでしょうが、中心座標がどこになるかというのもパターンに聞くのですか?
    ストリームで返すというのもよくわかりません。座標をテトリミノが持つデータに合わせるという事でしょうか?これをどうやって衝突判定に使うのかわかりません。
    またこの二つのクラスの関係はオブジェクト指向的にいうとどの様な関係にすれば良いでしょうか。テトリミノがパターンが持つ機能を持つという事でテトリミノがサブクラスになるのでしょうか。

    キャンセル

  • 2016/11/23 08:50 編集

    フィールド上のどこにいるかという座標はテトリミノが持ちます。パターンの中心がどこかはテトリミノは意識する必要がないように作れるかなというイメージです。ストリームはオブジェクト指向と関係ない話ですがjava.util.stream.Streamのことです。te.pattern.points(currentPoint)とやるとStream<Point>が結果となるような感じです。衝突判定はte.pattern.points(currentPoint).anyMatch(point -> gameField.そこにブロックある(point))みたいな使い方になります。関係はテトリミノ has a パターンです。継承関係じゃなく機能を分割した別クラスをイメージしました。

    キャンセル

+1

github tetris で検索して、いろいろな実装を調査してみると面白いと思います。

検索結果例からの紹介

-【プログラミング】テトリスを1時間強で作ってみた【実況解説】 http://www.nicovideo.jp/watch/sm8517855
35 分辺りに block の定義の作業がある。
ソースコード全体は https://github.com/DQNEO/CppTetris

typedef struct _TAG_BLOCK {
    int rotate;
    POSITION p[3];
} BLOCK;


ここでは、 
rotate: 回転種類の個数
p[3]: [0,0] のマスからの 残り3 つのマスの相対位置
回転は  90 度の回転を 繰り返すようにしている。(90 度回転を行列の計算でおこなう)

for(int j = 0; j < r; j++) {
  int nx = dx, ny = dy;
  dx = ny; dy = -nx;}

これはブロックの形の追加にも対応し易い構造だと思います。
回転中心は、常に (0,0) で、そこに対して、複数個のセルをどこに置くかを定義してやるだけです。
(質問文にあるような回転中心の定義にするためには、このプログラム例でのものをすこし変更してやる必要がありますが)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/11/24 23:25

    この構造そのままだと、回転軸がブロックの中心に存在するものにしか対応できていません。
    もうすこし変更してやる必要があると仰いますが、どのような変更を加えれば2パターンに対応できる様になるのでしょうか。

    キャンセル

0

どこを中心に回転させたいっていうのは、定義する必要があると思いますが。
ブロックのパターンの配列、その中心点、二つ持って処理するのが最も単純でしょう。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/11/23 02:15

    前提に回転軸の定義を載せました。
    ブロックのパターンの配列は私がやっていた様なやり方で大丈夫でしょうか。
    中心点はどの様に持たせるのが良いのでしょうか。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.00%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る