Java实现泡泡堂对战版游戏的示例代码

目录
  • 前言
  • 主要设计
  • 功能截图
  • 代码实现
    • 游戏启动类
    • 核心监听类
    • 核心线程类
  • 总结

前言

《泡泡堂II》是一个基于java的自制游戏,使用了MVC模式,分离了模型、视图和控制器,使得项目结构清晰易于扩展,使用配置文件来设置游戏基本配置,扩展地图人物道具等。同时,该程序编写期间用了单例模式、工厂模式、模板模式等设计模式。为了游戏的可玩性,特意设计了平滑碰撞以及机器人。

主要设计

  • 设计游戏界面,用swing实现
  • 绘制游戏启动界面、结束界面、地图、主角、道具
  • 实现泡泡爆炸
  • 为了尽量复原泡泡堂游戏,初步实现了机器人功能。该机器人可以判断障碍物释放炸弹、规避炸弹、攻击玩家。
  • 实现道具掉落和相应属性加成
  • 实现游戏音效和背景音乐
  • 平滑碰撞:人物在拐角处移动的时候经常不是刚好对齐的状态,程序会判定玩家碰撞了障碍物所以导致玩家无法拐弯。所以我们在处理这种情况的时候,会让玩家进行平滑的移动使得玩家看上去是滑进去的,增强玩家游戏体验
  • 设计单/双人模式

功能截图

游戏启动界面:

道具说明:

游戏开始:

释放炸弹·:

炸弹爆炸效果:

代码实现

游戏启动类

public class GameStart {
	private static GameFrame gameFrame;

	//游戏启动入口
	public static void main(String[] args) {
		// 资源加载
		try {
			ElementLoader.getElementLoader().readGamePro();
			ElementLoader.getElementLoader().readImagePro();
			ElementLoader.getElementLoader().readCharactorsPro();
			ElementLoader.getElementLoader().readBubblePro();
			ElementLoader.getElementLoader().readSquarePro();
		} catch (IOException e) {
			System.out.println("资源加载失败");
			e.printStackTrace();
		}
		//初始化
		gameFrame = new GameFrame();
		//界面显示
		gameFrame.setVisible(true);
		//音乐播放
		GameMusicPlayer musicPlayer = new GameMusicPlayer();
		musicPlayer.start();
	}

	/**
	 * 界面切换
	 * @param panelName 界面名称
	 */
	public static void changeJPanel(String panelName){
		if(panelName == "game") {
			GameController.setGameRunning(true);
			gameFrame.addListener();
		} else {
			GameController.setGameRunning(false);
			gameFrame.removeListener();
		}
		gameFrame.changePanel(panelName);

		//强制刷新,否则监听无效
		gameFrame.setVisible(false);
		gameFrame.setVisible(true);
	}

	public static void startNewGame() {
		GameController.setGameRunning(true);
		gameFrame.startGame();
		changeJPanel("game");
	}

}

核心监听类

public class GameThread extends Thread{
	private boolean running; //表示当前关卡是否在进行
	private boolean over = false; //表示游戏是否结束,结束返回开始菜单
	private static int sleepTime = 20; //runGame刷新时间
	//倒计时变量
	private static int allTime = 600*1000; //10分钟

	@Override
	public void run() {
		while(!over) {
			running = true;//当前关卡正在进行
			//加载元素
			loadElement();
			//显示人物,流程,自动化
			runGame();
			//结束当前关
			overGame(over);
		}
		GameStart.changeJPanel("over");
	}

	//加载元素
	private void loadElement() {
		ElementManager.getManager().loadMap();//加载地图及其元素
	}

	/**
	 * 关卡结束
	 * 如果over为真则游戏失败返回界面,否则进入下一关
	 * @param over
	 */
	private void overGame(Boolean over) {
		ElementManager.getManager().overGame(over);
	}

