演習

演習1

二人のプレイヤーでじゃんけんを行うようなクラスを作成すること考えます。 「プレイヤー」は「戦略」を持ち、その戦略に従って「手」を出します。 手には Gu, Choki, Pa があります。 プレイヤーは「ボックス」において双方の手を出し、一回の勝敗を決めます。

このようなクラスを作成するため、以下の小問に答えなさい。

問1-1

以下のプログラムが動作するように、クラス Hand1 を作成しなさい。 なお、Hand1 のヘッダファイルは Hand.h, プログラムファイルは Hand1.m と しなさい。 必要であれば、さらにクラスを追加して良い。

プログラム test1.m


#import <Foundation/NSString.h>
#import "Hand1.h"
int main(void){
  int i;
  for(i=0; i<3; i++){
    Hand1* hand = [[Hand1 alloc] initWithType: i];
    NSLog(@"%@",[hand toString]);
    [hand release];
  }
  return 0;
}
実行結果
Gu
Choki
Pa

(但し、 NSLog の仕様により、各文字列の左側にプロセス番号などの情報が表 示される)

問1-2

まず、下記のクラスと interface を追加してください。

追加

Hand.h

#import <Foundation/NSString.h>
@class Hand;
@protocol Hand 
- (int) wins: (Hand *) h;
- (int) getType;
- (NSString*)toString;
@end
HandBuilder.h

#import <Foundation/NSObject.h>
#import <Foundation/NSArray.h>
#import "Hand.h"
@protocol HandBuilder
- (Hand*)newHand:(int)hand;
@end
@interface HandBuilder1 : NSObject<HandBuilder>
- (Hand*)newHand:(int)hand;
@end
HandBuilder1.m

#import <Foundation/NSObject.h>
#import "Hand.h"
#import "HandBuilder.h"
@implementation HandBuilder1 
-(Hand*)newHand:(int)hand{
  if(hand <0 || hand>2 ) return nil;
  return [[Hand1 alloc] initWithType:hand];
}
-(void) release
{
  [super release];
}
@end

そして、問 1-1 で作成した Hand1 のプロトコルとして Hand 指定し、さらに、 次の条件を満たすように getType メソッドと wins メソッドを実装しなさい。

getType
initWithType で与えられた値を返す
wins
[a wins:b] で、じゃんけんとして a が b に勝てば 1, 負ければ -1, 引き分けなら 0 を返す

そして、下記のテストプログラムを実行し、出力が予想と一致しているか確か めなさい。

テストプログラム test2.m


#import <Foundation/NSString.h>
#import <Foundation/NSArray.h>
#import "Hand.h"
#import "HandBuilder.h"
int main(void){
  int i,j;
  id<HandBuilder> hb = [[HandBuilder1 alloc] init];
  NSMutableArray* h = [[NSMutableArray alloc] initWithCapacity: 3];
  for(i=0; i<3; i++){
    [h addObject: [hb newHand: i]];
  }
  NSLog(@"\t%@\t%@\t%@\n"
	,[[h objectAtIndex: 0 ] toString]
	,[[h objectAtIndex: 1 ] toString]
	,[[h objectAtIndex: 2 ] toString]
	);
  for(i=0; i<3; i++){
    NSMutableString* line = [[NSMutableString alloc] init];
    id<Hand> hand = [h objectAtIndex: i];
    [line appendFormat:@"%@\t",[hand toString]];
    for(j=0; j<3; j++){
      int result = [hand wins:[h objectAtIndex: j ]];
      [line appendFormat:@"%d\t",result];
    }
    NSLog(line);
    [line release];
  }      
  [h release];
  return 0;
}
出力
	Gu	Choki	Pa
Gu	0	1	-1
Choki	-1	0	1
Pa	1	-1	0

問1-3

さらに、下記のクラスを追加します。

Tactics.h


#import <Foundation/NSObject.h>
#import "Hand.h"
#import "HandBuilder.h"
@interface Tactics : NSObject
{
  id handBuilder;
}
- (void)setHandBuilder: (id) hb;
- (Hand*)newHand;
@end
Tactics.m

#import "Tactics.h"
@implementation Tactics
- (void)setHandBuilder:(id) hb
{
  if(handBuilder != hb){
    [handBuilder release];
    handBuilder = [hb retain];
  }
}
-(void)dealloc {
  [handBuilder release];
  [super dealloc];
}
@end

