【育児負荷低減の為のエンジニアリングその2　ポケモンしりとり編　AWS S3 lambda api-gateway terraform】

# 何を作ったか

- ポケモンの名前を入力すると、自動的にしりとりをしてくれる物を作りました。
- AIとかそんな大それたもの入ってないです。ランダムで対象のポケモンを繋いでいくものです。

![スクリーンショット 2021-05-04 11.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188508/f23d9112-3424-a556-1c5a-89a0443803df.png)

###しりとりモード
- ふつうにしりとり
    - 最初と最後のポケモンを入力すると勝手にしりとりやってくれます
- にゅうりょくもじからはじまるポケモン
    - 入力文字から始まるポケモンを返してくれます
- どこかにもじがはいるポケモン（尻じゃないですが）
    - 入力文字が含まれているポケモンを返してくれます
- にゅうりょくもじでおわるポケモン
    - 入力文字で終わるポケモンを返してくれます


# 何故作ろうと思ったか

https://qiita.com/zukaishi/items/7bcb16fb57fdcd762914
- 前回のこちらと同じ理由になりますが、あまりにもしりとりをする事を要求されることに疲れました
- ポケモンにハマっている息子（小学1年生）がたくさんポケモンの名前を覚えたのでそれをアピールしたいんでしょうね。そのためしりとり、毎日のように勝負を挑んでくるが面倒になってしまったので、ポケモンしりとりを勝手にしてくれるものをつくりました。
- そんなにポケモン知らないし、そもそも赤・緑で知識が止まっている状態ですし、、、。

# どのように実現したか

#### アーキテクチャ
![Untitled Diagram (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/188508/529b563a-8783-622d-d014-ae7fce5c769b.png)

- AWSとTerraformの勉強がてらこんなアーキテクチャで作ってみました。
- AWSでホスティングさせるための実際にブラウザでアクセスするindexとしりとりの対象にするリストをあらかじめs3 bucketにあらかじめ格納しておく
- しりとりの核となる処理は、API-Gateway　→　LambdaでAPIリクエストすることによって処理される
- 取得された結果をブラウザが表示する

#### AWS準備
```terraform
variable "bucket_name" {
  default = "website.shiritori.com"
}

data "aws_iam_policy_document" "s3_bucket_policy" {
  statement {
    actions = ["s3:GetObject"]
    effect  = "Allow"
    principals {
      type        = "AWS"
      identifiers = ["*"]
    }
    resources = ["arn:aws:s3:::${var.bucket_name}/*"]
    sid       = "PublicReadGetObject"
  }
}

resource "aws_s3_bucket" "website_shiritori_com" {
  bucket = var.bucket_name
  policy = data.aws_iam_policy_document.s3_bucket_policy.json
 
  website {
    index_document = "index.html"
    error_document = "error.html"
  }
}

resource "aws_s3_bucket_public_access_block" "website_shiritori" {
  bucket                  = var.bucket_name
  block_public_acls       = true
  block_public_policy     = false
  ignore_public_acls      = true
  restrict_public_buckets = false
}

output "url" {
  value = aws_s3_bucket.website_shiritori_com.website_endpoint
}

```

#### データ取得
``` golang
func comprised(word string) string {
	url := "https://s3-ap-northeast-1.amazonaws.com/website.shiritori.com/data/pokemon_list.csv"
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println(err)
	}

	defer resp.Body.Close()
	reader := csv.NewReader(resp.Body)
	pokemonList := map[int]string{}
	for {
		record, err := reader.Read()
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatal(err)
		}
		var i int
		i, _ = strconv.Atoi(record[0])
		pokemonList[i] = record[1]
	}

	result := ""
	for key, value := range pokemonList {
		if strings.Contains(value, word) {
			result += fmt.Sprintf("%d:%s", key, value)
			result += ","
		}
	}
	return result
}
```
#### メイン部
```golang
func shiritori(name1 string, name2 string) string {
	url := "https://s3-ap-northeast-1.amazonaws.com/website.shiritori.com/data/pokemon_list.csv"
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println(err)
	}

	defer resp.Body.Close()
	reader := csv.NewReader(resp.Body)
	pokemonList := map[int]string{}
	for {
		record, err := reader.Read()
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatal(err)
		}
		if name2 == record[1] || getLastString(record[1]) != "ン" {
			var i int
			i, _ = strconv.Atoi(record[0])
			pokemonList[i] = record[1]
		}
	}

	startNo := contains(pokemonList, name1)
	endNo := contains(pokemonList, name2)
	if startNo == 0 || endNo == 0 {
		fmt.Printf("input name failed ¥n")
	}

	word := ""
	no := startNo
	result := ""
	for {
		result += fmt.Sprintf("%d:%s", no, pokemonList[no])
		word = pokemonList[no]
		delete(pokemonList, no)

		lastString := getLastString(word)
		list := containsList(pokemonList, lastString)
		if len(list) == 0 || endNo == no {
			break
		}
		result += ","
		if lastString == getRuneAt(name2, 0) {
			result += fmt.Sprintf("%d:%s", endNo, name2)
			break
		}

		randMap := []int{}
		for key := range list {
			randMap = append(randMap, key)
		}
		rand.Seed(time.Now().UnixNano())
		randNo := rand.Intn(len(randMap))
		no = randMap[randNo]
	}
	return result
}
```

#### 文字変換周り
```golang
func getLastString(word string) string {
	// 対象文字の最後の文字を取得utf-8のため、/3している
	lastString := getRuneAt(word, len(word)/3-1)

	// 最後の文字が伸ばし棒の場合
	if lastString == "ー" {
		lastString = getRuneAt(word, len(word)/3-2)
	}

	// 特殊文字、捨て仮名を扱いやすい形へ変換する
	r := strings.NewReplacer("♂", "ス", "♀", "ス", "ァ", "ア", "ィ", "イ", "ゥ", "ウ", "ェ", "エ", "ォ", "オ", "ュ", "ユ", "ャ", "ヤ", "ョ", "ヨ")
	resStr := r.Replace(lastString)
	return resStr
}
```

# コード
- コードはこちらにアップしてあります。
    - https://github.com/zukaishi/aws_shiritori

# 使い方
- http://aws-shiritori.tk/

# 結果どうなったか
- その度にiPadをせがまれるようになって助かったのはいいのですが、僕がその時間iPad使えないのでちょっと不便、、、。
- 子供はキーボード叩いたりできないのでちょいちょい打ち込むのが面倒
- あと、ポケモンのブームが過ぎ去って、今度はLEGOブームが到来です。
- LEGOを自動化するものは、、、。ハードルが高いなぁと思う今日この頃です。

# 今後やりたいこと
- 最短、最長の経路を選択できるようにしたい
- イラストとか引っ張ってこれるようにしたい
- terraformでapi-gateway周りも実現できるようにしたい
- ポケモン名前を選択できるようにする or 音声入力を可能にするか

# 修正点
- 独自ドメイン対応