站点图标 AI技术聚合

Java实现五子棋小游戏(附思路讲解,全部代码,游戏截图)

本文章是如何实现一个单机版双人五子棋小游戏,通过Swing技术进行可视操作.

个人简介:

🤦‍♂️个人主页:码云不秃头.

😜本人是一名大三学生,马上就要变成考研狗啦,通过一学期对Java学习,经过老师的教学,实现单机版的双人五子棋小游戏,大家互相学习,也同时为了完成我的实验报告,如有不足请多指教.⚽⚽

🎉如果这篇文章对你有用的话,麻烦点赞收藏走一波,以后我也会继续写一些有关Java知识的博客,感谢支持!谢谢!.

前言

大家好,今天我们用Swing技术来编写一个单机双人五子棋对战小游戏适合和朋友一起玩,本篇文章是针对有一些Java基础的同学学习,有编程基础的同学也可以看懂哦.

部分效果展示


目录

前言

部分效果展示

一、项目说明

1.五子棋介绍

2.功能需求:

二、需求分析

三.系统概要设计

代码展示

总结

一、项目说明

1.五子棋介绍

        五子棋是 全国智力运动会 竞技项目之一,是一种两人对弈的纯策略型棋类游戏。 五子棋有两种玩法。 玩法一:双方分别使用黑白两色的棋子,下在棋盘直线与横线的交叉点上,先形成五子连线者获胜。 玩法二:自己形成五子连线就替换对方任意一枚棋子。 被替换的棋子可以和对方交换棋子。 最后以先出完所有棋子的一方为胜。

2.功能需求:

(1)实现人与人对战.

(2)游戏开始时要求为空棋盘.

(3)黑先,白后,交替下子,每次只能下一个棋子.

(4)棋子下定后不可移动.

(5)不可以悔棋.

(6)可以有一方在下棋时可以认输,弃权.

(7)可以重新开始游戏,直到有一方胜利对局结束.

(8)要有图形界面设计美观,交互性好.

(9)需要有游戏计时,到某方下棋时会在游戏界面提醒,到某方下棋.

二、需求分析

(1)对系统的数据结构进行分析:

本游戏使用的棋盘是14*14规格的棋盘,当然这个在代码中可以更改,由于我所选图片为了美观所以棋盘用的是14*14,对于棋子我们使用大小为allChess[16][16]这样可能会浪费空间但可以坐标与棋子对应的数组位置相等,也防止在判断五子连珠时的数组越界问题,这里大家也可以想想其他方法,感谢纠正.

(2)对系统的界面分析

系统的布局为左右布局左边为棋盘,右边则是负责功能的按钮,整个棋盘的大小为1080*850像素,有一个桌面背景图在棋盘下方分别记录对棋手的剩余时间,,棋盘上方为提示某方下棋防止下乱,要求界面美观大方.

(3)系统的按钮功能分析

本游戏有开始游戏,游戏说明,关于,游戏设置,认输,退出游戏.对于开始游戏我们可以在对棋过程中可重新开始游戏,将棋盘设为初始状态,黑棋先手,游戏说明关于游戏的玩法介绍,游戏设置可以进行游戏时间的调整,当游戏时间输入为0时,则时间无限制.认输结束这盘棋子提示是否要弃权,弃权后另一方获胜,之后棋盘不允许在进行下棋.退出游戏直接退出程序.

三.系统概要设计

实现过程:

1.绘制窗体对象。

2.UI设计(包括游戏区域、黑白棋子、按钮和标题区域)。

3.使用鼠标监听事件实现下棋。

4.连成五子判定获胜的实现。

5.利用线程进行对倒计时的显示

代码展示

在这里我写了一个测试类一个棋盘类

测试类

package FiveChess;


import javax.swing.JOptionPane;

import FiveChessFrame.ChessFrame;