	//显示人物,游戏流程,自动化
	private void runGame() {
		allTime = 600*1000;
		while(running) {
			Map<String, List<SuperElement>> map = ElementManager.getManager().getMap();
			Set<String> set = map.keySet();
			for(String key:set) {
				List<SuperElement> list = map.get(key);
				for(int i=list.size()-1; i>=0; i--) {
					list.get(i).update();
					if(!list.get(i).isAlive())
						list.remove(i);
				}
			}

			//添加游戏的流程控制linkGame()?

			//玩家与炸弹碰撞死亡
			playerBoom();
			//可破坏物与炸弹碰撞
			fragilityBoom();
			//电脑与炸弹碰撞死亡
			npcBoom();
			//电脑与道具碰撞效果,暂时不开启
			//npcMagicBox();
			//玩家与道具碰撞效果
			playerMagicBox();
			//检测是否玩家全部死亡
			defeat();

			//控制runGame进程
			allTime = allTime - sleepTime;
			try {
				sleep(20);
			} catch (InterruptedException e) {
				// TODO: handle exception
				e.printStackTrace();
			}
		}
	}

	private void defeat() {
		boolean allDead = true;
		int surviveP = 0;
		int winner = 2;//0为玩家1,1为玩家2,2为电脑获胜
		List<SuperElement> playerList = ElementManager.getManager().getElementList("player");
		List<SuperElement> npcList = ElementManager.getManager().getElementList("npc");
		for(SuperElement se:playerList) {
			if(!((Player)se).isDead()) {
				surviveP++;
			}
		}
		for(SuperElement npc:npcList) {
			if(!((Npc)npc).isDead()) {
				allDead = false;
			}
		}

		//玩家失败
		if(surviveP==0||(allTime<=0 && !allDead)) {
			running = false;
			over = true;
			OverJPanel.getResult().setText("defeated");
		}
		//玩家胜利
		if(allDead&&surviveP==1) {
			running = false;
			over = true;
			for(SuperElement se:playerList) {
				if(!((Player)se).isDead()) {
					surviveP++;
					winner = ((Player)se).getPlayerNum();
				}
			}
			OverJPanel.getResult().setText("player "+(winner+1)+" win");
		}

		//时间到,两个玩家都活着
		if(allTime<=0&&surviveP==2&&allDead) {
			running = false;
			over = true;
			int score1 = ((Player)playerList.get(0)).score;
			int score2 = ((Player)playerList.get(0)).score;
			if(score1==score2) {
				OverJPanel.getResult().setText("defeated");
			}
			else if(score1>score2)
			{
				OverJPanel.getResult().setText("player 1 win");
			}
			else {
				OverJPanel.getResult().setText("player 2 win");
			}
		}
	}

	//玩家与炸弹碰撞判断
	private void playerBoom() {
		List<SuperElement> playerList = ElementManager.getManager().getElementList("player");
		List<SuperElement> explodeList = ElementManager.getManager().getElementList("explode");
		for(int i=0; i<playerList.size(); i++) {
			for(int j=0; j<explodeList.size(); j++) {
				if(explodeList.get(j).crash(playerList.get(i))){
					Player player = (Player) playerList.get(i);
					player.setHealthPoint(-1);//生命值-1
				}
			}
		}

	}
	//npc与炸弹碰撞判断
	private void npcBoom() {
		List<SuperElement> playerList = ElementManager.getManager().getElementList("player");
		List<SuperElement> npcList = ElementManager.getManager().getElementList("npc");
		List<SuperElement> explodeList = ElementManager.getManager().getElementList("explode");
		for(int i=0; i<npcList.size(); i++) {
			for(int j=0; j<explodeList.size(); j++) {
				if(explodeList.get(j).crash(npcList.get(i))){
					Npc npc = (Npc) npcList.get(i);
					npc.setDead(true);
					npc.setX(-100);
					npc.setY(-100);
					BubbleExplode e = (BubbleExplode)explodeList.get(j);
					if(e.getPlayerNum()<2)//目前只有玩家计分
						((Player)playerList.get(e.getPlayerNum())).setScore(((Player)playerList.get(e.getPlayerNum())).getScore()+50);
				}
			}
		}
	}

	//障碍物与炸弹碰撞判断
	private void fragilityBoom() {
		List<SuperElement> playerList = ElementManager.getManager().getElementList("player");
		List<SuperElement> explodes = ElementManager.getManager().getElementList("explode");
		List<SuperElement> fragility = ElementManager.getManager().getElementList("fragility");
		for(int i=0; i<fragility.size(); i++) {
			for(int j=0; j<explodes.size(); j++) {
				if(explodes.get(j).crash(fragility.get(i))) {
					MapFragility mapFragility = (MapFragility)fragility.get(i);
					mapFragility.setDestoried(true);
					BubbleExplode e = (BubbleExplode)explodes.get(j);
					if(e.getPlayerNum()<2)//目前只有玩家计分
						((Player)playerList.get(e.getPlayerNum())).setScore(((Player)playerList.get(e.getPlayerNum())).getScore()+10);
				}
			}
		}
	}