この Tactics を継承し、次の条件を満たすクラス Tactics1, Tactics2 を作 成しなさい。

Tactics1
インスタンスに対して getHand を行うと、毎回 Gu, Choki, Pa のどれかをそ れぞれ確率 1/3 で出す(ヒント stdlib.h をインクルードして rand() を使用 する)
Tactics2
インスタンスに対して getHand を行うと、毎回 Gu, Choki, Pa を順番に出す

作成した Tactics1, Tactics2 に対して、下記のテストプログラムを実行し、 正常に動作していることを示しなさい。

Hand.hに追加

@interface Hand0 : NSObject<Hand>
{
  NSString* gu;
}
- (int) wins: (Hand *) h;
- (int) getType;
- (NSString*)toString;
@end
Hand0.m

#import <Foundation/NSString.h>
#import "Hand.h"
@implementation Hand0
- (id)initWithType:(int)t{
  if(self = [super init]){
    gu=[[NSString alloc] initWithString:@"Gu"];
  }
  return self;
}
- (int)getType{
  return 0;
}
- (int)wins:(Hand *)hand{
  return 0;
}
- (NSString*)toString{
  return gu;
}
-(void)dealloc {
  [gu release];
  [super dealloc];
}
@end;
HandBuilder.h に追加

@interface HandBuilder0 : NSObject<HandBuilder>
- (Hand*)newHand:(int)hand;
@end
HandBuilder0.m

#import "HandBuilder.h"
#import "Hand.h"
@implementation HandBuilder0
- (Hand*) newHand:(int) hand{
  return [[Hand0 alloc] initWithType: 0];
}
@end

テストプログラム test3.m


#import <Foundation/NSString.h>
#import "Hand.h"
#import "HandBuilder.h"
#import "Tactics.h"
void examineTactics(Tactics* t, id<HandBuilder> hb){
  int i;
  NSMutableString* line = [[NSMutableString alloc] init];
  [t setHandBuilder:hb];
  for(i=1; i<=20 ;  i++){
    Hand* hand = (Hand*) [t newHand];
    [line appendFormat:@"%d: %@ ",i,[hand toString]];
    [hand release];
  }
  NSLog(@"%@",line);
  [line release];
}
int main(void){
  id<HandBuilder> hb0 = [[HandBuilder0 alloc] init];
  id<HandBuilder> hb1 = [[HandBuilder1 alloc] init];
  Tactics* t = [[Tactics1 alloc] init];
  examineTactics(t, hb0);
  [t release];
  t = [[Tactics1 alloc] init];
  examineTactics(t, hb1);
  [t release];
  t = [[Tactics2 alloc] init];
  examineTactics(t,hb0);
  [t release];
  t = [[Tactics2 alloc] init];
    examineTactics(t,hb1);
  [t release];
  [hb0 release];
  [hb1 release];
  return 0;
}
出力例
1: Gu 2: Gu 3: Gu 4: Gu 5: Gu 6: Gu 7: Gu 8: Gu 9: Gu 10: Gu 11: Gu 12: Gu 13: Gu 14: Gu 15: Gu 16: Gu 17: Gu 18: Gu 19: Gu 20: Gu
1: Gu 2: Gu 3: Gu 4: Gu 5: Gu 6: Gu 7: Gu 8: Gu 9: Gu 10: Gu 11: Gu 12: Gu 13: Gu 14: Gu 15: Gu 16: Gu 17: Gu 18: Gu 19: Gu 20: Gu

1: Pa 2: Pa 3: Gu 4: Gu 5: Pa 6: Gu 7: Gu 8: Choki 9: Gu 10: Pa 11: Gu 12: Pa 13: Gu 14: Gu 15: Pa 16: Choki 17: Choki 18: Gu 19: Pa 20: Pa
1: Gu 2: Choki 3: Pa 4: Gu 5: Choki 6: Pa 7: Gu 8: Choki 9: Pa 10: Gu 11: Choki 12: Pa 13: Gu 14: Choki 15: Pa 16: Gu 17: Choki 18: Pa 19: Gu 20: Choki

