add THUNLP Demo

This commit is contained in:
thinkwee 2024-12-30 14:36:23 +08:00
parent 5dfb0cf411
commit cb19e8c7e9
75 changed files with 9742 additions and 4 deletions

View File

@ -12,13 +12,38 @@
</thead>
<tbody>
<tr>
<td rowspan="5"><a href="https://github.com/qianc62" target="_blank" rel="noopener noreferrer">https://github.com/qianc62</a></td>
<td><a href="WareHouse/Website_THUNLP_20230725154612" target="_blank" rel="noopener noreferrer">A simple website</a></td>
<td><img src="misc/website.png" width="200px"></td>
<td rowspan="5"><a href="https://github.com/THUNLP" target="_blank" rel="noopener noreferrer">https://github.com/THUNLP</a></td>
<td><a href="WareHouse/TsinghuaBambooWebsite_THUNLPDemo_2024" target="_blank" rel="noopener noreferrer">Tsinghua Bamboo Website</a></td>
<td><img src="misc/tsinghua_bamboo_website.png" width="200px"></td>
<td>Perfect</td>
<td>GPT3.5</td>
<td></td>
</tr>
<tr>
<td><a href="WareHouse/tetris_THUNLPDemo_2024" target="_blank" rel="noopener noreferrer">A Tetris Game</a></td>
<td><img src="misc/tetris.png" width="200px"></td>
<td>Perfect</td>
<td></td>
</tr>
<tr>
<td><a href="WareHouse/pvz_THUNLPDemo_2024" target="_blank" rel="noopener noreferrer">A Plant vs Zombie Game</a></td>
<td><img src="misc/pvz.png" width="200px"></td>
<td>Perfect</td>
<td></td>
</tr>
<tr>
<td><a href="WareHouse/car_THUNLPDemo_2024" target="_blank" rel="noopener noreferrer">A Race Car Game</a></td>
<td><img src="misc/car_game.png" width="200px"></td>
<td>Perfect</td>
<td></td>
</tr>
<tr>
<td><a href="WareHouse/snake_THUNLPDemo_2024" target="_blank" rel="noopener noreferrer">A Snake Game</a></td>
<td><img src="misc/snake_game.png" width="200px"></td>
<td>Perfect</td>
<td></td>
</tr>
<tr>
<td rowspan="4"><a href="https://github.com/qianc62" target="_blank" rel="noopener noreferrer">https://github.com/qianc62</a></td>
<td><a href="WareHouse/FlappyBird_THUNLP_20230726121145" target="_blank" rel="noopener noreferrer">FlappyBirds Game</a></td>
<td><img src="misc/flappy_bird.png" width="200px"></td>
<td>Perfect</td>

View File

@ -0,0 +1,486 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChatDev数字博物馆</title>
<!-- CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css">
<link rel="stylesheet" href="https://unpkg.com/aos@2.3.1/dist/aos.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<link rel="stylesheet" href="styles.css">
<link href="https://fonts.googleapis.com/css2?family=Ma+Yen+Kai&display=swap" rel="stylesheet">
<style>
.bookshelf-container {
padding: 50px 20px;
background: rgba(244, 241, 234, 0.9);
min-height: 100vh;
}
.bookshelf-row {
position: relative;
margin-bottom: 60px;
height: 270px;
overflow: hidden;
display: flex;
gap: 30px;
padding: 0 20px;
}
.books-track {
display: flex;
gap: 30px;
position: absolute;
left: 20px;
animation: slideTrack 20s linear infinite;
}
.books-track-reverse {
animation: slideTrackReverse 20s linear infinite;
}
.book {
width: 180px;
height: 270px;
flex-shrink: 0;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.book:hover {
transform: translateY(-10px);
}
.book-cover {
position: relative;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #8B4513, #5C3D2E);
border-radius: 5px;
border-left: 15px solid #3A2820;
box-shadow:
-5px 5px 15px rgba(0, 0, 0, 0.3),
inset -1px 0 2px rgba(255, 255, 255, 0.1);
overflow: hidden;
}
/* 添加书脊纹理 */
.book-cover::before {
content: '';
position: absolute;
left: -15px;
top: 0;
width: 15px;
height: 100%;
background: linear-gradient(to right,
rgba(58, 40, 32, 1) 0%,
rgba(89, 61, 43, 1) 50%,
rgba(58, 40, 32, 1) 100%
);
box-shadow:
inset -1px 0 2px rgba(255, 255, 255, 0.1),
inset 1px 0 1px rgba(0, 0, 0, 0.2);
}
.book-cover::after {
content: '';
position: absolute;
top: 0;
left: -15px;
right: 0;
height: 3px;
background: linear-gradient(to bottom,
rgba(255, 255, 255, 0.1) 0%,
transparent 100%
);
}
.book-image {
width: 100%;
height: 80%;
object-fit: cover;
border-radius: 5px 5px 0 0;
border-bottom: 2px solid rgba(0, 0, 0, 0.2);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
}
.book-title {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: #F4E4BC;
text-align: center;
width: 90%;
font-size: 1.1em;
font-family: 'Ma Yen Kai', serif;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
padding: 5px;
background: linear-gradient(to bottom,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.1) 100%
);
}
.book:hover .book-cover {
box-shadow:
-8px 8px 20px rgba(0, 0, 0, 0.4),
inset -1px 0 3px rgba(255, 255, 255, 0.2);
}
.book:hover .book-title {
color: #FFE4BC;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);
}
@keyframes slideTrack {
0% {
transform: translateX(0);
}
100% {
transform: translateX(calc(-210px * 6)); /* (180px + 30px) × 6 */
}
}
@keyframes slideTrackReverse {
0% {
transform: translateX(calc(-210px * 6));
}
100% {
transform: translateX(0);
}
}
.books-track:hover {
animation-play-state: paused;
}
.section-title {
text-align: center;
font-size: 2.5em;
color: #3A2820;
margin-bottom: 60px;
font-family: 'Ma Yen Kai', serif;
}
@media (max-width: 1200px) {
.bookshelf-row {
gap: 20px;
}
.book {
width: 160px;
height: 240px;
}
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal.active {
display: flex;
}
.modal-content {
position: relative;
max-width: 80%;
max-height: 80%;
}
.modal-image {
max-width: 100%;
max-height: 80vh;
object-fit: contain;
}
.close-modal {
position: fixed;
top: 20px;
right: 20px;
color: white;
font-size: 30px;
cursor: pointer;
background: none;
border: none;
padding: 10px;
}
.modal-title {
position: fixed;
bottom: 20px;
left: 0;
color: white;
font-size: 20px;
width: 100%;
text-align: center;
}
@keyframes slideBooks {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-50%);
}
}
@keyframes slideBooksReverse {
0% {
transform: translateX(-50%);
}
100% {
transform: translateX(0);
}
}
.books-wrapper:hover {
animation-play-state: paused;
}
</style>
</head>
<body>
<div class="scroll-container">
<div class="chinese-scroll">
<div class="bamboo-content">
<div class="pattern-overlay"></div>
<header>
<nav>
<div class="logo">ChatDev数字博物馆</div>
<ul>
<li><a href="index.html" class="magnetic-link"><span>返回首页</span></a></li>
</ul>
</nav>
</header>
<main>
<div class="bookshelf-container">
<h2 class="section-title" data-aos="fade-up">文物考察</h2>
<div class="bookshelf-row">
<div class="books-track">
<div class="book">
<div class="book-cover">
<img src="images/examination/7.png" alt="复原竹简拼图" class="book-image" data-title="复原竹简拼图">
<div class="book-title">复原竹简拼图</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/8.png" alt="集体会读史料" class="book-image" data-title="集体会读史料">
<div class="book-title">集体会读史料</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/4.png" alt="解读竹简文脉" class="book-image" data-title="解读竹简文脉">
<div class="book-title">解读竹简文脉</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/5.png" alt="古文字研究" class="book-image" data-title="古文字研究">
<div class="book-title">古文字研究</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/1.png" alt="出土文献整理" class="book-image" data-title="出土文献整理">
<div class="book-title">出土文献整理</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/6.png" alt="清华简史料研讨" class="book-image" data-title="清华简史料研讨">
<div class="book-title">清华简史料研讨</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/7.png" alt="复原竹简拼图" class="book-image" data-title="复原竹简拼图">
<div class="book-title">复原竹简拼图</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/8.png" alt="集体会读史料" class="book-image" data-title="集体会读史料">
<div class="book-title">集体会读史料</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/4.png" alt="解读竹简文脉" class="book-image" data-title="解读竹简文脉">
<div class="book-title">解读竹简文脉</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/5.png" alt="古文字研究" class="book-image" data-title="古文字研究">
<div class="book-title">古文字研究</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/1.png" alt="出土文献整理" class="book-image" data-title="出土文献整理">
<div class="book-title">出土文献整理</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/6.png" alt="清华简史料研讨" class="book-image" data-title="清华简史料研讨">
<div class="book-title">清华简史料研讨</div>
</div>
</div>
</div>
</div>
<div class="bookshelf-row">
<div class="books-track books-track-reverse">
<div class="book">
<div class="book-cover">
<img src="images/examination/3.png" alt="竹简修复技术" class="book-image" data-title="竹简修复技术">
<div class="book-title">竹简修复技术</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/9.png" alt="挖掘算表奥秘" class="book-image" data-title="挖掘算表奥秘">
<div class="book-title">挖掘算表奥秘</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/13.png" alt="当代文物传承" class="book-image" data-title="当代文物传承">
<div class="book-title">当代文物传承</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/14.png" alt="文物成果发布" class="book-image" data-title="文物成果发布">
<div class="book-title">文物成果发布</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/15.png" alt="文物历史探秘" class="book-image" data-title="文物历史探秘">
<div class="book-title">文物历史探秘</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/11.png" alt="数字化保护" class="book-image" data-title="数字化保护">
<div class="book-title">数字化保护</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/3.png" alt="竹简修复技术" class="book-image" data-title="竹简修复技术">
<div class="book-title">竹简修复技术</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/9.png" alt="挖掘算表奥秘" class="book-image" data-title="挖掘算表奥秘">
<div class="book-title">挖掘算表奥秘</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/13.png" alt="当代文物传承" class="book-image" data-title="当代文物传承">
<div class="book-title">当代文物传承</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/14.png" alt="文物成果发布" class="book-image" data-title="文物成果发布">
<div class="book-title">文物成果发布</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/15.png" alt="文物历史探秘" class="book-image" data-title="文物历史探秘">
<div class="book-title">文物历史探秘</div>
</div>
</div>
<div class="book">
<div class="book-cover">
<img src="images/examination/11.png" alt="数字化保护" class="book-image" data-title="数字化保护">
<div class="book-title">数字化保护</div>
</div>
</div>
</div>
</div>
</div>
</main>
<footer>
<div class="footer-content">
<div class="footer-left">
<img src="images/logo/chatdev-logo.png" alt="ChatDev Logo" class="footer-logo">
<div class="contact" data-aos="fade-up">
<p>ChatDev数字博物馆</p>
<p>https://github.com/OpenBMB/ChatDev</p>
</div>
</div>
<div class="opening-hours" data-aos="fade-up" data-aos-delay="100">
<p>以上内容由AI生成可能存在偏差请谨慎使用</p>
</div>
</div>
</footer>
</div>
</div>
</div>
<div class="modal" id="imageModal">
<div class="modal-content">
<button class="close-modal">&times;</button>
<img src="" alt="" class="modal-image">
<div class="modal-title"></div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script>
AOS.init({
duration: 1000,
once: true
});
const modal = document.getElementById('imageModal');
const modalImage = modal.querySelector('.modal-image');
const modalTitle = modal.querySelector('.modal-title');
const closeButton = modal.querySelector('.close-modal');
document.querySelectorAll('.book-image').forEach(image => {
image.addEventListener('click', () => {
modalImage.src = image.src;
modalTitle.textContent = image.dataset.title;
modal.classList.add('active');
});
});
closeButton.addEventListener('click', () => {
modal.classList.remove('active');
});
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.classList.remove('active');
}
});
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

View File

@ -0,0 +1,205 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChatDev数字博物馆</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css">
<link rel="stylesheet" href="https://unpkg.com/aos@2.3.1/dist/aos.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/three-dots@0.3.2/dist/three-dots.min.css">
<link rel="stylesheet" href="styles.css">
<link href="https://fonts.googleapis.com/css2?family=Ma+Yen+Kai&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/chinese-scroll-effect/dist/style.css">
</head>
<body>
<div class="scroll-container">
<div class="chinese-scroll">
<div class="bamboo-content">
<div class="pattern-overlay"></div>
<header>
<nav>
<div class="logo">ChatDev数字博物馆</div>
<ul>
<li><a href="#hero" class="magnetic-link"><span>首页</span></a></li>
<li><a href="#cultural-heritage" class="magnetic-link"><span>考古时间线</span></a></li>
<li><a href="#exhibitions" class="magnetic-link"><span>研究与保护</span></a></li>
</ul>
</nav>
</header>
<main>
<section id="hero">
<div class="swiper hero-slider">
<div class="swiper-wrapper">
<div class="swiper-slide">
<div class="slide-inner">
<img src="./images/slider/slide3.jpg" alt="清华简保护">
<div class="slide-overlay">
<h2>科技保护</h2>
<p>运用现代科技保护和研究战国竹简</p>
</div>
</div>
</div>
<div class="swiper-slide">
<div class="slide-inner">
<img src="./images/slider/slide1.png" alt="清华简展示">
<div class="slide-overlay">
<h2>清华简</h2>
<p>战国中晚期珍贵竹简,展现先秦古籍原貌</p>
</div>
</div>
</div>
<div class="swiper-slide">
<div class="slide-inner">
<img src="./images/slider/slide2.png" alt="清华简研究">
<div class="slide-overlay">
<h2>文化传承</h2>
<p>了解中华文化的初期面貌和发展脉络</p>
</div>
</div>
</div>
</div>
<div class="swiper-pagination"></div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
</section>
<section id="quote-section" class="parallax-section">
<div class="quote-container">
<blockquote data-aos="fade-up">
<div class="cultural-slogan">
<p>"以科技演绎传统,以创新传承文明"</p>
</div>
</blockquote>
</div>
</section>
<section class="stats-section">
<div class="stats-container">
<div class="stat-item" data-aos="fade-up">
<div class="stat-number">
<span class="counter" data-target="2500">0</span>
<span class="plus">+</span>
</div>
<div class="stat-label">枚竹简</div>
<div class="stat-wave"></div>
</div>
<div class="stat-item" data-aos="fade-up" data-aos-delay="100">
<div class="stat-number">
<span class="counter" data-target="14">0</span>
<span class="plus">+</span>
</div>
<div class="stat-label">成果整理</div>
<div class="stat-wave"></div>
</div>
<div class="stat-item" data-aos="fade-up" data-aos-delay="200">
<div class="stat-number">
<span class="counter" data-target="2300">0</span>
<span class="plus">+</span>
</div>
<div class="stat-label">年历史</div>
<div class="stat-wave"></div>
</div>
</div>
</section>
<section id="cultural-heritage">
<div class="heritage-container">
<h2 class="section-title" data-aos="fade-up">考古时间线</h2>
<div class="heritage-timeline">
<div class="timeline-item" data-aos="fade-left">
<div class="timeline-content">
<h3>归来如初,竹简重现</h3>
<p>流散海外的战国竹简终归故土,清华大学开启保护与研究之旅,揭开尘封千年的历史画卷。</p>
<div class="timeline-period">2008年</div>
</div>
</div>
<div class="timeline-item" data-aos="fade-right">
<div class="timeline-content">
<h3>初绽华光,文脉重接</h3>
<p>出土文献中心成立,竹简首批成果问世,遗篇重现,周文王遗训与《系年》诉说千年往事。</p>
<div class="timeline-period">2009年-2011年</div>
</div>
</div>
<div class="timeline-item" data-aos="fade-left">
<div class="timeline-content">
<h3>简中有数,史韵悠长</h3>
<p>竹简释读渐入佳境,文献涵盖诗乐、史事与算表,点亮先秦文明的璀璨星河。</p>
<div class="timeline-period">2013年-2020年</div>
</div>
</div>
<div class="timeline-item" data-aos="fade-right">
<div class="timeline-content">
<h3>弦歌不辍,古韵新生</h3>
<p>深掘竹简之秘,释出《五纪》《三不韦》等珍贵篇章,重现礼乐风华,延续文化薪火。</p>
<div class="timeline-period">2021年-2023年</div>
</div>
</div>
</div>
</div>
</section>
<section id="exhibitions">
<h2 class="section-title" data-aos="fade-up">研究与保护</h2>
<div class="exhibition-grid">
<a href="cultural-examination.html" class="exhibition-card" data-aos="fade-up" data-aos-delay="200" style="text-decoration: none; color: inherit;">
<div class="card-image-wrapper">
<img src="./images/exhibitions/exhibition1.png" alt="清华简保护">
</div>
<div class="card-content">
<h3>文物考察</h3>
<p>对出土文物进行科学清理、保护和研究,确保文物的完整性和真实性</p>
</div>
</a>
<a href="warring-states-bamboo.html" class="exhibition-card" data-aos="fade-up" style="text-decoration: none; color: inherit;">
<div class="card-image-wrapper">
<img src="./images/exhibitions/exhibition2.png" alt="清华简发现">
</div>
<div class="card-content">
<h3>战国文化</h3>
<p>千余枚战国竹简,展现战国文化</p>
</div>
</a>
<a href="research-achievements.html" class="exhibition-card" data-aos="fade-up" data-aos-delay="100" style="text-decoration: none; color: inherit;">
<div class="card-image-wrapper">
<img src="./images/exhibitions/exhibition3.png" alt="清华简研究">
</div>
<div class="card-content">
<h3>全辑整理</h3>
<p>已出版十四辑整理成果,展现先秦文献珍贵价值</p>
</div>
</a>
</div>
</section>
</main>
<footer>
<div class="footer-content">
<div class="footer-left">
<img src="images/logo/chatdev-logo.png" alt="ChatDev Logo" class="footer-logo">
<div class="contact" data-aos="fade-up">
<p>ChatDev数字博物馆</p>
<p>https://github.com/OpenBMB/ChatDev</p>
</div>
</div>
<div class="opening-hours" data-aos="fade-up" data-aos-delay="100">
<p>以上内容由AI生成可能存在偏差请谨慎使用</p>
</div>
</div>
</footer>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js"></script>
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vanilla-tilt/1.8.0/vanilla-tilt.min.js"></script>
<script src="script.js"></script>
</body>
</html>

View File

