--- /dev/null
+credentials*
+token*
+__pycache__
+build
--- /dev/null
+// by Ray Burnette 20161013 compiled on Linux 16.3 using Arduino 1.6.12\r
+//Hacked by Kosme 20170520 compiled on Ubuntu 14.04 using Arduino 1.6.11\r
+\r
+#include <ESP8266WiFi.h>\r
+#include "./functions.h"\r
+\r
+#define disable 0\r
+#define enable 1\r
+unsigned int channel = 1;\r
+\r
+void setup() {\r
+ Serial.begin(57600);\r
+ Serial.printf("\n\nSDK version:%s\n\r", system_get_sdk_version());\r
+ Serial.println(F("ESP8266 enhanced sniffer by Kosme https://github.com/kosme"));\r
+\r
+ wifi_set_opmode(STATION_MODE); // Promiscuous works only with station mode\r
+ wifi_set_channel(channel);\r
+ wifi_promiscuous_enable(disable);\r
+ wifi_set_promiscuous_rx_cb(promisc_cb); // Set up promiscuous callback\r
+ wifi_promiscuous_enable(enable);\r
+}\r
+\r
+void loop() {\r
+ channel = 1;\r
+ wifi_set_channel(channel);\r
+ while (true) {\r
+ nothing_new++; // Array is not finite, check bounds and adjust if required\r
+ if (nothing_new > 100) {\r
+ nothing_new = 0;\r
+ channel++;\r
+ if (channel == 15) break; // Only scan channels 1 to 14\r
+ wifi_set_channel(channel);\r
+ }\r
+ delay(1); // critical processing timeslice for NONOS SDK! No delay(0) yield()\r
+ }\r
+}\r
--- /dev/null
+// Notes.h tab in Arduino IDE is only for comments and references!\r
+\r
+// based on RandDruid/esp8266-deauth (MIT) https://github.com/RandDruid/esp8266-deauth\r
+// inspired by kripthor/WiFiBeaconJam (no license) https://github.com/kripthor/WiFiBeaconJam\r
+// https://git.schneefux.xyz/schneefux/jimmiejammer/src/master/jimmiejammer.ino\r
+// requires SDK v1.3: install esp8266/Arduino from git and checkout commit 1c5751460b7988041fdc80e0f28a31464cdf97a3\r
+// Modified by M. Ray Burnette for publication as WiFi Sniffer 20161013\r
+// Modified by Kosme for publication\r
+/*\r
+ Arduino 1.6.12 on Linux Mint 17.3\r
+ Sketch uses 227,309 bytes (21%) of program storage space. Maximum is 1,044,464 bytes.\r
+ Global variables use 45,196 bytes (55%) of dynamic memory, leaving 36,724 bytes for local variables. Maximum is 81,920 bytes.\r
+\r
+*/\r
+\r
+/*\r
+ // beacon template\r
+ uint8_t template_beacon[128] = { 0x80, 0x00, 0x00, 0x00,\r
+ /*4*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\r
+ /*10*/ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,\r
+ /*16*/ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,\r
+ /*22*/ 0xc0, 0x6c,\r
+ /*24*/ 0x83, 0x51, 0xf7, 0x8f, 0x0f, 0x00, 0x00, 0x00,\r
+ /*32*/ 0x64, 0x00,\r
+ /*34*/ 0x01, 0x04,\r
+ /* SSID */\r
+ /*36*/ 0x00, 0x06, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72,\r
+ 0x01, 0x08, 0x82, 0x84,\r
+ 0x8b, 0x96, 0x24, 0x30, 0x48, 0x6c, 0x03, 0x01,\r
+ /*56*/ 0x04\r
+};\r
+* /\r
+\r
+/* Notes:\r
+ Ref: http://www.esp8266.com/viewtopic.php?f=32&t=7025\r
+ In the ESP8266WiFi.h, there is the function getNetworkInfo() which I presume allows you to get\r
+ info for hidden AP.\r
+\r
+ bool getNetworkInfo(uint8_t networkItem, String &ssid, uint8_t &encryptionType, int32_t &RSSI, uint8_t* &BSSID, int32_t &channel, bool &isHidden);\r
+ CODE: SELECT ALL\r
+ /**\r
+ loads all infos from a scanned wifi in to the ptr parameters\r
+ @param networkItem uint8_t\r
+ @param ssid const char*\r
+ @param encryptionType uint8_t\r
+ @param RSSI int32_t\r
+ @param BSSID uint8_t *\r
+ @param channel int32_t\r
+ @param isHidden bool\r
+ @return (true if ok)\r
+*/\r
+\r
+/* Serial Console Sample Output:\r
+ ESP8266 mini-sniff by Ray Burnette http://www.hackster.io/rayburne/projects\r
+ Type: /-------MAC------/-----WiFi Access Point SSID-----/ /----MAC---/ Chnl RSSI\r
+ BEACON: <=============== [ TardisTime] 1afe34a08bc9 8 -76\r
+ BEACON: <=============== [ xfinitywifi] 56571a0730c0 11 -90\r
+ BEACON: <=============== [ ] 52571a0730c0 11 -91\r
+ BEACON: <=============== [ ATTGH6Gs22] 1005b1d6ff90 11 -95\r
+ BEACON: <=============== [ ATT4P3G9f8] 1c1448777420 11 -92\r
+ BEACON: <=============== [ HOME-30C2] 5c571a0730c0 11 -91\r
+ BEACON: <=============== [ ATT8Q4z656] b077acc4dfd0 11 -92\r
+ BEACON: <=============== [ HOME-B1C2] 94877c55b1c0 11 -94\r
+ BEACON: <=============== [ HUXU2012] 0c54a5d6e480 6 -94\r
+ BEACON: <=============== [ xfinitywifi] 0c54a5d6e482 6 -97\r
+ BEACON: <=============== [ ] 0c54a5d6e481 6 -96\r
+ DEVICE: 18fe34fdc2b8 ==> [ TardisTime] 1afe34a08bc9 8 -79\r
+ DEVICE: 18fe34f977a0 ==> [ TardisTime] 1afe34a08bc9 8 -94\r
+ DEVICE: 6002b4484f2d ==> [ ATTGH6Gs22] 0180c2000000 11 -98\r
+ BEACON: <=============== [ HOME-01FC-2.4] 84002da251d8 6 -100\r
+ DEVICE: 503955d34834 ==> [ ATT8Q4z656] 01005e7ffffa 11 -87\r
+ BEACON: <=============== [ ] 84002da251d9 6 -98\r
+ BEACON: <=============== [ xfinitywifi] 84002da251da 6 -95\r
+ BEACON: <=============== [ ] fa8fca34e26c 11 -94\r
+ DEVICE: cc0dec048363 ==> [ ATT8Q4z656] 01005e7ffffa 11 -88\r
+ BEACON: <=============== [ ] fa8fca95bad3 11 -92\r
+ BEACON: <=============== [ HOME-5475] 58238c3b5475 1 -96\r
+ BEACON: <=============== [ xfinitywifi] 5a238c3b5477 1 -94\r
+ BEACON: <=============== [ ] 5a238c3b5476 1 -96\r
+ DEVICE: 1859330bf08e ==> [ ATT8Q4z656] 01005e7ffffa 11 -92\r
+ BEACON: <=============== [ ] 92877c55b1c0 11 -92\r
+ DEVICE: f45fd47bd5e0 ==> [ ATTGH6Gs22] ffffffffffff 11 -93\r
+ BEACON: <=============== [ Lynch] 744401480a27 11 -96\r
+ BEACON: <=============== [ xfinitywifi] 96877c55b1c0 11 -93\r
+ DEVICE: f43e9d006c10 ==> [ xfinitywifi] 8485066ff726 6 -96\r
+ DEVICE: 285aeb4f16bf ==> [ ATTGH6Gs22] 3333ffb3c678 11 -94\r
+ DEVICE: 006b9e7fab90 ==> [ ATTGH6Gs22] 01005e7ffffa 11 -91\r
+ DEVICE: 78456155b9f0 ==> [ Lynch] 01005e7ffffa 11 -95\r
+ DEVICE: 6cadf84a419d ==> [ HOME-30C2] 88cb8787697a 11 -89\r
+ BEACON: <=============== [ Verizon-SM-G935V-6526] a608ea306526 11 -92\r
+\r
+\r
+*/\r
--- /dev/null
+// This-->tab == "functions.h"\r
+\r
+// Expose Espressif SDK functionality\r
+extern "C" {\r
+#include "user_interface.h"\r
+ typedef void (*freedom_outside_cb_t)(uint8 status);\r
+ int wifi_register_send_pkt_freedom_cb(freedom_outside_cb_t cb);\r
+ void wifi_unregister_send_pkt_freedom_cb(void);\r
+ int wifi_send_pkt_freedom(uint8 *buf, int len, bool sys_seq);\r
+}\r
+\r
+#include <ESP8266WiFi.h>\r
+#include "./structures.h"\r
+\r
+#define MAX_APS_TRACKED 100\r
+#define MAX_CLIENTS_TRACKED 200\r
+\r
+int aps_known_count = 0; // Number of known APs\r
+int nothing_new = 0;\r
+int clients_known_count = 0; // Number of known CLIENTs\r
+\r
+void promisc_cb(uint8_t *buf, uint16_t len)\r
+{\r
+ signed potencia;\r
+ if (len == 12) {\r
+ struct RxControl *sniffer = (struct RxControl*) buf;\r
+ potencia = sniffer->rssi;\r
+ } else if (len == 128) {\r
+ struct sniffer_buf2 *sniffer = (struct sniffer_buf2*) buf;\r
+ struct beaconinfo beacon = parse_beacon(sniffer->buf, 112, sniffer->rx_ctrl.rssi);\r
+ potencia = sniffer->rx_ctrl.rssi;\r
+ } else {\r
+ struct sniffer_buf *sniffer = (struct sniffer_buf*) buf;\r
+ potencia = sniffer->rx_ctrl.rssi;\r
+ }\r
+\r
+ // Position 12 in the array is where the packet type number is located\r
+ // For info on the different packet type numbers check:\r
+ // https://stackoverflow.com/questions/12407145/interpreting-frame-control-bytes-in-802-11-wireshark-trace\r
+ // https://supportforums.cisco.com/document/52391/80211-frames-starter-guide-learn-wireless-sniffer-traces\r
+ // https://ilovewifi.blogspot.mx/2012/07/80211-frame-types.html\r
+ if((buf[12]==0x88)||(buf[12]==0x40)||(buf[12]==0x94)||(buf[12]==0xa4)||(buf[12]==0xb4)||(buf[12]==0x08))\r
+ {\r
+ //Nils Serial.printf("%02x\n",buf[12]);\r
+ // if(buf[12]==0x40) Serial.printf("Disconnected: ");\r
+ // if(buf[12]==0x08) Serial.printf("Data: ");\r
+ // if(buf[12]==0x88) Serial.printf("QOS: ");\r
+ // Origin MAC address starts at byte 22\r
+ // Print MAC address\r
+ for(int i=0;i<5;i++) {\r
+ Serial.printf("%02x:",buf[22+i]);\r
+ }\r
+ Serial.printf("%02x\n",buf[22+5]);\r
+ // Signal strength is in byte 0\r
+ //Nils Serial.printf("%i\n",int8_t(buf[0]));\r
+\r
+ // Enable this lines if you want to scan for a specific MAC address\r
+ // Specify desired MAC address on line 10 of structures.h\r
+ /*int same = 1;\r
+ for(int i=0;i<6;i++)\r
+ {\r
+ if(buf[22+i]!=desired[i])\r
+ {\r
+ same=0;\r
+ break;\r
+ }\r
+ }\r
+ if(same)\r
+ {\r
+\r
+ }\r
+ //different device\r
+ else\r
+ {\r
+\r
+ }*/\r
+ }\r
+ //Different packet type numbers\r
+ else\r
+ {\r
+\r
+ }\r
+}\r
--- /dev/null
+// This-->tab == "structures.h"\r
+\r
+#define ETH_MAC_LEN 6\r
+\r
+uint8_t broadcast1[3] = { 0x01, 0x00, 0x5e };\r
+uint8_t broadcast2[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };\r
+uint8_t broadcast3[3] = { 0x33, 0x33, 0x00 };\r
+\r
+//If you want to detect a specific MAC Addess, put it here.\r
+uint8_t desired[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\r
+\r
+struct beaconinfo\r
+{\r
+ uint8_t bssid[ETH_MAC_LEN];\r
+ uint8_t ssid[33];\r
+ int ssid_len;\r
+ int channel;\r
+ int err;\r
+ signed rssi;\r
+ uint8_t capa[2];\r
+};\r
+\r
+struct clientinfo\r
+{\r
+ uint8_t bssid[ETH_MAC_LEN];\r
+ uint8_t station[ETH_MAC_LEN];\r
+ uint8_t ap[ETH_MAC_LEN];\r
+ int channel;\r
+ int err;\r
+ signed rssi;\r
+ uint16_t seq_n;\r
+};\r
+\r
+/* ==============================================\r
+ Promiscous callback structures, see ESP manual\r
+ ============================================== */\r
+struct RxControl {\r
+ signed rssi: 8;\r
+ unsigned rate: 4;\r
+ unsigned is_group: 1;\r
+ unsigned: 1;\r
+ unsigned sig_mode: 2;\r
+ unsigned legacy_length: 12;\r
+ unsigned damatch0: 1;\r
+ unsigned damatch1: 1;\r
+ unsigned bssidmatch0: 1;\r
+ unsigned bssidmatch1: 1;\r
+ unsigned MCS: 7;\r
+ unsigned CWB: 1;\r
+ unsigned HT_length: 16;\r
+ unsigned Smoothing: 1;\r
+ unsigned Not_Sounding: 1;\r
+ unsigned: 1;\r
+ unsigned Aggregation: 1;\r
+ unsigned STBC: 2;\r
+ unsigned FEC_CODING: 1;\r
+ unsigned SGI: 1;\r
+ unsigned rxend_state: 8;\r
+ unsigned ampdu_cnt: 8;\r
+ unsigned channel: 4;\r
+ unsigned: 12;\r
+};\r
+\r
+struct LenSeq {\r
+ uint16_t length;\r
+ uint16_t seq;\r
+ uint8_t address3[6];\r
+};\r
+\r
+struct sniffer_buf {\r
+ struct RxControl rx_ctrl;\r
+ uint8_t buf[36];\r
+ uint16_t cnt;\r
+ struct LenSeq lenseq[1];\r
+};\r
+\r
+struct sniffer_buf2 {\r
+ struct RxControl rx_ctrl;\r
+ uint8_t buf[112];\r
+ uint16_t cnt;\r
+ uint16_t len;\r
+};\r
+\r
+struct clientinfo parse_data(uint8_t *frame, uint16_t framelen, signed rssi, unsigned channel)\r
+{\r
+ struct clientinfo ci;\r
+ ci.channel = channel;\r
+ ci.err = 0;\r
+ ci.rssi = rssi;\r
+ int pos = 36;\r
+ uint8_t *bssid;\r
+ uint8_t *station;\r
+ uint8_t *ap;\r
+ uint8_t ds;\r
+\r
+ ds = frame[1] & 3; //Set first 6 bits to 0\r
+ switch (ds) {\r
+ // p[1] - xxxx xx00 => NoDS p[4]-DST p[10]-SRC p[16]-BSS\r
+ case 0:\r
+ bssid = frame + 16;\r
+ station = frame + 10;\r
+ ap = frame + 4;\r
+ break;\r
+ // p[1] - xxxx xx01 => ToDS p[4]-BSS p[10]-SRC p[16]-DST\r
+ case 1:\r
+ bssid = frame + 4;\r
+ station = frame + 10;\r
+ ap = frame + 16;\r
+ break;\r
+ // p[1] - xxxx xx10 => FromDS p[4]-DST p[10]-BSS p[16]-SRC\r
+ case 2:\r
+ bssid = frame + 10;\r
+ // hack - don't know why it works like this...\r
+ if (memcmp(frame + 4, broadcast1, 3) || memcmp(frame + 4, broadcast2, 3) || memcmp(frame + 4, broadcast3, 3)) {\r
+ station = frame + 16;\r
+ ap = frame + 4;\r
+ } else {\r
+ station = frame + 4;\r
+ ap = frame + 16;\r
+ }\r
+ break;\r
+ // p[1] - xxxx xx11 => WDS p[4]-RCV p[10]-TRM p[16]-DST p[26]-SRC\r
+ case 3:\r
+ bssid = frame + 10;\r
+ station = frame + 4;\r
+ ap = frame + 4;\r
+ break;\r
+ }\r
+\r
+ memcpy(ci.station, station, ETH_MAC_LEN);\r
+ memcpy(ci.bssid, bssid, ETH_MAC_LEN);\r
+ memcpy(ci.ap, ap, ETH_MAC_LEN);\r
+\r
+ ci.seq_n = frame[23] * 0xFF + (frame[22] & 0xF0);\r
+ return ci;\r
+}\r
+\r
+struct beaconinfo parse_beacon(uint8_t *frame, uint16_t framelen, signed rssi)\r
+{\r
+ struct beaconinfo bi;\r
+ bi.ssid_len = 0;\r
+ bi.channel = 0;\r
+ bi.err = 0;\r
+ bi.rssi = rssi;\r
+ int pos = 36;\r
+\r
+ if (frame[pos] == 0x00) {\r
+ while (pos < framelen) {\r
+ switch (frame[pos]) {\r
+ case 0x00: //SSID\r
+ bi.ssid_len = (int) frame[pos + 1];\r
+ if (bi.ssid_len == 0) {\r
+ memset(bi.ssid, '\x00', 33);\r
+ break;\r
+ }\r
+ if (bi.ssid_len < 0) {\r
+ bi.err = -1;\r
+ break;\r
+ }\r
+ if (bi.ssid_len > 32) {\r
+ bi.err = -2;\r
+ break;\r
+ }\r
+ memset(bi.ssid, '\x00', 33);\r
+ memcpy(bi.ssid, frame + pos + 2, bi.ssid_len);\r
+ bi.err = 0; // before was error??\r
+ break;\r
+ case 0x03: //Channel\r
+ bi.channel = (int) frame[pos + 2];\r
+ pos = -1;\r
+ break;\r
+ default:\r
+ break;\r
+ }\r
+ if (pos < 0) break;\r
+ pos += (int) frame[pos + 1] + 2;\r
+ }\r
+ } else {\r
+ bi.err = -3;\r
+ }\r
+\r
+ bi.capa[0] = frame[34];\r
+ bi.capa[1] = frame[35];\r
+ memcpy(bi.bssid, frame + 10, ETH_MAC_LEN);\r
+ return bi;\r
+}\r
--- /dev/null
+// by Ray Burnette 20161013 compiled on Linux 16.3 using Arduino 1.6.12\r
+//Hacked by Kosme 20170520 compiled on Ubuntu 14.04 using Arduino 1.6.11\r
+\r
+#include <ESP8266WiFi.h>\r
+#include "./functions.h"\r
+\r
+#define disable 0\r
+#define enable 1\r
+unsigned int channel = 1;\r
+\r
+void setup() {\r
+ Serial.begin(57600);\r
+ Serial.printf("\n\nSDK version:%s\n\r", system_get_sdk_version());\r
+ Serial.println(F("ESP8266 enhanced sniffer by Kosme https://github.com/kosme"));\r
+\r
+ wifi_set_opmode(STATION_MODE); // Promiscuous works only with station mode\r
+ wifi_set_channel(channel);\r
+ wifi_promiscuous_enable(disable);\r
+ wifi_set_promiscuous_rx_cb(promisc_cb); // Set up promiscuous callback\r
+ wifi_promiscuous_enable(enable);\r
+}\r
+\r
+void loop() {\r
+ channel = 1;\r
+ wifi_set_channel(channel);\r
+ while (true) {\r
+ nothing_new++; // Array is not finite, check bounds and adjust if required\r
+ if (nothing_new > 100) {\r
+ nothing_new = 0;\r
+ channel++;\r
+ if (channel == 15) break; // Only scan channels 1 to 14\r
+ wifi_set_channel(channel);\r
+ }\r
+ delay(1); // critical processing timeslice for NONOS SDK! No delay(0) yield()\r
+ }\r
+}\r
--- /dev/null
+// Notes.h tab in Arduino IDE is only for comments and references!\r
+\r
+// based on RandDruid/esp8266-deauth (MIT) https://github.com/RandDruid/esp8266-deauth\r
+// inspired by kripthor/WiFiBeaconJam (no license) https://github.com/kripthor/WiFiBeaconJam\r
+// https://git.schneefux.xyz/schneefux/jimmiejammer/src/master/jimmiejammer.ino\r
+// requires SDK v1.3: install esp8266/Arduino from git and checkout commit 1c5751460b7988041fdc80e0f28a31464cdf97a3\r
+// Modified by M. Ray Burnette for publication as WiFi Sniffer 20161013\r
+// Modified by Kosme for publication\r
+/*\r
+ Arduino 1.6.12 on Linux Mint 17.3\r
+ Sketch uses 227,309 bytes (21%) of program storage space. Maximum is 1,044,464 bytes.\r
+ Global variables use 45,196 bytes (55%) of dynamic memory, leaving 36,724 bytes for local variables. Maximum is 81,920 bytes.\r
+\r
+*/\r
+\r
+/*\r
+ // beacon template\r
+ uint8_t template_beacon[128] = { 0x80, 0x00, 0x00, 0x00,\r
+ /*4*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\r
+ /*10*/ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,\r
+ /*16*/ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,\r
+ /*22*/ 0xc0, 0x6c,\r
+ /*24*/ 0x83, 0x51, 0xf7, 0x8f, 0x0f, 0x00, 0x00, 0x00,\r
+ /*32*/ 0x64, 0x00,\r
+ /*34*/ 0x01, 0x04,\r
+ /* SSID */\r
+ /*36*/ 0x00, 0x06, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72,\r
+ 0x01, 0x08, 0x82, 0x84,\r
+ 0x8b, 0x96, 0x24, 0x30, 0x48, 0x6c, 0x03, 0x01,\r
+ /*56*/ 0x04\r
+};\r
+* /\r
+\r
+/* Notes:\r
+ Ref: http://www.esp8266.com/viewtopic.php?f=32&t=7025\r
+ In the ESP8266WiFi.h, there is the function getNetworkInfo() which I presume allows you to get\r
+ info for hidden AP.\r
+\r
+ bool getNetworkInfo(uint8_t networkItem, String &ssid, uint8_t &encryptionType, int32_t &RSSI, uint8_t* &BSSID, int32_t &channel, bool &isHidden);\r
+ CODE: SELECT ALL\r
+ /**\r
+ loads all infos from a scanned wifi in to the ptr parameters\r
+ @param networkItem uint8_t\r
+ @param ssid const char*\r
+ @param encryptionType uint8_t\r
+ @param RSSI int32_t\r
+ @param BSSID uint8_t *\r
+ @param channel int32_t\r
+ @param isHidden bool\r
+ @return (true if ok)\r
+*/\r
+\r
+/* Serial Console Sample Output:\r
+ ESP8266 mini-sniff by Ray Burnette http://www.hackster.io/rayburne/projects\r
+ Type: /-------MAC------/-----WiFi Access Point SSID-----/ /----MAC---/ Chnl RSSI\r
+ BEACON: <=============== [ TardisTime] 1afe34a08bc9 8 -76\r
+ BEACON: <=============== [ xfinitywifi] 56571a0730c0 11 -90\r
+ BEACON: <=============== [ ] 52571a0730c0 11 -91\r
+ BEACON: <=============== [ ATTGH6Gs22] 1005b1d6ff90 11 -95\r
+ BEACON: <=============== [ ATT4P3G9f8] 1c1448777420 11 -92\r
+ BEACON: <=============== [ HOME-30C2] 5c571a0730c0 11 -91\r
+ BEACON: <=============== [ ATT8Q4z656] b077acc4dfd0 11 -92\r
+ BEACON: <=============== [ HOME-B1C2] 94877c55b1c0 11 -94\r
+ BEACON: <=============== [ HUXU2012] 0c54a5d6e480 6 -94\r
+ BEACON: <=============== [ xfinitywifi] 0c54a5d6e482 6 -97\r
+ BEACON: <=============== [ ] 0c54a5d6e481 6 -96\r
+ DEVICE: 18fe34fdc2b8 ==> [ TardisTime] 1afe34a08bc9 8 -79\r
+ DEVICE: 18fe34f977a0 ==> [ TardisTime] 1afe34a08bc9 8 -94\r
+ DEVICE: 6002b4484f2d ==> [ ATTGH6Gs22] 0180c2000000 11 -98\r
+ BEACON: <=============== [ HOME-01FC-2.4] 84002da251d8 6 -100\r
+ DEVICE: 503955d34834 ==> [ ATT8Q4z656] 01005e7ffffa 11 -87\r
+ BEACON: <=============== [ ] 84002da251d9 6 -98\r
+ BEACON: <=============== [ xfinitywifi] 84002da251da 6 -95\r
+ BEACON: <=============== [ ] fa8fca34e26c 11 -94\r
+ DEVICE: cc0dec048363 ==> [ ATT8Q4z656] 01005e7ffffa 11 -88\r
+ BEACON: <=============== [ ] fa8fca95bad3 11 -92\r
+ BEACON: <=============== [ HOME-5475] 58238c3b5475 1 -96\r
+ BEACON: <=============== [ xfinitywifi] 5a238c3b5477 1 -94\r
+ BEACON: <=============== [ ] 5a238c3b5476 1 -96\r
+ DEVICE: 1859330bf08e ==> [ ATT8Q4z656] 01005e7ffffa 11 -92\r
+ BEACON: <=============== [ ] 92877c55b1c0 11 -92\r
+ DEVICE: f45fd47bd5e0 ==> [ ATTGH6Gs22] ffffffffffff 11 -93\r
+ BEACON: <=============== [ Lynch] 744401480a27 11 -96\r
+ BEACON: <=============== [ xfinitywifi] 96877c55b1c0 11 -93\r
+ DEVICE: f43e9d006c10 ==> [ xfinitywifi] 8485066ff726 6 -96\r
+ DEVICE: 285aeb4f16bf ==> [ ATTGH6Gs22] 3333ffb3c678 11 -94\r
+ DEVICE: 006b9e7fab90 ==> [ ATTGH6Gs22] 01005e7ffffa 11 -91\r
+ DEVICE: 78456155b9f0 ==> [ Lynch] 01005e7ffffa 11 -95\r
+ DEVICE: 6cadf84a419d ==> [ HOME-30C2] 88cb8787697a 11 -89\r
+ BEACON: <=============== [ Verizon-SM-G935V-6526] a608ea306526 11 -92\r
+\r
+\r
+*/\r
--- /dev/null
+\r
+\r
+\r
+\r
+// DENNA FIL ÄR MODIFIERAD AV NILS FORSSEN\r
+\r
+\r
+\r
+\r
+\r
+// This-->tab == "functions.h"\r
+\r
+// Expose Espressif SDK functionality\r
+extern "C" {\r
+#include "user_interface.h"\r
+ typedef void (*freedom_outside_cb_t)(uint8 status);\r
+ int wifi_register_send_pkt_freedom_cb(freedom_outside_cb_t cb);\r
+ void wifi_unregister_send_pkt_freedom_cb(void);\r
+ int wifi_send_pkt_freedom(uint8 *buf, int len, bool sys_seq);\r
+}\r
+\r
+#include <ESP8266WiFi.h>\r
+#include "./structures.h"\r
+\r
+#define MAX_APS_TRACKED 100\r
+#define MAX_CLIENTS_TRACKED 200\r
+\r
+int aps_known_count = 0; // Number of known APs\r
+int nothing_new = 0;\r
+int clients_known_count = 0; // Number of known CLIENTs\r
+\r
+void promisc_cb(uint8_t *buf, uint16_t len)\r
+{\r
+ signed potencia;\r
+ if (len == 12) {\r
+ struct RxControl *sniffer = (struct RxControl*) buf;\r
+ potencia = sniffer->rssi;\r
+ } else if (len == 128) {\r
+ struct sniffer_buf2 *sniffer = (struct sniffer_buf2*) buf;\r
+ struct beaconinfo beacon = parse_beacon(sniffer->buf, 112, sniffer->rx_ctrl.rssi);\r
+ potencia = sniffer->rx_ctrl.rssi;\r
+ } else {\r
+ struct sniffer_buf *sniffer = (struct sniffer_buf*) buf;\r
+ potencia = sniffer->rx_ctrl.rssi;\r
+ }\r
+\r
+ // Position 12 in the array is where the packet type number is located\r
+ // For info on the different packet type numbers check:\r
+ // https://stackoverflow.com/questions/12407145/interpreting-frame-control-bytes-in-802-11-wireshark-trace\r
+ // https://supportforums.cisco.com/document/52391/80211-frames-starter-guide-learn-wireless-sniffer-traces\r
+ // https://ilovewifi.blogspot.mx/2012/07/80211-frame-types.html\r
+ if((buf[12]==0x88)||(buf[12]==0x40)||(buf[12]==0x94)||(buf[12]==0xa4)||(buf[12]==0xb4)||(buf[12]==0x08))\r
+ {\r
+ //Nils Serial.printf("%02x\n",buf[12]);\r
+ // if(buf[12]==0x40) Serial.printf("Disconnected: ");\r
+ // if(buf[12]==0x08) Serial.printf("Data: ");\r
+ // if(buf[12]==0x88) Serial.printf("QOS: ");\r
+ // Origin MAC address starts at byte 22\r
+ // Print MAC address\r
+ for(int i=0;i<5;i++) {\r
+ Serial.printf("%02x:",buf[22+i]);\r
+ }\r
+ Serial.printf("%02x\n",buf[22+5]);\r
+ // Signal strength is in byte 0\r
+ //Nils Serial.printf("%i\n",int8_t(buf[0]));\r
+\r
+ // Enable this lines if you want to scan for a specific MAC address\r
+ // Specify desired MAC address on line 10 of structures.h\r
+ /*int same = 1;\r
+ for(int i=0;i<6;i++)\r
+ {\r
+ if(buf[22+i]!=desired[i])\r
+ {\r
+ same=0;\r
+ break;\r
+ }\r
+ }\r
+ if(same)\r
+ {\r
+\r
+ }\r
+ //different device\r
+ else\r
+ {\r
+\r
+ }*/\r
+ }\r
+ //Different packet type numbers\r
+ else\r
+ {\r
+\r
+ }\r
+}\r
--- /dev/null
+// This-->tab == "structures.h"\r
+\r
+#define ETH_MAC_LEN 6\r
+\r
+uint8_t broadcast1[3] = { 0x01, 0x00, 0x5e };\r
+uint8_t broadcast2[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };\r
+uint8_t broadcast3[3] = { 0x33, 0x33, 0x00 };\r
+\r
+//If you want to detect a specific MAC Addess, put it here.\r
+uint8_t desired[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };\r
+\r
+struct beaconinfo\r
+{\r
+ uint8_t bssid[ETH_MAC_LEN];\r
+ uint8_t ssid[33];\r
+ int ssid_len;\r
+ int channel;\r
+ int err;\r
+ signed rssi;\r
+ uint8_t capa[2];\r
+};\r
+\r
+struct clientinfo\r
+{\r
+ uint8_t bssid[ETH_MAC_LEN];\r
+ uint8_t station[ETH_MAC_LEN];\r
+ uint8_t ap[ETH_MAC_LEN];\r
+ int channel;\r
+ int err;\r
+ signed rssi;\r
+ uint16_t seq_n;\r
+};\r
+\r
+/* ==============================================\r
+ Promiscous callback structures, see ESP manual\r
+ ============================================== */\r
+struct RxControl {\r
+ signed rssi: 8;\r
+ unsigned rate: 4;\r
+ unsigned is_group: 1;\r
+ unsigned: 1;\r
+ unsigned sig_mode: 2;\r
+ unsigned legacy_length: 12;\r
+ unsigned damatch0: 1;\r
+ unsigned damatch1: 1;\r
+ unsigned bssidmatch0: 1;\r
+ unsigned bssidmatch1: 1;\r
+ unsigned MCS: 7;\r
+ unsigned CWB: 1;\r
+ unsigned HT_length: 16;\r
+ unsigned Smoothing: 1;\r
+ unsigned Not_Sounding: 1;\r
+ unsigned: 1;\r
+ unsigned Aggregation: 1;\r
+ unsigned STBC: 2;\r
+ unsigned FEC_CODING: 1;\r
+ unsigned SGI: 1;\r
+ unsigned rxend_state: 8;\r
+ unsigned ampdu_cnt: 8;\r
+ unsigned channel: 4;\r
+ unsigned: 12;\r
+};\r
+\r
+struct LenSeq {\r
+ uint16_t length;\r
+ uint16_t seq;\r
+ uint8_t address3[6];\r
+};\r
+\r
+struct sniffer_buf {\r
+ struct RxControl rx_ctrl;\r
+ uint8_t buf[36];\r
+ uint16_t cnt;\r
+ struct LenSeq lenseq[1];\r
+};\r
+\r
+struct sniffer_buf2 {\r
+ struct RxControl rx_ctrl;\r
+ uint8_t buf[112];\r
+ uint16_t cnt;\r
+ uint16_t len;\r
+};\r
+\r
+struct clientinfo parse_data(uint8_t *frame, uint16_t framelen, signed rssi, unsigned channel)\r
+{\r
+ struct clientinfo ci;\r
+ ci.channel = channel;\r
+ ci.err = 0;\r
+ ci.rssi = rssi;\r
+ int pos = 36;\r
+ uint8_t *bssid;\r
+ uint8_t *station;\r
+ uint8_t *ap;\r
+ uint8_t ds;\r
+\r
+ ds = frame[1] & 3; //Set first 6 bits to 0\r
+ switch (ds) {\r
+ // p[1] - xxxx xx00 => NoDS p[4]-DST p[10]-SRC p[16]-BSS\r
+ case 0:\r
+ bssid = frame + 16;\r
+ station = frame + 10;\r
+ ap = frame + 4;\r
+ break;\r
+ // p[1] - xxxx xx01 => ToDS p[4]-BSS p[10]-SRC p[16]-DST\r
+ case 1:\r
+ bssid = frame + 4;\r
+ station = frame + 10;\r
+ ap = frame + 16;\r
+ break;\r
+ // p[1] - xxxx xx10 => FromDS p[4]-DST p[10]-BSS p[16]-SRC\r
+ case 2:\r
+ bssid = frame + 10;\r
+ // hack - don't know why it works like this...\r
+ if (memcmp(frame + 4, broadcast1, 3) || memcmp(frame + 4, broadcast2, 3) || memcmp(frame + 4, broadcast3, 3)) {\r
+ station = frame + 16;\r
+ ap = frame + 4;\r
+ } else {\r
+ station = frame + 4;\r
+ ap = frame + 16;\r
+ }\r
+ break;\r
+ // p[1] - xxxx xx11 => WDS p[4]-RCV p[10]-TRM p[16]-DST p[26]-SRC\r
+ case 3:\r
+ bssid = frame + 10;\r
+ station = frame + 4;\r
+ ap = frame + 4;\r
+ break;\r
+ }\r
+\r
+ memcpy(ci.station, station, ETH_MAC_LEN);\r
+ memcpy(ci.bssid, bssid, ETH_MAC_LEN);\r
+ memcpy(ci.ap, ap, ETH_MAC_LEN);\r
+\r
+ ci.seq_n = frame[23] * 0xFF + (frame[22] & 0xF0);\r
+ return ci;\r
+}\r
+\r
+struct beaconinfo parse_beacon(uint8_t *frame, uint16_t framelen, signed rssi)\r
+{\r
+ struct beaconinfo bi;\r
+ bi.ssid_len = 0;\r
+ bi.channel = 0;\r
+ bi.err = 0;\r
+ bi.rssi = rssi;\r
+ int pos = 36;\r
+\r
+ if (frame[pos] == 0x00) {\r
+ while (pos < framelen) {\r
+ switch (frame[pos]) {\r
+ case 0x00: //SSID\r
+ bi.ssid_len = (int) frame[pos + 1];\r
+ if (bi.ssid_len == 0) {\r
+ memset(bi.ssid, '\x00', 33);\r
+ break;\r
+ }\r
+ if (bi.ssid_len < 0) {\r
+ bi.err = -1;\r
+ break;\r
+ }\r
+ if (bi.ssid_len > 32) {\r
+ bi.err = -2;\r
+ break;\r
+ }\r
+ memset(bi.ssid, '\x00', 33);\r
+ memcpy(bi.ssid, frame + pos + 2, bi.ssid_len);\r
+ bi.err = 0; // before was error??\r
+ break;\r
+ case 0x03: //Channel\r
+ bi.channel = (int) frame[pos + 2];\r
+ pos = -1;\r
+ break;\r
+ default:\r
+ break;\r
+ }\r
+ if (pos < 0) break;\r
+ pos += (int) frame[pos + 1] + 2;\r
+ }\r
+ } else {\r
+ bi.err = -3;\r
+ }\r
+\r
+ bi.capa[0] = frame[34];\r
+ bi.capa[1] = frame[35];\r
+ memcpy(bi.bssid, frame + 10, ETH_MAC_LEN);\r
+ return bi;\r
+}\r
--- /dev/null
+Insert MAC-adress-vendors below:
\ No newline at end of file
--- /dev/null
+#!/usr/bin/env python
+# Log data from serial port
+
+# Author: Diego Herranz + Nils Forssen
+
+import argparse
+import serial
+import time
+import os
+from datetime import datetime
+from mac_vendor_lookup import MacLookup
+
+uniqueMACList = []
+
+parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+parser.add_argument("-d", "--device", help="device to read from", default="/dev/ttyUSB0")#/dev/ttyUSB0"
+parser.add_argument("-s", "--speed", help="speed in bps", default=57600, type=int)
+args = parser.parse_args()
+
+outputFilePath = os.path.join(os.path.dirname(__file__) + "logs", "uniqueMACLog_" +
+ datetime.now().strftime("%Y-%m-%dT%H.%M.%S") + ".bin")
+
+blackListFilePath = os.path.join(os.path.dirname(__file__), "blackList.txt")
+
+
+def itemInFile(file, item):
+ file.seek(0)
+ for line in file.readlines():
+ if item in line:
+ return True
+ return False
+
+with serial.Serial(args.device, args.speed) as ser, open(outputFilePath, mode='wb+') as outputFile, open(
+ blackListFilePath, mode='r') as blackList:
+
+ print("Logging started. Ctrl-C to stop.")
+ try:
+ while True:
+ time.sleep(1)
+
+ serialString = (ser.read(ser.inWaiting()))
+
+ for line in serialString.splitlines():
+ if not line in uniqueMACList:
+
+ try:
+ company = MacLookup().lookup(line)#.decode("utf-8"))
+
+ if not itemInFile(blackList, company):
+ timeNow = datetime.now().strftime("%H:%M:%S")
+ outputFile.write(bytes(timeNow + '\t', "utf-8") + line + bytes('\t' + company + '\n', "utf-8"))
+
+ except KeyError:
+ pass
+
+ uniqueMACList.append(line)
+
+ outputFile.flush()
+
+ except KeyboardInterrupt:
+ outputFile.close()
+ print("Logging stopped")
+
+
--- /dev/null
+#!/usr/bin/env python
+# Log data from serial port
+
+# Author: Diego Herranz + Nils Forssen
+
+import argparse
+import serial
+import datetime
+import time
+import os
+from mac_vendor_lookup import MacLookup
+
+uniqueMACList = []
+
+parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+parser.add_argument("-d", "--device", help="device to read from", default="/dev/ttyUSB0") #"/dev/ttyUSB0"
+parser.add_argument("-s", "--speed", help="speed in bps", default=57600, type=int)
+args = parser.parse_args()
+
+outputFilePath = os.path.join(os.path.dirname(__file__) + "logs", "uniqueMACLog_" +
+ datetime.datetime.now().strftime("%Y-%m-%dT%H.%M.%S") + ".bin")
+
+blackListFilePath = os.path.join(os.path.dirname(__file__), "blackList.txt")
+
+
+def itemInFile(file, item):
+ file.seek(0)
+ for line in file.readlines():
+ if item in line:
+ return True
+ return False
+
+with serial.Serial(args.device, args.speed) as ser, open(outputFilePath, mode='wb+') as outputFile, open(
+ blackListFilePath, mode='r') as blackList:
+
+ print("Logging started. Ctrl-C to stop.")
+ try:
+ while True:
+ time.sleep(1)
+
+ serialString = (ser.read(ser.inWaiting()))
+
+ for line in serialString.splitlines():
+ if not line in uniqueMACList:
+
+ try:
+ company = MacLookup().lookup(line)#.decode("utf-8"))
+
+ if not itemInFile(blackList, company):
+ outputFile.write(line + bytes('\t' + company + '\n', "utf-8"))
+
+ except KeyError:
+ pass
+
+ uniqueMACList.append(line)
+
+ outputFile.flush()
+
+ except KeyboardInterrupt:
+ outputFile.close()
+ print("Logging stopped")
+
+
--- /dev/null
+Insert MAC-adress-vendors below:\r
+NETGEAR\r
+Apple, Inc.
\ No newline at end of file
--- /dev/null
+#!/usr/bin/env python
+# Log data from serial port
+
+# Author: Diego Herranz + Nils Forssén
+
+import argparse
+import serial
+import time
+import os
+from datetime import datetime
+from mac_vendor_lookup import MacLookup
+
+uniqueMACList = []
+
+parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+parser.add_argument("-d", "--device", help="device to read from", default="COM4") #"/dev/ttyUSB0"
+parser.add_argument("-s", "--speed", help="speed in bps", default=57600, type=int)
+args = parser.parse_args()
+
+outputFilePath = os.path.join(os.path.dirname(__file__) + "\logs", "uniqueMACLog_" +
+ datetime.now().strftime("%Y-%m-%dT%H.%M.%S") + ".bin")
+
+blackListFilePath = os.path.join(os.path.dirname(__file__), "blackList.txt")
+
+
+def itemInFile(file, item):
+ file.seek(0)
+ for line in file.readlines():
+ if item in line:
+ return True
+ return False
+
+
+with serial.Serial(args.device, args.speed) as ser, open(outputFilePath, mode='wb+') as outputFile, open(
+ blackListFilePath, mode='r') as blackList:
+
+ print("Logging started. Ctrl-C to stop.")
+ try:
+ while True:
+ time.sleep(1)
+
+ serialString = (ser.read(ser.inWaiting()))
+
+ for line in serialString.splitlines():
+ if not line in uniqueMACList:
+
+ try:
+ company = MacLookup().lookup(line)#.decode("utf-8"))
+
+ if not itemInFile(blackList, company):
+ timeNow = datetime.now().strftime("%H:%M:%S")
+ outputFile.write(bytes(timeNow + '\t', "utf-8") + line + bytes('\t' + company + '\n', "utf-8"))
+
+ except KeyError:
+ pass
+
+ uniqueMACList.append(line)
+
+ outputFile.flush()
+
+ except KeyboardInterrupt:
+ outputFile.close()
+ print("Logging stopped")
+
+
--- /dev/null
+#!/usr/bin/env python
+# Log data from serial port
+
+# Author: Diego Herranz + Nils Forssén
+
+import argparse
+import serial
+import datetime
+import time
+import os
+from mac_vendor_lookup import MacLookup
+
+uniqueMACList = []
+
+parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+parser.add_argument("-d", "--device", help="device to read from", default="COM4") #"/dev/ttyUSB0"
+parser.add_argument("-s", "--speed", help="speed in bps", default=57600, type=int)
+args = parser.parse_args()
+
+outputFilePath = os.path.join(os.path.dirname(__file__) + "\logs", "uniqueMACLog_" +
+ datetime.datetime.now().strftime("%Y-%m-%dT%H.%M.%S") + ".bin")
+
+blackListFilePath = os.path.join(os.path.dirname(__file__), "blackList.txt")
+
+
+def itemInFile(file, item):
+ file.seek(0)
+ for line in file.readlines():
+ if item in line:
+ return True
+ return False
+
+
+with serial.Serial(args.device, args.speed) as ser, open(outputFilePath, mode='wb+') as outputFile, open(
+ blackListFilePath, mode='r') as blackList:
+
+ print("Logging started. Ctrl-C to stop.")
+ try:
+ while True:
+ time.sleep(1)
+
+ serialString = (ser.read(ser.inWaiting()))
+
+ for line in serialString.splitlines():
+ if not line in uniqueMACList:
+
+ try:
+ company = MacLookup().lookup(line)#.decode("utf-8"))
+
+ if not itemInFile(blackList, company):
+ outputFile.write(line + bytes('\t' + company + '\n', "utf-8"))
+
+ except KeyError:
+ pass
+
+ uniqueMACList.append(line)
+
+ outputFile.flush()
+
+ except KeyboardInterrupt:
+ outputFile.close()
+ print("Logging stopped")
+
+
--- /dev/null
+#!/usr/bin/env python
+# Log data from serial port
+
+# Author: Diego Herranz
+
+import argparse
+import serial
+import datetime
+import time
+import os
+
+parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+parser.add_argument("-d", "--device", help="device to read from", default="/dev/ttyUSB0")
+parser.add_argument("-s", "--speed", help="speed in bps", default=57600, type=int)
+args = parser.parse_args()
+
+outputFilePath = os.path.join(os.path.dirname(__file__),
+ datetime.datetime.now().strftime("%Y-%m-%dT%H.%M.%S") + ".bin")
+
+with serial.Serial(args.device, args.speed) as ser, open(outputFilePath, mode='wb') as outputFile:
+ print("Logging started. Ctrl-C to stop.")
+ try:
+ while True:
+ time.sleep(1)
+ outputFile.write((ser.read(ser.inWaiting())))
+ outputFile.flush()
+ except KeyboardInterrupt:
+ print("Logging stopped")
--- /dev/null
+#!/usr/bin/env python
+# Log data from serial port
+
+# Author: Diego Herranz
+
+# Modified by Nils Forssen for specific usecase
+
+import argparse
+import serial
+import datetime
+import time
+import os
+
+uniqueMACList = []
+
+parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+parser.add_argument("-d", "--device", help="device to read from", default="/dev/ttyUSB0")
+parser.add_argument("-s", "--speed", help="speed in bps", default=57600, type=int)
+args = parser.parse_args()
+
+outputFilePath = os.path.join(os.path.dirname(__file__),"uniqueMACLog_" +
+ datetime.datetime.now().strftime("%Y-%m-%dT%H.%M.%S") + ".bin")
+
+with serial.Serial(args.device, args.speed) as ser, open(outputFilePath, mode='wb') as outputFile:
+ print("Logging started. Ctrl-C to stop.")
+ try:
+ while True:
+ time.sleep(1)
+
+ serialString = (ser.read(ser.inWaiting()))
+
+ for line in serialString.splitlines():
+ if not line in uniqueMACList:
+ uniqueMACList.append(line)
+ outputFile.write(line + b"\n")
+
+ outputFile.flush()
+
+ except KeyboardInterrupt:
+ outputFile.close()
+ print("Logging stopped")
--- /dev/null
+#!/usr/bin/env python
+# Log data from serial port
+
+# Author: Diego Herranz
+
+# Modified by Nils Forssén for specific usecase
+
+import argparse
+import serial
+import datetime
+import time
+import os
+
+uniqueMACList = []
+
+parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+parser.add_argument("-d", "--device", help="device to read from", default="COM5")
+parser.add_argument("-s", "--speed", help="speed in bps", default=57600, type=int)
+args = parser.parse_args()
+
+outputFilePath = os.path.join(os.path.dirname(__file__),"uniqueMACLog_" +
+ datetime.datetime.now().strftime("%Y-%m-%dT%H.%M.%S") + ".bin")
+
+with serial.Serial(args.device, args.speed) as ser, open(outputFilePath, mode='wb') as outputFile:
+ print("Logging started. Ctrl-C to stop.")
+ try:
+ while True:
+ time.sleep(1)
+
+ serialString = (ser.read(ser.inWaiting()))
+
+ for line in serialString.splitlines():
+ if not line in uniqueMACList:
+ uniqueMACList.append(line)
+ outputFile.write(line + b"\n")
+
+ outputFile.flush()
+
+ except KeyboardInterrupt:
+ outputFile.close()
+ print("Logging stopped")
--- /dev/null
+# -*- mode: python ; coding: utf-8 -*-\r
+\r
+block_cipher = None\r
+\r
+\r
+a = Analysis(['Fogis2Calendar.py'],\r
+ pathex=['C:\\Users\\forss\\Documents\\Python\\Fogis2Calendar'],\r
+ binaries=[],\r
+ datas=[('calendar_icon.ico', '.')],\r
+ hiddenimports=[],\r
+ hookspath=[],\r
+ runtime_hooks=[],\r
+ excludes=[],\r
+ win_no_prefer_redirects=False,\r
+ win_private_assemblies=False,\r
+ cipher=block_cipher,\r
+ noarchive=False)\r
+pyz = PYZ(a.pure, a.zipped_data,\r
+ cipher=block_cipher)\r
+exe = EXE(pyz,\r
+ a.scripts,\r
+ a.binaries,\r
+ a.zipfiles,\r
+ a.datas,\r
+ [],\r
+ name='Fogis2Calendar',\r
+ debug=False,\r
+ bootloader_ignore_signals=False,\r
+ strip=False,\r
+ upx=True,\r
+ upx_exclude=[],\r
+ runtime_tmpdir=None,\r
+ console=False , icon='calendar_icon.ico')\r
--- /dev/null
+# Fogis2GoogleCalendar\r
+\r
+## About\r
+Script to automatically fetch a schedule from Fogisdomarklienten, the common Swedish soccer administration system where referees find their upcoming games, and schedule them as events in Google Calendar. \r
+\r
+No more Tedious logging in to fogis, browsing upcoming games and manually creating calendar events for each upcoming game with correct time and location\r
+\r
+The schedule is parsed into Google Calendar events using an OAuth Client ID credentials file in working directory. Script is built in python utilizing the Google Calendar API and compiled to executable with pyinstaller.\r
+\r
+## Usage\r
+To use the executable your personal Google Calendar API must be enabled through a personal project in the [Google Developer Console](https://console.developers.google.com/).\r
+\r
+1. Open the Google Developer Console found above and navigate to "Dashboard" found in menu to the left.\r
+2. Enable the Google Calendar API: \r
+Create Project->Create->Enable APIs and Services->"Google Calendar API"->Enable\r
+3. Navigate back to the Project Page and download your Client ID: \r
+Credentials->Create Credentials->OAuth Client ID->Configure Consent Screen->External->Name and Image->Save->Credentials->Create Credentials->OAuth Client ID->Desktop app->Create\r
+Now you should see a your client in the Client IDs, next to it should be a download symbol where you can download your credentials file.\r
+4. Rename the downloaded file to "credentials" (keep the .json format) and put it in the same working directory as the downloaded Fogis2Calendar executable. \r
+\r
+Now the script will be able to locate your credentials and have access to only your Google Calendar events as seen in src code. \r
+\r
+The script can be run just by running exe by double-clicking the file, but can also be automated by running it via command prompt or a task in Windows Task Scheduler and passing the username and passwords as plain arguments.\r
+\r
+Fogis2Calendar.exe \[username\] \[password\]\r
+\r
+A token.pickle file will be created in the working directory when the script is first run. This file holds your approved permission for access to the Google Calendar. You will be taken to the Google consent screen every time the script is run without this file present in working directory.\r
+\r
+## Issues\r
+Here are some common issues you can have running the executable:\r
+* "Failed to execute script Fogis2Calendar" - This is caused by the script not detecting the credentials.json downloaded in the usage section. Follow the steps carefully, once completed, try running the script again, you should be redirected to a Google consent screen in your browser.\r
+* "Login Unsuccessfull" - Login to Fogisdomarklient failed. Double Check your username and password. Also try logging in to Fogisdomarklient manually since this issue can also be related to your account being suspended.\r
+* Same games are stacking in Google Calendar every time script is run. - This should only be an issue if the events created by the script are manually adjusted somehow. Normally the script searches for every event in your calendar within the time period of upcoming games. If the found event's description ends with "Matchnummer: " that event is recognized as a game and is replaced with the same game directly from fogis, preventing stacking.\r
+\r
+If any issues remain or additional issues are found, contact me either by email or by commenting on this repository.\r
+\r
+## Additional Notes\r
+This is basically my first complete project that save time and I personally use it with Windows Task Scheduler running once a day and love it, saves me lots of time.\r
+\r
+However, as youve probably already noticed the project is not very user-friendly, having the user manually downloading the credentials file and all that. From what I've understood I would need to host this aplication on an authorized domain to be able to submit the application for verification, and only then I could be able to have users redirected to a consent screen for Google Calendar permissions automatically instead of downloading the credentials file. This negates the ability to automate the process locally and i would also need to do some website integration with the application, something I am not very comfortable with. \r
+\r
+May be a future project, but for now I'll stick to this poor but functional solution.\r
+\r
+Thanks for downloading!\r
+\r
+/Nils Forssén
\ No newline at end of file
--- /dev/null
+import requests\r
+from bs4 import BeautifulSoup\r
+import unicodedata\r
+import googleCalendar\r
+import datetime\r
+import sys\r
+import os\r
+\r
+"""\r
+Python Script for adding fogis events to Google Calendar\r
+\r
+credentials.json from Google API project is to be located in working directory.\r
+\r
+A token.pickle file will be created in Working Directory.\r
+Deleting this file will result in you having to approve\r
+access to Google Calendar.\r
+\r
+Script can be executed:\r
+-> Manually by running executable(.py) and using GUI\r
+-> Automatically by CMD passing fogis credentials as arguments\r
+e.g script.exe(.py) myUser myPass\r
+\r
+The latter can be used with Windows task scheduler to schedule calendar updates.\r
+\r
+Author: Nils Forssén, Jämtland County, Sweden\r
+"""\r
+\r
+\r
+def resource_path(relative_path):\r
+ """\r
+ Get pyinstaller resource\r
+ """\r
+\r
+ if hasattr(sys, "_MEIPASS"):\r
+ return os.path.join(sys._MEIPASS, relative_path)\r
+\r
+ return os.path.join(os.path.abspath("."), relative_path)\r
+\r
+\r
+def getDataPage(uName, pWord):\r
+ """\r
+ Login to fogis and return the datapage.\r
+ If not accessible, return None.\r
+ """\r
+\r
+ with requests.Session() as session:\r
+\r
+ # Data to post to loginPage\r
+ payload = {\r
+ "tbAnvandarnamn": uName,\r
+ "tbLosenord": pWord,\r
+ "btnLoggaIn": "Logga in",\r
+ }\r
+\r
+ headers = {\r
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36"\r
+ }\r
+\r
+ # Log in page\r
+ loginPage = session.get(\r
+ "https://fogis.svenskfotboll.se/Fogisdomarklient/login/Login.aspx",\r
+ headers=headers,\r
+ verify=False,\r
+ )\r
+\r
+ # post with all required information\r
+ soup = BeautifulSoup(loginPage.content, features="lxml")\r
+\r
+ payload["__VIEWSTATE"] = soup.select_one("#__VIEWSTATE")["value"]\r
+ payload["__VIEWSTATEGENERATOR"] = soup.select_one("#__VIEWSTATEGENERATOR")[\r
+ "value"\r
+ ]\r
+ payload["__VIEWSTATE"] = soup.find("input", attrs={"name": "__VIEWSTATE"})[\r
+ "value"\r
+ ]\r
+ payload["__VIEWSTATEGENERATOR"] = soup.find(\r
+ "input", attrs={"name": "__VIEWSTATEGENERATOR"}\r
+ )["value"]\r
+ payload["__EVENTVALIDATION"] = soup.find(\r
+ "input", attrs={"name": "__EVENTVALIDATION"}\r
+ )["value"]\r
+\r
+ # Post with the login credentials and additional required information\r
+ session.post(\r
+ "https://fogis.svenskfotboll.se/Fogisdomarklient/login/Login.aspx",\r
+ data=payload,\r
+ headers=headers,\r
+ )\r
+\r
+ # Schedule is located here\r
+ dataPage = session.get(\r
+ "https://fogis.svenskfotboll.se/Fogisdomarklient/Uppdrag/UppdragUppdragLista.aspx"\r
+ )\r
+\r
+ if b"FOGIS - Domarinloggning" in dataPage.content:\r
+\r
+ # Login unsuccessful, access to dataPage url was not granted.\r
+ # e.g. username or password incorrect, account locked/banned etc.\r
+\r
+ return None\r
+ else:\r
+\r
+ # Login successful\r
+\r
+ return dataPage\r
+\r
+\r
+def formatGame(game, offset="+02:00"):\r
+ """\r
+ Turn given game into google calendar event format\r
+ """\r
+\r
+ # If time is TBD, set event time to 00:00:00\r
+ if ":" in game["time"][-5:]:\r
+ startTime = datetime.datetime.strptime(game["time"][-5:], "%H:%M")\r
+ duration = datetime.datetime.strptime("01:30", "%H:%M")\r
+ else:\r
+ startTime = datetime.datetime.strptime("00:00", "%H:%M")\r
+ duration = datetime.datetime.strptime("00:00", "%H:%M")\r
+\r
+ endTime = startTime + datetime.timedelta(\r
+ hours=duration.hour, minutes=duration.minute, seconds=duration.second\r
+ )\r
+\r
+ # create google-calendar-friendly event out of given game\r
+ gameEvent = {\r
+ "summary": "Domare {0}".format(game["competition"]),\r
+ "location": "{0}".format(game["location"].replace("GoogleBingHitta.se", "")),\r
+ "description": "{0}\n{1}\nMatchnummer: {2}".format(\r
+ game["game"], game["referees"], game["number"]\r
+ ),\r
+ "start": {\r
+ "dateTime": "{0}T{1}{2}".format(\r
+ game["time"][:10], startTime.strftime("%H:%M:%S"), offset\r
+ )\r
+ },\r
+ "end": {\r
+ "dateTime": "{0}T{1}{2}".format(\r
+ game["time"][:10], endTime.strftime("%H:%M:%S"), offset\r
+ )\r
+ },\r
+ "reminders": {"useDefault": True},\r
+ "colorId": googleCalendar.EVENT_COLORIDS["b_green"],\r
+ }\r
+\r
+ return gameEvent\r
+\r
+\r
+def updateCalendar(page, offset):\r
+ """\r
+ Update the calendar with games from given dataPage\r
+ """\r
+\r
+ # Parse the HTML schedule-table into a list with dictionaries for every coming game\r
+ soup = BeautifulSoup(page.content, features="lxml")\r
+\r
+ data = []\r
+ gameHeaders = [\r
+ "time",\r
+ "competition",\r
+ "round",\r
+ "number",\r
+ "game",\r
+ "location",\r
+ "referees",\r
+ ]\r
+\r
+ table = soup.find("table", attrs={"class": "fogisInfoTable"})\r
+ tableBody = table.find("tbody")\r
+\r
+ rows = tableBody.find_all("tr")\r
+ for row in rows:\r
+\r
+ # Remove some indesirable characters and whitespace from each cell\r
+ game = dict(\r
+ zip(\r
+ gameHeaders,\r
+ [\r
+ unicodedata.normalize("NFKC", item.text.strip().replace(" ", ""))\r
+ .replace("\n", "")\r
+ .replace("\r", "")\r
+ for item in row.find_all("td")\r
+ ],\r
+ )\r
+ )\r
+\r
+ data.append(game)\r
+\r
+ # Remove empty top row\r
+ data.pop(0)\r
+\r
+ # Format all games from fogis\r
+ data = list(map(lambda g: formatGame(g, offset), data))\r
+ if data:\r
+ comingEvents = googleCalendar.listEvents(\r
+ timeMin=data[0]["start"]["dateTime"], timeMax=data[-1]["end"]["dateTime"]\r
+ )\r
+ print(data[0]["start"]["dateTime"])\r
+ # Delete all coming games to refresh them\r
+ for comingEvent in comingEvents:\r
+\r
+ try:\r
+ lastLine = comingEvent["description"].splitlines()[-1]\r
+\r
+ if "Matchnummer: " in lastLine:\r
+\r
+ # The event is a previously uploaded game, delete it\r
+ # All currently active games will be added later\r
+ # This game could have e.g. been canceled recently, thus it needs removal\r
+\r
+ googleCalendar.deleteEvent(comingEvent["id"])\r
+\r
+ except KeyError:\r
+\r
+ # An event wiithout a description was found, this is not a game from fogis\r
+\r
+ pass\r
+\r
+ for game in data:\r
+\r
+ # Add all new events, in case the event was just deleted, this will just refresh it\r
+\r
+ googleCalendar.createEvent(game)\r
+\r
+ print("Event created! {0}".format(game["start"]["dateTime"]))\r
+\r
+\r
+if __name__ == "__main__":\r
+\r
+ if len(sys.argv) < 2:\r
+\r
+ # No arguments passed, launch GUI prompt for username and password\r
+\r
+ import tkinter as tk\r
+\r
+ root = tk.Tk()\r
+\r
+ # Make the Entrys expand to fill empty space\r
+ root.grid_columnconfigure(1, weight=1)\r
+\r
+ # Window icon\r
+ root.iconbitmap(resource_path("calendar_icon.ico"))\r
+ root.title("Fogis2Calendar")\r
+\r
+ # GUI elements\r
+ header = tk.Label(text="Enter your fogis credentials", font="Helvetica 16")\r
+ uNameLabel = tk.Label(text="Username:", font="Helvetica 10")\r
+ uNameEntry = tk.Entry()\r
+ pWordLabel = tk.Label(text="Password:", font="Helvetica 10")\r
+ pWordEntry = tk.Entry()\r
+\r
+ promptString = tk.StringVar()\r
+ promptString.set("")\r
+ promptLabel = tk.Label(textvariable=promptString, font="Helvetica 10")\r
+\r
+ offsetHeader = tk.Label(text="GMT offset:", font="Helvetica 10")\r
+ offsetString = tk.StringVar()\r
+ offsetString.set("+02:00")\r
+ offsetEntry = tk.Entry(textvariable=offsetString)\r
+\r
+ def btnUpdateCalendar():\r
+ """\r
+ Comprehensive update calendar function linked to btn in GUI\r
+ """\r
+\r
+ page = getDataPage(uNameEntry.get(), pWordEntry.get())\r
+\r
+ if page is not None:\r
+\r
+ promptString.set("Updated!")\r
+ promptLabel.config(fg="green2")\r
+ updateCalendar(page, offsetString.get())\r
+\r
+ # "Updated"\r
+\r
+ else:\r
+\r
+ promptString.set("Login unsuccessful!")\r
+ promptLabel.config(fg="red2")\r
+\r
+ btn = tk.Button(\r
+ text="Update Calendar",\r
+ font="Helvetica 10 bold",\r
+ command=btnUpdateCalendar,\r
+ bg="green2",\r
+ activebackground="green2",\r
+ )\r
+\r
+ # Grid GUI elements\r
+ header.grid(columnspan=2, row=0, column=0, sticky="NSEW")\r
+ uNameLabel.grid(row=1, sticky="W")\r
+ uNameEntry.grid(row=1, column=1, sticky="EW")\r
+ pWordLabel.grid(row=2, sticky="W")\r
+ pWordEntry.grid(row=2, column=1, sticky="EW")\r
+ offsetHeader.grid(row=3, column=0, sticky="W")\r
+ offsetEntry.grid(row=3, column=1, sticky="EW")\r
+ promptLabel.grid(columnspan=2, row=4, sticky="NSEW")\r
+ btn.grid(columnspan=2, row=5)\r
+\r
+ root.mainloop()\r
+\r
+ else:\r
+\r
+ # username and password arguments passed, don't launch GUI\r
+\r
+ try:\r
+ username = sys.argv[1]\r
+ password = sys.argv[2]\r
+ timeoff = sys.argv[3]\r
+ except IndexError:\r
+ print("Both username and password must be passed as arguments")\r
+ sys.exit()\r
+\r
+ page = getDataPage(username, password)\r
+\r
+ if page is not None:\r
+ updateCalendar(page, offset=timeoff)\r
+ else:\r
+ print("Login unsuccessfull")\r
--- /dev/null
+# -*- mode: python ; coding: utf-8 -*-\r
+\r
+block_cipher = None\r
+\r
+\r
+a = Analysis(['Fogis2Calendar.py'],\r
+ pathex=['C:\\Users\\forss\\Documents\\Python\\Fogis2Calendar\\src'],\r
+ binaries=[],\r
+ datas=[('calendar_icon.ico', '.')],\r
+ hiddenimports=[],\r
+ hookspath=[],\r
+ runtime_hooks=[],\r
+ excludes=[],\r
+ win_no_prefer_redirects=False,\r
+ win_private_assemblies=False,\r
+ cipher=block_cipher,\r
+ noarchive=False)\r
+pyz = PYZ(a.pure, a.zipped_data,\r
+ cipher=block_cipher)\r
+exe = EXE(pyz,\r
+ a.scripts,\r
+ a.binaries,\r
+ a.zipfiles,\r
+ a.datas,\r
+ [],\r
+ name='Fogis2Calendar',\r
+ debug=False,\r
+ bootloader_ignore_signals=False,\r
+ strip=False,\r
+ upx=True,\r
+ upx_exclude=[],\r
+ runtime_tmpdir=None,\r
+ console=False , icon='calendar_icon.ico')\r
--- /dev/null
+pyinstaller compile command:\r
+\r
+pyinstaller --onefile --icon=calendar_icon.ico --noconsole --add-data "calendar_icon.ico;." Fogis2Calendar.py
\ No newline at end of file
--- /dev/null
+from __future__ import print_function\r
+import datetime\r
+import pickle\r
+import os.path\r
+from googleapiclient.discovery import build\r
+from google_auth_oauthlib.flow import InstalledAppFlow\r
+from google.auth.transport.requests import Request\r
+\r
+"""\r
+Resource to update Google Calendar using credentials.json in Working Directory.\r
+\r
+A token.pickle file will be created for permissions to Google Calendar.\r
+\r
+Author: Nils Forssén, Jämtland County, Sweden\r
+"""\r
+\r
+EVENT_COLORIDS = {\r
+ "blue": 1,\r
+ "green": 2,\r
+ "purple": 3,\r
+ "red": 4,\r
+ "yellow": 5,\r
+ "orange": 6,\r
+ "turquoise": 7,\r
+ "gray": 8,\r
+ "b_blue": 9,\r
+ "b_green": 10,\r
+ "b_red": 11\r
+}\r
+\r
+\r
+# Give accesss to complete Google Calendar\r
+SCOPES = ["https://www.googleapis.com/auth/calendar.events"]\r
+\r
+def getCredentials():\r
+ """\r
+ Get the current credentials from the pickle file, \r
+ If not available, create new file with credentials\r
+ """\r
+\r
+ creds = None\r
+ if os.path.exists("token.pickle"):\r
+ with open("token.pickle", "rb") as token:\r
+ creds = pickle.load(token)\r
+\r
+ if not creds or not creds.valid:\r
+ if creds and creds.expired and creds.refresh_token:\r
+ creds.refresh(Request())\r
+ else:\r
+ flow = InstalledAppFlow.from_client_secrets_file(\r
+ "credentials.json", SCOPES)\r
+ creds = flow.run_local_server(port=0)\r
+ with open("token.pickle", "wb") as token:\r
+ pickle.dump(creds, token)\r
+\r
+ return creds\r
+\r
+\r
+service = build('calendar', 'v3', credentials=getCredentials())\r
+\r
+def createEvent(event):\r
+ """\r
+ Create google calendar event using the standard event formatting \r
+ """\r
+\r
+ event = service.events().insert(calendarId="primary", body=event).execute()\r
+\r
+ return event\r
+\r
+\r
+def deleteEvent(eventId):\r
+ """\r
+ Delete event with given id from google calendar\r
+ """\r
+\r
+ service.events().delete(calendarId="primary", eventId=eventId).execute()\r
+\r
+\r
+def listEvents(**kwargs):\r
+ """\r
+ Return list of all google calendar events\r
+ """\r
+\r
+ events_result = service.events().list(calendarId="primary", singleEvents=True, orderBy='startTime', **kwargs).execute()\r
+\r
+ events = events_result.get('items', [])\r
+\r
+ return events\r
--- /dev/null
+# -*- mode: python ; coding: utf-8 -*-\r
+\r
+block_cipher = None\r
+\r
+\r
+a = Analysis(['Injixo2Calendar.py'],\r
+ pathex=['C:\\Users\\forss\\Documents\\Python\\Injixo2Calendar'],\r
+ binaries=[],\r
+ datas=[('logo.ico', '.')],\r
+ hiddenimports=[],\r
+ hookspath=[],\r
+ runtime_hooks=[],\r
+ excludes=[],\r
+ win_no_prefer_redirects=False,\r
+ win_private_assemblies=False,\r
+ cipher=block_cipher,\r
+ noarchive=False)\r
+pyz = PYZ(a.pure, a.zipped_data,\r
+ cipher=block_cipher)\r
+exe = EXE(pyz,\r
+ a.scripts,\r
+ a.binaries,\r
+ a.zipfiles,\r
+ a.datas,\r
+ [],\r
+ name='Injixo2Calendar',\r
+ debug=False,\r
+ bootloader_ignore_signals=False,\r
+ strip=False,\r
+ upx=True,\r
+ upx_exclude=[],\r
+ runtime_tmpdir=None,\r
+ console=False , icon='logo.ico')\r
--- /dev/null
+# Injixo2Calendar\r
+\r
+## About\r
+Script to automatically fetch a schedule from a subdomain h1.me of injixo.com and schedule it as events in Google Calendar. \r
+\r
+No more Tedious logging in to injixo, browsing upcoming shifts and manually creating calendar events for each upcoming shift with correct time, location and lunchbreaks.\r
+\r
+The schedule is parsed into Google Calendar events using an OAuth Client ID credentials file in working directory. Script is built in python utilizing the Google Calendar API and compiled to executable with pyinstaller.\r
+\r
+## Usage\r
+To use the executable your personal Google Calendar API must be enabled through a personal project in the [Google Developer Console](https://console.developers.google.com/).\r
+\r
+1. Open the Google Developer Console found above and navigate to "Dashboard" found in menu to the left.\r
+2. Enable the Google Calendar API: \r
+Create Project->Create->Enable APIs and Services->"Google Calendar API"->Enable\r
+3. Navigate back to the Project Page and download your Client ID: \r
+Credentials->Create Credentials->OAuth Client ID->Configure Consent Screen->External->Name and Image->Save->Credentials->Create Credentials->OAuth Client ID->Desktop app->Create\r
+Now you should see a your client in the Client IDs, next to it should be a download symbol where you can download your credentials file.\r
+4. Rename the downloaded file to "credentials" (keep the .json format) and put it in the same working directory as the downloaded Injixo2Calendar executable. \r
+\r
+Now the script will have access to only your Google Calendar events as seen in source-code. \r
+\r
+The script can be executed just by running the file, but can also be automated by running it via command prompt or a task in Windows Task Scheduler and passing the username and passwords as plain arguments.\r
+\r
+Injixo2Calendar.exe \[username\] \[password\]\r
+\r
+A token.pickle file will be created in the working directory when the script is first run. This file holds your approved permission for access to the Google Calendar. You will be taken to the Google consent screen every time the script is run without this file present in working directory.\r
+\r
+## Issues\r
+Here are some common issues you can have running the executable:\r
+* "Failed to execute script Injixo2Calendar" - This is most likely caused by the script not detecting the credentials.json downloaded in the usage section. Follow the steps carefully, once completed, try running the script again, you should be redirected to a Google consent screen in your browser.\r
+* "Login Unsuccesfull" - Login to h1.me.injixo.com failed. Double Check your username and password. Also try logging in to Injixo manually since this issue can also be related to your account being suspended.\r
+* Same shifts are stacking in Google Calendar every time script is run. - This should only be an issue if the events created by the script are manually adjusted somehow. Normally the script searches for every event in your calendar within the time period of upcoming shifts. If the found event's description ends with "H1 Arbetspass" that event is recognized as a shift and is replaced with the same shift directly from Injixo, preventing stacking.\r
+\r
+If any issues remain or additional issues are found, contact me either by email or by commenting on this repository.\r
+\r
+## Additional Notes\r
+I personally use this with Windows Task Scheduler running once a day and love it, saves me lots of time.\r
+\r
+However, as youve probably already noticed the project is not very user-friendly, having the user manually downloading the credentials file and all that. From what I've understood I would need to host this aplication on an authorized domain to be able to submit the application for verification, and only then I could be able to have users redirected to a consent screen for Google Calendar permissions automatically instead of downloading the credentials file. This negates the ability to automate the process locally and i would also need to do some website integration with the application, something I am not very comfortable with. \r
+\r
+May be a future project, but for now I'll stick to this poor but functional solution.\r
+\r
+Thanks for downloading!\r
+\r
+/Nils Forssén\r
--- /dev/null
+# -*- mode: python ; coding: utf-8 -*-\r
+\r
+block_cipher = None\r
+\r
+\r
+a = Analysis(['Injixio2Calendar.py'],\r
+ pathex=['C:\\Users\\forss\\Documents\\Python\\Injixio2Calendar\\src'],\r
+ binaries=[],\r
+ datas=[('logo.ico', '.')],\r
+ hiddenimports=[],\r
+ hookspath=[],\r
+ runtime_hooks=[],\r
+ excludes=[],\r
+ win_no_prefer_redirects=False,\r
+ win_private_assemblies=False,\r
+ cipher=block_cipher,\r
+ noarchive=False)\r
+pyz = PYZ(a.pure, a.zipped_data,\r
+ cipher=block_cipher)\r
+exe = EXE(pyz,\r
+ a.scripts,\r
+ a.binaries,\r
+ a.zipfiles,\r
+ a.datas,\r
+ [],\r
+ name='Injixio2Calendar',\r
+ debug=False,\r
+ bootloader_ignore_signals=False,\r
+ strip=False,\r
+ upx=True,\r
+ upx_exclude=[],\r
+ runtime_tmpdir=None,\r
+ console=False , icon='logo.ico')\r
--- /dev/null
+import requests\r
+from bs4 import BeautifulSoup\r
+import googleCalendar\r
+import datetime\r
+import sys\r
+import os\r
+import locale\r
+import pkg_resources.py2_warn\r
+\r
+\r
+"""\r
+Python Script for adding h1.injixo shifts to Google Calendar. (Should work for other injixo subdomains aswell?)\r
+\r
+credentials.json from Google API project is to be located in working directory.\r
+\r
+A token.pickle file will be created in Working Directory.\r
+Deleting this file will result in you having to approve\r
+access to Google Calendar.\r
+\r
+Script can be executed:\r
+-> Manually by running executable(.py) and using GUI\r
+-> Automatically by CMD passing fogis credentials as arguments\r
+e.g script.exe(.py) myUser myPass\r
+\r
+The latter can be used with Windows task scheduler to schedule calendar updates.\r
+\r
+Author: Nils Forssén, Jämtland County, Sweden\r
+"""\r
+\r
+# Make sure the datetime %p works like AM/PM\r
+locale.setlocale(locale.LC_ALL, "en_US")\r
+\r
+# Global event-details\r
+LOCATION = "H1 Communication AB"\r
+\r
+\r
+class Shift():\r
+ """\r
+ Node to store shift-details such as date, start and ending times and the shift summary\r
+ Shift can be merged with other shifts and converted to Google Calendar event format\r
+ """\r
+\r
+ def __init__(self, date, startTime, endTime, summary, offset):\r
+ """\r
+ Initialize shitf with shift-date, starttime, endtime and summary\r
+ """\r
+\r
+ self.summary = summary\r
+ self.start = datetime.datetime.strptime(\r
+ date + startTime, "%B %d, %Y%I:%M %p")\r
+ self.end = datetime.datetime.strptime(\r
+ date + endTime, "%B %d, %Y%I:%M %p")\r
+ self.length = self.end - self.start\r
+ self.offset = offset\r
+\r
+ self.description = "Ingen lunchrast!"\r
+\r
+ def mergeShift(self, shift):\r
+ """\r
+ Merge shift with other shift, changing the ending time and adding a lunchbreak in event description\r
+ """\r
+\r
+ self.end = shift.end\r
+\r
+ if shift.summary == "Lunch":\r
+ self.description = str(int(\r
+ shift.length.seconds / 60)) + " minuter lunchrast!"\r
+\r
+ def getEvent(self):\r
+ """\r
+ Get a the shift in a Google Calendar event format\r
+ """\r
+\r
+ event = {\r
+ "summary": "H1 " + self.summary,\r
+ "location": LOCATION,\r
+ # The H1 tag "classifies" event as a shift\r
+ "description": self.description + "\n\nH1 Communication arbetspass",\r
+ "start": {\r
+ "dateTime": "{0}T{1}{2}".format(self.start.date(), self.start.time(), self.offset)\r
+ },\r
+ "end": {\r
+ "dateTime": "{0}T{1}{2}".format(self.end.date(), self.end.time(), self.offset)\r
+ },\r
+ "reminders": {\r
+ "useDefault": False,\r
+ "overrides": [\r
+ {\r
+ "method": "popup",\r
+ "minutes": 720 # 12 hours\r
+ },\r
+ {\r
+ "method": "popup",\r
+ "minutes": 5 # 12 hours\r
+ }\r
+ ]\r
+ },\r
+ "colorId": googleCalendar.EVENT_COLORIDS["yellow"]\r
+\r
+ }\r
+ return event\r
+\r
+\r
+def resource_path(relative_path):\r
+ """\r
+ Get pyinstaller resource\r
+ """\r
+\r
+ if hasattr(sys, '_MEIPASS'):\r
+ return os.path.join(sys._MEIPASS, relative_path)\r
+\r
+ return os.path.join(os.path.abspath("."), relative_path)\r
+\r
+\r
+def getDataPage(uName, pWord):\r
+ """\r
+ Login to fogis and return the datapage.\r
+ If not accessible, return None.\r
+ """\r
+\r
+ with requests.Session() as session:\r
+\r
+ # Data to post to loginPage\r
+ payload = {\r
+ "username": uName,\r
+ "password": pWord,\r
+ "locale": "en",\r
+ "commit": "Login"\r
+ }\r
+\r
+ headers = {\r
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36"\r
+ }\r
+\r
+ # Log in page\r
+ loginPage = session.get("https://h1.me.injixo.com/login")\r
+\r
+ # Soup the necesary login information from the login website\r
+ soup = BeautifulSoup(loginPage.text, features="lxml")\r
+\r
+ # The crsf-token was renamed with a crsf-param\r
+ token = soup.select_one("meta[name='csrf-token']")["content"]\r
+ tokenName = soup.select_one("meta[name='csrf-param']")["content"]\r
+ payload[tokenName] = token\r
+\r
+ # Post with the login credentials and additional required information in payload\r
+ session.post("https://h1.me.injixo.com/login",\r
+ data=payload, headers=headers)\r
+\r
+ # Agenda is located on the dashboard\r
+ dataPage = session.get("https://h1.me.injixo.com/dashboard")\r
+\r
+ if b"injixo Me | Login" in dataPage.content:\r
+\r
+ # Login unsuccessfull, access to dataPage url was not granted.\r
+ # e.g. username or password incorrect, account locked/banned etc.\r
+ return None\r
+\r
+ else:\r
+\r
+ # Login successfull\r
+ return dataPage\r
+\r
+\r
+def updateCalendar(page, offset="+02:00"):\r
+ """\r
+ Update the calendar with games from given dataPage\r
+ """\r
+\r
+ dashboard = BeautifulSoup(page.content, features="lxml")\r
+\r
+ # Only search in the agenda portion of the dashboard\r
+ agenda = dashboard.find("div", class_="pane__body agenda-spacer")\r
+\r
+ # List to store the all the shift of the next 7 days\r
+ shiftList = []\r
+\r
+ # Find all the list-items of the agenda, these may store the date, time or title of the shift\r
+ for item in agenda.find_all("div", class_="list-item"):\r
+\r
+ # If the list-item is a header including the current date, the date or none of those\r
+ # In the latter case, the list-item is not a header and instead includes shift details\r
+ # - Stupid HTML class-generalization on the website but that is what I have to work with...\r
+ date = item.find(\r
+ "span", class_="current-day") or item.find("span", class_="") or date\r
+\r
+ shiftName = item.find("span", class_="agenda_event_title")\r
+\r
+ # If there is something planned (not nothing) and that something is me being "unavailable"\r
+ if shiftName is not None:\r
+\r
+ # Both start and end times in 12-hour clock, %I:%M %p - %I:%M %p\r
+ shiftTime = item.find("div", class_="list-item__action")\r
+\r
+ # Create a shift node, this stores the details of every shift and can be parsed into events\r
+ newShift = Shift(date.text.strip(), shiftTime.text.strip()[\r
+ :8], shiftTime.text.strip()[-8:], shiftName.text.strip(), offset=offset)\r
+\r
+ # If there is a shift on the same day connected to the previous shift, merge them into one event, otherwise create two separate events\r
+ if "Kan Ej" not in newShift.summary:\r
+\r
+ try:\r
+ pastShift = shiftList[-1]\r
+ except IndexError:\r
+ shiftList.append(newShift)\r
+ else:\r
+\r
+ # If the same day and theres less than 1 hour between the shifts merge them, else just add the new shift as a separate shift\r
+ # If the newShift is a lunchbreak or shift after lunch they will all be merged as one long shift\r
+ if pastShift.start.date() == newShift.start.date() and newShift.start - pastShift.end < datetime.timedelta(minutes=60):\r
+ pastShift.mergeShift(newShift)\r
+ else:\r
+ shiftList.append(newShift)\r
+ if shiftList:\r
+ comingEvents = googleCalendar.listEvents(timeMin=shiftList[0].getEvent(\r
+ )["start"]["dateTime"], timeMax=shiftList[-1].getEvent()["end"]["dateTime"])\r
+\r
+ for comingEvent in comingEvents:\r
+\r
+ try:\r
+ lastLine = comingEvent["description"].splitlines()[-1]\r
+\r
+ if "H1 Communication arbetspass" in lastLine:\r
+\r
+ # The event is "classified" as a previously uploaded shift and should thus be updated\r
+ # All currently active shifts will be readded later\r
+ # This shift could have e.g. been canceled recently, thus it needs updating\r
+\r
+ googleCalendar.deleteEvent(comingEvent["id"])\r
+\r
+ except KeyError:\r
+\r
+ # An event without a description was found, this is not a game created by this script\r
+ pass\r
+\r
+ for shift in shiftList:\r
+\r
+ # Create a Google Calendar event for each parsed shift\r
+ googleCalendar.createEvent(shift.getEvent())\r
+ print("Event created! {0}".format(shift.start))\r
+\r
+\r
+if __name__ == "__main__":\r
+\r
+ if len(sys.argv) < 2:\r
+\r
+ # No arguments passed, launch GUI prompt for username and password\r
+\r
+ import tkinter as tk\r
+\r
+ root = tk.Tk()\r
+\r
+ # Make the Entrys expand to fill empty space\r
+ root.grid_columnconfigure(1, weight=1)\r
+\r
+ # Window icon\r
+ root.iconbitmap(resource_path("logo.ico"))\r
+ root.title("Injixo2Calendar")\r
+\r
+ # GUI elements\r
+ header = tk.Label(\r
+ text="Enter your injixo credentials", font='Helvetica 16')\r
+ uNameLabel = tk.Label(text="Username:", font="Helvetica 10")\r
+ uNameEntry = tk.Entry()\r
+ pWordLabel = tk.Label(text="Password:", font="Helvetica 10")\r
+ pWordEntry = tk.Entry(show="*")\r
+\r
+ promptString = tk.StringVar()\r
+ promptString.set("")\r
+ promptLabel = tk.Label(textvariable=promptString, font="Helvetica 10")\r
+\r
+ offsetHeader = tk.Label(text="GMT offset:", font="Helvetica 10")\r
+ offsetString = tk.StringVar()\r
+ offsetString.set("+02:00")\r
+ offsetEntry = tk.Entry(textvariable=offsetString)\r
+\r
+ def btnUpdateCalendar():\r
+ """\r
+ Comprehensive update calendar function linked to button in GUI\r
+ """\r
+\r
+ page = getDataPage(uNameEntry.get(), pWordEntry.get())\r
+\r
+ if page is not None:\r
+\r
+ promptString.set("Updated!")\r
+ promptLabel.config(fg="green2")\r
+ updateCalendar(page, offsetString.get())\r
+\r
+ # "Updated!"\r
+\r
+ else:\r
+\r
+ promptString.set("Login unsuccesful!")\r
+ promptLabel.config(fg="red2")\r
+\r
+ btn = tk.Button(text="Update Calendar", font="Helvetica 10 bold",\r
+ command=btnUpdateCalendar, bg="green2", activebackground="green2")\r
+\r
+ # Grid GUI elements\r
+ header.grid(columnspan=2, row=0, column=0, sticky="NSEW")\r
+ uNameLabel.grid(row=1, sticky="W")\r
+ uNameEntry.grid(row=1, column=1, sticky="EW")\r
+ pWordLabel.grid(row=2, sticky="W")\r
+ pWordEntry.grid(row=2, column=1, sticky="EW")\r
+ offsetHeader.grid(row=3, column=0, sticky="W")\r
+ offsetEntry.grid(row=3, column=1, sticky="EW")\r
+ promptLabel.grid(columnspan=2, row=4, sticky="NSEW")\r
+ btn.grid(columnspan=2, row=5)\r
+\r
+ root.mainloop()\r
+\r
+ else:\r
+\r
+ # username and password arguments passed, don't launch GUI\r
+\r
+ try:\r
+ username = sys.argv[1]\r
+ password = sys.argv[2]\r
+ timeoff = sys.argv[3]\r
+ except IndexError:\r
+ print("Both username and password must be passed as arguments")\r
+ sys.exit()\r
+\r
+ page = getDataPage(username, password)\r
+\r
+ if page is not None:\r
+ updateCalendar(page, offset=timeoff)\r
+ else:\r
+ print("Login unsuccessfull")\r
--- /dev/null
+# -*- mode: python ; coding: utf-8 -*-\r
+\r
+block_cipher = None\r
+\r
+\r
+a = Analysis(['Injixo2Calendar.py'],\r
+ pathex=['C:\\Users\\forss\\Documents\\Python\\Injixo2Calendar\\src'],\r
+ binaries=[],\r
+ datas=[('logo.ico', '.')],\r
+ hiddenimports=[],\r
+ hookspath=[],\r
+ runtime_hooks=[],\r
+ excludes=[],\r
+ win_no_prefer_redirects=False,\r
+ win_private_assemblies=False,\r
+ cipher=block_cipher,\r
+ noarchive=False)\r
+pyz = PYZ(a.pure, a.zipped_data,\r
+ cipher=block_cipher)\r
+exe = EXE(pyz,\r
+ a.scripts,\r
+ a.binaries,\r
+ a.zipfiles,\r
+ a.datas,\r
+ [],\r
+ name='Injixo2Calendar',\r
+ debug=False,\r
+ bootloader_ignore_signals=False,\r
+ strip=False,\r
+ upx=True,\r
+ upx_exclude=[],\r
+ runtime_tmpdir=None,\r
+ console=False , icon='logo.ico')\r
--- /dev/null
+pyinstaller --onefile --icon=logo.ico --noconsole --add-data "logo.ico;." Injixo2Calendar.py
\ No newline at end of file
--- /dev/null
+from __future__ import print_function\r
+import pickle\r
+import os.path\r
+from googleapiclient.discovery import build\r
+from google_auth_oauthlib.flow import InstalledAppFlow\r
+from google.auth.transport.requests import Request\r
+\r
+"""\r
+Resource to update Google Calendar using credentials.json in Working Directory.\r
+\r
+A token.pickle file will be created for permissions to Google Calendar.\r
+\r
+Author: Nils Forssén, Jämtland County, Sweden\r
+"""\r
+\r
+EVENT_COLORIDS = {\r
+ "blue": 1,\r
+ "green": 2,\r
+ "purple": 3,\r
+ "red": 4,\r
+ "yellow": 5,\r
+ "orange": 6,\r
+ "turquoise": 7,\r
+ "gray": 8,\r
+ "b_blue": 9,\r
+ "b_green": 10,\r
+ "b_red": 11\r
+}\r
+\r
+\r
+# Give accesss to complete Google Calendar\r
+SCOPES = ["https://www.googleapis.com/auth/calendar.events"]\r
+\r
+\r
+def getCredentials():\r
+ """\r
+ Get the current credentials from the pickle file,\r
+ If not available, create new file with credentials\r
+ """\r
+\r
+ creds = None\r
+ if os.path.exists("token.pickle"):\r
+ with open("token.pickle", "rb") as token:\r
+ creds = pickle.load(token)\r
+\r
+ if not creds or not creds.valid:\r
+ if creds and creds.expired and creds.refresh_token:\r
+ creds.refresh(Request())\r
+ else:\r
+ flow = InstalledAppFlow.from_client_secrets_file(\r
+ "credentials.json", SCOPES)\r
+ creds = flow.run_local_server(port=0)\r
+ with open("token.pickle", "wb") as token:\r
+ pickle.dump(creds, token)\r
+\r
+ return creds\r
+\r
+\r
+service = build('calendar', 'v3', credentials=getCredentials())\r
+\r
+\r
+def createEvent(event):\r
+ """\r
+ Create google calendar event using the standard event formatting\r
+ """\r
+\r
+ event = service.events().insert(calendarId="primary", body=event).execute()\r
+\r
+ return event\r
+\r
+\r
+def deleteEvent(eventId):\r
+ """\r
+ Delete event with given id from google calendar\r
+ """\r
+\r
+ service.events().delete(calendarId="primary", eventId=eventId).execute()\r
+\r
+\r
+def listEvents(**kwargs):\r
+ """\r
+ Return list of all google calendar events\r
+ """\r
+\r
+ events_result = service.events().list(calendarId="primary", singleEvents=True, orderBy='startTime', **kwargs).execute()\r
+\r
+ events = events_result.get('items', [])\r
+\r
+ return events\r
--- /dev/null
+{\r
+ "destinationDir": "C:\\Users\\forss\\Documents\\Python\\KeyLogger\\src\\dist",\r
+ "winX": 800,\r
+ "winY": 600,\r
+ "offsetX": 40,\r
+ "offsetY": 40,\r
+ "blackList": [],\r
+ "whiteList": [],\r
+ "imageFormats": [\r
+ "png"\r
+ ],\r
+ "imageDPI": 100\r
+}
\ No newline at end of file
--- /dev/null
+{\r
+ "destinationDir": "C:\\Users\\forss\\Documents\\Python\\KeyLogger\\Application",\r
+ "winX": 800,\r
+ "winY": 600,\r
+ "offsetX": 40,\r
+ "offsetY": 40,\r
+ "blackList": [],\r
+ "whiteList": [],\r
+ "imageFormats": [\r
+ "png"\r
+ ],\r
+ "imageDPI": 100\r
+}
\ No newline at end of file
--- /dev/null
+import tkinter as tk\r
+from Utilities import BGLogger, Graph\r
+from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg\r
+import os\r
+\r
+class GraphPage(tk.Frame):\r
+\r
+ def __init__(self, master, controller):\r
+\r
+ tk.Frame.__init__(self, master)\r
+\r
+ self.controller = controller\r
+ self.Logger = BGLogger.KeyboardLogger()\r
+ self.Grapher = Graph.BarGraph()\r
+\r
+ self.createWidgets()\r
+\r
+ def setLists(self):\r
+ """\r
+ Set black and white-list of keyboardlogger\r
+ """\r
+\r
+ # Set blackList, if empty, set instead whiteList\r
+ blackList, whiteList = self.controller.readSettings("blackList", "whiteList").values()\r
+ if blackList:\r
+ self.Logger.setBlackList(blackList)\r
+ elif whiteList:\r
+ self.Logger.setWhiteList(whiteList)\r
+\r
+\r
+ def createWidgets(self):\r
+ """\r
+ Create all the interactive widgets of the page\r
+ """\r
+\r
+ self.setLists()\r
+\r
+ self.rowconfigure((0,1,2), weight=1)\r
+ self.columnconfigure(1, weight=1)\r
+\r
+ def saveImg():\r
+ """\r
+ Save an image of the current graph\r
+ """\r
+\r
+ fTypes, dpi = self.controller.readSettings("imageFormats", "imageDPI").values()\r
+\r
+ # I know the following line isnt very practical but hey, who doesn't like a one-liner\r
+ fileTypeList = tuple(map(lambda f, t : tuple((s+t) for s in f), [("", "*.")]*len(fTypes), fTypes))\r
+\r
+ location = tk.filedialog.asksaveasfilename(\r
+ initialdir=self.controller.destinationDir,\r
+ title="save image",\r
+ defaultextension="png",\r
+ filetypes=fileTypeList)\r
+\r
+ name, ext = os.path.splitext(location)\r
+ if location:\r
+ self.Grapher.saveImg(location, format=ext.replace(".", ""), dpi=dpi)\r
+\r
+\r
+ self.keyLogButton = tk.Button(self,\r
+ text="Start logging", \r
+ background="green2",\r
+ activebackground="green2",\r
+ command=lambda : self.setToggleState(self.Logger.toggle()))\r
+ self.graphButton = tk.Button(self,\r
+ text="Update Graph",\r
+ background="yellow2",\r
+ activebackground="yellow2",\r
+ command=self.plotData)\r
+ self.saveImgButton = tk.Button(self,\r
+ text="Save Image",\r
+ background="royalblue1",\r
+ activebackground="royalblue1", \r
+ command=saveImg)\r
+\r
+ self.graphCanvas = FigureCanvasTkAgg(self.Grapher.figSetup(\r
+ title="Letter Frequency",\r
+ xlabel="Character",\r
+ ylabel="Percentage (%)",\r
+ size=(10, 6)), master= self)\r
+ \r
+\r
+ self.keyLogButton.grid(row=0, column=0, sticky="NSEW")\r
+ self.graphButton.grid(row=1, column=0 ,sticky="NSEW")\r
+ self.saveImgButton.grid(row=2, column=0, sticky="NSEW")\r
+\r
+ self.graphCanvas.get_tk_widget().grid(\r
+ row=0, rowspan=3, column=1, sticky="NSEW")\r
+\r
+\r
+ def setToggleState(self, default=None):\r
+ """\r
+ Toggle the state of the logging button. \r
+ This is needed as the logging can be toggled BGLogger stopButton (default f12).\r
+\r
+ """\r
+\r
+ toggleBool = default or self.Logger.logging\r
+\r
+ if toggleBool:\r
+\r
+ self.keyLogButton.config(\r
+ text="Stop logging",\r
+ relief="raised",\r
+ background="red2",\r
+ activebackground="red2")\r
+\r
+ # Check for any updates to the logging state\r
+ self.after(100, self.setToggleState)\r
+\r
+ else:\r
+\r
+ self.setLists()\r
+\r
+ self.keyLogButton.config(\r
+ text="Start logging",\r
+ relief="raised",\r
+ background="green2",\r
+ activebackground="green2")\r
+\r
+\r
+ def plotData(self, event=None):\r
+ """ \r
+ Plot the current log\r
+ """\r
+\r
+ self.Grapher.loadData(self.Logger.keyDict, mode="percent")\r
+ self.Grapher.plotData()\r
+ self.graphCanvas.draw()\r
+\r
+\r
+ def menuBar(self, root):\r
+ """\r
+ Return the menubar object of this page\r
+ """\r
+\r
+ def newLOG():\r
+ """\r
+ Flush the currently logged data\r
+ """\r
+\r
+ self.Logger.flush()\r
+ self.plotData()\r
+\r
+ def readLOGFile(path):\r
+ """\r
+ Read and return logged data from .log file at path\r
+ """\r
+\r
+ try:\r
+ with open(path, mode="r") as file:\r
+ newDict = {}\r
+ for line in file.readlines():\r
+ line = line.replace("'", "")\r
+ key, value = line.split(":")\r
+ newDict[key.strip()] = int(value.strip())\r
+ return newDict\r
+\r
+ except IOError:\r
+ print("Path not found")\r
+\r
+\r
+ def writeLOGFile(path, dataDict):\r
+ """\r
+ Write logged data to .log file at path\r
+ """\r
+\r
+ try:\r
+ with open(path, mode="w") as file:\r
+ for key in dataDict:\r
+ file.write("{}:{}\n".format(key, dataDict[key]))\r
+\r
+ except:\r
+ print("Unable to open file")\r
+\r
+\r
+ def loadLOGFile(replace=True):\r
+ """\r
+ Load logged data from .log file into page using readLOGFile()\r
+ """\r
+\r
+ if self.Logger.logging:\r
+ self.setToggleState(self.Logger.toggle())\r
+\r
+ filePath = tk.filedialog.askopenfilename(\r
+ initialdir=self.controller.destinationDir,\r
+ title="Select file",\r
+ filetypes=(("log files", "*.LOG"),))\r
+\r
+ self.Logger.keyDict = readLOGFile(filePath)\r
+ try:\r
+ self.plotData()\r
+ except AttributeError:\r
+ print("Unable to open file")\r
+\r
+\r
+ def saveNewLOGFile():\r
+ """\r
+ Save logged data into new .log file using writeLOGFile()\r
+ """\r
+\r
+ if self.Logger.logging:\r
+ self.setToggleState(self.Logger.toggle())\r
+\r
+ filePath = tk.filedialog.asksaveasfilename(\r
+ initialdir=self.controller.destinationDir,\r
+ defaultextension=".dat",\r
+ title="Create file",\r
+ filetypes=(("log file", "*.LOG"),))\r
+\r
+ writeLOGFile(filePath, self.Logger.keyDict)\r
+\r
+\r
+ def saveToLOGFile():\r
+ """\r
+ Save logged data into old .log file using writeLOGFile()\r
+ """\r
+\r
+ if self.Logger.logging:\r
+ self.setToggleState(self.Logger.toggle())\r
+\r
+ filePath = tk.filedialog.askopenfilename(\r
+ initialdir=self.controller.destinationDir, \r
+ title="Select file", \r
+ filetypes=(("log file", "*.LOG"),))\r
+\r
+ oldData = readLOGFile(filePath)\r
+ newData = self.Logger.keyDict\r
+ try:\r
+ for key in oldData:\r
+ if key in newData:\r
+ newData[key] += oldData[key]\r
+ else:\r
+ newData[key] = oldData[key]\r
+\r
+ writeLOGFile(filePath, self.Logger.keyDict)\r
+ except TypeError:\r
+ print("Unable to open file")\r
+\r
+\r
+\r
+ menu = tk.Menu(root)\r
+\r
+ filemenu = tk.Menu(menu, tearoff=0)\r
+ filemenu.add_command(label="New Log", command=newLOG)\r
+ filemenu.add_command(label="Open Log", command=loadLOGFile)\r
+ filemenu.add_command(label="Save As", command=saveNewLOGFile)\r
+ filemenu.add_command(label="Save To", command=saveToLOGFile)\r
+\r
+ filemenu.add_separator()\r
+ filemenu.add_command(label="Exit", command=self.controller.destroy)\r
+ menu.add_cascade(label="File", menu=filemenu)\r
+\r
+ def showSettings():\r
+ """\r
+ Stop logging if active and show settingsPage\r
+ """\r
+\r
+ if self.Logger.logging:\r
+ self.setToggleState(self.Logger.toggle())\r
+ self.controller.showFrame("SettingsPage")\r
+\r
+ menu.add_command(label="Settings",\r
+ command=showSettings)\r
+ menu.add_command(label="Exit", \r
+ command=self.controller.destroy)\r
+\r
+ return menu\r
--- /dev/null
+import tkinter as tk\r
+import os, string\r
+\r
+\r
+class SettingsPage(tk.Frame):\r
+ """\r
+ Page for chaging and saving the settings of the program\r
+ """\r
+\r
+ def __init__(self, master, controller):\r
+\r
+ tk.Frame.__init__(self, master)\r
+\r
+ self.controller = controller\r
+\r
+ self.createWidgets()\r
+\r
+\r
+ def createWidgets(self):\r
+ """\r
+ Create all the interactive widgets of the page\r
+ """\r
+\r
+ self.saveButton = tk.Button(self,\r
+ text="Save Settings",\r
+ background="green2", \r
+ activebackground="green2",\r
+ command=self.saveSettings)\r
+ self.discardButton = tk.Button(self,\r
+ text="Discard Changes", \r
+ background="red2",\r
+ activebackground="red2",\r
+ command=self.discardChanges)\r
+ self.defaultButton = tk.Button(self,\r
+ text="Default Settings", \r
+ background="royalblue2", \r
+ activebackground="royalblue2",\r
+ command=self.loadDefault)\r
+\r
+ self.entryText = tk.StringVar()\r
+ self.validateDirText = tk.StringVar()\r
+ self.blackListText = tk.StringVar()\r
+ self.blackList = tk.StringVar()\r
+ self.DPItext = tk.StringVar()\r
+ self.whiteListText = tk.StringVar()\r
+ self.whiteList = tk.StringVar()\r
+\r
+ self.pngBool = tk.BooleanVar()\r
+ self.pdfBool = tk.BooleanVar()\r
+ self.epsBool = tk.BooleanVar()\r
+ self.rawBool = tk.BooleanVar()\r
+\r
+ self.entryText.set(self.controller.destinationDir)\r
+ self.DPItext.set(self.controller.readSettings("imageDPI"))\r
+ self.blackList.set("".join(self.controller.readSettings("blackList")))\r
+ self.whiteList.set("".join(self.controller.readSettings("whiteList")))\r
+\r
+ self.pngBool.set("png" in self.controller.readSettings("imageFormats"))\r
+ self.pdfBool.set("pdf" in self.controller.readSettings("imageFormats"))\r
+ self.epsBool.set("eps" in self.controller.readSettings("imageFormats"))\r
+ self.rawBool.set("raw" in self.controller.readSettings("imageFormats"))\r
+\r
+ self.unsaved = lambda *args : self.saveButton.config(\r
+ background="yellow2",\r
+ activebackground="yellow2")\r
+\r
+ self.entryText.trace("w", callback=self.dirEntryCallback)\r
+ self.DPItext.trace("w", callback=self.unsaved)\r
+ self.pngBool.trace("w", callback=self.unsaved)\r
+ self.pdfBool.trace("w", callback=self.unsaved)\r
+ self.epsBool.trace("w", callback=self.unsaved)\r
+ self.rawBool.trace("w", callback=self.unsaved)\r
+ self.blackList.trace("w", callback=self.unsaved)\r
+ self.whiteList.trace("w", callback=self.unsaved)\r
+ \r
+ blackListHeader = tk.Label(self,\r
+ text="Blacklist Characters", \r
+ font="bold")\r
+ dirHeader = tk.Label(self, \r
+ text="Default Directory",\r
+ font="bold")\r
+ imageFormatHeader = tk.Label(self,\r
+ text="Image File Format", \r
+ font="bold")\r
+ whiteListHeader = tk.Label(self,\r
+ text="Whitelist characters", \r
+ font="bold")\r
+\r
+ imageDPIHeader = tk.Label(self,\r
+ text="DPI of image:")\r
+ blackListHeader2 = tk.Label(self,\r
+ text="Current blacklist:")\r
+ whiteListHeader2 = tk.Label(self,\r
+ text="Current whitelist:")\r
+\r
+ self.dirEntry = tk.Entry(self, textvariable=self.entryText)\r
+ self.blackListEntry = tk.Entry(self, textvariable=self.blackListText)\r
+ self.DPIEntry = tk.Entry(self, textvariable=self.DPItext)\r
+ self.whiteListEntry = tk.Entry(self, textvariable=self.whiteListText)\r
+\r
+ def concVars(headVar, *args):\r
+ """\r
+ Concatenates tk.StringVar headVar in place with given variables\r
+ """\r
+\r
+ text = headVar.get()\r
+ added = list(text)\r
+\r
+ for char in "".join(list(map(lambda var : var.get() , args))).upper():\r
+ if char in string.ascii_letters:\r
+\r
+ if char not in text and char not in added:\r
+ added.append(char)\r
+ elif char in text:\r
+ try:\r
+ added.remove(char)\r
+ except:\r
+ pass\r
+ else:\r
+ print("{0} is not a valid character".format(char))\r
+\r
+ headVar.set("".join(added))\r
+\r
+\r
+ def addToBlackList(*args):\r
+ """\r
+ Add entry-text to blackList\r
+ """\r
+\r
+ self.whiteList.set("")\r
+\r
+ concVars(*args)\r
+\r
+ self.blackListText.set("")\r
+\r
+ self.unsaved()\r
+\r
+\r
+ def addToWhiteList(*args):\r
+ """\r
+ Add entry-text to whiteList\r
+ """\r
+\r
+ self.blackList.set("")\r
+\r
+ concVars(*args)\r
+\r
+ self.whiteListText.set("")\r
+\r
+ self.unsaved()\r
+\r
+\r
+ self.browseButton = tk.Button(self,\r
+ text="Browse",\r
+ command=self.browseDirectory)\r
+ self.blackListButton = tk.Button(self, \r
+ text="Edit Entry",\r
+ command=lambda : addToBlackList(self.blackList, self.blackListText))\r
+ self.whiteListButton = tk.Button(self,\r
+ text="Edit Entry",\r
+ command=lambda : addToWhiteList(self.whiteList, self.whiteListText))\r
+\r
+ self.blackListEntry.bind("<Return>",\r
+ lambda e : addToBlackList(self.blackList, self.blackListText))\r
+ self.whiteListEntry.bind("<Return>",\r
+ lambda e : addToWhiteList(self.whiteList, self.whiteListText))\r
+\r
+ self.pngCheck = tk.Checkbutton(self,\r
+ text=".png",\r
+ variable=self.pngBool)\r
+ self.pdfCheck = tk.Checkbutton(self, \r
+ text=".pdf",\r
+ variable=self.pdfBool)\r
+ self.epsCheck = tk.Checkbutton(self, \r
+ text=".eps",\r
+ variable=self.epsBool)\r
+ self.rawCheck = tk.Checkbutton(self,\r
+ text=".raw", \r
+ variable=self.rawBool)\r
+\r
+ self.validateDirLabel = tk.Label(self,\r
+ textvariable=self.validateDirText)\r
+ self.blackListLabel = tk.Label(self,\r
+ textvariable=self.blackList)\r
+ self.whiteListLabel = tk.Label(self,\r
+ textvariable=self.whiteList)\r
+\r
+ dirHeader.grid(columnspan=3, column=0, row=0, sticky="W", padx=20)\r
+ blackListHeader.grid(columnspan=3, column=3, row=0, sticky="W", padx=20)\r
+ imageFormatHeader.grid(columnspan=3, column=0, row=3, sticky="W", padx=20)\r
+ whiteListHeader.grid(columnspan=3, column=3, row=4, sticky="W", padx=20)\r
+\r
+ imageDPIHeader.grid(column=1, row=5, sticky="W", padx=20)\r
+ blackListHeader2.grid(columnspan=3, column=3, row=2, sticky="W", padx=20)\r
+ whiteListHeader2.grid(columnspan=3, column=3, row=6, sticky="W", padx=20)\r
+\r
+ self.dirEntry.grid(columnspan=2, column=0, row=1, sticky="WE", padx=20)\r
+ self.blackListEntry.grid(columnspan=2, column=3, row=1, sticky="WE", padx=20)\r
+ self.DPIEntry.grid(column=1, row=6, sticky="WE", padx=20)\r
+ self.whiteListEntry.grid(columnspan=2, column=3, row=5, sticky="WE", padx=20)\r
+\r
+ self.pngCheck.grid(column=0, row=4, sticky="W", padx=20)\r
+ self.pdfCheck.grid(column=0, row=5, sticky="W", padx=20)\r
+ self.epsCheck.grid(column=0, row=6, sticky="W", padx=20)\r
+ self.rawCheck.grid(column=0, row=7, sticky="W", padx=20)\r
+\r
+ self.browseButton.grid(column=2, row=1, sticky="NSEW", padx=20)\r
+ self.blackListButton.grid(column=5, row=1, sticky="NSEW", padx=20)\r
+ self.whiteListButton.grid(column=5, row=5, sticky="NSEW", padx=20)\r
+\r
+ self.validateDirLabel.grid(columnspan=3, column=0, row=2, sticky="W", padx=20)\r
+ self.blackListLabel.grid(columnspan=3, column=3, row=3, sticky="W", padx=20)\r
+ self.whiteListLabel.grid(columnspan=3, column=3, row=7, sticky="W", padx=20)\r
+\r
+ self.saveButton.grid(columnspan=2, column=0, row=11, sticky="NSEW", padx=20)\r
+ self.discardButton.grid(columnspan=2, column=2, row=11, sticky="NSEW", padx=20)\r
+ self.defaultButton.grid(columnspan=2, column=4, row=11, sticky="NSEW", padx=20)\r
+\r
+\r
+ self.columnconfigure((0,1,2,3,4,5), weight=1, minsize=800/6)\r
+\r
+ self.rowconfigure(11, weight=1)\r
+\r
+\r
+ def dirEntryCallback(self, *args):\r
+ """\r
+ Check and alert if chosen directory is valid\r
+ """\r
+\r
+ if (os.path.exists(self.entryText.get())):\r
+ self.validateDirText.set("Valid Directory")\r
+ self.validateDirLabel.config(\r
+ foreground="green2")\r
+ else:\r
+ self.validateDirText.set("Invalid Directory")\r
+ self.validateDirLabel.config(\r
+ foreground="red2")\r
+\r
+ self.unsaved()\r
+\r
+\r
+ def browseDirectory(self):\r
+ """\r
+ Open filebrowser for user to chose destination folder\r
+ """\r
+ \r
+ path = tk.filedialog.askdirectory(\r
+ title="Choose a directory",\r
+ initialdir=self.controller.destinationDir).replace("/", "\\")\r
+\r
+ if path:\r
+ self.saveButton.config(\r
+ background="yellow2",\r
+ activebackground="yellow2")\r
+\r
+ self.entryText.set(path)\r
+\r
+\r
+ def saveSettings(self):\r
+ """\r
+ Save changes in this page into the config file\r
+ """\r
+\r
+ newSettings = {}\r
+\r
+ if (os.path.exists(self.entryText.get())):\r
+ newSettings["destinationDir"] = self.entryText.get()\r
+\r
+ formats = {\r
+ "png" : self.pngBool.get(),\r
+ "pdf" : self.pdfBool.get(),\r
+ "eps" : self.epsBool.get(),\r
+ "raw" : self.rawBool.get()\r
+ }\r
+ newSettings["imageFormats"] = [key for key, value in formats.items() if value]\r
+\r
+ dpi = int(self.DPItext.get())\r
+\r
+ if dpi > 0:\r
+ newSettings["imageDPI"] = dpi\r
+\r
+ newSettings["blackList"] = list(self.blackList.get())\r
+ newSettings["whiteList"] = list(self.whiteList.get())\r
+\r
+ self.controller.changeSettings(**newSettings)\r
+\r
+ self.saveButton.config(\r
+ background="green2",\r
+ activebackground="green2")\r
+\r
+\r
+ def discardChanges(self):\r
+ """\r
+ Discard the changes in this page by refreshing it, reloading the config file\r
+ """\r
+\r
+ self.controller.loadSettings()\r
+ self.controller.refreshFrame("SettingsPage")\r
+\r
+\r
+ def loadDefault(self):\r
+ """\r
+ Load the default settings from the default config file into the config file and this page\r
+ """\r
+\r
+ with open("defaultConfig.json", "r") as default, open("config.json", "w") as cfg:\r
+ cfg.write(default.read())\r
+\r
+ cfg.close()\r
+ default.close()\r
+\r
+ self.controller.loadSettings()\r
+ self.controller.refreshFrame("SettingsPage")\r
+\r
+\r
+ def menuBar(self, root):\r
+ """\r
+ Return the menubar object of this page\r
+ """\r
+\r
+ menu = tk.Menu(root)\r
+\r
+ menu.add_command(label="Graph", \r
+ command=lambda : self.controller.refreshFrame("GraphPage"))\r
+\r
+ menu.add_command(label="Exit",\r
+ command=self.controller.destroy)\r
+\r
+ return menu
\ No newline at end of file
--- /dev/null
+import tkinter as tk\r
+import os, json, sys\r
+from Pages import GraphPage, SettingsPage\r
+import pkg_resources.py2_warn\r
+\r
+class Root(tk.Tk):\r
+ """\r
+ Root class of tkinter application\r
+ """\r
+\r
+ def __init__(self):\r
+\r
+ tk.Tk.__init__(self)\r
+\r
+ # Also kill the thread created by BGLogger\r
+ self.protocol("WM_DELETE_WINDOW", sys.exit)\r
+\r
+ self.loadSettings()\r
+\r
+ self.container = tk.Frame(self)\r
+ self.container.pack(fill="both", expand=True)\r
+ self.container.grid_rowconfigure(0, weight=1)\r
+ self.container.grid_columnconfigure(0, weight=1)\r
+ self.frames = {}\r
+\r
+ for F in (GraphPage.GraphPage, SettingsPage.SettingsPage):\r
+ pageName = F.__name__\r
+ frame = F(master=self.container, controller=self)\r
+ self.frames[pageName] = frame\r
+ frame.grid(row=0, column=0, sticky="NSEW")\r
+\r
+ with open("defaultConfig.json", "r") as cfg:\r
+ settings = json.load(cfg)\r
+ settings["destinationDir"] = os.path.dirname(os.path.abspath(__file__))\r
+\r
+ with open("defaultConfig.json", "w") as cfg:\r
+ json.dump(settings, cfg, indent=4)\r
+\r
+ self.showFrame("GraphPage")\r
+\r
+\r
+ def loadSettings(self):\r
+ """\r
+ Load the application wide settings\r
+ """\r
+\r
+ with open("config.json", "r") as cfg:\r
+ settings = json.load(cfg)\r
+\r
+ with open("config.json", "w") as cfg:\r
+ json.dump(settings, cfg, indent=4)\r
+\r
+ self.thisDir = os.path.dirname(os.path.abspath(__file__))\r
+\r
+ if not settings["destinationDir"]:\r
+ self.destiantionDir = self.changeSettings(destinationDir=self.thisDir)\r
+\r
+ self.destinationDir = settings["destinationDir"]\r
+\r
+ self.iconbitmap(self.resource_path("icon.ico"))\r
+ self.title("Keylogger")\r
+ self.resizable(False, False)\r
+ self.geometry(newGeometry="{0}x{1}+{2}+{3}".format(\r
+ settings["winX"],\r
+ settings["winY"],\r
+ settings["offsetX"],\r
+ settings["offsetY"]))\r
+\r
+\r
+ def readSettings(self, *args):\r
+ """\r
+ Returns values of the given settings\r
+ """\r
+\r
+ with open("config.json", "r") as cfg:\r
+ settings = json.load(cfg)\r
+\r
+ if len(args) - 1:\r
+ return {key:settings[key] for key in args}\r
+ else:\r
+ return settings[args[0]]\r
+\r
+\r
+ def changeSettings(self, **kwargs):\r
+ """\r
+ Change given settings\r
+ """\r
+\r
+ with open("config.json", "r") as cfg:\r
+ settings = json.load(cfg)\r
+ for setting, value in kwargs.items():\r
+ settings[setting] = value\r
+\r
+ with open("config.json", "w") as cfg:\r
+ json.dump(settings, cfg, indent=4)\r
+\r
+ self.loadSettings()\r
+\r
+ return kwargs.values()\r
+\r
+\r
+ def showFrame(self, pageName):\r
+ """\r
+ Raise frame of given page name\r
+ """\r
+\r
+ frame = self.frames[pageName]\r
+ menuBar = frame.menuBar(self)\r
+ self.config(menu=menuBar)\r
+ frame.tkraise()\r
+\r
+\r
+ def refreshFrame(self, pageName):\r
+ """\r
+ Re-initilizes given frame\r
+ """\r
+\r
+ frame = self.frames[pageName]\r
+\r
+ frame.grid_forget()\r
+ \r
+ self.frames[pageName].__init__(\r
+ master=self.container, \r
+ controller=self)\r
+\r
+ self.frames[pageName].grid(row=0, column=0, sticky="NSEW")\r
+\r
+ menuBar = self.frames[pageName].menuBar(self)\r
+ self.config(menu=menuBar)\r
+ self.frames[pageName].tkraise()\r
+\r
+\r
+ def resource_path(self, relative_path):\r
+ """\r
+ Get pyinstaller resource\r
+ """\r
+\r
+ if hasattr(sys, '_MEIPASS'):\r
+ return os.path.join(sys._MEIPASS, relative_path)\r
+\r
+ return os.path.join(os.path.abspath("."), relative_path)\r
+\r
+\r
+if __name__ == "__main__":\r
+ app = Root()\r
+ app.mainloop()\r
+
\ No newline at end of file
--- /dev/null
+# -*- mode: python ; coding: utf-8 -*-\r
+\r
+block_cipher = None\r
+\r
+\r
+a = Analysis(['Root.py'],\r
+ pathex=['C:\\Users\\forss\\Documents\\Python\\KeyLogger\\src'],\r
+ binaries=[],\r
+ datas=[('icon.ico', '.')],\r
+ hiddenimports=[],\r
+ hookspath=[],\r
+ runtime_hooks=[],\r
+ excludes=[],\r
+ win_no_prefer_redirects=False,\r
+ win_private_assemblies=False,\r
+ cipher=block_cipher,\r
+ noarchive=False)\r
+pyz = PYZ(a.pure, a.zipped_data,\r
+ cipher=block_cipher)\r
+exe = EXE(pyz,\r
+ a.scripts,\r
+ a.binaries,\r
+ a.zipfiles,\r
+ a.datas,\r
+ [],\r
+ name='Root',\r
+ debug=False,\r
+ bootloader_ignore_signals=False,\r
+ strip=False,\r
+ upx=True,\r
+ upx_exclude=[],\r
+ runtime_tmpdir=None,\r
+ console=False , icon='icon.ico')\r
--- /dev/null
+from pynput.keyboard import Key, Listener, Controller\r
+import string\r
+\r
+class KeyboardLogger():\r
+ """\r
+ Class for logging the keyboard in another thread\r
+ Key presses are stored in dictinoary keyDict\r
+ """\r
+\r
+ def __init__(self, stop=Key.f12):\r
+\r
+ self.logging = False\r
+ self.stopButton = stop\r
+\r
+ self.keyDict = {}\r
+ self.wordDict = {}\r
+ self.allowed = string.ascii_uppercase\r
+\r
+ self.blackList = []\r
+ self.whiteList = []\r
+ \r
+\r
+ def setBlackList(self, blist):\r
+ """\r
+ Set black-list to string of characters and empties white-list.\r
+ Returns new black-list\r
+ """\r
+\r
+ self.blackList = blist\r
+ self.whiteList.clear()\r
+\r
+ return self.blackList\r
+\r
+\r
+ def setWhiteList(self, wlist):\r
+ """\r
+ Set white-list to string of characters and empties black-list.\r
+ Returns new white-list\r
+ """\r
+\r
+ self.whiteList = wlist\r
+ self.blackList.clear()\r
+\r
+ return self.whiteList\r
+\r
+\r
+ def getBlackList(self):\r
+ """\r
+ Get string of current black-list\r
+ """\r
+\r
+ return self.blackList\r
+\r
+\r
+ def getWhiteList(self):\r
+ """\r
+ Get string of current white-list\r
+ """\r
+\r
+ return self.whiteList\r
+\r
+\r
+ def addSpecialChars(self, charList):\r
+ """\r
+ Add a list of characters to be logged.\r
+ Characters of modifiers are not tracked.\r
+ """\r
+\r
+ self.allowed += "".join(charList)\r
+ return self.allowed\r
+\r
+\r
+ def on_press(self, key):\r
+ """\r
+ Add the given keypress to the dictionary if the key is not some arbitrary symbol.\r
+ """\r
+\r
+ if self.checkStop(key):\r
+ try:\r
+ upperKey = key.char.upper()\r
+ if upperKey in self.allowed:\r
+ if (not self.blackList and not self.whiteList) or ((self.blackList and upperKey not in self.blackList) or (self.whiteList and upperKey in self.whiteList)):\r
+ print(upperKey)\r
+ if upperKey in self.keyDict:\r
+ self.keyDict[upperKey] += 1\r
+ else:\r
+ self.keyDict[upperKey] = 1\r
+ except:\r
+ pass\r
+ \r
+ \r
+\r
+ def checkStop(self, key):\r
+ """\r
+ Return if the given key is the stopkey.\r
+ If True, this will also stop the logging.\r
+ """\r
+\r
+ if key is self.stopButton:\r
+ self.toggle()\r
+ return self.logging\r
+\r
+\r
+ def toggle(self):\r
+ """\r
+ Toggle the logging\r
+ """\r
+\r
+ if self.logging:\r
+ print("Logging Stopped")\r
+ self.logging = False\r
+ self.keyListener.stop()\r
+ else:\r
+ print("Logging Started")\r
+ self.logging = True\r
+ self.run()\r
+ return self.logging\r
+\r
+\r
+ def flush(self):\r
+ """\r
+ Flush the data-dictionaries\r
+ """\r
+\r
+ self.keyDict = {}\r
+ self.wordDict = {}\r
+\r
+\r
+ def run(self):\r
+ """\r
+ Runs the keyListener\r
+ """\r
+\r
+ self.keyListener = Listener(on_press=self.on_press)\r
+ self.keyListener.start()\r
+\r
+ def stop(self):\r
+ """\r
+ Stop logger\r
+ """\r
+ self.keyListener.stop()\r
+\r
+\r
+if __name__ == "__main__":\r
+ x = KeyboardLogger()\r
+ x.toggle()\r
+\r
--- /dev/null
+from matplotlib.figure import Figure\r
+from matplotlib import rcParams\r
+import matplotlib.pyplot as plt\r
+import numpy as np\r
+\r
+rcParams["axes.titlesize"] = 17\r
+\r
+class BarGraph():\r
+ """\r
+ Class for creating a bar-graph from a data-dict\r
+ """\r
+\r
+ def __init__(self, dataDir={}):\r
+\r
+ self.oldData = {}\r
+ self.loadData(dataDir)\r
+ self.yTicksDefault = [0,10,20,30,40,50,60,70,80,90,100]\r
+ \r
+ def loadData(self, newData, mode="values"):\r
+ """\r
+ Load the data into x and y axis,\r
+ mode=values (default) displays the bar chart with loaded data\r
+ mode=percent displays the bar chart y axis in percent of total\r
+ """\r
+ newData = dict(sorted(newData.items(),\r
+ key=lambda nD:(nD[1], nD[0]), reverse=True))\r
+\r
+ self.xList = [x for x in newData]\r
+\r
+ if mode == "values":\r
+ self.yList = [newData[x] for x in newData]\r
+\r
+ elif mode == "percent":\r
+ total = 0\r
+ for x in newData:\r
+ total += newData[x]\r
+ self.yList = [newData[x]*(100/total) for x in newData]\r
+\r
+\r
+ def figSetup(self,title, xlabel="X", ylabel="Y" , size=(5,5)):\r
+ """\r
+ Setup and return the figure used when plotting the dictionary.\r
+ This figure can be displayed in tkinter.\r
+ """\r
+\r
+ self.fig, self.ax = plt.subplots(figsize=size)\r
+ self.labels = (xlabel, ylabel)\r
+ self.title = title\r
+ self.setLimits()\r
+\r
+ return self.fig\r
+\r
+\r
+ def setLimits(self, yTicks=None):\r
+ """\r
+ Set x,y limits and ticks of figure\r
+ """\r
+\r
+ yTicks = yTicks or self.yTicksDefault\r
+ plt.xticks(np.arange(len(self.xList)), labels=self.xList)\r
+ plt.yticks(yTicks)\r
+\r
+ plt.ylim(bottom=0, top=max(yTicks) * 1.1)\r
+ plt.ylabel(self.labels[1])\r
+ plt.xlabel(self.labels[0])\r
+ plt.title(self.title, )\r
+\r
+ \r
+ def plotData(self):\r
+ """\r
+ Plot the loaded data in the figure\r
+ """\r
+\r
+ plt.clf()\r
+ try:\r
+ yLimits = [y + 10 for y in range(int(max(self.yList))) if y % 10 == 0]\r
+ yLimits.append(0)\r
+ \r
+ except ValueError:\r
+ yLimits = []\r
+\r
+ \r
+ self.setLimits(yTicks=yLimits)\r
+\r
+ rects = plt.bar(np.arange(len(self.xList)), \r
+ self.yList,\r
+ align="center")\r
+\r
+ for rect in rects:\r
+ height = rect.get_height()\r
+ plt.text(rect.get_x() + rect.get_width()/2,\r
+ 1.01*height, \r
+ str(int(round(height))),\r
+ ha='center', va='bottom')\r
+\r
+\r
+ def saveImg(self,*args, **kwargs):\r
+ """\r
+ Save an image of the figure\r
+ """\r
+\r
+ plt.savefig(*args, **kwargs)\r
+\r
+\r
+if __name__ == "__main__":\r
+ Grapher = BarGraph()
\ No newline at end of file
--- /dev/null
+{\r
+ "destinationDir": "C:\\Users\\forss\\Documents\\Python\\KeyLogger\\Application",\r
+ "winX": 800,\r
+ "winY": 600,\r
+ "offsetX": 40,\r
+ "offsetY": 40,\r
+ "blackList": [],\r
+ "whiteList": [],\r
+ "imageFormats": [\r
+ "png"\r
+ ],\r
+ "imageDPI": 100\r
+}
\ No newline at end of file
--- /dev/null
+{\r
+ "destinationDir": "C:\\Users\\forss\\Documents\\Python\\KeyLogger\\src",\r
+ "winX": 800,\r
+ "winY": 600,\r
+ "offsetX": 40,\r
+ "offsetY": 40,\r
+ "blackList": [],\r
+ "whiteList": [],\r
+ "imageFormats": [\r
+ "png"\r
+ ],\r
+ "imageDPI": 100\r
+}
\ No newline at end of file
--- /dev/null
+{\r
+ "destinationDir": "C:\\Users\\forss\\Documents\\Python\\KeyLogger\\src\\dist",\r
+ "winX": 800,\r
+ "winY": 600,\r
+ "offsetX": 40,\r
+ "offsetY": 40,\r
+ "blackList": [],\r
+ "whiteList": [],\r
+ "imageFormats": [\r
+ "png"\r
+ ],\r
+ "imageDPI": 100\r
+}
\ No newline at end of file
--- /dev/null
+{\r
+ "destinationDir": "C:\\Users\\forss\\Documents\\Python\\KeyLogger\\src\\dist",\r
+ "winX": 800,\r
+ "winY": 600,\r
+ "offsetX": 40,\r
+ "offsetY": 40,\r
+ "blackList": [],\r
+ "whiteList": [],\r
+ "imageFormats": [\r
+ "png"\r
+ ],\r
+ "imageDPI": 100\r
+}
\ No newline at end of file
--- /dev/null
+from pynput import keyboard\r
+\r
+def on_press(key):\r
+ try:\r
+ print('alphanumeric key {0} pressed'.format(\r
+ key.char))\r
+ except AttributeError:\r
+ print('special key {0} pressed'.format(\r
+ key))\r
+\r
+def on_release(key):\r
+ print('{0} released'.format(\r
+ key))\r
+ if key == keyboard.Key.esc:\r
+ # Stop listener\r
+ return False\r
+\r
+# Collect events until released\r
+with keyboard.Listener(\r
+ on_press=on_press,\r
+ on_release=on_release) as listener:\r
+ listener.join()
\ No newline at end of file
--- /dev/null
+import tkinter as tk\r
+\r
+root = tk.Tk()\r
+for row, text in enumerate((\r
+ "Hello", "short", "All the buttons are not the same size",\r
+ "Options", "Test2", "ABC", "This button is so much larger")):\r
+ button = tk.Button(root, text=text)\r
+ button.grid(row=row, column=row, sticky="ew")\r
+\r
+root.mainloop()
\ No newline at end of file
--- /dev/null
+import random\r
+game = True\r
+while game:\r
+ userInput = input("Ask a question: ")\r
+ if (userInput == ""):\r
+ pass\r
+ else:\r
+ x = random.randint(0,5)\r
+ if (x==0):\r
+ print ("I hope so")\r
+ if (x==1):\r
+ print ("If youre lucky")\r
+ if (x==2):\r
+ print ("For Sure!")\r
+ if (x==3):\r
+ print ("I dont know")\r
+ if (x==4):\r
+ print ("I dont like your options")\r
+ if (x==5):\r
+ print ("Yes, if you did your daily prayer today.")\r
+ print ("")\r
+\r
--- /dev/null
+import random\r
+game = True\r
+while game:\r
+ detect = True\r
+ guess = True\r
+ randomnumber = random.randint (1,6)\r
+ while detect:\r
+ try:\r
+ answer = int(input("Whats the magic number?: "))\r
+ detect = False\r
+ except ValueError:\r
+ print ("please type in an integer: ")\r
+ while guess:\r
+ if (answer==randomnumber):\r
+ print ("correct")\r
+ guess = False\r
+ else:\r
+ print ("incorrect")\r
+ guess = False\r
+ yesorno = str(input ("Do you want to play again?: "))\r
+ yes = "yes"\r
+ if (yesorno==yes):\r
+ game = True\r
+ else:\r
+ game = False\r
+ \r
+ \r
+ \r
--- /dev/null
+import random\r
+game = True\r
+again = 0\r
+while game:\r
+ countertotal = 0\r
+ loop1 = True\r
+ num1 = random.randint (1,10)\r
+ num2 = random.randint (1,10)\r
+ num3 = random.randint (1,10)\r
+ answer = 0\r
+ inputanswer = 0\r
+ rawinput = 0\r
+ key = 0\r
+ counter = 0\r
+ keyanswer = 0\r
+ print ("Welcome to level 1")\r
+ while loop1:\r
+ print ("what is the solution to",num1,"+",num2)\r
+ int (num1)\r
+ int (num2)\r
+ answer = num1 + num2\r
+ rawinput = input ("Answer: ")\r
+ inputanswer = int (rawinput)\r
+ if inputanswer == answer:\r
+ print ("Correct")\r
+ loop1 = False\r
+ loop2 = True\r
+ countertotal = countertotal + int (1)\r
+ print ("Your points so far: ",countertotal)\r
+ else:\r
+ counter = counter + int (1)\r
+ print ("Incorrect")\r
+ loop1 = True\r
+ loop2 = False\r
+ if counter >= 3:\r
+ key = input ("wish to progress, press 1. If not, press 0: ")\r
+ keyanswer = int (key)\r
+ if keyanswer == 1:\r
+ loop1 = False\r
+ loop2 = True\r
+ else:\r
+ loop2 = False\r
+ loop1 = True\r
+ num1 = random.randint (1,10)\r
+ num2 = random.randint (1,10)\r
+ num3 = random.randint (1,10)\r
+ answer = 0\r
+ inputanswer = 0\r
+ rawinput = 0\r
+ key = 0\r
+ counter = 0\r
+ keyanswer = 0\r
+ print ("Welcome to level 2")\r
+ while loop2:\r
+ print ("what is the solution to",num1,"+",num2, "+",num3)\r
+ int (num1)\r
+ int (num2)\r
+ int (num3)\r
+ answer = num1 + num2 + num3\r
+ rawinput = input("Answer: ")\r
+ inputanswer = int (rawinput)\r
+ if inputanswer == answer:\r
+ print ("Correct")\r
+ loop2 = False\r
+ loop3 = True\r
+ countertotal = countertotal + int (1)\r
+ print ("your points so far: ",countertotal)\r
+ else:\r
+ counter = counter + int (1)\r
+ print ("Incorrect")\r
+ loop2 = True\r
+ loop3 = False\r
+ if counter >= 3:\r
+ key = input ("wish to progress, press 1. If not, press 0: ")\r
+ keyanswer = int (key)\r
+ if keyanswer == 1:\r
+ loop2 = False\r
+ loop3 = True\r
+ else:\r
+ loop2 = True\r
+ loop3 = False\r
+ num1 = random.randint (1,10)\r
+ num2 = random.randint (1,10)\r
+ num3 = random.randint (1,10)\r
+ answer = 0\r
+ inputanswer = 0\r
+ rawinput = 0\r
+ key = 0\r
+ counter = 0\r
+ keyanswer = 0\r
+ print ("Welcome to level 3")\r
+ while loop3:\r
+ print ("what is the solution to",num1,"*",num2)\r
+ int (num1)\r
+ int (num2)\r
+ answer = num1 * num2\r
+ rawinput = input ("Answer: ")\r
+ inputanswer = int (rawinput)\r
+ if inputanswer == answer:\r
+ print ("Correct")\r
+ loop3 = False\r
+ loop4 = True\r
+ countertotal = countertotal + int (1)\r
+ print ("your points so far: ",countertotal)\r
+ else:\r
+ counter = counter + int (1)\r
+ print ("Incorrect")\r
+ loop3 = True\r
+ loop4 = False\r
+ if counter >= 3:\r
+ key = input ("wish to progress, press 1. If not, press 0: ")\r
+ keyanswer = int (key)\r
+ if keyanswer == 1:\r
+ loop3 = False\r
+ loop4 = True\r
+ else:\r
+ loop3 = True\r
+ loop4 = False\r
+ num1 = random.randint (1,10)\r
+ num2 = random.randint (1,10)\r
+ num3 = random.randint (1,10)\r
+ answer = 0\r
+ inputanswer = 0\r
+ rawinput = 0\r
+ key = 0\r
+ counter = 0\r
+ keyanswer = 0\r
+ print ("Welcome to level 4")\r
+ while loop4:\r
+ print ("what is the solution to",num1,"+",num2, "*",num3)\r
+ int (num1)\r
+ int (num2)\r
+ int (num3)\r
+ answer = num1 + num2 * num3\r
+ rawinput = input ("Answer: ")\r
+ inputanswer = int (rawinput)\r
+ if inputanswer == answer:\r
+ print ("Correct")\r
+ loop4 = False\r
+ loop5 = True\r
+ countertotal = countertotal + int (1)\r
+ print ("your points so far: ",countertotal)\r
+ else:\r
+ counter = counter + int (1)\r
+ print ("Incorrect")\r
+ loop4 = True\r
+ loop5 = False\r
+ if counter >= 3:\r
+ key = input ("wish to progress, press 1. If not, press 0: ")\r
+ keyanswer = int (key)\r
+ if keyanswer == 1:\r
+ loop4 = False\r
+ loop5 = True\r
+ else:\r
+ loop2 = True\r
+ loop3 = False\r
+ num1 = random.randint (1,10)\r
+ num2 = random.randint (1,10)\r
+ num3 = random.randint (1,10)\r
+ answer = 0\r
+ inputanswer = 0\r
+ rawinput = 0\r
+ key = 0\r
+ counter = 0\r
+ keyanswer = 0\r
+ print ("Welcome to level 5")\r
+ while loop5:\r
+ print ("what is the solution to",num1,"*",num2, "*",num3)\r
+ int (num1)\r
+ int (num2)\r
+ int (num3)\r
+ answer = num1 * num2 * num3\r
+ rawinput = input ("Answer: ")\r
+ inputanswer = int (rawinput)\r
+ if inputanswer == answer:\r
+ print ("Correct")\r
+ loop5 = False\r
+ loop6 = True\r
+ countertotal = countertotal + int (1)\r
+ else:\r
+ counter = counter + int (1)\r
+ print ("Incorrect")\r
+ loop5 = True\r
+ loop6 = False\r
+ if counter >= 3:\r
+ key = input ("wish to progress, press 1. If not, press 0: ")\r
+ keyanswer = int (key)\r
+ if keyanswer == 1:\r
+ loop5 = False\r
+ else:\r
+ loop5 = True\r
+ print ("Well done! You earned a score of ",countertotal," out of 5.")\r
+ again = input ("If you want to play again, press 1. If not, press 0: ")\r
+ again1 = int (again)\r
+ if again1 == 1:\r
+ continue\r
+ else:\r
+ game = False\r
+ \r
+ \r
+ \r
--- /dev/null
+import pygame\r
+import sys\r
+import time\r
+pygame.init()\r
+pygame.font.init()\r
+\r
+BLACK = (0,0,0)\r
+RED = (255,0,0)\r
+GREEN = (0,255,0)\r
+BLUE = (0,0,255)\r
+YELLOW = (255,255,0)\r
+TURQUOISE = (0,255,255)\r
+PURPLE = (255,0,255)\r
+GREY = (128,128,128)\r
+WHITE = (255,255,255)\r
+\r
+screenW = 800\r
+screenH = 600\r
+menyW = screenW\r
+menyH = 100\r
+length = 40\r
+height = 40\r
+\r
+blackPosX = (menyW/9)-(length/2)\r
+blackPosY = (menyH/4)*3-(height/2)\r
+redPosX = (menyW/9)*2-(length/2)\r
+redPosY = ((menyH/4)*3)-(height/2)\r
+greenPosX = (menyW/9)*3-(length/2)\r
+greenPosY = (menyH/4)*3-(height/2)\r
+bluePosX = (menyW/9)*4-(length/2)\r
+bluePosY = (menyH/4)*3-(height/2)\r
+yellowPosX = (menyW/9)*5-(length/2)\r
+yellowPosY = (menyH/4)*3-(height/2)\r
+turquoisePosX = (menyW/9)*6-(length/2)\r
+turquoisePosY = (menyH/4)*3-(height/2)\r
+purplePosX = (menyW/9)*7-(length/2)\r
+purplePosY = (menyH/4)*3-(height/2)\r
+whitePosX = (menyW/9)*8-(length/2)\r
+whitePosY = (menyH/4)*3-(height/2)\r
+\r
+font = pygame.font.Font(None, 40)\r
+pygame.key.set_repeat(1,200)\r
+clock = pygame.time.Clock()\r
+FPS = 500\r
+\r
+newX = 0\r
+newY = 0\r
+oldX = 0\r
+oldY = 0\r
+size = 10\r
+colour = BLACK\r
+labelX = 0\r
+labelY = 0\r
+\r
+screen = pygame.display.set_mode((screenW,screenH),0)\r
+pygame.display.set_caption("MS Paint")\r
+screen.fill(WHITE)\r
+pygame.draw.rect(screen,GREY,(0,0,menyW,menyH),0)\r
+\r
+pygame.draw.rect(screen,BLACK,(blackPosX,blackPosY,length,height),0)\r
+pygame.draw.rect(screen,RED,(redPosX,redPosY,length,height),0)\r
+pygame.draw.rect(screen,GREEN,(greenPosX,greenPosY,length,height),0)\r
+pygame.draw.rect(screen,BLUE,(bluePosX,bluePosY,length,height),0)\r
+pygame.draw.rect(screen,YELLOW,(yellowPosX,yellowPosY,length,height),0)\r
+pygame.draw.rect(screen,TURQUOISE,(turquoisePosX,turquoisePosY,length,height),0)\r
+pygame.draw.rect(screen,PURPLE,(purplePosX,purplePosY,length,height),0)\r
+pygame.draw.polygon(screen,WHITE,((whitePosX,whitePosY),length,height),0)\r
+\r
+pygame.draw.polygon(screen,BLACK,((blackPosX+(length/2),(menyH/2)),((blackPosX+(length/2))+10,(menyH/2)-10),((blackPosX+(length/2)-10),(menyH/2)-10)))\r
+\r
+main = True\r
+while main:\r
+ if (size == 0):\r
+ size = 1\r
+ elif (size > 50): \r
+ size = 50\r
+ sizeLabel = font.render("Brush Size: "+str(size),1,BLACK) \r
+ screen.blit(sizeLabel,(labelX,labelY))\r
+ mouseState = pygame.mouse.get_pressed()\r
+ newX,newY = pygame.mouse.get_pos()\r
+ if (mouseState == (True,False,False)):\r
+ pygame.draw.circle(screen,colour,(newX,newY),size,0)\r
+ pygame.draw.line(screen,colour,(oldX,oldY),(newX,newY),((size*2)+3))\r
+ oldX,oldY = newX,newY\r
+ if (newY <= (menyH+size)):\r
+ pygame.draw.rect(screen,GREY,(0,0,menyW,menyH),0)\r
+ pygame.draw.rect(screen,BLACK,(blackPosX,blackPosY,length,height),0)\r
+ pygame.draw.rect(screen,RED,(redPosX,redPosY,length,height),0)\r
+ pygame.draw.rect(screen,GREEN,(greenPosX,greenPosY,length,height),0)\r
+ pygame.draw.rect(screen,BLUE,(bluePosX,bluePosY,length,height),0)\r
+ pygame.draw.rect(screen,YELLOW,(yellowPosX,yellowPosY,length,height),0)\r
+ pygame.draw.rect(screen,TURQUOISE,(turquoisePosX,turquoisePosY,length,height),0)\r
+ pygame.draw.rect(screen,PURPLE,(purplePosX,purplePosY,length,height),0)\r
+ pygame.draw.rect(screen,WHITE,(whitePosX,whitePosY,length,height),0)\r
+ sizeLabel = font.render("Brush Size: "+str(size),1,BLACK) \r
+ screen.blit(sizeLabel,(labelX,labelY))\r
+ if (newX >= blackPosX) and (newX <= (blackPosX+length) and (newY >= blackPosY) and (newY <= (blackPosY+height)) and (mouseState == (True,False,False))):\r
+ colour = BLACK\r
+ pygame.draw.polygon(screen,BLACK,((blackPosX+(length/2),(menyH/2)),((blackPosX+(length/2))+10,(menyH/2)-10),((blackPosX+(length/2)-10),(menyH/2)-10)))\r
+ elif (newX >= redPosX) and (newX <= (redPosX+length) and (newY >= redPosY) and (newY <= (redPosY+height)) and (mouseState == (True,False,False))):\r
+ colour = RED\r
+ pygame.draw.polygon(screen,BLACK,((redPosX+(length/2),(menyH/2)),((redPosX+(length/2))+10,(menyH/2)-10),((redPosX+(length/2)-10),(menyH/2)-10)))\r
+ elif (newX >= greenPosX) and (newX <= (greenPosX+length) and (newY >= greenPosY) and (newY <= (greenPosY+height)) and (mouseState == (True,False,False))):\r
+ colour = GREEN\r
+ pygame.draw.polygon(screen,BLACK,((greenPosX+(length/2),(menyH/2)),((greenPosX+(length/2))+10,(menyH/2)-10),((greenPosX+(length/2)-10),(menyH/2)-10)))\r
+ elif (newX >= bluePosX) and (newX <= (bluePosX+length) and (newY >= bluePosY) and (newY <= (bluePosY+height)) and (mouseState == (True,False,False))):\r
+ colour = BLUE\r
+ pygame.draw.polygon(screen,BLACK,((bluePosX+(length/2),(menyH/2)),((bluePosX+(length/2))+10,(menyH/2)-10),((bluePosX+(length/2)-10),(menyH/2)-10)))\r
+ elif (newX >= yellowPosX) and (newX <= (yellowPosX+length) and (newY >= yellowPosY) and (newY <= (yellowPosY+height)) and (mouseState == (True,False,False))):\r
+ colour = YELLOW\r
+ pygame.draw.polygon(screen,BLACK,((yellowPosX+(length/2),(menyH/2)),((yellowPosX+(length/2))+10,(menyH/2)-10),((yellowPosX+(length/2)-10),(menyH/2)-10)))\r
+ elif (newX >= turquoisePosX) and (newX <= (turquoisePosX+length) and (newY >= turquoisePosY) and (newY <= (turquoisePosY+height)) and (mouseState == (True,False,False))):\r
+ colour = TURQUOISE\r
+ pygame.draw.polygon(screen,BLACK,((turquoisePosX+(length/2),(menyH/2)),((turquoisePosX+(length/2))+10,(menyH/2)-10),((turquoisePosX+(length/2)-10),(menyH/2)-10)))\r
+ elif (newX >= purplePosX) and (newX <= (purplePosX+length) and (newY >= purplePosY) and (newY <= (purplePosY+height)) and (mouseState == (True,False,False))):\r
+ colour = PURPLE\r
+ pygame.draw.polygon(screen,BLACK,((purplePosX+(length/2),(menyH/2)),((purplePosX+(length/2))+10,(menyH/2)-10),((purplePosX+(length/2)-10),(menyH/2)-10)))\r
+ elif (newX >= whitePosX) and (newX <= (whitePosX+length) and (newY >= whitePosY) and (newY <= (whitePosY+height)) and (mouseState == (True,False,False))):\r
+ colour = WHITE\r
+ pygame.draw.polygon(screen,BLACK,((whitePosX+(length/2),(menyH/2)),((whitePosX+(length/2))+10,(menyH/2)-10),((whitePosX+(length/2)-10),(menyH/2)-10)))\r
+ if (colour == BLACK):\r
+ pygame.draw.polygon(screen,BLACK,((blackPosX+(length/2),(menyH/2)),((blackPosX+(length/2))+10,(menyH/2)-10),((blackPosX+(length/2)-10),(menyH/2)-10)))\r
+ elif (colour == RED):\r
+ pygame.draw.polygon(screen,BLACK,((redPosX+(length/2),(menyH/2)),((redPosX+(length/2))+10,(menyH/2)-10),((redPosX+(length/2)-10),(menyH/2)-10)))\r
+ elif (colour == GREEN):\r
+ pygame.draw.polygon(screen,BLACK,((greenPosX+(length/2),(menyH/2)),((greenPosX+(length/2))+10,(menyH/2)-10),((greenPosX+(length/2)-10),(menyH/2)-10)))\r
+ elif (colour == BLUE):\r
+ pygame.draw.polygon(screen,BLACK,((bluePosX+(length/2),(menyH/2)),((bluePosX+(length/2))+10,(menyH/2)-10),((bluePosX+(length/2)-10),(menyH/2)-10)))\r
+ elif (colour == YELLOW):\r
+ pygame.draw.polygon(screen,BLACK,((yellowPosX+(length/2),(menyH/2)),((yellowPosX+(length/2))+10,(menyH/2)-10),((yellowPosX+(length/2)-10),(menyH/2)-10)))\r
+ elif (colour == TURQUOISE):\r
+ pygame.draw.polygon(screen,BLACK,((turquoisePosX+(length/2),(menyH/2)),((turquoisePosX+(length/2))+10,(menyH/2)-10),((turquoisePosX+(length/2)-10),(menyH/2)-10)))\r
+ elif (colour == PURPLE):\r
+ pygame.draw.polygon(screen,BLACK,((purplePosX+(length/2),(menyH/2)),((purplePosX+(length/2))+10,(menyH/2)-10),((purplePosX+(length/2)-10),(menyH/2)-10)))\r
+ elif (colour == WHITE):\r
+ pygame.draw.polygon(screen,BLACK,((whitePosX+(length/2),(menyH/2)),((whitePosX+(length/2))+10,(menyH/2)-10),((whitePosX+(length/2)-10),(menyH/2)-10)))\r
+ pygame.display.update()\r
+ pygame.draw.rect(screen,GREY,(labelX,labelY,menyW,40),0)\r
+ for event in pygame.event.get():\r
+ if (event.type == pygame.KEYDOWN):\r
+ if (event.key == pygame.K_KP_PLUS) or (event.key == pygame.K_RIGHT):\r
+ size = size + 1\r
+ elif (event.key == pygame.K_KP_MINUS) or (event.key == pygame.K_LEFT):\r
+ size = size - 1\r
+ elif (event.key == pygame.K_UP):\r
+ pygame.draw.rect(screen,WHITE,(0,menyH,screenW,screenH-menyH),0)\r
+ \r
+ if event.type ==pygame.QUIT:\r
+ main = False\r
+ clock.tick (FPS)\r
+pygame.quit()\r
+sys.exit()\r
--- /dev/null
+import pygame\r
+import sys\r
+import time\r
+import random\r
+clock = pygame.time.Clock()\r
+FPS = 100\r
+\r
+pygame.init()\r
+screenW = 800\r
+screenH = 600\r
+screen = pygame.display.set_mode((screenW,screenH),0)\r
+\r
+pygame.display.set_caption("CIRCLES!!")\r
+\r
+WHITE = (255,255,255)\r
+RED = (255,0,0)\r
+GREEN = (0,255,0)\r
+BLUE = (0,0,255)\r
+\r
+#Initialzing variables\r
+x = 100\r
+y = 100\r
+dx = 5\r
+dy = 5\r
+radius1 = 25\r
+x2 = 500\r
+y2 = 300\r
+dx2 = 5\r
+dy2 = 5\r
+radius2 = 50\r
+x3 = 300\r
+y3 = 500\r
+dx3 = 5\r
+dy3 = 5\r
+radius3 = 75\r
+colour1 = 0\r
+colour2 = 0\r
+colour3 = 0\r
+# main loop\r
+main = True\r
+while main:\r
+ for event in pygame.event.get():\r
+ if event.type ==pygame.QUIT:\r
+ main = False\r
+ \r
+ screen.fill(WHITE) #filling the screen white\r
+ value1 = random.randint (1,3)\r
+ value2 = random.randint (1,3)\r
+ value3 = random.randint (1,3)\r
+ value4 = random.randint (1,3)\r
+ value5 = random.randint (1,3)\r
+ value6 = random.randint (1,3)\r
+ rectName = pygame.draw.circle(screen,colour1,(x,y), radius1) #drawing the circle\r
+\r
+ #answer to challenge question\r
+ if (x >= screenW-radius1) or (x <= radius1): #edge of the screen, subtract radius of the circle\r
+ dx = -dx\r
+ if (dx == -1):\r
+ dx = -5\r
+ if (dy == -1):\r
+ dy = -5\r
+ elif (dy == 1):\r
+ dy = 5\r
+ elif (dx == 1):\r
+ dx = 5\r
+ if (dy == -1):\r
+ dy = -5\r
+ elif (dy == 1):\r
+ dy = 5\r
+ elif (dx == -5):\r
+ dx = -1\r
+ if (dy == -5):\r
+ dy = -1\r
+ elif (dy == 5):\r
+ dy = 1\r
+ elif (dx == 5):\r
+ dx = 1\r
+ if (dy == -5):\r
+ dy = -1\r
+ elif (dy == 5):\r
+ dy = 1\r
+ if (radius1 == 25):\r
+ radius1 = 50\r
+ elif (radius1 == 50):\r
+ radius1 = 25\r
+ if (x < 300):\r
+ x = radius1 + 1\r
+ elif (x > 300):\r
+ x = screenW - radius1 - 1\r
+ if (value1 == 1):\r
+ colour1 = RED\r
+ elif (value1 == 2):\r
+ colour1 = BLUE\r
+ elif (value1 == 3):\r
+ colour1 = GREEN\r
+ if (y >= screenH - radius1) or (y <= radius1):\r
+ dy = -dy #multiplying the speed by -1\r
+ if (dy == -1):\r
+ dy = -5\r
+ if (dx == -1):\r
+ dx = -5\r
+ elif (dx == 1):\r
+ dx = 5\r
+ elif (dy == 1):\r
+ dy = 5\r
+ if (dx == -1):\r
+ dx = -5\r
+ elif (dx == 1):\r
+ dx = 5\r
+ elif (dy == -5):\r
+ dy = -1\r
+ if (dx == -5):\r
+ dx = -1\r
+ elif (dx == 5):\r
+ dx = 1\r
+ elif (dy == 5):\r
+ dy = 1\r
+ if (dx == -5):\r
+ dx = -1\r
+ elif (dx == 5):\r
+ dx = 1\r
+ if (radius1 == 25):\r
+ radius1 = 50\r
+ elif (radius1 == 50):\r
+ radius1 = 25\r
+ if (y < 300):\r
+ y = radius1 + 1\r
+ if (y > 300):\r
+ y = screenH - radius1 - 1\r
+ if (value2 == 1):\r
+ colour1 = RED\r
+ elif (value2 == 2):\r
+ colour1 = BLUE\r
+ elif (value2 == 3):\r
+ colour1 = GREEN\r
+\r
+ x = x +dx; #adding the speed to x\r
+ y = y +dy; # adding the speed to y\r
+\r
+ rectName = pygame.draw.circle(screen,colour2,(x2,y2), radius2) #drawing the circle\r
+\r
+ #answer to challenge question\r
+ if (x2 >= screenW-radius2) or (x2 <= radius2): #edge of the screen, subtract radius of the circle\r
+ dx2 = -dx2\r
+ if (dx2 == -1):\r
+ dx2 = -5\r
+ if (dy2 == -1):\r
+ dy2 = -5\r
+ elif (dy2 == 1):\r
+ dy2 = 5\r
+ elif (dx2 == 1):\r
+ dx2 = 5\r
+ if (dy2 == -1):\r
+ dy2 = -5\r
+ elif (dy2 == 1):\r
+ dy2 = 5\r
+ elif (dx2 == -5):\r
+ dx2 = -1\r
+ if (dy2 == -5):\r
+ dy2 = -1\r
+ elif (dy2 == 5):\r
+ dy2 = 1\r
+ elif (dx2 == 5):\r
+ dx2 = 1\r
+ if (dy2 == -5):\r
+ dy2 = -1\r
+ elif (dy2 == 5):\r
+ dy2 = 1\r
+ if (radius2 == 50):\r
+ radius2 = 75\r
+ elif (radius2 == 75):\r
+ radius2 = 50\r
+ if (x2 < 400):\r
+ x2 = radius2 + 1\r
+ elif (x2 > 400):\r
+ x2 = screenW -radius2 - 1\r
+ if (value3 == 1):\r
+ colour2 = RED\r
+ elif (value3 == 2):\r
+ colour2 = BLUE\r
+ elif (value3 == 3):\r
+ colour2 = GREEN\r
+ if (y2 >= screenH - radius2) or (y2 <= radius2):\r
+ dy2 = -dy2 #multiplying the speed by -1\r
+ if (dy2 == -1):\r
+ dy2 = -5\r
+ if (dx2 == -1):\r
+ dx2 = -5\r
+ elif (dx2 == 1):\r
+ dx2 = 5\r
+ elif (dy2 == 1):\r
+ dy2 = 5\r
+ if (dx2 == -1):\r
+ dx2 = -5\r
+ elif (dx2 == 1):\r
+ dx2 = 5\r
+ elif (dy2 == -5):\r
+ dy2 = -1\r
+ if (dx2 == -5):\r
+ dx2 = -1\r
+ elif (dx2 == 5):\r
+ dx2 = 1\r
+ elif (dy2 == 5):\r
+ dy2 = 1\r
+ if (dx2 == -5):\r
+ dx2 = -1\r
+ elif (dx2 == 5):\r
+ dx2 = 1\r
+ if (radius2 == 50):\r
+ radius2 = 75\r
+ elif (radius2 == 75):\r
+ radius2 = 50\r
+ if (y2 < 300):\r
+ y2 = radius2 + 1\r
+ elif (y2 > 300):\r
+ y2 = screenH - radius2 - 1\r
+ if (value4 == 1):\r
+ colour2 = RED\r
+ elif (value4 == 2):\r
+ colour2 = BLUE\r
+ elif (value4 == 3):\r
+ colour2 = GREEN\r
+ x2 = x2 +dx2; #adding the speed to x\r
+ y2 = y2 +dy2; # adding the speed to y\r
+\r
+ rectName = pygame.draw.circle(screen,colour3,(x3,y3), radius3) #drawing the circle\r
+\r
+ #answer to challenge question\r
+ if (x3 >= screenW-radius3) or (x3 <= radius3): #edge of the screen, subtract radius of the circle\r
+ dx3 = -dx3\r
+ if (dx3 == -1):\r
+ dx3 = -5\r
+ if (dy3 == -1):\r
+ dy3 = -5\r
+ elif (dy3 == 1):\r
+ dy3 = 5\r
+ elif (dx3 == 1):\r
+ dx3 = 5\r
+ if (dy3 == -1):\r
+ dy3 = -5\r
+ elif (dy3 == 1):\r
+ dy3 = 5\r
+ elif (dx3 == -5):\r
+ dx3 = -1\r
+ if (dy3 == -5):\r
+ dy3 = -1\r
+ elif (dy3 == 5):\r
+ dy3 = 1\r
+ elif (dx3 == 5):\r
+ dx3 = 1\r
+ if (dy3 == -5):\r
+ dy3 = -1\r
+ elif (dy3 == 5):\r
+ dy3 = 1\r
+ if (radius3 == 75):\r
+ radius3 = 100\r
+ elif (radius3 == 100):\r
+ radius3 = 75\r
+ if (x3 < 400):\r
+ x3 = radius3 + 1\r
+ elif (x3 > 400):\r
+ x3 = screenW - radius3 - 1\r
+ if (value5 == 1):\r
+ colour3 = RED\r
+ elif (value5 == 2):\r
+ colour3 = BLUE\r
+ elif (value5 == 3):\r
+ colour3 = GREEN\r
+ if (y3 >= screenH - radius3) or (y3 <= radius3):\r
+ dy3 = -dy3 #multiplying the speed by -1\r
+ if (dy3 == -1):\r
+ dy3 = -5\r
+ if (dx3 == -1):\r
+ dx3 = -5\r
+ elif (dx3 == 1):\r
+ dx3 = 5\r
+ elif (dy3 == 1):\r
+ dy3 = 5\r
+ if (dx3 == -1):\r
+ dx3 = -5\r
+ elif (dx3 == 1):\r
+ dx3 = 5\r
+ elif (dy3 == -5):\r
+ dy3 = -1\r
+ if (dx3 == -5):\r
+ dx3 = -1\r
+ elif (dx3 == 5):\r
+ dx3 = 1\r
+ elif (dy3 == 5):\r
+ dy3 = 1\r
+ if (dx3 == -5):\r
+ dx3 = -1\r
+ elif (dx3 == 5):\r
+ dx3 = 1\r
+ if (radius3 == 75):\r
+ radius3 = 100\r
+ elif (radius3 == 100):\r
+ radius3 = 75\r
+ if (y3 < 300):\r
+ y3 = radius3 + 1\r
+ elif (y3 > 300):\r
+ y3 = screenH - radius3 - 1\r
+ if (value6 == 1):\r
+ colour3 = RED\r
+ elif (value6 == 2):\r
+ colour3 = BLUE\r
+ elif (value6 == 3):\r
+ colour3 = GREEN\r
+ x3 = x3 +dx3; #adding the speed to x\r
+ y3 = y3 +dy3; # adding the speed to y\r
+ pygame.display.update()\r
+ clock.tick (FPS)#slow it down a bit. dx and dy doesnt let me have a value <1 and >0.\r
+\r
+\r
+pygame.quit()\r
+sys.exit()\r
--- /dev/null
+# import the necessary modules\r
+import pygame\r
+import sys\r
+import random\r
+import time\r
+\r
+#initialize pygame\r
+pygame.init()\r
+screenH = 600\r
+screenW = 800\r
+# set the size for the surface (screen)\r
+screen = pygame.display.set_mode((screenW,screenH),0)\r
+# set the caption for the screen\r
+pygame.display.set_caption("Animated Circle")\r
+\r
+# define colours and variables you will be using\r
+WHITE = (255,255,255)\r
+GREEN = (0,255,0)\r
+RED = (255,0,0)\r
+BLUE = (0,0,255)\r
+\r
+x = 100\r
+y = 200\r
+radius1 = 25\r
+dx = 1\r
+dy = 1\r
+# set main loop to True so it will run\r
+main = True\r
+# main loop\r
+while main:\r
+ for event in pygame.event.get(): # check for any events (i.e key press, mouse click etc.)\r
+ if event.type ==pygame.QUIT: # check to see if it was "x" at top right of screen\r
+ main = False # set the "main" variable to False to exit while loop\r
+\r
+\r
+ # your code for animation will go here inside the main loop\r
+ screen.fill (WHITE)\r
+ rectName = pygame.draw.circle(screen,RED,(x,y), radius1)\r
+ if (x >= screenW-radius1) or (x <= radius1): #edge of the screen, subtract radius of the circle\r
+ dx = -dx\r
+ if (dx == -1):\r
+ dx = -5\r
+ if (dy == -1):\r
+ dy = -5\r
+ elif (dy == 1):\r
+ dy = 5\r
+ elif (dx == 1):\r
+ dx = 5\r
+ if (dy == -1):\r
+ dy = -5\r
+ elif (dy == 1):\r
+ dy = 5\r
+ elif (dx == -5):\r
+ dx = -1\r
+ if (dy == -5):\r
+ dy = -1\r
+ elif (dy == 5):\r
+ dy = 1\r
+ elif (dx == 5):\r
+ dx = 1\r
+ if (dy == -5):\r
+ dy = -1\r
+ elif (dy == 5):\r
+ dy = 1\r
+ if (y >= screenH - radius1) or (y <= radius1):\r
+ dy = -dy #multiplying the speed by -1\r
+ if (dy == -1):\r
+ dy = -5\r
+ if (dx == -1):\r
+ dx = -5\r
+ elif (dx == 1):\r
+ dx = 5\r
+ elif (dy == 1):\r
+ dy = 5\r
+ if (dx == -1):\r
+ dx = -5\r
+ elif (dx == 1):\r
+ dx = 5\r
+ elif (dy == -5):\r
+ dy = -1\r
+ if (dx == -5):\r
+ dx = -1\r
+ elif (dx == 5):\r
+ dx = 1\r
+ elif (dy == 5):\r
+ dy = 1\r
+ if (dx == -5):\r
+ dx = -1\r
+ elif (dx == 5):\r
+ dx = 1\r
+\r
+ x = x +dx; #adding the speed to x\r
+ y = y +dy; # adding the speed to y\r
+ time.sleep (0.01)\r
+ pygame.display.update() #updates the screen\r
+\r
+# quit pygame and exit the program (i.e. close everything down)\r
+pygame.quit()\r
+sys.exit()\r
--- /dev/null
+import pygame\r
+import sys\r
+import random\r
+import time\r
+pygame.init()\r
+screen = pygame.display.set_mode((800,600),0)\r
+pygame.display.set_caption("Rainbow")\r
+VIOLET =(148, 0, 211)\r
+GREEN = (0,255,0)\r
+RED = (255,0,0)\r
+BLUE = (0,0,255)\r
+INDIGO = (75,0,130)\r
+WHITE = (255,255,255)\r
+ORANGE = (255,127,0)\r
+YELLOW = (255,255,0)\r
+xcordinate = 0\r
+ycordinate = 0\r
+xcordinate2 = 0\r
+ycordinate2 = 0\r
+screen.fill (WHITE)\r
+main = True \r
+while main:\r
+ for event in pygame.event.get():\r
+ if event.type ==pygame.QUIT:\r
+ main = False\r
+ x = random.randint (1,7)\r
+ if (x==1):\r
+ COLOUR = RED\r
+ elif (x==2):\r
+ COLOUR = BLUE\r
+ elif (x==3):\r
+ COLOUR = GREEN\r
+ elif (x==4):\r
+ COLOUR = VIOLET\r
+ elif (x==5):\r
+ COLOUR = INDIGO\r
+ elif (x==6):\r
+ COLOUR = ORANGE\r
+ elif (x==7):\r
+ COLOUR = YELLOW\r
+ rectangle = pygame.draw.circle(screen,COLOUR,(xcordinate,ycordinate),5,0)\r
+ xcordinate = xcordinate + 10\r
+ if (xcordinate > 50) or (ycordinate > 0):\r
+ rectangle = pygame.draw.circle(screen,WHITE,(xcordinate2,ycordinate2),5,0)\r
+ xcordinate2 = xcordinate2 + 10\r
+ if (xcordinate2 >= 800):\r
+ ycordinate2 = ycordinate2 + 10\r
+ xcordinate2 = 0\r
+ if (ycordinate2 >= 600):\r
+ xcordinate = 0\r
+ ycordinate = 0\r
+ ycordinate2 = 0\r
+ xcordinate2 = 0\r
+ screen.fill (WHITE)\r
+ if (xcordinate >= 800):\r
+ ycordinate = ycordinate + 10\r
+ xcordinate = 0\r
+ time.sleep (0.5)\r
+ pygame.display.update()\r
+pygame.quit ()\r
+sys.exit ()\r
--- /dev/null
+import pygame\r
+import sys\r
+import random\r
+import time\r
+pygame.init()\r
+screen = pygame.display.set_mode((800,600),0)\r
+pygame.display.set_caption("Rainbow")\r
+VIOLET =(148, 0, 211)\r
+GREEN = (0,255,0)\r
+RED = (255,0,0)\r
+BLUE = (0,0,255)\r
+INDIGO = (75,0,130)\r
+WHITE = (255,255,255)\r
+ORANGE = (255,127,0)\r
+YELLOW = (255,255,0)\r
+xcordinate = 0\r
+ycordinate = 0\r
+xcordinate2 = 0\r
+ycordinate2 = 0\r
+k = 10\r
+ycordinate3 = 0\r
+screen.fill (WHITE)\r
+main = True \r
+while main:\r
+ for event in pygame.event.get():\r
+ if event.type ==pygame.QUIT:\r
+ main = False\r
+ ycordinate3 = ycordinate3 + k\r
+ if (ycordinate3 == 100):\r
+ k = -10\r
+ if (ycordinate3 == -100):\r
+ k = 10\r
+ x = random.randint (1,7)\r
+ if (x==1):\r
+ COLOUR = RED\r
+ elif (x==2):\r
+ COLOUR = BLUE\r
+ elif (x==3):\r
+ COLOUR = GREEN\r
+ elif (x==4):\r
+ COLOUR = VIOLET\r
+ elif (x==5):\r
+ COLOUR = INDIGO\r
+ elif (x==6):\r
+ COLOUR = ORANGE\r
+ elif (x==7):\r
+ COLOUR = YELLOW\r
+ rectangle = pygame.draw.circle(screen,COLOUR,(xcordinate,(ycordinate+ycordinate3)),5,0)\r
+ xcordinate = xcordinate + 10\r
+ if (xcordinate > 50) or (ycordinate > 0):\r
+ rectangle = pygame.draw.circle(screen,WHITE,(xcordinate2,(ycordinate2+ycordinate3)),5,0)\r
+ xcordinate2 = xcordinate2 + 10\r
+ if (xcordinate2 >= 800):\r
+ ycordinate2 = ycordinate2 + 10\r
+ xcordinate2 = 0\r
+ if (ycordinate2 >= 600):\r
+ xcordinate = 0\r
+ ycordinate = 0\r
+ ycordinate2 = 0\r
+ xcordinate2 = 0\r
+ screen.fill (WHITE)\r
+ if (xcordinate >= 800):\r
+ ycordinate = ycordinate + 10\r
+ xcordinate = 0\r
+ time.sleep (0.5)\r
+ pygame.display.update()\r
+pygame.quit ()\r
+sys.exit ()\r
--- /dev/null
+import ctypes\r
+\r
+LONG = ctypes.c_long\r
+DWORD = ctypes.c_ulong\r
+ULONG_PTR = ctypes.POINTER(DWORD)\r
+WORD = ctypes.c_ushort\r
+\r
+class MOUSEINPUT(ctypes.Structure):\r
+ _fields_ = (('dx', LONG),\r
+ ('dy', LONG),\r
+ ('mouseData', DWORD),\r
+ ('dwFlags', DWORD),\r
+ ('time', DWORD),\r
+ ('dwExtraInfo', ULONG_PTR))\r
+\r
+class KEYBDINPUT(ctypes.Structure):\r
+ _fields_ = (('wVk', WORD),\r
+ ('wScan', WORD),\r
+ ('dwFlags', DWORD),\r
+ ('time', DWORD),\r
+ ('dwExtraInfo', ULONG_PTR))\r
+\r
+class HARDWAREINPUT(ctypes.Structure):\r
+ _fields_ = (('uMsg', DWORD),\r
+ ('wParamL', WORD),\r
+ ('wParamH', WORD))\r
+\r
+class _INPUTunion(ctypes.Union):\r
+ _fields_ = (('mi', MOUSEINPUT),\r
+ ('ki', KEYBDINPUT),\r
+ ('hi', HARDWAREINPUT))\r
+\r
+class INPUT(ctypes.Structure):\r
+ _fields_ = (('type', DWORD),\r
+ ('union', _INPUTunion))\r
+\r
+def SendInput(*inputs):\r
+ nInputs = len(inputs)\r
+ LPINPUT = INPUT * nInputs\r
+ pInputs = LPINPUT(*inputs)\r
+ cbSize = ctypes.c_int(ctypes.sizeof(INPUT))\r
+ return ctypes.windll.user32.SendInput(nInputs, pInputs, cbSize)\r
+\r
+INPUT_MOUSE = 0\r
+INPUT_KEYBOARD = 1\r
+INPUT_HARDWARD = 2\r
+\r
+def Input(structure):\r
+ if isinstance(structure, MOUSEINPUT):\r
+ return INPUT(INPUT_MOUSE, _INPUTunion(mi=structure))\r
+ if isinstance(structure, KEYBDINPUT):\r
+ return INPUT(INPUT_KEYBOARD, _INPUTunion(ki=structure))\r
+ if isinstance(structure, HARDWAREINPUT):\r
+ return INPUT(INPUT_HARDWARE, _INPUTunion(hi=structure))\r
+ raise TypeError('Cannot create INPUT structure!')\r
+\r
+def MouseInput(flags, x, y, data):\r
+ return MOUSEINPUT(x, y, data, flags, 0, None)\r
+\r
+def KeybdInput(code, flags):\r
+ return KEYBDINPUT(code, code, flags, 0, None)\r
+\r
+def HardwareInput(message, parameter):\r
+ return HARDWAREINPUT(message & 0xFFFFFFFF,\r
+ parameter & 0xFFFF,\r
+ parameter >> 16 & 0xFFFF)\r
+\r
+def Mouse(flags, x=0, y=0, data=0):\r
+ return Input(MouseInput(flags, x, y, data))\r
+\r
+def Keyboard(code, flags=0):\r
+ return Input(KeybdInput(code, flags))\r
+\r
+def Hardware(message, parameter=0):\r
+ return Input(HardwareInput(message, parameter))
\ No newline at end of file
--- /dev/null
+from Direct_Input import *\r
+import time\r
+from PIL import ImageGrab\r
+\r
+from timeit import default_timer as timer\r
+\r
+from ctypes import windll\r
+# ...\r
+time.sleep(7)\r
+ #\r
+\r
+\r
+tileXPos = (0,100,200,300)\r
+YSearch = 0\r
+YClick = 80\r
+\r
+currentTile = 0\r
+# while True:\r
+# time.sleep(1)\r
+# SendInput(Mouse(0x0001, 100, 100))\r
+# time.sleep(1)\r
+# SendInput(Mouse(0x0008, 0, 0), Mouse(0x0010, 0, 0))\r
+time.sleep(2)\r
+\r
+while True:\r
+ img = ImageGrab.grab((522,470,823,480))\r
+ time.sleep(0.1)\r
+\r
+\r
+ for XSearch in tileXPos:\r
+\r
+ if img.getpixel((XSearch,1))[2] < 200:\r
+\r
+\r
+ if XSearch == 0:\r
+ SendInput(Mouse(0x0001, int(100*((XSearch/100) - currentTile)), 0))\r
+ currentTile = 0\r
+ elif XSearch == 100:\r
+ SendInput(Mouse(0x0001, int(100*((XSearch/100) - currentTile)), 0))\r
+ currentTile = 1\r
+ elif XSearch == 200:\r
+ SendInput(Mouse(0x0001, int(100*((XSearch/100) - currentTile)), 0))\r
+ currentTile = 2 \r
+ elif XSearch == 300:\r
+ SendInput(Mouse(0x0001, int(100*((XSearch/100) - currentTile)), 0))\r
+ currentTile = 3\r
+ SendInput(Mouse(0x0002, 0, 0, 0x0001), Mouse(0x0004, 0, 0, 0x0001))\r
+\r
+\r
+img.show()
\ No newline at end of file
--- /dev/null
+Subproject commit 6b4ef483690497a97acaeaf6f81790647495292e
--- /dev/null
+import eyed3\r
+import os\r
+import sys\r
+import argparse\r
+\r
+def run():\r
+\r
+ parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter,\r
+ description="Add ID3 tag to MP3 audio file")\r
+\r
+ parser.add_argument("filePath", action="store", help="path of MP3")\r
+ parser.add_argument("--imgpath", action="store", dest="imgPath", help="path of image")\r
+ parser.add_argument("--album", action="store", dest="album", help="Album of MP3")\r
+ parser.add_argument("--artist", action="store", dest="artist", help="Artist of MP3")\r
+ parser.add_argument("--title", action="store", dest="title", help="Title of MP3")\r
+ parser.add_argument("--genre", action="store", dest="genre", help="Genre of MP3")\r
+ args = parser.parse_args()\r
+\r
+ if not os.path.isabs(args.filePath):\r
+ args.filePath = os.path.join(os.path.dirname(__file__), args.filePath)\r
+\r
+ if args.imgPath is not None:\r
+ if not os.path.isabs(args.imgPath):\r
+ args.imgPath = os.path.join(os.path.dirname(__file__), args.imgPath)\r
+\r
+ try:\r
+ file = eyed3.load(args.filePath)\r
+ except OSError:\r
+ print("\nError: file not found")\r
+ return False\r
+\r
+ file.tag.artist = args.artist or file.tag.artist\r
+ file.tag.album = args.album or file.tag.album\r
+ file.tag.album_artist = args.artist or file.tag.artist\r
+ file.tag.title = args.title or file.tag.title\r
+ file.tag.genre = args.genre or file.tag.genre\r
+\r
+ if args.imgPath is not None:\r
+ with open(args.imgPath, "rb") as image:\r
+ file.tag.images.set(3, image.read(), "image","")\r
+\r
+ print("\nScript Successful\n")\r
+ print("--------------------------")\r
+ print("Title = {0}\nArtist = {1}\nAlbum = {2}\nGenre = {3}".format(\r
+ file.tag.title, \r
+ file.tag.artist, \r
+ file.tag.album,\r
+ file.tag.genre))\r
+ print("--------------------------")\r
+\r
+ file.tag.save()\r
+\r
+if __name__ == "__main__":\r
+ run()\r
+
\ No newline at end of file
--- /dev/null
+#!/bin/sh\r
+#cd ./ShiftScheduler\r
+#./Injixo2Calendar nils.for nils.for +02:00\r
+#cd ..\r
+cd ./GameScheduler\r
+./Fogis2Calendar nils.forssén N1sseP1sse02! +02:00
\ No newline at end of file
--- /dev/null
+# Spectrometer\r
+Code for University project of creating a simple spectrometer. \r
+\r
+All thats needed is a 3D printed black box with a small slit to let light in, a diffraction grating and a webcam with a USB-interface connected to a laptop running the python 3.x intepreter.\r
+\r
+Running the spectrometer.py file results in a camera image where the user can press 'c' to select an ROI in video feed. All pixels in selected ROI will be read and organized by their wavelength (color) and plotted accordingly. \r
--- /dev/null
+# ------- Capture and return image --------\r
+\r
+import cv2\r
+\r
+def getImage(videoObj, p1=None, p2=None):\r
+ """\r
+ Returns the image with drawn rectangle on top given by pt1 and pt2\r
+ """\r
+ try:\r
+ ret, frame = videoObj.read()\r
+ if not ret:\r
+ print("failed to grab frame")\r
+ return None\r
+ except cv2.error:\r
+ return None\r
+ \r
+ if p1 and p2:\r
+ cv2.rectangle(frame, p1, p2, (255,0,0))\r
+ \r
+ return frame\r
+\r
+def getSubImage(videoObj, p1, p2):\r
+ """"\r
+ Returns the part of the capture enclosed by pt1 and pt2.\r
+ """\r
+ frame = getImage(videoObj)\r
+ if frame is None:\r
+ return frame\r
+ analyze_frame = frame[p1[1]:p2[1], p1[0]:p2[0]]\r
+\r
+ return analyze_frame\r
+\r
+\r
+def closeCamera(videoObj):\r
+ videoObj.release()\r
+ cv2.destroyAllWindows()\r
+\r
--- /dev/null
+# ------ Create Specrometer graph from image data -------\r
+\r
+# Creates a video feed of the camera, lets the user select an ROI and graph the data\r
+# Sometimes the script crashes randomly. Just restart. Little time has been dedicated to user interface and EoA. \r
+# This spectrometer is by no means perfect, but is working surprisingly well. \r
+\r
+import cv2\r
+import matplotlib.pyplot as plt\r
+import image\r
+import numpy as np\r
+from threading import Thread\r
+import time\r
+\r
+# Class to keep a static ROI over video\r
+class videoROI(Thread):\r
+ def __init__(self, src, topleft, bottomright):\r
+ super().__init__()\r
+ self.bbox = [topleft, bottomright] # Bounding box for rectangle, (topleft, bottomright)\r
+ self.windowName = "Video"\r
+ self._frame = None\r
+ self.src = src\r
+ self.scale = 2 # Scale to increase the image resolution of when displaying\r
+ \r
+ def run(self, *args, **kwargs):\r
+ """\r
+ Main function of thread\r
+ """\r
+ self.capture = cv2.VideoCapture(self.src) # src = 0: built in webcam, src = 1: additional webcam\r
+\r
+ cv2.namedWindow(self.windowName)\r
+\r
+ while self.capture.isOpened():\r
+\r
+ # Capture image for video feed \r
+ self._frame = image.getImage(self.capture, *self.bbox)\r
+\r
+ # Sometimes async errors causes frame to be None\r
+ if self._frame is None:\r
+ cv2.waitKey(1)\r
+ continue\r
+ \r
+ # Increase the resolution/size of the shown video feed to fit laptop display better\r
+ show_frame = cv2.resize(self._frame, (640*self.scale, 480*self.scale))\r
+ cv2.imshow(self.windowName, show_frame)\r
+ \r
+ k = cv2.waitKey(1)\r
+\r
+ # User input\r
+ # 'p': print the coordinates of the mouse\r
+ # 'q': quit\r
+ # 's': save current image\r
+ if k == ord('p'):\r
+ cv2.setMouseCallback(self.windowName, self.mouseCB, param=0)\r
+ elif k == ord('q'):\r
+ break\r
+ elif k == ord('s'):\r
+ cv2.imwrite("frame.png", self._frame)\r
+ else:\r
+ print("capture closed")\r
+\r
+ def mouseCB(self, event, x, y, flags, param):\r
+ """\r
+ Mouse event callback\r
+ Print the coordinates of the mouse once.\r
+ """\r
+ print("Pos: {}, {}".format(x/self.scale,y/self.scale))\r
+\r
+ # Set next callback to anonymous function returning None\r
+ cv2.setMouseCallback(self.windowName, lambda *args : None)\r
+\r
+ def getImage(self):\r
+ """\r
+ Get current ROI\r
+ """\r
+ return image.getSubImage(self.capture, *self.bbox)\r
+\r
+ def close(self):\r
+ """\r
+ Close the camera\r
+ """\r
+ image.closeCamera(self.capture)\r
+ \r
+\r
+if __name__ == "__main__":\r
+ \r
+ # Analyze 2 lines of spectrum at height 300. \r
+ pixelY = 300\r
+ height = 2\r
+\r
+ # Calibration constants\r
+ calibWL = 532 # Calibrated to prefectly match green laser\r
+ calibPixelX = 189 # Green laser x value on detector\r
+ minWL = 300 # Minimum wavelength to be graphed\r
+ maxWL = 800 # Max wavelength to be graphed\r
+ scaleFactor = 2.6 # Calculated resolution (wavelengths per pixel) with other known lasers with green laser being zero\r
+\r
+ # Arange wavelength array\r
+ calibToMax = np.arange(calibWL, maxWL, scaleFactor)\r
+ calibToMin = np.flip(np.arange(calibWL, minWL, -scaleFactor))\r
+ wavelengths = np.concatenate((calibToMin, calibToMax))\r
+\r
+ # Determine the x interval of interest in detector\r
+ minPixelX = calibPixelX - len(calibToMin) + 1\r
+ maxPixelX = minPixelX + len(wavelengths)\r
+\r
+ # Dictionary to count intensity of each pixel/corresponding wavelength\r
+ intensities = dict(zip(wavelengths, np.zeros(len(wavelengths), dtype="int")))\r
+\r
+ graph = plt.plot(intensities.keys(), intensities.values(), color='darkred')[0]\r
+ plt.ylim(0, 600000)\r
+ plt.xlabel('Wavelength (nm)')\r
+ plt.ylabel('Intensity')\r
+ plt.ion()\r
+\r
+ video = videoROI(1, (minPixelX, pixelY), (maxPixelX, pixelY + height))\r
+ video.start()\r
+ time.sleep(1)\r
+\r
+ while True:\r
+\r
+ frame = video.getImage()\r
+\r
+ if not video.is_alive():\r
+ plt.close()\r
+ break\r
+\r
+ if not frame is None:\r
+ \r
+ for row in frame:\r
+ for colN, col in enumerate(row):\r
+ intensities[wavelengths[colN]] += np.sum(col) ** 2\r
+\r
+ if height != 1:\r
+ for k, v in intensities.items():\r
+ intensities[k] = v/height\r
+ \r
+ yData = list(intensities.values())\r
+ graph.set_ydata(yData)\r
+ plt.pause(0.1)\r
+\r
+ intensities = dict(zip(wavelengths, np.zeros(len(wavelengths), dtype="int")))
\ No newline at end of file
--- /dev/null
+# Classic_Test.py\r
+\r
+if __name__ == '__main__' and __package__ is None:\r
+ from os import sys, path\r
+ sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))\r
+\r
+from pawnshop.ChessBoard import initClassic\r
+from pawnshop.Pieces import *\r
+from pawnshop.Exceptions import *\r
+from pawnshop.ChessVector import ChessVector\r
+\r
+\r
+board = initClassic()\r
+\r
+print(board)\r
+\r
+\r
+def move(start, target, **kwargs):\r
+ print(board.movePiece(ChessVector(start, board), ChessVector(target, board), **kwargs))\r
+\r
+\r
+print(board)\r
+\r
+move("g2", "g3")\r
+move("f1", "g2")\r
+move("g1", "f3")\r
+move("d2", "d4")\r
+move("d1", "d3")\r
+move("c1", "d2")\r
+move("b1", "c3")\r
+\r
+move("e1", "g1")\r
+\r
+print(board)\r
--- /dev/null
+# Four_Player_Test.py\r
+\r
+if __name__ == '__main__' and __package__ is None:\r
+ from os import sys, path\r
+ sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))\r
+\r
+from pawnshop.ChessBoard import init4P\r
+from pawnshop.ChessVector import ChessVector\r
+\r
+\r
+board = init4P()\r
+\r
+\r
+def move(start, target, **kwargs):\r
+ board.movePiece(ChessVector(start, board), ChessVector(target, board), **kwargs)\r
+\r
+\r
+move("f2", "f3")\r
+\r
+move("g2", "g3")\r
+\r
+move("g1", "g2")\r
+\r
+move("b6", "c6")\r
+\r
+move("m7", "l7")\r
+\r
+move("n7", "m7")\r
+\r
+move("h2", "h3")\r
+\r
+move("g2", "g1")\r
+\r
+move("b5", "c5")\r
+\r
+print(board)\r
--- /dev/null
+import socket\r
+import pickle\r
+\r
+\r
+class Network:\r
+ def __init__(self, server="localhost", port=5555):\r
+ self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\r
+\r
+ def connect(self, ip="127.0.0.1", port=5555):\r
+ self.client.connect((ip, port))\r
+ echo = pickle.loads(self.client.recv(1024))\r
+ return echo\r
+\r
+ def send(self, data):\r
+ self.client.send(pickle.dumps(data))\r
+ try:\r
+ echo = pickle.loads(self.client.recv(4096 * 4))\r
+ except EOFError:\r
+ return None\r
+ else:\r
+ return echo\r
+\r
+ def close(self):\r
+ self.client.close()\r
--- /dev/null
+# pawnshop-implementation-GUI\r
+GUI implementation of my own pawnshop framework, feel fre to try it out by downloading the executable dist/main.exe.\r
+\r
+Some details (\*cough\* the sound effects) are just for laughs. Also some details are unfinished. Checkmates don't work properly in 4P etc.\r
+\r
--- /dev/null
+import socket\r
+import pickle\r
+import _pickle\r
+import _thread\r
+from pawnshop.Exceptions import PromotionError, Illegal\r
+from pawnshop.ChessBoard import initClassic, init4P\r
+\r
+server = "0.0.0.0"\r
+port = 5555\r
+\r
+MANDATORYFLAGS = {\r
+ "ignoreOrder": False,\r
+ "ignoreMate": False,\r
+ "ignoreCheck": False,\r
+ "checkForCheck": True,\r
+ "checkForMate": True,\r
+ "checkMove": True,\r
+ "printout": False\r
+}\r
+\r
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\r
+\r
+s.bind((server, port))\r
+s.settimeout(1.0)\r
+\r
+s.listen()\r
+print("Waiting for connection, server started")\r
+ALLGAMES = {}\r
+\r
+\r
+def client(conn, addr, color, gameID):\r
+ conn.send(pickle.dumps(color))\r
+ global players\r
+ global ALLGAMES\r
+ while True:\r
+ try:\r
+ data = pickle.loads(conn.recv(1024))\r
+ except EOFError:\r
+ del ALLGAMES[gameID]\r
+ break\r
+ except _pickle.UnpicklingError:\r
+ break\r
+ except ConnectionResetError:\r
+ break\r
+ try:\r
+ board = ALLGAMES[gameID]\r
+ except KeyError:\r
+ break\r
+\r
+ if data == "break":\r
+ break\r
+ else:\r
+ if not board.ready:\r
+ conn.send(pickle.dumps("waiting"))\r
+ continue\r
+ if data != "get":\r
+ # Data is of format (args, kwargs)\r
+ args, kwargs = data\r
+\r
+ if color == board.currentTurn == color:\r
+ try:\r
+ ALLGAMES[gameID].movePiece(*args, **{**kwargs, **MANDATORYFLAGS})\r
+ except PromotionError:\r
+ conn.send(pickle.dumps("promote"))\r
+ continue\r
+ except Illegal:\r
+ conn.send(pickle.dumps("illegal"))\r
+ continue\r
+\r
+ conn.send(pickle.dumps(ALLGAMES[gameID]))\r
+\r
+ conn.close()\r
+ players -= 1\r
+ print("Disconnected from ", addr)\r
+ return\r
+\r
+\r
+players = 0\r
+try:\r
+ while True:\r
+ try:\r
+ conn, addr = s.accept()\r
+\r
+ except socket.timeout:\r
+ continue\r
+ try:\r
+ if players % 2 == 0:\r
+ ALLGAMES[players // 2] = initClassic()\r
+ ALLGAMES[players // 2].ready = False\r
+\r
+ _thread.start_new_thread(client, (conn, addr, "white", players // 2))\r
+ else:\r
+ ALLGAMES[players // 2].ready = True\r
+ _thread.start_new_thread(client, (conn, addr, "black", players // 2))\r
+\r
+ players += 1\r
+ print("Connected to ", addr)\r
+ except:\r
+ print("fuck it")\r
+ s.shutdown(socket.SHUT_RDWR)\r
+ s.close()\r
+ s.listen()\r
+ ALLGAMES = {}\r
+ players = 0\r
+\r
+except KeyboardInterrupt:\r
+ s.close()\r
--- /dev/null
+import playsound\r
+from threading import Thread\r
+\r
+\r
+class SoundQueue(Thread):\r
+ def __init__(self):\r
+ super().__init__(daemon=True)\r
+ self.fileList = []\r
+\r
+ def addSound(self, mp3path):\r
+ self.fileList.append(mp3path)\r
+ if not self.is_alive():\r
+ self.start()\r
+\r
+ def run(self):\r
+ for file in self.fileList:\r
+ try:\r
+ playsound.playsound(file, True)\r
+ except playsound.PlaysoundException:\r
+ print("Cannot play sound right now!")\r
+\r
+\r
+if __name__ == "__main__":\r
+ first = SoundQueue()\r
+\r
+ for i in range(10):\r
+ first.addSound("sound/pawn.mp3")\r
+ first.join()\r
--- /dev/null
+import os\r
+import sys\r
+from PIL import Image\r
+import SoundQueue\r
+\r
+\r
+def getResourcePath(relative_path):\r
+ """\r
+ Get pyinstaller resource\r
+ """\r
+\r
+ if hasattr(sys, '_MEIPASS'):\r
+ return os.path.join(sys._MEIPASS, relative_path)\r
+\r
+ return os.path.join(os.path.abspath("."), relative_path)\r
+\r
+\r
+def fetchImage(color, imgpath):\r
+ """Fetch image from disk"""\r
+ img = Image.open(imgpath)\r
+ pixels = img.load()\r
+ fullGreen = 255\r
+ r, g, b = color\r
+ for x in range(img.height):\r
+ for y in range(img.width):\r
+ _, greenVal, _, alpha = pixels[x, y]\r
+ if greenVal:\r
+ ratio = greenVal / fullGreen\r
+ pixels[x, y] = (round(r * ratio), round(g * ratio), round(b * ratio), alpha)\r
+ return img\r
+\r
+\r
+def squeek(move):\r
+ queue = SoundQueue.SoundQueue()\r
+ if move[0].islower() or "P" in move:\r
+ queue.addSound(getResourcePath("sound/pawn.mp3"))\r
+ elif "N" in move:\r
+ queue.addSound(getResourcePath("sound/knight.mp3"))\r
+ elif "K" in move:\r
+ queue.addSound(getResourcePath("sound/king.mp3"))\r
+ elif "B" in move:\r
+ queue.addSound(getResourcePath("sound/bishop.mp3"))\r
+ elif "R" in move:\r
+ queue.addSound(getResourcePath("sound/rook.mp3"))\r
+ elif "Q" in move:\r
+ queue.addSound(getResourcePath("sound/queen.mp3"))\r
+ elif "O-O" in move or "O-O-O" in move:\r
+ queue.addSound(getResourcePath("sound/castling.mp3"))\r
+\r
+ if "x" in move:\r
+ queue.addSound(getResourcePath("sound/capture.mp3"))\r
+ if "=" in move:\r
+ queue.addSound(getResourcePath("sound/promote.mp3"))\r
+ if "+" in move:\r
+ queue.addSound(getResourcePath("sound/check.mp3"))\r
+ if "#" in move:\r
+ queue.addSound(getResourcePath("sound/checkmate.mp3"))\r
--- /dev/null
+# ChessGame.py\r
+\r
+import tkinter as tk\r
+import _thread\r
+import pickle\r
+import socket\r
+import webbrowser\r
+from tkinter import simpledialog, filedialog, messagebox\r
+from tkinter.font import Font\r
+from PIL import Image, ImageTk\r
+from pawnshop import ChessBoard, Pieces, GameNotations, ChessVector, Exceptions\r
+from Network import Network\r
+from socket import gaierror\r
+from Utils import getResourcePath, fetchImage, squeek\r
+import SoundQueue\r
+\r
+\r
+class LoadingDialog(tk.Toplevel):\r
+ def __init__(self, master, txt, GIFPath=getResourcePath("sprites\\")):\r
+ super().__init__(master, bg="white")\r
+ self._master = master\r
+ self._txt = txt\r
+ self.update_idletasks()\r
+ self.geometry(f"+{master.winfo_x() + 100}+{master.winfo_y() + 100}")\r
+ self.minsize(300, 200)\r
+ self.resizable(False, False)\r
+\r
+ gif = Image.open(GIFPath + "LoadingGIF.gif")\r
+ self._frames = []\r
+ for i in range(0, gif.n_frames):\r
+ gif.seek(i)\r
+ self._frames.append(ImageTk.PhotoImage(gif.resize((100, 100), Image.ANTIALIAS)))\r
+\r
+ self.txtLabel = tk.Label(self, text=self._txt, bg="white", font=("Segoe", 13))\r
+ self.imgLabel = tk.Label(self, image=self._frames[0], bg="white")\r
+ self.cancelButton = tk.Button(self, text="Cancel", font=("Segoe", 13), command=self.close)\r
+ self.cancelButton.focus()\r
+ self.txtLabel.pack(padx=10, pady=10)\r
+ self.imgLabel.pack(padx=10)\r
+ self.cancelButton.pack(padx=10, pady=10)\r
+\r
+ self.title("loading")\r
+\r
+ self.protocol("WM_DELETE_WINDOW", self.close)\r
+\r
+ @property\r
+ def text(self):\r
+ return self._txt\r
+\r
+ @text.setter\r
+ def text(self, text):\r
+ self._txt = text\r
+ self.txtLabel.configure(text=self._txt)\r
+\r
+ def start(self):\r
+ self.grab_set()\r
+ self.startLoad()\r
+ self._master.wait_window(self)\r
+\r
+ def startLoad(self, i=0):\r
+ if i == len(self._frames):\r
+ i = 0\r
+ self.imgLabel.configure(image=self._frames[i])\r
+ self._ID = self.after(50, lambda: self.startLoad(i + 1))\r
+\r
+ def stopLoad(self):\r
+ self.after_cancel(self._ID)\r
+\r
+ def close(self):\r
+ self.stopLoad()\r
+ self.destroy()\r
+\r
+\r
+IMAGEDIR = getResourcePath("sprites\\")\r
+COLORS = {\r
+ "black": (75, 75, 75),\r
+ "white": (255, 255, 255),\r
+ "red": (255, 0, 0),\r
+ "green": (0, 255, 0),\r
+ "blue": (0, 0, 255),\r
+ "yellow": (255, 255, 00)\r
+}\r
+\r
+\r
+class ScrollableLabel(tk.Frame):\r
+ def __init__(self, master, *args, **kwargs):\r
+ super().__init__(master)\r
+\r
+ self.grid_rowconfigure(0, weight=1)\r
+ self.grid_columnconfigure(0, weight=1)\r
+\r
+ self.canvas = tk.Canvas(self, *args, **kwargs)\r
+ self.label = tk.Label(self)\r
+ self.scroll = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)\r
+\r
+ self.canvas.create_window(0, 0, anchor="nw", window=self.label)\r
+ self.canvas.configure(yscrollcommand=self.scroll.set)\r
+\r
+ self.canvas.grid(row=0, column=0, sticky="NSEW")\r
+ self.scroll.grid(row=0, column=1, sticky="NSEW")\r
+\r
+ self.label.bind("<Configure>",\r
+ lambda e: self.canvas.configure(\r
+ scrollregion=self.canvas.bbox("all")))\r
+ self.canvas.bind("<Configure>", self.resize)\r
+\r
+ def resize(self, event):\r
+ self.label.configure(wraplength=event.width - 10)\r
+\r
+\r
+class ChessGame(tk.Tk):\r
+ def __init__(self, *args, **kwargs):\r
+ super().__init__()\r
+\r
+ # Geometry managment\r
+ self.minsize(600, 400)\r
+ self.grid_rowconfigure((0, 1), weight=1)\r
+ self.grid_columnconfigure(0, weight=10)\r
+ self.grid_columnconfigure(1, weight=1)\r
+\r
+ self.boardCanv = tk.Canvas(self, width=400, height=400, bg="black", highlightthickness=0)\r
+ self.historyFrame = tk.Frame(self, width=200, height=200, bg="green", highlightbackground="black", highlightthickness=1)\r
+ self.evalFrame = tk.Frame(self, width=200, height=200, bg="blue", highlightbackground="black", highlightthickness=1)\r
+\r
+ self.historyFrame.grid_rowconfigure(1, weight=1)\r
+ self.historyFrame.grid_columnconfigure(0, weight=1)\r
+ self.evalFrame.grid_rowconfigure(1, weight=1)\r
+ self.evalFrame.grid_columnconfigure(0, weight=1)\r
+\r
+ self.boardCanv.grid(row=0, rowspan=2, column=0, sticky="NSEW")\r
+ self.historyFrame.grid(row=0, column=1, sticky="NSEW")\r
+ self.evalFrame.grid(row=1, column=1, sticky="NSEW")\r
+\r
+ self.historyFrame.grid_propagate(0)\r
+ self.evalFrame.grid_propagate(0)\r
+\r
+ # Chesscanvas related\r
+ self.boardInits = {\r
+ "Empty": ChessBoard.Board,\r
+ "Classic": ChessBoard.initClassic,\r
+ "4P": ChessBoard.init4P\r
+ }\r
+ self.currentBoard = "Empty"\r
+ self.board = self.boardInits[self.currentBoard]()\r
+ self.board.ready = True\r
+ self.kingsInCheck = []\r
+ self.mated = False\r
+ self.selected = None\r
+\r
+ self.square = 64\r
+ self.blackbarTop, self.blackbarBottom, self.blackbarLeft, self.blackbarRight = 0, 0, 0, 0\r
+ # Online\r
+ self.inConnections = []\r
+ self.connection = None\r
+ self.clientColor = None\r
+ self.loadDialog = None\r
+ self.pollingRate = 500\r
+\r
+ # Variables\r
+ self.historyString = tk.StringVar(self)\r
+ self.evalString = tk.StringVar(self)\r
+\r
+ # Fonts\r
+ self.headingFont = Font(family="Curier", size=18, weight="bold")\r
+ self.labelFont = Font(family="Curier", size=12, weight="normal")\r
+\r
+ # Labels\r
+ self.historyHeading = tk.Label(self.historyFrame, text="HISTORY", font=self.headingFont)\r
+ self.evalHeading = tk.Label(self.evalFrame, text="EVALUATION", font=self.headingFont)\r
+ self.historyLabel = ScrollableLabel(self.historyFrame)\r
+ self.evalLabel = ScrollableLabel(self.evalFrame)\r
+\r
+ self.historyLabel.label.configure(textvariable=self.historyString, justify="left", font=self.labelFont)\r
+ self.evalLabel.label.configure(justify="left", font=self.labelFont)\r
+\r
+ self.historyHeading.grid(row=0, column=0, sticky="NSEW")\r
+ self.evalHeading.grid(row=0, column=0, sticky="NSEW")\r
+ self.historyLabel.grid(row=1, column=0, sticky="NSEW")\r
+ self.evalLabel.grid(row=1, column=0, sticky="NSEW")\r
+\r
+ # Menubar\r
+ self.menuBar = tk.Menu(self, tearoff=0)\r
+\r
+ self.gameMenu = tk.Menu(self.menuBar)\r
+ self.newGameMenu = tk.Menu(self.menuBar)\r
+ self.onlineMenu = tk.Menu(self.menuBar)\r
+\r
+ self.newGameMenu.add_command(label="Classic", command=lambda: self.reInitBoard("Classic"))\r
+ self.newGameMenu.add_command(label="Four Players", command=lambda: self.reInitBoard("4P"))\r
+\r
+ self.gameMenu.add_command(label="Save PGN", command=self.exportPGN)\r
+ self.gameMenu.add_command(label="Load PGN", command=self.importPGN)\r
+ self.gameMenu.add_command(label="Copy FEN", command=self.exportFEN)\r
+ self.gameMenu.add_command(label="Paste FEN", command=self.importFEN)\r
+ self.gameMenu.add_separator()\r
+ self.gameMenu.add_cascade(label="New Game", menu=self.newGameMenu)\r
+ self.gameMenu.add_command(label="Reset", command=self.reInitBoard)\r
+ self.gameMenu.add_separator()\r
+ self.gameMenu.add_command(label="Exit", command=lambda: self.destroy())\r
+\r
+ self.onlineMenu.add_command(label="Host game", command=self.hostGame)\r
+ self.onlineMenu.add_command(label="Join game", command=self.joinGame)\r
+ self.onlineMenu.add_command(label="Disconnect", command=self.disconnect, state="disabled")\r
+\r
+ self.menuBar.add_cascade(label="Game", menu=self.gameMenu)\r
+ self.menuBar.add_cascade(label="Online", menu=self.onlineMenu)\r
+ self.menuBar.add_command(label="Help", command=lambda: webbrowser.open("https://www.bible.com/sv"))\r
+\r
+ self.config(menu=self.menuBar)\r
+\r
+ # Chesscanvas actions\r
+ self.boardCanv.bind("<Configure>", self.resize)\r
+ self.boardCanv.bind("<Button-1>", self.chessInteract)\r
+\r
+ # Generate board and pieceimages\r
+ self.drawBoard()\r
+\r
+ # Set icon\r
+ self.iconphoto(True, ImageTk.PhotoImage(Image.open(IMAGEDIR + "\\King.ico")))\r
+\r
+ # Mainloop\r
+ self.mainloop()\r
+\r
+ def drawBoard(self):\r
+ self.selected = None\r
+ self.boardCanv.delete("all")\r
+ self.squares = {}\r
+ self.images = {}\r
+\r
+ for i, piece in enumerate(self.board):\r
+ color = ("saddle brown", "white")[(i + int((i) / self.board.getCols())) % 2 == 0]\r
+ if not isinstance(piece, Pieces.Disabled):\r
+ pos = piece.vector\r
+ self.squares[pos.tuple()] = (self.boardCanv.create_rectangle(\r
+ self.blackbarLeft + (pos.col * self.square),\r
+ self.blackbarTop + (pos.row * self.square),\r
+ self.blackbarLeft + ((pos.col + 1) * self.square),\r
+ self.blackbarTop + ((pos.row + 1) * self.square),\r
+ fill=color,\r
+ outline="black"))\r
+\r
+ if not isinstance(piece, Pieces.Empty):\r
+ self.getImage(piece)\r
+\r
+ self.updateGraphics()\r
+\r
+ def _waitForConn(self):\r
+ self._waitID = self.after(self.pollingRate, self._waitForConn)\r
+\r
+ def _client(self, conn, addr, color):\r
+ MANDATORYFLAGS = {\r
+ "ignoreOrder": False,\r
+ "ignoreMate": False,\r
+ "ignoreCheck": False,\r
+ "checkForCheck": True,\r
+ "checkForMate": True,\r
+ "checkMove": True,\r
+ "printOut": False\r
+ }\r
+ conn.send(pickle.dumps(color))\r
+ while True:\r
+ try:\r
+ data = pickle.loads(conn.recv(1024))\r
+ except EOFError as e:\r
+ print(e)\r
+ conn.close()\r
+ self.disconnect()\r
+ simpledialog.messagebox.showinfo("Lost connection", "Opponent disconnected from game server!")\r
+ break\r
+ except ConnectionAbortedError as e:\r
+ print(e)\r
+ break\r
+\r
+ if data == "break":\r
+ conn.close()\r
+ self.disconnect()\r
+ break\r
+ elif data != "get":\r
+ # Data is of format (args, kwargs)\r
+ args, kwargs = data\r
+\r
+ if color == self.board.currentTurn == color:\r
+ try:\r
+ self.board.movePiece(*args, **{**kwargs, **MANDATORYFLAGS})\r
+ except PromotionError:\r
+ conn.send(pickle.dumps("promote"))\r
+ continue\r
+ except Illegal:\r
+ conn.send(pickle.dumps("illegal"))\r
+ continue\r
+\r
+ conn.send(pickle.dumps(self.board))\r
+\r
+ print("Disconnected from ", addr)\r
+\r
+ def _server(self):\r
+ server = "0.0.0.0"\r
+ port = 10000\r
+ self.mySocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\r
+ self.mySocket.bind((server, port))\r
+ self.mySocket.listen()\r
+ print("Waiting for opponent...")\r
+\r
+ try:\r
+ conn, addr = self.mySocket.accept()\r
+ except OSError:\r
+ print("Stopped waiting for opponents")\r
+ else:\r
+ self.inConnections.append(conn)\r
+ self.reInitBoard("Classic")\r
+ self.loadDialog.close()\r
+ self.after_cancel(self._waitID)\r
+ self._updateID = self.after(self.pollingRate, self._updateVisuals)\r
+ self.onlineMenu.entryconfig("Disconnect", state="normal")\r
+ self.clientColor = "white"\r
+ self._client(conn, addr, "black")\r
+\r
+ def hostGame(self):\r
+ if self.connection is not None:\r
+ self.disconnect()\r
+\r
+ _thread.start_new_thread(self._server, ())\r
+\r
+ self.reInitBoard("Empty")\r
+ self.loadDialog = LoadingDialog(self, txt=f"Waiting for other player to connect to your IP.")\r
+ self.loadDialog.start()\r
+ self._waitID = self.after(self.pollingRate, lambda: self._checkLoading(loop=True))\r
+\r
+ def _updateVisuals(self):\r
+ self.updateGraphics()\r
+ self._updateID = self.after(self.pollingRate, self._updateVisuals)\r
+\r
+ def _checkLoading(self, loop=False):\r
+ if self.loadDialog.winfo_exists():\r
+ if loop:\r
+ self.after(self.pollingRate, lambda: self._checkLoading(loop=True))\r
+ return True\r
+ else:\r
+ self.disconnect()\r
+ del self.loadDialog\r
+ return False\r
+\r
+ def joinGame(self):\r
+ if self.connection is not None:\r
+ self.disconnect()\r
+ ip = simpledialog.askstring("IP-adress", "IP-adress of host?")\r
+ if not ip:\r
+ return\r
+ self.connection = Network()\r
+ try:\r
+ self.clientColor = self.connection.connect(ip)\r
+ except (gaierror, ConnectionRefusedError) as e:\r
+ print(e)\r
+ simpledialog.messagebox.showinfo("Could not Connect", f"Could not connect to \"{ip}\"!")\r
+ return\r
+ if not self._waitForGame():\r
+ self.reInitBoard("Empty")\r
+ self.loadDialog = LoadingDialog(self, txt="Waiting for opponent\n to connect!")\r
+ self.loadDialog.start()\r
+\r
+ def _waitForGame(self):\r
+ echo = self.connection.send("get")\r
+\r
+ if isinstance(echo, ChessBoard.Board):\r
+ try:\r
+ self.loadDialog.close()\r
+ del self.loadDialog\r
+ except AttributeError:\r
+ pass\r
+\r
+ self.onlineMenu.entryconfig("Disconnect", state="normal")\r
+ self.board = echo\r
+ self.drawBoard()\r
+ self._updateID = self.after(self.pollingRate, self._updateBoard)\r
+ return True\r
+ else:\r
+ try:\r
+ if not self._checkLoading():\r
+ return False\r
+ except AttributeError:\r
+ pass\r
+ self._waitID = self.after(self.pollingRate, self._waitForGame)\r
+ return False\r
+\r
+ def _updateBoard(self):\r
+ try:\r
+ newBoard = self.connection.send("get")\r
+ except ConnectionResetError:\r
+ self.disconnect()\r
+ simpledialog.messagebox.showinfo("Lost connection", "Lost connection to server!")\r
+ return\r
+ if newBoard is None:\r
+ self.disconnect()\r
+ simpledialog.messagebox.showinfo("Lost connection", "Opponent disconnected from game server!")\r
+ else:\r
+ if newBoard != self.board:\r
+ self.board = newBoard\r
+ squeek(self.board.getHistory()[-1])\r
+ self.clearHighlights()\r
+ self.updateGraphics()\r
+\r
+ self._updateID = self.after(self.pollingRate, self._updateBoard)\r
+\r
+ def disconnect(self):\r
+ try:\r
+ self.after_cancel(self._waitID)\r
+ except Exception:\r
+ pass\r
+ try:\r
+ self.after_cancel(self._updateID)\r
+ except Exception:\r
+ pass\r
+\r
+ self.onlineMenu.entryconfig("Disconnect", state="disabled")\r
+ for conn in self.inConnections:\r
+ conn.close()\r
+ try:\r
+ self.connection.close()\r
+ except AttributeError:\r
+ pass\r
+ try:\r
+ self.mySocket.close()\r
+ except AttributeError:\r
+ pass\r
+ self.connection = None\r
+ self.clientColor = None\r
+ self.reInitBoard("Empty")\r
+\r
+ def importPGN(self):\r
+ file = filedialog.askopenfile(title="Open PGN file", filetypes=[("PGN file", ".pgn"), ("Text file", ".txt")])\r
+ if file is None:\r
+ return\r
+ self.board = PGN2Board(file.read())\r
+ self.drawBoard()\r
+\r
+ def exportPGN(self):\r
+ file = filedialog.asksaveasfile(title="Save PGN file", defaultextension=".pgn", filetypes=[("PGN file", ".pgn")])\r
+ if file is None:\r
+ return\r
+ file.write(board2PGN(self.board, players=2))\r
+\r
+ def exportFEN(self):\r
+ self.clipboard_append(board2FEN(self.board))\r
+ simpledialog.messagebox.showinfo("FEN Copied!", "FEN-string has been copied to clipboard!")\r
+\r
+ def importFEN(self):\r
+ FEN = simpledialog.askstring("Load FEN!", "Enter a FEN string to load into the board.")\r
+ self.board = FEN2Board(FEN)\r
+ self.drawBoard()\r
+\r
+ def reInitBoard(self, boardType=None):\r
+ if boardType is not None:\r
+ self.currentBoard = boardType\r
+ self.board = self.boardInits[self.currentBoard]()\r
+ self.board.ready = True\r
+ self.resize()\r
+ self.drawBoard()\r
+\r
+ def getImage(self, piece):\r
+ """Get image of given piece"""\r
+ try:\r
+ return self.images[piece.color + piece.__class__.__name__]\r
+ except KeyError:\r
+ return self.createImage(piece)\r
+\r
+ def createImage(self, piece):\r
+ """Create and return image of given piece"""\r
+ img = fetchImage(COLORS[piece.color], IMAGEDIR + piece.__class__.__name__ + ".png")\r
+ self.images[piece.color + piece.__class__.__name__] = img\r
+ return img\r
+\r
+ def chessInteract(self, event, *args):\r
+ if not self.mated and self.board.ready:\r
+ if event.x + self.blackbarRight < self.boardCanv.winfo_width() and event.x - self.blackbarLeft > 0 and event.y + self.blackbarBottom < self.boardCanv.winfo_height() and event.y - self.blackbarTop > 0:\r
+ vec = ChessVector.ChessVector((int((event.y - self.blackbarTop) / self.square), int((event.x - self.blackbarRight) / self.square)))\r
+\r
+ try:\r
+ piece = self.board[vec]\r
+ except DisabledError:\r
+ pass\r
+ else:\r
+ if self.selected is not None and vec.matches(self.selected.getMoves(self.board)):\r
+ self.moveSelected(vec)\r
+\r
+ self.clearHighlights()\r
+\r
+ elif isinstance(piece, Pieces.Piece) and piece.color == self.board.currentTurn and (self.clientColor is None or self.clientColor == piece.color):\r
+ self.select(piece, highlights=piece.getMoves(self.board))\r
+ else:\r
+ self.clearHighlights()\r
+\r
+ if any(self.board.getCheckmates().values()):\r
+ while True:\r
+ if len(self.board.getCheckmates().keys()) > 2:\r
+ for color, value in self.board.getCheckmates().items():\r
+ if value:\r
+ self.board.removeColor(color)\r
+ self.updateGraphics()\r
+ break\r
+ else:\r
+ break\r
+ else:\r
+ messagebox.showinfo("Checkmate!", f"{[color for color, value in self.board.getCheckmates().items() if value].pop()} has been checkmated!")\r
+ self.mated = True\r
+ break\r
+\r
+ elif not self.board.ready:\r
+ print("Board is not ready yet.")\r
+\r
+ def moveSelected(self, vector, *args, **kwargs):\r
+ promote = False\r
+ try:\r
+ if self.connection is not None:\r
+ echo = self.connection.send(((self.selected.vector, vector, *args), kwargs))\r
+ if isinstance(echo, ChessBoard.Board):\r
+ self.board = echo\r
+ elif echo == "promote":\r
+ raise Exceptions.PromotionError\r
+ else:\r
+ print("something went wrong")\r
+ else:\r
+ self.board.movePiece(self.selected.vector, vector, *args, checkMove=False, **kwargs)\r
+ except Exceptions.PromotionError:\r
+ promote = True\r
+ msg = "What do you want the pawn to promote to?"\r
+ while True:\r
+ prompt = simpledialog.askstring("Promote!", msg)\r
+ if prompt is None:\r
+ break\r
+ try:\r
+ pType = {pType.__name__: pType for pType in self.board.getPromoteTo(self.selected.color)}[prompt.lower().capitalize()]\r
+ except KeyError:\r
+ msg = prompt + "is Not a valid piece, must be any of \n" + "\n".join([pType.__name__ for pType in self.board.getPromoteTo(self.selected.color)])\r
+ continue\r
+ else:\r
+ self.moveSelected(vector, promote=pType)\r
+ break\r
+ finally:\r
+ squeek(self.board.getHistory()[-1])\r
+\r
+ self.updateGraphics()\r
+\r
+ def select(self, piece, highlights=None):\r
+ self.clearHighlights()\r
+ self.selected = piece\r
+ self.boardCanv.itemconfigure(self.squares[piece.vector.tuple()], fill="light goldenrod")\r
+ self.highlight(highlights)\r
+\r
+ def highlightKings(self):\r
+ if any(self.board.getChecks().values()):\r
+ for color in [col for col, value in self.board.getChecks().items() if value]:\r
+ for king in self.board.getKings()[color]:\r
+ if self.board.isThreatened(king.vector, color):\r
+ if king not in self.kingsInCheck:\r
+ self.kingsInCheck.append(king)\r
+ else:\r
+ if king in self.kingsInCheck:\r
+ self.kingsInCheck.remove(king)\r
+ else:\r
+ self.kingsInCheck = []\r
+\r
+ for king in self.kingsInCheck:\r
+ self.boardCanv.itemconfigure(self.squares[king.vector.tuple()], fill="red")\r
+\r
+ def clearHighlights(self):\r
+ self.selected = None\r
+ for i, piece in enumerate(self.board):\r
+ color = ("saddle brown", "white")[(i + int((i) / self.board.getCols())) % 2 == 0]\r
+ if not isinstance(piece, Pieces.Disabled):\r
+ square = self.squares[piece.vector.tuple()]\r
+ if self.boardCanv.itemcget(square, "fill") != color:\r
+ self.boardCanv.itemconfig(square, fill=color)\r
+\r
+ self.highlightKings()\r
+\r
+ def highlight(self, vecs):\r
+ for vec in vecs:\r
+ self.boardCanv.itemconfigure(self.squares[vec.tuple()], fill="sky blue")\r
+\r
+ def resize(self, *args):\r
+ self.update_idletasks()\r
+ rankH = int(self.boardCanv.winfo_width() / self.board.getRows())\r
+ fileH = int(self.boardCanv.winfo_height() / self.board.getCols())\r
+ self.square = (rankH, fileH)[rankH > fileH]\r
+ self.blackbarLeft = int((self.boardCanv.winfo_width() - (self.board.getRows() * self.square)) / 2)\r
+ self.blackbarRight = self.boardCanv.winfo_width() - self.blackbarLeft - (self.board.getRows() * self.square)\r
+ self.blackbarTop = int((self.boardCanv.winfo_height() - (self.board.getCols() * self.square)) / 2)\r
+ self.blackbarBottom = self.boardCanv.winfo_height() - self.blackbarTop - (self.board.getCols() * self.square)\r
+\r
+ self.updateGraphics()\r
+\r
+ def updateGraphics(self):\r
+ """Update all widgets of frame"""\r
+ self.historyString.set(GameNotations.readable(self.board.getHistory(), len(self.board.getTurnorder()))) # this should be a getHistory function\r
+ self.photos = []\r
+ for (row, col), square in self.squares.items():\r
+ self.boardCanv.coords(square,\r
+ self.blackbarLeft + (col * self.square),\r
+ self.blackbarTop + (row * self.square),\r
+ self.blackbarLeft + ((col + 1) * self.square),\r
+ self.blackbarTop + ((row + 1) * self.square))\r
+\r
+ for piece in self.board:\r
+ if not isinstance(piece, Pieces.Disabled):\r
+ pos = piece.vector\r
+\r
+ if not isinstance(piece, Pieces.Empty):\r
+ img = self.getImage(piece).copy().resize((self.square, self.square))\r
+ photoImg = ImageTk.PhotoImage(img)\r
+\r
+ # To keep an image on the canvas, a reference to the ImageTk.PhotoImage\r
+ # object needs to be stored. I have no idea why this is, but oh well,\r
+ # I will store the reference in the photos list.\r
+ self.photos.append(photoImg)\r
+\r
+ self.boardCanv.create_image(\r
+ self.blackbarLeft + (pos.col * self.square) + int(self.square / 2),\r
+ self.blackbarTop + (pos.row * self.square) + int(self.square / 2),\r
+ image=photoImg)\r
+\r
+\r
+if __name__ == "__main__":\r
+ ChessGame()\r
--- /dev/null
+# -*- mode: python ; coding: utf-8 -*-\r
+\r
+\r
+block_cipher = None\r
+\r
+\r
+a = Analysis(['main.py'],\r
+ pathex=['C:\\Users\\forss\\Documents\\Python\\pawnshop-implementation-GUI'],\r
+ binaries=[],\r
+ datas=[('sound/*', 'sound/'), ('sprites/*', 'sprites/'), ('C:/Users/forss/Documents/myVenv/Lib/site-packages/pawnshop/configurations/*', 'pawnshop/configurations/')],\r
+ hiddenimports=[],\r
+ hookspath=[],\r
+ runtime_hooks=[],\r
+ excludes=[],\r
+ win_no_prefer_redirects=False,\r
+ win_private_assemblies=False,\r
+ cipher=block_cipher,\r
+ noarchive=False)\r
+pyz = PYZ(a.pure, a.zipped_data,\r
+ cipher=block_cipher)\r
+exe = EXE(pyz,\r
+ a.scripts,\r
+ a.binaries,\r
+ a.zipfiles,\r
+ a.datas,\r
+ [],\r
+ name='main',\r
+ debug=False,\r
+ bootloader_ignore_signals=False,\r
+ strip=False,\r
+ upx=True,\r
+ upx_exclude=[],\r
+ runtime_tmpdir=None,\r
+ console=True , icon='sprites\\King.ico')\r
--- /dev/null
+[LocalizedFileNames]\r
+pawn.mp3=@pawn.mp3,0\r
--- /dev/null
+import playsound\r
+import time\r
+from threading import Thread\r
+\r
+\r
+class SoundQueue(Thread):\r
+ def __init__(self):\r
+ super().__init__(daemon=True)\r
+ self.fileList = []\r
+\r
+ def addSound(self, mp3path):\r
+ self.fileList.append(mp3path)\r
+\r
+ def run(self):\r
+ for file in self.fileList:\r
+ playsound.playsound(file, True)\r
+\r
+\r
+first = SoundQueue()\r
+second = SoundQueue()\r
+for i in range(2):\r
+ first.addSound("sound/pawn.mp3")\r
+ second.addSound("sound/pawn.mp3")\r
+\r
+\r
+first.start()\r
+\r
+for i in range(5):\r
+ first.addSound("sound/pawn.mp3")\r
+ time.sleep(1)\r
+ print("main")\r
--- /dev/null
+# Pawnshop\r
+A simple chess package for python written solely as hobby project. I've also written a GUI with networking socket capabilities (add link here) and might create some AI furter down the line.\r
+\r
+The package also includes a 4-player mode, although the ruleset is unchanged from the classic game, Checkmate -> Game Over!\r
+## Background\r
+This is my first real project to scale (> 1000 lines of code) and the first to be published to [PyPi](https://pypi.org/project/pawnshop/).\r
+\r
+The project has certainly been a product of time with other hobby projects and school often taking priority. This along with multiple mid-project large scale refractorizations, reconsiderations and laziness should explain the unorganized codebase.\r
+\r
+Needless to say, I've attempted to document the code well and create a proper package that I can "proudly" publish, Although I still don't recommend anyone to continue developing or using this outside of simple projects like this. There are definitely better alternatives out there.\r
+\r
+Some central documentation, more extensive testing and usage examples would have been beneficial, but as of now I just want to continue with other projects and leave this as finished for the time being.\r
--- /dev/null
+[testpypi]\r
+username = __token__\r
+password = pypi-AgENdGVzdC5weXBpLm9yZwIkZGVmMzQ2NmItYTRiZS00NDQyLWI0NGMtMjE1YjA2YTdhYTA2AAIleyJwZXJtaXNzaW9ucyI6ICJ1c2VyIiwgInZlcnNpb24iOiAxfQAABiBeZ1fDv4uC4Jd4PqJ6DyIgOIzHaihlRwrxALZuKY5fvA\r
+\r
+[pypi]\r
+username = __token__\r
+password = pypi-AgEIcHlwaS5vcmcCJDA4OGFhYzUxLWU0ZGQtNDk5YS1hNzlmLTA0OTJmNDVkOTEzNwACJXsicGVybWlzc2lvbnMiOiAidXNlciIsICJ2ZXJzaW9uIjogMX0AAAYgYA0Y_MBPziPX1LkFMOTOyk1Az8oj6qVAhVFdz2OUW34\r
--- /dev/null
+Metadata-Version: 2.1\r
+Name: pawnshop\r
+Version: 1.0.3\r
+Summary: A simple chess library as hobby project.\r
+Home-page: https://github.com/NilsForssen/pawnshop\r
+Author: Nils Forssén\r
+Author-email: forssennils@gmail.com\r
+License: UNKNOWN\r
+Description: # Pawnshop\r
+ A simple chess package for python written solely as hobby project. I've also written a GUI with networking socket capabilities (add link here) and might create some AI furter down the line.\r
+ \r
+ The package also includes a 4-player mode, although the ruleset is unchanged from the classic game, Checkmate -> Game Over!\r
+ ## Background\r
+ This is my first real project to scale (> 1000 lines of code) and the first to be published to [PyPi](https://pypi.org/project/pawnshop/).\r
+ \r
+ The project has certainly been a product of time with other hobby projects and school often taking priority. This along with multiple mid-project large scale refractorizations, reconsiderations and laziness should explain the unorganized codebase.\r
+ \r
+ Needless to say, I've attempted to document the code well and create a proper package that I can "proudly" publish, Although I still don't recommend anyone to continue developing or using this outside of simple projects like this. There are definitely better alternatives out there.\r
+ \r
+ Some central documentation, more extensive testing and usage examples would have been beneficial, but as of now I just want to continue with other projects and leave this as finished for the time being.\r
+ \r
+Platform: UNKNOWN\r
+Classifier: Programming Language :: Python :: 3\r
+Classifier: License :: OSI Approved :: MIT License\r
+Classifier: Operating System :: OS Independent\r
+Requires-Python: >=3.6\r
+Description-Content-Type: text/markdown\r
--- /dev/null
+.gitattributes
+.gitignore
+LICENSE
+README.md
+setup.py
+dist/pawnshop-1.0.3-py3-none-any.whl
+dist/pawnshop-1.0.3.tar.gz
+pawnshop/ChessBoard.py
+pawnshop/ChessVector.py
+pawnshop/Exceptions.py
+pawnshop/GameNotations.py
+pawnshop/Moves.py
+pawnshop/Pieces.py
+pawnshop/Utils.py
+pawnshop/__init__.py
+pawnshop.egg-info/PKG-INFO
+pawnshop.egg-info/SOURCES.txt
+pawnshop.egg-info/dependency_links.txt
+pawnshop.egg-info/top_level.txt
+pawnshop/configurations/ClassicConfig.py
+pawnshop/configurations/DefaultConfig.JSON
+pawnshop/configurations/FourPlayerConfig.py
+pawnshop/configurations/__init__.py
+tests/test_1.py
+tests/test_2.py
\ No newline at end of file
--- /dev/null
+# ChessBoard.py\r
+\r
+import json\r
+import os\r
+from copy import deepcopy, copy\r
+from functools import wraps\r
+from typing import Union, List, Dict, Generator\r
+\r
+from .ChessVector import ChessVector\r
+from .Pieces import *\r
+from .Moves import *\r
+from .configurations import ClassicConfig, FourPlayerConfig\r
+from .Utils import countAlpha, getResourcePath\r
+from .Exceptions import *\r
+\r
+\r
+def _defaultColors(func):\r
+ @wraps(func)\r
+ def wrapper(self, *colors):\r
+ if not colors:\r
+ colors = self.getColors()\r
+ returned = func(self, *colors)\r
+ if isinstance(returned, dict) and len(returned) == 1:\r
+ returned = returned.pop(*colors)\r
+ return returned\r
+ return wrapper\r
+\r
+\r
+class Board():\r
+ """Board object for storing and moving pieces\r
+\r
+ :param config: Board configuration (defaults to emtpy board)\r
+ """\r
+\r
+ def __init__(self, config={}):\r
+\r
+ self._board = []\r
+ with open(getResourcePath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "configurations/DefaultConfig.JSON")), "r") as default:\r
+ dConfig = json.load(default)\r
+\r
+ self._rows = config.get("rows") or dConfig.get("rows")\r
+ self._cols = config.get("cols") or dConfig.get("cols")\r
+ self._pieces = config.get("pieces") or dConfig.get("pieces")\r
+ self._moves = config.get("moves") or dConfig.get("moves")\r
+ self._promoteTo = config.get("promoteTo") or dConfig.get("promoteTo")\r
+ self._promoteFrom = config.get("promoteFrom") or dConfig.get("promoteFrom")\r
+ self._promoteAt = config.get("promoteAt") or dConfig.get("promteAt")\r
+ self._turnorder = config.get("turnorder") or dConfig.get("turnorder")\r
+\r
+ try:\r
+ self.currentTurn = self._turnorder[0]\r
+ except IndexError:\r
+ self.currentTurn = None\r
+ self._board = [[Empty(ChessVector((row, col))) for col in range(self._cols)] for row in range(self._rows)]\r
+\r
+ for color, pieceList in self._pieces.items():\r
+ for piece in pieceList:\r
+ self[piece.vector] = piece\r
+\r
+ for vec in config.get("disabled") or dConfig.get("disabled"):\r
+ self[vec] = Disabled(vec)\r
+\r
+ self._checks = {key: False for key in self._pieces.keys()}\r
+ self._checkmates = copy(self._checks)\r
+ self._kings = {key: [piece for piece in self._pieces[key] if isinstance(piece, King)] for key in self._pieces.keys()}\r
+ self._history = []\r
+\r
+ self.checkForCheck()\r
+\r
+ def __eq__(self, other):\r
+ for p1, p2 in zip(self, other):\r
+ if type(p1) == type(p2):\r
+ continue\r
+ else:\r
+ break\r
+\r
+ else:\r
+ return True\r
+ return False\r
+\r
+ def __ne__(self, other):\r
+ return not self == other\r
+\r
+ def __iter__(self):\r
+ """Iterates through all positions in board\r
+\r
+ Use iterPieces() method to iterate through pieces of board.\r
+ """\r
+ for p in [p for row in self._board for p in row]:\r
+ yield p\r
+\r
+ def __str__(self):\r
+ string = "\n"\r
+ ending = "\t|\n\n\t\t__" + ("\t__" * self._cols) + "\n\n\t\t"\r
+ alpha = countAlpha()\r
+\r
+ for row in self._board:\r
+\r
+ num, char = next(alpha)\r
+\r
+ string += str(self._rows - num) + "\t|\t\t"\r
+\r
+ for piece in row:\r
+ string += str(piece) + "\t"\r
+\r
+ string += "\n\n"\r
+\r
+ ending += "\t" + char.upper()\r
+\r
+ return string + ending + "\n\n"\r
+\r
+ def __setitem__(self, index, item):\r
+\r
+ try:\r
+ iter(item)\r
+ except TypeError:\r
+ item = [item]\r
+\r
+ try:\r
+ iter(index)\r
+ except TypeError:\r
+ index = [index]\r
+\r
+ if len(index) != len(item):\r
+ raise ValueError("List index expected {0} values to unpack but {1} were given".format(\r
+ len(item), len(index)))\r
+\r
+ for i, vec in enumerate(index):\r
+\r
+ if isinstance(self._board[vec.row][vec.col], Disabled):\r
+ raise DisabledError(vec.getStr(self))\r
+\r
+ item1 = self._board[vec.row][vec.col]\r
+ item2 = item[i]\r
+\r
+ if not isinstance(item2, Disabled):\r
+\r
+ if not isinstance(item1, Empty):\r
+ self._removePiece(item1)\r
+\r
+ if not isinstance(item2, Empty):\r
+\r
+ if item2 in self.iterPieces(item2.color):\r
+ pass\r
+ else:\r
+ self._addPiece(item2, vec)\r
+\r
+ self._board[vec.row][vec.col] = item2\r
+\r
+ def __getitem__(self, index):\r
+ res = []\r
+\r
+ try:\r
+ iter(index)\r
+ except TypeError:\r
+ index = [index]\r
+\r
+ for vec in index:\r
+ if isinstance(self._board[vec.row][vec.col], Disabled):\r
+ raise DisabledError(vec.getStr(self))\r
+ res.append(self._board[vec.row][vec.col])\r
+\r
+ if len(res) == 1:\r
+ return res.pop()\r
+ else:\r
+ return res\r
+\r
+ def getRows(self) -> int:\r
+ """Get rows in board\r
+\r
+ :returns: Number of rows in board\r
+ :rtype: ``int``\r
+ """\r
+ return self._rows\r
+\r
+ def getCols(self) -> int:\r
+ """Get columns in board\r
+\r
+ :returns: Number of columns in board\r
+ :rtype: ``int``\r
+ """\r
+ return self._cols\r
+\r
+ def getHistory(self) -> list:\r
+ """Get history list of board\r
+\r
+ :returns: History of board\r
+ :rtype: ``list``\r
+ """\r
+ return self._history\r
+\r
+\r
+ def getTurnorder(self) -> list:\r
+ """Get turnorder list of board\r
+\r
+ :returns: Turnorder of board\r
+ :rtype: ``list``\r
+ """\r
+ return self._turnorder\r
+\r
+ @_defaultColors\r
+ def getChecks(self, *colors: str) -> Union[bool, Dict[str, bool]]:\r
+ """Get checks in board\r
+\r
+ If more than one color is given, this returns a ``dict``\r
+ with a ``bool`` corresponding to each color.\r
+\r
+ :param *colors: Colors to return\r
+ :returns: If colors are in check or not\r
+ :rtype: ``bool`` or ``dict``\r
+ """\r
+ return {col: self._checks[col] for col in colors}\r
+\r
+ @_defaultColors\r
+ def getCheckmates(self, *colors: str) -> Union[bool, Dict[str, bool]]:\r
+ """Get checkmates in board\r
+\r
+ If more than one color is given, this returns a ``dict``\r
+ with a ``bool`` corresponding to each color.\r
+\r
+ :param *colors: Colors to return\r
+ :returns: If colors are in checkmate or not\r
+ :rtype: ``bool`` or ``dict``\r
+ """\r
+ return {col: self._checkmates[col] for col in colors}\r
+\r
+ @_defaultColors\r
+ def getKings(self, *colors: str) -> Union[List[King], Dict[str, List[King]]]:\r
+ """Get kings in board\r
+\r
+ If more than one color is given, this returns a ``dict``\r
+ with a ``list`` of kings corresponding to each color.\r
+\r
+ :param *colors: Colors to return\r
+ :returns: All kings of colors in board\r
+ :rtype: ``list`` or ``dict``\r
+ """\r
+ return {col: self._kings[col] for col in colors}\r
+\r
+ @_defaultColors\r
+ def getMoves(self, *colors: str) -> Union[List[Move], Dict[str, List[Move]]]:\r
+ """Get moves of board\r
+\r
+ If more than one color is given, this returns a ``dict``\r
+ with a ``list`` of moves corresponding to each color.\r
+\r
+ :param *colors: Colors to return\r
+ :returns: All moves of colors in board\r
+ :rtype: ``list`` or ``dict``\r
+ """\r
+ return {col: self._moves[col] for col in colors}\r
+\r
+ @_defaultColors\r
+ def getPromoteAt(self, *colors: str) -> Union[int, Dict[str, int]]:\r
+ """Get promotion position of board\r
+\r
+ If more than one color is given, this returns a ``dict``\r
+ with a ``int`` corresponding to each color.\r
+\r
+ :param *colors: Colors to return\r
+ :returns: The promotion position of colors in board\r
+ :rtype: ``list`` or ``dict``\r
+ """\r
+ return {col: self._promoteAt[col] for col in colors}\r
+\r
+ @_defaultColors\r
+ def getPromoteFrom(self, *colors: str) -> Union[List[Piece], Dict[str, List[Piece]]]:\r
+ """Get promotion starting pieces of board\r
+\r
+ If more than one color is given, this returns a ``dict``\r
+ with a ``list`` corresponding to each color.\r
+\r
+ :param *colors: Colors to return\r
+ :returns: The promotion starting piece types of colors in board\r
+ :rtype: ``list`` or ``dict``\r
+ """\r
+ return {col: self._promoteFrom[col] for col in colors}\r
+\r
+ @_defaultColors\r
+ def getPromoteTo(self, *colors: str) -> Union[List[Piece], Dict[str, List[Piece]]]:\r
+ """Get promotion target pieces of board\r
+\r
+ If more than one color is given, this returns a ``dict``\r
+ with a ``list`` corresponding to each color.\r
+\r
+ :param *colors: Colors to return\r
+ :returns: The promotion target piece types of colors in board\r
+ :rtype: ``list`` or ``dict``\r
+ """\r
+ return {col: self._promoteTo[col] for col in colors}\r
+\r
+ def getColors(self) -> List[str]:\r
+ """Get all colors of board\r
+\r
+ :returns: List of colors in board\r
+ :rtype: ``list``\r
+ """\r
+ return self._pieces.keys()\r
+\r
+ @_defaultColors\r
+ def iterPieces(self, *colors: str) -> Generator[Piece, None, None]:\r
+ """Iterate through pieces of board\r
+\r
+ Use __iter__ to iterate through all positions of the board.\r
+\r
+ :param *colors: Colors of pieces to iterate through (default is all colors)\r
+ :yields: Every piece in board\r
+ :ytype: ``generator``\r
+ """\r
+ for col in colors:\r
+ for p in self._pieces[col]:\r
+ yield p\r
+\r
+ @_defaultColors\r
+ def eval(self, *colors: str) -> Dict[str, int]:\r
+ """Evaluate board\r
+\r
+ Returns the sum of all pieces' values of colors in board\r
+\r
+ :param *colors: Colors to evaluate (defaults to all colors of board)\r
+\r
+ :returns: Colors with corresponding sum of pieces\r
+ :rtype: ``dict``\r
+ """\r
+ return {col: sum(list(map(lambda p: p.value, list(self.iterPieces(col))))) for col in colors}\r
+\r
+ def removeColor(self, color: str) -> None:\r
+ """Remove color from board\r
+\r
+ :param color: Color to remove\r
+ """\r
+ vectors = list(map(lambda p: p.vector, list(self.iterPieces(color))))\r
+ self[vectors] = [Empty(vector) for vector in vectors]\r
+ self.checkForCheck()\r
+\r
+ def swapPositions(self, vec1: ChessVector, vec2: ChessVector) -> None:\r
+ """Swap position of two pieces\r
+\r
+ :param vec1: Starting position of first piece\r
+ :param vec2: Starting position of second piece\r
+ """\r
+ self._board[vec1.row][vec1.col].move(vec2)\r
+ self._board[vec2.row][vec2.col].move(vec1)\r
+ self._board[vec1.row][vec1.col], self._board[vec2.row][vec2.col] = self._board[vec2.row][vec2.col], self._board[vec1.row][vec1.col]\r
+\r
+ def isEmpty(self, vec: ChessVector) -> bool:\r
+ """Check if position is empty\r
+\r
+ :param vec: Position to check\r
+ :returns: True if position is empty, else False\r
+ :rtype: ``bool``\r
+ """\r
+ return isinstance(self[vec], Empty)\r
+\r
+ def isThreatened(self, vec: ChessVector, alliedColor: str) -> bool:\r
+ """Check if position is threatened by enemy pieces\r
+\r
+ :param vector: Position to check for threats\r
+ :param alliedColor: Color to exclude from enemy pieces\r
+ :returns: True if position is threatened, else False\r
+ :rtype: ``bool``\r
+ """\r
+ hostilePieces = [piece for col in self.getColors() if col != alliedColor for piece in self.iterPieces(col)]\r
+\r
+ for hp in hostilePieces:\r
+ hostile = hp.getMoves(self, ignoreCheck=True)\r
+ if vec in hostile:\r
+ return True\r
+ else:\r
+ return False\r
+\r
+ def checkForCheck(self, checkForMate=True) -> None:\r
+ """Check for any checks in board\r
+\r
+ If checkForMate is True and king is in check,\r
+ method checks if any allied pieces can move to\r
+ interfere with the threatened check.\r
+\r
+ :param checkForMate: Flag False to ignore checkmate (default is True)\r
+ :returns: None, stores result in attributes ``checks`` and ``checkmates``\r
+ """\r
+ for color in self.getColors():\r
+\r
+ for alliedKing in self._kings[color]:\r
+\r
+ if self.isThreatened(alliedKing.vector, color):\r
+ self._checks[color] = True\r
+ break\r
+ else:\r
+ self._checks[color] = False\r
+\r
+ if self._checks[color] and checkForMate:\r
+\r
+ alliedPiecesPos = map(lambda p: p.vector, list(self.iterPieces(color)))\r
+\r
+ for alliedPos in list(alliedPiecesPos):\r
+ for move in self[alliedPos].getMoves(self, ignoreCheck=True):\r
+ testBoard = deepcopy(self)\r
+ for pieceType in [None, *self._promoteTo[color]]:\r
+ try:\r
+ testBoard.movePiece(alliedPos, move, ignoreMate=True,\r
+ checkForMate=False, promote=pieceType,\r
+ printout=False, checkMove=False, ignoreOrder=True)\r
+ except PromotionError:\r
+ continue\r
+ else:\r
+ break\r
+\r
+ if testBoard._checks[color]:\r
+ continue\r
+ else:\r
+ self._checkmates[color] = False\r
+ break\r
+ else:\r
+ continue\r
+ break\r
+ else:\r
+ self._checkmates[color] = True\r
+\r
+ def advanceTurn(self) -> None:\r
+ """Advance the turn according to turnorder\r
+ """\r
+ newidx = self._turnorder.index(self.currentTurn) + 1\r
+ try:\r
+ self.currentTurn = self._turnorder[newidx]\r
+ except IndexError:\r
+ self.currentTurn = self._turnorder[0]\r
+\r
+ def movePiece(self, startVec: ChessVector, targetVec: ChessVector,\r
+ ignoreOrder=False, ignoreMate=False, ignoreCheck=False,\r
+ checkForCheck=True, checkForMate=True, checkMove=True,\r
+ printout=True, promote=None) -> str:\r
+ """Move piece on board\r
+\r
+ :param startVec: Position of moving piece\r
+ :param targetVec: Destination of moving piece\r
+ :param **Flags: Flags altering move rules, see below\r
+ :returns: Notation of move\r
+ :rtype: ``str``\r
+\r
+ :**Flags:\r
+ :ignoreOrder (False): Ignore the turnorder\r
+ :ignoreMate (False): Ignore if any pieces are in checkmate\r
+ :ignoreCheck (False): Ignore if any pieces are in check\r
+ :checkForCheck (True): Check for any checks after move\r
+ :checkForMate (True): Check for any checkmates after move\r
+ :checkMove (True): Check if piece is able to move to destination\r
+ :printout (True): Print the results of the move; checks, checkmates and move notation\r
+ :promote (None): Piece type to promote to\r
+ """\r
+\r
+ if self.isEmpty(startVec):\r
+ raise EmptyError(startVec.getStr(self))\r
+\r
+ startPiece = self[startVec]\r
+\r
+ if not ignoreOrder and self.currentTurn != startPiece.color:\r
+ raise TurnError\r
+\r
+ if self._checkmates[startPiece.color] and not ignoreMate:\r
+ raise CheckMate\r
+\r
+ if checkMove and not targetVec.matches(startPiece.getMoves(self, ignoreCheck=ignoreCheck, ignoreMate=ignoreMate)):\r
+ raise IllegalMove(startVec.getStr(self), targetVec.getStr(self))\r
+\r
+ for move in self._moves[startPiece.color]:\r
+ if move.pieceCondition(startPiece):\r
+ if targetVec in move.getDestinations(startPiece, self):\r
+ notation = move.action(startPiece, targetVec, self, promote)\r
+ if checkForCheck:\r
+ self.checkForCheck(checkForMate=checkForMate)\r
+ break\r
+\r
+ else:\r
+ raise IllegalMove(startVec.getStr(self), targetVec.getStr(self))\r
+\r
+ for color in self._checks.keys():\r
+ if self._checkmates[color]:\r
+ if printout:\r
+ print(f"{color} in Checkmate!")\r
+ if not "#" in notation:\r
+ notation += "#"\r
+\r
+ elif self._checks[color]:\r
+ if printout:\r
+ print(f"{color} in Check!")\r
+ if not "+" in notation:\r
+ notation += "+"\r
+\r
+ for piece in self.iterPieces():\r
+\r
+ if not piece is startPiece:\r
+ piece.postAction(self)\r
+\r
+ self._history.append(notation)\r
+ if printout:\r
+ print(notation)\r
+\r
+ self.advanceTurn()\r
+ return notation\r
+\r
+ def _addPiece(self, piece: Piece, vec: ChessVector) -> None:\r
+ if not piece.color in self.getColors():\r
+ self._pieces[piece.color] = []\r
+ self._kings[piece.color] = []\r
+ self._checks[piece.color] = False\r
+ self._checkmates[piece.color] = False\r
+\r
+ self._pieces[piece.color].append(piece)\r
+\r
+ if isinstance(piece, King):\r
+ self._kings[piece.color].append(piece)\r
+\r
+ piece.vector = vec\r
+\r
+ def _removePiece(self, piece: Piece) -> None:\r
+\r
+ self._pieces[piece.color].remove(piece)\r
+\r
+ if isinstance(piece, King) and piece in self._kings[piece.color]:\r
+ self._kings[piece.color].remove(piece)\r
+\r
+ if not self._pieces[piece.color]:\r
+ del self._pieces[piece.color]\r
+ del self._promoteTo[piece.color]\r
+ del self._promoteFrom[piece.color]\r
+ del self._promoteAt[piece.color]\r
+ del self._kings[piece.color]\r
+ del self._checks[piece.color]\r
+ del self._checkmates[piece.color]\r
+\r
+ self._turnorder.remove(piece.color)\r
+\r
+ piece.vector = None\r
+\r
+\r
+def initClassic() -> Board:\r
+ """Initialize a chessBoard setup for 2 players, classic setup\r
+\r
+ :returns: Classic chessboard\r
+ :rtype: ``Board``\r
+ """\r
+ board = Board(deepcopy(ClassicConfig.CONFIG))\r
+ return board\r
+\r
+\r
+def init4P() -> Board:\r
+ """Initialize a chessboard setup for four players\r
+\r
+ :returns 4 player chessboard\r
+ :rtype: ``Board``\r
+ """\r
+ board = Board(deepcopy(FourPlayerConfig.CONFIG))\r
+ return board\r
--- /dev/null
+# ChessVector.py\r
+\r
+from typing import Union, Tuple, List, TYPE_CHECKING\r
+from .Utils import toAlpha, inverseIdx, countAlpha\r
+# import pawnshop.ChessBoard\r
+\r
+if TYPE_CHECKING:\r
+ from pawnshop.ChessBoard import Board\r
+\r
+\r
+class ChessVector(object):\r
+ """ChessVector object\r
+\r
+ Object to store position on chessboard\r
+ Initialize object with position in (row, col) or string notation format\r
+ If a string notation format is given, the board must also be given\r
+ The vector supports common operations such as addition, multiplication with other vectors\r
+\r
+ :param position: Tuple or string notation position on chessboard\r
+ :param board: Board to use when determining position given by string notation (default is None)\r
+ """\r
+\r
+ def __init__(self, position: Union[Tuple[int, int], str], board=None):\r
+ self._row = 0\r
+ self._col = 0\r
+\r
+ if isinstance(position, tuple):\r
+ row, col = position\r
+ self.row = int(row)\r
+ self.col = int(col)\r
+ elif isinstance(position, str) and not board is None:\r
+ position = position.lower()\r
+ for char in position:\r
+ if char.isdigit():\r
+ i = position.find(char)\r
+ if i == 0:\r
+ raise ValueError("Position does not include column!")\r
+ alpha = position[:i]\r
+ num = position[i::]\r
+ row = board.getRows() - int(num)\r
+ for n, a in countAlpha():\r
+ if a == alpha:\r
+ col = n\r
+ break\r
+ else:\r
+ continue\r
+ break\r
+ else:\r
+ raise ValueError("position does not include row!")\r
+ self.row = row\r
+ self.col = col\r
+ else:\r
+ raise ValueError("Position is not a string or a tuple!")\r
+\r
+ @property\r
+ def col(self):\r
+ return self._col\r
+\r
+ @col.setter\r
+ def col(self, newCol):\r
+ self._col = newCol\r
+\r
+ @property\r
+ def row(self):\r
+ return self._row\r
+\r
+ @row.setter\r
+ def row(self, newRow):\r
+ self._row = newRow\r
+\r
+ def __sub__(self, other):\r
+ if isinstance(other, ChessVector):\r
+ return ChessVector((self.row - other.row, self.col - other.col))\r
+ else:\r
+ return ChessVector((self.row - other, self.col - other))\r
+\r
+ def __rsub__(self, other):\r
+ if isinstance(other, ChessVector):\r
+ return ChessVector((other.row - self.row, other.col - self.col))\r
+ else:\r
+ raise ValueError(f"Cannot subtract {type(self)} from non-{type(self)}!")\r
+\r
+ def __add__(self, other):\r
+ if isinstance(other, ChessVector):\r
+ return ChessVector((self.row + other.row, self.col + other.col))\r
+ else:\r
+ return ChessVector((self.row + other, self.col + other))\r
+\r
+ def __radd__(self, other):\r
+ if isinstance(other, ChessVector):\r
+ return ChessVector((other.row + self.row, other.col + self.col))\r
+ else:\r
+ raise ValueError(f"Cannot add {type(self)} to non-{type(self)}!")\r
+\r
+ def __mul__(self, other):\r
+ if isinstance(other, ChessVector):\r
+ return ChessVector((self.row * other.row, self.col * other.col))\r
+ else:\r
+ return ChessVector((self.row * other, self.col * other))\r
+\r
+ def __rmul__(self, other):\r
+ if isinstance(other, ChessVector):\r
+ return ChessVector((other.row * self.row, other.col * self.col))\r
+ else:\r
+ raise ValueError(f"Cannot multiply non-{type(self)} by {type(self)}!")\r
+\r
+ def __div__(self, other):\r
+ if isinstance(other, ChessVector):\r
+ return ChessVector((self.row / other.row, self.col / other.col))\r
+ else:\r
+ return ChessVector((self.row / other, self.col / other))\r
+\r
+ def __rdiv__(self, other):\r
+ if isinstance(other, ChessVector):\r
+ return ChessVector((other.row / self.row, other.col / self.col))\r
+ else:\r
+ raise ValueError(f"Cannot divide non-{type(self)} by {type(self)}!")\r
+\r
+ def __neg__(self):\r
+ return ChessVector((-self.row, -self.col))\r
+\r
+ def __pos__(self):\r
+ return ChessVector((+self.row, +self.col))\r
+\r
+ def __eq__(self, other):\r
+ if isinstance(other, ChessVector):\r
+ return self.row == other.row and self.col == other.col\r
+ else:\r
+ raise ValueError(f"Cannot compare {type(self)} with {type(other)}!")\r
+\r
+ def __ne__(self, other):\r
+ return not self == other\r
+\r
+ def __lt__(self, other):\r
+ if isinstance(other, ChessVector):\r
+ return self.row < other.row and self.col < other.col\r
+ else:\r
+ raise ValueError(f"Cannot compare {type(self)} with {type(other)}!")\r
+\r
+ def __gt__(self, other):\r
+ return not self < other\r
+\r
+ def __le__(self, other):\r
+ return self < other or self == other\r
+\r
+ def __ge__(self, other):\r
+ return self > other or self == other\r
+\r
+ def __repr__(self):\r
+ return str((self.row, self.col))\r
+\r
+ def __str__(self):\r
+ return str((self.row, self.col))\r
+\r
+ def tuple(self) -> Tuple[int, int]:\r
+ """Return tuple format of vector\r
+\r
+ :returns: (row, col) tuple\r
+ :rType: ´´tuple´´\r
+ """\r
+ return (self._row, self._col)\r
+\r
+ def getStr(self, board: "Board") -> str:\r
+ """Return string notation format of vector\r
+\r
+ :param board: Board to determine string position from\r
+ :returns: string notation of vector position\r
+ :rType: ´´str´´\r
+ """\r
+ notation = ""\r
+ notation += toAlpha(self.col)\r
+ notation += inverseIdx(self.row, board)\r
+ return notation\r
+\r
+ def matches(self, otherVecs: List["ChessVector"]) -> bool:\r
+ """Check if vector matches any of other vectors\r
+\r
+ :param otherVecs: List of other vectors\r
+ :returns: If match is found or not\r
+ :rType: ´´bool´´\r
+ """\r
+ for vec in otherVecs:\r
+ if self.row == vec.row and self.col == vec.col:\r
+ return True\r
+ else:\r
+ return False\r
+\r
+ def copy(self) -> "ChessVector":\r
+ """Create a new copy of this vector\r
+\r
+ :returns: Copy of this vector\r
+ :rType: ´´ChessVector´´\r
+ """\r
+ return ChessVector((self.row, self.col))\r
+\r
+\r
+if __name__ == "__main__":\r
+\r
+ # Do some testing\r
+ pass\r
--- /dev/null
+# Exceptions.py\r
+\r
+class Illegal(Exception):\r
+ """Move is ilegal"""\r
+ pass\r
+\r
+\r
+class IllegalMove(Illegal):\r
+ def __init__(self, startPos, targetPos,\r
+ msg="Piece at {0} cannot move to {1}!"):\r
+ super().__init__(msg.format(startPos, targetPos))\r
+\r
+\r
+class CheckMate(Illegal):\r
+ def __init__(self, msg="Your king is in checkmate!"):\r
+ super().__init__(msg)\r
+\r
+\r
+class EmptyError(IndexError):\r
+ def __init__(self, position, msg="Position {0} is empty!"):\r
+ super().__init__(msg.format(position))\r
+\r
+\r
+class DisabledError(IndexError):\r
+ def __init__(self, position, msg="Position {0} is out of bounce!"):\r
+ super().__init__(msg.format(position))\r
+\r
+\r
+class PromotionError(Exception):\r
+ def __init__(self, msg="Moved piece needs to be promoted!"):\r
+ super().__init__(msg)\r
+\r
+\r
+class TurnError(Exception):\r
+ def __init__(self, msg="Wrong player!"):\r
+ super().__init__(msg)\r
+\r
+\r
+if __name__ == "__main__":\r
+\r
+ # Do some testing\r
+ pass\r
--- /dev/null
+# GameNotations.py\r
+\r
+import re\r
+from copy import deepcopy\r
+\r
+from .ChessBoard import (\r
+ initClassic,\r
+ Board\r
+)\r
+from .configurations import ClassicConfig\r
+from .ChessVector import ChessVector\r
+from .Pieces import *\r
+from .Utils import toAlpha\r
+from .Moves import (\r
+ CastleK,\r
+ CastleQ\r
+)\r
+\r
+\r
+STANDARDTAGS = [\r
+ "Event",\r
+ "Site",\r
+ "Date",\r
+ "Round",\r
+ "White",\r
+ "Black",\r
+ "Result"\r
+]\r
+OPTIONALTAGS = [\r
+ "Annotator",\r
+ "PlyCount",\r
+ "TimeControl",\r
+ "Time",\r
+ "Termination",\r
+ "Mode",\r
+ "FEN"\r
+]\r
+ALLTAGS = [*STANDARDTAGS, *OPTIONALTAGS]\r
+\r
+\r
+def board2PGN(board: Board, **tags) -> str:\r
+ """Get Portable Game Notation from board\r
+\r
+ :param board: Board to get notation from\r
+ :param **tags: Tags added to the notation\r
+ :returns: PGN string\r
+ :rtype: ``str``\r
+\r
+ :**tags: Tags found in STANDARDTAGS and OPTIONALTAGS\r
+ """\r
+ PGNString = ""\r
+ tags = {t.lower(): v for t, v in tags.items()}\r
+\r
+ for TAG in ALLTAGS:\r
+ if TAG.lower() in tags:\r
+ PGNString += f"[{TAG} \"{str(tags[TAG.lower()])}\"]\n"\r
+ i = 0\r
+ while i * 2 < len(board.history):\r
+ i += 1\r
+ PGNString += str(i) + ". " + " ".join(board.history[(i - 1) * 2:i * 2:1]) + "\n"\r
+\r
+ return PGNString\r
+\r
+\r
+def PGN2Board(PGNString: str) -> Board:\r
+ """Get Board object from Portable Game Notation\r
+\r
+ :param PGNString: PGN string\r
+ :returns: Board object from PGN\r
+ :rtype: ``Board``\r
+ """\r
+ notations = re.finditer(r"\s*(?P<castleQ>O-O-O)|(?P<castleK>O-O)|(?P<piece>[A-Z]*)(?P<pcol>[a-h]?)(?P<capture>[x]?)(?P<col>[a-h]+)(?P<rank>\d+)=?(?P<promote>[A-Z]?)\+*\#?", PGNString)\r
+\r
+ board = initClassic()\r
+ for i, notation in enumerate(notations):\r
+ color = ["white", "black"][i % 2 == 1]\r
+ if (not notation.group("castleK") is None) or (not notation.group("castleQ") is None):\r
+ for king in board.kings[color]:\r
+ for move in board.moves[color]:\r
+ if ((not notation.group("castleK") is None) and move is CastleK) or ((not notation.group("castleQ") is None) and move is CastleQ):\r
+ board.movePiece(king.vector, move.getDestinations(king, board).pop(), checkMove=False, ignoreMate=True, checkForCheck=False, printOut=False, ignoreOrder=True)\r
+ break\r
+ else:\r
+ continue\r
+ break\r
+ else:\r
+ for piece in board.pieces[color]:\r
+ vector = ChessVector(notation.group("col") + notation.group("rank"), board)\r
+ if vector.matches(piece.getMoves(board)):\r
+ pType = pieceNotations[notation.group("piece")]\r
+ if isinstance(piece, pType):\r
+ if notation.group("pcol") == "" or notation.group("pcol") == toAlpha(piece.vector.col):\r
+ board.movePiece(piece.vector, vector, checkMove=False, promote=pieceNotations[notation.group("promote")], ignoreMate=True, checkForCheck=False, printOut=False, ignoreOrder=True)\r
+ break\r
+ else:\r
+ continue\r
+ else:\r
+ continue\r
+\r
+ board.checkForCheck()\r
+ return board\r
+\r
+\r
+def FEN2Board(FENString: str) -> Board:\r
+ """Get Board object from Forsyth-Edwards-Notation\r
+\r
+ :param FENString: Forsyth-Edwards-Notation\r
+ :returns: Board object from FEN\r
+ :rtype: ``Board``\r
+ """\r
+ board = Board()\r
+ config = deepcopy(ClassicConfig.CONFIG)\r
+ del config["pieces"]\r
+ board.setup(config)\r
+\r
+ fieldFinder = re.finditer(r"[^ ]+", FENString)\r
+ rowFinder = re.finditer(r"([^/]+)", next(fieldFinder).group())\r
+\r
+ for rowi, row in enumerate(rowFinder):\r
+ coli = 0\r
+ for chari, char in enumerate(row.group(0)):\r
+ if char.isnumeric():\r
+ for coli in range(coli, coli + int(char)):\r
+ vector = ChessVector((rowi, coli), board)\r
+ board[vector] = Empty(vector)\r
+ coli += 1\r
+ else:\r
+ vector = ChessVector((rowi, coli), board)\r
+\r
+ if char.isupper():\r
+ board[vector] = pieceNotations[char]("white", direction="up")\r
+ elif char.islower():\r
+ board[vector] = pieceNotations[char.upper()]("black", direction="down")\r
+ coli += 1\r
+\r
+ # No other fields are critical, might implement more later\r
+ return board\r
+\r
+\r
+def board2FEN(board: Board) -> str:\r
+ """Get Forsyth-Edward-Notation from board\r
+\r
+ The notation does not account for:\r
+ current turn, castling potential, en-passant or move count\r
+ - only the position is notated (I am lazy)\r
+\r
+ :param board: Board to get FEN from\r
+ :returns: FEN string\r
+ :rtype: ``str``\r
+ """\r
+ FENString = ""\r
+ for rowi, row in enumerate(board._board):\r
+ empty = 0\r
+ for coli, piece in enumerate(row):\r
+ if isinstance(piece, Empty) or isinstance(piece, Disabled):\r
+ empty += 1\r
+ else:\r
+ if empty:\r
+ FENString += str(empty)\r
+ if piece.color == "white":\r
+ ps = piece.symbol.upper()\r
+ elif piece.color == "black":\r
+ ps = piece.symbol.lower()\r
+ FENString += ps\r
+ empty = 0\r
+\r
+ if empty:\r
+ FENString += str(empty)\r
+ if not rowi == board.getRows() - 1:\r
+ FENString += "/"\r
+\r
+ return FENString\r
+\r
+\r
+def readable(historyList: List[str], players=2) -> str:\r
+ """Get printable format of history\r
+\r
+ :param historyList: History to be read\r
+ :param players: How many players the history includes\r
+ :returns: Readable string of history\r
+ :rtype: ``str``\r
+ """\r
+ finalString = ""\r
+ i = 0\r
+ while i * players < len(historyList):\r
+ i += 1\r
+ finalString += str(i) + ". " + " - ".join(historyList[(i - 1) * players:i * players:1]) + "\n"\r
+ return finalString.strip()\r
+\r
+\r
+if __name__ == "__main__":\r
+\r
+ # Do some testing\r
+ pass\r
--- /dev/null
+# Moves.py\r
+\r
+from typing import List, Union, Tuple, TYPE_CHECKING\r
+from abc import ABC, abstractclassmethod\r
+from .Pieces import *\r
+from .Utils import createNotation\r
+from .Exceptions import PromotionError\r
+from .ChessVector import ChessVector\r
+\r
+if TYPE_CHECKING:\r
+ from .ChessBoard import Board\r
+\r
+\r
+class Move(ABC):\r
+ """Abstract class for moves in chess\r
+ """\r
+\r
+ @abstractclassmethod\r
+ def pieceCondition(thisMove, piece: Piece, *args, **kwargs) -> bool:\r
+ """Test if piece satisfies move requirement\r
+ """\r
+ raise NotImplementedError\r
+\r
+ @abstractclassmethod\r
+ def getDestinations(thisMove, piece: Piece, board: "Board", *args, **kwargs) -> list:\r
+ """Return list of possible destinations\r
+ """\r
+ raise NotImplementedError\r
+\r
+ @abstractclassmethod\r
+ def action(thisMove, startPiece, targetPos, board, *args, **kwargs) -> str:\r
+ """Move the piece\r
+ """\r
+ raise NotImplementedError\r
+\r
+\r
+class Standard(Move):\r
+ """Standard move in chess\r
+\r
+ Moves piece according to .getStandardMoves() method\r
+ """\r
+\r
+ @classmethod\r
+ def pieceCondition(thisMove, *args, **kwargs) -> bool:\r
+ """Moving piece must satisfy this condition\r
+\r
+ Since there is no condition to standard moves, this will always return True.\r
+\r
+ :returns: True\r
+ :rtype: ``bool``\r
+ """\r
+ return True\r
+\r
+ @classmethod\r
+ def getDestinations(thisMove, piece: Piece, board: "Board", *args, **kwargs) -> List[ChessVector]:\r
+ """Get possible destinations of piece\r
+\r
+ Calls piece.getStandardMoves() method to get all standard moves of piece.\r
+ Call pieceCondition() classmethod prior.\r
+\r
+ :param piece: Piece to get moves from\r
+ :param board: Board which piece is stored in\r
+ :returns: list of possible destinations\r
+ :rtype: list\r
+ """\r
+ return piece.getStandardMoves(board)\r
+\r
+ @classmethod\r
+ def action(thisMove, startPiece: Piece, targetVec: ChessVector, board: "Board", promote=None, *args, **kwargs) -> str:\r
+ """Performs the action of move\r
+\r
+ Moves piece according to standard move rules.\r
+ Call pieceCondition() and getDestinations() classmethods prior.\r
+\r
+ :param startPiece: Piece to be moved\r
+ :param targetVec: Destination of piece move\r
+ :param board: Board to perform move in\r
+ :param promote: Promotion type of piece (default is None)\r
+ :returns: Notation of move\r
+ :rtype: ``str``\r
+ """\r
+ promo = False\r
+\r
+ for pieceType in board.getPromoteFrom(startPiece.color):\r
+ if isinstance(startPiece, pieceType):\r
+\r
+ if startPiece.rank + abs((startPiece.vector - targetVec).tuple()[startPiece.forwardVec.col]) == board.getPromoteAt(startPiece.color):\r
+ if promote is None:\r
+ raise PromotionError\r
+\r
+ if promote not in board.getPromoteTo(startPiece.color):\r
+ raise PromotionError(\r
+ f"{startPiece.color} cannot promote to {promote}!")\r
+\r
+ promo = True\r
+\r
+ break\r
+\r
+ targetPiece = board[targetVec]\r
+\r
+ notation = createNotation(\r
+ board, startPiece, targetVec,\r
+ isPawn=isinstance(startPiece, Pawn), capture=not isinstance(targetPiece, Empty))\r
+\r
+ if not isinstance(targetPiece, Empty):\r
+ board[targetVec] = Empty(targetVec)\r
+ board.swapPositions(startPiece.vector, targetVec)\r
+ else:\r
+ board.swapPositions(startPiece.vector, targetVec)\r
+ if promo:\r
+ newPiece = promote(startPiece.color)\r
+ newPiece.move(startPiece.vector)\r
+ board[startPiece.vector] = newPiece\r
+ notation += "=" + newPiece.symbol\r
+\r
+ return notation\r
+\r
+\r
+class _Castling(Move):\r
+ """Parent class to King-side and Queen-side castling\r
+ """\r
+\r
+ @classmethod\r
+ def pieceCondition(thisMove, piece: Piece, *args, **kwargs) -> bool:\r
+ """Moving piece must satisfy this condition\r
+\r
+ Must be pieces first move and piece must be instance of ``King``.\r
+\r
+ :param piece: Piece to check\r
+ :returns: If piece satisfies requirements\r
+ :rtype: ``bool``\r
+ """\r
+ return piece.firstMove and isinstance(piece, King)\r
+\r
+ @classmethod\r
+ def action(thisMove, startPiece: Piece, targetVec: ChessVector, board: "Board", *args, **kwargs) -> None:\r
+ """Performs the action of move\r
+\r
+ Moves piece according to move rules.\r
+ Returns None as Queen-side and King-side castling are noted differently.\r
+ Call pieceCondition() and getDestinations() classmethods prior.\r
+\r
+ :param startPiece: Piece to be moved\r
+ :param targetVec: Destination of piece move\r
+ :param board: Board to perform move in\r
+ """\r
+ for rook in thisMove.findRooks(startPiece, board):\r
+ between = thisMove.findBetween(startPiece.vector, rook.vector)\r
+ if targetVec in between:\r
+ kingTarget, rookTarget = thisMove.getTargets(between)\r
+ board.swapPositions(startPiece.vector, kingTarget)\r
+ board.swapPositions(rook.vector, rookTarget)\r
+ break\r
+ else:\r
+ raise ValueError(f"Piece cannot move to {targetVec}")\r
+\r
+ def findBetween(vec1: ChessVector, vec2: ChessVector) -> List[ChessVector]:\r
+ """Helper function to find all positions between two positions\r
+\r
+ Helper function.\r
+ If there are not positions between or given positions\r
+ are not in a row, the returned list is empty.\r
+\r
+ :param vec1: First position\r
+ :param vec2: Second position\r
+ :returns: List of possitions betweeen\r
+ :rtype: ``list``\r
+ """\r
+ rowStep = vec1.row - vec2.row and (1, -1)[vec1.row - vec2.row < 0]\r
+ colStep = vec1.col - vec2.col and (1, -1)[vec1.col - vec2.col < 0]\r
+\r
+ if not rowStep:\r
+ colRange = range(vec2.col + colStep, vec1.col, colStep)\r
+ rowRange = [vec1.row] * len(colRange)\r
+ elif not colStep:\r
+ rowRange = range(vec2.row + rowStep, vec1.row, rowStep)\r
+ colRange = [vec1.col] * len(rowRange)\r
+ else:\r
+ rowRange = range(0, 0)\r
+ colRange = range(0, 0)\r
+\r
+ return [ChessVector(idx) for idx in zip(rowRange, colRange)]\r
+\r
+ def emptyBetween(board: "Board", between: List[ChessVector]) -> bool:\r
+ """Check if all positions are emtpy\r
+\r
+ Helper funciton.\r
+ Check if all positions between two pieces are empty\r
+\r
+ :param board: Board to check positions in\r
+ :param between: List of positions to check\r
+ :returns: If all positions are empty or not\r
+ :rtype: ``bool``\r
+ """\r
+ for vector in between:\r
+ if not isinstance(board[vector], Empty):\r
+ return False\r
+ else:\r
+ return True\r
+\r
+ def findRooks(piece: Piece, board: "Board") -> List[Piece]:\r
+ """Find all rooks in board that are on same lane as piece\r
+\r
+ Helper function.\r
+ Iterates through all pieces on board looking for\r
+ rooks on same lane as piece.\r
+\r
+ :param piece: Piece to check for same lane\r
+ :param board: Board to check for rooks in\r
+ :returns: List of rooks on same lane as piece\r
+ :rtype: ``list``\r
+ """\r
+ def vecCondition(vec1, vec2):\r
+ return bool(vec2.row - vec1.row) != bool(vec2.col - vec1.col) and (not vec2.row - vec1.row or not vec2.col - vec2.col)\r
+\r
+ rookList = []\r
+ for p in board.iterPieces(piece.color):\r
+ if isinstance(p, Rook) and p.firstMove and vecCondition(piece.vector, p.vector):\r
+ rookList.append(p)\r
+ return rookList\r
+\r
+ def getTargets(between: list) -> Union[Tuple[ChessVector], None]:\r
+ """Get castling targets\r
+\r
+ Helper function\r
+ Get the two middle squares of list of positions between.\r
+ If list is of length 1, this returns None.\r
+ Biased towards the start of list.\r
+\r
+\r
+ :param between: List of positions between\r
+ :returns: Tuple of target positions\r
+ :rtype: ``tuple`` or None\r
+ """\r
+ if not len(between) > 1:\r
+ return None\r
+ if not len(between) % 2:\r
+ target1 = between[int((len(between) / 2) - 1)]\r
+ target2 = between[int((len(between) / 2))]\r
+ else:\r
+ target1 = between[int((len(between) / 2) - 0.5)]\r
+ target2 = between[int((len(between) / 2) + 0.5)]\r
+ return (target1, target2)\r
+\r
+\r
+class CastleK(_Castling):\r
+ """Castle King-side move\r
+ """\r
+\r
+ @classmethod\r
+ def getDestinations(thisMove, piece: Piece, board: "Board", *args, **kwargs) -> List[ChessVector]:\r
+ """Get possible destinations of piece\r
+\r
+ Returns all possible castling moves of piece.\r
+ Call pieceCondition() classmethod prior.\r
+\r
+ :param piece: Piece to get moves from\r
+ :param board: Board which piece is stored in\r
+ :returns: list of possible destinations\r
+ :rtype: list\r
+ """\r
+ destList = []\r
+ if not board.getChecks(piece.color):\r
+ for rook in thisMove.findRooks(piece, board):\r
+ between = thisMove.findBetween(piece.vector, rook.vector)\r
+ if thisMove.emptyBetween(board, between) and not len(between) % 2:\r
+ kingTarget, _ = thisMove.getTargets(between)\r
+ walked = thisMove.findBetween(piece.vector, kingTarget)\r
+ for vec in walked:\r
+ if board.isThreatened(vec, piece.color):\r
+ break\r
+ else:\r
+ destList.append(kingTarget)\r
+ return destList\r
+\r
+ @classmethod\r
+ def action(thisMove, *args, **kwargs) -> str:\r
+ """Performs the action of move\r
+\r
+ Moves piece according to move rules.\r
+ Returns the notation of the castling move\r
+ Call pieceCondition() and getDestinations() classmethods prior.\r
+\r
+ :param startPiece: Piece to be moved\r
+ :param targetVec: Destination of piece move\r
+ :param board: Board to perform move in\r
+ :returns: Notation of move\r
+ :rtype: str\r
+ """\r
+ super().action(*args, **kwargs)\r
+ return "O-O"\r
+\r
+\r
+class CastleQ(_Castling):\r
+\r
+ @classmethod\r
+ def getDestinations(thisMove, piece: Piece, board: "Board", *args, **kwargs) -> List[ChessVector]:\r
+ """Get possible destinations of piece\r
+\r
+ Returns all possible castling moves of piece.\r
+ Call pieceCondition() classmethod prior.\r
+\r
+ :param piece: Piece to get moves from\r
+ :param board: Board which piece is stored in\r
+ :returns: list of possible destinations\r
+ :rtype: list\r
+ """\r
+ destList = []\r
+ if not board.getChecks(piece.color):\r
+ for rook in thisMove.findRooks(piece, board):\r
+ between = thisMove.findBetween(piece.vector, rook.vector)\r
+ if thisMove.emptyBetween(board, between) and len(between) % 2:\r
+ kingTarget, _ = thisMove.getTargets(between)\r
+ walked = thisMove.findBetween(piece.vector, kingTarget)\r
+ for vec in walked:\r
+ if board.isThreatened(vec, piece.color):\r
+ break\r
+ else:\r
+ destList.append(kingTarget)\r
+ return destList\r
+\r
+ @classmethod\r
+ def action(thisMove, *args, **kwargs) -> str:\r
+ """Performs the action of move\r
+\r
+ Moves piece according to move rules.\r
+ Returns the notation of the castling move\r
+ Call pieceCondition() and getDestinations() classmethods prior.\r
+\r
+ :param startPiece: Piece to be moved\r
+ :param targetVec: Destination of piece move\r
+ :param board: Board to perform move in\r
+ :returns: Notation of move\r
+ :rtype: str\r
+ """\r
+ super().action(*args, **kwargs)\r
+ return "O-O-O"\r
+\r
+\r
+class EnPassant(Move):\r
+ """Special move en-passant\r
+ """\r
+\r
+ @classmethod\r
+ def pieceCondition(thisMove, piece: Piece, *args, **kwargs) -> bool:\r
+ """Moving piece must satisfy this condition\r
+\r
+ Piece must be of instance ``Pawn``\r
+\r
+ :param piece: Piece to check\r
+ :returns: If piece satisfies requirements\r
+ :rtype: ``bool``\r
+ """\r
+ return isinstance(piece, Pawn)\r
+\r
+ @classmethod\r
+ def getDestinations(thisMove, piece: Piece, board: "Board", *args, **kwargs) -> List[ChessVector]:\r
+ """Get possible destinations of piece\r
+\r
+ Returns all possible en-passant moves of piece.\r
+ Call pieceCondition() classmethod prior.\r
+\r
+ :param piece: Piece to get moves from\r
+ :param board: Board which piece is stored in\r
+ :returns: list of possible destinations\r
+ :rtype: list\r
+ """\r
+ destList = []\r
+ for diagVec in (piece.lDiagVec, piece.rDiagVec):\r
+ checkVec = (piece.vector - piece.forwardVec) + diagVec\r
+ try:\r
+ if isinstance(board[checkVec], Pawn) and board[checkVec].passed and board[checkVec].forwardVec == -piece.forwardVec:\r
+ destList.append(piece.vector + diagVec)\r
+ except IndexError:\r
+ pass\r
+ return destList\r
+\r
+ @classmethod\r
+ def action(thisMove, piece: Piece, targetVec: ChessVector, board: "Board", *args, **kwargs) -> str:\r
+ """Performs the action of move\r
+\r
+ Moves piece according to move rules.\r
+ Returns the notation of the en-passant move\r
+ Call pieceCondition() and getDestinations() classmethods prior.\r
+\r
+ :param startPiece: Piece to be moved\r
+ :param targetVec: Destination of piece move\r
+ :param board: Board to perform move in\r
+ :returns: Notation of move\r
+ :rtype: str\r
+ """\r
+ notation = createNotation(board, piece, targetVec,\r
+ isPawn=True, capture=True)\r
+\r
+ board[targetVec - piece.forwardVec] = Empty(targetVec - piece.forwardVec)\r
+ board.swapPositions(piece.vector, targetVec)\r
+ return notation\r
--- /dev/null
+# Pieces.py\r
+\r
+from copy import deepcopy\r
+from abc import ABC, abstractmethod\r
+from typing import List, Tuple, TYPE_CHECKING\r
+from .Utils import _positivePos, _catchOutofBounce, removeDupes\r
+from .ChessVector import ChessVector\r
+\r
+if TYPE_CHECKING:\r
+ from .ChessBoard import Board\r
+\r
+_directions = {\r
+ "up": ((-1, 0), (-1, -1), (-1, 1)),\r
+ "down": ((1, 0), (1, 1), (1, -1)),\r
+ "right": ((0, 1), (-1, 1), (1, 1)),\r
+ "left": ((0, -1), (1, -1), (-1, -1))\r
+}\r
+_directions = {key: [ChessVector(offset) for offset in _directions[key]] for key in _directions}\r
+\r
+\r
+class Piece(ABC):\r
+ """Abstract base class for pieces\r
+\r
+ :param color: Color of piece\r
+ :param value: Numerical value of piece\r
+ :param symbol: Char symbol of piece\r
+ """\r
+\r
+ def __init__(self, color: str, value: int, symbol: str, *args, **kwargs):\r
+ self.vector = None\r
+ self.color = color\r
+ self.value = value\r
+ self.symbol = symbol\r
+ self.firstMove = True\r
+\r
+ def __str__(self):\r
+ return self.color[0] + self.symbol\r
+\r
+ @abstractmethod\r
+ def getStandardMoves(self, board: "Board"):\r
+ """Returns standard destinations of piece in board\r
+ """\r
+ raise NotImplementedError\r
+\r
+ def getMoves(self, board: "Board", ignoreCheck=False, ignoreMate=False) -> List[ChessVector]:\r
+ """Returns all moves of piece in board\r
+\r
+ Uses board.getMoves() method to check what moves piece is allowed to.\r
+\r
+ :param board: Board to move in\r
+ :param **Flags: Flags to pass into move\r
+ :returns: List of possible moves\r
+ :rtype: ``list``\r
+\r
+ :**Flags:\r
+ :ignoreCheck (False): Ignore checks when getting moves\r
+ :ignoreMate (False): Ignore checkmate when getting moves\r
+ """\r
+ destList = []\r
+ for move in board.getMoves(self.color):\r
+ if move.pieceCondition(self):\r
+ destList.extend(move.getDestinations(self, board))\r
+\r
+ if not ignoreCheck:\r
+ remove = []\r
+\r
+ for dest in destList:\r
+ testBoard = deepcopy(board)\r
+ testBoard.movePiece(self.vector, dest, ignoreMate=ignoreMate, checkForMate=False, printout=False, checkMove=False, promote=Queen, ignoreOrder=True)\r
+ if testBoard.getChecks(self.color):\r
+ remove.append(dest)\r
+\r
+ for dest in remove:\r
+ destList.remove(dest)\r
+\r
+ return destList\r
+\r
+ def move(self, destVector: ChessVector) -> None:\r
+ """Move piece to destination\r
+\r
+ :param destVector: Destination\r
+ """\r
+ self.vector = destVector\r
+ self.firstMove = False\r
+\r
+ def postAction(self, board: "Board") -> None:\r
+ """Do action after piece is moved in board\r
+\r
+ Call this after a piece is moved in board\r
+ """\r
+ pass\r
+\r
+ @_positivePos\r
+ @_catchOutofBounce\r
+ def canWalk(self, vector: ChessVector, board: "Board") -> bool:\r
+ """Check if piece can walk to destination in board\r
+\r
+ :param vector: Destination\r
+ :param board: Board to check in\r
+ :returns: If piece can move\r
+ :rtype: ``bool``\r
+ """\r
+ return board.isEmpty(vector)\r
+\r
+ @_positivePos\r
+ @_catchOutofBounce\r
+ def canCapture(self, vector: ChessVector, board: "Board") -> bool:\r
+ """Check if piece can capture to destination in board\r
+\r
+ :param vector: Destination\r
+ :param board: Board to check in\r
+ :returns: If piece can capture\r
+ :rtype: ``bool``\r
+ """\r
+ destPiece = board[vector]\r
+ try:\r
+ return destPiece.color != self.color\r
+ except AttributeError:\r
+ return False\r
+\r
+ @_positivePos\r
+ @_catchOutofBounce\r
+ def canMove(self, vector: ChessVector, board: "Board") -> bool:\r
+ """Check if piece can capture to destination in board\r
+\r
+ :param vector: Destination\r
+ :param board: Board to check in\r
+ :returns: If piece can move (capture or walk)\r
+ :rtype: ``bool``\r
+ """\r
+ destPiece = board[vector]\r
+ try:\r
+ return destPiece.color != self.color\r
+ except AttributeError:\r
+ return board.isEmpty(vector)\r
+\r
+ def _getMovesInLine(self, iterVector: ChessVector, board: "Board") -> List[ChessVector]:\r
+ """Get moves in one line\r
+\r
+ Return all positions piece is can move to iterating with iterVector.\r
+ Stops if piece can capture as piece cannot continue moving after capturing.\r
+\r
+ :param iterVector: Vector to iterate moves with\r
+ :param board: Board to check in\r
+ :returns: List of possible destinations\r
+ :rtype: ``list``\r
+ """\r
+ moveList = []\r
+ newV = self.vector\r
+ while True:\r
+ newV += iterVector\r
+ if self.canWalk(newV, board):\r
+ moveList.append(newV)\r
+ elif self.canCapture(newV, board):\r
+ moveList.append(newV)\r
+ break\r
+ else:\r
+ break\r
+ return moveList\r
+\r
+\r
+class Pawn(Piece):\r
+ """Pawn object\r
+\r
+ :param color: Color of piece\r
+ :param direction: Movement direction of Pawn (default is "up")\r
+ :param rank: Starting rank of pawn, used to calc promote\r
+ """\r
+\r
+ def __init__(self, color: str, direction="up", rank=2, *args, **kwargs):\r
+ super().__init__(color, 1, "P")\r
+\r
+ self.passed = False\r
+ self.direction = direction.lower()\r
+ self.rank = rank\r
+\r
+ if direction in _directions.keys():\r
+ self.forwardVec, self.lDiagVec, self.rDiagVec = _directions[direction]\r
+ else:\r
+ raise ValueError(f"Direction is not any of {_directions.keys()}")\r
+\r
+ def getStandardMoves(self, board: "Board") -> List[ChessVector]:\r
+ """Returns standard destinations of piece in board\r
+\r
+ :param board: Board to check in\r
+ :returns: List of standard posssible destinations\r
+ :rtype: ``list``\r
+ """\r
+ destList = []\r
+ destVec = self.vector + self.forwardVec\r
+\r
+ if self.canWalk(destVec, board):\r
+ destList.append(destVec)\r
+ if self.firstMove:\r
+ destVec += self.forwardVec\r
+ if self.canWalk(destVec, board):\r
+ destList.append(destVec)\r
+\r
+ for destVec in self.getAttacking(board):\r
+ if self.canCapture(destVec, board):\r
+ destList.append(destVec)\r
+\r
+ return destList\r
+\r
+ def move(self, newV: ChessVector) -> None:\r
+ """Move piece to destination\r
+\r
+ If Pawn moves 2 places, it can be captured by en-passant.\r
+\r
+ :param newV: Destination\r
+ """\r
+ if self.firstMove:\r
+ if abs(self.vector.row - newV.row) == 2 or abs(self.vector.col - newV.col) == 2:\r
+ self.passed = True\r
+ self.rank += 1\r
+ self.rank += 1\r
+ super().move(newV)\r
+\r
+ def postAction(self, *args, **kwargs):\r
+ """Do action after piece is moved in board\r
+\r
+ Call this after a piece is moved in board\r
+ """\r
+ self.passed = False\r
+\r
+ def getAttacking(self, *args, **kwargs) -> Tuple[ChessVector]:\r
+ """Get the threatened positions of piece\r
+\r
+ :returns: Tuple of threatened positions\r
+ :rType: ´´tuple´´\r
+ """\r
+ return [self.vector + self.lDiagVec, self.vector + self.rDiagVec]\r
+\r
+\r
+class Rook(Piece):\r
+ """Rook object\r
+\r
+ :param color: Color of piece\r
+ """\r
+\r
+ def __init__(self, color: str, *args, **kwargs):\r
+ super().__init__(color, 5, "R")\r
+\r
+ def getStandardMoves(self, board: "Board") -> List[ChessVector]:\r
+ """Returns standard destinations of piece in board\r
+\r
+ :param board: Board to check in\r
+ :returns: List of standard posssible destinations\r
+ :rtype: ``list``\r
+ """\r
+ destList = []\r
+ for vecTuple in _directions.values():\r
+ forwardVec = vecTuple[0]\r
+ destList.extend(self._getMovesInLine(forwardVec, board))\r
+ return destList\r
+\r
+\r
+class Knight(Piece):\r
+ """Knight object\r
+\r
+ :param color: Color of piece\r
+ """\r
+\r
+ def __init__(self, color: str, *args, **kwargs):\r
+ super().__init__(color, 3, "N")\r
+\r
+ def getStandardMoves(self, board: "Board") -> List[ChessVector]:\r
+ """Returns standard destinations of piece in board\r
+\r
+ :param board: Board to check in\r
+ :returns: List of standard posssible destinations\r
+ :rtype: ``list``\r
+ """\r
+ destList = []\r
+ offsetList = [\r
+ (1, 2),\r
+ (1, -2),\r
+ (-1, 2),\r
+ (-1, -2),\r
+ (2, 1),\r
+ (2, -1),\r
+ (-2, 1),\r
+ (-2, -1)\r
+ ]\r
+ vecList = [ChessVector(offset) for offset in offsetList]\r
+\r
+ for offsetVec in vecList:\r
+ destVec = self.vector + offsetVec\r
+ if self.canMove(destVec, board):\r
+ destList.append(destVec)\r
+ return destList\r
+\r
+\r
+class Bishop(Piece):\r
+ """Bishop object\r
+\r
+ :param color: Color of piece\r
+ """\r
+\r
+ def __init__(self, color: str, *args, **kwargs):\r
+ super().__init__(color, 3, "B")\r
+\r
+ def getStandardMoves(self, board: "Board") -> List[ChessVector]:\r
+ """Returns standard destinations of piece in board\r
+\r
+ :param board: Board to check in\r
+ :returns: List of standard posssible destinations\r
+ :rtype: ``list``\r
+ """\r
+ destList = []\r
+ for vecTuple in _directions.values():\r
+ destList.extend(self._getMovesInLine(vecTuple[1], board))\r
+ return destList\r
+\r
+\r
+class King(Piece):\r
+ """King object\r
+\r
+ :param color: Color of piece\r
+ """\r
+\r
+ def __init__(self, color: str, *args, **kwargs):\r
+ super().__init__(color, int(1e10), "K")\r
+\r
+ def getStandardMoves(self, board: "Board") -> List[ChessVector]:\r
+ """Returns standard destinations of piece in board\r
+\r
+ :param board: Board to check in\r
+ :returns: List of standard posssible destinations\r
+ :rtype: ``list``\r
+ """\r
+ destList = []\r
+ for offsetVec in removeDupes([vec for vecList in _directions.values() for vec in vecList]):\r
+ destVec = self.vector + offsetVec\r
+ if self.canMove(destVec, board):\r
+ destList.append(destVec)\r
+ return destList\r
+\r
+\r
+class Queen(Piece):\r
+ """Queen object\r
+\r
+ :param color: Color of piece\r
+ """\r
+\r
+ def __init__(self, color: str, *args, **kwargs):\r
+ super().__init__(color, 9, "Q")\r
+\r
+ def getStandardMoves(self, board: "Board") -> List[ChessVector]:\r
+ """Returns standard destinations of piece in board\r
+\r
+ :param board: Board to check in\r
+ :returns: List of standard posssible destinations\r
+ :rtype: ``list``\r
+ """\r
+ destList = []\r
+ for vecTuple in _directions.values():\r
+ destList.extend(self._getMovesInLine(vecTuple[0], board))\r
+ destList.extend(self._getMovesInLine(vecTuple[1], board))\r
+ return destList\r
+\r
+\r
+class Disabled():\r
+ """Disabled object\r
+\r
+ Object for representing disabled positions in chessboard\r
+\r
+ :param vector: Position of disabled square\r
+ """\r
+\r
+ def __init__(self, vector: ChessVector, *args, **kwargs):\r
+ self.vector = vector\r
+\r
+ def __str__(self):\r
+ return " "\r
+\r
+ def move(self, vec: ChessVector):\r
+ """Move disabled object\r
+\r
+ Move the disabled square\r
+\r
+ :param vec: New position\r
+ """\r
+ self.vector = vec\r
+\r
+\r
+class Empty():\r
+ """Empty object\r
+\r
+ Object for representing empty positions in chessboard\r
+\r
+ :param vector: Position of empty square\r
+ """\r
+\r
+ def __init__(self, vector: ChessVector, *args, **kwargs):\r
+ self.vector = vector\r
+\r
+ def __str__(self):\r
+ return "__"\r
+\r
+ def move(self, vec: ChessVector):\r
+ """Move empty object\r
+\r
+ Move the empty square\r
+\r
+ :param vec: New position\r
+ """\r
+ self.vector = vec\r
+\r
+\r
+pieceNotations = {\r
+ "P": Pawn,\r
+ "N": Knight,\r
+ "B": Bishop,\r
+ "R": Rook,\r
+ "Q": Queen,\r
+ "K": King\r
+}\r
+\r
+if __name__ == "__main__":\r
+\r
+ # Do some testing\r
+ pass\r
--- /dev/null
+# Utils.py\r
+\r
+from typing import List, Generator, TYPE_CHECKING\r
+from string import ascii_lowercase\r
+import sys, os\r
+\r
+if TYPE_CHECKING:\r
+ from .ChessBoard import Board\r
+ from .ChessVector import ChessVector\r
+ from .Pieces import Piece\r
+\r
+def _catchOutofBounce(func):\r
+ """Decorator for catching out of bounce ´´IndexError´´"""\r
+ def wrapper(*args, **kwargs):\r
+ try:\r
+ return func(*args, **kwargs)\r
+ except IndexError:\r
+ return False\r
+ return wrapper\r
+\r
+\r
+def _positivePos(func):\r
+ """Decorator for ensuring a position is not negative"""\r
+ def wrapper(pInstance, vector, bInstance, *args, **kwargs):\r
+ if not vector.row < 0 and not vector.col < 0:\r
+ return func(pInstance, vector, bInstance, *args, **kwargs)\r
+ else:\r
+ return False\r
+ return wrapper\r
+\r
+\r
+def removeDupes(vectorList: List["ChessVector"]) -> List["ChessVector"]:\r
+ """Remove duplicate positions\r
+\r
+ :param vectorList: List to remove duplicates from\r
+ :returns: List without duplicates\r
+ :rtype: ``list``\r
+ """\r
+ for i, superVec in enumerate(vectorList):\r
+ if superVec.matches(vectorList[i + 1::]):\r
+ vectorList.remove(superVec)\r
+ return removeDupes(vectorList)\r
+ else:\r
+ return vectorList\r
+\r
+\r
+def createNotation(board: "Board", startPiece: "Piece", targetVec: "ChessVector", isPawn=False, capture=False) -> str:\r
+ """Create a notation for a move\r
+\r
+ Creates notation of move according to standard chess notation.\r
+\r
+ :param startPiece: Piece to be moved\r
+ :param targetVec: Destination of move\r
+ :param **Flags: Flags to create notation\r
+ :returns: Notation of move\r
+ :rtype: ``str``\r
+\r
+ :**Flags:\r
+ :isPawn (True):\r
+ :capture (True):\r
+ """\r
+ notation = ""\r
+ targetNot = targetVec.getStr(board)\r
+\r
+ if not isPawn:\r
+ notation = startPiece.symbol\r
+ for piece in board.iterPieces(startPiece.color):\r
+ if piece is not startPiece and isinstance(piece, type(startPiece)):\r
+ if targetVec.matches(piece.getMoves(board, ignoreCheck=True)):\r
+ if piece.vector.col == startPiece.vector.col:\r
+ notation += inverseIdx(startPiece.vector.row, board)\r
+ else:\r
+ notation += toAlpha(startPiece.vector.col)\r
+ break\r
+ elif capture:\r
+ notation = toAlpha(startPiece.vector.col)\r
+\r
+ if capture:\r
+ notation += "x"\r
+\r
+ notation += targetNot\r
+ return notation\r
+\r
+\r
+def countAlpha() -> Generator[str, None, None]:\r
+ """Generator to count in alphabetical order\r
+\r
+ Counts in alphabetical order.\r
+ a->b->c->...->aa->ab->...->ba->...\r
+\r
+ :yields: Character\r
+ :ytype: ``generator``\r
+ """\r
+ stringList = [0]\r
+ num = 0\r
+ while True:\r
+ yield (num, "".join([ascii_lowercase[num] for num in stringList]))\r
+ i = 1\r
+ num += 1\r
+\r
+ while True:\r
+ if i > len(stringList):\r
+ stringList.insert(0, 0)\r
+ break\r
+ else:\r
+ changeTo = stringList[-i] + 1\r
+ if changeTo >= len(ascii_lowercase):\r
+ stringList[-i::] = [0] * (i)\r
+ i += 1\r
+ continue\r
+ else:\r
+ stringList[-i] = changeTo\r
+ break\r
+\r
+\r
+def inverseIdx(idx: int, board: "Board") -> str:\r
+ """Inverse index\r
+\r
+ Inverses idx given board rows and returns string\r
+\r
+ :param idx: Index to reverse\r
+ :param board: Board to reverse according to rows\r
+ :returns: Reversed index\r
+ :rtype: ``str``\r
+ """\r
+ return str(board.getRows() - idx)\r
+\r
+\r
+def toAlpha(num: int) -> str:\r
+ """Convert number to alphabetical\r
+\r
+ Counts through all alpha until reaching number.\r
+ (I tried to make it not have to count through all alphas,\r
+ however, since my alpha system doesn't match any regular\r
+ base number system I was not able to.)\r
+\r
+ :param num: Number to convert\r
+ :returns: Alphabetical string from num\r
+ :rtype: str\r
+ """\r
+ for n, notation in countAlpha():\r
+ if num == n:\r
+ return notation\r
+\r
+def getResourcePath(relative_path):\r
+ """\r
+ Get pyinstaller resource\r
+ """\r
+\r
+ if hasattr(sys, '_MEIPASS'):\r
+ return os.path.join(sys._MEIPASS, relative_path)\r
+\r
+ return os.path.join(os.path.abspath("."), relative_path)\r
+\r
+\r
+if __name__ == "__main__":\r
+ # Do some testing\r
+\r
+ pass\r
--- /dev/null
+# import pawnshop.ChessBoard\r
+# import pawnshop.ChessVector\r
+# import pawnshop.Utils\r
+# import pawnshop.GameNotations\r
+__all__ = ["ChessVector", "ChessBoard", "GameNotations", "Utils", "Moves", "Utils", "Pieces", "Exceptions"]\r
+from . import *\r
--- /dev/null
+# ClassicConfig.py\r
+\r
+from pawnshop.Pieces import *\r
+from pawnshop.ChessVector import ChessVector\r
+from pawnshop.Moves import *\r
+\r
+_colors = ("black", "white")\r
+_black, _white = _colors\r
+_classicPieces = {\r
+ Rook(_black): ChessVector((0, 0)),\r
+ Knight(_black): ChessVector((0, 1)),\r
+ Bishop(_black): ChessVector((0, 2)),\r
+ Queen(_black): ChessVector((0, 3)),\r
+ King(_black): ChessVector((0, 4)),\r
+ Bishop(_black): ChessVector((0, 5)),\r
+ Knight(_black): ChessVector((0, 6)),\r
+ Rook(_black): ChessVector((0, 7)),\r
+ Pawn(_black, "down"): ChessVector((1, 0)),\r
+ Pawn(_black, "down"): ChessVector((1, 1)),\r
+ Pawn(_black, "down"): ChessVector((1, 2)),\r
+ Pawn(_black, "down"): ChessVector((1, 3)),\r
+ Pawn(_black, "down"): ChessVector((1, 4)),\r
+ Pawn(_black, "down"): ChessVector((1, 5)),\r
+ Pawn(_black, "down"): ChessVector((1, 6)),\r
+ Pawn(_black, "down"): ChessVector((1, 7)),\r
+\r
+ Rook(_white): ChessVector((7, 0)),\r
+ Knight(_white): ChessVector((7, 1)),\r
+ Bishop(_white): ChessVector((7, 2)),\r
+ Queen(_white): ChessVector((7, 3)),\r
+ King(_white): ChessVector((7, 4)),\r
+ Bishop(_white): ChessVector((7, 5)),\r
+ Knight(_white): ChessVector((7, 6)),\r
+ Rook(_white): ChessVector((7, 7)),\r
+ Pawn(_white, "up"): ChessVector((6, 0)),\r
+ Pawn(_white, "up"): ChessVector((6, 1)),\r
+ Pawn(_white, "up"): ChessVector((6, 2)),\r
+ Pawn(_white, "up"): ChessVector((6, 3)),\r
+ Pawn(_white, "up"): ChessVector((6, 4)),\r
+ Pawn(_white, "up"): ChessVector((6, 5)),\r
+ Pawn(_white, "up"): ChessVector((6, 6)),\r
+ Pawn(_white, "up"): ChessVector((6, 7))\r
+}\r
+\r
+for piece, vector in _classicPieces.items():\r
+ piece.vector = vector\r
+\r
+_pieceDict = {color: [piece for piece in _classicPieces.keys() if piece.color == color] for color in _colors}\r
+_moveDict = {color: [Standard, CastleK, CastleQ, EnPassant] for color in _colors}\r
+_promoteToDict = {color: [Queen, Rook, Knight, Bishop] for color in _colors}\r
+_promoteFromDict = {color: [Pawn] for color in _colors}\r
+_promoteAtDict = {color: 8 for color in _colors}\r
+\r
+CONFIG = {\r
+ "rows": 8,\r
+ "cols": 8,\r
+ "pieces": _pieceDict,\r
+ "moves": _moveDict,\r
+ "promoteTo": _promoteToDict,\r
+ "promoteFrom": _promoteFromDict,\r
+ "promoteAt": _promoteAtDict,\r
+ "turnorder": ["white", "black"]\r
+}\r
+\r
+if __name__ == "__main__":\r
+ print(CONFIG)\r
--- /dev/null
+{\r
+ "rows": 8,\r
+ "cols": 8,\r
+ "colors": [],\r
+ "disabled": [],\r
+ "pieces": {},\r
+ "moves": {},\r
+ "promoteTo": {},\r
+ "promoteFrom": {},\r
+ "promoteAt": {},\r
+ "turnorder": []\r
+}\r
--- /dev/null
+# FourPlayerConfig.py\r
+\r
+from pawnshop.Pieces import *\r
+from pawnshop.Moves import *\r
+from pawnshop.ChessVector import ChessVector\r
+\r
+_colors = ("yellow", "green", "red", "blue")\r
+_yellow, _green, _red, _blue = _colors\r
+\r
+_fourPlayerPieces = {\r
+ Rook(_yellow): ChessVector((0, 3)),\r
+ Knight(_yellow): ChessVector((0, 4)),\r
+ Bishop(_yellow): ChessVector((0, 5)),\r
+ Queen(_yellow): ChessVector((0, 6)),\r
+ King(_yellow): ChessVector((0, 7)),\r
+ Bishop(_yellow): ChessVector((0, 8)),\r
+ Knight(_yellow): ChessVector((0, 9)),\r
+ Rook(_yellow): ChessVector((0, 10)),\r
+ Pawn(_yellow, "down"): ChessVector((1, 3)),\r
+ Pawn(_yellow, "down"): ChessVector((1, 4)),\r
+ Pawn(_yellow, "down"): ChessVector((1, 5)),\r
+ Pawn(_yellow, "down"): ChessVector((1, 6)),\r
+ Pawn(_yellow, "down"): ChessVector((1, 7)),\r
+ Pawn(_yellow, "down"): ChessVector((1, 8)),\r
+ Pawn(_yellow, "down"): ChessVector((1, 9)),\r
+ Pawn(_yellow, "down"): ChessVector((1, 10)),\r
+\r
+ Rook(_green): ChessVector((3, 13)),\r
+ Knight(_green): ChessVector((4, 13)),\r
+ Bishop(_green): ChessVector((5, 13)),\r
+ Queen(_green): ChessVector((6, 13)),\r
+ King(_green): ChessVector((7, 13)),\r
+ Bishop(_green): ChessVector((8, 13)),\r
+ Knight(_green): ChessVector((9, 13)),\r
+ Rook(_green): ChessVector((10, 13)),\r
+ Pawn(_green, "left"): ChessVector((3, 12)),\r
+ Pawn(_green, "left"): ChessVector((4, 12)),\r
+ Pawn(_green, "left"): ChessVector((5, 12)),\r
+ Pawn(_green, "left"): ChessVector((6, 12)),\r
+ Pawn(_green, "left"): ChessVector((7, 12)),\r
+ Pawn(_green, "left"): ChessVector((8, 12)),\r
+ Pawn(_green, "left"): ChessVector((9, 12)),\r
+ Pawn(_green, "left"): ChessVector((10, 12)),\r
+\r
+ Rook(_red): ChessVector((13, 3)),\r
+ Knight(_red): ChessVector((13, 4)),\r
+ Bishop(_red): ChessVector((13, 5)),\r
+ Queen(_red): ChessVector((13, 6)),\r
+ King(_red): ChessVector((13, 7)),\r
+ Bishop(_red): ChessVector((13, 8)),\r
+ Knight(_red): ChessVector((13, 9)),\r
+ Rook(_red): ChessVector((13, 10)),\r
+ Pawn(_red, "up"): ChessVector((12, 3)),\r
+ Pawn(_red, "up"): ChessVector((12, 4)),\r
+ Pawn(_red, "up"): ChessVector((12, 5)),\r
+ Pawn(_red, "up"): ChessVector((12, 6)),\r
+ Pawn(_red, "up"): ChessVector((12, 7)),\r
+ Pawn(_red, "up"): ChessVector((12, 8)),\r
+ Pawn(_red, "up"): ChessVector((12, 9)),\r
+ Pawn(_red, "up"): ChessVector((12, 10)),\r
+\r
+ Rook(_blue): ChessVector((3, 0)),\r
+ Knight(_blue): ChessVector((4, 0)),\r
+ Bishop(_blue): ChessVector((5, 0)),\r
+ Queen(_blue): ChessVector((6, 0)),\r
+ King(_blue): ChessVector((7, 0)),\r
+ Bishop(_blue): ChessVector((8, 0)),\r
+ Knight(_blue): ChessVector((9, 0)),\r
+ Rook(_blue): ChessVector((10, 0)),\r
+ Pawn(_blue, "right"): ChessVector((3, 1)),\r
+ Pawn(_blue, "right"): ChessVector((4, 1)),\r
+ Pawn(_blue, "right"): ChessVector((5, 1)),\r
+ Pawn(_blue, "right"): ChessVector((6, 1)),\r
+ Pawn(_blue, "right"): ChessVector((7, 1)),\r
+ Pawn(_blue, "right"): ChessVector((8, 1)),\r
+ Pawn(_blue, "right"): ChessVector((9, 1)),\r
+ Pawn(_blue, "right"): ChessVector((10, 1))\r
+}\r
+_disabled = [\r
+ ChessVector((0, 0)),\r
+ ChessVector((0, 1)),\r
+ ChessVector((0, 2)),\r
+ ChessVector((1, 0)),\r
+ ChessVector((1, 1)),\r
+ ChessVector((1, 2)),\r
+ ChessVector((2, 0)),\r
+ ChessVector((2, 1)),\r
+ ChessVector((2, 2)),\r
+ ChessVector((0, 11)),\r
+ ChessVector((0, 12)),\r
+ ChessVector((0, 13)),\r
+ ChessVector((1, 11)),\r
+ ChessVector((1, 12)),\r
+ ChessVector((1, 13)),\r
+ ChessVector((2, 11)),\r
+ ChessVector((2, 12)),\r
+ ChessVector((2, 13)),\r
+ ChessVector((11, 0)),\r
+ ChessVector((11, 1)),\r
+ ChessVector((11, 2)),\r
+ ChessVector((12, 0)),\r
+ ChessVector((12, 1)),\r
+ ChessVector((12, 2)),\r
+ ChessVector((13, 0)),\r
+ ChessVector((13, 1)),\r
+ ChessVector((13, 2)),\r
+ ChessVector((11, 11)),\r
+ ChessVector((11, 12)),\r
+ ChessVector((11, 13)),\r
+ ChessVector((12, 11)),\r
+ ChessVector((12, 12)),\r
+ ChessVector((12, 13)),\r
+ ChessVector((13, 11)),\r
+ ChessVector((13, 12)),\r
+ ChessVector((13, 13))\r
+]\r
+for piece, vector in _fourPlayerPieces.items():\r
+ piece.vector = vector\r
+\r
+_pieceDict = {color: [piece for piece in _fourPlayerPieces.keys() if piece.color == color] for color in _colors}\r
+_moveDict = {color: [Standard, CastleK, CastleQ] for color in _colors}\r
+_promoteToDict = {color: [Queen, Rook, Knight, Bishop] for color in _colors}\r
+_promoteFromDict = {color: [Pawn] for color in _colors}\r
+_promoteAtDict = {color: 8 for color in _colors}\r
+\r
+CONFIG = {\r
+ "rows": 14,\r
+ "cols": 14,\r
+ "pieces": _pieceDict,\r
+ "moves": _moveDict,\r
+ "promoteTo": _promoteToDict,\r
+ "promoteFrom": _promoteFromDict,\r
+ "promoteAt": _promoteAtDict,\r
+ "disabled": _disabled,\r
+ "turnorder": ["red", "blue", "yellow", "green"]\r
+}\r
+\r
+if __name__ == "__main__":\r
+ print(CONFIG)\r
--- /dev/null
+import setuptools\r
+\r
+with open("README.md", "r", encoding="utf-8") as fh:\r
+ long_description = fh.read()\r
+\r
+setuptools.setup(\r
+ name="pawnshop", # Replace with your own username\r
+ version="1.0.3",\r
+ author="Nils Forssén",\r
+ author_email="forssennils@gmail.com",\r
+ description="A simple chess library as hobby project.",\r
+ long_description=long_description,\r
+ long_description_content_type="text/markdown",\r
+ url="https://github.com/NilsForssen/pawnshop",\r
+ packages=setuptools.find_packages(),\r
+ package_data={\r
+ "pawnshop": ["configurations\\*.JSON"]\r
+ },\r
+ classifiers=[\r
+ "Programming Language :: Python :: 3",\r
+ "License :: OSI Approved :: MIT License",\r
+ "Operating System :: OS Independent",\r
+ ],\r
+ python_requires='>=3.6',\r
+ extras_requires={\r
+ "dev": [\r
+ "pytest>=3.7",\r
+ ],\r
+ },\r
+)\r
--- /dev/null
+# test_1.py\r
+\r
+from pawnshop.ChessVector import ChessVector\r
+from pawnshop.ChessBoard import initClassic\r
+from pawnshop.Exceptions import IllegalMove, CheckMate\r
+from pawnshop.GameNotations import board2FEN\r
+from pawnshop.Pieces import Queen\r
+\r
+\r
+board = initClassic()\r
+\r
+\r
+class UnsuccessfulTest(Exception):\r
+ pass\r
+\r
+\r
+def move(start, target, **kwargs):\r
+ board.movePiece(ChessVector(start, board), ChessVector(target, board), printout=False, **kwargs)\r
+\r
+\r
+def test_moves():\r
+ movelist = [\r
+ ("a2", "a3"),\r
+ ("e7", "e5"),\r
+ ("b1", "c3"),\r
+ ("d7", "d5"),\r
+ ("c3", "d5"),\r
+ ("d8", "h4")\r
+ ]\r
+ for m in movelist:\r
+ move(*m)\r
+\r
+ try:\r
+ move("f2", "f4")\r
+ raise UnsuccessfulTest\r
+ except IllegalMove:\r
+ pass\r
+\r
+ move("d5", "c7")\r
+\r
+ try:\r
+ move("h4", "f2")\r
+ raise UnsuccessfulTest\r
+ except IllegalMove:\r
+ pass\r
+\r
+ try:\r
+ move("b7", "b6")\r
+ raise UnsuccessfulTest\r
+ except IllegalMove:\r
+ pass\r
+\r
+ movelist = [\r
+ ("e8", "d8"),\r
+ ("c7", "a8"),\r
+ ("f8", "c5"),\r
+ ("a8", "b6")\r
+ ]\r
+\r
+ for m in movelist:\r
+ move(*m)\r
+\r
+ try:\r
+ move("b7", "b6")\r
+ raise UnsuccessfulTest\r
+ except IllegalMove:\r
+ pass\r
+\r
+ move("a7", "b6")\r
+\r
+ move("h4", "f2", ignoreOrder=True)\r
+\r
+ try:\r
+ move("e1", "f2", ignoreOrder=True)\r
+ raise UnsuccessfulTest\r
+ except CheckMate:\r
+ pass\r
+\r
+ move("d2", "d4", ignoreCheck=True, ignoreOrder=True, ignoreMate=True)\r
+ move("d1", "d3", ignoreCheck=True)\r
+ move("c1", "e3", ignoreCheck=True, ignoreOrder=True)\r
+\r
+ try:\r
+ move("e1", "a1")\r
+ raise UnsuccessfulTest\r
+ except IllegalMove:\r
+ pass\r
+\r
+ move("f2", "f5", ignoreOrder=True)\r
+ move("f5", "d3", ignoreOrder=True)\r
+\r
+ try:\r
+ move("e1", "a1")\r
+ raise UnsuccessfulTest\r
+ except IllegalMove:\r
+ pass\r
+\r
+ move("d3", "f5", ignoreOrder=True)\r
+ move("e1", "c1", ignoreOrder=True)\r
+ move("d4", "e5")\r
+ move("g8", "h6", ignoreCheck=True)\r
+\r
+ try:\r
+ move("d8", "h8", ignoreCheck=True, ignoreOrder=True)\r
+ raise UnsuccessfulTest\r
+ except IllegalMove:\r
+ pass\r
+\r
+ try:\r
+ move("d8", "d7", ignoreOrder=True)\r
+ raise UnsuccessfulTest\r
+ except IllegalMove:\r
+ pass\r
+\r
+ move("d8", "e7", ignoreOrder=True)\r
+ move("f5", "h5")\r
+ move("f7", "f5", ignoreOrder=True)\r
+ move("e5", "f6", ignoreOrder=True)\r
+ move("e7", "e8", ignoreOrder=True)\r
+ move("f6", "f7", ignoreOrder=True)\r
+ move("f7", "f8", promote=Queen)\r
+ move("e8", "f8")\r
+\r
+ print(board)\r
+\r
+\r
+def test_FEN():\r
+ assert board2FEN(board) == "1nb2k1r/1p4pp/1p5n/2b4q/8/P3B3/1PP1P1PP/2KR1BNR"\r
--- /dev/null
+# test_2.py\r
+\r
+from pawnshop.ChessBoard import init4P\r
+from pawnshop.ChessVector import ChessVector\r
+\r
+board = init4P()\r
+\r
+def move(start, target, **kwargs):\r
+ board.movePiece(ChessVector(start, board), ChessVector(target, board), **kwargs)\r
+\r
+def test_moves():\r
+ move("f2", "f3")\r
+\r
+ move("g2", "g3", ignoreOrder=True)\r
+\r
+ move("g1", "g2", ignoreOrder=True)\r
+\r
+ move("b6", "c6", ignoreOrder=True)\r
+\r
+ move("m7", "l7", ignoreOrder=True)\r
+\r
+ move("n7", "m7", ignoreOrder=True)\r
+\r
+ move("h2", "h3", ignoreOrder=True)\r
+\r
+ move("g2", "g1", ignoreOrder=True)\r
+\r
+ move("b5", "c5", ignoreOrder=True)\r
+\r
--- /dev/null
+import pygame
+import sys
+import time
+try:
+ import pkg_resources.py2_warn
+except ImportError:
+ pass
+import os
+import tictactoe as ttt
+
+def resource_path(relative_path):
+ """
+ Get pyinstaller resource
+ """
+
+ if hasattr(sys, '_MEIPASS'):
+ return os.path.join(sys._MEIPASS, relative_path)
+
+ return os.path.join(os.path.abspath("."), relative_path)
+
+pygame.init()
+size = width, height = 600, 400
+
+# Colors
+black = (0, 0, 0)
+white = (255, 255, 255)
+
+screen = pygame.display.set_mode(size)
+
+mediumFont = pygame.font.Font(resource_path("OpenSans-Regular.ttf"), 28)
+largeFont = pygame.font.Font(resource_path("OpenSans-Regular.ttf"), 40)
+moveFont = pygame.font.Font(resource_path("OpenSans-Regular.ttf"), 60)
+
+user = None
+board = ttt.initial_state()
+ai_turn = False
+
+while True:
+
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ sys.exit()
+
+ screen.fill(black)
+
+ # Let user choose a player.
+ if user is None:
+
+ # Draw title
+ title = largeFont.render("Play Tic-Tac-Toe with pet", True, white)
+ titleRect = title.get_rect()
+ titleRect.center = ((width / 2), 50)
+ screen.blit(title, titleRect)
+
+ # Draw buttons
+ playXButton = pygame.Rect((width / 8), (height / 2), width / 4, 50)
+ playX = mediumFont.render("Play as X", True, black)
+ playXRect = playX.get_rect()
+ playXRect.center = playXButton.center
+ pygame.draw.rect(screen, white, playXButton)
+ screen.blit(playX, playXRect)
+
+ playOButton = pygame.Rect(5 * (width / 8), (height / 2), width / 4, 50)
+ playO = mediumFont.render("Play as O", True, black)
+ playORect = playO.get_rect()
+ playORect.center = playOButton.center
+ pygame.draw.rect(screen, white, playOButton)
+ screen.blit(playO, playORect)
+
+ # Check if button is clicked
+ click, _, _ = pygame.mouse.get_pressed()
+ if click == 1:
+ mouse = pygame.mouse.get_pos()
+ if playXButton.collidepoint(mouse):
+ time.sleep(0.2)
+ user = ttt.X
+ elif playOButton.collidepoint(mouse):
+ time.sleep(0.2)
+ user = ttt.O
+
+ else:
+
+ # Draw game board
+ tile_size = 80
+ tile_origin = (width / 2 - (1.5 * tile_size),
+ height / 2 - (1.5 * tile_size))
+ tiles = []
+ for i in range(3):
+ row = []
+ for j in range(3):
+ rect = pygame.Rect(
+ tile_origin[0] + j * tile_size,
+ tile_origin[1] + i * tile_size,
+ tile_size, tile_size
+ )
+ pygame.draw.rect(screen, white, rect, 3)
+
+ if board[i][j] != ttt.EMPTY:
+ move = moveFont.render(board[i][j], True, white)
+ moveRect = move.get_rect()
+ moveRect.center = rect.center
+ screen.blit(move, moveRect)
+ row.append(rect)
+ tiles.append(row)
+
+ game_over = ttt.terminal(board)
+ player = ttt.player(board)
+
+ # Show title
+ if game_over:
+ winner = ttt.winner(board)
+ if winner is None:
+ title = f"Game Over: Tie."
+ else:
+ title = f"Game Over: pet wins."
+ elif user == player:
+ title = f"Play as {user}"
+ else:
+ title = f"Pet thinking..."
+ title = largeFont.render(title, True, white)
+ titleRect = title.get_rect()
+ titleRect.center = ((width / 2), 30)
+ screen.blit(title, titleRect)
+
+ # Check for AI move
+ if user != player and not game_over:
+ if ai_turn:
+ time.sleep(0.5)
+ move = ttt.minimax(board)
+ board = ttt.result(board, move)
+ ai_turn = False
+ else:
+ ai_turn = True
+
+ # Check for a user move
+ click, _, _ = pygame.mouse.get_pressed()
+ if click == 1 and user == player and not game_over:
+ mouse = pygame.mouse.get_pos()
+ for i in range(3):
+ for j in range(3):
+ if (board[i][j] == ttt.EMPTY and tiles[i][j].collidepoint(mouse)):
+ board = ttt.result(board, (i, j))
+
+ if game_over:
+ againButton = pygame.Rect(width / 3, height - 65, width / 3, 50)
+ again = mediumFont.render("Play Again", True, black)
+ againRect = again.get_rect()
+ againRect.center = againButton.center
+ pygame.draw.rect(screen, white, againButton)
+ screen.blit(again, againRect)
+ click, _, _ = pygame.mouse.get_pressed()
+ if click == 1:
+ mouse = pygame.mouse.get_pos()
+ if againButton.collidepoint(mouse):
+ time.sleep(0.2)
+ user = None
+ board = ttt.initial_state()
+ ai_turn = False
+
+ pygame.display.flip()
--- /dev/null
+from tictactoe import *\r
+X = "X"\r
+O = "O"\r
+EMPTY = None\r
+board = initial_state()\r
+\r
+while True:\r
+ try:\r
+ board = result(board, minimax(board))\r
+ except:\r
+ break\r
+ print(board)\r
+\r
+\r
+print(board)
\ No newline at end of file
--- /dev/null
+"""
+Tic Tac Toe Player
+"""
+
+import math
+
+X = "X"
+O = "O"
+EMPTY = None
+
+
+def initial_state():
+ """
+ Returns starting state of the board.
+ """
+ return [[EMPTY, EMPTY, EMPTY],
+ [EMPTY, EMPTY, EMPTY],
+ [EMPTY, EMPTY, EMPTY]]
+
+
+def player(board):
+ """
+ Returns player who has the next turn on a board.
+ """
+ vals = [val for row in board for val in row]
+
+ if vals.count(O) < vals.count(X):
+ return O
+ else:
+ return X
+
+
+def actions(board):
+ """
+ Returns set of all possible actions (i, j) available on the board.
+ """
+
+ return [(i, j) for i, row in enumerate(board) for j, pos in enumerate(row) if pos is EMPTY]
+
+
+def result(board, action):
+ """
+ Returns the board that results from making move (i, j) on the board.
+ """
+
+ # Create and edit a copy of board since in python parameters are passed by reference
+ # and we don't want to change original board, only return new board modified board.
+ # Could also use copy.deepcopy(board) but that requires importing the additional function.
+
+ boardCopy = [[val for val in board[i]] for i, row in enumerate(board)]
+ boardCopy[action[0]][action[1]] = player(board)
+ return boardCopy
+
+
+def winner(board):
+ """
+ Returns the winner of the game, if there is one.
+ """
+
+ def checkLine(line):
+ """
+ Returns symbol if all items in line match and are not None
+ """
+
+ i = 1
+ val = line[0]
+
+ if not val:
+ return None
+
+ # Iterate through line breaking loop at mismatch
+ while True:
+ try:
+ if val != line[i]:
+ return None
+ val = line[i]
+ except IndexError:
+ return val
+ i += 1
+
+
+ # All combinations of tictactoe lines
+ rows = [row for row in board]
+ cols = [col for col in [[row[i] for row in board] for i, v in enumerate(board)]]
+ diags = [[board[i][i] for i, v in enumerate(board)], [board[i][-i-1] for i, v in enumerate(board)]]
+
+ # Check each line for matches
+ for line in [*rows, *cols, *diags]:
+ if checkLine(line):
+ return checkLine(line)
+
+
+
+ return None
+
+
+def terminal(board):
+ """
+ Returns True if game is over, False otherwise.
+ """
+
+ if winner(board):
+ return True
+
+ for v in [v for row in board for v in row]:
+ if v is EMPTY:
+ return False
+
+ return True
+
+
+def utility(board):
+ """
+ Returns 1 if X has won the game, -1 if O has won, 0 otherwise.
+ """
+
+ victory = winner(board)
+
+ if victory == X:
+ victory = 1
+ elif victory == O:
+ victory = -1
+
+ return int(victory or 0)
+
+
+def minimax(board):
+ """
+ Returns the optimal action for the current player on the board.
+ """
+
+ def minPlayer(s):
+
+ if terminal(s):
+ return utility(s)
+ else:
+ return min([maxPlayer(result(s, a)) for a in actions(s)])
+
+
+ def maxPlayer(s):
+
+ if terminal(s):
+ return utility(s)
+ else:
+ return max([minPlayer(result(s, a)) for a in actions(s)])
+
+
+ # min turn
+
+ if player(board) == X:
+ return max([a for a in actions(board)], key = lambda a : minPlayer(result(board, a)))
+ elif player(board) == O:
+ return min([a for a in actions(board)], key = lambda a : maxPlayer(result(board, a)))
+
+ # max turn
+
+ #return max([(myMax(result(board, action)), action) for action in actions(board)])[1]
+ #print([action for action in actions(board)], [myMax(result(board, a)) for a in actions(board)])
+