なお、三行目はランダムに出るので、必ず上記のようになるわけではありませ ん。 Gu, Choki, Pa が大体 1/3 出ていれば構いません。 しかし、他の行は必ず上記の出力と一致すること。

問1-4

下記のクラス Box を追加します。

追加クラス

Box.h

#import <Foundation/NSObject.h>
#import <Foundation/NSArray.h>
#import "Player.h"
@interface Box:NSObject
{
  Player* player1;
  Player* player2;
}
- (void)setPlayer1:(Player*) p;
- (void)setPlayer2:(Player*) p;
- (NSArray*)newPlay;
@end;
Box.m

#import <Foundation/NSObject.h>
#import <Foundation/NSArray.h>
#import "Hand.h"
#import "Player.h"
#import "Box.h"
@implementation Box
- (void)setPlayer1:(Player*) p
{
  if(player1!=p){
    [player1 release];
    player1 = [p retain];
  }
}
- (void)setPlayer2:(Player*) p
{
  if(player2!=p){
    [player2 release];
    player2 = [p retain];
  }
}
- (NSArray*)newPlay{
  NSMutableArray* result = [[NSMutableArray alloc] initWithCapacity:2];
  Hand* h1 = [player1 newHand];
  Hand* h2 = [player2 newHand];
  [result addObject: h1];
  [result addObject: h2];
  switch([h1 wins:h2]){
  case -1:
    [player1 lose];
    [player2 win];
    break;
  case 0:
    [player1 draw];
    [player2 draw];
    break;
  case 1:
    [player1 win];
    [player2 lose];
    break;
  }
  [h1 release];
  [h2 release];
  return result;
}
-(void) dealloc
{
  [player1 release];
  [player2 release];
  [super dealloc];
}
@end

このプログラムが動作するように Player クラスを作成しなさい。 Player クラスには、 getHand,win, lose, draw メソッドを実装し ます。 さらに、次の条件にも合致するようにメソッドを実装して下さい。

-(id) initWithString: (NSString*) name
文字列 name を引数にするとその文字列を名前として記憶し、オブジェクト を初期化します
-(void) setTactics:(Tactics*) t
Tactics オブジェクトを受け取り、記憶します。
- (Hand*) newHand
Tactics オブジェクトへの newHand メソッドの結果をそのまま返します。
- (NSString*) result
勝敗結果を文字列で返します。書式は次の通りです:
名前 : 勝った数 win(s), 負けた数 lose(s), 引き分けの数 draw(s)

そして、下記のテストプログラムで動作を確認しなさい。

テストプログラム

Tactics.hに追加

@interface Tactics0 : Tactics
{
}
- (Hand*)newHand;
@end
Tactics0.m

#import "Tactics.h"
@implementation Tactics0
- (Hand*)newHand
{
  Hand* hand = [handBuilder newHand: 1];
  return hand;
}
@end
test4.m

