#!/bin/env python """ThudBoard - The Discworld Boardgame without rules Version 1.8 Copyright 2003, 2004, 2005, 2006, 2007 by Marc Boeren More information ---------------- Read the docs (index.html, howto.html, download.html and about.html) for more information about ThudBoard and how to use ThudBoard. TODO ---- skins (partially done) web-save option keyboard-bindings two-player internet-connection game cleanup of code file > print? rules-plugin AI-plugin """ import sys import os import copy import Tkinter as tk #from tkMessageBox import askyesno from bugfix_askyesno import fixed_askyesno as askyesno from tkMessageBox import showwarning from tkFileDialog import askopenfilename from tkFileDialog import asksaveasfilename from tkFileDialog import askdirectory from battle import ThudMove, ThudBattle import skins import texts from webbrowser import open as webbrowser_open import ConfigParser #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# def main(filename=None): app = App() if filename: if app.battle.load(filename): app.new_mru(filename) app.UpdateBattle() app.saved = True app.update_infoimg() else: showwarning(texts.warningLoadFailedTitle, texts.warningLoadFailedMessage) app.mainloop() #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# class Size(object): """Size is a placeholder for items that have x, y, dx and dy values.""" def __init__(self, x, y, dx, dy): self.x = x self.y = y self.dx = dx self.dy = dy def inside(self, x, y): """Return True is x, y is inside this Size, treated as a rectangle.""" if self.x <= x <= self.x + self.dx \ and self.y <= y <= self.y + self.dy: return True return False #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# class App(tk.Tk): def __init__(self): # setup app-window self.startup = True tk.Tk.__init__(self) self.skin = skins.init() self.configure(background=self.skin.color['background']) self.wm_title("ThudBoard") try: self.wm_iconbitmap('thud.ico') except: pass # if it doesn't work, well, then I'll do without the icon self.wm_minsize(790, 540) self.wm_maxsize(790, 540) self.wm_geometry('790x540+0+0') self.wm_resizable(False, False) self.focus_force() # setup globals pointsize = self.calc_pointsize(10.7) try: winver = sys.winver self.font = ("microsoft sans serif", pointsize) except: self.font = ("helvetica", pointsize) self.toggle_boardmap = False self.toggle_highlight = True self.toggle_score = True self.toggle_comment = False self.submenu = False self.skip_next_click = False self.saved = True self.start_coord = "" self.drag_coord = "" self.highlight_area = None self.move_stage = 'moving' # free to move, other stage is capturing self.scrollersensitivity = 100 self.scrollerid = 0 self.motionposition = (0, 0) self.pieces = {} self.mru_count = 8 self.mru = ['',] * self.mru_count # determine user battle/configdir # existing dirs have preference, then, # user-specifics dirs have preference over common dirs self.configdir = os.getcwd() self.battledir = os.path.join(os.getcwd(), "battles") if not os.path.isdir(self.battledir): # no common dir configdir = os.path.join(os.path.expanduser("~"), ".thudboard") battledir = os.path.join(configdir, "battles") try: os.makedirs(battledir) except: pass if os.path.isdir(battledir): # success creating userdir self.configdir = configdir self.battledir = battledir else: # create common dir try: os.makedirs(self.battledir) except: pass # load saved options self.load_options(os.path.join(self.configdir, "thudboard.cfg")) self.listlines = self.toggle_comment and 19 or 25 # setup board self.size_battlename = Size(599, 115, 171, 13) self.size_list = Size(599, 157, 171, 13) self.size_thudurl = Size(599, 518, 171, 13) self.tile = Size(252, 252, 36, 36) self.DrawInitialize() # setup events self.EventInitialize() # go! self.background_disabled = False self.startup = True self.file_new() self.skip_next_click = False def EventInitialize(self): self.protocol("WM_DELETE_WINDOW", self.file_quit) self.bind('', self.focus_in) self.canvas.bind('', self.piece_highlight) self.canvas.bind('', self.drag_start) self.canvas.bind('', self.drag) self.canvas.bind('', self.canvas_mouserelease_dispatch) #self.canvas.bind('', self.hide_piece) self.background.bind('', self.background_area_click) self.background.bind('', self.background_scrollers) self.background.bind('', self.background_motion) self.bind('', self.cmd_copy) self.bind('', self.cmd_paste) self.commentsbox.bind('', self.update_comment) def calc_pointsize(self, pixelapprox): try: dpiy = self.winfo_screenheight()/(self.winfo_screenmmheight()/25.4) except: return 8 pointsize = 72.0*pixelapprox/dpiy return int(pointsize) # event handlers def update_comment(self, event): comment = self.commentsbox.get("1.0", tk.END).strip() if self.battle_index>=0 and comment!=self.battle.moves[self.battle_index].comment: self.battle.moves[self.battle_index].comment = comment self.saved = False elif self.battle_index==-1 and comment!=self.battle.comment: self.battle.comment = comment self.saved = False self.update_infoimg() def focus_in(self, event): if not self.startup: if self.battle_index>=0: dy, h = self.battle.get_offset_and_height(self.battle_index, self.list_offset) self.MoveHighlight(dy*self.size_list.dy, h*self.size_list.dy) else: self.MoveHighlightBattlename() self.startup = False def cmd_copy_click(self, event): self.background.focus_set() self.update_comment(None) self.cmd_copy(event) def cmd_copy(self, event): if self.background.focus_get() == self.commentsbox: return if self.battle.index<0: return txt = [line.strip() for line in self.battle.txt_battle_move().splitlines()] txt = " ".join(txt).replace("~ ", "") comment = self.battle.moves[self.battle_index].comment.strip() if comment: comment = "# "+"\n# ".join(comment.split('\n')) txt += (comment.find('\n') < 0 and " " or "\n") + comment self.clipboard_clear() self.clipboard_append(txt) def cmd_paste_click(self, event): self.temp_disable_background_click() self.cmd_paste(event) def cmd_paste(self, event): if self.background.focus_get() == self.commentsbox: return try: txt = self.selection_get(selection='CLIPBOARD') except: txt = '' if txt: parts = txt.split('#') txt = parts[0].strip() comment = '' if len(parts)>1: comment = "\n".join([p.strip() for p in parts[1:]]) txt = "".join(txt.splitlines()).replace(" ", "") test = ThudMove(-1, "", "", []) test.load_str(txt, comment) self.moving = True self.move = test self.end_move() def background_motion(self, event): self.piece_highlight_off(event) self.motion_highlight_area(event) self.motionposition = (event.x, event.y) def motion_highlight_area(self, event): # this could be nicer, perhaps combined with the click-handler clickable_areas = dict() clickable_areas[False] = [ Size(texts.menuFile_xpos_width[0], 79, texts.menuFile_xpos_width[1], 14),# self.menu_file_click), Size(texts.menuQuicksave_xpos_width[0], 79, texts.menuQuicksave_xpos_width[1], 14),# self.menu_save_click), Size(texts.menuOptions_xpos_width[0], 79, texts.menuOptions_xpos_width[1], 14),# self.menu_opts_click), Size(texts.menuHelp_xpos_width[0], 79, texts.menuHelp_xpos_width[1], 14),# self.menu_help_click), Size(599, 114, 171, 14),# self.battlename_click), #Size(599, 157, 171, 13*25),# self.list_click), #Size(599, 146, 171, 10),# self.scrollup_click), #Size(599, 483, 171, 10),# self.scrolldown_click), Size(599, 510, 171, 14),# self.thudurl_click), ] clickable_areas['file'] = [ Size(610, 91, 80, 14), Size(610,106, 80, 14), Size(610,121, 80, 14), Size(610,136, 80, 14), #Size(610,151, 80, 4), Size(610,156, 80, 14), Size(610,171, 80, 14), Size(610,186, 80, 14), Size(610,201, 80, 14), Size(610,216, 80, 14), Size(610,231, 80, 14), Size(610,246, 80, 14), Size(610,261, 80, 14), ] clickable_areas['options'] = [ Size(701, 91, 80, 14), Size(701,106, 80, 14), Size(701,121, 80, 14), Size(701,136, 80, 14), ] clickable_areas['help'] = [ Size(679, 91, 80, 14), Size(679,106, 80, 14), Size(679,121, 80, 14), ] anyarea = False for area in clickable_areas[self.submenu]: if area.inside(event.x, event.y): if area!=self.highlight_area: self.highlight_area = area anyarea = True self.background.delete("highlight_area") self.background.create_rectangle(area.x, area.y, area.x + area.dx, area.y + area.dy, tag="highlight_area", outline=self.skin.color['background']) if not anyarea: self.background.delete("highlight_area") def background_scrollers(self, event): self.background.focus_set() clickable_areas = [ (Size(599, 146, 171, 10), -1), (Size(599, 483, 171, 10), +1), ] if self.toggle_comment: clickable_areas[1] = (Size(599, 405, 171, 10), +1) if not self.skip_next_click: for (area, direction) in clickable_areas: if area.inside(event.x, event.y): self.motionposition = (event.x, event.y) self.scrollerid = self.background.after(100, self.pollscroller, area, direction) def pollscroller(self, area, direction): if area.inside(self.motionposition[0], self.motionposition[1]): self.UpdateScroll(direction) self.scrollerid = self.background.after(self.scrollersensitivity, self.pollscroller, area, direction) elif self.scrollerid: self.background.after_cancel(self.scrollerid) self.scrollerid = 0 def temp_disable_background_click(self): self.background_disabled = True self.background.after(500, self.enable_background_click) def enable_background_click(self): self.background_disabled = False def background_area_click(self, event): if self.background_disabled: return if self.scrollerid: self.background.after_cancel(self.scrollerid) self.scrollerid = 0 # handle possible outstanding stuff self.RemoveSubmenu() if self.move != ThudMove(-1, '??', '??', [], True, False): self.end_move() self.start_coord = self.drag_coord = "" # note: overlapping areas will be handled in order clickable_areas = [ (Size(texts.menuFile_xpos_width[0], 79, texts.menuFile_xpos_width[1], 15), self.menu_file_click), (Size(texts.menuQuicksave_xpos_width[0], 79, texts.menuQuicksave_xpos_width[1], 15), self.menu_save_click), (Size(texts.menuOptions_xpos_width[0], 79, texts.menuOptions_xpos_width[1], 15), self.menu_opts_click), (Size(texts.menuHelp_xpos_width[0], 79, texts.menuHelp_xpos_width[1], 15), self.menu_help_click), (Size(599, 114, 171, 15), self.battlename_click), (Size(599, 157, 171, 13*self.listlines), self.list_click), (Size(599, 146, 171, 10), self.scrollup_click), (Size(599, 483, 171, 10), self.scrolldown_click), (Size(599, 510, 171, 15), self.thudurl_click), ] if self.toggle_comment: clickable_areas[7]= (Size(599, 405, 171, 10), self.scrolldown_click) if not self.skip_next_click: for (area, handler) in clickable_areas: if area.inside(event.x, event.y): handler(event, area) self.skip_next_click = False # file menu def menu_file_click(self, event, area = Size(0,0,0,0)): items = [(texts.menuFileNew, self.file_new), (texts.menuFileOpen, self.file_open), (texts.menuFileSaveAs, self.file_save_as), (texts.menuFileSaveSnapshot, self.file_save_snapshot), ('_________', None), # separator ] for index, item in zip(range(len(self.mru)), self.mru): handler = getattr(self, 'file_mru%d' % index) mruname = os.path.splitext(os.path.split(item)[1])[0] items.append(('%d. '%(index+1)+mruname[0:10]+(mruname[10:] and '...' or ''), handler)) self.HandleSubmenu('file', area.x+4, area.y+4, items) def file_mru(self, mruindex): self.RemoveSubmenu() self.skip_next_click = True if not self.saved and not askyesno(texts.askOpenGameTitle, \ texts.askOpenGameMessage): return filename = self.mru[mruindex] if filename: if self.battle.load(filename): self.new_mru(filename) self.UpdateBattle() self.saved = True self.update_infoimg() else: showwarning(texts.warningLoadFailedTitle, \ texts.warningLoadFailedMessage) self.skip_next_click = True def file_mru0(self, event = None): self.file_mru(0) def file_mru1(self, event = None): self.file_mru(1) def file_mru2(self, event = None): self.file_mru(2) def file_mru3(self, event = None): self.file_mru(3) def file_mru4(self, event = None): self.file_mru(4) def file_mru5(self, event = None): self.file_mru(5) def file_mru6(self, event = None): self.file_mru(6) def file_mru7(self, event = None): self.file_mru(7) def file_new(self, event = None): self.RemoveSubmenu() if not self.saved and not askyesno(texts.askNewGameTitle, \ texts.askNewGameMessage): return self.battle = ThudBattle() self.battle.battledir = self.battledir self.UpdateBattle() self.saved = True self.update_infoimg() self.skip_next_click = True def file_open(self, event = None): self.RemoveSubmenu() self.skip_next_click = True if not self.saved and not askyesno(texts.askOpenGameTitle, \ texts.askOpenGameMessage): return ft = [(texts.filetypesThudBattles, ".thud"), (texts.filetypesAllFiles, "*")] filepath = os.path.split(self.battle.get_filename())[0] filename = askopenfilename(initialdir=filepath, filetypes = ft) if filename: if self.battle.load(filename): self.new_mru(filename) self.UpdateBattle() self.saved = True self.update_infoimg() else: showwarning(texts.warningLoadFailedTitle, \ texts.warningLoadFailedMessage) def file_save_as(self, event = None): self.RemoveSubmenu() ft = [(texts.filetypesThudBattles, ".thud"), (texts.filetypesAllFiles, "*")] filepath = os.path.split(self.battle.get_filename())[0] filename = asksaveasfilename(initialdir=filepath, filetypes = ft) if filename and filename!=texts.anonymousBattle: if not os.path.splitext(filename)[1]: filename+= ".thud" self.SaveGame(filename) self.skip_next_click = True def file_save_snapshot(self, event = None): self.RemoveSubmenu() ft = [(texts.filetypesThudBattles, ".thud"), (texts.filetypesAllFiles, "*")] filepath = os.path.split(self.battle.get_filename())[0] filename = asksaveasfilename(initialdir=filepath, filetypes = ft) if filename and filename!=texts.anonymousBattle: if not os.path.splitext(filename)[1]: filename+= ".thud" if not self.battle.save_position(filename, self.battle_index): showwarning(texts.warningSaveFailedTitle, \ texts.warningSaveSnapshotFailedMessage) else: self.new_mru(filename) self.skip_next_click = True def file_quit(self, event = None): self.RemoveSubmenu() if not self.saved and not askyesno(texts.askExitGameTitle, \ texts.askExitGameMessage): return self.save_options(os.path.join(self.configdir, "thudboard.cfg")) self.skip_next_click = True self.destroy() def file_skins(self, event = None): self.RemoveSubmenu() dirname = askdirectory(initialdir='./img') if dirname: try: self.skin = skins.init(dirname) self.RedrawBoard() except: self.skin = skins.init() self.RedrawBoard() self.EventInitialize() self.UpdateBattle() self.skip_next_click = True # quicksave menu def menu_save_click(self, event, area = Size(0,0,0,0)): # quicksave won't work unless there is a filename if self.battle.name==texts.anonymousBattle: ft = [(texts.filetypesThudBattles, ".thud"), (texts.filetypesAllFiles, "*")] filepath = os.path.split(self.battle.get_filename())[0] filename = asksaveasfilename(initialdir=filepath, filetypes = ft) if filename and filename!=texts.anonymousBattle: if not os.path.splitext(filename)[1]: filename+= ".thud" self.SaveGame(filename) else: self.SaveGame() # options menu def menu_opts_click(self, event, area = Size(0,0,0,0)): items = [(texts.menuOptionsBoardmap, self.opts_boardmap, self.toggle_boardmap and self.skin.check), (texts.menuOptionsHighlight, self.opts_highlight, self.toggle_highlight and self.skin.check), (texts.menuOptionsScore, self.opts_score, self.toggle_score and self.skin.check), (texts.menuOptionsComment, self.opts_comment, self.toggle_comment and self.skin.check)] self.HandleSubmenu('options', area.x+4, area.y+4, items) def opts_boardmap(self, event = None): self.RemoveSubmenu() self.toggle_boardmap = not self.toggle_boardmap self.canvas.delete(self.boardimg) img = self.toggle_boardmap and self.skin.boardgrid or self.skin.board self.boardimg = self.canvas.create_image(0, 0, image=img, anchor=tk.NW) self.canvas.tag_lower(self.boardimg, "thudstone") self.DrawPieces() self.skip_next_click = True def opts_highlight(self, event = None): self.RemoveSubmenu() self.toggle_highlight = not self.toggle_highlight self.skip_next_click = True def opts_score(self, event = None): self.RemoveSubmenu() self.toggle_score = not self.toggle_score self.DrawScore() self.skip_next_click = True def opts_comment(self, event = None): self.RemoveSubmenu() self.toggle_comment = not self.toggle_comment self.DrawCommentbox() self.skip_next_click = True # help menu def menu_help_click(self, event, area = Size(0,0,0,0)): items = [(texts.menuHelpContents, self.help_contents), (texts.menuHelpHowto, self.help_howto), (texts.menuHelpAbout, self.help_about)] self.HandleSubmenu('help', area.x+area.dx-96, area.y+4, items) # right align def help_contents(self, event = None): self.RemoveSubmenu() webbrowser_open("file:///"+os.getcwd()+"/docs/index.html") self.skip_next_click = True def help_howto(self, event = None): self.RemoveSubmenu() webbrowser_open("file:///"+os.getcwd()+"/docs/howto.html") self.skip_next_click = True def help_about(self, event = None): self.RemoveSubmenu() webbrowser_open("file:///"+os.getcwd()+"/docs/about.html") self.skip_next_click = True # other items clicked def battlename_click(self, event, area = Size(0,0,0,0)): self.battle_index = -1 self.battle.get_position(self.battle_index) self.MoveHighlightBattlename() self.DrawPieces() self.show_comment() def list_click(self, event, area = Size(0,0,0,0)): dy = ((event.y-self.size_list.y)/self.size_list.dy) i = self.battle.get_index(dy, self.list_offset) if i<0: return self.battle_index = i position = self.battle.get_position(self.battle_index) dy, h = self.battle.get_offset_and_height(self.battle_index, self.list_offset) self.DrawPieces() self.MoveHighlight(dy*self.size_list.dy, h*self.size_list.dy) self.show_comment() def scrollup_click(self, event, area = Size(0,0,0,0)): self.UpdateScroll(-1) def scrolldown_click(self, event, area = Size(0,0,0,0)): self.UpdateScroll(+1) def thudurl_click(self, event, area = Size(0,0,0,0)): webbrowser_open("http://www.thudgame.com") # handle the highlighters in the moves-list def MoveHighlight(self, dy, h): self.background.delete("move") if dy+h<=13*self.listlines and not self.battle_index', self.cmd_copy_click) self.update_paste(self.size_list.x+self.size_list.dx-15, self.size_list.y+dy) self.background.tag_lower("move", "movetxt") def MoveHighlightBattlename(self): self.background.delete("move") self.background.create_rectangle(self.size_battlename.x, self.size_battlename.y, self.size_battlename.x+self.size_battlename.dx, self.size_battlename.y+self.size_battlename.dy, tag="move", outline=self.skin.color['background'], fill=self.skin.color['background']) self.background.create_line(self.size_battlename.x, self.size_battlename.y+self.size_battlename.dy, self.size_battlename.x+self.size_battlename.dx+1, self.size_battlename.y+self.size_battlename.dy, fill=self.skin.color['text'], tag="move") self.update_paste(self.size_battlename.x+self.size_battlename.dx-15, self.size_battlename.y) self.background.tag_lower("move", "battlename") self.show_comment() def update_paste(self, x, y): try: txt = self.selection_get(selection='CLIPBOARD') except: txt = '' pasteimg = self.skin.noclip if txt: parts = txt.split('#') txt = parts[0].strip() comment = '' if len(parts)>1: comment = "\n".join([p.strip() for p in parts[1:]]) txt = "".join(txt.splitlines()).replace(" ", "") test = ThudMove(-1, "", "", []) if test.load_str(txt, comment): pasteimg = self.skin.nopaste if self.battle.check_move(test): pasteimg = self.skin.paste self.pasteicon = self.background.create_image(x, y, image=pasteimg, anchor=tk.NW, tag="move") if pasteimg==self.skin.paste: self.background.tag_bind(self.pasteicon, '', self.cmd_paste_click) def WriteMoves(self, movetxt): self.background.delete("movetxt") move_lines = movetxt.splitlines() h = 13 dx = 30 y = self.size_list.y moveno = (self.list_offset+1)/2 for line in move_lines: dy = ((y-self.size_list.y)/self.size_list.dy) i = self.battle.get_index(dy, self.list_offset) locked = (i<=self.battle.saved_index) line = line.replace(">>> ", "").replace("~ ", "") if locked and line.strip()[0]!="x": self.background.create_image(self.size_list.x, y, tag="movetxt", image=self.skin.lock, anchor=tk.NW) if line.strip().startswith('d') or line.strip().startswith('R'): moveno+= 1 self.background.create_text(self.size_list.x+dx, y, text="%d." % moveno, font=self.font, fill=self.skin.color['text'], tag="movetxt", anchor=tk.NE) self.background.create_text(self.size_list.x+dx, y, text=line, font=self.font, fill=self.skin.color['text'], tag="movetxt", anchor=tk.NW) y+= h def UpdateBattle(self): self.DrawCommentbox() self.battle_index = self.battle.index self.background.itemconfigure(self.text_gamename, text=self.battle.name) self.wm_title("ThudBoard [%s]" % self.battle.name) self.list_offset = self.battle.index mutable_offset = [self.list_offset] movetxt = "\n".join(self.battle.txt_battle_moves(mutable_offset, self.listlines, history_lines=12)) self.list_offset = mutable_offset[0] if not self.startup: self.WriteMoves(movetxt) #self.background.itemconfigure(self.movetxt, text=movetxt) position = self.battle.get_position(self.battle_index) dy, h = self.battle.get_offset_and_height(self.battle_index, self.list_offset) self.MoveHighlight(dy*self.size_list.dy, h*self.size_list.dy) self.DrawPieces() self.init_move() self.show_comment() self.update_infoimg() # create square-highlighter and make it move with the mouse def piece_highlight_off(self, event = None): self.canvas.itemconfigure("highlight", state='hidden') self.canvas.itemconfigure("stageimg", state='hidden') def piece_highlight(self, event): diffx, diffy = (event.x-self.tile.x), (event.y-self.tile.y) diffx = self.tile.dx * (diffx / self.tile.dx) diffy = self.tile.dy * (diffy / self.tile.dy) x = (self.tile.x+diffx)/self.tile.dx y = (self.tile.y+diffy)/self.tile.dy try: position = "%s%d" % (self.battle.remapx[x], 1+y) except: position = "" self.tile.x+= diffx self.tile.y+= diffy self.canvas.move("highlight", diffx, diffy) self.canvas.move("stageimg", diffx, diffy) if position in self.battle.valid_position: self.canvas.itemconfigure(self.highlight_tooltip, text=position) if self.toggle_highlight: self.canvas.itemconfigure("highlight", state='normal') self.canvas.lift("highlight") if self.move_stage=='capturing': self.canvas.itemconfigure("stageimg", state='normal') self.canvas.lift("stageimg") if position==self.move.to_pos: self.canvas.lift(self.moveimg) elif position in self.pieces: self.canvas.lift(self.captureimg) else: self.canvas.itemconfigure("stageimg", state='hidden') else: self.canvas.itemconfigure("highlight", state='hidden') self.canvas.itemconfigure("stageimg", state='hidden') def drag_highlight(self, position): self.canvas.delete("drag_highlight") if not self.toggle_highlight: return if position in self.battle.valid_position: x, y = self.GetPositionOrigin(position) self.canvas.create_rectangle(x, y, x+self.tile.dx, y+self.tile.dy, tag="drag_highlight", width=2, outline=self.skin.color['background']) self.canvas.create_rectangle(x+self.tile.dx-26, y+self.tile.dy-14, x+self.tile.dx, y+self.tile.dy, tag="drag_highlight", outline=self.skin.color['background'], fill=self.skin.color['background']) self.drag_highlight_tooltip = self.canvas.create_text( x+self.tile.dx-3, y+self.tile.dy, text=position, font=self.font, fill=self.skin.color['text'], tag="drag_highlight", anchor=tk.SE) # drag'n'drop pieces def drag_start(self, event): self.background.focus_set() self.background.delete("submenu") x, y = event.x/self.tile.dx, event.y/self.tile.dy try: position = "%s%d" % (self.battle.remapx[x], 1+y) except: position = "" if position in self.pieces: self.startposition = self.dragposition = position if position==self.move.to_pos: # continue drag self.move_stage = 'moving' self.update_move(to_pos = self.startposition) elif self.move.from_pos=='??': self.update_move(from_pos = self.startposition, to_pos = '??') elif self.move_stage=='capturing': self.hide_piece(event) else: self.end_move() self.update_move(from_pos = self.startposition, to_pos = '??') self.move_stage = 'moving' if self.move_stage!='capturing': self.canvas.lift(self.pieces[self.startposition]) else: self.end_move() self.move_stage = 'moving' self.startposition = self.dragposition = "" def drag(self, event): if not self.dragposition: return if self.move_stage=='capturing': return x, y = event.x/self.tile.dx, event.y/self.tile.dy try: position = "%s%d" % (self.battle.remapx[x], 1+y) except: position = "" self.drag_highlight(position) if position in self.battle.valid_position: diffx = (self.battle.mapx[position[0]]-self.battle.mapx[self.dragposition[0]]) \ *self.tile.dx diffy = (int(position[1:])-int(self.dragposition[1:])) \ *self.tile.dy self.canvas.move(self.pieces[self.startposition], diffx, diffy) self.dragposition = position self.update_move(to_pos = self.dragposition) def canvas_mouserelease_dispatch(self, event): if self.dragposition: self.drag_stop(event) self.move_stage = 'capturing' self.piece_highlight(event) #else: # self.move_stage = 'moving' def drag_stop(self, event): if not self.dragposition: return if self.move_stage=='capturing': return x, y = event.x/self.tile.dx, event.y/self.tile.dy try: position = "%s%d" % (self.battle.remapx[x], 1+y) except: position = "" if position not in self.battle.valid_position: position = self.dragposition if position!=self.startposition: self.update_move(to_pos = position) if position in self.pieces: self.update_move(thud_list_append = [position]) self.canvas.itemconfigure(self.pieces[position], state='hidden') del(self.pieces[position]) self.pieces[position] = self.pieces[self.startposition] del(self.pieces[self.startposition]) self.startposition = self.dragposition = "" self.drag_highlight("A1") # invalid position, won't show # hide captured pieces def hide_piece(self, event): x, y = event.x/self.tile.dx, event.y/self.tile.dy try: position = "%s%d" % (self.battle.remapx[x], 1+y) except: position = "" if position in self.pieces: self.update_move(thud_list_append = [position]) self.canvas.itemconfigure(self.pieces[position], state='hidden') del(self.pieces[position]) # handle moves def init_move(self): self.move = ThudMove(-1, '??', '??', [], True, False) self.moving = False def start_move(self): self.move = ThudMove(-1, '??', '??', [], True, False) self.moving = True self.show_move() def update_move(self, from_pos=None, to_pos=None, thud_list_append=None): if not self.moving: self.start_move() if not from_pos is None: self.move.from_pos = from_pos self.move.piece = self.battle.piece_on(from_pos) if not to_pos is None: self.move.to_pos = to_pos if not thud_list_append is None: self.move.thud_list+= thud_list_append self.show_move() def end_move(self): if not self.moving: self.show_move() return if self.move.from_pos=='??' or self.move.to_pos=='??': self.DrawPieces() # updates self.pieces! self.init_move() self.show_move() return if self.battle.add_thudmove(self.move): self.saved = False self.battle_index = self.battle.index else: self.bell() if self.battle_index13*self.listlines: self.UpdateScroll(+1) dy, h = self.battle.get_offset_and_height(self.battle.index, self.list_offset) dy = (dy+h) * self.size_list.dy h = self.move.get_height() * self.size_list.dy self.background.delete("movingtxt") self.current_movetxt = self.background.create_text( self.size_list.x, self.size_list.y+dy, text=self.move.txt_move(), font=self.font, fill=self.skin.color['text'], tag="movingtxt", anchor=tk.NW) self.background.delete("moving") self.background.create_rectangle(self.size_list.x, self.size_list.y+dy, self.size_list.x+self.size_list.dx, self.size_list.y+dy+h, tag="moving", outline=self.skin.color['background'], fill=self.skin.color['background']) self.background.create_line(self.size_list.x, self.size_list.y+dy, self.size_list.x+self.size_list.dx+1, self.size_list.y+dy, fill=self.skin.color['text'], tag="moving") self.background.tag_lower("moving", "movingtxt") def GetPositionOrigin(self, position): posx = self.battle.mapx[position[0]] posy = int(position[1:])-1 return posx * self.tile.dx, posy * self.tile.dy def DrawInitialize(self): # draw background self.background = tk.Canvas(self, width=790, height=540, borderwidth=0, bg=self.skin.color['background'], highlightthickness=0) self.background.pack() self.RedrawBoard() def RedrawBoard(self): self.background.delete() for x in range(4): for y in range(5): self.background.create_image(x*200, y*130, image=self.skin.bcktile, anchor=tk.NW) # draw board self.canvas = tk.Canvas(self.background, width=540, height=540, borderwidth=0, bg=self.skin.color['background'], highlightthickness=0) img = self.toggle_boardmap and self.skin.boardgrid or self.skin.board self.boardimg = self.canvas.create_image(0, 0, image=img, anchor=tk.NW) self.background.create_window(0, 0, window=self.canvas, anchor=tk.NW) self.background.create_image(540, 0, image=self.skin.score, anchor=tk.NW) scorefont = ('arial', 14, 'bold') self.score_dwarf = self.background.create_text(560, 390, text="0", font=scorefont, fill=self.skin.color['score']) self.score_troll = self.background.create_text(560, 410, text="0", font=scorefont, fill=self.skin.color['score']) self.background.create_image(590, 9, image=self.skin.thudtitle, anchor=tk.NW) self.background.create_image(590, 72, image=self.skin.banner, anchor=tk.NW) self.background.create_image(590, 107, image=self.skin.banner, anchor=tk.NW) self.background.create_image(590, 142, image=self.skin.list, anchor=tk.NW) self.background.create_image(590, 503, image=self.skin.banner, anchor=tk.NW) self.menu_file = self.background.create_text(texts.menuFile_xpos_width[0]+4, 86, font=self.font, text=texts.menuFile, fill=self.skin.color['text'], anchor=tk.W) self.menu_save = self.background.create_text(texts.menuQuicksave_xpos_width[0]+4, 86, font=self.font, text=texts.menuQuicksave, fill=self.skin.color['text'], anchor=tk.W) self.menu_opts = self.background.create_text(texts.menuOptions_xpos_width[0]+4, 86, font=self.font, text=texts.menuOptions, fill=self.skin.color['text'], anchor=tk.W) self.menu_help = self.background.create_text(texts.menuHelp_xpos_width[0]+4, 86, font=self.font, text=texts.menuHelp, fill=self.skin.color['text'], anchor=tk.W) self.text_gamename = self.background.create_text(685, 121, font=self.font, text=texts.anonymousBattle, fill=self.skin.color['text'], tag="battlename") self.text_about = self.background.create_text(685, 517, font=self.font, fill=self.skin.color['text'], text="http://www.thudgame.com") self.movetxt = self.background.create_text( self.size_list.x, self.size_list.y, text=texts.text_mini_howto, font=self.font, fill=self.skin.color['text'], tag="movetxt", anchor=tk.NW) # create highlight self.canvas.create_rectangle(self.tile.x, self.tile.y, self.tile.x+self.tile.dx, self.tile.y+self.tile.dy, tag="highlight", width=2, outline=self.skin.color['background']) self.canvas.create_rectangle(self.tile.x+self.tile.dx-26, self.tile.y+self.tile.dy-14, self.tile.x+self.tile.dx, self.tile.y+self.tile.dy, tag="highlight", outline=self.skin.color['background'], fill=self.skin.color['background']) self.highlight_tooltip = self.canvas.create_text( self.tile.x+self.tile.dx-3, self.tile.y+self.tile.dy, text="H8", font=self.font, fill=self.skin.color['text'], tag="highlight", anchor=tk.SE) self.canvas.itemconfigure("highlight", state='hidden') # create capture-mouseover-icons self.captureimg = self.canvas.create_image(self.tile.x+1, self.tile.y+1, image=self.skin.capture, anchor=tk.NW, tag="stageimg", state='hidden') self.moveimg = self.canvas.create_image(self.tile.x+1, self.tile.y+1, image=self.skin.move, anchor=tk.NW, tag="stageimg", state='hidden') #self.endmoveimg = self.canvas.create_image(self.tile.x+1, self.tile.y+1, image=self.skin.endmove, anchor=tk.NW, tag="stageimg", state='hidden') # initially hidden commentsbox self.downimg = self.background.create_image(599, 409, image=self.skin.down, anchor=tk.NW, tag="downimg", state='hidden') self.infoimg = self.background.create_image(599, 115, image=self.skin.info, anchor=tk.NW, tag="infoimg", state='hidden') self.commentsbox = tk.Text(self.background, borderwidth=1, background=self.skin.color['score'], foreground=self.skin.color['text']); self.DrawCommentbox() # draw pieces on the board def DrawPieces(self): def place_piece(piece, coord): if piece not in (self.battle.dwarf, self.battle.troll, self.battle.rock): return posx = self.battle.mapx[coord[0]] posy = int(coord[1:])-1 img = piece is self.battle.dwarf and self.skin.dwarf or piece is self.battle.troll and self.skin.troll or self.skin.rock tag = piece is self.battle.dwarf and 'dwarf' or piece is self.battle.troll and 'troll' or 'rock' self.pieces[coord] = self.canvas.create_image( posx*self.tile.dx, posy*self.tile.dy, image=img, tag=tag, anchor=tk.NW) for piece in self.pieces.values(): self.canvas.delete(piece) self.pieces = {} position = self.battle.get_position(self.battle.index) for piece in (self.battle.dwarf, self.battle.troll, self.battle.rock): for coord in position[piece]: place_piece(piece, coord) # update score self.DrawScore() def DrawScore(self): position = self.battle.get_position(self.battle.index) d = len(position[self.battle.dwarf]) cd = 32-d t = len(position[self.battle.troll]) ct = 8-t if self.toggle_score: d = cd t = ct self.background.itemconfigure(self.score_dwarf, text="%d" % d) self.background.itemconfigure(self.score_troll, text="%d" % t) # draw pieces self.background.delete("captures") x, y = 542, 353-11*cd for i in range(cd): self.background.create_image(x, y, image=self.skin.dwarf, anchor=tk.NW, tag="captures") y+= 11 y = 512-11*ct for i in range(ct): self.background.create_image(x, y, image=self.skin.troll, anchor=tk.NW, tag="captures") y+= 11 def RemoveSubmenu(self): self.background.delete("submenu") self.background.delete("highlight_area") self.submenu = False def HandleSubmenu(self, id, x, y, items): self.submenu = id self.background.create_image(x, y, image=self.skin.submenutop, tag="submenu", anchor=tk.NW) y+=8 dx=8 self.submenus = [] for i in range(len(items)): img = False if len(items[i])>2: img = items[i][2] # may still be false dx=18 if items[i][0].startswith('__') and items[i][1]==None: # separator h = 5 self.submenus.append(self.background.create_image(x, y, image=self.skin.submenusep, tag="submenu", anchor=tk.NW)) self.submenus.append(self.background.create_text(x+dx, y+7, tag="submenu", text='', fill=self.skin.color['text'], anchor=tk.W, font=self.font)) else: h = 15 self.submenus.append(self.background.create_image(x, y, image=self.skin.submenuitem, tag="submenu", anchor=tk.NW)) if img: checkboximg = self.background.create_image(x+8, y+1, image=img, tag="submenu", anchor=tk.NW) self.background.tag_bind(checkboximg, '', items[i][1]) self.submenus.append(self.background.create_text(x+dx, y+7, tag="submenu", text=items[i][0], fill=self.skin.color['text'], anchor=tk.W, font=self.font)) self.background.tag_bind(self.submenus[2*i], '', items[i][1]) self.background.tag_bind(self.submenus[2*i+1], '', items[i][1]) y+= h self.background.create_image(x, y, image=self.skin.submenubottom, tag="submenu", anchor=tk.NW) # actually save the game def SaveGame(self, filename = None): if not self.battle.save(filename): showwarning(texts.warningSaveFailedTitle, \ texts.warningSaveFailedMessage) return self.battle.saved_index = self.battle_index self.saved = True self.new_mru(filename or self.battle.get_filename()) mutable_offset = [self.list_offset] movetxt = "\n".join(self.battle.txt_battle_moves(mutable_offset, self.listlines)) self.list_offset = mutable_offset[0] self.WriteMoves(movetxt) self.background.itemconfigure(self.text_gamename, text=self.battle.name) self.wm_title("ThudBoard [%s]" % self.battle.name) def UpdateScroll(self, direction): self.list_offset+= direction if self.list_offset<0: self.list_offset=0 if self.list_offset>=len(self.battle.moves): self.list_offset=len(self.battle.moves)-1 mutable_offset = [self.list_offset] movetxt = "\n".join(self.battle.txt_battle_moves(mutable_offset, self.listlines)) self.list_offset = mutable_offset[0] self.WriteMoves(movetxt) # now, move highlighted line... dy, h = self.battle.get_offset_and_height(self.battle_index, self.list_offset) self.MoveHighlight(dy*self.size_list.dy, h*self.size_list.dy) def save_options(self, filename): config = ConfigParser.ConfigParser() config.read(filename) if not config.has_section('options'): config.add_section('options') if not config.has_section('mru'): config.add_section('mru') try: config.set('options', 'boardmap', self.toggle_boardmap) config.set('options', 'highlight', self.toggle_highlight) config.set('options', 'score', self.toggle_score) config.set('options', 'comment', self.toggle_comment) for index, value in zip(range(self.mru_count), self.mru[0:self.mru_count]): config.set('mru', 'mru%d' % index, value) fp = open(filename, 'w+b') config.write(fp) fp.close() except: showwarning(texts.warningSaveOptionsFailedTitle, \ texts.texts.warningSaveOptionsFailed % filename) def load_options(self, filename): config = ConfigParser.ConfigParser() config.read(filename) try: self.toggle_boardmap = config.getboolean('options', 'boardmap') self.toggle_highlight = config.getboolean('options', 'highlight') self.toggle_score = config.getboolean('options', 'score') self.toggle_comment = config.getboolean('options', 'comment') # config.items is new in python 2.3, so this will raise # an exception in 2.2, which is why mru doesn't work there. mruitems = [item for item in config.items('mru')] mruitems.sort() # uses item[0] ('mru%d') for sorting self.mru = [item[1] for item in mruitems] except: pass # no warning, defaults will be used self.mru = self.mru[0:self.mru_count] while (len(self.mru)=0: self.commentsbox.delete("1.0", tk.END) self.commentsbox.insert(tk.END, self.battle.moves[self.battle_index].comment) #self.commentsbox.edit_modified(False) elif self.battle_index==-1: self.commentsbox.delete("1.0", tk.END) self.commentsbox.insert(tk.END, self.battle.comment) #self.commentsbox.edit_modified(False) def update_infoimg(self): if self.battle.has_comments(): self.background.itemconfigure("infoimg", state='normal') else: self.background.itemconfigure("infoimg", state='hidden') #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~# if __name__=='__main__': if len(sys.argv)>1: main(os.path.abspath(sys.argv[1])) else: main()