0
0

ヘキサゴナルアーキテクチャでポケモンバトルを実施する。(Step2: coreのテスト実装)

Last updated at Posted at 2024-09-08

はじめに

前回の記事の続きです。

テストコード

プロダクトコードに乱数を含むケースがあり、テストが安定しないため、プロダクトコードも一部修正しています。

internal/core/move.go
package core

import (
	"math/rand"
	"time"
)

type Move struct {
	Name             string
	Power            int            // 技の威力
	Accuracy         int            // 命中率(0-100%)
	Type             string         // 技の属性(例: 電気、炎など)
	randSource       *rand.Rand     // 乱数生成器を保持
	randomFactorFunc func() float64 // ランダムなダメージ倍率を計算する関数
	hitCheckFunc     func() bool    // 命中判定の関数
}

// NewMove creates a new move and initializes the random source.
func NewMove(name string, power, accuracy int, moveType string) *Move {
	randSource := rand.New(rand.NewSource(time.Now().UnixNano()))
	move := &Move{
		Name:       name,
		Power:      power,
		Accuracy:   accuracy,
		Type:       moveType,
		randSource: randSource,
		randomFactorFunc: func() float64 {
			// デフォルトのダメージ倍率:0.85~1.0
			return 0.85 + randSource.Float64()*(1.0-0.85)
		},
		hitCheckFunc: func() bool {
			// デフォルトの命中判定:Accuracyに基づくランダムな命中判定
			return randSource.Intn(100) < accuracy
		},
	}
	return move
}

// CanHit checks if the move hits based on its accuracy.
func (m *Move) CanHit() bool {
	return m.hitCheckFunc() // 注入された命中判定関数を使用
}

// CalculateDamage calculates the damage of the move.
func (m *Move) CalculateDamage(attacker *Pokemon, defender *Pokemon) int {
	// ダメージ計算式
	damage := (attacker.Attack * m.Power) / defender.Defense

	// ランダム要素を使用してダメージを計算
	randomFactor := m.randomFactorFunc() // 注入された関数を使用
	damage = int(float64(damage) * randomFactor)

	if damage < 0 {
		damage = 0
	}
	return damage
}
internal/core/pokemon_test.go
package core

import (
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
)

// ポケモンのHPが正しく減少するかをテスト
func TestPokemonAttack_FixedValues(t *testing.T) {
	// Pikachu と Charmander のセットアップ
	pikachu := &Pokemon{
		Name:    "Pikachu",
		HP:      100,
		Attack:  55,
		Defense: 40,
		Speed:   90,
		Moves: []Move{
			*NewMove("Thunderbolt", 90, 95, "Electric"),
		},
	}

	charmander := &Pokemon{
		Name:    "Charmander",
		HP:      100,
		Attack:  52,
		Defense: 43,
		Speed:   65,
		Moves: []Move{
			*NewMove("Flamethrower", 90, 95, "Fire"),
		},
	}

	// Pikachu の技を取得
	move := &pikachu.Moves[0]

	// 乱数要素と命中判定を固定化する
	move.randomFactorFunc = func() float64 {
		return 1.0 // ダメージのランダム倍率を固定(1.0倍)
	}
	move.hitCheckFunc = func() bool {
		return true // 命中判定を固定(常に命中)
	}

	// Pikachu が Charmander を攻撃
	damage, err := pikachu.AttackPokemon(charmander, *move)

	// 結果を表示
	fmt.Println(pikachu.Name, "技:", move.Name, ", ", damage, "ダメージ!")

	// エラーがないことを確認
	assert.NoError(t, err)

	// ダメージが期待通りか確認
	expectedDamage := 115 // 期待する固定ダメージ(例: 計算に基づく値)
	assert.Equal(t, expectedDamage, damage, "ダメージが期待値と異なります")

	// Charmander のHPが正しく減少しているか確認
	expectedHP := 0
	assert.Equal(t, expectedHP, charmander.HP, "CharmanderのHPが期待値と異なります")
}

func TestCanHitWithFixedHitCheck(t *testing.T) {
	// テスト対象の技を作成
	move := NewMove("Thunderbolt", 90, 95, "Electric")

	// 命中判定を固定化(常に命中する)
	move.hitCheckFunc = func() bool {
		return true // 常に命中する
	}

	// CanHitの結果をテスト(命中するはず)
	if !move.CanHit() {
		t.Errorf("命中するはずのCanHitがfalseを返しました")
	}

	// 命中判定を固定化(常にミスする)
	move.hitCheckFunc = func() bool {
		return false // 常にミスする
	}

	// CanHitの結果をテスト(ミスするはず)
	if move.CanHit() {
		t.Errorf("ミスするはずのCanHitがtrueを返しました")
	}
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0