#import "Hand.h"
#import "HandBuilder.h"
#import "Tactics.h"
#import "Box.h"
#import "Player.h"
static int round=0;
void test(Tactics* t1, id<HandBuilder> h1,  Tactics* t2, id<HandBuilder> h2){
  int i;
  [t1 setHandBuilder: h1];
  [t2 setHandBuilder: h2];
  NSLog(@"Test %d",++round);
  Player* player1 = [[Player alloc] initWithString:@"Kaiji"];
  [player1 setTactics:t1];
  Player* player2 = [[Player alloc] initWithString:@"Funai"];
  [player2 setTactics:t2];
  Box* box = [[Box alloc] init];
  [box setPlayer1:player1];
  [box setPlayer2:player2];
  for(i=1; i<=10; i++){
    NSArray* h = [box newPlay];
    NSMutableString* line = [[NSMutableString alloc]init];
    [line appendFormat:@"%d: %@ v.s. %@"
	  ,i
	  ,[[h objectAtIndex: 0] toString]
	  ,[[h objectAtIndex: 1] toString]
     ];
    NSLog(@"%@",line);
    [line release];
    [h release];
  }
  NSLog(@"%@",[player1 result]);
  NSLog(@"%@",[player2 result]);
  [player1 release];
  [player2 release];
  [box release];
}
int main(){
  id<HandBuilder> hb0 = [[HandBuilder0 alloc] init];
  id<HandBuilder> hb1 = [[HandBuilder1 alloc] init];
  Tactics* t0;
  Tactics* t1;
  t0 = [[Tactics1 alloc] init];
  t1 = [[Tactics0 alloc] init];
  test(t0, hb0, t1 ,hb0);
  [t0 release];
  [t1 release];
  t0 = [[Tactics2 alloc] init];
  t1 = [[Tactics0 alloc] init];
  test(t0, hb1, t1 ,hb1);
  [t0 release];
  [t1 release];
  t0 = [[Tactics2 alloc] init];
  t1 = [[Tactics2 alloc] init];
  test(t0, hb1, t1 ,hb0);
  [t0 release];
  [t1 release];
  t0 = [[Tactics1 alloc] init];
  t1 = [[Tactics2 alloc] init];
  test(t0, hb1, t1 ,hb1);
  [t0 release];
  [t1 release];
  [hb0 release];
  [hb1 release];
  return 0;
}
出力例
Test 1
1: Gu v.s. Gu
2: Gu v.s. Gu
3: Gu v.s. Gu
4: Gu v.s. Gu
5: Gu v.s. Gu
6: Gu v.s. Gu
7: Gu v.s. Gu
8: Gu v.s. Gu
9: Gu v.s. Gu
10: Gu v.s. Gu
Kaiji: 0 win(s), 0 lose(s), 10 draw(s)
Funai: 0 win(s), 0 lose(s), 10 draw(s)
Test 2:
1: Choki v.s. Choki
2: Choki v.s. Choki
3: Gu v.s. Choki
4: Choki v.s. Choki
5: Gu v.s. Choki
6: Choki v.s. Choki
7: Choki v.s. Choki
8: Pa v.s. Choki
9: Choki v.s. Choki
10: Choki v.s. Choki
Kaiji: 2 win(s), 1 lose(s), 7 draw(s)
Funai: 1 win(s), 2 lose(s), 7 draw(s)
Test 3:
1: Gu v.s. Choki
2: Choki v.s. Choki
3: Pa v.s. Choki
4: Gu v.s. Choki
5: Choki v.s. Choki
6: Pa v.s. Choki
7: Gu v.s. Choki
8: Choki v.s. Choki
9: Pa v.s. Choki
10: Gu v.s. Choki
Kaiji: 4 win(s), 3 lose(s), 3 draw(s)
Funai: 3 win(s), 4 lose(s), 3 draw(s)
Test 4:
1: Gu v.s. Gu
2: Choki v.s. Gu
3: Pa v.s. Gu
4: Gu v.s. Gu
5: Choki v.s. Gu
6: Pa v.s. Gu
7: Gu v.s. Gu
8: Choki v.s. Gu
9: Pa v.s. Gu
10: Gu v.s. Gu
Kaiji: 3 win(s), 3 lose(s), 4 draw(s)
Funai: 3 win(s), 3 lose(s), 4 draw(s)
Test 5:
1: Pa v.s. Gu
2: Gu v.s. Choki
3: Pa v.s. Pa
4: Gu v.s. Gu
5: Pa v.s. Choki
6: Choki v.s. Pa
7: Pa v.s. Gu
8: Choki v.s. Choki
9: Gu v.s. Pa
10: Pa v.s. Gu
Kaiji: 5 win(s), 2 lose(s), 3 draw(s)
Funai: 2 win(s), 5 lose(s), 3 draw(s)

なお、上記の勝敗の数字の一部は乱数によって変化しますので、この通りの出力が必ず出るわけではありません。 自ら行った各テストがそれぞれ成功しているかどうか、理由をつけて説明しなさい。 また、レポート作成時には、テスト結果全てを添付して下さい。

問1-5

HandBuilder1 クラスの newHand は毎回 Hand オブジェクトを作成しています。 しかし、実際に必要なのは Gu, Choki, Pa のそれぞれのオブジェクトだけで す。 そこで、シングルトンデザインパターンのような考え方で、オブジェクトは Gu, Choki, Pa の 3 つしか生成しない getInstance メソッドを持つ HandBuilder2 を作成しなさい。 なお、必要であれば、 Hand1 など他のクラスも置き換えても構いません。

そして、次のテストプログラムにおいて、 Ok が 3 つ出ることを確認しなさ い。

テストプログラム test5.m


