ts项目 — 贪吃蛇
querySelectorAll 获取的元素是死的,页面变化需要重新获取getElementsByTagName 获取的元素是活的,页面变化不需要重新获取
1.项目初始化
- package.json
设置好这个文件终端输入npm i
{
"name": "part2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack serve --open chrome.exe"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/preset-env": "^7.12.7",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0",
"core-js": "^3.8.0",
"css-loader": "^5.0.1",
"html-webpack-plugin": "^4.5.0",
"less": "^3.12.2",
"less-loader": "^7.1.0",
"postcss": "^8.1.13",
"postcss-loader": "^4.1.0",
"postcss-preset-env": "^6.7.0",
"style-loader": "^2.0.0",
"ts-loader": "^8.0.11",
"typescript": "^4.1.2",
"webpack": "^5.6.0",
"webpack-cli": "^4.2.0",
"webpack-dev-server": "^3.11.0"
}
}
- tsconfig.json
{
"compilerOptions": {
"module": "ES2015",
"target": "ES2015",
"strict": true,
"noEmitOnError": true
}
}
- webpack.config.js
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: "./src/index.ts",
output: {
path: path.resolve(__dirname, 'dist'),
filename: "bundle.js",
environment:{
arrowFunction: false,
const: false
}
},
module: {
rules: [
{
test: /\.ts$/,
use: [
{
loader:"babel-loader",
options: {
presets:[
[
"@babel/preset-env",
{
targets:{
"chrome":"58",
"ie":"11"
},
"corejs":"3",
"useBuiltIns":"usage"
}
]
]
}
},
'ts-loader'
],
exclude: /node-modules/
},
{
test: /\.less$/,
use:[
"style-loader",
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions:{
plugins:[
[
"postcss-preset-env",
{
browsers: 'last 2 versions'
}
]
]
}
}
},
"less-loader"
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HTMLWebpackPlugin({
template: "./src/index.html"
}),
],
resolve: {
extensions: ['.ts', '.js']
},
mode:'development'
};
- 新建
src 文件夹,创建index.ts 和 index.html - src文件夹内,新增modules文件夹和style文件夹
2.编写结构和样式
src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>贪吃蛇</title>
</head>
<body>
<div id="main">
<div id="stage">
<div id="snake">
<div ></div>
</div>
<div id="food">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<div id="score-panel">
<div>SCORE: <span id="score">0</span></div>
<div>LEVEL: <span id="level">1</span></div>
</div>
</div>
</body>
</html>
src/style/index.less
//设置颜色变量
@bg-color:#b7d4a8;
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#main {
display: flex;
flex-flow: column;
align-items: center;
justify-content: space-around;
margin: 0 auto;
width: 360px;
height: 420px;
border: 10px solid #000;
background-color: @bg-color;
border-radius: 30px;
#stage{
position: relative;
width: 304px;
height: 304px;
border: 2px solid #000;
#snake{
&>div{
position: absolute;
width: 10px;
height: 10px;
background-color: #000;
border: 1px solid @bg-color;
}
}
&>#food{
display: flex;
flex-flow: row wrap;
justify-content: space-between;
align-content: space-between;
position: absolute;
top: 10px;
left: 10px;
width: 10px;
height: 10px;
&>div{
width: 4px;
height: 4px;
background: #000;
transform: rotate(45deg);
}
}
}
#score-panel{
width: 300px;
display: flex;
align-items: center;
justify-content: space-between;
}
}
body {
font:bold 20px "Courier";
}
3.模块化
src/modules/Food.ts
class Food{
element:HTMLElement;
constructor(){
this.element=document.getElementById('food')!;
}
get X() {
return this.element.offsetLeft
}
get Y() {
return this.element.offsetTop
}
change() {
this.element.style.left=Math.ceil(Math.random()*29)*10+'px';
this.element.style.top=Math.ceil(Math.random()*29)*10+'px';
}
}
export default Food;
src/modules/ScorePanel.ts
class ScorePanel{
score:number=0;
level:number=1;
scoreEle:HTMLElement;
levelEle:HTMLElement;
maxLevel:number;
upScore:number;
constructor(maxLevel:number=10,upScore:number=10){
this.scoreEle= document.getElementById('score')!;
this.levelEle= document.getElementById('level')!;
this.maxLevel=maxLevel;
this.upScore = upScore;
}
addScore(){
this.score++;
this.scoreEle.innerHTML = this.score + '';
if(this.score % this.upScore === 0){
this.levelUp();
}
}
levelUp(){
if(this.level<this.maxLevel){
this.levelEle.innerHTML = ++this.level + '';
}
}
}
export default ScorePanel;
src/modules/Snake.ts
class Snake{
head:HTMLElement;
bodies:HTMLCollection;
element:HTMLElement;
constructor(){
this.element = document.getElementById('snake')!;
this.head = document.querySelector('#snake>div')!;
this.bodies=this.element.getElementsByTagName('div')!;
}
public get X(){
return this.head.offsetLeft;
}
public get Y(){
return this.head.offsetTop;
}
set X(value:number){
if(this.X === value){
return;
}
if(value<0 || value>290){
throw new Error('蛇撞墙了!!')
}
if(this.bodies[1]&&(this.bodies[1] as HTMLElement).offsetLeft === value){
if(value>this.X){
value -= 20
}else{
value += 20
}
}
this.moveBody();
this.head.style.left = value + 'px';
this.checkHeadBody();
}
set Y(value:number){
if(this.Y === value){
return
}
if(value<0 || value>290){
throw new Error('蛇撞墙了!!')
}
if(this.bodies[1]&&(this.bodies[1] as HTMLElement).offsetTop === value){
if(value>this.Y){
value -= 20
}else{
value += 20
}
}
this.moveBody();
this.head.style.top = value + 'px';
this.checkHeadBody();
}
addBody(){
this.element.insertAdjacentElement('beforeend',document.createElement('div'))
}
moveBody(){
for(let i = this.bodies.length-1;i>0;i--){
let X = (this.bodies[i-1] as HTMLElement).offsetLeft;
let Y = (this.bodies[i-1] as HTMLElement).offsetTop;
(this.bodies[i] as HTMLElement).style.left = X + 'px';
(this.bodies[i] as HTMLElement).style.top = Y + 'px';
}
}
checkHeadBody(){
for (let i = 1; i < this.bodies.length; i++) {
if(this.X === (this.bodies[i] as HTMLElement).offsetLeft &&this.Y === (this.bodies[i] as HTMLElement).offsetTop){
throw new Error('撞到自己了!')
}
}
}
}
export default Snake
src/modules/GameControl.ts
import Food from "./Food";
import ScorePanel from "./ScorePanel";
import Snake from "./Snake";
class GameControl{
snake:Snake;
food:Food;
scorePanel:ScorePanel;
direction:string=' ';
isLive = true;
constructor(){
this.snake = new Snake();
this.food = new Food();
this.scorePanel = new ScorePanel();
this.init();
this.run();
}
init(){
document.addEventListener('keydown',this.keydownHandler.bind(this))
}
keydownHandler(event:KeyboardEvent){
const keyhefa = ['ArrowUp','ArrowDown','ArrowLeft','ArrowRight','Up','Down','Left','Right',' ']
if (keyhefa.indexOf(event.key)>-1) {
this.direction = event.key;
}
}
run(){
let X = this.snake.X;
let Y = this.snake.Y;
switch(this.direction){
case 'ArrowUp':
case 'UP':
Y -= 10;
break;
case 'ArrowDown':
case 'Down':
Y +=10;
break;
case 'ArrowRight':
case 'Right':
X += 10;
break;
case 'ArrowLeft':
case 'Left':
X -= 10;
break;
case ' ':
break;
default:
break;
}
this.eatHandler(X,Y)
try{
this.snake.X = X;
this.snake.Y = Y;
}catch (e:any){
alert(e.message)
this.isLive = false;
}
this.isLive && setTimeout(this.run.bind(this), 300-(this.scorePanel.level-1)*30)
}
eatHandler(X:number,Y:number){
if(X === this.food.X && Y ===this.food.Y){
this.food.change();
this.scorePanel.addScore();
this.snake.addBody();
}
}
}
export default GameControl
src/index.ts
import './style/index.less'
import GameControl from './modules/GameControl'
new GameControl();
|