	//玩家与道具碰撞判断
	private void playerMagicBox() {
		List<SuperElement> playerList = ElementManager.getManager().getElementList("player");
		List<SuperElement> magicBoxList = ElementManager.getManager().getElementList("magicBox");
		for(int i=0; i<playerList.size(); i++) {
			for(int j=magicBoxList.size()-1; j>=0; j--) {
				if(magicBoxList.get(j).crash(playerList.get(i))){
					MagicBox magicBox = (MagicBox) magicBoxList.get(j);
					magicBox.setCharacterIndex(i);//谁吃方块
					magicBox.setEaten(true);//方块被吃
					((Player)playerList.get(i)).setScore(((Player)playerList.get(i)).getScore()+30);
				}

			}
		}
	}

	//玩家与道具碰撞判断
	private void npcMagicBox() {
		List<SuperElement> npcList = ElementManager.getManager().getElementList("npc");
		List<SuperElement> magicBoxList = ElementManager.getManager().getElementList("magicBox");
		for(int i=0; i<npcList.size(); i++) {
			for(int j=magicBoxList.size()-1; j>=0; j--) {
				if(magicBoxList.get(j).crash(npcList.get(i))){
					MagicBox magicBox = (MagicBox) magicBoxList.get(j);
					magicBox.setCharacterIndex(i+2);//谁吃方块
					magicBox.setEaten(true);//方块被吃
				}
			}
		}
	}

	//runGame调用,加入拓展
	public void linkGame() {}

	public static int getAllTime() {
		return allTime;
	}

}

核心线程类

public class GameKeyListener implements KeyListener{
	/**
	 * 用栈来解决按键冲突
	 * 每个栈用来存放不同用户的按键,通过判断按键的code来设置移动方向或者攻击
	 *
	 */

	private List<?> list;
	private Stack<Integer> p1PressStack = new Stack<>();
	private Stack<Integer> p2PressStack = new Stack<>();

	@Override
	public void keyPressed(KeyEvent e) {
		list = ElementManager.getManager().getElementList("player");
		Player player1 = (Player) list.get(0);

		int code = e.getKeyCode();
		switch (code) {
		case 10://炸弹键
			if(player1.isKeepAttack())//不允许一直按着炸弹键,每次只能放一个炸弹
				player1.setAttack(false);
			else {
				player1.setKeepAttack(true);
				player1.setAttack(true);
			}
			break;
		case 37://左右上下
		case 38:
		case 39:
		case 40:
			if(!p1PressStack.contains(code)) {
				p1PressStack.push(code);
			}
			player1.setMoveType(MoveTypeEnum.codeToMoveType(code));
			break;
		default://其它按键无视
			break;
		}
		if(GameController.isTwoPlayer()) {
			Player player2 = (Player) list.get(1);
			switch (code) {
			case 32:
				if(player2.isKeepAttack())
					player2.setAttack(false);
				else {
					player2.setKeepAttack(true);
					player2.setAttack(true);
				}
				break;
			case 65:
			case 87:
			case 68:
			case 83:
				if(!p2PressStack.contains(code)) {
					p2PressStack.push(code);
				}
				player2.setMoveType(MoveTypeEnum.codeToMoveType(code));
				break;

			default:
				break;
			}
		}
	}

	@Override
	public void keyReleased(KeyEvent e) {
		List<?> list = ElementManager.getManager().getElementList("player");
		int code = e.getKeyCode();
		Player player1 = (Player) list.get(0);
		if(!player1.isDead()) {
			switch (code) {
			case 10:
				player1.setAttack(false);
				player1.setKeepAttack(false);
				break;
			case 37:
			case 38:
			case 39:
			case 40:
				if(p1PressStack.peek()!=code) {
					p1PressStack.remove(new Integer(code));
				} else {
					p1PressStack.pop();
					if(p1PressStack.size()==0) {
						player1.setMoveType(MoveTypeEnum.STOP);
					} else {
						player1.setMoveType(MoveTypeEnum.codeToMoveType(p1PressStack.peek()));
					}
				}
				break;
			default:
				break;
			}
		}
		if(GameController.isTwoPlayer()) {
			Player player2 = (Player) list.get(1);
			if(!player2.isDead()) {
				switch (code) {
				case 32:
					player2.setAttack(false);
					player2.setKeepAttack(false);
					break;
				case 65:
				case 87:
				case 68:
				case 83:
					if(p2PressStack.peek()!=code) {
						p2PressStack.remove(new Integer(code));
					} else {
						p2PressStack.pop();
						if(p2PressStack.size()==0) {
							player2.setMoveType(MoveTypeEnum.STOP);
						} else {
							player2.setMoveType(MoveTypeEnum.codeToMoveType(p2PressStack.peek()));
						}
					}
					break;
				default:
					break;
				}
			}

		}
	}

