【Python】昔C言語で作ったゲームをPythonでリファクタリングしてみた

# この記事について

特に初心者にとってためになる記事ではないと思います。
ただ今後のために記録に残したいがために記事にしました。
読む価値がありそうなのはゲームがしたい人、初心者のコードのレビューをしたい人とかでしょうか。

# 💣始めに

昔自分で作ったゲームをPythonで書き直し、同時にリファクタリングもしてみましたー！！

👇そのゲーム

https://qiita.com/scythercas/items/77d213b9f6ead9954dd6


あげた直後に[コメント](https://qiita.com/scythercas/items/77d213b9f6ead9954dd6#comment-9fde105241cf0dc75c86)にて @shiracamus さんが凄く綺麗に書き直してくださりました。

ここで初めて関数分割という概念を知り、見やすさとコードの簡潔さに感動した覚えがあります。

今回は成長出来ているか確かめるため参考にせずにやってみました（もちろん当時はめちゃくちゃ見ました）。

# 💣ルール説明

丁寧に説明してるつもりですが、プレイしていただいた方が早いです。
書いておきながらあまり見てほしくないような文章になったので折り畳んでおきます。

<details><summary>ルール説明</summary>


**盤面のどこかに2つの💣が設置されています！！
探し当てて生き延びてください！**

というゲームです。

マスのサイズ、検知できる回数はプレイヤーが選べますが、今回はどちらも `5` の体で書きます。

**5*5**の盤面のどこかに2つ💣が設置されています。
限られた試行回数の中でその位置を特定してください。

また、それぞれの試行で、**検知するマス**及び**どの範囲を検知するか**を選ぶことができ、その範囲設定は以下のようになります。
図は赤い部分が選ばれたマス、黄色い範囲が選ばれたマス以外の検知できるマスであり、赤い部分に💣が設置されていると`2`, 黄色い部分のどこかに設置されていると`1`と表示されます。

ただし、範囲で `7` を選んだ場合のみ、盤面の1辺の長さに等しい数のマスがランダムに検知され、検知出来た場合 `2` と表示されます(盤面の一辺が5マスなら5マス検知される)。

+ 5 の場合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2666100/eda6d620-341b-4417-80e9-db1d8892c9e7.png)

+ 6 の場合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2666100/be7404ac-6eed-06dd-53ff-50edd7a572fd.png)

+ 7 の場合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2666100/a55d0845-289c-7039-ee5f-3d9a655b24fc.png)

+ 8 の場合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2666100/48138469-e5e4-1bc1-f396-3f66c56c2472.png)

例えば範囲選択を「5」、調べるマスを「3 3」と入力し、「2 2」に💣が設置されていた場合、
```
0 0 0 0 0
0 1 1 1 0
0 1 0 1 0
0 1 1 1 0
0 0 0 0 0
```
「3 3」に💣が設置されいた場合、
```
0 0 0 0 0
0 0 0 0 0
0 0 2 0 0
0 0 0 0 0
0 0 0 0 0
```

のように表示されます。

</details>

# 💣閑話休題(早すぎ)

Python書きやすすぎる。
インデントでブロック定義するの不便だと思ってたけど意外と見やすい。
そのおかげもあるが２週間足らずでここまで出来るようになれたのは自信になった。

https://twitter.com/Scythercas/status/1591695389446512642?s=20&t=6xyrBiM8K8WVtgvqFPmGwQ




# 💣ソースコード

見比べるために半年前に書いたC言語のものと今回のPythonのものを両方並べてみます。

なっっっっがいのでどちらも折り畳みます。

<details><summary>C言語</summary>

