Eggdrop Tcl część 4
Z IrcWiki.
Spis treści |
Instrukcje sterujące
Najdłuższą i najtrudniejszą część kursu mamy już za sobą. Znamy wszystkie rodzaje danych, ale nadal nie potrafimy tworzyć prawdziwych skryptów. Nie powiedzieliśmy bowiem jeszcze o tym, co w każdym języku programowania musi być i jest bardzo istotne. Mowa o instrukcjach sterujących przebiegiem sterowania kodu.
Instrukcja warunkowa if
Instrukcja warunkowa pozwala wykonać jakiś fragment kodu pod jakimś warunkiem.
Składnia if:
if {<warunek>} { <kod> } elseif {<warunek>} { <kod> } elseif {<warunek>} { <kod> } else { <kod> }
Bloków elseif może być tyle, ile chcesz. Może też nie być wcale i wtedy zostanie samo if i else. Może też zostać samo if.
Instrukcja warunkowa wykona fragment kodu tylko jeśli podany warunek zostanie spełniony. W przeciwnym wypadku wykona kod zawarty w else. Jako warunek można podać dowolne wyrażenie zwracające ostatecznie wartość logiczną wg takiej samej składni, jak w poleceniu expr.
Ważne jest by pamiętać o pisaniu nawiasów klamrowych dokładnie tak jak w przykładzie powyżej. Jeśli spróbujesz przenieść je do osobnych wierszy, powstanie błąd.
Poniższy przykład sprawdza zakres podanej liczby:
proc SetLiczba {a_iLiczba} { variable m_iLiczba if {$a_iLiczba < 0} { set m_iLiczba 0 } elseif {$a_iLiczba > 100} { set m_iLiczba 100 } else { set m_iLiczba $a_iLiczba } }
Instrukcja wyboru switch
Jeśli chcesz podjąć jedną z wielu akcji w zależności od jakiejś wartości, lepszym wyborem będzie użycie instrukcji wyboru :)
Instrukcja switch wybiera jedną z dostępnych możliwości dopasowując podaną wartość do wartości zawartych w jej wnętrzu i w zależności od tego wykonuje podany kod. Jeśli podana wartość nie pasuje do żadnej sprawdzanej, wykonany zostanie kod podany jako default, jeśli taki istnieje (nie trzeba go umieszczać).
Składnia switch:
switch <wartość> { <wartość> { <kod> } <wartość> { <kod> } default { <kod> } }
Przykład z switch:
switch $iLiczba { 0 { Debug "Wartosc minimalna" } 100 { Debug "Wartosc maksymalna" } default { Debug "Inna wartosc" } }
Pętle
Pętla pozwala wykonać zawarty w niej fragment kodu wiele razy, np. określoną liczbę razy, dla każdego elementu listy lub najczęściej tyle razy, ile potrzeba by podany warunek sprawdzany przed lub po każdej iteracji pętli zwrócił określoną wartość logiczną. Istnieje kilka rodzajów pętli:
Pętla while
Składnia pętli while:
while {<warunek>} { <kod> }
W każdej iteracji najpierw sprawdzany jest warunek, a następnie wykonywany kod, o ile warunek jest prawdziwy (zwraca 1). Ponieważ warunek sprawdzany jest na początku, istnieje możliwość że będzie on fałszywy już za pierwszym razem i kod zawarty w pętli nie wykona się ani razu.
Przykład pętli while:
set i 0 while {$i < 10} { Debug $i incr i }
Powyższy kod wypisuje kolejne liczby od 0 do 9.
Pętla for
Składnia pętli for:
for {<instrukcja-inicjalizacji>} {<warunek>} {<instrukcja-iteracji>} { <kod> }
<instrukcja-inicjalizacji> - To instrukcja, która wykonuje się raz, podczas uruchamiania pętli. Działa tak samo, jak gdybyś postawił ją tuż przed pętlą jako osobną instrukcję.
<warunek> - To warunek sprawdzany przed każdą iteracją pętli. Kod wykonywany jest tylko, jeśli jest on spełniony.
<instrukcja-iteracji> - To instrukcja, która wykonuje się po każdej iteracji pętli.
Najczęściej pętlę tą wykorzystuje się w podany niżej sposób, z użyciem tzw. zmiennej sterującej. Jej wartość jest inicjalizowana w instrukcji inicjalizującej, sprawdzana w warunku, zmieniana (np. inkrementowana) w instrukcji iteracji i wykorzystywana w kodzie.
Przykład pętli for
for {set i 0} {$i < 10} {incr i} { Debug $i }
Nietrudno się domyślić, że kod ten robi dokładnie to samo, co przykład z poprzedniej pętli.
Pętla foreach
Składnia pętli foreach:
foreach <zmienna> <lista> { <kod> }
Pętla ta pozwala iterować po kolejnych elementach listy. Dla każdego elementu podanej listy wykonywany jest podany kod, a wartość tego elementu na czas jego wykonania przypisywania jest podanej zmiennej.
Przykład pętli foreach:
set lLista1 { { "Programowanie" "ciekawe" } { "TCL" "prosty" } } foreach lElement $lLista1 { Debug "[lindex $lElement 0] jest [lindex $lElement 1]" }
Pętla foreach doskonale nadaje się także, oprócz list, do iteracji po elementach tablicy. Wystarczy otrzymać listę kluczy tablicy poleceniem array names.
Polecenia sterujące pętlami
break
To polecenie przerywa wykonywanie pętli i powoduje natychmiastowe wyjście z niej.
continue
To polecenie powoduje natychmiastowe rozpoczęcie nowej iteracji pętli.
Instrukcja obsługi błędów catch
Jak sama nazwa wskazuje, instrukcja ta służy do łapania. Zapewne podczas programowania w TCL już nieraz spotkałeś się z błędami. Niezłapany błąd powoduje przerwanie wykonywania kodu lub, jeśli to był kod wykonywany podczas ładowania bota, całkowity jego wysyp. Teraz nauczymy się, jak błędy można łapać.
Składnia catch:
catch { <kod> }
Podany kod jest wykonywany, ale wystąpienie błędu nie powoduje przerwania dalszego wykonywania skryptu. Instrukcja ta zwraca 0 jeśli podczas wykonywania kodu nie wystąpił błąd lub kod błędu, jeśli błąd wystąpił.
Wyłapywane są wyłącznie błędy podczas wykonywania. Instrukcja ta nic nie pomoże na błędy składniowe języka TCL uniemożliwiające kompilację jego kodu.
PROJEKT - reklama
Ta część była krótka, więc za to projekt będzie długi :) Przedstawię skrypt do okresowego wyświetlania reklam na wybranym kanale. Analizę tego kodu pozostawiam tobie jako zadanie.
########################################################################### # Autor : Regedit # Data : 2003-08-15 # Opis : Skrypt do wyświetlania przerwy na reklamę ########################################################################### ########################################################################### # CEL # Skrypt ma co jakiś czas wyświetlać na jednym, ustalonym kanale wybraną # wiadomość reklamową. Wyświeli ją tylko jeśli nikt nic na kanale nie mówi # przez conajmniej 10 minut, jednak nie częściej niż raz na godzinę. ########################################################################### # Baza danych # Enkapsuluje funkcjonalność bazy reklam namespace eval db { # --- STAŁE --- # Ścieżka do pliku z bazą set sFileName "database/reklama.db" # --- ZMIENNE --- # Czy baza została zmodyfikowana od ostatniego otwarcia set bModified 0 # Tablica z zawartością bazy # aData # --- PROCEDURY --- # Otwiera bazę proc Begin { } { variable sFileName variable bModified variable aData # Jeśli plik istnieje if { [file exists $sFileName] } { # Otwarcie pliku set hFile [open $sFileName r] # Odczytanie danych z pliku do tablicy while { ![eof $hFile] } { set sKey [gets $hFile] set sValue [gets $hFile] if { ($sKey != "") && ($sValue != "") } { set aData($sKey) $sValue } } # Zamknięcie pliku close $hFile } # Tyle co wczytałem - nie zmodyfikowany set bModified 0 } # Zamyka bazę i jeśli trzeba, zapisuje zmiany proc End { } { variable sFileName variable bModified variable aData # Jeśli dane zostały zmodyfikowane if {$db::bModified} { # Otwarcie pliku set hFile [open $sFileName w] # Zapisanie danych do pliku if { [ array exists aData] } { foreach {sKey sValue} [array get aData] { puts $hFile $sKey puts $hFile $sValue } } # Zamknięcie pliku close $hFile } # Wyczyszczenie tablicy if { [array exists aData] } { array unset aData } } # Zapisuje rekord proc SetRec { a_sKey a_sValue } { variable bModified variable aData set aData($a_sKey) $a_sValue set bModified 1 } # Usuwa rekord proc DelRec { a_sKey } { variable bModified variable aData unset aData($a_sKey) set bModified 1 } # Odczytuje rekord proc GetRec { a_sKey } { variable aData return $aData($a_sKey) } # Zwraca listę kluczy proc GetKeys { } { variable aData return [array names aData] } # Zwraca liczbę rekordów proc GetCount { } { variable aData return [array size aData] } } ########################################################################### # Bindy # Binduje dowolny tekst wypowiedziany na kanale bind pubm - * OnPubm # Przed rehash - by usunąć timer bind evnt - prerehash OnPreRehash # --- STEROWANIE --- # Polecenie główne bind msg n "reklama" OnMain # Polecenie zapisania (dodanie lub modyfikacja) rekordu bind msg n "reklama_set" OnSet # Polecenie odczytania rekordu bind msg n "reklama_get" OnGet # Polecenie usunięcia rekordu bind msg n "reklama_del" OnDel # Polecenie wylistowania kluczy rekordów bind msg n "reklama_list" OnList ########################################################################### # Stałe # Nazwa kanału, na którym ma pracować skrypt wyświetlając reklamy set Channel "#lamerlandia" # Czas krótszy (oczekiwanie na ciszę na kanale) set Time1 10 # Czas dłuższy (przerwa między wyświetleniami reklamy) set Time2 60 ########################################################################### # Zmienne # 1 lub 0 zależnie, czy ktoś coś powiedział na kanale od ostatniego timera set Said 0 # ID timera set TimerID "" ########################################################################### # Procedury # Wysyła priv do autora w celu debugowania proc Debug {aText} { putserv "PRIVMSG Nick :(DEBUG) $aText" } # Wysyła priv w odpowiedzi proc Response { a_sNick a_sText } { puthelp "PRIVMSG $a_sNick :$a_sText" } # Wysyła linijkę reklamy proc Reklamuj { a_sText } { global Channel putserv "PRIVMSG $Channel :$a_sText" } # Wyświetla reklamę proc Go { } { global Channel db::Begin Reklamuj "#################### R E K L A M A ####################" foreach {sRow} [split [db::GetRec [lindex [db::GetKeys] [rand [db::GetCount]]]] "|"] { Reklamuj $sRow } Reklamuj "#######################################################" db::End } # Ustawia timer na podany w parametrze czas proc SetTimer {aCzas} { global TimerID set TimerID [timer $aCzas "OnTimer"] } # Usuwa timer proc KillTimer { } { global TimerID if {$TimerID != ""} { killtimer $TimerID } } # Procedura obsługi zdarzenia timera proc OnTimer { } { global Said Time1 Time2 # jeśli ktoś coś powiedział if {$Said == 1} { SetTimer $Time1 # jeśli nikt nic nie powiedział } else { Go SetTimer $Time2 } set Said 0 } # Procedura reakcji na zdarzenie powiedzenia czegokolwiek na kanale proc OnPubm {aNick aHost aHandle aChannel aText} { global Channel Said if {$aChannel != $Channel} { return 0 } set Said 1 return 0 } # reakcja na zdarzenie przed rehashem proc OnPreRehash {type} { KillTimer } proc OnMain {a_sNick a_sHost a_sHandle a_sText} { Response $a_sNick "*REKLAMA* Dostepne polecenia: reklama | reklama_set <key> <value>\ | reklama_get <key> | reklama_del <key> | reklama_list" } proc OnSet {a_sNick a_sHost a_sHandle a_sText} { db::Begin set lList [split $a_sText] set sKey [lindex $lList 0] set sValue [join [lrange $lList 1 end]] db::SetRec $sKey $sValue db::End Response $a_sNick "Reklama \"$sKey\" : \"$sValue\" dodana" } proc OnGet {a_sNick a_sHost a_sHandle a_sText} { db::Begin Response $a_sNick [db::GetRec $a_sText] db::End } proc OnDel {a_sNick a_sHost a_sHandle a_sText} { db::Begin db::DelRec $a_sText db::End Response $a_sNick "Reklama \"$a_sText\" usunieta" } proc OnList {a_sNick a_sHost a_sHandle a_sText} { db::Begin set bFirst 1 set sResult "" foreach {sKey} [lsort -dictionary [db::GetKeys]] { if {$bFirst} { append sResult "$sKey" set bFirst 0 } else { append sResult " $sKey" } } Response $a_sNick $sResult db::End } ########################################################################### # Kod # Zainicjalizowanie timera SetTimer $Time2 # Zalogowanie załadowania skryptu putlog "reklama... loaded"
Ten tutorial został wprowadzony tutaj za zgodą oryginalnego autora:
Adam Sawicki "Regedit"
http://www.programex.prv.pl
Przepisania do formatu wiki dokonał kemyd
