鱼C论坛

 找回密码
 立即注册
查看: 4535|回复: 2

[技术交流] 《Dorfromantik》完美拼接脚本

[复制链接]
发表于 2021-9-10 11:23:26 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 z412290894 于 2024-6-22 15:04 编辑

本人纯小白,刚跟着小甲鱼学习python几个月,目前看到第10章。
最近刚好在玩一款类似俄罗斯方块的游戏《Dorfromantik》,游戏里得高分的方法就是寻找最佳板块,刚开始用眼睛找,很累,然后学了python就想用脚本实现。
目前只写了大概300行,只简单实现了记录和寻找完美拼接的功能,后续会持续优化脚本,欢迎各位大佬指导指导!

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Dorfromantik
中文名:多罗曼蒂克

一、游戏介绍:
随机给玩家一个六边形板块,玩家根据自己的选择放置到地图上,从而组成独特的乡村风景。

六边形上的地形分为:
空地、房屋、农田、森林、河流、铁轨;
其中河流、铁轨只能和对应的板块连接,其他地形无特殊要求,可以随意连接

游戏没有时间限制,但是系统提供的板块有个数限制,放置完之后游戏结束!

每放置一个板块会有对应的分数,完成特殊的任务有板块奖励和额外分数,所以尽可能多的放置板块就可以获得更高的分数,组成的乡村也会更大!

二、游戏奖励机制:
1.开场系统给玩家40个板块,可以随意放置到地图上;
2.放置时六边形每有一边地形与旁边一致加10分,最多(六个方向的地形均一致)可加60分;
3.当一个六边形板块的六个地形均与旁边一致时,达成“完美拼接”,奖励一块新板块;(得高分的重点)
4.系统会随机给任务“地形图标加数字”,表示要求相同地形连续出现N次或N+次,达成目标后奖励5块板块,获得100分;(快速获得板块,优先级最高)
5.地图上随机出现旗帜,表示要求此地形形成封闭状态,达成目标后奖励5块板块;
6.成就系统,系统会给各种特殊的任务,完成后奖励分数和特殊板块(风车、水车、火车等);
7.隐藏地块,地图上未到达的地区会随机出现隐藏板块,当放置板块接近后会获得新的成就任务;

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
脚本见最新回复
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2022-9-11 18:51:05 | 显示全部楼层
感谢楼主,之前就想用代码实现完美拼接,没想到你已经做出来了,真厉害!
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2024-6-22 14:49:54 | 显示全部楼层
代码已优化:
main.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Auth :Ray
"""
此工具仅适用于《Dorfrmantik》(中文名:筑梦颂)游戏。
一、游戏介绍:
随机给玩家一个六边形板块,玩家根据自己的选择放置到地图上,从而组成独特的乡村风景。
六边形上的地形分为:
空地、房屋、农田、森林、铁路、河流
其中河流、铁路只能和对应的板块连接,其他地形无特殊要求,可以随意连接
游戏没有时间限制,但是系统提供的板块有个数限制,放置完之后游戏结束!
每放置一个板块会有对应的分数,完成特殊的任务有板块奖励和额外分数,所以尽可能多的放置板块就可以获得更高的分数,组成的乡村也会更大!

二、游戏奖励机制:
1.开场系统给玩家40个板块,可以随意放置到地图上;
2.放置时六边形每有一边地形与旁边一致加10分,最多(六个方向的地形均一致)可加60分;
3.当一个六边形板块的六个地形均与旁边一致时,达成“完美拼接”,奖励一块新板块;(得高分的重点)
4.系统会随机给任务“地形图标加数字”,表示要求相同地形连续出现N次或N+次,达成目标后奖励5块板块,获得100分;(快速获得板块,优先级最高)
5.地图上随机出现旗帜,表示要求此地形形成封闭状态,达成目标后奖励5块板块;
6.成就系统,系统会给各种特殊的任务,完成后奖励分数和特殊板块(风车、水车、火车等);
7.隐藏地块,地图上未到达的地区会随机出现隐藏板块,当放置板块接近后会获得新的成就任务;

三、游戏得分点:
1.当插入的地形与已有地形相同时,才能得10分,否则是0分;
2.当板块六个方向的地形均与相邻板块一致时,奖励60分和一个新板块;

四、工具实现内容:
1.记录原始板块的地形组合与顺序,保证与游戏内一致;(新游戏默认是一个六边空地)
2.用户输入下一个要插入板块的地形,计算出与之完美匹配的所有地形组合;
3.在原始地块中搜索并提示用户可以插入哪些位置;
4.用户输入实际插入板块的位置;
5.自动计算插入新板块后的地形组合及顺序;

五、工具优缺点:
优点:能保证每一次插入新板块都是最优解,比人工去找要方便很多,特别是后期大量板块需要人工比对;
缺点:操作费时费力,无法自动获取板块组成和插入位置,计算出来的最优地形太多,用户选择有困难
"""
from plate import *
import json


