9fans.net/go@v0.0.5/games/spacewar/spacewar.go (about) 1 // Copyright (c) 1996 Barry Silverman, Brian Silverman, Vadim Gerasimov. 2 // Portions Copyright (c) 2009 The Go Authors. 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 // Spacewar is the original PDP-1 Spacewar video game, 23 // running in a PDP-1 emulator. 24 package main // import "9fans.net/go/games/spacewar" 25 26 import ( 27 "bytes" 28 "image" 29 "log" 30 "os" 31 "time" 32 33 "9fans.net/go/draw" 34 "9fans.net/go/games/spacewar/pdp1" 35 ) 36 37 func main() { 38 d, err := draw.Init(nil, "", "spacewar", "512x512") 39 if err != nil { 40 log.Fatal(err) 41 } 42 43 var m SpacewarPDP1 44 m.Init(d) 45 m.PC = 4 46 f := bytes.NewBuffer([]byte(spacewarCode)) 47 if err = m.Load(f); err != nil { 48 log.Fatalf("loading %s: %s", "spacewar.lst", err) 49 } 50 for err == nil { 51 //fmt.Printf("step PC=%06o ", m.PC); 52 //fmt.Printf("inst=%06o AC=%06o IO=%06o OV=%o\n", 53 // m.Mem[m.PC], m.AC, m.IO, m.OV); 54 err = m.Step() 55 } 56 log.Fatalf("step: %s", err) 57 } 58 59 func quitter(c <-chan bool) { 60 <-c 61 os.Exit(0) 62 } 63 64 // A SpacewarPDP1 is a PDP-1 machine configured to run Spacewar! 65 // It responds to traps by drawing on the display, and it flushes the 66 // display and pauses every second time the program counter reaches 67 // instruction 02051. 68 type SpacewarPDP1 struct { 69 pdp1.M 70 nframe int 71 frameTime time.Time 72 disp *draw.Display 73 dx, dy int 74 screen *draw.Image 75 ctl pdp1.Word 76 kc *draw.Keyboardctl 77 mc *draw.Mousectl 78 cmap []*draw.Image 79 pix [][]uint8 80 oldpix [][]uint8 81 } 82 83 func min(a, b int) int { 84 if a < b { 85 return a 86 } 87 return b 88 } 89 90 func (m *SpacewarPDP1) Init(d *draw.Display) { 91 m.disp = d 92 m.mc = d.InitMouse() 93 m.kc = d.InitKeyboard() 94 m.screen = d.ScreenImage 95 m.dx = m.screen.R.Dx() 96 m.dy = m.screen.R.Dy() 97 m.pix = make([][]uint8, m.dy) 98 m.oldpix = make([][]uint8, m.dy) 99 for i := range m.pix { 100 m.pix[i] = make([]uint8, m.dx) 101 m.oldpix[i] = make([]uint8, m.dx) 102 } 103 m.cmap = make([]*draw.Image, 256) 104 for i := range m.cmap { 105 var r, g, b draw.Color 106 r = draw.Color(min(0, 255)) 107 g = draw.Color(min(i*2, 255)) 108 b = draw.Color(min(0, 255)) 109 m.cmap[i], _ = d.AllocImage(image.Rect(0, 0, 1, 1), d.ScreenImage.Pix, true, r<<24|g<<16|b<<8|0xff) 110 } 111 m.screen.Draw(m.screen.R, d.Black, nil, draw.ZP) 112 } 113 114 const ( 115 frameDelay = 56 * time.Millisecond 116 ) 117 118 var ctlBits = [...]pdp1.Word{ 119 'f': 0000001, 120 'd': 0000002, 121 'a': 0000004, 122 's': 0000010, 123 '\'': 0040000, 124 ';': 0100000, 125 'k': 0200000, 126 'l': 0400000, 127 } 128 129 func (m *SpacewarPDP1) Step() error { 130 if m.PC == 02051 { 131 m.pollInput() 132 m.nframe++ 133 if m.nframe&1 == 0 { 134 m.flush() 135 t := time.Now() 136 if t.After(m.frameTime.Add(3 * frameDelay)) { 137 m.frameTime = t 138 } else { 139 m.frameTime = m.frameTime.Add(frameDelay) 140 for t.Before(m.frameTime) { 141 time.Sleep(m.frameTime.Sub(t)) 142 t = time.Now() 143 } 144 } 145 } 146 } 147 return m.M.Step(m) 148 } 149 150 func (m *SpacewarPDP1) Trap(y pdp1.Word) { 151 switch y & 077 { 152 case 7: 153 x := int(m.AC+0400000) & 0777777 154 y := int(m.IO+0400000) & 0777777 155 x = x * m.dx / 0777777 156 y = y * m.dy / 0777777 157 if 0 <= x && x < m.dx && 0 <= y && y < m.dy { 158 n := uint8(min(int(m.pix[y][x])+128, 255)) 159 m.pix[y][x] = n 160 } 161 case 011: 162 m.IO = m.ctl 163 } 164 } 165 166 func (m *SpacewarPDP1) flush() { 167 // Update screen image; simulate phosphor decay. 168 for y := 0; y < m.dy; y++ { 169 for x := 0; x < m.dx; x++ { 170 if m.oldpix[y][x] != m.pix[y][x] { 171 r := image.Rect(x, y, x+1, y+1) 172 m.screen.Draw(r, m.cmap[m.pix[y][x]], nil, draw.ZP) 173 m.oldpix[y][x] = m.pix[y][x] 174 } 175 m.pix[y][x] >>= 1 176 } 177 } 178 m.disp.Flush() 179 } 180 181 func (m *SpacewarPDP1) pollInput() { 182 for { 183 select { 184 case ch := <-m.kc.C: 185 if 0 <= ch && ch < rune(len(ctlBits)) { 186 m.ctl |= ctlBits[ch] 187 } 188 if 0 <= -ch && -ch < rune(len(ctlBits)) { 189 m.ctl &^= ctlBits[-ch] 190 } 191 default: 192 return 193 } 194 } 195 }