You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

11 KiB

练手项目: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

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<Room> 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<BattleResult> 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<Player> 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);
        }
    }
}