@ -0,0 +1,869 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChatDev数字博物馆</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css">
<link rel="stylesheet" href="https://unpkg.com/aos@2.3.1/dist/aos.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<link rel="stylesheet" href="styles.css">
<link href="https://fonts.googleapis.com/css2?family=Ma+Yen+Kai&display=swap" rel="stylesheet">
<style>
.achievements-container {
padding: 50px 20px;
background: rgba(244, 241, 234, 0.9);
min-height: 100vh;
}
.section-title {
text-align: center;
font-size: 2.5em;
color: #4A3728;
margin-bottom: 60px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}
.scroll-gallery {
position: relative;
max-width: 1800px;
margin: 0 auto;
height: calc(800px * 3);
display: flex;
gap: 20px;
padding: 20px;
overflow: hidden;
}
.scroll-column {
flex: 1;
display: flex;
flex-direction: column;
gap: 20px;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-fill-mode: both;
will-change: transform;
}
.scroll-column:nth-child(1) {
animation-name: scrollDown;
animation-duration: 17.5s;
transform: translateY(-40%);
}
.scroll-column:nth-child(2) {
animation-name: scrollUp;
animation-duration: 22.5s;
transform: translateY(-30%);
}
.scroll-column:nth-child(3) {
animation-name: scrollDown;
animation-duration: 20s;
transform: translateY(-50%);
}
.scroll-column:nth-child(4) {
animation-name: scrollUp;
animation-duration: 25s;
transform: translateY(-20%);
}
.scroll-column:nth-child(5) {
animation-name: scrollDown;
animation-duration: 19s;
transform: translateY(-35%);
}
@keyframes scrollUp {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-50%);
}
}
@keyframes scrollDown {
0% {
transform: translateY(-50%);
}
100% {
transform: translateY(0);
}
}
.scroll-column:hover {
animation-play-state: paused;
}
.scroll-item {
position: relative;
height: 800px;
min-width: 250px;
background: #FFF;
border-radius: 15px;
overflow: hidden;
cursor: pointer;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
transition: all 0.4s ease;
flex-shrink: 0;
}
.scroll-item:hover {
transform: translateY(-10px);
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.2);
z-index: 10;
}
.scroll-image {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.4s ease;
}
.scroll-item:hover .scroll-image {
transform: scale(1.05);
}
.scroll-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent);
padding: 20px;
color: #FFF;
opacity: 0;
transform: translateY(20px);
transition: all 0.4s ease;
}
.scroll-item:hover .scroll-overlay {
opacity: 1;
transform: translateY(0);
}
.scroll-title {
font-size: 1.4em;
margin-bottom: 10px;
color: #F4E4BC;
}
.scroll-date {
font-size: 0.9em;
color: #DEB887;
margin-bottom: 10px;
}
.expanded-view {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.95);
z-index: 1000;
display: none;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.5s ease;
}
.expanded-view.active {
display: flex;
opacity: 1;
}
.expanded-content {
position: relative;
width: 90%;
max-width: 1200px;
height: 90%;
display: flex;
gap: 30px;
color: #FFF;
transform: scale(0.9);
transition: transform 0.5s ease;
}
.expanded-view.active .expanded-content {
transform: scale(1);
}
.expanded-image {
flex: 1;
max-width: 70%;
height: 100%;
object-fit: contain;
}
.expanded-info {
flex: 0 0 30%;
padding: 20px;
overflow-y: auto;
}
.close-expanded {
position: absolute;
top: 20px;
right: 20px;
color: #FFF;
font-size: 30px;
cursor: pointer;
background: none;
border: none;
z-index: 1;
}
.scroll-item:nth-child(3n+1),
.scroll-item:nth-child(3n+2),
.scroll-item:nth-child(3n+3) {
transform-origin: center;
}
@media (max-width: 1200px) {
.scroll-gallery {
gap: 15px;
height: calc(700px * 3);
}
.scroll-item {
height: 700px;
min-width: 200px;
}
}
@media (max-width: 768px) {
.scroll-gallery {
gap: 10px;
height: calc(600px * 3);
}
.scroll-item {
height: 600px;
min-width: 150px;
}
}
.scroll-indicator {
position: fixed;
right: 20px;
top: 50%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
gap: 10px;
z-index: 100;
}
.indicator-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: rgba(139, 69, 19, 0.3);
cursor: pointer;
transition: all 0.3s ease;
}
.indicator-dot.active {
background: #8B4513;
transform: scale(1.5);
}
</style>
</head>
<body>
<div class="scroll-container">
<div class="chinese-scroll">
<div class="bamboo-content">
<div class="pattern-overlay"></div>
<header>
<nav>
<div class="logo">ChatDev数字博物馆</div>
<ul>
<li><a href="index.html" class="magnetic-link"><span>返回首页</span></a></li>
</ul>
</nav>
</header>
<main>
<div class="achievements-container">
<h2 class="section-title" data-aos="fade-up">全辑整理</h2>
<div class="scroll-gallery">
<div class="scroll-column">
<div class="scroll-item">
<img src="images/research-achievements/1.png" alt="清华简第一辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第一辑</h3>
<div class="scroll-date">2010年12月</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/2.png" alt="清华简第二辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第二辑</h3>
<div class="scroll-date">2011年12月</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/3.png" alt="清华简第三辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第三辑</h3>
<div class="scroll-date">2012年12月</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/4.png" alt="清华简第四辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第四辑</h3>
<div class="scroll-date">2013年12月</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/5.png" alt="清华简第五辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第五辑</h3>
<div class="scroll-date">2015年4月</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/6.png" alt="清华简第六辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第六辑</h3>
<div class="scroll-date">2016年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/7.png" alt="清华简第七辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第七辑</h3>
<div class="scroll-date">2017年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/8.png" alt="清华简第八辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第八辑</h3>
<div class="scroll-date">2018年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/9.png" alt="清华简第九辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第九辑</h3>
<div class="scroll-date">2019年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/10.png" alt="清华简第十辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十辑</h3>
<div class="scroll-date">2020年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/11.png" alt="清华简第十一辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十一辑</h3>
<div class="scroll-date">2021年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/12.png" alt="清华简第十二辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十二辑</h3>
<div class="scroll-date">2022年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/13.png" alt="清华简第十三辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十三辑</h3>
<div class="scroll-date">2022年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/14.png" alt="清华简第十四辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十四辑</h3>
<div class="scroll-date">2023年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/15.png" alt="清华简第一辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第一辑</h3>
<div class="scroll-date">2010年12月</div>
</div>
</div>
</div>
<div class="scroll-column">
<div class="scroll-item">
<img src="images/research-achievements/14.png" alt="清华简第二辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第二辑</h3>
<div class="scroll-date">2011年12月</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/13.png" alt="清华简第三辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第三辑</h3>
<div class="scroll-date">2012年12月</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/12.png" alt="清华简第四辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第四辑</h3>
<div class="scroll-date">2013年12月</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/11.png" alt="清华简第五辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第五辑</h3>
<div class="scroll-date">2015年4月</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/10.png" alt="清华简第六辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第六辑</h3>
<div class="scroll-date">2016年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/9.png" alt="清华简第七辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第七辑</h3>
<div class="scroll-date">2017年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/8.png" alt="清华简第八辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第八辑</h3>
<div class="scroll-date">2018年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/7.png" alt="清华简第九辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第九辑</h3>
<div class="scroll-date">2019年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/6.png" alt="清华简第十辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十辑</h3>
<div class="scroll-date">2020年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/5.png" alt="清华简第十一辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十一辑</h3>
<div class="scroll-date">2021年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/4.png" alt="清华简第十二辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十二辑</h3>
<div class="scroll-date">2022年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/3.png" alt="清华简第十三辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十三辑</h3>
<div class="scroll-date">2022年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/2.png" alt="清华简第十四辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十四辑</h3>
<div class="scroll-date">2023年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/1.png" alt="清华简第二辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第二辑</h3>
<div class="scroll-date">2011年12月</div>
</div>
</div>
</div>
<div class="scroll-column">
<div class="scroll-item">
<img src="images/research-achievements/2.png" alt="清华简第三辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第三辑</h3>
<div class="scroll-date">2012年12月</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/3.png" alt="清华简第四辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第四辑</h3>
<div class="scroll-date">2013年12月</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/4.png" alt="清华简第五辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第五辑</h3>
<div class="scroll-date">2015年4月</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/5.png" alt="清华简第六辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第六辑</h3>
<div class="scroll-date">2016年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/6.png" alt="清华简第七辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第七辑</h3>
<div class="scroll-date">2017年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/7.png" alt="清华简第八辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第八辑</h3>
<div class="scroll-date">2018年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/8.png" alt="清华简第九辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第九辑</h3>
<div class="scroll-date">2019年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/9.png" alt="清华简第十辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十辑</h3>
<div class="scroll-date">2020年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/10.png" alt="清华简第十一辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十一辑</h3>
<div class="scroll-date">2021年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/11.png" alt="清华简第十二辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十二辑</h3>
<div class="scroll-date">2022年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/12.png" alt="清华简第十三辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十三辑</h3>
<div class="scroll-date">2022年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/13.png" alt="清华简第十四辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十四辑</h3>
<div class="scroll-date">2023年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/14.png" alt="清华简第三辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第三辑</h3>
<div class="scroll-date">2012年12月</div>
</div>
</div>
</div>
<div class="scroll-column">
<div class="scroll-item">
<img src="images/research-achievements/15.png" alt="清华简第四辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第四辑</h3>
<div class="scroll-date">2013年12月</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/14.png" alt="清华简第五辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第五辑</h3>
<div class="scroll-date">2015年4月</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/13.png" alt="清华简第六辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第六辑</h3>
<div class="scroll-date">2016年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/12.png" alt="清华简第七辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第七辑</h3>
<div class="scroll-date">2017年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/11.png" alt="清华简第八辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第八辑</h3>
<div class="scroll-date">2018年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/10.png" alt="清华简第九辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第九辑</h3>
<div class="scroll-date">2019年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/9.png" alt="清华简第十辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十辑</h3>
<div class="scroll-date">2020年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/8.png" alt="清华简第十一辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十一辑</h3>
<div class="scroll-date">2021年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/7.png" alt="清华简第十二辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十二辑</h3>
<div class="scroll-date">2022年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/6.png" alt="清华简第十三辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十三辑</h3>
<div class="scroll-date">2022年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/5.png" alt="清华简第十四辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十四辑</h3>
<div class="scroll-date">2023年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/4.png" alt="清华简第四辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第四辑</h3>
<div class="scroll-date">2013年12月</div>
</div>
</div>
</div>
<div class="scroll-column">
<div class="scroll-item">
<img src="images/research-achievements/3.png" alt="清华简第五辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第五辑</h3>
<div class="scroll-date">2015年4月</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/2.png" alt="清华简第六辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第六辑</h3>
<div class="scroll-date">2016年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/1.png" alt="清华简第七辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第七辑</h3>
<div class="scroll-date">2017年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/2.png" alt="清华简第八辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第八辑</h3>
<div class="scroll-date">2018年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/3.png" alt="清华简第九辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第九辑</h3>
<div class="scroll-date">2019年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/4.png" alt="清华简第十辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十辑</h3>
<div class="scroll-date">2020年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/5.png" alt="清华简第十一辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十一辑</h3>
<div class="scroll-date">2021年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/6.png" alt="清华简第十二辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十二辑</h3>
<div class="scroll-date">2022年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/7.png" alt="清华简第十三辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十三辑</h3>
<div class="scroll-date">2022年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/8.png" alt="清华简第十四辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第十四辑</h3>
<div class="scroll-date">2023年</div>
</div>
</div>
<div class="scroll-item">
<img src="images/research-achievements/9.png" alt="清华简第五辑" class="scroll-image">
<div class="scroll-overlay">
<h3 class="scroll-title">《清华简》第五辑</h3>
<div class="scroll-date">2015年4月</div>
</div>
</div>
</div>
</div>
</div>
</main>
<footer>
<div class="footer-content">
<div class="footer-left">
<img src="images/logo/chatdev-logo.png" alt="ChatDev Logo" class="footer-logo">
<div class="contact" data-aos="fade-up">
<p>ChatDev数字博物馆</p>
<p>https://github.com/OpenBMB/ChatDev</p>
</div>
</div>
<div class="opening-hours" data-aos="fade-up" data-aos-delay="100">
<p>以上内容由AI生成可能存在偏差请谨慎使用</p>
</div>
</div>
</footer>
</div>
</div>
</div>
<div class="expanded-view">
<button class="close-expanded">&times;</button>
<div class="expanded-content">
<img src="" alt="" class="expanded-image">
<div class="expanded-info">
<h2 class="expanded-title"></h2>
<div class="expanded-date"></div>
<div class="expanded-description"></div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script>
AOS.init({
duration: 1000,
once: true
});
const scrollItems = document.querySelectorAll('.scroll-item');
const expandedView = document.querySelector('.expanded-view');
const expandedImage = expandedView.querySelector('.expanded-image');
const expandedTitle = expandedView.querySelector('.expanded-title');
const expandedDate = expandedView.querySelector('.expanded-date');
const expandedDescription = expandedView.querySelector('.expanded-description');
const closeExpanded = expandedView.querySelector('.close-expanded');
const descriptions = [
"收录《周武王有疾周公所自以代王之志》《皇门》等重要文献为研究周代历史提供新材料。",
"包含《系年》等重要历史文献,为战国时期编年史研究提供重要参考。",
"收录《赵简子》《祭公》等文献,展现先秦时期政治制度与礼仪文化。",
"包含《郑文公》《克殷》等篇章,丰富了我们对商周之际历史的认识。",
"收录《湯處》《厚父》等重要文献,展现商周时期的政治思想。",
"包含多篇重要历史文献,进一步丰富了先秦历史研究资料。",
"展现了战国时期的政治、文化等多个方面的珍贵史料。",
"记载了大量先秦时期的重要历史事件和文化现象。",
"为研究先秦历史提供了新的文献依据和研究视角。",
"展现了战国时期的文化特征和历史发展脉络。",
"记录了大量珍贵的历史资料和文化遗产。",
"为研究先秦历史提供了重要的文献支持。",
"包含多篇重要的历史文献和文化记载。",
"展现了战国时期丰富的历史文化内涵。"
];
scrollItems.forEach((item, index) => {
item.addEventListener('click', () => {
const image = item.querySelector('.scroll-image');
const title = item.querySelector('.scroll-title');
const date = item.querySelector('.scroll-date');
expandedImage.src = image.src.replace('600x800', '1200x1600');
expandedTitle.textContent = title.textContent;
expandedDate.textContent = date.textContent;
const randomIndex = Math.floor(Math.random() * descriptions.length);
expandedDescription.textContent = descriptions[randomIndex];
expandedView.classList.add('active');
document.body.style.overflow = 'hidden';
});
});
closeExpanded.addEventListener('click', () => {
expandedView.classList.remove('active');
document.body.style.overflow = '';
});
expandedView.addEventListener('click', (e) => {
if (e.target === expandedView) {
expandedView.classList.remove('active');
document.body.style.overflow = '';
}
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && expandedView.classList.contains('active')) {
expandedView.classList.remove('active');
document.body.style.overflow = '';
}
});
</script>
</body>
</html>

View File

@ -0,0 +1,216 @@
document.addEventListener('DOMContentLoaded', function() {
AOS.init({
duration: 1000,
once: true,
offset: 100,
disable: '.parallax-section' // Disable AOS for parallax section
});
const swiper = new Swiper('.hero-slider', {
loop: true,
speed: 1000,
autoplay: {
delay: 5000,
disableOnInteraction: false,
},
pagination: {
el: '.swiper-pagination',
clickable: true,
},
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
});
const magneticLinks = document.querySelectorAll('.magnetic-link');
magneticLinks.forEach(link => {
link.addEventListener('mousemove', function(e) {
const bounds = this.getBoundingClientRect();
const mouseX = e.clientX - bounds.left;
const mouseY = e.clientY - bounds.top;
const centerX = bounds.width / 2;
const centerY = bounds.height / 2;
const deltaX = mouseX - centerX;
const deltaY = mouseY - centerY;
gsap.to(this, {
x: deltaX * 0.3,
y: deltaY * 0.3,
duration: 0.3
});
});
link.addEventListener('mouseleave', function() {
gsap.to(this, {
x: 0,
y: 0,
duration: 0.3
});
});
});
gsap.registerPlugin(ScrollTrigger);
const timelineItems = document.querySelectorAll('.timeline-item');
timelineItems.forEach((item, index) => {
const tl = gsap.timeline({
scrollTrigger: {
trigger: item,
start: "top center+=100",
end: "bottom center",
toggleActions: "play none none reverse"
}
});
tl.from(item.querySelector('::before'), {
scale: 0,
opacity: 0,
duration: 0.6,
ease: "back.out(1.7)"
});
tl.from(item.querySelector('.timeline-content'), {
x: index % 2 === 0 ? 50 : -50,
opacity: 0,
duration: 0.8,
ease: "power2.out"
}, "-=0.3");
tl.from(item.querySelector('.timeline-period'), {
y: 20,
opacity: 0,
duration: 0.5,
ease: "power2.out"
}, "-=0.4");
});
VanillaTilt.init(document.querySelectorAll('.digital-card'), {
max: 15,
speed: 400,
glare: true,
'max-glare': 0.2
});
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
window.scrollTo({
top: target.offsetTop - 70,
behavior: 'smooth'
});
}
});
});
const patternOverlay = document.querySelector('.pattern-overlay');
window.addEventListener('mousemove', (e) => {
const moveX = (e.clientX - window.innerWidth / 2) * 0.01;
const moveY = (e.clientY - window.innerHeight / 2) * 0.01;
gsap.to(patternOverlay, {
x: moveX,
y: moveY,
duration: 1,
ease: 'power2.out'
});
});
const exhibitionCards = document.querySelectorAll('.exhibition-card');
exhibitionCards.forEach(card => {
card.addEventListener('mouseenter', () => {
gsap.to(card, {
y: -10,
scale: 1.02,
duration: 0.3,
ease: 'power2.out'
});
});
card.addEventListener('mouseleave', () => {
gsap.to(card, {
y: 0,
scale: 1,
duration: 0.3,
ease: 'power2.out'
});
});
});
function createParticles() {
const particleContainer = document.createElement('div');
particleContainer.className = 'particle-container';
document.body.appendChild(particleContainer);
for (let i = 0; i < 50; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particle.style.left = Math.random() * 100 + 'vw';
particle.style.animationDelay = Math.random() * 5 + 's';
particleContainer.appendChild(particle);
}
}
document.addEventListener('DOMContentLoaded', () => {
createParticles();
const scrollElements = document.querySelectorAll('.bamboo-content > *');
scrollElements.forEach(element => {
element.style.opacity = '0';
element.style.transform = 'translateX(-20px)';
});
const revealOnScroll = () => {
scrollElements.forEach(element => {
const elementTop = element.getBoundingClientRect().top;
if (elementTop < window.innerHeight - 100) {
element.style.opacity = '1';
element.style.transform = 'translateX(0)';
element.style.transition = 'all 0.8s ease';
}
});
};
window.addEventListener('scroll', revealOnScroll);
revealOnScroll();
});
function animateCounter(element) {
const target = parseInt(element.dataset.target);
const duration = 2000;
const step = target / (duration / 16); // 60fps
let current = 0;
const updateCounter = () => {
current += step;
if (current < target) {
element.textContent = Math.floor(current);
requestAnimationFrame(updateCounter);
} else {
element.textContent = target;
}
};
updateCounter();
}
const observerOptions = {
threshold: 0.5
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const counters = entry.target.querySelectorAll('.counter');
counters.forEach(counter => animateCounter(counter));
observer.unobserve(entry.target);
}
});
}, observerOptions);
document.querySelectorAll('.stats-section').forEach(section => {
observer.observe(section);
});
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,369 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChatDev数字博物馆</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css">
<link rel="stylesheet" href="https://unpkg.com/aos@2.3.1/dist/aos.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
<link rel="stylesheet" href="styles.css">
<link href="https://fonts.googleapis.com/css2?family=Ma+Yen+Kai&display=swap" rel="stylesheet">
<style>
.bamboo-container {
padding: 50px 20px;
background: rgba(244, 241, 234, 0.9);
min-height: 100vh;
}
.section-title {
text-align: center;
font-size: 2.5em;
color: #4A3728;
margin-bottom: 60px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}
.gallery-container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 40px;
margin-bottom: 60px;
}
.gallery-item {
position: relative;
background: #DEB887;
border-radius: 15px;
overflow: hidden;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
}
.gallery-item:hover {
transform: translateY(-10px);
}
.gallery-image {
width: 100%;
height: 500px;
object-fit: cover;
border-radius: 15px 15px 0 0;
transition: transform 0.3s ease;
}
.gallery-item:hover .gallery-image {
transform: scale(1.05);
}
.gallery-overlay {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(to top, rgba(89, 44, 12, 0.95), transparent);
padding: 30px;
color: #FFFFFF;
transform: translateY(70%);
transition: transform 0.3s ease;
}
.gallery-item:hover .gallery-overlay {
transform: translateY(0);
}
.gallery-title {
font-size: 1.8em;
margin-bottom: 15px;
color: #FFE4B5;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.gallery-description {
font-size: 1.1em;
line-height: 1.6;
margin-bottom: 20px;
opacity: 1;
color: #FFFFFF;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
}
.gallery-details {
background: rgba(0, 0, 0, 0.3);
padding: 15px;
border-radius: 8px;
margin-top: 15px;
font-size: 0.95em;
}
.gallery-details ul {
list-style: none;
padding: 0;
margin: 0;
}
.gallery-details li {
margin-bottom: 8px;
padding-left: 20px;
position: relative;
color: #FFE4B5;
}
.gallery-details li::before {
content: '•';
position: absolute;
left: 0;
color: #FFE4B5;
}
.featured-section {
position: relative;
height: 600px;
margin-bottom: 60px;
border-radius: 20px;
overflow: hidden;
}
.featured-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.featured-content {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 40px;
background: linear-gradient(to top, rgba(89, 44, 12, 0.95), transparent);
color: #FFFFFF;
}
.featured-title {
font-size: 2.5em;
margin-bottom: 20px;
color: #FFE4B5;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.featured-description {
font-size: 1.2em;
line-height: 1.8;
max-width: 800px;
color: #FFFFFF;
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
opacity: 1;
}
/* Modal styles */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0);
z-index: 1000;
justify-content: center;
align-items: center;
transition: background-color 0.5s ease-in-out;
}
.modal.active {
display: flex;
background-color: rgba(0, 0, 0, 0.9);
}
.modal-content {
position: relative;
max-width: 90%;
max-height: 90vh;
}
.modal-image {
max-width: 100%;
max-height: 90vh;
object-fit: contain;
}
.close-modal {
position: absolute;
top: -40px;
right: 0;
color: white;
font-size: 30px;
cursor: pointer;
background: none;
border: none;
padding: 10px;
}
@media (max-width: 768px) {
.gallery-grid {
grid-template-columns: 1fr;
}
.gallery-image {
height: 400px;
}
.featured-section {
height: 400px;
}
.featured-title {
font-size: 2em;
}
}
</style>
</head>
<body>
<div class="scroll-container">
<div class="chinese-scroll">
<div class="bamboo-content">
<!-- Chinese Pattern Overlay -->
<div class="pattern-overlay"></div>
<header>
<nav>
<div class="logo">ChatDev数字博物馆</div>
<ul>
<li><a href="index.html" class="magnetic-link"><span>返回首页</span></a></li>
</ul>
</nav>
</header>
<main>
<div class="bamboo-container">
<h2 class="section-title" data-aos="fade-up">战国文化</h2>
<div class="gallery-container">
<div class="featured-section" data-aos="fade-up">
<img src="./images/warring-states-bamboo-1.png" alt="清华简" class="featured-image">
<div class="featured-content">
<h2 class="featured-title">清华简 - 战国时期的文化瑰宝</h2>
<p class="featured-description">2008年入藏清华大学的2500余枚战国竹简是迄今为止最重要的先秦文献发现之一为研究中国古代历史提供了珍贵的第一手资料。</p>
</div>
</div>
<div class="gallery-grid">
<div class="gallery-item" data-aos="fade-up">
<img src="./images/warring-states-bamboo-sub1.png" alt="礼仪记录" class="gallery-image">
<div class="gallery-overlay">
<h3 class="gallery-title">礼仪记录</h3>
<p class="gallery-description">两篇文献编连成卷竹简长约46厘米宽约0.6厘米,详细记载了楚国大夫饮食礼仪制度。</p>
<div class="gallery-details">
<ul>
<li>《大夫食礼》51支简《大夫食礼记》14支简</li>
<li>简背有刻画痕迹,各简均有编号</li>
<li>前者记载食礼内容,后者论述执事者职责</li>
</ul>
</div>
</div>
</div>
<div class="gallery-item" data-aos="fade-up" data-aos-delay="100">
<img src="./images/warring-states-bamboo-sub2.png" alt="八卦图" class="gallery-image">
<div class="gallery-overlay">
<h3 class="gallery-title">八卦占卜</h3>
<p class="gallery-description">战国时期楚国独特的占筮方法,是目前所见最早的《易》图。</p>
<div class="gallery-details">
<ul>
<li>保存完好,维持原来成卷状态</li>
<li>分栏书写,附有插图和表格</li>
<li>首次展现八卦分置八方的卦位图</li>
</ul>
</div>
</div>
</div>
<div class="gallery-item" data-aos="fade-up" data-aos-delay="200">
<img src="./images/warring-states-bamboo-sub3.png" alt="算表" class="gallery-image">
<div class="gallery-overlay">
<h3 class="gallery-title">数学算表</h3>
<p class="gallery-description">形成于公元前305年左右的古代数学文献比里耶秦简九九表更早。</p>
<div class="gallery-details">
<ul>
<li>21支竹简构成21行20列的计算表</li>
<li>可用于两位数乘除法运算</li>
<li>支持含分数1/2的两位数乘法</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
<footer>
<div class="footer-content">
<div class="footer-left">
<img src="images/logo/chatdev-logo.png" alt="ChatDev Logo" class="footer-logo">
<div class="contact" data-aos="fade-up">
<p>ChatDev数字博物馆</p>
<p>https://github.com/OpenBMB/ChatDev</p>
</div>
</div>
<div class="opening-hours" data-aos="fade-up" data-aos-delay="100">
<p>以上内容由AI生成可能存在偏差请谨慎使用</p>
</div>
</div>
</footer>
</div>
</div>
</div>
<div class="modal" id="imageModal">
<div class="modal-content">
<button class="close-modal">&times;</button>
<img src="" alt="" class="modal-image">
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script>
AOS.init({
duration: 1000,
once: true
});
const modal = document.getElementById('imageModal');
const modalImage = modal.querySelector('.modal-image');
const closeButton = modal.querySelector('.close-modal');
const images = document.querySelectorAll('.gallery-image, .featured-image');
images.forEach(image => {
image.addEventListener('click', () => {
modalImage.src = image.src.replace('600x800', '1200x1600').replace('1400x600', '2800x1200');
modal.classList.add('active');
});
});
closeButton.addEventListener('click', () => {
modal.classList.remove('active');
});
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.classList.remove('active');
}
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && modal.classList.contains('active')) {
modal.classList.remove('active');
}
});
</script>
</body>
</html>

