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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

Q&A

解決済

5回答

9630閲覧

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

mightyMask

総合スコア143

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

0グッド

1クリップ

投稿2016/11/22 15:38

編集2016/11/22 17:12

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

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

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

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

java

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

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

java

1// Boolean2次配列をコピー 2private boolean[][] copy( boolean[][] args ) 3{ 4 boolean[][] ret = new boolean[args.length][args[0].length]; 5 for( int x=0 ; x< args .length ; x++ ){ 6 for( int y=0 ; y< args[0].length ; y++ ){ 7 ret[y][x] = args[y][x]; 8 } 9 } 10 11 return ret; 12} 13 14// 右回転 15void rotateRight() 16{ 17 boolean[][] temp = copy( shape ); 18 for( int y = 0 ; y < shape.length ; y++ ){ 19 for( int x = 0 ; x < shape.length ; x++ ){ 20 shape[y][x] = temp[shape.length -x -1]y]; 21 } 22 } 23}

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

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

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

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2016/11/23 01:41

テトリスのブロックの回転軸は 4x4のブロック(あたり判定は違う)でやってますが。
mightyMask

2016/11/23 17:16

私なりに解釈しようとしましたが、意味がよくわかんないです。文章を素直に捉えると、回転軸=4×4のブロックってことですが。4×4の配列に凸型ブロックなどを入れてしまうと仕様通りに回転させる処理は難しくなりますし。
退会済みユーザー

退会済みユーザー

2016/11/23 22:10

さらにブロックによって変わるなら8*8だな
mightyMask

2016/11/24 14:41

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

2016/11/24 14:48

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

退会済みユーザー

2016/11/24 22:55

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

回答5

0

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

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

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


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

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

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

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

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


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

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

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

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

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


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

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

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

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

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

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

投稿2016/11/22 18:08

編集2016/11/23 13:09
LLman

総合スコア5592

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mightyMask

2016/11/22 19:20

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

2016/11/23 01:35 編集

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

2016/11/23 13:10

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

2016/11/23 13:20

>yubaさん プロトタイプを作る考え方は私も好みです。 事前設計で変化に完全に対応するのは難しいので、 プロトタイプやリファクタリングなどで、 事後的に再設計するのは有効だと思います。 最初から抽象的で汎用的な設計を目指すと、たいてい上手くいかなくて、 何回も同じように書いていたプログラムを抽象化、ライブラリ化する、 発見的なアプローチの方が上手くいった経験が実際にも多いです。 最初は「YAGNI」で書いてみて、 後から「DRY」に書き直していくのが良いと思います。
guest

0

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/22 22:58

katoy

総合スコア22324

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mightyMask

2016/11/24 14:25

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

0

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

  1. テトリミノとパターンの分割

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

  1. パターンの回転

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

投稿2016/11/22 18:11

KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mightyMask

2016/11/22 18:53

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

2016/11/23 00:04 編集

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

0

ベストアンサー

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

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

投稿2016/11/22 17:46

yuba

総合スコア5568

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mightyMask

2016/11/22 18:10

その方法ももちろん考えたのですが、後から機能を付け加えたい時に楽に実装できるのがいい設計だという事を踏まえると、ちょっと違うんじゃないかと思いました。 例えば、後から上の7個以外の形のブロックも追加したいと思った時、1種類だけの記述で済むか、4種類とも記述しなくてはいけないかという事が変わると思うのです。 上向きの形が分かっていれば、後は右、下、左向きの形を生成するアルゴリズムを組めば良さそうですかね。コンストラクタでこの処理を行い、後は4種類の形を保持し続ける。 でもそうなると、元のソースとそこまで変わらない様な... ビットパターンという事はテトリミノの形を配列で保持するという事ですよね? オブジェクト指向的に考えるならテトリミノオブジェクトが4つのブロックオブジェクトを持っているとしなくてはならない気がします。
yuba

2016/11/22 18:23

> 後から機能を付け加えたい時に楽に実装できるのがいい設計 はい、教科書には必ずこう書いてあります。しかしこれが言うは易く行うは難しの典型で、まったくもって簡単な話ではありません。 何が難しいかというと、後からどんな機能を付け足したくなるか、最初の開発の時点では想像もつかないということです(もし想像できているのならそれを拡張性の話ではなく最初からそれを仕様に盛り込むだけの話ですから)。 いま例として、「7個以外の形のブロックを追加」という拡張が出てきました。でもこんな拡張はどうですか? 「今の方向によって回転軸が変化するようなブロックを追加したい」「向きによって形が変わるブロックを追加したい」⋯ こんなのが出てくると、回転処理を組み直すくらいなら最初から4パターンを格納しておく方式の方が柔軟性が高かったとも言えます。 (後付けでこんなこと言うのもあれなんですが、一応こういう柔軟性を考えた上でのご提案でした) > テトリミノオブジェクトが4つのブロックオブジェクトを持っているとしなくてはならない気がします。 はい、そうなります。ただ、これはオブジェクト指向とあまり関係ありません。ごく普通のデータ構造です。
mightyMask

2016/11/22 19:10

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

0

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

投稿2016/11/22 16:45

mugicya

総合スコア1046

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

mightyMask

2016/11/22 17:15

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問