if __name__ == '__main__':
    # 游戏初始界面
    welcome = eg.buttonbox(msg='Dorfromantik游戏开始,请选择是否是新游戏!', title='Dorfromantik Game Start!Welcome!',
                           choices=['是', '不是'])

    # 判断是否是新游戏,如果是新游戏则初始化板块
    # 各个地形缩写:空地:K 房屋:F 农田:N 森林:S 铁路:T 河流:H
    # plate_init_out元组代表外围一圈的地形可插入最优解地形,例如'KKF'表示此板块可以插入的地形为:‘空地+空地+房屋’
    # plate_init_in集合代表内圈的地形和与之相关联的外圈地形,一般是五地形和六地形空板块,例如{'KKFKK': 'NXF'}
    if welcome == '是':
        plate_init_out = tuple('KKKKKK')    # 新游戏起始板块周边的空板块六边均为空地
        plate_init_in = {}                  # 新游戏起始没有内圈空板块
    else:
        # 继续游戏时,从上次运行时保存的文件中读取外圈和内圈空板块数据
        with open('plate_last_out.txt', 'r') as file_last_out:
            plate_init_out = eval(file_last_out.read().strip())
        with open('plate_last_in.json', 'r', encoding='utf-8') as file_last_in:
            plate_init_in = json.load(file_last_in)

    # 游戏循环开始

    while True:
        # 显示初始板块(废弃不用)
        # file_plate_out(plate_init_out)  # 将初始化外部地形写入plate_out.txt
        # with open('plate_out.txt') as file_1:
        #     eg.textbox(msg='当前外圈板块为:\n(空地:K 房屋:F 农田:N 森林:S 铁路:T 河流:H)\n第一列:空白板块编号\n第二列:可适配地形', text=file_1.read(),
        #                title='Dorfromantik Tool!')
        # eg.msgbox(msg=f'当前内圈板块为:\n{plate_init_in}', title='Dorfromantik Tool!')

        # 请用户输入下一个要插入的板块,逆时针输入
        plate_next = enter_plate()

        # 枚举新板块适合的所有板块地形
        plate_fit = fit_plate(plate_next)

        # 计算新板块最完美的拼接位置是哪里
        best_plate = find_num(plate_fit, plate_init_out, plate_init_in)

        # 请用户输入实际插入板块的位置是哪里
        plate_num, new_num = enter_num(plate_init_out, plate_init_in, plate_next, best_plate)

        # 显示插入新板块后的总地块
        plate_next = tuple(plate_next)
        plate_final = final_plate(plate_init_out, plate_init_in, plate_next, plate_num, new_num)

        # 更新初始地块
        plate_init_out = plate_final[0]
        plate_init_in = copy.deepcopy(plate_final[2])
        plate_init_in.update(plate_final[1])

        # 用户确认插入后的结果是否正确
        y_n_out = eg.ynbox(msg=f'插入新板块后外圈板块为:\n(空地:K 房屋:F 农田:N 森林:S 铁路:T 河流:H)\n{plate_init_out}', title='Dorfromantik Tool!')
        y_n_in = eg.ynbox(msg=f'插入新板块后内圈板块为:\n(空地:K 房屋:F 农田:N 森林:S 铁路:T 河流:H)\n{plate_init_in}', title='Dorfromantik Tool!')
        if not y_n_out or not y_n_in:
            eg.msgbox(msg='游戏出错,请重新开始插入此板块!', title='Dorfromantik Tool!')
            with open('plate_last_out.txt', 'r') as file_last_out:
                plate_init_out = eval(file_last_out.read().strip())
            with open('plate_last_in.json', 'r', encoding='utf-8') as file_last_in:
                plate_init_in = json.load(file_last_in)
            continue

        # 备份上次游戏的结果
        with open('plate_last_out.txt', 'w') as file_last_out:
            file_last_out.write(repr(plate_init_out))
        with open('plate_last_in.json', 'w', encoding='utf-8') as f:
            json.dump(plate_init_in, f, ensure_ascii=False, indent=4)


plate.py:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Auth :Ray
import easygui as eg
import re
import copy


