diff --git a/src/game.py b/src/game.py index 1980cef..c792663 100644 --- a/src/game.py +++ b/src/game.py @@ -91,7 +91,7 @@ def create_area(cls): map_w, map_h = map(lambda x: x+3, cls.map_size) # 获得地图大小 # 根据地图大小创建游戏区域,要比地图大小稍微大一点 game_area = curses.newwin(map_h, map_w, 1, 1) - msg_area = curses.newwin(7, map_w, map_h+1, 1) + msg_area = curses.newwin(8, map_w, map_h+1, 1) game_area.keypad(True) # 支持上下左右等特殊按键 game_area.nodelay(True) # 非阻塞,用户没操作游戏要持续进行 cls.game_area = game_area @@ -190,21 +190,39 @@ def draw_border(self): # 根据边界点坐标绘制游戏区域边框 def draw_score(self): line_ins = self.get_ins('line') score_text = f'SCORE: {self.__score} ' - len_text = f'LENGTH: {len(line_ins.attrs["body_pos"])}' - self.msg_area.addstr(0, 0, score_text+len_text) + len_text = f'TAIL LEN: {len(line_ins.attrs["body_pos"])}' + self.msg_area.addstr(0, 0, score_text+' / '+len_text) + + def calc_score(self): # 计算出最终得分,返回(分数,长度,总得分) + difficulty = self.all_cfg['difficulty']*0.1 + line_ins = self.get_ins('line') + body_len = len(line_ins.attrs['body_pos']) # 尾巴长度 + score = self.__score + multiply = score*body_len*difficulty + return (score, body_len, round(multiply, 2)) def over(self): # 游戏结束 + res_ins = Res() self.cancel_tasks() # 清除并行任务 self.tui.erase() # 擦除内容 self.game_area.erase() # 擦除游戏区域内容 - over_text = Res().art_texts('gameover')[2] # 获得艺术字GAME OVER - self.tui.addstr(1, 5, Res.x_offset( - over_text, 5), curses.color_pair(4)) + text_h, text_w, over_text = res_ins.art_texts( + 'gameover') # 获得艺术字GAME OVER + self.tui.addstr(1, 1, Res.x_offset( + over_text, 1), curses.color_pair(4)) + self.msg_area.mvwin(text_h+1, 1) # 移动一下msg区的位置 + pattern = '{:-^' + str(text_w) + '}' + result_text = pattern.format('RESULT') + score, tail_len, total = map(str, self.calc_score()) + score_text = 'SCORE: ' + score+'\nTAIL LENGTH: '+tail_len+'\nTOTAL SCORE: '+total + self.msg_area.addstr(0, 0, result_text) + self.msg_area.addstr(1, 0, score_text) self.msg_area.addstr( - 0, 0, 'Type: \n(R) to Replay the Game\n(B) to Return to Menu') + 5, 0, 'Type: \n(R) to Replay the Game\n(B) to Return to Menu') self.tui.refresh() self.msg_area.refresh() self.msg_area.nodelay(False) # 阻塞接受getch + res_ins.set_ranking(total) # 尝试计入排名 while True: recv = self.msg_area.getch() if recv in (ord('r'), ord('R')): # 按下R/r diff --git a/src/resource.py b/src/resource.py index 4199d71..fb59078 100644 --- a/src/resource.py +++ b/src/resource.py @@ -2,6 +2,7 @@ from os import path from math import floor import random +import time import json @@ -9,9 +10,11 @@ class Res: def __init__(self) -> None: self.f_path = path.dirname(__file__) # 当前程序运行所在的绝对目录 config_path = self.f_path+'/config.json' + ranking_path = self.f_path+'/ranking.json' default_config = { # 默认配置文件 'difficulty': 1, 'tps': 10, # ticks per second + 'max_rank_len': 100, # 排名最多收录多少条 'diff_cfg': { # 不同困难度对应的配置 "1": { "map_size": (50, 15), @@ -143,9 +146,16 @@ def __init__(self) -> None: } } } + default_ranking = { + "rank_list": [] + } + if not path.exists(config_path): # 如果没有就自动创建配置文件 with open(config_path, 'w+') as f: f.write(json.dumps(default_config, indent=2)) + if not path.exists(ranking_path): # 如果没有就自动创建排名 + with open(ranking_path, 'w+') as f: + f.write(json.dumps(default_ranking)) def art_texts(self, k): # 获取艺术字,返回值(高度,长度,艺术字文本),艺术字都放在了./texts目录下 file_path = self.f_path+'/texts/'+k+'.txt' @@ -153,8 +163,8 @@ def art_texts(self, k): # 获取艺术字,返回值(高度,长度,艺术 with open(file_path, 'r') as f: lines = f.readlines() text_height = len(lines) # 以行数为高度 - text_width = max(*map(lambda x: len(x), lines) - ) # 以最长的一行文本的长度为宽度 + # 以最长的一行文本的长度为宽度 + text_width = max(*map(lambda x: len(x), lines)) f.seek(0, 0) # readlines后文件指针指向末尾了,要拉回来!这是一个非常容易出错的点! result = (text_height, text_width, f.read()) return result # 让with as语句块执行完后再返回 @@ -176,6 +186,30 @@ def set_config(self, key, val): f.write(json.dumps(pre_cfg, indent=2)) # 写入修改后的配置 return True + def get_ranking(self): # 获得排名 + file_path = self.f_path+'/ranking.json' + with open(file_path, 'r') as f: + get_dict = json.loads(f.read()) + return get_dict + + def set_ranking(self, total_score): # 加入排名 + file_path = self.f_path+'/ranking.json' + config = self.get_config() + pre_ranking = self.get_ranking() + rank_list = pre_ranking['rank_list'] + current_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) + if len(rank_list) > config['max_rank_len']: # 超出排名收录的数量了 + last_one = rank_list[-1] # 找到当前排名最后的 + if total_score > last_one[1]: + rank_list.pop() + rank_list.append((current_time, total_score)) + else: + rank_list.append((current_time, total_score)) + rank_list.sort(key=lambda x: x[1], reverse=True) + with open(file_path, 'w+') as f: + f.write(json.dumps(pre_ranking)) # 写入排名 + return True + @staticmethod # 作为一个静态方法 def x_offset(string, offset): '''搭配addstr,处理字符串的偏移。如果只用addstr的x-offset的话就第一行有偏移,其他行都是一个样,这个方法将字符串除第一行之外所有行头部都加上offset空格''' diff --git a/src/test.py b/src/test.py index 922e0bd..266b051 100644 --- a/src/test.py +++ b/src/test.py @@ -3,31 +3,5 @@ import asyncio import os a=Figlet() -'''with open(os.path.dirname(__file__)+'/texts/gameover.txt', 'w') as file_object: - file_object.write(a.renderText('GAME OVER'))''' -''' -async def test(): - await asyncio.sleep(2) - print('Finished waiting') -async def main(task): - for i in range(50): - print(i) - if i==10: - task.append(asyncio.create_task(test())) - await asyncio.sleep(2) - -async def enter(): - task=[] - task.append(asyncio.create_task(main(task))) - await asyncio.wait(task) - - -asyncio.run(enter())''' -class Test: - def __init__(self,h) -> None: - self.hello=h - def delete(self): - del self -test=Test('world') -test.delete() -print(test) \ No newline at end of file +with open(os.path.dirname(__file__)+'/texts/ranking.txt', 'w') as file_object: + file_object.write(a.renderText('R A N K I N G')) \ No newline at end of file diff --git a/src/texts/ranking.txt b/src/texts/ranking.txt new file mode 100644 index 0000000..94d2c94 --- /dev/null +++ b/src/texts/ranking.txt @@ -0,0 +1,6 @@ + ____ _ _ _ _ __ ___ _ _ ____ +| _ \ / \ | \ | | | |/ / |_ _| | \ | | / ___| +| |_) | / _ \ | \| | | ' / | | | \| | | | _ +| _ < / ___ \ | |\ | | . \ | | | |\ | | |_| | +|_| \_\ /_/ \_\ |_| \_| |_|\_\ |___| |_| \_| \____| + diff --git a/src/view.py b/src/view.py index a9b3157..aa22453 100644 --- a/src/view.py +++ b/src/view.py @@ -24,8 +24,8 @@ def create_win(self, title_txt): title.addstr(1, 3, Res.x_offset(title_txt[2], 3)) # 打印出游戏名 title.border() # 标题旁边加个边框 # 再创建一个窗口,我们作为菜单 - # 高为 3 宽为 title_offset_w,在命令行窗口的 title_offset_h+2行2列 - choice_session = curses.newwin(5, title_offset_w, title_offset_h+2, 2) + # 高为 8 宽为 title_offset_w,在命令行窗口的 title_offset_h+2行2列 + choice_session = curses.newwin(10, title_offset_w, title_offset_h+2, 2) choice_session.nodelay(False) # 阻塞 choice_session.keypad(True) # 支持上下左右这种特殊按键 return (title, choice_session) @@ -37,12 +37,14 @@ def __init__(self) -> None: self.choice_dict = { # 几个选项 0: 'Start to line', 1: 'Set Difficulty', - 2: 'Exit' + 2: 'Ranking', + 3: 'Exit' } self.choice_func = { # 上述选项对应的函数 0: self.start_game, 1: DifficultyView().show_panel, - 2: self.leave + 2: RankingView().show_panel, + 3: self.leave } self.last_choice = len(self.choice_dict.keys())-1 # 最后一个选项的索引,用来封底 @@ -64,7 +66,7 @@ async def asyncio_game(self): # 开启并行任务 self.game_end_choice = game.end_choice # 把游戏结束后的值传出去 def start_game(self): # 开始游戏 - asyncio.run(self.asyncio_game()) + asyncio.run(self.asyncio_game()) # 开启事件循环 choice_dict = { 'restart': self.start_game, 'menu': self.menu @@ -140,3 +142,48 @@ def show_panel(self): curses.endwin() # 中止窗口,取消初始化 res_ins.set_config('difficulty', difficulty_set) MenuView().menu() # 返回主菜单 + + +class RankingView(BasicView): + def list_maker(self, chunk, start=0): + list_str = 'PLACE DATE SCORE\n' + for key, item in enumerate(chunk): + place = start+key+1 + date, score = item + list_str += f'{place} {date} {score}\n' + list_str += '\nPress (D) for Next Page, (A) for Prev Page' + return list_str + + def show_panel(self): + rank_list = Res().get_ranking()['rank_list'] + title, choice_session = self.create_win(Res().art_texts('ranking')) + chunked = [] + each_chunk = 6 + for i in range(0, len(rank_list), each_chunk): + the_chunk = rank_list[i:i+each_chunk] + chunked.append(the_chunk) # 分片 + self.tui.refresh() # 刷新总界面 + title.refresh() # 刷新标题窗口 + current_page = 0 + max_page = len(chunked)-1 + while True: + choice_session.erase() # 清除之前的显示 + if len(rank_list) > 0: + current_chunk = chunked[current_page] + start_place = current_page*each_chunk + choice_session.addstr( + 1, 0, self.list_maker(current_chunk, start_place)) + else: # 暂无记录 + choice_session.addstr(1, 1, 'NO RECORDS') + choice_session.refresh() # 刷新选项窗口,输出上面的内容 + recv = choice_session.getch() + if recv in (ord('D'), ord('d')) and current_page < max_page: + current_page += 1 + elif recv in (ord('A'), ord('a')) and current_page > 0: + current_page -= 1 + elif recv in (10, curses.KEY_ENTER): + break + del title, choice_session # 删除窗口 + self.tui.clear() # 清除屏幕 + curses.endwin() # 中止窗口,取消初始化 + MenuView().menu() # 返回主菜单