导言

在一个风和日丽的一天,看完了疯狂HTML 5+CSS 3+JavaScript讲义,跟着做了书里最后一章的俄罗斯方块小游戏,并做了一些改进,作为自己前端学习的第一站。

游戏效果:

制作思路

因为书里的俄罗斯方块比较普通,太常规了,不是很好看,所以我在网上找了上面那张图片,打算照着它来做。(请无视成品和原图的差距)

然后便是游戏界面和常规的俄罗斯方块游戏逻辑。

接着便是游戏结束界面了。

原本想做个弹出层,但觉得找图片有点麻烦,所以就在网上找了文字特效,套用了一下。

代码实现:

首先是html文件和css文件,主要涉及了布局方面。作为新手,在上面真的是翻来覆去的踩坑。o(╥﹏╥)o

index.html

俄罗斯方块

/*导入外部的字体文件*/

@font-face{

font-family:tmb;/*为字体命名为tmb*/

src:url("DS-DIGIB.TTF") format("TrueType");/*format为字体文件格式,TrueType为ttf*/

}

div>span{

font-family:tmb;

font-size:18pt;

color:green;

}

速度:1

当前分数:0

最高分数:0

teris.css

*{

margin:0;

padding:0;

}

html, body{

width:100%;

height:100%;

}

.bg{

font-size:13pt;

background-color:rgb(239, 239, 227);

/*好看的渐变色*/

background-image:radial-gradient(rgb(239, 239, 227), rgb(230, 220, 212));

/*阴影*/

box-shadow:#cdc8c1 -1px -1px 7px 0px;

padding-bottom:4px;

}

.ui_bg{

border-bottom:1px #a69e9ea3 solid;

padding-bottom:2px;

overflow:hidden;/*没有这句的话因为子div都设置了float,所以是浮在网页上的,所以父div就没有高度,这句清除了浮动,让父div有了子div的高度*/

}

然后是重头戏,teris.js

游戏变量

//游戏设定

var TETRIS_ROWS = 20;

var TETRIS_COLS = 14;

var CELL_SIZE = 24;

var NO_BLOCK=0;

var HAVE_BLOCK=1;

// 定义几种可能出现的方块组合

var blockArr = [

// Z

[

{x: TETRIS_COLS / 2 - 1 , y:0},

{x: TETRIS_COLS / 2 , y:0},

{x: TETRIS_COLS / 2 , y:1},

{x: TETRIS_COLS / 2 + 1 , y:1}

],

// 反Z

[

{x: TETRIS_COLS / 2 + 1 , y:0},

{x: TETRIS_COLS / 2 , y:0},

{x: TETRIS_COLS / 2 , y:1},

{x: TETRIS_COLS / 2 - 1 , y:1}

],

// 田

[

{x: TETRIS_COLS / 2 - 1 , y:0},

{x: TETRIS_COLS / 2 , y:0},

{x: TETRIS_COLS / 2 - 1 , y:1},

{x: TETRIS_COLS / 2 , y:1}

],

// L

[

{x: TETRIS_COLS / 2 - 1 , y:0},

{x: TETRIS_COLS / 2 - 1, y:1},

{x: TETRIS_COLS / 2 - 1 , y:2},

{x: TETRIS_COLS / 2 , y:2}

],

// J

[

{x: TETRIS_COLS / 2 , y:0},

{x: TETRIS_COLS / 2 , y:1},

{x: TETRIS_COLS / 2 , y:2},

{x: TETRIS_COLS / 2 - 1, y:2}

],

// □□□□

[

{x: TETRIS_COLS / 2 , y:0},

{x: TETRIS_COLS / 2 , y:1},

{x: TETRIS_COLS / 2 , y:2},

{x: TETRIS_COLS / 2 , y:3}

],

// ┴

[

{x: TETRIS_COLS / 2 , y:0},

{x: TETRIS_COLS / 2 - 1 , y:1},

{x: TETRIS_COLS / 2 , y:1},

{x: TETRIS_COLS / 2 + 1, y:1}

]

];

// 记录当前积分

var curScore=0;

// 记录曾经的最高积分

var maxScore=1;

var curSpeed=1;

//ui元素

var curSpeedEle=document.getElementById("cur_speed");

var curScoreEle=document.getElementById("cur_points");

