✨ Feat: finish cards game
This commit is contained in:
parent
95a2e5ac35
commit
db3b628a14
84
entry/src/main/ets/components/CardView.ets
Normal file
84
entry/src/main/ets/components/CardView.ets
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import { Card } from '../models/Card'
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export struct CardView {
|
||||||
|
@ObjectLink card: Card
|
||||||
|
@Consume count: number
|
||||||
|
@State cardRotateAngle: number = 0
|
||||||
|
@State angle: number = 0
|
||||||
|
|
||||||
|
animate() {
|
||||||
|
if (this.card.isMatch) {
|
||||||
|
this.cardRotateAngle = 0
|
||||||
|
animateTo({
|
||||||
|
duration: 1000,
|
||||||
|
tempo: 1,
|
||||||
|
curve: Curve.Linear,
|
||||||
|
delay: 200,
|
||||||
|
iterations: -1,
|
||||||
|
playMode: PlayMode.Normal
|
||||||
|
}, () => {
|
||||||
|
this.angle = 360
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (this.card.isFaceUp && !this.card.isMatch) {
|
||||||
|
animateTo({
|
||||||
|
duration: 300,
|
||||||
|
tempo: 2,
|
||||||
|
curve: Curve.Linear,
|
||||||
|
delay: 0,
|
||||||
|
iterations: 1,
|
||||||
|
playMode: PlayMode.Normal
|
||||||
|
}, () => {
|
||||||
|
this.cardRotateAngle = 180
|
||||||
|
})
|
||||||
|
} else if (!this.card.isFaceUp) {
|
||||||
|
animateTo({
|
||||||
|
duration: 300,
|
||||||
|
tempo: 2,
|
||||||
|
curve: Curve.Linear,
|
||||||
|
delay: 0,
|
||||||
|
iterations: 1,
|
||||||
|
playMode: PlayMode.Normal
|
||||||
|
}, () => {
|
||||||
|
this.cardRotateAngle = 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse() {
|
||||||
|
this.card.isFaceUp = !this.card.isFaceUp
|
||||||
|
this.count += 1
|
||||||
|
this.animate()
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Column() {
|
||||||
|
Column() {
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.border({ color: '#fffa9e0f', width: 2, radius: 15 })
|
||||||
|
.backgroundColor(this.card.isFaceUp ? '#fff5f0f0' : '#fffa9e0f')
|
||||||
|
.aspectRatio(2 / 3)
|
||||||
|
.rotate({ x: 0, y: 1, z: 0, angle: this.cardRotateAngle })
|
||||||
|
.onClick(() => {
|
||||||
|
this.reverse()
|
||||||
|
})
|
||||||
|
|
||||||
|
Text(this.card.name)
|
||||||
|
.textAlign(TextAlign.Center)
|
||||||
|
.visibility(this.card.isFaceUp ? Visibility.Visible : Visibility.Hidden)
|
||||||
|
.fontSize(20)
|
||||||
|
.position({ x: '50%', y: '50%' })
|
||||||
|
.markAnchor({ x: '50%', y: '50%' })
|
||||||
|
//.rotate(this.card.isMatcsh ? {angle:this.angle} : {angle:360})
|
||||||
|
}
|
||||||
|
.padding({ left: 2, right: 2 })
|
||||||
|
.margin({ top: 10 })
|
||||||
|
.width(80)
|
||||||
|
.height(120)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
37
entry/src/main/ets/components/GameView.ets
Normal file
37
entry/src/main/ets/components/GameView.ets
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { CardView } from './CardView'
|
||||||
|
import { ObservedArray } from '../models/ObservedArray'
|
||||||
|
import { Card } from '../models/Card'
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export struct GameView {
|
||||||
|
@ObjectLink cards: ObservedArray<Card>
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
getTitle() {
|
||||||
|
Text("卡片小游戏")
|
||||||
|
.fontSize(20)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
.width('100%')
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Column() {
|
||||||
|
Row() {
|
||||||
|
this.getTitle()
|
||||||
|
}
|
||||||
|
|
||||||
|
Flex({
|
||||||
|
direction: FlexDirection.Row,
|
||||||
|
wrap: FlexWrap.Wrap,
|
||||||
|
justifyContent: FlexAlign.SpaceEvenly,
|
||||||
|
alignContent: FlexAlign.SpaceEvenly
|
||||||
|
}) {
|
||||||
|
ForEach(this.cards, (card, idx) => {
|
||||||
|
CardView({ card: card })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding({ left: 6, right: 6 })
|
||||||
|
.margin({ top: 20 })
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ export default class EntryAbility extends UIAbility {
|
||||||
// Main window is created, set main page for this ability
|
// Main window is created, set main page for this ability
|
||||||
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
|
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
|
||||||
|
|
||||||
windowStage.loadContent('pages/Index', (err, data) => {
|
windowStage.loadContent('pages/GamePage', (err, data) => {
|
||||||
if (err.code) {
|
if (err.code) {
|
||||||
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
|
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
|
||||||
return;
|
return;
|
||||||
|
|
12
entry/src/main/ets/models/Card.ets
Normal file
12
entry/src/main/ets/models/Card.ets
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
@Observed
|
||||||
|
export class Card {
|
||||||
|
name: string
|
||||||
|
id: string
|
||||||
|
isFaceUp: boolean = false
|
||||||
|
isMatch: boolean = false
|
||||||
|
|
||||||
|
constructor(name: string) {
|
||||||
|
this.name = name
|
||||||
|
this.id = Date.now() + this.name
|
||||||
|
}
|
||||||
|
}
|
10
entry/src/main/ets/models/ObservedArray.ets
Normal file
10
entry/src/main/ets/models/ObservedArray.ets
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
@Observed
|
||||||
|
export class ObservedArray<T> extends Array<T> {
|
||||||
|
constructor(args?: T[]) {
|
||||||
|
if (args instanceof Array) {
|
||||||
|
super(...args)
|
||||||
|
} else {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
entry/src/main/ets/models/viewModel.ets
Normal file
35
entry/src/main/ets/models/viewModel.ets
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { Card } from './Card'
|
||||||
|
import { ObservedArray } from './ObservedArray'
|
||||||
|
|
||||||
|
export class CardViewModel {
|
||||||
|
cardList: ObservedArray<Card> = []
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.initCardList()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 初始化cardList赋值
|
||||||
|
private initCardList() {
|
||||||
|
const words = ["A", "A", "B", "B", "C", "C", "D", "D", "E", "E", "F", "F"]
|
||||||
|
this.cardList = new ObservedArray<Card>(words.map(w => new Card(w)))
|
||||||
|
this.shuffle()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打乱元素
|
||||||
|
private shuffle() {
|
||||||
|
this.cardList.forEach((_, idx) => {
|
||||||
|
const j = Math.floor(Math.random() * (idx + 1)); // 生成随机索引
|
||||||
|
[this.cardList[idx], this.cardList[j]] = [this.cardList[j], this.cardList[idx]]; // 交换元素
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCardList() {
|
||||||
|
return this.cardList
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否全部翻面
|
||||||
|
public isAllFaceUp() {
|
||||||
|
return !this.cardList.some(card =>!card.isFaceUp)
|
||||||
|
}
|
||||||
|
}
|
45
entry/src/main/ets/pages/GamePage.ets
Normal file
45
entry/src/main/ets/pages/GamePage.ets
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { GameView } from '../components/GameView'
|
||||||
|
import { CardViewModel } from '../models/viewModel'
|
||||||
|
import router from '@ohos.router'
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Entry
|
||||||
|
@Component
|
||||||
|
struct Index {
|
||||||
|
@State viewModel: CardViewModel = new CardViewModel()
|
||||||
|
@Provide @Watch("handleAllFaceUp") count: number = 0
|
||||||
|
private time: number = 0
|
||||||
|
private interval: number
|
||||||
|
|
||||||
|
onPageShow() {
|
||||||
|
this.interval = setInterval(() => {
|
||||||
|
this.time++;
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
onPageHide() {
|
||||||
|
if (this.interval) clearTimeout(this.interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当所有Cards都反面,跳转About page
|
||||||
|
*/
|
||||||
|
handleAllFaceUp() {
|
||||||
|
if (this.viewModel.isAllFaceUp()) {
|
||||||
|
router.pushUrl({
|
||||||
|
url: "pages/OverPage",
|
||||||
|
params: {
|
||||||
|
count: this.count,
|
||||||
|
time: this.time,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Column() {
|
||||||
|
GameView({ cards: this.viewModel.getCardList() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
@Preview
|
|
||||||
@Entry
|
|
||||||
@Component
|
|
||||||
struct Index {
|
|
||||||
@State message: string = 'Hello World';
|
|
||||||
@State messageList: any[] = [
|
|
||||||
{ message: this.message, checked: false },
|
|
||||||
{ message: this.message, checked: false },
|
|
||||||
{ message: this.message, checked: false },
|
|
||||||
{ message: this.message, checked: false },
|
|
||||||
{ message: this.message, checked: false },
|
|
||||||
]
|
|
||||||
private textController: TextInputController = new TextInputController()
|
|
||||||
private tabsController: TabsController = new TabsController()
|
|
||||||
|
|
||||||
build() {
|
|
||||||
Column() {
|
|
||||||
Tabs({ barPosition: BarPosition.End, controller: this.tabsController }) {
|
|
||||||
TabContent() {
|
|
||||||
Column() {
|
|
||||||
Text(this.message)
|
|
||||||
.fontSize(20)
|
|
||||||
.fontWeight(FontWeight.Bold)
|
|
||||||
.width('100%')
|
|
||||||
.padding({ left: 10, right: 10 })
|
|
||||||
|
|
||||||
List() {
|
|
||||||
ForEach(this.messageList, (item, idx) => {
|
|
||||||
ListItem() {
|
|
||||||
Row() {
|
|
||||||
Checkbox()
|
|
||||||
.select(item.checked)
|
|
||||||
.onChange((value) => {
|
|
||||||
// item.checked = value // array list 修改 item 的属性无法触发 UI 更新
|
|
||||||
// this.messageList[idx].checked = value // array list 修改 item 的属性无法触发 UI 更新
|
|
||||||
this.messageList[idx] = { ...item, checked: value } // array list 替换 item 触发 UI 更新
|
|
||||||
// array list 整体替换,触发 UI 更新
|
|
||||||
// const newMessageList = this.messageList
|
|
||||||
// newMessageList[idx].checked = value
|
|
||||||
// this.messageList = [...newMessageList]
|
|
||||||
})
|
|
||||||
Text(item.message)
|
|
||||||
.decoration({ type: item.checked ? TextDecorationType.LineThrough : TextDecorationType.None })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.align(Alignment.Start)
|
|
||||||
.width('100%')
|
|
||||||
.margin({ top: 4 })
|
|
||||||
.padding({ left: 4, right: 4, top: 2, bottom: 2 })
|
|
||||||
.backgroundColor(Color.Gray)
|
|
||||||
.borderRadius(20)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
.listDirection(Axis.Vertical)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tabBar('green')
|
|
||||||
.align(Alignment.Top)
|
|
||||||
|
|
||||||
TabContent() {
|
|
||||||
Column() {
|
|
||||||
TextInput({ placeholder: '请输入密码', controller: this.textController })
|
|
||||||
.type(InputType.Password)
|
|
||||||
.margin({ top: 4 })
|
|
||||||
|
|
||||||
Button('设置光标位置', { type: ButtonType.Capsule, stateEffect: true })
|
|
||||||
.onClick(() => {
|
|
||||||
this.textController.caretPosition(2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tabBar("red")
|
|
||||||
.align(Alignment.Top)
|
|
||||||
|
|
||||||
TabContent() {
|
|
||||||
LoadingProgress()
|
|
||||||
.color(Color.Blue)
|
|
||||||
.height(60)
|
|
||||||
.width(60)
|
|
||||||
}
|
|
||||||
.tabBar('yellow')
|
|
||||||
.align(Alignment.Top)
|
|
||||||
}
|
|
||||||
.barWidth('100%') // 设置TabBar宽度
|
|
||||||
.barHeight(60) // 设置TabBar高度
|
|
||||||
.width('100%') // 设置Tabs组件宽度
|
|
||||||
.height('100%') // 设置Tabs组件高度
|
|
||||||
.backgroundColor(0xF5F5F5) // 设置Tabs组件背景颜色
|
|
||||||
.vertical(false)
|
|
||||||
.padding({ top: 10, left: 4, right: 2 })
|
|
||||||
}
|
|
||||||
.height('100%')
|
|
||||||
.padding({ left: 4, right: 4 })
|
|
||||||
}
|
|
||||||
}
|
|
28
entry/src/main/ets/pages/OverPage.ets
Normal file
28
entry/src/main/ets/pages/OverPage.ets
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import router from '@ohos.router'
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Entry
|
||||||
|
@Component
|
||||||
|
struct About {
|
||||||
|
@State count: number = router.getParams()?.['count'] ?? 0
|
||||||
|
@State time: number = router.getParams()?.['time'] ?? 0
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Column() {
|
||||||
|
Text("恭喜通关!")
|
||||||
|
.fontSize(30)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
Text(`点击了${this.count}次,共耗时${this.time}秒`)
|
||||||
|
.opacity(0.6)
|
||||||
|
Text('超越了99%的人')
|
||||||
|
.fontColor(Color.Blue)
|
||||||
|
Button("再来一次").margin({ top: 10 })
|
||||||
|
.onClick(() => {
|
||||||
|
router.pushUrl({ url: "pages/GamePage" })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.justifyContent(FlexAlign.Center)
|
||||||
|
.height('100%')
|
||||||
|
.width('100%')
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
"src": [
|
"src": [
|
||||||
"pages/Index"
|
"pages/GamePage",
|
||||||
|
"pages/OverPage"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue
Block a user