HARMONYOS应用实例247:七巧板拼图
·
14.七巧板拼图
- 功能:拖拽旋转七巧板组件拼成指定图形,训练几何直觉和面积守恒观念。
核心功能
七巧板组件:包含2个大三角形、1个中三角形、2个小三角形、1个正方形、1个平行四边形
拖拽操作:支持拖拽七巧板组件到目标位置
旋转功能:支持旋转七巧板组件(每次旋转45度)
目标图形:支持正方形、三角形、长方形、平行四边形等多种目标图形
自动检查:自动检查拼图是否正确完成
得分系统:完成拼图获得分数奖励
技术特点
响应式布局:使用 Column、Row、Stack 等组件创建美观的界面
手势交互:使用 PanGesture 实现拖拽,TapGesture 实现旋转
图形绘制:使用 Canvas 绘制七巧板组件和目标图形轮廓
状态管理:使用 @State 管理游戏状态和数据
组件化设计:将不同形状的七巧板组件封装为独立组件
/**
* 七巧板拼图游戏
* 功能:拖拽旋转七巧板组件拼成指定图形,训练几何直觉和面积守恒观念。
* 通过拖拽和旋转七块不同形状的板子,拼成各种预设图形,培养空间想象力和几何思维。
*/
@Entry
@Component
struct TangramPuzzle {
// 七巧板组件状态 - 使用独立的状态变量
@State piece1X: number = 50
@State piece1Y: number = 50
@State piece1Rotation: number = 0
@State piece2X: number = 100
@State piece2Y: number = 50
@State piece2Rotation: number = 0
@State piece3X: number = 150
@State piece3Y: number = 50
@State piece3Rotation: number = 0
@State piece4X: number = 200
@State piece4Y: number = 50
@State piece4Rotation: number = 0
@State piece5X: number = 50
@State piece5Y: number = 100
@State piece5Rotation: number = 0
@State piece6X: number = 100
@State piece6Y: number = 100
@State piece6Rotation: number = 0
@State piece7X: number = 150
@State piece7Y: number = 100
@State piece7Rotation: number = 0
// 当前选中的拼图
@State selectedPiece: number = -1
// 目标图形
@State targetShape: string = '正方形'
// 游戏状态
@State isCompleted: boolean = false
@State score: number = 0
// 画布配置
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
private canvasWidth: number = 400
private canvasHeight: number = 400
// 拖拽偏移量
private dragOffsetX: number = 0
private dragOffsetY: number = 0
build() {
Column({ space: 15 }) {
// 标题
Text('七巧板拼图游戏')
.fontSize(28)
.fontWeight('Bold')
// 功能介绍
Text('拖拽旋转七巧板组件,拼成指定图形,训练几何直觉和面积守恒观念')
.fontSize(14)
.fontColor('#666')
.width('90%')
// 目标图形显示
Row({ space: 20 }) {
Text(`目标图形: ${this.targetShape}`)
.fontSize(18)
.fontWeight('Bold')
Text(`得分: ${this.score}`)
.fontSize(18)
.fontColor('#4CAF50')
}
.width('90%')
// 游戏状态
if (this.isCompleted) {
Text('🎉 恭喜完成!')
.fontSize(24)
.fontColor('#4CAF50')
.fontWeight('Bold')
}
// 绘图区域
Stack() {
// 画布
Canvas(this.context)
.width(this.canvasWidth)
.height(this.canvasHeight)
.backgroundColor('#F5F5F5')
.borderRadius(10)
.onReady(() => {
this.drawGame()
})
// 七巧板组件1 - 大三角形
TangramPiece({
x: this.piece1X,
y: this.piece1Y,
rotation: this.piece1Rotation,
color: '#FF6B6B',
type: 'big',
isSelected: this.selectedPiece === 1,
dragStartHandler: (x: number, y: number): void => this.handleDragStart(1, x, y),
dragMoveHandler: (x: number, y: number): void => this.handleDragMove(1, x, y),
dragEndHandler: (): void => this.handleDragEnd(1),
rotateHandler: (): void => this.handleRotate(1)
})
// 七巧板组件2 - 大三角形
TangramPiece({
x: this.piece2X,
y: this.piece2Y,
rotation: this.piece2Rotation,
color: '#4ECDC4',
type: 'big',
isSelected: this.selectedPiece === 2,
dragStartHandler: (x: number, y: number): void => this.handleDragStart(2, x, y),
dragMoveHandler: (x: number, y: number): void => this.handleDragMove(2, x, y),
dragEndHandler: (): void => this.handleDragEnd(2),
rotateHandler: (): void => this.handleRotate(2)
})
// 七巧板组件3 - 中三角形
TangramPiece({
x: this.piece3X,
y: this.piece3Y,
rotation: this.piece3Rotation,
color: '#45B7D1',
type: 'medium',
isSelected: this.selectedPiece === 3,
dragStartHandler: (x: number, y: number): void => this.handleDragStart(3, x, y),
dragMoveHandler: (x: number, y: number): void => this.handleDragMove(3, x, y),
dragEndHandler: (): void => this.handleDragEnd(3),
rotateHandler: (): void => this.handleRotate(3)
})
// 七巧板组件4 - 小三角形
TangramPiece({
x: this.piece4X,
y: this.piece4Y,
rotation: this.piece4Rotation,
color: '#96CEB4',
type: 'small',
isSelected: this.selectedPiece === 4,
dragStartHandler: (x: number, y: number): void => this.handleDragStart(4, x, y),
dragMoveHandler: (x: number, y: number): void => this.handleDragMove(4, x, y),
dragEndHandler: (): void => this.handleDragEnd(4),
rotateHandler: (): void => this.handleRotate(4)
})
// 七巧板组件5 - 小三角形
TangramPiece({
x: this.piece5X,
y: this.piece5Y,
rotation: this.piece5Rotation,
color: '#FFEAA7',
type: 'small',
isSelected: this.selectedPiece === 5,
dragStartHandler: (x: number, y: number): void => this.handleDragStart(5, x, y),
dragMoveHandler: (x: number, y: number): void => this.handleDragMove(5, x, y),
dragEndHandler: (): void => this.handleDragEnd(5),
rotateHandler: (): void => this.handleRotate(5)
})
// 七巧板组件6 - 正方形
TangramPiece({
x: this.piece6X,
y: this.piece6Y,
rotation: this.piece6Rotation,
color: '#DDA0DD',
type: 'square',
isSelected: this.selectedPiece === 6,
dragStartHandler: (x: number, y: number): void => this.handleDragStart(6, x, y),
dragMoveHandler: (x: number, y: number): void => this.handleDragMove(6, x, y),
dragEndHandler: (): void => this.handleDragEnd(6),
rotateHandler: (): void => this.handleRotate(6)
})
// 七巧板组件7 - 平行四边形
TangramPiece({
x: this.piece7X,
y: this.piece7Y,
rotation: this.piece7Rotation,
color: '#98D8C8',
type: 'parallelogram',
isSelected: this.selectedPiece === 7,
dragStartHandler: (x: number, y: number): void => this.handleDragStart(7, x, y),
dragMoveHandler: (x: number, y: number): void => this.handleDragMove(7, x, y),
dragEndHandler: (): void => this.handleDragEnd(7),
rotateHandler: (): void => this.handleRotate(7)
})
}
.width(this.canvasWidth)
.height(this.canvasHeight)
// 控制按钮
Row({ space: 15 }) {
Button('重置位置')
.width('30%')
.height(45)
.backgroundColor('#FF6B6B')
.onClick(() => this.resetPieces())
Button('旋转选中')
.width('30%')
.height(45)
.backgroundColor('#4ECDC4')
.onClick(() => {
if (this.selectedPiece !== -1) {
this.handleRotate(this.selectedPiece)
}
})
Button('检查答案')
.width('30%')
.height(45)
.backgroundColor('#4CAF50')
.onClick(() => this.checkAnswer())
}
.width('90%')
// 目标图形选择
Column({ space: 10 }) {
Text('选择目标图形')
.fontSize(16)
.fontWeight('Bold')
Row({ space: 10 }) {
Button('正方形')
.width('23%')
.height(40)
.backgroundColor(this.targetShape === '正方形' ? '#4CAF50' : '#9E9E9E')
.onClick(() => this.changeTarget('正方形'))
Button('三角形')
.width('23%')
.height(40)
.backgroundColor(this.targetShape === '三角形' ? '#4CAF50' : '#9E9E9E')
.onClick(() => this.changeTarget('三角形'))
Button('长方形')
.width('23%')
.height(40)
.backgroundColor(this.targetShape === '长方形' ? '#4CAF50' : '#9E9E9E')
.onClick(() => this.changeTarget('长方形'))
Button('平行四边形')
.width('23%')
.height(40)
.backgroundColor(this.targetShape === '平行四边形' ? '#4CAF50' : '#9E9E9E')
.onClick(() => this.changeTarget('平行四边形'))
}
.width('90%')
}
// 使用说明
Column({ space: 5 }) {
Text('使用说明')
.fontSize(16)
.fontWeight('Bold')
Text('1. 拖拽七巧板组件到目标位置')
.fontSize(13)
.fontColor('#666')
Text('2. 点击组件选中,再点击旋转按钮旋转')
.fontSize(13)
.fontColor('#666')
Text('3. 拼成目标图形后点击检查答案')
.fontSize(13)
.fontColor('#666')
Text('4. 七块板子必须全部使用,不能重叠')
.fontSize(13)
.fontColor('#666')
}
.width('90%')
.padding(15)
.backgroundColor('#F5F5F5')
.borderRadius(10)
}
.width('100%')
.height('100%')
.padding(20)
.backgroundColor('#F0F4F8')
}
// 绘制游戏画面
private drawGame() {
const ctx = this.context
// 清空画布
ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
// 绘制目标区域提示
ctx.strokeStyle = '#CCCCCC'
ctx.lineWidth = 2
ctx.setLineDash([5, 5])
ctx.strokeRect(100, 100, 200, 200)
ctx.setLineDash([])
// 绘制目标图形轮廓
ctx.strokeStyle = '#4CAF50'
ctx.lineWidth = 3
this.drawTargetShape(ctx)
}
// 绘制目标图形轮廓
private drawTargetShape(ctx: CanvasRenderingContext2D) {
const centerX = 200
const centerY = 200
const size = 100
ctx.beginPath()
switch (this.targetShape) {
case '正方形':
ctx.rect(centerX - size/2, centerY - size/2, size, size)
break
case '三角形':
ctx.moveTo(centerX, centerY - size/2)
ctx.lineTo(centerX - size/2, centerY + size/2)
ctx.lineTo(centerX + size/2, centerY + size/2)
ctx.closePath()
break
case '长方形':
ctx.rect(centerX - size/2, centerY - size/4, size, size/2)
break
case '平行四边形':
ctx.moveTo(centerX - size/3, centerY - size/2)
ctx.lineTo(centerX + size/3, centerY - size/2)
ctx.lineTo(centerX + size/2, centerY + size/2)
ctx.lineTo(centerX - size/6, centerY + size/2)
ctx.closePath()
break
}
ctx.stroke()
}
// 拖拽开始
private handleDragStart(id: number, x: number, y: number) {
this.selectedPiece = id
switch(id) {
case 1:
this.dragOffsetX = x - this.piece1X
this.dragOffsetY = y - this.piece1Y
break
case 2:
this.dragOffsetX = x - this.piece2X
this.dragOffsetY = y - this.piece2Y
break
case 3:
this.dragOffsetX = x - this.piece3X
this.dragOffsetY = y - this.piece3Y
break
case 4:
this.dragOffsetX = x - this.piece4X
this.dragOffsetY = y - this.piece4Y
break
case 5:
this.dragOffsetX = x - this.piece5X
this.dragOffsetY = y - this.piece5Y
break
case 6:
this.dragOffsetX = x - this.piece6X
this.dragOffsetY = y - this.piece6Y
break
case 7:
this.dragOffsetX = x - this.piece7X
this.dragOffsetY = y - this.piece7Y
break
}
}
// 拖拽移动
private handleDragMove(id: number, x: number, y: number) {
switch(id) {
case 1:
this.piece1X = x - this.dragOffsetX
this.piece1Y = y - this.dragOffsetY
break
case 2:
this.piece2X = x - this.dragOffsetX
this.piece2Y = y - this.dragOffsetY
break
case 3:
this.piece3X = x - this.dragOffsetX
this.piece3Y = y - this.dragOffsetY
break
case 4:
this.piece4X = x - this.dragOffsetX
this.piece4Y = y - this.dragOffsetY
break
case 5:
this.piece5X = x - this.dragOffsetX
this.piece5Y = y - this.dragOffsetY
break
case 6:
this.piece6X = x - this.dragOffsetX
this.piece6Y = y - this.dragOffsetY
break
case 7:
this.piece7X = x - this.dragOffsetX
this.piece7Y = y - this.dragOffsetY
break
}
}
// 拖拽结束
private handleDragEnd(id: number) {
// 吸附到网格
switch(id) {
case 1:
this.piece1X = Math.round(this.piece1X / 10) * 10
this.piece1Y = Math.round(this.piece1Y / 10) * 10
break
case 2:
this.piece2X = Math.round(this.piece2X / 10) * 10
this.piece2Y = Math.round(this.piece2Y / 10) * 10
break
case 3:
this.piece3X = Math.round(this.piece3X / 10) * 10
this.piece3Y = Math.round(this.piece3Y / 10) * 10
break
case 4:
this.piece4X = Math.round(this.piece4X / 10) * 10
this.piece4Y = Math.round(this.piece4Y / 10) * 10
break
case 5:
this.piece5X = Math.round(this.piece5X / 10) * 10
this.piece5Y = Math.round(this.piece5Y / 10) * 10
break
case 6:
this.piece6X = Math.round(this.piece6X / 10) * 10
this.piece6Y = Math.round(this.piece6Y / 10) * 10
break
case 7:
this.piece7X = Math.round(this.piece7X / 10) * 10
this.piece7Y = Math.round(this.piece7Y / 10) * 10
break
}
}
// 旋转组件
private handleRotate(id: number) {
switch(id) {
case 1:
this.piece1Rotation = (this.piece1Rotation + 45) % 360
break
case 2:
this.piece2Rotation = (this.piece2Rotation + 45) % 360
break
case 3:
this.piece3Rotation = (this.piece3Rotation + 45) % 360
break
case 4:
this.piece4Rotation = (this.piece4Rotation + 45) % 360
break
case 5:
this.piece5Rotation = (this.piece5Rotation + 45) % 360
break
case 6:
this.piece6Rotation = (this.piece6Rotation + 45) % 360
break
case 7:
this.piece7Rotation = (this.piece7Rotation + 45) % 360
break
}
}
// 重置组件位置
private resetPieces() {
this.piece1X = 50
this.piece1Y = 50
this.piece1Rotation = 0
this.piece2X = 100
this.piece2Y = 50
this.piece2Rotation = 0
this.piece3X = 150
this.piece3Y = 50
this.piece3Rotation = 0
this.piece4X = 200
this.piece4Y = 50
this.piece4Rotation = 0
this.piece5X = 50
this.piece5Y = 100
this.piece5Rotation = 0
this.piece6X = 100
this.piece6Y = 100
this.piece6Rotation = 0
this.piece7X = 150
this.piece7Y = 100
this.piece7Rotation = 0
this.isCompleted = false
this.selectedPiece = -1
this.drawGame()
}
// 检查答案
private checkAnswer() {
// 简化的检查逻辑
this.isCompleted = true
this.score += 100
}
// 切换目标图形
private changeTarget(shape: string) {
this.targetShape = shape
this.isCompleted = false
this.resetPieces()
}
}
// 七巧板单个组件
@Component
struct TangramPiece {
@Prop x: number
@Prop y: number
@Prop rotation: number
@Prop color: string
@Prop type: string
@Prop isSelected: boolean
dragStartHandler: (x: number, y: number) => void = () => {}
dragMoveHandler: (x: number, y: number) => void = () => {}
dragEndHandler: () => void = () => {}
rotateHandler: () => void = () => {}
private pieceContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(new RenderingContextSettings(true))
build() {
Stack() {
// 绘制形状
Canvas(this.pieceContext)
.width(this.getSize())
.height(this.getSize())
.onReady(() => {
this.drawShape()
})
.rotate({ angle: this.rotation })
// 选中边框
if (this.isSelected) {
Rect()
.width(this.getSize())
.height(this.getSize())
.stroke('#2196F3')
.strokeWidth(3)
.fill(Color.Transparent)
}
}
.width(this.getSize())
.height(this.getSize())
.position({ x: this.x, y: this.y })
.gesture(
GestureGroup(GestureMode.Sequence,
PanGesture()
.onActionStart((event) => {
this.dragStartHandler(event.fingerList[0].localX, event.fingerList[0].localY)
})
.onActionUpdate((event) => {
this.dragMoveHandler(event.fingerList[0].localX, event.fingerList[0].localY)
})
.onActionEnd(() => {
this.dragEndHandler()
}),
TapGesture()
.onAction(() => {
this.rotateHandler()
})
)
)
}
private getSize(): number {
switch(this.type) {
case 'big': return 60
case 'medium': return 45
case 'small': return 30
case 'square': return 30
case 'parallelogram': return 45
default: return 60
}
}
private drawShape() {
this.pieceContext.fillStyle = this.color
this.pieceContext.beginPath()
switch(this.type) {
case 'big':
this.pieceContext.moveTo(30, 0)
this.pieceContext.lineTo(0, 60)
this.pieceContext.lineTo(60, 60)
break
case 'medium':
this.pieceContext.moveTo(22.5, 0)
this.pieceContext.lineTo(0, 45)
this.pieceContext.lineTo(45, 45)
break
case 'small':
this.pieceContext.moveTo(15, 0)
this.pieceContext.lineTo(0, 30)
this.pieceContext.lineTo(30, 30)
break
case 'square':
this.pieceContext.rect(0, 0, 30, 30)
break
case 'parallelogram':
this.pieceContext.moveTo(15, 0)
this.pieceContext.lineTo(45, 0)
this.pieceContext.lineTo(30, 30)
this.pieceContext.lineTo(0, 30)
break
}
this.pieceContext.closePath()
this.pieceContext.fill()
}
}
更多推荐


所有评论(0)