var maxScoreEle=document.getElementById("max_points");

var timer;//方块下落控制

var myCanvas;

var canvasCtx;

var tetris_status;//地图数据

var currentFall;//当前下落的block

游戏界面的完善

//create canvas

function createCanvas(){

myCanvas=document.createElement("canvas");

myCanvas.width=TETRIS_COLS*CELL_SIZE;

myCanvas.height=TETRIS_ROWS*CELL_SIZE;

//绘制背景

canvasCtx=myCanvas.getContext("2d");

canvasCtx.beginPath();

//TETRIS_COS

for(let i=1; i

canvasCtx.moveTo(i*CELL_SIZE, 0);

canvasCtx.lineTo(i*CELL_SIZE, myCanvas.height);

}

for(let i=1; i

canvasCtx.moveTo(0, i*CELL_SIZE);

canvasCtx.lineTo(myCanvas.width, i*CELL_SIZE);

}

canvasCtx.closePath();

canvasCtx.strokeStyle="#b4a79d";

canvasCtx.lineWidth=0.6;

canvasCtx.stroke();

//第一行,最后一行,第一列,最后一列粗一点。

canvasCtx.beginPath();

canvasCtx.moveTo(0, 0);

canvasCtx.lineTo(myCanvas.width, 0);

canvasCtx.moveTo(0, myCanvas.height);

canvasCtx.lineTo(myCanvas.width, myCanvas.height);

canvasCtx.moveTo(0, 0);

canvasCtx.lineTo(0, myCanvas.height);

canvasCtx.moveTo(myCanvas.width, 0);

canvasCtx.lineTo(myCanvas.width, myCanvas.height);

canvasCtx.closePath();

canvasCtx.strokeStyle="#b4a79d";

canvasCtx.lineWidth=4;

canvasCtx.stroke();

//设置绘制block时的style

canvasCtx.fillStyle="#201a14";

}

draw canvas

function changeWidthAndHeight(w, h){

//通过jquery设置css

h+=$("ui_bg").css("height")+$("ui_bg").css("margin-rop")+$("ui_bg").css("margin-bottom")+$("ui_bg").css("padding-top")+$("ui_bg").css("padding-bottom");

$(".bg").css({

"width":w,

"height":h,

"top":0, "bottom":0, "right":0, "left":0,

"margin":"auto"

});

}

change width and height

//draw blocks

