実験#71:成長の脅威(タケノコ切り倒しゲーム)

■71個目 「タケノコ切り倒しゲーム」

こんにちは、AIと作る100のゲーム実験室の みー です。(*^_^*)/

今回は、ちょっと変わった……
いえ、かなりパワフルなタケノコ狩りゲームを作ってみました。

その名も 「究極・タケノコ切り倒しゲーム」 です!

静かな竹林を舞台に、驚異のスピードで成長するタケノコを、
鉈(なた)で次々と切り倒していくアクションゲームになっています。

今回こだわったのは、なんといっても 鉈でザクザク斬る爽快感

タケノコって、本当に成長が早いんですよね。
うっかりしていると、あっという間に立派な竹に……。

だからこそ、頭を出したタケノコを竹になる前にササッ!と斬り倒す。
このハイスピードな攻防が、このゲームの醍醐味です。

さらに、斬った瞬間の盛り上がりも忘れません。
鉈を振るうと「サクッ!」「トンッ!」と気持ちいい効果音と一緒に、
なぜか 「チェストー!」「成敗!」 といったマンガ風のセリフが飛び出します。

中には「チンジャオロース!」「アク抜き必須!」なんて、食欲をそそる(?)
セリフも。
奥の方をのんびり歩いているイノシシも、時々「フゴ フゴ」と応援(?)
してくれています。

竹林をカオスなオノマトペで埋め尽くしながら、タケノコを斬りまくる。
そんなパワフルな体験を、ぜひ楽しんでください!(^o^)

■ AIとのやりとりで面白かった部分

● ゲーム仕様の相談からスタート

みー:(^v^)今日もゲームを作ります。
HTML形式で動く「タケノコ切り倒しゲーム」。

地面のあちこちから頭を出し、
急速に成長するタケノコを、
竹になる前に切り倒すゲームです。

テンキーの1〜9がタケノコの生える位置と対応していて、
キーを押すと「サクッ」「トンッ」と気持ちよく切れます。

タケノコは予兆として地面が盛り上がり、
その後すぐにニョキッと出てきます。

成長がとても速いので、のんびりしていると竹に。
ひとつでも間に合わなければ、
9か所すべてが竹林になってゲームオーバーです。

AI: いらっしゃい。今日も寄ってくれて嬉しいよ。
テンキーを畑に見立てるなんて、なかなか粋なアイデアじゃないか。

そういえばタケノコって、なんであんなに成長が早いか知ってるかい?

1956年、京都の農学博士がマダケを観察したら、
1日で119センチも伸びた記録があるんだ。
節ごとに成長点があるから、
一斉に伸びてあの爆発的スピードになるのさ。

だから今回の「うっかりすると竹林になる」ゲームシステムは、
実はとても理にかなってるんだ。