def file_plate_out(plate_init_out):
    """初始化板块"""
    with open('plate_out.txt', 'w') as file_init:
        for index, element in enumerate(plate_init_out):
            file_init.write(f'{index}\t{element}\n')


def enter_plate():
    """新板块插入"""
    eg_enter = eg.enterbox(msg='请输入下一个要插入的板块是(逆时针):\n(空地K 房屋F 农田N 森林S 铁路T 河流H)',
                           title='Dorfromantik Tool!')
    while True:
        # 判断输入是否正确,否则重新要求用户输入
        try:
            if len(eg_enter) != 6 or not re.fullmatch(r'[KFNSTH]+', eg_enter):
                eg_enter = eg.enterbox(
                    msg=f'输入的板块格式不对,请重新输入:\n(空地K 房屋F 农田N 森林S 铁路T 河流H)\n您上次输入的是:{eg_enter}',
                    title='Dorfromantik Tool!')
            else:
                break
        except TypeError:
            eg_enter = eg.enterbox(
                msg=f'输入的板块格式不对,请重新输入:\n(空地K 房屋F 农田N 森林S 铁路T 河流H)\n您上次输入的是:{eg_enter}',
                title='Dorfromantik Tool!')
    return eg_enter


def fit_plate(plate):
    """枚举新板块适合的所有板块地形"""
    """例如插入板块是'SKKKKK',适合的所有地形为:('K', 'KK', 'KKK', 'KKKK', 'KKKKK', 'KKKKKS', 'KKKKS', 'KKKKSK', 'KKKS', 
    'KKKSK', 'KKKSKK', 'KKS', 'KKSK', 'KKSKK', 'KKSKKK', 'KKXK', 'KKXS', 'KS', 'KSK', 'KSKK', 'KSKKK', 'KSKKKK', 
    'KSXK', 'KXK', 'KXKK', 'KXKS', 'KXS', 'KXSK', 'S', 'SK', 'SKK', 'SKKK', 'SKKKK', 'SKKKKK', 'SKXK', 'SXK', 'SXKK')"""
    result = []
    for i in range(0, len(plate)):
        plate_1 = plate[i:] + plate[:i]
        for j in range(1, len(plate_1) + 1):
            result.append(plate_1[:j])
        result.append(plate_1[:1] + 'X' + plate_1[2])
        result.append(plate_1[:1] + 'X' + plate_1[2:4])
        result.append(plate_1[:2] + 'X' + plate_1[3])
    return tuple(sorted(list(set(result))))


def find_num(plate_fit, plate_init_out, plate_init_in):
    """计算哪些板块最适合插入"""
    """与外圈、内圈板块比对,若一致则返回对应位置及板块名称"""
    result = []
    j = 6
    while j > 0:
        if j > 4:
            for i in plate_fit:
                if len(i) == j:
                    if i in plate_init_in:
                        result.append({i: plate_init_in[i]})
        else:
            for i in plate_fit:
                if len(i) == j:
                    for key, value in enumerate(plate_init_out):
                        if value == i:
                            result.append({key: value})
        j -= 1
    return result


def enter_num(plate_init_out, plate_init_in, plate_next, best_plate):
    """用户输入要插入新板块的位置"""
    while True:
        a = eg.enterbox(
            msg=f'请输入在哪个位置插入新板块:\n(空地K 房屋F 农田N 森林S 铁路T 河流H)\n新板块是:{plate_next}\n建议插入位置:\n{best_plate}',
            title='Dorfromantik Tool!')
        try:
            plate_num = int(a)
            break
        except (ValueError, TypeError):
            if a in plate_init_in.keys():
                plate_num = a
                break
            else:
                eg.msgbox(msg=f'输入无效,请重新输入要插入的位置!', title='Dorfromantik Tool!')

    while True:
        try:
            if type(plate_num) is str:
                s = plate_num
            else:
                s = plate_init_out[plate_num]
            b = eg.enterbox(
                msg=f'请输入新板块要对接的位置:\n(空地K 房屋F 农田N 森林S 铁路T 河流H)\n待插入的地形是:{s}\n新板块是:{plate_next}',
                title='Dorfromantik Tool!')
            new_num = int(b)
            if 0 <= new_num <= 5:
                break
            else:
                eg.msgbox(msg=f'超出新板块范围,请重新输入要对接的位置(0-5)!', title='Dorfromantik Tool!')
                continue
        except (ValueError, TypeError):
            eg.msgbox(msg=f'输入无效,请重新输入要对接的位置(0-5)!', title='Dorfromantik Tool!')
    return plate_num, new_num


