fastapi +angular迷宫求解可跨域

说明:我计划使用fastapi +angular,实现​迷宫路径生成与求解

后端功能包括:

1.FastAPI搭建RESTful接口。写两个接口,

1.1生成迷宫,

1.2求解路径

前端功能包括

1.根据给定的长宽值,生成迷宫

2.点击按钮,刷新迷宫,生成新迷宫

3.点击按钮,可以给出正确的迷宫路径

4.增加开始按钮 和计时功能

5.鼠标拖拽绘制迷宫路径

6.绘制错误,弹窗提示失败,重新开始

7.拖拽绘制迷宫成功,弹窗提示成功,并给出分数

效果图:

step1:C:\Users\wangrusheng\PycharmProjects\FastAPIProject\main.py

python 复制代码
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List
import random
from collections import deque

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:4200"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


class MazeRequest(BaseModel):
    width: int
    height: int


class SolveRequest(BaseModel):
    maze: List[List[int]]
    start: List[int]
    end: List[int]


def generate_maze_dfs(width: int, height: int) -> List[List[int]]:
    # 确保奇数列和行
    width = width // 2 * 2 + 1
    height = height // 2 * 2 + 1

    maze = [[1 for _ in range(width)] for _ in range(height)]
    stack = [(0, 0)]
    maze[0][0] = 0

    directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]

    while stack:
        current = stack[-1]
        neighbors = []

        for dx, dy in directions:
            nx = current[0] + dx * 2
            ny = current[1] + dy * 2

            if 0 <= nx < height and 0 <= ny < width and maze[nx][ny] == 1:
                neighbors.append((nx, ny))

        if neighbors:
            next_cell = random.choice(neighbors)
            mx = current[0] + (next_cell[0] - current[0]) // 2
            my = current[1] + (next_cell[1] - current[1]) // 2

            maze[mx][my] = 0
            maze[next_cell[0]][next_cell[1]] = 0
            stack.append(next_cell)
        else:
            stack.pop()

    return maze


def solve_bfs(maze: List[List[int]], start: tuple, end: tuple) -> List[List[int]]:
    rows = len(maze)
    cols = len(maze[0]) if rows > 0 else 0
    visited = [[False for _ in range(cols)] for _ in range(rows)]
    queue = deque([(start[0], start[1], [])])
    directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]

    while queue:
        x, y, path = queue.popleft()
        current_path = path + [[x, y]]

        if (x, y) == end:
            return current_path

        for dx, dy in directions:
            nx, ny = x + dx, y + dy
            if 0 <= nx < rows and 0 <= ny < cols:
                if maze[nx][ny] == 0 and not visited[nx][ny]:
                    visited[nx][ny] = True
                    queue.append((nx, ny, current_path))

    return []


@app.post("/generate-maze/")
async def generate_maze(request: MazeRequest):
    try:
        maze = generate_maze_dfs(request.width, request.height)
        return {
            "maze": maze,
            "start": [0, 0],
            "end": [len(maze) - 1, len(maze[0]) - 1]
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/solve-maze/")
async def solve_maze(request: SolveRequest):
    try:
        path = solve_bfs(request.maze, tuple(request.start), tuple(request.end))
        return {"path": path}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8000)

step2:后端部分写完了,一定要记得去postman里面测试,确保后端接口正确,然后再写前端,

这种能正常请求返回json就表示接口没问题,避免500,404,或者其他error问题

bash 复制代码
post请求:http://127.0.0.1:8000/solve-maze/

{
    "detail": [
        {
            "type": "missing",
            "loc": [
                "body"
            ],
            "msg": "Field required",
            "input": null
        }
    ]
}

post请求:http://127.0.0.1:8000/generate-maze/

{
    "detail": [
        {
            "type": "missing",
            "loc": [
                "body"
            ],
            "msg": "Field required",
            "input": null
        }
    ]
}

step3:后端部分写完了,postman测试完成,接下来写前端angular

C:\Users\wangrusheng\WebstormProjects\untitled4\src\app\maze\maze.service.ts

javascript 复制代码
// maze.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class MazeService {
  private apiUrl = 'http://localhost:8000';

  constructor(private http: HttpClient) { }

  generateMaze(width: number, height: number): Observable<any> {
    return this.http.post(`${this.apiUrl}/generate-maze/`, { width, height });
  }

  solveMaze(maze: number[][], start: number[], end: number[]): Observable<any> {
    return this.http.post(`${this.apiUrl}/solve-maze/`, { maze, start, end });
  }
}

step4: C:\Users\wangrusheng\WebstormProjects\untitled4\src\app\maze\maze.component.ts

javascript 复制代码
// maze.component.ts
import { Component, OnInit } from '@angular/core';
import { MazeService } from './maze.service';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-maze',
  standalone: true,
  imports: [CommonModule, FormsModule],
  templateUrl: './maze.component.html',
  styleUrls: ['./maze.component.css']
})
export class MazeComponent implements OnInit {
  maze: number[][] = [];
  path: number[][] = [];
  width = 15;
  height = 15;
  start: number[] = [0, 0];
  end: number[] = [0, 0];
  isDrawing = false;
  currentPath: number[][] = [];
  gameStarted = false;
  startTime = 0;
  elapsedTime = 0;
  score = 0;
  showSuccess = false;
  showError = false;
  errorMessage = '';
  private timerInterval: any;

  constructor(private mazeService: MazeService) {}

  ngOnInit() {
    this.generateNewMaze();
  }

  startGame() {
    this.gameStarted = true;
    this.startTime = Date.now();
    this.timerInterval = setInterval(() => {
      this.elapsedTime = Math.floor((Date.now() - this.startTime) / 1000);
    }, 1000);
  }

  restartGame() {
    clearInterval(this.timerInterval);
    this.gameStarted = false;
    this.elapsedTime = 0;
    this.showSuccess = false;
    this.showError = false;
    this.generateNewMaze();
  }

  generateNewMaze() {
    this.mazeService.generateMaze(this.width, this.height).subscribe({
      next: (res) => {
        this.maze = res.maze;
        this.start = res.start;
        this.end = res.end;
        this.path = [];
        this.currentPath = [];
      },
      error: (err) => console.error(err)
    });
  }

  solveMaze() {
    this.mazeService.solveMaze(this.maze, this.start, this.end).subscribe({
      next: (res) => {
        this.path = res.path;
      },
      error: (err) => console.error(err)
    });
  }

  startDrawing(event: MouseEvent) {
    if (!this.gameStarted) return;

    const cell = this.getCellFromEvent(event);
    if (cell && this.isValidStart(cell)) {
      this.isDrawing = true;
      this.currentPath = [cell];
    }
  }

  draw(event: MouseEvent) {
    if (!this.isDrawing) return;

    const cell = this.getCellFromEvent(event);
    if (cell && this.isValidMove(cell)) {
      this.currentPath = [...this.currentPath, cell];
    }
  }

  stopDrawing() {
    if (!this.isDrawing) return;

    this.isDrawing = false;
    this.verifyPath();
  }

  private verifyPath() {
    const isValid = this.checkPathValidity();

    if (isValid) {
      this.score = Math.round((this.width * this.height * 100) / this.elapsedTime);
      this.showSuccess = true;
      clearInterval(this.timerInterval);
    } else {
      this.errorMessage = 'Invalid path or not reaching the end';
      this.showError = true;
    }
  }

  private checkPathValidity(): boolean {
    // Check if path starts at start point
    if (!this.arePointsEqual(this.currentPath[0], this.start)) return false;

    // Check if path ends at end point
    if (!this.arePointsEqual(this.currentPath[this.currentPath.length - 1], this.end)) return false;

    // Check path continuity and validity
    for (let i = 1; i < this.currentPath.length; i++) {
      const prev = this.currentPath[i - 1];
      const curr = this.currentPath[i];

      // Check if adjacent cells
      if (Math.abs(prev[0] - curr[0]) + Math.abs(prev[1] - curr[1]) !== 1) return false;

      // Check if path is on valid cells
      if (this.maze[curr[0]][curr[1]] === 1) return false;
    }
    return true;
  }

  private getCellFromEvent(event: MouseEvent): number[] | null {
    const target = event.target as HTMLElement;
    const row = target.parentElement?.parentElement?.children;
    const cell = target.parentElement?.children;

    if (!row || !cell) return null;

    const i = Array.from(row).indexOf(target.parentElement!);
    const j = Array.from(cell).indexOf(target);

    return i >= 0 && j >= 0 ? [i, j] : null;
  }

  private isValidStart(cell: number[]): boolean {
    return this.arePointsEqual(cell, this.start);
  }

  private isValidMove(cell: number[]): boolean {
    const lastCell = this.currentPath[this.currentPath.length - 1];
    return this.maze[cell[0]][cell[1]] === 0 &&
      !this.currentPath.some(p => this.arePointsEqual(p, cell)) &&
      Math.abs(lastCell[0] - cell[0]) + Math.abs(lastCell[1] - cell[1]) === 1;
  }

  private arePointsEqual(a: number[], b: number[]): boolean {
    return a[0] === b[0] && a[1] === b[1];
  }

  isPath(i: number, j: number): boolean {
    return this.path.some(p => p[0] === i && p[1] === j);
  }

  isDrawingPath(i: number, j: number): boolean {
    return this.currentPath.some(p => p[0] === i && p[1] === j);
  }
}

step5:

C:\Users\wangrusheng\WebstormProjects\untitled4\src\app\maze\maze.component.html

xml 复制代码
<div class="controls">
  <input type="number" [(ngModel)]="width" placeholder="Width" [disabled]="gameStarted">
  <input type="number" [(ngModel)]="height" placeholder="Height" [disabled]="gameStarted">
  <button (click)="generateNewMaze()" [disabled]="gameStarted">Generate Maze</button>
  <button (click)="solveMaze()" [disabled]="!maze.length">Solve Maze</button>
  <button (click)="startGame()" [disabled]="!maze.length || gameStarted">Start Game</button>
  <div class="timer">Time: {{ elapsedTime }}s</div>
</div>

<div class="maze-container"
     (mousedown)="startDrawing($event)"
     (mousemove)="draw($event)"
     (mouseup)="stopDrawing()"
     (mouseleave)="stopDrawing()">
  <div *ngFor="let row of maze; let i = index" class="row">
    <div
      *ngFor="let cell of row; let j = index"
      class="cell"
      [class.wall]="cell === 1"
      [class.path]="isPath(i, j)"
      [class.start]="i === start[0] && j === start[1]"
      [class.end]="i === end[0] && j === end[1]"
      [class.drawing]="isDrawingPath(i, j)">
    </div>
  </div>
</div>

<div class="overlay" *ngIf="showSuccess || showError">
  <div class="dialog">
    <div *ngIf="showSuccess">
      <h3>Success!</h3>
      <p>Time: {{ elapsedTime }}s</p>
      <p>Score: {{ score }}</p>
      <button (click)="restartGame()">Play Again</button>
    </div>
    <div *ngIf="showError">
      <h3>Wrong Path!</h3>
      <p>{{ errorMessage }}</p>
      <button (click)="restartGame()">Try Again</button>
    </div>
  </div>
</div>

step6: C:\Users\wangrusheng\WebstormProjects\untitled4\src\app\maze\maze.component.css

css 复制代码
/* 控件区域 */
.controls {
  margin-bottom: 20px;
  display: flex;
  gap: 10px;
  align-items: center;
}

/* 迷宫容器 */
.maze-container {
  border: 1px solid #ccc;
  user-select: none;
}

/* 行布局 */
.row {
  display: flex;
}

/* 单元格样式 */
.cell {
  width: 20px;
  height: 20px;
  border: 1px solid #eee;
  position: relative;
  cursor: crosshair;
}

/* 墙壁样式 */
.wall {
  background-color: #333;
  cursor: not-allowed;
}

/* 路径样式 */
.path {
  background-color: #ffd700;
}

/* 起点样式 */
.start {
  background-color: #00ff00;
}

/* 终点样式 */
.end {
  background-color: #ff0000;
}

/* 绘制中的路径 */
.drawing {
  background-color: #4a90e2 !important;
}

/* 计时器 */
.timer {
  font-weight: bold;
  color: #2c3e50;
}

/* 遮罩层 */
.overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0,0,0,0.5);
  display: flex;
  justify-content: center;
  align-items: center;
}

/* 对话框 */
.dialog {
  background: white;
  padding: 20px;
  border-radius: 8px;
  text-align: center;
}

end

相关推荐
计算机毕设定制辅导-无忧学长27 分钟前
HTML 新手入门:从零基础到搭建第一个静态页面(二)
前端·javascript·html
luckyext1 小时前
Postman用JSON格式数据发送POST请求及注意事项
java·前端·后端·测试工具·c#·json·postman
烛阴2 小时前
JavaScript 函数对象与 NFE:你必须知道的秘密武器!
前端·javascript
px52133442 小时前
Solder leakage problems and improvement strategies in electronics manufacturing
java·前端·数据库·pcb工艺
eli9602 小时前
node-ddk,electron 开发组件
前端·javascript·electron·node.js·js
全宝2 小时前
🔥一个有质感的拟态开关
前端·css·weui
老K(郭云开)2 小时前
最新版Chrome浏览器加载ActiveX控件技术--allWebPlugin中间件一键部署浏览器扩展
前端·javascript·chrome·中间件·edge
老K(郭云开)2 小时前
allWebPlugin中间件自动适应Web系统多层iframe嵌套
前端·javascript·chrome·中间件
银之夏雪3 小时前
Vue 3 vs Vue 2:深入解析从性能优化到源码层面的进化
前端·vue.js·性能优化
还是鼠鼠3 小时前
Node.js 的模块作用域和 module 对象详细介绍
前端·javascript·vscode·node.js·web