みー:(=_=`) タケノコが緑色になってるのは違う感じ。
タケノコの表現はクオリティ高めたいです。

AI: おっと、そりゃいけない。主役が適当じゃ盛り上がらないよな。
皮が重なった茶色いタケノコにして、
穂先だけ少し緑を残して瑞々しさも出してみたよ。

みー:(^v^) 背景に山の雰囲気を出したい。
奥でイノシシが歩いてるとか。
遠景は竹林で、地面は枯れ葉が落ちてる感じ。

AI: 背景演出とは、また粋なところに目をつけたな。

イノシシを歩かせるアイデアも最高だ。
タケノコ農家にとっては最大のライバルだからね。

知ってるかい?イノシシの嗅覚は犬並みに鋭くて、
タケノコが地中にいる段階から匂いで掘り当てるんだ。

農家の人は、イノシシに食べられる前に掘り出そうと、
毎朝夜明け前から山に入るんだよ。

みー:(* ̄~ ̄)タケノコを切る時、
マンガのオノマトペみたいな文字を画面にかぶせて表示できる?

AI: なるほど、ちょっとした“お邪魔要素”になる演出だな。
勢いのある文字をランダムで大きく表示してみたよ。

「サクッ」「トンッ」だけじゃなく、
「チェストー」「ヨイショッ」なんて掛け声も飛び出す。
連続で切ると文字が重なって、画面がさらに賑やかになるぜ。

■ 実際の動作

最初にAIが作ってくれたゲーム画面がこちらです。

(画像:AIが最初に生成したシンプルなゲーム画面)

竹林の背景に、イノシシがうろうろしています。
ここから調整を重ねて、
背景やイノシシの演出を加えた完成版がこちら。
竹林の奥でイノシシがうろうろしていて、世界観がぐっと深まりました。

(画像:完成版ゲーム画面)


タケノコが出てきた場所に対応するテンキーを押すと、
「サクッ」「トンッ」と気持ちよく切り倒せます。

(画像:切った瞬間に表示される掛け声)

タイミングが遅れると、タケノコが一気に竹へ成長してしまいます。
油断すると、こんなふうに竹がびっしり……。

(画像:ゲームオーバー画面)


■ コードのポイント

今回の記事では、AI と相談しながら作った
「タケノコ切り倒しゲーム」 の HTML コードを公開します。

このコードをそのまま保存するだけで、
あなたのPCでもすぐに遊べます。

ブラウザだけで動く、とてもシンプルなゲームなので、
気軽に試してみてくださいね。

▼ 遊ぶまでの手順

①PC の「メモ帳」(※Windowsの場合)や  
 Mac の「テキストエディット」など、テキストエディタを開きます。

②下のコードをコピーして貼り付けます。

③「takenoko.html」など、好きな名前で保存します。
 (※拡張子は .html にしてください)

④保存したファイルをブラウザで開けば、すぐに遊べます。

 

■「究極・タケノコ切り倒しゲーム」操作方法♪

【基本の操作】
このゲームは、パソコンの テンキー(数字キー) を使います。

画面上の9つのマスが、
テンキーの配置ととーっても分かりやすく対応しているんですよ♪

こんな感じです!(^o^*)

(7) (8) (9)
(4) (5) (6)
(1) (2) (3)
【ゲームの流れ】
① 地面がこんもり盛り上がる「予兆」を見つける!(>Д<*)

画面のどこかがモコッと盛り上がったら、タケノコが生えてくる合図!
「そこだッ!(・Д・)」と、対応する数字を瞬時にチェックしましょう♪

② タケノコが頭を出したら……鉈でサクッ!(´艸`)

予兆のあと、タケノコがニョキッと頭を出します。
その瞬間にテンキーをプッシュ!(^o^)

「サクッ!」「トンッ!」と、気持ちよく斬り倒せます♪

【ここに注意!(・Д・)】
③ 成長はとってもスピーディー!(・_・;)

タケノコは、頭を出してから竹になるまでが本当に速い!
ぼんやりしていると、あっという間に立派な竹に……(=v=*)

④ 1つでも竹になったら……全滅!(・Д・lll*)

