1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
| #!/usr/bin/env perl
use v5.36;
use Time::HiRes qw(sleep);
# ============================================================
# BulletType: 弾の種類(内部状態)
# ============================================================
package BulletType {
use Moo;
has name => (is => 'ro', required => 1);
has char => (is => 'ro', required => 1);
has color => (is => 'ro', default => 'white');
sub render($self, $x, $y, $screen) {
my $ix = int($x);
my $iy = int($y);
return if $iy < 0 || $iy >= @$screen;
return if $ix < 0 || $ix >= length($screen->[$iy]);
substr($screen->[$iy], $ix, 1) = $self->char;
}
}
# ============================================================
# BulletFactory: 弾の種類を管理(オブジェクトプール)
# ============================================================
package BulletFactory {
use Moo;
has _cache => (is => 'ro', default => sub { {} });
has _definitions => (
is => 'ro',
default => sub {
{
circle => { name => 'circle', char => '●' },
star => { name => 'star', char => '★' },
dot => { name => 'dot', char => '・' },
arrow => { name => 'arrow', char => '→' },
wave => { name => 'wave', char => '〜' },
}
},
);
sub get($self, $key) {
my $cache = $self->_cache;
my $defs = $self->_definitions;
$cache->{$key} //= BulletType->new(%{$defs->{$key}});
}
sub count($self) {
scalar keys %{$self->_cache};
}
sub types($self) {
sort keys %{$self->_cache};
}
}
# ============================================================
# BattleField: 戦場を管理(弾の生成・移動・描画)
# ============================================================
package BattleField {
use Moo;
has width => (is => 'ro', required => 1);
has height => (is => 'ro', required => 1);
has factory => (is => 'ro', required => 1);
has bullets => (is => 'ro', default => sub { [] });
# 弾を生成
sub spawn($self, $type_key, $x, $y, $vx, $vy) {
push @{$self->bullets}, {
type => $self->factory->get($type_key),
x => $x,
y => $y,
vx => $vx,
vy => $vy,
};
}
# 弾幕パターン: 放射状
sub spawn_radial($self, $type_key, $cx, $cy, $count, $speed) {
for my $i (0 .. $count - 1) {
my $angle = $i * (360 / $count) * 3.14159 / 180;
$self->spawn(
$type_key,
$cx, $cy,
cos($angle) * $speed,
sin($angle) * $speed * 0.5,
);
}
}
# 弾幕パターン: 螺旋
sub spawn_spiral($self, $type_key, $cx, $cy, $waves, $per_wave, $speed) {
for my $wave (0 .. $waves - 1) {
for my $i (0 .. $per_wave - 1) {
my $angle = ($i * (360 / $per_wave) + $wave * 15) * 3.14159 / 180;
push @{$self->bullets}, {
type => $self->factory->get($type_key),
x => $cx,
y => $cy,
vx => cos($angle) * $speed,
vy => sin($angle) * $speed * 0.5,
born => $wave * 2, # 遅延生成
};
}
}
}
# 弾を移動
sub update($self, $frame) {
my @alive;
for my $b (@{$self->bullets}) {
# 遅延生成チェック
next if defined $b->{born} && $frame < $b->{born};
# 移動
$b->{x} += $b->{vx};
$b->{y} += $b->{vy};
# 画面内なら生存
if ($b->{x} >= -5 && $b->{x} < $self->width + 5 &&
$b->{y} >= -5 && $b->{y} < $self->height + 5) {
push @alive, $b;
}
}
@{$self->bullets} = @alive;
}
# 描画
sub render($self, $frame) {
my @screen = map { " " x $self->width } (1 .. $self->height);
for my $b (@{$self->bullets}) {
next if defined $b->{born} && $frame < $b->{born};
$b->{type}->render($b->{x}, $b->{y}, \@screen);
}
return \@screen;
}
# 統計を表示
sub stats($self) {
my $bullet_count = scalar @{$self->bullets};
my $type_count = $self->factory->count;
return "弾: $bullet_count 発 / BulletType: $type_count 種類";
}
}
# ============================================================
# メイン処理
# ============================================================
my $WIDTH = 60;
my $HEIGHT = 25;
my $FRAMES = 20;
my $factory = BulletFactory->new;
my $field = BattleField->new(
width => $WIDTH,
height => $HEIGHT,
factory => $factory,
);
# 弾幕を生成
my $cx = $WIDTH / 2;
my $cy = $HEIGHT / 2;
# 3種類の弾幕パターンを重ねる
$field->spawn_spiral('circle', $cx, $cy, 3, 12, 1.5);
$field->spawn_spiral('star', $cx, $cy, 2, 8, 1.2);
$field->spawn_radial('dot', $cx, $cy, 16, 2.0);
# 初期統計
say "=== 弾幕シューティングエンジン ===";
say $field->stats;
say "";
say "使用中のBulletType: " . join(", ", $factory->types);
say "";
say "アニメーション開始...";
sleep(1);
# ゲームループ
print "\e[2J";
for my $frame (0 .. $FRAMES) {
my $screen = $field->render($frame);
print "\e[H";
for my $row (@$screen) {
say $row;
}
say "-" x $WIDTH;
say "Frame: $frame / $FRAMES | " . $field->stats;
$field->update($frame);
sleep(0.15);
}
# 最終統計
print "\e[" . ($HEIGHT + 3) . ";1H";
say "";
say "=== 完成! ===";
say "弾幕シューティングエンジンが動作しました。";
say "";
say "ポイント:";
say " ✓ 大量の弾を少数のBulletTypeオブジェクトで管理";
say " ✓ BulletFactoryでオブジェクトプールを実現";
say " ✓ BattleFieldで弾の生成・移動・描画を一元管理";
|