	@Override
	public void keyTyped(KeyEvent arg0) {
		// TODO 自动生成的方法存根

	}

	public void clearKeyStatcks() {
		p1PressStack.clear();
		p2PressStack.clear();
	}

}

总结

通过此次的《泡泡堂对战版》实现,让我对JAVA的相关知识有了进一步的了解,对java这门语言也有了比以前更深刻的认识。

java的一些基本语法,比如数据类型、运算符、程序流程控制和数组等,理解更加透彻。java最核心的核心就是面向对象思想,对于这一个概念,终于悟到了一些。

以上就是Java实现泡泡堂对战版游戏的示例代码的详细内容,更多关于Java泡泡堂的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java实现经典捕鱼达人游戏的示例代码

    目录 前言 主要设计 功能截图 代码实现 游戏窗体 鱼 鱼池类继承自Jpanel 总结 前言 <捕鱼达人>是一款以深海狩猎为题材的休闲竞技游戏.这是一场海底世界的远征,享受捕获大鱼的乐趣,但不是所有的鱼都是友善的,它们会用自己的方式保护自己,保卫属于自己的海底世界.当然,这里也是冒险与机遇共存的地方,诸多埋藏于海底的宝藏等待着被探寻. 游戏是用java语言实现,采用了swing技术进行了界面化处理,设计思路用了面向对象思想. 主要需求 在鱼池中有很多鱼,鱼各自游动. 有一张渔网,随鼠标移动,点

  • Java实现经典大富翁游戏的示例详解

    目录 前言 主要设计 功能截图 代码实现 总结 前言 大富翁,又名地产大亨.是一种多人策略图版游戏.参与者分得游戏金钱,凭运气(掷骰子)及交易策略,买地.建楼以赚取租金.英文原名monopoly意为“垄断”,因为最后只得一个胜利者,其余均破产收场. <大富翁>游戏是用java语言实现,采用了swing技术进行了界面化处理,设计思路用了面向对象思想. 主要需求 可多人参与的大富翁游戏,玩家有初始资金,通过掷骰子,玩家移动指定骰子点数步骤,根据对应格子上的交易策略,来决定是赚钱还是亏钱,其他玩家破

  • Java实现经典游戏打砖块游戏的示例代码

    目录 前言 主要设计 功能截图 代码实现 游戏核心类 小球类 砖块类 总结 前言 <JAVA打砖块>游戏是自制的游戏.玩家操作一根萤幕上水平的“棒子”,让一颗不断弹来弹去的“球”在撞击作为过关目标消去的“砖块”的途中不会落到萤幕底下. 主要设计 设计游戏界面,用swing实现 设计砖块,砖块类, 设计小球,满屏乱跑的小球类,负责打碎砖块 设计棒子,左右移动的木头板类 球碰到砖块.棒子与底下以外的三边会反弹,落到底下会失去一颗球,把砖块全部消去就可以破关. 小球碰到砖块的回调算法设计 小球碰到棒

  • Java实现经典游戏泡泡堂的示例代码

    目录 前言 主要设计 功能截图 代码实现 游戏启动类 核心监听类 核心线程类 总结 前言 <泡泡堂I>是一个基于java的自制游戏,游戏设计为双人pk积分赛模式,在这个模式里面,玩家只要率先达到一定分数既可以赢得比赛.玩家可以通过炸箱子可以得到少量的分数,也可以通过炸掉对手然后戳破包围对手的水泡得到大量分数.而玩家如果被泡泡爆炸击中,会被泡泡包裹一段时间,在这段时间内不可以移动和放泡泡,需要等时间过去或者被对手戳破水泡才能获得自由.但如果玩家被自己放的泡泡炸中,会扣一定的分数. 主要设计 设计

  • Java练习之潜艇小游戏的实现

    首先是主方法里定义的窗口(这些都是固定的格式,不会的也没什么事,直接Ctrl+c+v就行,基本上看一下数据都能看的懂) 写一个超类,超类里需要有潜艇,深水炸弹,水雷和战舰的宽,高,以及出场时的x,y坐标,和移动速度.所有对象的图片,所有对象的移动方法,以及碰撞 然后再写派生类,根据击败一个水雷潜艇,战舰会获得一条命,击败其他潜艇,战舰会获得分数,所以,需要定义两个接口,一个是用来加命,另一个用来加分 完整代码如下(图片什么的可以自己去找,只需要改一下Image类和各个对象的宽高就可以) 游戏世界

  • JAVA实现的CrazyArcade泡泡堂游戏

    项目地址 https://github.com/SCNU-A225/CrazyArcade 示例图片 项目结构 frame:包含如开始.游戏中.结束的JFrame main:包含程序入口和其它游戏控制 model: model.loader:包含资源加载器用于读取配置文件等信息,使用了单例设计模式 model.manager:包含元素管理器.工厂等,用于控制游戏各元素 model.vo:各种实体类,包括玩家.NPC.炸弹.方块等等 pro:配置文件目录,包含人物.道具.地图.方块.游戏设置等配置

  • Java+Swing实现中国象棋游戏

    目录 一.系统介绍 1.开发环境 2.技术选型 3.系统功能 二.系统展示 三.部分代码 一.系统介绍 1.开发环境 开发工具:Eclipse2021 JDK版本:jdk1.8 Mysql版本:8.0.13 2.技术选型 Java+Swing 3.系统功能 实现中国象棋游戏,开始游戏,悔棋,退出功能. 二.系统展示 1.首页 2.红旗走 3.黑棋走 三.部分代码 ChineseCheseRule.java package com.sjsq; import java.awt.event.Mouse

  • Java实现泡泡堂对战版游戏的示例代码

    目录 前言 主要设计 功能截图 代码实现 游戏启动类 核心监听类 核心线程类 总结 前言 <泡泡堂II>是一个基于java的自制游戏,使用了MVC模式,分离了模型.视图和控制器,使得项目结构清晰易于扩展,使用配置文件来设置游戏基本配置,扩展地图人物道具等.同时,该程序编写期间用了单例模式.工厂模式.模板模式等设计模式.为了游戏的可玩性,特意设计了平滑碰撞以及机器人. 主要设计 设计游戏界面,用swing实现 绘制游戏启动界面.结束界面.地图.主角.道具 实现泡泡爆炸 为了尽量复原泡泡堂游戏,初

  • Java实现warcraft java版游戏的示例代码

    目录 前言 主要需求 功能截图 代码实现 启动入口 ModelAttacker类 ModelUnit类 总结 前言 致敬经典的warcraft,<warcraft java版>是一款即时战略题材单机游戏,采用魔兽原味风格和机制.收集资源,建造防御工事,消灭所有敌军. 人类:洛丹伦人类联盟自兽人首次穿过黑暗之门时便告成立.他们坚韧不拔,勇敢无畏,身穿坚甲,手握利刃,英勇迎敌. 兽人:兽人是一个粗犷而坚韧的种族,他们身穿简单的皮毛和带有尖刺的皮甲,以肆意凶狠的战斗风格而闻名. 用java语言实现,

  • Java实现贪吃蛇大作战小游戏的示例代码

    目录 效果展示 项目介绍 项目背景 总体需求 实现过程 代码展示 项目结构 总结 大家好,今天尝试用swing技术写一个贪吃蛇大作战小游戏,供大家参考. 效果展示 效果展示 一.游戏界面 二.得分情况 项目介绍 项目背景 “贪吃蛇大作战”游戏是一个经典的游戏,它因操作简单.娱乐性强,自从计算机实现以来,深受广大电脑玩家的喜爱,本项目基于Java技术,开发了一个 操作简单.界面美观.功能较齐全 的“贪吃蛇”游戏.通过本游戏的开发,达到学习Java技术和熟悉软件开发流程的目的. 总体需求  本系统主

  • Java实现经典拳皇误闯冒险岛游戏的示例代码

    目录 前言 主要设计 功能截图 代码实现 游戏主界面 英雄 总结 前言 <拳皇误闯冒险岛>是拳皇和冒险岛素材的基于JavaSwing的动作类游戏,独创改编. 主要需求 拳皇迷迷糊糊醒来,发现自己在一间废弃的工厂里,地上爬满怪兽..这么可爱的怪兽,一拳下去,应该会哭很久吧~拳皇心里吐槽了下,向怪兽的怀抱冲了上去~~ 主要设计 1.游戏面板生成显示 2.背景选用冒险岛素材图 3.设计英雄,包含生命值,法术值,英雄的动作变化处理,英雄的技能特效 4.设计怪兽,包含怪物血量,攻击力,位置,步长等 5.

  • Java+Swing实现五子棋游戏的示例代码

    目录 一.系统介绍 1.开发环境 2.技术选型 3.系统功能 二.系统展示 三.部分代码 AI.java Chess.java Gobang.java GobangListener.java 一.系统介绍 1.开发环境 开发工具:Eclipse2021 JDK版本:jdk1.8 Mysql版本:8.0.13 2.技术选型 Java+Swing 3.系统功能 实现五子棋游戏,开始游戏,悔棋,认输,退出功能. 二.系统展示 1.首页 2.黑棋走 3.白棋走 三.部分代码 AI.java packag

  • Java实现单机版五子棋游戏的示例代码

    目录 前言 主要需求 主要设计 功能截图 代码实现 总结 前言 五子棋是世界智力运动会竞技项目之一,是一种两人对弈的纯策略型棋类游戏,是世界智力运动会竞技项目之一,通常双方分别使用黑白两色的棋子,下在棋盘直线与横线的交叉点上,先形成5子连线者获胜. 棋具与围棋通用,起源于中国上古时代的传统黑白棋种之一.主要流行于华人和汉字文化圈的国家以及欧美一些地区,是世界上最古老的棋. 容易上手,老少皆宜,而且趣味横生,引人入胜:不仅能增强思维能力,提高智力,而且富含哲理,有助于修身养性. 用java语言实现

  • Java实现萝卜勇者游戏的示例代码

    目录 前言 主要设计 功能截图 代码实现 启动类 键盘监听 核心算法 总结 前言 <萝卜勇者>是由国内玩家自制的一款独立游戏,玩家扮演萝卜勇士闯关,打败各种邪恶的敌人,获得最后的胜利. <萝卜勇者>游戏是用java语言实现,采用了swing技术进行了界面化处理,设计思路用了面向对象思想. 主要需求 参考<萝卜勇者>的剧情,实现JAVA版本的单机游戏. 主要设计 1. 用Swing库做可视化界面 2.键盘监听,用WSAD可以控制光标移动,J是确定,K是取消,游戏中,WSA

  • JAVA实现经典扫雷游戏的示例代码

    目录 前言 主要设计 功能截图 代码实现 总结 前言 windows自带的游戏<扫雷>是陪伴了无数人的经典游戏,本程序参考<扫雷>的规则进行了简化,用java语言实现,采用了swing技术进行了界面化处理,设计思路用了面向对象思想. 主要需求 1.要有难度等级,初级,中级,高级 2.由玩家逐个翻开方块,以找出所有地雷为最终游戏目标.如果玩家翻开的方块有地雷,则游戏结束 3.游戏主区域由很多个方格组成.使用鼠标左键随机点击一个方格,方格即被打开并显示出方格中的数字:方格中数字则表示其

  • Java实现经典角色扮演侦探游戏游戏的示例代码

    目录 前言 游戏背景 主要需求 主要设计 功能截图 代码实现 游戏主界面 主卧 初始化 大厅 总结 前言 游戏背景 百变山庄坐落于太平洋的一座小岛上,山庄主人亦是小岛的主人.这个神秘主人细致周到,邀请函里不仅附着往返港口的机票,港口的邮船也是通往小岛的专线. 初登小岛,恢宏大气的山庄直入眼帘,通过门廊,金碧辉煌的大厅震人心魄. 受邀的侦探们陆续到齐,[侍者]彬彬有礼地站在一旁,他安排你们围坐在一个奇特十边形的桌子旁稍加等待.[侦探指尖]回忆着自己临行前调查的各位名侦探的资料,除了那个神秘的[电话

随机推荐