View File

@ -0,0 +1,90 @@
{
"chain": [
{
"phase": "Coding",
"phaseType": "SimplePhase",
"max_turn_step": 1,
"need_reflect": "False"
},
{
"phase": "CodeCompleteAll",
"phaseType": "ComposedPhase",
"cycleNum": 10,
"Composition": [{
"phase": "CodeComplete",
"phaseType": "SimplePhase",
"max_turn_step": 1,
"need_reflect": "False"
}]
},
{
"phase": "CodeReview",
"phaseType": "ComposedPhase",
"cycleNum": 2,
"Composition": [{
"phase": "CodeReviewComment",
"phaseType": "SimplePhase",
"max_turn_step": 1,
"need_reflect": "False"
},
{
"phase": "CodeReviewModification",
"phaseType": "SimplePhase",
"max_turn_step": 1,
"need_reflect": "False"
}
]
},
{
"phase": "Test",
"phaseType": "ComposedPhase",
"cycleNum": 2,
"Composition": [
{
"phase": "TestErrorSummary",
"phaseType": "SimplePhase",
"max_turn_step": 1,
"need_reflect": "False"
},
{
"phase": "TestModification",
"phaseType": "SimplePhase",
"max_turn_step": 1,
"need_reflect": "False"
}
]
},
{
"phase": "HumanAgentInteraction",
"phaseType": "ComposedPhase",
"cycleNum": 1,
"Composition": [
{
"phase": "CodeReviewHuman",
"phaseType": "SimplePhase",
"max_turn_step": 2,
"need_reflect": "False"
}
]
}
],
"recruitments": [
"Chief Executive Officer",
"Counselor",
"Chief Human Resource Officer",
"Chief Product Officer",
"Chief Technology Officer",
"Programmer",
"Code Reviewer",
"Software Test Engineer",
"Chief Creative Officer"
],
"clear_structure": "True",
"gui_design": "True",
"git_management": "False",
"web_spider": "False",
"self_improve": "False",
"incremental_develop": "False",
"with_memory": "False",
"background_prompt": "ChatDev is a virtual LLM Agent software company aiming to build the best software with simple but robust code, maximizing the controllability and visual effect of the software in pure python."
}

View File

@ -0,0 +1 @@
仔细开发一个赛车小游戏

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,306 @@
{
"DemandAnalysis": {
"assistant_role_name": "Chief Product Officer",
"user_role_name": "Chief Executive Officer",
"phase_prompt": [
"ChatDev has made products in the following form before:",
"Image: can present information in line chart, bar chart, flow chart, cloud chart, Gantt chart, etc.",
"Document: can present information via .docx files.",
"PowerPoint: can present information via .pptx files.",
"Excel: can present information via .xlsx files.",
"PDF: can present information via .pdf files.",
"Website: can present personal resume, tutorial, products, or ideas, via .html files.",
"Application: can implement visualized game, software, tool, etc, via python.",
"Dashboard: can display a panel visualizing real-time information.",
"Mind Map: can represent ideas, with related concepts arranged around a core concept.",
"As the {assistant_role}, to satisfy the new user's demand and the product should be realizable, you should keep discussing with me to decide which product modality do we want the product to be?",
"Note that we must ONLY discuss the product modality and do not discuss anything else! Once we all have expressed our opinion(s) and agree with the results of the discussion unanimously, any of us must actively terminate the discussion by replying with only one line, which starts with a single word <INFO>, followed by our final product modality without any other words, e.g., \"<INFO> PowerPoint\"."
]
},
"LanguageChoose": {
"assistant_role_name": "Chief Technology Officer",
"user_role_name": "Chief Executive Officer",
"phase_prompt": [
"According to the new user's task and some creative brainstorm ideas listed below: ",
"Task: \"{task}\".",
"Modality: \"{modality}\".",
"Ideas: \"{ideas}\".",
"We have decided to complete the task through a executable software implemented via a programming language. ",
"As the {assistant_role}, to satisfy the new user's demand and make the software realizable, you should propose a concrete programming language. If python can complete this task via Python, please answer Python; otherwise, answer another programming language (e.g., Java, C++, etc,).",
"Note that we must ONLY discuss the target programming language and do not discuss anything else! Once we all have expressed our opinion(s) and agree with the results of the discussion unanimously, any of us must actively terminate the discussion and conclude the best programming language we have discussed without any other words or reasons, return only one line using the format: \"<INFO> *\" where \"*\" represents a programming language."
]
},
"Coding": {
"assistant_role_name": "Programmer",
"user_role_name": "Chief Technology Officer",
"phase_prompt": [
"According to the new user's task and our software designs listed below: ",
"Task: \"{task}\".",
"Task description: \"{description}\".",
"Modality: \"{modality}\".",
"Programming Language: \"{language}\"",
"Ideas:\"{ideas}\"",
"We have decided to complete the task through a executable software with multiple files implemented via {language}. As the {assistant_role}, to satisfy the new user's demands, you should write one or multiple files and make sure that every detail of the architecture is, in the end, implemented as code. {gui}",
"Think step by step and reason yourself to the right decisions to make sure we get it right.",
"If you are using python to develop a software, develop it all in pure python, do not use assets such as images, videos, etc. The animation and GUI are necessary but should all implement in pure python.",
"You will first lay out the names of the core classes, functions, methods that will be necessary, as well as a quick comment on their purpose.",
"Then you will output the content of each file including complete code. Each file must strictly follow a markdown code block format, where the following tokens must be replaced such that \"FILENAME\" is the lowercase file name including the file extension, \"LANGUAGE\" in the programming language, \"DOCSTRING\" is a string literal specified in source code that is used to document a specific segment of code, and \"CODE\" is the original code:",
"FILENAME",
"```LANGUAGE",
"'''",
"DOCSTRING",
"'''",
"CODE",
"```",
"You will start with the \"main\" file, then go to the ones that are imported by that file, and so on.",
"Please note that the code should be fully functional. Ensure to implement all functions. No placeholders (such as 'pass' in Python)."
]
},
"ArtDesign": {
"assistant_role_name": "Programmer",
"user_role_name": "Chief Creative Officer",
"phase_prompt": [
"Our developed source codes and corresponding test reports are listed below: ",
"Task: \"{task}\".",
"Programming Language: \"{language}\"",
"Source Codes:",
"\"{codes}\"",
"Note that each file must strictly follow a markdown code block format, where the following tokens must be replaced such that \"FILENAME\" is the lowercase file name including the file extension, \"LANGUAGE\" in the programming language, \"DOCSTRING\" is a string literal specified in source code that is used to document a specific segment of code, and \"CODE\" is the original code:",
"FILENAME",
"```LANGUAGE",
"'''",
"DOCSTRING",
"'''",
"CODE",
"```",
"As the {assistant_role}, to satisfy the new user's demand and equip the software with a beautiful graphical user interface (GUI), we will discuss and design many decorative images for GUI decoration. Now, we keep discussing the GUI beautification by listing some functionally independent elements in GUI that are being considered to be decorated by different pictures. For example, ten digits (0-9) in a calculator are functionally independent.",
"To answer, use the format: \" FILENAME.png: DESCRIPTION\" where \"FILENAME\" is the filename of the image and \"DESCRIPTION\" denotes the detailed description of the independent elements. For example:",
"'''",
"button_1.png: The button with the number \"1\" on it.",
"button_multiply.png: The button with the multiplication symbol (\"*\") on it.",
"background.png: the background color to decorate the Go game",
"'''",
"Now, list all functionally independent elements as much as possible."
]
},
"ArtIntegration": {
"assistant_role_name": "Programmer",
"user_role_name": "Chief Creative Officer",
"phase_prompt": [
"Our developed source codes and corresponding test reports are listed below: ",
"Task: \"{task}\".",
"Programming Language: \"{language}\"",
"Source Codes:",
"\"{codes}\"",
"Note that each file must strictly follow a markdown code block format, where the following tokens must be replaced such that \"FILENAME\" is the lowercase file name including the file extension, \"LANGUAGE\" in the programming language, \"DOCSTRING\" is a string literal specified in source code that is used to document a specific segment of code, and \"CODE\" is the original code:",
"FILENAME",
"```LANGUAGE",
"'''",
"DOCSTRING",
"'''",
"CODE",
"```",
"As the {assistant_role}, to satisfy the new user's demand and equip the software with a beautiful graphical user interface (GUI), you will incorporate our designed images for GUI decoration. Here are some ready-made high-quality pictures and corresponding descriptions:",
"{images}",
"Note that the designed images have a fixed size of 256x256 pixels and the images are located in the same directory as all the Python files; please dynamically scaling these images according to the size of GUI, and use \"self.*\" to avoid displaying-related problems caused by automatic garbage collection. For example:",
"```",
"self.image = ImageTk.PhotoImage(Image.open(\"./image.png\").resize((50, 50)))",
"```",
"Now, use some or all of the pictures into the GUI to make it more beautiful and creative. Output codes strictly following the required format mentioned above."
]
},
"CodeComplete": {
"assistant_role_name": "Programmer",
"user_role_name": "Chief Technology Officer",
"phase_prompt": [
"According to the new user's task and our software designs listed below: ",
"Task: \"{task}\".",
"Modality: \"{modality}\".",
"Programming Language: \"{language}\"",
"Codes:",
"\"{codes}\"",
"Unimplemented File:",
"\"{unimplemented_file}\"",
"If you are using python to develop a software, develop it all in pure python, do not use assets such as images, videos, etc. The animation and GUI are necessary but should all implement in pure python.",
"In our software, each file must strictly follow a markdown code block format, where the following tokens must be replaced such that \"FILENAME\" is the lowercase file name including the file extension, \"LANGUAGE\" in the programming language, \"DOCSTRING\" is a string literal specified in source code that is used to document a specific segment of code, and \"CODE\" is the original code:",
"FILENAME",
"```LANGUAGE",
"'''",
"DOCSTRING",
"'''",
"CODE",
"```",
"As the {assistant_role}, to satisfy the complete function of our developed software, you have to implement all methods in the {unimplemented_file} file which contains a unimplemented class. Now, implement all methods of the {unimplemented_file} and all other codes needed, then output the fully implemented codes, strictly following the required format."
]
},
"CodeReviewComment": {
"assistant_role_name": "Code Reviewer",
"user_role_name": "Programmer",
"phase_prompt": [
"According to the new user's task and our software designs: ",
"Task: \"{task}\".",
"Modality: \"{modality}\".",
"Programming Language: \"{language}\"",
"Ideas: \"{ideas}\"",
"Codes:",
"\"{codes}\"",
"As the {assistant_role}, to make the software directly operable without further coding, ChatDev have formulated the following regulations:",
"1) all referenced classes should be imported;",
"2) all methods should be implemented;",
"3) all methods need to have the necessary comments;",
"4) no potential bugs;",
"5) The entire project conforms to the tasks proposed by the user;",
"6) most importantly, do not only check the errors in the code, but also the logic of code. Make sure that user can interact with generated software without losing any feature in the requirement;",
"7) If you are using python to develop a software, develop it all in pure python, do not use assets such as images, videos, etc. The animation and GUI are necessary but should all implement in pure python.",
"Now, you should check the above regulations one by one and review the codes in detail, propose one comment with the highest priority about the codes, and give me instructions on how to fix. Tell me your comment with the highest priority and corresponding suggestions on revision. If the codes are perfect and you have no comment on them, return only one line like \"<INFO> Finished\"."
]
},
"CodeReviewHuman": {
"assistant_role_name": "Programmer",
"user_role_name": "Code Reviewer",
"phase_prompt": [
"According to the new user's task, our designed product modality and three creative ideas, our developed first-edition source codes are listed below: ",
"Task: \"{task}\".",
"Modality: \"{modality}\".",
"Programming Language: \"{language}\"",
"Ideas: \"{ideas}\"",
"Codes: ",
"\"{codes}\"",
"Comments on Codes:",
"\"{comments}\"",
"In the software, each file must strictly follow a markdown code block format, where the following tokens must be replaced such that \"FILENAME\" is the lowercase file name including the file extension, \"LANGUAGE\" in the programming language, \"DOCSTRING\" is a string literal specified in source code that is used to document a specific segment of code, and \"CODE\" is the original code. Format:",
"FILENAME",
"```LANGUAGE",
"'''",
"DOCSTRING",
"'''",
"CODE",
"```",
"As the {assistant_role}, to satisfy the new user's demand and make the software creative, executive and robust, you should modify corresponding codes according to the comments. Then, output the full and complete codes with all bugs fixed based on the comments. Return all codes strictly following the required format."
]
},
"CodeReviewModification": {
"assistant_role_name": "Programmer",
"user_role_name": "Code Reviewer",
"phase_prompt": [
"According to the new user's task, our designed product modality, languages and ideas, our developed first-edition source codes are listed below: ",
"Task: \"{task}\".",
"Modality: \"{modality}\".",
"Programming Language: \"{language}\"",
"Ideas: \"{ideas}\"",
"Codes: ",
"\"{codes}\"",
"Comments on Codes:",
"\"{comments}\"",
"If you are using python to develop a software, develop it all in pure python, do not use assets such as images, videos, etc. The animation and GUI are necessary but should all implement in pure python.",
"In the software, each file must strictly follow a markdown code block format, where the following tokens must be replaced such that \"FILENAME\" is the lowercase file name including the file extension, \"LANGUAGE\" in the programming language, \"DOCSTRING\" is a string literal specified in source code that is used to document a specific segment of code, and \"CODE\" is the original code. Format:",
"FILENAME",
"```LANGUAGE",
"'''",
"DOCSTRING",
"'''",
"CODE",
"```",
"As the {assistant_role}, to satisfy the new user's demand and make the software creative, executive and robust, you should modify corresponding codes according to the comments. Then, output the full and complete codes with all bugs fixed based on the comments. Return all codes strictly following the required format."
]
},
"TestErrorSummary": {
"assistant_role_name": "Programmer",
"user_role_name": "Software Test Engineer",
"phase_prompt": [
"Our developed source codes and corresponding test reports are listed below: ",
"Programming Language: \"{language}\"",
"Source Codes:",
"\"{codes}\"",
"Test Reports of Source Codes:",
"\"{test_reports}\"",
"According to my test reports, please locate and summarize the bugs that cause the problem."
]
},
"TestModification": {
"assistant_role_name": "Programmer",
"user_role_name": "Software Test Engineer",
"phase_prompt": [
"Our developed source codes and corresponding test reports are listed below: ",
"Programming Language: \"{language}\"",
"Source Codes:",
"\"{codes}\"",
"Test Reports of Source Codes:",
"\"{test_reports}\"",
"Error Summary of Test Reports:",
"\"{error_summary}\"",
"Note that each file must strictly follow a markdown code block format, where the following tokens must be replaced such that \"FILENAME\" is the lowercase file name including the file extension, \"LANGUAGE\" in the programming language, \"DOCSTRING\" is a string literal specified in source code that is used to document a specific segment of code, and \"CODE\" is the original code:",
"FILENAME",
"```LANGUAGE",
"'''",
"DOCSTRING",
"'''",
"CODE",
"```",
"As the {assistant_role}, to satisfy the new user's demand and make the software execute smoothly and robustly, you should modify the codes based on the error summary. Now, use the format exemplified above and modify the problematic codes based on the error summary. Output the codes that you fixed based on the test reported and corresponding explanations (strictly follow the format defined above, including FILENAME, LANGUAGE, DOCSTRING and CODE; incomplete \"TODO\" codes are strictly prohibited). If no bugs are reported, please return only one line like \"<INFO> Finished\"."
]
},
"EnvironmentDoc": {
"assistant_role_name": "Programmer",
"user_role_name": "Chief Technology Officer",
"phase_prompt": [
"The new user's task and our developed codes are listed: ",
"Task: \"{task}\".",
"Modality: \"{modality}\".",
"Programming Language: \"{language}\"",
"Ideas: \"{ideas}\"",
"Codes: ",
"\"{codes}\"",
"As the {assistant_role}, you should write a requirements.txt file, which is commonly used in Python projects to specify the dependencies or packages required for the project to run properly. It serves as a way to document and manage the project's dependencies in a standardized format. For example:",
"requirements.txt",
"```",
"numpy==1.19.2",
"pandas>=1.1.4",
"```",
"According to the codes and file format listed above, write a requirements.txt file to specify the dependencies or packages required for the project to run properly."
]
},
"Manual": {
"assistant_role_name": "Chief Product Officer",
"user_role_name": "Chief Executive Officer",
"phase_prompt": [
"The new user's task, our developed codes and required dependencies are listed: ",
"Task: \"{task}\".",
"Modality: \"{modality}\".",
"Programming Language: \"{language}\"",
"Ideas: \"{ideas}\"",
"Codes: ",
"\"{codes}\"",
"Requirements:",
"\"{requirements}\"",
"As the {assistant_role}, by using Markdown, you should write a manual.md file which is a detailed user manual to use the software, including introducing main functions of the software, how to install environment dependencies and how to use/play it. For example:",
"manual.md",
"```",
"# LangChain",
"Building applications with LLMs through composability",
"Looking for the JS/TS version? Check out LangChain.js.",
"**Production Support:** As you move your LangChains into production, we'd love to offer more comprehensive support.",
"Please fill out this form and we'll set up a dedicated support Slack channel.",
"## Quick Install",
"`pip install langchain`",
"or",
"`conda install langchain -c conda-forge`",
"## 🤔 What is this?",
"Large language models (LLMs) are emerging as a transformative technology, enabling developers to build applications that they previously could not. However, using these LLMs in isolation is often insufficient for creating a truly powerful app - the real power comes when you can combine them with other sources of computation or knowledge.",
"This library aims to assist in the development of those types of applications. Common examples of these applications include:",
"**❓ Question Answering over specific documents**",
"- Documentation",
"- End-to-end Example: Question Answering over Notion Database",
"**🤖 Agents**",
"- Documentation",
"- End-to-end Example: GPT+WolframAlpha",
"## 📖 Documentation",
"Please see [here](https://python.langchain.com) for full documentation on:",
"- Getting started (installation, setting up the environment, simple examples)",
"- How-To examples (demos, integrations, helper functions)",
"- Reference (full API docs)",
"- Resources (high-level explanation of core concepts)",
"```"
]
}
}