```5月に書いたやつ.c
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

int main(void){
	int s, m, a, b, n, i, j, k, p, q, range, rx, ry, bx, by;
	int rfield[100][100];
	int bfield[100][100];

	printf("何マス×何マスのサイズにしますか？： ");
	scanf("%d", &s);
	printf("何回ヒントがほしいですか？： ");
	scanf("%d", &m);

	printf("\n%d×%dマスのどこかに赤い爆弾と青い爆弾が設置されています！！\n\n"
			"%d手で得た情報をヒントに爆弾の位置を特定してください。\n"
			"片方でも爆弾の位置を間違えた場合爆発してしまいます。\n"
			"頑張って探し当ててください；；\n\n"
			"～ルール説明～\n\n"
			"検知する範囲、何行目、何列目の順に数値を入力してください。\n\n"
			"なお、爆弾を検知する範囲およびそのキーは、\n"
			"・ そのマスと、そのマスを囲む8マスを調べる場合\t・・・9\n"
			"・ そのマスを含む行の場合\t\t\t・・・6\n"
			"・ そのマスを含む列の場合\t\t\t・・・8\n"
			"・ ランダムの%dマスを調べる場合\t\t\t・・・7\n"
			"となります。\n\n"
			"・ そのマスに爆弾が設置されていた場合、「2」、\n"
			"・ そのマスの周囲8マスに爆弾が設置されていた場合、「1」、\n"
			"・ 上記の範囲に爆弾が存在しない場合、「0」と出力されます。\n"
			"\n\n"
			"%d回この作業を繰り返した後、2つの爆弾の位置を聞きます。"
			"どちらか片方でも外れた場合、爆発し、GAME OVERとなります。"
			"情報をたよりに爆弾を2つとも探し当ててください。\n\n"
			"では、Enterを押してGAME START!!!\n\n", s, s, m, s, m);
	getchar();
	srand((unsigned int)time(NULL));
	rx = rand()%s+1;
	ry = rand()%s+1;
	bx = rand()%s+1;
	by = rand()%s+1;
	//red[rx][ry];
	//blue[bx][by];
	for(j=1;j<=s;j++){//field作成
		for(i=1;i<=s;i++){
			rfield[i][j]=0;
			bfield[i][j]=0;
		}
	}
	for(n=1;n<=m;n++){
		printf("%d 回目\n", n);
		printf("範囲は？： "); scanf("%d", &range);
//range場合分け
		switch(range){
		case 9://3*3マス検知
			printf("何列目？： "); scanf("%d", &a);
			printf("何行目？： "); scanf("%d", &b);
			if(a==rx && b==ry){//redbombの処理
				rfield[a][b]=2;
				printf("2");
			}
			else if((a-1<=rx && rx<=a+1) && (b-1<=ry && ry<=b+1)){
				printf("1");
				for(i=a-1;i<=a+1;i++){
					for(j=b-1;j<=b+1;j++){
						rfield[i][j]=1;
					}
				}
				rfield[a][b]=0;
			}
			else printf("0");
			if(a==bx && b==by){//bluebombの処理
				printf("2");
				bfield[a][b]=2;
			}
			else if((a-1<=bx && bx<=a+1) && (b-1<=by && by<=b+1)){
				printf("1");
				for(j=b-1;j<=b+1;j++){
					for(i=a-1;i<=a+1;i++){
						bfield[i][j]=1;
					}
				}
				bfield[a][b]=0;
			}
			else printf("0");
			break;
		case 6://横一行検知
			printf("何列目？： "); scanf("%d", &a);
			printf("何行目？： "); scanf("%d", &b);
			if(b==ry){
				if(a==rx){
					rfield[a][b]=2;
				}
				else for(i=1;i<=s;i++){
					rfield[i][b]=1;
				}
			}
			if(b==by){
				if(a==bx){
					bfield[a][b]=2;
				}
				else for(i=1;i<=s;i++){
					bfield[i][b]=1;
				}
			}
			break;
		case 8://縦一列検知
			printf("何列目？： "); scanf("%d", &a);
			printf("何行目？： "); scanf("%d", &b);
			// if(a==rx){
			// 	if(b==ry){
			// 		rfield[a][b]=2;
			// 	}
			// 	else for(i=1;i<=s;i++)
			// 		rfield[a][i]=1;
			// }
			if(a==rx){
				if(b==ry){
					rfield[a][b]=2;
				}
				else for(i=1;i<=s;i++){
					rfield[a][i]=1;
				}
			}
			if(a==bx){
				if(b==by){
					bfield[a][b]=2;
				}
				else for(i=1;i<=s;i++){
					bfield[a][i]=1;
				}
			}
			break;
		case 7://ランダム検知
			for(i=1;i<=s;i++){
				p = rand()%s+1;
				q = rand()%s+1;
				if(p==rx && q==ry){
					rfield[p][q]=2;
				}
				else rfield[p][q]=0;
				if(p==bx && q==by){
					bfield[p][q]=2;
				}
				else bfield[p][q]=0;
			}
			break;
		}
		printf("\n\n");
		printf("赤い爆弾の検知結果\t青い爆弾の検知結果\n");
		for(j=1;j<=s;j++){//field作成
			for(k=1;k<=s;k++){
				printf("%d", rfield[k][j]);
			}
			printf("\t\t\t");
			for(k=1;k<=s;k++){
				printf("%d", bfield[k][j]);
			}
			printf("\n");
		}
		for(j=1;j<=s;j++){//field初期化
			for(k=1;k<=s;k++){
				rfield[k][j]=0;
				bfield[k][j]=0;
			}
		}
		printf("\n\n");
	}
	printf("%d回の動作を終えました。\n\n"
			"いよいよ運命の時です。\n"
			"あなたが予想する爆弾の位置を、赤い爆弾、青い爆弾の順に入力してください。\n\n", m);
	int arx, ary, abx, aby;
	printf("赤い爆弾：\t何列目？： "); scanf("%d", &arx);
	printf("\t	何行目？： "); scanf("%d", &ary);
	printf("青い爆弾：\t何列目？： "); scanf("%d", &abx);
	printf("\t	何行目？： "); scanf("%d", &aby);
	printf("\n\n");
	if((arx==rx && ary==ry) && (abx==bx && aby==by)){
		printf("おめでとうございます！！！\n爆弾は両方とも爆発しませんでした！！！\n\n");
	}else if((arx==rx && ary==ry) && (abx!=bx || aby!=by)){
		printf("ドカン！青い爆弾が爆発してしまいました。。\nもう一度挑戦してみてね！\n\n");
	}else if((arx!=rx || ary!=ry) && (abx==bx && aby==by)){
		printf("ドカン！赤い爆弾が爆発してしまいました。。\nもう一度挑戦してみてね！\n\n");
	}else if((arx!=rx || ary!=ry) && (abx!=bx || aby!=by)){
		printf("どっかーーーん！！！！！\n爆弾が両方とも爆発してしまいました！！！\nもう二度とプレイするな！！\n\n");
	}
	printf("正解は、\n\n"
			"赤い爆弾は%d行目の%d列目、\n"
			"青い爆弾は%d行目の%d列目でした！\n\n"
			"また遊んでね！", rx, ry, bx, by);
	getchar();
	getchar();
	return 0;
}

```