#import <Foundation/NSArray.h>
#import "Hand.h"
#import "HandBuilder.h"
int main(){
  int i,j;
  id<HandBuilder> hb = [[HandBuilder2 alloc] init];
  NSMutableArray* h = [[NSMutableArray alloc] initWithCapacity: 2];

  
  for(j=0; j<2; j++){
    NSMutableArray* ha = [[NSMutableArray alloc] initWithCapacity: 3];
    for(i=0; i<3; i++){
      [ha addObject: [hb newHand:i]];
    }
    [h addObject: ha];
    [ha release];
  }
  for(i=0; i<3; i++){
    NSMutableString* line = [[NSMutableString alloc] init];
    Hand* h0 = [[h objectAtIndex: 0] objectAtIndex: i];
    Hand* h1 = [[h objectAtIndex: 1] objectAtIndex: i];
    [line appendString:[h0 toString]];
    if(h0 == h1){
      [line appendString:@" Ok"];
    }else{
      [line appendString:@" NG"];
    }
    NSLog(@"%@",line);
    [line release];
  }
  [h release];
  [hb release];
  return 0;
}
成功例
Gu Ok
Choki Ok
Pa Ok

そして、次のプログラムを動作させ、効果を確かめなさい。 なお、下記のプログラムにおいて heap エラーが出るときは、繰り返し回数を 10万回に減らしても良い。

./test6 1 と動かすと HandBuilder1 が動作し、 ./test6 2 で動かすと HandBuilder2 が動作します。 これをそれぞれでたらめな順番で 5 回位計測して平均をとると、それなりに正確な計測になると期待できます。

テストプログラム

StopWatch.h

#include <sys/timeb.h>
#import <Foundation/NSString.h>
@interface StopWatch : NSObject
{
  struct timeb now;
  NSString* resultString;
}
- (NSString*) toString;
@end
StopWatch.m

#include <sys/timeb.h>
#import <Foundation/NSString.h>
#import "StopWatch.h"
@implementation StopWatch 
- (id) init
{
  if((self = [super init])!=nil){
    ftime(&now);
  }
  return self;
}
- (NSString*) toString
{
  struct timeb next;
  ftime(&next);
  double result = next.time-now.time
    +(double)next.millitm/1000-(double)now.millitm/1000;
    now = next;
    [resultString release];
    resultString = [[NSString alloc] initWithFormat:@"%f",result];
    return resultString;
}
-(void)dealloc
{
  [resultString release];
  [super dealloc];
}

@end


test6.m

#import <Foundation/NSArray.h>
#import "Hand.h"
#import "HandBuilder.h"
#import "Tactics.h"
#import "Box.h"
#import "Player.h"
#import "StopWatch.h"


void test(id<HandBuilder> hb){
  int i;
  Player* player1 = [[Player alloc] initWithString:@"Kaiji"];
  Tactics* t1 = [[Tactics1 alloc] init];
  [t1 setHandBuilder: hb];
  [player1 setTactics: t1];

  Player* player2 = [[Player alloc] initWithString:@"Funai"];
  Tactics* t2 = [[Tactics2 alloc] init];
  [t2 setHandBuilder: hb];
  [player2 setTactics: t2];
    
  Box* box = [[Box alloc] init];
  [box setPlayer1:player1];
  [box setPlayer2:player2];

  NSMutableArray* list = [[NSMutableArray alloc] init];
  for(i=1; i<=1000000; i++){
    NSArray* result = [box newPlay];
    [list addObject: result];
    [result release];
  }
  NSLog(@"%@",[player1 result]);
  NSLog(@"%@",[player2 result]);
  [player1 release];
  [player2 release];
  [t1 release];
  [t2 release];
  [box release];
  [list release];

}
int main(int argc, char* argv[]){
  id<HandBuilder> hb;
  if(argc!=2) return -1;
  if(argv[1][0]=='1'){
    hb = [[HandBuilder1 alloc] init];
  }else{
    hb = [[HandBuilder2 alloc] init];
  }
  StopWatch* sw = [[StopWatch alloc] init];
  test(hb);
  NSLog(@"%@",[sw toString]);
  return 0;
}



坂本直志 <sakamoto@c.dendai.ac.jp>
東京電機大学工学部情報通信工学科