github地址:https://github.com/YangLin2510/snake_game
最近学习了一下React前端开发框架,可以基于组件来开发可复用的前端界面。
写一个小项目来综合练习下
创建项目
用create-react-app 命令行工具创建项目: (create-react-app 命令可以通过 npm 安装)
create-react-app snake_game
项目目录结构如下:
src 目录
在src下建一个snake_game目录里面并建一个snake_game.js 文件
页面
先实现一个静态页面
|
|得分:0
|
|
游戏区 |-----------
|
| 暂停
|
|
|
|
class Game extends Component {
render(props){
var trs = this.refresh();
var pasuse_start_btn = <button onClick={this.pause}>暂停</button>;
if(!this.state.is_runing){
pasuse_start_btn =<button onClick={this.start}>开始</button>;
}
return (
<div className="game">
<div className="game_name">贪吃蛇</div>
<div className="game_menu"><a href="#">游戏</a><a href="#">帮助</a></div>
<div className="game_div">
<div className="game_background">
<table border="1">
{trs}
</table>
</div>
<div className="game_info">
<h4>得分: {this.state.score} </h4>
<h4>控制 W:上 S:下 A:左 D:右</h4>
{pasuse_start_btn}
<button onClick={this.init}>重新开始</button>
</div>
</div>
</div>
);
}
}
还有对应的css, 放到单独的样式文件
.game {
background-color: white;
width: 60%;
padding:16px 20%;
}
.game_name {
text-align: center;
font-weight: bold;
}
.game_menu a {
padding-right: 16px;
}
.game_background {
width: 60%;
float: left;
}
.game_background table {
border-collapse: collapse;
}
.game_background table td{
width: 32px;
height: 32px;
padding: 0;
margin: 0;
}
.game_info {
width: 40%;
float: left;
}
.game_background table td img {
margin: 0;
padding: 0;
}
.red {
background-color: red;
width: 100%;
height:100%;
}
.black {
background-color: black;
width: 100%;
height:100%;
}
组件的内部状态
var snake = [{"x":1,"y":1}];
var next_red = {"x":1,"y":3};
this.state = {"score":0,"snake":snake,"red_node_position":next_red,"direction":direction.right,size:{"col":15,"row":20},"i":"","is_runing":true};
snake: 贪吃蛇,用一个位置数组标识
score: 分数
red_node_position: 贪吃蛇食物的位置
direction:前进的方向
size: 背景的大小 初始为 15 * 20
is_running: 是否是暂停状态
实现贪吃蛇的前进
1.贪吃蛇的第一个元素上下左右移动一位
2.后面的元素依次占据它前面元素的位置
3.如果碰到了随机生成的点,则在移动之后再在末尾加一个元素。
move(d){
var snake = this.state.snake;
var s;
var first = {"x":snake[0].x,"y":snake[0].y};
var get_red = false;
var last_node={};
var game_over_flag = false;
if(d == direction.right){
first.y +=1;
}
else if(d == direction.left){
first.y -=1;
}
else if(d == direction.down){
first.x +=1;
}
else if (d == direction.up) {
first.x -=1;
}
if(snake.length >1 && first.x == snake[1].x && first.y == snake[1].y){//前进方向不能逆行
return ;
}
if(first.x == this.state.red_node_position.x && first.y == this.state.red_node_position.y){//碰到红点
get_red = true;
this.setState({"score":this.state.score+1})
var last_node_index = snake.length-1;
last_node.x = snake[last_node_index].x;
last_node.y = snake[last_node_index].y;
}
for(s in snake){
var next_first = {"x":snake[s].x,"y":snake[s].y};
snake[s].x = first.x;
snake[s].y = first.y;
first = next_first;
}
if(get_red){//追加一个元素
get_red = false;
var i = snake.length;
snake[i] = {"x":last_node.x,"y":last_node.y};
this.next_red();
}
this.setState(snake);
this.game_over_check();
}
游戏结束检测
判断条件,snake 数组中有元素超出了边界或者snake元素存在重复(碰撞)
game_over_check(){
//游戏结束监测
//1.超出边界
if(this.state.snake[0].x >= this.state.size.col || this.state.snake[0].x < 0 || this.state.snake[0].y < 0 || this.state.snake[0].y >= this.state.size.row){
this.init();
}
//2.和自己冲突
for(var s=1;s < this.state.snake.length && this.state.snake.length>1;s++){
if(this.state.snake[s].x == this.state.snake[0].x && this.state.snake[s].y == this.state.snake[0].y){
this.init();
}
}
}
生成下一个食物点
随机生成一个点,必须要去掉贪吃蛇占据的位置
先把所有不包括贪吃蛇位置的元素放到一个数组,然后随机选择一个。
next_red(){
//生成下一个红点
var nodes = new Array();
for(let c = 0;c < this.state.size.col;c ++){
for(let r =0;r < this.state.size.row;r ++){
if(this.get_status(c,r)==0){
var n = {"x":c,"y":r};
nodes.push(n);
}
}
}
var next_index = Math.round(Math.random()*nodes.length-1);
this.setState({"red_node_position":nodes[next_index]})
}
刷新界面
根据现在的内部数据生成界面
/* 刷新界面 */
refresh(){
var background = new Array();
for(let c = 0;c<this.state.size.col;c++){
background[c] = new Array();
for(let r = 0;r < this.state.size.row;r++){
background[c][r] = this.get_status(c,r);
}
}
return background.map(function(value,index,array){
var tds = value.map(function(v,i,a){
if(v==0){
return <td></td>
}
else if(v==1){
return <td>{black}</td>
}
else if (v ==2) {
return <td>{red}</td>
}
});
return <tr>{tds}</tr>;
});
}
暂停和开始
开始: 创建一个定时器,每隔一秒钟调用一次move
暂停: 去掉定时器
start(){
var i = setInterval(function(){
this.move(this.state.direction);
}.bind(this),600);
this.setState({"i":i,"is_runing":true});
}
pause(){
var i = this.state.i;
window.clearInterval(i);
this.setState({"is_runing":false});
}
游戏控制和初始化
componentDidMount 生命周期函数会在界面加载完成后自动调用。
如果按下了wsad键,则根据按键改变前进的方向。
调用start()函数开始游戏
componentDidMount(){
document.onkeydown = function(e){
this.pause();
var keyNum = window.event ? e.keyCode : e.which;
if(keyNum == 87){
this.move(direction.up);
this.setState({"direction":direction.up});
}
else if (keyNum == 83) {
this.move(direction.down);
this.setState({"direction":direction.down});
}
else if (keyNum == 65) {
this.move(direction.left);
this.setState({"direction":direction.left});
}
else if (keyNum == 68) {
this.move(direction.right);
this.setState({"direction":direction.right});
}
this.start();
}.bind(this);
this.start();
}