效果图:
主组件中状态变量定义模块:
先定义每一行棋的对象DraughtBoard(因为使用@State UI无法检测到二维数组的数值变化)
@Observed
class DraughtBoard extends Array<number>{
}
@State player:boolean
:这是一个关键的状态变量,决定了当前轮到哪一方下棋。true
表示当前轮到黑棋,false
则表示轮到白棋。其初始值被设定为true
,这意味着在游戏开始时,黑棋将率先落子。这种设计明确了游戏的起始下棋方,为后续的轮流下棋机制奠定了基础。@State winner:number
:此变量用于明确游戏的最终胜负结果。当winner
的值为0
时,代表游戏仍在进行中,尚未有一方取得胜利;当winner
的值为1
,表示黑棋获胜;而当winner
的值为2
,则表示白棋获胜。初始时,由于游戏尚未结束,winner
的值被初始化为0
,随着游戏进程的推进,其值会根据游戏的实际情况进行更新。@State draughtBoard:DraughtBoard[]
:这是一个二维数组,完整地代表了五子棋的棋盘状态。每一个元素都对应着棋盘上的一个位置。初始时,所有位置的值都被设置为0
,表示这些位置尚未有棋子落下。通过对这个数组的实时更新和监控,可以精确地掌握棋盘上每个位置的状态变化,从而实现对游戏进程的准确跟踪和判断。@State history:number[][]
:这个状态变量的作用是存储下棋的历史记录。它以二维数组的形式,记录了每一步下棋的坐标位置。通过这种方式,可以实现悔棋的功能。在游戏开始时,history
数组为空,随着玩家的下棋操作,不断将下棋的位置信息添加到数组中。-
// 判断下棋的为黑棋还是白棋 true 黑 false 白 @State player:boolean=true // 判断胜利者 0 未胜利 1黑棋胜 2白棋胜 @State winner:number=0 // 棋盘 15行棋 @State draughtBoard:DraughtBoard[] = [ new DraughtBoard(), new DraughtBoard(), new DraughtBoard(), new DraughtBoard(), new DraughtBoard(), new DraughtBoard(), new DraughtBoard(), new DraughtBoard(), new DraughtBoard(), new DraughtBoard(), new DraughtBoard(), new DraughtBoard(), new DraughtBoard(), new DraughtBoard(), new DraughtBoard() ] // 记录已下棋子的坐标和顺序 实现悔棋功能用 @State history:number[][]= []
棋盘行组件
draughtBoardRow
: @ObjectLink draughtBoardItem:DraughtBoard
:这一链接直接与每行棋的具体状态相关联。它能够实时反映出每行棋上棋子的分布情况,确保在游戏进行中,每行棋的状态都能得到准确和及时的更新和展示。@Link draughtBoard:DraughtBoard[]
:通过这种双向链接,组件能够获取到整个棋盘的状态信息。这对于在处理每行棋的逻辑时,特别是在进行胜利判断时,提供了全局的视角和必要的数据支持。@Link player:boolean
:明确当前是黑棋还是白棋在进行操作。这一信息对于决定当玩家点击空白位置时应设置的棋子颜色至关重要。它直接影响了游戏的逻辑流程和棋子的显示效果。@State y:number
:准确记录了棋盘中当前行的索引。在进行胜利判断时,这个索引值能够帮助确定具体的位置以及判断的方向,为准确判断是否有一方在某一行形成五子连珠提供了关键的位置信息。@State colors:ResourceColor[]
:定义了棋子可能呈现的颜色数组。其中,透明(未下棋)代表着棋盘上尚未落子的位置;黑色代表黑棋;白色代表白棋。默认情况下,棋盘上的位置颜色为透明,只有在玩家下棋后,相应位置的颜色才会根据棋子的颜色进行更新。@Link winner:number
:与主组件中的winner
状态建立链接。这样,在处理每行棋的过程中,可以根据全局的胜利情况来决定是否需要进行某些特殊的处理或显示相应的提示信息。@Link history:number[][]
:接收来自主组件的下棋历史记录。这为实现悔棋等功能提供了不可或缺的数据基础。在需要进行悔棋操作时,可以从这个历史记录中获取到之前下棋的位置信息,并据此恢复棋盘的
在 build
方法中:
通过使用ForEach
循环遍历每行棋的元素,从而实现了棋盘行的渲染和构建。当玩家点击棋盘上未下棋的位置时,会根据当前下棋者的身份(通过player
的布尔值来判断)来设置相应的棋子颜色。具体来说,如果当前是黑棋下棋(即player
为true
),那么会将对应位置的值设置为1
,从而在视觉上显示为黑棋;如果当前是白棋下棋(即player
为false
),则将对应位置的值设置为2
,显示为白棋。
紧接着,会调用win
函数来进行胜利判断。这个判断过程综合考虑了横向、纵向、斜向和反斜向等多个方向上的棋子连续情况。如果判断当前的下棋操作导致了一方的胜利,那么会更新winner
的值,以反映游戏的胜利结果。
同时,会将此次下棋的位置信息记录到history
中,形成下棋的历史记录。这一记录不仅用于悔棋操作,还为后续可能的游戏复盘或其他相关功能提供了数据支持。最后,通过交换player
的值,实现下棋者的交替,使游戏能够顺利地进行下去。
// 每一行棋封装成组件
@Component
struct draughtBoardRow{
@ObjectLink draughtBoardItem:DraughtBoard //每一行棋
@Link draughtBoard:DraughtBoard[] //整幅棋的棋盘 为了便于判断胜利 使用@Link进行双向传递
@Link player:boolean // 判断下棋的为黑棋还是白棋 true 黑 false 白
@State y:number=0 //棋盘中的第几行棋 为了判断胜利
@State colors:ResourceColor[]=[Color.Transparent, Color.Black, Color.White] //棋盘中的棋子颜色 默认透明
@Link winner:number // 判断胜利者 0 未胜利 1黑棋胜 2白棋胜
@Link history:number[][] // 棋盘 15行棋
build() {
Row(){
ForEach(this.draughtBoardItem,(item:number,index:number)=>{ //渲染这一行棋
Text()
.margin(1)
.borderRadius(10)
.backgroundColor(this.colors[item])
.height(20)
.aspectRatio(1)
.onClick(()=>{
//点击下棋 渲染棋子的颜色
if(item==0){
if (this.player) this.draughtBoardItem[index] = 1
else this.draughtBoardItem[index] = 2
// 胜利处理
if(win(this.draughtBoard, index, this.y, this.draughtBoardItem[index]))
this.winner = this.draughtBoardItem[index]
// 记录下棋的位置 实现悔棋功能
this.history.push([this.y, index])
// 交换下棋者
this.player = !this.player
}
})
})
}
}
}
主组件 Index
:
在 aboutToAppear
方法中:
通过使用两层嵌套的循环,对draughtBoard
数组进行初始化。外层循环遍历棋盘的行,内层循环遍历每行的位置。将所有位置的值都初始化为0
,这代表着棋盘在游戏开始时处于一个全新的、未被下棋的状态,为玩家提供了一个公平和清晰的游戏起点。
aboutToAppear(): void {
// 初始化棋盘 0为未下棋 1为已下白棋 2为已下黑棋
for(let i=0;i<this.draughtBoard.length;i++)
for(let j=0;j<this.draughtBoard.length;j++)
this.draughtBoard[j].push(0)
}
在 build
方法中:
- 顶部的行:首先,通过文本明确提示当前轮到哪一方下棋,使玩家能够清晰地了解游戏的当前状态。接着,展示当前下棋者的棋子颜色,通过直观的视觉效果增强玩家的代入感。然后,设置了重开按钮和悔棋按钮。重开按钮的作用是当玩家希望重新开始游戏时,能够将棋盘状态完全重置为初始状态,并重新确定下棋方为黑棋。悔棋按钮的有效性受到两个条件的限制:一是必须存在下棋的历史记录,二是游戏尚未决出胜利者。当这两个条件同时满足时,玩家点击悔棋按钮可以将最后一步下棋的位置恢复为未下棋状态,并交换下棋者,让游戏回到上一步的状态。
- 中间的列:根据
winner
的状态值来决定是否显示胜利提示和重新开始按钮。如果winner
的值为1
,则显示“恭喜黑子胜利”的提示,并提供重新开始按钮;如果winner
的值为2
,则显示“恭喜白子胜利”的提示和重新开始按钮。当winner
的值为0
时,即游戏仍在进行中,这些提示和按钮不会显示。 - 底部的
Stack
:首先,绘制棋盘的线条,为玩家提供清晰的下棋框架和视觉引导。然后,通过遍历draughtBoard
数组,逐个渲染每一行棋的组件draughtBoardRow
,从而完整地展示出棋盘上的棋子分布情况。最后,根据winner
的值来设置是否禁止下棋操作。当有一方获胜时,通过将zIndex
的值设置为1
,禁止进一步的下棋操作,确保游戏结果的确定性和稳定性。
build() {
Column(){
Row(){
//提示当前下棋者
Text('当前'+(this.player?'黑子':'白子')+'下棋')
//当前下棋的棋子 颜色区分
Text()
.height(25)
.aspectRatio(1)
.borderRadius(13)
.backgroundColor(this.player?Color.Black:Color.White)
// 重开按钮 将棋盘重新渲染为0
Button('重开')
.onClick(()=>{
this.draughtBoard.forEach((item0:DraughtBoard,index0:number)=>{
item0.forEach((item:number,index:number)=>{
this.draughtBoard[index0][index]=0
this.player = true
})
this.winner = 0
})
})
//悔棋 从历史记录中拿出最后一个 将棋盘中对应的位置置为0 并且要交换下棋者
Button('悔棋')
.onClick(()=>{
this.draughtBoard[this.history[this.history.length-1][0]]
[this.history[this.history.length-1][1]] = 0
this.history.pop()
this.player = !this.player
})
//已无记录或已胜利 按钮失效
.enabled(this.history.length>0&&this.winner==0)
}
.backgroundColor('#ff69bdfa')
.height(60)
.width('100%')
// 胜利处理
Column(){
Text(`恭喜${this.winner==2?'白子':'黑子'}胜利`)
.fontSize(21)
.fontWeight(700)
Button('重新开始')
.onClick(()=>{
this.draughtBoard.forEach((item0:DraughtBoard,index0:number)=>{
item0.forEach((item:number,index:number)=>{
this.draughtBoard[index0][index]=0
this.player = true
})
})
this.winner = 0
})
}
.opacity(this.winner)
// 绘制棋盘
Stack(){
//棋盘的线条和颜色 若不处理 则会导致下棋的位置在格子中
Column(){
ForEach([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],()=>{
Row(){
ForEach([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],()=>{
Text()
.height(22)
.aspectRatio(1)
.borderWidth(1)
})
}
})
}
.backgroundColor('#c09351')
//下棋的位置
Column(){
ForEach(this.draughtBoard,(item:DraughtBoard, index:number)=>{
// 每一行棋
draughtBoardRow({draughtBoardItem:item, player:this.player,
y:index, draughtBoard:this.draughtBoard, winner:this.winner,
history:this.history})
})
}
//胜利后禁止点击再下棋
Column()
.width('100%')
.height('100%')
.zIndex(this.winner==0?-1:1)
}
.width('100%')
.height(22*16)
}
.width('100%')
.height('100%')
}
胜利判断函数:
f1
函数:从给定的坐标开始,沿着横向向左和向右逐步检查。通过一个循环,不断比较相邻位置的棋子颜色是否相同。如果相同,则连续棋子的计数增加。如果连续相同颜色的棋子数量达到或超过 5 个,那么就判定为胜利。这种从局部逐步扩展的检查方式,能够准确地判断在横向方向上是否形成了五子连珠的胜利局面。f2
函数:与f1
函数的原理相似,但检查的方向是纵向。从给定的坐标向上和向下进行逐一比较,统计连续相同颜色棋子的数量,并依据数量判断是否胜利。通过这种纵向的检查,能够全面涵盖在垂直方向上可能出现的五子连珠情况。f3
函数:从给定坐标向左上和右下两个斜向方向进行检查。在检查过程中,同样通过循环和比较相邻位置的棋子颜色,来确定是否存在连续的相同颜色棋子达到或超过 5 个。这种斜向的检查增加了判断胜利的复杂性和全面性,涵盖了更多可能的胜利模式。f4
函数:从给定坐标向右上和左下两个反斜向方向进行检查。其工作方式与f3
函数类似,但方向相反。通过对这两个反斜向方向的检查,进一步完善了胜利判断的逻辑,确保不会遗漏任何可能的胜利情况。
win
函数:综合调用了上述四个方向的判断函数 f1
、 f2
、 f3
和 f4
。只要在任何一个方向上有一方形成了五子连珠,win
函数就会返回 true
,判定该方胜利。这种综合判断的方式,确保了游戏能够准确地识别出各种可能的胜利局面,保证了游戏结果的公正性和准确性。
//横
function f1( draughtBoard:DraughtBoard[], x:number, y:number, player:number ){
let count:number = 1 //已连成线的棋子
let p1:number = x-1 //横坐标 起点
let p2:number = x+1 //横坐标 终点
while(true){
if(p1>=0&&draughtBoard[y][p1]==player){
count++
p1--
if(count>=5) return true
continue
}
if(p2<=14&&draughtBoard[y][p2]==player){
count++
p2++
if(count>=5) return true
continue
}
return false //未够5子
}
}
//竖
function f2( draughtBoard:DraughtBoard[], x:number, y:number, player:number ){
let count:number = 1 //已连成线的棋子
let p1:number = y-1 //纵坐标 起点
let p2:number = y+1 //纵坐标 起点
while(true){
if(p1>=0&&draughtBoard[p1][x]==player){
count++
p1--
if(count>=5) return true
continue
}
if(p2<=14&&draughtBoard[p2][x]==player){
count++
p2++
if(count>=5) return true
continue
}
return false //未够5子
}
}
//斜
function f3( draughtBoard:DraughtBoard[], x:number, y:number, player:number ){
let count:number = 1 //已连成线的棋子
let px1:number = x-1 //横坐标 左上
let py1:number = y-1 //纵坐标 左上
let px2:number = x+1 //横坐标 右下
let py2:number = y+1 //纵坐标 右下
while(true){
if(px1>=0 && py1>=0 && draughtBoard[py1][px1]==player){
count++
px1--
py1--
if(count>=5) return true
continue
}
if(px2<=14 && py2<=14 && draughtBoard[py2][px2]==player){
count++
px2++
py2++
if(count>=5) return true
continue
}
return false
}
}
//反斜
function f4( draughtBoard:DraughtBoard[], x:number, y:number, player:number ){
let count:number = 1 //已连成线的棋子
let px1:number = x+1 //横坐标 右上 4
let py1:number = y-1 //纵坐标 右上 0
let px2:number = x-1 //横坐标 左下 2
let py2:number = y+1 //纵坐标 左下 2
while(true){
if(px1<=14 && py1>=0 && draughtBoard[py1][px1]==player){
count++
px1++
py1--
console.log('右上',px1,py1)
if(count>=5) return true
continue
}
if(px2>=0 && py2<=14 && draughtBoard[py2][px2]==player){
count++
px2--
py2++
console.log('左下',px2,py2)
if(count>=5) return true
continue
}
return false
}
}
function win(draughtBoard:DraughtBoard[], x:number, y:number, player:number){
console.log('',x,y,player)
return ( f1(draughtBoard, x, y, player) ||
f2(draughtBoard, x, y, player) ||
f3(draughtBoard, x, y, player) ||
f4(draughtBoard, x, y, player) )
}
完整代码:
@Observed
class DraughtBoard extends Array<number>{
}
//横
function f1( draughtBoard:DraughtBoard[], x:number, y:number, player:number ){
let count:number = 1 //已连成线的棋子
let p1:number = x-1 //横坐标 起点
let p2:number = x+1 //横坐标 终点
while(true){
if(p1>=0&&draughtBoard[y][p1]==player){
count++
p1--
if(count>=5) return true
continue
}
if(p2<=14&&draughtBoard[y][p2]==player){
count++
p2++
if(count>=5) return true
continue
}
return false //未够5子
}
}
//竖
function f2( draughtBoard:DraughtBoard[], x:number, y:number, player:number ){
let count:number = 1 //已连成线的棋子
let p1:number = y-1 //纵坐标 起点
let p2:number = y+1 //纵坐标 起点
while(true){
if(p1>=0&&draughtBoard[p1][x]==player){
count++
p1--
if(count>=5) return true
continue
}
if(p2<=14&&draughtBoard[p2][x]==player){
count++
p2++
if(count>=5) return true
continue
}
return false //未够5子
}
}
//斜
function f3( draughtBoard:DraughtBoard[], x:number, y:number, player:number ){
let count:number = 1 //已连成线的棋子
let px1:number = x-1 //横坐标 左上
let py1:number = y-1 //纵坐标 左上
let px2:number = x+1 //横坐标 右下
let py2:number = y+1 //纵坐标 右下
while(true){
if(px1>=0 && py1>=0 && draughtBoard[py1][px1]==player){
count++
px1--
py1--
if(count>=5) return true
continue
}
if(px2<=14 && py2<=14 && draughtBoard[py2][px2]==player){
count++
px2++
py2++
if(count>=5) return true
continue
}
return false
}
}
//反斜
function f4( draughtBoard:DraughtBoard[], x:number, y:number, player:number ){
let count:number = 1 //已连成线的棋子
let px1:number = x+1 //横坐标 右上 4
let py1:number = y-1 //纵坐标 右上 0
let px2:number = x-1 //横坐标 左下 2
let py2:number = y+1 //纵坐标 左下 2
while(true){
if(px1<=14 && py1>=0 && draughtBoard[py1][px1]==player){
count++
px1++
py1--
console.log('右上',px1,py1)
if(count>=5) return true
continue
}
if(px2>=0 && py2<=14 && draughtBoard[py2][px2]==player){
count++
px2--
py2++
console.log('左下',px2,py2)
if(count>=5) return true
continue
}
return false
}
}
function win(draughtBoard:DraughtBoard[], x:number, y:number, player:number){
console.log('',x,y,player)
return ( f1(draughtBoard, x, y, player) ||
f2(draughtBoard, x, y, player) ||
f3(draughtBoard, x, y, player) ||
f4(draughtBoard, x, y, player) )
}
// 每一行棋封装成组件
@Component
struct draughtBoardRow{
@ObjectLink draughtBoardItem:DraughtBoard //每一行棋
@Link draughtBoard:DraughtBoard[] //整幅棋的棋盘 为了便于判断胜利 使用@Link进行双向传递
@Link player:boolean // 判断下棋的为黑棋还是白棋 true 黑 false 白
@State y:number=0 //棋盘中的第几行棋 为了判断胜利
@State colors:ResourceColor[]=[Color.Transparent, Color.Black, Color.White] //棋盘中的棋子颜色 默认透明
@Link winner:number // 判断胜利者 0 未胜利 1黑棋胜 2白棋胜
@Link history:number[][] // 棋盘 15行棋
build() {
Row(){
ForEach(this.draughtBoardItem,(item:number,index:number)=>{ //渲染这一行棋
Text()
.margin(1)
.borderRadius(10)
.backgroundColor(this.colors[item])
.height(20)
.aspectRatio(1)
.onClick(()=>{
//点击下棋 渲染棋子的颜色
if(item==0){
if (this.player) this.draughtBoardItem[index] = 1
else this.draughtBoardItem[index] = 2
// 胜利处理
if(win(this.draughtBoard, index, this.y, this.draughtBoardItem[index]))
this.winner = this.draughtBoardItem[index]
// 记录下棋的位置 实现悔棋功能
this.history.push([this.y, index])
// 交换下棋者
this.player = !this.player
}
})
})
}
}
}
@Entry
@Component
struct Index {
// 判断下棋的为黑棋还是白棋 true 黑 false 白
@State player:boolean=true
// 判断胜利者 0 未胜利 1黑棋胜 2白棋胜
@State winner:number=0
// 棋盘 15行棋
@State draughtBoard:DraughtBoard[] = [
new DraughtBoard(),
new DraughtBoard(),
new DraughtBoard(),
new DraughtBoard(),
new DraughtBoard(),
new DraughtBoard(),
new DraughtBoard(),
new DraughtBoard(),
new DraughtBoard(),
new DraughtBoard(),
new DraughtBoard(),
new DraughtBoard(),
new DraughtBoard(),
new DraughtBoard(),
new DraughtBoard()
]
// 记录已下棋子的坐标和顺序 实现悔棋功能用
@State history:number[][]= []
aboutToAppear(): void {
// 初始化棋盘 0为未下棋 1为已下白棋 2为已下黑棋
for(let i=0;i<this.draughtBoard.length;i++)
for(let j=0;j<this.draughtBoard.length;j++)
this.draughtBoard[j].push(0)
}
build() {
Column(){
Row(){
//提示当前下棋者
Text('当前'+(this.player?'黑子':'白子')+'下棋')
//当前下棋的棋子 颜色区分
Text()
.height(25)
.aspectRatio(1)
.borderRadius(13)
.backgroundColor(this.player?Color.Black:Color.White)
// 重开按钮 将棋盘重新渲染为0
Button('重开')
.onClick(()=>{
this.draughtBoard.forEach((item0:DraughtBoard,index0:number)=>{
item0.forEach((item:number,index:number)=>{
this.draughtBoard[index0][index]=0
this.player = true
})
this.winner = 0
})
})
//悔棋 从历史记录中拿出最后一个 将棋盘中对应的位置置为0 并且要交换下棋者
Button('悔棋')
.onClick(()=>{
this.draughtBoard[this.history[this.history.length-1][0]]
[this.history[this.history.length-1][1]] = 0
this.history.pop()
this.player = !this.player
})
//已无记录或已胜利 按钮失效
.enabled(this.history.length>0&&this.winner==0)
}
.backgroundColor('#ff69bdfa')
.height(60)
.width('100%')
// 胜利处理
Column(){
Text(`恭喜${this.winner==2?'白子':'黑子'}胜利`)
.fontSize(21)
.fontWeight(700)
Button('重新开始')
.onClick(()=>{
this.draughtBoard.forEach((item0:DraughtBoard,index0:number)=>{
item0.forEach((item:number,index:number)=>{
this.draughtBoard[index0][index]=0
this.player = true
})
})
this.winner = 0
})
}
.opacity(this.winner)
// 绘制棋盘
Stack(){
//棋盘的线条和颜色 若不处理 则会导致下棋的位置在格子中
Column(){
ForEach([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],()=>{
Row(){
ForEach([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],()=>{
Text()
.height(22)
.aspectRatio(1)
.borderWidth(1)
})
}
})
}
.backgroundColor('#c09351')
//下棋的位置
Column(){
ForEach(this.draughtBoard,(item:DraughtBoard, index:number)=>{
// 每一行棋
draughtBoardRow({draughtBoardItem:item, player:this.player,
y:index, draughtBoard:this.draughtBoard, winner:this.winner,
history:this.history})
})
}
//胜利后禁止点击再下棋
Column()
.width('100%')
.height('100%')
.zIndex(this.winner==0?-1:1)
}
.width('100%')
.height(22*16)
}
.width('100%')
.height('100%')
}
}