View File

@ -0,0 +1,65 @@
{
"Chief Executive Officer": [
"{chatdev_prompt}",
"You are Chief Executive Officer. Now, we are both working at ChatDev and we share a common interest in collaborating to successfully complete a task assigned by a new customer.",
"Your main responsibilities include being an active decision-maker on users' demands and other key policy issues, leader, manager, and executor. Your decision-making role involves high-level decisions about policy and strategy; and your communicator role can involve speaking to the organization's management and employees.",
"Here is a new customer's task: {task}.",
"To complete the task, I will give you one or more instructions, and you must help me to write a specific solution that appropriately solves the requested instruction based on your expertise and my needs."
],
"Chief Product Officer": [
"{chatdev_prompt}",
"You are Chief Product Officer. we are both working at ChatDev. We share a common interest in collaborating to successfully complete a task assigned by a new customer.",
"You are responsible for all product-related matters in ChatDev. Usually includes product design, product strategy, product vision, product innovation, project management and product marketing.",
"Here is a new customer's task: {task}.",
"To complete the task, you must write a response that appropriately solves the requested instruction based on your expertise and customer's needs."
],
"Counselor": [
"{chatdev_prompt}",
"You are Counselor. Now, we share a common interest in collaborating to successfully complete a task assigned by a new customer.",
"Your main responsibilities include asking what user and customer think and provide your valuable suggestions. ",
"Here is a new customer's task: {task}.",
"To complete the task, I will give you one or more instructions, and you must help me to write a specific solution that appropriately solves the requested instruction based on your expertise and my needs."
],
"Chief Technology Officer": [
"{chatdev_prompt}",
"You are Chief Technology Officer. we are both working at ChatDev. We share a common interest in collaborating to successfully complete a task assigned by a new customer.",
"You are very familiar to information technology. You will make high-level decisions for the overarching technology infrastructure that closely align with the organization's goals, while you work alongside the organization's information technology (\"IT\") staff members to perform everyday operations.",
"Here is a new customer's task: {task}.",
"To complete the task, You must write a response that appropriately solves the requested instruction based on your expertise and customer's needs."
],
"Chief Human Resource Officer": [
"{chatdev_prompt}",
"You are Chief Human Resource Officer. Now, we are both working at ChatDev and we share a common interest in collaborating to successfully complete a task assigned by a new customer.",
"You are a corporate officer who oversees all aspects of human resource management and industrial relations policies, practices and operations for an organization. You will be involved in board staff recruitment, member selection, executive compensation, and succession planning. Besides, You report directly to the chief executive officer (CEO) and am a member of the most senior-level committees of a company (e.g., executive committee or office of CEO).",
"Here is a new customer's task: {task}.",
"To complete the task, you must write a response that appropriately solves the requested instruction based on your expertise and customer's needs."
],
"Programmer": [
"{chatdev_prompt}",
"You are Programmer. we are both working at ChatDev. We share a common interest in collaborating to successfully complete a task assigned by a new customer.",
"You can write/create computer software or applications by providing a specific programming language to the computer. You have extensive computing and coding experience in many varieties of programming languages and platforms, such as Python, Java, C, C++, HTML, CSS, JavaScript, XML, SQL, PHP, etc,.",
"Here is a new customer's task: {task}.",
"To complete the task, you must write a response that appropriately solves the requested instruction based on your expertise and customer's needs."
],
"Code Reviewer": [
"{chatdev_prompt}",
"You are Code Reviewer. we are both working at ChatDev. We share a common interest in collaborating to successfully complete a task assigned by a new customer.",
"You can help programmers to assess source codes for software troubleshooting, fix bugs to increase code quality and robustness, and offer proposals to improve the source codes.",
"Here is a new customer's task: {task}.",
"To complete the task, you must write a response that appropriately solves the requested instruction based on your expertise and customer's needs."
],
"Software Test Engineer": [
"{chatdev_prompt}",
"You are Software Test Engineer. we are both working at ChatDev. We share a common interest in collaborating to successfully complete a task assigned by a new customer.",
"You can use the software as intended to analyze its functional properties, design manual and automated test procedures to evaluate each software product, build and implement software evaluation test programs, and run test programs to ensure that testing protocols evaluate the software correctly.",
"Here is a new customer's task: {task}.",
"To complete the task, you must write a response that appropriately solves the requested instruction based on your expertise and customer's needs."
],
"Chief Creative Officer": [
"{chatdev_prompt}",
"You are Chief Creative Officer. we are both working at ChatDev. We share a common interest in collaborating to successfully complete a task assigned by a new customer.",
"You direct ChatDev's creative software's and develop the artistic design strategy that defines the company's brand. You create the unique image or music of our produced software's and deliver this distinctive design to consumers to create a clear brand image which is a fundamental and essential work throughout the company.",
"Here is a new customer's task: {task}.",
"To complete the task, you must write a response that appropriately solves the requested instruction based on your expertise and customer's needs."
]
}

View File

@ -0,0 +1,37 @@
'''
Player car class implementation with improved visual representation and road boundary constraints
'''
import pygame
class PlayerCar:
def __init__(self, x, y):
self.x = x
self.y = y
self.width = 40
self.height = 60
self.speed = 5
self.color = (255, 0, 0) # Red color
def handle_input(self, keys, screen_width):
# Road boundaries
left_boundary = screen_width//3 + 20 # Add padding for visibility
right_boundary = 2*screen_width//3 # Subtract padding for visibility
if keys[pygame.K_LEFT] and self.x > left_boundary:
self.x -= self.speed
if keys[pygame.K_RIGHT] and self.x < right_boundary - self.width:
self.x += self.speed
if keys[pygame.K_UP] and self.y > 0:
self.y -= self.speed
if keys[pygame.K_DOWN] and self.y < 600 - self.height:
self.y += self.speed
def update(self):
pass
def draw(self, screen):
# Draw car body
pygame.draw.rect(screen, self.color, (self.x, self.y, self.width, self.height))
# Draw car details (wheels and windows)
pygame.draw.rect(screen, (0, 0, 0), (self.x + 5, self.y + 10, 10, 40)) # Left wheel
pygame.draw.rect(screen, (0, 0, 0), (self.x + 25, self.y + 10, 10, 40)) # Right wheel
pygame.draw.rect(screen, (200, 200, 200), (self.x + 5, self.y + 5, 30, 15)) # Windshield
def check_collision(self, other):
return pygame.Rect(self.x, self.y, self.width, self.height).colliderect(
pygame.Rect(other.x, other.y, other.width, other.height)
)

View File

@ -0,0 +1,30 @@
'''
Game objects implementation including opponent cars with improved visuals and road constraints
'''
import pygame
import random
class OpponentCar:
def __init__(self, screen_width):
self.width = 40
self.height = 60
self.screen_width = screen_width
self.road_left = screen_width//3 + 20
self.road_right = 2*screen_width//3
self.reset_position()
self.color = (0, 0, 255) # Blue color
self.speed = random.randint(3, 7)
def reset_position(self):
# Ensure opponents spawn within road boundaries
self.x = random.randint(self.road_left, self.road_right - self.width)
self.y = random.randint(-300, -100)
def update(self):
self.y += self.speed
if self.y > 600:
self.reset_position()
def draw(self, screen):
# Draw car body
pygame.draw.rect(screen, self.color, (self.x, self.y, self.width, self.height))
# Draw car details
pygame.draw.rect(screen, (0, 0, 0), (self.x + 5, self.y + 10, 10, 40)) # Left wheel
pygame.draw.rect(screen, (0, 0, 0), (self.x + 25, self.y + 10, 10, 40)) # Right wheel
pygame.draw.rect(screen, (200, 200, 200), (self.x + 5, self.y + 5, 30, 15)) # Windshield

View File