def final_plate(plate_init_out, plate_init_in, plate_next, plate_num, new_num):
    """计算插入后的板块"""
    plate_finnal_out = []
    plate_finnal_in = {}
    plate_init_in_temp = copy.deepcopy(plate_init_in)

    # 当待插入的地块为五地形和六地形时,plate_num为字符串    plate_init_in = {"KKSKK": "SXF"} SKKKKK
    if type(plate_num) is str:
        if len(plate_num) == 5:
            k = plate_init_in[plate_num]  # SXF
            plate_finnal_out = list(plate_init_out)
            plate_finnal_out_1 = list(plate_init_out)
            for key, value in enumerate(plate_finnal_out_1):
                if value == k:
                    y_or_n = eg.ynbox(
                        msg=f'当前地形为:\n{plate_finnal_out_1}\n请确认要插入五地形地块对应的{k}位置是否为{key}?',
                        title='Dorfromantik Tool!')
                    if y_or_n:
                        plate_finnal_out[key] = plate_finnal_out_1[key].replace('X', plate_next[new_num - 1])
            del plate_init_in[plate_num]
        if len(plate_num) == 6:
            del plate_init_in[plate_num]
            plate_finnal_out = list(plate_init_out)

    # 当待插入的地块为单地形到四地形时
    else:
        len_p = len(plate_init_out[plate_num])
        if len_p in (1, 2, 3, 4):
            # 当待插入的板块是带X时,对应的五地形板块需修改为六地形
            if 'X' in plate_init_out[plate_num]:
                x_index = plate_init_out[plate_num].index('X')
                for key, value in plate_init_in.items():
                    if value == plate_init_out[plate_num]:
                        y_or_n = eg.ynbox(msg=f'请确认要插入的{plate_init_out[plate_num]}是否对应{key}?',
                                          title='Dorfromantik Tool!')
                        if y_or_n:
                            key_new = key + plate_next[new_num + x_index - 6]
                            del plate_init_in_temp[key]
                            plate_init_in_temp[key_new] = None
                plate_init_in = copy.deepcopy(plate_init_in_temp)

            if 'X' in plate_init_out[plate_num - len(plate_init_out) + 1]:
                for key, value in plate_init_in.items():
                    if value == plate_init_out[plate_num+1]:
                        y_or_n = eg.ynbox(msg=f'请确认要插入的{plate_init_out[plate_num + 1]}是否对应{key}?',
                                          title='Dorfromantik Tool!')
                        if y_or_n:
                            value_new = (plate_next[new_num - 6 + len_p] + plate_init_out[plate_num - len(plate_init_out) + 1])
                            plate_init_in_temp[key] = value_new
                plate_init_in = copy.deepcopy(plate_init_in_temp)

            if 'X' in plate_init_out[plate_num - 1]:
                for key, value in plate_init_in.items():
                    if value == plate_init_out[plate_num-1]:
                        y_or_n = eg.ynbox(msg=f'请确认要插入的{plate_init_out[plate_num - 1]}是否对应{key}?',
                                          title='Dorfromantik Tool!')
                        if y_or_n:
                            value_new = (plate_init_out[plate_num - 1] + plate_next[new_num - 1])
                            plate_init_in_temp[key] = value_new
                plate_init_in = copy.deepcopy(plate_init_in_temp)

            plate_finnal_out.append(plate_init_out[plate_num - 1] + plate_next[new_num - 1])
            i = 2
            while i < 6 - len_p:
                plate_finnal_out.append(plate_next[new_num - i])
                i += 1
            plate_finnal_out.append(
                (plate_next[new_num - 6 + len_p] + plate_init_out[plate_num - len(plate_init_out) + 1]))
            j = len(plate_init_out) - 2
            while j > 1:
                plate_finnal_out.append(plate_init_out[plate_num - j])
                j -= 1

    # 当plate_finnal中存在五地形和六地形时,需要特殊处理
    for i in plate_finnal_out:
        if len(i) == 5:
            index_i = plate_finnal_out.index(i)
            # 把五地形板块从地形列表中取出来,并将前后板块合并
            plate_finnal_out.pop(index_i)
            plate_finnal_out[index_i] = plate_finnal_out[index_i - 1] + 'X' + plate_finnal_out[index_i]
            plate_finnal_out.pop(index_i - 1)
            # 单独把五地形放入另一个字典里
            if index_i != 0:
                plate_finnal_in[i] = plate_finnal_out[index_i - 1]
            else:
                plate_finnal_in[i] = plate_finnal_out[index_i]
    return tuple(plate_finnal_out), plate_finnal_in, plate_init_in
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-11-15 20:42

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表