image.png

游戏介绍

黑白棋,又称翻转棋(Reversi)、奥赛罗棋(Othello)或苹果棋,是一种两人对弈的策略棋类游戏。以下是其基本规则:

  1. 棋盘: 使用标准的 8x8 棋盘。起始时在中间放置四枚棋子,形成对角线交叉的模样。
  2. 玩家身份: 一方执黑棋,另一方执白棋。
  3. 开局: 游戏开始时,中间的四颗棋子按照黑白相间的方式排列。
  4. 落子: 轮流由黑方和白方落子。每一步,玩家在一个空格上放置自己的棋子,使对方的一个或多个棋子被夹在新放置的棋子和已有的棋子之间。
  5. 翻转对方棋子: 落子后,夹在新放置的棋子和已有的棋子之间的对方棋子会被翻转变成己方棋子。
  6. 无子可落时: 如果一方无法进行合法的落子,则该方放弃该轮,对方继续行动。
  7. 游戏结束: 游戏结束时,棋盘被填满或者双方都无法进行合法的落子。胜者是拥有更多棋子的一方。
  8. 胜负判定: 胜者是拥有最多棋子的一方。

完整代码和资源

实现思路

  • 说明:只是例举核心思路,直接拷贝代码,不一定能运行

    主页面布局

    image.png

画背景和棋盘

  1. def paintEvent(self, event: QPaintEvent): # 绘图事件
  2. # 调用父类同名方法
  3. super().paintEvent(event)
  4. # 定义画家(请了一个画家来画画),画在主窗口上
  5. painter = QPainter(self)
  6. # 以窗口大小画背景图
  7. painter.drawPixmap(0, 0, self.width(), self.height(),
  8. QPixmap('./img/board.jpg') )
  9. # 窗口宽度分12份
  10. self.grid_width = self.width() // 12
  11. # 窗口高度分12份
  12. self.grid_height = self.height() // 12
  13. # 棋盘起点坐标为grid_width, grid_height
  14. self.start_x = self.grid_width*2
  15. self.start_y = self.grid_height*2
  16. # 设置线宽
  17. line_width = 4
  18. painter.setPen(QPen(Qt.black, line_width))
  19. # 取中间8份画棋盘
  20. for i in range(9):
  21. # 画横线
  22. painter.drawLine(self.start_x, self.start_y + self.grid_height * i,
  23. self.start_x + self.grid_width * 8, self.start_y + self.grid_height * i)
  24. # 画竖线
  25. painter.drawLine(self.start_x + self.grid_width * i, self.start_y,
  26. self.start_x + self.grid_width * i, self.start_y + self.grid_height * 8)

标志棋盘棋子状态

  1. # 标志棋子状态
  2. EMPTY = 0
  3. BLACK = 1
  4. WHITE = 2
  5. # 创建一个8x8的二维数组
  6. # EMPTY 0表示没有棋子,BLACK 1表示黑棋,WHITE 2表示白棋
  7. self.chess = [[EMPTY for _ in range(8)] for _ in range(8)]
  8. # 设置棋盘中间4个棋子状态
  9. self.chess[3][3] = BLACK
  10. self.chess[3][4] = WHITE
  11. self.chess[4][3] = WHITE
  12. self.chess[4][4] = BLACK

吃子规则算法

  1. def judge_rule(x, y, chess, current_role, eat_chess = True):
  2. """
  3. 功能:吃子的规则
  4. :param x: 棋盘x下标
  5. :param y: 棋盘y下标
  6. :param chess: 棋子状态数组
  7. :param current_role: 棋子角色
  8. :param eat_chess: 默认为True,代表改变原来的数组, False不改变数组内容
  9. """
  10. # 棋盘的八个方向
  11. directions = [(1, 0), (1, -1), (0, -1), (-1, -1), (-1, 0), (-1, 1), (0, 1), (1, 1)]
  12. temp_x, temp_y = x, y
  13. eat_num = 0
  14. if chess[temp_x][temp_y] != EMPTY:
  15. return 0
  16. for dx, dy in directions:
  17. temp_x, temp_y = x + dx, y + dy # 准备判断相邻棋子
  18. if 0 <= temp_x < len(chess) and 0 <= temp_y < len(chess) and \
  19. chess[temp_x][temp_y] != current_role and chess[temp_x][temp_y] != EMPTY:
  20. temp_x, temp_y = x + dx, y + dy # 继续判断下一个,向前走一步
  21. while 0 <= temp_x < len(chess) and 0 <= temp_y < len(chess):
  22. if chess[temp_x][temp_y] == EMPTY: # 遇到空位跳出
  23. break
  24. if chess[temp_x][temp_y] == current_role: # 找到自己的棋子,代表可以吃子
  25. if eat_chess: # 确定吃子
  26. chess[x][y] = current_role # 开始点标志为自己的棋子
  27. # 添加范围检查,确保不越界
  28. while 0 <= temp_x < len(chess) and 0 <= temp_y < len(chess) and \
  29. (temp_x, temp_y) != (x, y):
  30. chess[temp_x][temp_y] = current_role # 标志为自己的棋子
  31. temp_x, temp_y = temp_x - dx, temp_y - dy # 继续后退一步
  32. eat_num += 1 # 累计
  33. else: # 不吃子,只是判断这个位置能不能吃子
  34. temp_x, temp_y = temp_x - dx, temp_y - dy # 后退一步
  35. # 添加范围检查,确保不越界
  36. while 0 <= temp_x < len(chess) and 0 <= temp_y < len(chess) and \
  37. (temp_x, temp_y) != (x, y):
  38. temp_x, temp_y = temp_x - dx, temp_y - dy # 继续后退一步
  39. eat_num += 1
  40. break # 跳出循环
  41. temp_x, temp_y = temp_x + dx, temp_y + dy # 向前走一步
  42. # 如果这个方向不能吃子,就换一个方向
  43. temp_x, temp_y = x, y
  44. return eat_num # 返回能吃子的个数