public class Test {
	public static void main(String[] args) {
		/* MyChessFrame mf = new MyChessFrame(); */
//		JOptionPane.showMessageDialog(mf, "我的信息");//显示一个消息对话框,主要用来提示信息
//		int result = JOptionPane.showConfirmDialog(mf,"我的确认信息,现在要开始游戏吗?");//创建一个选择提示窗口,是0否1取消2 
//		if (result == 0) {
//		JOptionPane.showMessageDialog(mf, "游戏开始!"); }
//		if(result == 1) {
//		JOptionPane.showMessageDialog(mf, "游戏结束!"); } 
//		if(result == 2) {
//		JOptionPane.showMessageDialog(mf, "请重新选择!"); }
		
		
		
//		String username = JOptionPane.showInputDialog("请输入你的姓名:   ");//显示一个窗口进行输入信息
//		if (username !=null) {
//		System.out.println(username);
//		JOptionPane.showMessageDialog(mf, "输入的姓名为:  "+username);
//		}else {
//			JOptionPane.showMessageDialog(mf, "请重新输入你的用户名!");
//		}
		ChessFrame ff= new ChessFrame();
		
		
		
		
	}
}

棋盘类

package FiveChessFrame;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JOptionPane;

public class ChessFrame extends JFrame implements MouseListener, Runnable {
	int width = Toolkit.getDefaultToolkit().getScreenSize().width;// 获取屏幕宽度和宽度
	int height = Toolkit.getDefaultToolkit().getScreenSize().height;
	BufferedImage bgimage = null;
	// 保存棋子的坐标
	int x = 0;
	int y = 0;
	float nx = 0;
	float ny = 0;
	// 保存之前所有下过棋子的坐标,0表示这个点没有棋子,1这个点有黑棋子,2这个点有白棋子
	int[][] allChess = new int[16][16];
	// 表识当前是黑棋还是白棋
	boolean isBlack = true;
	// 标识当前游戏是否可以继续
	boolean canPlay = true;
	String message = "黑方先行";
	// 保存最多拥有多少时间(秒)线程操作
	int maxTime = 0;
	// 做倒计时线程类
	Thread t = new Thread(this);
	// 保存黑方与白方的剩余时间
	int blackTime = 0;
	int whiteTime = 0;
	// 保存双方剩余时间的信息
	String blackMessage = "无限制";
	String whiteMessage = "无限制";
	public ChessFrame() {
		// 设置标题
		this.setTitle("五子棋");
		// 设置窗口大小
		this.setSize(1080, 850);
		// 设置窗口位置
		this.setLocation((width - 1000) / 2, (height - 1000) / 2);// 设置窗口位置
		// 将窗口设置为大小不改变
		this.setResizable(false);
		// 将窗体的关闭方式设置为默认关闭程序结束
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		// 为窗体加入监听
		this.addMouseListener(this);
		// 导入图片
		try {
			bgimage = ImageIO.read(getClass().getResource("Hz.jpg"));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.setVisible(true);
		t.start();
		t.suspend();
		//刷新屏幕防止开始游戏时无法显示
		this.repaint();
	}

	public void paint(Graphics g) {
		// 双缓冲技术防止屏幕闪烁
		BufferedImage bi = new BufferedImage(1080, 850, BufferedImage.TYPE_INT_ARGB);
		Graphics g2 = bi.createGraphics();

		// 绘制背景
		g2.drawImage(bgimage, 0, 0, this);
		// 输出标题信息
		g2.setColor(Color.BLACK);
		g2.setFont(new Font("黑体", Font.BOLD, 50));
		g2.drawString("游戏信息:" + message, 70, 90);
		g2.setFont(new Font("宋体", 30, 30));
		g2.drawString("黑方时间:" + blackMessage, 75, 813);
		g2.drawString("白方时间:" + whiteMessage, 440, 813);
		g2.setColor(Color.WHITE);
		g2.setFont(new Font("黑体", Font.BOLD, 30));
		g2.drawString("开始游戏", 910, 130);
		g2.drawString("游戏说明", 910, 240);
		g2.drawString("退出游戏", 910, 590);
		g2.drawString("认输", 940, 470);
		g2.drawString("关于", 940, 360);
		g2.setColor(Color.ORANGE);
		g2.fillOval(900, 650, 150, 100);
		g2.fillOval(880, 640, 50, 50);
		g2.fillOval(1020, 640, 50, 50);
		g2.setColor(Color.WHITE);
		g2.drawString("游戏设置", 910, 710);
		g2.setColor(Color.BLACK);
		g2.setFont(new Font("黑体", Font.BOLD, 20));
//		g2.drawString("作者:信科2001蜜小桃", 850, 770);
//		g2.drawString("指导教师:王老师", 850, 800);
		// 绘制棋盘
		for (int i = 0; i <= 13; i++) {
			g2.drawLine(150 + i * 50, 125, 150 + i * 50, 775);
			g2.drawLine(150, 125 + i * 50, 800, 125 + i * 50);
		}
		// 绘制所有棋子
		for (int i = 1; i <= 14; i++) {
			for (int j = 1; j <= 14; j++) {
				if (allChess[i][j] == 1) {
					// 黑棋子
					int tempx = (i - 1) * 50 + 150;
					int tempy = (j - 1) * 50 + 125;
					g2.fillOval(tempx - 20, tempy - 20, 40, 40);
				}
				if (allChess[i][j] == 2) {
					// 白棋子
					int tempx = (i - 1) * 50 + 150;
					int tempy = (j - 1) * 50 + 125;
					g2.setColor(Color.WHITE);
					g2.fillOval(tempx - 20, tempy - 20, 40, 40);
					g2.setColor(Color.BLACK);
					g2.drawOval(tempx - 20, tempy - 20, 40, 40);
				}
			}
		}
		g.drawImage(bi, 0, 0, this);
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mousePressed(MouseEvent e) {
		// TODO Auto-generated method stub
		if (canPlay == true) {
			nx = e.getX();
			ny = e.getY();
			if (nx >= 150 && nx <= 800 && ny >= 125 && ny <= 775) {
				x = Math.round((nx - 150) / 50) + 1;
				y = Math.round((ny - 125) / 50) + 1;
				// 判断当前位置是否有棋子
				if (allChess[x][y] == 0) {
					// 判断当前下的是什么颜色的棋子
					if (isBlack == true) {
						allChess[x][y] = 1;
						isBlack = false;
						message = "轮到白方";
					} else {
						allChess[x][y] = 2;
						isBlack = true;
						message = "轮到黑方";
					}
					// 判断这个棋子是否与其他的棋子连成5个
					boolean flagwin = this.CheckWin();
					if (flagwin == true) {
						JOptionPane.showMessageDialog(this, "游戏结束," + (allChess[x][y] == 1 ? "黑方" : "白方") + "获胜! ");
						canPlay = false;
						message = "游戏结束!";
					}
				} else {
					JOptionPane.showMessageDialog(this, "当前位置已经有棋子了你不能在下了!");
				}

				this.repaint();
			}
		}
		// 判断鼠标所点击的位置在哪个按钮的区域
		boolean in = this.InRange(900, 85, 1050, 150, e.getX(), e.getY());
		if (in == true) {
			int result = JOptionPane.showConfirmDialog(this, "是否重新开始游戏!");
			// 现在重新开始游戏
			if (result == 0) {
				JOptionPane.showMessageDialog(this, "开始游戏!");
				// 重新开始所要做的操作
				// 1.棋盘清空,把allChess[][]归零;
				for (int i = 1; i <= 14; i++) {
					for (int j = 1; j <= 14; j++) {
						allChess[i][j] = 0;
					}
				}
				// 这里也可以用allChess=new int[16][16];但是会浪费空间
				// 2.将游戏信息的显示改为初始;
				message = "黑方先行";
				// 3.将下一步下棋的人改为黑方isBlack=true;
				isBlack = true;
				blackTime = maxTime;
				whiteTime = maxTime;
				if (maxTime > 0) {
					blackMessage = maxTime / 3600 + ":" + (maxTime / 60 - maxTime / 3600 * 60) + ":"
							+ (maxTime - maxTime / 60 * 60);
					whiteMessage = maxTime / 3600 + ":" + (maxTime / 60 - maxTime / 3600 * 60) + ":"
							+ (maxTime - maxTime / 60 * 60);
				}else {
					blackMessage = "无限制";
					whiteMessage = "无限制";
				}
				this.repaint();
			}
		} else {
			in = this.InRange(900, 200, 1050, 260, e.getX(), e.getY());
			if (in == true) {
				JOptionPane.showMessageDialog(this, "这是一个五子棋小游戏,黑白双方轮流下棋,当某一方连到五子时游戏结束!");
			} else {
				in = this.InRange(900, 315, 1050, 380, e.getX(), e.getY());
				if (in == true) {
					JOptionPane.showMessageDialog(this,
							"本游戏由信科2001蜜小桃,制作指导教师为我最敬爱的王老师,感谢老师!,如有问题可以加QQ:2632814845询问问题,我也不一定会,试试吧!");
				} else {
					in = this.InRange(900, 423, 1050, 485, e.getX(), e.getY());
					if (in == true) {
						int result = JOptionPane.showConfirmDialog(this, "是否确认认输!");
						if (result == 0) {
							JOptionPane.showMessageDialog(this,
									(isBlack == true ? "黑方认输," : "白方认输,") + (isBlack == true ? "白方获胜!" : "黑方获胜!"));
							canPlay = false;
						}
					} else {
						in = this.InRange(900, 540, 1050, 605, e.getX(), e.getY());
						if (in == true) {
							JOptionPane.showMessageDialog(this, "退出游戏");
							System.exit(0);
						} else {
							in = this.InRange(900, 670, 1050, 725, e.getX(), e.getY());
							if (in == true) {
								String input = JOptionPane.showInputDialog("请输入游戏的最大时间!(单位:分钟),输入0表示没有时间限制.");
								try {
									maxTime = Integer.parseInt(input) * 60;
									if (maxTime < 0) {
										JOptionPane.showMessageDialog(this, "请正确输入信息!不允许输入负数.");
									}
									if (maxTime == 0) {
										int result = JOptionPane.showConfirmDialog(this, "设置完成!是否重新开始游戏!");
										if (result == 0) {
											for (int i = 1; i <= 14; i++) {
												for (int j = 1; j <= 14; j++) {
													allChess[i][j] = 0;
												}
											}
											message = "黑方先行";
											isBlack = true;
											blackTime = maxTime;
											whiteTime = maxTime;
											blackMessage = "无限制";
											whiteMessage = "无限制";
											this.repaint();
										}
									}
									if (maxTime > 0) {
										int result = JOptionPane.showConfirmDialog(this, "设置完成!是否重新开始游戏!");
										if (result == 0) {
											for (int i = 1; i <= 14; i++) {
												for (int j = 1; j <= 14; j++) {
													allChess[i][j] = 0;
												}
											}
											message = "黑方先行";
											isBlack = true;
											blackTime = maxTime;
											whiteTime = maxTime;
											blackMessage = maxTime / 3600 + ":" + (maxTime / 60 - maxTime / 3600 * 60)
													+ ":" + (maxTime - maxTime / 60 * 60);
											whiteMessage = maxTime / 3600 + ":" + (maxTime / 60 - maxTime / 3600 * 60)
													+ ":" + (maxTime - maxTime / 60 * 60);
											t.resume();
											this.repaint();
										}
									}
								} catch (NumberFormatException e1) {
									// TODO Auto-generated catch block
									JOptionPane.showMessageDialog(this, "请正确输入信息!");

								}
							}
						}
					}
				}
			}
		}
		;
	}

	

	// 判断区域方法
	private boolean InRange(int x1, int y1, int x2, int y2, int x, int y) {
		boolean In = false;
		if (x >= x1 && x <= x2 && y >= y1 && y <= y2) {
			In = true;
		}
		return In;
	}

	// 判断胜利
	private boolean CheckWin() {
		boolean flag = false;
//		// 保存有多少棋子相连
//		int count = 1;
//		// 判断横向是否有五个棋子相连:特点纵坐标相同,及判断y值相同
//		// 通过循环来做判断
//		int color = allChess[x][y];
//		int i = 1;
//		while (color == allChess[x + i][y]) {
//			count++;
//			i++;
//		}
//		i = 1;
//		while (color == allChess[x - i][y]) {
//			count++;
//			i++;
//		}
//		if (count >= 5) {
//			flag = true;
//		}
//		int i2 = 1;
//		int count2 = 1;
//		while (color == allChess[x][y + i2]) {
//			count2++;
//			i2++;
//		}
//		i2 = 1;
//		while (color == allChess[x][y - i2]) {
//			count2++;
//			i2++;
//		}
//		if (count2 >= 5) {
//			flag = true;
//		}
//		// 斜方向的判断(右上+左下)
//		int i3 = 1;
//		int count3 = 1;
//		while (color == allChess[x + i3][y - i3]) {
//			count3++;
//			i3++;
//		}
//		i3 = 1;
//		while (color == allChess[x - i3][y + i3]) {
//			count3++;
//			i3++;
//		}
//		if (count3 >= 5) {
//			flag = true;
//		}
//		// 斜方向的判断(左上+右下)
//		int i4 = 1;
//		int count4 = 1;
//		while (color == allChess[x + i4][y + i4]) {
//			count4++;
//			i4++;
//		}
//		i4 = 1;
//		while (color == allChess[x - i4][y - i4]) {
//			count4++;
//			i4++;
//		}
//		if (count4 >= 5) {
//			flag = true;
//		}
		int count = 0;
		int color = allChess[x][y];
		// 判断横向
		count = this.checkCount(1, 0, color);
		if (count >= 5) {
			flag = true;
		} else {
			// 判断纵向
			count = this.checkCount(0, 1, color);
			if (count >= 5) {
				flag = true;
			} else {
				// 判断右上,左下
				count = this.checkCount(1, -1, color);
				if (count >= 5) {
					flag = true;
				} else {
					// 判断右下,左上
					count = this.checkCount(1, 1, color);
					if (count >= 5) {
						flag = true;
					}
				}
			}
		}
		return flag;
	}

//判断胜利总结的方法避免上方的代码冗余的问题
	private int checkCount(int xChange, int yChange, int color) {
		int count = 1;
		int TempX = xChange;
		int TempY = yChange;
		while (color == allChess[x + xChange][y + yChange]) {
			count++;
			if (xChange != 0) {
				xChange++;
			}
			if (yChange != 0) {
				if (yChange > 0) {
					yChange++;
				} else {
					yChange--;
				}
			}
		}
		xChange = TempX;
		yChange = TempY;
		while (color == allChess[x - xChange][y - yChange]) {
			count++;
			if (xChange != 0) {
				xChange++;
			}
			if (yChange != 0) {
				if (yChange > 0) {
					yChange++;
				} else {
					yChange--;
				}
			}
		}

		return count;
	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		// 判断是否有时间的限制
		if (maxTime > 0) {
			while (true) {
				if (isBlack) {
					blackTime--;
					if(blackTime==0) {
						JOptionPane.showMessageDialog(this, "黑方超时游戏结束!");
						canPlay=false;
					}
				} else {
					whiteTime--;
					if(whiteTime==0) {
						JOptionPane.showMessageDialog(this, "白方超时游戏结束!");
						canPlay=false;
					}
				}
				blackMessage = blackTime / 3600 + ":" + (blackTime / 60 - blackTime / 3600 * 60) + ":"
						+ (blackTime - blackTime / 60 * 60);
				whiteMessage = whiteTime / 3600 + ":" + (whiteTime / 60 - whiteTime / 3600 * 60) + ":"
						+ (whiteTime - whiteTime / 60 * 60);
				this.repaint();
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

}

这就是本五子棋游戏的全部代码.当然在导入背景图

我的背景图

 大家导入背景图是一定要修改一下名字确保导入的路径相同

// 导入图片
		try {
			bgimage = ImageIO.read(getClass().getResource("Hz.jpg"));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

本代码中间有大片注释为解决问题的第二种方法.

下面进行部分代码讲解:

我们五子棋游戏主要解决的就是如何下棋和怎么判断输赢.

if (canPlay == true) {
			nx = e.getX();
			ny = e.getY();
			if (nx >= 150 && nx <= 800 && ny >= 125 && ny <= 775) {
				x = Math.round((nx - 150) / 50) + 1;
				y = Math.round((ny - 125) / 50) + 1;
				// 判断当前位置是否有棋子
				if (allChess[x][y] == 0) {
					// 判断当前下的是什么颜色的棋子
					if (isBlack == true) {
						allChess[x][y] = 1;
						isBlack = false;
						message = "轮到白方";
					} else {
						allChess[x][y] = 2;
						isBlack = true;
						message = "轮到黑方";
					}

这一段代码为通过鼠标监听获取鼠标位置进行数学运算映射到一个二维数组上从而实现下棋的功能.

由于多次用到鼠标是否在一定的区域内,所以单独写了一个方法.

// 判断区域方法
	private boolean InRange(int x1, int y1, int x2, int y2, int x, int y) {
		boolean In = false;
		if (x >= x1 && x <= x2 && y >= y1 && y <= y2) {
			In = true;
		}
		return In;
	}

该函数的返回值类型为Boolean调用时需要输入6个参数分别范围左上角和右下角的坐标和当前鼠标所点击的位置.在范围内返回true,反之返回false.

private int checkCount(int xChange, int yChange, int color) {
		int count = 1;
		int TempX = xChange;
		int TempY = yChange;
		while (color == allChess[x + xChange][y + yChange]) {
			count++;
			if (xChange != 0) {
				xChange++;
			}
			if (yChange != 0) {
				if (yChange > 0) {
					yChange++;
				} else {
					yChange--;
				}
			}
		}
		xChange = TempX;
		yChange = TempY;
		while (color == allChess[x - xChange][y - yChange]) {
			count++;
			if (xChange != 0) {
				xChange++;
			}
			if (yChange != 0) {
				if (yChange > 0) {
					yChange++;
				} else {
					yChange--;
				}
			}
		}

		return count;
	}

这里是判断胜利的方法也可以用下一种方法

boolean flag = false;
// 保存有多少棋子相连
int count = 1;
// 判断横向是否有五个棋子相连:特点纵坐标相同,及判断y值相同
// 通过循环来做判断
int color = allChess[x][y];
int i = 1;
while (color == allChess[x + i][y]) {
	count++;
	i++;
}
i = 1;
while (color == allChess[x - i][y]) {
	count++;
	i++;
}
if (count >= 5) {
	flag = true;
}
int i2 = 1;
int count2 = 1;
while (color == allChess[x][y + i2]) {
	count2++;
	i2++;
}
i2 = 1;
while (color == allChess[x][y - i2]) {
	count2++;
	i2++;
}
if (count2 >= 5) {
	flag = true;
}
// 斜方向的判断(右上+左下)
int i3 = 1;
int count3 = 1;
while (color == allChess[x + i3][y - i3]) {
	count3++;
	i3++;
}
i3 = 1;
while (color == allChess[x - i3][y + i3]) {
	count3++;
	i3++;
}
if (count3 >= 5) {
	flag = true;
}
// 斜方向的判断(左上+右下)
int i4 = 1;
int count4 = 1;
while (color == allChess[x + i4][y + i4]) {
	count4++;
	i4++;
}
i4 = 1;
while (color == allChess[x - i4][y - i4]) {
	count4++;
	i4++;
}
if (count4 >= 5) {
	flag = true;
}

这个就比较好理解,首先对横向和纵向进行判断,之后在对斜方向上的判断,斜方向上的横纵坐标都进行变化注意这一点.

想要源码的文件可以直接私信我.可以加QQ:2632814845

总结

本项目的设计思路与“三子棋”游戏一致,程序在界面设计与功能实现上还有许多不足,但该项目的实现对于Java编程新手实战能力的提升有很大作用,在编写该项目的过程中,对编程者算法的设计能力与面向对象编程的理解都有深刻的考验。

程序在一定程序上还有还有缺陷,仅用于初学者学习,欢迎各位指正。

文章出处登录后可见!

已经登录?立即刷新
退出移动版