add tower builing game
|
After Width: | Height: | Size: 249 KiB |
@@ -12,7 +12,12 @@
|
|||||||
content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0, maximum-scale=1, user-scalable=no, minimal-ui">
|
content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0, maximum-scale=1, user-scalable=no, minimal-ui">
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
margin: 10px 0 20px 0 !important;
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 0 8px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ body {
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
/* margin: 80px 0; */
|
/* margin: 80px 0; */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.heading:after {
|
.heading:after {
|
||||||
@@ -144,8 +148,10 @@ hr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: 500px;
|
width: calc(100% - 16px);
|
||||||
|
max-width: 500px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@-webkit-keyframes fade-in {
|
@-webkit-keyframes fade-in {
|
||||||
@@ -683,8 +689,10 @@ hr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: 280px;
|
width: calc(100% - 16px);
|
||||||
|
max-width: 280px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.score-container,
|
.score-container,
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
[
|
||||||
|
"@babel/preset-env",
|
||||||
|
{
|
||||||
|
"targets": {
|
||||||
|
"browsers": [
|
||||||
|
"ios >= 9",
|
||||||
|
"android >= 4"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules
|
||||||
|
.idea/
|
||||||
|
npm-debug.log
|
||||||
|
yarn.lock
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 BMQB, Inc
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
[](LICENSE)
|
||||||
|
|
||||||
|
English | [简体中文](./README.zh-CN.md)
|
||||||
|
|
||||||
|
<h1 align="center">Tower Building Game</h1>
|
||||||
|
<p align="center"><img src="https://o2qq673j2.qnssl.com/tower-loading.gif"/></p>
|
||||||
|
|
||||||
|
> a tower building game based on ES6 and Canvas (Tower Bloxx Deluxe Skyscraper)
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
<p align="center"><img src="https://user-images.githubusercontent.com/17680888/47480922-93a20c00-d864-11e8-8f7c-6d1d60184730.gif"/></p>
|
||||||
|
<h2 align="center"><a href="https://iamkun.github.io/tower_game">Link to online Demo (Demo Link)</a></h2>
|
||||||
|
<h4 align="center">Mobile Devices can scan following QR code:</h4>
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://user-images.githubusercontent.com/17680888/47480646-abc55b80-d863-11e8-9337-4ea768ebe55d.png" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Game Rule
|
||||||
|
|
||||||
|
The following are the default game rules:
|
||||||
|
|
||||||
|
- The player starts with 3 hp, indicated by the hearts in the top right corner of the screen. Every time a Tower block falls the player loses 1 hp, and the game ends when hp is depleted.
|
||||||
|
|
||||||
|
- The player earns 25 points for every succesful stacked block (Success). If a block is stacked pefectly (Perfect) on top of the previous one, then the player is
|
||||||
|
rewarded with 50 points instead. Consecutive Perfects awards additional 25 points.
|
||||||
|
|
||||||
|
**Note: Each Success or Perfect constitutes a floor**
|
||||||
|
|
||||||
|
For example, the first Perfect awards 50 points. The second consecutive Perfect awards 75 points.
|
||||||
|
The third consecutive Perfect awards 100 points. etc.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img width="550" src="https://user-images.githubusercontent.com/17680888/47473105-d9021180-d843-11e8-8c19-b6b78d86cbdf.png" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Customizing the game rules
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/iamkun/tower_game.git
|
||||||
|
cd tower_game
|
||||||
|
npm install
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
Open `http://localhost:8082` in a web browser.
|
||||||
|
|
||||||
|
- To customize image and sound resource files directly replace the corresponding file under the `assets` directory.
|
||||||
|
- To customize game rules modify the `option` object in `index.html`.
|
||||||
|
|
||||||
|
## Option
|
||||||
|
|
||||||
|
Use the following table of `option` constants to complete customization of game rules.
|
||||||
|
|
||||||
|
**Note: all constants are optionally included**
|
||||||
|
|
||||||
|
| Option | Type | Description |
|
||||||
|
|---------|--------|-------------|
|
||||||
|
| width | number | Width of game interface |
|
||||||
|
| height | number | Height of game interface |
|
||||||
|
| canvasId | string | DOM ID in Canvas |
|
||||||
|
| soundOn | boolean | If sound is on |
|
||||||
|
| successScore | number | Points awarded for success |
|
||||||
|
| perfectScore | number | Additional points awarded for perfect |
|
||||||
|
| <a href="#hookspeed">hookSpeed</a> | function | Speed of hook's movement |
|
||||||
|
| <a href="#hookangle">hookAngle</a> | function | Angle of hook |
|
||||||
|
| <a href="#landblockspeed">landBlockSpeed</a> | function | Speed of block sway |
|
||||||
|
| <a href="#setgamescore">setGameScore</a> | function | hook for current score |
|
||||||
|
| <a href="#setgamesuccess">setGameSuccess</a> | function | hook for number of current succesful game |
|
||||||
|
| <a href="#setgamefailed">setGameFailed</a> | function | hook for number of current failed game |
|
||||||
|
|
||||||
|
#### hookSpeed
|
||||||
|
|
||||||
|
This function takes in two parameters, currentFloor and currentScore, and returns a speed value.
|
||||||
|
```
|
||||||
|
function(currentFloor, currentScore) {
|
||||||
|
return number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### hookAngle
|
||||||
|
|
||||||
|
This function takes in two parameters, currentFloor and currentScore, and returns a angle value.
|
||||||
|
```
|
||||||
|
function(currentFloor, currentScore) {
|
||||||
|
return number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### landBlockSpeed
|
||||||
|
|
||||||
|
This function takes in two parameters, currentFloor and currentScore, and returns a speed value.
|
||||||
|
```
|
||||||
|
function(currentFloor, currentScore) {
|
||||||
|
return number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### setGameScore
|
||||||
|
|
||||||
|
This function takes in one parameter, score, and sets currentScore to score.
|
||||||
|
```
|
||||||
|
function(score) {
|
||||||
|
// your logic
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### setGameSuccess
|
||||||
|
|
||||||
|
This function takes in one parameter, score, and sets GameSuccess to successCount.
|
||||||
|
```
|
||||||
|
function(successCount) {
|
||||||
|
// your logic
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### setGameFailed
|
||||||
|
|
||||||
|
This function takes in one parameter, score, and sets GameFailed to failedCount.
|
||||||
|
```
|
||||||
|
function(failedCount) {
|
||||||
|
// your logic
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT license.
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
[](LICENSE)
|
||||||
|
|
||||||
|
[English](./README.md) | 简体中文
|
||||||
|
|
||||||
|
<h1 align="center">盖楼游戏</h1>
|
||||||
|
<p align="center"><img src="https://o2qq673j2.qnssl.com/tower-loading.gif"/></p>
|
||||||
|
|
||||||
|
> 一个基于 Canvas 的盖楼游戏
|
||||||
|
|
||||||
|
> Tower Building Game (Tower Bloxx Deluxe Skyscraper)
|
||||||
|
|
||||||
|
## Demo 预览
|
||||||
|
<p align="center"><img src="https://user-images.githubusercontent.com/17680888/47480922-93a20c00-d864-11e8-8f7c-6d1d60184730.gif"/></p>
|
||||||
|
<h2 align="center"><a href="https://iamkun.github.io/tower_game">在线预览地址 (Demo Link)</a></h2>
|
||||||
|
<h4 align="center">手机设备可以扫描下方二维码</h4>
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://user-images.githubusercontent.com/17680888/47480646-abc55b80-d863-11e8-9337-4ea768ebe55d.png" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Game Rule 游戏规则
|
||||||
|
|
||||||
|
以下为默认游戏规则,也可参照下节自定义游戏参数
|
||||||
|
|
||||||
|
- 每局游戏生命值为3,掉落一块楼层生命值减1,掉落3块后游戏结束,单局游戏无时间限制
|
||||||
|
|
||||||
|
- 成功盖楼加25分,完美盖楼加50分,连续完美盖楼额外加25分,楼层掉落扣除生命值1,单局游戏共有3次掉落机会
|
||||||
|
|
||||||
|
栗子:第一块完美盖楼加50分,第二块连续完美盖楼加75分,第三块连续完美盖楼加100分,依此类推……
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://o2qq673j2.qnssl.com/Fv7ewqHHXeAnUAlF7AI9ndQulEOC" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Customise 自定义
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/iamkun/tower_game.git
|
||||||
|
cd tower_game
|
||||||
|
npm install
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
打开 `http://localhost:8082`
|
||||||
|
|
||||||
|
- 图片、音频资源可以直接替换 `assets` 目录下对应的资源文件
|
||||||
|
- 游戏规则可以修改 `index.html` 文件 `L480` 的 `option` 对象
|
||||||
|
|
||||||
|
## Option 自定义选项
|
||||||
|
|
||||||
|
可以使用以下 `option` 表格里的参数,完成游戏自定义,**所有参数都是非必填项**
|
||||||
|
|
||||||
|
| Option | Type | Description |
|
||||||
|
|---------|--------|-------------|
|
||||||
|
| width | number | 游戏主画面宽度 |
|
||||||
|
| height | number | 游戏主画面高度 |
|
||||||
|
| canvasId | string | Canvas 的 DOM ID |
|
||||||
|
| soundOn | boolean | 是否开启声音 |
|
||||||
|
| successScore | number | 成功盖楼分数 |
|
||||||
|
| perfectScore | number | 完美盖楼额外奖励分数 |
|
||||||
|
| <a href="#hookspeed">hookSpeed</a> | function | 钩子平移速度 |
|
||||||
|
| <a href="#hookangle">hookAngle</a> | function | 钩子摆动角度 |
|
||||||
|
| <a href="#landblockspeed">landBlockSpeed</a> | function | 下方楼房横向速度 |
|
||||||
|
| <a href="#setgamescore">setGameScore</a> | function | 当前游戏分数hook |
|
||||||
|
| <a href="#setgamesuccess">setGameSuccess</a> | function | 当前游戏成功次数hook |
|
||||||
|
| <a href="#setgamefailed">setGameFailed</a> | function | 当前游戏失败次数hook |
|
||||||
|
|
||||||
|
#### hookSpeed
|
||||||
|
钩子平移速度
|
||||||
|
函数接收两个参数,当前成功楼层和当前分数,返回速度数值
|
||||||
|
```
|
||||||
|
function(currentFloor, currentScore) {
|
||||||
|
return number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### hookAngle
|
||||||
|
钩子摆动角度
|
||||||
|
函数接收两个参数,当前成功楼层和当前分数,返回角度数值
|
||||||
|
```
|
||||||
|
function(currentFloor, currentScore) {
|
||||||
|
return number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### landBlockSpeed
|
||||||
|
下方楼房平移速度
|
||||||
|
函数接收两个参数,当前成功楼层和当前分数,返回速度数值
|
||||||
|
```
|
||||||
|
function(currentFloor, currentScore) {
|
||||||
|
return number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### setGameScore
|
||||||
|
当前游戏分数hook
|
||||||
|
函数接收一个参数,当前游戏分数
|
||||||
|
```
|
||||||
|
function(score) {
|
||||||
|
// your logic
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### setGameSuccess
|
||||||
|
当前游戏成功次数hook
|
||||||
|
函数接收一个参数,当前游戏成功次数
|
||||||
|
```
|
||||||
|
function(successCount) {
|
||||||
|
// your logic
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### setGameFailed
|
||||||
|
当前游戏失败次数hook
|
||||||
|
函数接收一个参数,当前游戏失败次数
|
||||||
|
```
|
||||||
|
function(failedCount) {
|
||||||
|
// your logic
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT license.
|
||||||
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 273 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 928 B |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
@@ -0,0 +1,17 @@
|
|||||||
|
const express = require('express')
|
||||||
|
const path = require('path')
|
||||||
|
const opn = require('opn')
|
||||||
|
|
||||||
|
const server = express()
|
||||||
|
const host = 'http://localhost:8082'
|
||||||
|
server.use('/assets', express.static(path.resolve(__dirname, './assets')))
|
||||||
|
server.use('/dist', express.static(path.resolve(__dirname, './dist')))
|
||||||
|
|
||||||
|
server.get('*', (req, res) => {
|
||||||
|
res.sendFile(path.resolve(__dirname, './index.html'));
|
||||||
|
})
|
||||||
|
|
||||||
|
server.listen(8082, () => {
|
||||||
|
console.log(`server started at ${host}`)
|
||||||
|
opn(host)
|
||||||
|
})
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "tower_game",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "npm run build && node index.js",
|
||||||
|
"build": "webpack --mode production --module-bind js=babel-loader"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/bmqb/tower_game.git"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/bmqb/tower_game/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/bmqb/tower_game#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.0.0-beta.42",
|
||||||
|
"@babel/preset-env": "^7.0.0-beta.42",
|
||||||
|
"babel-loader": "^8.0.0-beta",
|
||||||
|
"express": "^4.16.3",
|
||||||
|
"opn": "^5.3.0",
|
||||||
|
"webpack": "^4.0.1",
|
||||||
|
"webpack-cli": "^3.1.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cooljs": "^1.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import { Instance } from 'cooljs'
|
||||||
|
import { blockAction, blockPainter } from './block'
|
||||||
|
import {
|
||||||
|
checkMoveDown,
|
||||||
|
getMoveDownValue,
|
||||||
|
drawYellowString,
|
||||||
|
getAngleBase
|
||||||
|
} from './utils'
|
||||||
|
import { addFlight } from './flight'
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
export const endAnimate = (engine) => {
|
||||||
|
const gameStartNow = engine.getVariable(constant.gameStartNow)
|
||||||
|
if (!gameStartNow) return
|
||||||
|
const successCount = engine.getVariable(constant.successCount, 0)
|
||||||
|
const failedCount = engine.getVariable(constant.failedCount)
|
||||||
|
const gameScore = engine.getVariable(constant.gameScore, 0)
|
||||||
|
const threeFiguresOffset = Number(successCount) > 99 ? engine.width * 0.1 : 0
|
||||||
|
|
||||||
|
drawYellowString(engine, {
|
||||||
|
string: 'floor',
|
||||||
|
size: engine.width * 0.06,
|
||||||
|
x: (engine.width * 0.24) + threeFiguresOffset,
|
||||||
|
y: engine.width * 0.12,
|
||||||
|
textAlign: 'left',
|
||||||
|
fontName: 'Arial',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
})
|
||||||
|
drawYellowString(engine, {
|
||||||
|
string: successCount,
|
||||||
|
size: engine.width * 0.17,
|
||||||
|
x: (engine.width * 0.22) + threeFiguresOffset,
|
||||||
|
y: engine.width * 0.2,
|
||||||
|
textAlign: 'right'
|
||||||
|
})
|
||||||
|
const score = engine.getImg('score')
|
||||||
|
const scoreWidth = score.width
|
||||||
|
const scoreHeight = score.height
|
||||||
|
const zoomedWidth = engine.width * 0.35
|
||||||
|
const zoomedHeight = (scoreHeight * zoomedWidth) / scoreWidth
|
||||||
|
engine.ctx.drawImage(
|
||||||
|
score,
|
||||||
|
engine.width * 0.61,
|
||||||
|
engine.width * 0.038,
|
||||||
|
zoomedWidth,
|
||||||
|
zoomedHeight
|
||||||
|
)
|
||||||
|
drawYellowString(engine, {
|
||||||
|
string: gameScore,
|
||||||
|
size: engine.width * 0.06,
|
||||||
|
x: engine.width * 0.9,
|
||||||
|
y: engine.width * 0.11,
|
||||||
|
textAlign: 'right'
|
||||||
|
})
|
||||||
|
const { ctx } = engine
|
||||||
|
const heart = engine.getImg('heart')
|
||||||
|
const heartWidth = heart.width
|
||||||
|
const heartHeight = heart.height
|
||||||
|
const zoomedHeartWidth = engine.width * 0.08
|
||||||
|
const zoomedHeartHeight = (heartHeight * zoomedHeartWidth) / heartWidth
|
||||||
|
for (let i = 1; i <= 3; i += 1) {
|
||||||
|
ctx.save()
|
||||||
|
if (i <= failedCount) {
|
||||||
|
ctx.globalAlpha = 0.2
|
||||||
|
}
|
||||||
|
ctx.drawImage(
|
||||||
|
heart,
|
||||||
|
(engine.width * 0.66) + ((i - 1) * zoomedHeartWidth),
|
||||||
|
engine.width * 0.16,
|
||||||
|
zoomedHeartWidth,
|
||||||
|
zoomedHeartHeight
|
||||||
|
)
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startAnimate = (engine) => {
|
||||||
|
const gameStartNow = engine.getVariable(constant.gameStartNow)
|
||||||
|
if (!gameStartNow) return
|
||||||
|
const lastBlock = engine.getInstance(`block_${engine.getVariable(constant.blockCount)}`)
|
||||||
|
if (!lastBlock || [constant.land, constant.out].indexOf(lastBlock.status) > -1) {
|
||||||
|
if (checkMoveDown(engine) && getMoveDownValue(engine)) return
|
||||||
|
if (engine.checkTimeMovement(constant.hookUpMovement)) return
|
||||||
|
const angleBase = getAngleBase(engine)
|
||||||
|
const initialAngle = (Math.PI
|
||||||
|
* engine.utils.random(angleBase, angleBase + 5)
|
||||||
|
* engine.utils.randomPositiveNegative()
|
||||||
|
) / 180
|
||||||
|
engine.setVariable(constant.blockCount, engine.getVariable(constant.blockCount) + 1)
|
||||||
|
engine.setVariable(constant.initialAngle, initialAngle)
|
||||||
|
engine.setTimeMovement(constant.hookDownMovement, 500)
|
||||||
|
const block = new Instance({
|
||||||
|
name: `block_${engine.getVariable(constant.blockCount)}`,
|
||||||
|
action: blockAction,
|
||||||
|
painter: blockPainter
|
||||||
|
})
|
||||||
|
engine.addInstance(block)
|
||||||
|
}
|
||||||
|
const successCount = Number(engine.getVariable(constant.successCount, 0))
|
||||||
|
switch (successCount) {
|
||||||
|
case 2:
|
||||||
|
addFlight(engine, 1, 'leftToRight')
|
||||||
|
break
|
||||||
|
case 6:
|
||||||
|
addFlight(engine, 2, 'rightToLeft')
|
||||||
|
break
|
||||||
|
case 8:
|
||||||
|
addFlight(engine, 3, 'leftToRight')
|
||||||
|
break
|
||||||
|
case 14:
|
||||||
|
addFlight(engine, 4, 'bottomToTop')
|
||||||
|
break
|
||||||
|
case 18:
|
||||||
|
addFlight(engine, 5, 'bottomToTop')
|
||||||
|
break
|
||||||
|
case 22:
|
||||||
|
addFlight(engine, 6, 'bottomToTop')
|
||||||
|
break
|
||||||
|
case 25:
|
||||||
|
addFlight(engine, 7, 'rightTopToLeft')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import { checkMoveDown, getMoveDownValue } from './utils'
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
export const backgroundImg = (engine) => {
|
||||||
|
const bg = engine.getImg('background')
|
||||||
|
const bgWidth = bg.width
|
||||||
|
const bgHeight = bg.height
|
||||||
|
const zoomedHeight = (bgHeight * engine.width) / bgWidth
|
||||||
|
let offsetHeight = engine.getVariable(constant.bgImgOffset, engine.height - zoomedHeight)
|
||||||
|
if (offsetHeight > engine.height) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
engine.getTimeMovement(
|
||||||
|
constant.moveDownMovement,
|
||||||
|
[[offsetHeight, offsetHeight + (getMoveDownValue(engine, { pixelsPerFrame: s => s / 2 }))]],
|
||||||
|
(value) => {
|
||||||
|
offsetHeight = value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'background'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
engine.getTimeMovement(
|
||||||
|
constant.bgInitMovement,
|
||||||
|
[[offsetHeight, offsetHeight + (zoomedHeight / 4)]],
|
||||||
|
(value) => {
|
||||||
|
offsetHeight = value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
engine.setVariable(constant.bgImgOffset, offsetHeight)
|
||||||
|
engine.setVariable(constant.lineInitialOffset, engine.height - (zoomedHeight * 0.394))
|
||||||
|
engine.ctx.drawImage(
|
||||||
|
bg,
|
||||||
|
0, offsetHeight,
|
||||||
|
engine.width, zoomedHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLinearGradientColorRgb = (colorArr, colorIndex, proportion) => {
|
||||||
|
const currentIndex = colorIndex + 1 >= colorArr.length ? colorArr.length - 1 : colorIndex
|
||||||
|
const colorCurrent = colorArr[currentIndex]
|
||||||
|
const nextIndex = currentIndex + 1 >= colorArr.length - 1 ? currentIndex : currentIndex + 1
|
||||||
|
const colorNext = colorArr[nextIndex]
|
||||||
|
const calRgbValue = (index) => {
|
||||||
|
const current = colorCurrent[index]
|
||||||
|
const next = colorNext[index]
|
||||||
|
return Math.round(current + ((next - current) * proportion))
|
||||||
|
}
|
||||||
|
return `rgb(${calRgbValue(0)}, ${calRgbValue(1)}, ${calRgbValue(2)})`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const backgroundLinearGradient = (engine) => {
|
||||||
|
const grad = engine.ctx.createLinearGradient(0, 0, 0, engine.height)
|
||||||
|
const colorArr = [
|
||||||
|
[200, 255, 150],
|
||||||
|
[105, 230, 240],
|
||||||
|
[90, 190, 240],
|
||||||
|
[85, 100, 190],
|
||||||
|
[55, 20, 35],
|
||||||
|
[75, 25, 35],
|
||||||
|
[25, 0, 10]
|
||||||
|
]
|
||||||
|
const offsetHeight = engine.getVariable(constant.bgLinearGradientOffset, 0)
|
||||||
|
if (checkMoveDown(engine)) {
|
||||||
|
engine.setVariable(
|
||||||
|
constant.bgLinearGradientOffset
|
||||||
|
, offsetHeight + (getMoveDownValue(engine) * 1.5)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const colorIndex = parseInt(offsetHeight / engine.height, 10)
|
||||||
|
const calOffsetHeight = offsetHeight % engine.height
|
||||||
|
const proportion = calOffsetHeight / engine.height
|
||||||
|
const colorBase = getLinearGradientColorRgb(colorArr, colorIndex, proportion)
|
||||||
|
const colorTop = getLinearGradientColorRgb(colorArr, colorIndex + 1, proportion)
|
||||||
|
grad.addColorStop(0, colorTop)
|
||||||
|
grad.addColorStop(1, colorBase)
|
||||||
|
engine.ctx.fillStyle = grad
|
||||||
|
engine.ctx.beginPath()
|
||||||
|
engine.ctx.rect(0, 0, engine.width, engine.height)
|
||||||
|
engine.ctx.fill()
|
||||||
|
|
||||||
|
// lightning
|
||||||
|
const lightning = () => {
|
||||||
|
engine.ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'
|
||||||
|
engine.ctx.fillRect(0, 0, engine.width, engine.height)
|
||||||
|
}
|
||||||
|
engine.getTimeMovement(
|
||||||
|
constant.lightningMovement, [], () => {},
|
||||||
|
{
|
||||||
|
before: lightning,
|
||||||
|
after: lightning
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const background = (engine) => {
|
||||||
|
backgroundLinearGradient(engine)
|
||||||
|
backgroundImg(engine)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
import {
|
||||||
|
getMoveDownValue,
|
||||||
|
getLandBlockVelocity,
|
||||||
|
getSwingBlockVelocity,
|
||||||
|
touchEventHandler,
|
||||||
|
addSuccessCount,
|
||||||
|
addFailedCount,
|
||||||
|
addScore
|
||||||
|
} from './utils'
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
const checkCollision = (block, line) => {
|
||||||
|
// 0 goon 1 drop 2 rotate left 3 rotate right 4 ok 5 perfect
|
||||||
|
if (block.y + block.height >= line.y) {
|
||||||
|
if (block.x < line.x - block.calWidth || block.x > line.collisionX + block.calWidth) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if (block.x < line.x) {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
if (block.x > line.collisionX) {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
if (block.x > line.x + (block.calWidth * 0.8) && block.x < line.x + (block.calWidth * 1.2)) {
|
||||||
|
// -10% +10%
|
||||||
|
return 5
|
||||||
|
}
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
const swing = (instance, engine, time) => {
|
||||||
|
const ropeHeight = engine.getVariable(constant.ropeHeight)
|
||||||
|
if (instance.status !== constant.swing) return
|
||||||
|
const i = instance
|
||||||
|
const initialAngle = engine.getVariable(constant.initialAngle)
|
||||||
|
i.angle = initialAngle *
|
||||||
|
getSwingBlockVelocity(engine, time)
|
||||||
|
i.weightX = i.x +
|
||||||
|
(Math.sin(i.angle) * ropeHeight)
|
||||||
|
i.weightY = i.y +
|
||||||
|
(Math.cos(i.angle) * ropeHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkBlockOut = (instance, engine) => {
|
||||||
|
if (instance.status === constant.rotateLeft) {
|
||||||
|
// 左转 要等右上角消失才算消失
|
||||||
|
if (instance.y - instance.width >= engine.height) {
|
||||||
|
instance.visible = false
|
||||||
|
instance.status = constant.out
|
||||||
|
addFailedCount(engine)
|
||||||
|
}
|
||||||
|
} else if (instance.y >= engine.height) {
|
||||||
|
instance.visible = false
|
||||||
|
instance.status = constant.out
|
||||||
|
addFailedCount(engine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const blockAction = (instance, engine, time) => {
|
||||||
|
const i = instance
|
||||||
|
const ropeHeight = engine.getVariable(constant.ropeHeight)
|
||||||
|
if (!i.visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!i.ready) {
|
||||||
|
i.ready = true
|
||||||
|
i.status = constant.swing
|
||||||
|
instance.updateWidth(engine.getVariable(constant.blockWidth))
|
||||||
|
instance.updateHeight(engine.getVariable(constant.blockHeight))
|
||||||
|
instance.x = engine.width / 2
|
||||||
|
instance.y = ropeHeight * -1.5
|
||||||
|
}
|
||||||
|
const line = engine.getInstance('line')
|
||||||
|
switch (i.status) {
|
||||||
|
case constant.swing:
|
||||||
|
engine.getTimeMovement(
|
||||||
|
constant.hookDownMovement,
|
||||||
|
[[instance.y, instance.y + ropeHeight]],
|
||||||
|
(value) => {
|
||||||
|
instance.y = value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'block'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
swing(instance, engine, time)
|
||||||
|
break
|
||||||
|
case constant.beforeDrop:
|
||||||
|
i.x = instance.weightX - instance.calWidth
|
||||||
|
i.y = instance.weightY + (0.3 * instance.height) // add rope height
|
||||||
|
i.rotate = 0
|
||||||
|
i.ay = engine.pixelsPerFrame(0.0003 * engine.height) // acceleration of gravity
|
||||||
|
i.startDropTime = time
|
||||||
|
i.status = constant.drop
|
||||||
|
break
|
||||||
|
case constant.drop:
|
||||||
|
const deltaTime = time - i.startDropTime
|
||||||
|
i.startDropTime = time
|
||||||
|
i.vy += i.ay * deltaTime
|
||||||
|
i.y += (i.vy * deltaTime) + (0.5 * i.ay * (deltaTime ** 2))
|
||||||
|
const collision = checkCollision(instance, line)
|
||||||
|
const blockY = line.y - instance.height
|
||||||
|
const calRotate = (ins) => {
|
||||||
|
ins.originOutwardAngle = Math.atan(ins.height / ins.outwardOffset)
|
||||||
|
ins.originHypotenuse = Math.sqrt((ins.height ** 2)
|
||||||
|
+ (ins.outwardOffset ** 2))
|
||||||
|
engine.playAudio('rotate')
|
||||||
|
}
|
||||||
|
switch (collision) {
|
||||||
|
case 1:
|
||||||
|
checkBlockOut(instance, engine)
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
i.status = constant.rotateLeft
|
||||||
|
instance.y = blockY
|
||||||
|
instance.outwardOffset = (line.x + instance.calWidth) - instance.x
|
||||||
|
calRotate(instance)
|
||||||
|
break
|
||||||
|
case 3:
|
||||||
|
i.status = constant.rotateRight
|
||||||
|
instance.y = blockY
|
||||||
|
instance.outwardOffset = (line.collisionX + instance.calWidth) - instance.x
|
||||||
|
calRotate(instance)
|
||||||
|
break
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
i.status = constant.land
|
||||||
|
const lastSuccessCount = engine.getVariable(constant.successCount)
|
||||||
|
addSuccessCount(engine)
|
||||||
|
engine.setTimeMovement(constant.moveDownMovement, 500)
|
||||||
|
if (lastSuccessCount === 10 || lastSuccessCount === 15) {
|
||||||
|
engine.setTimeMovement(constant.lightningMovement, 150)
|
||||||
|
}
|
||||||
|
instance.y = blockY
|
||||||
|
line.y = blockY
|
||||||
|
line.x = i.x - i.calWidth
|
||||||
|
line.collisionX = line.x + i.width
|
||||||
|
// 作弊检测 超出左边或右边1/3
|
||||||
|
const cheatWidth = i.width * 0.3
|
||||||
|
if (i.x > engine.width - (cheatWidth * 2)
|
||||||
|
|| i.x < -cheatWidth) {
|
||||||
|
engine.setVariable(constant.hardMode, true)
|
||||||
|
}
|
||||||
|
if (collision === 5) {
|
||||||
|
instance.perfect = true
|
||||||
|
addScore(engine, true)
|
||||||
|
engine.playAudio('drop-perfect')
|
||||||
|
} else {
|
||||||
|
addScore(engine)
|
||||||
|
engine.playAudio('drop')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case constant.land:
|
||||||
|
engine.getTimeMovement(
|
||||||
|
constant.moveDownMovement,
|
||||||
|
[[instance.y, instance.y + (getMoveDownValue(engine, { pixelsPerFrame: s => s / 2 }))]],
|
||||||
|
(value) => {
|
||||||
|
if (!instance.visible) return
|
||||||
|
instance.y = value
|
||||||
|
if (instance.y > engine.height) {
|
||||||
|
instance.visible = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: instance.name
|
||||||
|
}
|
||||||
|
)
|
||||||
|
instance.x += getLandBlockVelocity(engine, time)
|
||||||
|
break
|
||||||
|
case constant.rotateLeft:
|
||||||
|
case constant.rotateRight:
|
||||||
|
const isRight = i.status === constant.rotateRight
|
||||||
|
const rotateSpeed = engine.pixelsPerFrame(Math.PI * 4)
|
||||||
|
const isShouldFall = isRight ? instance.rotate > 1.3 : instance.rotate < -1.3// 75度
|
||||||
|
const leftFix = isRight ? 1 : -1
|
||||||
|
if (isShouldFall) {
|
||||||
|
instance.rotate += (rotateSpeed / 8) * leftFix
|
||||||
|
instance.y += engine.pixelsPerFrame(engine.height * 0.7)
|
||||||
|
instance.x += engine.pixelsPerFrame(engine.width * 0.3) * leftFix
|
||||||
|
} else {
|
||||||
|
let rotateRatio = (instance.calWidth - instance.outwardOffset)
|
||||||
|
/ instance.calWidth
|
||||||
|
rotateRatio = rotateRatio > 0.5 ? rotateRatio : 0.5
|
||||||
|
instance.rotate += rotateSpeed * rotateRatio * leftFix
|
||||||
|
const angle = instance.originOutwardAngle + instance.rotate
|
||||||
|
const rotateAxisX = isRight ? line.collisionX + instance.calWidth
|
||||||
|
: line.x + instance.calWidth
|
||||||
|
const rotateAxisY = line.y
|
||||||
|
instance.x = rotateAxisX -
|
||||||
|
(Math.cos(angle) * instance.originHypotenuse)
|
||||||
|
instance.y = rotateAxisY -
|
||||||
|
(Math.sin(angle) * instance.originHypotenuse)
|
||||||
|
}
|
||||||
|
checkBlockOut(instance, engine)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawSwingBlock = (instance, engine) => {
|
||||||
|
const bl = engine.getImg('blockRope')
|
||||||
|
engine.ctx.drawImage(
|
||||||
|
bl, instance.weightX - instance.calWidth
|
||||||
|
, instance.weightY
|
||||||
|
, instance.width, instance.height * 1.3
|
||||||
|
)
|
||||||
|
const leftX = instance.weightX - instance.calWidth
|
||||||
|
engine.debugLineY(leftX)
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawBlock = (instance, engine) => {
|
||||||
|
const { perfect } = instance
|
||||||
|
const bl = engine.getImg(perfect ? 'block-perfect' : 'block')
|
||||||
|
engine.ctx.drawImage(bl, instance.x, instance.y, instance.width, instance.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawRotatedBlock = (instance, engine) => {
|
||||||
|
const { ctx } = engine
|
||||||
|
ctx.save()
|
||||||
|
ctx.translate(instance.x, instance.y)
|
||||||
|
ctx.rotate(instance.rotate)
|
||||||
|
ctx.translate(-instance.x, -instance.y)
|
||||||
|
drawBlock(instance, engine)
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const blockPainter = (instance, engine) => {
|
||||||
|
const { status } = instance
|
||||||
|
switch (status) {
|
||||||
|
case constant.swing:
|
||||||
|
drawSwingBlock(instance, engine)
|
||||||
|
break
|
||||||
|
case constant.drop:
|
||||||
|
case constant.land:
|
||||||
|
drawBlock(instance, engine)
|
||||||
|
break
|
||||||
|
case constant.rotateLeft:
|
||||||
|
case constant.rotateRight:
|
||||||
|
drawRotatedBlock(instance, engine)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { checkMoveDown, getMoveDownValue } from './utils'
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
const randomCloudImg = (instance) => {
|
||||||
|
const { count } = instance
|
||||||
|
const clouds = ['c1', 'c2', 'c3']
|
||||||
|
const stones = ['c4', 'c5', 'c6', 'c7', 'c8']
|
||||||
|
const randomImg = array => (array[Math.floor(Math.random() * array.length)])
|
||||||
|
instance.imgName = count > 6 ? randomImg(stones) : randomImg(clouds)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cloudAction = (instance, engine) => {
|
||||||
|
if (!instance.ready) {
|
||||||
|
instance.ready = true
|
||||||
|
randomCloudImg(instance)
|
||||||
|
instance.width = engine.getVariable(constant.cloudSize)
|
||||||
|
instance.height = engine.getVariable(constant.cloudSize)
|
||||||
|
const engineW = engine.width
|
||||||
|
const engineH = engine.height
|
||||||
|
const positionArr = [
|
||||||
|
{ x: engineW * 0.1, y: -engineH * 0.66 },
|
||||||
|
{ x: engineW * 0.65, y: -engineH * 0.33 },
|
||||||
|
{ x: engineW * 0.1, y: 0 },
|
||||||
|
{ x: engineW * 0.65, y: engineH * 0.33 }
|
||||||
|
]
|
||||||
|
const position = positionArr[instance.index - 1]
|
||||||
|
instance.x = engine.utils.random(position.x, (position.x * 1.2))
|
||||||
|
instance.originX = instance.x
|
||||||
|
instance.ax = engine.pixelsPerFrame(instance.width * engine.utils.random(0.05, 0.08)
|
||||||
|
* engine.utils.randomPositiveNegative())
|
||||||
|
instance.y = engine.utils.random(position.y, (position.y * 1.2))
|
||||||
|
}
|
||||||
|
instance.x += instance.ax
|
||||||
|
if (instance.x >= instance.originX + instance.width
|
||||||
|
|| instance.x <= instance.originX - instance.width) {
|
||||||
|
instance.ax *= -1
|
||||||
|
}
|
||||||
|
if (checkMoveDown(engine)) {
|
||||||
|
instance.y += getMoveDownValue(engine) * 1.2
|
||||||
|
}
|
||||||
|
if (instance.y >= engine.height) {
|
||||||
|
instance.y = -engine.height * 0.66
|
||||||
|
instance.count += 4
|
||||||
|
randomCloudImg(instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cloudPainter = (instance, engine) => {
|
||||||
|
const { ctx } = engine
|
||||||
|
const cloud = engine.getImg(instance.imgName)
|
||||||
|
ctx.drawImage(cloud, instance.x, instance.y, instance.width, instance.height)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
export const gameStartNow = 'GAME_START_NOW'
|
||||||
|
export const gameUserOption = 'GAME_USER_OPTION'
|
||||||
|
export const hardMode = 'HARD_MODE'
|
||||||
|
|
||||||
|
export const successCount = 'SUCCESS_COUNT'
|
||||||
|
export const failedCount = 'FAILED_COUNT'
|
||||||
|
export const perfectCount = 'PERFECT_COUNT'
|
||||||
|
export const gameScore = 'GAME_SCORE'
|
||||||
|
|
||||||
|
export const hookDown = 'HOOK_DOWN'
|
||||||
|
export const hookUp = 'HOOK_UP'
|
||||||
|
export const hookNormal = 'HOOK_NORMAL'
|
||||||
|
|
||||||
|
export const bgImgOffset = 'BACKGROUND_IMG_OFFSET_HEIGHT'
|
||||||
|
export const lineInitialOffset = 'LINE_INITIAL_OFFSET'
|
||||||
|
export const bgLinearGradientOffset = 'BACKGROUND_LINEAR_GRADIENT_OFFSET_HEIGHT'
|
||||||
|
|
||||||
|
|
||||||
|
export const blockCount = 'BLOCK_COUNT'
|
||||||
|
export const blockWidth = 'BLOCK_WIDTH'
|
||||||
|
export const blockHeight = 'BLOCK_HEIGHT'
|
||||||
|
export const cloudSize = 'CLOUD_SIZE'
|
||||||
|
export const ropeHeight = 'ROPE_HEIGHT'
|
||||||
|
export const flightCount = 'FLIGHT_COUNT'
|
||||||
|
export const flightLayer = 'FLIGHT_LAYER'
|
||||||
|
|
||||||
|
export const rotateRight = 'ROTATE_RIGHT'
|
||||||
|
export const rotateLeft = 'ROTATE_LEFT'
|
||||||
|
export const swing = 'SWING'
|
||||||
|
export const beforeDrop = 'BEFORE_DROP'
|
||||||
|
export const drop = 'DROP'
|
||||||
|
export const land = 'LAND'
|
||||||
|
export const out = 'OUT'
|
||||||
|
|
||||||
|
export const initialAngle = 'INITIAL_ANGLE'
|
||||||
|
|
||||||
|
export const bgInitMovement = 'BG_INIT_MOVEMENT'
|
||||||
|
export const hookDownMovement = 'HOOK_DOWN_MOVEMENT'
|
||||||
|
export const hookUpMovement = 'HOOK_UP_MOVEMENT'
|
||||||
|
export const lightningMovement = 'LIGHTNING_MOVEMENT'
|
||||||
|
export const tutorialMovement = 'TUTORIAL_MOVEMENT'
|
||||||
|
export const moveDownMovement = 'MOVE_DOWN_MOVEMENT'
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { Instance } from 'cooljs'
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
const getActionConfig = (engine, type) => {
|
||||||
|
const {
|
||||||
|
width, height, utils
|
||||||
|
} = engine
|
||||||
|
const { random } = utils
|
||||||
|
const size = engine.getVariable(constant.cloudSize)
|
||||||
|
const actionTypes = {
|
||||||
|
bottomToTop: {
|
||||||
|
x: width * random(0.3, 0.7),
|
||||||
|
y: height,
|
||||||
|
vx: 0,
|
||||||
|
vy: engine.pixelsPerFrame(height) * 0.7 * -1
|
||||||
|
},
|
||||||
|
leftToRight: {
|
||||||
|
x: size * -1,
|
||||||
|
y: height * random(0.3, 0.6),
|
||||||
|
vx: engine.pixelsPerFrame(width) * 0.4,
|
||||||
|
vy: engine.pixelsPerFrame(height) * 0.1 * -1
|
||||||
|
},
|
||||||
|
rightToLeft: {
|
||||||
|
x: width,
|
||||||
|
y: height * random(0.2, 0.5),
|
||||||
|
vx: engine.pixelsPerFrame(width) * 0.4 * -1,
|
||||||
|
vy: engine.pixelsPerFrame(height) * 0.1
|
||||||
|
},
|
||||||
|
rightTopToLeft: {
|
||||||
|
x: width,
|
||||||
|
y: 0,
|
||||||
|
vx: engine.pixelsPerFrame(width) * 0.6 * -1,
|
||||||
|
vy: engine.pixelsPerFrame(height) * 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actionTypes[type]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const flightAction = (instance, engine) => {
|
||||||
|
const { visible, ready, type } = instance
|
||||||
|
if (!visible) return
|
||||||
|
const size = engine.getVariable(constant.cloudSize)
|
||||||
|
if (!ready) {
|
||||||
|
const action = getActionConfig(engine, type)
|
||||||
|
instance.ready = true
|
||||||
|
instance.width = size
|
||||||
|
instance.height = size
|
||||||
|
instance.x = action.x
|
||||||
|
instance.y = action.y
|
||||||
|
instance.vx = action.vx
|
||||||
|
instance.vy = action.vy
|
||||||
|
}
|
||||||
|
instance.x += instance.vx
|
||||||
|
instance.y += instance.vy
|
||||||
|
if (instance.y + size < 0
|
||||||
|
|| instance.y > engine.height
|
||||||
|
|| instance.x + size < 0
|
||||||
|
|| instance.x > engine.width) {
|
||||||
|
instance.visible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const flightPainter = (instance, engine) => {
|
||||||
|
const { ctx } = engine
|
||||||
|
const flight = engine.getImg(instance.imgName)
|
||||||
|
ctx.drawImage(flight, instance.x, instance.y, instance.width, instance.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addFlight = (engine, number, type) => {
|
||||||
|
const flightCount = engine.getVariable(constant.flightCount)
|
||||||
|
if (flightCount === number) return
|
||||||
|
const flight = new Instance({
|
||||||
|
name: `flight_${number}`,
|
||||||
|
action: flightAction,
|
||||||
|
painter: flightPainter
|
||||||
|
})
|
||||||
|
flight.imgName = `f${number}`
|
||||||
|
flight.type = type
|
||||||
|
engine.addInstance(flight, constant.flightLayer)
|
||||||
|
engine.setVariable(constant.flightCount, number)
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { getSwingBlockVelocity } from './utils'
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
export const hookAction = (instance, engine, time) => {
|
||||||
|
const ropeHeight = engine.getVariable(constant.ropeHeight)
|
||||||
|
if (!instance.ready) {
|
||||||
|
instance.x = engine.width / 2
|
||||||
|
instance.y = ropeHeight * -1.5
|
||||||
|
instance.ready = true
|
||||||
|
}
|
||||||
|
engine.getTimeMovement(
|
||||||
|
constant.hookUpMovement,
|
||||||
|
[[instance.y, instance.y - ropeHeight]],
|
||||||
|
(value) => {
|
||||||
|
instance.y = value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
after: () => {
|
||||||
|
instance.y = ropeHeight * -1.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
engine.getTimeMovement(
|
||||||
|
constant.hookDownMovement,
|
||||||
|
[[instance.y, instance.y + ropeHeight]],
|
||||||
|
(value) => {
|
||||||
|
instance.y = value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'hook'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const initialAngle = engine.getVariable(constant.initialAngle)
|
||||||
|
instance.angle = initialAngle *
|
||||||
|
getSwingBlockVelocity(engine, time)
|
||||||
|
instance.weightX = instance.x +
|
||||||
|
(Math.sin(instance.angle) * ropeHeight)
|
||||||
|
instance.weightY = instance.y +
|
||||||
|
(Math.cos(instance.angle) * ropeHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hookPainter = (instance, engine) => {
|
||||||
|
const { ctx } = engine
|
||||||
|
const ropeHeight = engine.getVariable(constant.ropeHeight)
|
||||||
|
const ropeWidth = ropeHeight * 0.1
|
||||||
|
const hook = engine.getImg('hook')
|
||||||
|
ctx.save()
|
||||||
|
ctx.translate(instance.x, instance.y)
|
||||||
|
ctx.rotate((Math.PI * 2) - instance.angle)
|
||||||
|
ctx.translate(-instance.x, -instance.y)
|
||||||
|
engine.ctx.drawImage(hook, instance.x - (ropeWidth / 2), instance.y, ropeWidth, ropeHeight + 5)
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import { Engine, Instance } from 'cooljs'
|
||||||
|
import { touchEventHandler } from './utils'
|
||||||
|
import { background } from './background'
|
||||||
|
import { lineAction, linePainter } from './line'
|
||||||
|
import { cloudAction, cloudPainter } from './cloud'
|
||||||
|
import { hookAction, hookPainter } from './hook'
|
||||||
|
import { tutorialAction, tutorialPainter } from './tutorial'
|
||||||
|
import * as constant from './constant'
|
||||||
|
import { startAnimate, endAnimate } from './animateFuncs'
|
||||||
|
|
||||||
|
window.TowerGame = (option = {}) => {
|
||||||
|
const {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
canvasId,
|
||||||
|
soundOn
|
||||||
|
} = option
|
||||||
|
const game = new Engine({
|
||||||
|
canvasId,
|
||||||
|
highResolution: true,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
soundOn
|
||||||
|
})
|
||||||
|
const pathGenerator = (path) => `./assets/${path}`
|
||||||
|
|
||||||
|
game.addImg('background', pathGenerator('background.png'))
|
||||||
|
game.addImg('hook', pathGenerator('hook.png'))
|
||||||
|
game.addImg('blockRope', pathGenerator('block-rope.png'))
|
||||||
|
game.addImg('block', pathGenerator('block.png'))
|
||||||
|
game.addImg('block-perfect', pathGenerator('block-perfect.png'))
|
||||||
|
for (let i = 1; i <= 8; i += 1) {
|
||||||
|
game.addImg(`c${i}`, pathGenerator(`c${i}.png`))
|
||||||
|
}
|
||||||
|
game.addLayer(constant.flightLayer)
|
||||||
|
for (let i = 1; i <= 7; i += 1) {
|
||||||
|
game.addImg(`f${i}`, pathGenerator(`f${i}.png`))
|
||||||
|
}
|
||||||
|
game.swapLayer(0, 1)
|
||||||
|
game.addImg('tutorial', pathGenerator('tutorial.png'))
|
||||||
|
game.addImg('tutorial-arrow', pathGenerator('tutorial-arrow.png'))
|
||||||
|
game.addImg('heart', pathGenerator('heart.png'))
|
||||||
|
game.addImg('score', pathGenerator('score.png'))
|
||||||
|
game.addAudio('drop-perfect', pathGenerator('drop-perfect.mp3'))
|
||||||
|
game.addAudio('drop', pathGenerator('drop.mp3'))
|
||||||
|
game.addAudio('game-over', pathGenerator('game-over.mp3'))
|
||||||
|
game.addAudio('rotate', pathGenerator('rotate.mp3'))
|
||||||
|
game.addAudio('bgm', pathGenerator('bgm.mp3'))
|
||||||
|
game.setVariable(constant.blockWidth, game.width * 0.25)
|
||||||
|
game.setVariable(constant.blockHeight, game.getVariable(constant.blockWidth) * 0.71)
|
||||||
|
game.setVariable(constant.cloudSize, game.width * 0.3)
|
||||||
|
game.setVariable(constant.ropeHeight, game.height * 0.4)
|
||||||
|
game.setVariable(constant.blockCount, 0)
|
||||||
|
game.setVariable(constant.successCount, 0)
|
||||||
|
game.setVariable(constant.failedCount, 0)
|
||||||
|
game.setVariable(constant.gameScore, 0)
|
||||||
|
game.setVariable(constant.hardMode, false)
|
||||||
|
game.setVariable(constant.gameUserOption, option)
|
||||||
|
for (let i = 1; i <= 4; i += 1) {
|
||||||
|
const cloud = new Instance({
|
||||||
|
name: `cloud_${i}`,
|
||||||
|
action: cloudAction,
|
||||||
|
painter: cloudPainter
|
||||||
|
})
|
||||||
|
cloud.index = i
|
||||||
|
cloud.count = 5 - i
|
||||||
|
game.addInstance(cloud)
|
||||||
|
}
|
||||||
|
const line = new Instance({
|
||||||
|
name: 'line',
|
||||||
|
action: lineAction,
|
||||||
|
painter: linePainter
|
||||||
|
})
|
||||||
|
game.addInstance(line)
|
||||||
|
const hook = new Instance({
|
||||||
|
name: 'hook',
|
||||||
|
action: hookAction,
|
||||||
|
painter: hookPainter
|
||||||
|
})
|
||||||
|
game.addInstance(hook)
|
||||||
|
|
||||||
|
game.startAnimate = startAnimate
|
||||||
|
game.endAnimate = endAnimate
|
||||||
|
game.paintUnderInstance = background
|
||||||
|
game.addKeyDownListener('enter', () => {
|
||||||
|
if (game.debug) game.togglePaused()
|
||||||
|
})
|
||||||
|
game.touchStartListener = () => {
|
||||||
|
touchEventHandler(game)
|
||||||
|
}
|
||||||
|
|
||||||
|
game.playBgm = () => {
|
||||||
|
game.playAudio('bgm', true)
|
||||||
|
}
|
||||||
|
|
||||||
|
game.pauseBgm = () => {
|
||||||
|
game.pauseAudio('bgm')
|
||||||
|
}
|
||||||
|
|
||||||
|
game.start = () => {
|
||||||
|
const tutorial = new Instance({
|
||||||
|
name: 'tutorial',
|
||||||
|
action: tutorialAction,
|
||||||
|
painter: tutorialPainter
|
||||||
|
})
|
||||||
|
game.addInstance(tutorial)
|
||||||
|
const tutorialArrow = new Instance({
|
||||||
|
name: 'tutorial-arrow',
|
||||||
|
action: tutorialAction,
|
||||||
|
painter: tutorialPainter
|
||||||
|
})
|
||||||
|
game.addInstance(tutorialArrow)
|
||||||
|
game.setTimeMovement(constant.bgInitMovement, 500)
|
||||||
|
game.setTimeMovement(constant.tutorialMovement, 500)
|
||||||
|
game.setVariable(constant.gameStartNow, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return game
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { getMoveDownValue, getLandBlockVelocity } from './utils'
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
export const lineAction = (instance, engine, time) => {
|
||||||
|
const i = instance
|
||||||
|
if (!i.ready) {
|
||||||
|
i.y = engine.getVariable(constant.lineInitialOffset)
|
||||||
|
i.ready = true
|
||||||
|
i.collisionX = engine.width - engine.getVariable(constant.blockWidth)
|
||||||
|
}
|
||||||
|
engine.getTimeMovement(
|
||||||
|
constant.moveDownMovement,
|
||||||
|
[[instance.y, instance.y + (getMoveDownValue(engine, { pixelsPerFrame: s => s / 2 }))]],
|
||||||
|
(value) => {
|
||||||
|
instance.y = value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'line'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const landBlockVelocity = getLandBlockVelocity(engine, time)
|
||||||
|
instance.x += landBlockVelocity
|
||||||
|
instance.collisionX += landBlockVelocity
|
||||||
|
}
|
||||||
|
|
||||||
|
export const linePainter = (instance, engine) => {
|
||||||
|
const { ctx, debug } = engine
|
||||||
|
if (!debug) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.save()
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.strokeStyle = 'red'
|
||||||
|
ctx.moveTo(instance.x, instance.y)
|
||||||
|
ctx.lineTo(instance.collisionX, instance.y)
|
||||||
|
ctx.lineWidth = 1
|
||||||
|
ctx.stroke()
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { getHookStatus } from './utils'
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
export const tutorialAction = (instance, engine, time) => {
|
||||||
|
const { width, height } = engine
|
||||||
|
const { name } = instance
|
||||||
|
if (!instance.ready) {
|
||||||
|
instance.ready = true
|
||||||
|
const tutorialWidth = width * 0.2
|
||||||
|
instance.updateWidth(tutorialWidth)
|
||||||
|
instance.height = tutorialWidth * 0.46
|
||||||
|
instance.x = engine.calWidth - instance.calWidth
|
||||||
|
instance.y = height * 0.45
|
||||||
|
if (name !== 'tutorial') {
|
||||||
|
instance.y += instance.height * 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name !== 'tutorial') {
|
||||||
|
instance.y += Math.cos(time / 200) * instance.height * 0.01
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tutorialPainter = (instance, engine) => {
|
||||||
|
if (engine.checkTimeMovement(constant.tutorialMovement)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (getHookStatus(engine) !== constant.hookNormal) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { ctx } = engine
|
||||||
|
const { name } = instance
|
||||||
|
const t = engine.getImg(name)
|
||||||
|
ctx.drawImage(t, instance.x, instance.y, instance.width, instance.height)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
export const checkMoveDown = engine =>
|
||||||
|
(engine.checkTimeMovement(constant.moveDownMovement))
|
||||||
|
|
||||||
|
export const getMoveDownValue = (engine, store) => {
|
||||||
|
const pixelsPerFrame = store ? store.pixelsPerFrame : engine.pixelsPerFrame.bind(engine)
|
||||||
|
const successCount = engine.getVariable(constant.successCount)
|
||||||
|
const calHeight = engine.getVariable(constant.blockHeight) * 2
|
||||||
|
if (successCount <= 4) {
|
||||||
|
return pixelsPerFrame(calHeight * 1.25)
|
||||||
|
}
|
||||||
|
return pixelsPerFrame(calHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAngleBase = (engine) => {
|
||||||
|
const successCount = engine.getVariable(constant.successCount)
|
||||||
|
const gameScore = engine.getVariable(constant.gameScore)
|
||||||
|
const { hookAngle } = engine.getVariable(constant.gameUserOption)
|
||||||
|
if (hookAngle) {
|
||||||
|
return hookAngle(successCount, gameScore)
|
||||||
|
}
|
||||||
|
if (engine.getVariable(constant.hardMode)) {
|
||||||
|
return 90
|
||||||
|
}
|
||||||
|
switch (true) {
|
||||||
|
case successCount < 10:
|
||||||
|
return 30
|
||||||
|
case successCount < 20:
|
||||||
|
return 60
|
||||||
|
default:
|
||||||
|
return 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getSwingBlockVelocity = (engine, time) => {
|
||||||
|
const successCount = engine.getVariable(constant.successCount)
|
||||||
|
const gameScore = engine.getVariable(constant.gameScore)
|
||||||
|
const { hookSpeed } = engine.getVariable(constant.gameUserOption)
|
||||||
|
if (hookSpeed) {
|
||||||
|
return hookSpeed(successCount, gameScore)
|
||||||
|
}
|
||||||
|
let hard
|
||||||
|
switch (true) {
|
||||||
|
case successCount < 1:
|
||||||
|
hard = 0
|
||||||
|
break
|
||||||
|
case successCount < 10:
|
||||||
|
hard = 1
|
||||||
|
break
|
||||||
|
case successCount < 20:
|
||||||
|
hard = 0.8
|
||||||
|
break
|
||||||
|
case successCount < 30:
|
||||||
|
hard = 0.7
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
hard = 0.74
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (engine.getVariable(constant.hardMode)) {
|
||||||
|
hard = 1.1
|
||||||
|
}
|
||||||
|
return Math.sin(time / (200 / hard))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getLandBlockVelocity = (engine, time) => {
|
||||||
|
const successCount = engine.getVariable(constant.successCount)
|
||||||
|
const gameScore = engine.getVariable(constant.gameScore)
|
||||||
|
const { landBlockSpeed } = engine.getVariable(constant.gameUserOption)
|
||||||
|
if (landBlockSpeed) {
|
||||||
|
return landBlockSpeed(successCount, gameScore)
|
||||||
|
}
|
||||||
|
const { width } = engine
|
||||||
|
let hard
|
||||||
|
switch (true) {
|
||||||
|
case successCount < 5:
|
||||||
|
hard = 0
|
||||||
|
break
|
||||||
|
case successCount < 13:
|
||||||
|
hard = 0.001
|
||||||
|
break
|
||||||
|
case successCount < 23:
|
||||||
|
hard = 0.002
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
hard = 0.003
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return Math.cos(time / 200) * hard * width
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getHookStatus = (engine) => {
|
||||||
|
if (engine.checkTimeMovement(constant.hookDownMovement)) {
|
||||||
|
return constant.hookDown
|
||||||
|
}
|
||||||
|
if (engine.checkTimeMovement(constant.hookUpMovement)) {
|
||||||
|
return constant.hookUp
|
||||||
|
}
|
||||||
|
return constant.hookNormal
|
||||||
|
}
|
||||||
|
|
||||||
|
export const touchEventHandler = (engine) => {
|
||||||
|
if (!engine.getVariable(constant.gameStartNow)) return
|
||||||
|
if (engine.debug && engine.paused) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (getHookStatus(engine) !== constant.hookNormal) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
engine.removeInstance('tutorial')
|
||||||
|
engine.removeInstance('tutorial-arrow')
|
||||||
|
const b = engine.getInstance(`block_${engine.getVariable(constant.blockCount)}`)
|
||||||
|
if (b && b.status === constant.swing) {
|
||||||
|
engine.setTimeMovement(constant.hookUpMovement, 500)
|
||||||
|
b.status = constant.beforeDrop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addSuccessCount = (engine) => {
|
||||||
|
const { setGameSuccess } = engine.getVariable(constant.gameUserOption)
|
||||||
|
const lastSuccessCount = engine.getVariable(constant.successCount)
|
||||||
|
const success = lastSuccessCount + 1
|
||||||
|
engine.setVariable(constant.successCount, success)
|
||||||
|
if (engine.getVariable(constant.hardMode)) {
|
||||||
|
engine.setVariable(constant.ropeHeight, engine.height * engine.utils.random(0.35, 0.55))
|
||||||
|
}
|
||||||
|
if (setGameSuccess) setGameSuccess(success)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addFailedCount = (engine) => {
|
||||||
|
const { setGameFailed } = engine.getVariable(constant.gameUserOption)
|
||||||
|
const lastFailedCount = engine.getVariable(constant.failedCount)
|
||||||
|
const failed = lastFailedCount + 1
|
||||||
|
engine.setVariable(constant.failedCount, failed)
|
||||||
|
engine.setVariable(constant.perfectCount, 0)
|
||||||
|
if (setGameFailed) setGameFailed(failed)
|
||||||
|
if (failed >= 3) {
|
||||||
|
engine.pauseAudio('bgm')
|
||||||
|
engine.playAudio('game-over')
|
||||||
|
engine.setVariable(constant.gameStartNow, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addScore = (engine, isPerfect) => {
|
||||||
|
const { setGameScore, successScore, perfectScore } = engine.getVariable(constant.gameUserOption)
|
||||||
|
const lastPerfectCount = engine.getVariable(constant.perfectCount, 0)
|
||||||
|
const lastGameScore = engine.getVariable(constant.gameScore)
|
||||||
|
const perfect = isPerfect ? lastPerfectCount + 1 : 0
|
||||||
|
const score = lastGameScore + (successScore || 25) + ((perfectScore || 25) * perfect)
|
||||||
|
engine.setVariable(constant.gameScore, score)
|
||||||
|
engine.setVariable(constant.perfectCount, perfect)
|
||||||
|
if (setGameScore) setGameScore(score)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const drawYellowString = (engine, option) => {
|
||||||
|
const {
|
||||||
|
string, size, x, y, textAlign, fontName = 'wenxue', fontWeight = 'normal'
|
||||||
|
} = option
|
||||||
|
const { ctx } = engine
|
||||||
|
const fontSize = size
|
||||||
|
const lineSize = fontSize * 0.1
|
||||||
|
ctx.save()
|
||||||
|
ctx.beginPath()
|
||||||
|
const gradient = ctx.createLinearGradient(0, 0, 0, y)
|
||||||
|
gradient.addColorStop(0, '#FAD961')
|
||||||
|
gradient.addColorStop(1, '#F76B1C')
|
||||||
|
ctx.fillStyle = gradient
|
||||||
|
ctx.lineWidth = lineSize
|
||||||
|
ctx.strokeStyle = '#FFF'
|
||||||
|
ctx.textAlign = textAlign || 'center'
|
||||||
|
ctx.font = `${fontWeight} ${fontSize}px ${fontName}`
|
||||||
|
ctx.strokeText(string, x, y)
|
||||||
|
ctx.fillText(string, x, y)
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
@@ -73,6 +73,36 @@ const GamePage: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{ direction: 'ltr' }}
|
||||||
|
className='bg-white shadow-sm rounded-[10px] p-2 flex justify-between items-center cursor-pointer'
|
||||||
|
onClick={() => {
|
||||||
|
const currentPath = window.location.pathname
|
||||||
|
const basePath = currentPath.split('/game')[0]
|
||||||
|
router.push(`${basePath}/game/tower-builder`)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='flex gap-3 items-center'>
|
||||||
|
<Image
|
||||||
|
src='/assets/images/tower_building_logo.webp'
|
||||||
|
alt='Tower Builder'
|
||||||
|
width={80}
|
||||||
|
height={80}
|
||||||
|
className='size-[80px] object-cover'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='text-sm font-bold max-w-[92px]'>
|
||||||
|
Tower Builder
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='h-8 bg-[#F2F2F2] rounded-lg px-3 flex gap-1 items-center justify-center'>
|
||||||
|
<div className='text-xs'>اجرا کنید</div>
|
||||||
|
<Play size='20' color='currentColor' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
'use client'
|
||||||
|
import { type FC, type ReactNode } from 'react'
|
||||||
|
|
||||||
|
interface TowerBuilderLayoutProps {
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const TowerBuilderLayout: FC<TowerBuilderLayoutProps> = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<div className='h-screen w-full p-0 m-0'>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TowerBuilderLayout
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
'use client'
|
||||||
|
import { type FC } from 'react'
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { CloseCircle } from 'iconsax-react'
|
||||||
|
|
||||||
|
const TowerBuilderPage: FC = () => {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='fixed inset-0 w-full h-full p-0 m-0 flex items-center justify-center bg-white'>
|
||||||
|
<button
|
||||||
|
onClick={() => router.back()}
|
||||||
|
className='absolute top-4 left-4 z-50 bg-white rounded-full shadow-lg p-1 hover:bg-gray-100 transition-colors'
|
||||||
|
aria-label='بستن'
|
||||||
|
>
|
||||||
|
<CloseCircle size='32' color='#000' variant='Bold' />
|
||||||
|
</button>
|
||||||
|
<iframe
|
||||||
|
src='/tower_game-master/index.html'
|
||||||
|
className='w-full h-full border-0'
|
||||||
|
title='Tower Builder Game'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TowerBuilderPage
|
||||||