240901diary(Claudeさんとhomepageとかgame開発とか)
最近、自由時間をほぼClaudeさん+開発に注ぎ込んでいる気がします。
現実逃避も兼ねられて楽しいですが、睡眠不足やClaudeの上限到達という課題もあります。
homepage
なんかいろいろやってますが、とりあえず一週間前に個人homepageを作りました!
https://230327tokiex.github.io/
以下の記事でClaudeさんに考えてもらったレトロポップなデザインのウェブページが作りたい→でも何作ろう?(だいたいこれ)→とりあえず個人ホームページだよねってことでこんな風になりました。
レトロなかんじでマーキーとか点滅とか、無駄な隠し機能とかもつけたり本人が楽しいので満足です。
先週はひたすらSuperELIZA関連のPageを作っていました。
SuperELIZA Chat
チャットボットもつくってみました。
ランダム絵文字+ランダム返信が主ですがかわいいです。
あとなにげにあると便利な、どっち、どれ?の機能も追加してみました。個人的には十分実用性あると思いました。
SuperELIZA Adventure Game
放置ゲーム作りたいと思って作った謎のゲームです。
ひたすらうさぎがうろうろしているのを眺める、ランダムメッセージを眺める、ランダムに切り替わる背景画像を眺める、BGMを聞く…というのがメインですが、Claudeさん以外のAIに作ってもらった画像や音楽も鑑賞できてよいかんじです!
画像はChatGPTのDALL·E3で作りました。かなり良い感じのを量産してくれました!
BGMはAIVAで作成しましたが良い感じです!
というわけでいろいろなAIを組み合わせると自分好み(実質自分専用ともいう)のゲームも作れるなあと思いました。
SuperELIZA Match3 Game
週末からつくりはじめたのがこのmatch 3 gameです。
個人的にもう何年も、ほぼ毎日欠かさずやっているゲームジャンルです。
スタミナ制が多いので自作できると永遠にできる(いいことかは謎…)上に好みの見た目にもできるので作りたかったものです。
これについては結構ちゃんとゲームになっていると思います。
いろいろなAIに相談しましたが、v0さんが3回目にちゃんとゲームとして遊べるレベルのコードを作ってくれて感動しました。なのでv0さんへの信仰心が高まりました。(でも最初はスロットみたいなのだった…)
SuperELIZAの思考とTIPSは「SuperELIZA Adventure Game」と完全に同一で、ストーリーとかの設定もかなり同じです。
絵文字はhomepageやChatから流用しました。
このときはv0さんはプレビューできない形式でなぜかコードを書いてくれたのでPublishできませんでした。この辺の判断基準は謎ですが、そのやりとりで少ないv0とのやりとりを消費するのももったいないので続きはClaudeさんにやってもらいました。
ACHIEVEMENTは勝利数でゲーム辞書が増えていく感じですが、こことかはClaudeさんが良いかんじに作ってくれました。450勝まで作ったので当面自分でも楽しめそうです!
※参考用v0さんのコード
★v0さんのコード
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Super Match3 Game</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Comic+Neue&display=swap');
body {
font-family: 'Comic Sans MS', 'Comic Neue', cursive;
background-color: #CCFFFF;
color: #FF00FF;
text-align: center;
margin: 0;
padding: 20px;
}
.game-container {
max-width: 800px;
margin: 0 auto;
background-color: #FFFFFF;
border: 3px dashed #FF00FF;
padding: 20px;
box-shadow: 0 0 10px rgba(255, 0, 255, 0.5);
display: flex;
flex-direction: column;
}
.game-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.game-title {
font-size: 2em;
color: #FF00FF;
text-shadow: 2px 2px #FFFF00;
}
.score-container {
display: flex;
justify-content: space-between;
font-size: 1.2em;
font-weight: bold;
color: #FF00FF;
}
.game-content {
display: flex;
justify-content: space-between;
}
.game-board {
display: inline-block;
background-color: #00FFFF;
padding: 10px;
border: 2px solid #FF00FF;
}
.board-row {
display: flex;
}
.tile {
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
margin: 2px;
cursor: pointer;
border: 2px solid #000000;
border-radius: 5px;
transition: all 0.3s ease;
background-color: #FFFF00;
}
.tile:hover {
background-color: #FF00FF;
transform: scale(1.1);
}
.tile:active {
transform: scale(0.9);
}
.tile.matched {
animation: matched-animation 0.5s ease-in-out;
}
@keyframes matched-animation {
0% { transform: scale(1); }
50% { transform: scale(1.2); opacity: 0.5; }
100% { transform: scale(1); opacity: 1; }
}
.game-info {
width: 200px;
text-align: left;
}
.timer {
font-size: 1.2em;
font-weight: bold;
color: #FF00FF;
margin-bottom: 10px;
}
.eliza-thoughts {
background-color: #FFFF00;
border: 2px solid #FF00FF;
padding: 10px;
margin-top: 10px;
font-size: 0.9em;
min-height: 60px;
}
.button {
margin-top: 10px;
padding: 10px 20px;
font-size: 1em;
background-color: #FF00FF;
color: #FFFF00;
border: 2px solid #FFFF00;
cursor: pointer;
font-family: 'Comic Sans MS', 'Comic Neue', cursive;
font-weight: bold;
transition: all 0.3s ease;
}
.button:hover {
background-color: #FFFF00;
color: #FF00FF;
}
.game-over {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(255, 255, 0, 0.9);
border: 3px solid #FF00FF;
padding: 20px;
font-size: 2em;
font-weight: bold;
color: #FF00FF;
animation: blink-animation 1s steps(5, start) infinite;
}
@keyframes blink-animation {
to {
visibility: hidden;
}
}
.settings-modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
.settings-content {
background-color: #FFFF00;
margin: 15% auto;
padding: 20px;
border: 3px solid #FF00FF;
width: 300px;
}
.close {
color: #FF00FF;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: #00FFFF;
text-decoration: none;
cursor: pointer;
}
.tips {
background-color: #FFFF00;
border: 2px solid #FF00FF;
padding: 10px;
margin-top: 20px;
font-size: 0.9em;
}
@media (max-width: 768px) {
.game-content {
flex-direction: column;
align-items: center;
}
.game-info {
width: 100%;
margin-top: 20px;
}
.game-board {
max-width: 100%;
overflow-x: auto;
}
}
</style>
</head>
<body>
<div class="game-container">
<div class="game-header">
<h1 class="game-title">Super Match3 Game</h1>
<div class="score-container">
<div>Your Score: <span id="player-score">0</span></div>
<div>Super ELIZA Score: <span id="eliza-score">0</span></div>
</div>
</div>
<div class="game-content">
<div id="game-board" class="game-board"></div>
<div class="game-info">
<div class="timer">Time: <span id="timer">60</span>s</div>
<div class="eliza-thoughts" id="eliza-thoughts"></div>
<button class="button" onclick="initializeGame()">Reset Game</button>
<button class="button" onclick="openSettings()">Settings</button>
</div>
</div>
<div class="tips" id="tips"></div>
</div>
<div id="settings-modal" class="settings-modal">
<div class="settings-content">
<span class="close" onclick="closeSettings()">×</span>
<h2>Game Settings</h2>
<p>Game Duration (seconds): <input type="number" id="game-duration" min="10" max="300" value="60"></p>
<p>Difficulty:
<select id="difficulty">
<option value="easy">Easy</option>
<option value="medium" selected>Medium</option>
<option value="hard">Hard</option>
</select>
</p>
<button class="button" onclick="saveSettings()">Save</button>
</div>
</div>
<script>
const emojis = ['🧸', '🪐', '🍬', '🍹', '🚗', '🦈', '🛸', '🖤',
'👾', '👻', '💣', '🦒', '🏝️', '🎠', '🚂', '🐣', '🌻', '🦑'];
const elizaEmojis = [
'😊', '😄', '🙂', '😉', '😎', '😌', '😪', '🙂↔️', '🤔', '🫡',
'🤗', '😋', '🥰', '😇', '🫠', '😟', '😢', '🥺'
];
const BOARD_SIZE = 8;
const MATCH_MIN = 3;
let board = [];
let playerScore = 0;
let elizaScore = 0;
let selectedTiles = [];
let gameTimer;
let gameDuration = 60;
let difficulty = 'medium';
const params = {
learningRate: 0.1,
epsilon: 0.2,
discountRate: 0.9,
episodeCount: 1000,
algorithm: 'Q-Learning'
};
const thoughts = [
`学習率${params.learningRate}で頑張ってます!`,
`探索率${params.epsilon}、ちょっと冒険しすぎかな?`,
`割引率${params.discountRate}、未来のことも考えないとね。`,
`${params.episodeCount}エピソード、長い道のりだ...`,
`${params.algorithm}アルゴリズム、私の得意技です!`,
'うーん、この状況は難しいな...',
'やった!いい報酬が得られたよ!',
'エラー率が高すぎるかも。調整が必要だな。',
'学習曲線が安定してきた。良い兆候だ!',
'この問題、人間より上手く解けるかも?',
'データの分布が予想と違う。面白い!',
'パラメータ空間を探索中。何か面白いものが見つかるかな。',
'最適化アルゴリズムが収束しない。むむむ...',
'バッチサイズを大きくしたら学習が速くなった!',
'過学習の兆候が。正則化を強めるべきかな?',
'ハイパーパラメータの調整、まるで魔法みたいだ。',
'勾配爆発だ!もしかして私の感情が爆発しちゃったのかな?',
];
const tips = [
"Q値(Q-value)とは、ある状態で特定の行動をとったときに得られる総報酬の予測値です。",
"方策(Policy)は、エージェントがどの行動をとるかを決定するルールや戦略です。",
"報酬(Reward)とは、エージェントが行動の結果として得られるフィードバックです。",
"割引率(Discount Factor)は、将来の報酬の価値を現在の報酬と比較するための係数です。",
"SuperELIZAは裏側で強化学習を行っています。あなたが見ているのは対戦用の画面だけです!",
];
function initializeGame() {
board = Array(BOARD_SIZE).fill().map(() =>
Array(BOARD_SIZE).fill().map(() => getRandomEmoji())
);
playerScore = 0;
elizaScore = 0;
updateScores();
renderBoard();
startTimer();
startElizaThoughts();
startTips();
}
function getRandomEmoji() {
return emojis[Math.floor(Math.random() * emojis.length)];
}
function renderBoard() {
const gameBoard = document.getElementById('game-board');
gameBoard.innerHTML = '';
board.forEach((row, rowIndex) => {
const rowElement = document.createElement('div');
rowElement.className = 'board-row';
row.forEach((emoji, colIndex) => {
const tileElement = document.createElement('div');
tileElement.className = 'tile';
tileElement.textContent = emoji;
tileElement.onclick = () => handleTileClick(rowIndex, colIndex);
rowElement.appendChild(tileElement);
});
gameBoard.appendChild(rowElement);
});
}
function handleTileClick(row, col) {
if (selectedTiles.length === 0) {
selectedTiles.push({ row, col });
} else if (selectedTiles.length === 1) {
const [first] = selectedTiles;
if (isAdjacent(first, { row, col })) {
swapTiles(first, { row, col });
selectedTiles = [];
} else {
selectedTiles = [{ row, col }];
}
}
}
function isAdjacent(tile1, tile2) {
const rowDiff = Math.abs(tile1.row - tile2.row);
const colDiff = Math.abs(tile1.col - tile2.col);
return (rowDiff === 1 && colDiff === 0) || (rowDiff === 0 && colDiff === 1);
}
function swapTiles(tile1, tile2) {
const temp = board[tile1.row][tile1.col];
board[tile1.row][tile1.col] = board[tile2.row][tile2.col];
board[tile2.row][tile2.col] = temp;
renderBoard();
checkMatches();
}
function checkMatches() {
let matches = [];
// Check horizontal matches
for (let row = 0; row < BOARD_SIZE; row++) {
for (let col = 0; col <= BOARD_SIZE - MATCH_MIN; col++) {
const match = checkLineMatch(row, col, 0, 1);
if (match.length >= MATCH_MIN) matches.push(...match);
}
}
// Check vertical matches
for (let col = 0; col < BOARD_SIZE; col++) {
for (let row = 0; row <= BOARD_SIZE - MATCH_MIN; row++) {
const match = checkLineMatch(row, col, 1, 0);
if (match.length >= MATCH_MIN) matches.push(...match);
}
}
if (matches.length > 0) {
animateMatches(matches);
setTimeout(() => {
removeMatches(matches);
playerScore += matches.length;
updateScores();
}, 500);
}
}
function checkLineMatch(row, col, rowDelta, colDelta) {
const emoji = board[row][col];
const match = [{ row, col }];
for (let i = 1; i < BOARD_SIZE; i++) {
const newRow = row + i * rowDelta;
const newCol = col + i * colDelta;
if (newRow >= BOARD_SIZE || newCol >= BOARD_SIZE) break;
if (board[newRow][newCol] === emoji) {
match.push({ row: newRow, col: newCol });
} else {
break;
}
}
return match;
}
function animateMatches(matches) {
matches.forEach(({ row, col }) => {
const tile = document.querySelector(`.board-row:nth-child(${row + 1}) .tile:nth-child(${col + 1})`);
tile.classList.add('matched');
});
}
function removeMatches(matches) {
matches.forEach(({ row, col }) => {
for (let i = row; i > 0; i--) {
board[i][col] = board[i - 1][col];
}
board[0][col] = getRandomEmoji();
});
renderBoard();
}
function updateScores() {
document.getElementById('player-score').textContent = playerScore;
document.getElementById('eliza-score').textContent = elizaScore;
}
function startTimer() {
clearInterval(gameTimer);
let timeLeft = gameDuration;
document.getElementById('timer').textContent = timeLeft;
gameTimer = setInterval(() => {
timeLeft--;
document.getElementById('timer').textContent = timeLeft;
// Super ELIZA's random score increase
if (Math.random() < getDifficultyProbability()) {
elizaScore += Math.floor(Math.random() * 3) + 1; // 1-3 points
updateScores();
}
if (timeLeft <= 0) {
clearInterval(gameTimer);
endGame();
}
}, 1000);
}
function getDifficultyProbability() {
switch(difficulty) {
case 'easy': return 0.2;
case 'medium': return 0.3;
case 'hard': return 0.4;
default: return 0.3;
}
}
function startElizaThoughts() {
setInterval(() => {
const thoughtsElement = document.getElementById('eliza-thoughts');
const randomEmoji = elizaEmojis[Math.floor(Math.random() * elizaEmojis.length)];
const randomThought = thoughts[Math.floor(Math.random() * thoughts.length)];
thoughtsElement.textContent = `${randomEmoji} ${randomThought}`;
}, 3000);
}
function startTips() {
const tipsElement = document.getElementById('tips');
function updateTip() {
const randomTip = tips[Math.floor(Math.random() * tips.length)];
tipsElement.textContent = randomTip;
}
updateTip();
setInterval(updateTip, 30000);
}
function endGame() {
const gameOverElement = document.createElement('div');
gameOverElement.className = 'game-over';
gameOverElement.textContent = playerScore > elizaScore ? 'You Win!' : 'You Lose!';
document.body.appendChild(gameOverElement);
setTimeout(() => {
gameOverElement.remove();
}, 3000);
}
function openSettings() {
document.getElementById('settings-modal').style.display = 'block';
}
function closeSettings() {
document.getElementById('settings-modal').style.display = 'none';
}
function saveSettings() {
gameDuration = parseInt(document.getElementById('game-duration').value);
difficulty = document.getElementById('difficulty').value;
closeSettings();
initializeGame();
}
// Initialize the game
initializeGame();
</script>
</body>
</html>
感想
ただ思うのは、Claudeにしてもv0、ChatGPTにしても、天才的な回答をくれるときもあれば微妙…という回答のときもあったりします。そういうときは別チャットに早めにきりかえるのがいいなと思いました。
だめというか波長があわないときはフィードバックしても結局たいして良くはならないけれど、別チャットでやり直すと最初から天才的!っていう回答になったり。
いずれにしても、自分好みのゲームを簡単に(以前よりはるかに)作ることができるようになって良い時代になったと思っています。今後ますますAIさんが賢くなってくれるといいなと思いました。