├── .editorconfig ├── fix_demo_package.sh ├── assets ├── font3x6.png ├── font5x7.png ├── icon2.png ├── icon32.png ├── logo2.png ├── sprite4.png ├── tutorial.png ├── screenshot1.png ├── screenshot2.png ├── screenshot3.png ├── screenshot4.png ├── join_our_discord.png └── shaders │ ├── fill_shader.glsl │ ├── rotate_shader.glsl │ ├── project_shader.glsl │ ├── comb_shader.glsl │ └── state_shader.glsl ├── luasrc ├── imgs │ ├── flag.png │ ├── failure.png │ ├── success.png │ ├── levels │ │ ├── alu.png │ │ ├── bus.png │ │ ├── bus2.png │ │ ├── mem64.png │ │ ├── sss.png │ │ ├── gcd_img.png │ │ ├── or_icon.png │ │ ├── adder_icon.png │ │ ├── and_icon.png │ │ ├── bus_icon.png │ │ ├── demux_icon.png │ │ ├── gcd_icon.png │ │ ├── hanoi_icon.png │ │ ├── hanoi_img1.png │ │ ├── hanoi_img2.png │ │ ├── hanoi_img3.png │ │ ├── mof3_icon.png │ │ ├── mof3_img.png │ │ ├── mux_icon.png │ │ ├── nand_icon.png │ │ ├── not_icon.png │ │ ├── wires_icon.png │ │ ├── xor_icon.png │ │ ├── collatz_icon.png │ │ ├── collatz_img1.png │ │ ├── custom_icon.png │ │ ├── decoder_icon.png │ │ ├── primes_icon.png │ │ ├── primes_img1.png │ │ ├── sandbox_icon.png │ │ ├── shifter_icon.png │ │ ├── wire_example.png │ │ ├── comparator_icon.png │ │ ├── riscv_alu_icon.png │ │ ├── riscv_alu_img1.png │ │ ├── seven_seg_icon.png │ │ ├── seven_seg_img1.png │ │ ├── simple_ram_icon.png │ │ ├── subtractor_icon.png │ │ ├── wires_example.png │ │ ├── seven_seg_display.png │ │ └── wires_pins_example.png │ ├── tutorial │ │ ├── or1.png │ │ ├── or2.png │ │ ├── and1.png │ │ ├── and2.png │ │ ├── mem1.png │ │ ├── mem10.png │ │ ├── mem11.png │ │ ├── mem12.png │ │ ├── mem13.png │ │ ├── mem2.png │ │ ├── mem3.png │ │ ├── mem4.png │ │ ├── mem5.png │ │ ├── mem6.png │ │ ├── mem7.png │ │ ├── mem8.png │ │ ├── mem9.png │ │ ├── mux1.png │ │ ├── mux2.png │ │ ├── mux3.png │ │ ├── mux4.png │ │ ├── nand0.png │ │ ├── nand1.png │ │ ├── nand2.png │ │ ├── nand3.png │ │ ├── nand4.png │ │ ├── not1.png │ │ ├── not2.png │ │ ├── sub1.png │ │ ├── sub2.png │ │ ├── sync1.png │ │ ├── sync2.png │ │ ├── sync3.png │ │ ├── sync4.png │ │ ├── sync5.png │ │ ├── sync6.png │ │ ├── sync7.png │ │ ├── sync8.png │ │ ├── tldr.png │ │ ├── xor1.png │ │ ├── xor2.png │ │ ├── decoder1.png │ │ ├── decoder2.png │ │ ├── decoder3.png │ │ ├── decoder4.png │ │ ├── demux1.png │ │ ├── demux2.png │ │ ├── demux3.png │ │ ├── demux4.png │ │ ├── numbers1.png │ │ ├── numbers2.png │ │ ├── shift1.png │ │ ├── shift2.png │ │ ├── shift3.png │ │ ├── shift4.png │ │ ├── wires1.png │ │ ├── wires2.png │ │ ├── wires3.png │ │ ├── addition1.png │ │ ├── addition2.png │ │ ├── addition3.png │ │ ├── addition4.png │ │ ├── wire_cross.png │ │ ├── comparator1.png │ │ ├── comparator2.png │ │ ├── comparator3.png │ │ ├── comparator4.png │ │ ├── comparator5.png │ │ ├── simu_error1.png │ │ ├── simu_example1.png │ │ ├── wire_diagonal.png │ │ └── simu_error_cycle.png │ ├── sample_palette1.png │ ├── sample_palette2.png │ └── sample_palette3.png ├── tutorial │ ├── tldr.lua │ ├── not.lua │ ├── or.lua │ ├── xor.lua │ ├── posint.lua │ ├── subtraction.lua │ ├── and.lua │ ├── meminit.lua │ ├── mux.lua │ ├── propdelay.lua │ ├── init.lua │ ├── demux.lua │ ├── negint.lua │ ├── dlatch.lua │ ├── dflipflop.lua │ ├── addition.lua │ ├── shifter.lua │ ├── nand.lua │ ├── basic.lua │ ├── srlatch.lua │ ├── decoder.lua │ ├── comparator.lua │ ├── wire.lua │ └── synchronous.lua ├── template_scripts │ ├── my_custom_icon.png │ ├── my_startup_image.png │ ├── README.txt │ ├── example_custom_level.lua │ └── init.lua ├── camera.lua ├── levels │ ├── sandbox.lua │ ├── mof3.lua │ ├── primes.lua │ ├── bus.lua │ ├── collatz.lua │ ├── simple_ram.lua │ ├── custom_components.lua │ └── gcd.lua ├── component_examples │ ├── basic_rom.lua │ ├── basic_keyboard.lua │ ├── basic_ram.lua │ └── basic_ram_display.lua ├── legacy_levels │ ├── not.lua │ ├── or.lua │ ├── xor.lua │ ├── and.lua │ ├── decoder.lua │ ├── mux.lua │ ├── nand.lua │ ├── demux.lua │ ├── shifter.lua │ ├── subtractor.lua │ ├── adder.lua │ ├── comparator.lua │ └── wires.lua ├── classic.lua ├── textbox.lua ├── app.lua ├── clock.lua ├── c_api.lua └── utils.lua ├── solutions ├── sol_or.png ├── sol_7seg.png ├── sol_adder.png ├── sol_alu32.png ├── sol_and.png ├── sol_bus.png ├── sol_demux.png ├── sol_gcd.png ├── sol_hanoi.png ├── sol_mux.png ├── sol_nand.png ├── sol_not.png ├── sol_wires.png ├── sol_xor.png ├── sol_collatz.png ├── sol_decoder.png ├── sol_ram8bit.png ├── crash_example.png ├── sol_comparator.png ├── sol_mult_of_3.png ├── sol_subtractor.png └── minimal_cycle_example.png ├── src ├── clip_api_web.cpp ├── w_about.h ├── sprite.h ├── w_levels.h ├── w_text.h ├── w_tutorial.h ├── w_number.h ├── utils.h ├── w_dialog.h ├── filedialog_web.c ├── gui.h ├── clip_api.h ├── tmp.c ├── msg.h ├── w_main.h ├── utils.c ├── steam.h ├── profiler.h ├── colors.c ├── .ycm_extra_conf.py ├── colors.h ├── rect_int.h ├── filedialog.h ├── tiling.h ├── main.c ├── rect_int.c ├── loop_detector.h ├── ui.h ├── filedialog.c ├── loop_detector.c ├── font.h ├── brush.h ├── steam.cpp ├── msg.c ├── clip_api.cpp ├── shaders.h ├── profiler.c ├── w_text.c ├── rendering.h ├── widgets.h ├── w_number.c ├── version.h ├── w_dialog.c ├── shaders.c ├── w_tutorial.c ├── api.h └── w_about.c ├── .clang-format ├── KNOWN_ISSUES.txt ├── .gitignore ├── .gitmodules ├── deploy_here.sh ├── third_party └── README.txt └── README.md /.editorconfig: -------------------------------------------------------------------------------- 1 | [CMakeLists.txt] 2 | indent_size = 2 3 | -------------------------------------------------------------------------------- /fix_demo_package.sh: -------------------------------------------------------------------------------- 1 | rm bin/ca.* 2 | rm bin/ca_console.* 3 | -------------------------------------------------------------------------------- /assets/font3x6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/assets/font3x6.png -------------------------------------------------------------------------------- /assets/font5x7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/assets/font5x7.png -------------------------------------------------------------------------------- /assets/icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/assets/icon2.png -------------------------------------------------------------------------------- /assets/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/assets/icon32.png -------------------------------------------------------------------------------- /assets/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/assets/logo2.png -------------------------------------------------------------------------------- /assets/sprite4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/assets/sprite4.png -------------------------------------------------------------------------------- /assets/tutorial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/assets/tutorial.png -------------------------------------------------------------------------------- /luasrc/imgs/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/flag.png -------------------------------------------------------------------------------- /solutions/sol_or.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_or.png -------------------------------------------------------------------------------- /assets/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/assets/screenshot1.png -------------------------------------------------------------------------------- /assets/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/assets/screenshot2.png -------------------------------------------------------------------------------- /assets/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/assets/screenshot3.png -------------------------------------------------------------------------------- /assets/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/assets/screenshot4.png -------------------------------------------------------------------------------- /luasrc/imgs/failure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/failure.png -------------------------------------------------------------------------------- /luasrc/imgs/success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/success.png -------------------------------------------------------------------------------- /solutions/sol_7seg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_7seg.png -------------------------------------------------------------------------------- /solutions/sol_adder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_adder.png -------------------------------------------------------------------------------- /solutions/sol_alu32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_alu32.png -------------------------------------------------------------------------------- /solutions/sol_and.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_and.png -------------------------------------------------------------------------------- /solutions/sol_bus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_bus.png -------------------------------------------------------------------------------- /solutions/sol_demux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_demux.png -------------------------------------------------------------------------------- /solutions/sol_gcd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_gcd.png -------------------------------------------------------------------------------- /solutions/sol_hanoi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_hanoi.png -------------------------------------------------------------------------------- /solutions/sol_mux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_mux.png -------------------------------------------------------------------------------- /solutions/sol_nand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_nand.png -------------------------------------------------------------------------------- /solutions/sol_not.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_not.png -------------------------------------------------------------------------------- /solutions/sol_wires.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_wires.png -------------------------------------------------------------------------------- /solutions/sol_xor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_xor.png -------------------------------------------------------------------------------- /solutions/sol_collatz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_collatz.png -------------------------------------------------------------------------------- /solutions/sol_decoder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_decoder.png -------------------------------------------------------------------------------- /solutions/sol_ram8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_ram8bit.png -------------------------------------------------------------------------------- /assets/join_our_discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/assets/join_our_discord.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/alu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/alu.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/bus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/bus.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/bus2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/bus2.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/mem64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/mem64.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/sss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/sss.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/or1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/or1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/or2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/or2.png -------------------------------------------------------------------------------- /solutions/crash_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/crash_example.png -------------------------------------------------------------------------------- /solutions/sol_comparator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_comparator.png -------------------------------------------------------------------------------- /solutions/sol_mult_of_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_mult_of_3.png -------------------------------------------------------------------------------- /solutions/sol_subtractor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/sol_subtractor.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/gcd_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/gcd_img.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/or_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/or_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/and1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/and1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/and2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/and2.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mem1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mem1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mem10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mem10.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mem11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mem11.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mem12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mem12.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mem13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mem13.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mem2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mem2.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mem3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mem3.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mem4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mem4.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mem5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mem5.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mem6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mem6.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mem7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mem7.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mem8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mem8.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mem9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mem9.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mux1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mux1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mux2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mux2.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mux3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mux3.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/mux4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/mux4.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/nand0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/nand0.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/nand1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/nand1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/nand2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/nand2.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/nand3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/nand3.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/nand4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/nand4.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/not1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/not1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/not2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/not2.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/sub1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/sub1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/sub2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/sub2.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/sync1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/sync1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/sync2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/sync2.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/sync3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/sync3.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/sync4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/sync4.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/sync5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/sync5.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/sync6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/sync6.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/sync7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/sync7.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/sync8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/sync8.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/tldr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/tldr.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/xor1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/xor1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/xor2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/xor2.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/adder_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/adder_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/and_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/and_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/bus_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/bus_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/demux_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/demux_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/gcd_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/gcd_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/hanoi_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/hanoi_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/hanoi_img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/hanoi_img1.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/hanoi_img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/hanoi_img2.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/hanoi_img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/hanoi_img3.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/mof3_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/mof3_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/mof3_img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/mof3_img.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/mux_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/mux_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/nand_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/nand_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/not_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/not_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/wires_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/wires_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/xor_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/xor_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/sample_palette1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/sample_palette1.png -------------------------------------------------------------------------------- /luasrc/imgs/sample_palette2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/sample_palette2.png -------------------------------------------------------------------------------- /luasrc/imgs/sample_palette3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/sample_palette3.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/decoder1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/decoder1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/decoder2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/decoder2.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/decoder3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/decoder3.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/decoder4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/decoder4.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/demux1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/demux1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/demux2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/demux2.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/demux3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/demux3.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/demux4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/demux4.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/numbers1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/numbers1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/numbers2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/numbers2.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/shift1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/shift1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/shift2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/shift2.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/shift3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/shift3.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/shift4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/shift4.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/wires1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/wires1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/wires2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/wires2.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/wires3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/wires3.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/collatz_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/collatz_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/collatz_img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/collatz_img1.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/custom_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/custom_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/decoder_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/decoder_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/primes_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/primes_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/primes_img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/primes_img1.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/sandbox_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/sandbox_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/shifter_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/shifter_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/wire_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/wire_example.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/addition1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/addition1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/addition2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/addition2.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/addition3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/addition3.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/addition4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/addition4.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/wire_cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/wire_cross.png -------------------------------------------------------------------------------- /luasrc/tutorial/tldr.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name= "TL;DR", 3 | text = [[ 4 | 5 | !img:imgs/tutorial/tldr.png 6 | 7 | ]] 8 | } 9 | 10 | -------------------------------------------------------------------------------- /solutions/minimal_cycle_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/solutions/minimal_cycle_example.png -------------------------------------------------------------------------------- /src/clip_api_web.cpp: -------------------------------------------------------------------------------- 1 | #include "clip_api.h" 2 | 3 | Image ImageFromClipboard() { return (Image){0}; } 4 | void ImageToClipboard(Image img){}; 5 | -------------------------------------------------------------------------------- /luasrc/imgs/levels/comparator_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/comparator_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/riscv_alu_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/riscv_alu_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/riscv_alu_img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/riscv_alu_img1.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/seven_seg_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/seven_seg_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/seven_seg_img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/seven_seg_img1.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/simple_ram_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/simple_ram_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/subtractor_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/subtractor_icon.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/wires_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/wires_example.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/comparator1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/comparator1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/comparator2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/comparator2.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/comparator3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/comparator3.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/comparator4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/comparator4.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/comparator5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/comparator5.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/simu_error1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/simu_error1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/simu_example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/simu_example1.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/wire_diagonal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/wire_diagonal.png -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # Run manually to reformat a file: 2 | # # clang-format -i --style=file 3 | BasedOnStyle: Google 4 | DerivePointerAlignment: false 5 | -------------------------------------------------------------------------------- /luasrc/imgs/levels/seven_seg_display.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/seven_seg_display.png -------------------------------------------------------------------------------- /luasrc/imgs/levels/wires_pins_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/levels/wires_pins_example.png -------------------------------------------------------------------------------- /luasrc/imgs/tutorial/simu_error_cycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/imgs/tutorial/simu_error_cycle.png -------------------------------------------------------------------------------- /luasrc/template_scripts/my_custom_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/template_scripts/my_custom_icon.png -------------------------------------------------------------------------------- /luasrc/template_scripts/my_startup_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lets-all-be-stupid-forever/circuit-artist/HEAD/luasrc/template_scripts/my_startup_image.png -------------------------------------------------------------------------------- /src/w_about.h: -------------------------------------------------------------------------------- 1 | #ifndef W_ABOUT_H 2 | #define W_ABOUT_H 3 | #include "ui.h" 4 | 5 | // About screen. 6 | void AboutOpen(Ui* ui); 7 | void AboutUpdate(Ui* ui); 8 | void AboutDraw(Ui* ui); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /luasrc/template_scripts/README.txt: -------------------------------------------------------------------------------- 1 | Copy this folder to luasrc/scripts/ to have the simple custom level working. 2 | Then you can modify it as you want. 3 | Check init.lua for more info. 4 | The scripts are loaded from 'app.lua'. 5 | -------------------------------------------------------------------------------- /src/sprite.h: -------------------------------------------------------------------------------- 1 | #ifndef SPRITE_H 2 | #define SPRITE_H 3 | #include 4 | 5 | // Simple descriptor of a sprite. 6 | typedef struct { 7 | Texture2D tex; 8 | Rectangle region; 9 | } Sprite; 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /src/w_levels.h: -------------------------------------------------------------------------------- 1 | #ifndef W_LEVELS_H 2 | #define W_LEVELS_H 3 | #include "ui.h" 4 | 5 | // Level selection screen. 6 | void LevelsOpen(Ui* ui); 7 | void LevelsUpdate(Ui* ui); 8 | void LevelsDraw(Ui* ui); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/w_text.h: -------------------------------------------------------------------------------- 1 | #ifndef W_TEXT_H 2 | #define W_TEXT_H 3 | #include "ui.h" 4 | 5 | // Text input screen. 6 | void TextModalOpen(Ui* ui); 7 | void TextModalUpdate(Ui* ui); 8 | void TextModalDraw(Ui* ui); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/w_tutorial.h: -------------------------------------------------------------------------------- 1 | #ifndef W_TUTORIAL_H 2 | #define W_TUTORIAL_H 3 | #include "ui.h" 4 | 5 | // Tutorial screen. 6 | void TutorialOpen(Ui* ui); 7 | void TutorialUpdate(Ui* ui); 8 | void TutorialDraw(Ui* ui); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/w_number.h: -------------------------------------------------------------------------------- 1 | #ifndef W_NUMBER_H 2 | #define W_NUMBER_H 3 | #include "ui.h" 4 | 5 | // Text input screen. 6 | void NumberModalOpen(Ui* ui); 7 | void NumberModalUpdate(Ui* ui); 8 | void NumberModalDraw(Ui* ui); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /KNOWN_ISSUES.txt: -------------------------------------------------------------------------------- 1 | Mac: 2 | - Mouse doesnt appear when window opens (it does appear after it leaves and re-enters window) 3 | - Screen size is inconsistent (resizing fixes it) 4 | - Mouse disappears after resizing. 5 | - Application Icon is not appearing. 6 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | #include "stdbool.h" 4 | 5 | // Does the same as strdup(). 6 | char* CloneString(const char* str); 7 | 8 | // Checks for left control or macos command key down 9 | bool IsControlDown(); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | cmake-build-* 3 | third_party/*.gz 4 | build 5 | build-demo 6 | wip 7 | todo.txt 8 | sdk 9 | ca_* 10 | save.json 11 | luasrc/scripts 12 | third_party/steam 13 | third_party/redistributable_bin 14 | src/raylib.h 15 | src/rlgl.h 16 | src/stb_ds.h 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /src/w_dialog.h: -------------------------------------------------------------------------------- 1 | #ifndef W_DIALOG_H 2 | #define W_DIALOG_H 3 | #include "ui.h" 4 | #include "w_main.h" 5 | 6 | // Dialog screen. 7 | void DialogOpen(Ui* ui, const char* modal_msg, UiCallback modal_next_action); 8 | void DialogUpdate(Ui* ui); 9 | void DialogDraw(Ui* ui); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /luasrc/tutorial/not.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name='NOT Gate', 3 | text=[[ 4 | 5 | !img:imgs/tutorial/not1.png 6 | 7 | The not gate inverts the value of an input bit A. 8 | 9 | Example: 10 | a=0 --> NOT=1 11 | a=1 --> NOT=0 12 | !img:imgs/tutorial/not2.png 13 | A not gate can be create using a single NAND gate, as shown in the picture above. 14 | ]] 15 | } 16 | -------------------------------------------------------------------------------- /luasrc/camera.lua: -------------------------------------------------------------------------------- 1 | local Object = require "classic" 2 | local rl = require 'raylib_api' 3 | local Camera = Object:extend() 4 | 5 | function Camera:new(x, y, s) 6 | self.x = x 7 | self.y = y 8 | self.s = s 9 | end 10 | 11 | function Camera:setup() 12 | rl.rlTranslatef(self.x, self.y, 0); 13 | rl.rlScalef(self.s, self.s, 1); 14 | end 15 | 16 | return Camera 17 | -------------------------------------------------------------------------------- /luasrc/tutorial/or.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name='OR Gate', 3 | text=[[ 4 | 5 | !img:imgs/tutorial/or1.png 6 | 7 | The OR gate returns a 1 if either input `a` OR input `b` is 1. 8 | 9 | Example: 10 | a=0 b=0 --> OR=0 11 | a=0 b=1 --> OR=1 12 | a=1 b=0 --> OR=1 13 | a=1 b=1 --> OR=1 14 | !img:imgs/tutorial/or2.png 15 | An OR gate can be created using 3 NAND gates. 16 | ]] 17 | } 18 | -------------------------------------------------------------------------------- /src/filedialog_web.c: -------------------------------------------------------------------------------- 1 | #include "filedialog.h" 2 | 3 | ModalResult ModalSaveFile(const char* default_path, const char* default_name) { 4 | // Not implemented 5 | return (ModalResult){0}; 6 | } 7 | 8 | ModalResult ModalOpenFile(const char* default_path) { 9 | // Not implemented 10 | return (ModalResult){0}; 11 | } 12 | 13 | void UnloadModalResult(ModalResult mr) { 14 | // Not implemented 15 | } 16 | -------------------------------------------------------------------------------- /src/gui.h: -------------------------------------------------------------------------------- 1 | #ifndef GUI_H 2 | #define GUI_H 3 | 4 | struct widget; 5 | 6 | typedef struct widget { 7 | int id; 8 | int x; 9 | int y; 10 | int w; 11 | int h; 12 | struct widget* parent; 13 | struct widget** children; 14 | } widget; 15 | 16 | widget* gui_container(); 17 | widget* gui_btn(); 18 | void gui_add(widget* p, widget* c); 19 | void gui_del(widget* w); 20 | void gui_layout(widget* w); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /luasrc/tutorial/xor.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name='XOR Gate', 3 | text=[[ 4 | 5 | !img:imgs/tutorial/xor1.png 6 | 7 | The XOR gate returns 1 if exactly one of them is 1, otherwise returns a 0. (it's like a bit sum without carry) 8 | 9 | Example: 10 | a=0 b=0 --> XOR=0 11 | a=0 b=1 --> XOR=1 12 | a=1 b=0 --> XOR=1 13 | a=1 b=1 --> XOR=0 14 | !img:imgs/tutorial/xor2.png 15 | A XOR gate can be created using 4 NAND gates. 16 | ]] 17 | } 18 | -------------------------------------------------------------------------------- /src/clip_api.h: -------------------------------------------------------------------------------- 1 | #ifndef CLIP_API_H 2 | #define CLIP_API_H 3 | #include 4 | 5 | #if defined(__cplusplus) 6 | extern "C" { 7 | #endif 8 | #if defined(__cplusplus) 9 | } 10 | #endif 11 | 12 | #if defined(__cplusplus) 13 | extern "C" { // Prevents name mangling of functions 14 | #endif 15 | 16 | Image ImageFromClipboard(); 17 | void ImageToClipboard(Image img); 18 | 19 | #if defined(__cplusplus) 20 | } 21 | #endif 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/tmp.c: -------------------------------------------------------------------------------- 1 | #include "raylib.h" 2 | #include "stdio.h" 3 | 4 | int main() { 5 | printf("Hello World"); 6 | 7 | SetConfigFlags(FLAG_WINDOW_RESIZABLE); 8 | InitWindow(640 * 2, 320 * 2, "Circuit Artist"); 9 | 10 | while (!WindowShouldClose()) { 11 | BeginDrawing(); 12 | DrawRectangle(0, 0, 2000, 2000, BLACK); 13 | DrawText("hello world", 20, 20, 20, WHITE); 14 | EndDrawing(); 15 | } 16 | 17 | return 0; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /luasrc/tutorial/posint.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name="Unsigned Numbers", 3 | text=[[ 4 | 5 | We can represent `unsigned integer` numbers by simply counting them in increasing order from 0 on. By doing that, every bit will represent a power of two. 6 | !img:imgs/tutorial/numbers1.png 7 | So for example: 8 | 101 = 1*100 + 0*10 + 1*1 = 1*4 + 0*2 + 1*1 = 5 (3 bits number) 9 | 0100 = 0*1000 + 1*100 + 0*10 + 0*1 = 0*8 + 1*4 + 0*2 + 0*1 = 4 (4 bits number) 10 | 11 | ]] 12 | } 13 | -------------------------------------------------------------------------------- /luasrc/tutorial/subtraction.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name="A-B", 3 | text=[[ 4 | 5 | The subtraction of two integer numbers is very similar to its addition. All you need to do is invert the sign of the second number and sum them, as in `A-B = A + (-B)`, but since -N = NOT(N) + 1, all you need to do is A-B = A + NOT(B) + 1. 6 | 7 | This can be done by using the "carry in" of the first `FULL ADDER` in the n-bits adder. 8 | 9 | !img:imgs/tutorial/sub2.png 10 | ]] 11 | } 12 | -------------------------------------------------------------------------------- /src/msg.h: -------------------------------------------------------------------------------- 1 | #ifndef MSG_H 2 | #define MSG_H 3 | #include "ui.h" 4 | 5 | // ----------------------------------------- 6 | // Little messaging system for the main page. 7 | // ----------------------------------------- 8 | 9 | // Adds a message that will expire within `duration` seconds. 10 | void MsgAdd(const char* msg_txt, float duration); 11 | 12 | // Draws messages on screen. 13 | void MsgDraw(Ui* ui); 14 | 15 | // Updates lifetime of messages. 16 | void MsgUpdate(); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/clip"] 2 | path = third_party/clip 3 | url = https://github.com/dacap/clip.git 4 | [submodule "third_party/nativefiledialog-extended"] 5 | path = third_party/nativefiledialog-extended 6 | url = https://github.com/btzy/nativefiledialog-extended.git 7 | [submodule "third_party/raylib"] 8 | path = third_party/raylib 9 | url = https://github.com/raysan5/raylib.git 10 | [submodule "LuaJIT"] 11 | path = LuaJIT 12 | url = https://github.com/LuaJIT/LuaJIT.git 13 | -------------------------------------------------------------------------------- /src/w_main.h: -------------------------------------------------------------------------------- 1 | #ifndef W_MAIN_H 2 | #define W_MAIN_H 3 | #include "ui.h" 4 | 5 | // Main screen. 6 | void MainOpen(Ui* ui); 7 | void MainUpdate(Ui* ui); 8 | void MainDraw(Ui* ui); 9 | 10 | typedef void (*UiCallback)(Ui*); 11 | 12 | void MainUnload(); 13 | void MainSetPaletteFromImage(Image img); 14 | void MainAskForSaveAndProceed(Ui* ui, UiCallback next_action); 15 | void MainPasteText(const char* txt); 16 | void MainSetLineSep(int n); 17 | int MainOnSaveClick(Ui* ui, bool saveas); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "raylib.h" 7 | 8 | // Implementation of the stb_ds library. 9 | #define STB_DS_IMPLEMENTATION 10 | #include 11 | 12 | char* CloneString(const char* str) { 13 | size_t len = strlen(str) + 1; 14 | char* p = (char*)malloc(len); 15 | memmove(p, str, len); 16 | return p; 17 | } 18 | 19 | bool IsControlDown() { 20 | return IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /luasrc/template_scripts/example_custom_level.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This is just a simple example on how to create a level. 3 | You can check luasrc/levels for more details in the default levels. 4 | --]] 5 | require 'api' 6 | local BasicROM = require 'component_examples.basic_rom' 7 | local Clock = require 'clock' 8 | 9 | addLevel({ 10 | icon= '../luasrc/scripts/my_custom_icon.png', 11 | name= 'My Level', 12 | desc= [[ 13 | My level `description`. 14 | ]], 15 | chips={ 16 | Clock(), 17 | BasicROM()} 18 | }) 19 | -------------------------------------------------------------------------------- /luasrc/tutorial/and.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name='AND Gate', 3 | text=[[ 4 | 5 | !img:imgs/tutorial/and1.png 6 | 7 | The AND gate returns a 1 only if both inputs are 1, otherwise returns a 0. Ie, the output is only 1 if `a` AND `b` are 1. 8 | 9 | Example: 10 | a=0 b=0 --> AND=0 11 | a=0 b=1 --> AND=0 12 | a=1 b=0 --> AND=0 13 | a=1 b=1 --> AND=1 14 | !img:imgs/tutorial/and2.png 15 | An AND gate can be created using 2 NAND gates, as shown below. That is equivalent to a NAND gate followed by a NOT gate. 16 | ]] 17 | } 18 | -------------------------------------------------------------------------------- /src/steam.h: -------------------------------------------------------------------------------- 1 | #ifndef CA_STEAM_H 2 | #define CA_STEAM_H 3 | #include "defs.h" 4 | 5 | #if defined(__cplusplus) 6 | extern "C" { 7 | #endif 8 | 9 | CA_API bool SteamEnabled(); 10 | 11 | #ifdef WITH_STEAM 12 | void SteamInit(); 13 | void SteamShutdown(); 14 | CA_API bool SteamGetAchievement(const char* ach_name); 15 | CA_API void SteamSetAchievement(const char* ach_name); 16 | CA_API void SteamClearAchievement(const char* ach_name); 17 | CA_API void SteamRefreshAchievement(); 18 | #endif 19 | 20 | #if defined(__cplusplus) 21 | } 22 | #endif 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/profiler.h: -------------------------------------------------------------------------------- 1 | #ifndef PROFILER_H 2 | #define PROFILER_H 3 | 4 | // Resets stats. Needs to be called begin of each frame. 5 | void ProfilerReset(); 6 | 7 | // Profiling functions that are called every frame. 8 | // The statistics is the running average. 9 | void ProfilerTic(const char* name); 10 | void ProfilerTac(); 11 | 12 | // Profiling single-call functions, that are not called every frame. 13 | void ProfilerTicSingle(const char* name); 14 | void ProfilerTacSingle(const char* name); 15 | 16 | // Draws profiling stats on screen. 17 | void ProfilerDraw(); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /deploy_here.sh: -------------------------------------------------------------------------------- 1 | # Script to creates a release package 2 | # Usage: 3 | # (i) Create a folder for the release and go to it. 4 | # (ii) run bash ../deploy_here.sh 5 | set -e 6 | mkdir bin 7 | cp ../build/Release/* bin/ 8 | mkdir luasrc 9 | cp ../luasrc/*.lua luasrc/ 10 | cp -r ../luasrc/component_examples luasrc/ 11 | cp -r ../luasrc/tutorial luasrc/ 12 | cp -r ../luasrc/imgs luasrc/ 13 | cp -r ../luasrc/levels luasrc/ 14 | cp -r ../luasrc/template_scripts luasrc/ 15 | cp -r ../assets/ . 16 | cp ../LuaJIT/src/lua51.* bin/ 17 | cp ../build/steam_api64.dll bin/ 18 | -------------------------------------------------------------------------------- /src/colors.c: -------------------------------------------------------------------------------- 1 | #include "colors.h" 2 | 3 | Color GetLutColor(ColorLut c) { 4 | switch (c) { 5 | case COLOR_BTN0: 6 | return GetColor(0x6C6C53FF); // darker 7 | case COLOR_BTN1: 8 | return GetColor(0xBBC29CFF); 9 | case COLOR_BTN2: 10 | return GetColor(0xF8FFCBFF); // lighter 11 | case COLOR_ORANGE: 12 | case COLOR_BG0: 13 | return GetColor(0xBB830BFF); // ORANGE 14 | case COLOR_BG1: 15 | case COLOR_DARKGRAY: 16 | return GetColor(0x13151AFF); // DARK GRAY 17 | case COLOR_FC0: 18 | return WHITE; 19 | case COLOR_BTN_BG: 20 | return BLACK; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /luasrc/levels/sandbox.lua: -------------------------------------------------------------------------------- 1 | local LevelComponent = require 'level_component' 2 | local Clock = require 'clock' 3 | 4 | addLevel({ 5 | icon = "../luasrc/imgs/levels/sandbox_icon.png", 6 | name = 'Sandbox', 7 | desc = [[ 8 | 9 | !img:imgs/levels/sss.png 10 | 11 | Simple sandbox mode. No objective. 12 | 13 | You have a `clock` and a `power_on_reset` inputs. 14 | 15 | The `clock` input allows you to create synchronous auto-update circuits. 16 | 17 | The `power_on_reset` input allows you to initialize memory when applicable. It stays on (`1`) for the 2 first clock cycles, then it becomes off (`0`) forever. 18 | ]], 19 | chips = {Clock(false)}, 20 | }) 21 | -------------------------------------------------------------------------------- /assets/shaders/fill_shader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | // Input vertex attributes (from vertex shader) 4 | in vec2 fragTexCoord; 5 | in vec4 fragColor; 6 | 7 | // Input uniform values 8 | uniform sampler2D texture0; 9 | uniform vec4 colDiffuse; 10 | 11 | // Output fragment color 12 | out vec4 finalColor; 13 | 14 | // NOTE: Add here your custom variables 15 | 16 | void main() 17 | { 18 | // Texel color fetching from texture sampler 19 | vec4 texelColor = texture(texture0, fragTexCoord); 20 | 21 | // NOTE: Implement here your fragment shader code 22 | 23 | // finalColor = texelColor*colDiffuse; 24 | finalColor = vec4(0.0, 0.0, 0.0, 0.0); 25 | } 26 | 27 | -------------------------------------------------------------------------------- /luasrc/tutorial/meminit.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name="Memory Initialization", 3 | text=[[ 4 | 5 | `MEMORY INITIALIZATION:` Flip flops don't have an initial state: the latches start at an undefined state, so everytime you have a synchronous circuit, before doing any calculation you need to initialize your memory somehow. 6 | 7 | That's why we often introduce an extra auxiliar bit called `POWER-ON-RESET` that is 1 for a few initial clock cycles, then become zero. 8 | 9 | !img:imgs/tutorial/sync6.png 10 | !img:imgs/tutorial/sync8.png 11 | 12 | You can then augment your flip-flop to accept an extra `RESET` input as follows: 13 | 14 | !img:imgs/tutorial/sync7.png 15 | ]] 16 | } 17 | -------------------------------------------------------------------------------- /src/.ycm_extra_conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import ycm_core 3 | 4 | flags = [ 5 | '-Wall', 6 | '-Wextra', 7 | '-Werror', 8 | '-Wno-long-long', 9 | '-Wno-variadic-macros', 10 | '-fexceptions', 11 | '-ferror-limit=10000', 12 | '-DNDEBUG', 13 | '-std=c99', 14 | '-xc', 15 | '-I../third_party/raylib/src', 16 | '-I../third_party/nativefiledialog-extended/src/include', 17 | '-I../third_party/clip', 18 | '-I../LuaJIT/src', 19 | '-I../third_party', 20 | '-isystem/usr/include/', 21 | ] 22 | 23 | SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', ] 24 | 25 | def FlagsForFile( filename, **kwargs ): 26 | return { 27 | 'flags': flags, 28 | 'do_cache': True 29 | } 30 | -------------------------------------------------------------------------------- /assets/shaders/rotate_shader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | // Input vertex attributes (from vertex shader) 4 | in vec2 fragTexCoord; 5 | in vec4 fragColor; 6 | 7 | // Input uniform values 8 | uniform sampler2D texture0; 9 | uniform vec4 colDiffuse; 10 | uniform int ccw; 11 | 12 | // Output fragment color 13 | out vec4 finalColor; 14 | 15 | // NOTE: Add here your custom variables 16 | 17 | void main() 18 | { 19 | vec2 coord2; 20 | if (ccw == 0) { 21 | coord2 = vec2(-fragTexCoord.y, fragTexCoord.x); 22 | } else { 23 | coord2 = vec2(fragTexCoord.y, -fragTexCoord.x); 24 | } 25 | vec4 texelColor = texture(texture0, coord2); 26 | finalColor = texelColor*colDiffuse; 27 | } 28 | -------------------------------------------------------------------------------- /src/colors.h: -------------------------------------------------------------------------------- 1 | #ifndef COLORS_H 2 | #define COLORS_H 3 | #include "defs.h" 4 | 5 | // Palette for the UI colors. 6 | typedef enum { 7 | COLOR_BTN0, 8 | COLOR_BTN1, 9 | COLOR_BTN2, 10 | COLOR_BTN_BG, 11 | COLOR_BG0, 12 | COLOR_BG1, 13 | COLOR_FC0, 14 | COLOR_DARKGRAY, 15 | COLOR_ORANGE, 16 | } ColorLut; 17 | 18 | // Returns the raylib color for a given color name. 19 | Color GetLutColor(ColorLut c); 20 | 21 | // From RGB color to 0-255 gray value. 22 | static inline int ColorToGray(Color c) { 23 | float r = c.r; 24 | float g = c.g; 25 | float b = c.b; 26 | int out = 0.299f * r + 0.587f * g + 0.114f * b; 27 | if (out < 0) out = 0; 28 | if (out > 255) out = 255; 29 | return out; 30 | } 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /luasrc/tutorial/mux.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name='MUX', 3 | text=[[ 4 | 5 | !img:imgs/tutorial/mux1.png 6 | 7 | A `MUX` is a like a bit `selector`: Given 2 input bits `a` and `b`, and a selection bit `s`, if `s` is 0, `MUX` returns the value of `a`, and if `s` is 1, `MUX` returns the value of `b`. 8 | !img:imgs/tutorial/mux3.png 9 | Muxes work like selectors (or a "switch"). They're useful for example when you have multiple calculations and want to select only one of the results. It's like an "if" statement in programming: IF S=0, RETURN A; ELSE IF S=1 RETURN B;. 10 | !img:imgs/tutorial/mux2.png 11 | A mux can be implemented as shown in the diagram above. 12 | You can also create muxes with more inputs by combining muxes having fewer inputs: 13 | !img:imgs/tutorial/mux4.png 14 | ]] 15 | } 16 | -------------------------------------------------------------------------------- /src/rect_int.h: -------------------------------------------------------------------------------- 1 | #ifndef RECT_INT_H 2 | #define RECT_INT_H 3 | #include 4 | 5 | // Rectangle with int coordinates. 6 | typedef struct { 7 | int x; // Rectangle top-left corner position x 8 | int y; // Rectangle top-left corner position y 9 | int width; // Rectangle width 10 | int height; // Rectangle height 11 | } RectangleInt; 12 | 13 | typedef RectangleInt RecInt; 14 | 15 | // Checks if the rectangle is empty. (ie area == 0) 16 | bool IsRecIntEmpty(RectangleInt r); 17 | 18 | // Checks collision between int rectangles (similar to CheckRectCollision). 19 | bool CheckRecIntCollision(RectangleInt a, RectangleInt b); 20 | 21 | // Computes the intersection of two rectangles. 22 | RectangleInt GetCollisionRecInt(RectangleInt a, RectangleInt b); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /third_party/README.txt: -------------------------------------------------------------------------------- 1 | Credits 2 | ------- 3 | 4 | Game Framework 5 | raylib 6 | raylib.com 7 | 8 | C data structures 9 | stb_ds.h 10 | https://github.com/nothings/stb/blob/master/stb_ds.h 11 | 12 | Font 13 | m5x7 and m3x6 by Daniel Linssen. 14 | https://managore.itch.io/m5x7 15 | 16 | File Dialog 17 | Native File Dialog by Michael Labbe 18 | https://github.com/mlabbe/nativefiledialog 19 | 20 | Copy-paste 21 | Clip library by David Capello / Aseprite. 22 | https://github.com/aseprite/clip 23 | 24 | Scripts 25 | Luajit 26 | https://luajit.org/ 27 | 28 | Trailer Music 29 | Music by Kevin MacLeod 30 | https://freepd.com/electronic.php 31 | 32 | Classes in lua 33 | classic.lua by rxi 34 | https://github.com/rxi/classic 35 | 36 | JSON in lua 37 | json.lua by rxi 38 | https://github.com/rxi/json.lua; 39 | -------------------------------------------------------------------------------- /luasrc/tutorial/propdelay.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name="Propagation Delay", 3 | text =[[ 4 | 5 | `PROPAGATION DELAY:` You should avoid adding gates between the clock signal and the input of flip flops, otherwise your memory will no longer be synchronous and you might have bugs related to some memory being update after the others. 6 | 7 | !img:imgs/tutorial/sync5.png 8 | 9 | However there might be some cases where one would want to create `gated clocks`, namely to reduce NAND updates on unused parts. That might help reduce total update count and speed up simulation. In real circuits, that would be equivalent to consuming less power. 10 | 11 | In those cases, one should plan thoroughly to avoid asynchronous flip flop updates, minding that every NAND has the same update delay of 1 time unit. 12 | 13 | ]] 14 | } 15 | -------------------------------------------------------------------------------- /luasrc/tutorial/init.lua: -------------------------------------------------------------------------------- 1 | -- tutorial stuff 2 | return { 3 | require 'tutorial.tldr', 4 | require 'tutorial.basic', 5 | require 'tutorial.nand', 6 | require 'tutorial.wire', 7 | require 'tutorial.not', 8 | require 'tutorial.and', 9 | require 'tutorial.or', 10 | require 'tutorial.xor', 11 | require 'tutorial.mux', 12 | require 'tutorial.demux', 13 | require 'tutorial.decoder', 14 | require 'tutorial.posint', 15 | require 'tutorial.negint', 16 | require 'tutorial.addition', 17 | require 'tutorial.subtraction', 18 | require 'tutorial.comparator', 19 | require 'tutorial.shifter', 20 | require 'tutorial.srlatch', 21 | require 'tutorial.dlatch', 22 | require 'tutorial.dflipflop', 23 | require 'tutorial.synchronous', 24 | require 'tutorial.meminit', 25 | require 'tutorial.propdelay', 26 | } 27 | -------------------------------------------------------------------------------- /luasrc/tutorial/demux.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name='DEMUX', 3 | text=[[ 4 | 5 | !img:imgs/tutorial/demux1.png 6 | 7 | A `DEMUX` is like a bit `router`: Given an input bit `a`, 2 outputs `Y0` and `Y1` and a selector bit `s`, if `s` is 0, then set `Y0` to the value of `a` and `Y1` to 0. Otherwise, if `s` is 1, set `Y0` to 0 and `Y1` to the value of `a`. 8 | !img:imgs/tutorial/demux3.png 9 | Demuxes work like routers: you have a wire and the selector bit chooses to which wire the input bit should go. 10 | Example: 11 | a=0, s=0 --> Y0 = 0, Y1 = 0 12 | a=1, s=0 --> Y0 = 1, Y1 = 0 13 | a=0, s=1 --> Y0 = 0, Y1 = 0 14 | a=1, s=1 --> Y0 = 0, Y1 = 1 15 | A demux can be implemented as follows: 16 | !img:imgs/tutorial/demux2.png 17 | You can also create demuxes with more outputs by combining demuxes having fewer inputs: 18 | !img:imgs/tutorial/demux4.png 19 | 20 | ]] 21 | } 22 | -------------------------------------------------------------------------------- /luasrc/tutorial/negint.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name="Signed Numbers", 3 | text=[[ 4 | 5 | For `signed integers`, we create a sort of extension of the unsigned integer definition: 6 | !img:imgs/tutorial/numbers2.png 7 | This representation is also called `two's complement`. 8 | 9 | The formula to convert a number to its negative is given as: `-N = NOT(N) + 1`. It works for both positive and negative numbers. 10 | 11 | So let's say you want to find -3 in a 4-bit representation. You know that 3 is represented in positive integers by 0011. So, in order to find -3 first you find NOT(3) by inverting each bit, getting 1100. Then, you add 1 to it, getting 1100 + 1 = 1101, so the representation of -3 is 1101. You can also do the other way around, to get -(-3): First you invert 1101 to get 0010, then you add 1, to get 0010 + 1 = 0011, which is our starting representation of 3. 12 | 13 | ]] 14 | } 15 | -------------------------------------------------------------------------------- /luasrc/component_examples/basic_rom.lua: -------------------------------------------------------------------------------- 1 | local LevelComponent = require 'level_component' 2 | local Clock = require 'clock' 3 | 4 | local BasicROM = LevelComponent:extend() 5 | 6 | function BasicROM:new() 7 | BasicROM.super.new(self) 8 | self.pins = { 9 | {'input', 8, 'rom_ra'}, -- Read address 10 | {'output', 8, 'rom_rd'}, -- memory value 11 | } 12 | end 13 | 14 | function BasicROM:onStart() 15 | -- One could extend this code to read the ROM from a text file whenever 16 | -- simulation starts from example. 17 | local mem = {} 18 | for i=0,255 do 19 | mem[i] = i 20 | end 21 | self.memory = mem 22 | end 23 | 24 | 25 | -- Just returns the values defined on the current test case. 26 | function BasicROM:onUpdate(prevIn, nextIn, output) 27 | local addr = self:toNumber(nextIn.rom_ra) 28 | local memValue = self.memory[addr] 29 | self:setBits(output.rom_rd, memValue) 30 | end 31 | 32 | return BasicROM 33 | -------------------------------------------------------------------------------- /luasrc/tutorial/dlatch.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name='D Latch', 3 | text=[[ 4 | 5 | !img:imgs/tutorial/mem2.png 6 | We can extend the SR latch to create a `D`-Latch, where we have two inputs: a data input `D`, containing the bit to be assigned to the memory, and an `E` enable input, which will tell the memory to update or not (when E=0, the bit is not assigned and nothing happens). The output `Q` will represent the stored bit. 7 | !img:imgs/tutorial/mem3.png 8 | It can be implemented as follows: 9 | !img:imgs/tutorial/mem4.png 10 | As an example on how the output `Q` of the D-Latch changes over time when `E` and `D` changes. 11 | 12 | Mind that Q can change at anytime whenever `E`=1. This can be inconvenient sometimes. Imagine for example, that you pick the stored bit, do some calculation on it and want to store it again. If the E keeps active, the bit will be updated immediately, which will trigger the calculation again! 13 | 14 | ]] 15 | } 16 | -------------------------------------------------------------------------------- /luasrc/tutorial/dflipflop.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name='D Flip Flop', 3 | text=[[ 4 | 5 | !img:imgs/tutorial/mem8.png 6 | Sometimes you want the memory to be updated only once when the E is set to 1. 7 | 8 | Then, you can perform your calculations however you want, have the `D` bit modified with the new (next) value of the storage without interfering the current storage/calculation. Then, the storage is only updated again whenever the E goes to 0 and then back again to 1 ! (ie, in the next `CYCLE`, creating a proper sequential mechanism) 9 | 10 | !img:imgs/tutorial/mem9.png 11 | 12 | This can be achieved with a `D flip flop`, that can be created a using D latches. 13 | 14 | !img:imgs/tutorial/mem10.png 15 | 16 | We call this behaviour a `rising edge-enabled` memory, in contrast with the previous `level-enabled` memory of `D Latches`. Below a comparison between the two storage modes: 17 | !img:imgs/tutorial/mem13.png 18 | 19 | 20 | ]] 21 | } 22 | -------------------------------------------------------------------------------- /luasrc/legacy_levels/not.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | local Not = Tester:extend() 4 | 5 | function Not:new() 6 | Not.super.new(self) 7 | self.has_submit = false 8 | self.pins = { 9 | {'output', 1, 'a'}, 10 | {'input', 1, 'not_a'}, 11 | } 12 | self.schedule = { 13 | {a=0, not_a=1}, 14 | {a=1, not_a=0}, 15 | } 16 | end 17 | 18 | addLevel({ 19 | icon="../luasrc/imgs/levels/not_icon.png", 20 | name="NOT Gate", 21 | desc=[[ 22 | 23 | !img:imgs/tutorial/not1.png 24 | 25 | Objective: Compute the NOT operation of the input pin `a` and write it to `not_a` output pin. 26 | 27 | !hl 28 | 29 | The not gate inverts the value of an input bit A. 30 | 31 | Example: 32 | a=0 --> NOT=1 33 | a=1 --> NOT=0 34 | !img:imgs/tutorial/not2.png 35 | A not gate can be create using a single NAND gate, as shown in the picture above. 36 | 37 | ]], 38 | chips = { 39 | Clock(true), 40 | Not(), 41 | }, 42 | id='NOT', 43 | }) 44 | -------------------------------------------------------------------------------- /src/filedialog.h: -------------------------------------------------------------------------------- 1 | #ifndef FILEDIALOG_H 2 | #define FILEDIALOG_H 3 | #include 4 | 5 | // Result of the call to a file modal (open or save). 6 | // There's a need to call UnloadModalResult once the results have been treated. 7 | typedef struct { 8 | // Chosen file path. 9 | char* path; 10 | // OK flag. If false, it means an error has occurred. 11 | bool ok; 12 | // Cancel flag. 13 | bool cancel; 14 | // If not ok, contains a message describing why it didnt work. 15 | const char* error_msg; 16 | } ModalResult; 17 | 18 | // Initialize the file system modal library 19 | void ModalLoad(); 20 | 21 | // De-initialize the file system modal library 22 | void ModalUnload(); 23 | 24 | // Opens a save file system modal. 25 | ModalResult ModalSaveFile(const char* default_path, const char* default_name); 26 | 27 | // Opens an open file system modal. 28 | ModalResult ModalOpenFile(const char* default_path); 29 | 30 | // Destructor for the moddal result. 31 | void UnloadModalResult(ModalResult mr); 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/tiling.h: -------------------------------------------------------------------------------- 1 | #ifndef TILING_H 2 | #define TILING_H 3 | #include "ui.h" 4 | 5 | // Descriptor for a frame pattern. 6 | // It's used for drawing the frame of the drawing image and the border of 7 | // windows/modals. 8 | typedef struct { 9 | Texture2D tex; 10 | Rectangle frame_left; 11 | Rectangle frame_up; 12 | Rectangle frame_down; 13 | Rectangle frame_right; 14 | Rectangle corner; 15 | } FramePatternDesc; 16 | 17 | // Draws a tiled frame around a rectangle region. 18 | // `s` is the scale of the frame. 19 | void DrawTiledFrame(int s, FramePatternDesc pd, Rectangle inner_content); 20 | 21 | // Draws a tiled pattern in the whole screen. The pattern is the `src` region 22 | // within the texture `tex`, which is repeated on screen with scaling of `s`. 23 | // Used for drawing UI background. 24 | void DrawTiledScreen(int s, Texture2D tex, Rectangle src); 25 | 26 | // Default hard coded tiled pattern. 27 | void DrawDefaultTiledScreen(Ui* ui); 28 | 29 | // Default hard coded frame pattern. 30 | void DrawDefaultTiledFrame(Ui* ui, Rectangle inner_content); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /luasrc/legacy_levels/or.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | local Or = Tester:extend() 4 | 5 | function Or:new() 6 | Or.super.new(self) 7 | self.has_submit = false 8 | self.pins = { 9 | {'output', 1, 'a'}, 10 | {'output', 1, 'b'}, 11 | {'input', 1, 'or_a_b'}, 12 | } 13 | self.schedule = { 14 | {a=0, b=0, or_a_b=0}, 15 | {a=1, b=0, or_a_b=1}, 16 | {a=0, b=1, or_a_b=1}, 17 | {a=1, b=1, or_a_b=1}, 18 | } 19 | end 20 | 21 | addLevel({ 22 | icon="../luasrc/imgs/levels/or_icon.png", 23 | name="OR Gate", 24 | desc=[[ 25 | 26 | !img:imgs/tutorial/or1.png 27 | 28 | Objective: Compute the OR operation of the input pins `a` and `b` and write it to `or_a_b` output pin. 29 | 30 | !hl 31 | 32 | The OR gate returns a 1 if either input `a` OR input `b` is 1. 33 | 34 | Example: 35 | a=0 b=0 --> OR=0 36 | a=0 b=1 --> OR=1 37 | a=1 b=0 --> OR=1 38 | a=1 b=1 --> OR=1 39 | !img:imgs/tutorial/or2.png 40 | An OR gate can be created using 3 NAND gates. 41 | 42 | ]], 43 | chips = { 44 | Clock(true), 45 | Or(), 46 | }, 47 | id='OR', 48 | }) 49 | -------------------------------------------------------------------------------- /luasrc/tutorial/addition.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name="A+B", 3 | text=[[ 4 | 5 | In this section we will see how to `add` two integers of `N` bits (signed or unsigned). 6 | 7 | First, we have to create a sub-circuit that perform the addition of two bits, and outputs both the result, and an extra "carry", as you would do when summing numbers by hand. We call this sub-circuit a `HALF ADDER`. 8 | !img:imgs/tutorial/addition1.png 9 | That can be created as follows: it's just a XOR for the addition, with an extra AND to add a carry whenever both inputs are 1 (only situation where we have a carry=1). 10 | !img:imgs/tutorial/addition2.png 11 | Having the carry as output is not enough. Now we need to extend the subcircuit to accept a carry input as well, as we do when we are summing numbers by hand. This extended sub-circuit is called a `FULL ADDER`, and can be created as follows. Basically instead of summing `a` and `b`, we will sum `a`, `b` and `carry_in` bits. 12 | !img:imgs/tutorial/addition3.png 13 | Once we have the full adder, we can combine them together to create an adder for N-bits, as follows: 14 | !img:imgs/tutorial/addition4.png 15 | 16 | ]] 17 | } 18 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include "ui.h" 2 | 3 | #if defined(WITH_OPENMP) 4 | #include 5 | #endif 6 | 7 | #ifdef WIN32 8 | #ifndef CA_SHOW_CONSOLE 9 | // This pragma hides the console app terminal in window. 10 | // We might want to make it flexible so people can use it to debug lua scripts. 11 | #pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup") 12 | #endif 13 | #endif 14 | 15 | static Ui _ui = {0}; 16 | 17 | int main() { 18 | int demo = 0; 19 | #ifdef DEMO_VERSION 20 | demo = 1; 21 | #endif 22 | UiLoad(&_ui, demo); 23 | #if defined(WITH_OPENMP) 24 | // OpenMP is getting all CPU at Windows for some reason... 25 | // Add limit of 6 threads to reduce its effect until a better solution is 26 | // found. 27 | int nt = omp_get_max_threads(); 28 | nt = nt < 6 ? nt : 6; 29 | omp_set_num_threads(nt); 30 | #endif 31 | 32 | SetExitKey(0); // Avoids window closing with escape key 33 | SetTargetFPS(60); 34 | SetTraceLogLevel(LOG_ERROR); 35 | while (true) { 36 | UiUpdateFrame(&_ui); 37 | if (_ui.should_close) { 38 | break; 39 | } 40 | } 41 | UiUnload(&_ui); 42 | CloseWindow(); 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /luasrc/legacy_levels/xor.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | local Xor = Tester:extend() 4 | 5 | function Xor:new() 6 | Xor.super.new(self) 7 | self.has_submit = false 8 | self.pins = { 9 | {'output', 1, 'a'}, 10 | {'output', 1, 'b'}, 11 | {'input', 1, 'xor_a_b'}, 12 | } 13 | self.schedule = { 14 | {a=0, b=0, xor_a_b=0}, 15 | {a=1, b=0, xor_a_b=1}, 16 | {a=0, b=1, xor_a_b=1}, 17 | {a=1, b=1, xor_a_b=0}, 18 | } 19 | end 20 | 21 | addLevel({ 22 | icon="../luasrc/imgs/levels/xor_icon.png", 23 | name="XOR Gate", 24 | desc=[[ 25 | 26 | !img:imgs/tutorial/xor1.png 27 | 28 | Objective: Compute the XOR operation of the input pins `a` and `b` and write it to `xor_a_b` output pin. 29 | 30 | !hl 31 | 32 | The XOR gate returns 1 if exactly one of them is 1, otherwise returns a 0. (it's like a bit sum without carry) 33 | 34 | Example: 35 | a=0 b=0 --> XOR=0 36 | a=0 b=1 --> XOR=1 37 | a=1 b=0 --> XOR=1 38 | a=1 b=1 --> XOR=0 39 | !img:imgs/tutorial/xor2.png 40 | A XOR gate can be created using 4 NAND gates. 41 | 42 | ]], 43 | chips = { 44 | Clock(true), 45 | Xor(), 46 | }, 47 | id='XOR', 48 | }) 49 | -------------------------------------------------------------------------------- /luasrc/tutorial/shifter.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name="Barrel Shifter", 3 | text=[[ 4 | 5 | In this section we will see how to `shift` bits in a number. 6 | 7 | The `shift` operation is simply moving the bits either to the left or to the right, as in the example below: 8 | 9 | !img:imgs/tutorial/shift1.png 10 | 11 | The shift operation is often represented by `A << b` when `A` is shifted by `b` bits to the left and `A >> b` when it is shifted to the right. 12 | 13 | You can solve it by using a grid-like structure called `barrel shifter`. In order to understand how it works, let's look at a 4-bits right shifter `C = A >> B`. We would want the circuit for each shift value `B` to look more or less like the following: 14 | 15 | !img:imgs/tutorial/shift3.png 16 | 17 | This can be achieved by creating a cell, then reproduce it in a grid-like structure like the picture below, then you create activation bit flags for each possible shift and link them to the associate cells. The function of each cell is to create the "L"-like curves as in the circuit above. 18 | 19 | !img:imgs/tutorial/shift2.png 20 | 21 | We can build each cell the following way: 22 | 23 | !img:imgs/tutorial/shift4.png 24 | 25 | 26 | ]] 27 | } 28 | -------------------------------------------------------------------------------- /luasrc/component_examples/basic_keyboard.lua: -------------------------------------------------------------------------------- 1 | local LevelComponent = require 'level_component' 2 | local Clock = require 'clock' 3 | local rl = require 'raylib_api' 4 | 5 | local BasicKeyboard = LevelComponent:extend() 6 | 7 | function BasicKeyboard:new() 8 | BasicKeyboard.super.new(self) 9 | self.pins = { 10 | {'output', 1, 'key_w'}, 11 | {'output', 1, 'key_a'}, 12 | {'output', 1, 'key_s'}, 13 | {'output', 1, 'key_d'}, 14 | } 15 | end 16 | 17 | function BasicKeyboard:onStart() 18 | self.keys = {w=0,a=0,s=0,d=0} 19 | end 20 | 21 | -- Just returns the values defined on the current test case. 22 | function BasicKeyboard:onUpdate(prevIn, nextIn, output) 23 | output.key_w[1] = self.keys.w 24 | output.key_a[1] = self.keys.a 25 | output.key_s[1] = self.keys.s 26 | output.key_d[1] = self.keys.d 27 | end 28 | 29 | local function toBit(bool) 30 | if bool then return 1 else return 0 end 31 | end 32 | 33 | function BasicKeyboard:onTick(dt) 34 | self.keys.w = toBit(rl.IsKeyDown(rl.KEY_W)) 35 | self.keys.a = toBit(rl.IsKeyDown(rl.KEY_A)) 36 | self.keys.s = toBit(rl.IsKeyDown(rl.KEY_S)) 37 | self.keys.d = toBit(rl.IsKeyDown(rl.KEY_D)) 38 | self.dirty = true 39 | end 40 | 41 | return BasicKeyboard 42 | -------------------------------------------------------------------------------- /luasrc/tutorial/nand.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name="NAND Gate", 3 | text=[[ 4 | 5 | !img:imgs/tutorial/nand1.png 6 | 7 | A NAND logic gate is represented by 3 pixels, as shown below. 8 | 9 | !img:imgs/tutorial/nand0.png 10 | 11 | A logic gate is a "mechanism" that transforms one or two bit inputs into one bit output. 12 | 13 | The NAND logic gate has 2 inputs and 1 output. It "reads" the value of the inputs and, depending on their value, it will assign a value to the output wire. The assignment table can be found below: 14 | 15 | a=0 b=0 --> NAND=1 16 | a=0 b=1 --> NAND=1 17 | a=1 b=0 --> NAND=1 18 | a=1 b=1 --> NAND=0 19 | 20 | For example, if the 2 inputs are 0, then the output will be assigned to 1. See examples below. 21 | 22 | !img:imgs/tutorial/nand2.png 23 | 24 | The NAND gates can be drawn in any orientation (facing up, down, left or right): 25 | 26 | !img:imgs/tutorial/nand3.png 27 | 28 | Every NAND should have its 2 inputs present, as well as the output, otherwise it will trigger an error when the simulation is launched. 29 | 30 | !img:imgs/tutorial/nand4.png 31 | 32 | Every non-black pixel in the image that is not part of a NAND gate is considered a wire, and has some state associated to it, as described below. 33 | 34 | ]] 35 | } 36 | -------------------------------------------------------------------------------- /luasrc/tutorial/basic.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name= "Basics", 3 | text = [[ 4 | 5 | Each non-black pixel in the image represents either a wire or a NAND logic gate. 6 | 7 | You can load arbitrary images in the game, either by dragging, opening or copy-pasting. However, mind that only completely black pixels are considered background, all other pixels will be considered wires or NANDs. 8 | 9 | You can use the game in `sandbox` mode or try to solve `challenges`. The button in the upper-right corner of the screen allows you to switch modes. 10 | 11 | You can also create your own "challenge", or extend the sandbox mode via lua script. You can access the game installation folder, and on the `luasrc/` folder you will find most scripts of the game which you can play with. You can use it to create new challenges, write a "test" for a circuit you want to create, etc (just remember keeping a backup in case of automatic updates as API is not stable yet). The source code for all existing challenges are present there. 12 | 13 | You could use it for instance for creating your own CPU and write machine code as lua text. You could also reproduce your favorite youtube's tutorial, etc. 14 | 15 | You can change the color palette in the lua code. 16 | 17 | ]] 18 | } 19 | -------------------------------------------------------------------------------- /luasrc/component_examples/basic_ram.lua: -------------------------------------------------------------------------------- 1 | local LevelComponent = require 'level_component' 2 | local Clock = require 'clock' 3 | 4 | local BasicRAM = LevelComponent:extend() 5 | 6 | function BasicRAM:new() 7 | BasicRAM.super.new(self) 8 | self.pins = { 9 | {'input', 8, 'ram_wa'}, 10 | {'input', 8, 'ram_wd'}, 11 | {'input', 1, 'ram_we'}, 12 | {'output', 8, 'ram_rd'}, 13 | } 14 | local mem = {} 15 | for i=1,256 do 16 | mem[i] = 0 17 | end 18 | self.memory = mem 19 | end 20 | 21 | function BasicRAM:onStart() 22 | -- initializes with 0 23 | for i=1,256 do 24 | self.memory[i] = 0 25 | end 26 | self.rd = 0 27 | end 28 | 29 | function BasicRAM:onClock(inputs, reset) 30 | if reset then 31 | return 32 | end 33 | local addr = self:toNumber(inputs.ram_wa) 34 | local we = self:toNumber(inputs.ram_we) 35 | self.addr = addr + 1 36 | if we == 1 then 37 | local wd = self:toNumber(inputs.ram_wd) 38 | self.memory[addr + 1] = wd 39 | end 40 | self.rd = self.memory[addr + 1] 41 | self.dirty = true 42 | end 43 | 44 | -- Just returns the values defined on the current test case. 45 | function BasicRAM:onUpdate(prevIn, nextIn, output) 46 | self:setBits(output.ram_rd, self.rd) 47 | end 48 | 49 | return BasicRAM 50 | -------------------------------------------------------------------------------- /luasrc/legacy_levels/and.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | local And = Tester:extend() 4 | 5 | function And:new() 6 | And.super.new(self) 7 | self.has_submit = false 8 | self.pins = { 9 | {'output', 1, 'a'}, 10 | {'output', 1, 'b'}, 11 | {'input', 1, 'and_a_b'}, 12 | } 13 | self.schedule = { 14 | {a=0, b=0, and_a_b=0}, 15 | {a=1, b=0, and_a_b=0}, 16 | {a=0, b=1, and_a_b=0}, 17 | {a=1, b=1, and_a_b=1}, 18 | } 19 | end 20 | 21 | addLevel({ 22 | icon="../luasrc/imgs/levels/and_icon.png", 23 | name="AND Gate", 24 | desc=[[ 25 | 26 | !img:imgs/tutorial/and1.png 27 | 28 | Objective: Compute the AND operation of the input pins `a` and `b` and write it to `and_a_b` output pin. 29 | 30 | !hl 31 | 32 | The AND gate returns a 1 only if both inputs are 1, otherwise returns a 0. Ie, the output is only 1 if `a` AND `b` are 1. 33 | 34 | Example: 35 | a=0 b=0 --> AND=0 36 | a=0 b=1 --> AND=0 37 | a=1 b=0 --> AND=0 38 | a=1 b=1 --> AND=1 39 | !img:imgs/tutorial/and2.png 40 | An AND gate can be created using 2 NAND gates, as shown below. That is equivalent to a NAND gate followed by a NOT gate. 41 | 42 | ]], 43 | chips = { 44 | Clock(true), 45 | And(), 46 | }, 47 | id='AND', 48 | }) 49 | -------------------------------------------------------------------------------- /luasrc/classic.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- classic 3 | -- 4 | -- Copyright (c) 2014, rxi 5 | -- 6 | -- This module is free software; you can redistribute it and/or modify it under 7 | -- the terms of the MIT license. See LICENSE for details. 8 | -- 9 | 10 | 11 | local Object = {} 12 | Object.__index = Object 13 | 14 | 15 | function Object:new() 16 | end 17 | 18 | 19 | function Object:extend() 20 | local cls = {} 21 | for k, v in pairs(self) do 22 | if k:find("__") == 1 then 23 | cls[k] = v 24 | end 25 | end 26 | cls.__index = cls 27 | cls.super = self 28 | setmetatable(cls, self) 29 | return cls 30 | end 31 | 32 | 33 | function Object:implement(...) 34 | for _, cls in pairs({...}) do 35 | for k, v in pairs(cls) do 36 | if self[k] == nil and type(v) == "function" then 37 | self[k] = v 38 | end 39 | end 40 | end 41 | end 42 | 43 | 44 | function Object:is(T) 45 | local mt = getmetatable(self) 46 | while mt do 47 | if mt == T then 48 | return true 49 | end 50 | mt = getmetatable(mt) 51 | end 52 | return false 53 | end 54 | 55 | 56 | function Object:__tostring() 57 | return "Object" 58 | end 59 | 60 | 61 | function Object:__call(...) 62 | local obj = setmetatable({}, self) 63 | obj:new(...) 64 | return obj 65 | end 66 | 67 | 68 | return Object 69 | -------------------------------------------------------------------------------- /luasrc/tutorial/srlatch.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name="SR Latch", 3 | text=[[ 4 | 5 | The objective of this section is to show one way to store 1 bit of memory. 6 | 7 | It's generally not possible to store memory without using cycles, ie, wires which have outputs interlinked with some inputs somehow. We generally want to avoid arbitrarily using cycles in circuits because they can make the circuit complicated and can introduce oscillation errors, but bit storage is an exception. 8 | 9 | That being, the first important configuration we need to know is the `SR latch`: it's a very simple interlinked configuration of gates that is stable and allows memory to be stored somehow, in the sense that it can "remember" its previous configuration and you can change it using the right inputs: 10 | !img:imgs/tutorial/mem1.png 11 | The `S` and `R` inputs stand for `set` and `reset`: you can see that when S=1 and R=0, we have the value of `Q'` updated to 1, and when R=1 and S=0, the value of `Q'` is "reseted" to 0. When inputs are 1,1 the latch doesnt do anything, it just maintains the previous set result, thus working as a 1-bit storage memory. The S=0 and R=0 case is a bit tricky: it makes the output be (1, 1) which is considered invalid, because we want the `Q` and `Q'` values to be opposite, so in practice we try not to use this case. 12 | ]] 13 | } 14 | -------------------------------------------------------------------------------- /luasrc/tutorial/decoder.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name='DECODER', 3 | text=[[ 4 | 5 | !img:imgs/tutorial/decoder1.png 6 | 7 | A decoder transform an input binary number into a new binary number containing exactly 1 bit active, identifying the input. 8 | Example: 9 | `A`=0 (0) --> `D`=01 (1) 10 | `A`=1 (1) --> `D`=10 (2) 11 | !img:imgs/tutorial/decoder2.png 12 | A 1-bit input decoder can be implemented as in the example above. 13 | In order to have encoders with more inputs/outputs (example 2-bit input and 4-bit output), one trick you can do is to add an `ENABLE` input to the 1-bit decoder as follows: 14 | !img:imgs/tutorial/decoder3.png 15 | The enable flag basically makes the decoder work normally when E=1 (ie, the component is "enabled") and make it return all 0 when E=0 (ie, the component is "disabled"). 16 | Once you have this, you can compose them to create bigger decoders, as shown in the diagram below: 17 | !img:imgs/tutorial/decoder4.png 18 | The decoder allows you to easily identify which of the possible combination of inputs you have. For example, let's say you want your circuit to perform one kind of calculation for each possible value of input, then you can use a decoder to convert it to these "flag" formats and then use it as input to each calculation as "enables", allowing only the desired operation (the one with flag=1) to be performed. 19 | 20 | ]] 21 | } 22 | -------------------------------------------------------------------------------- /src/rect_int.c: -------------------------------------------------------------------------------- 1 | #include "rect_int.h" 2 | 3 | bool IsRecIntEmpty(RectangleInt r) { return r.width == 0 || r.height == 0; } 4 | 5 | bool CheckRecIntCollision(RectangleInt a, RectangleInt b) { 6 | int r1x = a.x; 7 | int r1y = a.y; 8 | int r1w = a.width; 9 | int r1h = a.height; 10 | int r2x = b.x; 11 | int r2y = b.y; 12 | int r2w = b.width; 13 | int r2h = b.height; 14 | if (r1x + r1w >= r2x && // r1 right edge past r2 left 15 | r1x <= r2x + r2w && // r1 left edge past r2 right 16 | r1y + r1h >= r2y && // r1 top edge past r2 bottom 17 | r1y <= r2y + r2h) { // r1 bottom edge past r2 top 18 | return true; 19 | } 20 | return false; 21 | } 22 | 23 | RectangleInt GetCollisionRecInt(RectangleInt a, RectangleInt b) { 24 | // adapted from raylib 25 | RectangleInt overlap = {0}; 26 | 27 | int left = (a.x > b.x) ? a.x : b.x; 28 | int right1 = a.x + a.width; 29 | int right2 = b.x + b.width; 30 | int right = (right1 < right2) ? right1 : right2; 31 | int top = (a.y > b.y) ? a.y : b.y; 32 | int bottom1 = a.y + a.height; 33 | int bottom2 = b.y + b.height; 34 | int bottom = (bottom1 < bottom2) ? bottom1 : bottom2; 35 | 36 | if ((left < right) && (top < bottom)) { 37 | overlap.x = left; 38 | overlap.y = top; 39 | overlap.width = right - left; 40 | overlap.height = bottom - top; 41 | } 42 | 43 | return overlap; 44 | } 45 | -------------------------------------------------------------------------------- /src/loop_detector.h: -------------------------------------------------------------------------------- 1 | #ifndef LOOP_DETECTOR_H 2 | #define LOOP_DETECTOR_H 3 | #include "stdbool.h" 4 | 5 | // Detects looping wire within same clock cycle during synchronous simulation. 6 | typedef struct { 7 | int num_updates; 8 | // Update count of each wire. 9 | // Allows the detection of loops: a wire that has been updated too many times 10 | // is identified as a buggy loop wire. 11 | // Has size `nc`. 12 | int* update_count; 13 | // Flag describing whether a given wire has changed state during a simulation 14 | // (simulate) step. Used for tracking which wires have updated in a simulation 15 | // step. Only used during the call to `simulate`. Has size `nc`. 16 | bool* wire_has_changed; 17 | // Total number of wires. 18 | int nc; 19 | // Stack containing the wires that have changed during simulation. 20 | // It is used to avoid iterating through all the wires to find out those who 21 | // have changed at each iteration. 22 | // Has max size `nc`. 23 | int* wire_changed_stack; 24 | } LoopDetector; 25 | 26 | void LoadLoopDetector(LoopDetector* ld, int num_wires); 27 | // Notifies that a wire has changed. Returns 28 | // true if a loop was detected. 29 | bool LoopDetectorNotify(LoopDetector* ld, int wire); 30 | bool LoopDetectorIsWireLooping(LoopDetector* ld, int wire); 31 | void LoopDetectorReset(LoopDetector* ld); 32 | void UnloadLoopDetector(LoopDetector* ld); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/ui.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_H 2 | #define UI_H 3 | #include 4 | 5 | typedef enum { 6 | MOUSE_ARROW, 7 | MOUSE_PEN, 8 | MOUSE_BUCKET, 9 | MOUSE_PICKER, 10 | MOUSE_SELECTION, 11 | MOUSE_MOVE, 12 | MOUSE_RESIZE, 13 | MOUSE_POINTER, 14 | } MouseCursorType; 15 | 16 | // Available windows/screens 17 | typedef enum { 18 | WINDOW_MAIN, 19 | WINDOW_ABOUT, 20 | WINDOW_TEXT, 21 | WINDOW_NUMBER, 22 | WINDOW_TUTORIAL, 23 | WINDOW_LEVELS, 24 | WINDOW_DIALOG, 25 | } WindowEnum; 26 | 27 | // Shared ui state. 28 | typedef struct { 29 | // Global UI pixel scaling. 30 | int scale; 31 | // Global UI sprites loaded from ../assets/sprite4.png 32 | Texture2D sprites; 33 | // Active window/screen. 34 | WindowEnum window; 35 | // Application's icon. 36 | Image icon; 37 | // Current mouse cursor type. 38 | MouseCursorType cursor; 39 | // If true, will try to close the game. 40 | bool close_requested; 41 | // If True, app will close next frame. 42 | bool should_close; 43 | // UI hitbox counter to see which widget is on foreground and which ones are 44 | // in background (increased with every hit each frame). 45 | int hit_count; 46 | // Debug flag, for profiling and others. 47 | bool debug; 48 | // Wether it's demo version. 49 | bool demo; 50 | } Ui; 51 | 52 | void UiLoad(Ui* ui, bool demo); 53 | void UiUpdateFrame(Ui* ui); 54 | void UiUnload(Ui* ui); 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /assets/shaders/project_shader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | // Input vertex attributes (from vertex shader) 4 | in vec2 fragTexCoord; 5 | in vec4 fragColor; 6 | 7 | // Input uniform values 8 | uniform sampler2D texture0; 9 | uniform sampler2D tpl; 10 | uniform vec4 colDiffuse; 11 | 12 | // Output fragment color 13 | out vec4 finalColor; 14 | 15 | uniform vec2 sp; // zoom 16 | uniform ivec2 img_size; // image size in image pixels 17 | 18 | vec4 msample(vec2 pos, int r) { 19 | float kx = 1.0 / float(img_size.x); 20 | float ky = 1.0 / float(img_size.y); 21 | vec4 ss = vec4(0.0,0.0,0.0,0.0); 22 | float cnt = 0.0; 23 | for (int y = -r; y < r; y++) { 24 | for (int x = -r; x < r; x++) { 25 | vec4 s = texture(texture0, vec2(pos.x + (x+0.5)*kx , pos.y +(y+0.5)*ky)); 26 | // if (s.a > 0.01) { 27 | if (true) { 28 | s.a = 1.0; 29 | ss = ss + s; 30 | cnt += 1.0; 31 | } 32 | } 33 | } 34 | return ss / cnt; 35 | } 36 | 37 | void main() 38 | { 39 | // Texel color fetching from texture sampler 40 | vec2 pos = fragTexCoord; 41 | vec4 img_color; 42 | if (sp.x > 1.0 - 1e-2) { 43 | img_color = texture(texture0, pos); 44 | } else if (sp.x > 0.5 - 1e-2) { 45 | img_color = msample(pos, 1); 46 | } else if (sp.x > 0.25 - 1e-2) { 47 | img_color = msample(pos, 2); 48 | } else { 49 | img_color = msample(pos, 4); 50 | } 51 | finalColor = img_color * colDiffuse *fragColor; 52 | } 53 | -------------------------------------------------------------------------------- /luasrc/levels/mof3.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | 4 | local MultipleOf3 = Tester:extend() 5 | 6 | function MultipleOf3:new() 7 | MultipleOf3.super.new(self) 8 | self.has_submit = false 9 | self.pins = { 10 | {'output', 8, 'n'}, 11 | {'input', 1, 'mof3'}, 12 | } 13 | local cases = { 1, 2, 4, 8, 16, 32, 64, 128, 0, 255, 123, 45, 111, 100, 48, 3, 33, 31, 129} 14 | local schedule = {} 15 | for i=1,#cases do 16 | local n = cases[i] 17 | local ans = 0 18 | if (n % 3) == 0 then 19 | ans = 1 20 | end 21 | 22 | local extra = '(multiple of 3)' 23 | if ans ~= 1 then 24 | extra = '(not multiple of 3)' 25 | end 26 | table.insert(schedule, {n=n, mof3=ans, name='n=' .. n .. ' ' .. extra}) 27 | end 28 | self.schedule = schedule 29 | end 30 | 31 | addLevel({ 32 | icon = "../luasrc/imgs/levels/mof3_icon.png", 33 | name = "Multiple of 3", 34 | desc = [[ 35 | 36 | !img:imgs/levels/mof3_img.png 37 | 38 | Check if a number is a multiple of 3. 39 | 40 | You're given as input an 8-bit unsigned positive number `n` and should return a 1-bit result `mof3` with 1 if `n` is a multiple of 3, and 0 otherwise. 41 | 42 | Examples: 43 | 44 | n=0 --> mof3=1 45 | n=1 --> mof3=0 46 | n=2 --> mof3=0 47 | n=44 --> mof3=0 48 | n=111 --> mof3=1 49 | 50 | ]], 51 | chips = { 52 | Clock(true), 53 | MultipleOf3(), 54 | }, 55 | id='MOF3', 56 | }) 57 | -------------------------------------------------------------------------------- /luasrc/legacy_levels/decoder.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | local Decoder = Tester:extend() 4 | 5 | function Decoder:new() 6 | Decoder.super.new(self) 7 | self.has_submit = false 8 | self.pins = { 9 | {'output', 2, 'a'}, 10 | {'input', 4, 'decoder'}, 11 | } 12 | self.schedule = { 13 | {a=0, decoder=1}, 14 | {a=1, decoder=2}, 15 | {a=2, decoder=4}, 16 | {a=3, decoder=8}, 17 | } 18 | end 19 | 20 | addLevel({ 21 | icon="../luasrc/imgs/levels/decoder_icon.png", 22 | name="Decoder", 23 | desc=[[ 24 | 25 | !img:imgs/levels/decoder_icon.png 26 | 27 | Objective: Implement a 2:4 decoder: given a 2-bit input `a`, activate only one bit in the output out of the 4, corresponding to the value of the number in `a`: 28 | 29 | - a=00 --> decoder[0]=1, or, decoder=0001 30 | - a=01 --> decoder[1]=1, or, decoder=0010 31 | - a=10 --> decoder[2]=1, or, decoder=0100 32 | - a=11 --> decoder[3]=1, or, decoder=1000 33 | 34 | !hl 35 | 36 | !img:imgs/tutorial/decoder1.png 37 | 38 | A decoder transform an input binary number into a new binary number containing exactly 1 bit active, identifying the input. 39 | Example: 40 | `A`=0 (0) --> `D`=01 (1) 41 | `A`=1 (1) --> `D`=10 (2) 42 | !img:imgs/tutorial/decoder2.png 43 | A 1-bit input decoder can be implemented as in the example above. 44 | 45 | ]], 46 | chips = { 47 | Clock(true), 48 | Decoder(), 49 | }, 50 | id='Decoder', 51 | }) 52 | -------------------------------------------------------------------------------- /luasrc/tutorial/comparator.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name="A>B", 3 | text=[[ 4 | 5 | The objective of this section is to compare two positive integer numbers `A` and `B`. We often want to do the 3 comparisons at a time, (i) `A > B`, (ii) `A = B` and (iii) `A < B`. 6 | 7 | We start creating a comparator for a single bit: 8 | !img:imgs/tutorial/comparator1.png 9 | Which can be done as follows: 10 | !img:imgs/tutorial/comparator2.png 11 | !img:imgs/tutorial/comparator3.png 12 | Then, in a second step, we extend this 1-bit comparator to accept inputs from a "more significant" bit. The idea is to do it the same way we do to compare two numbers: first we check if the most significant digit is equal, lower or higher than the other: if it's lower or higher we know whether a number is lower or higher, but if they're equal, we need to move forward to the next digit, creating a sequence of comparisons. 13 | !img:imgs/tutorial/comparator4.png 14 | The formulas will look like the following, where `Aprv` = previous A, ie, result from previous comparator (from a more signficant bit), and `Anxt` = next A, ie, result going to next comparator (towards a least significant bit). 15 | 16 | `A`<`Bnxt` = (`A`<`Bprv`) OR (`A`=`Bprv` AND `A`<`B`) 17 | `A`=`Bnxt` = (`A`=`Bprv`) AND (`A`=`B`) 18 | `A`>`Bnxt` = (`A`>`Bprv`) OR (`A`=`Bprv` AND `A`>`B`) 19 | 20 | Then, similarly to the addition, we chain these comparators to create a bigger comparator for N bits. 21 | !img:imgs/tutorial/comparator5.png 22 | ]] 23 | } 24 | -------------------------------------------------------------------------------- /assets/shaders/comb_shader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | // Input vertex attributes (from vertex shader) 4 | in vec2 fragTexCoord; 5 | in vec4 fragColor; 6 | 7 | // Input uniform values 8 | uniform sampler2D texture0; 9 | uniform sampler2D dst_tex; 10 | 11 | uniform ivec2 roi_size; 12 | uniform ivec2 src_size; 13 | uniform ivec2 src_off; 14 | uniform ivec2 dst_off; 15 | uniform ivec2 dst_size; 16 | uniform vec4 colDiffuse; 17 | 18 | out vec4 finalColor; 19 | 20 | void main() 21 | { 22 | vec4 src = texture(texture0, fragTexCoord); 23 | float src_dx = float(src_size.x); 24 | float src_dy = float(src_size.y); 25 | float dst_dx = float(dst_size.x); 26 | float dst_dy = float(dst_size.y); 27 | 28 | // pixel coordinate image in source 29 | int src_x = int(round(fragTexCoord.x * src_dx - 0.5)); 30 | int src_y = int(round((1.0 - fragTexCoord.y) * src_dy - 0.5)); 31 | 32 | // pixel coordinate image in dest 33 | int dst_x = src_x - src_off.x + dst_off.x; 34 | int dst_y = src_y - src_off.y + dst_off.y; 35 | 36 | float dst_tx = (float(dst_x) + 0.5) / dst_dx; 37 | float dst_ty = 1.0 - ((float(dst_y) + 0.5) / dst_dy); 38 | vec2 dst_pos = vec2(dst_tx, dst_ty); 39 | vec4 dst = texture(dst_tex, dst_pos); 40 | 41 | vec4 c = dst; 42 | if (src.r == 0.0 && src.g == 0.0 && src.b == 0.0 && src.a == 1.0) { 43 | c = vec4(0.0, 0.0, 0.0, 0.0); 44 | } else if (src.a != 0.0) { 45 | c = src; 46 | } 47 | finalColor = c*colDiffuse; 48 | } 49 | 50 | -------------------------------------------------------------------------------- /luasrc/textbox.lua: -------------------------------------------------------------------------------- 1 | -- Module to parse text content. 2 | -- Basically will load external assets referenced in text and format it 3 | -- before passing to C. 4 | local R = require 'raylib_api' 5 | local ffi = require 'ffi' 6 | local utils = require 'utils' 7 | 8 | -- module 9 | local tb = {} 10 | 11 | local function toSprite(img) 12 | if type(img)== 'string' then 13 | local tex = R.LoadTexture(img) 14 | img = { tex, 0, 0, tex.width, tex.height, } 15 | end 16 | 17 | local s = ffi.new('Sprite') 18 | s.tex = img[1] 19 | local x = img[2] 20 | local y = img[3] 21 | local w = img[4] 22 | local h = img[5] 23 | s.region = ffi.new('Rectangle', {x,y,w,h}) 24 | return s 25 | end 26 | 27 | -- Transforms a raw text into {text, imgs} pair to be used in 28 | -- draw_textbox 29 | function tb.prepareText(text) 30 | local array = {} 31 | local root = utils.scriptPath() 32 | i = 0 33 | for capture in string.gmatch(text, "!img:([^%s]*)%s") do 34 | i= i+1 35 | table.insert(array, capture) 36 | end 37 | local sprites = {} 38 | local out = text 39 | for i=1,#array do 40 | local path = array[i] 41 | local t = R.LoadTexture(root .. path) 42 | if not (t.width > 0) then 43 | print('bad image:', root .. path) 44 | end 45 | assert (t.width > 0) 46 | table.insert(sprites, toSprite({t, 0, 0, t.width, t.height})) 47 | out = out:gsub("!img:" .. path, '!img:'.. i-1) 48 | end 49 | return { 50 | text=out, 51 | sprites=sprites, 52 | } 53 | end 54 | 55 | return tb 56 | -------------------------------------------------------------------------------- /luasrc/app.lua: -------------------------------------------------------------------------------- 1 | package.path = '../luasrc/?.lua;' .. package.path 2 | package.path = '../luasrc/?/init.lua;' .. package.path 3 | local R = require 'raylib_api' 4 | local C = require 'c_api' 5 | local ffi = require 'ffi' 6 | local utils = require 'utils' 7 | require 'api' 8 | 9 | initTutorial() 10 | 11 | -- Loads each level 12 | require 'levels.sandbox' 13 | require 'levels.bus' 14 | require 'levels.seven_seg' 15 | require 'levels.mof3' 16 | require 'levels.simple_ram' 17 | require 'levels.collatz' 18 | require 'levels.gcd' 19 | require 'levels.riscv_alu' 20 | require 'levels.hanoi' 21 | require 'levels.custom_components' 22 | -- require 'levels.primes' 23 | 24 | 25 | -- Legacy levels 26 | -- require 'levels.legacy_levels.wires' 27 | -- require 'levels.legacy_levels.nand' 28 | -- require 'levels.legacy_levels.not' 29 | -- require 'levels.legacy_levels.and' 30 | -- require 'levels.legacy_levels.or' 31 | -- require 'levels.legacy_levels.xor' 32 | -- require 'levels.legacy_levels.mux' 33 | -- require 'levels.legacy_levels.decoder' 34 | -- require 'levels.legacy_levels.demux' 35 | -- require 'levels.legacy_levels.adder' 36 | -- require 'levels.legacy_levels.subtractor' 37 | -- require 'levels.legacy_levels.comparator' 38 | -- require 'levels.legacy_levels.shifter' 39 | 40 | loadProgress() 41 | 42 | setInitialLevelByName('Sandbox') 43 | 44 | if utils.isModuleAvailable('scripts') then 45 | require('scripts') 46 | C.CaAddMessage("Imported custom scripts.", 5) 47 | else 48 | print('scripts not found') 49 | end 50 | 51 | apiLoadLevel() 52 | -------------------------------------------------------------------------------- /src/filedialog.c: -------------------------------------------------------------------------------- 1 | #include "filedialog.h" 2 | 3 | #include 4 | 5 | #define NFD_NATIVE 6 | #include 7 | 8 | static nfdresult_t ModalInitResult = NFD_ERROR; 9 | // TODO: do something in case of error 10 | void ModalLoad() { ModalInitResult = NFD_Init(); } 11 | 12 | void ModalUnload() { 13 | if (ModalInitResult == NFD_OKAY) NFD_Quit(); 14 | } 15 | 16 | ModalResult ModalSaveFile(const char* default_path, const char* default_name) { 17 | ModalResult mr = {0}; 18 | nfdchar_t* outpath = NULL; 19 | nfdu8filteritem_t filter_list[] = {{"Image", "png"}}; 20 | nfdresult_t result = 21 | NFD_SaveDialogU8(&outpath, filter_list, 1, default_path, default_name); 22 | if (result != NFD_CANCEL && result != NFD_OKAY) { 23 | mr.error_msg = NFD_GetError(); 24 | } 25 | mr.path = outpath; 26 | mr.ok = result == NFD_OKAY; 27 | mr.cancel = result == NFD_CANCEL; 28 | return mr; 29 | } 30 | 31 | ModalResult ModalOpenFile(const char* default_path) { 32 | nfdchar_t* outpath = NULL; 33 | nfdu8filteritem_t filter_list[] = {{"Image", "png"}}; 34 | const nfdnchar_t* n_default_path = default_path; 35 | nfdresult_t result = 36 | NFD_OpenDialogU8(&outpath, filter_list, 1, n_default_path); 37 | ModalResult mr = {0}; 38 | mr.ok = result == NFD_OKAY; 39 | mr.path = outpath; 40 | mr.cancel = result == NFD_CANCEL; 41 | if (result != NFD_CANCEL && result != NFD_OKAY) { 42 | mr.error_msg = NFD_GetError(); 43 | } 44 | return mr; 45 | } 46 | 47 | void UnloadModalResult(ModalResult mr) { 48 | if (mr.path) { 49 | free(mr.path); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/loop_detector.c: -------------------------------------------------------------------------------- 1 | #include "loop_detector.h" 2 | 3 | #include "stdio.h" 4 | #include "stdlib.h" 5 | #include "string.h" 6 | 7 | static int MAX_CIRCUIT_UPDATE_COUNT = 100; 8 | 9 | void LoadLoopDetector(LoopDetector* ld, int nc) { 10 | ld->nc = nc; 11 | ld->num_updates = 0; 12 | ld->update_count = malloc(nc * sizeof(int)); 13 | ld->wire_has_changed = malloc(nc * sizeof(bool)); 14 | ld->wire_changed_stack = malloc(nc * sizeof(int)); 15 | for (int i = 0; i < nc; i++) { 16 | ld->wire_has_changed[i] = false; 17 | ld->update_count[i] = 0; 18 | } 19 | } 20 | 21 | bool LoopDetectorNotify(LoopDetector* ld, int k) { 22 | // Actual wire Update has happened 23 | // Attention: wire update is differetn from nand update 24 | ld->update_count[k]++; 25 | // the purpose of wire_has_changed right now is to set update to 0 26 | if (!ld->wire_has_changed[k]) { 27 | ld->wire_has_changed[k] = true; 28 | ld->wire_changed_stack[ld->num_updates++] = k; 29 | } 30 | return ld->update_count[k] > MAX_CIRCUIT_UPDATE_COUNT; 31 | } 32 | 33 | void UnloadLoopDetector(LoopDetector* ld) { 34 | free(ld->update_count); 35 | free(ld->wire_has_changed); 36 | free(ld->wire_changed_stack); 37 | } 38 | 39 | void LoopDetectorReset(LoopDetector* ld) { 40 | for (int i = 0; i < ld->num_updates; i++) { 41 | int w = ld->wire_changed_stack[i]; 42 | ld->wire_has_changed[w] = false; 43 | ld->update_count[w] = 0; 44 | } 45 | ld->num_updates = 0; 46 | } 47 | 48 | bool LoopDetectorIsWireLooping(LoopDetector* ld, int k) { 49 | return ld->update_count[k] > MAX_CIRCUIT_UPDATE_COUNT / 2; 50 | } 51 | -------------------------------------------------------------------------------- /luasrc/legacy_levels/mux.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | local Mux = Tester:extend() 4 | 5 | function Mux:new() 6 | Mux.super.new(self) 7 | self.has_submit = false 8 | self.pins = { 9 | {'output', 1, 'a'}, 10 | {'output', 1, 'b'}, 11 | {'output', 1, 's'}, 12 | {'input', 1, 'mux'}, 13 | } 14 | self.schedule = { 15 | {a=0, b=0, s=0, mux=0}, 16 | {a=1, b=0, s=0, mux=1}, 17 | {a=0, b=1, s=0, mux=0}, 18 | {a=1, b=1, s=0, mux=1}, 19 | {a=0, b=0, s=1, mux=0}, 20 | {a=1, b=0, s=1, mux=0}, 21 | {a=0, b=1, s=1, mux=1}, 22 | {a=1, b=1, s=1, mux=1}, 23 | } 24 | end 25 | 26 | addLevel({ 27 | icon="../luasrc/imgs/levels/mux_icon.png", 28 | name="MUX", 29 | desc=[[ 30 | 31 | !img:imgs/tutorial/mux1.png 32 | 33 | Objective: Implement a 2:1 multiplexer: given two input bits `a` and `b`, if `s` is off, return the value of `a`, otherwise return the value of `b`. 34 | 35 | !hl 36 | 37 | A `MUX` is a like a bit `selector`: Given 2 input bits `a` and `b`, and a selection bit `s`, if `s` is 0, `MUX` returns the value of `a`, and if `s` is 1, `MUX` returns the value of `b`. 38 | !img:imgs/tutorial/mux3.png 39 | Muxes work like selectors (or a "switch"). They're useful for example when you have multiple calculations and want to select only one of the results. It's like an "if" statement in programming: IF S=0, RETURN A; ELSE IF S=1 RETURN B;. 40 | !img:imgs/tutorial/mux2.png 41 | A mux can be implemented as shown in the diagram above. 42 | 43 | ]], 44 | chips = { 45 | Clock(true), 46 | Mux(), 47 | }, 48 | id='MUX', 49 | }) 50 | 51 | 52 | -------------------------------------------------------------------------------- /luasrc/levels/primes.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | 4 | local Primes = Tester:extend() 5 | 6 | function calcPrimes(n) 7 | -- p[i] = true if i is prime, false otherwise 8 | local p = {} 9 | for i=2,n do 10 | p[i] = true 11 | end 12 | for i=2,n do 13 | if p[i] then 14 | for j=2,n do 15 | local m = j*i 16 | if m > n then 17 | break 18 | end 19 | p[m] = false 20 | end 21 | end 22 | end 23 | return p 24 | end 25 | 26 | function Primes:new(n) 27 | Primes.super.new(self) 28 | self.has_submit = true 29 | self.pins = { 30 | {'input', 1, 'submit'}, 31 | {'output', 8, 'n'}, 32 | {'input', 1, 'isprime'}, 33 | } 34 | local primes = calcPrimes(n) 35 | local schedule = {} 36 | for i = 2,n do 37 | local r = 0 38 | if primes[i] then r = 1 end 39 | local extra = '(not prime)' 40 | if r == 1 then extra = '(prime)' end 41 | table.insert(schedule, {n=i, isprime=r, name='n=' .. i .. ' ' .. extra}) 42 | end 43 | self.schedule = schedule 44 | end 45 | 46 | addLevel({ 47 | icon = "../luasrc/imgs/levels/primes_icon.png", 48 | name = "Primes", 49 | desc=[[ 50 | 51 | !img:imgs/levels/primes_img1.png 52 | 53 | Find if an 8-bit (unsigned) positive number `n` > 1 is a prime. 54 | 55 | You can use `multiple clock cycles` to compute the result. The result will only be verified when the `submit` flag is on. The check is done on the rising edge of the clock. After verification is passed, the next test case will be provided. 56 | 57 | ]], 58 | chips = { 59 | Clock(), 60 | Primes(250), 61 | }, 62 | id='PRIMES', 63 | }) 64 | -------------------------------------------------------------------------------- /luasrc/clock.lua: -------------------------------------------------------------------------------- 1 | local LevelComponent = require 'level_component' 2 | local Clock = LevelComponent:extend() 3 | local C = require 'c_api' 4 | local S = C.GetSharedState() 5 | 6 | function Clock:new(hidden) 7 | if hidden == nil then hidden = false end 8 | self.super.new(self) 9 | self.hidden = hidden 10 | if hidden then 11 | self.pins = {} 12 | else 13 | self.pins = { 14 | {'output', 1, 'power_on_reset'}, 15 | {'output', 1, 'clock'}, 16 | } 17 | end 18 | end 19 | 20 | function Clock:stopClock() 21 | self.stop = true 22 | end 23 | 24 | function Clock:onStart() 25 | S.elapsed = 0 26 | self.value = 0 27 | self.cycle = 0 28 | self.stop = false 29 | end 30 | 31 | function Clock:onTick(dt) 32 | S.elapsed = S.elapsed + dt 33 | while (S.elapsed > S.clock_time) and not self.stop do 34 | self.value = 1 - self.value 35 | S.elapsed = S.elapsed - S.clock_time 36 | if self.value == 1 then 37 | self.cycle = self.cycle + 1 38 | end 39 | self.dirty = true 40 | end 41 | end 42 | 43 | function Clock:getClockCount() 44 | return self.cycle 45 | end 46 | 47 | 48 | function Clock:getPowerOnReset() 49 | local por = 0 50 | if self.cycle < 2 then 51 | por = 1 52 | end 53 | return por 54 | end 55 | 56 | function Clock:onUpdate(prevIn, nextIn, output) 57 | local por = self:getPowerOnReset() 58 | if not self.hidden then 59 | self:setBits(output.power_on_reset, por) 60 | self:setBits(output.clock, self.value) 61 | end 62 | end 63 | 64 | -- Global function, useful to stop clock from anywhere 65 | function stopClock() 66 | local clk = getLevelComponents()[1] 67 | clk:stopClock() 68 | local clock_count = clk:getClockCount() 69 | return clock_count 70 | end 71 | 72 | 73 | return Clock 74 | -------------------------------------------------------------------------------- /src/font.h: -------------------------------------------------------------------------------- 1 | #ifndef FONT_H 2 | #define FONT_H 3 | #include "sprite.h" 4 | 5 | // Loads font from a dedicated file. 6 | // It's not stable, the font format needs to move towards what raylib is using. 7 | void LoadArtFont(const char* filepath); 8 | 9 | // Destructor. 10 | void UnloadArtFont(); 11 | 12 | // Simple text drawing in an image. 13 | void FontDraw(Image* dst, const char* txt, int x, int y, Color c); 14 | 15 | // Draws a text line directly in the main screen. 16 | void FontDrawTexture(const char* txt, int x, int y, Color c); 17 | 18 | // Draws an outlined text line in main screen. 19 | void FontDrawTextureOutlined(const char* txt, int x, int y, Color c, Color bg); 20 | 21 | // Draws a text box. 22 | // If a height pointer is passed, instead of drawing it computes the height of 23 | // the rendered text. 24 | void DrawTextBox(const char* text, Rectangle rect, Color c, int* height); 25 | 26 | // Computes the height of a rendered textbox. 27 | void GetDrawTextBoxSize(const char* text, int lw, int* h, int* w); 28 | 29 | // Returns the font line height in pixels. 30 | int GetFontLineHeight(); 31 | 32 | // Computes the x and y size of a rendered text line. 33 | Vector2 GetRenderedTextSize(const char* txt); 34 | 35 | // Renders a text line into an image. The caller should take ownership of the 36 | // image. 37 | Image RenderText(const char* txt, Color c); 38 | 39 | // Advanced text box rendering. 40 | // It can render: 41 | // - Highlighted text via `blabla` 42 | // - Images via !img: --> uses the number-th sprite passed as input in 43 | // `sprites`. It is used for both tutorial and level description rendering. 44 | void DrawTextBoxAdvanced(const char* text, Rectangle rect, Color c, 45 | Sprite* sprites, int* height); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/brush.h: -------------------------------------------------------------------------------- 1 | #ifndef BRUSH_H 2 | #define BRUSH_H 3 | #include "img.h" 4 | 5 | // Context for 2d Brush tool. 6 | // 7 | // A brush context is basically a concatenated list of 2D points that is 8 | // created while the user drags his mouse with cursor down. 9 | typedef struct { 10 | // Number of points in the current path. 11 | int path_cnt; 12 | // Maximum buffer size for points. If the limit is reached, the buffer is 13 | // re-allocated and capacity is increased. 14 | int max_path_size; 15 | // List of XY pixel points in the brush path. 16 | // It's stored like X0,Y0,X1,Y1,X2,Y1, ... 17 | int* path_pixels; 18 | } Brush; 19 | 20 | // Initializes brush algorithm context. 21 | void BrushLoad(Brush* b); 22 | 23 | // Destructor. 24 | void BrushUnload(Brush* b); 25 | 26 | // Adds a new point to the brush path. 27 | void BrushAppendPoint(Brush* b, int px, int py); 28 | 29 | // Resets the brush path. 30 | void BrushReset(Brush* b); 31 | 32 | // Transforms the path into a Image, using a slightly modified version of 33 | // bresenham line algorithm, where we make sure that the path is connected via 34 | // 4-neighbour connectivity (so that it belongs to the same wire). 35 | // 36 | // It takes as argument the color of the brush (`c`), the total size of the 37 | // original image (`w` and `h`), a pointer to an image that is created and a 38 | // pointer to the offset of the created image. 39 | // The created image is actually a sub-image containing the path, it's not a 40 | // image of the size of the original image. This sub-image has an offset 41 | // returned via the `off` parameter. 42 | // 43 | // The caller should take ownership of the created `out` image. 44 | void BrushMakeImage(Brush* b, Color c, int w, int h, Image* out, 45 | Vector2Int* off); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /luasrc/tutorial/wire.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name="Wires", 3 | text=[[ 4 | 5 | Wires are formed by connected non-black pixels. Wires can have 4 states, as displayed below: 6 | - `ON` (lighter color) 7 | - `OFF` (darker color) 8 | - `UNDEFINED` (magenta color) 9 | - `ERROR` (red color) 10 | !img:imgs/tutorial/wires1.png 11 | The `ON` state represents the logic 1, and the `OFF` state represents the logic 0. Wires state can be toggled (1->0 or 0->1) by clicking on it during simulation mode. 12 | !img:imgs/tutorial/wires2.png 13 | The `ERROR` wires only appear when an error occurs during simulation. There are 2 possible errors that can occur: 14 | Error type 1: A wire that belongs to the output of more than one gate. Example below. Each wire can only be the output of maximum 1 gate. 15 | !img:imgs/tutorial/simu_error1.png 16 | Error type 2: When the wire state oscillates between different values without end. Example below. 17 | !img:imgs/tutorial/simu_error_cycle.png 18 | The `UNDEFINED` state means that we don't know the state of the wire. Every wire start with that state, with exception of wires that are not output of a NAND gate. Those wires are initialized as 0. 19 | Below an example of how the wire states change when the simulation is first launched: 20 | !img:imgs/tutorial/simu_example1.png 21 | The width or shape of a wire is not important, nor its color, as long as the pixels are connected. 22 | !img:imgs/tutorial/wires3.png 23 | However, wires are allowed to cross. As a rule of thumb, straight wires only connect at T or L connections. 24 | !img:imgs/tutorial/wire_cross.png 25 | Wires are only connected via up/down/left/right pixel neighbours. Diagonal pixels are not considered connected. In the example below, in the first case you have 4 different wires, while in the second example you have a single wire. 26 | !img:imgs/tutorial/wire_diagonal.png 27 | ]] 28 | } 29 | -------------------------------------------------------------------------------- /luasrc/template_scripts/init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This is just a template file, in order for it to work you'll need to copy-paste 3 | it to the luasrc/scripts/ folder. The scripts/ folder is never changed by steam 4 | on updates on uninstalls. 5 | 6 | The app will automatically look for the 'scripts/init.lua' folder at startup 7 | and run it. That's where you call/create your levels. Don't hesitate looking at 8 | other scripts for reference, or post something at github if you have doubts. 9 | 10 | When developping locally, you might want to change the steam launcher to the 11 | console launcher, so you have access to the logs and error messages. 12 | 13 | Even better, you might want to manually launch the application from the 14 | terminal. Just go to the installation folder's bin/ and launch it as 15 | ./ca_console.exe, this way the console messages will be in your terminal (you 16 | might even integrate in your development workflow) (You need to launch it from 17 | the bin/ folder, or set the CHDIR to the bin/ folder, because there are many 18 | relative paths from there) 19 | 20 | Mind that the app loads the script at startup, so you'll need to relaunch at 21 | every modification (unless you do some adaptions at lua side to support reload). 22 | 23 | Import your scripts from here. 24 | 25 | --]] 26 | 27 | -- Imports the custom script 28 | require 'scripts.example_custom_level' 29 | 30 | -- Makes the custom script be the initially selected one 31 | -- The name string should match the name defined in the level definition. 32 | setInitialLevelByName('My Level') 33 | 34 | -- You can use this function to modify the initial image so you don't need to 35 | -- reload your image everytime you relaunch the application/change your script. 36 | -- Path needs to be relative to the bin/ folder or an absolute path! 37 | setStartupImage('../luasrc/template_scripts/my_startup_image.png') 38 | -------------------------------------------------------------------------------- /src/steam.cpp: -------------------------------------------------------------------------------- 1 | #include "steam.h" 2 | 3 | #include 4 | 5 | #ifdef WITH_STEAM 6 | #include "steam/steam_api_flat.h" 7 | #endif 8 | 9 | bool SteamEnabled() { 10 | #ifdef WITH_STEAM 11 | return true; 12 | #else 13 | return false; 14 | #endif 15 | } 16 | 17 | #ifdef WITH_STEAM 18 | 19 | static struct { 20 | bool loaded; 21 | ISteamUserStats* user_stats; 22 | bool stats; 23 | } C = {0}; 24 | 25 | void SteamInit() { 26 | C.loaded = SteamAPI_Init(); 27 | if (C.loaded) { 28 | C.user_stats = SteamAPI_SteamUserStats(); 29 | C.stats = SteamAPI_ISteamUserStats_RequestCurrentStats(C.user_stats); 30 | uint32 num_achievements = 31 | SteamAPI_ISteamUserStats_GetNumAchievements(C.user_stats); 32 | printf("STEAM loaded=%d stats=%d num_achievements=%d\n", C.loaded, C.stats, 33 | num_achievements); 34 | } 35 | } 36 | 37 | bool SteamGetAchievement(const char* ach_name) { 38 | bool ret = false; 39 | if (C.loaded) { 40 | bool s = 41 | SteamAPI_ISteamUserStats_GetAchievement(C.user_stats, ach_name, &ret); 42 | if (!s) { 43 | printf("Error in steam api!\n"); 44 | } 45 | } 46 | return ret; 47 | } 48 | 49 | void SteamSetAchievement(const char* ach_name) { 50 | if (C.loaded) { 51 | bool s = SteamAPI_ISteamUserStats_SetAchievement(C.user_stats, ach_name); 52 | if (!s) { 53 | printf("Error in steam api!\n"); 54 | } 55 | } 56 | } 57 | 58 | void SteamShutdown() { 59 | if (C.loaded) { 60 | SteamAPI_Shutdown(); 61 | } 62 | } 63 | 64 | void SteamClearAchievement(const char* ach_name) { 65 | if (C.loaded) { 66 | bool s = SteamAPI_ISteamUserStats_ClearAchievement(C.user_stats, ach_name); 67 | if (!s) { 68 | printf("Error in steam api!\n"); 69 | } 70 | } 71 | } 72 | 73 | void SteamRefreshAchievement() { 74 | if (C.loaded) { 75 | bool s = SteamAPI_ISteamUserStats_StoreStats(C.user_stats); 76 | if (!s) { 77 | printf("Error in steam api!\n"); 78 | } 79 | } 80 | } 81 | #endif 82 | -------------------------------------------------------------------------------- /luasrc/legacy_levels/nand.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | local Nand = Tester:extend() 4 | 5 | function Nand:new() 6 | Nand.super.new(self) 7 | self.has_submit = false 8 | self.pins = { 9 | {'output', 1, 'a'}, 10 | {'output', 1, 'b'}, 11 | {'input', 1, 'nand_a_b'}, 12 | } 13 | self.schedule = { 14 | {a=0, b=0, nand_a_b=1}, 15 | {a=1, b=0, nand_a_b=1}, 16 | {a=0, b=1, nand_a_b=1}, 17 | {a=1, b=1, nand_a_b=0}, 18 | } 19 | end 20 | 21 | addLevel({ 22 | icon="../luasrc/imgs/levels/nand_icon.png", 23 | name="NAND Gate", 24 | desc=[[ 25 | 26 | !img:imgs/tutorial/nand1.png 27 | 28 | Objective: Compute the NAND operation of the input pins `a` and `b` and write it to `nand_a_b` output pin. 29 | 30 | !hl 31 | 32 | A NAND logic gate is represented by 3 pixels, as shown below. 33 | 34 | !img:imgs/tutorial/nand0.png 35 | 36 | A logic gate is a "mechanism" that transforms one or two bit inputs into one bit output. 37 | 38 | The NAND logic gate has 2 inputs and 1 output. It "reads" the value of the inputs and, depending on their value, it will assign a value to the output wire. The assignment table can be found below: 39 | 40 | a=0 b=0 --> NAND=1 41 | a=0 b=1 --> NAND=1 42 | a=1 b=0 --> NAND=1 43 | a=1 b=1 --> NAND=0 44 | 45 | For example, if the 2 inputs are 0, then the output will be assigned to 1. See examples below. 46 | 47 | !img:imgs/tutorial/nand2.png 48 | 49 | The NAND gates can be drawn in any orientation (facing up, down, left or right): 50 | 51 | !img:imgs/tutorial/nand3.png 52 | 53 | Every NAND should have its 2 inputs present, as well as the output, otherwise it will trigger an error when the simulation is launched. 54 | 55 | !img:imgs/tutorial/nand4.png 56 | 57 | Every non-black pixel in the image that is not part of a NAND gate is considered a wire, and has some state associated to it, as described below. 58 | 59 | 60 | ]], 61 | chips = { 62 | Clock(true), 63 | Nand(), 64 | }, 65 | id='NAND', 66 | }) 67 | -------------------------------------------------------------------------------- /src/msg.c: -------------------------------------------------------------------------------- 1 | #include "msg.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "colors.h" 9 | #include "font.h" 10 | #include "utils.h" 11 | 12 | typedef struct Msg { 13 | // Text to be displayed 14 | char* txt; 15 | // Time when the message will expire. 16 | float expire_at; 17 | // Linked list handle. 18 | struct Msg* nxt; 19 | } Msg; 20 | 21 | static struct { 22 | Msg* msg_queue; 23 | } C = {0}; 24 | 25 | void MsgAdd(const char* msg_txt, float duration) { 26 | Msg* m = malloc(sizeof(Msg)); 27 | m->txt = CloneString(msg_txt); 28 | m->expire_at = GetTime() + duration; 29 | m->nxt = C.msg_queue; 30 | C.msg_queue = m; 31 | } 32 | 33 | void MsgDraw(Ui* ui) { 34 | Msg* m = C.msg_queue; 35 | int y = 50; 36 | int sw = GetScreenWidth(); 37 | int s = 2; 38 | int dh = 24; 39 | float now = GetTime(); 40 | Color r = GetLutColor(COLOR_BTN2); 41 | Color r2 = r; 42 | r2.a = 255 * 0.3; 43 | while (m) { 44 | rlPushMatrix(); 45 | rlScalef(s, s, 1); 46 | float dt = m->expire_at - now; 47 | Vector2 size = GetRenderedTextSize(m->txt); 48 | int x = sw / s / 2 - size.x / 2; 49 | int p = 6; 50 | DrawRectangle(x - p, y - p, size.x + 2 * p, size.y + 2 * p, BLACK); 51 | DrawRectangle(x - p, y - p, size.x + 2 * p, size.y + 2 * p, r2); 52 | DrawRectangle(x - p + 1, y - p + 1, size.x + 2 * p - 2, size.y + 2 * p - 2, 53 | BLACK); 54 | float t = sinf(10 * dt); 55 | Color c = r; 56 | c.a = (t * 0.4 + 0.6) * 255; 57 | FontDrawTexture(m->txt, x, y, c); 58 | rlPopMatrix(); 59 | y += dh; 60 | m = m->nxt; 61 | } 62 | } 63 | 64 | void MsgUpdate() { 65 | Msg* prv = NULL; 66 | Msg* m = C.msg_queue; 67 | double now = GetTime(); 68 | while (m) { 69 | Msg* nxt = m->nxt; 70 | if (now > m->expire_at) { 71 | free(m->txt); 72 | free(m); 73 | m = prv; 74 | if (prv) { 75 | prv->nxt = nxt; 76 | } else { 77 | C.msg_queue = nxt; 78 | } 79 | } 80 | prv = m; 81 | m = nxt; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /luasrc/legacy_levels/demux.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | local Demux = Tester:extend() 4 | 5 | local function pow2(n) 6 | if n == 0 then return 1; 7 | elseif n == 1 then return 2; 8 | elseif n == 2 then return 4; 9 | elseif n == 3 then return 8; 10 | end 11 | end 12 | 13 | function Demux:new(bits_in, bits_sel) 14 | Demux.super.new(self) 15 | self.has_submit = false 16 | self.pins = { 17 | {'output', bits_in, 'a'}, 18 | {'output', bits_sel, 's'}, 19 | } 20 | 21 | local num_out = pow2(bits_sel) 22 | local num_a= pow2(bits_in) 23 | 24 | for i=1,num_out do 25 | table.insert(self.pins, {'input', bits_in, 'd' .. i-1}) 26 | end 27 | 28 | self.schedule = {} 29 | for a=0,num_a-1 do 30 | for s=1,num_out do 31 | local case = {a=a, s=s-1} 32 | for ss=1, num_out do 33 | case['d' .. ss-1] = 0 34 | end 35 | case['d' .. s-1] = a 36 | table.insert(self.schedule, case) 37 | end 38 | end 39 | end 40 | 41 | addLevel({ 42 | icon="../luasrc/imgs/levels/demux_icon.png", 43 | name="DEMUX", 44 | desc=[[ 45 | 46 | !img:imgs/levels/demux_icon.png 47 | 48 | Objective: Implement a demux: given a multi-bit input `a`, return it to the n-th output `d_n` if the value of `s` is `n`. 49 | 50 | For example: 51 | 52 | - `s=0` --> `d0`=`a`, `d1`=0 53 | - `s=1` --> `d0`=0, `d1`=`a` 54 | 55 | !hl 56 | 57 | !img:imgs/tutorial/demux1.png 58 | 59 | A `DEMUX` is like a bit `router`: Given an input bit `a`, 2 outputs `Y0` and `Y1` and a selector bit `s`, if `s` is 0, then set `Y0` to the value of `a` and `Y1` to 0. Otherwise, if `s` is 1, set `Y0` to 0 and `Y1` to the value of `a`. 60 | !img:imgs/tutorial/demux3.png 61 | Demuxes work like routers: you have a wire and the selector bit chooses to which wire the input bit should go. 62 | Example: 63 | a=0, s=0 --> Y0 = 0, Y1 = 0 64 | a=1, s=0 --> Y0 = 1, Y1 = 0 65 | a=0, s=1 --> Y0 = 0, Y1 = 0 66 | a=1, s=1 --> Y0 = 0, Y1 = 1 67 | A demux can be implemented as follows: 68 | !img:imgs/tutorial/demux2.png 69 | 70 | ]], 71 | chips = { 72 | Clock(true), 73 | Demux(1, 1), 74 | }, 75 | id='DEMUX', 76 | }) 77 | 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](assets/logo2.png) 2 | 3 | Circuit Artist is a digital circuit drawing and simulation game. Circuits are images. You can play in sandbox mode or solve puzzles. You can use lua to interact with your circuit. 4 | 5 | - [web demo on itch.io](https://lets-all-be-stupid-foreva.itch.io/circuit-artist-demo) (it's a bit laggy) 6 | - [discord](https://discord.gg/McpSTEW5jU) 7 | - [steam](https://store.steampowered.com/app/3139580/Circuit_Artist/) 8 | 9 | Made with [raylib](https://www.raylib.com/). 10 | 11 | ## Game Rules 12 | 13 | - Little pixel triangles are NANDs. 14 | - Black pixels are background. 15 | - Everything else is a wire. 16 | 17 | ## Screenshots 18 | 19 | ![screenshot1](assets/screenshot1.png) 20 | 21 | ![screenshot2](assets/screenshot2.png) 22 | 23 | ![screenshot3](assets/screenshot3.png) 24 | 25 | ![screenshot4](assets/screenshot4.png) 26 | 27 | ## Building 28 | 29 | Compiles in Windows, Linux and Mac. 30 | 31 | - Need to build within the `build/` directory (or something within the root directory so it can access `luasrc/` and `asset/` folder) 32 | - You need to compile `LuaJIT/`. You can see the instructions [here](https://luajit.org/install.html). The binaries will go to the `LuaJIT/src` folder. In MAC it seems you need to install it too. In linux I've managed to make it work with a symbolic link from `build/`. In Windows it worked directly with the cmake directives ( if it doesnt for you mind that it might be something related to link path). 33 | - In linux it might complain that it can't find `libraylib.so`, that's because linux won't look for libraries in the directory it is in, you can fix with it `LD_LIBRARY_PATH=$PWD ./ca` until we find a cleaner solution. 34 | - By default OPENMP is off, you might want to activate in the `OPENMP` cmake option. 35 | 36 | The Linux and MAC versions are not stable yet: they compile but have some divergences from windows version. 37 | 38 | ### Clone, compile, run on Linux 39 | ``` 40 | git clone https://github.com/lets-all-be-stupid-forever/circuit-artist.git 41 | cd circuit-artist/ 42 | git submodule init 43 | git submodule update 44 | mkdir build 45 | cd build/ 46 | cmake .. 47 | make -C ../LuaJIT/ 48 | make 49 | ln -s ../LuaJIT/src/libluajit.so libluajit-5.1.so.2 50 | LD_LIBRARY_PATH=$PWD ./ca 51 | ``` 52 | 53 | 54 | ## License 55 | 56 | GPLv3. See LICENSE file. For dependencies, see `third_party` folder. 57 | -------------------------------------------------------------------------------- /luasrc/levels/bus.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local math = require 'math' 3 | local Clock = require 'clock' 4 | local BroadcastBus = Tester:extend() 5 | 6 | function BroadcastBus:new() 7 | BroadcastBus.super.new(self) 8 | self.has_submit = false 9 | self.pins = { 10 | {'output', 3, 'origin'}, 11 | {'output', 8, 'C0_in'}, 12 | {'input', 8, 'C0_out'}, 13 | {'output', 8, 'C1_in'}, 14 | {'input', 8, 'C1_out'}, 15 | {'output', 8, 'C2_in'}, 16 | {'input', 8, 'C2_out'}, 17 | {'output', 8, 'C3_in'}, 18 | {'input', 8, 'C3_out'}, 19 | {'output', 8, 'C4_in'}, 20 | {'input', 8, 'C4_out'}, 21 | {'output', 8, 'C5_in'}, 22 | {'input', 8, 'C5_out'}, 23 | {'output', 8, 'C6_in'}, 24 | {'input', 8, 'C6_out'}, 25 | {'output', 8, 'C7_in'}, 26 | {'input', 8, 'C7_out'}, 27 | } 28 | local schedule = {} 29 | 30 | for i=1,32 do 31 | local inputs = {} 32 | for j = 1,8 do 33 | inputs[j] = math.random(256) - 1 34 | end 35 | local src = math.random(8) - 1 36 | local val = inputs[src+1] 37 | local s = {origin=src} 38 | for j = 1, 8 do 39 | s['C' .. j-1 .. '_out'] = val 40 | s['C' .. j-1 .. '_in'] = inputs[j] 41 | end 42 | table.insert(schedule, s) 43 | end 44 | self.schedule = schedule 45 | end 46 | 47 | addLevel({ 48 | icon="../luasrc/imgs/levels/bus_icon.png", 49 | name="Broadcast Bus", 50 | desc=[[ 51 | 52 | !img:imgs/levels/bus2.png 53 | 54 | You have 8 different components, each sending (`CX_in`) and receiving (`CX_out`) a 8-bit message at the same time. 55 | 56 | The objective is to send the message from one of those components to all the others. The source component is defined by the `origin` input number ranging from 0 to 7. 57 | 58 | `Example:` 59 | 60 | For the inputs: 61 | 62 | origin=3 63 | C0_in=00 64 | C1_in=11 65 | C2_in=22 66 | C3_in=33 67 | C4_in=44 68 | C5_in=55 69 | C6_in=66 70 | C7_in=77 71 | 72 | You should have as outputs: 73 | 74 | C0_out=33 75 | C1_out=33 76 | C2_out=33 77 | C3_out=33 78 | C4_out=33 79 | C5_out=33 80 | C6_out=33 81 | C7_out=33 82 | 83 | During test, each component will send random messages and origins. Your task is to redirect them as described above. 84 | 85 | There will be a total of 32 tests. 86 | 87 | ]], 88 | chips = { 89 | Clock(true), 90 | BroadcastBus(4), 91 | }, 92 | id='BUS', 93 | }) 94 | -------------------------------------------------------------------------------- /src/clip_api.cpp: -------------------------------------------------------------------------------- 1 | #include "clip_api.h" 2 | 3 | #include 4 | 5 | #include "img.h" 6 | 7 | Image ImageFromClipboard() { 8 | clip::image img; 9 | if (!clip::get_image(img)) { 10 | // std::cout << "Error getting image from clipboard\n"; 11 | Image r = {0, 0, 0, 0, 0}; 12 | return r; 13 | } 14 | clip::image_spec spec = img.spec(); 15 | int w = spec.width; 16 | int h = spec.height; 17 | Image ret = GenImageSimple(w, h); 18 | Color* colors = GetPixels(ret); 19 | int byte_stride = spec.bits_per_pixel >> 3; 20 | if (spec.alpha_mask != 0) { 21 | for (unsigned long y = 0; y < spec.height; ++y) { 22 | char* src = (img.data() + y * spec.bytes_per_row); 23 | for (unsigned long x = 0; x < spec.width; ++x) { 24 | const uint32_t c = *(uint32_t*)(src + byte_stride * x); 25 | int r = ((c & spec.red_mask) >> spec.red_shift); 26 | int g = ((c & spec.green_mask) >> spec.green_shift); 27 | int b = ((c & spec.blue_mask) >> spec.blue_shift); 28 | int a = ((c & spec.alpha_mask) >> spec.alpha_shift); 29 | colors[y * w + x].r = r; 30 | colors[y * w + x].g = g; 31 | colors[y * w + x].b = b; 32 | colors[y * w + x].a = a; 33 | } 34 | } 35 | } else { 36 | for (unsigned long y = 0; y < spec.height; ++y) { 37 | char* src = (img.data() + y * spec.bytes_per_row); 38 | for (unsigned long x = 0; x < spec.width; ++x) { 39 | const uint32_t c = *(uint32_t*)(src + byte_stride * x); 40 | uint32_t r = ((c & spec.red_mask) >> spec.red_shift); 41 | uint32_t g = ((c & spec.green_mask) >> spec.green_shift); 42 | uint32_t b = ((c & spec.blue_mask) >> spec.blue_shift); 43 | colors[y * w + x].r = r; 44 | colors[y * w + x].g = g; 45 | colors[y * w + x].b = b; 46 | colors[y * w + x].a = 255; 47 | } 48 | } 49 | } 50 | return ret; 51 | } 52 | 53 | void ImageToClipboard(Image img) { 54 | clip::image_spec spec; 55 | spec.width = img.width; 56 | spec.height = img.height; 57 | spec.bytes_per_row = 4 * img.width; 58 | spec.bits_per_pixel = 32; 59 | spec.red_mask = 0xff; 60 | spec.green_mask = 0xff00; 61 | spec.blue_mask = 0x00ff0000; 62 | spec.alpha_mask = 0xff000000; 63 | spec.red_shift = 0; 64 | spec.green_shift = 8; 65 | spec.blue_shift = 16; 66 | spec.alpha_shift = 24; 67 | clip::image clip_img((void*)GetPixels(img), spec); 68 | clip::set_image(clip_img); 69 | } 70 | -------------------------------------------------------------------------------- /src/shaders.h: -------------------------------------------------------------------------------- 1 | #ifndef SHADERS_H 2 | #define SHADERS_H 3 | #include "raylib.h" 4 | 5 | #define set_shader_vec2(name, loc, val) \ 6 | SetShaderValue(GetShaders()->name##_shader, GetShaders()->name##_loc_##loc, \ 7 | (val), SHADER_UNIFORM_VEC2); 8 | 9 | #define set_shader_vec4(name, loc, val) \ 10 | SetShaderValue(GetShaders()->name##_shader, GetShaders()->name##_loc_##loc, \ 11 | (val), SHADER_UNIFORM_VEC4); 12 | 13 | #define set_shader_ivec2(name, loc, val) \ 14 | SetShaderValue(GetShaders()->name##_shader, GetShaders()->name##_loc_##loc, \ 15 | (val), SHADER_UNIFORM_IVEC2); 16 | 17 | #define set_shader_tex(name, loc, val) \ 18 | SetShaderValueTexture(GetShaders()->name##_shader, \ 19 | GetShaders()->name##_loc_##loc, (val)); 20 | 21 | #define set_shader_int(name, loc, val) \ 22 | SetShaderValue(GetShaders()->name##_shader, GetShaders()->name##_loc_##loc, \ 23 | (val), SHADER_UNIFORM_INT); 24 | 25 | #define begin_shader(name) BeginShaderMode(GetShaders()->name##_shader) 26 | #define end_shader() EndShaderMode() 27 | 28 | typedef struct { 29 | Shader comb_shader; 30 | int comb_loc_dst_tex; 31 | int comb_loc_off; 32 | int comb_loc_src_size; 33 | int comb_loc_dst_size; 34 | int comb_loc_roi_size; 35 | int comb_loc_src_off; 36 | int comb_loc_dst_off; 37 | 38 | Shader selrect_shader; 39 | int selrect_loc_rsize; 40 | int selrect_loc_rect_pos; 41 | int selrect_loc_pattern_shift; 42 | int selrect_loc_pattern_width; 43 | 44 | Shader fill_shader; 45 | 46 | Shader rotate_shader; 47 | int rotate_loc_ccw; 48 | 49 | Shader project_shader; 50 | int project_loc_sp; 51 | int project_loc_img_size; 52 | int project_loc_tpl; 53 | 54 | Shader update_shader; 55 | int update_loc_img_size; 56 | int update_loc_sel_size; 57 | int update_loc_sel_off; 58 | int update_loc_sel; 59 | int update_loc_tool_size; 60 | int update_loc_tool_off; 61 | int update_loc_tool; 62 | int update_loc_mode; 63 | int update_loc_simu_state; 64 | int update_loc_bg_color; 65 | int update_loc_bugged_color; 66 | int update_loc_undefined_color; 67 | int update_loc_comp_x; 68 | int update_loc_comp_y; 69 | int update_loc_state_buf; 70 | 71 | } Shaders; 72 | 73 | void InitShaders(); 74 | Shaders* GetShaders(); 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /luasrc/legacy_levels/shifter.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | local Shifter = Tester:extend() 4 | local math = require 'math' 5 | 6 | local bit = require 'bit' 7 | local band = bit.band 8 | local lshift = bit.lshift 9 | 10 | function Shifter:new() 11 | Shifter.super.new(self) 12 | self.has_submit = false 13 | self.pins = { 14 | {'output', 4, 'a'}, 15 | {'output', 2, 'n'}, 16 | {'input', 4, 'a_lshift_n'}, 17 | } 18 | self.schedule = { } 19 | math.randomseed(1) 20 | for a=0,15 do 21 | for n=0,3 do 22 | local r = band(lshift(a, n), 15) 23 | local c = {a=a, n=n, a_lshift_n=r, name=a .. ' << ' .. n .. ' = ' .. r} 24 | table.insert(self.schedule, c) 25 | end 26 | end 27 | end 28 | 29 | addLevel({ 30 | icon="../luasrc/imgs/levels/shifter_icon.png", 31 | name="Bit Shifter", 32 | desc=[[ 33 | 34 | Objective: Shift the bits of a 4-bit input (`a`) to the left by a given ammount (`n`). The operation is represented by `a << n`. 35 | 36 | Example: 37 | - 0 << 0 = 0 (no shift) 38 | - 1 << 0 = 1 (no shift) 39 | - 2 << 1 = 4, because 2 = `0010`, and shifting 1 place to the left we get `010`0. 40 | - 8 << 1 = 0, because 8 = `1000`, and shifting 1 place to the left we get `000`0. 41 | - 3 << 2 = 12, because 3 = `0011`, and shifting 2 places to the left we get `11`00. 42 | 43 | !hl 44 | 45 | In this section we will see how to `shift` bits in a number. 46 | 47 | The `shift` operation is simply moving the bits either to the left or to the right, as in the example below: 48 | 49 | !img:imgs/tutorial/shift1.png 50 | 51 | The shift operation is often represented by `A << b` when `A` is shifted by `b` bits to the left and `A >> b` when it is shifted to the right. 52 | 53 | You can solve it by using a grid-like structure called `barrel shifter`. In order to understand how it works, let's look at a 4-bits right shifter `C = A >> B`. We would want the circuit for each shift value `B` to look more or less like the following: 54 | 55 | !img:imgs/tutorial/shift3.png 56 | 57 | This can be achieved by creating a cell, then reproduce it in a grid-like structure like the picture below, then you create activation bit flags for each possible shift and link them to the associate cells. The function of each cell is to create the "L"-like curves as in the circuit above. 58 | 59 | !img:imgs/tutorial/shift2.png 60 | 61 | We can build each cell the following way: 62 | 63 | !img:imgs/tutorial/shift4.png 64 | 65 | 66 | ]], 67 | chips = { 68 | Clock(true), 69 | Shifter() 70 | }, 71 | id='SHIFTER', 72 | }) 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /luasrc/levels/collatz.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | local Collatz = Tester:extend() 4 | 5 | function Collatz:new() 6 | Collatz.super.new(self) 7 | self.pins = { 8 | {'input', 1, 'submit'}, 9 | {'output', 8, 'n'}, 10 | {'input', 8, 'steps'}, 11 | } 12 | self.has_submit = true 13 | self.schedule = { 14 | {n=1,steps=0 }, 15 | {n=2,steps=1 }, 16 | {n=4,steps=2 }, 17 | {n=5,steps=5 }, 18 | {n=3,steps=7 }, 19 | {n=6,steps=8 }, 20 | {n=7,steps=16 }, 21 | {n=8,steps=3 }, 22 | {n=9,steps=19 }, 23 | {n=10 ,steps=6 }, 24 | {n=11 ,steps=14 }, 25 | {n=12 ,steps=9 }, 26 | {n=13 ,steps=9 }, 27 | {n=14 ,steps=17 }, 28 | {n=15 ,steps=17 }, 29 | {n=16 ,steps=4 }, 30 | {n=27 ,steps=111 }, 31 | {n=41 ,steps=109 }, 32 | {n=100 ,steps=25 }, 33 | {n=123 ,steps=46 }, 34 | -- {n=333 ,steps=112 }, These ones need more bits 35 | -- {n=512 ,steps=9 }, 36 | -- {n=999 ,steps=49 }, 37 | -- {n=703 ,steps=170 }, 38 | } 39 | 40 | for i=1,#self.schedule do 41 | local s = self.schedule[i] 42 | s.name = 'n=' .. s.n .. ' (steps should be ' .. s.steps .. ')' 43 | end 44 | 45 | end 46 | 47 | addLevel({ 48 | icon = "../luasrc/imgs/levels/collatz_icon.png", 49 | name = "3x+1 Sequence", 50 | desc=[[ 51 | 52 | The 3x+1 sequence is defined by: 53 | * x = x/2 if x is even 54 | * x = 3x+1 if x is odd 55 | 56 | Find the number of steps needed for the sequence starting with `n` to reach 1. 57 | 58 | !img:imgs/levels/collatz_img1.png 59 | 60 | You can use `multiple clock cycles` to compute the result. The result will only be verified when the `submit` flag is on. The check is done on the rising edge of the clock. After verification is passed, the next test case will be provided. 61 | 62 | `Examples:` 63 | n=1 --> steps=0 64 | n=2 --> steps=1 (2 -> 1) 65 | n=3 --> steps=7 (3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1) 66 | n=4 --> steps=2 (4 -> 2 -> 1) 67 | 68 | Inputs: 69 | - `n` (8bits): positive number corresponding to the initial value of the sequence. 70 | 71 | Outputs: 72 | - `submit` (1bit): flag saying when the computation is done and result is ready 73 | - `steps` (8bits): Result of calculation, containing the number of steps for the sequence starting at `n` to reach the number 1. 74 | 75 | The first 16 tests will be the positive numbers from `n`=1 to `n`=16. The 8 next numbers will be other positive numbers between 16 and 128. The sequence size is guaranteed to be shorter than 255. 76 | 77 | ]], 78 | chips = { 79 | Clock(), 80 | Collatz(), 81 | }, 82 | id='COLLATZ', 83 | }) 84 | -------------------------------------------------------------------------------- /assets/shaders/state_shader.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | // Input vertex attributes (from vertex shader) 4 | in vec2 fragTexCoord; 5 | in vec4 fragColor; 6 | 7 | // Input uniform values 8 | uniform sampler2D texture0; 9 | uniform sampler2D comp_x; 10 | uniform sampler2D comp_y; 11 | uniform sampler2D state_buf; 12 | 13 | // 0 = Edit 14 | // 1 = Simu 15 | uniform int mode; 16 | 17 | // Simulation State 18 | uniform int simu_state; 19 | uniform vec4 bg_color; 20 | uniform vec4 bugged_color; 21 | uniform vec4 undefined_color; 22 | 23 | // Edition State 24 | uniform int simu_state; 25 | uniform vec4 bg_color; 26 | uniform vec4 bugged_color; 27 | uniform vec4 undefined_color; 28 | uniform vec2 size_factor; 29 | 30 | uniform vec4 colDiffuse; 31 | 32 | // Output fragment color 33 | out vec4 finalColor; 34 | 35 | // NOTE: Add here your custom variables 36 | 37 | void main() 38 | { 39 | // Texel color fetching from texture sampler 40 | // vec4 texelColor = texture(texture0, fragTexCoord); 41 | vec4 src = texture(texture0, fragTexCoord); 42 | vec4 tx = texture(comp_x, fragTexCoord); 43 | vec4 ty = texture(comp_y, fragTexCoord); 44 | 45 | vec4 texelColor; 46 | texelColor = src; 47 | // Normal wire. 48 | //if (ty.r < 0.9) { 49 | vec4 ts = texture(state_buf, vec2(tx.r, ty.r)); 50 | texelColor = src; 51 | float s = ts.r; 52 | if (s <= 0.05) { 53 | texelColor = bg_color; 54 | } else if (s < 0.15) { 55 | // texelColor = bugged_color; 56 | texelColor = vec4(1.0, 0.0, 0.0, 1.0); 57 | } else if (s < 0.25) { 58 | // undefined = -1 59 | texelColor = undefined_color; 60 | if (simu_state == 1) { 61 | texelColor.rgb = 0.1 * texelColor.rgb; 62 | } 63 | } else if (s < 0.35) { 64 | // texelColor = ( 50.0 / 255.0) * texelColor; 65 | texelColor.rgb = ( 150.0 / 255.0) * texelColor.rgb; 66 | } else if (s < 0.45) { 67 | texelColor.a = 1.0; 68 | } 69 | //} 70 | 71 | // else { 72 | // // Here we have a nand pixel 73 | // if (tx.r < 0.915) { 74 | // // 0.91 75 | // texelColor = src; 76 | // texelColor.rgb = (100.0 / 255.0) * texelColor.rgb; 77 | // } else if (tx.r < 0.925) { 78 | // // 0.92 79 | // texelColor = undefined_color; 80 | // if (simu_state == 1) { 81 | // texelColor.rgb = 0.2 * texelColor.rgb; 82 | // } 83 | // } else if (tx.r < 0.935) { 84 | // // 0.93 85 | // texelColor = bugged_color; 86 | // } 87 | // } 88 | 89 | finalColor = texelColor*colDiffuse; 90 | } 91 | 92 | -------------------------------------------------------------------------------- /luasrc/c_api.lua: -------------------------------------------------------------------------------- 1 | require 'raylib_api' 2 | local ffi = require("ffi") 3 | ffi.cdef[[ 4 | 5 | typedef struct { 6 | Texture2D tex; 7 | Rectangle region; 8 | } Sprite; 9 | 10 | 11 | typedef struct { 12 | const char* help_name[50]; 13 | const char* help_txt[50]; 14 | Sprite help_sprites[50][20]; 15 | struct { 16 | int ilevel; 17 | const char* name; 18 | const char* desc; 19 | Sprite icon; 20 | Sprite sprites[20]; 21 | bool complete; 22 | } options[60]; 23 | char* startup_image_path; 24 | } LevelOptions; 25 | 26 | 27 | typedef struct { 28 | int ni; 29 | int no; 30 | bool dirty; 31 | int wires_in_x[128]; 32 | int wires_in_y[128]; 33 | int wires_out_x[128]; 34 | int wires_out_y[128]; 35 | } ExtComp; 36 | 37 | typedef enum { 38 | CONN_INPUT, 39 | CONN_OUTPUT, 40 | } ConnType; 41 | 42 | typedef struct { 43 | int num_conn; 44 | struct { 45 | ConnType type; 46 | int len; 47 | const char* name; 48 | } conn[50]; 49 | } PinDesc; 50 | 51 | typedef struct { 52 | int ilevel; 53 | int num_components; 54 | PinDesc* pindesc; 55 | ExtComp* extcomps; 56 | } LevelDesc; 57 | 58 | typedef struct { 59 | int ic; 60 | int* prev_in; 61 | int* next_in; 62 | int* output; 63 | bool reset; 64 | } ComponentUpdateCtx; 65 | 66 | typedef struct { 67 | float elapsed; 68 | float clock_time; 69 | float dt; 70 | float cx; 71 | float cy; 72 | float cs; 73 | RenderTexture2D rt; 74 | int requested_level; 75 | int clock_update_value; 76 | int clock_count; 77 | bool por; 78 | LevelDesc level_desc; 79 | LevelOptions level_options; 80 | ComponentUpdateCtx update_ctx; 81 | } SharedState; 82 | 83 | SharedState* GetSharedState(); 84 | 85 | int printf(const char *fmt, ...); 86 | void free( void * pointer ); 87 | void * malloc( size_t memorySize ); 88 | void CaDrawText(const char* txt, int x, int y, Color c); 89 | int CaGetDrawTextSize(const char* txt); 90 | void CaDrawTextBox(const char* txt, int x, int y, int w, Color c); 91 | void CaSetPalFromImage(Image img); 92 | void CaAddMessage(const char* txt, float duration); 93 | void CaSetStartupImage(const char* path); 94 | 95 | typedef struct RlDrawTextureProArgs{ 96 | Texture2D texture; 97 | Rectangle source; 98 | Rectangle dest; 99 | Vector2 origin; 100 | float rotation; 101 | Color tint; 102 | } RlDrawTextureProArgs; 103 | 104 | void RlDrawTexturePro(RlDrawTextureProArgs* args); 105 | 106 | // Steam stuff 107 | bool SteamEnabled(); 108 | bool SteamGetAchievement(const char* ach_name); 109 | void SteamSetAchievement(const char* ach_name); 110 | void SteamRefreshAchievement(); 111 | void SteamClearAchievement(const char* ach_name); 112 | ]] 113 | 114 | return ffi.C 115 | -------------------------------------------------------------------------------- /luasrc/tutorial/synchronous.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name='Synchronous Circuits', 3 | text=[[ 4 | 5 | In this section, you'll see how to use memory to create `synchronous sequential` circuits. 6 | 7 | `Sequential` as in, calculations and memory updates are always done in sequence: first you updated the memory, then you perform calculations, then you update the memory again, and so on. 8 | !img:imgs/tutorial/sync4.png 9 | `Synchronous` as in, every bit storage you have is updated once and at the `same time` for each `enable` cycle. We use a special bit to perform this sycnrhonization, which we call `CLOCK` bit. Whenever it goes up, the memories are updated (with the current input `D` value, which come from calculations from previous cycle) and it is only updated again when it goes to 0 and back to 1. This form what we call a `clock cycle`. We can see that the actual "sequential" property of our system is tightly linked with the cycles of the clock: each cycle defines one "update->compute" step in our sequence . 10 | !img:imgs/tutorial/sync2.png 11 | You can structure synchronous circuits in 2 major subcircuits: (i) the `combinatorial` subcircuit and (ii) the `memory` subcircuit: 12 | !img:imgs/tutorial/sync1.png 13 | The role of the `memory` subcircuit is to store and update the memory of the system. It's very similar to a big D flip flop but instead of a single bit, it stores multiple bits `S`, and the output is just a stored version of the input `S`. 14 | 15 | The role of the `combinatorial` subcircuit is to take the previous state `S_prv` as input, as well as some external input bits `Din` and generate both the bits corresponding to the next desired state `S_nxt` and the outputs of the system `Dout`. In this subcircuit there's no memory or "loops", just straight logic computation. 16 | 17 | This way, once you're designing a sequential circuit, you can separate the logic from the storage and think in terms of: (i) what should the memory look like? (ii) how can I use the current memory (and inputs) to generate the next state of the memory (and outputs)? 18 | 19 | `EXAMPLE`: Let's design a circuit with 1 bit input `A` and that outputs the bit `Y` that appeared 2 clock cycles ago. The circuit should look like: 20 | 21 | 1. CC=0 A=A0 --> Y=n/a 22 | 2. CC=1 A=A1 --> Y=n/a 23 | 3. CC=2 A=A2 --> Y=A0 24 | 4. CC=3 A=A3 --> Y=A1 25 | 26 | We would need 2 bits for the memory: one `S0` for storing the current bit and one `S1` for the bit one clock ago. At the current clock cycle, you want to output the bit that appeared 1-clock "ago" from the last clock cycle, ie, the `S1` that you have as input. The updates then would look like the following: 27 | 28 | - `S0_nxt = Din` 29 | - `S1_nxt = S0_prv` 30 | - `Dout = S1_prv` 31 | 32 | And the circuit would look something like that: 33 | !img:imgs/tutorial/sync3.png 34 | 35 | ]] 36 | } 37 | -------------------------------------------------------------------------------- /src/profiler.c: -------------------------------------------------------------------------------- 1 | #include "raylib.h" 2 | #include "stb_ds.h" 3 | #include "stdio.h" 4 | #include "stdlib.h" 5 | 6 | static struct { 7 | bool inited; 8 | double* stack_elapsed; 9 | const char** stack_cname; 10 | 11 | double* elapsed; 12 | const char** cname; 13 | int stack_size; 14 | 15 | struct { 16 | const char* key; 17 | double value; 18 | }* ravg; 19 | 20 | struct { 21 | const char* key; 22 | double value; 23 | }* tsingle; 24 | 25 | int first; 26 | int last; 27 | } C = {0}; 28 | 29 | void ProfilerInit() { 30 | if (C.inited) { 31 | return; 32 | } 33 | C.inited = true; 34 | C.stack_elapsed = malloc(50 * sizeof(double)); 35 | C.stack_cname = malloc(50 * sizeof(const char*)); 36 | C.stack_size = 0; 37 | } 38 | 39 | void ProfilerReset() { 40 | if (C.stack_size != 0) { 41 | abort(); 42 | } 43 | int n = arrlen(C.elapsed); 44 | if (n > 0) { 45 | arrfree(C.elapsed); 46 | arrfree(C.cname); 47 | } 48 | } 49 | 50 | void ProfilerDraw() { 51 | int n = arrlen(C.elapsed); 52 | int yy = 200; 53 | for (int i = 0; i < shlen(C.tsingle); i++) { 54 | char txt[200]; 55 | double t = 1000 * C.tsingle[i].value; 56 | sprintf(txt, "%20s: %.1lfms", C.tsingle[i].key, t); 57 | DrawText(txt, 50, yy + 1, 20, BLACK); 58 | DrawText(txt, 50, yy, 20, LIME); 59 | yy += 30; 60 | } 61 | 62 | for (int i = 0; i < n; i++) { 63 | char txt[200]; 64 | double t = 1000 * C.elapsed[i]; 65 | int k = shgeti(C.ravg, C.cname[i]); 66 | if (k == -1) { 67 | shput(C.ravg, C.cname[i], t); 68 | } 69 | double f = 0.9; 70 | t = f * shget(C.ravg, C.cname[i]) + (1 - f) * t; 71 | shput(C.ravg, C.cname[i], t); 72 | sprintf(txt, "%20s: %.1lfms", C.cname[i], t); 73 | DrawText(txt, 50, yy + 1, 20, BLACK); 74 | DrawText(txt, 50, yy, 20, LIME); 75 | yy += 30; 76 | } 77 | } 78 | 79 | void ProfilerTic(const char* name) { 80 | ProfilerInit(); 81 | C.stack_elapsed[C.stack_size] = GetTime(); 82 | C.stack_cname[C.stack_size] = name; 83 | C.stack_size++; 84 | } 85 | 86 | void ProfilerTac() { 87 | double now = GetTime(); 88 | double elapsed = now - C.stack_elapsed[C.stack_size - 1]; 89 | const char* name = C.stack_cname[C.stack_size - 1]; 90 | arrput(C.elapsed, elapsed); 91 | arrput(C.cname, name); 92 | C.stack_size--; 93 | }; 94 | 95 | void ProfilerTicSingle(const char* name) { 96 | if (shgeti(C.tsingle, name) != -1) { 97 | shdel(C.tsingle, name); 98 | }; 99 | shput(C.tsingle, name, GetTime()); 100 | } 101 | 102 | void ProfilerTacSingle(const char* name) { 103 | double now = GetTime(); 104 | double start = shget(C.tsingle, name); 105 | shput(C.tsingle, name, now - start); 106 | }; 107 | 108 | -------------------------------------------------------------------------------- /luasrc/legacy_levels/subtractor.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | local Subtractor = Tester:extend() 4 | local math = require 'math' 5 | 6 | local function fmt(n) 7 | if n >= 0 then return tostring(n) end 8 | return '(' .. n .. ')' 9 | end 10 | 11 | function Subtractor:new() 12 | Subtractor.super.new(self) 13 | self.has_submit = false 14 | self.pins = { 15 | {'output', 8, 'a'}, 16 | {'output', 8, 'b'}, 17 | {'input', 8, 'a_minus_b'}, 18 | } 19 | self.schedule = { 20 | {a=0, b=0, a_minus_b=0, name='0 - 0'}, 21 | {a=1, b=0, a_minus_b=1, name='1 - 0'}, 22 | {a=0, b=1, a_minus_b=-1, name='0 - 1'}, 23 | {a=1, b=1, a_minus_b=0, name='1 - 1'}, 24 | {a=-1, b=-1, a_minus_b=0, name='(-1) - (-1)'}, 25 | {a=127, b=-128, a_minus_b=255, name='127 - (-128)'}, 26 | {a=-128, b=127, a_minus_b=1, name='(-128) - 127'}, 27 | } 28 | math.randomseed(1) 29 | for i=1,64 do 30 | local a = math.random(256) - 128 31 | local b = math.random(256) - 128 32 | local s = a-b 33 | if s < -128 then s = 256 + s end 34 | if s > 127 then s = s - 256 end 35 | name = fmt(a) .. ' - ' .. fmt(b) 36 | table.insert(self.schedule, {a=a, b=b, a_minus_b=s, name=name}) 37 | end 38 | end 39 | 40 | addLevel({ 41 | icon="../luasrc/imgs/levels/subtractor_icon.png", 42 | name="A - B", 43 | desc=[[ 44 | 45 | Objective: Subtract two `signed` 8-bit numbers. 46 | 47 | We want `a_minus_b` = `a` - `b`. 48 | 49 | !hl 50 | 51 | In order to represent `signed integers`, we create a sort of extension of the unsigned integer definition: 52 | !img:imgs/tutorial/numbers2.png 53 | This representation is also called `two's complement`. 54 | 55 | The formula to convert a number to its negative is given as: `-N = NOT(N) + 1`. It works for both positive and negative numbers. 56 | 57 | So let's say you want to find -3 in a 4-bit representation. You know that 3 is represented in positive integers by 0011. So, in order to find -3 first you find NOT(3) by inverting each bit, getting 1100. Then, you add 1 to it, getting 1100 + 1 = 1101, so the representation of -3 is 1101. You can also do the other way around, to get -(-3): First you invert 1101 to get 0010, then you add 1, to get 0010 + 1 = 0011, which is our starting representation of 3. 58 | 59 | !hl 60 | 61 | The subtraction of two integer numbers is very similar to its addition. All you need to do is invert the sign of the second number and sum them, as in `A-B = A + (-B)`, but since -N = NOT(N) + 1, all you need to do is A-B = A + NOT(B) + 1. 62 | 63 | This can be done by using the "carry in" of the first `FULL ADDER` in the n-bits adder. 64 | 65 | !img:imgs/tutorial/sub2.png 66 | 67 | ]], 68 | chips = { 69 | Clock(true), 70 | Subtractor(), 71 | }, 72 | id='SUBTRACTOR', 73 | }) 74 | -------------------------------------------------------------------------------- /luasrc/levels/simple_ram.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | local math = require 'math' 4 | local utils = require 'utils' 5 | 6 | local SimpleRam = Tester:extend() 7 | 8 | local bit = require 'bit' 9 | local bnot = bit.bnot 10 | local band, bor, bxor = bit.band, bit.bor, bit.bxor 11 | local lshift, rshift, rol = bit.lshift, bit.rshift, bit.rol 12 | 13 | 14 | function SimpleRam:new() 15 | self.super.new(self) 16 | self.pins = { 17 | {'output', 1, 'we'}, 18 | {'output', 6, 'wa'}, 19 | {'output', 8, 'wd'}, 20 | {'input', 8, 'rd'}, 21 | } 22 | 23 | local cases = { 24 | {we=0, wa=0, wd=0x00000000, name='Initializing'}, 25 | } 26 | local db = {} 27 | math.randomseed(0) 28 | 29 | -- first set all registers with a random number 30 | local N = 64 31 | for i = 1, N do 32 | local r = i 33 | db[i] = r 34 | table.insert(cases, {we=1, wa=i-1, wd=r, msg=msg, name='MEM['.. i- 1 .. ']=' .. r}) 35 | end 36 | 37 | -- Now queries each register 38 | for j = 1, 4*N do 39 | local wd = utils.randomBits(8) 40 | local wa = math.random(1, N) 41 | local we = math.random(1, 2) - 1 42 | -- we = 0 43 | if we == 1 then 44 | local msg = 'MEM[' .. wa-1 .. ']=' .. wd 45 | table.insert(cases, {we=1, wa=wa-1, wd=wd, name=msg}) 46 | db[wa] = wd 47 | else 48 | local msg = 'READ MEM[' .. wa-1 .. '] (should be ' .. db[wa] .. ')' 49 | table.insert(cases, {we=0, wa=wa-1, wd=0, rd=db[wa], name=msg}) 50 | end 51 | end 52 | self.schedule = cases 53 | end 54 | 55 | addLevel({ 56 | icon = "../luasrc/imgs/levels/simple_ram_icon.png", 57 | name = "Memory: 8bit SRAM", 58 | desc=[[ 59 | 60 | !img:imgs/levels/mem64.png 61 | 62 | 63 | Write a memory for bytes (8-bit) with 64 addresses. (ie, total storage of 64x8 = 512 bits). 64 | 65 | The input bit flag `we` defines if the operation should be a write (`we`=1) or a read (`we`=0). 66 | 67 | In `read` operations (`we`=0) , we just return the value of the byte at address `wa` in `rd`. 68 | 69 | In `write` operations (`we`=1) we assign the value `wd` to the address `wa`. 70 | 71 | In other words: 72 | 73 | - `we`=0 (read) --> `rd`=MEM[`wa`] 74 | - `we`=1 (write) --> MEM[`wa`]=`wd` 75 | 76 | Inputs: 77 | - `we` (1bit): write enabled 78 | - `wa` (8bits): data address 79 | - `wd` (8bits): write data 80 | 81 | Output: 82 | - `rd` (8bits): read data 83 | 84 | The initial state of the memory is not important. 85 | 86 | `!! Attention !!`: The memory change should take place during the rising edge of the clock, ie, exactly when the clock value is passing from 0 to 1. 87 | 88 | For the tests, we will fill each memory address with random values, and then on a second time we will try to access each address to see if the values match. 89 | 90 | Then, we will randomly assign and query again for a few times before end. 91 | 92 | ]], 93 | chips = { 94 | Clock(), 95 | SimpleRam(), 96 | }, 97 | id='RAM8', 98 | }) 99 | -------------------------------------------------------------------------------- /luasrc/levels/custom_components.lua: -------------------------------------------------------------------------------- 1 | local LevelComponent = require 'level_component' 2 | local Clock = require 'clock' 3 | 4 | local BasicKeyboard = require 'component_examples.basic_keyboard' 5 | local BasicROM = require 'component_examples.basic_rom' 6 | local BasicRAM = require 'component_examples.basic_ram' 7 | local BasicRAMDisplay = require 'component_examples.basic_ram_display' 8 | 9 | local chips = {} 10 | table.insert(chips, Clock(false)) 11 | table.insert(chips, BasicKeyboard()) 12 | table.insert(chips, BasicROM()) 13 | 14 | -- Here's an example on how to share data between components: 15 | -- Our "display" component directly access the memory from the RAM component, 16 | -- that doesnt even know the display exists. In this specific case it assumes 17 | -- the table "memory" is created in the constructor of the ram component, but 18 | -- a callback could also be used. 19 | -- Mind also that you can have components that dont directly interact with the 20 | -- image chip (the ramdisplay here has an "display_offset" pin input, but it 21 | -- could have no pin and it would still work) 22 | local ram = BasicRAM() 23 | local ramDisplay = BasicRAMDisplay(ram.memory, 16, 16) 24 | table.insert(chips, ram) 25 | table.insert(chips, ramDisplay) 26 | 27 | 28 | addLevel({ 29 | icon = "../luasrc/imgs/levels/custom_icon.png", 30 | name = 'Custom Components', 31 | desc = [[ 32 | 33 | !img:imgs/levels/custom_icon.png 34 | 35 | Advanced Sandbox mode. No objective. 36 | 37 | You have an example of custom (reusable) components such as: 38 | 39 | 1. A WASD Keyboard input (`key_w` wire will activate when you type the W keyboard key, same for `key_a`, ` key_s` and `key_d`) 40 | 41 | 2. A ROM component (`rom_ra` is read address, `rom_rd` is the read value) 42 | 43 | 3. A RAM component (`ram_wa` is the write address, `ram_wd` is the write data, `ram_we` is the write enable (we=1 means write, we=0 means read) and `ram_rd` is the read address) 44 | 45 | 4. A RAM Display component (will automatically display the `ram` content in the top-right of the screen. The content can be shifted by an offset, to simulate offscreen buffers). This is an example on how to communicate/interact between components. 46 | 47 | Check the `luasrc/levels/custom_components.lua` script for more details. 48 | 49 | You can create your own components and levels in the `luasrc/scripts/` folder of your game installation. (if the folder is not there you can create one from the template in `luasrc/template_scripts/`) 50 | 51 | You can look at the `luasrc/` folder for references/examples. 52 | 53 | The game will look for the `scripts/init.lua` script at initialization, so you can import/register your levels and scripts from there. 54 | 55 | You can open the game folder in steam via right click on the game name in "library", then go to "Manage" -> "Browse Local Files". 56 | 57 | `Attention:` Mind not modifying scripts outside the `luasrc/scripts/` folder because they are overidden in updates. 58 | ]], 59 | chips = chips, 60 | }) 61 | -------------------------------------------------------------------------------- /src/w_text.c: -------------------------------------------------------------------------------- 1 | #include "w_text.h" 2 | 3 | #include 4 | 5 | #include "colors.h" 6 | #include "font.h" 7 | #include "tiling.h" 8 | #include "ui.h" 9 | #include "w_main.h" 10 | 11 | #define MAX_TEXT_SIZE 1000 12 | 13 | static struct { 14 | int tlen; 15 | char txt[MAX_TEXT_SIZE]; 16 | float alive; 17 | } C = {0}; 18 | 19 | void TextModalOpen(Ui* ui) { 20 | ui->window = WINDOW_TEXT; 21 | C.alive = 0; 22 | C.tlen = 0; 23 | C.txt[0] = '\0'; 24 | } 25 | 26 | void TextModalUpdate(Ui* ui) { 27 | float dt = GetFrameTime(); 28 | C.alive += dt; 29 | int key = GetCharPressed(); 30 | int max_input_chars = MAX_TEXT_SIZE; 31 | while (key > 0) { 32 | if ((key >= 32) && (key <= 125) && (C.tlen < max_input_chars)) { 33 | C.txt[C.tlen] = (char)key; 34 | C.txt[++C.tlen] = '\0'; // Add null terminator at the end of the string. 35 | } 36 | key = GetCharPressed(); // Check next character in the queue 37 | C.alive = 0; 38 | } 39 | 40 | if (IsKeyPressed(KEY_BACKSPACE)) { 41 | C.tlen--; 42 | if (C.tlen < 0) C.tlen = 0; 43 | C.txt[C.tlen] = '\0'; 44 | C.alive = 0; 45 | } 46 | 47 | bool escape = IsKeyPressed(KEY_ESCAPE); 48 | escape = escape || IsMouseButtonPressed(MOUSE_BUTTON_LEFT) || 49 | IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); 50 | 51 | if (escape) { 52 | C.tlen = 0; 53 | ui->window = WINDOW_MAIN; 54 | return; 55 | } 56 | 57 | if (IsKeyPressed(KEY_ENTER)) { 58 | C.alive = 0; 59 | int tlen = C.tlen; 60 | if (tlen > 0) { 61 | MainPasteText(C.txt); 62 | } 63 | ui->window = WINDOW_MAIN; 64 | return; 65 | } 66 | } 67 | 68 | void TextModalDraw(Ui* ui) { 69 | Color bg = BLACK; 70 | bg.a = 150; 71 | int sw = GetScreenWidth(); 72 | int sh = GetScreenHeight(); 73 | DrawRectangle(0, 0, sw, sh, bg); 74 | Color c1 = GetLutColor(COLOR_BG0); 75 | int cursor_type = ((int)(3 * C.alive)) % 2; 76 | int s = ui->scale; 77 | int hh = 50; 78 | int y0 = (sh - hh * s) / 2; 79 | rlPushMatrix(); 80 | 81 | rlTranslatef(0, y0, 0); 82 | rlScalef(s, s, 1); 83 | DrawRectangle(0, 0, sw, hh, c1); 84 | 85 | const char* cap = "Insert Text:"; 86 | int tx = GetRenderedTextSize(cap).x; 87 | 88 | rlTranslatef(sw / s / 2, 10, 0); 89 | 90 | rlTranslatef(-tx / 2, 0, 0); 91 | FontDrawTexture(cap, 1, 1, BLACK); 92 | FontDrawTexture(cap, 0, 0, WHITE); 93 | rlTranslatef(tx / 2, 0, 0); 94 | 95 | rlTranslatef(0, 20, 0); 96 | 97 | tx = GetRenderedTextSize(C.txt).x; 98 | rlTranslatef(-tx / 2, 0, 0); 99 | FontDrawTexture(C.txt, 1, 1, BLACK); 100 | FontDrawTexture(C.txt, 0, 0, WHITE); 101 | if (cursor_type == 0) { 102 | rlTranslatef(tx + 1, 0, 0); 103 | DrawRectangle(0, 0, 1 + 1, 7 + 1, BLACK); 104 | DrawRectangle(0, 0, 1, 7, WHITE); 105 | } 106 | rlPopMatrix(); 107 | 108 | Rectangle inner_content = (Rectangle){-12, y0, GetScreenWidth() + 24, hh * s}; 109 | DrawDefaultTiledFrame(ui, inner_content); 110 | } 111 | -------------------------------------------------------------------------------- /luasrc/component_examples/basic_ram_display.lua: -------------------------------------------------------------------------------- 1 | local LevelComponent = require 'level_component' 2 | local ffi = require 'ffi' 3 | local Clock = require 'clock' 4 | local utils = require 'utils' 5 | local rl = require 'raylib_api' 6 | local C = require 'c_api' 7 | 8 | local BasicRAMDisplay = LevelComponent:extend() 9 | 10 | local bget = utils.bget 11 | 12 | -- display_w must be a multiple of 8 ! 13 | function BasicRAMDisplay:new(memory, display_w, display_h) 14 | BasicRAMDisplay.super.new(self) 15 | self.pins = { 16 | {'input', 8, 'ram_display_offset'}, -- used for memory swap 17 | } 18 | self.memory = memory 19 | -- assuming that each memory address has 8 bits 20 | self.display_w = display_w 21 | self.display_h = display_h 22 | self.display_offset = 0 23 | end 24 | 25 | 26 | function BasicRAMDisplay:onClock(inputs) 27 | self.display_offset = self:toNumber(inputs.ram_display_offset) 28 | end 29 | 30 | function BasicRAMDisplay:onDraw(rt) 31 | -- Drawing using raylib workflow 32 | -- This rt parameter is a RenderTexure object that is drawn right above the 33 | -- image display. 34 | -- Be careful though: the ondraw is called for each component, so its up to 35 | -- the developper to manage the different components drawing not to overlap! 36 | 37 | -- this func is called even when the sim is not running, so mind returning here 38 | if not self.running then 39 | return 40 | end 41 | 42 | local c2 = ffi.new('Color', {248,255,203,255}) 43 | 44 | local black = ffi.new('Color', {0,0,0,255}) 45 | local white = ffi.new('Color', {255,255,255,255}) 46 | local blank = ffi.new('Color', {0,0,0,0}) 47 | 48 | local mem = self.memory 49 | rl.BeginTextureMode(rt) 50 | rl.ClearBackground(blank) 51 | local tw = rt.texture.width 52 | local dw = self.display_w 53 | local dh = self.display_h 54 | local pixel_size = 10 55 | local pad = 20 56 | 57 | rl.rlPushMatrix() 58 | rl.rlTranslatef(tw - pad - dw * pixel_size, pad, 0) 59 | 60 | rl.DrawRectangle(-5, -5, dw*pixel_size +10, dh*pixel_size +10, c2) 61 | rl.DrawRectangle(0, 0, dw*pixel_size , dh*pixel_size , black) 62 | 63 | local size = dw * dh 64 | local size_bytes = size/8 65 | local off_bytes = self.display_offset + 1 66 | for y=0,dh-1 do 67 | local dw_bytes = dw/8 68 | for x_bytes=0,dw_bytes-1 do 69 | local m = mem[off_bytes] 70 | off_bytes = off_bytes + 1 71 | local x = x_bytes * 8 72 | -- I'm not 100% confident the display here is correct, one might want to 73 | -- re-implement with a proper memory read order 74 | for ibit=0,7 do 75 | local bit = bget(m, ibit) 76 | local pixel_x = x*pixel_size 77 | local pixel_y = y*pixel_size 78 | if bit == 1 then 79 | pixel_color = white 80 | else 81 | pixel_color = black 82 | end 83 | rl.DrawRectangle(pixel_x, pixel_y, pixel_size, pixel_size, pixel_color) 84 | x = x + 1 85 | end 86 | end 87 | end 88 | rl.rlPopMatrix() 89 | rl.EndTextureMode() 90 | end 91 | 92 | return BasicRAMDisplay 93 | -------------------------------------------------------------------------------- /luasrc/legacy_levels/adder.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | local Adder = Tester:extend() 4 | local math = require 'math' 5 | 6 | function Adder:new() 7 | Adder.super.new(self) 8 | self.has_submit = false 9 | self.pins = { 10 | {'output', 4, 'a'}, 11 | {'output', 4, 'b'}, 12 | {'input', 4, 'a_plus_b'}, 13 | {'input', 1, 'carry'}, 14 | } 15 | self.schedule = { 16 | {a=0, b=0, a_plus_b=0, carry=0, name='0 + 0'}, 17 | {a=1, b=0, a_plus_b=1, carry=0, name='1 + 0'}, 18 | {a=0, b=1, a_plus_b=1, carry=0, name='0 + 1'}, 19 | {a=1, b=1, a_plus_b=2, carry=0, name='1 + 1'}, 20 | {a=15, b=0, a_plus_b=15, carry=0, name='15 + 0'}, 21 | {a=15, b=1, a_plus_b=0, carry=1, name='15 + 1'}, 22 | {a=15, b=15, a_plus_b=14, carry=1, name='15 + 15'}, 23 | } 24 | math.randomseed(1) 25 | for i=1,64 do 26 | local a = math.random(16) - 1 27 | local b = math.random(16) - 1 28 | local s = a+b 29 | local c = 0 30 | if s >= 16 then 31 | c = 1 32 | s = s - 16 33 | end 34 | name = a .. ' + ' .. b 35 | table.insert(self.schedule, {a=a, b=b, a_plus_b=s, carry=c, name=name}) 36 | end 37 | end 38 | 39 | addLevel({ 40 | icon="../luasrc/imgs/levels/adder_icon.png", 41 | name="A + B", 42 | desc=[[ 43 | 44 | Objective: Add two unsigned 4-bit numbers. 45 | 46 | We want `a_plus_b` = `a` + `b`, and if the sum uses more than 4 bits, we should return the first 4 bits of the results and activate a carry flag `carry=1`. 47 | 48 | 49 | !hl 50 | 51 | We can represent `unsigned integer` numbers by simply counting them in increasing order from 0 on. By doing that, every bit will represent a power of two. 52 | !img:imgs/tutorial/numbers1.png 53 | So for example: 54 | 101 = 1*100 + 0*10 + 1*1 = 1*4 + 0*2 + 1*1 = 5 (3 bits number) 55 | 0100 = 0*1000 + 1*100 + 0*10 + 0*1 = 0*8 + 1*4 + 0*2 + 0*1 = 4 (4 bits number) 56 | 57 | !hl 58 | 59 | In this section we will see how to `add` two integers of `N` bits. 60 | 61 | First, we have to create a sub-circuit that perform the addition of two bits, and outputs both the result, and an extra "carry", as you would do when summing numbers by hand. We call this sub-circuit a `HALF ADDER`. 62 | !img:imgs/tutorial/addition1.png 63 | That can be created as follows: it's just a XOR for the addition, with an extra AND to add a carry whenever both inputs are 1 (only situation where we have a carry=1). 64 | !img:imgs/tutorial/addition2.png 65 | Having the carry as output is not enough. Now we need to extend the subcircuit to accept a carry input as well, as we do when we are summing numbers by hand. This extended sub-circuit is called a `FULL ADDER`, and can be created as follows. Basically instead of summing `a` and `b`, we will sum `a`, `b` and `carry_in` bits. 66 | !img:imgs/tutorial/addition3.png 67 | Once we have the full adder, we can combine them together to create an adder for N-bits, as follows: 68 | !img:imgs/tutorial/addition4.png 69 | 70 | ]], 71 | chips = { 72 | Clock(true), 73 | Adder(), 74 | }, 75 | id='ADDER', 76 | }) 77 | 78 | -------------------------------------------------------------------------------- /luasrc/legacy_levels/comparator.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | local Comparator = Tester:extend() 4 | local math = require 'math' 5 | 6 | function Comparator:new() 7 | Comparator.super.new(self) 8 | self.has_submit = false 9 | self.pins = { 10 | {'output', 4, 'a'}, 11 | {'output', 4, 'b'}, 12 | {'input', 1, 'a_gt_b'}, 13 | {'input', 1, 'a_eq_b'}, 14 | {'input', 1, 'a_lt_b'}, 15 | } 16 | self.schedule = { 17 | {a=0, b=0, a_gt_b=0, a_eq_b=1, a_lt_b=0, name='a=0 b=0'}, 18 | {a=1, b=0, a_gt_b=1, a_eq_b=0, a_lt_b=0, name='a=1 b=0'}, 19 | {a=0, b=1, a_gt_b=0, a_eq_b=0, a_lt_b=1, name='a=0 b=1'}, 20 | } 21 | math.randomseed(1) 22 | for i=1,64 do 23 | local a = math.random(16) - 1 24 | local b = math.random(16) - 1 25 | local c = {a=a, b=b, a_gt_b=0, a_eq_b=0, a_lt_b=0, name='a='..a..' b='..b} 26 | if a > b then c['a_gt_b'] = 1 end 27 | if a == b then c['a_eq_b'] = 1 end 28 | if a < b then c['a_lt_b'] = 1 end 29 | table.insert(self.schedule, c) 30 | end 31 | end 32 | 33 | addLevel({ 34 | icon="../luasrc/imgs/levels/comparator_icon.png", 35 | name="A > B", 36 | desc=[[ 37 | 38 | Objective: Compare two unsigned 4-bit numbers `a` and `b` and tell if `a` is bigger (`a_gt_b`), equals (`a_eq_b`) or smaller (`a_lt_b`) then `b`. 39 | 40 | For example: 41 | 42 | - `a`=0 `b`=0 --> `a_gt_b`=0 `a_eq_b`=1 `a_lt_b`=0 43 | - `a`=1 `b`=0 --> `a_gt_b`=1 `a_eq_b`=0 `a_lt_b`=0 44 | - `a`=0 `b`=1 --> `a_gt_b`=0 `a_eq_b`=0 `a_lt_b`=1 45 | 46 | !hl 47 | 48 | This section describes how to compare two positive integer numbers `A` and `B`. We often want to do the 3 comparisons at a time, (i) `A > B`, (ii) `A = B` and (iii) `A < B`. 49 | 50 | We start creating a comparator for a single bit: 51 | !img:imgs/tutorial/comparator1.png 52 | Which can be done as follows: 53 | !img:imgs/tutorial/comparator2.png 54 | !img:imgs/tutorial/comparator3.png 55 | Then, in a second step, we extend this 1-bit comparator to accept inputs from a "more significant" bit. The idea is to do it the same way we do to compare two numbers: first we check if the most significant digit is equal, lower or higher than the other: if it's lower or higher we know whether a number is lower or higher, but if they're equal, we need to move forward to the next digit, creating a sequence of comparisons. 56 | !img:imgs/tutorial/comparator4.png 57 | The formulas will look like the following, where `Aprv` = previous A, ie, result from previous comparator (from a more signficant bit), and `Anxt` = next A, ie, result going to next comparator (towards a least significant bit). 58 | 59 | `A`<`Bnxt` = (`A`<`Bprv`) OR (`A`=`Bprv` AND `A`<`B`) 60 | `A`=`Bnxt` = (`A`=`Bprv`) AND (`A`=`B`) 61 | `A`>`Bnxt` = (`A`>`Bprv`) OR (`A`=`Bprv` AND `A`>`B`) 62 | 63 | Then, similarly to the addition, we chain these comparators to create a bigger comparator for N bits. 64 | !img:imgs/tutorial/comparator5.png 65 | 66 | ]], 67 | chips = { 68 | Clock(true), 69 | Comparator(), 70 | }, 71 | id='COMPARATOR', 72 | }) 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/rendering.h: -------------------------------------------------------------------------------- 1 | #ifndef RENDERING_H 2 | #define RENDERING_H 3 | #include "img.h" 4 | #include "sim.h" 5 | 6 | typedef enum { 7 | // Wire enters the image. 8 | CONN_INPUT, 9 | // Wire leaves the image. 10 | CONN_OUTPUT, 11 | } ConnType; 12 | 13 | typedef struct { 14 | // Number of connections. 15 | // A connection is a group of wires/bits represented by a name. 16 | int num_conn; 17 | //. List of connections. 18 | struct { 19 | // Input or output connection. 20 | ConnType type; 21 | // Number of wires (pins) per connection. 22 | int len; 23 | // Name of the connectino 24 | const char* name; 25 | } conn[50]; 26 | } PinDesc; 27 | 28 | // Main arguments for drawing in edit mode. 29 | typedef struct { 30 | // Target rendered image. 31 | // Mind that it is rendered in CPU. 32 | Image out; 33 | // Input main image buffer (pyramid/multi-resolution) 34 | Image img[3]; 35 | // Selection image buffer (pyramid/multi-resolution). 36 | Image sel[3]; 37 | // X offset of the selection. 38 | int sel_off_x; 39 | // Y offset of the selection. 40 | int sel_off_y; 41 | // Camera spacing: the size of a single image pixel in screen pixels. 42 | float pixel_size; 43 | // X camera position. 44 | int camera_x; 45 | // Y camera position. 46 | int camera_y; 47 | // Tool preview image (brush etc). 48 | Image tool_img; 49 | // Tool preview offset x in pixels. 50 | int tool_off_x; 51 | // Tool preview offset y in pixels. 52 | int tool_off_y; 53 | // A simple pixel preview created for pixel/line drawing. 54 | bool pixel_preview; 55 | // X position of the pixel preview. 56 | int pixel_preview_x; 57 | // Y position of the pixel preview. 58 | int pixel_preview_y; 59 | // Color for the pixel preview. 60 | Color pixel_preview_color; 61 | // Color outside the main image. 62 | Color bg; 63 | // Flag to draw a pixel grid on high zooms. 64 | bool grid; 65 | // Color of the high zoom grid. 66 | Color grid_color; 67 | } RenderImgCtx; 68 | 69 | // Renders main screen in edit mode, with tools and everything. 70 | void RenderImageEdit(RenderImgCtx rc); 71 | 72 | // renders an image in the camera space. Used for rendering the wire API stuff 73 | // after rendering the main image. 74 | void RenderImageSimple(Image* out, Image img, float pixel_size, int camera_x, 75 | int camera_y, int offx, int offy); 76 | 77 | // Renders the dotted selection rectangle. 78 | void RenderImageSelRect(Image* out, float pixel_size, int camera_x, 79 | int camera_y, RectangleInt r, int ls, double t); 80 | 81 | // Renders a filled rectangle in camera space. 82 | // Used for the resizing handle. 83 | void DrawImageSceneRect(Image* out, float pixel_size, int camera_x, 84 | int camera_y, Rectangle r, Color c); 85 | 86 | // Draws the wire API in the leftr side of the image. 87 | void RenderImageCompInput(Image* out, Image buffer, Sim* simu, int ncomp, 88 | PinDesc* cdesc_list); 89 | 90 | // Draws a simple rectangle (only the edges). 91 | void RenderImageSimpleRect(Image* out, float pixel_size, int camera_x, 92 | int camera_y, RectangleInt r, int ls, double t, 93 | Color c); 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /src/widgets.h: -------------------------------------------------------------------------------- 1 | #ifndef LISTBOX_H 2 | #define LISTBOX_H 3 | #include "img.h" 4 | #include "sprite.h" 5 | #include "ui.h" 6 | 7 | // Scroll widget. 8 | typedef struct { 9 | Rectangle content; 10 | Rectangle rect; 11 | Rectangle scroll_rect; 12 | int value; 13 | int value0; 14 | int mouse0; 15 | bool down; 16 | bool hit; 17 | bool hidden; 18 | } Scroll; 19 | 20 | // Listbox row widget, used internally in the Listbox widget. 21 | typedef struct { 22 | char* content; 23 | Rectangle hitbox_r; // relative hitbox 24 | Rectangle hitbox_g; 25 | int text_y; 26 | } ListboxRow; 27 | 28 | // Listbox widget. 29 | typedef struct { 30 | ListboxRow* rows; 31 | Rectangle hitbox; 32 | int height; 33 | int row_hit; 34 | Scroll scroll; 35 | } Listbox; 36 | 37 | // Textbox widget. Renders text using the DrawTextBoxAdvanced routine. 38 | typedef struct { 39 | Rectangle box; 40 | char* text; 41 | Sprite* sprites; 42 | int text_w; 43 | int text_x; 44 | int height; 45 | Scroll scroll; 46 | } Textbox; 47 | 48 | // Button widget. 49 | // The caller has to setup its properties manually at creation. 50 | typedef struct { 51 | bool disabled; 52 | bool pressed; 53 | bool toggled; 54 | bool hover; 55 | bool hidden; 56 | bool gradient; 57 | Rectangle hitbox; 58 | } Btn; 59 | 60 | // Listbox 61 | void ListboxLoad(Listbox* l); 62 | void ListboxUnload(Listbox* l); 63 | void ListboxSetBox(Listbox* l, Rectangle r); 64 | void ListboxAddRow(Listbox* l, const char* content); 65 | void ListboxUpdate(Listbox* l); 66 | void ListboxDraw(Listbox* l, Ui* ui, int sel); 67 | 68 | // Scroll Functions 69 | void ScrollLoad(Scroll* s); 70 | void ScrollSetContentBox(Scroll* s, Rectangle box, int w); 71 | void ScrollUpdate(Scroll* s, float h, Vector2 mouse); 72 | void ScrollDraw(Scroll* s); 73 | void ScrollResetValue(Scroll* s); 74 | int ScrollGetValue(Scroll* s); 75 | 76 | // Text Box widget functions 77 | void TextboxLoad(Textbox* t); 78 | void TextboxUnload(Textbox* t); 79 | void TextboxSetContent(Textbox* t, const char* txt, Sprite* sprites); 80 | void TextboxSetBox(Textbox* t, Rectangle box); 81 | void TextboxUpdate(Textbox* t, Ui* ui); 82 | void TextboxDraw(Textbox* t, Ui* ui); 83 | 84 | // Updates button state. Returns true if button was clicked. 85 | bool BtnUpdate(Btn* b, Ui* ui); 86 | 87 | // Text button (close button for example). 88 | void BtnDrawText(Btn* b, int ui_scale, const char* text); 89 | 90 | // Draws the legend of the button. Needs to be called separately from the button 91 | // itself. 92 | void BtnDrawLegend(Btn* b, int ui_scale, const char* text); 93 | 94 | // Icon button: tool buttons and challenge button. 95 | void BtnDrawIcon(Btn* b, int ui_scale, Texture2D tex, Rectangle r); 96 | 97 | // Colorbox button (displayed in the bottom of the UI. 98 | void BtnDrawColor(Ui* ui, Rectangle r, Color c, bool selected, bool disabled); 99 | 100 | // Checks if button is being hovered 101 | bool BtnHover(Btn* b); 102 | 103 | // Draws a basic frame of size 2px. 104 | void DrawWidgetFrame(Ui* ui, Rectangle inner_content); 105 | 106 | // Frame for windows. 107 | void DrawWidgetFrameInv(Ui* ui, Rectangle r); 108 | 109 | // Title for framed windows 110 | void DrawTitle(Ui* ui, Rectangle modal, const char* title); 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /luasrc/levels/gcd.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | local math = require 'math' 4 | local GCD = Tester:extend() 5 | 6 | local function gcd(a,b) 7 | if type(a) == "number" and type(b) == "number" and 8 | a == math.floor(a) and b == math.floor(b) then 9 | if b == 0 then 10 | return a 11 | else 12 | return gcd(b, a % b) -- tail recursion 13 | end 14 | else 15 | error("Invalid argument to gcd (" .. tostring(a) .. "," .. 16 | tostring(b) .. ")", 2) 17 | end 18 | end 19 | 20 | function GCD:new() 21 | GCD.super.new(self) 22 | self.has_submit = true 23 | self.pins = { 24 | {'input', 1, 'submit'}, 25 | {'output', 16, 'a'}, 26 | {'output', 16, 'b'}, 27 | {'input', 16, 'gcd_ab'}, 28 | } 29 | math.randomseed(0) 30 | local cases = {} 31 | local k = math.floor(32000 / (24*24)) 32 | for i=1,24 do 33 | local a = math.random(k * i * i) + 1 34 | local b = math.random(k * i * i) + 1 35 | local gcd_ab = gcd(a,b) 36 | cases[i] = { 37 | a=a, 38 | b=b, 39 | gcd_ab=gcd_ab, 40 | name='GCD(a=' .. a .. ', b=' .. b .. ') should be ' .. gcd_ab, 41 | } 42 | end 43 | self.schedule = cases 44 | end 45 | 46 | addLevel({ 47 | icon = "../luasrc/imgs/levels/gcd_icon.png", 48 | name = "Greatest Common Divisor", 49 | desc=[[ 50 | 51 | !img:imgs/levels/gcd_img.png 52 | 53 | Find the greatest common divisor (GCD) between two positive intergers. 54 | 55 | The GCD between two numbers is the biggest positive number that divides both numbers. Examples: 56 | 57 | `GCD(10, 6)=2` --> because 2 divides both 10 and 6 since 10 = 2x5 and 6 = 2x3. 58 | `GCD(20, 8)=4` --> 20 = 4x5 and 8=4x2. 59 | `GCD(15,14)=1` --> There's no positive number that divides 15 and 14 besides 1. 60 | `GCD(8,8)=8` --> A number is divisible by itself. 61 | `GCD(24, 8)=8` 62 | 63 | You'll be given as input two 8-bit positive numbers `A` and `B`, and you should compute in output `gcd_ab` the 8-bit number corresponding to its GCD. 64 | 65 | You can perform the calculation in multiple clock cycles, and the result is only verified when the `submit` flag (1bit output) will be 1. The result is verified in the rising edge of the clock. 66 | 67 | `Tips`: You can use the Euclidean Algorithm to compute the GCD. 68 | 69 | Basically, if `A` and `B` are positive numbers, then if you subtract both numbers, the GCD between them will remain the same. 70 | 71 | Using this property, you can create a sequence starting with two numbers A and B, then replace the biggest number by the difference between it and the smaller one. You can see that this sequence will eventually stop, because the numbers will always be smaller at each step. Repeating this operation, you'll get at the end equal numbers, which will correspond to the GCD you're seeking (one step before reaching 0). 72 | 73 | IF A>B THEN A = A - B 74 | IF B>A THEN B = B - A 75 | IF A=B THEN GCD=A 76 | 77 | For example, computing the GCD between 10 and 6: 78 | 79 | `STEP1`: A=10, B=6 --> A is bigger, so we make A=A-B=10-6=4 80 | 81 | `STEP2`: A=4, B=6 --> B is bigger, so we make B=B-A=6-4=2 82 | 83 | `STEP3`: A=4, B=2 --> A is bigger, so we make A=A-B=4-2=2 84 | 85 | `STEP4`: A=2, B=2 --> A and B are equal, so the GCD must be 2! 86 | 87 | 88 | ]], 89 | chips = { 90 | Clock(), 91 | GCD(), 92 | }, 93 | id='GCD', 94 | }) 95 | -------------------------------------------------------------------------------- /src/w_number.c: -------------------------------------------------------------------------------- 1 | #include "w_number.h" 2 | 3 | #include 4 | 5 | #include "colors.h" 6 | #include "font.h" 7 | #include "stdio.h" 8 | #include "tiling.h" 9 | #include "ui.h" 10 | #include "w_main.h" 11 | 12 | #define MAX_TEXT_SIZE 1000 13 | 14 | static struct { 15 | int tlen; 16 | char txt[MAX_TEXT_SIZE]; 17 | float alive; 18 | } C = {0}; 19 | 20 | static inline bool IsDigit(int key) { 21 | return key >= KEY_ZERO && key <= KEY_NINE; 22 | } 23 | 24 | void NumberModalOpen(Ui* ui) { 25 | ui->window = WINDOW_NUMBER; 26 | C.alive = 0; 27 | C.tlen = 0; 28 | C.txt[0] = '\0'; 29 | } 30 | 31 | static int ParseModalNumber() { 32 | int n = -1; 33 | if (sscanf(C.txt, "%d", &n) == 0) { 34 | return -1; 35 | } 36 | return n; 37 | } 38 | 39 | void NumberModalUpdate(Ui* ui) { 40 | float dt = GetFrameTime(); 41 | C.alive += dt; 42 | int key = GetCharPressed(); 43 | int max_input_chars = MAX_TEXT_SIZE; 44 | while (key > 0) { 45 | if (IsDigit(key) && (C.tlen < max_input_chars)) { 46 | C.txt[C.tlen] = (char)key; 47 | C.txt[++C.tlen] = '\0'; // Add null terminator at the end of the string. 48 | } 49 | key = GetCharPressed(); // Check next character in the queue 50 | C.alive = 0; 51 | } 52 | 53 | if (IsKeyPressed(KEY_BACKSPACE)) { 54 | C.tlen--; 55 | if (C.tlen < 0) C.tlen = 0; 56 | C.txt[C.tlen] = '\0'; 57 | C.alive = 0; 58 | } 59 | 60 | bool escape = IsKeyPressed(KEY_ESCAPE); 61 | escape = escape || IsMouseButtonPressed(MOUSE_BUTTON_LEFT) || 62 | IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); 63 | 64 | if (escape) { 65 | C.tlen = 0; 66 | ui->window = WINDOW_MAIN; 67 | return; 68 | } 69 | 70 | if (IsKeyPressed(KEY_ENTER)) { 71 | C.alive = 0; 72 | int tlen = C.tlen; 73 | if (tlen > 0) { 74 | int n = ParseModalNumber(); 75 | MainSetLineSep(n); 76 | } 77 | ui->window = WINDOW_MAIN; 78 | return; 79 | } 80 | } 81 | 82 | void NumberModalDraw(Ui* ui) { 83 | Color bg = BLACK; 84 | bg.a = 150; 85 | int sw = GetScreenWidth(); 86 | int sh = GetScreenHeight(); 87 | DrawRectangle(0, 0, sw, sh, bg); 88 | Color c1 = GetLutColor(COLOR_BG0); 89 | int cursor_type = ((int)(3 * C.alive)) % 2; 90 | int s = ui->scale; 91 | int hh = 50; 92 | int y0 = (sh - hh * s) / 2; 93 | rlPushMatrix(); 94 | 95 | rlTranslatef(0, y0, 0); 96 | rlScalef(s, s, 1); 97 | DrawRectangle(0, 0, sw, hh, c1); 98 | 99 | const char* cap = "Insert Separation Width:"; 100 | int tx = GetRenderedTextSize(cap).x; 101 | 102 | rlTranslatef(sw / s / 2, 10, 0); 103 | 104 | rlTranslatef(-tx / 2, 0, 0); 105 | FontDrawTexture(cap, 1, 1, BLACK); 106 | FontDrawTexture(cap, 0, 0, WHITE); 107 | rlTranslatef(tx / 2, 0, 0); 108 | 109 | rlTranslatef(0, 20, 0); 110 | 111 | tx = GetRenderedTextSize(C.txt).x; 112 | rlTranslatef(-tx / 2, 0, 0); 113 | FontDrawTexture(C.txt, 1, 1, BLACK); 114 | FontDrawTexture(C.txt, 0, 0, WHITE); 115 | if (cursor_type == 0) { 116 | rlTranslatef(tx + 1, 0, 0); 117 | DrawRectangle(0, 0, 1 + 1, 7 + 1, BLACK); 118 | DrawRectangle(0, 0, 1, 7, WHITE); 119 | } 120 | rlPopMatrix(); 121 | 122 | Rectangle inner_content = (Rectangle){-12, y0, GetScreenWidth() + 24, hh * s}; 123 | DrawDefaultTiledFrame(ui, inner_content); 124 | } 125 | -------------------------------------------------------------------------------- /luasrc/utils.lua: -------------------------------------------------------------------------------- 1 | local math = require 'math' 2 | local bit = require 'bit' 3 | local ffi = require 'ffi' 4 | local C = require 'c_api' 5 | local json = require 'json' 6 | local utils = {} 7 | local lshift, rshift, rol = bit.lshift, bit.rshift, bit.rol 8 | local bnot, band, bor, bxor = bit.bnot, bit.band, bit.bor, bit.bxor 9 | 10 | function utils.randomBits(n) 11 | local r = 0 12 | for ibit=1,n do 13 | local v = math.random(2) 14 | if v == 1 then 15 | r = bor(r, lshift(1, ibit-1)) 16 | end 17 | end 18 | return r 19 | end 20 | 21 | function utils.randomBits32() 22 | local r = 0 23 | for ibit=1,32 do 24 | local v = math.random(2) 25 | if v == 1 then 26 | r = bor(r, lshift(1, ibit-1)) 27 | end 28 | end 29 | return r 30 | end 31 | 32 | function utils.bget(n, ibit) 33 | local m = lshift(1, ibit) 34 | local r = rshift(band(n, m), ibit) 35 | return r 36 | end 37 | 38 | function utils.scriptPath() 39 | local str = debug.getinfo(2, "S").source:sub(2) 40 | return str:match("(.*/)") 41 | end 42 | 43 | function utils.rlDrawTexturePro(texture, source, dest, origin, rotation, tint) 44 | local args = ffi.new('RlDrawTextureProArgs', { texture, source, dest, origin, rotation, tint}) 45 | C.RlDrawTexturePro(args) 46 | end 47 | 48 | --- Check if a file or directory exists in this path 49 | function utils.exists(file) 50 | local ok, err, code = os.rename(file, file) 51 | if not ok then 52 | if code == 13 then 53 | -- Permission denied, but it exists 54 | return true 55 | end 56 | end 57 | return ok, err 58 | end 59 | 60 | --- Check if a directory exists in this path 61 | function utils.isdir(path) 62 | -- "/" works on both Unix and Windows 63 | return exists(path.."/") 64 | end 65 | 66 | function utils.isModuleAvailable(name) 67 | if package.loaded[name] then 68 | return true 69 | else 70 | for _, searcher in ipairs(package.searchers or package.loaders) do 71 | local loader = searcher(name) 72 | if type(loader) == 'function' then 73 | package.preload[name] = loader 74 | return true 75 | end 76 | end 77 | return false 78 | end 79 | end 80 | 81 | function utils.iswindows() 82 | return package.config:sub(1,1) == '\\' 83 | end 84 | 85 | 86 | function utils.saveJson(content, path) 87 | -- Open the file handle 88 | local file, errorString = io.open( path, "w" ) 89 | 90 | if not file then 91 | -- Error occurred; output the cause 92 | print( "File error: " .. errorString ) 93 | return false 94 | else 95 | -- Write encoded JSON data to file 96 | file:write( json.encode( content ) ) 97 | -- Close the file handle 98 | io.close( file ) 99 | return true 100 | end 101 | end 102 | 103 | function utils.loadJson(path) 104 | -- Open the file handle 105 | local file, errorString = io.open( path, "r" ) 106 | if not file then 107 | -- Error occurred; output the cause 108 | print( "File error: " .. errorString ) 109 | else 110 | -- Read data from file 111 | local contents = file:read( "*a" ) 112 | -- Decode JSON data into Lua table 113 | local t = json.decode( contents ) 114 | -- Close the file handle 115 | io.close( file ) 116 | -- Return table 117 | return t 118 | end 119 | end 120 | 121 | 122 | return utils 123 | -------------------------------------------------------------------------------- /src/version.h: -------------------------------------------------------------------------------- 1 | #ifndef VERSION_H 2 | #define VERSION_H 3 | #define CA_VERSION "v1.0.6 - 09Jan2025" 4 | 5 | // v1.0.6 - 09Jan2025 6 | // - Changed complete level icons 7 | // - Splitted sandbox in sandbox and custom component levels. 8 | // - Updated some icons (flag) 9 | // - Updated UI of line tool: separation width has a dedicated button like the 10 | // text tool. 11 | // - Moved tutorial initial image to the tutorial section as TLDR section. 12 | // - Removed dependency on levels so user can pick the one he wants directly. 13 | // - Added a DEMO version with limited image size (256) to make freely 14 | // available. 15 | // v1.0.4 --> v1.0.4: Level Progression 16 | // - Added Level Progression and Steam Achievements. 17 | // - Added 2 dark colors to default palette 18 | // - Added feature to save and load a selection (to mitigate lack of blueprint 19 | // support) 20 | // - Added possibility to change spacing between consecutive lines in line 21 | // tool. 22 | // - Removed minimap and moved sidebar buttons to topbar 23 | // - Added number of pixels to selection on status bar 24 | // - Rename circuitopedia to "Tutorial" 25 | // - Fix bug in SRLATCH image schema 26 | // - Fix a bug in 9 picture in the 7seg level 27 | // - Fix crash when window too small 28 | // v1.0.3 --> v1.0.4: Improved Lua Support 29 | // - (QoL) Loads lua scripts from luasrc/scripts folder. 30 | // Improved Sandbox level with Keyboard + ROM + RAM + Display 31 | // components for reference. 32 | // - (QoL) Added console launcher version for windows. 33 | // 34 | // v1.0.2 --> v1.0.3: Small QoL 35 | // - (QoL) Window starts maximized. Initial window is a bit smaller, to make 36 | // it work better at smaller screens. Dialogs change size with screen 37 | // size (to adapt for smaller screens). 38 | // - (QoL) Can pick color using ALT during brush, line and bucket tools. 39 | // - (Others) The parsing algorithm requires that all nands have 2 inputs and 40 | // 1 output, otherwise it enters an error state. 41 | // - (Bugfix) Fixed tilted rectangle display in levels. 42 | // 43 | // v1.0.1 --> v1.0.2: Bugfix 44 | // - (Bugfix) Pasted Image Ugly. 45 | // Pasting images that were not encoded with 32 bits were not pasted 46 | // propperly. It would happen specially when the saved image went 47 | // through a website that cropped the alpha channel. 48 | // - (Bugfix) Picker on Background Was Picking Transparent Color. 49 | // The picker on background was picking transparent color because 50 | // internally it is represented as such. The problem is that you 51 | // then couldn't use that color to "draw" black pixels. Fixed it by 52 | // making it be black pixels instead. 53 | // - (Improvement): Hotkeys on Selection. 54 | // Improved legend of selection tool to hotkeys not mentioned 55 | // anywhere like arrows, pressing CTRL before dragging, pressing 56 | // SHIFT while dragging etc 57 | // - (Bugfix) OpenMP is taking all machine's CPU. 58 | // It seems windows' openmp is doing busy wait on the openmp 59 | // threads, leading to a 100% cpu usage all the time, even when it 60 | // is very light on calculations. To remediate that we put a max of 61 | // 6 threads for openmp, until we find a better solution. 62 | #endif 63 | -------------------------------------------------------------------------------- /src/w_dialog.c: -------------------------------------------------------------------------------- 1 | #include "w_dialog.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "font.h" 7 | #include "tiling.h" 8 | #include "ui.h" 9 | #include "utils.h" 10 | #include "widgets.h" 11 | 12 | static struct { 13 | Rectangle confirm_modal; 14 | Btn confirm_buttons[3]; 15 | char* modal_msg; 16 | UiCallback modal_next_action; // Next action for modal 17 | } C = {0}; 18 | 19 | static void DialogUpdateLayout() { 20 | int n = 3; 21 | int gap = 12; 22 | int rw = 120; 23 | int tot = (n - 1) * gap + n * rw; 24 | int rh = 17 * 2; 25 | 26 | int modal_w = 500; 27 | int modal_h = 180; 28 | int modal_x = (GetScreenWidth() - modal_w) / 2; 29 | int modal_y = (GetScreenHeight() - modal_h) / 2; 30 | C.confirm_modal = (Rectangle){modal_x, modal_y, modal_w, modal_h}; 31 | int x0 = modal_x + (modal_w - tot) / 2; 32 | int y0 = modal_y + (modal_h - 17 * 2 - 20); 33 | 34 | for (int i = 0; i < n; i++) { 35 | C.confirm_buttons[i].hitbox = (Rectangle){ 36 | x0 + i * (gap + rw), 37 | y0, 38 | rw, 39 | rh, 40 | }; 41 | } 42 | } 43 | 44 | void DialogOpen(Ui* ui, const char* modal_msg, UiCallback modal_next_action) { 45 | ui->window = WINDOW_DIALOG; 46 | if (C.modal_msg) free(C.modal_msg); 47 | C.modal_msg = CloneString(modal_msg); 48 | C.modal_next_action = modal_next_action; 49 | for (int i = 0; i < 3; i++) { 50 | C.confirm_buttons[i].pressed = false; 51 | C.confirm_buttons[i].disabled = false; 52 | C.confirm_buttons[i].toggled = false; 53 | } 54 | } 55 | 56 | void DialogUpdate(Ui* ui) { 57 | DialogUpdateLayout(); 58 | int result = -1; 59 | if (BtnUpdate(&C.confirm_buttons[0], ui)) result = 0; 60 | if (BtnUpdate(&C.confirm_buttons[1], ui)) result = 1; 61 | if (BtnUpdate(&C.confirm_buttons[2], ui) || IsKeyPressed(KEY_ESCAPE)) 62 | result = 2; 63 | if (result == -1) { // nothing 64 | return; 65 | } 66 | if (result == 2) { // cancel: no action 67 | ui->window = WINDOW_MAIN; 68 | return; 69 | } 70 | if (result == 0) { // save 71 | if (MainOnSaveClick(ui, false)) { 72 | ui->window = WINDOW_MAIN; 73 | return; // Cancelled during save 74 | } 75 | } 76 | // Exectued on resul=0 or result=1 77 | C.modal_next_action(ui); 78 | ui->window = WINDOW_MAIN; 79 | } 80 | 81 | void DialogDraw(Ui* ui) { 82 | int sw = GetScreenWidth(); 83 | int sh = GetScreenHeight(); 84 | DrawRectangle(0, 0, sw, sh, (Color){.r = 0, .g = 0, .b = 0, .a = 150}); 85 | int s = ui->scale; 86 | Rectangle r = C.confirm_modal; 87 | BeginScissorMode(C.confirm_modal.x, C.confirm_modal.y, C.confirm_modal.width, 88 | C.confirm_modal.height); 89 | DrawDefaultTiledScreen(ui); 90 | EndScissorMode(); 91 | 92 | int msg_size = GetRenderedTextSize(C.modal_msg).x; 93 | rlPushMatrix(); 94 | rlTranslatef(r.x, r.y, 0); 95 | rlScalef(s, s, 1); 96 | int mx = (r.width / s - msg_size) / 2; 97 | int my = 36; 98 | FontDrawTexture(C.modal_msg, mx + 1, my + 1, BLACK); 99 | FontDrawTexture(C.modal_msg, mx, my, WHITE); 100 | rlPopMatrix(); 101 | BtnDrawText(&C.confirm_buttons[0], s, "Save"); 102 | BtnDrawText(&C.confirm_buttons[1], s, "Don't save"); 103 | BtnDrawText(&C.confirm_buttons[2], s, "Cancel"); 104 | DrawWidgetFrameInv(ui, C.confirm_modal); 105 | DrawTitle(ui, C.confirm_modal, "Confirm"); 106 | } 107 | -------------------------------------------------------------------------------- /luasrc/legacy_levels/wires.lua: -------------------------------------------------------------------------------- 1 | local Tester = require 'tester' 2 | local Clock = require 'clock' 3 | local Wires = Tester:extend() 4 | 5 | function Wires:new() 6 | Wires.super.new(self) 7 | self.has_submit = false 8 | self.pins = { 9 | {'output', 1, 'a_in'}, 10 | {'output', 2, 'b_in'}, 11 | {'output', 4, 'c_in'}, 12 | {'input', 1, 'a_out'}, 13 | {'input', 4, 'c1_out'}, 14 | {'input', 2, 'b_out'}, 15 | {'input', 4, 'c2_out'}, 16 | } 17 | 18 | self.schedule = {} 19 | for a=0,1 do 20 | for b=0,3 do 21 | for c=0,15 do 22 | table.insert( 23 | self.schedule, { 24 | a_in=a, 25 | b_in=b, 26 | c_in=c, 27 | a_out=a, 28 | b_out=b, 29 | c1_out=c, 30 | c2_out=c, 31 | } 32 | ) 33 | end 34 | end 35 | end 36 | end 37 | 38 | 39 | addLevel({ 40 | icon="../luasrc/imgs/levels/wires_icon.png", 41 | name="Wires", 42 | desc=[[ 43 | 44 | !img:imgs/levels/wires_icon.png 45 | 46 | Objective: Connect input pins to output pins using pixel wires. 47 | 48 | - `a_in` should be connected to `a_out`. 49 | - `b_in` should be connected to `b_out`. 50 | - `c_in` should be connected to `c1_out` and `c2_out`. 51 | 52 | !img:imgs/levels/wires_pins_example.png 53 | 54 | You can use the `line tool` to help with the multi-bit wires. 55 | 56 | For multi-bit pins, the lowest bit is the upper one both in the input and ouput, so they should be connected in the same order, as in the example below: 57 | 58 | !img:imgs/levels/wires_example.png 59 | 60 | !hl 61 | 62 | Wires are formed by connected non-black pixels. Wires can have 4 states, as displayed below: 63 | - `ON` (lighter color) 64 | - `OFF` (darker color) 65 | - `UNDEFINED` (magenta color) 66 | - `ERROR` (red color) 67 | !img:imgs/tutorial/wires1.png 68 | The `ON` state represents the logic 1, and the `OFF` state represents the logic 0. Wires state can be toggled (1->0 or 0->1) by clicking on it during simulation mode. 69 | !img:imgs/tutorial/wires2.png 70 | The `ERROR` wires only appear when an error occurs during simulation. There are 2 possible errors that can occur: 71 | Error type 1: A wire that belongs to the output of more than one gate. Example below. Each wire can only be the output of maximum 1 gate. 72 | !img:imgs/tutorial/simu_error1.png 73 | Error type 2: When the wire state oscillates between different values without end. Example below. 74 | !img:imgs/tutorial/simu_error_cycle.png 75 | The `UNDEFINED` state means that we don't know the state of the wire. Every wire start with that state, with exception of wires that are not output of a NAND gate. Those wires are initialized as 0. 76 | Below an example of how the wire states change when the simulation is first launched: 77 | !img:imgs/tutorial/simu_example1.png 78 | The width or shape of a wire is not important, nor its color, as long as the pixels are connected. 79 | !img:imgs/tutorial/wires3.png 80 | However, wires are allowed to cross. As a rule of thumb, straight wires only connect at T or L connections. 81 | !img:imgs/tutorial/wire_cross.png 82 | Wires are only connected via up/down/left/right pixel neighbours. Diagonal pixels are not considered connected. In the example below, in the first case you have 4 different wires, while in the second example you have a single wire. 83 | !img:imgs/tutorial/wire_diagonal.png 84 | 85 | ]], 86 | chips = { 87 | Clock(true), 88 | Wires(), 89 | }, 90 | id='WIRES', 91 | }) 92 | 93 | -------------------------------------------------------------------------------- /src/shaders.c: -------------------------------------------------------------------------------- 1 | 2 | #include "shaders.h" 3 | 4 | #include "assert.h" 5 | #include "stdio.h" 6 | #define SHADER_LOC(a, b) _s.a##_loc_##b = GetShaderLocation(_s.a##_shader, #b); 7 | #define SHADER_LOAD(name) \ 8 | { \ 9 | _s.name##_shader = \ 10 | LoadShader(0, "../assets/shaders/" #name "_shader.glsl"); \ 11 | assert(IsShaderReady(_s.name##_shader)); \ 12 | } 13 | 14 | #define SHADER_LOAD_CODE(name) \ 15 | { \ 16 | printf("Loading shader %s\n", #name); \ 17 | _s.name##_shader = LoadShaderFromMemory(0, name##_code); \ 18 | assert(IsShaderReady(_s.name##_shader)); \ 19 | } 20 | 21 | static Shaders _s = {0}; 22 | 23 | static const char selrect_code[] = 24 | "#version 330 \n " 25 | "in vec2 fragTexCoord; \n " 26 | "in vec4 fragColor; \n " 27 | "uniform vec4 colDiffuse; \n " 28 | "uniform vec2 rsize; \n " 29 | "uniform vec2 rect_pos; \n " 30 | "uniform int pattern_shift; \n " 31 | "uniform int pattern_width; \n " 32 | " out vec4 finalColor; \n " 33 | "void main() { \n " 34 | " int fx = int(round(fragTexCoord.x * rsize.x - 0.5)); \n " 35 | " int fy = int(round(fragTexCoord.y * rsize.y - 0.5)); \n " 36 | " int ix = int(round(rect_pos.x)); \n " 37 | " int iy = int(round(rect_pos.y)); \n " 38 | " int p = ix + fx + iy + fy + pattern_shift; \n " 39 | " p = p % (2 * pattern_width); \n " 40 | " if (p < pattern_width) { \n " 41 | " finalColor = fragColor * vec4(1.0, 1.0, 1.0, 1.0); \n " 42 | " } else { \n " 43 | " finalColor = fragColor * vec4(0.0, 0.0, 0.0, 1.0); \n " 44 | " } \n " 45 | "} \n "; 46 | 47 | void InitShaders() { 48 | SHADER_LOAD(fill); 49 | SHADER_LOAD(rotate); 50 | SHADER_LOC(rotate, ccw); 51 | 52 | SHADER_LOAD(comb); 53 | SHADER_LOC(comb, dst_tex); 54 | SHADER_LOC(comb, off); 55 | SHADER_LOC(comb, src_size); 56 | SHADER_LOC(comb, dst_size); 57 | SHADER_LOC(comb, roi_size); 58 | SHADER_LOC(comb, src_off); 59 | SHADER_LOC(comb, dst_off); 60 | 61 | SHADER_LOAD_CODE(selrect); 62 | SHADER_LOC(selrect, rsize); 63 | SHADER_LOC(selrect, rect_pos); 64 | SHADER_LOC(selrect, pattern_shift); 65 | SHADER_LOC(selrect, pattern_width); 66 | 67 | SHADER_LOAD(project); 68 | SHADER_LOC(project, sp); 69 | SHADER_LOC(project, img_size); 70 | SHADER_LOC(project, tpl); 71 | 72 | SHADER_LOAD(update); 73 | SHADER_LOC(update, img_size); 74 | SHADER_LOC(update, sel_size); 75 | SHADER_LOC(update, sel_off); 76 | SHADER_LOC(update, sel); 77 | SHADER_LOC(update, tool_size); 78 | SHADER_LOC(update, tool_off); 79 | SHADER_LOC(update, tool); 80 | SHADER_LOC(update, mode); 81 | SHADER_LOC(update, simu_state); 82 | SHADER_LOC(update, bg_color); 83 | SHADER_LOC(update, bugged_color); 84 | SHADER_LOC(update, undefined_color); 85 | SHADER_LOC(update, comp_x); 86 | SHADER_LOC(update, comp_y); 87 | SHADER_LOC(update, state_buf); 88 | } 89 | 90 | Shaders* GetShaders() { return &_s; } 91 | -------------------------------------------------------------------------------- /src/w_tutorial.c: -------------------------------------------------------------------------------- 1 | #include "w_tutorial.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "api.h" 8 | #include "font.h" 9 | #include "tiling.h" 10 | #include "ui.h" 11 | #include "widgets.h" 12 | 13 | static struct { 14 | bool inited; 15 | Textbox tb; 16 | Rectangle textbox_wrap; 17 | Listbox lb; 18 | int sel; 19 | Rectangle title; 20 | Rectangle modal; 21 | bool opened_this_frame; 22 | bool closed; 23 | Btn btn_close; 24 | } C = {0}; 25 | 26 | void TutorialUpdateLayout() { 27 | int sw = GetScreenWidth(); 28 | int sh = GetScreenHeight(); 29 | int lw = 4 * 35 * 2; 30 | int bw = 12 * 35 * 2; 31 | int total_w = lw + bw + 2 * 2; 32 | int x = (sw - total_w) / 2; 33 | int bh = 10 * 35 * 2; 34 | int s = 2; 35 | int pad = 10 * s; 36 | int total_h = (bh + 35 * 1 + 35 * 1); 37 | while (total_h + 2 * pad > sh) { 38 | bh -= 35 * 2; 39 | total_h -= 35 * 2; 40 | } 41 | 42 | bh -= 4 * s; 43 | int y = (sh - total_h) / 2; 44 | int yy = y + 35 * 1; 45 | C.title = (Rectangle){x, y, total_w, 35 * 2}; 46 | C.modal = (Rectangle){x - pad, y - pad, total_w + 2 * pad, total_h + 2 * pad}; 47 | Rectangle lb_box = {x, yy, lw, bh}; 48 | Rectangle box = {x + lw + 2 * s + 6 * s, yy, bw - 8 * s, bh}; 49 | C.textbox_wrap = box; 50 | Rectangle box2 = box; 51 | box2.x = box.x; 52 | // box2.y = box.y + 4 * s; 53 | // box2.height = box.height - 2 * 4 * s; 54 | box2.width = box.width; 55 | int bsize = 4 * 35 * s; 56 | C.btn_close.hitbox = (Rectangle){ 57 | box.x + box.width - bsize + 2 * s, 58 | box.y + box.height + 8 * 2, 59 | bsize, 60 | 17 * 2, 61 | }; 62 | TextboxSetBox(&C.tb, box2); 63 | ListboxSetBox(&C.lb, lb_box); 64 | } 65 | 66 | void TutorialOpen(Ui* ui) { 67 | ui->window = WINDOW_TUTORIAL; 68 | C.opened_this_frame = true; 69 | C.closed = false; 70 | if (!C.inited) { 71 | C.inited = true; 72 | C.sel = 0; 73 | TextboxLoad(&C.tb); 74 | LevelOptions* co = ApiGetLevelOptions(); 75 | TextboxSetContent(&C.tb, co->help_txt[C.sel], co->help_sprites[C.sel]); 76 | ListboxLoad(&C.lb); 77 | for (int i = 0; i < 50; i++) { 78 | if (co->help_name[i] == NULL) { 79 | break; 80 | } 81 | ListboxAddRow(&C.lb, co->help_name[i]); 82 | } 83 | } 84 | } 85 | 86 | void TutorialUpdate(Ui* ui) { 87 | TutorialUpdateLayout(); 88 | if (IsKeyPressed(KEY_TAB) && !C.opened_this_frame) { 89 | ui->window = WINDOW_MAIN; 90 | return; 91 | } 92 | C.opened_this_frame = false; 93 | 94 | // Cancel button: we close without performing any action. 95 | if (BtnUpdate(&C.btn_close, ui) || IsKeyPressed(KEY_ESCAPE)) { 96 | ui->window = WINDOW_MAIN; 97 | return; 98 | } 99 | 100 | ListboxUpdate(&C.lb); 101 | TextboxUpdate(&C.tb, ui); 102 | int hit = C.lb.row_hit; 103 | if (hit >= 0 && IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && C.sel != hit) { 104 | C.sel = hit; 105 | LevelOptions* co = ApiGetLevelOptions(); 106 | TextboxSetContent(&C.tb, co->help_txt[C.sel], co->help_sprites[C.sel]); 107 | } 108 | } 109 | 110 | void TutorialDraw(Ui* ui) { 111 | int sh = GetScreenHeight(); 112 | int sw = GetScreenWidth(); 113 | Color bg = BLACK; 114 | bg.a = 150; 115 | DrawRectangle(0, 0, sw, sh, bg); 116 | BeginScissorMode(C.modal.x, C.modal.y, C.modal.width, C.modal.height); 117 | DrawDefaultTiledScreen(ui); 118 | EndScissorMode(); 119 | DrawRectangleRec(C.textbox_wrap, BLACK); 120 | DrawWidgetFrame(ui, C.textbox_wrap); 121 | TextboxDraw(&C.tb, ui); 122 | ListboxDraw(&C.lb, ui, C.sel); 123 | BtnDrawText(&C.btn_close, ui->scale, "CLOSE"); 124 | DrawWidgetFrameInv(ui, C.modal); 125 | DrawTitle(ui, C.modal, "TUTORIAL"); 126 | } 127 | -------------------------------------------------------------------------------- /src/api.h: -------------------------------------------------------------------------------- 1 | #ifndef CA_API_H 2 | #define CA_API_H 3 | #include "img.h" 4 | #include "rendering.h" 5 | #include "sim.h" 6 | #include "sprite.h" 7 | 8 | // Describes the list of all Levels and Tutorial items available. 9 | typedef struct { 10 | // Title of each Tutorial chapter. 11 | const char* help_name[50]; 12 | // Multi-line text of each tutorial chapter (rendered with 13 | // DrawTextBoxAdvanced). 14 | const char* help_txt[50]; 15 | // Images associated to each tutorial chapter. 16 | Sprite help_sprites[50][20]; 17 | // Descriptor of each Level. 18 | struct { 19 | // Index of the level. 20 | int ilevel; 21 | // Legend of each level displayed when hovering in level selection window. 22 | const char* name; 23 | // Detailed text string for the description of each level. 24 | // The strings have a special format, allowing "bold" strings and embedded 25 | // images. It is rendered by DrawTextBoxAdvanced, you can look at its doc 26 | // for more details on the format. 27 | const char* desc; 28 | // Icon sprite of the level (displayed in the selection window) 29 | Sprite icon; 30 | // Sprites associated with the desc string. 31 | Sprite sprites[20]; 32 | // Flag when level is complete 33 | bool complete; 34 | } options[60]; 35 | // Image that is loaded on the game startup 36 | char* startup_image_path; 37 | } LevelOptions; 38 | 39 | // Detailed low level description of a Level. 40 | typedef struct { 41 | // Index of the level. 42 | int ilevel; 43 | // Number of simulation external components. 44 | int num_components; 45 | // Descriptor for the pins (one for each component). 46 | PinDesc* pindesc; 47 | // External component instance for each component. 48 | ExtComp* extcomps; 49 | } LevelDesc; 50 | 51 | // Result of the call to ApiOnLevelTick 52 | typedef struct { 53 | // Whether clock has been updated. 54 | bool clock_updated; 55 | // Whether the power_on_reset is on. 56 | bool reset; 57 | // Value of the clock. 58 | int clock_value; 59 | // Count of the clock (is displayed in the screen). 60 | int clock_count; 61 | } TickResult; 62 | 63 | // Initializes things in lua. 64 | // The first script it calls is the luasrc/app.lua, the entrypoint to the lua 65 | // scripts. 66 | void ApiLoad(); 67 | 68 | // Shuts down the lua context. 69 | void ApiUnload(); 70 | 71 | // Loads a level by index. 72 | void ApiLoadLevel(int i); 73 | 74 | // Sends the start simulation event to lua. 75 | void ApiStartLevelSimulation(); 76 | 77 | // Sends the stop simulation event to lua. 78 | void ApiStopLevelSimulation(); 79 | 80 | // Sets the clock rate. 81 | void ApiSetClockTime(float clock_rate); 82 | 83 | // Asks the Level to draw to a dedicated render texture. 84 | void ApiOnLevelDraw(RenderTexture2D target, float camera_x, float camera_y, 85 | float camera_spacing); 86 | 87 | // Clock event sent to lua. 88 | // Sent whenever a clock has been updated so the componets can sync their 89 | // internal memory. 90 | void ApiLevelClock(int ilevel, int* inputs, bool reset); 91 | 92 | // Main component update entrypoint, which triggers an update component in lua. 93 | // Called from the simulator during the simulation algorithm. 94 | void ApiUpdateLevelComponent(void* ctx, int ic, int* prev_in, int* next_in, 95 | int* output); 96 | 97 | // Low-level tick event. Called in a fixed high frequency rate. 98 | // Used mostly by the clock component, but can also be used in random 99 | // components to handle things like keyboard events (mind though that it can be 100 | // called multiple times per frame). 101 | TickResult ApiOnLevelTick(float dt); 102 | 103 | // Getter for the level descriptor of the active level. (set by the ApiLoadLevel 104 | // function) 105 | LevelDesc* ApiGetLevelDesc(); 106 | 107 | // Getter for the global list of all available levels and tutorial pages. 108 | LevelOptions* ApiGetLevelOptions(); 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /src/w_about.c: -------------------------------------------------------------------------------- 1 | #include "w_about.h" 2 | 3 | #include 4 | 5 | #include "font.h" 6 | #include "stdio.h" 7 | #include "string.h" 8 | #include "tiling.h" 9 | #include "ui.h" 10 | #include "version.h" 11 | #include "widgets.h" 12 | 13 | static struct { 14 | bool inited; 15 | Textbox tb; 16 | Rectangle textbox_wrap; 17 | Rectangle title; 18 | Rectangle modal; 19 | Sprite sprites[2]; 20 | bool closed; 21 | Btn btn_close; 22 | } C = {0}; 23 | 24 | static void AboutUpdateLayout() { 25 | int sw = GetScreenWidth(); 26 | int sh = GetScreenHeight(); 27 | int bw = 12 * 35 * 2; 28 | int s = 2; 29 | int total_w = bw + 2 * 2; 30 | int x = (sw - total_w) / 2; 31 | int bh = 9 * 35 * s; 32 | int total_h = (bh + 35 * 1 + 35 * 1); 33 | int pad = 10 * s; 34 | while (total_h + 2 * pad + 10 > sh) { 35 | total_h -= 35 * s; 36 | bh -= 35 * s; 37 | } 38 | int y = (sh - total_h) / 2; 39 | int yy = y + 35 * 1; 40 | C.title = (Rectangle){x, y, total_w, 35 * 2}; 41 | C.modal = (Rectangle){x - pad, y - pad, total_w + 2 * pad, total_h + 2 * pad}; 42 | Rectangle box = {x + 2 * s, yy, bw, bh - 4 * s}; 43 | C.textbox_wrap = box; 44 | Rectangle box2 = box; 45 | int bsize = 4 * 35 * s; 46 | C.btn_close.hitbox = (Rectangle){ 47 | box.x + box.width - bsize, 48 | box.y + box.height + 8 * 2, 49 | bsize, 50 | 17 * 2, 51 | }; 52 | TextboxSetBox(&C.tb, box2); 53 | } 54 | 55 | void AboutOpen(Ui* ui) { 56 | ui->window = WINDOW_ABOUT; 57 | const char* demo_str = ""; 58 | if (ui->demo) { 59 | demo_str = "`Demo Version (max image size 256)`\n"; 60 | } 61 | char about_page[1000]; 62 | sprintf( 63 | about_page, 64 | "\n!img:0\n\n%s" CA_VERSION 65 | "\n" 66 | "A game by `lets_all_be_stupid_forever`.\n" 67 | "circuitartistgame@gmail.com\n" 68 | "\n" 69 | "Available on " 70 | "Steam.\nhttps://store.steampowered.com/app/3139580/Circuit_Artist/\n\n" 71 | "\n`Credits:`" 72 | "\n!hl" 73 | "\n" 74 | "\n`Game Framework`" 75 | "\nRaylib + C" 76 | "\nraylib.com" 77 | "\n\n" 78 | "`Font`\n" 79 | "m5x7 and m3x6 by Daniel Linssen.\n" 80 | "https://managore.itch.io/m5x7\n" 81 | "\n" 82 | "`File Dialog`\n" 83 | "Native File Dialog by Michael Labbe\n" 84 | "https://github.com/mlabbe/nativefiledialog\n\n" 85 | "`Copy-paste`\n" 86 | "Clip library by David Capello / Aseprite.\n" 87 | "https://github.com/aseprite/clip\n\n" 88 | "`Scripts`\n" 89 | "Luajit\n" 90 | "https://luajit.org/\n\n" 91 | "`Trailer Music`\n" 92 | "Music by Kevin MacLeod\n" 93 | "https://freepd.com/electronic.php\n\n" 94 | "`Classes in lua`\nclassic.lua by rxi\n" 95 | "https://github.com/rxi/classic\n\n" 96 | "`JSON in lua`\njson.lua by rxi\n" 97 | "https://github.com/rxi/json.lua", 98 | demo_str); 99 | C.closed = false; 100 | if (!C.inited) { 101 | C.inited = true; 102 | TextboxLoad(&C.tb); 103 | C.sprites[0] = (Sprite){ 104 | .tex = ui->sprites, 105 | .region = rect_logo, 106 | }; 107 | TextboxSetContent(&C.tb, about_page, C.sprites); 108 | } 109 | } 110 | 111 | void AboutUpdate(Ui* ui) { 112 | AboutUpdateLayout(); 113 | if (BtnUpdate(&C.btn_close, ui) || IsKeyPressed(KEY_ESCAPE)) { 114 | ui->window = WINDOW_MAIN; 115 | return; 116 | } 117 | TextboxUpdate(&C.tb, ui); 118 | } 119 | 120 | void AboutDraw(Ui* ui) { 121 | int sh = GetScreenHeight(); 122 | int sw = GetScreenWidth(); 123 | Color bg = BLACK; 124 | bg.a = 150; 125 | DrawRectangle(0, 0, sw, sh, bg); 126 | BeginScissorMode(C.modal.x, C.modal.y, C.modal.width, C.modal.height); 127 | DrawDefaultTiledScreen(ui); 128 | EndScissorMode(); 129 | DrawRectangleRec(C.textbox_wrap, BLACK); 130 | TextboxDraw(&C.tb, ui); 131 | DrawWidgetFrame(ui, C.textbox_wrap); 132 | BtnDrawText(&C.btn_close, ui->scale, "CLOSE"); 133 | DrawWidgetFrameInv(ui, C.modal); 134 | DrawTitle(ui, C.modal, "ABOUT"); 135 | } 136 | --------------------------------------------------------------------------------