339 lines
9.3 KiB
JavaScript
Executable File
339 lines
9.3 KiB
JavaScript
Executable File
var c = document.getElementById("piano");
|
||
var context = c.getContext("2d");
|
||
var b = document.getElementById("background");
|
||
var context_back = b.getContext("2d");
|
||
var a = document.getElementById("score_bar");
|
||
var context_score = a.getContext("2d");
|
||
|
||
|
||
var numOfTiles = 5;
|
||
var myScore = 0;
|
||
var eachState = [false,false,false,false,false];
|
||
var myTiles = [];
|
||
|
||
var intervalTmp;
|
||
var geneTmp;
|
||
var gameSpeed = 1; // 游戏速度倍数,初始为1倍
|
||
var baseInterval = 5; // 基础更新间隔(毫秒)
|
||
var baseGenerateInterval = 600; // 基础生成间隔(毫秒)
|
||
|
||
paintWindow();
|
||
paintScoreBar();
|
||
|
||
// 添加全局鼠标和触摸事件监听
|
||
c.addEventListener('click', function(e) {
|
||
handleClick(e);
|
||
});
|
||
|
||
c.addEventListener('touchstart', function(e) {
|
||
e.preventDefault();
|
||
handleTouch(e);
|
||
});
|
||
|
||
document.getElementById('start_btn').addEventListener('click',function(e){
|
||
var content = document.getElementById('start_btn');
|
||
if(content.innerHTML == "开始游戏" || content.innerHTML == "继续游戏"){
|
||
startGame();
|
||
}
|
||
else{
|
||
pauseGame();
|
||
}
|
||
});
|
||
|
||
// 重新开始按钮事件
|
||
document.getElementById('restart-btn').addEventListener('click', function(){
|
||
restartGame();
|
||
});
|
||
|
||
function startGame(){
|
||
var content = document.getElementById('start_btn');
|
||
updateGameSpeed();
|
||
document.getElementById('music').play();
|
||
content.innerHTML = "暂停游戏";
|
||
content.className = "game-btn pause-btn";
|
||
}
|
||
|
||
// 更新游戏速度
|
||
function updateGameSpeed() {
|
||
// 清除现有定时器
|
||
if (intervalTmp) clearInterval(intervalTmp);
|
||
if (geneTmp) clearInterval(geneTmp);
|
||
|
||
// 保持正常1倍速度,不加速
|
||
gameSpeed = 1;
|
||
|
||
// 设置新的定时器,优化性能
|
||
intervalTmp = setInterval(upDate, Math.max(baseInterval / gameSpeed, 3));
|
||
geneTmp = setInterval(geneBlock, Math.max(baseGenerateInterval / gameSpeed, 200));
|
||
}
|
||
|
||
function pauseGame(){
|
||
var content = document.getElementById('start_btn');
|
||
document.getElementById('music').pause();
|
||
window.clearInterval(intervalTmp);
|
||
window.clearInterval(geneTmp);
|
||
content.innerHTML = "继续游戏";
|
||
content.className = "game-btn start-btn";
|
||
}
|
||
|
||
function gameOver(){
|
||
document.getElementById('music').pause();
|
||
window.clearInterval(intervalTmp);
|
||
window.clearInterval(geneTmp);
|
||
|
||
// 显示最终得分和达到的最高速度
|
||
document.getElementById('final-score-value').innerHTML = myScore;
|
||
document.getElementById('final-speed-value').innerHTML = gameSpeed.toFixed(1);
|
||
|
||
// 显示游戏结束弹窗
|
||
document.getElementById('game-over-modal').style.display = 'flex';
|
||
}
|
||
|
||
function restartGame(){
|
||
// 重置游戏状态
|
||
myScore = 0;
|
||
gameSpeed = 1; // 重置游戏速度
|
||
eachState = [false,false,false,false,false];
|
||
myTiles = [];
|
||
|
||
// 清空画布
|
||
context.clearRect(0,0,300,600);
|
||
context_back.clearRect(0,0,300,600);
|
||
context_score.clearRect(0,0,300,60);
|
||
|
||
// 重新绘制背景
|
||
paintWindow();
|
||
paintScoreBar();
|
||
|
||
// 隐藏弹窗
|
||
document.getElementById('game-over-modal').style.display = 'none';
|
||
|
||
// 重置按钮状态
|
||
var content = document.getElementById('start_btn');
|
||
content.innerHTML = "开始游戏";
|
||
content.className = "game-btn start-btn";
|
||
}
|
||
function paintScoreBar(){
|
||
// 清空画布
|
||
context_score.clearRect(0,0,300,60);
|
||
|
||
// 绘制黑色背景
|
||
context_score.fillStyle = "#333";
|
||
context_score.fillRect(0,0,300,60);
|
||
|
||
// 更新HTML显示
|
||
document.getElementById('score-value').textContent = myScore;
|
||
document.getElementById('speed-value').textContent = gameSpeed.toFixed(1);
|
||
}
|
||
function geneBlock(){
|
||
var myRand = Math.floor(Math.random()*numOfTiles);
|
||
var i;
|
||
var flag = true;
|
||
for( i = 0; i < numOfTiles; ++i){
|
||
if(!eachState[i]){
|
||
flag = false;
|
||
}
|
||
}
|
||
if(flag)return;//if mytiles array didn't have false element, then return
|
||
|
||
while(eachState[myRand])
|
||
myRand = Math.floor(Math.random()*numOfTiles);
|
||
myTiles[myRand] = new Block(myRand);
|
||
|
||
}
|
||
function paintWindow(){
|
||
// 清空背景
|
||
context_back.clearRect(0,0,300,600);
|
||
|
||
// 绘制白色背景
|
||
context_back.fillStyle = "white";
|
||
context_back.fillRect(0,0,300,600);
|
||
|
||
// 绘制分隔线
|
||
context_back.strokeStyle = "#ddd";
|
||
context_back.lineWidth = 2;
|
||
|
||
// 垂直分隔线
|
||
context_back.beginPath();
|
||
context_back.moveTo(75,0);
|
||
context_back.lineTo(75,600);
|
||
context_back.stroke();
|
||
|
||
context_back.beginPath();
|
||
context_back.moveTo(150,0);
|
||
context_back.lineTo(150,600);
|
||
context_back.stroke();
|
||
|
||
context_back.beginPath();
|
||
context_back.moveTo(225,0);
|
||
context_back.lineTo(225,600);
|
||
context_back.stroke();
|
||
|
||
// 可点击区域警戒线
|
||
context_back.strokeStyle = "#ff4444";
|
||
context_back.lineWidth = 3;
|
||
context_back.beginPath();
|
||
context_back.moveTo(0,250);
|
||
context_back.lineTo(300,250);
|
||
context_back.stroke();
|
||
|
||
// 底部失败线
|
||
context_back.strokeStyle = "#ff4444";
|
||
context_back.lineWidth = 3;
|
||
context_back.beginPath();
|
||
context_back.moveTo(0,470);
|
||
context_back.lineTo(300,470);
|
||
context_back.stroke();
|
||
}
|
||
function Block(index){
|
||
if(!eachState[index])
|
||
eachState[index] = true;
|
||
|
||
this.index = index;
|
||
this.appearPos = Math.floor(Math.random()*4);
|
||
|
||
this.width = 75;
|
||
this.height = 120;
|
||
this.color = "black";
|
||
switch(this.appearPos){
|
||
case 0:
|
||
this.x = 0;
|
||
this.y = -120;
|
||
break;
|
||
case 1:
|
||
this.x = 75;
|
||
this.y = -120;
|
||
break;
|
||
case 2:
|
||
this.x = 150;
|
||
this.y = -120;
|
||
break;
|
||
case 3:
|
||
this.x = 225;
|
||
this.y = -120;
|
||
break;
|
||
}
|
||
context.fillStyle = this.color;
|
||
context.fillRect(this.x,this.y,this.width,this.height);
|
||
this.live = true;
|
||
|
||
window.addEventListener('keydown',function(e){
|
||
myTiles[index].keyCode = e.keyCode;
|
||
});
|
||
window.addEventListener('keyup',function(e){
|
||
myTiles[index].keyCode = false;
|
||
});
|
||
}
|
||
function move(index){
|
||
if(myTiles[index].live){
|
||
myTiles[index].y += Math.ceil(gameSpeed);
|
||
// 绘制逻辑已移到upDate函数中,避免重复绘制
|
||
}
|
||
}
|
||
function afterRight(index){
|
||
myScore++;
|
||
// 清除方块(在upDate中统一处理绘制)
|
||
myTiles[index].live = false;
|
||
eachState[index] = false;
|
||
|
||
// 立即更新得分显示
|
||
paintScoreBar();
|
||
|
||
// 每次得分都更新游戏速度,实现平滑渐进加速
|
||
updateGameSpeed();
|
||
}
|
||
// 处理鼠标点击事件
|
||
function handleClick(e) {
|
||
var rect = c.getBoundingClientRect();
|
||
var x = e.clientX - rect.left;
|
||
var y = e.clientY - rect.top;
|
||
|
||
checkHit(x, y);
|
||
}
|
||
|
||
// 处理触摸事件
|
||
function handleTouch(e) {
|
||
var rect = c.getBoundingClientRect();
|
||
var touch = e.touches[0];
|
||
var x = touch.clientX - rect.left;
|
||
var y = touch.clientY - rect.top;
|
||
|
||
checkHit(x, y);
|
||
}
|
||
|
||
// 检查点击/触摸是否命中方块
|
||
function checkHit(x, y) {
|
||
// 检查是否点击到黑色方块
|
||
for (var i = 0; i < numOfTiles; i++) {
|
||
if (eachState[i] && myTiles[i].live) {
|
||
// 检查点击位置是否在方块范围内
|
||
if (x >= myTiles[i].x && x <= myTiles[i].x + 75 &&
|
||
y >= myTiles[i].y && y <= myTiles[i].y + 120) {
|
||
// 检查方块是否在可点击区域(提高到130像素以上)
|
||
if (myTiles[i].y + 120 > 130) {
|
||
afterRight(i);
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果没有点击到任何黑色方块,且点击位置在游戏区域内,则游戏结束
|
||
if (y > 130 && y < 600) {
|
||
gameOver();
|
||
return false;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
function upDate(){//check keyCode whether correct
|
||
var i;
|
||
|
||
// 清空整个游戏区域,避免重叠
|
||
context.clearRect(0, 0, 300, 600);
|
||
|
||
// 移动并重绘所有活跃的方块
|
||
for(i = 0; i < numOfTiles; ++i){
|
||
if(eachState[i]){
|
||
myTiles[i].y += Math.ceil(gameSpeed); // 使用整数移动,避免模糊
|
||
context.fillStyle = "black";
|
||
context.fillRect(myTiles[i].x, myTiles[i].y, 75, 120);
|
||
}
|
||
}
|
||
for(i = 0; i < numOfTiles; ++i){
|
||
if( eachState[i] ){
|
||
if(myTiles[i].y < 470 && myTiles[i].y >350){
|
||
switch(myTiles[i].keyCode){
|
||
case 65: //A
|
||
if(myTiles[i].x == 0)
|
||
afterRight(i);
|
||
break;
|
||
case 83: //S
|
||
if(myTiles[i].x == 75)
|
||
afterRight(i);
|
||
break;
|
||
case 68: //D
|
||
if(myTiles[i].x == 150)
|
||
afterRight(i);
|
||
break;
|
||
case 70: //F
|
||
if(myTiles[i].x == 225)
|
||
afterRight(i);
|
||
break;
|
||
}
|
||
}
|
||
if(myTiles[i].y > 470){
|
||
// 方块到达底部,游戏结束
|
||
myTiles[i].live = false;
|
||
eachState[i] = false;
|
||
gameOver();
|
||
return; // 立即退出,避免继续处理
|
||
}
|
||
}
|
||
else{//not alive
|
||
|
||
}
|
||
}
|
||
}
|
||
|