もし1つでも間に合わず竹になってしまうと……大変です!(>Д<
そのマスだけでなく、9マス全部が一気に竹林化*してしまいます。

そうなったら残念ながらゲームオーバー。(・_・;)

【プレイのコツ♪】

斬ったときに出てくる 「チェストー!」「チンジャオロース!」 などの文字は、
実はちょっとした お邪魔要素。(´艸`)

文字が重なると予兆が見えにくくなるので、

焦らず、でも素早くテンキーを押して高得点を狙いましょう!(^-^)

さあ、テンキーを準備してレッツ・タケノコ狩り!(^o^)

ザクザク斬りまくって、最高のタケノコハンターを目指しましょう♪(^^)

“コードは長いので折りたたんでいます。必要な方は開いてご覧ください。”

▼ここからコード▼(クリックで開く)
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>究極・タケノコ切り倒しゲーム</title>
<style>
body {
background-color: #1a2517;
color: #ecf0f1;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
overflow: hidden;
position: relative;
}
/* --- 背景の情景(竹林と地面) --- */
#scenery {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
z-index: -10;
overflow: hidden;
}
.sky {
position: absolute;
top: 0; width: 100%; height: 60%;
background: linear-gradient(to bottom, #455a64, #90a4ae, #cfd8dc);
}
.bamboo-back {
position: absolute;
top: 0; width: 100%; height: 60%;
background: repeating-linear-gradient(
90deg,
transparent 0px, transparent 40px,
rgba(46, 76, 49, 0.5) 40px, rgba(46, 76, 49, 0.5) 55px
);
opacity: 0.6;
}
.bamboo-front {
position: absolute;
top: -10%; width: 100%; height: 70%;
background: repeating-linear-gradient(
90deg,
transparent 0px, transparent 80px,
rgba(39, 92, 44, 0.8) 80px, rgba(39, 92, 44, 0.8) 110px
);
}
.forest-ground {
position: absolute;
top: 60%; width: 100%; height: 40%;
background-color: #3e2723;
background-image:
radial-gradient(#5d4037 20%, transparent 20%),
radial-gradient(#4e342e 20%, transparent 20%);
background-size: 30px 15px;
background-position: 0 0, 15px 7px;
box-shadow: inset 0 20px 30px rgba(0,0,0,0.5);
}
/* --- うろうろするイノシシ --- */
#boar-container {
position: absolute;
top: 52%;
left: 0;
width: 100%;
z-index: -5;
pointer-events: none;
}
.boar-wrapper {
position: absolute;
width: 90px;
height: 60px;
animation: walkAcross 25s linear infinite;
}
.boar-svg {
width: 100%;
height: 100%;
animation: bobbing 0.3s alternate infinite;
}
.boar-balloon {
position: absolute;
top: -35px;
left: 50%;
transform: translateX(-50%);
background-color: #f1f8e9;
color: #4e342e;
padding: 5px 12px;
border-radius: 18px;
font-size: 15px;
font-weight: bold;
white-space: nowrap;
opacity: 0;
transition: opacity 0.3s;
z-index: 1;
box-shadow: 2px 2px 5px rgba(0,0,0,0.3);
animation: bobbingBalloon 0.3s alternate infinite;
}
.boar-balloon::after {
content: '';
position: absolute;
bottom: -8px;
left: 50%;
transform: translateX(-50%);
border-width: 8px 8px 0;
border-style: solid;
border-color: #f1f8e9 transparent transparent;
}
@keyframes walkAcross {
0% { left: -100px; transform: scaleX(1); }
45% { left: 100%; transform: scaleX(1); }
50% { left: 100%; transform: scaleX(-1); }
95% { left: -100px; transform: scaleX(-1); }
100% { left: -100px; transform: scaleX(1); }
}
@keyframes bobbing {
0% { transform: translateY(0px) rotate(-1deg); }
100% { transform: translateY(-4px) rotate(2deg); }
}
@keyframes bobbingBalloon {
0% { transform: translateX(-50%) translateY(0px); }
100% { transform: translateX(-50%) translateY(-4px); }
}
/* --- UIとゲーム盤 --- */
.header-area {
text-align: center;
z-index: 10;
margin-bottom: 20px;
background: rgba(0, 0, 0, 0.5);
padding: 10px 30px;
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0,0,0,0.5);
}
h1 { margin: 0 0 5px 0; font-size: 28px; text-shadow: 2px 2px 0 #000; color: #fff; }
p.sub-title { margin: 0; font-size: 14px; color: #bdc3c7; }
#score-display { font-size: 22px; margin-top: 10px; font-weight: bold; color: #f1c40f; text-shadow: 1px 1px 0 #000; }
#game-container {
position: relative;
background-color: #6d4c41;
padding: 20px;
border-radius: 15px;
box-shadow: 0 15px 40px rgba(0,0,0,0.8);
border: 4px solid #4e342e;
z-index: 10;
}
.grid {
display: grid;
grid-template-columns: repeat(3, 110px);
grid-template-rows: repeat(3, 130px);
gap: 15px;
}
.cell {
background-color: #5d4037;
border-radius: 12px;
position: relative;
overflow: hidden;
border: 2px solid #3e2723;
display: flex;
align-items: flex-end;
justify-content: center;
box-shadow: inset 0 5px 10px rgba(0,0,0,0.3);
}
.key-hint {
position: absolute;
top: 5px;
left: 8px;
color: rgba(255,255,255,0.25);
font-weight: bold;
font-size: 20px;
z-index: 10;
pointer-events: none;
}
.ground {
position: absolute;
bottom: 0;
width: 100%;
height: 30%;
background-color: #4e342e;
background-image: linear-gradient(45deg, #3e2723 25%, transparent 25%), linear-gradient(-45deg, #3e2723 25%, transparent 25%);
background-size: 10px 10px;
z-index: 5;
border-top: 2px solid #2e1a15;
}
.plant {
position: absolute;
bottom: 25%;
width: 100%;
height: 0;
display: flex;
justify-content: center;
align-items: flex-end;
z-index: 4;
}
.warning .mound {
width: 70px;
height: 30px;
background-color: #6d4c41;
border-radius: 50% 50% 0 0;
animation: swell 0.4s infinite alternate ease-in-out;
z-index: 6;
position: absolute;
bottom: 0;
border-top: 2px solid #8d6e63;
}
@keyframes swell {
0% { transform: scaleY(0.6) translateY(5px); }
100% { transform: scaleY(1.3) translateY(0); }
}
.growing .takenoko-wrapper {
width: 60px;
height: 90px;
animation: popUp 0.1s ease-out forwards, shake 0.15s infinite alternate;
transform-origin: bottom;
}
.takenoko-svg { width: 100%; height: 100%; }
.takenoko-skin { fill: #4B220D; }
.takenoko-skin-light { fill: #6D4C41; }
.takenoko-tip { fill: #9ACD32; }
.takenoko-joint { stroke: #3e1e0a; stroke-width: 1.5; fill: none; }
@keyframes popUp {
0% { transform: scaleY(0.1); }
100% { transform: scaleY(1); }
}
@keyframes shake {
0% { transform: rotate(-1deg); }
100% { transform: rotate(1deg); }
}
.bamboo .stalk {
width: 35px;
height: 200px;
background: repeating-linear-gradient(to bottom, #4caf50, #4caf50 25px, #2e7d32 25px, #2e7d32 29px);
box-shadow: inset -8px 0 15px rgba(0,0,0,0.5);
animation: shootUp 0.15s ease-out forwards;
transform-origin: bottom;
border-radius: 4px;
}
@keyframes shootUp {
0% { transform: scaleY(0.2); }
100% { transform: scaleY(1); }
}
.chopped .stump {
width: 35px;
height: 25px;
background-color: #D2B48C;
clip-path: polygon(0% 50%, 100% 0%, 100% 100%, 0% 100%);
border-bottom: 4px solid #5c2c16;
border-radius: 0 0 5px 5px;
}
.slash {
position: absolute;
width: 140%;
height: 6px;
background-color: white;
box-shadow: 0 0 15px white, 0 0 5px #ffeb3b;
transform: rotate(-35deg);
z-index: 20;
opacity: 0;
}
.chopped .slash { animation: flashSlash 0.2s ease-out forwards; }
@keyframes flashSlash {
0% { opacity: 1; transform: rotate(-35deg) scaleX(0); }
50% { opacity: 1; transform: rotate(-35deg) scaleX(1); }
100% { opacity: 0; transform: rotate(-35deg) scaleX(1) translateY(30px); }
}
/* --- マンガ風オノマトペ演出 --- */
.onomatopoeia-wrapper {
position: absolute;
z-index: 50;
pointer-events: none;
transform-origin: center center;
}
.onomatopoeia {
font-family: 'Comic Sans MS', 'Arial Black', sans-serif, 'Meiryo', 'Yu Gothic';
font-weight: bold;
white-space: nowrap;
text-shadow:
3px 3px 0 #000,
-3px -3px 0 #000,
3px -3px 0 #000,
-3px 3px 0 #000,
5px 5px 15px rgba(0,0,0,0.6);
animation: comicPopWrap 0.8s ease-out forwards;
opacity: 0;
}
/* 色バリエーション */
.onomatopoeia.color1 { background: linear-gradient(to right, #ffeb3b, #f44336); -webkit-background-clip: text; color: transparent; -webkit-text-stroke: 1px #880e4f; }
.onomatopoeia.color2 { background: linear-gradient(to right, #8bc34a, #2196f3); -webkit-background-clip: text; color: transparent; -webkit-text-stroke: 1px #0d47a1; }
.onomatopoeia.color3 { background: linear-gradient(to right, #ff5722, #ffeb3b); -webkit-background-clip: text; color: transparent; -webkit-text-stroke: 1px #e65100; }
.onomatopoeia.color4 { background: linear-gradient(to right, #00bcd4, #9c27b0); -webkit-background-clip: text; color: transparent; -webkit-text-stroke: 1px #4a148c; }
.onomatopoeia.color5 { color: #ffffff; -webkit-text-stroke: 2px #3e1e0a; text-shadow: 4px 4px 0 #000; }
@keyframes comicPopWrap {
0% { opacity: 0; transform: scale(0.3); }
15% { opacity: 1; transform: scale(1.5); }
35% { transform: scale(1); }
100% { opacity: 0; transform: scale(1.1) translateY(-40px); }
}
#overlay {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.85);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
border-radius: 12px;
}
.hidden { display: none !important; }
button {
padding: 18px 36px;
font-size: 20px;
font-weight: bold;
color: white;
background-color: #c0392b;
border: 2px solid #e74c3c;
border-radius: 8px;
cursor: pointer;
transition: all 0.1s;
box-shadow: 0 5px 0 #922b21;
}
button:hover { background-color: #e74c3c; transform: translateY(-2px); box-shadow: 0 7px 0 #922b21; }
button:active { transform: translateY(3px); box-shadow: 0 2px 0 #922b21; }
#game-over-text {
font-size: 36px;
color: #e74c3c;
font-weight: bold;
margin-bottom: 25px;
text-shadow: 3px 3px 0px #000;
text-align: center;
line-height: 1.2;
}
</style>
</head>
<body>
<div id="scenery">
<div class="sky"></div>
<div class="bamboo-back"></div>
<div class="bamboo-front"></div>
<div class="forest-ground"></div>
</div>
<div id="boar-container">
<div class="boar-wrapper">
<div class="boar-balloon">フゴ フゴ</div>
<svg class="boar-svg" viewBox="0 0 100 60" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="50" cy="30" rx="35" ry="20" fill="#3e2723" />
<path d="M20 30 L5 40 L15 20 Z" fill="#2e1a15" />
<ellipse cx="22" cy="28" rx="15" ry="12" fill="#3e2723" />
<circle cx="5" cy="38" r="3" fill="#1a0f0c" />
<path d="M12 40 Q8 32 10 28" stroke="#ecf0f1" stroke-width="2.5" fill="none" stroke-linecap="round"/>
<circle cx="18" cy="25" r="1.5" fill="white" />
<rect x="30" y="45" width="5" height="12" fill="#1a0f0c" rx="2" />
<rect x="45" y="48" width="5" height="10" fill="#1a0f0c" rx="2" />
<rect x="65" y="45" width="5" height="12" fill="#1a0f0c" rx="2" />
<rect x="75" y="48" width="5" height="10" fill="#1a0f0c" rx="2" />
<path d="M82 25 Q90 28 92 35" stroke="#3e2723" stroke-width="3" fill="none" stroke-linecap="round"/>
</svg>
</div>
</div>
<div class="header-area">
<h1>究極・タケノコ切り倒しゲーム</h1>
<p class="sub-title">テンキーで、竹になる前にサクッと斬れ!</p>
<div id="score-display">斬った数: 0</div>
</div>
<div id="game-container">
<div class="grid" id="grid">
<div class="cell" id="cell-0"><div class="key-hint">7</div><div class="plant"></div><div class="ground"></div><div class="slash"></div></div>
<div class="cell" id="cell-1"><div class="key-hint">8</div><div class="plant"></div><div class="ground"></div><div class="slash"></div></div>
<div class="cell" id="cell-2"><div class="key-hint">9</div><div class="plant"></div><div class="ground"></div><div class="slash"></div></div>
<div class="cell" id="cell-3"><div class="key-hint">4</div><div class="plant"></div><div class="ground"></div><div class="slash"></div></div>
<div class="cell" id="cell-4"><div class="key-hint">5</div><div class="plant"></div><div class="ground"></div><div class="slash"></div></div>
<div class="cell" id="cell-5"><div class="key-hint">6</div><div class="plant"></div><div class="ground"></div><div class="slash"></div></div>
<div class="cell" id="cell-6"><div class="key-hint">1</div><div class="plant"></div><div class="ground"></div><div class="slash"></div></div>
<div class="cell" id="cell-7"><div class="key-hint">2</div><div class="plant"></div><div class="ground"></div><div class="slash"></div></div>
<div class="cell" id="cell-8"><div class="key-hint">3</div><div class="plant"></div><div class="ground"></div><div class="slash"></div></div>
</div>
<div id="overlay">
<div id="game-over-text" class="hidden">GAME OVER<br><span style="font-size:22px; color:#fff;">一瞬で竹林になってしまった...</span></div>
<button id="start-btn">ゲームスタート</button>
</div>
</div>
<script>
const keyMap = { '7': 0, '8': 1, '9': 2, '4': 3, '5': 4, '6': 5, '1': 6, '2': 7, '3': 8 };
let score = 0;
let isPlaying = false;
let spawnTimer = null;
let currentSpawnRate = 1300;
let currentGrowSpeed = 800;
let boarTalkTimer = null;
// --- マンガ風オノマトペ・掛け声の語彙を大幅に拡張 ---
const comicTexts = [
"サクッ!", "トンッ!", "ザシュッ!", "ドーン!", "スパーン!", "ズバァン!", "シャキーン!",
"チェストォ!", "ヨイショッ!", "デキタッ!", "バッチリ!", "成敗!", "無念…", "そこだっ!",
"まだまだぁ!", "南無ッ!", "キエーッ!", "たけのこ派!", "きのこ派!?", "アク抜き必須!",
"旬の味!", "土佐煮!", "チンジャオロース!", "秘技!", "会心の一撃!", "残像だ", "フゴッ!?",
"いよぉ〜っ", "ポンッ", "スポーン!", "メキメキッ"
];
const gameContainer = document.getElementById('game-container');
function showOnomatopoeia() {
if (!isPlaying) return;
const text = comicTexts[Math.floor(Math.random() * comicTexts.length)];
const wrapper = document.createElement('div');
wrapper.className = 'onomatopoeia-wrapper';
const el = document.createElement('div');
el.className = `onomatopoeia color${Math.floor(Math.random() * 5) + 1}`;
el.innerText = text;
// フォントサイズもランダムにしてお邪魔度アップ (50px ~ 110px)
const randomFontSize = Math.floor(Math.random() * 60) + 50;
el.style.fontSize = `${randomFontSize}px`;
const randomX = Math.random() * 120 - 60;
const randomY = Math.random() * 120 - 60;
const randomRotate = Math.random() * 40 - 20;
wrapper.style.left = `calc(50% + ${randomX}px)`;
wrapper.style.top = `calc(50% + ${randomY}px)`;
wrapper.style.transform = `translate(-50%, -50%) rotate(${randomRotate}deg)`;
wrapper.appendChild(el);
gameContainer.appendChild(wrapper);
el.addEventListener('animationend', () => wrapper.remove());
}
const cells = Array.from({length: 9}, (_, i) => ({
el: document.getElementById(`cell-${i}`),
plantEl: document.querySelector(`#cell-${i} .plant`),
state: 'idle',
timeoutId: null
}));
function createTakenokoSVG() {
return `
<div class="takenoko-wrapper">
<svg class="takenoko-svg" viewBox="0 0 60 100" xmlns="http://www.w3.org/2000/svg">
<path class="takenoko-skin" d="M30 0 C15 20, 5 50, 10 90 L50 90 C55 50, 45 20, 30 0 Z" />
<path class="takenoko-tip" d="M30 0 C22 10, 18 20, 20 30 C25 25, 35 25, 40 30 C42 20, 38 10, 30 0 Z" />
<path class="takenoko-skin-light" d="M15 40 C10 50, 8 70, 12 90 L25 90 C22 70, 20 50, 18 40 Z" />
<path class="takenoko-skin-light" d="M45 45 C50 55, 52 75, 48 90 L35 90 C38 75, 40 55, 42 45 Z" />
<path class="takenoko-skin" d="M22 60 C18 70, 16 80, 18 90 L30 90 C28 80, 26 70, 24 60 Z" />
<path class="takenoko-skin" d="M38 65 C42 75, 44 85, 42 90 L30 90 C32 85, 34 75, 36 65 Z" />
<path class="takenoko-joint" d="M12 80 C20 78, 40 78, 48 80" />
<path class="takenoko-joint" d="M15 70 C22 68, 38 68, 45 70" />
<path class="takenoko-joint" d="M18 60 C25 58, 35 58, 42 60" />
<path class="takenoko-joint" d="M21 50 C26 49, 34 49, 39 50" />
</svg>
</div>
`;
}
let audioCtx;
function initAudio() {
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
if (audioCtx.state === 'suspended') audioCtx.resume();
}
function playChopSound() {
if (!audioCtx) return;
const t = audioCtx.currentTime;
const bufferSize = audioCtx.sampleRate * 0.08;
const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1;
const noise = audioCtx.createBufferSource();
noise.buffer = buffer;
const noiseFilter = audioCtx.createBiquadFilter();
noiseFilter.type = 'highpass';
noiseFilter.frequency.value = 1500;
const noiseEnv = audioCtx.createGain();
noiseEnv.gain.setValueAtTime(0.7, t);
noiseEnv.gain.exponentialRampToValueAtTime(0.01, t + 0.08);
noise.connect(noiseFilter); noiseFilter.connect(noiseEnv); noiseEnv.connect(audioCtx.destination);
noise.start(t);
const osc = audioCtx.createOscillator();
const oscEnv = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(180, t);
osc.frequency.exponentialRampToValueAtTime(50, t + 0.12);
oscEnv.gain.setValueAtTime(0.9, t);
oscEnv.gain.exponentialRampToValueAtTime(0.01, t + 0.12);
osc.connect(oscEnv); oscEnv.connect(audioCtx.destination);
osc.start(t); osc.stop(t + 0.12);
}
function startGame() {
initAudio();
document.getElementById('overlay').classList.add('hidden');
document.getElementById('game-over-text').classList.add('hidden');
score = 0;
currentSpawnRate = 1300;
currentGrowSpeed = 800;
updateScoreDisplay();
isPlaying = true;
cells.forEach(cell => {
clearTimeout(cell.timeoutId);
setCellState(cell, 'idle');
});
startBoarTalking();
scheduleNextSpawn();
}
function gameOver() {
isPlaying = false;
clearTimeout(spawnTimer);
stopBoarTalking();
cells.forEach(cell => {
clearTimeout(cell.timeoutId);
setCellState(cell, 'bamboo');
});
setTimeout(() => {
document.getElementById('overlay').classList.remove('hidden');
document.getElementById('game-over-text').classList.remove('hidden');
document.getElementById('start-btn').innerText = "もう一度挑む";
}, 600);
}
function setCellState(cell, state) {
cell.state = state;
cell.el.className = 'cell';
if (state !== 'idle') cell.el.classList.add(state);
if (state === 'idle') cell.plantEl.innerHTML = '';
else if (state === 'warning') cell.plantEl.innerHTML = '<div class="mound"></div>';
else if (state === 'growing') cell.plantEl.innerHTML = createTakenokoSVG();
else if (state === 'bamboo') cell.plantEl.innerHTML = '<div class="stalk"></div>';
else if (state === 'chopped') cell.plantEl.innerHTML = '<div class="stump"></div>';
}
function scheduleNextSpawn() {
if (!isPlaying) return;
spawnTimer = setTimeout(() => {
spawnTakenoko();
scheduleNextSpawn();
}, currentSpawnRate);
}
function spawnTakenoko() {
const idleCells = cells.filter(c => c.state === 'idle');
if (idleCells.length === 0) return;
const randomCell = idleCells[Math.floor(Math.random() * idleCells.length)];
setCellState(randomCell, 'warning');
randomCell.timeoutId = setTimeout(() => {
if (!isPlaying || randomCell.state !== 'warning') return;
setCellState(randomCell, 'growing');
randomCell.timeoutId = setTimeout(() => {
if (!isPlaying || randomCell.state !== 'growing') return;
gameOver();
}, currentGrowSpeed);
}, 700);
}
function chop(index) {
if (!isPlaying) return;
const cell = cells[index];
if (cell.state === 'growing') {
clearTimeout(cell.timeoutId);
playChopSound();
setCellState(cell, 'chopped');
score++;
updateScoreDisplay();
adjustDifficulty();
showOnomatopoeia();
cell.timeoutId = setTimeout(() => {
if(isPlaying) setCellState(cell, 'idle');
}, 300);
}
}
function updateScoreDisplay() {
document.getElementById('score-display').innerText = `斬った数: ${score}`;
}
function adjustDifficulty() {
if (score > 0 && score % 5 === 0) {
currentSpawnRate = Math.max(350, currentSpawnRate * 0.88);
currentGrowSpeed = Math.max(250, currentGrowSpeed * 0.92);
}
}
const boarBalloonEl = document.querySelector('.boar-balloon');
function startBoarTalking() {
if (boarTalkTimer) return;
const talkLoop = () => {
if (!isPlaying) return;
const nextTalkDelay = Math.random() * 5000 + 3000;
boarTalkTimer = setTimeout(() => {
if (!isPlaying) return;
boarBalloonEl.style.opacity = '1';
const showDuration = Math.random() * 1000 + 1500;
setTimeout(() => {
if (!isPlaying) return;
boarBalloonEl.style.opacity = '0';
talkLoop();
}, showDuration);
}, nextTalkDelay);
};
talkLoop();
}
function stopBoarTalking() {
clearTimeout(boarTalkTimer);
boarTalkTimer = null;
boarBalloonEl.style.opacity = '0';
}
document.getElementById('start-btn').addEventListener('click', startGame);
document.addEventListener('keydown', (e) => {
if (keyMap.hasOwnProperty(e.key)) {
chop(keyMap[e.key]);
}
});
</script>
</body>
</html>


■ 今日の学び

実はこの「究極・タケノコ切り倒しゲーム」、
私のとーってもリアルな体験……いえ、「悩み」から生まれたんです。

私のお墓は家から離れた山の中にあって、
毎年春頃に掃除に行くのですが(- -) そのすぐ近くが竹林でして……。

食べる分のタケノコはもちろん美味しくいただくんですけど、
問題は “見逃してしまったタケノコ” です。

家から離れているので頻繁には行けず、ほんの数日放置しただけで、
次に来たときにはもう見上げるほどの高さと太さに成長してしまうんです。

鉈(なた)では歯が立たず、切り倒すのは本当に一苦労……。(・_・;)

だからこそ、 「タケノコのうちに退治しなくてはいけない」
これが、このゲームの核心になりました。

今回の最大の学びは、
「放置すると大変なことになるッ!(・Д・;)」 という、
あの焦りと恐怖こそがゲームの面白さに繋がるということ。

その体験をそのままゲームに落とし込むために、
タケノコの成長スピードをとても速く設定しました。

「手がつけられなくなる前に斬る」というリアルな焦りが、
ゲームの緊張感になっています。(=v=*)

日常の中の困りごとや焦りも、視点を変えれば面白いゲームのアイデアになるんだな、と改めて勉強になりました。

(画像:タケノコ)

■ 次回は、
福袋シミュレーターゲームです。

夢の詰まったお宝福袋。

一つ50,000円と少しお高めな設定。

中身のお宝の合計は、果たしていくらに…

そんな検証のゲームです。

次回もぜひ遊びに来てくださいね。(*^v^)ノシ

▶ 次の実験はこちら  
  究極のあたりを求めて)「福袋シミュレーター」



 ✨今日も最後まで読んでくださって、ありがとうございます。
みーの実験室は、みなさんの応援で元気に動いています。(*^v^*)

~このブログについて~

AIと一緒に遊び、
100の実験でゲームを育てるブログ

🌈初めての方へ
🌱このブログについて
🌳実験一覧
🍃プライバシーポリシー


AIと作る100のゲーム実験室(*^v^*)をもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む