@ -0,0 +1,76 @@
'''
Main game file that initializes the racing game and runs the game loop
'''
import pygame
import sys
from car import PlayerCar
from track import Track
from gameobjects import OpponentCar
class RacingGame:
def __init__(self):
pygame.init()
self.width = 800
self.height = 600
self.screen = pygame.display.set_mode((self.width, self.height))
pygame.display.set_caption("赛车游戏")
self.clock = pygame.time.Clock()
self.running = True
self.game_over = False
self.font = pygame.font.Font(None, 74)
self.reset_game()
def reset_game(self):
# Initialize player at the center of the road
road_center = (self.width//3 + (2*self.width//3 + 20)) // 2
self.player = PlayerCar(road_center - 20, self.height - 100)
self.track = Track(self.width, self.height)
self.opponents = [OpponentCar(self.width) for _ in range(3)]
self.score = 0
self.game_over = False
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
if event.type == pygame.KEYDOWN and self.game_over:
if event.key == pygame.K_SPACE:
self.reset_game()
if not self.game_over:
keys = pygame.key.get_pressed()
self.player.handle_input(keys, self.width)
def update(self):
if not self.game_over:
self.player.update()
self.track.update()
for opponent in self.opponents:
opponent.update()
if self.player.check_collision(opponent):
self.game_over = True
self.score += 1
def render(self):
self.screen.fill((100, 100, 100)) # Gray background
self.track.draw(self.screen)
self.player.draw(self.screen)
for opponent in self.opponents:
opponent.draw(self.screen)
# Draw score
font = pygame.font.Font(None, 36)
score_text = font.render(f'Score: {self.score}', True, (255, 255, 255))
self.screen.blit(score_text, (10, 10))
if self.game_over:
game_over_text = self.font.render('GAME OVER', True, (255, 0, 0))
restart_text = pygame.font.Font(None, 36).render('Press SPACE to restart', True, (255, 255, 255))
text_rect = game_over_text.get_rect(center=(self.width//2, self.height//2))
restart_rect = restart_text.get_rect(center=(self.width//2, self.height//2 + 50))
self.screen.blit(game_over_text, text_rect)
self.screen.blit(restart_text, restart_rect)
pygame.display.flip()
def run(self):
while self.running:
self.handle_events()
self.update()
self.render()
self.clock.tick(60)
pygame.quit()
sys.exit()
if __name__ == "__main__":
game = RacingGame()
game.run()

View File

@ -0,0 +1,33 @@
Task:
仔细开发一个赛车小游戏
Config:
ChatEnvConfig.with_memory: False
ChatEnvConfig.clear_structure: True
ChatEnvConfig.git_management: False
ChatEnvConfig.gui_design: True
ChatEnvConfig.incremental_develop: False
ChatEnvConfig.background_prompt: ChatDev is a virtual LLM Agent software company aiming to build the best software with simple but robust code, maximizing the controllability and visual effect of the software in pure python.
Roster:
Chief Executive Officer, Counselor, Chief Human Resource Officer, Chief Product Officer, Chief Technology Officer, Programmer, Code Reviewer, Software Test Engineer, Chief Creative Officer
Modality:
website or app, prefer app
Ideas:
Language:
python for app, or html/css/js for website, prefer app
Code_Version:
3.0
Proposed_images:
0
Incorporated_images:
0

View File

@ -0,0 +1,31 @@
'''
Track class for managing the racing environment with improved visuals and clear boundaries
'''
import pygame
class Track:
def __init__(self, width, height):
self.width = width
self.height = height
self.line_spacing = 100
self.line_height = 30
self.line_speed = 5
self.lines = []
self.initialize_lines()
self.road_left = self.width//3
self.road_right = 2*self.width//3 + 20
def initialize_lines(self):
for y in range(0, self.height, self.line_spacing):
self.lines.append(y)
def update(self):
self.lines = [(y + self.line_speed) % self.height for y in self.lines]
def draw(self, screen):
# Draw road background
pygame.draw.rect(screen, (50, 50, 50),
(self.road_left, 0, self.road_right - self.road_left, self.height))
# Draw road borders
pygame.draw.rect(screen, (255, 255, 0), (self.road_left - 5, 0, 5, self.height)) # Left border
pygame.draw.rect(screen, (255, 255, 0), (self.road_right, 0, 5, self.height)) # Right border
# Draw road lines
for y in self.lines:
pygame.draw.rect(screen, (255, 255, 255),
(self.width//2 - 10, y, 20, self.line_height))

Binary file not shown.

View File

@ -0,0 +1,63 @@
import pygame
from enum import Enum
# Window Settings
WINDOW_WIDTH = 1080
WINDOW_HEIGHT = 900
CELL_SIZE = 100
GRID_ROWS = 7
GRID_COLS = 9
TOP_MARGIN = 100
FPS = 60
# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
DARK_GREEN = (0, 100, 0)
BROWN = (139, 69, 19)
YELLOW = (255, 255, 0)
LAWN_GREEN = (124, 252, 0)
GRAY = (128, 128, 128)
RED = (255, 0, 0)
# Game States
class GameState(Enum):
MENU = 1
PLAYING = 2
PAUSED = 3
GAME_OVER = 4
# Plant Types
class PlantType(Enum):
SUNFLOWER = 1
PEASHOOTER = 2
ROSE_SHOOTER = 3
CHOMPER = 4
SNOW_PEA = 5
# Zombie Types
class ZombieType(Enum):
NORMAL = 1
CONE = 2
BUCKET = 3
NEWSPAPER = 4
DANCING = 5
# Plant Stats
PLANT_STATS = {
PlantType.SUNFLOWER: {"health": 100, "cost": 50, "color": YELLOW},
PlantType.PEASHOOTER: {"health": 120, "cost": 100, "color": GREEN},
PlantType.ROSE_SHOOTER: {"health": 140, "cost": 150, "color": (255, 192, 203)},
PlantType.CHOMPER: {"health": 180, "cost": 175, "color": (148, 0, 211)},
PlantType.SNOW_PEA: {"health": 130, "cost": 175, "color": (0, 191, 255)}
}
# Zombie Stats
ZOMBIE_STATS = {
ZombieType.NORMAL: {"health": 300, "speed": 0.4, "damage": 0.4},
ZombieType.CONE: {"health": 450, "speed": 0.45, "damage": 0.3},
ZombieType.BUCKET: {"health": 550, "speed": 0.35, "damage": 0.5},
ZombieType.NEWSPAPER: {"health": 200, "speed": 0.6, "damage": 0.3},
ZombieType.DANCING: {"health": 350, "speed": 0.5, "damage": 0.35}
}

View File

@ -0,0 +1,285 @@
import pygame
import math
from constants import *
from sprites import PLANT_DRAWINGS, ZOMBIE_DRAWINGS
class Plant:
def __init__(self, x, y, plant_type):
self.x = x
self.y = y
self.type = plant_type
stats = PLANT_STATS[plant_type]
self.health = stats["health"]
self.max_health = stats["health"]
self.cost = stats["cost"]
self.rect = pygame.Rect(x * CELL_SIZE, y * CELL_SIZE + TOP_MARGIN, CELL_SIZE, CELL_SIZE)
self.shoot_timer = 0
self.shoot_cooldown = 150 # Frames between shots
self.eating_timer = 0
self.eating_cooldown = 300 # Frames between chomps
self.sun_timer = 0
self.animation_state = "idle"
self.damaged_state = 0
# Special abilities
self.is_freezing = plant_type == PlantType.SNOW_PEA
self.can_eat = plant_type == PlantType.CHOMPER
self.is_rose = plant_type == PlantType.ROSE_SHOOTER
def update(self):
if self.type in [PlantType.PEASHOOTER, PlantType.SNOW_PEA, PlantType.ROSE_SHOOTER]:
self.shoot_timer += 1
elif self.type == PlantType.SUNFLOWER:
self.sun_timer += 1
elif self.type == PlantType.CHOMPER and self.eating_timer > 0:
self.eating_timer -= 1
# Update damage state
health_percentage = self.health / self.max_health
if health_percentage <= 0.3:
self.damaged_state = 2
elif health_percentage <= 0.6:
self.damaged_state = 1
def can_shoot(self):
if self.type == PlantType.PEASHOOTER:
return self.shoot_timer >= 90
elif self.type == PlantType.SNOW_PEA:
return self.shoot_timer >= 90
elif self.type == PlantType.ROSE_SHOOTER:
return self.shoot_timer >= 100 # Slightly slower fire rate
return False
def can_produce_sun(self):
return self.type == PlantType.SUNFLOWER and self.sun_timer >= 360
def can_eat_zombie(self):
return self.type == PlantType.CHOMPER and self.eating_timer <= 0
def start_eating(self):
self.eating_timer = 300 # 5 seconds cooldown
def reset_timer(self):
if self.type in [PlantType.PEASHOOTER, PlantType.SNOW_PEA, PlantType.ROSE_SHOOTER]:
self.shoot_timer = 0
elif self.type == PlantType.SUNFLOWER:
self.sun_timer = 0
def draw(self, screen):
# Draw shadow
shadow_surface = pygame.Surface((CELL_SIZE, CELL_SIZE//4), pygame.SRCALPHA)
pygame.draw.ellipse(shadow_surface, (0, 0, 0, 64), (0, 0, CELL_SIZE, CELL_SIZE//4))
screen.blit(shadow_surface, (self.rect.x, self.rect.y + CELL_SIZE - CELL_SIZE//8))
# Draw plant using the corresponding drawing function
if self.type in PLANT_DRAWINGS:
PLANT_DRAWINGS[self.type](screen, self.rect.x, self.rect.y, CELL_SIZE)
# Draw health bar when damaged
if self.health < self.max_health:
health_width = max(0, (self.rect.width * self.health) // self.max_health)
health_rect = pygame.Rect(self.rect.x, self.rect.y - 5, health_width, 3)
pygame.draw.rect(screen, (255, 0, 0), health_rect)
class Zombie:
def __init__(self, row, zombie_type):
self.x = WINDOW_WIDTH / CELL_SIZE
self.y = row
self.type = zombie_type
stats = ZOMBIE_STATS[zombie_type]
self.health = stats["health"]
self.max_health = stats["health"]
self.speed = stats["speed"]
self.damage = stats["damage"]
self.rect = pygame.Rect(
self.x * CELL_SIZE,
self.y * CELL_SIZE + TOP_MARGIN,
CELL_SIZE,
CELL_SIZE
)
self.eating = False
self.stun_timer = 0
self.frozen_timer = 0
self.frozen = False
self.intoxicated_timer = 0
self.intoxicated = False
self.animation_state = "walking"
# Special abilities for newspaper zombie
if self.type == ZombieType.NEWSPAPER:
self.has_newspaper = True
self.enraged = False
else:
self.has_newspaper = False
self.enraged = False
# Special abilities for dancing zombie
if self.type == ZombieType.DANCING:
self.summon_timer = 300
else:
self.summon_timer = 0
def move(self):
if not self.eating and self.stun_timer <= 0:
actual_speed = self.speed
if self.frozen:
actual_speed *= 0.5
if self.intoxicated:
actual_speed *= 0.3
if self.type == ZombieType.NEWSPAPER and self.enraged:
actual_speed *= 1.5
self.x -= actual_speed / FPS
self.rect.x = self.x * CELL_SIZE
if self.stun_timer > 0:
self.stun_timer -= 1
if self.frozen:
self.frozen_timer -= 1
if self.frozen_timer <= 0:
self.frozen = False
if self.intoxicated:
self.intoxicated_timer -= 1
if self.intoxicated_timer <= 0:
self.intoxicated = False
def intoxicate(self):
self.intoxicated = True
self.intoxicated_timer = 300
def take_damage(self, damage):
self.health -= damage
if self.type == ZombieType.NEWSPAPER and self.has_newspaper and self.health <= self.max_health * 0.5:
self.has_newspaper = False
self.enraged = True
self.speed *= 1.5
def freeze(self):
self.frozen = True
self.frozen_timer = 300
def draw(self, screen):
# Draw shadow
shadow_surface = pygame.Surface((CELL_SIZE, CELL_SIZE//3), pygame.SRCALPHA)
pygame.draw.ellipse(shadow_surface, (0, 0, 0, 64), (0, 0, CELL_SIZE, CELL_SIZE//3))
screen.blit(shadow_surface, (self.rect.x, self.rect.y + CELL_SIZE - CELL_SIZE//6))
# Draw zombie using the corresponding drawing function
if self.type in ZOMBIE_DRAWINGS:
ZOMBIE_DRAWINGS[self.type](screen, self.rect.x, self.rect.y, CELL_SIZE)
# Draw frozen effect
if self.frozen:
ice_surface = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
ice_surface.fill((150, 217, 255, 128))
screen.blit(ice_surface, self.rect)
# Draw intoxicated effect
if self.intoxicated:
love_surface = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
time = pygame.time.get_ticks()
for i in range(3):
heart_x = self.rect.x + CELL_SIZE//2 + math.cos(time * 0.003 + i * 2) * 15
heart_y = self.rect.y + CELL_SIZE//3 + math.sin(time * 0.003 + i * 2) * 10
# Draw heart shape
pygame.draw.circle(screen, (255, 192, 203, 200), (int(heart_x - 5), int(heart_y)), 5)
pygame.draw.circle(screen, (255, 192, 203, 200), (int(heart_x + 5), int(heart_y)), 5)
pygame.draw.polygon(screen, (255, 192, 203, 200), [
(heart_x, heart_y + 8),
(heart_x - 10, heart_y),
(heart_x + 10, heart_y)
])
# Health bar
health_width = max(0, (self.rect.width * self.health) // self.max_health)
health_rect = pygame.Rect(self.rect.x, self.rect.y - 10, health_width, 5)
pygame.draw.rect(screen, (255, 0, 0), health_rect)
class Projectile:
def __init__(self, x, y, damage=20, speed=5, freezing=False, is_rose=False):
self.x = (x + 0.5) * CELL_SIZE
self.y = y * CELL_SIZE + TOP_MARGIN + CELL_SIZE // 2
self.damage = damage
self.speed = speed
self.freezing = freezing
self.is_rose = is_rose
self.active = True
self.size = 10 # Set all projectiles to the same size
self.rect = pygame.Rect(self.x - self.size//2, self.y - self.size//2, self.size, self.size)
self.trail_positions = []
self.trail_lifetime = 15 if is_rose else 10
self.glow_offset = 0
self.rotation = 0
self.color = (255, 192, 203) if is_rose else ((0, 191, 255) if freezing else (0, 255, 0))
self.alpha = 255
def move(self):
# Store current position for trail
self.trail_positions.append((self.rect.x, self.rect.y))
if len(self.trail_positions) > self.trail_lifetime:
self.trail_positions.pop(0)
self.rect.x += self.speed
self.rotation += 15 # Rotate 15 degrees per frame
# Update glow effect
self.glow_offset = abs(math.sin(pygame.time.get_ticks() * 0.01)) * 2
if self.rect.x > WINDOW_WIDTH:
self.active = False
def draw(self, screen):
# Draw trail with rose petals or regular trail
for i, (x, y) in enumerate(self.trail_positions):
alpha = int(255 * (i / len(self.trail_positions)) * 0.5)
size = int(4 * (i / len(self.trail_positions))) # Same size for all trails
trail_surface = pygame.Surface((12, 12), pygame.SRCALPHA) # Same size for all trails
if self.is_rose:
# Draw rose petal trail
petal_color = (255, 192, 203, alpha) # Pink with alpha
# Draw multiple petals for a more detailed trail
for angle in range(0, 360, 72):
rad = math.radians(angle + self.rotation)
petal_x = 6 + math.cos(rad) * size # Center at 6 (half of 12)
petal_y = 6 + math.sin(rad) * size
pygame.draw.circle(trail_surface, petal_color, (int(petal_x), int(petal_y)), size)
else:
color = (0, 191, 255, alpha) if self.freezing else (0, 255, 0, alpha)
pygame.draw.circle(trail_surface, color, (6, 6), size)
screen.blit(trail_surface, (x - 6, y - 6))
# Draw main projectile
if self.is_rose:
# Draw rose projectile at same size as others
glow_surface = pygame.Surface((20, 20), pygame.SRCALPHA)
glow_radius = 8 + self.glow_offset
glow_color = (255, 192, 203, 64) # Pink with transparency
pygame.draw.circle(glow_surface, glow_color, (10, 10), glow_radius)
screen.blit(glow_surface, (self.rect.x - 10, self.rect.y - 10))
# Main projectile
pygame.draw.circle(screen, (255, 192, 203), (self.rect.x, self.rect.y), 6) # Same size as others
pygame.draw.circle(screen, (255, 105, 180), (self.rect.x, self.rect.y), 4) # Inner color
# Add highlight
highlight_pos = (self.rect.x - 2, self.rect.y - 2)
pygame.draw.circle(screen, (255, 255, 255, 180), highlight_pos, 2)
else:
# Draw regular projectile
glow_color = (0, 191, 255, 64) if self.freezing else (0, 255, 0, 64)
main_color = (0, 191, 255) if self.freezing else (0, 200, 0)
inner_color = (173, 216, 230) if self.freezing else (150, 255, 150)
glow_surface = pygame.Surface((20, 20), pygame.SRCALPHA)
glow_radius = 8 + self.glow_offset
pygame.draw.circle(glow_surface, glow_color, (10, 10), glow_radius)
screen.blit(glow_surface, (self.rect.x - 10, self.rect.y - 10))
pygame.draw.circle(screen, main_color, (self.rect.x, self.rect.y), 6)
pygame.draw.circle(screen, inner_color, (self.rect.x, self.rect.y), 4)
highlight_pos = (self.rect.x - 2, self.rect.y - 2)
pygame.draw.circle(screen, (255, 255, 255, 180), highlight_pos, 2)

View File

@ -0,0 +1,817 @@
import pygame
import sys
import random
import math
from constants import *
from entities import Plant, Zombie, Projectile
from sprites import PLANT_DRAWINGS, ZOMBIE_DRAWINGS
class Sun:
def __init__(self, x, y, from_sky=True):
self.x = x
self.y = y
self.initial_x = x
self.initial_y = y
self.value = 25
self.rect = pygame.Rect(x, y, 40, 40)
self.collected = False
self.from_sky = from_sky
self.fall_speed = 1.5
self.lifetime = 300 if from_sky else 450
self.hover_offset = 0
self.hover_speed = 0.03
self.hover_range = 5
self.fade_start = 60
self.alpha = 0
self.fade_in = 255
self.glow_offset = 0
self.size = 40
self.collect_speed = 5
self.collecting = False
def move(self):
if self.from_sky and self.y < self.initial_y + WINDOW_HEIGHT//3:
self.y += self.fall_speed
self.rect.y = self.y
self.hover_offset = math.sin(pygame.time.get_ticks() * self.hover_speed) * self.hover_range
self.rect.y = self.y + self.hover_offset
self.glow_offset = abs(math.sin(pygame.time.get_ticks() * 0.002)) * 5
if self.fade_in > 0:
self.alpha = min(255, self.alpha + 10)
self.fade_in -= 10
self.lifetime -= 1
if self.lifetime <= self.fade_start:
self.alpha = max(0, int(255 * (self.lifetime / self.fade_start)))
def draw(self, screen):
sun_surface = pygame.Surface((50, 50), pygame.SRCALPHA)
glow_radius = 25 + self.glow_offset
pygame.draw.circle(sun_surface, (255, 255, 100, int(self.alpha * 0.3)), (25, 25), glow_radius)
pygame.draw.circle(sun_surface, (255, 255, 0, self.alpha), (25, 25), 20)
pygame.draw.circle(sun_surface, (255, 255, 200, self.alpha), (25, 25), 15)
pygame.draw.circle(sun_surface, (255, 255, 255, int(self.alpha * 0.7)), (25, 25), 8)
screen.blit(sun_surface, (self.rect.x - 5, self.rect.y - 5))
class Game:
def __init__(self):
pygame.init()
pygame.mixer.init()
# Set window mode with fixed resolution
self.screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("植物大战僵尸 - ChatDev制作")
self.clock = pygame.time.Clock()
self.state = GameState.MENU
# Set scale factors based on window size
self.base_width = WINDOW_WIDTH
self.base_height = WINDOW_HEIGHT
self.scale_x = 1.0
self.scale_y = 1.0
# Load and play background music
self.load_music()
# Add Chinese font
try:
self.font = pygame.font.Font("/System/Library/Fonts/PingFang.ttc", 36)
self.large_font = pygame.font.Font("/System/Library/Fonts/PingFang.ttc", 74)
self.small_font = pygame.font.Font("/System/Library/Fonts/PingFang.ttc", 24)
except:
try:
self.font = pygame.font.Font("/System/Library/Fonts/STHeiti Light.ttc", 36)
self.large_font = pygame.font.Font("/System/Library/Fonts/STHeiti Light.ttc", 74)
self.small_font = pygame.font.Font("/System/Library/Fonts/STHeiti Light.ttc", 24)
except:
try:
self.font = pygame.font.Font("/System/Library/Fonts/Arial Unicode.ttf", 36)
self.large_font = pygame.font.Font("/System/Library/Fonts/Arial Unicode.ttf", 74)
self.small_font = pygame.font.Font("/System/Library/Fonts/Arial Unicode.ttf", 24)
except:
print("Warning: Could not load Chinese font, falling back to default font")
self.font = pygame.font.Font(None, 36)
self.large_font = pygame.font.Font(None, 74)
self.small_font = pygame.font.Font(None, 24)
self.reset_game()
def load_music(self):
try:
pygame.mixer.music.load("assets/music/bgm.mp3")
pygame.mixer.music.set_volume(0.5) # Set volume to 50%
pygame.mixer.music.play(-1) # -1 means loop indefinitely
except:
print("Warning: Could not load background music")
def reset_game(self):
self.plants = []
self.zombies = []
self.projectiles = []
self.suns = []
self.particles = []
self.sun_points = 2025
self.selected_plant = None
self.spawn_timer = 0
self.sun_spawn_timer = 0
self.wave_number = 1
self.wave_timer = 600
self.score = 0
self.game_over = False
def spawn_sun(self):
if self.sun_spawn_timer <= 0:
x = random.randint(100, WINDOW_WIDTH - 100)
self.suns.append(Sun(x, -40))
self.sun_spawn_timer = random.randint(300, 500)
self.sun_spawn_timer -= 1
def spawn_zombie(self):
if self.spawn_timer <= 0:
# Ensure minimum number of zombies (5) are present
if len(self.zombies) < 5 + self.wave_number:
# Spawn multiple zombies at once if below minimum
zombies_to_spawn = max(2, 5 + self.wave_number - len(self.zombies))
for _ in range(zombies_to_spawn):
row = random.randint(0, GRID_ROWS - 1)
# Zombie type selection based on wave number
zombie_types = [
ZombieType.NORMAL,
ZombieType.CONE,
ZombieType.BUCKET,
ZombieType.NEWSPAPER,
ZombieType.DANCING
]
zombie_type = random.choice(zombie_types)
self.zombies.append(Zombie(row, zombie_type))
else:
# Regular spawn for maintaining zombie presence
row = random.randint(0, GRID_ROWS - 1)
zombie_types = [
ZombieType.NORMAL,
ZombieType.CONE,
ZombieType.BUCKET,
ZombieType.NEWSPAPER,
ZombieType.DANCING
]
zombie_type = random.choice(zombie_types)
self.zombies.append(Zombie(row, zombie_type))
# Adjust spawn timer based on wave - make it faster
base_timer = max(100, 300 - (self.wave_number * 40)) # Reduced from 500 to 300
variation = random.randint(-30, 30) # Add some randomness
self.spawn_timer = base_timer + variation
self.spawn_timer -= 1
def handle_resize(self, event):
# Update screen size
width, height = event.size
self.screen = pygame.display.set_mode((width, height), pygame.RESIZABLE)
# Calculate new scale factors
self.scale_x = width / self.base_width
self.scale_y = height / self.base_height
# Update font sizes based on scale
scale = min(self.scale_x, self.scale_y)
try:
self.font = pygame.font.Font("/System/Library/Fonts/PingFang.ttc", int(36 * scale))
self.large_font = pygame.font.Font("/System/Library/Fonts/PingFang.ttc", int(74 * scale))
self.small_font = pygame.font.Font("/System/Library/Fonts/PingFang.ttc", int(24 * scale))
except:
try:
self.font = pygame.font.Font("/System/Library/Fonts/STHeiti Light.ttc", int(36 * scale))
self.large_font = pygame.font.Font("/System/Library/Fonts/STHeiti Light.ttc", int(74 * scale))
self.small_font = pygame.font.Font("/System/Library/Fonts/STHeiti Light.ttc", int(24 * scale))
except:
try:
self.font = pygame.font.Font("/System/Library/Fonts/Arial Unicode.ttf", int(36 * scale))
self.large_font = pygame.font.Font("/System/Library/Fonts/Arial Unicode.ttf", int(74 * scale))
self.small_font = pygame.font.Font("/System/Library/Fonts/Arial Unicode.ttf", int(24 * scale))
except:
self.font = pygame.font.Font(None, int(36 * scale))
self.large_font = pygame.font.Font(None, int(74 * scale))
self.small_font = pygame.font.Font(None, int(24 * scale))
def get_scaled_rect(self, rect):
# Helper function to scale rectangles
return pygame.Rect(
rect.x * self.scale_x,
rect.y * self.scale_y,
rect.width * self.scale_x,
rect.height * self.scale_y
)
def get_real_pos(self, pos):
# Convert screen position to game logic position
return (pos[0] / self.scale_x, pos[1] / self.scale_y)
def handle_click(self, pos):
# Convert screen position to game logic position
x, y = self.get_real_pos(pos)
# Check if clicking on a sun
for sun in self.suns[:]:
if sun.rect.collidepoint(x, y) and not sun.collected:
self.sun_points += sun.value
self.suns.remove(sun)
continue
# Plant placement
# Calculate grid position
grid_x = int(x // CELL_SIZE)
grid_y = int((y - TOP_MARGIN) // CELL_SIZE)
# Check if click is within the planting area
if 0 <= grid_x < GRID_COLS and 0 <= grid_y < GRID_ROWS:
# Check if there's already a plant there
plant_exists = any(p.x == grid_x and p.y == grid_y for p in self.plants)
if not plant_exists and self.selected_plant:
cost = PLANT_STATS[self.selected_plant]["cost"]
if self.sun_points >= cost:
self.plants.append(Plant(grid_x, grid_y, self.selected_plant))
self.sun_points -= cost
self.selected_plant = None
def update_plants(self):
for plant in self.plants:
plant.update()
if plant.can_shoot():
if plant.type == PlantType.SNOW_PEA:
self.projectiles.append(Projectile(plant.x, plant.y, freezing=True))
elif plant.type == PlantType.ROSE_SHOOTER:
# Shoot in current lane and adjacent lanes
lanes = [plant.y] # Current lane
if plant.y > 0: # Add lane above if exists
lanes.append(plant.y - 1)
if plant.y < GRID_ROWS - 1: # Add lane below if exists
lanes.append(plant.y + 1)
for lane in lanes:
# Create rose projectile with special properties
proj = Projectile(plant.x, lane, damage=20, speed=6, is_rose=True)
self.projectiles.append(proj)
else:
self.projectiles.append(Projectile(plant.x, plant.y))
plant.reset_timer()
elif plant.can_produce_sun():
self.suns.append(Sun(plant.rect.x, plant.rect.y, from_sky=False))
plant.reset_timer()
elif plant.can_eat_zombie():
# Check for zombies in range for Chomper
for zombie in self.zombies[:]:
if zombie.y == plant.y and abs(zombie.x - plant.x) <= 1:
self.zombies.remove(zombie)
plant.start_eating()
self.score += 100
break
def update_combat(self):
# Update projectiles and check collisions
for projectile in self.projectiles[:]:
if not projectile.active:
self.projectiles.remove(projectile)
continue
projectile.move()
for zombie in self.zombies[:]:
if projectile.rect.colliderect(zombie.rect):
# Create impact effect based on projectile type
if projectile.is_rose:
# Rose shooter effect (pink petals)
color = (255, 192, 203) # Pink for rose
for _ in range(12):
angle = random.uniform(0, 2 * math.pi)
speed = random.uniform(3, 6)
size = random.uniform(4, 7)
self.particles.append({
'x': projectile.rect.x,
'y': projectile.rect.y,
'dx': math.cos(angle) * speed,
'dy': math.sin(angle) * speed,
'lifetime': 45,
'color': (255, 192, 203),
'size': size,
'rotation': random.uniform(0, 360),
'is_petal': True,
'shape': 'petal'
})
# Add sparkle particle
self.particles.append({
'x': projectile.rect.x,
'y': projectile.rect.y,
'dx': math.cos(angle) * (speed * 0.7),
'dy': math.sin(angle) * (speed * 0.7),
'lifetime': 30,
'color': (255, 255, 255),
'size': size * 0.5,
'is_petal': False
})
zombie.intoxicate()
elif projectile.freezing:
# Snow pea effect (ice crystals)
color = (0, 191, 255) # Ice blue
for _ in range(12):
angle = random.uniform(0, 2 * math.pi)
speed = random.uniform(3, 6)
size = random.uniform(4, 7)
self.particles.append({
'x': projectile.rect.x,
'y': projectile.rect.y,
'dx': math.cos(angle) * speed,
'dy': math.sin(angle) * speed,
'lifetime': 40,
'color': (0, 191, 255),
'size': size,
'rotation': random.uniform(0, 360),
'is_petal': True,
'shape': 'snowflake'
})
# Add sparkle particle
self.particles.append({
'x': projectile.rect.x,
'y': projectile.rect.y,
'dx': math.cos(angle) * (speed * 0.7),
'dy': math.sin(angle) * (speed * 0.7),
'lifetime': 25,
'color': (255, 255, 255),
'size': size * 0.4,
'is_petal': False
})
zombie.freeze()
else:
# Regular peashooter effect (leaves and splashes)
color = (0, 255, 0) # Green
for _ in range(12):
angle = random.uniform(0, 2 * math.pi)
speed = random.uniform(3, 6)
size = random.uniform(4, 7)
self.particles.append({
'x': projectile.rect.x,
'y': projectile.rect.y,
'dx': math.cos(angle) * speed,
'dy': math.sin(angle) * speed,
'lifetime': 35,
'color': (0, 200, 0),
'size': size,
'rotation': random.uniform(0, 360),
'is_petal': True,
'shape': 'leaf'
})
# Add splash particle
self.particles.append({
'x': projectile.rect.x,
'y': projectile.rect.y,
'dx': math.cos(angle) * (speed * 0.8),
'dy': math.sin(angle) * (speed * 0.8),
'lifetime': 20,
'color': (150, 255, 150),
'size': size * 0.6,
'is_petal': False
})
zombie.take_damage(projectile.damage)
if projectile.freezing:
zombie.freeze()
zombie.stun_timer = 2
if zombie.health <= 0:
# Add death particles
for _ in range(12):
angle = random.uniform(0, 2 * math.pi)
speed = random.uniform(3, 6)
self.particles.append({
'x': zombie.rect.x + CELL_SIZE//2,
'y': zombie.rect.y + CELL_SIZE//2,
'dx': math.cos(angle) * speed,
'dy': math.sin(angle) * speed,
'lifetime': 30,
'color': (139, 69, 19), # Brown for zombie parts
'size': random.uniform(3, 6)
})
self.zombies.remove(zombie)
self.score += 100
if projectile in self.projectiles:
self.projectiles.remove(projectile)
break
# Update particles
for particle in self.particles[:]:
particle['x'] += particle['dx']
particle['y'] += particle['dy']
particle['lifetime'] -= 1
if particle['lifetime'] <= 0:
self.particles.remove(particle)
# Check zombie-plant interactions
for zombie in self.zombies:
for plant in self.plants[:]:
if zombie.rect.colliderect(plant.rect):
zombie.eating = True
plant.health -= zombie.damage
if plant.health <= 0:
# Add plant death particles
for _ in range(8):
angle = random.uniform(0, 2 * math.pi)
speed = random.uniform(2, 4)
self.particles.append({
'x': plant.rect.x + CELL_SIZE//2,
'y': plant.rect.y + CELL_SIZE//2,
'dx': math.cos(angle) * speed,
'dy': math.sin(angle) * speed,
'lifetime': 25,
'color': (0, 100, 0), # Dark green for plant parts
'size': random.uniform(2, 5)
})
self.plants.remove(plant)
zombie.eating = False
break
else:
zombie.eating = False
def draw_lawn(self):
# Create a surface for the lawn at base size
lawn_surface = pygame.Surface((self.base_width, self.base_height))
lawn_surface.fill(LAWN_GREEN)
# Draw grid with better visuals
for row in range(GRID_ROWS):
for col in range(GRID_COLS):
rect = pygame.Rect(
col * CELL_SIZE,
row * CELL_SIZE + TOP_MARGIN,
CELL_SIZE,
CELL_SIZE
)
if (row + col) % 2 == 0:
pygame.draw.rect(lawn_surface, (115, 235, 0), rect)
pygame.draw.rect(lawn_surface, (100, 200, 0), rect, 1)
# Scale and blit to screen
scaled_surface = pygame.transform.scale(lawn_surface, self.screen.get_size())
self.screen.blit(scaled_surface, (0, 0))
def draw_plant_menu(self):
menu_height = 100 * self.scale_y
menu_surface = pygame.Surface((self.screen.get_width(), menu_height), pygame.SRCALPHA)
pygame.draw.rect(menu_surface, (139, 69, 19, 200), (0, 0, self.screen.get_width(), menu_height))
# Get mouse position for hover effect
mouse_x, mouse_y = pygame.mouse.get_pos()
menu_y = mouse_y - (self.screen.get_height() - menu_height)
# Plant cards
cards = [
(PlantType.SUNFLOWER, YELLOW, 50),
(PlantType.PEASHOOTER, GREEN, 100),
(PlantType.ROSE_SHOOTER, (255, 192, 203), 125), # Pink color for rose
(PlantType.CHOMPER, (148, 0, 211), 150),
(PlantType.SNOW_PEA, (0, 191, 255), 175)
]
card_width = 70 * self.scale_x
card_height = 80 * self.scale_y
card_spacing = 90 * self.scale_x
for i, (plant_type, color, cost) in enumerate(cards):
card_x = 10 * self.scale_x + i * card_spacing
card_rect = pygame.Rect(card_x, 10 * self.scale_y, card_width, card_height)
# Check if card is hovered or selected
is_hovered = (0 <= menu_y <= card_height + 20 * self.scale_y and
card_x <= mouse_x <= card_x + card_width)
is_selected = self.selected_plant == plant_type
# Draw card background with hover/selected effect
if is_selected:
# Glowing effect for selected card
glow_surface = pygame.Surface((card_width + 4, card_height + 4), pygame.SRCALPHA)
pygame.draw.rect(glow_surface, (*PLANT_STATS[plant_type]["color"], 128),
(0, 0, card_width + 4, card_height + 4))
menu_surface.blit(glow_surface, (card_rect.x - 2, card_rect.y - 2))
pygame.draw.rect(menu_surface, WHITE,
(card_rect.x - 2, card_rect.y - 2, card_width + 4, card_height + 4),
max(1, int(2 * self.scale_x)))
elif is_hovered:
# Hover effect
pygame.draw.rect(menu_surface, (255, 255, 255, 30), card_rect)
pygame.draw.rect(menu_surface, color, card_rect)
# Draw plant image on card
if plant_type in PLANT_DRAWINGS:
# Create a smaller surface for the plant
plant_surface = pygame.Surface((CELL_SIZE, CELL_SIZE), pygame.SRCALPHA)
PLANT_DRAWINGS[plant_type](plant_surface, 0, 0, CELL_SIZE)
# Scale it down to fit the card
scaled_size = (int(50 * self.scale_x), int(50 * self.scale_y))
scaled_surface = pygame.transform.scale(plant_surface, scaled_size)
menu_surface.blit(scaled_surface,
(card_rect.x + 10 * self.scale_x,
card_rect.y + 5 * self.scale_y))
# Cost indicator with sun icon
sun_size = 10 * min(self.scale_x, self.scale_y)
pygame.draw.circle(menu_surface, YELLOW,
(card_rect.x + sun_size, card_rect.bottom - sun_size),
sun_size)
cost_text = self.small_font.render(str(cost), True, BLACK)
menu_surface.blit(cost_text,
(card_rect.x + sun_size * 2,
card_rect.bottom - sun_size * 1.5))
# Gray out if can't afford
if self.sun_points < cost:
gray_surface = pygame.Surface((card_width, card_height), pygame.SRCALPHA)
pygame.draw.rect(gray_surface, (128, 128, 128, 180),
(0, 0, card_width, card_height))
menu_surface.blit(gray_surface, card_rect)
self.screen.blit(menu_surface, (0, self.screen.get_height() - menu_height))
def draw_hud(self):
# Sun points
sun_size = 30 * min(self.scale_x, self.scale_y)
sun_icon = pygame.Surface((sun_size, sun_size), pygame.SRCALPHA)
pygame.draw.circle(sun_icon, YELLOW, (sun_size/2, sun_size/2), sun_size/2)
self.screen.blit(sun_icon, (10 * self.scale_x, 10 * self.scale_y))
sun_text = self.font.render(str(self.sun_points), True, BLACK)
self.screen.blit(sun_text, (45 * self.scale_x, 15 * self.scale_y))
# Wave number
wave_text = self.font.render(f"{self.wave_number} 波僵尸", True, BLACK)
self.screen.blit(wave_text,
(self.screen.get_width() - 200 * self.scale_x,
15 * self.scale_y))
# Score
score_text = self.font.render(f"得分: {self.score}", True, BLACK)
self.screen.blit(score_text,
(self.screen.get_width()//2 - score_text.get_width()//2,
15 * self.scale_y))
def draw_watermark(self):
watermark = self.small_font.render("ChatDev制作", True, (0, 0, 0, 128))
watermark.set_alpha(128) # Make it semi-transparent
self.screen.blit(watermark,
(self.screen.get_width() - watermark.get_width() - 10,
self.screen.get_height() - watermark.get_height() - 10))
def run(self):
while True:
if self.state == GameState.MENU:
self.run_menu()
elif self.state == GameState.PLAYING:
self.run_game()
elif self.state == GameState.GAME_OVER:
self.run_game_over()
def run_menu(self):
while self.state == GameState.MENU:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # Left click
# Start game button area
button_rect = pygame.Rect(
self.screen.get_width()//2 - 100 * self.scale_x,
self.screen.get_height()//2,
200 * self.scale_x,
50 * self.scale_y
)
if button_rect.collidepoint(event.pos):
self.state = GameState.PLAYING
self.reset_game()
elif event.type == pygame.VIDEORESIZE:
self.handle_resize(event)
# Draw menu
self.screen.fill(LAWN_GREEN)
# Draw title
title = self.large_font.render("植物大战僵尸", True, BLACK)
self.screen.blit(title,
(self.screen.get_width()//2 - title.get_width()//2,
self.screen.get_height()//4))
# Draw start button
button_rect = pygame.Rect(
self.screen.get_width()//2 - 100 * self.scale_x,
self.screen.get_height()//2,
200 * self.scale_x,
50 * self.scale_y
)
pygame.draw.rect(self.screen, GREEN, button_rect)
pygame.draw.rect(self.screen, BLACK, button_rect, 2)
start_text = self.font.render("开始游戏", True, BLACK)
self.screen.blit(start_text,
(self.screen.get_width()//2 - start_text.get_width()//2,
self.screen.get_height()//2 + 5 * self.scale_y))
self.draw_watermark()
pygame.display.flip()
self.clock.tick(FPS)
def run_game(self):
while self.state == GameState.PLAYING and not self.game_over:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.VIDEORESIZE:
self.handle_resize(event)
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
real_pos = self.get_real_pos(mouse_pos)
if self.base_height - 100 <= real_pos[1] <= self.base_height:
menu_y = real_pos[1] - (self.base_height - 100)
if 10 <= menu_y <= 90:
card_x = int((real_pos[0] - 10) // 90) # Convert to integer
if 0 <= card_x <= 4:
plant_types = [
PlantType.SUNFLOWER,
PlantType.PEASHOOTER,
PlantType.ROSE_SHOOTER,
PlantType.CHOMPER,
PlantType.SNOW_PEA
]
if card_x < len(plant_types):
plant_type = plant_types[card_x]
if self.sun_points >= PLANT_STATS[plant_type]["cost"]:
self.selected_plant = plant_type
else:
self.handle_click(mouse_pos)
# Update game state
self.spawn_zombie()
self.spawn_sun()
self.update_plants()
self.update_combat()
# Wave management
self.wave_timer -= 1
if self.wave_timer <= 0:
self.wave_number += 1
self.wave_timer = 600
# Move entities
for zombie in self.zombies:
zombie.move()
if zombie.x <= 0:
self.game_over = True
self.state = GameState.GAME_OVER
for sun in self.suns[:]:
sun.move()
if sun.lifetime <= 0:
self.suns.remove(sun)
# Draw everything
self.draw_lawn()
# Create a game surface at base size and draw everything on it
game_surface = pygame.Surface((self.base_width, self.base_height), pygame.SRCALPHA)
for plant in self.plants:
plant.draw(game_surface)
for zombie in self.zombies:
zombie.draw(game_surface)
for projectile in self.projectiles:
projectile.draw(game_surface)
for sun in self.suns:
sun.draw(game_surface)
# Draw particles
for particle in self.particles:
if particle.get('is_petal', False):
# Draw shaped particles based on type
shape = particle.get('shape', 'petal')
particle_surface = pygame.Surface((particle['size'] * 2, particle['size'] * 2), pygame.SRCALPHA)
center = (particle['size'], particle['size'])
if shape == 'petal':
# Draw rose petal shape
for angle in range(0, 360, 72):
rad = math.radians(angle + particle['rotation'])
petal_x = center[0] + math.cos(rad) * particle['size']
petal_y = center[1] + math.sin(rad) * particle['size']
pygame.draw.circle(particle_surface, particle['color'],
(int(petal_x), int(petal_y)),
int(particle['size'] * 0.6))
elif shape == 'snowflake':
# Draw snowflake shape
for angle in range(0, 360, 45):
rad = math.radians(angle + particle['rotation'])
# Draw main line
end_x = center[0] + math.cos(rad) * particle['size']
end_y = center[1] + math.sin(rad) * particle['size']
pygame.draw.line(particle_surface, particle['color'],
center, (int(end_x), int(end_y)), 2)
# Draw side branches
branch_length = particle['size'] * 0.5
mid_x = center[0] + math.cos(rad) * particle['size'] * 0.6
mid_y = center[1] + math.sin(rad) * particle['size'] * 0.6
side_angle1 = rad + math.pi / 4
side_angle2 = rad - math.pi / 4
pygame.draw.line(particle_surface, particle['color'],
(int(mid_x), int(mid_y)),
(int(mid_x + math.cos(side_angle1) * branch_length),
int(mid_y + math.sin(side_angle1) * branch_length)), 2)
pygame.draw.line(particle_surface, particle['color'],
(int(mid_x), int(mid_y)),
(int(mid_x + math.cos(side_angle2) * branch_length),
int(mid_y + math.sin(side_angle2) * branch_length)), 2)
elif shape == 'leaf':
# Draw leaf shape
points = []
leaf_length = particle['size'] * 1.5
leaf_width = particle['size'] * 0.8
rad = math.radians(particle['rotation'])
# Create leaf shape points
for t in range(0, 360, 10):
t_rad = math.radians(t)
x = center[0] + math.cos(rad) * leaf_length * math.cos(t_rad) - \
math.sin(rad) * leaf_width * math.sin(t_rad)
y = center[1] + math.sin(rad) * leaf_length * math.cos(t_rad) + \
math.cos(rad) * leaf_width * math.sin(t_rad)
points.append((int(x), int(y)))
if len(points) > 2:
pygame.draw.polygon(particle_surface, particle['color'], points)
# Draw leaf vein
vein_start = center
vein_end = (int(center[0] + math.cos(rad) * leaf_length),
int(center[1] + math.sin(rad) * leaf_length))
pygame.draw.line(particle_surface, (0, 150, 0),
vein_start, vein_end, 1)
# Add fade out effect
alpha = int(255 * (particle['lifetime'] / 45))
particle_surface.set_alpha(alpha)
game_surface.blit(particle_surface,
(particle['x'] - particle['size'],
particle['y'] - particle['size']))
else:
# Draw regular circular particles
alpha = int(255 * (particle['lifetime'] / 30))
color = (*particle['color'][:3], alpha)
pygame.draw.circle(game_surface, color,
(int(particle['x']), int(particle['y'])),
int(particle['size']))
# Scale and blit the game surface
scaled_surface = pygame.transform.scale(game_surface, self.screen.get_size())
self.screen.blit(scaled_surface, (0, 0))
self.draw_plant_menu()
self.draw_hud()
self.draw_watermark()
pygame.display.flip()
self.clock.tick(FPS)
def run_game_over(self):
while self.state == GameState.GAME_OVER:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1: # Left click
self.state = GameState.MENU
elif event.type == pygame.VIDEORESIZE:
self.handle_resize(event)
# Draw game over screen
self.screen.fill((0, 0, 0)) # Black background
# Draw game over text
game_over_text = self.large_font.render("游戏结束", True, RED)
score_text = self.font.render(f"最终得分: {self.score}", True, WHITE)
self.screen.blit(game_over_text,
(self.screen.get_width()//2 - game_over_text.get_width()//2,
self.screen.get_height()//3))
self.screen.blit(score_text,
(self.screen.get_width()//2 - score_text.get_width()//2,
self.screen.get_height()//2))
self.draw_watermark()
pygame.display.flip()
self.clock.tick(FPS)
if __name__ == "__main__":
game = Game()
game.run()

View File

@ -0,0 +1 @@
pygame==2.5.2

View File

@ -0,0 +1,561 @@
import pygame
import math
from constants import *
def draw_sunflower(surface, x, y, size):
# Get animation offset based on time
time = pygame.time.get_ticks()
sway = math.sin(time * 0.003) * 3
petal_spin = time * 0.002
# Draw stem with swaying animation
stem_points = [
(x + size//2 + sway, y + size*3//4),
(x + size//2, y + size*7//8),
(x + size//2, y + size)
]
pygame.draw.lines(surface, GREEN, False, stem_points, 3)
# Draw leaves
leaf_color = (34, 139, 34) # Forest green
leaf_points = [
(x + size//2, y + size*3//4),
(x + size//3, y + size*7//8),
(x + size//2, y + size*13//16)
]
pygame.draw.polygon(surface, leaf_color, leaf_points)
leaf_points = [
(x + size//2, y + size*3//4),
(x + size*2//3, y + size*7//8),
(x + size//2, y + size*13//16)
]
pygame.draw.polygon(surface, leaf_color, leaf_points)
# Draw center with gradient
center_x, center_y = x + size//2 + sway, y + size//2
pygame.draw.circle(surface, (160, 82, 45), (center_x, center_y), size//4) # Dark brown
pygame.draw.circle(surface, (139, 69, 19), (center_x, center_y), size//5) # Medium brown
pygame.draw.circle(surface, (101, 67, 33), (center_x, center_y), size//6) # Light brown
# Draw petals with rotation animation
petal_colors = [(255, 218, 0), (255, 200, 0), (255, 182, 0)] # Different yellow shades
for i, angle in enumerate(range(0, 360, 45)):
rad = math.radians(angle + petal_spin)
petal_x = center_x + math.cos(rad) * size//3
petal_y = center_y + math.sin(rad) * size//3
# Draw each petal with multiple layers for depth
pygame.draw.circle(surface, petal_colors[i % 3], (int(petal_x), int(petal_y)), size//6)
pygame.draw.circle(surface, petal_colors[(i + 1) % 3], (int(petal_x), int(petal_y)), size//8)
def draw_peashooter(surface, x, y, size):
# Get animation offset
time = pygame.time.get_ticks()
sway = math.sin(time * 0.003) * 3
# Draw stem with swaying animation
stem_points = [
(x + size//2 + sway, y + size*2//3),
(x + size//2, y + size*7//8),
(x + size//2, y + size)
]
pygame.draw.lines(surface, GREEN, False, stem_points, 3)
# Draw leaves
leaf_color = (34, 139, 34)
leaf_points = [
(x + size//2, y + size*3//4),
(x + size//3, y + size*7//8),
(x + size//2, y + size*13//16)
]
pygame.draw.polygon(surface, leaf_color, leaf_points)
# Draw head with gradient
head_x = x + size//2 + sway
head_color = (0, 200, 0)
pygame.draw.ellipse(surface, head_color, (head_x - size//4, y + size//4, size//2, size//2))
pygame.draw.ellipse(surface, (0, 180, 0), (head_x - size//5, y + size//3, size//2.5, size//2.5))
# Draw shooter with highlight
shooter_x = head_x + size//4
shooter_color = (0, 100, 0)
pygame.draw.circle(surface, shooter_color, (int(shooter_x), y + size//2), size//7)
pygame.draw.circle(surface, (0, 150, 0), (int(shooter_x), y + size//2), size//10)
# Draw eyes
eye_color = (0, 0, 0)
eye_x = head_x - size//8
pygame.draw.ellipse(surface, eye_color, (eye_x, y + size//3, size//10, size//8))
pygame.draw.ellipse(surface, eye_color, (eye_x + size//6, y + size//3, size//10, size//8))
def draw_wallnut(surface, x, y, size):
# Get animation offset
time = pygame.time.get_ticks()
wobble = math.sin(time * 0.004) * 2
# Draw main body with gradient and texture
nut_colors = [(139, 69, 19), (160, 82, 45), (205, 133, 63)]
for i, color in enumerate(nut_colors):
offset = i * 4
pygame.draw.ellipse(surface, color,
(x + size//6 + offset + wobble,
y + size//6 + offset,
size*2//3 - offset*2,
size*2//3 - offset*2))
# Draw crack details
crack_color = (101, 67, 33)
crack_points = [
(x + size//2, y + size//4),
(x + size*2//3, y + size//3),
(x + size//2, y + size//2)
]
pygame.draw.lines(surface, crack_color, False, crack_points, 2)
# Draw face with expression
eye_color = BLACK
blink = (time % 3000) < 200 # Blink every 3 seconds
if not blink:
# Draw eyes
pygame.draw.ellipse(surface, eye_color, (x + size//3, y + size//3, size//6, size//6))
pygame.draw.ellipse(surface, eye_color, (x + size//2, y + size//3, size//6, size//6))
# Draw white highlights in eyes
pygame.draw.circle(surface, WHITE, (x + size//3 + size//12, y + size//3 + size//12), size//20)
pygame.draw.circle(surface, WHITE, (x + size//2 + size//12, y + size//3 + size//12), size//20)
else:
# Draw closed eyes
pygame.draw.line(surface, eye_color, (x + size//3, y + size//3), (x + size//3 + size//6, y + size//3), 2)
pygame.draw.line(surface, eye_color, (x + size//2, y + size//3), (x + size//2 + size//6, y + size//3), 2)
# Draw smile that changes with wobble
smile_rect = pygame.Rect(x + size//3 + wobble, y + size//2, size//3, size//6)
pygame.draw.arc(surface, eye_color, smile_rect, 0, math.pi, 2)
def draw_chomper(surface, x, y, size):
# Get animation offset
time = pygame.time.get_ticks()
chomp = abs(math.sin(time * 0.004)) * size//4
sway = math.sin(time * 0.003) * 3
# Draw stem with swaying animation
stem_points = [
(x + size//2 + sway, y + size*2//3),
(x + size//2, y + size*7//8),
(x + size//2, y + size)
]
pygame.draw.lines(surface, GREEN, False, stem_points, 4)
# Draw head
head_color = (148, 0, 211) # Purple
head_x = x + size//2 + sway
# Draw back of mouth
pygame.draw.ellipse(surface, (101, 0, 148),
(head_x - size//3, y + size//4, size*2//3, size//2))
# Draw tongue
tongue_color = (255, 105, 180)
tongue_points = [
(head_x, y + size//2),
(head_x - size//4, y + size//2 + size//4),
(head_x + size//4, y + size//2 + size//4)
]
pygame.draw.polygon(surface, tongue_color, tongue_points)
# Draw mouth (upper and lower jaw)
jaw_points_upper = [
(head_x - size//3, y + size//3),
(head_x + size//3, y + size//3),
(head_x + size//2, y + size//2),
(head_x - size//2, y + size//2)
]
jaw_points_lower = [
(head_x - size//3, y + size//2 + chomp),
(head_x + size//3, y + size//2 + chomp),
(head_x + size//2, y + size*2//3 + chomp),
(head_x - size//2, y + size*2//3 + chomp)
]
pygame.draw.polygon(surface, head_color, jaw_points_upper)
pygame.draw.polygon(surface, head_color, jaw_points_lower)
# Draw teeth
teeth_color = WHITE
tooth_width = size//8
for tooth_x in range(int(head_x - size//3), int(head_x + size//3), tooth_width):
# Upper teeth
pygame.draw.polygon(surface, teeth_color, [
(tooth_x, y + size//2),
(tooth_x + tooth_width//2, y + size//2 - size//8),
(tooth_x + tooth_width, y + size//2)
])
# Lower teeth
pygame.draw.polygon(surface, teeth_color, [
(tooth_x, y + size//2 + chomp),
(tooth_x + tooth_width//2, y + size//2 + size//8 + chomp),
(tooth_x + tooth_width, y + size//2 + chomp)
])
def draw_snow_pea(surface, x, y, size):
# Get animation offset
time = pygame.time.get_ticks()
sway = math.sin(time * 0.003) * 3
ice_spin = time * 0.003
# Draw stem with swaying animation
stem_points = [
(x + size//2 + sway, y + size*2//3),
(x + size//2, y + size*7//8),
(x + size//2, y + size)
]
pygame.draw.lines(surface, GREEN, False, stem_points, 3)
# Draw leaves with frost effect
leaf_color = (150, 200, 150)
leaf_points = [
(x + size//2, y + size*3//4),
(x + size//3, y + size*7//8),
(x + size//2, y + size*13//16)
]
pygame.draw.polygon(surface, leaf_color, leaf_points)
# Draw head with ice effect
head_x = x + size//2 + sway
head_colors = [(0, 191, 255), (135, 206, 235), (176, 224, 230)]
for i, color in enumerate(head_colors):
offset = i * 3
pygame.draw.ellipse(surface, color,
(head_x - size//4 + offset,
y + size//4 + offset,
size//2 - offset*2,
size//2 - offset*2))
# Draw ice crystals with rotation
crystal_color = (200, 232, 255)
for i in range(4):
angle = ice_spin + i * (math.pi/2)
crystal_x = head_x + math.cos(angle) * size//3
crystal_y = y + size//2 + math.sin(angle) * size//3
crystal_points = [
(crystal_x, crystal_y - size//8),
(crystal_x + size//8, crystal_y),
(crystal_x, crystal_y + size//8),
(crystal_x - size//8, crystal_y)
]
pygame.draw.polygon(surface, crystal_color, crystal_points)
# Draw frost particles
for i in range(3):
particle_x = head_x + math.cos(time * 0.001 + i * 2) * size//4
particle_y = y + size//2 + math.sin(time * 0.001 + i * 2) * size//4
pygame.draw.circle(surface, (255, 255, 255, 128),
(int(particle_x), int(particle_y)), size//16)
def draw_normal_zombie(surface, x, y, size):
# Get animation offset
time = pygame.time.get_ticks()
wobble = math.sin(time * 0.004) * 3
# Draw shadow
shadow_surface = pygame.Surface((size*2//3, size//4), pygame.SRCALPHA)
pygame.draw.ellipse(shadow_surface, (0, 0, 0, 64), (0, 0, size*2//3, size//4))
surface.blit(shadow_surface, (x + size//6, y + size - size//8))
# Draw legs with walking animation
leg_color = (100, 100, 100)
leg_offset = abs(math.sin(time * 0.004)) * 5
pygame.draw.line(surface, leg_color,
(x + size//2, y + size*2//3),
(x + size//3, y + size - leg_offset), 4)
pygame.draw.line(surface, leg_color,
(x + size//2, y + size*2//3),
(x + size*2//3, y + size - leg_offset), 4)
# Draw tattered clothes
clothes_color = (50, 50, 50)
clothes_points = [
(x + size//3, y + size//2),
(x + size*2//3, y + size//2),
(x + size*2//3, y + size*3//4),
(x + size//3, y + size*3//4)
]
pygame.draw.polygon(surface, clothes_color, clothes_points)
# Draw arms with swaying animation
arm_color = (100, 100, 100)
arm_sway = math.sin(time * 0.004) * 10
pygame.draw.line(surface, arm_color,
(x + size//2, y + size//2),
(x + size//4 + arm_sway, y + size*2//3), 4)
pygame.draw.line(surface, arm_color,
(x + size//2, y + size//2),
(x + size*3//4 + arm_sway, y + size*2//3), 4)
# Draw body with details
body_color = (169, 169, 169)
pygame.draw.ellipse(surface, body_color,
(x + size//3 + wobble, y + size//3, size//3, size//2))
# Draw head with details
head_color = (169, 169, 169)
pygame.draw.circle(surface, head_color,
(int(x + size//2 + wobble), int(y + size//3)), size//4)
# Draw facial features
eye_color = (255, 0, 0) # Red eyes
pygame.draw.circle(surface, eye_color,
(int(x + size//2 - size//8 + wobble), int(y + size//3)), size//12)
pygame.draw.circle(surface, eye_color,
(int(x + size//2 + size//8 + wobble), int(y + size//3)), size//12)
# Draw mouth
mouth_color = (100, 0, 0)
mouth_points = [
(x + size//2 - size//6 + wobble, y + size//3 + size//6),
(x + size//2 + size//6 + wobble, y + size//3 + size//6),
(x + size//2 + wobble, y + size//3 + size//4)
]
pygame.draw.polygon(surface, mouth_color, mouth_points)
def draw_cone_zombie(surface, x, y, size):
# Draw base zombie
draw_normal_zombie(surface, x, y, size)
# Get animation offset
time = pygame.time.get_ticks()
wobble = math.sin(time * 0.004) * 3
# Draw cone with details and shading
cone_colors = [(139, 69, 19), (160, 82, 45), (205, 133, 63)] # Different shades of brown
for i, color in enumerate(cone_colors):
offset = i * 2
points = [
(x + size//2 + wobble, y - size//6 + offset),
(x + size//3 + offset, y + size//3),
(x + size*2//3 - offset, y + size//3)
]
pygame.draw.polygon(surface, color, points)
# Draw cone damage (dents and scratches)
scratch_color = (101, 67, 33)
scratch_points = [
(x + size//2 - size//8 + wobble, y + size//6),
(x + size//2 + size//8 + wobble, y + size//4)
]
pygame.draw.lines(surface, scratch_color, False, scratch_points, 2)
def draw_bucket_zombie(surface, x, y, size):
# Draw base zombie
draw_normal_zombie(surface, x, y, size)
# Get animation offset
time = pygame.time.get_ticks()
wobble = math.sin(time * 0.004) * 3
# Draw bucket with metallic effect
bucket_colors = [(192, 192, 192), (169, 169, 169), (128, 128, 128)]
for i, color in enumerate(bucket_colors):
offset = i * 2
pygame.draw.rect(surface, color,
(x + size//4 + offset + wobble,
y - size//6 + offset,
size//2 - offset*2,
size//3))
# Draw bucket rim
rim_color = (211, 211, 211)
pygame.draw.rect(surface, rim_color,
(x + size//4 - 2 + wobble, y - size//6, size//2 + 4, 4))
# Draw bucket highlights
highlight_color = (255, 255, 255)
pygame.draw.line(surface, highlight_color,
(x + size//3 + wobble, y),
(x + size*2//3 + wobble, y), 2)
# Draw dents and damage
dent_color = (128, 128, 128)
pygame.draw.arc(surface, dent_color,
(x + size//3 + wobble, y, size//4, size//6),
0, math.pi, 2)
def draw_newspaper_zombie(surface, x, y, size):
# Draw base zombie
draw_normal_zombie(surface, x, y, size)
# Get animation offset
time = pygame.time.get_ticks()
wobble = math.sin(time * 0.004) * 3
paper_shake = math.sin(time * 0.008) * 2
# Draw newspaper with animated shaking
paper_color = (255, 255, 255)
pygame.draw.rect(surface, paper_color,
(x + size//6 + paper_shake,
y + size//3,
size//2,
size//2))
# Draw newspaper content (headlines and text)
text_color = (0, 0, 0)
for i in range(4):
y_pos = y + size//3 + i*size//8
pygame.draw.line(surface, text_color,
(x + size//5 + paper_shake, y_pos),
(x + size*2//3 + paper_shake, y_pos), 1)
# Draw newspaper damage
if time % 2000 < 1000: # Animate paper damage
tear_points = [
(x + size//3 + paper_shake, y + size//3),
(x + size//2 + paper_shake, y + size//2),
(x + size//3 + paper_shake, y + size*2//3)
]
pygame.draw.lines(surface, (200, 200, 200), False, tear_points, 2)
def draw_dancing_zombie(surface, x, y, size):
# Get animation offset
time = pygame.time.get_ticks()
dance_move = math.sin(time * 0.006) * 10
spin = math.sin(time * 0.003) * 0.3
# Draw shadow
shadow_surface = pygame.Surface((size*2//3, size//4), pygame.SRCALPHA)
pygame.draw.ellipse(shadow_surface, (0, 0, 0, 64), (0, 0, size*2//3, size//4))
surface.blit(shadow_surface, (x + size//6, y + size - size//8))
# Draw legs in dancing pose
leg_color = (100, 100, 100)
pygame.draw.line(surface, leg_color,
(x + size//2, y + size//2),
(x + size//4 + dance_move, y + size), 4)
pygame.draw.line(surface, leg_color,
(x + size//2, y + size//2),
(x + size*3//4 - dance_move, y + size), 4)
# Draw disco outfit
outfit_color = (148, 0, 211) # Purple
outfit_points = [
(x + size//3 + dance_move/2, y + size//3),
(x + size*2//3 + dance_move/2, y + size//3),
(x + size*2//3 - dance_move/2, y + size*3//4),
(x + size//3 - dance_move/2, y + size*3//4)
]
pygame.draw.polygon(surface, outfit_color, outfit_points)
# Draw arms in dancing pose
arm_color = (100, 100, 100)
pygame.draw.line(surface, arm_color,
(x + size//2, y + size//2),
(x + size//4 - dance_move, y + size//3), 4)
pygame.draw.line(surface, arm_color,
(x + size//2, y + size//2),
(x + size*3//4 + dance_move, y + size//3), 4)
# Draw body with disco moves
body_color = (169, 169, 169)
body_rect = pygame.Rect(x + size//3 + dance_move/2, y + size//4,
size//3, size//2)
rotated_surface = pygame.Surface((size, size), pygame.SRCALPHA)
pygame.draw.ellipse(rotated_surface, body_color, body_rect)
# Draw head with cool hair
head_color = (169, 169, 169)
pygame.draw.circle(rotated_surface, head_color,
(int(x + size//2 + dance_move/2), int(y + size//3)),
size//4)
# Draw spiky hair with animation
hair_color = (0, 0, 0)
for i in range(6):
angle = i * math.pi/3 + spin
hair_x = x + size//2 + math.cos(angle) * size//3 + dance_move/2
hair_y = y + size//3 + math.sin(angle) * size//4
pygame.draw.line(surface, hair_color,
(x + size//2 + dance_move/2, y + size//3),
(hair_x, hair_y), 3)
# Draw sunglasses
glasses_color = (0, 0, 0)
pygame.draw.rect(surface, glasses_color,
(x + size//3 + dance_move/2, y + size//4,
size//3, size//8))
# Draw disco ball effect
for i in range(8):
angle = i * math.pi/4 + time * 0.01
sparkle_x = x + size//2 + math.cos(angle) * size//2
sparkle_y = y + size//3 + math.sin(angle) * size//2
pygame.draw.circle(surface, (255, 255, 255),
(int(sparkle_x), int(sparkle_y)), 2)
def draw_rose_shooter(surface, x, y, size):
# Get animation offset
time = pygame.time.get_ticks()
sway = math.sin(time * 0.003) * 3
petal_spin = time * 0.002
# Draw stem with swaying animation
stem_points = [
(x + size//2 + sway, y + size*2//3),
(x + size//2, y + size*7//8),
(x + size//2, y + size)
]
pygame.draw.lines(surface, GREEN, False, stem_points, 3)
# Draw leaves
leaf_color = (34, 139, 34) # Forest green
leaf_points = [
(x + size//2, y + size*3//4),
(x + size//3, y + size*7//8),
(x + size//2, y + size*13//16)
]
pygame.draw.polygon(surface, leaf_color, leaf_points)
# Draw thorns
thorn_color = (139, 69, 19) # Brown
thorn_points = [
[(x + size//2 - 5, y + size*3//4), (x + size//2 - 10, y + size*3//4 - 5), (x + size//2 - 5, y + size*3//4 - 5)],
[(x + size//2 + 5, y + size*3//4), (x + size//2 + 10, y + size*3//4 - 5), (x + size//2 + 5, y + size*3//4 - 5)]
]
for points in thorn_points:
pygame.draw.polygon(surface, thorn_color, points)
# Draw rose head with gradient
head_x = x + size//2 + sway
head_y = y + size//2
rose_colors = [(255, 192, 203), (255, 182, 193), (255, 105, 180)] # Pink gradients
# Draw petals in layers
for i, color in enumerate(rose_colors):
offset = i * 3
for angle in range(0, 360, 45):
rad = math.radians(angle + petal_spin)
petal_x = head_x + math.cos(rad) * (size//4 - offset)
petal_y = head_y + math.sin(rad) * (size//4 - offset)
pygame.draw.circle(surface, color, (int(petal_x), int(petal_y)), size//6 - offset)
# Draw center
pygame.draw.circle(surface, (139, 0, 0), (int(head_x), int(head_y)), size//8) # Dark red center
# Draw shooter with highlight
shooter_x = head_x + size//4
shooter_color = (255, 20, 147) # Deep pink
pygame.draw.circle(surface, shooter_color, (int(shooter_x), head_y), size//7)
pygame.draw.circle(surface, (255, 105, 180), (int(shooter_x), head_y), size//10) # Highlight
# Dictionary mapping plant types to their drawing functions
PLANT_DRAWINGS = {
PlantType.SUNFLOWER: draw_sunflower,
PlantType.PEASHOOTER: draw_peashooter,
PlantType.ROSE_SHOOTER: draw_rose_shooter,
PlantType.CHOMPER: draw_chomper,
PlantType.SNOW_PEA: draw_snow_pea
}
# Dictionary mapping zombie types to their drawing functions
ZOMBIE_DRAWINGS = {
ZombieType.NORMAL: draw_normal_zombie,
ZombieType.CONE: draw_cone_zombie,
ZombieType.BUCKET: draw_bucket_zombie,
ZombieType.NEWSPAPER: draw_newspaper_zombie,
ZombieType.DANCING: draw_dancing_zombie
}

View File

@ -0,0 +1,251 @@
import pygame
import random
import sys
import math
# Initialize Pygame
pygame.init()
# Colors
WHITE = (255, 255, 255)
RED = (255, 50, 50)
GREEN = (50, 255, 50)
BLUE = (50, 50, 255)
BLACK = (0, 0, 0)
DARK_GREEN = (34, 139, 34)
GOLD = (255, 215, 0)
# Game settings
WINDOW_SIZE = 800
GRID_SIZE = 20
GRID_COUNT = WINDOW_SIZE // GRID_SIZE
GAME_SPEED = 10
# Create window
screen = pygame.display.set_mode((WINDOW_SIZE, WINDOW_SIZE))
pygame.display.set_caption('贪吃蛇')
clock = pygame.time.Clock()
# Load and create background texture
background = pygame.Surface((WINDOW_SIZE, WINDOW_SIZE))
for y in range(0, WINDOW_SIZE, 4):
for x in range(0, WINDOW_SIZE, 4):
shade = random.randint(0, 20)
pygame.draw.rect(background, (shade, shade, shade), (x, y, 4, 4))
class Particle:
def __init__(self, x, y):
self.x = x
self.y = y
self.vx = random.uniform(-2, 2)
self.vy = random.uniform(-2, 2)
self.lifetime = 30
self.color = (random.randint(200, 255), random.randint(200, 255), random.randint(0, 50))
def update(self):
self.x += self.vx
self.y += self.vy
self.lifetime -= 1
def draw(self, surface):
alpha = int((self.lifetime / 30) * 255)
particle_surface = pygame.Surface((4, 4), pygame.SRCALPHA)
particle_surface.fill((*self.color, alpha))
surface.blit(particle_surface, (int(self.x), int(self.y)))
class Snake:
def __init__(self):
self.body = [(GRID_COUNT//2, GRID_COUNT//2)]
self.direction = (1, 0)
self.grow = False
self.angle = 0 # For snake movement animation
def move(self):
head = self.body[0]
new_head = (head[0] + self.direction[0], head[1] + self.direction[1])
if not self.grow:
self.body.pop()
else:
self.grow = False
self.body.insert(0, new_head)
self.angle += 0.2 # Update movement animation
def draw(self):
for i, segment in enumerate(self.body):
x = segment[0] * GRID_SIZE
y = segment[1] * GRID_SIZE
# Create snake skin pattern with gradient
base_color = (34, max(50, 255 - (i * 8)), 34)
# Add wave effect to snake body
offset = math.sin(self.angle + i * 0.3) * 2
# Draw main body segment with gradient
pygame.draw.rect(screen, base_color, (x, y + offset, GRID_SIZE-2, GRID_SIZE-2))
# Add scale pattern
if i > 0:
scale_color = (max(20, base_color[0] - 20),
max(20, base_color[1] - 20),
max(20, base_color[2] - 20))
pygame.draw.arc(screen, scale_color,
(x + 2, y + offset + 2, GRID_SIZE-6, GRID_SIZE-6),
0, 3.14, 2)
# Draw head with special details
if i == 0:
# Draw eyes with shine effect
eye_size = GRID_SIZE // 4
# Left eye
pygame.draw.circle(screen, WHITE,
(x + GRID_SIZE//3, y + offset + GRID_SIZE//3), eye_size)
pygame.draw.circle(screen, BLACK,
(x + GRID_SIZE//3, y + offset + GRID_SIZE//3), eye_size//2)
pygame.draw.circle(screen, WHITE,
(x + GRID_SIZE//3 - 1, y + offset + GRID_SIZE//3 - 1), eye_size//4)
# Right eye
pygame.draw.circle(screen, WHITE,
(x + 2*GRID_SIZE//3, y + offset + GRID_SIZE//3), eye_size)
pygame.draw.circle(screen, BLACK,
(x + 2*GRID_SIZE//3, y + offset + GRID_SIZE//3), eye_size//2)
pygame.draw.circle(screen, WHITE,
(x + 2*GRID_SIZE//3 - 1, y + offset + GRID_SIZE//3 - 1), eye_size//4)
class Food:
def __init__(self):
self.position = self.get_random_position()
self.angle = 0
self.particles = []
def get_random_position(self):
return (random.randint(0, GRID_COUNT-1), random.randint(0, GRID_COUNT-1))
def update(self):
self.angle += 0.1
# Update particles
self.particles = [p for p in self.particles if p.lifetime > 0]
for particle in self.particles:
particle.update()
def draw(self):
x = self.position[0] * GRID_SIZE
y = self.position[1] * GRID_SIZE
# Draw particles
for particle in self.particles:
particle.draw(screen)
# Draw apple with pulsing effect
size_mod = math.sin(self.angle) * 2
apple_size = GRID_SIZE//2 - 2 + size_mod
# Draw apple shadow
shadow_pos = (x + GRID_SIZE//2 + 2, y + GRID_SIZE//2 + 2)
pygame.draw.circle(screen, (20, 20, 20), shadow_pos, apple_size)
# Draw apple body
apple_pos = (x + GRID_SIZE//2, y + GRID_SIZE//2)
pygame.draw.circle(screen, RED, apple_pos, apple_size)
# Draw apple highlight
highlight_pos = (x + GRID_SIZE//2 - 2, y + GRID_SIZE//2 - 2)
pygame.draw.circle(screen, (255, 150, 150), highlight_pos, apple_size//3)
# Draw leaf with animation
leaf_x = x + GRID_SIZE//2 + math.sin(self.angle) * 2
leaf_y = y + math.cos(self.angle) * 2
pygame.draw.ellipse(screen, GREEN, (leaf_x, leaf_y, GRID_SIZE//4, GRID_SIZE//3))
def draw_title_and_score(score):
# Draw game title
title_font = pygame.font.Font(None, 74)
title_text = title_font.render('Snake Game', True, GOLD)
title_shadow = title_font.render('Snake Game', True, (50, 50, 50))
# Add shadow effect
screen.blit(title_shadow, (WINDOW_SIZE//2 - title_text.get_width()//2 + 2,
42))
screen.blit(title_text, (WINDOW_SIZE//2 - title_text.get_width()//2,
40))
# Draw score with fancy styling
score_font = pygame.font.Font(None, 48)
score_text = score_font.render(f'Score: {score}', True, WHITE)
score_shadow = score_font.render(f'Score: {score}', True, (50, 50, 50))
screen.blit(score_shadow, (12, 12))
screen.blit(score_text, (10, 10))
def main():
snake = Snake()
food = Food()
score = 0
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP and snake.direction != (0, 1):
snake.direction = (0, -1)
if event.key == pygame.K_DOWN and snake.direction != (0, -1):
snake.direction = (0, 1)
if event.key == pygame.K_LEFT and snake.direction != (1, 0):
snake.direction = (-1, 0)
if event.key == pygame.K_RIGHT and snake.direction != (-1, 0):
snake.direction = (1, 0)
# Move snake
snake.move()
# Update food animation
food.update()
# Check collision with food
if snake.body[0] == food.position:
snake.grow = True
food.position = food.get_random_position()
score += 1
# Add particles on food collection
x = food.position[0] * GRID_SIZE
y = food.position[1] * GRID_SIZE
for _ in range(20):
food.particles.append(Particle(x + GRID_SIZE//2, y + GRID_SIZE//2))
# Check collision with walls
head = snake.body[0]
if head[0] < 0 or head[0] >= GRID_COUNT or head[1] < 0 or head[1] >= GRID_COUNT:
pygame.quit()
sys.exit()
# Check collision with self
if head in snake.body[1:]:
pygame.quit()
sys.exit()
# Draw everything
screen.blit(background, (0, 0))
# Draw grid lines with fade effect
for i in range(GRID_COUNT):
alpha = abs(math.sin(i * 0.1 + pygame.time.get_ticks() * 0.001)) * 30 + 20
grid_surface = pygame.Surface((WINDOW_SIZE, 1), pygame.SRCALPHA)
grid_surface.fill((50, 50, 50, int(alpha)))
screen.blit(grid_surface, (0, i * GRID_SIZE))
screen.blit(grid_surface, (i * GRID_SIZE, 0))
snake.draw()
food.draw()
draw_title_and_score(score)
pygame.display.flip()
clock.tick(GAME_SPEED)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,35 @@
# Modern Tetris Game
A feature-rich Tetris implementation in Python using Pygame.
## Features
- Modern GUI with smooth animations
- Score system and levels
- Ghost piece preview
- Next piece preview
- Hold piece functionality
- Particle effects for line clears
- Background music and sound effects
- High score system
## Controls
- Left/Right Arrow: Move piece
- Up Arrow: Rotate piece clockwise
- Z: Rotate piece counter-clockwise
- Down Arrow: Soft drop
- Space: Hard drop
- C: Hold piece
- P: Pause game
- ESC: Quit game
## Installation
1. Install Python 3.8+
2. Install dependencies:
```bash
pip install -r requirements.txt
```
3. Run the game:
```bash
python tetris.py
```

View File

@ -0,0 +1 @@
700

View File

@ -0,0 +1,680 @@
import pygame
import random
import numpy as np
from typing import List, Tuple, Optional
import os
import math
import time
# Initialize Pygame
pygame.init()
pygame.mixer.init()
# Constants
BLOCK_SIZE = 30
GRID_WIDTH = 10
GRID_HEIGHT = 20
PREVIEW_SIZE = 4
# Calculate window size
SIDE_PANEL_WIDTH = 200
WINDOW_WIDTH = BLOCK_SIZE * GRID_WIDTH + SIDE_PANEL_WIDTH
WINDOW_HEIGHT = BLOCK_SIZE * GRID_HEIGHT
# Colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (128, 128, 128)
COLORS = {
'I': (0, 255, 255), # Cyan
'O': (255, 255, 0), # Yellow
'T': (128, 0, 128), # Purple
'S': (0, 255, 0), # Green
'Z': (255, 0, 0), # Red
'J': (0, 0, 255), # Blue
'L': (255, 165, 0), # Orange
}
# Game settings
INITIAL_FALL_SPEED = 0.8 # Initial time between falls in seconds
SOFT_DROP_SPEED = 0.05 # Time between falls when soft dropping
SPEED_UP_FACTOR = 0.08 # How much to speed up per level
MIN_FALL_SPEED = 0.1 # Minimum fall speed
LOCK_DELAY = 0.5 # Time in seconds before piece locks in place
MAX_LOCK_RESETS = 15 # Maximum number of lock delay resets
# Animation settings
MOVE_ANIMATION_SPEED = 0.05 # seconds (faster horizontal movement)
ROTATION_ANIMATION_SPEED = 0.08 # seconds
LINE_CLEAR_ANIMATION_TIME = 0.3 # seconds
FLASH_SPEED = 0.05 # seconds
# Tetromino shapes
SHAPES = {
'I': [[1, 1, 1, 1]],
'O': [[1, 1], [1, 1]],
'T': [[0, 1, 0], [1, 1, 1]],
'S': [[0, 1, 1], [1, 1, 0]],
'Z': [[1, 1, 0], [0, 1, 1]],
'J': [[1, 0, 0], [1, 1, 1]],
'L': [[0, 0, 1], [1, 1, 1]]
}
class AnimationState:
def __init__(self):
self.move_progress = 0
self.rotation_progress = 0
self.line_clear_progress = 0
self.flash_progress = 0
self.last_pos = None
self.last_shape = None
self.target_pos = None
self.target_shape = None
self.lines_being_cleared = []
self.flash_active = False
class Particle:
def __init__(self, x: int, y: int, color: Tuple[int, int, int]):
self.x = x
self.y = y
self.color = color
self.velocity = [random.uniform(-3, 3), random.uniform(-8, -4)]
self.life = 255
self.size = random.randint(2, 6)
self.rotation = random.uniform(0, 360)
self.rotation_speed = random.uniform(-5, 5)
def update(self):
self.x += self.velocity[0]
self.y += self.velocity[1]
self.velocity[1] += 0.2 # Gravity
self.velocity[0] *= 0.99 # Air resistance
self.life -= 3
self.rotation += self.rotation_speed
return self.life > 0
def draw(self, screen):
if self.life <= 0:
return
alpha = max(0, min(255, self.life))
color = (*self.color, alpha)
# Create rotated particle
surface = pygame.Surface((self.size * 2, self.size * 2), pygame.SRCALPHA)
points = [
(self.size + math.cos(math.radians(self.rotation)) * self.size,
self.size + math.sin(math.radians(self.rotation)) * self.size),
(self.size + math.cos(math.radians(self.rotation + 120)) * self.size,
self.size + math.sin(math.radians(self.rotation + 120)) * self.size),
(self.size + math.cos(math.radians(self.rotation + 240)) * self.size,
self.size + math.sin(math.radians(self.rotation + 240)) * self.size)
]
pygame.draw.polygon(surface, color, points)
screen.blit(surface, (self.x - self.size, self.y - self.size))
class Tetris:
def __init__(self):
self.screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption('俄罗斯方块')
self.clock = pygame.time.Clock()
self.grid = [[None for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
self.current_piece = None
self.current_shape = None
self.current_pos = None
self.held_piece = None
self.can_hold = True
self.next_piece = self._get_random_piece()
self.game_over = False
self.score = 0
self.level = 1
self.lines_cleared = 0
self.particles = []
self.fall_speed = INITIAL_FALL_SPEED
self.current_fall_speed = INITIAL_FALL_SPEED
self.last_fall_time = time.time()
self.lock_delay_time = 0
self.lock_delay_active = False
self.lock_reset_count = 0
self.last_move_time = time.time()
self.paused = False
self.combo = 0
self.force_down = False # New flag for forcing piece down
# Animation state
self.animation = AnimationState()
# Load high score
self.high_score = self._load_high_score()
# Initialize fonts
self.font_big = pygame.font.Font(None, 48)
self.font_small = pygame.font.Font(None, 36)
# Load sounds
self._load_sounds()
# Background gradient
self.background = self._create_background()
def _load_sounds(self):
# Create sounds directory if it doesn't exist
if not os.path.exists('sounds'):
os.makedirs('sounds')
# Initialize empty/silent sounds
empty_sound = pygame.mixer.Sound(buffer=bytes([0]*44)) # Minimal silent sound
self.sounds = {
'move': empty_sound,
'rotate': empty_sound,
'drop': empty_sound,
'clear': empty_sound,
'game_over': empty_sound
}
def _create_background(self):
surface = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT))
for y in range(WINDOW_HEIGHT):
progress = y / WINDOW_HEIGHT
color = (
int(20 + 20 * math.sin(progress * math.pi)),
int(10 + 10 * math.sin(progress * math.pi * 2)),
int(40 + 20 * math.sin(progress * math.pi * 0.5))
)
pygame.draw.line(surface, color, (0, y), (WINDOW_WIDTH, y))
return surface
def _load_high_score(self) -> int:
try:
if os.path.exists('highscore.txt'):
with open('highscore.txt', 'r') as f:
return int(f.read())
except:
pass
return 0
def _save_high_score(self):
with open('highscore.txt', 'w') as f:
f.write(str(self.high_score))
def _get_random_piece(self) -> str:
return random.choice(list(SHAPES.keys()))
def new_piece(self):
self.current_piece = self.next_piece
self.next_piece = self._get_random_piece()
self.current_shape = SHAPES[self.current_piece]
self.current_pos = [0, GRID_WIDTH//2 - len(self.current_shape[0])//2]
self.can_hold = True
# Reset animation state
self.animation.move_progress = 0
self.animation.rotation_progress = 0
self.animation.last_pos = self.current_pos.copy()
self.animation.last_shape = [row[:] for row in self.current_shape]
self.animation.target_pos = self.current_pos.copy()
self.animation.target_shape = [row[:] for row in self.current_shape]
# Check if game over
if self._check_collision():
self.game_over = True
self.sounds['game_over'].play()
def hold_piece(self):
if not self.can_hold:
return
self.sounds['rotate'].play()
if self.held_piece is None:
self.held_piece = self.current_piece
self.new_piece()
else:
self.held_piece, self.current_piece = self.current_piece, self.held_piece
self.current_shape = SHAPES[self.current_piece]
self.current_pos = [0, GRID_WIDTH//2 - len(self.current_shape[0])//2]
self.can_hold = False
def rotate_piece(self, clockwise: bool = True):
if self.current_piece == 'O':
return
self.sounds['rotate'].play()
old_shape = self.current_shape
self.current_shape = np.rot90(self.current_shape, 1 if not clockwise else -1).tolist()
# Update animation state
self.animation.last_shape = old_shape
self.animation.target_shape = self.current_shape
self.animation.rotation_progress = 0
if self._check_collision():
self.current_shape = old_shape
self.animation.target_shape = old_shape
def _check_collision(self) -> bool:
for y, row in enumerate(self.current_shape):
for x, cell in enumerate(row):
if cell:
grid_y = self.current_pos[0] + y
grid_x = self.current_pos[1] + x
if (grid_x < 0 or grid_x >= GRID_WIDTH or
grid_y >= GRID_HEIGHT or
(grid_y >= 0 and self.grid[grid_y][grid_x] is not None)):
return True
return False
def _get_ghost_position(self) -> List[int]:
ghost_pos = self.current_pos.copy()
temp = self.current_pos.copy()
while True:
ghost_pos[0] += 1
self.current_pos = ghost_pos.copy()
if self._check_collision():
ghost_pos[0] -= 1
self.current_pos = temp
break
return ghost_pos
def move(self, dx: int, dy: int):
old_pos = self.current_pos.copy()
self.current_pos[1] += dx
self.current_pos[0] += dy
if dx != 0:
self.sounds['move'].play()
collision = self._check_collision()
if collision:
self.current_pos[1] -= dx
self.current_pos[0] -= dy
if dy > 0: # If moving down caused collision
if not self.lock_delay_active:
# Start lock delay when piece first touches ground
self.lock_delay_active = True
self.lock_delay_time = time.time()
self.lock_reset_count = 0
elif time.time() - self.lock_delay_time > LOCK_DELAY or self.force_down:
# Lock piece if lock delay expired or forced down
self._place_piece()
self._clear_lines()
self.new_piece()
self.lock_delay_active = False
self.force_down = False
return True
else:
# Reset lock delay if piece moved successfully and still touching ground
if self.lock_delay_active and self.lock_reset_count < MAX_LOCK_RESETS:
# Check if still touching ground after move
self.current_pos[0] += 1
if self._check_collision():
self.lock_delay_time = time.time()
self.lock_reset_count += 1
self.current_pos[0] -= 1
else:
# If piece is not touching ground, deactivate lock delay
self.current_pos[0] += 1
if not self._check_collision():
self.lock_delay_active = False
self.current_pos[0] -= 1
# Update animation state for horizontal movement only
if dx != 0:
self.animation.last_pos = old_pos
self.animation.target_pos = self.current_pos.copy()
self.animation.move_progress = 0
return False
def hard_drop(self):
self.sounds['drop'].play()
ghost_pos = self._get_ghost_position()
self.current_pos = ghost_pos
self.force_down = True # Force the piece to lock immediately
self.move(0, 1) # This will trigger the locking process
def _place_piece(self):
for y, row in enumerate(self.current_shape):
for x, cell in enumerate(row):
if cell:
grid_y = self.current_pos[0] + y
grid_x = self.current_pos[1] + x
if 0 <= grid_y < GRID_HEIGHT:
self.grid[grid_y][grid_x] = self.current_piece
# Create landing particles
for x in range(len(self.current_shape[0])):
color = COLORS[self.current_piece]
px = (self.current_pos[1] + x) * BLOCK_SIZE
py = (self.current_pos[0] + len(self.current_shape) - 1) * BLOCK_SIZE
for _ in range(5):
self.particles.append(Particle(px, py, color))
def _clear_lines(self):
lines_to_clear = []
for y in range(GRID_HEIGHT):
if all(cell is not None for cell in self.grid[y]):
lines_to_clear.append(y)
if not lines_to_clear:
self.combo = 0
return
self.sounds['clear'].play()
self.animation.lines_being_cleared = lines_to_clear
self.animation.line_clear_progress = 0
self.animation.flash_active = True
self.animation.flash_progress = 0
# Create particles for cleared lines
for y in lines_to_clear:
for x in range(GRID_WIDTH):
color = COLORS[self.grid[y][x]]
px = x * BLOCK_SIZE
py = y * BLOCK_SIZE
for _ in range(5): # 5 particles per block
self.particles.append(Particle(px, py, color))
# Clear lines and update score
for y in lines_to_clear:
self.grid.pop(y)
self.grid.insert(0, [None] * GRID_WIDTH)
lines_count = len(lines_to_clear)
self.lines_cleared += lines_count
# Calculate score with combo bonus
self.combo += 1
combo_multiplier = min(self.combo, 10) # Cap combo at 10x
base_score = [100, 300, 500, 800][lines_count - 1]
self.score += base_score * self.level * combo_multiplier
self.level = self.lines_cleared // 10 + 1
self.fall_speed = max(MIN_FALL_SPEED,
INITIAL_FALL_SPEED - (self.level - 1) * SPEED_UP_FACTOR)
if self.score > self.high_score:
self.high_score = self.score
self._save_high_score()
def _interpolate_position(self, progress: float) -> List[int]:
if self.animation.last_pos is None or self.animation.target_pos is None:
return self.current_pos
# Only interpolate horizontal movement
return [
self.current_pos[0], # Vertical position is always current
self.animation.last_pos[1] + (self.animation.target_pos[1] - self.animation.last_pos[1]) * progress
]
def draw(self):
# Draw background
self.screen.blit(self.background, (0, 0))
# Draw grid
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
pygame.draw.rect(self.screen, GRAY,
(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE), 1)
# Draw placed pieces
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
if self.grid[y][x]:
if y in self.animation.lines_being_cleared:
# Skip drawing blocks in lines being cleared during animation
if self.animation.line_clear_progress < LINE_CLEAR_ANIMATION_TIME:
continue
color = COLORS[self.grid[y][x]]
pygame.draw.rect(self.screen, color,
(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE))
pygame.draw.rect(self.screen, WHITE,
(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE), 1)
# Draw ghost piece
if self.current_piece:
ghost_pos = self._get_ghost_position()
for y, row in enumerate(self.current_shape):
for x, cell in enumerate(row):
if cell:
color = (*COLORS[self.current_piece], 128)
ghost_x = (ghost_pos[1] + x) * BLOCK_SIZE
ghost_y = (ghost_pos[0] + y) * BLOCK_SIZE
surface = pygame.Surface((BLOCK_SIZE, BLOCK_SIZE), pygame.SRCALPHA)
pygame.draw.rect(surface, color, (0, 0, BLOCK_SIZE, BLOCK_SIZE))
self.screen.blit(surface, (ghost_x, ghost_y))
# Draw current piece with animation
if self.current_piece:
pos = self._interpolate_position(min(1, self.animation.move_progress / MOVE_ANIMATION_SPEED))
for y, row in enumerate(self.current_shape):
for x, cell in enumerate(row):
if cell:
color = COLORS[self.current_piece]
block_x = (pos[1] + x) * BLOCK_SIZE
block_y = (pos[0] + y) * BLOCK_SIZE
# Apply rotation animation
if self.animation.rotation_progress < ROTATION_ANIMATION_SPEED:
progress = self.animation.rotation_progress / ROTATION_ANIMATION_SPEED
scale = 1 - math.sin(progress * math.pi) * 0.2
# Calculate center of rotation
center_x = pos[1] * BLOCK_SIZE + len(row) * BLOCK_SIZE / 2
center_y = pos[0] * BLOCK_SIZE + len(self.current_shape) * BLOCK_SIZE / 2
# Adjust block position for rotation
block_x = center_x + (block_x - center_x) * scale
block_y = center_y + (block_y - center_y) * scale
pygame.draw.rect(self.screen, color,
(block_x, block_y, BLOCK_SIZE, BLOCK_SIZE))
pygame.draw.rect(self.screen, WHITE,
(block_x, block_y, BLOCK_SIZE, BLOCK_SIZE), 1)
# Draw side panel
panel_x = GRID_WIDTH * BLOCK_SIZE + 10
# Draw next piece preview
next_text = self.font_small.render('Next:', True, WHITE)
self.screen.blit(next_text, (panel_x, 20))
next_shape = SHAPES[self.next_piece]
for y, row in enumerate(next_shape):
for x, cell in enumerate(row):
if cell:
color = COLORS[self.next_piece]
pygame.draw.rect(self.screen, color,
(panel_x + x * BLOCK_SIZE,
60 + y * BLOCK_SIZE,
BLOCK_SIZE, BLOCK_SIZE))
pygame.draw.rect(self.screen, WHITE,
(panel_x + x * BLOCK_SIZE,
60 + y * BLOCK_SIZE,
BLOCK_SIZE, BLOCK_SIZE), 1)
# Draw held piece
held_text = self.font_small.render('Hold:', True, WHITE)
self.screen.blit(held_text, (panel_x, 160))
if self.held_piece:
held_shape = SHAPES[self.held_piece]
for y, row in enumerate(held_shape):
for x, cell in enumerate(row):
if cell:
color = COLORS[self.held_piece]
if not self.can_hold:
color = tuple(c//2 for c in color) # Darken color
pygame.draw.rect(self.screen, color,
(panel_x + x * BLOCK_SIZE,
200 + y * BLOCK_SIZE,
BLOCK_SIZE, BLOCK_SIZE))
pygame.draw.rect(self.screen, WHITE,
(panel_x + x * BLOCK_SIZE,
200 + y * BLOCK_SIZE,
BLOCK_SIZE, BLOCK_SIZE), 1)
# Draw score and level
score_text = self.font_small.render(f'Score: {self.score}', True, WHITE)
self.screen.blit(score_text, (panel_x, 300))
high_score_text = self.font_small.render(f'High: {self.high_score}', True, WHITE)
self.screen.blit(high_score_text, (panel_x, 340))
level_text = self.font_small.render(f'Level: {self.level}', True, WHITE)
self.screen.blit(level_text, (panel_x, 380))
lines_text = self.font_small.render(f'Lines: {self.lines_cleared}', True, WHITE)
self.screen.blit(lines_text, (panel_x, 420))
if self.combo > 1:
combo_text = self.font_small.render(f'Combo: x{self.combo}', True, WHITE)
self.screen.blit(combo_text, (panel_x, 460))
# Draw particles
self.particles = [p for p in self.particles if p.update()]
for particle in self.particles:
particle.draw(self.screen)
# Draw line clear flash effect
if self.animation.flash_active and self.animation.lines_being_cleared:
flash_alpha = int(255 * (1 - self.animation.flash_progress / FLASH_SPEED))
if flash_alpha > 0:
flash_surface = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA)
for y in self.animation.lines_being_cleared:
pygame.draw.rect(flash_surface, (255, 255, 255, flash_alpha),
(0, y * BLOCK_SIZE, GRID_WIDTH * BLOCK_SIZE, BLOCK_SIZE))
self.screen.blit(flash_surface, (0, 0))
# Draw game over or pause screen
if self.game_over:
self._draw_overlay("Game Over! Press R to restart")
elif self.paused:
self._draw_overlay("Paused")
pygame.display.flip()
def _draw_overlay(self, text: str):
overlay = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT))
overlay.set_alpha(128)
overlay.fill(BLACK)
self.screen.blit(overlay, (0, 0))
text_surface = self.font_big.render(text, True, WHITE)
text_rect = text_surface.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2))
self.screen.blit(text_surface, text_rect)
def reset(self):
self.grid = [[None for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
self.current_piece = None
self.current_shape = None
self.current_pos = None
self.held_piece = None
self.can_hold = True
self.next_piece = self._get_random_piece()
self.game_over = False
self.score = 0
self.level = 1
self.lines_cleared = 0
self.particles = []
self.fall_speed = INITIAL_FALL_SPEED
self.current_fall_speed = INITIAL_FALL_SPEED
self.last_fall_time = time.time()
self.lock_delay_time = 0
self.lock_delay_active = False
self.lock_reset_count = 0
self.combo = 0
self.animation = AnimationState()
self.paused = False
self.new_piece()
def update_animations(self, dt: float):
# Update move animation
if self.animation.move_progress < MOVE_ANIMATION_SPEED:
self.animation.move_progress += dt
# Update rotation animation
if self.animation.rotation_progress < ROTATION_ANIMATION_SPEED:
self.animation.rotation_progress += dt
# Update line clear animation
if self.animation.line_clear_progress < LINE_CLEAR_ANIMATION_TIME:
self.animation.line_clear_progress += dt
# Update flash animation
if self.animation.flash_active:
self.animation.flash_progress += dt
if self.animation.flash_progress >= FLASH_SPEED:
self.animation.flash_active = False
self.animation.flash_progress = 0
def run(self):
self.new_piece()
last_time = time.time()
while True:
current_time = time.time()
dt = current_time - last_time
last_time = current_time
self.clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
return
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.quit()
return
if self.game_over:
if event.key == pygame.K_r:
self.reset()
continue
if event.key == pygame.K_p:
self.paused = not self.paused
continue
if self.paused:
continue
if event.key == pygame.K_LEFT:
self.move(-1, 0)
elif event.key == pygame.K_RIGHT:
self.move(1, 0)
elif event.key == pygame.K_DOWN:
self.current_fall_speed = SOFT_DROP_SPEED
elif event.key == pygame.K_UP:
self.rotate_piece()
elif event.key == pygame.K_z:
self.rotate_piece(False)
elif event.key == pygame.K_SPACE:
self.hard_drop()
elif event.key == pygame.K_c:
self.hold_piece()
elif event.type == pygame.KEYUP:
if event.key == pygame.K_DOWN:
self.current_fall_speed = self.fall_speed
if not self.game_over and not self.paused:
# Update animations
self.update_animations(dt)
# Handle automatic falling
if current_time - self.last_fall_time > self.current_fall_speed:
self.move(0, 1) # Move down one grid
self.last_fall_time = current_time
self.draw()
if __name__ == '__main__':
game = Tetris()
game.run()

View File

@ -0,0 +1,2 @@
pygame==2.5.2
numpy==1.24.3

BIN
misc/car_game.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

BIN
misc/pvz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

BIN
misc/snake_game.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 KiB

BIN
misc/tetris.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB