1. 几何概型演示
    • 功能:在方形区域内随机撒点,计算落在特定区域内的概率,演示 P=目标面积总面积P = \frac{\text{目标面积}}{\text{总面积}}P=总面积目标面积
      在方形区域内随机撒点
      计算落在特定区域内的概率
      演示 P = 目标面积/总面积 的几何概型原理
      支持多种目标区域形状(圆形、正方形、三角形)
      实时显示实验概率和理论概率对比
      通过蒙特卡洛方法验证概率理论
      在这里插入图片描述
// 几何概型演示
// 功能:在方形区域内随机撒点,计算落在特定区域内的概率,演示 P = 目标面积/总面积

// 点坐标接口
interface GeoPoint {
  x: number;
  y: number;
}

// 区域配置接口
interface RegionConfig {
  type: 'circle' | 'square' | 'triangle';
  centerX: number;
  centerY: number;
  radius?: number;
  side?: number;
  color: string;
}

@Entry
@Component
struct GeometricProbability {
  @State points: GeoPoint[] = [];
  @State totalPoints: number = 0;
  @State targetPoints: number = 0;
  @State isSimulating: boolean = false;
  @State simulationSpeed: number = 10;
  @State maxPoints: number = 10000;
  @State selectedRegion: string = 'circle';
  @State canvasWidth: number = 350;
  @State canvasHeight: number = 350;
  @State regionConfig: RegionConfig = {
    type: 'circle',
    centerX: 175,
    centerY: 175,
    radius: 150,
    color: '#E91E63'
  };
  
  private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D();
  
  private drawCanvas() {
    const ctx = this.canvasContext
    const width = this.canvasWidth
    const height = this.canvasHeight
    
    ctx.clearRect(0, 0, width, height)
    
    ctx.fillStyle = '#F5F5F5'
    ctx.fillRect(0, 0, width, height)
    
    ctx.strokeStyle = '#333'
    ctx.lineWidth = 2
    ctx.strokeRect(0, 0, width, height)
    
    this.drawTargetRegion(ctx)
    
    this.drawPoints(ctx)
    
    this.updateProbability()
  }
  
  private drawTargetRegion(ctx: CanvasRenderingContext2D) {
    const config = this.regionConfig
    
    ctx.fillStyle = config.color + '33'
    ctx.strokeStyle = config.color
    ctx.lineWidth = 3
    
    if (config.type === 'circle' && config.radius) {
      ctx.beginPath()
      ctx.arc(config.centerX, config.centerY, config.radius, 0, 2 * Math.PI)
      ctx.fill()
      ctx.stroke()
    } else if (config.type === 'square' && config.side) {
      const halfSide = config.side / 2
      ctx.fillRect(config.centerX - halfSide, config.centerY - halfSide, config.side, config.side)
      ctx.strokeRect(config.centerX - halfSide, config.centerY - halfSide, config.side, config.side)
    } else if (config.type === 'triangle' && config.side) {
      const halfSide = config.side / 2
      const height = (Math.sqrt(3) / 2) * config.side
      
      ctx.beginPath()
      ctx.moveTo(config.centerX, config.centerY - height / 2)
      ctx.lineTo(config.centerX - halfSide, config.centerY + height / 2)
      ctx.lineTo(config.centerX + halfSide, config.centerY + height / 2)
      ctx.closePath()
      ctx.fill()
      ctx.stroke()
    }
  }
  
  private drawPoints(ctx: CanvasRenderingContext2D) {
    this.points.forEach((point) => {
      const isTarget = this.isPointInTargetRegion(point)
      
      ctx.beginPath()
      ctx.arc(point.x, point.y, 3, 0, 2 * Math.PI)
      ctx.fillStyle = isTarget ? '#4CAF50' : '#2196F3'
      ctx.fill()
    })
  }
  
  private isPointInTargetRegion(point: GeoPoint): boolean {
    const config = this.regionConfig
    const dx = point.x - config.centerX
    const dy = point.y - config.centerY
    
    if (config.type === 'circle' && config.radius) {
      return (dx * dx + dy * dy) <= (config.radius * config.radius)
    } else if (config.type === 'square' && config.side) {
      const halfSide = config.side / 2
      return Math.abs(dx) <= halfSide && Math.abs(dy) <= halfSide
    } else if (config.type === 'triangle' && config.side) {
      const halfSide = config.side / 2
      const height = (Math.sqrt(3) / 2) * config.side
      
      const x1 = config.centerX
      const y1 = config.centerY - height / 2
      const x2 = config.centerX - halfSide
      const y2 = config.centerY + height / 2
      const x3 = config.centerX + halfSide
      const y3 = config.centerY + height / 2
      
      const denominator = ((y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3))
      const a = ((y2 - y3) * (point.x - x3) + (x3 - x2) * (point.y - y3)) / denominator
      const b = ((y3 - y1) * (point.x - x3) + (x1 - x3) * (point.y - y3)) / denominator
      const c = 1 - a - b
      
      return a >= 0 && a <= 1 && b >= 0 && b <= 1 && c >= 0 && c <= 1
    }
    
    return false
  }
  
  private updateProbability() {
    this.targetPoints = this.points.filter(point => this.isPointInTargetRegion(point)).length
    this.totalPoints = this.points.length
  }
  
  private calculateTheoreticalProbability(): number {
    const totalArea = this.canvasWidth * this.canvasHeight
    const config = this.regionConfig

    let targetArea = 0

    if (config.type === 'circle' && config.radius) {
      targetArea = Math.PI * config.radius * config.radius
    } else if (config.type === 'square' && config.side) {
      targetArea = config.side * config.side
    } else if (config.type === 'triangle' && config.side) {
      const height = (Math.sqrt(3) / 2) * config.side
      targetArea = 0.5 * config.side * height
    }

    return targetArea / totalArea
  }

  private getErrorValue(): number {
    const theoretical = this.calculateTheoreticalProbability()
    const experimental = this.totalPoints > 0 ? this.targetPoints / this.totalPoints : 0
    return Math.abs(experimental - theoretical)
  }
  
  private addRandomPoints(count: number) {
    for (let i = 0; i < count; i++) {
      const x = Math.random() * this.canvasWidth
      const y = Math.random() * this.canvasHeight
      this.points.push({ x, y })
    }
    
    this.updateProbability()
    this.drawCanvas()
  }
  
  private startSimulation() {
    if (this.isSimulating) return
    
    this.isSimulating = true
    this.points = []
    this.totalPoints = 0
    this.targetPoints = 0
    
    const interval = setInterval(() => {
      if (this.totalPoints >= this.maxPoints) {
        clearInterval(interval)
        this.isSimulating = false
        return
      }
      
      this.addRandomPoints(this.simulationSpeed)
    }, 50)
  }
  
  private reset() {
    this.isSimulating = false
    this.points = []
    this.totalPoints = 0
    this.targetPoints = 0
    this.drawCanvas()
  }
  
  private changeRegionType(type: string) {
    this.selectedRegion = type
    
    if (type === 'circle') {
      this.regionConfig = {
        type: 'circle',
        centerX: 175,
        centerY: 175,
        radius: 150,
        color: '#E91E63'
      }
    } else if (type === 'square') {
      this.regionConfig = {
        type: 'square',
        centerX: 175,
        centerY: 175,
        side: 250,
        color: '#9C27B0'
      }
    } else if (type === 'triangle') {
      this.regionConfig = {
        type: 'triangle',
        centerX: 175,
        centerY: 175,
        side: 280,
        color: '#FF9800'
      }
    }
    
    this.reset()
  }
  
  build() {
    Column({ space: 15 }) {
      Text('几何概型演示')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .fontColor('#E91E63')
        .margin({ top: 10 })
      
      Text('在方形区域内随机撒点,计算落在特定区域内的概率,演示 P = 目标面积/总面积')
        .fontSize(14)
        .fontColor('#666')
        .textAlign(TextAlign.Center)
        .padding({ left: 15, right: 15 })
      
      Scroll() {
        Column({ space: 15 }) {
          Column({ space: 10 }) {
            Text('目标区域形状')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor('#333')
              .width('100%')
            
            Row({ space: 10 }) {
              Button('圆形')
                .width('33%')
                .height(45)
                .backgroundColor(this.selectedRegion === 'circle' ? '#E91E63' : '#9E9E9E')
                .fontSize(14)
                .onClick(() => {
                  this.changeRegionType('circle')
                })
              
              Button('正方形')
                .width('33%')
                .height(45)
                .backgroundColor(this.selectedRegion === 'square' ? '#9C27B0' : '#9E9E9E')
                .fontSize(14)
                .onClick(() => {
                  this.changeRegionType('square')
                })
              
              Button('三角形')
                .width('33%')
                .height(45)
                .backgroundColor(this.selectedRegion === 'triangle' ? '#FF9800' : '#9E9E9E')
                .fontSize(14)
                .onClick(() => {
                  this.changeRegionType('triangle')
                })
            }
            .width('100%')
          }
          .width('100%')
          .padding(15)
          .backgroundColor('#FAFAFA')
          .borderRadius(10)
          
          Column({ space: 10 }) {
            Text('模拟设置')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor('#333')
              .width('100%')
            
            Column({ space: 5 }) {
              Text(`最大点数:${this.maxPoints}`)
                .fontSize(14)
                .fontColor('#333')
                .width('100%')
              
              Slider({
                value: this.maxPoints,
                min: 1000,
                max: 50000,
                step: 1000,
                style: SliderStyle.OutSet
              })
                .width('100%')
                .blockColor('#E91E63')
                .trackColor('#FCE4EC')
                .selectedColor('#E91E63')
                .showSteps(false)
                .onChange((value: number) => {
                  this.maxPoints = value
                })
              
              Text(`模拟速度:${this.simulationSpeed} 点/次`)
                .fontSize(14)
                .fontColor('#333')
                .width('100%')
              
              Slider({
                value: this.simulationSpeed,
                min: 1,
                max: 50,
                step: 1,
                style: SliderStyle.OutSet
              })
                .width('100%')
                .blockColor('#E91E63')
                .trackColor('#FCE4EC')
                .selectedColor('#E91E63')
                .showSteps(false)
                .onChange((value: number) => {
                  this.simulationSpeed = value
                })
            }
            .width('100%')
            .padding(10)
            .backgroundColor('#F5F5F5')
            .borderRadius(8)
          }
          .width('100%')
          .padding(15)
          .backgroundColor('#FAFAFA')
          .borderRadius(10)
          
          Column({ space: 10 }) {
            Text('模拟区域')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor('#333')
              .width('100%')
            
            Canvas(this.canvasContext)
              .width(this.canvasWidth)
              .height(this.canvasHeight)
              .backgroundColor('#FFFFFF')
              .border({ width: 2, color: '#333' })
              .borderRadius(4)
              .onReady(() => {
                this.drawCanvas()
              })
          }
          .width('100%')
          .padding(15)
          .backgroundColor('#FAFAFA')
          .borderRadius(10)
          
          Row({ space: 10 }) {
            Button(this.isSimulating ? '模拟中...' : '开始模拟')
              .width('50%')
              .height(50)
              .backgroundColor(this.isSimulating ? '#9E9E9E' : '#E91E63')
              .fontSize(16)
              .enabled(!this.isSimulating)
              .onClick(() => {
                this.startSimulation()
              })
            
            Button('重置')
              .width('50%')
              .height(50)
              .backgroundColor('#FF9800')
              .fontSize(16)
              .onClick(() => {
                this.reset()
              })
          }
          .width('100%')
          
          Column({ space: 10 }) {
            Text('概率统计')
              .fontSize(18)
              .fontWeight(FontWeight.Bold)
              .fontColor('#333')
              .width('100%')
            
            Column({ space: 5 }) {
              Row({ space: 10 }) {
                Text('总点数:')
                  .fontSize(14)
                  .fontColor('#333')
                  .width(80)
                
                Text(this.totalPoints.toString())
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#2196F3')
                  .layoutWeight(1)
              }
              .width('100%')
              
              Row({ space: 10 }) {
                Text('目标内点数:')
                  .fontSize(14)
                  .fontColor('#333')
                  .width(80)
                
                Text(this.targetPoints.toString())
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#4CAF50')
                  .layoutWeight(1)
              }
              .width('100%')
              
              Row({ space: 10 }) {
                Text('实验概率:')
                  .fontSize(14)
                  .fontColor('#333')
                  .width(80)
                
                Text(this.totalPoints > 0 ? (this.targetPoints / this.totalPoints).toFixed(6) : '0')
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#E91E63')
                  .layoutWeight(1)
              }
              .width('100%')
              
              Row({ space: 10 }) {
                Text('理论概率:')
                  .fontSize(14)
                  .fontColor('#333')
                  .width(80)
                
                Text(this.calculateTheoreticalProbability().toFixed(6))
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .fontColor('#9C27B0')
                  .layoutWeight(1)
              }
              .width('100%')
              
              Row({ space: 10 }) {
                Text('误差:')
                  .fontSize(14)
                  .fontColor('#333')
                  .width(80)

                Text(this.getErrorValue().toFixed(6))
                  .fontSize(16)
                  .fontWeight(FontWeight.Bold)
                  .fontColor(this.getErrorValue() < 0.01 ? '#4CAF50' : '#FF5722')
                  .layoutWeight(1)
              }
              .width('100%')
            }
            .width('100%')
            .padding(10)
            .backgroundColor('#F5F5F5')
            .borderRadius(8)
          }
          .width('100%')
          .padding(15)
          .backgroundColor('#FAFAFA')
          .borderRadius(10)
          
          Column({ space: 5 }) {
            Text('几何概型原理')
              .fontSize(16)
              .fontWeight(FontWeight.Bold)
              .fontColor('#E91E63')
            
            Text('• 几何概型:如果每个基本事件发生的可能性相同,且样本空间和事件域可以用几何度量(长度、面积、体积)来表示')
              .fontSize(12)
              .fontColor('#666')
              .width('100%')
            
            Text('• 概率公式:P(A) = 目标区域面积 / 总区域面积')
              .fontSize(12)
              .fontColor('#666')
              .width('100%')
            
            Text('• 通过蒙特卡洛方法模拟:随着样本数量增加,实验概率会趋近于理论概率')
              .fontSize(12)
              .fontColor('#666')
              .width('100%')
            
            Text('• 绿色点表示落在目标区域内,蓝色点表示落在目标区域外')
              .fontSize(12)
              .fontColor('#666')
              .width('100%')
          }
          .width('100%')
          .padding(15)
          .backgroundColor('#FCE4EC')
          .borderRadius(10)
        }
        .width('100%')
        .padding(10)
      }
      .width('100%')
      .height('100%')
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
}
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