function drawBlocks(){

//清空地图

for(let i=0; i

for(let j=0;j

canvasCtx.clearRect(j*CELL_SIZE+1, i*CELL_SIZE+1, CELL_SIZE-2, CELL_SIZE-2);

}

//绘制地图

for(let i=0; i

for(let j=0;j

if(tetris_status[i][j]!=NO_BLOCK)

canvasCtx.fillRect(j*CELL_SIZE+1, i*CELL_SIZE+1, CELL_SIZE-2, CELL_SIZE-2);//中间留点缝隙

}

}

//绘制currentFall

for(let i=0;i

canvasCtx.fillRect(currentFall[i].x*CELL_SIZE+1, currentFall[i].y*CELL_SIZE+1, CELL_SIZE-2,CELL_SIZE-2);

}

draw block

游戏逻辑

function rotate(){

// 定义记录能否旋转的旗标

var canRotate = true;

for (var i = 0 ; i < currentFall.length ; i++)

{

var preX = currentFall[i].x;

var preY = currentFall[i].y;

// 始终以第三个方块作为旋转的中心,

// i == 2时,说明是旋转的中心

if(i != 2)

{

// 计算方块旋转后的x、y坐标

var afterRotateX = currentFall[2].x + preY - currentFall[2].y;

var afterRotateY = currentFall[2].y + currentFall[2].x - preX;

// 如果旋转后所在位置已有方块,表明不能旋转

if(tetris_status[afterRotateY][afterRotateX + 1] != NO_BLOCK)

{

canRotate = false;

break;

}

// 如果旋转后的坐标已经超出了最左边边界

if(afterRotateX < 0 || tetris_status[afterRotateY - 1][afterRotateX] != NO_BLOCK)

{

moveRight();

afterRotateX = currentFall[2].x + preY - currentFall[2].y;

afterRotateY = currentFall[2].y + currentFall[2].x - preX;

break;

}

if(afterRotateX < 0 || tetris_status[afterRotateY-1][afterRotateX] != NO_BLOCK)

{

moveRight();

break;

}

// 如果旋转后的坐标已经超出了最右边边界

if(afterRotateX >= TETRIS_COLS - 1 ||

tetris_status[afterRotateY][afterRotateX+1] != NO_BLOCK)

{

moveLeft();

afterRotateX = currentFall[2].x + preY - currentFall[2].y;

afterRotateY = currentFall[2].y + currentFall[2].x - preX;

break;

}

if(afterRotateX >= TETRIS_COLS - 1 ||

tetris_status[afterRotateY][afterRotateX+1] != NO_BLOCK)

{

moveLeft();

break;

}

}

}

if(canRotate){

for (var i = 0 ; i < currentFall.length ; i++){

var preX = currentFall[i].x;

var preY = currentFall[i].y;

if(i != 2){

currentFall[i].x = currentFall[2].x +

preY - currentFall[2].y;

currentFall[i].y = currentFall[2].y +

currentFall[2].x - preX;

}

}

localStorage.setItem("currentFall", JSON.stringify(currentFall));

}

}

旋转

//按下 下 或 interval到了

function next(){

if(moveDown()){

//记录block

for(let i=0;i

tetris_status[currentFall[i].y][currentFall[i].x]=HAVE_BLOCK;

//判断有没有满行的

for(let j=0;j

for(let i=0;i

if(tetris_status[currentFall[j].y][i]==NO_BLOCK)

break;

//最后一行满了

if(i==TETRIS_COLS-1){

//消除最后一行

for(let i=currentFall[j].y; i>0;i--){

for(let j=0;j

tetris_status[i][j]=tetris_status[i-1][j];

}

//分数增加

curScore+=5;

localStorage.setItem("curScore", curScore);

if(curScore>maxScore){

//超越最高分

maxScore=curScore;

localStorage.setItem("maxScore", maxScore);

}

//加速

curSpeed+=0.1;

localStorage.setItem("curSpeed", curSpeed);

//ui输出

curScoreEle.innerHTML=""+curScore;

maxScoreEle.innerHTML=""+maxScore;

curSpeedEle.innerHTML=curSpeed.toFixed(1);//保留两位小数

clearInterval(timer);

timer=setInterval(function(){

next();

}, 500/curSpeed);

}

}

}

//判断是否触顶

for(let i=0;i

if(currentFall[i].y==0){

gameEnd();

return;

}

}

localStorage.setItem("tetris_status", JSON.stringify(tetris_status));

//新的block

createBlock();

localStorage.setItem("currentFall", JSON.stringify(currentFall));

}

drawBlocks();

}

//右移

function moveRight(){

for(let i=0;i

if(currentFall[i].x+1>=TETRIS_ROWS || tetris_status[currentFall[i].y][currentFall[i].x+1]!=NO_BLOCK)

return;

}

for(let i=0;i

currentFall[i].x++;

localStorage.setItem("currentFall", JSON.stringify(currentFall));

return;

}

//左移

function moveLeft(){

for(let i=0;i

if(currentFall[i].x-1<0 || tetris_status[currentFall[i].y][currentFall[i].x-1]!=NO_BLOCK)

return;

}

for(let i=0;i

currentFall[i].x--;

localStorage.setItem("currentFall", JSON.stringify(currentFall));

return;

}

//judge can move down and if arrive at end return 1, if touch other blocks return 2, else, return 0

function moveDown(){

for(let i=0;i

if(currentFall[i].y>=TETRIS_ROWS-1 || tetris_status[currentFall[i].y+1][currentFall[i].x]!=NO_BLOCK)

return true;

}

for(let i=0;i

currentFall[i].y+=1;

return false;

}

上下左右移动

function gameKeyEvent(evt){

switch(evt.keyCode){

//向下

case 40://↓

case 83://S

next();

drawBlocks();

break;

//向左

case 37://←

case 65://A

moveLeft();

drawBlocks();

break;

//向右

case 39://→

case 68://D

moveRight();

drawBlocks();

break;

//旋转

case 38://↑

case 87://W

rotate();

drawBlocks();

break;

}

}

keydown事件监听

keydown事件监听

其他的详细情况可以看源代码,我就不整理了。

接下来我们看游戏结束时的特效。因为我也不是很懂,所以在这里整理的会比较详细。当做学习。

//game end

function gameEnd(){

clearInterval(timer);

//键盘输入监听结束

window.οnkeydοwn=function(){

//按任意键重新开始游戏

window.οnkeydοwn=gameKeyEvent;

//初始化游戏数据

initData();

createBlock();

localStorage.setItem("currentFall", JSON.stringify(currentFall));

localStorage.setItem("tetris_status", JSON.stringify(tetris_status));

localStorage.setItem("curScore", curScore);

localStorage.setItem("curSpeed", curSpeed);

//绘制

curScoreEle.innerHTML=""+curScore;

curSpeedEle.innerHTML=curSpeed.toFixed(1);//保留两位小数

drawBlocks();

timer=setInterval(function(){

next();

}, 500/curSpeed);

//清除特效

this.stage.removeAllChildren();

this.textStage.removeAllChildren();

};

//特效,游戏结束

setTimeout(function(){

initAnim();

//擦除黑色方块

for(let i=0; i

for(let j=0;j

canvasCtx.clearRect(j*CELL_SIZE+1, i*CELL_SIZE+1, CELL_SIZE-2, CELL_SIZE-2);

}

}, 200);

//推迟显示Failed

setTimeout(function(){

if(textFormed) {

explode();

setTimeout(function() {

createText("FAILED");

}, 810);

} else {

createText("FAILED");

}

}, 800);

}

上面代码里的localstorage是html5的本地数据存储。因为不是运用很难,所以具体看代码。

整个特效是运用了createjs插件。要引入几个文件。

easeljs-0.7.1.min.js,EasePacj.min.js,requestAnimationFrame.js和TweenLite.min.js 游戏重新开始就要清除特效。我看api里我第一眼望过去最明显的就是removeAllChildren(),所以就选了这个。其他的改进日后再说。

//清除特效

this.stage.removeAllChildren();

this.textStage.removeAllChildren();

function initAnim() {

initStages();

initText();

initCircles();

//在stage下方添加文字——按任意键重新开始游戏.

tmp = new createjs.Text("t", "12px 'Source Sans Pro'", "#54555C");

tmp.textAlign = 'center';

tmp.x = 180;

tmp.y=350;

tmp.text = "按任意键重新开始游戏";

stage.addChild(tmp);

animate();

}

initAnim

上面初始化了一个stage,用于存放特效,一个textstage,用于形成“FAILED”的像素图片。还有一个按任意键重新游戏的提示。同时开始每隔一段时间就刷新stage。

根据block的位置来初始化小圆点。

function initCircles() {

circles = [];

var p=[];

var count=0;

for(let i=0; i

for(let j=0;j

if(tetris_status[i][j]!=NO_BLOCK)

p.push({'x':j*CELL_SIZE+2, 'y':i*CELL_SIZE+2, 'w':CELL_SIZE-3, 'h':CELL_SIZE-4});

for(var i=0; i<250; i++) {

var circle = new createjs.Shape();

var r = 7;

//x和y范围限定在黑色block内

var x = p[count]['x']+p[count]['w']*Math.random();

var y = p[count]['y']+p[count]['h']*Math.random();

count++;

if(count>=p.length)

count=0;

var color = colors[Math.floor(i%colors.length)];

var alpha = 0.2 + Math.random()*0.5;

circle.alpha = alpha;

circle.radius = r;

circle.graphics.beginFill(color).drawCircle(0, 0, r);

circle.x = x;

circle.y = y;

circles.push(circle);

stage.addChild(circle);

circle.movement = 'float';

tweenCircle(circle);

}

}

initCircles

然后再讲显示特效Failed的createText()。先将FAILED的text显示在textstage里,然后ctx.getImageData.data获取像素数据,并以此来为每个小圆点定义位置。

function createText(t) {

curText=t;

var fontSize = 500/(t.length);

if (fontSize > 80) fontSize = 80;

text.text = t;

text.font = "900 "+fontSize+"px 'Source Sans Pro'";

text.textAlign = 'center';

text.x = TETRIS_COLS*CELL_SIZE/2;

text.y = 0;

textStage.addChild(text);

textStage.update();

var ctx = document.getElementById('text').getContext('2d');

var pix = ctx.getImageData(0,0,600,200).data;

textPixels = [];

for (var i = pix.length; i >= 0; i -= 4) {

if (pix[i] != 0) {

var x = (i / 4) % 600;

var y = Math.floor(Math.floor(i/600)/4);

if((x && x%8 == 0) && (y && y%8 == 0)) textPixels.push({x: x, y: y});

}

}

formText();

textStage.clear();//清楚text的显示

}

CreateText

跟着代码的节奏走,我们现在来到了formtext.

function formText() {

for(var i= 0, l=textPixels.length; i

circles[i].originX = offsetX + textPixels[i].x;

circles[i].originY = offsetY + textPixels[i].y;

tweenCircle(circles[i], 'in');

}

textFormed = true;

if(textPixels.length < circles.length) {

for(var j = textPixels.length; j

circles[j].tween = TweenLite.to(circles[j], 0.4, {alpha: 0.1});

}

}

}

formtext

explode()就是讲已组成字的小圆点给重新遣散。

动画实现是使用了tweenlite.

function tweenCircle(c, dir) {

if(c.tween) c.tween.kill();

if(dir == 'in') {

/*TweenLite.to 改变c实例的x坐标,y坐标,使用easeInOut弹性函数,透明度提到1,改变大小,radius,总用时0.4s*/

c.tween = TweenLite.to(c, 0.4, {x: c.originX, y: c.originY, ease:Quad.easeInOut, alpha: 1, radius: 5, scaleX: 0.4, scaleY: 0.4, onComplete: function() {

c.movement = 'jiggle';/*轻摇*/

tweenCircle(c);

}});

} else if(dir == 'out') {

c.tween = TweenLite.to(c, 0.8, {x: window.innerWidth*Math.random(), y: window.innerHeight*Math.random(), ease:Quad.easeInOut, alpha: 0.2 + Math.random()*0.5, scaleX: 1, scaleY: 1, onComplete: function() {

c.movement = 'float';

tweenCircle(c);

}});

} else {

if(c.movement == 'float') {

c.tween = TweenLite.to(c, 5 + Math.random()*3.5, {x: c.x + -100+Math.random()*200, y: c.y + -100+Math.random()*200, ease:Quad.easeInOut, alpha: 0.2 + Math.random()*0.5,

onComplete: function() {

tweenCircle(c);

}});

} else {

c.tween = TweenLite.to(c, 0.05, {x: c.originX + Math.random()*3, y: c.originY + Math.random()*3, ease:Quad.easeInOut,

onComplete: function() {

tweenCircle(c);

}});

}

}

}

TweenLite.to函数第一个参数,要做动画的实例,第二个参数,事件,第三个参数,动画改变参数。

Quad.easeInOut()意思是在动画开始和结束时缓动。onComplete动画完成时调用的函数。易得,在我们的应用中,我们将开始下一次动画。

个人感言

其实刚开始没想做这么复杂,所以文件排的比较随意,然后就导致了后期项目完成时那副杂乱无章的样子。^_^,以后改。等我等看懂动画效果时在说,现在用的有点半懵半懂。

这篇博客写得有点乱。新手之作,就先这样吧。同上,以后改。因为不知道这个项目会不会拿来直接当我们计算机职业实践的作业。要是的话,我就彻改,连同博客。

以下是源代码地址。

还在审核。明天再说。相信我,我在小番茄里做了记录。

总结

以上所述是小编给大家介绍的Html5写一个简单的俄罗斯方块小游戏,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

用html写游戏,Html5写一个简单的俄罗斯方块小游戏相关推荐

  1. python简单小游戏代码_一个简单的python小游戏---七彩同心圆

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理 用pygame做一个简单的python小游戏-七彩同心圆 玩法:每次点击鼠标时,会以鼠标为圆心,不断 ...

  2. Friends——一个简单的Processing小游戏

    Friends--一个简单的Processing小游戏 背景前言 人类永远是矛盾的个体,我们一边喜爱着无垠的夜空,一边又恐惧着深邃的孤独- 在无边无际的的黑夜中,每一个光点都是一个孤独的个体,他们本应 ...

  3. 原生JS实现一个简单的打字小游戏

    原生JS实现一个简单的打字小游戏 利用javascript制作一个简单的打字小游戏 之前写了一个贪吃蛇小游戏好像反响不错 今天我来写一个比贪吃蛇更low更简单的打字小游戏 打字小游戏原理 接下来咋们直 ...

  4. 一个简单的纸牌小游戏

    一个简单的纸牌小游戏 初始化页面布局 function initView(){var html = html2 = '';for(var i=1;i<=10;i++){html += '< ...

  5. 用pygame做一个简单的python小游戏---贪吃蛇

    用pygame做一个简单的python小游戏-贪吃蛇 贪吃蛇游戏博客链接:(方法一样,语言不一样) c++贪吃蛇:https://blog.csdn.net/weixin_46791942/artic ...

  6. 用pygame做一个简单的python小游戏---七彩同心圆

    用pygame做一个简单的python小游戏-七彩同心圆 这个小游戏原是我同学python课的课后作业,并不是很难,就简单实现了一下,顺便加强一下pygame库的学习. 玩法:每次点击鼠标时,会以鼠标 ...

  7. 用pygame做一个简单的python小游戏---生命游戏

    用pygame做一个简单的python小游戏-生命游戏 生命游戏(Game of Life) 生命游戏(Game of Life)是剑桥大学约翰·何顿·康威(John Horton Conway)教授 ...

  8. python七彩同心圆_用pygame做一个简单的python小游戏---七彩同心圆

    用pygame做一个简单的python小游戏---七彩同心圆 用pygame做一个简单的python小游戏-七彩同心圆 这个小游戏原是我同学python课的课后作业,并不是很难,就简单实现了一下,顺便 ...

  9. 做一个简单的java小游戏--单机版五子棋

    做一个简单的java小游戏–单机版五子棋 学了java有一段时间了,今天就来搞一个简单的单机版五子棋游戏. 实现功能:那必须能进行基础的输赢判断.还有重新开始的功能,悔棋的功能,先手设置的功能和退出的 ...

  10. 制作一个简单的switch小游戏

    好的,那么,我们可以这样来制作一个简单的 switch 小游戏: 首先,我们需要先引入所需的库,如 stdio.h 和 stdlib.h. 接着,我们可以使用 printf 和 scanf 函数来输出 ...

最新文章

  1. 工作300:处理预览界面
  2. (王道408考研操作系统)第三章内存管理-第一节6-4:非连续分配管理方式之基本分页存储管理之两级页表
  3. python已知有列表_python 列表常用方法
  4. python基础知识学习笔记(2)
  5. HTTP 如何传输大文件
  6. python语法糖怎么用_程序中的奇技淫巧之语法糖-释然
  7. Linux文件描述符和输入输出重定向
  8. springboot vue导出excel 使用easypoi
  9. 2011天涯上令人心酸至极的微瞬间
  10. 删除只读属性的文件夹及其子文件
  11. 陆奇天团二期成团,清北硕博超七成
  12. caffe cmake matlab,编译caffe时候抛出的错误
  13. 在CentOS7上源码安装MongoDB 3.2.7
  14. 【问题记录】ABP框架模板页面样式加载不完全
  15. Linux常用环境软件安装(提供对应安装包)
  16. VBA颜色转换 中英互译
  17. encode decode
  18. 全数字OQPSK调制解调的基本算法,包括成形滤波器、NCO模型、载波恢复
  19. 数字信号与图像处理实验一:信号处理基础
  20. h61 nvme硬盘_NVMe和PCIE谁对固态硬盘的影响更大?

热门文章

  1. adprw指令教程_三菱FX5U模拟量,通信,运动控制详解
  2. Unity5.6——VideoPlayer播放
  3. excel 将日期转换为8位数字
  4. python爬视频网站数据_Python爬虫:B站排行榜视频播放量,视频评论量等数据采集...
  5. 如何用计算机术语写论文,计算机毕业论文结论怎么写?
  6. 2020年全国大学生数学建模竞赛应该如何准备
  7. 弘玑Cyclone上榜36氪中国超自动化先锋企业
  8. ORA-1652: unable to extend temp segment by 128 in tablespace TEMP
  9. 炫界 (667) -(回应骑两小)_西番莲小棚架高产优质栽培技术,既能高产,又能保证质量...
  10. Win10 快速检查修复系统方法