</details>

<details><summary>Python</summary>

```今回(11月)書いたやつ.py
import random
import copy

def set_game():
    print('何マス×何マスでプレイしますか？推奨：5')
    size = int(input())
    print('何回ヒントが欲しいですか？')
    hint_times = int(input())
    return size, hint_times

def descrive_rules(size, hint_times):
    print(f'{size}×{size}マスのどこかに赤い爆弾と青い爆弾が設置されています！！\n\n')
    print(f'{hint_times}手で得た情報をヒントに爆弾の位置を特定してください。\n')
    print(f'片方でも爆弾の位置を間違えた場合爆発してしまいます。\n\n')

    print('---ルール説明---\n')
    print(f'''
検知する範囲、何行目、何列目の順に数値を入力してください。

なお、爆弾を検知する範囲およびそのキーは、

5 -> そのマスと、そのマスを囲む8マスを調べる場合
6 -> そのマスを含む行の場合
8 -> そのマスを含む列の場合
7 -> ランダムの{size}マスを調べる場合

となります。
また調査結果は、

2 -> あなたが選んだマスに爆弾が設置されていた場合
1 -> 検知された範囲に爆弾が設置されていた場合
0 -> 上記の範囲で爆弾が設置されていなかった場合

のように、表示され、調べていないマスには0と表示されます。

{hint_times}回この検知を繰り返した後、2つの爆弾の位置を聞きます。
どちらか片方でも外れた場合、爆発し、GameOverとなります。
情報を頼りに爆弾を2つとも探し当ててください。

では、Enterを押してGAME START!!

    ''')
    input()

def set_2bombs(size):
    a = []
    for i in range(0, 4):
        a.append(random.randint(1, size))
    return a

def set_field(size):
    field = [[0] * size for i in range(1, size+1)]
    return field

def reset_field(field):
    for i in range(0, len(field)):
        for j in range(0, len(field)):
            field[j][i] = 0 

def sellected_5(size, field, color_x, color_y, x, y):
    reset_field(field)
    if x==color_x and y==color_y:
        field[y-1][x-1] = 2
    elif (x-1<=color_x and color_x<=x+1) and (y-1<=color_y and color_y<=y+1):
        for i in range(x-2, x+1):
            if i < 0 or i > 4:
                continue
            for j in range(y-2, y+1):
                if j < 0 or j > 4:
                    continue
                field[j][i]=1
        field[y-1][x-1]=0
    print('\n')
    return field

def sellected_8(size, field, color_x, color_y, x, y):
    reset_field(field)
    if x==color_x:
        if y == color_y:
            field[y-1][x-1] = 2
        else:
            for i in range(0, size):
                field[i][x-1] = 1
    print('\n')
    return field

def sellected_7(size, field, color_x, color_y):
    reset_field(field)
    for i in range(1, size+1):
        p = random.randint(1, size)
        q = random.randint(1, size)
        if p == color_x and q == color_y:
            field[q-1][p-1] = 2
    print('\n')
    return field

def sellected_6(size, field, color_x, color_y, x, y):
    reset_field(field)
    if y==color_y:
        if x==color_x:
            field[y-1][x-1] = 2
        else:
            for i in range(0, size):
                field[y-1][i] = 1
    print('\n')
    return field


def display_fields(size, field, bombs, key, x, y):
    red_x, red_y, blue_x, blue_y = bombs
    print(bombs)
    print(red_x, red_y, blue_x, blue_y)
    if key == 5:
        red_field = copy.deepcopy(sellected_5(size, field, red_x, red_y, x, y))
        blue_field = copy.deepcopy(sellected_5(size, field, blue_x, blue_y, x, y))
    elif key == 8:
        red_field = copy.deepcopy(sellected_8(size, field, red_x, red_y, x, y))
        blue_field = copy.deepcopy(sellected_8(size, field, blue_x, blue_y, x, y))
    elif key == 7:
        red_field = copy.deepcopy(sellected_7(size, field, red_x, red_y))
        blue_field = copy.deepcopy(sellected_7(size, field, blue_x, blue_y))
    elif key == 6:
        red_field = copy.deepcopy(sellected_6(size, field, red_x, red_y, x, y))
        blue_field = copy.deepcopy(sellected_6(size, field, blue_x, blue_y, x, y))
    else:
        print('適切な値を入力してください。')
    print('赤い爆弾の検知結果\t青い爆弾の検知結果\n')
    print(*red_field, sep='\n')
    print('\n')
    print(*blue_field, sep='\n')
    # for j in range(0, size):
    #     for ri in range(0, size):
    #         print(red_field[ri][j])
    #     print('\t\t\t')
    #     for bi in range(0, size):
    #         print(blue_field[bi][j])
    #     print('\n')

def main():
    size, hint_times = set_game()
    descrive_rules(size, hint_times)
    bombs = set_2bombs(size)
    print(bombs)
    # 2つの爆弾のx, y座標
    field = set_field(size)
    for i in range(1, hint_times+1):
        print(f'{i} 回目\n')
        print('範囲は？(5, 6, 7, 8)')
        key_of_range = int(input())
        if key_of_range == 7:
            display_fields(size, field, bombs, key_of_range, 0, 0)
        else:
            print('何行目？何列目？(例: 3 4)')
            y, x=(int(n) for n in input().split())
            display_fields(size, field, bombs, key_of_range, x, y)
    print(f'{hint_times}回の動作を終えました。\nあなたが予想する爆弾の位置を入力してください。')
    print('赤い爆弾:何行目？何列目？(例:3 3)')
    ans_red_y, ans_red_x = (int(k) for k in input().split())

    print('青い爆弾:何行目？何列目？(例:3 3)')
    ans_blue_y, ans_blue_x = (int(k) for k in input().split())

    red_x, red_y, blue_x, blue_y = bombs
    if (ans_red_x==red_x and ans_red_y==red_y) and (ans_blue_x==blue_x and ans_blue_y==blue_y):
        print('Game Clear !!! \n2つとも爆発しませんでした')
    elif (ans_red_x==red_x and ans_red_y==red_y) and (ans_blue_x!=blue_x or ans_blue_y!=blue_y):
        print('ドカン！青い爆弾が爆発してしまいました。。\nもう一度挑戦してみてね')
    elif (ans_red_x!=red_x or ans_red_y!=red_y) and (ans_blue_x==blue_x and ans_blue_y==blue_y):
        print('ドカン！赤い爆弾が爆発してしまいました。。\nもう一度挑戦してみてね')
    else:
        print('どっかーーーん！！！！！\n爆弾が両方とも爆発してしまいました\nもう二度とプレイするな！')
    print(f'''正解は、

赤い爆弾は{red_y}行目の{red_x}列目、
青い爆弾は{blue_y}行目の{blue_x}列目

でした！
また遊んでね''')
    input()
    input()

main()

```

