Z80Golf , 53 36 34 bytes
-16 ไบต์ขอบคุณ @Lynn
-2 ไบต์ขอบคุณ @Neil
เนื่องจากนี่เป็นเพียงรหัสเครื่อง Z80 จึงมี unprintables จำนวนมากในนี้จึงมีxxd -r
hexdump ที่กลับมาได้อีกครั้ง:
00000000: ddb6 2120 10dd b615 280c 003e 62ff 3e65 ..! ....(..>b.>e
00000010: ffff 3e70 ff76 003e 62ff 3e65 ffff 3e70 ..>p.v.>b.>e..>p
00000020: ff76 .v
ลองออนไลน์! (เครื่องทดสอบครบถ้วนสมบูรณ์ใน Python)
คำอธิบาย
z80golf เป็นเครื่อง Z80 สมมุติของอนาธิปไตยกอล์ฟซึ่งcall $8000
เป็น putchar call $8003
เป็น getchar halt
ทำให้การออกล่ามโปรแกรมของคุณถูกวางไว้ที่$0000
และหน่วยความจำอื่น ๆ เต็มไปด้วยเลขศูนย์ การสร้างโปรแกรมป้องกันรังสีในชุดประกอบนั้นค่อนข้างยาก แต่เทคนิคที่มีประโยชน์โดยทั่วไปใช้คำแนะนำ idempotent หนึ่งไบต์ ตัวอย่างเช่น,
or c ; b1 ; a = a | c
เป็นเพียงไบต์เดียวและa | c | c == a | c
ดังนั้นจึงสามารถป้องกันรังสีได้โดยเพียงแค่ทำตามคำสั่งซ้ำ บน Z80 โหลดแบบทันทีขนาด 8 บิตคือสองไบต์ (โดยที่แบบทันทีเป็นแบบไบต์ที่สอง) ดังนั้นคุณสามารถโหลดค่าบางค่าลงในรีจิสเตอร์ได้อย่างน่าเชื่อถือ นี่คือสิ่งที่ฉันทำตอนเริ่มต้นของโปรแกรมเพื่อให้คุณสามารถวิเคราะห์ตัวแปรที่ยาวกว่าที่ฉันเก็บไว้ที่ด้านล่างของคำตอบได้ แต่จากนั้นฉันก็รู้ว่ามีวิธีที่ง่ายกว่า
โปรแกรมประกอบด้วยเพย์โหลดอิสระสองตัวซึ่งหนึ่งในนั้นอาจได้รับความเสียหายจากรังสี ฉันจะตรวจสอบว่ามีการลบไบต์หรือไม่และไบต์ที่ถูกลบนั้นอยู่ก่อนสำเนาที่สองของ payload หรือไม่โดยตรวจสอบค่าของที่อยู่หน่วยความจำสัมบูรณ์บางแห่ง
ก่อนอื่นเราต้องออกหากไม่พบรังสี:
or a, (ix+endbyte) ; dd b6 21 ; a |= memory[ix+0x0021]
jr nz, midbyte ; 20 10 ; jump to a halt instruction if not zero
หากมีการลบไบต์ใด ๆ ไบต์ทั้งหมดจะเลื่อนและ$0020
จะมีครั้งสุดท้าย76
ดังนั้น$0021
จะเป็นศูนย์ เราสามารถจ่ายค่าเริ่มต้นของโปรแกรมได้แม้ว่าจะไม่มีความซ้ำซ้อน:
- หากการชดเชยการกระโดด
$10
ถูกลบออกจะมีการตรวจจับรังสีอย่างถูกต้องการกระโดดจะไม่ถูกนำมาใช้และการชดเชยจะไม่สำคัญ ไบต์แรกของคำสั่งถัดไปจะถูกใช้ไป แต่เนื่องจากมันถูกออกแบบมาให้ทนทานต่อการลบไบต์จึงไม่สำคัญ
- หากการข้าม opcode
$20
ถูกลบออกแล้วออฟเซ็ตการข้าม$10
จะถอดรหัสเป็นdjnz $ffe4
(ใช้คำสั่งไบต์ถัดไปเป็นออฟเซ็ต - ดูด้านบน) ซึ่งเป็นคำสั่งวนรอบ - ลดค่า B และกระโดดถ้าผลลัพธ์ไม่เป็นศูนย์ เพราะffe4-ffff
เต็มไปด้วยเลขศูนย์nop
และตัวนับโปรแกรมล้อมรอบสิ่งนี้จะรันจุดเริ่มต้นของโปรแกรม 256 ครั้งจากนั้นจึงดำเนินการต่อในที่สุด ฉันประหลาดใจกับงานนี้
- การลบ
$dd
ส่วนที่เหลือจะทำให้ส่วนย่อยของข้อมูลถอดรหัสเป็นor (hl) / ld ($1020), hl
แล้วเลื่อนไปที่ส่วนถัดไปของโปรแกรม or
จะไม่เปลี่ยนแปลงการลงทะเบียนใด ๆ ที่สำคัญและเพราะ HL เป็นศูนย์ที่จุดนี้เขียนยังจะยกเลิกการออก
- การลบ
$b6
จะทำให้การถอดรหัสที่เหลือเป็นld ($1020), ix
และดำเนินการตามข้างต้น
- การถอด
$21
จะทำให้ตัวถอดรหัสกิน$20
และทำให้เกิดdjnz
พฤติกรรม
โปรดทราบว่าการใช้or a, (ix+*)
บันทึกสองไบต์มากกว่าld a, (**) / and a / and a
ต้องขอบคุณการตรวจสอบแบบรวมศูนย์
ตอนนี้เราต้องตัดสินใจว่าในสองส่วนใดของ payload ที่จะดำเนินการ:
or (ix+midbyte) ; dd b6 15
jr z, otherimpl ; 28 0c
nop ; 00
; first payload
ld a, 'b' ; 3e 62
rst $0038 ; ff
ld a, 'e' ; 3e 65
rst $0038 ; ff
rst $0038 ; ff
ld a, 'p' ; 3e 70
rst $0038 ; ff
midbyte:
halt ; 76
otherimpl:
nop ; 00
ld a, 'b' ; 3e 62
; ... ; ...
rst $0038 ; ff
endbyte:
halt ; 76
สำเนาสองชุดจะถูกคั่นด้วย nop เนื่องจากการกระโดดแบบสัมพัทธ์ถูกใช้เพื่อเลือกระหว่างมันและการแผ่รังสีสามารถเลื่อนโปรแกรมในลักษณะที่จะทำให้การข้ามข้ามไปเป็นไบต์แรกหลังจากปลายทาง นอกจากนี้ nop ถูกเข้ารหัสเป็นศูนย์ซึ่งทำให้ง่ายต่อการตรวจจับไบต์ที่ถูกเลื่อน โปรดทราบว่าไม่สำคัญว่าจะเลือกอัตราบรรทุกใดหากสวิตช์ตัวเองเสียหายเนื่องจากสำเนาทั้งสองนั้นปลอดภัย ตรวจสอบให้แน่ใจว่ามันจะไม่กระโดดเข้าไปในหน่วยความจำที่ไม่มีการเตรียมการ แต่:
- การลบจะทำให้อีกสองไบต์ถอดรหัสเป็น
$dd
or (hl) / dec d
Clobbers D. ไม่มีเรื่องใหญ่
- การลบจะสร้างที่ไม่มีเอกสารอีกต่อไปสำหรับการเข้ารหัส
$b6
dec d
เช่นเดียวกับข้างต้น
- การลบ
$15
จะอ่าน$28
แทนเป็นการชดเชยและการดำเนินการจะดำเนินการที่$0c
ดังต่อไปนี้
- เมื่อ
$28
หายไปที่ถอดรหัสเป็น$0c
น้ำหนักบรรทุกไม่สนใจเกี่ยวกับinc c
c
- การลบ
$0c
- นั่นคือสิ่งที่ nop ใช้ มิฉะนั้นไบต์แรกของเพย์โหลดจะถูกอ่านเป็นการข้ามออฟเซ็ตและโปรแกรมจะข้ามไปยังหน่วยความจำที่ไม่ได้กำหนดค่าเริ่มต้น
เพย์โหลดเองนั้นค่อนข้างง่าย ฉันคิดว่าสตริงที่มีขนาดเล็กทำให้วิธีนี้เล็กกว่าลูปและมันง่ายกว่าที่จะทำให้ตำแหน่งเป็นอิสระด้วยวิธีนี้ e
ในซ้ำดังนั้นฉันสามารถโกนหนึ่งbeep
ld a
นอกจากนี้เนื่องจากหน่วยความจำทั้งหมดระหว่าง$0038
และ$8000
จะกลายเป็นศูนย์ผมสามารถตกผ่านและใช้งานสั้นrst
แตกต่างจากcall
การเรียนการสอนซึ่งทำงานเฉพาะสำหรับ$0
,$8
, $10
และอื่น ๆ ขึ้นไป$38
ขึ้นไป
แนวทางที่เก่ากว่า
64 ไบต์
00000000: 2e3f 3f2e 3f3f 7e7e a7a7 201f 1e2b 2b1e .??.??~~.. ..++.
00000010: 2b2b 6b00 7ea7 2814 003e 62cd 0080 3e65 ++k.~.(..>b...>e
00000020: cd00 80cd 0080 3e70 cd00 8076 003e 62cd ......>p...v.>b.
00000030: 0080 3e65 cd00 80cd 0080 3e70 cd00 8076 ..>e......>p...v
58 ไบต์
00000000: 2e39 392e 3939 7e7e a7a7 2019 3a25 00a7 .99.99~~.. .:%..
00000010: 2814 003e 62cd 0080 3e65 cd00 80cd 0080 (..>b...>e......
00000020: 3e70 cd00 8076 003e 62cd 0080 3e65 cd00 >p...v.>b...>e..
00000030: 80cd 0080 3e70 cd00 8076 ....>p...v
53 ไบต์
สิ่งนี้มีคำอธิบายในประวัติการแก้ไข แต่ไม่แตกต่างกันเกินไป
00000000: 3a34 00a7 a720 193a 2000 a728 1400 3e62 :4... .: ..(..>b
00000010: cd00 803e 65cd 0080 cd00 803e 70cd 0080 ...>e......>p...
00000020: 7600 3e62 cd00 803e 65cd 0080 cd00 803e v.>b...>e......>
00000030: 70cd 0080 76 p...v
เกิดอะไรขึ้นถ้า: เอาท์พุทที่ไม่ว่างเปล่าก็ดีกว่าเสียงบี๊บ
1 ไบต์
v
halt
เป็นโปรแกรมปกติ แต่ถ้าการเอารังสีออกไปหน่วยความจำก็จะเต็มไปด้วยเลขศูนย์ทำให้$8000
มีจำนวนครั้งที่ไม่สิ้นสุดพิมพ์จำนวนมากเป็นโมฆะ