切换角色

  1. def switch_role(self):
  2. """切换下子角色"""
  3. if self.current_role == BLACK:
  4. self.current_role = WHITE
  5. # 显示黑,隐藏白
  6. self.ui.labelBlack.hide()
  7. self.ui.labelWhite.show()
  8. else:
  9. self.current_role = BLACK
  10. # 显示白,隐藏黑
  11. self.ui.labelBlack.show()
  12. self.ui.labelWhite.hide()

用户点击落子

  1. def mousePressEvent(self, event: QMouseEvent): # 鼠标按下事件
  2. # 调用父类同名方法
  3. super().mousePressEvent(event)
  4. # 获取 x, y 坐标
  5. x = event.x()
  6. y = event.y()
  7. # 范围判断,点击范围不要超过棋盘
  8. if (
  9. x >= self.start_x
  10. and x <= self.start_x + self.grid_width * 8
  11. and y >= self.start_y
  12. and y <= self.start_y + self.grid_height * 8
  13. ):
  14. self.press_i = (x - self.start_x) // self.grid_width
  15. self.press_j = (y - self.start_y) // self.grid_height
  16. print(self.press_i, self.press_j)
  17. # self.chess[self.press_i][self.press_j] = self.current_role
  18. # 吃子
  19. eat_num = judge_rule(self.press_i, self.press_j, self.chess, self.current_role)
  20. if eat_num > 0: # 成功吃子后再操作
  21. self.switch_role() # 切换下子角色
  22. self.update() # 更新绘图区域

机器落子

  1. def machineTimerSlot(self):
  2. flag = False
  3. # 找到能吃子的位置
  4. for i in range(8):
  5. for j in range(8):
  6. num = judge_rule(i, j, self.chess, self.current_role, False)
  7. if num > 0:
  8. self.press_i = i # 保存能吃子的第一个位置
  9. self.press_j = j
  10. flag = True
  11. break;
  12. if flag:
  13. break
  14. if flag == False: # 说明机器不能吃子
  15. self.switch_role() # 切换角色
  16. return
  17. # 吃子
  18. judge_rule(self.press_i, self.press_j, self.chess, self.current_role)
  19. # 更新绘图
  20. self.update()
  21. self.switch_role() # 切换角色

判断输赢

  1. def judge_result(self):
  2. """判断输赢:双方都不能吃子即可判断输赢"""
  3. black_count = 0
  4. white_count = 0
  5. is_over = True # 结束标志位,默认为True,代表结束
  6. for i in range(8):
  7. for j in range(8):
  8. if self.chess[i][j] == BLACK:
  9. black_count += 1
  10. elif self.chess[i][j] == WHITE:
  11. white_count += 1
  12. # 判断黑白子在i, j位置,能否吃子,没有改变chess的数组
  13. # > 0,说明能吃子,游戏还没结束
  14. if judge_rule(i, j, self.chess, BLACK, False) > 0 \
  15. or judge_rule(i, j, self.chess, WHITE, False) > 0:
  16. is_over = False # 游戏还没结束
  17. # lcd显示黑白棋个数
  18. self.ui.lcdBlack.display(black_count)
  19. self.ui.lcdWhite.display(white_count)
  20. if is_over == False: # 游戏还没结束,不往下执行
  21. return
  22. self.ui.labelBlack.show()
  23. self.ui.labelWhite.show()
  24. # 判断输赢,弹出对话框显示谁赢
  25. if black_count > white_count:
  26. QMessageBox.information(self, '黑棋胜利', '黑棋胜利')
  27. elif black_count < white_count:
  28. QMessageBox.information(self, '白棋胜利', '白棋胜利')
  29. else:
  30. QMessageBox.information(self, '平局', '平局')