练手项目:java地牢 dungeon-boot/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/dungeon/ │ │ │ ├── DungeonApplication.java // 项目启动类 │ │ │ ├── common/ // 公共配置与工具 │ │ │ │ ├── config/ // MyBatis-Plus/Redis 配置 │ │ │ │ ├── exception/ // 自定义异常(如:玩家死亡异常) │ │ │ │ └── result/ // 统一接口返回对象 (Result) │ │ │ ├── controller/ // API 控制层 (与前端交互) │ │ │ │ ├── PlayerController.java │ │ │ │ └── GameController.java │ │ │ ├── entity/ // 数据库映射实体 (POJO) │ │ │ │ ├── Player.java │ │ │ │ ├── Room.java │ │ │ │ └── Monster.java │ │ │ ├── mapper/ // MyBatis-Plus Mapper 接口 │ │ │ │ ├── PlayerMapper.java │ │ │ │ └── RoomMapper.java │ │ │ └── service/ // 核心业务逻辑层 │ │ │ ├── PlayerService.java // 玩家属性、升级逻辑 │ │ │ ├── MapService.java // 移动、房间探索逻辑 │ │ │ └── BattleService.java // 战斗计算逻辑 │ │ └── resources/ │ │ ├── application.yml // 数据库连接与游戏全局配置 │ └── test/ // 单元测试 └── pom.xml // 项目依赖管理 controller ```java package com.example.demo01.controller; import com.example.demo01.common.result.Result; import com.example.demo01.entity.Player; import com.example.demo01.entity.Room; import com.example.demo01.service.BattleService; import com.example.demo01.service.MapService; import com.example.demo01.service.PlayerService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/game") public class GameController { @Autowired private PlayerService playerService; @Autowired private MapService mapService; @Autowired private BattleService battleService; @PostMapping("/move") public Result move(@RequestBody MoveRequest request) { // 1. 获取玩家信息 Player player = playerService.getPlayerStatus(request.getPlayerId()); if (player == null) return Result.error("玩家不存在"); // 2. 调用地图服务判断是否可以移动 Room nextRoom = mapService.move(player.getCurrentRoomId(), request.getDirection()); if (nextRoom != null) { // 3. 移动成功,更新玩家所在的房间ID playerService.moveToRoom(player.getId(), nextRoom.getId()); // 返回新房间的信息,让前端显示描述文字 return Result.success(nextRoom); } else { return Result.error("哎呀!你撞到了墙上,那里没有路。"); } } @PostMapping("/attack") public Result doAttack(@RequestParam Long playerId, @RequestParam Long monsterId) { BattleResult result = battleService.attack(playerId, monsterId); return Result.success(result); } } ``` ``` @Data public class MoveRequest { /** * 正在操作的玩家 ID */ private Long playerId; /** * 移动方向:north, south, east, west */ private String direction; } ``` ``` @RestController @RequestMapping("/api/player") public class PlayerController { @Autowired private PlayerService playerService; // 获取玩家当前面板状态:GET /api/player/status?id=1 @GetMapping("/status") public Result getStatus(@RequestParam Long id) { Player player = playerService.getPlayerStatus(id); if (player == null) { return Result.error("未找到该角色"); } return Result.success(player); } } ``` service ``` package com.example.demo01.service.impl; import com.example.demo01.controller.BattleResult; import com.example.demo01.entity.Monster; import com.example.demo01.entity.Player; import com.example.demo01.mapper.MonsterMapper; import com.example.demo01.mapper.PlayerMapper; import com.example.demo01.service.BattleService; import com.example.demo01.service.PlayerService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class BattleServiceImpl implements BattleService { @Autowired private PlayerMapper playerMapper; @Autowired private MonsterMapper monsterMapper; @Autowired private PlayerService playerService; @Override @Transactional public BattleResult attack(Long playerId, Long monsterId) { Player player = playerMapper.selectById(playerId); Monster monster = monsterMapper.selectById(monsterId); StringBuilder log = new StringBuilder(); // 1. 玩家攻击怪物 // 伤害公式:攻击力 - 防御力(最小为1) int damageToMonster = Math.max(1, player.getAtk() - monster.getDef()); monster.setHp(Math.max(0, monster.getHp() - damageToMonster)); log.append(String.format("你攻击了 %s,造成了 %d 点伤害。 ", monster.getName(), damageToMonster)); // 2. 检查怪物是否死亡 if (!monster.isAlive()) { log.append(String.format("%s 倒下了!你获得了 %d 经验。", monster.getName(), monster.getExpReward())); // 结算奖励 playerService.addRewards(playerId, monster.getExpReward(), monster.getGoldReward()); // 从房间移除怪物(可选逻辑:更新Room表或者标记Monster已死) monsterMapper.deleteById(monsterId); return BattleResult.builder() .log(log.toString()).monsterHp(0).playerHp(player.getHp()) .monsterDead(true).rewardsExp(monster.getExpReward()).build(); } // 3. 怪物反击(如果还没死) int damageToPlayer = Math.max(1, monster.getAtk() - 5); // 假设玩家基础防御5 player.setHp(Math.max(0, player.getHp() - damageToPlayer)); playerMapper.updateById(player); // 更新玩家血量 monsterMapper.updateById(monster); // 更新怪物血量 log.append(String.format("%s 发起反击,你受到了 %d 点伤害。", monster.getName(), damageToPlayer)); return BattleResult.builder() .log(log.toString()) .playerHp(player.getHp()) .monsterHp(monster.getHp()) .playerDead(player.getHp() <= 0) .build(); } } ``` ``` package com.example.demo01.service.impl; import com.example.demo01.entity.Room; import com.example.demo01.mapper.RoomMapper; import com.example.demo01.service.MapService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class MapServiceImpl implements MapService { @Autowired private RoomMapper roomMapper; @Override public Room move(Long currentRoomId, String direction) { // 1. 先找到玩家所在的当前房间 Room currentRoom = roomMapper.selectById(currentRoomId); if (currentRoom == null) return null; // 2. 根据方向获取目标房间的 ID Long nextRoomId = null; switch (direction.toLowerCase()) { case "north": nextRoomId = currentRoom.getNorthRoomId(); break; case "south": nextRoomId = currentRoom.getSouthRoomId(); break; case "east": nextRoomId = currentRoom.getEastRoomId(); break; case "west": nextRoomId = currentRoom.getWestRoomId(); break; } // 3. 如果 ID 为空,说明那边是墙 if (nextRoomId == null) return null; // 4. 返回目标房间的完整信息 return roomMapper.selectById(nextRoomId); } @Override public Room getRoomById(Long roomId) { return roomMapper.selectById(roomId); } } ``` ``` package com.example.demo01.service.impl; import com.example.demo01.entity.Player; import com.example.demo01.mapper.PlayerMapper; import com.example.demo01.service.PlayerService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class PlayerServiceImpl implements PlayerService { @Autowired private PlayerMapper playerMapper; @Override public Player getPlayerStatus(Long playerId) { return playerMapper.selectById(playerId); } @Override @Transactional // 涉及数据库修改,建议开启事务 public boolean moveToRoom(Long playerId, Long newRoomId) { Player player = playerMapper.selectById(playerId); if (player == null) return false; player.setCurrentRoomId(newRoomId); return playerMapper.updateById(player) > 0; } @Override public void takeDamage(Long playerId, Integer damage) { Player player = playerMapper.selectById(playerId); if (player != null) { // 计算扣血后的 HP,确保不小于 0 int newHp = Math.max(0, player.getHp() - damage); player.setHp(newHp); playerMapper.updateById(player); if (newHp == 0) { // 这里可以触发死亡逻辑,比如清空存档或传回出生点 System.out.println("玩家 " + player.getName() + " 已阵亡!"); } } } @Override public void addRewards(Long playerId, Integer exp, Integer gold) { Player player = playerMapper.selectById(playerId); if (player != null) { player.setExp(player.getExp() + exp); // 简单的升级逻辑:每 100 经验升一级 if (player.getExp() >= player.getLevel() * 100) { player.setLevel(player.getLevel() + 1); player.setAtk(player.getAtk() + 5); // 升级加攻击 player.setHp(player.getMaxHp()); // 升级回满血 } playerMapper.updateById(player); } } } ```