package main
import (
"fmt"
"math"
"math/rand"
"time"
"github.com/ryomak/p5go"
)
var (
p *p5go.Canvas
gameMode int
frameCount int
pixelSize = 4 // ドット絵のピクセルサイズ
// 捕獲シーン変数
wildMonster Monster
captureState string // "encounter", "throwing", "shaking", "success", "failed", "gameover"
selectedAction int
pokeball Pokeball
particles []Particle
shakeOffset float64
textAnimation TextAnimation
captureAttempts int
selectedBall int // 選択中のボールタイプ
maxAttempts = 3 // 最大試行回数
// 背景変数
backgroundPixels [][]int
grassPixels [][]int
backgroundType int // 0: 草原, 1: 森, 2: 山, 3: 海辺, 4: 洞窟, 5: 火山, 6: 雪原, 7: 砂漠
animatedObjects []AnimatedObject
weatherParticles []WeatherParticle
weatherType int // 0: なし, 1: 雨, 2: 雪, 3: 落ち葉, 4: 砂嵐
)
type Monster struct {
name string
monsterType int
x, y float64
hp, maxHP int
animOffset float64
color struct{ r, g, b uint8 }
catchRate float64 // 0.0 から 1.0
level int
rarity int // 1-5 stars
hasHat bool // レア度の表示用
isShiny bool // 色違い
accessory string // "none", "crown", "scarf", "glasses", "bowtie", "cape"
size string // "XS", "S", "M", "L", "XL"
sizeValue float64 // 0.5 to 1.5 for actual size multiplier
}
type Pokeball struct {
x, y float64
vx, vy float64
rotation float64
state string // "待機", "投げる", "開く", "揺れる", "捕獲"
shakeCount int
shakeTimer int
trailX []float64
trailY []float64
ballType int // 0: モンスターボール, 1: スーパーボール, 2: ハイパーボール, 3: マスターボール
}
type AnimatedObject struct {
x, y float64
animType string
animFrame int
speed float64
}
type WeatherParticle struct {
x, y float64
vx, vy float64
life float64
size float64
}
type Particle struct {
x, y float64
vx, vy float64
life float64
color struct{ r, g, b, a uint8 }
size float64
particleType string
}
type TextAnimation struct {
text string
x, y float64
progress float64
fadeOut bool
color struct{ r, g, b uint8 }
}
type color struct {
r, g, b uint8
}
func main() {
p5go.Run("#canvas-detail",
p5go.Setup(setup),
p5go.Draw(draw),
p5go.MousePressed(mousePressed),
)
select {}
}
func setup(canvas *p5go.Canvas) {
p = canvas
p.CreateCanvas(400, 400)
p.FrameRate(60) // 60FPSでアニメーションを滑らかに
initBattleScene()
}
func draw(canvas *p5go.Canvas) {
p = canvas
frameCount++
drawBattleScene()
}
func mousePressed(canvas *p5go.Canvas) {
p = canvas
if captureState == "encounter" || captureState == "failed" {
// ランダムにボールを選択して投げる
throwPokeball()
} else if captureState == "gameover" {
// ゲームオーバー時はリセット
initBattleScene()
}
}
func initBattleScene() {
// ポケモン風の名前
monsterNames := []string{"ヒトカゲ", "ゼニガメ", "フシギダネ", "ピカチュウ", "ミュウツー"}
monsterColors := []struct{ r, g, b uint8 }{
{255, 100, 50}, // ほのお - 赤/オレンジ
{50, 150, 255}, // みず - 青
{100, 255, 100}, // くさ - 緑
{255, 255, 50}, // でんき - 黄色
{200, 100, 255}, // エスパー - 紫
}
catchRates := []float64{0.3, 0.4, 0.5, 0.35, 0.25}
monsterType := rand.Intn(5)
// レアリティを決定(1-5星)
rarityRoll := rand.Float64()
rarity := 1
if rarityRoll < 0.02 {
rarity = 5 // ★★★★★ (2%)
} else if rarityRoll < 0.08 {
rarity = 4 // ★★★★ (6%)
} else if rarityRoll < 0.20 {
rarity = 3 // ★★★ (12%)
} else if rarityRoll < 0.45 {
rarity = 2 // ★★ (25%)
} else {
rarity = 1 // ★ (55%)
}
// レベルを決定
level := rand.Intn(50) + 1 + rarity*10 // レア度が高いほどレベルも高い
// 色違い判定 (1/100の確率)
isShiny := rand.Float64() < 0.01
// サイズを決定
sizeRoll := rand.Float64()
var size string
var sizeValue float64
if sizeRoll < 0.05 {
size = "XS"
sizeValue = 0.5 + rand.Float64()*0.2 // 0.5-0.7
} else if sizeRoll < 0.20 {
size = "S"
sizeValue = 0.7 + rand.Float64()*0.2 // 0.7-0.9
} else if sizeRoll < 0.60 {
size = "M"
sizeValue = 0.9 + rand.Float64()*0.2 // 0.9-1.1
} else if sizeRoll < 0.85 {
size = "L"
sizeValue = 1.1 + rand.Float64()*0.2 // 1.1-1.3
} else {
size = "XL"
sizeValue = 1.3 + rand.Float64()*0.2 // 1.3-1.5
}
// 帽子判定 (レア度3以上で確率)
hasHat := rarity >= 3 || (rarity == 2 && rand.Float64() < 0.3)
// アクセサリー決定
accessory := "none"
accessories := []string{"none", "crown", "scarf", "glasses", "bowtie", "cape"}
// レア度が高いほど良いアクセサリー
if rarity == 5 {
accessory = "crown" // ★5は必ず王冠
} else if rarity == 4 {
accessory = accessories[rand.Intn(2)+4] // cape or bowtie
} else if rarity == 3 {
if rand.Float64() < 0.7 {
accessory = accessories[rand.Intn(2)+2] // glasses or scarf
}
} else if rarity == 2 {
if rand.Float64() < 0.3 {
accessory = "scarf"
}
}
// 色を決定(色違いの場合は変更)
monsterColor := monsterColors[monsterType]
if isShiny {
// 色違いは特別な色に
switch monsterType {
case 0: // 火タイプは青色に
monsterColor = struct{ r, g, b uint8 }{50, 100, 255}
case 1: // 水タイプは赤色に
monsterColor = struct{ r, g, b uint8 }{255, 50, 50}
case 2: // 草タイプは金色に
monsterColor = struct{ r, g, b uint8 }{255, 215, 0}
case 3: // 電気タイプは黒色に
monsterColor = struct{ r, g, b uint8 }{50, 50, 50}
case 4: // エスパータイプは白色に
monsterColor = struct{ r, g, b uint8 }{255, 255, 255}
}
}
// サイズによる捕獲率調整(大きいほど捕まえにくい)
sizeCatchModifier := 2.0 - sizeValue // XS(1.5~1.3) to XL(0.7~0.5)
wildMonster = Monster{
name: monsterNames[monsterType],
monsterType: monsterType,
x: 200,
y: 150,
hp: 100,
maxHP: 100,
color: monsterColor,
catchRate: (catchRates[monsterType] - float64(rarity-1)*0.15) * sizeCatchModifier, // レア度とサイズが捕獲率に影響
level: level,
rarity: rarity,
hasHat: hasHat,
isShiny: isShiny,
accessory: accessory,
size: size,
sizeValue: sizeValue,
}
// モンスターボール初期化
pokeball = Pokeball{
x: 200,
y: 350,
state: "idle",
}
captureState = "encounter"
captureAttempts = 0
selectedAction = 0
selectedBall = 0
particles = make([]Particle, 0)
// 背景初期化
initBackground()
}
func initBackground() {
// ランダムに背景タイプを選択
backgroundType = rand.Intn(8)
// ピクセルアート背景作成 (400x400キャンバス用100x100グリッド)
backgroundPixels = make([][]int, 100)
grassPixels = make([][]int, 100)
for i := range backgroundPixels {
backgroundPixels[i] = make([]int, 100)
grassPixels[i] = make([]int, 100)
}
// 背景タイプに応じて生成
switch backgroundType {
case 0: // 草原
generateGrassland()
case 1: // 森
generateForest()
case 2: // 山
generateMountain()
case 3: // 海辺
generateBeach()
case 4: // 洞窟
generateCave()
case 5: // 火山
generateVolcano()
case 6: // 雪原
generateSnowfield()
case 7: // 砂漠
generateDesert()
}
// 天候エフェクトを設定
switch backgroundType {
case 1: // 森は落ち葉
weatherType = 3
case 4: // 洞窟は何もなし
weatherType = 0
case 6: // 雪原は雪
weatherType = 2
case 7: // 砂漠は砂嵐
weatherType = 4
default:
// その他はランダム
if rand.Float64() < 0.3 {
weatherType = rand.Intn(3) + 1
} else {
weatherType = 0
}
}
// 天候パーティクル初期化
weatherParticles = make([]WeatherParticle, 0)
if weatherType > 0 {
for i := 0; i < 50; i++ {
weatherParticles = append(weatherParticles, WeatherParticle{
x: rand.Float64() * 400,
y: rand.Float64() * 400,
vx: rand.Float64()*2 - 1,
vy: rand.Float64()*2 + 1,
life: 1.0,
size: rand.Float64()*3 + 1,
})
}
}
}
func generateGrassland() {
for i := 0; i < 100; i++ {
for j := 0; j < 100; j++ {
if i < 50 {
// 空
if i < 20 {
backgroundPixels[i][j] = 1 // 薄い青
} else if i < 35 {
backgroundPixels[i][j] = 2 // 中間の青
} else {
backgroundPixels[i][j] = 3 // 濃い青
}
// 雲
if i > 10 && i < 30 && ((j+i)%20 < 3) {
backgroundPixels[i][j] = 4
}
} else {
// 地面
backgroundPixels[i][j] = 5
// 草
if rand.Float64() < 0.3 {
grassPixels[i][j] = 1
} else if rand.Float64() < 0.1 {
grassPixels[i][j] = 2 // 花
}
}
}
}
}
func generateForest() {
for i := 0; i < 100; i++ {
for j := 0; j < 100; j++ {
if i < 40 {
// 暗い空
backgroundPixels[i][j] = 6 // 暗い緑
} else {
// 森の地面
backgroundPixels[i][j] = 7
// 木
if rand.Float64() < 0.2 && i < 70 {
grassPixels[i][j] = 3 // 木
}
}
}
}
}
func generateMountain() {
for i := 0; i < 100; i++ {
for j := 0; j < 100; j++ {
if i < 30 {
// 空
backgroundPixels[i][j] = 1
} else if i < 60 {
// 山
if abs(j-50) < (60 - i) {
backgroundPixels[i][j] = 8 // 岩
} else {
backgroundPixels[i][j] = 3
}
} else {
// 地面
backgroundPixels[i][j] = 8
}
}
}
}
func generateBeach() {
for i := 0; i < 100; i++ {
for j := 0; j < 100; j++ {
if i < 40 {
// 空
backgroundPixels[i][j] = 1
} else if i < 70 {
// 海
if (i+j)%5 < 2 {
backgroundPixels[i][j] = 9 // 波
} else {
backgroundPixels[i][j] = 10 // 海
}
} else {
// 砂浜
backgroundPixels[i][j] = 11
}
}
}
}
func generateCave() {
for i := 0; i < 100; i++ {
for j := 0; j < 100; j++ {
// 洞窟内部
if rand.Float64() < 0.1 {
backgroundPixels[i][j] = 12 // 暗い部分
} else {
backgroundPixels[i][j] = 13 // 岩壁
}
// 鍾乳石
if i < 20 && rand.Float64() < 0.05 {
grassPixels[i][j] = 4
}
}
}
}
func generateVolcano() {
for i := 0; i < 100; i++ {
for j := 0; j < 100; j++ {
if i < 30 {
// 赤い空
backgroundPixels[i][j] = 14
} else if i < 60 {
// 火山
if rand.Float64() < 0.1 {
backgroundPixels[i][j] = 15 // 溶岩
} else {
backgroundPixels[i][j] = 16 // 火山岩
}
} else {
// 焼けた地面
backgroundPixels[i][j] = 16
}
}
}
}
func generateSnowfield() {
for i := 0; i < 100; i++ {
for j := 0; j < 100; j++ {
if i < 40 {
// 白い空
backgroundPixels[i][j] = 17
} else {
// 雪の地面
backgroundPixels[i][j] = 18
// 雪だるまや氷
if rand.Float64() < 0.05 {
grassPixels[i][j] = 5
}
}
}
}
}
func generateDesert() {
for i := 0; i < 100; i++ {
for j := 0; j < 100; j++ {
if i < 40 {
// 黄色い空
backgroundPixels[i][j] = 19
} else {
// 砂
if (i+j)%7 < 2 {
backgroundPixels[i][j] = 20 // 砂の模様
} else {
backgroundPixels[i][j] = 21 // 砂
}
// サボテン
if rand.Float64() < 0.02 && i < 80 {
grassPixels[i][j] = 6
}
}
}
}
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
func drawBattleScene() {
// ピクセルアート背景を描画
drawPixelBackground()
// 天候エフェクトを描画
drawWeatherEffects()
// アニメーションを更新
updateAnimations()
// パーティクルを更新して描画
updateParticles()
// 野生のモンスターを描画
if captureState != "shaking" {
// 揺れ中は非表示、成功時とその他は表示
drawMonster(wildMonster)
}
// モンスターボールを描画
drawPokeball()
// UIを描画
drawCaptureUI()
// テキストアニメーションを描画
if textAnimation.text != "" {
drawTextAnimation()
}
}
func drawPixelBackground() {
// 背景ピクセルを描画
for i := 0; i < 100; i++ {
for j := 0; j < 100; j++ {
pixel := backgroundPixels[i][j]
x := float64(j * pixelSize)
y := float64(i * pixelSize)
switch pixel {
case 1: // 薄い青空
p.Fill(150, 200, 255, 255)
case 2: // 中間の青空
p.Fill(120, 180, 240, 255)
case 3: // 濃い青空
p.Fill(100, 160, 220, 255)
case 4: // 雲
p.Fill(255, 255, 255, 200)
case 5: // 地面(草原)
p.Fill(120, 180, 80, 255)
case 6: // 暗い緑(森の空)
p.Fill(40, 80, 60, 255)
case 7: // 森の地面
p.Fill(60, 100, 40, 255)
case 8: // 岩(山)
p.Fill(120, 120, 140, 255)
case 9: // 波
p.Fill(100, 200, 255, 255)
case 10: // 海
p.Fill(50, 150, 220, 255)
case 11: // 砂浜
p.Fill(255, 230, 150, 255)
case 12: // 洞窟暗い部分
p.Fill(30, 30, 40, 255)
case 13: // 洞窟岩壁
p.Fill(80, 70, 90, 255)
case 14: // 赤い空(火山)
p.Fill(200, 80, 60, 255)
case 15: // 溶岩
p.Fill(255, 100, 0, 255)
case 16: // 火山岩
p.Fill(60, 40, 30, 255)
case 17: // 白い空(雪原)
p.Fill(230, 240, 255, 255)
case 18: // 雪
p.Fill(255, 255, 255, 255)
case 19: // 黄色い空(砂漠)
p.Fill(255, 220, 150, 255)
case 20: // 砂の模様
p.Fill(230, 200, 100, 255)
case 21: // 砂
p.Fill(255, 220, 130, 255)
}
p.NoStroke()
p.Rect(x, y, float64(pixelSize), float64(pixelSize))
// 草やオブジェクトを上に描画
grass := grassPixels[i][j]
switch grass {
case 1: // 草
p.Fill(80, 150, 60, 255)
p.Rect(x, y-float64(pixelSize), float64(pixelSize), float64(pixelSize))
case 2: // 花
p.Fill(255, 100, 150, 255)
p.Rect(x, y-float64(pixelSize), float64(pixelSize), float64(pixelSize))
case 3: // 木(森)
p.Fill(40, 120, 40, 255)
p.Rect(x, y-float64(pixelSize)*2, float64(pixelSize), float64(pixelSize)*3)
case 4: // 鍾乳石
p.Fill(150, 150, 180, 255)
p.Rect(x, y, float64(pixelSize), float64(pixelSize)*2)
case 5: // 雪だるま/氷
p.Fill(200, 220, 255, 255)
p.Ellipse(x+float64(pixelSize)/2, y, float64(pixelSize), float64(pixelSize))
case 6: // サボテン
p.Fill(50, 150, 50, 255)
p.Rect(x, y-float64(pixelSize)*2, float64(pixelSize), float64(pixelSize)*3)
p.Rect(x-float64(pixelSize), y-float64(pixelSize), float64(pixelSize), float64(pixelSize))
}
}
}
// パス/バトルエリアをパターンで描画
for i := 60; i < 80; i++ {
for j := 30; j < 70; j++ {
// チェッカーパターン
if (i+j)%2 == 0 {
p.Fill(200, 180, 140, 255)
} else {
p.Fill(180, 160, 120, 255)
}
p.NoStroke()
p.Rect(float64(j*pixelSize), float64(i*pixelSize), float64(pixelSize), float64(pixelSize))
}
}
}
func drawPokeball() {
if pokeball.state == "idle" || pokeball.state == "" {
return
}
ps := float64(pixelSize)
x := pokeball.x
y := pokeball.y
// Pokeball pixel art (7x7)
ballPixels := [][]int{
{0, 2, 2, 2, 2, 2, 0},
{2, 1, 1, 1, 1, 1, 2},
{2, 1, 1, 1, 1, 1, 2},
{2, 2, 2, 3, 2, 2, 2},
{2, 4, 4, 3, 4, 4, 2},
{2, 4, 4, 4, 4, 4, 2},
{0, 2, 2, 2, 2, 2, 0},
}
// 投げたら回転を適用
if pokeball.state == "thrown" {
pokeball.rotation += 0.8 // さらに高速回転
}
p.Push()
p.Translate(x, y)
if pokeball.state == "shaking" {
// 揺れアニメーション(高速化)
shakeAngle := math.Sin(float64(pokeball.shakeTimer)*1.2) * 0.5
p.Rotate(shakeAngle)
} else if pokeball.state == "thrown" {
p.Rotate(pokeball.rotation)
}
for row, pixels := range ballPixels {
for col, pixel := range pixels {
if pixel == 0 {
continue
}
px := float64(col-3) * ps
py := float64(row-3) * ps
switch pixel {
case 1: // Top half - ボールタイプによって色を変更
switch pokeball.ballType {
case 0: // モンスターボール(赤)
p.Fill(255, 50, 50, 255)
case 1: // スーパーボール(青)
p.Fill(50, 100, 255, 255)
case 2: // ハイパーボール(黄色)
p.Fill(255, 200, 0, 255)
case 3: // マスターボール(紫)
p.Fill(150, 50, 255, 255)
}
case 2: // Black outline
p.Fill(0, 0, 0, 255)
case 3: // Button
if pokeball.ballType == 3 {
p.Fill(255, 0, 255, 255) // マスターボールはピンクのボタン
} else {
p.Fill(255, 255, 255, 255)
}
case 4: // Bottom half
if pokeball.ballType == 3 {
p.Fill(255, 255, 255, 255) // マスターボールは白
} else {
p.Fill(240, 240, 240, 255)
}
}
p.NoStroke()
p.Rect(px, py, ps, ps)
}
}
p.Pop()
}
func drawMonster(monster Monster) {
x := monster.x + shakeOffset
y := monster.y + monster.animOffset
// Draw based on monster type with detailed pixel art
switch monster.monsterType {
case 0: // Fire type - Dragon-like
drawFireMonster(x, y, monster)
case 1: // Water type - Fish-like
drawWaterMonster(x, y, monster)
case 2: // Grass type - Plant-like
drawGrassMonster(x, y, monster)
case 3: // Electric type - Mouse-like
drawElectricMonster(x, y, monster)
case 4: // Psychic type - Floating creature
drawPsychicMonster(x, y, monster)
}
// アクセサリーを描画
drawAccessories(x, y, monster)
}
func drawFireMonster(x, y float64, monster Monster) {
scale := 1.2 * monster.sizeValue // サイズを適用
ps := float64(pixelSize) * scale
// ヒトカゲ風ピクセルアート(より詳細に)
charmanderPixels := [][]int{
{0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0},
{0, 0, 0, 2, 1, 1, 1, 1, 2, 0, 0, 0},
{0, 0, 2, 1, 1, 1, 1, 1, 1, 2, 0, 0},
{0, 2, 1, 1, 3, 1, 1, 3, 1, 1, 2, 0},
{0, 2, 1, 1, 7, 1, 1, 7, 1, 1, 2, 0},
{2, 1, 1, 1, 1, 8, 8, 1, 1, 1, 1, 2},
{2, 1, 1, 1, 4, 4, 4, 4, 1, 1, 1, 2},
{2, 1, 1, 4, 4, 4, 4, 4, 4, 1, 1, 2},
{2, 1, 1, 1, 4, 4, 4, 4, 1, 1, 1, 2},
{0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0},
{0, 0, 2, 5, 5, 0, 0, 5, 5, 2, 0, 0},
{0, 0, 5, 5, 5, 0, 0, 5, 5, 5, 0, 0},
{0, 0, 0, 6, 6, 6, 6, 6, 6, 0, 0, 0},
{0, 0, 6, 6, 6, 9, 9, 6, 6, 6, 0, 0},
}
// カラーマッピング: 0=透明, 1=メインカラー, 2=ダークシェード, 3=目白, 4=腹, 5=足, 6=尻尾の炎, 7=目黒, 8=鼻, 9=炎コア
for row, pixels := range charmanderPixels {
for col, pixel := range pixels {
if pixel == 0 {
continue
}
px := x + float64(col-6)*ps
py := y + float64(row-7)*ps
switch pixel {
case 1: // メインボディ
p.Fill(monster.color.r, monster.color.g, monster.color.b, 255)
case 2: // ダークアウトライン
p.Fill(monster.color.r*3/4, monster.color.g*3/4, monster.color.b*3/4, 255)
case 3: // 目白
p.Fill(255, 255, 255, 255)
case 4: // 腹(クリーム色)
p.Fill(255, 230, 180, 255)
case 5: // 足
p.Fill(monster.color.r*4/5, monster.color.g*4/5, monster.color.b*4/5, 255)
case 6: // 尻尾の炎
if frameCount%10 < 5 {
p.Fill(255, 100, 0, 255)
} else {
p.Fill(255, 200, 0, 255)
}
case 7: // 目黒
p.Fill(0, 0, 0, 255)
case 8: // 鼻
p.Fill(50, 50, 50, 255)
case 9: // 炎コア
if frameCount%8 < 4 {
p.Fill(255, 255, 150, 255)
} else {
p.Fill(255, 220, 100, 255)
}
}
p.NoStroke()
p.Rect(px, py, ps, ps)
}
}
// 炎のアニメーション
for i := 0; i < 5; i++ {
flameOffset := math.Sin(float64(frameCount+i*15)*0.15) * ps * 0.5
flameHeight := math.Sin(float64(frameCount+i*20)*0.2) * ps * 0.3
fx := x + float64(i-2)*ps*0.3 + flameOffset
fy := y + 4*ps - float64(i)*ps*0.5 + flameHeight
alpha := uint8(200 - i*30)
if frameCount%15 < 7 {
p.Fill(255, uint8(200-i*30), 0, alpha)
} else {
p.Fill(255, uint8(150-i*20), 50, alpha)
}
p.NoStroke()
p.Ellipse(fx, fy, ps*0.8, ps*1.2)
}
}
func drawWaterMonster(x, y float64, monster Monster) {
scale := 1.2 * monster.sizeValue // サイズを適用
ps := float64(pixelSize) * scale
swim := math.Sin(float64(frameCount)*0.15) * ps * 0.3
// ゼニガメ風ピクセルアート
turtlePixels := [][]int{
{0, 0, 0, 0, 5, 5, 5, 5, 0, 0, 0, 0},
{0, 0, 0, 5, 6, 6, 6, 6, 5, 0, 0, 0},
{0, 0, 5, 6, 6, 6, 6, 6, 6, 5, 0, 0},
{0, 5, 6, 6, 6, 6, 6, 6, 6, 6, 5, 0},
{0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0},
{2, 1, 1, 3, 1, 1, 1, 1, 3, 1, 1, 2},
{2, 1, 1, 7, 1, 1, 1, 1, 7, 1, 1, 2},
{2, 1, 1, 1, 1, 8, 8, 1, 1, 1, 1, 2},
{2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2},
{0, 2, 4, 1, 1, 1, 1, 1, 1, 4, 2, 0},
{0, 0, 4, 4, 0, 2, 2, 0, 4, 4, 0, 0},
{0, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0},
}
for row, pixels := range turtlePixels {
for col, pixel := range pixels {
if pixel == 0 {
continue
}
px := x + float64(col-6)*ps
py := y + float64(row-6)*ps + swim
switch pixel {
case 1: // Main body
p.Fill(monster.color.r, monster.color.g, monster.color.b, 255)
case 2: // Dark outline
p.Fill(monster.color.r*3/4, monster.color.g*3/4, monster.color.b*3/4, 255)
case 3: // Eyes white
p.Fill(255, 255, 255, 255)
case 4: // Fins/hands
p.Fill(monster.color.r-30, monster.color.g-30, monster.color.b+30, 200)
case 5: // Shell edge
p.Fill(100, 80, 60, 255)
case 6: // Shell
if (row+col)%2 == 0 {
p.Fill(120, 100, 80, 255)
} else {
p.Fill(140, 120, 100, 255)
}
case 7: // Eye pupil
p.Fill(0, 0, 0, 255)
case 8: // Nose
p.Fill(50, 50, 100, 255)
case 9: // Tail
waveOffset := math.Sin(float64(frameCount)*0.2+float64(col)*0.5) * ps * 0.2
py += waveOffset
p.Fill(monster.color.r*4/5, monster.color.g*4/5, monster.color.b*4/5, 255)
}
p.NoStroke()
p.Rect(px, py, ps, ps)
}
}
// 波紋エフェクト
if frameCount%30 < 15 {
for i := 0; i < 3; i++ {
ringRadius := float64(i+1)*15 + float64(frameCount%30)
alpha := uint8(100 - i*30 - frameCount%30*2)
p.Stroke(100, 150, 255, alpha)
p.StrokeWeight(2)
p.NoFill()
p.Ellipse(x, y+ps*2, ringRadius, ringRadius/2)
}
p.NoStroke()
}
// Bubbles
for i := 0; i < 2; i++ {
bubbleY := y - float64(4+i*2)*ps - float64(frameCount%40)*0.5
if bubbleY > y-8*ps {
p.Fill(200, 220, 255, 150)
p.Rect(x+float64(i-1)*ps*2, bubbleY, ps*0.6, ps*0.6)
}
}
}
func drawGrassMonster(x, y float64, monster Monster) {
scale := 1.2 * monster.sizeValue // サイズを適用
ps := float64(pixelSize) * scale
// フシギダネ風ピクセルアート
bulbasaurPixels := [][]int{
{0, 0, 0, 5, 5, 5, 5, 5, 5, 0, 0, 0},
{0, 0, 5, 6, 5, 5, 5, 5, 6, 5, 0, 0},
{0, 5, 6, 6, 5, 5, 5, 5, 6, 6, 5, 0},
{0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0},
{0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0},
{2, 1, 1, 3, 1, 1, 1, 1, 3, 1, 1, 2},
{2, 1, 1, 7, 1, 8, 8, 1, 7, 1, 1, 2},
{2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2},
{2, 9, 1, 1, 1, 1, 1, 1, 1, 1, 9, 2},
{0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0},
{0, 0, 2, 4, 4, 0, 0, 4, 4, 2, 0, 0},
{0, 0, 4, 4, 4, 0, 0, 4, 4, 4, 0, 0},
}
for row, pixels := range bulbasaurPixels {
for col, pixel := range pixels {
if pixel == 0 {
continue
}
px := x + float64(col-6)*ps
py := y + float64(row-6)*ps
switch pixel {
case 1: // Main body
p.Fill(monster.color.r, monster.color.g, monster.color.b, 255)
case 2: // Dark outline
p.Fill(monster.color.r*3/4, monster.color.g*3/4, monster.color.b*3/4, 255)
case 3: // Eyes white
p.Fill(255, 255, 255, 255)
case 4: // Roots/feet
p.Fill(monster.color.r*4/5, monster.color.g*4/5, monster.color.b*4/5, 255)
case 5: // Bulb leaves
p.Fill(50, 150, 50, 255)
case 6: // Bulb flower
if frameCount%60 < 30 {
p.Fill(255, 150, 200, 255)
} else {
p.Fill(255, 200, 150, 255)
}
case 7: // Eye pupil
p.Fill(0, 0, 0, 255)
case 8: // Nose
p.Fill(50, 100, 50, 255)
case 9: // Spots
p.Fill(monster.color.r/2, monster.color.g, monster.color.b/2, 255)
}
p.NoStroke()
p.Rect(px, py, ps, ps)
}
}
// 花びらエフェクト
for i := 0; i < 3; i++ {
petalOffset := math.Sin(float64(frameCount+i*40)*0.1) * ps * 2
petalY := y - float64(5+i)*ps + petalOffset
petalX := x + float64(i-1)*ps*3
if frameCount%60 < 30 {
p.Fill(255, 150, 200, 150)
} else {
p.Fill(255, 200, 150, 150)
}
p.NoStroke()
p.Ellipse(petalX, petalY, ps, ps*1.5)
}
}
func drawElectricMonster(x, y float64, monster Monster) {
scale := 1.0 * monster.sizeValue // サイズを適用
ps := float64(pixelSize) * scale
staticOffset := math.Sin(float64(frameCount)*0.3) * ps * 0.2
// Draw pixel art electric mouse
mousePixels := [][]int{
{0, 2, 0, 0, 0, 0, 0, 2, 0},
{2, 2, 0, 0, 0, 0, 0, 2, 2},
{0, 2, 1, 1, 1, 1, 1, 2, 0},
{2, 1, 3, 1, 1, 1, 3, 1, 2},
{2, 5, 1, 1, 1, 1, 1, 5, 2},
{2, 1, 1, 1, 4, 1, 1, 1, 2},
{0, 2, 1, 1, 1, 1, 1, 2, 0},
{0, 0, 2, 1, 1, 1, 2, 0, 0},
{0, 0, 2, 2, 0, 2, 2, 0, 0},
}
for row, pixels := range mousePixels {
for col, pixel := range pixels {
if pixel == 0 {
continue
}
px := x + float64(col-4)*ps
py := y + float64(row-4)*ps + staticOffset
switch pixel {
case 1: // Main body
p.Fill(monster.color.r, monster.color.g, monster.color.b, 255)
case 2: // Dark outline/ears
p.Fill(0, 0, 0, 255)
case 3: // Eyes
p.Fill(255, 255, 255, 255)
case 4: // Lightning bolt
p.Fill(255, 255, 150, 255)
case 5: // Cheeks
p.Fill(255, 100, 100, 255)
}
p.NoStroke()
p.Rect(px, py, ps, ps)
}
}
// Eye pupils
p.Fill(0, 0, 0, 255)
p.Rect(x-2*ps, y-ps+staticOffset, ps*0.5, ps*0.5)
p.Rect(x+1.5*ps, y-ps+staticOffset, ps*0.5, ps*0.5)
// Electric sparks
if frameCount%10 < 5 {
for i := 0; i < 3; i++ {
angle := float64(i) * 2 * math.Pi / 3
sparkX := x + math.Cos(angle)*5*ps
sparkY := y + math.Sin(angle)*5*ps
p.Fill(255, 255, 0, 200)
p.Rect(sparkX, sparkY, ps*0.4, ps*0.4)
}
}
}
func drawPsychicMonster(x, y float64, monster Monster) {
scale := 1.0 * monster.sizeValue // サイズを適用
ps := float64(pixelSize) * scale
float := math.Sin(float64(frameCount)*0.1) * ps * 0.5
// Draw pixel art psychic creature
psychicPixels := [][]int{
{0, 0, 5, 5, 5, 5, 5, 0, 0},
{0, 0, 2, 2, 2, 2, 2, 0, 0},
{0, 2, 1, 1, 1, 1, 1, 2, 0},
{2, 1, 3, 1, 4, 1, 3, 1, 2},
{2, 1, 1, 1, 1, 1, 1, 1, 2},
{2, 1, 1, 1, 1, 1, 1, 1, 2},
{0, 2, 1, 1, 1, 1, 1, 2, 0},
{0, 0, 2, 6, 6, 6, 2, 0, 0},
{0, 0, 6, 0, 0, 0, 6, 0, 0},
}
// Psychic aura
for i := 2; i > 0; i-- {
alpha := uint8(30 * (3 - i))
p.Fill(200, 150, 255, alpha)
p.NoStroke()
p.Rect(x-float64(i+3)*ps, y-float64(i+3)*ps+float, float64(2*i+7)*ps, float64(2*i+7)*ps)
}
for row, pixels := range psychicPixels {
for col, pixel := range pixels {
if pixel == 0 {
continue
}
px := x + float64(col-4)*ps
py := y + float64(row-4)*ps + float
switch pixel {
case 1: // Main body
p.Fill(monster.color.r, monster.color.g, monster.color.b, 255)
case 2: // Dark outline
p.Fill(monster.color.r/2, monster.color.g/2, monster.color.b/2, 255)
case 3: // Eyes
p.Fill(255, 255, 255, 255)
case 4: // Gem
p.Fill(255, 100, 200, 255)
case 5: // Crown/horns
p.Fill(255, 200, 100, 255)
case 6: // Tentacles
p.Fill(monster.color.r-30, monster.color.g-30, monster.color.b-30, 200)
}
p.NoStroke()
p.Rect(px, py, ps, ps)
}
}
// Eye pupils with glow
eyeGlow := math.Sin(float64(frameCount)*0.2)*127 + 128
p.Fill(150, 100, 200, uint8(eyeGlow))
p.Rect(x-2*ps, y-ps+float, ps*0.7, ps*0.7)
p.Rect(x+1.3*ps, y-ps+float, ps*0.7, ps*0.7)
// Orbiting particles
for i := 0; i < 4; i++ {
angle := float64(i)*math.Pi/2 + float64(frameCount)*0.05
particleX := x + math.Cos(angle)*6*ps
particleY := y + math.Sin(angle)*6*ps
p.Fill(255, 200, 255, 150)
p.Rect(particleX, particleY, ps*0.5, ps*0.5)
}
}
func updateAnimations() {
// モンスターの待機アニメーション更新
wildMonster.animOffset = math.Sin(float64(frameCount)*0.3) * 3 // 高速化
// モンスターボールのアニメーション更新
if pokeball.state == "thrown" {
// 軌跡を追加
pokeball.trailX = append(pokeball.trailX, pokeball.x)
pokeball.trailY = append(pokeball.trailY, pokeball.y)
if len(pokeball.trailX) > 10 {
pokeball.trailX = pokeball.trailX[1:]
pokeball.trailY = pokeball.trailY[1:]
}
// モンスターに向かって移動(大幅に高速化)
pokeball.x += pokeball.vx * 3
pokeball.y += pokeball.vy * 3
pokeball.vy += 0.8 // 重力
// モンスターにヒットしたかチェック
dist := math.Sqrt(math.Pow(pokeball.x-wildMonster.x, 2) + math.Pow(pokeball.y-wildMonster.y, 2))
if dist < 30 {
pokeball.state = "shaking"
pokeball.shakeCount = 0
pokeball.shakeTimer = 0
captureState = "shaking"
// モンスターを隠す(ボールに吸い込まれる演出)
// リッチな吸い込みエフェクト
for wave := 0; wave < 3; wave++ {
for i := 0; i < 30; i++ {
angle := float64(i) * math.Pi * 2 / 30
radius := float64(wave+1) * 20
particles = append(particles, Particle{
x: wildMonster.x + math.Cos(angle)*radius,
y: wildMonster.y + math.Sin(angle)*radius,
vx: -math.Cos(angle) * 6,
vy: -math.Sin(angle) * 6,
life: 1.0 + float64(wave)*0.2,
color: struct{ r, g, b, a uint8 }{
r: uint8(255 - wave*50),
g: uint8(100 + wave*30),
b: uint8(100 + wave*50),
a: uint8(250 - wave*30),
},
size: rand.Float64()*4 + 3,
particleType: "capture",
})
}
}
// スピードラインエフェクト
for i := 0; i < 15; i++ {
angle := rand.Float64() * math.Pi * 2
speed := rand.Float64()*10 + 5
particles = append(particles, Particle{
x: wildMonster.x,
y: wildMonster.y,
vx: math.Cos(angle) * speed,
vy: math.Sin(angle) * speed,
life: 0.5,
color: struct{ r, g, b, a uint8 }{
r: 255,
g: 255,
b: 255,
a: 200,
},
size: 1,
particleType: "line",
})
}
}
} else if pokeball.state == "shaking" {
pokeball.shakeTimer++
// 3回揺れる(さらに高速)
if pokeball.shakeTimer%5 == 0 { // 10から5に変更で2倍速
pokeball.shakeCount++
if pokeball.shakeCount >= 3 {
// 捕獲成功判定(ボールタイプによって補正)
catchBonus := 1.0
switch pokeball.ballType {
case 1: // スーパーボール
catchBonus = 1.5
case 2: // ハイパーボール
catchBonus = 2.0
case 3: // マスターボール
catchBonus = 100.0 // 必ず捕まえる
}
if rand.Float64() < wildMonster.catchRate*catchBonus {
// 成功!画面全体で祝福!
captureState = "success"
pokeball.state = "captured"
// ボールをモンスターの横に配置
pokeball.x = wildMonster.x - 50
pokeball.y = wildMonster.y + 20
// レア度に応じたエフェクト
particleCount := 80 + wildMonster.rarity*50
// リング状のエフェクト(レア度に応じて)
for ring := 0; ring < wildMonster.rarity+2; ring++ {
for i := 0; i < 36; i++ {
angle := float64(i) * math.Pi * 2 / 36
radius := float64(ring+1) * 25
particles = append(particles, Particle{
x: pokeball.x + math.Cos(angle)*radius,
y: pokeball.y + math.Sin(angle)*radius,
vx: math.Cos(angle) * 4,
vy: math.Sin(angle) * 4,
life: 2.0 + float64(ring)*0.2,
color: struct{ r, g, b, a uint8 }{
r: uint8(255),
g: uint8(215 - ring*20),
b: uint8(0 + ring*40),
a: uint8(255 - ring*20),
},
size: float64(8-ring) + rand.Float64()*4,
particleType: "star",
})
}
}
// 爆発エフェクト
for i := 0; i < 50; i++ {
angle := rand.Float64() * math.Pi * 2
speed := rand.Float64()*15 + 5
particles = append(particles, Particle{
x: pokeball.x,
y: pokeball.y,
vx: math.Cos(angle) * speed,
vy: math.Sin(angle) * speed,
life: 1.5,
color: struct{ r, g, b, a uint8 }{
r: uint8(rand.Intn(56) + 200),
g: uint8(rand.Intn(56) + 200),
b: uint8(rand.Intn(100) + 155),
a: 255,
},
size: rand.Float64()*6 + 2,
particleType: "circle",
})
}
// 画面全体に大量の祝福パーティクル
for i := 0; i < particleCount; i++ {
angle := rand.Float64() * math.Pi * 2
speed := rand.Float64()*8 + 2
// 画面の色々な場所から
startX := rand.Float64() * 400
startY := 400.0 // 下から打ち上げ
particles = append(particles, Particle{
x: startX,
y: startY,
vx: math.Cos(angle) * speed * 0.3,
vy: -speed - rand.Float64()*5, // 上に打ち上げ
life: 2.0,
color: struct{ r, g, b, a uint8 }{
r: uint8(rand.Intn(100) + 155),
g: uint8(rand.Intn(100) + 155),
b: uint8(rand.Intn(100) + 155),
a: 255,
},
size: rand.Float64()*8 + 4,
particleType: "star",
})
}
} else {
// 失敗時の処理
// ボールから逃げ出すエフェクト
for i := 0; i < 20; i++ {
angle := rand.Float64() * math.Pi * 2
speed := rand.Float64()*5 + 3
particles = append(particles, Particle{
x: pokeball.x,
y: pokeball.y,
vx: math.Cos(angle) * speed,
vy: math.Sin(angle) * speed,
life: 0.8,
color: struct{ r, g, b, a uint8 }{
r: 255,
g: 100,
b: 100,
a: 200,
},
size: rand.Float64()*3 + 2,
particleType: "escape",
})
}
if captureAttempts >= maxAttempts {
// 3回失敗したらゲームオーバー(逃げられた)
captureState = "gameover"
pokeball.state = "idle"
// ここで停止(自動で次のボールを投げない)
} else {
// まだ試行回数が残っている場合は自動で次のボールを投げる
captureState = "failed"
pokeball.state = "idle"
// 自動で次のボールを投げる
go func() {
time.Sleep(800 * time.Millisecond) // 少し間を置く
if captureState == "failed" && captureAttempts < maxAttempts {
// 自動で次のボールを投げる
throwPokeball()
}
}()
}
}
}
}
}
// テキストアニメーション更新
if textAnimation.text != "" {
textAnimation.progress += 0.08 // 大幅に高速化
if textAnimation.fadeOut && textAnimation.progress > 1.0 {
textAnimation.text = ""
}
}
}
func updateParticles() {
for i := len(particles) - 1; i >= 0; i-- {
p := &particles[i]
// 位置を更新
p.x += p.vx
p.y += p.vy
// Apply gravity for some particles
if p.particleType == "impact" {
p.vy += 0.3
}
// Fade out
p.life -= 0.02
// Remove dead particles
if p.life <= 0 {
particles = append(particles[:i], particles[i+1:]...)
}
}
// Draw particles
for _, particle := range particles {
alpha := uint8(particle.life * float64(particle.color.a))
p.Fill(particle.color.r, particle.color.g, particle.color.b, alpha)
p.NoStroke()
if particle.particleType == "star" {
// Draw star shape
drawStar(particle.x, particle.y, particle.size)
} else {
// Draw circle
p.Ellipse(particle.x, particle.y, particle.size, particle.size)
}
}
}
func drawStar(x, y, size float64) {
p.Push()
p.Translate(x, y)
for i := 0; i < 5; i++ {
angle := float64(i)*2*math.Pi/5 - math.Pi/2
x1 := math.Cos(angle) * size
y1 := math.Sin(angle) * size
angle2 := angle + math.Pi/5
x2 := math.Cos(angle2) * size * 0.5
y2 := math.Sin(angle2) * size * 0.5
p.Triangle(0, 0, x1, y1, x2, y2)
}
p.Pop()
}
func drawAccessories(x, y float64, monster Monster) {
ps := float64(pixelSize)
// 帽子の描画
if monster.hasHat {
// 赤い帽子
hatPixels := [][]int{
{0, 0, 2, 2, 2, 2, 2, 0, 0},
{0, 2, 1, 1, 1, 1, 1, 2, 0},
{2, 1, 1, 3, 1, 3, 1, 1, 2},
{2, 2, 2, 2, 2, 2, 2, 2, 2},
}
for row, pixels := range hatPixels {
for col, pixel := range pixels {
if pixel == 0 {
continue
}
px := x + float64(col-4)*ps
py := y + float64(row-10)*ps - 5
switch pixel {
case 1: // 帽子メイン
p.Fill(255, 50, 50, 255)
case 2: // 帽子の縁
p.Fill(180, 30, 30, 255)
case 3: // 飾り
p.Fill(255, 255, 0, 255)
}
p.NoStroke()
p.Rect(px, py, ps, ps)
}
}
}
// その他のアクセサリー
switch monster.accessory {
case "crown":
// 王冠
crownPixels := [][]int{
{0, 3, 0, 3, 0, 3, 0},
{0, 1, 1, 1, 1, 1, 0},
{1, 1, 2, 1, 2, 1, 1},
{1, 1, 1, 1, 1, 1, 1},
}
for row, pixels := range crownPixels {
for col, pixel := range pixels {
if pixel == 0 {
continue
}
px := x + float64(col-3)*ps
py := y + float64(row-10)*ps - 8
switch pixel {
case 1: // 王冠ベース
p.Fill(255, 215, 0, 255)
case 2: // 宝石
p.Fill(255, 50, 200, 255)
case 3: // トップ
p.Fill(255, 255, 100, 255)
}
p.NoStroke()
p.Rect(px, py, ps, ps)
}
}
case "scarf":
// マフラー
scarfColor := struct{ r, g, b uint8 }{255, 100, 100}
if monster.rarity >= 2 {
scarfColor = struct{ r, g, b uint8 }{150, 100, 255} // レアは紫
}
// 首回り
p.Fill(scarfColor.r, scarfColor.g, scarfColor.b, 255)
p.NoStroke()
p.Rect(x-3*ps, y+2*ps, 6*ps, ps)
// なびく部分
wave := math.Sin(float64(frameCount)*0.1) * ps
p.Rect(x+3*ps, y+2*ps, 2*ps, 3*ps)
p.Rect(x+4*ps+wave, y+3*ps, ps, 2*ps)
case "glasses":
// メガネ
p.Fill(0, 0, 0, 255)
p.NoStroke()
// フレーム
p.Rect(x-3*ps, y-ps, 2*ps, 2*ps)
p.Rect(x+ps, y-ps, 2*ps, 2*ps)
p.Rect(x-ps, y-0.5*ps, 2*ps, ps*0.5)
// レンズ
p.Fill(200, 200, 255, 100)
p.Rect(x-2.5*ps, y-0.5*ps, ps, ps)
p.Rect(x+1.5*ps, y-0.5*ps, ps, ps)
case "bowtie":
// 蝶ネクタイ
p.Fill(50, 50, 200, 255)
p.NoStroke()
// 左側
p.Triangle(x-ps, y+3*ps, x-3*ps, y+2*ps, x-3*ps, y+4*ps)
// 右側
p.Triangle(x+ps, y+3*ps, x+3*ps, y+2*ps, x+3*ps, y+4*ps)
// 中央
p.Fill(255, 255, 0, 255)
p.Rect(x-ps, y+2.5*ps, 2*ps, ps)
case "cape":
// マント
capeColor := struct{ r, g, b uint8 }{100, 50, 150}
if monster.rarity == 3 {
// レジェンドは金色のマント
capeColor = struct{ r, g, b uint8 }{200, 150, 0}
}
flow := math.Sin(float64(frameCount)*0.08) * ps * 0.5
p.Fill(capeColor.r, capeColor.g, capeColor.b, 200)
p.NoStroke()
// マントの形
for i := 0; i < 5; i++ {
width := float64(5-i) * ps
p.Rect(x-width/2, y+float64(i)*ps+flow, width, ps)
}
}
// 色違いの場合は特別なオーラエフェクト
if monster.isShiny {
if frameCount%20 < 10 {
// キラキラエフェクト
for i := 0; i < 3; i++ {
angle := float64(frameCount)*0.1 + float64(i)*2*math.Pi/3
sparkX := x + math.Cos(angle)*8*ps
sparkY := y + math.Sin(angle)*8*ps
p.Fill(255, 255, 100, 150)
p.NoStroke()
drawStar(sparkX, sparkY, ps)
}
}
}
}
func drawCaptureUI() {
// テキストボックスの背景を描画
p.Fill(255, 255, 255, 255)
p.NoStroke()
p.Rect(10, 300, 380, 90)
p.Fill(0, 0, 100, 255)
p.Rect(15, 305, 370, 80)
// ステータステキストを描画
p.Fill(255, 255, 255, 255)
p.TextSize(14)
if captureState == "encounter" {
shinyMark := ""
if wildMonster.isShiny {
shinyMark = "✨"
}
p.Text(fmt.Sprintf("やせいの %s%s があらわれた!", shinyMark, wildMonster.name), 30, 330)
p.TextSize(10)
p.Text("クリックで ボールを なげる!(ランダム)", 30, 350)
} else if captureState == "failed" {
p.Text(fmt.Sprintf("%s は ボールから でてしまった!", wildMonster.name), 30, 330)
p.TextSize(12)
p.Text(fmt.Sprintf("のこり %d かい", maxAttempts-captureAttempts), 30, 350)
p.TextSize(10)
p.Text("クリックで もういちど ボールを なげる!", 30, 370)
} else if captureState == "throwing" {
ballNames := []string{"モンスター", "スーパー", "ハイパー", "マスター"}
p.Text(fmt.Sprintf("いけっ! %sボール!", ballNames[pokeball.ballType]), 30, 340)
} else if captureState == "shaking" {
shakeText := ""
for i := 0; i < pokeball.shakeCount; i++ {
shakeText += "・"
}
p.Text(shakeText, 30, 340)
} else if captureState == "success" {
// 成功時の表示(捕獲後に詳細情報を表示)
shinyText := ""
if wildMonster.isShiny {
shinyText = "✨色違い✨ "
}
// 星でレア度を表示(色付き)
stars := ""
for i := 0; i < wildMonster.rarity; i++ {
stars += "⭐"
}
p.Text(fmt.Sprintf("%s%s を つかまえた!", shinyText, wildMonster.name), 30, 330)
p.Text(fmt.Sprintf("Lv.%d サイズ:%s %s", wildMonster.level, wildMonster.size, stars), 30, 350)
if wildMonster.accessory != "none" {
p.TextSize(123)
p.Text(fmt.Sprintf("アクセサリー: %s", wildMonster.accessory), 30, 350)
}
} else if captureState == "gameover" {
// ゲームオーバー時の表示
p.Text(fmt.Sprintf("%s は にげだした!", wildMonster.name), 30, 330)
p.TextSize(12)
p.Text("つかまえられなかった...", 30, 350)
p.Text("クリックで もういちど", 30, 370)
}
// 残り試行回数を描画
p.Fill(255, 255, 255, 255)
p.TextSize(10)
p.Text(fmt.Sprintf("のこり: %d/%d", maxAttempts-captureAttempts, maxAttempts), 300, 320)
}
func throwPokeball() {
if captureState != "encounter" && captureState != "failed" {
return
}
captureState = "throwing"
captureAttempts++
// ランダムにボールタイプを選択(運試し)
ballRoll := rand.Float64()
if ballRoll < 0.05 {
pokeball.ballType = 3 // マスターボール (5%)
} else if ballRoll < 0.20 {
pokeball.ballType = 2 // ハイパーボール (15%)
} else if ballRoll < 0.50 {
pokeball.ballType = 1 // スーパーボール (30%)
} else {
pokeball.ballType = 0 // モンスターボール (50%)
}
// 投げる軌道を計算
pokeball.state = "thrown"
pokeball.x = 200
pokeball.y = 350
dx := wildMonster.x - pokeball.x
dy := wildMonster.y - pokeball.y - 50 // 少し高めを狙う
// 初期速度を計算(高速化)
pokeball.vx = dx / 10 // 大幅に高速化
pokeball.vy = dy/10 - 7 // 上向きの弧を追加
pokeball.rotation = 0
}
func drawHPBar_old(x, y float64, monster Monster, isPlayer bool) {
// Background
p.Fill(0, 0, 0, 200)
p.NoStroke()
p.Rect(x-5, y-5, 110, 50)
// Name
p.Fill(255, 255, 255, 255)
p.TextSize(10)
p.Text(monster.name, x, y+8)
// HP bar background
p.Fill(50, 50, 50, 255)
p.Rect(x, y+12, 100, 8)
// HP bar fill
hpPercent := float64(monster.hp) / float64(monster.maxHP)
hpColor := struct{ r, g, b uint8 }{100, 255, 100}
if hpPercent < 0.5 {
hpColor = struct{ r, g, b uint8 }{255, 200, 0}
}
if hpPercent < 0.25 {
hpColor = struct{ r, g, b uint8 }{255, 100, 100}
}
p.Fill(hpColor.r, hpColor.g, hpColor.b, 255)
p.Rect(x, y+12, 100*hpPercent, 8)
// HP numbers
p.Fill(255, 255, 255, 255)
p.TextSize(8)
p.Text(fmt.Sprintf("%d/%d", monster.hp, monster.maxHP), x+35, y+30)
}
func drawTextAnimation() {
y := textAnimation.y
if !textAnimation.fadeOut {
y -= textAnimation.progress * 20
}
alpha := uint8(255)
if textAnimation.fadeOut {
alpha = uint8(255 * (1 - textAnimation.progress))
}
// Text shadow
p.Fill(0, 0, 0, alpha/2)
p.TextSize(20)
p.Text(textAnimation.text, textAnimation.x+2, y+2)
// Main text
p.Fill(textAnimation.color.r, textAnimation.color.g, textAnimation.color.b, alpha)
p.Text(textAnimation.text, textAnimation.x, y)
}
func drawMiniPokeball(x, y float64) {
// Draw small pokeball icon
p.Fill(255, 50, 50, 255)
p.NoStroke()
p.Ellipse(x, y, 8, 8)
p.Fill(240, 240, 240, 255)
p.Arc(x, y, 8, 8, 0, math.Pi)
p.Fill(0, 0, 0, 255)
p.Rect(x-4, y-1, 8, 2)
p.Fill(255, 255, 255, 255)
p.Ellipse(x, y, 3, 3)
}
func drawAnimatedObjects() {
for i := range animatedObjects {
obj := &animatedObjects[i]
// 位置を更新
obj.x += obj.speed
if obj.x > 410 {
obj.x = -10
}
// Draw based on type
if obj.animType == "butterfly" {
// Animated butterfly
wingFlap := math.Sin(float64(frameCount)*0.3+float64(i)) * 10
p.Fill(255, 200, 100, 200)
p.NoStroke()
p.Ellipse(obj.x-wingFlap, obj.y, 6, 4)
p.Ellipse(obj.x+wingFlap, obj.y, 6, 4)
p.Fill(100, 50, 0, 255)
p.Ellipse(obj.x, obj.y, 3, 6)
}
}
}
func drawWeatherEffects() {
if weatherType == 0 {
return
}
// Update and draw weather particles
for i := range weatherParticles {
particle := &weatherParticles[i]
// 位置を更新
particle.x += particle.vx
particle.y += particle.vy
// Wrap around screen
if particle.y > 400 {
particle.y = 0
particle.x = rand.Float64() * 400
}
if particle.x < 0 {
particle.x = 400
} else if particle.x > 400 {
particle.x = 0
}
// Draw particle
switch weatherType {
case 1: // Rain
p.Stroke(100, 150, 200, 150)
p.StrokeWeight(1)
p.Line(particle.x, particle.y, particle.x-particle.vx*2, particle.y-particle.vy*2)
p.NoStroke()
case 2: // Snow
p.Fill(255, 255, 255, 200)
p.NoStroke()
p.Ellipse(particle.x, particle.y, particle.size, particle.size)
case 3: // Leaves
leafAngle := float64(frameCount)*0.05 + particle.x
p.Fill(150, 100, 50, 150)
p.Push()
p.Translate(particle.x, particle.y)
p.Rotate(leafAngle)
p.Ellipse(0, 0, particle.size*2, particle.size)
p.Pop()
case 4: // Sandstorm
p.Fill(255, 220, 130, 100)
p.NoStroke()
p.Ellipse(particle.x, particle.y, particle.size*2, particle.size*2)
}
}
// 天候に応じた全体エフェクト
if weatherType == 4 {
// 砂嵐の場合は画面全体に薄い砂のエフェクト
p.Fill(255, 220, 100, 30)
p.NoStroke()
p.Rect(0, 0, 400, 400)
}
}