Aloittelijalle miinaharavan toteutus on hyvä ja kohtuullisen palkitseva harjoitus.
Ohessa rakenteeltaan äärimmäisen yksinkertainen, mutta täysi miinaharava pelin toteutus kirjoitettuna Hollywoodilla. Oleelliset kohdat koodista kääntyvät helposti mille tahansa ohjelmointikielelle, joten pelin perusrunko on valmiina käytettäväksi omiin projekteihin.
lähdekoodit ja binäärit täältä
Piirtorutiinien toteutuksen olen mallintanut Infernolta, koska olen niihin jotenkin kiintynyt... ;-)
; ; Hollywood Mine Sweeper v. 0.2 ; ; Jali Heinonen ; jali.heinonen@gmail.com ; @APPICON { Ic16x16 = "icons/my16x16icon.png", Ic24x24 = "icons/my24x24icon.png", Ic32x32 = "icons/my32x32icon.png", Ic48x48 = "icons/my48x48icon.png", Ic128x128 = "icons/my128x128icon.png", Ic256x256 = "icons/my256x256icon.png", Ic512x512 = "icons/my512x512icon.png" } @DISPLAY { Title = "Hollywood Mine Sweeper v. 0.2", Width = 384, Height = 384+32, Hidden = True } ; Include game framework @INCLUDE "hgf/game.hws" @BRUSH 1, "data/tile.png", {LoadAlpha = True} @BRUSH 2, "data/button_down.png", {LoadAlpha = True} @BRUSH 3, "data/mine.png", {LoadAlpha = True} @BRUSH 4, "data/button_up.png", {LoadAlpha = True} @BRUSH 8, "data/explosion.png", {LoadAlpha = True} @BRUSH 9, "data/flag.png", {LoadAlpha = True} @BRUSH 10, "data/n0.png", {LoadAlpha = True} @BRUSH 11, "data/n1.png", {LoadAlpha = True} @BRUSH 12, "data/n2.png", {LoadAlpha = True} @BRUSH 13, "data/n3.png", {LoadAlpha = True} @BRUSH 14, "data/n4.png", {LoadAlpha = True} @BRUSH 15, "data/n5.png", {LoadAlpha = True} @BRUSH 16, "data/n6.png", {LoadAlpha = True} @BRUSH 17, "data/n7.png", {LoadAlpha = True} @BRUSH 18, "data/n8.png", {LoadAlpha = True} ; Board size, internally board is 2 tiles larger Const #BRDSZ = 12 Const #BRDSZI = #BRDSZ + 2 ; Board tile states Const #UNSELECTED = 1 Const #SELECTED = 2 Const #MARKED = 4 ; Skill level Const #EASY = 25 Function init_board() Local i, j Local row, column finished = False score = 0 mines = 0 ; initialize board board = {} For i = 0 To #BRDSZI - 1 board[i] = {} For j = 0 To #BRDSZI - 1 board[i][j] = {} board[i][j].mine = 0 board[i][j].state = #UNSELECTED Next Next ;place mines For i = 0 To #EASY - 1 j = Rnd(#BRDSZ*#BRDSZ) row = Int((j/#BRDSZ)+1) column = Int((j%#BRDSZ)+1) If board[row][column].mine = 0 board[row][column].mine = 1 mines = mines + 1 EndIf Next EndFunction Function display_square(i, j, img) game.buffer:drawImg(img.r:addpt({ x = (j-1) * img.r:dx(), y = i * img.r:dy() }), img, Point:new()) EndFunction Function display_mines() For Local i = 1 To #BRDSZ For Local j = 1 To #BRDSZ If board[i][j].mine = 1 Then display_square(i, j, img3) Next Next EndFunction Function display_score() Local blankr = Rect:new({ min = { x = 140, y = 2}, max = { x = 384, y = 30 } }) Local score_text$ = "score: " ..PadNum(score, 3) Local percentage$ = PadNum(Int(score/((#BRDSZ*#BRDSZ)-mines)*100), 3) .."%" SetFont(#MONOSPACE, 20) SetFontStyle(#BOLD|#ANTIALIAS) SetFontColor(#BLACK) SetFillStyle(#FILLCOLOR) game.buffer:drawRect(blankr, $CCCCCC) game.buffer:drawText({ x = 150, y = 6 }, percentage$) game.buffer:drawText({ x = 250, y = 6 }, score_text$) EndFunction Function board_check(i, j) If board[i][j].mine = 1 Then Return(-1) If board[i][j].state & (#SELECTED|#MARKED) Then Return(-2) Local c = 0 For Local row = i-1 To i+1 For Local col = j-1 To j+1 If board[row][col].mine = 1 Then c = c + 1 Next Next Return(c) EndFunction Function mark_square(i, j) Switch board[i][j].state Case #UNSELECTED: board[i][j].state = #MARKED display_square(i, j, img7) Case #MARKED board[i][j].state = #UNSELECTED display_square(i, j, img6) EndSwitch EndFunction Function display_zeros(i, j) For Local row = i-1 To i+1 For Local col = j-1 To j+1 If row < 1 Or row > #BRDSZ Or col < 1 Or col > #BRDSZ Then Continue Local tile = board_check(row, col) If tile = 0 score = score + 1 board[row][col].state = #SELECTED display_square(row, col, images[tile]) display_zeros(row, col) Else If tile >= 1 And tile <= 8 board[row][col].state = #SELECTED display_square(row, col, images[tile]) score = score + 1 EndIf EndIf Next Next EndFunction Function init_gui() MakeButton(1000, #SIMPLEBUTTON, 10, 2, img4.r:dx(), img4.r:dy(), { OnMouseDown = menu_handler, OnMouseUp = menu_handler, OnMouseOver = menu_handler, OnMouseOut = menu_handler }) Local num_button = 1 Local tiler = Rect:new({min = { x = 0, y = 0 }, max = { x = 32, y = 32 }}) Local pos = Point:new({ x = 0, y = 32 }) ; Create buttons For Local i = 1 To #BRDSZ For Local j = 1 To #BRDSZ MakeButton(num_button, #SIMPLEBUTTON, tiler:addpt(pos).min.x, tiler:addpt(pos).min.y, tiler:addpt(pos):dx(), tiler:addpt(pos):dy(), { OnMouseUp = board_handler, OnRightMouseUp = board_handler, OnMouseOver = board_handler, OnMouseOut = board_handler }) pos.x = pos.x + tiler:dx() num_button = num_button + 1 Next pos.x = 0 pos.y = pos.y + tiler:dy() Next EndFunction Function draw_board() Local panelr = Rect:new({ min = { x = 0, y = 0 }, max = { x = 384, y = 32 } }) SetFillStyle(#FILLCOLOR) game.buffer:drawRect(panelr, $CCCCCC) game.buffer:drawImg(img4.r:addpt({ x = 10, y = 2 }), img4, Point:new()) display_score() Local pos = Point:new({ x = 0, y = 32 }) ; Draw button tiles For Local i = 1 To #BRDSZ For Local j = 1 To #BRDSZ game.buffer:drawImg(img1.r:addpt(pos), img1, Point:new()) pos.x = pos.x + img1.r:dx() Next pos.x = 0 pos.y = pos.y + img1.r:dy() Next EndFunction Function menu_handler(msg) Switch msg.action Case "OnMouseDown": game.buffer:drawImg(img2.r:addpt({ x = 10, y = 2 }), img2, Point:new()) Case "OnMouseUp": finished = False map = init_board() draw_board() game.buffer:drawImg(img10.r:addpt({ x = 10, y = 2 }), img10, Point:new()) Case "OnMouseOver": game.buffer:drawImg(img10.r:addpt({ x = 10, y = 2 }), img10, Point:new()) Case "OnMouseOut": game.buffer:drawImg(img4.r:addpt({ x = 10, y = 2 }), img4, Point:new()) EndSwitch EndFunction Function board_handler(msg) If finished Then Return Local by = Int(msg.id / #BRDSZ) Local bx = msg.id % #BRDSZ If bx = 0 Then bx = #BRDSZ Else by = by + 1 Switch msg.action Case "OnMouseUp": ; Handle game input Local i = board_check(by,bx) If i = -1 And board[by][bx].state <> #MARKED display_mines() display_square(by, bx, img8) finished = True ElseIf i >= 0 And i <= 8 score = score + 1 board[by][bx].state = #SELECTED display_square(by, bx, images[i]) If i = 0 Then display_zeros(by, bx) display_score() If score + mines = #BRDSZ*#BRDSZ display_mines() finished = True Endif EndIf Case "OnRightMouseUp" mark_square(by, bx) Case "OnMouseOver": If board[by][bx].state = #SELECTED Then Break If board[by][bx].state = #MARKED Then display_square(by, bx, img7) Else display_square(by, bx, img6) Case "OnMouseOut": If board[by][bx].state = #SELECTED Then Break If board[by][bx].state = #MARKED Then display_square(by, bx, img9) Else display_square(by, bx, img1) EndSwitch EndFunction ; Create tinted images for selection highlighting CopyBrush(1, 6) TintBrush(6, #WHITE, 128) CopyBrush(9, 7) TintBrush(7, #WHITE, 128) ; Create tinted image for menu GUI button highlighting CopyBrush(4, 19) TintBrush(19, #WHITE, 128) ; Create game images img1 = Image:new(1) img2 = Image:new(2) img3 = Image:new(3) img4 = Image:new(4) img6 = Image:new(6) img7 = Image:new(7) img8 = Image:new(8) img9 = Image:new(9) img10 = Image:new(19) images = {} images[0] = Image:new(10) images[1] = Image:new(11) images[2] = Image:new(12) images[3] = Image:new(13) images[4] = Image:new(14) images[5] = Image:new(15) images[6] = Image:new(16) images[7] = Image:new(17) images[8] = Image:new(18) ; Init game, draw the board and GUI init_gui() init_board() game.buffer:init() draw_board() OpenDisplay(1) ; Process events Repeat WaitEvent Forever
Jos oletetaan tämän koodivinkin kohdeyleisönä olevan aloittelijat, jotka eivät lähtökohtaisesti osaa itse miinaharavaa toteuttaa, niin se on melko huono. Jos on tarkoituksena opettaa, niin täytyisi kertoa _miksi_ asioita tehdään niin kuin tehdään. Kommentointi ei tuossa ole sen mukaista.
Loppupuolella kaivattaisiin myös lisää silmukkoja ja esim. init board funktiossa alustus ja miinojen asettelu olisi helposti yhdistettävissä lyhyemmäksi koodiksi. En tätä testannut, mutta se kokonaisuutena näyttää siltä, että sait sen juuri kasaan ja pullautit sitten suoraan tänne näkyville. Ei siinä mitään pahaa sinänsä, on aina hienoa saada jotain aikaan, mutta opetusmateriaaliksi tästä ei mielestäni ole ilman monen osan uudelleenkirjoitusta tai -jäsentelyä.
mietinvaan kirjoitti:
Loppupuolella kaivattaisiin myös lisää silmukkoja ja esim. init board funktiossa alustus ja miinojen asettelu olisi helposti yhdistettävissä lyhyemmäksi koodiksi.
Mitähän höpiset? Tuossa init_board() funktiossa nollataan ensin pelilaudan tila ja arvotaan sitten sopiva määrä miinoja pelialueelle. Mitähän meinasit tuossa yhdistellä lyhyemmäksi koodiksi?
Jos tietää miten Hollywood-ohjelma prosessoi tapahtumia, niin ohjelman kulkua on kuitenkin aika helppo seurata. En hirveästi näe järkeä kommentoida jokaista riviä lyhkäisissä funktioissa, joiden nimet sinällään jo kertovat mitä tehdään.
Minusta tämä on muuten kohtuullisen selvä vinkki, mutta BRDSZ(I) on epäselvä nimi ja myös kuvien nimet kannattaisi vaihtaa järkeviksi eli kuvan sisältöä vastaaviksi.
Miinaharavan PL/I:llä kirjoiteltu Windows versio löytyy täältä.
Aihe on jo aika vanha, joten et voi enää vastata siihen.