@ -0,0 +1,23 @@ |
|||||||
|
.DS_Store |
||||||
|
node_modules |
||||||
|
/dist |
||||||
|
|
||||||
|
|
||||||
|
# local env files |
||||||
|
.env.local |
||||||
|
.env.*.local |
||||||
|
|
||||||
|
# Log files |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
pnpm-debug.log* |
||||||
|
|
||||||
|
# Editor directories and files |
||||||
|
.idea |
||||||
|
.vscode |
||||||
|
*.suo |
||||||
|
*.ntvs* |
||||||
|
*.njsproj |
||||||
|
*.sln |
||||||
|
*.sw? |
@ -0,0 +1,24 @@ |
|||||||
|
# acapp_snake |
||||||
|
|
||||||
|
## Project setup |
||||||
|
``` |
||||||
|
npm install |
||||||
|
``` |
||||||
|
|
||||||
|
### Compiles and hot-reloads for development |
||||||
|
``` |
||||||
|
npm run serve |
||||||
|
``` |
||||||
|
|
||||||
|
### Compiles and minifies for production |
||||||
|
``` |
||||||
|
npm run build |
||||||
|
``` |
||||||
|
|
||||||
|
### Lints and fixes files |
||||||
|
``` |
||||||
|
npm run lint |
||||||
|
``` |
||||||
|
|
||||||
|
### Customize configuration |
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/). |
@ -0,0 +1,5 @@ |
|||||||
|
module.exports = { |
||||||
|
presets: [ |
||||||
|
'@vue/cli-plugin-babel/preset' |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"target": "es5", |
||||||
|
"module": "esnext", |
||||||
|
"baseUrl": "./", |
||||||
|
"moduleResolution": "node", |
||||||
|
"paths": { |
||||||
|
"@/*": [ |
||||||
|
"src/*" |
||||||
|
] |
||||||
|
}, |
||||||
|
"lib": [ |
||||||
|
"esnext", |
||||||
|
"dom", |
||||||
|
"dom.iterable", |
||||||
|
"scripthost" |
||||||
|
] |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
{ |
||||||
|
"name": "acapp_snake", |
||||||
|
"version": "0.1.0", |
||||||
|
"private": true, |
||||||
|
"scripts": { |
||||||
|
"serve": "vue-cli-service serve", |
||||||
|
"build": "vue-cli-service build", |
||||||
|
"lint": "vue-cli-service lint" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"core-js": "^3.8.3", |
||||||
|
"jquery": "^3.6.1", |
||||||
|
"vue": "^3.2.13", |
||||||
|
"vuex": "^4.0.0" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@babel/core": "^7.12.16", |
||||||
|
"@babel/eslint-parser": "^7.12.16", |
||||||
|
"@vue/cli-plugin-babel": "~5.0.0", |
||||||
|
"@vue/cli-plugin-eslint": "~5.0.0", |
||||||
|
"@vue/cli-plugin-vuex": "~5.0.0", |
||||||
|
"@vue/cli-service": "~5.0.0", |
||||||
|
"eslint": "^7.32.0", |
||||||
|
"eslint-plugin-vue": "^8.0.3" |
||||||
|
}, |
||||||
|
"eslintConfig": { |
||||||
|
"root": true, |
||||||
|
"env": { |
||||||
|
"node": true |
||||||
|
}, |
||||||
|
"extends": [ |
||||||
|
"plugin:vue/vue3-essential", |
||||||
|
"eslint:recommended" |
||||||
|
], |
||||||
|
"parserOptions": { |
||||||
|
"parser": "@babel/eslint-parser" |
||||||
|
}, |
||||||
|
"rules": {} |
||||||
|
}, |
||||||
|
"browserslist": [ |
||||||
|
"> 1%", |
||||||
|
"last 2 versions", |
||||||
|
"not dead", |
||||||
|
"not ie 11" |
||||||
|
] |
||||||
|
} |
After Width: | Height: | Size: 4.2 KiB |
@ -0,0 +1,17 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang=""> |
||||||
|
<head> |
||||||
|
<meta charset="utf-8"> |
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0"> |
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> |
||||||
|
<title><%= htmlWebpackPlugin.options.title %></title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<noscript> |
||||||
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> |
||||||
|
</noscript> |
||||||
|
<div id="app"></div> |
||||||
|
<!-- built files will be auto injected --> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,21 @@ |
|||||||
|
<template> |
||||||
|
<PlayGround /> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import PlayGround from '@/components/PlayGround' |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'App', |
||||||
|
components: { |
||||||
|
PlayGround, |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
/* div.playground { |
||||||
|
width: 80vw; |
||||||
|
height: 80vh; |
||||||
|
} */ |
||||||
|
</style> |
@ -0,0 +1,47 @@ |
|||||||
|
const AC_GAME_OBJECTS = []; |
||||||
|
|
||||||
|
export class AcGameObject { |
||||||
|
constructor() { |
||||||
|
AC_GAME_OBJECTS.push(this); |
||||||
|
this.timedelta = 0; |
||||||
|
this.has_called_start = false; |
||||||
|
} |
||||||
|
start() { |
||||||
|
|
||||||
|
} |
||||||
|
update() { |
||||||
|
|
||||||
|
} |
||||||
|
on_destroy() { |
||||||
|
|
||||||
|
} |
||||||
|
destroy() { |
||||||
|
this.on_destroy(); |
||||||
|
|
||||||
|
for (let i in AC_GAME_OBJECTS) { |
||||||
|
const obj = AC_GAME_OBJECTS[i]; |
||||||
|
if (obj === this) { |
||||||
|
AC_GAME_OBJECTS.splice(i, 1); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
let last_timestamp; |
||||||
|
const step = timestamp => { |
||||||
|
for (let obj of AC_GAME_OBJECTS) { |
||||||
|
if (!obj.has_called_start) { |
||||||
|
obj.start(); |
||||||
|
obj.has_called_start = true; |
||||||
|
} else { |
||||||
|
obj.timedelta = timestamp - last_timestamp; |
||||||
|
obj.update(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
last_timestamp = timestamp; |
||||||
|
requestAnimationFrame(step); |
||||||
|
}; |
||||||
|
|
||||||
|
requestAnimationFrame(step); |
@ -0,0 +1,8 @@ |
|||||||
|
export class Cell { |
||||||
|
constructor(i, j) { |
||||||
|
this.i = i; |
||||||
|
this.j = j; |
||||||
|
this.x = i + 0.5; |
||||||
|
this.y = j + 0.5; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,171 @@ |
|||||||
|
import { AcGameObject } from "./AcGameObject"; |
||||||
|
import { Snake } from "./Snake"; |
||||||
|
import $ from 'jquery'; |
||||||
|
|
||||||
|
export class GameMap extends AcGameObject { |
||||||
|
constructor(ctx, parent, store) { |
||||||
|
super(); |
||||||
|
|
||||||
|
this.ctx = ctx; |
||||||
|
this.parent = parent; |
||||||
|
this.store = store; |
||||||
|
this.L = 0; |
||||||
|
|
||||||
|
this.snake = new Snake(this.ctx, this); |
||||||
|
this.directions = []; |
||||||
|
|
||||||
|
this.status = "waiting"; // waiting -> playing -> win/lose
|
||||||
|
} |
||||||
|
|
||||||
|
start() { |
||||||
|
this.ctx.canvas.focus(); |
||||||
|
this.ctx.canvas.addEventListener('keydown', e => { |
||||||
|
if (this.store.state.restart) return false; |
||||||
|
|
||||||
|
if (e.key === 'w' || e.key === 'ArrowUp') { |
||||||
|
this.directions.push(0); |
||||||
|
this.snake.eye_direction = 0; |
||||||
|
e.preventDefault(); |
||||||
|
} |
||||||
|
else if (e.key === 'd' || e.key == 'ArrowRight') { |
||||||
|
this.directions.push(1); |
||||||
|
this.snake.eye_direction = 1; |
||||||
|
e.preventDefault(); |
||||||
|
} |
||||||
|
else if (e.key === 's' || e.key === 'ArrowDown') { |
||||||
|
this.directions.push(2); |
||||||
|
this.snake.eye_direction = 2; |
||||||
|
e.preventDefault(); |
||||||
|
} |
||||||
|
else if (e.key === 'a' || e.key === 'ArrowLeft') { |
||||||
|
this.directions.push(3); |
||||||
|
this.snake.eye_direction = 3; |
||||||
|
e.preventDefault(); |
||||||
|
} |
||||||
|
else if (e.key === 'M' || e.key === 'm') { |
||||||
|
if (this.store.state.music === true) { |
||||||
|
this.store.state.music = false; |
||||||
|
this.store.commit("updateMusic",false); |
||||||
|
const audio = document.querySelector('#player') |
||||||
|
audio.pause(); |
||||||
|
}else { |
||||||
|
this.store.state.music = true; |
||||||
|
this.store.commit("updateMusic",true); |
||||||
|
const audio = document.querySelector('#player') |
||||||
|
audio.currentTime = 0; |
||||||
|
audio.play(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
let k = this.directions.length; |
||||||
|
if (k > 1 && this.directions[k - 1] === this.directions[k - 2]) { |
||||||
|
this.directions.pop(); |
||||||
|
} |
||||||
|
|
||||||
|
while (this.directions.length > 2) |
||||||
|
this.directions.splice(0, 1); |
||||||
|
|
||||||
|
if (this.status === "waiting" && this.directions.length && this.directions[0] !== 3) { |
||||||
|
this.status = "playing"; |
||||||
|
this.snake.direction = this.directions[0]; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
update_size() { |
||||||
|
this.L = Math.min(this.parent.clientWidth / 17, this.parent.clientHeight / 15); |
||||||
|
this.ctx.canvas.width = this.L * 17; |
||||||
|
this.ctx.canvas.height = this.L * 15; |
||||||
|
} |
||||||
|
|
||||||
|
update_score() { |
||||||
|
$.ajax({ |
||||||
|
url: "https://app3359.acapp.acwing.com.cn/update_score/", |
||||||
|
type: "post", |
||||||
|
data: { |
||||||
|
score: this.store.state.score, |
||||||
|
}, |
||||||
|
headers: { |
||||||
|
'Authorization': "Bearer " + this.store.state.access, |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
win() { |
||||||
|
this.snake.color = "white"; |
||||||
|
this.status = "win"; |
||||||
|
this.store.commit('updateRestart', true); |
||||||
|
this.store.commit('updateNextStep',false); |
||||||
|
this.store.commit('updateMusic',false); |
||||||
|
this.store.state.audio.pause(); |
||||||
|
this.update_score(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
lose() { |
||||||
|
this.snake.color = "white"; |
||||||
|
this.status = "lose"; |
||||||
|
this.store.commit('updateRestart', true); |
||||||
|
this.store.commit('updateNextStep',false); |
||||||
|
this.store.commit("updateRecord",Math.max(this.store.state.score,this.store.state.record)); |
||||||
|
this.store.commit('updateMusic',false); |
||||||
|
if (this.store.state.music === false) { |
||||||
|
const audio = document.querySelector('#player') |
||||||
|
audio.pause() |
||||||
|
} |
||||||
|
this.update_score(); |
||||||
|
} |
||||||
|
|
||||||
|
restart() { |
||||||
|
this.store.state.score = 0; |
||||||
|
this.status = "waiting"; |
||||||
|
this.snake.destroy(); |
||||||
|
this.snake = new Snake(this.ctx, this); |
||||||
|
this.store.commit('updateRestart', false); |
||||||
|
this.store.commit('updateNextStep',false); |
||||||
|
this.ctx.canvas.focus(); |
||||||
|
// console.log(this.store.state.music);
|
||||||
|
if (this.store.state.music === true) { |
||||||
|
const audio = document.querySelector('#player') |
||||||
|
audio.currentTime = 0; |
||||||
|
audio.play() |
||||||
|
this.store.commit('updateMusic',true); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
upgrade() { |
||||||
|
// this.status = "waiting";
|
||||||
|
// this.snake.destroy();
|
||||||
|
if (this.store.state.music === true) { |
||||||
|
const audio = document.querySelector('#player') |
||||||
|
audio.currentTime = 0; |
||||||
|
audio.play() |
||||||
|
} |
||||||
|
|
||||||
|
this.snake = new Snake(this.ctx, this); |
||||||
|
this.snake.color = this.store.state.color; |
||||||
|
this.snake.speed = this.store.state.speed; |
||||||
|
this.snake.number = this.store.state.number;
|
||||||
|
this.store.commit("updateRecord",Math.max(this.store.state.score,this.store.state.record)); |
||||||
|
} |
||||||
|
|
||||||
|
update() { |
||||||
|
this.update_size(); |
||||||
|
this.render(); |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
let color_even = "#AAD751", color_odd = "#A2D149"; |
||||||
|
|
||||||
|
for (let i = 0; i < 17; i ++ ) { |
||||||
|
for (let j = 0; j < 15; j ++ ) { |
||||||
|
if ((i + j) % 2 == 0) { |
||||||
|
this.ctx.fillStyle = color_even;
|
||||||
|
} else { |
||||||
|
this.ctx.fillStyle = color_odd; |
||||||
|
} |
||||||
|
this.ctx.fillRect(i * this.L, j * this.L, this.L, this.L); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,290 @@ |
|||||||
|
import { AcGameObject } from './AcGameObject'; |
||||||
|
import { Cell } from './Cell'; |
||||||
|
|
||||||
|
export class Snake extends AcGameObject { |
||||||
|
constructor(ctx, gamemap) { |
||||||
|
super(); |
||||||
|
this.ctx = ctx; |
||||||
|
this.gamemap = gamemap; |
||||||
|
|
||||||
|
this.cells = []; |
||||||
|
this.color = "#4876ec"; |
||||||
|
this.dirs = [ |
||||||
|
{x: 0, y: -1}, |
||||||
|
{x: 1, y: 0}, |
||||||
|
{x: 0, y: 1}, |
||||||
|
{x: -1, y: 0}, |
||||||
|
]; |
||||||
|
this.direction = 2; |
||||||
|
this.eps = 1e-1; |
||||||
|
this.speed = 5; // 每秒钟走几格
|
||||||
|
this.apple_cell = new Cell(-1, -1); |
||||||
|
this.apple_img = new Image(); |
||||||
|
this.apple_img.src = "https://test.bnblogs.cc/acapp/images/apple/apple_00.png"; |
||||||
|
this.eating = false; |
||||||
|
this.tail_cell = null; |
||||||
|
this.number = 0; |
||||||
|
this.head = null; |
||||||
|
this.eye_direction = 1 // 初始的蛇眼睛朝右(0:上 1:右 2:下 3:左)
|
||||||
|
// 定义蛇左右眼的x和y坐标的偏移量
|
||||||
|
this.eyes_dx = [ |
||||||
|
[-1,1], |
||||||
|
[1,1], |
||||||
|
[-1,1], |
||||||
|
[-1,-1] |
||||||
|
]; |
||||||
|
|
||||||
|
this.eyes_dy = [ |
||||||
|
[-1,-1], |
||||||
|
[-1,1], |
||||||
|
[1,1], |
||||||
|
[-1,1] |
||||||
|
]; |
||||||
|
this.index = 0; // 当前的水果是哪个,来判断吃完之后的颜色
|
||||||
|
this.grades = 1; // 水果分值
|
||||||
|
} |
||||||
|
|
||||||
|
start() { |
||||||
|
this.cells.push(new Cell(4, 7)); |
||||||
|
for (let i = 4; i >= 1; i -- ) { |
||||||
|
this.cells.push(new Cell(i, 7)); |
||||||
|
} |
||||||
|
|
||||||
|
this.put_an_apple(); |
||||||
|
} |
||||||
|
|
||||||
|
put_an_apple() { |
||||||
|
const positions = new Set(); |
||||||
|
for (let i = 0; i < 17; i ++ ) { |
||||||
|
for (let j = 0; j < 15; j ++ ) { |
||||||
|
positions.add(`${i}-${j}`); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (let cell of this.cells) { |
||||||
|
positions.delete(`${cell.i}-${cell.j}`); |
||||||
|
} |
||||||
|
|
||||||
|
const items = Array.from(positions); |
||||||
|
if (items.length === 0) this.gamemap.win(); |
||||||
|
else { |
||||||
|
let [x, y] = items[Math.floor(Math.random() * items.length)].split('-'); |
||||||
|
x = parseInt(x), y = parseInt(y); |
||||||
|
this.apple_cell = new Cell(x, y); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
get_direction(a, b) { |
||||||
|
if (Math.abs(a.x - b.x) < this.eps && Math.abs(a.y - b.y) < this.eps) |
||||||
|
return -1; |
||||||
|
if (Math.abs(a.x - b.x) < this.eps) { |
||||||
|
if (b.y < a.y) return 0; |
||||||
|
return 2; |
||||||
|
} |
||||||
|
if (b.x > a.x) return 1; |
||||||
|
return 3; |
||||||
|
} |
||||||
|
|
||||||
|
check_die() { |
||||||
|
const head = this.cells[0]; |
||||||
|
if (head.i < 0 || head.i >= 17 || head.j < 0 || head.j >= 15) |
||||||
|
return true; |
||||||
|
|
||||||
|
for (let i = 2; i < this.cells.length; i ++ ) { |
||||||
|
if (head.i === this.cells[i].i && head.j === this.cells[i].j) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
update_body() { |
||||||
|
const k = this.cells.length - 1; |
||||||
|
const d = this.get_direction(this.cells[k], this.cells[k - 1]); |
||||||
|
|
||||||
|
if (d >= 0) { |
||||||
|
const distance = this.speed * this.timedelta / 1000; |
||||||
|
this.cells[k].x += this.dirs[d].x * distance; |
||||||
|
this.cells[k].y += this.dirs[d].y * distance; |
||||||
|
this.cells[0].x += this.dirs[this.direction].x * distance; |
||||||
|
this.cells[0].y += this.dirs[this.direction].y * distance; |
||||||
|
} else { |
||||||
|
const new_cells = []; |
||||||
|
const headi = this.cells[1].i + this.dirs[this.direction].x; |
||||||
|
const headj = this.cells[1].j + this.dirs[this.direction].y; |
||||||
|
new_cells.push(new Cell(headi, headj)); |
||||||
|
new_cells.push(new Cell(headi, headj)); |
||||||
|
this.head = new Cell(headi,headj); |
||||||
|
for (let i = 1; i < k; i ++ ) { |
||||||
|
new_cells.push(this.cells[i]); |
||||||
|
} |
||||||
|
this.cells = new_cells; |
||||||
|
|
||||||
|
if (this.eating) { |
||||||
|
this.cells.push(this.tail_cell); |
||||||
|
this.eating = false; |
||||||
|
this.tail_cell = null; |
||||||
|
} |
||||||
|
|
||||||
|
const ds = this.gamemap.directions; |
||||||
|
while (ds.length > 0 && (ds[0] === this.direction || ds[0] === (this.direction ^ 2))) |
||||||
|
ds.splice(0, 1); |
||||||
|
|
||||||
|
if (ds.length > 0) { |
||||||
|
this.direction = ds[0]; |
||||||
|
ds.splice(0, 1); |
||||||
|
} |
||||||
|
|
||||||
|
if (this.check_die()) { |
||||||
|
this.gamemap.lose(); |
||||||
|
} |
||||||
|
|
||||||
|
if (headi === this.apple_cell.i && headj === this.apple_cell.j) { |
||||||
|
if (this.gamemap.store.state.music === true) { |
||||||
|
const audio = document.querySelector('#eat_apple'); |
||||||
|
audio.play(); |
||||||
|
audio.currentTime=0.4 |
||||||
|
audio.playbackRate = 3 |
||||||
|
} |
||||||
|
this.eating = true; |
||||||
|
if (this.index === 5) { |
||||||
|
this.color = "#efefef"; |
||||||
|
this.grades = 5; |
||||||
|
} |
||||||
|
else if (this.index === 1) { |
||||||
|
this.color = "#ffd201"; |
||||||
|
this.grades = 5; |
||||||
|
} |
||||||
|
else if (this.index === 17) |
||||||
|
{ |
||||||
|
this.color = "#ff9999"; |
||||||
|
this.grades = 5; |
||||||
|
} |
||||||
|
else if (this.index === 7 || this.index === 0 || this.index === 8 || this.index === 12 || this.index === 20 || this.index === 19){ |
||||||
|
this.color= "#f2383b"; |
||||||
|
this.grades = 1; |
||||||
|
} |
||||||
|
else if (this.index === 11 || this.index === 13 || this.index === 14 || this.index === 15){ |
||||||
|
this.color= "#34b43d"; |
||||||
|
this.grades = 3; |
||||||
|
} |
||||||
|
else if (this.index === 3 || this.index === 6 || this.index === 10){ |
||||||
|
this.color = "#b445f1"; |
||||||
|
this.grades = 4; |
||||||
|
} |
||||||
|
else { |
||||||
|
this.color = "#ea881c"; |
||||||
|
this.grades = 2; |
||||||
|
} |
||||||
|
const cell = this.cells[this.cells.length - 1]; |
||||||
|
this.tail_cell = new Cell(cell.i, cell.j); |
||||||
|
this.put_an_apple(); |
||||||
|
|
||||||
|
// 换不同apple图片
|
||||||
|
const index_number = Math.round(this.randomNum(0,20)); |
||||||
|
this.index = index_number; |
||||||
|
let src_head = "https://test.bnblogs.cc/acapp/images/apple/apple_0"; |
||||||
|
const src_tail = ".png"; |
||||||
|
if (index_number >= 0 && index_number <=9) { |
||||||
|
this.apple_img.src = src_head + index_number + src_tail; |
||||||
|
}else { |
||||||
|
src_head = src_head.substring(0,src_head.length - 1); |
||||||
|
this.apple_img.src = src_head + index_number + src_tail; |
||||||
|
} |
||||||
|
console.log(this.grades); |
||||||
|
// console.log(this.apple_img.src,typeof this.apple_img.src);
|
||||||
|
const score = this.gamemap.store.state.score + this.grades; |
||||||
|
this.gamemap.store.commit('updateScore', score); |
||||||
|
this.gamemap.store.commit('updateRecord', Math.max(score,this.gamemap.store.state.record)); |
||||||
|
this.number++; |
||||||
|
this.gamemap.store.commit('updateNumber',this.number); |
||||||
|
|
||||||
|
if (this.number > 0 && this.number % 20 === 0) { |
||||||
|
// 吃20个进入下一回合,依次奖励10-20-30
|
||||||
|
this.speed = this.speed + 1; |
||||||
|
this.gamemap.store.commit("updateSpeed",this.speed); |
||||||
|
this.gamemap.store.commit("updateScore",score + 10 * (parseInt(this.number / 20))); |
||||||
|
this.gamemap.store.commit('updateRecord', Math.max(this.gamemap.store.state.score,this.gamemap.store.state.record)); |
||||||
|
this.gamemap.store.commit('updateColor',this.color); |
||||||
|
this.gamemap.snake.destroy(); |
||||||
|
|
||||||
|
this.gamemap.store.commit('updateNextStep',true); |
||||||
|
this.gamemap.status='waiting'; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
// console.log("num: ",this.number);
|
||||||
|
// console.log("speed: ",this.speed);
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
update() { |
||||||
|
if (this.gamemap.status === "playing") { |
||||||
|
this.update_body(); |
||||||
|
} |
||||||
|
this.render(); |
||||||
|
} |
||||||
|
|
||||||
|
randomNum (n,m) { |
||||||
|
var result = Math.random()*(m+1-n)+n; |
||||||
|
while(result>m) { |
||||||
|
result = Math.random()*(m+1-n)+n; |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
const L = this.gamemap.L; |
||||||
|
|
||||||
|
if (this.eating) { |
||||||
|
this.cells.push(this.tail_cell); |
||||||
|
} |
||||||
|
|
||||||
|
this.ctx.drawImage(this.apple_img, this.apple_cell.i * L, this.apple_cell.j * L, L, L); |
||||||
|
|
||||||
|
this.ctx.fillStyle = this.color; |
||||||
|
for (let cell of this.cells) { |
||||||
|
this.ctx.beginPath(); |
||||||
|
this.ctx.arc(cell.x * L, cell.y * L, L / 2 * 0.8, 0, Math.PI * 2); |
||||||
|
this.ctx.fill(); |
||||||
|
} |
||||||
|
|
||||||
|
for (let i = 1; i < this.cells.length; i ++ ) { |
||||||
|
const a = this.cells[i - 1], b = this.cells[i]; |
||||||
|
if (Math.abs(a.x - b.x) < this.eps && Math.abs(a.y - b.y) < this.eps) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if (Math.abs(a.x - b.x) < this.eps) { |
||||||
|
this.ctx.fillRect((a.x - 0.5 + 0.1) * L, Math.min(a.y, b.y) * L, L * 0.8, Math.abs(a.y - b.y) * L); |
||||||
|
} else { |
||||||
|
this.ctx.fillRect(Math.min(a.x, b.x) * L, (a.y - 0.5 + 0.1) * L, Math.abs(a.x - b.x) * L, L * 0.8); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (this.eye_direction !== -1) { |
||||||
|
// 画出蛇的眼睛
|
||||||
|
this.ctx.fillStyle = "black"; |
||||||
|
|
||||||
|
for (let i = 0; i < 2 ; i++) { |
||||||
|
const eye_x = (this.cells[0].x + this.eyes_dx[this.eye_direction][i] * 0.2) * L; |
||||||
|
const eye_y = (this.cells[0].y + this.eyes_dy[this.eye_direction][i] * 0.2) * L; |
||||||
|
|
||||||
|
// 画眼睛
|
||||||
|
|
||||||
|
this.ctx.beginPath(); |
||||||
|
this.ctx.arc(eye_x,eye_y,L*0.05,0,Math.PI*2); |
||||||
|
this.ctx.fill(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (this.eating) { |
||||||
|
this.cells.pop(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
import $ from 'jquery'; |
||||||
|
|
||||||
|
export const init = (store) => { |
||||||
|
const AcWingOS = store.state.AcWingOS; |
||||||
|
|
||||||
|
if (AcWingOS === "AcWingOS") return false; |
||||||
|
|
||||||
|
const vw = window.innerWidth; |
||||||
|
const vh = window.innerHeight; |
||||||
|
AcWingOS.api.window.resize(59.5 * vh / vw, 64.5); |
||||||
|
|
||||||
|
$.ajax({ |
||||||
|
url: "https://app3359.acapp.acwing.com.cn/apply_code/", |
||||||
|
type: "get", |
||||||
|
success: resp => { |
||||||
|
AcWingOS.api.oauth2.authorize(resp.appid, resp.redirect_uri, resp.scope, resp.state, resp => { |
||||||
|
if (resp.result === "success") { |
||||||
|
store.commit('updateAccess', resp.access); |
||||||
|
store.commit('updateRefresh', resp.refresh); |
||||||
|
|
||||||
|
setInterval(() => { |
||||||
|
$.ajax({ |
||||||
|
url: "https://app3359.acapp.acwing.com.cn/api/token/refresh/", |
||||||
|
type: "post", |
||||||
|
data: { |
||||||
|
refresh: resp.refresh, |
||||||
|
}, |
||||||
|
success: resp => { |
||||||
|
store.commit('updateAccess', resp.access); |
||||||
|
} |
||||||
|
}); |
||||||
|
}, 4.5 * 60 * 1000); |
||||||
|
} |
||||||
|
else { |
||||||
|
store.state.AcWingOS.api.window.close(); // 关闭acwing窗口
|
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,117 @@ |
|||||||
|
<template> |
||||||
|
<div class="gamemap" ref="div"> |
||||||
|
<canvas ref="canvas" tabindex="0"></canvas> |
||||||
|
<div class="operation" v-if="$store.state.restart"> |
||||||
|
<button @click="restart" v-if="$store.state.access !== ''">开始游戏</button> |
||||||
|
<button @click="show_ranklist" v-if="$store.state.access !== ''">排行榜</button> |
||||||
|
<button v-else>您没有授权该应用</button> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="operation" v-if="$store.state.upgrade"> |
||||||
|
<button @click="go_next_step">下一局</button> |
||||||
|
<button @click="cancel">放弃</button> |
||||||
|
</div> |
||||||
|
<RankList v-if="$store.state.ranklist" /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { ref, onMounted, onUpdated } from 'vue'; |
||||||
|
import { GameMap } from '@/assets/scripts/GameMap'; |
||||||
|
import { useStore } from 'vuex'; |
||||||
|
import { init } from '@/assets/scripts/init'; |
||||||
|
import RankList from './RankList'; |
||||||
|
import $ from 'jquery'; |
||||||
|
export default { |
||||||
|
name: "GameMap", |
||||||
|
components: { |
||||||
|
RankList, |
||||||
|
}, |
||||||
|
setup: () => { |
||||||
|
let div = ref(null); |
||||||
|
let canvas = ref(null); |
||||||
|
const store = useStore(); |
||||||
|
let gamemap = null; |
||||||
|
|
||||||
|
init(store); |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
gamemap = new GameMap(canvas.value.getContext('2d'), div.value, store); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
onUpdated(()=> { |
||||||
|
// 展示用户的最高分 |
||||||
|
$.ajax({ |
||||||
|
url: "https://app3359.acapp.acwing.com.cn/get_user_score/", |
||||||
|
type: "get", |
||||||
|
headers: { |
||||||
|
'Authorization': "Bearer " + store.state.access, |
||||||
|
}, |
||||||
|
success: resp => { |
||||||
|
store.commit("updateRecord",resp.me.score); |
||||||
|
} |
||||||
|
}); |
||||||
|
}) |
||||||
|
|
||||||
|
const restart = () => { |
||||||
|
gamemap.restart(); |
||||||
|
} |
||||||
|
|
||||||
|
const show_ranklist = () => { |
||||||
|
store.commit('updateRanklist', true); |
||||||
|
} |
||||||
|
|
||||||
|
const go_next_step = () => { |
||||||
|
gamemap.upgrade(); |
||||||
|
gamemap.ctx.canvas.focus(); |
||||||
|
store.commit('updateNextStep', false); |
||||||
|
} |
||||||
|
|
||||||
|
const cancel = () => { |
||||||
|
store.commit('updateNextStep', false); |
||||||
|
gamemap.lose(); |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
div, |
||||||
|
canvas, |
||||||
|
restart, |
||||||
|
show_ranklist, |
||||||
|
go_next_step, |
||||||
|
cancel, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
div.gamemap { |
||||||
|
height: calc(100% - 8vh); |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
canvas { |
||||||
|
background-color: #AAD751; |
||||||
|
} |
||||||
|
|
||||||
|
div.operation { |
||||||
|
position: absolute; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
button { |
||||||
|
background-color: #0d6efd; |
||||||
|
border: solid 0; |
||||||
|
border-radius: 5px; |
||||||
|
font-size: 3vh; |
||||||
|
color: white; |
||||||
|
padding: 3vh; |
||||||
|
cursor: pointer; |
||||||
|
margin: 0 0.5vh; |
||||||
|
} |
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,30 @@ |
|||||||
|
<template> |
||||||
|
<div class="playground"> |
||||||
|
<ScoreBoard /> |
||||||
|
<GameMap /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import ScoreBoard from '@/components/ScoreBoard'; |
||||||
|
import GameMap from '@/components/GameMap'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: "PlayGround", |
||||||
|
components: { |
||||||
|
ScoreBoard, |
||||||
|
GameMap |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
|
||||||
|
<style scoped> |
||||||
|
|
||||||
|
.playground { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
background-color: #578A34; |
||||||
|
} |
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,104 @@ |
|||||||
|
<template> |
||||||
|
<div class="ranklist"> |
||||||
|
<span class="close-ranklist" @click="close_ranklist">x</span> |
||||||
|
<div class="player" v-for="player in players" :key="player.id"> |
||||||
|
<div>#{{ player.rank }}</div> |
||||||
|
<div> |
||||||
|
<img :src="player.photo" alt=""> |
||||||
|
</div> |
||||||
|
<div>{{ player.username }}</div> |
||||||
|
<div>{{ player.score }}</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { ref } from 'vue'; |
||||||
|
import $ from 'jquery'; |
||||||
|
import { useStore } from 'vuex'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'RankList', |
||||||
|
setup() { |
||||||
|
const store = useStore(); |
||||||
|
let players = ref([]); |
||||||
|
$.ajax({ |
||||||
|
url: "https://app3359.acapp.acwing.com.cn/get_ranklist/", |
||||||
|
type: "get", |
||||||
|
headers: { |
||||||
|
'Authorization': "Bearer " + store.state.access, |
||||||
|
}, |
||||||
|
success: resp => { |
||||||
|
const new_players = [resp.me, ...resp.all]; |
||||||
|
let id = 0; |
||||||
|
for (let player of new_players) |
||||||
|
player.id = id ++ ; |
||||||
|
players.value = new_players; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
const close_ranklist = () => { |
||||||
|
store.commit("updateRanklist", false); |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
players, |
||||||
|
close_ranklist |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
div.ranklist { |
||||||
|
position: absolute; |
||||||
|
width: 30vh; |
||||||
|
height: 44vh; |
||||||
|
background-color: lightblue; |
||||||
|
} |
||||||
|
|
||||||
|
span.close-ranklist { |
||||||
|
float: right; |
||||||
|
position: absolute; |
||||||
|
right: 0.5vh; |
||||||
|
font-size: 2vh; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
div.player:nth-child(2) { |
||||||
|
background-color: #EECB07; |
||||||
|
} |
||||||
|
|
||||||
|
div.player { |
||||||
|
width: 100%; |
||||||
|
height: 4vh; |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(4, 7.5vh); |
||||||
|
line-height: 4vh; |
||||||
|
} |
||||||
|
|
||||||
|
div.player > div { |
||||||
|
text-align: center; |
||||||
|
color: white; |
||||||
|
} |
||||||
|
|
||||||
|
div.player > div:nth-child(2) { |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
div.player > div > img { |
||||||
|
width: 3vh; |
||||||
|
height: 3vh; |
||||||
|
border-radius: 50%; |
||||||
|
} |
||||||
|
|
||||||
|
div.player > div:nth-child(3) { |
||||||
|
overflow: hidden; |
||||||
|
text-overflow: ellipsis; |
||||||
|
white-space: pre; |
||||||
|
text-align: left; |
||||||
|
} |
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,125 @@ |
|||||||
|
<template> |
||||||
|
<div class="score-board"> |
||||||
|
<div class="apple"> |
||||||
|
<img src="https://test.bnblogs.cc/acapp/images/apple/count.png" alt=""> |
||||||
|
<div>{{ $store.state.score }}</div> |
||||||
|
</div> |
||||||
|
<div class="Music" @click="handleMusic"> |
||||||
|
<img src='./static/openMusic.png' v-if="$store.state.music"> |
||||||
|
<img src='./static/closeMusic.png' v-else> |
||||||
|
<audio id="player"> |
||||||
|
<source src="https://test.bnblogs.cc/acapp/audios/snake.mp3" type="audio/mpeg"> |
||||||
|
</audio> |
||||||
|
|
||||||
|
<audio id="eat_apple"> |
||||||
|
<source src="https://test.bnblogs.cc/acapp/audios/eat_apple.m4a" type="audio/mpeg"> |
||||||
|
</audio> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="cup"> |
||||||
|
<img src="https://app3359.acapp.acwing.com.cn/static/images/cup.png" alt=""> |
||||||
|
<div>{{ $store.state.record }}</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { ref } from 'vue' |
||||||
|
import { useStore } from 'vuex' |
||||||
|
|
||||||
|
export default { |
||||||
|
name: "ScoreBoard", |
||||||
|
setup() { |
||||||
|
let open = ref(false); |
||||||
|
const store = useStore(); |
||||||
|
if (store.state.music === true) { |
||||||
|
const audio_1 = document.querySelector('#player'); |
||||||
|
audio_1.play(); |
||||||
|
store.commit('updateAudio',audio_1); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
const handleMusic = () => { |
||||||
|
const audio = document.querySelector('#player') |
||||||
|
store.commit('updateAudio',audio); |
||||||
|
if (open.value === false) { |
||||||
|
open.value = true; |
||||||
|
audio.play(); |
||||||
|
} |
||||||
|
else { |
||||||
|
open.value= false; |
||||||
|
audio.pause(); |
||||||
|
} |
||||||
|
|
||||||
|
store.commit('updateMusic',open.value) |
||||||
|
// console.log(store.state.music); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
return { |
||||||
|
open, |
||||||
|
handleMusic, |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
|
||||||
|
<style scoped> |
||||||
|
|
||||||
|
.score-board { |
||||||
|
height: 8vh; |
||||||
|
width: 100%; |
||||||
|
background-color: #4A752C; |
||||||
|
display: flex; |
||||||
|
justify-content: space-around; |
||||||
|
user-select: none; |
||||||
|
} |
||||||
|
|
||||||
|
.apple { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.apple > img { |
||||||
|
width: 7vh; |
||||||
|
height: 7vh; |
||||||
|
} |
||||||
|
|
||||||
|
.apple > div { |
||||||
|
color: white; |
||||||
|
font-weight: bold; |
||||||
|
font-size: 2.5vh; |
||||||
|
} |
||||||
|
|
||||||
|
.cup { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.cup > img { |
||||||
|
width: 4.5vh; |
||||||
|
height: 5vh; |
||||||
|
} |
||||||
|
|
||||||
|
.cup > div { |
||||||
|
color: white; |
||||||
|
font-weight: bold; |
||||||
|
font-size: 2.5vh; |
||||||
|
margin-left: 1vh; |
||||||
|
} |
||||||
|
|
||||||
|
.Music { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
.Music > img { |
||||||
|
width: 5vh; |
||||||
|
height: 5vh; |
||||||
|
} |
||||||
|
|
||||||
|
</style> |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 532 B |
After Width: | Height: | Size: 455 B |
@ -0,0 +1,5 @@ |
|||||||
|
import { createApp } from 'vue' |
||||||
|
import App from './App.vue' |
||||||
|
import store from './store' |
||||||
|
|
||||||
|
createApp(App).use(store).mount('#app') |
@ -0,0 +1,63 @@ |
|||||||
|
import { createStore } from 'vuex' |
||||||
|
|
||||||
|
export default createStore({ |
||||||
|
state: { |
||||||
|
score: 0, |
||||||
|
record: 0, |
||||||
|
restart: true, |
||||||
|
AcWingOS: "AcWingOS", |
||||||
|
access: "", |
||||||
|
refresh: "", |
||||||
|
ranklist: false, |
||||||
|
upgrade: false, |
||||||
|
music: false, // 默认无音乐
|
||||||
|
audio: null, |
||||||
|
color: null, // 保存上一局的蛇体颜色
|
||||||
|
}, |
||||||
|
getters: { |
||||||
|
}, |
||||||
|
mutations: { |
||||||
|
updateScore: (state, score) => { |
||||||
|
state.score = score; |
||||||
|
}, |
||||||
|
updateRecord: (state, score) => { |
||||||
|
if (state.record < score) { |
||||||
|
state.record = score; |
||||||
|
} |
||||||
|
}, |
||||||
|
updateRestart: (state, restart) => { |
||||||
|
state.restart = restart; |
||||||
|
}, |
||||||
|
updateAccess: (state, access) => { |
||||||
|
state.access = access; |
||||||
|
}, |
||||||
|
updateRefresh: (state, refresh) => { |
||||||
|
state.refresh = refresh; |
||||||
|
}, |
||||||
|
updateRanklist: (state, ranklist) => { |
||||||
|
state.ranklist = ranklist; |
||||||
|
}, |
||||||
|
updateNumber: (state,number) => { |
||||||
|
state.number = number; |
||||||
|
}, |
||||||
|
updateSpeed: (state, speed) => { |
||||||
|
state.speed = speed; |
||||||
|
}, |
||||||
|
updateNextStep:(state,upgrade) => { |
||||||
|
state.upgrade = upgrade; |
||||||
|
}, |
||||||
|
updateMusic:(state,music) => { |
||||||
|
state.music= music; |
||||||
|
}, |
||||||
|
updateAudio:(state,audio) => { |
||||||
|
state.audio = audio; |
||||||
|
}, |
||||||
|
updateColor:(state,color) => { |
||||||
|
state.color = color; |
||||||
|
} |
||||||
|
}, |
||||||
|
actions: { |
||||||
|
}, |
||||||
|
modules: { |
||||||
|
} |
||||||
|
}) |
@ -0,0 +1,10 @@ |
|||||||
|
const { defineConfig } = require('@vue/cli-service') |
||||||
|
module.exports = defineConfig({ |
||||||
|
transpileDependencies: true, |
||||||
|
configureWebpack: { |
||||||
|
// No need for splitting
|
||||||
|
optimization: { |
||||||
|
splitChunks: false |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
@ -0,0 +1,23 @@ |
|||||||
|
.DS_Store |
||||||
|
node_modules |
||||||
|
/dist |
||||||
|
|
||||||
|
|
||||||
|
# local env files |
||||||
|
.env.local |
||||||
|
.env.*.local |
||||||
|
|
||||||
|
# Log files |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
pnpm-debug.log* |
||||||
|
|
||||||
|
# Editor directories and files |
||||||
|
.idea |
||||||
|
.vscode |
||||||
|
*.suo |
||||||
|
*.ntvs* |
||||||
|
*.njsproj |
||||||
|
*.sln |
||||||
|
*.sw? |
@ -0,0 +1,24 @@ |
|||||||
|
# snake |
||||||
|
|
||||||
|
## Project setup |
||||||
|
``` |
||||||
|
npm install |
||||||
|
``` |
||||||
|
|
||||||
|
### Compiles and hot-reloads for development |
||||||
|
``` |
||||||
|
npm run serve |
||||||
|
``` |
||||||
|
|
||||||
|
### Compiles and minifies for production |
||||||
|
``` |
||||||
|
npm run build |
||||||
|
``` |
||||||
|
|
||||||
|
### Lints and fixes files |
||||||
|
``` |
||||||
|
npm run lint |
||||||
|
``` |
||||||
|
|
||||||
|
### Customize configuration |
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/). |
@ -0,0 +1,5 @@ |
|||||||
|
module.exports = { |
||||||
|
presets: [ |
||||||
|
'@vue/cli-plugin-babel/preset' |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"target": "es5", |
||||||
|
"module": "esnext", |
||||||
|
"baseUrl": "./", |
||||||
|
"moduleResolution": "node", |
||||||
|
"paths": { |
||||||
|
"@/*": [ |
||||||
|
"src/*" |
||||||
|
] |
||||||
|
}, |
||||||
|
"lib": [ |
||||||
|
"esnext", |
||||||
|
"dom", |
||||||
|
"dom.iterable", |
||||||
|
"scripthost" |
||||||
|
] |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,46 @@ |
|||||||
|
{ |
||||||
|
"name": "snake", |
||||||
|
"version": "0.1.0", |
||||||
|
"private": true, |
||||||
|
"scripts": { |
||||||
|
"serve": "vue-cli-service serve", |
||||||
|
"build": "vue-cli-service build", |
||||||
|
"lint": "vue-cli-service lint" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"core-js": "^3.8.3", |
||||||
|
"jquery": "^3.6.1", |
||||||
|
"vue": "^3.2.13", |
||||||
|
"vuex": "^4.0.0" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@babel/core": "^7.12.16", |
||||||
|
"@babel/eslint-parser": "^7.12.16", |
||||||
|
"@vue/cli-plugin-babel": "~5.0.0", |
||||||
|
"@vue/cli-plugin-eslint": "~5.0.0", |
||||||
|
"@vue/cli-plugin-vuex": "~5.0.0", |
||||||
|
"@vue/cli-service": "~5.0.0", |
||||||
|
"eslint": "^7.32.0", |
||||||
|
"eslint-plugin-vue": "^8.0.3" |
||||||
|
}, |
||||||
|
"eslintConfig": { |
||||||
|
"root": true, |
||||||
|
"env": { |
||||||
|
"node": true |
||||||
|
}, |
||||||
|
"extends": [ |
||||||
|
"plugin:vue/vue3-essential", |
||||||
|
"eslint:recommended" |
||||||
|
], |
||||||
|
"parserOptions": { |
||||||
|
"parser": "@babel/eslint-parser" |
||||||
|
}, |
||||||
|
"rules": {} |
||||||
|
}, |
||||||
|
"browserslist": [ |
||||||
|
"> 1%", |
||||||
|
"last 2 versions", |
||||||
|
"not dead", |
||||||
|
"not ie 11" |
||||||
|
] |
||||||
|
} |
After Width: | Height: | Size: 4.2 KiB |
@ -0,0 +1,19 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang=""> |
||||||
|
<head> |
||||||
|
<meta charset="utf-8"> |
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0"> |
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> |
||||||
|
<title><%= htmlWebpackPlugin.options.title %></title> |
||||||
|
</head> |
||||||
|
<body style="margin: 0;"> |
||||||
|
<noscript> |
||||||
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> |
||||||
|
</noscript> |
||||||
|
<div id="app" style="width: 100vw; height: 100vh;display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center;"></div> |
||||||
|
<!-- built files will be auto injected --> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,23 @@ |
|||||||
|
<template> |
||||||
|
<div class="gameboard"> |
||||||
|
<PlayGround /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import PlayGround from '@/components/PlayGround' |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'App', |
||||||
|
components: { |
||||||
|
PlayGround, |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.gameboard { |
||||||
|
width: 30%; |
||||||
|
height: 80%; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,47 @@ |
|||||||
|
const AC_GAME_OBJECTS = []; |
||||||
|
|
||||||
|
export class AcGameObject { |
||||||
|
constructor() { |
||||||
|
AC_GAME_OBJECTS.push(this); |
||||||
|
this.timedelta = 0; |
||||||
|
this.has_called_start = false; |
||||||
|
} |
||||||
|
start() { |
||||||
|
|
||||||
|
} |
||||||
|
update() { |
||||||
|
|
||||||
|
} |
||||||
|
on_destroy() { |
||||||
|
|
||||||
|
} |
||||||
|
destroy() { |
||||||
|
this.on_destroy(); |
||||||
|
|
||||||
|
for (let i in AC_GAME_OBJECTS) { |
||||||
|
const obj = AC_GAME_OBJECTS[i]; |
||||||
|
if (obj === this) { |
||||||
|
AC_GAME_OBJECTS.splice(i, 1); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
let last_timestamp; |
||||||
|
const step = timestamp => { |
||||||
|
for (let obj of AC_GAME_OBJECTS) { |
||||||
|
if (!obj.has_called_start) { |
||||||
|
obj.start(); |
||||||
|
obj.has_called_start = true; |
||||||
|
} else { |
||||||
|
obj.timedelta = timestamp - last_timestamp; |
||||||
|
obj.update(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
last_timestamp = timestamp; |
||||||
|
requestAnimationFrame(step); |
||||||
|
}; |
||||||
|
|
||||||
|
requestAnimationFrame(step); |
@ -0,0 +1,8 @@ |
|||||||
|
export class Cell { |
||||||
|
constructor(i, j) { |
||||||
|
this.i = i; |
||||||
|
this.j = j; |
||||||
|
this.x = i + 0.5; |
||||||
|
this.y = j + 0.5; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,130 @@ |
|||||||
|
import { AcGameObject } from "./AcGameObject"; |
||||||
|
import { Snake } from "./Snake"; |
||||||
|
import $ from 'jquery'; |
||||||
|
|
||||||
|
export class GameMap extends AcGameObject { |
||||||
|
constructor(ctx, parent, store) { |
||||||
|
super(); |
||||||
|
|
||||||
|
this.ctx = ctx; |
||||||
|
this.parent = parent; |
||||||
|
this.store = store; |
||||||
|
this.L = 0; |
||||||
|
|
||||||
|
this.snake = new Snake(this.ctx, this); |
||||||
|
this.directions = []; |
||||||
|
|
||||||
|
this.status = "waiting"; // waiting -> playing -> win/lose
|
||||||
|
} |
||||||
|
|
||||||
|
start() { |
||||||
|
this.ctx.canvas.focus(); |
||||||
|
|
||||||
|
this.ctx.canvas.addEventListener('keydown', e => { |
||||||
|
if (this.store.state.restart) return false; |
||||||
|
|
||||||
|
if (e.key === 'w' || e.key === 'ArrowUp') { |
||||||
|
this.directions.push(0); |
||||||
|
e.preventDefault(); |
||||||
|
} |
||||||
|
else if (e.key === 'd' || e.key == 'ArrowRight') { |
||||||
|
this.directions.push(1); |
||||||
|
e.preventDefault(); |
||||||
|
} |
||||||
|
else if (e.key === 's' || e.key === 'ArrowDown') { |
||||||
|
this.directions.push(2); |
||||||
|
e.preventDefault(); |
||||||
|
} |
||||||
|
else if (e.key === 'a' || e.key === 'ArrowLeft') { |
||||||
|
this.directions.push(3); |
||||||
|
e.preventDefault(); |
||||||
|
} |
||||||
|
|
||||||
|
let k = this.directions.length; |
||||||
|
if (k > 1 && this.directions[k - 1] === this.directions[k - 2]) { |
||||||
|
this.directions.pop(); |
||||||
|
} |
||||||
|
|
||||||
|
while (this.directions.length > 2) |
||||||
|
this.directions.splice(0, 1); |
||||||
|
|
||||||
|
if (this.status === "waiting" && this.directions.length && this.directions[0] !== 3) { |
||||||
|
this.status = "playing"; |
||||||
|
this.snake.direction = this.directions[0]; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
update_size() { |
||||||
|
this.L = Math.min(this.parent.clientWidth / 17, this.parent.clientHeight / 15); |
||||||
|
this.ctx.canvas.width = this.L * 17; |
||||||
|
this.ctx.canvas.height = this.L * 15; |
||||||
|
} |
||||||
|
|
||||||
|
update_score() { |
||||||
|
$.ajax({ |
||||||
|
url: "https://app3359.acapp.acwing.com.cn/update_score/", |
||||||
|
type: "post", |
||||||
|
data: { |
||||||
|
score: this.store.state.score, |
||||||
|
}, |
||||||
|
headers: { |
||||||
|
'Authorization': "Bearer " + this.store.state.access, |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
win() { |
||||||
|
this.snake.color = "white"; |
||||||
|
this.status = "win"; |
||||||
|
this.store.commit('updateRestart', true); |
||||||
|
this.update_score(); |
||||||
|
} |
||||||
|
|
||||||
|
lose() { |
||||||
|
this.snake.color = "white"; |
||||||
|
this.status = "lose"; |
||||||
|
this.store.commit('updateRestart', true); |
||||||
|
this.update_score(); |
||||||
|
} |
||||||
|
|
||||||
|
restart() { |
||||||
|
this.store.state.score = 0; |
||||||
|
this.status = "waiting"; |
||||||
|
this.snake.destroy(); |
||||||
|
this.snake = new Snake(this.ctx, this); |
||||||
|
this.store.commit('updateRestart', false); |
||||||
|
this.store.commit('updateNumber',0); |
||||||
|
this.ctx.canvas.focus(); |
||||||
|
} |
||||||
|
|
||||||
|
upgrade() { |
||||||
|
this.status = "waiting"; |
||||||
|
this.snake.destroy(); |
||||||
|
this.snake = new Snake(this.ctx, this); |
||||||
|
this.snake.speed = this.store.state.speed; |
||||||
|
this.snake.number = this.store.state.number;
|
||||||
|
this.store.commit("updateRecord",Math.max(this.store.state.score,this.store.state.record)); |
||||||
|
this.ctx.canvas.focus(); |
||||||
|
} |
||||||
|
|
||||||
|
update() { |
||||||
|
this.update_size(); |
||||||
|
this.render(); |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
let color_even = "#AAD751", color_odd = "#A2D149"; |
||||||
|
|
||||||
|
for (let i = 0; i < 17; i ++ ) { |
||||||
|
for (let j = 0; j < 15; j ++ ) { |
||||||
|
if ((i + j) % 2 == 0) { |
||||||
|
this.ctx.fillStyle = color_even;
|
||||||
|
} else { |
||||||
|
this.ctx.fillStyle = color_odd; |
||||||
|
} |
||||||
|
this.ctx.fillRect(i * this.L, j * this.L, this.L, this.L); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,187 @@ |
|||||||
|
import { AcGameObject } from './AcGameObject'; |
||||||
|
import { Cell } from './Cell'; |
||||||
|
|
||||||
|
export class Snake extends AcGameObject { |
||||||
|
constructor(ctx, gamemap) { |
||||||
|
super(); |
||||||
|
this.ctx = ctx; |
||||||
|
this.gamemap = gamemap; |
||||||
|
|
||||||
|
this.cells = []; |
||||||
|
this.color = "#4876EC"; |
||||||
|
this.dirs = [ |
||||||
|
{x: 0, y: -1}, |
||||||
|
{x: 1, y: 0}, |
||||||
|
{x: 0, y: 1}, |
||||||
|
{x: -1, y: 0}, |
||||||
|
]; |
||||||
|
this.direction = 2; |
||||||
|
this.eps = 1e-1; |
||||||
|
this.speed = 5; // 每秒钟走几格,初始速度为5
|
||||||
|
this.apple_cell = new Cell(-1, -1); |
||||||
|
this.apple_img = new Image(); |
||||||
|
this.apple_img.src = "https://app3359.acapp.acwing.com.cn/static/images/apple.png"; |
||||||
|
this.eating = false; |
||||||
|
this.tail_cell = null; |
||||||
|
this.number = 0; |
||||||
|
} |
||||||
|
|
||||||
|
start() { |
||||||
|
this.cells.push(new Cell(4, 7)); |
||||||
|
for (let i = 4; i >= 1; i -- ) { |
||||||
|
this.cells.push(new Cell(i, 7)); |
||||||
|
} |
||||||
|
|
||||||
|
this.put_an_apple(); |
||||||
|
} |
||||||
|
|
||||||
|
put_an_apple() { |
||||||
|
const positions = new Set(); |
||||||
|
for (let i = 0; i < 17; i ++ ) { |
||||||
|
for (let j = 0; j < 15; j ++ ) { |
||||||
|
positions.add(`${i}-${j}`); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (let cell of this.cells) { |
||||||
|
positions.delete(`${cell.i}-${cell.j}`); |
||||||
|
} |
||||||
|
|
||||||
|
const items = Array.from(positions); |
||||||
|
if (items.length === 0) this.gamemap.win(); |
||||||
|
else { |
||||||
|
let [x, y] = items[Math.floor(Math.random() * items.length)].split('-'); |
||||||
|
x = parseInt(x), y = parseInt(y); |
||||||
|
this.apple_cell = new Cell(x, y); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
get_direction(a, b) { |
||||||
|
if (Math.abs(a.x - b.x) < this.eps && Math.abs(a.y - b.y) < this.eps) |
||||||
|
return -1; |
||||||
|
if (Math.abs(a.x - b.x) < this.eps) { |
||||||
|
if (b.y < a.y) return 0; |
||||||
|
return 2; |
||||||
|
} |
||||||
|
if (b.x > a.x) return 1; |
||||||
|
return 3; |
||||||
|
} |
||||||
|
|
||||||
|
check_die() { |
||||||
|
const head = this.cells[0]; |
||||||
|
if (head.i < 0 || head.i >= 17 || head.j < 0 || head.j >= 15) |
||||||
|
return true; |
||||||
|
|
||||||
|
for (let i = 2; i < this.cells.length; i ++ ) { |
||||||
|
if (head.i === this.cells[i].i && head.j === this.cells[i].j) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
update_body() { |
||||||
|
const k = this.cells.length - 1; |
||||||
|
const d = this.get_direction(this.cells[k], this.cells[k - 1]); |
||||||
|
if (d >= 0) { |
||||||
|
const distance = this.speed * this.timedelta / 1000; |
||||||
|
this.cells[k].x += this.dirs[d].x * distance; |
||||||
|
this.cells[k].y += this.dirs[d].y * distance; |
||||||
|
this.cells[0].x += this.dirs[this.direction].x * distance; |
||||||
|
this.cells[0].y += this.dirs[this.direction].y * distance; |
||||||
|
} else { |
||||||
|
const new_cells = []; |
||||||
|
const headi = this.cells[1].i + this.dirs[this.direction].x; |
||||||
|
const headj = this.cells[1].j + this.dirs[this.direction].y; |
||||||
|
new_cells.push(new Cell(headi, headj)); |
||||||
|
new_cells.push(new Cell(headi, headj)); |
||||||
|
for (let i = 1; i < k; i ++ ) { |
||||||
|
new_cells.push(this.cells[i]); |
||||||
|
} |
||||||
|
this.cells = new_cells; |
||||||
|
|
||||||
|
if (this.eating) { |
||||||
|
this.cells.push(this.tail_cell); |
||||||
|
this.eating = false; |
||||||
|
this.tail_cell = null; |
||||||
|
} |
||||||
|
|
||||||
|
const ds = this.gamemap.directions; |
||||||
|
while (ds.length > 0 && (ds[0] === this.direction || ds[0] === (this.direction ^ 2))) |
||||||
|
ds.splice(0, 1); |
||||||
|
|
||||||
|
if (ds.length > 0) { |
||||||
|
this.direction = ds[0]; |
||||||
|
ds.splice(0, 1); |
||||||
|
} |
||||||
|
|
||||||
|
if (this.check_die()) { |
||||||
|
this.gamemap.lose(); |
||||||
|
} |
||||||
|
|
||||||
|
if (headi === this.apple_cell.i && headj === this.apple_cell.j) { |
||||||
|
this.eating = true; |
||||||
|
const cell = this.cells[this.cells.length - 1]; |
||||||
|
this.tail_cell = new Cell(cell.i, cell.j); |
||||||
|
this.put_an_apple(); |
||||||
|
const score = this.gamemap.store.state.score + 1; |
||||||
|
this.gamemap.store.commit('updateScore', score); |
||||||
|
this.gamemap.store.commit('updateRecord', Math.max(score,this.gamemap.store.state.record)); |
||||||
|
this.number++; |
||||||
|
this.gamemap.store.commit('updateNumber',this.number); |
||||||
|
if (this.number > 0 && this.number % 10 === 0) { // 每吃10个增加速度1
|
||||||
|
this.speed = this.speed + 1; |
||||||
|
this.gamemap.store.commit("updateSpeed",this.speed); |
||||||
|
} |
||||||
|
|
||||||
|
if (this.number > 0 && this.number % 30 === 0) { |
||||||
|
// 进入下一回合,先加10分
|
||||||
|
this.gamemap.store.commit("updateScore",score + 10); |
||||||
|
this.gamemap.upgrade(); |
||||||
|
} |
||||||
|
console.log("num: ",this.number); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
update() { |
||||||
|
if (this.gamemap.status === "playing") { |
||||||
|
this.update_body(); |
||||||
|
} |
||||||
|
this.render(); |
||||||
|
} |
||||||
|
|
||||||
|
render() { |
||||||
|
const L = this.gamemap.L; |
||||||
|
|
||||||
|
if (this.eating) { |
||||||
|
this.cells.push(this.tail_cell); |
||||||
|
} |
||||||
|
|
||||||
|
this.ctx.drawImage(this.apple_img, this.apple_cell.i * L, this.apple_cell.j * L, L, L); |
||||||
|
|
||||||
|
this.ctx.fillStyle = this.color; |
||||||
|
for (let cell of this.cells) { |
||||||
|
this.ctx.beginPath(); |
||||||
|
this.ctx.arc(cell.x * L, cell.y * L, L / 2 * 0.8, 0, Math.PI * 2); |
||||||
|
this.ctx.fill(); |
||||||
|
} |
||||||
|
|
||||||
|
for (let i = 1; i < this.cells.length; i ++ ) { |
||||||
|
const a = this.cells[i - 1], b = this.cells[i]; |
||||||
|
if (Math.abs(a.x - b.x) < this.eps && Math.abs(a.y - b.y) < this.eps) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if (Math.abs(a.x - b.x) < this.eps) { |
||||||
|
this.ctx.fillRect((a.x - 0.5 + 0.1) * L, Math.min(a.y, b.y) * L, L * 0.8, Math.abs(a.y - b.y) * L); |
||||||
|
} else { |
||||||
|
this.ctx.fillRect(Math.min(a.x, b.x) * L, (a.y - 0.5 + 0.1) * L, Math.abs(a.x - b.x) * L, L * 0.8); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (this.eating) { |
||||||
|
this.cells.pop(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
import $ from 'jquery'; |
||||||
|
|
||||||
|
export const init = (store) => { |
||||||
|
const AcWingOS = store.state.AcWingOS; |
||||||
|
|
||||||
|
if (AcWingOS === "AcWingOS") return false; |
||||||
|
|
||||||
|
const vw = window.innerWidth; |
||||||
|
const vh = window.innerHeight; |
||||||
|
AcWingOS.api.window.resize(59.5 * vh / vw, 64.5); |
||||||
|
|
||||||
|
$.ajax({ |
||||||
|
url: "https://app3359.acapp.acwing.com.cn/apply_code/", |
||||||
|
type: "get", |
||||||
|
success: resp => { |
||||||
|
AcWingOS.api.oauth2.authorize(resp.appid, resp.redirect_uri, resp.scope, resp.state, resp => { |
||||||
|
if (resp.result === "success") { |
||||||
|
store.commit('updateAccess', resp.access); |
||||||
|
store.commit('updateRefresh', resp.refresh); |
||||||
|
|
||||||
|
setInterval(() => { |
||||||
|
$.ajax({ |
||||||
|
url: "https://app3359.acapp.acwing.com.cn/api/token/refresh/", |
||||||
|
type: "post", |
||||||
|
data: { |
||||||
|
refresh: resp.refresh, |
||||||
|
}, |
||||||
|
success: resp => { |
||||||
|
store.commit('updateAccess', resp.access); |
||||||
|
} |
||||||
|
}); |
||||||
|
}, 4.5 * 60 * 1000); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,82 @@ |
|||||||
|
<template> |
||||||
|
<div class="gamemap" ref="div"> |
||||||
|
<canvas ref="canvas" tabindex="0"></canvas> |
||||||
|
<div class="operation" v-if="$store.state.restart"> |
||||||
|
<button @click="restart">开始游戏</button> |
||||||
|
<button @click="show_ranklist">排行榜</button> |
||||||
|
</div> |
||||||
|
<RankList v-if="$store.state.ranklist" /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { ref, onMounted } from 'vue'; |
||||||
|
import { GameMap } from '@/assets/scripts/GameMap'; |
||||||
|
import { useStore } from 'vuex'; |
||||||
|
import { init } from '@/assets/scripts/init'; |
||||||
|
import RankList from './RankList' |
||||||
|
|
||||||
|
export default { |
||||||
|
name: "GameMap", |
||||||
|
components: { |
||||||
|
RankList, |
||||||
|
}, |
||||||
|
setup: () => { |
||||||
|
let div = ref(null); |
||||||
|
let canvas = ref(null); |
||||||
|
const store = useStore(); |
||||||
|
let gamemap = null; |
||||||
|
|
||||||
|
init(store); |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
gamemap = new GameMap(canvas.value.getContext('2d'), div.value, store); |
||||||
|
}); |
||||||
|
|
||||||
|
const restart = () => { |
||||||
|
gamemap.restart(); |
||||||
|
} |
||||||
|
|
||||||
|
const show_ranklist = () => { |
||||||
|
store.commit('updateRanklist', true); |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
div, |
||||||
|
canvas, |
||||||
|
restart, |
||||||
|
show_ranklist |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
div.gamemap { |
||||||
|
height: calc(100% - 8vh); |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
canvas { |
||||||
|
background-color: #AAD751; |
||||||
|
} |
||||||
|
|
||||||
|
div.operation { |
||||||
|
position: absolute; |
||||||
|
} |
||||||
|
|
||||||
|
button { |
||||||
|
background-color: #0d6efd; |
||||||
|
border: solid 0; |
||||||
|
border-radius: 5px; |
||||||
|
font-size: 3vh; |
||||||
|
color: white; |
||||||
|
padding: 3vh; |
||||||
|
cursor: pointer; |
||||||
|
margin: 0 0.5vh; |
||||||
|
} |
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,32 @@ |
|||||||
|
<template> |
||||||
|
<div class="playground"> |
||||||
|
<ScoreBoard /> |
||||||
|
<GameMap /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import ScoreBoard from '@/components/ScoreBoard'; |
||||||
|
import GameMap from '@/components/GameMap'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: "PlayGround", |
||||||
|
components: { |
||||||
|
ScoreBoard, |
||||||
|
GameMap |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
|
||||||
|
<style scoped> |
||||||
|
|
||||||
|
|
||||||
|
.playground { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
background-color: #578A34; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,104 @@ |
|||||||
|
<template> |
||||||
|
<div class="ranklist"> |
||||||
|
<span class="close-ranklist" @click="close_ranklist">x</span> |
||||||
|
<div class="player" v-for="player in players" :key="player.id"> |
||||||
|
<div>#{{ player.rank }}</div> |
||||||
|
<div> |
||||||
|
<img :src="player.photo" alt=""> |
||||||
|
</div> |
||||||
|
<div>{{ player.username }}</div> |
||||||
|
<div>{{ player.score }}</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import { ref } from 'vue'; |
||||||
|
import $ from 'jquery'; |
||||||
|
import { useStore } from 'vuex'; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: 'RankList', |
||||||
|
setup() { |
||||||
|
const store = useStore(); |
||||||
|
let players = ref([]); |
||||||
|
$.ajax({ |
||||||
|
url: "https://app3359.acapp.acwing.com.cn/get_ranklist/", |
||||||
|
type: "get", |
||||||
|
headers: { |
||||||
|
'Authorization': "Bearer " + store.state.access, |
||||||
|
}, |
||||||
|
success: resp => { |
||||||
|
const new_players = [resp.me, ...resp.all]; |
||||||
|
let id = 0; |
||||||
|
for (let player of new_players) |
||||||
|
player.id = id ++ ; |
||||||
|
players.value = new_players; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
const close_ranklist = () => { |
||||||
|
store.commit("updateRanklist", false); |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
players, |
||||||
|
close_ranklist |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
div.ranklist { |
||||||
|
position: absolute; |
||||||
|
width: 30vh; |
||||||
|
height: 44vh; |
||||||
|
background-color: lightblue; |
||||||
|
} |
||||||
|
|
||||||
|
span.close-ranklist { |
||||||
|
float: right; |
||||||
|
position: absolute; |
||||||
|
right: 0.5vh; |
||||||
|
font-size: 2vh; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
|
||||||
|
div.player:nth-child(2) { |
||||||
|
background-color: #EECB07; |
||||||
|
} |
||||||
|
|
||||||
|
div.player { |
||||||
|
width: 100%; |
||||||
|
height: 4vh; |
||||||
|
display: grid; |
||||||
|
grid-template-columns: repeat(4, 7.5vh); |
||||||
|
line-height: 4vh; |
||||||
|
} |
||||||
|
|
||||||
|
div.player > div { |
||||||
|
text-align: center; |
||||||
|
color: white; |
||||||
|
} |
||||||
|
|
||||||
|
div.player > div:nth-child(2) { |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
div.player > div > img { |
||||||
|
width: 3vh; |
||||||
|
height: 3vh; |
||||||
|
border-radius: 50%; |
||||||
|
} |
||||||
|
|
||||||
|
div.player > div:nth-child(3) { |
||||||
|
overflow: hidden; |
||||||
|
text-overflow: ellipsis; |
||||||
|
white-space: pre; |
||||||
|
text-align: left; |
||||||
|
} |
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,65 @@ |
|||||||
|
<template> |
||||||
|
<div class="score-board"> |
||||||
|
<div class="apple"> |
||||||
|
<img src="https://app3359.acapp.acwing.com.cn/static/images/apple.png" alt=""> |
||||||
|
<div>{{ $store.state.score }}</div> |
||||||
|
</div> |
||||||
|
<div class="cup"> |
||||||
|
<img src="https://app3359.acapp.acwing.com.cn/static/images/cup.png" alt=""> |
||||||
|
<div>{{ $store.state.record }}</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
name: "ScoreBoard", |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
|
||||||
|
<style scoped> |
||||||
|
|
||||||
|
.score-board { |
||||||
|
height: 8vh; |
||||||
|
width: 100%; |
||||||
|
background-color: #4A752C; |
||||||
|
display: flex; |
||||||
|
justify-content: space-around; |
||||||
|
user-select: none; |
||||||
|
} |
||||||
|
|
||||||
|
.apple { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.apple > img { |
||||||
|
width: 6vh; |
||||||
|
height: 6vh; |
||||||
|
} |
||||||
|
|
||||||
|
.apple > div { |
||||||
|
color: white; |
||||||
|
font-weight: bold; |
||||||
|
font-size: 2.5vh; |
||||||
|
} |
||||||
|
|
||||||
|
.cup { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
|
||||||
|
.cup > img { |
||||||
|
width: 4.5vh; |
||||||
|
height: 5vh; |
||||||
|
} |
||||||
|
|
||||||
|
.cup > div { |
||||||
|
color: white; |
||||||
|
font-weight: bold; |
||||||
|
font-size: 2.5vh; |
||||||
|
margin-left: 1vh; |
||||||
|
} |
||||||
|
|
||||||
|
</style> |
@ -0,0 +1,5 @@ |
|||||||
|
import { createApp } from 'vue' |
||||||
|
import App from './App.vue' |
||||||
|
import store from './store' |
||||||
|
|
||||||
|
createApp(App).use(store).mount('#app') |
@ -0,0 +1,49 @@ |
|||||||
|
import { createStore } from 'vuex' |
||||||
|
|
||||||
|
export default createStore({ |
||||||
|
state: { |
||||||
|
score: 0, |
||||||
|
record: 0, |
||||||
|
restart: true, |
||||||
|
AcWingOS: "AcWingOS", |
||||||
|
access: "", |
||||||
|
refresh: "", |
||||||
|
ranklist: false, |
||||||
|
speed: 5, |
||||||
|
number: 0, |
||||||
|
}, |
||||||
|
getters: { |
||||||
|
}, |
||||||
|
mutations: { |
||||||
|
updateSpeed: (state, speed) => { |
||||||
|
state.speed = speed; |
||||||
|
}, |
||||||
|
updateScore: (state, score) => { |
||||||
|
state.score = score; |
||||||
|
}, |
||||||
|
updateRecord: (state, score) => { |
||||||
|
if (state.record < score) { |
||||||
|
state.record = score; |
||||||
|
} |
||||||
|
}, |
||||||
|
updateRestart: (state, restart) => { |
||||||
|
state.restart = restart; |
||||||
|
}, |
||||||
|
updateAccess: (state, access) => { |
||||||
|
state.access = access; |
||||||
|
}, |
||||||
|
updateRefresh: (state, refresh) => { |
||||||
|
state.refresh = refresh; |
||||||
|
}, |
||||||
|
updateRanklist: (state, ranklist) => { |
||||||
|
state.ranklist = ranklist; |
||||||
|
}, |
||||||
|
updateNumber: (state,number) => { |
||||||
|
state.number = number; |
||||||
|
} |
||||||
|
}, |
||||||
|
actions: { |
||||||
|
}, |
||||||
|
modules: { |
||||||
|
} |
||||||
|
}) |
@ -0,0 +1,9 @@ |
|||||||
|
const { defineConfig } = require('@vue/cli-service') |
||||||
|
module.exports = defineConfig({ |
||||||
|
transpileDependencies: true, |
||||||
|
configureWebpack:{ |
||||||
|
optimization:{ |
||||||
|
splitChunks:false |
||||||
|
} |
||||||
|
} |
||||||
|
}) |