add drumming
|
|
@ -0,0 +1,21 @@
|
|||
# pointgaming
|
||||
### 1、项目说明
|
||||
总体思路:数独程序项目开发的总体思路如图1.1示意,拟实现头部区域,主体区域,右边的内容展示和操作区域等。
|
||||
图1.1 项目开发的总体思路图
|
||||
<img src="figs/overall.png" width=80% /></center>
|
||||
|
||||
|
||||
### 2、开发环境说明
|
||||
(1)系统版本:OpenHarmony 4.0 Release
|
||||
(2)SDK版本:API 10
|
||||
(3)Model类型:Stage
|
||||
(4)DevEco Studio版本:Deveco Studio 4.0 release
|
||||
(5)硬件环境:Unionpi Whale、RK3566开发板
|
||||
(6)预览器参数:(1)DeviceType:default;(2)Resolution:560-1280;(3)DPI:240;其余参数自定。
|
||||
### 3、项目主要函数调用关系图与项目输出界面演进图
|
||||
图3.1 项目主要函数调用关系图
|
||||
<img src="figs/relation.png" width=80% /></center>
|
||||
|
||||
|
||||
图3.2 项目输出界面演进图
|
||||
<img src="figs/evolution.png" width=80% /></center>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
/node_modules
|
||||
/oh_modules
|
||||
/local.properties
|
||||
/.idea
|
||||
**/build
|
||||
/.hvigor
|
||||
.cxx
|
||||
/.clangd
|
||||
/.clang-format
|
||||
/.clang-tidy
|
||||
**/.test
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"app": {
|
||||
"bundleName": "com.test.myapplication",
|
||||
"vendor": "example",
|
||||
"versionCode": 1000000,
|
||||
"versionName": "1.0.0",
|
||||
"icon": "$media:app_icon",
|
||||
"label": "$string:app_name"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "app_name",
|
||||
"value": "drawpart3"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"app": {
|
||||
"signingConfigs": [
|
||||
{
|
||||
"name": "default",
|
||||
"material": {
|
||||
"certpath": "C:\\Users\\cyf20\\.ohos\\config\\openharmony\\auto_ohos_default_drawpart3_com.test.myapplication.cer",
|
||||
"storePassword": "0000001B112EEEE42E9C65C70B8BE139E9746FD3A593BEC334955CFDC40C11FEB89314E23E845E58F31D6E",
|
||||
"keyAlias": "debugKey",
|
||||
"keyPassword": "0000001BACB92E1829CB96E1D19A1D0FD4AF75EC50D07DAACF62C3FB0B8D5EF8E82E944DC0FD8F397E8901",
|
||||
"profile": "C:\\Users\\cyf20\\.ohos\\config\\openharmony\\auto_ohos_default_drawpart3_com.test.myapplication.p7b",
|
||||
"signAlg": "SHA256withECDSA",
|
||||
"storeFile": "C:\\Users\\cyf20\\.ohos\\config\\openharmony\\auto_ohos_default_drawpart3_com.test.myapplication.p12"
|
||||
}
|
||||
}
|
||||
],
|
||||
"products": [
|
||||
{
|
||||
"name": "default",
|
||||
"signingConfig": "default",
|
||||
"compileSdkVersion": 10,
|
||||
"compatibleSdkVersion": 10,
|
||||
"runtimeOS": "OpenHarmony",
|
||||
}
|
||||
],
|
||||
"buildModeSet": [
|
||||
{
|
||||
"name": "debug",
|
||||
},
|
||||
{
|
||||
"name": "release"
|
||||
}
|
||||
]
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"name": "entry",
|
||||
"srcPath": "./entry",
|
||||
"targets": [
|
||||
{
|
||||
"name": "default",
|
||||
"applyToProducts": [
|
||||
"default"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
/node_modules
|
||||
/oh_modules
|
||||
/.preview
|
||||
/build
|
||||
/.cxx
|
||||
/.test
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"apiType": "stageMode",
|
||||
"buildOption": {
|
||||
"arkOptions": {
|
||||
// "apPath": "./modules.ap" /* Profile used for profile-guided optimization (PGO), a compiler optimization technique to improve app runtime performance. */
|
||||
}
|
||||
},
|
||||
"buildOptionSet": [
|
||||
{
|
||||
"name": "release",
|
||||
"arkOptions": {
|
||||
"obfuscation": {
|
||||
"ruleOptions": {
|
||||
"enable": true,
|
||||
"files": [
|
||||
"./obfuscation-rules.txt"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"name": "default"
|
||||
},
|
||||
{
|
||||
"name": "ohosTest",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
|
||||
|
||||
export default {
|
||||
system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
|
||||
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# Define project specific obfuscation rules here.
|
||||
# You can include the obfuscation configuration files in the current module's build-profile.json5.
|
||||
#
|
||||
# For more details, see
|
||||
# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md
|
||||
|
||||
# Obfuscation options:
|
||||
# -disable-obfuscation: disable all obfuscations
|
||||
# -enable-property-obfuscation: obfuscate the property names
|
||||
# -enable-toplevel-obfuscation: obfuscate the names in the global scope
|
||||
# -compact: remove unnecessary blank spaces and all line feeds
|
||||
# -remove-log: remove all console.* statements
|
||||
# -print-namecache: print the name cache that contains the mapping from the old names to new names
|
||||
# -apply-namecache: reuse the given cache file
|
||||
|
||||
# Keep options:
|
||||
# -keep-property-name: specifies property names that you want to keep
|
||||
# -keep-global-name: specifies names that you want to keep in the global scope
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"license": "",
|
||||
"devDependencies": {},
|
||||
"author": "",
|
||||
"name": "entry",
|
||||
"description": "Please describe the basic information.",
|
||||
"main": "",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* 字体大小(大)
|
||||
*/
|
||||
export const BIG_SIZE_FONT = 20
|
||||
/**
|
||||
* 字体大小(小)
|
||||
*/
|
||||
export const SMALL_SIZE_FONT = 15
|
||||
|
||||
//以da yu200屏幕为标准 imageShow组件的左上角和右下角的坐标
|
||||
//经过测试在whale RK3588上影响不大
|
||||
export const TOP_LEFT_X = 0
|
||||
export const TOP_LEFT_Y = 38
|
||||
export const BOTTOM_RIGHT_X = 681
|
||||
export const BOTTOM_RIGHT_Y = 373
|
||||
|
||||
//定义绘制节点的大小(vp)
|
||||
export const DRAW_NODE_SIZE = 40
|
||||
|
||||
let setting:RenderingContextSettings = new RenderingContextSettings(true)
|
||||
//绘制未选择/一般情况
|
||||
export const NORMAL_CONTEXT: CanvasRenderingContext2D = new CanvasRenderingContext2D(setting)
|
||||
//绘制选中
|
||||
export const SELECTED_CONTEXT: CanvasRenderingContext2D = new CanvasRenderingContext2D(setting)
|
||||
|
||||
export const X_CENTER = (BOTTOM_RIGHT_X - TOP_LEFT_X) / 2 - TOP_LEFT_X
|
||||
export const Y_CENTER = (BOTTOM_RIGHT_Y - TOP_LEFT_Y) / 2 - TOP_LEFT_Y
|
||||
|
||||
//TODO KEY 用户需要自行更改
|
||||
export const KEY = "14a9551d9cdbf6e2ce12768de807a5fe"
|
||||
|
||||
/**
|
||||
* 程序会默认使用这些预定的城市作为参数调用API以获取数据 然后将其写入文件
|
||||
*/
|
||||
//模拟的城市
|
||||
export const CITY = ["广州市","深圳市","珠海市","汕头市","佛山市","韶关市","湛江市","肇庆市","江门市","茂名市","惠州市","梅州市","汕尾市"]
|
||||
//,"汕头市","佛山市","韶关市","湛江市","肇庆市","江门市","茂名市","惠州市","梅州市","汕尾市"
|
||||
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import { cp } from '../../utils/algorithm/customPath'
|
||||
import { showToast } from '../../utils/functions/showToast'
|
||||
import Node from '../dataStruct/class/Node'
|
||||
|
||||
@CustomDialog
|
||||
export struct setNodeCount{
|
||||
controller:CustomDialogController
|
||||
@State count:number = 0
|
||||
@Link Nodes:Array<Node>
|
||||
@Consume refresh:boolean
|
||||
@Consume isStartToPath:boolean
|
||||
@Consume nodePathId:Array<number>
|
||||
build(){
|
||||
Column({space:5}){
|
||||
Row(){
|
||||
Text('设置个数:')
|
||||
TextInput()
|
||||
.layoutWeight(1)
|
||||
.onChange((value)=>{
|
||||
this.count = Number(value)
|
||||
})
|
||||
}.width('100%')
|
||||
Row(){
|
||||
Button('确定')
|
||||
.type(ButtonType.Normal)
|
||||
.onClick(()=>{
|
||||
this.isStartToPath = false
|
||||
this.nodePathId = []
|
||||
if(this.count <= 1 || this.count >= 14){
|
||||
showToast("节点数只能位于2至13之间")
|
||||
return
|
||||
}
|
||||
this.Nodes = cp.createNodeData(this.count)
|
||||
this.refresh = !this.refresh
|
||||
this.controller.close()
|
||||
})
|
||||
Button('取消')
|
||||
.type(ButtonType.Normal)
|
||||
.onClick(()=>{
|
||||
this.controller.close()
|
||||
})
|
||||
}.width('100%').justifyContent(FlexAlign.SpaceAround)
|
||||
}.width('100%').padding(5)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import Node from '../dataStruct/class/Node'
|
||||
|
||||
@CustomDialog
|
||||
export struct setNodeNameDialog{
|
||||
controller:CustomDialogController
|
||||
@ObjectLink node:Node
|
||||
@State inputName:string = ""
|
||||
build(){
|
||||
Column({space:5}){
|
||||
Row(){
|
||||
Text('设置名称:')
|
||||
TextInput()
|
||||
.layoutWeight(1)
|
||||
.onChange((value)=>{
|
||||
this.inputName = value
|
||||
})
|
||||
}.width('100%')
|
||||
Row(){
|
||||
Button('确定')
|
||||
.type(ButtonType.Normal)
|
||||
.onClick(()=>{
|
||||
this.node.label = this.inputName
|
||||
this.controller.close()
|
||||
})
|
||||
Button('取消')
|
||||
.type(ButtonType.Normal)
|
||||
.onClick(()=>{
|
||||
this.controller.close()
|
||||
})
|
||||
}.width('100%').justifyContent(FlexAlign.SpaceAround)
|
||||
}.width('100%').padding(5)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { RouteData } from './RouteData'
|
||||
|
||||
export class Data{
|
||||
status:string
|
||||
info:string
|
||||
count:string
|
||||
route:RouteData
|
||||
|
||||
constructor(status: string, info: string, count: string, route: RouteData) {
|
||||
this.status = status
|
||||
this.info = info
|
||||
this.count = count
|
||||
this.route = route
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
export default class Edge {
|
||||
id: number; // “连接对象编号”:连接线唯一标识,通常是自然数编号
|
||||
node1: number; // “节点对象1编号”
|
||||
node2: number; // “节点对象2编号”:连接线两端节点的编号
|
||||
visible: boolean; // “连接是否显示”:表示该连接是否存在
|
||||
marked: boolean; // ”连接是否标记“:该连接是否被标记
|
||||
label: string; // ”连接标签“:该连接的名字
|
||||
distance: number ; // ”连接的距离/成本“
|
||||
time: number; // ”连接的时间“
|
||||
|
||||
constructor(id: number, node1: number, node2: number, visible: boolean, marked: boolean, label: string, distance: number, time: number) {
|
||||
this.id = id;
|
||||
this.node1 = node1;
|
||||
this.node2 = node2;
|
||||
this.visible = visible;
|
||||
this.marked = marked;
|
||||
this.label = label;
|
||||
this.distance = distance;
|
||||
this.time = time;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
@Observed
|
||||
export default class Node {
|
||||
id: number; // “节点编号”:唯一标识一个节点,通常是自然数编号
|
||||
label: string; // “节点标签”:节点名称(如城市名)
|
||||
marked: boolean; // “节点是否标记”:节点是否被标记
|
||||
coordinates: [number, number]; // “节点坐标”:节点坐标组<x, y>
|
||||
mustVisit: boolean; // “是否为必经节点”:表示在遍历时是否必经该节点,默认为必经节点
|
||||
|
||||
constructor(id: number, label: string, marked: boolean, coordinates: [number, number], mustVisit: boolean = true) {
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
this.marked = marked;
|
||||
this.coordinates = coordinates;
|
||||
this.mustVisit = mustVisit;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { StepData } from './StepData'
|
||||
|
||||
export class PathData{
|
||||
path:string
|
||||
distance:string
|
||||
duration:string
|
||||
strategy:string
|
||||
tolls:string
|
||||
restriction:string
|
||||
traffic_lights:string
|
||||
toll_distance:string
|
||||
steps:Array<StepData>
|
||||
|
||||
constructor(path: string, distance: string, duration: string, strategy: string, tolls: string, restriction: string,
|
||||
traffic_lights: string, toll_distance: string, steps: Array<StepData>) {
|
||||
this.path = path
|
||||
this.distance = distance
|
||||
this.duration = duration
|
||||
this.strategy = strategy
|
||||
this.tolls = tolls
|
||||
this.restriction = restriction
|
||||
this.traffic_lights = traffic_lights
|
||||
this.toll_distance = toll_distance
|
||||
this.steps = steps
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { PathData } from './PathData'
|
||||
|
||||
export class RouteData{
|
||||
origin:string
|
||||
destination:string
|
||||
taxi_cost:string
|
||||
paths:Array<PathData>
|
||||
|
||||
constructor(origin: string, destination: string, taxi_cost: string, paths: Array<PathData>) {
|
||||
this.origin = origin
|
||||
this.destination = destination
|
||||
this.taxi_cost = taxi_cost
|
||||
this.paths = paths
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
export class StepData{
|
||||
step:string
|
||||
|
||||
constructor(step: string) {
|
||||
this.step = step
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
interface urbanTransitionCost {
|
||||
distance: number,
|
||||
time: number
|
||||
}
|
||||
|
||||
export type CityInfo = Record<string, Record<string, urbanTransitionCost>>;
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export interface nodeInfo {
|
||||
to: number,
|
||||
weight: number
|
||||
}
|
||||
|
||||
export type Graph = Record<number, nodeInfo[]>;
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export interface solutionData{
|
||||
count?:number
|
||||
time:string
|
||||
path:string
|
||||
otherPath:Array<string>
|
||||
}
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
import { SMALL_SIZE_FONT } from '../common/CommonConstant'
|
||||
import { setNodeCount } from '../common/customDialog/SetNodeCount'
|
||||
import Node from '../common/dataStruct/class/Node'
|
||||
import { solutionData } from '../common/dataStruct/interface/SolutionData'
|
||||
import { cp } from '../utils/algorithm/customPath'
|
||||
import { tsp } from '../utils/algorithm/TSP'
|
||||
import { showToast } from '../utils/functions/showToast'
|
||||
|
||||
@Component
|
||||
export struct header {
|
||||
@Link resTime:number
|
||||
@Link resDistance:number
|
||||
|
||||
@Prop headerContent:Array<string>
|
||||
@Link curHeaderContentIndex:number
|
||||
|
||||
@Consume Nodes:Array<Node>
|
||||
@Consume refresh: boolean;
|
||||
@Consume curNodesIndex:number
|
||||
@Consume isStartToPath:boolean
|
||||
@Consume solutionData:solutionData
|
||||
|
||||
controller:CustomDialogController = new CustomDialogController({
|
||||
builder:setNodeCount({Nodes:$Nodes})
|
||||
})
|
||||
|
||||
@Builder operatorMenu(){
|
||||
Menu(){
|
||||
MenuItem({content:"随机生成一个TSP问题"})
|
||||
.onClick(()=>{
|
||||
tsp.createRandomTspProblem();
|
||||
this.Nodes = cp.nodes;
|
||||
this.curHeaderContentIndex = 0
|
||||
this.isStartToPath = false
|
||||
})
|
||||
MenuItem({content:"重新更改节点位置"})
|
||||
.onClick(()=>{
|
||||
cp.regenerate();
|
||||
this.refresh = !this.refresh;
|
||||
this.curHeaderContentIndex = 0
|
||||
this.isStartToPath = false
|
||||
})
|
||||
MenuItem({content:"自动求解最优路径-遍历"})
|
||||
.onClick(()=>{
|
||||
|
||||
if (this.Nodes.length <= 10) {
|
||||
let startNodeId = this.Nodes[this.curNodesIndex].id;
|
||||
// let result: [number[][], number, number, number] = cp.solveTspInTraversal(startNodeId);
|
||||
let result: [number[][], number, number, number] = cp.solveTsp(startNodeId, cp.traversalSearchToPath);
|
||||
let NodeIdsPaths: number[][] = result[0].slice();
|
||||
if (!NodeIdsPaths.length) return;
|
||||
let displayPath: number[] = NodeIdsPaths[0]; // 要展示的最短路径
|
||||
let enumPaths: number[][] = NodeIdsPaths.slice(1); // 其余的最短路径
|
||||
cp.setDisplayPath(displayPath);
|
||||
this.refresh = !this.refresh;
|
||||
let distanceCost: number = result[1]; // 距离成本
|
||||
let runtime: number = result[2];
|
||||
let runtimeTag: string = ""; // 运行时间的字符串表示,已有单位
|
||||
if (runtime == 0) {
|
||||
runtimeTag = "<0.001s";
|
||||
} else {
|
||||
runtimeTag = runtime.toFixed(3) + "s";
|
||||
}
|
||||
let traversalNum: number = result[3]; // 遍历次数
|
||||
cp.createGraph(this.Nodes, false);
|
||||
let timeCost: number = cp.calculatePathCost(cp.graph, displayPath); // 时间成本
|
||||
this.curHeaderContentIndex = 3
|
||||
|
||||
this.resTime = Number((timeCost / 60).toFixed(0))
|
||||
this.resDistance = Number((distanceCost * 1e-3).toFixed(3))
|
||||
this.solutionData.count = traversalNum
|
||||
this.solutionData.time = runtimeTag
|
||||
this.solutionData.path = ""
|
||||
this.solutionData.otherPath = [""]
|
||||
for (let index = 0; index < displayPath.length; index++) {
|
||||
this.solutionData.path += (this.Nodes[displayPath[index]].label)
|
||||
if (index != displayPath.length - 1) {
|
||||
this.solutionData.path += "->";
|
||||
}
|
||||
}
|
||||
for (let index = 0; index < enumPaths.length; index++) {
|
||||
for (let i = 0; i < enumPaths[index].length; i++) {
|
||||
this.solutionData.otherPath[index] += (this.Nodes[enumPaths[index][i]].label)
|
||||
if (i != enumPaths[index].length - 1) {
|
||||
this.solutionData.otherPath[index] += "->";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
showToast("超过11个节点的遍历法求tsp问题会导致程序崩溃,不建议使用");
|
||||
}
|
||||
})
|
||||
MenuItem({content:"自动求解优化路径-贪心"})
|
||||
.onClick(()=>{
|
||||
|
||||
let startNodeId = this.Nodes[this.curNodesIndex].id;
|
||||
let result: [number[][], number, number, number] = cp.solveTsp(startNodeId, cp.greedySearchToPath);
|
||||
if (!result[0].length) return;
|
||||
let displayPath: number[] = result[0][0].slice(); // 要展示的最短路径
|
||||
cp.setDisplayPath(displayPath);
|
||||
this.refresh = !this.refresh;
|
||||
let distanceCost: number = result[1]; // 距离成本
|
||||
let runtime: number = result[2];
|
||||
let runtimeTag: string = ""; // 运行时间的字符串表示,已有单位
|
||||
if (runtime == 0) {
|
||||
runtimeTag = "<0.001s";
|
||||
} else {
|
||||
runtimeTag = runtime.toFixed(3) + "s";
|
||||
}
|
||||
cp.createGraph(this.Nodes, false);
|
||||
let timeCost: number = cp.calculatePathCost(cp.graph, displayPath); // 时间成本
|
||||
this.curHeaderContentIndex = 4
|
||||
|
||||
this.resTime = Number((timeCost / 60).toFixed(0))
|
||||
this.resDistance = Number((distanceCost * 1e-3).toFixed(3))
|
||||
this.solutionData.time = runtimeTag
|
||||
this.solutionData.path = ""
|
||||
for (let index = 0; index < displayPath.length; index++) {
|
||||
this.solutionData.path += (this.Nodes[displayPath[index]].label)
|
||||
if (index != displayPath.length - 1) {
|
||||
this.solutionData.path += "->";
|
||||
}
|
||||
}
|
||||
})
|
||||
MenuItem({content:"检查是否存在路径"})
|
||||
.onClick(()=>{
|
||||
|
||||
let id1:number|undefined = AppStorage.get("startNodeId")
|
||||
let id2:number|undefined = AppStorage.get("endNodeId")
|
||||
if(id1 == undefined || id2 == undefined){
|
||||
showToast("未选择两个节点,请前往路径操作中去选择两个节点")
|
||||
return
|
||||
}
|
||||
let node1Id: number = id1;
|
||||
let node2Id: number = id2;
|
||||
if (cp.graphCheckConnectivity(this.Nodes[node1Id], this.Nodes[node2Id])[0]) {
|
||||
showToast(`节点${node1Id} (${this.Nodes[node1Id].label})和节点${node2Id} (${this.Nodes[node2Id].label})之间存在路径`);
|
||||
} else {
|
||||
showToast(`节点${node1Id} (${this.Nodes[node1Id].label})和节点${node2Id} (${this.Nodes[node2Id].label})之间不存在路径`);
|
||||
}
|
||||
})
|
||||
}.radius(0)
|
||||
}
|
||||
|
||||
build() {
|
||||
Row({space:5}){
|
||||
|
||||
ForEach(this.headerContent,(item:string,index:number)=>{
|
||||
if(index <= 2){
|
||||
Text(item)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
.fontColor(this.curHeaderContentIndex == index?Color.Red:Color.Black)
|
||||
.onClick(()=>{
|
||||
this.curHeaderContentIndex = index
|
||||
if(this.curHeaderContentIndex != 2){
|
||||
this.isStartToPath = false
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
Blank()
|
||||
|
||||
Row({space:5}){
|
||||
Text("节点数目 " + (this.Nodes.length))
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
Button('修改')
|
||||
.width(60).height(25)
|
||||
.type(ButtonType.Normal)
|
||||
.onClick(()=>{
|
||||
this.controller.open()
|
||||
})
|
||||
}
|
||||
|
||||
Text("时间 " + this.resTime + " 分钟")
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
|
||||
Text("距离(成本) " + this.resDistance+ " 千米")
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
|
||||
Blank()
|
||||
|
||||
Text("TSP问题生成与自动求解")
|
||||
.onClick(()=>{
|
||||
this.isStartToPath = false
|
||||
})
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
.bindMenu(this.operatorMenu)
|
||||
|
||||
}.width('100%').height('100%').padding(5).backgroundColor('#F8F8F8')
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
import { DRAW_NODE_SIZE, NORMAL_CONTEXT, SELECTED_CONTEXT } from '../common/CommonConstant'
|
||||
import Edge from '../common/dataStruct/class/Edge'
|
||||
import Node from '../common/dataStruct/class/Node'
|
||||
import { drawEdgeList } from '../utils/functions/drawEdge'
|
||||
import { drawNode, drawNodeList } from '../utils/functions/drawNode'
|
||||
import { showToast } from '../utils/functions/showToast'
|
||||
import { Logger } from '../utils/Logger'
|
||||
|
||||
@Component
|
||||
export struct imageShow {
|
||||
@Consume Nodes:Array<Node>
|
||||
@Consume curNodesIndex:number
|
||||
@Consume Edges:Array<Edge>
|
||||
@Consume refresh:boolean
|
||||
@Consume isStartToPath:boolean
|
||||
@Consume nodePathId:Array<number>
|
||||
@Link curEdgesIndex:number
|
||||
@Prop loop:string = "100%"
|
||||
build() {
|
||||
Stack() {
|
||||
Canvas(SELECTED_CONTEXT).width('100%').height(this.loop)
|
||||
.onReady(()=>{
|
||||
SELECTED_CONTEXT.beginPath()
|
||||
SELECTED_CONTEXT.strokeStyle = 'yellow'
|
||||
SELECTED_CONTEXT.font = '20vp'
|
||||
drawNode(this.Nodes[this.curNodesIndex],SELECTED_CONTEXT)
|
||||
})
|
||||
Canvas(NORMAL_CONTEXT).width('100%').height(this.loop)
|
||||
.onReady(()=>{
|
||||
NORMAL_CONTEXT.beginPath()
|
||||
NORMAL_CONTEXT.strokeStyle = 'green'
|
||||
NORMAL_CONTEXT.fillStyle = 'green'
|
||||
//绘制线
|
||||
drawEdgeList(this.Edges,false)
|
||||
// drawEdgeList(this.Edges,true)
|
||||
NORMAL_CONTEXT.font = '15vp'
|
||||
//绘制点
|
||||
drawNodeList(this.Nodes.slice(0,this.curNodesIndex),NORMAL_CONTEXT)
|
||||
if(this.curNodesIndex == this.Nodes.length - 1){
|
||||
return
|
||||
}
|
||||
drawNodeList(this.Nodes.slice(this.curNodesIndex + 1,this.Nodes.length),NORMAL_CONTEXT)
|
||||
})
|
||||
.onClick((event:ClickEvent)=>{
|
||||
for (let index = 0; index < this.Nodes.length; index++) {
|
||||
//是否点到节点上
|
||||
if(event.x >= this.Nodes[index].coordinates[0] && event.x <= this.Nodes[index].coordinates[0] + DRAW_NODE_SIZE &&
|
||||
event.y >= this.Nodes[index].coordinates[1] && event.y <= this.Nodes[index].coordinates[1] + DRAW_NODE_SIZE){
|
||||
//进入到标记模式
|
||||
if (this.isStartToPath) {
|
||||
let i = index
|
||||
if(this.Nodes[i].marked){
|
||||
showToast("不允许选择相同的节点")
|
||||
}else{
|
||||
this.curNodesIndex = index
|
||||
this.Nodes[this.curNodesIndex].marked = true
|
||||
this.nodePathId.push(index)
|
||||
}
|
||||
}else{
|
||||
this.curNodesIndex = index
|
||||
this.refresh = !this.refresh
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for (let index = 0; index < this.Edges.length; index++) {
|
||||
if(!this.Edges[index].visible){
|
||||
continue
|
||||
}
|
||||
let x1 = this.Nodes[this.Edges[index].node1].coordinates[0] + DRAW_NODE_SIZE / 2
|
||||
let y1 = this.Nodes[this.Edges[index].node1].coordinates[1] + DRAW_NODE_SIZE / 2
|
||||
let x2 = this.Nodes[this.Edges[index].node2].coordinates[0] + DRAW_NODE_SIZE / 2
|
||||
let y2 = this.Nodes[this.Edges[index].node2].coordinates[1] + DRAW_NODE_SIZE / 2
|
||||
|
||||
let A = y2 - y1
|
||||
let B = x1 - x2
|
||||
let C = x2*y1 - x1*y2
|
||||
let t1 = A * event.x + B * event.y + C
|
||||
if(t1 < 0){
|
||||
t1 = -t1
|
||||
}
|
||||
let t2 = Math.sqrt((A * A + B * B))
|
||||
let d = t1 / t2
|
||||
Logger.i("index" + index + "label" + this.Nodes[this.Edges[this.curEdgesIndex].node1].label + this.Nodes[this.Edges[this.curEdgesIndex].node2].label )
|
||||
if(d < 3){
|
||||
this.curEdgesIndex = index
|
||||
Logger.i("cur - index" + index)
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
.width('100%').height('100%')
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,460 @@
|
|||
import { BIG_SIZE_FONT , SMALL_SIZE_FONT } from '../common/CommonConstant'
|
||||
import { setNodeNameDialog } from '../common/customDialog/SetNodeNameDialog'
|
||||
import Edge from '../common/dataStruct/class/Edge'
|
||||
import Node from '../common/dataStruct/class/Node'
|
||||
import { solutionData } from '../common/dataStruct/interface/SolutionData'
|
||||
import { cp } from '../utils/algorithm/customPath'
|
||||
import { showToast } from '../utils/functions/showToast'
|
||||
import { Logger } from '../utils/Logger'
|
||||
|
||||
@Component
|
||||
export struct operateBar {
|
||||
@Prop headerContent:Array<string>
|
||||
@Link curHeaderContentIndex:number
|
||||
|
||||
@Consume Nodes:Array<Node>
|
||||
@Consume curNodesIndex:number
|
||||
|
||||
build() {
|
||||
Column(){
|
||||
Text(this.headerContent[this.curHeaderContentIndex])
|
||||
.fontSize(BIG_SIZE_FONT)
|
||||
if(this.curHeaderContentIndex == 0){
|
||||
nodeOP({selectNode:this.Nodes[this.curNodesIndex]})
|
||||
.width('100%').layoutWeight(1)
|
||||
}
|
||||
if(this.curHeaderContentIndex == 1){
|
||||
linkOP()
|
||||
.width('100%').layoutWeight(1)
|
||||
}
|
||||
if(this.curHeaderContentIndex == 2){
|
||||
pathOP()
|
||||
.width('100%').layoutWeight(1)
|
||||
}
|
||||
if(this.curHeaderContentIndex == 3){
|
||||
solution_traverse()
|
||||
}
|
||||
if(this.curHeaderContentIndex == 4){
|
||||
solution_greedy()
|
||||
}
|
||||
}.width('100%').height('100%').backgroundColor(Color.Pink)
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
struct nodeOP {
|
||||
@Consume Nodes:Array<Node>
|
||||
@Consume curNodesIndex:number
|
||||
@Consume refresh:boolean
|
||||
@ObjectLink selectNode:Node
|
||||
controller:CustomDialogController = new CustomDialogController({
|
||||
builder:setNodeNameDialog({node:this.Nodes[this.curNodesIndex]})
|
||||
})
|
||||
build() {
|
||||
Column({space:5}){
|
||||
Text('当前共有' + this.Nodes.length + "个节点")
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
Row({space:5}){
|
||||
Text('当前节点ID:')
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
Text(this.selectNode.id.toString())
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
}.width('100%')
|
||||
Row({space:5}){
|
||||
Text('当前节点标签:')
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
Text(this.selectNode.label)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
}.width('100%')
|
||||
Button('设置标签')
|
||||
.type(ButtonType.Normal)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
.onClick(()=>{
|
||||
this.controller.open()
|
||||
})
|
||||
Button('删除当前节点')
|
||||
.type(ButtonType.Normal)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
.onClick(()=>{
|
||||
if (this.Nodes.length <= 2){
|
||||
showToast("节点数不可少于2")
|
||||
return
|
||||
}
|
||||
this.Nodes = cp.deleteNodeData(this.curNodesIndex)
|
||||
})
|
||||
Button('设置为非经过点')
|
||||
.type(ButtonType.Normal)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
.onClick(()=>{
|
||||
let c = 0
|
||||
for (let index = 0; index < this.Nodes.length; index++) {
|
||||
if(!this.Nodes[index].mustVisit){
|
||||
c++;
|
||||
}
|
||||
if(c == 2){
|
||||
showToast("最多只能选择两个非经过点")
|
||||
return
|
||||
}
|
||||
}
|
||||
this.Nodes[this.curNodesIndex].mustVisit = false
|
||||
this.refresh = !this.refresh
|
||||
})
|
||||
Button('设置为经过点')
|
||||
.type(ButtonType.Normal)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
.onClick(()=>{
|
||||
this.Nodes[this.curNodesIndex].mustVisit = true
|
||||
this.refresh = !this.refresh
|
||||
})
|
||||
Button('增加一个节点')
|
||||
.type(ButtonType.Normal)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
.onClick(()=>{
|
||||
if (this.Nodes.length >= 13) {
|
||||
showToast("节点数只能位于2至13之间")
|
||||
return
|
||||
}
|
||||
this.Nodes = cp.addNodeData()
|
||||
})
|
||||
}.width('100%').height('100%')
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
struct linkOP {
|
||||
@Consume Nodes:Array<Node>
|
||||
@Consume curNodesIndex:number
|
||||
@Consume Edges:Array<Edge>
|
||||
@Consume curEdgesIndex:number
|
||||
|
||||
@State isOnClick:boolean = false
|
||||
@State startNode:Node|undefined = undefined
|
||||
@State endNode:Node|undefined = undefined
|
||||
|
||||
@Consume refresh:boolean
|
||||
|
||||
build() {
|
||||
Scroll() {
|
||||
Column({ space: 5 }) {
|
||||
Button('建立为完全图')
|
||||
.type(ButtonType.Normal)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
.onClick(() => {
|
||||
for (let index = 0; index < this.Edges.length; index++) {
|
||||
this.Edges[index].visible = true
|
||||
}
|
||||
this.refresh = !this.refresh
|
||||
})
|
||||
Button('删除所有连接')
|
||||
.type(ButtonType.Normal)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
.onClick(() => {
|
||||
for (let index = 0; index < this.Edges.length; index++) {
|
||||
this.Edges[index].visible = false
|
||||
}
|
||||
this.refresh = !this.refresh
|
||||
})
|
||||
Column({ space: 5 }) {
|
||||
Text('当前选中的连接:')
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
Text(`从 节点${this.Edges[this.curEdgesIndex].node1}:` + this.Nodes[this.Edges[this.curEdgesIndex].node1].label)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
Text(`到 节点${this.Edges[this.curEdgesIndex].node2}:` + this.Nodes[this.Edges[this.curEdgesIndex].node2].label)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
Text(`距离${(this.Edges[this.curEdgesIndex].distance * 1e-3).toFixed(3)}千米`)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
Text(`时间${(this.Edges[this.curEdgesIndex].time / 60).toFixed(0)}分钟`)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
}.width('100%')
|
||||
|
||||
Button(this.isOnClick ? "取消" : '选择两个节点进行连接')
|
||||
.type(ButtonType.Normal)
|
||||
.onClick(() => {
|
||||
this.startNode = this.Nodes[this.curNodesIndex]
|
||||
this.isOnClick = !this.isOnClick
|
||||
})
|
||||
Row({ space: 5 }) {
|
||||
Text('当前节点:')
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
Text(this.isOnClick ? this.startNode!.label : this.Nodes[this.curNodesIndex].label)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
}.width('100%')
|
||||
|
||||
if (this.isOnClick) {
|
||||
Row({ space: 5 }) {
|
||||
Text('要连接节点:')
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
Text(this.Nodes[this.curNodesIndex].label)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
}.width('100%')
|
||||
|
||||
Button('确定')
|
||||
.type(ButtonType.Normal)
|
||||
.onClick(() => {
|
||||
let startNodeId = this.startNode!.id
|
||||
let endNodeId = this.Nodes[this.curNodesIndex].id
|
||||
Logger.i(startNodeId + " " + endNodeId)
|
||||
this.startNode = this.Nodes[this.curNodesIndex]
|
||||
if (startNodeId == endNodeId) {
|
||||
showToast("不允许连接同一个节点")
|
||||
return
|
||||
}
|
||||
this.isOnClick = false
|
||||
cp.connectTwoNodes(startNodeId, endNodeId);
|
||||
this.refresh = !this.refresh;
|
||||
})
|
||||
}
|
||||
}
|
||||
}.width('100%').height('100%').padding({bottom:5})
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
struct pathOP {
|
||||
@Consume Nodes:Array<Node>
|
||||
@Consume curNodesIndex:number
|
||||
@Consume Edges:Array<Edge>
|
||||
@Consume refresh:boolean
|
||||
@Consume nodePathId:Array<number>
|
||||
@Consume nodePath:string
|
||||
@Consume isStartToPath:boolean
|
||||
@Consume solutionData:solutionData
|
||||
@State isSelectNode:boolean = false
|
||||
@State selectIndex:number = 0
|
||||
@State index:number = -1
|
||||
@State startNode:Node|undefined = undefined
|
||||
@State endNode:Node|undefined = undefined
|
||||
@State displayShortPath: boolean = false
|
||||
|
||||
|
||||
|
||||
build() {
|
||||
Scroll(){
|
||||
Column({space:5}){
|
||||
Button(this.isStartToPath?"结束标记":'标记路径')
|
||||
.type(ButtonType.Normal)
|
||||
.onClick(()=>{
|
||||
if(this.isSelectNode){
|
||||
return
|
||||
}
|
||||
if(!this.isStartToPath){
|
||||
this.nodePathId = []
|
||||
} else {
|
||||
if (!cp.checkPathLegitimate(this.nodePathId)) {
|
||||
this.nodePathId.length = 0;
|
||||
cp.clearPath();
|
||||
}
|
||||
}
|
||||
this.isStartToPath = !this.isStartToPath
|
||||
|
||||
//先置空
|
||||
for (let index = 0; index < this.Edges.length; index++) {
|
||||
this.Edges[index].marked = false
|
||||
}
|
||||
for (let index = 0; index < this.Nodes.length; index++) {
|
||||
this.Nodes[index].marked = false
|
||||
}
|
||||
this.refresh = !this.refresh
|
||||
cp.setDisplayPath(this.nodePathId);
|
||||
})
|
||||
Text('用户指定路径:' + this.nodePath)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
.width("100%")
|
||||
if(this.isStartToPath){
|
||||
Row(){
|
||||
Button('清除')
|
||||
.type(ButtonType.Normal)
|
||||
.onClick(()=>{
|
||||
this.nodePathId = []
|
||||
cp.clearPath();
|
||||
for (let index = 0; index < this.Nodes.length; index++) {
|
||||
this.Nodes[index].marked = false
|
||||
}
|
||||
})
|
||||
Button('撤销')
|
||||
.type(ButtonType.Normal)
|
||||
.onClick(()=>{
|
||||
cp.undoPath();
|
||||
if(this.nodePathId.length == 0){
|
||||
return
|
||||
}
|
||||
this.Nodes[this.nodePathId[this.nodePathId.length-1]].marked = false
|
||||
this.nodePathId.pop()
|
||||
return
|
||||
|
||||
})
|
||||
}.width('100%').justifyContent(FlexAlign.SpaceEvenly)
|
||||
Button('检查路径是否合法')
|
||||
.type(ButtonType.Normal)
|
||||
.onClick(()=>{
|
||||
cp.checkPathLegitimate(this.nodePathId);
|
||||
})
|
||||
}
|
||||
Button('判断是否为最短路径')
|
||||
.type(ButtonType.Normal)
|
||||
.onClick(()=>{
|
||||
if(this.isStartToPath){
|
||||
return
|
||||
}
|
||||
if(this.isSelectNode){
|
||||
return
|
||||
}
|
||||
if(!this.nodePath.length){
|
||||
return
|
||||
}
|
||||
if(this.Nodes.length <= 10){
|
||||
if (cp.checkUserBest(cp.path, this.nodePathId[0])) {
|
||||
showToast("当前路径是最短路径");
|
||||
} else {
|
||||
showToast("当前路径不是最短路径");
|
||||
}
|
||||
}else{
|
||||
showToast("超过11个节点的遍历法求tsp问题会导致程序崩溃,不建议使用");
|
||||
}
|
||||
|
||||
})
|
||||
Button('判断是否为贪心路径')
|
||||
.type(ButtonType.Normal)
|
||||
.onClick(()=>{
|
||||
if(this.isStartToPath){
|
||||
return
|
||||
}
|
||||
if(this.isSelectNode){
|
||||
return
|
||||
}
|
||||
if(!this.nodePath.length){
|
||||
return
|
||||
}
|
||||
if (cp.checkIsGreedy(cp.path, this.nodePathId[0])) {
|
||||
showToast("当前路径是贪心路径");
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
showToast("当前路径不是贪心路径");
|
||||
}, 2000);
|
||||
}
|
||||
})
|
||||
Row(){
|
||||
Button(this.isSelectNode?"取消":'选择两个节点')
|
||||
.type(ButtonType.Normal)
|
||||
.onClick(()=>{
|
||||
if(this.isStartToPath){
|
||||
return
|
||||
}
|
||||
this.isSelectNode = !this.isSelectNode
|
||||
this.selectIndex = this.curNodesIndex
|
||||
this.startNode = this.Nodes[this.curNodesIndex]
|
||||
this.endNode = this.Nodes[this.curNodesIndex]
|
||||
this.index = -1
|
||||
this.displayShortPath = false;
|
||||
this.solutionData.path = "";
|
||||
this.solutionData.count = 0;
|
||||
this.Nodes.forEach(node => {
|
||||
node.mustVisit = true;
|
||||
})
|
||||
this.refresh = !this.refresh;
|
||||
})
|
||||
if(this.isSelectNode){
|
||||
Button("确定")
|
||||
.type(ButtonType.Normal)
|
||||
.onClick(()=>{
|
||||
if(this.curNodesIndex == this.startNode!.id){
|
||||
showToast("不能选择相同的节点")
|
||||
return
|
||||
}
|
||||
if (!cp.graphCheckConnectivity(this.Nodes[this.curNodesIndex], this.Nodes[this.startNode!.id])[0]) {
|
||||
showToast("节点间不可达,请重新选择节点");
|
||||
return;
|
||||
}
|
||||
if(this.index != -1){
|
||||
return
|
||||
}
|
||||
this.endNode = this.Nodes[this.curNodesIndex]
|
||||
this.index = this.curNodesIndex
|
||||
AppStorage.setOrCreate("startNodeId",this.startNode!.id)
|
||||
AppStorage.setOrCreate("endNodeId",this.endNode!.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
Text(`当前节点1ID:${this.isSelectNode?this.selectIndex + `(${this.Nodes[this.selectIndex].label})`:this.curNodesIndex + `(${this.Nodes[this.curNodesIndex].label})`}`)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
if(this.isSelectNode){
|
||||
Text(`当前节点2ID:${this.index != -1?this.index + `(${this.Nodes[this.index].label})`:this.curNodesIndex + `(${this.Nodes[this.curNodesIndex].label})`}`)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
Button('求解最短路径')
|
||||
.type(ButtonType.Normal)
|
||||
.onClick(()=>{
|
||||
this.displayShortPath = false;
|
||||
this.solutionData.path = "";
|
||||
this.solutionData.count = 0;
|
||||
if(this.endNode != undefined){
|
||||
if(this.endNode!.id != this.startNode!.id){
|
||||
let shortPathInfo: [number[], number] = cp.shortestPathAlgorithm(this.startNode!.id, this.endNode!.id);
|
||||
let shortPath: number[] = shortPathInfo[0];
|
||||
this.solutionData.count = Number((shortPathInfo[1] * 1e-3).toFixed(3));
|
||||
for (let index = 0; index < shortPath.length; index++) {
|
||||
this.solutionData.path += (this.Nodes[shortPath[index]].label)
|
||||
if (index != shortPath.length - 1) {
|
||||
this.solutionData.path += "->";
|
||||
}
|
||||
}
|
||||
if (this.solutionData.path != "") {
|
||||
this.displayShortPath = true;
|
||||
} else {
|
||||
showToast("没有找到路径");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
showToast("请选择第二个节点,或点击确定")
|
||||
})
|
||||
if (this.displayShortPath) {
|
||||
Text('最短路径长度:' + this.solutionData.count + "km")
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
.width("100%")
|
||||
Text('最短路径为:' + this.solutionData.path)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
.width("100%")
|
||||
}
|
||||
}
|
||||
}
|
||||
}.width('100%').height('100%').padding({bottom:5})
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
struct solution_traverse {
|
||||
@Consume solutionData:solutionData
|
||||
build() {
|
||||
Column({space:5}){
|
||||
Text('遍历次数' + this.solutionData.count!)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
Text('求解花费时间' + this.solutionData.time)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
Text('最短路径:' + this.solutionData.path)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
.width("100%")
|
||||
ForEach(this.solutionData.otherPath,(item:string,index:number)=>{
|
||||
Row(){
|
||||
Text('其他最短路径' + (index + 1) + ": " + item)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
}.width('100%')
|
||||
})
|
||||
}.width('100%').height('100%')
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
struct solution_greedy{
|
||||
|
||||
@Consume solutionData:solutionData
|
||||
build() {
|
||||
Column({space:5}){
|
||||
Text('求解花费时间' + this.solutionData.time)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
Text('最短路径:' + this.solutionData.path)
|
||||
.fontSize(SMALL_SIZE_FONT)
|
||||
.width('100%')
|
||||
}.width('100%').height('100%')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
|
||||
import hilog from '@ohos.hilog';
|
||||
import UIAbility from '@ohos.app.ability.UIAbility';
|
||||
import Want from '@ohos.app.ability.Want';
|
||||
import window from '@ohos.window';
|
||||
import dataPreferences from '@ohos.data.preferences';
|
||||
|
||||
export default class EntryAbility extends UIAbility {
|
||||
|
||||
async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
|
||||
let dp = await dataPreferences.getPreferences(this.context,"web")
|
||||
let loop = await dp.has("isLoad")
|
||||
if(loop){
|
||||
//读取文件操作
|
||||
AppStorage.setOrCreate('isOpFile',true)
|
||||
}else{
|
||||
dp.put("isLoad",true,()=>{
|
||||
dp.flush()
|
||||
})
|
||||
//调用api操作
|
||||
AppStorage.setOrCreate('isOpFile',false)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
onDestroy(): void {
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
|
||||
}
|
||||
|
||||
onWindowStageCreate(windowStage: window.WindowStage): void {
|
||||
// Main window is created, set main page for this ability
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
|
||||
|
||||
windowStage.loadContent('pages/Index', (err, data) => {
|
||||
if (err.code) {
|
||||
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
|
||||
return;
|
||||
}
|
||||
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
|
||||
});
|
||||
}
|
||||
|
||||
onWindowStageDestroy(): void {
|
||||
// Main window is destroyed, release UI related resources
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
|
||||
}
|
||||
|
||||
onForeground(): void {
|
||||
// Ability has brought to foreground
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
|
||||
}
|
||||
|
||||
onBackground(): void {
|
||||
// Ability has back to background
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
import { CityInfo } from '../common/dataStruct/interface/CityInfo';
|
||||
import { X_CENTER, Y_CENTER } from '../common/CommonConstant';
|
||||
import Edge from '../common/dataStruct/class/Edge';
|
||||
import Node from '../common/dataStruct/class/Node';
|
||||
import { solutionData } from '../common/dataStruct/interface/SolutionData';
|
||||
import { header } from '../component/Header';
|
||||
import { imageShow } from '../component/ImageShow';
|
||||
import { operateBar } from '../component/OperateBar';
|
||||
import { cp } from '../utils/algorithm/customPath';
|
||||
import { getRealData } from '../utils/functions/getRealData';
|
||||
import { Logger } from '../utils/Logger';
|
||||
import { createCityInfo } from '../utils/functions/createCityInfo';
|
||||
|
||||
export let cityInfo: CityInfo = {};
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct Index {
|
||||
@State headerContent:Array<string> = ["节点操作","连接操作","路径操作","最短路径-遍历","最短路径-贪心"]
|
||||
@State @Watch('refreshOption')curHeaderContentIndex:number = 0
|
||||
refreshOption(){
|
||||
for (let index = 0; index < this.Nodes.length; index++) {
|
||||
this.Nodes[index].marked = false
|
||||
}
|
||||
this.nodePathId = []
|
||||
}
|
||||
|
||||
@Provide @Watch('refreshNodes')Nodes:Array<Node> = [new Node(0, "noMean", false, [X_CENTER,Y_CENTER])];
|
||||
@Provide curNodesIndex:number = 0
|
||||
|
||||
@Provide Edges:Array<Edge> = []
|
||||
@Provide curEdgesIndex:number = 0
|
||||
|
||||
@State resTime:number = 0
|
||||
@State resDistance:number = 0
|
||||
|
||||
@Provide solutionData:solutionData = {
|
||||
count:-1,
|
||||
time:" ",
|
||||
path:" ",
|
||||
otherPath:[""]
|
||||
}
|
||||
|
||||
//画板刷新
|
||||
@Provide @Watch('refreshBoard')refresh:boolean = false
|
||||
@State loop:string = "100%"
|
||||
|
||||
@Provide isStartToPath:boolean = false
|
||||
@Provide nodePath:string = ""
|
||||
@Provide @Watch('refreshNodePath')nodePathId:Array<number> = []
|
||||
refreshNodePath(){
|
||||
this.nodePath = ''
|
||||
for (let index = 0; index < this.nodePathId.length; index++) {
|
||||
this.nodePath += (this.Nodes[this.nodePathId[index]].label + `(${this.nodePathId[index]})`);
|
||||
if (index != this.nodePathId.length - 1) {
|
||||
this.nodePath += "->";
|
||||
}
|
||||
}
|
||||
if(this.nodePathId.length == 0){
|
||||
this.curNodesIndex = 0
|
||||
}else{
|
||||
this.curNodesIndex = this.nodePathId[this.nodePathId.length-1]
|
||||
}
|
||||
this.refresh = !this.refresh
|
||||
Logger.i(JSON.stringify(this.nodePathId))
|
||||
}
|
||||
refreshNodes(){
|
||||
this.curNodesIndex = 0
|
||||
//重新给id排序
|
||||
for (let index = 0; index < this.Nodes.length; index++) {
|
||||
this.Nodes[index].id = index
|
||||
}
|
||||
|
||||
this.Edges = cp.createEdgeData(this.Nodes)
|
||||
|
||||
//涉及到节点数组的变化先更新节点数组再更新连接数组再更新画板
|
||||
this.refresh = !this.refresh
|
||||
}
|
||||
|
||||
refreshBoard(){
|
||||
this.loop = this.loop == "100%"?"101%":"100%"
|
||||
}
|
||||
|
||||
@State isShow:boolean = true
|
||||
build() {
|
||||
Stack(){
|
||||
Column(){
|
||||
|
||||
header({headerContent:this.headerContent,curHeaderContentIndex:$curHeaderContentIndex,resTime:$resTime,resDistance:$resDistance})
|
||||
.width('100%')
|
||||
.layoutWeight(1)
|
||||
|
||||
Divider()
|
||||
.width('100%')
|
||||
.strokeWidth(1)
|
||||
.color(Color.Orange)
|
||||
|
||||
Row(){
|
||||
|
||||
imageShow({loop:this.loop,curEdgesIndex:$curEdgesIndex})
|
||||
.width('80%')
|
||||
.height('100%')
|
||||
|
||||
operateBar({headerContent:this.headerContent,curHeaderContentIndex:$curHeaderContentIndex})
|
||||
.width('20%')
|
||||
.height('100%')
|
||||
|
||||
}.width('100%').height('90%')
|
||||
}.width('100%').height('100%').opacity(this.isShow?0.425:1)
|
||||
if(this.isShow) {
|
||||
Column() {
|
||||
Text('正在初始化数据...')
|
||||
.fontSize(40)
|
||||
.onAppear(async () => {
|
||||
await createCityInfo().then((info) => {
|
||||
cityInfo = info;
|
||||
})
|
||||
this.Nodes = cp.createNodeData(3);
|
||||
this.Edges = cp.createEdgeData(this.Nodes);
|
||||
this.isShow = false;
|
||||
})
|
||||
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import hilog from '@ohos.hilog';
|
||||
|
||||
const DOMAIN = 0
|
||||
const APP_TAG = 'TSPTestApp'
|
||||
|
||||
export class Logger {
|
||||
public static d(message: string) {
|
||||
hilog.debug(DOMAIN, APP_TAG, message)
|
||||
}
|
||||
|
||||
public static i(message: string) {
|
||||
hilog.info(DOMAIN, APP_TAG, message)
|
||||
}
|
||||
|
||||
public static e(message: string) {
|
||||
hilog.error(DOMAIN, APP_TAG, message)
|
||||
}
|
||||
|
||||
public static w(message: string) {
|
||||
hilog.warn(DOMAIN, APP_TAG, message)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import { cp } from "./customPath";
|
||||
import Node from '../../common/dataStruct/class/Node';
|
||||
import Edge from '../../common/dataStruct/class/Edge';
|
||||
|
||||
export class TSP {
|
||||
path: Edge[]; // 从连接构造的路径
|
||||
nodes: Node[]; // 节点对象列表
|
||||
edges: Edge[]; // 连接列表
|
||||
|
||||
constructor() {
|
||||
this.path = [];
|
||||
this.nodes = [];
|
||||
this.edges = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建节点数组和全连接数组
|
||||
* @param nodeNum 节点数量
|
||||
*/
|
||||
createNodesAndEdges(nodeNum: number) {
|
||||
this.path = [];
|
||||
this.nodes = cp.createNodeData(nodeNum);
|
||||
this.edges = cp.createEdgeData(this.nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建随机 tsp 问题
|
||||
*/
|
||||
createRandomTspProblem() {
|
||||
// 随机4到7个节点
|
||||
const nodeNum: number = Math.floor(Math.random() * (7 - 4 + 1)) + 4;
|
||||
this.createNodesAndEdges(nodeNum);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建随机路径
|
||||
* @param nodeNum 节点数量
|
||||
*/
|
||||
createRandomPath(nodeNum: number) {
|
||||
this.createNodesAndEdges(nodeNum);
|
||||
let connOrder: number[] = Array.from({length: nodeNum}, (_: number, i: number) => i);
|
||||
connOrder.sort(() => Math.random() - 0.5);
|
||||
this.path = cp.setDisplayPath(connOrder)[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export let tsp: TSP = new TSP();
|
||||
|
|
@ -0,0 +1,574 @@
|
|||
import { showToast } from "../functions/showToast"
|
||||
import Node from '../../common/dataStruct/class/Node'
|
||||
import Edge from '../../common/dataStruct/class/Edge'
|
||||
import { BOTTOM_RIGHT_X, BOTTOM_RIGHT_Y, DRAW_NODE_SIZE, TOP_LEFT_X, TOP_LEFT_Y, CITY } from '../../common/CommonConstant';
|
||||
import { cityInfo } from '../../pages/Index';
|
||||
import { Graph, nodeInfo } from '../../common/dataStruct/interface/Graph';
|
||||
import { deepCopyGraph } from '../functions/deepCopyGraph';
|
||||
import { degreesToRadians } from '../functions/degreesToRadians';
|
||||
|
||||
let cityName: string[] = CITY.slice();
|
||||
let usedCity: string[] = [];
|
||||
let surplusCity: string[] = [];
|
||||
|
||||
export class CustomPath {
|
||||
path: Edge[]; // 从连接构造的路径
|
||||
pathDistanceCount: number; // 路径距离成本
|
||||
pathTimeCount: number; // 路径时间成本
|
||||
elapsedTime: number; // 运行时间
|
||||
graph: Graph; // 邻接图
|
||||
nodes: Node[]; // 节点对象列表
|
||||
edges: Edge[]; // 连接列表
|
||||
circles: number[][]; // 形成环路径
|
||||
numberOfPasses: number; // 遍历次数
|
||||
|
||||
constructor(path: Edge[], pathDistanceCount: number, pathTimeCount: number, elapsedTime: number, graph: Graph, nodes: Node[], edges: Edge[]) {
|
||||
this.path = path;
|
||||
this.pathDistanceCount = pathDistanceCount;
|
||||
this.pathTimeCount = pathTimeCount;
|
||||
this.elapsedTime = elapsedTime;
|
||||
this.graph = graph;
|
||||
this.nodes = nodes;
|
||||
this.edges = edges;
|
||||
this.circles = [];
|
||||
this.numberOfPasses = 0;
|
||||
}
|
||||
|
||||
createNodeData(data: number): Node[] {
|
||||
let num: number = data;
|
||||
let ang = 360 / num;
|
||||
let R = 140;
|
||||
this.nodes = [];
|
||||
cityName.sort(() => Math.random() - 0.5);
|
||||
for (let i = 0; i < num; i++) {
|
||||
let origin: [number, number] = [(BOTTOM_RIGHT_X - TOP_LEFT_X - DRAW_NODE_SIZE) / 2, (BOTTOM_RIGHT_Y - TOP_LEFT_Y - DRAW_NODE_SIZE) / 2];
|
||||
let rad = degreesToRadians(i * ang);
|
||||
const x = origin[0] + Math.cos(rad) * R | 0;
|
||||
const y = origin[1] + Math.sin(rad) * R | 0;
|
||||
this.nodes.push(new Node(i, cityName[i], false, [x, y], true));
|
||||
}
|
||||
usedCity = cityName.slice(0, num);
|
||||
surplusCity = cityName.slice(num);
|
||||
return this.nodes;
|
||||
}
|
||||
|
||||
addNodeData(): Node[] {
|
||||
let num: number = this.nodes.length + 1;
|
||||
let ang = 360 / num;
|
||||
let R = 140;
|
||||
let origin: [number, number] = [(BOTTOM_RIGHT_X - TOP_LEFT_X - DRAW_NODE_SIZE) / 2, (BOTTOM_RIGHT_Y - TOP_LEFT_Y - DRAW_NODE_SIZE) / 2];
|
||||
surplusCity.sort(() => Math.random() - 0.5);
|
||||
this.nodes = this.nodes.map(node => {
|
||||
let rad = degreesToRadians(node.id * ang);
|
||||
const x = origin[0] + Math.cos(rad) * R | 0;
|
||||
const y = origin[1] + Math.sin(rad) * R | 0;
|
||||
node.coordinates = [x, y];
|
||||
return node;
|
||||
});
|
||||
let rad = degreesToRadians((num - 1) * ang);
|
||||
const x = origin[0] + Math.cos(rad) * R | 0;
|
||||
const y = origin[1] + Math.sin(rad) * R | 0;
|
||||
this.nodes.push(new Node(this.nodes.length, surplusCity[0] , false, [x, y], true));
|
||||
usedCity = usedCity.concat(surplusCity[0]);
|
||||
surplusCity = surplusCity.slice(1);
|
||||
this.createEdgeData(this.nodes);
|
||||
return this.nodes;
|
||||
}
|
||||
|
||||
deleteNodeData(currentNodeId: number): Node[] {
|
||||
// surplusCity = usedCity.slice(-1).concat(surplusCity);
|
||||
// usedCity = usedCity.slice(0, -1);
|
||||
// this.nodes.pop();
|
||||
usedCity = usedCity.filter((city) => city != this.nodes[currentNodeId].label);
|
||||
surplusCity = [this.nodes[currentNodeId].label].concat(surplusCity);
|
||||
this.nodes = this.nodes.filter(node => node.id != currentNodeId);
|
||||
this.nodes = this.nodes.map((node, index) => {
|
||||
node.id = index;
|
||||
return node;
|
||||
});
|
||||
this.createEdgeData(this.nodes);
|
||||
return this.nodes;
|
||||
}
|
||||
|
||||
regenerate() {
|
||||
this.nodes = this.nodes.map(node => {
|
||||
let x = Math.random()*(BOTTOM_RIGHT_X - TOP_LEFT_X - DRAW_NODE_SIZE);
|
||||
let y = Math.random()*(BOTTOM_RIGHT_Y - TOP_LEFT_Y - DRAW_NODE_SIZE);
|
||||
node.coordinates = [x, y];
|
||||
return node;
|
||||
})
|
||||
}
|
||||
|
||||
createEdgeData(nodes: Node[]): Edge[] {
|
||||
this.edges = [];
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
for (let j = i + 1; j < nodes.length; j++) {
|
||||
const connectionId = this.edges.length + 1;
|
||||
const node1Id = nodes[i].id;
|
||||
const node2Id = nodes[j].id;
|
||||
const visible = true;
|
||||
const marked = false;
|
||||
const label = `Connection-${connectionId}`;
|
||||
// const distance = Math.floor(Math.random() * (1000 - 10 + 1)) + 10; // 生成10到1000的随机整数 --你需要设置实际的距离值
|
||||
// const time = Math.floor(Math.random() * (720 - 60 + 1)) + 60; // 生成60到720的随机整数 --你需要设置实际的时间值
|
||||
const distance = cityInfo[nodes[i].label][nodes[j].label].distance;
|
||||
const time = cityInfo[nodes[i].label][nodes[j].label].time;
|
||||
// console.log(`${nodes[i].label}->${nodes[j].label}: 距离为${distance},时间为${time}`);
|
||||
this.edges.push(new Edge(connectionId, node1Id, node2Id, visible, marked, label, distance, time));
|
||||
}
|
||||
}
|
||||
return this.edges;
|
||||
}
|
||||
|
||||
connectTwoNodes(node1Id: number, node2Id: number) {
|
||||
let nodeId: [number, number] = [node1Id, node2Id];
|
||||
for (const edge of this.edges) {
|
||||
if (nodeId.includes(edge.node1) && nodeId.includes(edge.node2)) {
|
||||
if (!edge.visible) edge.visible = true;
|
||||
else showToast("当前两节点已连接");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setDisplayPath(nodeIds: number[]): [Edge[], number, number] {
|
||||
this.path = []; // 置空操作
|
||||
this.pathDistanceCount = 0;
|
||||
this.pathTimeCount = 0;
|
||||
if (nodeIds.length) {
|
||||
for (let i = 0; i < nodeIds.length; i++) {
|
||||
let id1: number = 0;
|
||||
let id2: number = 0;
|
||||
if (i < nodeIds.length - 1) {
|
||||
id1 = nodeIds[i];
|
||||
id2 = nodeIds[i + 1];
|
||||
} else {
|
||||
if (nodeIds[0] == nodeIds[-1]) {
|
||||
break;
|
||||
} else {
|
||||
id1 = nodeIds[i];
|
||||
id2 = nodeIds[0];
|
||||
}
|
||||
}
|
||||
let nodeId: [number, number] = [id1, id2]; // 用于寻找两节点的连接对象
|
||||
for (const edge of this.edges) {
|
||||
if (nodeId.includes(edge.node1) && nodeId.includes(edge.node2)) {
|
||||
edge.marked = true;
|
||||
this.path.push(edge);
|
||||
this.pathDistanceCount += edge.distance;
|
||||
this.pathTimeCount += edge.time;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [this.path, this.pathDistanceCount, this.pathTimeCount];
|
||||
}
|
||||
|
||||
clearPath() {
|
||||
this.path = [];
|
||||
this.pathDistanceCount = 0;
|
||||
this.pathTimeCount = 0;
|
||||
this.edges.forEach(edge => {
|
||||
edge.marked = false; // 全部连接清除标记
|
||||
})
|
||||
}
|
||||
|
||||
undoPath() {
|
||||
if (this.path.length) {
|
||||
this.path[-1].marked = false; // 路径末尾连接清除标记
|
||||
this.path.pop();
|
||||
}
|
||||
}
|
||||
|
||||
checkPathLegitimate(paths: number[]): boolean {
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
let node1Id: number = 0;
|
||||
let node2Id: number = 0;
|
||||
if (i < paths.length - 1) {
|
||||
node1Id = paths[i];
|
||||
node2Id = paths[i + 1];
|
||||
} else {
|
||||
node1Id = paths[i];
|
||||
node2Id = paths[0];
|
||||
}
|
||||
if (node1Id == node2Id) {
|
||||
return false;
|
||||
}
|
||||
let node1: Node = this.nodes[node1Id];
|
||||
let node2: Node = this.nodes[node2Id];
|
||||
if (!this.graphCheckConnectivity(node1, node2)[0]) {
|
||||
showToast(`在节点${node1.label}与节点${node2.label}之间没有连接, 路径不合法, 请重新选择路径`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
let nonessentialNode: Node[] = this.nodes.filter(node => node.mustVisit == false);
|
||||
let nonessentialNum: number = nonessentialNode.length;
|
||||
let tempPaths: number[] = paths.filter(nodeId => { // 路径中所有必经节点构成数组
|
||||
for (let i = 0; i < nonessentialNum; i++) {
|
||||
if (nodeId == nonessentialNode[i].id) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (tempPaths.length != this.nodes.length - nonessentialNum) {
|
||||
showToast(`所选路径未经所有必经节点城市, 路径不合法, 请重新选择路径`);
|
||||
return false;
|
||||
}
|
||||
showToast("所选路径合法");
|
||||
return true;
|
||||
}
|
||||
|
||||
createGraph(nodes: Node[], isDistance: boolean = true): Graph {
|
||||
this.graph = {};
|
||||
nodes.forEach(node => {
|
||||
if (node.mustVisit == true) {
|
||||
this.graph[node.id] = [];
|
||||
}
|
||||
})
|
||||
nodes.forEach(node => {
|
||||
if (node.mustVisit == true) {
|
||||
this.edges.forEach(edge => {
|
||||
if (edge.visible) {
|
||||
let nodeIds: [number, number] = [edge.node1, edge.node2];
|
||||
if (nodeIds.includes(node.id) && nodes[nodeIds[0]].mustVisit == true && nodes[nodeIds[1]].mustVisit == true) {
|
||||
let nodeId: number = nodeIds.find(id => id !== node.id) as number;
|
||||
let weight: number = isDistance ? edge.distance : edge.time;
|
||||
this.graph[node.id].push({ to: nodeId, weight: weight });
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
return this.graph;
|
||||
}
|
||||
|
||||
dfs(nodeId: number, visited: Set<number>, path: number[], startNeighbors: Set<number>) {
|
||||
try {
|
||||
visited.add(nodeId);
|
||||
path.push(nodeId);
|
||||
if (path.length == Object.keys(this.graph).length && startNeighbors.has(path[path.length - 1])) {
|
||||
this.circles.push(path.slice());
|
||||
return;
|
||||
}
|
||||
for (const adjacentNodeInfo of this.graph[nodeId]) {
|
||||
const neighbor: number = adjacentNodeInfo.to;
|
||||
if (!visited.has(neighbor)) {
|
||||
this.dfs(neighbor, visited, path, startNeighbors);
|
||||
visited.delete(neighbor);
|
||||
path.pop();
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.log(this.circles.length.toString());
|
||||
this.circles.length = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
findCompleteCycles(graph: Graph, startNodeId: number = 0): number[][] {
|
||||
this.circles = [];
|
||||
let visited: Set<number> = new Set();
|
||||
let startNeighborsArr: number[] = graph[startNodeId].map(nodeInfo => nodeInfo.to);
|
||||
let startNeighbors: Set<number> = new Set(startNeighborsArr);
|
||||
this.dfs(startNodeId, visited, [], startNeighbors);
|
||||
return this.circles;
|
||||
}
|
||||
|
||||
calculatePathCost(graph: Graph, path: number[]) {
|
||||
let costValue = 0;
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
let currentNodeId: number = path[i];
|
||||
let nextNodeId: number = path[i + 1];
|
||||
if (!(graph[currentNodeId] && graph[currentNodeId].length === 0)) {
|
||||
for (const adjacentNodeInfo of graph[currentNodeId]) {
|
||||
if (adjacentNodeInfo.to == nextNodeId) {
|
||||
costValue += adjacentNodeInfo.weight;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else break;
|
||||
}
|
||||
return costValue;
|
||||
}
|
||||
|
||||
traversalSearchToPath(cp: CustomPath, graph: Graph, startNodeId: number = 0, _mustVisitNode?: number[]): [number[][], number, number, number] {
|
||||
let startTime: number = Date.now();
|
||||
let allPaths: number[][] = cp.findCompleteCycles(graph, startNodeId);
|
||||
let numOfPasses = allPaths.length;
|
||||
if (allPaths.length == 0) {
|
||||
try {
|
||||
throw new Error("当前图无法获取完整路径,请检查节点连接是否完整");
|
||||
} catch {
|
||||
showToast("当前图无法获取完整路径,请检查节点连接是否完整");
|
||||
return [[], 0, 0, 0];
|
||||
}
|
||||
}
|
||||
let minPathList: number[][] = [];
|
||||
let minValue: number = Number.POSITIVE_INFINITY;
|
||||
for (const permutation of allPaths) {
|
||||
let currentPath = permutation.concat([startNodeId]);
|
||||
let currentCostValue = cp.calculatePathCost(graph, currentPath);
|
||||
if (currentCostValue < minValue) {
|
||||
minValue = currentCostValue;
|
||||
minPathList = [];
|
||||
minPathList.push(currentPath.slice());
|
||||
} else if (currentCostValue == minValue) {
|
||||
minPathList.push(currentPath.slice());
|
||||
}
|
||||
}
|
||||
let endTime: number = Date.now();
|
||||
cp.elapsedTime = (endTime - startTime) * 1e-3; // 转换为秒
|
||||
return [minPathList, minValue, cp.elapsedTime, numOfPasses];
|
||||
}
|
||||
|
||||
greedySearchToPath(cp: CustomPath, graph: Graph, startNodeId: number = 0, _mustVisitNode?: number[]): [number[][], number, number, number] {
|
||||
let startTime: number = Date.now();
|
||||
let startNeighborsArr: number[] = graph[startNodeId].map(nodeInfo => nodeInfo.to);
|
||||
let startNeighbors: Set<number> = new Set(startNeighborsArr);
|
||||
let remainingNodes: Set<number> = new Set(Object.keys(graph).map(i => Number(i)));
|
||||
remainingNodes.delete(startNodeId);
|
||||
let currentNodeId = startNodeId;
|
||||
let path: number[] = [startNodeId];
|
||||
while (remainingNodes.size > 0) {
|
||||
if (graph[currentNodeId].length === 0) break;
|
||||
let nextNodeId: number = Number.NaN;
|
||||
let minValue: number = Number.POSITIVE_INFINITY;
|
||||
for (const adjacentNodeInfo of graph[currentNodeId]) {
|
||||
if (adjacentNodeInfo.weight < minValue && !path.includes(adjacentNodeInfo.to)) {
|
||||
minValue = adjacentNodeInfo.weight;
|
||||
nextNodeId = adjacentNodeInfo.to;
|
||||
}
|
||||
}
|
||||
if (Number.isNaN(nextNodeId)) {
|
||||
try {
|
||||
throw new Error(`当前id为${currentNodeId}的节点无直接路径到id为${Array.from(remainingNodes)[0]}的节点,贪心算法求解失败`);
|
||||
} catch {
|
||||
showToast(`当前id为${currentNodeId}的节点无直接路径到id为${Array.from(remainingNodes)[0]}的节点,贪心算法求解失败`);
|
||||
return [[], 0, 0, 1];
|
||||
}
|
||||
}
|
||||
path.push(nextNodeId);
|
||||
remainingNodes.delete(nextNodeId);
|
||||
currentNodeId = nextNodeId;
|
||||
}
|
||||
if (startNeighbors.has(path[path.length - 1])) {
|
||||
path.push(startNodeId);
|
||||
} else {
|
||||
try {
|
||||
throw new Error(`当前id为${path[path.length - 1]}的节点无直接路径到id为${startNodeId}的节点,贪心算法求解失败`);
|
||||
} catch {
|
||||
showToast(`当前id为${path[path.length - 1]}的节点无直接路径到id为${startNodeId}的节点,贪心算法求解失败`);
|
||||
return [[], 0, 0, 1];
|
||||
}
|
||||
}
|
||||
let endTime: number = Date.now();
|
||||
cp.elapsedTime = (endTime - startTime) * 1e-3; // 转换为秒
|
||||
return [[path], cp.calculatePathCost(graph, path), cp.elapsedTime, 1];
|
||||
}
|
||||
|
||||
solveTsp(startNodeId: number = 0, func: (cp: CustomPath, graph: Graph, startNodeId: number) => [number[][], number, number, number]): [number[][], number, number, number] {
|
||||
let startTime: number = Date.now();
|
||||
let shortestDistance: number[] = [];
|
||||
let pathList: number[][][] = [];
|
||||
let remainGraph: Graph[] = [];
|
||||
let nonessentialNode: Node[] = this.nodes.filter(node => node.mustVisit == false);
|
||||
let nonessentialNum: number = nonessentialNode.length;
|
||||
this.numberOfPasses = 0;
|
||||
|
||||
for (let i = 0; i < nonessentialNum; i++) {
|
||||
if (startNodeId == nonessentialNode[i].id) {
|
||||
try {
|
||||
throw new Error("起始节点不能为非必经节点");
|
||||
} catch {
|
||||
showToast("起始节点不能为非必经节点");
|
||||
return [[], 0, 0, 0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let graph: Graph = this.createGraph(this.nodes);
|
||||
let res: [number[][], number, number, number] = func(this, graph, startNodeId);
|
||||
if (!res[0].length) return [[], 0, 0, 0];
|
||||
pathList.push(res[0].slice());
|
||||
shortestDistance.push(res[1]);
|
||||
this.numberOfPasses += res[3];
|
||||
remainGraph.push(deepCopyGraph(graph));
|
||||
for (let i = 0; i < nonessentialNum; i++) {
|
||||
this.nodes[nonessentialNode[i].id].mustVisit = true;
|
||||
for (let j = i + 1; j < nonessentialNum; j++) {
|
||||
this.nodes[nonessentialNode[j].id].mustVisit = true;
|
||||
graph = this.createGraph(this.nodes);
|
||||
res = func(this, graph, startNodeId);
|
||||
if (!res[0].length) return [[], 0, 0, 0];
|
||||
pathList.push(res[0].slice());
|
||||
shortestDistance.push(res[1]);
|
||||
this.numberOfPasses += res[3];
|
||||
remainGraph.push(deepCopyGraph(graph));
|
||||
this.nodes[nonessentialNode[j].id].mustVisit = false;
|
||||
}
|
||||
graph = this.createGraph(this.nodes);
|
||||
res = func(this, graph, startNodeId);
|
||||
if (!res[0].length) return [[], 0, 0, 0];
|
||||
pathList.push(res[0].slice());
|
||||
shortestDistance.push(res[1]);
|
||||
this.numberOfPasses += res[3];
|
||||
remainGraph.push(deepCopyGraph(graph));
|
||||
this.nodes[nonessentialNode[i].id].mustVisit = false;
|
||||
}
|
||||
let index: number = shortestDistance.reduce((minIndex, currentValue, currentIndex, array) => {
|
||||
return currentValue < array[minIndex] ? currentIndex : minIndex;
|
||||
}, 0);
|
||||
this.graph = deepCopyGraph(remainGraph[index]);
|
||||
let endTime: number = Date.now();
|
||||
this.elapsedTime = (endTime - startTime) * 1e-3; // 转换为秒
|
||||
return [pathList[index], shortestDistance[index], this.elapsedTime, this.numberOfPasses];
|
||||
}
|
||||
|
||||
graphCheckConnectivity(node1: Node, node2: Node): [boolean, number, number] {
|
||||
if (this.nodes && this.edges && this.nodes.length != 0 && this.edges.length != 0) {
|
||||
let find: number[] = [];
|
||||
for (const node of this.nodes) {
|
||||
find.push(node.id);
|
||||
}
|
||||
for (const edge of this.edges) {
|
||||
if (edge.visible) {
|
||||
let x: number = edge.node1;
|
||||
let y: number = edge.node2;
|
||||
while (x != find[x]) x = find[x];
|
||||
while (y != find[y]) y = find[y];
|
||||
if (x < y) find[y] = x;
|
||||
else find[x] = y;
|
||||
}
|
||||
}
|
||||
let x: number = node1.id;
|
||||
let y: number = node2.id;
|
||||
while (x != find[x]) x = find[x];
|
||||
while (y != find[y]) y = find[y];
|
||||
if (find[x] == find[y]) return [true, x, y];
|
||||
else return [false, x, y];
|
||||
} else {
|
||||
try {
|
||||
throw new Error("当前图无节点或无边");
|
||||
} catch {
|
||||
showToast("当前图无节点或无边");
|
||||
return [false, -1, -1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
specificPathCost(path: Edge[], isDistance: boolean = true): number {
|
||||
let minValue: number = 0;
|
||||
if (!isDistance) {
|
||||
for (const pathEle of path) {
|
||||
minValue += pathEle.time;
|
||||
}
|
||||
return minValue;
|
||||
} else {
|
||||
for (const pathEle of path) {
|
||||
minValue += pathEle.distance;
|
||||
}
|
||||
return minValue;
|
||||
}
|
||||
}
|
||||
|
||||
checkUserBest(path: Edge[], startNodeId: number = 0, isDistance: boolean = true): boolean {
|
||||
let revalue: [number[][], number, number, number] = this.solveTsp(startNodeId, this.traversalSearchToPath);
|
||||
let minPath: number[] = revalue[0][0];
|
||||
if (path.length + 1 != minPath.length) return false;
|
||||
let minValue: number = revalue[1];
|
||||
let userMinValue = this.specificPathCost(path, isDistance);
|
||||
if (minValue == userMinValue) return true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
NodeIdsPathToEdgesPath(path: number[]): Edge[] {
|
||||
let paths: Edge[] = [];
|
||||
let len: number = path.length;
|
||||
for (let i = 0; i < len - 1; i++) {
|
||||
for (const edge of this.edges) {
|
||||
if ((edge.node1 == path[i] && edge.node2 == path[i + 1]) || (edge.node1 == path[i + 1] && edge.node2 == path[i])) {
|
||||
paths.push(edge);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
EdgesPathToNodeIdsPath(path: Edge[], startNodeId: number = 0): number[] {
|
||||
let paths: number[] = [startNodeId];
|
||||
let nextNodeId: number = paths[0];
|
||||
for (const pathEle of path) {
|
||||
let nodeIds: number[] = [pathEle.node1, pathEle.node2];
|
||||
if (nodeIds.includes(nextNodeId)) {
|
||||
nextNodeId = nodeIds.find(id => id != nextNodeId) as number;
|
||||
paths.push(nextNodeId);
|
||||
}
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
checkIsGreedy(path: Edge[], startNodeId: number = 0) {
|
||||
let revalue: [number[][], number, number, number] = this.solveTsp(startNodeId, this.greedySearchToPath);
|
||||
let greedyPath: number[] = revalue[0][0];
|
||||
if (path.length + 1 != greedyPath.length) return false;
|
||||
let userPath: number[] = this.EdgesPathToNodeIdsPath(path, startNodeId);
|
||||
let len: number = userPath.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (userPath[i] != greedyPath[i]) {
|
||||
try {
|
||||
throw new Error(`正确的贪心节点顺序为${greedyPath.slice(0, -1)}\n你的第${i + 1}个节点${this.nodes[userPath[i]].label}不符合贪心规则。`);
|
||||
} catch {
|
||||
showToast(`正确的贪心节点顺序为${greedyPath.slice(0, -1)}\n你的第${i + 1}个节点${this.nodes[userPath[i]].label}不符合贪心规则。`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
shortestPathAlgorithm(startNodeId: number, endNodeId: number, isDistance: boolean = true): [number[], number] {
|
||||
this.graph = this.createGraph(this.nodes, isDistance);
|
||||
let distances: Record<number, number> = {}; // 记录从起始节点到每个节点的最短距离
|
||||
let previous: Record<number, number> = {}; // 记录最短路径中每个节点的前驱节点
|
||||
let visited: Set<number> = new Set(); // 存储已经处理过的节点
|
||||
let priorityQueue: Array<nodeInfo> = []; // 优先队列,用于处理待访问的节点,按照距离排序
|
||||
|
||||
for (const nodeId of Object.keys(this.graph)) {
|
||||
distances[nodeId] = Number.POSITIVE_INFINITY;
|
||||
previous[nodeId] = -1;
|
||||
}
|
||||
|
||||
distances[startNodeId] = 0;
|
||||
priorityQueue.push({ to: startNodeId, weight: 0 });
|
||||
while (priorityQueue.length > 0) {
|
||||
priorityQueue.sort((a, b) => a.weight - b.weight); // 维护优先队列,保证每次队列中距离最小的节点在最前面
|
||||
let currentNodeId = priorityQueue.shift()!.to;
|
||||
|
||||
if (currentNodeId === endNodeId) break;
|
||||
if (visited.has(currentNodeId)) continue;
|
||||
visited.add(currentNodeId);
|
||||
|
||||
for (const neighbor of this.graph[currentNodeId]) {
|
||||
const neighborNodeId: number = neighbor.to;
|
||||
const weight: number = neighbor.weight;
|
||||
let newDist: number = distances[currentNodeId] + weight;
|
||||
|
||||
if (newDist < distances[neighborNodeId]) {
|
||||
distances[neighborNodeId] = newDist;
|
||||
previous[neighborNodeId] = currentNodeId;
|
||||
priorityQueue.push({ to: neighborNodeId, weight: newDist });
|
||||
}
|
||||
}
|
||||
}
|
||||
let path: number[] = [];
|
||||
let currentNodeId: number = endNodeId;
|
||||
while (currentNodeId != -1) {
|
||||
path.unshift(currentNodeId);
|
||||
currentNodeId = previous[currentNodeId];
|
||||
}
|
||||
return [path, distances[endNodeId]];
|
||||
}
|
||||
}
|
||||
|
||||
export let cp: CustomPath = new CustomPath([], 0, 0, 0, {}, [], []);
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
import { CITY } from '../../common/CommonConstant';
|
||||
import { CityInfo } from '../../common/dataStruct/interface/CityInfo';
|
||||
import { Logger } from '../Logger';
|
||||
import { getRealData } from './getRealData';
|
||||
|
||||
export async function createCityInfo(): Promise<CityInfo> {
|
||||
//TODO 可注释掉第87、88行 再将第6行至85行注释打开 即可在指定的预览器参数下模拟程序
|
||||
// let res: [string, string, number, number][] = [
|
||||
// ["广州市", "深圳市", 123012, 10838],
|
||||
// ["广州市", "珠海市", 118916, 9380],
|
||||
// ["广州市", "汕头市", 408198, 20355],
|
||||
// ["广州市", "佛山市", 24721, 4042],
|
||||
// ["广州市", "韶关市", 214580, 10298],
|
||||
// ["广州市", "湛江市", 412296, 17672],
|
||||
// ["广州市", "肇庆市", 95526, 6740],
|
||||
// ["广州市", "江门市", 79764, 6810],
|
||||
// ["广州市", "茂名市", 330124, 16845],
|
||||
// ["广州市", "惠州市", 132169, 10257],
|
||||
// ["广州市", "梅州市", 375039, 23471],
|
||||
// ["广州市", "汕尾市", 250695, 22951],
|
||||
// ["深圳市", "珠海市", 96524, 6856],
|
||||
// ["深圳市", "汕头市", 327090, 16705],
|
||||
// ["深圳市", "佛山市", 131396, 7894],
|
||||
// ["深圳市", "韶关市", 314989, 14934],
|
||||
// ["深圳市", "湛江市", 458270, 17171],
|
||||
// ["深圳市", "肇庆市", 207397, 12600],
|
||||
// ["深圳市", "江门市", 117755, 7185],
|
||||
// ["深圳市", "茂名市", 379777, 14772],
|
||||
// ["深圳市", "惠州市", 85900, 8126],
|
||||
// ["深圳市", "梅州市", 333237, 19392],
|
||||
// ["深圳市", "汕尾市", 160776, 10851],
|
||||
// ["珠海市", "汕头市", 414804, 20789],
|
||||
// ["珠海市", "佛山市", 115634, 8714],
|
||||
// ["珠海市", "韶关市", 330794, 18384],
|
||||
// ["珠海市", "湛江市", 399887, 17975],
|
||||
// ["珠海市", "肇庆市", 170341, 10310],
|
||||
// ["珠海市", "江门市", 74683, 6582],
|
||||
// ["珠海市", "茂名市", 321394, 15731],
|
||||
// ["珠海市", "惠州市", 166006, 16927],
|
||||
// ["珠海市", "梅州市", 418175, 22730],
|
||||
// ["珠海市", "汕尾市", 248490, 14697],
|
||||
// ["汕头市", "佛山市", 428134, 21849],
|
||||
// ["汕头市", "韶关市", 439125, 28337],
|
||||
// ["汕头市", "湛江市", 777628, 31482],
|
||||
// ["汕头市", "肇庆市", 503503, 19173],
|
||||
// ["汕头市", "江门市", 437113, 21891],
|
||||
// ["汕头市", "茂名市", 699135, 28654],
|
||||
// ["汕头市", "惠州市", 281852, 13534],
|
||||
// ["汕头市", "梅州市", 150580, 10931],
|
||||
// ["汕头市", "汕尾市", 178082, 9358],
|
||||
// ["佛山市", "韶关市", 236498, 14079],
|
||||
// ["佛山市", "湛江市", 387931, 16579],
|
||||
// ["佛山市", "肇庆市", 83082, 6325],
|
||||
// ["佛山市", "江门市", 57582, 5669],
|
||||
// ["佛山市", "茂名市", 306652, 14224],
|
||||
// ["佛山市", "惠州市", 152376, 11891],
|
||||
// ["佛山市", "梅州市", 395246, 25040],
|
||||
// ["佛山市", "汕尾市", 270551, 21935],
|
||||
// ["韶关市", "湛江市", 597469, 29973],
|
||||
// ["韶关市", "肇庆市", 266051, 13269],
|
||||
// ["韶关市", "江门市", 297901, 15143],
|
||||
// ["韶关市", "茂名市", 512263, 25970],
|
||||
// ["韶关市", "惠州市", 241728, 12976],
|
||||
// ["韶关市", "梅州市", 351402, 17854],
|
||||
// ["韶关市", "汕尾市", 353072, 17957],
|
||||
// ["湛江市", "肇庆市", 337242, 17455],
|
||||
// ["湛江市", "江门市", 352004, 13773],
|
||||
// ["湛江市", "茂名市", 88410, 5807],
|
||||
// ["湛江市", "惠州市", 525222, 21359],
|
||||
// ["湛江市", "梅州市", 772108, 35020],
|
||||
// ["湛江市", "汕尾市", 611969, 26177],
|
||||
// ["肇庆市", "江门市", 96870, 6183],
|
||||
// ["肇庆市", "茂名市", 250335, 13916],
|
||||
// ["肇庆市", "惠州市", 227448, 15215],
|
||||
// ["肇庆市", "梅州市", 470318, 28141],
|
||||
// ["肇庆市", "汕尾市", 347288, 23119],
|
||||
// ["江门市", "茂名市", 273884, 11126],
|
||||
// ["江门市", "惠州市", 175884, 14701],
|
||||
// ["江门市", "梅州市", 425305, 22731],
|
||||
// ["江门市", "汕尾市", 271930, 18071],
|
||||
// ["茂名市", "惠州市", 446878, 18932],
|
||||
// ["茂名市", "梅州市", 693764, 33390],
|
||||
// ["茂名市", "汕尾市", 533625, 25063],
|
||||
// ["惠州市", "梅州市", 253720, 11521],
|
||||
// ["惠州市", "汕尾市", 121252, 8521],
|
||||
// ["梅州市", "汕尾市", 216441, 11933]
|
||||
// ]
|
||||
|
||||
let res: [string, string, number, number][] = await getRealData();
|
||||
Logger.i(JSON.stringify(res));
|
||||
|
||||
let cityName: string[] = CITY.slice();
|
||||
let cityInfo: CityInfo = {};
|
||||
cityName.forEach((city) => {
|
||||
cityInfo[city] = {};
|
||||
})
|
||||
res.forEach((urbanTransitionInfo) => {
|
||||
cityInfo[urbanTransitionInfo[0]][urbanTransitionInfo[1]] = { distance: urbanTransitionInfo[2], time: urbanTransitionInfo[3] };
|
||||
if (!cityInfo[urbanTransitionInfo[1]][urbanTransitionInfo[0]]) cityInfo[urbanTransitionInfo[1]][urbanTransitionInfo[0]] = { distance: urbanTransitionInfo[2], time: urbanTransitionInfo[3] };
|
||||
})
|
||||
|
||||
// console.log(Object.keys(cityInfo).toString())
|
||||
// for (const i of Object.keys(cityInfo)) {
|
||||
// console.log(Object.keys(cityInfo[i]).toString())
|
||||
// for (const j of Object.keys(cityInfo[i])) {
|
||||
// console.log(`${i}->${j}: 距离为${cityInfo[i][j].distance},时间为${cityInfo[i][j].time}`);
|
||||
// }
|
||||
// }
|
||||
return cityInfo;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { Graph, nodeInfo } from '../../common/dataStruct/interface/Graph';
|
||||
|
||||
export function deepCopyGraph(graph: Graph): Graph {
|
||||
const newGraph: Graph = {};
|
||||
|
||||
for (const nodeId of Object.keys(graph)) {
|
||||
// 深拷贝每个 nodeInfo 数组
|
||||
newGraph[nodeId] = graph[nodeId].map((node: nodeInfo) => {
|
||||
let nodeMessage: nodeInfo ={
|
||||
to: node.to,
|
||||
weight: node.weight
|
||||
};
|
||||
return nodeMessage;
|
||||
});
|
||||
}
|
||||
return newGraph;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export function degreesToRadians(degrees: number): number {
|
||||
return degrees * Math.PI / 180;
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { DRAW_NODE_SIZE, NORMAL_CONTEXT, SELECTED_CONTEXT } from '../../common/CommonConstant'
|
||||
import Edge from '../../common/dataStruct/class/Edge'
|
||||
import { cp } from '../algorithm/customPath'
|
||||
import { Logger } from '../Logger'
|
||||
|
||||
export let drawEdge = (edge:Edge,isSelectedPath:boolean)=>{
|
||||
let context = isSelectedPath?SELECTED_CONTEXT:NORMAL_CONTEXT
|
||||
|
||||
if(isSelectedPath){
|
||||
if(edge.marked){
|
||||
context.moveTo(cp.nodes[edge.node1].coordinates[0] + DRAW_NODE_SIZE/2,cp.nodes[edge.node1].coordinates[1] + DRAW_NODE_SIZE/2)
|
||||
context.lineTo(cp.nodes[edge.node2].coordinates[0] + DRAW_NODE_SIZE/2,cp.nodes[edge.node2].coordinates[1] + DRAW_NODE_SIZE/2)
|
||||
context.stroke()
|
||||
}
|
||||
}else{
|
||||
if(edge.visible){
|
||||
context.moveTo(cp.nodes[edge.node1].coordinates[0] + DRAW_NODE_SIZE/2,cp.nodes[edge.node1].coordinates[1] + DRAW_NODE_SIZE/2)
|
||||
context.lineTo(cp.nodes[edge.node2].coordinates[0] + DRAW_NODE_SIZE/2,cp.nodes[edge.node2].coordinates[1] + DRAW_NODE_SIZE/2)
|
||||
context.stroke()
|
||||
}
|
||||
}
|
||||
|
||||
context.stroke()
|
||||
}
|
||||
export let drawEdgeList = (edgeList:Array<Edge>,isSelectedPath:boolean)=>{
|
||||
if(edgeList.length == 0){
|
||||
return
|
||||
}
|
||||
for(let index = 0;index < edgeList.length;index++){
|
||||
drawEdge(edgeList[index],isSelectedPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import Node from '../../common/dataStruct/class/Node'
|
||||
import { BOTTOM_RIGHT_X, BOTTOM_RIGHT_Y, DRAW_NODE_SIZE, TOP_LEFT_X, TOP_LEFT_Y,
|
||||
X_CENTER,
|
||||
Y_CENTER
|
||||
} from '../../common/CommonConstant'
|
||||
|
||||
export let drawNode = (node:Node,context:CanvasRenderingContext2D)=>{
|
||||
|
||||
|
||||
if(!node.mustVisit){
|
||||
context.fillRect(node.coordinates[0],node.coordinates[1],DRAW_NODE_SIZE,DRAW_NODE_SIZE)
|
||||
}else{
|
||||
context.strokeRect(node.coordinates[0],node.coordinates[1],DRAW_NODE_SIZE,DRAW_NODE_SIZE)
|
||||
}
|
||||
let bias = getTextBiasValue(node)
|
||||
context.fillText(node.label,node.coordinates[0] + DRAW_NODE_SIZE/2 + bias[0],node.coordinates[1] + DRAW_NODE_SIZE/2 + bias[1])
|
||||
|
||||
context.stroke()
|
||||
}
|
||||
|
||||
export let drawNodeList = (nodeList:Array<Node>,context:CanvasRenderingContext2D)=>{
|
||||
if(nodeList.length == 0){
|
||||
return
|
||||
}
|
||||
for (let index = 0; index < nodeList.length; index++) {
|
||||
drawNode(nodeList[index],context)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let getTextBiasValue = (node:Node)=>{
|
||||
let bias:[number,number] = [0,0]
|
||||
if(node.coordinates[0] < X_CENTER){
|
||||
bias[0] = -40
|
||||
}
|
||||
if(node.coordinates[1] > Y_CENTER){
|
||||
bias[1] = 25
|
||||
}
|
||||
return bias
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
import { CITY, KEY } from '../../common/CommonConstant'
|
||||
import http from '@ohos.net.http'
|
||||
import { Logger } from '../Logger'
|
||||
import fs from '@ohos.file.fs';
|
||||
import picker from '@ohos.file.picker';
|
||||
import { BusinessError } from '@ohos.base';
|
||||
import process from '@ohos.process';
|
||||
|
||||
import { showToast } from './showToast';
|
||||
import { Data } from '../../common/dataStruct/class/Data';
|
||||
|
||||
export let getRealData = async ()=>{
|
||||
let res:Array<[string,string,number,number]> = []
|
||||
let loop:boolean = AppStorage.get("isOpFile") as boolean
|
||||
if(loop){
|
||||
res = await getRealDataFromFile()
|
||||
}else{
|
||||
let str = ""
|
||||
for(let i = 0;i < CITY.length;i++){
|
||||
for(let j = i + 1;j < CITY.length;j++){
|
||||
let tmp = await getRealDataFromMap(CITY[i],CITY[j])
|
||||
res.push(tmp)
|
||||
str += `${tmp[0]} ${tmp[1]} ${tmp[2]} ${tmp[3]}`
|
||||
str += '\n'
|
||||
|
||||
}
|
||||
}
|
||||
await writeDocument(str)
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从地图获取真实数据
|
||||
*/
|
||||
let getRealDataFromMap = async (cityName1:string,cityName2:string)=>{
|
||||
|
||||
let res:[string,string,number,number] = ["","",0,0]
|
||||
//获取经纬度
|
||||
let getLocation1_url = `https://restapi.amap.com/v3/geocode/geo?address=${cityName1}&key=${KEY}`
|
||||
let getLocation2_url = `https://restapi.amap.com/v3/geocode/geo?address=${cityName2}&key=${KEY}`
|
||||
let loc1 = await getLoc(getLocation1_url)
|
||||
let loc2 = await getLoc(getLocation2_url)
|
||||
//获取最短路径
|
||||
let getPath_url = `https://restapi.amap.com/v3/direction/driving?origin=${loc1}&destination=${loc2}&strategy=2&key=${KEY}`
|
||||
let path = await getPath(getPath_url)
|
||||
|
||||
res[0] = cityName1
|
||||
res[1] = cityName2
|
||||
res[2] = path[0]
|
||||
res[3] = path[1]
|
||||
Logger.i(JSON.stringify(res))
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件获取真实数据
|
||||
*/
|
||||
let getRealDataFromFile = async ()=>{
|
||||
let dataList:Array<[string,string,number,number]> = []
|
||||
//读取文件
|
||||
let res = await readDocument(dataList)
|
||||
if(!res){
|
||||
setTimeout(()=>{
|
||||
showToast("数据初始化异常,将自动关闭程序")
|
||||
setTimeout(()=>{
|
||||
let pro = new process.ProcessManager();
|
||||
pro.exit(0);
|
||||
},2000)
|
||||
},2000)
|
||||
}
|
||||
return dataList
|
||||
}
|
||||
|
||||
let getLoc = async (url:string)=>{
|
||||
try {
|
||||
let h = http.createHttp()
|
||||
let res = await h.request(url)
|
||||
|
||||
//JSON
|
||||
let json = res.result as string
|
||||
let i = json.indexOf("location")
|
||||
let loc = json.slice(i + 11,i+31)
|
||||
return loc;
|
||||
} catch (e) {
|
||||
Logger.e(JSON.stringify(e))
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
let getPath = async (url:string)=>{
|
||||
let h = http.createHttp()
|
||||
let res = await h.request(url)
|
||||
//JSON
|
||||
let json = res.result as string
|
||||
let obj:Data = JSON.parse(json)
|
||||
let loc:[number,number] = [Number(obj.route.paths[0].distance),Number(obj.route.paths[0].duration)]
|
||||
return loc;
|
||||
}
|
||||
|
||||
let writeDocument = async (str:string)=>{
|
||||
let documentPicker = new picker.DocumentViewPicker();
|
||||
let uris:Array<string> = []
|
||||
let DocumentSaveOptions = new picker.DocumentSaveOptions();
|
||||
DocumentSaveOptions.newFileNames = ['cityData.txt']
|
||||
uris = await documentPicker.save(DocumentSaveOptions)
|
||||
let uri = uris[0]
|
||||
let file = fs.openSync(uri, fs.OpenMode.WRITE_ONLY);
|
||||
fs.writeSync(file.fd,"THIS IS A TAG\n")
|
||||
let writeStr = str
|
||||
fs.writeSync(file.fd, writeStr);
|
||||
fs.closeSync(file);
|
||||
}
|
||||
|
||||
let readDocument = async (dataList:Array<[string,string,number,number]>)=>{
|
||||
let documentPicker = new picker.DocumentViewPicker()
|
||||
let DocumentSelectOptions = new picker.DocumentSelectOptions();
|
||||
let DocumentSelectResult: Array<string> = await documentPicker.select(DocumentSelectOptions)
|
||||
if(DocumentSelectResult.length == 0){
|
||||
showToast("您未选择任何文件!将无法获取到数据")
|
||||
return false
|
||||
}
|
||||
let uri = DocumentSelectResult[0]
|
||||
let file = fs.openSync(uri,fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
|
||||
//已经获取到选择的文件 进行读取操作
|
||||
let arrayBuffer = new ArrayBuffer(8)
|
||||
let size = 0
|
||||
let dSize = 0
|
||||
let str = fs.readTextSync(file.path)
|
||||
//解析路径
|
||||
fs.closeSync(file)
|
||||
|
||||
//解析文件样本点
|
||||
let arr = str.split('\n')
|
||||
if(arr[0] != "THIS IS A TAG"){
|
||||
showToast("读取文件失败,请确认文件是否选择正确或是否损坏")
|
||||
return false
|
||||
}
|
||||
for (let index = 1; index < arr.length - 1; index++) {
|
||||
let res = arr[index].split(' ')
|
||||
|
||||
let item:[string,string,number,number] = [res[0],res[1],Number(res[2]),Number(res[3])]
|
||||
dataList.push(item)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import promptAction from '@ohos.promptAction'
|
||||
|
||||
export let showToast = (message:string,duration:number = 2000)=>{
|
||||
promptAction.showToast({
|
||||
message:message,
|
||||
duration:duration
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"module": {
|
||||
"name": "entry",
|
||||
"type": "entry",
|
||||
"description": "$string:module_desc",
|
||||
"mainElement": "EntryAbility",
|
||||
"requestPermissions": [
|
||||
{
|
||||
"name": "ohos.permission.INTERNET"
|
||||
},
|
||||
{
|
||||
"name": "ohos.permission.FILE_ACCESS_MANAGER"
|
||||
},
|
||||
{
|
||||
"name": "ohos.permission.GET_BUNDLE_INFO_PRIVILEGED"
|
||||
}
|
||||
],
|
||||
"deviceTypes": [
|
||||
"default",
|
||||
"tablet"
|
||||
],
|
||||
"deliveryWithInstall": true,
|
||||
"installationFree": false,
|
||||
"pages": "$profile:main_pages",
|
||||
"abilities": [
|
||||
{
|
||||
"name": "EntryAbility",
|
||||
"srcEntry": "./ets/entryability/EntryAbility.ets",
|
||||
"description": "$string:EntryAbility_desc",
|
||||
"icon": "$media:icon",
|
||||
"label": "$string:EntryAbility_label",
|
||||
"startWindowIcon": "$media:startIcon",
|
||||
"startWindowBackground": "$color:start_window_background",
|
||||
"exported": true,
|
||||
"orientation": "landscape",
|
||||
"skills": [
|
||||
{
|
||||
"entities": [
|
||||
"entity.system.home"
|
||||
],
|
||||
"actions": [
|
||||
"action.system.home"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"color": [
|
||||
{
|
||||
"name": "start_window_background",
|
||||
"value": "#FFFFFF"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "module_desc",
|
||||
"value": "module description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_desc",
|
||||
"value": "description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_label",
|
||||
"value": "label"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"src": [
|
||||
"pages/Index"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "module_desc",
|
||||
"value": "module description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_desc",
|
||||
"value": "description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_label",
|
||||
"value": "label"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "module_desc",
|
||||
"value": "模块描述"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_desc",
|
||||
"value": "description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_label",
|
||||
"value": "label"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import hilog from '@ohos.hilog';
|
||||
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
|
||||
|
||||
export default function abilityTest() {
|
||||
describe('ActsAbilityTest', () => {
|
||||
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
|
||||
beforeAll(() => {
|
||||
// Presets an action, which is performed only once before all test cases of the test suite start.
|
||||
// This API supports only one parameter: preset action function.
|
||||
})
|
||||
beforeEach(() => {
|
||||
// Presets an action, which is performed before each unit test case starts.
|
||||
// The number of execution times is the same as the number of test cases defined by **it**.
|
||||
// This API supports only one parameter: preset action function.
|
||||
})
|
||||
afterEach(() => {
|
||||
// Presets a clear action, which is performed after each unit test case ends.
|
||||
// The number of execution times is the same as the number of test cases defined by **it**.
|
||||
// This API supports only one parameter: clear action function.
|
||||
})
|
||||
afterAll(() => {
|
||||
// Presets a clear action, which is performed after all test cases of the test suite end.
|
||||
// This API supports only one parameter: clear action function.
|
||||
})
|
||||
it('assertContain', 0, () => {
|
||||
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');
|
||||
let a = 'abc';
|
||||
let b = 'b';
|
||||
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
|
||||
expect(a).assertContain(b);
|
||||
expect(a).assertEqual(a);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import abilityTest from './Ability.test';
|
||||
|
||||
export default function testsuite() {
|
||||
abilityTest();
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import UIAbility from '@ohos.app.ability.UIAbility';
|
||||
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
|
||||
import hilog from '@ohos.hilog';
|
||||
import { Hypium } from '@ohos/hypium';
|
||||
import testsuite from '../test/List.test';
|
||||
import window from '@ohos.window';
|
||||
import Want from '@ohos.app.ability.Want';
|
||||
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
|
||||
|
||||
export default class TestAbility extends UIAbility {
|
||||
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate');
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? '');
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:' + JSON.stringify(launchParam) ?? '');
|
||||
let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator;
|
||||
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
|
||||
let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs;
|
||||
abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments();
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!');
|
||||
Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite);
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy');
|
||||
}
|
||||
|
||||
onWindowStageCreate(windowStage: window.WindowStage) {
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate');
|
||||
windowStage.loadContent('testability/pages/Index', (err, data) => {
|
||||
if (err.code) {
|
||||
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
|
||||
return;
|
||||
}
|
||||
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s',
|
||||
JSON.stringify(data) ?? '');
|
||||
});
|
||||
}
|
||||
|
||||
onWindowStageDestroy() {
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy');
|
||||
}
|
||||
|
||||
onForeground() {
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground');
|
||||
}
|
||||
|
||||
onBackground() {
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
@Entry
|
||||
@Component
|
||||
struct Index {
|
||||
@State message: string = 'Hello World';
|
||||
|
||||
build() {
|
||||
Row() {
|
||||
Column() {
|
||||
Text(this.message)
|
||||
.fontSize(50)
|
||||
.fontWeight(FontWeight.Bold)
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
.height('100%')
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import hilog from '@ohos.hilog';
|
||||
import TestRunner from '@ohos.application.testRunner';
|
||||
import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
|
||||
import Want from '@ohos.app.ability.Want';
|
||||
|
||||
let abilityDelegator: AbilityDelegatorRegistry.AbilityDelegator | undefined = undefined
|
||||
let abilityDelegatorArguments: AbilityDelegatorRegistry.AbilityDelegatorArgs | undefined = undefined
|
||||
|
||||
async function onAbilityCreateCallback() {
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback');
|
||||
}
|
||||
|
||||
async function addAbilityMonitorCallback(err : Error) {
|
||||
hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? '');
|
||||
}
|
||||
|
||||
export default class OpenHarmonyTestRunner implements TestRunner {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
onPrepare() {
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare ');
|
||||
}
|
||||
|
||||
async onRun() {
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run');
|
||||
abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments()
|
||||
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator()
|
||||
const bundleName = abilityDelegatorArguments.bundleName;
|
||||
const testAbilityName = 'TestAbility';
|
||||
let lMonitor: AbilityDelegatorRegistry.AbilityMonitor = {
|
||||
abilityName: testAbilityName,
|
||||
onAbilityCreate: onAbilityCreateCallback,
|
||||
};
|
||||
abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback)
|
||||
const want: Want = {
|
||||
bundleName: bundleName,
|
||||
abilityName: testAbilityName
|
||||
};
|
||||
abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator();
|
||||
abilityDelegator.startAbility(want, (err, data) => {
|
||||
hilog.info(0x0000, 'testTag', 'startAbility : err : %{public}s', JSON.stringify(err) ?? '');
|
||||
hilog.info(0x0000, 'testTag', 'startAbility : data : %{public}s',JSON.stringify(data) ?? '');
|
||||
})
|
||||
hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"module": {
|
||||
"name": "entry_test",
|
||||
"type": "feature",
|
||||
"description": "$string:module_test_desc",
|
||||
"mainElement": "TestAbility",
|
||||
"deviceTypes": [
|
||||
"default",
|
||||
"tablet"
|
||||
],
|
||||
"deliveryWithInstall": true,
|
||||
"installationFree": false,
|
||||
"pages": "$profile:test_pages",
|
||||
"abilities": [
|
||||
{
|
||||
"name": "TestAbility",
|
||||
"srcEntry": "./ets/testability/TestAbility.ets",
|
||||
"description": "$string:TestAbility_desc",
|
||||
"icon": "$media:icon",
|
||||
"label": "$string:TestAbility_label",
|
||||
"exported": true,
|
||||
"startWindowIcon": "$media:icon",
|
||||
"startWindowBackground": "$color:start_window_background",
|
||||
"skills": [
|
||||
{
|
||||
"actions": [
|
||||
"action.system.home"
|
||||
],
|
||||
"entities": [
|
||||
"entity.system.home"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"color": [
|
||||
{
|
||||
"name": "start_window_background",
|
||||
"value": "#FFFFFF"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "module_test_desc",
|
||||
"value": "test ability description"
|
||||
},
|
||||
{
|
||||
"name": "TestAbility_desc",
|
||||
"value": "the test ability"
|
||||
},
|
||||
{
|
||||
"name": "TestAbility_label",
|
||||
"value": "test label"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.6 KiB |
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"src": [
|
||||
"testability/pages/Index"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import localUnitTest from './LocalUnit.test';
|
||||
|
||||
export default function testsuite() {
|
||||
localUnitTest();
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
|
||||
|
||||
export default function localUnitTest() {
|
||||
describe('localUnitTest',() => {
|
||||
// Defines a test suite. Two parameters are supported: test suite name and test suite function.
|
||||
beforeAll(() => {
|
||||
// Presets an action, which is performed only once before all test cases of the test suite start.
|
||||
// This API supports only one parameter: preset action function.
|
||||
});
|
||||
beforeEach(() => {
|
||||
// Presets an action, which is performed before each unit test case starts.
|
||||
// The number of execution times is the same as the number of test cases defined by **it**.
|
||||
// This API supports only one parameter: preset action function.
|
||||
});
|
||||
afterEach(() => {
|
||||
// Presets a clear action, which is performed after each unit test case ends.
|
||||
// The number of execution times is the same as the number of test cases defined by **it**.
|
||||
// This API supports only one parameter: clear action function.
|
||||
});
|
||||
afterAll(() => {
|
||||
// Presets a clear action, which is performed after all test cases of the test suite end.
|
||||
// This API supports only one parameter: clear action function.
|
||||
});
|
||||
it('assertContain', 0, () => {
|
||||
// Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
|
||||
let a = 'abc';
|
||||
let b = 'b';
|
||||
// Defines a variety of assertion methods, which are used to declare expected boolean conditions.
|
||||
expect(a).assertContain(b);
|
||||
expect(a).assertEqual(a);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"hvigorVersion": "3.0.9",
|
||||
"dependencies": {
|
||||
"@ohos/hvigor-ohos-plugin": "3.0.9"
|
||||
},
|
||||
"execution": {
|
||||
// "daemon": true, /* Enable daemon compilation. Default: true */
|
||||
// "incremental": true, /* Enable incremental compilation. Default: true */
|
||||
// "parallel": true, /* Enable parallel compilation. Default: true */
|
||||
// "typeCheck": false, /* Enable typeCheck. Default: false */
|
||||
},
|
||||
"logging": {
|
||||
// "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
|
||||
},
|
||||
"debugging": {
|
||||
// "stacktrace": false /* Disable stacktrace compilation. Default: false */
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { appTasks } from '@ohos/hvigor-ohos-plugin';
|
||||
|
||||
export default {
|
||||
system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
|
||||
plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#!/bin/bash
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Hvigor startup script, version 1.0.0
|
||||
#
|
||||
# Required ENV vars:
|
||||
# ------------------
|
||||
# NODE_HOME - location of a Node home dir
|
||||
# or
|
||||
# Add /usr/local/nodejs/bin to the PATH environment variable
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
HVIGOR_APP_HOME="`pwd -P`"
|
||||
HVIGOR_WRAPPER_SCRIPT=${HVIGOR_APP_HOME}/hvigor/hvigor-wrapper.js
|
||||
warn() {
|
||||
echo ""
|
||||
echo -e "\033[1;33m`date '+[%Y-%m-%d %H:%M:%S]'`$@\033[0m"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo ""
|
||||
echo -e "\033[1;31m`date '+[%Y-%m-%d %H:%M:%S]'`$@\033[0m"
|
||||
}
|
||||
|
||||
fail() {
|
||||
error "$@"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Determine node to start hvigor wrapper script
|
||||
if [ -n "${NODE_HOME}" ];then
|
||||
EXECUTABLE_NODE="${NODE_HOME}/bin/node"
|
||||
if [ ! -x "$EXECUTABLE_NODE" ];then
|
||||
fail "ERROR: NODE_HOME is set to an invalid directory,check $NODE_HOME\n\nPlease set NODE_HOME in your environment to the location where your nodejs installed"
|
||||
fi
|
||||
else
|
||||
EXECUTABLE_NODE="node"
|
||||
which ${EXECUTABLE_NODE} > /dev/null 2>&1 || fail "ERROR: NODE_HOME is not set and not 'node' command found in your path"
|
||||
fi
|
||||
|
||||
# Check hvigor wrapper script
|
||||
if [ ! -r "$HVIGOR_WRAPPER_SCRIPT" ];then
|
||||
fail "ERROR: Couldn't find hvigor/hvigor-wrapper.js in ${HVIGOR_APP_HOME}"
|
||||
fi
|
||||
|
||||
# start hvigor-wrapper script
|
||||
exec "${EXECUTABLE_NODE}" \
|
||||
"${HVIGOR_WRAPPER_SCRIPT}" "$@"
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Hvigor startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
set WRAPPER_MODULE_PATH=%APP_HOME%\hvigor\hvigor-wrapper.js
|
||||
set NODE_EXE=node.exe
|
||||
|
||||
goto start
|
||||
|
||||
:start
|
||||
@rem Find node.exe
|
||||
if defined NODE_HOME goto findNodeFromNodeHome
|
||||
|
||||
%NODE_EXE% --version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: NODE_HOME is not set and no 'node' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the NODE_HOME variable in your environment to match the
|
||||
echo location of your NodeJs installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findNodeFromNodeHome
|
||||
set NODE_HOME=%NODE_HOME:"=%
|
||||
set NODE_EXE_PATH=%NODE_HOME%/%NODE_EXE%
|
||||
|
||||
if exist "%NODE_EXE_PATH%" goto execute
|
||||
echo.
|
||||
echo ERROR: NODE_HOME is not set and no 'node' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the NODE_HOME variable in your environment to match the
|
||||
echo location of your NodeJs installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Execute hvigor
|
||||
"%NODE_EXE%" "%WRAPPER_MODULE_PATH%" %*
|
||||
|
||||
if "%ERRORLEVEL%" == "0" goto hvigorwEnd
|
||||
|
||||
:fail
|
||||
exit /b 1
|
||||
|
||||
:hvigorwEnd
|
||||
if "%OS%" == "Windows_NT" endlocal
|
||||
|
||||
:end
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"lockfileVersion": 1,
|
||||
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
|
||||
"specifiers": {
|
||||
"@ohos/hypium@1.0.11": "@ohos/hypium@1.0.11"
|
||||
},
|
||||
"packages": {
|
||||
"@ohos/hypium@1.0.11": {
|
||||
"resolved": "https://repo.harmonyos.com/ohpm/@ohos/hypium/-/hypium-1.0.11.tgz",
|
||||
"integrity": "sha512-KawcLnv43C3QIYv1UbDnKCFX3MohtDxGuFvzlUxT/qf2DBilR56Ws6zrj90LdH6PjloJQwOPESuBQIHBACAK7w=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"license": "",
|
||||
"devDependencies": {
|
||||
"@ohos/hypium": "1.0.11"
|
||||
},
|
||||
"author": "",
|
||||
"name": "drawpart3",
|
||||
"description": "Please describe the basic information.",
|
||||
"main": "",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {}
|
||||
}
|
||||
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 61 KiB |