</details>

# 💣成長したであろう点

## 💡関数が使えるようになった
C言語のソースコードを見てもらえれば分かると思いますが、main関数しかありません(笑)
プログラミング始めて1か月ですししょうがないのでしょうか。パワー系エンジニアになりそう

## 💡変数名が見やすくなった
```やばい命名.c
int s, m, a, b, n, i, j, k, p, q, range, rx, ry, bx, by;
```
こんな記述があります(笑)

## 💡同じ記述を減らせた
関数分割しているのでそりゃそうですが。
ただもっとできる部分あったなー

# 💣反省点
+ 引数が多い！
  + 問題だと思うのだが、これどうしたら良いか分からない。Pythonに関する知識がまだ全然なくグローバル変数という概念があるのかすら分からず書き始めた。それ自体が反省
+ 配列は0から始まる！
  + 行列に関して、プレイヤーの入力が1から始まるのに対し、配列は0から始まることをたまに忘れてしまい、デバッグに苦労した。最初に考えておくべきだった。
+ もっと関数分割！
  + 同じような記述が続く部分があるので、ここも簡略化出来そうだと思った。その方法を思いついても後から直すのは気が引けるようなコードになっていたので、予めフローチャート等を作って作成を開始すればよかった。
+ 行数があまり変わらなかった。
  + 見やすくなっているのか？
  + もっと計画的にロジカルにコードを書くべきだった
+ コメント使ってない！
  + 後のこと考えたら書いてた方がいいよね
+ 妥協しちゃった！
  + もっと綺麗に出来る部分はあるが、これ以上機能追加しないしいいかと思ってしまった。よくない
+ テストが少ない！
  + やれ

# 💣以上を踏まえて今後につなげる

とにかく**計画性**が大事で、`ゲーム作ろう！➩書こう！`の間に`設計しよう！`を3時間くらい費やすべきだと思った。

# 💣感想

もっと読みやすい書き方いくらでもあったなと後悔。
Pythonに書き直せた満足感より反省が大きい。
リファクタリングするのにも知識が必要で、そのための勉強をする必要があると感じた。のでやる！